diff --git a/.aiderignore b/.aiderignore new file mode 100644 index 000000000000..b80c45944684 --- /dev/null +++ b/.aiderignore @@ -0,0 +1,21 @@ +# Build artifacts +buildroot/ +*.o +*.a +*.so +*.dylib +*.dll +*.exe + +# Web assets +*.min.js +*.min.css + +# Generated files +__pycache__/ +*.pyc +.DS_Store + +# IDE files +.vscode/ +.idea/ diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 54627d462b68..96d6af193213 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -38,14 +38,14 @@ "platformio.platformio-ide", "marlinfirmware.auto-build", "editorconfig.editorconfig" - ], + ] // Use 'forwardPorts' to make a list of ports inside the container available locally. - // "forwardPorts": [], + // , "forwardPorts": [] // Use 'postCreateCommand' to run commands after the container is created. - // "postCreateCommand": "pip3 install --user -r requirements.txt", + // , "postCreateCommand": "pip3 install --user -r requirements.txt" // Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. - // "remoteUser": "vscode" + // , "remoteUser": "vscode" } diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 1fe68966fbba..a22a1bb82e63 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -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 diff --git a/.github/code_of_conduct.md b/.github/code_of_conduct.md index 5fd9e0c8eeb5..f60db7ba3952 100644 --- a/.github/code_of_conduct.md +++ b/.github/code_of_conduct.md @@ -8,19 +8,19 @@ In the interest of fostering an open and welcoming environment, we as contributo Examples of behavior that contributes to creating a positive environment include: -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members Examples of unacceptable behavior by participants include: -* The use of sexualized language or imagery and unwelcome sexual attention or advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a professional setting +- The use of sexualized language or imagery and unwelcome sexual attention or advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities diff --git a/.github/contributing.md b/.github/contributing.md index c9b31998e99d..0648b2013555 100644 --- a/.github/contributing.md +++ b/.github/contributing.md @@ -11,18 +11,21 @@ The following is a set of guidelines for contributing to Marlin, hosted by the [ [I don't want to read this whole thing, I just have a question!!!](#i-dont-want-to-read-this-whole-thing-i-just-have-a-question) [How Can I Contribute?](#how-can-i-contribute) - * [Reporting Bugs](#reporting-bugs) - * [Suggesting Features or Changes](#suggesting-features-or-changes) - * [Your First Code Contribution](#your-first-code-contribution) - * [Pull Requests](#pull-requests) + +- [Reporting Bugs](#reporting-bugs) +- [Suggesting Features or Changes](#suggesting-features-or-changes) +- [Your First Code Contribution](#your-first-code-contribution) +- [Pull Requests](#pull-requests) [Styleguides](#styleguides) - * [Git Commit Messages](#git-commit-messages) - * [C++ Coding Standards](#c++-coding-standards) - * [Documentation Styleguide](#documentation) + +- [Git Commit Messages](#git-commit-messages) +- [C++ Coding Standards](#c++-coding-standards) +- [Documentation Styleguide](#documentation) [Additional Notes](#additional-notes) - * [Issue and Pull Request Labels](#issue-and-pull-request-labels) + +- [Issue and Pull Request Labels](#issue-and-pull-request-labels) ## Code of Conduct @@ -31,6 +34,7 @@ This project and everyone participating in it is governed by the [Marlin Code of ## I don't want to read this whole thing I just have a question!!! > [!NOTE] +> > Please don't file an issue to ask a question. You'll get faster results by using the resources below. We have a Message Board and a Facebook group where our knowledgable user community can provide helpful advice if you have questions. @@ -43,10 +47,10 @@ 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. -* Even though our Discord is pretty active, it may take a while for community members to respond — please be patient! -* Use the `#general` channel for general questions or discussion about Marlin. -* Other channels exist for certain topics or are limited to Patrons. Check the channel list. +- Use the link https://discord.com/servers/marlin-firmware-461605380783472640 to join up as a General User. +- Even though our Discord is pretty active, it may take a while for community members to respond — please be patient! +- Use the `#general` channel for general questions or discussion about Marlin. +- Other channels exist for certain topics or are limited to Patrons. Check the channel list. ## How Can I Contribute? @@ -57,6 +61,7 @@ This section guides you through submitting a Bug Report for Marlin. Following th Before creating a Bug Report, please test the "nightly" development branch, as you might find out that you don't need to create one. When you are creating a Bug Report, please [include as many details as possible](#how-do-i-submit-a-good-bug-report). Fill out [the required template](ISSUE_TEMPLATE/bug_report.yml), the information it asks for helps us resolve issues faster. > [!NOTE] +> > Regressions can happen. If you find a **Closed** issue that seems like your issue, go ahead and open a new issue and include a link to the original issue in the body of your new one. All you need to create a link is the issue number, preceded by #. For example, #8888. #### How Do I Submit A (Good) Bug Report? @@ -65,29 +70,29 @@ Bugs are tracked as [GitHub issues](https://guides.github.com/features/issues/). Explain the problem and include additional details to help maintainers reproduce the problem: -* **Use a clear and descriptive title** for the issue to identify the problem. -* **Describe the exact steps which reproduce the problem** in as many details as possible. For example, start by explaining how you started Marlin, e.g. which command exactly you used in the terminal, or how you started Marlin otherwise. When listing steps, **don't just say what you did, but explain how you did it**. For example, if you moved the cursor to the end of a line, explain if you used the mouse, or a keyboard shortcut or an Marlin command, and if so which one? -* **Provide specific examples to demonstrate the steps**. Include links to files or GitHub projects, or copy/pasteable snippets, which you use in those examples. If you're providing snippets or log output in the issue, use [Markdown code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines). -* **Describe the behavior you observed after following the steps** and point out what exactly is the problem with that behavior. -* **Explain which behavior you expected to see instead and why.** -* **Include detailed log output** especially for probing and leveling. See below for usage of `DEBUG_LEVELING_FEATURE`. -* **Include screenshots, links to videos, etc.** which clearly demonstrate the problem. -* **Include G-code** (if relevant) that reliably causes the problem to show itself. -* **If the problem wasn't triggered by a specific action**, describe what you were doing before the problem happened and share more information using the guidelines below. +- **Use a clear and descriptive title** for the issue to identify the problem. +- **Describe the exact steps which reproduce the problem** in as many details as possible. For example, start by explaining how you started Marlin, e.g. which command exactly you used in the terminal, or how you started Marlin otherwise. When listing steps, **don't just say what you did, but explain how you did it**. For example, if you moved the cursor to the end of a line, explain if you used the mouse, or a keyboard shortcut or an Marlin command, and if so which one? +- **Provide specific examples to demonstrate the steps**. Include links to files or GitHub projects, or copy/pasteable snippets, which you use in those examples. If you're providing snippets or log output in the issue, use [Markdown code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines). +- **Describe the behavior you observed after following the steps** and point out what exactly is the problem with that behavior. +- **Explain which behavior you expected to see instead and why.** +- **Include detailed log output** especially for probing and leveling. See below for usage of `DEBUG_LEVELING_FEATURE`. +- **Include screenshots, links to videos, etc.** which clearly demonstrate the problem. +- **Include G-code** (if relevant) that reliably causes the problem to show itself. +- **If the problem wasn't triggered by a specific action**, describe what you were doing before the problem happened and share more information using the guidelines below. Provide more context: -* **Can you reproduce the problem with a minimum of options enabled?** -* **Did the problem start happening recently** (e.g. after updating to a new version of Marlin) or was this always a problem? -* If the problem started happening recently, **can you reproduce the problem in an older version of Marlin?** What's the most recent version in which the problem doesn't happen? You can download older versions of Marlin from [the releases page](https://github.com/MarlinFirmware/Marlin/releases). -* **Can you reliably reproduce the issue?** If not, provide details about how often the problem happens and under which conditions it normally happens. +- **Can you reproduce the problem with a minimum of options enabled?** +- **Did the problem start happening recently** (e.g. after updating to a new version of Marlin) or was this always a problem? +- If the problem started happening recently, **can you reproduce the problem in an older version of Marlin?** What's the most recent version in which the problem doesn't happen? You can download older versions of Marlin from [the releases page](https://github.com/MarlinFirmware/Marlin/releases). +- **Can you reliably reproduce the issue?** If not, provide details about how often the problem happens and under which conditions it normally happens. Include details about your configuration and environment: -* **Which version of Marlin are you using?** Marlin's exact version and build date can be seen in the startup message when a host connects to Marlin, or in the LCD Info menu (if enabled). -* **What kind of 3D Printer and electronics are you using**? -* **What kind of add-ons (probe, filament sensor) do you have**? -* **Include your Configuration files.** Make a ZIP file containing `Configuration.h` and `Configuration_adv.h` and drop it on your reply. +- **Which version of Marlin are you using?** Marlin's exact version and build date can be seen in the startup message when a host connects to Marlin, or in the LCD Info menu (if enabled). +- **What kind of 3D Printer and electronics are you using**? +- **What kind of add-ons (probe, filament sensor) do you have**? +- **Include your Configuration files.** Make a ZIP file containing `Configuration.h` and `Configuration_adv.h` and drop it on your reply. ### Suggesting Features or Changes @@ -97,52 +102,52 @@ Before creating a suggestion, please check [this list](https://github.com/Marlin #### Before Submitting a Feature Request -* **Check the [Marlin website](https://marlinfw.org/)** for tips — you might discover that the feature is already included. Most importantly, check if you're using [the latest version of Marlin](https://github.com/MarlinFirmware/Marlin/releases) and if you can get the desired behavior by changing [Marlin's config settings](https://marlinfw.org/docs/configuration/configuration.html). -* **Perform a [cursory search](https://github.com/MarlinFirmware/Marlin/issues?q=is%3Aopen+is%3Aissue+label%3A%22T%3A+Feature+Request%22)** to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one. +- **Check the [Marlin website](https://marlinfw.org/)** for tips — you might discover that the feature is already included. Most importantly, check if you're using [the latest version of Marlin](https://github.com/MarlinFirmware/Marlin/releases) and if you can get the desired behavior by changing [Marlin's config settings](https://marlinfw.org/docs/configuration/configuration.html). +- **Perform a [cursory search](https://github.com/MarlinFirmware/Marlin/issues?q=is%3Aopen+is%3Aissue+label%3A%22T%3A+Feature+Request%22)** to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one. #### How Do I Submit A (Good) Feature Request? Feature Requests are tracked as [GitHub issues](https://guides.github.com/features/issues/). Please follow these guidelines in your request: -* **Use a clear and descriptive title** for the issue to identify the suggestion. -* **Provide a step-by-step description of the requested feature** in as much detail as possible. -* **Provide specific examples to demonstrate the steps**. -* **Describe the current behavior** and **explain which behavior you expected to see instead** and why. -* **Include screenshots and links to videos** which demonstrate the feature or point out the part of Marlin to which the request is related. -* **Explain why this feature would be useful** to most Marlin users. -* **Name other firmwares that have this feature, if any.** +- **Use a clear and descriptive title** for the issue to identify the suggestion. +- **Provide a step-by-step description of the requested feature** in as much detail as possible. +- **Provide specific examples to demonstrate the steps**. +- **Describe the current behavior** and **explain which behavior you expected to see instead** and why. +- **Include screenshots and links to videos** which demonstrate the feature or point out the part of Marlin to which the request is related. +- **Explain why this feature would be useful** to most Marlin users. +- **Name other firmwares that have this feature, if any.** ### Your First Code Contribution Unsure where to begin contributing to Marlin? You can start by looking through these `good-first-issue` and `help-wanted` issues: -* [Beginner issues][good-first-issue] - issues which should only require a few lines of code, and a test or two. -* [Help Wanted issues][help-wanted] - issues which should be a bit more involved than `beginner` issues. +- [Beginner issues][good-first-issue] - issues which should only require a few lines of code, and a test or two. +- [Help Wanted issues][help-wanted] - issues which should be a bit more involved than `beginner` issues. ### Pull Requests -Pull Requests should always be targeted to working branches (e.g., `bugfix-2.1.x` and/or `bugfix-1.1.x`) and never to release branches (e.g., `2.0.x` and/or `1.1.x`). If this is your first Pull Request, please read our [Guide to Pull Requests](https://marlinfw.org/docs/development/getting_started_pull_requests.html) and Github's [Pull Request](https://help.github.com/articles/creating-a-pull-request/) documentation. +Pull Requests should always be targeted to working branches (e.g., `bugfix-2.1.x` and/or `bugfix-1.1.x`) and never to release branches (e.g., `2.0.x` and/or `1.1.x`). If this is your first Pull Request, please read our [Guide to Pull Requests](https://marlinfw.org/docs/development/getting_started_pull_requests.html) and GitHub's [Pull Request](https://help.github.com/articles/creating-a-pull-request/) documentation. -* Fill in [the required template](pull_request_template.md). -* Don't include issue numbers in the PR title. -* Include pictures, diagrams, and links to videos in your Pull Request to demonstrate your changes, if needed. -* Follow the [Coding Standards](https://marlinfw.org/docs/development/coding_standards.html) posted on our website. -* Document new code with clear and concise comments. -* End all files with a newline. +- Fill in [the required template](pull_request_template.md). +- Don't include issue numbers in the PR title. +- Include pictures, diagrams, and links to videos in your Pull Request to demonstrate your changes, if needed. +- Follow the [Coding Standards](https://marlinfw.org/docs/development/coding_standards.html) posted on our website. +- Document new code with clear and concise comments. +- End all files with a newline. ## Styleguides ### Git Commit Messages -* Use the present tense ("Add feature" not "Added feature"). -* Use the imperative mood ("Move cursor to..." not "Moves cursor to..."). -* Limit the first line to 72 characters or fewer. -* Reference issues and Pull Requests liberally after the first line. +- Use the present tense ("Add feature" not "Added feature"). +- Use the imperative mood ("Move cursor to..." not "Moves cursor to..."). +- Limit the first line to 72 characters or fewer. +- Reference issues and Pull Requests liberally after the first line. ### C++ Coding Standards -* Please read and follow the [Coding Standards](https://marlinfw.org/docs/development/coding_standards.html) posted on our website. Failure to follow these guidelines will delay evaluation and acceptance of Pull Requests. +- Please read and follow the [Coding Standards](https://marlinfw.org/docs/development/coding_standards.html) posted on our website. Failure to follow these guidelines will delay evaluation and acceptance of Pull Requests. ### Documentation -* Guidelines for documentation are still under development. In-general, be clear, concise, and to-the-point. +- Guidelines for documentation are still under development. In-general, be clear, concise, and to-the-point. diff --git a/.github/lock.yml b/.github/lock.yml deleted file mode 100644 index c5ceff66b0a7..000000000000 --- a/.github/lock.yml +++ /dev/null @@ -1,40 +0,0 @@ -# -# Configuration for Lock Threads - https://github.com/dessant/lock-threads-app -# - -# Number of days of inactivity before a closed issue or pull request is locked -daysUntilLock: 60 - -# Skip issues and pull requests created before a given timestamp. Timestamp must -# follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable -skipCreatedBefore: false - -# Issues and pull requests with these labels will be ignored. Set to `[]` to disable -exemptLabels: [ 'no-locking' ] - -# Label to add before locking, such as `outdated`. Set to `false` to disable -lockLabel: false - -# Comment to post before locking. Set to `false` to disable -lockComment: > - This thread has been automatically locked since there has not been - any recent activity after it was closed. Please open a new issue for - related bugs. - -# Assign `resolved` as the reason for locking. Set to `false` to disable -setLockReason: true - -# Limit to only `issues` or `pulls` -# only: issues - -# Optionally, specify configuration settings just for `issues` or `pulls` -# issues: -# exemptLabels: -# - help-wanted -# lockLabel: outdated - -# pulls: -# daysUntilLock: 30 - -# Repository to extend settings from -# _extends: repo diff --git a/.github/workflows/auto-label.yml b/.github/workflows/auto-label.yml index c69e6c4fade9..3cbf110aa020 100644 --- a/.github/workflows/auto-label.yml +++ b/.github/workflows/auto-label.yml @@ -14,7 +14,7 @@ jobs: autolabel: name: Auto Label if: github.repository == 'MarlinFirmware/Marlin' - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Auto Label for [BUG] uses: actions/github-script@v7 diff --git a/.github/workflows/bump-date.yml b/.github/workflows/bump-date.yml index a08eeea561d7..cb27ef199e36 100644 --- a/.github/workflows/bump-date.yml +++ b/.github/workflows/bump-date.yml @@ -14,12 +14,12 @@ jobs: name: Bump Distribution Date if: github.repository == 'MarlinFirmware/Marlin' - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Check out bugfix-2.0.x - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: bugfix-2.0.x @@ -39,7 +39,7 @@ jobs: exit 0 - name: Check out bugfix-2.1.x - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: bugfix-2.1.x diff --git a/.github/workflows/check-pr.yml b/.github/workflows/check-pr.yml index 2b15067f5156..a28b7fdd620f 100644 --- a/.github/workflows/check-pr.yml +++ b/.github/workflows/check-pr.yml @@ -18,7 +18,7 @@ jobs: name: PR Bad Target if: github.repository == 'MarlinFirmware/Marlin' - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - uses: superbrothers/close-pull-request@v3 diff --git a/.github/workflows/test-builds.yml b/.github/workflows/ci-build-tests.yml similarity index 72% rename from .github/workflows/test-builds.yml rename to .github/workflows/ci-build-tests.yml index 7c62b5af6c54..5d53536a274d 100644 --- a/.github/workflows/test-builds.yml +++ b/.github/workflows/ci-build-tests.yml @@ -1,9 +1,9 @@ # -# test-builds.yml +# ci-build-tests.yml # Do test builds to catch compile errors # -name: CI +name: CI - Build Tests on: pull_request: @@ -14,30 +14,43 @@ on: - config/** - data/** - docs/** + - test/** + - Marlin/tests/** - '**/*.md' push: branches: - bugfix-2.1.x - 2.1.x + - release-* paths-ignore: - config/** - data/** - docs/** + - test/** + - Marlin/tests/** - '**/*.md' jobs: test_builds: - name: Run All Tests + name: Build Test if: github.repository == 'MarlinFirmware/Marlin' - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 + + env: + CONFIG_BRANCH: ${{ github.base_ref || github.ref_name }} strategy: + fail-fast: true matrix: test-platform: + # RP2040 + - SKR_Pico + # Native - linux_native + - simulator_linux_release # AVR - mega2560 @@ -45,7 +58,7 @@ jobs: - at90usb1286_dfu # AVR Extended - - FYSETC_F6 + - mega2560ext - melzi_optiboot - rambo - sanguino1284p @@ -100,11 +113,11 @@ jobs: # STM32F4 - ARMED - - BIGTREE_BTT002 - - BIGTREE_GTR_V1_0 - - BIGTREE_SKR_PRO + - BTT_BTT002 + - BTT_GTR_V1_0 + - BTT_SKR_PRO - FLYF407ZG - - FYSETC_S6 + - STM32F446VE_fysetc - LERDGEK - LERDGEX - mks_robin_pro2 @@ -142,6 +155,12 @@ jobs: # HC32 - HC32F460C_aquila_101 + # GD32F3 + - GD32F303RE_creality_mfl + + # GD32F1 + - GD32F103RC_aquila_mfl + # LPC176x - Lengthy tests - LPC1768 - LPC1769 @@ -152,21 +171,26 @@ jobs: uses: actions/checkout@v4 - name: Cache pip - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} + key: ${{ runner.os }}-pip-build-v1 restore-keys: | - ${{ runner.os }}-pip- + ${{ runner.os }}-pip-build- - name: Cache PlatformIO - uses: actions/cache@v3 + uses: actions/cache@v4 with: - path: ~/.platformio - key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }} + path: | + ~/.platformio + .pio/build + .pio/libdeps + key: ${{ runner.os }}-pio-build-v1 + restore-keys: | + ${{ runner.os }}-pio-build- - name: Select Python 3.9 - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.9' architecture: 'x64' @@ -177,6 +201,14 @@ jobs: pio upgrade --dev pio pkg update --global + - name: Install Simulator dependencies + run: | + sudo apt-get update + 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 }} diff --git a/.github/workflows/ci-unit-tests.yml b/.github/workflows/ci-unit-tests.yml new file mode 100644 index 000000000000..30af812dff56 --- /dev/null +++ b/.github/workflows/ci-unit-tests.yml @@ -0,0 +1,78 @@ +# +# ci-unit-tests.yml +# Build and execute unit tests to catch functional issues in code +# + +name: CI - Unit Tests + +on: + pull_request: + branches: + - bugfix-2.1.x + # Cannot be enabled on 2.1.x until it contains the unit test framework + #- 2.1.x + paths-ignore: + - config/** + - data/** + - docs/** + - '**/*.md' + push: + branches: + - bugfix-2.1.x + # Cannot be enabled on 2.1.x until it contains the unit test framework + #- 2.1.x + paths-ignore: + - config/** + - data/** + - docs/** + - '**/*.md' + +jobs: + # This runs all unit tests as a single job. While it should be possible to break this up into + # multiple jobs, they currently run quickly and finish long before the compilation tests. + run_unit_tests: + name: Unit Test + # These tests will only be able to run on the bugfix-2.1.x branch, until the next release + # pulls them into additional branches. + if: github.repository == 'MarlinFirmware/Marlin' + + runs-on: ubuntu-22.04 + + steps: + - name: Check out the PR + uses: actions/checkout@v4 + + - name: Cache pip + uses: actions/cache@v4 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-unit-v1 + restore-keys: | + ${{ runner.os }}-pip-unit- + + - name: Cache PlatformIO + uses: actions/cache@v4 + with: + path: | + ~/.platformio + .pio/build + .pio/libdeps + key: ${{ runner.os }}-pio-tests-v1 + restore-keys: | + ${{ runner.os }}-pio-tests- + + - name: Select Python 3.9 + uses: actions/setup-python@v5 + with: + python-version: '3.9' + architecture: 'x64' + + - name: Install PlatformIO + run: | + pip install -U platformio + pio upgrade --dev + pio pkg update --global + + - name: Run All Unit Tests + run: | + make unit-test-all-local diff --git a/.github/workflows/ci-validate-boards.yml b/.github/workflows/ci-validate-boards.yml new file mode 100644 index 000000000000..aa6d284f064b --- /dev/null +++ b/.github/workflows/ci-validate-boards.yml @@ -0,0 +1,48 @@ +# +# ci-validate-boards.yml +# Validate boards.h to make sure it's all set up correctly +# + +name: CI - Validate boards.h + +# We can do the on: section as two items, one for pull requests and one for pushes... +on: + pull_request: + branches: + - bugfix-2.1.x + paths: + - "Marlin/src/core/boards.h" + push: + branches: + - bugfix-2.1.x + paths: + - "Marlin/src/core/boards.h" + +jobs: + validate_pins_files: + name: Validate boards.h + if: github.repository == 'MarlinFirmware/Marlin' + + runs-on: ubuntu-22.04 + + steps: + - name: Check out the PR + uses: actions/checkout@v4 + + - name: Cache pip + uses: actions/cache@v4 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-validation-v1 + restore-keys: | + ${{ runner.os }}-pip-validation- + + - name: Select Python 3.9 + uses: actions/setup-python@v5 + with: + python-version: "3.9" + architecture: "x64" + + - name: Validate core/boards.h + run: | + make validate-boards -j diff --git a/.github/workflows/ci-validate-lines.yml b/.github/workflows/ci-validate-lines.yml new file mode 100644 index 000000000000..367db12e6c6e --- /dev/null +++ b/.github/workflows/ci-validate-lines.yml @@ -0,0 +1,40 @@ +# +# ci-validate-lines.yml +# Validate that all text files are unchanged by linesformat.py +# + +name: CI - Validate Source Files + +on: + pull_request: + branches: + - bugfix-2.1.x + - 2.1.x + push: + branches: + - bugfix-2.1.x + - 2.1.x + +jobs: + validate_source_files: + name: Validate Source Files + if: github.repository == 'MarlinFirmware/Marlin' + + runs-on: ubuntu-22.04 + + steps: + - name: Check out the PR + uses: actions/checkout@v4 + + - name: Cache node_modules + uses: actions/cache@v4 + with: + path: node_modules + key: ${{ runner.os }}-npm-lines-v1 + restore-keys: | + ${{ runner.os }}-npm-lines- + + - name: Validate text file formatting + run: | + npm install --save-dev prettier + make validate-lines -j diff --git a/.github/workflows/ci-validate-pins.yml b/.github/workflows/ci-validate-pins.yml new file mode 100644 index 000000000000..2086fea37aed --- /dev/null +++ b/.github/workflows/ci-validate-pins.yml @@ -0,0 +1,51 @@ +# +# ci-validate-pins.yml +# Validate that all of the pins files are unchanged by pinsformat.py +# + +name: CI - Validate Pins Files + +on: + pull_request: + branches: + - bugfix-2.1.x + # Cannot be enabled on 2.1.x until it contains the unit test framework + #- 2.1.x + paths: + - "Marlin/src/pins/*/**" + push: + branches: + - bugfix-2.1.x + # Cannot be enabled on 2.1.x until it contains the unit test framework + #- 2.1.x + paths: + - "Marlin/src/pins/*/**" + +jobs: + validate_pins_files: + name: Validate Pins Files + if: github.repository == 'MarlinFirmware/Marlin' + + runs-on: ubuntu-22.04 + + steps: + - name: Check out the PR + uses: actions/checkout@v4 + + - name: Cache pip + uses: actions/cache@v4 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-validation-v1 + restore-keys: | + ${{ runner.os }}-pip-validation- + + - name: Select Python 3.9 + uses: actions/setup-python@v5 + with: + python-version: "3.9" + architecture: "x64" + + - name: Validate all pins files + run: | + make validate-pins -j diff --git a/.github/workflows/clean-closed.yml b/.github/workflows/clean-closed.yml index 318d083dfae3..ad0ee8ea209c 100644 --- a/.github/workflows/clean-closed.yml +++ b/.github/workflows/clean-closed.yml @@ -13,7 +13,7 @@ on: jobs: remove_label: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 strategy: matrix: @@ -32,7 +32,7 @@ jobs: - "Needs: Work" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Remove Labels uses: actions-ecosystem/action-remove-labels@v1 with: diff --git a/.github/workflows/close-stale.yml b/.github/workflows/close-stale.yml index 397f9b5ea6c2..4bb488c56f0e 100644 --- a/.github/workflows/close-stale.yml +++ b/.github/workflows/close-stale.yml @@ -14,10 +14,10 @@ jobs: name: Close Stale Issues if: github.repository == 'MarlinFirmware/Marlin' - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - - uses: actions/stale@v8 + - uses: actions/stale@v9 with: repo-token: ${{ secrets.GITHUB_TOKEN }} stale-issue-message: | diff --git a/.github/workflows/lock-closed.yml b/.github/workflows/lock-closed.yml index 8cdcd7a8369e..1435a3ef44b6 100644 --- a/.github/workflows/lock-closed.yml +++ b/.github/workflows/lock-closed.yml @@ -14,18 +14,18 @@ jobs: name: Lock Closed Issues if: github.repository == 'MarlinFirmware/Marlin' - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - - uses: dessant/lock-threads@v2 + - uses: dessant/lock-threads@v5 with: github-token: ${{ github.token }} process-only: 'issues' - issue-lock-inactive-days: '60' - issue-exclude-created-before: '' - issue-exclude-labels: 'no-locking' - issue-lock-labels: '' - issue-lock-comment: > + issue-inactive-days: '60' + exclude-issue-created-before: '' + exclude-any-issue-labels: 'no-locking' + add-issue-labels: '' + issue-comment: > This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. diff --git a/.github/workflows/unlock-reopened.yml b/.github/workflows/unlock-reopened.yml index 614ef3fab297..f88e07cdc363 100644 --- a/.github/workflows/unlock-reopened.yml +++ b/.github/workflows/unlock-reopened.yml @@ -14,7 +14,7 @@ jobs: name: Unlock Reopened if: github.repository == 'MarlinFirmware/Marlin' - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - uses: OSDKDev/unlock-issues@v1.1 diff --git a/.gitignore b/.gitignore old mode 100755 new mode 100644 index c7d47c607fd1..e39db6f7b443 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,12 @@ out-csv/ out-language/ *.gen *.sublime-workspace +.cache/ + +# npm +node_modules/ +package.json +package-lock.json # OS applet/ @@ -125,6 +131,7 @@ vc-fileutils.settings # Visual Studio Code .vscode/* !.vscode/extensions.json +*.code-workspace # Simulation files imgui.ini @@ -168,3 +175,8 @@ __pycache__ tags *.logs *.bak +.aider* +!.aiderignore +.env +schema.json +sqlite*.db diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000000..62761b99e901 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,10 @@ +# Prettier Ignore file +*.min.js +web-ui/ +buildroot/share/PlatformIO/boards +buildroot/share/PlatformIO/variants +*.sublime-project +*.sublime-syntax +.github +.vscode +launch.json diff --git a/.zed/settings.json b/.zed/settings.json new file mode 100644 index 000000000000..70bb14920c4c --- /dev/null +++ b/.zed/settings.json @@ -0,0 +1,16 @@ +/** + * Marlin-specific settings for Zed + * + * For a full list of overridable settings, and general information on folder-specific settings, + * see the documentation: https://zed.dev/docs/configuring-zed#settings-files + */ +{ + "languages": { + "C": { + "enable_language_server": false + }, + "C++": { + "enable_language_server": false + } + } +} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000000..2092d39634a4 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,534 @@ +# Marlin 3D Printer Firmware + +> This file consolidates the project summary (`docs/project-summary.md`) into the top-level agent reference. It is the canonical orientation document for the whole repo. Per-HAL working notes live in `Marlin/src/HAL//AGENTS.md` (e.g. `Marlin/src/HAL/AT32/AGENTS.md`). + +## 1. Executive Summary + +Marlin is an open-source firmware for 3D printers, written in C/C++ and built on the Arduino framework via PlatformIO. It sits between a host computer (running slicer software) and the printer's hardware, translating G-code movement commands into precise stepper motor control, temperature regulation, and peripheral management. The firmware supports 15+ microcontroller platforms (STM32, AVR, ESP32, RP2040, and more), 400+ pin configuration files across 24 board families, and a modular feature system with 80+ enable/disable features. At over 2,500 source files, Marlin is one of the most widely deployed 3D printer firmware projects in the world, licensed under GPL v3.0. + +## 2. Architecture Overview + +Marlin follows a layered embedded firmware architecture organized around a Hardware Abstraction Layer (HAL), a G-code command processing pipeline, a set of core motion/thermal modules, and a rich feature system. The firmware's singleton entry point is the `Marlin` class (`MarlinCore`), which manages global state and the main control loop. + +```mermaid +graph TB + subgraph Environment["Printer Environment"] + Host["Host Computer\n(Slicer / Host Software)"] + SD["SD Card\n(SdFat)"] + Sensors["Thermistors &\nEndstops"] + Motors["Stepper Motors\n& Drivers"] + Heaters["Heaters & Fans"] + Display["LCD / TFT / DWIN"] + end + + subgraph Marlin["Marlin Firmware"] + subgraph GCode["G-Code Subsystem"] + Queue["GCodeQueue\nRing Buffer"] + Parser["GCodeParser\nParameter Extraction"] + Suite["GcodeSuite\nCommand Dispatcher"] + end + subgraph Modules["Core Modules"] + Motion["Motion Planner"] + Planner["Block Planner"] + Stepper["Stepper Driver"] + Temp["Temperature Manager"] + end + subgraph Features["Feature System\n80+ modules"] + BedLevel["Bed Leveling"] + PowerLoss["Power Loss Recovery"] + Runout["Filament Runout"] + MMU["Multi-Material\nUnit"] + end + subgraph HAL["Hardware Abstraction Layer"] + Shared["HAL Shared Code"] + Platforms["15+ Platform HALs"] + end + end + + Host -->|"USB Serial\n250k baud"| Suite + SD -->|"SD Interface"| Queue + Suite --> Parser + Parser --> Suite + Queue --> Suite + Suite --> Motion + Suite --> Temp + Motion --> Planner + Planner --> Stepper + Stepper --> Motors + Temp --> Heaters + Temp --> Sensors + Display <-->|"SPI/I2C/UART"| Suite +``` + +The firmware communicates with the host via serial at 250,000 baud (configurable), receiving G-code commands line-by-line. Commands are queued in a circular buffer (`GCodeQueue::ring_buffer`), parsed by `GCodeParser`, and dispatched by `GcodeSuite` to category-specific handlers (motion, temperature, configuration, calibration, etc.). The core modules handle the low-level real-time tasks: the planner generates motion blocks with jerk control and acceleration profiling, the stepper driver issues pulse trains, and the temperature manager runs PID loops on thermistor inputs. + +### Supported Hardware Platforms + +| Platform Family | MCUs | Architecture | Notes | +| --- | --- | --- | --- | +| STM32 (various) | STM32F0, F1, F4, F7, H7, G0 | ARM Cortex-M | Primary 32-bit target | +| AVR | ATmega2560, ATmega1280, AT90USB | 8-bit AVR | Legacy support | +| ESP32 | ESP32-S3, ESP32-S2 | Xtensa/RISC-V | WiFi/BLE capability | +| SAMD | SAMD21, SAMD51 | ARM Cortex-M0+/M4 | Adafruit Feather M4 | +| RP2040 | RP2040, RP2350 | ARM Cortex-M33 | Raspberry Pi Pico | +| LPC | LPC1768, LPC1769 | ARM Cortex-M3 | RAMBo, Melzi boards | +| GD32 | GD32F1, GD32F3 | ARM Cortex-M3 | GigaDevice clones | +| HC32 | HC32F4 | ARM Cortex-M4 | JCEC chips | +| AT32 | AT32F4 | ARM Cortex-M4 | Artery Tek | +| Teensy | 3.1/3.2, 3.5/3.6, 4.0/4.1 | ARM Cortex-M4/M7 | PJRC boards | +| Linux Simulator | x86/x64 | Native | CI/testing | +| Native Simulator | Any | Native | Unit testing | + +## 3. Processing Pipeline + +The G-code processing pipeline follows a well-defined flow from input ingestion through parsing, dispatch, and hardware actuation. + +```mermaid +flowchart TD + InputSerial["Serial Input\nUSB / UART"] --> Queue + InputSD["SD Card\nSdFat"] --> Queue + InputProgmem["Program Memory\nPROGMEM"] --> Queue + + Queue["GCodeQueue\nRing Buffer\nBUFSIZE entries"] --> Parser + + Parser["GCodeParser\n• Letter/Code/Subcode\n• Parameter extraction\n• FASTER_GCODE_PARSER\n pre-scanned flags"] --> Suite + + Suite["GcodeSuite\nCommand Dispatcher\n• Subcommand routing\n• Parameter validation\n• Endstop events"] + + Suite --> CatMotion["Motion\nG0/G1/G2/G3"] + Suite --> CatTemp["Temperature\nM104/M105/M106/M107/M109"] + Suite --> CatConfig["Configuration\nM200-M205/M301/M92"] + Suite --> CatCalibrate["Calibration\nG28/G33/G34/G425"] + Suite --> CatSD["SD Card\nM20-M34/M928"] + + CatMotion --> Planner + CatTemp --> Temp + CatCalibrate --> Motion + + Planner["Block Planner\n• Trapezoidal profile\n• Jerk control\n• Buffer: BLOCK_BUFFER_SIZE"] --> Stepper + Motion["Motion\n• axis_position()\n• relative_mode\n• dual_x_carriage"] --> Planner + Temp["Temperature\n• PID loops\n• thermistor tables\n• safety checks"] --> Heaters + + Stepper["Stepper Driver\n• issue_pending()\n• TMC SPI config\n• Trinamic drivers"] --> Output["Hardware Outputs\n• Step/Dir pulses\n• PWM heat/fan\n• Serial responses"] +``` + +### Key Pipeline Components + +**GCodeQueue** — A circular ring buffer (`RingBuffer`) that holds up to `BUFSIZE` G-code command strings. Commands enter via three injectors: serial input (USB/UART), SD card file reading, and in-firmware injected commands (PROGMEM). The queue's `advance()` method pops the next command and hands it to the parser. + +**GCodeParser** — Parses a single G-code line, extracting the command letter (G/M/T), code number, subcode, and all parameter values (X, Y, Z, E, F, S, P, etc.). When `FASTER_GCODE_PARSER` is enabled, the parser pre-scans all parameters into a flags array for O(1) lookups. + +**GcodeSuite** — The command dispatcher singleton. Each G/M code maps to a handler method within this class. Commands are routed to category subdirectories under `src/gcode/` (e.g., `motion/`, `temp/`, `config/`, `calibrate/`). The suite also handles pre- and post-command hooks for endstop events, buffer monitoring, and inactivity shutdown. + +## 4. Core Components + +### 4.1 Firmware Entry Point + +The firmware lifecycle is managed by `MarlinCore.cpp` (~1,777 lines), structured around Arduino's `setup()` and `loop()` functions: + +- **`setup()`** — Initializes HAL pins, serial ports, all modules (stepper, temperature, motion, planner, settings), features (runout detection, power loss recovery, LEDs), and the UI subsystem. Each initialization block is wrapped in `SETUP_RUN()` for debug logging in dev mode. +- **`loop()`** — Runs continuously (infinite loop on AVR, structured loop on 32-bit platforms). Each iteration calls `marlin.idle()`, processes the G-code queue, checks power-off timers, and handles endstop events. + +### 4.2 Singleton Architecture + +Global state is managed through singletons: + +| Singleton | File | Responsibility | +| --- | --- | --- | +| `marlin` | `MarlinCore.cpp/h` | Global state machine, inactivity management, kill/suicide, heatup waits | +| `gcode` | `gcode/gcode.cpp` | G-code command dispatch | +| `queue` | `gcode/queue.h` | Command queue ring buffer | +| `planner` | `module/planner.cpp` | Motion block buffer and trapezoidal profiling | +| `stepper` | `module/stepper.cpp` | Step pulse generation and TMC driver config | +| `thermalManager` | `module/temperature.cpp` | PID loops, thermistor reading, safety monitoring | +| `card` | `sd/cardreader.cpp` | SD card file system operations | +| `ui` | `lcd/marlinui.cpp/h` | LCD/UI state machine and menu system | + +### 4.3 Module System + +The `module/` directory contains the core motion and thermal management classes: + +| Module | File | Description | +| --- | --- | --- | +| **Motion** | `motion.h/cpp` | High-level axis positioning, coordinate transforms, joint interpolation | +| **Planner** | `planner.h/cpp` | Block buffer, trapezoidal velocity profiling, junction deviation | +| **Stepper** | `stepper.h/cpp` | Step pulse generation, direction control, TMC Trinamic SPI configuration | +| **Temperature** | `temperature.h/cpp` | PID autotune (M303), thermistor tables, hotend/bed/heater management | +| **Endstops** | `endstops.h/cpp` | Endstop interrupt handling, homing logic, soft endstops | +| **Probe** | `probe.h/cpp` | Bed probing (G30, G31/G32, G38, bltouch), Z-offset management | +| **Settings** | `settings.h/cpp` | EEPROM persistence, configuration storage and retrieval | +| **PrintCounter** | `printcounter.h/cpp` | Print time, filament usage, and job statistics | +| **Servo** | `servo.h/cpp` | Servo control for solenoids, lid mechanisms | +| **Delta/Scara/Polar** | `delta.h`, `scara.h`, `polar.h` | Kinematic transforms for non-Cartesian printers | +| **Tool Change** | `tool_change.h/cpp` | Multi-extruder tool switching, hotend offset, PTFE purge | + +### 4.4 Feature System + +The `feature/` directory contains 80+ optional modules, each gated by a `#if ENABLED(FEATURE_NAME)` preprocessor check: + +**Motion Features:** Linear Advance (`linearadvance`), Pressure Advance, Adaptive Multi-Axis Stepping, Direct Stepping, Bresenham acceleration, Resonance Compensation, X-Axis Twist Correction, Z-Stepper Alignment, Backlash Compensation. + +**Thermal Features:** Automatic PID Tuning (M303), Thermal Protection, Probe Temperature Compensation, Mixed Extruder (`mixing`), Bowden/Direct Drive Retraction (`fwretract`). + +**Peripheral Features:** Power Loss Recovery (`powerloss`), Filament Runout Detection (`runout`), Ethernet Connectivity (`ethernet`), RS485 Communication (`rs485`), MMU/Multi- Material Unit (`mmu`, `mmu3`), Solenoid Control (`solenoid`), Spindle/Laser (`spindle_laser`), Case Lights (`caselight`), LED Color Control (`leds`). + +**UI Features:** Touch Screen (`touch`), LVGL TFT UI (`mks_ui`), DWIN Display (`dwin`), Extensible UI (`extui`), Password Protection (`password`), Joystick Input (`joystick`). + +**Bed Leveling:** Unified Bed Leveling (UBL), Manual Bed Leveling (MBL), Automatic Bed Leveling (ABL), G26 Mesh Validation, G35 Auto Bed Leveling, Bed Level Visualizer. + +## 5. G-Code Command Reference + +Marlin implements 100+ G-code commands organized into 14 categories under `src/gcode/`: + +| Category | Directory | Key Commands | Description | +| --- | --- | --- | --- | +| **Motion** | `motion/` | G0, G1, G2, G3, G5, G6, G80, M400, M290 | Linear/arc moves, rapid moves, dwell, wait, jog | +| **Temperature** | `temp/` | M104/M105/M106/M107/M109, M140/M190, M303 | Hotend/bed temp set/read, fan control, PID autotune | +| **Configuration** | `config/` | M200-M205, M301, M92, M43, M218, M217 | Extruder steps, accel/jerk, PID, hotend offset, bowden length | +| **Calibration** | `calibrate/` | G28, G33, G34, G425, M48, M566, M665 | Home, delta calibration, nozzle purge, backlash, bellows | +| **Geometry** | `geometry/` | G17-G19, G53-G59, G92, M206, M428 | Plane selection, coordinate systems, position reset, offsets | +| **Control** | `control/` | M17/M18/M84, M80/M81, M3-M5, M7-M9, T | Enable axes, power, spindle, tool select, feedrate/extruder units | +| **Probe** | `probe/` | G30, G31/G32, G38, M851, M401/M402, M102 | Bed probe, probe type, probe actions | +| **SD Card** | `sd/` | M20-M34, M928, M1001, M1003 | File listing, select, start, stop, abort, load, save | +| **LCD** | `lcd/` | M0, M1, M73, M117, M145, M250, M300 | Pause, progress, message, color, beep | +| **Host** | `host/` | M16, M110, M113, M114, M115, M118, M119, M154, M360, M876 | Line numbers, machine info, endstop report, kinematic config | +| **EEPROM** | `eeprom/` | M500, M501, M502, M503, M504 | Save/load/reset/settings EEPROM operations | +| **Stats** | `stats/` | M31, M75-M78 | Print time, filament used, errors, buffer stats | +| **Units** | `units/` | G20/G21, M82/M83, M149 | mm/inch, extrusion mode, temp unit display | +| **OTA** | `ota/` | M936 | Over-the-air firmware update | + +## 6. Infrastructure & Deployment + +### 6.1 Build System + +Marlin uses PlatformIO as its primary build system, configured via `platformio.ini`: + +- **Framework**: Arduino (via PlatformIO) +- **Build flags**: `-g3 -D__MARLIN_FIRMWARE__ -DNDEBUG -fsingle-precision-constant` +- **Source filter**: Dynamic inclusion/exclusion of files based on target board via pre-build scripts (`configuration.py`, `common-dependencies.py`, `preflight-checks.py`) +- **Include directory**: `Marlin/src/` +- **Board definitions**: Custom PlatformIO board definitions in `buildroot/share/PlatformIO/boards/` + +### 6.2 Configuration System + +Configuration is managed through a layered preprocessor conditional system: + +1. **`Configuration.h`** — User-editable hardware and feature configuration +2. **`Configuration_adv.h`** — Advanced/experimental feature toggles +3. **`Version.h`** — Build version, machine name, website URL +4. **`inc/Conditionals-*.h`** — Auto-generated feature flags derived from Configuration.h +5. **`inc/MarlinConfig.h`** — Prefix header including all conditionals, types, and sanity checks + +The conditional system ensures that only enabled features compile, minimizing firmware footprint for resource-constrained 8-bit platforms. + +### 6.3 CI/CD Pipeline + +GitHub Actions workflows run on every pull request and push: + +| Workflow | Purpose | +| --- | --- | +| `ci-build-tests.yml` | Compiles Marlin for all supported boards (matrix strategy) | +| `ci-unit-tests.yml` | Runs C++ unit tests on the native simulator | +| `ci-validate-boards.yml` | Validates board pin configurations | +| `ci-validate-pins.yml` | Checks for missing/invalid pin definitions | +| `ci-validate-lines.yml` | Validates line count limits for 8-bit boards | +| `auto-label.yml` | Auto-labels PRs by changed paths | +| `check-pr.yml` | PR quality checks | + +### 6.4 Docker Build Environment + +A Dockerfile (`docker/Dockerfile`) provides a reproducible build environment: + +```dockerfile +FROM python:3.11-bookworm +RUN pip install -U platformio PyYaml +RUN pio upgrade --dev +WORKDIR /code +``` + +### 6.5 Testing + +- **Unit tests**: Located in `Marlin/tests/`, run via the Linux native simulator HAL +- **Build tests**: CI compiles against 20+ board targets to catch compile errors +- **Pin validation**: Automated checks for pin conflicts and missing definitions +- **SdFat**: Integrated SdFat library for SD card file operations (Sd2Card, SdBaseFile, SdVolume) + +## 7. Extension Patterns + +### 7.1 Adding a New Feature + +1. Create your feature files in `Marlin/src/feature/` (e.g., `my_feature.h` and `my_feature.cpp`) +2. Add `#define MY_FEATURE` to `Configuration_adv.h` +3. The conditional system in `inc/Conditionals-*.h` will auto-generate `HAS_MY_FEATURE` +4. Use `#if ENABLED(MY_FEATURE)` guards in your code +5. Include your header from `MarlinCore.cpp` under the appropriate `#if ENABLED()` block +6. Register G-code handlers in `gcode/gcode.cpp` or a new category subdirectory + +### 7.2 Adding a New G-Code Command + +1. Create a new file in the appropriate `src/gcode//` directory (e.g., `M1000.cpp`) +2. Implement the handler method in the `GcodeSuite` class +3. Register the command in the `process_commands()` method of `GcodeSuite` +4. The command will be dispatched automatically when the parser encounters the letter/code + +### 7.3 Adding Board Support + +1. Create a new PlatformIO board definition in `buildroot/share/PlatformIO/boards/` +2. Add pin definitions in `Marlin/src/pins//pins_.h` +3. If the MCU is new, add HAL support in `Marlin/src/HAL//` +4. Update `ini/.ini` in `platformio.ini`'s `extra_configs` +5. Run `ci-validate-pins.yml` to verify pin assignments + +### 7.4 Adding a Display Backend + +1. Create backend files in `Marlin/src/lcd//` +2. Implement the required display interface (SPI/I2C/UART) +3. Add `#define ULTIPANEL` or display-specific `#define` to `Configuration.h` +4. The LCD subsystem routes through `MarlinUI` to the appropriate backend + +## 8. Rules & Anti-Patterns + +### Best Practices + +- **Conditional compilation**: Always gate platform-specific code with `#if ENABLED()` or `#ifdef ARDUINO_ARCH_XXX` +- **PROGMEM usage**: Store large strings and lookup tables in program memory on 8-bit platforms +- **Interrupt safety**: Use `WAIT`/`NOOP` patterns for stepper ISR synchronization +- **EEPROM limits**: Respect memory constraints — 8-bit boards have limited EEPROM (4KB-8KB) +- **Thread safety**: The main loop and stepper ISR share state — use volatile and careful locking +- **Static analysis**: Use `bug_on()` macros for compile-time assertion checks +- **Code size**: 8-bit targets (AVR) have strict flash limits — enable only necessary features + +### Anti-Patterns + +- **Don't add features to `Configuration.h`** — use `Configuration_adv.h` for advanced/experimental features +- **Don't modify HAL shared code** without understanding platform implications +- **Don't use floating-point on AVR** without explicit `float` typing — the compiler defaults to single precision +- **Don't block in the main loop** — all long operations must yield to `idle()` or use non-blocking patterns +- **Don't assume 32-bit semantics** — code must compile for both 8-bit AVR and 32-bit ARM +- **Don't hardcode pin numbers** — always use the pin definition headers + +## 9. Dependencies + +### Build Tools + +| Dependency | Version | Purpose | +| ---------------- | ----------------- | -------------------------- | +| PlatformIO | Latest dev | Build system and framework | +| Arduino Core | Platform-specific | Microcontroller SDK | +| GCC ARM Embedded | 12.x | ARM cross-compiler | +| avr-gcc | 7.x | AVR cross-compiler | + +### Libraries (integrated) + +| Library | Purpose | +| ---------- | --------------------------------------------------- | +| SdFat | SD card file system (Sd2Card, SdBaseFile, SdVolume) | +| u8glib | OLED/LCD display driver (embedded in HAL) | +| LVGL | TFT graphical UI (optional, for MKS UI) | +| heatshrink | G-code decompression for SD prints | + +### Platform Libraries + +| Platform | Libraries | +| -------- | ----------------------------- | +| STM32 | STM32 HAL/LL, STM32duino core | +| AVR | Arduino AVR core, TimerOne | +| ESP32 | ESP-IDF components, WiFi | +| RP2040 | Arduino RP2040 core, PIOasm | +| Teensy | Teensyduino core, Bounce2 | +| SAMD | Arduino SAMD core | + +## 10. Code Structure + +``` +MarlinFirmware/ # Repo root (this directory). `cd` here for PlatformIO builds. +ā”œā”€ā”€ Marlin/ # The Arduino "sketch" (application source + config files) +│ ā”œā”€ā”€ Configuration.h # User hardware configuration +│ ā”œā”€ā”€ Configuration_adv.h # Advanced/experimental toggles +│ ā”œā”€ā”€ Marlin.ino # Board-specific entry stub +│ ā”œā”€ā”€ Makefile # Alternative build (simulator) +│ ā”œā”€ā”€ Version.h # Build version, machine name +│ ā”œā”€ā”€ config.ini # PlatformIO extra config +│ ā”œā”€ā”€ lib/ # Integrated libraries +│ └── src/ # Application source (see below) +ā”œā”€ā”€ buildroot/ # Build infrastructure +ā”œā”€ā”€ ini/ # PlatformIO platform configs +ā”œā”€ā”€ docs/ # Project documentation +ā”œā”€ā”€ docker/ # Docker build environment +ā”œā”€ā”€ .github/workflows/ # CI/CD pipelines +└── platformio.ini # Main PlatformIO configuration +``` + +### Marlin/src layout + +``` +Marlin/src/ +ā”œā”€ā”€ MarlinCore.cpp/h # Firmware entry, setup(), loop(), Marlin singleton +ā”œā”€ā”€ core/ # Core utilities (serial, language, types, mstring) +│ ā”œā”€ā”€ gcode/ # G-code subsystem +│ │ ā”œā”€ā”€ gcode.cpp/h # GcodeSuite dispatcher +│ │ ā”œā”€ā”€ parser.cpp/h # GCodeParser +│ │ ā”œā”€ā”€ queue.cpp/h # GCodeQueue ring buffer +│ │ ā”œā”€ā”€ motion/ # G0-G6, M290, M400 +│ │ ā”œā”€ā”€ temp/ # M104-M109, M140-M193, M303 +│ │ ā”œā”€ā”€ config/ # M200-M205, M301, M92, M218 +│ │ ā”œā”€ā”€ calibrate/ # G28, G33, G34, G425, M48 +│ │ ā”œā”€ā”€ bedlevel/ # G26, G35, G42, M420 +│ │ ā”œā”€ā”€ geometry/ # G17-G19, G53-G59, G92 +│ │ ā”œā”€ā”€ probe/ # G30-G38, M851, M401 +│ │ ā”œā”€ā”€ sd/ # M20-M34, M928 +│ │ ā”œā”€ā”€ lcd/ # M0, M1, M73, M117 +│ │ ā”œā”€ā”€ host/ # M16, M110-M119 +│ │ ā”œā”€ā”€ eeprom/ # M500-M504 +│ │ ā”œā”€ā”€ stats/ # M31, M75-M78 +│ │ ā”œā”€ā”€ units/ # G20/G21, M82/M83 +│ │ └── control/ # M3-M5, M7-M9, M17-M85, T +│ ā”œā”€ā”€ module/ # Core modules +│ │ ā”œā”€ā”€ motion.h/cpp # Axis positioning, kinematics +│ │ ā”œā”€ā”€ planner.h/cpp # Block buffer, trapezoidal profiling +│ │ ā”œā”€ā”€ stepper.h/cpp # Step pulses, TMC drivers +│ │ ā”œā”€ā”€ temperature.h/cpp # PID, thermistors, safety +│ │ ā”œā”€ā”€ endstops.h/cpp # Endstop interrupts, homing +│ │ ā”œā”€ā”€ probe.h/cpp # Bed probing, Z-offset +│ │ ā”œā”€ā”€ settings.h/cpp # EEPROM persistence +│ │ ā”œā”€ā”€ delta.h/cpp # Delta kinematics +│ │ ā”œā”€ā”€ scara.h/cpp # Scara kinematics +│ │ ā”œā”€ā”€ polar.h/cpp # Polar kinematics +│ │ ā”œā”€ā”€ polargraph.h/cpp # Polar graph kinematics +│ │ ā”œā”€ā”€ servo.h/cpp # Servo control +│ │ ā”œā”€ā”€ printcounter.h/cpp # Print statistics +│ │ ā”œā”€ā”€ tool_change.h/cpp # Multi-extruder switching +│ │ ā”œā”€ā”€ ft_motion/ # Fine-tune motion +│ │ └── stepper/ # Stepper internals, Trinamic +│ ā”œā”€ā”€ feature/ # 80+ optional features +│ │ ā”œā”€ā”€ bedlevel/ # UBL, MBL, ABL +│ │ ā”œā”€ā”€ leds/ # LED color control +│ │ ā”œā”€ā”€ mmu/ # Multi-material unit +│ │ ā”œā”€ā”€ mmu3/ # MMU3 variant +│ │ ā”œā”€ā”€ powerloss.h/cpp # Power loss recovery +│ │ ā”œā”€ā”€ runout.h/cpp # Filament runout detection +│ │ ā”œā”€ā”€ pause.h/cpp # Pause/resume +│ │ ā”œā”€ā”€ mixing.h/cpp # Mixed extruder +│ │ ā”œā”€ā”€ fwretract.h/cpp # Firmware retraction +│ │ ā”œā”€ā”€ resonance/ # Resonance compensation +│ │ ā”œā”€ā”€ spindle_laser.h/cpp # Spindle/laser control +│ │ ā”œā”€ā”€ ethernet.h/cpp # Ethernet connectivity +│ │ ā”œā”€ā”€ solenoid.h/cpp # Solenoid control +│ │ ā”œā”€ā”€ password/ # Password protection +│ │ ā”œā”€ā”€ joystick.h/cpp # Joystick input +│ │ ā”œā”€ā”€ tmc_util.h/cpp # TMC driver utilities +│ │ └── ... # 60+ more features +│ ā”œā”€ā”€ lcd/ # Display/UI subsystem +│ │ ā”œā”€ā”€ marlinui.h/cpp # Main UI controller +│ │ ā”œā”€ā”€ HD44780/ # Hitachi HD44780 LCD +│ │ ā”œā”€ā”€ dogm/ # DOGM OLED/LCD +│ │ ā”œā”€ā”€ tft/ # TFT displays +│ │ ā”œā”€ā”€ tft_io/ # TFT I/O drivers +│ │ ā”œā”€ā”€ dwin/ # DWIN displays +│ │ ā”œā”€ā”€ extui/ # Extensible UI (MKS LVGL) +│ │ ā”œā”€ā”€ menu/ # Menu system +│ │ ā”œā”€ā”€ touch/ # Touch screen +│ │ ā”œā”€ā”€ language/ # Multi-language strings +│ │ └── sovol_rts/ # Sovol RTS display +│ ā”œā”€ā”€ libs/ # Utility libraries +│ │ ā”œā”€ā”€ bresenham.h # Bresenham line algorithm +│ │ ā”œā”€ā”€ vector_3.h/cpp # 3D vector math +│ │ ā”œā”€ā”€ circularqueue.h # Generic circular queue +│ │ ā”œā”€ā”€ adc/ # ADC utilities +│ │ ā”œā”€ā”€ heatshrink/ # Decompression +│ │ ā”œā”€ā”€ nozzle.cpp/h # Nozzle cleaning +│ │ ā”œā”€ā”€ numtostr.h/cpp # Number-to-string +│ │ ā”œā”€ā”€ hex_print.h/cpp # Hex printing +│ │ ā”œā”€ā”€ crc16.h/cpp # CRC-16 calculation +│ │ ā”œā”€ā”€ stopwatch.h/cpp # Stopwatch utility +│ │ ā”œā”€ā”€ least_squares_fit.h # Least squares fitting +│ │ └── ... # 15+ utility libs +│ ā”œā”€ā”€ sd/ # SD card subsystem +│ │ ā”œā”€ā”€ cardreader.h/cpp # Card reader interface +│ │ ā”œā”€ā”€ SdFat* # SdFat library (file system) +│ │ ā”œā”€ā”€ disk_io_driver.h # Disk I/O abstraction +│ │ └── usb_flashdrive/ # USB flash drive support +│ ā”œā”€ā”€ pins/ # Pin definitions (400+ files) +│ │ ā”œā”€ā”€ pins.h # Pin inclusion router +│ │ ā”œā”€ā”€ ramps/ # Ramps board family +│ │ ā”œā”€ā”€ mega/ # Mega board family +│ │ ā”œā”€ā”€ rambo/ # Rambo board family +│ │ ā”œā”€ā”€ sanguino/ # Sanguino board family +│ │ ā”œā”€ā”€ stm32f1/ # STM32F1 boards +│ │ ā”œā”€ā”€ stm32f4/ # STM32F4 boards +│ │ ā”œā”€ā”€ stm32f7/ # STM32F7 boards +│ │ ā”œā”€ā”€ stm32h7/ # STM32H7 boards +│ │ ā”œā”€ā”€ stm32g0/ # STM32G0 boards +│ │ ā”œā”€ā”€ stm32f0/ # STM32F0 boards +│ │ ā”œā”€ā”€ sam/ # SAM boards +│ │ ā”œā”€ā”€ samd/ # SAMD boards +│ │ ā”œā”€ā”€ rp2040/ # RP2040 boards +│ │ ā”œā”€ā”€ lpc1768/ # LPC1768 boards +│ │ ā”œā”€ā”€ lpc1769/ # LPC1769 boards +│ │ ā”œā”€ā”€ teensy2/ # Teensy 2.x boards +│ │ ā”œā”€ā”€ teensy3/ # Teensy 3.x boards +│ │ ā”œā”€ā”€ teensy4/ # Teensy 4.x boards +│ │ ā”œā”€ā”€ esp32/ # ESP32 boards +│ │ ā”œā”€ā”€ gd32f1/ # GD32F1 boards +│ │ ā”œā”€ā”€ gd32f3/ # GD32F3 boards +│ │ ā”œā”€ā”€ hc32f4/ # HC32F4 boards +│ │ ā”œā”€ā”€ at32f4/ # AT32F4 boards +│ │ └── native/ # Native/simulator +│ ā”œā”€ā”€ HAL/ # Hardware Abstraction Layer +│ │ ā”œā”€ā”€ HAL.h # HAL interface definition +│ │ ā”œā”€ā”€ platforms.h # Platform selection +│ │ ā”œā”€ā”€ shared/ # Cross-platform HAL code +│ │ │ ā”œā”€ā”€ HAL.cpp/h # Shared HAL implementation +│ │ │ ā”œā”€ā”€ Delay.h # Safe delay function +│ │ │ ā”œā”€ā”€ eeprom_api.h # EEPROM API +│ │ │ ā”œā”€ā”€ cpu_exception/ # Exception handling +│ │ │ └── backtrace/ # Stack backtrace +│ │ ā”œā”€ā”€ STM32/ # STM32 platform HAL +│ │ ā”œā”€ā”€ AVR/ # AVR platform HAL +│ │ ā”œā”€ā”€ ESP32/ # ESP32 platform HAL +│ │ ā”œā”€ā”€ DUE/ # Arduino Due HAL +│ │ ā”œā”€ā”€ SAMD21/ # SAMD21 HAL +│ │ ā”œā”€ā”€ SAMD51/ # SAMD51 HAL +│ │ ā”œā”€ā”€ RP2040/ # RP2040 HAL +│ │ ā”œā”€ā”€ LPC1768/ # LPC1768 HAL +│ │ ā”œā”€ā”€ GD32_MFL/ # GD32 HAL +│ │ ā”œā”€ā”€ HC32/ # HC32 HAL +│ │ ā”œā”€ā”€ AT32/ # AT32 HAL +│ │ ā”œā”€ā”€ TEENSY31_32/ # Teensy 3.1/3.2 HAL +│ │ ā”œā”€ā”€ TEENSY35_36/ # Teensy 3.5/3.6 HAL +│ │ ā”œā”€ā”€ TEENSY40_41/ # Teensy 4.0/4.1 HAL +│ │ ā”œā”€ā”€ STM32F1/ # STM32F1 HAL +│ │ ā”œā”€ā”€ LINUX/ # Linux simulator HAL +│ │ └── NATIVE_SIM/ # Native simulator HAL +│ └── tests/ # Unit tests +│ ā”œā”€ā”€ unit_tests.h/cpp # Test suite +│ └── *.ini # Test configurations +ā”œā”€ā”€ docs/ # Project documentation +│ ā”œā”€ā”€ AGENTS.md # This document +│ └── diagrams/ # Architecture diagrams +│ ā”œā”€ā”€ high-level-architecture.drawio +│ ā”œā”€ā”€ processing-pipeline.drawio +│ └── component-relationships.drawio +ā”œā”€ā”€ buildroot/ # Build infrastructure +│ ā”œā”€ā”€ share/PlatformIO/ # PlatformIO board definitions & scripts +│ ā”œā”€ā”€ test-gcode/ # G-code test files +│ └── tests/ # Build test configurations +ā”œā”€ā”€ ini/ # PlatformIO platform configs +│ ā”œā”€ā”€ avr.ini, due.ini, esp32.ini +│ ā”œā”€ā”€ stm32-common.ini, stm32f1.ini, stm32f4.ini +│ ā”œā”€ā”€ stm32f7.ini, stm32h7.ini, stm32g0.ini +│ ā”œā”€ā”€ samd21.ini, samd51.ini +│ ā”œā”€ā”€ raspberrypi.ini, teensy.ini +│ ā”œā”€ā”€ at32.ini, gd32.ini, hc32.ini +│ ā”œā”€ā”€ lpc176x.ini, native.ini +│ ā”œā”€ā”€ features.ini, renamed.ini +│ └── stm32f1-maple.ini +ā”œā”€ā”€ test/ # Root-level test configurations +ā”œā”€ā”€ docker/ # Docker build environment +ā”œā”€ā”€ .github/workflows/ # CI/CD pipelines +└── platformio.ini # Main PlatformIO configuration +``` + +--- + +_This document was consolidated from `docs/project-summary.md` into the top-level `AGENTS.md` on 2026-07-11 to serve as the canonical repo orientation reference. It reflects the codebase as of the bugfix-2.1.x branch._ diff --git a/Makefile b/Makefile index bc26173aaf36..75479a9500d4 100644 --- a/Makefile +++ b/Makefile @@ -1,18 +1,61 @@ SCRIPTS_DIR := buildroot/share/scripts +MAKESCRIPTS_DIR := buildroot/share/make CONTAINER_RT_BIN := docker CONTAINER_RT_OPTS := --rm -v $(PWD):/code -v platformio-cache:/root/.platformio CONTAINER_IMAGE := marlin-dev +UNIT_TEST_CONFIG ?= default + +# Find a Python 3 interpreter +ifeq ($(OS),Windows_NT) + # Windows: use `where` – fall back through the three common names + PYTHON := $(shell which python 2>nul || which python3 2>nul || which py 2>nul) + # Windows: Use Python script to find pins files + ALL_PINS := $(shell $(PYTHON) $(MAKESCRIPTS_DIR)/find.py Marlin/src/pins -mindepth 2 -name 'pins_*.h') +else + # POSIX: use `command -v` – prefer python3 over python + PYTHON := $(shell command -v python3 2>/dev/null || command -v python 2>/dev/null) + # Unix/Linux: Use find command + ALL_PINS := $(shell find Marlin/src/pins -mindepth 2 -name 'pins_*.h') +endif + +PINSPATH ?= +PINSPATH := $(patsubst Marlin/src/pins/%,%,$(PINSPATH)) +PINSPATH := $(patsubst %/,%,$(PINSPATH)) + +# If PINSPATH already contains %, use it directly. +# Otherwise append /% to match all files below the path. +PINS := $(if $(PINSPATH),$(filter Marlin/src/pins/$(PINSPATH)/%,$(ALL_PINS)),$(ALL_PINS)) + +# Check that the found interpreter is Python 3 +# Error if there's no Python 3 available +ifneq ($(strip $(PYTHON)),) + PYTHON_VERSION := $(shell $(PYTHON) -c "import sys; print(sys.version_info[0])" 2>/dev/null) + ifneq ($(PYTHON_VERSION),3) + $(error $(PYTHON) is not Python 3 – install a Python‑3.x interpreter or adjust your PATH) + endif +else + $(error No Python executable found – install Python 3.x and make sure it is in your PATH) +endif help: @echo "Tasks for local development:" - @echo "make marlin : Build marlin for the configured board" - @echo "make format-pins : Reformat all pins files" + @echo "make marlin : Build Marlin for the configured board" + @echo "make format-pins -j : Reformat all pins files (-j for parallel execution)" + @echo "make format-pins -j PINSPATH=dir : Reformat only pins files under dir" + @echo "make validate-lines -j : Validate line endings, fails on trailing whitespace, etc." + @echo "make validate-pins -j : Validate all pins files, fails if any require reformatting" + @echo "make validate-boards -j : Validate boards.h and pins.h for standards compliance" + @echo "make validate-urls : Validate URLs in source files" @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" @echo "make tests-all-local : Run all tests locally" @echo "make tests-all-local-docker : Run all tests locally, using docker" - @echo "make setup-local-docker : Build the local docker image" + @echo "make unit-test-single-local : Run unit tests for a single config locally" + @echo "make unit-test-single-local-docker : Run unit tests for a single config locally, using docker" + @echo "make unit-test-all-local : Run all code tests locally" + @echo "make unit-test-all-local-docker : Run all code tests locally, using docker" + @echo "make setup-local-docker : Setup local docker" @echo "" @echo "Options for testing:" @echo " TEST_TARGET Set when running tests-single-*, to select the" @@ -22,6 +65,9 @@ help: @echo " run on GitHub CI" @echo " ONLY_TEST Limit tests to only those that contain this, or" @echo " the index of the test (1-based)" + @echo " UNIT_TEST_CONFIG Set the name of the config from the test folder, without" + @echo " the leading number. Default is 'default'". Used with the + @echo " unit-test-single-* tasks" @echo " VERBOSE_PLATFORMIO If you want the full PIO output, set any value" @echo " GIT_RESET_HARD Used by CI: reset all local changes. WARNING:" @echo " THIS WILL UNDO ANY CHANGES YOU'VE MADE!" @@ -30,6 +76,9 @@ marlin: ./buildroot/bin/mftest -a .PHONY: marlin +clean: + rm -rf .pio/build* + tests-single-ci: export GIT_RESET_HARD=true $(MAKE) tests-single-local TEST_TARGET=$(TEST_TARGET) PLATFORMIO_BUILD_FLAGS=-DGITHUB_ACTION @@ -43,23 +92,94 @@ tests-single-local: tests-single-local-docker: @if ! test -n "$(TEST_TARGET)" ; then echo "***ERROR*** Set TEST_TARGET= or use make tests-all-local-docker" ; return 1; fi @if ! $(CONTAINER_RT_BIN) images -q $(CONTAINER_IMAGE) > /dev/null ; then $(MAKE) setup-local-docker ; fi - $(CONTAINER_RT_BIN) run $(CONTAINER_RT_OPTS) $(CONTAINER_IMAGE) $(MAKE) tests-single-local TEST_TARGET=$(TEST_TARGET) VERBOSE_PLATFORMIO=$(VERBOSE_PLATFORMIO) GIT_RESET_HARD=$(GIT_RESET_HARD) ONLY_TEST="$(ONLY_TEST)" + $(CONTAINER_RT_BIN) run $(CONTAINER_RT_OPTS) $(CONTAINER_IMAGE) make tests-single-local TEST_TARGET=$(TEST_TARGET) VERBOSE_PLATFORMIO=$(VERBOSE_PLATFORMIO) GIT_RESET_HARD=$(GIT_RESET_HARD) ONLY_TEST="$(ONLY_TEST)" tests-all-local: + @$(PYTHON) -c "import yaml" 2>/dev/null || (echo 'pyyaml module is not installed. Install it with "$(PYTHON) -m pip install pyyaml"' && exit 1) export PATH="./buildroot/bin/:./buildroot/tests/:${PATH}" \ && export VERBOSE_PLATFORMIO=$(VERBOSE_PLATFORMIO) \ - && for TEST_TARGET in $$($(SCRIPTS_DIR)/get_test_targets.py) ; do echo "Running tests for $$TEST_TARGET" ; run_tests . $$TEST_TARGET ; done + && for TEST_TARGET in $$($(PYTHON) $(MAKESCRIPTS_DIR)/get_test_targets.py) ; do \ + if [ "$$TEST_TARGET" = "linux_native" ] && [ "$$(uname)" = "Darwin" ]; then \ + echo "Skipping tests for $$TEST_TARGET on macOS" ; \ + continue ; \ + fi ; \ + echo "Running tests for $$TEST_TARGET" ; \ + run_tests . $$TEST_TARGET || exit 1 ; \ + sleep 5; \ + done tests-all-local-docker: @if ! $(CONTAINER_RT_BIN) images -q $(CONTAINER_IMAGE) > /dev/null ; then $(MAKE) setup-local-docker ; fi - $(CONTAINER_RT_BIN) run $(CONTAINER_RT_OPTS) $(CONTAINER_IMAGE) $(MAKE) tests-all-local VERBOSE_PLATFORMIO=$(VERBOSE_PLATFORMIO) GIT_RESET_HARD=$(GIT_RESET_HARD) + $(CONTAINER_RT_BIN) run $(CONTAINER_RT_OPTS) $(CONTAINER_IMAGE) make tests-all-local VERBOSE_PLATFORMIO=$(VERBOSE_PLATFORMIO) GIT_RESET_HARD=$(GIT_RESET_HARD) + +unit-test-single-local: + platformio run -t marlin_$(UNIT_TEST_CONFIG) -e linux_native_test + +unit-test-single-local-docker: + @if ! $(CONTAINER_RT_BIN) images -q $(CONTAINER_IMAGE) > /dev/null ; then $(MAKE) setup-local-docker ; fi + $(CONTAINER_RT_BIN) run $(CONTAINER_RT_OPTS) $(CONTAINER_IMAGE) make unit-test-single-local UNIT_TEST_CONFIG=$(UNIT_TEST_CONFIG) + +unit-test-all-local: + platformio run -t test-marlin -e linux_native_test + +unit-test-all-local-docker: + @if ! $(CONTAINER_RT_BIN) images -q $(CONTAINER_IMAGE) > /dev/null ; then $(MAKE) setup-local-docker ; fi + $(CONTAINER_RT_BIN) run $(CONTAINER_RT_OPTS) $(CONTAINER_IMAGE) make unit-test-all-local + +USERNAME := $(shell whoami) +USER_ID := $(shell id -u) +GROUP_ID := $(shell id -g) + +.PHONY: setup-local-docker setup-local-docker-old setup-local-docker: - $(CONTAINER_RT_BIN) build -t $(CONTAINER_IMAGE) -f docker/Dockerfile . + @echo "Building marlin-dev Docker image..." + $(CONTAINER_RT_BIN) build -t $(CONTAINER_IMAGE) \ + --build-arg USERNAME=$(USERNAME) \ + --build-arg USER_ID=$(USER_ID) \ + --build-arg GROUP_ID=$(GROUP_ID) \ + -f docker/Dockerfile . + @echo + @echo "To run all tests in Docker:" + @echo " make tests-all-local-docker" + @echo "To run a single test in Docker:" + @echo " make tests-single-local-docker TEST_TARGET=mega2560" + +setup-local-docker-old: + $(CONTAINER_RT_BIN) buildx build -t $(CONTAINER_IMAGE) -f docker/Dockerfile . -PINS := $(shell find Marlin/src/pins -mindepth 2 -name '*.h') +.PHONY: $(PINS) format-pins validate-pins $(PINS): %: - @echo "Formatting $@" && node $(SCRIPTS_DIR)/pinsformat.js $@ + @echo "Formatting pins $@" + @$(PYTHON) $(SCRIPTS_DIR)/pinsformat.py $< $@ format-pins: $(PINS) + @echo "Processed $(words $(PINS)) pins files" + +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) + +.PHONY: format-lines validate-lines validate-urls + +format-lines: + @echo "Formatting all sources" + @$(PYTHON) $(SCRIPTS_DIR)/linesformat.py buildroot + @$(PYTHON) $(SCRIPTS_DIR)/linesformat.py Marlin + +validate-lines: + @echo "Validating text formatting" + @npx prettier --check . --editorconfig --object-wrap preserve --prose-wrap never + +validate-urls: + @echo "Checking URLs in source files" + @$(MAKESCRIPTS_DIR)/check-urls.sh + +BOARDS_FILE := Marlin/src/core/boards.h + +.PHONY: validate-boards + +validate-boards: + @echo "Validating boards.h file" + @$(PYTHON) $(MAKESCRIPTS_DIR)/validate_boards.py $(BOARDS_FILE) || (echo "\nError: boards.h file is not valid. Please check and correct it.\n" && exit 1) diff --git a/Marlin/Configuration.h b/Marlin/Configuration.h index 57bed3274f40..6f1c83241073 100644 --- a/Marlin/Configuration.h +++ b/Marlin/Configuration.h @@ -53,7 +53,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 * @@ -74,13 +74,15 @@ #define MOTHERBOARD BOARD_ANET_ET4 #endif +// @section serial + /** * Select the serial port on the board to use for communication with the host. * This allows the connection of wireless adapters (for instance) to non-default port pins. * 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 1 @@ -102,19 +104,29 @@ /** * 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, 8, 9] + */ +//#define RS485_SERIAL_PORT 1 +#ifdef RS485_SERIAL_PORT + //#define M485_PROTOCOL 1 // Check your host for protocol compatibility + //#define RS485_BUS_BUFFER_SIZE 128 +#endif + // Enable the Bluetooth serial interface on AT90USB devices //#define BLUETOOTH @@ -138,9 +150,9 @@ * Options: A4988, A5984, DRV8825, LV8729, TB6560, TB6600, TMC2100, * TMC2130, TMC2130_STANDALONE, TMC2160, TMC2160_STANDALONE, * TMC2208, TMC2208_STANDALONE, TMC2209, TMC2209_STANDALONE, - * TMC2660, TMC2660_STANDALONE, TMC5130, TMC5130_STANDALONE, - * TMC5160, TMC5160_STANDALONE - * :['A4988', 'A5984', 'DRV8825', 'LV8729', 'TB6560', 'TB6600', 'TMC2100', 'TMC2130', 'TMC2130_STANDALONE', 'TMC2160', 'TMC2160_STANDALONE', 'TMC2208', 'TMC2208_STANDALONE', 'TMC2209', 'TMC2209_STANDALONE', 'TMC2660', 'TMC2660_STANDALONE', 'TMC5130', 'TMC5130_STANDALONE', 'TMC5160', 'TMC5160_STANDALONE'] + * TMC2240, TMC2660, TMC2660_STANDALONE, + * TMC5130, TMC5130_STANDALONE, TMC5160, TMC5160_STANDALONE + * :['A4988', 'A5984', 'DRV8825', 'LV8729', 'TB6560', 'TB6600', 'TMC2100', 'TMC2130', 'TMC2130_STANDALONE', 'TMC2160', 'TMC2160_STANDALONE', 'TMC2208', 'TMC2208_STANDALONE', 'TMC2209', 'TMC2209_STANDALONE', 'TMC2240', 'TMC2660', 'TMC2660_STANDALONE', 'TMC5130', 'TMC5130_STANDALONE', 'TMC5160', 'TMC5160_STANDALONE'] */ #define X_DRIVER_TYPE A4988 #define Y_DRIVER_TYPE A4988 @@ -250,6 +262,7 @@ #define SWITCHING_NOZZLE_SERVO_ANGLES { 0, 90 } // A pair of angles for { E0, E1 }. // For Dual Servo use two pairs: { { lower, raise }, { lower, raise } } #define SWITCHING_NOZZLE_SERVO_DWELL 2500 // Dwell time to wait for servo to make physical move + #define SWITCHING_NOZZLE_LIFT_TO_PROBE // Lift toolheads out of the way while probing #endif // Switch nozzles by bumping the toolhead. Requires EVENT_GCODE_TOOLCHANGE_#. @@ -294,6 +307,18 @@ #endif +/** + * Differential Extruder + * + * The X and E steppers work together to create a differential drive system. + * Simple : E steps = X + E ; X steps = X (E drives a loop, X stays the same) + * Balanced: E steps = X + E/2 ; X steps = X - E/2 (Dual loop system) + */ +//#define DIFFERENTIAL_EXTRUDER +#if ENABLED(DIFFERENTIAL_EXTRUDER) + //#define BALANCED_DIFFERENTIAL_EXTRUDER +#endif + /** * Switching Toolhead * @@ -378,14 +403,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 @@ -404,9 +430,18 @@ //#define PS_OFF_SOUND // Beep 1s when power off #define PSU_ACTIVE_STATE LOW // Set 'LOW' for ATX, 'HIGH' for X-Box - //#define PSU_DEFAULT_OFF // Keep power off until enabled directly with M80 - //#define PSU_POWERUP_DELAY 250 // (ms) Delay for the PSU to warm up to full power - //#define LED_POWEROFF_TIMEOUT 10000 // (ms) Turn off LEDs after power-off, with this amount of delay + //#define PSU_DEFAULT_OFF // Keep power off until enabled directly with M80 + //#define PSU_POWERUP_DELAY 250 // (ms) Delay for the PSU to warm up to full power + //#define LED_POWEROFF_TIMEOUT 10000 // (ms) Turn off LEDs after power-off, with this amount of delay + + //#define PSU_OFF_REDUNDANT // Second pin for redundant power control + //#define PSU_OFF_REDUNDANT_INVERTED // Redundant pin state is the inverse of PSU_ACTIVE_STATE + + //#define PS_ON1_PIN 6 // Redundant pin required to enable power in combination with PS_ON_PIN + + //#define PS_ON_EDM_PIN 8 // External Device Monitoring pins for external power control relay feedback. Fault on mismatch. + //#define PS_ON1_EDM_PIN 9 + #define PS_EDM_RESPONSE 250 // (ms) Time to allow for relay action //#define POWER_OFF_TIMER // Enable M81 D to power off after a delay //#define POWER_OFF_WAIT_FOR_COOLDOWN // Enable M81 S to power off only after cooldown @@ -435,7 +470,7 @@ //=========================================================================== //============================= Thermal Settings ============================ //=========================================================================== -// @section temperature +// @section temperature sensors /** * Temperature Sensors: @@ -467,8 +502,8 @@ * 10 : 100kĪ© RS PRO 198-961 * 11 : 100kĪ© Keenovo AC silicone mats, most Wanhao i3 machines - beta 3950, 1% * 12 : 100kĪ© Vishay 0603 SMD NTCS0603E3104FXT (#8) - calibrated for Makibox hot bed - * 13 : 100kĪ© Hisens up to 300°C - for "Simple ONE" & "All In ONE" hotend - beta 3950, 1% - * 14 : 100kĪ© (R25), 4092K (beta25), 4.7kĪ© pull-up, bed thermistor as used in Ender-5 S1 + * 13 : 100kĪ© Hisense up to 300°C - for "Simple ONE" & "All In ONE" hotend - R25 = 100 kOhm, beta25 = 4100 K, 4.7kĪ© pull-up + * 14 : 100kĪ© (R25), 4092K (beta25), 4.7kĪ© pull-up, bed thermistor (as used in Ender-5 S1) * 15 : 100kĪ© Calibrated for JGAurora A5 hotend * 17 : 100kĪ© Dagoma NTC white thermistor * 18 : 200kĪ© ATC Semitec 204GT-2 Dagoma.Fr - MKS_Base_DKU001327 @@ -529,6 +564,7 @@ * ================================================================ * Analog Thermocouple Boards * ================================================================ + * -18 : ADS1118 with Thermocouple, e.g., Mightyboard rev G/H * -4 : AD8495 with Thermocouple * -1 : AD595 with Thermocouple * @@ -567,7 +603,7 @@ #define DUMMY_THERMISTOR_998_VALUE 25 #define DUMMY_THERMISTOR_999_VALUE 100 -// Resistor values when using MAX31865 sensors (-5) on TEMP_SENSOR_0 / 1 +// Resistor values when using MAX31865 sensors (-5) on TEMP_SENSOR_0 / 1 / 2 / BED #if TEMP_SENSOR_IS_MAX_TC(0) #define MAX31865_SENSOR_OHMS_0 100 // (Ī©) Typically 100 or 1000 (PT100 or PT1000) #define MAX31865_CALIBRATION_OHMS_0 430 // (Ī©) Typically 430 for Adafruit PT100; 4300 for Adafruit PT1000 @@ -580,6 +616,10 @@ #define MAX31865_SENSOR_OHMS_2 100 #define MAX31865_CALIBRATION_OHMS_2 430 #endif +#if TEMP_SENSOR_IS_MAX_TC(BED) + #define MAX31865_SENSOR_OHMS_BED 100 + #define MAX31865_CALIBRATION_OHMS_BED 430 +#endif #if HAS_E_TEMP_SENSOR #define TEMP_RESIDENCY_TIME 10 // (seconds) Time to wait for hotend to "settle" in M109 @@ -615,6 +655,8 @@ #define TEMP_SENSOR_REDUNDANT_MAX_DIFF 10 // (°C) Temperature difference that will trigger a print abort. #endif +// @section temperature + // Below this temperature the heater will be switched off // because it probably indicates a broken thermistor wire. #define HEATER_0_MINTEMP 5 @@ -666,27 +708,29 @@ * MPCTEMP : Predictive Model temperature control. (~1.8K without auto-tune) */ #define PIDTEMP // See the PID Tuning Guide at https://reprap.org/wiki/PID_Tuning -//#define MPCTEMP // ** EXPERIMENTAL ** See https://marlinfw.org/docs/features/model_predictive_control.html +//#define MPCTEMP // See https://marlinfw.org/docs/features/model_predictive_control.html #define PID_MAX 255 // Limit hotend current while PID is active (see PID_FUNCTIONAL_RANGE below); 255=full current #define PID_K1 0.95 // Smoothing factor within any PID loop #if ENABLED(PIDTEMP) - //#define PID_DEBUG // Print PID debug data to the serial port. Use 'M303 D' to toggle activation. + //#define MIN_POWER 0 // Min power to improve PID stability (0..PID_MAX). + // Get the power from the temperature report ('M105' => @:nnn) and try P*2-20 to P*2-10. + //#define PID_DEBUG // Print PID debug data to the serial port. Use 'M303 D' to enable/disable. //#define PID_PARAMS_PER_HOTEND // Use separate PID parameters for each extruder (useful for mismatched extruders) // Set/get with G-code: M301 E[extruder number, 0-2] #if ENABLED(PID_PARAMS_PER_HOTEND) // Specify up to one value per hotend here, according to your setup. // If there are fewer values, the last one applies to the remaining hotends. - #define DEFAULT_Kp_LIST { 22.20, 22.20 } - #define DEFAULT_Ki_LIST { 1.08, 1.08 } - #define DEFAULT_Kd_LIST { 114.00, 114.00 } + #define DEFAULT_KP_LIST { 22.20, 22.20 } + #define DEFAULT_KI_LIST { 1.08, 1.08 } + #define DEFAULT_KD_LIST { 114.00, 114.00 } #else // ET4 Autotune PID results - #define DEFAULT_Kp 30.11 - #define DEFAULT_Ki 2.63 - #define DEFAULT_Kd 86.12 + #define DEFAULT_KP 30.11 + #define DEFAULT_KI 2.63 + #define DEFAULT_KD 86.12 #endif #else #define BANG_MAX 255 // Limit hotend current while in bang-bang mode; 255=full current @@ -698,15 +742,23 @@ * 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) #define MPC_MAX 255 // (0..255) Current to nozzle while MPC is active. - #define MPC_HEATER_POWER { 40.0f } // (W) Heat cartridge powers. + #define MPC_HEATER_POWER { 40.0f } // (W) Nominal heat cartridge powers. + //#define MPC_PTC // Hotend power changes with temperature (e.g., PTC heat cartridges). + #if ENABLED(MPC_PTC) + #define MPC_HEATER_ALPHA { 0.0028f } // Temperature coefficient of resistance of the heat cartridges. + #define MPC_HEATER_REFTEMP { 20 } // (°C) Reference temperature for MPC_HEATER_POWER and MPC_HEATER_ALPHA. + #endif #define MPC_INCLUDE_FAN // Model the fan speed? @@ -738,6 +790,7 @@ #define MPC_TUNING_POS { X_CENTER, Y_CENTER, 1.0f } // (mm) M306 Autotuning position, ideally bed center at first layer height. #define MPC_TUNING_END_Z 10.0f // (mm) M306 Autotuning final Z position. + //#define EVENT_GCODE_AFTER_MPC_TUNE "M84" // G-code to execute after MPC tune finished and Z raised. #endif //=========================================================================== @@ -769,8 +822,9 @@ //#define PIDTEMPBED #if ENABLED(PIDTEMPBED) - //#define MIN_BED_POWER 0 - //#define PID_BED_DEBUG // Print Bed PID debug data to the serial port. + //#define MIN_BED_POWER 0 // Min power to improve PID stability (0..MAX_BED_POWER). + // Get the power from the temperature report ('M105' => B@:nnn) and try P*2-20 to P*2-10. + //#define PID_BED_DEBUG // Print Bed PID debug data to the serial port. Use 'M303 D' to enable/disable. // 120V 250W silicone heater into 4mm borosilicate (MendelMax 1.5+) // from FOPDT model - kp=.39 Tp=405 Tdead=66, Tc set to 79.2, aggressive factor of .15 (vs .1, 1, 10) @@ -780,7 +834,41 @@ // FIND YOUR OWN: "M303 E-1 C8 S90" to run autotune on the bed at 90 degreesC for 8 cycles. #else - //#define BED_LIMIT_SWITCHING // Keep the bed temperature within BED_HYSTERESIS of the target + //#define BED_LIMIT_SWITCHING // Keep the bed temperature within BED_LIMIT_HYSTERESIS of the target +#endif + +/** + * Peltier Bed - Heating and Cooling + * + * A Peltier device transfers heat from one side to the other in proportion to the amount of + * current flowing through the device and the direction of current flow. So the same device + * can both heat and cool. + * + * When "cooling" in addition to rejecting the heat transferred from the hot side to the cold + * side, the dissipated power (voltage * current) must also be rejected. Be sure to set up a + * fan that can be powered in sync with the Peltier unit. + * + * This feature is only set up to run in bang-bang mode because Peltiers don't handle PWM + * well without filter circuitry. + * + * Since existing 3D printers are made to handle relatively high current for the heated bed, + * we can use the heated bed power pins to control the Peltier power using the same G-codes + * as the heated bed (M140, M190, etc.). + * + * A second GPIO pin is required to control current direction. + * Two configurations are possible: Relay and H-Bridge + * + * (At this time only relay is supported. H-bridge requires 4 MOS switches configured in H-Bridge.) + * + * Power is handled by the bang-bang control loop: 0 or 255. + * Cooling applications are more common than heating, so the pin states are commonly: + * LOW = Heating = Relay Energized + * HIGH = Cooling = Relay in "Normal" state + */ +//#define PELTIER_BED +#if ENABLED(PELTIER_BED) + #define PELTIER_DIR_PIN -1 // Relay control pin for Peltier + #define PELTIER_DIR_HEAT_STATE LOW // The relay pin state that causes the Peltier to heat #endif // Add 'M190 R T' for more gradual M190 R bed cooling. @@ -817,24 +905,26 @@ #define MAX_CHAMBER_POWER 255 // limits duty cycle to chamber heater; 255=full current #if ENABLED(PIDTEMPCHAMBER) - #define MIN_CHAMBER_POWER 0 - //#define PID_CHAMBER_DEBUG // Print Chamber PID debug data to the serial port. + //#define MIN_CHAMBER_POWER 0 // Min power to improve PID stability. (0..MAX_CHAMBER_POWER) + // Get the power from the temperature report ('M105' => C@:nnn) and try P*2-20 to P*2-10. + //#define PID_CHAMBER_DEBUG // Print Chamber PID debug data to the serial port. Use 'M303 D' to enable/disable. // 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_chamberKd 655.17 + #define DEFAULT_CHAMBER_KP 37.04 + #define DEFAULT_CHAMBER_KI 1.40 + #define DEFAULT_CHAMBER_KD 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 - #define PID_FUNCTIONAL_RANGE 10 // If the temperature difference between the target temperature and the actual temperature + #define PID_FUNCTIONAL_RANGE 20 // If the temperature difference between the target temperature and the actual temperature // is more than PID_FUNCTIONAL_RANGE then the PID will be shut off and the heater will be set to min/max. #define PID_EDIT_MENU // Add PID editing to the "Advanced Settings" menu. (~700 bytes of flash) @@ -870,7 +960,7 @@ * protect against a broken or disconnected thermistor wire. * * The issue: If a thermistor falls out, it will report the much lower - * temperature of the air in the room, and the the firmware will keep + * temperature of the air in the room, and the firmware will keep * the heater on. * * If you get "Thermal Runaway" or "Heating failed" errors the @@ -886,7 +976,7 @@ //============================= Mechanical Settings ========================= //=========================================================================== -// @section machine +// @section kinematics // Enable one of the options below for CoreXY, CoreXZ, or CoreYZ kinematics, // either in the usual order or reversed @@ -910,7 +1000,18 @@ // Enable for a belt style printer with endless "Z" motion //#define BELTPRINTER -// Enable for Polargraph Kinematics +// Articulated robot (arm). Joints are directly mapped to axes with no kinematics. +//#define ARTICULATED_ROBOT_ARM + +// For a hot wire cutter with parallel horizontal axes (X, I) where the heights of the two wire +// ends are controlled by parallel axes (Y, J). Joints are directly mapped to axes (no kinematics). +//#define FOAMCUTTER_XYUV + +// @section polargraph + +/** + * Polargraph Kinematics + */ //#define POLARGRAPH #if ENABLED(POLARGRAPH) #define POLARGRAPH_MAX_BELT_LEN 1035.0 // (mm) Belt length at full extension. Override with M665 H. @@ -920,7 +1021,9 @@ // @section delta -// Enable for DELTA kinematics and configure below +/** + * DELTA kinematics + */ //#define DELTA #if ENABLED(DELTA) @@ -953,9 +1056,6 @@ // Print surface diameter/2 minus unreachable space (avoid collisions with vertical towers). #define PRINTABLE_RADIUS 140.0 // (mm) - // Maximum reachable area - #define DELTA_MAX_RADIUS 140.0 // (mm) - // Center-to-center distance of the holes in the diagonal push rods. #define DELTA_DIAGONAL_ROD 250.0 // (mm) @@ -975,49 +1075,62 @@ // Delta radius and diagonal rod adjustments //#define DELTA_RADIUS_TRIM_TOWER { 0.0, 0.0, 0.0 } // (mm) //#define DELTA_DIAGONAL_ROD_TRIM_TOWER { 0.0, 0.0, 0.0 } // (mm) -#endif + +#endif // DELTA // @section scara /** - * MORGAN_SCARA was developed by QHARLEY in South Africa in 2012-2013. + * SCARA kinematics + * + * - Positive angles rotate counterclockwise when looking down from above. + * - Theta is the shoulder angle. 0 degrees aligns to Cartesian positive X. + * - Psi is the elbow angle. 0° is straight, ±180° is completely folded (right-handed positive, left-handed negative). + * + * The Morgan SCARA was developed by QHARLEY in South Africa in 2012-2013. * Implemented and slightly reworked by JCERNY in June, 2014. * * Mostly Printed SCARA is an open source design by Tyler Williams. See: * https://www.thingiverse.com/thing:2487048 * https://www.thingiverse.com/thing:1241491 */ -//#define MORGAN_SCARA -//#define MP_SCARA -#if ANY(MORGAN_SCARA, MP_SCARA) + +//#define SCARA +#if ENABLED(SCARA) // If movement is choppy try lowering this value #define DEFAULT_SEGMENTS_PER_SECOND 200 // Length of inner and outer support arms. Measure arm lengths precisely. - #define SCARA_LINKAGE_1 150 // (mm) - #define SCARA_LINKAGE_2 150 // (mm) + #define SCARA_LINKAGE_1 135 // (mm) + #define SCARA_LINKAGE_2 135 // (mm) - // SCARA tower offset (position of Tower relative to bed zero position) + // SCARA tower offset (position of shoulder axis relative to bed zero position) // This needs to be reasonably accurate as it defines the printbed position in the SCARA space. - #define SCARA_OFFSET_X 100 // (mm) - #define SCARA_OFFSET_Y -56 // (mm) - - #if ENABLED(MORGAN_SCARA) + #define SCARA_OFFSET_X 0 // (mm) + #define SCARA_OFFSET_Y -150 // (mm) - //#define DEBUG_SCARA_KINEMATICS - #define FEEDRATE_SCALING // Convert XY feedrate from mm/s to degrees/s on the fly + // Radius of unreachable area near shoulder axis + #define MIDDLE_DEAD_ZONE_R 0 // (mm) - // Radius around the center where the arm cannot reach - #define MIDDLE_DEAD_ZONE_R 0 // (mm) + // Direction of elbow bend. 1 = right-handed (counterclockwise), -1 = left-handed (clockwise) + #define SCARA_ELBOW_DIR 1 - #elif ENABLED(MP_SCARA) + // This defines how shoulder movement affects the distal arm angle. Common values: + // 0.0 if distal arm retains its angle relative to proximal arm (e.g. if elbow motor rides on proximal arm) + // 1.0 if distal arm retains its angle relative to cartesian X axis (e.g. Morgan and MPSCARA) + // A two-stage reduction with an intermediate pulley on the shoulder axis has crosstalk = + // intermediate_pulley_teeth/elbow_pulley_teeth (first reduction stage does not affect crosstalk) + #define SCARA_CROSSTALK_FACTOR (40.0/60.0) - #define SCARA_OFFSET_THETA1 12 // degrees - #define SCARA_OFFSET_THETA2 131 // degrees + // Shoulder and elbow angles when in home position. If left undefined, cartesian home is used and + // angles are calculated by inverse kinematics (note: M665 home offsets are still angles) + #define SCARA_HOME_THETA -40 + #define SCARA_HOME_PSI 160 - #endif + // Enable M360-M364 to calibrate SCARA angles + //#define SCARA_CALIBRATION -#endif +#endif // SCARA // @section tpara @@ -1031,17 +1144,37 @@ #define TPARA_LINKAGE_1 120 // (mm) #define TPARA_LINKAGE_2 120 // (mm) - // TPARA tower offset (position of Tower relative to bed zero position) + // Height of the Shoulder axis (pivot) relative to the tower floor + #define TPARA_SHOULDER_AXIS_HEIGHT 135.0 // (mm) + + // The position of the last linkage relative to the robot arm origin + // (intersection of the base axis and floor) when at the home position + #define TPARA_ARM_X_HOME_POS 28.75 // (mm) Measured from shoulder axis to tool holder axis in home position + #define TPARA_ARM_Y_HOME_POS 0 // (mm) + #define TPARA_ARM_Z_HOME_POS 250.00 // (mm) Measured from tool holder axis to the floor + + // TPARA Workspace offset relative to the tower (position of workspace origin relative to robot Tower origin ) // This needs to be reasonably accurate as it defines the printbed position in the TPARA space. - #define TPARA_OFFSET_X 0 // (mm) - #define TPARA_OFFSET_Y 0 // (mm) - #define TPARA_OFFSET_Z 0 // (mm) + #define TPARA_OFFSET_X 127.0 // (mm) to coincide with minimum radius MIDDLE_DEAD_ZONE_R, and W(0,0,0) is reachable + #define TPARA_OFFSET_Y 0.0 // (mm) + #define TPARA_OFFSET_Z 0.0 // (mm) + + // TPARA tool connection point offset, relative to the tool moving frame origin which is in the last linkage axis, + // (TCP: tool center/connection point) of the robot, + // the plane of measured offset must be alligned with home position plane + #define TPARA_TCP_OFFSET_X 27.0 // (mm) Tool flange: 27 (distance from pivot to bolt holes), extruder tool: 50.0, + #define TPARA_TCP_OFFSET_Y 0.0 // (mm) + #define TPARA_TCP_OFFSET_Z -65.0 // (mm) Tool flange (bottom): -6 (caution as Z 0 posiion will crash second linkage to the floor, -35 is safe for testing with no tool), extruder tool (depends on extruder): -65.0 #define FEEDRATE_SCALING // Convert XY feedrate from mm/s to degrees/s on the fly // Radius around the center where the arm cannot reach - #define MIDDLE_DEAD_ZONE_R 0 // (mm) -#endif + // For now use a hardcoded uniform limit, although it should be calculated, or fix a limit for each axis angle + #define MIDDLE_DEAD_ZONE_R 100 // (mm) + + // Max angle between L1 and L2 + #define TPARA_MAX_L1L2_ANGLE 140.0f // (degrees) +#endif // AXEL_TPARA // @section polar @@ -1096,15 +1229,6 @@ #define FEEDRATE_SCALING // Convert XY feedrate from mm/s to degrees/s on the fly #endif -// @section machine - -// Articulated robot (arm). Joints are directly mapped to axes with no kinematics. -//#define ARTICULATED_ROBOT_ARM - -// For a hot wire cutter with parallel horizontal axes (X, I) where the heights of the two wire -// ends are controlled by parallel axes (Y, J). Joints are directly mapped to axes (no kinematics). -//#define FOAMCUTTER_XYUV - //=========================================================================== //============================== Endstop Settings =========================== //=========================================================================== @@ -1326,6 +1450,11 @@ * See https://github.com/synthetos/TinyG/wiki/Jerk-Controlled-Motion-Explained */ #define S_CURVE_ACCELERATION +#if ENABLED(S_CURVE_ACCELERATION) + // Define to use 4th instead of 6th order motion curve + //#define S_CURVE_FACTOR 0.25 // Initial and final acceleration factor, ideally 0.1 to 0.4. + // Shouldn't generally require tuning. +#endif //=========================================================================== //============================= Z Probe Options ============================= @@ -1361,6 +1490,13 @@ */ //#define Z_MIN_PROBE_PIN -1 +/** + * Use Pin 27 adapter (on the EXP port) for probe (and BEEPER). + * Also enable USE_PROBE_FOR_Z_HOMING for older BLTouch/3DTouch + * connected to Z_MIN_PIN. + */ +//#define USE_PIN_27_BOARD + /** * Probe Type * @@ -1449,7 +1585,7 @@ * 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 */ //#define BIQU_MICROPROBE_V1 // Triggers HIGH //#define BIQU_MICROPROBE_V2 // Triggers LOW @@ -1477,6 +1613,20 @@ #define PROBE_DEPLOY_FEEDRATE (133*60) // (mm/min) Probe deploy speed #define PROBE_STOW_FEEDRATE (133*60) // (mm/min) Probe stow speed + /** + * Magnetically Mounted Probe with a Servo mechanism + * Probe Deploy and Stow both follow the same basic sequence: + * - Rotate the SERVO to its Deployed angle + * - Perform XYZ moves to deploy or stow the PROBE + * - Rotate the SERVO to its Stowed angle + */ + //#define MAG_MOUNTED_PROBE_SERVO_NR 0 // Servo Number for this probe + #ifdef MAG_MOUNTED_PROBE_SERVO_NR + #define MAG_MOUNTED_PROBE_SERVO_ANGLES { 90, 0 } // Servo Angles for Deployed, Stowed + #define MAG_MOUNTED_PRE_DEPLOY { PROBE_DEPLOY_FEEDRATE, { 15, 160, 30 } } // Safe position for servo activation + #define MAG_MOUNTED_PRE_STOW { PROBE_DEPLOY_FEEDRATE, { 15, 160, 30 } } // Safe position for servo deactivation + #endif + #define MAG_MOUNTED_DEPLOY_1 { PROBE_DEPLOY_FEEDRATE, { 245, 114, 30 } } // Move to side Dock & Attach probe #define MAG_MOUNTED_DEPLOY_2 { PROBE_DEPLOY_FEEDRATE, { 210, 114, 30 } } // Move probe off dock #define MAG_MOUNTED_DEPLOY_3 { PROBE_DEPLOY_FEEDRATE, { 0, 0, 0 } } // Extra move if needed @@ -1540,7 +1690,7 @@ * Nozzle-to-Probe offsets { X, Y, Z } * * X and Y offset - * Use a caliper or ruler to measure the distance from the tip of + * Use a caliper or ruler to measure the distance (in mm) from the tip of * the Nozzle to the center-point of the Probe in the X and Y axes. * * Z offset @@ -1584,18 +1734,21 @@ //#define PROBE_TOOLCHANGE_NO_MOVE // Suppress motion on probe tool-change #endif +//#define PROBE_WAKEUP_TIME_MS 30 // (ms) Time for the probe to wake up + // Most probes should stay away from the edges of the bed, but // with NOZZLE_AS_PROBE this can be negative for a wider probing area. #define PROBING_MARGIN 20 -// X and Y axis travel speed (mm/min) between probes -#define XY_PROBE_FEEDRATE (133*60) +// X and Y axis travel speed between probes. +// Leave undefined to use the average of the current XY homing feedrate. +#define XY_PROBE_FEEDRATE (133*60) // (mm/min) // Feedrate (mm/min) for the first approach when double-probing (MULTIPLE_PROBING == 2) #define Z_PROBE_FEEDRATE_FAST (8*60) -// Feedrate (mm/min) for the "accurate" probe of each point -#define Z_PROBE_FEEDRATE_SLOW (Z_PROBE_FEEDRATE_FAST / 2) +// Feedrate for the "accurate" probe of each point +#define Z_PROBE_FEEDRATE_SLOW (Z_PROBE_FEEDRATE_FAST / 2) // (mm/min) /** * Probe Activation Switch @@ -1619,6 +1772,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 @@ -1656,8 +1810,8 @@ * probe Z Offset set with NOZZLE_TO_PROBE_OFFSET, M851, or the LCD. * Only integer values >= 1 are valid here. * - * Example: `M851 Z-5` with a CLEARANCE of 4 => 9mm from bed to nozzle. - * But: `M851 Z+1` with a CLEARANCE of 2 => 2mm from bed to nozzle. + * Example: 'M851 Z-5' with a CLEARANCE of 4 => 9mm from bed to nozzle. + * But: 'M851 Z+1' with a CLEARANCE of 2 => 2mm from bed to nozzle. */ #define Z_CLEARANCE_DEPLOY_PROBE 10 // (mm) Z Clearance for Deploy/Stow #define Z_CLEARANCE_BETWEEN_PROBES 5 // (mm) Z Clearance between probe points @@ -1708,18 +1862,20 @@ #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 -#define Y_ENABLE_ON 0 -#define Z_ENABLE_ON 0 -#define E_ENABLE_ON 0 // For all extruders -//#define I_ENABLE_ON 0 -//#define J_ENABLE_ON 0 -//#define K_ENABLE_ON 0 -//#define U_ENABLE_ON 0 -//#define V_ENABLE_ON 0 -//#define W_ENABLE_ON 0 +// :['LOW', 'HIGH'] +#define X_ENABLE_ON LOW +#define Y_ENABLE_ON LOW +#define Z_ENABLE_ON LOW +#define E_ENABLE_ON LOW // For all extruders +//#define I_ENABLE_ON LOW +//#define J_ENABLE_ON LOW +//#define K_ENABLE_ON LOW +//#define U_ENABLE_ON LOW +//#define V_ENABLE_ON LOW +//#define W_ENABLE_ON LOW // Disable axis steppers immediately when they're not being stepped. // WARNING: When motors turn off there is a chance of losing position accuracy! @@ -1733,15 +1889,12 @@ //#define DISABLE_V //#define DISABLE_W -// Turn off the display blinking that warns about possible accuracy reduction -//#define DISABLE_REDUCED_ACCURACY_WARNING - // @section extruder //#define DISABLE_E // Disable the extruder when not stepping #define DISABLE_OTHER_EXTRUDERS // Keep only the active extruder enabled -// @section motion +// @section stepper drivers // Invert the stepper direction. Change (or reverse the motor connector) if an axis goes the wrong way. #define INVERT_X_DIR true @@ -1754,8 +1907,6 @@ //#define INVERT_V_DIR false //#define INVERT_W_DIR false -// @section extruder - // For direct drive extruder v9 set to true, for geared extruder set to false. #define INVERT_E0_DIR false #define INVERT_E1_DIR false @@ -1881,6 +2032,8 @@ #endif /** + * @section filament runout sensors + * * Filament Runout Sensors * Mechanical or opto endstops are used to check for the presence of filament. * @@ -1949,13 +2102,16 @@ #ifdef FILAMENT_RUNOUT_DISTANCE_MM // Enable this option to use an encoder disc that toggles the runout pin - // as the filament moves. (Be sure to set FILAMENT_RUNOUT_DISTANCE_MM + // as the filament moves. (Be sure to set FILAMENT_MOTION_DISTANCE_MM // large enough to avoid false positives.) //#define FILAMENT_MOTION_SENSOR #if ENABLED(FILAMENT_MOTION_SENSOR) - //#define FILAMENT_SWITCH_AND_MOTION + //#define FILAMENT_SWITCH_AND_MOTION // Define separate pins below to sense motion #if ENABLED(FILAMENT_SWITCH_AND_MOTION) + + #define FILAMENT_MOTION_DISTANCE_MM 3.0 // (mm) Missing distance required to trigger runout + #define NUM_MOTION_SENSORS 1 // Number of sensors, up to one per extruder. Define a FIL_MOTION#_PIN for each. //#define FIL_MOTION1_PIN -1 @@ -1991,10 +2147,10 @@ //#define FIL_MOTION8_STATE LOW //#define FIL_MOTION8_PULLUP //#define FIL_MOTION8_PULLDOWN - #endif - #endif - #endif -#endif + #endif // FILAMENT_SWITCH_AND_MOTION + #endif // FILAMENT_MOTION_SENSOR + #endif // FILAMENT_RUNOUT_DISTANCE_MM +#endif // FILAMENT_RUNOUT_SENSOR //=========================================================================== //=============================== Bed Leveling ============================== @@ -2040,6 +2196,12 @@ //#define AUTO_BED_LEVELING_UBL //#define MESH_BED_LEVELING +/** + * Commands to execute at the start of G29 probing, + * after switching to the PROBING_TOOL. + */ +//#define EVENT_GCODE_BEFORE_G29 "M300 P440 S200" + /** * Commands to execute at the end of G29 probing. * Useful to retract or move the Z probe out of the way. @@ -2108,6 +2270,8 @@ #define G26_RETRACT_MULTIPLIER 1.0 // G26 Q (retraction) used by default between mesh test elements. #endif + #define MAX_SAVED_MESHES 100 // Maximum number of meshes to store to EEPROM. Use 0 to disable saving. + #endif #if ANY(AUTO_BED_LEVELING_LINEAR, AUTO_BED_LEVELING_BILINEAR) @@ -2188,7 +2352,7 @@ //=========================================================================== #define MESH_INSET 10 // Set Mesh bounds as an inset region of the bed - #define GRID_MAX_POINTS_X 3 // Don't use more than 7 points per axis, implementation limited. + #define GRID_MAX_POINTS_X 3 #define GRID_MAX_POINTS_Y GRID_MAX_POINTS_X //#define MESH_G28_REST_ORIGIN // After homing all axes ('G28' or 'G28 XYZ') rest Z at Z_MIN_POS @@ -2277,6 +2441,9 @@ // Homing speeds (linear=mm/min, rotational=°/min) #define HOMING_FEEDRATE_MM_M { (50*60), (50*60), (8*60) } +// Edit homing feedrates with M210 and MarlinUI menu items +//#define EDITABLE_HOMING_FEEDRATE + // Validate that endstops are triggered on homing moves #define VALIDATE_HOMING_ENDSTOPS @@ -2386,14 +2553,14 @@ // //#define TEMPERATURE_UNITS_SUPPORT -// @section temperature +// @section temperature presets // // Preheat Constants - Up to 10 are supported without changes // #define PREHEAT_1_LABEL "PLA" #define PREHEAT_1_TEMP_HOTEND 180 -#define PREHEAT_1_TEMP_BED 70 +#define PREHEAT_1_TEMP_BED 60 #define PREHEAT_1_TEMP_CHAMBER 35 #define PREHEAT_1_FAN_SPEED 0 // Value from 0 to 255 @@ -2403,9 +2570,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. @@ -2428,6 +2595,8 @@ #endif /** + * @section nozzle clean + * * Clean Nozzle Feature * * Adds the G12 command to perform a nozzle cleaning process. @@ -2588,9 +2757,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 @@ -2599,10 +2783,10 @@ * * Select the language to display on the LCD. These languages are available: * - * en, an, bg, ca, cz, da, de, el, el_CY, es, eu, fi, fr, gl, hr, hu, it, + * en, an, bg, ca, cz, da, de, el, el_CY, es, eu, fi, fr, gl, hg, hr, hu, id, it, * jp_kana, ko_KR, nl, pl, pt, pt_br, ro, ru, sk, sv, tr, uk, vi, zh_CN, zh_TW * - * :{ 'en':'English', 'an':'Aragonese', 'bg':'Bulgarian', 'ca':'Catalan', 'cz':'Czech', 'da':'Danish', 'de':'German', 'el':'Greek (Greece)', 'el_CY':'Greek (Cyprus)', 'es':'Spanish', 'eu':'Basque-Euskera', 'fi':'Finnish', 'fr':'French', 'gl':'Galician', 'hr':'Croatian', 'hu':'Hungarian', 'it':'Italian', 'jp_kana':'Japanese', 'ko_KR':'Korean (South Korea)', 'nl':'Dutch', 'pl':'Polish', 'pt':'Portuguese', 'pt_br':'Portuguese (Brazilian)', 'ro':'Romanian', 'ru':'Russian', 'sk':'Slovak', 'sv':'Swedish', 'tr':'Turkish', 'uk':'Ukrainian', 'vi':'Vietnamese', 'zh_CN':'Chinese (Simplified)', 'zh_TW':'Chinese (Traditional)' } + * :{ 'en':'English', 'an':'Aragonese', 'bg':'Bulgarian', 'ca':'Catalan', 'cz':'Czech', 'da':'Danish', 'de':'German', 'el':'Greek (Greece)', 'el_CY':'Greek (Cyprus)', 'es':'Spanish', 'eu':'Basque-Euskera', 'fi':'Finnish', 'fr':'French', 'gl':'Galician', 'hg':'Hinglish (Hindi-Latin)', 'hr':'Croatian', 'hu':'Hungarian', 'id':'Indonesian', 'it':'Italian', 'jp_kana':'Japanese', 'ko_KR':'Korean (South Korea)', 'nl':'Dutch', 'pl':'Polish', 'pt':'Portuguese', 'pt_br':'Portuguese (Brazilian)', 'ro':'Romanian', 'ru':'Russian', 'sk':'Slovak', 'sv':'Swedish', 'tr':'Turkish', 'uk':'Ukrainian', 'vi':'Vietnamese', 'zh_CN':'Chinese (Simplified)', 'zh_TW':'Chinese (Traditional)' } */ #define LCD_LANGUAGE es @@ -2631,9 +2815,9 @@ #define DISPLAY_CHARSET_HD44780 WESTERN /** - * Info Screen Style (0:Classic, 1:PrÅÆÅ”a) + * Info Screen Style (0:Classic, 1:PrÅÆÅ”a, 2:CNC) * - * :[0:'Classic', 1:'PrÅÆÅ”a'] + * :[0:'Classic', 1:'PrÅÆÅ”a', 2:'CNC'] */ #define LCD_INFO_SCREEN_STYLE 0 @@ -2780,7 +2964,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 @@ -2819,13 +3003,15 @@ // //#define MAKEBOARD_MINI_2_LINE_DISPLAY_1602 -// -// ANET and Tronxy 20x4 Controller -// -//#define ZONESTAR_LCD // Requires ADC_KEYPAD_PIN to be assigned to an analog pin. - // This LCD is known to be susceptible to electrical interference - // which scrambles the display. Pressing any button clears it up. - // This is a LCD2004 display with 5 analog buttons. +/** + * ANET and Tronxy 20x4 Controller + * LCD2004 display with 5 analog buttons. + * + * NOTE: Requires ADC_KEYPAD_PIN to be assigned to an analog pin. + * This LCD is known to be susceptible to electrical interference which + * scrambles the display. Press any button to clear it up. + */ +//#define ZONESTAR_LCD // // Generic 16x2, 16x4, 20x2, or 20x4 character-based LCD. @@ -2846,7 +3032,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 @@ -2900,6 +3086,18 @@ // //#define FF_INTERFACEBOARD +// +// MightyBoard LCD and Interface +// +//#define MIGHTYBOARD_LCD +#if ENABLED(MIGHTYBOARD_LCD) + //#define MIGHTYBOARD_DEBUG // Lightweight debug output for buttons and encoder + //#define MIGHTYBOARD_DISABLE_ENC_PULLUP // Enable if the encoder button is unreliable + //#define MIGHTYBOARD_BUTTON_PULLUPS // Enable if other buttons are unreliable + //#define MIGHTYBOARD_BACK_STATUS_BUTTONS // Use LEFT/RIGHT buttons for Back / Home. + // Otherwise they act like encoder down / up. +#endif + // // TFT GLCD Panel with Marlin UI // Panel connected to main board by SPI or I2C interface. @@ -2978,7 +3176,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 @@ -3013,7 +3211,7 @@ // // FYSETC variant of the MINI12864 graphic controller with SD support -// https://wiki.fysetc.com/Mini12864_Panel/ +// https://wiki.fysetc.com/docs/Mini12864Panel // //#define FYSETC_MINI_12864_X_X // Type C/D/E/F. No tunable RGB Backlight by default //#define FYSETC_MINI_12864_1_2 // Type C/D/E/F. Simple RGB Backlight (always on) @@ -3102,14 +3300,14 @@ // // Tiny, but very sharp OLED display // -//#define MKS_12864OLED // Uses the SH1106 controller (default) +//#define MKS_12864OLED // Uses the SH1106 controller //#define MKS_12864OLED_SSD1306 // Uses the SSD1306 controller // // Zonestar OLED 128Ɨ64 Full Graphics Controller // //#define ZONESTAR_12864LCD // Graphical (DOGM) with ST7920 controller -//#define ZONESTAR_12864OLED // 1.3" OLED with SH1106 controller (default) +//#define ZONESTAR_12864OLED // 1.3" OLED with SH1106 controller //#define ZONESTAR_12864OLED_SSD1306 // 0.96" OLED with SSD1306 controller // @@ -3187,7 +3385,7 @@ #endif // -// Touch-screen LCD for Malyan M200/M300 printers +// LCD for Malyan M200/M300 printers // //#define MALYAN_LCD @@ -3215,6 +3413,11 @@ // //#define ANYCUBIC_LCD_VYPER +// +// Sovol SV-06 Resistive Touch Screen +// +//#define SOVOL_SV06_RTS + // // 320x240 Nextion 2.8" serial TFT Resistive Touch Screen NX3224T028 // @@ -3353,19 +3556,22 @@ #if ENABLED(TFT_COLOR_UI) /** - * TFT Font for Color_UI. Choose one of the following: + * TFT Font for Color UI. Choose one of the following: * * NOTOSANS - Default font with anti-aliasing. Supports Latin Extended and non-Latin characters. * UNIFONT - Lightweight font, no anti-aliasing. Supports Latin Extended and non-Latin characters. * HELVETICA - Lightweight font, no anti-aliasing. Supports Basic Latin (0x0020-0x007F) and Latin-1 Supplement (0x0080-0x00FF) characters only. + * :['NOTOSANS', 'UNIFONT', 'HELVETICA'] */ #define TFT_FONT NOTOSANS /** - * TFT Theme for Color_UI. Choose one of the following or add a new one to 'Marlin/src/lcd/tft/themes' directory + * TFT Theme for Color UI. Choose one of the following or add a new one to 'Marlin/src/lcd/tft/themes' directory * * BLUE_MARLIN - Default theme with 'midnight blue' background * BLACK_MARLIN - Theme with 'black' background + * ANET_BLACK - Theme used for Anet ET4/5 + * :['BLUE_MARLIN', 'BLACK_MARLIN', 'ANET_BLACK'] */ #define TFT_THEME ANET_BLACK @@ -3385,6 +3591,8 @@ * TFT_ROTATE_180, TFT_ROTATE_180_MIRROR_X, TFT_ROTATE_180_MIRROR_Y, * TFT_ROTATE_270, TFT_ROTATE_270_MIRROR_X, TFT_ROTATE_270_MIRROR_Y, * TFT_MIRROR_X, TFT_MIRROR_Y, TFT_NO_ROTATION + * + * :{ 'TFT_NO_ROTATION':'None', 'TFT_ROTATE_90':'90°', 'TFT_ROTATE_90_MIRROR_X':'90° (Mirror X)', 'TFT_ROTATE_90_MIRROR_Y':'90° (Mirror Y)', 'TFT_ROTATE_180':'180°', 'TFT_ROTATE_180_MIRROR_X':'180° (Mirror X)', 'TFT_ROTATE_180_MIRROR_Y':'180° (Mirror Y)', 'TFT_ROTATE_270':'270°', 'TFT_ROTATE_270_MIRROR_X':'270° (Mirror X)', 'TFT_ROTATE_270_MIRROR_Y':'270° (Mirror Y)', 'TFT_MIRROR_X':'Mirror X', 'TFT_MIRROR_Y':'Mirror Y' } */ //#define TFT_ROTATION TFT_NO_ROTATION @@ -3401,6 +3609,11 @@ //#define DWIN_MARLINUI_PORTRAIT // MarlinUI (portrait orientation) //#define DWIN_MARLINUI_LANDSCAPE // MarlinUI (landscape orientation) +#if ENABLED(DWIN_CREALITY_LCD) + //#define USE_STRING_HEADINGS // Use string headings for Creality UI instead of images + //#define USE_STRING_TITLES // Use string titles for Creality UI instead of images +#endif + // // Touch Screen Settings // @@ -3409,7 +3622,9 @@ #define BUTTON_DELAY_EDIT 50 // (ms) Button repeat delay for edit screens #define BUTTON_DELAY_MENU 250 // (ms) Button repeat delay for menus - //#define DISABLE_ENCODER // Disable the click encoder, if any + #if ANY(TFT_CLASSIC_UI, TFT_COLOR_UI) + //#define NO_BACK_MENU_ITEM // Don't display a top menu item to go back to the parent menu + #endif #define TOUCH_SCREEN_CALIBRATION @@ -3433,7 +3648,9 @@ // https://reprapworld.com/products/electronics/ramps/keypad_v1_0_fully_assembled/ // //#define REPRAPWORLD_KEYPAD -//#define REPRAPWORLD_KEYPAD_MOVE_STEP 10.0 // (mm) Distance to move per key-press +#if ENABLED(REPRAPWORLD_KEYPAD) + //#define REPRAPWORLD_KEYPAD_MOVE_STEP 10.0 // (mm) Distance to move per key-press +#endif // // EasyThreeD ET-4000+ with button input and status LED @@ -3455,17 +3672,19 @@ // is too low, you should also increment SOFT_PWM_SCALE. #define FAN_SOFT_PWM -// Incrementing this by 1 will double the software PWM frequency, -// affecting heaters, and the fan if FAN_SOFT_PWM is enabled. -// However, control resolution will be halved for each increment; -// at zero value, there are 128 effective control positions. -// :[0,1,2,3,4,5,6,7] +/** + * Incrementing this by 1 will double the software PWM frequency, affecting heaters, and + * the fan if FAN_SOFT_PWM is enabled. However, control resolution will be halved for each + * increment; at zero value, there are 128 effective control positions. + * :[0,1,2,3,4,5,6,7] + */ #define SOFT_PWM_SCALE 0 -// If SOFT_PWM_SCALE is set to a value higher than 0, dithering can -// be used to mitigate the associated resolution loss. If enabled, -// some of the PWM cycles are stretched so on average the desired -// duty cycle is attained. +/** + * If SOFT_PWM_SCALE is set to a value higher than 0, dithering can be used to mitigate the + * associated resolution loss. If enabled, some of the PWM cycles are stretched so on average + * the desired duty cycle is attained. + */ //#define SOFT_PWM_DITHER // @section extras @@ -3475,9 +3694,11 @@ // @section lights -// Temperature status LEDs that display the hotend and bed temperature. -// If all hotends, bed temperature, and target temperature are under 54C -// then the BLUE led is on. Otherwise the RED led is on. (1C hysteresis) +/** + * Temperature status LEDs that display the hotend and bed temperature. + * If all hotends, bed temperature, and target temperature are under 54C + * the BLUE led is on. Otherwise the RED led is on. (1C hysteresis) + */ //#define TEMP_STAT_LEDS // Support for BlinkM/CyzRgb diff --git a/Marlin/Configuration_adv.h b/Marlin/Configuration_adv.h index 8fe4239f4a2e..091aeea3d049 100644 --- a/Marlin/Configuration_adv.h +++ b/Marlin/Configuration_adv.h @@ -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 ============================ @@ -173,9 +174,10 @@ * Thermocouple Options — for MAX6675 (-2), MAX31855 (-3), and MAX31865 (-5). */ //#define TEMP_SENSOR_FORCE_HW_SPI // Ignore SCK/MOSI/MISO pins; use CS and the default SPI bus. -//#define MAX31865_SENSOR_WIRES_0 2 // (2-4) Number of wires for the probe connected to a MAX31865 board. -//#define MAX31865_SENSOR_WIRES_1 2 -//#define MAX31865_SENSOR_WIRES_2 2 +//#define MAX31865_SENSOR_WIRES_0 2 // (2-4) Number of wires for the probe connected to a MAX31865 board. +//#define MAX31865_SENSOR_WIRES_1 2 +//#define MAX31865_SENSOR_WIRES_2 2 +//#define MAX31865_SENSOR_WIRES_BED 2 //#define MAX31865_50HZ_FILTER // Use a 50Hz filter instead of the default 60Hz. //#define MAX31865_USE_READ_ERROR_DETECTION // Treat value spikes (20°C delta in under 1s) as read errors. @@ -187,6 +189,7 @@ //#define MAX31865_WIRE_OHMS_0 0.95f // For 2-wire, set the wire resistances for more accurate readings. //#define MAX31865_WIRE_OHMS_1 0.0f //#define MAX31865_WIRE_OHMS_2 0.0f +//#define MAX31865_WIRE_OHMS_BED 0.0f /** * Hephestos 2 24V heated bed upgrade kit. @@ -201,27 +204,28 @@ // Heated Bed Bang-Bang options // #if DISABLED(PIDTEMPBED) - #define BED_CHECK_INTERVAL 5000 // (ms) Interval between checks in bang-bang control - #if ENABLED(BED_LIMIT_SWITCHING) - #define BED_HYSTERESIS 2 // (°C) Only set the relevant heater state when ABS(T-target) > BED_HYSTERESIS + #define BED_CHECK_INTERVAL 5000 // (ms) Interval between checks in bang-bang control + #if ANY(BED_LIMIT_SWITCHING, PELTIER_BED) + #define BED_LIMIT_HYSTERESIS 2 // (°C) Only set the relevant heater state when ABS(T-target) > BED_LIMIT_HYSTERESIS #endif #endif // // Heated Chamber options // -#if DISABLED(PIDTEMPCHAMBER) - #define CHAMBER_CHECK_INTERVAL 5000 // (ms) Interval between checks in bang-bang control - #if ENABLED(CHAMBER_LIMIT_SWITCHING) - #define CHAMBER_HYSTERESIS 2 // (°C) Only set the relevant heater state when ABS(T-target) > CHAMBER_HYSTERESIS - #endif -#endif #if TEMP_SENSOR_CHAMBER //#define HEATER_CHAMBER_PIN P2_04 // Required heater on/off pin (example: SKR 1.4 Turbo HE1 plug) //#define HEATER_CHAMBER_INVERTING false //#define FAN1_PIN -1 // Remove the fan signal on pin P2_04 (example: SKR 1.4 Turbo HE1 plug) + #if DISABLED(PIDTEMPCHAMBER) + #define CHAMBER_CHECK_INTERVAL 5000 // (ms) Interval between checks in bang-bang control + #if ENABLED(CHAMBER_LIMIT_SWITCHING) + #define CHAMBER_LIMIT_HYSTERESIS 2 // (°C) Only set the relevant heater state when ABS(T-target) > CHAMBER_LIMIT_HYSTERESIS + #endif + #endif + //#define CHAMBER_FAN // Enable a fan on the chamber #if ENABLED(CHAMBER_FAN) //#define CHAMBER_FAN_INDEX 2 // Index of a fan to repurpose as the chamber fan. (Default: first unused fan) @@ -293,7 +297,7 @@ * protect against a broken or disconnected thermistor wire. * * The issue: If a thermistor falls out, it will report the much lower - * temperature of the air in the room, and the the firmware will keep + * temperature of the air in the room, and the firmware will keep * the heater on. * * The solution: Once the temperature reaches the target, start observing. @@ -303,9 +307,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 5 // (°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 +338,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 60 // (seconds) #define THERMAL_PROTECTION_BED_HYSTERESIS 2 // (°C) @@ -348,7 +352,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 +366,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) @@ -411,14 +415,19 @@ // A well-chosen Kc value should add just enough power to melt the increased material volume. //#define PID_EXTRUSION_SCALING #if ENABLED(PID_EXTRUSION_SCALING) - #define DEFAULT_Kc (100) // heating power = Kc * e_speed #define LPQ_MAX_LEN 50 + #define DEFAULT_KC 100 // heating power = Kc * e_speed + #if ENABLED(PID_PARAMS_PER_HOTEND) + // Specify up to one value per hotend here, according to your setup. + // If there are fewer values, the last one applies to the remaining hotends. + #define DEFAULT_KC_LIST { DEFAULT_KC, DEFAULT_KC } // heating power = Kc * e_speed + #endif #endif /** * Add an additional term to the heater power, proportional to the fan speed. * A well-chosen Kf value should add just enough power to compensate for power-loss from the cooling fan. - * You can either just add a constant compensation with the DEFAULT_Kf value + * You can either just add a constant compensation with the DEFAULT_KF value * or follow the instruction below to get speed-dependent compensation. * * Constant compensation (use only with fan speeds of 0% and 100%) @@ -449,21 +458,26 @@ #if ENABLED(PID_FAN_SCALING_ALTERNATIVE_DEFINITION) // The alternative definition is used for an easier configuration. // Just figure out Kf at full speed (255) and PID_FAN_SCALING_MIN_SPEED. - // DEFAULT_Kf and PID_FAN_SCALING_LIN_FACTOR are calculated accordingly. + // DEFAULT_KF and PID_FAN_SCALING_LIN_FACTOR are calculated accordingly. - #define PID_FAN_SCALING_AT_FULL_SPEED 13.0 //=PID_FAN_SCALING_LIN_FACTOR*255+DEFAULT_Kf - #define PID_FAN_SCALING_AT_MIN_SPEED 6.0 //=PID_FAN_SCALING_LIN_FACTOR*PID_FAN_SCALING_MIN_SPEED+DEFAULT_Kf + #define PID_FAN_SCALING_AT_FULL_SPEED 13.0 //=PID_FAN_SCALING_LIN_FACTOR*255+DEFAULT_KF + #define PID_FAN_SCALING_AT_MIN_SPEED 6.0 //=PID_FAN_SCALING_LIN_FACTOR*PID_FAN_SCALING_MIN_SPEED+DEFAULT_KF #define PID_FAN_SCALING_MIN_SPEED 10.0 // Minimum fan speed at which to enable PID_FAN_SCALING - #define DEFAULT_Kf (255.0*PID_FAN_SCALING_AT_MIN_SPEED-PID_FAN_SCALING_AT_FULL_SPEED*PID_FAN_SCALING_MIN_SPEED)/(255.0-PID_FAN_SCALING_MIN_SPEED) - #define PID_FAN_SCALING_LIN_FACTOR (PID_FAN_SCALING_AT_FULL_SPEED-DEFAULT_Kf)/255.0 + #define DEFAULT_KF (255.0*PID_FAN_SCALING_AT_MIN_SPEED-PID_FAN_SCALING_AT_FULL_SPEED*PID_FAN_SCALING_MIN_SPEED)/(255.0-PID_FAN_SCALING_MIN_SPEED) + #define PID_FAN_SCALING_LIN_FACTOR (PID_FAN_SCALING_AT_FULL_SPEED-DEFAULT_KF)/255.0 #else #define PID_FAN_SCALING_LIN_FACTOR (0) // Power-loss due to cooling = Kf * (fan_speed) - #define DEFAULT_Kf 10 // A constant value added to the PID-tuner + #define DEFAULT_KF 10 // A constant value added to the PID-tuner #define PID_FAN_SCALING_MIN_SPEED 10 // Minimum fan speed at which to enable PID_FAN_SCALING #endif #endif + #if ENABLED(PID_PARAMS_PER_HOTEND) + // Specify up to one value per hotend here, according to your setup. + // If there are fewer values, the last one applies to the remaining hotends. + #define DEFAULT_KF_LIST { DEFAULT_KF, DEFAULT_KF } + #endif #endif /** @@ -482,15 +496,15 @@ #define AUTOTEMP #if ENABLED(AUTOTEMP) #define AUTOTEMP_OLDWEIGHT 0.98 // Factor used to weight previous readings (0.0 < value < 1.0) - #define AUTOTEMP_MIN 210 - #define AUTOTEMP_MAX 250 + #define AUTOTEMP_MIN 210 + #define AUTOTEMP_MAX 250 #define AUTOTEMP_FACTOR 0.1f // Turn on AUTOTEMP on M104/M109 by default using proportions set here //#define AUTOTEMP_PROPORTIONAL #if ENABLED(AUTOTEMP_PROPORTIONAL) - #define AUTOTEMP_MIN_P 0 // (°C) Added to the target temperature - #define AUTOTEMP_MAX_P 5 // (°C) Added to the target temperature - #define AUTOTEMP_FACTOR_P 1 // Apply this F parameter by default (overridden by M104/M109 F) + #define AUTOTEMP_MIN_P 0 // (°C) Added to the target temperature + #define AUTOTEMP_MAX_P 5 // (°C) Added to the target temperature + #define AUTOTEMP_FACTOR_P 1 // Apply this F parameter by default (overridden by M104/M109 F) #endif #endif @@ -566,6 +580,25 @@ // @section fans +/** + * Part Cooling Fan Pins + * Override the default part cooling fan pins for each fan index. + * Allows remapping which physical fan pin is used for part cooling. + * By default: FAN0 -> FAN0_PIN, FAN1 -> FAN1_PIN, etc. + */ +//#define PART_COOLING_FAN0_PIN FAN0_PIN +//#define PART_COOLING_FAN1_PIN FAN1_PIN +//#define PART_COOLING_FAN2_PIN FAN2_PIN +//#define PART_COOLING_FAN3_PIN FAN3_PIN +//#define PART_COOLING_FAN4_PIN FAN4_PIN +//#define PART_COOLING_FAN5_PIN FAN5_PIN +//#define PART_COOLING_FAN6_PIN FAN6_PIN +//#define PART_COOLING_FAN7_PIN FAN7_PIN +//#define PART_COOLING_FAN8_PIN FAN8_PIN +//#define PART_COOLING_FAN9_PIN FAN9_PIN +//#define PART_COOLING_FAN10_PIN FAN10_PIN +//#define PART_COOLING_FAN11_PIN FAN11_PIN + /** * Controller Fan * To cool down the stepper drivers and MOSFETs. @@ -590,6 +623,8 @@ // Use TEMP_SENSOR_SOC as a trigger for enabling the controller fan //#define CONTROLLER_FAN_MIN_SOC_TEMP 40 // (°C) Turn on the fan if the SoC reaches this temperature + #define CONTROLLER_FAN_BED_HEATING // Turn on the fan when heating the bed + //#define CONTROLLER_FAN_EDITABLE // Enable M710 configurable settings #if ENABLED(CONTROLLER_FAN_EDITABLE) #define CONTROLLER_FAN_MENU // Enable the Controller Fan submenu @@ -604,6 +639,8 @@ */ //#define FAN_KICKSTART_TIME 100 // (ms) //#define FAN_KICKSTART_POWER 180 // 64-255 +//#define FAN_KICKSTART_LINEAR // Set kickstart time linearly based on the speed, e.g., for 20% (51) it will be FAN_KICKSTART_TIME * 0.2. + // Useful for quick speed up to low speed. Kickstart power must be set to 255. // Some coolers may require a non-zero "off" state. //#define FAN_OFF_PWM 1 @@ -770,7 +807,7 @@ // @section endstops -// If you want endstops to stay on (by default) even when not homing +// If you want endstops to stay on (by default) even when not homing, // enable this option. Override at any time with M120, M121. //#define ENDSTOPS_ALWAYS_ON_DEFAULT @@ -1014,7 +1051,7 @@ #endif // BLTOUCH -// @section calibration +// @section calibrate /** * Z Steppers Auto-Alignment @@ -1073,15 +1110,32 @@ #define G34_MAX_GRADE 5 // (%) Maximum incline that G34 will handle #define Z_STEPPER_ALIGN_ITERATIONS 5 // Number of iterations to apply during alignment #define Z_STEPPER_ALIGN_ACC 0.02 // Stop iterating early if the accuracy is better than this + #define RESTORE_LEVELING_AFTER_G34 // Restore leveling after G34 is done? + // After G34, re-home Z (G28 Z) or just calculate it from the last probe heights? // Re-homing might be more precise in reproducing the actual 'G28 Z' homing height, especially on an uneven bed. #define HOME_AFTER_G34 -#endif -// -// Add the G35 command to read bed corners to help adjust screws. Requires a bed probe. -// + /** + * Commands to execute at the start of G34 probing, + * after switching to the PROBING_TOOL. + */ + //#define EVENT_GCODE_BEFORE_G34 "M300 P440 S200" + + /** + * Commands to execute at the end of G34 probing. + * Useful to retract or move the Z probe out of the way. + */ + //#define EVENT_GCODE_AFTER_G34 "G1 Z10 F12000\nG1 X15 Y330\nG1 Z0.5\nG1 Z10" + +#endif // Z_STEPPER_AUTO_ALIGN + +/** + * Assisted Tramming + * + * Add the G35 command to measure bed corners and help adjust screws. Requires a bed probe. + */ //#define ASSISTED_TRAMMING #if ENABLED(ASSISTED_TRAMMING) @@ -1103,120 +1157,179 @@ //#define ASSISTED_TRAMMING_WAIT_POSITION { X_CENTER, Y_CENTER, 30 } // Move the nozzle out of the way for adjustment /** - * Screw thread: - * M3: 30 = Clockwise, 31 = Counter-Clockwise - * M4: 40 = Clockwise, 41 = Counter-Clockwise - * M5: 50 = Clockwise, 51 = Counter-Clockwise + * Screw Thread. Use one of the following defines: + * + * M3_CW = M3 Clockwise, M3_CCW = M3 Counter-Clockwise + * M4_CW = M4 Clockwise, M4_CCW = M4 Counter-Clockwise + * M5_CW = M5 Clockwise, M5_CCW = M5 Counter-Clockwise + * + * :{'M3_CW':'M3 Clockwise','M3_CCW':'M3 Counter-Clockwise','M4_CW':'M4 Clockwise','M4_CCW':'M4 Counter-Clockwise','M5_CW':'M5 Clockwise','M5_CCW':'M5 Counter-Clockwise'} */ - #define TRAMMING_SCREW_THREAD 30 + #define TRAMMING_SCREW_THREAD M3_CW #endif // @section motion control /** - * Fixed-time-based Motion Control -- EXPERIMENTAL - * Enable/disable and set parameters with G-code M493. + * Fixed-time-based Motion Control -- BETA FEATURE + * Enable/disable and set parameters with G-code M493 and M494. + * 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_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_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 + //#define FTM_IS_DEFAULT_MOTION // Use FT Motion as the factory default? + //#define FT_MOTION_MENU // Provide a MarlinUI menu to set M493 and M494 parameters - #define FTM_SHAPING_V_TOL_X 0.05f // Vibration tolerance used by EI input shapers for X axis - #define FTM_SHAPING_V_TOL_Y 0.05f // Vibration tolerance used by EI input shapers for Y axis + //#define NO_STANDARD_MOTION // Disable the standard motion system entirely to save Flash and RAM + #if DISABLED(NO_STANDARD_MOTION) + //#define FTM_HOME_AND_PROBE // Use FT Motion for homing / probing. Disable if FT Motion breaks these functions. + #endif - //#define FT_MOTION_MENU // Provide a MarlinUI menu to set M493 parameters + //#define FTM_DYNAMIC_FREQ // Enable for linear adjustment of XY shaping frequency according to Z or E + #if ENABLED(FTM_DYNAMIC_FREQ) + #define FTM_DEFAULT_DYNFREQ_MODE dynFreqMode_DISABLED // Default mode of dynamic frequency calculation. (DISABLED, Z_BASED, MASS_BASED) + #endif + + // Disable unused shapers if you need more free space + #define FTM_SHAPER_ZV + #define FTM_SHAPER_ZVD + #define FTM_SHAPER_ZVDD + #define FTM_SHAPER_ZVDDD + #define FTM_SHAPER_EI + #define FTM_SHAPER_2HEI + #define FTM_SHAPER_3HEI + #define FTM_SHAPER_MZV + + #define FTM_DEFAULT_SHAPER_X ftMotionShaper_NONE // Default shaper mode on X axis (NONE, ZV, ZVD, ZVDD, ZVDDD, EI, 2HEI, 3HEI, MZV) + #define FTM_SHAPING_DEFAULT_FREQ_X 37.0f // (Hz) Default peak frequency used by input shapers + #define FTM_SHAPING_ZETA_X 0.1f // Zeta used by input shapers for X axis + #define FTM_SHAPING_V_TOL_X 0.05f // Vibration tolerance used by EI input shapers for X axis + + #define FTM_DEFAULT_SHAPER_Y ftMotionShaper_NONE // Default shaper mode on Y axis + #define FTM_SHAPING_DEFAULT_FREQ_Y 37.0f // (Hz) Default peak frequency used by input shapers + #define FTM_SHAPING_ZETA_Y 0.1f // Zeta used by input shapers for Y axis + #define FTM_SHAPING_V_TOL_Y 0.05f // Vibration tolerance used by EI input shapers for Y axis + + //#define FTM_SHAPER_Z // Include Z shaping support + #define FTM_DEFAULT_SHAPER_Z ftMotionShaper_NONE // Default shaper mode on Z axis + #define FTM_SHAPING_DEFAULT_FREQ_Z 21.0f // (Hz) Default peak frequency used by input shapers + #define FTM_SHAPING_ZETA_Z 0.03f // Zeta used by input shapers for Z axis + #define FTM_SHAPING_V_TOL_Z 0.05f // Vibration tolerance used by EI input shapers for Z axis + + //#define FTM_SHAPER_E // Include E shaping support + // Required to synchronize extruder with XYZ (better quality) + #define FTM_DEFAULT_SHAPER_E ftMotionShaper_NONE // Default shaper mode on Extruder axis + #define FTM_SHAPING_DEFAULT_FREQ_E 21.0f // (Hz) Default peak frequency used by input shapers + #define FTM_SHAPING_ZETA_E 0.03f // Zeta used by input shapers for E axis + #define FTM_SHAPING_V_TOL_E 0.05f // Vibration tolerance used by EI input shapers for E axis + + //#define FTM_SMOOTHING // Smoothing can reduce artifacts and make steppers quieter + // on sharp corners, but too much will round corners. + #if ENABLED(FTM_SMOOTHING) + #define FTM_MAX_SMOOTHING_TIME 0.10f // (s) Maximum smoothing time. Higher values consume more RAM. + // Increase smoothing time to reduce jerky motion, ghosting and noises. + #define FTM_SMOOTHING_TIME_X 0.00f // (s) Smoothing time for X axis. Zero means disabled. + #define FTM_SMOOTHING_TIME_Y 0.00f // (s) Smoothing time for Y axis + #define FTM_SMOOTHING_TIME_Z 0.00f // (s) Smoothing time for Z axis + #define FTM_SMOOTHING_TIME_E 0.02f // (s) Smoothing time for E axis. Prevents noise/skipping from LA by + // smoothing acceleration peaks, which may also smooth curved surfaces. + #endif + + #define FTM_POLYS // Disable POLY5/6 to save ~3k of Flash. Preserves TRAPEZOIDAL. + #if ENABLED(FTM_POLYS) + #define FTM_POLY6_ACCELERATION_OVERSHOOT 1.875f // Max acceleration overshoot factor for POLY6 (1.25 to 1.875) + #endif /** - * Advanced configuration + * FTM Constant-Jolt Trajectory (7-phase S-curve). + * Jolt is the rate of change of acceleration, not related to Marlin's "classic jerk." + * Ramps acceleration gradually so max acceleration is limited by max speed and distance traveled. */ - #define FTM_UNIFIED_BWS // DON'T DISABLE unless you use Ulendo FBS (not implemented) - #if ENABLED(FTM_UNIFIED_BWS) - #define FTM_BW_SIZE 100 // Unified Window and Batch size with a ratio of 2 - #else - #define FTM_WINDOW_SIZE 200 // Custom Window size for trajectory generation needed by Ulendo FBS - #define FTM_BATCH_SIZE 100 // Custom Batch size for trajectory generation needed by Ulendo FBS + //#define FTM_CONSTANT_JOLT + #if ENABLED(FTM_CONSTANT_JOLT) + #define FTM_DEFAULT_JOLT 250.0f // (m/s³) Default jolt for constant-jolt trajectory. + // Higher values print faster at the cost of increased resonance and extruder stress #endif - #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() + // Block acceleration profile + // :[ 'TRAPEZOIDAL', 'POLY5', 'POLY6', 'CONSTANT_JOLT' ] + #define FTM_TRAJECTORY_TYPE TRAPEZOIDAL // TRAPEZOIDAL: Continuous Velocity. Max acceleration is respected. + // POLY5: Like POLY6 with 1.5x but uses less CPU. Requires FTM_POLYS. + // POLY6: Continuous Acceleration (aka S_CURVE). Requires FTM_POLYS. + // CONSTANT_JOLT: 7-phase S-curve. Requires FTM_CONSTANT_JOLT. + // POLY trajectories not only reduce resonances without rounding corners, but + // also reduce extruder strain due to linear advance. - #if DISABLED(COREXY) - #define FTM_STEPPER_FS 20000 // (Hz) Frequency for stepper I/O update + /** + * Advanced configuration + */ + #define FTM_BUFFER_SIZE 128 // Window size for trajectory generation, must be a power of 2 (e.g 64, 128, 256, ...) + // The total buffered time in seconds is (FTM_BUFFER_SIZE/FTM_FS) + #define FTM_FS 1000 // (Hz) Frequency for trajectory generation. + #define FTM_MIN_SHAPE_FREQ 20 // (Hz) Minimum shaping frequency, lower consumes more RAM - // 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 - #define FTM_STEPPERCMD_BUFF_SIZE 6000 + /** + * TMC2208 / TMC2208_STANDALONE drivers require a brief pause after a DIR change + * to prevent a standstill shutdown when using StealthChop (the standalone default). + * These options cause FT Motion to delay for > 750µs after a DIR change on a given axis. + * Disable only if you are certain that this can never happen with your TMC2208s. + */ + #if AXIS_DRIVER_TYPE_X(TMC2208) || AXIS_DRIVER_TYPE_X(TMC2208_STANDALONE) + #define FTM_DIR_CHANGE_HOLD_X + #endif + #if AXIS_DRIVER_TYPE_Y(TMC2208) || AXIS_DRIVER_TYPE_Y(TMC2208_STANDALONE) + #define FTM_DIR_CHANGE_HOLD_Y + #endif + #if AXIS_DRIVER_TYPE_Z(TMC2208) || AXIS_DRIVER_TYPE_Z(TMC2208_STANDALONE) + #define FTM_DIR_CHANGE_HOLD_Z + #endif + #if HAS_E_DRIVER(TMC2208) || HAS_E_DRIVER(TMC2208_STANDALONE) + #define FTM_DIR_CHANGE_HOLD_E #endif - #define FTM_STEPS_PER_UNIT_TIME (FTM_STEPPER_FS / FTM_FS) // Interpolated stepper commands per unit time - #define FTM_CTS_COMPARE_VAL (FTM_STEPS_PER_UNIT_TIME / 2) // Comparison value used in interpolation algorithm - #define FTM_MIN_TICKS ((STEPPER_TIMER_RATE) / (FTM_STEPPER_FS)) // Minimum stepper ticks between steps - - #define FTM_MIN_SHAPE_FREQ 10 // Minimum shaping frequency - #define FTM_RATIO (FTM_FS / FTM_MIN_SHAPE_FREQ) // Factor for use in FTM_ZMAX. DON'T CHANGE. - #define FTM_ZMAX (FTM_RATIO * 2) // Maximum delays for shaping functions (even numbers only!) - // Calculate as: - // ZV : FTM_RATIO / 2 - // ZVD, MZV : FTM_RATIO - // 2HEI : FTM_RATIO * 3 / 2 - // 3HEI : FTM_RATIO * 2 -#endif +#endif // FT_MOTION /** - * Input Shaping -- EXPERIMENTAL + * Input Shaping * * Zero Vibration (ZV) Input Shaping for X and/or Y movements. * * This option uses a lot of SRAM for the step buffer. The buffer size is - * calculated automatically from SHAPING_FREQ_[XY], DEFAULT_AXIS_STEPS_PER_UNIT, + * calculated automatically from SHAPING_FREQ_[XYZ], DEFAULT_AXIS_STEPS_PER_UNIT, * DEFAULT_MAX_FEEDRATE and ADAPTIVE_STEP_SMOOTHING. The default calculation can * be overridden by setting SHAPING_MIN_FREQ and/or SHAPING_MAX_FEEDRATE. * The higher the frequency and the lower the feedrate, the smaller the buffer. * If the buffer is too small at runtime, input shaping will have reduced * effectiveness during high speed movements. * - * Tune with M593 D F: - * - * D Set the zeta/damping factor. If axes (X, Y, etc.) are not specified, set for all axes. - * F Set the frequency. If axes (X, Y, etc.) are not specified, set for all axes. - * T[map] Input Shaping type, 0:ZV, 1:EI, 2:2H EI (not implemented yet) - * X<1> Set the given parameters only for the X axis. - * Y<1> Set the given parameters only for the Y axis. + * Tune with M593 D F */ //#define INPUT_SHAPING_X //#define INPUT_SHAPING_Y -#if ANY(INPUT_SHAPING_X, INPUT_SHAPING_Y) +//#define INPUT_SHAPING_Z +#if ANY(INPUT_SHAPING_X, INPUT_SHAPING_Y, INPUT_SHAPING_Z) #if ENABLED(INPUT_SHAPING_X) - #define SHAPING_FREQ_X 40 // (Hz) The default dominant resonant frequency on the X axis. - #define SHAPING_ZETA_X 0.15f // Damping ratio of the X axis (range: 0.0 = no damping to 1.0 = critical damping). + #define SHAPING_FREQ_X 40.0 // (Hz) The default dominant resonant frequency on the X axis. + #define SHAPING_ZETA_X 0.15 // Damping ratio of the X axis (range: 0.0 = no damping to 1.0 = critical damping). #endif #if ENABLED(INPUT_SHAPING_Y) - #define SHAPING_FREQ_Y 40 // (Hz) The default dominant resonant frequency on the Y axis. - #define SHAPING_ZETA_Y 0.15f // Damping ratio of the Y axis (range: 0.0 = no damping to 1.0 = critical damping). + #define SHAPING_FREQ_Y 40.0 // (Hz) The default dominant resonant frequency on the Y axis. + #define SHAPING_ZETA_Y 0.15 // Damping ratio of the Y axis (range: 0.0 = no damping to 1.0 = critical damping). #endif - //#define SHAPING_MIN_FREQ 20 // By default the minimum of the shaping frequencies. Override to affect SRAM usage. + #if ENABLED(INPUT_SHAPING_Z) + #define SHAPING_FREQ_Z 40.0 // (Hz) The default dominant resonant frequency on the Z axis. + #define SHAPING_ZETA_Z 0.15 // Damping ratio of the Z axis (range: 0.0 = no damping to 1.0 = critical damping). + #endif + //#define SHAPING_MIN_FREQ 20.0 // (Hz) By default the minimum of the shaping frequencies. Override to affect SRAM usage. //#define SHAPING_MAX_STEPRATE 10000 // By default the maximum total step rate of the shaped axes. Override to affect SRAM usage. //#define SHAPING_MENU // Add a menu to the LCD to set shaping parameters. #endif +#if ANY(INPUT_SHAPING_X, INPUT_SHAPING_Y, INPUT_SHAPING_Z, FTM_SHAPER_ZV, FTM_SHAPER_ZVD, FTM_SHAPER_ZVDD, FTM_SHAPER_ZVDDD, FTM_SHAPER_EI, FTM_SHAPER_2HEI, FTM_SHAPER_3HEI, FTM_SHAPER_MZV) + //#define RESONANCE_TEST // Sine sweep motion for resonance study +#endif + // @section motion #define AXIS_RELATIVE_MODES { false, false, false, false } @@ -1254,8 +1367,8 @@ #define DISABLE_IDLE_E // Shut down all idle extruders // Default Minimum Feedrates for printing and travel moves -#define DEFAULT_MINIMUMFEEDRATE 0.0 // (mm/s. °/s for rotational-only moves) Minimum feedrate. Set with M205 S. -#define DEFAULT_MINTRAVELFEEDRATE 0.0 // (mm/s. °/s for rotational-only moves) Minimum travel feedrate. Set with M205 T. +#define DEFAULT_MINIMUMFEEDRATE 0.0 // (mm/s) Minimum feedrate. Set with M205 S. +#define DEFAULT_MINTRAVELFEEDRATE 0.0 // (mm/s) Minimum travel feedrate. Set with M205 T. // Minimum time that a segment needs to take as the buffer gets emptied #define DEFAULT_MINSEGMENTTIME 20000 // (µs) Set with M205 B. @@ -1271,11 +1384,11 @@ * XY Frequency limit * Reduce resonance by limiting the frequency of small zigzag infill moves. * See https://hydraraptor.blogspot.com/2010/12/frequency-limit.html - * Use M201 F G to change limits at runtime. + * Use M201 F S to change limits at runtime. */ #define XY_FREQUENCY_LIMIT 10 // (Hz) Maximum frequency of small zigzag infill moves. Set with M201 F. #ifdef XY_FREQUENCY_LIMIT - #define XY_FREQUENCY_MIN_PERCENT 5 // (%) Minimum FR percentage to apply. Set with M201 G. + #define XY_FREQUENCY_MIN_PERCENT 5 // (%) Minimum FR percentage to apply. Set with M201 S. #endif // @@ -1333,22 +1446,20 @@ //#define CALIBRATION_SCRIPT_PRE "M117 Starting Auto-Calibration\nT0\nG28\nG12\nM117 Calibrating..." //#define CALIBRATION_SCRIPT_POST "M500\nM117 Calibration data saved" - #define CALIBRATION_MEASUREMENT_RESOLUTION 0.01 // mm - - #define CALIBRATION_FEEDRATE_SLOW 60 // mm/min - #define CALIBRATION_FEEDRATE_FAST 1200 // mm/min - #define CALIBRATION_FEEDRATE_TRAVEL 3000 // mm/min + #define CALIBRATION_FEEDRATE_SLOW 60 // (mm/min) + #define CALIBRATION_FEEDRATE_FAST 1200 // (mm/min) + #define CALIBRATION_FEEDRATE_TRAVEL 3000 // (mm/min) // The following parameters refer to the conical section of the nozzle tip. - #define CALIBRATION_NOZZLE_TIP_HEIGHT 1.0 // mm - #define CALIBRATION_NOZZLE_OUTER_DIAMETER 2.0 // mm + #define CALIBRATION_NOZZLE_TIP_HEIGHT 1.0 // (mm) + #define CALIBRATION_NOZZLE_OUTER_DIAMETER 2.0 // (mm) // Uncomment to enable reporting (required for "G425 V", but consumes flash). //#define CALIBRATION_REPORTING // The true location and dimension the cube/bolt/washer on the bed. - #define CALIBRATION_OBJECT_CENTER { 264.0, -22.0, -2.0 } // mm - #define CALIBRATION_OBJECT_DIMENSIONS { 10.0, 10.0, 10.0 } // mm + #define CALIBRATION_OBJECT_CENTER { 264.0, -22.0, -2.0 } // (mm) + #define CALIBRATION_OBJECT_DIMENSIONS { 10.0, 10.0, 10.0 } // (mm) // Comment out any sides which are unreachable by the probe. For best // auto-calibration results, all sides must be reachable. @@ -1387,7 +1498,7 @@ * Multi-stepping sends steps in bursts to reduce MCU usage for high step-rates. * This allows higher feedrates than the MCU could otherwise support. */ -#define MULTISTEPPING_LIMIT 16 //: [1, 2, 4, 8, 16, 32, 64, 128] +#define MULTISTEPPING_LIMIT 16 // :[1, 2, 4, 8, 16, 32, 64, 128] /** * Adaptive Step Smoothing increases the resolution of multi-axis moves, particularly at step frequencies @@ -1412,24 +1523,24 @@ #define MICROSTEP_MODES { 16, 16, 16, 16, 16, 16 } // [1,2,4,8,16] /** - * @section stepper motor current + * @section stepper motor current * - * Some boards have a means of setting the stepper motor current via firmware. + * Some boards have a means of setting the stepper motor current via firmware. * - * The power on motor currents are set by: - * PWM_MOTOR_CURRENT - used by MINIRAMBO & ULTIMAIN_2 - * known compatible chips: A4982 - * DIGIPOT_MOTOR_CURRENT - used by BQ_ZUM_MEGA_3D, RAMBO & SCOOVO_X9H - * known compatible chips: AD5206 - * DAC_MOTOR_CURRENT_DEFAULT - used by PRINTRBOARD_REVF & RIGIDBOARD_V2 - * known compatible chips: MCP4728 - * DIGIPOT_I2C_MOTOR_CURRENTS - used by 5DPRINT, AZTEEG_X3_PRO, AZTEEG_X5_MINI_WIFI, MIGHTYBOARD_REVE - * known compatible chips: MCP4451, MCP4018 + * The power on motor currents are set by: + * PWM_MOTOR_CURRENT - used by MINIRAMBO & ULTIMAIN_2 + * known compatible chips: A4982 + * DIGIPOT_MOTOR_CURRENT - used by BQ_ZUM_MEGA_3D, RAMBO & SCOOVO_X9H + * known compatible chips: AD5206 + * DAC_MOTOR_CURRENT_DEFAULT - used by PRINTRBOARD_REVF & RIGIDBOARD_V2 + * known compatible chips: MCP4728 + * DIGIPOT_I2C_MOTOR_CURRENTS - used by 5DPRINT, AZTEEG_X3_PRO, AZTEEG_X5_MINI_WIFI, MIGHTYBOARD_REVE + * known compatible chips: MCP4451, MCP4018 * - * Motor currents can also be set by M907 - M910 and by the LCD. - * M907 - applies to all. - * M908 - BQ_ZUM_MEGA_3D, RAMBO, PRINTRBOARD_REVF, RIGIDBOARD_V2 & SCOOVO_X9H - * M909, M910 & LCD - only PRINTRBOARD_REVF & RIGIDBOARD_V2 + * Motor currents can also be set by M907 - M910 and by the LCD. + * M907 - applies to all. + * M908 - BQ_ZUM_MEGA_3D, RAMBO, PRINTRBOARD_REVF, RIGIDBOARD_V2 & SCOOVO_X9H + * M909, M910 & LCD - only PRINTRBOARD_REVF & RIGIDBOARD_V2 */ //#define PWM_MOTOR_CURRENT { 1300, 1300, 1250 } // Values in milliamps //#define DIGIPOT_MOTOR_CURRENT { 135,135,135,135,135 } // Values 0-255 (RAMBO 135 = ~0.75A, 185 = ~1A) @@ -1469,6 +1580,9 @@ // @section lcd +// Turn off the display blinking that warns about possible accuracy reduction +//#define DISABLE_REDUCED_ACCURACY_WARNING + #if HAS_MANUAL_MOVE_MENU #define MANUAL_FEEDRATE { 50*60, 50*60, 4*60, 2*60 } // (mm/min) Feedrates for manual moves along X, Y, Z, E from panel #define FINE_MANUAL_MOVE 0.025 // (mm) Smallest manual move (< 0.1mm) applying to Z on most machines @@ -1493,6 +1607,15 @@ #define FEEDRATE_CHANGE_BEEP_FREQUENCY 440 #endif +/** + * Probe Offset Wizard + * Add a Probe Z Offset calibration option to the LCD menu. + * Use this helper to get a perfect 'M851 Z' probe offset. + * When launched this powerful wizard: + * - Measures the bed height at the configured position with the probe. + * - Moves the nozzle to the same position for a "paper" measurement. + * - The difference is used to set the probe Z offset. + */ #if HAS_BED_PROBE && ANY(HAS_MARLINUI_MENU, HAS_TFT_LVGL_UI) #define PROBE_OFFSET_WIZARD // Add a Probe Z Offset calibration option to the LCD menu #if ENABLED(PROBE_OFFSET_WIZARD) @@ -1511,6 +1634,10 @@ #if HAS_MARLINUI_MENU #if HAS_BED_PROBE + + // Show Deploy / Stow Probe options in the Motion menu. + #define PROBE_DEPLOY_STOW_MENU + // Add calibration in the Probe Offsets menu to compensate for X-axis twist. //#define X_AXIS_TWIST_COMPENSATION #if ENABLED(X_AXIS_TWIST_COMPENSATION) @@ -1525,14 +1652,13 @@ #define XATC_Z_OFFSETS { 0, 0, 0 } // Z offsets for X axis sample points #endif - // Show Deploy / Stow Probe options in the Motion menu. - #define PROBE_DEPLOY_STOW_MENU - #endif + #endif // HAS_BED_PROBE // Include a page of printer information in the LCD Main Menu #define LCD_INFO_MENU #if ENABLED(LCD_INFO_MENU) //#define LCD_PRINTER_INFO_IS_BOOTSCREEN // Show bootscreen(s) instead of Printer Info pages + //#define BUILD_INFO_MENU_ITEM // Add a menu item to display the build date and time #endif /** @@ -1557,6 +1683,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 @@ -1585,7 +1714,7 @@ #if HAS_MARLINUI_U8GLIB //#define BOOT_MARLIN_LOGO_ANIMATED // Animated Marlin logo. Costs ~3260 (or ~940) bytes of flash. #endif - #if ANY(HAS_MARLINUI_U8GLIB, TOUCH_UI_FTDI_EVE) + #if ANY(HAS_MARLINUI_U8GLIB, TOUCH_UI_FTDI_EVE, HAS_MARLINUI_HD44780, HAS_GRAPHICAL_TFT) //#define SHOW_CUSTOM_BOOTSCREEN // Show the bitmap in Marlin/_Bootscreen.h on startup. #endif #endif @@ -1597,6 +1726,10 @@ //#define SOUND_MENU_ITEM // Add a mute option to the LCD menu #define SOUND_ON_DEFAULT // Buzzer/speaker default enabled state + #if ENABLED(U8GLIB_SSD1309) + //#define LCD_DOUBLE_BUFFER // Optimize display updates. Costs ~1K of SRAM. + #endif + #if HAS_WIRED_LCD //#define DOUBLE_LCD_FRAMERATE // Not recommended for slow boards. #endif @@ -1647,11 +1780,12 @@ #endif // HAS_DISPLAY -#if HAS_FEEDRATE_EDIT +// Some displays offer Feedrate / Flow editing. +#if ANY(HAS_MARLINUI_MENU, DWIN_CREALITY_LCD, DWIN_LCD_PROUI, MALYAN_LCD, TOUCH_SCREEN, ULTIPANEL_FEEDMULTIPLY) #define SPEED_EDIT_MIN 10 // (%) Feedrate percentage edit range minimum #define SPEED_EDIT_MAX 999 // (%) Feedrate percentage edit range maximum #endif -#if HAS_FLOW_EDIT +#if ANY(HAS_MARLINUI_MENU, DWIN_CREALITY_LCD, DWIN_LCD_PROUI, MALYAN_LCD, TOUCH_SCREEN) #define FLOW_EDIT_MIN 10 // (%) Flow percentage edit range minimum #define FLOW_EDIT_MAX 999 // (%) Flow percentage edit range maximum #endif @@ -1702,6 +1836,12 @@ */ //#define SD_SPI_SPEED SPI_HALF_SPEED + /** + * Reinit the LCD after SD Card insert/remove or when entering the menu. + * Required for some LCDs that use shared SPI with an external SD Card reader. + */ + #define REINIT_NOISY_LCD + // The standard SD detect circuit reads LOW when media is inserted and HIGH when empty. // Enable this option and set to HIGH if your SD cards are incorrectly detected. //#define SD_DETECT_STATE HIGH @@ -1736,6 +1876,14 @@ #define PE_LEDS_COMPLETED_TIME (30*60) // (seconds) Time to keep the LED "done" color before restoring normal illumination #endif + /** + * Priming for the Remaining Time estimate + * Long processes at the start of a G-code file can skew the Remaining Time estimate. + * Enable these options to start this estimation at a later point in the G-code file. + */ + //#define REMAINING_TIME_PRIME // Provide G-code 'M75 R' to prime the Remaining Time estimate + //#define REMAINING_TIME_AUTOPRIME // Prime the Remaining Time estimate later (e.g., at the end of 'M109') + /** * Continue after Power-Loss (Creality3D) * @@ -1746,7 +1894,9 @@ */ #define POWER_LOSS_RECOVERY #if ENABLED(POWER_LOSS_RECOVERY) - #define PLR_ENABLED_DEFAULT true // Power-Loss Recovery enabled by default. (Set with 'M413 Sn' & M500) + #define PLR_ENABLED_DEFAULT true // Power-Loss Recovery enabled by default. (Set with 'M413 Sn' & M500) + //#define PLR_HEAT_BED_ON_REBOOT // Heat up bed immediately on reboot to mitigate object detaching/warping. + //#define PLR_HEAT_BED_EXTRA 0 // (°C) Relative increase of bed temperature for better adhesion (limited by max temp). //#define PLR_BED_THRESHOLD BED_MAXTEMP // (°C) Skip user confirmation at or above this bed temperature (0 to disable) #define POWER_LOSS_PIN PA8 // Pin to detect power-loss. Set to -1 to disable default pin on boards without module, or comment to use board default. @@ -1800,16 +1950,21 @@ // SD Card Sorting options #if ENABLED(SDCARD_SORT_ALPHA) - #define SDSORT_REVERSE false // Default to sorting file names in reverse order. - #define SDSORT_LIMIT 40 // Maximum number of sorted items (10-256). Costs 27 bytes each. - #define SDSORT_FOLDERS -1 // -1=above 0=none 1=below - #define SDSORT_GCODE false // Enable G-code M34 to set sorting behaviors: M34 S<-1|0|1> F<-1|0|1> - #define SDSORT_USES_RAM false // Pre-allocate a static array for faster pre-sorting. - #define SDSORT_USES_STACK false // Prefer the stack for pre-sorting to give back some SRAM. (Negated by next 2 options.) - #define SDSORT_CACHE_NAMES false // Keep sorted items in RAM longer for speedy performance. Most expensive option. - #define SDSORT_DYNAMIC_RAM false // Use dynamic allocation (within SD menus). Least expensive option. Set SDSORT_LIMIT before use! - #define SDSORT_CACHE_VFATS 2 // Maximum number of 13-byte VFAT entries to use for sorting. - // Note: Only affects SCROLL_LONG_FILENAMES with SDSORT_CACHE_NAMES but not SDSORT_DYNAMIC_RAM. + #define SDSORT_QUICK true // Use Quick Sort as a sorting algorithm. Otherwise use Bubble Sort. + #define SDSORT_REVERSE false // Default to sorting file names in reverse order. + #define SDSORT_LIMIT 40 // Maximum number of sorted items (10-256). Costs 27 bytes each. + #define SDSORT_FOLDERS -1 // -1=above 0=none 1=below + #define SDSORT_GCODE false // Enable G-code M34 to set sorting behaviors: M34 S<-1|0|1> F<-1|0|1> + #define SDSORT_USES_STACK false // Prefer the stack for pre-sorting to give back some SRAM. (Negated by next 2 options.) + #define SDSORT_USES_RAM false // Pre-allocate a static array for faster pre-sorting. + #if ENABLED(SDSORT_USES_RAM) + #define SDSORT_CACHE_NAMES false // Keep sorted items in RAM longer for speedy performance. Most expensive option. + #if ENABLED(SDSORT_CACHE_NAMES) + #define SDSORT_DYNAMIC_RAM false // Use dynamic allocation (within SD menus). Least expensive option. Set SDSORT_LIMIT before use! + #define SDSORT_CACHE_VFATS 2 // Maximum number of 13-byte VFAT entries to use for sorting. + // Note: Only affects SCROLL_LONG_FILENAMES with SDSORT_CACHE_NAMES but not SDSORT_DYNAMIC_RAM. + #endif + #endif #endif // Allow international symbols in long filenames. To display correctly, the @@ -1848,7 +2003,7 @@ * * SCLK, MOSI, MISO --> SCLK, MOSI, MISO * INT --> SD_DETECT_PIN [1] - * SS --> SDSS + * SS --> SD_SS_PIN * * [1] On AVR an interrupt-capable pin is best for UHS3 compatibility. */ @@ -1875,7 +2030,7 @@ //#define USE_OTG_USB_HOST #if DISABLED(USE_OTG_USB_HOST) - #define USB_CS_PIN SDSS + #define USB_CS_PIN SD_SS_PIN #define USB_INTR_PIN SD_DETECT_PIN #endif #endif @@ -1912,6 +2067,9 @@ //#define CUSTOM_FIRMWARE_UPLOAD #endif + // "Over-the-air" Firmware Update with M936 - Required to set EEPROM flag + //#define OTA_FIRMWARE_UPDATE + /** * Set this option to one of the following (or the board's defaults apply): * @@ -1934,8 +2092,8 @@ #if ENABLED(MULTI_VOLUME) #define VOLUME_SD_ONBOARD #define VOLUME_USB_FLASH_DRIVE - #define DEFAULT_VOLUME SV_SD_ONBOARD - #define DEFAULT_SHARED_VOLUME SV_USB_FLASH_DRIVE + #define DEFAULT_VOLUME SD_ONBOARD // :[ 'SD_ONBOARD', 'USB_FLASH_DRIVE' ] + #define DEFAULT_SHARED_VOLUME USB_FLASH_DRIVE // :[ 'SD_ONBOARD', 'USB_FLASH_DRIVE' ] #endif #endif // HAS_MEDIA @@ -1986,7 +2144,7 @@ #if IS_U8GLIB_ST7920 // Enable this option and reduce the value to optimize screen updates. // The normal delay is 10µs. Use the lowest value that still gives a reliable display. - //#define DOGM_SPI_DELAY_US 5 + //#define DOGM_SPI_DELAY_US 5 // (µs) Delay after each SPI transfer //#define LIGHTWEIGHT_UI #if ENABLED(LIGHTWEIGHT_UI) @@ -2016,17 +2174,17 @@ //#define STATUS_HEAT_PERCENT // Show heating in a progress bar //#define STATUS_HEAT_POWER // Show heater output power as a vertical bar - // Frivolous Game Options - //#define MARLIN_BRICKOUT - //#define MARLIN_INVADERS - //#define MARLIN_SNAKE - //#define GAMES_EASTER_EGG // Add extra blank lines above the "Games" sub-menu - #endif // HAS_MARLINUI_U8GLIB #if HAS_MARLINUI_U8GLIB || IS_DWIN_MARLINUI #define MENU_HOLLOW_FRAME // Enable to save many cycles by drawing a hollow frame on Menu Screens //#define OVERLAY_GFX_REVERSE // Swap the CW/CCW indicators in the graphics overlay + + // Frivolous Game Options + //#define MARLIN_BRICKOUT + //#define MARLIN_INVADERS + //#define MARLIN_SNAKE + //#define GAMES_EASTER_EGG // Add extra blank lines above the "Games" sub-menu #endif // @@ -2205,7 +2363,7 @@ // Developer menu (accessed by touching "About Printer" copyright text) //#define TOUCH_UI_DEVELOPER_MENU -#endif +#endif // TOUCH_UI_FTDI_EVE // // Classic UI Options @@ -2239,9 +2397,11 @@ // ADC Button Debounce // #if HAS_ADC_BUTTONS - #define ADC_BUTTON_DEBOUNCE_DELAY 16 // Increase if buttons bounce or repeat too fast + #define ADC_BUTTON_DEBOUNCE_DELAY 16 // (count) Increase if buttons bounce or repeat too fast #endif +//#define FAST_BUTTON_POLLING // Poll buttons at ~1kHz on 8-bit AVR. Set to 'false' for slow polling on 32-bit. + // @section safety /** @@ -2258,7 +2418,7 @@ //#define WATCHDOG_RESET_MANUAL #endif -// @section lcd +// @section baby-stepping /** * Babystepping enables movement of the axes by tiny increments without changing @@ -2280,7 +2440,7 @@ #define DOUBLECLICK_FOR_Z_BABYSTEPPING // Double-click on the Status Screen for Z Babystepping. #if ENABLED(DOUBLECLICK_FOR_Z_BABYSTEPPING) - #define DOUBLECLICK_MAX_INTERVAL 1250 // Maximum interval between clicks, in milliseconds. + #define DOUBLECLICK_MAX_INTERVAL 1250 // (ms) Maximum interval between clicks. // Note: Extra time may be added to mitigate controller latency. #define MOVE_Z_WHEN_IDLE // Jump to the move Z menu on double-click when printer is idle. #if ENABLED(MOVE_Z_WHEN_IDLE) @@ -2315,15 +2475,37 @@ * See https://marlinfw.org/docs/features/lin_advance.html for full instructions. */ //#define LIN_ADVANCE -#if ENABLED(LIN_ADVANCE) + +#if ANY(LIN_ADVANCE, FT_MOTION) #if ENABLED(DISTINCT_E_FACTORS) - #define ADVANCE_K { 0.22 } // (mm) Compression length per 1mm/s extruder speed, per extruder + #define ADVANCE_K { 0.22 } // (mm) Compression length per 1mm/s extruder speed, per extruder. Override with 'M900 T K'. #else - #define ADVANCE_K 0.22 // (mm) Compression length applying to all extruders + #define ADVANCE_K 0.22 // (mm) Compression length for all extruders. Override with 'M900 K'. #endif - //#define ADVANCE_K_EXTRA // Add a second linear advance constant, configurable with M900 L. + //#define ADVANCE_K_EXTRA // Add a second linear advance constant, configurable with 'M900 L'. +#endif + +#if ENABLED(LIN_ADVANCE) //#define LA_DEBUG // Print debug information to serial during operation. Disable for production use. //#define EXPERIMENTAL_I2S_LA // Allow I2S_STEPPER_STREAM to be used with LA. Performance degrades as the LA step rate reaches ~20kHz. + + //#define SMOOTH_LIN_ADVANCE // Remove limits on acceleration by gradual increase of nozzle pressure + #if ENABLED(SMOOTH_LIN_ADVANCE) + /** + * ADVANCE_TAU is also the time ahead that the smoother needs to look + * into the planner, so the planner needs to have enough blocks loaded. + * For k=0.04 at 10k acceleration and an "Orbiter 2" extruder it can be as low as 0.0075. + * Adjust by lowering the value until you observe the extruder skipping, then raise slightly. + * Higher k and higher XY acceleration may require larger ADVANCE_TAU to avoid skipping steps. + */ + #if ENABLED(DISTINCT_E_FACTORS) + #define ADVANCE_TAU { 0.02 } // (s) Smoothing time to reduce extruder acceleration, per extruder + #else + #define ADVANCE_TAU 0.02 // (s) Smoothing time to reduce extruder acceleration + #endif + #define SMOOTH_LIN_ADV_HZ 1000 // (Hz) How often to update extruder speed + #define INPUT_SHAPING_E_SYNC // Synchronize the extruder-shaped XY axes (to increase precision) + #endif #endif /** @@ -2331,8 +2513,12 @@ * * Control extrusion rate based on instantaneous extruder velocity. Can be used to correct for * underextrusion at high extruder speeds that are otherwise well-behaved (i.e., not skipping). + * For better results also enable ADAPTIVE_STEP_SMOOTHING. */ //#define NONLINEAR_EXTRUSION +#if ENABLED(NONLINEAR_EXTRUSION) + //#define NONLINEAR_EXTRUSION_DEFAULT_ON // Enable if NLE should be ON by default +#endif // @section leveling @@ -2485,13 +2671,15 @@ #endif #endif // PTC_PROBE || PTC_BED || PTC_HOTEND -// @section extras +// @section gcode // // G60/G61 Position Save and Return // //#define SAVED_POSITIONS 1 // Each saved position slot costs 12 bytes +// @section motion + // // G2/G3 Arc Support // @@ -2521,7 +2709,9 @@ * less step aliasing by calculating all motions in advance. * Preparing your G-code: https://github.com/colinrgodsey/step-daemon */ -#define DIRECT_STEPPING +//#define DIRECT_STEPPING + +// @section calibrate /** * G38 Probe Target @@ -2558,27 +2748,28 @@ //#define MINIMUM_STEPPER_PRE_DIR_DELAY 650 /** - * Minimum stepper driver pulse width (in µs) - * 0 : Smallest possible width the MCU can produce, compatible with TMC2xxx drivers - * 0 : Minimum 500ns for LV8729, adjusted in stepper.h - * 1 : Minimum for A4988 and A5984 stepper drivers - * 2 : Minimum for DRV8825 stepper drivers - * 3 : Minimum for TB6600 stepper drivers - * 30 : Minimum for TB6560 stepper drivers + * Minimum stepper driver pulse width (in ns) + * If undefined, these defaults (from Conditionals-4-adv.h) apply: + * 100 : Minimum for TMC2xxx stepper drivers + * 500 : Minimum for LV8729 + * 1000 : Minimum for A4988 and A5984 stepper drivers + * 2000 : Minimum for DRV8825 stepper drivers + * 3000 : Minimum for TB6600 stepper drivers + * 30000 : Minimum for TB6560 stepper drivers * * Override the default value based on the driver type set in Configuration.h. */ -//#define MINIMUM_STEPPER_PULSE 2 +//#define MINIMUM_STEPPER_PULSE_NS 2000 /** * Maximum stepping rate (in Hz) the stepper driver allows - * If undefined, defaults to 1MHz / (2 * MINIMUM_STEPPER_PULSE) + * If undefined, these defaults (from Conditionals-4-adv.h) apply: * 5000000 : Maximum for TMC2xxx stepper drivers * 1000000 : Maximum for LV8729 stepper driver - * 500000 : Maximum for A4988 stepper driver - * 250000 : Maximum for DRV8825 stepper driver - * 150000 : Maximum for TB6600 stepper driver - * 15000 : Maximum for TB6560 stepper driver + * 500000 : Maximum for A4988 stepper driver + * 250000 : Maximum for DRV8825 stepper driver + * 150000 : Maximum for TB6600 stepper driver + * 15000 : Maximum for TB6560 stepper driver * * Override the default value based on the driver type set in Configuration.h. */ @@ -2610,19 +2801,23 @@ #define MAX_CMD_SIZE 96 #define BUFSIZE 4 -// Transmission to Host Buffer Size -// To save 386 bytes of flash (and TX_BUFFER_SIZE+3 bytes of RAM) set to 0. -// To buffer a simple "ok" you need 4 bytes. -// For ADVANCED_OK (M105) you need 32 bytes. -// For debug-echo: 128 bytes for the optimal speed. -// Other output doesn't need to be that speedy. -// :[0, 2, 4, 8, 16, 32, 64, 128, 256] +/** + * Host Transmit Buffer Size + * - Costs 386 bytes of flash and TX_BUFFER_SIZE+3 bytes of SRAM (if not 0). + * - 4 bytes required to buffer a simple "ok". + * - 32 bytes for ADVANCED_OK (M105). + * - 128 bytes for the optimal speed of 'debug-echo:' + * - Other output doesn't need to be that speedy. + * :[0, 2, 4, 8, 16, 32, 64, 128, 256] + */ #define TX_BUFFER_SIZE 0 -// Host Receive Buffer Size -// Without XON/XOFF flow control (see SERIAL_XON_XOFF below) 32 bytes should be enough. -// To use flow control, set this buffer size to at least 1024 bytes. -// :[0, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048] +/** + * Host Receive Buffer Size + * Without XON/XOFF flow control (see SERIAL_XON_XOFF below) 32 bytes should be enough. + * To use flow control, set this buffer size to at least 1024 bytes. + * :[0, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048] + */ //#define RX_BUFFER_SIZE 1024 #if RX_BUFFER_SIZE >= 1024 @@ -2639,6 +2834,11 @@ // Enable this option to collect and display the number // of dropped bytes after a file transfer to SD. //#define SERIAL_STATS_DROPPED_RX + + // Enable this option to collect and display framing errors. + // Framing errors occur when invalid start/stop bits or other + // serial protocol violations are detected. + //#define SERIAL_STATS_RX_FRAMING_ERRORS #endif // Monitor RX buffer usage @@ -2666,8 +2866,8 @@ * * Adds support for commands: * S000 : Report State and Position while moving. - * P000 : Instant Pause / Hold while moving. - * R000 : Resume from Pause / Hold. + * P000 : Instant Pause / Hold while moving. Enable SOFT_FEED_HOLD for soft deceleration. + * R000 : Resume from Pause / Hold. Enable SOFT_FEED_HOLD for soft acceleration. * * - During Hold all Emergency Parser commands are available, as usual. * - Enable NANODLP_Z_SYNC and NANODLP_ALL_AXIS for move command end-state reports. @@ -2699,14 +2899,14 @@ * This feature is EXPERIMENTAL so use with caution and test thoroughly. * Enable this option to receive data on the serial ports via the onboard DMA * controller for more stable and reliable high-speed serial communication. - * Only some STM32 MCUs are currently supported. + * Support is currently limited to some STM32 MCUs and all HC32 MCUs. * Note: This has no effect on emulated USB serial ports. */ //#define SERIAL_DMA /** * Set the number of proportional font spaces required to fill up a typical character space. - * This can help to better align the output of commands like `G29 O` Mesh Output. + * This can help to better align the output of commands like 'G29 O' Mesh Output. * * For clients that use a fixed-width font (like OctoPrint), leave this set to 1.0. * Otherwise, adjust according to your client and font. @@ -2724,7 +2924,7 @@ */ //#define EXTRA_FAN_SPEED -// @section gcode +// @section firmware retraction /** * Firmware-based and LCD-controlled retract @@ -2941,7 +3141,7 @@ /** * Trinamic Smart Drivers * - * To use TMC2130, TMC2160, TMC2660, TMC5130, TMC5160 stepper drivers in SPI mode: + * To use TMC2130, TMC2160, TMC2240, TMC2660, TMC5130, TMC5160 stepper drivers in SPI mode: * - Connect your SPI pins to the Hardware SPI interface on the board. * Some boards have simple jumper connections! See your board's documentation. * - Define the required Stepper CS pins in your `pins_MYBOARD.h` file. @@ -2963,15 +3163,25 @@ #define HOLD_MULTIPLIER 0.5 // Scales down the holding current from run current + //#define EDITABLE_HOMING_CURRENT // Add a G-code and menu to modify the Homing Current + /** * Interpolate microsteps to 256 * Override for each driver with _INTERPOLATE settings below */ #define INTERPOLATE true + #if HAS_DRIVER(TMC2240) + #define TMC2240_RREF 12000 // (Ī©) 12000 .. 60000. (FLY TMC2240 = 12300) + // Max Current. Lower for more internal resolution. Raise to run cooler. + #define TMC2240_CURRENT_RANGE 1 // :{ 0:'RMS=690mA PEAK=1A', 1:'RMS=1410mA PEAK=2A', 2:'RMS=2120mA PEAK=3A', 3:'RMS=2110mA PEAK=3A' } + // Slope Control: Lower is more silent. Higher runs cooler. + #define TMC2240_SLOPE_CONTROL 0 // :{ 0:'100V/µs', 1:'200V/µs', 2:'400V/µs', 3:'800V/µs' } + #endif + #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,10 +3391,17 @@ //#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 /** - * Override default SPI pins for TMC2130, TMC2160, TMC2660, TMC5130 and TMC5160 drivers here. + * Override default SPI pins for TMC2130, TMC2160, TMC2240, TMC2660, TMC5130 and TMC5160 drivers here. * The default pins can be found in your board's pins file. */ //#define X_CS_PIN -1 @@ -3211,7 +3428,7 @@ //#define E7_CS_PIN -1 /** - * Software option for SPI driven drivers (TMC2130, TMC2160, TMC2660, TMC5130 and TMC5160). + * Software option for SPI driven drivers (TMC2130, TMC2160, TMC2240, TMC2660, TMC5130 and TMC5160). * The default SW SPI pins are defined the respective pins files, * but you can override or define them here. */ @@ -3270,7 +3487,7 @@ // @section tmc/stealthchop /** - * TMC2130, TMC2160, TMC2208, TMC2209, TMC5130 and TMC5160 only + * TMC2130, TMC2160, TMC2208, TMC2209, TMC2240, TMC5130 and TMC5160 only * Use Trinamic's ultra quiet stepping mode. * When disabled, Marlin will use spreadCycle stepping mode. */ @@ -3349,7 +3566,7 @@ // @section tmc/hybrid /** - * TMC2130, TMC2160, TMC2208, TMC2209, TMC5130 and TMC5160 only + * TMC2130, TMC2160, TMC2208, TMC2209, TMC2240, TMC5130 and TMC5160 only * The driver will switch to spreadCycle when stepper speed is over HYBRID_THRESHOLD. * This mode allows for faster movements at the expense of higher noise levels. * STEALTHCHOP_(XY|Z|E) must be enabled to use HYBRID_THRESHOLD. @@ -3383,20 +3600,20 @@ /** * Use StallGuard to home / probe X, Y, Z. * - * TMC2130, TMC2160, TMC2209, TMC2660, TMC5130, and TMC5160 only + * TMC2130, TMC2160, TMC2209, TMC2240, TMC2660, TMC5130, and TMC5160 only * Connect the stepper driver's DIAG1 pin to the X/Y endstop pin. * X, Y, and Z homing will always be done in spreadCycle mode. * * X/Y/Z_STALL_SENSITIVITY is the default stall threshold. * Use M914 X Y Z to set the stall threshold at runtime: * - * Sensitivity TMC2209 Others - * HIGHEST 255 -64 (Too sensitive => False positive) - * LOWEST 0 63 (Too insensitive => No trigger) + * Sensitivity TMC2209 Others + * HIGHEST 255 -64 (Too sensitive => False positive) + * LOWEST 0 63 (Too insensitive => No trigger) * * It is recommended to set HOMING_BUMP_MM to { 0, 0, 0 }. * - * SPI_ENDSTOPS *** TMC2130/TMC5160 Only *** + * SPI_ENDSTOPS *** TMC2130, TMC2240, and TMC5160 Only *** * Poll the driver through SPI to determine load when homing. * Removes the need for a wire from DIAG1 to an endstop pin. * @@ -3424,9 +3641,16 @@ //#define U_STALL_SENSITIVITY 8 //#define V_STALL_SENSITIVITY 8 //#define W_STALL_SENSITIVITY 8 - //#define SPI_ENDSTOPS // TMC2130/TMC5160 only + //#define SPI_ENDSTOPS // TMC2130, TMC2240, and TMC5160 //#define IMPROVE_HOMING_RELIABILITY - #endif + //#define SENSORLESS_STALLGUARD_DELAY 0 // (ms) Delay to allow drivers to settle + + #if HAS_MARLINUI_MENU + // Convenient homing menu items next to Sensorless Homing edit items + //#define SENSORLESS_HOMING_TEST_MENU_ITEMS + #endif + + #endif // SENSORLESS_HOMING || SENSORLESS_PROBING // @section tmc/config @@ -3445,7 +3669,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. @@ -3523,11 +3747,11 @@ //#define PHOTO_RETRACT_MM 6.5 // (mm) E retract/recover for the photo move (M240 R S) // Canon RC-1 or homebrew digital camera trigger - // Data from: https://www.doc-diy.net/photo/rc-1_hacked/ + // Data from: https://web.archive.org/web/20250327153953/www.doc-diy.net/photo/rc-1_hacked/ //#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 @@ -3559,7 +3783,7 @@ * Add the M3, M4, and M5 commands to turn the spindle/laser on and off, and * to set spindle speed, spindle direction, and laser power. * - * SuperPid is a router/spindle speed controller used in the CNC milling community. + * SuperPID is a router/spindle speed controller used in the CNC milling community. * Marlin can be used to turn the spindle on and off. It can also be used to set * the spindle speed from 5,000 to 30,000 RPM. * @@ -3641,6 +3865,8 @@ #define SPEED_POWER_MIN 5000 // (RPM) #define SPEED_POWER_MAX 30000 // (RPM) SuperPID router controller 0 - 30,000 RPM #define SPEED_POWER_STARTUP 25000 // (RPM) M3/M4 speed/power default (with no arguments) + + //#define DEFAULT_ACCELERATION_SPINDLE 1000 // (°/s/s) Default spindle acceleration (speed change with time) #endif #else @@ -3911,7 +4137,7 @@ #endif /** - * M115 - Report capabilites. Disable to save ~1150 bytes of flash. + * M115 - Report capabilities. Disable to save ~1150 bytes of flash. * Some hosts (and serial TFT displays) rely on this feature. */ #define CAPABILITIES_REPORT @@ -3929,7 +4155,6 @@ * Spend 28 bytes of SRAM to optimize the G-code parser */ #define FASTER_GCODE_PARSER - #if ENABLED(FASTER_GCODE_PARSER) #define GCODE_QUOTED_STRINGS // Support for quoted string parameters #endif @@ -3987,13 +4212,17 @@ /** * G-code Macros * - * Add G-codes M810-M819 to define and run G-code macros. - * Macros are not saved to EEPROM. + * Add G-codes M810-M819 to define and run G-code macros + * and M820 to report the current set of macros. + * Macros are not saved to EEPROM unless enabled below. */ //#define GCODE_MACROS #if ENABLED(GCODE_MACROS) #define GCODE_MACROS_SLOTS 5 // Up to 10 may be used #define GCODE_MACROS_SLOT_SIZE 50 // Maximum length of a single macro + #if ENABLED(EEPROM_SETTINGS) + //#define GCODE_MACROS_IN_EEPROM // Include macros in EEPROM + #endif #endif /** @@ -4015,22 +4244,27 @@ #define MAIN_MENU_ITEM_1_DESC "Home & UBL Info" #define MAIN_MENU_ITEM_1_GCODE "G28\nG29 W" //#define MAIN_MENU_ITEM_1_CONFIRM // Show a confirmation dialog before this action + //#define MAIN_MENU_ITEM_1_IMMEDIATE // Skip the queue and execute immediately. Rarely needed. #define MAIN_MENU_ITEM_2_DESC "Preheat for " PREHEAT_1_LABEL #define MAIN_MENU_ITEM_2_GCODE "M140 S" STRINGIFY(PREHEAT_1_TEMP_BED) "\nM104 S" STRINGIFY(PREHEAT_1_TEMP_HOTEND) //#define MAIN_MENU_ITEM_2_CONFIRM + //#define MAIN_MENU_ITEM_2_IMMEDIATE //#define MAIN_MENU_ITEM_3_DESC "Preheat for " PREHEAT_2_LABEL //#define MAIN_MENU_ITEM_3_GCODE "M140 S" STRINGIFY(PREHEAT_2_TEMP_BED) "\nM104 S" STRINGIFY(PREHEAT_2_TEMP_HOTEND) //#define MAIN_MENU_ITEM_3_CONFIRM + //#define MAIN_MENU_ITEM_3_IMMEDIATE //#define MAIN_MENU_ITEM_4_DESC "Heat Bed/Home/Level" //#define MAIN_MENU_ITEM_4_GCODE "M140 S" STRINGIFY(PREHEAT_2_TEMP_BED) "\nG28\nG29" //#define MAIN_MENU_ITEM_4_CONFIRM + //#define MAIN_MENU_ITEM_4_IMMEDIATE //#define MAIN_MENU_ITEM_5_DESC "Home & Info" //#define MAIN_MENU_ITEM_5_GCODE "G28\nM503" //#define MAIN_MENU_ITEM_5_CONFIRM + //#define MAIN_MENU_ITEM_5_IMMEDIATE #endif // @section custom config menu @@ -4047,22 +4281,27 @@ #define CONFIG_MENU_ITEM_1_DESC "Wifi ON" #define CONFIG_MENU_ITEM_1_GCODE "M118 [ESP110] WIFI-STA pwd=12345678" //#define CONFIG_MENU_ITEM_1_CONFIRM // Show a confirmation dialog before this action + //#define CONFIG_MENU_ITEM_1_IMMEDIATE // Skip the queue and execute immediately. Rarely needed. #define CONFIG_MENU_ITEM_2_DESC "Bluetooth ON" #define CONFIG_MENU_ITEM_2_GCODE "M118 [ESP110] BT pwd=12345678" //#define CONFIG_MENU_ITEM_2_CONFIRM + //#define CONFIG_MENU_ITEM_2_IMMEDIATE //#define CONFIG_MENU_ITEM_3_DESC "Radio OFF" //#define CONFIG_MENU_ITEM_3_GCODE "M118 [ESP110] OFF pwd=12345678" //#define CONFIG_MENU_ITEM_3_CONFIRM + //#define CONFIG_MENU_ITEM_3_IMMEDIATE //#define CONFIG_MENU_ITEM_4_DESC "Wifi ????" //#define CONFIG_MENU_ITEM_4_GCODE "M118 ????" //#define CONFIG_MENU_ITEM_4_CONFIRM + //#define CONFIG_MENU_ITEM_4_IMMEDIATE //#define CONFIG_MENU_ITEM_5_DESC "Wifi ????" //#define CONFIG_MENU_ITEM_5_GCODE "M118 ????" //#define CONFIG_MENU_ITEM_5_CONFIRM + //#define CONFIG_MENU_ITEM_5_IMMEDIATE #endif // @section custom buttons @@ -4079,6 +4318,7 @@ #define BUTTON1_WHEN_PRINTING false // Button allowed to trigger during printing? #define BUTTON1_GCODE "G28" #define BUTTON1_DESC "Homing" // Optional string to set the LCD status + //#define BUTTON1_IMMEDIATE // Skip the queue and execute immediately. Rarely needed. #endif //#define BUTTON2_PIN -1 @@ -4087,6 +4327,7 @@ #define BUTTON2_WHEN_PRINTING false #define BUTTON2_GCODE "M140 S" STRINGIFY(PREHEAT_1_TEMP_BED) "\nM104 S" STRINGIFY(PREHEAT_1_TEMP_HOTEND) #define BUTTON2_DESC "Preheat for " PREHEAT_1_LABEL + //#define BUTTON2_IMMEDIATE #endif //#define BUTTON3_PIN -1 @@ -4095,6 +4336,7 @@ #define BUTTON3_WHEN_PRINTING false #define BUTTON3_GCODE "M140 S" STRINGIFY(PREHEAT_2_TEMP_BED) "\nM104 S" STRINGIFY(PREHEAT_2_TEMP_HOTEND) #define BUTTON3_DESC "Preheat for " PREHEAT_2_LABEL + //#define BUTTON3_IMMEDIATE #endif #endif @@ -4119,7 +4361,7 @@ #define HOST_PAUSE_M76 // Tell the host to pause in response to M76 #define HOST_PROMPT_SUPPORT // Initiate host prompts to get user feedback #if ENABLED(HOST_PROMPT_SUPPORT) - #define HOST_STATUS_NOTIFICATIONS // Send some status messages to the host as notifications + #define HOST_STATUS_NOTIFICATIONS // Send some status messages to the host as notifications #endif #define HOST_START_MENU_ITEM // Add a menu item that tells the host to start #define HOST_SHUTDOWN_MENU_ITEM // Add a menu item that tells the host to shut down @@ -4142,7 +4384,7 @@ * Developed by Chris Barr at Aus3D. * * Wiki: https://wiki.aus3d.com.au/Magnetic_Encoder - * Github: https://github.com/Aus3D/MagneticEncoder + * GitHub: https://github.com/Aus3D/MagneticEncoder * * Supplier: https://aus3d.com.au/products/magnetic-encoder-module * Alternative Supplier: https://reliabuild3d.com/ @@ -4167,7 +4409,7 @@ //#define I2CPE_ENC_1_TICKS_REV (16 * 200) // Only needed for rotary encoders; number of stepper // steps per full revolution (motor steps/rev * microstepping) //#define I2CPE_ENC_1_INVERT // Invert the direction of axis travel. - #define I2CPE_ENC_1_EC_METHOD I2CPE_ECM_MICROSTEP // Type of error error correction. + #define I2CPE_ENC_1_EC_METHOD I2CPE_ECM_MICROSTEP // Type of error correction. #define I2CPE_ENC_1_EC_THRESH 0.10 // Threshold size for error (in mm) above which the // printer will attempt to correct the error; errors // smaller than this are ignored to minimize effects of @@ -4261,14 +4503,38 @@ #endif /** - * Instant freeze / unfreeze functionality - * Potentially useful for emergency stop that allows being resumed. - * @section interface + * Freeze / Unfreeze + * + * Pause / Hold that keeps power available and does not stop the spindle can be initiated by + * the FREEZE_PIN. Halts instantly (default) or performs a soft feed hold that decelerates and + * halts movement at FREEZE_JERK (requires SOFT_FEED_HOLD). + * Motion can be resumed by using the FREEZE_PIN. + * + * NOTE: Controls Laser PWM but does NOT pause Spindle, Fans, Heaters or other devices. + * @section freeze */ //#define FREEZE_FEATURE #if ENABLED(FREEZE_FEATURE) - //#define FREEZE_PIN 41 // Override the default (KILL) pin here - #define FREEZE_STATE LOW // State of pin indicating freeze + //#define FREEZE_PIN -1 // Override the default (KILL) pin here + #define FREEZE_STATE LOW // State of pin indicating freeze +#endif + +#if ANY(FREEZE_FEATURE, REALTIME_REPORTING_COMMANDS) + /** + * Command P000 (REALTIME_REPORTING_COMMANDS and EMERGENCY_PARSER) or + * FREEZE_PIN (FREEZE_FEATURE) initiates a soft feed hold that keeps + * power available and does not stop the spindle. + * + * The soft feed hold decelerates and halts movement at FREEZE_JERK. + * Motion can be resumed with command R000 (requires REALTIME_REPORTING_COMMANDS) or + * by using the FREEZE_PIN (requires FREEZE_FEATURE). + * + * NOTE: Affects Laser PWM but DOES NOT pause Spindle, Fans, Heaters or other devices. + */ + //#define SOFT_FEED_HOLD + #if ENABLED(SOFT_FEED_HOLD) + #define FREEZE_JERK 2 // (mm/s) Completely halt when motion has decelerated below this value + #endif #endif /** @@ -4309,6 +4575,7 @@ // See class CodeProfiler. //#define MAX7219_DEBUG_MULTISTEPPING 6 // Show multi-stepping 1 to 128 on this LED matrix row. //#define MAX7219_DEBUG_SLOWDOWN 6 // Count (mod 16) how many times SLOWDOWN has reduced print speed. + //#define MAX7219_REINIT_ON_POWERUP // Re-initialize MAX7129 when power supply turns on #endif /** @@ -4342,7 +4609,7 @@ * Extras for an ESP32-based motherboard with WIFISUPPORT * These options don't apply to add-on WiFi modules based on ESP32 WiFi101. */ -#if ENABLED(WIFISUPPORT) +#if ANY(WIFISUPPORT, ESP3D_WIFISUPPORT) //#define WEBSUPPORT // Start a webserver (which may include auto-discovery) using SPIFFS //#define OTASUPPORT // Support over-the-air firmware updates //#define WIFI_CUSTOM_COMMAND // Accept feature config commands (e.g., WiFi ESP3D) from the host @@ -4378,58 +4645,81 @@ //#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. - #define MMU2_SERIAL_PORT 2 - - // 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 - - // G-code to execute when MMU2 F.I.N.D.A. probe detects filament runout - #define MMU2_FILAMENT_RUNOUT_SCRIPT "M600" - - // Add an LCD menu for MMU2 - //#define MMU2_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 - #define MMU2_LOAD_TO_NOZZLE_SEQUENCE \ - { 7.2, 1145 }, \ - { 14.4, 871 }, \ - { 36.0, 1393 }, \ - { 14.4, 871 }, \ - { 50.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 } + +#elif HAS_PRUSA_MMU2 || HAS_PRUSA_MMU3 + // Common settings for MMU2/MMU2S/MMU3 + // Serial port used for communication with MMU2/MMU2S/MMU3. + #define MMU_SERIAL_PORT 2 + #define MMU_BAUD 115200 + + //#define MMU_RST_PIN 23 // Define this pin to use Hardware Reset for MMU2/MMU2S/MMU3 + + //#define MMU_MENUS // Add an LCD menu for MMU2/MMU2S/MMU3 + + //#define MMU_DEBUG // Write debug info to serial output + + // Options pertaining to MMU2 and MMU2S + #if HAS_PRUSA_MMU2 + // Enable if the MMU2 has 12V stepper motors (MMU2 Firmware 1.0.2 and up) + //#define MMU2_MODE_12V + + // 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 + + // G-code to execute when MMU2 F.I.N.D.A. probe detects filament runout + #define MMU2_FILAMENT_RUNOUT_SCRIPT "M600" + + // MMU2 sequences use mm/min. Not compatible with MMU3, which use mm/sec. + #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 } + + #endif // HAS_PRUSA_MMU2 /** - * Using a sensor like the MMU2S - * This mode requires a MK3S extruder with a sensor at the extruder idler, like the MMU2S. + * Options pertaining to MMU2S devices + * Requires the MK3S extruder with a sensor at the extruder idler, like the MMU2S. * See https://help.prusa3d.com/guide/3b-mk3s-mk2-5s-extruder-upgrade_41560#42048, step 11 */ #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 @@ -4440,10 +4730,90 @@ // Continue unloading if sensor detects filament after the initial unload move //#define MMU_IR_UNLOAD_MOVE - #else + + #elif HAS_PRUSA_MMU3 + + // MMU3 settings + + #define MMU3_HAS_CUTTER // Enable cutter related functionality + + #define MMU3_MAX_RETRIES 3 // Number of retries (total time = timeout*retries) + + // 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 MMU3_FILAMENT_SENSOR_E_POSITION 0 // (mm) + #define _MMU3_LOAD_DISTANCE_PAST_GEARS 5 // (mm) + #define MMU3_TOOL_CHANGE_LOAD_LENGTH (MMU3_FILAMENT_SENSOR_E_POSITION + _MMU3_LOAD_DISTANCE_PAST_GEARS) // (mm) + + #define MMU3_LOAD_TO_NOZZLE_FEED_RATE 20.0 // (mm/s) + + #define MMU3_VERIFY_LOAD_TO_NOZZLE_FEED_RATE 50.0 // (mm/s) + #define _MMU3_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 MMU3_RETRY_UNLOAD_TO_FINDA_LENGTH 80.0 // (mm) + #define MMU3_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 _MMU_EXTRUDER_PTFE_LENGTH 42.3 // (mm) + #define _MMU_EXTRUDER_HEATBREAK_LENGTH 17.7 // (mm) + #define MMU3_CHECK_FILAMENT_PRESENCE_EXTRUSION_LENGTH (MMU3_FILAMENT_SENSOR_E_POSITION + _MMU_EXTRUDER_PTFE_LENGTH + _MMU_EXTRUDER_HEATBREAK_LENGTH + _MMU3_VERIFY_LOAD_TO_NOZZLE_TWEAK) // (mm) + + /** + * 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 MMU3_SPOOL_JOIN_CONSUMES_ALL_FILAMENT + + // MMU3 sequences use mm/sec. Not compatible with MMU2 which use mm/min. + #define MMU3_LOAD_TO_NOZZLE_SEQUENCE \ + { _MMU_EXTRUDER_PTFE_LENGTH, MMM_TO_MMS(810) }, /* (13.5 mm/s) Fast load ahead of heatbreak */ \ + { _MMU_EXTRUDER_HEATBREAK_LENGTH, MMM_TO_MMS(198) } /* ( 3.3 mm/s) Slow load after heatbreak */ + + #define MMU3_RAMMING_SEQUENCE \ + { 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) } + + #else // MMU2 (not MMU2S) /** - * MMU1 Extruder Sensor + * MMU2 Extruder Sensor * * Support for a PrÅÆÅ”a (or other) IR Sensor to detect filament near the extruder * and make loading more reliable. Suitable for an extruder equipped with a filament @@ -4453,16 +4823,14 @@ * move up to the gears. If no filament is detected, the MMU2 can make some more attempts. * If all attempts fail, a filament runout will be triggered. */ - //#define MMU_EXTRUDER_SENSOR - #if ENABLED(MMU_EXTRUDER_SENSOR) - #define MMU_LOADING_ATTEMPTS_NR 5 // max. number of attempts to load filament if first load fail + //#define MMU2_EXTRUDER_SENSOR + #if ENABLED(MMU2_EXTRUDER_SENSOR) + #define MMU2_LOADING_ATTEMPTS_NR 5 // Number of times to try loading filament before failure #endif #endif - //#define MMU2_DEBUG // Write debug info to serial output - -#endif // HAS_PRUSA_MMU2 +#endif // HAS_PRUSA_MMU2 || HAS_PRUSA_MMU3 /** * Advanced Print Counter settings @@ -4496,6 +4864,11 @@ // #define PINS_DEBUGGING +// +// M265 - I2C Scanner +// +//#define I2C_SCANNER + // Enable Tests that will run at startup and produce a report //#define MARLIN_TEST_BUILD diff --git a/Marlin/Makefile b/Marlin/Makefile index e5ba9cb341e4..aed2506ac8e6 100644 --- a/Marlin/Makefile +++ b/Marlin/Makefile @@ -127,9 +127,9 @@ NEOPIXEL ?= 0 # on GCC versions: # https://www.avrfreaks.net/comment/1789106#comment-1789106 -CC_MAJ:=$(shell $(CC) -dM -E - < /dev/null | grep __GNUC__ | cut -f3 -d\ ) -CC_MIN:=$(shell $(CC) -dM -E - < /dev/null | grep __GNUC_MINOR__ | cut -f3 -d\ ) -CC_PATCHLEVEL:=$(shell $(CC) -dM -E - < /dev/null | grep __GNUC_PATCHLEVEL__ | cut -f3 -d\ ) +CC_MAJ:=$(shell $(CC) -dM -E - < /dev/null | grep __GNUC__ | cut -f3 -d' ' ) +CC_MIN:=$(shell $(CC) -dM -E - < /dev/null | grep __GNUC_MINOR__ | cut -f3 -d' ' ) +CC_PATCHLEVEL:=$(shell $(CC) -dM -E - < /dev/null | grep __GNUC_PATCHLEVEL__ | cut -f3 -d' ' ) CC_VER:=$(shell echo $$(( $(CC_MAJ) * 10000 + $(CC_MIN) * 100 + $(CC_PATCHLEVEL) ))) ifeq ($(shell test $(CC_VER) -lt 40901 && echo 1),1) $(warning This GCC version $(CC_VER) is likely broken. Enabling relocation workaround.) @@ -187,6 +187,17 @@ else ifeq ($(HARDWARE_MOTHERBOARD),1033) # RAMPS Plus 3DYMY (Power outputs: Spindle, Controller Fan) else ifeq ($(HARDWARE_MOTHERBOARD),1034) +# RAMPS 1.6+ (Power outputs: Hotend, Fan, Bed) +else ifeq ($(HARDWARE_MOTHERBOARD),1040) +# RAMPS 1.6+ (Power outputs: Hotend0, Hotend1, Bed) +else ifeq ($(HARDWARE_MOTHERBOARD),1041) +# RAMPS 1.6+ (Power outputs: Hotend, Fan0, Fan1) +else ifeq ($(HARDWARE_MOTHERBOARD),1042) +# RAMPS 1.6+ (Power outputs: Hotend0, Hotend1, Fan) +else ifeq ($(HARDWARE_MOTHERBOARD),1043) +# RAMPS 1.6+ (Power outputs: Spindle, Controller Fan) +else ifeq ($(HARDWARE_MOTHERBOARD),1044) + # # RAMPS Derivatives - ATmega1280, ATmega2560 # @@ -221,79 +232,79 @@ else ifeq ($(HARDWARE_MOTHERBOARD),1112) else ifeq ($(HARDWARE_MOTHERBOARD),1113) # BigTreeTech or BIQU KFB2.0 else ifeq ($(HARDWARE_MOTHERBOARD),1114) -# zrib V2.0 (Chinese RAMPS replica) +# Zonestar zrib V2.0 (Chinese RAMPS replica) else ifeq ($(HARDWARE_MOTHERBOARD),1115) -# zrib V5.2 (Chinese RAMPS replica) +# Zonestar zrib V5.2 (Chinese RAMPS replica) else ifeq ($(HARDWARE_MOTHERBOARD),1116) -# Felix 2.0+ Electronics Board (RAMPS like) +# Zonestar zrib V5.3 (Chinese RAMPS replica) else ifeq ($(HARDWARE_MOTHERBOARD),1117) -# Invent-A-Part RigidBoard +# Felix 2.0+ Electronics Board (RAMPS like) else ifeq ($(HARDWARE_MOTHERBOARD),1118) -# Invent-A-Part RigidBoard V2 +# Invent-A-Part RigidBoard else ifeq ($(HARDWARE_MOTHERBOARD),1119) -# Sainsmart 2-in-1 board +# Invent-A-Part RigidBoard V2 else ifeq ($(HARDWARE_MOTHERBOARD),1120) -# Ultimaker +# Sainsmart 2-in-1 board else ifeq ($(HARDWARE_MOTHERBOARD),1121) -# Ultimaker (Older electronics. Pre 1.5.4. This is rare) +# Ultimaker else ifeq ($(HARDWARE_MOTHERBOARD),1122) +# Ultimaker (Older electronics. Pre 1.5.4. This is rare) +else ifeq ($(HARDWARE_MOTHERBOARD),1123) MCU ?= atmega1280 PROG_MCU ?= m1280 # Azteeg X3 -else ifeq ($(HARDWARE_MOTHERBOARD),1123) -# Azteeg X3 Pro else ifeq ($(HARDWARE_MOTHERBOARD),1124) -# Ultimainboard 2.x (Uses TEMP_SENSOR 20) +# Azteeg X3 Pro else ifeq ($(HARDWARE_MOTHERBOARD),1125) -# Rumba +# Ultimainboard 2.x (Uses TEMP_SENSOR 20) else ifeq ($(HARDWARE_MOTHERBOARD),1126) -# Raise3D N series Rumba derivative +# Rumba else ifeq ($(HARDWARE_MOTHERBOARD),1127) -# Rapide Lite 200 (v1, low-cost RUMBA clone with drv) +# Raise3D N series Rumba derivative else ifeq ($(HARDWARE_MOTHERBOARD),1128) -# Formbot T-Rex 2 Plus +# Rapide Lite 200 (v1, low-cost RUMBA clone with drv) else ifeq ($(HARDWARE_MOTHERBOARD),1129) -# Formbot T-Rex 3 +# Formbot T-Rex 2 Plus else ifeq ($(HARDWARE_MOTHERBOARD),1130) -# Formbot Raptor +# Formbot T-Rex 3 else ifeq ($(HARDWARE_MOTHERBOARD),1131) -# Formbot Raptor 2 +# Formbot Raptor else ifeq ($(HARDWARE_MOTHERBOARD),1132) -# bq ZUM Mega 3D +# Formbot Raptor 2 else ifeq ($(HARDWARE_MOTHERBOARD),1133) -# MakeBoard Mini v2.1.2 by MicroMake +# bq ZUM Mega 3D else ifeq ($(HARDWARE_MOTHERBOARD),1134) -# TriGorilla Anycubic version 1.3-based on RAMPS EFB +# MakeBoard Mini v2.1.2 by MicroMake else ifeq ($(HARDWARE_MOTHERBOARD),1135) -# ... Ver 1.4 +# TriGorilla Anycubic version 1.3-based on RAMPS EFB else ifeq ($(HARDWARE_MOTHERBOARD),1136) -# ... Rev 1.1 (new servo pin order) +# ... Ver 1.4 else ifeq ($(HARDWARE_MOTHERBOARD),1137) -# Creality: Ender-4, CR-8 +# ... Rev 1.1 (new servo pin order) else ifeq ($(HARDWARE_MOTHERBOARD),1138) -# Creality: CR10S, CR20, CR-X +# Creality: Ender-4, CR-8 else ifeq ($(HARDWARE_MOTHERBOARD),1139) -# Dagoma F5 +# Creality: CR10S, CR20, CR-X else ifeq ($(HARDWARE_MOTHERBOARD),1140) -# FYSETC F6 1.3 +# Creality CR-10 V2, CR-10 V3 else ifeq ($(HARDWARE_MOTHERBOARD),1141) -# FYSETC F6 1.4 +# Dagoma F5 else ifeq ($(HARDWARE_MOTHERBOARD),1142) -# Wanhao Duplicator i3 Plus +# Dagoma D6 (as found in the Dagoma DiscoUltimate V2 TMC) else ifeq ($(HARDWARE_MOTHERBOARD),1143) -# VORON Design +# FYSETC F6 1.3 else ifeq ($(HARDWARE_MOTHERBOARD),1144) -# Tronxy TRONXY-V3-1.0 +# FYSETC F6 1.4 else ifeq ($(HARDWARE_MOTHERBOARD),1145) -# Z-Bolt X Series +# Wanhao Duplicator i3 Plus else ifeq ($(HARDWARE_MOTHERBOARD),1146) -# TT OSCAR +# VORON Design else ifeq ($(HARDWARE_MOTHERBOARD),1147) -# Overlord/Overlord Pro +# Tronxy TRONXY-V3-1.0 else ifeq ($(HARDWARE_MOTHERBOARD),1148) -# ADIMLab Gantry v1 +# Z-Bolt X Series else ifeq ($(HARDWARE_MOTHERBOARD),1149) -# ADIMLab Gantry v2 +# TT OSCAR else ifeq ($(HARDWARE_MOTHERBOARD),1150) # BIQU Tango V1 else ifeq ($(HARDWARE_MOTHERBOARD),1151) @@ -307,7 +318,7 @@ else ifeq ($(HARDWARE_MOTHERBOARD),1154) else ifeq ($(HARDWARE_MOTHERBOARD),1155) # Tenlog D3 Hero IDEX printer else ifeq ($(HARDWARE_MOTHERBOARD),1156) -# Tenlog D3,5,6 Pro IDEX printers +# Tenlog D3, D5, D6 IDEX Printer else ifeq ($(HARDWARE_MOTHERBOARD),1157) # Ramps S 1.2 by Sakul.cz (Power outputs: Hotend0, Hotend1, Fan, Bed) else ifeq ($(HARDWARE_MOTHERBOARD),1158) @@ -319,10 +330,18 @@ else ifeq ($(HARDWARE_MOTHERBOARD),1160) else ifeq ($(HARDWARE_MOTHERBOARD),1161) # Longer LKx PRO / Alfawise Uxx Pro (PRO version) else ifeq ($(HARDWARE_MOTHERBOARD),1162) -# Zonestar zrib V5.3 (Chinese RAMPS replica) -else ifeq ($(HARDWARE_MOTHERBOARD),1163) # Pxmalion Core I3 +else ifeq ($(HARDWARE_MOTHERBOARD),1163) +# Panowin Cutlass (as found in the Panowin F1) else ifeq ($(HARDWARE_MOTHERBOARD),1164) +# Kodama Bardo V1.x (as found in the Kodama Trinus) +else ifeq ($(HARDWARE_MOTHERBOARD),1165) +# XTLW MFF V1.0 +else ifeq ($(HARDWARE_MOTHERBOARD),1166) +# XTLW MFF V2.0 +else ifeq ($(HARDWARE_MOTHERBOARD),1167) +# E3D Rumba BigBox +else ifeq ($(HARDWARE_MOTHERBOARD),1168) # # RAMBo and derivatives @@ -340,7 +359,7 @@ else ifeq ($(HARDWARE_MOTHERBOARD),1203) else ifeq ($(HARDWARE_MOTHERBOARD),1204) # abee Scoovo X9H else ifeq ($(HARDWARE_MOTHERBOARD),1205) -# Rambo ThinkerV2 +# ThinkerV2 else ifeq ($(HARDWARE_MOTHERBOARD),1206) # @@ -383,30 +402,42 @@ else ifeq ($(HARDWARE_MOTHERBOARD),1315) else ifeq ($(HARDWARE_MOTHERBOARD),1316) # Geeetech GT2560 Rev B for A10(M/T/D) else ifeq ($(HARDWARE_MOTHERBOARD),1317) -# Geeetech GT2560 Rev B for A10(M/T/D) -else ifeq ($(HARDWARE_MOTHERBOARD),1318) # Geeetech GT2560 Rev B for Mecreator2 -else ifeq ($(HARDWARE_MOTHERBOARD),1319) +else ifeq ($(HARDWARE_MOTHERBOARD),1318) # Geeetech GT2560 Rev B for A20(M/T/D) +else ifeq ($(HARDWARE_MOTHERBOARD),1319) +# Geeetech GT2560 Rev B for A10(M/T/D) else ifeq ($(HARDWARE_MOTHERBOARD),1320) -# Einstart retrofit +# Geeetech GT2560 Rev B for A20(M/T/D) else ifeq ($(HARDWARE_MOTHERBOARD),1321) -# Wanhao 0ne+ i3 Mini +# Geeetech GT2560 V4.1B for A10(M/T/D) else ifeq ($(HARDWARE_MOTHERBOARD),1322) -# Leapfrog Xeed 2015 +# Einstart retrofit else ifeq ($(HARDWARE_MOTHERBOARD),1323) -# PICA Shield (original version) +# Wanhao 0ne+ i3 Mini else ifeq ($(HARDWARE_MOTHERBOARD),1324) -# PICA Shield (rev C or later) +# Wanhao D9 MK2 else ifeq ($(HARDWARE_MOTHERBOARD),1325) -# Intamsys 4.0 (Funmat HT) +# Overlord/Overlord Pro else ifeq ($(HARDWARE_MOTHERBOARD),1326) -# Malyan M180 Mainboard Version 2 (no display function, direct G-code only) +# ADIMLab Gantry v1 else ifeq ($(HARDWARE_MOTHERBOARD),1327) -# Geeetech GT2560 Rev B for A20(M/T/D) +# ADIMLab Gantry v2 else ifeq ($(HARDWARE_MOTHERBOARD),1328) -# Mega controller & Protoneer CNC Shield V3.00 +# Leapfrog Xeed 2015 else ifeq ($(HARDWARE_MOTHERBOARD),1329) +# PICA Shield (original version) +else ifeq ($(HARDWARE_MOTHERBOARD),1330) +# PICA Shield (rev C or later) +else ifeq ($(HARDWARE_MOTHERBOARD),1331) +# Intamsys 4.0 (Funmat HT) +else ifeq ($(HARDWARE_MOTHERBOARD),1332) +# Malyan M180 Mainboard Version 2 +else ifeq ($(HARDWARE_MOTHERBOARD),1333) +# Mega controller & Protoneer CNC Shield V3.00 +else ifeq ($(HARDWARE_MOTHERBOARD),1334) +# WEEDO 62A board (TINA2, Monoprice Cadet, etc.) +else ifeq ($(HARDWARE_MOTHERBOARD),1335) # # ATmega1281, ATmega2561 @@ -440,7 +471,7 @@ else ifeq ($(HARDWARE_MOTHERBOARD),1502) HARDWARE_VARIANT ?= Sanguino MCU ?= atmega644p PROG_MCU ?= m644p -# Melzi V2.0 +# Melzi V2 else ifeq ($(HARDWARE_MOTHERBOARD),1503) HARDWARE_VARIANT ?= Sanguino MCU ?= atmega1284p @@ -450,41 +481,46 @@ else ifeq ($(HARDWARE_MOTHERBOARD),1504) HARDWARE_VARIANT ?= Sanguino MCU ?= atmega1284p PROG_MCU ?= m1284p -# Melzi Creality3D board (for CR-10 etc) +# Melzi Creality3D (for CR-10 etc) else ifeq ($(HARDWARE_MOTHERBOARD),1505) HARDWARE_VARIANT ?= Sanguino MCU ?= atmega1284p PROG_MCU ?= m1284p -# Melzi Malyan M150 board +# Melzi Creality3D (for Ender-2) else ifeq ($(HARDWARE_MOTHERBOARD),1506) HARDWARE_VARIANT ?= Sanguino MCU ?= atmega1284p PROG_MCU ?= m1284p -# Tronxy X5S +# Melzi Malyan M150 else ifeq ($(HARDWARE_MOTHERBOARD),1507) HARDWARE_VARIANT ?= Sanguino MCU ?= atmega1284p PROG_MCU ?= m1284p -# STB V1.1 +# Tronxy X5S else ifeq ($(HARDWARE_MOTHERBOARD),1508) HARDWARE_VARIANT ?= Sanguino MCU ?= atmega1284p PROG_MCU ?= m1284p -# Azteeg X1 +# STB V1.1 else ifeq ($(HARDWARE_MOTHERBOARD),1509) HARDWARE_VARIANT ?= Sanguino MCU ?= atmega1284p PROG_MCU ?= m1284p -# Anet 1.0 (Melzi clone) +# Azteeg X1 else ifeq ($(HARDWARE_MOTHERBOARD),1510) HARDWARE_VARIANT ?= Sanguino MCU ?= atmega1284p PROG_MCU ?= m1284p -# ZoneStar ZMIB V2 +# Anet 1.0 (Melzi clone) else ifeq ($(HARDWARE_MOTHERBOARD),1511) HARDWARE_VARIANT ?= Sanguino MCU ?= atmega1284p PROG_MCU ?= m1284p +# ZoneStar ZMIB V2 +else ifeq ($(HARDWARE_MOTHERBOARD),1512) + HARDWARE_VARIANT ?= Sanguino + MCU ?= atmega1284p + PROG_MCU ?= m1284p # # Other ATmega644P, ATmega644, ATmega1284P @@ -595,6 +631,10 @@ else ifeq ($(HARDWARE_MOTHERBOARD),1707) MCU ?= at90usb1286 PROG_MCU ?= usb1286 +# +# SAM3X8E ARM Cortex-M3 +# + # UltiMachine Archim1 (with DRV8825 drivers) else ifeq ($(HARDWARE_MOTHERBOARD),3023) HARDWARE_VARIANT ?= archim @@ -767,10 +807,10 @@ endif ifeq ($(TMC), 1) LIB_CXXSRC += TMCStepper.cpp COOLCONF.cpp DRV_STATUS.cpp IHOLD_IRUN.cpp \ - CHOPCONF.cpp GCONF.cpp PWMCONF.cpp DRV_CONF.cpp DRVCONF.cpp DRVCTRL.cpp \ - DRVSTATUS.cpp ENCMODE.cpp RAMP_STAT.cpp SGCSCONF.cpp SHORT_CONF.cpp \ - SMARTEN.cpp SW_MODE.cpp SW_SPI.cpp TMC2130Stepper.cpp TMC2208Stepper.cpp \ - TMC2209Stepper.cpp TMC2660Stepper.cpp TMC5130Stepper.cpp TMC5160Stepper.cpp + CHOPCONF.cpp GCONF.cpp PWMCONF.cpp DRV_CONF.cpp DRVCONF.cpp DRVCTRL.cpp DRVSTATUS.cpp \ + GLOBAL_SCALER.cpp SLAVECONF.cpp IOIN.cpp ENCMODE.cpp RAMP_STAT.cpp SGCSCONF.cpp \ + SHORT_CONF.cpp SMARTEN.cpp SW_MODE.cpp SW_SPI.cpp TMC2130Stepper.cpp TMC2208Stepper.cpp \ + TMC2209Stepper.cpp TMC2240Stepper.cpp TMC2660Stepper.cpp TMC5130Stepper.cpp TMC5160Stepper.cpp endif ifeq ($(RELOC_WORKAROUND), 1) @@ -837,8 +877,8 @@ else ifeq ($(HARDWARE_VARIANT), archim) endif # Add all the source directories as include directories too -CINCS = ${addprefix -I ,${VPATH}} -CXXINCS = ${addprefix -I ,${VPATH}} +CINCS = ${addprefix -I, ${VPATH}} +CXXINCS = ${addprefix -I, ${VPATH}} # Silence warnings for library code (won't work for .h files, unfortunately) LIBWARN = -w -Wno-packed-bitfield-compat @@ -995,7 +1035,7 @@ extcoff: $(TARGET).elf $(NM) -n $< > $@ # Link: create ELF output file from library. - +LDFLAGS+= -Wl,-V $(BUILD_DIR)/$(TARGET).elf: $(OBJ) Configuration.h $(Pecho) " CXX $@" $P $(CXX) $(LD_PREFIX) $(ALL_CXXFLAGS) -o $@ -L. $(OBJ) $(LDFLAGS) $(LD_SUFFIX) diff --git a/Marlin/Version.h b/Marlin/Version.h index e3dc44bc9d06..d505f4c09cef 100644 --- a/Marlin/Version.h +++ b/Marlin/Version.h @@ -41,7 +41,14 @@ * here we define this default string as the date where the latest release * version was tagged. */ -//#define STRING_DISTRIBUTION_DATE "2024-02-28" +//#define STRING_DISTRIBUTION_DATE "2026-08-25" + +/** + * The protocol for communication to the host. Protocol indicates communication + * standards such as the use of ASCII, "echo:" and "error:" line prefixes, etc. + * (Other behaviors are given by the firmware version and capabilities report.) + */ +//#define PROTOCOL_VERSION "1.0" /** * Defines a generic printer name to be output to the LCD after booting Marlin. @@ -51,7 +58,7 @@ /** * The SOURCE_CODE_URL is the location where users will find the Marlin Source * Code which is installed on the device. In most cases —unless the manufacturer - * has a distinct Github fork— the Source Code URL should just be the main + * has a distinct GitHub fork— the Source Code URL should just be the main * Marlin repository. */ //#define SOURCE_CODE_URL "github.com/MarlinFirmware/Marlin" @@ -68,8 +75,8 @@ //#define WEBSITE_URL "marlinfw.org" /** - * Set the vendor info the serial USB interface, if changable - * Currently only supported by DUE platform + * Set the vendor info the serial USB interface, if changeable. + * Currently only supported by DUE platform. */ //#define USB_DEVICE_VENDOR_ID 0x0000 //#define USB_DEVICE_PRODUCT_ID 0x0000 diff --git a/Marlin/config.ini b/Marlin/config.ini index fed2a5c68c0a..fd2b81062adb 100644 --- a/Marlin/config.ini +++ b/Marlin/config.ini @@ -62,6 +62,11 @@ motherboard = BOARD_RAMPS_14_EFB serial_port = 0 baudrate = 250000 +string_config_h_author = "(default from config.ini)" + +capabilities_report = on +extended_capabilities_report = on + use_watchdog = on thermal_protection_hotends = on thermal_protection_hysteresis = 4 @@ -77,18 +82,25 @@ temp_sensor_0 = 1 temp_hysteresis = 3 heater_0_mintemp = 5 heater_0_maxtemp = 275 -preheat_1_temp_hotend = 180 -bang_max = 255 pidtemp = on pid_k1 = 0.95 pid_max = 255 -pid_functional_range = 10 +pid_functional_range = 20 default_kp = 22.20 default_ki = 1.08 default_kd = 114.00 +temp_sensor_bed = 1 +bed_check_interval = 5000 +bed_mintemp = 5 +bed_maxtemp = 150 + +thermal_protection_bed = on +thermal_protection_bed_hysteresis = 2 +thermal_protection_bed_period = 20 + x_driver_type = A4988 y_driver_type = A4988 z_driver_type = A4988 @@ -121,10 +133,10 @@ default_max_acceleration = { 3000, 3000, 100, 10000 } homing_feedrate_mm_m = { (50*60), (50*60), (4*60) } homing_bump_divisor = { 2, 2, 4 } -x_enable_on = 0 -y_enable_on = 0 -z_enable_on = 0 -e_enable_on = 0 +x_enable_on = LOW +y_enable_on = LOW +z_enable_on = LOW +e_enable_on = LOW invert_x_dir = false invert_y_dir = true @@ -136,11 +148,6 @@ step_state_x = HIGH step_state_y = HIGH step_state_z = HIGH -disable_x = off -disable_y = off -disable_z = off -disable_e = off - proportional_font_ratio = 1.0 default_nominal_filament_dia = 1.75 @@ -153,30 +160,33 @@ default_retract_acceleration = 3000 default_minimumfeedrate = 0.0 default_mintravelfeedrate = 0.0 -minimum_planner_speed = 0.05 min_steps_per_segment = 6 default_minsegmenttime = 20000 [config:basic] +hotend_overshoot = 15 bed_overshoot = 10 +max_bed_power = 255 + busy_while_heating = on -default_ejerk = 5.0 +host_keepalive_feature = on default_keepalive_interval = 2 -default_leveling_fade_height = 0.0 -disable_other_extruders = on -display_charset_hd44780 = JAPANESE +printjob_timer_autostart = on + +jd_handle_small_segments = on +validate_homing_endstops = on +editable_steps_per_unit = on + eeprom_boot_silent = on eeprom_chitchat = on + endstoppullups = on -extrude_maxlength = 200 + +prevent_cold_extrusion = on extrude_mintemp = 170 -host_keepalive_feature = on -hotend_overshoot = 15 -jd_handle_small_segments = on -lcd_info_screen_style = 0 -lcd_language = en -max_bed_power = 255 -mesh_inset = 0 +prevent_lengthy_extrude = on +extrude_maxlength = 200 + min_software_endstops = on max_software_endstops = on min_software_endstop_x = on @@ -185,63 +195,60 @@ min_software_endstop_z = on max_software_endstop_x = on max_software_endstop_y = on max_software_endstop_z = on -preheat_1_fan_speed = 0 + preheat_1_label = "PLA" +preheat_1_temp_hotend = 180 preheat_1_temp_bed = 70 -prevent_cold_extrusion = on -prevent_lengthy_extrude = on -printjob_timer_autostart = on -probing_margin = 10 -show_bootscreen = on -soft_pwm_scale = 0 -string_config_h_author = "(none, default config)" +preheat_1_fan_speed = 0 + +preheat_2_label = "ABS" +preheat_2_temp_hotend = 240 +preheat_2_temp_bed = 110 +preheat_2_fan_speed = 0 + temp_bed_hysteresis = 3 temp_bed_residency_time = 10 temp_bed_window = 1 temp_residency_time = 10 temp_window = 1 -validate_homing_endstops = on -xy_probe_feedrate = (133*60) -z_clearance_between_probes = 5 -z_clearance_deploy_probe = 10 -z_clearance_multi_probe = 5 [config:advanced] arc_support = on +min_arc_segment_mm = 0.1 +max_arc_segment_mm = 1.0 +min_circle_segments = 72 +n_arc_correction = 25 + auto_report_temperatures = on + autotemp = on +autotemp_min = 210 +autotemp_max = 250 +autotemp_factor = 0.1f autotemp_oldweight = 0.98 -bed_check_interval = 5000 + default_stepper_timeout_sec = 120 -default_volumetric_extruder_limit = 0.00 disable_idle_x = on disable_idle_y = on disable_idle_z = on disable_idle_e = on + e0_auto_fan_pin = -1 -encoder_100x_steps_per_sec = 80 -encoder_10x_steps_per_sec = 30 -encoder_rate_multiplier = on -extended_capabilities_report = on -extruder_auto_fan_speed = 255 -extruder_auto_fan_temperature = 50 -fanmux0_pin = -1 -fanmux1_pin = -1 -fanmux2_pin = -1 + faster_gcode_parser = on +debug_flags_gcode = on + homing_bump_mm = { 5, 5, 2 } -max_arc_segment_mm = 1.0 -min_arc_segment_mm = 0.1 -min_circle_segments = 72 -n_arc_correction = 25 -serial_overrun_protection = on + slowdown = on slowdown_divisor = 2 -temp_sensor_bed = 0 -thermal_protection_bed_hysteresis = 2 -thermocouple_max_errors = 15 +multistepping_limit = 16 + +serial_overrun_protection = on tx_buffer_size = 0 + +watch_temp_increase = 2 +watch_temp_period = 40 + watch_bed_temp_increase = 2 watch_bed_temp_period = 60 -watch_temp_increase = 2 -watch_temp_period = 20 diff --git a/Marlin/src/HAL/AGENTS.md b/Marlin/src/HAL/AGENTS.md new file mode 100644 index 000000000000..48b78b23c4b1 --- /dev/null +++ b/Marlin/src/HAL/AGENTS.md @@ -0,0 +1,169 @@ +# AGENTS.md — HAL folder (Hardware Abstraction Layer) + +Umbrella documentation for the entire `Marlin/src/HAL/` folder — the top-level HAL directory, **not** any single family subfolder. Per-family working notes live in each `Marlin/src/HAL//AGENTS.md` (e.g. `Marlin/src/HAL/STM32/AGENTS.md`). This file describes what the folder contains and how the HAL participates in Marlin's `inc/` include cascade and `ini` build-time source selection. + +## What this folder contains + +`Marlin/src/HAL/` is the root of Marlin's hardware abstraction layer. At the top level: + +- **`HAL.h`** — the HAL dispatcher / entry point. It `#include "platforms.h"`, then pulls in the _active_ family's `HAL.h` via `#include HAL_PATH(.., HAL.h)` and declares the global `extern MarlinHAL hal;`. Defines a few cross-platform helpers (`HAL_ADC_RANGE`, `I2C_ADDRESS`, `S_FMT`, `PGMSTR`). See "Where the HAL sits in the inc/ include ordering" for how/when it is actually included into a translation unit. +- **`platforms.h`** — maps a compiler/board guard macro to a family folder. It defines the `HAL_PATH(PATH, NAME)` macro to stringize `PATH/HAL//NAME` for the currently-selected platform (see "HAL selection (platforms.h / guard macros)"). +- **`shared/`** — cross-HAL APIs and helpers reused by every family: `eeprom_api.{h,cpp}`, `HAL_SPI.h`, `serial_ports.h`, `servo.{h,cpp}`, `servo_private.h`, `Marduino.h`, `math_32bit.h`, `progmem.h`, `Delay.{h,cpp}`, `MinSerial.{h,cpp}`, `fauxpins.h`, `HAL_ST7920.h`, `esp_wifi.{h,cpp}`, `eeprom_if*.{h,cpp}`, plus the `backtrace/` and `cpu_exception/` subfolders. Family HALs include these rather than reimplementing drivers. +- **`/`** — one implementation folder per microcontroller family. Each contains that family's `HAL.h`/`HAL.cpp`, `MarlinHAL` subclass, timer/serial/SPI/fastio/servo code, an `inc/` subfolder (family-local `Conditionals*` and `SanityCheck.h`), and often an `eeprom/`, `u8g/`, and `tft/` subfolder. + +The current family folders are: + +``` +AT32/ AVR/ DUE/ ESP32/ GD32_MFL/ HC32/ LINUX/ LPC1768/ +NATIVE_SIM/ RP2040/ SAMD21/ SAMD51/ STM32/ STM32F1/ +TEENSY31_32/ TEENSY35_36/ TEENSY40_41/ +``` + +There is **no** `Marlin/src/HAL/inc/` subfolder at the HAL top level. Family-local `inc/` directories live _inside each family folder_ (e.g. `HAL/STM32/inc/SanityCheck.h`, `HAL/AT32/inc/Conditionals_adv.h`). The top-level shared include cascade lives in `Marlin/src/inc/`. + +## HAL selection (platforms.h / guard macros) + +`platforms.h` contains one `#if`/`#elif` chain keyed on compiler- or board-defined guard macros. The first matching branch defines `HAL_PATH(PATH, NAME)` to expand to `PATH/HAL//NAME`. The macros and their resolved folders (verified from `platforms.h`): + +| Guard macro | Family folder | +| --------------------------------- | -------------------------------------- | +| `__AVR__` | `HAL/AVR` | +| `ARDUINO_ARCH_SAM` | `HAL/DUE` | +| `__MK20DX256__` | `HAL/TEENSY31_32` | +| `__MK64FX512__` / `__MK66FX1M0__` | `HAL/TEENSY35_36` | +| `__IMXRT1062__` | `HAL/TEENSY40_41` | +| `TARGET_LPC1768` | `HAL/LPC1768` | +| `ARDUINO_ARCH_HC32` | `HAL/HC32` | +| `ARDUINO_ARCH_MFL` | `HAL/GD32_MFL` | +| `__STM32F1__` / `TARGET_STM32F1` | `HAL/STM32F1` | +| `ARDUINO_ARCH_STM32` | `HAL/STM32` (also `#define HAL_STM32`) | +| `ARDUINO_ARCH_ESP32` | `HAL/ESP32` | +| `__PLAT_LINUX__` | `HAL/LINUX` | +| `__PLAT_NATIVE_SIM__` | `HAL/NATIVE_SIM` | +| `__SAMD51__` | `HAL/SAMD51` | +| `__SAMD21__` | `HAL/SAMD21` | +| `__PLAT_RP2040__` | `HAL/RP2040` | +| `ARDUINO_ARCH_AT32` | `HAL/AT32` | + +If none match, `platforms.h` emits `#error "Unsupported Platform!"`. The guard macros are supplied by the Arduino core / toolchain / board JSON for the active environment, so the _compiler_ (not the build script) decides which family `HAL_PATH` points at. + +> Note: `HAL_PATH` is a stringizing macro, so every "include the family file" line in the cascade uses `HAL_PATH(.., )` rather than a literal path. The `..` argument is the relative path back to `Marlin/src/` from inside `Marlin/src/HAL/`. + +## Build-time inclusion (ini build_src_filter) + +The PlatformIO `default_src_filter` (defined in `platformio.ini`'s `[common]` section) starts from `+` and then **excludes the whole HAL tree**: + +``` +default_src_filter = + - - + ... + ; Marlin HAL + - + + + - + - + - + - + ... +``` + +So by default _no_ family folder is compiled (only `HAL/shared/` and a few named shared files survive). Each family's board environment re-enables exactly one family folder via its `build_src_filter`, e.g.: + +- `ini/at32.ini` (`[AT32F403_base]`): `build_src_filter = ${common.default_src_filter} +` +- `ini/avr.ini`: `+` +- `ini/raspberrypi.ini`: `+` +- `ini/esp32.ini`: `+` +- `ini/lpc176x.ini`: `+ - +` +- `ini/stm32f1-maple.ini`: `+ -` + +Some environments additionally enable `src/HAL/shared/backtrace` or prune per-board subtrees (e.g. `-`, re-added selectively through `custom_marlin.*` switches). The net effect: at build time, only the _one active_ family folder plus the shared helpers are compiled — even though all families ship in the source tree. + +## Layering: HAL vs pins vs boards + +Three layers cooperate to bind a binary to a specific board: + +1. **`Marlin/src/core/boards.h`** — selects the board. The active `MOTHERBOARD` define (from `Configuration.h` / `Config.h`) maps to a board header under `Marlin/src/pins//`, which in turn supplies `pins_arduino.h` (or equivalent) pin macros and the family's `HAL_PATH`-relevant defines. +2. **`Marlin/src/pins//`** — board pin maps (logical pin → MCU pin) for one family. These are data only; they do not implement behavior. +3. **`Marlin/src/HAL//`** — the _implementation_: timers, serial, SPI, fastio, EEPROM backends, `MarlinHAL` subclass, etc. This is the only layer that contains executable HAL logic. + +The compiler guard macro in `platforms.h` (section above) decides which `HAL/` directory the `HAL_PATH` macro resolves to; `core/boards.h` + `pins/` decide which pin table and which `MOTHERBOARD` apply; and `ini/*.ini` `build_src_filter` decides which family's `.cpp` files are actually passed to the compiler. They must agree for a build to be coherent. + +## Where the HAL sits in the inc/ include ordering + +Every Marlin translation unit includes `Marlin/src/inc/MarlinConfig.h` (or, for dependency-only scans, `MarlinConfigPre.h`). The cascade below is traced from the real files (`MarlinConfig.h`, `MarlinConfigPre.h`, `MarlinConfigPre-5-post.h`, `MarlinConfigPre-6-type.h`, `Conditionals-*`). + +### Net ordered cascade + +``` +MarlinConfig.h +└─ Conditionals-6-type.h + └─ MarlinConfigPre-6-type.h + ā”œā”€ Conditionals-5-post.h + │ ā”œā”€ MarlinConfigPre.h + │ │ ā”œā”€ Conditionals-1-axes.h + │ │ │ └─ MarlinConfigPre-1-axes.h + │ │ │ ā”œā”€ Config.h + │ │ │ ā”œā”€ macros.h + │ │ │ ā”œā”€ core/boards.h ← board selection + │ │ │ ā”œā”€ Configuration.h (if not Config.h) + │ │ │ ā”œā”€ HAL/platforms.h ← defines HAL_PATH macro + family + │ │ │ └─ Version.h + │ │ ā”œā”€ Conditionals-2-LCD.h + │ │ ā”œā”€ Conditionals-3-etc.h + │ │ ā”œā”€ Conditionals-4-adv.h + │ │ │ └─ MarlinConfigPre-4-adv.h + │ │ │ ā”œā”€ Conditionals-3-etc.h (re-included) + │ │ │ ā”œā”€ drivers.h + │ │ │ └─ Configuration_adv.h + │ │ └─ HAL_PATH(.., inc/Conditionals_adv.h) ← FAMILY conditionals + │ └─ (rest of Conditionals-5-post.h) + │ ā”œā”€ HAL_PATH(.., inc/Conditionals_post.h) ← FAMILY post-conditionals + │ └─ core/types.h + └─ MarlinConfigPre-5-post.h + ā”œā”€ ../HAL/HAL.h ← HAL DISPATCHER (pulls family HAL.h via HAL_PATH; + │ declares `extern MarlinHAL hal;`) + ā”œā”€ ../pins/pins.h ← board pin table + ā”œā”€ HAL_PATH(.., timers.h) + └─ HAL_PATH(.., spi_pins.h) + (type-level conditionals defined in Conditionals-6-type.h) +MarlinConfig.h (resumes) +ā”œā”€ HAL_PATH(.., inc/Conditionals_type.h) ← FAMILY type conditionals +ā”œā”€ Changes.h +ā”œā”€ core/language.h, core/utility.h, core/mstring.h, core/serial.h, core/endianness.h +ā”œā”€ MarlinCore.h +ā”œā”€ SanityCheck.h ← TOP-LEVEL src/inc/SanityCheck.h +└─ HAL_PATH(.., inc/SanityCheck.h) ← FAMILY sanity checks +``` + +Key points: + +- **`HAL/platforms.h` is pulled in early** — inside `MarlinConfigPre-1-axes.h`, before any Conditionals run — so the `HAL_PATH` macro and the selected family are defined before anything else needs them. +- **`HAL.h` (the dispatcher) is included mid-cascade**, from `Marlin/src/inc/MarlinConfigPre-5-post.h` via `#include "../HAL/HAL.h"` (or `HAL/shared/fauxpins.h` when building with `__MARLIN_DEPS__`). This is the point where the family's own `HAL.h` (via `HAL_PATH(.., HAL.h)`) and the `MarlinHAL hal` instance become visible. +- **`pins.h` and the family `timers.h` / `spi_pins.h` are included immediately after `HAL.h`** in the same file, so pin and timer configuration are available before any module source compiles. +- **Family-local `inc/` files are included through `HAL_PATH(.., inc/.h)`, never by literal path.** The top-level cascade includes the family's `inc/Conditionals_adv.h` (from `MarlinConfigPre.h`), `inc/Conditionals_post.h` (from `MarlinConfigPre-6-type.h`), and `inc/Conditionals_type.h` plus `inc/SanityCheck.h` (both from `MarlinConfig.h`). Each family provides whichever of these files it needs; missing ones simply aren't compiled. + +### How the family SanityCheck is reached + +`Marlin/src/inc/MarlinConfig.h` ends the cascade with (inside `#ifndef __MARLIN_DEPS__`): + +```cpp +#include "SanityCheck.h" // Marlin/src/inc/SanityCheck.h (top-level) +#include HAL_PATH(.., inc/SanityCheck.h) // e.g. Marlin/src/HAL/STM32/inc/SanityCheck.h +``` + +So the _family_ sanity checks run **last**, after the top-level `SanityCheck.h` and after all `Conditionals-*`. The family file guards itself with `#pragma once` and family-specific `#ifdef` checks (e.g. the STM32/AT32 files open with `#ifndef PA0 #error "Your ARM platform pins are not defined as macros, only as enums!"`, forcing the board pin file to re-expose pins as numeric `#define`s). The top-level `src/inc/SanityCheck.h` may also reference HAL-specific macros and, where it does, point the reader to `HAL//inc/SanityCheck.h` for the platform-specific rule. + +## Conventions + +- The HAL top level is deliberately thin: `HAL.h` + `platforms.h` + `shared/` + the family folders. Do not add cross-family logic at the top level — put shared code in `HAL/shared/` and family-specific code in `HAL//`. +- Edit `Marlin/src/inc/*` (Conditionals/SanityCheck) only for _cross-family_ rules. Platform-specific checks belong in `HAL//inc/`. +- Keep board pin tables in `Marlin/src/pins//`; keep HAL behavior in `Marlin/src/HAL//`. +- When adding a new family, you must (a) add a branch to `platforms.h`, (b) create a `HAL//` tree with its own `HAL.h`/`MarlinHAL` and `inc/` files, and (c) add a `build_src_filter = ${common.default_src_filter} +>` to the family's `ini/*.ini` environment. + +## Related + +- Per-family docs: `Marlin/src/HAL//AGENTS.md` (e.g. `STM32/`, `AT32/`, `AVR/`, `ESP32/`, `RP2040/`, `LPC1768/`, `LINUX/`, `NATIVE_SIM/`, …). +- `Marlin/src/inc/MarlinConfig.h`, `MarlinConfigPre.h` — the include cascade. +- `Marlin/src/core/boards.h` — board selection; `Marlin/src/pins/` — board pin maps. +- `Marlin/src/HAL/shared/` — shared HAL APIs every family reuses. +- `platformio.ini` (`[common]` `default_src_filter`) and `ini/*.ini` — build-time family selection via `build_src_filter`. +- Wiki: [[marlin-src-hal]], [[hal-selection]], [[marlin-config-cascade]]. diff --git a/Marlin/src/HAL/AVR/AGENTS.md b/Marlin/src/HAL/AVR/AGENTS.md new file mode 100644 index 000000000000..1fc5c6f296d1 --- /dev/null +++ b/Marlin/src/HAL/AVR/AGENTS.md @@ -0,0 +1,53 @@ +# AGENTS.md — AVR HAL + +Working notes and conventions for the **AVR HAL** folder (`Marlin/src/HAL/AVR/`). This HAL covers the legacy 8-bit AVR microcontrollers (ATmega1280/2560/644/1284, AT90USB family) used on classic boards (Arduino Mega, RAMBo, Sanguinololu, Melzi, MightyBoard). It is the tightest resource-constrained target in Marlin: 8-bit, no FPU, tiny RAM/flash. + +## Build & Test Loop + +Validate with `mftest` (it regenerates `Marlin/Configuration.h` per target, which raw `pio run -e` does not do reliably): + +```bash +cd "$(git rev-parse --show-toplevel)" +buildroot/bin/mftest -t mega2560 -n1 -y # ATmega2560, 256 KB flash / 8 KB RAM — tightest constraint +buildroot/bin/mftest -t mega2560ext -n1 -y # ATmega2560, extended pins 70-85 +buildroot/bin/mftest -t mega1280 -n1 -y # ATmega1280 +buildroot/bin/mftest -t rambo -n1 -y # RepRap RAMBo (ATmega2560) +buildroot/bin/mftest -t sanguino644p -n1 -y # ATmega644p +buildroot/bin/mftest -t sanguino1284p -n1 -y # ATmega1284p +buildroot/bin/mftest -t melzi_optiboot -n1 -y # Melzi (ATmega1284p, optiboot) +``` + +All targets build from `ini/avr.ini` (`env:` sections). The ATmega2560 (8 KB RAM, 256 KB flash) is the tightest constraint — if it fits there, the 1284p/rambo builds will too, but watch **RAM**, not just flash. `melzi_optiboot` / `sanguino1284p_optimized` add size-saving flags (`-fno-split-wide-types -mcall-prologues`) because the 1284p is flash- starved. + +> Do NOT use `rm -rf .pio/build/...` to force a rebuild — the cross-profile write guard blocks it and it is unsafe. Use `pio run -e -t clean` or just let `mftest` rebuild. + +## Framework / Platform Integration (upstream, NOT vendored) + +Unlike AT32, the AVR Arduino core is the **upstream** PlatformIO `atmelavr@~4.0.1` platform and the stock Arduino AVR core — it is NOT vendored under `buildroot/share/PlatformIO/`. There is no "mirror to installed package" requirement. Marlin-specific glue lives only in this HAL folder and in `buildroot/share/PlatformIO/` scripts (e.g. `generic_create_variant.py` for the `MARLIN_MEGA_EXTENDED` / `MARLIN_MEGA1281` variants). Most build flags are set in `ini/avr.ini`, not a framework build script. + +### Gotchas that are not obvious from the source + +1. **Harvard architecture — `PROGMEM` is mandatory for read-only data.** AVR has separate flash and RAM; any string or table left in `.data`/`.bss` silently eats the ~8 KB RAM. `HAL.h` defines `PGMSTR(NAM,STR)` and `pgm_read_ptr` shims; `pinsDebug.h`, `pinsDebug_plus_70.h`, and `u8g/u8g_com_HAL_AVR_sw_spi.cpp` all read via `pgm_read_byte` / `pgm_read_word`. **Never put a lookup table or string literal in plain RAM on AVR — mark it `PROGMEM` and read it back with the `pgm_read_*` macros.** + +2. **No FPU — float math is emulated in software and is slow + large.** AVR has no native float unit. `HAL/AVR/math.h` provides hand-written assembly fixed-point helpers (`MultiU24X32toH16`, `MultiU8X8toH8`) used in the stepper hot path. **Avoid `float`/ `double` in ISRs and step-timing code; prefer the fixed-point helpers or integer math, or the build will bloat and stutter.** + +3. **`MARLIN_EEPROM_SIZE` comes from `E2END`.** `eeprom.cpp` derives `MARLIN_EEPROM_SIZE` as `E2END + 1` (the framework defines `E2END` per-MCU — 4095 on ATmega2560). There is **no external EEPROM backend** (no `I2C_EEPROM`/`SPI_EEPROM`/ `SDCARD_EEPROM_EMULATION`) auto-selected; AVR uses the MCU's internal EEPROM only. Writes are byte-diffed to limit wear, with `delay(2)` to dodge the watchdog. + +4. **`SERIAL_PORT` (and `_2`/`_3`) must be 0–3, and USB-CDC is not the default.** AVR Marlin uses hardware UART, not USB-CDC, except on the AT90USB family (native USB; `USBCON` defined → `HardwareSerial`, and `BOARD_NO_NATIVE_USB` is NOT defined). For non-USBCON parts `HAL.h` pulls in `MarlinSerial.h` and enforces `WITHIN(SERIAL_PORT, 0, 3)` with `#error`. **Don't raise `SERIAL_PORT` past 3.** + +5. **Optimization flags live in `ini/avr.ini`, not a framework script.** Unlike AT32, there is no vendored `platformio-build-arduino.py`. Size-saving flags are in the env definitions: `common_avr8` adds `-Wl,--relax`; `tuned_1284p` / `mega_extended_optimized` add `-fno-tree-scev-cprop -fno-split-wide-types -mcall-prologues`. Flash overflow on a 1284p board means reaching for those flags, not editing source. + +6. **`freeMemory()` is a real symbol, not a macro.** `HAL.h` declares `extern "C" int freeMemory();` (defined in `Marlin/src/HAL/shared/`); the AVR build's SRAM pressure makes it the go-to diagnostic. The watchdog is a no-op unless `USE_WATCHDOG` is set (`watchdog_init`/`watchdog_refresh` are `IF_DISABLED(USE_WATCHDOG, {})`), and reset source is cleared via `MCUSR = 0`. + +## Conventions + +- The compiler guard macro is `__AVR__` (defined by the compiler, NOT `ARDUINO_ARCH_AVR` — `HAL/platforms.h` keys on `__AVR__`). Do not add `ARDUINO_ARCH_AVR` guards. +- Keep HAL changes in `Marlin/src/HAL/AVR/`; per-board pin maps live in `Marlin/src/pins/rambo/`, `Marlin/src/pins/mega/`, `Marlin/src/pins/sanguino/`, `Marlin/src/pins/melzi/`, etc. Build/variant glue goes in `buildroot/share/PlatformIO/` and `ini/avr.ini`. +- Fast I/O macros are in `fastio.h` with per-MCU `fastio/fastio_*.h` includes (1280, 1281, 168, 644, AT90USB). Servo timing lives in `ServoTimers.h`. +- Treat RAM as the scarce resource: prefer `PROGMEM`, fixed-point math, and the tuned build flags on 1284p-class targets. + +## Related + +- Wiki: [[hal-avr]], [[marlin-src-hal]]. +- `Marlin/src/HAL/shared/` — shared HAL APIs the AVR HAL reuses (`eeprom_api`, `HAL_SPI`, serial hook). +- `ini/avr.ini` — all AVR env definitions and size-saving flags. diff --git a/Marlin/src/HAL/AVR/HAL.cpp b/Marlin/src/HAL/AVR/HAL.cpp index 97173c63f9c1..1fa7222711a1 100644 --- a/Marlin/src/HAL/AVR/HAL.cpp +++ b/Marlin/src/HAL/AVR/HAL.cpp @@ -96,7 +96,7 @@ void MarlinHAL::init() { // Might disable other peripherals using the pin; to circumvent that please undefine one of the above things! // The true culprit is the AVR ArduinoCore that enables peripherals redundantly. // (USART1 on the GeeeTech GT2560) - // https://www.youtube.com/watch?v=jMgCvRXkexk + // https://youtube.be/jMgCvRXkexk _ATmega_savePinAlternate(BEEPER_PIN); OUT_WRITE(BEEPER_PIN, LOW); @@ -119,7 +119,6 @@ void MarlinHAL::reboot() { #if ENABLED(USE_WATCHDOG) #include - #include "../../MarlinCore.h" // Initialize watchdog with 8s timeout, if possible. Otherwise, make it 4s. void MarlinHAL::watchdog_init() { @@ -154,7 +153,7 @@ void MarlinHAL::reboot() { ISR(WDT_vect) { sei(); // With the interrupt driven serial we need to allow interrupts. SERIAL_ERROR_MSG(STR_WATCHDOG_FIRED); - minkill(); // interrupt-safe final kill and infinite loop + marlin.minkill(); // interrupt-safe final kill and infinite loop } #endif diff --git a/Marlin/src/HAL/AVR/HAL.h b/Marlin/src/HAL/AVR/HAL.h index 3c883b645c20..02c5d42beb95 100644 --- a/Marlin/src/HAL/AVR/HAL.h +++ b/Marlin/src/HAL/AVR/HAL.h @@ -129,11 +129,11 @@ typedef Servo hal_servo_t; #endif #endif -#ifdef MMU2_SERIAL_PORT - #if !WITHIN(MMU2_SERIAL_PORT, 0, 3) - #error "MMU2_SERIAL_PORT must be from 0 to 3" +#ifdef MMU_SERIAL_PORT + #if !WITHIN(MMU_SERIAL_PORT, 0, 3) + #error "MMU_SERIAL_PORT must be from 0 to 3" #endif - #define MMU2_SERIAL mmuSerial + #define MMU_SERIAL mmuSerial #endif #ifdef LCD_SERIAL_PORT @@ -141,7 +141,7 @@ typedef Servo hal_servo_t; #error "LCD_SERIAL_PORT must be from 0 to 3." #endif #define LCD_SERIAL lcdSerial - #if HAS_DGUS_LCD + #if ANY(HAS_DGUS_LCD, EXTENSIBLE_UI) #define LCD_SERIAL_TX_BUFFER_FREE() LCD_SERIAL.get_tx_buffer_free() #endif #endif @@ -159,7 +159,7 @@ typedef Servo hal_servo_t; #define GET_PIN_MAP_INDEX(pin) pin #define PARSED_PIN_INDEX(code, dval) parser.intval(code, dval) -#define HAL_SENSITIVE_PINS 0, 1, +#define HAL_SENSITIVE_PINS 0, 1 #ifdef __AVR_AT90USB1286__ #define JTAG_DISABLE() do{ MCUCR = 0x80; MCUCR = 0x80; }while(0) @@ -204,9 +204,9 @@ class MarlinHAL { static void isr_on() { sei(); } static void isr_off() { cli(); } - static void delay_ms(const int ms) { _delay_ms(ms); } + static void delay_ms(const int ms) { delay(ms); } - // Tasks, called from idle() + // Tasks, called from marlin.idle() static void idletask() {} // Reset diff --git a/Marlin/src/HAL/AVR/MarlinSerial.cpp b/Marlin/src/HAL/AVR/MarlinSerial.cpp index 986462437c8f..750776c4be33 100644 --- a/Marlin/src/HAL/AVR/MarlinSerial.cpp +++ b/Marlin/src/HAL/AVR/MarlinSerial.cpp @@ -41,7 +41,6 @@ #if !defined(USBCON) && (defined(UBRRH) || defined(UBRR0H) || defined(UBRR1H) || defined(UBRR2H) || defined(UBRR3H)) #include "MarlinSerial.h" -#include "../../MarlinCore.h" #if ENABLED(DIRECT_STEPPING) #include "../../feature/direct_stepping.h" @@ -601,20 +600,20 @@ MSerialT1 customizedSerial1(MSerialT1::HasEmergencyParser); #endif // SERIAL_PORT_3 -#ifdef MMU2_SERIAL_PORT +#ifdef MMU_SERIAL_PORT - ISR(SERIAL_REGNAME(USART, MMU2_SERIAL_PORT, _RX_vect)) { - MarlinSerial>::store_rxd_char(); + ISR(SERIAL_REGNAME(USART, MMU_SERIAL_PORT, _RX_vect)) { + MarlinSerial>::store_rxd_char(); } - ISR(SERIAL_REGNAME(USART, MMU2_SERIAL_PORT, _UDRE_vect)) { - MarlinSerial>::_tx_udr_empty_irq(); + ISR(SERIAL_REGNAME(USART, MMU_SERIAL_PORT, _UDRE_vect)) { + MarlinSerial>::_tx_udr_empty_irq(); } - template class MarlinSerial< MMU2SerialCfg >; + template class MarlinSerial< MMU2SerialCfg >; MSerialMMU2 mmuSerial(MSerialMMU2::HasEmergencyParser); -#endif // MMU2_SERIAL_PORT +#endif // MMU_SERIAL_PORT #ifdef LCD_SERIAL_PORT @@ -629,7 +628,7 @@ MSerialT1 customizedSerial1(MSerialT1::HasEmergencyParser); template class MarlinSerial< LCDSerialCfg >; MSerialLCD lcdSerial(MSerialLCD::HasEmergencyParser); - #if HAS_DGUS_LCD + #if ANY(HAS_DGUS_LCD, EXTENSIBLE_UI) template typename MarlinSerial::ring_buffer_pos_t MarlinSerial::get_tx_buffer_free() { const ring_buffer_pos_t t = tx_buffer.tail, // next byte to send. diff --git a/Marlin/src/HAL/AVR/MarlinSerial.h b/Marlin/src/HAL/AVR/MarlinSerial.h index a40730e0a87f..9ecf3bde2201 100644 --- a/Marlin/src/HAL/AVR/MarlinSerial.h +++ b/Marlin/src/HAL/AVR/MarlinSerial.h @@ -205,7 +205,7 @@ static ring_buffer_pos_t available(); static void write(const uint8_t c); static void flushTX(); - #if HAS_DGUS_LCD + #if ANY(HAS_DGUS_LCD, EXTENSIBLE_UI) static ring_buffer_pos_t get_tx_buffer_free(); #endif @@ -246,7 +246,7 @@ #endif // !USBCON -#ifdef MMU2_SERIAL_PORT +#ifdef MMU_SERIAL_PORT template struct MMU2SerialCfg { static constexpr int PORT = serial; @@ -260,7 +260,7 @@ static constexpr bool RX_OVERRUNS = false; }; - typedef Serial1Class< MarlinSerial< MMU2SerialCfg > > MSerialMMU2; + typedef Serial1Class< MarlinSerial< MMU2SerialCfg > > MSerialMMU2; extern MSerialMMU2 mmuSerial; #endif diff --git a/Marlin/src/HAL/AVR/endstop_interrupts.h b/Marlin/src/HAL/AVR/endstop_interrupts.h index 91cb336e2462..a6813ff27752 100644 --- a/Marlin/src/HAL/AVR/endstop_interrupts.h +++ b/Marlin/src/HAL/AVR/endstop_interrupts.h @@ -345,6 +345,14 @@ void setup_endstop_interrupts() { pciSetup(Z_MIN_PROBE_PIN); #endif #endif + #if USE_CALIBRATION + #if (digitalPinToInterrupt(CALIBRATION_PIN) != NOT_AN_INTERRUPT) + _ATTACH(CALIBRATION_PIN); + #else + static_assert(digitalPinHasPCICR(CALIBRATION_PIN), "CALIBRATION_PIN is not interrupt-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue."); + pciSetup(CALIBRATION_PIN); + #endif + #endif // If we arrive here without raising an assertion, each pin has either an EXT-interrupt or a PCI. } diff --git a/Marlin/src/HAL/AVR/fast_pwm.cpp b/Marlin/src/HAL/AVR/fast_pwm.cpp index 6da68e6245d3..936f9e56889b 100644 --- a/Marlin/src/HAL/AVR/fast_pwm.cpp +++ b/Marlin/src/HAL/AVR/fast_pwm.cpp @@ -150,7 +150,7 @@ void MarlinHAL::set_pwm_frequency(const pin_t pin, const uint16_t f_desired) { else { if (p == 32 || p == 128) continue; // Skip TIMER2 specific prescalers when not TIMER2 const uint16_t rft = (F_CPU) / (p * f_desired); - DEBUG_ECHOLNPGM("(Not Timer 2) F_CPU=" STRINGIFY(F_CPU), " prescaler=", p, " f_desired=", f_desired); + DEBUG_ECHOLNPGM("(Not Timer 2) F_CPU=", STRINGIFY(F_CPU), " prescaler=", p, " f_desired=", f_desired); res_fast_temp = rft - 1; res_pc_temp = rft / 2; } diff --git a/Marlin/src/HAL/AVR/fastio.cpp b/Marlin/src/HAL/AVR/fastio.cpp index 5c6ef1891512..98fd636ebf1c 100644 --- a/Marlin/src/HAL/AVR/fastio.cpp +++ b/Marlin/src/HAL/AVR/fastio.cpp @@ -241,7 +241,7 @@ uint8_t extDigitalRead(const int8_t pin) { * * DC values -1.0 to 1.0. Negative duty cycle inverts the pulse. */ -uint16_t set_pwm_frequency_hz(const_float_t hz, const float dca, const float dcb, const float dcc) { +uint16_t set_pwm_frequency_hz(const float hz, const float dca, const float dcb, const float dcc) { float count = 0; if (hz > 0 && (dca || dcb || dcc)) { count = float(F_CPU) / hz; // 1x prescaler, TOP for 16MHz base freq. @@ -254,7 +254,7 @@ uint16_t set_pwm_frequency_hz(const_float_t hz, const float dca, const float dcb else { prescaler = 1; SET_CS(5, PRESCALER_1); } count /= float(prescaler); - const float pwm_top = round(count); // Get the rounded count + const float pwm_top = roundf(count); // Get the rounded count ICR5 = (uint16_t)pwm_top - 1; // Subtract 1 for TOP OCR5A = pwm_top * ABS(dca); // Update and scale DCs @@ -280,7 +280,7 @@ uint16_t set_pwm_frequency_hz(const_float_t hz, const float dca, const float dcb SET_CS(5, PRESCALER_64); // 16MHz / 64 = 250kHz OCR5A = OCR5B = OCR5C = 0; } - return round(count); + return roundf(count); } #endif diff --git a/Marlin/src/HAL/AVR/fastio/fastio_1280.h b/Marlin/src/HAL/AVR/fastio/fastio_1280.h index 633774dda916..57d6181d2e98 100644 --- a/Marlin/src/HAL/AVR/fastio/fastio_1280.h +++ b/Marlin/src/HAL/AVR/fastio/fastio_1280.h @@ -28,9 +28,6 @@ * Port : E0 E1 E4 E5 G5 E3 H3 H4 H5 H6 B4 B5 B6 B7 J1 J0 H1 H0 D3 D2 D1 D0 A0 A1 A2 A3 A4 A5 A6 A7 C7 C6 C5 C4 C3 C2 C1 C0 D7 G2 G1 G0 L7 L6 L5 L4 L3 L2 L1 L0 B3 B2 B1 B0 F0 F1 F2 F3 F4 F5 F6 F7 K0 K1 K2 K3 K4 K5 K6 K7 | E2 E6 E7 xx xx H2 H7 G3 G4 xx xx xx xx xx D4 D5 D6 xx xx J2 J3 J4 J5 J6 J7 xx xx xx xx xx * Logical Pin : 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | 78 79 80 xx xx 84 85 71 70 xx xx xx xx xx 81 82 83 xx xx 72 73 75 76 77 74 xx xx xx xx xx * Analog Input : 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 - * - * Arduino Pin Layout video: https://youtu.be/rIqeVCX09FA - * AVR alternate pin function overview video: https://youtu.be/1yd8wuI5Plg */ #include "../fastio.h" diff --git a/Marlin/src/HAL/AVR/fastio/fastio_1281.h b/Marlin/src/HAL/AVR/fastio/fastio_1281.h index 6067248978ae..fdff219ec3d7 100644 --- a/Marlin/src/HAL/AVR/fastio/fastio_1281.h +++ b/Marlin/src/HAL/AVR/fastio/fastio_1281.h @@ -26,9 +26,6 @@ * * Logical Pin: 38 39 40 41 42 43 44 45 16 10 11 12 06 07 08 09 30 31 32 33 34 35 36 37 17 18 19 20 21 22 23 24 00 01 13 05 02 03 14 15 46 47 48 49 50 51 52 53 25 26 27 28 29 04 * 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 G0 G1 G2 G3 G4 G5 - * - * Arduino Pin Layout video: https://youtu.be/rIqeVCX09FA - * AVR alternate pin function overview video: https://youtu.be/1yd8wuI5Plg */ #include "../fastio.h" diff --git a/Marlin/src/HAL/AVR/fastio/fastio_168.h b/Marlin/src/HAL/AVR/fastio/fastio_168.h index cc559797408b..36dc55238527 100644 --- a/Marlin/src/HAL/AVR/fastio/fastio_168.h +++ b/Marlin/src/HAL/AVR/fastio/fastio_168.h @@ -26,9 +26,6 @@ * * Logical Pin: 08 09 10 11 12 13 14 15 16 17 18 19 20 21 00 01 02 03 04 05 06 07 * Port: B0 B1 B2 B3 B4 B5 C0 C1 C2 C3 C4 C5 C6 C7 D0 D1 D2 D3 D4 D5 D6 D7 - * - * Arduino Pin Layout video: https://youtu.be/rIqeVCX09FA - * AVR alternate pin function overview video: https://youtu.be/1yd8wuI5Plg */ #include "../fastio.h" diff --git a/Marlin/src/HAL/AVR/fastio/fastio_644.h b/Marlin/src/HAL/AVR/fastio/fastio_644.h index 94b322a8190d..e20a3d345e37 100644 --- a/Marlin/src/HAL/AVR/fastio/fastio_644.h +++ b/Marlin/src/HAL/AVR/fastio/fastio_644.h @@ -26,9 +26,6 @@ * * Logical Pin: 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 * Port: B0 B1 B2 B3 B4 B5 B6 B7 D0 D1 D2 D3 D4 D5 D6 D7 C0 C1 C2 C3 C4 C5 C6 C7 A7 A6 A5 A4 A3 A2 A1 A0 - * - * Arduino Pin Layout video: https://youtu.be/rIqeVCX09FA - * AVR alternate pin function overview video: https://youtu.be/1yd8wuI5Plg */ /** ATMega644 diff --git a/Marlin/src/HAL/AVR/fastio/fastio_AT90USB.h b/Marlin/src/HAL/AVR/fastio/fastio_AT90USB.h index 51c5e096586e..a9af519ff3f1 100644 --- a/Marlin/src/HAL/AVR/fastio/fastio_AT90USB.h +++ b/Marlin/src/HAL/AVR/fastio/fastio_AT90USB.h @@ -26,10 +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 - * - * Arduino Pin Layout video: https://youtu.be/rIqeVCX09FA - * AVR alternate pin function overview video: https://youtu.be/1yd8wuI5Plg + * Logical pins 46-47 aren't supported by Teensyduino, but are supported below as E2 and E3 */ #include "../fastio.h" @@ -366,8 +363,11 @@ #define AIO7_PWM 0 #define AIO7_DDR DDRF -//-- Begin not supported by Teensyduino -//-- don't use Arduino functions on these pins pinMode/digitalWrite/etc +//-- 46-47 are not supported by Teensyduino +//-- Don't use Arduino functions (pinMode, digitalWrite, etc.) on these pins +#define PIN_E2 46 +#define PIN_E3 47 + #define DIO46_PIN PINE2 #define DIO46_RPORT PINE #define DIO46_WPORT PORTE @@ -380,10 +380,7 @@ #define DIO47_PWM 0 #define DIO47_DDR DDRE -#define TEENSY_E2 46 -#define TEENSY_E3 47 - -//-- end not supported by Teensyduino +//-- #undef PA0 #define PA0_PIN PINA0 diff --git a/Marlin/src/HAL/AVR/inc/SanityCheck.h b/Marlin/src/HAL/AVR/inc/SanityCheck.h index 85ee683685ec..08fe21d4f8e0 100644 --- a/Marlin/src/HAL/AVR/inc/SanityCheck.h +++ b/Marlin/src/HAL/AVR/inc/SanityCheck.h @@ -95,7 +95,7 @@ /** * The Trinamic library includes SoftwareSerial.h, leading to a compile error. */ -#if ALL(HAS_TRINAMIC_CONFIG, ENDSTOP_INTERRUPTS_FEATURE) +#if ALL(HAS_TMC_SW_SERIAL, ENDSTOP_INTERRUPTS_FEATURE) #error "TMCStepper includes SoftwareSerial.h which is incompatible with ENDSTOP_INTERRUPTS_FEATURE. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." #endif diff --git a/Marlin/src/HAL/AVR/pinsDebug.h b/Marlin/src/HAL/AVR/pinsDebug.h index 15db63b4d7f8..ce801333a23c 100644 --- a/Marlin/src/HAL/AVR/pinsDebug.h +++ b/Marlin/src/HAL/AVR/pinsDebug.h @@ -22,14 +22,30 @@ #pragma once /** - * PWM print routines for Atmel 8 bit AVR CPUs + * Pins Debugging for Atmel 8 bit AVR CPUs + * + * - NUMBER_PINS_TOTAL + * - MULTI_NAME_PAD + * - getPinByIndex(index) + * - printPinNameByIndex(index) + * - getPinIsDigitalByIndex(index) + * - digitalPinToAnalogIndex(pin) + * - getValidPinMode(pin) + * - isValidPin(pin) + * - isAnalogPin(pin) + * - digitalRead_mod(pin) + * - pwm_status(pin) + * - printPinPWM(pin) + * - printPinPort(pin) + * - printPinNumber(pin) + * - printPinAnalog(pin) */ #include "../../inc/MarlinConfig.h" #define NUMBER_PINS_TOTAL NUM_DIGITAL_PINS -#if MB(BQ_ZUM_MEGA_3D, MIGHTYBOARD_REVE, MINIRAMBO, SCOOVO_X9H, TRIGORILLA_14) +#if MB(BQ_ZUM_MEGA_3D, MIGHTYBOARD_REVE, MINIRAMBO, SCOOVO_X9H, TRIGORILLA_14, MIGHTYBOARD_REVG) #define AVR_ATmega2560_FAMILY_PLUS_70 1 #endif @@ -39,44 +55,44 @@ #include "pinsDebug_Teensyduino.h" // Can't use the "digitalPinToPort" function from the Teensyduino type IDEs // portModeRegister takes a different argument - #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 digitalPinToTimer_DEBUG(P) digitalPinToTimer(P) + #define digitalPinToBitMask_DEBUG(P) digitalPinToBitMask(P) + #define digitalPinToPort_DEBUG(P) digitalPinToPort(P) + #define getValidPinMode(P) (*portModeRegister(P) & digitalPinToBitMask_DEBUG(P)) #elif AVR_ATmega2560_FAMILY_PLUS_70 // So we can access/display all the pins on boards using more than 70 #include "pinsDebug_plus_70.h" - #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); } + #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 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) + #define digitalPinToTimer_DEBUG(P) digitalPinToTimer(P) + #define digitalPinToBitMask_DEBUG(P) digitalPinToBitMask(P) + #define digitalPinToPort_DEBUG(P) digitalPinToPort(P) + bool getValidPinMode(pin_t pin) {return *portModeRegister(digitalPinToPort_DEBUG(pin)) & digitalPinToBitMask_DEBUG(pin); } + #define getPinByIndex(x) pgm_read_byte(&pin_array[x].pin) #endif -#define VALID_PIN(pin) (pin >= 0 && pin < NUM_DIGITAL_PINS ? 1 : 0) +#define isValidPin(P) (P >= 0 && P < NUMBER_PINS_TOTAL) #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(x) pgm_read_byte(&pin_array[x].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) { - PGM_P const name_mem_pointer = (PGM_P)pgm_read_ptr(&pin_array[x].name); +void printPinNameByIndex(const uint8_t index) { + PGM_P const name_mem_pointer = (PGM_P)pgm_read_ptr(&pin_array[index].name); for (uint8_t y = 0; y < MAX_NAME_LENGTH; ++y) { char temp_char = pgm_read_byte(name_mem_pointer + y); if (temp_char != 0) @@ -88,7 +104,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 @@ -109,7 +125,7 @@ void PRINT_ARRAY_NAME(uint8_t x) { * Print a pin's PWM status. * Return true if it's currently a PWM pin. */ -bool pwm_status(uint8_t pin) { +bool pwm_status(const uint8_t pin) { char buffer[20]; // for the sprintf statements switch (digitalPinToTimer_DEBUG(pin)) { @@ -276,7 +292,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(const uint8_t pin) { switch (digitalPinToTimer_DEBUG(pin)) { #if ABTEST(0) @@ -347,7 +363,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,21 +372,21 @@ 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: "); #if AVR_AT90USB1286_FAMILY - x = (pin == 46 || pin == 47) ? 'E' : digitalPinToPort_DEBUG(pin) + 64; + x = (pin == PIN_E2 || pin == PIN_E3) ? 'E' : 'A' + digitalPinToPort_DEBUG(pin) - 1; #else - x = digitalPinToPort_DEBUG(pin) + 64; + x = 'A' + digitalPinToPort_DEBUG(pin) - 1; #endif SERIAL_CHAR(x); #if AVR_AT90USB1286_FAMILY - if (pin == 46) + if (pin == PIN_E2) x = '2'; - else if (pin == 47) + else if (pin == PIN_E3) x = '3'; else { uint8_t temp = digitalPinToBitMask_DEBUG(pin); @@ -386,7 +402,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(P)); SERIAL_ECHO(buffer); }while(0) #undef ABTEST diff --git a/Marlin/src/HAL/AVR/pinsDebug_Teensyduino.h b/Marlin/src/HAL/AVR/pinsDebug_Teensyduino.h index c812d4fb1131..463a77ec1d08 100644 --- a/Marlin/src/HAL/AVR/pinsDebug_Teensyduino.h +++ b/Marlin/src/HAL/AVR/pinsDebug_Teensyduino.h @@ -102,7 +102,7 @@ const uint8_t PROGMEM digital_pin_to_port_PGM[] = { // digitalPinToBitMask(pin) is OK -#define digitalRead_mod(p) extDigitalRead(p) // Teensyduino's version of digitalRead doesn't +#define digitalRead_mod(P) extDigitalRead(P) // Teensyduino's version of digitalRead doesn't // disable the PWMs so we can use it as is // portModeRegister(pin) is OK diff --git a/Marlin/src/HAL/AVR/pinsDebug_plus_70.h b/Marlin/src/HAL/AVR/pinsDebug_plus_70.h index fa479cfe8fd9..46fe45c1f822 100644 --- a/Marlin/src/HAL/AVR/pinsDebug_plus_70.h +++ b/Marlin/src/HAL/AVR/pinsDebug_plus_70.h @@ -25,7 +25,7 @@ * Structures for 2560 family boards that use more than 70 pins */ -#if MB(BQ_ZUM_MEGA_3D, MINIRAMBO, SCOOVO_X9H, TRIGORILLA_14) +#if MB(BQ_ZUM_MEGA_3D, MINIRAMBO, SCOOVO_X9H, TRIGORILLA_14, MIGHTYBOARD_REVG) #undef NUM_DIGITAL_PINS #define NUM_DIGITAL_PINS 85 #elif MB(MIGHTYBOARD_REVE) @@ -48,92 +48,92 @@ const uint8_t PROGMEM digital_pin_to_port_PGM_plus_70[] = { // PORTLIST // ------------------------ - PE , // PE 0 ** 0 ** USART0_RX - PE , // PE 1 ** 1 ** USART0_TX - PE , // PE 4 ** 2 ** PWM2 - PE , // PE 5 ** 3 ** PWM3 - PG , // PG 5 ** 4 ** PWM4 - PE , // PE 3 ** 5 ** PWM5 - PH , // PH 3 ** 6 ** PWM6 - PH , // PH 4 ** 7 ** PWM7 - PH , // PH 5 ** 8 ** PWM8 - PH , // PH 6 ** 9 ** PWM9 - PB , // PB 4 ** 10 ** PWM10 - PB , // PB 5 ** 11 ** PWM11 - PB , // PB 6 ** 12 ** PWM12 - PB , // PB 7 ** 13 ** PWM13 - PJ , // PJ 1 ** 14 ** USART3_TX - PJ , // PJ 0 ** 15 ** USART3_RX - PH , // PH 1 ** 16 ** USART2_TX - PH , // PH 0 ** 17 ** USART2_RX - PD , // PD 3 ** 18 ** USART1_TX - PD , // PD 2 ** 19 ** USART1_RX - PD , // PD 1 ** 20 ** I2C_SDA - PD , // PD 0 ** 21 ** I2C_SCL - PA , // PA 0 ** 22 ** D22 - PA , // PA 1 ** 23 ** D23 - PA , // PA 2 ** 24 ** D24 - PA , // PA 3 ** 25 ** D25 - PA , // PA 4 ** 26 ** D26 - PA , // PA 5 ** 27 ** D27 - PA , // PA 6 ** 28 ** D28 - PA , // PA 7 ** 29 ** D29 - PC , // PC 7 ** 30 ** D30 - PC , // PC 6 ** 31 ** D31 - PC , // PC 5 ** 32 ** D32 - PC , // PC 4 ** 33 ** D33 - PC , // PC 3 ** 34 ** D34 - PC , // PC 2 ** 35 ** D35 - PC , // PC 1 ** 36 ** D36 - PC , // PC 0 ** 37 ** D37 - PD , // PD 7 ** 38 ** D38 - PG , // PG 2 ** 39 ** D39 - PG , // PG 1 ** 40 ** D40 - PG , // PG 0 ** 41 ** D41 - PL , // PL 7 ** 42 ** D42 - PL , // PL 6 ** 43 ** D43 - PL , // PL 5 ** 44 ** D44 - PL , // PL 4 ** 45 ** D45 - PL , // PL 3 ** 46 ** D46 - PL , // PL 2 ** 47 ** D47 - PL , // PL 1 ** 48 ** D48 - PL , // PL 0 ** 49 ** D49 - PB , // PB 3 ** 50 ** SPI_MISO - PB , // PB 2 ** 51 ** SPI_MOSI - PB , // PB 1 ** 52 ** SPI_SCK - PB , // PB 0 ** 53 ** SPI_SS - PF , // PF 0 ** 54 ** A0 - PF , // PF 1 ** 55 ** A1 - PF , // PF 2 ** 56 ** A2 - PF , // PF 3 ** 57 ** A3 - PF , // PF 4 ** 58 ** A4 - PF , // PF 5 ** 59 ** A5 - PF , // PF 6 ** 60 ** A6 - PF , // PF 7 ** 61 ** A7 - PK , // PK 0 ** 62 ** A8 - PK , // PK 1 ** 63 ** A9 - PK , // PK 2 ** 64 ** A10 - PK , // PK 3 ** 65 ** A11 - PK , // PK 4 ** 66 ** A12 - PK , // PK 5 ** 67 ** A13 - PK , // PK 6 ** 68 ** A14 - PK , // PK 7 ** 69 ** A15 - PG , // PG 4 ** 70 ** - PG , // PG 3 ** 71 ** - PJ , // PJ 2 ** 72 ** - PJ , // PJ 3 ** 73 ** - PJ , // PJ 7 ** 74 ** - PJ , // PJ 4 ** 75 ** - PJ , // PJ 5 ** 76 ** - PJ , // PJ 6 ** 77 ** - PE , // PE 2 ** 78 ** - PE , // PE 6 ** 79 ** - PE , // PE 7 ** 80 ** - PD , // PD 4 ** 81 ** - PD , // PD 5 ** 82 ** - PD , // PD 6 ** 83 ** - PH , // PH 2 ** 84 ** - PH , // PH 7 ** 85 ** + PE, // PE 0 ** 0 ** USART0_RX + PE, // PE 1 ** 1 ** USART0_TX + PE, // PE 4 ** 2 ** PWM2 + PE, // PE 5 ** 3 ** PWM3 + PG, // PG 5 ** 4 ** PWM4 + PE, // PE 3 ** 5 ** PWM5 + PH, // PH 3 ** 6 ** PWM6 + PH, // PH 4 ** 7 ** PWM7 + PH, // PH 5 ** 8 ** PWM8 + PH, // PH 6 ** 9 ** PWM9 + PB, // PB 4 ** 10 ** PWM10 + PB, // PB 5 ** 11 ** PWM11 + PB, // PB 6 ** 12 ** PWM12 + PB, // PB 7 ** 13 ** PWM13 + PJ, // PJ 1 ** 14 ** USART3_TX + PJ, // PJ 0 ** 15 ** USART3_RX + PH, // PH 1 ** 16 ** USART2_TX + PH, // PH 0 ** 17 ** USART2_RX + PD, // PD 3 ** 18 ** USART1_TX + PD, // PD 2 ** 19 ** USART1_RX + PD, // PD 1 ** 20 ** I2C_SDA + PD, // PD 0 ** 21 ** I2C_SCL + PA, // PA 0 ** 22 ** D22 + PA, // PA 1 ** 23 ** D23 + PA, // PA 2 ** 24 ** D24 + PA, // PA 3 ** 25 ** D25 + PA, // PA 4 ** 26 ** D26 + PA, // PA 5 ** 27 ** D27 + PA, // PA 6 ** 28 ** D28 + PA, // PA 7 ** 29 ** D29 + PC, // PC 7 ** 30 ** D30 + PC, // PC 6 ** 31 ** D31 + PC, // PC 5 ** 32 ** D32 + PC, // PC 4 ** 33 ** D33 + PC, // PC 3 ** 34 ** D34 + PC, // PC 2 ** 35 ** D35 + PC, // PC 1 ** 36 ** D36 + PC, // PC 0 ** 37 ** D37 + PD, // PD 7 ** 38 ** D38 + PG, // PG 2 ** 39 ** D39 + PG, // PG 1 ** 40 ** D40 + PG, // PG 0 ** 41 ** D41 + PL, // PL 7 ** 42 ** D42 + PL, // PL 6 ** 43 ** D43 + PL, // PL 5 ** 44 ** D44 + PL, // PL 4 ** 45 ** D45 + PL, // PL 3 ** 46 ** D46 + PL, // PL 2 ** 47 ** D47 + PL, // PL 1 ** 48 ** D48 + PL, // PL 0 ** 49 ** D49 + PB, // PB 3 ** 50 ** SPI_MISO + PB, // PB 2 ** 51 ** SPI_MOSI + PB, // PB 1 ** 52 ** SPI_SCK + PB, // PB 0 ** 53 ** SPI_SS + PF, // PF 0 ** 54 ** A0 + PF, // PF 1 ** 55 ** A1 + PF, // PF 2 ** 56 ** A2 + PF, // PF 3 ** 57 ** A3 + PF, // PF 4 ** 58 ** A4 + PF, // PF 5 ** 59 ** A5 + PF, // PF 6 ** 60 ** A6 + PF, // PF 7 ** 61 ** A7 + PK, // PK 0 ** 62 ** A8 + PK, // PK 1 ** 63 ** A9 + PK, // PK 2 ** 64 ** A10 + PK, // PK 3 ** 65 ** A11 + PK, // PK 4 ** 66 ** A12 + PK, // PK 5 ** 67 ** A13 + PK, // PK 6 ** 68 ** A14 + PK, // PK 7 ** 69 ** A15 + PG, // PG 4 ** 70 ** + PG, // PG 3 ** 71 ** + PJ, // PJ 2 ** 72 ** + PJ, // PJ 3 ** 73 ** + PJ, // PJ 7 ** 74 ** + PJ, // PJ 4 ** 75 ** + PJ, // PJ 5 ** 76 ** + PJ, // PJ 6 ** 77 ** + PE, // PE 2 ** 78 ** + PE, // PE 6 ** 79 ** + PE, // PE 7 ** 80 ** + PD, // PD 4 ** 81 ** + PD, // PD 5 ** 82 ** + PD, // PD 6 ** 83 ** + PH, // PH 2 ** 84 ** + PH, // PH 7 ** 85 ** }; #define digitalPinToPort_plus_70(P) ( pgm_read_byte( digital_pin_to_port_PGM_plus_70 + (P) ) ) @@ -141,92 +141,92 @@ const uint8_t PROGMEM digital_pin_to_port_PGM_plus_70[] = { const uint8_t PROGMEM digital_pin_to_bit_mask_PGM_plus_70[] = { // PIN IN PORT // ------------------------ - _BV( 0 ) , // PE 0 ** 0 ** USART0_RX - _BV( 1 ) , // PE 1 ** 1 ** USART0_TX - _BV( 4 ) , // PE 4 ** 2 ** PWM2 - _BV( 5 ) , // PE 5 ** 3 ** PWM3 - _BV( 5 ) , // PG 5 ** 4 ** PWM4 - _BV( 3 ) , // PE 3 ** 5 ** PWM5 - _BV( 3 ) , // PH 3 ** 6 ** PWM6 - _BV( 4 ) , // PH 4 ** 7 ** PWM7 - _BV( 5 ) , // PH 5 ** 8 ** PWM8 - _BV( 6 ) , // PH 6 ** 9 ** PWM9 - _BV( 4 ) , // PB 4 ** 10 ** PWM10 - _BV( 5 ) , // PB 5 ** 11 ** PWM11 - _BV( 6 ) , // PB 6 ** 12 ** PWM12 - _BV( 7 ) , // PB 7 ** 13 ** PWM13 - _BV( 1 ) , // PJ 1 ** 14 ** USART3_TX - _BV( 0 ) , // PJ 0 ** 15 ** USART3_RX - _BV( 1 ) , // PH 1 ** 16 ** USART2_TX - _BV( 0 ) , // PH 0 ** 17 ** USART2_RX - _BV( 3 ) , // PD 3 ** 18 ** USART1_TX - _BV( 2 ) , // PD 2 ** 19 ** USART1_RX - _BV( 1 ) , // PD 1 ** 20 ** I2C_SDA - _BV( 0 ) , // PD 0 ** 21 ** I2C_SCL - _BV( 0 ) , // PA 0 ** 22 ** D22 - _BV( 1 ) , // PA 1 ** 23 ** D23 - _BV( 2 ) , // PA 2 ** 24 ** D24 - _BV( 3 ) , // PA 3 ** 25 ** D25 - _BV( 4 ) , // PA 4 ** 26 ** D26 - _BV( 5 ) , // PA 5 ** 27 ** D27 - _BV( 6 ) , // PA 6 ** 28 ** D28 - _BV( 7 ) , // PA 7 ** 29 ** D29 - _BV( 7 ) , // PC 7 ** 30 ** D30 - _BV( 6 ) , // PC 6 ** 31 ** D31 - _BV( 5 ) , // PC 5 ** 32 ** D32 - _BV( 4 ) , // PC 4 ** 33 ** D33 - _BV( 3 ) , // PC 3 ** 34 ** D34 - _BV( 2 ) , // PC 2 ** 35 ** D35 - _BV( 1 ) , // PC 1 ** 36 ** D36 - _BV( 0 ) , // PC 0 ** 37 ** D37 - _BV( 7 ) , // PD 7 ** 38 ** D38 - _BV( 2 ) , // PG 2 ** 39 ** D39 - _BV( 1 ) , // PG 1 ** 40 ** D40 - _BV( 0 ) , // PG 0 ** 41 ** D41 - _BV( 7 ) , // PL 7 ** 42 ** D42 - _BV( 6 ) , // PL 6 ** 43 ** D43 - _BV( 5 ) , // PL 5 ** 44 ** D44 - _BV( 4 ) , // PL 4 ** 45 ** D45 - _BV( 3 ) , // PL 3 ** 46 ** D46 - _BV( 2 ) , // PL 2 ** 47 ** D47 - _BV( 1 ) , // PL 1 ** 48 ** D48 - _BV( 0 ) , // PL 0 ** 49 ** D49 - _BV( 3 ) , // PB 3 ** 50 ** SPI_MISO - _BV( 2 ) , // PB 2 ** 51 ** SPI_MOSI - _BV( 1 ) , // PB 1 ** 52 ** SPI_SCK - _BV( 0 ) , // PB 0 ** 53 ** SPI_SS - _BV( 0 ) , // PF 0 ** 54 ** A0 - _BV( 1 ) , // PF 1 ** 55 ** A1 - _BV( 2 ) , // PF 2 ** 56 ** A2 - _BV( 3 ) , // PF 3 ** 57 ** A3 - _BV( 4 ) , // PF 4 ** 58 ** A4 - _BV( 5 ) , // PF 5 ** 59 ** A5 - _BV( 6 ) , // PF 6 ** 60 ** A6 - _BV( 7 ) , // PF 7 ** 61 ** A7 - _BV( 0 ) , // PK 0 ** 62 ** A8 - _BV( 1 ) , // PK 1 ** 63 ** A9 - _BV( 2 ) , // PK 2 ** 64 ** A10 - _BV( 3 ) , // PK 3 ** 65 ** A11 - _BV( 4 ) , // PK 4 ** 66 ** A12 - _BV( 5 ) , // PK 5 ** 67 ** A13 - _BV( 6 ) , // PK 6 ** 68 ** A14 - _BV( 7 ) , // PK 7 ** 69 ** A15 - _BV( 4 ) , // PG 4 ** 70 ** - _BV( 3 ) , // PG 3 ** 71 ** - _BV( 2 ) , // PJ 2 ** 72 ** - _BV( 3 ) , // PJ 3 ** 73 ** - _BV( 7 ) , // PJ 7 ** 74 ** - _BV( 4 ) , // PJ 4 ** 75 ** - _BV( 5 ) , // PJ 5 ** 76 ** - _BV( 6 ) , // PJ 6 ** 77 ** - _BV( 2 ) , // PE 2 ** 78 ** - _BV( 6 ) , // PE 6 ** 79 ** - _BV( 7 ) , // PE 7 ** 80 ** - _BV( 4 ) , // PD 4 ** 81 ** - _BV( 5 ) , // PD 5 ** 82 ** - _BV( 6 ) , // PD 6 ** 83 ** - _BV( 2 ) , // PH 2 ** 84 ** - _BV( 7 ) , // PH 7 ** 85 ** + _BV( 0 ), // PE 0 ** 0 ** USART0_RX + _BV( 1 ), // PE 1 ** 1 ** USART0_TX + _BV( 4 ), // PE 4 ** 2 ** PWM2 + _BV( 5 ), // PE 5 ** 3 ** PWM3 + _BV( 5 ), // PG 5 ** 4 ** PWM4 + _BV( 3 ), // PE 3 ** 5 ** PWM5 + _BV( 3 ), // PH 3 ** 6 ** PWM6 + _BV( 4 ), // PH 4 ** 7 ** PWM7 + _BV( 5 ), // PH 5 ** 8 ** PWM8 + _BV( 6 ), // PH 6 ** 9 ** PWM9 + _BV( 4 ), // PB 4 ** 10 ** PWM10 + _BV( 5 ), // PB 5 ** 11 ** PWM11 + _BV( 6 ), // PB 6 ** 12 ** PWM12 + _BV( 7 ), // PB 7 ** 13 ** PWM13 + _BV( 1 ), // PJ 1 ** 14 ** USART3_TX + _BV( 0 ), // PJ 0 ** 15 ** USART3_RX + _BV( 1 ), // PH 1 ** 16 ** USART2_TX + _BV( 0 ), // PH 0 ** 17 ** USART2_RX + _BV( 3 ), // PD 3 ** 18 ** USART1_TX + _BV( 2 ), // PD 2 ** 19 ** USART1_RX + _BV( 1 ), // PD 1 ** 20 ** I2C_SDA + _BV( 0 ), // PD 0 ** 21 ** I2C_SCL + _BV( 0 ), // PA 0 ** 22 ** D22 + _BV( 1 ), // PA 1 ** 23 ** D23 + _BV( 2 ), // PA 2 ** 24 ** D24 + _BV( 3 ), // PA 3 ** 25 ** D25 + _BV( 4 ), // PA 4 ** 26 ** D26 + _BV( 5 ), // PA 5 ** 27 ** D27 + _BV( 6 ), // PA 6 ** 28 ** D28 + _BV( 7 ), // PA 7 ** 29 ** D29 + _BV( 7 ), // PC 7 ** 30 ** D30 + _BV( 6 ), // PC 6 ** 31 ** D31 + _BV( 5 ), // PC 5 ** 32 ** D32 + _BV( 4 ), // PC 4 ** 33 ** D33 + _BV( 3 ), // PC 3 ** 34 ** D34 + _BV( 2 ), // PC 2 ** 35 ** D35 + _BV( 1 ), // PC 1 ** 36 ** D36 + _BV( 0 ), // PC 0 ** 37 ** D37 + _BV( 7 ), // PD 7 ** 38 ** D38 + _BV( 2 ), // PG 2 ** 39 ** D39 + _BV( 1 ), // PG 1 ** 40 ** D40 + _BV( 0 ), // PG 0 ** 41 ** D41 + _BV( 7 ), // PL 7 ** 42 ** D42 + _BV( 6 ), // PL 6 ** 43 ** D43 + _BV( 5 ), // PL 5 ** 44 ** D44 + _BV( 4 ), // PL 4 ** 45 ** D45 + _BV( 3 ), // PL 3 ** 46 ** D46 + _BV( 2 ), // PL 2 ** 47 ** D47 + _BV( 1 ), // PL 1 ** 48 ** D48 + _BV( 0 ), // PL 0 ** 49 ** D49 + _BV( 3 ), // PB 3 ** 50 ** SPI_MISO + _BV( 2 ), // PB 2 ** 51 ** SPI_MOSI + _BV( 1 ), // PB 1 ** 52 ** SPI_SCK + _BV( 0 ), // PB 0 ** 53 ** SPI_SS + _BV( 0 ), // PF 0 ** 54 ** A0 + _BV( 1 ), // PF 1 ** 55 ** A1 + _BV( 2 ), // PF 2 ** 56 ** A2 + _BV( 3 ), // PF 3 ** 57 ** A3 + _BV( 4 ), // PF 4 ** 58 ** A4 + _BV( 5 ), // PF 5 ** 59 ** A5 + _BV( 6 ), // PF 6 ** 60 ** A6 + _BV( 7 ), // PF 7 ** 61 ** A7 + _BV( 0 ), // PK 0 ** 62 ** A8 + _BV( 1 ), // PK 1 ** 63 ** A9 + _BV( 2 ), // PK 2 ** 64 ** A10 + _BV( 3 ), // PK 3 ** 65 ** A11 + _BV( 4 ), // PK 4 ** 66 ** A12 + _BV( 5 ), // PK 5 ** 67 ** A13 + _BV( 6 ), // PK 6 ** 68 ** A14 + _BV( 7 ), // PK 7 ** 69 ** A15 + _BV( 4 ), // PG 4 ** 70 ** + _BV( 3 ), // PG 3 ** 71 ** + _BV( 2 ), // PJ 2 ** 72 ** + _BV( 3 ), // PJ 3 ** 73 ** + _BV( 7 ), // PJ 7 ** 74 ** + _BV( 4 ), // PJ 4 ** 75 ** + _BV( 5 ), // PJ 5 ** 76 ** + _BV( 6 ), // PJ 6 ** 77 ** + _BV( 2 ), // PE 2 ** 78 ** + _BV( 6 ), // PE 6 ** 79 ** + _BV( 7 ), // PE 7 ** 80 ** + _BV( 4 ), // PD 4 ** 81 ** + _BV( 5 ), // PD 5 ** 82 ** + _BV( 6 ), // PD 6 ** 83 ** + _BV( 2 ), // PH 2 ** 84 ** + _BV( 7 ), // PH 7 ** 85 ** }; #define digitalPinToBitMask_plus_70(P) ( pgm_read_byte( digital_pin_to_bit_mask_PGM_plus_70 + (P) ) ) @@ -234,86 +234,86 @@ const uint8_t PROGMEM digital_pin_to_bit_mask_PGM_plus_70[] = { const uint8_t PROGMEM digital_pin_to_timer_PGM_plus_70[] = { // TIMERS // ------------------------ - NOT_ON_TIMER , // PE 0 ** 0 ** USART0_RX - NOT_ON_TIMER , // PE 1 ** 1 ** USART0_TX - TIMER3B , // PE 4 ** 2 ** PWM2 - TIMER3C , // PE 5 ** 3 ** PWM3 - TIMER0B , // PG 5 ** 4 ** PWM4 - TIMER3A , // PE 3 ** 5 ** PWM5 - TIMER4A , // PH 3 ** 6 ** PWM6 - TIMER4B , // PH 4 ** 7 ** PWM7 - TIMER4C , // PH 5 ** 8 ** PWM8 - TIMER2B , // PH 6 ** 9 ** PWM9 - TIMER2A , // PB 4 ** 10 ** PWM10 - TIMER1A , // PB 5 ** 11 ** PWM11 - TIMER1B , // PB 6 ** 12 ** PWM12 - TIMER0A , // PB 7 ** 13 ** PWM13 - NOT_ON_TIMER , // PJ 1 ** 14 ** USART3_TX - NOT_ON_TIMER , // PJ 0 ** 15 ** USART3_RX - NOT_ON_TIMER , // PH 1 ** 16 ** USART2_TX - NOT_ON_TIMER , // PH 0 ** 17 ** USART2_RX - NOT_ON_TIMER , // PD 3 ** 18 ** USART1_TX - NOT_ON_TIMER , // PD 2 ** 19 ** USART1_RX - NOT_ON_TIMER , // PD 1 ** 20 ** I2C_SDA - NOT_ON_TIMER , // PD 0 ** 21 ** I2C_SCL - NOT_ON_TIMER , // PA 0 ** 22 ** D22 - NOT_ON_TIMER , // PA 1 ** 23 ** D23 - NOT_ON_TIMER , // PA 2 ** 24 ** D24 - NOT_ON_TIMER , // PA 3 ** 25 ** D25 - NOT_ON_TIMER , // PA 4 ** 26 ** D26 - NOT_ON_TIMER , // PA 5 ** 27 ** D27 - NOT_ON_TIMER , // PA 6 ** 28 ** D28 - NOT_ON_TIMER , // PA 7 ** 29 ** D29 - NOT_ON_TIMER , // PC 7 ** 30 ** D30 - NOT_ON_TIMER , // PC 6 ** 31 ** D31 - NOT_ON_TIMER , // PC 5 ** 32 ** D32 - NOT_ON_TIMER , // PC 4 ** 33 ** D33 - NOT_ON_TIMER , // PC 3 ** 34 ** D34 - NOT_ON_TIMER , // PC 2 ** 35 ** D35 - NOT_ON_TIMER , // PC 1 ** 36 ** D36 - NOT_ON_TIMER , // PC 0 ** 37 ** D37 - NOT_ON_TIMER , // PD 7 ** 38 ** D38 - NOT_ON_TIMER , // PG 2 ** 39 ** D39 - NOT_ON_TIMER , // PG 1 ** 40 ** D40 - NOT_ON_TIMER , // PG 0 ** 41 ** D41 - NOT_ON_TIMER , // PL 7 ** 42 ** D42 - NOT_ON_TIMER , // PL 6 ** 43 ** D43 - TIMER5C , // PL 5 ** 44 ** D44 - TIMER5B , // PL 4 ** 45 ** D45 - TIMER5A , // PL 3 ** 46 ** D46 - NOT_ON_TIMER , // PL 2 ** 47 ** D47 - NOT_ON_TIMER , // PL 1 ** 48 ** D48 - NOT_ON_TIMER , // PL 0 ** 49 ** D49 - NOT_ON_TIMER , // PB 3 ** 50 ** SPI_MISO - NOT_ON_TIMER , // PB 2 ** 51 ** SPI_MOSI - NOT_ON_TIMER , // PB 1 ** 52 ** SPI_SCK - NOT_ON_TIMER , // PB 0 ** 53 ** SPI_SS - NOT_ON_TIMER , // PF 0 ** 54 ** A0 - NOT_ON_TIMER , // PF 1 ** 55 ** A1 - NOT_ON_TIMER , // PF 2 ** 56 ** A2 - NOT_ON_TIMER , // PF 3 ** 57 ** A3 - NOT_ON_TIMER , // PF 4 ** 58 ** A4 - NOT_ON_TIMER , // PF 5 ** 59 ** A5 - NOT_ON_TIMER , // PF 6 ** 60 ** A6 - NOT_ON_TIMER , // PF 7 ** 61 ** A7 - NOT_ON_TIMER , // PK 0 ** 62 ** A8 - NOT_ON_TIMER , // PK 1 ** 63 ** A9 - NOT_ON_TIMER , // PK 2 ** 64 ** A10 - NOT_ON_TIMER , // PK 3 ** 65 ** A11 - NOT_ON_TIMER , // PK 4 ** 66 ** A12 - NOT_ON_TIMER , // PK 5 ** 67 ** A13 - NOT_ON_TIMER , // PK 6 ** 68 ** A14 - NOT_ON_TIMER , // PK 7 ** 69 ** A15 - NOT_ON_TIMER , // PG 4 ** 70 ** - NOT_ON_TIMER , // PG 3 ** 71 ** - NOT_ON_TIMER , // PJ 2 ** 72 ** - NOT_ON_TIMER , // PJ 3 ** 73 ** - NOT_ON_TIMER , // PJ 7 ** 74 ** - NOT_ON_TIMER , // PJ 4 ** 75 ** - NOT_ON_TIMER , // PJ 5 ** 76 ** - NOT_ON_TIMER , // PJ 6 ** 77 ** - NOT_ON_TIMER , // PE 2 ** 78 ** - NOT_ON_TIMER , // PE 6 ** 79 ** + NOT_ON_TIMER, // PE 0 ** 0 ** USART0_RX + NOT_ON_TIMER, // PE 1 ** 1 ** USART0_TX + TIMER3B, // PE 4 ** 2 ** PWM2 + TIMER3C, // PE 5 ** 3 ** PWM3 + TIMER0B, // PG 5 ** 4 ** PWM4 + TIMER3A, // PE 3 ** 5 ** PWM5 + TIMER4A, // PH 3 ** 6 ** PWM6 + TIMER4B, // PH 4 ** 7 ** PWM7 + TIMER4C, // PH 5 ** 8 ** PWM8 + TIMER2B, // PH 6 ** 9 ** PWM9 + TIMER2A, // PB 4 ** 10 ** PWM10 + TIMER1A, // PB 5 ** 11 ** PWM11 + TIMER1B, // PB 6 ** 12 ** PWM12 + TIMER0A, // PB 7 ** 13 ** PWM13 + NOT_ON_TIMER, // PJ 1 ** 14 ** USART3_TX + NOT_ON_TIMER, // PJ 0 ** 15 ** USART3_RX + NOT_ON_TIMER, // PH 1 ** 16 ** USART2_TX + NOT_ON_TIMER, // PH 0 ** 17 ** USART2_RX + NOT_ON_TIMER, // PD 3 ** 18 ** USART1_TX + NOT_ON_TIMER, // PD 2 ** 19 ** USART1_RX + NOT_ON_TIMER, // PD 1 ** 20 ** I2C_SDA + NOT_ON_TIMER, // PD 0 ** 21 ** I2C_SCL + NOT_ON_TIMER, // PA 0 ** 22 ** D22 + NOT_ON_TIMER, // PA 1 ** 23 ** D23 + NOT_ON_TIMER, // PA 2 ** 24 ** D24 + NOT_ON_TIMER, // PA 3 ** 25 ** D25 + NOT_ON_TIMER, // PA 4 ** 26 ** D26 + NOT_ON_TIMER, // PA 5 ** 27 ** D27 + NOT_ON_TIMER, // PA 6 ** 28 ** D28 + NOT_ON_TIMER, // PA 7 ** 29 ** D29 + NOT_ON_TIMER, // PC 7 ** 30 ** D30 + NOT_ON_TIMER, // PC 6 ** 31 ** D31 + NOT_ON_TIMER, // PC 5 ** 32 ** D32 + NOT_ON_TIMER, // PC 4 ** 33 ** D33 + NOT_ON_TIMER, // PC 3 ** 34 ** D34 + NOT_ON_TIMER, // PC 2 ** 35 ** D35 + NOT_ON_TIMER, // PC 1 ** 36 ** D36 + NOT_ON_TIMER, // PC 0 ** 37 ** D37 + NOT_ON_TIMER, // PD 7 ** 38 ** D38 + NOT_ON_TIMER, // PG 2 ** 39 ** D39 + NOT_ON_TIMER, // PG 1 ** 40 ** D40 + NOT_ON_TIMER, // PG 0 ** 41 ** D41 + NOT_ON_TIMER, // PL 7 ** 42 ** D42 + NOT_ON_TIMER, // PL 6 ** 43 ** D43 + TIMER5C, // PL 5 ** 44 ** D44 + TIMER5B, // PL 4 ** 45 ** D45 + TIMER5A, // PL 3 ** 46 ** D46 + NOT_ON_TIMER, // PL 2 ** 47 ** D47 + NOT_ON_TIMER, // PL 1 ** 48 ** D48 + NOT_ON_TIMER, // PL 0 ** 49 ** D49 + NOT_ON_TIMER, // PB 3 ** 50 ** SPI_MISO + NOT_ON_TIMER, // PB 2 ** 51 ** SPI_MOSI + NOT_ON_TIMER, // PB 1 ** 52 ** SPI_SCK + NOT_ON_TIMER, // PB 0 ** 53 ** SPI_SS + NOT_ON_TIMER, // PF 0 ** 54 ** A0 + NOT_ON_TIMER, // PF 1 ** 55 ** A1 + NOT_ON_TIMER, // PF 2 ** 56 ** A2 + NOT_ON_TIMER, // PF 3 ** 57 ** A3 + NOT_ON_TIMER, // PF 4 ** 58 ** A4 + NOT_ON_TIMER, // PF 5 ** 59 ** A5 + NOT_ON_TIMER, // PF 6 ** 60 ** A6 + NOT_ON_TIMER, // PF 7 ** 61 ** A7 + NOT_ON_TIMER, // PK 0 ** 62 ** A8 + NOT_ON_TIMER, // PK 1 ** 63 ** A9 + NOT_ON_TIMER, // PK 2 ** 64 ** A10 + NOT_ON_TIMER, // PK 3 ** 65 ** A11 + NOT_ON_TIMER, // PK 4 ** 66 ** A12 + NOT_ON_TIMER, // PK 5 ** 67 ** A13 + NOT_ON_TIMER, // PK 6 ** 68 ** A14 + NOT_ON_TIMER, // PK 7 ** 69 ** A15 + NOT_ON_TIMER, // PG 4 ** 70 ** + NOT_ON_TIMER, // PG 3 ** 71 ** + NOT_ON_TIMER, // PJ 2 ** 72 ** + NOT_ON_TIMER, // PJ 3 ** 73 ** + NOT_ON_TIMER, // PJ 7 ** 74 ** + NOT_ON_TIMER, // PJ 4 ** 75 ** + NOT_ON_TIMER, // PJ 5 ** 76 ** + NOT_ON_TIMER, // PJ 6 ** 77 ** + NOT_ON_TIMER, // PE 2 ** 78 ** + NOT_ON_TIMER, // PE 6 ** 79 ** }; #define digitalPinToTimer_plus_70(P) ( pgm_read_byte( digital_pin_to_timer_PGM_plus_70 + (P) ) ) diff --git a/Marlin/src/HAL/AVR/registers.h b/Marlin/src/HAL/AVR/registers.h index 64c0955c3e8e..0eac4888cd3c 100644 --- a/Marlin/src/HAL/AVR/registers.h +++ b/Marlin/src/HAL/AVR/registers.h @@ -93,15 +93,15 @@ namespace AVRHelpers { typedef T type; }; template - struct voltype { + struct voltype { typedef uint8_t type; }; template - struct voltype { + struct voltype { typedef uint16_t type; }; template - struct voltype { + struct voltype { typedef uint32_t type; }; @@ -2007,7 +2007,7 @@ inline void _ATmega_resetperipherals() { #if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM03__) || defined(__AVR_TRM05__) _EEAR._EEAR = 0; - dwrite(_EEDR, (uint8_t)0u); + dwrite(_EEDR, (uint8_t)0U); #endif #if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM03__) || defined(__AVR_TRM04__) || defined(__AVR_TRM05__) diff --git a/Marlin/src/HAL/AVR/spi_pins.h b/Marlin/src/HAL/AVR/spi_pins.h index 831972938a7b..934b6e3b1466 100644 --- a/Marlin/src/HAL/AVR/spi_pins.h +++ b/Marlin/src/HAL/AVR/spi_pins.h @@ -23,43 +23,41 @@ /** * Define SPI Pins: SCK, MISO, MOSI, SS + * Platform pins have parentheses, e.g., "(53)", so we cannot use them. */ #if defined(__AVR_ATmega168__) || defined(__AVR_ATmega328__) || defined(__AVR_ATmega328P__) - #define AVR_SCK_PIN 13 - #define AVR_MISO_PIN 12 - #define AVR_MOSI_PIN 11 - #define AVR_SS_PIN 10 + #define _PIN_SPI_SCK 13 + #define _PIN_SPI_MISO 12 + #define _PIN_SPI_MOSI 11 + #define _PIN_SPI_SS 10 #elif defined(__AVR_ATmega644__) || defined(__AVR_ATmega644P__) || defined(__AVR_ATmega644PA__) || defined(__AVR_ATmega1284P__) - #define AVR_SCK_PIN 7 - #define AVR_MISO_PIN 6 - #define AVR_MOSI_PIN 5 - #define AVR_SS_PIN 4 + #define _PIN_SPI_SCK 7 + #define _PIN_SPI_MISO 6 + #define _PIN_SPI_MOSI 5 + #define _PIN_SPI_SS 4 #elif defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) - #define AVR_SCK_PIN 52 - #define AVR_MISO_PIN 50 - #define AVR_MOSI_PIN 51 - #define AVR_SS_PIN 53 + #define _PIN_SPI_SCK 52 + #define _PIN_SPI_MISO 50 + #define _PIN_SPI_MOSI 51 + #define _PIN_SPI_SS 53 #elif defined(__AVR_AT90USB1287__) || defined(__AVR_AT90USB1286__) || defined(__AVR_AT90USB646__) || defined(__AVR_AT90USB647__) - #define AVR_SCK_PIN 21 - #define AVR_MISO_PIN 23 - #define AVR_MOSI_PIN 22 - #define AVR_SS_PIN 20 + #define _PIN_SPI_SCK 21 + #define _PIN_SPI_MISO 23 + #define _PIN_SPI_MOSI 22 + #define _PIN_SPI_SS 20 #elif defined(__AVR_ATmega1281__) || defined(__AVR_ATmega2561__) - #define AVR_SCK_PIN 10 - #define AVR_MISO_PIN 12 - #define AVR_MOSI_PIN 11 - #define AVR_SS_PIN 16 + #define _PIN_SPI_SCK 10 + #define _PIN_SPI_MISO 12 + #define _PIN_SPI_MOSI 11 + #define _PIN_SPI_SS 16 #endif #ifndef SD_SCK_PIN - #define SD_SCK_PIN AVR_SCK_PIN + #define SD_SCK_PIN _PIN_SPI_SCK #endif #ifndef SD_MISO_PIN - #define SD_MISO_PIN AVR_MISO_PIN + #define SD_MISO_PIN _PIN_SPI_MISO #endif #ifndef SD_MOSI_PIN - #define SD_MOSI_PIN AVR_MOSI_PIN -#endif -#ifndef SD_SS_PIN - #define SD_SS_PIN AVR_SS_PIN + #define SD_MOSI_PIN _PIN_SPI_MOSI #endif diff --git a/Marlin/src/HAL/AVR/timers.h b/Marlin/src/HAL/AVR/timers.h index 94b17f310270..1800da0985bc 100644 --- a/Marlin/src/HAL/AVR/timers.h +++ b/Marlin/src/HAL/AVR/timers.h @@ -28,7 +28,7 @@ // ------------------------ typedef uint16_t hal_timer_t; -#define HAL_TIMER_TYPE_MAX 0xFFFF +#define HAL_TIMER_TYPE_MAX hal_timer_t(UINT16_MAX) // ------------------------ // Defines @@ -46,15 +46,14 @@ typedef uint16_t hal_timer_t; #define MF_TIMER_TEMP 0 #endif -#define TEMP_TIMER_FREQUENCY (((F_CPU) + 0x2000) / 0x4000) +#define TEMP_TIMER_FREQUENCY (((F_CPU) + 0x2000) / 0x4000) -#define STEPPER_TIMER_RATE HAL_TIMER_RATE -#define STEPPER_TIMER_PRESCALE 8 -#define STEPPER_TIMER_TICKS_PER_US ((STEPPER_TIMER_RATE) / 1000000) +#define STEPPER_TIMER_RATE HAL_TIMER_RATE +#define STEPPER_TIMER_PRESCALE 8 +#define STEPPER_TIMER_TICKS_PER_US ((STEPPER_TIMER_RATE) / 1000000UL) // (MHz) Stepper Timer ticks per µs -#define PULSE_TIMER_RATE STEPPER_TIMER_RATE -#define PULSE_TIMER_PRESCALE STEPPER_TIMER_PRESCALE -#define PULSE_TIMER_TICKS_PER_US STEPPER_TIMER_TICKS_PER_US +#define PULSE_TIMER_RATE STEPPER_TIMER_RATE // (Hz) Frequency of Pulse Timer +#define PULSE_TIMER_PRESCALE STEPPER_TIMER_PRESCALE #define ENABLE_STEPPER_DRIVER_INTERRUPT() SBI(TIMSK1, OCIE1A) #define DISABLE_STEPPER_DRIVER_INTERRUPT() CBI(TIMSK1, OCIE1A) @@ -111,8 +110,8 @@ FORCE_INLINE void HAL_timer_start(const uint8_t timer_num, const uint32_t) { * (otherwise, characters will be lost due to UART overflow). * Then: Stepper, Endstops, Temperature, and -finally- all others. */ -#define HAL_timer_isr_prologue(T) NOOP -#define HAL_timer_isr_epilogue(T) NOOP +inline void HAL_timer_isr_prologue(const uint8_t) {} +inline void HAL_timer_isr_epilogue(const uint8_t) {} #ifndef HAL_STEP_TIMER_ISR diff --git a/Marlin/src/HAL/AVR/u8g_com_HAL_AVR_sw_spi.cpp b/Marlin/src/HAL/AVR/u8g/u8g_com_HAL_AVR_sw_spi.cpp similarity index 92% rename from Marlin/src/HAL/AVR/u8g_com_HAL_AVR_sw_spi.cpp rename to Marlin/src/HAL/AVR/u8g/u8g_com_HAL_AVR_sw_spi.cpp index 131174b06c41..f4fa1eb42873 100644 --- a/Marlin/src/HAL/AVR/u8g_com_HAL_AVR_sw_spi.cpp +++ b/Marlin/src/HAL/AVR/u8g/u8g_com_HAL_AVR_sw_spi.cpp @@ -55,12 +55,12 @@ #if defined(ARDUINO) && !defined(ARDUINO_ARCH_STM32) && !defined(ARDUINO_ARCH_SAM) -#include "../../inc/MarlinConfigPre.h" +#include "../../../inc/MarlinConfigPre.h" #if HAS_MARLINUI_U8GLIB -#include "../shared/Marduino.h" -#include "../shared/Delay.h" +#include "../../shared/Marduino.h" +#include "../../shared/Delay.h" #include @@ -120,7 +120,7 @@ void u8g_spiSend_sw_AVR_mode_3(uint8_t val) { U8G_ATOMIC_END(); } -#if ENABLED(FYSETC_MINI_12864) +#if U8G_SPI_USE_MODE_3 #define SPISEND_SW_AVR u8g_spiSend_sw_AVR_mode_3 #else #define SPISEND_SW_AVR u8g_spiSend_sw_AVR_mode_0 @@ -143,9 +143,9 @@ uint8_t u8g_com_HAL_AVR_sw_sp_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void break; case U8G_COM_MSG_CHIP_SELECT: - #if ENABLED(FYSETC_MINI_12864) // LCD SPI is running mode 3 while SD card is running mode 0 - if (arg_val) { // SCK idle state needs to be set to the proper idle state before - // the next chip select goes active + #if U8G_SPI_USE_MODE_3 // LCD SPI is running mode 3 while SD card is running mode 0 + if (arg_val) { // SCK idle state needs to be set to the proper idle state before + // the next chip select goes active u8g_com_arduino_digital_write(u8g, U8G_PI_SCK, 1); // Set SCK to mode 3 idle state before CS goes active u8g_com_arduino_digital_write(u8g, U8G_PI_CS, LOW); } diff --git a/Marlin/src/HAL/DUE/AGENTS.md b/Marlin/src/HAL/DUE/AGENTS.md new file mode 100644 index 000000000000..b91014c364b2 --- /dev/null +++ b/Marlin/src/HAL/DUE/AGENTS.md @@ -0,0 +1,56 @@ +# AGENTS.md — DUE HAL + +Working notes and conventions for the **DUE HAL** folder (`Marlin/src/HAL/DUE/`). Covers the Arduino Due and compatible boards built on the Atmel/Microchip **SAM3X8E** (ARM Cortex-M3). + +## Build & Test Loop + +Validate with `mftest` (it regenerates `Marlin/Configuration.h` per target, which raw `pio run -e` does not do reliably): + +```bash +cd "$(git rev-parse --show-toplevel)" +buildroot/bin/mftest -t DUE -n1 -y # RAMPS4DUE_EFB, hardware UART (Serial) +buildroot/bin/mftest -t DUE_USB -n1 -y # RAMPS4DUE over native USB CDC +buildroot/bin/mftest -t DUE_archim -n1 -y # UltiMachine Archim 1/2 (SAM3X8E) +``` + +All three DUE-family envs live in `ini/due.ini` (`env:DUE`, `env:DUE_USB`, `env:DUE_archim`) and map to the PlatformIO `due`, `dueUSB`, and `marlin_archim` board JSONs. `DUE` (config-01 is the heaviest, with ABL/S-Curve/ExtUI/SD) is the broadest smoke test; Archim uses the upstream Arduino SAM variant shipped under `buildroot/share/PlatformIO/variants/` (see `board_build.variants_dir`). + +> Verified in this environment (toolchain `toolchain-gccarmnoneeabi` + `framework-arduino-sam` installed): `mftest -t DUE_archim` **builds green** (~8.7 s, ~103 KB / 524 KB flash). `mftest -t DUE` **fails** with a pre-existing `SanityCheck.h` Serial-1 pin-conflict error (`Serial Port 1 pin D18 and/or D19 conflicts…`) in the RAMPS4DUE_EFB test config — not caused by this doc; investigate before relying on the plain `DUE` target. + +> Do NOT use `rm -rf .pio/build/...` to force a rebuild — the cross-profile write guard blocks it and it is unsafe. Use `pio run -e -t clean` or just let `mftest` rebuild. + +## Framework / Platform Integration (upstream, NOT vendored) + +Unlike AT32, the DUE build uses the **upstream Arduino SAM core** (`framework-arduino-sam`, pulled by the `atmelsam` PlatformIO platform and `toolchain-gccarmnoneeabi`). It is NOT vendored under `buildroot/share/PlatformIO/`, so there is no installed-package ↔ repo mirror step. Marlin-specific glue lives entirely in this HAL folder, plus: + +- `Marlin/src/HAL/DUE/upload_extra_script.py` — Archim-specific upload hook (`env:DUE_archim`). +- `buildroot/share/PlatformIO/variants/` — Archim board variant. + +Do not "fix" the SAM core in `~/.platformio/packages/framework-arduino-sam/`; any Marlin change belongs in `Marlin/src/HAL/DUE/` (or `src/pins/...` for boards). + +### Gotchas that are not obvious from the source + +1. **Native USB is a real CDC stack, not a UART emulation.** When `SERIAL_PORT == -1` (the `DUE_USB` env uses it), Marlin routes serial over the SAM3X native USB (`MarlinSerialUSB`). The USB stack is vendored from **Atmel ASF (2015)** in `usb/` and MUST be initialized — `HAL.cpp` calls `usb_task_init()` in `init()` and `usb_task_idle()` in `idletask()`. If `idletask()` doesn't run, USB never enumerates. See `usb/README.md` for the ASF provenance and upgrade caveats. + +2. **USB interrupt race fix lives in `usb/uotghs_device_due.c`.** A fix was applied there to resolve a race that left interrupts asserted when freezing the peripheral clock — causing hangs and watchdog resets from an interrupt storm. Preserve that fix if you touch the USB driver. + +3. **EEPROM backend is per-board, not auto-selected.** Marlin does NOT auto-pick an EEPROM backend for DUE. `eeprom/eeprom_flash.cpp` implements `FLASH_EEPROM_EMULATION` (diff-based, wear-leveled over flash page groups — see the header comment) and `eeprom/eeprom_wired.cpp` implements I²C/SPI (`I2C_EEPROM` / `SPI_EEPROM`). Each DUE board's pins file must `#define` the backend it wants; nothing selects one by default. + +4. **Watchdog is configured at boot, overriding the Arduino runtime.** `HAL.cpp`'s `watchdogSetup()` runs before `setup()` and either enables the WDT (4 s, or 8 s with `WATCHDOG_DURATION_8S`) or fully disables it. On SAM3X, late watchdog init after hardware reset is unreliable, so it must happen this early. `due.ini` passes `-DWATCHDOG_PIO_RESET` so a WDT fault also resets the PIO controller. + +5. **Clock is 84 MHz from a 12 MHz crystal (PLL).** `F_CPU` is 84 MHz; the stepper and temperature timers (`timers.h`, prescaler 2, `TC2`/`TC4`/`TC6` handlers) are derived from it. Don't assume 16 MHz AVR-style timing — PWM frequencies and `analogWrite` scaling differ. + +6. **`pin_t` is `int8_t` and ADC is 10-bit / 3.3 V.** `HAL.h` sets `HAL_ADC_VREF_MV 3300`, `HAL_ADC_RESOLUTION 10`, and `analogInputToDigitalPin(p) = p<12 ? p+54 : -1`. DUE analog pins are mapped above the digital range; `M43`/`M42` pin math depends on this. + +## Conventions + +- All HAL source is guarded by `#ifdef ARDUINO_ARCH_SAM` (defined by the upstream SAM core / `atmelsam` platform — no `ini` flag needed for it). +- Keep HAL changes in `Marlin/src/HAL/DUE/` and board pin changes in `Marlin/src/pins/ramps4due/`, `Marlin/src/pins/archim/`, etc. +- The `usb/` subtree is vendored ASF — treat as fragile; bring Marlin-local fixes forward when comparing against newer ASF. +- Don't pin or vendor the toolchain casually: `due.ini` leaves a commented `platform_packages = toolchain-gccarmnoneeabi@1.120301.0` because the default resolves to an older GCC 7.2.1. + +## Related + +- `usb/README.md` in this folder — ASF source provenance and USB upgrade notes. +- Wiki: [[hal-due]], [[marlin-src-hal]]. +- `Marlin/src/HAL/shared/` — shared HAL APIs (`eeprom_api`, SPI helpers) the DUE HAL reuses. diff --git a/Marlin/src/HAL/DUE/HAL.cpp b/Marlin/src/HAL/DUE/HAL.cpp index 763091cb009a..19a174259a3a 100644 --- a/Marlin/src/HAL/DUE/HAL.cpp +++ b/Marlin/src/HAL/DUE/HAL.cpp @@ -27,7 +27,6 @@ #ifdef ARDUINO_ARCH_SAM #include "../../inc/MarlinConfig.h" -#include "../../MarlinCore.h" #include #include "usb/usb_task.h" @@ -48,7 +47,7 @@ uint16_t MarlinHAL::adc_result; void MarlinHAL::init() { #if HAS_MEDIA - OUT_WRITE(SDSS, HIGH); // Try to set SDSS inactive before any other SPI users start up + OUT_WRITE(SD_SS_PIN, HIGH); // Try to set SDSS inactive before any other SPI users start up #endif usb_task_init(); // Initialize the USB stack TERN_(POSTMORTEM_DEBUGGING, install_min_serial()); // Install the min serial handler @@ -102,6 +101,10 @@ void watchdogSetup() { #if ENABLED(USE_WATCHDOG) + #ifndef WATCHDOG_PIO_RESET + #define WATCHDOG_PIO_RESET + #endif + // 4 seconds timeout uint32_t timeout = TERN(WATCHDOG_DURATION_8S, 8000, 4000); @@ -115,15 +118,16 @@ void watchdogSetup() { timeout = 0xFFF; // We want to enable the watchdog with the specified timeout - uint32_t value = - WDT_MR_WDV(timeout) | // With the specified timeout - WDT_MR_WDD(timeout) | // and no invalid write window - #if !(SAMV70 || SAMV71 || SAME70 || SAMS70) - WDT_MR_WDRPROC | // WDT fault resets processor only - We want - // to keep PIO controller state - #endif - WDT_MR_WDDBGHLT | // WDT stops in debug state. - WDT_MR_WDIDLEHLT; // WDT stops in idle state. + uint32_t value = (0 + | WDT_MR_WDV(timeout) // With the specified timeout + | WDT_MR_WDD(timeout) // and no invalid write window + #if NONE(WATCHDOG_PIO_RESET, SAMV70, SAMV71, SAME70, SAMS70) + | WDT_MR_WDRPROC // WDT fault resets processor only with this flag. + // Omit to also reset the PIO controller. + #endif + | WDT_MR_WDDBGHLT // WDT stops in debug state. + | WDT_MR_WDIDLEHLT // WDT stops in idle state. + ); #if ENABLED(WATCHDOG_RESET_MANUAL) // We enable the watchdog timer, but only for the interrupt. diff --git a/Marlin/src/HAL/DUE/HAL.h b/Marlin/src/HAL/DUE/HAL.h index 49a8be3fe7bb..f83668ca9db9 100644 --- a/Marlin/src/HAL/DUE/HAL.h +++ b/Marlin/src/HAL/DUE/HAL.h @@ -35,67 +35,9 @@ #include -#include "../../core/serial_hook.h" - -// ------------------------ -// Serial ports -// ------------------------ - -typedef ForwardSerial1Class< decltype(Serial) > DefaultSerial1; -typedef ForwardSerial1Class< decltype(Serial1) > DefaultSerial2; -typedef ForwardSerial1Class< decltype(Serial2) > DefaultSerial3; -typedef ForwardSerial1Class< decltype(Serial3) > DefaultSerial4; -extern DefaultSerial1 MSerial0; -extern DefaultSerial2 MSerial1; -extern DefaultSerial3 MSerial2; -extern DefaultSerial4 MSerial3; - -#define _MSERIAL(X) MSerial##X -#define MSERIAL(X) _MSERIAL(X) - -#if SERIAL_PORT == -1 || ENABLED(EMERGENCY_PARSER) - #define MYSERIAL1 customizedSerial1 -#elif WITHIN(SERIAL_PORT, 0, 3) - #define MYSERIAL1 MSERIAL(SERIAL_PORT) -#else - #error "The required SERIAL_PORT must be from 0 to 3, or -1 for USB Serial." -#endif - -#ifdef SERIAL_PORT_2 - #if SERIAL_PORT_2 == -1 || ENABLED(EMERGENCY_PARSER) - #define MYSERIAL2 customizedSerial2 - #elif WITHIN(SERIAL_PORT_2, 0, 3) - #define MYSERIAL2 MSERIAL(SERIAL_PORT_2) - #else - #error "SERIAL_PORT_2 must be from 0 to 3, or -1 for USB Serial." - #endif -#endif - -#ifdef SERIAL_PORT_3 - #if SERIAL_PORT_3 == -1 || ENABLED(EMERGENCY_PARSER) - #define MYSERIAL3 customizedSerial3 - #elif WITHIN(SERIAL_PORT_3, 0, 3) - #define MYSERIAL3 MSERIAL(SERIAL_PORT_3) - #else - #error "SERIAL_PORT_3 must be from 0 to 3, or -1 for USB Serial." - #endif -#endif - -#ifdef MMU2_SERIAL_PORT - #if WITHIN(MMU2_SERIAL_PORT, 0, 3) - #define MMU2_SERIAL MSERIAL(MMU2_SERIAL_PORT) - #else - #error "MMU2_SERIAL_PORT must be from 0 to 3." - #endif -#endif - -#ifdef LCD_SERIAL_PORT - #if WITHIN(LCD_SERIAL_PORT, 0, 3) - #define LCD_SERIAL MSERIAL(LCD_SERIAL_PORT) - #else - #error "LCD_SERIAL_PORT must be from 0 to 3." - #endif -#endif +// +// Serial Ports +// #include "MarlinSerial.h" #include "MarlinSerialUSB.h" @@ -127,7 +69,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 // @@ -190,7 +132,7 @@ class MarlinHAL { static void delay_ms(const int ms) { delay(ms); } - // Tasks, called from idle() + // Tasks, called from marlin.idle() static void idletask(); // Reset diff --git a/Marlin/src/HAL/DUE/HAL_SPI.cpp b/Marlin/src/HAL/DUE/HAL_SPI.cpp index c2fabb0d49b1..e44549357ea7 100644 --- a/Marlin/src/HAL/DUE/HAL_SPI.cpp +++ b/Marlin/src/HAL/DUE/HAL_SPI.cpp @@ -208,8 +208,8 @@ A("str %[sck_mask],[%[sck_port],#0x4]") /* CODR */ A("bfi %[bin],%[work],#0,#1") /* Store read bit as the bit 0 */ - : [bin]"+r"(bin), - [work]"+r"(work) + : [bin]"+r"( bin ), + [work]"+r"( work ) : [bitband_miso_port]"r"( BITBAND_MISO_PORT ), [sck_mask]"r"( SCK_MASK ), [sck_port]"r"( SCK_PORT_PLUS30 ) @@ -350,7 +350,7 @@ static void spiRxBlock0(uint8_t *ptr, uint32_t todo) { uint32_t bin = 0; uint32_t work = 0; - uint32_t BITBAND_MISO_PORT = BITBAND_ADDRESS( ((uint32_t)PORT(SD_MISO_PIN))+0x3C, PIN_SHIFT(SD_MISO_PIN)); /* PDSR of port in bitband area */ + uint32_t BITBAND_MISO_PORT = BITBAND_ADDRESS(((uint32_t)PORT(SD_MISO_PIN))+0x3C, PIN_SHIFT(SD_MISO_PIN)); /* PDSR of port in bitband area */ uint32_t SCK_PORT_PLUS30 = ((uint32_t) PORT(SD_SCK_PIN)) + 0x30; /* SODR of port */ uint32_t SCK_MASK = PIN_MASK(SD_SCK_PIN); @@ -412,10 +412,10 @@ A("strb.w %[bin], [%[ptr]], #1") /* Store read value into buffer, increment buffer pointer */ A("bne.n loop%=") /* Repeat until done */ - : [ptr]"+r"(ptr), - [todo]"+r"(todo), - [bin]"+r"(bin), - [work]"+r"(work) + : [ptr]"+r"( ptr ), + [todo]"+r"( todo ), + [bin]"+r"( bin ), + [work]"+r"( work ) : [bitband_miso_port]"r"( BITBAND_MISO_PORT ), [sck_mask]"r"( SCK_MASK ), [sck_port]"r"( SCK_PORT_PLUS30 ) @@ -600,9 +600,8 @@ OUT_WRITE(SPI_EEPROM1_CS_PIN, HIGH); OUT_WRITE(SPI_EEPROM2_CS_PIN, HIGH); OUT_WRITE(SPI_FLASH_CS_PIN, HIGH); - WRITE(SD_SS_PIN, HIGH); - - OUT_WRITE(SDSS, LOW); + OUT_WRITE(SD_SS_PIN, HIGH); + WRITE(SD_SS_PIN, LOW); PIO_Configure( g_APinDescription[SPI_PIN].pPort, @@ -767,7 +766,7 @@ // Disable PIO on A26 and A27 REG_PIOA_PDR = 0x0C000000; - OUT_WRITE(SDSS, HIGH); + OUT_WRITE(SD_SS_PIN, HIGH); // Reset SPI0 (from sam lib) SPI0->SPI_CR = SPI_CR_SPIDIS; diff --git a/Marlin/src/HAL/DUE/MarlinSerial.cpp b/Marlin/src/HAL/DUE/MarlinSerial.cpp index 90efe55fc23d..2e80b4c8d1d6 100644 --- a/Marlin/src/HAL/DUE/MarlinSerial.cpp +++ b/Marlin/src/HAL/DUE/MarlinSerial.cpp @@ -31,7 +31,6 @@ #include "MarlinSerial.h" #include "InterruptVectors.h" -#include "../../MarlinCore.h" template typename MarlinSerial::ring_buffer_r MarlinSerial::rx_buffer = { 0, 0, { 0 } }; template typename MarlinSerial::ring_buffer_t MarlinSerial::tx_buffer = { 0 }; diff --git a/Marlin/src/HAL/DUE/MarlinSerial.h b/Marlin/src/HAL/DUE/MarlinSerial.h index b80ae2182312..cee0857d2b11 100644 --- a/Marlin/src/HAL/DUE/MarlinSerial.h +++ b/Marlin/src/HAL/DUE/MarlinSerial.h @@ -33,6 +33,21 @@ #include "../../core/types.h" #include "../../core/serial_hook.h" +typedef ForwardSerial1Class< decltype(Serial) > DefaultSerial1; +typedef ForwardSerial1Class< decltype(Serial1) > DefaultSerial2; +typedef ForwardSerial1Class< decltype(Serial2) > DefaultSerial3; +typedef ForwardSerial1Class< decltype(Serial3) > DefaultSerial4; +extern DefaultSerial1 MSerial0; +extern DefaultSerial2 MSerial1; +extern DefaultSerial3 MSerial2; +extern DefaultSerial4 MSerial3; + +#define SERIAL_INDEX_MIN 0 +#define SERIAL_INDEX_MAX 3 +#define EP_SERIAL_PORT(N) customizedSerial##N +#define USB_SERIAL_PORT(N) customizedSerial##N +#include "../shared/serial_ports.h" + // Define constants and variables for buffering incoming serial data. We're // using a ring buffer (I think), in which rx_buffer_head is the index of the // location to which to write the next incoming character and rx_buffer_tail diff --git a/Marlin/src/HAL/DUE/Servo.cpp b/Marlin/src/HAL/DUE/Servo.cpp index 2dab88238dd6..2f72d66b9bfc 100644 --- a/Marlin/src/HAL/DUE/Servo.cpp +++ b/Marlin/src/HAL/DUE/Servo.cpp @@ -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); diff --git a/Marlin/src/HAL/DUE/Tone.cpp b/Marlin/src/HAL/DUE/Tone.cpp index 4bc8142aba27..743cae9d701a 100644 --- a/Marlin/src/HAL/DUE/Tone.cpp +++ b/Marlin/src/HAL/DUE/Tone.cpp @@ -24,7 +24,7 @@ /** * Description: Tone function for Arduino Due and compatible (SAM3X8E) - * Derived from https://forum.arduino.cc/index.php?topic=136500.msg2903012#msg2903012 + * Derived from https://forum.arduino.cc/t/arduino-due-and-tone/133302/13 */ #ifdef ARDUINO_ARCH_SAM diff --git a/Marlin/src/HAL/DUE/eeprom_flash.cpp b/Marlin/src/HAL/DUE/eeprom/eeprom_flash.cpp similarity index 99% rename from Marlin/src/HAL/DUE/eeprom_flash.cpp rename to Marlin/src/HAL/DUE/eeprom/eeprom_flash.cpp index a5c7ab836dbf..b33d15e1061a 100644 --- a/Marlin/src/HAL/DUE/eeprom_flash.cpp +++ b/Marlin/src/HAL/DUE/eeprom/eeprom_flash.cpp @@ -21,7 +21,7 @@ */ #ifdef ARDUINO_ARCH_SAM -#include "../../inc/MarlinConfig.h" +#include "../../../inc/MarlinConfig.h" #if ENABLED(FLASH_EEPROM_EMULATION) @@ -132,7 +132,7 @@ static uint8_t buffer[256] = {0}, // The RAM buffer to accumulate writes curGroup = 0xFF; // Current FLASH group #define DEBUG_OUT ENABLED(EE_EMU_DEBUG) -#include "../../core/debug_out.h" +#include "../../../core/debug_out.h" static void ee_Dump(const int page, const void *data) { @@ -291,7 +291,7 @@ static bool ee_PageWrite(uint16_t page, const void *data) { uint32_t *p1 = (uint32_t*)addrflash; uint32_t *p2 = (uint32_t*)data; int count = 0; - for (i =0; i> 2; i++) { + for (i = 0; i < PageSize >> 2; i++) { if (p1[i] != p2[i]) { uint32_t delta = p1[i] ^ p2[i]; while (delta) { @@ -953,7 +953,7 @@ static void ee_Init() { /* PersistentStore -----------------------------------------------------------*/ -#include "../shared/eeprom_api.h" +#include "../../shared/eeprom_api.h" #ifndef MARLIN_EEPROM_SIZE #define MARLIN_EEPROM_SIZE 0x1000 // 4KB diff --git a/Marlin/src/HAL/DUE/eeprom_wired.cpp b/Marlin/src/HAL/DUE/eeprom/eeprom_wired.cpp similarity index 95% rename from Marlin/src/HAL/DUE/eeprom_wired.cpp rename to Marlin/src/HAL/DUE/eeprom/eeprom_wired.cpp index 84338ccb4bf5..cf9233816c5b 100644 --- a/Marlin/src/HAL/DUE/eeprom_wired.cpp +++ b/Marlin/src/HAL/DUE/eeprom/eeprom_wired.cpp @@ -21,7 +21,7 @@ */ #ifdef ARDUINO_ARCH_SAM -#include "../../inc/MarlinConfig.h" +#include "../../../inc/MarlinConfig.h" #if USE_WIRED_EEPROM @@ -30,8 +30,8 @@ * with simple implementations supplied by Marlin. */ -#include "../shared/eeprom_if.h" -#include "../shared/eeprom_api.h" +#include "../../shared/eeprom_if.h" +#include "../../shared/eeprom_api.h" #ifndef MARLIN_EEPROM_SIZE #error "MARLIN_EEPROM_SIZE is required for I2C / SPI EEPROM." diff --git a/Marlin/src/HAL/DUE/endstop_interrupts.h b/Marlin/src/HAL/DUE/endstop_interrupts.h index 954eb625a575..798ca4f0cbf7 100644 --- a/Marlin/src/HAL/DUE/endstop_interrupts.h +++ b/Marlin/src/HAL/DUE/endstop_interrupts.h @@ -64,6 +64,7 @@ void setup_endstop_interrupts() { TERN_(USE_Z4_MAX, _ATTACH(Z4_MAX_PIN)); TERN_(USE_Z4_MIN, _ATTACH(Z4_MIN_PIN)); TERN_(USE_Z_MIN_PROBE, _ATTACH(Z_MIN_PROBE_PIN)); + TERN_(USE_CALIBRATION, _ATTACH(CALIBRATION_PIN)); TERN_(USE_I_MAX, _ATTACH(I_MAX_PIN)); TERN_(USE_I_MIN, _ATTACH(I_MIN_PIN)); TERN_(USE_J_MAX, _ATTACH(J_MAX_PIN)); diff --git a/Marlin/src/HAL/DUE/fastio/G2_PWM.cpp b/Marlin/src/HAL/DUE/fastio/G2_PWM.cpp index 800915ff692b..745e4205bc8e 100644 --- a/Marlin/src/HAL/DUE/fastio/G2_PWM.cpp +++ b/Marlin/src/HAL/DUE/fastio/G2_PWM.cpp @@ -40,11 +40,12 @@ * Some jitter in the Vref signal is OK so the interrupt priority is left at its default value. */ -#include "../../../inc/MarlinConfig.h" +#include "../../../inc/MarlinConfigPre.h" #if MB(PRINTRBOARD_G2) #include "G2_PWM.h" +#include "../../../module/stepper.h" #if PIN_EXISTS(MOTOR_CURRENT_PWM_X) #define G2_PWM_X 1 @@ -56,16 +57,12 @@ #else #define G2_PWM_Y 0 #endif -#if PIN_EXISTS(MOTOR_CURRENT_PWM_Z) +#if HAS_MOTOR_CURRENT_PWM_Z #define G2_PWM_Z 1 #else #define G2_PWM_Z 0 #endif -#if PIN_EXISTS(MOTOR_CURRENT_PWM_E) - #define G2_PWM_E 1 -#else - #define G2_PWM_E 0 -#endif +#define G2_PWM_E HAS_MOTOR_CURRENT_PWM_E #define G2_MASK_X(V) (G2_PWM_X * (V)) #define G2_MASK_Y(V) (G2_PWM_Y * (V)) #define G2_MASK_Z(V) (G2_PWM_Z * (V)) @@ -80,17 +77,22 @@ PWM_map ISR_table[NUM_PWMS] = PWM_MAP_INIT; void Stepper::digipot_init() { - #if PIN_EXISTS(MOTOR_CURRENT_PWM_X) - OUT_WRITE(MOTOR_CURRENT_PWM_X_PIN, 0); // init pins + #if G2_PWM_X + OUT_WRITE(MOTOR_CURRENT_PWM_X_PIN, LOW); // init pins #endif - #if PIN_EXISTS(MOTOR_CURRENT_PWM_Y) - OUT_WRITE(MOTOR_CURRENT_PWM_Y_PIN, 0); + #if G2_PWM_Y + OUT_WRITE(MOTOR_CURRENT_PWM_Y_PIN, LOW); #endif #if G2_PWM_Z - OUT_WRITE(MOTOR_CURRENT_PWM_Z_PIN, 0); + OUT_WRITE(MOTOR_CURRENT_PWM_Z_PIN, LOW); #endif #if G2_PWM_E - OUT_WRITE(MOTOR_CURRENT_PWM_E_PIN, 0); + #if PIN_EXISTS(MOTOR_CURRENT_PWM_E) + OUT_WRITE(MOTOR_CURRENT_PWM_E_PIN, LOW); + #endif + #if PIN_EXISTS(MOTOR_CURRENT_PWM_E0) + OUT_WRITE(MOTOR_CURRENT_PWM_E0_PIN, LOW); + #endif #endif #define WPKEY (0x50574D << 8) // ā€œPWMā€ in ASCII diff --git a/Marlin/src/HAL/DUE/fastio/G2_PWM.h b/Marlin/src/HAL/DUE/fastio/G2_PWM.h index 054eb2cf8039..7fb2aaf7a886 100644 --- a/Marlin/src/HAL/DUE/fastio/G2_PWM.h +++ b/Marlin/src/HAL/DUE/fastio/G2_PWM.h @@ -26,10 +26,7 @@ * PR #7500. It is hardwired for the PRINTRBOARD_G2 Motor Current needs. */ -#include "../../../inc/MarlinConfigPre.h" -#include "../../../module/stepper.h" -//C:\Users\bobku\Documents\GitHub\Marlin-Bob-2\Marlin\src\module\stepper.h -//C:\Users\bobku\Documents\GitHub\Marlin-Bob-2\Marlin\src\HAL\HAL_DUE\G2_PWM.h +#include #define PWM_PERIOD_US 100 // base repetition rate in micro seconds diff --git a/Marlin/src/HAL/DUE/inc/SanityCheck.h b/Marlin/src/HAL/DUE/inc/SanityCheck.h index 1eaa2fbd8c73..599e2aa464db 100644 --- a/Marlin/src/HAL/DUE/inc/SanityCheck.h +++ b/Marlin/src/HAL/DUE/inc/SanityCheck.h @@ -68,16 +68,15 @@ * Usually the hardware SPI pins are only available to the LCD. This makes the DUE hard SPI used at the same time * as the TMC2130 soft SPI the most common setup. */ -#define _IS_HW_SPI(P) (defined(TMC_SPI_##P) && (TMC_SPI_##P == SD_MOSI_PIN || TMC_SPI_##P == SD_MISO_PIN || TMC_SPI_##P == SD_SCK_PIN)) - #if HAS_MEDIA && HAS_DRIVER(TMC2130) - #if ENABLED(TMC_USE_SW_SPI) - #if DISABLED(SOFTWARE_SPI) && (_IS_HW_SPI(MOSI) || _IS_HW_SPI(MISO) || _IS_HW_SPI(SCK)) - #error "DUE hardware SPI is required but is incompatible with TMC2130 software SPI. Either disable TMC_USE_SW_SPI or use separate pins for the two SPIs." - #endif - #elif ENABLED(SOFTWARE_SPI) + #define _IS_HW_SPI(P) (defined(TMC_SPI_##P) && (TMC_SPI_##P == SD_MOSI_PIN || TMC_SPI_##P == SD_MISO_PIN || TMC_SPI_##P == SD_SCK_PIN)) + #if DISABLED(SOFTWARE_SPI) && ENABLED(TMC_USE_SW_SPI) && (_IS_HW_SPI(MOSI) || _IS_HW_SPI(MISO) || _IS_HW_SPI(SCK)) + #error "DUE hardware SPI is required but is incompatible with TMC2130 software SPI. Either disable TMC_USE_SW_SPI or use separate pins for the two SPIs." + #endif + #if ENABLED(SOFTWARE_SPI) && DISABLED(TMC_USE_SW_SPI) #error "DUE software SPI is required but is incompatible with TMC2130 hardware SPI. Enable TMC_USE_SW_SPI to fix." #endif + #undef _IS_HW_SPI #endif #if ENABLED(FAST_PWM_FAN) || SPINDLE_LASER_FREQUENCY diff --git a/Marlin/src/HAL/DUE/pinsDebug.h b/Marlin/src/HAL/DUE/pinsDebug.h index 1544853553f0..e9e364dcec77 100644 --- a/Marlin/src/HAL/DUE/pinsDebug.h +++ b/Marlin/src/HAL/DUE/pinsDebug.h @@ -19,13 +19,26 @@ * along with this program. If not, see . * */ +#pragma once /** - * Support routines for Due - */ - -/** - * Translation of routines & variables used by pinsDebug.h + * Pins Debugging for DUE + * + * - NUMBER_PINS_TOTAL + * - MULTI_NAME_PAD + * - getPinByIndex(index) + * - printPinNameByIndex(index) + * - getPinIsDigitalByIndex(index) + * - digitalPinToAnalogIndex(pin) + * - getValidPinMode(pin) + * - isValidPin(pin) + * - isAnalogPin(pin) + * - digitalRead_mod(pin) + * - pwm_status(pin) + * - printPinPWM(pin) + * - printPinPort(pin) + * - printPinNumber(pin) + * - printPinAnalog(pin) */ #include "../shared/Marduino.h" @@ -63,20 +76,20 @@ #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 pwm_status(pin) (((g_pinStatus[pin] & 0xF) == PIN_STATUS_PWM) && \ - ((g_APinDescription[pin].ulPinAttribute & PIN_ATTR_PWM) == PIN_ATTR_PWM)) +#define digitalRead_mod(P) extDigitalRead(P) // AVR digitalRead disabled PWM before it read the 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(P)); SERIAL_ECHO(buffer); }while(0) +#define getPinByIndex(x) pin_array[x].pin +#define getPinIsDigitalByIndex(x) pin_array[x].is_digital +#define isValidPin(P) (P >= 0 && P < pin_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(P) (((g_pinStatus[P] & 0xF) == PIN_STATUS_PWM) && \ + ((g_APinDescription[P].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(const pin_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 +98,14 @@ bool GET_PINMODE(int8_t pin) { // 1: output, 0: input || pwm_status(pin)); } -void pwm_details(int32_t pin) { +void printPinPWM(const 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 diff --git a/Marlin/src/HAL/DUE/spi_pins.h b/Marlin/src/HAL/DUE/spi_pins.h index 41ac7a8b5826..e289c8ced206 100644 --- a/Marlin/src/HAL/DUE/spi_pins.h +++ b/Marlin/src/HAL/DUE/spi_pins.h @@ -24,41 +24,38 @@ /** * Define SPI Pins: SCK, MISO, MOSI, SS * - * Available chip select pins for HW SPI are 4 10 52 77 + * Available chip select pins for HW SPI are 4 10 52 77 87 */ -#if SDSS == 4 || SDSS == 10 || SDSS == 52 || SDSS == 77 || SDSS == 87 - #if SDSS == 4 - #define SPI_PIN 87 - #define SPI_CHAN 1 - #elif SDSS == 10 - #define SPI_PIN 77 - #define SPI_CHAN 0 - #elif SDSS == 52 - #define SPI_PIN 86 - #define SPI_CHAN 2 - #elif SDSS == 77 - #define SPI_PIN 77 - #define SPI_CHAN 0 - #else - #define SPI_PIN 87 - #define SPI_CHAN 1 - #endif - #define SD_SCK_PIN 76 - #define SD_MISO_PIN 74 - #define SD_MOSI_PIN 75 +#if SD_SS_PIN == 4 || SD_SS_PIN == 10 || SD_SS_PIN == 52 || SD_SS_PIN == 77 || SD_SS_PIN == 87 + #define SD_SCK_PIN 76 + #define SD_MISO_PIN 74 + #define SD_MOSI_PIN 75 +#endif + +#if SD_SS_PIN == 4 + #define SPI_PIN 87 + #define SPI_CHAN 1 +#elif SD_SS_PIN == 10 + #define SPI_PIN 77 + #define SPI_CHAN 0 +#elif SD_SS_PIN == 52 + #define SPI_PIN 86 + #define SPI_CHAN 2 +#elif SD_SS_PIN == 77 + #define SPI_PIN 77 + #define SPI_CHAN 0 +#elif SD_SS_PIN == 87 + #define SPI_PIN 87 + #define SPI_CHAN 1 #else - // defaults #define SOFTWARE_SPI #ifndef SD_SCK_PIN - #define SD_SCK_PIN 52 + #define SD_SCK_PIN 52 #endif #ifndef SD_MISO_PIN - #define SD_MISO_PIN 50 + #define SD_MISO_PIN 50 #endif #ifndef SD_MOSI_PIN - #define SD_MOSI_PIN 51 + #define SD_MOSI_PIN 51 #endif #endif - -/* A.28, A.29, B.21, C.26, C.29 */ -#define SD_SS_PIN SDSS diff --git a/Marlin/src/HAL/DUE/timers.h b/Marlin/src/HAL/DUE/timers.h index db5d83a06f74..f892eac8f5c4 100644 --- a/Marlin/src/HAL/DUE/timers.h +++ b/Marlin/src/HAL/DUE/timers.h @@ -34,7 +34,7 @@ #define FORCE_INLINE __attribute__((always_inline)) inline typedef uint32_t hal_timer_t; -#define HAL_TIMER_TYPE_MAX 0xFFFFFFFF +#define HAL_TIMER_TYPE_MAX hal_timer_t(UINT32_MAX) #define HAL_TIMER_PRESCALER 2 #define HAL_TIMER_RATE ((F_CPU) / (HAL_TIMER_PRESCALER)) // frequency of timers peripherals @@ -52,19 +52,18 @@ typedef uint32_t hal_timer_t; #define MF_TIMER_TONE 6 // index of timer to use for beeper tones #endif -#define TEMP_TIMER_FREQUENCY 1000 // temperature interrupt frequency +#define TEMP_TIMER_FREQUENCY 1000 // (Hz) Temperature ISR frequency -#define STEPPER_TIMER_RATE HAL_TIMER_RATE // frequency of stepper timer (HAL_TIMER_RATE / STEPPER_TIMER_PRESCALE) -#define STEPPER_TIMER_TICKS_PER_US ((STEPPER_TIMER_RATE) / 1000000) // stepper timer ticks per µs +#define STEPPER_TIMER_RATE HAL_TIMER_RATE // (Hz) Frequency of Stepper Timer ISR (HAL_TIMER_RATE / STEPPER_TIMER_PRESCALE) +#define STEPPER_TIMER_TICKS_PER_US ((STEPPER_TIMER_RATE) / 1000000UL) // (MHz) Stepper Timer ticks per µs #define STEPPER_TIMER_PRESCALE (CYCLES_PER_MICROSECOND / STEPPER_TIMER_TICKS_PER_US) -#define PULSE_TIMER_RATE STEPPER_TIMER_RATE // frequency of pulse timer +#define PULSE_TIMER_RATE STEPPER_TIMER_RATE // (Hz) Frequency of Pulse Timer #define PULSE_TIMER_PRESCALE STEPPER_TIMER_PRESCALE -#define PULSE_TIMER_TICKS_PER_US STEPPER_TIMER_TICKS_PER_US -#define ENABLE_STEPPER_DRIVER_INTERRUPT() HAL_timer_enable_interrupt(MF_TIMER_STEP) +#define ENABLE_STEPPER_DRIVER_INTERRUPT() HAL_timer_enable_interrupt(MF_TIMER_STEP) #define DISABLE_STEPPER_DRIVER_INTERRUPT() HAL_timer_disable_interrupt(MF_TIMER_STEP) -#define STEPPER_ISR_ENABLED() HAL_timer_interrupt_enabled(MF_TIMER_STEP) +#define STEPPER_ISR_ENABLED() HAL_timer_interrupt_enabled(MF_TIMER_STEP) #define ENABLE_TEMPERATURE_INTERRUPT() HAL_timer_enable_interrupt(MF_TIMER_TEMP) #define DISABLE_TEMPERATURE_INTERRUPT() HAL_timer_disable_interrupt(MF_TIMER_TEMP) @@ -127,4 +126,4 @@ FORCE_INLINE static void HAL_timer_isr_prologue(const uint8_t timer_num) { pConfig->pTimerRegs->TC_CHANNEL[pConfig->channel].TC_SR; } -#define HAL_timer_isr_epilogue(T) NOOP +inline void HAL_timer_isr_epilogue(const uint8_t) {} diff --git a/Marlin/src/HAL/DUE/u8g/u8g_com_HAL_DUE_sw_spi.cpp b/Marlin/src/HAL/DUE/u8g/u8g_com_HAL_DUE_sw_spi.cpp index 68e3e74a45a0..a8f4dd2b0307 100644 --- a/Marlin/src/HAL/DUE/u8g/u8g_com_HAL_DUE_sw_spi.cpp +++ b/Marlin/src/HAL/DUE/u8g/u8g_com_HAL_DUE_sw_spi.cpp @@ -66,7 +66,7 @@ #include -#if ENABLED(FYSETC_MINI_12864) +#if U8G_SPI_USE_MODE_3 #define SPISEND_SW_DUE u8g_spiSend_sw_DUE_mode_3 #else #define SPISEND_SW_DUE u8g_spiSend_sw_DUE_mode_0 @@ -96,15 +96,15 @@ uint8_t u8g_com_HAL_DUE_sw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void break; case U8G_COM_MSG_CHIP_SELECT: - #if ENABLED(FYSETC_MINI_12864) // LCD SPI is running mode 3 while SD card is running mode 0 - if (arg_val) { // SCK idle state needs to be set to the proper idle state before - // the next chip select goes active - u8g_SetPILevel_DUE(u8g, U8G_PI_SCK, 1); //set SCK to mode 3 idle state before CS goes active + #if U8G_SPI_USE_MODE_3 // LCD SPI is running mode 3 while SD card is running mode 0 + if (arg_val) { // SCK idle state needs to be set to the proper idle state before + // the next chip select goes active + u8g_SetPILevel_DUE(u8g, U8G_PI_SCK, 1); // Set SCK to mode 3 idle state before CS goes active u8g_SetPILevel_DUE(u8g, U8G_PI_CS, LOW); } else { u8g_SetPILevel_DUE(u8g, U8G_PI_CS, HIGH); - u8g_SetPILevel_DUE(u8g, U8G_PI_SCK, 0); //set SCK to mode 0 idle state after CS goes inactive + u8g_SetPILevel_DUE(u8g, U8G_PI_SCK, 0); // Set SCK to mode 0 idle state after CS goes inactive } #else u8g_SetPILevel_DUE(u8g, U8G_PI_CS, !arg_val); diff --git a/Marlin/src/HAL/DUE/upload_extra_script.py b/Marlin/src/HAL/DUE/upload_extra_script.py index ca12b3b54f43..729b1d67de61 100644 --- a/Marlin/src/HAL/DUE/upload_extra_script.py +++ b/Marlin/src/HAL/DUE/upload_extra_script.py @@ -11,7 +11,7 @@ if current_OS == 'Windows': - Import("env") + env = pioutil.env # Use bossac.exe on Windows env.Replace( diff --git a/Marlin/src/HAL/DUE/usb/.editorconfig b/Marlin/src/HAL/DUE/usb/.editorconfig new file mode 100644 index 000000000000..f2c26de5eea8 --- /dev/null +++ b/Marlin/src/HAL/DUE/usb/.editorconfig @@ -0,0 +1,5 @@ +# editorconfig.org + +[{*.c,*.cpp,*.h}] +indent_style = tab +indent_size = 4 diff --git a/Marlin/src/HAL/DUE/usb/ctrl_access.c b/Marlin/src/HAL/DUE/usb/ctrl_access.c index b766ed12732b..ec0e4adc4b23 100644 --- a/Marlin/src/HAL/DUE/usb/ctrl_access.c +++ b/Marlin/src/HAL/DUE/usb/ctrl_access.c @@ -63,8 +63,8 @@ #include "compiler.h" #include "preprocessor.h" #ifdef FREERTOS_USED -#include "FreeRTOS.h" -#include "semphr.h" +#include +#include #endif #include "ctrl_access.h" diff --git a/Marlin/src/HAL/DUE/usb/sd_mmc_spi_mem.cpp b/Marlin/src/HAL/DUE/usb/sd_mmc_spi_mem.cpp index 65a926ff362b..74cfd9b39bbf 100644 --- a/Marlin/src/HAL/DUE/usb/sd_mmc_spi_mem.cpp +++ b/Marlin/src/HAL/DUE/usb/sd_mmc_spi_mem.cpp @@ -18,32 +18,31 @@ extern "C" { void sd_mmc_spi_mem_init() { } +inline bool media_ready() { + return card.isMounted() && card.isInserted() && !card.isFileOpen() && !card.isStillPrinting(); +} + +bool sd_mmc_spi_unload(bool) { return true; } + +bool sd_mmc_spi_wr_protect() { return false; } + +bool sd_mmc_spi_removal() { return !media_ready(); } + Ctrl_status sd_mmc_spi_test_unit_ready() { - #ifdef DISABLE_DUE_SD_MMC + #if ENABLED(DISABLE_DUE_SD_MMC) return CTRL_NO_PRESENT; #endif - if (!IS_SD_INSERTED() || IS_SD_PRINTING() || IS_SD_FILE_OPEN() || !card.isMounted()) - return CTRL_NO_PRESENT; - return CTRL_GOOD; + return sd_mmc_spi_removal() ? CTRL_NO_PRESENT : CTRL_GOOD; } // NOTE: This function is defined as returning the address of the last block // in the card, which is cardSize() - 1 Ctrl_status sd_mmc_spi_read_capacity(uint32_t *nb_sector) { - if (!IS_SD_INSERTED() || IS_SD_PRINTING() || IS_SD_FILE_OPEN() || !card.isMounted()) - return CTRL_NO_PRESENT; + if (sd_mmc_spi_removal()) return CTRL_NO_PRESENT; *nb_sector = card.diskIODriver()->cardSize() - 1; return CTRL_GOOD; } -bool sd_mmc_spi_unload(bool) { return true; } - -bool sd_mmc_spi_wr_protect() { return false; } - -bool sd_mmc_spi_removal() { - return (!IS_SD_INSERTED() || IS_SD_PRINTING() || IS_SD_FILE_OPEN() || !card.isMounted()); -} - #if ACCESS_USB == true /** * \name MEM <-> USB Interface @@ -58,11 +57,11 @@ uint8_t sector_buf[SD_MMC_BLOCK_SIZE]; // #define DEBUG_MMC Ctrl_status sd_mmc_spi_usb_read_10(uint32_t addr, uint16_t nb_sector) { - #ifdef DISABLE_DUE_SD_MMC + #if ENABLED(DISABLE_DUE_SD_MMC) return CTRL_NO_PRESENT; #endif - if (!IS_SD_INSERTED() || IS_SD_PRINTING() || IS_SD_FILE_OPEN() || !card.isMounted()) - return CTRL_NO_PRESENT; + + if (sd_mmc_spi_removal()) return CTRL_NO_PRESENT; #ifdef DEBUG_MMC { @@ -98,11 +97,11 @@ Ctrl_status sd_mmc_spi_usb_read_10(uint32_t addr, uint16_t nb_sector) { } Ctrl_status sd_mmc_spi_usb_write_10(uint32_t addr, uint16_t nb_sector) { - #ifdef DISABLE_DUE_SD_MMC + #if ENABLED(DISABLE_DUE_SD_MMC) return CTRL_NO_PRESENT; #endif - if (!IS_SD_INSERTED() || IS_SD_PRINTING() || IS_SD_FILE_OPEN() || !card.isMounted()) - return CTRL_NO_PRESENT; + + if (sd_mmc_spi_removal()) return CTRL_NO_PRESENT; #ifdef DEBUG_MMC { diff --git a/Marlin/src/HAL/DUE/usb/sd_mmc_spi_mem.h b/Marlin/src/HAL/DUE/usb/sd_mmc_spi_mem.h index c0d3c925e80a..464d106e5293 100644 --- a/Marlin/src/HAL/DUE/usb/sd_mmc_spi_mem.h +++ b/Marlin/src/HAL/DUE/usb/sd_mmc_spi_mem.h @@ -74,7 +74,7 @@ //! //! @brief This function initializes the hw/sw resources required to drive the SD_MMC_SPI. //!/ -extern void sd_mmc_spi_mem_init(void); +void sd_mmc_spi_mem_init(); //! //! @brief This function tests the state of the SD_MMC memory and sends it to the Host. @@ -87,7 +87,7 @@ extern void sd_mmc_spi_mem_init(void); //! Media not present -> CTRL_NO_PRESENT //! Media has changed -> CTRL_BUSY //!/ -extern Ctrl_status sd_mmc_spi_test_unit_ready(void); +Ctrl_status sd_mmc_spi_test_unit_ready(); //! //! @brief This function gives the address of the last valid sector. @@ -98,7 +98,7 @@ extern Ctrl_status sd_mmc_spi_test_unit_ready(void); //! Media ready -> CTRL_GOOD //! Media not present -> CTRL_NO_PRESENT //!/ -extern Ctrl_status sd_mmc_spi_read_capacity(uint32_t *nb_sector); +Ctrl_status sd_mmc_spi_read_capacity(uint32_t *nb_sector); /*! \brief Unload/Load the SD/MMC card selected * @@ -109,7 +109,7 @@ extern Ctrl_status sd_mmc_spi_read_capacity(uint32_t *nb_sector); * * \return \c true if unload/load done success. */ -extern bool sd_mmc_spi_unload(bool unload); +bool sd_mmc_spi_unload(bool unload); //! //! @brief This function returns the write protected status of the memory. @@ -120,14 +120,14 @@ extern bool sd_mmc_spi_unload(bool unload); //! //! @return false -> the memory is not write-protected (always) //!/ -extern bool sd_mmc_spi_wr_protect(void); +bool sd_mmc_spi_wr_protect(); //! //! @brief This function tells if the memory has been removed or not. //! //! @return false -> The memory isn't removed //! -extern bool sd_mmc_spi_removal(void); +bool sd_mmc_spi_removal(); //---- ACCESS DATA FUNCTIONS ---- @@ -147,7 +147,7 @@ extern bool sd_mmc_spi_removal(void); //! It is ready -> CTRL_GOOD //! A error occur -> CTRL_FAIL //! -extern Ctrl_status sd_mmc_spi_usb_read_10(uint32_t addr, uint16_t nb_sector); +Ctrl_status sd_mmc_spi_usb_read_10(uint32_t addr, uint16_t nb_sector); //! This function initializes the SD/MMC memory for a write operation //! @@ -161,7 +161,7 @@ extern Ctrl_status sd_mmc_spi_usb_read_10(uint32_t addr, uint16_t nb_sector); //! It is ready -> CTRL_GOOD //! An error occurs -> CTRL_FAIL //! -extern Ctrl_status sd_mmc_spi_usb_write_10(uint32_t addr, uint16_t nb_sector); +Ctrl_status sd_mmc_spi_usb_write_10(uint32_t addr, uint16_t nb_sector); #endif // #if ACCESS_USB == true diff --git a/Marlin/src/HAL/DUE/usb/udc.h b/Marlin/src/HAL/DUE/usb/udc.h index aba08d956e84..e8c0e7fbeac3 100644 --- a/Marlin/src/HAL/DUE/usb/udc.h +++ b/Marlin/src/HAL/DUE/usb/udc.h @@ -229,7 +229,7 @@ usb_iface_desc_t UDC_DESC_STORAGE *udc_get_interface_desc(void); * - USB Device Controller (UDC) provides USB chapter 9 compliance * - USB Device Interface (UDI) provides USB Class compliance * - USB Device Driver (UDD) provides USB Driver for each Atmel MCU - + * * Many USB Device applications can be implemented on Atmel MCU. * Atmel provides many application notes for different applications: * - AVR4900, provides general information about Device Stack diff --git a/Marlin/src/HAL/DUE/usb/uotghs_device_due.c b/Marlin/src/HAL/DUE/usb/uotghs_device_due.c index 01dda7e7fec8..5635f2ba0c7e 100644 --- a/Marlin/src/HAL/DUE/usb/uotghs_device_due.c +++ b/Marlin/src/HAL/DUE/usb/uotghs_device_due.c @@ -523,7 +523,7 @@ static bool udd_ep_interrupt(void); * \internal * \brief Function called by UOTGHS interrupt to manage USB Device interrupts * - * USB Device interrupt events are splited in three parts: + * USB Device interrupt events are split in three parts: * - USB line events (SOF, reset, suspend, resume, wakeup) * - control endpoint events (setup reception, end of data transfer, underflow, overflow, stall) * - bulk/interrupt/isochronous endpoints events (end of data transfer) @@ -1567,7 +1567,7 @@ static void udd_ctrl_out_received(void) udd_ctrl_payload_buf_cnt))) { // End of reception because it is a short packet // Before send ZLP, call intermediate callback - // in case of data receiv generate a stall + // in case of data receive generate a stall udd_g_ctrlreq.payload_size = udd_ctrl_payload_buf_cnt; if (NULL != udd_g_ctrlreq.over_under_run) { if (!udd_g_ctrlreq.over_under_run()) { @@ -1808,7 +1808,7 @@ static void udd_ep_trans_done(udd_ep_id_t ep) } if (ptr_job->buf_cnt != ptr_job->buf_size) { - // Need to send or receiv other data + // Need to send or receive other data next_trans = ptr_job->buf_size - ptr_job->buf_cnt; if (UDD_ENDPOINT_MAX_TRANS < next_trans) { diff --git a/Marlin/src/HAL/ESP32/AGENTS.md b/Marlin/src/HAL/ESP32/AGENTS.md new file mode 100644 index 000000000000..ac2def94afc5 --- /dev/null +++ b/Marlin/src/HAL/ESP32/AGENTS.md @@ -0,0 +1,66 @@ +# AGENTS.md — ESP32 HAL + +Working notes and conventions for the **ESP32 HAL** folder (`Marlin/src/HAL/ESP32/`). This HAL runs Marlin on Espressif ESP32 (Xtensa) boards, using I2S-DMA stepper pulse generation, an optional serial I/O expander, and optional WiFi/OTA/Web support. + +## Build & Test Loop + +Validate with `mftest` (it regenerates `Marlin/Configuration.h` per target, which raw `pio run -e` does not do reliably): + +```bash +cd "$(git rev-parse --show-toplevel)" +buildroot/bin/mftest -t esp32 -n1 -y # BOARD_ESPRESSIF_ESP32, WIFISUPPORT+WEBSUPPORT+OTA +buildroot/bin/mftest -t esp32 -n2 -y # BOARD_ESPRESSIF_ESP32, TMC HW serial (Serial1) +buildroot/bin/mftest -t mks_tinybee -n1 -y # BOARD_MKS_TINYBEE, ESP3D_WIFISUPPORT +``` + +Both `esp32` test targets exercise the base `env:esp32` build; `mks_tinybee` exercises the `ESP3D_WIFISUPPORT` path. Additional `ini/esp32.ini` envs with no test folder — `FYSETC_E4`, `PANDA`, `godi_esp32` — can be built directly with `pio run -e -y` to check their partition-table / EXIO variants. + +> Builds are **not verified in this environment** (the `espressif32` PlatformIO platform may not be installed here). Run the `mftest` commands above to validate; do not claim green without running them. +> +> Do NOT use `rm -rf .pio/build/...` to force a rebuild — the cross-profile write guard blocks it and it is unsafe. Use `pio run -e -t clean` or just let `mftest` rebuild. + +## Framework / Platform Integration (upstream, NOT vendored) + +ESP32 uses the **upstream `espressif32` PlatformIO platform** (`espressif32@2.1.0`, toolchain `toolchain-xtensa-esp32s3`) plus the upstream Arduino-ESP32 / ESP-IDF libraries. There is **no Marlin-vendored core** (AT32 is the exception). All Marlin-specific glue lives in: + +- `Marlin/src/HAL/ESP32/` — HAL implementation, I2S stepper, WiFi, OTA, SPIFFS, serial. +- `Marlin/src/pins/esp32/` — board pin tables (each pins file declares its `env:`). +- `ini/esp32.ini` — env defs, partition CSV selection, build flags. + +Because the framework is upstream, there is **no "mirror to installed package" step** — just edit the repo files and rebuild. + +### Gotchas that are not obvious from the source + +1. **Marlin runs on the Arduino "app" core, not alone.** The I2S stepper task is pinned to `CONFIG_ARDUINO_RUNNING_CORE` via `xTaskCreatePinnedToCore(...)` in `i2s.cpp:343`. The chip is dual-core; do not assume single-core timing and avoid long `portENTER_CRITICAL` sections that would stall the other core. + +2. **`DELAY_CYCLES` can pause prints for ~17.9 s.** The cycle-delay loop in `HAL.h` reads the `ccount` cycle counter and is vulnerable to overflow if a long FreeRTOS preemption/ISR lands mid-delay (it re-loops until `ccount` passes `stop`). This has caused real, apparently-random print stalls. The code already guards the overflow case — preserve that logic; do not "simplify" it. + +3. **`MYSERIAL1` is `flushableSerial`, not the USB `Serial`.** `HAL.h` defines `MYSERIAL1 flushableSerial` (`FlushableHardwareSerial.h`). On ESP32 the bare `Serial` is USB/JTAG CDC; Marlin's G-code link is a hardware UART (Serial1 by default). Do not confuse the two. TMC HW-serial remaps Serial1/Serial2 to user pins in `HAL.cpp init_board()`. + +4. **ADC2 is unusable while WiFi is active.** `HAL.cpp adc_init()` only configures ADC1 channels (`get_channel()` maps pins 32–39); ADC2 is shared with the WiFi radio and is intentionally left unconfigured (see comment at `HAL.cpp:253`). Keep thermistor pins on ADC1, or readings will fail when WiFi is up. + +5. **Partition table is per-board and matters.** `env:esp32`/`mks_tinybee` use the platform default CSV; `FYSETC_E4` uses `default_16MB.csv`; `PANDA` and `godi_esp32` use the custom `Marlin/src/HAL/ESP32/esp32.csv` (otadata + two OTA app slots of 0x180000 + a SPIFFS partition). `WEBSUPPORT` needs the SPIFFS partition — if you change flash layout, keep the board's partition CSV in sync or OTA/Web won't fit. + +6. **Two mutually-exclusive stepper backends.** `I2S_STEPPER_STREAM` (DMA-driven I2S GPIO expander, the default) and `USE_ESP32_EXIO` (serial I/O expander, used by PANDA). `i2s.cpp` is compiled only when `!USE_ESP32_EXIO`. Do not enable both; expander-pin PWM (pin > 127) is routed through the I2S path in `HAL.cpp` `set_pwm_duty`. + +7. **EEPROM auto-selects the Arduino `EEPROM` class.** `eeprom.cpp` backs `PersistentStore` with the ESP32 `EEPROM` NVS emulation (`MARLIN_EEPROM_SIZE` = 4 KB, `EEPROM.begin()` / `EEPROM.end()`). No per-board `SDCARD_EEPROM_EMULATION` / `I2C_EEPROM` define is required (unlike AT32). + +8. **Hardware-PWM pins are GPIO 0–33 only.** `MAX_PWM_IOPIN = 33` in `HAL.h`; `get_pwm_channel()` (HAL.cpp) rejects pins ≄ 34 (input-only) and caps at 15 LEDC channels. Pins 34–39 are input-only on ESP32 — never assign them to heater/fan PWM. + +9. **Watchdog is split and partly stubbed.** `USE_WATCHDOG` refreshes the ESP task WDT via `esp_task_wdt_reset()`, but `MarlinHAL::watchdog_init()` is an explicit TODO stub. Separately, `USE_ESP32_TASK_WDT` inits a 10 s task WDT in `init_board()`. Don't rely on `watchdog_init()` doing setup. + +10. **WiFi builds are size-sensitive.** `WIFISUPPORT` pulls ESPAsyncWebServer + Wi-Fi; `ESP3D_WIFISUPPORT` pulls esp3dlib. `ini/esp32.ini` sets `-DCORE_DEBUG_LEVEL=0` to keep the image small, and `mks_tinybee` adds `build_src_flags = -O3`. If a WiFi-enabled config overflows, check the partition CSV and debug level before touching HAL code. + +## Conventions + +- All HAL source is guarded by `#ifdef ARDUINO_ARCH_ESP32`. +- Keep HAL changes in `Marlin/src/HAL/ESP32/` and board pin changes in `Marlin/src/pins/esp32/`. Build/partition/env glue goes in `ini/esp32.ini`. +- The framework is upstream — edit repo files and rebuild; there is no vendored package to mirror. +- Don't enable both `I2S_STEPPER_STREAM` and `USE_ESP32_EXIO`; don't put thermistor pins on ADC2 when WiFi is in use; don't assign heater/fan pins ≄ 34. + +## Related + +- No `README.md` in this folder. +- Wiki: [[hal-esp32]], [[marlin-src-hal]]. +- `Marlin/src/HAL/shared/` — shared HAL APIs (`eeprom_api`, SPI helpers) the ESP32 HAL reuses. +- `ini/esp32.ini` — env/partition/flag definitions for all ESP32 boards. diff --git a/Marlin/src/HAL/ESP32/FlushableHardwareSerial.cpp b/Marlin/src/HAL/ESP32/FlushableHardwareSerial.cpp index 145662215a9b..211cfda65c85 100644 --- a/Marlin/src/HAL/ESP32/FlushableHardwareSerial.cpp +++ b/Marlin/src/HAL/ESP32/FlushableHardwareSerial.cpp @@ -25,5 +25,6 @@ #include "FlushableHardwareSerial.h" Serial1Class flushableSerial(false, 0); +Serial1Class flushableSerial2(false, 2); #endif diff --git a/Marlin/src/HAL/ESP32/FlushableHardwareSerial.h b/Marlin/src/HAL/ESP32/FlushableHardwareSerial.h index 012dda8626b0..ee864d08fadb 100644 --- a/Marlin/src/HAL/ESP32/FlushableHardwareSerial.h +++ b/Marlin/src/HAL/ESP32/FlushableHardwareSerial.h @@ -32,3 +32,4 @@ class FlushableHardwareSerial : public HardwareSerial { }; extern Serial1Class flushableSerial; +extern Serial1Class flushableSerial2; diff --git a/Marlin/src/HAL/ESP32/HAL.cpp b/Marlin/src/HAL/ESP32/HAL.cpp index 4890972b0193..9adff634a03b 100644 --- a/Marlin/src/HAL/ESP32/HAL.cpp +++ b/Marlin/src/HAL/ESP32/HAL.cpp @@ -34,13 +34,13 @@ #if ENABLED(WIFISUPPORT) #include - #include "wifi.h" + #include "wifi/wifi.h" #if ENABLED(OTASUPPORT) - #include "ota.h" + #include "wifi/ota.h" #endif #if ENABLED(WEBSUPPORT) - #include "spiffs.h" - #include "web.h" + #include "wifi/spiffs.h" + #include "wifi/web.h" #endif #endif @@ -175,8 +175,6 @@ uint8_t MarlinHAL::get_reset_source() { return rtc_get_reset_reason(1); } void MarlinHAL::reboot() { ESP.restart(); } -void _delay_ms(const int ms) { delay(ms); } - // return free memory between end of heap (or end bss) and whatever is current int MarlinHAL::freeMemory() { return ESP.getFreeHeap(); } @@ -209,16 +207,17 @@ int MarlinHAL::freeMemory() { return ESP.getFreeHeap(); } // ADC // ------------------------ -#define ADC1_CHANNEL(pin) ADC1_GPIO ## pin ## _CHANNEL - +// https://docs.espressif.com/projects/esp-idf/en/release-v4.4/esp32/api-reference/peripherals/adc.html adc1_channel_t get_channel(int pin) { switch (pin) { - case 39: return ADC1_CHANNEL(39); - case 36: return ADC1_CHANNEL(36); - case 35: return ADC1_CHANNEL(35); - case 34: return ADC1_CHANNEL(34); - case 33: return ADC1_CHANNEL(33); - case 32: return ADC1_CHANNEL(32); + case 39: return ADC1_CHANNEL_3; + case 36: return ADC1_CHANNEL_0; + case 35: return ADC1_CHANNEL_7; + case 34: return ADC1_CHANNEL_6; + case 33: return ADC1_CHANNEL_5; + case 32: return ADC1_CHANNEL_4; + case 37: return ADC1_CHANNEL_1; + case 38: return ADC1_CHANNEL_2; } return ADC1_CHANNEL_MAX; } @@ -243,12 +242,13 @@ void MarlinHAL::adc_init() { TERN_(HAS_TEMP_ADC_5, adc1_set_attenuation(get_channel(TEMP_5_PIN), ADC_ATTEN_11db)); TERN_(HAS_TEMP_ADC_6, adc2_set_attenuation(get_channel(TEMP_6_PIN), ADC_ATTEN_11db)); TERN_(HAS_TEMP_ADC_7, adc3_set_attenuation(get_channel(TEMP_7_PIN), ADC_ATTEN_11db)); - TERN_(HAS_HEATED_BED, adc1_set_attenuation(get_channel(TEMP_BED_PIN), ADC_ATTEN_11db)); - TERN_(HAS_TEMP_CHAMBER, adc1_set_attenuation(get_channel(TEMP_CHAMBER_PIN), ADC_ATTEN_11db)); - TERN_(HAS_TEMP_PROBE, adc1_set_attenuation(get_channel(TEMP_PROBE_PIN), ADC_ATTEN_11db)); - TERN_(HAS_TEMP_COOLER, adc1_set_attenuation(get_channel(TEMP_COOLER_PIN), ADC_ATTEN_11db)); - TERN_(HAS_TEMP_BOARD, adc1_set_attenuation(get_channel(TEMP_BOARD_PIN), ADC_ATTEN_11db)); - TERN_(FILAMENT_WIDTH_SENSOR, adc1_set_attenuation(get_channel(FILWIDTH_PIN), ADC_ATTEN_11db)); + TERN_(HAS_TEMP_ADC_BED, adc1_set_attenuation(get_channel(TEMP_BED_PIN), ADC_ATTEN_11db)); + TERN_(HAS_TEMP_ADC_CHAMBER, adc1_set_attenuation(get_channel(TEMP_CHAMBER_PIN), ADC_ATTEN_11db)); + TERN_(HAS_TEMP_ADC_PROBE, adc1_set_attenuation(get_channel(TEMP_PROBE_PIN), ADC_ATTEN_11db)); + TERN_(HAS_TEMP_ADC_COOLER, adc1_set_attenuation(get_channel(TEMP_COOLER_PIN), ADC_ATTEN_11db)); + TERN_(HAS_TEMP_ADC_BOARD, adc1_set_attenuation(get_channel(TEMP_BOARD_PIN), ADC_ATTEN_11db)); + TERN_(HAS_FILWIDTH_ADC, adc1_set_attenuation(get_channel(FILWIDTH_PIN), ADC_ATTEN_11db)); + TERN_(HAS_FILWIDTH2_ADC, adc1_set_attenuation(get_channel(FILWIDTH2_PIN), ADC_ATTEN_11db)); // Note that adc2 is shared with the WiFi module, which has higher priority, so the conversion may fail. // That's why we're not setting it up here. @@ -271,7 +271,8 @@ void MarlinHAL::adc_start(const pin_t pin) { uint32_t mv; esp_adc_cal_get_voltage((adc_channel_t)chan, &characteristics[attenuations[chan]], &mv); - adc_result = mv * isr_float_t(1023) / isr_float_t(ADC_REFERENCE_VOLTAGE) / isr_float_t(1000); + static constexpr uint32_t adc_divisor = uint32_t((ADC_REFERENCE_VOLTAGE) * 1000UL); + adc_result = (mv * 1023UL) / adc_divisor; // Change the attenuation level based on the new reading adc_atten_t atten; diff --git a/Marlin/src/HAL/ESP32/HAL.h b/Marlin/src/HAL/ESP32/HAL.h index 0acb3676a2f9..6fa37705d45c 100644 --- a/Marlin/src/HAL/ESP32/HAL.h +++ b/Marlin/src/HAL/ESP32/HAL.h @@ -37,11 +37,11 @@ #include "i2s.h" #if ENABLED(WIFISUPPORT) - #include "WebSocketSerial.h" + #include "wifi/WebSocketSerial.h" #endif #if ENABLED(ESP3D_WIFISUPPORT) - #include "esp3dlib.h" + #include #endif #include "FlushableHardwareSerial.h" @@ -60,14 +60,23 @@ #define MYSERIAL2 webSocketSerial #endif +#if SERIAL_PORT_2 == 2 + #define MYSERIAL2 flushableSerial2 +#endif + +#if LCD_SERIAL_PORT == 2 + #define LCD_SERIAL flushableSerial2 + #define SERIAL_GET_TX_BUFFER_FREE() LCD_SERIAL.availableForWrite() +#endif + #define CRITICAL_SECTION_START() portENTER_CRITICAL(&hal.spinlock) #define CRITICAL_SECTION_END() portEXIT_CRITICAL(&hal.spinlock) #define HAL_CAN_SET_PWM_FREQ // This HAL supports PWM Frequency adjustment -#define PWM_FREQUENCY 1000u // Default PWM frequency when set_pwm_duty() is called without set_pwm_frequency() -#define PWM_RESOLUTION 10u // Default PWM bit resolution -#define CHANNEL_MAX_NUM 15u // max PWM channel # to allocate (7 to only use low speed, 15 to use low & high) -#define MAX_PWM_IOPIN 33u // hardware pwm pins < 34 +#define PWM_FREQUENCY 1000U // Default PWM frequency when set_pwm_duty() is called without set_pwm_frequency() +#define PWM_RESOLUTION 10U // Default PWM bit resolution +#define CHANNEL_MAX_NUM 15U // max PWM channel # to allocate (7 to only use low speed, 15 to use low & high) +#define MAX_PWM_IOPIN 33U // hardware pwm pins < 34 #ifndef MAX_EXPANDER_BITS #define MAX_EXPANDER_BITS 32 // I2S expander bit width (max 32) #endif @@ -76,7 +85,6 @@ // Types // ------------------------ -typedef double isr_float_t; // FPU ops are used for single-precision, so use double for ISRs. typedef int16_t pin_t; typedef struct pwm_pin { @@ -165,8 +173,6 @@ int freeMemory(); #pragma GCC diagnostic pop -void _delay_ms(const int ms); - // ------------------------ // MarlinHAL Class // ------------------------ @@ -194,9 +200,9 @@ class MarlinHAL { static void isr_on() { if (spinlock.owner != portMUX_FREE_VAL) portEXIT_CRITICAL(&spinlock); } static void isr_off() { portENTER_CRITICAL(&spinlock); } - static void delay_ms(const int ms) { _delay_ms(ms); } + static void delay_ms(const int ms) { delay(ms); } - // Tasks, called from idle() + // Tasks, called from marlin.idle() static void idletask(); // Reset diff --git a/Marlin/src/HAL/ESP32/Servo.cpp b/Marlin/src/HAL/ESP32/Servo.cpp index ca3950d07f75..3a2c11832de7 100644 --- a/Marlin/src/HAL/ESP32/Servo.cpp +++ b/Marlin/src/HAL/ESP32/Servo.cpp @@ -35,7 +35,7 @@ Servo::Servo() {} int8_t Servo::attach(const int inPin) { if (inPin > 0) pin = inPin; - channel = get_pwm_channel(pin, 50u, 16u); + channel = get_pwm_channel(pin, 50U, 16U); return channel; // -1 if no PWM avail. } diff --git a/Marlin/src/HAL/ESP32/Tone.cpp b/Marlin/src/HAL/ESP32/Tone.cpp index 839c612b6a87..1bb084012988 100644 --- a/Marlin/src/HAL/ESP32/Tone.cpp +++ b/Marlin/src/HAL/ESP32/Tone.cpp @@ -24,7 +24,7 @@ /** * Description: Tone function for ESP32 - * Derived from https://forum.arduino.cc/index.php?topic=136500.msg2903012#msg2903012 + * Derived from https://forum.arduino.cc/t/arduino-due-and-tone/133302/13 */ #ifdef ARDUINO_ARCH_ESP32 diff --git a/Marlin/src/HAL/ESP32/endstop_interrupts.h b/Marlin/src/HAL/ESP32/endstop_interrupts.h index 137734599295..8b88f1f41f33 100644 --- a/Marlin/src/HAL/ESP32/endstop_interrupts.h +++ b/Marlin/src/HAL/ESP32/endstop_interrupts.h @@ -59,6 +59,7 @@ void setup_endstop_interrupts() { TERN_(USE_Z4_MAX, _ATTACH(Z4_MAX_PIN)); TERN_(USE_Z4_MIN, _ATTACH(Z4_MIN_PIN)); TERN_(USE_Z_MIN_PROBE, _ATTACH(Z_MIN_PROBE_PIN)); + TERN_(USE_CALIBRATION, _ATTACH(CALIBRATION_PIN)); TERN_(USE_I_MAX, _ATTACH(I_MAX_PIN)); TERN_(USE_I_MIN, _ATTACH(I_MIN_PIN)); TERN_(USE_J_MAX, _ATTACH(J_MAX_PIN)); diff --git a/Marlin/src/HAL/ESP32/fastio.h b/Marlin/src/HAL/ESP32/fastio.h index c8e3f7e343e9..a85423d76812 100644 --- a/Marlin/src/HAL/ESP32/fastio.h +++ b/Marlin/src/HAL/ESP32/fastio.h @@ -37,6 +37,10 @@ // Set pin as output #define _SET_OUTPUT(IO) pinMode(IO, OUTPUT) +// TODO: Store set modes in an array and use those to get the mode +#define _IS_OUTPUT(IO) true +#define _IS_INPUT(IO) true + // Set pin as input with pullup mode #define _PULLUP(IO, v) pinMode(IO, v ? INPUT_PULLUP : INPUT) @@ -70,6 +74,9 @@ // Set pin as output and init #define OUT_WRITE(IO,V) do{ _SET_OUTPUT(IO); WRITE(IO,V); }while(0) +#define IS_OUTPUT(IO) _IS_OUTPUT(IO) +#define IS_INPUT(IO) _IS_INPUT(IO) + // digitalRead/Write wrappers #define extDigitalRead(IO) digitalRead(IO) #define extDigitalWrite(IO,V) digitalWrite(IO,V) diff --git a/Marlin/src/HAL/ESP32/i2s.cpp b/Marlin/src/HAL/ESP32/i2s.cpp index 5404c3e9e2de..f7c01730b399 100644 --- a/Marlin/src/HAL/ESP32/i2s.cpp +++ b/Marlin/src/HAL/ESP32/i2s.cpp @@ -145,49 +145,54 @@ static void IRAM_ATTR i2s_intr_handler_default(void *arg) { void stepperTask(void *parameter) { uint32_t nextMainISR = 0; #if ENABLED(LIN_ADVANCE) - uint32_t nextAdvanceISR = Stepper::LA_ADV_NEVER; + uint32_t nextAdvanceISR = stepper.LA_ADV_NEVER; #endif for (;;) { 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) { - #if ENABLED(FT_MOTION) + if (using_ftMotion) { - if (using_ftMotion) { + #if ENABLED(FT_MOTION) if (!nextMainISR) stepper.ftMotion_stepper(); nextMainISR = 0; - } + #endif - #endif + } + else { - if (!using_ftMotion) { - if (!nextMainISR) { - Stepper::pulse_phase_isr(); - nextMainISR = Stepper::block_phase_isr(); - } - #if ENABLED(LIN_ADVANCE) - else if (!nextAdvanceISR) { - Stepper::advance_isr(); - nextAdvanceISR = Stepper::la_interval; + #if HAS_STANDARD_MOTION + + if (!nextMainISR) { + stepper.pulse_phase_isr(); + nextMainISR = stepper.block_phase_isr(); } - #endif - else - i2s_push_sample(); + #if ENABLED(LIN_ADVANCE) + else if (!nextAdvanceISR) { + stepper.advance_isr(); + nextAdvanceISR = stepper.la_interval; + } + #endif + else + i2s_push_sample(); - nextMainISR--; + nextMainISR--; - #if ENABLED(LIN_ADVANCE) - if (nextAdvanceISR == Stepper::LA_ADV_NEVER) - nextAdvanceISR = Stepper::la_interval; + #if ENABLED(LIN_ADVANCE) + if (nextAdvanceISR == stepper.LA_ADV_NEVER) + nextAdvanceISR = stepper.la_interval; + + if (nextAdvanceISR && nextAdvanceISR != stepper.LA_ADV_NEVER) + nextAdvanceISR--; + #endif + + #endif // HAS_STANDARD_MOTION - if (nextAdvanceISR && nextAdvanceISR != Stepper::LA_ADV_NEVER) - nextAdvanceISR--; - #endif } } } diff --git a/Marlin/src/HAL/ESP32/inc/SanityCheck.h b/Marlin/src/HAL/ESP32/inc/SanityCheck.h index dd199c390ded..7914fb847332 100644 --- a/Marlin/src/HAL/ESP32/inc/SanityCheck.h +++ b/Marlin/src/HAL/ESP32/inc/SanityCheck.h @@ -57,7 +57,7 @@ #endif #if ALL(I2S_STEPPER_STREAM, LIN_ADVANCE) && DISABLED(EXPERIMENTAL_I2S_LA) - #error "I2S stream is currently incompatible with LIN_ADVANCE." + #error "I2S stream is currently incompatible with LIN_ADVANCE. Enable EXPERIMENTAL_I2S_LA to proceed anyway." #endif #if ALL(I2S_STEPPER_STREAM, PRINTCOUNTER) && PRINTCOUNTER_SAVE_INTERVAL > 0 && DISABLED(PRINTCOUNTER_SYNC) diff --git a/Marlin/src/HAL/ESP32/pinsDebug.h b/Marlin/src/HAL/ESP32/pinsDebug.h new file mode 100644 index 000000000000..42304b2a0be0 --- /dev/null +++ b/Marlin/src/HAL/ESP32/pinsDebug.h @@ -0,0 +1,71 @@ +/** + * 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 . + * + */ +#pragma once + +#error "PINS_DEBUGGING is not yet supported for ESP32!" + +/** + * Pins Debugging for ESP32 + * + * - NUMBER_PINS_TOTAL + * - MULTI_NAME_PAD + * - getPinByIndex(index) + * - printPinNameByIndex(index) + * - getPinIsDigitalByIndex(index) + * - digitalPinToAnalogIndex(pin) + * - getValidPinMode(pin) + * - isValidPin(pin) + * - isAnalogPin(pin) + * - digitalRead_mod(pin) + * - pwm_status(pin) + * - printPinPWM(pin) + * - printPinPort(pin) + * - printPinNumber(pin) + * - printPinAnalog(pin) + */ + +#define NUMBER_PINS_TOTAL NUM_DIGITAL_PINS +#define MULTI_NAME_PAD 16 // space needed to be pretty if not first name assigned to a pin + +#define digitalRead_mod(P) extDigitalRead(P) +#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(P)); SERIAL_ECHO(buffer); }while(0) +#define getPinByIndex(x) pin_array[x].pin +#define getPinIsDigitalByIndex(x) pin_array[x].is_digital +#define isValidPin(P) (P >= 0 && P < pin_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))) +bool pwm_status(const pin_t) { return false; } + +void printPinPort(const pin_t) {} + +static bool getValidPinMode(const pin_t pin) { + return isValidPin(pin) && !IS_INPUT(pin); +} + +void printPinPWM(const int32_t pin) { + if (pwm_status(pin)) { + //uint32_t chan = g_APinDescription[pin].ulPWMChannel TODO when fast pwm is operative; + //SERIAL_ECHOPGM("PWM = ", duty); + } +} diff --git a/Marlin/src/HAL/ESP32/spi_pins.h b/Marlin/src/HAL/ESP32/spi_pins.h index 58881f0ea7ec..b50ea725abdc 100644 --- a/Marlin/src/HAL/ESP32/spi_pins.h +++ b/Marlin/src/HAL/ESP32/spi_pins.h @@ -21,7 +21,16 @@ */ #pragma once -#define SD_SS_PIN SDSS -#define SD_SCK_PIN 18 -#define SD_MISO_PIN 19 -#define SD_MOSI_PIN 23 +#define PIN_SPI_SCK 18 +#define PIN_SPI_MISO 19 +#define PIN_SPI_MOSI 23 + +#ifndef SD_SCK_PIN + #define SD_SCK_PIN PIN_SPI_SCK +#endif +#ifndef SD_MISO_PIN + #define SD_MISO_PIN PIN_SPI_MISO +#endif +#ifndef SD_MOSI_PIN + #define SD_MOSI_PIN PIN_SPI_MOSI +#endif diff --git a/Marlin/src/HAL/ESP32/timers.cpp b/Marlin/src/HAL/ESP32/timers.cpp index a6893150b663..a2996a860faa 100644 --- a/Marlin/src/HAL/ESP32/timers.cpp +++ b/Marlin/src/HAL/ESP32/timers.cpp @@ -78,8 +78,8 @@ void IRAM_ATTR timer_isr(void *para) { /** * Enable and initialize the timer - * @param timer_num timer number to initialize - * @param frequency frequency of the timer + * @param timer_num timer number to initialize + * @param frequency frequency of the timer */ void HAL_timer_start(const uint8_t timer_num, const uint32_t frequency) { const tTimerConfig timer = timer_config[timer_num]; @@ -90,7 +90,7 @@ void HAL_timer_start(const uint8_t timer_num, const uint32_t frequency) { config.counter_en = TIMER_PAUSE; config.alarm_en = TIMER_ALARM_EN; config.intr_type = TIMER_INTR_LEVEL; - config.auto_reload = true; + config.auto_reload = TIMER_AUTORELOAD_EN; // Select and initialize the timer timer_init(timer.group, timer.idx, &config); diff --git a/Marlin/src/HAL/ESP32/timers.h b/Marlin/src/HAL/ESP32/timers.h index aa4e1551f066..d656e92224b0 100644 --- a/Marlin/src/HAL/ESP32/timers.h +++ b/Marlin/src/HAL/ESP32/timers.h @@ -30,42 +30,45 @@ #define FORCE_INLINE __attribute__((always_inline)) inline typedef uint64_t hal_timer_t; -#define HAL_TIMER_TYPE_MAX 0xFFFFFFFFFFFFFFFFULL +#define HAL_TIMER_TYPE_MAX hal_timer_t(UINT64_MAX) #ifndef MF_TIMER_STEP #define MF_TIMER_STEP 0 // Timer Index for Stepper #endif #ifndef MF_TIMER_PULSE - #define MF_TIMER_PULSE MF_TIMER_STEP + #define MF_TIMER_PULSE MF_TIMER_STEP // Timer Index for Pulse interval #endif #ifndef MF_TIMER_TEMP #define MF_TIMER_TEMP 1 // Timer Index for Temperature #endif #ifndef MF_TIMER_PWM - #define MF_TIMER_PWM 2 // index of timer to use for PWM outputs + #define MF_TIMER_PWM 2 // Timer Index for PWM outputs #endif #ifndef MF_TIMER_TONE - #define MF_TIMER_TONE 3 // index of timer for beeper tones + #define MF_TIMER_TONE 3 // Timer Index for beeper tones #endif -#define HAL_TIMER_RATE APB_CLK_FREQ // frequency of timer peripherals +#define HAL_TIMER_RATE APB_CLK_FREQ // Frequency of timer peripherals + +#define TEMP_TIMER_PRESCALE 1000 // Prescaler for setting Temp Timer, 72Khz +#define TEMP_TIMER_FREQUENCY 1000 // (Hz) Temperature ISR frequency #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 + #define STEPPER_TIMER_RATE 250'000 // 250khz, 4µs pulses of i2s word clock + #define STEPPER_TIMER_TICKS_PER_US 0.25 // (MHz) Stepper Timer ticks per µs #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 + #define STEPPER_TIMER_RATE ((HAL_TIMER_RATE) / (STEPPER_TIMER_PRESCALE)) // (Hz) Frequency of Stepper Timer ISR, 2MHz + #define STEPPER_TIMER_TICKS_PER_US ((STEPPER_TIMER_RATE) / 1'000'000UL) // (MHz) Stepper Timer ticks per µs #endif #define STEP_TIMER_MIN_INTERVAL 8 // minimum time in µs between stepper interrupts -#define TONE_TIMER_PRESCALE 1000 // Arbitrary value, no idea what i'm doing here +#define PULSE_TIMER_RATE STEPPER_TIMER_RATE // (Hz) Frequency of Pulse Timer +#define PULSE_TIMER_PRESCALE STEPPER_TIMER_PRESCALE -#define TEMP_TIMER_PRESCALE 1000 // prescaler for setting Temp timer, 72Khz -#define TEMP_TIMER_FREQUENCY 1000 // temperature interrupt frequency +#define TONE_TIMER_PRESCALE 1000 // Arbitrary value, no idea what i'm doing here #define PWM_TIMER_PRESCALE 10 #if ENABLED(FAST_PWM_FAN) @@ -75,13 +78,9 @@ typedef uint64_t hal_timer_t; #endif #define MAX_PWM_PINS 32 // Number of PWM pin-slots -#define PULSE_TIMER_RATE STEPPER_TIMER_RATE // frequency of pulse timer -#define PULSE_TIMER_PRESCALE STEPPER_TIMER_PRESCALE -#define PULSE_TIMER_TICKS_PER_US STEPPER_TIMER_TICKS_PER_US - -#define ENABLE_STEPPER_DRIVER_INTERRUPT() HAL_timer_enable_interrupt(MF_TIMER_STEP) +#define ENABLE_STEPPER_DRIVER_INTERRUPT() HAL_timer_enable_interrupt(MF_TIMER_STEP) #define DISABLE_STEPPER_DRIVER_INTERRUPT() HAL_timer_disable_interrupt(MF_TIMER_STEP) -#define STEPPER_ISR_ENABLED() HAL_timer_interrupt_enabled(MF_TIMER_STEP) +#define STEPPER_ISR_ENABLED() HAL_timer_interrupt_enabled(MF_TIMER_STEP) #define ENABLE_TEMPERATURE_INTERRUPT() HAL_timer_enable_interrupt(MF_TIMER_TEMP) #define DISABLE_TEMPERATURE_INTERRUPT() HAL_timer_disable_interrupt(MF_TIMER_TEMP) @@ -136,5 +135,5 @@ void HAL_timer_enable_interrupt(const uint8_t timer_num); void HAL_timer_disable_interrupt(const uint8_t timer_num); bool HAL_timer_interrupt_enabled(const uint8_t timer_num); -#define HAL_timer_isr_prologue(T) NOOP -#define HAL_timer_isr_epilogue(T) NOOP +inline void HAL_timer_isr_prologue(const uint8_t) {} +inline void HAL_timer_isr_epilogue(const uint8_t) {} diff --git a/Marlin/src/HAL/ESP32/u8g_esp32_spi.cpp b/Marlin/src/HAL/ESP32/u8g_esp32_spi.cpp index a4c1980adc73..42903da9d343 100644 --- a/Marlin/src/HAL/ESP32/u8g_esp32_spi.cpp +++ b/Marlin/src/HAL/ESP32/u8g_esp32_spi.cpp @@ -35,7 +35,7 @@ #if HAS_MEDIA #include "../../sd/cardreader.h" #if ENABLED(ESP3D_WIFISUPPORT) - #include "sd_ESP32.h" + #include #endif #endif diff --git a/Marlin/src/HAL/ESP32/WebSocketSerial.cpp b/Marlin/src/HAL/ESP32/wifi/WebSocketSerial.cpp similarity index 99% rename from Marlin/src/HAL/ESP32/WebSocketSerial.cpp rename to Marlin/src/HAL/ESP32/wifi/WebSocketSerial.cpp index eb5b9d60395a..9c8933289d4d 100644 --- a/Marlin/src/HAL/ESP32/WebSocketSerial.cpp +++ b/Marlin/src/HAL/ESP32/wifi/WebSocketSerial.cpp @@ -21,7 +21,7 @@ */ #ifdef ARDUINO_ARCH_ESP32 -#include "../../inc/MarlinConfigPre.h" +#include "../../../inc/MarlinConfigPre.h" #if ENABLED(WIFISUPPORT) diff --git a/Marlin/src/HAL/ESP32/WebSocketSerial.h b/Marlin/src/HAL/ESP32/wifi/WebSocketSerial.h similarity index 96% rename from Marlin/src/HAL/ESP32/WebSocketSerial.h rename to Marlin/src/HAL/ESP32/wifi/WebSocketSerial.h index 6b3e419d10c5..3d2fdf1e6ab1 100644 --- a/Marlin/src/HAL/ESP32/WebSocketSerial.h +++ b/Marlin/src/HAL/ESP32/wifi/WebSocketSerial.h @@ -21,8 +21,8 @@ */ #pragma once -#include "../../inc/MarlinConfig.h" -#include "../../core/serial_hook.h" +#include "../../../inc/MarlinConfig.h" +#include "../../../core/serial_hook.h" #include diff --git a/Marlin/src/HAL/ESP32/ota.cpp b/Marlin/src/HAL/ESP32/wifi/ota.cpp similarity index 96% rename from Marlin/src/HAL/ESP32/ota.cpp rename to Marlin/src/HAL/ESP32/wifi/ota.cpp index 01f5924871ff..03508840a5c6 100644 --- a/Marlin/src/HAL/ESP32/ota.cpp +++ b/Marlin/src/HAL/ESP32/wifi/ota.cpp @@ -22,11 +22,15 @@ #ifdef ARDUINO_ARCH_ESP32 -#include "../../inc/MarlinConfigPre.h" +#include + +#undef ENABLED +#undef DISABLED + +#include "../../../inc/MarlinConfigPre.h" #if ALL(WIFISUPPORT, OTASUPPORT) -#include #include #include #include diff --git a/Marlin/src/HAL/ESP32/ota.h b/Marlin/src/HAL/ESP32/wifi/ota.h similarity index 100% rename from Marlin/src/HAL/ESP32/ota.h rename to Marlin/src/HAL/ESP32/wifi/ota.h diff --git a/Marlin/src/HAL/ESP32/spiffs.cpp b/Marlin/src/HAL/ESP32/wifi/spiffs.cpp similarity index 94% rename from Marlin/src/HAL/ESP32/spiffs.cpp rename to Marlin/src/HAL/ESP32/wifi/spiffs.cpp index 043ad7849adb..ba891acda9b6 100644 --- a/Marlin/src/HAL/ESP32/spiffs.cpp +++ b/Marlin/src/HAL/ESP32/wifi/spiffs.cpp @@ -21,11 +21,11 @@ */ #ifdef ARDUINO_ARCH_ESP32 -#include "../../inc/MarlinConfigPre.h" +#include "../../../inc/MarlinConfigPre.h" #if ALL(WIFISUPPORT, WEBSUPPORT) -#include "../../core/serial.h" +#include "../../../core/serial.h" #include #include diff --git a/Marlin/src/HAL/ESP32/spiffs.h b/Marlin/src/HAL/ESP32/wifi/spiffs.h similarity index 100% rename from Marlin/src/HAL/ESP32/spiffs.h rename to Marlin/src/HAL/ESP32/wifi/spiffs.h diff --git a/Marlin/src/HAL/ESP32/web.cpp b/Marlin/src/HAL/ESP32/wifi/web.cpp similarity index 94% rename from Marlin/src/HAL/ESP32/web.cpp rename to Marlin/src/HAL/ESP32/wifi/web.cpp index 63a101595ff7..f08dfd87c074 100644 --- a/Marlin/src/HAL/ESP32/web.cpp +++ b/Marlin/src/HAL/ESP32/wifi/web.cpp @@ -21,11 +21,11 @@ */ #ifdef ARDUINO_ARCH_ESP32 -#include "../../inc/MarlinConfigPre.h" +#include "../../../inc/MarlinConfigPre.h" #if ALL(WIFISUPPORT, WEBSUPPORT) -#include "../../inc/MarlinConfig.h" +#include "../../../inc/MarlinConfig.h" #undef DISABLED // esp32-hal-gpio.h #include diff --git a/Marlin/src/HAL/ESP32/web.h b/Marlin/src/HAL/ESP32/wifi/web.h similarity index 100% rename from Marlin/src/HAL/ESP32/web.h rename to Marlin/src/HAL/ESP32/wifi/web.h diff --git a/Marlin/src/HAL/ESP32/wifi.cpp b/Marlin/src/HAL/ESP32/wifi/wifi.cpp similarity index 96% rename from Marlin/src/HAL/ESP32/wifi.cpp rename to Marlin/src/HAL/ESP32/wifi/wifi.cpp index 060f3bdb4874..1f31ca1bb95d 100644 --- a/Marlin/src/HAL/ESP32/wifi.cpp +++ b/Marlin/src/HAL/ESP32/wifi/wifi.cpp @@ -21,11 +21,12 @@ */ #ifdef ARDUINO_ARCH_ESP32 -#include "../../core/serial.h" -#include "../../inc/MarlinConfigPre.h" +#include "../../../inc/MarlinConfigPre.h" #if ENABLED(WIFISUPPORT) +#include "../../../core/serial.h" + #include #include #include diff --git a/Marlin/src/HAL/ESP32/wifi.h b/Marlin/src/HAL/ESP32/wifi/wifi.h similarity index 100% rename from Marlin/src/HAL/ESP32/wifi.h rename to Marlin/src/HAL/ESP32/wifi/wifi.h diff --git a/Marlin/src/HAL/GD32_MFL/AGENTS.md b/Marlin/src/HAL/GD32_MFL/AGENTS.md new file mode 100644 index 000000000000..bbf498ffeb0b --- /dev/null +++ b/Marlin/src/HAL/GD32_MFL/AGENTS.md @@ -0,0 +1,60 @@ +# AGENTS.md — GD32_MFL HAL + +Working notes and conventions for the **GD32_MFL HAL** folder (`Marlin/src/HAL/GD32_MFL/`). + +This HAL targets GigaDevice GD32F1/GD32F3 MCUs via the third-party **MFL Arduino Core** (`https://github.com/bnmguy/ArduinoCore_MFL`), not the older Maple/STM32duino cores. It currently supports the GD32F303RE (Creality V4.2.x) and GD32F103RC (Aquila v1.0.1) boards. + +## Build & Test Loop + +Always validate with `mftest` (it regenerates `Marlin/Configuration.h` per target, which raw `pio run -e` does not do reliably): + +```bash +cd "$(git rev-parse --show-toplevel)" +buildroot/bin/mftest -t GD32F303RE_creality_mfl -n1 -y # GD32F303RE, 512 KB flash +buildroot/bin/mftest -t GD32F103RC_aquila_mfl -n1 -y # GD32F103RC, 256 KB flash +``` + +The Aquila (`GD32F103RC`, 256 KB) build is the tighter flash constraint — if it fits there, the Creality (512 KB) build will too. Both use a 0x7000 bootloader offset, so the linked firmware image reserves space below 0x08007000. + +> Do NOT use `rm -rf .pio/build/...` to force a rebuild — the cross-profile write guard blocks it and it is unsafe. Use `pio run -e -t clean` (removes only build artifacts) or just let `mftest` rebuild. + +## Framework / Platform Integration (upstream, not vendored) + +The MFL framework is **upstream**, pulled from a release ZIP (`platform = https://github.com/bmourit/platform-mfl/archive/refs/tags/V1.0.4.zip`) in `ini/gd32.ini`. It is **not** Marlin-vendored like AT32 — there is no `buildroot/share/PlatformIO/framework-arduino-*` to keep in sync, so the AT32 "mirror to installed package" warning does NOT apply. Marlin-specific glue lives entirely in this `HAL/GD32_MFL/` folder and in `ini/gd32.ini` / `buildroot/share/PlatformIO/` scripts (e.g. `offset_and_rename.py`). + +Key `gd32_base` settings (`ini/gd32.ini`): + +- `board_build.core = gd32`, C++23 (`-std=gnu++23`; `-std=gnu++11/14/17` stripped). +- `-DARDUINO_ARCH_MFL` (the compiler guard macro), `-DPLATFORM_M997_SUPPORT`, `-DTIMER_IRQ_PRIORITY=12`, `-DADC_RESOLUTION=12`, `-DCORE_DEBUG`. +- Per-board: `-DGD32F303RE` / `-DGD32F103RC`, `-DTIMER_TONE=2 -DSS_TIMER=3 -DTIMER_SERVO=4 -DTRANSFER_CLOCK_DIV=8`, `board_build.offset = 0x7000`, `board_upload.offset_address = 0x08007000`, `board_build.rename = firmware-{time}.bin`. + +### Gotchas that are not obvious from the source + +1. **Pins must be numeric `#define`s, not `enum`s.** `inc/SanityCheck.h` (lines 27–29) hard-errors `#ifndef PA0` with _"Your ARM platform pins are not defined as macros, only as enums!"_. The MFL core's `variant.h` / `pins_arduino.h` (included via `fastio.h` and `HAL.h`) supplies `PA0`, `PC6`, … as macros. **If that error appears, fix it at the pin-define level (add the missing `#define` to the variant), never by deleting the SanityCheck guard.** This is the same class of bug that bit AT32. + +2. **0x7000 bootloader offset is mandatory.** Both boards link with `board_build.offset = 0x7000` and upload to `0x08007000`; the `offset_and_rename.py` extra script also renames the artifact to `firmware-{time}.bin`. Removing the offset / the script bricks the image (it would overwrite the bootloader). Keep them. + +3. **EEPROM backend is per-board / fallback, not auto-picked like STM32.** `inc/Conditionals_post.h` falls back to `SDCARD_EEPROM_EMULATION` when no real or emulated EEPROM is selected, and maps `I2C_EEPROM`/`SPI_EEPROM` → `USE_SHARED_EEPROM`. `FLASH_EEPROM_LEVELING` is explicitly unsupported (SanityCheck error). The two test boards enable `SDSUPPORT` + `EEPROM_SETTINGS`, so the fallback SD emulation is what is exercised — make sure `SDSUPPORT` is on wherever `SDCARD_EEPROM_EMULATION` is used. + +4. **Serial uses hardware USART, not USB-CDC.** `MarlinSerial` wraps the MFL `UsartSerial` (`MarlinSerial.h`); `SERIAL_INDEX_MIN/MAX` = 0..4. `EMERGENCY_PARSER` works via `register_emergency_callback()`. There is no USB-CDC path for Marlin — do not add one. `SERIAL_STATS_MAX_RX_QUEUED` and `SERIAL_STATS_DROPPED_RX` are explicitly unsupported (SanityCheck errors) — don't enable them. + +5. **ADC defaults to 16Ɨ hardware oversampling.** The MFL framework oversamples by default; for `GD32F303RE` the HAL sets `HAL_ADC_FILTERED`. `HAL_ADC_RESOLUTION` is 12 (from `-DADC_RESOLUTION=12`). Software oversampling in Marlin is disabled to avoid double-counting. + +6. **C++23 only.** `gd32_base` forces `-std=gnu++23` and unflags the older standards. Don't reintroduce `gnu++11/14/17` and don't use constructs that break under gnu++23. + +7. **Timer assignment is fixed per board.** `TIMER_TONE=2`, `SS_TIMER=3`, `TIMER_SERVO=4`, `TRANSFER_CLOCK_DIV=8`, `TIMER_IRQ_PRIORITY=12` are passed in `ini/gd32.ini`. Changing them shifts which hardware timers are used for toning, stepper ISR, and servo — verify pin/timer conflicts before editing. + +## Conventions + +- All HAL source is guarded by `#ifdef ARDUINO_ARCH_MFL` (defined in `ini/gd32.ini`, NOT by the core). +- Keep HAL changes in `Marlin/src/HAL/GD32_MFL/` and board pin changes in `Marlin/src/pins/gd32f1/` (Aquila) and `Marlin/src/pins/gd32f3/` (Creality). The Creality/Aquila pins files `#include` the matching STM32F1 Creality pins and add `ALLOW_GD32F3` — they are thin overlays, not full pin lists. +- `MarlinSPI = SPIClass` (from the MFL ``); no special SPI glue is needed. +- `M997` in-application firmware update is supported (`PLATFORM_M997_SUPPORT`, `flashFirmware()` in `HAL.cpp`). +- Pin mappings reuse the STM32F1 Creality layout; treat them as verified for those boards only and don't "fix" them to match guesses without a source. + +## Related + +- `README.md` in this folder — overview and supported boards. +- Wiki: [[hal-gd32-mfl]], [[marlin-src-hal]]. +- `Marlin/src/HAL/shared/` — shared HAL APIs (`eeprom_api`, SPI helpers) this HAL reuses. +- `ini/gd32.ini` — the env definitions and build flags for this family. diff --git a/Marlin/src/HAL/GD32_MFL/HAL.cpp b/Marlin/src/HAL/GD32_MFL/HAL.cpp new file mode 100644 index 000000000000..9ba20784f044 --- /dev/null +++ b/Marlin/src/HAL/GD32_MFL/HAL.cpp @@ -0,0 +1,123 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2025 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 . + * + */ + +#include "../platforms.h" + +#ifdef ARDUINO_ARCH_MFL + +#include "../../inc/MarlinConfig.h" +#include "../shared/Delay.h" + +uint16_t MarlinHAL::adc_result; + +#if ENABLED(POSTMORTEM_DEBUGGING) + extern void install_min_serial(); +#endif + +#if ENABLED(MARLIN_DEV_MODE) + // Dump the clock frequencies of the system, AHB, APB1, APB2, and F_CPU. + static inline void HAL_clock_frequencies_dump() { + auto& rcuInstance = rcu::RCU::get_instance(); + uint32_t freq = rcuInstance.get_clock_frequency(rcu::Clock_Frequency::CK_SYS); + SERIAL_ECHOPGM("\nSYSTEM_CLOCK=", freq); + freq = rcuInstance.get_clock_frequency(rcu::Clock_Frequency::CK_AHB); + SERIAL_ECHOPGM("\nABH_CLOCK=", freq); + freq = rcuInstance.get_clock_frequency(rcu::Clock_Frequency::CK_APB1); + SERIAL_ECHOPGM("\nAPB1_CLOCK=", freq); + freq = rcuInstance.get_clock_frequency(rcu::Clock_Frequency::CK_APB2); + SERIAL_ECHOPGM("\nAPB2_CLOCK=", freq, + "\nF_CPU=", F_CPU); + // Done + SERIAL_ECHOPGM("\n--\n"); + } +#endif // MARLIN_DEV_MODE + +// Initializes the Marlin HAL +void MarlinHAL::init() { + // Ensure F_CPU is a constant expression. + // If the compiler breaks here, it means that delay code that should compute at compile time will not work. + // So better safe than sorry here. + constexpr unsigned int cpuFreq = F_CPU; + UNUSED(cpuFreq); + + #if PIN_EXISTS(LED) + OUT_WRITE(LED_PIN, LOW); + #endif + + SetTimerInterruptPriorities(); + + // Print clock frequencies to host serial + TERN_(MARLIN_DEV_MODE, HAL_clock_frequencies_dump()); + + // Register min serial + TERN_(POSTMORTEM_DEBUGGING, install_min_serial()); +} + +// Returns the reset source based on the flags set in the RCU module +uint8_t MarlinHAL::get_reset_source() { + return + (RCU_I.get_flag(rcu::Status_Flags::FLAG_FWDGTRST)) ? RST_WATCHDOG : + (RCU_I.get_flag(rcu::Status_Flags::FLAG_SWRST)) ? RST_SOFTWARE : + (RCU_I.get_flag(rcu::Status_Flags::FLAG_EPRST)) ? RST_EXTERNAL : + (RCU_I.get_flag(rcu::Status_Flags::FLAG_PORRST)) ? RST_POWER_ON : + (RCU_I.get_flag(rcu::Status_Flags::FLAG_LPRST)) ? RST_BROWN_OUT : + 0; +} + +// Returns the amount of free memory available in bytes +int MarlinHAL::freeMemory() { + volatile char top; + return &top - reinterpret_cast(_sbrk(0)); +} + +// Watchdog Timer +#if ENABLED(USE_WATCHDOG) + #define WDT_TIMEOUT_US TERN(WATCHDOG_DURATION_8S, 8000000, 4000000) // 4 or 8 second timeout + + #include + + FWatchdogTimer& watchdogTimer = FWatchdogTimer::get_instance(); + + // Initializes the watchdog timer + void MarlinHAL::watchdog_init() { + IF_DISABLED(DISABLE_WATCHDOG_INIT, watchdogTimer.begin(WDT_TIMEOUT_US)); + } + + // Refreshes the watchdog timer to prevent system reset + void MarlinHAL::watchdog_refresh() { + watchdogTimer.reload(); + #if DISABLED(PINS_DEBUGGING) && PIN_EXISTS(LED) + TOGGLE(LED_PIN); // Heartbeat indicator + #endif + } +#endif + +extern "C" { + extern unsigned int _ebss; // End of bss section +} + +// Resets the system to initiate a firmware flash. +WEAK void flashFirmware(const int16_t) { + hal.reboot(); +} + +#endif // ARDUINO_ARCH_MFL diff --git a/Marlin/src/HAL/GD32_MFL/HAL.h b/Marlin/src/HAL/GD32_MFL/HAL.h new file mode 100644 index 000000000000..61952120283a --- /dev/null +++ b/Marlin/src/HAL/GD32_MFL/HAL.h @@ -0,0 +1,159 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2025 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 . + * + */ +#pragma once + +#define CPU_32_BIT + +#include "../../core/macros.h" +#include "../shared/Marduino.h" +#include "../shared/math_32bit.h" +#include "../shared/HAL_SPI.h" + +#include "temp_soc.h" +#include "fastio.h" +#include "Servo.h" + +#include "../../inc/MarlinConfigPre.h" + +#include +#include +#include + +// Default graphical display delays +#define CPU_ST7920_DELAY_1 300 +#define CPU_ST7920_DELAY_2 40 +#define CPU_ST7920_DELAY_3 340 + +// Serial Ports +#include "MarlinSerial.h" + +// Interrupts +#define CRITICAL_SECTION_START() const bool irqon = !__get_PRIMASK(); __disable_irq() +#define CRITICAL_SECTION_END() if (irqon) __enable_irq() + +#define cli() __disable_irq() +#define sei() __enable_irq() + +// Alias of __bss_end__ +#define __bss_end __bss_end__ + +// Types +typedef uint8_t pin_t; // Parity with mfl platform + +// Servo +class libServo; +typedef libServo hal_servo_t; +#define PAUSE_SERVO_OUTPUT() libServo::pause_all_servos() +#define RESUME_SERVO_OUTPUT() libServo::resume_all_servos() + +// Debugging +#define JTAG_DISABLE() AFIO_I.set_remap(gpio::Pin_Remap_Select::SWJ_DP_ONLY_REMAP) +#define JTAGSWD_DISABLE() AFIO_I.set_remap(gpio::Pin_Remap_Select::SWJ_ALL_DISABLED_REMAP) +#define JTAGSWD_RESET() AFIO_I.set_remap(gpio::Pin_Remap_Select::FULL_SWJ_REMAP) + +// ADC +#ifdef ADC_RESOLUTION + #define HAL_ADC_RESOLUTION ADC_RESOLUTION +#else + #define HAL_ADC_RESOLUTION 12 +#endif + +#define HAL_ADC_VREF_MV 3300 + +// Disable Marlin's software oversampling. +// The MFL framework uses 16x hardware oversampling by default +#ifdef GD32F303RE + #define HAL_ADC_FILTERED +#endif + +#define GET_PIN_MAP_PIN(index) index +#define GET_PIN_MAP_INDEX(pin) pin +#define PARSED_PIN_INDEX(code, dval) parser.intval(code, dval) + +#ifndef PLATFORM_M997_SUPPORT + #define PLATFORM_M997_SUPPORT +#endif + +void flashFirmware(const int16_t); + +#define HAL_CAN_SET_PWM_FREQ // This HAL supports PWM Frequency adjustment + +extern "C" char* _sbrk(int incr); +extern "C" char* dtostrf(double val, signed char width, unsigned char prec, char* sout); + +// MarlinHAL Class +class MarlinHAL { +public: + // Before setup() + MarlinHAL() = default; + + // Watchdog + static void watchdog_init() IF_DISABLED(USE_WATCHDOG, {}); + static void watchdog_refresh() IF_DISABLED(USE_WATCHDOG, {}); + + static void init(); // called early in setup() + static void init_board() {} // called less early in setup() + static void reboot() { NVIC_SystemReset(); } // restart the firmware from 0x0 + + // Interrupts + static bool isr_state() { return !__get_PRIMASK(); } + static void isr_on() { sei(); } + static void isr_off() { cli(); } + static void delay_ms(const int ms) { delay(ms); } + + // Tasks called from idle() + static void idletask() {} + + // Reset + static uint8_t get_reset_source(); + static void clear_reset_source() { RCU_I.clear_all_reset_flags(); } + + // Free SRAM + static int freeMemory(); + + // ADC methods + static uint16_t adc_result; + + // Called by Temperature::init once at startup + static void adc_init() { analogReadResolution(HAL_ADC_RESOLUTION); } + + // Called by Temperature::init for each sensor at startup + static void adc_enable(const pin_t pin) { pinMode(pin, INPUT); } + + // Called from Temperature::isr to start ADC sampling on the given pin + static void adc_start(const pin_t pin) { adc_result = static_cast(analogRead(pin)); } + + // Check if ADC is ready for reading + static bool adc_ready() { return true; } + + // Current value of the ADC register + static uint16_t adc_value() { return adc_result; } + + // Set the PWM duty cycle for the pin to the given value. + // Optionally invert the duty cycle [default = false] + // Optionally change the maximum size of the provided value to enable finer PWM duty control [default = 255] + static void set_pwm_duty(const pin_t pin, const uint16_t value, const uint16_t scale = 255U, const bool invert = false); + + // Set the frequency of the timer for the given pin. + // All Timer PWM pins run at the same frequency. + static void set_pwm_frequency(const pin_t pin, const uint16_t f_desired); +}; diff --git a/Marlin/src/HAL/GD32_MFL/MarlinSPI.h b/Marlin/src/HAL/GD32_MFL/MarlinSPI.h new file mode 100644 index 000000000000..d0731f9e8480 --- /dev/null +++ b/Marlin/src/HAL/GD32_MFL/MarlinSPI.h @@ -0,0 +1,26 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2025 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 . + * + */ +#pragma once + +#include + +using MarlinSPI = SPIClass; diff --git a/Marlin/src/HAL/GD32_MFL/MarlinSerial.cpp b/Marlin/src/HAL/GD32_MFL/MarlinSerial.cpp new file mode 100644 index 000000000000..95ea8bea25dc --- /dev/null +++ b/Marlin/src/HAL/GD32_MFL/MarlinSerial.cpp @@ -0,0 +1,97 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2025 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 . + * + */ + +#include "../platforms.h" + +#ifdef ARDUINO_ARCH_MFL + +#include "../../inc/MarlinConfig.h" +#include "MarlinSerial.h" + +#if ENABLED(EMERGENCY_PARSER) + #include "../../feature/e_parser.h" +#endif + +using namespace arduino; + +auto MarlinSerial::get_instance(usart::USART_Base Base, pin_size_t rxPin, pin_size_t txPin) -> MarlinSerial& { + auto& serial = UsartSerial::get_instance(Base, rxPin, txPin); + return *reinterpret_cast(&serial); +} + +#if USING_HW_SERIAL0 + MSerialT MSerial0(true, MarlinSerial::get_instance(usart::USART_Base::USART0_BASE, NO_PIN, NO_PIN)); +#endif +#if USING_HW_SERIAL1 + MSerialT MSerial1(true, MarlinSerial::get_instance(usart::USART_Base::USART1_BASE, NO_PIN, NO_PIN)); +#endif +#if USING_HW_SERIAL2 + MSerialT MSerial2(true, MarlinSerial::get_instance(usart::USART_Base::USART2_BASE, NO_PIN, NO_PIN)); +#endif +#if USING_HW_SERIAL3 + MSerialT MSerial3(true, MarlinSerial::get_instance(usart::USART_Base::UART3_BASE, NO_PIN, NO_PIN)); +#endif +#if USING_HW_SERIAL4 + MSerialT MSerial4(true, MarlinSerial::get_instance(usart::USART_Base::UART4_BASE, NO_PIN, NO_PIN)); +#endif + +#if ENABLED(EMERGENCY_PARSER) + // This callback needs to access the specific MarlinSerial instance + // We'll use a static pointer to track the current instance + static MarlinSerial* current_serial_instance = nullptr; + + static void emergency_callback() { + if (!current_serial_instance) return; + const auto last_data = current_serial_instance->get_last_data(); + emergency_parser.update(current_serial_instance->emergency_state, last_data); + } + + void MarlinSerial::register_emergency_callback(void (*callback)()) { + usart_.register_interrupt_callback(usart::Interrupt_Type::INTR_RBNEIE, callback); + } +#endif + +void MarlinSerial::begin(unsigned long baudrate, uint16_t config) { + UsartSerial::begin(baudrate, config, ENABLED(SERIAL_DMA)); + #if ENABLED(EMERGENCY_PARSER) && DISABLED(SERIAL_DMA) + current_serial_instance = this; + register_emergency_callback(emergency_callback); + #endif +} + +void MarlinSerial::updateRxDmaBuffer() { + #if ENABLED(EMERGENCY_PARSER) + // Get the number of bytes available in the receive buffer + const size_t available_bytes = usart_.available_for_read(true); + + // Process only the available data + for (size_t i = 0; i < available_bytes; ++i) { + uint8_t data; + if (usart_.read_rx_buffer(data)) + emergency_parser.update(emergency_state, data); + } + #endif + // Call the base class implementation to handle any additional updates + UsartSerial::updateRxDmaBuffer(); +} + +#endif // ARDUINO_ARCH_MFL diff --git a/Marlin/src/HAL/GD32_MFL/MarlinSerial.h b/Marlin/src/HAL/GD32_MFL/MarlinSerial.h new file mode 100644 index 000000000000..6b33ce0e6136 --- /dev/null +++ b/Marlin/src/HAL/GD32_MFL/MarlinSerial.h @@ -0,0 +1,75 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2025 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 . + * + */ +#pragma once + +#include "../../inc/MarlinConfigPre.h" + +#if ENABLED(EMERGENCY_PARSER) + #include "../../feature/e_parser.h" +#endif + +#include + +#include "../../core/serial_hook.h" + +#define SERIAL_INDEX_MIN 0 +#define SERIAL_INDEX_MAX 4 + +#include "../shared/serial_ports.h" + +#if defined(LCD_SERIAL_PORT) && ANY(HAS_DGUS_LCD, EXTENSIBLE_UI) + #define LCD_SERIAL_TX_BUFFER_FREE() LCD_SERIAL.availableForWrite() +#endif + +using namespace arduino; + +struct MarlinSerial : public UsartSerial { + static auto get_instance(usart::USART_Base Base, pin_size_t rxPin = NO_PIN, pin_size_t txPin = NO_PIN) -> MarlinSerial&; + + void begin(unsigned long baudrate, uint16_t config); + inline void begin(unsigned long baudrate) { begin(baudrate, SERIAL_8N1); } + void updateRxDmaBuffer(); + + #if DISABLED(SERIAL_DMA) + FORCE_INLINE static uint8_t buffer_overruns() { return 0; } + #endif + + #if ENABLED(EMERGENCY_PARSER) + EmergencyParser::State emergency_state; + + // Accessor method to get the last received byte + auto get_last_data() -> uint8_t { return usart_.get_last_data(); } + + // Register the emergency callback + void register_emergency_callback(void (*callback)()); + #endif + +protected: + using UsartSerial::UsartSerial; +}; + +typedef Serial1Class MSerialT; +extern MSerialT MSerial0; +extern MSerialT MSerial1; +extern MSerialT MSerial2; +extern MSerialT MSerial3; +extern MSerialT MSerial4; diff --git a/Marlin/src/HAL/GD32_MFL/MinSerial.cpp b/Marlin/src/HAL/GD32_MFL/MinSerial.cpp new file mode 100644 index 000000000000..2cc79c8853b8 --- /dev/null +++ b/Marlin/src/HAL/GD32_MFL/MinSerial.cpp @@ -0,0 +1,163 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2025 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 . + * + */ +#include "../platforms.h" + +#ifdef ARDUINO_ARCH_MFL + +#include "../../inc/MarlinConfigPre.h" + +#if ENABLED(POSTMORTEM_DEBUGGING) +#include "../shared/MinSerial.h" + +// Base addresses for USART peripherals +static constexpr uintptr_t USART_base[] = { + 0x40013800, // USART0 + 0x40004400, // USART1 + 0x40004800, // USART2 + 0x40004C00, // UART3 + 0x40005000 // UART4 +}; + +// Register offsets +static constexpr uint32_t STAT0_OFFSET = 0x00U; +static constexpr uint32_t DATA_OFFSET = 0x04U; +static constexpr uint32_t BAUD_OFFSET = 0x08U; +static constexpr uint32_t CTL0_OFFSET = 0x0CU; +static constexpr uint32_t CTL1_OFFSET = 0x14U; + +// Bit positions +static constexpr uint32_t TBE_BIT = 7; +static constexpr uint32_t TEN_BIT = 3; +static constexpr uint32_t UEN_BIT = 13; + +// NVIC interrupt numbers for USART +static constexpr int nvicUART[] = { 37, 38, 39, 52, 53 }; + +// RCU PCLK values for USART +static constexpr rcu::RCU_PCLK clockRegs[] = { + rcu::RCU_PCLK::PCLK_USART0, + rcu::RCU_PCLK::PCLK_USART1, + rcu::RCU_PCLK::PCLK_USART2, + rcu::RCU_PCLK::PCLK_UART3, + rcu::RCU_PCLK::PCLK_UART4 +}; + +// Memory barrier instructions +#define isb() __asm__ __volatile__ ("isb" : : : "memory") +#define dsb() __asm__ __volatile__ ("dsb" : : : "memory") +#define sw_barrier() __asm__ volatile("" : : : "memory") + +// Direct register access macros +#define USART_REG(offset) (*(volatile uint32_t*)(USART_base[SERIAL_PORT] + (offset))) +#define USART_STAT0 USART_REG(STAT0_OFFSET) +#define USART_DATA USART_REG(DATA_OFFSET) +#define USART_BAUD USART_REG(BAUD_OFFSET) +#define USART_CTL0 USART_REG(CTL0_OFFSET) +#define USART_CTL1 USART_REG(CTL1_OFFSET) + +// Bit manipulation macros +#define READ_BIT(reg, bit) (((reg) >> (bit)) & 1U) +#define SET_BIT(reg, bit) ((reg) |= (1U << (bit))) +#define CLEAR_BIT(reg, bit) ((reg) &= ~(1U << (bit))) + +// Initializes the MinSerial interface. +// This function sets up the USART interface for serial communication. +// If the selected serial port is not a hardware port, it disables the severe error reporting feature. +static void MinSerialBegin() { + #if !WITHIN(SERIAL_PORT, 0, 4) + #warning "Using POSTMORTEM_DEBUGGING requires a physical U(S)ART hardware in case of severe error." + #warning "Disabling the severe error reporting feature currently because the used serial port is not a HW port." + #else + int nvicIndex = nvicUART[SERIAL_PORT]; + + // NVIC base address for interrupt disable + struct NVICMin { + volatile uint32_t ISER[32]; + volatile uint32_t ICER[32]; + }; + NVICMin *nvicBase = (NVICMin*)0xE000E100; + + SBI32(nvicBase->ICER[nvicIndex >> 5], nvicIndex & 0x1F); + + // We require memory barriers to properly disable interrupts + // (https://dzone.com/articles/nvic-disabling-interrupts-on-arm-cortex-m-and-the) + dsb(); + isb(); + + // Get the RCU PCLK for this USART + rcu::RCU_PCLK pclk = clockRegs[SERIAL_PORT]; + + // Disable then enable usart peripheral clocks + rcu::RCU_DEVICE.set_pclk_enable(pclk, false); + rcu::RCU_DEVICE.set_pclk_enable(pclk, true); + + // Save current baudrate + uint32_t baudrate = USART_BAUD; + + // Reset USART control registers + USART_CTL0 = 0; + USART_CTL1 = 0; // 1 stop bit + + // Restore baudrate + USART_BAUD = baudrate; + + // Enable transmitter and USART (8 bits, no parity, 1 stop bit) + SET_BIT(USART_CTL0, TEN_BIT); + SET_BIT(USART_CTL0, UEN_BIT); + #endif +} + +// Writes a single character to the serial port. +static void MinSerialWrite(char c) { + #if WITHIN(SERIAL_PORT, 0, 4) + // Wait until transmit buffer is empty + while (!READ_BIT(USART_STAT0, TBE_BIT)) { + hal.watchdog_refresh(); + sw_barrier(); + } + // Write character to data register + USART_DATA = c; + #endif +} + +// Installs the minimum serial interface. +// Sets the HAL_min_serial_init and HAL_min_serial_out function pointers to MinSerialBegin and MinSerialWrite respectively. +void install_min_serial() { + HAL_min_serial_init = &MinSerialBegin; + HAL_min_serial_out = &MinSerialWrite; +} + +extern "C" { + // A low-level assembly-based jump handler. + // Unconditionally branches to the CommonHandler_ASM function. + __attribute__((naked, aligned(4))) void JumpHandler_ASM() { + __asm__ __volatile__ ("b CommonHandler_ASM\n"); + } + void __attribute__((naked, alias("JumpHandler_ASM"), nothrow)) HardFault_Handler(); + void __attribute__((naked, alias("JumpHandler_ASM"), nothrow)) BusFault_Handler(); + void __attribute__((naked, alias("JumpHandler_ASM"), nothrow)) UsageFault_Handler(); + void __attribute__((naked, alias("JumpHandler_ASM"), nothrow)) MemManage_Handler(); + void __attribute__((naked, alias("JumpHandler_ASM"), nothrow)) NMI_Handler(); +} + +#endif // POSTMORTEM_DEBUGGING +#endif // ARDUINO_ARCH_MFL diff --git a/Marlin/src/HAL/GD32_MFL/README.md b/Marlin/src/HAL/GD32_MFL/README.md new file mode 100644 index 000000000000..61800eda1c5b --- /dev/null +++ b/Marlin/src/HAL/GD32_MFL/README.md @@ -0,0 +1,9 @@ +# Generic GD32 HAL based on the MFL Arduino Core + +This HAL is eventually intended to act as the generic HAL for all GD32 chips using the MFL library. + +Currently it supports: + +- GD32F303RET6 + +Targeting the official [MFL Arduino Core](https://github.com/bnmguy/ArduinoCore_MFL). diff --git a/Marlin/src/HAL/GD32_MFL/Servo.cpp b/Marlin/src/HAL/GD32_MFL/Servo.cpp new file mode 100644 index 000000000000..6cbcbc049be6 --- /dev/null +++ b/Marlin/src/HAL/GD32_MFL/Servo.cpp @@ -0,0 +1,125 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2025 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 . + * + */ +#include "../platforms.h" + +#ifdef ARDUINO_ARCH_MFL + +#include "../../inc/MarlinConfig.h" + +#if HAS_SERVOS + +#include "Servo.h" + +static uint_fast8_t servoCount = 0; +static libServo* servos[NUM_SERVOS] = {0}; +constexpr millis_t servoDelay[] = SERVO_DELAY; +static_assert(COUNT(servoDelay) == NUM_SERVOS, "SERVO_DELAY must be an array NUM_SERVOS long."); + +// Initialize to the default timer priority. This will be overridden by a call from timers.cpp. +// This allows all timer interrupt priorities to be managed from a single location in the HAL. +static uint32_t servo_interrupt_priority = NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 12, 0); + +// This must be called after the MFL Servo class has initialized the timer. +// To be safe this is currently called after every call to attach(). +static void fixServoTimerInterruptPriority() { + auto& servoTimerIdx = GeneralTimer::get_instance(static_cast(TIMER_SERVO)); + NVIC_SetPriority(servoTimerIdx.getTimerUpIRQ(), servo_interrupt_priority); +} + +// Default constructor for libServo class. +// Initializes the servo delay, pause state, and pause value. +// Registers the servo instance in the servos array. +libServo::libServo() : + delay(servoDelay[servoCount]), + was_attached_before_pause(false), + value_before_pause(0) +{ + servos[servoCount++] = this; +} + +// Attaches a servo to a specified pin. +int8_t libServo::attach(const int pin) { + if (servoCount >= MAX_SERVOS) return -1; + if (pin > 0) servoPin = pin; + auto result = mflServo.attach(servoPin); + fixServoTimerInterruptPriority(); + return result; +} + +// Attaches a servo to a specified pin with minimum and maximum pulse widths. +int8_t libServo::attach(const int pin, const int min, const int max) { + if (servoCount >= MAX_SERVOS) return -1; + if (pin > 0) servoPin = pin; + auto result = mflServo.attach(servoPin, min, max); + fixServoTimerInterruptPriority(); + return result; +} + +// Moves the servo to a specified position. +void libServo::move(const int value) { + if (attach(0) >= 0) { + mflServo.write(value); + safe_delay(delay); + TERN_(DEACTIVATE_SERVOS_AFTER_MOVE, detach()); + } +} + +// Pause the servo by detaching it and storing its current state. +void libServo::pause() { + was_attached_before_pause = mflServo.attached(); + if (was_attached_before_pause) { + value_before_pause = mflServo.read(); + mflServo.detach(); + } +} + +// Resume a previously paused servo. +// If the servo was attached before the pause, this function re-attaches +// the servo and moves it to the position it was in before the pause. +void libServo::resume() { + if (was_attached_before_pause) { + attach(); + move(value_before_pause); + } +} + +// Pause all servos by stopping their timers. +void libServo::pause_all_servos() { + for (auto& servo : servos) + if (servo) servo->pause(); +} + +// Resume all paused servos by starting their timers. +void libServo::resume_all_servos() { + for (auto& servo : servos) + if (servo) servo->resume(); +} + +// Set the interrupt priority for the servo. +// @param preemptPriority The preempt priority level. +// @param subPriority The sub priority level. +void libServo::setInterruptPriority(uint32_t preemptPriority, uint32_t subPriority) { + servo_interrupt_priority = NVIC_EncodePriority(NVIC_GetPriorityGrouping(), preemptPriority, subPriority); +} + +#endif // HAS_SERVOS +#endif // ARDUINO_ARCH_MFL diff --git a/Marlin/src/HAL/GD32_MFL/Servo.h b/Marlin/src/HAL/GD32_MFL/Servo.h new file mode 100644 index 000000000000..80cff46ff894 --- /dev/null +++ b/Marlin/src/HAL/GD32_MFL/Servo.h @@ -0,0 +1,56 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2025 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 . + * + */ +#pragma once + +#include + +#include "../../core/millis_t.h" + +// Inherit and expand on the official library +class libServo { +public: + libServo(); + + int8_t attach(const int pin = 0); // pin == 0 uses value from previous call + int8_t attach(const int pin, const int min, const int max); + void detach() { mflServo.detach(); } + + int read() { return mflServo.read(); } + void move(const int value); + + void pause(); + void resume(); + + static void pause_all_servos(); + static void resume_all_servos(); + + static void setInterruptPriority(uint32_t preemptPriority, uint32_t subPriority); + +private: + Servo mflServo; + + int servoPin = 0; + millis_t delay = 0; + + bool was_attached_before_pause; + int value_before_pause; +}; diff --git a/Marlin/src/HAL/GD32_MFL/dogm/u8g_com_mfl_swspi.cpp b/Marlin/src/HAL/GD32_MFL/dogm/u8g_com_mfl_swspi.cpp new file mode 100644 index 000000000000..b36cbfe44dfd --- /dev/null +++ b/Marlin/src/HAL/GD32_MFL/dogm/u8g_com_mfl_swspi.cpp @@ -0,0 +1,129 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2025 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] + * + * Based on Sprinter and grbl. + * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm / Ryan Power + * + * 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 . + * + */ + +#ifdef ARDUINO_ARCH_MFL + +#include "../../../inc/MarlinConfig.h" + +#if ALL(HAS_MARLINUI_U8GLIB, FORCE_SOFT_SPI) + +#include +#include "../../shared/HAL_SPI.h" + +#define nop asm volatile ("\tnop\n") + +static inline uint8_t swSpiTransfer_mode_0(uint8_t b) { + for (uint8_t i = 0; i < 8; ++i) { + const uint8_t state = (b & 0x80) ? HIGH : LOW; + WRITE(DOGLCD_SCK, HIGH); + WRITE(DOGLCD_MOSI, state); + b <<= 1; + WRITE(DOGLCD_SCK, LOW); + } + return b; +} + +static inline uint8_t swSpiTransfer_mode_3(uint8_t b) { + for (uint8_t i = 0; i < 8; ++i) { + const uint8_t state = (b & 0x80) ? HIGH : LOW; + WRITE(DOGLCD_SCK, LOW); + WRITE(DOGLCD_MOSI, state); + b <<= 1; + WRITE(DOGLCD_SCK, HIGH); + } + return b; +} + +static void u8g_sw_spi_shift_out(uint8_t val) { + #if U8G_SPI_USE_MODE_3 + swSpiTransfer_mode_3(val); + #else + swSpiTransfer_mode_0(val); + #endif +} + +static void swSpiInit() { + #if PIN_EXISTS(LCD_RESET) + SET_OUTPUT(LCD_RESET_PIN); + #endif + SET_OUTPUT(DOGLCD_A0); + OUT_WRITE(DOGLCD_SCK, LOW); + OUT_WRITE(DOGLCD_MOSI, LOW); + OUT_WRITE(DOGLCD_CS, HIGH); +} + +uint8_t u8g_com_HAL_MFL_sw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr) { + switch (msg) { + case U8G_COM_MSG_INIT: + swSpiInit(); + break; + case U8G_COM_MSG_STOP: + break; + case U8G_COM_MSG_RESET: + #if PIN_EXISTS(LCD_RESET) + WRITE(LCD_RESET_PIN, arg_val); + #endif + break; + case U8G_COM_MSG_CHIP_SELECT: + #if U8G_SPI_USE_MODE_3 // This LCD SPI is running mode 3 while SD card is running mode 0 + if (arg_val) { // SCK idle state needs to be set to the proper idle state before + // the next chip select goes active + WRITE(DOGLCD_SCK, HIGH); // Set SCK to mode 3 idle state before CS goes active + WRITE(DOGLCD_CS, LOW); + nop; // Hold SCK high for a few ns + nop; + } + else { + WRITE(DOGLCD_CS, HIGH); + WRITE(DOGLCD_SCK, LOW); // Set SCK to mode 0 idle state after CS goes inactive + } + #else + WRITE(DOGLCD_CS, !arg_val); + #endif + break; + case U8G_COM_MSG_WRITE_BYTE: + u8g_sw_spi_shift_out(arg_val); + break; + case U8G_COM_MSG_WRITE_SEQ: { + uint8_t* ptr = (uint8_t*)arg_ptr; + while (arg_val > 0) { + u8g_sw_spi_shift_out(*ptr++); + arg_val--; + } + } break; + case U8G_COM_MSG_WRITE_SEQ_P: { + uint8_t* ptr = (uint8_t*)arg_ptr; + while (arg_val > 0) { + u8g_sw_spi_shift_out(u8g_pgm_read(ptr)); + ptr++; + arg_val--; + } + } break; + case U8G_COM_MSG_ADDRESS: // Define cmd (arg_val = 0) or data mode (arg_val = 1) + WRITE(DOGLCD_A0, arg_val); + break; + } + return 1; +} + +#endif // HAS_MARLINUI_U8GLIB && FORCE_SOFT_SPI +#endif // ARDUINO_ARCH_MFL diff --git a/Marlin/src/HAL/GD32_MFL/eeprom/eeprom_bl24cxx.cpp b/Marlin/src/HAL/GD32_MFL/eeprom/eeprom_bl24cxx.cpp new file mode 100644 index 000000000000..1053c80a4153 --- /dev/null +++ b/Marlin/src/HAL/GD32_MFL/eeprom/eeprom_bl24cxx.cpp @@ -0,0 +1,93 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2025 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 . + * + */ + +/** + * PersistentStore for Arduino-style EEPROM interface + * with simple implementations supplied by Marlin. + */ + +#include "../../platforms.h" + +#ifdef ARDUINO_ARCH_MFL + +#include "../../../inc/MarlinConfig.h" + +#if ENABLED(IIC_BL24CXX_EEPROM) + +#include "../../shared/eeprom_if.h" +#include "../../shared/eeprom_api.h" + +#ifndef MARLIN_EEPROM_SIZE + #error "MARLIN_EEPROM_SIZE is required for IIC_BL24CXX_EEPROM." +#endif + +size_t PersistentStore::capacity() { + return MARLIN_EEPROM_SIZE - eeprom_exclude_size; +} + +bool PersistentStore::access_start() { + eeprom_init(); + return true; +} + +bool PersistentStore::access_finish() { + return true; +} + +bool PersistentStore::write_data(int &pos, const uint8_t *value, size_t size, uint16_t *crc) { + uint16_t written = 0; + while (size--) { + uint8_t v = *value; + uint8_t * const p = (uint8_t * const)REAL_EEPROM_ADDR(pos); + // EPROM has only ~100,000 write cycles, + // so only write bytes that have changed! + if (v != eeprom_read_byte(p)) { + eeprom_write_byte(p, v); + if (++written & 0x7F) delay(2); else safe_delay(2); + if (eeprom_read_byte(p) != v) { + SERIAL_ECHO_MSG(STR_ERR_EEPROM_WRITE); + return true; + } + } + + crc16(crc, &v, 1); + pos++; + value++; + } + + return false; +} + +bool PersistentStore::read_data(int &pos, uint8_t *value, size_t size, uint16_t *crc, const bool writing/*=true*/) { + do { + const uint8_t c = eeprom_read_byte((uint8_t*)REAL_EEPROM_ADDR(pos)); + if (writing) *value = c; + crc16(crc, &c, 1); + pos++; + value++; + } while (--size); + + return false; +} + +#endif // IIC_BL24CXX_EEPROM +#endif // ARDUINO_ARCH_MFL diff --git a/Marlin/src/HAL/GD32_MFL/eeprom/eeprom_if_iic.cpp b/Marlin/src/HAL/GD32_MFL/eeprom/eeprom_if_iic.cpp new file mode 100644 index 000000000000..765c997e1f2d --- /dev/null +++ b/Marlin/src/HAL/GD32_MFL/eeprom/eeprom_if_iic.cpp @@ -0,0 +1,54 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2025 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 . + * + */ + +/** + * Platform-independent Arduino functions for I2C EEPROM. + * Enable USE_SHARED_EEPROM if not supplied by the framework. + */ + +#include "../../platforms.h" + +#ifdef ARDUINO_ARCH_MFL + +#include "../../../inc/MarlinConfig.h" + +#if ENABLED(IIC_BL24CXX_EEPROM) + +#include "../../../libs/BL24CXX.h" +#include "../../shared/eeprom_if.h" + +void eeprom_init() { + BL24CXX::init(); +} + +void eeprom_write_byte(uint8_t *pos, uint8_t value) { + const unsigned eeprom_address = (unsigned)pos; + BL24CXX::writeOneByte(eeprom_address, value); +} + +uint8_t eeprom_read_byte(uint8_t *pos) { + const unsigned eeprom_address = (unsigned)pos; + return BL24CXX::readOneByte(eeprom_address); +} + +#endif // IIC_BL24CXX_EEPROM +#endif // ARDUINO_ARCH_MFL diff --git a/Marlin/src/HAL/GD32_MFL/eeprom/eeprom_wired.cpp b/Marlin/src/HAL/GD32_MFL/eeprom/eeprom_wired.cpp new file mode 100644 index 000000000000..8860e53a62c3 --- /dev/null +++ b/Marlin/src/HAL/GD32_MFL/eeprom/eeprom_wired.cpp @@ -0,0 +1,96 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2025 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 . + * + */ +#include "../../platforms.h" + +#ifdef ARDUINO_ARCH_MFL + +#include "../../../inc/MarlinConfig.h" + +#if USE_WIRED_EEPROM + +/** + * PersistentStore for Arduino-style EEPROM interface + * with simple implementations supplied by Marlin. + */ + +#include "../../shared/eeprom_if.h" +#include "../../shared/eeprom_api.h" + +#ifndef MARLIN_EEPROM_SIZE + #define MARLIN_EEPROM_SIZE size_t(E2END + 1) +#endif + +size_t PersistentStore::capacity() { + return MARLIN_EEPROM_SIZE - eeprom_exclude_size; +} + +bool PersistentStore::access_start() { + eeprom_init(); + return true; +} + +bool PersistentStore::access_finish() { + return true; +} + +bool PersistentStore::write_data(int &pos, const uint8_t *value, size_t size, uint16_t *crc) { + uint16_t written = 0; + while (size--) { + uint8_t v = *value; + uint8_t * const p = (uint8_t * const)REAL_EEPROM_ADDR(pos); + // EEPROM has only ~100,000 write cycles, + // so only write bytes that have changed! + if (v != eeprom_read_byte(p)) { + eeprom_write_byte(p, v); + // Avoid triggering watchdog during long EEPROM writes + if (++written & 0x7F) + delay(2); + else + safe_delay(2); + if (eeprom_read_byte(p) != v) { + SERIAL_ECHO_MSG(STR_ERR_EEPROM_WRITE); + return true; + } + } + crc16(crc, &v, 1); + pos++; + value++; + } + + return false; +} + +bool PersistentStore::read_data(int &pos, uint8_t *value, size_t size, uint16_t *crc, const bool writing/*=true*/) { + do { + const uint8_t c = eeprom_read_byte((uint8_t*)REAL_EEPROM_ADDR(pos)); + if (writing) + *value = c; + crc16(crc, &c, 1); + pos++; + value++; + } while (--size); + + return false; +} + +#endif // USE_WIRED_EEPROM +#endif // ARDUINO_ARCH_MFL diff --git a/Marlin/src/HAL/GD32_MFL/endstop_interrupts.h b/Marlin/src/HAL/GD32_MFL/endstop_interrupts.h new file mode 100644 index 000000000000..175dec395978 --- /dev/null +++ b/Marlin/src/HAL/GD32_MFL/endstop_interrupts.h @@ -0,0 +1,61 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2025 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 . + * + */ +#pragma once + +#include "../../module/endstops.h" + +// One ISR for all EXT-Interrupts +void endstop_ISR() { endstops.update(); } + +void setup_endstop_interrupts() { + #define _ATTACH(P) attachInterrupt(P, endstop_ISR, CHANGE) + TERN_(USE_X_MAX, _ATTACH(X_MAX_PIN)); + TERN_(USE_X_MIN, _ATTACH(X_MIN_PIN)); + TERN_(USE_Y_MAX, _ATTACH(Y_MAX_PIN)); + TERN_(USE_Y_MIN, _ATTACH(Y_MIN_PIN)); + TERN_(USE_Z_MAX, _ATTACH(Z_MAX_PIN)); + TERN_(USE_Z_MIN, _ATTACH(Z_MIN_PIN)); + TERN_(USE_X2_MAX, _ATTACH(X2_MAX_PIN)); + TERN_(USE_X2_MIN, _ATTACH(X2_MIN_PIN)); + TERN_(USE_Y2_MAX, _ATTACH(Y2_MAX_PIN)); + TERN_(USE_Y2_MIN, _ATTACH(Y2_MIN_PIN)); + TERN_(USE_Z2_MAX, _ATTACH(Z2_MAX_PIN)); + TERN_(USE_Z2_MIN, _ATTACH(Z2_MIN_PIN)); + TERN_(USE_Z3_MAX, _ATTACH(Z3_MAX_PIN)); + TERN_(USE_Z3_MIN, _ATTACH(Z3_MIN_PIN)); + TERN_(USE_Z4_MAX, _ATTACH(Z4_MAX_PIN)); + TERN_(USE_Z4_MIN, _ATTACH(Z4_MIN_PIN)); + TERN_(USE_Z_MIN_PROBE, _ATTACH(Z_MIN_PROBE_PIN)); + TERN_(USE_CALIBRATION, _ATTACH(CALIBRATION_PIN)); + TERN_(USE_I_MAX, _ATTACH(I_MAX_PIN)); + TERN_(USE_I_MIN, _ATTACH(I_MIN_PIN)); + TERN_(USE_J_MAX, _ATTACH(J_MAX_PIN)); + TERN_(USE_J_MIN, _ATTACH(J_MIN_PIN)); + TERN_(USE_K_MAX, _ATTACH(K_MAX_PIN)); + TERN_(USE_K_MIN, _ATTACH(K_MIN_PIN)); + TERN_(USE_U_MAX, _ATTACH(U_MAX_PIN)); + TERN_(USE_U_MIN, _ATTACH(U_MIN_PIN)); + TERN_(USE_V_MAX, _ATTACH(V_MAX_PIN)); + TERN_(USE_V_MIN, _ATTACH(V_MIN_PIN)); + TERN_(USE_W_MAX, _ATTACH(W_MAX_PIN)); + TERN_(USE_W_MIN, _ATTACH(W_MIN_PIN)); +} diff --git a/Marlin/src/HAL/GD32_MFL/fast_pwm.cpp b/Marlin/src/HAL/GD32_MFL/fast_pwm.cpp new file mode 100644 index 000000000000..9fba673efcc7 --- /dev/null +++ b/Marlin/src/HAL/GD32_MFL/fast_pwm.cpp @@ -0,0 +1,97 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2025 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 . + * + */ + +#include "../platforms.h" + +#ifdef ARDUINO_ARCH_MFL + +#include "../../inc/MarlinConfig.h" + +#include +#include +#include "timers.h" + +static uint16_t timer_frequency[TIMER_COUNT]; + +void MarlinHAL::set_pwm_duty(const pin_t pin, const uint16_t value, const uint16_t scale, const bool invert) { + // Calculate duty cycle based on inversion flag + const uint16_t duty = invert ? scale - value : value; + + // Check if the pin supports PWM + if (PWM_PIN(pin)) { + // Get the timer peripheral base associated with the pin + const auto timer_base = getPinOpsPeripheralBase(TIMER_PinOps, static_cast(pin)); + + // Initialize the timer instance + auto& TimerInstance = GeneralTimer::get_instance(timer_base); + + // Get channel and previous channel mode + const auto channel = getPackedPinChannel(getPackedPinOps(TIMER_PinOps, static_cast(pin))); + const InputOutputMode previous = TimerInstance.getChannelMode(channel); + + if (timer_frequency[static_cast(timer_base)] == 0) { + set_pwm_frequency(pin, PWM_FREQUENCY); + } + + // Set the PWM duty cycle + TimerInstance.setCaptureCompare(channel, duty, CCFormat::B8); + + // Configure pin as PWM output + pinOpsPinout(TIMER_PinOps, static_cast(pin)); + + // Set channel mode if not already set and start timer + if (previous != InputOutputMode::PWM0) { + TimerInstance.setChannelMode(channel, InputOutputMode::PWM0, static_cast(pin)); + TimerInstance.start(); + } + } else { + pinMode(pin, OUTPUT); + digitalWrite(pin, duty < scale / 2 ? LOW : HIGH); + } +} + +void MarlinHAL::set_pwm_frequency(const pin_t pin, const uint16_t f_desired) { + // Check if the pin supports PWM + if (!PWM_PIN(pin)) return; + + // Get the timer peripheral base associated with the pin + const auto timer_base = getPinOpsPeripheralBase(TIMER_PinOps, static_cast(pin)); + + // Guard against modifying protected timers + #ifdef STEP_TIMER + if (timer_base == static_cast(STEP_TIMER)) return; + #endif + #ifdef TEMP_TIMER + if (timer_base == static_cast(TEMP_TIMER)) return; + #endif + #if defined(PULSE_TIMER) && MF_TIMER_PULSE != MF_TIMER_STEP + if (timer_base == static_cast(PULSE_TIMER)) return; + #endif + + // Initialize the timer instance + auto& TimerInstance = GeneralTimer::get_instance(timer_base); + + TimerInstance.setRolloverValue(f_desired, TimerFormat::HERTZ); + timer_frequency[timer_base_to_index(timer_base)] = f_desired; +} + +#endif // ARDUINO_ARCH_MFL diff --git a/Marlin/src/HAL/GD32_MFL/fastio.h b/Marlin/src/HAL/GD32_MFL/fastio.h new file mode 100644 index 000000000000..11020f3e525b --- /dev/null +++ b/Marlin/src/HAL/GD32_MFL/fastio.h @@ -0,0 +1,86 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2025 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 . + * + */ +#pragma once + +// Fast I/O interfaces for GD32 + +#include +#include +#include +#include + +template +FORCE_INLINE static void fast_write_pin_wrapper(pin_size_t IO, T V) { + const PortPinPair& pp = port_pin_map[IO]; + gpio::fast_write_pin(pp.port, pp.pin, static_cast(V)); +} + +FORCE_INLINE static auto fast_read_pin_wrapper(pin_size_t IO) -> bool { + const PortPinPair& pp = port_pin_map[IO]; + return gpio::fast_read_pin(pp.port, pp.pin); +} + +FORCE_INLINE static void fast_toggle_pin_wrapper(pin_size_t IO) { + const PortPinPair& pp = port_pin_map[IO]; + gpio::fast_toggle_pin(pp.port, pp.pin); +} + +// ------------------------ +// Defines +// ------------------------ + +#ifndef PWM + #define PWM OUTPUT +#endif + +#define _WRITE(IO, V) fast_write_pin_wrapper(IO, V) +#define _READ(IO) fast_read_pin_wrapper(IO) +#define _TOGGLE(IO) fast_toggle_pin_wrapper(IO) + +#define _GET_MODE(IO) +#define _SET_MODE(IO, M) pinMode((IO), (M)) +#define _SET_OUTPUT(IO) pinMode((IO), OUTPUT) +#define _SET_OUTPUT_OD(IO) pinMode((IO), OUTPUT_OPEN_DRAIN) + +#define WRITE(IO, V) _WRITE((IO), (V)) +#define READ(IO) _READ(IO) +#define TOGGLE(IO) _TOGGLE(IO) + +#define OUT_WRITE(IO, V) do { _SET_OUTPUT(IO); WRITE((IO), (V)); } while (0) +#define OUT_WRITE_OD(IO, V) do { _SET_OUTPUT_OD(IO); WRITE((IO), (V)); } while (0) + +#define SET_INPUT(IO) _SET_MODE((IO), INPUT) +#define SET_INPUT_PULLUP(IO) _SET_MODE((IO), INPUT_PULLUP) +#define SET_INPUT_PULLDOWN(IO) _SET_MODE((IO), INPUT_PULLDOWN) +#define SET_OUTPUT(IO) OUT_WRITE((IO), LOW) +#define SET_OUTPUT_OD(IO) OUT_WRITE_OD((IO), LOW) +#define SET_PWM(IO) _SET_MODE((IO), PWM) + +#define IS_INPUT(IO) +#define IS_OUTPUT(IO) + +#define PWM_PIN(P) isPinInPinOps(TIMER_PinOps, P) +#define NO_COMPILE_TIME_PWM + +// Wrappers for digitalRead and digitalWrite +#define extDigitalRead(IO) digitalRead(IO) +#define extDigitalWrite(IO, V) digitalWrite((IO), (V)) diff --git a/Marlin/src/HAL/GD32_MFL/inc/Conditionals_LCD.h b/Marlin/src/HAL/GD32_MFL/inc/Conditionals_LCD.h new file mode 100644 index 000000000000..379ecfa7f0eb --- /dev/null +++ b/Marlin/src/HAL/GD32_MFL/inc/Conditionals_LCD.h @@ -0,0 +1,26 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2025 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 . + * + */ +#pragma once + +#if ALL(HAS_MARLINUI_U8GLIB, FORCE_SOFT_SPI) + #define U8G_SW_SPI_MFL 1 +#endif diff --git a/Marlin/src/HAL/GD32_MFL/inc/Conditionals_adv.h b/Marlin/src/HAL/GD32_MFL/inc/Conditionals_adv.h new file mode 100644 index 000000000000..6df84f6f92a3 --- /dev/null +++ b/Marlin/src/HAL/GD32_MFL/inc/Conditionals_adv.h @@ -0,0 +1,26 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2025 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 . + * + */ +#pragma once + +#if ALL(HAS_MEDIA, USBD_USE_CDC_MSC) + #define HAS_SD_HOST_DRIVE 1 +#endif diff --git a/Marlin/src/HAL/GD32_MFL/inc/Conditionals_post.h b/Marlin/src/HAL/GD32_MFL/inc/Conditionals_post.h new file mode 100644 index 000000000000..a3db12268209 --- /dev/null +++ b/Marlin/src/HAL/GD32_MFL/inc/Conditionals_post.h @@ -0,0 +1,29 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2025 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 . + * + */ +#pragma once + +// If no real or emulated EEPROM selected, fall back to SD emulation +#if USE_FALLBACK_EEPROM + #define SDCARD_EEPROM_EMULATION +#elif ANY(I2C_EEPROM, SPI_EEPROM) + #define USE_SHARED_EEPROM 1 +#endif diff --git a/Marlin/src/HAL/GD32_MFL/inc/Conditionals_type.h b/Marlin/src/HAL/GD32_MFL/inc/Conditionals_type.h new file mode 100644 index 000000000000..92cc457df57c --- /dev/null +++ b/Marlin/src/HAL/GD32_MFL/inc/Conditionals_type.h @@ -0,0 +1,22 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2025 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 . + * + */ +#pragma once diff --git a/Marlin/src/HAL/GD32_MFL/inc/SanityCheck.h b/Marlin/src/HAL/GD32_MFL/inc/SanityCheck.h new file mode 100644 index 000000000000..562665e8c96f --- /dev/null +++ b/Marlin/src/HAL/GD32_MFL/inc/SanityCheck.h @@ -0,0 +1,104 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2025 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 . + * + */ +#pragma once + +/** + * Require pins to be defined as macros with numerical values + */ +#ifndef PA0 + #error "Your ARM platform pins are not defined as macros, only as enums! Provide pins_arduino.h and/or 'buildroot/share/PlatformIO/variants/*/variant.h' to define the pins." +#endif + +// Test MFL GD32 specific configuration values for errors at compile-time. +#if ENABLED(SDCARD_EEPROM_EMULATION) && !HAS_MEDIA + #undef SDCARD_EEPROM_EMULATION // avoid additional error noise + #if USE_FALLBACK_EEPROM + #warning "EEPROM type not specified. Fallback is SDCARD_EEPROM_EMULATION." + #endif + #error "SDCARD_EEPROM_EMULATION requires SDSUPPORT. Enable SDSUPPORT or choose another EEPROM emulation." +#endif + +#if ENABLED(FLASH_EEPROM_LEVELING) + #error "FLASH_EEPROM_LEVELING is not supported on GD32." +#endif + +#if ENABLED(SERIAL_STATS_MAX_RX_QUEUED) + #error "SERIAL_STATS_MAX_RX_QUEUED is not supported on GD32." +#elif ENABLED(SERIAL_STATS_DROPPED_RX) + #error "SERIAL_STATS_DROPPED_RX is not supported on GD32." +#endif + +#if TEMP_SENSOR_SOC && defined(ATEMP) && TEMP_SOC_PIN != ATEMP + #error "TEMP_SENSOR_SOC requires 'TEMP_SOC_PIN ATEMP' on GD32" +#endif + +// Check for common serial pin conflicts +#define _CHECK_SERIAL_PIN(N) (( \ + BTN_EN1 == N || BTN_EN2 == N || DOGLCD_CS == N || HEATER_BED_PIN == N || FAN0_PIN == N || \ + SDIO_D2_PIN == N || SDIO_D3_PIN == N || SDIO_CK_PIN == N || SDIO_CMD_PIN == N || \ + Y_STEP_PIN == N || Y_ENABLE_PIN == N || E0_ENABLE_PIN == N || POWER_LOSS_PIN == N \ + )) + +#define CHECK_SERIAL_PIN(T, N) defined(UART##N##_##T##_PIN) && _CHECK_SERIAL_PIN(UART##N##_##T##_PIN) + +#if SERIAL_IN_USE(0) + #if CHECK_SERIAL_PIN(TX, 0) + #error "Serial Port 0 TX IO pins conflict with another pin on the board." + #endif + #if CHECK_SERIAL_PIN(RX, 0) + #error "Serial Port 0 RX IO pins conflict with another pin on the board." + #endif +#endif +#if SERIAL_IN_USE(1) + #if CHECK_SERIAL_PIN(TX, 1) + #error "Serial Port 1 TX IO pins conflict with another pin on the board." + #endif + #if CHECK_SERIAL_PIN(RX, 1) + #error "Serial Port 1 RX IO pins conflict with another pin on the board." + #endif +#endif +#if SERIAL_IN_USE(2) + #if CHECK_SERIAL_PIN(TX, 2) + #error "Serial Port 2 TX IO pins conflict with another pin on the board." + #endif + #if CHECK_SERIAL_PIN(RX, 2) + #error "Serial Port 2 RX IO pins conflict with another pin on the board." + #endif +#endif +#if SERIAL_IN_USE(3) + #if CHECK_SERIAL_PIN(TX, 3) + #error "Serial Port 3 TX IO pins conflict with another pin on the board." + #endif + #if CHECK_SERIAL_PIN(RX, 3) + #error "Serial Port 3 RX IO pins conflict with another pin on the board." + #endif +#endif +#if SERIAL_IN_USE(4) + #if CHECK_SERIAL_PIN(TX, 4) + #error "Serial Port 4 TX IO pins conflict with another pin on the board." + #endif + #if CHECK_SERIAL_PIN(RX, 4) + #error "Serial Port 4 RX IO pins conflict with another pin on the board." + #endif +#endif +#undef CHECK_SERIAL_PIN +#undef _CHECK_SERIAL_PIN diff --git a/Marlin/src/HAL/GD32_MFL/pinsDebug.h b/Marlin/src/HAL/GD32_MFL/pinsDebug.h new file mode 100644 index 000000000000..be7e4ce1ba2a --- /dev/null +++ b/Marlin/src/HAL/GD32_MFL/pinsDebug.h @@ -0,0 +1,104 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2025 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 . + * + */ +#pragma once + +/** + * Pins Debugging for GD32 + * + * - NUMBER_PINS_TOTAL + * - MULTI_NAME_PAD + * - getPinByIndex(index) + * - printPinNameByIndex(index) + * - getPinIsDigitalByIndex(index) + * - digitalPinToAnalogIndex(pin) + * - getValidPinMode(pin) + * - isValidPin(pin) + * - isAnalogPin(pin) + * - digitalRead_mod(pin) + * - pwm_status(pin) + * - printPinPWM(pin) + * - printPinPort(pin) + * - printPinNumber(pin) + * - printPinAnalog(pin) + */ + +#include "../../inc/MarlinConfig.h" +#include +#include +#include + +#ifndef TOTAL_PIN_COUNT + #error "Expected TOTAL_PIN_COUNT not found." +#endif + +#define NUM_DIGITAL_PINS TOTAL_PIN_COUNT +#define NUMBER_PINS_TOTAL TOTAL_PIN_COUNT + +#define getPinByIndex(x) pin_t(pin_array[x].pin) +#define isValidPin(P) WITHIN(P, 0, (NUM_DIGITAL_PINS - 1)) +#define digitalRead_mod(P) extDigitalRead(P) +#define printPinNumber(P) do { sprintf_P(buffer, PSTR("%3hd "), pin_t(P)); SERIAL_ECHO(buffer); } while (0) +#define printPinAnalog(P) do { sprintf_P(buffer, PSTR(" (A%2d) "), pin_t(getAdcChannelFromPin(P))); 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 + +#ifndef M43_NEVER_TOUCH + #define M43_NEVER_TOUCH(x) WITHIN(x, 9, 10) // SERIAL pins: PA9(TX) PA10(RX) +#endif + +bool isAnalogPin(const pin_t pin) { + if (!isValidPin(pin)) return false; + + if (getAdcChannel(pin) != adc::ADC_Channel::INVALID) { + const PortPinPair& pp = port_pin_map[pin]; + auto& instance = gpio::GPIO::get_instance(pp.port).value(); + return instance.get_pin_mode(pp.pin) == gpio::Pin_Mode::ANALOG && !M43_NEVER_TOUCH(pin); + } + + return false; +} + +bool getValidPinMode(const pin_t pin) { + if (!isValidPin(pin)) return false; + + const PortPinPair& pp = port_pin_map[pin]; + auto& instance = gpio::GPIO::get_instance(pp.port).value(); + gpio::Pin_Mode mode = instance.get_pin_mode(pp.pin); + + return mode != gpio::Pin_Mode::ANALOG && mode != gpio::Pin_Mode::INPUT_FLOATING && + mode != gpio::Pin_Mode::INPUT_PULLUP && mode != gpio::Pin_Mode::INPUT_PULLDOWN; +} + +bool getPinIsDigitalByIndex(const int16_t index) { + const pin_t pin = getPinByIndex(index); + return (!isAnalogPin(pin)); +} + +int8_t digitalPinToAnalogIndex(const pin_t pin) { + if (!isValidPin(pin) || !isAnalogPin(pin)) return -1; + return pin; // Analog and digital pin indexes are shared +} + +bool pwm_status(const pin_t pin) { return false; } +void printPinPWM(const pin_t pin) { /* TODO */ } +void printPinPort(const pin_t pin) { /* TODO */ } diff --git a/Marlin/src/HAL/GD32_MFL/sd/SDCard.cpp b/Marlin/src/HAL/GD32_MFL/sd/SDCard.cpp new file mode 100644 index 000000000000..2e7ba4dfd9eb --- /dev/null +++ b/Marlin/src/HAL/GD32_MFL/sd/SDCard.cpp @@ -0,0 +1,1022 @@ +// +// MFL gd32f30x SDCARD using DMA through SDIO in C++ +// +// Copyright (C) 2025 B. Mourit +// +// This software is free software: you can redistribute it and/or modify it under the terms of the +// GNU Lesser General Public License as published by the Free Software Foundation, +// either version 3 of the License, or (at your option) any later version. +// +// This software 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License along with this software. +// If not, see . +// + +#include "../../platforms.h" + +#ifdef ARDUINO_ARCH_MFL + +#include "../../../inc/MarlinConfig.h" +#include "../../shared/Delay.h" + +#include "SDCard.h" +#include +#include + +namespace sdio { + +auto CardDMA::get_instance() -> CardDMA& { + static CardDMA instance; + return instance; +} + +CardDMA::CardDMA() : + sdcard_csd_{0U, 0U, 0U, 0U}, + sdcard_cid_{0U, 0U, 0U, 0U}, + sdcard_scr_{0U, 0U}, + desired_clock_(Default_Desired_Clock), + total_bytes_(0U), + sdio_(SDIO::get_instance()), + config_(sdio_.get_config()), + dmaBase_(dma::DMA_Base::DMA1_BASE), + dmaChannel_(dma::DMA_Channel::CHANNEL3), + dma_(dma::DMA::get_instance(dmaBase_, dmaChannel_).value()), + sdcard_rca_(0U), + transfer_error_(SDIO_Error_Type::OK), + interface_version_(Interface_Version::UNKNOWN), + card_type_(Card_Type::UNKNOWN), + current_state_(Operational_State::READY), + transfer_end_(false), + multiblock_(false), + is_rx_(false) +{ +} + +// Initialize card and put in standby state +auto CardDMA::init() -> SDIO_Error_Type { + // Reset SDIO peripheral + sdio_.reset(); + sync_domains(); + + // Initialize SDIO peripheral + // If no SDIO_Config structure is provided the default is used. + // The default provides the parameters for initialization + // using a very low clock speed (typically <= 400KHz). + sdio_.init(); + sync_domains(); + + SDIO_Error_Type result = begin_startup_procedure(); + if (result != SDIO_Error_Type::OK) { + return result; + } + + return card_init(); +} + +// Startup command procedure according to SDIO specification +auto CardDMA::begin_startup_procedure() -> SDIO_Error_Type { + sdio_.set_power_mode(Power_Control::POWER_ON); + sdio_.set_clock_enable(true); + sync_domains(); + + // CMD0 (GO_IDLE_STATE) + if (send_command_and_check(Command_Index::CMD0, 0, Command_Response::RSP_NONE, Wait_Type::WT_NONE, [this]() { + return this->get_command_sent_result(); + }) != SDIO_Error_Type::OK) { + return SDIO_Error_Type::CMD0_FAILED; + } + + // CMD8 + if (send_command_and_check(Command_Index::CMD8, Check_Pattern, Command_Response::RSP_SHORT, Wait_Type::WT_NONE, [this]() { + return this->get_r7_result(); + }) != SDIO_Error_Type::OK) { + // V1.0 card + // CMD0 (GO_IDLE_STATE) + interface_version_ = Interface_Version::INTERFACE_V1_1; + if (send_command_and_check(Command_Index::CMD0, 0, Command_Response::RSP_NONE, Wait_Type::WT_NONE, [this]() { + return this->get_command_sent_result(); + }) != SDIO_Error_Type::OK) { + return SDIO_Error_Type::CMD0_FAILED; + } + } else { + // V2.0 card + // CMD55 + interface_version_ = Interface_Version::INTERFACE_V2_0; + if (send_command_and_check(Command_Index::CMD55, 0, Command_Response::RSP_SHORT, Wait_Type::WT_NONE, [this, cmd = Command_Index::CMD55]() { + return get_r1_result(cmd); + }) != SDIO_Error_Type::OK) { + return SDIO_Error_Type::CMD55_FAILED; + } + } + + if (send_command_and_check(Command_Index::CMD55, 0, Command_Response::RSP_SHORT, Wait_Type::WT_NONE, [this, cmd = Command_Index::CMD55]() { + return get_r1_result(cmd); + }) != SDIO_Error_Type::OK) { + return SDIO_Error_Type::CMD55_FAILED; + } + + return validate_voltage(); +} + +// Voltage validation +auto CardDMA::validate_voltage() -> SDIO_Error_Type { + uint32_t response = 0U; + uint32_t count = 0U; + bool valid_voltage = false; + + while (count < Max_Voltage_Checks && valid_voltage == false) { + if (send_command_and_check(Command_Index::CMD55, 0, Command_Response::RSP_SHORT, + Wait_Type::WT_NONE, [this, cmd = Command_Index::CMD55]() { + return get_r1_result(cmd); + }) != SDIO_Error_Type::OK) { + return SDIO_Error_Type::CMD55_FAILED; + } + + if (send_command_and_check(Command_Index::ACMD41, Voltage_Window | SDCARD_HCS | Switch_1_8V_Capacity, + Command_Response::RSP_SHORT, Wait_Type::WT_NONE, [this, cmd = Command_Index::INVALID, index = false, crc = true]() { + return check_sdio_status(cmd, index, crc); + }) != SDIO_Error_Type::OK) { + return SDIO_Error_Type::ACMD41_FAILED; + } + response = sdio_.get_response(Response_Type::RESPONSE0); + valid_voltage = ((response >> 31U) == 1U); + count++; + } + + if (count >= Max_Voltage_Checks) { + return SDIO_Error_Type::INVALID_VOLTAGE; + } + + card_type_ = (response & SDCARD_HCS) ? Card_Type::SDCARD_HIGH_CAPACITY : Card_Type::SDCARD_STANDARD_CAPACITY; + + #if ENABLED(MARLIN_DEV_MODE) + if (card_type_ == Card_Type::SDCARD_HIGH_CAPACITY) { + SERIAL_ECHOPGM("\n SDHC!"); + } else { + SERIAL_ECHOPGM("\n SDSC!"); + } + #endif + + return SDIO_Error_Type::OK; +} + +// Shutdown +void CardDMA::begin_shutdown_procedure() { + sdio_.set_power_mode(Power_Control::POWER_OFF); +} + +// Initialize card +auto CardDMA::card_init() -> SDIO_Error_Type { + if (sdio_.get_power_mode() == static_cast(Power_Control::POWER_OFF)) { + return SDIO_Error_Type::INVALID_OPERATION; + } + + // Skip CID/RCA for IO cards + if (card_type_ != Card_Type::SD_IO_CARD) { + if (send_command_and_check(Command_Index::CMD2, 0U, Command_Response::RSP_LONG, Wait_Type::WT_NONE, + [this, cmd = Command_Index::INVALID, index = false, crc = true]() { + return check_sdio_status(cmd, index, crc); + }) != SDIO_Error_Type::OK) { + return SDIO_Error_Type::CMD2_FAILED; + } + // Store CID + store_cid(); + + // Get RCA + uint16_t r6_rca; + if (send_command_and_check(Command_Index::CMD3, 0U, Command_Response::RSP_SHORT, Wait_Type::WT_NONE, + [this, cmd = Command_Index::CMD3, rca = &r6_rca]() { + return get_r6_result(cmd, rca); + }) != SDIO_Error_Type::OK) { + return SDIO_Error_Type::CMD3_FAILED; + } + // Store RCA + sdcard_rca_ = r6_rca; + if (send_command_and_check(Command_Index::CMD9, static_cast(sdcard_rca_ << RCA_Shift), + Command_Response::RSP_LONG, Wait_Type::WT_NONE, + [this, cmd = Command_Index::INVALID, index = false, crc = true]() { + return check_sdio_status(cmd, index, crc); + }) != SDIO_Error_Type::OK) { + return SDIO_Error_Type::CMD9_FAILED; + } + // Store CSD + store_csd(); + } + + Card_Info card_info; + SDIO_Error_Type result = get_card_specific_data(&card_info); + if (result != SDIO_Error_Type::OK) { + return result; + } + + if (select_deselect() != SDIO_Error_Type::OK) { + return SDIO_Error_Type::SELECT_DESELECT_FAILED; + } + + return SDIO_Error_Type::OK; +} + +auto CardDMA::store_cid() -> SDIO_Error_Type { + // Store the CID register values + sdcard_cid_[0] = sdio_.get_response(Response_Type::RESPONSE0); + sdcard_cid_[1] = sdio_.get_response(Response_Type::RESPONSE1); + sdcard_cid_[2] = sdio_.get_response(Response_Type::RESPONSE2); + sdcard_cid_[3] = sdio_.get_response(Response_Type::RESPONSE3); + + return SDIO_Error_Type::OK; +} + +auto CardDMA::store_csd() -> SDIO_Error_Type { + // Store the CSD register values + sdcard_csd_[0] = sdio_.get_response(Response_Type::RESPONSE0); + sdcard_csd_[1] = sdio_.get_response(Response_Type::RESPONSE1); + sdcard_csd_[2] = sdio_.get_response(Response_Type::RESPONSE2); + sdcard_csd_[3] = sdio_.get_response(Response_Type::RESPONSE3); + + return SDIO_Error_Type::OK; +} + +auto CardDMA::set_hardware_bus_width(Bus_Width width) -> SDIO_Error_Type { + if (card_type_ == Card_Type::SD_MMC || width == Bus_Width::WIDTH_8BIT) { + return SDIO_Error_Type::UNSUPPORTED_FUNCTION; + } + + // Retrieve SCR + SDIO_Error_Type result = get_scr(sdcard_rca_, sdcard_scr_); + if (result != SDIO_Error_Type::OK) { + return result; + } + + // Check and set bus width + // This function is only used to set a higher width than the default 1bit + // so no 1bit configuration logic is required. + if (width == Bus_Width::WIDTH_4BIT) { + // Send CMD55 (APP_CMD) + if (send_command_and_check(Command_Index::CMD55, static_cast(sdcard_rca_ << RCA_Shift), Command_Response::RSP_SHORT, + Wait_Type::WT_NONE, [this, cmd = Command_Index::CMD55]() { + return get_r1_result(cmd); + }) != SDIO_Error_Type::OK) { + return SDIO_Error_Type::CMD55_FAILED; + } + + // Send ACMD6 (SET_BUS_WIDTH) + if (send_command_and_check(Command_Index::ACMD6, 2U, Command_Response::RSP_SHORT, + Wait_Type::WT_NONE, [this, cmd = Command_Index::ACMD6]() { + return get_r1_result(cmd); + }) != SDIO_Error_Type::OK) { + return SDIO_Error_Type::ACMD6_FAILED; + } + + #if ENABLED(MARLIN_DEV_MODE) + SERIAL_ECHOPGM("\n wide bus set!"); + #endif + sdio_.set_bus_width(Bus_Width::WIDTH_4BIT); + + return SDIO_Error_Type::OK; + } + + return SDIO_Error_Type::UNSUPPORTED_FUNCTION; +} + +auto CardDMA::read(uint8_t* buf, uint32_t address, uint32_t count) -> SDIO_Error_Type { + if (current_state_ == Operational_State::READY) { + transfer_error_ = SDIO_Error_Type::OK; + current_state_ = Operational_State::BUSY; + is_rx_ = true; + multiblock_ = (count > 1); + + sdio_.clear_data_state_machine(Transfer_Direction::CARD_TO_SDIO); + + // Enable the interrupts + sdio_.set_interrupt_enable(Interrupt_Type::DTCRCERRIE, true); + sdio_.set_interrupt_enable(Interrupt_Type::DTTMOUTIE, true); + sdio_.set_interrupt_enable(Interrupt_Type::RXOREIE, true); + sdio_.set_interrupt_enable(Interrupt_Type::DTENDIE, true); + sdio_.set_interrupt_enable(Interrupt_Type::STBITEIE, true); + + total_bytes_ = BLOCK_SIZE * count; + + // Set DMA transfer parameters + set_dma_parameters(buf, (total_bytes_ / 4U), false); + sdio_.set_dma_enable(true); + + if (card_type_ != Card_Type::SDCARD_HIGH_CAPACITY) { + address *= 512U; + } + + // CMD16 set card block size + if (send_command_and_check(Command_Index::CMD16, BLOCK_SIZE, Command_Response::RSP_SHORT, + Wait_Type::WT_NONE, [this, cmd = Command_Index::CMD16]() { + return get_r1_result(cmd); + }) != SDIO_Error_Type::OK) { + sdio_.clear_multiple_interrupt_flags(clear_common_flags); + current_state_ = Operational_State::READY; + return SDIO_Error_Type::CMD16_FAILED; + } + + Block_Size block_size = get_data_block_size_index(BLOCK_SIZE); + + sdio_.set_data_state_machine_and_send(Data_Timeout, total_bytes_, block_size, + Transfer_Mode::BLOCK, Transfer_Direction::CARD_TO_SDIO, true); + + // CMD17/CMD18 (READ_SINGLE_BLOCK/READ_MULTIPLE_BLOCKS) send read command + Command_Index read_cmd = (count > 1U) ? Command_Index::CMD18 : Command_Index::CMD17; + if (send_command_and_check(read_cmd, address, + Command_Response::RSP_SHORT, Wait_Type::WT_NONE, [this, cmd = read_cmd]() { + return get_r1_result(cmd); + }) != SDIO_Error_Type::OK) { + sdio_.clear_multiple_interrupt_flags(clear_common_flags); + current_state_ = Operational_State::READY; + return (count > 1U) ? SDIO_Error_Type::CMD18_FAILED : SDIO_Error_Type::CMD17_FAILED; + } + return SDIO_Error_Type::OK; + } else { + return SDIO_Error_Type::BUSY; + } +} + +auto CardDMA::write(uint8_t* buf, uint32_t address, uint32_t count) -> SDIO_Error_Type { + // Enable the interrupts + sdio_.set_interrupt_enable(Interrupt_Type::DTCRCERRIE, true); + sdio_.set_interrupt_enable(Interrupt_Type::DTTMOUTIE, true); + sdio_.set_interrupt_enable(Interrupt_Type::STBITEIE, true); + sdio_.set_interrupt_enable(Interrupt_Type::TXUREIE, true); + + if (card_type_ != Card_Type::SDCARD_HIGH_CAPACITY) { + address *= 512U; + } + + // CMD16 + if (send_command_and_check(Command_Index::CMD16, BLOCK_SIZE, Command_Response::RSP_SHORT, + Wait_Type::WT_NONE, [this, cmd = Command_Index::CMD16]() { + return get_r1_result(cmd); + }) != SDIO_Error_Type::OK) { + sdio_.clear_multiple_interrupt_flags(clear_common_flags); + return SDIO_Error_Type::CMD16_FAILED; + } + + // CMD25/CMD24 (WRITE_MULTIPLE_BLOCK/WRITE_BLOCK) send write command + Command_Index write_cmd = (count > 1U) ? Command_Index::CMD25 : Command_Index::CMD24; + if (send_command_and_check(write_cmd, address, Command_Response::RSP_SHORT, + Wait_Type::WT_NONE, [this, cmd = write_cmd]() { + return get_r1_result(cmd); + }) != SDIO_Error_Type::OK) { + sdio_.clear_multiple_interrupt_flags(clear_common_flags); + return (count > 1U) ? SDIO_Error_Type::CMD25_FAILED : SDIO_Error_Type::CMD24_FAILED; + } + + total_bytes_ = BLOCK_SIZE * count; + // Start DMA transfer + set_dma_parameters(buf, (total_bytes_ / 4U), true); + sdio_.set_dma_enable(true); + + sdio_.clear_data_state_machine(Transfer_Direction::SDIO_TO_CARD); + Block_Size block_size = get_data_block_size_index(total_bytes_); + + sdio_.set_data_state_machine_and_send(Data_Timeout, total_bytes_, block_size, + Transfer_Mode::BLOCK, Transfer_Direction::SDIO_TO_CARD, true); + + while (dma_.get_flag(dma::Status_Flags::FLAG_FTFIF) || dma_.get_flag(dma::Status_Flags::FLAG_ERRIF)) { + // Wait for the IRQ handler to clear + } + + return SDIO_Error_Type::OK; +} + +auto CardDMA::erase(uint32_t address_start, uint32_t address_end) -> SDIO_Error_Type { + SDIO_Error_Type result = SDIO_Error_Type::OK; + + // Card command classes CSD + uint8_t temp_byte = static_cast((sdcard_csd_[1] & (0xFFU << 24U)) >> 24U); + uint16_t classes = static_cast(temp_byte << 4U); + temp_byte = static_cast((sdcard_csd_[1] & (0xFFU << 16U)) >> 16U); + classes |= static_cast((temp_byte & 0xF0U) >> 4U); + + if ((classes & (1U << static_cast(Card_Command_Class::ERASE))) == Clear) { + return SDIO_Error_Type::UNSUPPORTED_FUNCTION; + } + + uint32_t delay_time = 120000U / sdio_.get_clock_divider(); + + if (sdio_.get_response(Response_Type::RESPONSE0) & Card_Locked) { + return SDIO_Error_Type::LOCK_UNLOCK_FAILED; + } + + // Size is fixed at 512 bytes for SDHC + if (card_type_ != Card_Type::SDCARD_HIGH_CAPACITY) { + address_start *= 512U; + address_end *= 512U; + } + + if ((card_type_ == Card_Type::SDCARD_STANDARD_CAPACITY) || (card_type_ == Card_Type::SDCARD_HIGH_CAPACITY)) { + // CMD32 (ERASE_WR_BLK_START) + if (send_command_and_check(Command_Index::CMD32, address_start, Command_Response::RSP_SHORT, + Wait_Type::WT_NONE, [this, cmd = Command_Index::CMD32]() { + return get_r1_result(cmd); + }) != SDIO_Error_Type::OK) { + return SDIO_Error_Type::CMD32_FAILED; + } + // CMD33 (ERASE_WR_BLK_END) + if (send_command_and_check(Command_Index::CMD33, address_end, Command_Response::RSP_SHORT, + Wait_Type::WT_NONE, [this, cmd = Command_Index::CMD33]() { + return get_r1_result(cmd); + }) != SDIO_Error_Type::OK) { + return SDIO_Error_Type::CMD33_FAILED; + } + } + + // CMD38 (ERASE) + if (send_command_and_check(Command_Index::CMD38, 0U, Command_Response::RSP_SHORT, + Wait_Type::WT_NONE, [this, cmd = Command_Index::CMD38]() { + return get_r1_result(cmd); + }) != SDIO_Error_Type::OK) { + return SDIO_Error_Type::CMD38_FAILED; + } + + // Loop until the counter reaches the calculated time + for (uint32_t count = 0U; count < delay_time; count++) {} + + Card_State card_state; + result = get_card_state(&card_state); + while ((result == SDIO_Error_Type::OK) && ((card_state == Card_State::PROGRAMMING) || (card_state == Card_State::RECEIVE_DATA))) { + result = get_card_state(&card_state); + } + + return result; +} + +void CardDMA::handle_interrupts() { + transfer_error_ = SDIO_Error_Type::OK; + + if (sdio_.get_interrupt_flag(Interrupt_Flags::FLAG_INTR_DTEND)) { + sdio_.clear_interrupt_flag(Clear_Flags::FLAG_DTENDC); + // Disable all interrupts + disable_all_interrupts(); + sdio_.set_data_state_machine_enable(false); + + if (multiblock_ && !is_rx_) { + transfer_error_ = stop_transfer(); + if (transfer_error_ != SDIO_Error_Type::OK) { + return; + } + } + + if (!is_rx_) { + sdio_.set_dma_enable(false); + current_state_ = Operational_State::READY; + } + } else if (sdio_.get_interrupt_flag(Interrupt_Flags::FLAG_INTR_DTCRCERR) || + sdio_.get_interrupt_flag(Interrupt_Flags::FLAG_INTR_DTTMOUT) || + sdio_.get_interrupt_flag(Interrupt_Flags::FLAG_INTR_STBITE) || + sdio_.get_interrupt_flag(Interrupt_Flags::FLAG_INTR_TXURE) || + sdio_.get_interrupt_flag(Interrupt_Flags::FLAG_INTR_RXORE)) { + + if (sdio_.get_interrupt_flag(Interrupt_Flags::FLAG_INTR_DTCRCERR)) { + transfer_error_ = SDIO_Error_Type::DATA_CRC_ERROR; + } + if (sdio_.get_interrupt_flag(Interrupt_Flags::FLAG_INTR_DTTMOUT)) { + transfer_error_ = SDIO_Error_Type::DATA_TIMEOUT; + } + if (sdio_.get_interrupt_flag(Interrupt_Flags::FLAG_INTR_STBITE)) { + transfer_error_ = SDIO_Error_Type::START_BIT_ERROR; + } + if (sdio_.get_interrupt_flag(Interrupt_Flags::FLAG_INTR_TXURE)) { + transfer_error_ = SDIO_Error_Type::TX_FIFO_UNDERRUN; + } + if (sdio_.get_interrupt_flag(Interrupt_Flags::FLAG_INTR_RXORE)) { + transfer_error_ = SDIO_Error_Type::RX_FIFO_OVERRUN; + } + sdio_.clear_multiple_interrupt_flags(clear_data_flags); + sdio_.clear_interrupt_flag(Clear_Flags::FLAG_STBITEC); + disable_all_interrupts(); + + dma_.set_transfer_abandon(); + } +} + +auto CardDMA::select_deselect() -> SDIO_Error_Type { + // CMD7 (SELECT/DESELECT_CARD) + if (send_command_and_check(Command_Index::CMD7, static_cast(sdcard_rca_ << RCA_Shift), + Command_Response::RSP_SHORT, Wait_Type::WT_NONE, [this, cmd = Command_Index::CMD7]() { + return get_r1_result(cmd); + }) != SDIO_Error_Type::OK) { + return SDIO_Error_Type::CMD7_FAILED; + } + return SDIO_Error_Type::OK; +} + +auto CardDMA::get_card_interface_status(uint32_t* status) -> SDIO_Error_Type { + if (status == nullptr) return SDIO_Error_Type::INVALID_PARAMETER; + + // CMD13 (SEND_STATUS) + if (send_command_and_check(Command_Index::CMD13, static_cast(sdcard_rca_ << RCA_Shift), + Command_Response::RSP_SHORT, Wait_Type::WT_NONE, [this, cmd = Command_Index::CMD13]() { + return get_r1_result(cmd); + }) != SDIO_Error_Type::OK) { + return SDIO_Error_Type::CMD13_FAILED; + } + + *status = sdio_.get_response(Response_Type::RESPONSE0); + + return SDIO_Error_Type::OK; +} + +auto CardDMA::get_sdcard_status(uint32_t* status) -> SDIO_Error_Type { + uint32_t count = 0U; + + // CMD16 (SET_BLOCKLEN) + if (send_command_and_check(Command_Index::CMD16, 64U, + Command_Response::RSP_SHORT, Wait_Type::WT_NONE, [this, cmd = Command_Index::CMD16]() { + return get_r1_result(cmd); + }) != SDIO_Error_Type::OK) { + return SDIO_Error_Type::CMD16_FAILED; + } + + // CMD55 (APP_CMD) + if (send_command_and_check(Command_Index::CMD55, static_cast(sdcard_rca_ << RCA_Shift), + Command_Response::RSP_SHORT, Wait_Type::WT_NONE, [this, cmd = Command_Index::CMD55]() { + return get_r1_result(cmd); + }) != SDIO_Error_Type::OK) { + return SDIO_Error_Type::CMD55_FAILED; + } + + sdio_.set_data_state_machine_and_send(Data_Timeout, 64U, Block_Size::BYTES_64, Transfer_Mode::BLOCK, Transfer_Direction::CARD_TO_SDIO, true); + + // ACMD13 (SD_STATUS) + if (send_command_and_check(Command_Index::ACMD13, 0U, + Command_Response::RSP_SHORT, Wait_Type::WT_NONE, [this, cmd = Command_Index::ACMD13]() { + return get_r1_result(cmd); + }) != SDIO_Error_Type::OK) { + return SDIO_Error_Type::ACMD13_FAILED; + } + + while (!sdio_.check_scr_flags()) { + if (sdio_.get_flag(Status_Flags::FLAG_RFH)) { + for (count = 0U; count < FIFO_Half_Words; count++) { + *(status + count) = sdio_.read_fifo_word(); + } + status += FIFO_Half_Words; + } + + //SDIO_Error_Type result = SDIO_Error_Type::OK; + if (sdio_.get_flag(Status_Flags::FLAG_DTCRCERR)) { + sdio_.clear_flag(Clear_Flags::FLAG_DTCRCERRC); + return SDIO_Error_Type::DATA_CRC_ERROR; + } else if (sdio_.get_flag(Status_Flags::FLAG_DTTMOUT)) { + sdio_.clear_flag(Clear_Flags::FLAG_DTTMOUTC); + return SDIO_Error_Type::DATA_TIMEOUT; + } else if (sdio_.get_flag(Status_Flags::FLAG_RXORE)) { + sdio_.clear_flag(Clear_Flags::FLAG_RXOREC); + return SDIO_Error_Type::RX_FIFO_OVERRUN; + } else if (sdio_.get_flag(Status_Flags::FLAG_STBITE)) { + sdio_.clear_flag(Clear_Flags::FLAG_STBITEC); + return SDIO_Error_Type::START_BIT_ERROR; + } + while (sdio_.get_flag(Status_Flags::FLAG_RXDTVAL)) { + *status = sdio_.read_fifo_word(); + ++status; + } + + sdio_.clear_multiple_interrupt_flags(clear_data_flags); + status -= 16U; + for (count = 0U; count < 16U; count++) { + status[count] = ((status[count] & 0xFFU) << 24U) | + ((status[count] & (0xFFU << 8U)) << 8U) | + ((status[count] & (0xFFU << 16U)) >> 8U) | + ((status[count] & (0xFFU << 24U)) >> 24U); + } + } + + return SDIO_Error_Type::OK; +} + +void CardDMA::check_dma_complete() { + while ((dma_.get_flag(dma::Status_Flags::FLAG_FTFIF)) || (dma_.get_flag(dma::Status_Flags::FLAG_ERRIF))) { + // Wait for the IRQ handler to clear + } +} + +auto CardDMA::stop_transfer() -> SDIO_Error_Type { + // CMD12 (STOP_TRANSMISSION) + if (send_command_and_check(Command_Index::CMD12, 0, Command_Response::RSP_SHORT, Wait_Type::WT_NONE, [this, cmd = Command_Index::CMD12]() { + return get_r1_result(cmd); + }) != SDIO_Error_Type::OK) { + return SDIO_Error_Type::CMD12_FAILED; + } + return SDIO_Error_Type::OK; +} + +auto CardDMA::get_transfer_state() -> Transfer_State { + Transfer_State transfer_state = Transfer_State::IDLE; + if (sdio_.get_flag(Status_Flags::FLAG_TXRUN) | sdio_.get_flag(Status_Flags::FLAG_RXRUN)) { + transfer_state = Transfer_State::BUSY; + } + + return transfer_state; +} + +[[nodiscard]] auto CardDMA::get_card_capacity() const -> uint32_t { + auto extract_bits = [](uint32_t value, uint8_t start_bit, uint8_t length) -> uint32_t { + return (value >> start_bit) & ((1U << length) - 1U); + }; + + uint32_t capacity = 0U; + uint32_t device_size = 0U; + + if (card_type_ == Card_Type::SDCARD_STANDARD_CAPACITY) { + // Extract fields from CSD data using bit manipulation + uint8_t device_size_high = static_cast(extract_bits(sdcard_csd_[1], 8U, 2U)); // Bits [73:72] + uint8_t device_size_mid = static_cast(extract_bits(sdcard_csd_[1], 0U, 8U)); // Bits [71:64] + uint8_t device_size_low = static_cast(extract_bits(sdcard_csd_[2], 24U, 2U)); // Bits [63:62] + + device_size = static_cast((device_size_high) << 10U) | + static_cast((device_size_mid) << 2U) | + static_cast((device_size_low)); + + uint8_t device_size_multiplier_high = static_cast(extract_bits(sdcard_csd_[2], 16U, 2U)); // Bits [49:48] + uint8_t device_size_multiplier_low = static_cast(extract_bits(sdcard_csd_[2], 8U, 1U)); // Bit [47] + uint8_t device_size_multiplier = static_cast((device_size_multiplier_high << 1U) | device_size_multiplier_low); + uint8_t read_block_length = static_cast(extract_bits(sdcard_csd_[1], 16U, 4U)); // Bits [83:80] + + // Capacity = (device_size + 1) * MULT * BLOCK_LEN + uint32_t mult = static_cast(1U << (device_size_multiplier + 2U)); // MULT = 2 ^ (C_SIZE_MULT + 2) + uint32_t block_length = static_cast(1U << read_block_length); // BLOCK_LEN = 2 ^ READ_BL_LEN + + capacity = (device_size + 1U) * mult * block_length; + capacity /= KILOBYTE; // Convert capacity to kilobytes + + } else if (card_type_ == Card_Type::SDCARD_HIGH_CAPACITY) { + // High-capacity card calculation + uint8_t device_size_high = static_cast(extract_bits(sdcard_csd_[1], 0U, 6U)); // Bits [69:64] + uint8_t device_size_mid = static_cast(extract_bits(sdcard_csd_[2], 24U, 8U)); // Bits [55:48] + uint8_t device_size_low = static_cast(extract_bits(sdcard_csd_[2], 16U, 8U)); // Bits [47:40] + + device_size = static_cast((device_size_high) << 16U) | + static_cast((device_size_mid) << 8U) | + static_cast(device_size_low); + + // Capacity in kilobytes + capacity = (device_size + 1U) * BLOCK_SIZE; + } + + return capacity; +} + +auto CardDMA::get_card_specific_data(Card_Info* info) -> SDIO_Error_Type { + if (info == nullptr) return SDIO_Error_Type::INVALID_PARAMETER; + + // Store basic card information + info->type = card_type_; + info->relative_address = sdcard_rca_; + + // Helper function to convert 32-bit registers to byte array + auto convert_registers_to_bytes = [](const uint32_t* registers, uint8_t* bytes, size_t reg_count) { + for (size_t i = 0U; i < reg_count; ++i) { + for (size_t j = 0U; j < 4U; ++j) { + bytes[i * 4U + j] = (registers[i] >> (24U - j * 8U)) & 0xFFU; + } + } + }; + + // Process CID data + uint8_t cid_bytes[16]; + convert_registers_to_bytes(sdcard_cid_, cid_bytes, 4); + + info->cid = { + .manufacture_id = cid_bytes[0], + .oem_id = static_cast( + (cid_bytes[1] << 8U) | + cid_bytes[2] + ), + .name0 = static_cast( + (cid_bytes[3] << 24U) | + (cid_bytes[4] << 16U) | + (cid_bytes[5] << 8U) | + cid_bytes[6] + ), + .name1 = cid_bytes[7], + .revision = cid_bytes[8], + .serial_number = static_cast( + (cid_bytes[9] << 24U) | + (cid_bytes[10] << 16U) | + (cid_bytes[11] << 8U) | + cid_bytes[12] + ), + .manufacture_date = static_cast( + ((cid_bytes[13] & 0x0FU) << 8U) | + cid_bytes[14] + ), + .checksum = static_cast(((cid_bytes[15] & 0xFEU) >> 1U)) + }; + + // Process CSD data + uint8_t csd_bytes[16]; + convert_registers_to_bytes(sdcard_csd_, csd_bytes, 4U); + + // Fill common CSD fields + info->csd = { + .transfer_speed = csd_bytes[3], + .card_command_class = static_cast((csd_bytes[4] << 4U) | ((csd_bytes[5] & 0xF0U) >> 4U)), + }; + + // Process card-type specific CSD fields and calculate capacity + if (card_type_ == Card_Type::SDCARD_STANDARD_CAPACITY) { + process_sdsc_specific_csd(info, csd_bytes); + } else if (card_type_ == Card_Type::SDCARD_HIGH_CAPACITY) { + process_sdhc_specific_csd(info, csd_bytes); + } + + // Fill remaining common CSD fields + process_common_csd_tail(info, csd_bytes); + + return SDIO_Error_Type::OK; +} + +constexpr auto CardDMA::get_data_block_size_index(uint16_t size) -> Block_Size { + if (size < 1 || size > 16384) return Block_Size::BYTES_1; + + // Check if size is a power of two + if ((size & (size - 1)) != 0) return Block_Size::BYTES_1; + + // Count trailing zeros to find the index + uint16_t index = 0; + while ((size >>= 1) != 0) ++index; + + return static_cast(index); +} + +auto CardDMA::get_card_state(Card_State* card_state) -> SDIO_Error_Type { + // CMD13 (SEND_STATUS) + if (send_command_and_check(Command_Index::CMD13, static_cast(sdcard_rca_ << RCA_Shift), + Command_Response::RSP_SHORT, Wait_Type::WT_NONE, [this, cmd = Command_Index::CMD13]() { + return get_r1_result(cmd); + }) != SDIO_Error_Type::OK) { + return SDIO_Error_Type::CMD13_FAILED; + } + + uint32_t response = sdio_.get_response(Response_Type::RESPONSE0); + *card_state = static_cast((response >> 9U) & CardStateMask); + if ((response & All_R1_Error_Bits) == 0U) { + return SDIO_Error_Type::OK; + } + + if (response & All_R1_Error_Bits) { + for (const auto& entry : errorTableR1) { + if (TEST(response, entry.bit_position)) { + return entry.errorType; + } + } + return SDIO_Error_Type::ERROR; + } + + return SDIO_Error_Type::OK; +} + +auto CardDMA::get_command_sent_result() -> SDIO_Error_Type { + constexpr uint32_t MAX_TIMEOUT = 0x00FFFFFFU; + uint32_t timeout = MAX_TIMEOUT; + + // Wait for command sent flag or timeout + while (!sdio_.get_flag(Status_Flags::FLAG_CMDSEND) && timeout) { + --timeout; + } + + // Check if timeout occurred + if (timeout == 0U) { + return SDIO_Error_Type::RESPONSE_TIMEOUT; + } + + // Clear command flags and return success + sdio_.clear_multiple_interrupt_flags(clear_command_flags); + return SDIO_Error_Type::OK; +} + +auto CardDMA::check_sdio_status(Command_Index index, bool check_index, bool ignore_crc) -> SDIO_Error_Type { + // Wait until one of the relevant flags is set + if (!sdio_.wait_cmd_flags()) { + return SDIO_Error_Type::RESPONSE_TIMEOUT; + } + + // Check the cmd received bit first, since noise can sometimes + // cause the timeout bit to get erroneously set + // If cmd was received we can safely ignore other checks + if (sdio_.get_flag(Status_Flags::FLAG_CMDRECV)) { + // If cmd was received, check the index + // Responses that dont do an index check will send an invalid cmd index + if (check_index && (index != Command_Index::INVALID)) { + uint8_t received_idx = sdio_.get_command_index(); + if (received_idx != static_cast(index)) { + sdio_.clear_multiple_interrupt_flags(clear_command_flags); + return SDIO_Error_Type::ILLEGAL_COMMAND; + } + } + + // Command received successfully + sdio_.clear_multiple_interrupt_flags(clear_command_flags); + return SDIO_Error_Type::OK; + } + + // Check for timeout + if (sdio_.get_flag(Status_Flags::FLAG_CMDTMOUT)) { + sdio_.clear_flag(Clear_Flags::FLAG_CMDTMOUTC); + return SDIO_Error_Type::RESPONSE_TIMEOUT; + } + + // Check for CRC error if not ignored + if (!ignore_crc && sdio_.get_flag(Status_Flags::FLAG_CCRCERR)) { + sdio_.clear_flag(Clear_Flags::FLAG_CCRCERRC); + return SDIO_Error_Type::COMMAND_CRC_ERROR; + } + + // Final index check (redundant with the first check, but keeping for safety) + // This code path should rarely be taken due to the earlier checks + if (check_index && (index != Command_Index::INVALID)) { + uint8_t received_idx = sdio_.get_command_index(); + if (received_idx != static_cast(index)) { + sdio_.clear_multiple_interrupt_flags(clear_command_flags); + return SDIO_Error_Type::ILLEGAL_COMMAND; + } + } + + // Clear all flags and return success + sdio_.clear_multiple_interrupt_flags(clear_command_flags); + return SDIO_Error_Type::OK; +} + +auto CardDMA::get_r1_result(Command_Index index) -> SDIO_Error_Type { + SDIO_Error_Type result = check_sdio_status(index, true, false); + if (result != SDIO_Error_Type::OK) return result; + + // Get the R1 response and check for errors + uint32_t response = sdio_.get_response(Response_Type::RESPONSE0); + + if (response & All_R1_Error_Bits) { + for (const auto& entry : errorTableR1) { + if (TEST(response, entry.bit_position)) { + return entry.errorType; + } + } + return SDIO_Error_Type::ERROR; + } + + return SDIO_Error_Type::OK; +} + +auto CardDMA::get_r6_result(Command_Index index, uint16_t* rca) -> SDIO_Error_Type { + SDIO_Error_Type result = check_sdio_status(index, true, false); + if (result != SDIO_Error_Type::OK) return result; + + uint32_t response = sdio_.get_response(Response_Type::RESPONSE0); + + if (response & R6_Error_Bits) { + for (const auto& entry : errorTableR6) { + if (TEST(response, entry.bit_position)) { + return entry.errorType; + } + } + return SDIO_Error_Type::ERROR; + } + *rca = static_cast(response >> 16U); + + return SDIO_Error_Type::OK; +} + +auto CardDMA::get_scr(uint16_t rca, uint32_t* scr) -> SDIO_Error_Type { + uint32_t temp_scr[2] = {0U, 0U}; + uint32_t index_scr = 0U; + + // CMD16 (SET_BLOCKLEN) + if (send_command_and_check(Command_Index::CMD16, 8U, Command_Response::RSP_SHORT, + Wait_Type::WT_NONE, [this, cmd = Command_Index::CMD16]() { + return get_r1_result(cmd); + }) != SDIO_Error_Type::OK) { + return SDIO_Error_Type::CMD16_FAILED; + } + + // CMD55 (APP_CMD) + if (send_command_and_check(Command_Index::CMD55, static_cast(sdcard_rca_ << RCA_Shift), + Command_Response::RSP_SHORT, Wait_Type::WT_NONE, [this, cmd = Command_Index::CMD55]() { + return get_r1_result(cmd); + }) != SDIO_Error_Type::OK) { + return SDIO_Error_Type::CMD55_FAILED; + } + + // Set data parameters + sdio_.set_data_state_machine_and_send(Data_Timeout, 8U, Block_Size::BYTES_8, + Transfer_Mode::BLOCK, Transfer_Direction::CARD_TO_SDIO, true); + + // ACMD51 (SEND_SCR) + if (send_command_and_check(Command_Index::ACMD51, 0U, Command_Response::RSP_SHORT, + Wait_Type::WT_NONE, [this, cmd = Command_Index::ACMD51]() { + return get_r1_result(cmd); + }) != SDIO_Error_Type::OK) { + return SDIO_Error_Type::ACMD51_FAILED; + } + + // Store SCR + while (!sdio_.check_scr_flags()) { + if (sdio_.get_flag(Status_Flags::FLAG_RXDTVAL)) { + temp_scr[index_scr++] = sdio_.read_fifo_word(); + } + } + + // Check for errors + if (sdio_.get_flag(Status_Flags::FLAG_DTTMOUT)) { + sdio_.clear_flag(Clear_Flags::FLAG_DTTMOUTC); + return SDIO_Error_Type::DATA_TIMEOUT; + } + else if (sdio_.get_flag(Status_Flags::FLAG_DTCRCERR)) { + sdio_.clear_flag(Clear_Flags::FLAG_DTCRCERRC); + return SDIO_Error_Type::DATA_CRC_ERROR; + } + else if (sdio_.get_flag(Status_Flags::FLAG_RXORE)) { + sdio_.clear_flag(Clear_Flags::FLAG_RXOREC); + return SDIO_Error_Type::RX_FIFO_OVERRUN; + } + + sdio_.clear_multiple_interrupt_flags(clear_data_flags); + + constexpr uint32_t BYTE0_MASK = 0xFFU; + constexpr uint32_t BYTE1_MASK = 0xFF00U; + constexpr uint32_t BYTE2_MASK = 0xFF0000U; + constexpr uint32_t BYTE3_MASK = 0xFF000000U; + + // Byte-swap the SCR values (convert from big-endian to little-endian) + scr[0] = ((temp_scr[1] & BYTE0_MASK) << 24) | + ((temp_scr[1] & BYTE1_MASK) << 8) | + ((temp_scr[1] & BYTE2_MASK) >> 8) | + ((temp_scr[1] & BYTE3_MASK) >> 24); + + scr[1] = ((temp_scr[0] & BYTE0_MASK) << 24) | + ((temp_scr[0] & BYTE1_MASK) << 8) | + ((temp_scr[0] & BYTE2_MASK) >> 8) | + ((temp_scr[0] & BYTE3_MASK) >> 24); + + return SDIO_Error_Type::OK; +} + +// DMA for rx/tx is always DMA1 channel 3 +void CardDMA::set_dma_parameters(uint8_t* buf, uint32_t count, bool is_write) { + constexpr uint32_t flag_bits = (1U << static_cast(dma::INTF_Bits::GIF3)); + dma_.clear_flags(flag_bits); + + // Disable and reset DMA + dma_.set_channel_enable(false); + dma_.clear_channel(); + + dma_.init(dma::DMA_Config{ + .count = count, + .memory_address = static_cast(reinterpret_cast(buf)), + .peripheral_address = static_cast(reinterpret_cast(sdio_.reg_address(SDIO_Regs::FIFO))), + .peripheral_bit_width = dma::Bit_Width::WIDTH_32BIT, + .memory_bit_width = dma::Bit_Width::WIDTH_32BIT, + .peripheral_increase = dma::Increase_Mode::INCREASE_DISABLE, + .memory_increase = dma::Increase_Mode::INCREASE_ENABLE, + .channel_priority = dma::Channel_Priority::MEDIUM_PRIORITY, + .direction = is_write ? dma::Transfer_Direction::M2P : dma::Transfer_Direction::P2M + }); + + dma_.set_memory_to_memory_enable(false); + dma_.set_circulation_mode_enable(false); + + // Enable DMA interrupts for transfer complete and error + dma_.set_interrupt_enable(dma::Interrupt_Type::INTR_FTFIE, true); + dma_.set_interrupt_enable(dma::Interrupt_Type::INTR_ERRIE, true); + + // Start the DMA channel + dma_.set_channel_enable(true); +} + +auto CardDMA::wait_for_card_ready() -> SDIO_Error_Type { + constexpr uint32_t MAX_TIMEOUT = 0x00FFFFFFU; + uint32_t timeout = MAX_TIMEOUT; + uint32_t response = sdio_.get_response(Response_Type::RESPONSE0); + + // Poll until card is ready for data or timeout occurs + while (((response & static_cast(R1_Status::READY_FOR_DATA)) == 0U) && timeout) { + --timeout; + + // CMD13 (SEND_STATUS) + if (send_command_and_check(Command_Index::CMD13, static_cast(sdcard_rca_ << RCA_Shift), + Command_Response::RSP_SHORT, Wait_Type::WT_NONE, [this, cmd = Command_Index::CMD13]() { + return get_r1_result(cmd); + }) != SDIO_Error_Type::OK) { + return SDIO_Error_Type::CMD13_FAILED; + } + + // Get updated response + response = sdio_.get_response(Response_Type::RESPONSE0); + } + + // Return error if timeout occurred, otherwise success + return timeout ? SDIO_Error_Type::OK : SDIO_Error_Type::ERROR; +} + +} // namespace sdio + +sdio::CardDMA& CardDMA_I = sdio::CardDMA::get_instance(); + +#endif // ARDUINO_ARCH_MFL diff --git a/Marlin/src/HAL/GD32_MFL/sd/SDCard.h b/Marlin/src/HAL/GD32_MFL/sd/SDCard.h new file mode 100644 index 000000000000..de28c4080972 --- /dev/null +++ b/Marlin/src/HAL/GD32_MFL/sd/SDCard.h @@ -0,0 +1,222 @@ +// +// MFL gd32f30x SDCARD using DMA through SDIO in C++ +// +// Copyright (C) 2025 B. Mourit +// +// This software is free software: you can redistribute it and/or modify it under the terms of the +// GNU Lesser General Public License as published by the Free Software Foundation, +// either version 3 of the License, or (at your option) any later version. +// +// This software 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License along with this software. +// If not, see . +// +#pragma once + +#include "../../../inc/MarlinConfig.h" + +#include + +namespace sdio { + +class DMA; + +class CardDMA { +public: + static auto get_instance() -> CardDMA&; + + // Initialization + auto init() -> SDIO_Error_Type; + auto card_init() -> SDIO_Error_Type; + + // Startup and shutdown procedures + auto begin_startup_procedure() -> SDIO_Error_Type; + void begin_shutdown_procedure(); + + // Configuration + auto set_hardware_bus_width(Bus_Width width) -> SDIO_Error_Type; + auto send_bus_width_command(uint32_t width_value) -> SDIO_Error_Type; + + // Main read/write/erase functions + auto read(uint8_t* buf, uint32_t address, uint32_t count) -> SDIO_Error_Type; + auto write(uint8_t* buf, uint32_t address, uint32_t count) -> SDIO_Error_Type; + auto erase(uint32_t address_start, uint32_t address_end) -> SDIO_Error_Type; + + // Card select + auto select_deselect() -> SDIO_Error_Type; + + // Status and state + auto get_card_interface_status(uint32_t* status) -> SDIO_Error_Type; + auto get_sdcard_status(uint32_t* status) -> SDIO_Error_Type; + auto get_transfer_state() -> Transfer_State; + auto get_card_state(Card_State* card_state) -> SDIO_Error_Type; + auto check_sdio_status(Command_Index index = Command_Index::INVALID, bool check_index = false, bool ignore_crc = false) -> SDIO_Error_Type; + + // DMA + void set_dma_parameters(uint8_t* buf, uint32_t count, bool is_write); + void check_dma_complete(); + + // Stop transfer + auto stop_transfer() -> SDIO_Error_Type; + + // Card information + auto get_card_specific_data(Card_Info* info) -> SDIO_Error_Type; + constexpr auto get_data_block_size_index(uint16_t size) -> Block_Size; + [[nodiscard]] auto get_card_capacity() const -> uint32_t; + + // SDIO configuration + void sdio_configure(const SDIO_Config config) { sdio_.init(config); } + + // Interrupt handler + void handle_interrupts(); + + // Variable stored parameters + auto get_scr(uint16_t rca, uint32_t* scr) -> SDIO_Error_Type; + auto store_cid() -> SDIO_Error_Type; + auto store_csd() -> SDIO_Error_Type; + + // Inlined accessor methods + auto get_config() -> SDIO_Config& { return config_; } + auto get_dma_instance() -> dma::DMA& { return dma_; } + void set_data_end_interrupt() { sdio_.set_interrupt_enable(Interrupt_Type::DTENDIE, true); } + void set_sdio_dma_enable(bool enable) { sdio_.set_dma_enable(enable); } + auto get_is_sdio_rx() -> bool { return is_rx_; } + void clear_sdio_data_flags() { sdio_.clear_multiple_interrupt_flags(clear_data_flags); } + void clear_sdio_cmd_flags() { sdio_.clear_multiple_interrupt_flags(clear_command_flags); } + void clear_sdio_common_flags() { sdio_.clear_multiple_interrupt_flags(clear_common_flags); } + auto get_state() -> Operational_State { return current_state_; } + void set_state(Operational_State state) { current_state_ = state; } + void set_transfer_error(SDIO_Error_Type error) { transfer_error_ = error; } + void set_transfer_end(bool end) { transfer_end_ = end; }; + + auto set_desired_clock(uint32_t desired_clock, bool wide_bus, bool low_power) -> SDIO_Error_Type { + sdio_.init(SDIO_Config{ + .desired_clock = desired_clock, + .enable_bypass = false, + .enable_powersave = low_power, + .enable_hwclock = false, + .clock_edge = Clock_Edge::RISING_EDGE, + .width = wide_bus ? Bus_Width::WIDTH_4BIT : Bus_Width::WIDTH_1BIT + }); + + sync_domains(); + desired_clock_ = desired_clock; + + return SDIO_Error_Type::OK; + } + +private: + CardDMA(); + + // Prevent copying or assigning + CardDMA(const CardDMA&) = delete; + auto operator=(const CardDMA&) -> CardDMA& = delete; + + // Helper function + auto wait_for_card_ready() -> SDIO_Error_Type; + + // Member variables + alignas(4) uint32_t sdcard_csd_[4]; + alignas(4) uint32_t sdcard_cid_[4]; + alignas(4) uint32_t sdcard_scr_[2]; + uint32_t desired_clock_; + uint32_t total_bytes_; + SDIO& sdio_; + SDIO_Config& config_; + const dma::DMA_Base dmaBase_; + const dma::DMA_Channel dmaChannel_; + dma::DMA& dma_; + uint16_t sdcard_rca_; + SDIO_Error_Type transfer_error_; + Interface_Version interface_version_; + Card_Type card_type_; + Operational_State current_state_; + bool transfer_end_; + bool multiblock_; + bool is_rx_; + + // Private helper methods + auto validate_voltage() -> SDIO_Error_Type; + auto get_command_sent_result() -> SDIO_Error_Type; + auto get_r1_result(Command_Index index) -> SDIO_Error_Type; + auto get_r6_result(Command_Index index, uint16_t* rca) -> SDIO_Error_Type; + auto get_r7_result() -> SDIO_Error_Type { return check_sdio_status(Command_Index::INVALID, false, false); }; + void sync_domains() { delayMicroseconds(8); } + + auto validate_transfer_params(uint32_t* buf, uint16_t size) -> bool { + if (buf == nullptr) return false; + // Size must be > 0, <= 2048 and power of 2 + return size > 0U && size <= 2048U && !(size & (size - 1U)); + } + + void process_sdsc_specific_csd(Card_Info* info, const uint8_t* csd_bytes) { + const uint32_t device_size = ((csd_bytes[6] & 0x3U) << 10) | + (csd_bytes[7] << 2) | + ((csd_bytes[8] >> 6) & 0x3U); + + const uint8_t device_size_multiplier = ((csd_bytes[9] & 0x3U) << 1) | + ((csd_bytes[10] >> 7) & 0x1U); + + // Store calculated values + info->csd.device_size = device_size; + info->csd.device_size_multiplier = device_size_multiplier; + + // Calculate block size and capacity + info->block_size = 1U << info->csd.read_block_length; + info->capacity = (device_size + 1U) * + (1U << (device_size_multiplier + 2U)) * + info->block_size; + } + + void process_sdhc_specific_csd(Card_Info* info, const uint8_t* csd_bytes) { + info->csd.device_size = static_cast((csd_bytes[7] & 0x3FU) << 16) | + static_cast((csd_bytes[8]) << 8) | + static_cast(csd_bytes[9]); + + // Set block size and calculate capacity + info->block_size = BLOCK_SIZE; + info->capacity = static_cast((info->csd.device_size + 1U) * + BLOCK_SIZE * KILOBYTE); + } + + void process_common_csd_tail(Card_Info* info, const uint8_t* csd_bytes) { + // Calculate sector_size + info->csd.sector_size = static_cast(((csd_bytes[9] & 0x3FU) << 1) | + (csd_bytes[10] & 0x80U) >> 7); + + // Calculate speed_factor and write_block_length + info->csd.speed_factor = static_cast((csd_bytes[11] & 0x1CU) >> 2); + info->csd.write_block_length = static_cast(((csd_bytes[11] & 0x3U) << 2) | + ((csd_bytes[12] & 0xC0U) >> 6)); + + // Calculate checksum + info->csd.checksum = static_cast((csd_bytes[15] & 0xFEU) >> 1); + } + + void disable_all_interrupts() { + sdio_.set_interrupt_enable(Interrupt_Type::DTCRCERRIE, false); + sdio_.set_interrupt_enable(Interrupt_Type::DTTMOUTIE, false); + sdio_.set_interrupt_enable(Interrupt_Type::DTENDIE, false); + sdio_.set_interrupt_enable(Interrupt_Type::STBITEIE, false); + sdio_.set_interrupt_enable(Interrupt_Type::TFHIE, false); + sdio_.set_interrupt_enable(Interrupt_Type::RFHIE, false); + sdio_.set_interrupt_enable(Interrupt_Type::TXUREIE, false); + sdio_.set_interrupt_enable(Interrupt_Type::RXOREIE, false); + } + + template + auto send_command_and_check(Command_Index command, uint32_t argument, + Command_Response response, Wait_Type type, CheckFunc check_result) -> SDIO_Error_Type { + sdio_.set_command_state_machine(command, argument, response, type); + sync_domains(); + sdio_.set_command_state_machine_enable(true); + return check_result(); + } +}; + +} // namespace sdio + +extern sdio::CardDMA& CardDMA_I; diff --git a/Marlin/src/HAL/GD32_MFL/sd/sdio.cpp b/Marlin/src/HAL/GD32_MFL/sd/sdio.cpp new file mode 100644 index 000000000000..a474c1977e6c --- /dev/null +++ b/Marlin/src/HAL/GD32_MFL/sd/sdio.cpp @@ -0,0 +1,231 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2025 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 . + * + */ + +#include "../../platforms.h" + +#ifdef ARDUINO_ARCH_MFL + +#include "../../../inc/MarlinConfig.h" + +#if ENABLED(ONBOARD_SDIO) + +#include +#include +#include "SDCard.h" +#include "sdio.h" + +using namespace sdio; + +#define TARGET_CLOCK 6000000U +#define BLOCK_SIZE 512U +#define CARD_TIMEOUT 500 // ms +#define READ_RETRIES 3U + +inline constexpr uint32_t TARGET_SDIO_CLOCK = TARGET_CLOCK; +inline constexpr uint32_t SDIO_BLOCK_SIZE = BLOCK_SIZE; +inline constexpr uint32_t SD_TIMEOUT = CARD_TIMEOUT; +inline constexpr uint8_t SDIO_READ_RETRIES = READ_RETRIES; + +Card_State cardState = Card_State::READY; + +auto SDIO_SetBusWidth(Bus_Width width) -> bool { + return (CardDMA_I.set_hardware_bus_width(width) == SDIO_Error_Type::OK); +} + +void mfl_sdio_init() { + pinOpsPinout(SD_CMD_PinOps, static_cast(SDIO_CMD_PIN)); + pinOpsPinout(SD_CK_PinOps, static_cast(SDIO_CK_PIN)); + pinOpsPinout(SD_DATA0_PinOps, static_cast(SDIO_D0_PIN)); + pinOpsPinout(SD_DATA1_PinOps, static_cast(SDIO_D1_PIN)); + pinOpsPinout(SD_DATA2_PinOps, static_cast(SDIO_D2_PIN)); + pinOpsPinout(SD_DATA3_PinOps, static_cast(SDIO_D3_PIN)); + + NVIC_EnableIRQ(DMA1_Channel3_4_IRQn); + NVIC_EnableIRQ(SDIO_IRQn); +} + +bool SDIO_Init() { + SDIO_Error_Type result = SDIO_Error_Type::OK; + uint8_t retryCount = SDIO_READ_RETRIES; + + mfl_sdio_init(); + + uint8_t retries = retryCount; + for (;;) { + hal.watchdog_refresh(); + result = CardDMA_I.init(); + if (result == SDIO_Error_Type::OK) break; + if (!--retries) return false; + } + + CardDMA_I.set_desired_clock(TARGET_SDIO_CLOCK, false, false); + + retries = retryCount; + for (;;) { + hal.watchdog_refresh(); + if (SDIO_SetBusWidth(Bus_Width::WIDTH_4BIT)) break; + if (!--retries) break; + } + + CardDMA_I.set_desired_clock(TARGET_SDIO_CLOCK, true, true); + + // Fallback + if (!retries) { + mfl_sdio_init(); + retries = retryCount; + for (;;) { + hal.watchdog_refresh(); + result = CardDMA_I.init(); + if (result == SDIO_Error_Type::OK) break; + if (!--retries) return false; + } + CardDMA_I.set_desired_clock(TARGET_SDIO_CLOCK, false, true); + } + + return true; +} + +static bool SDIO_ReadWriteBlock_DMA(uint32_t block, const uint8_t* src, uint8_t* dst) { + hal.watchdog_refresh(); + SDIO_Error_Type result = SDIO_Error_Type::OK; + + // Write + if (src) { + result = CardDMA_I.write(reinterpret_cast(const_cast(src)), block, 1); + } + // Read + else { + result = CardDMA_I.read(dst, block, 1); + } + + if (result != SDIO_Error_Type::OK) { + return false; + } + + millis_t timeout = millis() + SD_TIMEOUT; + while (CardDMA_I.get_state() != sdio::Operational_State::READY) { + if (ELAPSED(millis(), timeout)) { + return false; + } + } + + CardDMA_I.check_dma_complete(); + + timeout = millis() + SD_TIMEOUT; + do { + result = CardDMA_I.get_card_state(&cardState); + if (ELAPSED(millis(), timeout)) { + return false; + } + } while (result == SDIO_Error_Type::OK && cardState != sdio::Card_State::TRANSFER); + + return true; +} + +bool SDIO_ReadBlock(uint32_t block, uint8_t* dst) { + // Check if the address is aligned to 4 bytes + if (reinterpret_cast(dst) & 0x03) { + return false; + } + + uint8_t retries = SDIO_READ_RETRIES; + while (retries--) { + if (SDIO_ReadWriteBlock_DMA(block, nullptr, dst)) { + return true; + } + } + + return false; +} + +bool SDIO_WriteBlock(uint32_t block, const uint8_t* src) { + // Check if the address is aligned to 4 bytes + if (reinterpret_cast(src) & 0x03) { + return false; + } + + uint8_t retries = SDIO_READ_RETRIES; + while (retries--) { + if (SDIO_ReadWriteBlock_DMA(block, src, nullptr)) { + return true; + delay(10); + } + } + + return false; +} + +bool SDIO_IsReady() { + return (CardDMA_I.get_state() == sdio::Operational_State::READY); +} + +uint32_t SDIO_GetCardSize() { + return CardDMA_I.get_card_capacity(); +} + +// DMA interrupt handler +void DMA1_IRQHandler() { + auto& dma_instance = CardDMA_I.get_dma_instance(); + bool is_receive = CardDMA_I.get_is_sdio_rx(); + + // Check for Transfer Complete Interrupt + if (dma_instance.get_interrupt_flag(dma::Interrupt_Flags::INTR_FLAG_FTFIF)) { + dma_instance.set_interrupt_enable(dma::Interrupt_Type::INTR_FTFIE, false); + dma_instance.set_interrupt_enable(dma::Interrupt_Type::INTR_ERRIE, false); + dma_instance.clear_interrupt_flag(dma::Interrupt_Flags::INTR_FLAG_FTFIF); + if (is_receive) { + CardDMA_I.set_sdio_dma_enable(false); + CardDMA_I.clear_sdio_data_flags(); + CardDMA_I.set_state(sdio::Operational_State::READY); + } else { + CardDMA_I.set_data_end_interrupt(); + } + // Signal that transfer is complete + CardDMA_I.set_transfer_end(true); + } + + else if (dma_instance.get_interrupt_flag(dma::Interrupt_Flags::INTR_FLAG_ERRIF)) { + dma_instance.set_interrupt_enable(dma::Interrupt_Type::INTR_HTFIE, false); + dma_instance.set_interrupt_enable(dma::Interrupt_Type::INTR_ERRIE, false); + dma_instance.set_interrupt_enable(dma::Interrupt_Type::INTR_FTFIE, false); + // Clear all flags + dma_instance.clear_interrupt_flag(dma::Interrupt_Flags::INTR_FLAG_GIF); + // Signal that an error occurred + CardDMA_I.set_transfer_error(SDIO_Error_Type::ERROR); + CardDMA_I.set_state(sdio::Operational_State::READY); + } +} + +extern "C" { + + void SDIO_IRQHandler(void) { + CardDMA_I.handle_interrupts(); + } + + void DMA1_Channel3_4_IRQHandler(void) { + DMA1_IRQHandler(); + } + +} // extern "C" + +#endif // ONBOARD_SDIO +#endif // ARDUINO_ARCH_MFL diff --git a/Marlin/src/HAL/GD32_MFL/sd/sdio.h b/Marlin/src/HAL/GD32_MFL/sd/sdio.h new file mode 100644 index 000000000000..a39e8c7a66b5 --- /dev/null +++ b/Marlin/src/HAL/GD32_MFL/sd/sdio.h @@ -0,0 +1,36 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2025 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 . + * + */ +#pragma once + +#include +#include + +#define SDIO_D0_PIN PC8 +#define SDIO_D1_PIN PC9 +#define SDIO_D2_PIN PC10 +#define SDIO_D3_PIN PC11 +#define SDIO_CK_PIN PC12 +#define SDIO_CMD_PIN PD2 + +void sdio_mfl_init(); +auto SDIO_SetBusWidth(sdio::Bus_Width width) -> bool; +void DMA1_IRQHandler(dma::DMA_Channel channel); diff --git a/Marlin/src/HAL/GD32_MFL/spi_pins.h b/Marlin/src/HAL/GD32_MFL/spi_pins.h new file mode 100644 index 000000000000..c8a58368388d --- /dev/null +++ b/Marlin/src/HAL/GD32_MFL/spi_pins.h @@ -0,0 +1,32 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2025 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 . + */ +#pragma once + +// Define SPI Pins: SCK, MISO, MOSI +#ifndef SD_SCK_PIN + #define SD_SCK_PIN PIN_SPI_SCK +#endif +#ifndef SD_MISO_PIN + #define SD_MISO_PIN PIN_SPI_MISO +#endif +#ifndef SD_MOSI_PIN + #define SD_MOSI_PIN PIN_SPI_MOSI +#endif diff --git a/Marlin/src/HAL/GD32_MFL/temp_soc.h b/Marlin/src/HAL/GD32_MFL/temp_soc.h new file mode 100644 index 000000000000..bd78fba5b94c --- /dev/null +++ b/Marlin/src/HAL/GD32_MFL/temp_soc.h @@ -0,0 +1,29 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2025 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 . + * + */ +#pragma once + +#define TS_TYPICAL_V 1.405 +#define TS_TYPICAL_TEMP 25 +#define TS_TYPICAL_SLOPE 4.5 + +// TODO: Implement voltage scaling (calibrated Vrefint) and ADC resolution scaling (when applicable) +#define TEMP_SOC_SENSOR(RAW) ((TS_TYPICAL_V - (RAW) / float(OVERSAMPLENR) / float(HAL_ADC_RANGE) * (float(ADC_VREF_MV) * 0.001f)) / ((TS_TYPICAL_SLOPE) * 0.001f) + TS_TYPICAL_TEMP) diff --git a/Marlin/src/HAL/GD32_MFL/timers.cpp b/Marlin/src/HAL/GD32_MFL/timers.cpp new file mode 100644 index 000000000000..3bcfdd464b04 --- /dev/null +++ b/Marlin/src/HAL/GD32_MFL/timers.cpp @@ -0,0 +1,237 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2025 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 . + * + */ +#include "../platforms.h" + +#ifdef ARDUINO_ARCH_MFL + +#include "../../inc/MarlinConfig.h" +#include "timers.h" + +// ------------------------ +// Local defines +// ------------------------ + +#define SWSERIAL_TIMER_IRQ_PRIORITY_DEFAULT 1 // Requires tight bit timing to communicate reliably with TMC drivers +#define SERVO_TIMER_IRQ_PRIORITY_DEFAULT 1 // Requires tight PWM timing to control a BLTouch reliably +#define STEP_TIMER_IRQ_PRIORITY_DEFAULT 2 +#define TEMP_TIMER_IRQ_PRIORITY_DEFAULT 14 // Low priority avoids interference with other hardware and timers + +#ifndef TIMER_IRQ_PRIORITY + #define TIMER_IRQ_PRIORITY 12 +#endif + +#ifndef STEP_TIMER_IRQ_PRIORITY + #define STEP_TIMER_IRQ_PRIORITY STEP_TIMER_IRQ_PRIORITY_DEFAULT +#endif + +#ifndef TEMP_TIMER_IRQ_PRIORITY + #define TEMP_TIMER_IRQ_PRIORITY TEMP_TIMER_IRQ_PRIORITY_DEFAULT +#endif + +#if HAS_TMC_SW_SERIAL + #include + #ifndef SWSERIAL_TIMER_IRQ_PRIORITY + #define SWSERIAL_TIMER_IRQ_PRIORITY SWSERIAL_TIMER_IRQ_PRIORITY_DEFAULT + #endif +#endif + +#if HAS_SERVOS + #include "Servo.h" + #ifndef SERVO_TIMER_IRQ_PRIORITY + #define SERVO_TIMER_IRQ_PRIORITY SERVO_TIMER_IRQ_PRIORITY_DEFAULT + #endif +#endif + +#if ENABLED(SPEAKER) + // The MFL framework default timer priority is 12. The TEMP timer must have lower priority + // than this due to the long running temperature ISR, and STEP timer should higher priority. + #if !(TIMER_IRQ_PRIORITY > STEP_TIMER_IRQ_PRIORITY && TIMER_IRQ_PRIORITY < TEMP_TIMER_IRQ_PRIORITY) + #error "Default timer interrupt priority is unspecified or set to a value which may degrade performance." + #endif +#endif + +#ifndef STEP_TIMER + #define STEP_TIMER MF_TIMER_STEP +#endif +#ifndef TEMP_TIMER + #define TEMP_TIMER MF_TIMER_TEMP +#endif + +GeneralTimer& Step_Timer = GeneralTimer::get_instance(static_cast(STEP_TIMER)); +GeneralTimer& Temp_Timer = GeneralTimer::get_instance(static_cast(TEMP_TIMER)); + +bool is_step_timer_initialized = false; +bool is_temp_timer_initialized = false; + +// ------------------------ +// Public functions +// ------------------------ + +// Retrieves the clock frequency of the stepper timer +uint32_t GetStepperTimerClkFreq() { + // Cache result + static uint32_t clkFreq = Step_Timer.getTimerClockFrequency(); + return clkFreq; +} + +/** + * @brief Starts a hardware timer + * + * If the timer is not already initialized, this function will initialize it with the given frequency. + * The timer is started immediately after initialization + * + * @param timer The timer base index to start + * @param frequency The frequency at which the timer should run + * @return None + */ +void HAL_timer_start(const uint8_t timer_number, const uint32_t frequency) { + if (HAL_timer_initialized(timer_number) || (timer_number != MF_TIMER_STEP && timer_number != MF_TIMER_TEMP)) + return; + + const bool is_step = (timer_number == MF_TIMER_STEP); + const uint8_t priority = is_step ? + static_cast(STEP_TIMER_IRQ_PRIORITY) : + static_cast(TEMP_TIMER_IRQ_PRIORITY); + + // Get the reference of the timer instance + GeneralTimer& timer = is_step ? Step_Timer : Temp_Timer; + + if (is_step) { + timer.setPrescaler(STEPPER_TIMER_PRESCALE); + timer.setRolloverValue( + _MIN(HAL_TIMER_TYPE_MAX, hal_timer_t((HAL_TIMER_RATE) / (STEPPER_TIMER_PRESCALE))), + TimerFormat::TICK + ); + is_step_timer_initialized = true; + } + else { + timer.setRolloverValue(frequency, TimerFormat::HERTZ); + is_temp_timer_initialized = true; + } + + timer.setAutoReloadEnable(false); + timer.setInterruptPriority(priority, 0U); + HAL_timer_enable_interrupt(timer_number); + timer.start(); +} + +/** + * @brief Enables the interrupt for the specified timer + * + * @param handle The timer handle for which to enable the interrupt + * @return None + */ +void HAL_timer_enable_interrupt(const uint8_t timer_number) { + if (!HAL_timer_initialized(timer_number)) return; + + GeneralTimer& timer = (timer_number == MF_TIMER_STEP) ? Step_Timer : Temp_Timer; + + if (timer_number == MF_TIMER_STEP && !timer.hasInterrupt()) + timer.attachInterrupt(Step_Handler); + else if (timer_number == MF_TIMER_TEMP && !timer.hasInterrupt()) + timer.attachInterrupt(Temp_Handler); +} + +/** + * @brief Disables the interrupt for the specified timer + * + * @param handle The timer handle for which to disable the interrupt + * @return None + */ +void HAL_timer_disable_interrupt(const uint8_t timer_number) { + if (!HAL_timer_initialized(timer_number)) return; + + GeneralTimer& timer = (timer_number == MF_TIMER_STEP) ? Step_Timer : Temp_Timer; + if (timer_number == MF_TIMER_STEP || timer_number == MF_TIMER_TEMP) + timer.detachInterrupt(); +} + +/** + * @brief Checks if the interrupt is enabled for the specified timer + * + * @param handle The timer handle to check + * @return True if the interrupt is enabled, false otherwise + */ +bool HAL_timer_interrupt_enabled(const uint8_t timer_number) { + if (!HAL_timer_initialized(timer_number)) return false; + + GeneralTimer& timer = (timer_number == MF_TIMER_STEP) ? Step_Timer : Temp_Timer; + return (timer_number == MF_TIMER_STEP || timer_number == MF_TIMER_TEMP) + ? timer.hasInterrupt() + : false; +} + +// Sets the interrupt priorities for timers used by TMC SW serial and servos. +void SetTimerInterruptPriorities() { + TERN_(HAS_TMC_SW_SERIAL, SoftwareSerial::setInterruptPriority(SWSERIAL_TIMER_IRQ_PRIORITY, 0)); + TERN_(HAS_SERVOS, libServo::setInterruptPriority(SERVO_TIMER_IRQ_PRIORITY, 0)); +} + +// ------------------------ +// Detect timer conflicts +// ------------------------ + +TERN_(HAS_TMC_SW_SERIAL, static constexpr timer::TIMER_Base timer_serial[] = {static_cast(TIMER_SERIAL)}); +TERN_(SPEAKER, static constexpr timer::TIMER_Base timer_tone[] = {static_cast(TIMER_TONE)}); +TERN_(HAS_SERVOS, static constexpr timer::TIMER_Base timer_servo[] = {static_cast(TIMER_SERVO)}); + +enum TimerPurpose { + PURPOSE_SERIAL, + PURPOSE_TONE, + PURPOSE_SERVO, + PURPOSE_STEP, + PURPOSE_TEMP +}; + +// List of timers to check for conflicts +// Includes the timer purpose to ease debugging when evaluating at build-time +// This cannot yet account for timers used for PWM output, such as for fans +static constexpr struct { TimerPurpose p; int t; } timers_in_use[] = { + #if HAS_TMC_SW_SERIAL + { PURPOSE_SERIAL, timer_base_to_index(timer_serial[0]) }, // Set in variant.h + #endif + #if ENABLED(SPEAKER) + { PURPOSE_TONE, timer_base_to_index(timer_tone[0]) }, // Set in variant.h + #endif + #if HAS_SERVOS + { PURPOSE_SERVO, timer_base_to_index(timer_servo[0]) }, // Set in variant.h + #endif + { PURPOSE_STEP, MF_TIMER_STEP }, + { PURPOSE_TEMP, MF_TIMER_TEMP }, +}; + +// Verifies if there are any timer conflicts in the timers_in_use array +static constexpr bool verify_no_timer_conflicts() { + for (uint8_t i = 0; i < COUNT(timers_in_use); i++) + for (uint8_t j = i + 1; j < COUNT(timers_in_use); j++) + if (timers_in_use[i].t == timers_in_use[j].t) + return false; + + return true; +} + +// If this assertion fails at compile time, review the timers_in_use array. +// If default_envs is defined properly in platformio.ini, VSCode can evaluate the array +// when hovering over it, making it easy to identify the conflicting timers +static_assert(verify_no_timer_conflicts(), "One or more timer conflict detected. Examine \"timers_in_use\" to help identify conflict."); + +#endif // ARDUINO_ARCH_MFL diff --git a/Marlin/src/HAL/GD32_MFL/timers.h b/Marlin/src/HAL/GD32_MFL/timers.h new file mode 100644 index 000000000000..8aff77aa96b1 --- /dev/null +++ b/Marlin/src/HAL/GD32_MFL/timers.h @@ -0,0 +1,149 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2025 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 . + * + */ +#pragma once + +#include "../../inc/MarlinConfig.h" + +#include + +// ------------------------ +// Defines +// ------------------------ + +// Timer instance definitions +#define MF_TIMER_STEP 3 +#define MF_TIMER_TEMP 1 +#define MF_TIMER_PULSE MF_TIMER_STEP + +typedef uint32_t hal_timer_t; +#define HAL_TIMER_TYPE_MAX hal_timer_t(UINT16_MAX) + +#ifndef HAL_TIMER_RATE + extern uint32_t GetStepperTimerClkFreq(); + #define HAL_TIMER_RATE GetStepperTimerClkFreq() +#endif + +// Timer configuration constants +#define STEPPER_TIMER_RATE 2000000 +#define TEMP_TIMER_FREQUENCY 1000 + +// Timer prescaler calculations +#define STEPPER_TIMER_PRESCALE ((HAL_TIMER_RATE) / (STEPPER_TIMER_RATE)) // Prescaler = 30 +#define STEPPER_TIMER_TICKS_PER_US ((STEPPER_TIMER_RATE) / 1000000UL) // (MHz) Stepper Timer ticks per µs + +// Pulse Timer (counter) calculations +#define PULSE_TIMER_RATE STEPPER_TIMER_RATE // (Hz) Frequency of Pulse Timer +#define PULSE_TIMER_PRESCALE STEPPER_TIMER_PRESCALE + +// Timer interrupt priorities +#define STEP_TIMER_IRQ_PRIORITY 2 +#define TEMP_TIMER_IRQ_PRIORITY 14 + +#define ENABLE_STEPPER_DRIVER_INTERRUPT() HAL_timer_enable_interrupt(MF_TIMER_STEP) +#define DISABLE_STEPPER_DRIVER_INTERRUPT() HAL_timer_disable_interrupt(MF_TIMER_STEP) +#define STEPPER_ISR_ENABLED() HAL_timer_interrupt_enabled(MF_TIMER_STEP) +#define ENABLE_TEMPERATURE_INTERRUPT() HAL_timer_enable_interrupt(MF_TIMER_TEMP) +#define DISABLE_TEMPERATURE_INTERRUPT() HAL_timer_disable_interrupt(MF_TIMER_TEMP) + +extern void Step_Handler(); +extern void Temp_Handler(); + +#ifndef HAL_STEP_TIMER_ISR + #define HAL_STEP_TIMER_ISR() void Step_Handler() +#endif +#ifndef HAL_TEMP_TIMER_ISR + #define HAL_TEMP_TIMER_ISR() void Temp_Handler() +#endif + +extern GeneralTimer& Step_Timer; +extern GeneralTimer& Temp_Timer; + +extern bool is_step_timer_initialized; +extern bool is_temp_timer_initialized; + +// Build-time mapping between timer base and index. Used in timers.cpp and fast_pwm.cpp +static inline constexpr struct {timer::TIMER_Base base; uint8_t timer_number;} base_to_index[] = { + { timer::TIMER_Base::TIMER0_BASE, 0 }, + { timer::TIMER_Base::TIMER1_BASE, 1 }, + { timer::TIMER_Base::TIMER2_BASE, 2 }, + { timer::TIMER_Base::TIMER3_BASE, 3 }, + { timer::TIMER_Base::TIMER4_BASE, 4 }, + { timer::TIMER_Base::TIMER5_BASE, 5 }, + { timer::TIMER_Base::TIMER6_BASE, 6 }, + { timer::TIMER_Base::TIMER7_BASE, 7 } +}; + +// Converts a timer base to an integer timer index. +constexpr auto timer_base_to_index(timer::TIMER_Base base) -> int { + for (const auto& timer : base_to_index) { + if (timer.base == base) { + return static_cast(timer.timer_number); + } + } + return -1; +} + +// ------------------------ +// Public functions +// ------------------------ + +void HAL_timer_start(const uint8_t timer, const uint32_t frequency); +void HAL_timer_enable_interrupt(const uint8_t timer); +void HAL_timer_disable_interrupt(const uint8_t timer); +bool HAL_timer_interrupt_enabled(const uint8_t timer); + +// Configure timer priorities for peripherals such as Software Serial or Servos. +// Exposed here to allow all timer priority information to reside in timers.cpp +void SetTimerInterruptPriorities(); + +// FORCE_INLINE because these are used in performance-critical situations +FORCE_INLINE bool HAL_timer_initialized(const uint8_t timer_number) { + return (timer_number == MF_TIMER_STEP) ? is_step_timer_initialized : + (timer_number == MF_TIMER_TEMP) ? is_temp_timer_initialized : + false; +} + +FORCE_INLINE static hal_timer_t HAL_timer_get_count(const uint8_t timer_number) { + if (!HAL_timer_initialized(timer_number)) return 0U; + + GeneralTimer& timer = (timer_number == MF_TIMER_STEP) ? Step_Timer : Temp_Timer; + + return (timer_number == MF_TIMER_STEP || timer_number == MF_TIMER_TEMP) + ? timer.getCounter(TimerFormat::TICK) + : 0U; +} + +FORCE_INLINE static void HAL_timer_set_compare(const uint8_t timer_number, const hal_timer_t value) { + if (!HAL_timer_initialized(timer_number)) return; + + const auto new_value = static_cast(value + 1U); + GeneralTimer& timer = (timer_number == MF_TIMER_STEP) ? Step_Timer : Temp_Timer; + + if (timer_number == MF_TIMER_STEP || timer_number == MF_TIMER_TEMP) { + timer.setRolloverValue(new_value, TimerFormat::TICK); + if (value < static_cast(timer.getCounter(TimerFormat::TICK))) + timer.refresh(); + } +} + +inline void HAL_timer_isr_prologue(const uint8_t) {} +inline void HAL_timer_isr_epilogue(const uint8_t) {} diff --git a/Marlin/src/HAL/GD32_MFL/u8g/LCD_defines.h b/Marlin/src/HAL/GD32_MFL/u8g/LCD_defines.h new file mode 100644 index 000000000000..720d958779dc --- /dev/null +++ b/Marlin/src/HAL/GD32_MFL/u8g/LCD_defines.h @@ -0,0 +1,26 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2025 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 . + * + */ +#pragma once + +// MFL LCD-specific defines +uint8_t u8g_com_HAL_MFL_sw_spi_fn(u8g_t* u8g, uint8_t msg, uint8_t arg_val, void* arg_ptr); // u8g_com_mfl_swspi.cpp +#define U8G_COM_HAL_SW_SPI_FN u8g_com_HAL_MFL_sw_spi_fn diff --git a/Marlin/src/HAL/HAL.h b/Marlin/src/HAL/HAL.h index f3e16cfdf12c..a211dfe2590a 100644 --- a/Marlin/src/HAL/HAL.h +++ b/Marlin/src/HAL/HAL.h @@ -27,7 +27,7 @@ #define GCC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) #endif -#include HAL_PATH(..,HAL.h) +#include HAL_PATH(.., HAL.h) extern MarlinHAL hal; #define HAL_ADC_RANGE _BV(HAL_ADC_RESOLUTION) diff --git a/Marlin/src/HAL/HC32/AGENTS.md b/Marlin/src/HAL/HC32/AGENTS.md new file mode 100644 index 000000000000..3b086236e8c7 --- /dev/null +++ b/Marlin/src/HAL/HC32/AGENTS.md @@ -0,0 +1,71 @@ +# AGENTS.md — HC32 HAL + +Working notes and conventions for the **HC32 HAL** folder (`Marlin/src/HAL/HC32/`). + +## Build & Test Loop + +Always validate with `mftest` (it regenerates `Marlin/Configuration.h` per target, which raw `pio run -e` does not do reliably): + +```bash +cd "$(git rev-parse --show-toplevel)" +buildroot/bin/mftest -t HC32F460C_aquila_101 -n1 -y # Voxelab Aquila V1.0.x, 256 KB flash +``` + +`HC32F460C_aquila_101` is the only in-repo test target for HC32 (board `BOARD_AQUILA_V101`, 256 KB flash, `HC32F460C` base → `maximum_size = 262144`). It must be green before considering HC32 work done. The Aquila (256 KB) build is the tighter constraint — the 512 KB variants (`HC32F460E_*`) will fit if this does. + +> Do NOT use `rm -rf .pio/build/...` to force a rebuild — the cross-profile write guard blocks it and it is unsafe. Use `pio run -e -t clean` (removes only build artifacts) or just let `mftest` rebuild. + +## Framework / Platform Integration (upstream, not vendored) + +Unlike AT32, the HC32 HAL uses **upstream (third-party) Arduino packages** pulled straight from GitHub by PlatformIO — there is **no vendored framework** in this repo (`buildroot/share/PlatformIO/` carries only `framework-arduino-at32f4` and `platform-at32`, nothing for HC32). `ini/hc32.ini` declares them as remote archives: + +```ini +platform = https://github.com/shadow578/platform-hc32f46x/archive/1.1.1.zip +platform_packages = framework-hc32f46x-ddl@https://github.com/shadow578/framework-hc32f46x-ddl/archive/2.2.3.zip + framework-arduino-hc32f46x@https://github.com/shadow578/framework-arduino-hc32f46x/archive/1.3.1.zip +``` + +Implications: + +- **Framework fixes are NOT made in this repo.** To change the core (flags, serial, USB, DDL/DDL options) you edit `ini/hc32.ini` `build_flags` / `board_build.*` keys, or pin a new upstream core tag. There is no `buildroot/share/PlatformIO/framework-arduino-hc32f4x` to keep in sync with an installed package — the AT32 "mirror the two copies" rule does **not** apply here. +- The core version is pinned and **enforced at compile time**: `inc/SanityCheck.h` requires `ARDUINO_CORE_VERSION_INT >= 1.1.0` (for `app_config.h`) and gates `SERIAL_DMA`, `EMERGENCY_PARSER`, and `SoftwareSerial` on core 1.2.0 / 1.3.1. Bumping the core tag is a real compatibility event — check the `ARDUINO_CORE_VERSION_INT` checks before doing so. + +### Gotchas that are not obvious from the source + +1. **Pins are an `enum`, not `#define`.** The upstream core defines `PA0`, `PB1`, … as a C `enum`. The C preprocessor cannot see `enum` values in `#if`, so `PIN_EXISTS(PA0)` evaluated `PA0` as `0` → false pin-conflict errors. `inc/SanityCheck.h` now hard-errors if `PA0` is not `#define`d: _"Your ARM platform pins are not defined as macros, only as enums! Provide pins_arduino.h to define the pins."_ The fix is at the pin-define level (a `pins_arduino.h` mapping enum → macro). **Do not "fix" the symptom by editing SanityCheck — fix it at the pin-define level.** + +2. **`app_config.h` is injected into EVERY core source.** `board_build.app_config = Marlin/src/HAL/HC32/app_config.h` is force-included by the Arduino build script for all translation units. It re-configures the core/DDL from `Configuration.h`/`Configuration_adv.h` (`MARLIN_DEV_MODE` → `__DEBUG`/`__CORE_DEBUG`, `POSTMORTEM_DEBUGGING` → `CORE_DISABLE_FAULT_HANDLER`, `SERIAL_DMA` → `USART_RX_DMA_SUPPORT`). It also **`#define DISABLE_SERIAL_GLOBALS 1`** (the core's `Serial1`/`Serial2`/`Serial` globals are suppressed — Marlin defines its own `MarlinSerial`/`Serial` alias) and force-enables the custom `PANIC_ENABLE` panic handler (in `MinSerial.cpp`). Changing Marlin serial/panic behavior without touching `app_config.h` will silently mismatch the core. + +3. **Hardware UART, not USB-CDC.** `MarlinSerial` derives from the core's `Usart` class (`drivers/usart/Usart.h`) — Marlin talks over hardware USART only. There is no USB-CDC serial class here. `SERIAL_INDEX_MIN=1`, `SERIAL_INDEX_MAX=4` (ports 1–4). The core's `SERIAL_STATS_MAX_RX_QUEUED` / `SERIAL_STATS_DROPPED_RX` are explicitly error-unsupported. + +4. **Emergency parser hooks the USART RX ISR.** `HAL.h` declares `extern "C" void usart_rx_irq_hook(uint8_t ch, uint8_t usart);` (behind `ENABLED(EMERGENCY_PARSER)`). `SERIAL_DMA` + `EMERGENCY_PARSER` together require core `>= 1.2.0` (that release added `core_hook_usart_rx_irq`), else SanityCheck errors. Enable both with an older core at your peril. + +5. **EEPROM backend is per-board, not auto-selected.** Marlin does NOT auto-pick an `EEPROM_*_EMULATION` backend. Three implementations ship in `eeprom/`: `eeprom_wired.cpp` (I2C/SPI wired), `eeprom_if_iic.cpp` (generic I2C), `eeprom_bl24cxx.cpp` (BL24Cxx I2C EEPROM), and `eeprom_sdcard.cpp` (`SDCARD_EEPROM_EMULATION`, requires `SDSUPPORT`). The board pins file must `#define` the backend it wants; the Aquila test uses SD-card EEPROM (`sdsupport = on`, `eeprom_settings = on`). `SDCARD_EEPROM_EMULATION` without `HAS_MEDIA` is an error. + +6. **`FAST_PWM_FAN` is unsupported.** `inc/SanityCheck.h` hard-errors on `FAST_PWM_FAN` — there is no hardware PWM-fan implementation yet. Don't enable it; use software PWM fans. + +7. **`NEOPIXEL_LED` (Adafruit) is unsupported** except on `MKS_MINI_12864_V3`; any other config is an error. `TEMP_SENSOR_SOC` requires `TEMP_SOC_PIN` defined and not a real GPIO pin. These are Marlin feature gaps specific to this HAL. + +8. **No compile-time PWM check (`NO_COMPILE_TIME_PWM`).** `fastio.h` defines `NO_COMPILE_TIME_PWM` because PWM capability is discovered at runtime via the TimerA assignment table (`timera_get_assignment`), not via a pin macro. `PWM_PIN(IO)` calls `isAnalogWritePin(IO)` at runtime. Don't assume `analogWrite`-eligible pins are known at preprocessor time. + +9. **Flash size / offset is board-specific and lives in `ini/hc32.ini`.** The Aquila uses `board_upload.offset_address = 0xC000` (bootloader start, also `secondary` boot mode to save ~1.4 KB by dropping the ICG); Ender-2-Pro uses `0x8000`; OpenHC32Boot builds use `0x4000`. `HC32F460C` = 256 KB (`maximum_size = 262144`), `HC32F460E` = 512 KB. The linker script / flash layout is supplied by the upstream platform, not vendored here. + +10. **`-fno-signed-char` is required for meatpack.** `board_build.flags.common` forces `-fno-signed-char`; removing it breaks meatpack. The build also adds `-fno-threadsafe-statics`, `-fno-exceptions`, `-fno-rtti` (no C++ runtime features used by Marlin) and `-g3` (debug symbols only — does not affect binary size). + +11. **DDL driver middleware is selected via `board_build.ddl.*` / `mw.*` keys.** The `ots`, `sdioc`, `wdt`, `timer0`, `timera` DDL modules and the `sd_card` middleware are pulled in through `ini/hc32.ini`. `sdio.cpp` (SD card over SDIO) depends on `ddl.sdioc` + `mw.sd_card`; dropping those breaks SD support. **Don't assume a peripheral driver exists unless its DDL/MW flag is enabled.** + +## Conventions + +- All HAL source is guarded by `#ifdef ARDUINO_ARCH_HC32` (added by `ini/hc32.ini` `-DARDUINO_ARCH_HC32`; `inc/SanityCheck.h` sanity-checks the env via `NOT_TARGET`). +- Keep HAL changes in `Marlin/src/HAL/HC32/` and board pin changes in `Marlin/src/pins/hc32f4/`. Build/platform integration changes (core version, DDL/MW flags, flash offsets) go in `ini/hc32.ini` — there is no vendored framework to edit. +- Pin mappings are board-specific; see `Marlin/src/pins/hc32f4/pins_AQUILA_101.h` and `pins_CREALITY_ENDER2P_V24S4.h`. Mark any pin as a placeholder if it is not verified against a schematic. +- When you change the upstream core version (the `*@https://...archive/X.Y.Z.zip` tags in `ini/hc32.ini`), re-check every `ARDUINO_CORE_VERSION_INT` gate in `inc/SanityCheck.h` (1.1.0 for `app_config.h`, 1.2.0 for `SERIAL_DMA`+`EMERGENCY_PARSER`, 1.3.1 for `SoftwareSerial`) and rebuild the test target. + +## Related + +- `README.md` in this folder — overview, "adding a board" guide, dependencies, documentation notes. +- `app_config.h` in this folder — the auto-included core/DDL config bridge. +- Wiki: [[hal-hc32]], [[marlin-src-hal]]. +- `Marlin/src/pins/hc32f4/` — board pin files. +- `Marlin/src/HAL/shared/` — shared HAL APIs (`eeprom_api`, SPI helpers) the HC32 HAL reuses. +- `Marlin/src/HAL/AT32/AGENTS.md` — the younger sibling HAL, modeled on this one. diff --git a/Marlin/src/HAL/HC32/HAL.h b/Marlin/src/HAL/HC32/HAL.h index dd02183dd0ea..f751dea09109 100644 --- a/Marlin/src/HAL/HC32/HAL.h +++ b/Marlin/src/HAL/HC32/HAL.h @@ -30,69 +30,18 @@ #include "../../inc/MarlinConfig.h" -#include "../../core/macros.h" #include "../shared/Marduino.h" #include "../shared/math_32bit.h" #include "../shared/HAL_SPI.h" #include "fastio.h" #include "timers.h" -#include "MarlinSerial.h" - -#include // // Serial Ports // -#define _MSERIAL(X) MSerial##X -#define MSERIAL(X) _MSERIAL(X) -#define NUM_UARTS 4 - -#if SERIAL_PORT == -1 - #error "USB Serial is not supported on HC32F460" -#elif WITHIN(SERIAL_PORT, 1, NUM_UARTS) - #define MYSERIAL1 MSERIAL(SERIAL_PORT) -#else - #define MYSERIAL1 MSERIAL(1) // Dummy port - static_assert(false, "SERIAL_PORT must be from 1 to " STRINGIFY(NUM_UARTS) ".") -#endif - -#ifdef SERIAL_PORT_2 - #if SERIAL_PORT_2 == -1 - #error "USB Serial is not supported on HC32F460" - #elif WITHIN(SERIAL_PORT_2, 1, NUM_UARTS) - #define MYSERIAL2 MSERIAL(SERIAL_PORT_2) - #else - #define MYSERIAL2 MSERIAL(1) // Dummy port - static_assert(false, "SERIAL_PORT_2 must be from 1 to " STRINGIFY(NUM_UARTS) ".") - #endif -#endif - -#ifdef SERIAL_PORT_3 - #if SERIAL_PORT_3 == -1 - #error "USB Serial is not supported on HC32F460" - #elif WITHIN(SERIAL_PORT_3, 1, NUM_UARTS) - #define MYSERIAL3 MSERIAL(SERIAL_PORT_3) - #else - #define MYSERIAL3 MSERIAL(1) // Dummy port - static_assert(false, "SERIAL_PORT_3 must be from 1 to " STRINGIFY(NUM_UARTS) ".") - #endif -#endif -#ifdef LCD_SERIAL_PORT - #if LCD_SERIAL_PORT == -1 - #error "USB Serial is not supported on HC32F460" - #elif WITHIN(LCD_SERIAL_PORT, 1, NUM_UARTS) - #define LCD_SERIAL MSERIAL(LCD_SERIAL_PORT) - #else - #define LCD_SERIAL MSERIAL(1) // Dummy port - static_assert(false, "LCD_SERIAL_PORT must be from 1 to " STRINGIFY(NUM_UARTS) ".") - #endif - - #if HAS_DGUS_LCD - #define LCD_SERIAL_TX_BUFFER_FREE() LCD_SERIAL.availableForWrite() - #endif -#endif +#include "MarlinSerial.h" // // Emergency Parser @@ -114,22 +63,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_END \ - if (!primask) \ - (void)__iSeiRetVal() - -// Disable interrupts -#define cli() noInterrupts() +#define CRITICAL_SECTION_START() \ + const bool irqon = !__get_PRIMASK(); \ + __disable_irq(); \ + __DSB(); +#define CRITICAL_SECTION_END() \ + __DSB(); \ + if (irqon) __enable_irq(); -// Enable interrupts -#define sei() interrupts() +#define cli() __disable_irq() +#define sei() __enable_irq() // bss_end alias #define __bss_end __bss_end__ @@ -142,12 +88,31 @@ // ADC // #define HAL_ADC_VREF_MV 3300 -#define HAL_ADC_RESOLUTION 10 +#define HAL_ADC_RESOLUTION 12 #define GET_PIN_MAP_PIN(index) index #define GET_PIN_MAP_INDEX(pin) pin #define PARSED_PIN_INDEX(code, dval) parser.intval(code, dval) +// +// Debug port disable +// JTMS / SWDIO = PA13 +// JTCK / SWCLK = PA14 +// JTDI = PA15 +// JTDO = PB3 +// NJTRST = PB4 +// +#define DBG_SWCLK _BV(0) +#define DBG_SWDIO _BV(1) +#define DBG_TDO _BV(2) +#define DBG_TDI _BV(3) +#define DBG_TRST _BV(4) +#define DBG_ALL (DBG_SWCLK | DBG_SWDIO | DBG_TDO | DBG_TDI | DBG_TRST) + +#define JTAGSWD_RESET() PORT_DebugPortSetting(DBG_ALL, Enable); +#define JTAG_DISABLE() PORT_DebugPortSetting(DBG_TDO | DBG_TDI | DBG_TRST, Disable); +#define JTAGSWD_DISABLE() PORT_DebugPortSetting(DBG_ALL, Disable); + // // MarlinHAL implementation // diff --git a/Marlin/src/HAL/HC32/MarlinHAL.cpp b/Marlin/src/HAL/HC32/MarlinHAL.cpp index 1ab374fbf15c..f1d85f169476 100644 --- a/Marlin/src/HAL/HC32/MarlinHAL.cpp +++ b/Marlin/src/HAL/HC32/MarlinHAL.cpp @@ -123,6 +123,11 @@ void MarlinHAL::init() { // Register min serial TERN_(POSTMORTEM_DEBUGGING, install_min_serial()); + + // warn if low memory after init + if (freeMemory() < 1024) { + SERIAL_WARN_MSG("HAL: low memory after init!\n"); + } } void MarlinHAL::init_board() {} @@ -147,7 +152,31 @@ void MarlinHAL::delay_ms(const int ms) { delay(ms); } -void MarlinHAL::idletask() {} +void MarlinHAL::idletask() { + #if ENABLED(MARLIN_DEV_MODE) + // check & print serial RX errors + MSerialT *serials[] = { &MSerial1, &MSerial2 }; + for (int serial = 0; serial < 2; serial++) { + usart_receive_error_t err = serials[serial]->getReceiveError(); + if (err != usart_receive_error_t::None) { + // "Warning: MSerial[n] RX [Framing|Parity|Overrun] Error" + SERIAL_WARN_START(); + SERIAL_ECHOPGM(" MSerial"); + SERIAL_ECHO(serial + 1); + SERIAL_ECHOPGM(" RX "); + switch(err) { + case usart_receive_error_t::FramingError: SERIAL_ECHOPGM("Framing"); break; + case usart_receive_error_t::ParityError: SERIAL_ECHOPGM("Parity"); break; + case usart_receive_error_t::OverrunError: SERIAL_ECHOPGM("Overrun"); break; + case usart_receive_error_t::RxDataDropped: SERIAL_ECHOPGM("DataDropped"); break; + default: break; + } + SERIAL_ECHOPGM(" Error"); + SERIAL_EOL(); + } + } + #endif +} uint8_t MarlinHAL::get_reset_source() { // Query reset cause from RMU @@ -203,7 +232,9 @@ int MarlinHAL::freeMemory() { return &top - _sbrk(0); } -void MarlinHAL::adc_init() {} +void MarlinHAL::adc_init() { + analogReadResolution(HAL_ADC_RESOLUTION); +} void MarlinHAL::adc_enable(const pin_t pin) { #if TEMP_SENSOR_SOC diff --git a/Marlin/src/HAL/HC32/MarlinHAL.h b/Marlin/src/HAL/HC32/MarlinHAL.h index 86dc3c7e53b1..ba3ac4c73191 100644 --- a/Marlin/src/HAL/HC32/MarlinHAL.h +++ b/Marlin/src/HAL/HC32/MarlinHAL.h @@ -67,7 +67,7 @@ class MarlinHAL { static void delay_ms(const int ms); - // Tasks, called from idle() + // Tasks, called from marlin.idle() static void idletask(); // Reset diff --git a/Marlin/src/HAL/HC32/MarlinSerial.cpp b/Marlin/src/HAL/HC32/MarlinSerial.cpp index eb203f79d3ba..df8d39ae3507 100644 --- a/Marlin/src/HAL/HC32/MarlinSerial.cpp +++ b/Marlin/src/HAL/HC32/MarlinSerial.cpp @@ -30,30 +30,55 @@ * it would not make sense to parse G-Code from TMC responses */ constexpr bool serial_handles_emergency(int port) { - return false + return (false #ifdef SERIAL_PORT - || (SERIAL_PORT) == port + || (SERIAL_PORT) == port #endif #ifdef SERIAL_PORT_2 - || (SERIAL_PORT_2) == port + || (SERIAL_PORT_2) == port #endif #ifdef LCD_SERIAL_PORT - || (LCD_SERIAL_PORT) == port + || (LCD_SERIAL_PORT) == port #endif - ; + ); } // // Define serial ports // -#define DEFINE_HWSERIAL_MARLIN(name, n) \ + +// serial port where RX and TX use IRQs +#define DEFINE_IRQ_SERIAL_MARLIN(name, n) \ MSerialT name(serial_handles_emergency(n), \ &USART##n##_config, \ BOARD_USART##n##_TX_PIN, \ BOARD_USART##n##_RX_PIN); -DEFINE_HWSERIAL_MARLIN(MSerial1, 1); -DEFINE_HWSERIAL_MARLIN(MSerial2, 2); +// serial port where RX uses DMA and TX uses IRQs +// all serial ports use DMA1 +// since there are 4 USARTs and 4 DMA channels, we can use the USART number as the DMA channel +#define DEFINE_DMA_SERIAL_MARLIN(name, n) \ + MSerialT name(serial_handles_emergency(n), \ + &USART##n##_config, \ + BOARD_USART##n##_TX_PIN, \ + BOARD_USART##n##_RX_PIN, \ + M4_DMA1, \ + ((en_dma_channel_t)(n - 1))); // map USART1 to DMA channel 0, USART2 to DMA channel 1, etc. + +#define DEFINE_SERIAL_MARLIN(name, n) TERN(SERIAL_DMA, DEFINE_DMA_SERIAL_MARLIN(name, n), DEFINE_IRQ_SERIAL_MARLIN(name, n)) + +#if PINS_EXIST(BOARD_USART1_RX, BOARD_USART1_TX) + DEFINE_SERIAL_MARLIN(MSerial1, 1); +#endif +#if PINS_EXIST(BOARD_USART2_RX, BOARD_USART2_TX) + DEFINE_SERIAL_MARLIN(MSerial2, 2); +#endif +#if PINS_EXIST(BOARD_USART3_RX, BOARD_USART3_TX) + DEFINE_SERIAL_MARLIN(MSerial3, 3); +#endif +#if PINS_EXIST(BOARD_USART4_RX, BOARD_USART4_TX) + DEFINE_SERIAL_MARLIN(MSerial4, 4); +#endif // // Serial port assertions diff --git a/Marlin/src/HAL/HC32/MarlinSerial.h b/Marlin/src/HAL/HC32/MarlinSerial.h index 08eeef43951e..899e1bf2a7d3 100644 --- a/Marlin/src/HAL/HC32/MarlinSerial.h +++ b/Marlin/src/HAL/HC32/MarlinSerial.h @@ -24,18 +24,51 @@ #include "../../core/serial_hook.h" #include +#define SERIAL_INDEX_MIN 1 +#define SERIAL_INDEX_MAX 4 +#include "../shared/serial_ports.h" + +#if defined(LCD_SERIAL_PORT) && ANY(HAS_DGUS_LCD, EXTENSIBLE_UI) + #define LCD_SERIAL_TX_BUFFER_FREE() LCD_SERIAL.availableForWrite() +#endif + // Optionally set uart IRQ priority to reduce overflow errors -// #define UART_IRQ_PRIO 1 +//#define UART_RX_IRQ_PRIO 1 +//#define UART_TX_IRQ_PRIO 1 +//#define UART_RX_DMA_IRQ_PRIO 1 struct MarlinSerial : public Usart { - MarlinSerial(struct usart_config_t *usart_device, gpio_pin_t tx_pin, gpio_pin_t rx_pin) : Usart(usart_device, tx_pin, rx_pin) {} + MarlinSerial( + struct usart_config_t *usart_device, + gpio_pin_t tx_pin, + gpio_pin_t rx_pin + #if ENABLED(SERIAL_DMA) + , M4_DMA_TypeDef *dma_unit = nullptr, + en_dma_channel_t rx_dma_channel = DmaCh0 + #endif + ) : Usart(usart_device, tx_pin, rx_pin) { + #if ENABLED(SERIAL_DMA) + if (dma_unit != nullptr) { + enableRxDma(dma_unit, rx_dma_channel); + } + #endif + } - #ifdef UART_IRQ_PRIO + #if defined(UART_RX_IRQ_PRIO) || defined(UART_TX_IRQ_PRIO) || defined(UART_RX_DMA_IRQ_PRIO) void setPriority() { - NVIC_SetPriority(c_dev()->interrupts.rx_data_available.interrupt_number, UART_IRQ_PRIO); - NVIC_SetPriority(c_dev()->interrupts.rx_error.interrupt_number, UART_IRQ_PRIO); - NVIC_SetPriority(c_dev()->interrupts.tx_buffer_empty.interrupt_number, UART_IRQ_PRIO); - NVIC_SetPriority(c_dev()->interrupts.tx_complete.interrupt_number, UART_IRQ_PRIO); + #if defined(UART_RX_IRQ_PRIO) + NVIC_SetPriority(c_dev()->interrupts.rx_data_available.interrupt_number, UART_RX_IRQ_PRIO); + NVIC_SetPriority(c_dev()->interrupts.rx_error.interrupt_number, UART_RX_IRQ_PRIO); + #endif + + #if defined(UART_TX_IRQ_PRIO) + NVIC_SetPriority(c_dev()->interrupts.tx_buffer_empty.interrupt_number, UART_TX_IRQ_PRIO); + NVIC_SetPriority(c_dev()->interrupts.tx_complete.interrupt_number, UART_TX_IRQ_PRIO); + #endif + + #if defined(UART_RX_DMA_IRQ_PRIO) && ENABLED(SERIAL_DMA) + NVIC_SetPriority(c_dev()->dma.rx.rx_data_available_dma_btc.interrupt_number, UART_RX_DMA_IRQ_PRIO); + #endif } void begin(uint32_t baud) { @@ -47,10 +80,17 @@ struct MarlinSerial : public Usart { Usart::begin(baud, config); setPriority(); } - #endif + + void begin(uint32_t baud, const stc_usart_uart_init_t *config, const bool rxNoiseFilter = true) { + Usart::begin(baud, config, rxNoiseFilter); + setPriority(); + } + #endif // UART_RX_IRQ_PRIO || UART_TX_IRQ_PRIO || UART_RX_DMA_IRQ_PRIO }; typedef Serial1Class MSerialT; extern MSerialT MSerial1; extern MSerialT MSerial2; +extern MSerialT MSerial3; +extern MSerialT MSerial4; diff --git a/Marlin/src/HAL/HC32/MinSerial.cpp b/Marlin/src/HAL/HC32/MinSerial.cpp index 93017ee0dfa2..3475aca39d4b 100644 --- a/Marlin/src/HAL/HC32/MinSerial.cpp +++ b/Marlin/src/HAL/HC32/MinSerial.cpp @@ -33,7 +33,7 @@ // Shared by both panic and PostMortem debugging // static void minserial_begin() { - #if !WITHIN(SERIAL_PORT, 1, 3) + #if !WITHIN(SERIAL_PORT, 1, 4) #warning "MinSerial requires a physical UART port for output." #warning "Disabling MinSerial because the used serial port is not a HW port." #else diff --git a/Marlin/src/HAL/HC32/README.md b/Marlin/src/HAL/HC32/README.md index c9ae8a9a20ef..c852edcf6cc1 100644 --- a/Marlin/src/HAL/HC32/README.md +++ b/Marlin/src/HAL/HC32/README.md @@ -6,12 +6,12 @@ This document provides notes on the HAL for the HC32F460 MCU. The HC32F460 HAL is designed to be generic enough for any HC32F460-based board. Adding support for a new HC32F460-based board will require the following steps: -1. Follow [the usual instructions](https://marlinfw.org/docs/development/boards.html#adding-a-new-board) to add a new board to Marlin. (i.e., Add a pins file, edit `boards.h` and `pins.h`, etc.) +1. Follow [the usual instructions](//marlinfw.org/docs/development/boards.html#adding-a-new-board) to add a new board to Marlin. (i.e., Add a pins file, edit `boards.h` and `pins.h`, etc.) 2. Determine the flash size your board uses: - Examine the board's main processor. (Refer the naming key in `hc32.ini`.) - Extend the `HC32F460C_common` base env for 256K, or `HC32F460E_common` for 512K. 3. Determine your board's application start address (see [below](#finding-the-application-start-address)) -4. Set `board_build.ld_args.flash_start` to the app start address once you've found it. If your board doesn't use a bootloader, you may be able to use the "ICSP" header or DFU. This document will be updated once we have more information about flashing without a bootloader. +4. Set `board_upload.offset_address` to the app start address once you've found it. If your board doesn't use a bootloader, you may be able to use the "ICSP" header or DFU. This document will be updated once we have more information about flashing without a bootloader. ### Finding the application start address @@ -50,10 +50,12 @@ SCB->VTOR = ((uint32_t) APP_START_ADDRESS & SCB_VTOR_TBLOFF_Msk); Just searching for `SCB->VTOR` should yield some results. From there, you just need to look at the value that's assigned to it. The example uses `APP_START_ADDRESS`. > [!NOTE] +> > Some vendors publish incomplete source code. But they sometimes leave version control related files in the repo, which can contain previous version of files that were removed. Find these by including folders like `.git` or `.svn` in your search. > [!NOTE] -> The example is based on the [Voxelab-64/Aquila_X2](https://github.com/Voxelab-64/Aquila_X2/blob/d1f23adf96920996b979bc31023d1dce236d05db/firmware/Sources/.svn/pristine/ec/ec82bcb480b511906bc3e6658450e3a803ab9813.svn-base#L96) which actually includes deleted files in its repo. +> +> The example is based on the [Voxelab-64/Aquila_X2](//github.com/Voxelab-64/Aquila_X2/blob/main/firmware/Sources/.svn/pristine/ec/ec82bcb480b511906bc3e6658450e3a803ab9813.svn-base#L96) which actually includes deleted files in its repo. 2. Using a linker script @@ -72,7 +74,8 @@ MEMORY ``` > [!NOTE] -> This example is based on [Voxelab-64/Aquila_X2](https://github.com/Voxelab-64/Aquila_X2/blob/d1f23adf96920996b979bc31023d1dce236d05db/firmware/Sources/main/hdsc32core/hc32f46x_flash.ld#L55) +> +> This example is based on [Voxelab-64/Aquila_X2](//github.com/Voxelab-64/Aquila_X2/blob/d1f23adf96920996b979bc31023d1dce236d05db/firmware/Sources/main/hdsc32core/hc32f46x_flash.ld#L55) ## Documentation on the HC32F460 @@ -96,15 +99,15 @@ Contact me on Discord (@shadow578) if you need it. This HAL depends on the following projects: -- [shadow578/platform-hc32f46x](https://github.com/shadow578/platform-hc32f46x) (PlatformIO platform for HC32F46x) -- [shadow578/framework-arduino-hc32f46x](https://github.com/shadow578/framework-arduino-hc32f46x) (Arduino framework for HC32F46x) -- [shadow578/framework-hc32f46x-ddl](https://github.com/shadow578/framework-hc32f46x-ddl) (HC32F46x DDL framework) +- [shadow578/platform-hc32f46x](//github.com/shadow578/platform-hc32f46x) (PlatformIO platform for HC32F46x) +- [shadow578/framework-arduino-hc32f46x](//github.com/shadow578/framework-arduino-hc32f46x) (Arduino framework for HC32F46x) +- [shadow578/framework-hc32f46x-ddl](//github.com/shadow578/framework-hc32f46x-ddl) (HC32F46x DDL framework) ## Credits This HAL wouldn't be possible without the following projects: -- [Voxelab-64/Aquila_X2](https://github.com/Voxelab-64/Aquila_X2) (original implementation) -- [alexqzd/Marlin-H32](https://github.com/alexqzd/Marlin-H32) (misc. fixes to the original implementation) -- [kgoveas/Arduino-Core-Template](https://github.com/kgoveas/Arduino-Core-Template) (template for Arduino headers) -- [stm32duino/Arduino_Core_STM32](https://github.com/stm32duino/Arduino_Core_STM32) (misc. Arduino functions) +- [Voxelab-64/Aquila_X2](//github.com/Voxelab-64/Aquila_X2) (original implementation) +- [alexqzd/Marlin-H32](//github.com/alexqzd/Marlin-H32) (misc. fixes to the original implementation) +- [kgoveas/Arduino-Core-Template](//github.com/kgoveas/Arduino-Core-Template) (template for Arduino headers) +- [stm32duino/Arduino_Core_STM32](//github.com/stm32duino/Arduino_Core_STM32) (misc. Arduino functions) diff --git a/Marlin/src/HAL/HC32/Servo.h b/Marlin/src/HAL/HC32/Servo.h index db2f60d190b5..0a9715494c7b 100644 --- a/Marlin/src/HAL/HC32/Servo.h +++ b/Marlin/src/HAL/HC32/Servo.h @@ -37,9 +37,9 @@ class MarlinServo { MarlinServo(); /** - * @brief attach the pin to the servo, set pin mode, return channel number - * @param pin pin to attach to - * @return channel number, -1 if failed + * @brief attach the pin to the servo, set pin mode, return channel number + * @param pin pin to attach to + * @return channel number, -1 if failed */ int8_t attach(const pin_t apin); diff --git a/Marlin/src/HAL/HC32/app_config.h b/Marlin/src/HAL/HC32/app_config.h new file mode 100644 index 000000000000..2714887028cf --- /dev/null +++ b/Marlin/src/HAL/HC32/app_config.h @@ -0,0 +1,75 @@ +/** + * app_config.h is included by the hc32f460 arduino build script for every source file. + * it is used to configure the arduino core (and ddl) automatically according + * to the settings in Configuration.h and Configuration_adv.h. + */ +#pragma once +#ifndef _HC32_APP_CONFIG_H_ +#define _HC32_APP_CONFIG_H_ + +#include "../../inc/MarlinConfigPre.h" +#include "sysclock.h" + +// +// dev mode +// +#if ENABLED(MARLIN_DEV_MODE) + #define __DEBUG 1 + #define __CORE_DEBUG 1 +#endif + +// +// Fault Handlers and Panic +// + +#if ENABLED(POSTMORTEM_DEBUGGING) + // disable arduino core fault handler, as we define our own + #define CORE_DISABLE_FAULT_HANDLER 1 +#endif + +// force-enable panic handler so that we can use our custom one (in MinSerial) +#define PANIC_ENABLE 1 + +// use short filenames in ddl debug and core panic output +#define __DEBUG_SHORT_FILENAMES 1 +#define __PANIC_SHORT_FILENAMES 1 + +// omit panic messages in core panic output +#define __OMIT_PANIC_MESSAGE 1 + +// +// Usart +// + +// disable serial globals (Serial1, Serial2, ...), as we define our own +#define DISABLE_SERIAL_GLOBALS 1 + +// increase the size of the Usart buffers (both RX and TX) +// NOTE: +// the heap usage will increase by (SERIAL_BUFFER_SIZE - 64) * "number of serial ports used" +// if running out of heap, the system may become unstable +//#define SERIAL_BUFFER_SIZE 256 + +// enable support for Usart Clock Divider / Oversampling auto config +#define USART_AUTO_CLKDIV_OS_CONFIG 1 + +// enable USART_RX_DMA_SUPPORT core option when SERIAL_DMA is enabled +#if ENABLED(SERIAL_DMA) + #define USART_RX_DMA_SUPPORT 1 +#endif + +// SoftwareSerial uses STM32duino API compatibility mode +#define SOFTWARE_SERIAL_STM32_API_COMPATIBILITY 1 + +// +// Misc. +// + +// redirect printf to host serial +#define REDIRECT_PRINTF_TO_SERIAL 1 + +// F_CPU is F_HCLK, as that's the main CPU core's clock. +// see 'sysclock.h' for more information. +#define F_CPU F_HCLK + +#endif // _HC32_APP_CONFIG_H_ diff --git a/Marlin/src/HAL/HC32/docs/HC32F460 series Datasheet Rev1.3.pdf b/Marlin/src/HAL/HC32/docs/HC32F460 series Datasheet Rev1.3.pdf new file mode 100644 index 000000000000..a8943c819eea Binary files /dev/null and b/Marlin/src/HAL/HC32/docs/HC32F460 series Datasheet Rev1.3.pdf differ diff --git a/Marlin/src/HAL/HC32/eeprom_bl24cxx.cpp b/Marlin/src/HAL/HC32/eeprom/eeprom_bl24cxx.cpp similarity index 95% rename from Marlin/src/HAL/HC32/eeprom_bl24cxx.cpp rename to Marlin/src/HAL/HC32/eeprom/eeprom_bl24cxx.cpp index 59da99b3f56d..a53a7c406205 100644 --- a/Marlin/src/HAL/HC32/eeprom_bl24cxx.cpp +++ b/Marlin/src/HAL/HC32/eeprom/eeprom_bl24cxx.cpp @@ -26,12 +26,12 @@ */ #ifdef ARDUINO_ARCH_HC32 -#include "../../inc/MarlinConfig.h" +#include "../../../inc/MarlinConfig.h" #if ENABLED(IIC_BL24CXX_EEPROM) -#include "../shared/eeprom_api.h" -#include "../shared/eeprom_if.h" +#include "../../shared/eeprom_api.h" +#include "../../shared/eeprom_if.h" #ifndef MARLIN_EEPROM_SIZE #error "MARLIN_EEPROM_SIZE is required for IIC_BL24CXX_EEPROM." diff --git a/Marlin/src/HAL/HC32/eeprom_if_iic.cpp b/Marlin/src/HAL/HC32/eeprom/eeprom_if_iic.cpp similarity index 89% rename from Marlin/src/HAL/HC32/eeprom_if_iic.cpp rename to Marlin/src/HAL/HC32/eeprom/eeprom_if_iic.cpp index 02b1d3fd54b9..0a161f23f272 100644 --- a/Marlin/src/HAL/HC32/eeprom_if_iic.cpp +++ b/Marlin/src/HAL/HC32/eeprom/eeprom_if_iic.cpp @@ -26,12 +26,12 @@ */ #ifdef ARDUINO_ARCH_HC32 -#include "../../inc/MarlinConfig.h" +#include "../../../inc/MarlinConfig.h" #if ENABLED(IIC_BL24CXX_EEPROM) -#include "../../libs/BL24CXX.h" -#include "../shared/eeprom_if.h" +#include "../../../libs/BL24CXX.h" +#include "../../shared/eeprom_if.h" void eeprom_init() { BL24CXX::init(); @@ -39,7 +39,7 @@ void eeprom_init() { void eeprom_write_byte(uint8_t *pos, unsigned char value) { const unsigned eeprom_address = (unsigned)pos; - return BL24CXX::writeOneByte(eeprom_address, value); + BL24CXX::writeOneByte(eeprom_address, value); } uint8_t eeprom_read_byte(uint8_t *pos) { diff --git a/Marlin/src/HAL/HC32/eeprom_sdcard.cpp b/Marlin/src/HAL/HC32/eeprom/eeprom_sdcard.cpp similarity index 93% rename from Marlin/src/HAL/HC32/eeprom_sdcard.cpp rename to Marlin/src/HAL/HC32/eeprom/eeprom_sdcard.cpp index 601e86dd3054..759ea5722d5e 100644 --- a/Marlin/src/HAL/HC32/eeprom_sdcard.cpp +++ b/Marlin/src/HAL/HC32/eeprom/eeprom_sdcard.cpp @@ -25,12 +25,12 @@ */ #ifdef ARDUINO_ARCH_HC32 -#include "../../inc/MarlinConfig.h" +#include "../../../inc/MarlinConfig.h" #if ENABLED(SDCARD_EEPROM_EMULATION) -#include "../shared/eeprom_api.h" -#include "../../sd/cardreader.h" +#include "../../shared/eeprom_api.h" +#include "../../../sd/cardreader.h" #define EEPROM_FILENAME "eeprom.dat" @@ -53,7 +53,7 @@ bool PersistentStore::access_start() { int bytes_read = file.read(HAL_eeprom_data, MARLIN_EEPROM_SIZE); if (bytes_read < 0) return false; - for (; bytes_read < MARLIN_EEPROM_SIZE; bytes_read++) + for (; bytes_read < long(MARLIN_EEPROM_SIZE); bytes_read++) HAL_eeprom_data[bytes_read] = 0xFF; file.close(); diff --git a/Marlin/src/HAL/HC32/eeprom_wired.cpp b/Marlin/src/HAL/HC32/eeprom/eeprom_wired.cpp similarity index 92% rename from Marlin/src/HAL/HC32/eeprom_wired.cpp rename to Marlin/src/HAL/HC32/eeprom/eeprom_wired.cpp index d9be65b4c07c..997180bd98c6 100644 --- a/Marlin/src/HAL/HC32/eeprom_wired.cpp +++ b/Marlin/src/HAL/HC32/eeprom/eeprom_wired.cpp @@ -2,6 +2,9 @@ * Marlin 3D Printer Firmware * Copyright (c) 2023 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 @@ -18,7 +21,7 @@ */ #ifdef ARDUINO_ARCH_HC32 -#include "../../inc/MarlinConfig.h" +#include "../../../inc/MarlinConfig.h" #if USE_WIRED_EEPROM @@ -29,8 +32,8 @@ * with simple implementations supplied by Marlin. */ -#include "../shared/eeprom_if.h" -#include "../shared/eeprom_api.h" +#include "../../shared/eeprom_if.h" +#include "../../shared/eeprom_api.h" #ifndef MARLIN_EEPROM_SIZE #error "MARLIN_EEPROM_SIZE is required for I2C / SPI EEPROM." diff --git a/Marlin/src/HAL/HC32/endstop_interrupts.cpp b/Marlin/src/HAL/HC32/endstop_interrupts.cpp index 99987ec76f5b..06c4d0e1867d 100644 --- a/Marlin/src/HAL/HC32/endstop_interrupts.cpp +++ b/Marlin/src/HAL/HC32/endstop_interrupts.cpp @@ -101,12 +101,14 @@ void setup_endstop_interrupts() { SETUP(Z_MIN_PROBE); + SETUP(CALIBRATION); + #undef SETUP } // Ensure 1 - 10 IRQs are registered // Disable some endstops if you encounter this error -#define ENDSTOPS_INTERRUPTS_COUNT COUNT_ENABLED(USE_X_MAX, USE_X_MIN, USE_X2_MAX, USE_X2_MIN, USE_Y_MAX, USE_Y_MIN, USE_Y2_MAX, USE_Y2_MIN, USE_Z_MAX, USE_Z_MIN, USE_Z2_MAX, USE_Z2_MIN, USE_Z3_MAX, USE_Z3_MIN, USE_Z4_MAX, USE_Z4_MIN, USE_Z_MIN_PROBE) +#define ENDSTOPS_INTERRUPTS_COUNT COUNT_ENABLED(USE_X_MAX, USE_X_MIN, USE_X2_MAX, USE_X2_MIN, USE_Y_MAX, USE_Y_MIN, USE_Y2_MAX, USE_Y2_MIN, USE_Z_MAX, USE_Z_MIN, USE_Z2_MAX, USE_Z2_MIN, USE_Z3_MAX, USE_Z3_MIN, USE_Z4_MAX, USE_Z4_MIN, USE_Z_MIN_PROBE, USE_CALIBRATION) #if ENDSTOPS_INTERRUPTS_COUNT > 10 #error "Too many endstop interrupts! HC32F460 only supports 10 endstop interrupts." #elif ENDSTOPS_INTERRUPTS_COUNT == 0 diff --git a/Marlin/src/HAL/HC32/inc/SanityCheck.h b/Marlin/src/HAL/HC32/inc/SanityCheck.h index ef8d9a997583..2c5a6e96fec4 100644 --- a/Marlin/src/HAL/HC32/inc/SanityCheck.h +++ b/Marlin/src/HAL/HC32/inc/SanityCheck.h @@ -20,6 +20,27 @@ * */ #pragma once +#include + +/** + * Require pins to be defined as macros with numerical values + */ +#ifndef PA0 + #error "Your ARM platform pins are not defined as macros, only as enums! Provide pins_arduino.h and/or 'buildroot/share/PlatformIO/variants/*/variant.h' to define the pins." +#endif + +#if !defined(ARDUINO_CORE_VERSION_INT) || !defined(GET_VERSION_INT) + // version macros were introduced in arduino core version 1.1.0 + // below that version, we polyfill them + #define GET_VERSION_INT(major, minor, patch) ((major * 100000) + (minor * 1000) + patch) + #define ARDUINO_CORE_VERSION_INT GET_VERSION_INT(1, 0, 0) +#endif + +#if ARDUINO_CORE_VERSION_INT < GET_VERSION_INT(1, 1, 0) + // because we use app_config.h introduced in arduino core version 1.1.0, the + // HAL is not compatible with older versions + #error "The HC32 HAL is not compatible with Arduino Core versions < 1.1.0. Consider updating the Arduino Core." +#endif #ifndef BOARD_XTAL_FREQUENCY #error "BOARD_XTAL_FREQUENCY is required for HC32F460." @@ -29,8 +50,11 @@ #error "FAST_PWM_FAN is not yet implemented for this platform." #endif -#if !defined(HAVE_SW_SERIAL) && HAS_TMC_SW_SERIAL - #error "Missing SoftwareSerial implementation." +// SoftwareSerial introduced in arduino core version 1.3.1 +#if ARDUINO_CORE_VERSION_INT < GET_VERSION_INT(1, 3, 1) + #if !defined(HAVE_SW_SERIAL) && HAS_TMC_SW_SERIAL + #error "Missing SoftwareSerial implementation." + #endif #endif #if ENABLED(SDCARD_EEPROM_EMULATION) && !HAS_MEDIA @@ -69,8 +93,25 @@ #error "POSTMORTEM_DEBUGGING requires CORE_DISABLE_FAULT_HANDLER to be set." #endif -#if defined(PANIC_ENABLE) +#ifdef PANIC_ENABLE #if defined(PANIC_USART1_TX_PIN) || defined(PANIC_USART2_TX_PIN) || defined(PANIC_USART3_TX_PIN) || defined(PANIC_USART3_TX_PIN) #error "HC32 HAL uses a custom panic handler. Do not define PANIC_USARTx_TX_PIN." #endif #endif + +#if ENABLED(SERIAL_DMA) + #if !defined(USART_RX_DMA_SUPPORT) + #error "SERIAL_DMA requires USART_RX_DMA_SUPPORT to be enabled in the arduino core." + #endif + + // Before arduino core version 1.2.0, USART_RX_DMA_SUPPORT did not implement + // core_hook_usart_rx_irq, which is required for the emergency parser. + // With 1.2.0, this was fixed (see https://github.com/shadow578/framework-arduino-hc32f46x/pull/25). + #if ENABLED(EMERGENCY_PARSER) && ARDUINO_CORE_VERSION_INT < GET_VERSION_INT(1, 2, 0) + #error "EMERGENCY_PARSER is not supported with SERIAL_DMA. Please disable either SERIAL_DMA or EMERGENCY_PARSER." + #endif + + #if ARDUINO_CORE_VERSION_INT < GET_VERSION_INT(1, 1, 0) + #error "SERIAL_DMA is not supported with arduino core version < 1.1.0." + #endif +#endif diff --git a/Marlin/src/HAL/HC32/pinsDebug.h b/Marlin/src/HAL/HC32/pinsDebug.h index 1bee6ceb3e50..9f8e23ce4478 100644 --- a/Marlin/src/HAL/HC32/pinsDebug.h +++ b/Marlin/src/HAL/HC32/pinsDebug.h @@ -2,6 +2,9 @@ * Marlin 3D Printer Firmware * Copyright (c) 2023 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 @@ -18,41 +21,47 @@ */ #pragma once +/** + * Pins Debugging for HC32 + * + * - NUMBER_PINS_TOTAL + * - MULTI_NAME_PAD + * - getPinByIndex(index) + * - printPinNameByIndex(index) + * - getPinIsDigitalByIndex(index) + * - digitalPinToAnalogIndex(pin) + * - getValidPinMode(pin) + * - isValidPin(pin) + * - isAnalogPin(pin) + * - digitalRead_mod(pin) + * - pwm_status(pin) + * - printPinPWM(pin) + * - printPinPort(pin) + * - printPinNumber(pin) + * - printPinAnalog(pin) + */ + #include "../../inc/MarlinConfig.h" #include "fastio.h" #include -// -// Translation of routines & variables used by pinsDebug.h -// #ifndef BOARD_NR_GPIO_PINS #error "Expected BOARD_NR_GPIO_PINS not found." #endif #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(P) IS_GPIO_PIN(P) // 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 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_PORT(p) print_port(p) -#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 getPinByIndex(x) pin_t(pin_array[x].pin) +#define digitalRead_mod(P) extDigitalRead(P) + +#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(P)); 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 @@ -71,14 +80,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; +int8_t digitalPinToAnalogIndex(const 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; +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 +95,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); +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)); +bool getPinIsDigitalByIndex(const int16_t index) { + const pin_t pin = getPinByIndex(index); + return (!isAnalogPin(pin)); } /** @@ -117,7 +126,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 +170,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]; diff --git a/Marlin/src/HAL/HC32/sdio.cpp b/Marlin/src/HAL/HC32/sdio.cpp index 4360d715ff19..5f30dda7207b 100644 --- a/Marlin/src/HAL/HC32/sdio.cpp +++ b/Marlin/src/HAL/HC32/sdio.cpp @@ -54,7 +54,7 @@ fn \ } -stc_sd_handle_t *handle; +stc_sd_handle_t *handle = nullptr; bool SDIO_Init() { // Configure SDIO pins @@ -66,66 +66,75 @@ bool SDIO_Init() { GPIO_SetFunc(BOARD_SDIO_CMD, Func_Sdio); GPIO_SetFunc(BOARD_SDIO_DET, Func_Sdio); + // If a handle is already initialized, free it before creating a new one + // otherwise, we will leak memory, which will eventually crash the system + if (handle != nullptr) { + delete handle->pstcDmaInitCfg; + delete handle->pstcCardInitCfg; + delete handle; + handle = nullptr; + } + // Create DMA configuration stc_sdcard_dma_init_t *dmaConf = new stc_sdcard_dma_init_t; dmaConf->DMAx = SDIO_DMA_PERIPHERAL; dmaConf->enDmaCh = SDIO_DMA_CHANNEL; + // Create card configuration + // This should be a fairly safe configuration for most cards + stc_sdcard_init_t *cardConf = new stc_sdcard_init_t; + cardConf->enBusWidth = SdiocBusWidth4Bit; + cardConf->enClkFreq = SdiocClk400K; + cardConf->enSpeedMode = SdiocNormalSpeedMode; + cardConf->pstcInitCfg = nullptr; + // Create handle in DMA mode handle = new stc_sd_handle_t; handle->SDIOCx = SDIO_PERIPHERAL; handle->enDevMode = SdCardDmaMode; handle->pstcDmaInitCfg = dmaConf; - - // Create card configuration - // This should be a fairly safe configuration for most cards - stc_sdcard_init_t cardConf = { - .enBusWidth = SdiocBusWidth4Bit, - .enClkFreq = SdiocClk400K, - .enSpeedMode = SdiocNormalSpeedMode, - //.pstcInitCfg = NULL, - }; + //handle->pstcCardInitCfg = cardConf; // assigned in SDCARD_Init // Initialize sd card - en_result_t rc = SDCARD_Init(handle, &cardConf); + en_result_t rc = SDCARD_Init(handle, cardConf); if (rc != Ok) printf("SDIO_Init() error (rc=%u)\n", rc); return rc == Ok; } bool SDIO_ReadBlock(uint32_t block, uint8_t *dst) { - CORE_ASSERT(handle != NULL, "SDIO not initialized"); - CORE_ASSERT(dst != NULL, "SDIO_ReadBlock dst is NULL"); + CORE_ASSERT(handle != nullptr, "SDIO not initialized", return false); + CORE_ASSERT(dst != nullptr, "SDIO_ReadBlock dst is NULL", return false); WITH_RETRY(SDIO_READ_RETRIES, { en_result_t rc = SDCARD_ReadBlocks(handle, block, 1, dst, SDIO_READ_TIMEOUT); if (rc == Ok) return true; - printf("SDIO_ReadBlock error (rc=%u; ErrorCode=%lu)\n", rc, handle->u32ErrorCode); + printf("SDIO_ReadBlock error (rc=%u; ErrorCode=%" PRIu32 ")\n", rc, handle->u32ErrorCode); }) return false; } bool SDIO_WriteBlock(uint32_t block, const uint8_t *src) { - CORE_ASSERT(handle != NULL, "SDIO not initialized"); - CORE_ASSERT(src != NULL, "SDIO_WriteBlock src is NULL"); + CORE_ASSERT(handle != nullptr, "SDIO not initialized", return false); + CORE_ASSERT(src != nullptr, "SDIO_WriteBlock src is NULL", return false); WITH_RETRY(SDIO_WRITE_RETRIES, { en_result_t rc = SDCARD_WriteBlocks(handle, block, 1, (uint8_t *)src, SDIO_WRITE_TIMEOUT); if (rc == Ok) return true; - printf("SDIO_WriteBlock error (rc=%u; ErrorCode=%lu)\n", rc, handle->u32ErrorCode); + printf("SDIO_WriteBlock error (rc=%u; ErrorCode=%" PRIu32 ")\n", rc, handle->u32ErrorCode); }) return false; } bool SDIO_IsReady() { - CORE_ASSERT(handle != NULL, "SDIO not initialized"); + CORE_ASSERT(handle != nullptr, "SDIO not initialized", return false); return bool(handle->stcCardStatus.READY_FOR_DATA); } uint32_t SDIO_GetCardSize() { - CORE_ASSERT(handle != NULL, "SDIO not initialized"); + CORE_ASSERT(handle != nullptr, "SDIO not initialized", return 0); // Multiply number of blocks with block size to get size in bytes const uint64_t cardSizeBytes = uint64_t(handle->stcSdCardInfo.u32LogBlockNbr) * uint64_t(handle->stcSdCardInfo.u32LogBlockSize); diff --git a/Marlin/src/HAL/HC32/spi_pins.h b/Marlin/src/HAL/HC32/spi_pins.h index 8a8e054b9bd9..5f1a94e9203a 100644 --- a/Marlin/src/HAL/HC32/spi_pins.h +++ b/Marlin/src/HAL/HC32/spi_pins.h @@ -2,6 +2,9 @@ * Marlin 3D Printer Firmware * Copyright (c) 2023 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 diff --git a/Marlin/src/HAL/HC32/sysclock.cpp b/Marlin/src/HAL/HC32/sysclock.cpp index d205d725cc72..0077bd4db3e0 100644 --- a/Marlin/src/HAL/HC32/sysclock.cpp +++ b/Marlin/src/HAL/HC32/sysclock.cpp @@ -26,64 +26,144 @@ #ifdef ARDUINO_ARCH_HC32 -// Get BOARD_XTAL_FREQUENCY from configuration / pins #include "../../inc/MarlinConfig.h" +#include "sysclock.h" #include #include +/*** + * @brief Automatically calculate M, N, P values for the MPLL to reach a target frequency. + * @param input_frequency The input frequency. + * @param target_frequency The target frequency. + * @return The MPLL configuration structure. Q and R are not set. + * + * @note + * Simplified MPLL block diagram, with intermediary clocks (1) = VCO_in, (2) = VCO_out: + * + * INPUT -> [/ M] -(1)-> [* N] -(2)-|-> [/ P] -> MPLL-P + */ +constexpr stc_clk_mpll_cfg_t get_mpll_config(double input_frequency, double target_frequency) { + // PLL input clock divider: M in [1, 24] + for (uint32_t M = 1; M <= 24; M++) { + double f_vco_in = input_frequency / M; + + // 1 <= VCO_in <= 25 MHz + if (f_vco_in < 1e6 || f_vco_in > 25e6) continue; + + // VCO multiplier: N in [20, 480] + for (uint32_t N = 20; N <= 480; N++) { + double f_vco_out = f_vco_in * N; + + // 240 <= VCO_out <= 480 MHz + if (f_vco_out < 240e6 || f_vco_out > 480e6) continue; + + // Output "P" divider: P in [2, 16] + for (uint32_t P = 2; P <= 16; P++) { + double f_calculated_out = f_vco_out / P; + if (f_calculated_out == target_frequency) { + // Found a match, return it + return { + .PllpDiv = P, + .PllqDiv = P, // Don't care for Q and R + .PllrDiv = P, // " + .plln = N, + .pllmDiv = M + }; + } + } + } + } + + // If no valid M, N, P found, return invalid config + return { 0, 0, 0, 0, 0 }; +} + +/** + * @brief Get the division factor required to get the target frequency from the input frequency. + * @tparam input_freq The input frequency. + * @tparam target_freq The target frequency. + * @return The division factor. + */ +template +constexpr en_clk_sysclk_div_factor_t get_division_factor() { + // Calculate the divider to get the target frequency + constexpr float fdivider = static_cast(input_freq) / static_cast(target_freq); + constexpr int divider = static_cast(fdivider); + + // divider must be an integer + static_assert(fdivider == divider, "Target frequency not achievable, divider must be an integer"); + + // divider must be between 1 and 64 (enum range), and must be a power of 2 + static_assert(divider >= 1 && divider <= 64, "Invalid divider, out of range"); + static_assert((divider & (divider - 1)) == 0, "Invalid divider, not a power of 2"); + + // return the divider + switch (divider) { + case 1: return ClkSysclkDiv1; + case 2: return ClkSysclkDiv2; + case 4: return ClkSysclkDiv4; + case 8: return ClkSysclkDiv8; + case 16: return ClkSysclkDiv16; + case 32: return ClkSysclkDiv32; + case 64: return ClkSysclkDiv64; + } +} + +/** + * @brief Validate the runtime clocks match the expected values. + */ +void validate_system_clocks() { + #define CLOCK_ASSERT(expected, actual) \ + if (expected != actual) { \ + SERIAL_ECHOPGM( \ + "Clock Mismatch for " #expected ": " \ + "expected ", expected, \ + ", got ", actual \ + ); \ + CORE_ASSERT_FAIL("Clock Mismatch: " #expected); \ + } + + update_system_clock_frequencies(); + + CLOCK_ASSERT(F_SYSTEM_CLOCK, SYSTEM_CLOCK_FREQUENCIES.system); + CLOCK_ASSERT(F_HCLK, SYSTEM_CLOCK_FREQUENCIES.hclk); + CLOCK_ASSERT(F_EXCLK, SYSTEM_CLOCK_FREQUENCIES.exclk); + CLOCK_ASSERT(F_PCLK0, SYSTEM_CLOCK_FREQUENCIES.pclk0); + CLOCK_ASSERT(F_PCLK1, SYSTEM_CLOCK_FREQUENCIES.pclk1); + CLOCK_ASSERT(F_PCLK2, SYSTEM_CLOCK_FREQUENCIES.pclk2); + CLOCK_ASSERT(F_PCLK3, SYSTEM_CLOCK_FREQUENCIES.pclk3); + CLOCK_ASSERT(F_PCLK4, SYSTEM_CLOCK_FREQUENCIES.pclk4); +} + +/** + * @brief Configure HC32 system clocks. + * + * This function is called by the Arduino core early in the startup process, before setup() is called. + * It is used to configure the system clocks to the desired state. + * + * See https://github.com/MarlinFirmware/Marlin/pull/27099 for more information. + */ void core_hook_sysclock_init() { // Set wait cycles, as we are about to switch to 200 MHz HCLK sysclock_configure_flash_wait_cycles(); sysclock_configure_sram_wait_cycles(); - // Configure MPLLp to 200 MHz output, with different settings depending on XTAL availability - #if BOARD_XTAL_FREQUENCY == 8000000 // 8 MHz XTAL - // - M = 1 => 8 MHz / 1 = 8 MHz - // - N = 50 => 8 MHz * 50 = 400 MHz - // - P = 2 => 400 MHz / 2 = 200 MHz (sysclk) - // - Q,R = 4 => 400 MHz / 4 = 100 MHz (dont care) - stc_clk_mpll_cfg_t pllConf = { - .PllpDiv = 2u, // P - .PllqDiv = 4u, // Q - .PllrDiv = 4u, // R - .plln = 50u, // N - .pllmDiv = 1u, // M - }; - sysclock_configure_xtal(); - sysclock_configure_mpll(ClkPllSrcXTAL, &pllConf); - - #elif BOARD_XTAL_FREQUENCY == 16000000 // 16 MHz XTAL - // - M = 1 => 16 MHz / 1 = 16 MHz - // - N = 50 => 16 MHz * 25 = 400 MHz - // - P = 2 => 400 MHz / 2 = 200 MHz (sysclk) - // - Q,R = 4 => 400 MHz / 4 = 100 MHz (dont care) - stc_clk_mpll_cfg_t pllConf = { - .PllpDiv = 2u, // P - .PllqDiv = 4u, // Q - .PllrDiv = 4u, // R - .plln = 50u, // N - .pllmDiv = 1u, // M - }; + // Select MPLL input frequency based on clock availability + #if BOARD_XTAL_FREQUENCY == 8000000 || BOARD_XTAL_FREQUENCY == 16000000 // 8 MHz or 16 MHz XTAL + constexpr uint32_t mpll_input_clock = BOARD_XTAL_FREQUENCY; + sysclock_configure_xtal(); - sysclock_configure_mpll(ClkPllSrcXTAL, &pllConf); - #warning "HC32F460 with 16 MHz XTAL has not been tested." + #if BOARD_XTAL_FREQUENCY == 16000000 + #warning "HC32F460 with 16 MHz XTAL has not been tested." + #endif #else // HRC (16 MHz) - // - M = 1 => 16 MHz / 1 = 16 MHz - // - N = 25 => 16 MHz * 25 = 400 MHz - // - P = 2 => 400 MHz / 2 = 200 MHz (sysclk) - // - Q,R = 4 => 400 MHz / 4 = 100 MHz (dont care) - stc_clk_mpll_cfg_t pllConf = { - .PllpDiv = 2u, // P - .PllqDiv = 4u, // Q - .PllrDiv = 4u, // R - .plln = 25u, // N - .pllmDiv = 1u, // M - }; + + constexpr uint32_t mpll_input_clock = 16000000; + sysclock_configure_hrc(); - sysclock_configure_mpll(ClkPllSrcHRC, &pllConf); // HRC could have been configured by ICG to 20 MHz // TODO: handle gracefully if HRC is not 16 MHz @@ -94,31 +174,69 @@ void core_hook_sysclock_init() { #ifdef BOARD_XTAL_FREQUENCY #warning "No valid XTAL frequency defined, falling back to HRC." #endif + #endif - // 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) - stc_clk_sysclk_cfg_t sysClkConf = { - .enHclkDiv = ClkSysclkDiv1, // HCLK = 200 MHz (CPU) - .enExclkDiv = ClkSysclkDiv2, // EXCLK = 100 MHz (SDIO) - .enPclk0Div = ClkSysclkDiv1, // PCLK0 = 200 MHz (Timer6 (not used)) - .enPclk1Div = ClkSysclkDiv4, // PCLK1 = 50 MHz (USART, SPI, I2S, Timer0 (step+temp), TimerA (Servo)) - .enPclk2Div = ClkSysclkDiv4, // PCLK2 = 50 MHz (ADC) - .enPclk3Div = ClkSysclkDiv4, // PCLK3 = 50 MHz (I2C, WDT) - .enPclk4Div = ClkSysclkDiv2, // PCLK4 = 100 MHz (ADC ctl) + // Automagically calculate MPLL configuration + constexpr stc_clk_mpll_cfg_t pllConf = get_mpll_config(mpll_input_clock, F_SYSTEM_CLOCK); + static_assert(pllConf.pllmDiv != 0 && pllConf.plln != 0 && pllConf.PllpDiv != 0, "MPLL auto-configuration failed"); + sysclock_configure_mpll(ClkPllSrcXTAL, &pllConf); + + // Setup clock divisors + constexpr stc_clk_sysclk_cfg_t sysClkConf = { + .enHclkDiv = get_division_factor(), + .enExclkDiv = get_division_factor(), + .enPclk0Div = get_division_factor(), + .enPclk1Div = get_division_factor(), + .enPclk2Div = get_division_factor(), + .enPclk3Div = get_division_factor(), + .enPclk4Div = get_division_factor(), }; sysclock_set_clock_dividers(&sysClkConf); - // Set power mode - #define POWER_MODE_SYSTEM_CLOCK 200000000 // 200 MHz - power_mode_update_pre(POWER_MODE_SYSTEM_CLOCK); + // Set power mode, before switch + power_mode_update_pre(F_SYSTEM_CLOCK); - // Switch to MPLL as sysclk source + // Switch to MPLL-P as system clock source CLK_SetSysClkSource(CLKSysSrcMPLL); - // Set power mode - power_mode_update_post(POWER_MODE_SYSTEM_CLOCK); - #undef POWER_MODE_SYSTEM_CLOCK + // Set power mode, after switch + power_mode_update_post(F_SYSTEM_CLOCK); + + // Verify clocks match expected values (at runtime) + #if ANY(MARLIN_DEV_MODE, ALWAYS_VALIDATE_CLOCKS) + validate_system_clocks(); + #endif + + // Verify clock configuration (at compile time) + #if ARDUINO_CORE_VERSION_INT >= GET_VERSION_INT(1, 2, 0) + assert_mpll_config_valid< + mpll_input_clock, + pllConf.pllmDiv, + pllConf.plln, + pllConf.PllpDiv, + pllConf.PllqDiv, + pllConf.PllrDiv + >(); + + assert_system_clocks_valid< + F_SYSTEM_CLOCK, + sysClkConf.enHclkDiv, + sysClkConf.enPclk0Div, + sysClkConf.enPclk1Div, + sysClkConf.enPclk2Div, + sysClkConf.enPclk3Div, + sysClkConf.enPclk4Div, + sysClkConf.enExclkDiv + >(); + + static_assert(get_mpll_output_clock( + mpll_input_clock, + pllConf.pllmDiv, + pllConf.plln, + pllConf.PllpDiv + ) == F_SYSTEM_CLOCK, "actual MPLL output clock does not match F_SYSTEM_CLOCK"); + #endif } #endif // ARDUINO_ARCH_HC32 diff --git a/Marlin/src/HAL/HC32/sysclock.h b/Marlin/src/HAL/HC32/sysclock.h new file mode 100644 index 000000000000..783d5677e9de --- /dev/null +++ b/Marlin/src/HAL/HC32/sysclock.h @@ -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 . + * + */ +#pragma once + +/** + * HC32F460 system clock configuration. + * + * With the HC32 HAL, the various peripheral clocks (including the CPU clock) are derived + * from the main PLL (MPLL-P) output (referred to at F_SYSTEM_CLOCK). + * + * F_SYSTEM_CLOCK is the target frequency of the main PLL, and the PLL is automatically configured + * to achieve this frequency. + * + * The peripheral clocks are the result of integer division of F_SYSTEM_CLOCK. + * Their target frequencies are defined here, and the required division factors are calculated automatically. + * Note that the division factor must be a power of 2 between 1 and 64. + * If the target frequency is not achievable, a compile-time error will be generated. + * + * Additionally, there are interdependencies between the peripheral clocks, which are described in + * Section 4.4 "Working Clock Specifications" of the HC32F460 Reference Manual. + * With Arduino core >= 1.2.0, these interdependencies are checked at compile time. + * On earlier versions, you are on your own. + * + * For all clock frequencies, they can be checked at runtime by enabling the 'ALWAYS_VALIDATE_CLOCKS' define. + * In MARLIN_DEV_MODE, they will also be printed to the serial console by 'MarlinHAL::HAL_clock_frequencies_dump'. + * + * See https://github.com/MarlinFirmware/Marlin/pull/27099 for more information. + */ + +// Target peripheral clock frequencies, must be integer divisors of F_SYSTEM_CLOCK. +// Changing the frequency here will automagically update everything else. +#define F_HCLK 200000000UL // 200 MHz; CPU +#define F_EXCLK (F_HCLK / 2) // 100 MHz; SDIO +#define F_PCLK0 (F_HCLK / 2) // 100 MHz; Timer6 (unused) +#define F_PCLK1 (F_HCLK / 4) // 50 MHz; USART, SPI, Timer0 (step + temp), TimerA (Servo) +#define F_PCLK2 (F_HCLK / 8) // 25 MHz; ADC Sampling +#define F_PCLK3 (F_HCLK / 8) // 25 MHz; I2C, WDT +#define F_PCLK4 (F_HCLK / 2) // 100 MHz; ADC Control + +// MPLL-P clock target frequency. This must be >= the highest peripheral clock frequency. +// PLL config is automatically calculated based on this value. +#define F_SYSTEM_CLOCK F_HCLK + +// The Peripheral clocks are only checked at runtime if this is enabled OR MARLIN_DEV_MODE is enabled. +// Compile time checks are always performed with Arduino core version >= 1.2.0. +#define ALWAYS_VALIDATE_CLOCKS 1 diff --git a/Marlin/src/HAL/HC32/timers.cpp b/Marlin/src/HAL/HC32/timers.cpp index 5f7d499622d5..b8eab34cf7bd 100644 --- a/Marlin/src/HAL/HC32/timers.cpp +++ b/Marlin/src/HAL/HC32/timers.cpp @@ -35,19 +35,19 @@ Timer0 temp_timer(&TIMER02A_config, &Temp_Handler); */ Timer0 step_timer(&TIMER02B_config, &Step_Handler); -void HAL_timer_start(const timer_channel_t timer_num, const uint32_t frequency) { - if (timer_num == TEMP_TIMER_NUM) { +void HAL_timer_start(const timer_channel_t timer_ch, const uint32_t frequency) { + if (timer_ch == MF_TIMER_TEMP) { CORE_DEBUG_PRINTF("HAL_timer_start: temp timer, f=%ld\n", long(frequency)); - timer_num->start(frequency, TEMP_TIMER_PRESCALE); - timer_num->setCallbackPriority(TEMP_TIMER_PRIORITY); + timer_ch->start(frequency, TEMP_TIMER_PRESCALE); + timer_ch->setCallbackPriority(TEMP_TIMER_PRIORITY); } - else if (timer_num == STEP_TIMER_NUM) { + else if (timer_ch == MF_TIMER_STEP) { CORE_DEBUG_PRINTF("HAL_timer_start: step timer, f=%ld\n", long(frequency)); - timer_num->start(frequency, STEPPER_TIMER_PRESCALE); - timer_num->setCallbackPriority(STEP_TIMER_PRIORITY); + timer_ch->start(frequency, STEPPER_TIMER_PRESCALE); + timer_ch->setCallbackPriority(STEP_TIMER_PRIORITY); } else { - CORE_ASSERT_FAIL("HAL_timer_start: invalid timer_num") + CORE_ASSERT_FAIL("HAL_timer_start: invalid timer_ch") } } diff --git a/Marlin/src/HAL/HC32/timers.h b/Marlin/src/HAL/HC32/timers.h index 17d8967982c2..8a4465e2d86e 100644 --- a/Marlin/src/HAL/HC32/timers.h +++ b/Marlin/src/HAL/HC32/timers.h @@ -20,13 +20,14 @@ #pragma once #include #include +#include "sysclock.h" // // Timer Types // typedef Timer0 *timer_channel_t; typedef uint16_t hal_timer_t; -#define HAL_TIMER_TYPE_MAX 0xFFFF +#define HAL_TIMER_TYPE_MAX hal_timer_t(UINT16_MAX) // // Timer instances @@ -38,88 +39,83 @@ extern Timer0 step_timer; // Timer Configurations // -// TODO: some calculations (step irq min_step_rate) require the timer rate to be known at compile time -// this is not possible with the HC32F460, as the timer rate depends on PCLK1 -// as a workaround, PCLK1 = 50MHz is assumed (check with clock dump in MarlinHAL::init()) -#define HAL_TIMER_RATE 50000000 // 50MHz -// #define HAL_TIMER_RATE TIMER0_BASE_FREQUENCY - -// TODO: CYCLES_PER_MICROSECOND seems to be used by Marlin to calculate the number of cycles per microsecond in the timer ISRs -// by default, it uses F_CPU, but since that is not known at compile time for HC32, we overwrite it here -#undef CYCLES_PER_MICROSECOND -#define CYCLES_PER_MICROSECOND (HAL_TIMER_RATE / 1000000UL) +/** + * HAL_TIMER_RATE must be known at compile time since it's used to calculate + * STEPPER_TIMER_RATE, which is used in 'constexpr' calculations. + * On the HC32F460 the timer rate depends on PCLK1, which is derived from the + * system clock configured at runtime. + * Thus we use the 'F_PCLK1' constant defined in 'sysclock.h'. + * + * See https://github.com/MarlinFirmware/Marlin/pull/27099 for more information. + * + * NOTE: If the 'constexpr' requirement is ever lifted, TIMER0_BASE_FREQUENCY could + * be used instead. Tho this would probably not make any noticeable difference. + */ +#define HAL_TIMER_RATE F_PCLK1 // Temperature timer -#define TEMP_TIMER_NUM (&temp_timer) -#define TEMP_TIMER_PRIORITY DDL_IRQ_PRIORITY_02 -#define TEMP_TIMER_PRESCALE 16ul -#define TEMP_TIMER_RATE 1000 // 1kHz -#define TEMP_TIMER_FREQUENCY TEMP_TIMER_RATE // Alias for Marlin +#define MF_TIMER_TEMP (&temp_timer) +#define TEMP_TIMER_PRIORITY DDL_IRQ_PRIORITY_02 +#define TEMP_TIMER_PRESCALE 16UL // 12.5MHz +#define TEMP_TIMER_RATE 1000 // 1kHz +#define TEMP_TIMER_FREQUENCY TEMP_TIMER_RATE // 1kHz also // Stepper timer -#define STEP_TIMER_NUM (&step_timer) -#define STEP_TIMER_PRIORITY DDL_IRQ_PRIORITY_01 -#define STEPPER_TIMER_PRESCALE 16ul +#define MF_TIMER_STEP (&step_timer) +#define STEP_TIMER_PRIORITY DDL_IRQ_PRIORITY_00 // Top priority, nothing else uses it +#define STEPPER_TIMER_PRESCALE 16UL // 12.5MHz -// TODO: STEPPER_TIMER_RATE seems to work fine like this, but requires further testing... -#define STEPPER_TIMER_RATE (HAL_TIMER_RATE / STEPPER_TIMER_PRESCALE) // 50MHz / 16 = 3.125MHz -#define STEPPER_TIMER_TICKS_PER_US (STEPPER_TIMER_RATE / 1000000) +#define STEPPER_TIMER_RATE (HAL_TIMER_RATE / STEPPER_TIMER_PRESCALE) // 50MHz / 16 = 3.125MHz +#define STEPPER_TIMER_TICKS_PER_US ((STEPPER_TIMER_RATE) / 1000000UL) // Integer 3 // Pulse timer (== stepper timer) -#define PULSE_TIMER_NUM STEP_TIMER_NUM -#define PULSE_TIMER_PRESCALE STEPPER_TIMER_PRESCALE -#define PULSE_TIMER_TICKS_PER_US STEPPER_TIMER_TICKS_PER_US - -// -// Channel aliases -// -#define MF_TIMER_TEMP TEMP_TIMER_NUM -#define MF_TIMER_STEP STEP_TIMER_NUM -#define MF_TIMER_PULSE PULSE_TIMER_NUM +#define MF_TIMER_PULSE MF_TIMER_STEP +#define PULSE_TIMER_RATE STEPPER_TIMER_RATE // (Hz) Frequency of Pulse Timer +#define PULSE_TIMER_PRESCALE STEPPER_TIMER_PRESCALE // // HAL functions // -void HAL_timer_start(const timer_channel_t timer_num, const uint32_t frequency); +void HAL_timer_start(const timer_channel_t timer_ch, const uint32_t frequency); // Inlined since they are somewhat critical #define MARLIN_HAL_TIMER_INLINE_ATTR __attribute__((always_inline)) inline -MARLIN_HAL_TIMER_INLINE_ATTR void HAL_timer_enable_interrupt(const timer_channel_t timer_num) { - timer_num->resume(); +MARLIN_HAL_TIMER_INLINE_ATTR void HAL_timer_enable_interrupt(const timer_channel_t timer_ch) { + timer_ch->resume(); } -MARLIN_HAL_TIMER_INLINE_ATTR void HAL_timer_disable_interrupt(const timer_channel_t timer_num) { - timer_num->pause(); +MARLIN_HAL_TIMER_INLINE_ATTR void HAL_timer_disable_interrupt(const timer_channel_t timer_ch) { + timer_ch->pause(); } -MARLIN_HAL_TIMER_INLINE_ATTR bool HAL_timer_interrupt_enabled(const timer_channel_t timer_num) { - return timer_num->isPaused(); +MARLIN_HAL_TIMER_INLINE_ATTR bool HAL_timer_interrupt_enabled(const timer_channel_t timer_ch) { + return timer_ch->isPaused(); } -MARLIN_HAL_TIMER_INLINE_ATTR void HAL_timer_set_compare(const timer_channel_t timer_num, const hal_timer_t compare) { - timer_num->setCompareValue(compare); +MARLIN_HAL_TIMER_INLINE_ATTR void HAL_timer_set_compare(const timer_channel_t timer_ch, const hal_timer_t compare) { + timer_ch->setCompareValue(compare); } -MARLIN_HAL_TIMER_INLINE_ATTR hal_timer_t HAL_timer_get_count(const timer_channel_t timer_num) { - return timer_num->getCount(); +MARLIN_HAL_TIMER_INLINE_ATTR hal_timer_t HAL_timer_get_count(const timer_channel_t timer_ch) { + return timer_ch->getCount(); } -MARLIN_HAL_TIMER_INLINE_ATTR void HAL_timer_isr_prologue(const timer_channel_t timer_num) { - timer_num->clearInterruptFlag(); +MARLIN_HAL_TIMER_INLINE_ATTR void HAL_timer_isr_prologue(const timer_channel_t timer_ch) { + timer_ch->clearInterruptFlag(); } -MARLIN_HAL_TIMER_INLINE_ATTR void HAL_timer_isr_epilogue(const timer_channel_t timer_num) {} +inline void HAL_timer_isr_epilogue(const timer_channel_t) {} // // HAL function aliases // -#define ENABLE_STEPPER_DRIVER_INTERRUPT() HAL_timer_enable_interrupt(STEP_TIMER_NUM) -#define DISABLE_STEPPER_DRIVER_INTERRUPT() HAL_timer_disable_interrupt(STEP_TIMER_NUM) -#define STEPPER_ISR_ENABLED() HAL_timer_interrupt_enabled(STEP_TIMER_NUM) +#define ENABLE_STEPPER_DRIVER_INTERRUPT() HAL_timer_enable_interrupt(MF_TIMER_STEP) +#define DISABLE_STEPPER_DRIVER_INTERRUPT() HAL_timer_disable_interrupt(MF_TIMER_STEP) +#define STEPPER_ISR_ENABLED() HAL_timer_interrupt_enabled(MF_TIMER_STEP) -#define ENABLE_TEMPERATURE_INTERRUPT() HAL_timer_enable_interrupt(TEMP_TIMER_NUM) -#define DISABLE_TEMPERATURE_INTERRUPT() HAL_timer_disable_interrupt(TEMP_TIMER_NUM); +#define ENABLE_TEMPERATURE_INTERRUPT() HAL_timer_enable_interrupt(MF_TIMER_TEMP) +#define DISABLE_TEMPERATURE_INTERRUPT() HAL_timer_disable_interrupt(MF_TIMER_TEMP); // // HAL ISR callbacks @@ -128,8 +124,8 @@ void Step_Handler(); void Temp_Handler(); #ifndef HAL_STEP_TIMER_ISR -#define HAL_STEP_TIMER_ISR() void Step_Handler() + #define HAL_STEP_TIMER_ISR() void Step_Handler() #endif #ifndef HAL_TEMP_TIMER_ISR -#define HAL_TEMP_TIMER_ISR() void Temp_Handler() + #define HAL_TEMP_TIMER_ISR() void Temp_Handler() #endif diff --git a/Marlin/src/HAL/HC32/u8g/u8g_com_HAL_HC32_sw_spi.cpp b/Marlin/src/HAL/HC32/u8g/u8g_com_HAL_HC32_sw_spi.cpp index 9f360961942d..97f4b85eaf83 100644 --- a/Marlin/src/HAL/HC32/u8g/u8g_com_HAL_HC32_sw_spi.cpp +++ b/Marlin/src/HAL/HC32/u8g/u8g_com_HAL_HC32_sw_spi.cpp @@ -82,7 +82,7 @@ static inline uint8_t swSpiTransfer_mode_3(uint8_t b, const uint8_t spi_speed, c } static void u8g_sw_spi_shift_out(uint8_t val) { - #if ANY(FYSETC_MINI_12864, MKS_MINI_12864) + #if U8G_SPI_USE_MODE_3 swSpiTransfer_mode_3(val, SPI_speed); #else swSpiTransfer_mode_0(val, SPI_speed); @@ -116,15 +116,15 @@ uint8_t u8g_com_HAL_HC32_sw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, voi break; case U8G_COM_MSG_CHIP_SELECT: - #if ANY(FYSETC_MINI_12864, MKS_MINI_12864) // This LCD SPI is running mode 3 while SD card is running mode 0 - if (arg_val) { // SCK idle state needs to be set to the proper idle state before - // the next chip select goes active - WRITE(DOGLCD_SCK, HIGH); // Set SCK to mode 3 idle state before CS goes active + #if U8G_SPI_USE_MODE_3 // This LCD SPI is running mode 3 while SD card is running mode 0 + if (arg_val) { // SCK idle state needs to be set to the proper idle state before + // the next chip select goes active + WRITE(DOGLCD_SCK, HIGH); // Set SCK to mode 3 idle state before CS goes active WRITE(DOGLCD_CS, LOW); } else { WRITE(DOGLCD_CS, HIGH); - WRITE(DOGLCD_SCK, LOW); // Set SCK to mode 0 idle state after CS goes inactive + WRITE(DOGLCD_SCK, LOW); // Set SCK to mode 0 idle state after CS goes inactive } #else WRITE(DOGLCD_CS, !arg_val); diff --git a/Marlin/src/HAL/LINUX/AGENTS.md b/Marlin/src/HAL/LINUX/AGENTS.md new file mode 100644 index 000000000000..263fedb99a9c --- /dev/null +++ b/Marlin/src/HAL/LINUX/AGENTS.md @@ -0,0 +1,66 @@ +# AGENTS.md — LINUX HAL + +Working notes and conventions for the **LINUX HAL** folder (`Marlin/src/HAL/LINUX/`). + +## Build & Test Loop + +The LINUX HAL is a **native host build** — there is no MCU and no Arduino framework. Marlin compiles as a regular C++ program (a console application) and runs on the host CPU. Always validate with `mftest` (it regenerates `Marlin/Configuration.h` per target, which raw `pio run -e` does not do reliably): + +```bash +cd "$(git rev-parse --show-toplevel)" +buildroot/bin/mftest -t linux_native -n1 -y # host-native build, EEPROM enabled +buildroot/bin/mftest -t linux_native_test -n1 -y # build + Unity unit tests +``` + +The `linux_native` target (`buildroot/tests/linux_native/config-01.ini`, `BOARD_SIMULATED`, `EEPROM_SETTINGS` on) is the primary smoke target and must be green before considering LINUX work done. The `linux_native_test` target extends it with the PlatformIO Unity test framework (`+` sources, `-Werror`) and is the one to run when changing HAL code that has unit tests. + +> Do NOT use `rm -rf .pio/build/...` to force a rebuild — the cross-profile write guard blocks it and it is unsafe. Use `pio run -e -t clean` (removes only build artifacts) or just let `mftest` rebuild. + +## Framework / Platform Integration (native, not embedded) + +This is a `platform = native` build (`ini/native.ini`, `[env:linux_native]`). There is **no Arduino core, no toolchain SDK, no vendored framework** — the only "platform" is the host's C++ compiler (`-std=gnu++17`), linked against `librt` / `libpthread`. The HAL selected by `__PLAT_LINUX__` (`HAL/platforms.h` maps `__PLAT_LINUX__` → `src/HAL/LINUX/`); it has no `ARDUINO_ARCH_*` define. `main()` lives in `main.cpp` (not Arduino's `setup()`/`loop()` shim) and spins up `std::thread`s for serial I/O and the simulation loop, then calls `setup()` / `loop()`. + +Because it is a plain C++ program: + +- `build_src_filter` adds `+` to the common filter; the rest of Marlin is compiled normally. +- `build_src_flags` adds `-IMarlin/src/HAL/LINUX/include` so the local `Arduino.h`, `serial.h`, and `pinmapping.h` are found ahead of any system headers. +- `-lrt -lpthread` are required link flags (timers/clock, threading). +- `HAS_LIBBSD` (macOS) provides `strlcpy`; on Linux the HAL falls back to a local `MarlinHAL::_strlcpy` (`HAL.h`/`HAL.cpp`), so no system lib is needed there. + +### Gotchas that are not obvious from the source + +1. **No real pins — GPIO is a software array.** `Gpio::pin_count = 255` (`hardware/Gpio.h`); pins are indices into an in-memory `pin_map[]` (`Gpio.cpp`), not physical lines. `fastio.h` macros (`READ_PIN`, `WRITE_PIN`, `SET_DIR_*`) all delegate to `Gpio::` static methods. `PIN_EXISTS`/`GET_PIN_MAP_INDEX` are pass-through identity ops; `PWM_PIN` and `INTERRUPT_PIN` always return `false`. Don't add hardware-specific pin logic here. + +2. **Serial is virtual, via stdio.** `HalSerial` (`include/serial.h`) is a pair of `RingBuffer` buffers. `main.cpp`'s `write_serial_thread` flushes the TX ring to `stdout` and `read_serial_thread` reads `stdin` (via `fgets`) into the RX ring. So G-code must be piped/entered on the process's stdin/stdout — there is no UART/USB device. `host_connected` starts `true`; `write()` busy-waits while the TX ring is full. + +3. **EEPROM is a file on disk.** `eeprom.cpp` reads/writes `eeprom.dat` (4 KB, `MARLIN_EEPROM_SIZE = 0x1000`) in the current working directory via `fopen`. Settings persist across runs in that file, and a stale `eeprom.dat` in your CWD can mask config changes — delete it to get a fresh store. The buffer is the in-memory source of truth; `access_finish()` is what actually flushes to disk. + +4. **No real ADC / PID feedback.** `MarlinHAL::adc_value()` synthesizes a 10-bit reading from the simulated GPIO bit value (`Gpio::get(pin) >> 2`). `main.cpp`'s `simulation_loop()` runs `Heater` (hotend/bed) and `LinearAxis` (X/Y/Z/E) objects that update pin state in software — this is the only "hardware" feedback loop, and it runs on its own thread alongside `loop()`. + +5. **`main()` is excluded under `UNIT_TEST`.** `main.cpp` is wrapped in `#ifndef UNIT_TEST` (and `#ifdef __PLAT_LINUX__`). The `linux_native_test` env compiles the HAL as a library for Unity tests and does **not** link `main()` — so a test build "succeeds" without the console entry point. Don't add HAL runtime code that only runs from `main()` and expect it to execute under the test env. + +6. **Threads + `yield()` model timing, not interrupts.** There are no real ISRs; `CRITICAL_SECTION_START/END` are empty, `isr_on/off` are no-ops, and `DELAY_CYCLES` is `Clock::delayCycles()`. The step/sim timing is driven by `Clock` (set to `F_CPU` in `main()`) and `HAL_timer_init()`, plus per-thread `std::this_thread::yield()`. Real-time behavior is approximate and host-load dependent. + +7. **`freeMemory()` always returns 0.** Unlike embedded HALs, the LINUX HAL reports no meaningful free-SRAM figure (`HAL.cpp` returns `0`; `MarlinHAL::freeMemory()` wraps it). Don't key any logic off the returned heap value. + +8. **`NUM_ANALOG_INPUTS` is fixed at 16.** `include/pinmapping.h` hard-codes 16 analog inputs mapped to the top of the 255-entry digital space (`analog_offset`). The analog index ↔ digital pin conversions are constexpr arithmetic, not a board-specific table. + +9. **SD / media is file-based, not SPI.** There is no SD card controller; `spi_pins.h` still defines default `SD_SCK_PIN`/`SD_MISO_PIN`/`SD_MOSI_PIN` (50/51/52) and a `SOFTWARE_SPI` fallback, but actual media acts on the host filesystem. Treat SD operations as ordinary file I/O in the host environment. + +10. **Optional GPIO/position CSV logging.** `#define GPIO_LOGGING` in `main.cpp` (off by default) attaches an `IOLoggerCSV` and writes `all_gpio_log.csv` / `axis_position_log.csv` into the CWD. It is a debugging aid only and adds filesystem side effects; leave it disabled unless actively tracing pin/axis activity. + +## Conventions + +- All HAL source is guarded by `#ifdef __PLAT_LINUX__` (not `ARDUINO_ARCH_*`). The licensing/skeleton header wraps every `.cpp` in this guard. +- Keep HAL changes in `Marlin/src/HAL/LINUX/` and its `hardware/`, `include/`, `inc/`, `u8g/` subfolders. There are no board pin files here — `BOARD_SIMULATED` is the only board and the pin map is the fixed software array. +- The build is host-native: no flash/linker-script constraints, no MCU family macros, no Arduino `Serial` object — `MYSERIAL1` is the `HalSerial`-backed `usb_serial` (`HAL.h`/`HAL.cpp`). +- `simulator_linux_*` envs in `ini/native.ini` are a _different_ target: they build the graphical `NATIVE_SIM` HAL (`src/HAL/NATIVE_SIM`, SDL2/GLM UI), **not** this LINUX HAL. Do not conflate the two — LINUX is the headless console build, NATIVE_SIM is the GUI simulator. +- When you change HAL code, run `linux_native` (and `linux_native_test` if unit tests are affected) and verify green before committing. + +## Related + +- `Marlin/src/HAL/NATIVE_SIM/` — the sibling GUI simulator HAL (SDL2/GLM), built via `simulator_linux_*` envs (distinct from this LINUX HAL). +- Wiki: [[hal-linux]], [[marlin-src-hal]]. +- `Marlin/src/HAL/shared/` — shared HAL APIs (`eeprom_api`, SPI helpers, `Marduino.h`) that this HAL reuses. +- `ini/native.ini` — `[env:linux_native]` / `[env:linux_native_test]` definitions. +- `buildroot/tests/linux_native/config-01.ini` — the `BOARD_SIMULATED` + EEPROM test config. diff --git a/Marlin/src/HAL/LINUX/HAL.cpp b/Marlin/src/HAL/LINUX/HAL.cpp index a90819b6b0c1..8a9028c8cc1d 100644 --- a/Marlin/src/HAL/LINUX/HAL.cpp +++ b/Marlin/src/HAL/LINUX/HAL.cpp @@ -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 } diff --git a/Marlin/src/HAL/LINUX/HAL.h b/Marlin/src/HAL/LINUX/HAL.h index bb5fb73e0502..66e4036fdc03 100644 --- a/Marlin/src/HAL/LINUX/HAL.h +++ b/Marlin/src/HAL/LINUX/HAL.h @@ -124,9 +124,9 @@ class MarlinHAL { static void isr_on() {} static void isr_off() {} - static void delay_ms(const int ms) { _delay_ms(ms); } + static void delay_ms(const int ms) { delay(ms); } - // Tasks, called from idle() + // Tasks, called from marlin.idle() static void idletask() {} // Reset diff --git a/Marlin/src/HAL/LINUX/arduino.cpp b/Marlin/src/HAL/LINUX/arduino.cpp index 075b4ccde2f4..0a09a110910e 100644 --- a/Marlin/src/HAL/LINUX/arduino.cpp +++ b/Marlin/src/HAL/LINUX/arduino.cpp @@ -31,10 +31,8 @@ void cli() { } // Disable void sei() { } // Enable // Time functions -void _delay_ms(const int ms) { delay(ms); } - -uint32_t millis() { - return (uint32_t)Clock::millis(); +unsigned long millis() { + return (unsigned long)Clock::millis(); } // This is required for some Arduino libraries we are using @@ -49,28 +47,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) { diff --git a/Marlin/src/HAL/LINUX/eeprom.cpp b/Marlin/src/HAL/LINUX/eeprom.cpp index 2b9b37e66d2c..f665600d5e22 100644 --- a/Marlin/src/HAL/LINUX/eeprom.cpp +++ b/Marlin/src/HAL/LINUX/eeprom.cpp @@ -45,7 +45,7 @@ bool PersistentStore::access_start() { fseek(eeprom_file, 0L, SEEK_END); std::size_t file_size = ftell(eeprom_file); - if (file_size < MARLIN_EEPROM_SIZE) { + if (file_size < long(MARLIN_EEPROM_SIZE)) { memset(buffer + file_size, eeprom_erase_value, MARLIN_EEPROM_SIZE - file_size); } else { diff --git a/Marlin/src/HAL/LINUX/hardware/Timer.cpp b/Marlin/src/HAL/LINUX/hardware/Timer.cpp index 9f0d6a8f3ae2..013690a404bf 100644 --- a/Marlin/src/HAL/LINUX/hardware/Timer.cpp +++ b/Marlin/src/HAL/LINUX/hardware/Timer.cpp @@ -37,7 +37,10 @@ Timer::Timer() { } Timer::~Timer() { - timer_delete(timerid); + if (timerid != 0) { + timer_delete(timerid); + timerid = 0; + } } void Timer::init(uint32_t sig_id, uint32_t sim_freq, callback_fn* fn) { diff --git a/Marlin/src/HAL/LINUX/include/Arduino.h b/Marlin/src/HAL/LINUX/include/Arduino.h index f05aaed88083..87148a40b26f 100644 --- a/Marlin/src/HAL/LINUX/include/Arduino.h +++ b/Marlin/src/HAL/LINUX/include/Arduino.h @@ -74,11 +74,10 @@ extern "C" { // Time functions extern "C" void delay(const int ms); -void _delay_ms(const int ms); void delayMicroseconds(unsigned long); -uint32_t millis(); +unsigned long millis(); -//IO functions +// IO functions void pinMode(const pin_t, const uint8_t); void digitalWrite(pin_t, uint8_t); bool digitalRead(pin_t); diff --git a/Marlin/src/HAL/LINUX/include/pinmapping.h b/Marlin/src/HAL/LINUX/include/pinmapping.h index cfac5e3b48e4..9147ef82de74 100644 --- a/Marlin/src/HAL/LINUX/include/pinmapping.h +++ b/Marlin/src/HAL/LINUX/include/pinmapping.h @@ -37,29 +37,29 @@ constexpr uint8_t NUM_ANALOG_INPUTS = 16; constexpr uint8_t analog_offset = NUM_DIGITAL_PINS - NUM_ANALOG_INPUTS; // Get the digital pin for an analog index -constexpr pin_t analogInputToDigitalPin(const int8_t p) { - return (WITHIN(p, 0, NUM_ANALOG_INPUTS) ? analog_offset + p : P_NC); +constexpr pin_t analogInputToDigitalPin(const int8_t a) { + return (WITHIN(a, 0, NUM_ANALOG_INPUTS - 1) ? analog_offset + a : P_NC); } // Get the analog index for a digital pin -constexpr int8_t DIGITAL_PIN_TO_ANALOG_PIN(const pin_t p) { - return (WITHIN(p, analog_offset, NUM_DIGITAL_PINS) ? p - analog_offset : P_NC); +constexpr int8_t digitalPinToAnalogIndex(const pin_t pin) { + return (WITHIN(pin, analog_offset, NUM_DIGITAL_PINS - 1) ? pin - analog_offset : P_NC); } // Return the index of a pin number 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 pin) { return WITHIN(pin, 0, NUM_DIGITAL_PINS - 1); } // Test whether the pin is PWM -constexpr bool PWM_PIN(const pin_t p) { return false; } +constexpr bool PWM_PIN(const pin_t) { return false; } // Test whether the pin is interruptible -constexpr bool INTERRUPT_PIN(const pin_t p) { return false; } +constexpr bool INTERRUPT_PIN(const pin_t) { return false; } // Get the pin number at the given index -constexpr pin_t GET_PIN_MAP_PIN(const int16_t ind) { return ind; } +constexpr pin_t GET_PIN_MAP_PIN(const int16_t index) { return pin_t(index); } // Parse a G-code word into a pin index int16_t PARSED_PIN_INDEX(const char code, const int16_t dval); diff --git a/Marlin/src/HAL/LINUX/main.cpp b/Marlin/src/HAL/LINUX/main.cpp index f2af2ff33f52..27a066d619ce 100644 --- a/Marlin/src/HAL/LINUX/main.cpp +++ b/Marlin/src/HAL/LINUX/main.cpp @@ -21,6 +21,7 @@ */ #ifdef __PLAT_LINUX__ +#ifndef UNIT_TEST //#define GPIO_LOGGING // Full GPIO and Positional Logging @@ -135,4 +136,5 @@ int main() { read_serial.join(); } +#endif // UNIT_TEST #endif // __PLAT_LINUX__ diff --git a/Marlin/src/HAL/LINUX/pinsDebug.h b/Marlin/src/HAL/LINUX/pinsDebug.h index e4ee27e8dd01..aacb626105d1 100644 --- a/Marlin/src/HAL/LINUX/pinsDebug.h +++ b/Marlin/src/HAL/LINUX/pinsDebug.h @@ -19,30 +19,50 @@ * along with this program. If not, see . * */ +#pragma once /** - * Support routines for X86_64 - */ - -/** - * Translation of routines & variables used by pinsDebug.h + * Pins Debugging for Linux Native + * + * - NUMBER_PINS_TOTAL + * - MULTI_NAME_PAD + * - getPinByIndex(index) + * - printPinNameByIndex(index) + * - getPinIsDigitalByIndex(index) + * - digitalPinToAnalogIndex(pin) + * - getValidPinMode(pin) + * - isValidPin(pin) + * - isAnalogPin(pin) + * - digitalRead_mod(pin) + * - pwm_status(pin) + * - printPinPWM(pin) + * - printPinPort(pin) + * - printPinNumber(pin) + * - printPinAnalog(pin) */ #define NUMBER_PINS_TOTAL NUM_DIGITAL_PINS -#define IS_ANALOG(P) (DIGITAL_PIN_TO_ANALOG_PIN(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 MULTI_NAME_PAD 16 // space needed to be pretty if not first name assigned to a pin +#define getPinByIndex(x) 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) + // active ADC function/mode/code values for PINSEL registers -constexpr int8_t ADC_pin_mode(pin_t pin) { return -1; } +constexpr int8_t ADC_pin_mode(const pin_t) { return -1; } + +// The pin and index are the same on this platform +bool getPinIsDigitalByIndex(const pin_t pin) { + return (!isAnalogPin(pin) || get_pin_mode(pin) != ADC_pin_mode(pin)); +} + +#define isAnalogPin(P) (digitalPinToAnalogIndex(P) >= 0) -int8_t get_pin_mode(const pin_t pin) { return VALID_PIN(pin) ? 0 : -1; } +#define digitalRead_mod(P) digitalRead(P) -bool GET_PINMODE(const pin_t pin) { +int8_t get_pin_mode(const pin_t pin) { return isValidPin(pin) ? 0 : -1; } + +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 +70,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)); -} - -void pwm_details(const pin_t pin) {} +void printPinPWM(const pin_t) {} bool pwm_status(const pin_t) { return false; } -void print_port(const pin_t) {} +void printPinPort(const pin_t) {} + +#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(P)); SERIAL_ECHO(buffer); }while(0) diff --git a/Marlin/src/HAL/LINUX/spi_pins.h b/Marlin/src/HAL/LINUX/spi_pins.h index 607b96428152..221145e4b3cb 100644 --- a/Marlin/src/HAL/LINUX/spi_pins.h +++ b/Marlin/src/HAL/LINUX/spi_pins.h @@ -28,12 +28,6 @@ // spiBeginTransaction. #endif -// Onboard SD -//#define SD_SCK_PIN P0_07 -//#define SD_MISO_PIN P0_08 -//#define SD_MOSI_PIN P0_09 -//#define SD_SS_PIN P0_06 - // External SD #ifndef SD_SCK_PIN #define SD_SCK_PIN 50 @@ -44,9 +38,3 @@ #ifndef SD_MOSI_PIN #define SD_MOSI_PIN 52 #endif -#ifndef SD_SS_PIN - #define SD_SS_PIN 53 -#endif -#ifndef SDSS - #define SDSS SD_SS_PIN -#endif diff --git a/Marlin/src/HAL/LINUX/timers.h b/Marlin/src/HAL/LINUX/timers.h index 2b29edce0bb4..39c76f02b59f 100644 --- a/Marlin/src/HAL/LINUX/timers.h +++ b/Marlin/src/HAL/LINUX/timers.h @@ -34,7 +34,7 @@ #define FORCE_INLINE __attribute__((always_inline)) inline typedef uint32_t hal_timer_t; -#define HAL_TIMER_TYPE_MAX 0xFFFFFFFF +#define HAL_TIMER_TYPE_MAX hal_timer_t(UINT32_MAX) #define HAL_TIMER_RATE ((SystemCoreClock) / 4) // frequency of timers peripherals @@ -49,21 +49,20 @@ typedef uint32_t hal_timer_t; #endif #define TEMP_TIMER_RATE 1000000 -#define TEMP_TIMER_FREQUENCY 1000 // temperature interrupt frequency +#define TEMP_TIMER_FREQUENCY 1000 // (Hz) Temperature ISR frequency -#define STEPPER_TIMER_RATE HAL_TIMER_RATE // frequency of stepper timer (HAL_TIMER_RATE / STEPPER_TIMER_PRESCALE) -#define STEPPER_TIMER_TICKS_PER_US ((STEPPER_TIMER_RATE) / 1000000) // stepper timer ticks per µs -#define STEPPER_TIMER_PRESCALE (CYCLES_PER_MICROSECOND / STEPPER_TIMER_TICKS_PER_US) +#define STEPPER_TIMER_RATE HAL_TIMER_RATE // (Hz) Frequency of Stepper Timer ISR (HAL_TIMER_RATE / STEPPER_TIMER_PRESCALE) +#define STEPPER_TIMER_TICKS_PER_US ((STEPPER_TIMER_RATE) / 1000000UL) // (MHz) Stepper Timer ticks per µs +#define STEPPER_TIMER_PRESCALE (CYCLES_PER_MICROSECOND / STEPPER_TIMER_TICKS_PER_US) -#define PULSE_TIMER_RATE STEPPER_TIMER_RATE // frequency of pulse timer -#define PULSE_TIMER_PRESCALE STEPPER_TIMER_PRESCALE -#define PULSE_TIMER_TICKS_PER_US STEPPER_TIMER_TICKS_PER_US +#define PULSE_TIMER_RATE STEPPER_TIMER_RATE // (Hz) Frequency of Pulse Timer +#define PULSE_TIMER_PRESCALE STEPPER_TIMER_PRESCALE -#define ENABLE_STEPPER_DRIVER_INTERRUPT() HAL_timer_enable_interrupt(MF_TIMER_STEP) -#define DISABLE_STEPPER_DRIVER_INTERRUPT() HAL_timer_disable_interrupt(MF_TIMER_STEP) -#define STEPPER_ISR_ENABLED() HAL_timer_interrupt_enabled(MF_TIMER_STEP) +#define ENABLE_STEPPER_DRIVER_INTERRUPT() HAL_timer_enable_interrupt(MF_TIMER_STEP) +#define DISABLE_STEPPER_DRIVER_INTERRUPT() HAL_timer_disable_interrupt(MF_TIMER_STEP) +#define STEPPER_ISR_ENABLED() HAL_timer_interrupt_enabled(MF_TIMER_STEP) -#define ENABLE_TEMPERATURE_INTERRUPT() HAL_timer_enable_interrupt(MF_TIMER_TEMP) +#define ENABLE_TEMPERATURE_INTERRUPT() HAL_timer_enable_interrupt(MF_TIMER_TEMP) #define DISABLE_TEMPERATURE_INTERRUPT() HAL_timer_disable_interrupt(MF_TIMER_TEMP) #ifndef HAL_STEP_TIMER_ISR @@ -93,5 +92,5 @@ void HAL_timer_enable_interrupt(const uint8_t timer_num); void HAL_timer_disable_interrupt(const uint8_t timer_num); bool HAL_timer_interrupt_enabled(const uint8_t timer_num); -#define HAL_timer_isr_prologue(T) NOOP -#define HAL_timer_isr_epilogue(T) NOOP +inline void HAL_timer_isr_prologue(const uint8_t) {} +inline void HAL_timer_isr_epilogue(const uint8_t) {} diff --git a/Marlin/src/HAL/LPC1768/AGENTS.md b/Marlin/src/HAL/LPC1768/AGENTS.md new file mode 100644 index 000000000000..0ef5af11b931 --- /dev/null +++ b/Marlin/src/HAL/LPC1768/AGENTS.md @@ -0,0 +1,60 @@ +# AGENTS.md — LPC1768 HAL + +Working notes and conventions for the **LPC1768 HAL** folder (`Marlin/src/HAL/LPC1768/`). Covers the NXP LPC1768/LPC1769 family (ARM Cortex-M3) used on ReARM, Smoothieboard, BTT SKR 1.3/1.4, MKS SBASE, etc. + +## Build & Test Loop + +Validate with `mftest` (it regenerates `Marlin/Configuration.h` per target, which raw `pio run -e` does not do reliably): + +```bash +cd "$(git rev-parse --show-toplevel)" +buildroot/bin/mftest -t LPC1768 -n1 -y # env LPC1768 — ReARM / RAMPS_14_RE_ARM boards +buildroot/bin/mftest -t LPC1769 -n1 -y # env LPC1769 — Smoothieboard, etc. +``` + +Both targets should be green before considering LPC1768 work done. `LPC1768` is the primary/most-used env (both MCUs share 512 KB flash / 64 KB RAM; LPC1769 just clocks higher). Pick the individual test with `-n` (e.g. `-n1` … `-n5`); `LPC1768/config-01.ini` is ReARM EFB + VIKI2 + SD + NeoPixel + dual stepper. + +> Do NOT use `rm -rf .pio` to force a rebuild — the cross-profile write guard blocks it and it is unsafe. Use `pio run -e -t clean` or just let `mftest` rebuild. + +## Framework / Platform Integration (upstream community core, not vendored) + +The LPC176x support is **not** a Marlin-vendored framework. `ini/lpc176x.ini` pulls an upstream **community** Arduino core from GitHub: + +- `platform = https://github.com/p3p/pio-nxplpc-arduino-lpc176x/archive/0.1.3.zip` +- `framework-arduino-lpc176x@https://github.com/p3p/pio-framework-arduino-lpc176x/archive/ab41696b64.zip` +- `toolchain-gccarmnoneeabi@1.100301.220327` + +These land in `~/.platformio/packages/…` as normal PlatformIO packages — there is no copy under `buildroot/share/PlatformIO/`, so the AT32 "mirror to installed package" warning does NOT apply here. Marlin-specific glue lives entirely in this HAL folder plus `Marlin/src/pins/lpc1768/`. Build flags of note: `-std=gnu++20 -Wno-volatile -DU8G_HAL_LINKS -DPLATFORM_M997_SUPPORT`, and `build_src_filter` excludes `src/HAL/LPC1768/tft` by default (selected per-board via `custom_marlin.HAS_SPI_TFT`). + +### Gotchas that are not obvious from the source + +1. **Guard macro is `TARGET_LPC1768`, not `ARDUINO_ARCH_*`.** It is defined by the platform/board JSON, not by the core. Every file in this HAL is wrapped in `#ifdef TARGET_LPC1768`, and `HAL/platforms.h` selects `HAL/LPC1768` on that macro. `LPC1769` builds compile the _same_ folder (the MCU difference is handled inside the core, e.g. `isLPC1769()` in `HAL.cpp`). + +2. **The default `Serial` is USB-CDC, not a UART.** `MarlinSerial.h` defines `USB_SERIAL_PORT(...) USBSerial` where `USBSerial` is a `ForwardSerial1Class` over the USB CDC stack (`usb_serial.cpp`, `cdcuser.h`). Hardware UARTs `MSerial0..3` (UART0–UART3) are defined separately in `MarlinSerial.cpp` behind `USING_HW_SERIALx`. Boards that want UART-as-primary must route a hardware serial to `Serial`. + +3. **`HardwareSerial` interface mismatch → `ForwardSerial1Class` wrapper.** The core's `HardwareSerial` does not implement Arduino's `Serial` overloads, so `MarlinSerial.h` wraps `MarlinSerial` in a `ForwardSerial1Class` (`MSerialT`) and warns in comments. Because of that, `SERIAL_RUNTIME_HOOK` is explicitly unsupported here — `MarlinSerial.h` has `#error "SERIAL_RUNTIME_HOOK is not yet supported for LPC176x."` + +4. **EEPROM backend is per-board, never auto-selected.** Three backends ship in `eeprom/`, each gated by a define the board/pins file must set: `eeprom_wired.cpp` (`USE_WIRED_EEPROM`), `eeprom_sdcard.cpp` (`SDCARD_EEPROM_EMULATION`), `eeprom_flash.cpp` (`FLASH_EEPROM_EMULATION`). Most boards use `SDCARD_EEPROM_EMULATION` (e.g. `pins_BTT_SKR_V1_4.h` selects it over `I2C_EEPROM`). Marlin will not pick one for you — forgetting the define yields no settings storage. + +5. **Flash EEPROM uses IAP on sector 29 with wear-leveling.** `eeprom_flash.cpp` reserves a 32 KB sector and writes 4 KB "slots" in turn, erasing the whole sector only when all slots are used. It holds a `ram_eeprom[]` image and disables IRQs around the IAP calls (`lpc17xx_iap.h` `CopyRAM2Flash`/`EraseSector`). Don't touch the IAP critical sections. + +6. **Hardware-SPI pins can't be bit-banged while the SSP is active.** `HAL_SPI.cpp` documents that SCK/MOSI/MISO cannot be set with `WRITE`/`digitalWrite` once the LPC17xx SSP module is on; if the LCD and SD share those pins you MUST use `SOFTWARE_SPI`. The global `SPI` object is instantiated in `HAL_SPI.cpp` keyed off `SD_MISO_PIN` matching `BOARD_SPI1_MISO_PIN`/`BOARD_SPI2_MISO_PIN`. + +7. **Firmware update (M997) works via USB reconnect + NVIC reset.** `HAL.cpp` `flashFirmware()` does `USB_Connect(false)`, delays, then `hal.reboot()` (`NVIC_SystemReset()`). `PLATFORM_M997_SUPPORT` is forced on in `HAL.h`. Upload itself is mass-storage copy: `upload_extra_script.py` auto-finds the `REARM` volume or a `FIRMWARE.CUR` file and sets `upload_port` — manual copy of `.pio/build//firmware.bin` also works. + +8. **All pins are PWM-capable; ADC is median-filtered.** `fastio.h` sets `PWM_PIN(P) true` and `HAL.h` defines `HAL_CAN_SET_PWM_FREQ`. ADC uses a median filter (`ADC_MEDIAN_FILTER_SIZE 23`) + low-pass (`ADC_LOWPASS_K_VALUE 2`) and is already filtered in-HAL (`HAL_ADC_FILTERED`), so don't re-filter upstream. + +9. **Onboard-SD pins are sensitive.** `HAL.h` declares `HAL_SENSITIVE_PINS P0_06, P0_07, P0_08, P0_09` (the MCU's onboard SD bus) — M42/M43 must not let these be toggled. + +## Conventions + +- All HAL source is guarded by `#ifdef TARGET_LPC1768` (set by the platform, not the core). +- Keep HAL changes in `Marlin/src/HAL/LPC1768/`; board pin changes in `Marlin/src/pins/lpc1768/`. Build-root integration (`ini/lpc176x.ini`, `upload_extra_script.py`) is the only place for build/upload tweaks. +- The core is an upstream p3p package, not a Marlin-vendored one — no `buildroot/share/PlatformIO` mirror step is needed. Fixes to the core itself must be upstreamed to p3p's repos, not patched in-tree. +- Don't "fix" missing `Serial`/EEPROM behavior in `SanityCheck` — solve it at the pin/ backend level in the board's pins file. + +## Related + +- `Marlin/src/pins/lpc1768/` — board pin definitions (ReARM, Smoothieboard, SKR, SBASE…). +- Wiki: [[hal-lpc1768]], [[marlin-src-hal]]. +- `Marlin/src/HAL/shared/` — shared HAL APIs (`eeprom_api`, `HAL_SPI`, `serial_ports`) reused here. diff --git a/Marlin/src/HAL/LPC1768/HAL.cpp b/Marlin/src/HAL/LPC1768/HAL.cpp index db9881cdd4ac..4b0e255f37ca 100644 --- a/Marlin/src/HAL/LPC1768/HAL.cpp +++ b/Marlin/src/HAL/LPC1768/HAL.cpp @@ -35,8 +35,6 @@ #include #include -DefaultSerial1 USBSerial(false, UsbSerial); - uint32_t MarlinHAL::adc_result = 0; pin_t MarlinHAL::adc_pin = 0; @@ -175,13 +173,8 @@ void MarlinHAL::init() { // HAL idle task void MarlinHAL::idletask() { #if HAS_SHARED_MEDIA - // If Marlin is using the SD card we need to lock it to prevent access from - // a PC via USB. - // Other HALs use IS_SD_PRINTING() and IS_SD_FILE_OPEN() to check for access but - // this will not reliably detect delete operations. To be safe we will lock - // the disk if Marlin has it mounted. Unfortunately there is currently no way - // to unmount the disk from the LCD menu. - // if (IS_SD_PRINTING() || IS_SD_FILE_OPEN()) + // When Marlin is using the SD Card it must be locked to prevent PC access via USB. + // For maximum safety we lock the disk if Marlin has it mounted for any reason. if (card.isMounted()) MSC_Aquire_Lock(); else diff --git a/Marlin/src/HAL/LPC1768/HAL.h b/Marlin/src/HAL/LPC1768/HAL.h index ab28e06eb89e..052d6637c818 100644 --- a/Marlin/src/HAL/LPC1768/HAL.h +++ b/Marlin/src/HAL/LPC1768/HAL.h @@ -38,72 +38,15 @@ extern "C" volatile uint32_t _millis; #include "../shared/math_32bit.h" #include "../shared/HAL_SPI.h" #include "fastio.h" -#include "MarlinSerial.h" #include #include -#include - -// ------------------------ -// Serial ports -// ------------------------ - -typedef ForwardSerial1Class< decltype(UsbSerial) > DefaultSerial1; -extern DefaultSerial1 USBSerial; -#define _MSERIAL(X) MSerial##X -#define MSERIAL(X) _MSERIAL(X) - -#if SERIAL_PORT == -1 - #define MYSERIAL1 USBSerial -#elif WITHIN(SERIAL_PORT, 0, 3) - #define MYSERIAL1 MSERIAL(SERIAL_PORT) -#else - #error "SERIAL_PORT must be from 0 to 3. You can also use -1 if the board supports Native USB." -#endif - -#ifdef SERIAL_PORT_2 - #if SERIAL_PORT_2 == -1 - #define MYSERIAL2 USBSerial - #elif WITHIN(SERIAL_PORT_2, 0, 3) - #define MYSERIAL2 MSERIAL(SERIAL_PORT_2) - #else - #error "SERIAL_PORT_2 must be from 0 to 3. You can also use -1 if the board supports Native USB." - #endif -#endif - -#ifdef SERIAL_PORT_3 - #if SERIAL_PORT_3 == -1 - #define MYSERIAL3 USBSerial - #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 USBSerial - #elif WITHIN(MMU2_SERIAL_PORT, 0, 3) - #define MMU2_SERIAL MSERIAL(MMU2_SERIAL_PORT) - #else - #error "MMU2_SERIAL_PORT must be from 0 to 3. You can also use -1 if the board supports Native USB." - #endif -#endif +// +// Serial Ports +// -#ifdef LCD_SERIAL_PORT - #if LCD_SERIAL_PORT == -1 - #define LCD_SERIAL USBSerial - #elif WITHIN(LCD_SERIAL_PORT, 0, 3) - #define LCD_SERIAL MSERIAL(LCD_SERIAL_PORT) - #else - #error "LCD_SERIAL_PORT must be from 0 to 3. You can also use -1 if the board supports Native USB." - #endif - #if HAS_DGUS_LCD - #define LCD_SERIAL_TX_BUFFER_FREE() LCD_SERIAL.available() - #endif -#endif +#include "MarlinSerial.h" // // Interrupts @@ -137,12 +80,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; } @@ -159,7 +102,7 @@ constexpr pin_t GET_PIN_MAP_PIN(const int16_t index) { // Parse a G-code word into a pin index int16_t PARSED_PIN_INDEX(const char code, const int16_t dval); // P0.6 thru P0.9 are for the onboard SD card -#define HAL_SENSITIVE_PINS P0_06, P0_07, P0_08, P0_09, +#define HAL_SENSITIVE_PINS P0_06, P0_07, P0_08, P0_09 // ------------------------ // Defines @@ -217,7 +160,7 @@ class MarlinHAL { static bool watchdog_timed_out() IF_DISABLED(USE_WATCHDOG, { return false; }); static void watchdog_clear_timeout_flag() IF_DISABLED(USE_WATCHDOG, {}); - // Tasks, called from idle() + // Tasks, called from marlin.idle() static void idletask(); // Reset diff --git a/Marlin/src/HAL/LPC1768/HAL_SPI.cpp b/Marlin/src/HAL/LPC1768/HAL_SPI.cpp index 6ce7f755527f..15ace81dc254 100644 --- a/Marlin/src/HAL/LPC1768/HAL_SPI.cpp +++ b/Marlin/src/HAL/LPC1768/HAL_SPI.cpp @@ -40,10 +40,6 @@ * SPI sharing pins. The SCK, MOSI & MISO pins can NOT be set/cleared with * WRITE nor digitalWrite when the hardware SPI module within the LPC17xx is * active. If any of these pins are shared then the software SPI must be used. - * - * A more sophisticated hardware SPI can be found at the following link. - * This implementation has not been fully debugged. - * https://github.com/MarlinFirmware/Marlin/tree/071c7a78f27078fd4aee9a3ef365fcf5e143531e */ #ifdef TARGET_LPC1768 diff --git a/Marlin/src/HAL/LPC1768/MarlinSerial.cpp b/Marlin/src/HAL/LPC1768/MarlinSerial.cpp index f2aecf54a050..291eba68a700 100644 --- a/Marlin/src/HAL/LPC1768/MarlinSerial.cpp +++ b/Marlin/src/HAL/LPC1768/MarlinSerial.cpp @@ -25,6 +25,8 @@ #include "../../inc/MarlinConfig.h" +DefaultSerial1 USBSerial(false, UsbSerial); + #if USING_HW_SERIAL0 MarlinSerial _MSerial0(LPC_UART0); MSerialT MSerial0(true, _MSerial0); diff --git a/Marlin/src/HAL/LPC1768/MarlinSerial.h b/Marlin/src/HAL/LPC1768/MarlinSerial.h index 2fadd8209bdf..04110e4aff23 100644 --- a/Marlin/src/HAL/LPC1768/MarlinSerial.h +++ b/Marlin/src/HAL/LPC1768/MarlinSerial.h @@ -21,6 +21,7 @@ */ #pragma once +#include #include #include @@ -30,6 +31,18 @@ #endif #include "../../core/serial_hook.h" +typedef ForwardSerial1Class< decltype(UsbSerial) > DefaultSerial1; +extern DefaultSerial1 USBSerial; + +#define SERIAL_INDEX_MIN 0 +#define SERIAL_INDEX_MAX 3 +#define USB_SERIAL_PORT(...) USBSerial +#include "../shared/serial_ports.h" + +#if defined(LCD_SERIAL_PORT) && ANY(HAS_DGUS_LCD, EXTENSIBLE_UI) + #define LCD_SERIAL_TX_BUFFER_FREE() LCD_SERIAL.available() +#endif + class MarlinSerial : public HardwareSerial { public: MarlinSerial(LPC_UART_TypeDef *UARTx) : HardwareSerial(UARTx) { } diff --git a/Marlin/src/HAL/LPC1768/Servo.h b/Marlin/src/HAL/LPC1768/Servo.h index 221001c9483c..d7f0a2a5553c 100644 --- a/Marlin/src/HAL/LPC1768/Servo.h +++ b/Marlin/src/HAL/LPC1768/Servo.h @@ -49,6 +49,8 @@ #include +#include "../../MarlinCore.h" + class libServo: public Servo { public: void move(const int value) { diff --git a/Marlin/src/HAL/LPC1768/eeprom_flash.cpp b/Marlin/src/HAL/LPC1768/eeprom/eeprom_flash.cpp similarity index 94% rename from Marlin/src/HAL/LPC1768/eeprom_flash.cpp rename to Marlin/src/HAL/LPC1768/eeprom/eeprom_flash.cpp index 9f873d5774a3..e24b0fdbc01d 100644 --- a/Marlin/src/HAL/LPC1768/eeprom_flash.cpp +++ b/Marlin/src/HAL/LPC1768/eeprom/eeprom_flash.cpp @@ -36,11 +36,11 @@ * 16Kb I/O buffers (intended to hold DMA USB and Ethernet data, but currently * unused). */ -#include "../../inc/MarlinConfig.h" +#include "../../../inc/MarlinConfig.h" #if ENABLED(FLASH_EEPROM_EMULATION) -#include "../shared/eeprom_api.h" +#include "../../shared/eeprom_api.h" extern "C" { #include @@ -74,7 +74,7 @@ bool PersistentStore::access_start() { if (status == CMD_SUCCESS) { // sector is blank so nothing stored yet - for (int i = 0; i < MARLIN_EEPROM_SIZE; i++) ram_eeprom[i] = EEPROM_ERASE; + for (int i = 0; i < long(MARLIN_EEPROM_SIZE); i++) ram_eeprom[i] = EEPROM_ERASE; current_slot = EEPROM_SLOTS; } else { @@ -82,7 +82,7 @@ bool PersistentStore::access_start() { current_slot = first_nblank_loc / (MARLIN_EEPROM_SIZE); uint8_t *eeprom_data = SLOT_ADDRESS(EEPROM_SECTOR, current_slot); // load current settings - for (int i = 0; i < MARLIN_EEPROM_SIZE; i++) ram_eeprom[i] = eeprom_data[i]; + for (int i = 0; i < long(MARLIN_EEPROM_SIZE); i++) ram_eeprom[i] = eeprom_data[i]; } eeprom_dirty = false; diff --git a/Marlin/src/HAL/LPC1768/eeprom_sdcard.cpp b/Marlin/src/HAL/LPC1768/eeprom/eeprom_sdcard.cpp similarity index 98% rename from Marlin/src/HAL/LPC1768/eeprom_sdcard.cpp rename to Marlin/src/HAL/LPC1768/eeprom/eeprom_sdcard.cpp index 30ecb01a0915..5bf2d353b6c6 100644 --- a/Marlin/src/HAL/LPC1768/eeprom_sdcard.cpp +++ b/Marlin/src/HAL/LPC1768/eeprom/eeprom_sdcard.cpp @@ -26,13 +26,13 @@ #ifdef TARGET_LPC1768 -#include "../../inc/MarlinConfig.h" +#include "../../../inc/MarlinConfig.h" #if ENABLED(SDCARD_EEPROM_EMULATION) //#define DEBUG_SD_EEPROM_EMULATION -#include "../shared/eeprom_api.h" +#include "../../shared/eeprom_api.h" #include #include @@ -52,7 +52,6 @@ bool eeprom_file_open = false; size_t PersistentStore::capacity() { return MARLIN_EEPROM_SIZE - eeprom_exclude_size; } bool PersistentStore::access_start() { - const char eeprom_erase_value = 0xFF; MSC_Aquire_Lock(); if (f_mount(&fat_fs, "", 1)) { MSC_Release_Lock(); @@ -65,6 +64,7 @@ bool PersistentStore::access_start() { UINT bytes_written; FSIZE_t file_size = f_size(&eeprom_file); f_lseek(&eeprom_file, file_size); + const char eeprom_erase_value = 0xFF; while (file_size < capacity() && res == FR_OK) { res = f_write(&eeprom_file, &eeprom_erase_value, 1, &bytes_written); file_size++; diff --git a/Marlin/src/HAL/LPC1768/eeprom_wired.cpp b/Marlin/src/HAL/LPC1768/eeprom/eeprom_wired.cpp similarity index 95% rename from Marlin/src/HAL/LPC1768/eeprom_wired.cpp rename to Marlin/src/HAL/LPC1768/eeprom/eeprom_wired.cpp index 3230e29afcd1..6a5d90bd02ff 100644 --- a/Marlin/src/HAL/LPC1768/eeprom_wired.cpp +++ b/Marlin/src/HAL/LPC1768/eeprom/eeprom_wired.cpp @@ -21,7 +21,7 @@ */ #ifdef TARGET_LPC1768 -#include "../../inc/MarlinConfig.h" +#include "../../../inc/MarlinConfig.h" #if USE_WIRED_EEPROM @@ -30,8 +30,8 @@ * with implementations supplied by the framework. */ -#include "../shared/eeprom_if.h" -#include "../shared/eeprom_api.h" +#include "../../shared/eeprom_if.h" +#include "../../shared/eeprom_api.h" #ifndef MARLIN_EEPROM_SIZE #define MARLIN_EEPROM_SIZE 0x8000 // 32K diff --git a/Marlin/src/HAL/LPC1768/endstop_interrupts.h b/Marlin/src/HAL/LPC1768/endstop_interrupts.h index 7a67b958b441..2c75fe6986cc 100644 --- a/Marlin/src/HAL/LPC1768/endstop_interrupts.h +++ b/Marlin/src/HAL/LPC1768/endstop_interrupts.h @@ -146,6 +146,12 @@ void setup_endstop_interrupts() { #endif _ATTACH(Z_MIN_PROBE_PIN); #endif + #if USE_CALIBRATION + #if !LPC1768_PIN_INTERRUPT_M(CALIBRATION_PIN) + #error "CALIBRATION_PIN is not INTERRUPT-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #endif + _ATTACH(CALIBRATION_PIN); + #endif #if USE_I_MAX #if !LPC1768_PIN_INTERRUPT_M(I_MAX_PIN) #error "I_MAX_PIN is not INTERRUPT-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." diff --git a/Marlin/src/HAL/LPC1768/fast_pwm.cpp b/Marlin/src/HAL/LPC1768/fast_pwm.cpp index 6d2b1a9002c1..7cb212f5f914 100644 --- a/Marlin/src/HAL/LPC1768/fast_pwm.cpp +++ b/Marlin/src/HAL/LPC1768/fast_pwm.cpp @@ -26,8 +26,10 @@ void MarlinHAL::set_pwm_duty(const pin_t pin, const uint16_t v, const uint16_t v_size/*=255*/, const bool invert/*=false*/) { if (!LPC176x::pin_is_valid(pin)) return; - if (LPC176x::pwm_attach_pin(pin)) - LPC176x::pwm_write_ratio(pin, invert ? 1.0f - (float)v / v_size : (float)v / v_size); // map 1-254 onto PWM range + if (LPC176x::pwm_attach_pin(pin)) { + const uint32_t duty = map(invert ? v_size - v : v, 0, v_size, 0, LPC176x::pwm_get_period(pin)); + LPC176x::pwm_write(pin, duty); + } } void MarlinHAL::set_pwm_frequency(const pin_t pin, const uint16_t f_desired) { diff --git a/Marlin/src/HAL/LPC1768/fastio.h b/Marlin/src/HAL/LPC1768/fastio.h index c553ffb1820d..48583340809e 100644 --- a/Marlin/src/HAL/LPC1768/fastio.h +++ b/Marlin/src/HAL/LPC1768/fastio.h @@ -66,7 +66,7 @@ #define _WRITE(IO,V) WRITE_PIN(IO,V) /// toggle a pin -#define _TOGGLE(IO) _WRITE(IO, !READ(IO)) +#define _TOGGLE(IO) LPC176x::gpio_toggle(IO) /// set pin as input #define _SET_INPUT(IO) SET_DIR_INPUT(IO) diff --git a/Marlin/src/HAL/LPC1768/inc/Conditionals_post.h b/Marlin/src/HAL/LPC1768/inc/Conditionals_post.h index 0b03cb2aea63..a1b4dd509971 100644 --- a/Marlin/src/HAL/LPC1768/inc/Conditionals_post.h +++ b/Marlin/src/HAL/LPC1768/inc/Conditionals_post.h @@ -29,6 +29,6 @@ // LPC1768 boards seem to lose steps when saving to EEPROM during print (issue #20785) // TODO: Which other boards are incompatible? -#if defined(MCU_LPC1768) && ENABLED(FLASH_EEPROM_EMULATION) && PRINTCOUNTER_SAVE_INTERVAL > 0 +#if ALL(MCU_LPC1768, FLASH_EEPROM_EMULATION) && PRINTCOUNTER_SAVE_INTERVAL > 0 #define PRINTCOUNTER_SYNC #endif diff --git a/Marlin/src/HAL/LPC1768/inc/SanityCheck.h b/Marlin/src/HAL/LPC1768/inc/SanityCheck.h index 2782f225b0df..a75382d8bc9c 100644 --- a/Marlin/src/HAL/LPC1768/inc/SanityCheck.h +++ b/Marlin/src/HAL/LPC1768/inc/SanityCheck.h @@ -28,6 +28,13 @@ #error "framework-arduino-lpc176x package is out of date, Please update the PlatformIO platforms, frameworks and libraries." #endif +/** + * Require pins to be defined as macros with numerical values + */ +#ifndef P1_01 + #error "Your ARM platform pins are not defined as macros, only as enums! Provide pins_arduino.h to define the pins." +#endif + /** * Detect an old pins file by checking for old ADC pins values. */ @@ -121,7 +128,7 @@ static_assert(DISABLED(BAUD_RATE_GCODE), "BAUD_RATE_GCODE is not yet supported o #if IS_TX1(BTN_EN2) || IS_RX1(BTN_EN1) #error "Serial port pins (1) conflict with Encoder Buttons!" #elif ANY_TX(1, SD_SCK_PIN, LCD_PINS_D4, DOGLCD_SCK, LCD_RESET_PIN, LCD_PINS_RS, SHIFT_CLK_PIN) \ - || ANY_RX(1, LCD_SDSS, LCD_PINS_RS, SD_MISO_PIN, DOGLCD_A0, SD_SS_PIN, LCD_SDSS, DOGLCD_CS, LCD_RESET_PIN, LCD_BACKLIGHT_PIN) + || ANY_RX(1, LCD_SDSS_PIN, LCD_PINS_RS, SD_MISO_PIN, DOGLCD_A0, SD_SS_PIN, DOGLCD_CS, LCD_RESET_PIN, LCD_BACKLIGHT_PIN) #error "Serial port pins (1) conflict with LCD pins!" #endif #endif @@ -211,8 +218,8 @@ static_assert(DISABLED(BAUD_RATE_GCODE), "BAUD_RATE_GCODE is not yet supported o #error "SCL0 overlaps with Encoder Button!" #elif IS_SCL0(SD_SS_PIN) #error "SCL0 overlaps with SD_SS_PIN!" - #elif IS_SCL0(LCD_SDSS) - #error "SCL0 overlaps with LCD_SDSS!" + #elif IS_SCL0(LCD_SDSS_PIN) + #error "SCL0 overlaps with LCD_SDSS_PIN!" #endif #undef PIN_IS_SDA0 #undef IS_SCL0 diff --git a/Marlin/src/HAL/LPC1768/include/digipot_mcp4451_I2C_routines.c b/Marlin/src/HAL/LPC1768/include/digipot_mcp4451_I2C_routines.c index c489c16e5ef6..281c920b12c6 100644 --- a/Marlin/src/HAL/LPC1768/include/digipot_mcp4451_I2C_routines.c +++ b/Marlin/src/HAL/LPC1768/include/digipot_mcp4451_I2C_routines.c @@ -22,7 +22,7 @@ /** * digipot_mcp4451_I2C_routines.c - * Adapted from https://www-users.cs.york.ac.uk/~pcc/MCP/HAPR-Course-web/CMSIS/examples/html/master_8c_source.html + * Adapted from https://www-users.york.ac.uk/~pcc1/MCP/HAPR-Course-web/CMSIS/examples/html/master_8c_source.html */ #ifdef TARGET_LPC1768 diff --git a/Marlin/src/HAL/LPC1768/include/digipot_mcp4451_I2C_routines.h b/Marlin/src/HAL/LPC1768/include/digipot_mcp4451_I2C_routines.h index 9b6c62b052a3..4da863389aec 100644 --- a/Marlin/src/HAL/LPC1768/include/digipot_mcp4451_I2C_routines.h +++ b/Marlin/src/HAL/LPC1768/include/digipot_mcp4451_I2C_routines.h @@ -23,7 +23,7 @@ /** * digipot_mcp4451_I2C_routines.h - * Adapted from https://www-users.cs.york.ac.uk/~pcc/MCP/HAPR-Course-web/CMSIS/examples/html/master_8c_source.html + * Adapted from https://www-users.york.ac.uk/~pcc1/MCP/HAPR-Course-web/CMSIS/examples/html/master_8c_source.html */ #ifdef __cplusplus diff --git a/Marlin/src/HAL/LPC1768/pinsDebug.h b/Marlin/src/HAL/LPC1768/pinsDebug.h index 975511be9a94..5baeadd0dd72 100644 --- a/Marlin/src/HAL/LPC1768/pinsDebug.h +++ b/Marlin/src/HAL/LPC1768/pinsDebug.h @@ -19,22 +19,35 @@ * along with this program. If not, see . * */ +#pragma once /** - * Support routines for LPC1768 - */ - -/** - * Translation of routines & variables used by pinsDebug.h + * Pins Debugging for LPC1768/9 + * + * - NUMBER_PINS_TOTAL + * - MULTI_NAME_PAD + * - getPinByIndex(index) + * - printPinNameByIndex(index) + * - getPinIsDigitalByIndex(index) + * - digitalPinToAnalogIndex(pin) + * - getValidPinMode(pin) + * - isValidPin(pin) + * - isAnalogPin(pin) + * - digitalRead_mod(pin) + * - pwm_status(pin) + * - printPinPWM(pin) + * - printPinPort(pin) + * - printPinNumber(pin) + * - printPinAnalog(pin) */ #define NUMBER_PINS_TOTAL NUM_DIGITAL_PINS -#define IS_ANALOG(P) (DIGITAL_PIN_TO_ANALOG_PIN(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 isAnalogPin(P) (digitalPinToAnalogIndex(P) >= 0) +#define digitalRead_mod(P) extDigitalRead(P) +#define getPinByIndex(x) 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 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(P)); 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 +55,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; } diff --git a/Marlin/src/HAL/LPC1768/spi_pins.h b/Marlin/src/HAL/LPC1768/spi_pins.h index 295d6462fd91..036bf0e02383 100644 --- a/Marlin/src/HAL/LPC1768/spi_pins.h +++ b/Marlin/src/HAL/LPC1768/spi_pins.h @@ -28,12 +28,12 @@ // spiBeginTransaction. #endif -/** onboard SD card */ -//#define SD_SCK_PIN P0_07 -//#define SD_MISO_PIN P0_08 -//#define SD_MOSI_PIN P0_09 -//#define SD_SS_PIN P0_06 -/** external */ +// Onboard SD +//#define SD_SCK_PIN P0_07 +//#define SD_MISO_PIN P0_08 +//#define SD_MOSI_PIN P0_09 + +// External SD #ifndef SD_SCK_PIN #define SD_SCK_PIN P0_15 #endif @@ -43,10 +43,3 @@ #ifndef SD_MOSI_PIN #define SD_MOSI_PIN P0_18 #endif -#ifndef SD_SS_PIN - #define SD_SS_PIN P1_23 -#endif -#if !defined(SDSS) || SDSS == P_NC // gets defaulted in pins.h - #undef SDSS - #define SDSS SD_SS_PIN -#endif diff --git a/Marlin/src/HAL/LPC1768/timers.h b/Marlin/src/HAL/LPC1768/timers.h index bae01703ed85..66fa7b1ebb4a 100644 --- a/Marlin/src/HAL/LPC1768/timers.h +++ b/Marlin/src/HAL/LPC1768/timers.h @@ -32,11 +32,14 @@ #define SBIT_TIMER0 1 #define SBIT_TIMER1 2 -#define SBIT_CNTEN 0 - -#define SBIT_MR0I 0 // Timer 0 Interrupt when TC matches MR0 -#define SBIT_MR0R 1 // Timer 0 Reset TC on Match -#define SBIT_MR0S 2 // Timer 0 Stop TC and PC on Match +// TCR (Timer Control Register) bits +#define SBIT_CNTEN 0 // Counter Enable +#define SBIT_CRST 1 // Counter Reset (hold TC and PC at 0 while set) + +// MCR (Match Control Register) bits — also doubles as IR (Interrupt Register) bit positions +#define SBIT_MR0I 0 // Interrupt when TC matches MR0 (IR bit 0 = MR0 interrupt flag) +#define SBIT_MR0R 1 // Reset TC on Match +#define SBIT_MR0S 2 // Stop TC and PC on Match #define SBIT_MR1I 3 #define SBIT_MR1R 4 #define SBIT_MR1S 5 @@ -57,9 +60,9 @@ #define _HAL_TIMER_ISR(T) __HAL_TIMER_ISR(T) typedef uint32_t hal_timer_t; -#define HAL_TIMER_TYPE_MAX 0xFFFFFFFF +#define HAL_TIMER_TYPE_MAX hal_timer_t(UINT32_MAX) -#define HAL_TIMER_RATE ((F_CPU) / 4) // frequency of timers peripherals +#define HAL_TIMER_RATE ((F_CPU) / 4) // (Hz) Frequency of timers peripherals #ifndef MF_TIMER_STEP #define MF_TIMER_STEP 0 // Timer Index for Stepper @@ -74,22 +77,21 @@ typedef uint32_t hal_timer_t; #define MF_TIMER_PWM 3 // Timer Index for PWM #endif -#define TEMP_TIMER_RATE 1000000 -#define TEMP_TIMER_FREQUENCY 1000 // temperature interrupt frequency +#define TEMP_TIMER_RATE 1000000 // 1MHz +#define TEMP_TIMER_FREQUENCY 1000 // (Hz) Temperature ISR frequency -#define STEPPER_TIMER_RATE HAL_TIMER_RATE // frequency of stepper timer (HAL_TIMER_RATE / STEPPER_TIMER_PRESCALE) -#define STEPPER_TIMER_TICKS_PER_US ((STEPPER_TIMER_RATE) / 1000000) // stepper timer ticks per µs -#define STEPPER_TIMER_PRESCALE (CYCLES_PER_MICROSECOND / STEPPER_TIMER_TICKS_PER_US) +#define STEPPER_TIMER_RATE HAL_TIMER_RATE // (Hz) Frequency of stepper timer (HAL_TIMER_RATE / STEPPER_TIMER_PRESCALE) +#define STEPPER_TIMER_TICKS_PER_US ((STEPPER_TIMER_RATE) / 1000000UL) // (MHz) Stepper Timer ticks per µs +#define STEPPER_TIMER_PRESCALE (CYCLES_PER_MICROSECOND / STEPPER_TIMER_TICKS_PER_US) -#define PULSE_TIMER_RATE STEPPER_TIMER_RATE // frequency of pulse timer -#define PULSE_TIMER_PRESCALE STEPPER_TIMER_PRESCALE -#define PULSE_TIMER_TICKS_PER_US STEPPER_TIMER_TICKS_PER_US +#define PULSE_TIMER_RATE STEPPER_TIMER_RATE // (Hz) Frequency of Pulse Timer +#define PULSE_TIMER_PRESCALE STEPPER_TIMER_PRESCALE -#define ENABLE_STEPPER_DRIVER_INTERRUPT() HAL_timer_enable_interrupt(MF_TIMER_STEP) -#define DISABLE_STEPPER_DRIVER_INTERRUPT() HAL_timer_disable_interrupt(MF_TIMER_STEP) -#define STEPPER_ISR_ENABLED() HAL_timer_interrupt_enabled(MF_TIMER_STEP) +#define ENABLE_STEPPER_DRIVER_INTERRUPT() HAL_timer_enable_interrupt(MF_TIMER_STEP) +#define DISABLE_STEPPER_DRIVER_INTERRUPT() HAL_timer_disable_interrupt(MF_TIMER_STEP) +#define STEPPER_ISR_ENABLED() HAL_timer_interrupt_enabled(MF_TIMER_STEP) -#define ENABLE_TEMPERATURE_INTERRUPT() HAL_timer_enable_interrupt(MF_TIMER_TEMP) +#define ENABLE_TEMPERATURE_INTERRUPT() HAL_timer_enable_interrupt(MF_TIMER_TEMP) #define DISABLE_TEMPERATURE_INTERRUPT() HAL_timer_disable_interrupt(MF_TIMER_TEMP) #ifndef HAL_STEP_TIMER_ISR @@ -111,8 +113,25 @@ void HAL_timer_start(const uint8_t timer_num, const uint32_t frequency); FORCE_INLINE static void HAL_timer_set_compare(const uint8_t timer_num, const hal_timer_t compare) { switch (timer_num) { - case MF_TIMER_STEP: STEP_TIMER_PTR->MR0 = compare; break; // Stepper Timer Match Register 0 - case MF_TIMER_TEMP: TEMP_TIMER_PTR->MR0 = compare; break; // Temp Timer Match Register 0 + case MF_TIMER_STEP: { + STEP_TIMER_PTR->MR0 = compare; + // If the counter has already passed the compare value, reset it now to avoid + // waiting for TC to wrap around (~171s at 25MHz) before the next interrupt. + // Read TC once to minimize the window between the comparison and the reset. + const hal_timer_t tc = STEP_TIMER_PTR->TC; + if (tc >= compare) { + SBI(STEP_TIMER_PTR->TCR, SBIT_CRST); // Assert counter reset (TC held at 0) + CBI(STEP_TIMER_PTR->TCR, SBIT_CRST); // Release reset; TC starts counting from 0 + } + } break; + case MF_TIMER_TEMP: { + TEMP_TIMER_PTR->MR0 = compare; + const hal_timer_t tc = TEMP_TIMER_PTR->TC; + if (tc >= compare) { + SBI(TEMP_TIMER_PTR->TCR, SBIT_CRST); + CBI(TEMP_TIMER_PTR->TCR, SBIT_CRST); + } + } break; } } @@ -166,9 +185,10 @@ FORCE_INLINE static bool HAL_timer_interrupt_enabled(const uint8_t timer_num) { FORCE_INLINE static void HAL_timer_isr_prologue(const uint8_t timer_num) { switch (timer_num) { - case MF_TIMER_STEP: SBI(STEP_TIMER_PTR->IR, SBIT_CNTEN); break; - case MF_TIMER_TEMP: SBI(TEMP_TIMER_PTR->IR, SBIT_CNTEN); break; + // Clear the match interrupt flag (IR bit 0 = MR0 interrupt) by writing 1 to it + case MF_TIMER_STEP: SBI(STEP_TIMER_PTR->IR, SBIT_MR0I); break; + case MF_TIMER_TEMP: SBI(TEMP_TIMER_PTR->IR, SBIT_MR0I); break; } } -#define HAL_timer_isr_epilogue(T) NOOP +inline void HAL_timer_isr_epilogue(const uint8_t) {} diff --git a/Marlin/src/HAL/LPC1768/u8g/LCD_I2C_routines.cpp b/Marlin/src/HAL/LPC1768/u8g/LCD_I2C_routines.cpp index e714c3c16da1..3fc973e9f548 100644 --- a/Marlin/src/HAL/LPC1768/u8g/LCD_I2C_routines.cpp +++ b/Marlin/src/HAL/LPC1768/u8g/LCD_I2C_routines.cpp @@ -21,14 +21,14 @@ */ // adapted from I2C/master/master.c example -// https://www-users.cs.york.ac.uk/~pcc/MCP/HAPR-Course-web/CMSIS/examples/html/master_8c_source.html +// https://www-users.york.ac.uk/~pcc1/MCP/HAPR-Course-web/CMSIS/examples/html/master_8c_source.html #ifdef TARGET_LPC1768 #include "../include/i2c_util.h" #include "../../../core/millis_t.h" -extern int millis(); +uint32_t millis(); #ifdef __cplusplus extern "C" { diff --git a/Marlin/src/HAL/LPC1768/u8g/LCD_pin_routines.c b/Marlin/src/HAL/LPC1768/u8g/LCD_pin_routines.c index 466fc8020335..51ad7e095b45 100644 --- a/Marlin/src/HAL/LPC1768/u8g/LCD_pin_routines.c +++ b/Marlin/src/HAL/LPC1768/u8g/LCD_pin_routines.c @@ -27,7 +27,7 @@ * * Couldn't just call exact copies because the overhead killed the LCD update speed * With an intermediate level the softspi was running in the 10-20kHz range which - * resulted in using about about 25% of the CPU's time. + * resulted in using about 25% of the CPU's time. */ #ifdef TARGET_LPC1768 diff --git a/Marlin/src/HAL/LPC1768/u8g/LCD_pin_routines.h b/Marlin/src/HAL/LPC1768/u8g/LCD_pin_routines.h index d60d93daddbd..45e0610fb11e 100644 --- a/Marlin/src/HAL/LPC1768/u8g/LCD_pin_routines.h +++ b/Marlin/src/HAL/LPC1768/u8g/LCD_pin_routines.h @@ -28,7 +28,7 @@ * * Couldn't just call exact copies because the overhead killed the LCD update speed * With an intermediate level the softspi was running in the 10-20kHz range which - * resulted in using about about 25% of the CPU's time. + * resulted in using about 25% of the CPU's time. */ void u8g_SetPinOutput(uint8_t internal_pin_number); diff --git a/Marlin/src/HAL/LPC1768/u8g/u8g_com_HAL_LPC1768_sw_spi.cpp b/Marlin/src/HAL/LPC1768/u8g/u8g_com_HAL_LPC1768_sw_spi.cpp index 4edd0d38861e..77e5870de7f7 100644 --- a/Marlin/src/HAL/LPC1768/u8g/u8g_com_HAL_LPC1768_sw_spi.cpp +++ b/Marlin/src/HAL/LPC1768/u8g/u8g_com_HAL_LPC1768_sw_spi.cpp @@ -132,7 +132,7 @@ uint8_t swSpiTransfer_mode_3(uint8_t b, const uint8_t spi_speed, const pin_t sck static uint8_t SPI_speed = 0; static void u8g_sw_spi_shift_out(uint8_t dataPin, uint8_t clockPin, uint8_t val) { - #if ANY(FYSETC_MINI_12864, MKS_MINI_12864) + #if U8G_SPI_USE_MODE_3 swSpiTransfer_mode_3(val, SPI_speed, clockPin, -1, dataPin); #else swSpiTransfer_mode_0(val, SPI_speed, clockPin, -1, dataPin); @@ -160,15 +160,15 @@ uint8_t u8g_com_HAL_LPC1768_sw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, break; case U8G_COM_MSG_CHIP_SELECT: - #if ANY(FYSETC_MINI_12864, MKS_MINI_12864) // LCD SPI is running mode 3 while SD card is running mode 0 - if (arg_val) { // SCK idle state needs to be set to the proper idle state before - // the next chip select goes active - u8g_SetPILevel(u8g, U8G_PI_SCK, 1); // Set SCK to mode 3 idle state before CS goes active + #if U8G_SPI_USE_MODE_3 // LCD SPI is running mode 3 while SD card is running mode 0 + if (arg_val) { // SCK idle state needs to be set to the proper idle state before + // the next chip select goes active + u8g_SetPILevel(u8g, U8G_PI_SCK, 1); // Set SCK to mode 3 idle state before CS goes active u8g_SetPILevel(u8g, U8G_PI_CS, LOW); } else { u8g_SetPILevel(u8g, U8G_PI_CS, HIGH); - u8g_SetPILevel(u8g, U8G_PI_SCK, 0); // Set SCK to mode 0 idle state after CS goes inactive + u8g_SetPILevel(u8g, U8G_PI_SCK, 0); // Set SCK to mode 0 idle state after CS goes inactive } #else u8g_SetPILevel(u8g, U8G_PI_CS, !arg_val); diff --git a/Marlin/src/HAL/LPC1768/upload_extra_script.py b/Marlin/src/HAL/LPC1768/upload_extra_script.py index efd46fdd6309..67552454367e 100755 --- a/Marlin/src/HAL/LPC1768/upload_extra_script.py +++ b/Marlin/src/HAL/LPC1768/upload_extra_script.py @@ -13,9 +13,9 @@ target_drive = "REARM" import platform - current_OS = platform.system() - Import("env") + + env = pioutil.env def print_error(e): print('\nUnable to find destination disk (%s)\n' \ @@ -37,12 +37,12 @@ def before_upload(source, target, env): # # platformio.ini will accept this for a Windows upload port designation: 'upload_port = L:' # Windows - doesn't care about the disk's name, only cares about the drive letter - import subprocess,string + import subprocess, string from ctypes import windll from pathlib import PureWindowsPath - # getting list of drives - # https://stackoverflow.com/questions/827371/is-there-a-way-to-list-all-the-available-drive-letters-in-python + # Getting a list of drives + # https://stackoverflow.com/questions/827371/is-there-a-way-to-list-all-the-available-windows-drives drives = [] bitmask = windll.kernel32.GetLogicalDrives() for letter in string.ascii_uppercase: @@ -54,18 +54,25 @@ def before_upload(source, target, env): final_drive_name = drive + ':' # print ('disc check: {}'.format(final_drive_name)) try: - volume_info = str(subprocess.check_output('cmd /C dir ' + final_drive_name, stderr=subprocess.STDOUT)) + volume_info = str(subprocess.check_output('cmd /C vol ' + final_drive_name, stderr=subprocess.STDOUT)) except Exception as e: print ('error:{}'.format(e)) continue else: - if target_drive in volume_info and not target_file_found: # set upload if not found target file yet - target_drive_found = True + if target_drive in volume_info: # set upload upload_disk = PureWindowsPath(final_drive_name) - if target_filename in volume_info: - if not target_file_found: + target_drive_found = True + break + try: + dir_info = str(subprocess.check_output('cmd /C dir ' + final_drive_name, stderr=subprocess.STDOUT)) + except Exception as e: + print ('error:{}'.format(e)) + continue + else: + if target_filename in dir_info: upload_disk = PureWindowsPath(final_drive_name) - target_file_found = True + target_file_found = True + break elif current_OS == 'Linux': # diff --git a/Marlin/src/HAL/NATIVE_SIM/AGENTS.md b/Marlin/src/HAL/NATIVE_SIM/AGENTS.md new file mode 100644 index 000000000000..f3f71e5d6c28 --- /dev/null +++ b/Marlin/src/HAL/NATIVE_SIM/AGENTS.md @@ -0,0 +1,81 @@ +# AGENTS.md — NATIVE_SIM HAL + +Working notes and conventions for the **NATIVE_SIM HAL** folder (`Marlin/src/HAL/NATIVE_SIM/`). + +## Build & Test Loop + +NATIVE_SIM is a **native host simulator with a GUI** — there is no MCU, no Arduino framework, and no embedded toolchain. Marlin compiles as a regular C++ program (`platform = native`, `ini/native.ini`, `[simulator_common]`) that renders a printer UI on the host using SDL2 / SDL2_net / OpenGL / GLM. Always validate with `mftest` (it regenerates `Marlin/Configuration.h` per target, which raw `pio run -e` does not do reliably): + +```bash +cd "$(git rev-parse --show-toplevel)" +buildroot/bin/mftest -t simulator_linux_release -n1 -y # Linux GUI simulator (primary) +buildroot/bin/mftest -t simulator_linux_debug -n1 -y # Linux GUI simulator, debug build +# On macOS: +buildroot/bin/mftest -t simulator_macos_release -n1 -y +# On Windows (MSYS2): +buildroot/bin/mftest -t simulator_windows -n1 -y +``` + +The `simulator_linux_release` target (`buildroot/tests/simulator_linux_release/config-01.ini` → `ini_use_config = example/Simulator`, `BOARD_SIMULATED`) is the canonical smoke target and must be green before considering NATIVE_SIM work done. + +> `mftest` only **builds** the simulator — it does not launch it. The produced binary (`MarlinSimulator`, see below) needs a graphical session (X11 / macOS window server) to actually run, so a build that links clean here can still require a desktop to exercise the UI. Don't expect `mftest` to "run" the firmware. +> +> Do NOT use `rm -rf .pio/build/...` to force a rebuild — the cross-profile write guard blocks it and it is unsafe. Use `pio run -e -t clean` (removes only build artifacts) or just let `mftest` rebuild. + +## Framework / Platform Integration (native, GUI — distinct from LINUX) + +This is a `platform = native` build, but unlike the **LINUX** HAL (a headless console app that talks over stdin/stdout), NATIVE_SIM is the **graphical** simulator. The HAL is selected by `__PLAT_NATIVE_SIM__` (`HAL/platforms.h` maps `__PLAT_NATIVE_SIM__` → `src/HAL/NATIVE_SIM/`); it has no `ARDUINO_ARCH_*` define. There is **no vendored Arduino core, no MCU SDK** — the only "platform" is the host C++ compiler (`-std=gnu++17`). + +The native GUI / Arduino-shim headers are **not** in this folder and **not** in `HAL/LINUX/include`. They are pulled in at build time from the `MarlinSimUI` library dependency (a pinned GitHub zip in `[simulator_common]` `lib_deps`, plus `Adafruit NeoPixel` and `LiquidCrystal` zips). `MarlinSimUI` supplies `Arduino.h`, `serial.h`, `pinmapping.h`, and the `Gpio` class — so `HAL.h`'s `#include "serial.h"` and `fastio.h`'s `#include ` resolve against _that_ library, not local or LINUX files. The `MarlinSimulator` program entry point (`main()`) also lives in `MarlinSimUI`, **not** in `src/HAL/NATIVE_SIM/` — there is no `main.cpp` here. + +The build is wired by: + +- `[simulator_common]` (`ini/native.ini`): `platform = native`, `-D__PLAT_NATIVE_SIM__ -DU8G_HAL_LINKS -DGLM_ENABLE_EXPERIMENTAL`, `build_src_filter = ... +`, `lib_deps` (MarlinSimUI + others), and `pre:buildroot/share/PlatformIO/scripts/simulator.py`. +- `simulator.py`: renames the binary to `MarlinSimulator`, and on macOS **fails the build** if `/usr/bin/gcc` (Apple clang) is the compiler — it requires a real GCC from MacPorts/Homebrew (`gcc14`) plus Mesa/SDL2. + +Required host libraries (from `native.ini` comments): + +- Linux: `libsdl2-dev`, `libsdl2-net-dev`, `libglm-dev` (and `python3-venv`, `build-essential`). +- macOS: MacPorts or Homebrew `gcc@14`, `glm`, `mesa`, `sdl2`, `sdl2_net`. +- Windows: MSYS2 `mingw-w64-x86_64` toolchain + `glm`, `SDL2`, `SDL2_net`, `dlfcn`. + +### Gotchas that are not obvious from the source + +1. **No MCU, no real pins — GPIO is a software array.** `fastio.h` macros (`READ_PIN`, `WRITE_PIN`, `SET_DIR_*`, `SET_MODE`) all delegate to a `Gpio` class that operates on an in-memory `pin_map[256]` (`Gpio::pin_count = 255`, matching the LINUX HAL model). There are no physical lines; `PWM_PIN`/`INTERRUPT_PIN` effectively never fire. Do not add hardware-specific pin logic here — this is the same virtual-pin model as the LINUX HAL, just with a GUI on top. + +2. **No real interrupts / ISRs.** `CRITICAL_SECTION_START/END` are empty, `isr_on/off` are no-ops, and `DELAY_CYCLES` is a software delay. Endstop/timer "hardware" events are driven by the simulator's own loop (MarlinSimUI), not by vector interrupts. Don't write ISR-dependent timing assumptions into HAL code. + +3. **Serial is virtual over the host process.** `HAL.h` declares `MSerialT serial_stream_0..3` (`SERIAL_INDEX_MIN/MAX` 0..3) and `serial.h` (from MarlinSimUI) defines `MSerialT` as `Serial1Class` — ring-buffer-backed virtual ports. G-code is exchanged with the `MarlinSimulator` process's stdin/stdout, not a UART/USB device. There is no `Serial` USB-CDC object. + +4. **Arduino/serial/pinmapping/Gpio headers come from MarlinSimUI, not this folder.** A HAL change that _looks_ like a missing local header (e.g. `serial.h`, `pinmapping.h`, `Gpio.h`) is actually supplied by the downloaded library dependency. If a build fails with "header not found", suspect a `lib_deps` fetch failure or a `simulator.py`/GCC mismatch — not a missing file in `src/HAL/NATIVE_SIM/`. + +5. **The GUI entry point is external.** There is no `main.cpp` in this HAL. `MarlinSimUI` provides `main()` and the SDL2 event/render loop; `simulator.py` sets `PROGNAME = "MarlinSimulator"`. HAL code here runs _inside_ that host GUI program — don't add process entry points or assume `setup()`/`loop()` are the only callers. + +6. **macOS requires real GCC, not Apple clang.** `simulator.py` hard-fails when `/usr/bin/gcc` (which is clang) is on PATH; you must `port install gcc14 ...` or `brew install gcc@14 ...` and symlink `gcc`/`g++` to `gcc-14`/`g++-14`. Linux/Windows use their respective native GCC/MinGW toolchains. A "can't build on macOS" symptom is almost always this GCC-vs-clang issue, not a HAL bug. + +7. **`NO_COMPILE_TIME_PWM` is set in `fastio.h`.** PWM duty is computed at runtime via `MarlinHAL::set_pwm_duty()` (`map(v, 0, v_size, 0, UINT16_MAX)` → `analogWrite`), so there are no compile-time PWM pin tables. Don't add `PWM`/compile-time duty logic. + +8. **Display layer is software-SPI u8g, not hardware.** `u8g/u8g_com_sw_spi.cpp`, `u8g_com_st7920_sw_spi.cpp`, and the `LCD_*` routines implement bit-banged SPI that the simulator renders into its GUI window. The `build_flags` carry `-DU8G_HAL_LINKS`; the drawing ultimately goes through U8glib-HAL in the MarlinSimUI host. Treat these as rendering shims, not real bus drivers. + +9. **No local `eeprom.cpp` / `main.cpp`.** Unlike the LINUX HAL (which has its own `eeprom.cpp` writing `eeprom.dat`), NATIVE_SIM has no EEPROM file in this folder — persistence uses the shared `HAL/shared` EEPROM API mediated by the simulator host. A "missing eeprom" symptom points at the shared API or MarlinSimUI, not a missing `src/HAL/NATIVE_SIM` file. + +10. **`freeMemory()` is a stub.** Per `HAL.h`, `MarlinHAL::freeMemory()` and `::freeMemory()` are placeholders (no meaningful host heap figure). Don't key any logic off the returned value. + +## Conventions + +- All HAL source is guarded by `#ifdef __PLAT_NATIVE_SIM__` (not `ARDUINO_ARCH_*`). Every `.cpp` wraps its body in this guard (see `HAL.cpp`, `pinsDebug.cpp`, `Servo.cpp`). +- Keep HAL changes in `Marlin/src/HAL/NATIVE_SIM/` and its `u8g/`, `tft/`, `inc/` subfolders. Board pin layout is `Marlin/src/pins/native/pins_RAMPS_NATIVE.h` (`BOARD_SIMULATED`, a RAMPS-like map) — not in this folder. +- This is the **GUI** simulator; the **LINUX** HAL (`src/HAL/LINUX/`, `linux_native` env) is the separate headless console build. Do not conflate the two. Both share the same virtual-`Gpio` pin model, so a pin/simulation fix may need to be mirrored in the LINUX HAL too. +- External dependencies (`MarlinSimUI`, `Adafruit NeoPixel`, `LiquidCrystal`) are pinned git-zip `lib_deps`; if you change a HAL API they call, bump/verify the library version and re-test the build. +- When you change HAL code, run `simulator_linux_release` (and the platform you develop on) and verify green before committing. + +## Related + +- `Marlin/src/HAL/AT32/AGENTS.md` — the precedent for this file's structure. +- `Marlin/src/HAL/LINUX/AGENTS.md` — the sibling headless console simulator (shared virtual-`Gpio` model, distinct build). +- Wiki: [[hal-native-sim]], [[marlin-src-hal]], [[hal-linux]]. +- `Marlin/src/HAL/shared/` — shared HAL APIs (`eeprom_api`, SPI helpers, `Marduino.h`) reused by this HAL. +- `ini/native.ini` — `[simulator_common]` / `simulator_linux*` / `simulator_macos*` / `simulator_windows` env definitions. +- `buildroot/tests/simulator_linux_release/config-01.ini` — the `BOARD_SIMULATED` + - `example/Simulator` test config. +- `buildroot/share/PlatformIO/scripts/simulator.py` — pre-build script (binary rename, macOS GCC/OpenGL checks). diff --git a/Marlin/src/HAL/NATIVE_SIM/HAL.h b/Marlin/src/HAL/NATIVE_SIM/HAL.h index f57e065a8e82..b3b66d54d354 100644 --- a/Marlin/src/HAL/NATIVE_SIM/HAL.h +++ b/Marlin/src/HAL/NATIVE_SIM/HAL.h @@ -52,7 +52,9 @@ uint8_t _getc(); // ------------------------ #define CPU_32_BIT -#define SHARED_SERVOS HAS_SERVOS // Use shared/servos.cpp + +class Servo; +typedef Servo hal_servo_t; #define F_CPU 100000000 #define SystemCoreClock F_CPU @@ -71,37 +73,10 @@ extern MSerialT serial_stream_2; extern MSerialT serial_stream_3; #define _MSERIAL(X) serial_stream_##X -#define MSERIAL(X) _MSERIAL(X) - -#if WITHIN(SERIAL_PORT, 0, 3) - #define MYSERIAL1 MSERIAL(SERIAL_PORT) -#else - #error "SERIAL_PORT must be from 0 to 3. Please update your configuration." -#endif - -#ifdef SERIAL_PORT_2 - #if WITHIN(SERIAL_PORT_2, 0, 3) - #define MYSERIAL2 MSERIAL(SERIAL_PORT_2) - #else - #error "SERIAL_PORT_2 must be from 0 to 3. Please update your configuration." - #endif -#endif -#ifdef MMU2_SERIAL_PORT - #if WITHIN(MMU2_SERIAL_PORT, 0, 3) - #define MMU2_SERIAL MSERIAL(MMU2_SERIAL_PORT) - #else - #error "MMU2_SERIAL_PORT must be from 0 to 3. Please update your configuration." - #endif -#endif - -#ifdef LCD_SERIAL_PORT - #if WITHIN(LCD_SERIAL_PORT, 0, 3) - #define LCD_SERIAL MSERIAL(LCD_SERIAL_PORT) - #else - #error "LCD_SERIAL_PORT must be from 0 to 3. Please update your configuration." - #endif -#endif +#define SERIAL_INDEX_MIN 0 +#define SERIAL_INDEX_MAX 3 +#include "../shared/serial_ports.h" // ------------------------ // Interrupts @@ -220,9 +195,9 @@ class MarlinHAL { static void isr_on() {} static void isr_off() {} - static void delay_ms(const int ms) { _delay_ms(ms); } + static void delay_ms(const int ms) { delay(ms); } - // Tasks, called from idle() + // Tasks, called from marlin.idle() static void idletask(); // Reset @@ -259,8 +234,10 @@ class MarlinHAL { * No option to invert the duty cycle [default = false] * No option to change the scale of the provided value to enable finer PWM duty control [default = 255] */ - static void set_pwm_duty(const pin_t pin, const uint16_t v, const uint16_t=255, const bool=false) { - analogWrite(pin, v); + static void set_pwm_duty(const pin_t pin, const uint16_t v, const uint16_t v_size=255, const bool invert=false) { + auto value = map(v, 0, v_size, 0, UINT16_MAX); + value = invert ? UINT16_MAX - value : value; + analogWrite(pin, value); } static void set_pwm_frequency(const pin_t, int) {} diff --git a/Marlin/src/HAL/NATIVE_SIM/Servo.cpp b/Marlin/src/HAL/NATIVE_SIM/Servo.cpp new file mode 100644 index 000000000000..a15eda1bf6d6 --- /dev/null +++ b/Marlin/src/HAL/NATIVE_SIM/Servo.cpp @@ -0,0 +1,104 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2025 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 . + * + */ +#include "../platforms.h" + +#ifdef __PLAT_NATIVE_SIM__ + +#include "../../inc/MarlinConfig.h" + +#if HAS_SERVOS + +#include "Servo.h" + +//#define DEBUG_SERVOS +#define DEBUG_OUT ENABLED(DEBUG_SERVOS) +#include "../../../core/debug_out.h" + +uint8_t ServoCount = 0; // the total number of attached servos + +Servo::Servo() { + // Constructor stub + DEBUG_ECHOLNPGM("Debug Servo: constructor"); + this->servoIndex = ServoCount++; // assign a servo index to this instance +} + +uint8_t Servo::attach(int pin) { + // Attach stub + DEBUG_ECHOLNPGM("Debug Servo: attach to pin ", pin, " servo index ", this->servoIndex); + return attach(pin, MIN_PULSE_WIDTH, MAX_PULSE_WIDTH); +} + +uint8_t Servo::attach(int pin, int min, int max) { + // Attach with min and max stub + DEBUG_ECHOLNPGM("Debug Servo: attach to pin ", pin, " with min ", min, " and max ", max); + if (pin > 0) servo_pin = pin; + return this->servoIndex; +} + +void Servo::detach() { + // Detach stub + DEBUG_ECHOLNPGM("Debug Servo: detach"); +} + +// If value is < 200 it is treated as an angle, otherwise as pulse width in microseconds +void Servo::write(int value) { + if (value < MIN_PULSE_WIDTH) { // treat values less than 544 as angles in degrees (valid values in microseconds are handled as microseconds) + value = map(constrain(value, 0, 180), 0, 180, SERVO_MIN_US(min), SERVO_MAX_US(max)); + } + writeMicroseconds(value); + DEBUG_ECHOLNPGM("Debug Servo: write ", value); +} + +void Servo::writeMicroseconds(int value) { + // Simulate the servo movement + this->value = value; + hal.set_pwm_duty(pin_t(this->servo_pin), (float(value) / 20000) * UINT16_MAX, UINT16_MAX); + DEBUG_ECHOLNPGM("Debug Servo: write microseconds ", value); +} + +int Servo::read() { + // Read stub + DEBUG_ECHOLNPGM("Debug Servo: read ", this->value); + return this->value; +} + +int Servo::readMicroseconds() { + // Read microseconds stub + DEBUG_ECHOLNPGM("Debug Servo: read microseconds"); + return 0; +} + +bool Servo::attached() { + // Attached stub + DEBUG_ECHOLNPGM("Debug Servo: attached"); + return false; +} + +int Servo::move(const unsigned char cmd) { + // Move stub + DEBUG_ECHOLNPGM("Debug Servo: move ", cmd); + write(cmd); + return 0; +} + +#endif // HAS_SERVOS +#endif // __PLAT_NATIVE_SIM__ diff --git a/Marlin/src/HAL/NATIVE_SIM/Servo.h b/Marlin/src/HAL/NATIVE_SIM/Servo.h new file mode 100644 index 000000000000..428871142c15 --- /dev/null +++ b/Marlin/src/HAL/NATIVE_SIM/Servo.h @@ -0,0 +1,48 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2025 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 . + * + */ +#pragma once + +#define MIN_PULSE_WIDTH 544 // the shortest pulse sent to a servo +#define MAX_PULSE_WIDTH 2400 // the longest pulse sent to a servo +#define DEFAULT_PULSE_WIDTH 1500 // default pulse width when servo is attached +#define SERVO_MIN_US(v) (MIN_PULSE_WIDTH - (v) * 4) // minimum value in uS for this servo +#define SERVO_MAX_US(v) (MAX_PULSE_WIDTH - (v) * 4) // maximum value in uS for this servo + +class Servo { +public: + Servo(); + uint8_t attach(int pin); // Attach the given pin to the next free channel, set pinMode, return channel number or INVALID_SERVO if failure + uint8_t attach(int pin, int min, int max); // As above but also set min and max values for writes. + void detach(); + void write(int value); // If value is < 200 it's treated as an angle, otherwise as pulse width in microseconds + void writeMicroseconds(int value); // Write pulse width in microseconds + int read(); // Return current pulse width as an angle between 0 and 180 degrees + int readMicroseconds(); // Return current pulse width in microseconds for this servo + bool attached(); // Return true if this servo is attached, otherwise false + int move (const unsigned char cmd); +private: + uint8_t servoIndex; // Index into the channel data for this servo + int8_t min; // Minimum is this value times 4 added to MIN_PULSE_WIDTH + int8_t max; // Maximum is this value times 4 added to MAX_PULSE_WIDTH + int value; // Pulse width in microseconds for this servo + int servo_pin = 0; // pin number for this servo +}; diff --git a/Marlin/src/HAL/NATIVE_SIM/endstop_interrupts.h b/Marlin/src/HAL/NATIVE_SIM/endstop_interrupts.h new file mode 100644 index 000000000000..e74c3122742b --- /dev/null +++ b/Marlin/src/HAL/NATIVE_SIM/endstop_interrupts.h @@ -0,0 +1,30 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2025 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 . + * + */ +#pragma once + +#error "ENDSTOP_INTERRUPTS_FEATURE is not supported in this simulation environment." + +void setup_endstop_interrupts() { + // This function is a stub for setting up endstop interrupts. + // Since this is a simulation environment, actual hardware interrupts + // are not applicable. Add any necessary simulation-specific logic here. +} diff --git a/Marlin/src/HAL/NATIVE_SIM/fastio.h b/Marlin/src/HAL/NATIVE_SIM/fastio.h index f501afdbaa4e..e0b7af09f947 100644 --- a/Marlin/src/HAL/NATIVE_SIM/fastio.h +++ b/Marlin/src/HAL/NATIVE_SIM/fastio.h @@ -28,6 +28,8 @@ #include "../shared/Marduino.h" #include +#define NO_COMPILE_TIME_PWM + #define SET_DIR_INPUT(IO) Gpio::setDir(IO, 1) #define SET_DIR_OUTPUT(IO) Gpio::setDir(IO, 0) diff --git a/Marlin/src/HAL/NATIVE_SIM/pinsDebug.cpp b/Marlin/src/HAL/NATIVE_SIM/pinsDebug.cpp index c4d56c6c218d..1354c79b31ef 100644 --- a/Marlin/src/HAL/NATIVE_SIM/pinsDebug.cpp +++ b/Marlin/src/HAL/NATIVE_SIM/pinsDebug.cpp @@ -25,11 +25,11 @@ #include "../../inc/MarlinConfig.h" #include "pinsDebug.h" -int8_t ADC_pin_mode(pin_t pin) { return -1; } +int8_t ADC_pin_mode(const pin_t) { 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,13 @@ 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); +// The pin and index are the same on this platform +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 diff --git a/Marlin/src/HAL/NATIVE_SIM/pinsDebug.h b/Marlin/src/HAL/NATIVE_SIM/pinsDebug.h index 3321d1484dbe..752802d43808 100644 --- a/Marlin/src/HAL/NATIVE_SIM/pinsDebug.h +++ b/Marlin/src/HAL/NATIVE_SIM/pinsDebug.h @@ -19,30 +19,43 @@ * along with this program. If not, see . * */ - -/** - * Support routines for X86_64 - */ #pragma once /** - * Translation of routines & variables used by pinsDebug.h + * Pins Debugging for x86_64 + * + * - NUMBER_PINS_TOTAL + * - MULTI_NAME_PAD + * - getPinByIndex(index) + * - printPinNameByIndex(index) + * - getPinIsDigitalByIndex(index) + * - digitalPinToAnalogIndex(pin) + * - getValidPinMode(pin) + * - isValidPin(pin) + * - isAnalogPin(pin) + * - digitalRead_mod(pin) + * - pwm_status(pin) + * - printPinPWM(pin) + * - printPinPort(pin) + * - printPinNumber(pin) + * - printPinAnalog(pin) */ #define NUMBER_PINS_TOTAL NUM_DIGITAL_PINS -#define IS_ANALOG(P) (DIGITAL_PIN_TO_ANALOG_PIN(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 isValidPin(P) (P >= 0 && P < pin_t(NUMBER_PINS_TOTAL)) +#define isAnalogPin(P) (digitalPinToAnalogIndex(P) >= 0) +#define digitalRead_mod(P) digitalRead(P) +#define getPinByIndex(x) 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 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(P)); 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); +int8_t ADC_pin_mode(const pin_t); +int8_t get_pin_mode(const pin_t); +bool getValidPinMode(const pin_t); +bool getPinIsDigitalByIndex(const pin_t); +void printPinPort(const pin_t); +void printPinPWM(const pin_t); bool pwm_status(const pin_t); diff --git a/Marlin/src/HAL/NATIVE_SIM/servo_private.h b/Marlin/src/HAL/NATIVE_SIM/servo_private.h deleted file mode 100644 index e0eb30ab2846..000000000000 --- a/Marlin/src/HAL/NATIVE_SIM/servo_private.h +++ /dev/null @@ -1,79 +0,0 @@ -/** - * Marlin 3D Printer Firmware - * Copyright (c) 2021 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 . - * - */ -#pragma once - -/** - * servo.h - Interrupt driven Servo library for Arduino using 16 bit timers- Version 2 - * Copyright (c) 2009 Michael Margolis. All right reserved. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - */ - -/** - * Based on "servo.h - Interrupt driven Servo library for Arduino using 16 bit timers - - * Version 2 Copyright (c) 2009 Michael Margolis. All right reserved. - * - * The only modification was to update/delete macros to match the LPC176x. - * - */ - -#include - -// Macros -//values in microseconds -#define MIN_PULSE_WIDTH 544 // the shortest pulse sent to a servo -#define MAX_PULSE_WIDTH 2400 // the longest pulse sent to a servo -#define DEFAULT_PULSE_WIDTH 1500 // default pulse width when servo is attached -#define REFRESH_INTERVAL 20000 // minimum time to refresh servos in microseconds - -#define MAX_SERVOS 4 - -#define INVALID_SERVO 255 // flag indicating an invalid servo index - -// Types - -typedef struct { - uint8_t nbr : 8 ; // a pin number from 0 to 254 (255 signals invalid pin) - uint8_t isActive : 1 ; // true if this channel is enabled, pin not pulsed if false -} ServoPin_t; - -typedef struct { - ServoPin_t Pin; - unsigned int pulse_width; // pulse width in microseconds -} ServoInfo_t; - -// Global variables - -extern uint8_t ServoCount; -extern ServoInfo_t servo_info[MAX_SERVOS]; diff --git a/Marlin/src/HAL/NATIVE_SIM/spi_pins.h b/Marlin/src/HAL/NATIVE_SIM/spi_pins.h index d9911bf56ce4..0e83e1369273 100644 --- a/Marlin/src/HAL/NATIVE_SIM/spi_pins.h +++ b/Marlin/src/HAL/NATIVE_SIM/spi_pins.h @@ -32,7 +32,6 @@ //#define SD_SCK_PIN P0_07 //#define SD_MISO_PIN P0_08 //#define SD_MOSI_PIN P0_09 -//#define SD_SS_PIN P0_06 // External SD #ifndef SD_SCK_PIN @@ -44,9 +43,3 @@ #ifndef SD_MOSI_PIN #define SD_MOSI_PIN 52 #endif -#ifndef SD_SS_PIN - #define SD_SS_PIN 53 -#endif -#ifndef SDSS - #define SDSS SD_SS_PIN -#endif diff --git a/Marlin/src/HAL/NATIVE_SIM/timers.h b/Marlin/src/HAL/NATIVE_SIM/timers.h index d46e8e7b9402..be59bb8676c2 100644 --- a/Marlin/src/HAL/NATIVE_SIM/timers.h +++ b/Marlin/src/HAL/NATIVE_SIM/timers.h @@ -34,7 +34,7 @@ #define FORCE_INLINE __attribute__((always_inline)) inline typedef uint64_t hal_timer_t; -#define HAL_TIMER_TYPE_MAX 0xFFFFFFFFFFFFFFFF +#define HAL_TIMER_TYPE_MAX hal_timer_t(UINT64_MAX) #define HAL_TIMER_RATE ((SystemCoreClock) / 4) // frequency of timers peripherals @@ -52,22 +52,21 @@ typedef uint64_t hal_timer_t; #endif #define SYSTICK_TIMER_FREQUENCY 1000 -#define TEMP_TIMER_RATE 1000000 -#define TEMP_TIMER_FREQUENCY 1000 // temperature interrupt frequency +#define TEMP_TIMER_RATE 1'000'000 // (Hz) Temperature Timer count rate +#define TEMP_TIMER_FREQUENCY 1000 // (Hz) Temperature ISR call frequency -#define STEPPER_TIMER_RATE HAL_TIMER_RATE // frequency of stepper timer (HAL_TIMER_RATE / STEPPER_TIMER_PRESCALE) -#define STEPPER_TIMER_TICKS_PER_US ((STEPPER_TIMER_RATE) / 1000000) // stepper timer ticks per µs -#define STEPPER_TIMER_PRESCALE (CYCLES_PER_MICROSECOND / STEPPER_TIMER_TICKS_PER_US) +#define STEPPER_TIMER_RATE HAL_TIMER_RATE // (Hz) Frequency of Stepper Timer (HAL_TIMER_RATE / STEPPER_TIMER_PRESCALE) +#define STEPPER_TIMER_TICKS_PER_US ((STEPPER_TIMER_RATE) / 1'000'000) // (MHz) Stepper Timer ticks per µs +#define STEPPER_TIMER_PRESCALE (CYCLES_PER_MICROSECOND / STEPPER_TIMER_TICKS_PER_US) -#define PULSE_TIMER_RATE STEPPER_TIMER_RATE // frequency of pulse timer -#define PULSE_TIMER_PRESCALE STEPPER_TIMER_PRESCALE -#define PULSE_TIMER_TICKS_PER_US STEPPER_TIMER_TICKS_PER_US +#define PULSE_TIMER_RATE STEPPER_TIMER_RATE // (Hz) Frequency of Pulse Timer +#define PULSE_TIMER_PRESCALE STEPPER_TIMER_PRESCALE -#define ENABLE_STEPPER_DRIVER_INTERRUPT() HAL_timer_enable_interrupt(MF_TIMER_STEP) -#define DISABLE_STEPPER_DRIVER_INTERRUPT() HAL_timer_disable_interrupt(MF_TIMER_STEP) -#define STEPPER_ISR_ENABLED() HAL_timer_interrupt_enabled(MF_TIMER_STEP) +#define ENABLE_STEPPER_DRIVER_INTERRUPT() HAL_timer_enable_interrupt(MF_TIMER_STEP) +#define DISABLE_STEPPER_DRIVER_INTERRUPT() HAL_timer_disable_interrupt(MF_TIMER_STEP) +#define STEPPER_ISR_ENABLED() HAL_timer_interrupt_enabled(MF_TIMER_STEP) -#define ENABLE_TEMPERATURE_INTERRUPT() HAL_timer_enable_interrupt(MF_TIMER_TEMP) +#define ENABLE_TEMPERATURE_INTERRUPT() HAL_timer_enable_interrupt(MF_TIMER_TEMP) #define DISABLE_TEMPERATURE_INTERRUPT() HAL_timer_disable_interrupt(MF_TIMER_TEMP) #ifndef HAL_STEP_TIMER_ISR @@ -88,5 +87,5 @@ void HAL_timer_enable_interrupt(const uint8_t timer_num); void HAL_timer_disable_interrupt(const uint8_t timer_num); bool HAL_timer_interrupt_enabled(const uint8_t timer_num); -#define HAL_timer_isr_prologue(T) NOOP -#define HAL_timer_isr_epilogue(T) NOOP +inline void HAL_timer_isr_prologue(const uint8_t) {} +inline void HAL_timer_isr_epilogue(const uint8_t) {} diff --git a/Marlin/src/HAL/NATIVE_SIM/u8g/LCD_I2C_routines.cpp b/Marlin/src/HAL/NATIVE_SIM/u8g/LCD_I2C_routines.cpp index 745454394aae..8033b79e3d0e 100644 --- a/Marlin/src/HAL/NATIVE_SIM/u8g/LCD_I2C_routines.cpp +++ b/Marlin/src/HAL/NATIVE_SIM/u8g/LCD_I2C_routines.cpp @@ -21,7 +21,7 @@ */ // adapted from I2C/master/master.c example -// https://www-users.cs.york.ac.uk/~pcc/MCP/HAPR-Course-web/CMSIS/examples/html/master_8c_source.html +// https://www-users.york.ac.uk/~pcc1/MCP/HAPR-Course-web/CMSIS/examples/html/master_8c_source.html #ifdef __PLAT_NATIVE_SIM__ diff --git a/Marlin/src/HAL/NATIVE_SIM/u8g/LCD_pin_routines.cpp b/Marlin/src/HAL/NATIVE_SIM/u8g/LCD_pin_routines.cpp index 356652807983..9cb33694a353 100644 --- a/Marlin/src/HAL/NATIVE_SIM/u8g/LCD_pin_routines.cpp +++ b/Marlin/src/HAL/NATIVE_SIM/u8g/LCD_pin_routines.cpp @@ -27,7 +27,7 @@ * * Couldn't just call exact copies because the overhead killed the LCD update speed * With an intermediate level the softspi was running in the 10-20kHz range which - * resulted in using about about 25% of the CPU's time. + * resulted in using about 25% of the CPU's time. */ #ifdef __PLAT_NATIVE_SIM__ diff --git a/Marlin/src/HAL/NATIVE_SIM/u8g/LCD_pin_routines.h b/Marlin/src/HAL/NATIVE_SIM/u8g/LCD_pin_routines.h index 39af4d7e6844..ab73635d2892 100644 --- a/Marlin/src/HAL/NATIVE_SIM/u8g/LCD_pin_routines.h +++ b/Marlin/src/HAL/NATIVE_SIM/u8g/LCD_pin_routines.h @@ -28,7 +28,7 @@ * * Couldn't just call exact copies because the overhead killed the LCD update speed * With an intermediate level the softspi was running in the 10-20kHz range which - * resulted in using about about 25% of the CPU's time. + * resulted in using about 25% of the CPU's time. */ #ifdef __cplusplus diff --git a/Marlin/src/HAL/NATIVE_SIM/u8g/u8g_com_st7920_sw_spi.cpp b/Marlin/src/HAL/NATIVE_SIM/u8g/u8g_com_st7920_sw_spi.cpp index 46f2798afaa6..b07bc1644d52 100644 --- a/Marlin/src/HAL/NATIVE_SIM/u8g/u8g_com_st7920_sw_spi.cpp +++ b/Marlin/src/HAL/NATIVE_SIM/u8g/u8g_com_st7920_sw_spi.cpp @@ -71,13 +71,13 @@ static uint8_t SPI_speed = 0; static uint8_t swSpiTransfer(uint8_t b, const uint8_t spi_speed, const pin_t sck_pin, const pin_t miso_pin, const pin_t mosi_pin) { for (uint8_t i = 0; i < 8; i++) { - WRITE_PIN(mosi_pin, !!(b & 0x80)); + WRITE_PIN(sck_pin, TERN(U8G_SPI_USE_MODE_3, LOW, HIGH)); DELAY_CYCLES(SPI_SPEED); - WRITE_PIN(sck_pin, HIGH); + WRITE_PIN(mosi_pin, !!(b & 0x80)); DELAY_CYCLES(SPI_SPEED); b <<= 1; if (miso_pin >= 0 && READ_PIN(miso_pin)) b |= 1; - WRITE_PIN(sck_pin, LOW); + WRITE_PIN(sck_pin, TERN(U8G_SPI_USE_MODE_3, HIGH, LOW)); DELAY_CYCLES(SPI_SPEED); } return b; @@ -85,7 +85,7 @@ static uint8_t swSpiTransfer(uint8_t b, const uint8_t spi_speed, const pin_t sck static uint8_t swSpiInit(const uint8_t spiRate, const pin_t sck_pin, const pin_t mosi_pin) { WRITE_PIN(mosi_pin, HIGH); - WRITE_PIN(sck_pin, LOW); + WRITE_PIN(sck_pin, TERN(U8G_SPI_USE_MODE_3, HIGH, LOW)); return spiRate; } @@ -93,11 +93,11 @@ static void u8g_com_st7920_write_byte_sw_spi(uint8_t rs, uint8_t val) { static uint8_t rs_last_state = 255; if (rs != rs_last_state) { // Transfer Data (FA) or Command (F8) - swSpiTransfer(rs ? 0x0FA : 0x0F8, SPI_speed, SCK_pin_ST7920_HAL, -1, MOSI_pin_ST7920_HAL_HAL); + swSpiTransfer(rs ? 0xFA : 0xF8, SPI_speed, SCK_pin_ST7920_HAL, -1, MOSI_pin_ST7920_HAL_HAL); rs_last_state = rs; DELAY_US(40); // Give the controller time to process the data: 20 is bad, 30 is OK, 40 is safe } - swSpiTransfer(val & 0x0F0, SPI_speed, SCK_pin_ST7920_HAL, -1, MOSI_pin_ST7920_HAL_HAL); + swSpiTransfer(val & 0xF0, SPI_speed, SCK_pin_ST7920_HAL, -1, MOSI_pin_ST7920_HAL_HAL); swSpiTransfer(val << 4, SPI_speed, SCK_pin_ST7920_HAL, -1, MOSI_pin_ST7920_HAL_HAL); } @@ -169,5 +169,32 @@ uint8_t u8g_com_ST7920_sw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void } #endif +#if ENABLED(LIGHTWEIGHT_UI) + + #define ST7920_CS() { WRITE(LCD_PINS_RS, HIGH); } + #define ST7920_NCS() { WRITE(LCD_PINS_RS, LOW); } + #define ST7920_SET_CMD() { ST7920_SWSPI_SND_8BIT(0xF8); } + #define ST7920_SET_DAT() { ST7920_SWSPI_SND_8BIT(0xFA); } + #define ST7920_WRITE_BYTE(a) { ST7920_SWSPI_SND_8BIT((uint8_t)((a)&0xF0u)); ST7920_SWSPI_SND_8BIT((uint8_t)((a)<<4U)); } + + #define ST7920_DAT(V) !!((V) & 0x80) + + #define ST7920_SND_BIT(...) do{ \ + WRITE(LCD_PINS_D4, LOW); \ + WRITE(LCD_PINS_EN, ST7920_DAT(val)); \ + WRITE(LCD_PINS_D4, HIGH); \ + val <<= 1; }while(0); + + void ST7920_SWSPI_SND_8BIT(uint8_t val) { + REPEAT(8, ST7920_SND_BIT); + } + + void ST7920_cs() { ST7920_CS(); } + void ST7920_ncs() { ST7920_NCS(); } + void ST7920_set_cmd() { ST7920_SET_CMD(); } + void ST7920_set_dat() { ST7920_SET_DAT(); } + void ST7920_write_byte(const uint8_t val) { ST7920_WRITE_BYTE(val); } +#endif // LIGHTWEIGHT_UI + #endif // IS_U8GLIB_ST7920 #endif // __PLAT_NATIVE_SIM__ diff --git a/Marlin/src/HAL/NATIVE_SIM/u8g/u8g_com_sw_spi.cpp b/Marlin/src/HAL/NATIVE_SIM/u8g/u8g_com_sw_spi.cpp index 9184e2f6188c..fd11e5d767f2 100644 --- a/Marlin/src/HAL/NATIVE_SIM/u8g/u8g_com_sw_spi.cpp +++ b/Marlin/src/HAL/NATIVE_SIM/u8g/u8g_com_sw_spi.cpp @@ -127,11 +127,11 @@ uint8_t swSpiTransfer_mode_3(uint8_t b, const uint8_t spi_speed, const pin_t sck static uint8_t SPI_speed = 0; static uint8_t swSpiInit(const uint8_t spi_speed, const uint8_t clk_pin, const uint8_t mosi_pin) { - return spi_speed; + return spi_speed; } static void u8g_sw_spi_shift_out(uint8_t dataPin, uint8_t clockPin, uint8_t val) { - #if ANY(FYSETC_MINI_12864, MKS_MINI_12864) + #if U8G_SPI_USE_MODE_3 swSpiTransfer_mode_3(val, SPI_speed, clockPin, -1, dataPin); #else swSpiTransfer_mode_0(val, SPI_speed, clockPin, -1, dataPin); @@ -159,15 +159,15 @@ uint8_t u8g_com_sw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_pt break; case U8G_COM_MSG_CHIP_SELECT: - #if ANY(FYSETC_MINI_12864, MKS_MINI_12864) // LCD SPI is running mode 3 while SD card is running mode 0 - if (arg_val) { // SCK idle state needs to be set to the proper idle state before - // the next chip select goes active - u8g_SetPILevel(u8g, U8G_PI_SCK, 1); // Set SCK to mode 3 idle state before CS goes active + #if U8G_SPI_USE_MODE_3 // LCD SPI is running mode 3 while SD card is running mode 0 + if (arg_val) { // SCK idle state needs to be set to the proper idle state before + // the next chip select goes active + u8g_SetPILevel(u8g, U8G_PI_SCK, 1); // Set SCK to mode 3 idle state before CS goes active u8g_SetPILevel(u8g, U8G_PI_CS, LOW); } else { u8g_SetPILevel(u8g, U8G_PI_CS, HIGH); - u8g_SetPILevel(u8g, U8G_PI_SCK, 0); // Set SCK to mode 0 idle state after CS goes inactive + u8g_SetPILevel(u8g, U8G_PI_SCK, 0); // Set SCK to mode 0 idle state after CS goes inactive } #else u8g_SetPILevel(u8g, U8G_PI_CS, !arg_val); diff --git a/Marlin/src/HAL/RP2040/AGENTS.md b/Marlin/src/HAL/RP2040/AGENTS.md new file mode 100644 index 000000000000..05d8c74d3b75 --- /dev/null +++ b/Marlin/src/HAL/RP2040/AGENTS.md @@ -0,0 +1,55 @@ +# AGENTS.md — RP2040 HAL + +Working notes and conventions for the **RP2040 HAL** folder (`Marlin/src/HAL/RP2040/`). + +## Build & Test Loop + +Validate with `mftest` (it regenerates `Marlin/Configuration.h` per target, which raw `pio run -e` does not do reliably): + +```bash +cd "$(git rev-parse --show-toplevel)" +buildroot/bin/mftest -t SKR_Pico -n1 -y # BTT SKR Pico, BOARD_BTT_SKR_PICO +``` + +`SKR_Pico` is the only RP2040 test folder under `buildroot/tests/` and is the canonical RP2040 target. The `ini/raspberrypi.ini` file also defines the `RP2040` and `RP2040-alt` environments (board `pico`), but they have no dedicated test folder, so `SKR_Pico` is what CI and maintainers build for this HAL. + +> Build not verified in this environment — the Arduino-Pico / Raspberry Pi Pico SDK toolchain is not installed here. Run `mftest -t SKR_Pico -n1 -y` locally to confirm green. Do NOT use `rm -rf .pio/build/...` to force a rebuild — the cross-profile write guard blocks it and it is unsafe. Use `pio run -e SKR_Pico -t clean` (build artifacts only) or just let `mftest` rebuild. + +## Framework / Platform Integration (upstream, not vendored) + +The RP2040 Arduino core is **upstream**, NOT Marlin-vendored. It is the [Arduino-Pico core](https://arduino-pico.readthedocs.io/) (earlephilhower), which wraps the Raspberry Pi **Pico SDK** (`pico-sdk`) and TinyUSB. `ini/raspberrypi.ini` selects it: + +- `env:RP2040` → `platform = raspberrypi`, `framework = arduino` (default `earlephilhower` core, board `pico`). +- `env:RP2040-alt` → `platform = https://github.com/maxgerhardt/platform-raspberrypi.git` with `board_build.core = earlephilhower`. + +Because the framework is upstream, there is **no mirror-to-installed-package step** like AT32 requires. Marlin-specific glue lives entirely in `Marlin/src/HAL/RP2040/` (plus `buildroot/share/PlatformIO/` scripts common to all HALs). The compile guard is `-D__PLAT_RP2040__` (set in `raspberrypi.ini` `build_flags`), not `ARDUINO_ARCH_RP2040`. All HAL sources are guarded by `#ifdef __PLAT_RP2040__`. + +### Gotchas that are not obvious from the source + +1. **The MCU is dual-core and Marlin splits work across cores.** `MarlinHAL::adc_init()` launches `core1_adc_task()` on Core 1 via `multicore_launch_core1()` (`HAL.cpp`); it runs a continuous ADC scan loop forever. Marlin's `Temperature` ISR and main loop run on Core 0. Do not block or spin Core 1, and keep shared ADC state (`adc_values[]`, `*_heartbeat`) `volatile`. The watchdog refresh (`watchdog_refresh`) cross-checks both cores' heartbeats — if Core 1 stalls >2 s, Core 0 stops refreshing the WDT and lets the chip reset (`HAL.cpp`). + +2. **`Serial` is USB-CDC, not a UART.** In the Arduino-Pico core, `Serial`/`SerialUSB` is the USB CDC ACM port and `Serial1`/`Serial2` are hardware UART0/UART1. Marlin's `MarlinSerial.h` wraps these as `MSerial0`/`MSerial1` (UART0/UART1) and `MSerial2` (USB). `SERIAL_PORT`/`SERIAL_PORT_2`/`LCD_SERIAL_PORT` are clamped to `-1..1` (`HAL.h`). USB-CDC emergency parsing requires `USBD_USE_CDC` + `EMERGENCY_PARSER` (`HAL.cpp`); otherwise Marlin talks over a real UART or the USB serial. + +3. **EEPROM backend is per-board, not auto-selected.** The HAL ships two backends: `eeprom/eeprom_flash.cpp` (`FLASH_EEPROM_EMULATION`) and `eeprom/eeprom_wired.cpp` (`USE_WIRED_EEPROM`). Each RP2040 board's pins file must `#define` one of them; Marlin does not pick it automatically. The flash backend writes the **last flash sector** via `FLASH_TARGET_OFFSET = PICO_FLASH_SIZE_BYTES - FLASH_SECTOR_SIZE` and **assumes 2 MB flash** — change that if a board has different flash size (`eeprom_flash.cpp`). Writes disable interrupts around `flash_range_erase`/ `flash_range_program`. + +4. **USB SD-host drive (MSC) is TinyUSB-based and off by default on RP2040.** `raspberrypi.ini` compiles with `-DNO_SD_HOST_DRIVE`; `HAS_SD_HOST_DRIVE` / `msc_sd.cpp` (TinyUSB `tud_msc_*_cb` callbacks) is only active if you drop that flag (see the commented `#custom_marlin.HAS_SD_HOST_DRIVE = tinyusb` line). The USB connect/disconnect toggle in `HAL.cpp` (`USB_CONNECT_PIN`) exists only when that pin is defined. + +5. **`freeMemory()` relies on a linker symbol, not a stack probe.** It computes `(char*)&__StackLimit - (char*)_sbrk(0)` using the Pico linker-provided `__StackLimit` (`HAL.cpp`). Don't "fix" it with a local-variable subtraction as on single-stack chips — that is wrong on this core. + +6. **`flashFirmware()` (M997) reboots into the bootloader via the watchdog.** `flashFirmware()` → `hal.reboot()` → `watchdog_reboot(0,0,1)` (`HAL.cpp`). The `raspberrypi.ini` build flags include `-DPLATFORM_M997_SUPPORT` (also forced on in `HAL.h`). A UF2/picotool upload picks up the reset. + +7. **SPI uses the core's `` directly; SOFTWARE_SPI path also exists.** `HAL_SPI.cpp` includes `` and calls `spiBegin()`; a software-SPI fallback is compiled when `SOFTWARE_SPI` is enabled. There is no custom `SPI` global shim needed (unlike AT32). `spi_pins.h` pins live in `HAL/RP2040/`. + +## Conventions + +- All HAL source is guarded by `#ifdef __PLAT_RP2040__` (defined by `ini/raspberrypi.ini`, not by the core). +- Keep HAL changes in `Marlin/src/HAL/RP2040/` and board pin changes in `Marlin/src/pins/rp2040/`. Build/wrapper glue for the upstream core goes in `ini/raspberrypi.ini` + `buildroot/share/PlatformIO/` (no vendored framework to mirror). +- The framework is upstream Arduino-Pico (Pico SDK + TinyUSB): do not vendor or patch it in-repo for Marlin; if a core fix is needed, report upstream or keep it in the HAL. +- ADC is continuous on Core 1; temperature updates are driven from Core 0's `Temperature` ISR reading `adc_values[]`. Treat the ADC plumbing as a dual-core contract. + +## Related + +- `README.md` in this folder — overview and supported boards. +- Wiki: [[hal-rp2040]], [[marlin-src-hal]]. +- `Marlin/src/HAL/shared/` — shared HAL APIs (`eeprom_api`, SPI helpers) the RP2040 HAL reuses. +- Upstream: . diff --git a/Marlin/src/HAL/RP2040/HAL.cpp b/Marlin/src/HAL/RP2040/HAL.cpp new file mode 100644 index 000000000000..51f68648bc7d --- /dev/null +++ b/Marlin/src/HAL/RP2040/HAL.cpp @@ -0,0 +1,298 @@ +/** + * 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 . + * + */ +#include "../platforms.h" + +#ifdef __PLAT_RP2040__ + +#include "HAL.h" +//#include "usb_serial.h" + +#include "../../inc/MarlinConfig.h" +#include "../shared/Delay.h" +#include "../../module/temperature.h" // For OVERSAMPLENR + +extern "C" { + #include "pico/bootrom.h" + #include "hardware/watchdog.h" + #include "pico/multicore.h" + #include "hardware/adc.h" + #include "pico/time.h" +} + +#if HAS_SD_HOST_DRIVE + #include "msc_sd.h" +#endif + +// ------------------------ +// Public Variables +// ------------------------ + +volatile uint32_t adc_accumulators[5] = {0}; // Accumulators for oversampling (sum of readings) +volatile uint8_t adc_counts[5] = {0}; // Count of readings accumulated per channel +volatile uint16_t adc_values[5] = {4095, 4095, 4095, 4095, 4095}; // Averaged ADC values (single reading equivalent) - initialized to max (open circuit) + +// Core monitoring for watchdog +volatile uint32_t core0_last_heartbeat = 0; // Timestamp of Core 0's last activity +volatile uint32_t core1_last_heartbeat = 0; // Timestamp of Core 1's last activity +#if ENABLED(MARLIN_DEV_MODE) + volatile bool core1_freeze_test = false; // Flag to freeze Core 1 for watchdog testing +#endif +volatile uint8_t current_pin; +volatile bool MarlinHAL::adc_has_result; +volatile uint8_t adc_channels_enabled[5] = {false}; // Track which ADC channels are enabled + +// Core 1 ADC reading task - dynamically reads all enabled channels with oversampling +void core1_adc_task() { + static uint32_t last_temp_update = 0; + + while (true) { + #if ENABLED(MARLIN_DEV_MODE) + // Check if we should freeze for watchdog test + if (core1_freeze_test) { + // Stop updating heartbeat and spin forever + while (core1_freeze_test) { + busy_wait_us(100000); // 100ms delay + } + } + #endif + + // Scan all enabled ADC channels + for (uint8_t channel = 0; channel < 5; channel++) { + if (!adc_channels_enabled[channel]) continue; + + // Enable temperature sensor if reading channel 4 + if (channel == 4) { + adc_set_temp_sensor_enabled(true); + } + + // Select and read the channel + adc_select_input(channel); + busy_wait_us(100); // Settling delay + adc_fifo_drain(); + adc_run(true); + + // Wait for conversion with timeout + uint32_t timeout = 10000; + while (adc_fifo_is_empty() && timeout--) { + busy_wait_us(1); + } + + adc_run(false); + uint16_t reading = adc_fifo_is_empty() ? 0 : adc_fifo_get(); + + // Accumulate readings for oversampling + adc_accumulators[channel] += reading; + adc_counts[channel]++; + + // When we reach the full oversampling count, calculate averaged value (Marlin ISR does its own oversampling) + if (adc_counts[channel] >= OVERSAMPLENR) { + adc_values[channel] = adc_accumulators[channel] / OVERSAMPLENR; // Return single-reading equivalent + adc_accumulators[channel] = 0; + adc_counts[channel] = 0; + } + + // Disable temp sensor after reading to save power + if (channel == 4) { + adc_set_temp_sensor_enabled(false); + } + } + + // Core 1 just provides ADC readings - don't trigger temperature updates from here + // Let Marlin's main temperature ISR on Core 0 handle the timing and updates + uint32_t now = time_us_32(); + if (now - last_temp_update >= 100000) { // 100ms = 100000 microseconds + last_temp_update = now; + #if ENABLED(USE_WATCHDOG) + // Refresh watchdog here like AVR ISR does indirectly via temperature updates + // Use 2 second delay to allow watchdog_init to be called during boot + static uint32_t core1_start_time = 0; + if (core1_start_time == 0) core1_start_time = time_us_32(); + + if (time_us_32() - core1_start_time > 2000000) { + hal.watchdog_refresh(1); // Refresh from Core 1 + } + #endif + } + + // Delay between full scan cycles + busy_wait_us(10000); // 10ms between scans + } +} + +volatile uint16_t adc_result; + +// ------------------------ +// Public functions +// ------------------------ + +TERN_(POSTMORTEM_DEBUGGING, extern void install_min_serial()); + +// HAL initialization task +void MarlinHAL::init() { + // Ensure F_CPU is a constant expression. + // If the compiler breaks here, it means that delay code that should compute at compile time will not work. + // So better safe than sorry here. + constexpr unsigned int cpuFreq = F_CPU; + UNUSED(cpuFreq); + + #if HAS_MEDIA && DISABLED(ONBOARD_SDIO) && PIN_EXISTS(SD_SS) + OUT_WRITE(SD_SS_PIN, HIGH); // Try to set SD_SS_PIN inactive before any other SPI users start up + #endif + + #if PIN_EXISTS(LED) + OUT_WRITE(LED_PIN, LOW); + #endif + + #if ENABLED(SRAM_EEPROM_EMULATION) + // __HAL_RCC_PWR_CLK_ENABLE(); + // HAL_PWR_EnableBkUpAccess(); // Enable access to backup SRAM + // __HAL_RCC_BKPSRAM_CLK_ENABLE(); + // LL_PWR_EnableBkUpRegulator(); // Enable backup regulator + // while (!LL_PWR_IsActiveFlag_BRR()); // Wait until backup regulator is initialized + #endif + + HAL_timer_init(); + + #if ALL(EMERGENCY_PARSER, USBD_USE_CDC) + USB_Hook_init(); + #endif + + TERN_(POSTMORTEM_DEBUGGING, install_min_serial()); // Install the min serial handler + + TERN_(HAS_SD_HOST_DRIVE, MSC_SD_init()); // Enable USB SD card access + + #if PIN_EXISTS(USB_CONNECT) + OUT_WRITE(USB_CONNECT_PIN, !USB_CONNECT_INVERTING); // USB clear connection + delay_ms(1000); // Give OS time to notice + WRITE(USB_CONNECT_PIN, USB_CONNECT_INVERTING); + #endif +} + +uint8_t MarlinHAL::get_reset_source() { + return watchdog_enable_caused_reboot() ? RST_WATCHDOG : 0; +} + +void MarlinHAL::reboot() { watchdog_reboot(0, 0, 1); } + +// ------------------------ +// Watchdog Timer +// ------------------------ + +#if ENABLED(USE_WATCHDOG) + + #define WDT_TIMEOUT_US TERN(WATCHDOG_DURATION_8S, 8000000, 4000000) // 4 or 8 second timeout + + extern "C" { + #include "hardware/watchdog.h" + } + + void MarlinHAL::watchdog_init() { + #if DISABLED(DISABLE_WATCHDOG_INIT) + static_assert(WDT_TIMEOUT_US > 1000, "WDT Timeout is too small, aborting"); + // Initialize Core 0 heartbeat + core0_last_heartbeat = time_us_32(); + watchdog_enable(WDT_TIMEOUT_US/1000, true); + #endif + } + + void MarlinHAL::watchdog_refresh(const uint8_t core/*=0*/) { + if (core == 0) { + // Update Core 0 heartbeat + core0_last_heartbeat = time_us_32(); + + // Check if Core 1 is alive (2 second timeout) + if (time_us_32() - core1_last_heartbeat < 2000000) { + watchdog_update(); // Only refresh if Core 1 is responding + #if DISABLED(PINS_DEBUGGING) && PIN_EXISTS(LED) + TOGGLE(LED_PIN); // Heartbeat indicator + #endif + } + // If Core 1 is stuck, don't refresh - let watchdog reset the system + } + else { + // Update Core 1 heartbeat + core1_last_heartbeat = time_us_32(); + + // Check if Core 0 is alive (2 second timeout) + if (time_us_32() - core0_last_heartbeat < 2000000) { + watchdog_update(); // Only refresh if Core 0 is responding + #if DISABLED(PINS_DEBUGGING) && PIN_EXISTS(LED) + TOGGLE(LED_PIN); // Heartbeat indicator + #endif + } + // If Core 0 is stuck, don't refresh - let watchdog reset the system + } + } + +#endif // USE_WATCHDOG + +// ------------------------ +// ADC +// ------------------------ + +void MarlinHAL::adc_init() { + analogReadResolution(HAL_ADC_RESOLUTION); + ::adc_init(); + adc_fifo_setup(true, false, 1, false, false); + // Launch Core 1 for continuous ADC reading + multicore_launch_core1(core1_adc_task); + adc_has_result = true; // Results are always available with continuous sampling +} + +void MarlinHAL::adc_enable(const pin_t pin) { + if (pin >= A0 && pin <= A3) { + adc_gpio_init(pin); + adc_channels_enabled[pin - A0] = true; // Mark this channel as enabled + } + else if (pin == HAL_ADC_MCU_TEMP_DUMMY_PIN) { + adc_channels_enabled[4] = true; // Mark MCU temp channel as enabled + } +} + +void MarlinHAL::adc_start(const pin_t pin) { + // Just store which pin we need to read - values are continuously updated by Core 1 + current_pin = pin; +} + +uint16_t MarlinHAL::adc_value() { + // Return the latest ADC value from Core 1's continuous readings + const uint8_t channel = (current_pin == HAL_ADC_MCU_TEMP_DUMMY_PIN) ? 4 : (current_pin - A0); + return adc_values[channel]; +} + +// Reset the system to initiate a firmware flash +void flashFirmware(const int16_t) { hal.reboot(); } + +extern "C" { + void * _sbrk(int incr); + extern unsigned int __StackLimit; // Lowest address the stack can grow to +} + +// Return free memory between end of heap and start of stack +int freeMemory() { + void* heap_end = _sbrk(0); + // Use the linker-provided stack limit instead of a local variable + // __StackLimit is the lowest address the stack can grow to + return (char*)&__StackLimit - (char*)heap_end; +} + +#endif // __PLAT_RP2040__ diff --git a/Marlin/src/HAL/RP2040/HAL.h b/Marlin/src/HAL/RP2040/HAL.h new file mode 100644 index 000000000000..a49b343899fd --- /dev/null +++ b/Marlin/src/HAL/RP2040/HAL.h @@ -0,0 +1,215 @@ +/** + * 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 . + * + */ +#pragma once + +#define CPU_32_BIT + +#ifndef F_CPU + #define F_CPU (XOSC_MHZ * 1000000UL) +#endif + +#include "arduino_extras.h" +#include "../../core/macros.h" +#include "../shared/math_32bit.h" +#include "../shared/HAL_SPI.h" +#include "fastio.h" +#include "watchdog.h" + +#include "../../inc/MarlinConfigPre.h" + +#if HAS_SD_HOST_DRIVE + #include "msc_sd.h" +#endif + +// ADC index 4 is the MCU temperature +#define HAL_ADC_MCU_TEMP_DUMMY_PIN 127 +#define TEMP_SOC_PIN HAL_ADC_MCU_TEMP_DUMMY_PIN // ADC4 is internal temp sensor +#include "temp_soc.h" + +// +// Serial Ports +// + +#include "MarlinSerial.h" + +#if !WITHIN(SERIAL_PORT, -1, 1) + #error "SERIAL_PORT must be from -1 to 1." +#endif + +#ifdef SERIAL_PORT_2 + #if !WITHIN(SERIAL_PORT_2, -1, 1) + #error "SERIAL_PORT_2 must be from -1 to 1." + #endif +#endif + +#ifdef SERIAL_PORT_3 + #if !WITHIN(SERIAL_PORT_3, -1, 1) + #error "SERIAL_PORT_3 must be from -1 to 1." + #endif +#endif + +#ifdef LCD_SERIAL_PORT + #if !WITHIN(LCD_SERIAL_PORT, -1, 1) + #error "LCD_SERIAL_PORT must be from -1 to 1." + #endif +#endif + +// ------------------------ +// Defines +// ------------------------ + +/** + * TODO: review this to return 1 for pins that are not analog input + */ +#ifndef analogInputToDigitalPin + #define analogInputToDigitalPin(p) (p) +#endif + +#define CRITICAL_SECTION_START() uint32_t primask = __get_PRIMASK(); __disable_irq() +#define CRITICAL_SECTION_END() if (!primask) __enable_irq() +#define cli() __disable_irq() +#define sei() __enable_irq() + +// ------------------------ +// Types +// ------------------------ + +template struct IFPIN { typedef R type; }; +template struct IFPIN { typedef L type; }; +typedef IFPIN::type pin_t; + +class libServo; +typedef libServo hal_servo_t; +#define PAUSE_SERVO_OUTPUT() libServo::pause_all_servos() +#define RESUME_SERVO_OUTPUT() libServo::resume_all_servos() + +// ------------------------ +// ADC +// ------------------------ + +#define HAL_ADC_VREF 3.3 +#ifdef ADC_RESOLUTION + #define HAL_ADC_RESOLUTION ADC_RESOLUTION +#else + #define HAL_ADC_RESOLUTION 12 +#endif + +// +// Pin Mapping for M42, M43, M226 +// +#define GET_PIN_MAP_PIN(index) index +#define GET_PIN_MAP_INDEX(pin) pin +#define PARSED_PIN_INDEX(code, dval) parser.intval(code, dval) + +#ifndef PLATFORM_M997_SUPPORT + #define PLATFORM_M997_SUPPORT +#endif +void flashFirmware(const int16_t); + +// Maple Compatibility +typedef void (*systickCallback_t)(void); +void systick_attach_callback(systickCallback_t cb); +void HAL_SYSTICK_Callback(); + +extern volatile uint32_t systick_uptime_millis; + +#define HAL_CAN_SET_PWM_FREQ // This HAL supports PWM Frequency adjustment +#define PWM_FREQUENCY 1000 // Default PWM frequency when set_pwm_duty() is called without set_pwm_frequency() + +// ------------------------ +// Class Utilities +// ------------------------ + +int freeMemory(); + +// ------------------------ +// MarlinHAL Class +// ------------------------ + +class MarlinHAL { +public: + + // Earliest possible init, before setup() + MarlinHAL() {} + + // Watchdog + static void watchdog_init() IF_DISABLED(USE_WATCHDOG, {}); + static void watchdog_refresh(const uint8_t=0) IF_DISABLED(USE_WATCHDOG, {}); + + static void init(); // Called early in setup() + static void init_board() {} // Called less early in setup() + static void reboot(); // Restart the firmware from 0x0 + + // Interrupts + static bool isr_state() { return !__get_PRIMASK(); } + static void isr_on() { __enable_irq(); } + static void isr_off() { __disable_irq(); } + + static void delay_ms(const int ms) { delay(ms); } + + // Tasks, called from marlin.idle() + static void idletask() { TERN_(HAS_SD_HOST_DRIVE, tuh_task()); } + + // Reset + static uint8_t get_reset_source(); + static void clear_reset_source() {} + + // Free SRAM + static int freeMemory() { return ::freeMemory(); } + + // + // ADC Methods + // + + // Called by Temperature::init once at startup + static void adc_init(); + + // Called by Temperature::init for each sensor at startup + static void adc_enable(const pin_t pin); + + // Begin ADC sampling on the given pin. Called from Temperature::isr! + static void adc_start(const pin_t pin); + + // Is the ADC ready for reading? + static volatile bool adc_has_result; + static bool adc_ready() { return adc_has_result; } + + // The current value of the ADC register + static uint16_t adc_value(); + + /** + * Set the PWM duty cycle for the pin to the given value. + * Optionally invert the duty cycle [default = false] + * Optionally change the scale of the provided value to enable finer PWM duty control [default = 255] + */ + static void set_pwm_duty(const pin_t pin, const uint16_t v, const uint16_t v_size=255, const bool invert=false); + + /** + * Set the frequency of the timer for the given pin as close as + * possible to the provided desired frequency. Internally calculate + * the required waveform generation mode, prescaler, and resolution + * values and set timer registers accordingly. + * NOTE that the frequency is applied to all pins on the timer (Ex OC3A, OC3B and OC3B) + * NOTE that there are limitations, particularly if using TIMER2. (see Configuration_adv.h -> FAST_PWM_FAN Settings) + */ + static void set_pwm_frequency(const pin_t pin, const uint16_t f_desired); +}; diff --git a/Marlin/src/HAL/RP2040/HAL_MinSerial.cpp b/Marlin/src/HAL/RP2040/HAL_MinSerial.cpp new file mode 100644 index 000000000000..dc332053bc5f --- /dev/null +++ b/Marlin/src/HAL/RP2040/HAL_MinSerial.cpp @@ -0,0 +1,68 @@ +/** + * 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 . + * + */ +#include "../platforms.h" + +#ifdef __PLAT_RP2040__ + +#include "../../inc/MarlinConfigPre.h" + +#if ENABLED(POSTMORTEM_DEBUGGING) + +#include "../shared/HAL_MinSerial.h" + +static void TXBegin() { + #if !WITHIN(SERIAL_PORT, -1, 2) + #warning "Using POSTMORTEM_DEBUGGING requires a physical U(S)ART hardware in case of severe error." + #warning "Disabling the severe error reporting feature currently because the used serial port is not a HW port." + #else + #if SERIAL_PORT == -1 + USBSerial.begin(BAUDRATE); + #elif SERIAL_PORT == 0 + USBSerial.begin(BAUDRATE); + #elif SERIAL_PORT == 1 + Serial1.begin(BAUDRATE); + #endif + #endif +} + +static void TX(char b) { + #if SERIAL_PORT == -1 + USBSerial + #elif SERIAL_PORT == 0 + USBSerial + #elif SERIAL_PORT == 1 + Serial1 + #endif + .write(b); +} + +// A SW memory barrier, to ensure GCC does not overoptimize loops +#define sw_barrier() __asm__ volatile("": : :"memory"); + + +void install_min_serial() { + HAL_min_serial_init = &TXBegin; + HAL_min_serial_out = &TX; +} + +#endif // POSTMORTEM_DEBUGGING +#endif // __PLAT_RP2040__ diff --git a/Marlin/src/HAL/RP2040/HAL_SPI.cpp b/Marlin/src/HAL/RP2040/HAL_SPI.cpp new file mode 100644 index 000000000000..c88b6d1e5b03 --- /dev/null +++ b/Marlin/src/HAL/RP2040/HAL_SPI.cpp @@ -0,0 +1,228 @@ +/** + * 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 . + * + */ +#include "../platforms.h" + +#ifdef __PLAT_RP2040__ + +#include "../../inc/MarlinConfig.h" + +#include + +// ------------------------ +// Public Variables +// ------------------------ + +static SPISettings spiConfig; + +// ------------------------ +// Public functions +// ------------------------ + +#if ENABLED(SOFTWARE_SPI) + + // ------------------------ + // Software SPI + // ------------------------ + + #include "../shared/Delay.h" + + void spiBegin(void) { + OUT_WRITE(SD_SS_PIN, HIGH); + OUT_WRITE(SD_SCK_PIN, HIGH); + SET_INPUT(SD_MISO_PIN); + OUT_WRITE(SD_MOSI_PIN, HIGH); + } + + // Use function with compile-time value so we can actually reach the desired frequency + // Need to adjust this a little bit: on a 72MHz clock, we have 14ns/clock + // and we'll use ~3 cycles to jump to the method and going back, so it'll take ~40ns from the given clock here + #define CALLING_COST_NS (3U * 1000000000U) / (F_CPU) + void (*delaySPIFunc)(); + void delaySPI_125() { DELAY_NS(125 - CALLING_COST_NS); } + void delaySPI_250() { DELAY_NS(250 - CALLING_COST_NS); } + void delaySPI_500() { DELAY_NS(500 - CALLING_COST_NS); } + void delaySPI_1000() { DELAY_NS(1000 - CALLING_COST_NS); } + void delaySPI_2000() { DELAY_NS(2000 - CALLING_COST_NS); } + void delaySPI_4000() { DELAY_NS(4000 - CALLING_COST_NS); } + + void spiInit(uint8_t spiRate) { + // Use datarates Marlin uses + switch (spiRate) { + case SPI_FULL_SPEED: delaySPIFunc = &delaySPI_125; break; // desired: 8,000,000 actual: ~1.1M + case SPI_HALF_SPEED: delaySPIFunc = &delaySPI_125; break; // desired: 4,000,000 actual: ~1.1M + case SPI_QUARTER_SPEED:delaySPIFunc = &delaySPI_250; break; // desired: 2,000,000 actual: ~890K + case SPI_EIGHTH_SPEED: delaySPIFunc = &delaySPI_500; break; // desired: 1,000,000 actual: ~590K + case SPI_SPEED_5: delaySPIFunc = &delaySPI_1000; break; // desired: 500,000 actual: ~360K + case SPI_SPEED_6: delaySPIFunc = &delaySPI_2000; break; // desired: 250,000 actual: ~210K + default: delaySPIFunc = &delaySPI_4000; break; // desired: 125,000 actual: ~123K + } + SPI.begin(); + } + + // Begin SPI transaction, set clock, bit order, data mode + void spiBeginTransaction(uint32_t spiClock, uint8_t bitOrder, uint8_t dataMode) { /* do nothing */ } + + uint8_t HAL_SPI_RP2040_SpiTransfer_Mode_3(uint8_t b) { // using Mode 3 + for (uint8_t bits = 8; bits--;) { + WRITE(SD_SCK_PIN, LOW); + WRITE(SD_MOSI_PIN, b & 0x80); + + delaySPIFunc(); + WRITE(SD_SCK_PIN, HIGH); + delaySPIFunc(); + + b <<= 1; // little setup time + b |= (READ(SD_MISO_PIN) != 0); + } + DELAY_NS(125); + return b; + } + + // Soft SPI receive byte + uint8_t spiRec() { + hal.isr_off(); // No interrupts during byte receive + const uint8_t data = HAL_SPI_RP2040_SpiTransfer_Mode_3(0xFF); + hal.isr_on(); // Enable interrupts + return data; + } + + // Soft SPI read data + void spiRead(uint8_t *buf, uint16_t nbyte) { + for (uint16_t i = 0; i < nbyte; i++) + buf[i] = spiRec(); + } + + // Soft SPI send byte + void spiSend(uint8_t data) { + hal.isr_off(); // No interrupts during byte send + HAL_SPI_RP2040_SpiTransfer_Mode_3(data); // Don't care what is received + hal.isr_on(); // Enable interrupts + } + + // Soft SPI send block + void spiSendBlock(uint8_t token, const uint8_t *buf) { + spiSend(token); + for (uint16_t i = 0; i < 512; i++) + spiSend(buf[i]); + } + +#else + + // ------------------------ + // Hardware SPI + // ------------------------ + + /** + * VGPV SPI speed start and PCLK2/2, by default 108/2 = 54Mhz + */ + + /** + * @brief Begin SPI port setup + * + * @return Nothing + * + * @details Only configures SS pin since stm32duino creates and initialize the SPI object + */ + void spiBegin() { + #if PIN_EXISTS(SD_SS) + OUT_WRITE(SD_SS_PIN, HIGH); + #endif + } + + // Configure SPI for specified SPI speed + void spiInit(uint8_t spiRate) { + // Use datarates Marlin uses + uint32_t clock; + switch (spiRate) { + case SPI_FULL_SPEED: clock = 20000000; break; // 13.9mhz=20000000 6.75mhz=10000000 3.38mhz=5000000 .833mhz=1000000 + case SPI_HALF_SPEED: clock = 5000000; break; + case SPI_QUARTER_SPEED: clock = 2500000; break; + case SPI_EIGHTH_SPEED: clock = 1250000; break; + case SPI_SPEED_5: clock = 625000; break; + case SPI_SPEED_6: clock = 300000; break; + default: + clock = 4000000; // Default from the SPI library + } + spiConfig = SPISettings(clock, MSBFIRST, SPI_MODE0); + + //SPI.setMISO(SD_MISO_PIN); //todo: implement? bad interface + //SPI.setMOSI(SD_MOSI_PIN); + //SPI.setSCLK(SD_SCK_PIN); + + SPI.begin(); + } + + /** + * @brief Receives a single byte from the SPI port. + * + * @return Byte received + * + * @details + */ + uint8_t spiRec() { + uint8_t returnByte = SPI.transfer(0xFF); + return returnByte; + } + + /** + * @brief Receive a number of bytes from the SPI port to a buffer + * + * @param buf Pointer to starting address of buffer to write to. + * @param nbyte Number of bytes to receive. + * @return Nothing + * + * @details Uses DMA + */ + void spiRead(uint8_t *buf, uint16_t nbyte) { + if (nbyte == 0) return; + memset(buf, 0xFF, nbyte); + SPI.transfer(buf, nbyte); + } + + /** + * @brief Send a single byte on SPI port + * + * @param b Byte to send + * + * @details + */ + void spiSend(uint8_t b) { + SPI.transfer(b); + } + + /** + * @brief Write token and then write from 512 byte buffer to SPI (for SD card) + * + * @param buf Pointer with buffer start address + * @return Nothing + * + * @details Use DMA + */ + void spiSendBlock(uint8_t token, const uint8_t *buf) { + //uint8_t rxBuf[512]; + //SPI.transfer(token); + SPI.transfer((uint8_t*)buf, 512); //implement? bad interface + } + +#endif // SOFTWARE_SPI + +#endif // __PLAT_RP2040__ diff --git a/Marlin/src/HAL/RP2040/MarlinSerial.cpp b/Marlin/src/HAL/RP2040/MarlinSerial.cpp new file mode 100644 index 000000000000..5e3bf0c148fb --- /dev/null +++ b/Marlin/src/HAL/RP2040/MarlinSerial.cpp @@ -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 . + * + */ +#include "../platforms.h" + +#ifdef __PLAT_RP2040__ + +#include "../../inc/MarlinConfig.h" +#include "MarlinSerial.h" + +#if ENABLED(EMERGENCY_PARSER) + #include "../../feature/e_parser.h" +#endif + +#include + +// Marlin uses: -1=USB, 0=UART0, 1=UART1 +// Arduino uses: Serial=USB, Serial1=UART0, Serial2=UART1 +// +// To remap Arduino's numbering to Marlin's convention, we create MarlinSerial0/MarlinSerial1 +// as new UART instances with custom pins. +// +// We use distinct names (MarlinSerial0/MarlinSerial1) to avoid symbol conflicts with +// the Arduino framework's pre-defined Serial1/Serial2 objects, which use the same +// underlying hardware (_UART0_ and _UART1_). + +// Create Serial0 as UART0 with custom or default pins +arduino::UART MarlinSerial0( + #if PINS_EXIST(SERIAL0_TX, SERIAL0_RX) + SERIAL0_TX_PIN, SERIAL0_RX_PIN // Custom pins for UART0 (Marlin Serial0) + #else + 0, 1 // Default UART0 pins (GP0/GP1) + #endif +); + +// Not using PINS_EXIST(SERIAL1_TX, SERIAL1_RX) because SERIAL1_TX and SERIAL1_RX +// are defined in framework-arduino-mbed/variants/RASPBERRY_PI_PICO/pins_arduino.h + +// Create Serial1 as UART1 with custom or default pins +#if defined(SERIAL1_TX_PIN) && defined(SERIAL1_RX_PIN) + arduino::UART MarlinSerial1(SERIAL1_TX_PIN, SERIAL1_RX_PIN); // Custom pins for UART1 (Marlin Serial1) +#endif + +// Wrap the serial ports for Marlin +DefaultSerial0 MSerial0(false, MarlinSerial0); // Marlin Serial0 = UART0 +#if defined(SERIAL1_TX_PIN) && defined(SERIAL1_RX_PIN) + DefaultSerial1 MSerial1(false, MarlinSerial1); // Marlin Serial1 = UART1 +#endif +DefaultSerial2 MSerial2(false, Serial); // Marlin Serial2 = USB (-1) + +#endif // __PLAT_RP2040__ diff --git a/Marlin/src/HAL/RP2040/MarlinSerial.h b/Marlin/src/HAL/RP2040/MarlinSerial.h new file mode 100644 index 000000000000..f407a838dfc5 --- /dev/null +++ b/Marlin/src/HAL/RP2040/MarlinSerial.h @@ -0,0 +1,80 @@ +/** + * 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 . + * + */ +#pragma once + +#include "../../inc/MarlinConfigPre.h" + +#if ENABLED(EMERGENCY_PARSER) + #include "../../feature/e_parser.h" +#endif + +#include "../../core/serial_hook.h" + +/** + * Serial Port Configuration for RP2040 (Raspberry Pi Pico) + * + * Arduino-Pico Core Serial Objects: + * - Serial: USB Serial (CDC ACM) + * - Serial1: Hardware UART0 + * - Serial2: Hardware UART1 + * - SerialUSB: Alias for Serial (USB) + * + * Marlin Serial Wrappers: + * - MSerial0: Wrapper for MarlinSerial0 (UART0), used as Serial0 + * - MSerial1: Wrapper for MarlinSerial1 (UART1), declared dynamically if used + * - MSerial2: Wrapper for Serial (USB) + * - USBSerial: Wrapper for SerialUSB (USB) + * + * How it all joins together: + * - Configuration defines SERIAL_PORT, SERIAL_PORT_2, etc. (-1 to 1 range) + * - shared/serial_ports.h maps these to MYSERIAL1, MYSERIAL2, etc. + * - MYSERIAL1 uses MSerialX based on the port index + * - USB ports (-1) use USB_SERIAL_PORT (MSerial2) + */ + +// Forward declare our custom Serial objects (defined in MarlinSerial.cpp) +namespace arduino { class UART; } +extern arduino::UART MarlinSerial0; // Always declared +extern arduino::UART MarlinSerial1; // Custom Marlin Serial1 to avoid conflict + +typedef ForwardSerial1Class< decltype(MarlinSerial0) > DefaultSerial0; +extern DefaultSerial0 MSerial0; +typedef ForwardSerial1Class< decltype(MarlinSerial1) > DefaultSerial1; +extern DefaultSerial1 MSerial1; +typedef ForwardSerial1Class< decltype(Serial) > DefaultSerial2; +extern DefaultSerial2 MSerial2; +typedef ForwardSerial1Class USBSerialType; +extern USBSerialType USBSerial; + +#define _DECLARE_SERIAL(X) \ + typedef ForwardSerial1Class DefaultSerial##X; \ + extern DefaultSerial##X MSerial##X +#define DECLARE_SERIAL(X) _DECLARE_SERIAL(X) + +#define SERIAL_INDEX_MIN 0 +#define SERIAL_INDEX_MAX 1 +#define USB_SERIAL_PORT(...) MSerial2 +#include "../shared/serial_ports.h" + +#if defined(LCD_SERIAL_PORT) && ANY(HAS_DGUS_LCD, EXTENSIBLE_UI) + #define SERIAL_GET_TX_BUFFER_FREE() LCD_SERIAL.availableForWrite() +#endif diff --git a/Marlin/src/HAL/RP2040/README.md b/Marlin/src/HAL/RP2040/README.md new file mode 100644 index 000000000000..4f9f70b8c9e4 --- /dev/null +++ b/Marlin/src/HAL/RP2040/README.md @@ -0,0 +1 @@ +# RP2040 Hardware Interface diff --git a/Marlin/src/HAL/RP2040/Servo.cpp b/Marlin/src/HAL/RP2040/Servo.cpp new file mode 100644 index 000000000000..2b1b2a1744b6 --- /dev/null +++ b/Marlin/src/HAL/RP2040/Servo.cpp @@ -0,0 +1,93 @@ +/** + * 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 . + * + */ +#include "../platforms.h" + +#ifdef __PLAT_RP2040__ + +#include "../../inc/MarlinConfig.h" + +#if HAS_SERVOS + +#include "Servo.h" + +static uint_fast8_t servoCount = 0; +static libServo *servos[NUM_SERVOS] = {0}; +constexpr millis_t servoDelay[] = SERVO_DELAY; +static_assert(COUNT(servoDelay) == NUM_SERVOS, "SERVO_DELAY must be an array NUM_SERVOS long."); + +libServo::libServo() +: delay(servoDelay[servoCount]), + was_attached_before_pause(false), + value_before_pause(0) +{ + servos[servoCount++] = this; +} + +int8_t libServo::attach(const int pin) { + if (servoCount >= MAX_SERVOS) return -1; + if (pin > 0) servo_pin = pin; + auto result = pico_servo.attach(servo_pin); + return result; +} + +int8_t libServo::attach(const int pin, const int min, const int max) { + if (servoCount >= MAX_SERVOS) return -1; + if (pin > 0) servo_pin = pin; + auto result = pico_servo.attach(servo_pin, min, max); + return result; +} + +void libServo::move(const int value) { + if (attach(0) >= 0) { + pico_servo.write(value); + safe_delay(delay); + TERN_(DEACTIVATE_SERVOS_AFTER_MOVE, detach()); + } +} + +void libServo::pause() { + was_attached_before_pause = pico_servo.attached(); + if (was_attached_before_pause) { + value_before_pause = pico_servo.read(); + pico_servo.detach(); + } +} + +void libServo::resume() { + if (was_attached_before_pause) { + attach(); + move(value_before_pause); + } +} + +void libServo::pause_all_servos() { + for (auto& servo : servos) + if (servo) servo->pause(); +} + +void libServo::resume_all_servos() { + for (auto& servo : servos) + if (servo) servo->resume(); +} + +#endif // HAS_SERVOS +#endif // __PLAT_RP2040__ diff --git a/Marlin/src/HAL/RP2040/Servo.h b/Marlin/src/HAL/RP2040/Servo.h new file mode 100644 index 000000000000..031610fd1df1 --- /dev/null +++ b/Marlin/src/HAL/RP2040/Servo.h @@ -0,0 +1,77 @@ +/** + * 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 . + * + */ +#pragma once + +#include + +#if 1 + +#include "../../core/millis_t.h" + +// Inherit and expand on the official library +class libServo { + public: + libServo(); + int8_t attach(const int pin = 0); // pin == 0 uses value from previous call + int8_t attach(const int pin, const int min, const int max); + void detach() { pico_servo.detach(); } + int read() { return pico_servo.read(); } + void move(const int value); + + void pause(); + void resume(); + + static void pause_all_servos(); + static void resume_all_servos(); + static void setInterruptPriority(uint32_t preemptPriority, uint32_t subPriority); + + private: + Servo pico_servo; + + int servo_pin = 0; + millis_t delay = 0; + + bool was_attached_before_pause; + int value_before_pause; +}; + +#else + +class libServo: public Servo { + public: + void move(const int value) { + constexpr uint16_t servo_delay[] = SERVO_DELAY; + static_assert(COUNT(servo_delay) == NUM_SERVOS, "SERVO_DELAY must be an array NUM_SERVOS long."); + + if (attach(servo_info[servoIndex].Pin.nbr) >= 0) { // try to reattach + write(value); + safe_delay(servo_delay[servoIndex]); // delay to allow servo to reach position + TERN_(DEACTIVATE_SERVOS_AFTER_MOVE, detach()); + } + + } +}; + +class libServo; +typedef libServo hal_servo_t; + +#endif diff --git a/Marlin/src/HAL/RP2040/arduino_extras.cpp b/Marlin/src/HAL/RP2040/arduino_extras.cpp new file mode 100644 index 000000000000..cdc0a0abf2ef --- /dev/null +++ b/Marlin/src/HAL/RP2040/arduino_extras.cpp @@ -0,0 +1,33 @@ +/** + * 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 . + * + */ +#ifdef __PLAT_RP2040__ + +#include + +char *dtostrf(double __val, signed char __width, unsigned char __prec, char *__s) { + char format_string[20]; + snprintf(format_string, 20, "%%%d.%df", __width, __prec); + sprintf(__s, format_string, __val); + return __s; +} + +#endif // __PLAT_RP2040__ diff --git a/Marlin/src/HAL/RP2040/arduino_extras.h b/Marlin/src/HAL/RP2040/arduino_extras.h new file mode 100644 index 000000000000..9794140212a6 --- /dev/null +++ b/Marlin/src/HAL/RP2040/arduino_extras.h @@ -0,0 +1,29 @@ +/** + * 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 . + * + */ +#pragma once + +// #include +// #include +// #include +// #include + +char *dtostrf(double __val, signed char __width, unsigned char __prec, char *__s); diff --git a/Marlin/src/HAL/RP2040/docs/rp2040-datasheet.pdf b/Marlin/src/HAL/RP2040/docs/rp2040-datasheet.pdf new file mode 100644 index 000000000000..4c5c9db2e313 Binary files /dev/null and b/Marlin/src/HAL/RP2040/docs/rp2040-datasheet.pdf differ diff --git a/Marlin/src/HAL/RP2040/eeprom/eeprom_flash.cpp b/Marlin/src/HAL/RP2040/eeprom/eeprom_flash.cpp new file mode 100644 index 000000000000..bf52109173f7 --- /dev/null +++ b/Marlin/src/HAL/RP2040/eeprom/eeprom_flash.cpp @@ -0,0 +1,108 @@ +/** + * 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 . + * + */ +#include "../../platforms.h" + +#ifdef __PLAT_RP2040__ + +#include "../../../inc/MarlinConfig.h" + +#if ENABLED(FLASH_EEPROM_EMULATION) + +#include "../../shared/eeprom_api.h" + +// NOTE: The Bigtreetech SKR Pico has an onboard W25Q16 flash module + +// RP2040 Flash-based EEPROM emulation using internal flash memory +#include +#include + +// Flash sector size is already defined in hardware/flash.h as FLASH_SECTOR_SIZE +// Place EEPROM emulation at the end of flash, before the filesystem +// This assumes 2MB flash, adjust if using different flash size +#define FLASH_TARGET_OFFSET (PICO_FLASH_SIZE_BYTES - FLASH_SECTOR_SIZE) + +#ifndef MARLIN_EEPROM_SIZE + #define MARLIN_EEPROM_SIZE size_t(E2END + 1) +#endif + +static uint8_t eeprom_buffer[MARLIN_EEPROM_SIZE]; +static bool eeprom_data_written = false; +static bool eeprom_initialized = false; +size_t PersistentStore::capacity() { return MARLIN_EEPROM_SIZE; } + +bool PersistentStore::access_start() { + if (!eeprom_initialized) { + // Read from flash into buffer + const uint8_t *flash_data = (const uint8_t *)(XIP_BASE + FLASH_TARGET_OFFSET); + memcpy(eeprom_buffer, flash_data, MARLIN_EEPROM_SIZE); + eeprom_initialized = true; + } + return true; +} + +bool PersistentStore::access_finish() { + if (eeprom_data_written) { + TERN_(HAS_PAUSE_SERVO_OUTPUT, PAUSE_SERVO_OUTPUT()); + + // Disable interrupts during flash write + const uint32_t intstate = save_and_disable_interrupts(); + + // Erase and program the sector + flash_range_erase(FLASH_TARGET_OFFSET, FLASH_SECTOR_SIZE); + flash_range_program(FLASH_TARGET_OFFSET, eeprom_buffer, MARLIN_EEPROM_SIZE); + + // Restore interrupts + restore_interrupts(intstate); + + TERN_(HAS_PAUSE_SERVO_OUTPUT, RESUME_SERVO_OUTPUT()); + eeprom_data_written = false; + } + return true; +} + +bool PersistentStore::write_data(int &pos, const uint8_t *value, size_t size, uint16_t *crc) { + while (size--) { + uint8_t v = *value; + if (pos < (int)MARLIN_EEPROM_SIZE && v != eeprom_buffer[pos]) { + eeprom_buffer[pos] = v; + eeprom_data_written = true; + } + crc16(crc, &v, 1); + pos++; + value++; + } + return false; +} + +bool PersistentStore::read_data(int &pos, uint8_t *value, size_t size, uint16_t *crc, const bool writing/*=true*/) { + do { + const uint8_t c = (pos < (int)MARLIN_EEPROM_SIZE) ? eeprom_buffer[pos] : 0xFF; + if (writing) *value = c; + crc16(crc, &c, 1); + pos++; + value++; + } while (--size); + return false; +} + +#endif // FLASH_EEPROM_EMULATION +#endif // __PLAT_RP2040__ diff --git a/Marlin/src/HAL/RP2040/eeprom/eeprom_wired.cpp b/Marlin/src/HAL/RP2040/eeprom/eeprom_wired.cpp new file mode 100644 index 000000000000..7a5ca86c4ce5 --- /dev/null +++ b/Marlin/src/HAL/RP2040/eeprom/eeprom_wired.cpp @@ -0,0 +1,79 @@ +/** + * 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 . + * + */ +#include "../../platforms.h" + +#ifdef __PLAT_RP2040__ + +#include "../../../inc/MarlinConfig.h" + +#if USE_WIRED_EEPROM + +/** + * PersistentStore for Arduino-style EEPROM interface + * with simple implementations supplied by Marlin. + */ + +#include "../../shared/eeprom_if.h" +#include "../../shared/eeprom_api.h" + +#ifndef MARLIN_EEPROM_SIZE + #define MARLIN_EEPROM_SIZE size_t(E2END + 1) +#endif +size_t PersistentStore::capacity() { return MARLIN_EEPROM_SIZE; } + +bool PersistentStore::access_start() { eeprom_init(); return true; } +bool PersistentStore::access_finish() { return true; } + +bool PersistentStore::write_data(int &pos, const uint8_t *value, size_t size, uint16_t *crc) { + uint16_t written = 0; + while (size--) { + uint8_t v = *value; + uint8_t * const p = (uint8_t * const)pos; + if (v != eeprom_read_byte(p)) { // EEPROM has only ~100,000 write cycles, so only write bytes that have changed! + eeprom_write_byte(p, v); + if (++written & 0x7F) delay(2); else safe_delay(2); // Avoid triggering watchdog during long EEPROM writes + if (eeprom_read_byte(p) != v) { + SERIAL_ECHO_MSG(STR_ERR_EEPROM_WRITE); + return true; + } + } + crc16(crc, &v, 1); + pos++; + value++; + } + return false; +} + +bool PersistentStore::read_data(int &pos, uint8_t *value, size_t size, uint16_t *crc, const bool writing/*=true*/) { + do { + // Read from either external EEPROM, program flash or Backup SRAM + const uint8_t c = eeprom_read_byte((uint8_t*)pos); + if (writing) *value = c; + crc16(crc, &c, 1); + pos++; + value++; + } while (--size); + return false; +} + +#endif // USE_WIRED_EEPROM +#endif // __PLAT_RP2040__ diff --git a/Marlin/src/HAL/RP2040/endstop_interrupts.h b/Marlin/src/HAL/RP2040/endstop_interrupts.h new file mode 100644 index 000000000000..af538406b8c7 --- /dev/null +++ b/Marlin/src/HAL/RP2040/endstop_interrupts.h @@ -0,0 +1,60 @@ +/** + * 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 . + * + */ +#pragma once + +#include "../../module/endstops.h" + +// One ISR for all EXT-Interrupts +void endstop_ISR() { endstops.update(); } + +void setup_endstop_interrupts() { + #define _ATTACH(P) attachInterrupt(digitalPinToInterrupt(P), endstop_ISR, CHANGE) + TERN_(USE_X_MAX, _ATTACH(X_MAX_PIN)); + TERN_(USE_X_MIN, _ATTACH(X_MIN_PIN)); + TERN_(USE_Y_MAX, _ATTACH(Y_MAX_PIN)); + TERN_(USE_Y_MIN, _ATTACH(Y_MIN_PIN)); + TERN_(USE_Z_MAX, _ATTACH(Z_MAX_PIN)); + TERN_(HAS_Z_MIN_PIN, _ATTACH(Z_MIN_PIN)); + TERN_(USE_X2_MAX, _ATTACH(X2_MAX_PIN)); + TERN_(USE_X2_MIN, _ATTACH(X2_MIN_PIN)); + TERN_(USE_Y2_MAX, _ATTACH(Y2_MAX_PIN)); + TERN_(USE_Y2_MIN, _ATTACH(Y2_MIN_PIN)); + TERN_(USE_Z2_MAX, _ATTACH(Z2_MAX_PIN)); + TERN_(USE_Z2_MIN, _ATTACH(Z2_MIN_PIN)); + TERN_(USE_Z3_MAX, _ATTACH(Z3_MAX_PIN)); + TERN_(USE_Z3_MIN, _ATTACH(Z3_MIN_PIN)); + TERN_(USE_Z4_MAX, _ATTACH(Z4_MAX_PIN)); + TERN_(USE_Z4_MIN, _ATTACH(Z4_MIN_PIN)); + TERN_(USE_Z_MIN_PROBE_PIN, _ATTACH(Z_MIN_PROBE_PIN)); + TERN_(USE_I_MAX, _ATTACH(I_MAX_PIN)); + TERN_(USE_I_MIN, _ATTACH(I_MIN_PIN)); + TERN_(USE_J_MAX, _ATTACH(J_MAX_PIN)); + TERN_(USE_J_MIN, _ATTACH(J_MIN_PIN)); + TERN_(USE_K_MAX, _ATTACH(K_MAX_PIN)); + TERN_(USE_K_MIN, _ATTACH(K_MIN_PIN)); + TERN_(USE_U_MAX, _ATTACH(U_MAX_PIN)); + TERN_(USE_U_MIN, _ATTACH(U_MIN_PIN)); + TERN_(USE_V_MAX, _ATTACH(V_MAX_PIN)); + TERN_(USE_V_MIN, _ATTACH(V_MIN_PIN)); + TERN_(USE_W_MAX, _ATTACH(W_MAX_PIN)); + TERN_(USE_W_MIN, _ATTACH(W_MIN_PIN)); +} diff --git a/Marlin/src/HAL/RP2040/fast_pwm.cpp b/Marlin/src/HAL/RP2040/fast_pwm.cpp new file mode 100644 index 000000000000..8e932ffd7689 --- /dev/null +++ b/Marlin/src/HAL/RP2040/fast_pwm.cpp @@ -0,0 +1,43 @@ +/** + * 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 . + * + */ +#include "../platforms.h" + +#ifdef __PLAT_RP2040__ + +#include "../../inc/MarlinConfigPre.h" + +#include "HAL.h" +#include "pinDefinitions.h" + +void MarlinHAL::set_pwm_duty(const pin_t pin, const uint16_t v, const uint16_t v_size/*=255*/, const bool invert/*=false*/) { + analogWrite(pin, v); +} + +void MarlinHAL::set_pwm_frequency(const pin_t pin, const uint16_t f_desired) { + mbed::PwmOut* pwm = digitalPinToPwm(pin); + if (pwm != nullptr) delete pwm; + pwm = new mbed::PwmOut(digitalPinToPinName(pin)); + digitalPinToPwm(pin) = pwm; + pwm->period_ms(1000 / f_desired); +} + +#endif // __PLAT_RP2040__ diff --git a/Marlin/src/HAL/RP2040/fastio.cpp b/Marlin/src/HAL/RP2040/fastio.cpp new file mode 100644 index 000000000000..fa77106cef9d --- /dev/null +++ b/Marlin/src/HAL/RP2040/fastio.cpp @@ -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 . + * + */ +#include "../platforms.h" + +#ifdef __PLAT_RP2040__ + +#include "../../inc/MarlinConfig.h" + +void FastIO_init() { + +} + +#endif // __PLAT_RP2040__ diff --git a/Marlin/src/HAL/RP2040/fastio.h b/Marlin/src/HAL/RP2040/fastio.h new file mode 100644 index 000000000000..e84d2e77784f --- /dev/null +++ b/Marlin/src/HAL/RP2040/fastio.h @@ -0,0 +1,87 @@ +/** + * 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 . + * + */ +#pragma once + +/** + * Fast I/O interfaces for RP2040 + * These use GPIO register access for fast port manipulation. + */ + +// ------------------------ +// Public Variables +// ------------------------ + + +// ------------------------ +// Public functions +// ------------------------ + +void FastIO_init(); // Must be called before using fast io macros +#define FASTIO_INIT() FastIO_init() + +// ------------------------ +// Defines +// ------------------------ + +#define _BV32(b) (1UL << (b)) + +#ifndef PWM + #define PWM OUTPUT +#endif + +#define _WRITE(IO, V) digitalWrite((IO), (V)) + +#define _READ(IO) digitalRead(IO) +#define _TOGGLE(IO) digitalWrite(IO, !digitalRead(IO)) + +#define _GET_MODE(IO) +#define _SET_MODE(IO,M) pinMode(IO, M) +#define _SET_OUTPUT(IO) pinMode(IO, OUTPUT) //!< Output Push Pull Mode & GPIO_NOPULL +#define _SET_OUTPUT_OD(IO) pinMode(IO, OUTPUT_OPEN_DRAIN) + +#define WRITE(IO,V) _WRITE(IO,V) +#define READ(IO) _READ(IO) +#define TOGGLE(IO) _TOGGLE(IO) + +#define OUT_WRITE(IO,V) do{ _SET_OUTPUT(IO); WRITE(IO,V); }while(0) +#define OUT_WRITE_OD(IO,V) do{ _SET_OUTPUT_OD(IO); WRITE(IO,V); }while(0) + +#define SET_INPUT(IO) _SET_MODE(IO, INPUT) //!< Input Floating Mode +#define SET_INPUT_PULLUP(IO) _SET_MODE(IO, INPUT_PULLUP) //!< Input with Pull-up activation +#define SET_INPUT_PULLDOWN(IO) _SET_MODE(IO, INPUT_PULLDOWN) //!< Input with Pull-down activation +#define SET_OUTPUT(IO) OUT_WRITE(IO, LOW) +#define SET_PWM(IO) _SET_MODE(IO, PWM) + +#define IS_INPUT(IO) +#define IS_OUTPUT(IO) + +#define PWM_PIN(P) true //digitalPinHasPWM(P) +#define NO_COMPILE_TIME_PWM + +// digitalRead/Write wrappers +#define extDigitalRead(IO) digitalRead(IO) +#define extDigitalWrite(IO,V) digitalWrite(IO,V) + +#undef I2C_SDA +#define I2C_SDA_PIN PIN_WIRE_SDA +#undef I2C_SCL +#define I2C_SCL_PIN PIN_WIRE_SCL diff --git a/Marlin/src/HAL/RP2040/inc/Conditionals_LCD.h b/Marlin/src/HAL/RP2040/inc/Conditionals_LCD.h new file mode 100644 index 000000000000..82f95a10357f --- /dev/null +++ b/Marlin/src/HAL/RP2040/inc/Conditionals_LCD.h @@ -0,0 +1,22 @@ +/** + * 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 . + * + */ +#pragma once diff --git a/Marlin/src/HAL/RP2040/inc/Conditionals_adv.h b/Marlin/src/HAL/RP2040/inc/Conditionals_adv.h new file mode 100644 index 000000000000..b96b3baa64cd --- /dev/null +++ b/Marlin/src/HAL/RP2040/inc/Conditionals_adv.h @@ -0,0 +1,35 @@ +/** + * 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 . + * + */ +#pragma once + +#if HAS_MEDIA && DISABLED(NO_SD_HOST_DRIVE) + #define HAS_SD_HOST_DRIVE 1 +#endif + +// Fix F_CPU not being a compile-time constant in RP2040 framework +#ifdef BOARD_F_CPU + #undef F_CPU + #define F_CPU BOARD_F_CPU +#endif + +// The Sensitive Pins array is not optimizable +#define RUNTIME_ONLY_ANALOG_TO_DIGITAL diff --git a/Marlin/src/HAL/RP2040/inc/Conditionals_post.h b/Marlin/src/HAL/RP2040/inc/Conditionals_post.h new file mode 100644 index 000000000000..ef7853b98736 --- /dev/null +++ b/Marlin/src/HAL/RP2040/inc/Conditionals_post.h @@ -0,0 +1,29 @@ +/** + * 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 . + * + */ +#pragma once + +// If no real or emulated EEPROM selected, fall back to SD emulation +#if USE_FALLBACK_EEPROM + #define SDCARD_EEPROM_EMULATION +#elif ANY(I2C_EEPROM, SPI_EEPROM) + #define USE_SHARED_EEPROM 1 +#endif diff --git a/Marlin/src/HAL/RP2040/inc/Conditionals_type.h b/Marlin/src/HAL/RP2040/inc/Conditionals_type.h new file mode 100644 index 000000000000..82f95a10357f --- /dev/null +++ b/Marlin/src/HAL/RP2040/inc/Conditionals_type.h @@ -0,0 +1,22 @@ +/** + * 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 . + * + */ +#pragma once diff --git a/Marlin/src/HAL/RP2040/inc/SanityCheck.h b/Marlin/src/HAL/RP2040/inc/SanityCheck.h new file mode 100644 index 000000000000..e4b1d7ee8d9d --- /dev/null +++ b/Marlin/src/HAL/RP2040/inc/SanityCheck.h @@ -0,0 +1,60 @@ +/** + * 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 . + * + */ +#pragma once + +/** + * Test RP2040-specific configuration values for errors at compile-time. + */ +//#if ENABLED(SPINDLE_LASER_PWM) && !(SPINDLE_LASER_PWM_PIN == 4 || SPINDLE_LASER_PWM_PIN == 6 || SPINDLE_LASER_PWM_PIN == 11) +// #error "SPINDLE_LASER_PWM_PIN must use SERVO0, SERVO1 or SERVO3 connector" +//#endif + +#if ENABLED(SDCARD_EEPROM_EMULATION) && !HAS_MEDIA + #undef SDCARD_EEPROM_EMULATION // Avoid additional error noise + #if USE_FALLBACK_EEPROM + #warning "EEPROM type not specified. Fallback is SDCARD_EEPROM_EMULATION." + #endif + #error "SDCARD_EEPROM_EMULATION requires SDSUPPORT. Enable SDSUPPORT or choose another EEPROM emulation." +#endif + +#if ENABLED(SRAM_EEPROM_EMULATION) + #error "SRAM_EEPROM_EMULATION is not supported for RP2040." +#endif + +#if ALL(PRINTCOUNTER, FLASH_EEPROM_EMULATION) + #warning "FLASH_EEPROM_EMULATION may cause long delays when writing and should not be used while printing." + #error "Disable PRINTCOUNTER or choose another EEPROM emulation." +#endif + +#if ENABLED(FLASH_EEPROM_LEVELING) + #error "FLASH_EEPROM_LEVELING is not supported for RP2040." +#endif + +#if ENABLED(SERIAL_STATS_MAX_RX_QUEUED) + #error "SERIAL_STATS_MAX_RX_QUEUED is not supported on RP2040." +#elif ENABLED(SERIAL_STATS_DROPPED_RX) + #error "SERIAL_STATS_DROPPED_RX is not supported on RP2040." +#endif + +#if ANY(TFT_COLOR_UI, TFT_LVGL_UI, TFT_CLASSIC_UI) + #error "TFT_COLOR_UI, TFT_LVGL_UI and TFT_CLASSIC_UI are not supported for RP2040." +#endif diff --git a/Marlin/src/HAL/RP2040/msc_sd.cpp b/Marlin/src/HAL/RP2040/msc_sd.cpp new file mode 100644 index 000000000000..b0de2241e569 --- /dev/null +++ b/Marlin/src/HAL/RP2040/msc_sd.cpp @@ -0,0 +1,104 @@ +/** + * 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 . + * + */ +#include "../platforms.h" + +#ifdef __PLAT_RP2040__ + +#include "../../inc/MarlinConfigPre.h" + +#if HAS_SD_HOST_DRIVE + +#include "../../sd/cardreader.h" + +#include // TinyUSB device stack + +#define BLOCK_SIZE 512 +#define SD_MULTIBLOCK_RETRY_CNT 1 + +DiskIODriver* diskIODriver() { + #if HAS_MULTI_VOLUME + #if SHARED_VOLUME_IS(SD_ONBOARD) + return &card.media_driver_sdcard; + #elif SHARED_VOLUME_IS(USB_FLASH_DRIVE) + return &card.media_driver_usbFlash; + #endif + #else + return card.diskIODriver(); + #endif +} + +/** Callbacks used by TinyUSB MSC **/ + +extern "C" { + +bool tud_msc_ready_cb(uint8_t lun) { + return diskIODriver()->isReady(); +} + +int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, void* buffer, uint32_t bufsize) { + const uint32_t blocks = bufsize / BLOCK_SIZE; + for (uint16_t rcount = SD_MULTIBLOCK_RETRY_CNT; rcount--; ) { + if (diskIODriver()->readBlocks(lba, (uint8_t*)buffer, blocks)) + return bufsize; // Success + } + return -1; // Failure after retries +} + +int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint8_t const* buffer, uint32_t bufsize) { + const uint32_t blocks = bufsize / BLOCK_SIZE; + for (uint16_t rcount = SD_MULTIBLOCK_RETRY_CNT; rcount--; ) { + if (diskIODriver()->writeBlocks(lba, buffer, blocks)) + return bufsize; // Success + } + return -1; // Failure after retries +} + +void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4]) { + memcpy(vendor_id, "MARLIN ", 8); + memcpy(product_id, "Product ", 16); + memcpy(product_rev, "0.01", 4); +} + +void tud_msc_capacity_cb(uint8_t lun, uint32_t* block_count, uint16_t* block_size) { + *block_count = diskIODriver()->cardSize(); + *block_size = BLOCK_SIZE; +} + +void tud_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, bool load_eject) { + if (load_eject) { + if (start) { + // Handle media load + } else { + // Handle media eject + } + } +} + +} // extern "C" + +void MSC_SD_init() { + tusb_init(); + // Add USB reinitialization logic if needed +} + +#endif // HAS_SD_HOST_DRIVE +#endif // __PLAT_RP2040__ diff --git a/Marlin/src/HAL/RP2040/msc_sd.h b/Marlin/src/HAL/RP2040/msc_sd.h new file mode 100644 index 000000000000..1c13f5578dad --- /dev/null +++ b/Marlin/src/HAL/RP2040/msc_sd.h @@ -0,0 +1,24 @@ +/** + * 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 . + * + */ +#pragma once + +void MSC_SD_init(); diff --git a/Marlin/src/HAL/RP2040/pinsDebug.h b/Marlin/src/HAL/RP2040/pinsDebug.h new file mode 100644 index 000000000000..a13df3a1a549 --- /dev/null +++ b/Marlin/src/HAL/RP2040/pinsDebug.h @@ -0,0 +1,167 @@ +/** + * 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 . + * + */ +#pragma once + +#include +#include "HAL.h" + +#ifndef NUM_DIGITAL_PINS + #error "Expected NUM_DIGITAL_PINS not found." +#endif + +/** + * Life gets complicated if you want an easy to use 'M43 I' output (in port/pin order) + * because the variants in this platform do not always define all the I/O port/pins + * that a CPU has. + * + * VARIABLES: + * Ard_num - Arduino pin number - defined by the platform. It is used by digitalRead and + * digitalWrite commands and by M42. + * - does not contain port/pin info + * - is not in port/pin order + * - typically a variant will only assign Ard_num to port/pins that are actually used + * Index - M43 counter - only used to get Ard_num + * x - a parameter/argument used to search the pin_array to try to find a signal name + * associated with a Ard_num + * Port_pin - port number and pin number for use with CPU registers and printing reports + * + * Since M43 uses digitalRead and digitalWrite commands, only the Port_pins with an Ard_num + * are accessed and/or displayed. + * + * Three arrays are used. + * + * digitalPin[] is provided by the platform. It consists of the Port_pin numbers in + * Arduino pin number order. + * + * pin_array is a structure generated by the pins/pinsDebug.h header file. It is generated by + * the preprocessor. Only the signals associated with enabled options are in this table. + * It contains: + * - name of the signal + * - the Ard_num assigned by the pins_YOUR_BOARD.h file using the platform defines. + * EXAMPLE: "#define KILL_PIN PB1" results in Ard_num of 57. 57 is then used as the + * argument to digitalPinToPinName(IO) to get the Port_pin number + * - if it is a digital or analog signal. PWMs are considered digital here. + * + * pin_xref is a structure generated by this header file. It is generated by the + * preprocessor. It is in port/pin order. It contains just the port/pin numbers defined by the + * platform for this variant. + * - Ard_num + * - printable version of Port_pin + * + * Routines with an "x" as a parameter/argument are used to search the pin_array to try to + * find a signal name associated with a port/pin. + * + * NOTE - the Arduino pin number is what is used by the M42 command, NOT the port/pin for that + * signal. The Arduino pin number is listed by the M43 I command. + */ + +/** + * Pins Debugging for RP2040 + * + * - NUMBER_PINS_TOTAL + * - MULTI_NAME_PAD + * - getPinByIndex(index) + * - printPinNameByIndex(index) + * - getPinIsDigitalByIndex(index) + * - digitalPinToAnalogIndex(pin) + * - getValidPinMode(pin) + * - isValidPin(pin) + * - isAnalogPin(pin) + * - digitalRead_mod(pin) + * - pwm_status(pin) + * - printPinPWM(pin) + * - printPinPort(pin) + * - printPinNumber(pin) + * - printPinAnalog(pin) + */ + +#define NUMBER_PINS_TOTAL NUM_DIGITAL_PINS +#define NUM_ANALOG_FIRST A0 + +#define MODE_PIN_INPUT 0 // Input mode (reset state) +#define MODE_PIN_OUTPUT 1 // General purpose output mode +#define MODE_PIN_ALT 2 // Alternate function mode +#define MODE_PIN_ANALOG 3 // Analog mode + +#define getPinByIndex(x) 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 getPinIsDigitalByIndex(x) pin_array[x].is_digital +#define digitalPinToAnalogIndex(P) digital_pin_to_analog_pin(P) + +uint8_t get_pin_mode(const pin_t pin) { + // Check if pin is in alternate function mode (I2C, SPI, etc.) + const uint32_t gpio_func = gpio_get_function(pin); + + // GPIO_FUNC_I2C is typically function 3 on RP2040 + if ( gpio_func == GPIO_FUNC_I2C + || gpio_func == GPIO_FUNC_SPI + || gpio_func == GPIO_FUNC_UART + || gpio_func == GPIO_FUNC_PWM + ) { + return MODE_PIN_ALT; + } + + // For GPIO mode, check direction + return gpio_get_dir(pin) ? MODE_PIN_OUTPUT : MODE_PIN_INPUT; +} + +bool getValidPinMode(const pin_t pin) { + const uint8_t pin_mode = get_pin_mode(pin); + return pin_mode == MODE_PIN_OUTPUT || pin_mode == MODE_PIN_ALT; // assume all alt definitions are PWM +} + +#define isValidPin(P) WITHIN(P, 0, pin_t(NUMBER_PINS_TOTAL - 1)) + +int8_t digital_pin_to_analog_pin(pin_t pin) { + pin -= NUM_ANALOG_FIRST; + return (WITHIN(pin, 0, NUM_ANALOG_INPUTS - 1)) ? pin : -1; +} + +bool isAnalogPin(const pin_t pin) { + return digital_pin_to_analog_pin(pin) >= 0; +} + +#define digitalRead_mod(A) extDigitalRead(A) // must use Arduino pin numbers when doing reads +#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(P)); SERIAL_ECHO(buffer); }while(0) +#define MULTI_NAME_PAD 33 // space needed to be pretty if not first name assigned to a pin + +//bool is_digital(const pin_t pin) { +// const uint8_t pin_mode = get_pin_mode(pin); +// return pin_mode == MODE_PIN_INPUT || pin_mode == MODE_PIN_OUTPUT; +//} + +bool pwm_status(const pin_t pin) { + // Check if this pin is configured for PWM + return PWM_PIN(pin) && get_pin_mode(pin) == MODE_PIN_ALT; +} + +void printPinPWM(const pin_t pin) { + if (pwm_status(pin)) { + // RP2040 has hardware PWM on specific pins + char buffer[22]; + sprintf_P(buffer, PSTR("PWM: pin %d "), pin); + SERIAL_ECHO(buffer); + } +} + +void printPinPort(const pin_t pin) {} diff --git a/Marlin/src/HAL/RP2040/spi_pins.h b/Marlin/src/HAL/RP2040/spi_pins.h new file mode 100644 index 000000000000..e6ee840b5544 --- /dev/null +++ b/Marlin/src/HAL/RP2040/spi_pins.h @@ -0,0 +1,38 @@ +/** + * 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 . + * + */ +#pragma once + +/** + * Define SPI Pins: SCK, MISO, MOSI, SS + */ +#ifndef SD_SCK_PIN + #define SD_SCK_PIN PIN_SPI_SCK +#endif +#ifndef SD_MISO_PIN + #define SD_MISO_PIN PIN_SPI_MISO +#endif +#ifndef SD_MOSI_PIN + #define SD_MOSI_PIN PIN_SPI_MOSI +#endif +#ifndef SD_SS_PIN + #define SD_SS_PIN PIN_SPI_SS +#endif diff --git a/Marlin/src/HAL/RP2040/temp_soc.h b/Marlin/src/HAL/RP2040/temp_soc.h new file mode 100644 index 000000000000..e182fb88dc67 --- /dev/null +++ b/Marlin/src/HAL/RP2040/temp_soc.h @@ -0,0 +1,30 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2025 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 . + * + */ +#pragma once + +// RP2040 internal temperature sensor +// Formula: T = 27 - (ADC_voltage - 0.706) / 0.001721 +// ADC_voltage = (RAW / OVERSAMPLENR) * 3.3 / 4096 (RAW is accumulated over OVERSAMPLENR samples) +// T = 27 - ((RAW / OVERSAMPLENR) * 3.3 / 4096 - 0.706) / 0.001721 +// Simplified: T = 437.423 - (RAW / OVERSAMPLENR) * 0.46875 + +#define TEMP_SOC_SENSOR(RAW) (437.423f - ((RAW) / OVERSAMPLENR) * 0.46875f) diff --git a/Marlin/src/HAL/RP2040/timers.cpp b/Marlin/src/HAL/RP2040/timers.cpp new file mode 100644 index 000000000000..e6a41b23a634 --- /dev/null +++ b/Marlin/src/HAL/RP2040/timers.cpp @@ -0,0 +1,126 @@ +/** + * 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 . + * + */ +#include "../platforms.h" + +#ifdef __PLAT_RP2040__ + +#include "../../inc/MarlinConfig.h" + +alarm_pool_t* HAL_timer_pool_0; +alarm_pool_t* HAL_timer_pool_1; +alarm_pool_t* HAL_timer_pool_2; +alarm_pool_t* HAL_timer_pool_3; + +struct repeating_timer HAL_timer_0; +struct repeating_timer HAL_timer_1; +struct repeating_timer HAL_timer_2; +struct repeating_timer HAL_timer_3; + +volatile bool HAL_timer_irq_en[4] = { false, false, false, false }; + +void HAL_timer_init() { + //reserve all the available alarm pools to use as "pseudo" hardware timers + //HAL_timer_pool_0 = alarm_pool_create(0,2); + HAL_timer_pool_1 = alarm_pool_create(1, 6); + HAL_timer_pool_0 = HAL_timer_pool_1; + HAL_timer_pool_2 = alarm_pool_create(2, 6); + HAL_timer_pool_3 = HAL_timer_pool_2; + //HAL_timer_pool_3 = alarm_pool_create(3, 6); + + irq_set_priority(TIMER_IRQ_0, 0xC0); + irq_set_priority(TIMER_IRQ_1, 0x80); + irq_set_priority(TIMER_IRQ_2, 0x40); + irq_set_priority(TIMER_IRQ_3, 0x00); + + //alarm_pool_init_default(); +} + +void HAL_timer_start(const uint8_t timer_num, const uint32_t frequency) { + const int64_t freq = (int64_t)frequency, + us = (1000000ll / freq) * -1ll; + bool result; + switch (timer_num) { + case 0: + result = alarm_pool_add_repeating_timer_us(HAL_timer_pool_0, us, HAL_timer_repeating_0_callback, nullptr, &HAL_timer_0); + break; + case 1: + result = alarm_pool_add_repeating_timer_us(HAL_timer_pool_1, us, HAL_timer_repeating_1_callback, nullptr, &HAL_timer_1); + break; + case 2: + result = alarm_pool_add_repeating_timer_us(HAL_timer_pool_2, us, HAL_timer_repeating_2_callback, nullptr, &HAL_timer_2); + break; + case 3: + result = alarm_pool_add_repeating_timer_us(HAL_timer_pool_3, us, HAL_timer_repeating_3_callback, nullptr, &HAL_timer_3); + break; + } + UNUSED(result); +} + +void HAL_timer_stop(const uint8_t timer_num) { + switch (timer_num) { + case 0: cancel_repeating_timer(&HAL_timer_0); break; + case 1: cancel_repeating_timer(&HAL_timer_1); break; + case 2: cancel_repeating_timer(&HAL_timer_2); break; + case 3: cancel_repeating_timer(&HAL_timer_3); break; + } +} + +int64_t HAL_timer_alarm_pool_0_callback(long int, void*) { + if (HAL_timer_irq_en[0]) HAL_timer_0_callback(); + return 0; +} +int64_t HAL_timer_alarm_pool_1_callback(long int, void*) { + if (HAL_timer_irq_en[1]) HAL_timer_1_callback(); + return 0; +} +int64_t HAL_timer_alarm_pool_2_callback(long int, void*) { + if (HAL_timer_irq_en[2]) HAL_timer_2_callback(); + return 0; +} +int64_t HAL_timer_alarm_pool_3_callback(long int, void*) { + if (HAL_timer_irq_en[3]) HAL_timer_3_callback(); + return 0; +} + +bool HAL_timer_repeating_0_callback(repeating_timer* timer) { + if (HAL_timer_irq_en[0]) HAL_timer_0_callback(); + return true; +} +bool HAL_timer_repeating_1_callback(repeating_timer* timer) { + if (HAL_timer_irq_en[1]) HAL_timer_1_callback(); + return true; +} +bool HAL_timer_repeating_2_callback(repeating_timer* timer) { + if (HAL_timer_irq_en[2]) HAL_timer_2_callback(); + return true; +} +bool HAL_timer_repeating_3_callback(repeating_timer* timer) { + if (HAL_timer_irq_en[3]) HAL_timer_3_callback(); + return true; +} + +void __attribute__((weak)) HAL_timer_0_callback() {} +void __attribute__((weak)) HAL_timer_1_callback() {} +void __attribute__((weak)) HAL_timer_2_callback() {} +void __attribute__((weak)) HAL_timer_3_callback() {} + +#endif // __PLAT_RP2040__ diff --git a/Marlin/src/HAL/RP2040/timers.h b/Marlin/src/HAL/RP2040/timers.h new file mode 100644 index 000000000000..24d0303ed865 --- /dev/null +++ b/Marlin/src/HAL/RP2040/timers.h @@ -0,0 +1,164 @@ +/** + * 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 . + * + */ +#include + +#include "../../core/macros.h" + +#ifdef PICO_TIME_DEFAULT_ALARM_POOL_DISABLED + #undef PICO_TIME_DEFAULT_ALARM_POOL_DISABLED + #define PICO_TIME_DEFAULT_ALARM_POOL_DISABLED 0 +#else + #define PICO_TIME_DEFAULT_ALARM_POOL_DISABLED 0 +#endif + +// ------------------------ +// Defines +// ------------------------ + +//#define _HAL_TIMER(T) _CAT(LPC_TIM, T) +//#define _HAL_TIMER_IRQ(T) TIMER##T##_IRQn +//#define __HAL_TIMER_ISR(T) extern "C" alarm_callback_t HAL_timer_alarm_pool_##T##_callback() +#define __HAL_TIMER_ISR(T) extern void HAL_timer_##T##_callback() +#define _HAL_TIMER_ISR(T) __HAL_TIMER_ISR(T) + +typedef uint64_t hal_timer_t; +#define HAL_TIMER_TYPE_MAX hal_timer_t(UINT64_MAX) + +#define HAL_TIMER_RATE (1'000'000ULL) // fixed value as we use a microsecond timesource +#ifndef MF_TIMER_STEP + #define MF_TIMER_STEP 0 // Timer Index for Stepper +#endif +#ifndef MF_TIMER_PULSE + #define MF_TIMER_PULSE MF_TIMER_STEP +#endif +#ifndef MF_TIMER_TEMP + #define MF_TIMER_TEMP 1 // Timer Index for Temperature +#endif +#ifndef MF_TIMER_PWM + #define MF_TIMER_PWM 3 // Timer Index for PWM +#endif + +#define TEMP_TIMER_RATE HAL_TIMER_RATE +#define TEMP_TIMER_FREQUENCY 1000 // (Hz) Temperature ISR frequency + +#define STEPPER_TIMER_RATE HAL_TIMER_RATE / 10 // 100khz roughly +#define STEPPER_TIMER_TICKS_PER_US (0.1) // fixed value as we use a microsecond timesource +#define STEPPER_TIMER_PRESCALE (10) + +#define PULSE_TIMER_RATE STEPPER_TIMER_RATE // (Hz) Frequency of Pulse Timer +#define PULSE_TIMER_PRESCALE STEPPER_TIMER_PRESCALE + +#define ENABLE_STEPPER_DRIVER_INTERRUPT() HAL_timer_enable_interrupt(MF_TIMER_STEP) +#define DISABLE_STEPPER_DRIVER_INTERRUPT() HAL_timer_disable_interrupt(MF_TIMER_STEP) +#define STEPPER_ISR_ENABLED() HAL_timer_interrupt_enabled(MF_TIMER_STEP) + +#define ENABLE_TEMPERATURE_INTERRUPT() HAL_timer_enable_interrupt(MF_TIMER_TEMP) +#define DISABLE_TEMPERATURE_INTERRUPT() HAL_timer_disable_interrupt(MF_TIMER_TEMP) + +#ifndef HAL_STEP_TIMER_ISR + #define HAL_STEP_TIMER_ISR() _HAL_TIMER_ISR(MF_TIMER_STEP) +#endif +#ifndef HAL_TEMP_TIMER_ISR + #define HAL_TEMP_TIMER_ISR() _HAL_TIMER_ISR(MF_TIMER_TEMP) +#endif + +// Timer references by index +//#define STEP_TIMER_PTR _HAL_TIMER(MF_TIMER_STEP) +//#define TEMP_TIMER_PTR _HAL_TIMER(MF_TIMER_TEMP) + +extern alarm_pool_t *HAL_timer_pool_0; +extern alarm_pool_t *HAL_timer_pool_1; +extern alarm_pool_t *HAL_timer_pool_2; +extern alarm_pool_t *HAL_timer_pool_3; + +extern struct repeating_timer HAL_timer_0; + +void HAL_timer_0_callback(); +void HAL_timer_1_callback(); +void HAL_timer_2_callback(); +void HAL_timer_3_callback(); + +int64_t HAL_timer_alarm_pool_0_callback(long int, void*); +int64_t HAL_timer_alarm_pool_1_callback(long int, void*); +int64_t HAL_timer_alarm_pool_2_callback(long int, void*); +int64_t HAL_timer_alarm_pool_3_callback(long int, void*); + +bool HAL_timer_repeating_0_callback(repeating_timer* timer); +bool HAL_timer_repeating_1_callback(repeating_timer* timer); +bool HAL_timer_repeating_2_callback(repeating_timer* timer); +bool HAL_timer_repeating_3_callback(repeating_timer* timer); + +extern volatile bool HAL_timer_irq_en[4]; + +// ------------------------ +// Public functions +// ------------------------ + +void HAL_timer_init(); +void HAL_timer_start(const uint8_t timer_num, const uint32_t frequency); +void HAL_timer_stop(const uint8_t timer_num); + +FORCE_INLINE static void HAL_timer_set_compare(const uint8_t timer_num, hal_timer_t compare) { + + if (timer_num == MF_TIMER_STEP && compare == HAL_TIMER_TYPE_MAX) { + HAL_timer_stop(timer_num); + return; + } + + compare *= 10; // Dirty fix, figure out a proper way + + switch (timer_num) { + case 0: + alarm_pool_add_alarm_in_us(HAL_timer_pool_0, compare, HAL_timer_alarm_pool_0_callback, 0, true); + break; + case 1: + alarm_pool_add_alarm_in_us(HAL_timer_pool_1, compare, HAL_timer_alarm_pool_1_callback, 0, true); + break; + case 2: + alarm_pool_add_alarm_in_us(HAL_timer_pool_2, compare, HAL_timer_alarm_pool_2_callback, 0, true); + break; + case 3: + alarm_pool_add_alarm_in_us(HAL_timer_pool_3, compare, HAL_timer_alarm_pool_3_callback, 0, true); + break; + } +} + +FORCE_INLINE static hal_timer_t HAL_timer_get_compare(const uint8_t timer_num) { + return 0; +} +FORCE_INLINE static hal_timer_t HAL_timer_get_count(const uint8_t timer_num) { + if (timer_num == MF_TIMER_STEP) return 0ull; + return time_us_64(); +} + +FORCE_INLINE static void HAL_timer_enable_interrupt(const uint8_t timer_num) { + HAL_timer_irq_en[timer_num] = 1; +} +FORCE_INLINE static void HAL_timer_disable_interrupt(const uint8_t timer_num) { + HAL_timer_irq_en[timer_num] = 0; +} +FORCE_INLINE static bool HAL_timer_interrupt_enabled(const uint8_t timer_num) { + return HAL_timer_irq_en[timer_num]; // Lucky coincidence that timer_num and rp2040 IRQ num matches +} + +inline void HAL_timer_isr_prologue(const uint8_t) {} +inline void HAL_timer_isr_epilogue(const uint8_t) {} diff --git a/Marlin/src/HAL/RP2040/u8g/LCD_defines.h b/Marlin/src/HAL/RP2040/u8g/LCD_defines.h new file mode 100644 index 000000000000..acdd94b4771a --- /dev/null +++ b/Marlin/src/HAL/RP2040/u8g/LCD_defines.h @@ -0,0 +1,28 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2025 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 . + * + */ +#pragma once + +/** + * RP2040 LCD-specific defines + */ +uint8_t u8g_com_rp2040_ssd_i2c_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr); // u8g_com_rp2040_ssd_i2c.cpp +#define U8G_COM_SSD_I2C_HAL u8g_com_rp2040_ssd_i2c_fn diff --git a/Marlin/src/HAL/RP2040/u8g/u8g_com_rp2040_ssd_i2c.cpp b/Marlin/src/HAL/RP2040/u8g/u8g_com_rp2040_ssd_i2c.cpp new file mode 100644 index 000000000000..ea42bf190def --- /dev/null +++ b/Marlin/src/HAL/RP2040/u8g/u8g_com_rp2040_ssd_i2c.cpp @@ -0,0 +1,108 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2025 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 . + * + */ + +/** + * 2-Wire I2C COM Driver + * + * Handles Hardware I2C on valid pin combinations. + * Wire library is used for Hardware I2C. + * + * Hardware I2C uses pins defined in pins_arduino.h. + */ + +#ifdef __PLAT_RP2040__ + +#include "../../../inc/MarlinConfig.h" + +#if HAS_U8GLIB_I2C_OLED + +#include + +#include +#ifndef MASTER_ADDRESS + #define MASTER_ADDRESS 0x01 +#endif + +/** + * BUFFER_LENGTH is defined in libraries\Wire\utility\WireBase.h + * Default value is 32 + * Increase this value to 144 to send U8G_COM_MSG_WRITE_SEQ in single block + */ +#ifndef BUFFER_LENGTH + #define BUFFER_LENGTH 32 +#endif +#if BUFFER_LENGTH > 144 + #error "BUFFER_LENGTH should not be greater than 144." +#endif +#define I2C_MAX_LENGTH (BUFFER_LENGTH - 1) + +uint8_t u8g_com_rp2040_ssd_i2c_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr) { + + // Hardware I2C flag + //static bool i2c_initialized = false; // Flag to only run init/linking code once + //if (!i2c_initialized) { // Init runtime linkages + // i2c_initialized = true; // Only do this once + //} + + static uint8_t control; + // Use the global Wire instance (already initialized with correct pins for RP2040) + switch (msg) { + case U8G_COM_MSG_INIT: + Wire.setClock(400000); + // Wire already initialized in MarlinUI::init(), no need to call begin() again + break; + + case U8G_COM_MSG_ADDRESS: // Define cmd (arg_val = 0) or data mode (arg_val = 1) + control = arg_val ? 0x40 : 0x00; + break; + + case U8G_COM_MSG_WRITE_BYTE: + ::Wire.beginTransmission(0x3C); + ::Wire.write(control); + ::Wire.write(arg_val); + ::Wire.endTransmission(); + break; + + case U8G_COM_MSG_WRITE_SEQ: { + uint8_t* dataptr = (uint8_t*)arg_ptr; + while (arg_val > 0) { + ::Wire.beginTransmission(0x3C); + ::Wire.write(control); + if (arg_val <= I2C_MAX_LENGTH) { + ::Wire.write(dataptr, arg_val); + arg_val = 0; + } + else { + ::Wire.write(dataptr, I2C_MAX_LENGTH); + arg_val -= I2C_MAX_LENGTH; + dataptr += I2C_MAX_LENGTH; + } + ::Wire.endTransmission(); + } + break; + } + } + return 1; +} + +#endif // HAS_U8GLIB_I2C_OLED +#endif // __PLAT_RP2040__ diff --git a/Marlin/src/HAL/SAMD21/AGENTS.md b/Marlin/src/HAL/SAMD21/AGENTS.md new file mode 100644 index 000000000000..be5980d6726f --- /dev/null +++ b/Marlin/src/HAL/SAMD21/AGENTS.md @@ -0,0 +1,56 @@ +# AGENTS.md — SAMD21 HAL + +Working notes and conventions for the **SAMD21 HAL** folder (`Marlin/src/HAL/SAMD21/`). + +## Build & Test Loop + +One real test target exists for this family. Validate with `mftest` (it regenerates `Marlin/Configuration.h` per target, which raw `pio run -e` does not do reliably): + +```bash +cd "$(git rev-parse --show-toplevel)" +buildroot/bin/mftest -t SAMD21_minitronics20 -n1 -y # ReprapWorld Minitronics 2.0, SAMD21J18 +``` + +`SAMD21_minitronics20` is the only SAMD21 env (`ini/samd21.ini`, `buildroot/tests/SAMD21_minitronics20/config-01.ini`, `motherboard = BOARD_MINITRONICS20`). It is the tightest (and only) constraint for SAMD21 — get it green and the family is covered. (The separate `SAMD51_grandcentral_m4` target is the SAMD51 family, not SAMD21 — do not confuse the two.) + +The toolchain (`atmelsam` platform + upstream Arduino SAMD framework) is installed in this environment, so the build can actually be run here. + +> Do NOT use `rm -rf .pio/build/...` to force a rebuild — the cross-profile write guard blocks it and it is unsafe. Use `pio run -e -t clean` or just let `mftest` rebuild. + +## Framework / Platform Integration (upstream, NOT vendored) + +Unlike AT32, the SAMD21 Arduino core is the **upstream** PlatformIO `atmelsam` platform (`framework-arduino-samd-*`, e.g. `framework-arduino-samd-reprap`/`...-adafruit`) — there is no Marlin-vendored copy under `buildroot/share/PlatformIO/`. Marlin-specific glue lives in: + +- `Marlin/src/HAL/SAMD21/` — the HAL itself (serial, SPI, ADC, timers, servo, EEPROM backends, u8g LCD drivers). +- `Marlin/src/pins/samd/` — board pin files (`pins_MINITRONICS20.h`, `pins_BRICOLEMON_V1_0.h`, `pins_BRICOLEMON_LITE_V1_0.h`, `pins_RAMPS_144.h`). +- `buildroot/share/PlatformIO/scripts/SAMD21_minitronics20.py` — per-env extra script (only adjusts `BUILD_FLAGS` serial buffer sizing). + +Because the framework is upstream, there is **no mirror-to-installed-package warning** (that applies only to the vendored AT32 core). + +### Gotchas that are not obvious from the source + +1. **Compiler guard is `__SAMD21__`, set by build flags, not the core.** `HAL.h` / `HAL.cpp` / `HAL_SPI.cpp` / `eeprom_*.cpp` all key on `#ifdef __SAMD21__`, but the upstream Arduino SAMD core defines `ARDUINO_ARCH_SAMD`, not `__SAMD21__`. `ini/samd21.ini` passes `-D__SAMD21__`. **If you add a new SAMD21 board env, you MUST include `-D__SAMD21__` in its `build_flags` or nothing in this HAL compiles.** + +2. **Primary serial is native USB-CDC, not a UART.** `HAL.h` defines `DefaultSerial1 = ForwardSerial1Class` as `MSerialUSB` and `#define USB_SERIAL_PORT(...) MSerialUSB`. Build flags add `-DUSBCON -DUSBD_USE_CDC` so `SerialUSB` exists. `Serial1`/`Serial2` map to hardware UARTs (`MSerial1`/`MSerial2` via SERCOM). The test config sets `serial_port = -1` for the UART and relies on USB — a board without native USB must re-point the serial port. + +3. **Software SPI is a hard `#error`.** `HAL_SPI.cpp` rejects `SOFTWARE_SPI` / `FORCE_SOFT_SPI` ("Software SPI not supported for SAMD21. Use Hardware SPI."). `MarlinSPI` is just `SPIClass` (`MarlinSPI.h`), i.e. the upstream SAMD SERCOM SPI driver. `spiSendBlock()` _claims_ DMA in a comment but actually calls blocking `SPI.transfer()` — fine, just don't expect DMA offload on the SD path. + +4. **EEPROM backend is per-board, not auto-selected.** `eeprom/` provides `I2C_EEPROM`, `FLASH_EEPROM_EMULATION`, and `QSPI_EEPROM` (the latter via `eeprom_qspi.cpp`/`QSPIFlash.cpp`), but `USE_WIRED_EEPROM` is an explicit `#error` ("Not implemented on SAMD21"). Each `pins/samd/*.h` must pick one: `BRICOLEMON_*` and `RAMPS_144` default to `I2C_EEPROM`; `MINITRONICS20` leaves `FLASH_EEPROM_EMULATION` commented out (with `MARLIN_EEPROM_SIZE 500U`). `QSPI_EEPROM` is coded but not selected by any current board. + +5. **`WDT` macro collides with the CMSIS struct field.** In `HAL.cpp`, `get_reset_source()` wraps its body in `#pragma push_macro("WDT")` / `#undef WDT` / `#pragma pop_macro("WDT")` because `REG_WDT_CTRL` resolves to `.bit.WDT` and the preprocessor wrongly substitutes the `WDT` define. **Do not "fix" this by renaming the field — keep the macro push/pop.** + +6. **ADC is free-running INPUTSCAN with remapped sensor order.** `adc_init()` configures the 12-bit ADC as 16-bit averaging (32 samples, DIV2 gain), free-running, scanning 3 contiguous AINs (`HAL_ADC_AIN_NUM_SENSORS = 3`). The SAMD21 ADC only scans contiguous AINs, so `ADC_Handler()` reads `RESULT` by `INPUTOFFSET` and `adc_start()` remaps the pin's AIN back to the array index (`HAL.cpp`). `HAL_ADC_FILTERED 1` means Marlin's own oversampling is disabled — the HAL already filters. + +7. **48 MHz Cortex-M0+, no FPU.** SAMD21 tops out at 48 MHz with no hardware float unit; keep math in integer/fixed-point where hot. `freeMemory()` is a `_sbrk`/heap-end sbrk hack (`HAL.cpp`), not a real stack-check. + +## Conventions + +- All HAL source is guarded by `#ifdef __SAMD21__` — ensure new files and new board envs set `-D__SAMD21__` (see gotcha 1). +- Keep HAL changes in `Marlin/src/HAL/SAMD21/` and board pin changes in `Marlin/src/pins/samd/`. Build glue goes in `buildroot/share/PlatformIO/scripts/` (per-env) or `ini/samd21.ini`. +- Native USB CDC is the intended host link; treat `SerialUSB`/`MSerialUSB` as the primary port unless a board explicitly moves to a UART. + +## Related + +- `Marlin/src/pins/samd/` — board pin files (no `README.md` in the HAL folder). +- Wiki: [[hal-samd21]], [[marlin-src-hal]]. +- `Marlin/src/HAL/shared/` — shared HAL APIs (`eeprom_api`, SPI helpers) the SAMD21 HAL reuses. diff --git a/Marlin/src/HAL/SAMD21/HAL.cpp b/Marlin/src/HAL/SAMD21/HAL.cpp index 3656d97190e6..0a95366cc4f7 100644 --- a/Marlin/src/HAL/SAMD21/HAL.cpp +++ b/Marlin/src/HAL/SAMD21/HAL.cpp @@ -107,7 +107,7 @@ void MarlinHAL::init() { #if HAS_SD_DETECT && SD_CONNECTION_IS(ONBOARD) SET_INPUT_PULLUP(SD_DETECT_PIN); #endif - OUT_WRITE(SDSS, HIGH); // Try to set SDSS inactive before any other SPI users start up + OUT_WRITE(SD_SS_PIN, HIGH); // Try to set SDSS inactive before any other SPI users start up #endif } diff --git a/Marlin/src/HAL/SAMD21/HAL.h b/Marlin/src/HAL/SAMD21/HAL.h index 95f391fa8678..5545630ce30a 100644 --- a/Marlin/src/HAL/SAMD21/HAL.h +++ b/Marlin/src/HAL/SAMD21/HAL.h @@ -36,11 +36,11 @@ // ------------------------ // Serial ports // ------------------------ + #include "../../core/serial_hook.h" typedef ForwardSerial1Class< decltype(SerialUSB) > DefaultSerial1; extern DefaultSerial1 MSerialUSB; -// Serial ports typedef ForwardSerial1Class< decltype(Serial1) > DefaultSerial2; typedef ForwardSerial1Class< decltype(Serial2) > DefaultSerial3; @@ -51,43 +51,10 @@ extern DefaultSerial3 MSerial1; #define _MSERIAL(X) __MSERIAL(X) #define MSERIAL(X) _MSERIAL(INCREMENT(X)) -#if WITHIN(SERIAL_PORT, 0, 1) - #define MYSERIAL1 MSERIAL(SERIAL_PORT) -#elif SERIAL_PORT == -1 - #define MYSERIAL1 MSerialUSB -#else - #error "SERIAL_PORT must be -1 (Native USB only)." -#endif - -#ifdef SERIAL_PORT_2 - #if WITHIN(SERIAL_PORT_2, 0, 1) - #define MYSERIAL2 MSERIAL(SERIAL_PORT) - #elif SERIAL_PORT_2 == -1 - #define MYSERIAL2 MSerialUSB - #else - #error "SERIAL_PORT_2 must be -1 (Native USB only)." - #endif -#endif - -#ifdef MMU2_SERIAL_PORT - #if WITHIN(MMU2_SERIAL_PORT, 0, 1) - #define MMU2_SERIAL MSERIAL(SERIAL_PORT) - #elif MMU2_SERIAL_PORT == -1 - #define MMU2_SERIAL MSerialUSB - #else - #error "MMU2_SERIAL_PORT must be -1 (Native USB only)." - #endif -#endif - -#ifdef LCD_SERIAL_PORT - #if WITHIN(LCD_SERIAL_PORT, 0, 1) - #define LCD_SERIAL MSERIAL(SERIAL_PORT) - #elif LCD_SERIAL_PORT == -1 - #define LCD_SERIAL MSerialUSB - #else - #error "LCD_SERIAL_PORT must be -1 (Native USB only)." - #endif -#endif +#define SERIAL_INDEX_MIN 0 +#define SERIAL_INDEX_MAX 1 +#define USB_SERIAL_PORT(...) MSerialUSB +#include "../shared/serial_ports.h" typedef int8_t pin_t; @@ -177,7 +144,7 @@ class MarlinHAL { static void delay_ms(const int ms) { delay(ms); } - // Tasks, called from idle() + // Tasks, called from marlin.idle() static void idletask() {} // Reset diff --git a/Marlin/src/HAL/SAMD21/HAL_SPI.cpp b/Marlin/src/HAL/SAMD21/HAL_SPI.cpp index e01f540cf8a1..d41f868cdb0a 100644 --- a/Marlin/src/HAL/SAMD21/HAL_SPI.cpp +++ b/Marlin/src/HAL/SAMD21/HAL_SPI.cpp @@ -108,7 +108,6 @@ SPI.beginTransaction(spiConfig); SPI.transfer(buf, nbyte); SPI.endTransaction(); - } /** diff --git a/Marlin/src/HAL/SAMD21/QSPIFlash.cpp b/Marlin/src/HAL/SAMD21/eeprom/QSPIFlash.cpp similarity index 98% rename from Marlin/src/HAL/SAMD21/QSPIFlash.cpp rename to Marlin/src/HAL/SAMD21/eeprom/QSPIFlash.cpp index fa54c6207125..2a93226a6fd4 100644 --- a/Marlin/src/HAL/SAMD21/QSPIFlash.cpp +++ b/Marlin/src/HAL/SAMD21/eeprom/QSPIFlash.cpp @@ -24,7 +24,7 @@ * SAMD21 HAL developed by Bart Meijer (brupje) * Based on SAMD51 HAL by Giuliano Zaro (AKA GMagician) */ -#include "../../inc/MarlinConfig.h" +#include "../../../inc/MarlinConfig.h" #if ENABLED(QSPI_EEPROM) diff --git a/Marlin/src/HAL/SAMD21/QSPIFlash.h b/Marlin/src/HAL/SAMD21/eeprom/QSPIFlash.h similarity index 100% rename from Marlin/src/HAL/SAMD21/QSPIFlash.h rename to Marlin/src/HAL/SAMD21/eeprom/QSPIFlash.h diff --git a/Marlin/src/HAL/SAMD21/eeprom_flash.cpp b/Marlin/src/HAL/SAMD21/eeprom/eeprom_flash.cpp similarity index 97% rename from Marlin/src/HAL/SAMD21/eeprom_flash.cpp rename to Marlin/src/HAL/SAMD21/eeprom/eeprom_flash.cpp index 66329bff19c8..0b5323cda49e 100644 --- a/Marlin/src/HAL/SAMD21/eeprom_flash.cpp +++ b/Marlin/src/HAL/SAMD21/eeprom/eeprom_flash.cpp @@ -26,7 +26,7 @@ */ #ifdef __SAMD21__ -#include "../../inc/MarlinConfig.h" +#include "../../../inc/MarlinConfig.h" #if ENABLED(FLASH_EEPROM_EMULATION) @@ -35,7 +35,7 @@ /* reserve flash memory */ static const uint8_t flashdata[TOTAL_FLASH_SIZE] __attribute__((__aligned__(256))) { }; \ -#include "../shared/eeprom_api.h" +#include "../../shared/eeprom_api.h" size_t PersistentStore::capacity() { return MARLIN_EEPROM_SIZE - eeprom_exclude_size; } @@ -83,7 +83,7 @@ bool PersistentStore::access_start() { NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMDEX_KEY | NVMCTRL_CTRLA_CMD_PBC; while (NVMCTRL->INTFLAG.bit.READY == 0) { } - PAGE_SIZE = pow(2,3 + NVMCTRL->PARAM.bit.PSZ); + PAGE_SIZE = POW(2, 3 + NVMCTRL->PARAM.bit.PSZ); ROW_SIZE= PAGE_SIZE * 4; /*NVMCTRL->SEECFG.reg = NVMCTRL_SEECFG_WMODE_BUFFERED; // Buffered mode and segment reallocation active if (NVMCTRL->SEESTAT.bit.RLOCK) diff --git a/Marlin/src/HAL/SAMD21/eeprom_qspi.cpp b/Marlin/src/HAL/SAMD21/eeprom/eeprom_qspi.cpp similarity index 96% rename from Marlin/src/HAL/SAMD21/eeprom_qspi.cpp rename to Marlin/src/HAL/SAMD21/eeprom/eeprom_qspi.cpp index 12977a178c57..8bd1bd353931 100644 --- a/Marlin/src/HAL/SAMD21/eeprom_qspi.cpp +++ b/Marlin/src/HAL/SAMD21/eeprom/eeprom_qspi.cpp @@ -26,13 +26,13 @@ */ #ifdef __SAMD21__ -#include "../../inc/MarlinConfig.h" +#include "../../../inc/MarlinConfig.h" #if ENABLED(QSPI_EEPROM) #error "QSPI_EEPROM emulation Not implemented on SAMD21" -#include "../shared/eeprom_api.h" +#include "../../shared/eeprom_api.h" #include "QSPIFlash.h" diff --git a/Marlin/src/HAL/SAMD21/eeprom_wired.cpp b/Marlin/src/HAL/SAMD21/eeprom/eeprom_wired.cpp similarity index 95% rename from Marlin/src/HAL/SAMD21/eeprom_wired.cpp rename to Marlin/src/HAL/SAMD21/eeprom/eeprom_wired.cpp index da0eb1b0c81b..82c701ebb17c 100644 --- a/Marlin/src/HAL/SAMD21/eeprom_wired.cpp +++ b/Marlin/src/HAL/SAMD21/eeprom/eeprom_wired.cpp @@ -26,7 +26,7 @@ */ #ifdef __SAMD21__ -#include "../../inc/MarlinConfig.h" +#include "../../../inc/MarlinConfig.h" #if USE_WIRED_EEPROM @@ -36,8 +36,8 @@ * with simple implementations supplied by Marlin. */ -#include "../shared/eeprom_if.h" -#include "../shared/eeprom_api.h" +#include "../../shared/eeprom_if.h" +#include "../../shared/eeprom_api.h" #ifndef MARLIN_EEPROM_SIZE #error "MARLIN_EEPROM_SIZE is required for I2C / SPI EEPROM." diff --git a/Marlin/src/HAL/SAMD21/endstop_interrupts.h b/Marlin/src/HAL/SAMD21/endstop_interrupts.h index 37fdb7504b58..8d8f9cd06f65 100644 --- a/Marlin/src/HAL/SAMD21/endstop_interrupts.h +++ b/Marlin/src/HAL/SAMD21/endstop_interrupts.h @@ -32,7 +32,7 @@ * On SAMD21, all pins support external interrupt capability. * Any pin can be used for external interrupts, but there are some restrictions. * At most 16 different external interrupts can be used at one time. - * Further, you can’t just pick any 16 pins to use. This is because every pin on the SAMD21 + * Further, you can't just pick any 16 pins to use. This is because every pin on the SAMD21 * connects to what is called an EXTINT line, and only one pin per EXTINT line can be used for external * interrupts at a time */ @@ -83,6 +83,7 @@ #define MATCH_Z4_MAX_EILINE(P) TERN0(USE_Z4_MAX, DEFER4(MATCH_EILINE)(P, Z4_MAX_PIN)) #define MATCH_Z4_MIN_EILINE(P) TERN0(USE_Z4_MIN, DEFER4(MATCH_EILINE)(P, Z4_MIN_PIN)) #define MATCH_Z_MIN_PROBE_EILINE(P) TERN0(USE_Z_MIN_PROBE, DEFER4(MATCH_EILINE)(P, Z_MIN_PROBE_PIN)) +#define MATCH_CALIBRATION_EILINE(P) TERN0(USE_CALIBRATION, DEFER4(MATCH_EILINE)(P, CALIBRATION_PIN)) #define AVAILABLE_EILINE(P) ( PIN_TO_EILINE(P) != -1 \ && !MATCH_X_MAX_EILINE(P) && !MATCH_X_MIN_EILINE(P) \ @@ -99,7 +100,8 @@ && !MATCH_Z2_MAX_EILINE(P) && !MATCH_Z2_MIN_EILINE(P) \ && !MATCH_Z3_MAX_EILINE(P) && !MATCH_Z3_MIN_EILINE(P) \ && !MATCH_Z4_MAX_EILINE(P) && !MATCH_Z4_MIN_EILINE(P) \ - && !MATCH_Z_MIN_PROBE_EILINE(P) ) + && !MATCH_Z_MIN_PROBE_EILINE(P) \ + && !MATCH_CALIBRATION_EILINE(P) ) // One ISR for all EXT-Interrupts void endstop_ISR() { endstops.update(); } @@ -208,6 +210,12 @@ void setup_endstop_interrupts() { #endif _ATTACH(Z_MIN_PROBE_PIN); #endif + #if USE_CALIBRATION + #if !AVAILABLE_EILINE(CALIBRATION_PIN) + #error "CALIBRATION_PIN has no EXTINT line available. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #endif + _ATTACH(CALIBRATION_PIN); + #endif #if USE_I_MAX #if !AVAILABLE_EILINE(I_MAX_PIN) #error "I_MAX_PIN has no EXTINT line available. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." diff --git a/Marlin/src/HAL/SAMD21/fastio.h b/Marlin/src/HAL/SAMD21/fastio.h index 471e8b62abc7..32cc5575281f 100644 --- a/Marlin/src/HAL/SAMD21/fastio.h +++ b/Marlin/src/HAL/SAMD21/fastio.h @@ -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 diff --git a/Marlin/src/HAL/SAMD21/pinsDebug.h b/Marlin/src/HAL/SAMD21/pinsDebug.h index f94315cdf539..14c2e66f8923 100644 --- a/Marlin/src/HAL/SAMD21/pinsDebug.h +++ b/Marlin/src/HAL/SAMD21/pinsDebug.h @@ -22,42 +22,57 @@ #pragma once /** - * SAMD21 HAL developed by Bart Meijer (brupje) - * Based on SAMD51 HAL by Giuliano Zaro (AKA GMagician) + * Pins Debugging for SAMD21 + * + * - NUMBER_PINS_TOTAL + * - MULTI_NAME_PAD + * - getPinByIndex(index) + * - printPinNameByIndex(index) + * - getPinIsDigitalByIndex(index) + * - digitalPinToAnalogIndex(pin) + * - getValidPinMode(pin) + * - isValidPin(pin) + * - isAnalogPin(pin) + * - digitalRead_mod(pin) + * - pwm_status(pin) + * - printPinPWM(pin) + * - printPinPort(pin) + * - printPinNumber(pin) + * - printPinAnalog(pin) */ #define NUMBER_PINS_TOTAL PINS_COUNT -#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 pwm_status(pin) digitalPinHasPWM(pin) +#define digitalRead_mod(P) extDigitalRead(P) +#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(P)); SERIAL_ECHO(buffer); }while(0) +#define getPinByIndex(x) pin_array[x].pin +#define getPinIsDigitalByIndex(x) pin_array[x].is_digital +#define isValidPin(P) (P >= 0 && P < pin_t(NUMBER_PINS_TOTAL)) +#define isAnalogPin(P) (digitalPinToAnalogIndex(P) >= 0) +#define pwm_status(P) digitalPinHasPWM(P) #define MULTI_NAME_PAD 27 // 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 // uses pin index #define M43_NEVER_TOUCH(Q) ((Q) >= 75) -bool GET_PINMODE(int8_t pin) { // 1: output, 0: input +bool getValidPinMode(const 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(const int32_t pin) { if (pwm_status(pin)) { //uint32_t chan = g_APinDescription[pin].ulPWMChannel TODO when fast pwm is operative; //SERIAL_ECHOPGM("PWM = ", duty); } } +void printPinPort(const pin_t) {} + /** * SAMD21 Board pin| PORT | Label * ----------------+--------+------- diff --git a/Marlin/src/HAL/SAMD21/spi_pins.h b/Marlin/src/HAL/SAMD21/spi_pins.h index 8c25b84dc182..1186c3de48c5 100644 --- a/Marlin/src/HAL/SAMD21/spi_pins.h +++ b/Marlin/src/HAL/SAMD21/spi_pins.h @@ -45,10 +45,3 @@ #ifndef SD_MOSI_PIN #define SD_MOSI_PIN 37 #endif -#ifndef SDSS - #define SDSS 18 -#endif - -#ifndef SD_SS_PIN - #define SD_SS_PIN SDSS -#endif diff --git a/Marlin/src/HAL/SAMD21/timers.cpp b/Marlin/src/HAL/SAMD21/timers.cpp index b5f1d4f7bd80..21a1767fc03c 100644 --- a/Marlin/src/HAL/SAMD21/timers.cpp +++ b/Marlin/src/HAL/SAMD21/timers.cpp @@ -165,7 +165,7 @@ void HAL_timer_start(const uint8_t timer_num, const uint32_t frequency) { tc->COUNT32.INTENCLR.reg = TC_INTENCLR_OVF; // disable overflow interrupt // TCn clock setup - GCLK->CLKCTRL.reg = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID(GCM_TC4_TC5)) ; + GCLK->CLKCTRL.reg = uint16_t(GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID(GCM_TC4_TC5)); SYNC (GCLK->STATUS.bit.SYNCBUSY); tcReset(tc); // reset TC @@ -176,7 +176,7 @@ void HAL_timer_start(const uint8_t timer_num, const uint32_t frequency) { tc->COUNT32.CTRLA.reg |= TC_CTRLA_WAVEGEN_MFRQ; //set prescaler //the clock normally counts at the GCLK_TC frequency, but we can set it to divide that frequency to slow it down - //you can use different prescaler divisons here like TC_CTRLA_PRESCALER_DIV1 to get a different range + //you can use different prescaler divisions here like TC_CTRLA_PRESCALER_DIV1 to get a different range tc->COUNT32.CTRLA.reg |= TC_CTRLA_PRESCALER_DIV1 | TC_CTRLA_ENABLE; //it will divide GCLK_TC frequency by 1024 //set the compare-capture register. //The counter will count up to this value (it's a 16bit counter so we use uint16_t) diff --git a/Marlin/src/HAL/SAMD21/timers.h b/Marlin/src/HAL/SAMD21/timers.h index 303ccbdc505c..dfae6055952f 100644 --- a/Marlin/src/HAL/SAMD21/timers.h +++ b/Marlin/src/HAL/SAMD21/timers.h @@ -33,7 +33,7 @@ // -------------------------------------------------------------------------- typedef uint32_t hal_timer_t; -#define HAL_TIMER_TYPE_MAX 0xFFFFFFFF +#define HAL_TIMER_TYPE_MAX hal_timer_t(UINT32_MAX) #define HAL_TIMER_RATE F_CPU // frequency of timers peripherals @@ -49,15 +49,14 @@ typedef uint32_t hal_timer_t; #define MF_TIMER_TEMP MF_TIMER_RTC // Timer Index for Temperature #endif -#define TEMP_TIMER_FREQUENCY 1000 // temperature interrupt frequency +#define TEMP_TIMER_FREQUENCY 1000 // (Hz) Temperature ISR frequency -#define STEPPER_TIMER_RATE HAL_TIMER_RATE // frequency of stepper timer (HAL_TIMER_RATE / STEPPER_TIMER_PRESCALE) -#define STEPPER_TIMER_TICKS_PER_US (STEPPER_TIMER_RATE / 1000000) // stepper timer ticks per µs +#define STEPPER_TIMER_RATE HAL_TIMER_RATE // (Hz) Frequency of Stepper Timer ISR (HAL_TIMER_RATE / STEPPER_TIMER_PRESCALE) +#define STEPPER_TIMER_TICKS_PER_US (STEPPER_TIMER_RATE / 1000000) // (MHz) Stepper Timer ticks per µs #define STEPPER_TIMER_PRESCALE (CYCLES_PER_MICROSECOND / STEPPER_TIMER_TICKS_PER_US) -#define PULSE_TIMER_RATE STEPPER_TIMER_RATE -#define PULSE_TIMER_PRESCALE STEPPER_TIMER_PRESCALE -#define PULSE_TIMER_TICKS_PER_US STEPPER_TIMER_TICKS_PER_US +#define PULSE_TIMER_RATE STEPPER_TIMER_RATE // (Hz) Frequency of Pulse Timer +#define PULSE_TIMER_PRESCALE STEPPER_TIMER_PRESCALE #define ENABLE_STEPPER_DRIVER_INTERRUPT() HAL_timer_enable_interrupt(MF_TIMER_STEP) #define DISABLE_STEPPER_DRIVER_INTERRUPT() HAL_timer_disable_interrupt(MF_TIMER_STEP) @@ -143,9 +142,8 @@ FORCE_INLINE static void HAL_timer_isr_prologue(const uint8_t timer_num) { Rtc * const rtc = timer_config[timer_num].pRtc; // Clear interrupt flag rtc->MODE0.INTFLAG.reg = RTC_MODE0_INTFLAG_CMP0| RTC_MODE0_INTFLAG_OVF; - } - else if (timer_config[timer_num].type == TimerType::tcc){ + else if (timer_config[timer_num].type == TimerType::tcc) { Tcc * const tc = timer_config[timer_num].pTcc; // Clear interrupt flag tc->INTFLAG.reg = TCC_INTFLAG_OVF; @@ -157,4 +155,4 @@ FORCE_INLINE static void HAL_timer_isr_prologue(const uint8_t timer_num) { } } -#define HAL_timer_isr_epilogue(timer_num) +inline void HAL_timer_isr_epilogue(const uint8_t) {} diff --git a/Marlin/src/HAL/SAMD21/u8g/LCD_I2C_routines.cpp b/Marlin/src/HAL/SAMD21/u8g/LCD_I2C_routines.cpp index 41da7c10fc24..7cbedd84fcf9 100644 --- a/Marlin/src/HAL/SAMD21/u8g/LCD_I2C_routines.cpp +++ b/Marlin/src/HAL/SAMD21/u8g/LCD_I2C_routines.cpp @@ -25,7 +25,7 @@ * Based on SAMD51 HAL by Giuliano Zaro (AKA GMagician) */ // adapted from I2C/master/master.c example -// https://www-users.cs.york.ac.uk/~pcc/MCP/HAPR-Course-web/CMSIS/examples/html/master_8c_source.html +// https://www-users.york.ac.uk/~pcc1/MCP/HAPR-Course-web/CMSIS/examples/html/master_8c_source.html #ifdef __SAMD21__ diff --git a/Marlin/src/HAL/SAMD21/u8g/LCD_pin_routines.c b/Marlin/src/HAL/SAMD21/u8g/LCD_pin_routines.c index f9f77825f6e0..b2079fef9bfb 100644 --- a/Marlin/src/HAL/SAMD21/u8g/LCD_pin_routines.c +++ b/Marlin/src/HAL/SAMD21/u8g/LCD_pin_routines.c @@ -32,7 +32,7 @@ * * Couldn't just call exact copies because the overhead killed the LCD update speed * With an intermediate level the softspi was running in the 10-20kHz range which - * resulted in using about about 25% of the CPU's time. + * resulted in using about 25% of the CPU's time. */ #ifdef __SAMD21__ diff --git a/Marlin/src/HAL/SAMD21/u8g/LCD_pin_routines.h b/Marlin/src/HAL/SAMD21/u8g/LCD_pin_routines.h index 92626552b013..a4bf2800fe21 100644 --- a/Marlin/src/HAL/SAMD21/u8g/LCD_pin_routines.h +++ b/Marlin/src/HAL/SAMD21/u8g/LCD_pin_routines.h @@ -33,7 +33,7 @@ * * Couldn't just call exact copies because the overhead killed the LCD update speed * With an intermediate level the softspi was running in the 10-20kHz range which - * resulted in using about about 25% of the CPU's time. + * resulted in using about 25% of the CPU's time. */ void u8g_SetPinOutput(uint8_t internal_pin_number); diff --git a/Marlin/src/HAL/SAMD51/AGENTS.md b/Marlin/src/HAL/SAMD51/AGENTS.md new file mode 100644 index 000000000000..84bc0a44a13e --- /dev/null +++ b/Marlin/src/HAL/SAMD51/AGENTS.md @@ -0,0 +1,51 @@ +# AGENTS.md — SAMD51 HAL + +Working notes and conventions for the **SAMD51 HAL** folder (`Marlin/src/HAL/SAMD51/`). + +## Build & Test Loop + +Validate with `mftest` (it regenerates `Marlin/Configuration.h` per target, which raw `pio run -e` does not do reliably): + +```bash +cd "$(git rev-parse --show-toplevel)" +buildroot/bin/mftest -t SAMD51_grandcentral_m4 -n1 -y +``` + +There is exactly one SAMD51 test target: `SAMD51_grandcentral_m4`, which builds the Adafruit Grand Central M4 (`board = adafruit_grandcentral_m4`, `platform = atmelsam`) against `buildroot/tests/SAMD51_grandcentral_m4/config-01.ini` (board `BOARD_AGCM4_RAMPS_144`, TMC2209 + BLTouch + SD + EEPROM features). It must be green before considering SAMD51 work done. The Grand Central M4 has 1 MB flash / 256 KB RAM — not a tight flash constraint, but the build is the only real SAMD51 gate. + +> Do NOT use `rm -rf .pio/build/...` to force a rebuild — it is unsafe and guarded. Use `pio run -e SAMD51_grandcentral_m4 -t clean` or just let `mftest` rebuild. + +## Framework / Platform Integration (upstream, NOT vendored) + +SAMD51 uses the **upstream Arduino SAMD core** (`atmelsam` platform + `framework-arduino-samd-*`), not a Marlin-vendored core like AT32. Do NOT mirror framework changes to a repo path — there is no `buildroot/share/PlatformIO/framework-*` for SAMD51; Marlin-specific glue lives only in `Marlin/src/HAL/SAMD51/` and the per-env build script. + +- The `env:SAMD51_grandcentral_m4` section lives in `ini/samd51.ini`. It pulls in `Adafruit TinyUSB Library` and `SoftwareSerialM` as `lib_deps`, and adds `-DUSE_TINYUSB`. +- Marlin-specific build glue is in `buildroot/share/PlatformIO/scripts/SAMD51_grandcentral_m4.py`: it sets `-DSERIAL_BUFFER_SIZE` to `max(RX,TX,350)` from the resolved config. If you change how serial buffers are sized, update this script. +- EEPROM backend is **per-board, not auto-selected**. The HAL ships three backends that compile only when explicitly enabled: `eeprom/eeprom_wired.cpp` (`USE_WIRED_EEPROM`, needs `MARLIN_EEPROM_SIZE`), `eeprom/eeprom_qspi.cpp` (`QSPI_EEPROM`, via `QSPIFlash`), and `eeprom/eeprom_flash.cpp` (`FLASH_EEPROM_EMULATION`, SAMD51 NVM smart-EEPROM). Each SAMD51 board's pins file must `#define` the one it uses (`pins_RAMPS_144.h` / `pins_BRICOLEMON*.h` set these). Marlin does NOT auto-pick one. + +### Gotchas that are not obvious from the source + +1. **Native USB via TinyUSB, not Serial.** The Grand Central M4 exposes native USB CDC through Adafruit TinyUSB (`-DUSE_TINYUSB`). The USB serial is `Serial` (a `Serial_`, i.e. USB CDC), while hardware UARTs are `Serial1`/`Serial2`/… Marlin's port mapping in `MarlinSerial_AGCM4.h` aliases `MSerial0` → `Serial` (USB) and `MSerial1..4` → `Serial1..4`. `USB_SERIAL_PORT(...)` is `MSerial0`. If a board has no USB-CDC `Serial`, `USING_HW_SERIALUSB` is false and `MSerial0` is not defined — guard any USB-only code behind that macro (see `HAL.cpp:33`). + +2. **Extra UARTs are defined by the HAL, not the framework.** The Adafruit core leaves `Serial2`–`Serial4` undefined to save SERCOM resources. `MarlinSerial_AGCM4.cpp` instantiates them on specific SERCOMs (`Serial2`→SERCOM4, `Serial3`→SERCOM1, `Serial4`→SERCOM5) and provides the matching `SERCOMx_y_Handler()` ISRs. If you add a hardware serial port, you must wire both the `UartT` instance and its four SERCOM interrupt handlers here, or it will silently not receive data. + +3. **Pin representation is an integer Arduino index, not an enum.** `pin_t` is `int8_t` and `GET_PIN_MAP_PIN(index)`/`GET_PIN_MAP_INDEX(pin)` are identity, so `PIN_EXISTS(P)` works in the preprocessor (no AT32-style enum-pin trap). The SAMD port/pin is derived later via `SAMD51.h` macros (`PIN_TO_SAMD_PIN` → `DIO##P##_PIN` from the framework's `pins_arduino.h`). Don't reintroduce an enum-based pin scheme. + +4. **ADC uses both ADC0 and ADC1 behind a DMA scan.** `HAL.cpp` builds an `ADCIndex` enum and a `PIN_TO_INPUTCTRL` list split into ADC0-then-ADC1 inputs, then drives conversion via `Adafruit_ZeroDMA` (`DMA_IS_REQUIRED` whenever any ADC is used). The `GET_TEMP_n_ADC()` / `GET_*_ADC()` macros must resolve to ADC unit 0 or 1; a pin mapped to the wrong ADC unit silently won't be sampled. Watchdog refresh must happen during long operations so the 4–8 s WDT (`WDT_CONFIG_PER_CYC*`) doesn't trip. + +5. **Software SPI is unsupported.** `HAL_SPI.cpp` `#error`s on `SOFTWARE_SPI` / `FORCE_SOFT_SPI` — SAMD51 only does hardware SPI. SD SPI uses `SDCARD_SPI` when the SD is `ONBOARD` (QSPI), else the global `SPI`. `spiSendBlock()` uses DMA transfer. `MarlinSPI` is just `SPIClass` (`MarlinSPI.h`). + +6. **SERCOM/SD/QSPI sharing.** The Grand Central M4 routes the SD card over QSPI (`sdcard_connection = LCD` maps to the onboard SPI/QSPI flash in the test config). Repurposing SERCOM or QSPI pins in a board's pins file can collide with the SD/QSPI backend — the board comments in `pins/samd/` warn to "be careful with the Sercom configurations" when enabling hardware serials. + +## Conventions + +- All HAL source is guarded by `#ifdef __SAMD51__` (the compiler macro for the SAM D51 family; `HAL/platforms.h` maps `__SAMD51__` → `HAL/SAMD51/`). Board-variant glue is further guarded by `#ifdef ADAFRUIT_GRAND_CENTRAL_M4`. +- Keep HAL changes in `Marlin/src/HAL/SAMD51/` and board pin changes in `Marlin/src/pins/samd/` (e.g. `pins_RAMPS_144.h`, `pins_BRICOLEMON*.h`). Build/env glue goes in `buildroot/share/PlatformIO/scripts/SAMD51_grandcentral_m4.py` and `ini/samd51.ini`. +- Because the framework is upstream, HAL fixes must be portable — don't assume a Marlin-patched core. Anything that needs a core change has to go through Adafruit's `framework-arduino-samd-adafruit` upstream. + +## Related + +- `README.md` — none in this folder yet. +- Wiki: [[hal-samd51]], [[marlin-src-hal]]. +- `Marlin/src/HAL/shared/` — shared HAL APIs (`eeprom_api`, SPI helpers) the SAMD51 HAL reuses. +- `Marlin/src/pins/samd/` — SAMD51 board pin definitions. diff --git a/Marlin/src/HAL/SAMD51/HAL.cpp b/Marlin/src/HAL/SAMD51/HAL.cpp index a3c871ce516a..aea908707bd2 100644 --- a/Marlin/src/HAL/SAMD51/HAL.cpp +++ b/Marlin/src/HAL/SAMD51/HAL.cpp @@ -61,7 +61,8 @@ #define GET_COOLER_ADC() TERN(HAS_TEMP_ADC_COOLER, PIN_TO_ADC(TEMP_COOLER_PIN), -1) #define GET_BOARD_ADC() TERN(HAS_TEMP_ADC_BOARD, PIN_TO_ADC(TEMP_BOARD_PIN), -1) #define GET_SOC_ADC() TERN(HAS_TEMP_ADC_BOARD, PIN_TO_ADC(TEMP_BOARD_PIN), -1) -#define GET_FILAMENT_WIDTH_ADC() TERN(FILAMENT_WIDTH_SENSOR, PIN_TO_ADC(FILWIDTH_PIN), -1) +#define GET_FILAMENT_WIDTH_ADC() TERN(HAS_FILWIDTH_ADC, PIN_TO_ADC(FILWIDTH_PIN), -1) +#define GET_FILAMENT2_WIDTH_ADC() TERN(HAS_FILWIDTH2_ADC, PIN_TO_ADC(FILWIDTH2_PIN), -1) #define GET_BUTTONS_ADC() TERN(HAS_ADC_BUTTONS, PIN_TO_ADC(ADC_KEYPAD_PIN), -1) #define GET_JOY_ADC_X() TERN(HAS_JOY_ADC_X, PIN_TO_ADC(JOY_X_PIN), -1) #define GET_JOY_ADC_Y() TERN(HAS_JOY_ADC_Y, PIN_TO_ADC(JOY_Y_PIN), -1) @@ -77,7 +78,7 @@ || GET_PROBE_ADC() == n \ || GET_COOLER_ADC() == n \ || GET_BOARD_ADC() == n || GET_SOC_ADC() == n \ - || GET_FILAMENT_WIDTH_ADC() == n \ + || GET_FILAMENT_WIDTH_ADC() == n || GET_FILAMENT2_WIDTH_ADC() == n \ || GET_BUTTONS_ADC() == n \ || GET_JOY_ADC_X() == n || GET_JOY_ADC_Y() == n || GET_JOY_ADC_Z() == n \ || GET_POWERMON_ADC_CURRENT() == n || GET_POWERMON_ADC_VOLTS() == n \ @@ -146,6 +147,9 @@ enum ADCIndex { #if GET_FILAMENT_WIDTH_ADC() == 0 FILWIDTH, #endif + #if GET_FILAMENT2_WIDTH_ADC() == 0 + FILWIDTH2, + #endif #if GET_BUTTONS_ADC() == 0 ADC_KEY, #endif @@ -212,6 +216,9 @@ enum ADCIndex { #if GET_FILAMENT_WIDTH_ADC() == 1 FILWIDTH, #endif + #if GET_FILAMENT2_WIDTH_ADC() == 1 + FILWIDTH2, + #endif #if GET_BUTTONS_ADC() == 1 ADC_KEY, #endif @@ -334,6 +341,9 @@ enum ADCIndex { #if GET_FILAMENT_WIDTH_ADC() == 0 FILWIDTH_PIN, #endif + #if GET_FILAMENT2_WIDTH_ADC() == 0 + FILWIDTH2_PIN, + #endif #if GET_BUTTONS_ADC() == 0 ADC_KEYPAD_PIN, #endif @@ -400,6 +410,9 @@ enum ADCIndex { #if GET_FILAMENT_WIDTH_ADC() == 1 FILWIDTH_PIN, #endif + #if GET_FILAMENT2_WIDTH_ADC() == 1 + FILWIDTH2_PIN, + #endif #if GET_BUTTONS_ADC() == 1 ADC_KEYPAD_PIN, #endif @@ -471,6 +484,9 @@ enum ADCIndex { #if GET_FILAMENT_WIDTH_ADC() == 0 { PIN_TO_INPUTCTRL(FILWIDTH_PIN) }, #endif + #if GET_FILAMENT2_WIDTH_ADC() == 0 + { PIN_TO_INPUTCTRL(FILWIDTH2_PIN) }, + #endif #if GET_BUTTONS_ADC() == 0 { PIN_TO_INPUTCTRL(ADC_KEYPAD_PIN) }, #endif @@ -543,6 +559,9 @@ enum ADCIndex { #if GET_FILAMENT_WIDTH_ADC() == 1 { PIN_TO_INPUTCTRL(FILWIDTH_PIN) }, #endif + #if GET_FILAMENT2_WIDTH_ADC() == 1 + { PIN_TO_INPUTCTRL(FILWIDTH2_PIN) }, + #endif #if GET_BUTTONS_ADC() == 1 { PIN_TO_INPUTCTRL(ADC_KEYPAD_PIN) }, #endif @@ -669,7 +688,7 @@ void MarlinHAL::init() { #if HAS_SD_DETECT && SD_CONNECTION_IS(ONBOARD) SET_INPUT_PULLUP(SD_DETECT_PIN); #endif - OUT_WRITE(SDSS, HIGH); // Try to set SDSS inactive before any other SPI users start up + OUT_WRITE(SD_SS_PIN, HIGH); // Try to set SDSS inactive before any other SPI users start up #endif } diff --git a/Marlin/src/HAL/SAMD51/HAL.h b/Marlin/src/HAL/SAMD51/HAL.h index c96401fd9750..65dcce966ddb 100644 --- a/Marlin/src/HAL/SAMD51/HAL.h +++ b/Marlin/src/HAL/SAMD51/HAL.h @@ -34,62 +34,7 @@ #ifdef ADAFRUIT_GRAND_CENTRAL_M4 #include "MarlinSerial_AGCM4.h" - - // Serial ports - typedef ForwardSerial1Class< decltype(Serial) > DefaultSerial1; - typedef ForwardSerial1Class< decltype(Serial1) > DefaultSerial2; - typedef ForwardSerial1Class< decltype(Serial2) > DefaultSerial3; - typedef ForwardSerial1Class< decltype(Serial3) > DefaultSerial4; - typedef ForwardSerial1Class< decltype(Serial4) > DefaultSerial5; - extern DefaultSerial1 MSerial0; - extern DefaultSerial2 MSerial1; - extern DefaultSerial3 MSerial2; - extern DefaultSerial4 MSerial3; - extern DefaultSerial5 MSerial4; - - #define __MSERIAL(X) MSerial##X - #define _MSERIAL(X) __MSERIAL(X) - #define MSERIAL(X) _MSERIAL(INCREMENT(X)) - - #if SERIAL_PORT == -1 - #define MYSERIAL1 MSerial0 - #elif WITHIN(SERIAL_PORT, 0, 3) - #define MYSERIAL1 MSERIAL(SERIAL_PORT) - #else - #error "SERIAL_PORT must be from 0 to 3. You can also use -1 if the board supports Native USB." - #endif - - #ifdef SERIAL_PORT_2 - #if SERIAL_PORT_2 == -1 - #define MYSERIAL2 MSerial0 - #elif WITHIN(SERIAL_PORT_2, 0, 3) - #define MYSERIAL2 MSERIAL(SERIAL_PORT_2) - #else - #error "SERIAL_PORT_2 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 - #elif WITHIN(MMU2_SERIAL_PORT, 0, 3) - #define MMU2_SERIAL MSERIAL(MMU2_SERIAL_PORT) - #else - #error "MMU2_SERIAL_PORT must be from 0 to 3. You can also use -1 if the board supports Native USB." - #endif - #endif - - #ifdef LCD_SERIAL_PORT - #if LCD_SERIAL_PORT == -1 - #define LCD_SERIAL MSerial0 - #elif WITHIN(LCD_SERIAL_PORT, 0, 3) - #define LCD_SERIAL MSERIAL(LCD_SERIAL_PORT) - #else - #error "LCD_SERIAL_PORT must be from 0 to 3. You can also use -1 if the board supports Native USB." - #endif - #endif - -#endif // ADAFRUIT_GRAND_CENTRAL_M4 +#endif typedef int8_t pin_t; @@ -176,7 +121,7 @@ class MarlinHAL { static void delay_ms(const int ms) { delay(ms); } - // Tasks, called from idle() + // Tasks, called from marlin.idle() static void idletask() {} // Reset diff --git a/Marlin/src/HAL/SAMD51/MarlinSerial_AGCM4.h b/Marlin/src/HAL/SAMD51/MarlinSerial_AGCM4.h index 1044d9fcd0ef..7827c1f95851 100644 --- a/Marlin/src/HAL/SAMD51/MarlinSerial_AGCM4.h +++ b/Marlin/src/HAL/SAMD51/MarlinSerial_AGCM4.h @@ -32,3 +32,24 @@ typedef Serial1Class UartT; extern UartT Serial2; extern UartT Serial3; extern UartT Serial4; + +typedef ForwardSerial1Class< decltype(Serial) > DefaultSerial1; +typedef ForwardSerial1Class< decltype(Serial1) > DefaultSerial2; +typedef ForwardSerial1Class< decltype(Serial2) > DefaultSerial3; +typedef ForwardSerial1Class< decltype(Serial3) > DefaultSerial4; +typedef ForwardSerial1Class< decltype(Serial4) > DefaultSerial5; + +extern DefaultSerial1 MSerial0; +extern DefaultSerial2 MSerial1; +extern DefaultSerial3 MSerial2; +extern DefaultSerial4 MSerial3; +extern DefaultSerial5 MSerial4; + +#define __MSERIAL(X) MSerial##X +#define _MSERIAL(X) __MSERIAL(X) +#define MSERIAL(X) _MSERIAL(INCREMENT(X)) + +#define SERIAL_INDEX_MIN 0 +#define SERIAL_INDEX_MAX 3 +#define USB_SERIAL_PORT(...) MSerial0 +#include "../shared/serial_ports.h" diff --git a/Marlin/src/HAL/SAMD51/QSPIFlash.cpp b/Marlin/src/HAL/SAMD51/eeprom/QSPIFlash.cpp similarity index 98% rename from Marlin/src/HAL/SAMD51/QSPIFlash.cpp rename to Marlin/src/HAL/SAMD51/eeprom/QSPIFlash.cpp index fc21a1ad8c80..191da1f30c40 100644 --- a/Marlin/src/HAL/SAMD51/QSPIFlash.cpp +++ b/Marlin/src/HAL/SAMD51/eeprom/QSPIFlash.cpp @@ -20,7 +20,7 @@ * */ -#include "../../inc/MarlinConfig.h" +#include "../../../inc/MarlinConfig.h" #if ENABLED(QSPI_EEPROM) diff --git a/Marlin/src/HAL/SAMD51/QSPIFlash.h b/Marlin/src/HAL/SAMD51/eeprom/QSPIFlash.h similarity index 100% rename from Marlin/src/HAL/SAMD51/QSPIFlash.h rename to Marlin/src/HAL/SAMD51/eeprom/QSPIFlash.h diff --git a/Marlin/src/HAL/SAMD51/eeprom_flash.cpp b/Marlin/src/HAL/SAMD51/eeprom/eeprom_flash.cpp similarity index 97% rename from Marlin/src/HAL/SAMD51/eeprom_flash.cpp rename to Marlin/src/HAL/SAMD51/eeprom/eeprom_flash.cpp index 7d5518956c7a..2387a0f99e18 100644 --- a/Marlin/src/HAL/SAMD51/eeprom_flash.cpp +++ b/Marlin/src/HAL/SAMD51/eeprom/eeprom_flash.cpp @@ -25,11 +25,11 @@ */ #ifdef __SAMD51__ -#include "../../inc/MarlinConfig.h" +#include "../../../inc/MarlinConfig.h" #if ENABLED(FLASH_EEPROM_EMULATION) -#include "../shared/eeprom_api.h" +#include "../../shared/eeprom_api.h" #define NVMCTRL_CMD(c) do{ \ SYNC(!NVMCTRL->STATUS.bit.READY); \ diff --git a/Marlin/src/HAL/SAMD51/eeprom_qspi.cpp b/Marlin/src/HAL/SAMD51/eeprom/eeprom_qspi.cpp similarity index 96% rename from Marlin/src/HAL/SAMD51/eeprom_qspi.cpp rename to Marlin/src/HAL/SAMD51/eeprom/eeprom_qspi.cpp index a39e4c4fa31f..e829c28e26a6 100644 --- a/Marlin/src/HAL/SAMD51/eeprom_qspi.cpp +++ b/Marlin/src/HAL/SAMD51/eeprom/eeprom_qspi.cpp @@ -25,11 +25,11 @@ */ #ifdef __SAMD51__ -#include "../../inc/MarlinConfig.h" +#include "../../../inc/MarlinConfig.h" #if ENABLED(QSPI_EEPROM) -#include "../shared/eeprom_api.h" +#include "../../shared/eeprom_api.h" #include "QSPIFlash.h" diff --git a/Marlin/src/HAL/SAMD51/eeprom_wired.cpp b/Marlin/src/HAL/SAMD51/eeprom/eeprom_wired.cpp similarity index 95% rename from Marlin/src/HAL/SAMD51/eeprom_wired.cpp rename to Marlin/src/HAL/SAMD51/eeprom/eeprom_wired.cpp index 00a739a5872b..fc1eb09a0c39 100644 --- a/Marlin/src/HAL/SAMD51/eeprom_wired.cpp +++ b/Marlin/src/HAL/SAMD51/eeprom/eeprom_wired.cpp @@ -25,7 +25,7 @@ */ #ifdef __SAMD51__ -#include "../../inc/MarlinConfig.h" +#include "../../../inc/MarlinConfig.h" #if USE_WIRED_EEPROM @@ -34,8 +34,8 @@ * with simple implementations supplied by Marlin. */ -#include "../shared/eeprom_if.h" -#include "../shared/eeprom_api.h" +#include "../../shared/eeprom_if.h" +#include "../../shared/eeprom_api.h" #ifndef MARLIN_EEPROM_SIZE #error "MARLIN_EEPROM_SIZE is required for I2C / SPI EEPROM." diff --git a/Marlin/src/HAL/SAMD51/endstop_interrupts.h b/Marlin/src/HAL/SAMD51/endstop_interrupts.h index 84b6c46da985..4e342f754f1b 100644 --- a/Marlin/src/HAL/SAMD51/endstop_interrupts.h +++ b/Marlin/src/HAL/SAMD51/endstop_interrupts.h @@ -31,7 +31,7 @@ * On SAMD51, all pins support external interrupt capability. * Any pin can be used for external interrupts, but there are some restrictions. * At most 16 different external interrupts can be used at one time. - * Further, you can’t just pick any 16 pins to use. This is because every pin on the SAMD51 + * Further, you can't just pick any 16 pins to use. This is because every pin on the SAMD51 * connects to what is called an EXTINT line, and only one pin per EXTINT line can be used for external * interrupts at a time */ @@ -82,6 +82,7 @@ #define MATCH_Z4_MAX_EILINE(P) TERN0(USE_Z4_MAX, DEFER4(MATCH_EILINE)(P, Z4_MAX_PIN)) #define MATCH_Z4_MIN_EILINE(P) TERN0(USE_Z4_MIN, DEFER4(MATCH_EILINE)(P, Z4_MIN_PIN)) #define MATCH_Z_MIN_PROBE_EILINE(P) TERN0(USE_Z_MIN_PROBE, DEFER4(MATCH_EILINE)(P, Z_MIN_PROBE_PIN)) +#define MATCH_CALIBRATION_EILINE(P) TERN0(USE_CALIBRATION, DEFER4(MATCH_EILINE)(P, CALIBRATION_PIN)) #define AVAILABLE_EILINE(P) ( PIN_TO_EILINE(P) != -1 \ && !MATCH_X_MAX_EILINE(P) && !MATCH_X_MIN_EILINE(P) \ @@ -98,7 +99,8 @@ && !MATCH_Z2_MAX_EILINE(P) && !MATCH_Z2_MIN_EILINE(P) \ && !MATCH_Z3_MAX_EILINE(P) && !MATCH_Z3_MIN_EILINE(P) \ && !MATCH_Z4_MAX_EILINE(P) && !MATCH_Z4_MIN_EILINE(P) \ - && !MATCH_Z_MIN_PROBE_EILINE(P) ) + && !MATCH_Z_MIN_PROBE_EILINE(P) \ + && !MATCH_CALIBRATION_EILINE(P) ) // One ISR for all EXT-Interrupts void endstop_ISR() { endstops.update(); } @@ -183,6 +185,12 @@ void setup_endstop_interrupts() { #endif _ATTACH(Z_MIN_PROBE_PIN); #endif + #if USE_CALIBRATION + #if !AVAILABLE_EILINE(CALIBRATION_PIN) + #error "CALIBRATION_PIN has no EXTINT line available. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #endif + _ATTACH(CALIBRATION_PIN); + #endif #if USE_I_MAX #if !AVAILABLE_EILINE(I_MAX_PIN) #error "I_MAX_PIN has no EXTINT line available. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." diff --git a/Marlin/src/HAL/SAMD51/fastio.h b/Marlin/src/HAL/SAMD51/fastio.h index 3d43bdb24d8b..1a67b6ce2052 100644 --- a/Marlin/src/HAL/SAMD51/fastio.h +++ b/Marlin/src/HAL/SAMD51/fastio.h @@ -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 diff --git a/Marlin/src/HAL/SAMD51/pinsDebug.h b/Marlin/src/HAL/SAMD51/pinsDebug.h index 94f91c77bcce..21fd9da28a8c 100644 --- a/Marlin/src/HAL/SAMD51/pinsDebug.h +++ b/Marlin/src/HAL/SAMD51/pinsDebug.h @@ -22,41 +22,57 @@ #pragma once /** - * SAMD51 HAL developed by Giuliano Zaro (AKA GMagician) + * Pins Debugging for SAMD51 + * + * - NUMBER_PINS_TOTAL + * - MULTI_NAME_PAD + * - getPinByIndex(index) + * - printPinNameByIndex(index) + * - getPinIsDigitalByIndex(index) + * - digitalPinToAnalogIndex(pin) + * - getValidPinMode(pin) + * - isValidPin(pin) + * - isAnalogPin(pin) + * - digitalRead_mod(pin) + * - pwm_status(pin) + * - printPinPWM(pin) + * - printPinPort(pin) + * - printPinNumber(pin) + * - printPinAnalog(pin) */ #define NUMBER_PINS_TOTAL PINS_COUNT -#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 pwm_status(pin) digitalPinHasPWM(pin) +#define digitalRead_mod(P) extDigitalRead(P) +#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(P)); SERIAL_ECHO(buffer); }while(0) +#define getPinByIndex(x) pin_array[x].pin +#define getPinIsDigitalByIndex(x) pin_array[x].is_digital +#define isValidPin(P) (P >= 0 && P < pin_t(NUMBER_PINS_TOTAL)) +#define isAnalogPin(P) (digitalPinToAnalogIndex(P) >= 0) +#define pwm_status(P) digitalPinHasPWM(P) #define MULTI_NAME_PAD 27 // 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 // uses pin index #define M43_NEVER_TOUCH(Q) ((Q) >= 75) -bool GET_PINMODE(int8_t pin) { // 1: output, 0: input +bool getValidPinMode(const 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(const int32_t pin) { if (pwm_status(pin)) { //uint32_t chan = g_APinDescription[pin].ulPWMChannel TODO when fast pwm is operative; //SERIAL_ECHOPGM("PWM = ", duty); } } +void printPinPort(const pin_t) {} + /** * AGCM4 Board pin | PORT | Label * ----------------+--------+------- diff --git a/Marlin/src/HAL/SAMD51/spi_pins.h b/Marlin/src/HAL/SAMD51/spi_pins.h index 2c7cbeb99432..a2d5b72ec409 100644 --- a/Marlin/src/HAL/SAMD51/spi_pins.h +++ b/Marlin/src/HAL/SAMD51/spi_pins.h @@ -46,14 +46,9 @@ #ifndef SD_MOSI_PIN #define SD_MOSI_PIN 51 #endif - #ifndef SDSS - #define SDSS 53 - #endif #else #error "Unsupported board!" #endif - -#define SD_SS_PIN SDSS diff --git a/Marlin/src/HAL/SAMD51/timers.h b/Marlin/src/HAL/SAMD51/timers.h index 86c324189223..2682891cdc03 100644 --- a/Marlin/src/HAL/SAMD51/timers.h +++ b/Marlin/src/HAL/SAMD51/timers.h @@ -32,7 +32,7 @@ // -------------------------------------------------------------------------- typedef uint32_t hal_timer_t; -#define HAL_TIMER_TYPE_MAX 0xFFFFFFFF +#define HAL_TIMER_TYPE_MAX hal_timer_t(UINT32_MAX) #define HAL_TIMER_RATE F_CPU // frequency of timers peripherals @@ -48,15 +48,14 @@ typedef uint32_t hal_timer_t; #define MF_TIMER_TEMP MF_TIMER_RTC // Timer Index for Temperature #endif -#define TEMP_TIMER_FREQUENCY 1000 // temperature interrupt frequency +#define TEMP_TIMER_FREQUENCY 1000 // (Hz) Temperature ISR frequency -#define STEPPER_TIMER_RATE HAL_TIMER_RATE // frequency of stepper timer (HAL_TIMER_RATE / STEPPER_TIMER_PRESCALE) -#define STEPPER_TIMER_TICKS_PER_US (STEPPER_TIMER_RATE / 1000000) // stepper timer ticks per µs +#define STEPPER_TIMER_RATE HAL_TIMER_RATE // (Hz) Frequency of Stepper Timer ISR (HAL_TIMER_RATE / STEPPER_TIMER_PRESCALE) +#define STEPPER_TIMER_TICKS_PER_US (STEPPER_TIMER_RATE / 1000000) // (MHz) Stepper Timer ticks per µs #define STEPPER_TIMER_PRESCALE (CYCLES_PER_MICROSECOND / STEPPER_TIMER_TICKS_PER_US) -#define PULSE_TIMER_RATE STEPPER_TIMER_RATE -#define PULSE_TIMER_PRESCALE STEPPER_TIMER_PRESCALE -#define PULSE_TIMER_TICKS_PER_US STEPPER_TIMER_TICKS_PER_US +#define PULSE_TIMER_RATE STEPPER_TIMER_RATE // (Hz) Frequency of Pulse Timer +#define PULSE_TIMER_PRESCALE STEPPER_TIMER_PRESCALE #define ENABLE_STEPPER_DRIVER_INTERRUPT() HAL_timer_enable_interrupt(MF_TIMER_STEP) #define DISABLE_STEPPER_DRIVER_INTERRUPT() HAL_timer_disable_interrupt(MF_TIMER_STEP) @@ -145,4 +144,4 @@ FORCE_INLINE static void HAL_timer_isr_prologue(const uint8_t timer_num) { } } -#define HAL_timer_isr_epilogue(timer_num) +inline void HAL_timer_isr_epilogue(const uint8_t) {} diff --git a/Marlin/src/HAL/STM32/AGENTS.md b/Marlin/src/HAL/STM32/AGENTS.md new file mode 100644 index 000000000000..fffe560b830f --- /dev/null +++ b/Marlin/src/HAL/STM32/AGENTS.md @@ -0,0 +1,83 @@ +# AGENTS.md — STM32 HAL + +Working notes and conventions for the **STM32 HAL** folder (`Marlin/src/HAL/STM32/`). + +The STM32 HAL covers the **official STM32duino** ("generic STM32") core and serves the entire STM32 F0 / F1 / F4 / F7 / G0 / H7 family (and friends) from one directory. It is the primary 32-bit target for Marlin. + +## Build & Test Loop + +Always validate with `mftest` (it regenerates `Marlin/Configuration.h` per target, which raw `pio run -e` does not do reliably): + +```bash +cd "$(git rev-parse --show-toplevel)" +buildroot/bin/mftest -t STM32F103RE -n1 -y # STM32F103RE, generic variant (F1) +buildroot/bin/mftest -t STM32F103RC_btt -n1 -y # SKR Mini E3 (F103RC, variant) +buildroot/bin/mftest -t STM32F401RC_creality -n1 -y # Creality F401RC (F4) +buildroot/bin/mftest -t STM32F446VE_fysetc -n1 -y # FYSETC S6 / Spider (F446) +buildroot/bin/mftest -t STM32F407VE_black -n1 -y # "Black" F407VET6 (F4) +buildroot/bin/mftest -t NUCLEO_F767ZI -n1 -y # Nucleo F767ZI (F7) +buildroot/bin/mftest -t STM32G0B1RE_btt -n1 -y # BIGTREETECH G0B1RE (G0) +buildroot/bin/mftest -t STM32H743VI_btt -n1 -y # BIGTREETECH H743VI (H7) +``` + +Targets should be picked to span the families (F1 / F4 / F7 / G0 / H7) and both the `common_stm32` and `stm32_variant` code paths. The F103 generic (`STM32F103RE`) and an F4 board (`STM32F446VE_fysetc`) are the cheapest, highest-value smoke tests; the H7 build (`STM32H743VI_btt`) exercises the 32-bit-timer / FMC-TFT paths not seen elsewhere. + +> Do NOT use `rm -rf .pio/build/...` to force a rebuild — the cross-profile write guard blocks it and it is unsafe. Use `pio run -e -t clean` (removes only build artifacts) or just let `mftest` rebuild. + +### Maple legacy path is a SEPARATE HAL + +`*_maple` boards (e.g. `chitu_f103`, `mks_robin_maple`, `jgaurora_a5s_a1_maple`, `STM32F103RC_btt_maple`, `STM32F103RC_fysetc_maple`) do **not** use this folder. They compile against the legacy **LibMaple** core via `ini/stm32f1-maple.ini`, which sets `board_build.core = maple` and `-DMAPLE_STM32F1`, and pull sources from `src/HAL/STM32F1/` (a distinct HAL). If a maple board fails, the fix usually lives in `HAL/STM32F1/`, not here. Treat maple as out-of-scope for changes to `HAL/STM32/`. + +## Framework / Platform Integration (upstream, NOT vendored) + +Unlike the AT32 HAL, the STM32 HAL uses the **upstream STM32duino framework**, pulled from the PlatformIO registry — it is **not** vendored under `buildroot/share/PlatformIO/`. + +- Platform: `platform = ststm32@~12.1` (STM32 commons in `ini/stm32-common.ini`). +- Core / framework: `board_build.core = stm32` → the official `framework-arduinoststm32` (Arduino_Core_STM32) package installed by PlatformIO into `~/.platformio/packages/`. +- The **USB Flash Drive** mix-in (`[stm_flash_drive]`) is the one exception: it points `framework-arduinoststm32` at MarlinFirmware's fork archive (`Arduino_Core_STM32/archive/stm-flash-drive.zip`). + +Consequences: + +- Framework/toolchain fixes belong **upstream** (the installed PlatformIO package), not in the Marlin repo. There is no vendored copy to keep in sync — do not go looking for one. +- All STM32 `mftest` targets therefore require the `ststm32` platform + STM32duino core to already be installed in the PlatformIO cache. If a build fails with "unknown package / platform ststm32", run a normal `pio` install rather than editing repo files. +- `HAL_STM32` is the guard macro set by the build (`-DHAL_STM32` in `common_stm32`), not `ARDUINO_ARCH_STM32` alone. HAL sources guard on `#ifdef HAL_STM32`. (The maple path instead defines `ARDUINO_ARCH_STM32 -DMAPLE_STM32F1`.) + +### Gotchas that are not obvious from the source + +1. **`Serial` vs `MSerial1` — there is no bare `Serial`.** The Arduino STM32 core defines `Serial1`…`SerialN`, not a default `Serial`. Marlin's `MarlinSerial.h` wraps them as `MSerial1`…`MSerial10` (`Serial1Class`) and the shared `serial_ports.h` maps `Serial` to `MSerial1` only when a port index 0 is selected. **Do not reference the bare `Serial` object in HAL code** expecting the Arduino default — use `MSerialN` or the configured `SERIAL_PORT` index. When `USBCON` is set, `SerialUSB` is wrapped as `MSerialUSB` and `USB_SERIAL_PORT(...)` resolves to it. + +2. **USB-CDC vs hardware UART is a build-flag split, not a code split.** `common_stm32` builds with `-DUSBCON -DUSBD_USE_CDC` (USB CDC is the _default_ serial transport). Many boards `build_unflags` those and set `DISABLE_GENERIC_SERIALUSB` to fall back to a real UART (e.g. `rumba32`, `Anet_ET4`, the `lerdge_*` family) and enable `-DHAL_UART_MODULE_ENABLED`. So "is the port USB or UART?" is decided entirely by `build_flags`/`build_unflags`, not by which `Serial` object you touch. Watch for `-DUSE_USB_FS`, `-DUSBD_USE_CDC_MSC`, `-DUSBD_USE_CDC_COMPOSITE` variants too. + +3. **EEPROM backend is per-board, not auto-selected.** Marlin does NOT auto-pick an EEPROM backend on STM32. Each board must `#define` one of `FLASH_EEPROM_EMULATION`, `SDCARD_EEPROM_EMULATION`, `IIC_EEPROM`, `SPI_EEPROM`, `SRAM_EEPROM`, etc. in its pins/Configuration files. The `eeprom/` subfolder ships ALL of these backends (`eeprom_flash.cpp`, `eeprom_sdcard.cpp`, `eeprom_if_iic.cpp`, `eeprom_bl24cxx.cpp`, `eeprom_sram.cpp`, …) but only the one selected is compiled. `FLASH_EEPROM_EMULATION` (via the core `EEPROM.h`) works only on parts with a single "sector" flash design; `FLASH_EEPROM_LEVELING` is hard-guarded to `STM32F4xx`/`STM32H7xx` only (see `inc/SanityCheck.h`). Do not assume flash-emulation works on an F0/F1/G0. + +4. **Pins must be `#define` macros, or SanityCheck dies.** `inc/SanityCheck.h` opens with `#ifndef PA0 #error "Your ARM platform pins are not defined as macros, only as enums!"`. The STM32duino core represents pins as `PinName` enums, so each board/variant supplies a `pins_arduino.h` (or the generated variant) that re-exposes `PA0`, `PC6`, … as numeric `#define`s. If you add a board and hit that `#error`, the fix is at the pin-define level — **not** by relaxing SanityCheck. (`HAL.h` uses `typedef int32_t pin_t;` for parity with `platform/ststm32`.) + +5. **FastIO needs `FastIO_init()` before use.** `fastio.h` routes `_WRITE`/`_READ`/`_TOGGLE` through `FastIOPortMap[]` and requires `FASTIO_INIT()` (`FastIO_init()`) to be called first; otherwise the port table is unpopulated. The F0/F1/L0/L4 branch uses `BRR` while F2/F4/F7/H7 use the `BSRR` high/low-bit form — a subtle per-family macro split. + +6. **Timers are 16- or 32-bit per part.** `timers.h` defines `hal_timer_t uint32_t` but comments that STM32F0 pauses timers if `UINT32_MAX` is written to a 16-bit counter register; the 32-bit capability is queried at runtime via `IS_TIM_32B_COUNTER_INSTANCE`. Servo, stepper, and temperature timing all depend on `TIMER_SERIAL`/`TIMER_SERVO` being assigned a valid timer (e.g. `-DTIMER_SERIAL=TIM9`), and ISR priorities are pinned (`-DTIM_IRQ_PRIO=13`, USB host `USBH_IRQ_PRIO=3`). Changing a timer without rechecking the priority map can deadlock serial ISRs. + +7. **ADC resolution is forced to 12-bit.** `HAL.h` sets `HAL_ADC_RESOLUTION 12` (or `ADC_RESOLUTION` if defined; `common_stm32` passes `-DADC_RESOLUTION=12`), and `MarlinHAL::adc_init()` calls `analogReadResolution(HAL_ADC_RESOLUTION)`. Don't hardcode a 10-bit assumption in sensor code. + +8. **SPI is wrapped, not the raw core class.** `MarlinSPI.h` wraps the STM32duino `SPIClass` (via `utility/spi_com.h`) and exposes the `SPIClass`/`SPISettings` globals libraries expect (`HAL/shared/HAL_SPI.h`). `spi_pins.h` selects AF pins. Touching SPI for TFT/LCD or card readers goes through this wrapper, not a bare `SPI.begin()`. + +9. **Servo uses `libServo` over the core `Servo`.** `Servo.h` defines `libServo` as a subclass of the STM32duino `Servo`, with `pause_all_servos()`/`resume_all_servos()` (referenced from `HAL.h` as `PAUSE_SERVO_OUTPUT`/`RESUME_SERVO_OUTPUT`) and an interrupt-priority setter. Use `libServo`, not the raw core `Servo`, in HAL-adjacent code. + +10. **TFT backends are conditionally compiled in.** `common_stm32` excludes `src/HAL/STM32/tft` by default and re-adds only the pieces a board needs via `custom_marlin.*` switches (`HAS_LTDC_TFT`, `HAS_FSMC_TFT`, `HAS_SPI_TFT`, `HAS_TFT_XPT2046`, `TFT_TOUCH_DEVICE_GT911`). FSMC needs `-DHAL_SRAM_MODULE_ENABLED`. Don't add a `#include` of a tft cpp expecting it to always be linked — it may be filtered out of the build. + +11. **`M997` firmware flash / reboot is supported.** `HAL.h` defines `PLATFORM_M997_SUPPORT` and `flashFirmware()`; STM32 can apply firmware updates in place (bootloader-dependent). The maple F1 path is the one notable case that uses a different core entirely (see above). + +## Conventions + +- All HAL source is guarded by `#ifdef HAL_STM32` (set by `common_stm32`'s `-DHAL_STM32`). Do not key STM32-specific code on `ARDUINO_ARCH_STM32` alone, since the maple legacy path also defines that. +- Keep HAL changes in `Marlin/src/HAL/STM32/` and board pin changes in `Marlin/src/pins/stm32f1/` (and the other `pins/` families). Variant `pins_arduino.h` files for `stm32_variant` boards are auto-generated by `buildroot/share/PlatformIO/scripts/generic_create_variant.py` — edit the source pins file, not the generated variant. +- PlatformIO integration lives in `ini/stm32-common.ini` (`[common_stm32]`, `[stm32_variant]`, `[stm_flash_drive]`) and the per-family `ini/stm32f*.ini`; framework behavior is upstream (STM32duino), not vendored — there is no repo copy to mirror. +- When changing serial/USB behavior, remember the upstream-framework + `build_flags` split (gotcha #2): the fix is usually a flag in the board env or a variant define, not HAL C++. +- Pin mappings must remain as numeric `#define`s (gotcha #4). Mark any placeholder pins as such and don't "fix" them to guesses without a source. + +## Related + +- `README.md` in this folder — overview and supported STM32 families. +- Wiki: [[hal-stm32]], [[marlin-src-hal]]. +- `Marlin/src/HAL/shared/` — shared HAL APIs (`eeprom_api`, `serial_ports.h`, `HAL_SPI.h`) the STM32 HAL reuses. +- `Marlin/src/HAL/STM32F1/` — the **legacy LibMaple** HAL for `*_maple` boards (separate core, out of scope for this folder). +- `Marlin/src/pins/stm32f1/` and sibling `pins/` families — board pin tables. diff --git a/Marlin/src/HAL/STM32/HAL.cpp b/Marlin/src/HAL/STM32/HAL.cpp index 610bd0b243bd..018fb4888108 100644 --- a/Marlin/src/HAL/STM32/HAL.cpp +++ b/Marlin/src/HAL/STM32/HAL.cpp @@ -43,8 +43,8 @@ #endif #if HAS_SD_HOST_DRIVE - #include "msc_sd.h" - #include "usbd_cdc_if.h" + #include "sd/msc_sd.h" + #include #endif // ------------------------ @@ -66,11 +66,11 @@ void MarlinHAL::init() { // Ensure F_CPU is a constant expression. // If the compiler breaks here, it means that delay code that should compute at compile time will not work. // So better safe than sorry here. - constexpr int cpuFreq = F_CPU; + constexpr unsigned int cpuFreq = F_CPU; UNUSED(cpuFreq); - #if HAS_MEDIA && DISABLED(ONBOARD_SDIO) && (defined(SDSS) && SDSS != -1) - OUT_WRITE(SDSS, HIGH); // Try to set SDSS inactive before any other SPI users start up + #if HAS_MEDIA && DISABLED(ONBOARD_SDIO) && PIN_EXISTS(SD_SS) + OUT_WRITE(SD_SS_PIN, HIGH); // Try to set SDSS inactive before any other SPI users start up #endif #if PIN_EXISTS(LED) @@ -87,7 +87,7 @@ void MarlinHAL::init() { SetTimerInterruptPriorities(); - #if ENABLED(EMERGENCY_PARSER) && (USBD_USE_CDC || USBD_USE_CDC_MSC) + #if ENABLED(EMERGENCY_PARSER) && ANY(USBD_USE_CDC, USBD_USE_CDC_MSC) USB_Hook_init(); #endif @@ -97,7 +97,7 @@ void MarlinHAL::init() { #if PIN_EXISTS(USB_CONNECT) OUT_WRITE(USB_CONNECT_PIN, !USB_CONNECT_INVERTING); // USB clear connection - delay(1000); // Give OS time to notice + delay_ms(1000); // Give OS time to notice WRITE(USB_CONNECT_PIN, USB_CONNECT_INVERTING); #endif } @@ -114,7 +114,7 @@ void MarlinHAL::idletask() { void MarlinHAL::reboot() { NVIC_SystemReset(); } uint8_t MarlinHAL::get_reset_source() { - return + return ( #ifdef RCC_FLAG_IWDGRST // Some sources may not exist... RESET != __HAL_RCC_GET_FLAG(RCC_FLAG_IWDGRST) ? RST_WATCHDOG : #endif @@ -134,7 +134,7 @@ uint8_t MarlinHAL::get_reset_source() { RESET != __HAL_RCC_GET_FLAG(RCC_FLAG_PORRST) ? RST_POWER_ON : #endif 0 - ; + ); } void MarlinHAL::clear_reset_source() { __HAL_RCC_CLEAR_RESET_FLAGS(); } diff --git a/Marlin/src/HAL/STM32/HAL.h b/Marlin/src/HAL/STM32/HAL.h index 276f031685fe..24889c872a10 100644 --- a/Marlin/src/HAL/STM32/HAL.h +++ b/Marlin/src/HAL/STM32/HAL.h @@ -23,18 +23,13 @@ #define CPU_32_BIT -#include "../../core/macros.h" -#include "../shared/Marduino.h" +#include "../../inc/MarlinConfigPre.h" + #include "../shared/math_32bit.h" #include "../shared/HAL_SPI.h" #include "temp_soc.h" #include "fastio.h" #include "Servo.h" -#include "MarlinSerial.h" - -#include "../../inc/MarlinConfigPre.h" - -#include // // Default graphical display delays @@ -43,85 +38,17 @@ #define CPU_ST7920_DELAY_2 40 #define CPU_ST7920_DELAY_3 340 -// ------------------------ -// Serial ports -// ------------------------ -#ifdef USBCON - #include - #include "../../core/serial_hook.h" - typedef ForwardSerial1Class< decltype(SerialUSB) > DefaultSerial1; - extern DefaultSerial1 MSerialUSB; -#endif - -#define _MSERIAL(X) MSerial##X -#define MSERIAL(X) _MSERIAL(X) - -#if WITHIN(SERIAL_PORT, 1, 9) - #define MYSERIAL1 MSERIAL(SERIAL_PORT) -#elif !defined(USBCON) - #error "SERIAL_PORT must be from 1 to 9." -#elif SERIAL_PORT == -1 - #define MYSERIAL1 MSerialUSB -#else - #error "SERIAL_PORT must be from 1 to 9, or -1 for Native USB." -#endif - -#ifdef SERIAL_PORT_2 - #if WITHIN(SERIAL_PORT_2, 1, 9) - #define MYSERIAL2 MSERIAL(SERIAL_PORT_2) - #elif !defined(USBCON) - #error "SERIAL_PORT_2 must be from 1 to 9." - #elif SERIAL_PORT_2 == -1 - #define MYSERIAL2 MSerialUSB - #else - #error "SERIAL_PORT_2 must be from 1 to 9, or -1 for Native USB." - #endif -#endif - -#ifdef SERIAL_PORT_3 - #if WITHIN(SERIAL_PORT_3, 1, 9) - #define MYSERIAL3 MSERIAL(SERIAL_PORT_3) - #elif !defined(USBCON) - #error "SERIAL_PORT_3 must be from 1 to 9." - #elif SERIAL_PORT_3 == -1 - #define MYSERIAL3 MSerialUSB - #else - #error "SERIAL_PORT_3 must be from 1 to 9, or -1 for Native USB." - #endif -#endif - -#ifdef MMU2_SERIAL_PORT - #if WITHIN(MMU2_SERIAL_PORT, 1, 9) - #define MMU2_SERIAL MSERIAL(MMU2_SERIAL_PORT) - #elif !defined(USBCON) - #error "MMU2_SERIAL_PORT must be from 1 to 9." - #elif MMU2_SERIAL_PORT == -1 - #define MMU2_SERIAL MSerialUSB - #else - #error "MMU2_SERIAL_PORT must be from 1 to 9, or -1 for Native USB." - #endif -#endif +// +// Serial Ports +// -#ifdef LCD_SERIAL_PORT - #if WITHIN(LCD_SERIAL_PORT, 1, 9) - #define LCD_SERIAL MSERIAL(LCD_SERIAL_PORT) - #elif !defined(USBCON) - #error "LCD_SERIAL_PORT must be from 1 to 9." - #elif LCD_SERIAL_PORT == -1 - #define LCD_SERIAL MSerialUSB - #else - #error "LCD_SERIAL_PORT must be from 1 to 9, or -1 for Native USB." - #endif - #if HAS_DGUS_LCD - #define LCD_SERIAL_TX_BUFFER_FREE() LCD_SERIAL.availableForWrite() - #endif -#endif +#include "MarlinSerial.h" /** * 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 // @@ -136,8 +63,6 @@ // Types // ------------------------ -typedef double isr_float_t; // FPU ops are used for single-precision, so use double for ISRs. - typedef int32_t pin_t; // Parity with platform/ststm32 class libServo; @@ -230,7 +155,7 @@ class MarlinHAL { static void delay_ms(const int ms) { delay(ms); } - // Tasks, called from idle() + // Tasks, called from marlin.idle() static void idletask(); // Reset diff --git a/Marlin/src/HAL/STM32/HAL_SPI.cpp b/Marlin/src/HAL/STM32/HAL_SPI.cpp index cc035ecffabc..b220ae6a7885 100644 --- a/Marlin/src/HAL/STM32/HAL_SPI.cpp +++ b/Marlin/src/HAL/STM32/HAL_SPI.cpp @@ -37,7 +37,7 @@ static SPISettings spiConfig; // Public functions // ------------------------ -#if ENABLED(SOFTWARE_SPI) +#if ANY(SOFTWARE_SPI, FORCE_SOFT_SPI) // ------------------------ // Software SPI diff --git a/Marlin/src/HAL/STM32/HardwareSerial.cpp b/Marlin/src/HAL/STM32/HardwareSerial.cpp index 2a389447b78f..58360cc31e75 100644 --- a/Marlin/src/HAL/STM32/HardwareSerial.cpp +++ b/Marlin/src/HAL/STM32/HardwareSerial.cpp @@ -37,7 +37,17 @@ #include "HardwareSerial.h" #include "uart.h" -// USART/UART PIN MAPPING FOR STM32F0/F1/F2/F4/F7 +// Prevent selection of LPUART1 on STM32H7xx +#if defined(STM32H7xx) && (PIN_SERIAL1_TX == PA_9) + #undef PIN_SERIAL1_TX + #define PIN_SERIAL1_TX PA_9_ALT1 +#endif +#if defined(STM32H7xx) && (PIN_SERIAL1_RX == PA_10) + #undef PIN_SERIAL1_RX + #define PIN_SERIAL1_RX PA_10_ALT1 +#endif + +// USART/UART pin mapping for STM32F0/F1/F2/F4/F7/H7 #ifndef PIN_SERIAL1_TX #define PIN_SERIAL1_TX PA9 #endif @@ -75,46 +85,6 @@ #define PIN_SERIAL6_RX PC7 #endif -// TODO: Get from include file - -#if ANY(STM32F2xx, STM32F4xx, STM32F7xx) - - #define RCC_AHB1Periph_DMA1 ((uint32_t)0x00200000) - #define RCC_AHB1Periph_DMA2 ((uint32_t)0x00400000) - - void RCC_AHB1PeriphClockCmd(uint32_t RCC_AHB1Periph, FunctionalState NewState) { - // Check the parameters - assert_param(IS_RCC_AHB1_CLOCK_PERIPH(RCC_AHB1Periph)); - - assert_param(IS_FUNCTIONAL_STATE(NewState)); - if (NewState != DISABLE) - RCC->AHB1ENR |= RCC_AHB1Periph; - else - RCC->AHB1ENR &= ~RCC_AHB1Periph; - } - -#endif - -#if ANY(STM32F0xx, STM32F1xx) - - #define RCC_AHBPeriph_DMA1 ((uint32_t)0x00000001) - #define RCC_AHBPeriph_DMA2 ((uint32_t)0x00000002) - - void RCC_AHBPeriphClockCmd(uint32_t RCC_AHBPeriph, FunctionalState NewState) { - /* Check the parameters */ - assert_param(IS_RCC_AHB_PERIPH(RCC_AHBPeriph)); - assert_param(IS_FUNCTIONAL_STATE(NewState)); - - if (NewState != DISABLE) - RCC->AHBENR |= RCC_AHBPeriph; - else - RCC->AHBENR &= ~RCC_AHBPeriph; - } - -#endif - -// END OF TODO------------------------------------------------------ - // SerialEvent functions are weak, so when the user doesn't define them, // the linker just sets their address to 0 (which is checked below). #ifdef USING_HW_SERIAL1 @@ -161,11 +131,12 @@ HAL_HardwareSerial::HAL_HardwareSerial(void *peripheral) { setRx(PIN_SERIAL1_RX); setTx(PIN_SERIAL1_TX); _uart_index = 0; - #ifdef DMA2_Stream2 - RX_DMA = { USART1, RCC_AHB1Periph_DMA2, 4, DMA2_Stream2 }; + + #ifdef DMA2_Stream2 // F2 / F4 / F7 / H7 + RX_DMA = { USART1, 2, DMA2_Stream2 }; // USART, DMA controller no., DMA stream #endif - #ifdef DMA1_Channel5 - RX_DMA = { USART1, RCC_AHBPeriph_DMA1, DMA1, DMA1_Channel5 }; + #ifdef DMA1_Channel5 // F0 / F1 + RX_DMA = { USART1, 1, DMA1_Channel5 }; // USART, DMA controller no., DMA channel #endif } else if (peripheral == USART2) { @@ -173,10 +144,10 @@ HAL_HardwareSerial::HAL_HardwareSerial(void *peripheral) { setTx(PIN_SERIAL2_TX); _uart_index = 1; #ifdef DMA1_Stream5 - RX_DMA = { USART2, RCC_AHB1Periph_DMA1, 4, DMA1_Stream5 }; + RX_DMA = { USART2, 1, DMA1_Stream5 }; #endif #ifdef DMA1_Channel6 - RX_DMA = { USART2, RCC_AHBPeriph_DMA1, DMA1, DMA1_Channel6 }; + RX_DMA = { USART2, 1, DMA1_Channel6 }; #endif } else if (peripheral == USART3) { @@ -184,17 +155,17 @@ HAL_HardwareSerial::HAL_HardwareSerial(void *peripheral) { setTx(PIN_SERIAL3_TX); _uart_index = 2; #ifdef DMA1_Stream1 - RX_DMA = { USART3, RCC_AHB1Periph_DMA1, 4, DMA1_Stream1 }; + RX_DMA = { USART3, 1, DMA1_Stream1 }; #endif #ifdef DMA1_Channel3 // F0 has no support for UART3, requires system remapping - RX_DMA = { USART3, RCC_AHBPeriph_DMA1, DMA1, DMA1_Channel3 }; + RX_DMA = { USART3, 1, DMA1_Channel3 }; #endif } #ifdef USART4 // Only F2 / F4 / F7 else if (peripheral == USART4) { #ifdef DMA1_Stream2 - RX_DMA = { USART4, RCC_AHB1Periph_DMA1, 4, DMA1_Stream2 }; + RX_DMA = { USART4, 1, DMA1_Stream2 }; #endif setRx(PIN_SERIAL4_RX); setTx(PIN_SERIAL4_TX); @@ -205,10 +176,10 @@ HAL_HardwareSerial::HAL_HardwareSerial(void *peripheral) { #ifdef UART4 else if (peripheral == UART4) { #ifdef DMA1_Stream2 - RX_DMA = { UART4, RCC_AHB1Periph_DMA1, 4, DMA1_Stream2 }; + RX_DMA = { UART4, 1, DMA1_Stream2 }; #endif #ifdef DMA2_Channel3 // STM32F0xx has only 3 UARTs - RX_DMA = { UART4, RCC_AHBPeriph_DMA2, DMA2, DMA2_Channel3 }; + RX_DMA = { UART4, 2, DMA2_Channel3 }; #endif setRx(PIN_SERIAL4_RX); setTx(PIN_SERIAL4_TX); @@ -216,10 +187,10 @@ HAL_HardwareSerial::HAL_HardwareSerial(void *peripheral) { } #endif - #ifdef UART5 // Only F2 / F4 / F7 + #ifdef UART5 // Only F2 / F4 / F7 / H7 else if (peripheral == UART5) { #ifdef DMA1_Stream0 - RX_DMA = { UART5, RCC_AHB1Periph_DMA1, 4, DMA1_Stream0 }; + RX_DMA = { UART5, 1, DMA1_Stream0 }; #endif setRx(PIN_SERIAL5_RX); setTx(PIN_SERIAL5_TX); @@ -227,10 +198,10 @@ HAL_HardwareSerial::HAL_HardwareSerial(void *peripheral) { } #endif - #ifdef USART6 // Only F2 / F4 / F7 + #ifdef USART6 // Only F2 / F4 / F7 / H7 else if (peripheral == USART6) { #ifdef DMA2_Stream1 - RX_DMA = { USART6, RCC_AHB1Periph_DMA2, 4, DMA2_Stream1 }; + RX_DMA = { USART6, 2, DMA2_Stream1 }; #endif setRx(PIN_SERIAL6_RX); setTx(PIN_SERIAL6_TX); @@ -238,7 +209,7 @@ HAL_HardwareSerial::HAL_HardwareSerial(void *peripheral) { } #endif - else { // else get the pins of the first peripheral occurence in PinMap + else { // else get the pins of the first peripheral occurrence in PinMap _serial.pin_rx = pinmap_pin(peripheral, PinMap_UART_RX); _serial.pin_tx = pinmap_pin(peripheral, PinMap_UART_TX); } @@ -271,14 +242,34 @@ void HAL_HardwareSerial::init(PinName _rx, PinName _tx) { * @param obj : pointer to serial_t structure * @retval last character received */ -int HAL_HardwareSerial::_tx_complete_irq(serial_t *obj) { - // If interrupts are enabled, there must be more data in the output buffer. Send the next byte - obj->tx_tail = (obj->tx_tail + 1) % TX_BUFFER_SIZE; - if (obj->tx_head == obj->tx_tail) return -1; +#if DISABLED(STM32H7xx) - return 0; -} + int HAL_HardwareSerial::_tx_complete_irq(serial_t *obj) { + // If interrupts are enabled, there must be more data in the output buffer. Send the next byte + obj->tx_tail = (obj->tx_tail + 1) % TX_BUFFER_SIZE; + if (obj->tx_head == obj->tx_tail) + return -1; + + return 0; + } + +#else // STM32H7xx, has different uart_attach_tx_callback + + int HAL_HardwareSerial::_tx_complete_irq(serial_t *obj) { + // If interrupts are enabled, there must be more data in the output buffer. Send the next byte + obj->tx_tail = (obj->tx_tail + obj->tx_size) % TX_BUFFER_SIZE; + + if (obj->tx_head != obj->tx_tail) { + size_t remaining_data = (TX_BUFFER_SIZE + obj->tx_head - obj->tx_tail) % TX_BUFFER_SIZE; + obj->tx_size = min(remaining_data, (size_t)(TX_BUFFER_SIZE - obj->tx_tail)); + uart_attach_tx_callback(obj, _tx_complete_irq, obj->tx_size); + return -1; + } + return 0; + } + +#endif // Public Methods ////////////////////////////////////////////////////////////// @@ -340,7 +331,7 @@ void HAL_HardwareSerial::update_rx_head() { } #endif - #if ANY(STM32F2xx, STM32F4xx, STM32F7xx) + #if ANY(STM32F2xx, STM32F4xx, STM32F7xx, STM32H7xx) _serial.rx_head = RX_BUFFER_SIZE - RX_DMA.dma_streamRX->NDTR; #endif @@ -352,18 +343,22 @@ void HAL_HardwareSerial::update_rx_head() { int HAL_HardwareSerial::available() { update_rx_head(); + return ((unsigned int)(RX_BUFFER_SIZE + _serial.rx_head - _serial.rx_tail)) % RX_BUFFER_SIZE; } int HAL_HardwareSerial::peek() { update_rx_head(); - if (_serial.rx_head == _serial.rx_tail) return -1; + if (_serial.rx_head == _serial.rx_tail) + return -1; + return _serial.rx_buff[_serial.rx_tail]; } int HAL_HardwareSerial::read() { update_rx_head(); - if (_serial.rx_head == _serial.rx_tail) return -1; // No chars if the head isn't ahead of the tail + if (_serial.rx_head == _serial.rx_tail) + return -1; // No chars if the head isn't ahead of the tail unsigned char c = _serial.rx_buff[_serial.rx_tail]; _serial.rx_tail = (rx_buffer_index_t)(_serial.rx_tail + 1) % RX_BUFFER_SIZE; @@ -380,8 +375,18 @@ size_t HAL_HardwareSerial::write(uint8_t c) { // Interrupt based wri _serial.tx_buff[_serial.tx_head] = c; _serial.tx_head = i; - if (!serial_tx_active(&_serial)) - uart_attach_tx_callback(&_serial, _tx_complete_irq); // Write next byte, launch interrupt + #ifdef STM32H7xx // Support STM32H7xx with different uart_attach_tx_callback + if ((!serial_tx_active(&_serial)) && (_serial.tx_head != _serial.tx_tail)) { + size_t remaining_data = (TX_BUFFER_SIZE + _serial.tx_head -_serial.tx_tail) % TX_BUFFER_SIZE; + _serial.tx_size = min(remaining_data, (size_t)(TX_BUFFER_SIZE - _serial.tx_tail)); + uart_attach_tx_callback(&_serial, _tx_complete_irq, _serial.tx_size); + + return -1; + } + #else + if (!serial_tx_active(&_serial)) + uart_attach_tx_callback(&_serial, _tx_complete_irq); // Write next byte, launch interrupt + #endif return 1; } @@ -390,57 +395,132 @@ void HAL_HardwareSerial::flush() { while ((_serial.tx_head != _serial.tx_tail)) { /* nada */ } // nop, the interrupt handler will free up space for us } -#if ANY(STM32F2xx, STM32F4xx, STM32F7xx) +#if ANY(STM32F2xx, STM32F4xx, STM32F7xx, STM32H7xx) void HAL_HardwareSerial::Serial_DMA_Read_Enable() { - RCC_AHB1PeriphClockCmd(RX_DMA.dma_rcc, ENABLE); // Enable DMA clock - #ifdef STM32F7xx - RX_DMA.dma_streamRX->PAR = (uint32_t)(&RX_DMA.uart->RDR); // RX peripheral receive address (usart) F7 + if (RX_DMA.DMA_ID == 1) + __HAL_RCC_DMA1_CLK_ENABLE(); // Enable DMA1 clock + else + __HAL_RCC_DMA2_CLK_ENABLE(); // Enable DMA2 clock + + // Reset DMA, wait if needed to complete the running process + RX_DMA.dma_streamRX->CR = 0; // DMA stream clear/disable + while (RX_DMA.dma_streamRX->CR & DMA_SxCR_EN) { /* just wait for DMA to complete */ } + + // UART clear/disable + RX_DMA.uart->CR1 = 0; + + // Configure DMA + #if ANY(STM32F7xx, STM32H7xx) // F7 and H7 use RDR (Receive Data Register) + RX_DMA.dma_streamRX->PAR = (uint32_t)(&RX_DMA.uart->RDR); // DMA stream Peripheral Address Register = USART Data Register #else - RX_DMA.dma_streamRX->PAR = (uint32_t)(&RX_DMA.uart->DR); // RX peripheral address (usart) F2 / F4 + RX_DMA.dma_streamRX->PAR = (uint32_t)(&RX_DMA.uart->DR); // DMA stream Peripheral Address Register = USART Data Register #endif - RX_DMA.dma_streamRX->M0AR = (uint32_t)_serial.rx_buff; // RX destination address (memory) - RX_DMA.dma_streamRX->NDTR = RX_BUFFER_SIZE; // RX buffer size - RX_DMA.dma_streamRX->CR = (RX_DMA.dma_channel << 25); // RX channel selection, set to 0 all the other CR bits + RX_DMA.dma_streamRX->M0AR = (uint32_t)_serial.rx_buff; // DMA stream Memory 0 Adress Register = RX buffer address + RX_DMA.dma_streamRX->NDTR = RX_BUFFER_SIZE; // DMA stream Number of Data Transfer Register + + #if DISABLED(STM32H7xx) // Select channel via CR register - RX_DMA.dma_streamRX->CR |= (3 << 16); // RX priority level: Very High + RX_DMA.dma_streamRX->CR = 4 << DMA_SxCR_CHSEL_Pos; // DMA stream Channel Selection, always use channel 4 - //RX_DMA.dma_streamRX->CR &= ~(3 << 13); // RX memory data size: 8 bit - //RX_DMA.dma_streamRX->CR &= ~(3 << 11); // RX peripheral data size: 8 bit - RX_DMA.dma_streamRX->CR |= (1 << 10); // RX memory increment mode - //RX_DMA.dma_streamRX->CR &= ~(1 << 9); // RX peripheral no increment mode - RX_DMA.dma_streamRX->CR |= (1 << 8); // RX circular mode enabled - //RX_DMA.dma_streamRX->CR &= ~(1 << 6); // RX data transfer direction: Peripheral-to-memory - RX_DMA.uart->CR3 |= (1 << 6); // Enable DMA receiver (DMAR) - RX_DMA.dma_streamRX->CR |= (1 << 0); // RX enable DMA + #else // STM32H7xx, select channel with DMAMUX1, channel DMA1 is channel DMAMUX, channel DMA2 is channel DMAMUX + 8 + + if (RX_DMA.uart == USART1) DMAMUX1_Channel10->CCR |= DMA_REQUEST_USART1_RX; // DMA2, Stream 2 + if (RX_DMA.uart == USART2) DMAMUX1_Channel5->CCR |= DMA_REQUEST_USART2_RX; // DMA1, Stream 5 + if (RX_DMA.uart == USART3) DMAMUX1_Channel1->CCR |= DMA_REQUEST_USART3_RX; // DMA1, Stream 1 + #ifdef UART4 + if (RX_DMA.uart == UART4) DMAMUX1_Channel2->CCR |= DMA_REQUEST_UART4_RX; // DMA1, Stream 2 + #endif + #ifdef USART4 + if (RX_DMA.uart == USART4) DMAMUX1_Channel2->CCR |= DMA_REQUEST_USART4_RX; // DMA1, Stream 2 + #endif + #ifdef UART5 + if (RX_DMA.uart == UART5) DMAMUX1_Channel0->CCR |= DMA_REQUEST_UART5_RX; // DMA1, Stream 0 + #endif + #ifdef USART6 + if (RX_DMA.uart == USART6) DMAMUX1_Channel9->CCR |= DMA_REQUEST_USART6_RX; // DMA2, Stream 1 + #endif + + #endif // !STM32H7xx + + // Configure DMA + //RX_DMA.dma_streamRX->CR |= DMA_MBURST_SINGLE; // DMA stream Memory Burst transfer: single transfer = 0b00 + //RX_DMA.dma_streamRX->CR |= DMA_PBURST_SINGLE; // DMA stream Peripheral Burst transfer: single transfer = 0b00 + + #if ENABLED(STM32H7xx) + RX_DMA.dma_streamRX->CR |= DMA_SxCR_TRBUFF; // DMA stream Transfer handle bufferable (required for UART/USART) + #endif + + //RX_DMA.dma_streamRX->CR &= ~DMA_SxCR_CT; // DMA stream Current Target (only in double buffer mode) + //RX_DMA.dma_streamRX->CR &= ~DMA_SxCR_DBM; // DMA stream Double Buffer Mode + //RX_DMA.dma_streamRX->CR |= DMA_PRIORITY_LOW; // DMA stream Priority Level Low = 0b00 + //RX_DMA.dma_streamRX->CR &= ~DMA_SxCR_PINCOS; // DMA stream Peripheral Increment Offset Size + //RX_DMA.dma_streamRX->CR &= ~DMA_SxCR_MSIZE; // DMA stream Memory data Size: 8 bit = 0b00 + //RX_DMA.dma_streamRX->CR &= ~DMA_SxCR_PSIZE; // DMA stream Peripheral data Size: 8 bit = 0b00 + RX_DMA.dma_streamRX->CR |= DMA_SxCR_MINC; // DMA stream Memory Increment enable + //RX_DMA.dma_streamRX->CR &= ~DMA_SxCR_PINC; // DMA stream Peripheral increment + RX_DMA.dma_streamRX->CR |= DMA_SxCR_CIRC; // DMA stream Circular mode enable + //RX_DMA.dma_streamRX->CR |= DMA_PERIPH_TO_MEMORY; // DMA stream transfer Direction: Peripheral-to-memory = b00 + //RX_DMA.dma_streamRX->CR &= ~DMA_SxCR_PFCTRL; // DMA stream Peripheral Flow Controller: DMA = 0 + //RX_DMA.dma_streamRX->CR &= ~DMA_SxCR_TCIE; // DMA stream Transfer Complete Interrupt + //RX_DMA.dma_streamRX->CR &= ~DMA_SxCR_HTIE; // DMA stream Half Transfer Interrupt + //RX_DMA.dma_streamRX->CR &= ~DMA_SxCR_TEIE; // DMA stream Transfer Error Interrupt + //RX_DMA.dma_streamRX->CR &= ~DMA_SxCR_DMEIE; // DMA stream Direct Mode Error Interrupt + RX_DMA.dma_streamRX->CR |= DMA_SxCR_EN; // DMA stream Enable + + // Configure UART/USART + RX_DMA.uart->CR3 |= USART_CR3_DMAR; // UART DMA Receiver + RX_DMA.uart->CR1 |= USART_CR1_TE; // UART Transmitter Enable + RX_DMA.uart->CR1 |= USART_CR1_RE; // UART Receiver Enable + RX_DMA.uart->CR1 |= USART_CR1_UE; // UART Enable } -#endif // STM32F2xx || STM32F4xx || STM32F7xx +#endif // STM32F2xx || STM32F4xx || STM32F7xx || STM32H7xx #if ANY(STM32F0xx, STM32F1xx) void HAL_HardwareSerial::Serial_DMA_Read_Enable() { - RCC_AHBPeriphClockCmd(RX_DMA.dma_rcc, ENABLE); // enable DMA clock - RX_DMA.dma_channelRX->CPAR = (uint32_t)(&RX_DMA.uart->DR); // RX peripheral address (usart) - RX_DMA.dma_channelRX->CMAR = (uint32_t)_serial.rx_buff; // RX destination address (memory) - RX_DMA.dma_channelRX->CNDTR = RX_BUFFER_SIZE; // RX buffer size + if (RX_DMA.DMA_ID == 1) + __HAL_RCC_DMA1_CLK_ENABLE(); // enable DMA1 clock + else + __HAL_RCC_DMA2_CLK_ENABLE(); // enable DMA2 clock - RX_DMA.dma_channelRX->CCR = 0; // RX channel selection, set to 0 all the other CR bits + RX_DMA.dma_channelRX->CCR &= ~USART_CR1_UE; // DMA stream clear/disable + while (RX_DMA.dma_channelRX->CCR & DMA_CCR_EN) { /* just wait for DMA to complete */ } - RX_DMA.dma_channelRX->CCR |= (3<<12); // RX priority level: Very High + // Clear/disable UART and DMA + RX_DMA.uart->CR1 = 0; // UART clear CR1, disable DMA - //RX_DMA.dma_channelRX->CCR &= ~(1<<10); // RX memory data size: 8 bit - //RX_DMA.dma_channelRX->CCR &= ~(1<<8); // RX peripheral data size: 8 bit - RX_DMA.dma_channelRX->CCR |= (1<<7); // RX memory increment mode - //RX_DMA.dma_channelRX->CCR &= ~(1<<6); // RX peripheral no increment mode - RX_DMA.dma_channelRX->CCR |= (1<<5); // RX circular mode enabled - //RX_DMA.dma_channelRX->CCR &= ~(1<<4); // RX data transfer direction: Peripheral-to-memory + // Configure DMA + + #ifdef STM32F0xx + RX_DMA.dma_channelRX->CPAR = (uint32_t)(&RX_DMA.uart->RDR); // DMA channel Peripheral Address Register = USART Data Register + #else + RX_DMA.dma_channelRX->CPAR = (uint32_t)(&RX_DMA.uart->DR); // DMA channel Peripheral Address Register = USART Data Register + #endif - RX_DMA.uart->CR3 |= (1<<6); // enable DMA receiver (DMAR) - RX_DMA.dma_channelRX->CCR |= (1<<0); // RX enable DMA + RX_DMA.dma_channelRX->CMAR = (uint32_t)_serial.rx_buff; // DMA channel Memory Address Register + RX_DMA.dma_channelRX->CNDTR = RX_BUFFER_SIZE; // DMA channel Number of Data Transfer Register + //RX_DMA.dma_channelRX->CCR |= (0b00 << DMA_CCR_PL_Pos); // DMA channel Priority Level: Low = 0b00 + //RX_DMA.dma_channelRX->CCR &= ~DMA_CCR_MSIZE; // DMA channel Data Size: 8 bit = 0 + //RX_DMA.dma_channelRX->CCR &= ~DMA_CCR_PSIZE; // DMA channel Peripheral data size: 8 bit = 0 + RX_DMA.dma_channelRX->CCR |= DMA_CCR_MINC; // DMA channel Memory Increment enable + //RX_DMA.dma_channelRX->CCR &= ~DMA_CCR_PINC; // DMA channel Peripheral Increment disable + RX_DMA.dma_channelRX->CCR |= DMA_CCR_CIRC; // DMA channel Circular mode enable + //RX_DMA.dma_channelRX->CCR &= ~DMA_CCR_DIR; // DMA channel Data Transfer direction: 0=Read peripheral, 1=Read memory + //RX_DMA.dma_channelRX->CCR &= ~DMA_CCR_TEIE; // DMA channel Transfer Error Interrupt + //RX_DMA.dma_channelRX->CCR &= ~DMA_CCR_HTIE; // DMA channel Half Transfer Interrupt + //RX_DMA.dma_channelRX->CCR &= ~DMA_CCR_TCIE; // DMA channel Transfer Complete Interrupt + RX_DMA.dma_channelRX->CCR |= DMA_CCR_EN; // DMA channel enable + + // Configure UART/USART + RX_DMA.uart->CR3 |= USART_CR3_DMAR; // UART DMA Receiver enabled + RX_DMA.uart->CR1 |= USART_CR1_TE; // UART Transmitter Enable + RX_DMA.uart->CR1 |= USART_CR1_RE; // UART Receiver Enable + RX_DMA.uart->CR1 |= USART_CR1_UE; // UART Enable } #endif // STM32F0xx || STM32F1xx diff --git a/Marlin/src/HAL/STM32/HardwareSerial.h b/Marlin/src/HAL/STM32/HardwareSerial.h index cc564322b4d5..cf976372782a 100644 --- a/Marlin/src/HAL/STM32/HardwareSerial.h +++ b/Marlin/src/HAL/STM32/HardwareSerial.h @@ -38,12 +38,10 @@ typedef struct { USART_TypeDef * uart; - uint32_t dma_rcc; + uint32_t DMA_ID; // DMA1=1; DM2=2; BDMA=3 #if ANY(STM32F0xx, STM32F1xx) // F0 / F1 - DMA_TypeDef * dma_controller; DMA_Channel_TypeDef * dma_channelRX; - #else // F2 / F4 / F7 - uint32_t dma_channel; + #else // F2 / F4 / F7 / H7 DMA_Stream_TypeDef * dma_streamRX; #endif } DMA_CFG; diff --git a/Marlin/src/HAL/STM32/MarlinSPI.cpp b/Marlin/src/HAL/STM32/MarlinSPI.cpp index f7c603d77e5c..d109c6561925 100644 --- a/Marlin/src/HAL/STM32/MarlinSPI.cpp +++ b/Marlin/src/HAL/STM32/MarlinSPI.cpp @@ -39,12 +39,16 @@ static void spi_init(spi_t *obj, uint32_t speed, spi_mode_e mode, uint8_t msb, u void MarlinSPI::setClockDivider(uint8_t _div) { _speed = spi_getClkFreq(&_spi);// / _div; - _clockDivider = _div; + if (_clockDivider != _div) { + _clockDivider = _div; + _mustInit = true; + } } void MarlinSPI::begin(void) { - //TODO: only call spi_init if any parameter changed!! + if (!_mustInit) return; spi_init(&_spi, _speed, _dataMode, _bitOrder, _dataSize); + _mustInit = false; } void MarlinSPI::setupDma(SPI_HandleTypeDef &_spiHandle, DMA_HandleTypeDef &_dmaHandle, uint32_t direction, bool minc) { diff --git a/Marlin/src/HAL/STM32/MarlinSPI.h b/Marlin/src/HAL/STM32/MarlinSPI.h index fbd3585ff447..1c106de36442 100644 --- a/Marlin/src/HAL/STM32/MarlinSPI.h +++ b/Marlin/src/HAL/STM32/MarlinSPI.h @@ -76,15 +76,23 @@ class MarlinSPI { /* These methods are deprecated and kept for compatibility. * Use SPISettings with SPI.beginTransaction() to configure SPI parameters. */ - void setBitOrder(BitOrder _order) { _bitOrder = _order; } + void setBitOrder(BitOrder order) { + if (_bitOrder == order) return; + _bitOrder = order; + _mustInit = true; + } - void setDataMode(uint8_t _mode) { - switch (_mode) { + void setDataMode(uint8_t mode) { + auto previous_mode = _dataMode; + switch (mode) { case SPI_MODE0: _dataMode = SPI_MODE_0; break; case SPI_MODE1: _dataMode = SPI_MODE_1; break; case SPI_MODE2: _dataMode = SPI_MODE_2; break; case SPI_MODE3: _dataMode = SPI_MODE_3; break; + default: return; } + if (previous_mode != _dataMode) + _mustInit = true; } void setClockDivider(uint8_t _div); @@ -104,4 +112,5 @@ class MarlinSPI { pin_t _misoPin; pin_t _sckPin; pin_t _ssPin; + bool _mustInit = true; }; diff --git a/Marlin/src/HAL/STM32/MarlinSerial.cpp b/Marlin/src/HAL/STM32/MarlinSerial.cpp index c4bc62994908..862678373fb1 100644 --- a/Marlin/src/HAL/STM32/MarlinSerial.cpp +++ b/Marlin/src/HAL/STM32/MarlinSerial.cpp @@ -37,6 +37,9 @@ #ifndef USART5 #define USART5 UART5 #endif +#ifndef USART6 + #define USART6 UART6 +#endif #ifndef USART7 #define USART7 UART7 #endif diff --git a/Marlin/src/HAL/STM32/MarlinSerial.h b/Marlin/src/HAL/STM32/MarlinSerial.h index 4ab1e4e75a6b..73ab77d8d484 100644 --- a/Marlin/src/HAL/STM32/MarlinSerial.h +++ b/Marlin/src/HAL/STM32/MarlinSerial.h @@ -33,6 +33,21 @@ #include "../../core/serial_hook.h" +#ifdef USBCON + #include + typedef ForwardSerial1Class< decltype(SerialUSB) > DefaultSerial1; + extern DefaultSerial1 MSerialUSB; + #define USB_SERIAL_PORT(...) MSerialUSB +#endif + +#define SERIAL_INDEX_MIN 1 +#define SERIAL_INDEX_MAX 9 +#include "../shared/serial_ports.h" + +#if defined(LCD_SERIAL_PORT) && ANY(HAS_DGUS_LCD, EXTENSIBLE_UI) + #define LCD_SERIAL_TX_BUFFER_FREE() LCD_SERIAL.availableForWrite() +#endif + #if ENABLED(SERIAL_DMA) struct MarlinSerial : public HAL_HardwareSerial { @@ -53,6 +68,7 @@ inline void begin(unsigned long baud) { begin(baud, SERIAL_8N1); } void _rx_complete_irq(serial_t *obj); + FORCE_INLINE static uint8_t buffer_overruns() { return 0; } // Not implemented. Void to avoid platform-dependent code. protected: usart_rx_callback_t _rx_callback; diff --git a/Marlin/src/HAL/STM32/README.md b/Marlin/src/HAL/STM32/README.md index 7680df665494..cf8aa50d502f 100644 --- a/Marlin/src/HAL/STM32/README.md +++ b/Marlin/src/HAL/STM32/README.md @@ -3,9 +3,10 @@ This HAL is intended to act as the generic STM32 HAL for all STM32 chips (The whole F, H and L family). Currently it supports: - * STM32F0xx - * STM32F1xx - * STM32F4xx - * STM32F7xx + +- STM32F0xx +- STM32F1xx +- STM32F4xx +- STM32F7xx Targeting the official [Arduino STM32 Core](https://github.com/stm32duino/Arduino_Core_STM32). diff --git a/Marlin/src/HAL/STM32/Servo.cpp b/Marlin/src/HAL/STM32/Servo.cpp index 4f026ffc6df4..eb535b1eb191 100644 --- a/Marlin/src/HAL/STM32/Servo.cpp +++ b/Marlin/src/HAL/STM32/Servo.cpp @@ -39,8 +39,8 @@ static_assert(COUNT(servoDelay) == NUM_SERVOS, "SERVO_DELAY must be an array NUM static uint32_t servo_interrupt_priority = NVIC_EncodePriority(NVIC_GetPriorityGrouping(), TIM_IRQ_PRIO, TIM_IRQ_SUBPRIO); // This must be called after the STM32 Servo class has initialized the timer. -// It may only be needed after the first call to attach(), but it is possible -// that is is necessary after every detach() call. To be safe this is currently +// It may only be needed after the first call to attach(), but it's possible +// that this is needed after every detach() call. To be safe this is currently // called after every call to attach(). static void fixServoTimerInterruptPriority() { NVIC_SetPriority(getTimerUpIrq(TIMER_SERVO), servo_interrupt_priority); diff --git a/Marlin/src/HAL/STM32/dogm/u8g_com_stm32duino_swspi.cpp b/Marlin/src/HAL/STM32/dogm/u8g_com_stm32duino_swspi.cpp new file mode 100644 index 000000000000..68c6430538a5 --- /dev/null +++ b/Marlin/src/HAL/STM32/dogm/u8g_com_stm32duino_swspi.cpp @@ -0,0 +1,136 @@ +/** + * 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 / Ryan Power + * + * 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 . + * + */ + +#ifdef HAL_STM32 + +#include "../../../inc/MarlinConfig.h" + +#if ALL(HAS_MARLINUI_U8GLIB, FORCE_SOFT_SPI) + +#include +#include "../../shared/HAL_SPI.h" + +#define nop asm volatile ("\tnop\n") + +static inline uint8_t swSpiTransfer_mode_0(uint8_t b) { + for (uint8_t i = 0; i < 8; ++i) { + const uint8_t state = (b & 0x80) ? HIGH : LOW; + WRITE(DOGLCD_SCK, HIGH); + WRITE(DOGLCD_MOSI, state); + b <<= 1; + WRITE(DOGLCD_SCK, LOW); + } + return b; +} + +static inline uint8_t swSpiTransfer_mode_3(uint8_t b) { + for (uint8_t i = 0; i < 8; ++i) { + const uint8_t state = (b & 0x80) ? HIGH : LOW; + WRITE(DOGLCD_SCK, LOW); + WRITE(DOGLCD_MOSI, state); + b <<= 1; + WRITE(DOGLCD_SCK, HIGH); + } + return b; +} + +static void u8g_sw_spi_shift_out(uint8_t val) { + #if U8G_SPI_USE_MODE_3 + swSpiTransfer_mode_3(val); + #else + swSpiTransfer_mode_0(val); + #endif +} + +static void swSpiInit() { + #if PIN_EXISTS(LCD_RESET) + SET_OUTPUT(LCD_RESET_PIN); + #endif + SET_OUTPUT(DOGLCD_A0); + OUT_WRITE(DOGLCD_SCK, LOW); + OUT_WRITE(DOGLCD_MOSI, LOW); + OUT_WRITE(DOGLCD_CS, HIGH); +} + +uint8_t u8g_com_HAL_STM32_sw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr) { + switch (msg) { + case U8G_COM_MSG_INIT: + swSpiInit(); + break; + + case U8G_COM_MSG_STOP: + break; + + case U8G_COM_MSG_RESET: + #if PIN_EXISTS(LCD_RESET) + WRITE(LCD_RESET_PIN, arg_val); + #endif + break; + + case U8G_COM_MSG_CHIP_SELECT: + #if U8G_SPI_USE_MODE_3 // This LCD SPI is running mode 3 while SD card is running mode 0 + if (arg_val) { // SCK idle state needs to be set to the proper idle state before + // the next chip select goes active + WRITE(DOGLCD_SCK, HIGH); // Set SCK to mode 3 idle state before CS goes active + WRITE(DOGLCD_CS, LOW); + nop; // hold SCK high for a few ns + nop; + } + else { + WRITE(DOGLCD_CS, HIGH); + WRITE(DOGLCD_SCK, LOW); // Set SCK to mode 0 idle state after CS goes inactive + } + #else + WRITE(DOGLCD_CS, !arg_val); + #endif + break; + + case U8G_COM_MSG_WRITE_BYTE: + u8g_sw_spi_shift_out(arg_val); + break; + + case U8G_COM_MSG_WRITE_SEQ: { + uint8_t *ptr = (uint8_t *)arg_ptr; + while (arg_val > 0) { + u8g_sw_spi_shift_out(*ptr++); + arg_val--; + } + } break; + + case U8G_COM_MSG_WRITE_SEQ_P: { + uint8_t *ptr = (uint8_t *)arg_ptr; + while (arg_val > 0) { + u8g_sw_spi_shift_out(u8g_pgm_read(ptr)); + ptr++; + arg_val--; + } + } break; + + case U8G_COM_MSG_ADDRESS: /* define cmd (arg_val = 0) or data mode (arg_val = 1) */ + WRITE(DOGLCD_A0, arg_val); + break; + } + return 1; +} + +#endif // HAS_MARLINUI_U8GLIB && FORCE_SOFT_SPI +#endif // HAL_STM32 diff --git a/Marlin/src/HAL/STM32/eeprom_bl24cxx.cpp b/Marlin/src/HAL/STM32/eeprom/eeprom_bl24cxx.cpp similarity index 94% rename from Marlin/src/HAL/STM32/eeprom_bl24cxx.cpp rename to Marlin/src/HAL/STM32/eeprom/eeprom_bl24cxx.cpp index 3e0bb58dad14..8240f15d3a68 100644 --- a/Marlin/src/HAL/STM32/eeprom_bl24cxx.cpp +++ b/Marlin/src/HAL/STM32/eeprom/eeprom_bl24cxx.cpp @@ -20,7 +20,7 @@ * */ -#include "../platforms.h" +#include "../../platforms.h" #ifdef HAL_STM32 @@ -29,12 +29,12 @@ * with simple implementations supplied by Marlin. */ -#include "../../inc/MarlinConfig.h" +#include "../../../inc/MarlinConfig.h" #if ENABLED(IIC_BL24CXX_EEPROM) -#include "../shared/eeprom_if.h" -#include "../shared/eeprom_api.h" +#include "../../shared/eeprom_if.h" +#include "../../shared/eeprom_api.h" // // PersistentStore diff --git a/Marlin/src/HAL/STM32/eeprom_flash.cpp b/Marlin/src/HAL/STM32/eeprom/eeprom_flash.cpp similarity index 79% rename from Marlin/src/HAL/STM32/eeprom_flash.cpp rename to Marlin/src/HAL/STM32/eeprom/eeprom_flash.cpp index 37963ad50154..5a9062e9565c 100644 --- a/Marlin/src/HAL/STM32/eeprom_flash.cpp +++ b/Marlin/src/HAL/STM32/eeprom/eeprom_flash.cpp @@ -19,15 +19,15 @@ * along with this program. If not, see . * */ -#include "../platforms.h" +#include "../../platforms.h" #ifdef HAL_STM32 -#include "../../inc/MarlinConfig.h" +#include "../../../inc/MarlinConfig.h" #if ENABLED(FLASH_EEPROM_EMULATION) -#include "../shared/eeprom_api.h" +#include "../../shared/eeprom_api.h" // Better: "utility/stm32_eeprom.h", but only after updating stm32duino to 2.0.0 // Use EEPROM.h for compatibility, for now. @@ -50,10 +50,10 @@ #if ENABLED(FLASH_EEPROM_LEVELING) - #include "stm32_def.h" + #include #define DEBUG_OUT ENABLED(EEPROM_CHITCHAT) - #include "../../core/debug_out.h" + #include "../../../core/debug_out.h" #ifndef MARLIN_EEPROM_SIZE #define MARLIN_EEPROM_SIZE 0x1000 // 4KB @@ -63,7 +63,7 @@ #define FLASH_SECTOR (FLASH_SECTOR_TOTAL - 1) #endif #ifndef FLASH_UNIT_SIZE - #define FLASH_UNIT_SIZE 0x20000 // 128kB + #define FLASH_UNIT_SIZE 0x20000 // 128K #endif #ifndef FLASH_ADDRESS_START @@ -74,13 +74,13 @@ #define EEPROM_SLOTS ((FLASH_UNIT_SIZE) / (MARLIN_EEPROM_SIZE)) #define SLOT_ADDRESS(slot) (FLASH_ADDRESS_START + (slot * (MARLIN_EEPROM_SIZE))) - #define UNLOCK_FLASH() if (!flash_unlocked) { \ - HAL_FLASH_Unlock(); \ - __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR | \ - FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR | FLASH_FLAG_PGSERR); \ - flash_unlocked = true; \ - } - #define LOCK_FLASH() if (flash_unlocked) { HAL_FLASH_Lock(); flash_unlocked = false; } + #ifdef STM32H7xx + #define FLASHWORD_SIZE 32U // STM32H7xx a FLASHWORD is 32 bytes (256 bits) + #define FLASH_FLAGS_TO_CLEAR (FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR | FLASH_FLAG_PGSERR) + #else + #define FLASHWORD_SIZE 4U // STM32F4xx a FLASHWORD is 4 bytes sizeof(uint32_t) + #define FLASH_FLAGS_TO_CLEAR (FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR | FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR | FLASH_FLAG_PGSERR) + #endif #define EMPTY_UINT32 ((uint32_t)-1) #define EMPTY_UINT8 ((uint8_t)-1) @@ -88,7 +88,7 @@ static uint8_t ram_eeprom[MARLIN_EEPROM_SIZE] __attribute__((aligned(4))) = {0}; static int current_slot = -1; - static_assert(0 == MARLIN_EEPROM_SIZE % 4, "MARLIN_EEPROM_SIZE must be a multiple of 4"); // Ensure copying as uint32_t is safe + static_assert(0 == MARLIN_EEPROM_SIZE % FLASHWORD_SIZE, "MARLIN_EEPROM_SIZE must be a multiple of the FLASHWORD size"); // Ensure copying as uint32_t is safe static_assert(0 == FLASH_UNIT_SIZE % MARLIN_EEPROM_SIZE, "MARLIN_EEPROM_SIZE must divide evenly into your FLASH_UNIT_SIZE"); static_assert(FLASH_UNIT_SIZE >= MARLIN_EEPROM_SIZE, "FLASH_UNIT_SIZE must be greater than or equal to your MARLIN_EEPROM_SIZE"); static_assert(IS_FLASH_SECTOR(FLASH_SECTOR), "FLASH_SECTOR is invalid"); @@ -125,13 +125,13 @@ bool PersistentStore::access_start() { } if (current_slot == -1) { // We didn't find anything, so we'll just initialize to empty - for (int i = 0; i < MARLIN_EEPROM_SIZE; i++) ram_eeprom[i] = EMPTY_UINT8; + for (int i = 0; i < long(MARLIN_EEPROM_SIZE); i++) ram_eeprom[i] = EMPTY_UINT8; current_slot = EEPROM_SLOTS; } else { // load current settings uint8_t *eeprom_data = (uint8_t *)SLOT_ADDRESS(current_slot); - for (int i = 0; i < MARLIN_EEPROM_SIZE; i++) ram_eeprom[i] = eeprom_data[i]; + for (int i = 0; i < long(MARLIN_EEPROM_SIZE); i++) ram_eeprom[i] = eeprom_data[i]; DEBUG_ECHOLNPGM("EEPROM loaded from slot ", current_slot, "."); } eeprom_data_written = false; @@ -147,11 +147,15 @@ bool PersistentStore::access_start() { bool PersistentStore::access_finish() { if (eeprom_data_written) { + #ifdef STM32F4xx // MCU may come up with flash error bits which prevent some flash operations. // Clear flags prior to flash operations to prevent errors. __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR | FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR | FLASH_FLAG_PGSERR); #endif + #ifdef STM32H7xx + __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR | FLASH_FLAG_PGSERR); + #endif #if ENABLED(FLASH_EEPROM_LEVELING) @@ -170,7 +174,12 @@ bool PersistentStore::access_finish() { EraseInitStruct.NbSectors = 1; current_slot = EEPROM_SLOTS - 1; - UNLOCK_FLASH(); + + if (!flash_unlocked) { + HAL_FLASH_Unlock(); + __HAL_FLASH_CLEAR_FLAG(FLASH_FLAGS_TO_CLEAR); + flash_unlocked = true; + } TERN_(HAS_PAUSE_SERVO_OUTPUT, PAUSE_SERVO_OUTPUT()); hal.isr_off(); @@ -181,26 +190,37 @@ bool PersistentStore::access_finish() { DEBUG_ECHOLNPGM("HAL_FLASHEx_Erase=", status); DEBUG_ECHOLNPGM("GetError=", HAL_FLASH_GetError()); DEBUG_ECHOLNPGM("SectorError=", SectorError); - LOCK_FLASH(); + if (flash_unlocked) { + HAL_FLASH_Lock(); + flash_unlocked = false; + } return false; } } - UNLOCK_FLASH(); + if (!flash_unlocked) { + HAL_FLASH_Unlock(); + __HAL_FLASH_CLEAR_FLAG(FLASH_FLAGS_TO_CLEAR); + flash_unlocked = true; + } uint32_t offset = 0, address = SLOT_ADDRESS(current_slot), - address_end = address + MARLIN_EEPROM_SIZE, - data = 0; + address_end = address + MARLIN_EEPROM_SIZE; bool success = true; while (address < address_end) { - memcpy(&data, ram_eeprom + offset, sizeof(data)); - status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, address, data); + #ifdef STM32H7xx + status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_FLASHWORD, address, uint32_t(ram_eeprom + offset)); + #else + //memcpy(&data, ram_eeprom + offset, sizeof(data)); // IRON, IMPROVED + //status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, address, data); + status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, address, *(uint32_t*)(ram_eeprom + offset)); // IRON, OPTIMIZED + #endif if (status == HAL_OK) { - address += sizeof(uint32_t); - offset += sizeof(uint32_t); + address += FLASHWORD_SIZE; + offset += FLASHWORD_SIZE; } else { DEBUG_ECHOLNPGM("HAL_FLASH_Program=", status); @@ -211,7 +231,10 @@ bool PersistentStore::access_finish() { } } - LOCK_FLASH(); + if (flash_unlocked) { + HAL_FLASH_Lock(); + flash_unlocked = false; + } if (success) { eeprom_data_written = false; diff --git a/Marlin/src/HAL/STM32/eeprom_if_iic.cpp b/Marlin/src/HAL/STM32/eeprom/eeprom_if_iic.cpp similarity index 88% rename from Marlin/src/HAL/STM32/eeprom_if_iic.cpp rename to Marlin/src/HAL/STM32/eeprom/eeprom_if_iic.cpp index ad8712c0c075..2733c8f28346 100644 --- a/Marlin/src/HAL/STM32/eeprom_if_iic.cpp +++ b/Marlin/src/HAL/STM32/eeprom/eeprom_if_iic.cpp @@ -20,7 +20,7 @@ * */ -#include "../platforms.h" +#include "../../platforms.h" #ifdef HAL_STM32 @@ -29,12 +29,12 @@ * Enable USE_SHARED_EEPROM if not supplied by the framework. */ -#include "../../inc/MarlinConfig.h" +#include "../../../inc/MarlinConfig.h" #if ENABLED(IIC_BL24CXX_EEPROM) -#include "../../libs/BL24CXX.h" -#include "../shared/eeprom_if.h" +#include "../../../libs/BL24CXX.h" +#include "../../shared/eeprom_if.h" void eeprom_init() { BL24CXX::init(); } @@ -44,7 +44,7 @@ void eeprom_init() { BL24CXX::init(); } void eeprom_write_byte(uint8_t *pos, uint8_t value) { const unsigned eeprom_address = (unsigned)pos; - return BL24CXX::writeOneByte(eeprom_address, value); + BL24CXX::writeOneByte(eeprom_address, value); } uint8_t eeprom_read_byte(uint8_t *pos) { diff --git a/Marlin/src/HAL/STM32/eeprom_sdcard.cpp b/Marlin/src/HAL/STM32/eeprom/eeprom_sdcard.cpp similarity index 92% rename from Marlin/src/HAL/STM32/eeprom_sdcard.cpp rename to Marlin/src/HAL/STM32/eeprom/eeprom_sdcard.cpp index 071d0bac0008..64da3745d13d 100644 --- a/Marlin/src/HAL/STM32/eeprom_sdcard.cpp +++ b/Marlin/src/HAL/STM32/eeprom/eeprom_sdcard.cpp @@ -20,7 +20,7 @@ * */ -#include "../platforms.h" +#include "../../platforms.h" #ifdef HAL_STM32 @@ -28,12 +28,12 @@ * Implementation of EEPROM settings in SD Card */ -#include "../../inc/MarlinConfig.h" +#include "../../../inc/MarlinConfig.h" #if ENABLED(SDCARD_EEPROM_EMULATION) -#include "../shared/eeprom_api.h" -#include "../../sd/cardreader.h" +#include "../../shared/eeprom_api.h" +#include "../../../sd/cardreader.h" #define EEPROM_FILENAME "eeprom.dat" @@ -54,7 +54,7 @@ bool PersistentStore::access_start() { int bytes_read = file.read(HAL_eeprom_data, MARLIN_EEPROM_SIZE); if (bytes_read < 0) return false; - for (; bytes_read < MARLIN_EEPROM_SIZE; bytes_read++) + for (; bytes_read < long(MARLIN_EEPROM_SIZE); bytes_read++) HAL_eeprom_data[bytes_read] = 0xFF; file.close(); return true; diff --git a/Marlin/src/HAL/STM32/eeprom_sram.cpp b/Marlin/src/HAL/STM32/eeprom/eeprom_sram.cpp similarity index 93% rename from Marlin/src/HAL/STM32/eeprom_sram.cpp rename to Marlin/src/HAL/STM32/eeprom/eeprom_sram.cpp index 58a67f1759ab..a9d62ec29cac 100644 --- a/Marlin/src/HAL/STM32/eeprom_sram.cpp +++ b/Marlin/src/HAL/STM32/eeprom/eeprom_sram.cpp @@ -19,16 +19,16 @@ * along with this program. If not, see . * */ -#include "../platforms.h" +#include "../../platforms.h" #ifdef HAL_STM32 -#include "../../inc/MarlinConfig.h" +#include "../../../inc/MarlinConfig.h" #if ENABLED(SRAM_EEPROM_EMULATION) -#include "../shared/eeprom_if.h" -#include "../shared/eeprom_api.h" +#include "../../shared/eeprom_if.h" +#include "../../shared/eeprom_api.h" #ifndef MARLIN_EEPROM_SIZE #define MARLIN_EEPROM_SIZE 0x1000 // 4KB diff --git a/Marlin/src/HAL/STM32/eeprom_wired.cpp b/Marlin/src/HAL/STM32/eeprom/eeprom_wired.cpp similarity index 94% rename from Marlin/src/HAL/STM32/eeprom_wired.cpp rename to Marlin/src/HAL/STM32/eeprom/eeprom_wired.cpp index 5440030bd4c4..fa45e6c40de6 100644 --- a/Marlin/src/HAL/STM32/eeprom_wired.cpp +++ b/Marlin/src/HAL/STM32/eeprom/eeprom_wired.cpp @@ -19,11 +19,11 @@ * along with this program. If not, see . * */ -#include "../platforms.h" +#include "../../platforms.h" #ifdef HAL_STM32 -#include "../../inc/MarlinConfig.h" +#include "../../../inc/MarlinConfig.h" #if USE_WIRED_EEPROM @@ -32,8 +32,8 @@ * with simple implementations supplied by Marlin. */ -#include "../shared/eeprom_if.h" -#include "../shared/eeprom_api.h" +#include "../../shared/eeprom_if.h" +#include "../../shared/eeprom_api.h" #ifndef MARLIN_EEPROM_SIZE #define MARLIN_EEPROM_SIZE size_t(E2END + 1) diff --git a/Marlin/src/HAL/STM32/endstop_interrupts.h b/Marlin/src/HAL/STM32/endstop_interrupts.h index ab86bf29c220..e17b8a4c8ea3 100644 --- a/Marlin/src/HAL/STM32/endstop_interrupts.h +++ b/Marlin/src/HAL/STM32/endstop_interrupts.h @@ -45,6 +45,7 @@ void setup_endstop_interrupts() { TERN_(USE_Z4_MAX, _ATTACH(Z4_MAX_PIN)); TERN_(USE_Z4_MIN, _ATTACH(Z4_MIN_PIN)); TERN_(USE_Z_MIN_PROBE, _ATTACH(Z_MIN_PROBE_PIN)); + TERN_(USE_CALIBRATION, _ATTACH(CALIBRATION_PIN)); TERN_(USE_I_MAX, _ATTACH(I_MAX_PIN)); TERN_(USE_I_MIN, _ATTACH(I_MIN_PIN)); TERN_(USE_J_MAX, _ATTACH(J_MAX_PIN)); diff --git a/Marlin/src/HAL/STM32/fastio.h b/Marlin/src/HAL/STM32/fastio.h index af2941c49c1a..a92bbc492e20 100644 --- a/Marlin/src/HAL/STM32/fastio.h +++ b/Marlin/src/HAL/STM32/fastio.h @@ -77,6 +77,7 @@ void FastIO_init(); // Must be called before using fast io macros #define SET_INPUT_PULLUP(IO) _SET_MODE(IO, INPUT_PULLUP) //!< Input with Pull-up activation #define SET_INPUT_PULLDOWN(IO) _SET_MODE(IO, INPUT_PULLDOWN) //!< Input with Pull-down activation #define SET_OUTPUT(IO) OUT_WRITE(IO, LOW) +#define SET_OUTPUT_OD(IO) OUT_WRITE_OD(IO, LOW) #define SET_PWM(IO) _SET_MODE(IO, PWM) #define IS_INPUT(IO) diff --git a/Marlin/src/HAL/STM32/inc/Conditionals_adv.h b/Marlin/src/HAL/STM32/inc/Conditionals_adv.h index 032716a294ce..f345b925bbb4 100644 --- a/Marlin/src/HAL/STM32/inc/Conditionals_adv.h +++ b/Marlin/src/HAL/STM32/inc/Conditionals_adv.h @@ -30,6 +30,3 @@ #undef F_CPU #define F_CPU BOARD_F_CPU #endif - -// The Sensitive Pins array is not optimizable -#define RUNTIME_ONLY_ANALOG_TO_DIGITAL diff --git a/Marlin/src/HAL/STM32/inc/Conditionals_post.h b/Marlin/src/HAL/STM32/inc/Conditionals_post.h index 6c97a635b3cc..8d72e720c14e 100644 --- a/Marlin/src/HAL/STM32/inc/Conditionals_post.h +++ b/Marlin/src/HAL/STM32/inc/Conditionals_post.h @@ -29,6 +29,6 @@ #endif // Some STM32F4 boards may lose steps when saving to EEPROM during print (PR #17946) -#if defined(STM32F4xx) && ENABLED(FLASH_EEPROM_EMULATION) && PRINTCOUNTER_SAVE_INTERVAL > 0 +#if ALL(STM32F4xx, FLASH_EEPROM_EMULATION) && PRINTCOUNTER_SAVE_INTERVAL > 0 #define PRINTCOUNTER_SYNC #endif diff --git a/Marlin/src/HAL/STM32/inc/SanityCheck.h b/Marlin/src/HAL/STM32/inc/SanityCheck.h index e35b4e59cf81..78d580870d88 100644 --- a/Marlin/src/HAL/STM32/inc/SanityCheck.h +++ b/Marlin/src/HAL/STM32/inc/SanityCheck.h @@ -21,6 +21,13 @@ */ #pragma once +/** + * Require pins to be defined as macros with numerical values + */ +#ifndef PA0 + #error "Your ARM platform pins are not defined as macros, only as enums! Provide pins_arduino.h and/or 'buildroot/share/PlatformIO/variants/*/variant.h' to define the pins." +#endif + /** * Test STM32-specific configuration values for errors at compile-time. */ @@ -36,8 +43,8 @@ #error "SDCARD_EEPROM_EMULATION requires SDSUPPORT. Enable SDSUPPORT or choose another EEPROM emulation." #endif -#if !defined(STM32F4xx) && ENABLED(FLASH_EEPROM_LEVELING) - #error "FLASH_EEPROM_LEVELING is currently only supported on STM32F4 hardware." +#if NONE(STM32F4xx, STM32H7xx) && ENABLED(FLASH_EEPROM_LEVELING) + #error "FLASH_EEPROM_LEVELING is currently only supported on STM32F4/H7 hardware." // IRON #endif #if ENABLED(SERIAL_STATS_MAX_RX_QUEUED) @@ -58,7 +65,7 @@ * Check for common serial pin conflicts */ #define _CHECK_SERIAL_PIN(N) (( \ - BTN_EN1 == N || BTN_EN2 == N ||DOGLCD_CS == N || HEATER_BED_PIN == N || FAN0_PIN == N || \ + BTN_EN1 == N || BTN_EN2 == N || DOGLCD_CS == N || HEATER_BED_PIN == N || FAN0_PIN == N || \ SDIO_D2_PIN == N || SDIO_D3_PIN == N || SDIO_CK_PIN == N || SDIO_CMD_PIN == N || \ Y_STEP_PIN == N || Y_ENABLE_PIN == N || E0_ENABLE_PIN == N || POWER_LOSS_PIN == N \ )) diff --git a/Marlin/src/HAL/STM32/pinsDebug.h b/Marlin/src/HAL/STM32/pinsDebug.h index 13990a69f5ef..89e2514af0b0 100644 --- a/Marlin/src/HAL/STM32/pinsDebug.h +++ b/Marlin/src/HAL/STM32/pinsDebug.h @@ -21,6 +21,26 @@ */ #pragma once +/** + * Pins Debugging for STM32 + * + * - NUMBER_PINS_TOTAL + * - MULTI_NAME_PAD + * - getPinByIndex(index) + * - printPinNameByIndex(index) + * - getPinIsDigitalByIndex(index) + * - digitalPinToAnalogIndex(pin) + * - getValidPinMode(pin) + * - isValidPin(pin) + * - isAnalogPin(pin) + * - digitalRead_mod(pin) + * - pwm_status(pin) + * - printPinPWM(pin) + * - printPinPort(pin) + * - printPinNumber(pin) + * - printPinAnalog(pin) + */ + #include #ifndef NUM_DIGITAL_PINS @@ -34,11 +54,11 @@ * that a CPU has. * * VARIABLES: - * Ard_num - Arduino pin number - defined by the platform. It is used by digitalRead and - * digitalWrite commands and by M42. - * - does not contain port/pin info - * - is not in port/pin order - * - typically a variant will only assign Ard_num to port/pins that are actually used + * A - Arduino pin number - defined by the platform. It is used by digitalRead and + * digitalWrite commands and by M42. + * - does not contain port/pin info + * - is not in port/pin order + * - typically a variant will only assign Ard_num to port/pins that are actually used * Index - M43 counter - only used to get Ard_num * x - a parameter/argument used to search the pin_array to try to find a signal name * associated with a Ard_num @@ -98,15 +118,11 @@ const XrefInfo pin_xref[] PROGMEM = { #define MODE_PIN_ALT 2 // Alternate function mode #define MODE_PIN_ANALOG 3 // Analog mode -#define PIN_NUM(P) (P & 0x000F) -#define PIN_NUM_ALPHA_LEFT(P) (((P & 0x000F) < 10) ? ('0' + (P & 0x000F)) : '1') -#define PIN_NUM_ALPHA_RIGHT(P) (((P & 0x000F) > 9) ? ('0' + (P & 0x000F) - 10) : 0 ) -#define PORT_NUM(P) ((P >> 4) & 0x0007) -#define PORT_ALPHA(P) ('A' + (P >> 4)) - -/** - * Translation of routines & variables used by pinsDebug.h - */ +#define PIN_NUM(P) ((P) & 0x000F) +#define PIN_NUM_ALPHA_LEFT(P) ((((P) & 0x000F) < 10) ? ('0' + ((P) & 0x000F)) : '1') +#define PIN_NUM_ALPHA_RIGHT(P) ((((P) & 0x000F) > 9) ? ('0' + ((P) & 0x000F) - 10) : 0 ) +#define PORT_NUM(P) (((P) >> 4) & 0x0007) +#define PORT_ALPHA(P) ('A' + ((P) >> 4)) #if NUM_ANALOG_FIRST >= NUM_DIGITAL_PINS #define HAS_HIGH_ANALOG_PINS 1 @@ -115,42 +131,40 @@ 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 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 - -// 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 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(A) extDigitalRead(A) // must use Arduino pin numbers when doing reads +#define printPinNumber(Q) +#define printPinAnalog(P) do{ sprintf_P(buffer, PSTR(" (A%2d) "), digitalPinToAnalogIndex(P)); SERIAL_ECHO(buffer); }while(0) +#define digitalPinToAnalogIndex(P) -1 // will report analog pin number in the print port routine +#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 // // Pin Mapping for M43 // -#define GET_PIN_MAP_PIN_M43(Index) pin_xref[Index].Ard_num +#define GET_PIN_MAP_PIN_M43(x) pin_xref[x].Ard_num #ifndef M43_NEVER_TOUCH - #define _M43_NEVER_TOUCH(Index) (Index >= 9 && Index <= 12) // SERIAL/USB pins: PA9(TX) PA10(RX) PA11(USB_DM) PA12(USB_DP) - #ifdef KILL_PIN - #define M43_NEVER_TOUCH(Index) m43_never_touch(Index) + #define _M43_NEVER_TOUCH(x) WITHIN(x, 9, 12) // SERIAL/USB pins: PA9(TX) PA10(RX) PA11(USB_DM) PA12(USB_DP) + #if PIN_EXISTS(KILL) + #define M43_NEVER_TOUCH(x) m43_never_touch(x) - bool m43_never_touch(const pin_t Index) { + bool m43_never_touch(const pin_t index) { static pin_t M43_kill_index = -1; if (M43_kill_index < 0) for (M43_kill_index = 0; M43_kill_index < NUMBER_PINS_TOTAL; M43_kill_index++) if (KILL_PIN == GET_PIN_MAP_PIN_M43(M43_kill_index)) break; - return _M43_NEVER_TOUCH(Index) || Index == M43_kill_index; // KILL_PIN and SERIAL/USB + return _M43_NEVER_TOUCH(index) || index == M43_kill_index; // KILL_PIN and SERIAL/USB } #else - #define M43_NEVER_TOUCH(Index) _M43_NEVER_TOUCH(Index) + #define M43_NEVER_TOUCH(index) _M43_NEVER_TOUCH(index) #endif #endif -uint8_t get_pin_mode(const pin_t Ard_num) { - const PinName dp = digitalPinToPinName(Ard_num); +uint8_t get_pin_mode(const pin_t pin) { + const PinName dp = digitalPinToPinName(pin); uint32_t ll_pin = STM_LL_GPIO_PIN(dp); GPIO_TypeDef *port = get_GPIO_Port(STM_PORT(dp)); uint32_t mode = LL_GPIO_GetPinMode(port, ll_pin); @@ -164,41 +178,41 @@ uint8_t get_pin_mode(const pin_t Ard_num) { } } -bool GET_PINMODE(const pin_t Ard_num) { - const uint8_t pin_mode = get_pin_mode(Ard_num); +bool getValidPinMode(const pin_t pin) { + const uint8_t pin_mode = get_pin_mode(pin); return pin_mode == MODE_PIN_OUTPUT || pin_mode == MODE_PIN_ALT; // assume all alt definitions are PWM } -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; +int8_t digital_pin_to_analog_pin(const pin_t pin) { + if (WITHIN(pin, NUM_ANALOG_FIRST, NUM_ANALOG_LAST)) + return pin - NUM_ANALOG_FIRST; - const uint32_t ind = digitalPinToAnalogInput(Ard_num); + const int8_t ind = digitalPinToAnalogIndex(pin); return (ind < NUM_ANALOG_INPUTS) ? ind : -1; } -bool IS_ANALOG(const pin_t Ard_num) { - return get_pin_mode(Ard_num) == MODE_PIN_ANALOG; +bool isAnalogPin(const pin_t pin) { + return get_pin_mode(pin) == MODE_PIN_ANALOG; } -bool is_digital(const pin_t Ard_num) { - const uint8_t pin_mode = get_pin_mode(pin_array[Ard_num].pin); +bool is_digital(const pin_t pin) { + const uint8_t pin_mode = get_pin_mode(pin_array[pin].pin); return pin_mode == MODE_PIN_INPUT || pin_mode == MODE_PIN_OUTPUT; } -void print_port(const pin_t Ard_num) { +void printPinPort(const pin_t pin) { char buffer[16]; - pin_t Index; - for (Index = 0; Index < NUMBER_PINS_TOTAL; Index++) - if (Ard_num == GET_PIN_MAP_PIN_M43(Index)) break; + pin_t index; + for (index = 0; index < NUMBER_PINS_TOTAL; index++) + if (pin == GET_PIN_MAP_PIN_M43(index)) break; - const char * ppa = pin_xref[Index].Port_pin_alpha; + const char * ppa = pin_xref[index].Port_pin_alpha; sprintf_P(buffer, PSTR("%s"), ppa); SERIAL_ECHO(buffer); if (ppa[3] == '\0') SERIAL_CHAR(' '); // print analog pin number - const int8_t Port_pin = digital_pin_to_analog_pin(Ard_num); + const int8_t Port_pin = digital_pin_to_analog_pin(pin); if (Port_pin >= 0) { sprintf_P(buffer, PSTR(" (A%d) "), Port_pin); SERIAL_ECHO(buffer); @@ -208,13 +222,12 @@ void print_port(const pin_t Ard_num) { SERIAL_ECHO_SP(7); // Print number to be used with M42 - int calc_p = Ard_num; - if (Ard_num > NUM_DIGITAL_PINS) { + int calc_p = pin; + if (pin > NUM_DIGITAL_PINS) { calc_p -= NUM_ANALOG_FIRST; if (calc_p > 7) calc_p += 8; } - SERIAL_ECHOPGM(" M42 P", calc_p); - SERIAL_CHAR(' '); + SERIAL_ECHO(F(" M42 P"), calc_p, C(' ')); if (calc_p < 100) { SERIAL_CHAR(' '); if (calc_p < 10) @@ -222,15 +235,15 @@ void print_port(const pin_t Ard_num) { } } -bool pwm_status(const pin_t Ard_num) { - return get_pin_mode(Ard_num) == MODE_PIN_ALT; +bool pwm_status(const pin_t pin) { + return get_pin_mode(pin) == MODE_PIN_ALT; } -void pwm_details(const pin_t Ard_num) { +void printPinPWM(const pin_t pin) { #ifndef STM32F1xx - if (pwm_status(Ard_num)) { + if (pwm_status(pin)) { uint32_t alt_all = 0; - const PinName dp = digitalPinToPinName(Ard_num); + const PinName dp = digitalPinToPinName(pin); pin_t pin_number = uint8_t(PIN_NUM(dp)); const bool over_7 = pin_number >= 8; const uint8_t ind = over_7 ? 1 : 0; @@ -285,4 +298,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 diff --git a/Marlin/src/HAL/STM32/msc_sd.cpp b/Marlin/src/HAL/STM32/sd/msc_sd.cpp similarity index 95% rename from Marlin/src/HAL/STM32/msc_sd.cpp rename to Marlin/src/HAL/STM32/sd/msc_sd.cpp index 5c8bee9c62bc..9bb65aab4afd 100644 --- a/Marlin/src/HAL/STM32/msc_sd.cpp +++ b/Marlin/src/HAL/STM32/sd/msc_sd.cpp @@ -20,20 +20,19 @@ * along with this program. If not, see . * */ -#include "../platforms.h" +#include "../../platforms.h" #ifdef HAL_STM32 -#include "../../inc/MarlinConfigPre.h" +#include "../../../inc/MarlinConfigPre.h" #if HAS_SD_HOST_DRIVE -#include "../shared/Marduino.h" -#include "msc_sd.h" -#include "usbd_core.h" +#include "../../../sd/cardreader.h" -#include "../../sd/cardreader.h" +#include "msc_sd.h" +#include #include #include @@ -49,7 +48,8 @@ class Sd2CardUSBMscHandler : public USBMscHandler { public: DiskIODriver* diskIODriver() { - #if ENABLED(MULTI_VOLUME) + // TODO: Explore a variable shared volume, or auto share the un-mounted volume(s) + #if HAS_MULTI_VOLUME #if SHARED_VOLUME_IS(SD_ONBOARD) return &card.media_driver_sdcard; #elif SHARED_VOLUME_IS(USB_FLASH_DRIVE) diff --git a/Marlin/src/HAL/STM32/msc_sd.h b/Marlin/src/HAL/STM32/sd/msc_sd.h similarity index 100% rename from Marlin/src/HAL/STM32/msc_sd.h rename to Marlin/src/HAL/STM32/sd/msc_sd.h diff --git a/Marlin/src/HAL/STM32/usb_host.cpp b/Marlin/src/HAL/STM32/sd/usb_host.cpp similarity index 89% rename from Marlin/src/HAL/STM32/usb_host.cpp rename to Marlin/src/HAL/STM32/sd/usb_host.cpp index afafe1d4f3d1..ab3ce07ebee1 100644 --- a/Marlin/src/HAL/STM32/usb_host.cpp +++ b/Marlin/src/HAL/STM32/sd/usb_host.cpp @@ -20,18 +20,17 @@ * */ -#include "../platforms.h" +#include "../../platforms.h" #ifdef HAL_STM32 -#include "../../inc/MarlinConfig.h" +#include "../../../inc/MarlinConfig.h" #if ALL(USE_OTG_USB_HOST, USBHOST) #include "usb_host.h" -#include "../shared/Marduino.h" -#include "usbh_core.h" -#include "usbh_msc.h" +#include +#include USBH_HandleTypeDef hUsbHost; USBHost usb; @@ -89,9 +88,9 @@ void USBHost::setUsbTaskState(uint8_t state) { capacity = info.capacity.block_nbr / 2000; block_size = info.capacity.block_size; block_count = info.capacity.block_nbr; - //SERIAL_ECHOLNPGM("info.capacity.block_nbr : %ld\n", info.capacity.block_nbr); - //SERIAL_ECHOLNPGM("info.capacity.block_size: %d\n", info.capacity.block_size); - //SERIAL_ECHOLNPGM("capacity : %d MB\n", capacity); + //SERIAL_ECHOLNPGM("info.capacity.block_nbr : ", info.capacity.block_nbr); + //SERIAL_ECHOLNPGM("info.capacity.block_size: ", info.capacity.block_size); + //SERIAL_ECHOLNPGM("capacity : ", capacity, "MB"); } }; diff --git a/Marlin/src/HAL/STM32/usb_host.h b/Marlin/src/HAL/STM32/sd/usb_host.h similarity index 100% rename from Marlin/src/HAL/STM32/usb_host.h rename to Marlin/src/HAL/STM32/sd/usb_host.h diff --git a/Marlin/src/HAL/STM32/sdio.cpp b/Marlin/src/HAL/STM32/sdio.cpp index de388b8e146a..0fb7607849eb 100644 --- a/Marlin/src/HAL/STM32/sdio.cpp +++ b/Marlin/src/HAL/STM32/sdio.cpp @@ -52,6 +52,9 @@ #include #include #include + // Arduino_Core_STM32 2.6.0 split PinMap_SD into per-signal maps and dropped this + // declaration from PeripheralPins.h, but Marlin's variants still define the combined map. + extern const PinMap PinMap_SD[]; #else #error "SDIO is only supported with STM32F103xE, STM32F103xG, STM32F4xx, STM32F7xx, and STM32H7xx." #endif @@ -238,7 +241,7 @@ void HAL_SD_MspInit(SD_HandleTypeDef *hsd) { hdma_sdio.Init.MemInc = DMA_MINC_ENABLE; hdma_sdio.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD; hdma_sdio.Init.MemDataAlignment = DMA_MDATAALIGN_WORD; - hdma_sdio.Init.Priority = DMA_PRIORITY_LOW; + hdma_sdio.Init.Priority = DMA_PRIORITY_MEDIUM; __HAL_LINKDMA(&hsd, hdmarx, hdma_sdio); __HAL_LINKDMA(&hsd, hdmatx, hdma_sdio); diff --git a/Marlin/src/HAL/STM32/spi_pins.h b/Marlin/src/HAL/STM32/spi_pins.h index 7f341a8c253f..da6fa0d160b5 100644 --- a/Marlin/src/HAL/STM32/spi_pins.h +++ b/Marlin/src/HAL/STM32/spi_pins.h @@ -33,6 +33,3 @@ #ifndef SD_MOSI_PIN #define SD_MOSI_PIN PIN_SPI_MOSI #endif -#ifndef SD_SS_PIN - #define SD_SS_PIN PIN_SPI_SS -#endif diff --git a/Marlin/src/HAL/STM32/temp_soc.h b/Marlin/src/HAL/STM32/temp_soc.h index 05fad695c3c0..cc165dd5e463 100644 --- a/Marlin/src/HAL/STM32/temp_soc.h +++ b/Marlin/src/HAL/STM32/temp_soc.h @@ -341,6 +341,6 @@ #elif defined(TS_TYPICAL_V) && defined(TS_TYPICAL_SLOPE) && defined(TS_TYPICAL_TEMP) - #define TEMP_SOC_SENSOR(RAW) ((TS_TYPICAL_V - (RAW) / float(OVERSAMPLENR) / float(HAL_ADC_RANGE) * (float(ADC_VREF_MV) / 1000.0f)) / ((TS_TYPICAL_SLOPE) / 1000) + TS_TYPICAL_TEMP) + #define TEMP_SOC_SENSOR(RAW) ((TS_TYPICAL_V - (RAW) / float(OVERSAMPLENR) / float(HAL_ADC_RANGE) * (float(ADC_VREF_MV) / 1000.0f)) / ((TS_TYPICAL_SLOPE) / 1000.0f) + TS_TYPICAL_TEMP) #endif diff --git a/Marlin/src/HAL/STM32/tft/tft_fsmc.cpp b/Marlin/src/HAL/STM32/tft/tft_fsmc.cpp index 4dffe8b4fcc4..70caef677810 100644 --- a/Marlin/src/HAL/STM32/tft/tft_fsmc.cpp +++ b/Marlin/src/HAL/STM32/tft/tft_fsmc.cpp @@ -132,6 +132,9 @@ void TFT_FSMC::init() { DMAtx.Init.Priority = DMA_PRIORITY_HIGH; LCD = (LCD_CONTROLLER_TypeDef *)controllerAddress; + + DMAtx.Init.PeriphInc = DMA_PINC_DISABLE; + HAL_DMA_Init(&DMAtx); } uint32_t TFT_FSMC::getID() { @@ -179,14 +182,19 @@ void TFT_FSMC::abort() { } void TFT_FSMC::transmitDMA(uint32_t memoryIncrease, uint16_t *data, uint16_t count) { - DMAtx.Init.PeriphInc = memoryIncrease; - HAL_DMA_Init(&DMAtx); + if (!__IS_DMA_CONFIGURED(&DMAtx) || DMAtx.Init.PeriphInc != memoryIncrease) { + DMAtx.Init.PeriphInc = memoryIncrease; + HAL_DMA_Init(&DMAtx); + } HAL_DMA_Start(&DMAtx, (uint32_t)data, (uint32_t)&(LCD->RAM), count); + TERN_(TFT_SHARED_IO, while (isBusy())); } void TFT_FSMC::transmit(uint32_t memoryIncrease, uint16_t *data, uint16_t count) { - DMAtx.Init.PeriphInc = memoryIncrease; - HAL_DMA_Init(&DMAtx); + if (!__IS_DMA_CONFIGURED(&DMAtx) || DMAtx.Init.PeriphInc != memoryIncrease) { + DMAtx.Init.PeriphInc = memoryIncrease; + HAL_DMA_Init(&DMAtx); + } dataTransferBegin(); HAL_DMA_Start(&DMAtx, (uint32_t)data, (uint32_t)&(LCD->RAM), count); HAL_DMA_PollForTransfer(&DMAtx, HAL_DMA_FULL_TRANSFER, HAL_MAX_DELAY); diff --git a/Marlin/src/HAL/STM32/tft/tft_spi.cpp b/Marlin/src/HAL/STM32/tft/tft_spi.cpp index cda2eb28e304..71ad14534f8d 100644 --- a/Marlin/src/HAL/STM32/tft/tft_spi.cpp +++ b/Marlin/src/HAL/STM32/tft/tft_spi.cpp @@ -337,7 +337,7 @@ void TFT_SPI::transmitDMA(uint32_t memoryIncrease, uint16_t *data, uint16_t coun SET_BIT(SPIx.Instance->CR2, SPI_CR2_TXDMAEN); // Enable Tx DMA Request #endif - TERN_(TFT_SHARED_IO, while (isBusy()) { /* nada */ }); + TERN_(TFT_SHARED_IO, while (isBusy())); } void TFT_SPI::transmit(uint32_t memoryIncrease, uint16_t *data, uint16_t count) { diff --git a/Marlin/src/HAL/STM32/tft/xpt2046.h b/Marlin/src/HAL/STM32/tft/xpt2046.h index 685c9441aedb..f3d3e532914f 100644 --- a/Marlin/src/HAL/STM32/tft/xpt2046.h +++ b/Marlin/src/HAL/STM32/tft/xpt2046.h @@ -49,7 +49,11 @@ #define TOUCH_INT_PIN -1 #endif -#define XPT2046_DFR_MODE 0x00 +#if PIN_EXISTS(TOUCH_INT) + #define XPT2046_DFR_MODE 0x00 +#else + #define XPT2046_DFR_MODE 0x01 +#endif #define XPT2046_SER_MODE 0x04 #define XPT2046_CONTROL 0x80 diff --git a/Marlin/src/HAL/STM32/timers.cpp b/Marlin/src/HAL/STM32/timers.cpp index 10e0dc43a436..5b58a915f0ab 100644 --- a/Marlin/src/HAL/STM32/timers.cpp +++ b/Marlin/src/HAL/STM32/timers.cpp @@ -81,10 +81,6 @@ #define MCU_TEMP_TIMER 14 // TIM7 is consumed by Software Serial if used. #endif -#ifndef HAL_TIMER_RATE - #define HAL_TIMER_RATE GetStepperTimerClkFreq() -#endif - #ifndef STEP_TIMER #define STEP_TIMER MCU_STEP_TIMER #endif @@ -141,7 +137,7 @@ void HAL_timer_start(const uint8_t timer_num, const uint32_t frequency) { */ timer_instance[timer_num]->setPrescaleFactor(STEPPER_TIMER_PRESCALE); //the -1 is done internally - timer_instance[timer_num]->setOverflow(_MIN(hal_timer_t(HAL_TIMER_TYPE_MAX), (HAL_TIMER_RATE) / (STEPPER_TIMER_PRESCALE) /* /frequency */), TICK_FORMAT); + timer_instance[timer_num]->setOverflow(_MIN(HAL_TIMER_TYPE_MAX, hal_timer_t((HAL_TIMER_RATE) / (STEPPER_TIMER_PRESCALE) /* / frequency */)), TICK_FORMAT); break; case MF_TIMER_TEMP: // TEMP TIMER - any available 16bit timer timer_instance[timer_num] = new HardwareTimer(TEMP_TIMER_DEV); diff --git a/Marlin/src/HAL/STM32/timers.h b/Marlin/src/HAL/STM32/timers.h index 180e24031471..a6c9204a77d7 100644 --- a/Marlin/src/HAL/STM32/timers.h +++ b/Marlin/src/HAL/STM32/timers.h @@ -38,7 +38,7 @@ // of adding a run-time check and HAL_TIMER_TYPE_MAX is refactored to allow unique // values for each timer. #define hal_timer_t uint32_t -#define HAL_TIMER_TYPE_MAX UINT16_MAX +#define HAL_TIMER_TYPE_MAX hal_timer_t(UINT16_MAX) // Marlin timer_instance[] content (unrelated to timer selection) #define MF_TIMER_STEP 0 // Timer Index for Stepper @@ -48,23 +48,28 @@ #define TIMER_INDEX_(T) TIMER##T##_INDEX // TIMER#_INDEX enums (timer_index_t) depend on TIM#_BASE defines. #define TIMER_INDEX(T) TIMER_INDEX_(T) // Convert Timer ID to HardwareTimer_Handle index. -#define TEMP_TIMER_FREQUENCY 1000 // Temperature::isr() is expected to be called at around 1kHz +#ifndef HAL_TIMER_RATE + extern uint32_t GetStepperTimerClkFreq(); + #define HAL_TIMER_RATE GetStepperTimerClkFreq() +#endif + +// Timer configuration constants +#define STEPPER_TIMER_RATE 2000000 +#define TEMP_TIMER_FREQUENCY 1000 // Temperature::isr() should run at ~1kHz -// TODO: get rid of manual rate/prescale/ticks/cycles taken for procedures in stepper.cpp -#define STEPPER_TIMER_RATE 2000000 // 2 Mhz -extern uint32_t GetStepperTimerClkFreq(); -#define STEPPER_TIMER_PRESCALE (GetStepperTimerClkFreq() / (STEPPER_TIMER_RATE)) -#define STEPPER_TIMER_TICKS_PER_US ((STEPPER_TIMER_RATE) / 1000000) // stepper timer ticks per µs +// Timer prescaler calculations +#define STEPPER_TIMER_PRESCALE ((HAL_TIMER_RATE) / (STEPPER_TIMER_RATE)) +#define STEPPER_TIMER_TICKS_PER_US ((STEPPER_TIMER_RATE) / 1000000UL) // (ticks/μs) Stepper Timer ticks per µs -#define PULSE_TIMER_RATE STEPPER_TIMER_RATE -#define PULSE_TIMER_PRESCALE STEPPER_TIMER_PRESCALE -#define PULSE_TIMER_TICKS_PER_US STEPPER_TIMER_TICKS_PER_US +// Pulse Timer (counter) calculations +#define PULSE_TIMER_RATE STEPPER_TIMER_RATE // (Hz) Frequency of Pulse Timer +#define PULSE_TIMER_PRESCALE STEPPER_TIMER_PRESCALE -#define ENABLE_STEPPER_DRIVER_INTERRUPT() HAL_timer_enable_interrupt(MF_TIMER_STEP) -#define DISABLE_STEPPER_DRIVER_INTERRUPT() HAL_timer_disable_interrupt(MF_TIMER_STEP) -#define STEPPER_ISR_ENABLED() HAL_timer_interrupt_enabled(MF_TIMER_STEP) +#define ENABLE_STEPPER_DRIVER_INTERRUPT() HAL_timer_enable_interrupt(MF_TIMER_STEP) +#define DISABLE_STEPPER_DRIVER_INTERRUPT() HAL_timer_disable_interrupt(MF_TIMER_STEP) +#define STEPPER_ISR_ENABLED() HAL_timer_interrupt_enabled(MF_TIMER_STEP) -#define ENABLE_TEMPERATURE_INTERRUPT() HAL_timer_enable_interrupt(MF_TIMER_TEMP) +#define ENABLE_TEMPERATURE_INTERRUPT() HAL_timer_enable_interrupt(MF_TIMER_TEMP) #define DISABLE_TEMPERATURE_INTERRUPT() HAL_timer_disable_interrupt(MF_TIMER_TEMP) extern void Step_Handler(); @@ -116,5 +121,5 @@ FORCE_INLINE static void HAL_timer_set_compare(const uint8_t timer_num, const ha } } -#define HAL_timer_isr_prologue(T) NOOP -#define HAL_timer_isr_epilogue(T) NOOP +inline void HAL_timer_isr_prologue(const uint8_t) {} +inline void HAL_timer_isr_epilogue(const uint8_t) {} diff --git a/Marlin/src/HAL/STM32/u8g/LCD_defines.h b/Marlin/src/HAL/STM32/u8g/LCD_defines.h index 35f1674d69dc..96f73002a5fe 100644 --- a/Marlin/src/HAL/STM32/u8g/LCD_defines.h +++ b/Marlin/src/HAL/STM32/u8g/LCD_defines.h @@ -25,8 +25,11 @@ * STM32 LCD-specific defines */ -uint8_t u8g_com_std_sw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr); // See U8glib-HAL -uint8_t u8g_com_stm32duino_hw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr); // See U8glib-HAL +uint8_t u8g_com_HAL_STM32_sw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr); // u8g_com_stm32duino_swspi.cpp +#define U8G_COM_HAL_SW_SPI_FN u8g_com_HAL_STM32_sw_spi_fn -#define U8G_COM_HAL_SW_SPI_FN u8g_com_std_sw_spi_fn +uint8_t u8g_com_stm32duino_hw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr); // See U8glib-HAL #define U8G_COM_HAL_HW_SPI_FN u8g_com_stm32duino_hw_spi_fn + +uint8_t u8g_com_stm32duino_ssd_i2c_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr); // u8g_com_stm32duino_ssd_i2c.cpp +#define U8G_COM_SSD_I2C_HAL u8g_com_stm32duino_ssd_i2c_fn diff --git a/Marlin/src/HAL/STM32/u8g/u8g_com_stm32duino_ssd_i2c.cpp b/Marlin/src/HAL/STM32/u8g/u8g_com_stm32duino_ssd_i2c.cpp new file mode 100644 index 000000000000..72abe1a656a7 --- /dev/null +++ b/Marlin/src/HAL/STM32/u8g/u8g_com_stm32duino_ssd_i2c.cpp @@ -0,0 +1,194 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2025 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 . + * + */ + +/** + * 2-Wire I2C COM Driver + * + * Handles both Hardware and Software I2C so any pins can be used as SDA and SLC. + * Wire library is used for Hardware I2C. + * SlowSoftWire is used for Software I2C. + * + * Wire / SoftWire library selection can be done automatically at runtime. + * + * SDA and SLC pins must be named DOGLCD_SDA_PIN, DOGLCD_SCL_PIN to distinguish + * from other I2C devices (e.g., EEPROM) that use I2C_SDA_PIN, I2C_SLC_PIN. + */ +#ifdef ARDUINO_ARCH_STM32 + +#include "../../../inc/MarlinConfig.h" + +#if HAS_U8GLIB_I2C_OLED + +#include + +#if ENABLED(U8G_USES_HW_I2C) + #include + #ifndef MASTER_ADDRESS + #define MASTER_ADDRESS 0x01 + #endif +#endif + +#if ENABLED(U8G_USES_SW_I2C) + #include + #include +#endif + +/** + * BUFFER_LENGTH is defined in libraries\Wire\utility\WireBase.h + * Default value is 32 + * Increase this value to 144 to send U8G_COM_MSG_WRITE_SEQ in single block + */ +#ifndef BUFFER_LENGTH + #define BUFFER_LENGTH 32 +#endif +#if BUFFER_LENGTH > 144 + #error "BUFFER_LENGTH should not be greater than 144." +#endif +#define I2C_MAX_LENGTH (BUFFER_LENGTH - 1) + +uint8_t u8g_com_stm32duino_ssd_i2c_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr) { + // Hardware I2C flag + #ifdef COMPILE_TIME_I2C_IS_HARDWARE + constexpr bool isHardI2C = ENABLED(COMPILE_TIME_I2C_IS_HARDWARE); + #else + static bool isHardI2C = false; + static bool i2c_initialized = false; // Flag to only run init/linking code once + if (!i2c_initialized) { // Init runtime linkages + i2c_initialized = true; // Only do this once + I2C_TypeDef *i2cInstance1 = (I2C_TypeDef *)pinmap_peripheral(digitalPinToPinName(DOGLCD_SDA_PIN), PinMap_I2C_SDA); + I2C_TypeDef *i2cInstance2 = (I2C_TypeDef *)pinmap_peripheral(digitalPinToPinName(DOGLCD_SCL_PIN), PinMap_I2C_SCL); + isHardI2C = (i2cInstance1 && (i2cInstance1 == i2cInstance2)); // Found hardware I2C controller + } + #endif + + static uint8_t msgInitCount = 0; // Ignore all messages until 2nd U8G_COM_MSG_INIT + if (msgInitCount) { + if (msg == U8G_COM_MSG_INIT) msgInitCount--; + if (msgInitCount) return -1; + } + + static uint8_t control; + if (isHardI2C) { // Found hardware I2C controller + #if ENABLED(U8G_USES_HW_I2C) + static TwoWire wire2; // A TwoWire object for use below + switch (msg) { + case U8G_COM_MSG_INIT: + wire2.setClock(400000); + wire2.setSCL(DOGLCD_SCL_PIN); + wire2.setSDA(DOGLCD_SDA_PIN); + wire2.begin(MASTER_ADDRESS, 0); // Start as master + break; + + case U8G_COM_MSG_ADDRESS: // Define cmd (arg_val = 0) or data mode (arg_val = 1) + control = arg_val ? 0x40 : 0x00; + break; + + case U8G_COM_MSG_WRITE_BYTE: + wire2.beginTransmission(0x3C); + wire2.write(control); + wire2.write(arg_val); + wire2.endTransmission(); + break; + + case U8G_COM_MSG_WRITE_SEQ: { + uint8_t* dataptr = (uint8_t*)arg_ptr; + #ifdef I2C_MAX_LENGTH + while (arg_val > 0) { + wire2.beginTransmission(0x3C); + wire2.write(control); + if (arg_val <= I2C_MAX_LENGTH) { + wire2.write(dataptr, arg_val); + arg_val = 0; + } + else { + wire2.write(dataptr, I2C_MAX_LENGTH); + arg_val -= I2C_MAX_LENGTH; + dataptr += I2C_MAX_LENGTH; + } + wire2.endTransmission(); + } + #else + wire2.beginTransmission(0x3C); + wire2.write(control); + wire2.write(dataptr, arg_val); + wire2.endTransmission(); + #endif // I2C_MAX_LENGTH + break; + } + } + #endif // U8G_USES_HW_I2C + } + else { // Software I2C + #if ENABLED(U8G_USES_SW_I2C) + static SlowSoftWire sWire = SlowSoftWire(DOGLCD_SDA_PIN, DOGLCD_SCL_PIN); + + switch (msg) { + case U8G_COM_MSG_INIT: + sWire.setClock(400000); + sWire.begin(); // Start as master + break; + + case U8G_COM_MSG_ADDRESS: // Define cmd (arg_val = 0) or data mode (arg_val = 1) + control = arg_val ? 0x40 : 0x00; + break; + + case U8G_COM_MSG_WRITE_BYTE: + sWire.beginTransmission((uint8_t)0x3C); + sWire.write((uint8_t)control); + sWire.write((uint8_t)arg_val); + sWire.endTransmission(); + break; + + case U8G_COM_MSG_WRITE_SEQ: { + uint8_t* dataptr = (uint8_t*)arg_ptr; + #ifdef I2C_MAX_LENGTH + while (arg_val > 0) { + sWire.beginTransmission((uint8_t)0x3C); + sWire.write((uint8_t)control); + if (arg_val <= I2C_MAX_LENGTH) { + sWire.write((const uint8_t *)dataptr, (size_t)arg_val); + arg_val = 0; + } + else { + sWire.write((const uint8_t *)dataptr, I2C_MAX_LENGTH); + arg_val -= I2C_MAX_LENGTH; + dataptr += I2C_MAX_LENGTH; + } + sWire.endTransmission(); + } + #else + sWire.beginTransmission((uint8_t)0x3C); + sWire.write((uint8_t)control); + sWire.write((const uint8_t *)dataptr, (size_t)arg_val); + sWire.endTransmission(); + #endif // I2C_MAX_LENGTH + break; + } + } + #endif // U8G_USES_SW_I2C + } + + return 1; +} + +#endif // HAS_U8GLIB_I2C_OLED +#endif // ARDUINO_ARCH_STM32 diff --git a/Marlin/src/HAL/STM32/usb_serial.cpp b/Marlin/src/HAL/STM32/usb_serial.cpp index 0b2372f3a79d..1ca8b1997623 100644 --- a/Marlin/src/HAL/STM32/usb_serial.cpp +++ b/Marlin/src/HAL/STM32/usb_serial.cpp @@ -26,7 +26,7 @@ #include "../../inc/MarlinConfigPre.h" -#if ENABLED(EMERGENCY_PARSER) && (USBD_USE_CDC || USBD_USE_CDC_MSC) +#if ENABLED(EMERGENCY_PARSER) && ANY(USBD_USE_CDC, USBD_USE_CDC_MSC) #include "usb_serial.h" #include "../../feature/e_parser.h" @@ -56,5 +56,5 @@ void USB_Hook_init() { USBD_CDC_fops.Receive = USBD_CDC_Receive_hook; } -#endif // EMERGENCY_PARSER && USBD_USE_CDC +#endif // EMERGENCY_PARSER && (USBD_USE_CDC || USBD_USE_CDC_MSC) #endif // HAL_STM32 diff --git a/Marlin/src/HAL/STM32F1/AGENTS.md b/Marlin/src/HAL/STM32F1/AGENTS.md new file mode 100644 index 000000000000..aa482a1f9c94 --- /dev/null +++ b/Marlin/src/HAL/STM32F1/AGENTS.md @@ -0,0 +1,72 @@ +# AGENTS.md — STM32F1 HAL + +Working notes and conventions for the **STM32F1 HAL** folder (`Marlin/src/HAL/STM32F1/`). + +This HAL targets STM32F103 (and GD32F103 / N32G455 clones) boards using the **LibMaple-based** "Roger Clark" Arduino_STM32 core (`board_build.core = maple`), NOT the unified STM32duino core that the rest of the STM32 family uses. + +## Build & Test Loop + +Always validate with `mftest` (it regenerates `Marlin/Configuration.h` per target): + +```bash +cd "$(git rev-parse --show-toplevel)" +buildroot/bin/mftest -t STM32F103RC_btt_maple -n1 -y # SKR Mini E3 1.0, 256K +buildroot/bin/mftest -t mks_robin_maple -n1 -y # MKS Robin, FSMC UI +buildroot/bin/mftest -t jgaurora_a5s_a1_maple -n1 -y # JGAurora A5S/A1, ZE +buildroot/bin/mftest -t STM32F103RC_meeb_maple -n1 -y # MEEB_3DP, 512K, USB +buildroot/bin/mftest -t mks_robin_nano_v1v2_maple -n1 -y # Robin Nano, FSMC +buildroot/bin/mftest -t STM32F103VE_longer_maple -n1 -y # Alfawise U20, VE +buildroot/bin/mftest -t mks_robin_pro_maple -n1 -y +buildroot/bin/mftest -t mks_robin_lite_maple -n1 -y +buildroot/bin/mftest -t STM32F103RC_fysetc_maple -n1 -y +buildroot/bin/mftest -t STM32F103RC_btt_USB_maple -n1 -y # USB Composite +buildroot/bin/mftest -t STM32F103VE_ZM3E4V2_USB_maple -n1 -y +``` + +> **Only the `_maple` envs in `ini/stm32f1-maple.ini` compile this HAL** (`build_src_filter` includes `src/HAL/STM32F1`, `-DMAPLE_STM32F1`). The non-maple envs in `ini/stm32f1.ini` (`STM32F103RE`, `mks_robin`, `STM32F103RE_creality`, …) extend `common_stm32` and build the **unified** `src/HAL/STM32` (`-DHAL_STM32`) — they do NOT exercise this folder. + +> Do NOT use `rm -rf .pio/build/...` to force a rebuild — the cross-profile write guard blocks it and it is unsafe. Use `pio run -e -t clean` or just let `mftest` rebuild. + +## Framework / Platform Integration (upstream, not vendored) + +The STM32F1 (LibMaple) core is pulled from the **upstream** `ststm32@~15.4.1` PlatformIO platform (`board_build.core = maple`) — it is **not** vendored by Marlin, so there is no "mirror to installed package" step (that warning is AT32-specific only). Marlin-specific glue lives in: + +- `Marlin/src/HAL/STM32F1/` — the HAL itself. +- `buildroot/share/PlatformIO/scripts/STM32F1_build_flags.py` — emits base C/CXX/LINK flags (`-Os`, `-mcpu=cortex-m3`, `-ffreestanding`, `--specs=nano.specs`, `-IMarlin/src/HAL/STM32F1`, `-DTARGET_STM32F1 -DARDUINO_ARCH_STM32 -DPLATFORM_M997_SUPPORT`). +- `buildroot/share/PlatformIO/scripts/{fix_framework_weakness,stm32_serialbuffer,custom_board,offset_and_rename,STM32F1_create_variant}.py` — run as `pre:`/`custom_marlin` extras (per-board offsets, variant creation for Chitu/Meeb). + +Toolchain is pinned: `toolchain-gccarmnoneeabi@1.120301.0` (else GCC 7.2.1) and `tool-stm32duino`. `lib_ignore = SPI, FreeRTOS701, FreeRTOS821`; `SoftwareSerialM` is added. + +### Gotchas that are not obvious from the source + +1. **Two different "STM32F1" paths.** `ini/stm32f1.ini` targets (e.g. `STM32F103RE`, `mks_robin`) silently use the unified `HAL/STM32`, while only `ini/stm32f1-maple.ini` targets use this `HAL/STM32F1`. Changing one does not test the other — build a `_maple` target to exercise this folder. + +2. **Pins are numeric `#define`s, not an `enum`.** `fastio.h` defines `PA0 0x00` … `PG15 0x6F` and macros use the libmaple `PIN_MAP[IO]` table (e.g. `READ`/`WRITE`). Unlike the AT32 `enum` quirk, `PIN_EXISTS` works in the preprocessor here. BUT the **CB/C8 parts** only expose `PC13/PC14/PC15` (guarded at `fastio.h:97`), while larger parts expose `PC0‑PC15` — pin files must match the die. + +3. **Flash size / linker script are per-board.** `HAL.h` defaults `STM32_FLASH_SIZE` to 256, but 512 for `MCU_STM32F103RE/VE/ZE`. Each env passes its own `board_build.ldscript` (`creality256k.ld`, `creality.ld`, `crealityPro.ld`, `mks_robin.ld`, `mks_robin_mini.ld`, `fly_mini.ld`, `jgaurora_a5s_a1.ld`, `ZONESTAR_ZM3E_*.ld`, …). Mismatched `.ld` → silent overflow or wrong vector table. + +4. **EEPROM backend is auto-fallback, not auto-selected.** `inc/Conditionals_post.h` defines `SDCARD_EEPROM_EMULATION` only when `USE_FALLBACK_EEPROM` (no I2C/SPI/flash EEPROM). `eeprom/` carries `eeprom_sdcard`, `eeprom_wired`, `eeprom_if_iic`, `eeprom_flash`, `eeprom_bl24cxx` — a board pins file must pick one. `SDCARD_EEPROM_EMULATION` requires `SDSUPPORT` (else `SanityCheck.h` warns and undefs it). + +5. **N32 (Nationstar) clone layer.** `HAL_N32.h/.cpp` re-implements ADC/GPIO/DMA register maps for the N32G455 (board `N32G455RE_voxelab_maple`, `-DVOXELAB_N32`). It is not a generic N32 port — don't assume other N32 parts build. + +6. **USB vs hardware UART.** Default is hardware USART (`HardwareSerial`). USB serial is opt-in: `MarlinSerial.h` only defines `USBSerial`/`UsbSerial` under `#ifdef SERIAL_USB`, and the Zonestar/Meeb/BTT-USB targets add `USBComposite for STM32F1` + `-DUSE_USB_COMPOSITE` / `-DSERIAL_USB`. Don't enable USB flags on a UART-only board. + +7. **Timer assignments vary per board.** `TIMER_SERVO`, `TIMER_TONE`, `SS_TIMER`, `TEMP_TIMER_CHAN` are set in each env (e.g. `TIMER_SERVO=TIM5`, `SS_TIMER=4`). Two Chitu envs expose `chitu_v5_gpio_init` / `-DCHITU_V5_Z_MIN_BUGFIX` for a known GPIO-init bug (use when G28/G29 always fail on Chitu V5). + +8. **GD32F103 / N32 reuse this HAL.** `GD32F103RC_voxelab_maple`, `N32G455RE_voxelab_maple`, and `STM32_XL_DENSITY` Z-parts (`jgaurora_a5s_a1_maple`, `chitu_f103_maple`) all build `src/HAL/STM32F1`, so a HAL change can affect clone boards too. + +9. **`SPI` global.** The maple core leaves `SPI` out; `HAL_SPI.cpp` instantiates `SPIClass SPI(...)` and provides `spiBegin()` (mirrors the AT32 pattern). + +## Conventions + +- Source is selected by the `MAPLE_STM32F1` build flag (with `ARDUINO_ARCH_STM32`); some files guard on `BOARD_NR_GPIO_PINS` ("Only in MAPLE_STM32F1", see `pinsDebug.h`). +- Keep HAL changes in `Marlin/src/HAL/STM32F1/` and board pin changes in `Marlin/src/pins/stm32f1/` (and the various `pins/` clones). Build/glue changes go in `buildroot/share/PlatformIO/scripts/` and `ini/stm32f1-maple.ini`. +- Build flags live in `STM32F1_build_flags.py` (a `pre:` extra), **not** in `platform.json`. +- Pin tables in `fastio.h` are MCU-die-specific (CB/C8 vs others) — don't generalize. + +## Related + +- `README.md` in this folder — overview and supported boards. +- Wiki: [[hal-stm32f1]], [[marlin-src-hal]]. +- `Marlin/src/HAL/shared/` — shared HAL APIs (`eeprom_api`, SPI helpers) this HAL reuses. +- `Marlin/src/HAL/STM32/` — the unified STM32duino HAL that the non-maple F1 targets use. diff --git a/Marlin/src/HAL/STM32F1/HAL.cpp b/Marlin/src/HAL/STM32F1/HAL.cpp index e6cbb9fc0605..fc1680cbf4f1 100644 --- a/Marlin/src/HAL/STM32F1/HAL.cpp +++ b/Marlin/src/HAL/STM32F1/HAL.cpp @@ -65,7 +65,8 @@ uint16_t adc_results[ADC_COUNT]; emergency_parser.update(MSerial0.emergency_state, buf[i + total - len]); } #endif -#endif + +#endif // SERIAL_USB && !HAS_SD_HOST_DRIVE // ------------------------ // Watchdog Timer @@ -130,30 +131,31 @@ uint16_t MarlinHAL::adc_result; #include -// Init the AD in continuous capture mode +// Init the ADC in continuous capture mode void MarlinHAL::adc_init() { static const uint8_t adc_pins[] = { - OPTITEM(HAS_TEMP_ADC_0, TEMP_0_PIN) - OPTITEM(HAS_TEMP_ADC_1, TEMP_1_PIN) - OPTITEM(HAS_TEMP_ADC_2, TEMP_2_PIN) - OPTITEM(HAS_TEMP_ADC_3, TEMP_3_PIN) - OPTITEM(HAS_TEMP_ADC_4, TEMP_4_PIN) - OPTITEM(HAS_TEMP_ADC_5, TEMP_5_PIN) - OPTITEM(HAS_TEMP_ADC_6, TEMP_6_PIN) - OPTITEM(HAS_TEMP_ADC_7, TEMP_7_PIN) - OPTITEM(HAS_HEATED_BED, TEMP_BED_PIN) - OPTITEM(HAS_TEMP_CHAMBER, TEMP_CHAMBER_PIN) - OPTITEM(HAS_TEMP_ADC_PROBE, TEMP_PROBE_PIN) - OPTITEM(HAS_TEMP_COOLER, TEMP_COOLER_PIN) - OPTITEM(HAS_TEMP_BOARD, TEMP_BOARD_PIN) - OPTITEM(HAS_TEMP_SOC, TEMP_SOC_PIN) - OPTITEM(FILAMENT_WIDTH_SENSOR, FILWIDTH_PIN) - OPTITEM(HAS_ADC_BUTTONS, ADC_KEYPAD_PIN) - OPTITEM(HAS_JOY_ADC_X, JOY_X_PIN) - OPTITEM(HAS_JOY_ADC_Y, JOY_Y_PIN) - OPTITEM(HAS_JOY_ADC_Z, JOY_Z_PIN) - OPTITEM(POWER_MONITOR_CURRENT, POWER_MONITOR_CURRENT_PIN) - OPTITEM(POWER_MONITOR_VOLTAGE, POWER_MONITOR_VOLTAGE_PIN) + OPTITEM(HAS_TEMP_ADC_0, TEMP_0_PIN ) + OPTITEM(HAS_TEMP_ADC_1, TEMP_1_PIN ) + OPTITEM(HAS_TEMP_ADC_2, TEMP_2_PIN ) + OPTITEM(HAS_TEMP_ADC_3, TEMP_3_PIN ) + OPTITEM(HAS_TEMP_ADC_4, TEMP_4_PIN ) + OPTITEM(HAS_TEMP_ADC_5, TEMP_5_PIN ) + OPTITEM(HAS_TEMP_ADC_6, TEMP_6_PIN ) + OPTITEM(HAS_TEMP_ADC_7, TEMP_7_PIN ) + OPTITEM(HAS_TEMP_ADC_BED, TEMP_BED_PIN ) + OPTITEM(HAS_TEMP_ADC_CHAMBER, TEMP_CHAMBER_PIN ) + OPTITEM(HAS_TEMP_ADC_PROBE, TEMP_PROBE_PIN ) + OPTITEM(HAS_TEMP_ADC_COOLER, TEMP_COOLER_PIN ) + OPTITEM(HAS_TEMP_ADC_BOARD, TEMP_BOARD_PIN ) + OPTITEM(HAS_TEMP_ADC_SOC, TEMP_SOC_PIN ) + OPTITEM(HAS_FILWIDTH_ADC, FILWIDTH_PIN ) + OPTITEM(HAS_FILWIDTH2_ADC, FILWIDTH2_PIN ) + OPTITEM(HAS_ADC_BUTTONS, ADC_KEYPAD_PIN ) + OPTITEM(HAS_JOY_ADC_X, JOY_X_PIN ) + OPTITEM(HAS_JOY_ADC_Y, JOY_Y_PIN ) + OPTITEM(HAS_JOY_ADC_Z, JOY_Z_PIN ) + OPTITEM(POWER_MONITOR_CURRENT, POWER_MONITOR_CURRENT_PIN) + OPTITEM(POWER_MONITOR_VOLTAGE, POWER_MONITOR_VOLTAGE_PIN) }; static STM32ADC adc(ADC1); // Configure the ADC @@ -174,27 +176,28 @@ void MarlinHAL::adc_start(const pin_t pin) { ADCIndex pin_index; switch (pin) { default: return; - _TCASE(HAS_TEMP_ADC_0, TEMP_0_PIN, TEMP_0) - _TCASE(HAS_TEMP_ADC_1, TEMP_1_PIN, TEMP_1) - _TCASE(HAS_TEMP_ADC_2, TEMP_2_PIN, TEMP_2) - _TCASE(HAS_TEMP_ADC_3, TEMP_3_PIN, TEMP_3) - _TCASE(HAS_TEMP_ADC_4, TEMP_4_PIN, TEMP_4) - _TCASE(HAS_TEMP_ADC_5, TEMP_5_PIN, TEMP_5) - _TCASE(HAS_TEMP_ADC_6, TEMP_6_PIN, TEMP_6) - _TCASE(HAS_TEMP_ADC_7, TEMP_7_PIN, TEMP_7) - _TCASE(HAS_HEATED_BED, TEMP_BED_PIN, TEMP_BED) - _TCASE(HAS_TEMP_CHAMBER, TEMP_CHAMBER_PIN, TEMP_CHAMBER) - _TCASE(HAS_TEMP_ADC_PROBE, TEMP_PROBE_PIN, TEMP_PROBE) - _TCASE(HAS_TEMP_COOLER, TEMP_COOLER_PIN, TEMP_COOLER) - _TCASE(HAS_TEMP_BOARD, TEMP_BOARD_PIN, TEMP_BOARD) - _TCASE(HAS_TEMP_SOC, TEMP_SOC_PIN, TEMP_SOC) - _TCASE(HAS_JOY_ADC_X, JOY_X_PIN, JOY_X) - _TCASE(HAS_JOY_ADC_Y, JOY_Y_PIN, JOY_Y) - _TCASE(HAS_JOY_ADC_Z, JOY_Z_PIN, JOY_Z) - _TCASE(FILAMENT_WIDTH_SENSOR, FILWIDTH_PIN, FILWIDTH) - _TCASE(HAS_ADC_BUTTONS, ADC_KEYPAD_PIN, ADC_KEY) - _TCASE(POWER_MONITOR_CURRENT, POWER_MONITOR_CURRENT_PIN, POWERMON_CURRENT) - _TCASE(POWER_MONITOR_VOLTAGE, POWER_MONITOR_VOLTAGE_PIN, POWERMON_VOLTAGE) + _TCASE(HAS_TEMP_ADC_0, TEMP_0_PIN, TEMP_0 ) + _TCASE(HAS_TEMP_ADC_1, TEMP_1_PIN, TEMP_1 ) + _TCASE(HAS_TEMP_ADC_2, TEMP_2_PIN, TEMP_2 ) + _TCASE(HAS_TEMP_ADC_3, TEMP_3_PIN, TEMP_3 ) + _TCASE(HAS_TEMP_ADC_4, TEMP_4_PIN, TEMP_4 ) + _TCASE(HAS_TEMP_ADC_5, TEMP_5_PIN, TEMP_5 ) + _TCASE(HAS_TEMP_ADC_6, TEMP_6_PIN, TEMP_6 ) + _TCASE(HAS_TEMP_ADC_7, TEMP_7_PIN, TEMP_7 ) + _TCASE(HAS_TEMP_ADC_BED, TEMP_BED_PIN, TEMP_BED ) + _TCASE(HAS_TEMP_ADC_CHAMBER, TEMP_CHAMBER_PIN, TEMP_CHAMBER ) + _TCASE(HAS_TEMP_ADC_PROBE, TEMP_PROBE_PIN, TEMP_PROBE ) + _TCASE(HAS_TEMP_ADC_COOLER, TEMP_COOLER_PIN, TEMP_COOLER ) + _TCASE(HAS_TEMP_ADC_BOARD, TEMP_BOARD_PIN, TEMP_BOARD ) + _TCASE(HAS_TEMP_ADC_SOC, TEMP_SOC_PIN, TEMP_SOC ) + _TCASE(HAS_FILWIDTH_ADC, FILWIDTH_PIN, FILWIDTH ) + _TCASE(HAS_FILWIDTH2_ADC, FILWIDTH2_PIN, FILWIDTH2 ) + _TCASE(HAS_ADC_BUTTONS, ADC_KEYPAD_PIN, ADC_KEY ) + _TCASE(HAS_JOY_ADC_X, JOY_X_PIN, JOY_X ) + _TCASE(HAS_JOY_ADC_Y, JOY_Y_PIN, JOY_Y ) + _TCASE(HAS_JOY_ADC_Z, JOY_Z_PIN, JOY_Z ) + _TCASE(POWER_MONITOR_CURRENT, POWER_MONITOR_CURRENT_PIN, POWERMON_CURRENT) + _TCASE(POWER_MONITOR_VOLTAGE, POWER_MONITOR_VOLTAGE_PIN, POWERMON_VOLTAGE) } adc_result = (adc_results[(int)pin_index] & 0xFFF) >> (12 - HAL_ADC_RESOLUTION); // shift out unused bits } @@ -252,7 +255,7 @@ void MarlinHAL::init() { #endif #if PIN_EXISTS(USB_CONNECT) OUT_WRITE(USB_CONNECT_PIN, !USB_CONNECT_INVERTING); // USB clear connection - delay(1000); // Give OS time to notice + delay_ms(1000); // Give OS time to notice WRITE(USB_CONNECT_PIN, USB_CONNECT_INVERTING); #endif TERN_(POSTMORTEM_DEBUGGING, install_min_serial()); // Install the minimal serial handler @@ -264,7 +267,7 @@ void MarlinHAL::idletask() { /** * When Marlin is using the SD card it should be locked to prevent it being * accessed from a PC over USB. - * Other HALs use (IS_SD_PRINTING() || IS_SD_FILE_OPEN()) to check for access + * Other HALs use (card.isStillPrinting() || card.isFileOpen()) to check for access * but this won't reliably detect other file operations. To be safe we just lock * the drive whenever Marlin has it mounted. LCDs should include an Unmount * command so drives can be released as needed. diff --git a/Marlin/src/HAL/STM32F1/HAL.h b/Marlin/src/HAL/STM32F1/HAL.h index 41c91e200f89..2c7321403fe4 100644 --- a/Marlin/src/HAL/STM32F1/HAL.h +++ b/Marlin/src/HAL/STM32F1/HAL.h @@ -40,11 +40,9 @@ #include "../../inc/MarlinConfigPre.h" #if HAS_SD_HOST_DRIVE - #include "msc_sd.h" + #include "sd/msc_sd.h" #endif -#include "MarlinSerial.h" - // ------------------------ // Defines // ------------------------ @@ -64,90 +62,17 @@ #endif #endif -// ------------------------ -// Serial ports -// ------------------------ - -#ifdef SERIAL_USB - typedef ForwardSerial1Class< USBSerial > DefaultSerial1; - extern DefaultSerial1 MSerial0; - #if HAS_SD_HOST_DRIVE - #define UsbSerial MarlinCompositeSerial - #else - #define UsbSerial MSerial0 - #endif -#endif - -#define _MSERIAL(X) MSerial##X -#define MSERIAL(X) _MSERIAL(X) - -#if ANY(STM32_HIGH_DENSITY, STM32_XL_DENSITY) - #define NUM_UARTS 5 -#else - #define NUM_UARTS 3 -#endif - -#if SERIAL_PORT == -1 - #define MYSERIAL1 UsbSerial -#elif WITHIN(SERIAL_PORT, 1, NUM_UARTS) - #define MYSERIAL1 MSERIAL(SERIAL_PORT) -#else - #define MYSERIAL1 MSERIAL(1) // dummy port - static_assert(false, "SERIAL_PORT must be from 1 to " STRINGIFY(NUM_UARTS) ". You can also use -1 if the board supports Native USB.") -#endif - -#ifdef SERIAL_PORT_2 - #if SERIAL_PORT_2 == -1 - #define MYSERIAL2 UsbSerial - #elif WITHIN(SERIAL_PORT_2, 1, NUM_UARTS) - #define MYSERIAL2 MSERIAL(SERIAL_PORT_2) - #else - #define MYSERIAL2 MSERIAL(1) // dummy port - static_assert(false, "SERIAL_PORT_2 must be from 1 to " STRINGIFY(NUM_UARTS) ". You can also use -1 if the board supports Native USB.") - #endif -#endif - -#ifdef SERIAL_PORT_3 - #if SERIAL_PORT_3 == -1 - #define MYSERIAL3 UsbSerial - #elif WITHIN(SERIAL_PORT_3, 1, NUM_UARTS) - #define MYSERIAL3 MSERIAL(SERIAL_PORT_3) - #else - #define MYSERIAL3 MSERIAL(1) // dummy port - static_assert(false, "SERIAL_PORT_3 must be from 1 to " STRINGIFY(NUM_UARTS) ". 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 UsbSerial - #elif WITHIN(MMU2_SERIAL_PORT, 1, NUM_UARTS) - #define MMU2_SERIAL MSERIAL(MMU2_SERIAL_PORT) - #else - #define MMU2_SERIAL MSERIAL(1) // dummy port - static_assert(false, "MMU2_SERIAL_PORT must be from 1 to " STRINGIFY(NUM_UARTS) ". You can also use -1 if the board supports Native USB.") - #endif -#endif +// +// Serial Ports +// -#ifdef LCD_SERIAL_PORT - #if LCD_SERIAL_PORT == -1 - #define LCD_SERIAL UsbSerial - #elif WITHIN(LCD_SERIAL_PORT, 1, NUM_UARTS) - #define LCD_SERIAL MSERIAL(LCD_SERIAL_PORT) - #else - #define LCD_SERIAL MSERIAL(1) // dummy port - static_assert(false, "LCD_SERIAL_PORT must be from 1 to " STRINGIFY(NUM_UARTS) ". You can also use -1 if the board supports Native USB.") - #endif - #if HAS_DGUS_LCD - #define LCD_SERIAL_TX_BUFFER_FREE() LCD_SERIAL.availableForWrite() - #endif -#endif +#include "MarlinSerial.h" /** * 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 @@ -262,7 +187,7 @@ class MarlinHAL { static void delay_ms(const int ms) { delay(ms); } - // Tasks, called from idle() + // Tasks, called from marlin.idle() static void idletask(); // Reset diff --git a/Marlin/src/HAL/STM32F1/HAL_N32.cpp b/Marlin/src/HAL/STM32F1/HAL_N32.cpp index 971a344f210d..e45cbbbfff06 100644 --- a/Marlin/src/HAL/STM32F1/HAL_N32.cpp +++ b/Marlin/src/HAL/STM32F1/HAL_N32.cpp @@ -612,7 +612,7 @@ void ADC_DMA_init() { * n32g452 - end ==============================================================================*/ -#define NS_PINRT(V...) do{ SERIAL_ECHO_START(); SERIAL_ECHOLNPAIR(V); }while(0) +#define NS_PINRT(V...) do{ SERIAL_ECHO_START(); SERIAL_ECHOLNPGM(V); }while(0) // Init the AD in continuous capture mode void MarlinHAL::adc_init() { @@ -622,7 +622,7 @@ void MarlinHAL::adc_init() { // GPIO settings reg_temp = ADC_RCC_APB2PCLKEN; - reg_temp |= 0x0f; // Make PORT mouth clock + reg_temp |= 0x0F; // Make PORT mouth clock ADC_RCC_APB2PCLKEN = reg_temp; //reg_temp = NS_GPIOC_PL_CFG; diff --git a/Marlin/src/HAL/STM32F1/HAL_N32.h b/Marlin/src/HAL/STM32F1/HAL_N32.h index 7162e2b97147..719368f6f1da 100644 --- a/Marlin/src/HAL/STM32F1/HAL_N32.h +++ b/Marlin/src/HAL/STM32F1/HAL_N32.h @@ -239,8 +239,8 @@ typedef struct { #define ADC_WORKMODE_SLOW_INTERL ((uint32_t)0x00080000) #define ADC_WORKMODE_ALTER_TRIG ((uint32_t)0x00090000) -#define ADC_EXT_TRIGCONV_T1_CC3 ((uint32_t)0x00040000) //!< For ADC1, ADC2 , ADC3 and ADC4 -#define ADC_EXT_TRIGCONV_NONE ((uint32_t)0x000E0000) //!< For ADC1, ADC2 , ADC3 and ADC4 +#define ADC_EXT_TRIGCONV_T1_CC3 ((uint32_t)0x00040000) //!< For ADC1, ADC2, ADC3, and ADC4 +#define ADC_EXT_TRIGCONV_NONE ((uint32_t)0x000E0000) //!< For ADC1, ADC2, ADC3, and ADC4 #define ADC_DAT_ALIGN_R ((uint32_t)0x00000000) #define ADC_DAT_ALIGN_L ((uint32_t)0x00000800) @@ -603,9 +603,9 @@ typedef struct { #define DMA_CHCFG7_PINC ((uint16_t)0x0040) //!< Peripheral increment mode #define DMA_CHCFG7_MINC ((uint16_t)0x0080) //!< Memory increment mode -#define DMA_CHCFG7_PSIZE , ((uint16_t)0x0300) //!< PSIZE[1:0] bits (Peripheral size) -#define DMA_CHCFG7_PSIZE_0 ((uint16_t)0x0100) //!< Bit 0 -#define DMA_CHCFG7_PSIZE_1 ((uint16_t)0x0200) //!< Bit 1 +#define DMA_CHCFG7_PSIZE ((uint16_t)0x0300) //!< PSIZE[1:0] bits (Peripheral size) +#define DMA_CHCFG7_PSIZE_0 ((uint16_t)0x0100) //!< Bit 0 +#define DMA_CHCFG7_PSIZE_1 ((uint16_t)0x0200) //!< Bit 1 #define DMA_CHCFG7_MSIZE ((uint16_t)0x0C00) //!< MSIZE[1:0] bits (Memory size) #define DMA_CHCFG7_MSIZE_0 ((uint16_t)0x0400) //!< Bit 0 @@ -627,9 +627,9 @@ typedef struct { #define DMA_CHCFG8_PINC ((uint16_t)0x0040) //!< Peripheral increment mode #define DMA_CHCFG8_MINC ((uint16_t)0x0080) //!< Memory increment mode -#define DMA_CHCFG8_PSIZE , ((uint16_t)0x0300) //!< PSIZE[1:0] bits (Peripheral size) -#define DMA_CHCFG8_PSIZE_0 ((uint16_t)0x0100) //!< Bit 0 -#define DMA_CHCFG8_PSIZE_1 ((uint16_t)0x0200) //!< Bit 1 +#define DMA_CHCFG8_PSIZE ((uint16_t)0x0300) //!< PSIZE[1:0] bits (Peripheral size) +#define DMA_CHCFG8_PSIZE_0 ((uint16_t)0x0100) //!< Bit 0 +#define DMA_CHCFG8_PSIZE_1 ((uint16_t)0x0200) //!< Bit 1 #define DMA_CHCFG8_MSIZE ((uint16_t)0x0C00) //!< MSIZE[1:0] bits (Memory size) #define DMA_CHCFG8_MSIZE_0 ((uint16_t)0x0400) //!< Bit 0 @@ -800,7 +800,7 @@ void ADC_StartCalibration(ADC_Module* NS_ADCx); void ADC_EnableDMA(ADC_Module* NS_ADCx, uint32_t Cmd); /**================================================================ - * Configure ADC interrupt enable enable + * Configure ADC interrupt enable ================================================================*/ void ADC_ConfigInt(ADC_Module* NS_ADCx, uint16_t ADC_IT, uint32_t Cmd); diff --git a/Marlin/src/HAL/STM32F1/MarlinSerial.h b/Marlin/src/HAL/STM32F1/MarlinSerial.h index 53bcd4847638..c2dc5bd57145 100644 --- a/Marlin/src/HAL/STM32F1/MarlinSerial.h +++ b/Marlin/src/HAL/STM32F1/MarlinSerial.h @@ -28,6 +28,29 @@ #include "../../inc/MarlinConfigPre.h" #include "../../core/serial_hook.h" +#ifdef SERIAL_USB + typedef ForwardSerial1Class< USBSerial > DefaultSerial1; + extern DefaultSerial1 MSerial0; + #if HAS_SD_HOST_DRIVE + #define UsbSerial MarlinCompositeSerial + #else + #define UsbSerial MSerial0 + #endif +#endif + +#define SERIAL_INDEX_MIN 1 +#if ANY(STM32_HIGH_DENSITY, STM32_XL_DENSITY) + #define SERIAL_INDEX_MAX 5 +#else + #define SERIAL_INDEX_MAX 3 +#endif +#define USB_SERIAL_PORT(...) UsbSerial +#include "../shared/serial_ports.h" + +#if defined(LCD_SERIAL_PORT) && ANY(HAS_DGUS_LCD, EXTENSIBLE_UI) + #define LCD_SERIAL_TX_BUFFER_FREE() LCD_SERIAL.availableForWrite() +#endif + // Increase priority of serial interrupts, to reduce overflow errors #define UART_IRQ_PRIO 1 diff --git a/Marlin/src/HAL/STM32F1/MinSerial.cpp b/Marlin/src/HAL/STM32F1/MinSerial.cpp index 8fb913325496..0d9a611d7e8b 100644 --- a/Marlin/src/HAL/STM32F1/MinSerial.cpp +++ b/Marlin/src/HAL/STM32F1/MinSerial.cpp @@ -92,7 +92,7 @@ void install_min_serial() { HAL_min_serial_out = &TX; } -#if DISABLED(DYNAMIC_VECTORTABLE) && DISABLED(STM32F0xx) // Cortex M0 can't branch to a symbol that's too far, so we have a specific hack for them +#if NONE(DYNAMIC_VECTORTABLE, STM32F0xx) // Cortex M0 can't branch to a symbol that's too far, so we have a specific hack for them extern "C" { __attribute__((naked)) void JumpHandler_ASM() { __asm__ __volatile__ ( diff --git a/Marlin/src/HAL/STM32F1/README.md b/Marlin/src/HAL/STM32F1/README.md index b5bd5141fbff..e289f35433a1 100644 --- a/Marlin/src/HAL/STM32F1/README.md +++ b/Marlin/src/HAL/STM32F1/README.md @@ -5,6 +5,7 @@ This HAL is for STM32F103 boards used with [Arduino STM32](https://github.com/ro Currently has been tested in Malyan M200 (103CBT6), SKRmini (103RCT6), Chitu 3d (103ZET6), and various 103VET6 boards. ### Main developers: + - Victorpv - xC000005 - thisiskeithb diff --git a/Marlin/src/HAL/STM32F1/SPI.h b/Marlin/src/HAL/STM32F1/SPI.h index 27bf684388ea..72063df69914 100644 --- a/Marlin/src/HAL/STM32F1/SPI.h +++ b/Marlin/src/HAL/STM32F1/SPI.h @@ -33,12 +33,14 @@ #include #include +#include "../../core/macros.h" // for PIN_EXISTS + // Number of SPI ports -#ifdef BOARD_SPI3_SCK_PIN +#if PIN_EXISTS(BOARD_SPI3_SCK) #define BOARD_NR_SPI 3 -#elif defined(BOARD_SPI2_SCK_PIN) +#elif PIN_EXISTS(BOARD_SPI2_SCK) #define BOARD_NR_SPI 2 -#elif defined(BOARD_SPI1_SCK_PIN) +#elif PIN_EXISTS(BOARD_SPI1_SCK) #define BOARD_NR_SPI 1 #endif diff --git a/Marlin/src/HAL/STM32F1/Servo.cpp b/Marlin/src/HAL/STM32F1/Servo.cpp index 7aa8fe3d00df..00caec287ba7 100644 --- a/Marlin/src/HAL/STM32F1/Servo.cpp +++ b/Marlin/src/HAL/STM32F1/Servo.cpp @@ -29,8 +29,6 @@ uint8_t ServoCount = 0; #include "Servo.h" -//#include "Servo.h" - #include #include #include diff --git a/Marlin/src/HAL/STM32F1/adc.h b/Marlin/src/HAL/STM32F1/adc.h index 25f4a7ce16d1..a2f5652de0dc 100644 --- a/Marlin/src/HAL/STM32F1/adc.h +++ b/Marlin/src/HAL/STM32F1/adc.h @@ -44,7 +44,8 @@ enum ADCIndex : uint8_t { OPTITEM(HAS_TEMP_ADC_COOLER, TEMP_COOLER ) OPTITEM(HAS_TEMP_ADC_BOARD, TEMP_BOARD ) OPTITEM(HAS_TEMP_ADC_SOC, TEMP_SOC ) - OPTITEM(FILAMENT_WIDTH_SENSOR, FILWIDTH ) + OPTITEM(HAS_FILWIDTH_ADC, FILWIDTH ) + OPTITEM(HAS_FILWIDTH2_ADC, FILWIDTH2 ) OPTITEM(HAS_ADC_BUTTONS, ADC_KEY ) OPTITEM(HAS_JOY_ADC_X, JOY_X ) OPTITEM(HAS_JOY_ADC_Y, JOY_Y ) diff --git a/Marlin/src/HAL/STM32F1/build_flags.py b/Marlin/src/HAL/STM32F1/build_flags.py deleted file mode 100755 index 970ca8b767f9..000000000000 --- a/Marlin/src/HAL/STM32F1/build_flags.py +++ /dev/null @@ -1,56 +0,0 @@ -from __future__ import print_function -import sys - -#dynamic build flags for generic compile options -if __name__ == "__main__": - args = " ".join([ "-std=gnu++14", - "-Os", - "-mcpu=cortex-m3", - "-mthumb", - - "-fsigned-char", - "-fno-move-loop-invariants", - "-fno-strict-aliasing", - "-fsingle-precision-constant", - - "--specs=nano.specs", - "--specs=nosys.specs", - - "-IMarlin/src/HAL/STM32F1", - - "-MMD", - "-MP", - "-DTARGET_STM32F1" - ]) - - for i in range(1, len(sys.argv)): - args += " " + sys.argv[i] - - print(args) - -# extra script for linker options -else: - import pioutil - if pioutil.is_pio_build(): - from SCons.Script import DefaultEnvironment - env = DefaultEnvironment() - env.Append( - ARFLAGS=["rcs"], - - ASFLAGS=["-x", "assembler-with-cpp"], - - CXXFLAGS=[ - "-fabi-version=0", - "-fno-use-cxa-atexit", - "-fno-threadsafe-statics" - ], - LINKFLAGS=[ - "-Os", - "-mcpu=cortex-m3", - "-ffreestanding", - "-mthumb", - "--specs=nano.specs", - "--specs=nosys.specs", - "-u_printf_float", - ], - ) diff --git a/Marlin/src/HAL/STM32F1/eeprom_bl24cxx.cpp b/Marlin/src/HAL/STM32F1/eeprom/eeprom_bl24cxx.cpp similarity index 95% rename from Marlin/src/HAL/STM32F1/eeprom_bl24cxx.cpp rename to Marlin/src/HAL/STM32F1/eeprom/eeprom_bl24cxx.cpp index 1252e77b0b3a..9e96a741bf1f 100644 --- a/Marlin/src/HAL/STM32F1/eeprom_bl24cxx.cpp +++ b/Marlin/src/HAL/STM32F1/eeprom/eeprom_bl24cxx.cpp @@ -26,12 +26,12 @@ * with simple implementations supplied by Marlin. */ -#include "../../inc/MarlinConfig.h" +#include "../../../inc/MarlinConfig.h" #if ENABLED(IIC_BL24CXX_EEPROM) -#include "../shared/eeprom_if.h" -#include "../shared/eeprom_api.h" +#include "../../shared/eeprom_if.h" +#include "../../shared/eeprom_api.h" // // PersistentStore diff --git a/Marlin/src/HAL/STM32F1/eeprom_flash.cpp b/Marlin/src/HAL/STM32F1/eeprom/eeprom_flash.cpp similarity index 85% rename from Marlin/src/HAL/STM32F1/eeprom_flash.cpp rename to Marlin/src/HAL/STM32F1/eeprom/eeprom_flash.cpp index afdfefd5f076..2292c0220380 100644 --- a/Marlin/src/HAL/STM32F1/eeprom_flash.cpp +++ b/Marlin/src/HAL/STM32F1/eeprom/eeprom_flash.cpp @@ -28,18 +28,18 @@ #ifdef __STM32F1__ -#include "../../inc/MarlinConfig.h" +#include "../../../inc/MarlinConfig.h" #if ENABLED(FLASH_EEPROM_EMULATION) -#include "../shared/eeprom_api.h" +#include "../../shared/eeprom_api.h" #include #include // Store settings in the last two pages #ifndef MARLIN_EEPROM_SIZE - #define MARLIN_EEPROM_SIZE ((EEPROM_PAGE_SIZE) * 2) + #define MARLIN_EEPROM_SIZE ((EEPROM_PAGE_SIZE) * 2UL) #endif size_t PersistentStore::capacity() { return MARLIN_EEPROM_SIZE - eeprom_exclude_size; } @@ -47,14 +47,14 @@ static uint8_t ram_eeprom[MARLIN_EEPROM_SIZE] __attribute__((aligned(4))) = {0}; static bool eeprom_dirty = false; bool PersistentStore::access_start() { - const uint32_t *source = reinterpret_cast(EEPROM_PAGE0_BASE); - uint32_t *destination = reinterpret_cast(ram_eeprom); + const uint32_t *src = reinterpret_cast(EEPROM_PAGE0_BASE); + uint32_t *dst = reinterpret_cast(ram_eeprom); static_assert(0 == (MARLIN_EEPROM_SIZE) % 4, "MARLIN_EEPROM_SIZE is corrupted. (Must be a multiple of 4.)"); // Ensure copying as uint32_t is safe constexpr size_t eeprom_size_u32 = (MARLIN_EEPROM_SIZE) / 4; - for (size_t i = 0; i < eeprom_size_u32; ++i, ++destination, ++source) - *destination = *source; + for (size_t i = 0; i < eeprom_size_u32; ++i, ++dst, ++src) + *dst = *src; eeprom_dirty = false; return true; @@ -80,9 +80,9 @@ bool PersistentStore::access_finish() { status = FLASH_ErasePage(EEPROM_PAGE1_BASE); if (status != FLASH_COMPLETE) ACCESS_FINISHED(true); - const uint16_t *source = reinterpret_cast(ram_eeprom); - for (size_t i = 0; i < MARLIN_EEPROM_SIZE; i += 2, ++source) { - if (FLASH_ProgramHalfWord(EEPROM_PAGE0_BASE + i, *source) != FLASH_COMPLETE) + const uint16_t *src = reinterpret_cast(ram_eeprom); + for (size_t i = 0; i < long(MARLIN_EEPROM_SIZE); i += 2, ++src) { + if (FLASH_ProgramHalfWord(EEPROM_PAGE0_BASE + i, *src) != FLASH_COMPLETE) ACCESS_FINISHED(false); } diff --git a/Marlin/src/HAL/STM32F1/eeprom_if_iic.cpp b/Marlin/src/HAL/STM32F1/eeprom/eeprom_if_iic.cpp similarity index 90% rename from Marlin/src/HAL/STM32F1/eeprom_if_iic.cpp rename to Marlin/src/HAL/STM32F1/eeprom/eeprom_if_iic.cpp index 78b7af0b0418..e7e7fc1db1c4 100644 --- a/Marlin/src/HAL/STM32F1/eeprom_if_iic.cpp +++ b/Marlin/src/HAL/STM32F1/eeprom/eeprom_if_iic.cpp @@ -27,12 +27,12 @@ #ifdef __STM32F1__ -#include "../../inc/MarlinConfig.h" +#include "../../../inc/MarlinConfig.h" #if ENABLED(IIC_BL24CXX_EEPROM) -#include "../../libs/BL24CXX.h" -#include "../shared/eeprom_if.h" +#include "../../../libs/BL24CXX.h" +#include "../../shared/eeprom_if.h" void eeprom_init() { BL24CXX::init(); } @@ -42,7 +42,7 @@ void eeprom_init() { BL24CXX::init(); } void eeprom_write_byte(uint8_t *pos, uint8_t value) { const unsigned eeprom_address = (unsigned)pos; - return BL24CXX::writeOneByte(eeprom_address, value); + BL24CXX::writeOneByte(eeprom_address, value); } uint8_t eeprom_read_byte(uint8_t *pos) { diff --git a/Marlin/src/HAL/STM32F1/eeprom_sdcard.cpp b/Marlin/src/HAL/STM32F1/eeprom/eeprom_sdcard.cpp similarity index 93% rename from Marlin/src/HAL/STM32F1/eeprom_sdcard.cpp rename to Marlin/src/HAL/STM32F1/eeprom/eeprom_sdcard.cpp index 6b7219342219..e41c6e0aa02d 100644 --- a/Marlin/src/HAL/STM32F1/eeprom_sdcard.cpp +++ b/Marlin/src/HAL/STM32F1/eeprom/eeprom_sdcard.cpp @@ -27,12 +27,12 @@ #ifdef __STM32F1__ -#include "../../inc/MarlinConfig.h" +#include "../../../inc/MarlinConfig.h" #if ENABLED(SDCARD_EEPROM_EMULATION) -#include "../shared/eeprom_api.h" -#include "../../sd/cardreader.h" +#include "../../shared/eeprom_api.h" +#include "../../../sd/cardreader.h" #define EEPROM_FILENAME "eeprom.dat" @@ -53,7 +53,7 @@ bool PersistentStore::access_start() { int bytes_read = file.read(HAL_eeprom_data, MARLIN_EEPROM_SIZE); if (bytes_read < 0) return false; - for (; bytes_read < MARLIN_EEPROM_SIZE; bytes_read++) + for (; bytes_read < long(MARLIN_EEPROM_SIZE); bytes_read++) HAL_eeprom_data[bytes_read] = 0xFF; file.close(); return true; diff --git a/Marlin/src/HAL/STM32F1/eeprom_wired.cpp b/Marlin/src/HAL/STM32F1/eeprom/eeprom_wired.cpp similarity index 95% rename from Marlin/src/HAL/STM32F1/eeprom_wired.cpp rename to Marlin/src/HAL/STM32F1/eeprom/eeprom_wired.cpp index bfb771809443..a1464ab983ba 100644 --- a/Marlin/src/HAL/STM32F1/eeprom_wired.cpp +++ b/Marlin/src/HAL/STM32F1/eeprom/eeprom_wired.cpp @@ -26,12 +26,12 @@ #ifdef __STM32F1__ -#include "../../inc/MarlinConfig.h" +#include "../../../inc/MarlinConfig.h" #if USE_WIRED_EEPROM -#include "../shared/eeprom_if.h" -#include "../shared/eeprom_api.h" +#include "../../shared/eeprom_if.h" +#include "../../shared/eeprom_api.h" #ifndef MARLIN_EEPROM_SIZE #error "MARLIN_EEPROM_SIZE is required for I2C / SPI EEPROM." diff --git a/Marlin/src/HAL/STM32F1/endstop_interrupts.h b/Marlin/src/HAL/STM32F1/endstop_interrupts.h index d11b3bf50503..f8f21a4c6d88 100644 --- a/Marlin/src/HAL/STM32F1/endstop_interrupts.h +++ b/Marlin/src/HAL/STM32F1/endstop_interrupts.h @@ -27,7 +27,7 @@ * On STM32F, all pins support external interrupt capability. * Any pin can be used for external interrupts, but there are some restrictions. * At most 16 different external interrupts can be used at one time. - * Further, you can’t just pick any 16 pins to use. This is because every pin on the STM32 + * Further, you can't just pick any 16 pins to use. This is because every pin on the STM32 * connects to what is called an EXTI line, and only one pin per EXTI line can be used for external interrupts at a time * Check the Reference Manual of the MCU to confirm which line is used by each pin */ @@ -70,6 +70,7 @@ void setup_endstop_interrupts() { TERN_(USE_Z4_MAX, _ATTACH(Z4_MAX_PIN)); TERN_(USE_Z4_MIN, _ATTACH(Z4_MIN_PIN)); TERN_(USE_Z_MIN_PROBE, _ATTACH(Z_MIN_PROBE_PIN)); + TERN_(USE_CALIBRATION, _ATTACH(CALIBRATION_PIN)); TERN_(USE_I_MAX, _ATTACH(I_MAX_PIN)); TERN_(USE_I_MIN, _ATTACH(I_MIN_PIN)); TERN_(USE_J_MAX, _ATTACH(J_MAX_PIN)); diff --git a/Marlin/src/HAL/STM32F1/fastio.h b/Marlin/src/HAL/STM32F1/fastio.h index 5b3ebaa52c49..8bb986b4662c 100644 --- a/Marlin/src/HAL/STM32F1/fastio.h +++ b/Marlin/src/HAL/STM32F1/fastio.h @@ -44,6 +44,7 @@ #define SET_INPUT_PULLUP(IO) _SET_MODE(IO, GPIO_INPUT_PU) #define SET_INPUT_PULLDOWN(IO) _SET_MODE(IO, GPIO_INPUT_PD) #define SET_OUTPUT(IO) OUT_WRITE(IO, LOW) +#define SET_OUTPUT_OD(IO) OUT_WRITE_OD(IO, LOW) #define SET_PWM(IO) pinMode(IO, PWM) // do{ gpio_set_mode(PIN_MAP[pin].gpio_device, PIN_MAP[pin].gpio_bit, GPIO_AF_OUTPUT_PP); timer_set_mode(PIN_MAP[pin].timer_device, PIN_MAP[pin].timer_channel, TIMER_PWM); }while(0) #define SET_PWM_OD(IO) pinMode(IO, PWM_OPEN_DRAIN) diff --git a/Marlin/src/HAL/STM32F1/inc/SanityCheck.h b/Marlin/src/HAL/STM32F1/inc/SanityCheck.h index 1da42dcc8fd0..b786d99c5a19 100644 --- a/Marlin/src/HAL/STM32F1/inc/SanityCheck.h +++ b/Marlin/src/HAL/STM32F1/inc/SanityCheck.h @@ -25,6 +25,13 @@ * Test STM32F1-specific configuration values for errors at compile-time. */ +/** + * Require pins to be defined as macros with numerical values + */ +#ifndef PA0 + #error "Your ARM platform pins are not defined as macros, only as enums! Provide pins_arduino.h and/or 'buildroot/share/PlatformIO/variants/*/variant.h' to define the pins." +#endif + #if ENABLED(SDCARD_EEPROM_EMULATION) && !HAS_MEDIA #undef SDCARD_EEPROM_EMULATION // Avoid additional error noise #if USE_FALLBACK_EEPROM diff --git a/Marlin/src/HAL/STM32F1/pinsDebug.h b/Marlin/src/HAL/STM32F1/pinsDebug.h index 6f8e48f455bd..9191614587b4 100644 --- a/Marlin/src/HAL/STM32F1/pinsDebug.h +++ b/Marlin/src/HAL/STM32F1/pinsDebug.h @@ -22,11 +22,23 @@ #pragma once /** - * Support routines for MAPLE_STM32F1 - */ - -/** - * Translation of routines & variables used by pinsDebug.h + * Pins Debugging for Maple STM32F1 + * + * - NUMBER_PINS_TOTAL + * - MULTI_NAME_PAD + * - getPinByIndex(index) + * - printPinNameByIndex(index) + * - getPinIsDigitalByIndex(index) + * - digitalPinToAnalogIndex(pin) + * - getValidPinMode(pin) + * - isValidPin(pin) + * - isAnalogPin(pin) + * - digitalRead_mod(pin) + * - pwm_status(pin) + * - printPinPWM(pin) + * - printPinPort(pin) + * - printPinNumber(pin) + * - printPinAnalog(pin) */ #ifndef BOARD_NR_GPIO_PINS // Only in MAPLE_STM32F1 @@ -39,12 +51,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 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 isValidPin(P) (P >= 0 && P < BOARD_NR_GPIO_PINS) +#define getPinByIndex(x) pin_t(pin_array[x].pin) +#define digitalRead_mod(P) extDigitalRead(P) +#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(P)); 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 +64,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 +75,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 +86,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 index) { + const pin_t pin = getPinByIndex(index); + return (!isAnalogPin(pin) #ifdef NUM_ANALOG_INPUTS || PIN_MAP[pin].adc_channel >= NUM_ANALOG_INPUTS #endif @@ -89,7 +101,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 +123,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]; diff --git a/Marlin/src/HAL/STM32F1/msc_sd.cpp b/Marlin/src/HAL/STM32F1/sd/msc_sd.cpp similarity index 96% rename from Marlin/src/HAL/STM32F1/msc_sd.cpp rename to Marlin/src/HAL/STM32F1/sd/msc_sd.cpp index 067b46eb8bad..5f1ba30c8a7a 100644 --- a/Marlin/src/HAL/STM32F1/msc_sd.cpp +++ b/Marlin/src/HAL/STM32F1/sd/msc_sd.cpp @@ -22,20 +22,21 @@ */ #ifdef __STM32F1__ -#include "../../inc/MarlinConfigPre.h" +#include "../../../inc/MarlinConfigPre.h" #if HAS_SD_HOST_DRIVE #include "msc_sd.h" -#include "SPI.h" -#include "usb_reg_map.h" +#include "../SPI.h" + +#include #define PRODUCT_ID 0x29 USBMassStorage MarlinMSC; Serial1Class MarlinCompositeSerial(true); -#include "../../inc/MarlinConfig.h" +#include "../../../inc/MarlinConfig.h" #if SD_CONNECTION_IS(ONBOARD) diff --git a/Marlin/src/HAL/STM32F1/msc_sd.h b/Marlin/src/HAL/STM32F1/sd/msc_sd.h similarity index 93% rename from Marlin/src/HAL/STM32F1/msc_sd.h rename to Marlin/src/HAL/STM32F1/sd/msc_sd.h index 371211efc68a..871d98eaf24c 100644 --- a/Marlin/src/HAL/STM32F1/msc_sd.h +++ b/Marlin/src/HAL/STM32F1/sd/msc_sd.h @@ -24,8 +24,8 @@ #include -#include "../../inc/MarlinConfigPre.h" -#include "../../core/serial_hook.h" +#include "../../../inc/MarlinConfigPre.h" +#include "../../../core/serial_hook.h" extern USBMassStorage MarlinMSC; extern Serial1Class MarlinCompositeSerial; diff --git a/Marlin/src/HAL/STM32F1/onboard_sd.cpp b/Marlin/src/HAL/STM32F1/sd/onboard_sd.cpp similarity index 99% rename from Marlin/src/HAL/STM32F1/onboard_sd.cpp rename to Marlin/src/HAL/STM32F1/sd/onboard_sd.cpp index a3d8dcb2d57e..ace1204b7560 100644 --- a/Marlin/src/HAL/STM32F1/onboard_sd.cpp +++ b/Marlin/src/HAL/STM32F1/sd/onboard_sd.cpp @@ -13,13 +13,13 @@ #ifdef __STM32F1__ -#include "../../inc/MarlinConfig.h" +#include "../../../inc/MarlinConfig.h" #if SD_CONNECTION_IS(ONBOARD) #include "onboard_sd.h" -#include "SPI.h" -#include "fastio.h" +#include "../SPI.h" +#include "../fastio.h" #ifndef ONBOARD_SPI_DEVICE #define ONBOARD_SPI_DEVICE SPI_DEVICE diff --git a/Marlin/src/HAL/STM32F1/onboard_sd.h b/Marlin/src/HAL/STM32F1/sd/onboard_sd.h similarity index 100% rename from Marlin/src/HAL/STM32F1/onboard_sd.h rename to Marlin/src/HAL/STM32F1/sd/onboard_sd.h diff --git a/Marlin/src/HAL/STM32F1/sdio.cpp b/Marlin/src/HAL/STM32F1/sd/sdio.cpp similarity index 98% rename from Marlin/src/HAL/STM32F1/sdio.cpp rename to Marlin/src/HAL/STM32F1/sd/sdio.cpp index 23f984eff329..d657fd3b0ec0 100644 --- a/Marlin/src/HAL/STM32F1/sdio.cpp +++ b/Marlin/src/HAL/STM32F1/sd/sdio.cpp @@ -19,11 +19,11 @@ * along with this program. If not, see . * */ -#ifdef ARDUINO_ARCH_STM32F1 +#ifdef __STM32F1__ #include -#include "../../inc/MarlinConfig.h" // Allow pins/pins.h to set density +#include "../../../inc/MarlinConfig.h" // Allow pins/pins.h to set density #if ANY(STM32_HIGH_DENSITY, STM32_XL_DENSITY) @@ -145,7 +145,7 @@ bool SDIO_ReadBlock(uint32_t blockAddress, uint8_t *data) { return false; } -uint32_t millis(); +unsigned long millis(); bool SDIO_WriteBlock(uint32_t blockAddress, const uint8_t *data) { if (SDIO_GetCardState() != SDIO_CARD_TRANSFER) return false; @@ -219,7 +219,7 @@ bool SDIO_CmdAppSetBusWidth(uint32_t rsa, uint32_t argument) { bool SDIO_CmdAppOperCommand(uint32_t sdType) { if (!SDIO_CmdAppCommand(0)) return false; - SDIO_SendCommand(ACMD41_SD_APP_OP_COND , SDMMC_VOLTAGE_WINDOW_SD | sdType); + SDIO_SendCommand(ACMD41_SD_APP_OP_COND, SDMMC_VOLTAGE_WINDOW_SD | sdType); return SDIO_GetCmdResp3(); } @@ -308,4 +308,4 @@ bool SDIO_GetCmdResp7() { } #endif // STM32_HIGH_DENSITY || STM32_XL_DENSITY -#endif // ARDUINO_ARCH_STM32F1 +#endif // __STM32F1__ diff --git a/Marlin/src/HAL/STM32F1/sdio.h b/Marlin/src/HAL/STM32F1/sd/sdio.h similarity index 98% rename from Marlin/src/HAL/STM32F1/sdio.h rename to Marlin/src/HAL/STM32F1/sd/sdio.h index 08c884666d8c..ffcd3549150f 100644 --- a/Marlin/src/HAL/STM32F1/sdio.h +++ b/Marlin/src/HAL/STM32F1/sd/sdio.h @@ -21,7 +21,7 @@ */ #pragma once -#include "../../inc/MarlinConfig.h" // Allow pins/pins.h to override SDIO clock / retries +#include "../../../inc/MarlinConfig.h" // Allow pins/pins.h to override SDIO clock / retries #include #include diff --git a/Marlin/src/HAL/STM32F1/spi_pins.h b/Marlin/src/HAL/STM32F1/spi_pins.h index 3d3c8f8d2f18..d9c5e1f8c636 100644 --- a/Marlin/src/HAL/STM32F1/spi_pins.h +++ b/Marlin/src/HAL/STM32F1/spi_pins.h @@ -46,11 +46,6 @@ #ifndef SD_MOSI_PIN #define SD_MOSI_PIN PA7 #endif -#ifndef SD_SS_PIN - #define SD_SS_PIN PA4 -#endif -#undef SDSS -#define SDSS SD_SS_PIN #ifndef SPI_DEVICE #define SPI_DEVICE 1 diff --git a/Marlin/src/HAL/STM32F1/tft/tft_fsmc.cpp b/Marlin/src/HAL/STM32F1/tft/tft_fsmc.cpp index 51f70b936527..73b0e7efb956 100644 --- a/Marlin/src/HAL/STM32F1/tft/tft_fsmc.cpp +++ b/Marlin/src/HAL/STM32F1/tft/tft_fsmc.cpp @@ -86,7 +86,6 @@ __attribute__((always_inline)) __STATIC_INLINE void __DSB() { #define FSMC_ADDRESS_SETUP_TIME 15 // AddressSetupTime #define FSMC_DATA_SETUP_TIME 15 // DataSetupTime -static uint8_t fsmcInit = 0; void TFT_FSMC::init() { uint8_t cs = FSMC_CS_PIN, rs = FSMC_RS_PIN; uint32_t controllerAddress; @@ -99,8 +98,9 @@ void TFT_FSMC::init() { struct fsmc_nor_psram_reg_map* fsmcPsramRegion; + static bool fsmcInit = false; if (fsmcInit) return; - fsmcInit = 1; + fsmcInit = true; switch (cs) { case FSMC_CS_NE1: controllerAddress = (uint32_t)FSMC_NOR_PSRAM_REGION1; fsmcPsramRegion = FSMC_NOR_PSRAM1_BASE; break; diff --git a/Marlin/src/HAL/STM32F1/timers.cpp b/Marlin/src/HAL/STM32F1/timers.cpp index 12477a458314..141dc80b808a 100644 --- a/Marlin/src/HAL/STM32F1/timers.cpp +++ b/Marlin/src/HAL/STM32F1/timers.cpp @@ -84,7 +84,7 @@ void HAL_timer_start(const uint8_t timer_num, const uint32_t frequency) { timer_set_prescaler(STEP_TIMER_DEV, (uint16_t)(STEPPER_TIMER_PRESCALE - 1)); timer_set_reload(STEP_TIMER_DEV, 0xFFFF); timer_oc_set_mode(STEP_TIMER_DEV, STEP_TIMER_CHAN, TIMER_OC_MODE_FROZEN, TIMER_OC_NO_PRELOAD); // no output pin change - timer_set_compare(STEP_TIMER_DEV, STEP_TIMER_CHAN, _MIN(hal_timer_t(HAL_TIMER_TYPE_MAX), (STEPPER_TIMER_RATE) / frequency)); + timer_set_compare(STEP_TIMER_DEV, STEP_TIMER_CHAN, _MIN(HAL_TIMER_TYPE_MAX, hal_timer_t((STEPPER_TIMER_RATE) / frequency))); timer_no_ARR_preload_ARPE(STEP_TIMER_DEV); // Need to be sure no preload on ARR register timer_attach_interrupt(STEP_TIMER_DEV, STEP_TIMER_CHAN, stepTC_Handler); HAL_timer_set_interrupt_priority(MF_TIMER_STEP, STEP_TIMER_IRQ_PRIO); @@ -97,7 +97,7 @@ void HAL_timer_start(const uint8_t timer_num, const uint32_t frequency) { timer_set_count(TEMP_TIMER_DEV, 0); timer_set_prescaler(TEMP_TIMER_DEV, (uint16_t)(TEMP_TIMER_PRESCALE - 1)); timer_set_reload(TEMP_TIMER_DEV, 0xFFFF); - timer_set_compare(TEMP_TIMER_DEV, TEMP_TIMER_CHAN, _MIN(hal_timer_t(HAL_TIMER_TYPE_MAX), (F_CPU) / (TEMP_TIMER_PRESCALE) / frequency)); + timer_set_compare(TEMP_TIMER_DEV, TEMP_TIMER_CHAN, _MIN(HAL_TIMER_TYPE_MAX, hal_timer_t((F_CPU) / (TEMP_TIMER_PRESCALE) / frequency))); timer_attach_interrupt(TEMP_TIMER_DEV, TEMP_TIMER_CHAN, tempTC_Handler); HAL_timer_set_interrupt_priority(MF_TIMER_TEMP, TEMP_TIMER_IRQ_PRIO); timer_generate_update(TEMP_TIMER_DEV); diff --git a/Marlin/src/HAL/STM32F1/timers.h b/Marlin/src/HAL/STM32F1/timers.h index 89a609c2c39d..3d6650a1a299 100644 --- a/Marlin/src/HAL/STM32F1/timers.h +++ b/Marlin/src/HAL/STM32F1/timers.h @@ -40,7 +40,7 @@ */ typedef uint16_t hal_timer_t; -#define HAL_TIMER_TYPE_MAX 0xFFFF +#define HAL_TIMER_TYPE_MAX hal_timer_t(UINT16_MAX) #define HAL_TIMER_RATE uint32_t(F_CPU) // frequency of timers peripherals @@ -95,27 +95,26 @@ typedef uint16_t hal_timer_t; #define TEMP_TIMER_IRQ_PRIO 3 #define SERVO0_TIMER_IRQ_PRIO 1 -#define TEMP_TIMER_PRESCALE 1000 // prescaler for setting Temp timer, 72Khz -#define TEMP_TIMER_FREQUENCY 1000 // temperature interrupt frequency +#define TEMP_TIMER_PRESCALE 1000 // Prescaler for setting Temp Timer, 72Khz +#define TEMP_TIMER_FREQUENCY 1000 // (Hz) Temperature ISR frequency -#define STEPPER_TIMER_PRESCALE 18 // prescaler for setting stepper timer, 4Mhz -#define STEPPER_TIMER_RATE (HAL_TIMER_RATE / STEPPER_TIMER_PRESCALE) // frequency of stepper timer -#define STEPPER_TIMER_TICKS_PER_US ((STEPPER_TIMER_RATE) / 1000000) // stepper timer ticks per µs +#define STEPPER_TIMER_PRESCALE 18 // Prescaler for setting stepper timer, 4Mhz +#define STEPPER_TIMER_RATE (HAL_TIMER_RATE / STEPPER_TIMER_PRESCALE) // (Hz) Frequency of Stepper Timer ISR +#define STEPPER_TIMER_TICKS_PER_US ((STEPPER_TIMER_RATE) / 1000000UL) // (MHz) Stepper Timer ticks per µs -#define PULSE_TIMER_RATE STEPPER_TIMER_RATE // frequency of pulse timer +#define PULSE_TIMER_RATE STEPPER_TIMER_RATE // (Hz) Frequency of Pulse Timer #define PULSE_TIMER_PRESCALE STEPPER_TIMER_PRESCALE -#define PULSE_TIMER_TICKS_PER_US STEPPER_TIMER_TICKS_PER_US timer_dev* HAL_get_timer_dev(int number); #define TIMER_DEV(num) HAL_get_timer_dev(num) #define STEP_TIMER_DEV TIMER_DEV(MF_TIMER_STEP) #define TEMP_TIMER_DEV TIMER_DEV(MF_TIMER_TEMP) -#define ENABLE_STEPPER_DRIVER_INTERRUPT() timer_enable_irq(STEP_TIMER_DEV, STEP_TIMER_CHAN) -#define DISABLE_STEPPER_DRIVER_INTERRUPT() timer_disable_irq(STEP_TIMER_DEV, STEP_TIMER_CHAN) -#define STEPPER_ISR_ENABLED() HAL_timer_interrupt_enabled(MF_TIMER_STEP) +#define ENABLE_STEPPER_DRIVER_INTERRUPT() timer_enable_irq(STEP_TIMER_DEV, STEP_TIMER_CHAN) +#define DISABLE_STEPPER_DRIVER_INTERRUPT() timer_disable_irq(STEP_TIMER_DEV, STEP_TIMER_CHAN) +#define STEPPER_ISR_ENABLED() HAL_timer_interrupt_enabled(MF_TIMER_STEP) -#define ENABLE_TEMPERATURE_INTERRUPT() timer_enable_irq(TEMP_TIMER_DEV, TEMP_TIMER_CHAN) +#define ENABLE_TEMPERATURE_INTERRUPT() timer_enable_irq(TEMP_TIMER_DEV, TEMP_TIMER_CHAN) #define DISABLE_TEMPERATURE_INTERRUPT() timer_disable_irq(TEMP_TIMER_DEV, TEMP_TIMER_CHAN) #define HAL_timer_get_count(timer_num) timer_get_count(TIMER_DEV(timer_num)) @@ -188,7 +187,7 @@ FORCE_INLINE static void HAL_timer_isr_prologue(const uint8_t timer_num) { } } -#define HAL_timer_isr_epilogue(T) NOOP +inline void HAL_timer_isr_epilogue(const uint8_t) {} // No command is available in framework to turn off ARPE bit, which is turned on by default in libmaple. // Needed here to reset ARPE=0 for stepper timer diff --git a/Marlin/src/HAL/STM32F1/u8g/LCD_defines.h b/Marlin/src/HAL/STM32F1/u8g/LCD_defines.h index 4bd461cb39d2..9d0137ae6a20 100644 --- a/Marlin/src/HAL/STM32F1/u8g/LCD_defines.h +++ b/Marlin/src/HAL/STM32F1/u8g/LCD_defines.h @@ -25,7 +25,7 @@ * STM32F1 (Maple) LCD-specific defines */ -uint8_t u8g_com_HAL_STM32F1_sw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr); +uint8_t u8g_com_HAL_STM32F1_sw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr); // u8g_com_stm32duino_swspi.cpp uint8_t u8g_com_stm32duino_hw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr); // See U8glib-HAL #define U8G_COM_HAL_SW_SPI_FN u8g_com_HAL_STM32F1_sw_spi_fn diff --git a/Marlin/src/HAL/STM32F1/dogm/u8g_com_stm32duino_swspi.cpp b/Marlin/src/HAL/STM32F1/u8g/u8g_com_stm32duino_swspi.cpp similarity index 90% rename from Marlin/src/HAL/STM32F1/dogm/u8g_com_stm32duino_swspi.cpp rename to Marlin/src/HAL/STM32F1/u8g/u8g_com_stm32duino_swspi.cpp index fc421897c4ec..28302edaf5b1 100644 --- a/Marlin/src/HAL/STM32F1/dogm/u8g_com_stm32duino_swspi.cpp +++ b/Marlin/src/HAL/STM32F1/u8g/u8g_com_stm32duino_swspi.cpp @@ -89,7 +89,7 @@ static inline uint8_t swSpiTransfer_mode_3(uint8_t b, const uint8_t spi_speed, c } static void u8g_sw_spi_shift_out(uint8_t val) { - #if ENABLED(FYSETC_MINI_12864) + #if U8G_SPI_USE_MODE_3 swSpiTransfer_mode_3(val, SPI_speed); #else swSpiTransfer_mode_0(val, SPI_speed); @@ -123,15 +123,15 @@ uint8_t u8g_com_HAL_STM32F1_sw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, break; case U8G_COM_MSG_CHIP_SELECT: - #if ENABLED(FYSETC_MINI_12864) // This LCD SPI is running mode 3 while SD card is running mode 0 - if (arg_val) { // SCK idle state needs to be set to the proper idle state before - // the next chip select goes active - WRITE(DOGLCD_SCK, HIGH); // Set SCK to mode 3 idle state before CS goes active + #if U8G_SPI_USE_MODE_3 // This LCD SPI is running mode 3 while SD card is running mode 0 + if (arg_val) { // SCK idle state needs to be set to the proper idle state before + // the next chip select goes active + WRITE(DOGLCD_SCK, HIGH); // Set SCK to mode 3 idle state before CS goes active WRITE(DOGLCD_CS, LOW); } else { WRITE(DOGLCD_CS, HIGH); - WRITE(DOGLCD_SCK, LOW); // Set SCK to mode 0 idle state after CS goes inactive + WRITE(DOGLCD_SCK, LOW); // Set SCK to mode 0 idle state after CS goes inactive } #else WRITE(DOGLCD_CS, !arg_val); diff --git a/Marlin/src/HAL/TEENSY31_32/AGENTS.md b/Marlin/src/HAL/TEENSY31_32/AGENTS.md new file mode 100644 index 000000000000..24c7d9e31129 --- /dev/null +++ b/Marlin/src/HAL/TEENSY31_32/AGENTS.md @@ -0,0 +1,51 @@ +# AGENTS.md — TEENSY31_32 HAL + +Working notes and conventions for the **TEENSY31_32 HAL** folder (`Marlin/src/HAL/TEENSY31_32/`). This HAL drives the PJRC Teensy 3.1 / 3.2 boards (NXP/Freescale MK20DX256, ARM Cortex-M4, 72 MHz, 256 KB flash). **Unlike AT32, the Teensy core is NOT vendored** — it is the upstream Teensyduino/Arduino core pulled in by the `teensy` PlatformIO platform. + +## Build & Test Loop + +Validate with `mftest` (it regenerates `Marlin/Configuration.h` per target, which raw `pio run -e` does not do reliably): + +```bash +cd "$(git rev-parse --show-toplevel)" +buildroot/bin/mftest -t teensy31 -n1 -y # BOARD_TEENSY31_32, MK20DX256 +``` + +`buildroot/tests/teensy31/` holds three configs (`config-01` default, `config-02` zero-endstops, `config-03` many-features). They all target the same board (`BOARD_TEENSY31_32`); `config-03` is the tightest constraint — the full-feature build that stresses the 256 KB flash. All three must be green before considering Teensy 3.1/3.2 work done. + +> Do NOT use `rm -rf .pio/build/...` to force a rebuild — the cross-profile write guard blocks it and it is unsafe. Use `pio run -e teensy31 -t clean` (removes only build artifacts) or just let `mftest` rebuild. + +## Framework / Platform Integration (upstream, not vendored) + +The Teensy Arduino core is the **upstream** Teensyduino package (`framework-arduinoteensy`, platform `teensy@~4.12.0` in `ini/teensy.ini`), installed at `~/.platformio/packages/framework-arduinoteensy*`. It is NOT vendored under `buildroot/share/PlatformIO/` (that is the AT32 exception). There is no "mirror to the installed package" step — changes to Marlin glue live only in this HAL folder, and the core comes straight from the PlatformIO registry. + +- `ini/teensy.ini` `[env:teensy31]` extends `teensy_arm` with `board = teensy31` and adds `src/HAL/TEENSY31_32` to `build_src_filter`. When you add a new HAL source file it is picked up automatically by that filter. +- EEPROM backend: this HAL uses the **wired** EEPROM (``) — the MK20DX256 has 2 KB of real EEPROM. No per-board `SDCARD_EEPROM_EMULATION` / `I2C_EEPROM` / `FLASH_EEPROM` define is needed; `eeprom.cpp` builds under `USE_WIRED_EEPROM`. + +### Gotchas that are not obvious from the source + +1. **The compiler guard is `__MK20DX256__`, not `ARDUINO_ARCH_TEENSY`.** Every `.cpp` in this folder is wrapped in `#ifdef __MK20DX256__`, and `platforms.h` maps that macro to `HAL/TEENSY31_32`. The Teensy 3.5/3.6 and 4.0/4.1 HALs all share the `teensy_arm` platform/`teensy` toolchain, so a missing guard would compile your file for the wrong MCU. Always keep the `#ifdef __MK20DX256__` guard. + +2. **EEPROM is wired ``, not SD/flash emulation.** `eeprom.cpp` calls the AVR-style `eeprom_read_byte`/`eeprom_write_byte` against `REAL_EEPROM_ADDR(pos)` and is gated on `USE_WIRED_EEPROM`. Do NOT add `SDCARD_EEPROM_EMULATION` or any SPI/I2C backend — the silicon has real EEPROM. + +3. **Bus-clock hard assumption in `timers.cpp`.** `HAL_timer_start()` derives FTM compare values from `FTM0_TIMER_RATE`/`FTM1_TIMER_RATE` presuming a **60 MHz bus clock**. The `teensy31` board JSON sets `F_CPU` accordingly; if that ever changes the step/temp timers run at the wrong rate. Also note `adc_init()` calls `NVIC_ENABLE_IRQ(IRQ_FTM1)` — the temperature timer (FTM1) must be enabled there for the shared temperature ISR to fire. + +4. **SPI wraps the Teensyduino `SPI` library, not a custom driver.** `HAL_SPI.cpp` calls `SPI.begin()` / `SPI.transfer()` from upstream ``; `MarlinSPI` is just `SPIClass`. `spiSendBlock()` still contains dead AVR `SPDR`/`SPSR` register code — that path is disabled; the active path uses `SPI.transfer`. Don't be misled into "fixing" it as a register driver. + +5. **Servo `move()` calls `attach(0)` — the `0` is a placeholder, not pin 0.** `Servo.cpp` stores the real pin in `servoPin[servoIndex]` and `libServo::move()` re-attaches with `attach(0)`, which resolves to the stored entry. Do NOT change it to the actual pin number. + +6. **ADC reads go through a fixed `pin2sc1a[]` lookup, not `analogRead`.** `adc_start()` indexes a hand-built table (`HAL.cpp`) that maps Marlin pin numbers to MK20DX256 SC1A ADC channels. The `analogInputToDigitalPin` macro in `HAL.h` only covers the first 12 A pins. Route analog sampling through `hal.adc_start()`/`adc_value()`, never `analogRead()`. + +## Conventions + +- All HAL source is guarded by `#ifdef __MK20DX256__` (plus `IS_TEENSY31_32` / `IS_TEENSY32` in `HAL.h`). The upstream framework also defines `ARDUINO_ARCH_TEENSY`, but the HAL does not rely on it for selection. +- Keep HAL changes in `Marlin/src/HAL/TEENSY31_32/` and board pin changes in `Marlin/src/pins/teensy3/pins_TEENSY31_32.h`. Core/PlatformIO integration is upstream — there is no vendored framework to edit here. +- `fastio.h` header comment says "Teensy 3.5 and 3.6" but is the 3.1/3.2 implementation; it uses Cortex-M bitbanding via the core's `CORE_PINxx_PORTSET`/`PORTCLEAR` symbols. Note `_IS_OUTPUT` is defined identically to `_IS_INPUT` — output direction is not actually distinguished; harmless today, but avoid adding output-direction checks that depend on it. +- Serial: `Serial0` aliases the core `Serial`; `USBSerial` wraps `SerialUSB`; multi-port `DefaultSerialX` objects forward to `SerialX`. `SERIAL_INDEX_MIN/MAX` is 0..3. + +## Related + +- Wiki: [[hal-teensy31-32]], [[marlin-src-hal]]. +- `Marlin/src/HAL/shared/` — shared HAL APIs (`eeprom_api`, SPI helpers) this HAL reuses. +- `Marlin/src/pins/teensy3/pins_TEENSY31_32.h` — board pin map. +- `ini/teensy.ini` — `[env:teensy31]` build definition. diff --git a/Marlin/src/HAL/TEENSY31_32/HAL.cpp b/Marlin/src/HAL/TEENSY31_32/HAL.cpp index 2892368967e4..f54025991d22 100644 --- a/Marlin/src/HAL/TEENSY31_32/HAL.cpp +++ b/Marlin/src/HAL/TEENSY31_32/HAL.cpp @@ -37,10 +37,20 @@ #define _IMPLEMENT_SERIAL(X) DefaultSerial##X MSerial##X(false, Serial##X) #define IMPLEMENT_SERIAL(X) _IMPLEMENT_SERIAL(X) -#if WITHIN(SERIAL_PORT, 0, 3) +#if WITHIN(SERIAL_PORT, SERIAL_INDEX_MIN, SERIAL_INDEX_MAX) IMPLEMENT_SERIAL(SERIAL_PORT); -#else - #error "SERIAL_PORT must be from 0 to 3." +#endif +#if defined(SERIAL_PORT_2) && WITHIN(SERIAL_PORT_2, SERIAL_INDEX_MIN, SERIAL_INDEX_MAX) + IMPLEMENT_SERIAL(SERIAL_PORT_2); +#endif +#if defined(SERIAL_PORT_3) && WITHIN(SERIAL_PORT_3, SERIAL_INDEX_MIN, SERIAL_INDEX_MAX) + IMPLEMENT_SERIAL(SERIAL_PORT_3); +#endif +#if defined(MMU_SERIAL_PORT) && WITHIN(MMU_SERIAL_PORT, SERIAL_INDEX_MIN, SERIAL_INDEX_MAX) + IMPLEMENT_SERIAL(MMU_SERIAL_PORT); +#endif +#if defined(LCD_SERIAL_PORT) && WITHIN(LCD_SERIAL_PORT, SERIAL_INDEX_MIN, SERIAL_INDEX_MAX) + IMPLEMENT_SERIAL(LCD_SERIAL_PORT); #endif USBSerialType USBSerial(false, SerialUSB); diff --git a/Marlin/src/HAL/TEENSY31_32/HAL.h b/Marlin/src/HAL/TEENSY31_32/HAL.h index 16594c16a821..b98ee9eb39e7 100644 --- a/Marlin/src/HAL/TEENSY31_32/HAL.h +++ b/Marlin/src/HAL/TEENSY31_32/HAL.h @@ -64,17 +64,10 @@ typedef ForwardSerial1Class USBSerialType; extern USBSerialType USBSerial; -#define _MSERIAL(X) MSerial##X -#define MSERIAL(X) _MSERIAL(X) - -#if SERIAL_PORT == -1 - #define MYSERIAL1 USBSerial -#elif WITHIN(SERIAL_PORT, 0, 3) - DECLARE_SERIAL(SERIAL_PORT); - #define MYSERIAL1 MSERIAL(SERIAL_PORT) -#else - #error "The required SERIAL_PORT must be from 0 to 3, or -1 for Native USB." -#endif +#define SERIAL_INDEX_MIN 0 +#define SERIAL_INDEX_MAX 3 +#define USB_SERIAL_PORT(...) USBSerial +#include "../shared/serial_ports.h" // ------------------------ // Types @@ -98,7 +91,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 @@ -149,7 +142,7 @@ class MarlinHAL { static void delay_ms(const int ms) { delay(ms); } - // Tasks, called from idle() + // Tasks, called from marlin.idle() static void idletask() {} // Reset diff --git a/Marlin/src/HAL/TEENSY31_32/Servo.h b/Marlin/src/HAL/TEENSY31_32/Servo.h index 82b601d956be..2da74fd25d00 100644 --- a/Marlin/src/HAL/TEENSY31_32/Servo.h +++ b/Marlin/src/HAL/TEENSY31_32/Servo.h @@ -31,7 +31,5 @@ class libServo : public Servo { void move(const int value); private: typedef Servo super; - uint16_t min_ticks; - uint16_t max_ticks; uint8_t servoIndex; // index into the channel data for this servo }; diff --git a/Marlin/src/HAL/TEENSY31_32/endstop_interrupts.h b/Marlin/src/HAL/TEENSY31_32/endstop_interrupts.h index 954eb625a575..798ca4f0cbf7 100644 --- a/Marlin/src/HAL/TEENSY31_32/endstop_interrupts.h +++ b/Marlin/src/HAL/TEENSY31_32/endstop_interrupts.h @@ -64,6 +64,7 @@ void setup_endstop_interrupts() { TERN_(USE_Z4_MAX, _ATTACH(Z4_MAX_PIN)); TERN_(USE_Z4_MIN, _ATTACH(Z4_MIN_PIN)); TERN_(USE_Z_MIN_PROBE, _ATTACH(Z_MIN_PROBE_PIN)); + TERN_(USE_CALIBRATION, _ATTACH(CALIBRATION_PIN)); TERN_(USE_I_MAX, _ATTACH(I_MAX_PIN)); TERN_(USE_I_MIN, _ATTACH(I_MIN_PIN)); TERN_(USE_J_MAX, _ATTACH(J_MAX_PIN)); diff --git a/Marlin/src/HAL/TEENSY31_32/fastio.h b/Marlin/src/HAL/TEENSY31_32/fastio.h index 622799ec8cac..b582c7bfecbe 100644 --- a/Marlin/src/HAL/TEENSY31_32/fastio.h +++ b/Marlin/src/HAL/TEENSY31_32/fastio.h @@ -53,17 +53,17 @@ #define _SET_INPUT(P) do{ \ CORE_PIN ## P ## _CONFIG = PORT_PCR_MUX(1); \ - GPIO_BITBAND(CORE_PIN ## P ## _DDRREG , CORE_PIN ## P ## _BIT) = 0; \ + GPIO_BITBAND(CORE_PIN ## P ## _DDRREG, CORE_PIN ## P ## _BIT) = 0; \ }while(0) #define _SET_OUTPUT(P) do{ \ CORE_PIN ## P ## _CONFIG = PORT_PCR_MUX(1)|PORT_PCR_SRE|PORT_PCR_DSE; \ - GPIO_BITBAND(CORE_PIN ## P ## _DDRREG , CORE_PIN ## P ## _BIT) = 1; \ + GPIO_BITBAND(CORE_PIN ## P ## _DDRREG, CORE_PIN ## P ## _BIT) = 1; \ }while(0) #define _SET_INPUT_PULLUP(P) do{ \ CORE_PIN ## P ## _CONFIG = PORT_PCR_MUX(1) | PORT_PCR_PE | PORT_PCR_PS; \ - GPIO_BITBAND(CORE_PIN ## P ## _DDRREG , CORE_PIN ## P ## _BIT) = 0; \ + GPIO_BITBAND(CORE_PIN ## P ## _DDRREG, CORE_PIN ## P ## _BIT) = 0; \ }while(0) #define _IS_INPUT(P) ((CORE_PIN ## P ## _DDRREG & CORE_PIN ## P ## _BITMASK) == 0) diff --git a/Marlin/src/HAL/TEENSY31_32/pinsDebug.h b/Marlin/src/HAL/TEENSY31_32/pinsDebug.h index d4a91ce801c2..741909bf0a40 100644 --- a/Marlin/src/HAL/TEENSY31_32/pinsDebug.h +++ b/Marlin/src/HAL/TEENSY31_32/pinsDebug.h @@ -1 +1,71 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2019 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 . + * + */ +#pragma once + #error "PINS_DEBUGGING is not yet supported for Teensy 3.1 / 3.2!" + +/** + * Pins Debugging for ESP32 + * + * - NUMBER_PINS_TOTAL + * - MULTI_NAME_PAD + * - getPinByIndex(index) + * - printPinNameByIndex(index) + * - getPinIsDigitalByIndex(index) + * - digitalPinToAnalogIndex(pin) + * - getValidPinMode(pin) + * - isValidPin(pin) + * - isAnalogPin(pin) + * - digitalRead_mod(pin) + * - pwm_status(pin) + * - printPinPWM(pin) + * - printPinPort(pin) + * - printPinNumber(pin) + * - printPinAnalog(pin) + */ + +#define NUMBER_PINS_TOTAL NUM_DIGITAL_PINS +#define MULTI_NAME_PAD 16 // space needed to be pretty if not first name assigned to a pin + +#define digitalRead_mod(P) extDigitalRead(P) +#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(P)); SERIAL_ECHO(buffer); }while(0) +#define getPinByIndex(x) pin_array[x].pin +#define getPinIsDigitalByIndex(x) pin_array[x].is_digital +#define isValidPin(P) (P >= 0 && P < pin_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))) +bool pwm_status(const pin_t) { return false; } + +void printPinPort(const pin_t) {} + +static bool getValidPinMode(const pin_t pin) { + return isValidPin(pin) /* && !IS_INPUT(pin) */ ; +} + +void printPinPWM(const int32_t pin) { + if (pwm_status(pin)) { + //uint32_t chan = g_APinDescription[pin].ulPWMChannel TODO when fast pwm is operative; + //SERIAL_ECHOPGM("PWM = ", duty); + } +} diff --git a/Marlin/src/HAL/TEENSY31_32/spi_pins.h b/Marlin/src/HAL/TEENSY31_32/spi_pins.h index 6d0d05f85a5d..dbd0bb9174e3 100644 --- a/Marlin/src/HAL/TEENSY31_32/spi_pins.h +++ b/Marlin/src/HAL/TEENSY31_32/spi_pins.h @@ -24,4 +24,3 @@ #define SD_SCK_PIN 13 #define SD_MISO_PIN 12 #define SD_MOSI_PIN 11 -#define SD_SS_PIN 20 // SDSS // A.28, A.29, B.21, C.26, C.29 diff --git a/Marlin/src/HAL/TEENSY31_32/timers.h b/Marlin/src/HAL/TEENSY31_32/timers.h index 9fcbb6f232c9..cdca84ec1c5c 100644 --- a/Marlin/src/HAL/TEENSY31_32/timers.h +++ b/Marlin/src/HAL/TEENSY31_32/timers.h @@ -34,14 +34,14 @@ #define FORCE_INLINE __attribute__((always_inline)) inline typedef uint32_t hal_timer_t; -#define HAL_TIMER_TYPE_MAX 0xFFFFFFFF +#define HAL_TIMER_TYPE_MAX hal_timer_t(UINT32_MAX) #define FTM0_TIMER_PRESCALE 8 #define FTM1_TIMER_PRESCALE 4 #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) @@ -58,26 +58,25 @@ typedef uint32_t hal_timer_t; #define TEMP_TIMER_FREQUENCY 1000 -#define STEPPER_TIMER_RATE HAL_TIMER_RATE -#define STEPPER_TIMER_TICKS_PER_US ((STEPPER_TIMER_RATE) / 1000000) -#define STEPPER_TIMER_PRESCALE (CYCLES_PER_MICROSECOND / STEPPER_TIMER_TICKS_PER_US) +#define STEPPER_TIMER_RATE HAL_TIMER_RATE +#define STEPPER_TIMER_TICKS_PER_US ((STEPPER_TIMER_RATE) / 1000000UL) // (MHz) Stepper Timer ticks per µs +#define STEPPER_TIMER_PRESCALE (CYCLES_PER_MICROSECOND / STEPPER_TIMER_TICKS_PER_US) -#define PULSE_TIMER_RATE STEPPER_TIMER_RATE // frequency of pulse timer -#define PULSE_TIMER_PRESCALE STEPPER_TIMER_PRESCALE -#define PULSE_TIMER_TICKS_PER_US STEPPER_TIMER_TICKS_PER_US +#define PULSE_TIMER_RATE STEPPER_TIMER_RATE // (Hz) Frequency of Pulse Timer +#define PULSE_TIMER_PRESCALE STEPPER_TIMER_PRESCALE -#define ENABLE_STEPPER_DRIVER_INTERRUPT() HAL_timer_enable_interrupt(MF_TIMER_STEP) -#define DISABLE_STEPPER_DRIVER_INTERRUPT() HAL_timer_disable_interrupt(MF_TIMER_STEP) -#define STEPPER_ISR_ENABLED() HAL_timer_interrupt_enabled(MF_TIMER_STEP) +#define ENABLE_STEPPER_DRIVER_INTERRUPT() HAL_timer_enable_interrupt(MF_TIMER_STEP) +#define DISABLE_STEPPER_DRIVER_INTERRUPT() HAL_timer_disable_interrupt(MF_TIMER_STEP) +#define STEPPER_ISR_ENABLED() HAL_timer_interrupt_enabled(MF_TIMER_STEP) -#define ENABLE_TEMPERATURE_INTERRUPT() HAL_timer_enable_interrupt(MF_TIMER_TEMP) +#define ENABLE_TEMPERATURE_INTERRUPT() HAL_timer_enable_interrupt(MF_TIMER_TEMP) #define DISABLE_TEMPERATURE_INTERRUPT() HAL_timer_disable_interrupt(MF_TIMER_TEMP) #ifndef HAL_STEP_TIMER_ISR - #define HAL_STEP_TIMER_ISR() extern "C" void ftm0_isr() //void TC3_Handler() + #define HAL_STEP_TIMER_ISR() extern "C" void ftm0_isr() #endif #ifndef HAL_TEMP_TIMER_ISR - #define HAL_TEMP_TIMER_ISR() extern "C" void ftm1_isr() //void TC4_Handler() + #define HAL_TEMP_TIMER_ISR() extern "C" void ftm1_isr() #endif void HAL_timer_start(const uint8_t timer_num, const uint32_t frequency); @@ -110,4 +109,4 @@ void HAL_timer_disable_interrupt(const uint8_t timer_num); bool HAL_timer_interrupt_enabled(const uint8_t timer_num); void HAL_timer_isr_prologue(const uint8_t timer_num); -#define HAL_timer_isr_epilogue(T) NOOP +inline void HAL_timer_isr_epilogue(const uint8_t) {} diff --git a/Marlin/src/HAL/TEENSY35_36/AGENTS.md b/Marlin/src/HAL/TEENSY35_36/AGENTS.md new file mode 100644 index 000000000000..c52ef4b5775b --- /dev/null +++ b/Marlin/src/HAL/TEENSY35_36/AGENTS.md @@ -0,0 +1,57 @@ +# AGENTS.md — TEENSY35_36 HAL + +Working notes and conventions for the **TEENSY35_36 HAL** folder (`Marlin/src/HAL/TEENSY35_36/`). + +Targets PJRC **Teensy 3.5** (MK64FX512, 512 KB flash) and **Teensy 3.6** (MK66FX1M0, 1 MB flash) — both ARM Cortex-M4. They share this one HAL folder; the physical chip is selected by the compiler macro `__MK64FX512__` / `__MK66FX1M0__` (set by the PlatformIO `teensy35` / `teensy36` board JSONs), not by a runtime check. + +## Build & Test Loop + +Validate with `mftest` (it regenerates `Marlin/Configuration.h` per target, which raw `pio run -e` does not do reliably): + +```bash +cd "$(git rev-parse --show-toplevel)" +buildroot/bin/mftest -t teensy35 -n1 -y # Teensy 3.5 (MK64FX512) +buildroot/bin/mftest -t teensy35 -n8 -y # ...dual-Z endstop stress config +``` + +All real test targets for this HAL live under `buildroot/tests/teensy35/` (`config-01` … `config-08`, all with `motherboard = BOARD_TEENSY35_36`). There is no `buildroot/tests/teensy36/` folder — the 3.6 build reuses the same HAL and board define, only differing by the PlatformIO `board = teensy36` (which flips `__MK66FX1M0__` on). To exercise the 3.6-only path, build env `teensy36` (`pio run -e teensy36`); otherwise the `teensy35` targets cover the shared code. + +The tighter constraint is the **Teensy 3.5** (512 KB flash). If it fits there, the 3.6 (1 MB) will too. + +> Do NOT use `rm -rf .pio/build/...` to force a rebuild — the cross-profile write guard blocks it and it is unsafe. Use `pio run -e -t clean` (removes only build artifacts) or just let `mftest` rebuild. + +## Framework / Platform Integration (upstream, NOT vendored) + +The Teensy Arduino core is the **upstream Teensyduino** package (`platform = teensy@~4.12.0` in `ini/teensy.ini`), provided by PlatformIO — it is NOT vendored under `buildroot/share/PlatformIO/` like the AT32 framework is. The `teensy_arm` env pulls in Teensyduino's `cores/teensy3/` (register headers like `kinetis.h`, `pins_teensy.h`, and the native `Serial1…`, `SerialUSB`, `SPI`, `Wire`, and `avr/eeprom.h` emulation). Marlin glue lives entirely in this HAL folder plus `Marlin/src/pins/teensy3/pins_TEENSY35_36.h`. + +There is no installed-package-vs-repo mirror step (that warning applies only to the vendored AT32 framework). Build changes are just source edits under `Marlin/src/HAL/TEENSY35_36/` and `buildroot/` scripts. + +### Gotchas that are not obvious from the source + +1. **Chip is chosen by a compile-time macro, not runtime.** `HAL.h` does `#ifdef __MK66FX1M0__ … IS_TEENSY36 … #else IS_TEENSY35`, and every `.cpp` in the folder is wrapped in `#if defined(__MK64FX512__) || defined(__MK66FX1M0__)`. The 3.5 vs 3.6 distinction comes purely from the `board = teensy35` / `board = teensy36` PlatformIO JSON. Don't add runtime branching for the chip. + +2. **No real EEPROM — the core emulates it in flash.** `eeprom.cpp` uses `USE_WIRED_EEPROM` and `#include `; Teensyduino supplies an _emulated_ EEPROM (backed by program flash) since the Kinetis parts have no physical EEPROM. `MARLIN_EEPROM_SIZE` defaults to `E2END + 1`. The HAL adds `delay(2)` / `safe_delay(2)` between byte writes specifically to avoid tripping the watchdog during the slow flash-write cycles — keep that; it is load-bearing. + +3. **Raw Kinetis register names come from the Teensyduino core.** `HAL.cpp` references `RCM_SRS0`, `WDOG_TOVALH`, `WDOG_STCTRLH`, `WDOG_REFRESH`, `ADC0_SC3`, `ADC1_SC3`, `IRQ_FTM1`, `NVIC_ENABLE_IRQ`, and `CORE_PIN…` (`fastio.h`) — all defined by Teensyduino's `cores/teensy3/`, not by Marlin. Do not redefine them; if a build can't find `RCM_SRS0` etc., the wrong core is selected, not a missing Marlin header. + +4. **Dual-ADC with a physical-pin→SC1A lookup table.** `adc_start()` indexes a `pin2sc1a[]` array (in `HAL.cpp`) by _physical pin number_; digital-only pins map to `255` and yield `adc_select = -1`. Pin→ADC-channel wiring is fixed by the Teensy 3.5/3.6 silicon — match it against the Teensyduino `pins_teensy.h` numbering if it ever changes. + +5. **Watchdog is the Kinetis `WDOG`, refreshed with a two-write sequence.** `watchdog_refresh()` writes `0xA602` then `0xB480` to `WDOG_REFRESH` (hardware-enforced order). The Teensy 3.x has no separate cortex `WDT`; don't port the STM32-style watchdog code here. + +6. **Serial objects are native — no custom `Serial` global needed.** Unlike the AT32 HAL, Teensyduino already provides `Serial1`…`Serial3`, `SerialUSB`, and a bare `Serial`. `HAL.h`/`HAL.cpp` only forward-declare `MSerialN` via `ForwardSerial1Class` and `USBSerial` (wrapping `SerialUSB`), with `SERIAL_INDEX_MIN 0` / `SERIAL_INDEX_MAX 3`. No `HardwareSerial& Serial = Serial1;` alias hack is required. + +7. **SPI wraps the native `SPI` (SPIClass).** `MarlinSPI = SPIClass` (in `MarlinSPI.h`); `HAL_SPI.cpp` drives the Teensyduino `` `SPI` object via `SPISettings`/`SPI.transfer`. `spiBegin()` configures the SD pins as GPIO. Note `spiSendBlock()` still contains leftover direct `SPDR` writes mixed with `SPI.transfer()` — functional but don't copy the pattern elsewhere. + +8. **`reboot()` calls `_reboot_Teensyduino_()`** — a core-provided reset, not a hand-rolled `SCB->AIRCR` jump. Keep using the core helper. + +## Conventions + +- All HAL source is guarded by `#if defined(__MK64FX512__) || defined(__MK66FX1M0__)` (the chip macro, set by the PlatformIO board JSON — there is no `ARDUINO_ARCH_*` guard for this family). +- Keep HAL changes in `Marlin/src/HAL/TEENSY35_36/` and board pin changes in `Marlin/src/pins/teensy3/pins_TEENSY35_36.h`. Board selection (`BOARD_TEENSY35_36`) lives in `Marlin/src/core/boards.h`. +- The HAL folder has no `README.md` and no vendored framework — framework/platform integration changes are upstream Teensyduino behavior, not something patched in `buildroot/share/PlatformIO/`. + +## Related + +- Wiki: [[hal-teensy35-36]], [[marlin-src-hal]]. +- `Marlin/src/HAL/shared/` — shared HAL APIs (`serial_ports.h`, `HAL_SPI.h`, `eeprom_api.h`) this HAL reuses. +- `Marlin/src/HAL/TEENSY31_32/` and `Marlin/src/HAL/TEENSY40_41/` — sibling Teensy HALs (different silicon, different macros). diff --git a/Marlin/src/HAL/TEENSY35_36/HAL.cpp b/Marlin/src/HAL/TEENSY35_36/HAL.cpp index bc02ac1c4524..f41582be30e9 100644 --- a/Marlin/src/HAL/TEENSY35_36/HAL.cpp +++ b/Marlin/src/HAL/TEENSY35_36/HAL.cpp @@ -37,10 +37,21 @@ #define _IMPLEMENT_SERIAL(X) DefaultSerial##X MSerial##X(false, Serial##X) #define IMPLEMENT_SERIAL(X) _IMPLEMENT_SERIAL(X) -#if WITHIN(SERIAL_PORT, 0, 3) +#if WITHIN(SERIAL_PORT, SERIAL_INDEX_MIN, SERIAL_INDEX_MAX) IMPLEMENT_SERIAL(SERIAL_PORT); #endif - +#if defined(SERIAL_PORT_2) && WITHIN(SERIAL_PORT_2, SERIAL_INDEX_MIN, SERIAL_INDEX_MAX) + IMPLEMENT_SERIAL(SERIAL_PORT_2); +#endif +#if defined(SERIAL_PORT_3) && WITHIN(SERIAL_PORT_3, SERIAL_INDEX_MIN, SERIAL_INDEX_MAX) + IMPLEMENT_SERIAL(SERIAL_PORT_3); +#endif +#if defined(MMU_SERIAL_PORT) && WITHIN(MMU_SERIAL_PORT, SERIAL_INDEX_MIN, SERIAL_INDEX_MAX) + IMPLEMENT_SERIAL(MMU_SERIAL_PORT); +#endif +#if defined(LCD_SERIAL_PORT) && WITHIN(LCD_SERIAL_PORT, SERIAL_INDEX_MIN, SERIAL_INDEX_MAX) + IMPLEMENT_SERIAL(LCD_SERIAL_PORT); +#endif USBSerialType USBSerial(false, SerialUSB); // ------------------------ diff --git a/Marlin/src/HAL/TEENSY35_36/HAL.h b/Marlin/src/HAL/TEENSY35_36/HAL.h index 692133400390..85d02cec8cce 100644 --- a/Marlin/src/HAL/TEENSY35_36/HAL.h +++ b/Marlin/src/HAL/TEENSY35_36/HAL.h @@ -70,17 +70,10 @@ typedef ForwardSerial1Class USBSerialType; extern USBSerialType USBSerial; -#define _MSERIAL(X) MSerial##X -#define MSERIAL(X) _MSERIAL(X) - -#if SERIAL_PORT == -1 - #define MYSERIAL1 USBSerial -#elif WITHIN(SERIAL_PORT, 0, 3) - #define MYSERIAL1 MSERIAL(SERIAL_PORT) - DECLARE_SERIAL(SERIAL_PORT); -#else - #error "SERIAL_PORT must be from 0 to 3, or -1 for Native USB." -#endif +#define SERIAL_INDEX_MIN 0 +#define SERIAL_INDEX_MAX 3 +#define USB_SERIAL_PORT(...) USBSerial +#include "../shared/serial_ports.h" // ------------------------ // Types @@ -103,7 +96,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 @@ -154,7 +147,7 @@ class MarlinHAL { static void delay_ms(const int ms) { delay(ms); } - // Tasks, called from idle() + // Tasks, called from marlin.idle() static void idletask() {} // Reset diff --git a/Marlin/src/HAL/TEENSY35_36/Servo.h b/Marlin/src/HAL/TEENSY35_36/Servo.h index 719011f102a0..c37567a81301 100644 --- a/Marlin/src/HAL/TEENSY35_36/Servo.h +++ b/Marlin/src/HAL/TEENSY35_36/Servo.h @@ -35,7 +35,5 @@ class libServo : public Servo { void move(const int value); private: typedef Servo super; - uint16_t min_ticks; - uint16_t max_ticks; uint8_t servoIndex; // Index into the channel data for this servo }; diff --git a/Marlin/src/HAL/TEENSY35_36/endstop_interrupts.h b/Marlin/src/HAL/TEENSY35_36/endstop_interrupts.h index bc8e177fb7dc..a938041b5abd 100644 --- a/Marlin/src/HAL/TEENSY35_36/endstop_interrupts.h +++ b/Marlin/src/HAL/TEENSY35_36/endstop_interrupts.h @@ -63,6 +63,7 @@ void setup_endstop_interrupts() { TERN_(USE_Z4_MAX, _ATTACH(Z4_MAX_PIN)); TERN_(USE_Z4_MIN, _ATTACH(Z4_MIN_PIN)); TERN_(USE_Z_MIN_PROBE, _ATTACH(Z_MIN_PROBE_PIN)); + TERN_(USE_CALIBRATION, _ATTACH(CALIBRATION_PIN)); TERN_(USE_I_MAX, _ATTACH(I_MAX_PIN)); TERN_(USE_I_MIN, _ATTACH(I_MIN_PIN)); TERN_(USE_J_MAX, _ATTACH(J_MAX_PIN)); diff --git a/Marlin/src/HAL/TEENSY35_36/fastio.h b/Marlin/src/HAL/TEENSY35_36/fastio.h index 622799ec8cac..b582c7bfecbe 100644 --- a/Marlin/src/HAL/TEENSY35_36/fastio.h +++ b/Marlin/src/HAL/TEENSY35_36/fastio.h @@ -53,17 +53,17 @@ #define _SET_INPUT(P) do{ \ CORE_PIN ## P ## _CONFIG = PORT_PCR_MUX(1); \ - GPIO_BITBAND(CORE_PIN ## P ## _DDRREG , CORE_PIN ## P ## _BIT) = 0; \ + GPIO_BITBAND(CORE_PIN ## P ## _DDRREG, CORE_PIN ## P ## _BIT) = 0; \ }while(0) #define _SET_OUTPUT(P) do{ \ CORE_PIN ## P ## _CONFIG = PORT_PCR_MUX(1)|PORT_PCR_SRE|PORT_PCR_DSE; \ - GPIO_BITBAND(CORE_PIN ## P ## _DDRREG , CORE_PIN ## P ## _BIT) = 1; \ + GPIO_BITBAND(CORE_PIN ## P ## _DDRREG, CORE_PIN ## P ## _BIT) = 1; \ }while(0) #define _SET_INPUT_PULLUP(P) do{ \ CORE_PIN ## P ## _CONFIG = PORT_PCR_MUX(1) | PORT_PCR_PE | PORT_PCR_PS; \ - GPIO_BITBAND(CORE_PIN ## P ## _DDRREG , CORE_PIN ## P ## _BIT) = 0; \ + GPIO_BITBAND(CORE_PIN ## P ## _DDRREG, CORE_PIN ## P ## _BIT) = 0; \ }while(0) #define _IS_INPUT(P) ((CORE_PIN ## P ## _DDRREG & CORE_PIN ## P ## _BITMASK) == 0) diff --git a/Marlin/src/HAL/TEENSY35_36/inc/SanityCheck.h b/Marlin/src/HAL/TEENSY35_36/inc/SanityCheck.h index 843905a8510a..8f2651d3ed43 100644 --- a/Marlin/src/HAL/TEENSY35_36/inc/SanityCheck.h +++ b/Marlin/src/HAL/TEENSY35_36/inc/SanityCheck.h @@ -48,3 +48,7 @@ #if USING_PULLDOWNS #error "PULLDOWN pin mode is not available for Teensy 3.5/3.6." #endif + +#if ENABLED(PINS_DEBUGGING) + #error "PINS_DEBUGGING is not yet supported for Teensy 3.5/3.6. Needs is_output(pin), etc." +#endif diff --git a/Marlin/src/HAL/TEENSY35_36/pinsDebug.h b/Marlin/src/HAL/TEENSY35_36/pinsDebug.h index 8526febf10e3..f0f61b8bbe8f 100644 --- a/Marlin/src/HAL/TEENSY35_36/pinsDebug.h +++ b/Marlin/src/HAL/TEENSY35_36/pinsDebug.h @@ -22,7 +22,23 @@ #pragma once /** - * HAL Pins Debugging for Teensy 3.5 (MK64FX512) and Teensy 3.6 (MK66FX1M0) + * Pins Debugging for Teensy 3.5 (MK64FX512) and Teensy 3.6 (MK66FX1M0) + * + * - NUMBER_PINS_TOTAL + * - MULTI_NAME_PAD + * - getPinByIndex(index) + * - printPinNameByIndex(index) + * - getPinIsDigitalByIndex(index) + * - digitalPinToAnalogIndex(pin) + * - getValidPinMode(pin) + * - isValidPin(pin) + * - isAnalogPin(pin) + * - digitalRead_mod(pin) + * - pwm_status(pin) + * - printPinPWM(pin) + * - printPinPort(pin) + * - printPinNumber(pin) + * - printPinAnalog(pin) */ #define NUMBER_PINS_TOTAL NUM_DIGITAL_PINS @@ -53,9 +69,18 @@ #define TPM1_CH1_PIN 17 #endif -#define IS_ANALOG(P) ((P) >= analogInputToDigitalPin(0) && (P) <= analogInputToDigitalPin(9)) || ((P) >= analogInputToDigitalPin(12) && (P) <= analogInputToDigitalPin(20)) +#define getPinByIndex(x) 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 getPinIsDigitalByIndex(x) pin_array[x].is_digital +#define digitalPinToAnalogIndex(P) int(P - analogInputToDigitalPin(0)) +#define getValidPinMode(P) (isValidPin(P) && IS_OUTPUT(P)) +#define isValidPin(P) (P >= 0 && P < pin_t(NUMBER_PINS_TOTAL)) +#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(P)); SERIAL_ECHO(buffer); }while(0) + +#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)); } @@ -77,7 +102,7 @@ void analog_pin_state(char buffer[], int8_t pin) { * Print a pin's PWM status. * Return true if it's currently a PWM pin. */ -bool pwm_status(int8_t pin) { +bool pwm_status(const int8_t pin) { char buffer[20]; // for the sprintf statements switch (pin) { FTM_CASE(0,0); @@ -108,4 +133,6 @@ bool pwm_status(int8_t pin) { SERIAL_ECHOPGM(" "); } -void pwm_details(uint8_t pin) { /* TODO */ } +void printPinPWM(const pin_t) { /* TODO */ } + +void printPinPort(const pin_t) {} diff --git a/Marlin/src/HAL/TEENSY35_36/spi_pins.h b/Marlin/src/HAL/TEENSY35_36/spi_pins.h index cfffdc932598..d898a923f583 100644 --- a/Marlin/src/HAL/TEENSY35_36/spi_pins.h +++ b/Marlin/src/HAL/TEENSY35_36/spi_pins.h @@ -28,4 +28,3 @@ #define SD_SCK_PIN 13 #define SD_MISO_PIN 12 #define SD_MOSI_PIN 11 -#define SD_SS_PIN 20 // SDSS // A.28, A.29, B.21, C.26, C.29 diff --git a/Marlin/src/HAL/TEENSY35_36/timers.h b/Marlin/src/HAL/TEENSY35_36/timers.h index 3536b62265f2..1a5a8460c6f4 100644 --- a/Marlin/src/HAL/TEENSY35_36/timers.h +++ b/Marlin/src/HAL/TEENSY35_36/timers.h @@ -34,7 +34,7 @@ #define FORCE_INLINE __attribute__((always_inline)) inline typedef uint32_t hal_timer_t; -#define HAL_TIMER_TYPE_MAX 0xFFFFFFFF +#define HAL_TIMER_TYPE_MAX hal_timer_t(UINT32_MAX) #define FTM0_TIMER_PRESCALE 8 #define FTM1_TIMER_PRESCALE 4 @@ -58,19 +58,18 @@ typedef uint32_t hal_timer_t; #define TEMP_TIMER_FREQUENCY 1000 -#define STEPPER_TIMER_RATE HAL_TIMER_RATE -#define STEPPER_TIMER_TICKS_PER_US ((STEPPER_TIMER_RATE) / 1000000) -#define STEPPER_TIMER_PRESCALE (CYCLES_PER_MICROSECOND / STEPPER_TIMER_TICKS_PER_US) +#define STEPPER_TIMER_RATE HAL_TIMER_RATE +#define STEPPER_TIMER_TICKS_PER_US ((STEPPER_TIMER_RATE) / 1000000UL) // (MHz) Stepper Timer ticks per µs +#define STEPPER_TIMER_PRESCALE (CYCLES_PER_MICROSECOND / STEPPER_TIMER_TICKS_PER_US) -#define PULSE_TIMER_RATE STEPPER_TIMER_RATE // frequency of pulse timer -#define PULSE_TIMER_PRESCALE STEPPER_TIMER_PRESCALE -#define PULSE_TIMER_TICKS_PER_US STEPPER_TIMER_TICKS_PER_US +#define PULSE_TIMER_RATE STEPPER_TIMER_RATE // (Hz) Frequency of Pulse Timer +#define PULSE_TIMER_PRESCALE STEPPER_TIMER_PRESCALE -#define ENABLE_STEPPER_DRIVER_INTERRUPT() HAL_timer_enable_interrupt(MF_TIMER_STEP) -#define DISABLE_STEPPER_DRIVER_INTERRUPT() HAL_timer_disable_interrupt(MF_TIMER_STEP) -#define STEPPER_ISR_ENABLED() HAL_timer_interrupt_enabled(MF_TIMER_STEP) +#define ENABLE_STEPPER_DRIVER_INTERRUPT() HAL_timer_enable_interrupt(MF_TIMER_STEP) +#define DISABLE_STEPPER_DRIVER_INTERRUPT() HAL_timer_disable_interrupt(MF_TIMER_STEP) +#define STEPPER_ISR_ENABLED() HAL_timer_interrupt_enabled(MF_TIMER_STEP) -#define ENABLE_TEMPERATURE_INTERRUPT() HAL_timer_enable_interrupt(MF_TIMER_TEMP) +#define ENABLE_TEMPERATURE_INTERRUPT() HAL_timer_enable_interrupt(MF_TIMER_TEMP) #define DISABLE_TEMPERATURE_INTERRUPT() HAL_timer_disable_interrupt(MF_TIMER_TEMP) #ifndef HAL_STEP_TIMER_ISR @@ -110,4 +109,4 @@ void HAL_timer_disable_interrupt(const uint8_t timer_num); bool HAL_timer_interrupt_enabled(const uint8_t timer_num); void HAL_timer_isr_prologue(const uint8_t timer_num); -#define HAL_timer_isr_epilogue(T) NOOP +inline void HAL_timer_isr_epilogue(const uint8_t) {} diff --git a/Marlin/src/HAL/TEENSY40_41/AGENTS.md b/Marlin/src/HAL/TEENSY40_41/AGENTS.md new file mode 100644 index 000000000000..9ca76a65aacf --- /dev/null +++ b/Marlin/src/HAL/TEENSY40_41/AGENTS.md @@ -0,0 +1,78 @@ +# AGENTS.md — TEENSY40_41 HAL + +Working notes and conventions for the **TEENSY40_41 HAL** folder (`Marlin/src/HAL/TEENSY40_41/`). + +## Build & Test Loop + +Always validate with `mftest` (it regenerates `Marlin/Configuration.h` per target, which raw `pio run -e` does not do reliably). The build shortcut for these boards is `teensy41` / `t41` (and `teensy40` / `t40` for the bare 4.0 env). Only the **Teensy 4.1** board has committed test fixtures, so that is the primary green target: + +```bash +cd "$(git rev-parse --show-toplevel)" +buildroot/bin/mftest -t teensy41 -n1 -y # config-01 : default BOARD_TEENSY41 +buildroot/bin/mftest -t teensy41 -n8 -y # config-08 : dual Z + dual Z endstops +``` + +The 8 fixtures live in `buildroot/tests/teensy41/config-0[1-8].ini`, all `motherboard = BOARD_TEENSY41`. Run the whole set (or at least config-01 plus a feature-heavy one like config-08) before considering TEENSY40_41 work done. + +To compile for the **Teensy 4.0** board specifically (no committed fixtures), build the env directly from a `MOTHERBOARD`: + +```bash +buildroot/bin/mftest -t teensy40 -b # -b builds the auto-detected env +# or: pio run -e teensy40 +``` + +Both `teensy40` and `teensy41` envs pull in `src/HAL/TEENSY40_41/` via `ini/teensy.ini`; the only difference is `-DIS_TEENSY40` vs `-DIS_TEENSY41`. + +> Do NOT use `rm -rf .pio/build/...` to force a rebuild — the cross-profile write guard blocks it and it is unsafe. Use `pio run -e -t clean` (removes only build artifacts) or just let `mftest` rebuild. + +## Framework / Platform Integration (upstream, NOT vendored) + +Unlike the AT32 / HC32 HALs, the Teensy Arduino core is **NOT vendored** in the Marlin repo. The build uses the upstream **Teensyduino** core shipped by PlatformIO's `teensy` platform (`platform = teensy@~4.12.0` in `ini/teensy.ini`). The entire HAL source is guarded by `#ifdef __IMXRT1062__` (the chip macro the upstream core defines), not by a Marlin-injected `ARDUINO_ARCH_*` define. + +Practical consequences: + +- There is **no vendored framework to keep in sync**. Framework/platform fixes are not made in `buildroot/share/PlatformIO/` for this HAL — they belong upstream in Teensyduino. If a Hal-level bug is really a core bug, document it and file it upstream; do not vendor a copy. +- The Teensy core provides `Wire.h`, `SPI.h`, `avr/eeprom.h` (EEPROM emulation), `Serial`, `Serial1`–`Serial8`, and `SerialUSB`. Marlin's `HAL.cpp` wires these into its `MSerialX` / `USBSerial` forwarders (see `HAL.h` serial-port block). +- Build flags come entirely from `ini/teensy.ini` + the HAL's `extra_flags`. There is no Marlin-side `platformio-build-*.py` to patch for size/optimization — the Teensy platform already builds `-O2` correctly. + +### Gotchas that are not obvious from the source + +1. **`__IMXRT1062__` is the real guard.** `HAL.h`/`HAL.cpp`/`eeprom.cpp` all guard on `__IMXRT1062__`, not `IS_TEENSY40_41`. `IS_TEENSY40_41` / `IS_TEENSY41` / `IS_TEENSY40` are _derived_ macros used for behavior selection inside the HAL (`HAL.h` sets `IS_TEENSY41` unless `IS_TEENSY40` is defined). Don't add `ARDUINO_ARCH_TEENSY` guards — they don't exist here. + +2. **TFT displays are unsupported.** `inc/SanityCheck.h` hard-errors on `HAS_SPI_TFT || HAS_FSMC_TFT` ("TFT displays are not available for Teensy 4.0/4.1"). Don't try to enable an SPI/FSMC TFT on this HAL. + +3. **`EMERGENCY_PARSER` is not implemented.** SanityCheck errors out if `EMERGENCY_PARSER` is enabled. Hosts must rely on the normal command queue, not the emergency-parser fast path. + +4. **TMC220x software serial is unsupported.** `HAS_TMC_SW_SERIAL` errors out. TMC drivers must use hardware UART or SPI (the `MarlinSPI.h` / `HAL_SPI.cpp` path). + +5. **`POSTMORTEM_DEBUGGING` and `SERIAL_STATS_*` are unsupported.** Both error out in SanityCheck. Don't enable them for Teensy 4.x boards. + +6. **EEPROM uses the Teensyduino emulated EEPROM, not SD-card emulation.** `eeprom.cpp` is guarded by `USE_WIRED_EEPROM` and includes the upstream `` (Teensyduino's flash-backed emulation). It does **not** use `SDCARD_EEPROM_EMULATION`; per-board `pins_teensy*` files do not need to define an SD-backed store. `MARLIN_EEPROM_SIZE` defaults to `E2END + 1` from the core. + +7. **`PSTR` is redefined to defeat PROGMEM.** `HAL.h` does `#undef PSTR` / `#define PSTR(str) ({static const char *data = (str); &data[0];})` because the IMXRT1062 has linear address space and the AVR `PSTR`/progmem model does not apply. String literals in this HAL behave like normal RAM pointers — do not assume `__flash`/`PROGMEM` semantics. + +8. **`sq()` is redefined.** `HAL.h` `#undef sq` / `#define sq(x) ((x)*(x))` to match the Teensy core's math macros and avoid collisions with the Arduino `sq`. + +9. **ADC pins map to a different index range.** `analogInputToDigitalPin(p)` returns `p + 54` for `p < 12` (Teensy's analog pins are numbered 54+), else `-1`. Pin numbers here are the Teensy digital pin numbers, not the AVR-style `A0`→`0` scheme. + +10. **FastIO goes through `digitalRead`/`digitalWrite`, not direct-port manipulation.** `fastio.h` implements `READ`/`WRITE`/`SET_*` as calls to the Teensy GPIO functions (no AVR-style port registers). `is_output(pin)` is a real helper in `HAL.cpp` used by `_GET_MODE`/`IS_INPUT`/`IS_OUTPUT`. PWM capability is `digitalPinHasPWM(P)`. + +11. **PWM frequency is set per-pin via the Teensy pulse API.** `set_pwm_frequency()` wraps `analogWriteFrequency()`; `set_pwm_duty()` wraps `analogWrite()`. The SanityCheck emits a `#warning` if `FAST_PWM_FAN` is left at the 1000 Hz default (suggesting a higher, quieter frequency) and another `#warning` that `SPINDLE_LASER_USE_PWM` is untested. See https://www.pjrc.com/teensy/td_pulse.html. + +12. **Serial port indices are 0–8, with USB and Ethernet aliases.** `HAL.h` defines `SERIAL_INDEX_MIN 0` / `SERIAL_INDEX_MAX 8`, `Serial0` = `Serial`, `USBSerial` (forwarding `SerialUSB`), and `ETH_SERIAL_PORT` = `ethernet.telnetClient`. `BAUD_RATE_GCODE` is rejected when `SERIAL_PORT_2 == -2` (the Ethernet serial port). + +13. **No `FRAME` / variant linker script surgery needed.** The Teensy platform supplies the correct flash/RAM linker script for both 4.0 (1 MB flash / 512 KB RAM) and 4.1 (1 MB / 8 MB PSRAM variants). There is no per-board linker-script assignment to maintain in the Marlin repo. + +## Conventions + +- All HAL source is guarded by `#ifdef __IMXRT1062__`. Select 4.0-vs-4.1 behavior with `IS_TEENSY40` / `IS_TEENSY41` (set from `extra_flags` in `ini/teensy.ini`). +- Keep HAL implementation in `Marlin/src/HAL/TEENSY40_41/` and board pin files in `Marlin/src/pins/teensy40_41/` (and any shared `pins_teensy*` helpers). +- Because the Arduino core is upstream (not vendored), do NOT mirror framework changes into `buildroot/share/PlatformIO/` for this HAL — there is nothing to mirror. +- Don't paper over SanityCheck errors with config edits; the errors document real unsupported features (TFT, emergency parser, TMC SW serial, postmortem, serial stats). + +## Related + +- `ini/teensy.ini` — env definitions for `teensy31` / `teensy35` / `teensy36` / `teensy40` / `teensy41`. +- `buildroot/tests/teensy41/*.ini` — the committed test fixtures for this HAL. +- Wiki: [[hal-teensy40-41]], [[marlin-src-hal]]. +- `Marlin/src/HAL/shared/` — shared HAL APIs (`eeprom_api`, SPI helpers, serial ports) the TEENSY40_41 HAL reuses. diff --git a/Marlin/src/HAL/TEENSY40_41/HAL.cpp b/Marlin/src/HAL/TEENSY40_41/HAL.cpp index 4dd5c3678d78..3a1f06095ba1 100644 --- a/Marlin/src/HAL/TEENSY40_41/HAL.cpp +++ b/Marlin/src/HAL/TEENSY40_41/HAL.cpp @@ -39,18 +39,20 @@ #define _IMPLEMENT_SERIAL(X) DefaultSerial##X MSerial##X(false, Serial##X) #define IMPLEMENT_SERIAL(X) _IMPLEMENT_SERIAL(X) -#if WITHIN(SERIAL_PORT, 0, 8) +#if WITHIN(SERIAL_PORT, SERIAL_INDEX_MIN, SERIAL_INDEX_MAX) IMPLEMENT_SERIAL(SERIAL_PORT); #endif -#ifdef SERIAL_PORT_2 - #if WITHIN(SERIAL_PORT_2, 0, 8) - IMPLEMENT_SERIAL(SERIAL_PORT_2); - #endif +#if defined(SERIAL_PORT_2) && WITHIN(SERIAL_PORT_2, SERIAL_INDEX_MIN, SERIAL_INDEX_MAX) + IMPLEMENT_SERIAL(SERIAL_PORT_2); #endif -#ifdef SERIAL_PORT_3 - #if WITHIN(SERIAL_PORT_3, 0, 8) - IMPLEMENT_SERIAL(SERIAL_PORT_3); - #endif +#if defined(SERIAL_PORT_3) && WITHIN(SERIAL_PORT_3, SERIAL_INDEX_MIN, SERIAL_INDEX_MAX) + IMPLEMENT_SERIAL(SERIAL_PORT_3); +#endif +#if defined(MMU_SERIAL_PORT) && WITHIN(MMU_SERIAL_PORT, SERIAL_INDEX_MIN, SERIAL_INDEX_MAX) + IMPLEMENT_SERIAL(MMU_SERIAL_PORT); +#endif +#if defined(LCD_SERIAL_PORT) && WITHIN(LCD_SERIAL_PORT, SERIAL_INDEX_MIN, SERIAL_INDEX_MAX) + IMPLEMENT_SERIAL(LCD_SERIAL_PORT); #endif USBSerialType USBSerial(false, SerialUSB); @@ -96,7 +98,7 @@ void MarlinHAL::clear_reset_source() { #define WDT_TIMEOUT TERN(WATCHDOG_DURATION_8S, 8, 4) // 4 or 8 second timeout - constexpr uint8_t timeoutval = (WDT_TIMEOUT - 0.5f) / 0.5f; + constexpr uint8_t timeoutval = (WDT_TIMEOUT - 0.5f) * 2.0f; void MarlinHAL::watchdog_init() { CCM_CCGR3 |= CCM_CCGR3_WDOG1(3); // enable WDOG1 clocks @@ -202,18 +204,13 @@ uint16_t MarlinHAL::adc_value() { // Free Memory Accessor // ------------------------ -#define __bss_end _ebss - extern "C" { - extern char __bss_end; - extern char __heap_start; - extern void* __brkval; + // Reference for Teensy 4.x: https://forum.pjrc.com/index.php?threads/how-to-display-free-ram.33443/#post-275013 + extern unsigned long _heap_end; + extern char *__brkval; - // Doesn't work on Teensy 4.x uint32_t freeMemory() { - uint32_t free_memory; - free_memory = ((uint32_t)&free_memory) - (((uint32_t)__brkval) ?: ((uint32_t)&__bss_end)); - return free_memory; + return (char *)&_heap_end - __brkval; } } diff --git a/Marlin/src/HAL/TEENSY40_41/HAL.h b/Marlin/src/HAL/TEENSY40_41/HAL.h index fa5971a68102..fc75539e9b23 100644 --- a/Marlin/src/HAL/TEENSY40_41/HAL.h +++ b/Marlin/src/HAL/TEENSY40_41/HAL.h @@ -61,6 +61,8 @@ #undef PSTR #define PSTR(str) ({static const char *data = (str); &data[0];}) +#define HAL_CAN_SET_PWM_FREQ + // ------------------------ // Serial ports // ------------------------ @@ -76,41 +78,11 @@ typedef ForwardSerial1Class USBSerialType; extern USBSerialType USBSerial; -#define _MSERIAL(X) MSerial##X -#define MSERIAL(X) _MSERIAL(X) - -#if SERIAL_PORT == -1 - #define MYSERIAL1 USBSerial -#elif WITHIN(SERIAL_PORT, 0, 8) - DECLARE_SERIAL(SERIAL_PORT); - #define MYSERIAL1 MSERIAL(SERIAL_PORT) -#else - #error "The required SERIAL_PORT must be from 0 to 8, or -1 for Native USB." -#endif - -#ifdef SERIAL_PORT_2 - #if SERIAL_PORT_2 == -1 - #define MYSERIAL2 USBSerial - #elif SERIAL_PORT_2 == -2 - #define MYSERIAL2 ethernet.telnetClient - #elif WITHIN(SERIAL_PORT_2, 0, 8) - DECLARE_SERIAL(SERIAL_PORT_2); - #define MYSERIAL2 MSERIAL(SERIAL_PORT_2) - #else - #error "SERIAL_PORT_2 must be from 0 to 8, or -1 for Native USB, or -2 for Ethernet." - #endif -#endif - -#ifdef SERIAL_PORT_3 - #if SERIAL_PORT_3 == -1 - #define MYSERIAL3 USBSerial - #elif WITHIN(SERIAL_PORT_3, 0, 8) - DECLARE_SERIAL(SERIAL_PORT_3); - #define MYSERIAL3 MSERIAL(SERIAL_PORT_3) - #else - #error "SERIAL_PORT_3 must be from 0 to 8, or -1 for Native USB." - #endif -#endif +#define SERIAL_INDEX_MIN 0 +#define SERIAL_INDEX_MAX 8 +#define USB_SERIAL_PORT(...) USBSerial +#define ETH_SERIAL_PORT(...) ethernet.telnetClient +#include "../shared/serial_ports.h" // ------------------------ // Types @@ -133,7 +105,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 @@ -188,7 +160,7 @@ class MarlinHAL { static void delay_ms(const int ms) { delay(ms); } - // Tasks, called from idle() + // Tasks, called from marlin.idle() static void idletask() {} // Reset @@ -221,11 +193,15 @@ class MarlinHAL { /** * Set the PWM duty cycle for the pin to the given value. - * No option to invert the duty cycle [default = false] - * No option to change the scale of the provided value to enable finer PWM duty control [default = 255] + * Optionally invert the duty cycle [default = false] + * Optionally change the scale of the provided value to enable finer PWM duty control [default = 255] */ - static void set_pwm_duty(const pin_t pin, const uint16_t v, const uint16_t=255, const bool=false) { - analogWrite(pin, v); - } + static void set_pwm_duty(const pin_t pin, const uint16_t v, const uint16_t v_size=255, const bool invert=false); + /** + * Set the PWM output frequency. This may affect multiple pins, though + * Teensy 4.x provides many timers affecting only a single pin. + * See: https://www.pjrc.com/teensy/td_pulse.html + */ + static void set_pwm_frequency(const pin_t pin, const uint16_t f_desired); }; diff --git a/Marlin/src/HAL/TEENSY40_41/Servo.h b/Marlin/src/HAL/TEENSY40_41/Servo.h index 699fd700c943..fceb9465ea29 100644 --- a/Marlin/src/HAL/TEENSY40_41/Servo.h +++ b/Marlin/src/HAL/TEENSY40_41/Servo.h @@ -37,7 +37,5 @@ class libServo : public PWMServo { private: typedef PWMServo super; uint8_t servoPin; - uint16_t min_ticks; - uint16_t max_ticks; uint8_t servoIndex; // Index into the channel data for this servo }; diff --git a/Marlin/src/HAL/TEENSY40_41/endstop_interrupts.h b/Marlin/src/HAL/TEENSY40_41/endstop_interrupts.h index 722912c89056..f59a9409c872 100644 --- a/Marlin/src/HAL/TEENSY40_41/endstop_interrupts.h +++ b/Marlin/src/HAL/TEENSY40_41/endstop_interrupts.h @@ -63,6 +63,7 @@ void setup_endstop_interrupts() { TERN_(USE_Z4_MAX, _ATTACH(Z4_MAX_PIN)); TERN_(USE_Z4_MIN, _ATTACH(Z4_MIN_PIN)); TERN_(USE_Z_MIN_PROBE, _ATTACH(Z_MIN_PROBE_PIN)); + TERN_(USE_CALIBRATION, _ATTACH(CALIBRATION_PIN)); TERN_(USE_I_MAX, _ATTACH(I_MAX_PIN)); TERN_(USE_I_MIN, _ATTACH(I_MIN_PIN)); TERN_(USE_J_MAX, _ATTACH(J_MAX_PIN)); diff --git a/Marlin/src/HAL/TEENSY40_41/fast_pwm.cpp b/Marlin/src/HAL/TEENSY40_41/fast_pwm.cpp new file mode 100644 index 000000000000..21531d115d0b --- /dev/null +++ b/Marlin/src/HAL/TEENSY40_41/fast_pwm.cpp @@ -0,0 +1,54 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2025 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 . + * + */ + +#ifdef __IMXRT1062__ + +#include "../../inc/MarlinConfig.h" + +#include "HAL.h" + +void MarlinHAL::set_pwm_duty(const pin_t pin, const uint16_t v, const uint16_t v_size /*=255*/, const bool invert) { + + uint32_t bits = 1; + uint16_t value = _MIN(v, v_size); + + if (invert) value = v_size - value; + + // Choose scale as smallest power of 2 which holds v_size. + uint32_t scale = 1; + while (scale < v_size) { + bits++; + scale *= 2; + } + + uint32_t scaled_val = scale * value / v_size; + + uint32_t prior = analogWriteResolution(bits); + analogWrite(pin, scaled_val); + analogWriteResolution(prior); +} + +void MarlinHAL::set_pwm_frequency(const pin_t pin, const uint16_t f_desired) { + analogWriteFrequency(pin, f_desired); +} + +#endif // __IMXRT1062__ diff --git a/Marlin/src/HAL/TEENSY40_41/inc/SanityCheck.h b/Marlin/src/HAL/TEENSY40_41/inc/SanityCheck.h index 731658b4a31c..b02fd480b40e 100644 --- a/Marlin/src/HAL/TEENSY40_41/inc/SanityCheck.h +++ b/Marlin/src/HAL/TEENSY40_41/inc/SanityCheck.h @@ -33,10 +33,6 @@ #error "EMERGENCY_PARSER is not yet implemented for Teensy 4.0/4.1. Disable EMERGENCY_PARSER to continue." #endif -#if ENABLED(FAST_PWM_FAN) || SPINDLE_LASER_FREQUENCY - #error "Features requiring Hardware PWM (FAST_PWM_FAN, SPINDLE_LASER_FREQUENCY) are not yet supported for Teensy 4.0/4.1." -#endif - #if HAS_TMC_SW_SERIAL #error "TMC220x Software Serial is not supported for Teensy 4.0/4.1." #endif @@ -44,3 +40,19 @@ #if ENABLED(POSTMORTEM_DEBUGGING) #error "POSTMORTEM_DEBUGGING is not yet supported for Teensy 4.0/4.1." #endif + +#if ANY(SERIAL_STATS_MAX_RX_QUEUED, SERIAL_STATS_DROPPED_RX, SERIAL_STATS_RX_FRAMING_ERRORS, SERIAL_STATS_RX_BUFFER_OVERRUNS) + #error "SERIAL_STATS_* features not supported on Teensy 4.0/4.1." +#endif + +#if ENABLED(BAUD_RATE_GCODE) && SERIAL_PORT_2 == -2 + #error "BAUD_RATE_GCODE cannot be enabled when using the Ethernet serial port." +#endif + +#if ENABLED(SPINDLE_LASER_USE_PWM) && !ENABLED(SILENCE_TEENSY_SPINDLE_LASER_WARNING) + #warning "SPINDLE_LASER_USE_PWM is untested on Teensy 4.0/4.1, see https://www.pjrc.com/teensy/td_pulse.html for details on frequencies, and resolution. #define SILENCE_TEENSY_SPINDLE_LASER_WARNING to silence warning." +#endif + +#if ENABLED(FAST_PWM_FAN) && FAST_PWM_FAN_FREQUENCY == 1000U + #warning "FAST_PWM_FAN_FREQUENCY has been left as default, see https://www.pjrc.com/teensy/td_pulse.html and consider raising it to reduce noise." +#endif diff --git a/Marlin/src/HAL/TEENSY40_41/pinsDebug.h b/Marlin/src/HAL/TEENSY40_41/pinsDebug.h index 54f3cb5885a3..3307b3f71fd6 100644 --- a/Marlin/src/HAL/TEENSY40_41/pinsDebug.h +++ b/Marlin/src/HAL/TEENSY40_41/pinsDebug.h @@ -22,23 +22,39 @@ #pragma once /** - * HAL Pins Debugging for Teensy 4.0 (IMXRT1062DVL6A) / 4.1 (IMXRT1062DVJ6A) + * Pins Debugging for Teensy 4.0 (IMXRT1062DVL6A) / 4.1 (IMXRT1062DVJ6A) + * + * - NUMBER_PINS_TOTAL + * - MULTI_NAME_PAD + * - getPinByIndex(index) + * - printPinNameByIndex(index) + * - getPinIsDigitalByIndex(index) + * - digitalPinToAnalogIndex(pin) + * - getValidPinMode(pin) + * - isValidPin(pin) + * - isAnalogPin(pin) + * - digitalRead_mod(pin) + * - pwm_status(pin) + * - printPinPWM(pin) + * - printPinPort(pin) + * - printPinNumber(pin) + * - printPinAnalog(pin) */ #warning "PINS_DEBUGGING is not fully supported for Teensy 4.0 / 4.1 so 'M43' may cause hangs." #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 getPinByIndex(x) 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 getPinIsDigitalByIndex(x) pin_array[x].is_digital +#define digitalPinToAnalogIndex(P) int(P - analogInputToDigitalPin(0)) +#define getValidPinMode(P) (isValidPin(P) && IS_OUTPUT(P)) +#define isValidPin(P) (P >= 0 && P < pin_t(NUMBER_PINS_TOTAL)) +#define isAnalogPin(P) (pin_t(P) >= analogInputToDigitalPin(0) && pin_t(P) <= analogInputToDigitalPin(13)) || (pin_t(P) >= analogInputToDigitalPin(14) && pin_t(P) <= analogInputToDigitalPin(17)) +#define digitalRead_mod(P) extDigitalRead(P) // AVR digitalRead disabled PWM before it read the pin +#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(P)); SERIAL_ECHO(buffer); }while(0) #define MULTI_NAME_PAD 16 // space needed to be pretty if not first name assigned to a pin struct pwm_pin_info_struct { @@ -118,7 +134,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 +165,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) {} diff --git a/Marlin/src/HAL/TEENSY40_41/spi_pins.h b/Marlin/src/HAL/TEENSY40_41/spi_pins.h index ba4a2c700a4b..fc1c2a7fed2b 100644 --- a/Marlin/src/HAL/TEENSY40_41/spi_pins.h +++ b/Marlin/src/HAL/TEENSY40_41/spi_pins.h @@ -28,4 +28,3 @@ #define SD_SCK_PIN 13 #define SD_MISO_PIN 12 #define SD_MOSI_PIN 11 -#define SD_SS_PIN 20 // SDSS // A.28, A.29, B.21, C.26, C.29 diff --git a/Marlin/src/HAL/TEENSY40_41/timers.cpp b/Marlin/src/HAL/TEENSY40_41/timers.cpp index ed99f65d6e26..011f9fe31c7c 100644 --- a/Marlin/src/HAL/TEENSY40_41/timers.cpp +++ b/Marlin/src/HAL/TEENSY40_41/timers.cpp @@ -30,41 +30,82 @@ void HAL_timer_start(const uint8_t timer_num, const uint32_t frequency) { switch (timer_num) { + + // + // Step Timer – GPT1 - Compare Interrupt OCR1 - Reset Mode + // case MF_TIMER_STEP: - CCM_CSCMR1 &= ~CCM_CSCMR1_PERCLK_CLK_SEL; // turn off 24mhz mode + // 24MHz mode off – Use peripheral clock (150MHz) + CCM_CSCMR1 &= ~CCM_CSCMR1_PERCLK_CLK_SEL; + // Enable GPT1 clock gating CCM_CCGR1 |= CCM_CCGR1_GPT1_BUS(CCM_CCGR_ON); - GPT1_CR = 0; // disable timer - GPT1_SR = 0x3F; // clear all prior status + // Disable timer, clear all status bits + GPT1_CR = 0; // Disable timer + GPT1_SR = 0x3F; // Clear all prior status + + // Prescaler = 2 => 75MHz counting clock GPT1_PR = GPT1_TIMER_PRESCALE - 1; - GPT1_CR |= GPT_CR_CLKSRC(1); //clock selection #1 (peripheral clock = 150 MHz) - GPT1_CR |= GPT_CR_ENMOD; //reset count to zero before enabling - GPT1_CR |= GPT_CR_OM1(1); // toggle mode - GPT1_OCR1 = (GPT1_TIMER_RATE / frequency) -1; // Initial compare value - GPT1_IR = GPT_IR_OF1IE; // Compare3 value - GPT1_CR |= GPT_CR_EN; //enable GPT2 counting at 150 MHz - - OUT_WRITE(15, HIGH); + + GPT1_CR = GPT_CR_CLKSRC(1) // Clock selection #1 (peripheral clock = 150 MHz) + | GPT_CR_ENMOD // Reset count to zero before enabling + | GPT_CR_OM2(TERN(MARLIN_DEV_MODE, 1, 0)); // 0 = edge compare, 1 = toggle + + // Compare value – the number of clocks between edges + GPT1_OCR1 = (GPT1_TIMER_RATE / frequency) - 1; + + // Enable compare‑event interrupt + GPT1_IR = GPT_IR_OF1IE; // OF1 interrupt enabled + + // Pull Pin 15 HIGH (logic‑high is the ā€œidleā€ state) + TERN_(MARLIN_DEV_MODE, OUT_WRITE(15, HIGH)); + + // Attach and enable Stepper IRQ + // Note: UART priority is 16 attachInterruptVector(IRQ_GPT1, &stepTC_Handler); - NVIC_SET_PRIORITY(IRQ_GPT1, 16); + NVIC_SET_PRIORITY(IRQ_GPT1, 16); // Priority 16 (higher than Temp Timer) + + // Start GPT1 counting at 150 MHz + GPT1_CR |= GPT_CR_EN; + break; + + // + // Temperature Timer – GPT2 - Compare Interrupt OCR1 - Reset Mode + // case MF_TIMER_TEMP: - CCM_CSCMR1 &= ~CCM_CSCMR1_PERCLK_CLK_SEL; // turn off 24mhz mode + // 24MHz mode off – Use peripheral clock (150MHz) + CCM_CSCMR1 &= ~CCM_CSCMR1_PERCLK_CLK_SEL; + // Enable GPT2 clock gating CCM_CCGR0 |= CCM_CCGR0_GPT2_BUS(CCM_CCGR_ON); - GPT2_CR = 0; // disable timer - GPT2_SR = 0x3F; // clear all prior status + // Disable timer, clear all status bits + GPT2_CR = 0; // Disable timer + GPT2_SR = 0x3F; // Clear all prior status + + // Prescaler = 10 => 15MHz counting clock GPT2_PR = GPT2_TIMER_PRESCALE - 1; - GPT2_CR |= GPT_CR_CLKSRC(1); //clock selection #1 (peripheral clock = 150 MHz) - GPT2_CR |= GPT_CR_ENMOD; //reset count to zero before enabling - GPT2_CR |= GPT_CR_OM1(1); // toggle mode - GPT2_OCR1 = (GPT2_TIMER_RATE / frequency) -1; // Initial compare value - GPT2_IR = GPT_IR_OF1IE; // Compare3 value - GPT2_CR |= GPT_CR_EN; //enable GPT2 counting at 150 MHz - - OUT_WRITE(14, HIGH); + + GPT2_CR = GPT_CR_CLKSRC(1) // Clock selection #1 (peripheral clock = 150 MHz) + | GPT_CR_ENMOD // and reset count to zero before enabling + | GPT_CR_OM2(TERN(MARLIN_DEV_MODE, 1, 0)); // 0 = edge compare, 1 = toggle + + // Compare value – the number of clocks between edges + GPT2_OCR1 = (GPT2_TIMER_RATE / frequency) - 1; + + // Enable compare‑event interrupt + GPT2_IR = GPT_IR_OF1IE; // OF1 interrupt enabled + + // Pull Pin 14 HIGH (logic‑high is the ā€œidleā€ state) + TERN_(MARLIN_DEV_MODE, OUT_WRITE(14, HIGH)); + + // Attach Temperature ISR attachInterruptVector(IRQ_GPT2, &tempTC_Handler); - NVIC_SET_PRIORITY(IRQ_GPT2, 32); + NVIC_SET_PRIORITY(IRQ_GPT2, 32); // Priority 32 (lower than Step Timer) + + // Start GPT2 counting at 150 MHz + GPT2_CR |= GPT_CR_EN; + break; } } @@ -82,6 +123,7 @@ void HAL_timer_disable_interrupt(const uint8_t timer_num) { case MF_TIMER_TEMP: NVIC_DISABLE_IRQ(IRQ_GPT2); break; } + // Ensure the CPU actually stops servicing the IRQ // We NEED memory barriers to ensure Interrupts are actually disabled! // ( https://dzone.com/articles/nvic-disabling-interrupts-on-arm-cortex-m-and-the ) asm volatile("dsb"); @@ -97,8 +139,8 @@ bool HAL_timer_interrupt_enabled(const uint8_t timer_num) { void HAL_timer_isr_prologue(const uint8_t timer_num) { switch (timer_num) { - case MF_TIMER_STEP: GPT1_SR = GPT_IR_OF1IE; break; // clear OF3 bit - case MF_TIMER_TEMP: GPT2_SR = GPT_IR_OF1IE; break; // clear OF3 bit + case MF_TIMER_STEP: GPT1_SR = GPT_IR_OF1IE; break; // clear OF1 + case MF_TIMER_TEMP: GPT2_SR = GPT_IR_OF1IE; break; } asm volatile("dsb"); } diff --git a/Marlin/src/HAL/TEENSY40_41/timers.h b/Marlin/src/HAL/TEENSY40_41/timers.h index 3c7cda0b4e66..66d00b178814 100644 --- a/Marlin/src/HAL/TEENSY40_41/timers.h +++ b/Marlin/src/HAL/TEENSY40_41/timers.h @@ -34,15 +34,15 @@ #define FORCE_INLINE __attribute__((always_inline)) inline typedef uint32_t hal_timer_t; -#define HAL_TIMER_TYPE_MAX 0xFFFFFFFE +#define HAL_TIMER_TYPE_MAX hal_timer_t(UINT32_MAX-1UL) -#define GPT_TIMER_RATE F_BUS_ACTUAL // 150MHz +#define GPT_TIMER_RATE (F_CPU / 4) // 150MHz (Can't use F_BUS_ACTUAL because it's extern volatile) #define GPT1_TIMER_PRESCALE 2 #define GPT2_TIMER_PRESCALE 10 -#define GPT1_TIMER_RATE (GPT_TIMER_RATE / GPT1_TIMER_PRESCALE) // 75MHz -#define GPT2_TIMER_RATE (GPT_TIMER_RATE / GPT2_TIMER_PRESCALE) // 15MHz +#define GPT1_TIMER_RATE (GPT_TIMER_RATE / GPT1_TIMER_PRESCALE) // 150MHz / 2 = 75MHz +#define GPT2_TIMER_RATE (GPT_TIMER_RATE / GPT2_TIMER_PRESCALE) // 150MHz / 10 = 15MHz #ifndef MF_TIMER_STEP #define MF_TIMER_STEP 0 // Timer Index for Stepper @@ -57,19 +57,19 @@ typedef uint32_t hal_timer_t; #define TEMP_TIMER_RATE 1000000 #define TEMP_TIMER_FREQUENCY 1000 -#define STEPPER_TIMER_RATE GPT1_TIMER_RATE -#define STEPPER_TIMER_TICKS_PER_US ((STEPPER_TIMER_RATE) / 1000000) -#define STEPPER_TIMER_PRESCALE ((GPT_TIMER_RATE / 1000000) / STEPPER_TIMER_TICKS_PER_US) +#define HAL_TIMER_RATE GPT1_TIMER_RATE +#define STEPPER_TIMER_RATE HAL_TIMER_RATE +#define STEPPER_TIMER_TICKS_PER_US ((STEPPER_TIMER_RATE) / 1000000UL) +#define STEPPER_TIMER_PRESCALE (GPT_TIMER_RATE / STEPPER_TIMER_RATE) -#define PULSE_TIMER_RATE STEPPER_TIMER_RATE // frequency of pulse timer -#define PULSE_TIMER_PRESCALE STEPPER_TIMER_PRESCALE -#define PULSE_TIMER_TICKS_PER_US STEPPER_TIMER_TICKS_PER_US +#define PULSE_TIMER_RATE STEPPER_TIMER_RATE // (Hz) Frequency of Pulse Timer +#define PULSE_TIMER_PRESCALE STEPPER_TIMER_PRESCALE -#define ENABLE_STEPPER_DRIVER_INTERRUPT() HAL_timer_enable_interrupt(MF_TIMER_STEP) -#define DISABLE_STEPPER_DRIVER_INTERRUPT() HAL_timer_disable_interrupt(MF_TIMER_STEP) -#define STEPPER_ISR_ENABLED() HAL_timer_interrupt_enabled(MF_TIMER_STEP) +#define ENABLE_STEPPER_DRIVER_INTERRUPT() HAL_timer_enable_interrupt(MF_TIMER_STEP) +#define DISABLE_STEPPER_DRIVER_INTERRUPT() HAL_timer_disable_interrupt(MF_TIMER_STEP) +#define STEPPER_ISR_ENABLED() HAL_timer_interrupt_enabled(MF_TIMER_STEP) -#define ENABLE_TEMPERATURE_INTERRUPT() HAL_timer_enable_interrupt(MF_TIMER_TEMP) +#define ENABLE_TEMPERATURE_INTERRUPT() HAL_timer_enable_interrupt(MF_TIMER_TEMP) #define DISABLE_TEMPERATURE_INTERRUPT() HAL_timer_disable_interrupt(MF_TIMER_TEMP) #ifndef HAL_STEP_TIMER_ISR @@ -88,8 +88,16 @@ void HAL_timer_start(const uint8_t timer_num, const uint32_t frequency); FORCE_INLINE static void HAL_timer_set_compare(const uint8_t timer_num, const hal_timer_t compare) { switch (timer_num) { - case MF_TIMER_STEP: GPT1_OCR1 = compare - 1; break; - case MF_TIMER_TEMP: GPT2_OCR1 = compare - 1; break; + case MF_TIMER_STEP: + GPT1_CR |= GPT_CR_FRR; // Free Run Mode (setting OCRx preserves CNT) + GPT1_OCR1 = compare - 1; + GPT1_CR &= ~GPT_CR_FRR; // Reset Mode (CNT resets on trigger) + break; + case MF_TIMER_TEMP: + GPT2_CR |= GPT_CR_FRR; // Free Run Mode (setting OCRx preserves CNT) + GPT2_OCR1 = compare - 1; + GPT2_CR &= ~GPT_CR_FRR; // Reset Mode (CNT resets on trigger) + break; } } @@ -114,5 +122,4 @@ void HAL_timer_disable_interrupt(const uint8_t timer_num); bool HAL_timer_interrupt_enabled(const uint8_t timer_num); void HAL_timer_isr_prologue(const uint8_t timer_num); -//void HAL_timer_isr_epilogue(const uint8_t timer_num) {} -#define HAL_timer_isr_epilogue(T) NOOP +inline void HAL_timer_isr_epilogue(const uint8_t) {} diff --git a/Marlin/src/HAL/platforms.h b/Marlin/src/HAL/platforms.h index 9eaf7cea9894..e7eea443082c 100644 --- a/Marlin/src/HAL/platforms.h +++ b/Marlin/src/HAL/platforms.h @@ -37,6 +37,8 @@ #define HAL_PATH(PATH, NAME) XSTR(PATH/HAL/LPC1768/NAME) #elif defined(ARDUINO_ARCH_HC32) #define HAL_PATH(PATH, NAME) XSTR(PATH/HAL/HC32/NAME) +#elif defined(ARDUINO_ARCH_MFL) + #define HAL_PATH(PATH, NAME) XSTR(PATH/HAL/GD32_MFL/NAME) #elif defined(__STM32F1__) || defined(TARGET_STM32F1) #define HAL_PATH(PATH, NAME) XSTR(PATH/HAL/STM32F1/NAME) #elif defined(ARDUINO_ARCH_STM32) @@ -54,6 +56,8 @@ #define HAL_PATH(PATH, NAME) XSTR(PATH/HAL/SAMD51/NAME) #elif defined(__SAMD21__) #define HAL_PATH(PATH, NAME) XSTR(PATH/HAL/SAMD21/NAME) +#elif defined(__PLAT_RP2040__) + #define HAL_PATH(PATH, NAME) XSTR(PATH/HAL/RP2040/NAME) #else #error "Unsupported Platform!" #endif diff --git a/Marlin/src/HAL/shared/Delay.cpp b/Marlin/src/HAL/shared/Delay.cpp index bfcf6bcc42ef..5aa3a235fdae 100644 --- a/Marlin/src/HAL/shared/Delay.cpp +++ b/Marlin/src/HAL/shared/Delay.cpp @@ -85,7 +85,7 @@ } else { // Enable DWT counter - // From https://stackoverflow.com/a/41188674/1469714 + // From https://stackoverflow.com/questions/36378280/stm32-how-to-enable-dwt-cycle-counter/41188674#41188674 HW_REG(_DEM_CR) = HW_REG(_DEM_CR) | 0x01000000; // Enable trace #if __CORTEX_M == 7 HW_REG(_LAR) = 0xC5ACCE55; // Unlock access to DWT registers, see https://developer.arm.com/documentation/ihi0029/e/ section B2.3.10 diff --git a/Marlin/src/HAL/shared/Delay.h b/Marlin/src/HAL/shared/Delay.h index a6795a78eaf4..eeaf4c59fcbe 100644 --- a/Marlin/src/HAL/shared/Delay.h +++ b/Marlin/src/HAL/shared/Delay.h @@ -100,7 +100,7 @@ void calibrate_delay_loop(); // For delay in microseconds, no smart delay selection is required, directly call the delay function // Teensy compiler is too old and does not accept smart delay compile-time / run-time selection correctly - #define DELAY_US(x) DelayCycleFnc((x) * ((F_CPU) / 1000000UL)) + #define DELAY_US(x) DelayCycleFnc((unsigned long)(x) * ((F_CPU) / 1000000UL)) #elif defined(__AVR__) FORCE_INLINE static void __delay_up_to_3c(uint8_t cycles) { @@ -164,7 +164,7 @@ void calibrate_delay_loop(); } // Delay in microseconds - #define DELAY_US(x) DELAY_CYCLES((x) * ((F_CPU) / 1000000UL)) + #define DELAY_US(x) DELAY_CYCLES((unsigned long)(x) * ((F_CPU) / 1000000UL)) #define DELAY_CYCLES_VAR DELAY_CYCLES @@ -173,7 +173,10 @@ void calibrate_delay_loop(); // DELAY_CYCLES specified inside platform // Delay in microseconds - #define DELAY_US(x) DELAY_CYCLES((x) * ((F_CPU) / 1000000UL)) + #define DELAY_US(x) DELAY_CYCLES((unsigned long)(x) * ((F_CPU) / 1000000UL)) + + #define DELAY_CYCLES_VAR DELAY_CYCLES + #else #error "Unsupported MCU architecture" diff --git a/Marlin/src/HAL/shared/backtrace/unwarm.h b/Marlin/src/HAL/shared/backtrace/unwarm.h index 72ea0b062767..8e432ebe77be 100644 --- a/Marlin/src/HAL/shared/backtrace/unwarm.h +++ b/Marlin/src/HAL/shared/backtrace/unwarm.h @@ -15,7 +15,7 @@ #include "unwinder.h" -/** The maximum number of instructions to interpet in a function. +/** The maximum number of instructions to interpret in a function. * Unwinding will be unconditionally stopped and UNWIND_EXHAUSTED returned * if more than this number of instructions are interpreted in a single * function without unwinding a stack frame. This prevents infinite loops diff --git a/Marlin/src/HAL/shared/backtrace/unwarm_arm.cpp b/Marlin/src/HAL/shared/backtrace/unwarm_arm.cpp index decf74e6e9c5..e6064f65dab9 100644 --- a/Marlin/src/HAL/shared/backtrace/unwarm_arm.cpp +++ b/Marlin/src/HAL/shared/backtrace/unwarm_arm.cpp @@ -414,7 +414,7 @@ UnwResult UnwStartArm(UnwState * const state) { /* S indicates that banked registers (untracked) are used, unless * this is a load including the PC when the S-bit indicates that - * that CPSR is loaded from SPSR (also untracked, but ignored). + * CPSR is loaded from SPSR (also untracked, but ignored). */ if (S && (!L || (regList & (0x01 << 15)) == 0)) { UnwPrintd1("\nError:S-bit set requiring banked registers\n"); @@ -431,7 +431,7 @@ UnwResult UnwStartArm(UnwState * const state) { /* Check if ascending or descending. * Registers are loaded/stored in order of address. - * i.e. r0 is at the lowest address, r15 at the highest. + * i.e., r0 is at the lowest address, r15 at the highest. */ r = U ? 0 : 15; do { diff --git a/Marlin/src/HAL/shared/cpu_exception/exception_arm.cpp b/Marlin/src/HAL/shared/cpu_exception/exception_arm.cpp index f4435c733e06..43a086589b6b 100644 --- a/Marlin/src/HAL/shared/cpu_exception/exception_arm.cpp +++ b/Marlin/src/HAL/shared/cpu_exception/exception_arm.cpp @@ -66,7 +66,7 @@ void * hook_get_usagefault_vector_address(unsigned vtor) { return (void*)(vtor + void * hook_get_reserved_vector_address(unsigned vtor) { return (void*)(vtor + 0x07); } // Common exception frame for ARM, should work for all ARM CPU -// Described here (modified for convenience): https://interrupt.memfault.com/blog/cortex-m-fault-debug +// Described here (modified for convenience): https://interrupt.memfault.com/blog/cortex-m-hardfault-debug struct __attribute__((packed)) ContextStateFrame { uint32_t r0; uint32_t r1; diff --git a/Marlin/src/HAL/shared/eeprom_if_i2c.cpp b/Marlin/src/HAL/shared/eeprom_if_i2c.cpp index bba9c626a4da..68dc7a2b6c19 100644 --- a/Marlin/src/HAL/shared/eeprom_if_i2c.cpp +++ b/Marlin/src/HAL/shared/eeprom_if_i2c.cpp @@ -40,11 +40,12 @@ #endif void eeprom_init() { - eWire.begin( - #if PINS_EXIST(I2C_SCL, I2C_SDA) && DISABLED(SOFT_I2C_EEPROM) - uint8_t(I2C_SDA_PIN), uint8_t(I2C_SCL_PIN) - #endif - ); + #if PINS_EXIST(I2C_SCL, I2C_SDA) && DISABLED(SOFT_I2C_EEPROM) + eWire.setSDA(I2C_SDA_PIN); + eWire.setSCL(I2C_SCL_PIN); + #endif + + eWire.begin(); } #if ENABLED(USE_SHARED_EEPROM) diff --git a/Marlin/src/HAL/shared/eeprom_if_spi.cpp b/Marlin/src/HAL/shared/eeprom_if_spi.cpp index 72c35addcb45..6aa23e823a50 100644 --- a/Marlin/src/HAL/shared/eeprom_if_spi.cpp +++ b/Marlin/src/HAL/shared/eeprom_if_spi.cpp @@ -81,4 +81,4 @@ void eeprom_write_byte(uint8_t *pos, uint8_t value) { } #endif // USE_SHARED_EEPROM -#endif // I2C_EEPROM +#endif // SPI_EEPROM diff --git a/Marlin/src/HAL/shared/math_32bit.h b/Marlin/src/HAL/shared/math_32bit.h index 1fb233e3e896..daf82e0650df 100644 --- a/Marlin/src/HAL/shared/math_32bit.h +++ b/Marlin/src/HAL/shared/math_32bit.h @@ -22,6 +22,7 @@ #pragma once #include "../../core/macros.h" +#include /** * Math helper functions for 32 bit CPUs diff --git a/Marlin/src/HAL/shared/progmem.h b/Marlin/src/HAL/shared/progmem.h index 4cd7663df9af..b3bd5c32fd75 100644 --- a/Marlin/src/HAL/shared/progmem.h +++ b/Marlin/src/HAL/shared/progmem.h @@ -39,7 +39,7 @@ #endif #ifndef F class __FlashStringHelper; -#define F(str) (reinterpret_cast(PSTR(str))) +#define F(string_literal) (reinterpret_cast(PSTR(string_literal))) #endif #ifndef _SFR_BYTE #define _SFR_BYTE(n) (n) diff --git a/Marlin/src/HAL/shared/serial_ports.h b/Marlin/src/HAL/shared/serial_ports.h new file mode 100644 index 000000000000..2daeea34e38d --- /dev/null +++ b/Marlin/src/HAL/shared/serial_ports.h @@ -0,0 +1,178 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2025 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 . + * + */ +#pragma once + +/** + * serial_ports.h - Define Marlin serial port macros and optionally declare ports + * + * This header defines one or more of the serial ports... + * - MYSERIAL1/2/3 ..... for host devices + * - MMU_SERIAL ........ for Multi-Material color changers + * - LCD_SERIAL ........ for serial LCDs + * - RS485_SERIAL ...... for CAN bus devices + * + * Before including this header define the following macros: + * - SERIAL_INDEX_MIN, SERIAL_INDEX_MAX to provide the valid range of serial port indexes. + * - _MSERIAL(X) and MSERIAL(X) to provide the instance name of Serial Port X. (Default: MSerial##X) + * - EP_SERIAL_PORT(X) to provide the instance name of Emergency Parser serial port X (if it is special). + * - USB_SERIAL_PORT(X) to provide the instance name of the USB serial port (if any). + * - ETH_SERIAL_PORT(X) to provide the instance name of the Ethernet serial port (if any). + * - DECLARE_SERIAL(X) to declare standard a serial port with the given index. + */ + +#ifndef _MSERIAL + #define _MSERIAL(X) MSerial##X +#endif +#ifndef MSERIAL + #define MSERIAL(X) _MSERIAL(X) +#endif + +#define INDEX_RANGE_MSG " must be from " STRINGIFY(SERIAL_INDEX_MIN) " to " STRINGIFY(SERIAL_INDEX_MAX) +#if defined(USB_SERIAL_PORT) && defined(ETH_SERIAL_PORT) + #define MORE_INDEXES_MSG ", -1 for Native USB, or -2 for Ethernet" +#elif defined(USB_SERIAL_PORT) + #define MORE_INDEXES_MSG ", or -1 for Native USB" +#elif defined(ETH_SERIAL_PORT) + #define MORE_INDEXES_MSG ", or -2 for Ethernet" +#else + #define MORE_INDEXES_MSG +#endif + +// +// SERIAL_PORT => MYSERIAL1 +// + +#if defined(EP_SERIAL_PORT) && ENABLED(EMERGENCY_PARSER) + #define MYSERIAL1 EP_SERIAL_PORT(1) +#elif WITHIN(SERIAL_PORT, SERIAL_INDEX_MIN, SERIAL_INDEX_MAX) + #define MYSERIAL1 MSERIAL(SERIAL_PORT) + #ifdef DECLARE_SERIAL + DECLARE_SERIAL(SERIAL_PORT); + #endif +#elif SERIAL_PORT == -1 && defined(USB_SERIAL_PORT) + #define MYSERIAL1 USB_SERIAL_PORT(1) +#elif SERIAL_PORT == -2 && defined(ETH_SERIAL_PORT) + #define MYSERIAL1 ETH_SERIAL_PORT(1) +#endif +#ifndef MYSERIAL1 + static_assert(false, "SERIAL_PORT" INDEX_RANGE_MSG MORE_INDEXES_MSG "."); + #define MYSERIAL1 _MSERIAL(1) // Dummy port +#endif + +// +// SERIAL_PORT_2 => MYSERIAL2 +// + +#ifdef SERIAL_PORT_2 + #if defined(EP_SERIAL_PORT) && ENABLED(EMERGENCY_PARSER) + #define MYSERIAL2 EP_SERIAL_PORT(2) + #elif WITHIN(SERIAL_PORT_2, SERIAL_INDEX_MIN, SERIAL_INDEX_MAX) + #define MYSERIAL2 MSERIAL(SERIAL_PORT_2) + #ifdef DECLARE_SERIAL + DECLARE_SERIAL(SERIAL_PORT_2); + #endif + #elif SERIAL_PORT_2 == -1 && defined(USB_SERIAL_PORT) + #define MYSERIAL2 USB_SERIAL_PORT(2) + #elif SERIAL_PORT_2 == -2 && defined(ETH_SERIAL_PORT) + #define MYSERIAL2 ETH_SERIAL_PORT(2) + #endif + #ifndef MYSERIAL2 + static_assert(false, "SERIAL_PORT_2" INDEX_RANGE_MSG MORE_INDEXES_MSG "."); + #define MYSERIAL2 _MSERIAL(1) // Dummy port + #endif +#endif + +// +// SERIAL_PORT_3 => MYSERIAL3 +// + +#ifdef SERIAL_PORT_3 + #if defined(EP_SERIAL_PORT) && ENABLED(EMERGENCY_PARSER) + #define MYSERIAL3 EP_SERIAL_PORT(3) + #elif WITHIN(SERIAL_PORT_3, SERIAL_INDEX_MIN, SERIAL_INDEX_MAX) + #define MYSERIAL3 MSERIAL(SERIAL_PORT_3) + #ifdef DECLARE_SERIAL + DECLARE_SERIAL(SERIAL_PORT_3); + #endif + #elif SERIAL_PORT_3 == -1 && defined(USB_SERIAL_PORT) + #define MYSERIAL3 USB_SERIAL_PORT(3) + #elif SERIAL_PORT_3 == -2 && defined(ETH_SERIAL_PORT) + #define MYSERIAL3 ETH_SERIAL_PORT(3) + #endif + #ifndef MYSERIAL3 + static_assert(false, "SERIAL_PORT_3" INDEX_RANGE_MSG MORE_INDEXES_MSG "."); + #define MYSERIAL3 _MSERIAL(1) // Dummy port + #endif +#endif + +// +// MMU_SERIAL_PORT => MMU_SERIAL +// + +#ifdef MMU_SERIAL_PORT + #if WITHIN(MMU_SERIAL_PORT, SERIAL_INDEX_MIN, SERIAL_INDEX_MAX) + #define MMU_SERIAL MSERIAL(MMU_SERIAL_PORT) + #ifdef DECLARE_SERIAL + DECLARE_SERIAL(MMU_SERIAL_PORT); + #endif + #else + static_assert(false, "MMU_SERIAL_PORT" INDEX_RANGE_MSG "."); + #define MMU_SERIAL _MSERIAL(1) // Dummy port + #endif +#endif + +// +// LCD_SERIAL_PORT => LCD_SERIAL +// + +#ifdef LCD_SERIAL_PORT + #if WITHIN(LCD_SERIAL_PORT, SERIAL_INDEX_MIN, SERIAL_INDEX_MAX) + #define LCD_SERIAL MSERIAL(LCD_SERIAL_PORT) + #ifdef DECLARE_SERIAL + DECLARE_SERIAL(LCD_SERIAL_PORT); + #endif + #else + static_assert(false, "LCD_SERIAL_PORT" INDEX_RANGE_MSG "."); + #define LCD_SERIAL _MSERIAL(1) // Dummy port + #endif +#endif + +// +// RS485_SERIAL_PORT => RS485_SERIAL +// + +#ifdef RS485_SERIAL_PORT + #if WITHIN(RS485_SERIAL_PORT, SERIAL_INDEX_MIN, SERIAL_INDEX_MAX) + #define RS485_SERIAL MSERIAL(RS485_SERIAL_PORT) + #ifdef DECLARE_SERIAL + DECLARE_SERIAL(RS485_SERIAL_PORT); + #endif + #else + static_assert(false, "RS485_SERIAL_PORT" INDEX_RANGE_MSG "."); + #define RS485_SERIAL _MSERIAL(1) // Dummy port + #endif +#endif + +#undef DECLARE_SERIAL +#undef SERIAL_INDEX_MIN +#undef SERIAL_INDEX_MAX +#undef INDEX_RANGE_MSG diff --git a/Marlin/src/HAL/shared/servo.h b/Marlin/src/HAL/shared/servo.h index 786c1e6a7231..85e645146c1e 100644 --- a/Marlin/src/HAL/shared/servo.h +++ b/Marlin/src/HAL/shared/servo.h @@ -59,7 +59,7 @@ * write() - Sets the servo angle in degrees. (invalid angle that is valid as pulse in microseconds is treated as microseconds) * writeMicroseconds() - Sets the servo pulse width in microseconds * read() - Gets the last written servo pulse width as an angle between 0 and 180. - * readMicroseconds() - Gets the last written servo pulse width in microseconds. (was read_us() in first release) + * readMicroseconds() - Gets the last written servo pulse width in microseconds. * attached() - Returns true if there is a servo attached. * detach() - Stops an attached servos from pulsing its i/o pin. * move(angle) - Sequence of attach(0), write(angle), @@ -76,19 +76,25 @@ #include "../LPC1768/Servo.h" #elif defined(ARDUINO_ARCH_HC32) #include "../HC32/Servo.h" +#elif defined(ARDUINO_ARCH_MFL) + #include "../GD32_MFL/Servo.h" #elif defined(__STM32F1__) || defined(TARGET_STM32F1) #include "../STM32F1/Servo.h" #elif defined(ARDUINO_ARCH_STM32) #include "../STM32/Servo.h" #elif defined(ARDUINO_ARCH_ESP32) #include "../ESP32/Servo.h" +#elif defined(__PLAT_RP2040__) + #include "../RP2040/Servo.h" +#elif defined(__PLAT_NATIVE_SIM__) + #include "../NATIVE_SIM/Servo.h" #else #include - #if defined(__AVR__) || defined(ARDUINO_ARCH_SAM) || defined(__SAMD51__) || defined(__SAMD21__) + #if defined(__AVR__) || defined(ARDUINO_ARCH_SAM) || defined(__SAMD51__) || defined(__SAMD21__) || defined(__PLAT_RP2040__) // we're good to go #else - #error "This library only supports boards with an AVR, SAM3X, SAMD21 or SAMD51 processor." + #error "This library only supports boards with an AVR, SAM3X, SAMD21, SAMD51, or RP2040 processor." #endif #define Servo_VERSION 2 // software version of this library @@ -96,22 +102,22 @@ class Servo { public: Servo(); - int8_t attach(const int pin); // attach the given pin to the next free channel, set pinMode, return channel number (-1 on fail) - int8_t attach(const int pin, const int min, const int max); // as above but also sets min and max values for writes. + int8_t attach(const int pin); // Attach the given pin to the next free channel, set pinMode, return channel number (-1 on fail) + int8_t attach(const int pin, const int min, const int max); // As above but also set min and max values for writes. void detach(); - void write(int value); // if value is < 200 it is treated as an angle, otherwise as pulse width in microseconds - void writeMicroseconds(int value); // write pulse width in microseconds - void move(const int value); // attach the servo, then move to value - // if value is < 200 it is treated as an angle, otherwise as pulse width in microseconds - // if DEACTIVATE_SERVOS_AFTER_MOVE wait SERVO_DELAY, then detach - int read(); // returns current pulse width as an angle between 0 and 180 degrees - int readMicroseconds(); // returns current pulse width in microseconds for this servo (was read_us() in first release) - bool attached(); // return true if this servo is attached, otherwise false + void write(int value); // If value is < 200 it is treated as an angle, otherwise as pulse width in microseconds + void writeMicroseconds(int value); // Write pulse width in microseconds + void move(const int value); // Attach the servo, then move to value + // If value is < 200 it is treated as an angle, otherwise as pulse width in microseconds + // If DEACTIVATE_SERVOS_AFTER_MOVE wait SERVO_DELAY, then detach + int read(); // Return current pulse width as an angle between 0 and 180 degrees + int readMicroseconds(); // Return current pulse width in microseconds for this servo + bool attached(); // Return true if this servo is attached, otherwise false private: - uint8_t servoIndex; // index into the channel data for this servo - int8_t min; // minimum is this value times 4 added to MIN_PULSE_WIDTH - int8_t max; // maximum is this value times 4 added to MAX_PULSE_WIDTH + uint8_t servoIndex; // Index into the channel data for this servo + int8_t min; // Minimum is this value times 4 added to MIN_PULSE_WIDTH + int8_t max; // Maximum is this value times 4 added to MAX_PULSE_WIDTH }; #endif diff --git a/Marlin/src/MarlinCore.cpp b/Marlin/src/MarlinCore.cpp index 53a74871efbb..00819e3148d4 100644 --- a/Marlin/src/MarlinCore.cpp +++ b/Marlin/src/MarlinCore.cpp @@ -74,13 +74,13 @@ #endif #if HAS_DWIN_E3V2 - #include "lcd/e3v2/common/encoder.h" + #include "lcd/dwin/common/encoder.h" #if ENABLED(DWIN_CREALITY_LCD) - #include "lcd/e3v2/creality/dwin.h" - #elif ENABLED(DWIN_LCD_PROUI) - #include "lcd/e3v2/proui/dwin.h" + #include "lcd/dwin/creality/dwin.h" #elif ENABLED(DWIN_CREALITY_LCD_JYERSUI) - #include "lcd/e3v2/jyersui/dwin.h" + #include "lcd/dwin/jyersui/dwin.h" + #elif ENABLED(SOVOL_SV06_RTS) + #include "lcd/sovol_rts/sovol_rts.h" #endif #endif @@ -152,23 +152,14 @@ #include "feature/encoder_i2c.h" #endif -#if (HAS_TRINAMIC_CONFIG || HAS_TMC_SPI) && DISABLED(PSU_DEFAULT_OFF) - #include "feature/tmc_util.h" +#if HAS_TRINAMIC_CONFIG + #include "module/stepper/trinamic.h" #endif #if HAS_CUTTER #include "feature/spindle_laser.h" #endif -#if HAS_MEDIA - CardReader card; -#endif - -#if ENABLED(G38_PROBE_TARGET) - uint8_t G38_move; // = 0 - bool G38_did_trigger; // = false -#endif - #if ENABLED(DELTA) #include "module/delta.h" #elif ENABLED(POLARGRAPH) @@ -231,12 +222,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/mmu3.h" + #include "feature/mmu3/mmu3_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) @@ -263,25 +256,59 @@ #include "tests/marlin_tests.h" #endif -PGMSTR(M112_KILL_STR, "M112 Shutdown"); +#if HAS_RS485_SERIAL + #include "feature/rs485.h" +#endif -MarlinState marlin_state = MF_INITIALIZING; +#if ENABLED(SOFT_FEED_HOLD) + #include "feature/e_parser.h" +#endif + +/** + * Spin in place here while keeping temperature processing alive + */ +void safe_delay(millis_t ms) { + while (ms > 50) { + ms -= 50; + delay(50); + thermalManager.task(); + } + delay(ms); + thermalManager.task(); // This keeps us safe if too many small safe_delay() calls are made +} + +// Singleton for Marlin global data and methods +Marlin marlin; + +// Marlin static data +#if ENABLED(CONFIGURABLE_MACHINE_NAME) + MString<64> Marlin::machine_name; +#endif + +// Global state of the firmware +MarlinState Marlin::state = MF_INITIALIZING; // For M109 and M190, this flag may be cleared (by M108) to exit the wait loop -bool wait_for_heatup = true; +bool Marlin::wait_for_heatup = false; + +#if !HAS_MEDIA + CardReader card; // Stub instance with "no media" methods +#endif + +PGMSTR(M112_KILL_STR, "M112 Shutdown"); // For M0/M1, this flag may be cleared (by M108) to exit the wait-for-user loop #if HAS_RESUME_CONTINUE - bool wait_for_user; // = false; + bool Marlin::wait_for_user; // = false - void wait_for_user_response(millis_t ms/*=0*/, const bool no_sleep/*=false*/) { + void Marlin::wait_for_user_response(millis_t ms/*=0*/, const bool no_sleep/*=false*/) { UNUSED(no_sleep); KEEPALIVE_STATE(PAUSED_FOR_USER); - wait_for_user = true; + wait_start(); if (ms) ms += millis(); // expire time while (wait_for_user && !(ms && ELAPSED(millis(), ms))) idle(TERN_(ADVANCED_PAUSE_FEATURE, no_sleep)); - wait_for_user = false; + user_resume(); while (ui.button_pressed()) safe_delay(50); } @@ -309,56 +336,47 @@ bool wait_for_heatup = true; #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wnarrowing" - -#ifndef RUNTIME_ONLY_ANALOG_TO_DIGITAL - template - constexpr pin_t OnlyPins<_SP_END, D...>::table[sizeof...(D)]; -#endif - -bool pin_is_protected(const pin_t pin) { - #ifdef RUNTIME_ONLY_ANALOG_TO_DIGITAL - static const pin_t sensitive_pins[] PROGMEM = { SENSITIVE_PINS }; - const size_t pincount = COUNT(sensitive_pins); - #else - static constexpr size_t pincount = OnlyPins::size; - static const pin_t (&sensitive_pins)[pincount] PROGMEM = OnlyPins::table; - #endif - for (uint8_t i = 0; i < pincount; ++i) { - const pin_t * const pptr = &sensitive_pins[i]; - if (pin == (sizeof(pin_t) == 2 ? (pin_t)pgm_read_word(pptr) : (pin_t)pgm_read_byte(pptr))) return true; - } +#pragma GCC diagnostic ignored "-Wsign-compare" + +bool Marlin::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_aio[i]))) return true; return false; } #pragma GCC diagnostic pop -bool printer_busy() { - return planner.movesplanned() || printingIsActive(); +bool Marlin::printer_busy() { + return planner.has_blocks_queued() || printingIsActive(); } /** * A Print Job exists when the timer is running or SD is printing */ -bool printJobOngoing() { return print_job_timer.isRunning() || IS_SD_PRINTING(); } +bool Marlin::printJobOngoing() { return print_job_timer.isRunning() || card.isStillPrinting(); } /** * Printing is active when a job is underway but not paused */ -bool printingIsActive() { return !did_pause_print && printJobOngoing(); } +bool Marlin::printingIsActive() { return !did_pause_print && printJobOngoing(); } /** * Printing is paused according to SD or host indicators */ -bool printingIsPaused() { - return did_pause_print || print_job_timer.isPaused() || IS_SD_PAUSED(); +bool Marlin::printingIsPaused() { + return did_pause_print || print_job_timer.isPaused() || card.isPaused(); } -void startOrResumeJob() { +void Marlin::startOrResumeJob() { if (!printingIsPaused()) { TERN_(GCODE_REPEAT_MARKERS, repeat.reset()); TERN_(CANCEL_OBJECTS, cancelable.reset()); - TERN_(LCD_SHOW_E_TOTAL, e_move_accumulator = 0); + TERN_(LCD_SHOW_E_TOTAL, motion.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(); } @@ -370,7 +388,7 @@ void startOrResumeJob() { card.abortFilePrintNow(TERN_(SD_RESORT, true)); queue.clear(); - quickstop_stepper(); + motion.quickstop_stepper(); print_job_timer.abort(); @@ -378,7 +396,7 @@ void startOrResumeJob() { TERN(HAS_CUTTER, cutter.kill(), thermalManager.zero_fan_speeds()); // Full cutter shutdown including ISR control - wait_for_heatup = false; + marlin.heatup_done(); TERN_(POWER_LOSS_RECOVERY, recovery.purge()); @@ -391,7 +409,7 @@ void startOrResumeJob() { inline void finishSDPrinting() { if (queue.enqueue_one(F("M1001"))) { // Keep trying until it gets queued - marlin_state = MF_RUNNING; // Signal to stop trying + marlin.setState(MF_RUNNING); // Signal to stop trying TERN_(PASSWORD_AFTER_SD_PRINT_END, password.lock_machine()); TERN_(DGUS_LCD_UI_MKS, screen.sdPrintingFinished()); } @@ -412,7 +430,7 @@ void startOrResumeJob() { * - Check if an idle but hot extruder needs filament extruded (EXTRUDER_RUNOUT_PREVENT) * - Pulse FET_SAFETY_PIN if it exists */ -inline void manage_inactivity(const bool no_stepper_sleep=false) { +void Marlin::manage_inactivity(const bool no_stepper_sleep/*=false*/) { queue.get_available_commands(); @@ -427,8 +445,7 @@ inline void manage_inactivity(const bool no_stepper_sleep=false) { if (gcode.stepper_max_timed_out(ms)) { SERIAL_ERROR_START(); - SERIAL_ECHOPGM(STR_KILL_PRE); - SERIAL_ECHOLNPGM(STR_KILL_INACTIVE_TIME, parser.command_ptr); + SERIAL_ECHOLN(F(STR_KILL_PRE), F(STR_KILL_INACTIVE_TIME), parser.command_ptr); kill(); } @@ -476,11 +493,16 @@ inline void manage_inactivity(const bool no_stepper_sleep=false) { #if HAS_KILL - // Check if the kill button was pressed and wait just in case it was an accidental - // key kill key press + // Check if the kill button was pressed and wait to ensure the signal is not noise + // typically caused by poor insulation and grounding on LCD cables. + // Lower numbers here will increase response time and therefore safety rating. + // It is recommended to set this as low as possible without false triggers. // ------------------------------------------------------------------------------- + #ifndef KILL_DELAY + #define KILL_DELAY 250 + #endif + static int killCount = 0; // make the inactivity button a bit less responsive - const int KILL_DELAY = 750; if (kill_state()) killCount++; else if (killCount > 0) @@ -491,21 +513,26 @@ inline void manage_inactivity(const bool no_stepper_sleep=false) { // ---------------------------------------------------------------- if (killCount >= KILL_DELAY) { SERIAL_ERROR_START(); - SERIAL_ECHOPGM(STR_KILL_PRE); - SERIAL_ECHOLNPGM(STR_KILL_BUTTON); + SERIAL_ECHOLN(F(STR_KILL_PRE), F(STR_KILL_BUTTON)); kill(); } #endif - #if ENABLED(FREEZE_FEATURE) - stepper.frozen = READ(FREEZE_PIN) == FREEZE_STATE; + // Handle the FREEZE button + #if ANY(FREEZE_FEATURE, SOFT_FEED_HOLD) + stepper.set_frozen_triggered( + TERN0(FREEZE_FEATURE, READ(FREEZE_PIN) == FREEZE_STATE) + #if ALL(SOFT_FEED_HOLD, REALTIME_REPORTING_COMMANDS) + || realtime_ramping_pause_flag + #endif + ); #endif #if HAS_HOME // Handle a standalone HOME button constexpr millis_t HOME_DEBOUNCE_DELAY = 1000UL; static millis_t next_home_key_ms; // = 0 - if (!IS_SD_PRINTING() && !READ(HOME_PIN)) { // HOME_PIN goes LOW when pressed + if (!card.isStillPrinting() && !READ(HOME_PIN)) { // HOME_PIN goes LOW when pressed if (ELAPSED(ms, next_home_key_ms)) { next_home_key_ms = ms + HOME_DEBOUNCE_DELAY; LCD_MESSAGE(MSG_AUTO_HOME); @@ -523,11 +550,15 @@ inline void manage_inactivity(const bool no_stepper_sleep=false) { constexpr millis_t CUB_DEBOUNCE_DELAY_##N = 250UL; \ static millis_t next_cub_ms_##N; \ if (BUTTON##N##_HIT_STATE == READ(BUTTON##N##_PIN) \ - && (ENABLED(BUTTON##N##_WHEN_PRINTING) || printer_not_busy)) { \ + && (ENABLED(BUTTON##N##_WHEN_PRINTING) || printer_not_busy) \ + ) { \ if (ELAPSED(ms, next_cub_ms_##N)) { \ next_cub_ms_##N = ms + CUB_DEBOUNCE_DELAY_##N; \ CODE; \ - queue.inject(F(BUTTON##N##_GCODE)); \ + if (ENABLED(BUTTON##N##_IMMEDIATE)) \ + gcode.process_subcommands_now(F(BUTTON##N##_GCODE)); \ + else \ + queue.inject(F(BUTTON##N##_GCODE)); \ TERN_(HAS_MARLINUI_MENU, ui.quick_feedback()); \ } \ } \ @@ -671,20 +702,26 @@ inline void manage_inactivity(const bool no_stepper_sleep=false) { TERN_(HOTEND_IDLE_TIMEOUT, hotend_idle.check()); + #if ANY(PSU_CONTROL, AUTO_POWER_CONTROL) && PIN_EXISTS(PS_ON_EDM) + if ( ELAPSED(ms, powerManager.last_state_change_ms, PS_EDM_RESPONSE) + && (READ(PS_ON_PIN) != READ(PS_ON_EDM_PIN) || TERN0(PSU_OFF_REDUNDANT, extDigitalRead(PS_ON1_PIN) != extDigitalRead(PS_ON1_EDM_PIN))) + ) kill(GET_TEXT_F(MSG_POWER_EDM_FAULT)); + #endif + #if ENABLED(EXTRUDER_RUNOUT_PREVENT) - if (thermalManager.degHotend(active_extruder) > (EXTRUDER_RUNOUT_MINTEMP) - && ELAPSED(ms, gcode.previous_move_ms + SEC_TO_MS(EXTRUDER_RUNOUT_SECONDS)) + if (thermalManager.degHotend(motion.extruder) > (EXTRUDER_RUNOUT_MINTEMP) + && ELAPSED(ms, gcode.previous_move_ms, SEC_TO_MS(EXTRUDER_RUNOUT_SECONDS)) && !planner.has_blocks_queued() ) { - const int8_t e_stepper = TERN(HAS_SWITCHING_EXTRUDER, active_extruder >> 1, active_extruder); + const int8_t e_stepper = TERN(HAS_SWITCHING_EXTRUDER, motion.extruder / 2, motion.extruder); const bool e_off = !stepper.AXIS_IS_ENABLED(E_AXIS, e_stepper); if (e_off) stepper.ENABLE_EXTRUDER(e_stepper); - const float olde = current_position.e; - current_position.e += EXTRUDER_RUNOUT_EXTRUDE; - line_to_current_position(MMM_TO_MMS(EXTRUDER_RUNOUT_SPEED)); - current_position.e = olde; - planner.set_e_position_mm(olde); + const float olde = motion.position.e; + motion.position.e += EXTRUDER_RUNOUT_EXTRUDE; + motion.goto_current_position(MMM_TO_MMS(EXTRUDER_RUNOUT_SPEED)); + motion.position.e = olde; + motion.sync_plan_position_e(); planner.synchronize(); if (e_off) stepper.DISABLE_EXTRUDER(e_stepper); @@ -695,11 +732,11 @@ inline void manage_inactivity(const bool no_stepper_sleep=false) { #if ENABLED(DUAL_X_CARRIAGE) // handle delayed move timeout - if (delayed_move_time && ELAPSED(ms, delayed_move_time) && IsRunning()) { + if (motion.delayed_move_time && ELAPSED(ms, motion.delayed_move_time) && isRunning()) { // travel moves have been received so enact them - delayed_move_time = 0xFFFFFFFFUL; // force moves to be done - destination = current_position; - prepare_line_to_destination(); + motion.delayed_move_time = UINT32_MAX; // force moves to be done + motion.destination = motion.position; + motion.prepare_line_to_destination(); planner.synchronize(); } #endif @@ -724,7 +761,8 @@ inline void manage_inactivity(const bool no_stepper_sleep=false) { WRITE(FET_SAFETY_PIN, FET_SAFETY_INVERTED); } #endif -} + +} // Marlin::manage_inactivity() #if ALL(EP_BABYSTEPPING, EMERGENCY_PARSER) #include "feature/babystep.h" @@ -752,14 +790,14 @@ inline void manage_inactivity(const bool no_stepper_sleep=false) { * - Update the PrÅÆÅ”a MMU2 * - Handle Joystick jogging */ -void idle(const bool no_stepper_sleep/*=false*/) { +void Marlin::idle(const bool no_stepper_sleep/*=false*/) { #ifdef MAX7219_DEBUG_PROFILE CodeProfiler idle_profiler; #endif #if ENABLED(MARLIN_DEV_MODE) static uint16_t idle_depth = 0; - if (++idle_depth > 5) SERIAL_ECHOLNPGM("idle() call depth: ", idle_depth); + if (++idle_depth > 5) SERIAL_ECHOLNPGM("Marlin::idle() call depth: ", idle_depth); #endif // Bed Distance Sensor task @@ -775,14 +813,14 @@ void idle(const bool no_stepper_sleep/*=false*/) { TERN_(MAX7219_DEBUG, max7219.idle_tasks()); // Return if setup() isn't completed - if (marlin_state == MF_INITIALIZING) goto IDLE_DONE; + if (is(MF_INITIALIZING)) goto IDLE_DONE; // TODO: Still causing errors - TERN_(TOOL_SENSOR, (void)check_tool_sensor_stats(active_extruder, true)); + TERN_(TOOL_SENSOR, (void)check_tool_sensor_stats(motion.extruder, true)); // 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 @@ -794,7 +832,7 @@ void idle(const bool no_stepper_sleep/*=false*/) { // Handle Power-Loss Recovery #if ENABLED(POWER_LOSS_RECOVERY) && PIN_EXISTS(POWER_LOSS) - if (IS_SD_PRINTING()) recovery.outage(); + if (card.isStillPrinting()) recovery.outage(); #endif // Run StallGuard endstop checks @@ -806,9 +844,6 @@ void idle(const bool no_stepper_sleep/*=false*/) { // Handle SD Card insert / remove TERN_(HAS_MEDIA, card.manage_media()); - // Handle USB Flash Drive insert / remove - TERN_(USB_FLASH_DRIVE_SUPPORT, card.diskIODriver()->idle()); - // Announce Host Keepalive state (if any) TERN_(HOST_KEEPALIVE_FEATURE, gcode.host_keepalive()); @@ -818,8 +853,23 @@ void idle(const bool no_stepper_sleep/*=false*/) { // Update the Beeper queue TERN_(HAS_BEEPER, buzzer.tick()); + // Async Babystepping via the Emergency Parser + #if ALL(EP_BABYSTEPPING, EMERGENCY_PARSER) + babystep.do_ep_steps(); + #endif + + // Direct Stepping + TERN_(DIRECT_STEPPING, page_manager.write_responses()); + + // Manage Fixed-time Motion Control + TERN_(FT_MOTION, ftMotion.loop()); + // Handle UI input / draw events - ui.update(); + #if ENABLED(SOVOL_SV06_RTS) + RTS_Update(); + #else + ui.update(); + #endif // Run i2c Position Encoders #if ENABLED(I2C_POSITION_ENCODERS) @@ -841,42 +891,36 @@ void idle(const bool no_stepper_sleep/*=false*/) { TERN_(AUTO_REPORT_TEMPERATURES, thermalManager.auto_reporter.tick()); TERN_(AUTO_REPORT_FANS, fan_check.auto_reporter.tick()); TERN_(AUTO_REPORT_SD_STATUS, card.auto_reporter.tick()); - TERN_(AUTO_REPORT_POSITION, position_auto_reporter.tick()); + TERN_(AUTO_REPORT_POSITION, motion.position_auto_reporter.tick()); TERN_(BUFFER_MONITORING, queue.auto_report_buffer_statistics()); } #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()); - // Async Babystepping via the Emergency Parser - #if ALL(EP_BABYSTEPPING, EMERGENCY_PARSER) - babystep.do_ep_steps(); - #endif - - // Direct Stepping - TERN_(DIRECT_STEPPING, page_manager.write_responses()); - // Update the LVGL interface TERN_(HAS_TFT_LVGL_UI, LV_TASK_HANDLER()); - // Manage Fixed-time Motion Control - TERN_(FT_MOTION, ftMotion.loop()); - IDLE_DONE: TERN_(MARLIN_DEV_MODE, idle_depth--); return; -} + +} // Marlin::idle() /** * Kill all activity and lock the machine. * After this the machine will need to be reset. */ -void kill(FSTR_P const lcd_error/*=nullptr*/, FSTR_P const lcd_component/*=nullptr*/, const bool steppers_off/*=false*/) { +void Marlin::kill(FSTR_P const lcd_error/*=nullptr*/, FSTR_P const lcd_component/*=nullptr*/, const bool steppers_off/*=false*/) { thermalManager.disable_all_heaters(); TERN_(HAS_CUTTER, cutter.kill()); // Full cutter shutdown including ISR control @@ -902,7 +946,7 @@ void kill(FSTR_P const lcd_error/*=nullptr*/, FSTR_P const lcd_component/*=nullp minkill(steppers_off); } -void minkill(const bool steppers_off/*=false*/) { +void Marlin::minkill(const bool steppers_off/*=false*/) { // Wait a short time (allows messages to get out before shutting down. for (int i = 1000; i--;) DELAY_US(600); @@ -942,13 +986,14 @@ void minkill(const bool steppers_off/*=false*/) { for (;;) hal.watchdog_refresh(); // Wait for RESET button or power-cycle #endif -} + +} // Marlin::minkill /** * Turn off heaters and stop the print in progress * After a stop the machine may be resumed with M999 */ -void stop() { +void Marlin::stop() { thermalManager.disable_all_heaters(); // 'unpause' taken care of in here print_job_timer.stop(); @@ -957,13 +1002,13 @@ void stop() { thermalManager.set_fans_paused(false); // Un-pause fans for safety #endif - if (!IsStopped()) { + if (!isStopped()) { SERIAL_ERROR_MSG(STR_ERR_STOPPED); LCD_MESSAGE(MSG_STOPPED); - safe_delay(350); // allow enough time for messages to get out before stopping - marlin_state = MF_STOPPED; + safe_delay(350); // Allow enough time for messages to get out before stopping + setState(MF_STOPPED); } -} +} // Marlin::stop() inline void tmc_standby_setup() { #if PIN_EXISTS(X_STDBY) @@ -1032,7 +1077,7 @@ inline void tmc_standby_setup() { #if PIN_EXISTS(E7_STDBY) SET_INPUT_PULLDOWN(E7_STDBY_PIN); #endif -} +} // tmc_standby_setup() /** * Marlin Firmware entry-point. Abandon Hope All Ye Who Enter Here. @@ -1120,6 +1165,7 @@ inline void tmc_standby_setup() { * - Apply Password Lock - Hold for Authentication * - Open Touch Screen Calibration screen, if not calibrated * - Set Marlin to RUNNING State + * - Stop print timer */ void setup() { #ifdef FASTIO_INIT @@ -1152,6 +1198,12 @@ void setup() { millis_t serial_connect_timeout = millis() + 1000UL; while (!MYSERIAL1.connected() && PENDING(millis(), serial_connect_timeout)) { /*nada*/ } + #if ENABLED(SOVOL_SV06_RTS) + LCD_SERIAL.begin(BAUDRATE); + serial_connect_timeout = millis() + 1000UL; + while (!LCD_SERIAL.connected() && PENDING(millis(), serial_connect_timeout)) { /*nada*/ } + #endif + #if HAS_MULTI_SERIAL && !HAS_ETHERNET #ifndef BAUDRATE_2 #define BAUDRATE_2 BAUDRATE @@ -1180,7 +1232,7 @@ void setup() { #endif #endif - #if ENABLED(FREEZE_FEATURE) + #if ENABLED(FREEZE_FEATURE) && DISABLED(NO_FREEZE_PIN) SETUP_LOG("FREEZE_PIN"); #if FREEZE_STATE SET_INPUT_PULLDOWN(FREEZE_PIN); @@ -1236,7 +1288,7 @@ void setup() { SETUP_RUN(runout.setup()); #endif - #if HAS_TMC220x + #if HAS_TMC_UART SETUP_RUN(tmc_serial_begin()); #endif @@ -1275,7 +1327,7 @@ void setup() { // Identify myself as Marlin x.x.x SERIAL_ECHOLNPGM("Marlin " SHORT_BUILD_VERSION); - #if defined(STRING_DISTRIBUTION_DATE) && defined(STRING_CONFIG_H_AUTHOR) + #ifdef STRING_DISTRIBUTION_DATE SERIAL_ECHO_MSG( " Last Updated: " STRING_DISTRIBUTION_DATE " | Author: " STRING_CONFIG_H_AUTHOR @@ -1309,8 +1361,11 @@ void setup() { // UI must be initialized before EEPROM // (because EEPROM code calls the UI). - - SETUP_RUN(ui.init()); + #if ENABLED(SOVOL_SV06_RTS) + SETUP_RUN(RTS_Update()); + #else + SETUP_RUN(ui.init()); + #endif #if PIN_EXISTS(SAFE_POWER) #if HAS_DRIVER_SAFE_POWER_PROTECT @@ -1321,18 +1376,36 @@ void setup() { #endif #endif - #if ALL(HAS_MEDIA, SDCARD_EEPROM_EMULATION) - SETUP_RUN(card.mount()); // Mount media with settings before first_load + #if HAS_MEDIA + SETUP_RUN(card.init()); // Prepare for media usage + #if ANY(SDCARD_EEPROM_EMULATION, POWER_LOSS_RECOVERY) + SETUP_RUN(card.mount()); // Mount media with settings before first_load + #endif #endif - SETUP_RUN(settings.first_load()); // Load data from EEPROM if available (or use defaults) - // This also updates variables in the planner, elsewhere + //#if ENABLED(PRINTJOB_TIMER_AUTOSTART) + // // Stop timer and set welcome message + // if (TERN1(POWER_LOSS_RECOVERY, !recovery.check())) + // thermalManager.auto_job_check_timer(false, true); + //#endif + + // Prepare some LCDs to display early + #if HAS_EARLY_LCD_SETTINGS + SETUP_RUN(settings.load_lcd_state()); + #endif #if ALL(HAS_WIRED_LCD, SHOW_BOOTSCREEN) SETUP_RUN(ui.show_bootscreen()); const millis_t bootscreen_ms = millis(); #endif + SETUP_RUN(settings.first_load()); // Load data from EEPROM if available (or use defaults) + // This also updates variables in the planner, elsewhere + + #if ENABLED(CONFIGURABLE_MACHINE_NAME) + SETUP_RUN(ui.reset_status(false)); // machine_name Initialized by settings.load() + #endif + #if ENABLED(PROBE_TARE) SETUP_RUN(probe.tare_init()); #endif @@ -1345,9 +1418,9 @@ void setup() { SETUP_RUN(touchBt.init()); #endif - TERN_(HAS_HOME_OFFSET, current_position += home_offset); // Init current position based on home_offset + TERN_(HAS_HOME_OFFSET, motion.position += motion.home_offset); // Init current position based on home_offset - sync_plan_position(); // Vital to init stepper/planner equivalent for current_position + motion.sync_plan_position(); // Vital to init stepper/planner equivalent for motion.position SETUP_RUN(thermalManager.init()); // Initialize temperature loop @@ -1578,7 +1651,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 @@ -1588,11 +1665,13 @@ void setup() { SERIAL_ECHO_TERNARY(err, "BL24CXX Check ", "failed", "succeeded", "!\n"); #endif - #if HAS_DWIN_E3V2_BASIC + #if ENABLED(DWIN_CREALITY_LCD) SETUP_RUN(dwinInitScreen()); + #elif ENABLED(SOVOL_SV06_RTS) + SETUP_RUN(rts.init()); #endif - #if HAS_SERVICE_INTERVALS && !HAS_DWIN_E3V2_BASIC + #if HAS_SERVICE_INTERVALS && DISABLED(DWIN_CREALITY_LCD) SETUP_RUN(ui.reset_status(true)); // Show service messages or keep current status #endif @@ -1639,11 +1718,15 @@ void setup() { SETUP_RUN(bdl.init(I2C_BD_SDA_PIN, I2C_BD_SCL_PIN, I2C_BD_DELAY)); #endif + #if HAS_RS485_SERIAL + SETUP_RUN(rs485_init()); + #endif + #if ENABLED(FT_MOTION) SETUP_RUN(ftMotion.init()); #endif - marlin_state = MF_RUNNING; + marlin.setState(MF_RUNNING); #ifdef STARTUP_TUNE // Play a short startup tune before continuing. @@ -1654,12 +1737,12 @@ void setup() { SETUP_LOG("setup() completed."); TERN_(MARLIN_TEST_BUILD, runStartupTests()); -} +} // setup() /** * The main Marlin program loop * - * - Call idle() to handle all tasks between G-code commands + * - Call marlin.idle() to handle all tasks between G-code commands * Note that no G-codes from the queue can be executed during idle() * but many G-codes can be called directly anytime like macros. * - Check whether SD card auto-start is needed now. @@ -1671,11 +1754,11 @@ void setup() { */ void loop() { do { - idle(); + marlin.idle(); #if HAS_MEDIA if (card.flag.abort_sd_printing) abortSDPrinting(); - if (marlin_state == MF_SD_COMPLETE) finishSDPrinting(); + if (marlin.is(MF_SD_COMPLETE)) finishSDPrinting(); #endif queue.advance(); diff --git a/Marlin/src/MarlinCore.h b/Marlin/src/MarlinCore.h index e9c63bb31f35..35482983ecd6 100644 --- a/Marlin/src/MarlinCore.h +++ b/Marlin/src/MarlinCore.h @@ -27,20 +27,6 @@ #include #include -void stop(); - -// Pass true to keep steppers from timing out -void idle(const bool no_stepper_sleep=false); -inline void idle_no_sleep() { idle(true); } - -#if ENABLED(G38_PROBE_TARGET) - extern uint8_t G38_move; // Flag to tell the ISR that G38 is in progress, and the type - extern bool G38_did_trigger; // Flag from the ISR to indicate the endstop changed -#endif - -void kill(FSTR_P const lcd_error=nullptr, FSTR_P const lcd_component=nullptr, const bool steppers_off=false); -void minkill(const bool steppers_off=false); - // Global State of the firmware enum MarlinState : uint8_t { MF_INITIALIZING = 0, @@ -52,35 +38,81 @@ enum MarlinState : uint8_t { MF_WAITING, }; -extern MarlinState marlin_state; -inline bool IsRunning() { return marlin_state >= MF_RUNNING; } -inline bool IsStopped() { return marlin_state == MF_STOPPED; } +typedef bool (*testFunc_t)(); + +// Delay ensuring that temperatures are updated and the watchdog is kept alive +void safe_delay(millis_t ms); + +// Singleton for Marlin global data and methods + +class Marlin { +public: + #if ENABLED(CONFIGURABLE_MACHINE_NAME) + static MString<64> machine_name; + #endif + + static MarlinState state; + static void setState(const MarlinState s) { state = s; } + static bool is(const MarlinState s) { return state == s; } + static bool isStopped() { return is(MF_STOPPED); } + static bool isRunning() { return state >= MF_RUNNING; } -bool printingIsActive(); -bool printJobOngoing(); -bool printingIsPaused(); -void startOrResumeJob(); + static bool printingIsActive(); + static bool printJobOngoing(); + static bool printingIsPaused(); + static void startOrResumeJob(); -bool printer_busy(); + static bool printer_busy(); -extern bool wait_for_heatup; + static void stop(); -#if HAS_RESUME_CONTINUE - extern bool wait_for_user; - void wait_for_user_response(millis_t ms=0, const bool no_sleep=false); -#endif + // Maintain all important activities + static void manage_inactivity(const bool no_stepper_sleep=false); -bool pin_is_protected(const pin_t pin); + // Pass true to keep steppers from timing out + static void idle(const bool no_stepper_sleep=false); + static void idle_no_sleep() { idle(true); } -#if HAS_SUICIDE - inline void suicide() { OUT_WRITE(SUICIDE_PIN, SUICIDE_PIN_STATE); } -#endif + static void kill(FSTR_P const lcd_error=nullptr, FSTR_P const lcd_component=nullptr, const bool steppers_off=false); + static void minkill(const bool steppers_off=false); -#if HAS_KILL - #ifndef KILL_PIN_STATE - #define KILL_PIN_STATE LOW + #if HAS_RESUME_CONTINUE + // Global waiting for user response + static bool wait_for_user; + static void wait_start() { wait_for_user = true; } + static void user_resume() { wait_for_user = false; } + static void wait_for_user_response(millis_t ms=0, const bool no_sleep=false); #endif - inline bool kill_state() { return READ(KILL_PIN) == KILL_PIN_STATE; } -#endif + + // Global waiting for heatup state + static bool wait_for_heatup; + static bool is_heating() { return wait_for_heatup; } + static void heatup_start() { wait_for_heatup = true; } + static void heatup_done() { wait_for_heatup = false; } + static void end_waiting() { TERN_(HAS_RESUME_CONTINUE, wait_for_user =) wait_for_heatup = false; } + + // Shared function for M42 / M43 + static bool pin_is_protected(const pin_t pin); + + #if HAS_SUICIDE + static void suicide() { OUT_WRITE(SUICIDE_PIN, SUICIDE_PIN_STATE); } + #endif + + static bool kill_state() { + return ( + #if HAS_KILL + #ifndef KILL_PIN_STATE + #define KILL_PIN_STATE LOW + #endif + READ(KILL_PIN) == KILL_PIN_STATE + #else + false + #endif + ); + } + +}; + +extern Marlin marlin; extern const char M112_KILL_STR[]; diff --git a/Marlin/src/core/boards.h b/Marlin/src/core/boards.h index 1dfcabdefb86..718bc8f7d53d 100644 --- a/Marlin/src/core/boards.h +++ b/Marlin/src/core/boards.h @@ -28,6 +28,7 @@ #include "macros.h" +#define BOARD_ERROR -2 #define BOARD_UNKNOWN -1 // @@ -54,11 +55,11 @@ #define BOARD_RAMPS_PLUS_EEF 1033 // RAMPS Plus 3DYMY (Power outputs: Hotend0, Hotend1, Fan) #define BOARD_RAMPS_PLUS_SF 1034 // RAMPS Plus 3DYMY (Power outputs: Spindle, Controller Fan) -#define BOARD_RAMPS_BTT_16_PLUS_EFB 1035 // RAMPS 1.6+ (Power outputs: Hotend, Fan, Bed) -#define BOARD_RAMPS_BTT_16_PLUS_EEB 1036 // RAMPS 1.6+ (Power outputs: Hotend0, Hotend1, Bed) -#define BOARD_RAMPS_BTT_16_PLUS_EFF 1037 // RAMPS 1.6+ (Power outputs: Hotend, Fan0, Fan1) -#define BOARD_RAMPS_BTT_16_PLUS_EEF 1038 // RAMPS 1.6+ (Power outputs: Hotend0, Hotend1, Fan) -#define BOARD_RAMPS_BTT_16_PLUS_SF 1039 // RAMPS 1.6+ (Power outputs: Spindle, Controller Fan) +#define BOARD_RAMPS_BTT_16_PLUS_EFB 1040 // RAMPS 1.6+ (Power outputs: Hotend, Fan, Bed) +#define BOARD_RAMPS_BTT_16_PLUS_EEB 1041 // RAMPS 1.6+ (Power outputs: Hotend0, Hotend1, Bed) +#define BOARD_RAMPS_BTT_16_PLUS_EFF 1042 // RAMPS 1.6+ (Power outputs: Hotend, Fan0, Fan1) +#define BOARD_RAMPS_BTT_16_PLUS_EEF 1043 // RAMPS 1.6+ (Power outputs: Hotend0, Hotend1, Fan) +#define BOARD_RAMPS_BTT_16_PLUS_SF 1044 // RAMPS 1.6+ (Power outputs: Spindle, Controller Fan) // // RAMPS Derivatives - ATmega1280, ATmega2560 @@ -79,42 +80,42 @@ #define BOARD_MKS_GEN_13 1112 // MKS GEN v1.3 or 1.4 #define BOARD_MKS_GEN_L 1113 // MKS GEN L #define BOARD_KFB_2 1114 // BigTreeTech or BIQU KFB2.0 -#define BOARD_ZRIB_V20 1115 // zrib V2.0 (Chinese RAMPS replica) -#define BOARD_ZRIB_V52 1116 // zrib V5.2 (Chinese RAMPS replica) -#define BOARD_FELIX2 1117 // Felix 2.0+ Electronics Board (RAMPS like) -#define BOARD_RIGIDBOARD 1118 // Invent-A-Part RigidBoard -#define BOARD_RIGIDBOARD_V2 1119 // Invent-A-Part RigidBoard V2 -#define BOARD_SAINSMART_2IN1 1120 // Sainsmart 2-in-1 board -#define BOARD_ULTIMAKER 1121 // Ultimaker -#define BOARD_ULTIMAKER_OLD 1122 // Ultimaker (Older electronics. Pre 1.5.4. This is rare) -#define BOARD_AZTEEG_X3 1123 // Azteeg X3 -#define BOARD_AZTEEG_X3_PRO 1124 // Azteeg X3 Pro -#define BOARD_ULTIMAIN_2 1125 // Ultimainboard 2.x (Uses TEMP_SENSOR 20) -#define BOARD_RUMBA 1126 // Rumba -#define BOARD_RUMBA_RAISE3D 1127 // Raise3D N series Rumba derivative -#define BOARD_RL200 1128 // Rapide Lite 200 (v1, low-cost RUMBA clone with drv) -#define BOARD_FORMBOT_TREX2PLUS 1129 // Formbot T-Rex 2 Plus -#define BOARD_FORMBOT_TREX3 1130 // Formbot T-Rex 3 -#define BOARD_FORMBOT_RAPTOR 1131 // Formbot Raptor -#define BOARD_FORMBOT_RAPTOR2 1132 // Formbot Raptor 2 -#define BOARD_BQ_ZUM_MEGA_3D 1133 // bq ZUM Mega 3D -#define BOARD_MAKEBOARD_MINI 1134 // MakeBoard Mini v2.1.2 by MicroMake -#define BOARD_TRIGORILLA_13 1135 // TriGorilla Anycubic version 1.3-based on RAMPS EFB -#define BOARD_TRIGORILLA_14 1136 // ... Ver 1.4 -#define BOARD_TRIGORILLA_14_11 1137 // ... Rev 1.1 (new servo pin order) -#define BOARD_RAMPS_ENDER_4 1138 // Creality: Ender-4, CR-8 -#define BOARD_RAMPS_CREALITY 1139 // Creality: CR10S, CR20, CR-X -#define BOARD_DAGOMA_F5 1140 // Dagoma F5 -#define BOARD_FYSETC_F6_13 1141 // FYSETC F6 1.3 -#define BOARD_FYSETC_F6_14 1142 // FYSETC F6 1.4 -#define BOARD_DUPLICATOR_I3_PLUS 1143 // Wanhao Duplicator i3 Plus -#define BOARD_VORON 1144 // VORON Design -#define BOARD_TRONXY_V3_1_0 1145 // Tronxy TRONXY-V3-1.0 -#define BOARD_Z_BOLT_X_SERIES 1146 // Z-Bolt X Series -#define BOARD_TT_OSCAR 1147 // TT OSCAR -#define BOARD_OVERLORD 1148 // Overlord/Overlord Pro -#define BOARD_HJC2560C_REV1 1149 // ADIMLab Gantry v1 -#define BOARD_HJC2560C_REV2 1150 // ADIMLab Gantry v2 +#define BOARD_ZRIB_V20 1115 // Zonestar zrib V2.0 (Chinese RAMPS replica) +#define BOARD_ZRIB_V52 1116 // Zonestar zrib V5.2 (Chinese RAMPS replica) +#define BOARD_ZRIB_V53 1117 // Zonestar zrib V5.3 (Chinese RAMPS replica) +#define BOARD_FELIX2 1118 // Felix 2.0+ Electronics Board (RAMPS like) +#define BOARD_RIGIDBOARD 1119 // Invent-A-Part RigidBoard +#define BOARD_RIGIDBOARD_V2 1120 // Invent-A-Part RigidBoard V2 +#define BOARD_SAINSMART_2IN1 1121 // Sainsmart 2-in-1 board +#define BOARD_ULTIMAKER 1122 // Ultimaker +#define BOARD_ULTIMAKER_OLD 1123 // Ultimaker (Older electronics. Pre 1.5.4. This is rare) +#define BOARD_AZTEEG_X3 1124 // Azteeg X3 +#define BOARD_AZTEEG_X3_PRO 1125 // Azteeg X3 Pro +#define BOARD_ULTIMAIN_2 1126 // Ultimainboard 2.x (Uses TEMP_SENSOR 20) +#define BOARD_RUMBA 1127 // Rumba +#define BOARD_RUMBA_RAISE3D 1128 // Raise3D N series Rumba derivative +#define BOARD_RL200 1129 // Rapide Lite 200 (v1, low-cost RUMBA clone with drv) +#define BOARD_FORMBOT_TREX2PLUS 1130 // Formbot T-Rex 2 Plus +#define BOARD_FORMBOT_TREX3 1131 // Formbot T-Rex 3 +#define BOARD_FORMBOT_RAPTOR 1132 // Formbot Raptor +#define BOARD_FORMBOT_RAPTOR2 1133 // Formbot Raptor 2 +#define BOARD_BQ_ZUM_MEGA_3D 1134 // bq ZUM Mega 3D +#define BOARD_MAKEBOARD_MINI 1135 // MakeBoard Mini v2.1.2 by MicroMake +#define BOARD_TRIGORILLA_13 1136 // TriGorilla Anycubic version 1.3-based on RAMPS EFB +#define BOARD_TRIGORILLA_14 1137 // ... Ver 1.4 +#define BOARD_TRIGORILLA_14_11 1138 // ... Rev 1.1 (new servo pin order) +#define BOARD_RAMPS_ENDER_4 1139 // Creality: Ender-4, CR-8 +#define BOARD_RAMPS_CREALITY 1140 // Creality: CR10S, CR20, CR-X +#define BOARD_CREALITY_V252 1141 // Creality CR-10 V2, CR-10 V3 +#define BOARD_DAGOMA_F5 1142 // Dagoma F5 +#define BOARD_DAGOMA_D6 1143 // Dagoma D6 (as found in the Dagoma DiscoUltimate V2 TMC) +#define BOARD_FYSETC_F6_13 1144 // FYSETC F6 1.3 +#define BOARD_FYSETC_F6_14 1145 // FYSETC F6 1.4 +#define BOARD_DUPLICATOR_I3_PLUS 1146 // Wanhao Duplicator i3 Plus +#define BOARD_VORON 1147 // VORON Design +#define BOARD_TRONXY_V3_1_0 1148 // Tronxy TRONXY-V3-1.0 +#define BOARD_Z_BOLT_X_SERIES 1149 // Z-Bolt X Series +#define BOARD_TT_OSCAR 1150 // TT OSCAR #define BOARD_TANGO 1151 // BIQU Tango V1 #define BOARD_MKS_GEN_L_V2 1152 // MKS GEN L V2 #define BOARD_MKS_GEN_L_V21 1153 // MKS GEN L V2.1 @@ -127,10 +128,12 @@ #define BOARD_RAMPS_S_12_EFFB 1160 // Ramps S 1.2 by Sakul.cz (Power outputs: Hotend, Fan0, Fan1, Bed) #define BOARD_LONGER3D_LK1_PRO 1161 // Longer LK1 PRO / Alfawise U20 Pro (PRO version) #define BOARD_LONGER3D_LKx_PRO 1162 // Longer LKx PRO / Alfawise Uxx Pro (PRO version) -#define BOARD_ZRIB_V53 1163 // Zonestar zrib V5.3 (Chinese RAMPS replica) -#define BOARD_PXMALION_CORE_I3 1164 // Pxmalion Core I3 -#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_PXMALION_CORE_I3 1163 // Pxmalion Core I3 +#define BOARD_PANOWIN_CUTLASS 1164 // Panowin Cutlass (as found in the Panowin F1) +#define BOARD_KODAMA_BARDO 1165 // Kodama Bardo V1.x (as found in the Kodama Trinus) +#define BOARD_XTLW_MFF_V1 1166 // XTLW MFF V1.0 +#define BOARD_XTLW_MFF_V2 1167 // XTLW MFF V2.0 +#define BOARD_RUMBA_E3D 1168 // E3D Rumba BigBox // // RAMBo and derivatives @@ -166,20 +169,25 @@ #define BOARD_GT2560_REV_A_PLUS 1315 // Geeetech GT2560 Rev A+ (with auto level probe) #define BOARD_GT2560_REV_B 1316 // Geeetech GT2560 Rev B #define BOARD_GT2560_V3 1317 // Geeetech GT2560 Rev B for A10(M/T/D) -#define BOARD_GT2560_V4 1318 // Geeetech GT2560 Rev B for A10(M/T/D) -#define BOARD_GT2560_V3_MC2 1319 // Geeetech GT2560 Rev B for Mecreator2 -#define BOARD_GT2560_V3_A20 1320 // Geeetech GT2560 Rev B for A20(M/T/D) -#define BOARD_EINSTART_S 1321 // Einstart retrofit -#define BOARD_WANHAO_ONEPLUS 1322 // Wanhao 0ne+ i3 Mini -#define BOARD_LEAPFROG_XEED2015 1323 // Leapfrog Xeed 2015 -#define BOARD_PICA_REVB 1324 // PICA Shield (original version) -#define BOARD_PICA 1325 // PICA Shield (rev C or later) -#define BOARD_INTAMSYS40 1326 // Intamsys 4.0 (Funmat HT) -#define BOARD_MALYAN_M180 1327 // Malyan M180 Mainboard Version 2 (no display function, direct G-code only) -#define BOARD_GT2560_V4_A20 1328 // Geeetech GT2560 Rev B for A20(M/T/D) -#define BOARD_PROTONEER_CNC_SHIELD_V3 1329 // Mega controller & Protoneer CNC Shield V3.00 -#define BOARD_WEEDO_62A 1330 // WEEDO 62A board (TINA2, Monoprice Cadet, etc.) -#define BOARD_GT2560_V41B 1331 // Geeetech GT2560 V4.1B for A10(M/T/D) +#define BOARD_GT2560_V3_MC2 1318 // Geeetech GT2560 Rev B for Mecreator2 +#define BOARD_GT2560_V3_A20 1319 // Geeetech GT2560 Rev B for A20(M/T/D) +#define BOARD_GT2560_V4 1320 // Geeetech GT2560 Rev B for A10(M/T/D) +#define BOARD_GT2560_V4_A20 1321 // Geeetech GT2560 Rev B for A20(M/T/D) +#define BOARD_GT2560_V41B 1322 // Geeetech GT2560 V4.1B for A10(M/T/D) +#define BOARD_EINSTART_S 1323 // Einstart retrofit +#define BOARD_WANHAO_ONEPLUS 1324 // Wanhao 0ne+ i3 Mini +#define BOARD_WANHAO_D9 1325 // Wanhao D9 MK2 +#define BOARD_OVERLORD 1326 // Overlord/Overlord Pro +#define BOARD_HJC2560C_REV1 1327 // ADIMLab Gantry v1 +#define BOARD_HJC2560C_REV2 1328 // ADIMLab Gantry v2 +#define BOARD_LEAPFROG_XEED2015 1329 // Leapfrog Xeed 2015 +#define BOARD_PICA_REVB 1330 // PICA Shield (original version) +#define BOARD_PICA 1331 // PICA Shield (rev C or later) +#define BOARD_INTAMSYS40 1332 // Intamsys 4.0 (Funmat HT) +#define BOARD_MALYAN_M180 1333 // Malyan M180 Mainboard Version 2 (no display function, direct G-code only) +#define BOARD_PROTONEER_CNC_SHIELD_V3 1334 // Mega controller & Protoneer CNC Shield V3.00 +#define BOARD_WEEDO_62A 1335 // WEEDO 62A board (TINA2, Monoprice Cadet, etc.) +#define BOARD_MIGHTYBOARD_REVG 1336 // Makerbot Mightyboard Revision G and H // // ATmega1281, ATmega2561 @@ -272,6 +280,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 8 // // SAM3X8E ARM Cortex-M3 @@ -279,7 +288,7 @@ #define BOARD_DUE3DOM 3000 // DUE3DOM for Arduino DUE #define BOARD_DUE3DOM_MINI 3001 // DUE3DOM MINI for Arduino DUE -#define BOARD_RADDS 3002 // RADDS +#define BOARD_RADDS 3002 // RADDS v1.5/v1.6 #define BOARD_RAMPS_FD_V1 3003 // RAMPS-FD v1 #define BOARD_RAMPS_FD_V2 3004 // RAMPS-FD v2 #define BOARD_RAMPS_SMART_EFB 3005 // RAMPS-SMART (Power outputs: Hotend, Fan, Bed) @@ -314,7 +323,7 @@ #define BOARD_ADSK 3101 // Arduino DUE Shield Kit (ADSK) // -// STM32 ARM Cortex-M0+ +// STM32 ARM Cortex-M0/+ // #define BOARD_BTT_EBB42_V1_1 4000 // BigTreeTech EBB42 V1.1 (STM32G0B1CB) @@ -324,84 +333,94 @@ #define BOARD_BTT_MANTA_M5P_V1_0 4004 // BigTreeTech Manta M5P V1.0 (STM32G0B1RE) #define BOARD_BTT_MANTA_M8P_V1_0 4005 // BigTreeTech Manta M8P V1.0 (STM32G0B1VE) #define BOARD_BTT_MANTA_M8P_V1_1 4006 // BigTreeTech Manta M8P V1.1 (STM32G0B1VE) +#define BOARD_BTT_SKRAT_V1_0 4007 // BigTreeTech SKRat V1.0 (STM32G0B1VE) + +// +// STM32 ARM Cortex-M0 +// + +#define BOARD_MALYAN_M200_V2 4100 // STM32F070CB controller +#define BOARD_MALYAN_M300 4101 // STM32F070-based delta +#define BOARD_FLY_D5 4102 // FLY_D5 (STM32F072RB) +#define BOARD_FLY_DP5 4103 // FLY_DP5 (STM32F072RB) +#define BOARD_FLY_D7 4104 // FLY_D7 (STM32F072RB) // // STM32 ARM Cortex-M3 // -#define BOARD_MALYAN_M200_V2 5000 // STM32F070CB controller -#define BOARD_MALYAN_M300 5001 // STM32F070-based delta -#define BOARD_STM32F103RE 5002 // STM32F103RE Libmaple-based STM32F1 controller -#define BOARD_MALYAN_M200 5003 // STM32C8 Libmaple-based STM32F1 controller -#define BOARD_STM3R_MINI 5004 // STM32F103RE Libmaple-based STM32F1 controller -#define BOARD_GTM32_PRO_VB 5005 // STM32F103VE controller -#define BOARD_GTM32_MINI 5006 // STM32F103VE controller -#define BOARD_GTM32_MINI_A30 5007 // STM32F103VE controller -#define BOARD_GTM32_REV_B 5008 // STM32F103VE controller -#define BOARD_MORPHEUS 5009 // STM32F103C8 / STM32F103CB Libmaple-based STM32F1 controller -#define BOARD_CHITU3D 5010 // Chitu3D (STM32F103RE) -#define BOARD_MKS_ROBIN 5011 // MKS Robin (STM32F103ZE) -#define BOARD_MKS_ROBIN_MINI 5012 // MKS Robin Mini (STM32F103VE) -#define BOARD_MKS_ROBIN_NANO 5013 // MKS Robin Nano (STM32F103VE) -#define BOARD_MKS_ROBIN_NANO_V2 5014 // MKS Robin Nano V2 (STM32F103VE) -#define BOARD_MKS_ROBIN_LITE 5015 // MKS Robin Lite/Lite2 (STM32F103RC) -#define BOARD_MKS_ROBIN_LITE3 5016 // MKS Robin Lite3 (STM32F103RC) -#define BOARD_MKS_ROBIN_PRO 5017 // MKS Robin Pro (STM32F103ZE) -#define BOARD_MKS_ROBIN_E3 5018 // MKS Robin E3 (STM32F103RC) -#define BOARD_MKS_ROBIN_E3_V1_1 5019 // MKS Robin E3 V1.1 (STM32F103RC) -#define BOARD_MKS_ROBIN_E3D 5020 // MKS Robin E3D (STM32F103RC) -#define BOARD_MKS_ROBIN_E3D_V1_1 5021 // MKS Robin E3D V1.1 (STM32F103RC) -#define BOARD_MKS_ROBIN_E3P 5022 // MKS Robin E3P (STM32F103VE) -#define BOARD_BTT_SKR_MINI_V1_1 5023 // BigTreeTech SKR Mini v1.1 (STM32F103RC) -#define BOARD_BTT_SKR_MINI_E3_V1_0 5024 // BigTreeTech SKR Mini E3 (STM32F103RC) -#define BOARD_BTT_SKR_MINI_E3_V1_2 5025 // BigTreeTech SKR Mini E3 V1.2 (STM32F103RC) -#define BOARD_BTT_SKR_MINI_E3_V2_0 5026 // BigTreeTech SKR Mini E3 V2.0 (STM32F103RC / STM32F103RE) -#define BOARD_BTT_SKR_MINI_MZ_V1_0 5027 // BigTreeTech SKR Mini MZ V1.0 (STM32F103RC) -#define BOARD_BTT_SKR_E3_DIP 5028 // BigTreeTech SKR E3 DIP V1.0 (STM32F103RC / STM32F103RE) -#define BOARD_BTT_SKR_CR6 5029 // BigTreeTech SKR CR6 v1.0 (STM32F103RE) -#define BOARD_JGAURORA_A5S_A1 5030 // JGAurora A5S A1 (STM32F103ZE) -#define BOARD_FYSETC_AIO_II 5031 // FYSETC AIO_II (STM32F103RC) -#define BOARD_FYSETC_CHEETAH 5032 // FYSETC Cheetah (STM32F103RC) -#define BOARD_FYSETC_CHEETAH_V12 5033 // FYSETC Cheetah V1.2 (STM32F103RC) -#define BOARD_LONGER3D_LK 5034 // Longer3D LK1/2 - Alfawise U20/U20+/U30 (STM32F103VE) -#define BOARD_CCROBOT_MEEB_3DP 5035 // ccrobot-online.com MEEB_3DP (STM32F103RC) -#define BOARD_CHITU3D_V5 5036 // Chitu3D TronXY X5SA V5 Board (STM32F103ZE) -#define BOARD_CHITU3D_V6 5037 // Chitu3D TronXY X5SA V6 Board (STM32F103ZE) -#define BOARD_CHITU3D_V9 5038 // Chitu3D TronXY X5SA V9 Board (STM32F103ZE) -#define BOARD_CREALITY_V4 5039 // Creality v4.x (STM32F103RC / STM32F103RE) -#define BOARD_CREALITY_V422 5040 // Creality v4.2.2 (STM32F103RC / STM32F103RE) -#define BOARD_CREALITY_V423 5041 // Creality v4.2.3 (STM32F103RC / STM32F103RE) -#define BOARD_CREALITY_V425 5042 // Creality v4.2.5 (STM32F103RC / STM32F103RE) -#define BOARD_CREALITY_V427 5043 // Creality v4.2.7 (STM32F103RC / STM32F103RE) -#define BOARD_CREALITY_V4210 5044 // Creality v4.2.10 (STM32F103RC / STM32F103RE) as found in the CR-30 -#define BOARD_CREALITY_V431 5045 // Creality v4.3.1 (STM32F103RC / STM32F103RE) -#define BOARD_CREALITY_V431_A 5046 // Creality v4.3.1a (STM32F103RC / STM32F103RE) -#define BOARD_CREALITY_V431_B 5047 // Creality v4.3.1b (STM32F103RC / STM32F103RE) -#define BOARD_CREALITY_V431_C 5048 // Creality v4.3.1c (STM32F103RC / STM32F103RE) -#define BOARD_CREALITY_V431_D 5049 // Creality v4.3.1d (STM32F103RC / STM32F103RE) -#define BOARD_CREALITY_V452 5050 // Creality v4.5.2 (STM32F103RC / STM32F103RE) -#define BOARD_CREALITY_V453 5051 // Creality v4.5.3 (STM32F103RC / STM32F103RE) -#define BOARD_CREALITY_V521 5052 // Creality v5.2.1 (STM32F103VE) as found in the SV04 -#define BOARD_CREALITY_V24S1 5053 // Creality v2.4.S1 (STM32F103RC / STM32F103RE) v101 as found in the Ender-7 -#define BOARD_CREALITY_V24S1_301 5054 // Creality v2.4.S1_301 (STM32F103RC / STM32F103RE) v301 as found in the Ender-3 S1 -#define BOARD_CREALITY_V25S1 5055 // Creality v2.5.S1 (STM32F103RE) as found in the CR-10 Smart Pro -#define BOARD_TRIGORILLA_PRO 5056 // Trigorilla Pro (STM32F103ZE) -#define BOARD_FLY_MINI 5057 // FLYmaker FLY MINI (STM32F103RC) -#define BOARD_FLSUN_HISPEED 5058 // FLSUN HiSpeedV1 (STM32F103VE) -#define BOARD_BEAST 5059 // STM32F103RE Libmaple-based controller -#define BOARD_MINGDA_MPX_ARM_MINI 5060 // STM32F103ZE Mingda MD-16 -#define BOARD_GTM32_PRO_VD 5061 // STM32F103VE controller -#define BOARD_ZONESTAR_ZM3E2 5062 // Zonestar ZM3E2 (STM32F103RC) -#define BOARD_ZONESTAR_ZM3E4 5063 // Zonestar ZM3E4 V1 (STM32F103VC) -#define BOARD_ZONESTAR_ZM3E4V2 5064 // Zonestar ZM3E4 V2 (STM32F103VC) -#define BOARD_ERYONE_ERY32_MINI 5065 // Eryone Ery32 mini (STM32F103VE) -#define BOARD_PANDA_PI_V29 5066 // Panda Pi V2.9 - Standalone (STM32F103RC) -#define BOARD_SOVOL_V131 5067 // Sovol V1.3.1 (GD32F103RE) -#define BOARD_TRIGORILLA_V006 5068 // Trigorilla V0.0.6 (GD32F103RE) -#define BOARD_KEDI_CONTROLLER_V1_2 5069 // EDUTRONICS Kedi Controller V1.2 (STM32F103RC) -#define BOARD_MD_D301 5070 // Mingda D2 DZ301 V1.0 (STM32F103ZE) -#define BOARD_VOXELAB_AQUILA 5071 // Voxelab Aquila V1.0.0/V1.0.1 (GD32F103RC / N32G455RE / STM32F103RE) -#define BOARD_SPRINGER_CONTROLLER 5072 // ORCA 3D SPRINGER Modular Controller (STM32F103VC) +#define BOARD_STM32F103RE 5000 // STM32F103RE Libmaple-based STM32F1 controller +#define BOARD_MALYAN_M200 5001 // STM32C8 Libmaple-based STM32F1 controller +#define BOARD_STM3R_MINI 5002 // STM32F103RE Libmaple-based STM32F1 controller +#define BOARD_GTM32_PRO_VB 5003 // STM32F103VE controller +#define BOARD_GTM32_PRO_VD 5004 // STM32F103VE controller +#define BOARD_GTM32_MINI 5005 // STM32F103VE controller +#define BOARD_GTM32_MINI_A30 5006 // STM32F103VE controller +#define BOARD_GTM32_REV_B 5007 // STM32F103VE controller +#define BOARD_MORPHEUS 5008 // STM32F103C8 / STM32F103CB Libmaple-based STM32F1 controller +#define BOARD_CHITU3D 5009 // Chitu3D (STM32F103RE) +#define BOARD_MKS_ROBIN 5010 // MKS Robin (STM32F103ZE) +#define BOARD_MKS_ROBIN_MINI 5011 // MKS Robin Mini (STM32F103VE) +#define BOARD_MKS_ROBIN_NANO 5012 // MKS Robin Nano (STM32F103VE) +#define BOARD_MKS_ROBIN_NANO_V2 5013 // MKS Robin Nano V2 (STM32F103VE) +#define BOARD_MKS_ROBIN_LITE 5014 // MKS Robin Lite/Lite2 (STM32F103RC) +#define BOARD_MKS_ROBIN_LITE3 5015 // MKS Robin Lite3 (STM32F103RC) +#define BOARD_MKS_ROBIN_PRO 5016 // MKS Robin Pro (STM32F103ZE) +#define BOARD_MKS_ROBIN_E3 5017 // MKS Robin E3 (STM32F103RC) +#define BOARD_MKS_ROBIN_E3_V1_1 5018 // MKS Robin E3 V1.1 (STM32F103RC) +#define BOARD_MKS_ROBIN_E3D 5019 // MKS Robin E3D (STM32F103RC) +#define BOARD_MKS_ROBIN_E3D_V1_1 5020 // MKS Robin E3D V1.1 (STM32F103RC) +#define BOARD_MKS_ROBIN_E3P 5021 // MKS Robin E3P (STM32F103VE) +#define BOARD_BTT_SKR_MINI_V1_1 5022 // BigTreeTech SKR Mini v1.1 (STM32F103RC) +#define BOARD_BTT_SKR_MINI_E3_V1_0 5023 // BigTreeTech SKR Mini E3 (STM32F103RC) +#define BOARD_BTT_SKR_MINI_E3_V1_2 5024 // BigTreeTech SKR Mini E3 V1.2 (STM32F103RC) +#define BOARD_BTT_SKR_MINI_E3_V2_0 5025 // BigTreeTech SKR Mini E3 V2.0 (STM32F103RC / STM32F103RE) +#define BOARD_BTT_SKR_MINI_MZ_V1_0 5026 // BigTreeTech SKR Mini MZ V1.0 (STM32F103RC) +#define BOARD_BTT_SKR_E3_DIP 5027 // BigTreeTech SKR E3 DIP V1.0 (STM32F103RC / STM32F103RE) +#define BOARD_BTT_SKR_CR6 5028 // BigTreeTech SKR CR6 v1.0 (STM32F103RE) +#define BOARD_JGAURORA_A5S_A1 5029 // JGAurora A5S A1 (STM32F103ZE) +#define BOARD_FYSETC_AIO_II 5030 // FYSETC AIO_II (STM32F103RC) +#define BOARD_FYSETC_CHEETAH 5031 // FYSETC Cheetah (STM32F103RC) +#define BOARD_FYSETC_CHEETAH_V12 5032 // FYSETC Cheetah V1.2 (STM32F103RC) +#define BOARD_LONGER3D_LK 5033 // Longer3D LK1/2 - Alfawise U20/U20+/U30 (STM32F103VE) +#define BOARD_CCROBOT_MEEB_3DP 5034 // ccrobot-online.com MEEB_3DP (STM32F103RC) +#define BOARD_CHITU3D_V5 5035 // Chitu3D TronXY X5SA V5 Board (STM32F103ZE) +#define BOARD_CHITU3D_V6 5036 // Chitu3D TronXY X5SA V6 Board (STM32F103ZE) +#define BOARD_CHITU3D_V9 5037 // Chitu3D TronXY X5SA V9 Board (STM32F103ZE) +#define BOARD_CREALITY_V4 5038 // Creality v4.x (STM32F103RC / STM32F103RE) +#define BOARD_CREALITY_V422 5039 // Creality v4.2.2 (STM32F103RC / STM32F103RE) ... GD32 Variant Below! +#define BOARD_CREALITY_V423 5040 // Creality v4.2.3 (STM32F103RC / STM32F103RE) +#define BOARD_CREALITY_V425 5041 // Creality v4.2.5 (STM32F103RC / STM32F103RE) +#define BOARD_CREALITY_V427 5042 // Creality v4.2.7 (STM32F103RC / STM32F103RE) ... GD32 Variant Below! +#define BOARD_CREALITY_V4210 5043 // Creality v4.2.10 (STM32F103RC / STM32F103RE) as found in the CR-30 +#define BOARD_CREALITY_V431 5044 // Creality v4.3.1 (STM32F103RC / STM32F103RE) +#define BOARD_CREALITY_V431_A 5045 // Creality v4.3.1a (STM32F103RC / STM32F103RE) +#define BOARD_CREALITY_V431_B 5046 // Creality v4.3.1b (STM32F103RC / STM32F103RE) +#define BOARD_CREALITY_V431_C 5047 // Creality v4.3.1c (STM32F103RC / STM32F103RE) +#define BOARD_CREALITY_V431_D 5048 // Creality v4.3.1d (STM32F103RC / STM32F103RE) +#define BOARD_CREALITY_V452 5049 // Creality v4.5.2 (STM32F103RC / STM32F103RE) +#define BOARD_CREALITY_V453 5050 // Creality v4.5.3 (STM32F103RC / STM32F103RE) +#define BOARD_CREALITY_V521 5051 // Creality v5.2.1 (STM32F103VE) as found in the SV04 +#define BOARD_CREALITY_V24S1 5052 // Creality v2.4.S1 (STM32F103RC / STM32F103RE) CR-FDM-v2.4.S1_v101 as found in the Ender-7 +#define BOARD_CREALITY_V24S1_301 5053 // Creality v2.4.S1_301 (STM32F103RC / STM32F103RE) CR-FDM-v24S1_301 as found in the Ender-3 S1 +#define BOARD_CREALITY_V25S1 5054 // Creality v2.5.S1 (STM32F103RE) CR-FDM-v2.5.S1_100 as found in the CR-10 Smart Pro +#define BOARD_TRIGORILLA_PRO 5055 // Trigorilla Pro (STM32F103ZE) +#define BOARD_FLY_MINI 5056 // FLYmaker FLY MINI (STM32F103RC) +#define BOARD_FLSUN_HISPEED 5057 // FLSUN HiSpeedV1 (STM32F103VE) +#define BOARD_BEAST 5058 // STM32F103RE Libmaple-based controller +#define BOARD_MINGDA_MPX_ARM_MINI 5059 // STM32F103ZE Mingda MD-16 +#define BOARD_ZONESTAR_ZM3E2 5060 // Zonestar ZM3E2 (STM32F103RC) +#define BOARD_ZONESTAR_ZM3E4 5061 // Zonestar ZM3E4 V1 (STM32F103VC) +#define BOARD_ZONESTAR_ZM3E4V2 5062 // Zonestar ZM3E4 V2 (STM32F103VC) +#define BOARD_ERYONE_ERY32_MINI 5063 // Eryone Ery32 mini (STM32F103VE) +#define BOARD_PANDA_PI_V29 5064 // Panda Pi V2.9 - Standalone (STM32F103RC) +#define BOARD_SOVOL_V131 5065 // Sovol V1.3.1 (GD32F103RE) +#define BOARD_TRIGORILLA_V006 5066 // Trigorilla V0.0.6 (GD32F103RE) +#define BOARD_KEDI_CONTROLLER_V1_2 5067 // EDUTRONICS Kedi Controller V1.2 (STM32F103RC) +#define BOARD_MD_D301 5068 // Mingda D2 DZ301 V1.0 (STM32F103ZE) +#define BOARD_VOXELAB_AQUILA 5069 // Voxelab Aquila V1.0.0/V1.0.1 (GD32F103RC / N32G455RE / STM32F103RE) +#define BOARD_SPRINGER_CONTROLLER 5070 // ORCA 3D SPRINGER Modular Controller (STM32F103VC) +#define BOARD_ATOMSTACK_FB5_V2 5071 // Atomstack FB5 V2.0 (STM32F103RCT6) // // ARM Cortex-M4F @@ -414,76 +433,97 @@ // STM32 ARM Cortex-M4F // -#define BOARD_ARMED 5200 // Arm'ed STM32F4-based controller -#define BOARD_RUMBA32_V1_0 5201 // RUMBA32 STM32F446VE based controller from Aus3D -#define BOARD_RUMBA32_V1_1 5202 // RUMBA32 STM32F446VE based controller from Aus3D -#define BOARD_RUMBA32_MKS 5203 // RUMBA32 STM32F446VE based controller from Makerbase -#define BOARD_RUMBA32_BTT 5204 // RUMBA32 STM32F446VE based controller from BIGTREETECH -#define BOARD_BLACK_STM32F407VE 5205 // BLACK_STM32F407VE -#define BOARD_BLACK_STM32F407ZE 5206 // BLACK_STM32F407ZE -#define BOARD_BTT_SKR_MINI_E3_V3_0_1 5207 // BigTreeTech SKR Mini E3 V3.0.1 (STM32F401RC) -#define BOARD_BTT_SKR_PRO_V1_1 5208 // BigTreeTech SKR Pro v1.1 (STM32F407ZG) -#define BOARD_BTT_SKR_PRO_V1_2 5209 // BigTreeTech SKR Pro v1.2 (STM32F407ZG) -#define BOARD_BTT_BTT002_V1_0 5210 // BigTreeTech BTT002 v1.0 (STM32F407VG) -#define BOARD_BTT_E3_RRF 5211 // BigTreeTech E3 RRF (STM32F407VG) -#define BOARD_BTT_SKR_V2_0_REV_A 5212 // BigTreeTech SKR v2.0 Rev A (STM32F407VG) -#define BOARD_BTT_SKR_V2_0_REV_B 5213 // BigTreeTech SKR v2.0 Rev B (STM32F407VG/STM32F429VG) -#define BOARD_BTT_GTR_V1_0 5214 // BigTreeTech GTR v1.0 (STM32F407IGT) -#define BOARD_BTT_OCTOPUS_V1_0 5215 // BigTreeTech Octopus v1.0 (STM32F446ZE) -#define BOARD_BTT_OCTOPUS_V1_1 5216 // BigTreeTech Octopus v1.1 (STM32F446ZE) -#define BOARD_BTT_OCTOPUS_PRO_V1_0 5217 // BigTreeTech Octopus Pro v1.0 (STM32F446ZE / STM32F429ZG) -#define BOARD_LERDGE_K 5218 // Lerdge K (STM32F407ZG) -#define BOARD_LERDGE_S 5219 // Lerdge S (STM32F407VE) -#define BOARD_LERDGE_X 5220 // Lerdge X (STM32F407VE) -#define BOARD_FYSETC_S6 5221 // FYSETC S6 (STM32F446VE) -#define BOARD_FYSETC_S6_V2_0 5222 // FYSETC S6 v2.0 (STM32F446VE) -#define BOARD_FYSETC_SPIDER 5223 // FYSETC Spider (STM32F446VE) -#define BOARD_FLYF407ZG 5224 // FLYmaker FLYF407ZG (STM32F407ZG) -#define BOARD_MKS_ROBIN2 5225 // MKS Robin2 V1.0 (STM32F407ZE) -#define BOARD_MKS_ROBIN_PRO_V2 5226 // MKS Robin Pro V2 (STM32F407VE) -#define BOARD_MKS_ROBIN_NANO_V3 5227 // MKS Robin Nano V3 (STM32F407VG) -#define BOARD_MKS_ROBIN_NANO_V3_1 5228 // MKS Robin Nano V3.1 (STM32F407VE) -#define BOARD_MKS_MONSTER8_V1 5229 // MKS Monster8 V1 (STM32F407VE) -#define BOARD_MKS_MONSTER8_V2 5230 // MKS Monster8 V2 (STM32F407VE) -#define BOARD_ANET_ET4 5231 // ANET ET4 V1.x (STM32F407VG) -#define BOARD_ANET_ET4P 5232 // ANET ET4P V1.x (STM32F407VG) -#define BOARD_FYSETC_CHEETAH_V20 5233 // FYSETC Cheetah V2.0 (STM32F401RC) -#define BOARD_TH3D_EZBOARD_V2 5234 // TH3D EZBoard v2.0 (STM32F405RG) -#define BOARD_OPULO_LUMEN_REV3 5235 // Opulo Lumen PnP Controller REV3 (STM32F407VE / STM32F407VG) -#define BOARD_MKS_ROBIN_NANO_V1_3_F4 5236 // MKS Robin Nano V1.3 and MKS Robin Nano-S V1.3 (STM32F407VE) -#define BOARD_MKS_EAGLE 5237 // MKS Eagle (STM32F407VE) -#define BOARD_ARTILLERY_RUBY 5238 // Artillery Ruby (STM32F401RC) -#define BOARD_FYSETC_SPIDER_V2_2 5239 // FYSETC Spider V2.2 (STM32F446VE) -#define BOARD_CREALITY_V24S1_301F4 5240 // Creality v2.4.S1_301F4 (STM32F401RC) as found in the Ender-3 S1 F4 -#define BOARD_CREALITY_CR4NTXXC10 5241 // Creality E3 Free-runs Silent Motherboard (STM32F401RET6) -#define BOARD_OPULO_LUMEN_REV4 5242 // Opulo Lumen PnP Controller REV4 (STM32F407VE / STM32F407VG) -#define BOARD_FYSETC_SPIDER_KING407 5243 // FYSETC Spider King407 (STM32F407ZG) -#define BOARD_MKS_SKIPR_V1 5244 // MKS SKIPR v1.0 all-in-one board (STM32F407VE) -#define BOARD_TRONXY_CXY_446_V10 5245 // TRONXY CXY-446-V10-220413/CXY-V6-191121 (STM32F446ZE) -#define BOARD_CREALITY_F401RE 5246 // Creality CR4NS200141C13 (STM32F401RE) as found in the Ender-5 S1 -#define BOARD_BLACKPILL_CUSTOM 5247 // Custom board based on STM32F401CDU6. -#define BOARD_I3DBEEZ9_V1 5248 // I3DBEEZ9 V1 (STM32F407ZG) -#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_ARMED 5200 // Arm'ed STM32F4-based controller +#define BOARD_RUMBA32_V1_0 5201 // RUMBA32 STM32F446VE based controller from Aus3D +#define BOARD_RUMBA32_V1_1 5202 // RUMBA32 STM32F446VE based controller from Aus3D +#define BOARD_RUMBA32_MKS 5203 // RUMBA32 STM32F446VE based controller from Makerbase +#define BOARD_RUMBA32_BTT 5204 // RUMBA32 STM32F446VE based controller from BIGTREETECH +#define BOARD_BLACK_STM32F407VE 5205 // Black STM32F407VE development board +#define BOARD_BLACK_STM32F407ZE 5206 // Black STM32F407ZE development board +#define BOARD_BTT_SKR_MINI_E3_V3_0_1 5207 // BigTreeTech SKR Mini E3 V3.0.1 (STM32F401RC) +#define BOARD_BTT_SKR_PRO_V1_1 5208 // BigTreeTech SKR Pro v1.1 (STM32F407ZG) +#define BOARD_BTT_SKR_PRO_V1_2 5209 // BigTreeTech SKR Pro v1.2 (STM32F407ZG) +#define BOARD_BTT_BTT002_V1_0 5210 // BigTreeTech BTT002 v1.0 (STM32F407VG) +#define BOARD_BTT_E3_RRF 5211 // BigTreeTech E3 RRF (STM32F407VG) +#define BOARD_BTT_SKR_V2_0_REV_A 5212 // BigTreeTech SKR v2.0 Rev A (STM32F407VG) +#define BOARD_BTT_SKR_V2_0_REV_B 5213 // BigTreeTech SKR v2.0 Rev B (STM32F407VG/STM32F429VG) +#define BOARD_BTT_GTR_V1_0 5214 // BigTreeTech GTR v1.0 (STM32F407IGT) +#define BOARD_BTT_OCTOPUS_V1_0 5215 // BigTreeTech Octopus v1.0 (STM32F446ZE) +#define BOARD_BTT_OCTOPUS_V1_1 5216 // BigTreeTech Octopus v1.1 (STM32F446ZE) +#define BOARD_BTT_OCTOPUS_PRO_V1_0 5217 // BigTreeTech Octopus Pro v1.0 (STM32F446ZE / STM32F429ZG) +#define BOARD_LERDGE_K 5218 // Lerdge K (STM32F407ZG) +#define BOARD_LERDGE_S 5219 // Lerdge S (STM32F407VE) +#define BOARD_LERDGE_X 5220 // Lerdge X (STM32F407VE) +#define BOARD_FYSETC_S6 5221 // FYSETC S6 (STM32F446VE) +#define BOARD_FYSETC_S6_V2_0 5222 // FYSETC S6 v2.0 (STM32F446VE) +#define BOARD_FYSETC_SPIDER 5223 // FYSETC Spider (STM32F446VE) +#define BOARD_FYSETC_SPIDER_V2_2 5224 // FYSETC Spider V2.2 (STM32F446VE) +#define BOARD_FLYF407ZG 5225 // FLYmaker FLYF407ZG (STM32F407ZG) +#define BOARD_MKS_ROBIN2 5226 // MKS Robin2 V1.0 (STM32F407ZE) +#define BOARD_MKS_ROBIN_PRO_V2 5227 // MKS Robin Pro V2 (STM32F407VE) +#define BOARD_MKS_ROBIN_NANO_V3 5228 // MKS Robin Nano V3 (STM32F407VG) +#define BOARD_MKS_ROBIN_NANO_V3_1 5229 // MKS Robin Nano V3.1 (STM32F407VE) +#define BOARD_MKS_MONSTER8_V1 5230 // MKS Monster8 V1 (STM32F407VE) +#define BOARD_MKS_MONSTER8_V2 5231 // MKS Monster8 V2 (STM32F407VE) +#define BOARD_ANET_ET4 5232 // ANET ET4 V1.x (STM32F407VG) +#define BOARD_ANET_ET4P 5233 // ANET ET4P V1.x (STM32F407VG) +#define BOARD_FYSETC_CHEETAH_V20 5234 // FYSETC Cheetah V2.0 (STM32F401RC) +#define BOARD_FYSETC_CHEETAH_V30 5235 // FYSETC Cheetah V3.0 (STM32F446RC) +#define BOARD_TH3D_EZBOARD_V2 5236 // TH3D EZBoard v2.0 (STM32F405RG) +#define BOARD_OPULO_LUMEN_REV3 5237 // Opulo Lumen PnP Controller REV3 (STM32F407VE / STM32F407VG) +#define BOARD_OPULO_LUMEN_REV4 5238 // Opulo Lumen PnP Controller REV4 (STM32F407VE / STM32F407VG) +#define BOARD_MKS_ROBIN_NANO_V1_3_F4 5239 // MKS Robin Nano V1.3 and MKS Robin Nano-S V1.3 (STM32F407VE) +#define BOARD_MKS_EAGLE 5240 // MKS Eagle (STM32F407VE) +#define BOARD_ARTILLERY_RUBY 5241 // Artillery Ruby (STM32F401RC) +#define BOARD_CREALITY_V24S1_301F4 5242 // Creality v2.4.S1_301F4 (STM32F401RC) as found in the Ender-3 S1 F4 +#define BOARD_CREALITY_CR4NTXXC10 5243 // Creality E3 Free-runs Silent Motherboard (STM32F401RET6) +#define BOARD_FYSETC_SPIDER_KING_V1_F407 5244 // FYSETC Spider King v1 (STM32F407ZG) +#define BOARD_FYSETC_SPIDER_KING_V1_1_F407 5245 // FYSETC Spider King v1.1 (STM32F407ZG) +#define BOARD_MKS_SKIPR_V1 5246 // MKS SKIPR v1.0 all-in-one board (STM32F407VE) +#define BOARD_TRONXY_CXY_446_V10 5247 // TRONXY CXY-446-V10-220413/CXY-V6-191121 (STM32F446ZE) +#define BOARD_CREALITY_F401RE 5248 // Creality CR4NS200141C13 (STM32F401RE) as found in the Ender-5 S1 +#define BOARD_BLACKPILL_CUSTOM 5249 // Custom board based on STM32F401CDU6. +#define BOARD_I3DBEEZ9_V1 5250 // I3DBEEZ9 V1 (STM32F407ZG) +#define BOARD_MELLOW_FLY_E3_V2 5251 // Mellow Fly E3 V2 (STM32F407VG) +#define BOARD_MELLOW_FLY_E3_PRO_V3 5252 // Mellow Fly E3 Pro V3 (STM32F407VG) +#define BOARD_BLACKBEEZMINI_V1 5253 // BlackBeezMini V1 (STM32F401CCU6) +#define BOARD_XTLW_CLIMBER_8TH 5254 // XTLW Climber-8th (STM32F407VGT6) +#define BOARD_FLY_RRF_E3_V1 5255 // Fly RRF E3 V1.0 (STM32F407VG) +#define BOARD_FLY_SUPER8 5256 // Fly SUPER8 (STM32F407ZGT6) +#define BOARD_FLY_D8 5257 // FLY D8 (STM32F407VG) +#define BOARD_FLY_CDY_V3 5258 // FLY CDY V3 (STM32F407VGT6) +#define BOARD_ZNP_ROBIN_NANO 5259 // Elegoo Neptune 2 v1.2 board +#define BOARD_ZNP_ROBIN_NANO_V1_3 5260 // Elegoo Neptune 2 v1.3 board +#define BOARD_MKS_NEPTUNE_X 5261 // Elegoo Neptune X +#define BOARD_MKS_NEPTUNE_3 5262 // Elegoo Neptune 3 + +// +// Other ARM Cortex-M4 +// +#define BOARD_CREALITY_CR4NS 5300 // Creality CR4NS200320C13 (GD32F303RET6) as found in the Ender-3 V3 SE // // ARM Cortex-M7 // -#define BOARD_REMRAM_V1 6000 // RemRam v1 -#define BOARD_TEENSY41 6001 // Teensy 4.1 -#define BOARD_T41U5XBB 6002 // T41U5XBB Teensy 4.1 breakout board -#define BOARD_NUCLEO_F767ZI 6003 // ST NUCLEO-F767ZI Dev Board -#define BOARD_BTT_SKR_SE_BX_V2 6004 // BigTreeTech SKR SE BX V2.0 (STM32H743II) -#define BOARD_BTT_SKR_SE_BX_V3 6005 // BigTreeTech SKR SE BX V3.0 (STM32H743II) -#define BOARD_BTT_SKR_V3_0 6006 // BigTreeTech SKR V3.0 (STM32H743VI / STM32H723VG) -#define BOARD_BTT_SKR_V3_0_EZ 6007 // BigTreeTech SKR V3.0 EZ (STM32H743VI / STM32H723VG) -#define BOARD_BTT_OCTOPUS_MAX_EZ_V1_0 6008 // BigTreeTech Octopus Max EZ V1.0 (STM32H723ZE) -#define BOARD_BTT_OCTOPUS_PRO_V1_0_1 6009 // BigTreeTech Octopus Pro v1.0.1 (STM32H723ZE) -#define BOARD_BTT_OCTOPUS_PRO_V1_1 6010 // BigTreeTech Octopus Pro v1.1 (STM32H723ZE) -#define BOARD_BTT_MANTA_M8P_V2_0 6011 // BigTreeTech Manta M8P V2.0 (STM32H723ZE) -#define BOARD_BTT_KRAKEN_V1_0 6012 // BigTreeTech Kraken v1.0 (STM32H723ZG) +#define BOARD_REMRAM_V1 6000 // RemRam v1 +#define BOARD_NUCLEO_F767ZI 6001 // ST NUCLEO-F767ZI Dev Board +#define BOARD_BTT_SKR_SE_BX_V2 6002 // BigTreeTech SKR SE BX V2.0 (STM32H743II) +#define BOARD_BTT_SKR_SE_BX_V3 6003 // BigTreeTech SKR SE BX V3.0 (STM32H743II) +#define BOARD_BTT_SKR_V3_0 6004 // BigTreeTech SKR V3.0 (STM32H743VI / STM32H723VG) +#define BOARD_BTT_SKR_V3_0_EZ 6005 // BigTreeTech SKR V3.0 EZ (STM32H743VI / STM32H723VG) +#define BOARD_BTT_OCTOPUS_MAX_EZ_V1_0 6006 // BigTreeTech Octopus Max EZ V1.0 (STM32H723ZE) +#define BOARD_BTT_OCTOPUS_PRO_V1_0_1 6007 // BigTreeTech Octopus Pro v1.0.1 (STM32H723ZE) +#define BOARD_BTT_OCTOPUS_PRO_V1_1 6008 // BigTreeTech Octopus Pro v1.1 (STM32H723ZE) +#define BOARD_BTT_MANTA_M8P_V2_0 6009 // BigTreeTech Manta M8P V2.0 (STM32H723ZE) +#define BOARD_BTT_KRAKEN_V1_0 6010 // BigTreeTech Kraken v1.0 (STM32H723ZG) +#define BOARD_TEENSY40 6011 // Teensy 4.0 +#define BOARD_TEENSY41 6012 // Teensy 4.1 +#define BOARD_T41U5XBB 6013 // T41U5XBB Teensy 4.1 breakout board +#define BOARD_FLY_D8_PRO 6014 // FLY_D8_PRO (STM32H723VG) +#define BOARD_FLY_SUPER8_PRO 6015 // FLY SUPER8 PRO (STM32H723ZG) +#define BOARD_FYSETC_SPIDER_KING_V1_H723 6016 // FYSETC Spider King v1 (STM32H723ZG) +#define BOARD_FYSETC_SPIDER_KING_V1_1_H723 6017 // FYSETC Spider King v1.1 (STM32H723ZG) // // Espressif ESP32 WiFi @@ -520,9 +560,30 @@ // HC32 ARM Cortex-M4 // -#define BOARD_AQUILA_V101 7200 // Voxelab Aquila V1.0.0/V1.0.1/V1.0.2/V1.0.3 as found in the Voxelab Aquila X2 and C2 +#define BOARD_AQUILA_V101 7200 // Voxelab Aquila V1.0.0/1/2/3 (e.g., Aquila X2, C2). ... GD32 Variant Below! #define BOARD_CREALITY_ENDER2P_V24S4 7201 // Creality Ender 2 Pro v2.4.S4_170 (HC32f460kcta) +// +// GD32 ARM Cortex-M3 +// + +#define BOARD_AQUILA_V101_GD32_MFL 7300 // Voxelab Aquila V1.0.1 MFL (GD32F103RC) ... STM32/HC32 Variant Above! + +// +// GD32 ARM Cortex-M4 +// + +#define BOARD_CREALITY_V422_GD32_MFL 7400 // Creality V4.2.2 MFL (GD32F303RE) ... STM32 Variant Above! +#define BOARD_CREALITY_V427_GD32_MFL 7401 // Creality V4.2.7 MFL (GD32F303RE) ... STM32 Variant Above! + +// +// Raspberry Pi +// + +#define BOARD_RP2040 6200 // Generic RP2040 Test board +#define BOARD_RASPBERRY_PI_PICO 6201 // Raspberry Pi Pico +#define BOARD_BTT_SKR_PICO 6202 // BigTreeTech SKR Pico 1.x + // // Custom board // @@ -533,7 +594,7 @@ // Simulations // -#define BOARD_SIMULATED 9999 +#define BOARD_SIMULATED 9999 // Simulated 3D Printer with LCD / TFT for development #define _MB_1(B) (defined(BOARD_##B) && MOTHERBOARD==BOARD_##B) #define MB(V...) DO(MB,||,V) diff --git a/Marlin/src/core/bug_on.h b/Marlin/src/core/bug_on.h index 7f1243ed40b6..8ff565ff739b 100644 --- a/Marlin/src/core/bug_on.h +++ b/Marlin/src/core/bug_on.h @@ -27,12 +27,12 @@ // Useful macro for stopping the CPU on an unexpected condition // This is used like SERIAL_ECHOPGM, that is: a key-value call of the local variables you want // to dump to the serial port before stopping the CPU. - // \/ Don't replace by SERIAL_ECHOPGM since ONLY_FILENAME cannot be transformed to a PGM string on Arduino and it breaks building - #define BUG_ON(V...) do { SERIAL_ECHO(ONLY_FILENAME); SERIAL_ECHO(__LINE__); SERIAL_ECHOLNPGM(": "); SERIAL_ECHOLNPGM(V); SERIAL_FLUSHTX(); *(char*)0 = 42; } while(0) + // \/ Don't use SERIAL_ECHOPGM with ONLY_FILENAME. It can't be a PGM string, + #define BUG_ON(V...) do { SERIAL_ECHOLN(ONLY_FILENAME, __LINE__, F(": ")); SERIAL_ECHOLNPGM(V); SERIAL_FLUSHTX(); *(char*)0 = 42; } while(0) #elif ENABLED(MARLIN_DEV_MODE) // Don't stop the CPU here, but at least dump the bug on the serial port - // \/ Don't replace by SERIAL_ECHOPGM since ONLY_FILENAME cannot be transformed to a PGM string on Arduino and it breaks building - #define BUG_ON(V...) do { SERIAL_ECHO(ONLY_FILENAME); SERIAL_ECHO(__LINE__); SERIAL_ECHOLNPGM(": BUG!"); SERIAL_ECHOLNPGM(V); SERIAL_FLUSHTX(); } while(0) + // \/ Don't use SERIAL_ECHOPGM with ONLY_FILENAME. It can't be a PGM string, + #define BUG_ON(V...) do { SERIAL_ECHOLN(ONLY_FILENAME, __LINE__, F(": BUG!")); SERIAL_ECHOLNPGM(V); SERIAL_FLUSHTX(); } while(0) #else // Release mode, let's ignore the bug #define BUG_ON(V...) NOOP diff --git a/Marlin/src/core/debug_section.h b/Marlin/src/core/debug_section.h index 6319515b71e1..15ff8f270bf3 100644 --- a/Marlin/src/core/debug_section.h +++ b/Marlin/src/core/debug_section.h @@ -41,6 +41,6 @@ class SectionLog { SERIAL_ECHO(fpre); if (the_msg) SERIAL_ECHO(C(' '), the_msg); SERIAL_CHAR(' '); - print_xyz(xyz_pos_t(current_position)); + print_xyz(xyz_pos_t(motion.position)); } }; diff --git a/Marlin/src/core/drivers.h b/Marlin/src/core/drivers.h index e28fc1bba7af..80980380a539 100644 --- a/Marlin/src/core/drivers.h +++ b/Marlin/src/core/drivers.h @@ -41,6 +41,7 @@ #define _TMC2208_STANDALONE 0x2208B #define _TMC2209 0x2209A #define _TMC2209_STANDALONE 0x2209B +#define _TMC2240 0x2240A #define _TMC2660 0x2660A #define _TMC2660_STANDALONE 0x2660B #define _TMC5130 0x5130A @@ -96,31 +97,43 @@ // Does not match standalone configurations #if ( HAS_DRIVER(TMC2130) || HAS_DRIVER(TMC2160) \ || HAS_DRIVER(TMC2208) || HAS_DRIVER(TMC2209) \ - || HAS_DRIVER(TMC2660) \ + || HAS_DRIVER(TMC2240) || HAS_DRIVER(TMC2660) \ || HAS_DRIVER(TMC5130) || HAS_DRIVER(TMC5160) ) #define HAS_TRINAMIC_CONFIG 1 #endif #define HAS_TRINAMIC HAS_TRINAMIC_CONFIG -#if ( HAS_DRIVER(TMC2130_STANDALONE) || HAS_DRIVER(TMC2160_STANDALONE) \ +#if ( HAS_DRIVER(TMC2100) \ + || HAS_DRIVER(TMC2130_STANDALONE) || HAS_DRIVER(TMC2160_STANDALONE) \ || HAS_DRIVER(TMC2208_STANDALONE) || HAS_DRIVER(TMC2209_STANDALONE) \ - || HAS_DRIVER(TMC2660_STANDALONE) || HAS_DRIVER(TMC5130_STANDALONE) \ - || HAS_DRIVER(TMC5160_STANDALONE) ) + || HAS_DRIVER(TMC2660_STANDALONE) \ + || HAS_DRIVER(TMC5130_STANDALONE) || HAS_DRIVER(TMC5160_STANDALONE) ) #define HAS_TRINAMIC_STANDALONE 1 #endif #if HAS_DRIVER(TMC2130) || HAS_DRIVER(TMC2160) || HAS_DRIVER(TMC5130) || HAS_DRIVER(TMC5160) #define HAS_TMCX1X0 1 #endif - +#if HAS_TMCX1X0 || HAS_DRIVER(TMC2240) + #define HAS_TMCX1X0_OR_2240 1 +#endif #if HAS_DRIVER(TMC2208) || HAS_DRIVER(TMC2209) #define HAS_TMC220x 1 #endif +//#if HAS_TMC_220x || HAS_DRIVER(TMC2240) +// #define HAS_TMC22xx 1 +//#endif +//#if HAS_TMCX1X0 || HAS_TMC220x +// #define HAS_TMC_CS_ACTUAL 1 +//#endif +//#if HAS_TMCX1X0 || HAS_DRIVER(TMC2209) +// #define HAS_TMC_SG_RESULT 1 +//#endif #define AXIS_IS_TMC(A) ( AXIS_DRIVER_TYPE(A,TMC2130) || AXIS_DRIVER_TYPE(A,TMC2160) \ || AXIS_DRIVER_TYPE(A,TMC2208) || AXIS_DRIVER_TYPE(A,TMC2209) \ - || AXIS_DRIVER_TYPE(A,TMC2660) \ + || AXIS_DRIVER_TYPE(A,TMC2240) || AXIS_DRIVER_TYPE(A,TMC2660) \ || AXIS_DRIVER_TYPE(A,TMC5130) || AXIS_DRIVER_TYPE(A,TMC5160) ) #define AXIS_IS_TMC_CONFIG AXIS_IS_TMC @@ -128,8 +141,8 @@ // Test for a driver that uses SPI - this allows checking whether a _CS_ pin // is considered sensitive #define AXIS_HAS_SPI(A) ( AXIS_DRIVER_TYPE(A,TMC2130) || AXIS_DRIVER_TYPE(A,TMC2160) \ - || AXIS_DRIVER_TYPE(A,TMC2660) || AXIS_DRIVER_TYPE(A,TMC5130) \ - || AXIS_DRIVER_TYPE(A,TMC5160) ) + || AXIS_DRIVER_TYPE(A,TMC2240) || AXIS_DRIVER_TYPE(A,TMC2660) \ + || AXIS_DRIVER_TYPE(A,TMC5130) || AXIS_DRIVER_TYPE(A,TMC5160) ) #define AXIS_HAS_UART(A) ( AXIS_DRIVER_TYPE(A,TMC2208) || AXIS_DRIVER_TYPE(A,TMC2209) ) @@ -139,19 +152,21 @@ #define AXIS_HAS_SW_SERIAL(A) ( AXIS_HAS_UART(A) && !defined(A##_HARDWARE_SERIAL) ) #define AXIS_HAS_STALLGUARD(A) ( AXIS_DRIVER_TYPE(A,TMC2130) || AXIS_DRIVER_TYPE(A,TMC2160) \ - || AXIS_DRIVER_TYPE(A,TMC2209) \ + || AXIS_DRIVER_TYPE(A,TMC2209) || AXIS_DRIVER_TYPE(A,TMC2240) \ || AXIS_DRIVER_TYPE(A,TMC2660) \ || AXIS_DRIVER_TYPE(A,TMC5130) || AXIS_DRIVER_TYPE(A,TMC5160) ) #define AXIS_HAS_STEALTHCHOP(A) ( AXIS_DRIVER_TYPE(A,TMC2130) || AXIS_DRIVER_TYPE(A,TMC2160) \ || AXIS_DRIVER_TYPE(A,TMC2208) || AXIS_DRIVER_TYPE(A,TMC2209) \ + || AXIS_DRIVER_TYPE(A,TMC2240) \ || AXIS_DRIVER_TYPE(A,TMC5130) || AXIS_DRIVER_TYPE(A,TMC5160) ) #define AXIS_HAS_SG_RESULT(A) ( AXIS_DRIVER_TYPE(A,TMC2130) || AXIS_DRIVER_TYPE(A,TMC2160) \ - || AXIS_DRIVER_TYPE(A,TMC2208) || AXIS_DRIVER_TYPE(A,TMC2209) ) + || AXIS_DRIVER_TYPE(A,TMC2208) || AXIS_DRIVER_TYPE(A,TMC2209) \ + || AXIS_DRIVER_TYPE(A,TMC2240) ) #define AXIS_HAS_COOLSTEP(A) ( AXIS_DRIVER_TYPE(A,TMC2130) \ - || AXIS_DRIVER_TYPE(A,TMC2209) \ + || AXIS_DRIVER_TYPE(A,TMC2209) || AXIS_DRIVER_TYPE(A,TMC2240) \ || AXIS_DRIVER_TYPE(A,TMC5130) || AXIS_DRIVER_TYPE(A,TMC5160) ) #define _OR_EAH(N,T) || AXIS_HAS_##T(E##N) @@ -187,3 +202,17 @@ || HAS_DRIVER(TMC5130_STANDALONE) || HAS_DRIVER(TMC5160_STANDALONE) #define HAS_DIAG_PINS 1 #endif + +// Hybrid Threshold ranges +#define THRS_TMC2100 65535 +#define THRS_TMC2130 65535 +#define THRS_TMC2160 255 +#define THRS_TMC2208 255 +#define THRS_TMC2209 255 +#define THRS_TMC2240 255 +#define THRS_TMC2660 65535 +#define THRS_TMC5130 65535 +#define THRS_TMC5160 65535 + +#define _DRIVER_THRS(V) CAT(THRS_, V) +#define STEPPER_MAX_THRS(S) _DRIVER_THRS(S##_DRIVER_TYPE) diff --git a/Marlin/src/core/language.h b/Marlin/src/core/language.h index 82ed6cc549b2..00181b602fb5 100644 --- a/Marlin/src/core/language.h +++ b/Marlin/src/core/language.h @@ -57,8 +57,10 @@ // fr French // fr_na French without accents (DWIN T5UID1 touchscreen) // gl Galician +// hg Hinglish (Hindi in Latin script) // hr Croatian // hu Hungarian +// id Indonesian (Bahasa) // it Italian // jp_kana Japanese // ko_KR Korean (South Korea) @@ -88,6 +90,7 @@ #undef MACHINE_NAME #define MACHINE_NAME DEFAULT_MACHINE_NAME #endif +#define MACHINE_NAME_SUBST TERN(CONFIGURABLE_MACHINE_NAME, "$", MACHINE_NAME) #define MARLIN_WEBSITE_URL "marlinfw.org" @@ -135,6 +138,7 @@ #define STR_BUSY_PAUSED_FOR_USER "busy: paused for user" #define STR_BUSY_PAUSED_FOR_INPUT "busy: paused for input" #define STR_Z_MOVE_COMP "Z_move_comp" +#define STR_LINE_NO "Line: " #define STR_RESEND "Resend: " #define STR_UNKNOWN_COMMAND "Unknown command: \"" #define STR_ACTIVE_EXTRUDER "Active Extruder: " @@ -161,8 +165,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." @@ -190,6 +194,7 @@ #define STR_ERR_HOTEND_TOO_COLD "Hotend too cold" #define STR_ERR_EEPROM_WRITE "Error writing to EEPROM!" #define STR_ERR_EEPROM_CORRUPT "EEPROM Corrupt" +#define STR_EEPROM_INITIALIZED "EEPROM Initialized" #define STR_FILAMENT_CHANGE_HEAT_LCD "Press button to heat nozzle" #define STR_FILAMENT_CHANGE_INSERT_LCD "Insert filament and press button" @@ -207,6 +212,8 @@ #define STR_KILL_BUTTON "KILL button/pin" // temperature.cpp strings +#define STR_WAIT_FOR_HOTEND "Wait for hotend heating..." +#define STR_WAIT_FOR_BED "Wait for bed heating..." #define STR_PID_AUTOTUNE "PID Autotune" #define STR_PID_AUTOTUNE_START " start" #define STR_PID_BAD_HEATER_ID " failed! Bad heater id" @@ -227,6 +234,8 @@ #define STR_PID_DEBUG_INPUT ": Input " #define STR_PID_DEBUG_OUTPUT " Output " #define STR_INVALID_EXTRUDER_NUM " - Invalid extruder number !" + +// MPCTEMP strings #define STR_MPC_AUTOTUNE_START "MPC Autotune start for " STR_E #define STR_MPC_AUTOTUNE_INTERRUPTED "MPC Autotune interrupted!" #define STR_MPC_AUTOTUNE_FINISHED "MPC Autotune finished! Put the constants below into Configuration.h" @@ -235,6 +244,7 @@ #define STR_MPC_MEASURING_AMBIENT "Measuring ambient heatloss at " #define STR_MPC_TEMPERATURE_ERROR "Temperature error" +// Temperature Sensors #define STR_HEATER_BED "bed" #define STR_HEATER_CHAMBER "chamber" #define STR_COOLER "cooler" @@ -244,6 +254,7 @@ #define STR_REDUNDANT "redundant " #define STR_LASER_TEMP "laser temperature" +// Misc. Errors, Thermal Runaway #define STR_STOPPED_HEATER ", system stopped! Heater_ID: " #define STR_DETECTED_TEMP_B " (temp: " #define STR_DETECTED_TEMP_E ")" @@ -266,6 +277,7 @@ #define STR_DEBUG_COMMUNICATION "COMMUNICATION" #define STR_DEBUG_DETAIL "DETAIL" +// Password Security #define STR_PRINTER_LOCKED "Printer locked! (Unlock with M511 or LCD)" #define STR_WRONG_PASSWORD "Incorrect Password" #define STR_PASSWORD_TOO_LONG "Password too long" @@ -289,9 +301,11 @@ #define STR_MAX_ACCELERATION "Max Acceleration (units/s2)" #define STR_MAX_FEEDRATES "Max feedrates (units/s)" #define STR_ACCELERATION_P_R_T "Acceleration (units/s2) (P R T)" +#define STR_HOMING_FEEDRATE "Homing Feedrate" #define STR_TOOL_CHANGING "Tool-changing" #define STR_HOTEND_OFFSETS "Hotend offsets" #define STR_SERVO_ANGLES "Servo Angles" +#define STR_AUTOTEMP "Auto Temp Control" #define STR_HOTEND_PID "Hotend PID" #define STR_BED_PID "Bed PID" #define STR_CHAMBER_PID "Chamber PID" @@ -308,8 +322,9 @@ #define STR_FILAMENT_RUNOUT_SENSOR "Filament runout sensor" #define STR_DRIVER_STEPPING_MODE "Driver stepping mode" #define STR_STEPPER_DRIVER_CURRENT "Stepper driver current" +#define STR_HOMING_CURRENT "Homing Current (mA)" #define STR_HYBRID_THRESHOLD "Hybrid Threshold" -#define STR_STALLGUARD_THRESHOLD "StallGuard threshold" +#define STR_STALLGUARD_THRESHOLD "StallGuard Threshold" #define STR_HOME_OFFSET "Home offset" #define STR_SOFT_ENDSTOPS "Soft endstops" #define STR_MATERIAL_HEATUP "Material heatup parameters" @@ -321,6 +336,53 @@ #define STR_TEMPERATURE_UNITS "Temperature Units" #define STR_USER_THERMISTORS "User thermistors" #define STR_DELAYED_POWEROFF "Delayed poweroff" +#define STR_STORED_MACROS "Stored macros" + +// +// General axis names +// +#if HAS_X_AXIS + #define AXIS1_NAME 'X' +#endif +#if HAS_Y_AXIS + #define AXIS2_NAME 'Y' +#endif +#if HAS_Z_AXIS + #define AXIS3_NAME 'Z' +#endif +#define STR_X "X" +#define STR_Y "Y" +#define STR_Z "Z" +#define STR_E "E" +#if IS_KINEMATIC + #define STR_A "A" + #define STR_B "B" + #define STR_C "C" +#else + #define STR_A STR_X + #define STR_B STR_Y + #define STR_C STR_Z +#endif +#define STR_X2 STR_A "2" +#define STR_Y2 STR_B "2" +#define STR_Z2 STR_C "2" +#define STR_Z3 STR_C "3" +#define STR_Z4 STR_C "4" +#if ANY(HAS_REAL_X, IS_SCARA, DELTA) + #define STEPPER_A_NAME 'A' +#else + #define STEPPER_A_NAME 'X' +#endif +#if ANY(HAS_REAL_Y, IS_SCARA, DELTA, POLAR) + #define STEPPER_B_NAME 'B' +#else + #define STEPPER_B_NAME 'Y' +#endif +#if ANY(HAS_REAL_Z, DELTA) + #define STEPPER_C_NAME 'C' +#else + #define STEPPER_C_NAME 'Z' +#endif // // Endstop Names used by Endstops::report_states @@ -353,26 +415,7 @@ #define STR_Z_PROBE "z_probe" #define STR_PROBE_EN "probe_en" #define STR_FILAMENT "filament" - -// General axis names -#define STR_X "X" -#define STR_Y "Y" -#define STR_Z "Z" -#define STR_E "E" -#if IS_KINEMATIC - #define STR_A "A" - #define STR_B "B" - #define STR_C "C" -#else - #define STR_A "X" - #define STR_B "Y" - #define STR_C "Z" -#endif -#define STR_X2 "X2" -#define STR_Y2 "Y2" -#define STR_Z2 "Z2" -#define STR_Z3 "Z3" -#define STR_Z4 "Z4" +#define STR_CALIBRATION "calibration" // Extra Axis and Endstop Names #if HAS_I_AXIS diff --git a/Marlin/src/core/macros.h b/Marlin/src/core/macros.h index 784002c769c4..56272c253fbd 100644 --- a/Marlin/src/core/macros.h +++ b/Marlin/src/core/macros.h @@ -25,12 +25,6 @@ #define __has_include(...) 1 #endif -#define ABCE 4 -#define XYZE 4 -#define ABC 3 -#define XYZ 3 -#define XY 2 - #define _AXIS(A) (A##_AXIS) #define _FORCE_INLINE_ __attribute__((__always_inline__)) __inline__ @@ -55,19 +49,17 @@ #define CYCLES_PER_MICROSECOND (F_CPU / 1000000UL) // 16 or 20 on AVR #endif -// Nanoseconds per cycle -#define NANOSECONDS_PER_CYCLE (1000000000.0 / F_CPU) - // Macros to make a string from a macro #define STRINGIFY_(M) #M #define STRINGIFY(M) STRINGIFY_(M) +#define CHARIFY(M) STRINGIFY(M)[0] #define A(CODE) " " CODE "\n\t" #define L(CODE) CODE ":\n\t" // 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 @@ -83,18 +75,22 @@ #define CBI32(n,b) (n &= ~_BV32(b)) #define TBI32(N,B) (N ^= _BV32(B)) +// Macros for common maths operations #define cu(x) ({__typeof__(x) _x = (x); (_x)*(_x)*(_x);}) #define RADIANS(d) ((d)*float(M_PI)/180.0f) #define DEGREES(r) ((r)*180.0f/float(M_PI)) #define HYPOT2(x,y) (sq(x)+sq(y)) #define NORMSQ(x,y,z) (sq(x)+sq(y)+sq(z)) -#define CIRCLE_AREA(R) (float(M_PI) * sq(float(R))) +#define FLOAT_SQ(I) sq(float(I)) +#define CIRCLE_AREA(R) (float(M_PI) * FLOAT_SQ(R)) #define CIRCLE_CIRC(R) (2 * float(M_PI) * float(R)) #define SIGN(a) ({__typeof__(a) _a = (a); (_a>0)-(_a<0);}) #define IS_POWER_OF_2(x) ((x) && !((x) & ((x) - 1))) +#define FLIP(X) (X = !(X)) + // Macros to constrain values #ifdef __cplusplus @@ -196,8 +192,8 @@ #define ENABLED(V...) DO(ENA,&&,V) #define DISABLED(V...) DO(DIS,&&,V) #define ANY(V...) !DISABLED(V) -#define ALL ENABLED -#define NONE DISABLED +#define ALL(V...) ENABLED(V) +#define NONE(V...) DISABLED(V) #define COUNT_ENABLED(V...) DO(ENA,+,V) #define MANY(V...) (COUNT_ENABLED(V) > 1) @@ -205,19 +201,26 @@ #define TERN(O,A,B) _TERN(_ENA_1(O),B,A) // OPTION ? 'A' : 'B' #define TERN0(O,A) _TERN(_ENA_1(O),0,A) // OPTION ? 'A' : '0' #define TERN1(O,A) _TERN(_ENA_1(O),1,A) // OPTION ? 'A' : '1' -#define TERN_(O,A) _TERN(_ENA_1(O),,A) // OPTION ? 'A' : '' #define _TERN(E,V...) __TERN(_CAT(T_,E),V) // Prepend 'T_' to get 'T_0' or 'T_1' #define __TERN(T,V...) ___TERN(_CAT(_NO,T),V) // Prepend '_NO' to get '_NOT_0' or '_NOT_1' #define ___TERN(P,V...) THIRD(P,V) // If first argument has a comma, A. Else B. #define IF_DISABLED(O,A) TERN(O,,A) +// "Ternary" that emits or omits the given content +#define EMIT(V...) V +#define OMIT(...) +#define TERN_(O,A) TERF(O,EMIT)(A) // OPTION ? 'A' : '' ; Usage: TERN_(OPTION, EMITTHIS) + +// Call G(...) or swallow with OMIT(...) +#define TERF(O,G) _TERN(_ENA_1(O),OMIT,G) // OPTION ? 'G' : 'OMIT' ; Usage: TERF(OPTION, CALLTHIS)(ARGS...) + // Macros to conditionally emit array items and function arguments #define _OPTITEM(A...) A, -#define OPTITEM(O,A...) TERN_(O,DEFER4(_OPTITEM)(A)) +#define OPTITEM(O,A...) TERN_(O,DEFER(_OPTITEM)(A)) #define _OPTARG(A...) , A -#define OPTARG(O,A...) TERN_(O,DEFER4(_OPTARG)(A)) +#define OPTARG(O,A...) TERN_(O,DEFER(_OPTARG)(A)) #define _OPTCODE(A) A; -#define OPTCODE(O,A) TERN_(O,DEFER4(_OPTCODE)(A)) +#define OPTCODE(O,A) TERN_(O,DEFER(_OPTCODE)(A)) // Macros to avoid operations that aren't always optimized away (e.g., 'f + 0.0' and 'f * 1.0'). // Compiler flags -fno-signed-zeros -ffinite-math-only also cover 'f * 1.0', 'f - f', etc. @@ -253,6 +256,7 @@ // Array shorthand #define COUNT(a) (sizeof(a)/sizeof(*a)) #define ZERO(a) memset((void*)a,0,sizeof(a)) +#define OBJZERO(a) memset(&a,0,sizeof(a)) #define COPY(a,b) do{ \ static_assert(sizeof(a[0]) == sizeof(b[0]), "COPY: '" STRINGIFY(a) "' and '" STRINGIFY(b) "' types (sizes) don't match!"); \ memcpy(&a[0],&b[0],_MIN(sizeof(a),sizeof(b))); \ @@ -302,6 +306,12 @@ #define GANG_N_1(N,K) _GANG_N(N,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K) // Expansion of some list items +#define LIST_32(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,AA,BB,CC,DD,EE,FF,...) A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,AA,BB,CC,DD,EE,FF +#define LIST_31(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,AA,BB,CC,DD,EE,...) A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,AA,BB,CC,DD,EE +#define LIST_30(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,AA,BB,CC,DD,...) A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,AA,BB,CC,DD +#define LIST_29(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,AA,BB,CC,...) A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,AA,BB,CC +#define LIST_28(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,AA,BB,...) A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,TU,V,W,X,Y,Z,AA,BB +#define LIST_27(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,AA,...) A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,AA #define LIST_26(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,...) A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z #define LIST_25(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,...) A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y #define LIST_24(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,...) A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X @@ -542,6 +552,9 @@ #endif +// Limit an index to an array size +#define ALIM(I,ARR) _MIN(I, (signed)COUNT(ARR) - 1) + // Macros for adding #define INC_0 1 #define INC_1 2 @@ -564,6 +577,17 @@ #define INC_18 19 #define INC_19 20 #define INC_20 21 +#define INC_21 22 +#define INC_22 23 +#define INC_23 24 +#define INC_24 25 +#define INC_25 26 +#define INC_26 27 +#define INC_27 28 +#define INC_28 29 +#define INC_29 30 +#define INC_30 31 +#define INC_31 32 #define INCREMENT_(n) INC_##n #define INCREMENT(n) INCREMENT_(n) @@ -599,6 +623,23 @@ #define DEC_13 12 #define DEC_14 13 #define DEC_15 14 +#define DEC_16 15 +#define DEC_17 16 +#define DEC_18 17 +#define DEC_19 18 +#define DEC_20 19 +#define DEC_21 20 +#define DEC_22 21 +#define DEC_23 22 +#define DEC_24 23 +#define DEC_25 24 +#define DEC_26 25 +#define DEC_27 26 +#define DEC_28 27 +#define DEC_29 28 +#define DEC_30 29 +#define DEC_31 30 +#define DEC_32 31 #define DECREMENT_(n) DEC_##n #define DECREMENT(n) DECREMENT_(n) @@ -629,7 +670,7 @@ #define DEFER4(M) M EMPTY EMPTY EMPTY EMPTY()()()() // Force define expansion -#define EVAL EVAL16 +#define EVAL(V...) EVAL16(V) #define EVAL4096(V...) EVAL2048(EVAL2048(V)) #define EVAL2048(V...) EVAL1024(EVAL1024(V)) #define EVAL1024(V...) EVAL512(EVAL512(V)) @@ -653,11 +694,8 @@ #define IF_ELSE(TF) _IF_ELSE(_BOOL(TF)) #define _IF_ELSE(TF) _CAT(_IF_, TF) -#define _IF_1(V...) V _IF_1_ELSE -#define _IF_0(...) _IF_0_ELSE - -#define _IF_1_ELSE(...) -#define _IF_0_ELSE(V...) V +#define _IF_1(V...) V OMIT +#define _IF_0(...) EMIT #define HAS_ARGS(V...) _BOOL(FIRST(_END_OF_ARGUMENTS_ V)()) #define _END_OF_ARGUMENTS_() 0 diff --git a/Marlin/src/core/millis_t.h b/Marlin/src/core/millis_t.h index e7032a2e5579..1d3cc853b3a9 100644 --- a/Marlin/src/core/millis_t.h +++ b/Marlin/src/core/millis_t.h @@ -30,5 +30,7 @@ typedef uint32_t millis_t; #define MS_TO_SEC(N) millis_t((N)/1000UL) #define MS_TO_SEC_PRECISE(N) (float(N)/1000.0f) -#define PENDING(NOW,SOON) ((int32_t)(NOW-(SOON))<0) -#define ELAPSED(NOW,SOON) (!PENDING(NOW,SOON)) +constexpr bool _PENDING(const millis_t now, const millis_t when) { return int32_t(when - now) > 0; } +constexpr bool _PENDING(const millis_t now, const millis_t start, const millis_t interval) { return (now - start) < interval; } +#define PENDING(V...) _PENDING(V) +#define ELAPSED(V...) !_PENDING(V) diff --git a/Marlin/src/core/mstring.h b/Marlin/src/core/mstring.h index 31e8c8c6e6ec..3ed21950def3 100644 --- a/Marlin/src/core/mstring.h +++ b/Marlin/src/core/mstring.h @@ -143,13 +143,13 @@ class MString { // Set with format string and arguments, like printf template - MString& setf_P(PGM_P const fmt, Args... more) { SNPRINTF_P(str, SIZE, fmt, more...); debug(F("setf_P")); return *this; } + MString& setf_P(PGM_P const pfmt, Args... more) { SNPRINTF_P(str, SIZE, pfmt, more...); debug(F("setf_P")); return *this; } template - MString& setf(const char *fmt, Args... more) { SNPRINTF(str, SIZE, fmt, more...); debug(F("setf")); return *this; } + MString& setf(const char *fmt, Args... more) { SNPRINTF(str, SIZE, fmt, more...); debug(F("setf")); return *this; } template - MString& setf(FSTR_P const fmt, Args... more) { return setf_P(FTOP(fmt), more...); } + MString& setf(FSTR_P const ffmt, Args... more) { return setf_P(FTOP(ffmt), more...); } // Chainable String appenders MString& append() { debug(F("nil")); return *this; } // for macros that might emit no output @@ -158,16 +158,16 @@ class MString { MString& append_P(PGM_P const s) { int sz = length(); if (sz < SIZE) strlcpy_P(str + sz, s, SIZE - sz + 1); debug(F("pstring")); return *this; } MString& append(FSTR_P const f) { return append_P(FTOP(f)); } MString& append(const bool &b) { return append(b ? F("true") : F("false")); } - MString& append(const char c) { int sz = length(); if (sz < SIZE) { str[sz] = c; if (sz < SIZE - 1) str[sz + 1] = '\0'; } return *this; } + MString& append(const char c) { int sz = length(); if (sz < SIZE) { str[sz] = c; if (sz < SIZE - 1) str[sz + 1] = '\0'; } debug(F("char")); return *this; } #if ENABLED(FASTER_APPEND) - MString& append(const int8_t &i) { int sz = length(); SNPRINTF(&str[sz], SIZE - sz, "%d", i); return *this; } - MString& append(const short &i) { int sz = length(); SNPRINTF(&str[sz], SIZE - sz, "%d", i); return *this; } - MString& append(const int &i) { int sz = length(); SNPRINTF(&str[sz], SIZE - sz, "%d", i); return *this; } - MString& append(const long &l) { int sz = length(); SNPRINTF(&str[sz], SIZE - sz, "%ld", l); return *this; } - MString& append(const unsigned char &i) { int sz = length(); SNPRINTF(&str[sz], SIZE - sz, "%u", i); return *this; } - MString& append(const unsigned short &i) { int sz = length(); SNPRINTF(&str[sz], SIZE - sz, "%u", i); return *this; } - MString& append(const unsigned int &i) { int sz = length(); SNPRINTF(&str[sz], SIZE - sz, "%u", i); return *this; } - MString& append(const unsigned long &l) { int sz = length(); SNPRINTF(&str[sz], SIZE - sz, "%lu", l); return *this; } + MString& append(const int8_t &i) { int sz = length(); SNPRINTF(&str[sz], SIZE - sz, "%d", i); debug(F("int8_t")); return *this; } + MString& append(const short &i) { int sz = length(); SNPRINTF(&str[sz], SIZE - sz, "%d", i); debug(F("short")); return *this; } + MString& append(const int &i) { int sz = length(); SNPRINTF(&str[sz], SIZE - sz, "%d", i); debug(F("int")); return *this; } + MString& append(const long &l) { int sz = length(); SNPRINTF(&str[sz], SIZE - sz, "%ld", l); debug(F("long")); return *this; } + MString& append(const unsigned char &i) { int sz = length(); SNPRINTF(&str[sz], SIZE - sz, "%u", i); debug(F("uchar")); return *this; } + MString& append(const unsigned short &i) { int sz = length(); SNPRINTF(&str[sz], SIZE - sz, "%u", i); debug(F("ushort")); return *this; } + MString& append(const unsigned int &i) { int sz = length(); SNPRINTF(&str[sz], SIZE - sz, "%u", i); debug(F("uint")); return *this; } + MString& append(const unsigned long &l) { int sz = length(); SNPRINTF(&str[sz], SIZE - sz, "%lu", l); debug(F("ulong")); return *this; } #else MString& append(const int8_t &i) { char buf[ 5]; sprintf(buf, "%d", i); return append(buf); } MString& append(const short &i) { char buf[12]; sprintf(buf, "%d", i); return append(buf); } @@ -206,9 +206,9 @@ class MString { MString& append(const spaces_t &s) { return append(repchr_t(' ', s.count)); } template - MString& appendf_P(PGM_P const fmt, Args... more) { + MString& appendf_P(PGM_P const pfmt, Args... more) { int sz = length(); - if (sz < SIZE) SNPRINTF_P(str + sz, SIZE - sz, fmt, more...); + if (sz < SIZE) SNPRINTF_P(str + sz, SIZE - sz, pfmt, more...); debug(F("appendf_P")); return *this; } @@ -280,12 +280,11 @@ class MString { // Quick hash to detect change (e.g., to avoid expensive drawing) typedef IF::type hash_t; hash_t hash() const { + const int sz = length(); #if ENABLED(DJB2_HASH) hash_t hval = 5381; - char c; - while ((c = *str++)) hval += (hval << 5) + c; // = hval * 33 + c + for (int i = 0; i < sz; i++) hval += (hval << 5) + str[i]; // = hval * 33 + c #else - const int sz = length(); hash_t hval = hash_t(sz); for (int i = 0; i < sz; i++) hval = ((hval << 1) | (hval >> 15)) ^ str[i]; // ROL, XOR #endif @@ -298,6 +297,9 @@ class MString { MString& clear() { return set(); } MString& eol() { return append('\n'); } MString& trunc(const int &i) { if (i <= SIZE) str[i] = '\0'; debug(F("trunc")); return *this; } + MString& ltrim() { char *s = str; while (*s == ' ') ++s; if (s != str) strcpy(str, s); return *this; } + MString& rtrim() { int s = length(); while (s && str[s - 1] == ' ') --s; str[s] = '\0'; return *this; } + MString& trim() { return rtrim().ltrim(); } // Truncate on a Unicode boundary MString& utrunc(const int &n=SIZE) { diff --git a/Marlin/src/core/multi_language.h b/Marlin/src/core/multi_language.h index 2c0eb7aa7297..81a1754d5ef4 100644 --- a/Marlin/src/core/multi_language.h +++ b/Marlin/src/core/multi_language.h @@ -91,6 +91,7 @@ typedef const char Language_Str[]; #define LANG_CHARSIZE GET_TEXT(CHARSIZE) #define USE_WIDE_GLYPH (LANG_CHARSIZE > 2) +// The final "\0" is added invisibly by the compiler #define MSG_1_LINE(A) A "\0" "\0" #define MSG_2_LINE(A,B) A "\0" B "\0" #define MSG_3_LINE(A,B,C) A "\0" B "\0" C diff --git a/Marlin/src/core/serial.cpp b/Marlin/src/core/serial.cpp index d6c4b55459de..852cfc77f67d 100644 --- a/Marlin/src/core/serial.cpp +++ b/Marlin/src/core/serial.cpp @@ -27,6 +27,8 @@ #include "../feature/ethernet.h" #endif +#include // dtostrf + // Echo commands to the terminal by default in dev mode uint8_t marlin_debug_flags = TERN(MARLIN_DEV_MODE, MARLIN_DEBUG_ECHO, MARLIN_DEBUG_NONE); @@ -75,8 +77,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'); } @@ -97,7 +99,7 @@ void SERIAL_WARN_START() { SERIAL_ECHO(F("Warning:")); } void SERIAL_ECHO_SP(uint8_t count) { count *= (PROPORTIONAL_FONT_RATIO); while (count--) SERIAL_CHAR(' '); } -void serial_offset(const_float_t v, const uint8_t sp/*=0*/) { +void serial_offset(const float v, const uint8_t sp/*=0*/) { if (v == 0 && sp == 1) SERIAL_CHAR(' '); else if (v > 0 || (v == 0 && sp == 2)) @@ -112,10 +114,6 @@ void serial_ternary(FSTR_P const pre, const bool onoff, FSTR_P const on, FSTR_P if (post) SERIAL_ECHO(post); } -void serialprint_onoff(const bool onoff) { SERIAL_ECHO(onoff ? F(STR_ON) : F(STR_OFF)); } -void serialprintln_onoff(const bool onoff) { serialprint_onoff(onoff); SERIAL_EOL(); } -void serialprint_truefalse(const bool tf) { SERIAL_ECHO(tf ? F("true") : F("false")); } - void print_bin(uint16_t val) { for (uint8_t i = 16; i--;) { SERIAL_CHAR('0' + TEST(val, i)); @@ -123,21 +121,23 @@ void print_bin(uint16_t val) { } } -void _print_xyz(NUM_AXIS_ARGS_(const_float_t) FSTR_P const prefix) { +void _print_xyz(NUM_AXIS_ARGS_(const float) FSTR_P const prefix) { if (prefix) SERIAL_ECHO(prefix); #if NUM_AXES - SERIAL_ECHOPGM_P( - LIST_N(DOUBLE(NUM_AXES), SP_X_STR, x, SP_Y_STR, y, SP_Z_STR, z, SP_I_STR, i, SP_J_STR, j, SP_K_STR, k, SP_U_STR, u, SP_V_STR, v, SP_W_STR, w) - ); + SERIAL_ECHOPGM_P(NUM_AXIS_PAIRED_LIST( + SP_X_STR, x, SP_Y_STR, y, SP_Z_STR, z, + SP_I_STR, i, SP_J_STR, j, SP_K_STR, k, + SP_U_STR, u, SP_V_STR, v, SP_W_STR, w + )); #endif } -void print_xyz(NUM_AXIS_ARGS_(const_float_t) FSTR_P const prefix/*=nullptr*/, FSTR_P const suffix/*=nullptr*/) { +void print_xyz(NUM_AXIS_ARGS_(const float) FSTR_P const prefix/*=nullptr*/, FSTR_P const suffix/*=nullptr*/) { _print_xyz(NUM_AXIS_LIST_(x, y, z, i, j, k, u, v, w) prefix); if (suffix) SERIAL_ECHO(suffix); else SERIAL_EOL(); } -void print_xyze(LOGICAL_AXIS_ARGS_(const_float_t) FSTR_P const prefix/*=nullptr*/, FSTR_P const suffix/*=nullptr*/) { +void print_xyze(LOGICAL_AXIS_ARGS_(const float) FSTR_P const prefix/*=nullptr*/, FSTR_P const suffix/*=nullptr*/) { _print_xyz(NUM_AXIS_LIST_(x, y, z, i, j, k, u, v, w) prefix); #if HAS_EXTRUDERS SERIAL_ECHOPGM_P(SP_E_STR, e); diff --git a/Marlin/src/core/serial.h b/Marlin/src/core/serial.h index f9b73e6d2667..6c73d72a2248 100644 --- a/Marlin/src/core/serial.h +++ b/Marlin/src/core/serial.h @@ -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 @@ -185,7 +185,7 @@ void SERIAL_ECHOLN(T arg1, Args ... args) { SERIAL_ECHO(arg1); SERIAL_ECHO(args // all the odd loose string elements as PROGMEM strings. // -// Print up to 20 pairs of values. Odd elements must be literal strings. +// Print pairs of values. Odd elements must be literal strings. #define __SEP_N(N,V...) _SEP_##N(V) #define _SEP_N(N,V...) __SEP_N(N,V) #define _SEP_N_REF() _SEP_N @@ -194,7 +194,7 @@ void SERIAL_ECHOLN(T arg1, Args ... args) { SERIAL_ECHO(arg1); SERIAL_ECHO(args #define _SEP_3(s,v,V...) _SEP_2(s,v); DEFER2(_SEP_N_REF)()(TWO_ARGS(V),V); #define SERIAL_ECHOPGM(V...) do{ EVAL(_SEP_N(TWO_ARGS(V),V)); }while(0) -// Print up to 20 pairs of values followed by newline. Odd elements must be literal strings. +// Print pairs of values followed by newline. Odd elements must be literal strings. #define __SELP_N(N,V...) _SELP_##N(V) #define _SELP_N(N,V...) __SELP_N(N,V) #define _SELP_N_REF() _SELP_N @@ -203,7 +203,7 @@ void SERIAL_ECHOLN(T arg1, Args ... args) { SERIAL_ECHO(arg1); SERIAL_ECHO(args #define _SELP_3(s,v,V...) _SEP_2(s,v); DEFER2(_SELP_N_REF)()(TWO_ARGS(V),V); #define SERIAL_ECHOLNPGM(V...) do{ EVAL(_SELP_N(TWO_ARGS(V),V)); }while(0) -// Print up to 20 pairs of values. Odd elements must be PSTR pointers. +// Print pairs of values. Odd elements must be PSTR pointers. #define __SEP_N_P(N,V...) _SEP_##N##_P(V) #define _SEP_N_P(N,V...) __SEP_N_P(N,V) #define _SEP_N_P_REF() _SEP_N_P @@ -212,7 +212,7 @@ void SERIAL_ECHOLN(T arg1, Args ... args) { SERIAL_ECHO(arg1); SERIAL_ECHO(args #define _SEP_3_P(p,v,V...) _SEP_2_P(p,v); DEFER2(_SEP_N_P_REF)()(TWO_ARGS(V),V); #define SERIAL_ECHOPGM_P(V...) do{ EVAL(_SEP_N_P(TWO_ARGS(V),V)); }while(0) -// Print up to 20 pairs of values followed by newline. Odd elements must be PSTR pointers. +// Print pairs of values followed by newline. Odd elements must be PSTR pointers. #define __SELP_N_P(N,V...) _SELP_##N##_P(V) #define _SELP_N_P(N,V...) __SELP_N_P(N,V) #define _SELP_N_P_REF() _SELP_N_P @@ -233,21 +233,21 @@ void serial_ternary(FSTR_P const pre, const bool onoff, FSTR_P const on, FSTR_P // Print up to 255 spaces void SERIAL_ECHO_SP(uint8_t count); -void serialprint_onoff(const bool onoff); -void serialprintln_onoff(const bool onoff); -void serialprint_truefalse(const bool tf); -void serial_offset(const_float_t v, const uint8_t sp=0); // For v==0 draw space (sp==1) or plus (sp==2) +inline FSTR_P const ON_OFF(const bool onoff) { return onoff ? F("ON") : F("OFF"); } +inline FSTR_P const TRUE_FALSE(const bool tf) { return tf ? F("true") : F("false"); } + +void serial_offset(const float v, const uint8_t sp=0); // For v==0 draw space (sp==1) or plus (sp==2) void print_bin(const uint16_t val); -void print_xyz(NUM_AXIS_ARGS_(const_float_t) FSTR_P const prefix=nullptr, FSTR_P const suffix=nullptr); +void print_xyz(NUM_AXIS_ARGS_(const float) FSTR_P const prefix=nullptr, FSTR_P const suffix=nullptr); inline void print_xyz(const xyz_pos_t &xyz, FSTR_P const prefix=nullptr, FSTR_P const suffix=nullptr) { print_xyz(NUM_AXIS_ELEM_(xyz) prefix, suffix); } -void print_xyze(LOGICAL_AXIS_ARGS_(const_float_t) FSTR_P const prefix=nullptr, FSTR_P const suffix=nullptr); +void print_xyze(LOGICAL_AXIS_ARGS_(const float) 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) @@ -271,13 +271,13 @@ class SString : public MString { SString& set() { super::set(); return *this; } template - SString& setf_P(PGM_P const fmt, Args... more) { super::setf_P(fmt, more...); return *this; } + SString& setf_P(PGM_P const pfmt, Args... more) { super::setf_P(pfmt, more...); return *this; } template - SString& setf(const char *fmt, Args... more) { super::setf(fmt, more...); return *this; } + SString& setf(const char *fmt, Args... more) { super::setf(fmt, more...); return *this; } template - SString& setf(FSTR_P const fmt, Args... more) { super::setf(fmt, more...); return *this; } + SString& setf(FSTR_P const ffmt, Args... more) { super::setf(ffmt, more...); return *this; } template SString& set(const T &v) { super::set(v); return *this; } diff --git a/Marlin/src/core/serial_base.h b/Marlin/src/core/serial_base.h index a2f49417b7dc..07bed55b35a9 100644 --- a/Marlin/src/core/serial_base.h +++ b/Marlin/src/core/serial_base.h @@ -220,7 +220,7 @@ struct SerialBase { // On non 2-complement CPU, there would be no possible representation for 2147483648. write('-'); } - printNumber_unsigned((uint_fixed_print_t)n , base); + printNumber_unsigned((uint_fixed_print_t)n, base); } // Print a decimal number @@ -228,7 +228,7 @@ struct SerialBase { // Handle negative numbers if (number < 0.0) { write('-'); - number = -number; + number *= -1; } // Round correctly so that print(1.999, 2) prints as "2.00" diff --git a/Marlin/src/core/types.h b/Marlin/src/core/types.h index e67644e52fc6..ca826b1914a7 100644 --- a/Marlin/src/core/types.h +++ b/Marlin/src/core/types.h @@ -42,33 +42,59 @@ template struct IF { 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 NUM_AXIS_CALL(G) do { NUM_AXIS_CODE(G(X_AXIS), G(Y_AXIS), G(Z_AXIS), G(I_AXIS), G(J_AXIS), G(K_AXIS), G(U_AXIS), G(V_AXIS), G(W_AXIS)); } while(0) #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 NUM_AXIS_ANY(x, y, z, i, j, k, u, v, w) (false \ + NUM_AXIS_GANG(|| (x), || (y), || (z), || (i), || (j), || (k), || (u), || (v), || (w))) +#define NUM_AXIS_ALL(x, y, z, i, j, k, u, v, w) ((NUM_AXES > 0) \ + NUM_AXIS_GANG(&& (x), && (y), && (z), && (i), && (j), && (k), && (u), && (v), && (w))) +#define NUM_AXIS_NONE(V...) !NUM_AXIS_ANY(V) + +#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) +#define LOGICAL_AXIS_MAP_LC(F) MAP(F, LOGICAL_AXIS_NAMES_LC) +#define LOGICAL_AXIS_CALL(G) do { LOGICAL_AXIS_CODE(G(E_AXIS), G(X_AXIS), G(Y_AXIS), G(Z_AXIS), G(I_AXIS), G(J_AXIS), G(K_AXIS), G(U_AXIS), G(V_AXIS), G(W_AXIS)); } while(0) #define STR_AXES_LOGICAL LOGICAL_AXIS_GANG("E", "X", "Y", "Z", STR_I, STR_J, STR_K, STR_U, STR_V, STR_W) +#define LOGICAL_AXIS_ANY(e, x, y, z, i, j, k, u, v, w) (false \ + LOGICAL_AXIS_GANG(|| (e), || (x), || (y), || (z), || (i), || (j), || (k), || (u), || (v), || (w))) +#define LOGICAL_AXIS_ALL(e, x, y, z, i, j, k, u, v, w) ((LOGICAL_AXES > 0) \ + LOGICAL_AXIS_GANG(&& (e), && (x), && (y), && (z), && (i), && (j), && (k), && (u), && (v), && (w))) +#define LOGICAL_AXIS_NONE(V...) !LOGICAL_AXIS_ANY(V) + +#define NUM_AXIS_PAIRED_LIST(V...) LIST_N(DOUBLE(NUM_AXES), V) +#define LOGICAL_AXIS_PAIRED_LIST(EA,EB,V...) NUM_AXIS_PAIRED_LIST(V) LIST_PAIR_E(EA, EB) #if NUM_AXES #define NUM_AXES_SEP , #define MAIN_AXIS_MAP(F) MAP(F, MAIN_AXIS_NAMES) - #define OPTARGS_NUM(T) , NUM_AXIS_ARGS(T) - #define OPTARGS_LOGICAL(T) , LOGICAL_AXIS_ARGS(T) + #define MAIN_AXIS_MAP_LC(F) MAP(F, MAIN_AXIS_NAMES_LC) + #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) + #define MAIN_AXIS_MAP_LC(F) #define OPTARGS_NUM(T) #define OPTARGS_LOGICAL(T) #endif @@ -76,10 +102,12 @@ template struct IF { 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 +// Currently we always have at least one Logical Axis (X or E) #if LOGICAL_AXES #define LOGICAL_AXES_SEP , #else @@ -89,14 +117,26 @@ template struct IF { 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 @@ -116,10 +156,14 @@ template struct IF { typedef L type; }; #define XY_ARRAY(V...) ARRAY_N(XY_COUNT, V) #define XY_CODE(V...) CODE_N(XY_COUNT, V) #define XY_GANG(V...) GANG_N(XY_COUNT, V) +#define XY_ANY(x,y) (false XY_GANG(|| (x), || (y))) +#define XY_ALL(x,y) ((NUM_AXES > 0) XY_GANG(&& (x), && (y))) #define XYZ_LIST(V...) LIST_N(XYZ_COUNT, V) #define XYZ_ARRAY(V...) ARRAY_N(XYZ_COUNT, V) #define XYZ_CODE(V...) CODE_N(XYZ_COUNT, V) #define XYZ_GANG(V...) GANG_N(XYZ_COUNT, V) +#define XYZ_ANY(x,y,z) (false XYZ_GANG(|| (x), || (y), || (z))) +#define XYZ_ALL(x,y,z) ((NUM_AXES > 0) XYZ_GANG(&& (x), && (y), && (z))) #if HAS_ROTATIONAL_AXES #define ROTATIONAL_AXIS_GANG(V...) GANG_N(ROTATIONAL_AXES, V) @@ -134,17 +178,35 @@ template struct IF { typedef L type; }; #define CODE_ITEM_E(N) N #endif #define GANG_ITEM_E(N) N + #define LIST_PAIR_E(A,B) LIST_ITEM_E(A), B #else #define LIST_ITEM_E(N) #define CODE_ITEM_E(N) #define GANG_ITEM_E(N) + #define LIST_PAIR_E(A,B) +#endif + +// Emitters for code that only cares about XYZE and not IJKUVW +#define CARTES_COUNT TERN(HAS_EXTRUDERS, INCREMENT(XYZ_COUNT), XYZ_COUNT) +#define CARTES_LIST(x,y,z,e) XYZ_LIST(x,y,z) LIST_ITEM_E(e) +#define CARTES_PAIRED_LIST(V...) LIST_N(DOUBLE(CARTES_COUNT), V) +#define CARTES_ARRAY(x,y,z,e) { CARTES_LIST(x,y,z,e) } +#define CARTES_CODE(x,y,z,e) XYZ_CODE(x,y,z) CODE_ITEM_E(e) +#define CARTES_GANG(x,y,z,e) XYZ_GANG(x,y,z) GANG_ITEM_E(e) +#define CARTES_AXIS_NAMES CARTES_LIST(X,Y,Z,E) +#define CARTES_AXIS_NAMES_LC CARTES_LIST(x,y,z,e) +#define CARTES_MAP(F) MAP(F, CARTES_AXIS_NAMES) +#if CARTES_COUNT + #define CARTES_COMMA , +#else + #define CARTES_COMMA #endif #define AXIS_COLLISION(L) (AXIS4_NAME == L || AXIS5_NAME == L || AXIS6_NAME == L || AXIS7_NAME == L || AXIS8_NAME == L || AXIS9_NAME == L) // Helpers #define _RECIP(N) ((N) ? 1.0f / static_cast(N) : 0.0f) -#define _ABS(N) ((N) < 0 ? -(N) : (N)) +#define _ABS(N) ((N) < decltype(N)(0) ? -(N) : (N)) #define _LS(N) T(uint32_t(N) << p) #define _RS(N) T(uint32_t(N) >> p) #define _LSE(N) N = T(uint32_t(N) << p) @@ -152,36 +214,110 @@ template struct IF { 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 +class BitProxy; + +// Define a template for a bit field of N bits, using the smallest type that can hold N bits +template 64)> +struct Flags; + +// Flag bits for <= 64 states template -struct Flags { - typedef uvalue_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; +struct Flags { + typedef bits_t(N) flagbits_t; + 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; } + + FI Flags& operator|=(Flags &p) const { b |= p.b; return *this; } + FI Flags& operator&=(Flags &p) const { b &= p.b; return *this; } + FI Flags& operator^=(Flags &p) const { b ^= p.b; return *this; } + + FI Flags& operator|=(const flagbits_t &p) { b |= flagbits_t(p); return *this; } + FI Flags& operator&=(const flagbits_t &p) { b &= flagbits_t(p); return *this; } + FI Flags& operator^=(const flagbits_t &p) { b ^= flagbits_t(p); return *this; } + + FI Flags operator|(Flags &p) const { return Flags(b | p.b); } + FI Flags operator&(Flags &p) const { return Flags(b & p.b); } + FI Flags operator^(Flags &p) const { return Flags(b ^ p.b); } + FI Flags operator~() const { return Flags(~b); } + + FI flagbits_t operator|(const flagbits_t &p) const { return b | flagbits_t(p); } + FI flagbits_t operator&(const flagbits_t &p) const { return b & flagbits_t(p); } + FI flagbits_t operator^(const flagbits_t &p) const { return b ^ flagbits_t(p); } + +}; + +// Flag bits for more than 64 states +template +struct Flags { + 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); } @@ -211,15 +347,15 @@ 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; // // Enumerated axis indices // -// - X_AXIS, Y_AXIS, and Z_AXIS should be used for axes in Cartesian space -// - A_AXIS, B_AXIS, and C_AXIS should be used for Steppers, corresponding to XYZ on Cartesians -// - X_HEAD, Y_HEAD, and Z_HEAD should be used for Steppers on Core kinematics +// - X_REAL, Y_REAL, and Z_REAL should be used for axes in Cartesian space +// - A_AXIS, B_AXIS, and C_AXIS should be used for Steppers +// - X_AXIS, Y_AXIS, and Z_AXIS are now more generic interchangeble indexes // enum AxisEnum : uint8_t { @@ -231,16 +367,26 @@ enum AxisEnum : uint8_t { #undef _EN_ITEM // Core also keeps toolhead directions - #if ANY(IS_CORE, MARKFORGED_XY, MARKFORGED_YX) - X_HEAD, Y_HEAD, Z_HEAD, + #if HAS_REAL_X + X_REAL, + #endif + #if HAS_REAL_Y + Y_REAL, + #endif + #if HAS_REAL_Z + Z_REAL, #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 E_AXIS = E0_AXIS, + E_REAL = E_AXIS, + #define _EN_REAL(N) E##N##_REAL = E##N##_AXIS, + REPEAT(EXTRUDERS, _EN_REAL) + #undef _EN_REAL #endif // A, B, and C are for DELTA, SCARA, etc. @@ -254,6 +400,35 @@ enum AxisEnum : uint8_t { C_AXIS = Z_AXIS, #endif + // Aliases to distinguish tool axes from stepper indexes + #if HAS_X_AXIS && !HAS_REAL_X + X_REAL = X_AXIS, + #endif + #if HAS_Y_AXIS && !HAS_REAL_Y + Y_REAL = Y_AXIS, + #endif + #if HAS_Z_AXIS && !HAS_REAL_Z + Z_REAL = Z_AXIS, + #endif + #if HAS_I_AXIS + I_REAL = I_AXIS, + #endif + #if HAS_J_AXIS + J_REAL = J_AXIS, + #endif + #if HAS_K_AXIS + K_REAL = K_AXIS, + #endif + #if HAS_U_AXIS + U_REAL = U_AXIS, + #endif + #if HAS_V_AXIS + V_REAL = V_AXIS, + #endif + #if HAS_W_AXIS + W_REAL = W_AXIS, + #endif + // To refer to all or none ALL_AXES_ENUM = 0xFE, NO_AXIS_ENUM = 0xFF }; @@ -268,29 +443,18 @@ enum AxisEnum : uint8_t { #define LOOP_DISTINCT_E(VAR) for (uint8_t VAR = 0; VAR < DISTINCT_E; ++VAR) // -// feedRate_t is just a humble float +// feedRate_t is just a humble float that can represent mm/s or mm/min // typedef float feedRate_t; // // celsius_t is the native unit of temperature. Signed to handle a disconnected thermistor value (-14). -// For more resolition (e.g., for a chocolate printer) this may later be changed to Celsius x 100 +// For more resolution (e.g., for a chocolate printer) this may later be changed to Celsius x 100 // typedef uint16_t raw_adc_t; typedef int16_t celsius_t; typedef float celsius_float_t; -// -// On AVR pointers are only 2 bytes so use 'const float &' for 'const float' -// -#ifdef __AVR__ - typedef const float & const_float_t; -#else - typedef const float const_float_t; -#endif -typedef const_float_t const_feedRate_t; -typedef const_float_t const_celsius_float_t; - // Type large enough to count leveling grid points typedef IF 255)), uint16_t, uint8_t>::type grid_count_t; @@ -299,7 +463,7 @@ typedef IF 255)), uint16_t, uint8_t>::ty #define MMS_TO_MMM(MM_S) (static_cast(MM_S) * 60.0f) // Packaged character for C macro and other usage -typedef struct SerialChar { char c; SerialChar(char n) : c(n) { } } serial_char_t; +typedef struct SerialChar { char c; SerialChar(const char n) : c(n) { } } serial_char_t; #define C(c) serial_char_t(c) // Packaged types: float with precision and/or width; a repeated space/character @@ -405,7 +569,7 @@ typedef ab_float_t ab_pos_t; typedef abc_float_t abc_pos_t; typedef abce_float_t abce_pos_t; -// External conversion methods +// External conversion methods (motion.h) void toLogical(xy_pos_t &raw); void toLogical(xyz_pos_t &raw); void toLogical(xyze_pos_t &raw); @@ -421,7 +585,9 @@ template struct XYval { union { struct { T x, y; }; + struct { T X, Y; }; struct { T a, b; }; + struct { T A, B; }; T pos[2]; }; @@ -434,9 +600,9 @@ struct XYval { #endif #if HAS_Y_AXIS FI void set(const T px, const T py) { x = px; y = py; } - FI void set(const T (&arr)[XY]) { x = arr[0]; y = arr[1]; } + FI void set(const T (&arr)[2]) { x = arr[0]; y = arr[1]; } #endif - #if NUM_AXES > XY + #if NUM_AXES > 2 FI void set(const T (&arr)[NUM_AXES]) { x = arr[0]; y = arr[1]; } #endif #if LOGICAL_AXES > NUM_AXES @@ -447,9 +613,9 @@ struct XYval { #endif // Length reduced to one dimension - FI constexpr T magnitude() const { return (T)sqrtf(x*x + y*y); } + FI constexpr T magnitude() const { return (T)SQRT(x*x + y*y); } // Pointer to the data as a simple array - FI operator T* () { return pos; } + explicit FI operator T* () { return pos; } // If any element is true then it's true FI constexpr operator bool() const { return x || y; } // Smallest element @@ -458,13 +624,18 @@ struct XYval { FI constexpr T large() const { return _MAX(x, y); } // Explicit copy and copies with conversion - FI constexpr XYval copy() const { return *this; } - FI constexpr XYval ABS() const { return { T(_ABS(x)), T(_ABS(y)) }; } - FI constexpr XYval asInt() const { return { int16_t(x), int16_t(y) }; } - FI constexpr XYval asLong() const { return { int32_t(x), int32_t(y) }; } - FI constexpr XYval ROUNDL() const { return { int32_t(LROUND(x)), int32_t(LROUND(y)) }; } - FI constexpr XYval asFloat() const { return { static_cast(x), static_cast(y) }; } - FI constexpr XYval reciprocal() const { return { _RECIP(x), _RECIP(y) }; } + FI constexpr XYval copy() const { return *this; } + FI constexpr XYval ABS() const { return { T(_ABS(x)), T(_ABS(y)) }; } + FI constexpr XYval ROUNDL() const { return { int32_t(LROUND(x)), int32_t(LROUND(y)) }; } + FI constexpr XYval reciprocal() const { return { _RECIP(x), _RECIP(y) }; } + + // Conversion to other types + FI constexpr XYval asInt16() const { return { int16_t(x), int16_t(y) }; } + FI constexpr XYval asInt32() const { return { int32_t(x), int32_t(y) }; } + FI constexpr XYval asUInt32() const { return { uint32_t(x), uint32_t(y) }; } + FI constexpr XYval asInt64() const { return { int64_t(x), int64_t(y) }; } + FI constexpr XYval asUInt64() const { return { uint64_t(x), uint64_t(y) }; } + FI constexpr XYval asFloat() const { return { float(x), float(y) }; } // Marlin workspace shifting is done with G92 and M206 FI XYval asLogical() const { XYval o = asFloat(); toLogical(o); return o; } @@ -484,11 +655,11 @@ struct XYval { FI XYval& operator= (const XYZEval &rs) { set(XY_LIST(rs.x, rs.y)); return *this; } // Override other operators to get intuitive behaviors - #define XY_OP(OP) { x TERN_(HAS_X_AXIS, OP rs.x), y TERN_(HAS_Y_AXIS, OP rs.y) } - FI constexpr XYval operator+ (const XYval &rs) const { return { x + rs.x, y + rs.y }; } - FI constexpr XYval operator- (const XYval &rs) const { return { x - rs.x, y - rs.y }; } - FI constexpr XYval operator* (const XYval &rs) const { return { x * rs.x, y * rs.y }; } - FI constexpr XYval operator/ (const XYval &rs) const { return { x / rs.x, y / rs.y }; } + #define XY_OP(OP) { T(x TERN_(HAS_X_AXIS, OP rs.x)), T(y TERN_(HAS_Y_AXIS, OP rs.y)) } + FI constexpr XYval operator+ (const XYval &rs) const { return { T(x + rs.x), T(y + rs.y) }; } + FI constexpr XYval operator- (const XYval &rs) const { return { T(x - rs.x), T(y - rs.y) }; } + FI constexpr XYval operator* (const XYval &rs) const { return { T(x * rs.x), T(y * rs.y) }; } + FI constexpr XYval operator/ (const XYval &rs) const { return { T(x / rs.x), T(y / rs.y) }; } FI constexpr XYval operator+ (const XYZval &rs) const { return { XY_OP(+) }; } FI constexpr XYval operator- (const XYZval &rs) const { return { XY_OP(-) }; } FI constexpr XYval operator* (const XYZval &rs) const { return { XY_OP(*) }; } @@ -531,11 +702,31 @@ struct XYval { // Exact comparisons. For floats a "NEAR" operation may be better. FI bool operator==(const XYval &rs) const { return x == rs.x && y == rs.y; } - FI bool operator==(const XYZval &rs) const { return ENABLED(HAS_X_AXIS) XY_GANG(&& x == rs.x, && y == rs.y); } - FI bool operator==(const XYZEval &rs) const { return ENABLED(HAS_X_AXIS) XY_GANG(&& x == rs.x, && y == rs.y); } + FI bool operator==(const XYZval &rs) const { return XY_ALL(x == rs.x, y == rs.y); } + FI bool operator==(const XYZEval &rs) const { return XY_ALL(x == rs.x, y == rs.y); } FI bool operator!=(const XYval &rs) const { return !operator==(rs); } FI bool operator!=(const XYZval &rs) const { return !operator==(rs); } FI bool operator!=(const XYZEval &rs) const { return !operator==(rs); } + + // Exact comparison to a single value + FI bool operator==(const T &p) const { return x == p && y == p; } + FI bool operator!=(const T &p) const { return !operator==(p); } + + FI bool operator< (const XYval &rs) const { return x < rs.x && y < rs.y; } + FI bool operator<=(const XYval &rs) const { return x <= rs.x && y <= rs.y; } + FI bool operator> (const XYval &rs) const { return x > rs.x && y > rs.y; } + FI bool operator>=(const XYval &rs) const { return x >= rs.x && y >= rs.y; } + + FI bool operator< (const XYZval &rs) const { return XY_ALL(x < rs.x, y < rs.y); } + FI bool operator<=(const XYZval &rs) const { return XY_ALL(x <= rs.x, y <= rs.y); } + FI bool operator> (const XYZval &rs) const { return XY_ALL(x > rs.x, y > rs.y); } + FI bool operator>=(const XYZval &rs) const { return XY_ALL(x >= rs.x, y >= rs.y); } + + FI bool operator< (const XYZEval &rs) const { return XY_ALL(x < rs.x, y < rs.y); } + FI bool operator<=(const XYZEval &rs) const { return XY_ALL(x <= rs.x, y <= rs.y); } + FI bool operator> (const XYZEval &rs) const { return XY_ALL(x > rs.x, y > rs.y); } + FI bool operator>=(const XYZEval &rs) const { return XY_ALL(x >= rs.x, y >= rs.y); } + }; // @@ -547,7 +738,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]; }; @@ -556,71 +749,76 @@ struct XYZval { FI void reset() { NUM_AXIS_CODE(x = 0, y = 0, z = 0, i = 0, j = 0, k = 0, u = 0, v = 0, w = 0); } // Setters taking struct types and arrays - FI void set(const XYval pxy) { XY_CODE(x = pxy.x, y = pxy.y); } - FI void set(const XYval pxy, const T pz) { XYZ_CODE(x = pxy.x, y = pxy.y, z = pz); } + FI void set(const XYval &pxy) { XY_CODE(x = pxy.x, y = pxy.y); } + FI void set(const XYval &pxy, const T pz) { XYZ_CODE(x = pxy.x, y = pxy.y, z = pz); } 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 - FI void set(const T px) { x = px; } + FI void set(const T px) { x = px; } #endif #if HAS_Z_AXIS - FI void set(const T px, const T py) { x = px; y = py; } + FI void set(const T px, const T py) { set(px); y = py; } #endif #if HAS_I_AXIS - FI void set(const T px, const T py, const T pz) { x = px; y = py; z = pz; } + FI void set(const T px, const T py, const T pz) { set(px, py); z = pz; } #endif #if HAS_J_AXIS - FI void set(const T px, const T py, const T pz, const T pi) { x = px; y = py; z = pz; i = pi; } + FI void set(const T px, const T py, const T pz, const T pi) { set(px, py, pz); i = pi; } #endif #if HAS_K_AXIS - FI void set(const T px, const T py, const T pz, const T pi, const T pj) { x = px; y = py; z = pz; i = pi; j = pj; } + FI void set(const T px, const T py, const T pz, const T pi, const T pj) { set(px, py, pz, pi); j = pj; } #endif #if HAS_U_AXIS - FI void set(const T px, const T py, const T pz, const T pi, const T pj, const T pk) { x = px; y = py; z = pz; i = pi; j = pj; k = pk; } + FI void set(const T px, const T py, const T pz, const T pi, const T pj, const T pk) { set(px, py, pz, pi, pj); k = pk; } #endif #if HAS_V_AXIS - FI void set(const T px, const T py, const T pz, const T pi, const T pj, const T pk, const T pu) { x = px; y = py; z = pz; i = pi; j = pj; k = pk; u = pu; } + FI void set(const T px, const T py, const T pz, const T pi, const T pj, const T pk, const T pu) { set(px, py, pz, pi, pj, pk); u = pu; } #endif #if HAS_W_AXIS - FI void set(const T px, const T py, const T pz, const T pi, const T pj, const T pk, const T pu, const T pv) { x = px; y = py; z = pz; i = pi; j = pj; k = pk; u = pu; v = pv; } + FI void set(const T px, const T py, const T pz, const T pi, const T pj, const T pk, const T pu, const T pv) { set(px, py, pz, pi, pj, pk, pu); v = pv; } #endif // Length reduced to one dimension - FI constexpr T magnitude() const { return (T)TERN(HAS_X_AXIS, sqrtf(NUM_AXIS_GANG(x*x, + y*y, + z*z, + i*i, + j*j, + k*k, + u*u, + v*v, + w*w)), 0); } + FI constexpr T magnitude() const { return (T)TERN(HAS_X_AXIS, SQRT(NUM_AXIS_GANG(x*x, + y*y, + z*z, + i*i, + j*j, + k*k, + u*u, + v*v, + w*w)), 0); } // Pointer to the data as a simple array - FI operator T* () { return pos; } + explicit FI operator T* () { return pos; } // If any element is true then it's true - FI constexpr operator bool() const { return 0 NUM_AXIS_GANG(|| x, || y, || z, || i, || j, || k, || u, || v, || w); } + FI constexpr operator bool() const { return NUM_AXIS_ANY(x, y, z, i, j, k, u, v, w); } // Smallest element - FI constexpr T small() const { return TERN(HAS_X_AXIS, _MIN(NUM_AXIS_LIST(x, y, z, i, j, k, u, v, w)), 0); } + FI constexpr T small() const { return TERN0(HAS_X_AXIS, _MIN(NUM_AXIS_LIST(x, y, z, i, j, k, u, v, w))); } // Largest element - FI constexpr T large() const { return TERN(HAS_X_AXIS, _MAX(NUM_AXIS_LIST(x, y, z, i, j, k, u, v, w)), 0); } + FI constexpr T large() const { return TERN0(HAS_X_AXIS, _MAX(NUM_AXIS_LIST(x, y, z, i, j, k, u, v, w))); } // Explicit copy and copies with conversion FI constexpr XYZval copy() const { XYZval o = *this; return o; } FI constexpr XYZval ABS() const { return NUM_AXIS_ARRAY(T(_ABS(x)), T(_ABS(y)), T(_ABS(z)), T(_ABS(i)), T(_ABS(j)), T(_ABS(k)), T(_ABS(u)), T(_ABS(v)), T(_ABS(w))); } - FI constexpr XYZval asInt() const { return NUM_AXIS_ARRAY(int16_t(x), int16_t(y), int16_t(z), int16_t(i), int16_t(j), int16_t(k), int16_t(u), int16_t(v), int16_t(w)); } - FI constexpr XYZval asLong() const { return NUM_AXIS_ARRAY(int32_t(x), int32_t(y), int32_t(z), int32_t(i), int32_t(j), int32_t(k), int32_t(u), int32_t(v), int32_t(w)); } FI constexpr XYZval ROUNDL() const { return NUM_AXIS_ARRAY(int32_t(LROUND(x)), int32_t(LROUND(y)), int32_t(LROUND(z)), int32_t(LROUND(i)), int32_t(LROUND(j)), int32_t(LROUND(k)), int32_t(LROUND(u)), int32_t(LROUND(v)), int32_t(LROUND(w))); } - FI constexpr XYZval asFloat() const { return NUM_AXIS_ARRAY(static_cast(x), static_cast(y), static_cast(z), static_cast(i), static_cast(j), static_cast(k), static_cast(u), static_cast(v), static_cast(w)); } FI constexpr XYZval reciprocal() const { return NUM_AXIS_ARRAY(_RECIP(x), _RECIP(y), _RECIP(z), _RECIP(i), _RECIP(j), _RECIP(k), _RECIP(u), _RECIP(v), _RECIP(w)); } + // Conversion to other types + FI constexpr XYZval asInt16() const { return NUM_AXIS_ARRAY(int16_t(x), int16_t(y), int16_t(z), int16_t(i), int16_t(j), int16_t(k), int16_t(u), int16_t(v), int16_t(w)); } + FI constexpr XYZval asInt32() const { return NUM_AXIS_ARRAY(int32_t(x), int32_t(y), int32_t(z), int32_t(i), int32_t(j), int32_t(k), int32_t(u), int32_t(v), int32_t(w)); } + FI constexpr XYZval asUInt32() const { return NUM_AXIS_ARRAY(uint32_t(x), uint32_t(y), uint32_t(z), uint32_t(i), uint32_t(j), uint32_t(k), uint32_t(u), uint32_t(v), uint32_t(w)); } + FI constexpr XYZval asInt64() const { return NUM_AXIS_ARRAY(int64_t(x), int64_t(y), int64_t(z), int64_t(i), int64_t(j), int64_t(k), int64_t(u), int64_t(v), int64_t(w)); } + FI constexpr XYZval asUInt64() const { return NUM_AXIS_ARRAY(uint64_t(x), uint64_t(y), uint64_t(z), uint64_t(i), uint64_t(j), uint64_t(k), uint64_t(u), uint64_t(v), uint64_t(w)); } + FI constexpr XYZval asFloat() const { return NUM_AXIS_ARRAY(float(x), float(y), float(z), float(i), float(j), float(k), float(u), float(v), float(w)); } + // Marlin workspace shifting is done with G92 and M206 FI XYZval asLogical() const { XYZval o = asFloat(); toLogical(o); return o; } FI XYZval asNative() const { XYZval o = asFloat(); toNative(o); return o; } - // In-place cast to types having fewer fields + // In-place reinterpret-cast to types having fewer fields FI operator XYval&() { return *(XYval*)this; } FI operator const XYval&() const { return *(const XYval*)this; } @@ -634,24 +832,24 @@ struct XYZval { // Assignment operator overrides do the expected thing FI XYZval& operator= (const T v) { set(ARRAY_N_1(NUM_AXES, v)); return *this; } FI XYZval& operator= (const XYval &rs) { set(rs.x, rs.y); return *this; } - FI XYZval& operator= (const XYZEval &rs) { set(NUM_AXIS_ELEM(rs)); return *this; } + FI XYZval& operator= (const XYZEval &rs) { set(NUM_AXIS_ELEM_LC(rs)); return *this; } // Override other operators to get intuitive behaviors - FI constexpr XYZval operator+ (const XYval &rs) const { return NUM_AXIS_ARRAY(x + rs.x, y + rs.y, z, i, j, k, u, v, w ); } - FI constexpr XYZval operator- (const XYval &rs) const { return NUM_AXIS_ARRAY(x - rs.x, y - rs.y, z, i, j, k, u, v, w ); } - FI constexpr XYZval operator* (const XYval &rs) const { return NUM_AXIS_ARRAY(x * rs.x, y * rs.y, z, i, j, k, u, v, w ); } - FI constexpr XYZval operator/ (const XYval &rs) const { return NUM_AXIS_ARRAY(x / rs.x, y / rs.y, z, i, j, k, u, v, w ); } - FI constexpr XYZval operator+ (const XYZval &rs) const { return NUM_AXIS_ARRAY(x + rs.x, y + rs.y, z + rs.z, i + rs.i, j + rs.j, k + rs.k, u + rs.u, v + rs.v, w + rs.w ); } - FI constexpr XYZval operator- (const XYZval &rs) const { return NUM_AXIS_ARRAY(x - rs.x, y - rs.y, z - rs.z, i - rs.i, j - rs.j, k - rs.k, u - rs.u, v - rs.v, w - rs.w ); } - FI constexpr XYZval operator* (const XYZval &rs) const { return NUM_AXIS_ARRAY(x * rs.x, y * rs.y, z * rs.z, i * rs.i, j * rs.j, k * rs.k, u * rs.u, v * rs.v, w * rs.w ); } - FI constexpr XYZval operator/ (const XYZval &rs) const { return NUM_AXIS_ARRAY(x / rs.x, y / rs.y, z / rs.z, i / rs.i, j / rs.j, k / rs.k, u / rs.u, v / rs.v, w / rs.w ); } - FI constexpr XYZval operator+ (const XYZEval &rs) const { return NUM_AXIS_ARRAY(x + rs.x, y + rs.y, z + rs.z, i + rs.i, j + rs.j, k + rs.k, u + rs.u, v + rs.v, w + rs.w ); } - FI constexpr XYZval operator- (const XYZEval &rs) const { return NUM_AXIS_ARRAY(x - rs.x, y - rs.y, z - rs.z, i - rs.i, j - rs.j, k - rs.k, u - rs.u, v - rs.v, w - rs.w ); } - FI constexpr XYZval operator* (const XYZEval &rs) const { return NUM_AXIS_ARRAY(x * rs.x, y * rs.y, z * rs.z, i * rs.i, j * rs.j, k * rs.k, u * rs.u, v * rs.v, w * rs.w ); } - FI constexpr XYZval operator/ (const XYZEval &rs) const { return NUM_AXIS_ARRAY(x / rs.x, y / rs.y, z / rs.z, i / rs.i, j / rs.j, k / rs.k, u / rs.u, v / rs.v, w / rs.w ); } - FI constexpr XYZval operator* (const float &p) const { return NUM_AXIS_ARRAY((T)(x * p), (T)(y * p), (T)(z * p), (T)(i * p), (T)(j * p), (T)(k * p), (T)(u * p), (T)(v * p), (T)(w * p)); } + FI constexpr XYZval operator+ (const XYval &rs) const { return NUM_AXIS_ARRAY(T(x + rs.x), T(y + rs.y), z, i, j, k, u, v, w ); } + FI constexpr XYZval operator- (const XYval &rs) const { return NUM_AXIS_ARRAY(T(x - rs.x), T(y - rs.y), z, i, j, k, u, v, w ); } + FI constexpr XYZval operator* (const XYval &rs) const { return NUM_AXIS_ARRAY(T(x * rs.x), T(y * rs.y), z, i, j, k, u, v, w ); } + FI constexpr XYZval operator/ (const XYval &rs) const { return NUM_AXIS_ARRAY(T(x / rs.x), T(y / rs.y), z, i, j, k, u, v, w ); } + FI constexpr XYZval operator+ (const XYZval &rs) const { return NUM_AXIS_ARRAY(T(x + rs.x), T(y + rs.y), T(z + rs.z), T(i + rs.i), T(j + rs.j), T(k + rs.k), T(u + rs.u), T(v + rs.v), T(w + rs.w) ); } + FI constexpr XYZval operator- (const XYZval &rs) const { return NUM_AXIS_ARRAY(T(x - rs.x), T(y - rs.y), T(z - rs.z), T(i - rs.i), T(j - rs.j), T(k - rs.k), T(u - rs.u), T(v - rs.v), T(w - rs.w) ); } + FI constexpr XYZval operator* (const XYZval &rs) const { return NUM_AXIS_ARRAY(T(x * rs.x), T(y * rs.y), T(z * rs.z), T(i * rs.i), T(j * rs.j), T(k * rs.k), T(u * rs.u), T(v * rs.v), T(w * rs.w) ); } + FI constexpr XYZval operator/ (const XYZval &rs) const { return NUM_AXIS_ARRAY(T(x / rs.x), T(y / rs.y), T(z / rs.z), T(i / rs.i), T(j / rs.j), T(k / rs.k), T(u / rs.u), T(v / rs.v), T(w / rs.w) ); } + FI constexpr XYZval operator+ (const XYZEval &rs) const { return NUM_AXIS_ARRAY(T(x + rs.x), T(y + rs.y), T(z + rs.z), T(i + rs.i), T(j + rs.j), T(k + rs.k), T(u + rs.u), T(v + rs.v), T(w + rs.w) ); } + FI constexpr XYZval operator- (const XYZEval &rs) const { return NUM_AXIS_ARRAY(T(x - rs.x), T(y - rs.y), T(z - rs.z), T(i - rs.i), T(j - rs.j), T(k - rs.k), T(u - rs.u), T(v - rs.v), T(w - rs.w) ); } + FI constexpr XYZval operator* (const XYZEval &rs) const { return NUM_AXIS_ARRAY(T(x * rs.x), T(y * rs.y), T(z * rs.z), T(i * rs.i), T(j * rs.j), T(k * rs.k), T(u * rs.u), T(v * rs.v), T(w * rs.w) ); } + FI constexpr XYZval operator/ (const XYZEval &rs) const { return NUM_AXIS_ARRAY(T(x / rs.x), T(y / rs.y), T(z / rs.z), T(i / rs.i), T(j / rs.j), T(k / rs.k), T(u / rs.u), T(v / rs.v), T(w / rs.w) ); } + FI constexpr XYZval operator* (const float &p) const { return NUM_AXIS_ARRAY(T(x * p), T(y * p), T(z * p), T(i * p), T(j * p), T(k * p), T(u * p), T(v * p), T(w * p)); } FI constexpr XYZval operator* (const int &p) const { return NUM_AXIS_ARRAY(x * p, y * p, z * p, i * p, j * p, k * p, u * p, v * p, w * p); } - FI constexpr XYZval operator/ (const float &p) const { return NUM_AXIS_ARRAY((T)(x / p), (T)(y / p), (T)(z / p), (T)(i / p), (T)(j / p), (T)(k / p), (T)(u / p), (T)(v / p), (T)(w / p)); } + FI constexpr XYZval operator/ (const float &p) const { return NUM_AXIS_ARRAY(T(x / p), T(y / p), T(z / p), T(i / p), T(j / p), T(k / p), T(u / p), T(v / p), T(w / p)); } FI constexpr XYZval operator/ (const int &p) const { return NUM_AXIS_ARRAY(x / p, y / p, z / p, i / p, j / p, k / p, u / p, v / p, w / p); } FI constexpr XYZval operator>>(const int &p) const { return NUM_AXIS_ARRAY(_RS(x), _RS(y), _RS(z), _RS(i), _RS(j), _RS(k), _RS(u), _RS(v), _RS(w)); } FI constexpr XYZval operator<<(const int &p) const { return NUM_AXIS_ARRAY(_LS(x), _LS(y), _LS(z), _LS(i), _LS(j), _LS(k), _LS(u), _LS(v), _LS(w)); } @@ -659,7 +857,7 @@ struct XYZval { // Absolute difference between two objects FI constexpr XYZval diff(const XYZEval &rs) const { return NUM_AXIS_ARRAY(T(_ABS(x - rs.x)), T(_ABS(y - rs.y)), T(_ABS(z - rs.z)), T(_ABS(i - rs.i)), T(_ABS(j - rs.j)), T(_ABS(k - rs.k)), T(_ABS(u - rs.u)), T(_ABS(v - rs.v)), T(_ABS(w - rs.w)) ); } - FI constexpr XYZval diff(const XYZval &rs) const { return NUM_AXIS_ARRAY(T(_ABS(x - rs.x)), T(_ABS(y - rs.y)), T(_ABS(z - rs.z)), T(_ABS(i - rs.i)), T(_ABS(j - rs.j)), T(_ABS(k - rs.k)), T(_ABS(u - rs.u)), T(_ABS(v - rs.v)), T(_ABS(w - rs.w)) ); } + FI constexpr XYZval diff(const XYZval &rs) const { return NUM_AXIS_ARRAY(T(_ABS(x - rs.x)), T(_ABS(y - rs.y)), T(_ABS(z - rs.z)), T(_ABS(i - rs.i)), T(_ABS(j - rs.j)), T(_ABS(k - rs.k)), T(_ABS(u - rs.u)), T(_ABS(v - rs.v)), T(_ABS(w - rs.w)) ); } FI constexpr XYZval diff(const XYval &rs) const { return NUM_AXIS_ARRAY(T(_ABS(x - rs.x)), T(_ABS(y - rs.y)), z, i, j, k, u, v, w ); } // Modifier operators @@ -677,12 +875,28 @@ struct XYZval { FI XYZval& operator/=(const XYZEval &rs) { NUM_AXIS_CODE(x /= rs.x, y /= rs.y, z /= rs.z, i /= rs.i, j /= rs.j, k /= rs.k, u /= rs.u, v /= rs.v, w /= rs.w); return *this; } FI XYZval& operator*=(const float &p) { NUM_AXIS_CODE(x *= p, y *= p, z *= p, i *= p, j *= p, k *= p, u *= p, v *= p, w *= p); return *this; } FI XYZval& operator*=(const int &p) { NUM_AXIS_CODE(x *= p, y *= p, z *= p, i *= p, j *= p, k *= p, u *= p, v *= p, w *= p); return *this; } + FI XYZval& operator/=(const float &p) { NUM_AXIS_CODE(x /= p, y /= p, z /= p, i /= p, j /= p, k /= p, u /= p, v /= p, w /= p); return *this; } FI XYZval& operator>>=(const int &p) { NUM_AXIS_CODE(_RSE(x), _RSE(y), _RSE(z), _RSE(i), _RSE(j), _RSE(k), _RSE(u), _RSE(v), _RSE(w)); return *this; } FI XYZval& operator<<=(const int &p) { NUM_AXIS_CODE(_LSE(x), _LSE(y), _LSE(z), _LSE(i), _LSE(j), _LSE(k), _LSE(u), _LSE(v), _LSE(w)); return *this; } // Exact comparisons. For floats a "NEAR" operation may be better. - FI bool operator==(const XYZEval &rs) const { return true NUM_AXIS_GANG(&& x == rs.x, && y == rs.y, && z == rs.z, && i == rs.i, && j == rs.j, && k == rs.k, && u == rs.u, && v == rs.v, && w == rs.w); } + FI bool operator==(const XYZEval &rs) const { return NUM_AXIS_ALL(x == rs.x, y == rs.y, z == rs.z, i == rs.i, j == rs.j, k == rs.k, u == rs.u, v == rs.v, w == rs.w); } FI bool operator!=(const XYZEval &rs) const { return !operator==(rs); } + + // Exact comparison to a single value + FI bool operator==(const T &p) const { return NUM_AXIS_ALL(x == p, y == p, z == p, i == p, j == p, k == p, u == p, v == p, w == p); } + FI bool operator!=(const T &p) const { return !operator==(p); } + + FI bool operator< (const XYZval &rs) const { return NUM_AXIS_ALL(x < rs.x, y < rs.y, z < rs.z, i < rs.i, j < rs.j, k < rs.k, u < rs.u, v < rs.v, w < rs.w); } + FI bool operator<=(const XYZval &rs) const { return NUM_AXIS_ALL(x <= rs.x, y <= rs.y, z <= rs.z, i <= rs.i, j <= rs.j, k <= rs.k, u <= rs.u, v <= rs.v, w <= rs.w); } + FI bool operator> (const XYZval &rs) const { return NUM_AXIS_ALL(x > rs.x, y > rs.y, z > rs.z, i > rs.i, j > rs.j, k > rs.k, u > rs.u, v > rs.v, w > rs.w); } + FI bool operator>=(const XYZval &rs) const { return NUM_AXIS_ALL(x >= rs.x, y >= rs.y, z >= rs.z, i >= rs.i, j >= rs.j, k >= rs.k, u >= rs.u, v >= rs.v, w >= rs.w); } + + FI bool operator< (const XYZEval &rs) const { return NUM_AXIS_ALL(x < rs.x, y < rs.y, z < rs.z, i < rs.i, j < rs.j, k < rs.k, u < rs.u, v < rs.v, w < rs.w); } + FI bool operator<=(const XYZEval &rs) const { return NUM_AXIS_ALL(x <= rs.x, y <= rs.y, z <= rs.z, i <= rs.i, j <= rs.j, k <= rs.k, u <= rs.u, v <= rs.v, w <= rs.w); } + FI bool operator> (const XYZEval &rs) const { return NUM_AXIS_ALL(x > rs.x, y > rs.y, z > rs.z, i > rs.i, j > rs.j, k > rs.k, u > rs.u, v > rs.v, w > rs.w); } + FI bool operator>=(const XYZEval &rs) const { return NUM_AXIS_ALL(x >= rs.x, y >= rs.y, z >= rs.z, i >= rs.i, j >= rs.j, k >= rs.k, u >= rs.u, v >= rs.v, w >= rs.w); } + }; // @@ -693,82 +907,89 @@ struct XYZval { template 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 - FI void reset() { LOGICAL_AXIS_GANG(e =, x =, y =, z =, i =, j =, k =, u =, v =, w =) 0; } + FI void reset() { LOGICAL_AXIS_GANG(e =, x =, y =, z =, i =, j =, k =, u =, v =, w =) 0; } // Setters taking struct types and arrays - FI void set(const XYval pxy) { XY_CODE(x = pxy.x, y = pxy.y); } - FI void set(const XYval pxy, const T pz) { XYZ_CODE(x = pxy.x, y = pxy.y, z = pz); } - FI void set(const XYZval pxyz) { set(NUM_AXIS_ELEM(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]); } + FI void set(const XYval &pxy) { XY_CODE(x = pxy.x, y = pxy.y); } + FI void set(const XYval &pxy, const T pz) { XYZ_CODE(x = pxy.x, y = pxy.y, z = pz); } + FI void set(const XYZval &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 pxy, const T pz, const T pe) { set(pxy, pz); e = pe; } - FI void set(const XYZval 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(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 &pxy, const T pz, const T pe) { set(pxy, pz); e = pe; } + FI void set(const XYZval &pxyz, const T pe) { set(pxyz); e = pe; } + 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]); } + FI void set(const T (&arr)[DISTINCT_AXES], const uint8_t eindex) { LOGICAL_AXIS_CODE(e = arr[LOGICAL_AXES-1 + eindex], 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 FI void set(const T px) { x = px; } #endif #if HAS_Z_AXIS - FI void set(const T px, const T py) { x = px; y = py; } + FI void set(const T px, const T py) { set(px); y = py; } #endif #if HAS_I_AXIS - FI void set(const T px, const T py, const T pz) { x = px; y = py; z = pz; } + FI void set(const T px, const T py, const T pz) { set(px, py); z = pz; } #endif #if HAS_J_AXIS - FI void set(const T px, const T py, const T pz, const T pi) { x = px; y = py; z = pz; i = pi; } + FI void set(const T px, const T py, const T pz, const T pi) { set(px, py, pz); i = pi; } #endif #if HAS_K_AXIS - FI void set(const T px, const T py, const T pz, const T pi, const T pj) { x = px; y = py; z = pz; i = pi; j = pj; } + FI void set(const T px, const T py, const T pz, const T pi, const T pj) { set(px, py, pz, pi); j = pj; } #endif #if HAS_U_AXIS - FI void set(const T px, const T py, const T pz, const T pi, const T pj, const T pk) { x = px; y = py; z = pz; i = pi; j = pj; k = pk; } + FI void set(const T px, const T py, const T pz, const T pi, const T pj, const T pk) { set(px, py, pz, pi, pj); k = pk; } #endif #if HAS_V_AXIS - FI void set(const T px, const T py, const T pz, const T pi, const T pj, const T pk, const T pu) { x = px; y = py; z = pz; i = pi; j = pj; k = pk; u = pu; } + FI void set(const T px, const T py, const T pz, const T pi, const T pj, const T pk, const T pu) { set(px, py, pz, pi, pj, pk); u = pu; } #endif #if HAS_W_AXIS - FI void set(const T px, const T py, const T pz, const T pi, const T pj, const T pk, const T pu, const T pv) { x = px; y = py; z = pz; i = pi; j = pj; k = pk; u = pu; v = pv; } + FI void set(const T px, const T py, const T pz, const T pi, const T pj, const T pk, const T pu, const T pv) { set(px, py, pz, pi, pj, pk, pu); v = pv; } #endif // Length reduced to one dimension - FI constexpr T magnitude() const { return (T)sqrtf(LOGICAL_AXIS_GANG(+ e*e, + x*x, + y*y, + z*z, + i*i, + j*j, + k*k, + u*u, + v*v, + w*w)); } + FI constexpr T magnitude() const { return (T)SQRT(LOGICAL_AXIS_GANG(+ e*e, + x*x, + y*y, + z*z, + i*i, + j*j, + k*k, + u*u, + v*v, + w*w)); } // Pointer to the data as a simple array - FI operator T* () { return pos; } + explicit FI operator T* () { return pos; } // If any element is true then it's true - FI constexpr operator bool() const { return 0 LOGICAL_AXIS_GANG(|| e, || x, || y, || z, || i, || j, || k, || u, || v, || w); } + FI constexpr operator bool() const { return LOGICAL_AXIS_ANY(e, x, y, z, i, j, k, u, v, w); } // Smallest element FI constexpr T small() const { return _MIN(LOGICAL_AXIS_LIST(e, x, y, z, i, j, k, u, v, w)); } // Largest element FI constexpr T large() const { return _MAX(LOGICAL_AXIS_LIST(e, x, y, z, i, j, k, u, v, w)); } // Explicit copy and copies with conversion - FI constexpr XYZEval copy() const { XYZEval v = *this; return v; } - FI constexpr XYZEval ABS() const { return LOGICAL_AXIS_ARRAY(T(_ABS(e)), T(_ABS(x)), T(_ABS(y)), T(_ABS(z)), T(_ABS(i)), T(_ABS(j)), T(_ABS(k)), T(_ABS(u)), T(_ABS(v)), T(_ABS(w))); } - FI constexpr XYZEval asInt() const { return LOGICAL_AXIS_ARRAY(int16_t(e), int16_t(x), int16_t(y), int16_t(z), int16_t(i), int16_t(j), int16_t(k), int16_t(u), int16_t(v), int16_t(w)); } - FI constexpr XYZEval asLong() const { return LOGICAL_AXIS_ARRAY(int32_t(e), int32_t(x), int32_t(y), int32_t(z), int32_t(i), int32_t(j), int32_t(k), int32_t(u), int32_t(v), int32_t(w)); } - FI constexpr XYZEval ROUNDL() const { return LOGICAL_AXIS_ARRAY(int32_t(LROUND(e)), int32_t(LROUND(x)), int32_t(LROUND(y)), int32_t(LROUND(z)), int32_t(LROUND(i)), int32_t(LROUND(j)), int32_t(LROUND(k)), int32_t(LROUND(u)), int32_t(LROUND(v)), int32_t(LROUND(w))); } - FI constexpr XYZEval asFloat() const { return LOGICAL_AXIS_ARRAY(static_cast(e), static_cast(x), static_cast(y), static_cast(z), static_cast(i), static_cast(j), static_cast(k), static_cast(u), static_cast(v), static_cast(w)); } - FI constexpr XYZEval reciprocal() const { return LOGICAL_AXIS_ARRAY(_RECIP(e), _RECIP(x), _RECIP(y), _RECIP(z), _RECIP(i), _RECIP(j), _RECIP(k), _RECIP(u), _RECIP(v), _RECIP(w)); } + FI constexpr XYZEval copy() const { XYZEval v = *this; return v; } + FI constexpr XYZEval ABS() const { return LOGICAL_AXIS_ARRAY(T(_ABS(e)), T(_ABS(x)), T(_ABS(y)), T(_ABS(z)), T(_ABS(i)), T(_ABS(j)), T(_ABS(k)), T(_ABS(u)), T(_ABS(v)), T(_ABS(w))); } + FI constexpr XYZEval ROUNDL() const { return LOGICAL_AXIS_ARRAY(int32_t(LROUND(e)), int32_t(LROUND(x)), int32_t(LROUND(y)), int32_t(LROUND(z)), int32_t(LROUND(i)), int32_t(LROUND(j)), int32_t(LROUND(k)), int32_t(LROUND(u)), int32_t(LROUND(v)), int32_t(LROUND(w))); } + FI constexpr XYZEval reciprocal() const { return LOGICAL_AXIS_ARRAY(_RECIP(e), _RECIP(x), _RECIP(y), _RECIP(z), _RECIP(i), _RECIP(j), _RECIP(k), _RECIP(u), _RECIP(v), _RECIP(w)); } + + // Conversion to other types + FI constexpr XYZEval asInt16() const { return LOGICAL_AXIS_ARRAY(int16_t(e), int16_t(x), int16_t(y), int16_t(z), int16_t(i), int16_t(j), int16_t(k), int16_t(u), int16_t(v), int16_t(w)); } + FI constexpr XYZEval asInt32() const { return LOGICAL_AXIS_ARRAY(int32_t(e), int32_t(x), int32_t(y), int32_t(z), int32_t(i), int32_t(j), int32_t(k), int32_t(u), int32_t(v), int32_t(w)); } + FI constexpr XYZEval asUInt32() const { return LOGICAL_AXIS_ARRAY(uint32_t(e), uint32_t(x), uint32_t(y), uint32_t(z), uint32_t(i), uint32_t(j), uint32_t(k), uint32_t(u), uint32_t(v), uint32_t(w)); } + FI constexpr XYZEval asInt64() const { return LOGICAL_AXIS_ARRAY(int64_t(e), int64_t(x), int64_t(y), int64_t(z), int64_t(i), int64_t(j), int64_t(k), int64_t(u), int64_t(v), int64_t(w)); } + FI constexpr XYZEval asUInt64() const { return LOGICAL_AXIS_ARRAY(uint64_t(e), uint64_t(x), uint64_t(y), uint64_t(z), uint64_t(i), uint64_t(j), uint64_t(k), uint64_t(u), uint64_t(v), uint64_t(w)); } + FI constexpr XYZEval asFloat() const { return LOGICAL_AXIS_ARRAY(float(e), float(x), float(y), float(z), float(i), float(j), float(k), float(u), float(v), float(w)); } // Marlin workspace shifting is done with G92 and M206 FI XYZEval asLogical() const { XYZEval o = asFloat(); toLogical(o); return o; } FI XYZEval asNative() const { XYZEval o = asFloat(); toNative(o); return o; } - // In-place cast to types having fewer fields + // In-place reinterpret-cast to types having fewer fields FI operator XYval&() { return *(XYval*)this; } FI operator const XYval&() const { return *(const XYval*)this; } FI operator XYZval&() { return *(XYZval*)this; } @@ -781,24 +1002,27 @@ struct XYZEval { // Assignment operator overrides do the expected thing FI XYZEval& operator= (const T v) { set(LOGICAL_AXIS_LIST_1(v)); return *this; } FI XYZEval& operator= (const XYval &rs) { set(rs.x, rs.y); return *this; } - FI XYZEval& operator= (const XYZval &rs) { set(NUM_AXIS_ELEM(rs)); return *this; } + FI XYZEval& operator= (const XYZval &rs) { set(NUM_AXIS_ELEM_LC(rs)); return *this; } // Override other operators to get intuitive behaviors - FI constexpr XYZEval operator+ (const XYval &rs) const { return LOGICAL_AXIS_ARRAY(e, x + rs.x, y + rs.y, z, i, j, k, u, v, w); } - FI constexpr XYZEval operator- (const XYval &rs) const { return LOGICAL_AXIS_ARRAY(e, x - rs.x, y - rs.y, z, i, j, k, u, v, w); } - FI constexpr XYZEval operator* (const XYval &rs) const { return LOGICAL_AXIS_ARRAY(e, x * rs.x, y * rs.y, z, i, j, k, u, v, w); } - FI constexpr XYZEval operator/ (const XYval &rs) const { return LOGICAL_AXIS_ARRAY(e, x / rs.x, y / rs.y, z, i, j, k, u, v, w); } - FI constexpr XYZEval operator+ (const XYZval &rs) const { return LOGICAL_AXIS_ARRAY(e, x + rs.x, y + rs.y, z + rs.z, i + rs.i, j + rs.j, k + rs.k, u + rs.u, v + rs.v, w + rs.w); } - FI constexpr XYZEval operator- (const XYZval &rs) const { return LOGICAL_AXIS_ARRAY(e, x - rs.x, y - rs.y, z - rs.z, i - rs.i, j - rs.j, k - rs.k, u - rs.u, v - rs.v, w - rs.w); } - FI constexpr XYZEval operator* (const XYZval &rs) const { return LOGICAL_AXIS_ARRAY(e, x * rs.x, y * rs.y, z * rs.z, i * rs.i, j * rs.j, k * rs.k, u * rs.u, v * rs.v, w * rs.w); } - FI constexpr XYZEval operator/ (const XYZval &rs) const { return LOGICAL_AXIS_ARRAY(e, x / rs.x, y / rs.y, z / rs.z, i / rs.i, j / rs.j, k / rs.k, u / rs.u, v / rs.v, w / rs.w); } - FI constexpr XYZEval operator+ (const XYZEval &rs) const { return LOGICAL_AXIS_ARRAY(e + rs.e, x + rs.x, y + rs.y, z + rs.z, i + rs.i, j + rs.j, k + rs.k, u + rs.u, v + rs.v, w + rs.w); } - FI constexpr XYZEval operator- (const XYZEval &rs) const { return LOGICAL_AXIS_ARRAY(e - rs.e, x - rs.x, y - rs.y, z - rs.z, i - rs.i, j - rs.j, k - rs.k, u - rs.u, v - rs.v, w - rs.w); } - FI constexpr XYZEval operator* (const XYZEval &rs) const { return LOGICAL_AXIS_ARRAY(e * rs.e, x * rs.x, y * rs.y, z * rs.z, i * rs.i, j * rs.j, k * rs.k, u * rs.u, v * rs.v, w * rs.w); } - FI constexpr XYZEval operator/ (const XYZEval &rs) const { return LOGICAL_AXIS_ARRAY(e / rs.e, x / rs.x, y / rs.y, z / rs.z, i / rs.i, j / rs.j, k / rs.k, u / rs.u, v / rs.v, w / rs.w); } - FI constexpr XYZEval operator* (const float &p) const { return LOGICAL_AXIS_ARRAY((T)(e * p), (T)(x * p), (T)(y * p), (T)(z * p), (T)(i * p), (T)(j * p), (T)(k * p), (T)(u * p), (T)(v * p), (T)(w * p)); } + FI constexpr XYZEval operator+ (const XYval &rs) const { return LOGICAL_AXIS_ARRAY(e, T(x + rs.x), T(y + rs.y), z, i, j, k, u, v, w); } + FI constexpr XYZEval operator- (const XYval &rs) const { return LOGICAL_AXIS_ARRAY(e, T(x - rs.x), T(y - rs.y), z, i, j, k, u, v, w); } + FI constexpr XYZEval operator* (const XYval &rs) const { return LOGICAL_AXIS_ARRAY(e, T(x * rs.x), T(y * rs.y), z, i, j, k, u, v, w); } + FI constexpr XYZEval operator/ (const XYval &rs) const { return LOGICAL_AXIS_ARRAY(e, T(x / rs.x), T(y / rs.y), z, i, j, k, u, v, w); } + FI constexpr XYZEval operator+ (const XYZval &rs) const { return LOGICAL_AXIS_ARRAY(e, T(x + rs.x), T(y + rs.y), T(z + rs.z), T(i + rs.i), T(j + rs.j), T(k + rs.k), T(u + rs.u), T(v + rs.v), T(w + rs.w)); } + FI constexpr XYZEval operator- (const XYZval &rs) const { return LOGICAL_AXIS_ARRAY(e, T(x - rs.x), T(y - rs.y), T(z - rs.z), T(i - rs.i), T(j - rs.j), T(k - rs.k), T(u - rs.u), T(v - rs.v), T(w - rs.w)); } + FI constexpr XYZEval operator* (const XYZval &rs) const { return LOGICAL_AXIS_ARRAY(e, T(x * rs.x), T(y * rs.y), T(z * rs.z), T(i * rs.i), T(j * rs.j), T(k * rs.k), T(u * rs.u), T(v * rs.v), T(w * rs.w)); } + FI constexpr XYZEval operator/ (const XYZval &rs) const { return LOGICAL_AXIS_ARRAY(e, T(x / rs.x), T(y / rs.y), T(z / rs.z), T(i / rs.i), T(j / rs.j), T(k / rs.k), T(u / rs.u), T(v / rs.v), T(w / rs.w)); } + FI constexpr XYZEval operator+ (const XYZEval &rs) const { return LOGICAL_AXIS_ARRAY(T(e + rs.e), T(x + rs.x), T(y + rs.y), T(z + rs.z), T(i + rs.i), T(j + rs.j), T(k + rs.k), T(u + rs.u), T(v + rs.v), T(w + rs.w)); } + FI constexpr XYZEval operator- (const XYZEval &rs) const { return LOGICAL_AXIS_ARRAY(T(e - rs.e), T(x - rs.x), T(y - rs.y), T(z - rs.z), T(i - rs.i), T(j - rs.j), T(k - rs.k), T(u - rs.u), T(v - rs.v), T(w - rs.w)); } + FI constexpr XYZEval operator* (const XYZEval &rs) const { return LOGICAL_AXIS_ARRAY(T(e * rs.e), T(x * rs.x), T(y * rs.y), T(z * rs.z), T(i * rs.i), T(j * rs.j), T(k * rs.k), T(u * rs.u), T(v * rs.v), T(w * rs.w)); } + FI constexpr XYZEval operator/ (const XYZEval &rs) const { return LOGICAL_AXIS_ARRAY(T(e / rs.e), T(x / rs.x), T(y / rs.y), T(z / rs.z), T(i / rs.i), T(j / rs.j), T(k / rs.k), T(u / rs.u), T(v / rs.v), T(w / rs.w)); } + FI constexpr XYZEval operator+ (const uint32_t &p) const { return LOGICAL_AXIS_ARRAY(T(e + p), T(x + p), T(y + p), T(z + p), T(i + p), T(j + p), T(k + p), T(u + p), T(v + p), T(w + p)); } + FI constexpr XYZEval operator* (const float &p) const { return LOGICAL_AXIS_ARRAY(T(e * p), T(x * p), T(y * p), T(z * p), T(i * p), T(j * p), T(k * p), T(u * p), T(v * p), T(w * p)); } + FI constexpr XYZEval operator* (const uint32_t &p) const { return LOGICAL_AXIS_ARRAY(T(e * p), T(x * p), T(y * p), T(z * p), T(i * p), T(j * p), T(k * p), T(u * p), T(v * p), T(w * p)); } + FI constexpr XYZEval operator& (const int64_t &p) const { return LOGICAL_AXIS_ARRAY(T(e & p), T(x & p), T(y & p), T(z & p), T(i & p), T(j & p), T(k & p), T(u & p), T(v & p), T(w & p)); } FI constexpr XYZEval operator* (const int &p) const { return LOGICAL_AXIS_ARRAY(e * p, x * p, y * p, z * p, i * p, j * p, k * p, u * p, v * p, w * p); } - FI constexpr XYZEval operator/ (const float &p) const { return LOGICAL_AXIS_ARRAY((T)(e / p), (T)(x / p), (T)(y / p), (T)(z / p), (T)(i / p), (T)(j / p), (T)(k / p), (T)(u / p), (T)(v / p), (T)(w / p)); } + FI constexpr XYZEval operator/ (const float &p) const { return LOGICAL_AXIS_ARRAY(T(e / p), T(x / p), T(y / p), T(z / p), T(i / p), T(j / p), T(k / p), T(u / p), T(v / p), T(w / p)); } FI constexpr XYZEval operator/ (const int &p) const { return LOGICAL_AXIS_ARRAY(e / p, x / p, y / p, z / p, i / p, j / p, k / p, u / p, v / p, w / p); } FI constexpr XYZEval operator>>(const int &p) const { return LOGICAL_AXIS_ARRAY(_RS(e), _RS(x), _RS(y), _RS(z), _RS(i), _RS(j), _RS(k), _RS(u), _RS(v), _RS(w)); } FI constexpr XYZEval operator<<(const int &p) const { return LOGICAL_AXIS_ARRAY(_LS(e), _LS(x), _LS(y), _LS(z), _LS(i), _LS(j), _LS(k), _LS(u), _LS(v), _LS(w)); } @@ -806,8 +1030,8 @@ struct XYZEval { // Absolute difference between two objects FI constexpr XYZEval diff(const XYZEval &rs) const { return LOGICAL_AXIS_ARRAY(T(_ABS(e - rs.e)), T(_ABS(x - rs.x)), T(_ABS(y - rs.y)), T(_ABS(z - rs.z)), T(_ABS(i - rs.i)), T(_ABS(j - rs.j)), T(_ABS(k - rs.k)), T(_ABS(u - rs.u)), T(_ABS(v - rs.v)), T(_ABS(w - rs.w)) ); } - FI constexpr XYZEval diff(const XYZval &rs) const { return LOGICAL_AXIS_ARRAY(0 , T(_ABS(x - rs.x)), T(_ABS(y - rs.y)), T(_ABS(z - rs.z)), T(_ABS(i - rs.i)), T(_ABS(j - rs.j)), T(_ABS(k - rs.k)), T(_ABS(u - rs.u)), T(_ABS(v - rs.v)), T(_ABS(w - rs.w)) ); } - FI constexpr XYZEval diff(const XYval &rs) const { return LOGICAL_AXIS_ARRAY(0 , T(_ABS(x - rs.x)), T(_ABS(y - rs.y)), z, i, j, k, u, v, w ); } + FI constexpr XYZEval diff(const XYZval &rs) const { return LOGICAL_AXIS_ARRAY(0, T(_ABS(x - rs.x)), T(_ABS(y - rs.y)), T(_ABS(z - rs.z)), T(_ABS(i - rs.i)), T(_ABS(j - rs.j)), T(_ABS(k - rs.k)), T(_ABS(u - rs.u)), T(_ABS(v - rs.v)), T(_ABS(w - rs.w)) ); } + FI constexpr XYZEval diff(const XYval &rs) const { return LOGICAL_AXIS_ARRAY(0, T(_ABS(x - rs.x)), T(_ABS(y - rs.y)), z, i, j, k, u, v, w ); } // Modifier operators FI XYZEval& operator+=(const XYval &rs) { XY_CODE(x += rs.x, y += rs.y); return *this; } @@ -822,60 +1046,83 @@ struct XYZEval { FI XYZEval& operator-=(const XYZEval &rs) { LOGICAL_AXIS_CODE(e -= rs.e, x -= rs.x, y -= rs.y, z -= rs.z, i -= rs.i, j -= rs.j, k -= rs.k, u -= rs.u, v -= rs.v, w -= rs.w); return *this; } FI XYZEval& operator*=(const XYZEval &rs) { LOGICAL_AXIS_CODE(e *= rs.e, x *= rs.x, y *= rs.y, z *= rs.z, i *= rs.i, j *= rs.j, k *= rs.k, u *= rs.u, v *= rs.v, w *= rs.w); return *this; } FI XYZEval& operator/=(const XYZEval &rs) { LOGICAL_AXIS_CODE(e /= rs.e, x /= rs.x, y /= rs.y, z /= rs.z, i /= rs.i, j /= rs.j, k /= rs.k, u /= rs.u, v /= rs.v, w /= rs.w); return *this; } + FI XYZEval& operator+=(const T &p) { LOGICAL_AXIS_CODE(e += p, x += p, y += p, z += p, i += p, j += p, k += p, u += p, v += p, w += p); return *this; } + FI XYZEval& operator-=(const T &p) { LOGICAL_AXIS_CODE(e -= p, x -= p, y -= p, z -= p, i -= p, j -= p, k -= p, u -= p, v -= p, w -= p); return *this; } FI XYZEval& operator*=(const T &p) { LOGICAL_AXIS_CODE(e *= p, x *= p, y *= p, z *= p, i *= p, j *= p, k *= p, u *= p, v *= p, w *= p); return *this; } + FI XYZEval& operator/=(const T &p) { LOGICAL_AXIS_CODE(e /= p, x /= p, y /= p, z /= p, i /= p, j /= p, k /= p, u /= p, v /= p, w /= p); return *this; } FI XYZEval& operator>>=(const int &p) { LOGICAL_AXIS_CODE(_RSE(e), _RSE(x), _RSE(y), _RSE(z), _RSE(i), _RSE(j), _RSE(k), _RSE(u), _RSE(v), _RSE(w)); return *this; } FI XYZEval& operator<<=(const int &p) { LOGICAL_AXIS_CODE(_LSE(e), _LSE(x), _LSE(y), _LSE(z), _LSE(i), _LSE(j), _LSE(k), _LSE(u), _LSE(v), _LSE(w)); return *this; } // Exact comparisons. For floats a "NEAR" operation may be better. - FI bool operator==(const XYZval &rs) const { return true NUM_AXIS_GANG(&& x == rs.x, && y == rs.y, && z == rs.z, && i == rs.i, && j == rs.j, && k == rs.k, && u == rs.u, && v == rs.v, && w == rs.w); } - FI bool operator==(const XYZEval &rs) const { return true LOGICAL_AXIS_GANG(&& e == rs.e, && x == rs.x, && y == rs.y, && z == rs.z, && i == rs.i, && j == rs.j, && k == rs.k, && u == rs.u, && v == rs.v, && w == rs.w); } + FI bool operator==(const XYZval &rs) const { return NUM_AXIS_ALL(x == rs.x, y == rs.y, z == rs.z, i == rs.i, j == rs.j, k == rs.k, u == rs.u, v == rs.v, w == rs.w); } + FI bool operator==(const XYZEval &rs) const { return LOGICAL_AXIS_ALL(e == rs.e, x == rs.x, y == rs.y, z == rs.z, i == rs.i, j == rs.j, k == rs.k, u == rs.u, v == rs.v, w == rs.w); } FI bool operator!=(const XYZval &rs) const { return !operator==(rs); } FI bool operator!=(const XYZEval &rs) const { return !operator==(rs); } + + // Exact comparison to a single value + FI bool operator==(const T &p) const { return LOGICAL_AXIS_ALL(e == p, x == p, y == p, z == p, i == p, j == p, k == p, u == p, v == p, w == p); } + FI bool operator!=(const T &p) const { return !operator==(p); } + + FI bool operator< (const XYZEval &rs) const { return LOGICAL_AXIS_ALL(e < rs.e, x < rs.x, y < rs.y, z < rs.z, i < rs.i, j < rs.j, k < rs.k, u < rs.u, v < rs.v, w < rs.w); } + FI bool operator<=(const XYZEval &rs) const { return LOGICAL_AXIS_ALL(e <= rs.e, x <= rs.x, y <= rs.y, z <= rs.z, i <= rs.i, j <= rs.j, k <= rs.k, u <= rs.u, v <= rs.v, w <= rs.w); } + FI bool operator> (const XYZEval &rs) const { return LOGICAL_AXIS_ALL(e > rs.e, x > rs.x, y > rs.y, z > rs.z, i > rs.i, j > rs.j, k > rs.k, u > rs.u, v > rs.v, w > rs.w); } + FI bool operator>=(const XYZEval &rs) const { return LOGICAL_AXIS_ALL(e >= rs.e, x >= rs.x, y >= rs.y, z >= rs.z, i >= rs.i, j >= rs.j, k >= rs.k, u >= rs.u, v >= rs.v, w >= rs.w); } + + FI bool operator< (const XYZval &rs) const { return NUM_AXIS_ALL(x < rs.x, y < rs.y, z < rs.z, i < rs.i, j < rs.j, k < rs.k, u < rs.u, v < rs.v, w < rs.w); } + FI bool operator<=(const XYZval &rs) const { return NUM_AXIS_ALL(x <= rs.x, y <= rs.y, z <= rs.z, i <= rs.i, j <= rs.j, k <= rs.k, u <= rs.u, v <= rs.v, w <= rs.w); } + FI bool operator> (const XYZval &rs) const { return NUM_AXIS_ALL(x > rs.x, y > rs.y, z > rs.z, i > rs.i, j > rs.j, k > rs.k, u > rs.u, v > rs.v, w > rs.w); } + FI bool operator>=(const XYZval &rs) const { return NUM_AXIS_ALL(x >= rs.x, y >= rs.y, z >= rs.z, i >= rs.i, j >= rs.j, k >= rs.k, u >= rs.u, v >= rs.v, w >= rs.w); } + }; #include // for memset +// +// Axis indexed arrays of type T (x[SIZE], y[SIZE], etc.) +// template struct XYZarray { typedef T el[SIZE]; 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); } - FI void set(const int n, const XYval p) { NUM_AXIS_CODE(x[n]=p.x, y[n]=p.y,,,,,,,); } - FI void set(const int n, const XYZval p) { NUM_AXIS_CODE(x[n]=p.x, y[n]=p.y, z[n]=p.z, i[n]=p.i, j[n]=p.j, k[n]=p.k, u[n]=p.u, v[n]=p.v, w[n]=p.w ); } - FI void set(const int n, const XYZEval p) { NUM_AXIS_CODE(x[n]=p.x, y[n]=p.y, z[n]=p.z, i[n]=p.i, j[n]=p.j, k[n]=p.k, u[n]=p.u, v[n]=p.v, w[n]=p.w ); } + FI void set(const int n, const XYval &p) { NUM_AXIS_CODE(x[n]=p.x, y[n]=p.y,,,,,,,); } + FI void set(const int n, const XYZval &p) { NUM_AXIS_CODE(x[n]=p.x, y[n]=p.y, z[n]=p.z, i[n]=p.i, j[n]=p.j, k[n]=p.k, u[n]=p.u, v[n]=p.v, w[n]=p.w ); } + FI void set(const int n, const XYZEval &p) { NUM_AXIS_CODE(x[n]=p.x, y[n]=p.y, z[n]=p.z, i[n]=p.i, j[n]=p.j, k[n]=p.k, u[n]=p.u, v[n]=p.v, w[n]=p.w ); } // 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); } // Setters with fewer elements leave the rest untouched #if HAS_Y_AXIS - FI void set(const int n, const T px) { x[n] = px; } + FI void set(const int n, const T px) { x[n] = px; } #endif #if HAS_Z_AXIS - FI void set(const int n, const T px, const T py) { x[n] = px; y[n] = py; } + FI void set(const int n, const T px, const T py) { set(n, px); y[n] = py; } #endif #if HAS_I_AXIS - FI void set(const int n, const T px, const T py, const T pz) { x[n] = px; y[n] = py; z[n] = pz; } + FI void set(const int n, const T px, const T py, const T pz) { set(n, px, py); z[n] = pz; } #endif #if HAS_J_AXIS - FI void set(const int n, const T px, const T py, const T pz, const T pi) { x[n] = px; y[n] = py; z[n] = pz; i[n] = pi; } + FI void set(const int n, const T px, const T py, const T pz, const T pi) { set(n, px, py, pz); i[n] = pi; } #endif #if HAS_K_AXIS - FI void set(const int n, const T px, const T py, const T pz, const T pi, const T pj) { x[n] = px; y[n] = py; z[n] = pz; i[n] = pi; j[n] = pj; } + FI void set(const int n, const T px, const T py, const T pz, const T pi, const T pj) { set(n, px, py, pz, pi); j[n] = pj; } #endif #if HAS_U_AXIS - FI void set(const int n, const T px, const T py, const T pz, const T pi, const T pj, const T pk) { x[n] = px; y[n] = py; z[n] = pz; i[n] = pi; j[n] = pj; k[n] = pk; } + FI void set(const int n, const T px, const T py, const T pz, const T pi, const T pj, const T pk) { set(n, px, py, pz, pi, pj); k[n] = pk; } #endif #if HAS_V_AXIS - FI void set(const int n, const T px, const T py, const T pz, const T pi, const T pj, const T pk, const T pu) { x[n] = px; y[n] = py; z[n] = pz; i[n] = pi; j[n] = pj; k[n] = pk; u[n] = pu; } + FI void set(const int n, const T px, const T py, const T pz, const T pi, const T pj, const T pk, const T pu) { set(n, px, py, pz, pi, pj, pk); u[n] = pu; } #endif #if HAS_W_AXIS - FI void set(const int n, const T px, const T py, const T pz, const T pi, const T pj, const T pk, const T pu, const T pv) { x[n] = px; y[n] = py; z[n] = pz; i[n] = pi; j[n] = pj; k[n] = pk; u[n] = pu; v[n] = pv; } + FI void set(const int n, const T px, const T py, const T pz, const T pi, const T pj, const T pk, const T pu, const T pv) { set(n, px, py, pz, pi, pj, pk, pu); v[n] = pv; } #endif FI XYZval operator[](const int n) const { return XYZval(NUM_AXIS_ARRAY(x[n], y[n], z[n], i[n], j[n], k[n], u[n], v[n], w[n])); } @@ -887,18 +1134,20 @@ 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); } - FI void set(const int n, const XYval p) { NUM_AXIS_CODE(x[n]=p.x, y[n]=p.y,,,,,,,); } - FI void set(const int n, const XYZval p) { NUM_AXIS_CODE(x[n]=p.x, y[n]=p.y, z[n]=p.z, i[n]=p.i, j[n]=p.j, k[n]=p.k, u[n]=p.u, v[n]=p.v, w[n]=p.w ); } - FI void set(const int n, const XYZEval p) { LOGICAL_AXIS_CODE(e[n]=p.e, x[n]=p.x, y[n]=p.y, z[n]=p.z, i[n]=p.i, j[n]=p.j, k[n]=p.k, u[n]=p.u, v[n]=p.v, w[n]=p.w ); } + FI void set(const int n, const XYval &p) { NUM_AXIS_CODE(x[n]=p.x, y[n]=p.y,,,,,,,); } + FI void set(const int n, const XYZval &p) { NUM_AXIS_CODE(x[n]=p.x, y[n]=p.y, z[n]=p.z, i[n]=p.i, j[n]=p.j, k[n]=p.k, u[n]=p.u, v[n]=p.v, w[n]=p.w ); } + FI void set(const int n, const XYZEval &p) { LOGICAL_AXIS_CODE(e[n]=p.e, x[n]=p.x, y[n]=p.y, z[n]=p.z, i[n]=p.i, j[n]=p.j, k[n]=p.k, u[n]=p.u, v[n]=p.v, w[n]=p.w ); } // 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 @@ -930,14 +1179,15 @@ struct XYZEarray { FI XYZEval operator[](const int n) const { return XYZval(LOGICAL_AXIS_ARRAY(e[n], x[n], y[n], z[n], i[n], j[n], k[n], u[n], v[n], w[n])); } }; -class AxisBits; - +// +// Axes mapped to bits in a mask of minimum size, bits_t(NUM_AXIS_HEADS) +// 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 + // Axes x, y, z ... e0, e1, e2 ... rx, ry, rz struct { #if NUM_AXES bool NUM_AXIS_LIST(x:1, y:1, z:1, i:1, j:1, k:1, u:1, v:1, w:1); @@ -945,11 +1195,17 @@ class AxisBits { #define _EN_ITEM(N) bool e##N:1; REPEAT(EXTRUDERS,_EN_ITEM) #undef _EN_ITEM - #if ANY(IS_CORE, MARKFORGED_XY, MARKFORGED_YX) - bool hx:1, hy:1, hz:1; + #if HAS_REAL_X + bool rx:1; + #endif + #if HAS_REAL_Y + bool ry:1; + #endif + #if HAS_REAL_Z + bool rz:1; #endif }; - // Axes X, Y, Z ... E0, E1, E2 ... HX, HY, HZ + // Axes X, Y, Z ... E0, E1, E2 ... RX, RY, RZ struct { #if NUM_AXES bool NUM_AXIS_LIST(X:1, Y:1, Z:1, I:1, J:1, K:1, U:1, V:1, W:1); @@ -957,11 +1213,17 @@ class AxisBits { #define _EN_ITEM(N) bool E##N:1; REPEAT(EXTRUDERS,_EN_ITEM) #undef _EN_ITEM - #if ANY(IS_CORE, MARKFORGED_XY, MARKFORGED_YX) - bool HX:1, HY:1, HZ:1; + #if HAS_REAL_X + bool RX:1; + #endif + #if HAS_REAL_Y + bool RY:1; + #endif + #if HAS_REAL_Z + bool RZ:1; #endif }; - // a, b, c, e ... ha, hb, hc + // a, b, c, e ... ra, rb, rc struct { bool LOGICAL_AXIS_LIST(e:1, a:1, b:1, c:1, ii:1, jj:1, kk:1, uu:1, vv:1, ww:1); #if EXTRUDERS > 1 @@ -969,11 +1231,17 @@ class AxisBits { REPEAT_S(1,EXTRUDERS,_EN_ITEM) #undef _EN_ITEM #endif - #if ANY(IS_CORE, MARKFORGED_XY, MARKFORGED_YX) - bool ha:1, hb:1, hc:1; + #if HAS_REAL_X + bool ra:1; + #endif + #if HAS_REAL_Y + bool rb:1; + #endif + #if HAS_REAL_Z + bool rc:1; #endif }; - // A, B, C, E ... HA, HB, HC + // A, B, C, E ... RA, RB, RC struct { bool LOGICAL_AXIS_LIST(E:1, A:1, B:1, C:1, II:1, JJ:1, KK:1, UU:1, VV:1, WW:1); #if EXTRUDERS > 1 @@ -981,12 +1249,37 @@ class AxisBits { REPEAT_S(1,EXTRUDERS,_EN_ITEM) #undef _EN_ITEM #endif - #if ANY(IS_CORE, MARKFORGED_XY, MARKFORGED_YX) - bool HA:1, HB:1, HC:1; + #if HAS_REAL_X + bool RA:1; + #endif + #if HAS_REAL_Y + bool RB:1; + #endif + #if HAS_REAL_Z + bool RC:1; #endif }; }; + 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 @@ -995,7 +1288,7 @@ class AxisBits { 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) @@ -1084,9 +1377,12 @@ class AxisBits { FI bool toggle(const AxisEnum n) { TBI(bits, n); return TEST(bits, n); } FI void bset(const AxisEnum n) { SBI(bits, n); } FI void bclr(const AxisEnum n) { CBI(bits, n); } + 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; } @@ -1107,6 +1403,7 @@ class AxisBits { FI AxisBits operator|(const AxisBits &p) const { return AxisBits(bits | p.bits); } FI AxisBits operator&(const AxisBits &p) const { return AxisBits(bits & p.bits); } FI AxisBits operator^(const AxisBits &p) const { return AxisBits(bits ^ p.bits); } + FI AxisBits operator~() const { return AxisBits(~bits); } FI operator bool() const { return !!bits; } FI operator uint16_t() const { return uint16_t(bits & 0xFFFF); } @@ -1121,3 +1418,14 @@ class AxisBits { #undef _LSE #undef _RSE #undef FI + +// Axis names for G-code parsing, reports, etc. +constexpr xyze_char_t axis_codes LOGICAL_AXIS_ARRAY('E', 'X', 'Y', 'Z', AXIS4_NAME, AXIS5_NAME, AXIS6_NAME, AXIS7_NAME, AXIS8_NAME, AXIS9_NAME); +#if NUM_AXES <= 3 && !HAS_EXTRUDERS + #define AXIS_CHAR(A) ((char)('X' + A)) + #define IAXIS_CHAR AXIS_CHAR +#else + constexpr xyze_char_t iaxis_codes LOGICAL_AXIS_ARRAY('E', 'X', 'Y', 'Z', 'I', 'J', 'K', 'U', 'V', 'W'); + #define AXIS_CHAR(A) axis_codes[A] + #define IAXIS_CHAR(A) iaxis_codes[A] +#endif diff --git a/Marlin/src/core/utility.cpp b/Marlin/src/core/utility.cpp index b555d9f2a69b..86a7b75640b4 100644 --- a/Marlin/src/core/utility.cpp +++ b/Marlin/src/core/utility.cpp @@ -22,26 +22,16 @@ #include "utility.h" -#include "../MarlinCore.h" #include "../module/temperature.h" #if ENABLED(MARLIN_DEV_MODE) MarlinError marlin_error_number; // Error Number - Marlin can beep X times periodically, display, and emit... #endif -void safe_delay(millis_t ms) { - while (ms > 50) { - ms -= 50; - delay(50); - thermalManager.task(); - } - delay(ms); - thermalManager.task(); // This keeps us safe if too many small safe_delay() calls are made -} - // A delay to provide brittle hosts time to receive bytes #if ENABLED(SERIAL_OVERRUN_PROTECTION) + #include "../MarlinCore.h" // for safe_delay #include "../gcode/gcode.h" // for set_autoreport_paused void serial_delay(const millis_t ms) { @@ -60,29 +50,38 @@ void safe_delay(millis_t ms) { #include "../feature/bedlevel/bedlevel.h" void log_machine_info() { - SERIAL_ECHOLNPGM("Machine Type: " - TERN_(DELTA, "Delta") - TERN_(IS_SCARA, "SCARA") - TERN_(IS_CORE, "Core") - TERN_(MARKFORGED_XY, "MarkForgedXY") - TERN_(MARKFORGED_YX, "MarkForgedYX") - TERN_(IS_CARTESIAN, "Cartesian") + SERIAL_ECHOLNPGM("Machine Type:" + TERN_(DELTA, " Delta") + TERN_(IS_SCARA, " SCARA") + TERN_(AXEL_TPARA, " TPARA") + TERN_(IS_CORE, " Core") + TERN_(BELTPRINTER, " Belt Printer") + TERN_(MARKFORGED_XY, " MarkForgedXY") + TERN_(MARKFORGED_YX, " MarkForgedYX") + TERN_(POLAR, " Polar") + TERN_(POLARGRAPH, " Polargraph") + TERN_(ARTICULATED_ROBOT_ARM, " Robot Arm") + TERN_(FOAMCUTTER_XYUV, " Foam Cutter") + TERN_(IS_CARTESIAN, " Cartesian") ); SERIAL_ECHOLNPGM("Probe: " - TERN_(PROBE_MANUALLY, "PROBE_MANUALLY") - TERN_(NOZZLE_AS_PROBE, "NOZZLE_AS_PROBE") - TERN_(FIX_MOUNTED_PROBE, "FIX_MOUNTED_PROBE") - TERN_(HAS_Z_SERVO_PROBE, TERN(BLTOUCH, "BLTOUCH", "SERVO PROBE")) - TERN_(BD_SENSOR, "BD_SENSOR") - TERN_(TOUCH_MI_PROBE, "TOUCH_MI_PROBE") - TERN_(Z_PROBE_SLED, "Z_PROBE_SLED") - TERN_(Z_PROBE_ALLEN_KEY, "Z_PROBE_ALLEN_KEY") - TERN_(SOLENOID_PROBE, "SOLENOID_PROBE") - TERN_(MAGLEV4, "MAGLEV4") - TERN_(BIQU_MICROPROBE_V1, "BIQU_MICROPROBE_V1") - TERN_(BIQU_MICROPROBE_V2, "BIQU_MICROPROBE_V2") - IF_DISABLED(PROBE_SELECTED, "NONE") + TERN_(PROBE_MANUALLY, "PROBE_MANUALLY") + TERN_(NOZZLE_AS_PROBE, "NOZZLE_AS_PROBE") + TERN_(FIX_MOUNTED_PROBE, "FIX_MOUNTED_PROBE") + TERN_(HAS_Z_SERVO_PROBE, TERN(BLTOUCH, "BLTOUCH", "SERVO PROBE")) + TERN_(BD_SENSOR, "BD_SENSOR") + TERN_(TOUCH_MI_PROBE, "TOUCH_MI_PROBE") + TERN_(Z_PROBE_ALLEN_KEY, "Z_PROBE_ALLEN_KEY") + TERN_(Z_PROBE_SLED, "Z_PROBE_SLED") + TERN_(RACK_AND_PINION_PROBE, "RACK_AND_PINION_PROBE") + TERN_(SOLENOID_PROBE, "SOLENOID_PROBE") + TERN_(SENSORLESS_PROBING, "SENSORLESS_PROBING") + TERN_(MAGLEV4, "MAGLEV4") + TERN_(MAG_MOUNTED_PROBE, "MAG_MOUNTED_PROBE") + TERN_(BIQU_MICROPROBE_V1, "BIQU_MICROPROBE_V1") + TERN_(BIQU_MICROPROBE_V2, "BIQU_MICROPROBE_V2") + IF_DISABLED(PROBE_SELECTED, "NONE") ); #if HAS_BED_PROBE @@ -134,7 +133,7 @@ void safe_delay(millis_t ms) { SERIAL_ECHOPGM("ABL Adjustment"); LOOP_NUM_AXES(a) { SERIAL_ECHOPGM_P((PGM_P)pgm_read_ptr(&SP_AXIS_STR[a])); - serial_offset(planner.get_axis_position_mm(AxisEnum(a)) - current_position[a]); + serial_offset(planner.get_axis_position_mm((AxisEnum)a) - motion.position[a]); } #else #if ENABLED(AUTO_BED_LEVELING_UBL) @@ -142,13 +141,11 @@ void safe_delay(millis_t ms) { #elif ENABLED(AUTO_BED_LEVELING_BILINEAR) SERIAL_ECHOPGM("ABL Adjustment Z"); #endif - const float rz = bedlevel.get_z_correction(current_position); + const float rz = bedlevel.get_z_correction(motion.position); SERIAL_ECHO(ftostr43sign(rz, '+')); #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) - if (planner.z_fade_height) { - SERIAL_ECHOPGM(" (", ftostr43sign(rz * planner.fade_scaling_factor_for_z(current_position.z), '+')); - SERIAL_CHAR(')'); - } + if (planner.z_fade_height) + SERIAL_ECHO(F(" ("), ftostr43sign(rz * planner.fade_scaling_factor_for_z(motion.position.z), '+'), C(')')); #endif #endif } @@ -163,14 +160,11 @@ void safe_delay(millis_t ms) { if (planner.leveling_active) { SERIAL_ECHOLNPGM(" (enabled)"); const float z_offset = bedlevel.get_z_offset(), - z_correction = bedlevel.get_z_correction(current_position); + z_correction = bedlevel.get_z_correction(motion.position); SERIAL_ECHOPGM("MBL Adjustment Z", ftostr43sign(z_offset + z_correction, '+')); #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) if (planner.z_fade_height) { - SERIAL_ECHOPGM(" (", ftostr43sign( - z_offset + z_correction * planner.fade_scaling_factor_for_z(current_position.z), '+' - )); - SERIAL_CHAR(')'); + SERIAL_ECHO(F(" ("), ftostr43sign(z_offset + z_correction * planner.fade_scaling_factor_for_z(motion.position.z), '+'), C(')')); } #endif } diff --git a/Marlin/src/core/utility.h b/Marlin/src/core/utility.h index c3324443bab9..b8b4de7f289b 100644 --- a/Marlin/src/core/utility.h +++ b/Marlin/src/core/utility.h @@ -25,8 +25,6 @@ #include "../core/types.h" #include "../core/millis_t.h" -void safe_delay(millis_t ms); // Delay ensuring that temperatures are updated and the watchdog is kept alive. - #if ENABLED(SERIAL_OVERRUN_PROTECTION) void serial_delay(const millis_t ms); #else @@ -82,17 +80,6 @@ class restorer { // in the range 0-100 while avoiding rounding artifacts constexpr uint8_t ui8_to_percent(const uint8_t i) { return (int(i) * 100 + 127) / 255; } -// Axis names for G-code parsing, reports, etc. -const xyze_char_t axis_codes LOGICAL_AXIS_ARRAY('E', 'X', 'Y', 'Z', AXIS4_NAME, AXIS5_NAME, AXIS6_NAME, AXIS7_NAME, AXIS8_NAME, AXIS9_NAME); -#if NUM_AXES <= XYZ && !HAS_EXTRUDERS - #define AXIS_CHAR(A) ((char)('X' + A)) - #define IAXIS_CHAR AXIS_CHAR -#else - const xyze_char_t iaxis_codes LOGICAL_AXIS_ARRAY('E', 'X', 'Y', 'Z', 'I', 'J', 'K', 'U', 'V', 'W'); - #define AXIS_CHAR(A) axis_codes[A] - #define IAXIS_CHAR(A) iaxis_codes[A] -#endif - #if ENABLED(MARLIN_DEV_MODE) enum MarlinError : uint8_t { ERR_NONE, diff --git a/Marlin/src/feature/babystep.cpp b/Marlin/src/feature/babystep.cpp index c6717268a64f..6a5c2bb13fba 100644 --- a/Marlin/src/feature/babystep.cpp +++ b/Marlin/src/feature/babystep.cpp @@ -25,8 +25,7 @@ #if ENABLED(BABYSTEPPING) #include "babystep.h" -#include "../MarlinCore.h" -#include "../module/motion.h" // for axes_should_home(), BABYSTEP_ALLOWED +#include "../module/motion.h" // for axis_should_home(), BABYSTEP_ALLOWED #include "../module/planner.h" // for axis_steps_per_mm[] #include "../module/stepper.h" @@ -49,18 +48,18 @@ int16_t Babystep::accum; void Babystep::step_axis(const AxisEnum axis) { const int16_t curTodo = steps[BS_AXIS_IND(axis)]; // get rid of volatile for performance if (curTodo) { - stepper.do_babystep((AxisEnum)axis, curTodo > 0); + stepper.do_babystep(axis, curTodo > 0); if (curTodo > 0) steps[BS_AXIS_IND(axis)]--; else steps[BS_AXIS_IND(axis)]++; } } -void Babystep::add_mm(const AxisEnum axis, const_float_t mm) { +void Babystep::add_mm(const AxisEnum axis, const float mm) { add_steps(axis, mm * planner.settings.axis_steps_per_mm[axis]); } #if ENABLED(BD_SENSOR) - void Babystep::set_mm(const AxisEnum axis, const_float_t mm) { - //if (DISABLED(BABYSTEP_WITHOUT_HOMING) && axes_should_home(_BV(axis))) return; + void Babystep::set_mm(const AxisEnum axis, const float mm) { + //if (DISABLED(BABYSTEP_WITHOUT_HOMING) && motion.axis_should_home(axis)) return; const int16_t distance = mm * planner.settings.axis_steps_per_mm[axis]; accum = distance; // Count up babysteps for the UI steps[BS_AXIS_IND(axis)] = distance; @@ -70,8 +69,12 @@ void Babystep::add_mm(const AxisEnum axis, const_float_t mm) { } #endif +bool Babystep::can_babystep(const AxisEnum axis) { + return (ENABLED(BABYSTEP_WITHOUT_HOMING) || !motion.axis_should_home(axis)); +} + void Babystep::add_steps(const AxisEnum axis, const int16_t distance) { - if (DISABLED(BABYSTEP_WITHOUT_HOMING) && axes_should_home(_BV(axis))) return; + if (!can_babystep(axis)) return; accum += distance; // Count up babysteps for the UI steps[BS_AXIS_IND(axis)] += distance; diff --git a/Marlin/src/feature/babystep.h b/Marlin/src/feature/babystep.h index 38a19a3b1473..da330672b2d6 100644 --- a/Marlin/src/feature/babystep.h +++ b/Marlin/src/feature/babystep.h @@ -59,8 +59,9 @@ class Babystep { } #endif + static bool can_babystep(const AxisEnum axis); static void add_steps(const AxisEnum axis, const int16_t distance); - static void add_mm(const AxisEnum axis, const_float_t mm); + static void add_mm(const AxisEnum axis, const float mm); #if ENABLED(EP_BABYSTEPPING) // Step Z for M293 / M294 @@ -78,7 +79,7 @@ class Babystep { #endif // EP_BABYSTEPPING #if ENABLED(BD_SENSOR) - static void set_mm(const AxisEnum axis, const_float_t mm); + static void set_mm(const AxisEnum axis, const float mm); #endif static bool has_steps() { diff --git a/Marlin/src/feature/backlash.cpp b/Marlin/src/feature/backlash.cpp index 07fa7725a06c..c18d32edd155 100644 --- a/Marlin/src/feature/backlash.cpp +++ b/Marlin/src/feature/backlash.cpp @@ -29,7 +29,7 @@ #include "../module/motion.h" #include "../module/planner.h" -AxisBits Backlash::last_direction_bits; +AxisBits Backlash::last_direction; xyz_long_t Backlash::residual_error{0}; #ifdef BACKLASH_DISTANCE_MM @@ -64,7 +64,7 @@ Backlash backlash; */ void Backlash::add_correction_steps(const xyze_long_t &dist, const AxisBits dm, block_t * const block) { - AxisBits changed_dir = last_direction_bits ^ dm; + AxisBits changed_dir = last_direction ^ dm; // Ignore direction change unless steps are taken in that direction #if DISABLED(CORE_BACKLASH) || ANY(MARKFORGED_XY, MARKFORGED_YX) if (!dist.a) changed_dir.x = false; @@ -83,7 +83,7 @@ void Backlash::add_correction_steps(const xyze_long_t &dist, const AxisBits dm, if (!(dist.b - dist.c)) changed_dir.z = false; if (!dist.a) changed_dir.x = false; #endif - last_direction_bits ^= changed_dir; + last_direction ^= changed_dir; if (!correction && !residual_error) return; @@ -167,17 +167,18 @@ void Backlash::add_correction_steps(const xyze_long_t &dist, const AxisBits dm, int32_t Backlash::get_applied_steps(const AxisEnum axis) { if (axis >= NUM_AXES) return 0; - const bool forward = last_direction_bits[axis]; + const bool forward = last_direction[axis]; const int32_t residual_error_axis = residual_error[axis]; - // At startup it is assumed the last move was forward. - // So the applied steps will always be negative. + // At startup, when no steps are applied, it is assumed the last move was backwards. + // So the applied steps will always be zero (when moving backwards) or a positive + // number (when moving forwards). - if (forward) return -residual_error_axis; + if (!forward) return -residual_error_axis; const float f_corr = float(correction) / all_on; - const int32_t full_error_axis = -f_corr * distance_mm[axis] * planner.settings.axis_steps_per_mm[axis]; + const int32_t full_error_axis = f_corr * distance_mm[axis] * planner.settings.axis_steps_per_mm[axis]; return full_error_axis - residual_error_axis; } @@ -223,12 +224,12 @@ class Backlash::StepAdjuster { void Backlash::measure_with_probe() { if (measured_count.z == 255) return; - const float start_height = current_position.z; - while (current_position.z < (start_height + BACKLASH_MEASUREMENT_LIMIT) && PROBE_TRIGGERED()) - do_blocking_move_to_z(current_position.z + BACKLASH_MEASUREMENT_RESOLUTION, MMM_TO_MMS(BACKLASH_MEASUREMENT_FEEDRATE)); + const float start_height = motion.position.z; + while (motion.position.z < (start_height + BACKLASH_MEASUREMENT_LIMIT) && PROBE_TRIGGERED()) + motion.blocking_move_z(motion.position.z + BACKLASH_MEASUREMENT_RESOLUTION, MMM_TO_MMS(BACKLASH_MEASUREMENT_FEEDRATE)); // The backlash from all probe points is averaged, so count the number of measurements - measured_mm.z += current_position.z - start_height; + measured_mm.z += motion.position.z - start_height; measured_count.z++; } diff --git a/Marlin/src/feature/backlash.h b/Marlin/src/feature/backlash.h index 593e51b9d0fe..39c55ea21c77 100644 --- a/Marlin/src/feature/backlash.h +++ b/Marlin/src/feature/backlash.h @@ -29,7 +29,7 @@ class Backlash { static constexpr uint8_t all_on = 0xFF, all_off = 0x00; private: - static AxisBits last_direction_bits; + static AxisBits last_direction; static xyz_long_t residual_error; #if ENABLED(BACKLASH_GCODE) @@ -81,10 +81,10 @@ class Backlash { static void set_correction(const float v) { set_correction_uint8(_MAX(0, _MIN(1.0, v)) * all_on + 0.5f); } static float get_correction() { return float(get_correction_uint8()) / all_on; } static void set_distance_mm(const AxisEnum axis, const float v); - static float get_distance_mm(const AxisEnum axis) {return distance_mm[axis];} + static float get_distance_mm(const AxisEnum axis) { return distance_mm[axis]; } #ifdef BACKLASH_SMOOTHING_MM static void set_smoothing_mm(const float v); - static float get_smoothing_mm() {return smoothing_mm;} + static float get_smoothing_mm() { return smoothing_mm; } #endif #endif diff --git a/Marlin/src/feature/bedlevel/abl/bbl.cpp b/Marlin/src/feature/bedlevel/abl/bbl.cpp index 14c4bd24bcf0..3c596fc3ca40 100644 --- a/Marlin/src/feature/bedlevel/abl/bbl.cpp +++ b/Marlin/src/feature/bedlevel/abl/bbl.cpp @@ -37,12 +37,12 @@ LevelingBilinear bedlevel; -xy_pos_t LevelingBilinear::grid_spacing, - LevelingBilinear::grid_start; xy_float_t LevelingBilinear::grid_factor; +xy_pos_t LevelingBilinear::grid_spacing, + LevelingBilinear::grid_start, + LevelingBilinear::cached_rel; +xy_int8_t LevelingBilinear::cached_g; bed_mesh_t LevelingBilinear::z_values; -xy_pos_t LevelingBilinear::cached_rel; -xy_int8_t LevelingBilinear::cached_g; /** * Extrapolate a single point from its neighbors @@ -106,9 +106,17 @@ void LevelingBilinear::reset() { } } +/** + * Set grid spacing and start position + */ void LevelingBilinear::set_grid(const xy_pos_t& _grid_spacing, const xy_pos_t& _grid_start) { - grid_spacing = _grid_spacing; - grid_start = _grid_start; + #if HAS_PROUI_MESH_EDIT + grid_spacing.set(MESH_X_DIST, MESH_Y_DIST); + grid_start = mesh_min; + #else + grid_spacing = _grid_spacing; + grid_start = _grid_start; + #endif grid_factor = grid_spacing.reciprocal(); } @@ -181,7 +189,7 @@ void LevelingBilinear::print_leveling_grid(const bed_mesh_t* _z_values/*=nullptr // The requested point requires extrapolating two points beyond the mesh. // These values are only requested for the edges of the mesh, which are always an actual mesh point, // and do not require interpolation. When interpolation is not needed, this "Mesh + 2" point is - // cancelled out in virt_cmr and does not impact the result. Return 0.0 rather than + // canceled out in virt_cmr and does not impact the result. Return 0.0 rather than // making this function more complex by extrapolating two points. return 0.0; } @@ -229,7 +237,7 @@ void LevelingBilinear::print_leveling_grid(const bed_mesh_t* _z_values/*=nullptr ) * 0.5f; } - float LevelingBilinear::virt_2cmr(const uint8_t x, const uint8_t y, const_float_t tx, const_float_t ty) { + float LevelingBilinear::virt_2cmr(const uint8_t x, const uint8_t y, const float tx, const float ty) { float row[4], column[4]; for (uint8_t i = 0; i < 4; ++i) { for (uint8_t j = 0; j < 4; ++j) { @@ -369,10 +377,10 @@ float LevelingBilinear::get_z_correction(const xy_pos_t &raw) { * Prepare a bilinear-leveled linear move on Cartesian, * splitting the move where it crosses grid borders. */ - void LevelingBilinear::line_to_destination(const_feedRate_t scaled_fr_mm_s, uint16_t x_splits, uint16_t y_splits) { + void LevelingBilinear::line_to_destination(const feedRate_t scaled_fr_mm_s, uint16_t x_splits, uint16_t y_splits) { // Get current and destination cells for this line - xy_int_t c1 { CELL_INDEX(x, current_position.x), CELL_INDEX(y, current_position.y) }, - c2 { CELL_INDEX(x, destination.x), CELL_INDEX(y, destination.y) }; + xy_int_t c1 { CELL_INDEX(x, motion.position.x), CELL_INDEX(y, motion.position.y) }, + c2 { CELL_INDEX(x, motion.destination.x), CELL_INDEX(y, motion.destination.y) }; LIMIT(c1.x, 0, ABL_BG_POINTS_X - 2); LIMIT(c1.y, 0, ABL_BG_POINTS_Y - 2); LIMIT(c2.x, 0, ABL_BG_POINTS_X - 2); @@ -380,12 +388,12 @@ float LevelingBilinear::get_z_correction(const xy_pos_t &raw) { // Start and end in the same cell? No split needed. if (c1 == c2) { - current_position = destination; - line_to_current_position(scaled_fr_mm_s); + motion.position = motion.destination; + motion.goto_current_position(scaled_fr_mm_s); return; } - #define LINE_SEGMENT_END(A) (current_position.A + (destination.A - current_position.A) * normalized_dist) + #define LINE_SEGMENT_END(A) (motion.position.A + (motion.destination.A - motion.position.A) * normalized_dist) float normalized_dist; xyze_pos_t end; @@ -396,36 +404,36 @@ float LevelingBilinear::get_z_correction(const xy_pos_t &raw) { if (c2.x != c1.x && TEST(x_splits, gc.x)) { // Split on the X grid line CBI(x_splits, gc.x); - end = destination; - destination.x = grid_start.x + ABL_BG_SPACING(x) * gc.x; - normalized_dist = (destination.x - current_position.x) / (end.x - current_position.x); - destination.y = LINE_SEGMENT_END(y); + end = motion.destination; + motion.destination.x = grid_start.x + ABL_BG_SPACING(x) * gc.x; + normalized_dist = (motion.destination.x - motion.position.x) / (end.x - motion.position.x); + motion.destination.y = LINE_SEGMENT_END(y); } // Crosses on the Y and not already split on this Y? else if (c2.y != c1.y && TEST(y_splits, gc.y)) { // Split on the Y grid line CBI(y_splits, gc.y); - end = destination; - destination.y = grid_start.y + ABL_BG_SPACING(y) * gc.y; - normalized_dist = (destination.y - current_position.y) / (end.y - current_position.y); - destination.x = LINE_SEGMENT_END(x); + end = motion.destination; + motion.destination.y = grid_start.y + ABL_BG_SPACING(y) * gc.y; + normalized_dist = (motion.destination.y - motion.position.y) / (end.y - motion.position.y); + motion.destination.x = LINE_SEGMENT_END(x); } else { // Must already have been split on these border(s) // This should be a rare case. - current_position = destination; - line_to_current_position(scaled_fr_mm_s); + motion.position = motion.destination; + motion.goto_current_position(scaled_fr_mm_s); return; } - destination.z = LINE_SEGMENT_END(z); - destination.e = LINE_SEGMENT_END(e); + motion.destination.z = LINE_SEGMENT_END(z); + motion.destination.e = LINE_SEGMENT_END(e); // Do the split and look for more borders line_to_destination(scaled_fr_mm_s, x_splits, y_splits); // Restore destination from stack - destination = end; + motion.destination = end; line_to_destination(scaled_fr_mm_s, x_splits, y_splits); } diff --git a/Marlin/src/feature/bedlevel/abl/bbl.h b/Marlin/src/feature/bedlevel/abl/bbl.h index ca2e96593fde..fb890333dc76 100644 --- a/Marlin/src/feature/bedlevel/abl/bbl.h +++ b/Marlin/src/feature/bedlevel/abl/bbl.h @@ -45,7 +45,7 @@ class LevelingBilinear { static float virt_coord(const uint8_t x, const uint8_t y); static float virt_cmr(const float p[4], const uint8_t i, const float t); - static float virt_2cmr(const uint8_t x, const uint8_t y, const_float_t tx, const_float_t ty); + static float virt_2cmr(const uint8_t x, const uint8_t y, const float tx, const float ty); static void subdivide_mesh(); #endif @@ -63,7 +63,7 @@ class LevelingBilinear { static constexpr float get_z_offset() { return 0.0f; } #if IS_CARTESIAN && DISABLED(SEGMENT_LEVELED_MOVES) - static void line_to_destination(const_feedRate_t scaled_fr_mm_s, uint16_t x_splits=0xFFFF, uint16_t y_splits=0xFFFF); + static void line_to_destination(const feedRate_t scaled_fr_mm_s, uint16_t x_splits=0xFFFF, uint16_t y_splits=0xFFFF); #endif }; diff --git a/Marlin/src/feature/bedlevel/bdl/bdl.cpp b/Marlin/src/feature/bedlevel/bdl/bdl.cpp index d469bb0c0673..7a455a771b50 100644 --- a/Marlin/src/feature/bedlevel/bdl/bdl.cpp +++ b/Marlin/src/feature/bedlevel/bdl/bdl.cpp @@ -24,7 +24,6 @@ #if ENABLED(BD_SENSOR) -#include "../../../MarlinCore.h" #include "../../../gcode/gcode.h" #include "../../../module/settings.h" #include "../../../module/motion.h" @@ -46,10 +45,16 @@ BDS_Leveling bdl; #define DEBUG_OUT ENABLED(DEBUG_OUT_BD) #include "../../../core/debug_out.h" -// M102 S-5 Read raw Calibrate data -// M102 S-6 Start Calibrate -// M102 S4 Set the adjustable Z height value (e.g., 'M102 S4' means it will do adjusting while the Z height <= 0.4mm , disable with 'M102 S0'.) -// M102 S-1 Read sensor information +/** + * M102 S<#> : Set adjustable Z height in 0.1mm units (10ths of a mm) + * (e.g., 'M102 S4' enables adjusting for Z <= 0.4mm) + * M102 S0 : Disable adjustable Z height + * + * M102 S-1 : Read BDsensor version + * M102 S-2 : Read BDsensor distance value + * M102 S-5 : Read raw Calibration data + * M102 S-6 : Start Calibration + */ #define MAX_BD_HEIGHT 4.0f #define CMD_READ_VERSION 1016 @@ -67,8 +72,8 @@ void BDS_Leveling::init(uint8_t _sda, uint8_t _scl, uint16_t delay_s) { config_state = BDS_IDLE; const int ret = BD_I2C_SENSOR.i2c_init(_sda, _scl, BD_SENSOR_I2C_ADDR, delay_s); if (ret != 1) SERIAL_ECHOLNPGM("BD Sensor Init Fail (", ret, ")"); - sync_plan_position(); - pos_zero_offset = planner.get_axis_position_mm(Z_AXIS) - current_position.z; + motion.sync_plan_position(); + pos_zero_offset = planner.get_axis_position_mm(Z_AXIS) - motion.position.z; SERIAL_ECHOLNPGM("BD Sensor Zero Offset:", pos_zero_offset); } @@ -95,7 +100,7 @@ bool BDS_Leveling::check(const uint16_t data, const bool raw_data/*=false*/, con } float BDS_Leveling::interpret(const uint16_t data) { - return (data & 0x3FF) / 100.0f; + return (data & 0x3FF) * 0.01f; } float BDS_Leveling::read() { @@ -104,7 +109,7 @@ float BDS_Leveling::read() { } void BDS_Leveling::process() { - if (config_state == BDS_IDLE && printingIsActive()) return; + if (config_state == BDS_IDLE && marlin.printingIsActive()) return; static millis_t next_check_ms = 0; // starting at T=0 static float zpos = 0.0f; const millis_t ms = millis(); @@ -114,7 +119,7 @@ void BDS_Leveling::process() { uint16_t tmp = 0; const float cur_z = planner.get_axis_position_mm(Z_AXIS) - pos_zero_offset; - static float old_cur_z = cur_z, old_buf_z = current_position.z; + static float old_cur_z = cur_z, old_buf_z = motion.position.z; tmp = BD_I2C_SENSOR.BD_i2c_read(); if (BD_I2C_SENSOR.BD_Check_OddEven(tmp) && good_data(tmp)) { const float z_sensor = interpret(tmp); @@ -122,11 +127,11 @@ void BDS_Leveling::process() { if (config_state > 0) { if (cur_z < config_state * 0.1f && old_cur_z == cur_z - && old_buf_z == current_position.z + && old_buf_z == motion.position.z && z_sensor < (MAX_BD_HEIGHT) - 0.1f ) { babystep.set_mm(Z_AXIS, cur_z - z_sensor); - DEBUG_ECHOLNPGM("BD:", z_sensor, ", Z:", cur_z, "|", current_position.z); + DEBUG_ECHOLNPGM("BD:", z_sensor, ", Z:", cur_z, "|", motion.position.z); } else babystep.set_mm(Z_AXIS, 0); @@ -134,26 +139,24 @@ void BDS_Leveling::process() { #endif old_cur_z = cur_z; - old_buf_z = current_position.z; + old_buf_z = motion.position.z; endstops.bdp_state_update(z_sensor <= BD_SENSOR_HOME_Z_POSITION); #if HAS_STATUS_MESSAGE static float old_z_sensor = 0; if (old_z_sensor != z_sensor) { old_z_sensor = z_sensor; - char tmp_1[32]; - sprintf_P(tmp_1, PSTR("BD:%d.%02dmm"), int(z_sensor), int(z_sensor * 100) % 100); //SERIAL_ECHOLNPGM("Bed Dis:", z_sensor, "mm"); - ui.set_status(tmp_1, true); + ui.set_status(TS(F("BD:"), p_float_t(z_sensor, 2), F("mm")), true); } #endif } else if (config_state == BDS_HOMING_Z) { SERIAL_ECHOLNPGM("Read:", tmp); - kill(F("BDsensor connect Err!")); + marlin.kill(F("BDsensor connect Err!")); } - DEBUG_ECHOLNPGM("BD:", tmp & 0x3FF, " Z:", cur_z, "|", current_position.z); + DEBUG_ECHOLNPGM("BD:", tmp & 0x3FF, " Z:", cur_z, "|", motion.position.z); if (TERN0(DEBUG_OUT_BD, BD_I2C_SENSOR.BD_Check_OddEven(tmp) == 0)) DEBUG_ECHOLNPGM("CRC error"); if (!good_data(tmp)) { @@ -199,15 +202,15 @@ void BDS_Leveling::process() { SERIAL_ECHOLNPGM("c_z0:", planner.get_axis_position_mm(Z_AXIS), "-", pos_zero_offset); // Move the z axis instead of enabling the Z axis with M17 - // TODO: Use do_blocking_move_to_z for synchronized move. - current_position.z = 0; - sync_plan_position(); + // TODO: Use motion.blocking_move_z for synchronized move. + motion.position.z = 0; + motion.sync_plan_position(); gcode.process_subcommands_now(F("G1Z0.05")); safe_delay(300); gcode.process_subcommands_now(F("G1Z0.00")); safe_delay(300); - current_position.z = 0; - sync_plan_position(); + motion.position.z = 0; + motion.sync_plan_position(); //safe_delay(1000); while ((planner.get_axis_position_mm(Z_AXIS) - pos_zero_offset) > 0.00001f) { @@ -229,11 +232,10 @@ void BDS_Leveling::process() { safe_delay(500); } else { - char tmp_1[32]; - // TODO: Use prepare_internal_move_to_destination to guarantee machine space - sprintf_P(tmp_1, PSTR("G1Z%d.%d"), int(zpos), int(zpos * 10) % 10); + // TODO: Use motion.prepare_internal_move_to_destination to guarantee machine space + MString<16> tmp_1(F("G1Z"), p_float_t(zpos, 1)); gcode.process_subcommands_now(tmp_1); - SERIAL_ECHO(tmp_1); SERIAL_ECHOLNPGM(", Z:", current_position.z); + SERIAL_ECHOLN(&tmp_1, F(", Z:"), motion.position.z); uint16_t failcount = 300; for (float tmp_k = 0; abs(zpos - tmp_k) > 0.006f && failcount--;) { tmp_k = planner.get_axis_position_mm(Z_AXIS) - pos_zero_offset; diff --git a/Marlin/src/feature/bedlevel/bedlevel.cpp b/Marlin/src/feature/bedlevel/bedlevel.cpp index a76c6cdd269e..567c62683294 100644 --- a/Marlin/src/feature/bedlevel/bedlevel.cpp +++ b/Marlin/src/feature/bedlevel/bedlevel.cpp @@ -46,8 +46,18 @@ #include "../../lcd/extui/ui_api.h" #endif +#if ALL(HAS_MESH, DWIN_LCD_PROUI) + #include "../../lcd/dwin/proui/bedlevel_tools.h" +#endif + bool leveling_is_valid() { - return TERN1(HAS_MESH, bedlevel.mesh_is_valid()); + return ( + #if ALL(HAS_MESH, DWIN_LCD_PROUI) + bedLevelTools.meshValidate() + #else + TERN1(HAS_MESH, bedlevel.mesh_is_valid()) + #endif + ); } /** @@ -66,9 +76,9 @@ void set_bed_leveling_enabled(const bool enable/*=true*/) { auto _report_leveling = []{ if (DEBUGGING(LEVELING)) { if (planner.leveling_active) - DEBUG_POS("Leveling ON", current_position); + DEBUG_POS("Leveling ON", motion.position); else - DEBUG_POS("Leveling OFF", current_position); + DEBUG_POS("Leveling OFF", motion.position); } }; @@ -76,11 +86,11 @@ void set_bed_leveling_enabled(const bool enable/*=true*/) { planner.synchronize(); // Get the corrected leveled / unleveled position - planner.apply_modifiers(current_position, true); // Physical position with all modifiers - planner.leveling_active ^= true; // Toggle leveling between apply and unapply - planner.unapply_modifiers(current_position, true); // Logical position with modifiers removed + planner.apply_modifiers(motion.position, true); // Physical position with all modifiers + FLIP(planner.leveling_active); // Toggle leveling between apply and unapply + planner.unapply_modifiers(motion.position, true); // Logical position with modifiers removed - sync_plan_position(); + motion.sync_plan_position(); _report_leveling(); } } @@ -91,7 +101,7 @@ TemporaryBedLevelingState::TemporaryBedLevelingState(const bool enable) : saved( #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) - void set_z_fade_height(const_float_t zfh, const bool do_report/*=true*/) { + void set_z_fade_height(const float zfh, const bool do_report/*=true*/) { if (planner.z_fade_height == zfh) return; @@ -101,10 +111,10 @@ TemporaryBedLevelingState::TemporaryBedLevelingState(const bool enable) : saved( planner.set_z_fade_height(zfh); if (leveling_was_active) { - const xyz_pos_t oldpos = current_position; + const xyz_pos_t oldpos = motion.position; set_bed_leveling_enabled(true); - if (do_report && oldpos != current_position) - report_current_position(); + if (do_report && oldpos != motion.position) + motion.report_position(); } } @@ -198,16 +208,16 @@ void reset_bed_level() { #else #warning "It's recommended to set some MANUAL_PROBE_START_Z value for manual leveling." #endif - #if Z_CLEARANCE_BETWEEN_MANUAL_PROBES > 0 // A probe/obstacle clearance exists so there is a raise: + #if Z_CLEARANCE_BETWEEN_MANUAL_PROBES > 0 // A probe/obstacle clearance exists so there is a raise: #ifndef MANUAL_PROBE_START_Z - const float finalz = current_position.z; // - Use the current Z for starting-Z if no MANUAL_PROBE_START_Z was provided + const float finalz = motion.position.z; // - Use the current Z for starting-Z if no MANUAL_PROBE_START_Z was provided #endif - do_blocking_move_to_xy_z(pos, Z_CLEARANCE_BETWEEN_MANUAL_PROBES); // - Raise Z, then move to the new XY - do_blocking_move_to_z(finalz); // - Lower down to the starting Z height, ready for adjustment! - #elif defined(MANUAL_PROBE_START_Z) // A starting-Z was provided, but there's no raise: - do_blocking_move_to_xy_z(pos, finalz); // - Move in XY then down to the starting Z height, ready for adjustment! - #else // Zero raise and no starting Z height either: - do_blocking_move_to_xy(pos); // - Move over with no raise, ready for adjustment! + motion.blocking_move_xy_z(pos, Z_CLEARANCE_BETWEEN_MANUAL_PROBES); // - Raise Z, then move to the new XY + motion.blocking_move_z(finalz); // - Lower down to the starting Z height, ready for adjustment! + #elif defined(MANUAL_PROBE_START_Z) // A starting-Z was provided, but there's no raise: + motion.blocking_move_xy_z(pos, finalz); // - Move in XY then down to the starting Z height, ready for adjustment! + #else // Zero raise and no starting Z height either: + motion.blocking_move_xy(pos); // - Move over with no raise, ready for adjustment! #endif TERN_(LCD_BED_LEVELING, ui.wait_for_move = false); diff --git a/Marlin/src/feature/bedlevel/bedlevel.h b/Marlin/src/feature/bedlevel/bedlevel.h index ccb9543e72e5..5bfa2b7fafe0 100644 --- a/Marlin/src/feature/bedlevel/bedlevel.h +++ b/Marlin/src/feature/bedlevel/bedlevel.h @@ -38,7 +38,7 @@ void set_bed_leveling_enabled(const bool enable=true); void reset_bed_level(); #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) - void set_z_fade_height(const_float_t zfh, const bool do_report=true); + void set_z_fade_height(const float zfh, const bool do_report=true); #endif #if ANY(MESH_BED_LEVELING, PROBE_MANUALLY) diff --git a/Marlin/src/feature/bedlevel/hilbert_curve.cpp b/Marlin/src/feature/bedlevel/hilbert_curve.cpp index 57cbdfb34ddd..b57991297ed3 100644 --- a/Marlin/src/feature/bedlevel/hilbert_curve.cpp +++ b/Marlin/src/feature/bedlevel/hilbert_curve.cpp @@ -102,8 +102,8 @@ bool hilbert_curve::search_from(uint8_t x, uint8_t y, hilbert_curve::callback_pt */ bool hilbert_curve::search_from_closest(const xy_pos_t &pos, hilbert_curve::callback_ptr func, void *data) { // Find closest grid intersection - const uint8_t grid_x = LROUND(constrain(float(pos.x - (MESH_MIN_X)) / (MESH_X_DIST), 0, (GRID_MAX_POINTS_X) - 1)); - const uint8_t grid_y = LROUND(constrain(float(pos.y - (MESH_MIN_Y)) / (MESH_Y_DIST), 0, (GRID_MAX_POINTS_Y) - 1)); + const uint8_t grid_x = LROUND(constrain((pos.x - mesh_min.x) / (MESH_X_DIST), 0, (GRID_MAX_POINTS_X) - 1)); + const uint8_t grid_y = LROUND(constrain((pos.y - mesh_min.y) / (MESH_Y_DIST), 0, (GRID_MAX_POINTS_Y) - 1)); return search_from(grid_x, grid_y, func, data); } diff --git a/Marlin/src/feature/bedlevel/mbl/mesh_bed_leveling.cpp b/Marlin/src/feature/bedlevel/mbl/mesh_bed_leveling.cpp index f40cbccadd79..93e50b649857 100644 --- a/Marlin/src/feature/bedlevel/mbl/mesh_bed_leveling.cpp +++ b/Marlin/src/feature/bedlevel/mbl/mesh_bed_leveling.cpp @@ -39,14 +39,21 @@ mesh_bed_leveling::index_to_xpos[GRID_MAX_POINTS_X], mesh_bed_leveling::index_to_ypos[GRID_MAX_POINTS_Y]; - mesh_bed_leveling::mesh_bed_leveling() { + mesh_bed_leveling::mesh_bed_leveling() { initialize(); } + + void mesh_bed_leveling::initialize() { for (uint8_t i = 0; i < GRID_MAX_POINTS_X; ++i) - index_to_xpos[i] = MESH_MIN_X + i * (MESH_X_DIST); + index_to_xpos[i] = mesh_min.x + i * (MESH_X_DIST); for (uint8_t i = 0; i < GRID_MAX_POINTS_Y; ++i) - index_to_ypos[i] = MESH_MIN_Y + i * (MESH_Y_DIST); + index_to_ypos[i] = mesh_min.y + i * (MESH_Y_DIST); reset(); } + void mesh_bed_leveling::report_mesh() { + SERIAL_ECHOLN(F(STRINGIFY(GRID_MAX_POINTS_X) "x" STRINGIFY(GRID_MAX_POINTS_Y) " mesh. Z offset: "), p_float_t(z_offset, 5), F("\nMeasured points:")); + print_2d_array(GRID_MAX_POINTS_X, GRID_MAX_POINTS_Y, 5, z_values[0]); + } + void mesh_bed_leveling::reset() { z_offset = 0; ZERO(z_values); @@ -61,9 +68,9 @@ * Prepare a mesh-leveled linear move in a Cartesian setup, * splitting the move where it crosses mesh borders. */ - void mesh_bed_leveling::line_to_destination(const_feedRate_t scaled_fr_mm_s, uint8_t x_splits, uint8_t y_splits) { + void mesh_bed_leveling::line_to_destination(const feedRate_t scaled_fr_mm_s, uint8_t x_splits, uint8_t y_splits) { // Get current and destination cells for this line - xy_int8_t scel = cell_indexes(current_position), ecel = cell_indexes(destination); + xy_uint8_t scel = cell_indexes(motion.position), ecel = cell_indexes(motion.destination); NOMORE(scel.x, GRID_MAX_CELLS_X - 1); NOMORE(scel.y, GRID_MAX_CELLS_Y - 1); NOMORE(ecel.x, GRID_MAX_CELLS_X - 1); @@ -71,60 +78,55 @@ // Start and end in the same cell? No split needed. if (scel == ecel) { - current_position = destination; - line_to_current_position(scaled_fr_mm_s); + motion.position = motion.destination; + motion.goto_current_position(scaled_fr_mm_s); return; } - #define MBL_SEGMENT_END(A) (current_position.A + (destination.A - current_position.A) * normalized_dist) + #define MBL_SEGMENT_END(A) (motion.position.A + (motion.destination.A - motion.position.A) * normalized_dist) float normalized_dist; xyze_pos_t dest; - const int8_t gcx = _MAX(scel.x, ecel.x), gcy = _MAX(scel.y, ecel.y); + const uint8_t gcx = _MAX(scel.x, ecel.x), gcy = _MAX(scel.y, ecel.y); // Crosses on the X and not already split on this X? // The x_splits flags are insurance against rounding errors. if (ecel.x != scel.x && TEST(x_splits, gcx)) { // Split on the X grid line CBI(x_splits, gcx); - dest = destination; - destination.x = index_to_xpos[gcx]; - normalized_dist = (destination.x - current_position.x) / (dest.x - current_position.x); - destination.y = MBL_SEGMENT_END(y); + dest = motion.destination; + motion.destination.x = index_to_xpos[gcx]; + normalized_dist = (motion.destination.x - motion.position.x) / (dest.x - motion.position.x); + motion.destination.y = MBL_SEGMENT_END(y); } // Crosses on the Y and not already split on this Y? else if (ecel.y != scel.y && TEST(y_splits, gcy)) { // Split on the Y grid line CBI(y_splits, gcy); - dest = destination; - destination.y = index_to_ypos[gcy]; - normalized_dist = (destination.y - current_position.y) / (dest.y - current_position.y); - destination.x = MBL_SEGMENT_END(x); + dest = motion.destination; + motion.destination.y = index_to_ypos[gcy]; + normalized_dist = (motion.destination.y - motion.position.y) / (dest.y - motion.position.y); + motion.destination.x = MBL_SEGMENT_END(x); } else { // Must already have been split on these border(s) // This should be a rare case. - current_position = destination; - line_to_current_position(scaled_fr_mm_s); + motion.position = motion.destination; + motion.goto_current_position(scaled_fr_mm_s); return; } - destination.z = MBL_SEGMENT_END(z); - destination.e = MBL_SEGMENT_END(e); + motion.destination.z = MBL_SEGMENT_END(z); + motion.destination.e = MBL_SEGMENT_END(e); // Do the split and look for more borders line_to_destination(scaled_fr_mm_s, x_splits, y_splits); // Restore destination from stack - destination = dest; + motion.destination = dest; line_to_destination(scaled_fr_mm_s, x_splits, y_splits); } #endif // IS_CARTESIAN && !SEGMENT_LEVELED_MOVES - void mesh_bed_leveling::report_mesh() { - SERIAL_ECHOLN(F(STRINGIFY(GRID_MAX_POINTS_X) "x" STRINGIFY(GRID_MAX_POINTS_Y) " mesh. Z offset: "), p_float_t(z_offset, 5), F("\nMeasured points:")); - print_2d_array(GRID_MAX_POINTS_X, GRID_MAX_POINTS_Y, 5, z_values[0]); - } - #endif // MESH_BED_LEVELING diff --git a/Marlin/src/feature/bedlevel/mbl/mesh_bed_leveling.h b/Marlin/src/feature/bedlevel/mbl/mesh_bed_leveling.h index cb4f36cd59f2..92791d3f38c0 100644 --- a/Marlin/src/feature/bedlevel/mbl/mesh_bed_leveling.h +++ b/Marlin/src/feature/bedlevel/mbl/mesh_bed_leveling.h @@ -32,8 +32,7 @@ enum MeshLevelingState : char { MeshReset // G29 S5 }; -#define MESH_X_DIST (float((MESH_MAX_X) - (MESH_MIN_X)) / (GRID_MAX_CELLS_X)) -#define MESH_Y_DIST (float((MESH_MAX_Y) - (MESH_MIN_Y)) / (GRID_MAX_CELLS_Y)) +#include "../../../module/motion.h" class mesh_bed_leveling { public: @@ -44,10 +43,12 @@ class mesh_bed_leveling { mesh_bed_leveling(); - static void report_mesh(); - static void reset(); + static void initialize(); + + static void report_mesh(); + FORCE_INLINE static bool has_mesh() { GRID_LOOP(x, y) if (z_values[x][y]) return true; return false; @@ -55,7 +56,7 @@ class mesh_bed_leveling { static bool mesh_is_valid() { return has_mesh(); } - static void set_z(const int8_t px, const int8_t py, const_float_t z) { z_values[px][py] = z; } + static void set_z(const int8_t px, const int8_t py, const float z) { z_values[px][py] = z; } static void zigzag(const int8_t index, int8_t &px, int8_t &py) { px = index % (GRID_MAX_POINTS_X); @@ -63,7 +64,7 @@ class mesh_bed_leveling { if (py & 1) px = (GRID_MAX_POINTS_X) - 1 - px; // Zig zag } - static void set_zigzag_z(const int8_t index, const_float_t z) { + static void set_zigzag_z(const int8_t index, const float z) { int8_t px, py; zigzag(index, px, py); set_z(px, py, z); @@ -72,33 +73,33 @@ class mesh_bed_leveling { static float get_mesh_x(const uint8_t i) { return index_to_xpos[i]; } static float get_mesh_y(const uint8_t i) { return index_to_ypos[i]; } - static uint8_t cell_index_x(const_float_t x) { - int8_t cx = (x - (MESH_MIN_X)) * RECIPROCAL(MESH_X_DIST); + static uint8_t cell_index_x(const float x) { + const int8_t cx = (x - mesh_min.x) * RECIPROCAL(MESH_X_DIST); return constrain(cx, 0, GRID_MAX_CELLS_X - 1); } - static uint8_t cell_index_y(const_float_t y) { - int8_t cy = (y - (MESH_MIN_Y)) * RECIPROCAL(MESH_Y_DIST); + static uint8_t cell_index_y(const float y) { + const int8_t cy = (y - mesh_min.y) * RECIPROCAL(MESH_Y_DIST); return constrain(cy, 0, GRID_MAX_CELLS_Y - 1); } - static xy_uint8_t cell_indexes(const_float_t x, const_float_t y) { + static xy_uint8_t cell_indexes(const float x, const float y) { return { cell_index_x(x), cell_index_y(y) }; } static xy_uint8_t cell_indexes(const xy_pos_t &xy) { return cell_indexes(xy.x, xy.y); } - static int8_t probe_index_x(const_float_t x) { - int8_t px = (x - (MESH_MIN_X) + 0.5f * (MESH_X_DIST)) * RECIPROCAL(MESH_X_DIST); + static int8_t probe_index_x(const float x) { + const int8_t px = (x - mesh_min.x + 0.5f * (MESH_X_DIST)) * RECIPROCAL(MESH_X_DIST); return WITHIN(px, 0, (GRID_MAX_POINTS_X) - 1) ? px : -1; } - static int8_t probe_index_y(const_float_t y) { - int8_t py = (y - (MESH_MIN_Y) + 0.5f * (MESH_Y_DIST)) * RECIPROCAL(MESH_Y_DIST); + static int8_t probe_index_y(const float y) { + const int8_t py = (y - mesh_min.y + 0.5f * (MESH_Y_DIST)) * RECIPROCAL(MESH_Y_DIST); return WITHIN(py, 0, (GRID_MAX_POINTS_Y) - 1) ? py : -1; } - static xy_int8_t probe_indexes(const_float_t x, const_float_t y) { + static xy_int8_t probe_indexes(const float x, const float y) { return { probe_index_x(x), probe_index_y(y) }; } static xy_int8_t probe_indexes(const xy_pos_t &xy) { return probe_indexes(xy.x, xy.y); } - static float calc_z0(const_float_t a0, const_float_t a1, const_float_t z1, const_float_t a2, const_float_t z2) { + static float calc_z0(const float a0, const float a1, const float z1, const float a2, const float z2) { const float delta_z = (z2 - z1) / (a2 - a1), delta_a = a0 - a1; return z1 + delta_a * delta_z; @@ -118,7 +119,7 @@ class mesh_bed_leveling { } #if IS_CARTESIAN && DISABLED(SEGMENT_LEVELED_MOVES) - static void line_to_destination(const_feedRate_t scaled_fr_mm_s, uint8_t x_splits=0xFF, uint8_t y_splits=0xFF); + static void line_to_destination(const feedRate_t scaled_fr_mm_s, uint8_t x_splits=0xFF, uint8_t y_splits=0xFF); #endif }; diff --git a/Marlin/src/feature/bedlevel/ubl/ubl.cpp b/Marlin/src/feature/bedlevel/ubl/ubl.cpp index 0228bd247ebd..8837029df9ac 100644 --- a/Marlin/src/feature/bedlevel/ubl/ubl.cpp +++ b/Marlin/src/feature/bedlevel/ubl/ubl.cpp @@ -28,7 +28,6 @@ unified_bed_leveling bedlevel; -#include "../../../MarlinCore.h" #include "../../../gcode/gcode.h" #include "../../../module/settings.h" @@ -62,25 +61,29 @@ void unified_bed_leveling::report_state() { serial_delay(50); } -int8_t unified_bed_leveling::storage_slot; - -float unified_bed_leveling::z_values[GRID_MAX_POINTS_X][GRID_MAX_POINTS_Y]; - -#define _GRIDPOS(A,N) (MESH_MIN_##A + N * (MESH_##A##_DIST)) +#if HAS_MESH_STORAGE + int8_t unified_bed_leveling::storage_slot; +#endif -const float -unified_bed_leveling::_mesh_index_to_xpos[GRID_MAX_POINTS_X] PROGMEM = ARRAY_N(GRID_MAX_POINTS_X, - _GRIDPOS(X, 0), _GRIDPOS(X, 1), _GRIDPOS(X, 2), _GRIDPOS(X, 3), - _GRIDPOS(X, 4), _GRIDPOS(X, 5), _GRIDPOS(X, 6), _GRIDPOS(X, 7), - _GRIDPOS(X, 8), _GRIDPOS(X, 9), _GRIDPOS(X, 10), _GRIDPOS(X, 11), - _GRIDPOS(X, 12), _GRIDPOS(X, 13), _GRIDPOS(X, 14), _GRIDPOS(X, 15) -), -unified_bed_leveling::_mesh_index_to_ypos[GRID_MAX_POINTS_Y] PROGMEM = ARRAY_N(GRID_MAX_POINTS_Y, - _GRIDPOS(Y, 0), _GRIDPOS(Y, 1), _GRIDPOS(Y, 2), _GRIDPOS(Y, 3), - _GRIDPOS(Y, 4), _GRIDPOS(Y, 5), _GRIDPOS(Y, 6), _GRIDPOS(Y, 7), - _GRIDPOS(Y, 8), _GRIDPOS(Y, 9), _GRIDPOS(Y, 10), _GRIDPOS(Y, 11), - _GRIDPOS(Y, 12), _GRIDPOS(Y, 13), _GRIDPOS(Y, 14), _GRIDPOS(Y, 15) -); +bed_mesh_t unified_bed_leveling::z_values; + +#if !HAS_PROUI_MESH_EDIT + #define _GRIDPOS(A,N) (MESH_MIN_##A + N * (MESH_##A##_DIST)) + + const float + unified_bed_leveling::_mesh_index_to_xpos[GRID_MAX_POINTS_X] PROGMEM = ARRAY_N(GRID_MAX_POINTS_X, + _GRIDPOS(X, 0), _GRIDPOS(X, 1), _GRIDPOS(X, 2), _GRIDPOS(X, 3), + _GRIDPOS(X, 4), _GRIDPOS(X, 5), _GRIDPOS(X, 6), _GRIDPOS(X, 7), + _GRIDPOS(X, 8), _GRIDPOS(X, 9), _GRIDPOS(X, 10), _GRIDPOS(X, 11), + _GRIDPOS(X, 12), _GRIDPOS(X, 13), _GRIDPOS(X, 14), _GRIDPOS(X, 15) + ), + unified_bed_leveling::_mesh_index_to_ypos[GRID_MAX_POINTS_Y] PROGMEM = ARRAY_N(GRID_MAX_POINTS_Y, + _GRIDPOS(Y, 0), _GRIDPOS(Y, 1), _GRIDPOS(Y, 2), _GRIDPOS(Y, 3), + _GRIDPOS(Y, 4), _GRIDPOS(Y, 5), _GRIDPOS(Y, 6), _GRIDPOS(Y, 7), + _GRIDPOS(Y, 8), _GRIDPOS(Y, 9), _GRIDPOS(Y, 10), _GRIDPOS(Y, 11), + _GRIDPOS(Y, 12), _GRIDPOS(Y, 13), _GRIDPOS(Y, 14), _GRIDPOS(Y, 15) + ); +#endif volatile int16_t unified_bed_leveling::encoder_diff; @@ -89,12 +92,12 @@ unified_bed_leveling::unified_bed_leveling() { reset(); } void unified_bed_leveling::reset() { const bool was_enabled = planner.leveling_active; set_bed_leveling_enabled(false); - storage_slot = -1; + TERN_(HAS_MESH_STORAGE, storage_slot = -1); ZERO(z_values); #if ENABLED(EXTENSIBLE_UI) GRID_LOOP(x, y) ExtUI::onMeshUpdate(x, y, 0); #endif - if (was_enabled) report_current_position(); + if (was_enabled) motion.report_position(); } void unified_bed_leveling::invalidate() { @@ -102,7 +105,7 @@ void unified_bed_leveling::invalidate() { set_all_mesh_points_to_value(NAN); } -void unified_bed_leveling::set_all_mesh_points_to_value(const_float_t value) { +void unified_bed_leveling::set_all_mesh_points_to_value(const float value) { GRID_LOOP(x, y) { z_values[x][y] = value; TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(x, y, value)); @@ -115,7 +118,7 @@ void unified_bed_leveling::set_all_mesh_points_to_value(const_float_t value) { constexpr int16_t Z_STEPS_NAN = INT16_MAX; void unified_bed_leveling::set_store_from_mesh(const bed_mesh_t &in_values, mesh_store_t &stored_values) { - auto z_to_store = [](const_float_t z) { + auto z_to_store = [](const float z) { if (isnan(z)) return Z_STEPS_NAN; const int32_t z_scaled = TRUNC(z * mesh_store_scaling); if (z_scaled == Z_STEPS_NAN || !WITHIN(z_scaled, INT16_MIN, INT16_MAX)) @@ -174,8 +177,8 @@ void unified_bed_leveling::display_map(const uint8_t map_type) { SERIAL_ECHOPGM("\nBed Topography Report"); if (human) { SERIAL_ECHOLNPGM(":\n"); - serial_echo_xy(4, MESH_MIN_X, MESH_MAX_Y); - serial_echo_xy(twixt, MESH_MAX_X, MESH_MAX_Y); + serial_echo_xy(4, mesh_min.x, mesh_max.y); + serial_echo_xy(twixt, mesh_max.x, mesh_max.y); SERIAL_EOL(); serial_echo_column_labels(eachsp - 2); } @@ -185,7 +188,7 @@ void unified_bed_leveling::display_map(const uint8_t map_type) { // Add XY probe offset from extruder because probe.probe_at_point() subtracts them when // moving to the XY position to be measured. This ensures better agreement between // the current Z position after G28 and the mesh values. - const xy_int8_t curr = closest_indexes(xy_pos_t(current_position) + probe.offset_xy); + const xy_int8_t curr = closest_indexes(xy_pos_t(motion.position) + probe.offset_xy); if (!lcd) SERIAL_EOL(); for (int8_t j = (GRID_MAX_POINTS_Y) - 1; j >= 0; j--) { @@ -208,6 +211,7 @@ void unified_bed_leveling::display_map(const uint8_t map_type) { const float f = z_values[i][j]; if (lcd) { // TODO: Display on Graphical LCD + TERN_(DWIN_LCD_PROUI, dwinMeshViewer()); } else if (isnan(f)) SERIAL_ECHO(human ? F(" . ") : F("NAN")); @@ -221,7 +225,7 @@ void unified_bed_leveling::display_map(const uint8_t map_type) { if (human) SERIAL_CHAR(is_current ? ']' : ' '); SERIAL_FLUSHTX(); - idle_no_sleep(); + marlin.idle_no_sleep(); } if (!lcd) SERIAL_EOL(); @@ -232,8 +236,8 @@ void unified_bed_leveling::display_map(const uint8_t map_type) { if (human) { serial_echo_column_labels(eachsp - 2); SERIAL_EOL(); - serial_echo_xy(4, MESH_MIN_X, MESH_MIN_Y); - serial_echo_xy(twixt, MESH_MAX_X, MESH_MIN_Y); + serial_echo_xy(4, mesh_min.x, mesh_min.y); + serial_echo_xy(twixt, mesh_max.x, mesh_min.y); SERIAL_EOL(); SERIAL_EOL(); } @@ -242,14 +246,16 @@ void unified_bed_leveling::display_map(const uint8_t map_type) { } bool unified_bed_leveling::sanity_check() { - uint8_t error_flag = 0; + bool error_flag = false; - if (settings.calc_num_meshes() < 1) { - SERIAL_ECHOLNPGM("?Mesh too big for EEPROM."); - error_flag++; - } + #if HAS_MESH_STORAGE + if (settings.calc_num_meshes() < 1) { + SERIAL_ECHOLNPGM("?Mesh too big for EEPROM."); + error_flag = true; + } + #endif - return !!error_flag; + return error_flag; } #if ENABLED(UBL_MESH_WIZARD) @@ -283,11 +289,10 @@ bool unified_bed_leveling::sanity_check() { PROBE_GCODE "\n" // Build mesh with available hardware "G29P3\nG29P3")); // Ensure mesh is complete by running smart fill twice - if (parser.seenval('S')) { - char umw_gcode[32]; - sprintf_P(umw_gcode, PSTR("G29S%i"), parser.value_int()); - queue.inject(umw_gcode); - } + #if HAS_MESH_STORAGE + if (parser.seenval('S')) + queue.inject(TS(F("G29S"), parser.value_int())); + #endif process_subcommands_now(F("G29A\nG29F10\n" // Set UBL Active & Fade 10 "M140S0\nM104S0\n" // Turn off heaters diff --git a/Marlin/src/feature/bedlevel/ubl/ubl.h b/Marlin/src/feature/bedlevel/ubl/ubl.h index 7377f7dfc03a..ce42b777a129 100644 --- a/Marlin/src/feature/bedlevel/ubl/ubl.h +++ b/Marlin/src/feature/bedlevel/ubl/ubl.h @@ -38,16 +38,15 @@ enum MeshPointType : char { INVALID, REAL, SET_IN_BITMAP, CLOSEST }; struct mesh_index_pair; -#define MESH_X_DIST (float((MESH_MAX_X) - (MESH_MIN_X)) / (GRID_MAX_CELLS_X)) -#define MESH_Y_DIST (float((MESH_MAX_Y) - (MESH_MIN_Y)) / (GRID_MAX_CELLS_Y)) - #if ENABLED(OPTIMIZED_MESH_STORAGE) typedef int16_t mesh_store_t[GRID_MAX_POINTS_X][GRID_MAX_POINTS_Y]; #endif typedef struct { bool C_seen; - int8_t KLS_storage_slot; + #if HAS_MESH_STORAGE + int8_t KLS_storage_slot; + #endif grid_count_t R_repetition; uint8_t V_verbosity, P_phase, @@ -67,15 +66,15 @@ class unified_bed_leveling { static G29_parameters_t param; #if IS_NEWPANEL - static void move_z_with_encoder(const_float_t multiplier); + static void move_z_with_encoder(const float multiplier); static float measure_point_with_encoder(); static float measure_business_card_thickness(); - static void manually_probe_remaining_mesh(const xy_pos_t&, const_float_t , const_float_t , const bool) __O0; + static void manually_probe_remaining_mesh(const xy_pos_t&, const float, const float, const bool) __O0; static void fine_tune_mesh(const xy_pos_t &pos, const bool do_ubl_mesh_map) __O0; #endif static bool G29_parse_parameters() __O0; - static void shift_mesh_height(); + static void shift_mesh_height(const float zoffs); static void probe_entire_mesh(const xy_pos_t &near, const bool do_ubl_mesh_map, const bool stow_probe, const bool do_furthest) __O0; static void tilt_mesh_based_on_probed_grid(const bool do_ubl_mesh_map); static bool smart_fill_one(const uint8_t x, const uint8_t y, const int8_t xdir, const int8_t ydir); @@ -83,10 +82,22 @@ class unified_bed_leveling { return smart_fill_one(pos.x, pos.y, dir.x, dir.y); } + // G29 sub-function handlers + static void G29_handle_homing_and_setup(); + static void G29_handle_invalidate(); + static bool G29_handle_test_patterns(); + #if HAS_BED_PROBE + static void G29_handle_tilt_mesh(); + #endif + static bool G29_handle_phase_ops(); + static void G29_handle_post_processing(); + #if ENABLED(UBL_DEVEL_DEBUGGING) static void g29_what_command(); - static void g29_eeprom_dump(); - static void g29_compare_current_mesh_to_stored_mesh(); + #if HAS_MESH_STORAGE + static void g29_eeprom_dump(); + static void g29_compare_current_mesh_to_stored_mesh(); + #endif #endif public: @@ -95,29 +106,34 @@ class unified_bed_leveling { static void report_current_mesh(); static void report_state(); static void save_ubl_active_state_and_disable(); - static void restore_ubl_active_state_and_leave(); + static void restore_ubl_active_state(const bool is_done=true); static void display_map(const uint8_t) __O0; static mesh_index_pair find_closest_mesh_point_of_type(const MeshPointType, const xy_pos_t&, const bool=false, MeshFlags *done_flags=nullptr) __O0; static mesh_index_pair find_furthest_invalid_mesh_point() __O0; static void reset(); static void invalidate(); - static void set_all_mesh_points_to_value(const_float_t value); - static void adjust_mesh_to_mean(const bool cflag, const_float_t value); + static void set_all_mesh_points_to_value(const float value); + static void adjust_mesh_to_mean(const bool cflag, const float value); static bool sanity_check(); static void smart_fill_mesh(); static void G29() __O0; // O0 for no optimization - static void smart_fill_wlsf(const_float_t ) __O2; // O2 gives smaller code than Os on A2560 + static void smart_fill_wlsf(const float ) __O2; // O2 gives smaller code than Os on A2560 - static int8_t storage_slot; + #if HAS_MESH_STORAGE + static int8_t storage_slot; + #endif static bed_mesh_t z_values; #if ENABLED(OPTIMIZED_MESH_STORAGE) static void set_store_from_mesh(const bed_mesh_t &in_values, mesh_store_t &stored_values); static void set_mesh_from_store(const mesh_store_t &stored_values, bed_mesh_t &out_values); #endif - static const float _mesh_index_to_xpos[GRID_MAX_POINTS_X], - _mesh_index_to_ypos[GRID_MAX_POINTS_Y]; + + #if !HAS_PROUI_MESH_EDIT + static const float _mesh_index_to_xpos[GRID_MAX_POINTS_X], + _mesh_index_to_ypos[GRID_MAX_POINTS_Y]; + #endif #if HAS_MARLINUI_MENU static bool lcd_map_control; @@ -130,43 +146,43 @@ class unified_bed_leveling { unified_bed_leveling(); - FORCE_INLINE static void set_z(const int8_t px, const int8_t py, const_float_t z) { z_values[px][py] = z; } + FORCE_INLINE static void set_z(const int8_t px, const int8_t py, const float z) { z_values[px][py] = z; } - static int8_t cell_index_x_raw(const_float_t x) { - return FLOOR((x - (MESH_MIN_X)) * RECIPROCAL(MESH_X_DIST)); + static int8_t cell_index_x_raw(const float x) { + return FLOOR((x - mesh_min.x) * RECIPROCAL(MESH_X_DIST)); } - static int8_t cell_index_y_raw(const_float_t y) { - return FLOOR((y - (MESH_MIN_Y)) * RECIPROCAL(MESH_Y_DIST)); + static int8_t cell_index_y_raw(const float y) { + return FLOOR((y - mesh_min.y) * RECIPROCAL(MESH_Y_DIST)); } - static bool cell_index_x_valid(const_float_t x) { + static bool cell_index_x_valid(const float x) { return WITHIN(cell_index_x_raw(x), 0, GRID_MAX_CELLS_X - 1); } - static bool cell_index_y_valid(const_float_t y) { + static bool cell_index_y_valid(const float y) { return WITHIN(cell_index_y_raw(y), 0, GRID_MAX_CELLS_Y - 1); } - static uint8_t cell_index_x(const_float_t x) { + static uint8_t cell_index_x(const float x) { return constrain(cell_index_x_raw(x), 0, GRID_MAX_CELLS_X - 1); } - static uint8_t cell_index_y(const_float_t y) { + static uint8_t cell_index_y(const float y) { return constrain(cell_index_y_raw(y), 0, GRID_MAX_CELLS_Y - 1); } - static xy_uint8_t cell_indexes(const_float_t x, const_float_t y) { + static xy_uint8_t cell_indexes(const float x, const float y) { return { cell_index_x(x), cell_index_y(y) }; } static xy_uint8_t cell_indexes(const xy_pos_t &xy) { return cell_indexes(xy.x, xy.y); } - static int8_t closest_x_index(const_float_t x) { - const int8_t px = (x - (MESH_MIN_X) + (MESH_X_DIST) * 0.5) * RECIPROCAL(MESH_X_DIST); + static int8_t closest_x_index(const float x) { + const int8_t px = (x - mesh_min.x + (MESH_X_DIST) * 0.5) * RECIPROCAL(MESH_X_DIST); return WITHIN(px, 0, (GRID_MAX_POINTS_X) - 1) ? px : -1; } - static int8_t closest_y_index(const_float_t y) { - const int8_t py = (y - (MESH_MIN_Y) + (MESH_Y_DIST) * 0.5) * RECIPROCAL(MESH_Y_DIST); + static int8_t closest_y_index(const float y) { + const int8_t py = (y - mesh_min.y + (MESH_Y_DIST) * 0.5) * RECIPROCAL(MESH_Y_DIST); return WITHIN(py, 0, (GRID_MAX_POINTS_Y) - 1) ? py : -1; } static xy_int8_t closest_indexes(const xy_pos_t &xy) { @@ -188,7 +204,7 @@ class unified_bed_leveling { * It is fairly expensive with its 4 floating point additions and 2 floating point * multiplications. */ - FORCE_INLINE static float calc_z0(const_float_t a0, const_float_t a1, const_float_t z1, const_float_t a2, const_float_t z2) { + FORCE_INLINE static float calc_z0(const float a0, const float a1, const float z1, const float a2, const float z2) { return z1 + (z2 - z1) * (a0 - a1) / (a2 - a1); } @@ -202,7 +218,7 @@ class unified_bed_leveling { * z_correction_for_x_on_horizontal_mesh_line is an optimization for * the case where the printer is making a vertical line that only crosses horizontal mesh lines. */ - static float z_correction_for_x_on_horizontal_mesh_line(const_float_t rx0, const int x1_i, const int yi) { + static float z_correction_for_x_on_horizontal_mesh_line(const float rx0, const int x1_i, const int yi) { if (!WITHIN(x1_i, 0, (GRID_MAX_POINTS_X) - 1) || !WITHIN(yi, 0, (GRID_MAX_POINTS_Y) - 1)) { if (DEBUGGING(LEVELING)) { @@ -225,7 +241,7 @@ class unified_bed_leveling { // // See comments above for z_correction_for_x_on_horizontal_mesh_line // - static float z_correction_for_y_on_vertical_mesh_line(const_float_t ry0, const int xi, const int y1_i) { + static float z_correction_for_y_on_vertical_mesh_line(const float ry0, const int xi, const int y1_i) { if (!WITHIN(xi, 0, (GRID_MAX_POINTS_X) - 1) || !WITHIN(y1_i, 0, (GRID_MAX_POINTS_Y) - 1)) { if (DEBUGGING(LEVELING)) { @@ -251,7 +267,7 @@ class unified_bed_leveling { * Z-Height at both ends. Then it does a linear interpolation of these heights based * on the Y position within the cell. */ - static float get_z_correction(const_float_t rx0, const_float_t ry0) { + static float get_z_correction(const float rx0, const float ry0) { const int8_t cx = cell_index_x(rx0), cy = cell_index_y(ry0); // return values are clamped /** @@ -259,7 +275,7 @@ class unified_bed_leveling { * UBL_Z_RAISE_WHEN_OFF_MESH is specified, that value is returned. */ #ifdef UBL_Z_RAISE_WHEN_OFF_MESH - if (!WITHIN(rx0, MESH_MIN_X, MESH_MAX_X) || !WITHIN(ry0, MESH_MIN_Y, MESH_MAX_Y)) + if (!WITHIN(rx0, mesh_min.x, mesh_max.x) || !WITHIN(ry0, mesh_min.y, mesh_max.y)) return UBL_Z_RAISE_WHEN_OFF_MESH; #endif @@ -287,17 +303,25 @@ class unified_bed_leveling { static constexpr float get_z_offset() { return 0.0f; } - static float get_mesh_x(const uint8_t i) { - return i < (GRID_MAX_POINTS_X) ? pgm_read_float(&_mesh_index_to_xpos[i]) : MESH_MIN_X + i * (MESH_X_DIST); - } - static float get_mesh_y(const uint8_t i) { - return i < (GRID_MAX_POINTS_Y) ? pgm_read_float(&_mesh_index_to_ypos[i]) : MESH_MIN_Y + i * (MESH_Y_DIST); - } + static float _get_mesh_x(const uint8_t i) { return mesh_min.x + i * (MESH_X_DIST); } + static float _get_mesh_y(const uint8_t i) { return mesh_min.y + i * (MESH_Y_DIST); } + + #if HAS_PROUI_MESH_EDIT + static float get_mesh_x(const uint8_t i) { return _get_mesh_x(i); } + static float get_mesh_y(const uint8_t i) { return _get_mesh_y(i); } + #else + static float get_mesh_x(const uint8_t i) { + return i < (GRID_MAX_POINTS_X) ? pgm_read_float(&_mesh_index_to_xpos[i]) : _get_mesh_x(i); + } + static float get_mesh_y(const uint8_t i) { + return i < (GRID_MAX_POINTS_Y) ? pgm_read_float(&_mesh_index_to_ypos[i]) : _get_mesh_y(i); + } + #endif #if UBL_SEGMENTED - static bool line_to_destination_segmented(const_feedRate_t scaled_fr_mm_s); + static bool line_to_destination_segmented(const feedRate_t scaled_fr_mm_s); #else - static void line_to_destination_cartesian(const_feedRate_t scaled_fr_mm_s, const uint8_t e); + static void line_to_destination_cartesian(const feedRate_t scaled_fr_mm_s, const uint8_t e); #endif static bool mesh_is_valid() { diff --git a/Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp b/Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp index 2e7ea743273f..c0c8355b9af3 100644 --- a/Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp +++ b/Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp @@ -26,7 +26,6 @@ #include "../bedlevel.h" -#include "../../../MarlinCore.h" #include "../../../HAL/shared/eeprom_api.h" #include "../../../libs/hex_print.h" #include "../../../module/settings.h" @@ -44,10 +43,18 @@ #include "../../../lcd/extui/ui_api.h" #endif +#if ENABLED(DWIN_LCD_PROUI) + #include "../../../lcd/dwin/proui/meshviewer.h" +#endif + #if ENABLED(UBL_HILBERT_CURVE) #include "../hilbert_curve.h" #endif +#if ENABLED(FT_MOTION) + #include "../../../module/ft_motion.h" +#endif + #include #define UBL_G29_P31 @@ -239,8 +246,8 @@ * current state of the Unified Bed Leveling system in the EEPROM. * * S # Store Store the current Mesh at the specified location in EEPROM. Activate this location - * for subsequent Load and Store operations. Valid storage slot numbers begin at 0 and - * extend to a limit related to the available EEPROM storage. + * for subsequent Load and Store operations. Valid storage slot numbers range from 0 + * to the highest index permitted, based on available EEPROM storage. * * S -1 Store Print the current Mesh as G-code that can be used to restore the mesh anytime. * @@ -295,308 +302,49 @@ G29_parameters_t unified_bed_leveling::param; +#ifdef EVENT_GCODE_AFTER_G29 + bool ubl_probe_deployed = false; + #define SET_UBL_PROBE_DEPLOYED(N) ubl_probe_deployed = N +#else + #define SET_UBL_PROBE_DEPLOYED(N) +#endif + void unified_bed_leveling::G29() { + SET_UBL_PROBE_DEPLOYED(false); - bool probe_deployed = false; if (G29_parse_parameters()) return; // Abort on parameter error const uint8_t p_val = parser.byteval('P'); const bool may_move = p_val == 1 || p_val == 2 || p_val == 4 || parser.seen_test('J'); - // Check for commands that require the printer to be homed - if (may_move) { - planner.synchronize(); - #if ALL(DWIN_LCD_PROUI, ZHOME_BEFORE_LEVELING) - save_ubl_active_state_and_disable(); - gcode.process_subcommands_now(F("G28Z")); - restore_ubl_active_state_and_leave(); - #else - // Send 'N' to force homing before G29 (internal only) - if (axes_should_home() || parser.seen_test('N')) gcode.home_all_axes(); - #endif - probe.use_probing_tool(); + // Potentially disable Fixed-Time Motion for probing + TERN_(FT_MOTION, FTM_DISABLE_IN_SCOPE()); - // Position bed horizontally and Z probe vertically. - #if HAS_SAFE_BED_LEVELING - xyze_pos_t safe_position = current_position; - #ifdef SAFE_BED_LEVELING_START_X - safe_position.x = SAFE_BED_LEVELING_START_X; - #endif - #ifdef SAFE_BED_LEVELING_START_Y - safe_position.y = SAFE_BED_LEVELING_START_Y; - #endif - #ifdef SAFE_BED_LEVELING_START_Z - safe_position.z = SAFE_BED_LEVELING_START_Z; - #endif - #ifdef SAFE_BED_LEVELING_START_I - safe_position.i = SAFE_BED_LEVELING_START_I; - #endif - #ifdef SAFE_BED_LEVELING_START_J - safe_position.j = SAFE_BED_LEVELING_START_J; - #endif - #ifdef SAFE_BED_LEVELING_START_K - safe_position.k = SAFE_BED_LEVELING_START_K; - #endif - #ifdef SAFE_BED_LEVELING_START_U - safe_position.u = SAFE_BED_LEVELING_START_U; - #endif - #ifdef SAFE_BED_LEVELING_START_V - safe_position.v = SAFE_BED_LEVELING_START_V; - #endif - #ifdef SAFE_BED_LEVELING_START_W - safe_position.w = SAFE_BED_LEVELING_START_W; - #endif + // Handle homing and initial setup + if (may_move) G29_handle_homing_and_setup(); - do_blocking_move_to(safe_position); - #endif // HAS_SAFE_BED_LEVELING - } + // Handle mesh invalidation (I parameter) + if (parser.seen('I')) G29_handle_invalidate(); - // Invalidate one or more nearby mesh points, possibly all. - if (parser.seen('I')) { - grid_count_t count = parser.has_value() ? parser.value_ushort() : 1; - bool invalidate_all = count >= GRID_MAX_POINTS; - if (!invalidate_all) { - while (count--) { - if ((count & 0x0F) == 0x0F) idle(); - const mesh_index_pair closest = find_closest_mesh_point_of_type(REAL, param.XY_pos); - // No more REAL mesh points to invalidate? Assume the user meant - // to invalidate the ENTIRE mesh, which can't be done with - // find_closest_mesh_point (which only returns REAL points). - if (closest.pos.x < 0) { invalidate_all = true; break; } - z_values[closest.pos.x][closest.pos.y] = NAN; - TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(closest.pos, 0.0f)); - } - } - if (invalidate_all) { - invalidate(); - SERIAL_ECHOPGM("Entire Mesh"); - } - else - SERIAL_ECHOPGM("Locations"); - SERIAL_ECHOLNPGM(" invalidated.\n"); - } - - if (parser.seen('Q')) { - const int16_t test_pattern = parser.has_value() ? parser.value_int() : -99; - if (!WITHIN(test_pattern, TERN0(UBL_DEVEL_DEBUGGING, -1), 2)) { - SERIAL_ECHOLNPGM("?Invalid (Q) test pattern. (" TERN(UBL_DEVEL_DEBUGGING, "-1", "0") " to 2)\n"); - return; - } - SERIAL_ECHOLNPGM("Applying test pattern.\n"); - switch (test_pattern) { - - default: - case -1: TERN_(UBL_DEVEL_DEBUGGING, g29_eeprom_dump()); break; - - case 0: - GRID_LOOP(x, y) { // Create a bowl shape similar to a poorly-calibrated Delta - const float p1 = 0.5f * (GRID_MAX_POINTS_X) - x, - p2 = 0.5f * (GRID_MAX_POINTS_Y) - y; - z_values[x][y] += 2.0f * HYPOT(p1, p2); - TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(x, y, z_values[x][y])); - } - break; - - case 1: - for (uint8_t x = 0; x < GRID_MAX_POINTS_X; ++x) { // Create a diagonal line several Mesh cells thick that is raised - const uint8_t x2 = x + (x < (GRID_MAX_POINTS_Y) - 1 ? 1 : -1); - z_values[x][x] += 9.999f; - z_values[x][x2] += 9.999f; // We want the altered line several mesh points thick - #if ENABLED(EXTENSIBLE_UI) - ExtUI::onMeshUpdate(x, x, z_values[x][x]); - ExtUI::onMeshUpdate(x, x2, z_values[x][x2]); - #endif - } - break; - - case 2: - // Allow the user to specify the height because 10mm is a little extreme in some cases. - for (uint8_t x = (GRID_MAX_POINTS_X) / 3; x < 2 * (GRID_MAX_POINTS_X) / 3; x++) // Create a rectangular raised area in - for (uint8_t y = (GRID_MAX_POINTS_Y) / 3; y < 2 * (GRID_MAX_POINTS_Y) / 3; y++) { // the center of the bed - z_values[x][y] += parser.seen_test('C') ? param.C_constant : 9.99f; - TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(x, y, z_values[x][y])); - } - break; - } - } + // Handle test patterns (Q parameter) + if (parser.seen('Q') && !G29_handle_test_patterns()) return; + // Handle tilt mesh (J parameter) #if HAS_BED_PROBE + if (parser.seen_test('J')) G29_handle_tilt_mesh(); + #endif - if (parser.seen_test('J')) { - save_ubl_active_state_and_disable(); - tilt_mesh_based_on_probed_grid(param.J_grid_size == 0); // Zero size does 3-Point - restore_ubl_active_state_and_leave(); - #if ENABLED(UBL_G29_J_RECENTER) - do_blocking_move_to_xy(0.5f * ((MESH_MIN_X) + (MESH_MAX_X)), 0.5f * ((MESH_MIN_Y) + (MESH_MAX_Y))); - #endif - report_current_position(); - probe_deployed = true; - } - - #endif // HAS_BED_PROBE - - if (parser.seen_test('P')) { - if (WITHIN(param.P_phase, 0, 1) && storage_slot == -1) { - storage_slot = 0; - SERIAL_ECHOLNPGM("Default storage slot 0 selected."); - } - - switch (param.P_phase) { - case 0: - // - // Zero Mesh Data - // - reset(); - SERIAL_ECHOLNPGM("Mesh zeroed."); - break; - - #if HAS_BED_PROBE - - case 1: { - // - // Invalidate Entire Mesh and Automatically Probe Mesh in areas that can be reached by the probe - // - if (!parser.seen_test('C')) { - invalidate(); - SERIAL_ECHOLNPGM("Mesh invalidated. Probing mesh."); - } - if (param.V_verbosity > 1) - SERIAL_ECHOLN(F("Probing around ("), param.XY_pos.x, C(','), param.XY_pos.y, F(").\n")); - probe_entire_mesh(param.XY_pos, parser.seen_test('T'), parser.seen_test('E'), parser.seen_test('U')); - - report_current_position(); - probe_deployed = true; - } break; - - #endif // HAS_BED_PROBE - - case 2: { - #if HAS_MARLINUI_MENU - // - // Manually Probe Mesh in areas that can't be reached by the probe - // - SERIAL_ECHOLNPGM("Manually probing unreachable points."); - do_z_clearance(Z_CLEARANCE_BETWEEN_PROBES); - - if (parser.seen_test('C') && !param.XY_seen) { - - /** - * Use a good default location for the path. - * The flipped > and < operators in these comparisons is intentional. - * It should cause the probed points to follow a nice path on Cartesian printers. - * It may make sense to have Delta printers default to the center of the bed. - * Until that is decided, this can be forced with the X and Y parameters. - */ - param.XY_pos.set( - #if IS_KINEMATIC - X_HOME_POS, Y_HOME_POS - #else - probe.offset_xy.x > 0 ? X_BED_SIZE : 0, - probe.offset_xy.y < 0 ? Y_BED_SIZE : 0 - #endif - ); - } - - if (parser.seen('B')) { - param.B_shim_thickness = parser.has_value() ? parser.value_float() : measure_business_card_thickness(); - if (ABS(param.B_shim_thickness) > 1.5f) { - SERIAL_ECHOLNPGM("?Error in Business Card measurement."); - return; - } - probe_deployed = true; - } - - if (!position_is_reachable(param.XY_pos)) { - SERIAL_ECHOLNPGM("XY outside printable radius."); - return; - } - - const float height = parser.floatval('H', Z_CLEARANCE_BETWEEN_PROBES); - manually_probe_remaining_mesh(param.XY_pos, height, param.B_shim_thickness, parser.seen_test('T')); - - SERIAL_ECHOLNPGM("G29 P2 finished."); - - report_current_position(); - - #else - - SERIAL_ECHOLNPGM("?P2 is only available when an LCD is present."); - return; - - #endif - } break; - - case 3: { - /** - * Populate invalid mesh areas. Proceed with caution. - * Two choices are available: - * - Specify a constant with the 'C' parameter. - * - Allow 'G29 P3' to choose a 'reasonable' constant. - */ - - if (param.C_seen) { - if (param.R_repetition >= GRID_MAX_POINTS) { - set_all_mesh_points_to_value(param.C_constant); - } - else { - while (param.R_repetition--) { // this only populates reachable mesh points near - const mesh_index_pair closest = find_closest_mesh_point_of_type(INVALID, param.XY_pos); - const xy_int8_t &cpos = closest.pos; - if (cpos.x < 0) { - // No more REAL INVALID mesh points to populate, so we ASSUME - // user meant to populate ALL INVALID mesh points to value - GRID_LOOP(x, y) if (isnan(z_values[x][y])) z_values[x][y] = param.C_constant; - break; // No more invalid Mesh Points to populate - } - else { - z_values[cpos.x][cpos.y] = param.C_constant; - TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(cpos, param.C_constant)); - } - } - } - } - else { - const float cvf = parser.value_float(); - switch ((int)TRUNC(cvf * 10.0f) - 30) { // 3.1 -> 1 - #if ENABLED(UBL_G29_P31) - case 1: { - - // P3.1 use least squares fit to fill missing mesh values - // P3.10 zero weighting for distance, all grid points equal, best fit tilted plane - // P3.11 10X weighting for nearest grid points versus farthest grid points - // P3.12 100X distance weighting - // P3.13 1000X distance weighting, approaches simple average of nearest points - - const float weight_power = (cvf - 3.10f) * 100.0f, // 3.12345 -> 2.345 - weight_factor = weight_power ? POW(10.0f, weight_power) : 0; - smart_fill_wlsf(weight_factor); - } - break; - #endif - case 0: // P3 or P3.0 - default: // and anything P3.x that's not P3.1 - smart_fill_mesh(); // Do a 'Smart' fill using nearby known values - break; - } - } - break; - } - - case 4: // Fine Tune (i.e., Edit) the Mesh - #if HAS_MARLINUI_MENU - fine_tune_mesh(param.XY_pos, parser.seen_test('T')); - #else - SERIAL_ECHOLNPGM("?P4 is only available when an LCD is present."); - return; - #endif - break; - - case 5: adjust_mesh_to_mean(param.C_seen, param.C_constant); break; + // Handle phase operations (P parameter) + if (parser.seen_test('P') && !G29_handle_phase_ops()) return; - case 6: shift_mesh_height(); break; - } - } + // Handle post-processing and cleanup + G29_handle_post_processing(); +} +/** + * Handle post-processing and cleanup + */ +void unified_bed_leveling::G29_handle_post_processing() { #if ENABLED(UBL_DEVEL_DEBUGGING) // @@ -620,52 +368,72 @@ void unified_bed_leveling::G29() { // if (parser.seen('L')) { // Load Current Mesh Data - param.KLS_storage_slot = parser.has_value() ? (int8_t)parser.value_int() : storage_slot; - int16_t a = settings.calc_num_meshes(); + #if !HAS_MESH_STORAGE - if (!a) { SERIAL_ECHOLNPGM("?EEPROM storage not available."); return; - } - if (!WITHIN(param.KLS_storage_slot, 0, a - 1)) { - SERIAL_ECHOLNPGM("?Invalid storage slot.\n?Use 0 to ", a - 1); - return; - } + #else // HAS_MESH_STORAGE + + param.KLS_storage_slot = parser.has_value() ? (int8_t)parser.value_int() : storage_slot; + + const int16_t a = settings.calc_num_meshes(); + + if (!a) { + SERIAL_ECHOLNPGM("?EEPROM storage not available."); + return; + } + + if (!WITHIN(param.KLS_storage_slot, 0, a - 1)) { + SERIAL_ECHOLN(F("?Invalid "), F("storage slot. (0.."), a - 1, C(')')); + return; + } + + settings.load_mesh(param.KLS_storage_slot); + storage_slot = param.KLS_storage_slot; - settings.load_mesh(param.KLS_storage_slot); - storage_slot = param.KLS_storage_slot; + SERIAL_ECHOLNPGM(STR_DONE); - SERIAL_ECHOLNPGM(STR_DONE); + #endif // HAS_MESH_STORAGE } // // Store a Mesh in the EEPROM // - if (parser.seen('S')) { // Store (or Save) Current Mesh Data - param.KLS_storage_slot = parser.has_value() ? (int8_t)parser.value_int() : storage_slot; + else if (parser.seen('S')) { // Store (or Save) Current Mesh Data - if (param.KLS_storage_slot == -1) // Special case: 'Export' the mesh to the - return report_current_mesh(); // host so it can be saved in a file. + #if !HAS_MESH_STORAGE - int16_t a = settings.calc_num_meshes(); - - if (!a) { SERIAL_ECHOLNPGM("?EEPROM storage not available."); goto LEAVE; - } - if (!WITHIN(param.KLS_storage_slot, 0, a - 1)) { - SERIAL_ECHOLNPGM("?Invalid storage slot.\n?Use 0 to ", a - 1); - goto LEAVE; - } + #else // HAS_MESH_STORAGE + + param.KLS_storage_slot = parser.has_value() ? (int8_t)parser.value_int() : storage_slot; - settings.store_mesh(param.KLS_storage_slot); - storage_slot = param.KLS_storage_slot; + if (param.KLS_storage_slot == -1) // Special case: 'Export' the mesh to the + return report_current_mesh(); // host so it can be saved in a file. - SERIAL_ECHOLNPGM(STR_DONE); + const int16_t a = settings.calc_num_meshes(); + + if (!a) { + SERIAL_ECHOLNPGM("?EEPROM storage not available."); + goto LEAVE; + } + + if (!WITHIN(param.KLS_storage_slot, 0, a - 1)) { + SERIAL_ECHOLN(F("?Invalid "), F("storage slot. (0.."), a - 1, C(')')); + goto LEAVE; + } + + settings.store_mesh(param.KLS_storage_slot); + storage_slot = param.KLS_storage_slot; + + SERIAL_ECHOLNPGM(STR_DONE); + + #endif // HAS_MESH_STORAGE } if (parser.seen_test('T')) @@ -681,17 +449,350 @@ void unified_bed_leveling::G29() { #endif #ifdef EVENT_GCODE_AFTER_G29 - if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("Z Probe End Script: ", EVENT_GCODE_AFTER_G29); - if (probe_deployed) { + if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("After G29 G-code: ", EVENT_GCODE_AFTER_G29); + if (ubl_probe_deployed) { planner.synchronize(); gcode.process_subcommands_now(F(EVENT_GCODE_AFTER_G29)); } - #else - UNUSED(probe_deployed); #endif probe.use_probing_tool(false); - return; +} + +/** + * Handle phase operations (P parameter) + * @return true if successful, false if error occurred + */ +bool unified_bed_leveling::G29_handle_phase_ops() { + + #if HAS_MESH_STORAGE + if (WITHIN(param.P_phase, 0, 1) && storage_slot == -1) { + storage_slot = 0; + SERIAL_ECHOLNPGM("Default storage slot 0 selected."); + } + #endif + + switch (param.P_phase) { + case 0: + // + // Zero Mesh Data + // + reset(); + SERIAL_ECHOLNPGM("Mesh zeroed."); + break; + + #if HAS_BED_PROBE + + case 1: { + // + // Invalidate Entire Mesh and Automatically Probe Mesh in areas that can be reached by the probe + // + if (!parser.seen_test('C')) { + invalidate(); + SERIAL_ECHOLNPGM("Mesh invalidated. Probing mesh."); + } + if (param.V_verbosity > 1) + SERIAL_ECHOLN(F("Probing around ("), param.XY_pos.x, C(','), param.XY_pos.y, F(").\n")); + probe_entire_mesh(param.XY_pos, parser.seen_test('T'), parser.seen_test('E'), parser.seen_test('U')); + + motion.report_position(); + SET_UBL_PROBE_DEPLOYED(true); + } break; + + #endif // HAS_BED_PROBE + + case 2: { + #if HAS_MARLINUI_MENU + // + // Manually Probe Mesh in areas that can't be reached by the probe + // + SERIAL_ECHOLNPGM("Manually probing unreachable points."); + motion.do_z_clearance(Z_CLEARANCE_BETWEEN_PROBES); + + if (parser.seen_test('C') && !param.XY_seen) { + + /** + * Use a good default location for the path. + * The flipped > and < operators in these comparisons is intentional. + * It should cause the probed points to follow a nice path on Cartesian printers. + * It may make sense to have Delta printers default to the center of the bed. + * Until that is decided, this can be forced with the X and Y parameters. + */ + param.XY_pos.set( + #if IS_KINEMATIC + X_HOME_POS, Y_HOME_POS + #else + probe.offset_xy.x > 0 ? X_BED_SIZE : 0, + probe.offset_xy.y < 0 ? Y_BED_SIZE : 0 + #endif + ); + } + + if (parser.seen('B')) { + param.B_shim_thickness = parser.has_value() ? parser.value_float() : measure_business_card_thickness(); + if (ABS(param.B_shim_thickness) > 1.5f) { + SERIAL_ECHOLNPGM("?Error in Business Card measurement."); + return false; + } + SET_UBL_PROBE_DEPLOYED(true); + } + + if (!motion.can_reach(param.XY_pos)) { + SERIAL_ECHOLNPGM("XY outside printable radius."); + return false; + } + + const float height = parser.floatval('H', Z_CLEARANCE_BETWEEN_PROBES); + manually_probe_remaining_mesh(param.XY_pos, height, param.B_shim_thickness, parser.seen_test('T')); + + SERIAL_ECHOLNPGM("G29 P2 finished."); + + motion.report_position(); + + #else + + SERIAL_ECHOLNPGM("?P2 is only available when an LCD is present."); + return false; + + #endif + } break; + + case 3: { + /** + * Populate invalid mesh areas. Proceed with caution. + * Two choices are available: + * - Specify a constant with the 'C' parameter. + * - Allow 'G29 P3' to choose a 'reasonable' constant. + */ + + if (param.C_seen) { + if (param.R_repetition >= GRID_MAX_POINTS) { + set_all_mesh_points_to_value(param.C_constant); + } + else { + while (param.R_repetition--) { // this only populates reachable mesh points near + const mesh_index_pair closest = find_closest_mesh_point_of_type(INVALID, param.XY_pos); + const xy_int8_t &cpos = closest.pos; + if (cpos.x < 0) { + // No more REAL INVALID mesh points to populate, so we ASSUME + // user meant to populate ALL INVALID mesh points to value + GRID_LOOP(x, y) if (isnan(z_values[x][y])) z_values[x][y] = param.C_constant; + break; // No more invalid Mesh Points to populate + } + else { + z_values[cpos.x][cpos.y] = param.C_constant; + TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(cpos, param.C_constant)); + } + } + } + } + else { + const float cvf = parser.value_float(); + switch ((int)TRUNC(cvf * 10.0f) - 30) { // 3.1 -> 1 + #if ENABLED(UBL_G29_P31) + case 1: { + + // P3.1 use least squares fit to fill missing mesh values + // P3.10 zero weighting for distance, all grid points equal, best fit tilted plane + // P3.11 10X weighting for nearest grid points versus farthest grid points + // P3.12 100X distance weighting + // P3.13 1000X distance weighting, approaches simple average of nearest points + + const float weight_power = (cvf - 3.10f) * 100.0f, // 3.12345 -> 2.345 + weight_factor = weight_power ? POW(10.0f, weight_power) : 0; + smart_fill_wlsf(weight_factor); + } + break; + #endif + case 0: // P3 or P3.0 + default: // and anything P3.x that's not P3.1 + smart_fill_mesh(); // Do a 'Smart' fill using nearby known values + break; + } + } + break; + } + + case 4: // Fine Tune (i.e., Edit) the Mesh + #if HAS_MARLINUI_MENU + fine_tune_mesh(param.XY_pos, parser.seen_test('T')); + #else + SERIAL_ECHOLNPGM("?P4 is only available when an LCD is present."); + return false; + #endif + break; + + case 5: adjust_mesh_to_mean(param.C_seen, param.C_constant); break; + + case 6: shift_mesh_height(param.C_constant); break; + + default: + SERIAL_ECHOLNPGM("?Invalid P value.\n"); + return false; + } + + return true; +} + +/** + * Handle homing and initial setup for G29 command + * @param may_move Whether the command requires printer movement + * @return true if successful, false if error occurred + */ +void unified_bed_leveling::G29_handle_homing_and_setup() { + planner.synchronize(); + #if ALL(DWIN_LCD_PROUI, ZHOME_BEFORE_LEVELING) + save_ubl_active_state_and_disable(); + gcode.process_subcommands_now(F("G28Z")); + restore_ubl_active_state(false); // ...without telling ExtUI "done" + #else + // Send 'N' to force homing before G29 (internal only) + if (motion.axes_should_home() || parser.seen_test('N')) gcode.home_all_axes(); + #endif + probe.use_probing_tool(); + + #ifdef EVENT_GCODE_BEFORE_G29 + if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("Before G29 G-code: ", EVENT_GCODE_BEFORE_G29); + gcode.process_subcommands_now(F(EVENT_GCODE_BEFORE_G29)); + #endif + + // Position bed horizontally and Z probe vertically. + #if HAS_SAFE_BED_LEVELING + xyze_pos_t safe_position = motion.position; + #ifdef SAFE_BED_LEVELING_START_X + safe_position.x = SAFE_BED_LEVELING_START_X; + #endif + #ifdef SAFE_BED_LEVELING_START_Y + safe_position.y = SAFE_BED_LEVELING_START_Y; + #endif + #ifdef SAFE_BED_LEVELING_START_Z + safe_position.z = SAFE_BED_LEVELING_START_Z; + #endif + #ifdef SAFE_BED_LEVELING_START_I + safe_position.i = SAFE_BED_LEVELING_START_I; + #endif + #ifdef SAFE_BED_LEVELING_START_J + safe_position.j = SAFE_BED_LEVELING_START_J; + #endif + #ifdef SAFE_BED_LEVELING_START_K + safe_position.k = SAFE_BED_LEVELING_START_K; + #endif + #ifdef SAFE_BED_LEVELING_START_U + safe_position.u = SAFE_BED_LEVELING_START_U; + #endif + #ifdef SAFE_BED_LEVELING_START_V + safe_position.v = SAFE_BED_LEVELING_START_V; + #endif + #ifdef SAFE_BED_LEVELING_START_W + safe_position.w = SAFE_BED_LEVELING_START_W; + #endif + motion.blocking_move(safe_position); + #endif // HAS_SAFE_BED_LEVELING +} + +/** + * Handle mesh invalidation (I parameter) + */ +void unified_bed_leveling::G29_handle_invalidate() { + // Invalidate one or more nearby mesh points, possibly all. + grid_count_t count = parser.has_value() ? parser.value_ushort() : 1; + bool invalidate_all = count >= GRID_MAX_POINTS; + if (!invalidate_all) { + while (count--) { + if ((count & 0x0F) == 0x0F) marlin.idle(); + const mesh_index_pair closest = find_closest_mesh_point_of_type(REAL, param.XY_pos); + // No more REAL mesh points to invalidate? Assume the user meant + // to invalidate the ENTIRE mesh, which can't be done with + // find_closest_mesh_point (which only returns REAL points). + if (closest.pos.x < 0) { invalidate_all = true; break; } + z_values[closest.pos.x][closest.pos.y] = NAN; + TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(closest.pos, 0.0f)); + } + } + if (invalidate_all) { + invalidate(); + SERIAL_ECHOPGM("Entire Mesh"); + } + else + SERIAL_ECHOPGM("Locations"); + SERIAL_ECHOLNPGM(" invalidated.\n"); +} + +#if HAS_BED_PROBE + + /** + * Handle tilt mesh (J parameter) + */ + void unified_bed_leveling::G29_handle_tilt_mesh() { + save_ubl_active_state_and_disable(); + tilt_mesh_based_on_probed_grid(param.J_grid_size == 0); // Zero size does 3-Point + restore_ubl_active_state(); + #if ENABLED(UBL_G29_J_RECENTER) + motion.blocking_move_xy(0.5f * (mesh_min.x + mesh_max.x), 0.5f * (mesh_min.y + mesh_max.y)); + #endif + motion.report_position(); + SET_UBL_PROBE_DEPLOYED(true); + } + +#endif // HAS_BED_PROBE + +/** + * Handle test patterns (Q parameter) + * @return true if successful, false if error occurred + */ +bool unified_bed_leveling::G29_handle_test_patterns() { + const int16_t test_pattern = parser.has_value() ? parser.value_int() : -99; + if (!WITHIN(test_pattern, TERN0(UBL_DEVEL_DEBUGGING, -1), 2)) { + SERIAL_ECHOLN(F("?Invalid "), F("(Q) test pattern. (" TERN(UBL_DEVEL_DEBUGGING, "-1", "0") " to 2)\n")); + return false; + } + SERIAL_ECHOLNPGM("Applying test pattern.\n"); + switch (test_pattern) { + + default: + case -1: TERN_(UBL_DEVEL_DEBUGGING, g29_eeprom_dump()); break; + + case 0: + // Create a bowl shape similar to a poorly-calibrated Delta + GRID_LOOP(x, y) { + const float p1 = 0.5f * (GRID_MAX_POINTS_X) - x, + p2 = 0.5f * (GRID_MAX_POINTS_Y) - y; + z_values[x][y] += 2.0f * HYPOT(p1, p2); + TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(x, y, z_values[x][y])); + } + break; + + case 1: + // Create a diagonal line several Mesh cells thick that is raised + for (uint8_t x = 0; x < GRID_MAX_POINTS_X; ++x) { + const uint8_t x2 = x + (x < (GRID_MAX_POINTS_Y) - 1 ? 1 : -1); + z_values[x][x] += 9.999f; + z_values[x][x2] += 9.999f; // We want the altered line several mesh points thick + #if ENABLED(EXTENSIBLE_UI) + ExtUI::onMeshUpdate(x, x, z_values[x][x]); + ExtUI::onMeshUpdate(x, x2, z_values[x][x2]); + #endif + } + break; + + case 2: + // Allow the user to specify the height because 10mm is a little extreme in some cases. + for (uint8_t x = (GRID_MAX_POINTS_X) / 3; x < 2 * (GRID_MAX_POINTS_X) / 3; x++) // Create a rectangular raised area in + for (uint8_t y = (GRID_MAX_POINTS_Y) / 3; y < 2 * (GRID_MAX_POINTS_Y) / 3; y++) { // the center of the bed + z_values[x][y] += parser.seen_test('C') ? param.C_constant : 9.99f; + TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(x, y, z_values[x][y])); + } + break; + } + return true; +} + +void unified_bed_leveling::shift_mesh_height(const float zoffs) { + GRID_LOOP(x, y) + if (!isnan(z_values[x][y])) { + z_values[x][y] += zoffs; + TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(x, y, z_values[x][y])); + } } /** @@ -699,7 +800,7 @@ void unified_bed_leveling::G29() { * G29 P5 C : Adjust Mesh To Mean (and subtract the given offset). * Find the mean average and shift the mesh to center on that value. */ -void unified_bed_leveling::adjust_mesh_to_mean(const bool cflag, const_float_t offset) { +void unified_bed_leveling::adjust_mesh_to_mean(const bool cflag, const float offset) { float sum = 0; uint8_t n = 0; GRID_LOOP(x, y) @@ -732,17 +833,6 @@ void unified_bed_leveling::adjust_mesh_to_mean(const bool cflag, const_float_t o } } -/** - * G29 P6 C : Shift Mesh Height by a uniform constant. - */ -void unified_bed_leveling::shift_mesh_height() { - GRID_LOOP(x, y) - if (!isnan(z_values[x][y])) { - z_values[x][y] += param.C_constant; - TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(x, y, z_values[x][y])); - } -} - #if HAS_BED_PROBE /** * G29 P1 T V : Probe Entire Mesh @@ -754,7 +844,6 @@ void unified_bed_leveling::shift_mesh_height() { TERN_(HAS_MARLINUI_MENU, ui.capture()); TERN_(EXTENSIBLE_UI, ExtUI::onLevelingStart()); - TERN_(DWIN_LCD_PROUI, dwinLevelingStart()); save_ubl_active_state_and_disable(); // No bed level correction so only raw data is obtained grid_count_t count = GRID_MAX_POINTS; @@ -768,7 +857,10 @@ void unified_bed_leveling::shift_mesh_height() { SERIAL_ECHOLNPGM("Probing mesh point ", point_num, "/", GRID_MAX_POINTS, "."); TERN_(HAS_STATUS_MESSAGE, ui.status_printf(0, F(S_FMT " %i/%i"), GET_TEXT(MSG_PROBING_POINT), point_num, int(GRID_MAX_POINTS))); TERN_(HAS_BACKLIGHT_TIMEOUT, ui.refresh_backlight_timeout()); - TERN_(DWIN_LCD_PROUI, dwinRedrawScreen()); + + #if ENABLED(DWIN_LCD_PROUI) + if (!hmiFlag.cancel_lev) dwinRedrawScreen(); else break; + #endif #if HAS_MARLINUI_MENU if (ui.button_pressed()) { @@ -778,8 +870,7 @@ void unified_bed_leveling::shift_mesh_height() { ui.quick_feedback(); ui.release(); probe.stow(); // Release UI before stow to allow for PAUSE_BEFORE_DEPLOY_STOW - TERN_(EXTENSIBLE_UI, ExtUI::onLevelingDone()); - return restore_ubl_active_state_and_leave(); + return restore_ubl_active_state(); } #endif @@ -804,6 +895,9 @@ void unified_bed_leveling::shift_mesh_height() { } while (best.pos.x >= 0 && --count); + if (TERN0(DWIN_LCD_PROUI, hmiFlag.cancel_lev)) + goto EXIT_PROBE_MESH; + GRID_LOOP(x, y) if (z_values[x][y] == HUGE_VALF) z_values[x][y] = NAN; // Restore NAN for HUGE_VALF marks TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(best.pos, ExtUI::G29_FINISH)); @@ -815,15 +909,18 @@ void unified_bed_leveling::shift_mesh_height() { probe.move_z_after_probing(); - restore_ubl_active_state_and_leave(); + #if ENABLED(DWIN_LCD_PROUI) + bedlevel.smart_fill_mesh(); + #else + motion.blocking_move_xy( + constrain(nearby.x - probe.offset_xy.x, mesh_min.x, mesh_max.x), + constrain(nearby.y - probe.offset_xy.y, mesh_min.y, mesh_max.y) + ); + #endif - do_blocking_move_to_xy( - constrain(nearby.x - probe.offset_xy.x, MESH_MIN_X, MESH_MAX_X), - constrain(nearby.y - probe.offset_xy.y, MESH_MIN_Y, MESH_MAX_Y) - ); + EXIT_PROBE_MESH: - TERN_(EXTENSIBLE_UI, ExtUI::onLevelingDone()); - TERN_(DWIN_LCD_PROUI, dwinLevelingDone()); + restore_ubl_active_state(); } #endif // HAS_BED_PROBE @@ -846,7 +943,7 @@ void set_message_with_feedback(FSTR_P const fstr) { ui.quick_feedback(false); // Preserve button state for click-and-hold const millis_t nxt = millis() + 1500UL; while (ui.button_pressed()) { // Loop while the encoder is pressed. Uses hardware flag! - idle(); // idle, of course + marlin.idle(); // idle, of course if (ELAPSED(millis(), nxt)) { // After 1.5 seconds ui.quick_feedback(); if (func) (*func)(); @@ -859,13 +956,13 @@ void set_message_with_feedback(FSTR_P const fstr) { return false; } - void unified_bed_leveling::move_z_with_encoder(const_float_t multiplier) { + void unified_bed_leveling::move_z_with_encoder(const float multiplier) { ui.wait_for_release(); while (!ui.button_pressed()) { - idle(); + marlin.idle(); gcode.reset_stepper_timeout(); // Keep steppers powered if (encoder_diff) { - do_blocking_move_to_z(current_position.z + float(encoder_diff) * multiplier); + motion.blocking_move_z(motion.position.z + float(encoder_diff) * multiplier); encoder_diff = 0; } } @@ -875,7 +972,7 @@ void set_message_with_feedback(FSTR_P const fstr) { KEEPALIVE_STATE(PAUSED_FOR_USER); const float z_step = 0.01f; move_z_with_encoder(z_step); - return current_position.z; + return motion.position.z; } static void echo_and_take_a_measurement() { SERIAL_ECHOLNPGM(" and take a measurement."); } @@ -884,10 +981,10 @@ void set_message_with_feedback(FSTR_P const fstr) { ui.capture(); save_ubl_active_state_and_disable(); // Disable bed level correction for probing - do_blocking_move_to( + motion.blocking_move( xyz_pos_t({ - 0.5f * ((MESH_MAX_X) - (MESH_MIN_X)), - 0.5f * ((MESH_MAX_Y) - (MESH_MIN_Y)), + 0.5f * (mesh_min.x + mesh_max.x), + 0.5f * (mesh_min.y + mesh_max.y), MANUAL_PROBE_START_Z #ifdef SAFE_BED_LEVELING_START_I , SAFE_BED_LEVELING_START_I @@ -918,21 +1015,21 @@ void set_message_with_feedback(FSTR_P const fstr) { echo_and_take_a_measurement(); const float z1 = measure_point_with_encoder(); - do_z_clearance_by(SIZE_OF_LITTLE_RAISE); + motion.do_z_clearance_by(SIZE_OF_LITTLE_RAISE); SERIAL_ECHOPGM("Remove shim"); LCD_MESSAGE(MSG_UBL_BC_REMOVE); echo_and_take_a_measurement(); const float z2 = measure_point_with_encoder(); - do_z_clearance_by(Z_CLEARANCE_BETWEEN_PROBES); + motion.do_z_clearance_by(Z_CLEARANCE_BETWEEN_PROBES); const float thickness = ABS(z1 - z2); if (param.V_verbosity > 1) SERIAL_ECHOLNPGM("Business Card is ", p_float_t(thickness, 4), "mm thick."); - restore_ubl_active_state_and_leave(); + restore_ubl_active_state(); return thickness; } @@ -942,12 +1039,12 @@ void set_message_with_feedback(FSTR_P const fstr) { * Move to INVALID points and * NOTE: Blocks the G-code queue and captures Marlin UI during use. */ - void unified_bed_leveling::manually_probe_remaining_mesh(const xy_pos_t &pos, const_float_t z_clearance, const_float_t thick, const bool do_ubl_mesh_map) { + void unified_bed_leveling::manually_probe_remaining_mesh(const xy_pos_t &pos, const float z_clearance, const float thick, const bool do_ubl_mesh_map) { ui.capture(); TERN_(EXTENSIBLE_UI, ExtUI::onLevelingStart()); save_ubl_active_state_and_disable(); // No bed level correction so only raw data is obtained - do_blocking_move_to_xy_z(current_position, z_clearance); + motion.blocking_move_xy_z(motion.position, z_clearance); ui.return_to_status(); @@ -960,12 +1057,12 @@ void set_message_with_feedback(FSTR_P const fstr) { const xyz_pos_t ppos = { get_mesh_x(lpos.x), get_mesh_y(lpos.y), z_clearance }; - if (!position_is_reachable(ppos)) break; // SHOULD NOT OCCUR (find_closest_mesh_point only returns reachable points) + if (!motion.can_reach(ppos)) break; // SHOULD NOT OCCUR (find_closest_mesh_point only returns reachable points) LCD_MESSAGE(MSG_UBL_MOVING_TO_NEXT); - do_blocking_move_to(ppos); - do_z_clearance(z_clearance); + motion.blocking_move(ppos); + motion.do_z_clearance(z_clearance); KEEPALIVE_STATE(PAUSED_FOR_USER); ui.capture(); @@ -986,11 +1083,11 @@ void set_message_with_feedback(FSTR_P const fstr) { if (_click_and_hold([]{ SERIAL_ECHOLNPGM("\nMesh only partially populated."); - do_z_clearance(Z_CLEARANCE_DEPLOY_PROBE); - })) return restore_ubl_active_state_and_leave(); + motion.do_z_clearance(Z_CLEARANCE_DEPLOY_PROBE); + })) return restore_ubl_active_state(); // Store the Z position minus the shim height - z_values[lpos.x][lpos.y] = current_position.z - thick; + z_values[lpos.x][lpos.y] = motion.position.z - thick; // Tell the external UI to update TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(location, z_values[lpos.x][lpos.y])); @@ -1002,10 +1099,8 @@ void set_message_with_feedback(FSTR_P const fstr) { if (do_ubl_mesh_map) display_map(param.T_map_type); // show user where we're probing - restore_ubl_active_state_and_leave(); - do_blocking_move_to_xy_z(pos, Z_CLEARANCE_DEPLOY_PROBE); - - TERN_(EXTENSIBLE_UI, ExtUI::onLevelingDone()); + restore_ubl_active_state(); + motion.blocking_move_xy_z(pos, Z_CLEARANCE_DEPLOY_PROBE); } /** @@ -1026,7 +1121,7 @@ void set_message_with_feedback(FSTR_P const fstr) { mesh_index_pair location; - if (!position_is_reachable(pos)) { + if (!motion.can_reach(pos)) { SERIAL_ECHOLNPGM("(X,Y) outside printable radius."); return; } @@ -1036,7 +1131,7 @@ void set_message_with_feedback(FSTR_P const fstr) { LCD_MESSAGE(MSG_UBL_FINE_TUNE_MESH); ui.capture(); // Take over control of the LCD encoder - do_blocking_move_to_xy_z(pos, Z_TWEEN_SAFE_CLEARANCE); // Move to the given XY with probe clearance + motion.blocking_move_xy_z(pos, Z_TWEEN_SAFE_CLEARANCE); // Move to the given XY with probe clearance MeshFlags done_flags{0}; const xy_int8_t &lpos = location.pos; @@ -1055,18 +1150,18 @@ void set_message_with_feedback(FSTR_P const fstr) { // location is used on the next loop const xyz_pos_t raw = { get_mesh_x(lpos.x), get_mesh_y(lpos.y), Z_TWEEN_SAFE_CLEARANCE }; - if (!position_is_reachable(raw)) break; // SHOULD NOT OCCUR (find_closest_mesh_point_of_type only returns reachable) + if (!motion.can_reach(raw)) break; // SHOULD NOT OCCUR (find_closest_mesh_point_of_type only returns reachable) - do_blocking_move_to(raw); // Move the nozzle to the edit point with probe clearance + motion.blocking_move(raw); // Move the nozzle to the edit point with probe clearance - TERN_(UBL_MESH_EDIT_MOVES_Z, do_blocking_move_to_z(h_offset)); // Move Z to the given 'H' offset before editing + TERN_(UBL_MESH_EDIT_MOVES_Z, motion.blocking_move_z(h_offset)); // Move Z to the given 'H' offset before editing KEEPALIVE_STATE(PAUSED_FOR_USER); if (do_ubl_mesh_map) display_map(param.T_map_type); // Display the current point #if IS_TFTGLCD_PANEL - ui.ubl_plot(lpos.x, lpos.y); // update plot screen + ui.ubl_plot(lpos.x, lpos.y); // Update plot screen #endif ui.refresh(); @@ -1077,23 +1172,23 @@ void set_message_with_feedback(FSTR_P const fstr) { ui.ubl_mesh_edit_start(new_z); - SET_SOFT_ENDSTOP_LOOSE(true); + motion.set_soft_endstop_loose(true); do { - idle_no_sleep(); + marlin.idle_no_sleep(); new_z = ui.ubl_mesh_value(); - TERN_(UBL_MESH_EDIT_MOVES_Z, do_blocking_move_to_z(h_offset + new_z)); // Move the nozzle as the point is edited + TERN_(UBL_MESH_EDIT_MOVES_Z, motion.blocking_move_z(h_offset + new_z)); // Move the nozzle as the point is edited SERIAL_FLUSH(); // Prevent host M105 buffer overrun. } while (!ui.button_pressed()); - SET_SOFT_ENDSTOP_LOOSE(false); + motion.set_soft_endstop_loose(false); if (!lcd_map_control) ui.return_to_status(); // Just editing a single point? Return to status // Button held down? Abort editing if (_click_and_hold([]{ ui.return_to_status(); - do_z_clearance(Z_TWEEN_SAFE_CLEARANCE); + motion.do_z_clearance(Z_TWEEN_SAFE_CLEARANCE); set_message_with_feedback(GET_TEXT_F(MSG_EDITING_STOPPED)); })) break; @@ -1111,9 +1206,9 @@ void set_message_with_feedback(FSTR_P const fstr) { } while (lpos.x >= 0 && --param.R_repetition > 0); if (do_ubl_mesh_map) display_map(param.T_map_type); - restore_ubl_active_state_and_leave(); + restore_ubl_active_state(); - do_blocking_move_to_xy_z(pos, Z_TWEEN_SAFE_CLEARANCE); + motion.blocking_move_xy_z(pos, Z_TWEEN_SAFE_CLEARANCE); LCD_MESSAGE(MSG_UBL_DONE_EDITING_MESH); SERIAL_ECHOLNPGM("Done Editing Mesh"); @@ -1153,16 +1248,16 @@ bool unified_bed_leveling::G29_parse_parameters() { } if (parser.seen('P')) { - const uint8_t pv = parser.value_byte(); + const uint8_t pval = parser.value_byte(); #if !HAS_BED_PROBE - if (pv == 1) { + if (pval == 1) { SERIAL_ECHOLNPGM("G29 P1 requires a probe.\n"); err_flag = true; } else #endif { - param.P_phase = pv; + param.P_phase = pval; if (!WITHIN(param.P_phase, 0, 6)) { SERIAL_ECHOLNPGM("?(P)hase value invalid (0-6).\n"); err_flag = true; @@ -1174,7 +1269,7 @@ bool unified_bed_leveling::G29_parse_parameters() { #if HAS_BED_PROBE param.J_grid_size = parser.value_byte(); if (param.J_grid_size && !WITHIN(param.J_grid_size, 2, 9)) { - SERIAL_ECHOLNPGM("?Invalid grid size (J) specified (2-9).\n"); + SERIAL_ECHOLN(F("?Invalid "), F("grid size (J) specified (2-9).\n")); err_flag = true; } #else @@ -1184,9 +1279,9 @@ bool unified_bed_leveling::G29_parse_parameters() { } param.XY_seen.x = parser.seenval('X'); - float sx = param.XY_seen.x ? parser.value_float() : current_position.x; + float sx = param.XY_seen.x ? parser.value_float() : DIFF_TERN(HAS_BED_PROBE, motion.position.x, probe.offset.x); param.XY_seen.y = parser.seenval('Y'); - float sy = param.XY_seen.y ? parser.value_float() : current_position.y; + float sy = param.XY_seen.y ? parser.value_float() : DIFF_TERN(HAS_BED_PROBE, motion.position.y, probe.offset.y); if (param.XY_seen.x != param.XY_seen.y) { SERIAL_ECHOLNPGM("Both X & Y locations must be specified.\n"); @@ -1195,8 +1290,8 @@ bool unified_bed_leveling::G29_parse_parameters() { // If X or Y are not valid, use center of the bed values // (for UBL_HILBERT_CURVE default to lower-left corner instead) - if (!COORDINATE_OKAY(sx, X_MIN_BED, X_MAX_BED)) sx = TERN(UBL_HILBERT_CURVE, 0, X_CENTER); - if (!COORDINATE_OKAY(sy, Y_MIN_BED, Y_MAX_BED)) sy = TERN(UBL_HILBERT_CURVE, 0, Y_CENTER); + if (!COORDINATE_OKAY(sx, X_MIN_BED, X_MAX_BED)) sx = TERN(UBL_HILBERT_CURVE, 0, DIFF_TERN(HAS_BED_PROBE, X_CENTER, probe.offset.x)); + if (!COORDINATE_OKAY(sy, Y_MIN_BED, Y_MAX_BED)) sy = TERN(UBL_HILBERT_CURVE, 0, DIFF_TERN(HAS_BED_PROBE, Y_CENTER, probe.offset.y)); if (err_flag) return UBL_ERR; @@ -1263,17 +1358,20 @@ void unified_bed_leveling::save_ubl_active_state_and_disable() { set_bed_leveling_enabled(false); } -void unified_bed_leveling::restore_ubl_active_state_and_leave() { +void unified_bed_leveling::restore_ubl_active_state(const bool is_done/*=true*/) { TERN_(HAS_MARLINUI_MENU, ui.release()); #if ENABLED(UBL_DEVEL_DEBUGGING) if (--ubl_state_recursion_chk) { - SERIAL_ECHOLNPGM("restore_ubl_active_state_and_leave() called too many times."); + SERIAL_ECHOLNPGM("restore_ubl_active_state() called too many times."); set_message_with_feedback(GET_TEXT_F(MSG_UBL_RESTORE_ERROR)); return; } #endif set_bed_leveling_enabled(ubl_state_at_invocation); - TERN_(EXTENSIBLE_UI, ExtUI::onLevelingDone()); + + if (is_done) { + TERN_(EXTENSIBLE_UI, ExtUI::onLevelingDone()); + } } mesh_index_pair unified_bed_leveling::find_furthest_invalid_mesh_point() { @@ -1348,7 +1446,7 @@ mesh_index_pair unified_bed_leveling::find_furthest_invalid_mesh_point() { // Also for round beds, there are grid points outside the bed the nozzle can't reach. // Prune them from the list and ignore them till the next Phase (manual nozzle probing). - if (!(d->probe_relative ? probe.can_reach(mpos) : position_is_reachable(mpos))) + if (!(d->probe_relative ? probe.can_reach(mpos) : motion.can_reach(mpos))) return false; d->closest.pos.set(i, j); return true; @@ -1392,12 +1490,12 @@ mesh_index_pair unified_bed_leveling::find_closest_mesh_point_of_type(const Mesh // Also for round beds, there are grid points outside the bed the nozzle can't reach. // Prune them from the list and ignore them till the next Phase (manual nozzle probing). - if (!(probe_relative ? probe.can_reach(mpos) : position_is_reachable(mpos))) + if (!(probe_relative ? probe.can_reach(mpos) : motion.can_reach(mpos))) continue; // Reachable. Check if it's the best_so_far location to the nozzle. - const xy_pos_t diff = current_position - mpos; + const xy_pos_t diff = motion.position - mpos; const float distance = (ref - mpos).magnitude() + diff.magnitude() * 0.1f; // factor in the distance from the current location for the normal case @@ -1525,10 +1623,10 @@ void unified_bed_leveling::smart_fill_mesh() { #ifndef G29J_MESH_TILT_MARGIN #define G29J_MESH_TILT_MARGIN 0 #endif - const float x_min = _MAX((X_MIN_POS) + (G29J_MESH_TILT_MARGIN), MESH_MIN_X, probe.min_x()), - x_max = _MIN((X_MAX_POS) - (G29J_MESH_TILT_MARGIN), MESH_MAX_X, probe.max_x()), - y_min = _MAX((Y_MIN_POS) + (G29J_MESH_TILT_MARGIN), MESH_MIN_Y, probe.min_y()), - y_max = _MIN((Y_MAX_POS) - (G29J_MESH_TILT_MARGIN), MESH_MAX_Y, probe.max_y()), + const float x_min = _MAX((X_MIN_POS) + (G29J_MESH_TILT_MARGIN), mesh_min.x, probe.min_x()), + x_max = _MIN((X_MAX_POS) - (G29J_MESH_TILT_MARGIN), mesh_max.x, probe.max_x()), + y_min = _MAX((Y_MIN_POS) + (G29J_MESH_TILT_MARGIN), mesh_min.y, probe.min_y()), + y_max = _MIN((Y_MAX_POS) - (G29J_MESH_TILT_MARGIN), mesh_max.y, probe.max_y()), dx = (x_max - x_min) / (param.J_grid_size - 1), dy = (y_max - y_min) / (param.J_grid_size - 1); @@ -1595,7 +1693,7 @@ void unified_bed_leveling::smart_fill_mesh() { } if (abort_flag) break; - zig_zag ^= true; + FLIP(zig_zag); } } probe.stow(); @@ -1649,10 +1747,10 @@ void unified_bed_leveling::smart_fill_mesh() { */ #if ENABLED(VALIDATE_MESH_TILT) auto d_from = []{ DEBUG_ECHOPGM("D from "); }; - auto normed = [&](const xy_pos_t &pos, const_float_t zadd) { + auto normed = [&](const xy_pos_t &pos, const float zadd) { return normal.x * pos.x + normal.y * pos.y + zadd; }; - auto debug_pt = [](const int num, const xy_pos_t &pos, const_float_t zadd) { + auto debug_pt = [](const int num, const xy_pos_t &pos, const float zadd) { d_from(); DEBUG_ECHOLN(F("Point "), num, C(':'), p_float_t(normed(pos, zadd), 6), F(" Z error = "), p_float_t(zadd - get_z_correction(pos), 6)); }; @@ -1673,7 +1771,7 @@ void unified_bed_leveling::smart_fill_mesh() { #endif // HAS_BED_PROBE #if ENABLED(UBL_G29_P31) - void unified_bed_leveling::smart_fill_wlsf(const_float_t weight_factor) { + void unified_bed_leveling::smart_fill_wlsf(const float weight_factor) { // For each undefined mesh point, compute a distance-weighted least squares fit // from all the originally populated mesh points, weighted toward the point @@ -1717,7 +1815,7 @@ void unified_bed_leveling::smart_fill_mesh() { const float ez = -lsf_results.D - lsf_results.A * ppos.x - lsf_results.B * ppos.y; z_values[ix][iy] = ez; TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(ix, iy, z_values[ix][iy])); - idle(); // housekeeping + marlin.idle(); // housekeeping } } } @@ -1734,11 +1832,13 @@ void unified_bed_leveling::smart_fill_mesh() { void unified_bed_leveling::g29_what_command() { report_state(); - if (storage_slot == -1) - SERIAL_ECHOLNPGM("No Mesh Loaded."); - else - SERIAL_ECHOLNPGM("Mesh ", storage_slot, " Loaded."); - serial_delay(50); + #if HAS_MESH_STORAGE + if (storage_slot == -1) + SERIAL_ECHOLNPGM("No Mesh Loaded."); + else + SERIAL_ECHOLNPGM("Mesh ", storage_slot, " Loaded."); + serial_delay(50); + #endif #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) SERIAL_ECHOLN(F("Fade Height M420 Z"), p_float_t(planner.z_fade_height, 4)); @@ -1750,31 +1850,29 @@ void unified_bed_leveling::smart_fill_mesh() { SERIAL_ECHOLNPGM("Probe Offset M851 Z", p_float_t(probe.offset.z, 7)); #endif - SERIAL_ECHOLNPGM("MESH_MIN_X " STRINGIFY(MESH_MIN_X) "=", MESH_MIN_X); serial_delay(50); - SERIAL_ECHOLNPGM("MESH_MIN_Y " STRINGIFY(MESH_MIN_Y) "=", MESH_MIN_Y); serial_delay(50); - SERIAL_ECHOLNPGM("MESH_MAX_X " STRINGIFY(MESH_MAX_X) "=", MESH_MAX_X); serial_delay(50); - SERIAL_ECHOLNPGM("MESH_MAX_Y " STRINGIFY(MESH_MAX_Y) "=", MESH_MAX_Y); serial_delay(50); - SERIAL_ECHOLNPGM("GRID_MAX_POINTS_X ", GRID_MAX_POINTS_X); serial_delay(50); - SERIAL_ECHOLNPGM("GRID_MAX_POINTS_Y ", GRID_MAX_POINTS_Y); serial_delay(50); + SERIAL_ECHOLNPGM("mesh_min ", mesh_min.x, ", ", mesh_min.y); serial_delay(50); + SERIAL_ECHOLNPGM("mesh_max ", mesh_max.x, ", ", mesh_max.y); serial_delay(50); + SERIAL_ECHOLNPGM("GRID_MAX_POINTS_X ", GRID_MAX_POINTS_X); serial_delay(50); + SERIAL_ECHOLNPGM("GRID_MAX_POINTS_Y ", GRID_MAX_POINTS_Y); serial_delay(50); SERIAL_ECHOLNPGM("MESH_X_DIST ", MESH_X_DIST); - SERIAL_ECHOLNPGM("MESH_Y_DIST ", MESH_Y_DIST); serial_delay(50); + SERIAL_ECHOLNPGM("MESH_Y_DIST ", MESH_Y_DIST); serial_delay(50); SERIAL_ECHOPGM("X-Axis Mesh Points at: "); for (uint8_t i = 0; i < GRID_MAX_POINTS_X; ++i) { - SERIAL_ECHO(p_float_t(LOGICAL_X_POSITION(get_mesh_x(i)), 3), F(" ")); + SERIAL_ECHO(p_float_t(motion.logical_x(get_mesh_x(i)), 3), F(" ")); serial_delay(25); } SERIAL_EOL(); SERIAL_ECHOPGM("Y-Axis Mesh Points at: "); for (uint8_t i = 0; i < GRID_MAX_POINTS_Y; ++i) { - SERIAL_ECHO(p_float_t(LOGICAL_Y_POSITION(get_mesh_y(i)), 3), F(" ")); + SERIAL_ECHO(p_float_t(motion.logical_y(get_mesh_y(i)), 3), F(" ")); serial_delay(25); } SERIAL_EOL(); #if HAS_KILL - SERIAL_ECHOLNPGM("Kill pin on :", KILL_PIN, " state:", kill_state()); + SERIAL_ECHOLNPGM("Kill pin on :", KILL_PIN, " state:", marlin.kill_state()); #endif SERIAL_EOL(); @@ -1783,18 +1881,19 @@ void unified_bed_leveling::smart_fill_mesh() { SERIAL_ECHOLNPGM("ubl_state_at_invocation :", ubl_state_at_invocation, "\nubl_state_recursion_chk :", ubl_state_recursion_chk); serial_delay(50); - SERIAL_ECHOLNPGM("Meshes go from ", hex_address((void*)settings.meshes_start_index()), " to ", hex_address((void*)settings.meshes_end_index())); + SERIAL_ECHOLNPGM("Meshes go from ", _hex_word(settings.meshes_start_index()), " to ", _hex_word(settings.meshes_end_index())); serial_delay(50); SERIAL_ECHOLNPGM("sizeof(unified_bed_leveling) : ", sizeof(unified_bed_leveling)); SERIAL_ECHOLNPGM("z_value[][] size: ", sizeof(z_values)); serial_delay(25); - SERIAL_ECHOLNPGM("EEPROM free for UBL: ", hex_address((void*)(settings.meshes_end_index() - settings.meshes_start_index()))); - serial_delay(50); - - SERIAL_ECHOLNPGM("EEPROM can hold ", settings.calc_num_meshes(), " meshes.\n"); - serial_delay(25); + #if HAS_MESH_STORAGE + SERIAL_ECHOLNPGM("EEPROM free for UBL: ", _hex_word(settings.meshes_end_index() - settings.meshes_start_index())); + serial_delay(50); + SERIAL_ECHOLNPGM("EEPROM can hold ", settings.calc_num_meshes(), " meshes.\n"); + serial_delay(25); + #endif if (!sanity_check()) { echo_name(); @@ -1802,60 +1901,64 @@ void unified_bed_leveling::smart_fill_mesh() { } } - /** - * When we are fully debugged, the EEPROM dump command will get deleted also. But - * right now, it is good to have the extra information. Soon... we prune this. - */ - void unified_bed_leveling::g29_eeprom_dump() { - uint8_t cccc; - - SERIAL_ECHO_MSG("EEPROM Dump:"); - persistentStore.access_start(); - for (uint16_t i = 0; i < persistentStore.capacity(); i += 16) { - if (!(i & 0x3)) idle(); - print_hex_word(i); - SERIAL_ECHOPGM(": "); - for (uint16_t j = 0; j < 16; j++) { - int pos = i + j; - persistentStore.read_data(pos, &cccc, sizeof(uint8_t)); - print_hex_byte(cccc); - SERIAL_CHAR(' '); + #if HAS_MESH_STORAGE + + /** + * When we are fully debugged, the EEPROM dump command will get deleted also. But + * right now, it is good to have the extra information. Soon... we prune this. + */ + void unified_bed_leveling::g29_eeprom_dump() { + uint8_t cccc; + + SERIAL_ECHO_MSG("EEPROM Dump:"); + persistentStore.access_start(); + for (uint16_t i = 0; i < persistentStore.capacity(); i += 16) { + if (!(i & 0x3)) marlin.idle(); + print_hex_word(i); + SERIAL_ECHOPGM(": "); + for (uint16_t j = 0; j < 16; j++) { + int pos = i + j; + persistentStore.read_data(pos, &cccc, sizeof(uint8_t)); + print_hex_byte(cccc); + SERIAL_CHAR(' '); + } + SERIAL_EOL(); } SERIAL_EOL(); + persistentStore.access_finish(); } - SERIAL_EOL(); - persistentStore.access_finish(); - } - /** - * When we are fully debugged, this may go away. But there are some valid - * use cases for the users. So we can wait and see what to do with it. - */ - void unified_bed_leveling::g29_compare_current_mesh_to_stored_mesh() { - const int16_t a = settings.calc_num_meshes(); + /** + * When we are fully debugged, this may go away. But there are some valid + * use cases for the users. So we can wait and see what to do with it. + */ + void unified_bed_leveling::g29_compare_current_mesh_to_stored_mesh() { + const int16_t a = settings.calc_num_meshes(); - if (!a) { - SERIAL_ECHOLNPGM("?EEPROM storage not available."); - return; - } + if (!a) { + SERIAL_ECHOLNPGM("?EEPROM storage not available."); + return; + } - if (!parser.has_value() || !WITHIN(parser.value_int(), 0, a - 1)) { - SERIAL_ECHOLNPGM("?Invalid storage slot.\n?Use 0 to ", a - 1); - return; - } + if (!parser.has_value() || !WITHIN(parser.value_int(), 0, a - 1)) { + SERIAL_ECHOLN(F("?Invalid "), F("storage slot. (0.."), a - 1, C(')')); + return; + } - param.KLS_storage_slot = (int8_t)parser.value_int(); + param.KLS_storage_slot = (int8_t)parser.value_int(); - float tmp_z_values[GRID_MAX_POINTS_X][GRID_MAX_POINTS_Y]; - settings.load_mesh(param.KLS_storage_slot, &tmp_z_values); + float tmp_z_values[GRID_MAX_POINTS_X][GRID_MAX_POINTS_Y]; + settings.load_mesh(param.KLS_storage_slot, &tmp_z_values); - SERIAL_ECHOLNPGM("Subtracting mesh in slot ", param.KLS_storage_slot, " from current mesh."); + SERIAL_ECHOLNPGM("Subtracting mesh in slot ", param.KLS_storage_slot, " from current mesh."); - GRID_LOOP(x, y) { - z_values[x][y] -= tmp_z_values[x][y]; - TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(x, y, z_values[x][y])); + GRID_LOOP(x, y) { + z_values[x][y] -= tmp_z_values[x][y]; + TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(x, y, z_values[x][y])); + } } - } + + #endif // HAS_MESH_STORAGE #endif // UBL_DEVEL_DEBUGGING diff --git a/Marlin/src/feature/bedlevel/ubl/ubl_motion.cpp b/Marlin/src/feature/bedlevel/ubl/ubl_motion.cpp index 053a68b77d78..40c308e39a7d 100644 --- a/Marlin/src/feature/bedlevel/ubl/ubl_motion.cpp +++ b/Marlin/src/feature/bedlevel/ubl/ubl_motion.cpp @@ -32,7 +32,6 @@ #include "../../../module/delta.h" #endif -#include "../../../MarlinCore.h" #include //#define DEBUG_UBL_MOTION @@ -47,18 +46,18 @@ // corners of cells. To fix the issue, simply check if the start/end of the line // is very close to a cell boundary in advance and don't split the line there. - void unified_bed_leveling::line_to_destination_cartesian(const_feedRate_t scaled_fr_mm_s, const uint8_t extruder) { + void unified_bed_leveling::line_to_destination_cartesian(const feedRate_t scaled_fr_mm_s, const uint8_t extruder) { /** * Much of the nozzle movement will be within the same cell. So we will do as little computation * as possible to determine if this is the case. If this move is within the same cell, we will * just do the required Z-Height correction, call the Planner's buffer_line() routine, and leave */ #if HAS_POSITION_MODIFIERS - xyze_pos_t start = current_position, end = destination; + xyze_pos_t start = motion.position, end = motion.destination; planner.apply_modifiers(start); planner.apply_modifiers(end); #else - const xyze_pos_t &start = current_position, &end = destination; + const xyze_pos_t &start = motion.position, &end = motion.destination; #endif const xy_uint8_t istart = cell_indexes(start), iend = cell_indexes(end); @@ -79,7 +78,7 @@ end.z += UBL_Z_RAISE_WHEN_OFF_MESH; planner.buffer_segment(end, scaled_fr_mm_s, extruder); - current_position = destination; + motion.position = motion.destination; return; } #endif @@ -97,7 +96,7 @@ // Replace NAN corrections with 0.0 to prevent NAN propagation. if (!isnan(z0)) end.z += z0; planner.buffer_segment(end, scaled_fr_mm_s, extruder); - current_position = destination; + motion.position = motion.destination; return; } @@ -191,10 +190,10 @@ } // At the final destination? Usually not, but when on a Y Mesh Line it's completed. - if (xy_pos_t(current_position) != xy_pos_t(end)) + if (xy_pos_t(motion.position) != xy_pos_t(end)) goto FINAL_MOVE; - current_position = destination; + motion.position = motion.destination; return; } @@ -241,10 +240,10 @@ DEBUG_ECHOLNPGM("[ubl] skip Y segment"); } - if (xy_pos_t(current_position) != xy_pos_t(end)) + if (xy_pos_t(motion.position) != xy_pos_t(end)) goto FINAL_MOVE; - current_position = destination; + motion.position = motion.destination; return; } @@ -325,10 +324,10 @@ if (cnt.x < 0 || cnt.y < 0) break; // Too far! Exit the loop and go to FINAL_MOVE } - if (xy_pos_t(current_position) != xy_pos_t(end)) + if (xy_pos_t(motion.position) != xy_pos_t(end)) goto FINAL_MOVE; - current_position = destination; + motion.position = motion.destination; } #else // UBL_SEGMENTED @@ -348,15 +347,15 @@ /** * Prepare a segmented linear move for DELTA/SCARA/CARTESIAN with UBL and FADE semantics. * This calls planner.buffer_segment multiple times for small incremental moves. - * Returns true if did NOT move, false if moved (requires current_position update). + * Returns true if did NOT move, false if moved (requires motion.position update). */ - bool __O2 unified_bed_leveling::line_to_destination_segmented(const_feedRate_t scaled_fr_mm_s) { + bool __O2 unified_bed_leveling::line_to_destination_segmented(const feedRate_t scaled_fr_mm_s) { - if (!position_is_reachable(destination)) // fail if moving outside reachable boundary - return true; // did not move, so current_position still accurate + if (!motion.can_reach(motion.destination)) // Fail if moving outside reachable boundary + return true; // Did not move, so motion.position still accurate - const xyze_pos_t total = destination - current_position; + const xyze_pos_t total = motion.destination - motion.position; const float cart_xy_mm_2 = HYPOT2(total.x, total.y), cart_xy_mm = SQRT(cart_xy_mm_2); // Total XY distance @@ -384,22 +383,22 @@ // Note that E segment distance could vary slightly as z mesh height // changes for each segment, but small enough to ignore. - xyze_pos_t raw = current_position; + xyze_pos_t raw = motion.position; // Just do plain segmentation if UBL is inactive or the target is above the fade height - if (!planner.leveling_active || !planner.leveling_active_at_z(destination.z)) { + if (!planner.leveling_active || !planner.leveling_active_at_z(motion.destination.z)) { while (--segments) { raw += diff; - planner.buffer_line(raw, scaled_fr_mm_s, active_extruder, hints); + planner.buffer_line(raw, scaled_fr_mm_s, motion.extruder, hints); } - planner.buffer_line(destination, scaled_fr_mm_s, active_extruder, hints); + planner.buffer_line(motion.destination, scaled_fr_mm_s, motion.extruder, hints); return false; // Did not set current from destination } // Otherwise perform per-segment leveling #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) - const float fade_scaling_factor = planner.fade_scaling_factor_for_z(destination.z); + const float fade_scaling_factor = planner.fade_scaling_factor_for_z(motion.destination.z); #endif // Move to first segment destination @@ -415,8 +414,8 @@ // for mesh inset area. xy_int8_t icell = { - int8_t((raw.x - (MESH_MIN_X)) * RECIPROCAL(MESH_X_DIST)), - int8_t((raw.y - (MESH_MIN_Y)) * RECIPROCAL(MESH_Y_DIST)) + int8_t((raw.x - mesh_min.x) * RECIPROCAL(MESH_X_DIST)), + int8_t((raw.y - mesh_min.y) * RECIPROCAL(MESH_Y_DIST)) }; LIMIT(icell.x, 0, GRID_MAX_CELLS_X); LIMIT(icell.y, 0, GRID_MAX_CELLS_Y); @@ -457,13 +456,13 @@ for (;;) { // for all segments within this mesh cell - if (--segments == 0) raw = destination; // if this is last segment, use destination for exact + if (--segments == 0) raw = motion.destination; // If this is last segment, use destination for exact const float z_cxcy = (z_cxy0 + z_cxym * cell.y) // interpolated mesh z height along cell.x at cell.y TERN_(ENABLE_LEVELING_FADE_HEIGHT, * fade_scaling_factor); // apply fade factor to interpolated height const float oldz = raw.z; raw.z += z_cxcy; - planner.buffer_line(raw, scaled_fr_mm_s, active_extruder, hints); + planner.buffer_line(raw, scaled_fr_mm_s, motion.extruder, hints); raw.z = oldz; if (segments == 0) // done with last segment @@ -484,7 +483,7 @@ } // segment loop } // cell loop - return false; // caller will update current_position + return false; // caller will update motion.position } #endif // UBL_SEGMENTED diff --git a/Marlin/src/feature/binary_stream.h b/Marlin/src/feature/binary_stream.h index a9cd12b85e9a..304fdae3009f 100644 --- a/Marlin/src/feature/binary_stream.h +++ b/Marlin/src/feature/binary_stream.h @@ -134,21 +134,21 @@ class SDFileTransferProtocol { public: static void idle() { - // If a transfer is interrupted and a file is left open, abort it after TIMEOUT ms + // If a transfer is interrupted and a file is left open, abort it after 'idle_period' ms const millis_t ms = millis(); if (transfer_active && ELAPSED(ms, idle_timeout)) { - idle_timeout = ms + IDLE_PERIOD; + idle_timeout = ms + idle_period; if (ELAPSED(ms, transfer_timeout)) transfer_abort(); } } static void process(uint8_t packet_type, char *buffer, const uint16_t length) { - transfer_timeout = millis() + TIMEOUT; + transfer_timeout = millis() + timeout; switch (static_cast(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 @@ -194,7 +194,7 @@ class SDFileTransferProtocol { } } - static const uint16_t VERSION_MAJOR = 0, VERSION_MINOR = 1, VERSION_PATCH = 0, TIMEOUT = 10000, IDLE_PERIOD = 1000; + static const uint16_t version_major = 0, version_minor = 1, version_patch = 0, timeout = 10000, idle_period = 1000; }; class BinaryStream { @@ -209,7 +209,7 @@ class BinaryStream { struct Packet { // 10 byte protocol overhead, ascii with checksum and line number has a minimum of 7 increasing with line union Header { - static constexpr uint16_t HEADER_TOKEN = 0xB5AD; + static constexpr uint16_t header_token = 0xB5AD; struct [[gnu::packed]] { uint16_t token; // packet start token uint8_t sync; // stream sync, resend id and packet loss detection @@ -245,7 +245,7 @@ class BinaryStream { bytes_received = 0; checksum = 0; header_checksum = 0; - timeout = millis() + PACKET_MAX_WAIT; + timeout = millis() + packet_max_wait; buffer = nullptr; } } packet{}; @@ -272,14 +272,14 @@ class BinaryStream { } if (!bs_serial_data_available(card.transfer_port_index)) return false; data = bs_read_serial(card.transfer_port_index); - packet.timeout = millis() + PACKET_MAX_WAIT; + packet.timeout = millis() + packet_max_wait; return true; } template void receive(char (&buffer)[buffer_size]) { uint8_t data = 0; - millis_t transfer_window = millis() + RX_TIMESLICE; + millis_t transfer_window = millis() + rx_timeslice; #if HAS_MEDIA PORT_REDIRECT(SERIAL_PORTMASK(card.transfer_port_index)); @@ -299,7 +299,7 @@ class BinaryStream { case StreamState::PACKET_WAIT: if (!stream_read(data)) { idle(); return; } // no active packet so don't wait packet.header.data[1] = data; - if (packet.header.token == packet.header.HEADER_TOKEN) { + if (packet.header.token == packet.header.header_token) { packet.bytes_received = 2; stream_state = StreamState::PACKET_HEADER; } @@ -322,7 +322,7 @@ class BinaryStream { 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(packet.header.protocol()) == Protocol::CONTROL && static_cast(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; } @@ -398,7 +398,7 @@ class BinaryStream { stream_state = StreamState::PACKET_RESET; break; case StreamState::PACKET_RESEND: - if (packet_retries < MAX_RETRIES || MAX_RETRIES == 0) { + if (packet_retries < max_retries || max_retries == 0) { packet_retries++; stream_state = StreamState::PACKET_RESET; SERIAL_ECHO_MSG("Resend request ", packet_retries); @@ -446,7 +446,7 @@ class BinaryStream { SDFileTransferProtocol::idle(); } - static const uint16_t PACKET_MAX_WAIT = 500, RX_TIMESLICE = 20, MAX_RETRIES = 0, VERSION_MAJOR = 0, VERSION_MINOR = 1, VERSION_PATCH = 0; + static const uint16_t packet_max_wait = 500, rx_timeslice = 20, max_retries = 0, version_major = 0, version_minor = 1, version_patch = 0; uint8_t packet_retries, sync; uint16_t buffer_next_index; uint32_t bytes_received; diff --git a/Marlin/src/feature/bltouch.cpp b/Marlin/src/feature/bltouch.cpp index 02f76c139243..85b69d66c4a5 100644 --- a/Marlin/src/feature/bltouch.cpp +++ b/Marlin/src/feature/bltouch.cpp @@ -48,7 +48,7 @@ bool BLTouch::command(const BLTCommand cmd, const millis_t &ms) { // The previous write should've already delayed to detect the alarm. if (cmd != current) { servo[Z_PROBE_SERVO_NR].move(cmd); - safe_delay(_MAX(ms, (uint32_t)BLTOUCH_DELAY)); // BLTOUCH_DELAY is also the *minimum* delay + safe_delay(_MAX(ms, uint32_t(BLTOUCH_DELAY))); // BLTOUCH_DELAY is also the *minimum* delay } return triggered(); } @@ -71,15 +71,9 @@ void BLTouch::init(const bool set_voltage/*=false*/) { #else - #ifdef DEBUG_OUT - if (DEBUGGING(LEVELING)) { - PGMSTR(mode0, "OD"); - PGMSTR(mode1, "5V"); - DEBUG_ECHOPGM("BLTouch Mode: "); - DEBUG_ECHOPGM_P(bltouch.od_5v_mode ? mode1 : mode0); - DEBUG_ECHOLNPGM(" (Default " TERN(BLTOUCH_SET_5V_MODE, "5V", "OD") ")"); - } - #endif + if (DEBUGGING(LEVELING)) + DEBUG_ECHOLN( F("BLTouch Mode: "), bltouch.od_5v_mode ? F("5V") : F("OD"), + F(" (Default " TERN(BLTOUCH_SET_5V_MODE, "5V", "OD") ")")); const bool should_set = od_5v_mode != ENABLED(BLTOUCH_SET_5V_MODE); diff --git a/Marlin/src/feature/bltouch.h b/Marlin/src/feature/bltouch.h index 0f9f2e68ba31..1371ec9f46e6 100644 --- a/Marlin/src/feature/bltouch.h +++ b/Marlin/src/feature/bltouch.h @@ -26,6 +26,7 @@ // BLTouch commands are sent as servo angles typedef unsigned char BLTCommand; +#define DEPLOY_ALARM true #define STOW_ALARM true #define BLTOUCH_DEPLOY 10 #define BLTOUCH_STOW 90 @@ -104,7 +105,7 @@ class BLTouch { static bool triggered(); private: - static bool _deploy_query_alarm() { return command(BLTOUCH_DEPLOY, BLTOUCH_DEPLOY_DELAY); } + static bool _deploy_query_alarm() { return command(BLTOUCH_DEPLOY, BLTOUCH_DEPLOY_DELAY) == DEPLOY_ALARM; } static bool _stow_query_alarm() { return command(BLTOUCH_STOW, BLTOUCH_STOW_DELAY) == STOW_ALARM; } static void clear(); diff --git a/Marlin/src/feature/cancel_object.cpp b/Marlin/src/feature/cancel_object.cpp index 0040f6ed9df8..c17c9988e4f1 100644 --- a/Marlin/src/feature/cancel_object.cpp +++ b/Marlin/src/feature/cancel_object.cpp @@ -30,23 +30,20 @@ CancelObject cancelable; -int8_t CancelObject::object_count, // = 0 - CancelObject::active_object = -1; -uint32_t CancelObject::canceled; // = 0x0000 -bool CancelObject::skipping; // = false +cancel_state_t CancelObject::state; void CancelObject::set_active_object(const int8_t obj) { - active_object = obj; + state.active_object = obj; if (WITHIN(obj, 0, 31)) { - if (obj >= object_count) object_count = obj + 1; - skipping = TEST(canceled, obj); + if (obj >= state.object_count) state.object_count = obj + 1; + state.skipping = TEST(state.canceled, obj); } else - skipping = false; + state.skipping = false; #if ALL(HAS_STATUS_MESSAGE, CANCEL_OBJECTS_REPORTING) - if (active_object >= 0) - ui.set_status(MString<30>(GET_TEXT_F(MSG_PRINTING_OBJECT), ' ', active_object)); + if (state.active_object >= 0) + ui.set_status(MString<30>(GET_TEXT_F(MSG_PRINTING_OBJECT), ' ', state.active_object)); else ui.reset_status(); #endif @@ -54,29 +51,29 @@ void CancelObject::set_active_object(const int8_t obj) { void CancelObject::cancel_object(const int8_t obj) { if (WITHIN(obj, 0, 31)) { - SBI(canceled, obj); - if (obj == active_object) skipping = true; + SBI(state.canceled, obj); + if (obj == state.active_object) state.skipping = true; } } void CancelObject::uncancel_object(const int8_t obj) { if (WITHIN(obj, 0, 31)) { - CBI(canceled, obj); - if (obj == active_object) skipping = false; + CBI(state.canceled, obj); + if (obj == state.active_object) state.skipping = false; } } void CancelObject::report() { - if (active_object >= 0) - SERIAL_ECHO_MSG("Active Object: ", active_object); + if (state.active_object >= 0) + SERIAL_ECHO_MSG("Active Object: ", state.active_object); - if (canceled) { - SERIAL_ECHO_START(); - SERIAL_ECHOPGM("Canceled:"); - for (int i = 0; i < object_count; i++) - if (TEST(canceled, i)) { SERIAL_CHAR(' '); SERIAL_ECHO(i); } - SERIAL_EOL(); - } + if (state.canceled == 0x0000) return; + + SERIAL_ECHO_START(); + SERIAL_ECHOPGM("Canceled:"); + for (int i = 0; i < state.object_count; i++) + if (TEST(state.canceled, i)) SERIAL_ECHO(C(' '), i); + SERIAL_EOL(); } #endif // CANCEL_OBJECTS diff --git a/Marlin/src/feature/cancel_object.h b/Marlin/src/feature/cancel_object.h index 62548a371937..7f04612d13b1 100644 --- a/Marlin/src/feature/cancel_object.h +++ b/Marlin/src/feature/cancel_object.h @@ -23,19 +23,23 @@ #include +typedef struct CancelState { + bool skipping = false; + int8_t object_count = 0, active_object = 0; + uint32_t canceled = 0x0000; +} cancel_state_t; + class CancelObject { public: - static bool skipping; - static int8_t object_count, active_object; - static uint32_t canceled; - static void set_active_object(const int8_t obj); + static cancel_state_t state; + static void set_active_object(const int8_t obj=state.active_object); static void cancel_object(const int8_t obj); static void uncancel_object(const int8_t obj); static void report(); - static bool is_canceled(const int8_t obj) { return TEST(canceled, obj); } + static bool is_canceled(const int8_t obj) { return TEST(state.canceled, obj); } static void clear_active_object() { set_active_object(-1); } - static void cancel_active_object() { cancel_object(active_object); } - static void reset() { canceled = 0x0000; object_count = 0; clear_active_object(); } + static void cancel_active_object() { cancel_object(state.active_object); } + static void reset() { state.canceled = 0x0000; state.object_count = 0; clear_active_object(); } }; extern CancelObject cancelable; diff --git a/Marlin/src/feature/caselight.cpp b/Marlin/src/feature/caselight.cpp index eb580a6d6269..95221111b2a9 100644 --- a/Marlin/src/feature/caselight.cpp +++ b/Marlin/src/feature/caselight.cpp @@ -40,7 +40,7 @@ bool CaseLight::on = CASE_LIGHT_DEFAULT_ON; #if CASE_LIGHT_IS_COLOR_LED constexpr uint8_t init_case_light[] = CASE_LIGHT_DEFAULT_COLOR; - LEDColor CaseLight::color = { init_case_light[0], init_case_light[1], init_case_light[2] OPTARG(HAS_WHITE_LED, init_case_light[3]) }; + LED1Color_t CaseLight::color = { init_case_light[0], init_case_light[1], init_case_light[2] OPTARG(HAS_WHITE_LED, init_case_light[3]) }; #endif void CaseLight::update(const bool sflag) { @@ -67,13 +67,13 @@ void CaseLight::update(const bool sflag) { #if ENABLED(CASE_LIGHT_USE_NEOPIXEL) if (on) // Use current color of (NeoPixel) leds and new brightness level - leds.set_color(LEDColor(leds.color.r, leds.color.g, leds.color.b OPTARG(HAS_WHITE_LED, leds.color.w) OPTARG(NEOPIXEL_LED, n10ct))); + leds.set_color(LED1Color_t(leds.color.r, leds.color.g, leds.color.b OPTARG(HAS_WHITE_LED, leds.color.w) OPTARG(NEOPIXEL_LED, n10ct))); else // Switch off leds leds.set_off(); #else // Use CaseLight color (CASE_LIGHT_DEFAULT_COLOR) and new brightness level - leds.set_color(LEDColor(color.r, color.g, color.b OPTARG(HAS_WHITE_LED, color.w) OPTARG(NEOPIXEL_LED, n10ct))); + leds.set_color(LED1Color_t(color.r, color.g, color.b OPTARG(HAS_WHITE_LED, color.w) OPTARG(NEOPIXEL_LED, n10ct))); #endif #else // !CASE_LIGHT_IS_COLOR_LED diff --git a/Marlin/src/feature/caselight.h b/Marlin/src/feature/caselight.h index d88b3d67bf83..28466cecaa7e 100644 --- a/Marlin/src/feature/caselight.h +++ b/Marlin/src/feature/caselight.h @@ -24,7 +24,7 @@ #include "../inc/MarlinConfig.h" #if CASE_LIGHT_IS_COLOR_LED - #include "leds/leds.h" // for LEDColor + #include "leds/leds.h" // for LED1Color_t #endif class CaseLight { @@ -50,7 +50,7 @@ class CaseLight { #if ENABLED(CASE_LIGHT_IS_COLOR_LED) private: - static LEDColor color; + static LED1Color_t color; #endif }; diff --git a/Marlin/src/feature/controllerfan.cpp b/Marlin/src/feature/controllerfan.cpp index acbae459e716..063640213662 100644 --- a/Marlin/src/feature/controllerfan.cpp +++ b/Marlin/src/feature/controllerfan.cpp @@ -44,9 +44,30 @@ uint8_t ControllerFan::speed; void ControllerFan::setup() { SET_OUTPUT(CONTROLLER_FAN_PIN); - #ifdef CONTROLLER_FAN2_PIN + #if PIN_EXISTS(CONTROLLER_FAN2) SET_OUTPUT(CONTROLLER_FAN2_PIN); #endif + #if PIN_EXISTS(CONTROLLER_FAN3) + SET_OUTPUT(CONTROLLER_FAN3_PIN); + #endif + #if PIN_EXISTS(CONTROLLER_FAN4) + SET_OUTPUT(CONTROLLER_FAN4_PIN); + #endif + #if PIN_EXISTS(CONTROLLER_FAN5) + SET_OUTPUT(CONTROLLER_FAN5_PIN); + #endif + #if PIN_EXISTS(CONTROLLER_FAN6) + SET_OUTPUT(CONTROLLER_FAN6_PIN); + #endif + #if PIN_EXISTS(CONTROLLER_FAN7) + SET_OUTPUT(CONTROLLER_FAN7_PIN); + #endif + #if PIN_EXISTS(CONTROLLER_FAN8) + SET_OUTPUT(CONTROLLER_FAN8_PIN); + #endif + #if PIN_EXISTS(CONTROLLER_FAN9) + SET_OUTPUT(CONTROLLER_FAN9_PIN); + #endif init(); } @@ -70,7 +91,9 @@ void ControllerFan::update() { */ const ena_mask_t axis_mask = TERN(CONTROLLER_FAN_USE_Z_ONLY, _BV(Z_AXIS), (ena_mask_t)~TERN0(CONTROLLER_FAN_IGNORE_Z, _BV(Z_AXIS))); if ( (stepper.axis_enabled.bits & axis_mask) - || TERN0(HAS_HEATED_BED, thermalManager.temp_bed.soft_pwm_amount > 0) + #if ALL(HAS_HEATED_BED, CONTROLLER_FAN_BED_HEATING) + || thermalManager.temp_bed.soft_pwm_amount > 0 + #endif #ifdef CONTROLLER_FAN_MIN_BOARD_TEMP || thermalManager.wholeDegBoard() >= CONTROLLER_FAN_MIN_BOARD_TEMP #endif @@ -85,7 +108,7 @@ void ControllerFan::update() { * - If System is on idle and idle fan speed settings is activated. */ set_fan_speed( - settings.auto_mode && lastComponentOn && PENDING(ms, lastComponentOn + SEC_TO_MS(settings.duration)) + settings.auto_mode && lastComponentOn && PENDING(ms, lastComponentOn, SEC_TO_MS(settings.duration)) ? settings.active_speed : settings.idle_speed ); @@ -105,19 +128,38 @@ void ControllerFan::update() { fan_kick_end = 0; #endif + #define SET_CONTROLLER_FAN(N) do { \ + if (PWM_PIN(CONTROLLER_FAN##N##_PIN)) hal.set_pwm_duty(pin_t(CONTROLLER_FAN##N##_PIN), speed); \ + else WRITE(CONTROLLER_FAN##N##_PIN, speed > 0);\ + } while (0) + #if ENABLED(FAN_SOFT_PWM) - soft_pwm_speed = speed; + soft_pwm_speed = speed >> 1; // Controller Fan Soft PWM uses 0-127 as 0-100% so cut the 0-255 range in half. #else - if (PWM_PIN(CONTROLLER_FAN_PIN)) - hal.set_pwm_duty(pin_t(CONTROLLER_FAN_PIN), speed); - else - WRITE(CONTROLLER_FAN_PIN, speed > 0); - - #ifdef CONTROLLER_FAN2_PIN - if (PWM_PIN(CONTROLLER_FAN2_PIN)) - hal.set_pwm_duty(pin_t(CONTROLLER_FAN2_PIN), speed); - else - WRITE(CONTROLLER_FAN2_PIN, speed > 0); + SET_CONTROLLER_FAN(); + #if PIN_EXISTS(CONTROLLER_FAN2) + SET_CONTROLLER_FAN(2); + #endif + #if PIN_EXISTS(CONTROLLER_FAN3) + SET_CONTROLLER_FAN(3); + #endif + #if PIN_EXISTS(CONTROLLER_FAN4) + SET_CONTROLLER_FAN(4); + #endif + #if PIN_EXISTS(CONTROLLER_FAN5) + SET_CONTROLLER_FAN(5); + #endif + #if PIN_EXISTS(CONTROLLER_FAN6) + SET_CONTROLLER_FAN(6); + #endif + #if PIN_EXISTS(CONTROLLER_FAN7) + SET_CONTROLLER_FAN(7); + #endif + #if PIN_EXISTS(CONTROLLER_FAN8) + SET_CONTROLLER_FAN(8); + #endif + #if PIN_EXISTS(CONTROLLER_FAN9) + SET_CONTROLLER_FAN(9); #endif #endif } diff --git a/Marlin/src/feature/cooler.h b/Marlin/src/feature/cooler.h index 9891514e23c9..ef3590c59379 100644 --- a/Marlin/src/feature/cooler.h +++ b/Marlin/src/feature/cooler.h @@ -40,7 +40,7 @@ class Cooler { static bool enabled; static void enable() { enabled = true; } static void disable() { enabled = false; } - static void toggle() { enabled = !enabled; } + static void toggle() { FLIP(enabled); } static uint8_t mode; // 0 = CO2 Liquid cooling, 1 = Laser Diode TEC Heatsink Cooling static void set_mode(const uint8_t m) { mode = m; } @@ -96,7 +96,7 @@ class Cooler { #if ENABLED(FLOWMETER_SAFETY) static bool flowfault; // Flag that the cooler is in a fault state static bool flowsafety_enabled; // Flag to disable the cutter if flow rate is too low - static void flowsafety_toggle() { flowsafety_enabled = !flowsafety_enabled; } + static void flowsafety_toggle() { FLIP(flowsafety_enabled); } static bool check_flow_too_low() { const bool too_low = flowsafety_enabled && flowrate < (FLOWMETER_MIN_LITERS_PER_MINUTE); flowfault = too_low; diff --git a/Marlin/src/feature/dac/dac_dac084s085.cpp b/Marlin/src/feature/dac/dac_dac084s085.cpp index 772bb68de42e..1338e5979dcb 100644 --- a/Marlin/src/feature/dac/dac_dac084s085.cpp +++ b/Marlin/src/feature/dac/dac_dac084s085.cpp @@ -10,7 +10,6 @@ #include "dac_dac084s085.h" -#include "../../MarlinCore.h" #include "../../HAL/shared/Delay.h" dac084s085::dac084s085() { } diff --git a/Marlin/src/feature/dac/stepper_dac.cpp b/Marlin/src/feature/dac/stepper_dac.cpp index f5664bc59855..65423d318960 100644 --- a/Marlin/src/feature/dac/stepper_dac.cpp +++ b/Marlin/src/feature/dac/stepper_dac.cpp @@ -68,7 +68,7 @@ void StepperDAC::set_current_value(const uint8_t channel, uint16_t val) { } void StepperDAC::set_current_percent(const uint8_t channel, float val) { - set_current_value(channel, _MIN(val, 100.0f) * (DAC_STEPPER_MAX) / 100.0f); + set_current_value(channel, _MIN(val, 100.0f) * (DAC_STEPPER_MAX) * 0.01f); } static float dac_perc(int8_t n) { return mcp4728.getDrvPct(dac_order[n]); } @@ -87,7 +87,7 @@ void StepperDAC::print_values() { LOOP_LOGICAL_AXES(a) { SERIAL_CHAR(' ', IAXIS_CHAR(a), ':'); SERIAL_ECHO(dac_perc(a)); - SERIAL_ECHOPGM_P(PSTR(" ("), dac_amps(AxisEnum(a)), PSTR(")")); + SERIAL_ECHOPGM_P(PSTR(" ("), dac_amps((AxisEnum)a), PSTR(")")); } #if HAS_EXTRUDERS SERIAL_ECHOLNPGM_P(SP_E_LBL, dac_perc(E_AXIS), PSTR(" ("), dac_amps(E_AXIS), PSTR(")")); diff --git a/Marlin/src/feature/direct_stepping.cpp b/Marlin/src/feature/direct_stepping.cpp index 13cf71e07695..08cda561b1cc 100644 --- a/Marlin/src/feature/direct_stepping.cpp +++ b/Marlin/src/feature/direct_stepping.cpp @@ -175,7 +175,7 @@ namespace DirectStepping { template void SerialPageManager::write_responses() { if (fatal_error) { - kill(GET_TEXT_F(MSG_BAD_PAGE)); + marlin.kill(GET_TEXT_F(MSG_BAD_PAGE)); return; } diff --git a/Marlin/src/feature/e_parser.cpp b/Marlin/src/feature/e_parser.cpp index da193ed30aba..f88f838e72ac 100644 --- a/Marlin/src/feature/e_parser.cpp +++ b/Marlin/src/feature/e_parser.cpp @@ -33,6 +33,9 @@ // Static data members bool EmergencyParser::killed_by_M112, // = false EmergencyParser::quickstop_by_M410, + #if ENABLED(RESONANCE_TEST) + EmergencyParser::rt_stop_by_M496, // = false + #endif #if HAS_MEDIA EmergencyParser::sd_abort_by_M524, #endif @@ -46,24 +49,26 @@ bool EmergencyParser::killed_by_M112, // = false // Global instance EmergencyParser emergency_parser; -// External references -extern bool wait_for_user, wait_for_heatup; - #if ENABLED(EP_BABYSTEPPING) #include "babystep.h" #endif #if ENABLED(REALTIME_REPORTING_COMMANDS) - // From motion.h, which cannot be included here - void report_current_position_moving(); - void quickpause_stepper(); - void quickresume_stepper(); + #include "../module/motion.h" +#endif + +#if ENABLED(SOFT_FEED_HOLD) + bool realtime_ramping_pause_flag = false; #endif void EmergencyParser::update(EmergencyParser::State &state, const uint8_t c) { + auto uppercase = [](char c) { + return TERN0(GCODE_CASE_INSENSITIVE, WITHIN(c, 'a', 'z')) ? c + 'A' - 'a' : c; + }; + switch (state) { case EP_RESET: - switch (c) { + switch (uppercase(c)) { case ' ': case '\n': case '\r': break; case 'N': state = EP_N; break; case 'M': state = EP_M; break; @@ -81,7 +86,7 @@ void EmergencyParser::update(EmergencyParser::State &state, const uint8_t c) { break; case EP_N: - switch (c) { + switch (uppercase(c)) { case '0' ... '9': case '-': case ' ': break; case 'M': state = EP_M; break; @@ -143,29 +148,30 @@ void EmergencyParser::update(EmergencyParser::State &state, const uint8_t c) { case EP_M10: state = (c == '8') ? EP_M108 : EP_IGNORE; break; case EP_M11: state = (c == '2') ? EP_M112 : EP_IGNORE; break; - case EP_M4: state = (c == '1') ? EP_M41 : EP_IGNORE; break; + case EP_M4: + switch (c) { + case '1' :state = EP_M41; break; + #if ENABLED(RESONANCE_TEST) + case '9': state = EP_M49; break; + #endif + default: state = EP_IGNORE; + } + break; + case EP_M41: state = (c == '0') ? EP_M410 : EP_IGNORE; break; + #if ENABLED(RESONANCE_TEST) + case EP_M49: state = (c == '6') ? EP_M496 : EP_IGNORE; break; + #endif + #if HAS_MEDIA case EP_M5: state = (c == '2') ? EP_M52 : EP_IGNORE; break; case EP_M52: state = (c == '4') ? EP_M524 : EP_IGNORE; break; #endif #if ENABLED(EP_BABYSTEPPING) - case EP_M2: - switch (c) { - case '9': state = EP_M29; break; - default: state = EP_IGNORE; - } - break; - - case EP_M29: - switch (c) { - case '3': state = EP_M293; break; - case '4': state = EP_M294; break; - default: state = EP_IGNORE; - } - break; + case EP_M2: state = (c == '9') ? EP_M29 : EP_IGNORE; break; + case EP_M29: state = (c == '3') ? EP_M293 : (c == '4') ? EP_M294 : EP_IGNORE; break; #endif #if ENABLED(HOST_PROMPT_SUPPORT) @@ -174,7 +180,7 @@ void EmergencyParser::update(EmergencyParser::State &state, const uint8_t c) { case EP_M87: state = (c == '6') ? EP_M876 : EP_IGNORE; break; case EP_M876: - switch (c) { + switch (uppercase(c)) { case ' ': break; case 'S': state = EP_M876S; break; default: state = EP_IGNORE; break; @@ -200,9 +206,12 @@ void EmergencyParser::update(EmergencyParser::State &state, const uint8_t c) { default: if (ISEOL(c)) { if (enabled) switch (state) { - case EP_M108: wait_for_user = wait_for_heatup = false; break; + case EP_M108: marlin.end_waiting(); break; case EP_M112: killed_by_M112 = true; break; case EP_M410: quickstop_by_M410 = true; break; + #if ENABLED(RESONANCE_TEST) + case EP_M496: rt_stop_by_M496 = true; break; + #endif #if ENABLED(EP_BABYSTEPPING) case EP_M293: babystep.ep_babysteps++; break; case EP_M294: babystep.ep_babysteps--; break; @@ -214,9 +223,9 @@ void EmergencyParser::update(EmergencyParser::State &state, const uint8_t c) { case EP_M876SN: hostui.handle_response(M876_reason); break; #endif #if ENABLED(REALTIME_REPORTING_COMMANDS) - case EP_GRBL_STATUS: report_current_position_moving(); break; - case EP_GRBL_PAUSE: quickpause_stepper(); break; - case EP_GRBL_RESUME: quickresume_stepper(); break; + case EP_GRBL_STATUS: motion.report_position_moving(); break; + case EP_GRBL_PAUSE: TERN(SOFT_FEED_HOLD, realtime_ramping_pause_flag = true, motion.quickpause_stepper()); break; + case EP_GRBL_RESUME: TERN(SOFT_FEED_HOLD, realtime_ramping_pause_flag = false, motion.quickresume_stepper()); break; #endif #if ENABLED(SOFT_RESET_VIA_SERIAL) case EP_KILL: hal.reboot(); break; diff --git a/Marlin/src/feature/e_parser.h b/Marlin/src/feature/e_parser.h index 8dacb0581c12..1f1ca58c35fb 100644 --- a/Marlin/src/feature/e_parser.h +++ b/Marlin/src/feature/e_parser.h @@ -43,6 +43,9 @@ class EmergencyParser { #if HAS_MEDIA EP_M5, EP_M52, EP_M524, #endif + #if ENABLED(RESONANCE_TEST) + EP_M49, EP_M496, + #endif #if ENABLED(EP_BABYSTEPPING) EP_M2, EP_M29, EP_M293, EP_M294, #endif @@ -64,6 +67,10 @@ class EmergencyParser { static bool killed_by_M112; static bool quickstop_by_M410; + #if ENABLED(RESONANCE_TEST) + static bool rt_stop_by_M496; + #endif + #if HAS_MEDIA static bool sd_abort_by_M524; #endif @@ -79,8 +86,14 @@ class EmergencyParser { static void update(State &state, const uint8_t c); + static bool isEnabled() { return enabled; } + private: static bool enabled; }; extern EmergencyParser emergency_parser; + +#if ENABLED(SOFT_FEED_HOLD) + extern bool realtime_ramping_pause_flag; +#endif diff --git a/Marlin/src/feature/easythreed_ui.cpp b/Marlin/src/feature/easythreed_ui.cpp index 39248bfc7d78..49d490cd7620 100644 --- a/Marlin/src/feature/easythreed_ui.cpp +++ b/Marlin/src/feature/easythreed_ui.cpp @@ -78,9 +78,9 @@ void EasythreedUI::blinkLED() { prev_blink_interval_ms = blink_interval_ms; blink_start_ms = ms; } - if (PENDING(ms, blink_start_ms + blink_interval_ms)) + if (PENDING(ms, blink_start_ms, blink_interval_ms)) WRITE(EASYTHREED_LED_PIN, LOW); - else if (PENDING(ms, blink_start_ms + 2 * blink_interval_ms)) + else if (PENDING(ms, blink_start_ms, 2 * blink_interval_ms)) WRITE(EASYTHREED_LED_PIN, HIGH); else blink_start_ms = ms; @@ -91,7 +91,7 @@ void EasythreedUI::blinkLED() { // Load/Unload buttons are a 3 position switch with a common center ground. // void EasythreedUI::loadButton() { - if (printingIsActive()) return; + if (marlin.printingIsActive()) return; enum FilamentStatus : uint8_t { FS_IDLE, FS_PRESS, FS_CHECK, FS_PROCEED }; static uint8_t filament_status = FS_IDLE; @@ -107,7 +107,7 @@ void EasythreedUI::loadButton() { break; case FS_PRESS: - if (ELAPSED(millis(), filament_time + BTN_DEBOUNCE_MS)) { // After a short debounce delay... + if (ELAPSED(millis(), filament_time, BTN_DEBOUNCE_MS)) { // After a short debounce delay... if (!READ(BTN_RETRACT) || !READ(BTN_FEED)) { // ...if switch still toggled... thermalManager.setTargetHotend(EXTRUDE_MINTEMP + 10, 0); // Start heating up blink_interval_ms = LED_BLINK_7; // Set the LED to blink fast @@ -131,12 +131,12 @@ void EasythreedUI::loadButton() { break; case FS_PROCEED: { - // Feed or Retract just once. Hard abort all moves and return to idle on swicth release. + // Feed or Retract just once. Hard abort all moves and return to idle on switch release. static bool flag = false; if (READ(BTN_RETRACT) && READ(BTN_FEED)) { // Switch in center position (stop) flag = false; // Restore flag to false filament_status = FS_IDLE; // Go back to idle state - quickstop_stepper(); // Hard-stop all the steppers ... now! + motion.quickstop_stepper(); // Hard-stop all the steppers ... now! thermalManager.disable_all_heaters(); // And disable all the heaters blink_interval_ms = LED_ON; } @@ -175,21 +175,21 @@ void EasythreedUI::printButton() { break; case KS_PRESS: - if (ELAPSED(ms, key_time + BTN_DEBOUNCE_MS)) // Wait for debounce interval to expire + if (ELAPSED(ms, key_time, BTN_DEBOUNCE_MS)) // Wait for debounce interval to expire key_status = READ(BTN_PRINT) ? KS_IDLE : KS_PROCEED; // Proceed if still pressed break; case KS_PROCEED: if (!READ(BTN_PRINT)) break; // Wait for the button to be released key_status = KS_IDLE; // Ready for the next press - if (PENDING(ms, key_time + 1200 - BTN_DEBOUNCE_MS)) { // Register a press < 1.2 seconds + if (PENDING(ms, key_time, 1200 - BTN_DEBOUNCE_MS)) { // Register a press < 1.2 seconds switch (print_key_flag) { case PF_START: { // The "Print" button starts an SD card print - if (printingIsActive()) break; // Already printing? (find another line that checks for 'is planner doing anything else right now?') + if (marlin.printingIsActive()) break; // Already printing? (find another line that checks for 'is planner doing anything else right now?') blink_interval_ms = LED_BLINK_2; // Blink the indicator LED at 1 second intervals print_key_flag = PF_PAUSE; // The "Print" button now pauses the print card.mount(); // Force SD card to mount - now! - if (!card.isMounted) { // Failed to mount? + if (!card.isMounted()) { // Failed to mount? blink_interval_ms = LED_OFF; // Turn off LED print_key_flag = PF_START; return; // Bail out @@ -201,13 +201,13 @@ void EasythreedUI::printButton() { card.openAndPrintFile(card.filename); // Start printing it } break; case PF_PAUSE: { // Pause printing (not currently firing) - if (!printingIsActive()) break; + if (!marlin.printingIsActive()) break; blink_interval_ms = LED_ON; // Set indicator to steady ON queue.inject(F("M25")); // Queue Pause print_key_flag = PF_RESUME; // The "Print" button now resumes the print } break; case PF_RESUME: { // Resume printing - if (printingIsActive()) break; + if (marlin.printingIsActive()) break; blink_interval_ms = LED_BLINK_2; // Blink the indicator LED at 1 second intervals queue.inject(F("M24")); // Queue resume print_key_flag = PF_PAUSE; // The "Print" button now pauses the print @@ -215,7 +215,7 @@ void EasythreedUI::printButton() { } } else { // Register a longer press - if (print_key_flag == PF_START && !printingIsActive()) { // While not printing, this moves Z up 10mm + if (print_key_flag == PF_START && !marlin.printingIsActive()) { // While not printing, this moves Z up 10mm blink_interval_ms = LED_ON; queue.inject(F("G91\nG0 Z10 F600\nG90")); // Raise Z soon after returning to main loop } diff --git a/Marlin/src/feature/encoder_i2c.cpp b/Marlin/src/feature/encoder_i2c.cpp index 135178456b73..3b5163ca3230 100644 --- a/Marlin/src/feature/encoder_i2c.cpp +++ b/Marlin/src/feature/encoder_i2c.cpp @@ -36,7 +36,7 @@ #include "../module/stepper.h" #include "../gcode/parser.h" -#include "../feature/babystep.h" +#include "babystep.h" #include @@ -149,7 +149,7 @@ void I2CPositionEncoder::update() { #ifdef I2CPE_ERR_THRESH_ABORT if (ABS(error) > I2CPE_ERR_THRESH_ABORT * planner.settings.axis_steps_per_mm[encoderAxis]) { - //kill(F("Significant Error")); + //marlin.kill(F("Significant Error")); SERIAL_ECHOLNPGM("Axis error over threshold, aborting!", error); safe_delay(5000); } @@ -168,8 +168,7 @@ void I2CPositionEncoder::update() { float sumP = 0; for (uint8_t i = 0; i < I2CPE_ERR_PRST_ARRAY_SIZE; ++i) sumP += errPrst[i]; const int32_t errorP = int32_t(sumP * RECIPROCAL(I2CPE_ERR_PRST_ARRAY_SIZE)); - SERIAL_CHAR(AXIS_CHAR(encoderAxis)); - SERIAL_ECHOLNPGM(" : CORRECT ERR ", errorP * planner.mm_per_step[encoderAxis], "mm"); + SERIAL_ECHOLN(C(AXIS_CHAR(encoderAxis)), F(" : CORRECT ERR "), errorP * planner.mm_per_step[encoderAxis], F("mm")); babystep.add_steps(encoderAxis, -LROUND(errorP)); errPrstIdx = 0; } @@ -188,8 +187,7 @@ void I2CPositionEncoder::update() { if (ABS(error) > I2CPE_ERR_CNT_THRESH * planner.settings.axis_steps_per_mm[encoderAxis]) { const millis_t ms = millis(); if (ELAPSED(ms, nextErrorCountTime)) { - SERIAL_CHAR(AXIS_CHAR(encoderAxis)); - SERIAL_ECHOLNPGM(" : LARGE ERR ", error, "; diffSum=", diffSum); + SERIAL_ECHOLN(C(AXIS_CHAR(encoderAxis)), F(" : LARGE ERR "), error, F("; diffSum="), diffSum); errorCount++; nextErrorCountTime = ms + I2CPE_ERR_CNT_DEBOUNCE_MS; } @@ -208,8 +206,7 @@ void I2CPositionEncoder::set_homed() { homed = trusted = true; #ifdef I2CPE_DEBUG - SERIAL_CHAR(AXIS_CHAR(encoderAxis)); - SERIAL_ECHOLNPGM(" axis encoder homed, offset of ", zeroOffset, " ticks."); + SERIAL_ECHO(C(AXIS_CHAR(encoderAxis)), F(" axis encoder homed, offset of "), zeroOffset, F(" ticks.\n")); #endif } } @@ -219,8 +216,7 @@ void I2CPositionEncoder::set_unhomed() { homed = trusted = false; #ifdef I2CPE_DEBUG - SERIAL_CHAR(AXIS_CHAR(encoderAxis)); - SERIAL_ECHOLNPGM(" axis encoder unhomed."); + SERIAL_ECHO(C(AXIS_CHAR(encoderAxis)), F(" axis encoder unhomed.\n")); #endif } @@ -247,10 +243,8 @@ float I2CPositionEncoder::get_axis_error_mm(const bool report) { diff = actual - target, error = ABS(diff) > 10000 ? 0 : diff; // Huge error is a bad reading - if (report) { - SERIAL_CHAR(AXIS_CHAR(encoderAxis)); - SERIAL_ECHOLNPGM(" axis target=", target, "mm; actual=", actual, "mm; err=", error, "mm"); - } + if (report) + SERIAL_ECHO(C(AXIS_CHAR(encoderAxis)), F(" axis target="), target, F("mm; actual="), actual, F("mm; err="), error, F("mm\n")); return error; } @@ -282,10 +276,8 @@ int32_t I2CPositionEncoder::get_axis_error_steps(const bool report) { errorPrev = error; - if (report) { - SERIAL_CHAR(AXIS_CHAR(encoderAxis)); - SERIAL_ECHOLNPGM(" axis target=", target, "; actual=", encoderCountInStepperTicksScaled, "; err=", error); - } + if (report) + SERIAL_ECHOLN(C(AXIS_CHAR(encoderAxis)), F(" axis target="), target, F("; actual="), encoderCountInStepperTicksScaled, F("; err="), error); if (suppressOutput) { if (report) SERIAL_ECHOLNPGM("!Discontinuity. Suppressing error."); @@ -326,9 +318,9 @@ bool I2CPositionEncoder::test_axis() { // Only works on XYZ Cartesian machines for the time being if (!(encoderAxis == X_AXIS || encoderAxis == Y_AXIS || encoderAxis == Z_AXIS)) return false; - const float startPosition = soft_endstop.min[encoderAxis] + 10, - endPosition = soft_endstop.max[encoderAxis] - 10; - const feedRate_t fr_mm_s = FLOOR(homing_feedrate(encoderAxis)); + const float startPosition = motion.soft_endstop.min[encoderAxis] + 10, + endPosition = motion.soft_endstop.max[encoderAxis] - 10; + const feedRate_t fr_mm_s = FLOOR(motion.homing_feedrate(encoderAxis)); ec = false; @@ -381,13 +373,13 @@ void I2CPositionEncoder::calibrate_steps_mm(const uint8_t iter) { int32_t startCount, stopCount; - const feedRate_t fr_mm_s = homing_feedrate(encoderAxis); + const feedRate_t fr_mm_s = motion.homing_feedrate(encoderAxis); bool oldec = ec; ec = false; startDistance = 20; - endDistance = soft_endstop.max[encoderAxis] - 20; + endDistance = motion.soft_endstop.max[encoderAxis] - 20; travelDistance = endDistance - startDistance; xyze_pos_t startCoord, endCoord; @@ -408,7 +400,7 @@ void I2CPositionEncoder::calibrate_steps_mm(const uint8_t iter) { delay(250); startCount = get_position(); - //do_blocking_move_to(endCoord); + //motion.blocking_move(endCoord); TERN_(HAS_EXTRUDERS, endCoord.e = planner.get_axis_position_mm(E_AXIS)); planner.buffer_line(endCoord, fr_mm_s, 0); @@ -647,23 +639,22 @@ void I2CPositionEncodersMgr::init() { void I2CPositionEncodersMgr::report_position(const int8_t idx, const bool units, const bool noOffset) { CHECK_IDX(); - if (units) + if (units) { SERIAL_ECHOLN(noOffset ? encoders[idx].mm_from_count(encoders[idx].get_raw_count()) : encoders[idx].get_position_mm()); - else { - if (noOffset) { - const int32_t raw_count = encoders[idx].get_raw_count(); - SERIAL_CHAR(AXIS_CHAR(encoders[idx).get_axis()], ' '); + return; + } - for (uint8_t j = 31; j > 0; j--) - SERIAL_ECHO((bool)(0x00000001 & (raw_count >> j))); + if (noOffset) { + const int32_t raw_count = encoders[idx].get_raw_count(); + SERIAL_CHAR(AXIS_CHAR(encoders[idx].get_axis()), ' '); - SERIAL_ECHO((bool)(0x00000001 & raw_count)); - SERIAL_CHAR(' '); - SERIAL_ECHOLN(raw_count); - } - else - SERIAL_ECHOLN(encoders[idx].get_position()); + for (uint8_t j = 32; j--;) + SERIAL_ECHO(TEST32(raw_count, j)); + + SERIAL_ECHOLN(C(' '), raw_count); } + else + SERIAL_ECHOLN(encoders[idx].get_position()); } void I2CPositionEncodersMgr::change_module_address(const uint8_t oldaddr, const uint8_t newaddr) { @@ -707,7 +698,7 @@ void I2CPositionEncodersMgr::change_module_address(const uint8_t oldaddr, const // and enable it (it will likely have failed initialization on power-up, before the address change). const int8_t idx = idx_from_addr(newaddr); if (idx >= 0 && !encoders[idx].get_active()) { - SERIAL_CHAR(AXIS_CHAR(encoders[idx).get_axis()]); + SERIAL_CHAR(AXIS_CHAR(encoders[idx].get_axis())); SERIAL_ECHOLNPGM(" axis encoder was not detected on printer startup. Trying again."); encoders[idx].set_active(encoders[idx].passes_test(true)); } @@ -810,7 +801,7 @@ void I2CPositionEncodersMgr::M860() { if (I2CPE_idx == 0xFF) { LOOP_LOGICAL_AXES(i) { if (!I2CPE_anyaxis || parser.seen_test(AXIS_CHAR(i))) { - const uint8_t idx = idx_from_axis(AxisEnum(i)); + const uint8_t idx = idx_from_axis((AxisEnum)i); if ((int8_t)idx >= 0) report_position(idx, hasU, hasO); } } @@ -837,7 +828,7 @@ void I2CPositionEncodersMgr::M861() { if (I2CPE_idx == 0xFF) { LOOP_LOGICAL_AXES(i) { if (!I2CPE_anyaxis || parser.seen(AXIS_CHAR(i))) { - const uint8_t idx = idx_from_axis(AxisEnum(i)); + const uint8_t idx = idx_from_axis((AxisEnum)i); if ((int8_t)idx >= 0) report_status(idx); } } @@ -865,7 +856,7 @@ void I2CPositionEncodersMgr::M862() { if (I2CPE_idx == 0xFF) { LOOP_LOGICAL_AXES(i) { if (!I2CPE_anyaxis || parser.seen(AXIS_CHAR(i))) { - const uint8_t idx = idx_from_axis(AxisEnum(i)); + const uint8_t idx = idx_from_axis((AxisEnum)i); if ((int8_t)idx >= 0) test_axis(idx); } } @@ -896,7 +887,7 @@ void I2CPositionEncodersMgr::M863() { if (I2CPE_idx == 0xFF) { LOOP_LOGICAL_AXES(i) { if (!I2CPE_anyaxis || parser.seen(AXIS_CHAR(i))) { - const uint8_t idx = idx_from_axis(AxisEnum(i)); + const uint8_t idx = idx_from_axis((AxisEnum)i); if ((int8_t)idx >= 0) calibrate_steps_mm(idx, iterations); } } @@ -972,7 +963,7 @@ void I2CPositionEncodersMgr::M865() { if (!I2CPE_addr) { LOOP_LOGICAL_AXES(i) { if (!I2CPE_anyaxis || parser.seen(AXIS_CHAR(i))) { - const uint8_t idx = idx_from_axis(AxisEnum(i)); + const uint8_t idx = idx_from_axis((AxisEnum)i); if ((int8_t)idx >= 0) report_module_firmware(encoders[idx].get_address()); } } @@ -1003,12 +994,12 @@ void I2CPositionEncodersMgr::M866() { if (I2CPE_idx == 0xFF) { LOOP_LOGICAL_AXES(i) { if (!I2CPE_anyaxis || parser.seen(AXIS_CHAR(i))) { - const uint8_t idx = idx_from_axis(AxisEnum(i)); + const uint8_t idx = idx_from_axis((AxisEnum)i); if ((int8_t)idx >= 0) { if (hasR) - reset_error_count(idx, AxisEnum(i)); + reset_error_count(idx, (AxisEnum)i); else - report_error_count(idx, AxisEnum(i)); + report_error_count(idx, (AxisEnum)i); } } } @@ -1041,10 +1032,10 @@ void I2CPositionEncodersMgr::M867() { if (I2CPE_idx == 0xFF) { LOOP_LOGICAL_AXES(i) { if (!I2CPE_anyaxis || parser.seen(AXIS_CHAR(i))) { - const uint8_t idx = idx_from_axis(AxisEnum(i)); + const uint8_t idx = idx_from_axis((AxisEnum)i); if ((int8_t)idx >= 0) { const bool ena = onoff == -1 ? !encoders[I2CPE_idx].get_ec_enabled() : !!onoff; - enable_ec(idx, ena, AxisEnum(i)); + enable_ec(idx, ena, (AxisEnum)i); } } } @@ -1077,7 +1068,7 @@ void I2CPositionEncodersMgr::M868() { if (I2CPE_idx == 0xFF) { LOOP_LOGICAL_AXES(i) { if (!I2CPE_anyaxis || parser.seen(AXIS_CHAR(i))) { - const uint8_t idx = idx_from_axis(AxisEnum(i)); + const uint8_t idx = idx_from_axis((AxisEnum)i); if ((int8_t)idx >= 0) { if (newThreshold != -9999) set_ec_threshold(idx, newThreshold, encoders[idx].get_axis()); @@ -1111,7 +1102,7 @@ void I2CPositionEncodersMgr::M869() { if (I2CPE_idx == 0xFF) { LOOP_LOGICAL_AXES(i) { if (!I2CPE_anyaxis || parser.seen(AXIS_CHAR(i))) { - const uint8_t idx = idx_from_axis(AxisEnum(i)); + const uint8_t idx = idx_from_axis((AxisEnum)i); if ((int8_t)idx >= 0) report_error(idx); } } diff --git a/Marlin/src/feature/encoder_i2c.h b/Marlin/src/feature/encoder_i2c.h index 861a8e52d4d6..e8485a6b7548 100644 --- a/Marlin/src/feature/encoder_i2c.h +++ b/Marlin/src/feature/encoder_i2c.h @@ -188,7 +188,7 @@ class I2CPositionEncoder { FORCE_INLINE void set_ec_method(const byte method) { ecMethod = method; } FORCE_INLINE float get_ec_threshold() { return ecThreshold; } - FORCE_INLINE void set_ec_threshold(const_float_t newThreshold) { ecThreshold = newThreshold; } + FORCE_INLINE void set_ec_threshold(const float newThreshold) { ecThreshold = newThreshold; } FORCE_INLINE int get_encoder_ticks_mm() { switch (type) { diff --git a/Marlin/src/feature/ethernet.cpp b/Marlin/src/feature/ethernet.cpp index c5bfa932cb5b..cdf176f8323e 100644 --- a/Marlin/src/feature/ethernet.cpp +++ b/Marlin/src/feature/ethernet.cpp @@ -141,7 +141,7 @@ void MarlinEthernet::check() { case CONNECTING: telnetClient.println("Marlin " SHORT_BUILD_VERSION); - #if defined(STRING_DISTRIBUTION_DATE) && defined(STRING_CONFIG_H_AUTHOR) + #ifdef STRING_DISTRIBUTION_DATE telnetClient.println( " Last Updated: " STRING_DISTRIBUTION_DATE " | Author: " STRING_CONFIG_H_AUTHOR @@ -172,4 +172,46 @@ void MarlinEthernet::check() { } } +void say_ethernet() { SERIAL_ECHOPGM(" Ethernet "); } + +void MarlinEthernet::ETH0_report(const bool forReplay/*=true*/) { + say_ethernet(); + SERIAL_ECHO_TERNARY(ethernet.hardware_enabled, "port ", "en", "dis", "abled.\n"); + if (ethernet.hardware_enabled) { + say_ethernet(); + SERIAL_ECHO_TERNARY(ethernet.have_telnet_client, "client ", "en", "dis", "abled.\n"); + } + else + SERIAL_ECHOLNPGM("Send 'M552 S1' to enable."); +} + +void MarlinEthernet::MAC_report(const bool forReplay/*=true*/) { + if (!forReplay) SERIAL_ECHO_START(); + SERIAL_ECHOPGM("MAC: "); + if (ethernet.hardware_enabled) { + uint8_t mac[6]; + Ethernet.MACAddress(mac); + for (uint8_t i = 0; i < 6; ++i) { + if (mac[i] < 0x10) SERIAL_CHAR('0'); + SERIAL_PRINT(mac[i], PrintBase::Hex); + if (i < 5) SERIAL_CHAR(':'); + } + } + else + SERIAL_ECHOPGM("Disabled"); + SERIAL_EOL(); +} + +// Display current values when the link is active, +// otherwise show the stored values +void MarlinEthernet::ip_report(const uint16_t cmd, FSTR_P const post, const IPAddress &ipo, const bool forReplay/*=true*/) { + if (!forReplay) SERIAL_ECHO_START(); + SERIAL_ECHO(F(" M"), cmd, C(' ')); + for (uint8_t i = 0; i < 4; ++i) { + SERIAL_ECHO(ipo[i]); + if (i < 3) SERIAL_CHAR('.'); + } + SERIAL_ECHOLN(F(" ; "), post); +} + #endif // HAS_ETHERNET diff --git a/Marlin/src/feature/ethernet.h b/Marlin/src/feature/ethernet.h index 70a58efce7e3..7aa364fc3c21 100644 --- a/Marlin/src/feature/ethernet.h +++ b/Marlin/src/feature/ethernet.h @@ -25,6 +25,8 @@ #include #endif +#include "../HAL/shared/Marduino.h" + // Teensy 4.1 uses internal MAC Address class MarlinEthernet { @@ -34,6 +36,10 @@ class MarlinEthernet { static EthernetClient telnetClient; static void init(); static void check(); + + static void ETH0_report(const bool forReplay=true); + static void MAC_report(const bool forReplay=true); + static void ip_report(const uint16_t cmd, FSTR_P const post, const IPAddress &ipo, const bool forReplay=true); }; extern MarlinEthernet ethernet; diff --git a/Marlin/src/feature/fancheck.cpp b/Marlin/src/feature/fancheck.cpp index 844191e7e442..d0582ed97ae4 100644 --- a/Marlin/src/feature/fancheck.cpp +++ b/Marlin/src/feature/fancheck.cpp @@ -42,62 +42,18 @@ bool FanCheck::enabled; void FanCheck::init() { #define _TACHINIT(N) TERN(E##N##_FAN_TACHO_PULLUP, SET_INPUT_PULLUP, TERN(E##N##_FAN_TACHO_PULLDOWN, SET_INPUT_PULLDOWN, SET_INPUT))(E##N##_FAN_TACHO_PIN) - #if HAS_E0_FAN_TACHO - _TACHINIT(0); - #endif - #if HAS_E1_FAN_TACHO - _TACHINIT(1); - #endif - #if HAS_E2_FAN_TACHO - _TACHINIT(2); - #endif - #if HAS_E3_FAN_TACHO - _TACHINIT(3); - #endif - #if HAS_E4_FAN_TACHO - _TACHINIT(4); - #endif - #if HAS_E5_FAN_TACHO - _TACHINIT(5); - #endif - #if HAS_E6_FAN_TACHO - _TACHINIT(6); - #endif - #if HAS_E7_FAN_TACHO - _TACHINIT(7); - #endif + #define _EN_TACHINIT(N) TERF(HAS_E##N##_FAN_TACHO, _TACHINIT)(N); + REPEAT(8, _EN_TACHINIT); } void FanCheck::update_tachometers() { bool status; - #define _TACHO_CASE(N) case N: status = READ(E##N##_FAN_TACHO_PIN); break; + #define __TACHO_GET_STATUS(N) case N: status = READ(E##N##_FAN_TACHO_PIN); break; + #define _TACHO_GET_STATUS(N) TERF(HAS_E##N##_FAN_TACHO, __TACHO_GET_STATUS)(N) for (uint8_t f = 0; f < TACHO_COUNT; ++f) { switch (f) { - #if HAS_E0_FAN_TACHO - _TACHO_CASE(0) - #endif - #if HAS_E1_FAN_TACHO - _TACHO_CASE(1) - #endif - #if HAS_E2_FAN_TACHO - _TACHO_CASE(2) - #endif - #if HAS_E3_FAN_TACHO - _TACHO_CASE(3) - #endif - #if HAS_E4_FAN_TACHO - _TACHO_CASE(4) - #endif - #if HAS_E5_FAN_TACHO - _TACHO_CASE(5) - #endif - #if HAS_E6_FAN_TACHO - _TACHO_CASE(6) - #endif - #if HAS_E7_FAN_TACHO - _TACHO_CASE(7) - #endif + REPEAT(8, _TACHO_GET_STATUS) default: continue; } @@ -115,14 +71,8 @@ void FanCheck::compute_speed(uint16_t elapsedTime) { uint8_t fan_error_msk = 0; for (uint8_t f = 0; f < TACHO_COUNT; ++f) { switch (f) { - TERN_(HAS_E0_FAN_TACHO, case 0:) - TERN_(HAS_E1_FAN_TACHO, case 1:) - TERN_(HAS_E2_FAN_TACHO, case 2:) - TERN_(HAS_E3_FAN_TACHO, case 3:) - TERN_(HAS_E4_FAN_TACHO, case 4:) - TERN_(HAS_E5_FAN_TACHO, case 5:) - TERN_(HAS_E6_FAN_TACHO, case 6:) - TERN_(HAS_E7_FAN_TACHO, case 7:) + #define _EN_COMPUTE_FAN_CASE(N) TERN_(HAS_E##N##_FAN_TACHO, case N:) + REPEAT(8, _EN_COMPUTE_FAN_CASE) // Compute fan speed rps[f] = edge_counter[f] * float(250) / elapsedTime; edge_counter[f] = 0; @@ -143,7 +93,7 @@ void FanCheck::compute_speed(uint16_t elapsedTime) { // Drop the error when all fans are ok if (!fan_error_msk && error == TachoError::REPORTED) error = TachoError::FIXED; - if (error == TachoError::FIXED && !printJobOngoing() && !printingIsPaused()) { + if (error == TachoError::FIXED && !marlin.printJobOngoing() && !marlin.printingIsPaused()) { error = TachoError::NONE; // if the issue has been fixed while the printer is idle, reenable immediately ui.reset_alert_level(); } @@ -156,17 +106,17 @@ void FanCheck::compute_speed(uint16_t elapsedTime) { } void FanCheck::report_speed_error(uint8_t fan) { - if (printJobOngoing()) { + if (marlin.printJobOngoing()) { if (error == TachoError::NONE) { if (thermalManager.degTargetHotend(fan) != 0) { - kill(GET_TEXT_F(MSG_FAN_SPEED_FAULT)); + marlin.kill(GET_TEXT_F(MSG_FAN_SPEED_FAULT)); error = TachoError::REPORTED; } else error = TachoError::DETECTED; // Plans error for next processed command } } - else if (!printingIsPaused()) { + else if (!marlin.printingIsPaused()) { thermalManager.setTargetHotend(0, fan); // Always disable heating if (error == TachoError::NONE) error = TachoError::REPORTED; } @@ -179,14 +129,8 @@ void FanCheck::print_fan_states() { for (uint8_t s = 0; s < 2; ++s) { for (uint8_t f = 0; f < TACHO_COUNT; ++f) { switch (f) { - TERN_(HAS_E0_FAN_TACHO, case 0:) - TERN_(HAS_E1_FAN_TACHO, case 1:) - TERN_(HAS_E2_FAN_TACHO, case 2:) - TERN_(HAS_E3_FAN_TACHO, case 3:) - TERN_(HAS_E4_FAN_TACHO, case 4:) - TERN_(HAS_E5_FAN_TACHO, case 5:) - TERN_(HAS_E6_FAN_TACHO, case 6:) - TERN_(HAS_E7_FAN_TACHO, case 7:) + #define _EN_PRINT_FAN_CASE(N) TERN_(HAS_E##N##_FAN_TACHO, case N:) + REPEAT(8, _EN_PRINT_FAN_CASE) SERIAL_ECHOPGM("E", f); if (s == 0) SERIAL_ECHOPGM(":", 60 * rps[f], " RPM "); diff --git a/Marlin/src/feature/fancheck.h b/Marlin/src/feature/fancheck.h index b13a34fb19eb..fed6a798e15a 100644 --- a/Marlin/src/feature/fancheck.h +++ b/Marlin/src/feature/fancheck.h @@ -25,7 +25,6 @@ #if HAS_FANCHECK -#include "../MarlinCore.h" #include "../lcd/marlinui.h" #if ENABLED(AUTO_REPORT_FANS) @@ -67,14 +66,18 @@ class FanCheck { static void compute_speed(uint16_t elapsedTime); static void print_fan_states(); #if HAS_PWMFANCHECK - static void toggle_measuring() { measuring = !measuring; } + static void toggle_measuring() { FLIP(measuring); } static bool is_measuring() { return measuring; } #endif static void check_deferred_error() { if (error == TachoError::DETECTED) { error = TachoError::REPORTED; - TERN(PARK_HEAD_ON_PAUSE, queue.inject(F("M125")), kill(GET_TEXT_F(MSG_FAN_SPEED_FAULT))); + #if ENABLED(PARK_HEAD_ON_PAUSE) + queue.inject(F("M125")); + #else + marlin.kill(GET_TEXT_F(MSG_FAN_SPEED_FAULT)); + #endif } } diff --git a/Marlin/src/feature/filwidth.cpp b/Marlin/src/feature/filwidth.cpp index 3befd7752a6e..1142423cf77a 100644 --- a/Marlin/src/feature/filwidth.cpp +++ b/Marlin/src/feature/filwidth.cpp @@ -28,7 +28,7 @@ FilamentWidthSensor filwidth; -bool FilamentWidthSensor::enabled; // = false; // (M405-M406) Filament Width Sensor ON/OFF. +bool FilamentWidthSensor::enabled; // = false // (M405-M406) Filament Width Sensor ON/OFF. uint32_t FilamentWidthSensor::accum; // = 0 // ADC accumulator uint16_t FilamentWidthSensor::raw; // = 0 // Measured filament diameter - one extruder only float FilamentWidthSensor::nominal_mm = DEFAULT_NOMINAL_FILAMENT_DIA, // (M104) Nominal filament width diff --git a/Marlin/src/feature/filwidth.h b/Marlin/src/feature/filwidth.h index ab50fe0af3f9..a16240936a3b 100644 --- a/Marlin/src/feature/filwidth.h +++ b/Marlin/src/feature/filwidth.h @@ -67,7 +67,7 @@ class FilamentWidthSensor { } // Convert raw measurement to mm - static float raw_to_mm(const uint16_t v) { return v * (float(ADC_VREF_MV) / 1000.0f) * RECIPROCAL(float(MAX_RAW_THERMISTOR_VALUE)); } + static float raw_to_mm(const uint16_t v) { return v * (float(ADC_VREF_MV) * 0.001f) * RECIPROCAL(float(MAX_RAW_THERMISTOR_VALUE)); } static float raw_to_mm() { return raw_to_mm(raw); } // A scaled reading is ready @@ -78,7 +78,7 @@ class FilamentWidthSensor { static void update_measured_mm() { measured_mm = raw_to_mm(); } // Update ring buffer used to delay filament measurements - static void advance_e(const_float_t e_move) { + static void advance_e(const float e_move) { // Increment counters with the E distance e_count += e_move; diff --git a/Marlin/src/feature/fwretract.cpp b/Marlin/src/feature/fwretract.cpp index aee775bffff4..6a726445a748 100644 --- a/Marlin/src/feature/fwretract.cpp +++ b/Marlin/src/feature/fwretract.cpp @@ -92,14 +92,14 @@ void FWRetract::reset() { */ void FWRetract::retract(const bool retracting E_OPTARG(bool swapping/*=false*/)) { // Prevent two retracts or recovers in a row - if (retracted[active_extruder] == retracting) return; + if (retracted[motion.extruder] == retracting) return; // Prevent two swap-retract or recovers in a row #if HAS_MULTI_EXTRUDER // Allow G10 S1 only after G11 - if (swapping && retracted_swap[active_extruder] == retracting) return; + if (swapping && retracted_swap[motion.extruder] == retracting) return; // G11 priority to recover the long retract if activated - if (!retracting) swapping = retracted_swap[active_extruder]; + if (!retracting) swapping = retracted_swap[motion.extruder]; #else constexpr bool swapping = false; #endif @@ -108,7 +108,7 @@ void FWRetract::retract(const bool retracting E_OPTARG(bool swapping/*=false*/)) SERIAL_ECHOLNPGM( "retracting ", AS_DIGIT(retracting), " swapping ", swapping, - " active extruder ", active_extruder + " active extruder ", motion.extruder ); EXTRUDER_LOOP() { SERIAL_ECHOLNPGM("retracted[", e, "] ", AS_DIGIT(retracted[e])); @@ -116,8 +116,8 @@ void FWRetract::retract(const bool retracting E_OPTARG(bool swapping/*=false*/)) SERIAL_ECHOLNPGM("retracted_swap[", e, "] ", AS_DIGIT(retracted_swap[e])); #endif } - SERIAL_ECHOLNPGM("current_position.z ", current_position.z); - SERIAL_ECHOLNPGM("current_position.e ", current_position.e); + SERIAL_ECHOLNPGM("motion.position.z ", motion.position.z); + SERIAL_ECHOLNPGM("motion.position.e ", motion.position.e); SERIAL_ECHOLNPGM("current_hop ", current_hop); //*/ @@ -125,7 +125,7 @@ void FWRetract::retract(const bool retracting E_OPTARG(bool swapping/*=false*/)) * (swapping ? settings.swap_retract_length : settings.retract_length); // The current position will be the destination for E and Z moves - destination = current_position; + motion.destination = motion.position; #if ENABLED(RETRACT_SYNC_MIXING) const uint8_t old_mixing_tool = mixer.get_current_vtool(); @@ -135,16 +135,16 @@ void FWRetract::retract(const bool retracting E_OPTARG(bool swapping/*=false*/)) const feedRate_t fr_max_z = planner.settings.max_feedrate_mm_s[Z_AXIS]; if (retracting) { // Retract by moving from a faux E position back to the current E position - current_retract[active_extruder] = base_retract; - prepare_internal_move_to_destination( // set current from destination - settings.retract_feedrate_mm_s * TERN1(RETRACT_SYNC_MIXING, (MIXING_STEPPERS)) + current_retract[motion.extruder] = base_retract; + motion.prepare_internal_move_to_destination( // Set current from destination + MUL_TERN(RETRACT_SYNC_MIXING, settings.retract_feedrate_mm_s, MIXING_STEPPERS) ); // Is a Z hop set, and has the hop not yet been done? if (!current_hop && settings.retract_zraise > 0.01f) { // Apply hop only once current_hop += settings.retract_zraise; // Add to the hop total (again, only once) // Raise up, set_current_to_destination. Maximum Z feedrate - prepare_internal_move_to_destination(fr_max_z); + motion.prepare_internal_move_to_destination(fr_max_z); } } else { @@ -152,45 +152,42 @@ void FWRetract::retract(const bool retracting E_OPTARG(bool swapping/*=false*/)) if (current_hop) { current_hop = 0; // Lower Z, set_current_to_destination. Maximum Z feedrate - prepare_internal_move_to_destination(fr_max_z); + motion.prepare_internal_move_to_destination(fr_max_z); } + // Adjust the current E position by the extra amount to recover + // Sync the planner position so the extra amount is recovered const float extra_recover = swapping ? settings.swap_retract_recover_extra : settings.retract_recover_extra; - if (extra_recover) { - current_position.e -= extra_recover; // Adjust the current E position by the extra amount to recover - sync_plan_position_e(); // Sync the planner position so the extra amount is recovered - } + if (extra_recover) motion.set_and_sync_e(motion.position.e - extra_recover); - current_retract[active_extruder] = 0; + current_retract[motion.extruder] = 0; // Recover E, set_current_to_destination - prepare_internal_move_to_destination( - (swapping ? settings.swap_retract_recover_feedrate_mm_s : settings.retract_recover_feedrate_mm_s) - * TERN1(RETRACT_SYNC_MIXING, (MIXING_STEPPERS)) - ); + const feedRate_t fr_mm_s = swapping ? settings.swap_retract_recover_feedrate_mm_s : settings.retract_recover_feedrate_mm_s; + motion.prepare_internal_move_to_destination(MUL_TERN(RETRACT_SYNC_MIXING, fr_mm_s, MIXING_STEPPERS)); } TERN_(RETRACT_SYNC_MIXING, mixer.T(old_mixing_tool)); // Restore original mixing tool - retracted.set(active_extruder, retracting); // Active extruder now retracted / recovered + retracted.set(motion.extruder, retracting); // Active extruder now retracted / recovered // If swap retract/recover update the retracted_swap flag too #if HAS_MULTI_EXTRUDER - if (swapping) retracted_swap.set(active_extruder, retracting); + if (swapping) retracted_swap.set(motion.extruder, retracting); #endif /* // debugging SERIAL_ECHOLNPGM("retracting ", AS_DIGIT(retracting)); SERIAL_ECHOLNPGM("swapping ", AS_DIGIT(swapping)); - SERIAL_ECHOLNPGM("active_extruder ", active_extruder); + SERIAL_ECHOLNPGM("motion.extruder ", motion.extruder); EXTRUDER_LOOP() { SERIAL_ECHOLNPGM("retracted[", e, "] ", AS_DIGIT(retracted[e])); #if HAS_MULTI_EXTRUDER SERIAL_ECHOLNPGM("retracted_swap[", e, "] ", AS_DIGIT(retracted_swap[e])); #endif } - SERIAL_ECHOLNPGM("current_position.z ", current_position.z); - SERIAL_ECHOLNPGM("current_position.e ", current_position.e); + SERIAL_ECHOLNPGM("motion.position.z ", motion.position.z); + SERIAL_ECHOLNPGM("motion.position.e ", motion.position.e); SERIAL_ECHOLNPGM("current_hop ", current_hop); //*/ } @@ -245,6 +242,7 @@ void FWRetract::M208_report() { " M208 S", LINEAR_UNIT(settings.retract_recover_extra) , " W", LINEAR_UNIT(settings.swap_retract_recover_extra) , " F", LINEAR_UNIT(MMS_TO_MMM(settings.retract_recover_feedrate_mm_s)) + , " R", LINEAR_UNIT(MMS_TO_MMM(settings.swap_retract_recover_feedrate_mm_s)) ); } diff --git a/Marlin/src/feature/host_actions.cpp b/Marlin/src/feature/host_actions.cpp index cc5face2594d..94bc4db0113c 100644 --- a/Marlin/src/feature/host_actions.cpp +++ b/Marlin/src/feature/host_actions.cpp @@ -87,10 +87,6 @@ void HostUI::action(FSTR_P const fstr, const bool eol) { PGMSTR(CONTINUE_STR, "Continue"); PGMSTR(DISMISS_STR, "Dismiss"); - #if HAS_RESUME_CONTINUE - extern bool wait_for_user; - #endif - void HostUI::notify(const char * const cstr) { PORT_REDIRECT(SerialMask::All); action(F("notification "), false); @@ -186,13 +182,13 @@ void HostUI::action(FSTR_P const fstr, const bool eol) { switch (response) { case 0: // "Purge More" button - #if ALL(M600_PURGE_MORE_RESUMABLE, ADVANCED_PAUSE_FEATURE) + #if ENABLED(M600_PURGE_MORE_RESUMABLE) pause_menu_response = PAUSE_RESPONSE_EXTRUDE_MORE; // Simulate menu selection (menu exits, doesn't extrude more) #endif break; case 1: // "Continue" / "Disable Runout" button - #if ALL(M600_PURGE_MORE_RESUMABLE, ADVANCED_PAUSE_FEATURE) + #if ENABLED(M600_PURGE_MORE_RESUMABLE) pause_menu_response = PAUSE_RESPONSE_RESUME_PRINT; // Simulate menu selection #endif #if HAS_FILAMENT_SENSOR @@ -205,7 +201,7 @@ void HostUI::action(FSTR_P const fstr, const bool eol) { } break; case PROMPT_USER_CONTINUE: - TERN_(HAS_RESUME_CONTINUE, wait_for_user = false); + TERN_(HAS_RESUME_CONTINUE, marlin.user_resume()); break; case PROMPT_PAUSE_RESUME: #if ALL(ADVANCED_PAUSE_FEATURE, HAS_MEDIA) diff --git a/Marlin/src/feature/hotend_idle.cpp b/Marlin/src/feature/hotend_idle.cpp index 050898e3b53b..f05e127a9950 100644 --- a/Marlin/src/feature/hotend_idle.cpp +++ b/Marlin/src/feature/hotend_idle.cpp @@ -40,10 +40,10 @@ HotendIdleProtection hotend_idle; millis_t HotendIdleProtection::next_protect_ms = 0; -hotend_idle_settings_t HotendIdleProtection::cfg; // Initialized by settings.load() +hotend_idle_settings_t HotendIdleProtection::cfg; // Initialized by settings.load void HotendIdleProtection::check_hotends(const millis_t &ms) { - const bool busy = (TERN0(HAS_RESUME_CONTINUE, wait_for_user) || planner.has_blocks_queued()); + const bool busy = (TERN0(HAS_RESUME_CONTINUE, marlin.wait_for_user) || planner.has_blocks_queued()); bool do_prot = false; if (!busy && cfg.timeout != 0) { HOTEND_LOOP() { @@ -60,8 +60,8 @@ void HotendIdleProtection::check_hotends(const millis_t &ms) { void HotendIdleProtection::check_e_motion(const millis_t &ms) { static float old_e_position = 0; - if (old_e_position != current_position.e) { - old_e_position = current_position.e; // Track filament motion + if (old_e_position != motion.position.e) { + old_e_position = motion.position.e; // Track filament motion if (next_protect_ms) // If some heater is on then... next_protect_ms = ms + 1000UL * cfg.timeout; // ...delay the timeout till later } diff --git a/Marlin/src/feature/joystick.cpp b/Marlin/src/feature/joystick.cpp index acab5d7437a2..1bc38aba8c63 100644 --- a/Marlin/src/feature/joystick.cpp +++ b/Marlin/src/feature/joystick.cpp @@ -67,18 +67,10 @@ Joystick joystick; #if ENABLED(JOYSTICK_DEBUG) void Joystick::report() { SERIAL_ECHOPGM("Joystick"); - #if HAS_JOY_ADC_X - SERIAL_ECHOPGM_P(SP_X_STR, JOY_X(x.getraw())); - #endif - #if HAS_JOY_ADC_Y - SERIAL_ECHOPGM_P(SP_Y_STR, JOY_Y(y.getraw())); - #endif - #if HAS_JOY_ADC_Z - SERIAL_ECHOPGM_P(SP_Z_STR, JOY_Z(z.getraw())); - #endif - #if HAS_JOY_ADC_EN - SERIAL_ECHO_TERNARY(READ(JOY_EN_PIN), " EN=", "HIGH (dis", "LOW (en", "abled)"); - #endif + TERF(HAS_JOY_ADC_X, SERIAL_ECHOPGM_P)(SP_X_STR, JOY_X(x.getraw())); + TERF(HAS_JOY_ADC_Y, SERIAL_ECHOPGM_P)(SP_Y_STR, JOY_Y(y.getraw())); + TERF(HAS_JOY_ADC_Z, SERIAL_ECHOPGM_P)(SP_Z_STR, JOY_Z(z.getraw())); + TERF(HAS_JOY_ADC_EN, SERIAL_ECHO_TERNARY)(READ(JOY_EN_PIN), " EN=", "HIGH (dis", "LOW (en", "abled)"); SERIAL_EOL(); } #endif @@ -123,11 +115,11 @@ Joystick joystick; void Joystick::inject_jog_moves() { // Recursion barrier - static bool injecting_now; // = false; + static bool injecting_now; // = false if (injecting_now) return; #if ENABLED(NO_MOTION_BEFORE_HOMING) - if (TERN0(HAS_JOY_ADC_X, axis_should_home(X_AXIS)) || TERN0(HAS_JOY_ADC_Y, axis_should_home(Y_AXIS)) || TERN0(HAS_JOY_ADC_Z, axis_should_home(Z_AXIS))) + if (TERN0(HAS_JOY_ADC_X, motion.axis_should_home(X_AXIS)) || TERN0(HAS_JOY_ADC_Y, motion.axis_should_home(Y_AXIS)) || TERN0(HAS_JOY_ADC_Z, motion.axis_should_home(Z_AXIS))) return; #endif @@ -169,12 +161,12 @@ Joystick joystick; } if (!UNEAR_ZERO(hypot2)) { - current_position += move_dist; - apply_motion_limits(current_position); + motion.position += move_dist; + motion.apply_limits(motion.position); const float length = sqrt(hypot2); PlannerHints hints(length); injecting_now = true; - planner.buffer_line(current_position, length / seg_time, active_extruder, hints); + planner.buffer_line(motion.position, length / seg_time, motion.extruder, hints); injecting_now = false; } } diff --git a/Marlin/src/feature/leds/blinkm.cpp b/Marlin/src/feature/leds/blinkm.cpp index 868eb4b3d916..b040c8e76f60 100644 --- a/Marlin/src/feature/leds/blinkm.cpp +++ b/Marlin/src/feature/leds/blinkm.cpp @@ -32,7 +32,7 @@ #include "leds.h" #include -void blinkm_set_led_color(const LEDColor &color) { +void blinkm_set_led_color(const LED1Color_t &color) { Wire.begin(); Wire.beginTransmission(I2C_ADDRESS(0x09)); Wire.write('o'); //to disable ongoing script, only needs to be used once diff --git a/Marlin/src/feature/leds/blinkm.h b/Marlin/src/feature/leds/blinkm.h index 29a9e784120d..d3c528acbf6d 100644 --- a/Marlin/src/feature/leds/blinkm.h +++ b/Marlin/src/feature/leds/blinkm.h @@ -25,7 +25,6 @@ * blinkm.h - Control a BlinkM over i2c */ -struct LEDColor; -typedef LEDColor LEDColor; +struct LED1Color_t; -void blinkm_set_led_color(const LEDColor &color); +void blinkm_set_led_color(const LED1Color_t &color); diff --git a/Marlin/src/feature/leds/leds.cpp b/Marlin/src/feature/leds/leds.cpp index ac7f1815162b..55a10fcbbdef 100644 --- a/Marlin/src/feature/leds/leds.cpp +++ b/Marlin/src/feature/leds/leds.cpp @@ -31,19 +31,19 @@ #include "leds.h" #if ANY(CASE_LIGHT_USE_RGB_LED, CASE_LIGHT_USE_NEOPIXEL) - #include "../../feature/caselight.h" + #include "../caselight.h" #endif #if ENABLED(LED_COLOR_PRESETS) - const LEDColor LEDLights::defaultLEDColor = LEDColor( + const LED1Color_t LEDLights::defaultLEDColor { LED_USER_PRESET_RED, LED_USER_PRESET_GREEN, LED_USER_PRESET_BLUE OPTARG(HAS_WHITE_LED, LED_USER_PRESET_WHITE) OPTARG(NEOPIXEL_LED, LED_USER_PRESET_BRIGHTNESS) - ); + }; #endif #if ANY(LED_CONTROL_MENU, PRINTER_EVENT_LEDS, CASE_LIGHT_IS_COLOR_LED) - LEDColor LEDLights::color; + LED1Color_t LEDLights::color; bool LEDLights::lights_on; #endif @@ -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 @@ -101,7 +101,7 @@ void LEDLights::setup() { constexpr int8_t led_pin_count = TERN(HAS_WHITE_LED, 4, 3); // Startup animation - LEDColor curColor = LEDColorOff(); + LED1Color_t curColor = LEDColorOff(); PCA9632_set_led_color(curColor); // blackout delay(200); @@ -156,15 +156,15 @@ void LEDLights::setup() { TERN_(LED_USER_PRESET_STARTUP, set_default()); } -void LEDLights::set_color(const LEDColor &incol +void LEDLights::set_color(const LED1Color_t &incol OPTARG(NEOPIXEL_IS_SEQUENTIAL, bool isSequence/*=false*/) ) { #if ENABLED(NEOPIXEL_LED) const uint32_t neocolor = LEDColorWhite() == incol - ? neo.Color(NEO_WHITE) - : neo.Color(incol.r, incol.g, incol.b OPTARG(HAS_WHITE_LED, incol.w)); + ? neo.White() + : neo.Color(incol.r, incol.g, incol.b OPTARG(HAS_WHITE_NEOPIXEL_1, incol.w)); #if ENABLED(NEOPIXEL_IS_SEQUENTIAL) static uint16_t nextLed = 0; @@ -239,7 +239,7 @@ void LEDLights::set_color(const LEDColor &incol void LEDLights::toggle() { if (lights_on) set_off(); else update(); } #endif -#if LED_POWEROFF_TIMEOUT > 0 +#if HAS_LED_POWEROFF_TIMEOUT millis_t LEDLights::led_off_time; // = 0 @@ -258,7 +258,7 @@ void LEDLights::set_color(const LEDColor &incol #if ENABLED(NEOPIXEL2_SEPARATE) #if ENABLED(NEO2_COLOR_PRESETS) - const LEDColor LEDLights2::defaultLEDColor = LEDColor( + const LED2Color_t LEDLights2::defaultLEDColor2 = LED2Color_t( NEO2_USER_PRESET_RED, NEO2_USER_PRESET_GREEN, NEO2_USER_PRESET_BLUE OPTARG(HAS_WHITE_LED2, NEO2_USER_PRESET_WHITE) OPTARG(NEOPIXEL_LED, NEO2_USER_PRESET_BRIGHTNESS) @@ -266,7 +266,7 @@ void LEDLights::set_color(const LEDColor &incol #endif #if ENABLED(LED_CONTROL_MENU) - LEDColor LEDLights2::color; + LED2Color_t LEDLights2::color; bool LEDLights2::lights_on; #endif @@ -277,10 +277,10 @@ void LEDLights::set_color(const LEDColor &incol TERN_(NEO2_USER_PRESET_STARTUP, set_default()); } - void LEDLights2::set_color(const LEDColor &incol) { - const uint32_t neocolor = LEDColorWhite() == incol - ? neo2.Color(NEO2_WHITE) - : neo2.Color(incol.r, incol.g, incol.b OPTARG(HAS_WHITE_LED2, incol.w)); + void LEDLights2::set_color(const LED2Color_t &incol) { + const uint32_t neocolor = LEDColorWhite2() == incol + ? neo2.White() + : neo2.Color(incol.r, incol.g, incol.b OPTARG(HAS_WHITE_NEOPIXEL_2, incol.w)); neo2.set_brightness(incol.i); neo2.set_color(neocolor); diff --git a/Marlin/src/feature/leds/leds.h b/Marlin/src/feature/leds/leds.h index 7a31ca685d49..0410f6ca3719 100644 --- a/Marlin/src/feature/leds/leds.h +++ b/Marlin/src/feature/leds/leds.h @@ -29,11 +29,6 @@ #include -// A white component can be passed -#if ANY(RGBW_LED, PCA9632_RGBW) - #define HAS_WHITE_LED 1 -#endif - #if ENABLED(NEOPIXEL_LED) #define _NEOPIXEL_INCLUDE_ #include "neopixel.h" @@ -52,75 +47,142 @@ #include "pca9632.h" #endif +#if ANY(RGBW_LED, PCA9632_RGBW, HAS_WHITE_NEOPIXEL_1) + #define HAS_WHITE_LED 1 +#endif +#if HAS_WHITE_NEOPIXEL_2 + #define HAS_WHITE_LED2 1 +#endif + /** * LEDcolor type for use with leds.set_color */ -typedef struct LEDColor { - uint8_t r, g, b - OPTARG(HAS_WHITE_LED, w) - OPTARG(NEOPIXEL_LED, i) - ; +struct LED1Color_t { + // Basic RGB color components + uint8_t r, g, b OPTARG(HAS_WHITE_LED, w) OPTARG(NEOPIXEL_LED, i); + // Default constructor - white color + LED1Color_t() : r(255), g(255), b(255) OPTARG(HAS_WHITE_LED, w(255)) OPTARG(NEOPIXEL_LED, i(NEOPIXEL_BRIGHTNESS)){} - LEDColor() : r(255), g(255), b(255) - OPTARG(HAS_WHITE_LED, w(255)) - OPTARG(NEOPIXEL_LED, i(NEOPIXEL_BRIGHTNESS)) - {} + // Copy constructor + LED1Color_t(const LED1Color_t&) = default; - LEDColor(const LEDColor&) = default; - - LEDColor(uint8_t r, uint8_t g, uint8_t b OPTARG(HAS_WHITE_LED, uint8_t w=0) OPTARG(NEOPIXEL_LED, uint8_t i=NEOPIXEL_BRIGHTNESS)) + // Constructor with individual components + LED1Color_t(uint8_t r, uint8_t g, uint8_t b OPTARG(HAS_WHITE_LED, uint8_t w=0) OPTARG(NEOPIXEL_LED, uint8_t i=NEOPIXEL_BRIGHTNESS)) : r(r), g(g), b(b) OPTARG(HAS_WHITE_LED, w(w)) OPTARG(NEOPIXEL_LED, i(i)) {} - LEDColor(const uint8_t (&rgbw)[4]) : r(rgbw[0]), g(rgbw[1]), b(rgbw[2]) - OPTARG(HAS_WHITE_LED, w(rgbw[3])) - OPTARG(NEOPIXEL_LED, i(NEOPIXEL_BRIGHTNESS)) - {} + // Constructor from array + LED1Color_t(const uint8_t (&rgbw)[4]) : r(rgbw[0]), g(rgbw[1]), b(rgbw[2]) + OPTARG(HAS_WHITE_LED, w(rgbw[3])) OPTARG(NEOPIXEL_LED, i(NEOPIXEL_BRIGHTNESS)){} - LEDColor& operator=(const uint8_t (&rgbw)[4]) { + // Array assignment operator + LED1Color_t& operator=(const uint8_t (&rgbw)[4]) { r = rgbw[0]; g = rgbw[1]; b = rgbw[2]; TERN_(HAS_WHITE_LED, w = rgbw[3]); return *this; } - bool operator==(const LEDColor &right) { - if (this == &right) return true; - return 0 == memcmp(this, &right, sizeof(LEDColor)); + // Comparison operators + bool operator==(const LED1Color_t &right) { + return (this == &right) || (0 == memcmp(this, &right, sizeof(LED1Color_t))); } - bool operator!=(const LEDColor &right) { return !operator==(right); } + bool operator!=(const LED1Color_t &right) { + return !operator==(right); + } + // Check if LED is effectively off bool is_off() const { return 3 > r + g + b + TERN0(HAS_WHITE_LED, w); } -} LEDColor; +}; + + +struct LED2Color_t { + // Basic RGB color components + uint8_t r, g, b OPTARG(HAS_WHITE_LED2, w) OPTARG(NEOPIXEL_LED, i); + // Default constructor - white color + LED2Color_t() : r(255), g(255), b(255) OPTARG(HAS_WHITE_LED2, w(255)) OPTARG(NEOPIXEL_LED, i(NEOPIXEL_BRIGHTNESS)){} + + // Copy constructor + LED2Color_t(const LED2Color_t&) = default; + + // Constructor with individual components + LED2Color_t(uint8_t r, uint8_t g, uint8_t b OPTARG(HAS_WHITE_LED2, uint8_t w=0) OPTARG(NEOPIXEL_LED, uint8_t i=NEOPIXEL_BRIGHTNESS)) + : r(r), g(g), b(b) OPTARG(HAS_WHITE_LED2, w(w)) OPTARG(NEOPIXEL_LED, i(i)) {} + + // Constructor from array + LED2Color_t(const uint8_t (&rgbw)[4]) : r(rgbw[0]), g(rgbw[1]), b(rgbw[2]) + OPTARG(HAS_WHITE_LED2, w(rgbw[3])) OPTARG(NEOPIXEL_LED, i(NEOPIXEL_BRIGHTNESS)){} + + // Array assignment operator + LED2Color_t& operator=(const uint8_t (&rgbw)[4]) { + r = rgbw[0]; g = rgbw[1]; b = rgbw[2]; + TERN_(HAS_WHITE_LED2, w = rgbw[3]); + return *this; + } + + // Comparison operators + bool operator==(const LED2Color_t &right) { + return (this == &right) || (0 == memcmp(this, &right, sizeof(LED1Color_t))); + } + + bool operator!=(const LED2Color_t &right) { + return !operator==(right); + } + + // Check if LED is effectively off + bool is_off() const { + return 3 > r + g + b + TERN0(HAS_WHITE_LED2, w); + } +}; /** * Color presets */ -#define LEDColorOff() LEDColor( 0, 0, 0) -#define LEDColorRed() LEDColor(255, 0, 0) +#define LEDColorOff() LED1Color_t( 0, 0, 0) +#define LEDColorRed() LED1Color_t(255, 0, 0) #if ENABLED(LED_COLORS_REDUCE_GREEN) - #define LEDColorOrange() LEDColor(255, 25, 0) - #define LEDColorYellow() LEDColor(255, 75, 0) + #define LEDColorOrange() LED1Color_t(255, 25, 0) + #define LEDColorYellow() LED1Color_t(255, 75, 0) #else - #define LEDColorOrange() LEDColor(255, 80, 0) - #define LEDColorYellow() LEDColor(255, 255, 0) + #define LEDColorOrange() LED1Color_t(255, 80, 0) + #define LEDColorYellow() LED1Color_t(255, 255, 0) #endif -#define LEDColorGreen() LEDColor( 0, 255, 0) -#define LEDColorBlue() LEDColor( 0, 0, 255) -#define LEDColorIndigo() LEDColor( 0, 255, 255) -#define LEDColorViolet() LEDColor(255, 0, 255) +#define LEDColorGreen() LED1Color_t( 0, 255, 0) +#define LEDColorBlue() LED1Color_t( 0, 0, 255) +#define LEDColorIndigo() LED1Color_t( 0, 255, 255) +#define LEDColorViolet() LED1Color_t(255, 0, 255) #if HAS_WHITE_LED && DISABLED(RGB_LED) - #define LEDColorWhite() LEDColor( 0, 0, 0, 255) + #define LEDColorWhite() LED1Color_t( 0, 0, 0, 255) #else - #define LEDColorWhite() LEDColor(255, 255, 255) + #define LEDColorWhite() LED1Color_t(255, 255, 255) #endif +#define LEDColorOff2() LED2Color_t( 0, 0, 0) +#define LEDColorRed2() LED2Color_t(255, 0, 0) +#if ENABLED(LED_COLORS_REDUCE_GREEN) + #define LEDColorOrange2() LED2Color_t(255, 25, 0) + #define LEDColorYellow2() LED2Color_t(255, 75, 0) +#else + #define LEDColorOrange2() LED2Color_t(255, 80, 0) + #define LEDColorYellow2() LED2Color_t(255, 255, 0) +#endif +#define LEDColorGreen2() LED2Color_t( 0, 255, 0) +#define LEDColorBlue2() LED2Color_t( 0, 0, 255) +#define LEDColorIndigo2() LED2Color_t( 0, 255, 255) +#define LEDColorViolet2() LED2Color_t(255, 0, 255) +#if HAS_WHITE_LED2 && DISABLED(RGB_LED) + #define LEDColorWhite2() LED2Color_t( 0, 0, 0, 255) +#else + #define LEDColorWhite2() LED2Color_t(255, 255, 255) +#endif + + class LEDLights { public: #if ANY(LED_CONTROL_MENU, PRINTER_EVENT_LEDS, CASE_LIGHT_IS_COLOR_LED) - static LEDColor color; // last non-off color + static LED1Color_t color; // last non-off color static bool lights_on; // the last set color was "on" #else static constexpr bool lights_on = true; @@ -130,7 +192,7 @@ class LEDLights { static void setup(); // init() - static void set_color(const LEDColor &color + static void set_color(const LED1Color_t &color OPTARG(NEOPIXEL_IS_SEQUENTIAL, bool isSequence=false) ); @@ -139,7 +201,7 @@ class LEDLights { OPTARG(NEOPIXEL_LED, uint8_t i=NEOPIXEL_BRIGHTNESS) OPTARG(NEOPIXEL_IS_SEQUENTIAL, bool isSequence=false) ) { - set_color(LEDColor(r, g, b OPTARG(HAS_WHITE_LED, w) OPTARG(NEOPIXEL_LED, i)) OPTARG(NEOPIXEL_IS_SEQUENTIAL, isSequence)); + set_color(LED1Color_t(r, g, b OPTARG(HAS_WHITE_LED, w) OPTARG(NEOPIXEL_LED, i)) OPTARG(NEOPIXEL_IS_SEQUENTIAL, isSequence)); } static void set_off() { set_color(LEDColorOff()); } @@ -147,7 +209,7 @@ class LEDLights { static void set_white() { set_color(LEDColorWhite()); } #if ENABLED(LED_COLOR_PRESETS) - static const LEDColor defaultLEDColor; + static const LED1Color_t defaultLEDColor; static void set_default() { set_color(defaultLEDColor); } static void set_red() { set_color(LEDColorRed()); } static void set_orange() { set_color(LEDColorOrange()); } @@ -158,17 +220,17 @@ class LEDLights { #endif #if ENABLED(PRINTER_EVENT_LEDS) - static LEDColor get_color() { return lights_on ? color : LEDColorOff(); } + static LED1Color_t get_color() { return lights_on ? color : LEDColorOff(); } #endif #if ENABLED(LED_CONTROL_MENU) static void toggle(); // swap "off" with color #endif - #if ANY(LED_CONTROL_MENU, CASE_LIGHT_USE_RGB_LED) || LED_POWEROFF_TIMEOUT > 0 + #if ANY(LED_CONTROL_MENU, PRINTER_EVENT_LEDS, CASE_LIGHT_IS_COLOR_LED) static void update() { set_color(color); } #endif - #if LED_POWEROFF_TIMEOUT > 0 + #if HAS_LED_POWEROFF_TIMEOUT private: static millis_t led_off_time; public: @@ -190,35 +252,35 @@ extern LEDLights leds; static void setup(); // init() - static void set_color(const LEDColor &color); + static void set_color(const LED2Color_t &color); static void set_color(uint8_t r, uint8_t g, uint8_t b - OPTARG(HAS_WHITE_LED, uint8_t w=0) + OPTARG(HAS_WHITE_LED2, uint8_t w=0) OPTARG(NEOPIXEL_LED, uint8_t i=NEOPIXEL_BRIGHTNESS) ) { - set_color(LEDColor(r, g, b - OPTARG(HAS_WHITE_LED, w) + set_color(LED2Color_t(r, g, b + OPTARG(HAS_WHITE_LED2, w) OPTARG(NEOPIXEL_LED, i) )); } - static void set_off() { set_color(LEDColorOff()); } - static void set_green() { set_color(LEDColorGreen()); } - static void set_white() { set_color(LEDColorWhite()); } + static void set_off() { set_color(LEDColorOff2()); } + static void set_green() { set_color(LEDColorGreen2()); } + static void set_white() { set_color(LEDColorWhite2()); } #if ENABLED(NEO2_COLOR_PRESETS) - static const LEDColor defaultLEDColor; - static void set_default() { set_color(defaultLEDColor); } - static void set_red() { set_color(LEDColorRed()); } - static void set_orange() { set_color(LEDColorOrange()); } - static void set_yellow() { set_color(LEDColorYellow()); } - static void set_blue() { set_color(LEDColorBlue()); } - static void set_indigo() { set_color(LEDColorIndigo()); } - static void set_violet() { set_color(LEDColorViolet()); } + static const LED2Color_t defaultLEDColor2; + static void set_default() { set_color(defaultLEDColor2); } + static void set_red() { set_color(LEDColorRed2()); } + static void set_orange() { set_color(LEDColorOrange2()); } + static void set_yellow() { set_color(LEDColorYellow2()); } + static void set_blue() { set_color(LEDColorBlue2()); } + static void set_indigo() { set_color(LEDColorIndigo2()); } + static void set_violet() { set_color(LEDColorViolet2()); } #endif #if ENABLED(NEOPIXEL2_SEPARATE) - static LEDColor color; // last non-off color + static LED2Color_t color; // last non-off color static bool lights_on; // the last set color was "on" static void toggle(); // swap "off" with color static void update() { set_color(color); } diff --git a/Marlin/src/feature/leds/neopixel.cpp b/Marlin/src/feature/leds/neopixel.cpp index 1b0772c2f9a6..8165c7715c2b 100644 --- a/Marlin/src/feature/leds/neopixel.cpp +++ b/Marlin/src/feature/leds/neopixel.cpp @@ -103,7 +103,7 @@ void Marlin_NeoPixel::init() { safe_delay(500); set_color_startup(adaneo1.Color(0, 0, 255, 0)); // blue safe_delay(500); - #if HAS_WHITE_LED + #if HAS_WHITE_NEOPIXEL_1 set_color_startup(adaneo1.Color(0, 0, 0, 255)); // white safe_delay(500); #endif @@ -158,7 +158,7 @@ void Marlin_NeoPixel::init() { safe_delay(500); set_color_startup(adaneo.Color(0, 0, 255, 0)); // blue safe_delay(500); - #if HAS_WHITE_LED2 + #if HAS_WHITE_NEOPIXEL_2 set_color_startup(adaneo.Color(0, 0, 0, 255)); // white safe_delay(500); #endif diff --git a/Marlin/src/feature/leds/neopixel.h b/Marlin/src/feature/leds/neopixel.h index 6cc8b6157e80..f420a25b83f0 100644 --- a/Marlin/src/feature/leds/neopixel.h +++ b/Marlin/src/feature/leds/neopixel.h @@ -42,19 +42,17 @@ // Defines // ------------------------ -#define _NEO_IS_RGB(N) (N == NEO_RGB || N == NEO_RBG || N == NEO_GRB || N == NEO_GBR || N == NEO_BRG || N == NEO_BGR) +#define _NEO_IS_RGBW(N) ((N) & 0x30) != (((N) >> 2) & 0x30) -#if !_NEO_IS_RGB(NEOPIXEL_TYPE) - #define HAS_WHITE_LED 1 +#if _NEO_IS_RGBW(NEOPIXEL_TYPE) + #define HAS_WHITE_NEOPIXEL_1 1 #endif -#if HAS_WHITE_LED - #define NEO_WHITE 0, 0, 0, 255 -#else - #define NEO_WHITE 255, 255, 255 -#endif - -#if defined(NEOPIXEL2_TYPE) && NEOPIXEL2_TYPE != NEOPIXEL_TYPE && DISABLED(NEOPIXEL2_SEPARATE) +#if ENABLED(NEOPIXEL2_SEPARATE) + #if _NEO_IS_RGBW(NEOPIXEL2_TYPE) + #define HAS_WHITE_NEOPIXEL_2 1 + #endif +#elif defined(NEOPIXEL2_TYPE) && NEOPIXEL2_TYPE != NEOPIXEL_TYPE #define MULTIPLE_NEOPIXEL_TYPES 1 #endif @@ -62,6 +60,8 @@ #define CONJOINED_NEOPIXEL 1 #endif +#undef _NEO_IS_RGBW + // ------------------------ // Types // ------------------------ @@ -130,7 +130,7 @@ class Marlin_NeoPixel { } // Accessors - static uint16_t pixels() { return adaneo1.numPixels() * TERN1(NEOPIXEL2_INSERIES, 2); } + static uint16_t pixels() { return MUL_TERN(NEOPIXEL2_INSERIES, adaneo1.numPixels(), 2); } static uint32_t pixel_color(const uint16_t n) { #if ENABLED(NEOPIXEL2_INSERIES) @@ -141,8 +141,17 @@ class Marlin_NeoPixel { static uint8_t brightness() { return adaneo1.getBrightness(); } - static uint32_t Color(uint8_t r, uint8_t g, uint8_t b OPTARG(HAS_WHITE_LED, uint8_t w)) { - return adaneo1.Color(r, g, b OPTARG(HAS_WHITE_LED, w)); + static uint32_t Color(uint8_t r, uint8_t g, uint8_t b OPTARG(HAS_WHITE_NEOPIXEL_1, uint8_t w=0)) { + return adaneo1.Color(r, g, b OPTARG(HAS_WHITE_NEOPIXEL_1, w)); + } + static uint32_t White() { + return Color( + #if HAS_WHITE_NEOPIXEL_1 + 0, 0, 0, 255 + #else + 255, 255, 255 + #endif + ); } }; @@ -151,15 +160,6 @@ extern Marlin_NeoPixel neo; // Neo pixel channel 2 #if ENABLED(NEOPIXEL2_SEPARATE) - #if _NEO_IS_RGB(NEOPIXEL2_TYPE) - #define NEOPIXEL2_IS_RGB 1 - #define NEO2_WHITE 255, 255, 255 - #else - #define NEOPIXEL2_IS_RGBW 1 - #define HAS_WHITE_LED2 1 // A white component can be passed for NEOPIXEL2 - #define NEO2_WHITE 0, 0, 0, 255 - #endif - class Marlin_NeoPixel2 { private: static Adafruit_NeoPixel adaneo; @@ -184,13 +184,20 @@ extern Marlin_NeoPixel neo; static uint16_t pixels() { return adaneo.numPixels();} static uint32_t pixel_color(const uint16_t n) { return adaneo.getPixelColor(n); } static uint8_t brightness() { return adaneo.getBrightness(); } - static uint32_t Color(uint8_t r, uint8_t g, uint8_t b OPTARG(HAS_WHITE_LED2, uint8_t w)) { - return adaneo.Color(r, g, b OPTARG(HAS_WHITE_LED2, w)); + static uint32_t Color(uint8_t r, uint8_t g, uint8_t b OPTARG(HAS_WHITE_NEOPIXEL_2, uint8_t w=0)) { + return adaneo.Color(r, g, b OPTARG(HAS_WHITE_NEOPIXEL_2, w)); + } + static uint32_t White() { + return Color( + #if HAS_WHITE_NEOPIXEL_2 + 0, 0, 0, 255 + #else + 255, 255, 255 + #endif + ); } }; extern Marlin_NeoPixel2 neo2; #endif // NEOPIXEL2_SEPARATE - -#undef _NEO_IS_RGB diff --git a/Marlin/src/feature/leds/pca9632.cpp b/Marlin/src/feature/leds/pca9632.cpp index 07c379a8154a..29c00fc682c0 100644 --- a/Marlin/src/feature/leds/pca9632.cpp +++ b/Marlin/src/feature/leds/pca9632.cpp @@ -68,7 +68,7 @@ #ifndef PCA9632_BLU #define PCA9632_BLU 0x04 #endif -#if HAS_WHITE_LED && !defined(PCA9632_WHT) +#if ENABLED(PCA9632_RGBW) && !defined(PCA9632_WHT) #define PCA9632_WHT 0x06 #endif @@ -81,7 +81,11 @@ #define LED_ON 0x01 #define LED_PWM 0x02 -#define PCA9632_ADDRESS 0b01100000 +#ifndef PCA9632_ADDRESS + #define PCA9632_ADDRESS 0b01100000 + #warning "Using default PCA9632_ADDRESS for I2C (0b01100000)." +#endif +//static_assert(false, "Using PCA9632 I2C address=" STRINGIFY(PCA9632_ADDRESS)); byte PCA_init = 0; @@ -98,16 +102,16 @@ static void PCA9632_WriteAllRegisters(const byte addr, const byte regadd, const #if DISABLED(PCA9632_NO_AUTO_INC) uint8_t data[4]; data[0] = PCA9632_AUTO_IND | regadd; - data[1 + (PCA9632_RED >> 1)] = vr; + data[1 + (PCA9632_RED >> 1)] = TERN(PCA9632_SWAP_RB, vb, vr); data[1 + (PCA9632_GRN >> 1)] = vg; - data[1 + (PCA9632_BLU >> 1)] = vb; + data[1 + (PCA9632_BLU >> 1)] = TERN(PCA9632_SWAP_RB, vr, vb); Wire.beginTransmission(I2C_ADDRESS(addr)); Wire.write(data, sizeof(data)); Wire.endTransmission(); #else - PCA9632_WriteRegister(addr, regadd + (PCA9632_RED >> 1), vr); + PCA9632_WriteRegister(addr, regadd + (PCA9632_RED >> 1), TERN(PCA9632_SWAP_RB, vb, vr)); PCA9632_WriteRegister(addr, regadd + (PCA9632_GRN >> 1), vg); - PCA9632_WriteRegister(addr, regadd + (PCA9632_BLU >> 1), vb); + PCA9632_WriteRegister(addr, regadd + (PCA9632_BLU >> 1), TERN(PCA9632_SWAP_RB, vb, vr)); #if ENABLED(PCA9632_RGBW) PCA9632_WriteRegister(addr, regadd + (PCA9632_WHT >> 1), vw); #endif @@ -124,7 +128,7 @@ static void PCA9632_WriteAllRegisters(const byte addr, const byte regadd, const } #endif -void PCA9632_set_led_color(const LEDColor &color) { +void PCA9632_set_led_color(const LED1Color_t &color) { Wire.begin(); if (!PCA_init) { PCA_init = 1; @@ -132,23 +136,25 @@ void PCA9632_set_led_color(const LEDColor &color) { PCA9632_WriteRegister(PCA9632_ADDRESS,PCA9632_MODE2, PCA9632_MODE2_VALUE); } - const byte LEDOUT = (color.r ? LED_PWM << PCA9632_RED : 0) + const byte LEDOUT = (TERN(PCA9632_SWAP_RB, color.b, color.r) ? LED_PWM << PCA9632_RED : 0) | (color.g ? LED_PWM << PCA9632_GRN : 0) - | (color.b ? LED_PWM << PCA9632_BLU : 0) - #if ENABLED(PCA9632_RGBW) - | (color.w ? LED_PWM << PCA9632_WHT : 0) - #endif - ; - - PCA9632_WriteAllRegisters(PCA9632_ADDRESS,PCA9632_PWM0, color.r, color.g, color.b + | (TERN(PCA9632_SWAP_RB, color.r, color.b) ? LED_PWM << PCA9632_BLU : 0) + | (TERN0(PCA9632_RGBW, color.w ? LED_PWM << PCA9632_WHT : 0)); + + PCA9632_WriteAllRegisters( + PCA9632_ADDRESS, PCA9632_PWM0, + TERN(PCA9632_SWAP_RB, color.b, color.r), + color.g, + TERN(PCA9632_SWAP_RB, color.r, color.b) OPTARG(PCA9632_RGBW, color.w) ); PCA9632_WriteRegister(PCA9632_ADDRESS,PCA9632_LEDOUT, LEDOUT); + } #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)); diff --git a/Marlin/src/feature/leds/pca9632.h b/Marlin/src/feature/leds/pca9632.h index adef0200afb9..442166c24591 100644 --- a/Marlin/src/feature/leds/pca9632.h +++ b/Marlin/src/feature/leds/pca9632.h @@ -26,10 +26,9 @@ * Written by Robert Mendon Feb 2017. */ -struct LEDColor; -typedef LEDColor LEDColor; +struct LED1Color_t; -void PCA9632_set_led_color(const LEDColor &color); +void PCA9632_set_led_color(const LED1Color_t &color); #if ENABLED(PCA9632_BUZZER) #include diff --git a/Marlin/src/feature/leds/printer_event_leds.cpp b/Marlin/src/feature/leds/printer_event_leds.cpp index e6407a6320a5..3cbce3da8c13 100644 --- a/Marlin/src/feature/leds/printer_event_leds.cpp +++ b/Marlin/src/feature/leds/printer_event_leds.cpp @@ -47,7 +47,7 @@ PrinterEventLEDs printerEventLEDs; inline void pel_set_rgb(const uint8_t r, const uint8_t g, const uint8_t b OPTARG(HAS_WHITE_LED, const uint8_t w=0)) { leds.set_color( - LEDColor(r, g, b OPTARG(HAS_WHITE_LED, w) OPTARG(NEOPIXEL_LED, neo.brightness())) + LED1Color_t(r, g, b OPTARG(HAS_WHITE_LED, w) OPTARG(NEOPIXEL_LED, neo.brightness())) OPTARG(NEOPIXEL_IS_SEQUENTIAL, true) ); } diff --git a/Marlin/src/feature/leds/printer_event_leds.h b/Marlin/src/feature/leds/printer_event_leds.h index 3a037eba96e5..5fb08a547e52 100644 --- a/Marlin/src/feature/leds/printer_event_leds.h +++ b/Marlin/src/feature/leds/printer_event_leds.h @@ -40,23 +40,23 @@ class PrinterEventLEDs { public: #if HAS_TEMP_HOTEND - static LEDColor onHotendHeatingStart() { old_intensity = 0; return leds.get_color(); } + static LED1Color_t onHotendHeatingStart() { old_intensity = 0; return leds.get_color(); } static void onHotendHeating(const celsius_t start, const celsius_t current, const celsius_t target); #endif #if HAS_HEATED_BED - static LEDColor onBedHeatingStart() { old_intensity = 127; return leds.get_color(); } + static LED1Color_t onBedHeatingStart() { old_intensity = 127; return leds.get_color(); } static void onBedHeating(const celsius_t start, const celsius_t current, const celsius_t target); #endif #if HAS_HEATED_CHAMBER - static LEDColor onChamberHeatingStart() { old_intensity = 127; return leds.get_color(); } + static LED1Color_t onChamberHeatingStart() { old_intensity = 127; return leds.get_color(); } static void onChamberHeating(const celsius_t start, const celsius_t current, const celsius_t target); #endif #if HAS_TEMP_HOTEND || HAS_HEATED_BED || HAS_HEATED_CHAMBER static void onHeatingDone() { leds.set_white(); } - static void onPidTuningDone(LEDColor c) { leds.set_color(c); } + static void onPIDTuningDone(LED1Color_t c) { leds.set_color(c); } #endif #if HAS_MEDIA diff --git a/Marlin/src/feature/max7219.cpp b/Marlin/src/feature/max7219.cpp index 6089b1a86e85..81f588008d77 100644 --- a/Marlin/src/feature/max7219.cpp +++ b/Marlin/src/feature/max7219.cpp @@ -72,6 +72,26 @@ uint16_t CodeProfiler::call_count = 0; #endif +#if defined(MAX7219_DEBUG_PLANNER_HEAD) && defined(MAX7219_DEBUG_PLANNER_TAIL) && MAX7219_DEBUG_PLANNER_HEAD == MAX7219_DEBUG_PLANNER_TAIL + static int16_t last_head_cnt = 0xF, last_tail_cnt = 0xF; +#else + #ifdef MAX7219_DEBUG_PLANNER_HEAD + static int16_t last_head_cnt = 0x1; + #endif + #ifdef MAX7219_DEBUG_PLANNER_TAIL + static int16_t last_tail_cnt = 0x1; + #endif +#endif +#ifdef MAX7219_DEBUG_PLANNER_QUEUE + static int16_t last_depth = 0; +#endif +#ifdef MAX7219_DEBUG_PROFILE + static uint8_t last_time_fraction = 0; +#endif +#ifdef MAX7219_DEBUG_MULTISTEPPING + static uint8_t last_multistepping = 0; +#endif + Max7219 max7219; uint8_t Max7219::led_line[MAX7219_LINES]; // = { 0 }; @@ -263,7 +283,7 @@ void Max7219::set(const uint8_t line, const uint8_t bits) { } // Draw a float with a decimal point and optional digits - void Max7219::print(const uint8_t start, const_float_t value, const uint8_t pre_size, const uint8_t post_size, const bool leadzero=false) { + void Max7219::print(const uint8_t start, const float value, const uint8_t pre_size, const uint8_t post_size, const bool leadzero=false) { if (pre_size) print(start, value, pre_size, leadzero, !!post_size); if (post_size) { const int16_t after = ABS(value) * (10 ^ post_size); @@ -460,32 +480,30 @@ void Max7219::register_setup() { #if MAX7219_INIT_TEST uint8_t test_mode = 0; - millis_t next_patt_ms; bool patt_on; #if MAX7219_INIT_TEST == 2 #define MAX7219_LEDS (MAX7219_X_LEDS * MAX7219_Y_LEDS) - constexpr millis_t pattern_delay = 4; - - int8_t spiralx, spiraly, spiral_dir; + xy_int8_t spiral; + int8_t spiral_dir; uvalue_t(MAX7219_LEDS) spiral_count; - void Max7219::test_pattern() { - constexpr int8_t way[][2] = { { 1, 0 }, { 0, 1 }, { -1, 0 }, { 0, -1 } }; - led_set(spiralx, spiraly, patt_on); - const int8_t x = spiralx + way[spiral_dir][0], y = spiraly + way[spiral_dir][1]; - if (!WITHIN(x, 0, MAX7219_X_LEDS - 1) || !WITHIN(y, 0, MAX7219_Y_LEDS - 1) || BIT_7219(x, y) == patt_on) + void Max7219::run_test_pattern() { + constexpr xy_int8_t way[] = { { 1, 0 }, { 0, 1 }, { -1, 0 }, { 0, -1 } }; + led_set(spiral.x, spiral.y, patt_on); + const xy_int8_t xy = spiral + way[spiral_dir]; + if (!WITHIN(xy.x, 0, MAX7219_X_LEDS - 1) || !WITHIN(xy.y, 0, MAX7219_Y_LEDS - 1) || BIT_7219(xy.x, xy.y) == patt_on) spiral_dir = (spiral_dir + 1) & 0x3; - spiralx += way[spiral_dir][0]; - spiraly += way[spiral_dir][1]; + spiral += way[spiral_dir]; if (!spiral_count--) { if (!patt_on) test_mode = 0; else { spiral_count = MAX7219_LEDS; - spiralx = spiraly = spiral_dir = 0; + spiral.reset(); + spiral_dir = 0; patt_on = false; } } @@ -496,7 +514,11 @@ void Max7219::register_setup() { constexpr millis_t pattern_delay = 20; int8_t sweep_count, sweepx, sweep_dir; - void Max7219::test_pattern() { + void Max7219::run_test_pattern() { + static millis_t next_pattern_ms = 0; + const millis_t ms = millis(); + if (PENDING(ms, next_pattern_ms)) return; + next_pattern_ms = ms + pattern_delay; set_column(sweepx, patt_on ? 0xFFFFFFFF : 0x00000000); sweepx += sweep_dir; if (!WITHIN(sweepx, 0, MAX7219_X_LEDS - 1)) { @@ -506,27 +528,21 @@ void Max7219::register_setup() { } else sweepx -= MAX7219_X_LEDS * sweep_dir; - patt_on ^= true; - next_patt_ms += 100; + FLIP(patt_on); + next_pattern_ms += 100; if (++test_mode > 4) test_mode = 0; } } #endif - void Max7219::run_test_pattern() { - const millis_t ms = millis(); - if (PENDING(ms, next_patt_ms)) return; - next_patt_ms = ms + pattern_delay; - test_pattern(); - } - void Max7219::start_test_pattern() { clear(); test_mode = 1; patt_on = true; #if MAX7219_INIT_TEST == 2 - spiralx = spiraly = spiral_dir = 0; + spiral.reset(); + spiral_dir = 0; spiral_count = MAX7219_LEDS; #else sweep_dir = 1; @@ -550,6 +566,29 @@ void Max7219::init() { #if MAX7219_INIT_TEST start_test_pattern(); #endif + + #ifdef MAX7219_REINIT_ON_POWERUP + #if defined(MAX7219_DEBUG_PLANNER_HEAD) && defined(MAX7219_DEBUG_PLANNER_TAIL) && MAX7219_DEBUG_PLANNER_HEAD == MAX7219_DEBUG_PLANNER_TAIL + last_head_cnt = 0xF; + last_tail_cnt = 0xF; + #else + #ifdef MAX7219_DEBUG_PLANNER_HEAD + last_head_cnt = 0x1; + #endif + #ifdef MAX7219_DEBUG_PLANNER_TAIL + last_tail_cnt = 0x1; + #endif + #endif + #ifdef MAX7219_DEBUG_PLANNER_QUEUE + last_depth = 0; + #endif + #ifdef MAX7219_DEBUG_PROFILE + last_time_fraction = 0; + #endif + #ifdef MAX7219_DEBUG_MULTISTEPPING + last_multistepping = 0; + #endif + #endif } /** @@ -676,8 +715,6 @@ void Max7219::idle_tasks() { #if defined(MAX7219_DEBUG_PLANNER_HEAD) && defined(MAX7219_DEBUG_PLANNER_TAIL) && MAX7219_DEBUG_PLANNER_HEAD == MAX7219_DEBUG_PLANNER_TAIL - static int16_t last_head_cnt = 0xF, last_tail_cnt = 0xF; - if (last_head_cnt != head || last_tail_cnt != tail) { range16(MAX7219_DEBUG_PLANNER_HEAD, last_tail_cnt, tail, last_head_cnt, head, &row_change_mask); last_head_cnt = head; @@ -687,7 +724,6 @@ void Max7219::idle_tasks() { #else #ifdef MAX7219_DEBUG_PLANNER_HEAD - static int16_t last_head_cnt = 0x1; if (last_head_cnt != head) { mark16(MAX7219_DEBUG_PLANNER_HEAD, last_head_cnt, head, &row_change_mask); last_head_cnt = head; @@ -695,7 +731,6 @@ void Max7219::idle_tasks() { #endif #ifdef MAX7219_DEBUG_PLANNER_TAIL - static int16_t last_tail_cnt = 0x1; if (last_tail_cnt != tail) { mark16(MAX7219_DEBUG_PLANNER_TAIL, last_tail_cnt, tail, &row_change_mask); last_tail_cnt = tail; @@ -705,7 +740,6 @@ void Max7219::idle_tasks() { #endif #ifdef MAX7219_DEBUG_PLANNER_QUEUE - static int16_t last_depth = 0; const int16_t current_depth = BLOCK_MOD(head - tail + (BLOCK_BUFFER_SIZE)) & 0xF; if (current_depth != last_depth) { quantity16(MAX7219_DEBUG_PLANNER_QUEUE, last_depth, current_depth, &row_change_mask); @@ -714,7 +748,6 @@ void Max7219::idle_tasks() { #endif #ifdef MAX7219_DEBUG_PROFILE - static uint8_t last_time_fraction = 0; const uint8_t current_time_fraction = (uint16_t(CodeProfiler::get_time_fraction()) * MAX7219_NUMBER_UNITS + 8) / 16; if (current_time_fraction != last_time_fraction) { quantity(MAX7219_DEBUG_PROFILE, last_time_fraction, current_time_fraction, &row_change_mask); @@ -724,7 +757,7 @@ void Max7219::idle_tasks() { #ifdef MAX7219_DEBUG_MULTISTEPPING static uint8_t last_multistepping = 0; - const uint8_t multistepping = Stepper::steps_per_isr; + const uint8_t multistepping = stepper.steps_per_isr; if (multistepping != last_multistepping) { static uint8_t log2_old = 0; uint8_t log2_new = 0; @@ -737,7 +770,7 @@ void Max7219::idle_tasks() { #ifdef MAX7219_DEBUG_SLOWDOWN static uint8_t last_slowdown_count = 0; - const uint8_t slowdown_count = Planner::slowdown_count; + const uint8_t slowdown_count = planner.slowdown_count; if (slowdown_count != last_slowdown_count) { mark16(MAX7219_DEBUG_SLOWDOWN, last_slowdown_count, slowdown_count, &row_change_mask); last_slowdown_count = slowdown_count; diff --git a/Marlin/src/feature/max7219.h b/Marlin/src/feature/max7219.h index a6b110fdf4c9..9c39541a7cc2 100644 --- a/Marlin/src/feature/max7219.h +++ b/Marlin/src/feature/max7219.h @@ -74,14 +74,13 @@ #ifdef MAX7219_DEBUG_PROFILE // This class sums up the amount of time for which its instances exist. - // By default there is one instantiated for the duration of the idle() - // function. But an instance can be created in any code block to measure - // the time spent from the point of instantiation until the CPU leaves - // block. Be careful about having multiple instances of CodeProfiler as - // it does not guard against double counting. In general mixing ISR and - // non-ISR use will require critical sections but note that mode setting - // is atomic so the total or average times can safely be read if you set - // mode to FREEZE first. + // By default there is one instantiated for the duration of marlin.idle() + // but an instance can be created in any code block to measure time spent + // from instantiation until the CPU leaves the block. + // Be careful about having multiple instances of CodeProfiler as it does + // not guard against double counting. In general mixing ISR and non-ISR + // use will require critical sections but note that mode setting is atomic + // so the total or average times can safely be read if you set mode to FREEZE first. class CodeProfiler { public: enum Mode : uint8_t { ACCUMULATE_AVERAGE, ACCUMULATE_TOTAL, FREEZE }; @@ -110,7 +109,7 @@ if (mode == ACCUMULATE_TOTAL) return; // update time_fraction every hundred milliseconds - if (instance_count == 0 && ELAPSED(now, last_calc_time + 100000)) { + if (instance_count == 0 && now - last_calc_time > 100000) { time_fraction = total_time * 128 / (now - last_calc_time); last_calc_time = now; total_time = 0; @@ -166,7 +165,7 @@ class Max7219 { // Draw an integer with optional leading zeros and optional decimal point void print(const uint8_t start, int16_t value, uint8_t size, const bool leadzero=false, bool dec=false); // Draw a float with a decimal point and optional digits - void print(const uint8_t start, const_float_t value, const uint8_t pre_size, const uint8_t post_size, const bool leadzero=false); + void print(const uint8_t start, const float value, const uint8_t pre_size, const uint8_t post_size, const bool leadzero=false); #endif // Set a single LED by XY coordinate diff --git a/Marlin/src/feature/meatpack.cpp b/Marlin/src/feature/meatpack.cpp index fe3dabe8da53..3b762d4dedb8 100644 --- a/Marlin/src/feature/meatpack.cpp +++ b/Marlin/src/feature/meatpack.cpp @@ -169,9 +169,11 @@ void MeatPack::handle_command(const MeatPack_Command c) { void MeatPack::report_state() { // NOTE: if any configuration vars are added below, the outgoing sync text for host plugin // should not contain the "PV' substring, as this is used to indicate protocol version - SERIAL_ECHOPGM("[MP] " MeatPack_ProtocolVersion " "); - serialprint_onoff(TEST(state, MPConfig_Bit_Active)); - SERIAL_ECHO(TEST(state, MPConfig_Bit_NoSpaces) ? F(" NSP\n") : F(" ESP\n")); + SERIAL_ECHO( + F("[MP] " MeatPack_ProtocolVersion " "), + ON_OFF(TEST(state, MPConfig_Bit_Active)), + TEST(state, MPConfig_Bit_NoSpaces) ? F(" NSP\n") : F(" ESP\n") + ); } /** diff --git a/Marlin/src/feature/mixing.cpp b/Marlin/src/feature/mixing.cpp index 4021393f18e6..fb4ebc894b51 100644 --- a/Marlin/src/feature/mixing.cpp +++ b/Marlin/src/feature/mixing.cpp @@ -28,10 +28,6 @@ Mixer mixer; -#ifdef MIXER_NORMALIZER_DEBUG - #include "../core/serial.h" -#endif - // Used up to Planner level uint_fast8_t Mixer::selected_vtool = 0; float Mixer::collector[MIXING_STEPPERS]; // mix proportion. 0.0 = off, otherwise <= COLOR_A_MASK. @@ -170,7 +166,7 @@ void Mixer::refresh_collector(const float proportion/*=1.0*/, const uint8_t t/*= float Mixer::prev_z; // = 0 - void Mixer::update_gradient_for_z(const_float_t z) { + void Mixer::update_gradient_for_z(const float z) { if (z == prev_z) return; prev_z = z; @@ -189,8 +185,8 @@ void Mixer::refresh_collector(const float proportion/*=1.0*/, const uint8_t t/*= void Mixer::update_gradient_for_planner_z() { #if ENABLED(DELTA) - get_cartesian_from_steppers(); - update_gradient_for_z(cartes.z); + motion.get_cartesian_from_steppers(); + update_gradient_for_z(motion.cartes.z); #else update_gradient_for_z(planner.get_axis_position_mm(Z_AXIS)); #endif diff --git a/Marlin/src/feature/mixing.h b/Marlin/src/feature/mixing.h index c0f45e364ab4..bd7ffb560e98 100644 --- a/Marlin/src/feature/mixing.h +++ b/Marlin/src/feature/mixing.h @@ -174,9 +174,9 @@ class Mixer { static float prev_z; // Update the current mix from the gradient for a given Z - static void update_gradient_for_z(const_float_t z); + static void update_gradient_for_z(const float z); static void update_gradient_for_planner_z(); - static void gradient_control(const_float_t z) { + static void gradient_control(const float z) { if (gradient.enabled) { if (z >= gradient.end_z) T(gradient.end_vtool); diff --git a/Marlin/src/feature/mmu/mmu.cpp b/Marlin/src/feature/mmu/mmu.cpp index 58c49ed22454..b1e66cc42506 100644 --- a/Marlin/src/feature/mmu/mmu.cpp +++ b/Marlin/src/feature/mmu/mmu.cpp @@ -24,7 +24,6 @@ #if HAS_PRUSA_MMU1 -#include "../../MarlinCore.h" #include "../../module/planner.h" #include "../../module/stepper.h" diff --git a/Marlin/src/feature/mmu/mmu2-serial-protocol.md b/Marlin/src/feature/mmu/mmu2-serial-protocol.md index 088d41b44693..80480d15c7c7 100644 --- a/Marlin/src/feature/mmu/mmu2-serial-protocol.md +++ b/Marlin/src/feature/mmu/mmu2-serial-protocol.md @@ -1,5 +1,4 @@ -Startup sequence -================ +# Startup sequence When initialized, MMU sends @@ -8,9 +7,9 @@ When initialized, MMU sends We follow with - MMU <= 'S1\n' -- MMU => 'ok*Firmware version*\n' +- MMU => 'ok<_Firmware version_>\n' - MMU <= 'S2\n' -- MMU => 'ok*Build number*\n' +- MMU => 'ok<_Build number_>\n' #if (12V_mode) @@ -20,65 +19,60 @@ We follow with #endif - MMU <= 'P0\n' -- MMU => '*FINDA status*\n' +- MMU => '<_FINDA status_>\n' Now we are sure MMU is available and ready. If there was a timeout or other communication problem somewhere, printer will be killed. -- *Firmware version* is an integer value, but we don't care about it -- *Build number* is an integer value and has to be >=126, or =>132 if 12V mode is enabled -- *FINDA status* is 1 if the filament is loaded to the extruder, 0 otherwise +- <_Firmware version_> is an integer value, but we don't care about it. +- <_Build number_> is an integer value and has to be >=126, or =>132 if 12V mode is enabled. +- <_FINDA status_> is 1 if the filament is loaded to the extruder, 0 otherwise. -*Build number* is checked against the required value, if it does not match, printer is halted. +<_Build number_> is checked against the required value, if it does not match, printer is halted. -Toolchange -========== +# Toolchange -- MMU <= 'T*Filament index*\n' +- MMU <= 'T<_Filament index_>\n' MMU sends - MMU => 'ok\n' -as soon as the filament is fed down to the extruder. We follow with +as soon as the filament is fed down to the extruder. We follow with: - MMU <= 'C0\n' -MMU will feed a few more millimeters of filament for the extruder gears to grab. +MMU will feed a few more millimeters of filament for the extruder gears to grab.\ When done, the MMU sends - MMU => 'ok\n' -We don't wait for a response here but immediately continue with the next G-code which should +We don't wait for a response here but immediately continue with the next G-code which should\ be one or more extruder moves to feed the filament into the hotend. -FINDA status -============ +# FINDA status - MMU <= 'P0\n' -- MMU => '*FINDA status*\n' +- MMU => '<_FINDA status_>\n' -*FINDA status* is 1 if the is filament loaded to the extruder, 0 otherwise. This could be used as filament runout sensor if probed regularly. +_FINDA status_ is 1 if the is filament loaded to the extruder, 0 otherwise. This could be used as filament runout sensor if probed regularly. -Load filament -============= +# Load filament -- MMU <= 'L*Filament index*\n' +- MMU <= 'L<_Filament index_>\n' -MMU will feed filament down to the extruder, when done +MMU will feed filament down to the extruder, when done: - MMU => 'ok\n' -Unload filament -============= +# Unload filament - MMU <= 'U0\n' -MMU will retract current filament from the extruder, when done +MMU will retract current filament from the extruder, when done: - MMU => 'ok\n' -Eject filament -============== +# Eject filament -- MMU <= 'E*Filament index*\n' +- MMU <= 'E<_Filament index_>\n' - MMU => 'ok\n' diff --git a/Marlin/src/feature/mmu/mmu2.cpp b/Marlin/src/feature/mmu/mmu2.cpp index 5ef56c7eacfc..0a4a2cb4ed29 100644 --- a/Marlin/src/feature/mmu/mmu2.cpp +++ b/Marlin/src/feature/mmu/mmu2.cpp @@ -24,6 +24,10 @@ #if HAS_PRUSA_MMU2 +/** + * mmu2.cpp - Support for PrÅÆÅ”a MMU2 and MMU2S + */ + #include "mmu2.h" #include "../../lcd/menu/menu_mmu2.h" @@ -36,17 +40,16 @@ MMU2 mmu2; #include "../../module/temperature.h" #include "../../module/planner.h" #include "../../module/stepper.h" -#include "../../MarlinCore.h" #if ENABLED(HOST_PROMPT_SUPPORT) - #include "../../feature/host_actions.h" + #include "../host_actions.h" #endif #if ENABLED(EXTENSIBLE_UI) #include "../../lcd/extui/ui_api.h" #endif -#define DEBUG_OUT ENABLED(MMU2_DEBUG) +#define DEBUG_OUT ENABLED(MMU_DEBUG) #include "../../core/debug_out.h" #define MMU_TODELAY 100 @@ -57,7 +60,7 @@ MMU2 mmu2; #define MMU2_SEND(S) tx_str(F(S "\n")) #define MMU2_RECV(S) rx_str(F(S "\n")) -#if ENABLED(MMU_EXTRUDER_SENSOR) +#if ENABLED(MMU2_EXTRUDER_SENSOR) uint8_t mmu_idl_sens = 0; static bool mmu_loading_flag = false; #endif @@ -92,9 +95,9 @@ struct E_Step { feedRate_t feedRate; //!< feed rate in mm/s }; -inline void unscaled_mmu2_e_move(const float &dist, const feedRate_t fr_mm_s, const bool sync=true) { - current_position.e += dist / planner.e_factor[active_extruder]; - line_to_current_position(fr_mm_s); +inline void unscaled_mmu2_e_move(const float dist, const feedRate_t fr_mm_s, const bool sync=true) { + motion.position.e += dist / planner.e_factor[motion.extruder]; + motion.goto_current_position(fr_mm_s); if (sync) planner.synchronize(); } @@ -106,12 +109,12 @@ void MMU2::init() { set_runout_valid(false); - #if PIN_EXISTS(MMU2_RST) - WRITE(MMU2_RST_PIN, HIGH); - SET_OUTPUT(MMU2_RST_PIN); + #if PIN_EXISTS(MMU_RST) + WRITE(MMU_RST_PIN, HIGH); + SET_OUTPUT(MMU_RST_PIN); #endif - MMU2_SERIAL.begin(MMU_BAUD); + MMU_SERIAL.begin(MMU_BAUD); extruder = MMU2_NO_TOOL; safe_delay(10); @@ -123,10 +126,10 @@ void MMU2::init() { void MMU2::reset() { DEBUG_ECHOLNPGM("MMU <= reset"); - #if PIN_EXISTS(MMU2_RST) - WRITE(MMU2_RST_PIN, LOW); + #if PIN_EXISTS(MMU_RST) + WRITE(MMU_RST_PIN, LOW); safe_delay(20); - WRITE(MMU2_RST_PIN, HIGH); + WRITE(MMU_RST_PIN, HIGH); #else MMU2_SEND("X0"); // Send soft reset #endif @@ -134,7 +137,7 @@ void MMU2::reset() { int8_t MMU2::get_current_tool() { return extruder == MMU2_NO_TOOL ? -1 : extruder; } -#if ANY(HAS_PRUSA_MMU2S, MMU_EXTRUDER_SENSOR) +#if ANY(HAS_PRUSA_MMU2S, MMU2_EXTRUDER_SENSOR) #define FILAMENT_PRESENT() (READ(FIL_RUNOUT1_PIN) != FIL_RUNOUT1_STATE) #else #define FILAMENT_PRESENT() true @@ -160,7 +163,7 @@ void MMU2::mmu_loop() { MMU2_SEND("S1"); // Read Version state = -2; } - else if (ELAPSED(millis(), prev_request + 30000)) { // 30sec after reset disable MMU + else if (ELAPSED(millis(), prev_request, 30000)) { // 30sec after reset disable MMU SERIAL_ECHOLNPGM("MMU not responding - DISABLED"); state = 0; } @@ -226,7 +229,7 @@ void MMU2::mmu_loop() { const int filament = cmd - MMU_CMD_T0; DEBUG_ECHOLNPGM("MMU <= T", filament); tx_printf(F("T%d\n"), filament); - TERN_(MMU_EXTRUDER_SENSOR, mmu_idl_sens = 1); // enable idler sensor, if any + TERN_(MMU2_EXTRUDER_SENSOR, mmu_idl_sens = 1); // enable idler sensor, if any state = 3; // wait for response } else if (WITHIN(cmd, MMU_CMD_L0, MMU_CMD_L0 + EXTRUDERS - 1)) { @@ -272,7 +275,7 @@ void MMU2::mmu_loop() { last_cmd = cmd; cmd = MMU_CMD_NONE; } - else if (ELAPSED(millis(), prev_P0_request + 300)) { + else if (ELAPSED(millis(), prev_P0_request, 300)) { MMU2_SEND("P0"); // Read FINDA state = 2; // wait for response } @@ -292,14 +295,14 @@ void MMU2::mmu_loop() { if (cmd == MMU_CMD_NONE) ready = true; state = 1; } - else if (ELAPSED(millis(), prev_request + MMU_P0_TIMEOUT)) // Resend request after timeout (3s) + else if (ELAPSED(millis(), prev_request, MMU_P0_TIMEOUT)) // Resend request after timeout (3s) state = 1; TERN_(HAS_PRUSA_MMU2S, check_filament()); break; case 3: // response to mmu commands - #if ENABLED(MMU_EXTRUDER_SENSOR) + #if ENABLED(MMU2_EXTRUDER_SENSOR) if (mmu_idl_sens) { if (FILAMENT_PRESENT() && mmu_loading_flag) { DEBUG_ECHOLNPGM("MMU <= 'A'"); @@ -331,7 +334,7 @@ void MMU2::mmu_loop() { last_cmd = MMU_CMD_NONE; } } - else if (ELAPSED(millis(), prev_request + MMU_CMD_TIMEOUT)) { + else if (ELAPSED(millis(), prev_request, MMU_CMD_TIMEOUT)) { // resend request after timeout if (last_cmd) { DEBUG_ECHOLNPGM("MMU retry"); @@ -361,8 +364,8 @@ bool MMU2::rx_str(FSTR_P fstr) { uint8_t i = strlen(rx_buffer); - while (MMU2_SERIAL.available()) { - rx_buffer[i++] = MMU2_SERIAL.read(); + while (MMU_SERIAL.available()) { + rx_buffer[i++] = MMU_SERIAL.read(); if (i == sizeof(rx_buffer) - 1) { DEBUG_ECHOLNPGM("rx buffer overrun"); @@ -393,7 +396,7 @@ bool MMU2::rx_str(FSTR_P fstr) { void MMU2::tx_str(FSTR_P fstr) { clear_rx_buffer(); PGM_P pstr = FTOP(fstr); - while (const char c = pgm_read_byte(pstr)) { MMU2_SERIAL.write(c); pstr++; } + while (const char c = pgm_read_byte(pstr)) { MMU_SERIAL.write(c); pstr++; } prev_request = millis(); } @@ -403,7 +406,7 @@ void MMU2::tx_str(FSTR_P fstr) { void MMU2::tx_printf(FSTR_P format, int argument = -1) { clear_rx_buffer(); const uint8_t len = sprintf_P(tx_buffer, FTOP(format), argument); - for (uint8_t i = 0; i < len; ++i) MMU2_SERIAL.write(tx_buffer[i]); + for (uint8_t i = 0; i < len; ++i) MMU_SERIAL.write(tx_buffer[i]); prev_request = millis(); } @@ -413,7 +416,7 @@ void MMU2::tx_printf(FSTR_P format, int argument = -1) { void MMU2::tx_printf(FSTR_P format, int argument1, int argument2) { clear_rx_buffer(); const uint8_t len = sprintf_P(tx_buffer, FTOP(format), argument1, argument2); - for (uint8_t i = 0; i < len; ++i) MMU2_SERIAL.write(tx_buffer[i]); + for (uint8_t i = 0; i < len; ++i) MMU_SERIAL.write(tx_buffer[i]); prev_request = millis(); } @@ -421,7 +424,7 @@ void MMU2::tx_printf(FSTR_P format, int argument1, int argument2) { * Empty the rx buffer */ void MMU2::clear_rx_buffer() { - while (MMU2_SERIAL.available()) MMU2_SERIAL.read(); + while (MMU_SERIAL.available()) MMU_SERIAL.read(); rx_buffer[0] = '\0'; } @@ -442,7 +445,7 @@ bool MMU2::rx_ok() { void MMU2::check_version(const uint16_t buildnr) { if (buildnr < MMU_REQUIRED_FW_BUILDNR) { SERIAL_ERROR_MSG("Invalid MMU2 firmware. Version >= " STRINGIFY(MMU_REQUIRED_FW_BUILDNR) " required."); - kill(GET_TEXT_F(MSG_KILL_MMU2_FIRMWARE)); + marlin.kill(GET_TEXT_F(MSG_KILL_MMU2_FIRMWARE)); } } @@ -502,7 +505,7 @@ inline void beep_bad_cmd() { BUZZ(400, 40); } if (load_to_gears()) { extruder = index; // filament change is finished - active_extruder = 0; + motion.extruder = 0; stepper.enable_extruder(); SERIAL_ECHO_MSG(STR_ACTIVE_EXTRUDER, extruder); } @@ -526,9 +529,9 @@ 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); + while (!thermalManager.wait_for_hotend(motion.extruder, false)) safe_delay(100); load_to_nozzle(index); #else beep_bad_cmd(); @@ -536,7 +539,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(); @@ -547,7 +550,7 @@ inline void beep_bad_cmd() { BUZZ(400, 40); } mmu_loop(); stepper.enable_extruder(); extruder = index; - active_extruder = 0; + motion.extruder = 0; } #else beep_bad_cmd(); @@ -555,7 +558,7 @@ inline void beep_bad_cmd() { BUZZ(400, 40); } } break; case 'c': { - while (!thermalManager.wait_for_hotend(active_extruder, false)) safe_delay(100); + while (!thermalManager.wait_for_hotend(motion.extruder, false)) safe_delay(100); load_to_nozzle_sequence(); } break; } @@ -563,7 +566,7 @@ inline void beep_bad_cmd() { BUZZ(400, 40); } set_runout_valid(true); } -#elif ENABLED(MMU_EXTRUDER_SENSOR) +#elif ENABLED(MMU2_EXTRUDER_SENSOR) /** * Handle tool change @@ -588,7 +591,7 @@ inline void beep_bad_cmd() { BUZZ(400, 40); } mmu_continue_loading(); //command(MMU_CMD_C0); extruder = index; - active_extruder = 0; + motion.extruder = 0; stepper.enable_extruder(); SERIAL_ECHO_MSG(STR_ACTIVE_EXTRUDER, extruder); @@ -614,9 +617,9 @@ 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); + while (!thermalManager.wait_for_hotend(motion.extruder, false)) safe_delay(100); load_to_nozzle(index); #else beep_bad_cmd(); @@ -625,7 +628,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(); @@ -637,7 +640,7 @@ inline void beep_bad_cmd() { BUZZ(400, 40); } stepper.enable_extruder(); extruder = index; - active_extruder = 0; + motion.extruder = 0; #else beep_bad_cmd(); #endif @@ -645,7 +648,7 @@ inline void beep_bad_cmd() { BUZZ(400, 40); } case 'c': { DEBUG_ECHOLNPGM("case c\n"); - while (!thermalManager.wait_for_hotend(active_extruder, false)) safe_delay(100); + while (!thermalManager.wait_for_hotend(motion.extruder, false)) safe_delay(100); load_to_nozzle_sequence(); } break; } @@ -656,7 +659,7 @@ inline void beep_bad_cmd() { BUZZ(400, 40); } void MMU2::mmu_continue_loading() { // Try to load the filament a limited number of times bool fil_present = 0; - for (uint8_t i = 0; i < MMU_LOADING_ATTEMPTS_NR; i++) { + for (uint8_t i = 0; i < MMU2_LOADING_ATTEMPTS_NR; i++) { DEBUG_ECHOLNPGM("Load attempt #", i + 1); // Done as soon as filament is present @@ -668,8 +671,8 @@ inline void beep_bad_cmd() { BUZZ(400, 40); } stepper.enable_extruder(); const millis_t expire_ms = millis() + 3000; do { - current_position.e += 1; - line_to_current_position(MMU_LOAD_FEEDRATE); + motion.position.e += 1; + motion.goto_current_position(MMU_LOAD_FEEDRATE); planner.synchronize(); // When (T0 rx->ok) load is ready, but in fact it did not load // successfully or an overload created pressure in the extruder. @@ -688,7 +691,7 @@ inline void beep_bad_cmd() { BUZZ(400, 40); } mmu_idl_sens = 0; } -#else // !HAS_PRUSA_MMU2S && !MMU_EXTRUDER_SENSOR +#else // !HAS_PRUSA_MMU2S && !MMU2_EXTRUDER_SENSOR /** * Handle tool change @@ -705,7 +708,7 @@ inline void beep_bad_cmd() { BUZZ(400, 40); } manage_response(true, true); command(MMU_CMD_C0); extruder = index; // Filament change is finished - active_extruder = 0; + motion.extruder = 0; stepper.enable_extruder(); SERIAL_ECHO_MSG(STR_ACTIVE_EXTRUDER, extruder); ui.reset_status(); @@ -729,9 +732,9 @@ 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); + while (!thermalManager.wait_for_hotend(motion.extruder, false)) safe_delay(100); load_to_nozzle(index); #else beep_bad_cmd(); @@ -740,7 +743,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(); @@ -751,7 +754,7 @@ inline void beep_bad_cmd() { BUZZ(400, 40); } stepper.enable_extruder(); extruder = index; - active_extruder = 0; + motion.extruder = 0; #else beep_bad_cmd(); #endif @@ -759,7 +762,7 @@ inline void beep_bad_cmd() { BUZZ(400, 40); } case 'c': { DEBUG_ECHOLNPGM("case c\n"); - while (!thermalManager.wait_for_hotend(active_extruder, false)) safe_delay(100); + while (!thermalManager.wait_for_hotend(motion.extruder, false)) safe_delay(100); load_to_nozzle_sequence(); } break; } @@ -782,10 +785,10 @@ void MMU2::command(const uint8_t mmu_cmd) { * Wait for response from MMU */ bool MMU2::get_response() { - while (cmd != MMU_CMD_NONE) idle(); + while (cmd != MMU_CMD_NONE) marlin.idle(); while (!ready) { - idle(); + marlin.idle(); if (state != 3) break; } @@ -803,7 +806,7 @@ void MMU2::manage_response(const bool move_axes, const bool turn_off_nozzle) { constexpr xyz_pos_t park_point = NOZZLE_PARK_POINT; bool response = false, mmu_print_saved = false; xyz_pos_t resume_position; - celsius_t resume_hotend_temp = thermalManager.degTargetHotend(active_extruder); + celsius_t resume_hotend_temp = thermalManager.degTargetHotend(motion.extruder); KEEPALIVE_STATE(PAUSED_FOR_USER); @@ -820,12 +823,12 @@ void MMU2::manage_response(const bool move_axes, const bool turn_off_nozzle) { SERIAL_ECHOLNPGM("MMU not responding"); - resume_hotend_temp = thermalManager.degTargetHotend(active_extruder); - resume_position = current_position; + resume_hotend_temp = thermalManager.degTargetHotend(motion.extruder); + resume_position = motion.position; - if (move_axes && all_axes_homed()) nozzle.park(0, park_point); + if (move_axes && motion.all_axes_homed()) nozzle.park(0, park_point); - if (turn_off_nozzle) thermalManager.setTargetHotend(0, active_extruder); + if (turn_off_nozzle) thermalManager.setTargetHotend(0, motion.extruder); mmu2_not_responding(); } @@ -834,10 +837,10 @@ void MMU2::manage_response(const bool move_axes, const bool turn_off_nozzle) { SERIAL_ECHOLNPGM("\nMMU starts responding"); if (turn_off_nozzle && resume_hotend_temp) { - thermalManager.setTargetHotend(resume_hotend_temp, active_extruder); + thermalManager.setTargetHotend(resume_hotend_temp, motion.extruder); LCD_MESSAGE(MSG_HEATING); ERR_BUZZ(); - while (!thermalManager.wait_for_hotend(active_extruder, false)) safe_delay(1000); + while (!thermalManager.wait_for_hotend(motion.extruder, false)) safe_delay(1000); } LCD_MESSAGE(MSG_MMU2_RESUMING); @@ -846,11 +849,11 @@ void MMU2::manage_response(const bool move_axes, const bool turn_off_nozzle) { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" - if (move_axes && all_axes_homed()) { + if (move_axes && motion.all_axes_homed()) { // Move XY to starting position, then Z - do_blocking_move_to_xy(resume_position, feedRate_t(NOZZLE_PARK_XY_FEEDRATE)); + motion.blocking_move_xy(resume_position, feedRate_t(NOZZLE_PARK_XY_FEEDRATE)); // Move Z_AXIS to saved position - do_blocking_move_to_z(resume_position.z, feedRate_t(NOZZLE_PARK_Z_FEEDRATE)); + motion.blocking_move_z(resume_position.z, feedRate_t(NOZZLE_PARK_Z_FEEDRATE)); } #pragma GCC diagnostic pop @@ -891,8 +894,8 @@ void MMU2::filament_runout() { } bool MMU2::can_load() { - static const E_Step can_load_sequence[] PROGMEM = { MMU2_CAN_LOAD_SEQUENCE }, - can_load_increment_sequence[] PROGMEM = { MMU2_CAN_LOAD_INCREMENT_SEQUENCE }; + static constexpr E_Step can_load_sequence[] PROGMEM = { MMU2_CAN_LOAD_SEQUENCE }, + can_load_increment_sequence[] PROGMEM = { MMU2_CAN_LOAD_INCREMENT_SEQUENCE }; execute_extruder_sequence(can_load_sequence, COUNT(can_load_sequence)); @@ -932,7 +935,7 @@ void MMU2::load_to_feeder(const uint8_t index) { bool MMU2::load_to_nozzle(const uint8_t index) { if (!_enabled) return false; - if (thermalManager.tooColdToExtrude(active_extruder)) { + if (thermalManager.tooColdToExtrude(motion.extruder)) { mmu2_attn_buzz(); LCD_ALERTMESSAGE(MSG_HOTEND_TOO_COLD); return false; @@ -953,7 +956,7 @@ bool MMU2::load_to_nozzle(const uint8_t index) { if (success) { mmu_loop(); extruder = index; - active_extruder = 0; + motion.extruder = 0; load_to_nozzle_sequence(); mmu2_attn_buzz(); } @@ -964,7 +967,7 @@ bool MMU2::eject_filament(const uint8_t index, const bool recover) { if (!_enabled) return false; - if (thermalManager.tooColdToExtrude(active_extruder)) { + if (thermalManager.tooColdToExtrude(motion.extruder)) { mmu2_attn_buzz(); LCD_ALERTMESSAGE(MSG_HOTEND_TOO_COLD); return false; @@ -981,7 +984,7 @@ bool MMU2::eject_filament(const uint8_t index, const bool recover) { mmu2_attn_buzz(); TERN_(HOST_PROMPT_SUPPORT, hostui.continue_prompt(GET_TEXT_F(MSG_MMU2_EJECT_RECOVER))); TERN_(EXTENSIBLE_UI, ExtUI::onUserConfirmRequired(GET_TEXT_F(MSG_MMU2_EJECT_RECOVER))); - TERN_(HAS_RESUME_CONTINUE, wait_for_user_response()); + TERN_(HAS_RESUME_CONTINUE, marlin.wait_for_user_response()); mmu2_attn_buzz(); command(MMU_CMD_R0); @@ -1009,7 +1012,7 @@ bool MMU2::unload() { if (!_enabled) return false; - if (thermalManager.tooColdToExtrude(active_extruder)) { + if (thermalManager.tooColdToExtrude(motion.extruder)) { mmu2_attn_buzz(); LCD_ALERTMESSAGE(MSG_HOTEND_TOO_COLD); return false; @@ -1041,7 +1044,8 @@ void MMU2::load_to_nozzle_sequence() { execute_extruder_sequence(sequence, COUNT(sequence)); } -void MMU2::execute_extruder_sequence(const E_Step * sequence, int steps) { +void MMU2::execute_extruder_sequence(const E_Step * const sequence, const uint8_t steps) { + planner.synchronize(); const E_Step *step = sequence; diff --git a/Marlin/src/feature/mmu/mmu2.h b/Marlin/src/feature/mmu/mmu2.h index 2c9fd3308d97..117b3a6046dd 100644 --- a/Marlin/src/feature/mmu/mmu2.h +++ b/Marlin/src/feature/mmu/mmu2.h @@ -21,6 +21,10 @@ */ #pragma once +/** + * mmu2.h - Support for PrÅÆÅ”a MMU2 and MMU2S + */ + #include "../../inc/MarlinConfig.h" #if HAS_FILAMENT_SENSOR @@ -70,7 +74,7 @@ class MMU2 { static bool get_response(); static void manage_response(const bool move_axes, const bool turn_off_nozzle); - static void execute_extruder_sequence(const E_Step * sequence, int steps); + static void execute_extruder_sequence(const E_Step * const sequence, const uint8_t steps); static void ramming_sequence(); static void load_to_nozzle_sequence(); @@ -85,7 +89,7 @@ class MMU2 { FORCE_INLINE static bool load_to_gears() { return true; } #endif - #if ENABLED(MMU_EXTRUDER_SENSOR) + #if ENABLED(MMU2_EXTRUDER_SENSOR) #define MMU_LOAD_FEEDRATE 19.02f // (mm/s) static void mmu_continue_loading(); #endif diff --git a/Marlin/src/feature/mmu3/SpoolJoin.cpp b/Marlin/src/feature/mmu3/SpoolJoin.cpp new file mode 100644 index 000000000000..f27d2bc7e935 --- /dev/null +++ b/Marlin/src/feature/mmu3/SpoolJoin.cpp @@ -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 . + * + */ + +/** + * 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. + FLIP(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 diff --git a/Marlin/src/feature/mmu3/SpoolJoin.h b/Marlin/src/feature/mmu3/SpoolJoin.h new file mode 100644 index 000000000000..b205d26ef501 --- /dev/null +++ b/Marlin/src/feature/mmu3/SpoolJoin.h @@ -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 . + * + */ +#pragma once + +/** + * SpoolJoin.h + */ + +#include "../../MarlinCore.h" + +#include + +// 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; diff --git a/Marlin/src/feature/mmu3/mmu3-serial-protocol.md b/Marlin/src/feature/mmu3/mmu3-serial-protocol.md new file mode 100644 index 000000000000..0d0f7ac6e0a2 --- /dev/null +++ b/Marlin/src/feature/mmu3/mmu3-serial-protocol.md @@ -0,0 +1,172 @@ +# 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:S1*ad\n +MMU3:S2*10\n +MMU3:S0*c6\n +MMU3:>S0*c6\n +MMU3:>S0*c6\n +... +``` + +Once communication is established the MMU responds with: + +``` +MMU3:S1*ad\n +MMU3:S2*10\n +MMU3:M1*{CRC8}; +MMU3:<---nothing--- +``` + +``` +MMU3:>P0 +MMU3:T{Filament index}*{CRC8}\n +MMU3:T0*{CRC8}\n + +MMU3:>Q0*{CRC8}\n +MMU3: FeedingToFinda + +MMU3:>Q0*{CRC8}\n +MMU3: 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: 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:L{Filament index}*{CRC8}\n +MMU3:Q0*{CRC8}\n +``` + +The MMU will respond with status messages: + +``` +MMU3:Q0*{CRC8}\n +MMU3: 'ok\n'` + +## Eject filament + +- `MMU <= 'E*Filament index*\n'` +- `MMU => 'ok\n'` diff --git a/Marlin/src/feature/mmu3/mmu3.cpp b/Marlin/src/feature/mmu3/mmu3.cpp new file mode 100644 index 000000000000..4c3c5e6fd723 --- /dev/null +++ b/Marlin/src/feature/mmu3/mmu3.cpp @@ -0,0 +1,1184 @@ +/** + * 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 . + * + */ + +/** + * mmu2.cpp + */ + +#include "../../inc/MarlinConfigPre.h" + +#if HAS_PRUSA_MMU3 + +#include "mmu3.h" +#include "mmu3_error_converter.h" +#include "mmu3_fsensor.h" +#include "mmu3_log.h" +#include "mmu3_marlin.h" +#include "mmu3_marlin_macros.h" +#include "mmu3_power.h" +#include "mmu3_progress_converter.h" +#include "mmu3_reporting.h" + +#include "SpoolJoin.h" + +#include "../../inc/MarlinConfig.h" + +#include "../../lcd/marlinui.h" +#include "../../module/planner.h" +#include "../../module/motion.h" +#include "../../gcode/parser.h" +#include "../../gcode/queue.h" +#include "../runout.h" +#if HAS_LEVELING + #include "../bedlevel/bedlevel.h" +#endif +#include "../pause.h" +#include "../../libs/stopwatch.h" + +// As of FW 3.12 we only support building the FW with only one extruder, all the multi-extruder infrastructure will be removed. +// Saves at least 800B of code size +//#ifdef __AVR__ +//static_assert(EXTRUDERS == 1); +//#endif + +#define MMU2_NO_TOOL 99 + +MMU3::MMU3 mmu3; + +namespace MMU3 { + + template + void waitForHotendTargetTemp(uint16_t delay, F f) { + while (((thermal_degTargetHotend() - thermal_degHotend()) > 5)) { + f(); + safe_delay_keep_alive(delay); + } + } + + void WaitForHotendTargetTempBeep() { + waitForHotendTargetTemp(3000, []{}); + //MakeSound(Prompt); + } + + uint8_t MMU3::cutter_mode; // Initialized by settings.load + int MMU3::cutter_mode_addr; // Initialized by settings.load + uint8_t MMU3::stealth_mode; // Initialized by settings.load + int MMU3::stealth_mode_addr; // Initialized by settings.load + // TODO: Currently, by logic, the value stored in the EEPROM for is ignored and + // mmu_hw_enabled is always overwritten by the MMU State. Thus restarting + // printer will always set the MMU as senabled. + bool MMU3::mmu_hw_enabled; // Initialized by settings.load + int MMU3::mmu_hw_enabled_addr; // Initialized by settings.load + + MMU3::MMU3() + : logic(MMU3_TOOL_CHANGE_LOAD_LENGTH, MMU3_LOAD_TO_NOZZLE_FEED_RATE) + , extruder(MMU2_NO_TOOL) + , tool_change_extruder(MMU2_NO_TOOL) + , resume_position() + , resume_hotend_temp(0) + , logicStepLastStatus(StepStatus::Finished) + , _state(xState::Stopped) + , mmu_print_saved(SavedState::None) + , loadFilamentStarted(false) + , unloadFilamentStarted(false) + , toolchange_counter(0) + , _tmcFailures(0) { } + + void MMU3::status() { + // Useful information to see during bootup and change state + SERIAL_ECHOLN(F("MMU is "), mmu_hw_enabled ? GET_TEXT_F(MSG_ON) : GET_TEXT_F(MSG_OFF)); + } + + void MMU3::start() { + mmu_hw_enabled = true; + + #if ENABLED(EEPROM_SETTINGS) + // Save mmu_hw_enabled to EEPROM + // TODO: Move to settings.cpp (for now) + persistentStore.access_start(); + persistentStore.write_data(mmu_hw_enabled_addr, mmu_hw_enabled); + persistentStore.access_finish(); + settings.save(); + #endif + + MMU_SERIAL.begin(MMU_BAUD); + + powerOn(); + MMU_SERIAL.flush(); // Make sure the UART buffer is clear before starting communication + + setCurrentTool(MMU2_NO_TOOL); + _state = xState::Connecting; + + // Start communication + logic.start(); + logic.ResetRetryAttempts(); + logic.ResetCommunicationTimeoutAttempts(); + } + + void MMU3::stop() { + stopKeepPowered(); + powerOff(); + } + + void MMU3::stopKeepPowered() { + mmu_hw_enabled = false; + + #if ENABLED(EEPROM_SETTINGS) + // Save mmu_hw_enabled to EEPROM + persistentStore.access_start(); + persistentStore.write_data(mmu_hw_enabled_addr, mmu_hw_enabled); + persistentStore.access_finish(); + settings.save(); + #endif + + _state = xState::Stopped; + logic.stop(); + MMU_SERIAL.end(); + } + + void MMU3::tune() { + switch (lastErrorCode) { + case ErrorCode::HOMING_SELECTOR_FAILED: + case ErrorCode::HOMING_IDLER_FAILED: { + // Prompt a menu for different values + tuneIdlerStallguardThreshold(); + break; + } + default: break; + } + } + + void MMU3::reset(ResetForm level) { + switch (level) { + case Software: resetX0(); break; + case ResetPin: triggerResetPin(); break; + case CutThePower: powerCycle(); break; + case EraseEEPROM: resetX42(); break; + default: break; + } + } + + void MMU3::resetX0() { logic.ResetMMU(); } // Send soft reset + void MMU3::resetX42() { logic.ResetMMU(42); } + + void MMU3::triggerResetPin() { power_reset(); } + + void MMU3::powerCycle() { + // cut the power to the MMU and after a while restore it + // Sadly, MK3/S/+ cannot do this + stop(); + safe_delay_keep_alive(1000); + start(); + } + + void MMU3::powerOff() { power_off(); } + void MMU3::powerOn() { power_on(); } + + bool MMU3::readRegister(uint8_t address) { + if (!waitForMMUReady()) return false; + + do { + logic.readRegister(address); // we may signal the accepted/rejected status of the response as return value of this function + } while (!manage_response(false, false)); + + // Update cached value + lastReadRegisterValue = logic.rsp.paramValue; + return true; + } + + bool __attribute__((noinline)) MMU3::writeRegister(uint8_t address, uint16_t data) { + if (!waitForMMUReady()) return false; + + // special cases - intercept requests of registers which influence the printer's behaviour too + perform the change even on the printer's side + switch (address) { + case (uint8_t)Register::Extra_Load_Distance: logic.PlanExtraLoadDistance(data); break; + case (uint8_t)Register::Pulley_Slow_Feedrate: logic.PlanPulleySlowFeedRate(data); break; + default: break; // Don't intercept any other register writes + } + + do { + logic.writeRegister(address, data); // we may signal the accepted/rejected status of the response as return value of this function + } while (!manage_response(false, false)); + + return true; + } + + void MMU3::mmu_loop() { + // We only leave this method if the current command was successfully + // completed - that's the Marlin's way of blocking operation + // Atomic compare_exchange would have been the most appropriate solution + // here, but this gets called only in Marlin's task, so thread safety + // should be kept + static bool avoidRecursion = false; + if (avoidRecursion) return; + avoidRecursion = true; + + mmu_loop_inner(true); + + avoidRecursion = false; + } + + void __attribute__((noinline)) MMU3::mmu_loop_inner(bool reportErrors) { + logicStepLastStatus = logicStep(reportErrors); // it looks like the mmu_loop doesn't need to be a blocking call + CheckErrorScreenUserInput(); + } + + /** + * Check if there are extruder moves planned ahead. + * + * TODO: This should go to the planner, but for now keep it here! + */ + bool MMU3::e_active() { + unsigned char e_active = 0; + block_t *block; + if (planner.block_buffer_tail != planner.block_buffer_head) { + uint8_t block_index = planner.block_buffer_tail; + while (block_index != planner.block_buffer_head) { + block = &planner.block_buffer[block_index]; + if (block->steps.e != 0) e_active++; + block_index = (block_index + 1) & (BLOCK_BUFFER_SIZE - 1); + } + } + return (e_active > 0); + } + + /** + * Trigger an M600 or the SpoolJoin feature if the FINDA cannot detect any + * filament during the print. + * + * In case of SpoolJoin feature is triggered, Marlin's implementation is a + * little different than Prusa's, as we are completely consuming the filament + * before switching to the next slot. There will be a little bit of filament + * left when the new filament is extruded SpoolJoin is not intended to be used with + * multi color/material prints so this should be fine. + */ + void MMU3::checkFINDARunout() { + if (!findaDetectsFilament() + //&& marlin.printJobOngoing() + && parser.codenum != 600 + && TERN1(HAS_LEVELING, planner.leveling_active) + && xy_are_trusted() + && e_active() + #if ENABLED(MMU3_SPOOL_JOIN_CONSUMES_ALL_FILAMENT) + && runout.enabled // to prevent M600 to be triggered during M600 AUTO + && !FILAMENT_PRESENT() // so the filament is totally consumed + #endif + ) { + SERIAL_ECHOLN_P("FINDA filament runout!"); + if (spooljoin.isEnabled() && get_current_tool() != (uint8_t)FILAMENT_UNKNOWN) { // Can't auto if F=? + #if ENABLED(MMU3_SPOOL_JOIN_CONSUMES_ALL_FILAMENT) + // set the current tool to FILAMENT_UNKNOWN so that we don't try to unload it + extruder = MMU2_NO_TOOL; + // disable the filament runout sensor (this is going to be re-enabled after the filament is loaded) + runout.reset(); + runout.filament_ran_out = false; // trying to disable the purge more / continue message + runout.enabled = false; + #endif + queue.enqueue_now(F("M600A")); // Save print and run M600 A (automatic) command + } + else { + marlin_stop_and_save_print_to_ram(); + resume_print(); + queue.enqueue_now(F("M600")); // Save print and run M600 command + } + } + } + + struct ReportingRAII { + CommandInProgress cip; + explicit inline __attribute__((always_inline)) ReportingRAII(CommandInProgress cip) + : cip(cip) { + BeginReport(cip, ProgressCode::EngagingIdler); + } + inline __attribute__((always_inline)) ~ReportingRAII() { + EndReport(cip, ProgressCode::OK); + } + }; + + bool MMU3::waitForMMUReady() { + switch (state()) { + case xState::Stopped: return false; + case xState::Connecting: + // Should we wait until the MMU reconnects? + // Fire up a fsm_dlg and show "MMU not responding"? + default: return true; + } + } + + bool MMU3::retryIfPossible(const ErrorCode ec) { + if (logic.RetryAttempts()) { + SetButtonResponse(ButtonOperations::Retry); + // check, that Retry is actually allowed on that operation + if (ButtonAvailable(ec) != Buttons::NoButton) { + logic.SetInAutoRetry(true); + SERIAL_ECHOLN_P("RetryButtonPressed"); + // We don't decrement until the button is acknowledged by the MMU. + // --retryAttempts; // "used" one retry attempt + return true; + } + } + logic.SetInAutoRetry(false); + return false; + } + + bool MMU3::verifyFilamentEnteredPTFE() { + planner_synchronize(); + + if (WhereIsFilament() != FilamentState::AT_FSENSOR) + return false; + + // MMU has finished its load, push the filament further by some defined constant length + // If the filament sensor reads 0 at any moment, then report FAILURE + const float tryload_length = MMU3_CHECK_FILAMENT_PRESENCE_EXTRUSION_LENGTH - logic.ExtraLoadDistance(); + TryLoadUnloadReporter tlur(tryload_length); + + /** + * The position is a triangle wave. + * Current position is not zero, it is an offset + * + * Keep in mind that the relationship between machine position + * and pixel index is not linear. The area around the amplitude + * needs to be taken care of carefully. The current implementation + * handles each move separately so there is no need to watch for the change + * in the slope's sign or check the last machine position. + * y(x) + * ā–² + * │ ^◄────────── tryload_length + motion.position + * machine │ / \ + * position │ / \◄────────── stepper_position_mm + motion.position + * (mm) │ / \ + * │ / \ + * │/ \◄───────motion.position + * └──────────────► x + * 0 19 + * pixel # + */ + + bool filament_inserted = true; // Expect success + // Pixel index will go from 0 to 10, then back from 10 to 0. + // A change in this value indicates a new pixel should be drawn on the display. + for (uint8_t move = 0; move < 2; move++) { + extruder_move(move == 0 ? tryload_length : -tryload_length, MMU3_VERIFY_LOAD_TO_NOZZLE_FEED_RATE); + while (planner_any_moves()) { + filament_inserted = filament_inserted && (WhereIsFilament() == FilamentState::AT_FSENSOR); + tlur.Progress(filament_inserted); + safe_delay_keep_alive(0); + } + } + Disable_E0(); + if (!filament_inserted) IncrementLoadFails(); + tlur.DumpToSerial(); + return filament_inserted; + } + + bool MMU3::toolChangeCommonOnce(uint8_t slot) { + static_assert(MMU3_MAX_RETRIES > 1); // Need >1 retries to do the cut in the last attempt + uint8_t retries = 0; + for (;;) { + for (;;) { + Disable_E0(); // It may seem counterintuitive to disable the E-motor, but it gets enabled in the planner whenever the E-motor is to move + tool_change_extruder = slot; + logic.ToolChange(slot); // Let the MMU pull the filament out and push a new one in + + if (manage_response(true, true)) break; + + // Otherwise: failed to perform the command - unload first and then let it run again + IncrementMMUFails(); + + // Just in case we stood in an error screen for too long and the hotend got cold + resumeHotendTemp(); + // If the extruder has been parked, it will get unparked once the ToolChange command finishes OK + // - so no resumeUnpark() at this spot + + unloadInner(); + // If we run out of retries, we must do something ... maybe raise an error screen and allow the user to do something. + // But honestly - if the MMU restarts during every toolchange something else is seriously broken + // and stopping a print is probably our best option. + } + if (verifyFilamentEnteredPTFE()) return true; // success + + // Prepare a retry attempt + unloadInner(); + if (retries == (MMU3_MAX_RETRIES) - 1 && cutter_enabled()) { + cutFilamentInner(slot); // try cutting filament tip at the last attempt + retries = 0; // reset retries every MMU3_MAX_RETRIES + } + + ++retries; + } + return false; // Couldn't accomplish the task + } + + void MMU3::toolChangeCommon(uint8_t slot) { + while (!toolChangeCommonOnce(slot)) { // While not successfully fed into extruder's PTFE tube... + // Failed autoretry, report an error by forcing a "printer" error into the MMU infrastructure - it is a hack to leverage existing code + // @@TODO theoretically logic layer may not need to be spoiled with the printer error - maybe just the manage_response needs it... + logic.SetPrinterError(ErrorCode::LOAD_TO_EXTRUDER_FAILED); + // We only have to wait for the user to fix the issue and press "Retry". + // Please see checkUserInput() for details how we "leave" manage_response. + // If manage_response returns false at this spot (MMU operation interrupted aka MMU reset) + // we can safely continue because the MMU is not doing an operation now. + static_cast(manage_response(true, true)); // yes, I'd like to silence [[nodiscard]] warning at this spot by casting to void + } + + setCurrentTool(slot); // filament change is finished + spooljoin.setSlot(slot); + + ++toolchange_counter; + + // Also increment the total number of tool changes + operation_statistics.increment_tool_change_counter(); + } + + bool MMU3::tool_change(uint8_t slot) { + if (!waitForMMUReady()) return false; + + if (slot != extruder) { + if ( + //findaDetectsFilament() + //!card.isStillPrinting() && !usb_timer.running() + !marlin_printingIsActive() + ) { + // If Tcodes are used manually through the serial + // we need to unload manually as well -- but only if FINDA detects filament + unload(); + } + + ReportingRAII rep(CommandInProgress::ToolChange); + FSensorBlockRunout blockRunout; + planner_synchronize(); + toolChangeCommon(slot); + } + return true; + } + + /** + * Handle special T?/Tx/Tc commands + * + * - T? Gcode to extrude shouldn't have to follow, load to extruder wheels is done automatically + * - Tx Same as T?, except nozzle doesn't have to be preheated. Tc must be placed after extruder nozzle is preheated to finish filament load. + * - Tc Load to nozzle after filament was prepared by Tx and extruder nozzle is already heated. + */ + bool MMU3::tool_change(char code, uint8_t slot) { + if (!waitForMMUReady()) return false; + + FSensorBlockRunout blockRunout; + + switch (code) { + case '?': { + waitForHotendTargetTemp(100, []{}); + load_to_nozzle(slot); + } + break; + + case 'x': { + thermal_setExtrudeMintemp(0); // Allow cold extrusion since Tx only loads to the gears not nozzle + tool_change(slot); + thermal_setExtrudeMintemp(EXTRUDE_MINTEMP); + } + break; + + case 'c': { + waitForHotendTargetTemp(100, []{}); + execute_load_to_nozzle_sequence(); + } + break; + } + + return true; + } + + void MMU3::get_statistics() { + logic.Statistics(); + } + + uint8_t __attribute__((noinline)) MMU3::get_current_tool() const { + return extruder == MMU2_NO_TOOL ? (uint8_t)FILAMENT_UNKNOWN : extruder; + } + + uint8_t MMU3::get_tool_change_tool() const { + return tool_change_extruder == MMU2_NO_TOOL ? (uint8_t)FILAMENT_UNKNOWN : tool_change_extruder; + } + + void MMU3::setCurrentTool(uint8_t ex) { + extruder = ex; + MMU2_ECHO_MSGRPGM(PSTR("MMU2tool=")); + SERIAL_ECHOLN((int)ex); + } + + bool MMU3::set_filament_type(uint8_t /*slot*/, uint8_t /*type*/) { + if (!waitForMMUReady()) return false; + + // @@TODO - this is not supported in the new MMU yet + // slot = slot; // @@TODO + // type = type; // @@TODO + // cmd_arg = filamentType; + // command(MMU_CMD_F0 + index); + + if (!manage_response(false, false)) { + // @@TODO failed to perform the command - retry + // Comment: how is it possible for a filament type set to fail? manage_response(true, true) + } + + return true; + } + + void MMU3::unloadInner() { + FSensorBlockRunout blockRunout; + filament_ramming(); + + // we assume the printer managed to relieve filament tip from the gears, + // so repeating that part in case of an MMU restart is not necessary + for (;;) { + Disable_E0(); + logic.UnloadFilament(); + if (manage_response(false, true)) break; + IncrementMMUFails(); + } + //MakeSound(Confirm); + + // no active tool + setCurrentTool(MMU2_NO_TOOL); + tool_change_extruder = MMU2_NO_TOOL; + } + + bool MMU3::unload() { + if (!waitForMMUReady()) return false; + + WaitForHotendTargetTempBeep(); + + // Scope for ReportingRAII + { + ReportingRAII rep(CommandInProgress::UnloadFilament); + unloadInner(); + } + + ScreenUpdateEnable(); + return true; + } + + void MMU3::cutFilamentInner(uint8_t slot) { + for (;;) { + Disable_E0(); + logic.CutFilament(slot); + if (manage_response(false, true)) break; + IncrementMMUFails(); + } + } + + bool MMU3::cut_filament(uint8_t slot, bool enableFullScreenMsg /*= true*/) { + if (!waitForMMUReady()) return false; + + if (enableFullScreenMsg) fullScreenMsgCut(slot); + + // Scope for ReportingRAII + { + if (findaDetectsFilament()) unload(); + + ReportingRAII rep(CommandInProgress::CutFilament); + cutFilamentInner(slot); + setCurrentTool(MMU2_NO_TOOL); + tool_change_extruder = MMU2_NO_TOOL; + //MakeSound(SoundType::Confirm); + } + ScreenUpdateEnable(); + return true; + } + + bool MMU3::loading_test(uint8_t slot) { + fullScreenMsgTest(slot); + tool_change(slot); + planner_synchronize(); + unload(); + ScreenUpdateEnable(); + return true; + } + + bool MMU3::load_to_feeder(uint8_t slot) { + if (!waitForMMUReady()) return false; + + fullScreenMsgLoad(slot); + + // Scope for ReportingRAII + { + ReportingRAII rep(CommandInProgress::LoadFilament); + for (;;) { + Disable_E0(); + logic.LoadFilament(slot); + if (manage_response(false, false)) break; + IncrementMMUFails(); + } + //MakeSound(SoundType::Confirm); + } + ScreenUpdateEnable(); + return true; + } + + bool MMU3::load_to_nozzle(uint8_t slot) { + if (!waitForMMUReady()) return false; + + WaitForHotendTargetTempBeep(); + + fullScreenMsgLoad(slot); + + // Scope for ReportingRAII + { + // Used for MMU-menu operation "Load to Nozzle" + ReportingRAII rep(CommandInProgress::ToolChange); + FSensorBlockRunout blockRunout; + + // Filament already loaded? Free it and shape its tip properly. + if (extruder != MMU2_NO_TOOL) filament_ramming(); + + toolChangeCommon(slot); + + // Finish loading to the nozzle with finely tuned steps. + execute_load_to_nozzle_sequence(); + //MakeSound(Confirm); + } + ScreenUpdateEnable(); + return true; + } + + bool MMU3::eject_filament(uint8_t slot, bool enableFullScreenMsg /* = true */) { + if (!waitForMMUReady()) return false; + + if (enableFullScreenMsg) fullScreenMsgEject(slot); + + // Scope for ReportingRAII + { + if (findaDetectsFilament()) + unload(); + + ReportingRAII rep(CommandInProgress::EjectFilament); + for (;;) { + Disable_E0(); + logic.EjectFilament(slot); + if (manage_response(false, true)) + break; + IncrementMMUFails(); + } + setCurrentTool(MMU2_NO_TOOL); + tool_change_extruder = MMU2_NO_TOOL; + //MakeSound(Confirm); + } + ScreenUpdateEnable(); + return true; + } + + void MMU3::button(uint8_t index) { + LogEchoEvent(F("button")); + logic.button(index); + } + + void MMU3::home(uint8_t mode) { + logic.home(mode); + } + + void MMU3::saveHotendTemp(bool turn_off_nozzle) { + if (mmu_print_saved & SavedState::Cooldown) return; + + if (turn_off_nozzle && !(mmu_print_saved & SavedState::CooldownPending)) { + Disable_E0(); + resume_hotend_temp = thermal_degTargetHotend(); + mmu_print_saved |= SavedState::CooldownPending; + LogEchoEvent(F("Heater cooldown pending")); + } + } + + void MMU3::saveAndPark(bool move_axes) { + if (mmu_print_saved == SavedState::None) { // First occurrence. Save current position, park print head, disable nozzle heater. + LogEchoEvent(F("Saving and parking")); + Disable_E0(); + planner_synchronize(); + + // In case a power panic happens while waiting for the user + // take a partial back up of print state into RAM (current position, etc.) + marlin_refresh_print_state_in_ram(); + + if (move_axes) { + mmu_print_saved |= SavedState::ParkExtruder; + resume_position = planner_current_position(); // save current pos + + // Do not lift Z, as it will double lift if there is another error + // right after the current one is solved. + + // Move XY aside + if (xy_are_trusted()) nozzle_park(); + } + } + } + + void MMU3::resumeHotendTemp() { + if ((mmu_print_saved & SavedState::CooldownPending)) { + // Clear the "pending" flag if we haven't cooled yet. + mmu_print_saved &= ~(SavedState::CooldownPending); + LogEchoEvent(F("Cooldown flag cleared")); + } + if ((mmu_print_saved & SavedState::Cooldown) && resume_hotend_temp) { + LogEchoEvent(F("Resuming Temp")); + // @@TODO MMU2_ECHO_MSGRPGM(PSTR("Restoring hotend temperature ")); + SERIAL_ECHOLN(resume_hotend_temp); + mmu_print_saved &= ~(SavedState::Cooldown); + thermal_setTargetHotend(resume_hotend_temp); + fullScreenMsgRestoringTemperature(); + // @todo better report the event and let the GUI do its work somewhere else + ReportErrorHookSensorLineRender(); + waitForHotendTargetTemp(100, [] { + marlin_manage_inactivity(true); + mmu3.mmu_loop_inner(false); + ReportErrorHookDynamicRender(); + }); + ScreenUpdateEnable(); // temporary hack to stop this locking the printer... + LogEchoEvent(F("Hotend temperature reached")); + ScreenClear(); + } + } + + void MMU3::resumeUnpark() { + if (mmu_print_saved & SavedState::ParkExtruder) { + LogEchoEvent(F("Resuming XYZ")); + + // Move XY to starting position, then Z + motion_blocking_move_xy(resume_position.x, resume_position.y, feedRate_t(NOZZLE_PARK_XY_FEEDRATE)); + + // Move Z_AXIS to saved position + motion_blocking_move_z(resume_position.z, feedRate_t(NOZZLE_PARK_Z_FEEDRATE)); + + // From this point forward, power panic should not use + // the partial backup in RAM since the extruder is no + // longer in parking position + marlin_clear_print_state_in_ram(); + + mmu_print_saved &= ~(SavedState::ParkExtruder); + } + } + + void MMU3::checkUserInput() { + auto btn = ButtonPressed(lastErrorCode); + + // Was a button pressed on the MMU itself instead of the LCD? + if (btn == Buttons::NoButton && lastButton != Buttons::NoButton) { + btn = lastButton; + lastButton = Buttons::NoButton; // Clear it. + } + + if (mmuLastErrorSource() == MMU3::ErrorSourcePrinter && btn != Buttons::NoButton) { + // When the printer has raised an error screen, and a button was selected + // the error screen should always be dismissed. + clearPrinterError(); + // A horrible hack - clear the explicit printer error allowing manage_response to recover on MMU's Finished state + // Moreover - if the MMU is currently doing something (like the LoadFilament - see comment above) + // we'll actually wait for it automagically in manage_response and after it finishes correctly, + // we'll issue another command (like toolchange) + } + + switch (btn) { + case Buttons::Left: + case Buttons::Middle: + case Buttons::Right: + SERIAL_ECHOPGM("checkUserInput-btnLMR "); + SERIAL_ECHOLN((int)buttons_to_uint8t(btn)); + resumeHotendTemp(); // Recover the hotend temp before we attempt to do anything else... + + if (mmuLastErrorSource() == MMU3::ErrorSourceMMU) + // Do not send a button to the MMU unless the MMU is in error state + button(buttons_to_uint8t(btn)); + + // A quick hack: for specific error codes move the E-motor every time. + // Not sure if we can rely on the fsensor. + // Just plan the move, let the MMU take over when it is ready + switch (lastErrorCode) { + case ErrorCode::FSENSOR_DIDNT_SWITCH_OFF: + case ErrorCode::FSENSOR_TOO_EARLY: helpUnloadToFinda(); break; + default: break; + } + break; + case Buttons::TuneMMU: + tune(); + break; + case Buttons::Load: + case Buttons::Eject: + // High level operation + setPrinterButtonOperation(btn); + break; + case Buttons::ResetMMU: + reset(ResetPin); // Cannot do power cycle on the MK3 + // ... but mmu2_power.cpp knows this and triggers a soft-reset instead. + break; + case Buttons::DisableMMU: + stop(); + //DisableMMUInSettings(); // stop() already does this... + status(); + break; + case Buttons::StopPrint: + // @@TODO Unsure if we should handle this high level operation at this spot + break; + default: break; + } + } + + /** + * Originally, this was used to wait for response and deal with timeout if necessary. + * The new protocol implementation enables much nicer and intense reporting, so this method will boil down + * just to verify the result of an issued command (which was basically the original idea) + * + * It is closely related to mmu_loop() (which corresponds to our ProtocolLogic::Step()), which does NOT perform any blocking wait for a command to finish. + * But - in case of an error, the command is not yet finished, but we must react accordingly - move the printhead elsewhere, stop heating, eat a cat or so. + * That's what's being done here... + */ + bool MMU3::manage_response(const bool move_axes, const bool turn_off_nozzle) { + mmu_print_saved = SavedState::None; + + MARLIN_KEEPALIVE_STATE_IN_PROCESS; + + Stopwatch nozzle_timer; + + for (;;) { + // in our new implementation, we know the exact state of the MMU at any moment, we do not have to wait for a timeout + // So in this case we should decide if the operation is: + // - still running -> wait normally in marlin.idle() + // - failed -> then do the safety moves on the printer like before + // - finished ok -> proceed with reading other commands + safe_delay_keep_alive(0); // calls logicStep() and remembers its return status + + if (mmu_print_saved & SavedState::CooldownPending) { + if (!nozzle_timer.isRunning()) { + nozzle_timer.start(); + LogEchoEvent(F("Cooling Timeout started")); + } + else if (nozzle_timer.duration() > (PAUSE_PARK_NOZZLE_TIMEOUT * 1000UL)) { // mins->msec. + mmu_print_saved &= ~(SavedState::CooldownPending); + mmu_print_saved |= SavedState::Cooldown; + thermal_setTargetHotend(0); + LogEchoEvent(F("Heater cooldown")); + } + } + else if (nozzle_timer.isRunning()) { + nozzle_timer.stop(); + LogEchoEvent(F("Cooling timer stopped")); + } + + switch (logicStepLastStatus) { + case Finished: + // command/operation completed, let Marlin continue its work + // the E may have some more moves to finish - wait for them + resumeHotendTemp(); + resumeUnpark(); // We can now travel back to the tower or wherever we were when we saved. + if (!TuneMenuEntered()) { + // If the error screen is sleeping (running 'Tune' menu) + // then don't reset retry attempts because we this will trigger + // an automatic retry attempt when 'Tune' button is selected. We want the + // error screen to appear once more so the user can hit 'Retry' button manually. + logic.ResetRetryAttempts(); // Reset the retry counter. + } + planner_synchronize(); + return true; + case Interrupted: + // now what :D ... big bad ... ramming, unload, retry the whole command originally issued + return false; + case VersionMismatch: // this basically means the MMU will be disabled until reconnected + checkUserInput(); + return true; + case PrinterError: + saveAndPark(move_axes); + saveHotendTemp(turn_off_nozzle); + checkUserInput(); + // if button pressed "Done", return true, otherwise stay within manage_response + // Please see checkUserInput() for details how we "leave" manage_response + break; + case CommandError: + case CommunicationTimeout: + case ProtocolError: + case ButtonPushed: + if (!logic.InAutoRetry()) { + // Don't proceed to the park/save if we are doing an autoretry. + saveAndPark(move_axes); + saveHotendTemp(turn_off_nozzle); + checkUserInput(); + } + break; + case CommunicationRecovered: // @@TODO communication recovered and maybe an error recovered as well + // Maybe the logic layer can detect the change of state a respond with one "Recovered" to be handled here + resumeHotendTemp(); + resumeUnpark(); + break; + case Processing: // Wait for the MMU to respond + default: break; + } + } + } + + StepStatus MMU3::logicStep(bool reportErrors) { + // Process any buttons before proceeding with another MMU Query + checkUserInput(); + + const StepStatus ss = logic.Step(); + switch (ss) { + + case Finished: + // At this point it is safe to trigger a runout and not interrupt the MMU protocol + checkFINDARunout(); + break; + + case Processing: + onMMUProgressMsg(logic.Progress()); + break; + + case ButtonPushed: + lastButton = logic.button(); + LogEchoEvent(F("MMU button pushed")); + checkUserInput(); // Process the button immediately + break; + + case Interrupted: + // can be silently handed over to a higher layer, no processing necessary at this spot + break; + + default: + if (reportErrors) { + switch (ss) { + + case CommandError: + reportError(logic.Error(), ErrorSourceMMU); + break; + + case CommunicationTimeout: + _state = xState::Connecting; + reportError(ErrorCode::MMU_NOT_RESPONDING, ErrorSourcePrinter); + break; + + case ProtocolError: + _state = xState::Connecting; + reportError(ErrorCode::PROTOCOL_ERROR, ErrorSourcePrinter); + break; + + case VersionMismatch: + stopKeepPowered(); + reportError(ErrorCode::VERSION_MISMATCH, ErrorSourcePrinter); + break; + + case PrinterError: + reportError(logic.PrinterError(), ErrorSourcePrinter); + break; + + default: + break; + } + } + } + + if (logic.Running()) _state = xState::Active; + + return ss; + } + + void MMU3::filament_ramming() { + execute_extruder_sequence(ramming_sequence, sizeof(ramming_sequence) / sizeof(E_Step)); + } + + void MMU3::execute_extruder_sequence(const E_Step *sequence, uint8_t steps) { + planner_synchronize(); + + const E_Step *step = sequence; + for (uint8_t i = steps; i > 0; --i) { + extruder_move(pgm_read_float(&(step->extrude)), pgm_read_float(&(step->feedRate))); + step++; + } + planner_synchronize(); // it looks like it's better to sync the moves at the end - smoother move (if the sequence is not too long). + + Disable_E0(); + } + + void MMU3::execute_load_to_nozzle_sequence() { + planner_synchronize(); + // Compensate for configurable Extra Loading Distance + planner_set_current_position_E(planner_get_current_position_E() - (logic.ExtraLoadDistance() - MMU3_FILAMENT_SENSOR_E_POSITION)); + execute_extruder_sequence(load_to_nozzle_sequence, sizeof(load_to_nozzle_sequence) / sizeof(load_to_nozzle_sequence[0])); + } + + void MMU3::reportError(ErrorCode ec, ErrorSource res) { + // Due to a potential lossy error reporting layers linked to this hook + // we'd better report everything to make sure especially the error states + // do not get lost. + // - The good news here is the fact, that the MMU reports the errors repeatedly until resolved. + // - The bad news is, that MMU not responding may repeatedly occur on printers not having the MMU at all. + // + // Not sure how to properly handle this situation, options: + // - skip reporting "MMU not responding" (at least for now) + // - report only changes of states (we can miss an error message) + // - maybe some combination of MMUAvailable + UseMMU flags and decide based on their state + // Right now the filtering of MMU_NOT_RESPONDING is done in ReportErrorHook() as it is not a problem if mmu2.cpp + + // Depending on the Progress code, we may want to do some action when an error occurs + switch (logic.Progress()) { + case ProgressCode::UnloadingToFinda: + unloadFilamentStarted = false; + planner_abort_queued_moves(); // Abort excess E-moves to be safe + break; + case ProgressCode::FeedingToFSensor: + // FSENSOR error during load. Make sure E-motor stops moving. + loadFilamentStarted = false; + planner_abort_queued_moves(); // Abort excess E-moves to be safe + break; + default: break; + } + + if (ec != lastErrorCode) { // deduplicate: only report changes in error codes into the log + lastErrorCode = ec; + lastErrorSource = res; + LogErrorEvent(PrusaErrorTitle(PrusaErrorCodeIndex(ec))); + + if (ec != ErrorCode::OK && ec != ErrorCode::FILAMENT_EJECTED && ec != ErrorCode::FILAMENT_CHANGE) { + IncrementMMUFails(); + + // Check if it is a "power" failure. TMC-related errors are considered power failures. + static constexpr uint16_t tmcMask = + ( (uint16_t)ErrorCode::TMC_IOIN_MISMATCH + | (uint16_t)ErrorCode::TMC_RESET + | (uint16_t)ErrorCode::TMC_UNDERVOLTAGE_ON_CHARGE_PUMP + | (uint16_t)ErrorCode::TMC_SHORT_TO_GROUND + | (uint16_t)ErrorCode::TMC_OVER_TEMPERATURE_WARN + | (uint16_t)ErrorCode::TMC_OVER_TEMPERATURE_ERROR + | (uint16_t)ErrorCode::MMU_SOLDERING_NEEDS_ATTENTION ) & 0x7FFFU; // skip the top bit + + static_assert(tmcMask == 0x7E00); // Just make sure we fail compilation if any of the TMC error codes change + + if ((uint16_t)ec & tmcMask) { // @@TODO can be optimized to uint8_t operation + // TMC-related errors are from 0x8200 higher + incrementTMCFailures(); + } + } + } + + if (!retryIfPossible(ec)) + // If retry attempts are all used up + // or if 'Retry' operation is not available + // raise the MMU error screen and wait for user input + ReportErrorHook((CommandInProgress)logic.CommandInProgress(), ec, uint8_t(lastErrorSource)); + } + + void MMU3::reportProgress(ProgressCode pc) { + ReportProgressHook((CommandInProgress)logic.CommandInProgress(), pc); + LogEchoEvent(ProgressCodeToText(pc)); + } + + void MMU3::onMMUProgressMsg(ProgressCode pc) { + if (pc != lastProgressCode) + onMMUProgressMsgChanged(pc); + else + onMMUProgressMsgSame(pc); + } + + void MMU3::onMMUProgressMsgChanged(ProgressCode pc) { + reportProgress(pc); + lastProgressCode = pc; + switch (pc) { + case ProgressCode::UnloadingToFinda: + if ( (CommandInProgress)logic.CommandInProgress() == CommandInProgress::UnloadFilament + || ((CommandInProgress)logic.CommandInProgress() == CommandInProgress::ToolChange) + ) { + // If MK3S sent U0 command, ramming sequence takes care of releasing the filament. + // If Toolchange is done while printing, PrusaSlicer takes care of releasing the filament + // If printing is not in progress, ToolChange will issue a U0 command. + break; + } + else { + // We're likely recovering from an MMU error + planner_synchronize(); + unloadFilamentStarted = true; + helpUnloadToFinda(); + } + break; + case ProgressCode::FeedingToFSensor: + // prepare for the movement of the E-motor + planner_synchronize(); + loadFilamentStarted = true; + break; + default: break; // do nothing yet + } + } + + void __attribute__((noinline)) MMU3::helpUnloadToFinda() { + extruder_move(-MMU3_RETRY_UNLOAD_TO_FINDA_LENGTH, MMU3_RETRY_UNLOAD_TO_FINDA_FEED_RATE); + } + + void MMU3::onMMUProgressMsgSame(ProgressCode pc) { + const uint8_t pulley_slow_feedrate = logic.PulleySlowFeedRate(); + const float extrude_distance = _MIN(_MAX(EXTRUDE_MAXLENGTH - 1, 1), pulley_slow_feedrate); + + switch (pc) { + case ProgressCode::UnloadingToFinda: + if (unloadFilamentStarted && !planner_any_moves()) { // Only plan a move if there is no move ongoing + switch (WhereIsFilament()) { + case FilamentState::AT_FSENSOR: + case FilamentState::IN_NOZZLE: + case FilamentState::UNAVAILABLE: // actually Unavailable makes sense as well to start the E-move to release the filament from the gears + helpUnloadToFinda(); + break; + default: + unloadFilamentStarted = false; + } + } + break; + + case ProgressCode::FeedingToFSensor: + if (loadFilamentStarted) { + switch (WhereIsFilament()) { + case FilamentState::AT_FSENSOR: + // fsensor triggered, finish FeedingToExtruder state + loadFilamentStarted = false; + + // Abort any excess E-move from the planner queue + planner_abort_queued_moves(); + + // After the MMU knows the FSENSOR is triggered it will: + // 1. Push the filament by additional 30mm (see fsensorToNozzle) + // 2. Disengage the idler and push another 2mm. + extruder_move(logic.ExtraLoadDistance() + 2, logic.PulleySlowFeedRate()); + break; + case FilamentState::NOT_PRESENT: + // fsensor not triggered, continue moving extruder + // + // Instead of doing a very long extrude as in PrusaFirmware, + // Marlin's own MMU2s code has a better approach to this by spinning + // the extruder indefinitely... + // + // this ensures that while the MMU is pushing the filament, + // the extruder will keep rotating, preventing the filament to hit + // the extruder gears... + while (planner.movesplanned() < 3) { + extruder_move(extrude_distance, pulley_slow_feedrate, false); + } + break; + default: break; // Abort here? + } + } + break; + + default: break; // do nothing yet + } + } + +} // MMU3 + +#endif // HAS_PRUSA_MMU3 diff --git a/Marlin/src/feature/mmu3/mmu3.h b/Marlin/src/feature/mmu3/mmu3.h new file mode 100644 index 000000000000..968f76a91273 --- /dev/null +++ b/Marlin/src/feature/mmu3/mmu3.h @@ -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 . + * + */ +#pragma once + +/** + * mmu2.h + */ + +#include "mmu3_state.h" +#include "mmu3_marlin.h" + +#include "mmu3_protocol_logic.h" + +#include "../../MarlinCore.h" + + #ifdef __AVR__ + typedef float feedRate_t; + #else + //#include + #endif + + struct E_Step { + float extrude; //!< extrude distance in mm + float feedRate; //!< feed rate in mm/s + }; + + static constexpr E_Step ramming_sequence[] PROGMEM = { MMU3_RAMMING_SEQUENCE }; + static constexpr E_Step load_to_nozzle_sequence[] PROGMEM = { MMU3_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 hexadecimal + // @return true upon success + bool readRegister(uint8_t address); + + // Write from a MMU register (See gcode M708) + // @param address Address of register in hexadecimal + // @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 truly 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; diff --git a/Marlin/src/feature/mmu3/mmu3_crc.cpp b/Marlin/src/feature/mmu3/mmu3_crc.cpp new file mode 100644 index 000000000000..2e752c4873a6 --- /dev/null +++ b/Marlin/src/feature/mmu3/mmu3_crc.cpp @@ -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 . + * + */ + +/** + * mmu2_crc.cpp + */ + +#include "../../inc/MarlinConfigPre.h" + +#if HAS_PRUSA_MMU3 + +#include "mmu3_crc.h" + +#ifdef __AVR__ + #include +#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 diff --git a/Marlin/src/feature/mmu3/mmu3_crc.h b/Marlin/src/feature/mmu3/mmu3_crc.h new file mode 100644 index 000000000000..f7221b38f5a7 --- /dev/null +++ b/Marlin/src/feature/mmu3/mmu3_crc.h @@ -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 . + * + */ +#pragma once + +/** + * mmu2_crc.h + */ + +#include + +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 diff --git a/Marlin/src/feature/mmu3/mmu3_error_converter.cpp b/Marlin/src/feature/mmu3/mmu3_error_converter.cpp new file mode 100644 index 000000000000..6452e0f295cb --- /dev/null +++ b/Marlin/src/feature/mmu3/mmu3_error_converter.cpp @@ -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 . + * + */ + +/** + * mmu2_error_converter.cpp + */ + +#include "../../inc/MarlinConfigPre.h" + +#if HAS_PRUSA_MMU3 + +#include "../../core/language.h" +#include "mmu3_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 + 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 algorithm 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 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 diff --git a/Marlin/src/feature/mmu3/mmu3_error_converter.h b/Marlin/src/feature/mmu3/mmu3_error_converter.h new file mode 100644 index 000000000000..93a4d5e455ae --- /dev/null +++ b/Marlin/src/feature/mmu3/mmu3_error_converter.h @@ -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 . + * + */ +#pragma once + +/** + * mmu2_error_converter.h + */ + +#include +#include +#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 diff --git a/Marlin/src/feature/mmu3/mmu3_fsensor.cpp b/Marlin/src/feature/mmu3/mmu3_fsensor.cpp new file mode 100644 index 000000000000..32d2fac3f60b --- /dev/null +++ b/Marlin/src/feature/mmu3/mmu3_fsensor.cpp @@ -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 . + * + */ + +/** + * mmu2_fsensor.cpp + */ + +#include "../../inc/MarlinConfigPre.h" + +#if HAS_PRUSA_MMU3 + +#include "../runout.h" +#include "mmu3_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 diff --git a/Marlin/src/feature/mmu3/mmu3_fsensor.h b/Marlin/src/feature/mmu3/mmu3_fsensor.h new file mode 100644 index 000000000000..0ef4864e9fee --- /dev/null +++ b/Marlin/src/feature/mmu3/mmu3_fsensor.h @@ -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 . + * + */ +#pragma once + +/** + * mmu2_fsensor.h + */ + +#include "../../core/macros.h" +#include + +#define FILAMENT_PRESENT() (READ(FIL_RUNOUT1_PIN) != FIL_RUNOUT1_STATE) + +namespace MMU3 { + + // Can be used to block printer's filament sensor handling - to avoid erroneous 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 diff --git a/Marlin/src/feature/mmu3/mmu3_log.cpp b/Marlin/src/feature/mmu3/mmu3_log.cpp new file mode 100644 index 000000000000..af2c92ec50c5 --- /dev/null +++ b/Marlin/src/feature/mmu3/mmu3_log.cpp @@ -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 . + * + */ + +/** + * mmu2_log.cpp + */ + +#include "../../inc/MarlinConfigPre.h" + +#if HAS_PRUSA_MMU3 + +#include "mmu3_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 diff --git a/Marlin/src/feature/mmu3/mmu3_log.h b/Marlin/src/feature/mmu3/mmu3_log.h new file mode 100644 index 000000000000..e4dbffeee2a4 --- /dev/null +++ b/Marlin/src/feature/mmu3/mmu3_log.h @@ -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 . + * + */ +#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 diff --git a/Marlin/src/feature/mmu3/mmu3_marlin.h b/Marlin/src/feature/mmu3/mmu3_marlin.h new file mode 100644 index 000000000000..499af0f285cc --- /dev/null +++ b/Marlin/src/feature/mmu3/mmu3_marlin.h @@ -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 . + * + */ +#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 distance, const float feedRate_mm_s, const bool sync=true); + void extruder_schedule_turning(const float feedRate_mm_s); + + float move_raise_z(const float 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_blocking_move_xy(float rx, float ry, float feedRate_mm_s); + void motion_blocking_move_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 diff --git a/Marlin/src/feature/mmu3/mmu3_marlin1.cpp b/Marlin/src/feature/mmu3/mmu3_marlin1.cpp new file mode 100644 index 000000000000..aeddcbef4087 --- /dev/null +++ b/Marlin/src/feature/mmu3/mmu3_marlin1.cpp @@ -0,0 +1,153 @@ +/** + * 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 . + * + */ + +/** + * 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 "../pause.h" +#include "../../libs/nozzle.h" +#include "mmu3_marlin.h" + +namespace MMU3 { + + static void planner_line_to_current_position(float feedRate_mm_s) { + motion.goto_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 delta, const float feedRate_mm_s, const bool sync/*=true*/) { + motion.position.e += delta / planner.e_factor[motion.extruder]; + planner_line_to_current_position(feedRate_mm_s); + if (sync) planner.synchronize(); + } + + float move_raise_z(const float delta) { + //return raise_z(delta); + xyze_pos_t current_position_before = motion.position; + motion.do_z_clearance_by(delta); + return (motion.position - current_position_before).z; + } + + void planner_abort_queued_moves() { + //planner_abort_hard(); + motion.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 motion.position.e; } + float stepper_get_machine_position_E_mm() { return planner.get_axis_position_mm(E_AXIS); } + float planner_get_current_position_E() { return motion.position.e; } + void planner_set_current_position_E(float e) { motion.position.e = e; } + xyz_pos_t planner_current_position() { return xyz_pos_t(motion.position); } + + void motion_blocking_move_xy(float rx, float ry, float feedRate_mm_s) { + motion.position.set(rx, ry); + planner_line_to_current_position_sync(feedRate_mm_s); + } + + void motion_blocking_move_z(float z, float feedRate_mm_s) { + motion.position.z = 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 marlin.printingIsActive(); } + + void marlin_manage_heater() { thermalManager.task(); } + + void marlin_manage_inactivity(const bool b) { marlin.idle(b); } + + void marlin_idle(const bool b) { + thermalManager.task(); + marlin.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) { + marlin.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 motion.axis_is_trusted(X_AXIS) && motion.axis_is_trusted(Y_AXIS); + } + +} // MMU3 + +#endif // HAS_PRUSA_MMU3 diff --git a/Marlin/src/feature/mmu3/mmu3_marlin_macros.h b/Marlin/src/feature/mmu3/mmu3_marlin_macros.h new file mode 100644 index 000000000000..b277ce98056e --- /dev/null +++ b/Marlin/src/feature/mmu3/mmu3_marlin_macros.h @@ -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 . + * + */ +#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 diff --git a/Marlin/src/feature/mmu3/mmu3_power.cpp b/Marlin/src/feature/mmu3/mmu3_power.cpp new file mode 100644 index 000000000000..1782cb49fc18 --- /dev/null +++ b/Marlin/src/feature/mmu3/mmu3_power.cpp @@ -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 . + * + */ + +/** + * mmu2_power.cpp + */ + +#include "../../inc/MarlinConfigPre.h" + +#if HAS_PRUSA_MMU3 + +#include "mmu3.h" +#include "mmu3_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(MMU_RST) + OUT_WRITE(MMU_RST_PIN, HIGH); + #endif + power_reset(); + } + + void power_off() {} + + void power_reset() { + #if PIN_EXISTS(MMU_RST) // HW - pulse reset pin + WRITE(MMU_RST_PIN, LOW); + safe_delay(100); + WRITE(MMU_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 diff --git a/Marlin/src/inc/Conditionals_type.h b/Marlin/src/feature/mmu3/mmu3_power.h similarity index 79% rename from Marlin/src/inc/Conditionals_type.h rename to Marlin/src/feature/mmu3/mmu3_power.h index 1f9cfcae16fb..77259073a1a5 100644 --- a/Marlin/src/inc/Conditionals_type.h +++ b/Marlin/src/feature/mmu3/mmu3_power.h @@ -22,11 +22,11 @@ #pragma once /** - * Conditionals_type.h - * Internal defines that depend on Configurations and Pins but are not user-editable. - * Define conditionals in this file if they depend on core/types.h. + * mmu2_power.h */ -#ifdef GITHUB_ACTIONS - // Extras for CI testing -#endif +namespace MMU3 { + void power_on(); + void power_off(); + void power_reset(); +} diff --git a/Marlin/src/feature/mmu3/mmu3_progress_converter.cpp b/Marlin/src/feature/mmu3/mmu3_progress_converter.cpp new file mode 100644 index 000000000000..8933f2d5fd6e --- /dev/null +++ b/Marlin/src/feature/mmu3/mmu3_progress_converter.cpp @@ -0,0 +1,79 @@ +/** + * 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 . + * + */ + +/** + * mmu2_progress_converter.cpp + */ + +#include "../../inc/MarlinConfigPre.h" + +#if HAS_PRUSA_MMU3 + +#include "../../core/language.h" +#include "mmu3_progress_converter.h" +#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), // TODO: Generic messages for Marlin + GET_TEXT_F(MSG_PROGRESS_ENGAGE_IDLER), // reused below + GET_TEXT_F(MSG_PROGRESS_DISENGAGE_IDLER), // reused below + 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), + 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), + GET_TEXT_F(MSG_LOADING_FILAMENT), + 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(pgm_read_ptr(&progressTexts[(uint16_t)pc])) + : static_cast(pgm_read_ptr(&progressTexts[0])); + } + +} // MMU3 + +#endif // HAS_PRUSA_MMU3 diff --git a/Marlin/src/feature/mmu3/mmu3_progress_converter.h b/Marlin/src/feature/mmu3/mmu3_progress_converter.h new file mode 100644 index 000000000000..e7b1d8682153 --- /dev/null +++ b/Marlin/src/feature/mmu3/mmu3_progress_converter.h @@ -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 . + * + */ +#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); + +} diff --git a/Marlin/src/feature/mmu3/mmu3_protocol.cpp b/Marlin/src/feature/mmu3/mmu3_protocol.cpp new file mode 100644 index 000000000000..739599f142b0 --- /dev/null +++ b/Marlin/src/feature/mmu3/mmu3_protocol.cpp @@ -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 . + * + */ + +/** + * mmu2_protocol.cpp + */ + +#include "../../inc/MarlinConfigPre.h" + +#if HAS_PRUSA_MMU3 + +#include "mmu3_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] +// P[0-9] : command being processed i.e. operation running, may contain a state number +// E[0-9][0-9] : error 1-9 while doing a tool change +// 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 diff --git a/Marlin/src/feature/mmu3/mmu3_protocol.h b/Marlin/src/feature/mmu3/mmu3_protocol.h new file mode 100644 index 000000000000..f9553b58c8a8 --- /dev/null +++ b/Marlin/src/feature/mmu3/mmu3_protocol.h @@ -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 . + * + */ +#pragma once + +/** + * mmu2_protocol.h + */ + +#include "../../MarlinCore.h" + +#include +#include "mmu3_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 preceded 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 stateful 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 encoding 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 encoding 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 + diff --git a/Marlin/src/feature/mmu3/mmu3_protocol_logic.cpp b/Marlin/src/feature/mmu3/mmu3_protocol_logic.cpp new file mode 100644 index 000000000000..4323925b6b18 --- /dev/null +++ b/Marlin/src/feature/mmu3/mmu3_protocol_logic.cpp @@ -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 . + * + */ + +/** + * mmu2_protocol_logic.cpp + */ + +#include "../../inc/MarlinConfigPre.h" + +#if HAS_PRUSA_MMU3 + +#include "mmu3_protocol_logic.h" +#include "mmu3_log.h" +#include "mmu3_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 + #define _millis millis + #ifdef UNITTEST + #define strncmp_P strncmp + #else + #include "../../core/serial.h" + #endif + #endif + + #include + #include "mmu3_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 = MMU_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++) { + MMU_SERIAL.write(txbuff[i]); + } + #else + MMU_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++) { + MMU_SERIAL.write(txbuff[i]); + } + #else + MMU_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 (MMU_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(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(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(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(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(rsp.paramValue); + errorCode = ErrorCode::OK; + break; + default: + progressCode = ProgressCode::ERRWaitingForUser; + errorCode = static_cast(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(MMU3_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 ELAPSED(_millis(), lastUARTActivityMs + timeout); + } + + void ProtocolLogic::RecordUARTActivity() { + lastUARTActivityMs = _millis(); + } + + void ProtocolLogic::RecordReceivedByte(uint8_t c) { + lastReceivedBytes[lrb] = c; + lrb = (lrb + 1) % COUNT(lastReceivedBytes); + } + + 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 < COUNT(lastReceivedBytes); ++i) { + uint8_t b = lastReceivedBytes[(COUNT(lastReceivedBytes) - 1 + (lrb - i)) % COUNT(lastReceivedBytes)]; + dst[i * 3] = NibbleToChar(b >> 4); + dst[i * 3 + 1] = NibbleToChar(b & 0xf); + dst[i * 3 + 2] = ' '; + } + dst[(COUNT(lastReceivedBytes) - 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[COUNT(lastReceivedBytes) * 3]; + FormatLastReceivedBytes(_lrb); + + MMU2_ERROR_MSGRPGM(reason_P); + SERIAL_ECHOPGM(", last bytes: "); + SERIAL_ECHOLN(_lrb); + } + + void ProtocolLogic::LogResponse() { + char _lrb[COUNT(lastReceivedBytes)]; + 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() { + MMU_SERIAL.flush(); // clear the output buffer + protocol.ResetResponseDecoder(); + start(); + return SuppressShortDropOuts(PSTR("Communication timeout"), CommunicationTimeout); + } + + StepStatus ProtocolLogic::HandleProtocolError() { + MMU_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 = MMU3_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 diff --git a/Marlin/src/feature/mmu3/mmu3_protocol_logic.h b/Marlin/src/feature/mmu3/mmu3_protocol_logic.h new file mode 100644 index 000000000000..30f9c5d0d524 --- /dev/null +++ b/Marlin/src/feature/mmu3/mmu3_protocol_logic.h @@ -0,0 +1,380 @@ +/** + * 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 . + * + */ +#pragma once + +/** + * mmu2_protocol_logic.h + */ + +#include "../../MarlinCore.h" + +#include + +#ifdef __AVR__ + #include + #include "mmu_hw/error_codes.h" + #include "mmu_hw/progress_codes.h" + #include "mmu_hw/buttons.h" + #include "mmu_hw/registers.h" + #include "mmu3_protocol.h" +#else // !__AVR__ + + #include "mmu_hw/error_codes.h" + #include "mmu_hw/progress_codes.h" + + // Prevent ARM HAL macros from breaking our code + #undef CRC + #include "mmu3_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 + + uint8_t lrb, lastReceivedBytes[16]; //!< keep the last few bytes of incoming communication for diagnostic purposes + + 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 diff --git a/Marlin/src/feature/mmu3/mmu3_reporting.cpp b/Marlin/src/feature/mmu3/mmu3_reporting.cpp new file mode 100644 index 000000000000..c9b9dbe554a3 --- /dev/null +++ b/Marlin/src/feature/mmu3/mmu3_reporting.cpp @@ -0,0 +1,705 @@ +/** + * 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 . + * + */ + +/** + * mmu2_reporting.cpp + */ + +#include "../../inc/MarlinConfig.h" + +#if HAS_PRUSA_MMU3 + +#include "mmu3.h" +#include "mmu3_log.h" +#include "mmu3_fsensor.h" +#include "mmu3_reporting.h" +#include "mmu3_error_converter.h" +#include "mmu3_marlin_macros.h" +#include "mmu3_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 "../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 (!marlin.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 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 equivalent 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_replace_status_char(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 diff --git a/Marlin/src/feature/mmu3/mmu3_reporting.h b/Marlin/src/feature/mmu3/mmu3_reporting.h new file mode 100644 index 000000000000..d3e8db9c5eea --- /dev/null +++ b/Marlin/src/feature/mmu3/mmu3_reporting.h @@ -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 . + * + */ +#pragma once + +/** + * mmu2_reporting.h + */ + +#include "../../MarlinCore.h" + +#include +#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 diff --git a/Marlin/src/feature/mmu3/mmu3_state.h b/Marlin/src/feature/mmu3/mmu3_state.h new file mode 100644 index 000000000000..036d3ae25500 --- /dev/null +++ b/Marlin/src/feature/mmu3/mmu3_state.h @@ -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 . + * + */ +#pragma once + +/** + * mmu2_state.h + */ + +#include + +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 diff --git a/Marlin/src/feature/mmu3/mmu3_supported_version.h b/Marlin/src/feature/mmu3/mmu3_supported_version.h new file mode 100644 index 000000000000..c03fbee2b048 --- /dev/null +++ b/Marlin/src/feature/mmu3/mmu3_supported_version.h @@ -0,0 +1,34 @@ +/** + * 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 . + * + */ +#pragma once + +/** + * mmu2_supported_version.h + */ + +#include + +#define mmuVersionMajor 3 +#define mmuVersionMinor 0 +#define mmuVersionPatch 2 + +#define MMU_VERSION STRINGIFY(mmuVersionMajor) "." STRINGIFY(mmuVersionMinor) "." STRINGIFY(mmuVersionPatch) diff --git a/Marlin/src/feature/mmu3/mmu_hw/buttons.h b/Marlin/src/feature/mmu3/mmu_hw/buttons.h new file mode 100644 index 000000000000..2b35d6339c3b --- /dev/null +++ b/Marlin/src/feature/mmu3/mmu_hw/buttons.h @@ -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 . + * + */ +#pragma once + +/** + * buttons.h + */ + +#include + +// 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(b); +} + +} // MMU3 diff --git a/Marlin/src/feature/mmu3/mmu_hw/check-pce.sh b/Marlin/src/feature/mmu3/mmu_hw/check-pce.sh new file mode 100755 index 000000000000..197bc6dcc50b --- /dev/null +++ b/Marlin/src/feature/mmu3/mmu_hw/check-pce.sh @@ -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 diff --git a/Marlin/src/feature/mmu3/mmu_hw/error_codes.h b/Marlin/src/feature/mmu3/mmu_hw/error_codes.h new file mode 100644 index 000000000000..b0bc36e2064d --- /dev/null +++ b/Marlin/src/feature/mmu3/mmu_hw/error_codes.h @@ -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 . + * + */ +#pragma once + +/** + * error_codes.h + */ + +#include + +/** + * 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), + * because 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, + +}; diff --git a/Marlin/src/feature/mmu3/mmu_hw/errors_list.h b/Marlin/src/feature/mmu3/mmu_hw/errors_list.h new file mode 100644 index 000000000000..9cfdba228335 --- /dev/null +++ b/Marlin/src/feature/mmu3/mmu_hw/errors_list.h @@ -0,0 +1,351 @@ +/** + * 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 . + * + */ +#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 +#endif +#include "buttons.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 inadvertently 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 diff --git a/Marlin/src/feature/mmu3/mmu_hw/progress_codes.h b/Marlin/src/feature/mmu3/mmu_hw/progress_codes.h new file mode 100644 index 000000000000..16e63a856432 --- /dev/null +++ b/Marlin/src/feature/mmu3/mmu_hw/progress_codes.h @@ -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 . + * + */ +#pragma once + +/** + * progress_codes.h + */ + +#include + +/** + * 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 +}; diff --git a/Marlin/src/feature/mmu3/mmu_hw/registers.h b/Marlin/src/feature/mmu3/mmu_hw/registers.h new file mode 100644 index 000000000000..3e423c479dc7 --- /dev/null +++ b/Marlin/src/feature/mmu3/mmu_hw/registers.h @@ -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 . + * + */ +#pragma once + +/** + * registers.h + */ + +#include + +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 diff --git a/Marlin/src/feature/mmu3/registers.h b/Marlin/src/feature/mmu3/registers.h new file mode 100644 index 000000000000..3e423c479dc7 --- /dev/null +++ b/Marlin/src/feature/mmu3/registers.h @@ -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 . + * + */ +#pragma once + +/** + * registers.h + */ + +#include + +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 diff --git a/Marlin/src/feature/mmu3/sound.cpp b/Marlin/src/feature/mmu3/sound.cpp new file mode 100644 index 000000000000..63edd2dd5e27 --- /dev/null +++ b/Marlin/src/feature/mmu3/sound.cpp @@ -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 . + * + */ + +/** + * 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(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 diff --git a/Marlin/src/feature/mmu3/sound.h b/Marlin/src/feature/mmu3/sound.h new file mode 100644 index 000000000000..f72e6ecafe53 --- /dev/null +++ b/Marlin/src/feature/mmu3/sound.h @@ -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 . + * + */ +#pragma once + +/** + * sound.h + */ + +#include + +#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); diff --git a/Marlin/src/feature/mmu3/ultralcd.cpp b/Marlin/src/feature/mmu3/ultralcd.cpp new file mode 100644 index 000000000000..d3cd3d371e26 --- /dev/null +++ b/Marlin/src/feature/mmu3/ultralcd.cpp @@ -0,0 +1,211 @@ +/** + * 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 . + * + */ + +/** + * ultralcd.cpp + */ + +#include "../../inc/MarlinConfigPre.h" + +#if HAS_PRUSA_MMU3 + +#include "mmu3.h" +#include "mmu3_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 is_whitespace_P(const char *c_addr) { + const char c = pgm_read_byte(c_addr); + return c == ' ' || c == '\t' || c == '\r' || c == '\n'; +} + +bool is_punctuation_P(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_async(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 (is_whitespace_P(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 && !is_whitespace_P(msgend) && !is_punctuation_P(msgend)) { + // Splitting a word. Find the start of the current word. + while (msgend > msg && !is_whitespace_P(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); + } + } + 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_async(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) { + marlin.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_replace_status_char(const uint8_t position, const char message) { + if (position >= MAX_MESSAGE_SIZE) 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 diff --git a/Marlin/src/feature/mmu3/ultralcd.h b/Marlin/src/feature/mmu3/ultralcd.h new file mode 100644 index 000000000000..e7f06dfb1fc0 --- /dev/null +++ b/Marlin/src/feature/mmu3/ultralcd.h @@ -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 . + * + */ +#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.refresh(LCDViewAction::LCDVIEW_NONE)); + } + ~LcdUpdateDisabler() { + #if HAS_WIRED_LCD + ui.refresh(m_updateEnabled); + ui.clear_lcd(); + ui.update(); + #endif + } + + private: + LCDViewAction m_updateEnabled; +}; + +bool is_whitespace_P(const char *c_addr); +bool is_punctuation_P(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_replace_status_char(uint8_t position, const char message); diff --git a/Marlin/src/feature/password/password.cpp b/Marlin/src/feature/password/password.cpp index 1d376cc586cd..d36b3edd098e 100644 --- a/Marlin/src/feature/password/password.cpp +++ b/Marlin/src/feature/password/password.cpp @@ -36,7 +36,7 @@ uint32_t Password::value, Password::value_entry; // // Authenticate user with password. -// Called from Setup, after SD Prinitng Stops/Aborts, and M510 +// Called from Setup, after SD Printing Stops/Aborts, and M510 // void Password::lock_machine() { is_locked = true; diff --git a/Marlin/src/feature/password/password.h b/Marlin/src/feature/password/password.h index 208765b21225..0f8bc28bd859 100644 --- a/Marlin/src/feature/password/password.h +++ b/Marlin/src/feature/password/password.h @@ -37,6 +37,8 @@ class Password { static void access_menu_password(); static void authentication_done(); static void media_gatekeeper(); + static void media_gatekeeper_sd(); + static void media_gatekeeper_usb(); private: static void authenticate_user(const screenFunc_t, const screenFunc_t); diff --git a/Marlin/src/feature/pause.cpp b/Marlin/src/feature/pause.cpp index 8756c339e3c1..cb4ea24a6b6b 100644 --- a/Marlin/src/feature/pause.cpp +++ b/Marlin/src/feature/pause.cpp @@ -23,6 +23,8 @@ /** * feature/pause.cpp - Pause feature support functions * This may be combined with related G-codes if features are consolidated. + * + * Note: Calls to ui.pause_show_message are passed to either ExtUI or MarlinUI. */ #include "../inc/MarlinConfigPre.h" @@ -60,8 +62,8 @@ #if ENABLED(EXTENSIBLE_UI) #include "../lcd/extui/ui_api.h" -#elif ENABLED(DWIN_LCD_PROUI) - #include "../lcd/e3v2/proui/dwin.h" +#elif ENABLED(SOVOL_SV06_RTS) + #include "../lcd/sovol_rts/sovol_rts.h" #endif #include "../lcd/marlinui.h" @@ -144,23 +146,28 @@ static bool ensure_safe_temperature(const bool wait=true, const PauseMode mode=P DEBUG_ECHOLNPGM("... wait:", wait, " mode:", mode); #if ENABLED(PREVENT_COLD_EXTRUSION) - if (!DEBUGGING(DRYRUN) && thermalManager.targetTooColdToExtrude(active_extruder)) - thermalManager.setTargetHotend(thermalManager.extrude_min_temp, active_extruder); + if (!DEBUGGING(DRYRUN) && thermalManager.targetTooColdToExtrude(motion.extruder)) + thermalManager.setTargetHotend(thermalManager.extrude_min_temp, motion.extruder); #endif - ui.pause_show_message(PAUSE_MESSAGE_HEATING, mode); UNUSED(mode); + ui.pause_show_message(PAUSE_MESSAGE_HEATING, mode); + + #if ENABLED(SOVOL_SV06_RTS) + rts.gotoPage(ID_Cold_L, ID_Cold_D); + rts.updateTempE0(); + #endif - if (wait) return thermalManager.wait_for_hotend(active_extruder); + if (wait) return thermalManager.wait_for_hotend(motion.extruder); // Allow interruption by Emergency Parser M108 - wait_for_heatup = TERN1(PREVENT_COLD_EXTRUSION, !thermalManager.allow_cold_extrude); - while (wait_for_heatup && ABS(thermalManager.wholeDegHotend(active_extruder) - thermalManager.degTargetHotend(active_extruder)) > (TEMP_WINDOW)) - idle(); - wait_for_heatup = false; + marlin.wait_for_heatup = TERN1(PREVENT_COLD_EXTRUSION, !thermalManager.allow_cold_extrude); + while (marlin.is_heating() && ABS(thermalManager.wholeDegHotend(motion.extruder) - thermalManager.degTargetHotend(motion.extruder)) > (TEMP_WINDOW)) + marlin.idle(); + marlin.heatup_done(); #if ENABLED(PREVENT_COLD_EXTRUSION) // A user can cancel wait-for-heating with M108 - if (!DEBUGGING(DRYRUN) && thermalManager.targetTooColdToExtrude(active_extruder)) { + if (!DEBUGGING(DRYRUN) && thermalManager.targetTooColdToExtrude(motion.extruder)) { SERIAL_ECHO_MSG(STR_ERR_HOTEND_TOO_COLD); return false; } @@ -181,10 +188,8 @@ static bool ensure_safe_temperature(const bool wait=true, const PauseMode mode=P * * Returns 'true' if load was completed, 'false' for abort */ -bool load_filament(const_float_t slow_load_length/*=0*/, const_float_t fast_load_length/*=0*/, const_float_t purge_length/*=0*/, const int8_t max_beep_count/*=0*/, - const bool show_lcd/*=false*/, const bool pause_for_user/*=false*/, - const PauseMode mode/*=PAUSE_MODE_PAUSE_PRINT*/ - DXC_ARGS +bool load_filament(const float slow_load_length/*=0*/, const float fast_load_length/*=0*/, const float purge_length/*=0*/, const int8_t max_beep_count/*=0*/, + const bool show_lcd/*=false*/, const bool pause_for_user/*=false*/, const PauseMode mode/*=PAUSE_MODE_PAUSE_PRINT*/ DXC_ARGS ) { DEBUG_SECTION(lf, "load_filament", true); DEBUG_ECHOLNPGM("... slowlen:", slow_load_length, " fastlen:", fast_load_length, " purgelen:", purge_length, " maxbeep:", max_beep_count, " showlcd:", show_lcd, " pauseforuser:", pause_for_user, " pausemode:", mode DXC_SAY); @@ -201,45 +206,45 @@ bool load_filament(const_float_t slow_load_length/*=0*/, const_float_t fast_load first_impatient_beep(max_beep_count); KEEPALIVE_STATE(PAUSED_FOR_USER); - wait_for_user = true; // LCD click or M108 will clear this + marlin.wait_start(); // LCD click or M108 will clear this TERN_(EXTENSIBLE_UI, ExtUI::onUserConfirmRequired(GET_TEXT_F(MSG_FILAMENTLOAD))); #if ENABLED(HOST_PROMPT_SUPPORT) - const char tool = '0' + TERN0(MULTI_FILAMENT_SENSOR, active_extruder); + const char tool = '0' + TERN0(MULTI_FILAMENT_SENSOR, motion.extruder); hostui.prompt_do(PROMPT_USER_CONTINUE, F("Load Filament T"), tool, FPSTR(CONTINUE_STR)); #endif - while (wait_for_user) { + while (marlin.wait_for_user) { impatient_beep(max_beep_count); - #if ALL(FILAMENT_CHANGE_RESUME_ON_INSERT, FILAMENT_RUNOUT_SENSOR) + #if ALL(HAS_FILAMENT_SENSOR, FILAMENT_CHANGE_RESUME_ON_INSERT) #if MULTI_FILAMENT_SENSOR - #define _CASE_INSERTED(N) case N-1: if (READ(FIL_RUNOUT##N##_PIN) != FIL_RUNOUT##N##_STATE) wait_for_user = false; break; - switch (active_extruder) { + #define _CASE_INSERTED(N) case N-1: if (!FILAMENT_IS_OUT(N)) marlin.user_resume(); break; + switch (motion.extruder) { REPEAT_1(NUM_RUNOUT_SENSORS, _CASE_INSERTED) } #else - if (READ(FIL_RUNOUT_PIN) != FIL_RUNOUT_STATE) wait_for_user = false; + if (!FILAMENT_IS_OUT()) marlin.user_resume(); #endif #endif - idle_no_sleep(); + marlin.idle_no_sleep(); } } if (show_lcd) ui.pause_show_message(PAUSE_MESSAGE_LOAD, mode); #if ENABLED(DUAL_X_CARRIAGE) - const int8_t saved_ext = active_extruder; - const bool saved_ext_dup_mode = extruder_duplication_enabled; - set_duplication_enabled(false, DXC_ext); + const int8_t saved_ext = motion.extruder; + const bool saved_ext_dup_mode = motion.extruder_duplication; + motion.set_extruder_duplication(false, DXC_ext); #endif - TERN_(BELTPRINTER, do_blocking_move_to_xy(0.00, 50.00)); + TERN_(BELTPRINTER, motion.blocking_move_xy(0.00, 50.00)); TERN_(MPCTEMP, MPC::e_paused = true); // Slow Load filament - if (slow_load_length) unscaled_e_move(slow_load_length, FILAMENT_CHANGE_SLOW_LOAD_FEEDRATE); + if (slow_load_length) motion.unscaled_e_move(slow_load_length, FILAMENT_CHANGE_SLOW_LOAD_FEEDRATE); // Fast Load Filament if (fast_load_length) { @@ -248,7 +253,7 @@ bool load_filament(const_float_t slow_load_length/*=0*/, const_float_t fast_load planner.settings.retract_acceleration = FILAMENT_CHANGE_FAST_LOAD_ACCEL; #endif - unscaled_e_move(fast_load_length, FILAMENT_CHANGE_FAST_LOAD_FEEDRATE); + motion.unscaled_e_move(fast_load_length, FILAMENT_CHANGE_FAST_LOAD_FEEDRATE); #if FILAMENT_CHANGE_FAST_LOAD_ACCEL > 0 planner.settings.retract_acceleration = saved_acceleration; @@ -256,7 +261,7 @@ bool load_filament(const_float_t slow_load_length/*=0*/, const_float_t fast_load } #if ENABLED(DUAL_X_CARRIAGE) // Tie the two extruders movement back together. - set_duplication_enabled(saved_ext_dup_mode, saved_ext); + motion.set_extruder_duplication(saved_ext_dup_mode, saved_ext); #endif #if ENABLED(ADVANCED_PAUSE_CONTINUOUS_PURGE) @@ -265,10 +270,10 @@ bool load_filament(const_float_t slow_load_length/*=0*/, const_float_t fast_load TERN_(EXTENSIBLE_UI, ExtUI::onUserConfirmRequired(GET_TEXT_F(MSG_FILAMENT_CHANGE_PURGE))); TERN_(HOST_PROMPT_SUPPORT, hostui.continue_prompt(GET_TEXT_F(MSG_FILAMENT_CHANGE_PURGE))); - wait_for_user = true; // A click or M108 breaks the purge_length loop - for (float purge_count = purge_length; purge_count > 0 && wait_for_user; --purge_count) - unscaled_e_move(1, ADVANCED_PAUSE_PURGE_FEEDRATE); - wait_for_user = false; + marlin.wait_start(); // A click or M108 breaks the purge_length loop + for (float purge_count = purge_length; purge_count > 0 && marlin.wait_for_user; --purge_count) + motion.unscaled_e_move(1, ADVANCED_PAUSE_PURGE_FEEDRATE); + marlin.user_resume(); #else @@ -277,8 +282,13 @@ bool load_filament(const_float_t slow_load_length/*=0*/, const_float_t fast_load // "Wait for filament purge" if (show_lcd) ui.pause_show_message(PAUSE_MESSAGE_PURGE); + #if ENABLED(SOVOL_SV06_RTS) + rts.updateTempE0(); + rts.gotoPage(ID_Purge_L, ID_Purge_D); + #endif + // Extrude filament to get into hotend - unscaled_e_move(purge_length, ADVANCED_PAUSE_PURGE_FEEDRATE); + motion.unscaled_e_move(purge_length, ADVANCED_PAUSE_PURGE_FEEDRATE); } TERN_(HOST_PROMPT_SUPPORT, hostui.filament_load_prompt()); // Initiate another host prompt. @@ -287,13 +297,14 @@ bool load_filament(const_float_t slow_load_length/*=0*/, const_float_t fast_load if (show_lcd) { // Show "Purge More" / "Resume" menu and wait for reply KEEPALIVE_STATE(PAUSED_FOR_USER); - wait_for_user = false; - #if ANY(HAS_MARLINUI_MENU, DWIN_LCD_PROUI) - ui.pause_show_message(PAUSE_MESSAGE_OPTION); // Also sets PAUSE_RESPONSE_WAIT_FOR + marlin.user_resume(); + pause_menu_response = PAUSE_RESPONSE_WAIT_FOR; + #if ANY(HAS_MARLINUI_MENU, EXTENSIBLE_UI) + ui.pause_show_message(PAUSE_MESSAGE_OPTION); // MarlinUI and MKS UI also set PAUSE_RESPONSE_WAIT_FOR #else - pause_menu_response = PAUSE_RESPONSE_WAIT_FOR; + TERN_(SOVOL_SV06_RTS, rts.gotoPage(ID_PurgeMore_L, ID_PurgeMore_D)); #endif - while (pause_menu_response == PAUSE_RESPONSE_WAIT_FOR) idle_no_sleep(); + while (pause_menu_response == PAUSE_RESPONSE_WAIT_FOR) marlin.idle_no_sleep(); } #endif @@ -316,7 +327,7 @@ bool load_filament(const_float_t slow_load_length/*=0*/, const_float_t fast_load */ inline void disable_active_extruder() { #if HAS_EXTRUDERS - stepper.DISABLE_EXTRUDER(active_extruder); + stepper.DISABLE_EXTRUDER(motion.extruder); safe_delay(100); #endif } @@ -331,10 +342,10 @@ inline void disable_active_extruder() { * * Returns 'true' if unload was completed, 'false' for abort */ -bool unload_filament(const_float_t unload_length, const bool show_lcd/*=false*/, +bool unload_filament(const float unload_length, const bool show_lcd/*=false*/, const PauseMode mode/*=PAUSE_MODE_PAUSE_PRINT*/ #if ALL(FILAMENT_UNLOAD_ALL_EXTRUDERS, MIXING_EXTRUDER) - , const_float_t mix_multiplier/*=1.0*/ + , const float mix_multiplier/*=1.0*/ #endif ) { DEBUG_SECTION(uf, "unload_filament", true); @@ -355,14 +366,19 @@ bool unload_filament(const_float_t unload_length, const bool show_lcd/*=false*/, if (show_lcd) ui.pause_show_message(PAUSE_MESSAGE_UNLOAD, mode); + #if ENABLED(SOVOL_SV06_RTS) + rts.updateTempE0(); + rts.gotoPage(ID_Unload_L, ID_Unload_D); + #endif + // Retract filament - unscaled_e_move(-(FILAMENT_UNLOAD_PURGE_RETRACT) * mix_multiplier, (PAUSE_PARK_RETRACT_FEEDRATE) * mix_multiplier); + motion.unscaled_e_move(-(FILAMENT_UNLOAD_PURGE_RETRACT) * mix_multiplier, (PAUSE_PARK_RETRACT_FEEDRATE) * mix_multiplier); // Wait for filament to cool safe_delay(FILAMENT_UNLOAD_PURGE_DELAY); // Quickly purge - unscaled_e_move((FILAMENT_UNLOAD_PURGE_RETRACT + FILAMENT_UNLOAD_PURGE_LENGTH) * mix_multiplier, + motion.unscaled_e_move((FILAMENT_UNLOAD_PURGE_RETRACT + FILAMENT_UNLOAD_PURGE_LENGTH) * mix_multiplier, (FILAMENT_UNLOAD_PURGE_FEEDRATE) * mix_multiplier); // Unload filament @@ -371,7 +387,7 @@ bool unload_filament(const_float_t unload_length, const bool show_lcd/*=false*/, planner.settings.retract_acceleration = FILAMENT_CHANGE_UNLOAD_ACCEL; #endif - unscaled_e_move(unload_length * mix_multiplier, (FILAMENT_CHANGE_UNLOAD_FEEDRATE) * mix_multiplier); + motion.unscaled_e_move(unload_length * mix_multiplier, (FILAMENT_CHANGE_UNLOAD_FEEDRATE) * mix_multiplier); #if FILAMENT_CHANGE_FAST_LOAD_ACCEL > 0 planner.settings.retract_acceleration = saved_acceleration; @@ -400,7 +416,7 @@ bool unload_filament(const_float_t unload_length, const bool show_lcd/*=false*/, */ uint8_t did_pause_print = 0; -bool pause_print(const_float_t retract, const xyz_pos_t &park_point, const bool show_lcd/*=false*/, const_float_t unload_length/*=0*/ DXC_ARGS) { +bool pause_print(const float retract, const xyz_pos_t &park_point, const bool show_lcd/*=false*/, const float unload_length/*=0*/ DXC_ARGS) { DEBUG_SECTION(pp, "pause_print", true); DEBUG_ECHOLNPGM("... park.x:", park_point.x, " y:", park_point.y, " z:", park_point.z, " unloadlen:", unload_length, " showlcd:", show_lcd DXC_SAY); @@ -421,7 +437,7 @@ bool pause_print(const_float_t retract, const xyz_pos_t &park_point, const bool // Pause the print job and timer #if HAS_MEDIA - const bool was_sd_printing = IS_SD_PRINTING(); + const bool was_sd_printing = card.isStillPrinting(); if (was_sd_printing) { card.pauseSDPrint(); ++did_pause_print; // Indicate SD pause also @@ -431,14 +447,14 @@ bool pause_print(const_float_t retract, const xyz_pos_t &park_point, const bool print_job_timer.pause(); // Save current position - resume_position = current_position; + resume_position = motion.position; // Will the nozzle be parking? - const bool do_park = !axes_should_home(); + const bool do_park = !motion.axes_should_home(); #if ENABLED(POWER_LOSS_RECOVERY) // Save PLR info in case the power goes out while parked - const float park_raise = do_park ? nozzle.park_mode_0_height(park_point.z) - current_position.z : POWER_LOSS_ZRAISE; + const float park_raise = do_park ? nozzle.park_mode_0_height(park_point.z) - motion.position.z : POWER_LOSS_ZRAISE; if (was_sd_printing && recovery.enabled) recovery.save(true, park_raise, do_park); #endif @@ -450,7 +466,7 @@ bool pause_print(const_float_t retract, const xyz_pos_t &park_point, const bool #endif // Initial retract before move to filament change position - if (retract && thermalManager.hotEnoughToExtrude(active_extruder)) { + if (retract && thermalManager.hotEnoughToExtrude(motion.extruder)) { DEBUG_ECHOLNPGM("... retract:", retract); #if ENABLED(AUTO_BED_LEVELING_UBL) @@ -458,7 +474,7 @@ bool pause_print(const_float_t retract, const xyz_pos_t &park_point, const bool set_bed_leveling_enabled(false); // turn off leveling #endif - unscaled_e_move(retract, PAUSE_PARK_RETRACT_FEEDRATE); + motion.unscaled_e_move(retract, PAUSE_PARK_RETRACT_FEEDRATE); TERN_(AUTO_BED_LEVELING_UBL, set_bed_leveling_enabled(leveling_was_enabled)); // restore leveling } @@ -468,16 +484,16 @@ bool pause_print(const_float_t retract, const xyz_pos_t &park_point, const bool if (!do_park) LCD_MESSAGE(MSG_PARK_FAILED); #if ENABLED(DUAL_X_CARRIAGE) - const int8_t saved_ext = active_extruder; - const bool saved_ext_dup_mode = extruder_duplication_enabled; - set_duplication_enabled(false, DXC_ext); + const int8_t saved_ext = motion.extruder; + const bool saved_ext_dup_mode = motion.extruder_duplication; + motion.set_extruder_duplication(false, DXC_ext); #endif // Unload the filament, if specified if (unload_length) unload_filament(unload_length, show_lcd, PAUSE_MODE_CHANGE_FILAMENT); - TERN_(DUAL_X_CARRIAGE, set_duplication_enabled(saved_ext_dup_mode, saved_ext)); + TERN_(DUAL_X_CARRIAGE, motion.set_extruder_duplication(saved_ext_dup_mode, saved_ext)); // Disable the Extruder for manual change disable_active_extruder(); @@ -503,6 +519,11 @@ void show_continue_prompt(const bool is_reload) { DEBUG_ECHOLNPGM("... is_reload:", is_reload); ui.pause_show_message(is_reload ? PAUSE_MESSAGE_INSERT : PAUSE_MESSAGE_WAITING); + #if ENABLED(SOVOL_SV06_RTS) + rts.updateTempE0(); + rts.gotoPage(ID_Insert_L, ID_Insert_D); + rts.sendData(Beep, SoundAddr); + #endif SERIAL_ECHO_START(); SERIAL_ECHO(is_reload ? F(_PMSG(STR_FILAMENT_CHANGE_INSERT) "\n") : F(_PMSG(STR_FILAMENT_CHANGE_WAIT) "\n")); } @@ -518,22 +539,21 @@ void wait_for_confirmation(const bool is_reload/*=false*/, const int8_t max_beep first_impatient_beep(max_beep_count); // Start the heater idle timers - const millis_t nozzle_timeout = SEC_TO_MS(PAUSE_PARK_NOZZLE_TIMEOUT); - - HOTEND_LOOP() thermalManager.heater_idle[e].start(nozzle_timeout); + constexpr millis_t nozzle_timeout_ms = SEC_TO_MS(PAUSE_PARK_NOZZLE_TIMEOUT); + HOTEND_LOOP() thermalManager.heater_idle[e].start(nozzle_timeout_ms); #if ENABLED(DUAL_X_CARRIAGE) - const int8_t saved_ext = active_extruder; - const bool saved_ext_dup_mode = extruder_duplication_enabled; - set_duplication_enabled(false, DXC_ext); + const int8_t saved_ext = motion.extruder; + const bool saved_ext_dup_mode = motion.extruder_duplication; + motion.set_extruder_duplication(false, DXC_ext); #endif // Wait for filament insert by user and press button KEEPALIVE_STATE(PAUSED_FOR_USER); TERN_(HOST_PROMPT_SUPPORT, hostui.continue_prompt(GET_TEXT_F(MSG_NOZZLE_PARKED))); TERN_(EXTENSIBLE_UI, ExtUI::onUserConfirmRequired(GET_TEXT_F(MSG_NOZZLE_PARKED))); - wait_for_user = true; // LCD click or M108 will clear this - while (wait_for_user) { + marlin.wait_start(); // LCD click or M108 will clear this + while (marlin.wait_for_user) { impatient_beep(max_beep_count); // If the nozzle has timed out... @@ -544,6 +564,10 @@ void wait_for_confirmation(const bool is_reload/*=false*/, const int8_t max_beep // re-heat the nozzle, re-show the continue prompt, restart idle timers, start over if (nozzle_timed_out) { ui.pause_show_message(PAUSE_MESSAGE_HEAT); + #if ENABLED(SOVOL_SV06_RTS) + rts.updateTempE0(); + rts.gotoPage(ID_HeatNozzle_L, ID_HeatNozzle_D); + #endif SERIAL_ECHO_MSG(_PMSG(STR_FILAMENT_CHANGE_HEAT)); TERN_(HOST_PROMPT_SUPPORT, hostui.prompt_do(PROMPT_USER_CONTINUE, GET_TEXT_F(MSG_HEATER_TIMEOUT), GET_TEXT_F(MSG_REHEAT))); @@ -554,7 +578,7 @@ void wait_for_confirmation(const bool is_reload/*=false*/, const int8_t max_beep ExtUI::onUserConfirmRequired(GET_TEXT_F(MSG_HEATER_TIMEOUT)); #endif - TERN_(HAS_RESUME_CONTINUE, wait_for_user_response(0, true)); // Wait for LCD click or M108 + TERN_(HAS_RESUME_CONTINUE, marlin.wait_for_user_response(0, true)); // Wait for LCD click or M108 TERN_(HOST_PROMPT_SUPPORT, hostui.prompt_do(PROMPT_INFO, GET_TEXT_F(MSG_REHEATING))); @@ -570,9 +594,8 @@ void wait_for_confirmation(const bool is_reload/*=false*/, const int8_t max_beep show_continue_prompt(is_reload); // Start the heater idle timers - const millis_t nozzle_timeout = SEC_TO_MS(PAUSE_PARK_NOZZLE_TIMEOUT); - - HOTEND_LOOP() thermalManager.heater_idle[e].start(nozzle_timeout); + constexpr millis_t nozzle_timeout_ms = SEC_TO_MS(PAUSE_PARK_NOZZLE_TIMEOUT); + HOTEND_LOOP() thermalManager.heater_idle[e].start(nozzle_timeout_ms); TERN_(HOST_PROMPT_SUPPORT, hostui.continue_prompt(GET_TEXT_F(MSG_REHEATDONE))); #if ENABLED(EXTENSIBLE_UI) @@ -581,14 +604,14 @@ void wait_for_confirmation(const bool is_reload/*=false*/, const int8_t max_beep LCD_MESSAGE(MSG_REHEATDONE); #endif - IF_DISABLED(PAUSE_REHEAT_FAST_RESUME, wait_for_user = true); + IF_DISABLED(PAUSE_REHEAT_FAST_RESUME, marlin.wait_start()); nozzle_timed_out = false; first_impatient_beep(max_beep_count); } - idle_no_sleep(); + marlin.idle_no_sleep(); } - TERN_(DUAL_X_CARRIAGE, set_duplication_enabled(saved_ext_dup_mode, saved_ext)); + TERN_(DUAL_X_CARRIAGE, motion.set_extruder_duplication(saved_ext_dup_mode, saved_ext)); } /** @@ -611,15 +634,33 @@ 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 slow_load_length/*=0*/, + const float fast_load_length/*=0*/, + const float 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( - "start of resume_print()\ndual_x_carriage_mode:", dual_x_carriage_mode, - "\nextruder_duplication_enabled:", extruder_duplication_enabled, - "\nactive_extruder:", active_extruder, + "start of resume_print()\ndual_x_carriage_mode:", motion.idex_mode, + "\nmotion.extruder_duplication:", motion.extruder_duplication, + "\nmotion.extruder:", motion.extruder, "\n" ); //*/ @@ -627,21 +668,21 @@ 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); } - if (targetTemp > thermalManager.degTargetHotend(active_extruder)) - thermalManager.setTargetHotend(targetTemp, active_extruder); + if (targetTemp > thermalManager.degTargetHotend(motion.extruder)) + thermalManager.setTargetHotend(targetTemp, motion.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); - thermalManager.wait_for_hotend(active_extruder, false); + thermalManager.setTargetHotend(targetTemp, motion.extruder); + thermalManager.wait_for_hotend(motion.extruder, false); } ui.pause_show_message(PAUSE_MESSAGE_RESUME); @@ -650,16 +691,16 @@ void resume_print(const_float_t slow_load_length/*=0*/, const_float_t fast_load_ ensure_safe_temperature(DISABLED(BELTPRINTER)); // Retract to prevent oozing - unscaled_e_move(-(PAUSE_PARK_RETRACT_LENGTH), feedRate_t(PAUSE_PARK_RETRACT_FEEDRATE)); + motion.unscaled_e_move(-(PAUSE_PARK_RETRACT_LENGTH), feedRate_t(PAUSE_PARK_RETRACT_FEEDRATE)); - if (!axes_should_home()) { + if (!motion.axes_should_home()) { // Move XY back to saved position - destination.set(resume_position.x, resume_position.y, current_position.z, current_position.e); - prepare_internal_move_to_destination(NOZZLE_PARK_XY_FEEDRATE); + motion.destination.set(resume_position.x, resume_position.y, motion.position.z, motion.position.e); + motion.prepare_internal_move_to_destination(NOZZLE_PARK_XY_FEEDRATE); // Move Z back to saved position - destination.z = resume_position.z; - prepare_internal_move_to_destination(NOZZLE_PARK_Z_FEEDRATE); + motion.destination.z = resume_position.z; + motion.prepare_internal_move_to_destination(NOZZLE_PARK_Z_FEEDRATE); } #if ENABLED(AUTO_BED_LEVELING_UBL) @@ -668,29 +709,36 @@ void resume_print(const_float_t slow_load_length/*=0*/, const_float_t fast_load_ #endif // Unretract - unscaled_e_move(PAUSE_PARK_RETRACT_LENGTH, feedRate_t(PAUSE_PARK_RETRACT_FEEDRATE)); + motion.unscaled_e_move(PAUSE_PARK_RETRACT_LENGTH, feedRate_t(PAUSE_PARK_RETRACT_FEEDRATE)); TERN_(AUTO_BED_LEVELING_UBL, set_bed_leveling_enabled(leveling_was_enabled)); // restore leveling // Intelligent resuming #if ENABLED(FWRETRACT) // If retracted before goto pause - if (fwretract.retracted[active_extruder]) - unscaled_e_move(-fwretract.settings.retract_length, fwretract.settings.retract_feedrate_mm_s); + if (fwretract.retracted[motion.extruder]) + motion.unscaled_e_move(-fwretract.settings.retract_length, fwretract.settings.retract_feedrate_mm_s); #endif // If resume_position is negative - if (resume_position.e < 0) unscaled_e_move(resume_position.e, feedRate_t(PAUSE_PARK_RETRACT_FEEDRATE)); + if (resume_position.e < 0) motion.unscaled_e_move(resume_position.e, feedRate_t(PAUSE_PARK_RETRACT_FEEDRATE)); #ifdef ADVANCED_PAUSE_RESUME_PRIME if (ADVANCED_PAUSE_RESUME_PRIME != 0) - unscaled_e_move(ADVANCED_PAUSE_RESUME_PRIME, feedRate_t(ADVANCED_PAUSE_PURGE_FEEDRATE)); + motion.unscaled_e_move(ADVANCED_PAUSE_RESUME_PRIME, feedRate_t(ADVANCED_PAUSE_PURGE_FEEDRATE)); #endif // Now all extrusion positions are resumed and ready to be confirmed // Set extruder to saved position - planner.set_e_position_mm((destination.e = current_position.e = resume_position.e)); + motion.destination.e = motion.position.e = resume_position.e; + motion.sync_plan_position_e(); ui.pause_show_message(PAUSE_MESSAGE_STATUS); + #if ENABLED(SOVOL_SV06_RTS) + if (pause_flag) + rts.gotoPage(ID_PrintResume_L, ID_PrintResume_D); + else + rts.refreshTime(); + #endif #ifdef ACTION_ON_RESUMED hostui.resumed(); @@ -714,7 +762,7 @@ void resume_print(const_float_t slow_load_length/*=0*/, const_float_t fast_load_ } #endif - #if ENABLED(ADVANCED_PAUSE_FANS_PAUSE) && HAS_FAN + #if ALL(ADVANCED_PAUSE_FANS_PAUSE, HAS_FAN) thermalManager.set_fans_paused(false); #endif diff --git a/Marlin/src/feature/pause.h b/Marlin/src/feature/pause.h index 304c8a611d13..4fbb108180a3 100644 --- a/Marlin/src/feature/pause.h +++ b/Marlin/src/feature/pause.h @@ -52,10 +52,12 @@ enum PauseMessage : char { PAUSE_MESSAGE_RESUME, PAUSE_MESSAGE_HEAT, PAUSE_MESSAGE_HEATING, - PAUSE_MESSAGE_STATUS + PAUSE_MESSAGE_STATUS, + PAUSE_MESSAGE_COUNT }; #if M600_PURGE_MORE_RESUMABLE + // Input methods can Purge More, Resume, or request input enum PauseMenuResponse : char { PAUSE_RESPONSE_WAIT_FOR, PAUSE_RESPONSE_EXTRUDE_MORE, @@ -89,10 +91,10 @@ extern uint8_t did_pause_print; // Pause the print. If unload_length is set, do a Filament Unload bool pause_print( - const_float_t retract, // (mm) Retraction length + const float retract, // (mm) Retraction length const xyz_pos_t &park_point, // Parking XY Position and Z Raise const bool show_lcd=false, // Set LCD status messages? - const_float_t unload_length=0 // (mm) Filament Change Unload Length - 0 to skip + const float unload_length=0 // (mm) Filament Change Unload Length - 0 to skip DXC_PARAMS // Dual-X-Carriage extruder index ); @@ -103,18 +105,20 @@ void wait_for_confirmation( ); void resume_print( - const_float_t slow_load_length=0, // (mm) Slow Load Length for finishing move - const_float_t fast_load_length=0, // (mm) Fast Load Length for initial move - const_float_t extrude_length=ADVANCED_PAUSE_PURGE_LENGTH, // (mm) Purge length + const float slow_load_length=0, // (mm) Slow Load Length for finishing move + const float fast_load_length=0, // (mm) Fast Load Length for initial move + const float 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 ); bool load_filament( - const_float_t slow_load_length=0, // (mm) Slow Load Length for finishing move - const_float_t fast_load_length=0, // (mm) Fast Load Length for initial move - const_float_t extrude_length=0, // (mm) Purge length + const float slow_load_length=0, // (mm) Slow Load Length for finishing move + const float fast_load_length=0, // (mm) Fast Load Length for initial move + const float purge_length=0, // (mm) Purge length const int8_t max_beep_count=0, // Beep alert for attention const bool show_lcd=false, // Set LCD status messages? const bool pause_for_user=false, // Pause for user before returning? @@ -123,11 +127,11 @@ bool load_filament( ); bool unload_filament( - const_float_t unload_length, // (mm) Filament Unload Length - 0 to skip + const float unload_length, // (mm) Filament Unload Length - 0 to skip const bool show_lcd=false, // Set LCD status messages? const PauseMode mode=PAUSE_MODE_PAUSE_PRINT // Pause Mode to apply #if ALL(FILAMENT_UNLOAD_ALL_EXTRUDERS, MIXING_EXTRUDER) - , const_float_t mix_multiplier=1.0f // Extrusion multiplier (for a Mixing Extruder) + , const float mix_multiplier=1.0f // Extrusion multiplier (for a Mixing Extruder) #endif ); diff --git a/Marlin/src/feature/power.cpp b/Marlin/src/feature/power.cpp index a9655029fbeb..2068558fe9e5 100644 --- a/Marlin/src/feature/power.cpp +++ b/Marlin/src/feature/power.cpp @@ -34,6 +34,10 @@ #include "../module/temperature.h" #include "../MarlinCore.h" +#if ENABLED(MAX7219_REINIT_ON_POWERUP) + #include "max7219.h" +#endif + #if ENABLED(PS_OFF_SOUND) #include "../libs/buzzer.h" #endif @@ -60,6 +64,10 @@ bool Power::psu_on; millis_t Power::lastPowerOn; #endif +#if PSU_TRACK_STATE_MS + millis_t Power::last_state_change_ms = 0; +#endif + /** * Initialize pins & state for the power manager. * @@ -87,9 +95,18 @@ void Power::power_on() { #endif OUT_WRITE(PS_ON_PIN, PSU_ACTIVE_STATE); + #if ENABLED(PSU_OFF_REDUNDANT) + OUT_WRITE(PS_ON1_PIN, TERN_(PSU_OFF_REDUNDANT_INVERTED, !)PSU_ACTIVE_STATE); + #endif + TERN_(PSU_TRACK_STATE_MS, last_state_change_ms = millis()); + psu_on = true; safe_delay(PSU_POWERUP_DELAY); + restore_stepper_drivers(); + + TERN_(MAX7219_REINIT_ON_POWERUP, max7219.init()); + TERN_(HAS_TRINAMIC_CONFIG, safe_delay(PSU_POWERUP_DELAY)); #ifdef PSU_POWERUP_GCODE @@ -102,7 +119,7 @@ void Power::power_on() { * Processes any PSU_POWEROFF_GCODE and makes a PS_OFF_SOUND if enabled. */ void Power::power_off() { - TERN_(HAS_SUICIDE, suicide()); + TERN_(HAS_SUICIDE, marlin.suicide()); if (!psu_on) return; @@ -117,6 +134,11 @@ void Power::power_off() { #endif OUT_WRITE(PS_ON_PIN, !PSU_ACTIVE_STATE); + #if ENABLED(PSU_OFF_REDUNDANT) + OUT_WRITE(PS_ON1_PIN, IF_DISABLED(PSU_OFF_REDUNDANT_INVERTED, !)PSU_ACTIVE_STATE); + #endif + TERN_(PSU_TRACK_STATE_MS, last_state_change_ms = millis()); + psu_on = false; #if ANY(POWER_OFF_TIMER, POWER_OFF_WAIT_FOR_COOLDOWN) @@ -179,14 +201,14 @@ void Power::power_off() { /** * Check all conditions that would signal power needing to be on. * - * @returns bool if power is needed + * @return bool if power is needed */ bool Power::is_power_needed() { // If any of the stepper drivers are enabled... if (stepper.axis_enabled.bits) return true; - if (printJobOngoing() || printingIsPaused()) return true; + if (marlin.printJobOngoing() || marlin.printingIsPaused()) return true; #if ENABLED(AUTO_POWER_FANS) FANS_LOOP(i) if (thermalManager.fan_speed[i]) return true; @@ -239,7 +261,7 @@ void Power::power_off() { nextPowerCheck = now + 2500UL; if (is_power_needed()) power_on(); - else if (!lastPowerOn || (POWER_TIMEOUT > 0 && ELAPSED(now, lastPowerOn + SEC_TO_MS(POWER_TIMEOUT)))) + else if (!lastPowerOn || (POWER_TIMEOUT > 0 && ELAPSED(now, lastPowerOn, SEC_TO_MS(POWER_TIMEOUT)))) power_off(); } } diff --git a/Marlin/src/feature/power.h b/Marlin/src/feature/power.h index fdbb7126ceae..16f9dbcef54f 100644 --- a/Marlin/src/feature/power.h +++ b/Marlin/src/feature/power.h @@ -24,8 +24,11 @@ /** * power.h - power control */ +#if PIN_EXISTS(PS_ON_EDM) || (PIN_EXISTS(PS_ON1_EDM) && ENABLED(PSU_OFF_REDUNDANT)) + #define PSU_TRACK_STATE_MS 1 +#endif -#if ANY(AUTO_POWER_CONTROL, POWER_OFF_TIMER) +#if ANY(AUTO_POWER_CONTROL, POWER_OFF_TIMER, PSU_TRACK_STATE_MS) #include "../core/millis_t.h" #endif @@ -37,6 +40,10 @@ class Power { static void power_on(); static void power_off(); + #if PSU_TRACK_STATE_MS + static millis_t last_state_change_ms; + #endif + #if ANY(POWER_OFF_TIMER, POWER_OFF_WAIT_FOR_COOLDOWN) #if ENABLED(POWER_OFF_TIMER) static millis_t power_off_time; diff --git a/Marlin/src/feature/powerloss.cpp b/Marlin/src/feature/powerloss.cpp index 903a0b39b8c9..9bd2766ab07c 100644 --- a/Marlin/src/feature/powerloss.cpp +++ b/Marlin/src/feature/powerloss.cpp @@ -28,17 +28,18 @@ #if ENABLED(POWER_LOSS_RECOVERY) +#include "../inc/MarlinConfig.h" + #include "powerloss.h" -#include "../core/macros.h" #if ENABLED(EXTENSIBLE_UI) #include "../lcd/extui/ui_api.h" #endif -bool PrintJobRecovery::enabled; // Initialized by settings.load() +bool PrintJobRecovery::enabled; // Initialized by settings.load #if HAS_PLR_BED_THRESHOLD - celsius_t PrintJobRecovery::bed_temp_threshold; // Initialized by settings.load() + celsius_t PrintJobRecovery::bed_temp_threshold; // Initialized by settings.load #endif MediaFile PrintJobRecovery::file; @@ -48,8 +49,8 @@ uint8_t PrintJobRecovery::queue_index_r; uint32_t PrintJobRecovery::cmd_sdpos, // = 0 PrintJobRecovery::sdpos[BUFSIZE]; -#if HAS_DWIN_E3V2_BASIC - bool PrintJobRecovery::dwin_flag; // = false +#if HAS_PLR_UI_FLAG + bool PrintJobRecovery::ui_flag_resume; // = false #endif #include "../sd/cardreader.h" @@ -60,12 +61,15 @@ uint32_t PrintJobRecovery::cmd_sdpos, // = 0 #include "../module/planner.h" #include "../module/printcounter.h" #include "../module/temperature.h" -#include "../core/serial.h" #if HOMING_Z_WITH_PROBE #include "../module/probe.h" #endif +#if ENABLED(SOVOL_SV06_RTS) + #include "../lcd/sovol_rts/sovol_rts.h" +#endif + #if ENABLED(FWRETRACT) #include "fwretract.h" #endif @@ -95,7 +99,7 @@ PrintJobRecovery recovery; /** * Clear the recovery info */ -void PrintJobRecovery::init() { memset(&info, 0, sizeof(info)); } +void PrintJobRecovery::init() { info = { 0 }; } /** * Enable or disable then call changed() @@ -113,7 +117,7 @@ void PrintJobRecovery::enable(const bool onoff) { void PrintJobRecovery::changed() { if (!enabled) purge(); - else if (IS_SD_PRINTING()) + else if (card.isStillPrinting()) save(true); TERN_(EXTENSIBLE_UI, ExtUI::onSetPowerLoss(enabled)); } @@ -137,6 +141,14 @@ bool PrintJobRecovery::check() { return success; } +/** + * Cancel recovery + */ +void PrintJobRecovery::cancel() { + TERN_(PLR_HEAT_BED_ON_REBOOT, set_bed_temp(false)); + purge(); +} + /** * Delete the recovery file and clear the recovery data */ @@ -172,7 +184,7 @@ void PrintJobRecovery::prepare() { */ void PrintJobRecovery::save(const bool force/*=false*/, const float zraise/*=POWER_LOSS_ZRAISE*/, const bool raised/*=false*/) { - // We don't check IS_SD_PRINTING here so a save may occur during a pause + // We don't check isStillPrinting here so a save may occur during a pause #if SAVE_INFO_INTERVAL_MS > 0 static millis_t next_save_ms; // = 0 @@ -190,7 +202,7 @@ void PrintJobRecovery::save(const bool force/*=false*/, const float zraise/*=POW || ELAPSED(ms, next_save_ms) #endif // Save if Z is above the last-saved position by some minimum height - || current_position.z > info.current_position.z + POWER_LOSS_MIN_Z_CHANGE + || motion.position.z > info.current_position.z + POWER_LOSS_MIN_Z_CHANGE #endif ) { @@ -200,27 +212,31 @@ void PrintJobRecovery::save(const bool force/*=false*/, const float zraise/*=POW // Set Head and Foot to matching non-zero values if (!++info.valid_head) ++info.valid_head; // non-zero in sequence - //if (!IS_SD_PRINTING()) info.valid_head = 0; + //if (!card.isStillPrinting()) info.valid_head = 0; info.valid_foot = info.valid_head; // Machine state // info.sdpos and info.current_position are pre-filled from the Stepper ISR - info.feedrate = uint16_t(MMS_TO_MMM(feedrate_mm_s)); + info.feedrate = uint16_t(MMS_TO_MMM(motion.feedrate_mm_s)); + info.feedrate_percentage = motion.feedrate_percentage; + COPY(info.flow_percentage, planner.flow_percentage); + info.zraise = zraise; info.flag.raised = raised; // Was Z raised before power-off? + TERN_(CANCEL_OBJECTS, info.cancel_state = cancelable.state); TERN_(GCODE_REPEAT_MARKERS, info.stored_repeat = repeat); - TERN_(HAS_HOME_OFFSET, info.home_offset = home_offset); - TERN_(HAS_WORKSPACE_OFFSET, info.workspace_offset = workspace_offset); - E_TERN_(info.active_extruder = active_extruder); + TERN_(HAS_HOME_OFFSET, info.home_offset = motion.home_offset); + TERN_(HAS_WORKSPACE_OFFSET, info.workspace_offset = motion.workspace_offset); + E_TERN_(info.extruder = motion.extruder); - #if DISABLED(NO_VOLUMETRICS) + #if HAS_VOLUMETRIC_EXTRUSION info.flag.volumetric_enabled = parser.volumetric_enabled; #if HAS_MULTI_EXTRUDER - EXTRUDER_LOOP() info.filament_size[e] = planner.filament_size[e]; + COPY(info.filament_size, planner.filament_size); #else - if (parser.volumetric_enabled) info.filament_size[0] = planner.filament_size[active_extruder]; + if (parser.volumetric_enabled) info.filament_size[0] = planner.filament_size[motion.extruder]; #endif #endif @@ -264,14 +280,17 @@ void PrintJobRecovery::save(const bool force/*=false*/, const float zraise/*=POW #if ENABLED(BACKUP_POWER_SUPPLY) - void PrintJobRecovery::retract_and_lift(const_float_t zraise) { + void PrintJobRecovery::retract_and_lift(const float zraise) { #if POWER_LOSS_RETRACT_LEN || POWER_LOSS_ZRAISE gcode.set_relative_mode(true); // Use relative coordinates #if POWER_LOSS_RETRACT_LEN // Retract filament now - gcode.process_subcommands_now(F("G1 F3000 E-" STRINGIFY(POWER_LOSS_RETRACT_LEN))); + const uint16_t old_flow = planner.flow_percentage[motion.extruder]; + planner.set_flow(motion.extruder, 100); + gcode.process_subcommands_now(F("G1F3000E-" STRINGIFY(POWER_LOSS_RETRACT_LEN))); + planner.set_flow(motion.extruder, old_flow); #endif #if POWER_LOSS_ZRAISE @@ -308,20 +327,20 @@ void PrintJobRecovery::save(const bool force/*=false*/, const float zraise/*=POW #if ENABLED(BACKUP_POWER_SUPPLY) static bool lock = false; - if (lock) return; // No re-entrance from idle() during retract_and_lift() + if (lock) return; // No re-entrance from marlin.idle() during retract_and_lift() lock = true; #endif #if POWER_LOSS_ZRAISE // Get the limited Z-raise to do now or on resume - const float zraise = _MAX(0, _MIN(current_position.z + POWER_LOSS_ZRAISE, Z_MAX_POS - 1) - current_position.z); + const float zraise = _MAX(0, _MIN(motion.position.z + POWER_LOSS_ZRAISE, Z_MAX_POS - 1) - motion.position.z); #else constexpr float zraise = 0; #endif // Save the current position, distance that Z was (or should be) raised, // and a flag whether the raise was already done here. - if (IS_SD_PRINTING()) save(true, zraise, ENABLED(BACKUP_POWER_SUPPLY)); + if (card.isStillPrinting()) save(true, zraise, ENABLED(BACKUP_POWER_SUPPLY)); // Tell the LCD about the outage, even though it is about to die TERN_(EXTENSIBLE_UI, ExtUI::onPowerLoss()); @@ -331,7 +350,7 @@ void PrintJobRecovery::save(const bool force/*=false*/, const float zraise/*=POW #if ENABLED(BACKUP_POWER_SUPPLY) // Do a hard-stop of the steppers (with possibly a loud thud) - quickstop_stepper(); + motion.quickstop_stepper(); // With backup power a retract and raise can be done now retract_and_lift(zraise); #endif @@ -342,11 +361,11 @@ void PrintJobRecovery::save(const bool force/*=false*/, const float zraise/*=POW if (TERN0(DEBUG_POWER_LOSS_RECOVERY, simulated)) { card.fileHasFinished(); - current_position.reset(); - sync_plan_position(); + motion.position.reset(); + motion.sync_plan_position(); } else - kill(GET_TEXT_F(MSG_OUTAGE_RECOVERY)); + marlin.kill(GET_TEXT_F(MSG_OUTAGE_RECOVERY)); } #endif // POWER_LOSS_PIN || DEBUG_POWER_LOSS_RECOVERY @@ -365,6 +384,13 @@ void PrintJobRecovery::write() { if (!file.close()) DEBUG_ECHOLNPGM("Power-loss file close failed."); } +#if ENABLED(PLR_HEAT_BED_ON_REBOOT) + // Set bed temperature and wait. Called from M1000 to resume bed heating. + void PrintJobRecovery::set_bed_temp(const bool on) { + PROCESS_SUBCOMMANDS_NOW(TS(F("M190S"), on ? info.target_temperature_bed + PLR_HEAT_BED_EXTRA : 0)); + } +#endif + /** * Resume the saved print job */ @@ -419,8 +445,11 @@ void PrintJobRecovery::resume() { #endif // Interpret the saved Z according to flags - const float z_print = resume_pos.z, - z_raised = z_print + info.zraise; + const float z_print = resume_pos.z; + + #if ANY(Z_HOME_TO_MAX, POWER_LOSS_RECOVER_ZHOME) || DISABLED(BELTPRINTER) + const float z_raised = z_print + info.zraise; + #endif // // Home the axes that can safely be homed, and @@ -470,8 +499,8 @@ void PrintJobRecovery::resume() { PROCESS_SUBCOMMANDS_NOW(TS(F("G1F1000X"), p_float_t(p.x, 3), 'Y', p_float_t(p.y, 3), F("\nG28HZ"))); #endif - // Mark all axes as having been homed (no effect on current_position) - set_all_homed(); + // Mark all axes as having been homed (no effect on motion.position) + motion.set_all_homed(); #if HAS_LEVELING // Restore Z fade and possibly re-enable bed leveling compensation. @@ -481,7 +510,7 @@ void PrintJobRecovery::resume() { #if !HOMING_Z_DOWN // The physical Z was adjusted at power-off so undo the M420S1 correction to Z with G92.9. - PROCESS_SUBCOMMANDS_NOW(TS(F("G92.9Z"), p_float_t(z_now, 1))); + PROCESS_SUBCOMMANDS_NOW(TS(F("G92.9Z"), p_float_t(z_now, 3))); #endif #endif @@ -492,12 +521,12 @@ void PrintJobRecovery::resume() { #endif // Recover volumetric extrusion state - #if DISABLED(NO_VOLUMETRICS) + #if HAS_VOLUMETRIC_EXTRUSION #if HAS_MULTI_EXTRUDER EXTRUDER_LOOP() PROCESS_SUBCOMMANDS_NOW(TS(F("M200T"), e, F("D"), p_float_t(info.filament_size[e], 3))); if (!info.flag.volumetric_enabled) - PROCESS_SUBCOMMANDS_NOW(TS(F("M200D0T"), info.active_extruder)); + PROCESS_SUBCOMMANDS_NOW(TS(F("M200D0T"), info.extruder)); #else if (info.flag.volumetric_enabled) PROCESS_SUBCOMMANDS_NOW(TS(F("M200D"), p_float_t(info.filament_size[0], 3))); @@ -517,7 +546,7 @@ void PrintJobRecovery::resume() { // Restore the previously active tool (with no_move) #if HAS_MULTI_EXTRUDER || HAS_MULTI_HOTEND - PROCESS_SUBCOMMANDS_NOW(TS('T', info.active_extruder, 'S')); + PROCESS_SUBCOMMANDS_NOW(TS('T', info.extruder, 'S')); #endif // Restore print cooling fan speeds @@ -528,7 +557,7 @@ void PrintJobRecovery::resume() { } #endif - // Restore retract and hop state from an active `G10` command + // Restore retract and hop state from an active 'G10' command #if ENABLED(FWRETRACT) EXTRUDER_LOOP() { if (info.retract[e] != 0.0) { @@ -565,15 +594,24 @@ void PrintJobRecovery::resume() { // Move back down to the saved Z for printing PROCESS_SUBCOMMANDS_NOW(TS(F("G1F600Z"), p_float_t(z_print, 3))); - // Restore the feedrate + // Restore the feedrate and percentage PROCESS_SUBCOMMANDS_NOW(TS(F("G1F"), info.feedrate)); + motion.feedrate_percentage = info.feedrate_percentage; + + // Flowrate percentage + EXTRUDER_LOOP() planner.set_flow(e, info.flow_percentage[e]); // Restore E position with G92.9 PROCESS_SUBCOMMANDS_NOW(TS(F("G92.9E"), p_float_t(resume_pos.e, 3))); + #if ENABLED(CANCEL_OBJECTS) + cancelable.state = info.cancel_state; + cancelable.set_active_object(); // Sets the status message + #endif + TERN_(GCODE_REPEAT_MARKERS, repeat = info.stored_repeat); - TERN_(HAS_HOME_OFFSET, home_offset = info.home_offset); - TERN_(HAS_WORKSPACE_OFFSET, workspace_offset = info.workspace_offset); + TERN_(HAS_HOME_OFFSET, motion.home_offset = info.home_offset); + TERN_(HAS_WORKSPACE_OFFSET, motion.workspace_offset = info.workspace_offset); // Relative axis modes gcode.axis_relative = info.axis_relative; @@ -584,6 +622,11 @@ void PrintJobRecovery::resume() { // Resume the SD file from the last position PROCESS_SUBCOMMANDS_NOW(MString(F("M23 "), info.sd_filename)); PROCESS_SUBCOMMANDS_NOW(TS(F("M24S"), resume_sdpos, 'T', info.print_job_elapsed)); + + #if ENABLED(SOVOL_SV06_RTS) + if (rts.print_state) rts.refreshTime(); + rts.start_print_flag = false; + #endif } #if ENABLED(DEBUG_POWER_LOSS_RECOVERY) @@ -592,17 +635,26 @@ void PrintJobRecovery::resume() { DEBUG_ECHOLN(prefix, F(" Job Recovery Info...\nvalid_head:"), info.valid_head, F(" valid_foot:"), info.valid_foot); if (info.valid_head) { if (info.valid_head == info.valid_foot) { - DEBUG_ECHOPGM("current_position: "); + DEBUG_ECHOPGM("curr.position: "); LOOP_LOGICAL_AXES(i) { if (i) DEBUG_CHAR(','); DEBUG_ECHO(info.current_position[i]); } DEBUG_EOL(); - DEBUG_ECHOLNPGM("feedrate: ", info.feedrate); + DEBUG_ECHOLN(F("feedrate: "), info.feedrate, F(" x "), info.feedrate_percentage, '%'); + EXTRUDER_LOOP() DEBUG_ECHOLN('E', e + 1, F(" flow %: "), info.flow_percentage[e]); DEBUG_ECHOLNPGM("zraise: ", info.zraise, " ", info.flag.raised ? "(before)" : ""); + #if ENABLED(CANCEL_OBJECTS) + const cancel_state_t cs = info.cancel_state; + DEBUG_ECHOPGM("Canceled:"); + for (int i = 0; i < cs.object_count; i++) + if (TEST(cs.canceled, i)) { DEBUG_CHAR(' '); DEBUG_ECHO(i); } + DEBUG_EOL(); + #endif + #if ENABLED(GCODE_REPEAT_MARKERS) const uint8_t ind = info.stored_repeat.count(); DEBUG_ECHOLNPGM("repeat markers: ", ind); @@ -629,10 +681,10 @@ void PrintJobRecovery::resume() { #endif #if HAS_MULTI_EXTRUDER - DEBUG_ECHOLNPGM("active_extruder: ", info.active_extruder); + DEBUG_ECHOLNPGM("extruder: ", info.extruder); #endif - #if DISABLED(NO_VOLUMETRICS) + #if HAS_VOLUMETRIC_EXTRUSION DEBUG_ECHOPGM("filament_size:"); EXTRUDER_LOOP() DEBUG_ECHOLNPGM(" ", info.filament_size[e]); DEBUG_EOL(); @@ -698,7 +750,7 @@ void PrintJobRecovery::resume() { DEBUG_ECHOLNPGM("flag.dryrun: ", AS_DIGIT(info.flag.dryrun)); DEBUG_ECHOLNPGM("flag.allow_cold_extrusion: ", AS_DIGIT(info.flag.allow_cold_extrusion)); - #if DISABLED(NO_VOLUMETRICS) + #if HAS_VOLUMETRIC_EXTRUSION DEBUG_ECHOLNPGM("flag.volumetric_enabled: ", AS_DIGIT(info.flag.volumetric_enabled)); #endif } diff --git a/Marlin/src/feature/powerloss.h b/Marlin/src/feature/powerloss.h index 579731ffddcb..df17f956ed1c 100644 --- a/Marlin/src/feature/powerloss.h +++ b/Marlin/src/feature/powerloss.h @@ -30,12 +30,16 @@ #include "../inc/MarlinConfig.h" +#if ENABLED(CANCEL_OBJECTS) + #include "cancel_object.h" +#endif + #if ENABLED(GCODE_REPEAT_MARKERS) - #include "../feature/repeat.h" + #include "repeat.h" #endif #if ENABLED(MIXING_EXTRUDER) - #include "../feature/mixing.h" + #include "mixing.h" #endif #if !defined(POWER_LOSS_STATE) && PIN_EXISTS(POWER_LOSS) @@ -59,9 +63,16 @@ typedef struct { // Machine state xyze_pos_t current_position; uint16_t feedrate; + int16_t feedrate_percentage; + uint16_t flow_percentage[EXTRUDERS]; float zraise; + // Canceled objects + #if ENABLED(CANCEL_OBJECTS) + cancel_state_t cancel_state; + #endif + // Repeat information #if ENABLED(GCODE_REPEAT_MARKERS) Repeat stored_repeat; @@ -74,10 +85,10 @@ typedef struct { xyz_pos_t workspace_offset; #endif #if HAS_MULTI_EXTRUDER - uint8_t active_extruder; + uint8_t extruder; #endif - #if DISABLED(NO_VOLUMETRICS) + #if HAS_VOLUMETRIC_EXTRUSION float filament_size[EXTRUDERS]; #endif @@ -129,7 +140,7 @@ typedef struct { #if HAS_LEVELING bool leveling:1; // M420 S #endif - #if DISABLED(NO_VOLUMETRICS) + #if HAS_VOLUMETRIC_EXTRUSION bool volumetric_enabled:1; // M200 S D #endif } flag; @@ -151,8 +162,8 @@ class PrintJobRecovery { static uint32_t cmd_sdpos, //!< SD position of the next command sdpos[BUFSIZE]; //!< SD positions of queued commands - #if HAS_DWIN_E3V2_BASIC - static bool dwin_flag; + #if HAS_PLR_UI_FLAG + static bool ui_flag_resume; //!< Flag the UI to show a dialog to Resume (M1000) or Cancel (M1000C) #endif static void init(); @@ -190,10 +201,15 @@ class PrintJobRecovery { static void close() { file.close(); } static bool check(); + + #if ENABLED(PLR_HEAT_BED_ON_REBOOT) + static void set_bed_temp(const bool turn_on); + #endif + static void resume(); static void purge(); - static void cancel() { purge(); } + static void cancel(); static void load(); static void save(const bool force=ENABLED(SAVE_EACH_CMD_MODE), const float zraise=POWER_LOSS_ZRAISE, const bool raised=false); @@ -226,7 +242,7 @@ class PrintJobRecovery { static void write(); #if ENABLED(BACKUP_POWER_SUPPLY) - static void retract_and_lift(const_float_t zraise); + static void retract_and_lift(const float zraise); #endif #if PIN_EXISTS(POWER_LOSS) || ENABLED(DEBUG_POWER_LOSS_RECOVERY) diff --git a/Marlin/src/feature/probe_temp_comp.cpp b/Marlin/src/feature/probe_temp_comp.cpp index f640a9fd2f71..7dfa28f4b99e 100644 --- a/Marlin/src/feature/probe_temp_comp.cpp +++ b/Marlin/src/feature/probe_temp_comp.cpp @@ -104,12 +104,12 @@ void ProbeTempComp::print_offsets() { #endif } -void ProbeTempComp::prepare_new_calibration(const_float_t init_meas_z) { +void ProbeTempComp::prepare_new_calibration(const float init_meas_z) { calib_idx = 0; init_measurement = init_meas_z; } -void ProbeTempComp::push_back_new_measurement(const TempSensorID tsi, const_float_t meas_z) { +void ProbeTempComp::push_back_new_measurement(const TempSensorID tsi, const float meas_z) { if (calib_idx >= cali_info[tsi].measurements) return; sensor_z_offsets[tsi][calib_idx++] = static_cast((meas_z - init_measurement) * 1000.0f); } @@ -186,7 +186,7 @@ void ProbeTempComp::compensate_measurement(const TempSensorID tsi, const celsius }; // Interpolate Z based on a temperature being within a given range - auto linear_interp = [](const_float_t x, xy_float_t p1, xy_float_t p2) { + auto linear_interp = [](const float x, xy_float_t p1, xy_float_t p2) { // zoffs1 + zoffset_per_toffset * toffset return p1.y + (p2.y - p1.y) / (p2.x - p1.x) * (x - p1.x); }; @@ -212,7 +212,7 @@ void ProbeTempComp::compensate_measurement(const TempSensorID tsi, const celsius } // convert offset to mm and apply it - meas_z -= offset / 1000.0f; + meas_z -= offset * 0.001f; } bool ProbeTempComp::linear_regression(const TempSensorID tsi, float &k, float &d) { diff --git a/Marlin/src/feature/probe_temp_comp.h b/Marlin/src/feature/probe_temp_comp.h index 42348db68473..e6509a20cf3d 100644 --- a/Marlin/src/feature/probe_temp_comp.h +++ b/Marlin/src/feature/probe_temp_comp.h @@ -84,8 +84,8 @@ class ProbeTempComp { } static bool set_offset(const TempSensorID tsi, const uint8_t idx, const int16_t offset); static void print_offsets(); - static void prepare_new_calibration(const_float_t init_meas_z); - static void push_back_new_measurement(const TempSensorID tsi, const_float_t meas_z); + static void prepare_new_calibration(const float init_meas_z); + static void push_back_new_measurement(const TempSensorID tsi, const float meas_z); static bool finish_calibration(const TempSensorID tsi); static void set_enabled(const bool ena) { enabled = ena; } diff --git a/Marlin/src/feature/resonance/resonance_generator.cpp b/Marlin/src/feature/resonance/resonance_generator.cpp new file mode 100644 index 000000000000..ca62b12ae0ec --- /dev/null +++ b/Marlin/src/feature/resonance/resonance_generator.cpp @@ -0,0 +1,194 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2026 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 . + * + */ + +#include "../../inc/MarlinConfigPre.h" + +#if ENABLED(RESONANCE_TEST) + +#include "resonance_generator.h" + +#if ENABLED(FT_MOTION) + #include "../../module/ft_motion.h" +#endif + +#include "../../gcode/gcode.h" // for home_all_axes + +resonance_test_params_t ResonanceGenerator::rt_params; // Resonance test parameters +float ResonanceGenerator::timeline; + +bool ResonanceGenerator::active = false; // Resonance test active +bool ResonanceGenerator::done = false; // Resonance test done +int32_t ResonanceGenerator::freq_to_phase_fp; +int32_t ResonanceGenerator::max_freq_fp; +int32_t ResonanceGenerator::phase_fp; +int32_t ResonanceGenerator::current_freq_fp; +int32_t ResonanceGenerator::amplitude_precalc_fp; + + +#if HAS_STANDARD_MOTION + block_t ResonanceGenerator::block; +#endif + +ResonanceGenerator rtg; + +ResonanceGenerator::ResonanceGenerator() {} + +void ResonanceGenerator::start() { + gcode.home_all_axes(); // Always home axes first + motion.blocking_move_xy(X_CENTER, Y_CENTER, Z_CLEARANCE_FOR_HOMING); + + rt_params.start_pos = motion.position; + active = true; + done = false; + + // Clamp Z-axis acceleration for safety + if (rt_params.axis == Z_AXIS) + NOMORE(rt_params.accel_per_hz, 15.0f); + + // Calculate time constant for sine sweep + const float rt_time = rt_params.octave_duration * (logf(RATIO) / logf(2.0f)); + + #if HAS_STANDARD_MOTION + if (TERN1(FT_MOTION, !ftMotion.cfg.active)) { + block.reset(); + block.initial_rate = (rt_params.axis == Z_AXIS) ? 2000 : 8000; + } + #endif + + // Precompute fixed-point sine sweep parameters + amplitude_precalc_fp = F2FP((rt_params.amplitude_correction * rt_params.accel_per_hz * 0.25f) / sq(M_PI)); + current_freq_fp = F2FP(rt_params.min_freq); + freq_to_phase_fp = F2FP(2.0f * M_PI * rt_time); + max_freq_fp = F2FP(rt_params.max_freq); + phase_fp = 0; +} + +void ResonanceGenerator::abort() { + reset(); + #if HAS_STANDARD_MOTION + if (!TERN0(FT_MOTION, ftMotion.cfg.active)) + return; + #endif + TERN_(FT_MOTION, ftMotion.reset()); +} + +void ResonanceGenerator::reset() { + rt_params = resonance_test_params_t(); + #if HAS_STANDARD_MOTION + if (!TERN0(FT_MOTION, ftMotion.cfg.active)) + block.reset(); + #endif + active = false; + done = false; +} + +float ResonanceGenerator::calc_next_pos() { + // Phase accumulation and wrapping within [0, 2Ļ€) + phase_fp += (int32_t)(((int64_t)current_freq_fp * freq_to_phase_fp) >> FP_BITS); + if (phase_fp >= M_TAU_FP) phase_fp -= M_TAU_FP; + else if (phase_fp < 0) phase_fp += M_TAU_FP; + + // -Ļ€ <= r_fp <= Ļ€ + const int32_t r_fp = (phase_fp > M_PI_FP) ? phase_fp - M_TAU_FP : phase_fp; + + // Calculate windowing polynomial: 1.0 - 0.101321184 * r² + const int32_t poly_fp = FP_ONE - ((C0101321184_FP * ((r_fp * r_fp) >> FP_BITS)) >> FP_BITS); + + // Combine amplitude, phase, and polynomial and return new position + const int32_t amplitude_fp = (int32_t)(((int64_t)amplitude_precalc_fp * FP_ONE) / current_freq_fp); + const int32_t pos_fp = (int32_t)((((int64_t)amplitude_fp * r_fp) >> FP_BITS) * poly_fp) >> FP_BITS; + + return FP2F(pos_fp); +} + +#if ENABLED(FT_MOTION) + + void ResonanceGenerator::fill_stepper_plan_buffer() { + #if HAS_FTM_DIR_CHANGE_HOLD + xyze_float_t traj_coords = ftMotion.get_last_target_traj(); + traj_coords[rt_params.axis] = rt_params.start_pos[rt_params.axis]; + #else + xyze_float_t traj_coords = rt_params.start_pos; + #endif + // Save starting position, avoid cumulative errors + const float start_pos = rt_params.start_pos[rt_params.axis]; + + while (!ftMotion.stepping.is_full()) { + // Calculate current frequency with exponential sweep + current_freq_fp += current_freq_fp >> FP_BITS; + if (current_freq_fp > max_freq_fp) { + done = true; + return; + } + + // Resonate the axis being tested + traj_coords[rt_params.axis] = start_pos + calc_next_pos(); + + TERN_(HAS_FTM_DIR_CHANGE_HOLD, traj_coords = ftMotion.ftm_hold_frames(traj_coords)); + + // Store in buffer + ftMotion.stepping_enqueue(traj_coords); + } + } + +#endif // FT_MOTION + +#if HAS_STANDARD_MOTION + + block_t* ResonanceGenerator::generate_resonance_block() { + // Static variables to retain state between calls and avoid cumulative errors + static float prev_pos = 0.0f, step_accumulator = 0.0f; + + const float step_mm = planner.settings.axis_steps_per_mm[rt_params.axis]; + const uint8_t axis_bit = 1 << rt_params.axis; + + // Calculate current frequency with exponential sweep + current_freq_fp += current_freq_fp >> FP_BITS; + if (current_freq_fp > max_freq_fp) { + done = true; + return nullptr; + } + + // Calculate position and accumulate steps + const float current_pos = calc_next_pos(); + step_accumulator += (current_pos - prev_pos) * step_mm; + prev_pos = current_pos; + + // Extract steps + const int32_t delta_steps = (int32_t)floor(step_accumulator); + step_accumulator -= delta_steps; + const uint32_t abs_steps = abs(delta_steps); + + // Update block + block.steps[rt_params.axis] = abs_steps; + block.step_event_count = abs_steps; + if (delta_steps < 0) + block.direction_bits |= axis_bit; + else + block.direction_bits &= ~axis_bit; + + return █ + } + +#endif // HAS_STANDARD_MOTION + +#endif // RESONANCE_TEST diff --git a/Marlin/src/feature/resonance/resonance_generator.h b/Marlin/src/feature/resonance/resonance_generator.h new file mode 100644 index 000000000000..1bfc7db6719f --- /dev/null +++ b/Marlin/src/feature/resonance/resonance_generator.h @@ -0,0 +1,110 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2026 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 . + * + */ +#pragma once + +#include "../../inc/MarlinConfigPre.h" + +#include + +#if HAS_STANDARD_MOTION + #include "../../module/planner.h" +#endif + +// Fixed-point configuration +#define FP_BITS 16 // 16 bits for fractional part +#define FP_ONE (1UL << FP_BITS) // Fixed-point 1.0 + +// Convert float to fixed-point +#define F2FP(x) ((int32_t)((x) * FP_ONE + 0.5f)) + +// Convert fixed-point to float +#define FP2F(x) ((float)(x) / FP_ONE) + +// Fixed-point constants +#define M_TAU_FP F2FP(2.0f * M_PI) +#define M_PI_FP F2FP(M_PI) +#define C0101321184_FP F2FP(0.101321184f) + +// For rt_time calculation, perfect match between octave duration and frequency sweep +#define RATIO (1.0f + 1.0f / 65536.0f) // 1 + 1/2^16 + +typedef struct ResonanceTestParams { + AxisEnum axis = NO_AXIS_ENUM; // Axis to test + float min_freq = 5.0f; // Minimum frequency [Hz] + float max_freq = 100.0f; // Maximum frequency [Hz] + float octave_duration = 40.0f; // Octave duration for logarithmic progression + float accel_per_hz = 60.0f; // Acceleration per Hz [mm/sec/Hz] or [g/Hz] + int16_t amplitude_correction = 5; // Amplitude correction factor + xyze_pos_t start_pos; // Initial stepper position +} resonance_test_params_t; + +class ResonanceGenerator { + public: + static resonance_test_params_t rt_params; // Resonance test parameters + static float timeline; // Timeline Value to calculate resonance frequency + + ResonanceGenerator(); + + static void reset(); + static void start(); + + // Return frequency based on timeline + static float getFrequencyFromTimeline() { + // Logarithmic approach with duration per octave + return rt_params.min_freq * exp2f(timeline / rt_params.octave_duration); + } + + #if HAS_STANDARD_MOTION + static block_t* generate_resonance_block(); // Generate planner block for standard motion + #endif + #if ENABLED(FT_MOTION) + static void fill_stepper_plan_buffer(); // Fill stepper plan buffer with trajectory points + #endif + + static void setActive(const bool state) { active = state; } + static bool isActive() { return active; } + + static void setDone(const bool state) { done = state; } + static bool isDone() { return done; } + + static void abort(); // Abort resonance test + + private: + static float calc_next_pos(); // Calculate next position point based on current frequency + + // Fixed-point variables + static int32_t amplitude_precalc_fp; // Fixed-point amplitude precalculation + static int32_t current_freq_fp; // Fixed-point current frequency + + // Phase variables (in radians, stored as fixed-point) + static int32_t phase_fp; // Fixed-point phase accumulator + static int32_t freq_to_phase_fp; // Fixed-point frequency to phase conversion + + static int32_t max_freq_fp; // Fixed-point maximum frequency + #if HAS_STANDARD_MOTION + static block_t block; + #endif + static bool active; // Resonance test active + static bool done; // Resonance test done +}; + +extern ResonanceGenerator rtg; diff --git a/Marlin/src/feature/rs485.cpp b/Marlin/src/feature/rs485.cpp new file mode 100644 index 000000000000..800918545081 --- /dev/null +++ b/Marlin/src/feature/rs485.cpp @@ -0,0 +1,39 @@ +/** + * 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 . + * + */ +#include "../inc/MarlinConfig.h" + +#if HAS_RS485_SERIAL + +#include "rs485.h" + +HardwareSerialBusIO rs485BusIO(&RS485_SERIAL); +RS485Bus rs485Bus(rs485BusIO, RS485_RX_ENABLE_PIN, RS485_TX_ENABLE_PIN); + +PhotonProtocol rs485Protocol; + +Packetizer rs485Packetizer(rs485Bus, rs485Protocol); + +uint8_t rs485Buffer[RS485_SEND_BUFFER_SIZE]; + +void rs485_init() { RS485_SERIAL.begin(57600); } + +#endif // HAS_RS485_SERIAL diff --git a/Marlin/src/feature/rs485.h b/Marlin/src/feature/rs485.h new file mode 100644 index 000000000000..3327626a3c05 --- /dev/null +++ b/Marlin/src/feature/rs485.h @@ -0,0 +1,40 @@ +/** + * 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 . + * + */ +#pragma once + +#include "../inc/MarlinConfigPre.h" + +#include +#include + +#include +#include + +#define RS485_SEND_BUFFER_SIZE 32 + +extern HardwareSerialBusIO rs485BusIO; +extern RS485Bus rs485Bus; +extern PhotonProtocol rs485Protocol; +extern Packetizer rs485Packetizer; +extern uint8_t rs485Buffer[RS485_SEND_BUFFER_SIZE]; + +void rs485_init(); diff --git a/Marlin/src/feature/runout.cpp b/Marlin/src/feature/runout.cpp index 1d684c6b856a..f0b94d0de856 100644 --- a/Marlin/src/feature/runout.cpp +++ b/Marlin/src/feature/runout.cpp @@ -33,7 +33,7 @@ FilamentMonitor runout; bool FilamentMonitorBase::enabled = true, - FilamentMonitorBase::filament_ran_out; // = false + FilamentMonitorBase::filament_ran_out; // = false #if ENABLED(HOST_ACTION_COMMANDS) bool FilamentMonitorBase::host_handling; // = false @@ -51,6 +51,10 @@ bool FilamentMonitorBase::enabled = true, #if ENABLED(FILAMENT_MOTION_SENSOR) uint8_t FilamentSensorEncoder::motion_detected; #endif + #if ENABLED(FILAMENT_SWITCH_AND_MOTION) + bool RunoutResponseDelayed::ignore_motion = false; + float RunoutResponseDelayed::motion_distance_mm = FILAMENT_MOTION_DISTANCE_MM; + #endif #else int8_t RunoutResponseDebounced::runout_count[NUM_RUNOUT_SENSORS]; // = 0 #endif @@ -59,7 +63,7 @@ bool FilamentMonitorBase::enabled = true, // Filament Runout event handler // #include "../MarlinCore.h" -#include "../feature/pause.h" +#include "pause.h" #include "../gcode/queue.h" #if ENABLED(HOST_ACTION_COMMANDS) @@ -68,12 +72,12 @@ bool FilamentMonitorBase::enabled = true, #if ENABLED(EXTENSIBLE_UI) #include "../lcd/extui/ui_api.h" -#elif ENABLED(DWIN_LCD_PROUI) - #include "../lcd/e3v2/proui/dwin.h" #endif void event_filament_runout(const uint8_t extruder) { + runout.init_for_restart(false); // Reset and disable + if (did_pause_print) return; // Action already in progress. Purge triggered repeated runout. #if ENABLED(TOOLCHANGE_MIGRATION_FEATURE) @@ -88,7 +92,6 @@ void event_filament_runout(const uint8_t extruder) { #endif TERN_(EXTENSIBLE_UI, ExtUI::onFilamentRunout(ExtUI::getTool(extruder))); - TERN_(DWIN_LCD_PROUI, dwinFilamentRunout(extruder)); #if ANY(HOST_PROMPT_SUPPORT, HOST_ACTION_COMMANDS, MULTI_FILAMENT_SENSOR) const char tool = '0' + TERN0(MULTI_FILAMENT_SENSOR, extruder); diff --git a/Marlin/src/feature/runout.h b/Marlin/src/feature/runout.h index c95c39f27387..c33980d7e627 100644 --- a/Marlin/src/feature/runout.h +++ b/Marlin/src/feature/runout.h @@ -30,8 +30,7 @@ #include "../module/planner.h" #include "../module/stepper.h" // for block_t #include "../gcode/queue.h" -#include "../feature/pause.h" // for did_pause_print -#include "../MarlinCore.h" // for printingIsActive() +#include "pause.h" // for did_pause_print #include "../inc/MarlinConfig.h" @@ -46,11 +45,15 @@ #if ENABLED(FILAMENT_MOTION_SENSOR) #define HAS_FILAMENT_MOTION 1 -#endif -#if DISABLED(FILAMENT_MOTION_SENSOR) || ENABLED(FILAMENT_SWITCH_AND_MOTION) + #if ENABLED(FILAMENT_SWITCH_AND_MOTION) + #define HAS_FILAMENT_SWITCH 1 + #endif +#else #define HAS_FILAMENT_SWITCH 1 #endif +#define FILAMENT_IS_OUT(N...) (READ(FIL_RUNOUT##N##_PIN) == FIL_RUNOUT##N##_STATE) + typedef Flags< #if NUM_MOTION_SENSORS > NUM_RUNOUT_SENSORS NUM_MOTION_SENSORS @@ -60,7 +63,7 @@ typedef Flags< > runout_flags_t; void event_filament_runout(const uint8_t extruder); -inline bool should_monitor_runout() { return did_pause_print || printingIsActive(); } +inline bool should_monitor_runout() { return did_pause_print || marlin.printingIsActive(); } template class TFilamentMonitor; @@ -118,15 +121,19 @@ class TFilamentMonitor : public FilamentMonitorBase { static void filament_motion_present(const uint8_t extruder) { response.filament_motion_present(extruder); } + static float& motion_distance() { return response.motion_distance_mm; } + static void set_motion_distance(const float mm) { response.motion_distance_mm = mm; } + static void set_ignore_motion(const bool ignore=true) { response.set_ignore_motion(ignore); } #endif #if HAS_FILAMENT_RUNOUT_DISTANCE static float& runout_distance() { return response.runout_distance_mm; } - static void set_runout_distance(const_float_t mm) { response.runout_distance_mm = mm; } + static void set_runout_distance(const float mm) { response.runout_distance_mm = mm; } #endif // Handle a block completion. RunoutResponseDelayed uses this to // add up the length of filament moved while the filament is out. + // Called from ISR context! static void block_completed(const block_t * const b) { if (enabled) { response.block_completed(b); @@ -136,39 +143,44 @@ class TFilamentMonitor : public FilamentMonitorBase { // Give the response a chance to update its counter. static void run() { - if (enabled && !filament_ran_out && should_monitor_runout()) { - TERN_(HAS_FILAMENT_RUNOUT_DISTANCE, cli()); // Prevent RunoutResponseDelayed::block_completed from accumulating here - response.run(); - sensor.run(); - const runout_flags_t runout_flags = response.has_run_out(); - TERN_(HAS_FILAMENT_RUNOUT_DISTANCE, sei()); - #if MULTI_FILAMENT_SENSOR - #if ENABLED(WATCH_ALL_RUNOUT_SENSORS) - const bool ran_out = bool(runout_flags); // any sensor triggers - uint8_t extruder = 0; - if (ran_out) while (!runout_flags.test(extruder)) extruder++; - #else - const bool ran_out = runout_flags[active_extruder]; // suppress non active extruders - uint8_t extruder = active_extruder; - #endif + if (!enabled || filament_ran_out || !should_monitor_runout()) return; + TERN_(HAS_FILAMENT_RUNOUT_DISTANCE, cli()); // Prevent RunoutResponseDelayed::block_completed from accumulating here + response.run(); + sensor.run(); + const runout_flags_t runout_flags = response.has_run_out(); + TERN_(HAS_FILAMENT_RUNOUT_DISTANCE, sei()); + #if MULTI_FILAMENT_SENSOR + #if ENABLED(WATCH_ALL_RUNOUT_SENSORS) + const bool ran_out = bool(runout_flags); // any sensor triggers + uint8_t extruder = 0; + if (ran_out) while (!runout_flags.test(extruder)) extruder++; #else - const bool ran_out = bool(runout_flags); - uint8_t extruder = active_extruder; + const bool ran_out = runout_flags[motion.extruder]; // suppress non active extruders + uint8_t extruder = motion.extruder; #endif + #else + const bool ran_out = bool(runout_flags); + uint8_t extruder = motion.extruder; + #endif - if (ran_out) { - #if ENABLED(FILAMENT_RUNOUT_SENSOR_DEBUG) - SERIAL_ECHOPGM("Runout Sensors: "); - for (uint8_t i = 0; i < 8; ++i) SERIAL_CHAR('0' + char(runout_flags[i])); - SERIAL_ECHOLNPGM(" -> ", extruder, " RUN OUT"); - #endif + if (!ran_out) return; - filament_ran_out = true; - event_filament_runout(extruder); - planner.synchronize(); - } - } + #if ENABLED(FILAMENT_RUNOUT_SENSOR_DEBUG) + SERIAL_ECHOPGM("Runout Sensors: "); + for (uint8_t i = 0; i < 8; ++i) SERIAL_CHAR('0' + char(runout_flags[i])); + SERIAL_ECHOLNPGM(" -> ", extruder, " RUN OUT"); + #endif + + filament_ran_out = true; + event_filament_runout(extruder); + planner.synchronize(); + } + + // Reset after a filament runout or upon resuming a job + static void init_for_restart(const bool onoff=true) { + response.init_for_restart(onoff); } + }; /*************************** FILAMENT PRESENCE SENSORS ***************************/ @@ -186,6 +198,9 @@ class FilamentSensorBase { static void filament_motion_present(const uint8_t extruder) { runout.filament_motion_present(extruder); // ...which calls response.filament_motion_present(extruder) } + static void set_ignore_motion(const bool ignore=true) { + runout.set_ignore_motion(ignore); + } #endif public: @@ -265,6 +280,7 @@ class FilamentSensorBase { } public: + // Called from ISR context to indicate a block was completed static void block_completed(const block_t * const b) { // If the sensor wheel has moved since the last call to // this method reset the runout counter for the extruder. @@ -291,8 +307,8 @@ class FilamentSensorBase { static bool poll_runout_state(const uint8_t extruder) { const uint8_t runout_states = poll_runout_states(); #if MULTI_FILAMENT_SENSOR - if ( !TERN0(DUAL_X_CARRIAGE, idex_is_duplicating()) - && !TERN0(MULTI_NOZZLE_DUPLICATION, extruder_duplication_enabled) + if ( !TERN0(DUAL_X_CARRIAGE, motion.idex_is_duplicating()) + && !TERN0(MULTI_NOZZLE_DUPLICATION, motion.extruder_duplication) ) return TEST(runout_states, extruder); // A specific extruder ran out #else UNUSED(extruder); @@ -301,12 +317,14 @@ class FilamentSensorBase { } public: + // Called from ISR context to indicate a block was completed static void block_completed(const block_t * const) {} static void run() { for (uint8_t s = 0; s < NUM_RUNOUT_SENSORS; ++s) { const bool out = poll_runout_state(s); if (!out) filament_present(s); + TERN_(FILAMENT_SWITCH_AND_MOTION, set_ignore_motion(out)); #if ENABLED(FILAMENT_RUNOUT_SENSOR_DEBUG) static uint8_t was_out; // = 0 if (out != TEST(was_out, s)) { @@ -330,6 +348,7 @@ class FilamentSensorBase { TERN_(HAS_FILAMENT_SWITCH, static FilamentSensorSwitch switch_sensor); public: + // Called from ISR context to indicate a block was completed static void block_completed(const block_t * const b) { TERN_(HAS_FILAMENT_MOTION, encoder_sensor.block_completed(b)); TERN_(HAS_FILAMENT_SWITCH, switch_sensor.block_completed(b)); @@ -360,40 +379,68 @@ class FilamentSensorBase { class RunoutResponseDelayed { private: static countdown_t mm_countdown; + #if ENABLED(FILAMENT_SWITCH_AND_MOTION) + static bool ignore_motion; // Flag to ignore the encoder + #endif public: static float runout_distance_mm; + #if ENABLED(FILAMENT_SWITCH_AND_MOTION) + static float motion_distance_mm; + #endif + + static void set_ignore_motion(const bool ignore=true) { + UNUSED(ignore); + TERN_(FILAMENT_SWITCH_AND_MOTION, ignore_motion = ignore); + } + static void reset() { for (uint8_t i = 0; i < NUM_RUNOUT_SENSORS; ++i) filament_present(i); #if ENABLED(FILAMENT_SWITCH_AND_MOTION) for (uint8_t i = 0; i < NUM_MOTION_SENSORS; ++i) filament_motion_present(i); #endif + set_ignore_motion(false); } static void run() { #if ENABLED(FILAMENT_RUNOUT_SENSOR_DEBUG) static millis_t t = 0; const millis_t ms = millis(); - if (ELAPSED(ms, t)) { - t = millis() + 1000UL; - for (uint8_t i = 0; i < NUM_RUNOUT_SENSORS; ++i) - SERIAL_ECHO(i ? F(", ") : F("Runout remaining mm: "), mm_countdown.runout[i]); - #if ENABLED(FILAMENT_SWITCH_AND_MOTION) - for (uint8_t i = 0; i < NUM_MOTION_SENSORS; ++i) - SERIAL_ECHO(i ? F(", ") : F("Motion remaining mm: "), mm_countdown.motion[i]); - #endif - SERIAL_EOL(); - } + if (PENDING(ms, t)) return; + t = ms + 1000UL; + PORT_REDIRECT(SerialMask::All); + for (uint8_t i = 0; i < NUM_RUNOUT_SENSORS; ++i) + SERIAL_ECHO(i ? F(", ") : F("Runout remaining mm: "), mm_countdown.runout[i]); + #if ENABLED(FILAMENT_SWITCH_AND_MOTION) + for (uint8_t i = 0; i < NUM_MOTION_SENSORS; ++i) + SERIAL_ECHO(i ? F(", ") : F(", motion remaining mm: "), mm_countdown.motion[i]); + #endif + SERIAL_EOL(); #endif } + // Get runout status for all presence sensors and motion sensors static runout_flags_t has_run_out() { runout_flags_t runout_flags{0}; - for (uint8_t i = 0; i < NUM_RUNOUT_SENSORS; ++i) if (mm_countdown.runout[i] < 0) runout_flags.set(i); + #if ENABLED(FILAMENT_SWITCH_AND_MOTION) - for (uint8_t i = 0; i < NUM_MOTION_SENSORS; ++i) if (mm_countdown.motion[i] < 0) runout_flags.set(i); + // Runout based on filament motion + if (!ignore_motion) { + for (uint8_t i = 0; i < NUM_MOTION_SENSORS; ++i) { + if (mm_countdown.motion[i] < 0) { + runout_flags.set(i); + mm_countdown.runout[i] = -1; // For a filament jam don't wait for runout_distance_mm! + } + } + } #endif + + // Runout based on filament presence + for (uint8_t i = 0; i < NUM_RUNOUT_SENSORS; ++i) + if (mm_countdown.runout[i] < 0) + runout_flags.set(i); + return runout_flags; } @@ -417,8 +464,8 @@ class FilamentSensorBase { #if ENABLED(FILAMENT_SWITCH_AND_MOTION) static void filament_motion_present(const uint8_t extruder) { // Same logic as filament_present - if (mm_countdown.motion[extruder] < runout_distance_mm || did_pause_print) { - mm_countdown.motion[extruder] = runout_distance_mm; + if (mm_countdown.motion[extruder] < motion_distance_mm || did_pause_print) { + mm_countdown.motion[extruder] = motion_distance_mm; mm_countdown.motion_reset.clear(extruder); } else @@ -426,29 +473,42 @@ class FilamentSensorBase { } #endif + // Called from ISR context to indicate a block was completed static void block_completed(const block_t * const b) { - const int32_t esteps = b->steps.e; - if (!esteps) return; - // No calculation unless paused or printing if (!should_monitor_runout()) return; + // Only extrusion moves are examined + const int32_t esteps = b->steps.e; + if (!esteps) return; + // No need to ignore retract/unretract movement since they complement each other const uint8_t e = b->extruder; const float mm = (b->direction_bits.e ? esteps : -esteps) * planner.mm_per_step[E_AXIS_N(e)]; + // Apply E distance to runout countdown, reset if flagged if (e < NUM_RUNOUT_SENSORS) { mm_countdown.runout[e] -= mm; if (mm_countdown.runout_reset[e]) filament_present(e); // Reset pending. Try to reset. } #if ENABLED(FILAMENT_SWITCH_AND_MOTION) - if (e < NUM_MOTION_SENSORS) { + // Apply E distance to motion countdown, reset if flagged + if (!ignore_motion && e < NUM_MOTION_SENSORS) { mm_countdown.motion[e] -= mm; if (mm_countdown.motion_reset[e]) filament_motion_present(e); // Reset pending. Try to reset. } #endif } + + // Reset after a filament runout or upon resuming a job + static void init_for_restart(const bool onoff=true) { + UNUSED(onoff); + #if ENABLED(FILAMENT_SWITCH_AND_MOTION) + reset(); // also calls set_ignore_motion(false) + set_ignore_motion(!onoff); + #endif + } }; #else // !HAS_FILAMENT_RUNOUT_DISTANCE @@ -476,11 +536,15 @@ class FilamentSensorBase { return runout_flags; } + // Called from ISR context to indicate a block was completed static void block_completed(const block_t * const) { } static void filament_present(const uint8_t extruder) { runout_count[extruder] = runout_threshold; } + + // Reset after a filament runout or upon resuming a job + static void init_for_restart(const bool=true) { reset(); } }; #endif // !HAS_FILAMENT_RUNOUT_DISTANCE diff --git a/Marlin/src/feature/solenoid.cpp b/Marlin/src/feature/solenoid.cpp index cc4522e30a62..33799c5ffbd0 100644 --- a/Marlin/src/feature/solenoid.cpp +++ b/Marlin/src/feature/solenoid.cpp @@ -26,7 +26,7 @@ #include "solenoid.h" -#include "../module/motion.h" // for active_extruder +#include "../module/motion.h" // for motion.extruder #include "../module/tool_change.h" // for parking_extruder_set_parked // Used primarily with MANUAL_SOLENOID_CONTROL @@ -38,7 +38,7 @@ static void set_solenoid(const uint8_t num, const uint8_t state) { } #if ENABLED(PARKING_EXTRUDER) - if (state == LOW && active_extruder == num) // If active extruder's solenoid is disabled, carriage is considered parked + if (state == LOW && motion.extruder == num) // If active extruder's solenoid is disabled, carriage is considered parked parking_extruder_set_parked(true); #endif } diff --git a/Marlin/src/feature/spindle_laser.cpp b/Marlin/src/feature/spindle_laser.cpp index c0635c72206f..dd8d859bde10 100644 --- a/Marlin/src/feature/spindle_laser.cpp +++ b/Marlin/src/feature/spindle_laser.cpp @@ -35,7 +35,7 @@ #endif #if ENABLED(I2C_AMMETER) - #include "../feature/ammeter.h" + #include "ammeter.h" #endif SpindleLaser cutter; @@ -43,6 +43,10 @@ bool SpindleLaser::enable_state; // Virtual uint8_t SpindleLaser::power, // Actual power output 0-255 ocr or "0 = off" > 0 = "on" SpindleLaser::last_power_applied; // = 0 // Basic power state tracking +#if HAS_SPINDLE_ACCELERATION + uint32_t SpindleLaser::acceleration_spindle_deg_per_s2; // (°/s/s) Spindle acceleration. Initialized by settings.load +#endif + #if ENABLED(LASER_FEATURE) cutter_test_pulse_t SpindleLaser::testPulse = 50; // (ms) Test fire pulse default duration uint8_t SpindleLaser::last_block_power; // = 0 // Track power changes for dynamic inline power @@ -100,7 +104,22 @@ void SpindleLaser::init() { #if ENABLED(HAL_CAN_SET_PWM_FREQ) && SPINDLE_LASER_FREQUENCY hal.set_pwm_frequency(pin_t(SPINDLE_LASER_PWM_PIN), frequency); #endif - hal.set_pwm_duty(pin_t(SPINDLE_LASER_PWM_PIN), ocr ^ SPINDLE_LASER_PWM_OFF); + #if HAS_SPINDLE_ACCELERATION + const int16_t diff = ocr - last_power_applied; + const uint8_t abs_diff = ABS(diff); + uint8_t current_ocr = last_power_applied; + // Duration between ocr increments. SPEED_POWER_MAX is in RPM. + const millis_t duration = (float(SPEED_POWER_MAX) * (60000.f / 2550.f) / float(acceleration_spindle_deg_per_s2)) * abs_diff; + millis_t next_ocr_change = millis() + duration; + while (current_ocr != ocr) { + while (PENDING(millis(), next_ocr_change)) marlin.idle(); + current_ocr += diff > 0 ? 1 : -1; + hal.set_pwm_duty(pin_t(SPINDLE_LASER_PWM_PIN), current_ocr ^ SPINDLE_LASER_PWM_OFF); + next_ocr_change += duration; + } + #else + hal.set_pwm_duty(pin_t(SPINDLE_LASER_PWM_PIN), ocr ^ SPINDLE_LASER_PWM_OFF); + #endif } void SpindleLaser::set_ocr(const uint8_t ocr) { @@ -111,10 +130,10 @@ void SpindleLaser::init() { } void SpindleLaser::ocr_off() { + _set_ocr(0); #if PIN_EXISTS(SPINDLE_LASER_ENA) WRITE(SPINDLE_LASER_ENA_PIN, !SPINDLE_LASER_ACTIVE_STATE); // Cutter OFF #endif - _set_ocr(0); } #endif // SPINDLE_LASER_USE_PWM @@ -127,9 +146,8 @@ void SpindleLaser::init() { */ void SpindleLaser::apply_power(const uint8_t opwr) { if (enabled() || opwr == 0) { // 0 check allows us to disable where no ENA pin exists - // Test and set the last power used to improve performance + // Test the last power used to improve performance if (opwr == last_power_applied) return; - last_power_applied = opwr; // Handle PWM driven or just simple on/off #if ENABLED(SPINDLE_LASER_USE_PWM) if (CUTTER_UNIT_IS(RPM) && unitPower == 0) @@ -146,6 +164,7 @@ void SpindleLaser::apply_power(const uint8_t opwr) { WRITE(SPINDLE_LASER_ENA_PIN, enabled() ? SPINDLE_LASER_ACTIVE_STATE : !SPINDLE_LASER_ACTIVE_STATE); isReadyForUI = true; #endif + last_power_applied = opwr; } else { #if PIN_EXISTS(SPINDLE_LASER_ENA) diff --git a/Marlin/src/feature/spindle_laser.h b/Marlin/src/feature/spindle_laser.h index 681df2f081e7..8305c1fec463 100644 --- a/Marlin/src/feature/spindle_laser.h +++ b/Marlin/src/feature/spindle_laser.h @@ -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 { @@ -50,13 +54,13 @@ class SpindleLaser { public: static CutterMode cutter_mode; - static constexpr uint8_t pct_to_ocr(const_float_t pct) { return uint8_t(PCT_TO_PWM(pct)); } + static constexpr uint8_t pct_to_ocr(const float pct) { return uint8_t(PCT_TO_PWM(pct)); } // cpower = configured values (e.g., SPEED_POWER_MAX) // Convert configured power range to a percentage static constexpr cutter_cpower_t power_floor = TERN(CUTTER_POWER_RELATIVE, SPEED_POWER_MIN, 0); static constexpr uint8_t cpwr_to_pct(const cutter_cpower_t cpwr) { - return cpwr ? round(100.0f * (cpwr - power_floor) / (SPEED_POWER_MAX - power_floor)) : 0; + return cpwr ? LROUND(100.0f * (cpwr - power_floor) / (SPEED_POWER_MAX - power_floor)) : 0; } // Convert config defines from RPM to %, angle or PWM when in Spindle mode @@ -105,11 +109,14 @@ class SpindleLaser { static uint8_t power, last_power_applied; // Basic power state tracking - static cutter_frequency_t frequency; // Set PWM frequency; range: 2K-50K + static cutter_frequency_t frequency; // (Hz) Laser/Spindle PWM frequency (2000..50000) - static cutter_power_t menuPower, // Power as set via LCD menu in PWM, Percentage or RPM - unitPower; // Power as displayed status in PWM, Percentage or RPM + static cutter_power_t menuPower, // Power as set via LCD menu in PWM, Percentage, or RPM + unitPower; // Power as displayed status in PWM, Percentage, or RPM + #if HAS_SPINDLE_ACCELERATION + static uint32_t acceleration_spindle_deg_per_s2; // (°/s/s) Spindle acceleration + #endif static void init(); #if ENABLED(HAL_CAN_SET_PWM_FREQ) && SPINDLE_LASER_FREQUENCY @@ -157,7 +164,7 @@ class SpindleLaser { */ static cutter_power_t power_to_range(const cutter_power_t pwr, const uint8_t pwrUnit=_CUTTER_POWER(CUTTER_POWER_UNIT)) { static constexpr float - min_pct = TERN(CUTTER_POWER_RELATIVE, 0, TERN(SPINDLE_FEATURE, round(100.0f * (SPEED_POWER_MIN) / (SPEED_POWER_MAX)), SPEED_POWER_MIN)), + min_pct = TERN(CUTTER_POWER_RELATIVE, 0, TERN(SPINDLE_FEATURE, roundf(100.0f * (SPEED_POWER_MIN) / (SPEED_POWER_MAX)), SPEED_POWER_MIN)), max_pct = TERN(SPINDLE_FEATURE, 100, SPEED_POWER_MAX); if (pwr <= 0) return 0; cutter_power_t upwr; diff --git a/Marlin/src/feature/tmc_util.cpp b/Marlin/src/feature/tmc_util.cpp index 421f9b0c22e1..bd743c713641 100644 --- a/Marlin/src/feature/tmc_util.cpp +++ b/Marlin/src/feature/tmc_util.cpp @@ -24,14 +24,23 @@ #if HAS_TRINAMIC_CONFIG +/** + * feature/tmc_util.cpp - Functions for debugging Trinamic stepper drivers. + * The main entry point is `tmc_report_all` which is called by M122 to collect + * and report diagnostic information about each enabled TMC driver. + */ + #include "tmc_util.h" -#include "../MarlinCore.h" #include "../module/stepper/indirection.h" #include "../module/printcounter.h" #include "../libs/duration_t.h" #include "../gcode/gcode.h" +#if ENABLED(SOVOL_SV06_RTS) + #include "../lcd/sovol_rts/sovol_rts.h" +#endif + #if ENABLED(TMC_DEBUG) #include "../libs/hex_print.h" #if ENABLED(MONITOR_DRIVER_STATUS) @@ -39,6 +48,10 @@ #endif #endif +#if ENABLED(EDITABLE_HOMING_CURRENT) + homing_current_t homing_current_mA; +#endif + /** * Check for over temperature or short to ground error flags. * Report and log warning of overtemperature condition. @@ -64,7 +77,7 @@ #endif ; #if ENABLED(TMC_DEBUG) - #if HAS_TMCX1X0 || HAS_TMC220x + #if HAS_TMCX1X0_OR_2240 || HAS_TMC220x uint8_t cs_actual; #endif #if HAS_STALLGUARD @@ -134,6 +147,67 @@ #endif // HAS_TMCX1X0 + #if HAS_DRIVER(TMC2240) + + #if ENABLED(TMC_DEBUG) + static uint32_t get_pwm_scale(TMC2240Stepper &st) { return st.PWM_SCALE(); } + #endif + + static TMC_driver_data get_driver_data(TMC2240Stepper &st) { + constexpr uint8_t OT_bp = 25, OTPW_bp = 26; + constexpr uint32_t S2G_bm = 0x18000000; + #if ENABLED(TMC_DEBUG) + constexpr uint16_t SG_RESULT_bm = 0x3FF; // 0:9 + constexpr uint8_t STEALTH_bp = 14; + constexpr uint32_t CS_ACTUAL_bm = 0x1F0000; // 16:20 + constexpr uint8_t STALL_GUARD_bp = 24; + constexpr uint8_t STST_bp = 31; + #endif + TMC_driver_data data; + const auto ds = data.drv_status = st.DRV_STATUS(); + #ifdef __AVR__ + + // 8-bit optimization saves up to 70 bytes of PROGMEM per axis + uint8_t spart; + #if ENABLED(TMC_DEBUG) + data.sg_result = ds & SG_RESULT_bm; + spart = ds >> 8; + data.is_stealth = TEST(spart, STEALTH_bp - 8); + spart = ds >> 16; + data.cs_actual = spart & (CS_ACTUAL_bm >> 16); + #endif + spart = ds >> 24; + data.is_ot = TEST(spart, OT_bp - 24); + data.is_otpw = TEST(spart, OTPW_bp - 24); + data.is_s2g = !!(spart & (S2G_bm >> 24)); + #if ENABLED(TMC_DEBUG) + data.is_stall = TEST(spart, STALL_GUARD_bp - 24); + data.is_standstill = TEST(spart, STST_bp - 24); + data.sg_result_reasonable = !data.is_standstill; // sg_result has no reasonable meaning while standstill + #endif + + #else // !__AVR__ + + data.is_ot = TEST(ds, OT_bp); + data.is_otpw = TEST(ds, OTPW_bp); + data.is_s2g = !!(ds & S2G_bm); + #if ENABLED(TMC_DEBUG) + constexpr uint8_t CS_ACTUAL_sb = 16; + data.sg_result = ds & SG_RESULT_bm; + data.is_stealth = TEST(ds, STEALTH_bp); + data.cs_actual = (ds & CS_ACTUAL_bm) >> CS_ACTUAL_sb; + data.is_stall = TEST(ds, STALL_GUARD_bp); + data.is_standstill = TEST(ds, STST_bp); + data.sg_result_reasonable = !data.is_standstill; // sg_result has no reasonable meaning while standstill + #endif + + #endif // !__AVR__ + + return data; + } + + #endif // TMC2240 + #if HAS_TMC220x #if ENABLED(TMC_DEBUG) @@ -207,7 +281,8 @@ if (data.is_ot) SERIAL_ECHOLNPGM("overtemperature"); if (data.is_s2g) SERIAL_ECHOLNPGM("coil short circuit"); TERN_(TMC_DEBUG, tmc_report_all()); - kill(F("Driver error")); + TERN_(SOVOL_SV06_RTS, rts.gotoPage(ID_DriverError_L, ID_DriverError_D)); + marlin.kill(F("Driver error")); } #endif @@ -222,13 +297,14 @@ SString<50>(F(" driver overtemperature warning! ("), st.getMilliamps(), F("mA)")).echoln(); } - template - void report_polled_driver_data(TMC &st, const TMC_driver_data &data) { - const uint32_t pwm_scale = get_pwm_scale(st); - st.printLabel(); - SString<60> report(':', pwm_scale); - #if ENABLED(TMC_DEBUG) - #if HAS_TMCX1X0 || HAS_TMC220x + #if ENABLED(TMC_DEBUG) + + template + void report_polled_driver_data(TMC &st, const TMC_driver_data &data) { + const uint32_t pwm_scale = get_pwm_scale(st); + st.printLabel(); + SString<60> report(':', pwm_scale); + #if HAS_TMCX1X0_OR_2240 || HAS_TMC220x report.append('/', data.cs_actual); #endif #if HAS_STALLGUARD @@ -238,22 +314,21 @@ else report += '-'; #endif - #endif - report += '|'; - if (st.error_count) report += 'E'; // Error - if (data.is_ot) report += 'O'; // Over-temperature - if (data.is_otpw) report += 'W'; // over-temperature pre-Warning - #if ENABLED(TMC_DEBUG) + report += '|'; + if (st.error_count) report += 'E'; // Error + if (data.is_ot) report += 'O'; // Over-temperature + if (data.is_otpw) report += 'W'; // over-temperature pre-Warning if (data.is_stall) report += 'G'; // stallGuard if (data.is_stealth) report += 'T'; // stealthChop if (data.is_standstill) report += 'I'; // standstIll - #endif - if (st.flag_otpw) report += 'F'; // otpw Flag - report += '|'; - if (st.otpw_count > 0) report += st.otpw_count; - report += '\t'; - report.echo(); - } + if (st.flag_otpw) report += 'F'; // otpw Flag + report += '|'; + if (st.otpw_count > 0) report += st.otpw_count; + report += '\t'; + report.echo(); + } + + #endif // TMC_DEBUG #if CURRENT_STEP_DOWN > 0 @@ -313,9 +388,9 @@ else if (st.otpw_count > 0) st.otpw_count = 0; } - #if ENABLED(TMC_DEBUG) - if (need_debug_reporting) report_polled_driver_data(st, data); - #endif + if (need_debug_reporting) { + TERN_(TMC_DEBUG, report_polled_driver_data(st, data)); + } return should_step_down; } @@ -339,127 +414,70 @@ if (need_update_error_counters || need_debug_reporting) { - #if AXIS_IS_TMC(X) || AXIS_IS_TMC(X2) - { - bool result = false; - #if AXIS_IS_TMC(X) - if (monitor_tmc_driver(stepperX, need_update_error_counters, need_debug_reporting)) result = true; - #endif - #if AXIS_IS_TMC(X2) - if (monitor_tmc_driver(stepperX2, need_update_error_counters, need_debug_reporting)) result = true; - #endif - if (result) { - #if AXIS_IS_TMC(X) - step_current_down(stepperX); - #endif - #if AXIS_IS_TMC(X2) - step_current_down(stepperX2); - #endif + #if X_IS_TRINAMIC || X2_IS_TRINAMIC + if ( TERN0(X_IS_TRINAMIC, monitor_tmc_driver(stepperX, need_update_error_counters, need_debug_reporting)) + || TERN0(X2_IS_TRINAMIC, monitor_tmc_driver(stepperX2, need_update_error_counters, need_debug_reporting)) + ) { + TERN_(X_IS_TRINAMIC, step_current_down(stepperX)); + TERN_(X2_IS_TRINAMIC, step_current_down(stepperX2)); } - } #endif - #if AXIS_IS_TMC(Y) || AXIS_IS_TMC(Y2) - { - bool result = false; - #if AXIS_IS_TMC(Y) - if (monitor_tmc_driver(stepperY, need_update_error_counters, need_debug_reporting)) result = true; - #endif - #if AXIS_IS_TMC(Y2) - if (monitor_tmc_driver(stepperY2, need_update_error_counters, need_debug_reporting)) result = true; - #endif - if (result) { - #if AXIS_IS_TMC(Y) - step_current_down(stepperY); - #endif - #if AXIS_IS_TMC(Y2) - step_current_down(stepperY2); - #endif + #if Y_IS_TRINAMIC || Y2_IS_TRINAMIC + if ( TERN0(Y_IS_TRINAMIC, monitor_tmc_driver(stepperY, need_update_error_counters, need_debug_reporting)) + || TERN0(Y2_IS_TRINAMIC, monitor_tmc_driver(stepperY2, need_update_error_counters, need_debug_reporting)) + ) { + TERN_(Y_IS_TRINAMIC, step_current_down(stepperY)); + TERN_(Y2_IS_TRINAMIC, step_current_down(stepperY2)); } - } #endif - #if AXIS_IS_TMC(Z) || AXIS_IS_TMC(Z2) || AXIS_IS_TMC(Z3) || AXIS_IS_TMC(Z4) - { - bool result = false; - #if AXIS_IS_TMC(Z) - if (monitor_tmc_driver(stepperZ, need_update_error_counters, need_debug_reporting)) result = true; - #endif - #if AXIS_IS_TMC(Z2) - if (monitor_tmc_driver(stepperZ2, need_update_error_counters, need_debug_reporting)) result = true; - #endif - #if AXIS_IS_TMC(Z3) - if (monitor_tmc_driver(stepperZ3, need_update_error_counters, need_debug_reporting)) result = true; - #endif - #if AXIS_IS_TMC(Z4) - if (monitor_tmc_driver(stepperZ4, need_update_error_counters, need_debug_reporting)) result = true; - #endif - if (result) { - #if AXIS_IS_TMC(Z) - step_current_down(stepperZ); - #endif - #if AXIS_IS_TMC(Z2) - step_current_down(stepperZ2); - #endif - #if AXIS_IS_TMC(Z3) - step_current_down(stepperZ3); - #endif - #if AXIS_IS_TMC(Z4) - step_current_down(stepperZ4); - #endif + #if ANY(Z_IS_TRINAMIC, Z2_IS_TRINAMIC, Z3_IS_TRINAMIC, Z4_IS_TRINAMIC) + if ( TERN0(Z_IS_TRINAMIC, monitor_tmc_driver(stepperZ, need_update_error_counters, need_debug_reporting)) + || TERN0(Z2_IS_TRINAMIC, monitor_tmc_driver(stepperZ2, need_update_error_counters, need_debug_reporting)) + || TERN0(Z3_IS_TRINAMIC, monitor_tmc_driver(stepperZ3, need_update_error_counters, need_debug_reporting)) + || TERN0(Z4_IS_TRINAMIC, monitor_tmc_driver(stepperZ4, need_update_error_counters, need_debug_reporting)) + ) { + TERN_(Z_IS_TRINAMIC, step_current_down(stepperZ)); + TERN_(Z2_IS_TRINAMIC, step_current_down(stepperZ2)); + TERN_(Z3_IS_TRINAMIC, step_current_down(stepperZ3)); + TERN_(Z4_IS_TRINAMIC, step_current_down(stepperZ4)); } - } #endif - #if AXIS_IS_TMC(I) + #if I_IS_TRINAMIC if (monitor_tmc_driver(stepperI, need_update_error_counters, need_debug_reporting)) step_current_down(stepperI); #endif - #if AXIS_IS_TMC(J) + #if J_IS_TRINAMIC if (monitor_tmc_driver(stepperJ, need_update_error_counters, need_debug_reporting)) step_current_down(stepperJ); #endif - #if AXIS_IS_TMC(K) + #if K_IS_TRINAMIC if (monitor_tmc_driver(stepperK, need_update_error_counters, need_debug_reporting)) step_current_down(stepperK); #endif - #if AXIS_IS_TMC(U) + #if U_IS_TRINAMIC if (monitor_tmc_driver(stepperU, need_update_error_counters, need_debug_reporting)) step_current_down(stepperU); #endif - #if AXIS_IS_TMC(V) + #if V_IS_TRINAMIC if (monitor_tmc_driver(stepperV, need_update_error_counters, need_debug_reporting)) step_current_down(stepperV); #endif - #if AXIS_IS_TMC(W) + #if W_IS_TRINAMIC if (monitor_tmc_driver(stepperW, need_update_error_counters, need_debug_reporting)) step_current_down(stepperW); #endif - #if AXIS_IS_TMC(E0) - (void)monitor_tmc_driver(stepperE0, need_update_error_counters, need_debug_reporting); - #endif - #if AXIS_IS_TMC(E1) - (void)monitor_tmc_driver(stepperE1, need_update_error_counters, need_debug_reporting); - #endif - #if AXIS_IS_TMC(E2) - (void)monitor_tmc_driver(stepperE2, need_update_error_counters, need_debug_reporting); - #endif - #if AXIS_IS_TMC(E3) - (void)monitor_tmc_driver(stepperE3, need_update_error_counters, need_debug_reporting); - #endif - #if AXIS_IS_TMC(E4) - (void)monitor_tmc_driver(stepperE4, need_update_error_counters, need_debug_reporting); - #endif - #if AXIS_IS_TMC(E5) - (void)monitor_tmc_driver(stepperE5, need_update_error_counters, need_debug_reporting); - #endif - #if AXIS_IS_TMC(E6) - (void)monitor_tmc_driver(stepperE6, need_update_error_counters, need_debug_reporting); - #endif - #if AXIS_IS_TMC(E7) - (void)monitor_tmc_driver(stepperE7, need_update_error_counters, need_debug_reporting); - #endif + TERN_(E0_IS_TRINAMIC, (void)monitor_tmc_driver(stepperE0, need_update_error_counters, need_debug_reporting)); + TERN_(E1_IS_TRINAMIC, (void)monitor_tmc_driver(stepperE1, need_update_error_counters, need_debug_reporting)); + TERN_(E2_IS_TRINAMIC, (void)monitor_tmc_driver(stepperE2, need_update_error_counters, need_debug_reporting)); + TERN_(E3_IS_TRINAMIC, (void)monitor_tmc_driver(stepperE3, need_update_error_counters, need_debug_reporting)); + TERN_(E4_IS_TRINAMIC, (void)monitor_tmc_driver(stepperE4, need_update_error_counters, need_debug_reporting)); + TERN_(E5_IS_TRINAMIC, (void)monitor_tmc_driver(stepperE5, need_update_error_counters, need_debug_reporting)); + TERN_(E6_IS_TRINAMIC, (void)monitor_tmc_driver(stepperE6, need_update_error_counters, need_debug_reporting)); + TERN_(E7_IS_TRINAMIC, (void)monitor_tmc_driver(stepperE7, need_update_error_counters, need_debug_reporting)); if (TERN0(TMC_DEBUG, need_debug_reporting)) SERIAL_EOL(); } @@ -505,7 +523,7 @@ TMC_TSTEP, TMC_TPWMTHRS, TMC_TPWMTHRS_MMS, - TMC_OTPW, + TMC_DEBUG_OTPW, TMC_OTPW_TRIGGERED, TMC_TOFF, TMC_TBL, @@ -513,7 +531,12 @@ TMC_HSTRT, TMC_SGT, TMC_MSCNT, - TMC_INTERPOLATE + TMC_INTERPOLATE, + TMC_VAIN, + TMC_VSUPPLY, + TMC_TEMP, + TMC_OVERTEMP, + TMC_OVERVOLT_THD }; enum TMC_drv_status_enum : char { TMC_DRV_CODES, @@ -557,24 +580,49 @@ TMC_GET_DRVCTRL, TMC_GET_DRVSTATUS, TMC_GET_SGCSCONF, - TMC_GET_SMARTEN + TMC_GET_SMARTEN, + TMC_GET_SG4_THRS, + TMC_GET_SG4_RESULT }; template static void print_vsense(TMC &st) { SERIAL_ECHO(st.vsense() ? F("1=.18") : F("0=.325")); } + #if HAS_DRIVER(TMC2160) + template + void print_vsense(TMCMarlin &) { } + #endif + #if HAS_DRIVER(TMC5160) + template + void print_vsense(TMCMarlin &) { } + #endif + #if HAS_DRIVER(TMC2240) + template + void print_vsense(TMCMarlin &) { } + #endif + + template + void print_cs_actual(TMC &st) { SERIAL_ECHO(st.cs_actual(), F("/31")); } + #if HAS_DRIVER(TMC2240) + template + void print_cs_actual(TMCMarlin &) { } + #endif + + static void print_true_or_false(const bool tf) { SERIAL_ECHO(TRUE_FALSE(tf)); } #if HAS_DRIVER(TMC2130) || HAS_DRIVER(TMC5130) + // Additional tmc_status fields for 2130/5130 and related drivers static void _tmc_status(TMC2130Stepper &st, const TMC_debug_enum i) { switch (i) { case TMC_PWM_SCALE: SERIAL_ECHO(st.PWM_SCALE()); break; case TMC_SGT: SERIAL_ECHO(st.sgt()); break; - case TMC_STEALTHCHOP: serialprint_truefalse(st.en_pwm_mode()); break; - case TMC_INTERPOLATE: serialprint_truefalse(st.intpol()); break; + case TMC_STEALTHCHOP: print_true_or_false(st.en_pwm_mode()); break; + case TMC_INTERPOLATE: print_true_or_false(st.intpol()); break; default: break; } } #endif #if HAS_TMCX1X0 + // Additional tmc_parse_drv_status fields for 2130 and related drivers static void _tmc_parse_drv_status(TMC2130Stepper &st, const TMC_drv_status_enum i) { switch (i) { case TMC_STALLGUARD: if (st.stallguard()) SERIAL_CHAR('*'); break; @@ -587,57 +635,56 @@ #endif #if HAS_DRIVER(TMC2160) || HAS_DRIVER(TMC5160) - template - void print_vsense(TMCMarlin &) { } - - template - void print_vsense(TMCMarlin &) { } - + // Additional tmc_status fields for 2160/5160 and related drivers static void _tmc_status(TMC2160Stepper &st, const TMC_debug_enum i) { switch (i) { case TMC_PWM_SCALE: SERIAL_ECHO(st.PWM_SCALE()); break; case TMC_SGT: SERIAL_ECHO(st.sgt()); break; - case TMC_STEALTHCHOP: serialprint_truefalse(st.en_pwm_mode()); break; - case TMC_GLOBAL_SCALER: - { - const uint16_t value = st.GLOBAL_SCALER(); - SERIAL_ECHO(value ?: 256); - SERIAL_ECHOPGM("/256"); - } - break; - case TMC_INTERPOLATE: serialprint_truefalse(st.intpol()); break; + case TMC_STEALTHCHOP: print_true_or_false(st.en_pwm_mode()); break; + case TMC_GLOBAL_SCALER: { + const uint16_t value = st.GLOBAL_SCALER(); + SERIAL_ECHO(value ?: 256); + SERIAL_ECHOPGM("/256"); + } break; + case TMC_INTERPOLATE: print_true_or_false(st.intpol()); break; default: break; } } - #endif + #endif // TMC2160 || TMC5160 #if HAS_TMC220x + + // Additional tmc_status fields for 2208/2224/2209 drivers static void _tmc_status(TMC2208Stepper &st, const TMC_debug_enum i) { switch (i) { + // PWM_SCALE case TMC_PWM_SCALE_SUM: SERIAL_ECHO(st.pwm_scale_sum()); break; case TMC_PWM_SCALE_AUTO: SERIAL_ECHO(st.pwm_scale_auto()); break; + // PWM_AUTO case TMC_PWM_OFS_AUTO: SERIAL_ECHO(st.pwm_ofs_auto()); break; case TMC_PWM_GRAD_AUTO: SERIAL_ECHO(st.pwm_grad_auto()); break; - case TMC_STEALTHCHOP: serialprint_truefalse(st.stealth()); break; - case TMC_INTERPOLATE: serialprint_truefalse(st.intpol()); break; + // CHOPCONF + case TMC_STEALTHCHOP: print_true_or_false(st.stealth()); break; + case TMC_INTERPOLATE: print_true_or_false(st.intpol()); break; default: break; } } #if HAS_DRIVER(TMC2209) + // Additional tmc_status fields for 2209 drivers template static void _tmc_status(TMCMarlin &st, const TMC_debug_enum i) { switch (i) { case TMC_SGT: SERIAL_ECHO(st.SGTHRS()); break; case TMC_UART_ADDR: SERIAL_ECHO(st.get_address()); break; default: - TMC2208Stepper *parent = &st; - _tmc_status(*parent, i); + _tmc_status(static_cast(st), i); break; } } #endif + // Additional tmc_parse_drv_status fields for 2208/2224/2209 drivers static void _tmc_parse_drv_status(TMC2208Stepper &st, const TMC_drv_status_enum i) { switch (i) { case TMC_T157: if (st.t157()) SERIAL_CHAR('*'); break; @@ -652,52 +699,99 @@ } #if HAS_DRIVER(TMC2209) + // Additional tmc_parse_drv_status fields for 2209 drivers static void _tmc_parse_drv_status(TMC2209Stepper &st, const TMC_drv_status_enum i) { switch (i) { case TMC_SG_RESULT: SERIAL_ECHO(st.SG_RESULT()); break; - default: _tmc_parse_drv_status(static_cast(st), i); break; + default: + _tmc_parse_drv_status(static_cast(st), i); + break; } } #endif - #endif + + #endif // HAS_TMC220x + + #if HAS_DRIVER(TMC2240) + + // Additional tmc_parse_drv_status fields for 2240 drivers + static void _tmc_parse_drv_status(TMC2240Stepper &st, const TMC_drv_status_enum i) { + switch (i) { + case TMC_S2VSA: if (st.s2vsa()) SERIAL_CHAR('*'); break; + case TMC_S2VSB: if (st.s2vsb()) SERIAL_CHAR('*'); break; + case TMC_STEALTHCHOP: print_true_or_false(st.stealth()); break; + case TMC_FSACTIVE: if (st.fsactive()) SERIAL_CHAR('*'); break; + case TMC_DRV_CS_ACTUAL: if (st.CS_ACTUAL()) SERIAL_CHAR('*'); break; + case TMC_STALLGUARD: if (st.stallguard()) SERIAL_CHAR('*'); break; + case TMC_OT: if (st.ot()) SERIAL_CHAR('*'); break; + case TMC_SG_RESULT: SERIAL_ECHO(st.SG_RESULT()); break; + default: break; // other... + } + } + + // Additional tmc_status fields for 2240 drivers + static void _tmc_status(TMC2240Stepper &st, const TMC_debug_enum i) { + switch (i) { + // PWM_SCALE + case TMC_PWM_SCALE_SUM: SERIAL_ECHO(st.pwm_scale_sum()); break; + case TMC_PWM_SCALE_AUTO: SERIAL_ECHO(st.pwm_scale_auto()); break; + // PWM_AUTO + case TMC_PWM_OFS_AUTO: SERIAL_ECHO(st.pwm_ofs_auto()); break; + case TMC_PWM_GRAD_AUTO: SERIAL_ECHO(st.pwm_grad_auto()); break; + // CHOPCONF + case TMC_STEALTHCHOP: print_true_or_false(st.stealth()); break; + case TMC_INTERPOLATE: print_true_or_false(st.intpol()); break; + case TMC_VAIN: SERIAL_ECHO(st.get_ain_voltage()); break; + case TMC_VSUPPLY: SERIAL_ECHO(st.get_vsupply_voltage()); break; + case TMC_TEMP: SERIAL_ECHO(st.get_chip_temperature()); break; + case TMC_OVERTEMP: SERIAL_ECHO(st.get_overtemp_prewarn_celsius()); break; + case TMC_OVERVOLT_THD: SERIAL_ECHO(st.get_overvoltage_threshold_voltage()); break; + default: break; + } + } + + #endif // TMC2240 #if HAS_DRIVER(TMC2660) static void _tmc_parse_drv_status(TMC2660Stepper, const TMC_drv_status_enum) { } static void _tmc_status(TMC2660Stepper &st, const TMC_debug_enum i) { switch (i) { - case TMC_INTERPOLATE: serialprint_truefalse(st.intpol()); break; + case TMC_INTERPOLATE: print_true_or_false(st.intpol()); break; default: break; } } #endif + template + void print_tstep(TMC &st) { + const uint32_t tstep_value = st.TSTEP(); + if (tstep_value != 0xFFFFF) + SERIAL_ECHO(tstep_value); + else + SERIAL_ECHOPGM("max"); + } + void print_tstep(TMC2660Stepper &st) { } + + template + void print_blank_time(TMC &st) { SERIAL_ECHO(st.blank_time()); } + template + void print_blank_time(TMCMarlin &) { } + template static void tmc_status(TMC &st, const TMC_debug_enum i) { SERIAL_CHAR('\t'); switch (i) { case TMC_CODES: st.printLabel(); break; - case TMC_ENABLED: serialprint_truefalse(st.isEnabled()); break; + case TMC_ENABLED: print_true_or_false(st.isEnabled()); break; case TMC_CURRENT: SERIAL_ECHO(st.getMilliamps()); break; case TMC_RMS_CURRENT: SERIAL_ECHO(st.rms_current()); break; - case TMC_MAX_CURRENT: SERIAL_ECHO(p_float_t(st.rms_current() * 1.41, 0)); break; - case TMC_IRUN: - SERIAL_ECHO(st.irun()); - SERIAL_ECHOPGM("/31"); - break; - case TMC_IHOLD: - SERIAL_ECHO(st.ihold()); - SERIAL_ECHOPGM("/31"); - break; - case TMC_CS_ACTUAL: - SERIAL_ECHO(st.cs_actual()); - SERIAL_ECHOPGM("/31"); - break; + case TMC_MAX_CURRENT: SERIAL_ECHO(int(st.rms_current() * 1.41)); break; + case TMC_IRUN: SERIAL_ECHO(st.irun()); SERIAL_ECHOPGM("/31"); break; + case TMC_IHOLD: SERIAL_ECHO(st.ihold()); SERIAL_ECHOPGM("/31"); break; + case TMC_CS_ACTUAL: print_cs_actual(st); break; case TMC_VSENSE: print_vsense(st); break; case TMC_MICROSTEPS: SERIAL_ECHO(st.microsteps()); break; - case TMC_TSTEP: { - const uint32_t tstep_value = st.TSTEP(); - if (tstep_value != 0xFFFFF) SERIAL_ECHO(tstep_value); else SERIAL_ECHOPGM("max"); - } break; + case TMC_TSTEP: print_tstep(st); break; #if ENABLED(HYBRID_THRESHOLD) case TMC_TPWMTHRS: SERIAL_ECHO(uint32_t(st.TPWMTHRS())); break; case TMC_TPWMTHRS_MMS: { @@ -705,12 +799,12 @@ if (tpwmthrs_val) SERIAL_ECHO(tpwmthrs_val); else SERIAL_CHAR('-'); } break; #endif - case TMC_OTPW: serialprint_truefalse(st.otpw()); break; + case TMC_DEBUG_OTPW: print_true_or_false(st.otpw()); break; #if ENABLED(MONITOR_DRIVER_STATUS) - case TMC_OTPW_TRIGGERED: serialprint_truefalse(st.getOTPW()); break; + case TMC_OTPW_TRIGGERED: print_true_or_false(st.getOTPW()); break; #endif case TMC_TOFF: SERIAL_ECHO(st.toff()); break; - case TMC_TBL: SERIAL_ECHO(st.blank_time()); break; + case TMC_TBL: print_blank_time(st); break; case TMC_HEND: SERIAL_ECHO(st.hysteresis_end()); break; case TMC_HSTRT: SERIAL_ECHO(st.hysteresis_start()); break; case TMC_MSCNT: SERIAL_ECHO(st.get_microstep_counter()); break; @@ -724,132 +818,82 @@ SERIAL_CHAR('\t'); switch (i) { case TMC_CODES: st.printLabel(); break; - case TMC_ENABLED: serialprint_truefalse(st.isEnabled()); break; + case TMC_ENABLED: print_true_or_false(st.isEnabled()); break; case TMC_CURRENT: SERIAL_ECHO(st.getMilliamps()); break; case TMC_RMS_CURRENT: SERIAL_ECHO(st.rms_current()); break; - case TMC_MAX_CURRENT: SERIAL_ECHO(p_float_t(st.rms_current() * 1.41, 0)); break; - case TMC_IRUN: - SERIAL_ECHO(st.cs()); - SERIAL_ECHOPGM("/31"); - break; + case TMC_MAX_CURRENT: SERIAL_ECHO(int(st.rms_current() * 1.41)); break; + case TMC_IRUN: SERIAL_ECHO(st.cs()); SERIAL_ECHOPGM("/31"); break; case TMC_VSENSE: SERIAL_ECHO(st.vsense() ? F("1=.165") : F("0=.310")); break; case TMC_MICROSTEPS: SERIAL_ECHO(st.microsteps()); break; - //case TMC_OTPW: serialprint_truefalse(st.otpw()); break; - //case TMC_OTPW_TRIGGERED: serialprint_truefalse(st.getOTPW()); break; + //case TMC_DEBUG_OTPW: print_true_or_false(st.otpw()); break; + //case TMC_OTPW_TRIGGERED: print_true_or_false(st.getOTPW()); break; case TMC_SGT: SERIAL_ECHO(st.sgt()); break; case TMC_TOFF: SERIAL_ECHO(st.toff()); break; - case TMC_TBL: SERIAL_ECHO(st.blank_time()); break; + case TMC_TBL: print_blank_time(st); break; case TMC_HEND: SERIAL_ECHO(st.hysteresis_end()); break; case TMC_HSTRT: SERIAL_ECHO(st.hysteresis_start()); break; - default: break; + default: _tmc_status(st, i); break; } } - #endif + #endif // TMC2660 template static void tmc_parse_drv_status(TMC &st, const TMC_drv_status_enum i) { SERIAL_CHAR('\t'); switch (i) { - case TMC_DRV_CODES: st.printLabel(); break; - case TMC_STST: if (!st.stst()) SERIAL_CHAR('*'); break; - case TMC_OLB: if (st.olb()) SERIAL_CHAR('*'); break; - case TMC_OLA: if (st.ola()) SERIAL_CHAR('*'); break; - case TMC_S2GB: if (st.s2gb()) SERIAL_CHAR('*'); break; - case TMC_S2GA: if (st.s2ga()) SERIAL_CHAR('*'); break; - case TMC_DRV_OTPW: if (st.otpw()) SERIAL_CHAR('*'); break; - case TMC_OT: if (st.ot()) SERIAL_CHAR('*'); break; + case TMC_DRV_CODES: st.printLabel(); break; + case TMC_STST: if (!st.stst()) SERIAL_CHAR('*'); break; + case TMC_OLB: if (st.olb()) SERIAL_CHAR('*'); break; + case TMC_OLA: if (st.ola()) SERIAL_CHAR('*'); break; + case TMC_S2GB: if (st.s2gb()) SERIAL_CHAR('*'); break; + case TMC_S2GA: if (st.s2ga()) SERIAL_CHAR('*'); break; + case TMC_DRV_OTPW: if (st.otpw()) SERIAL_CHAR('*'); break; + case TMC_OT: if (st.ot()) SERIAL_CHAR('*'); break; case TMC_DRV_STATUS_HEX: { const uint32_t drv_status = st.DRV_STATUS(); - SERIAL_CHAR('\t'); - st.printLabel(); - SERIAL_CHAR('\t'); - print_hex_long(drv_status, ':', true); + SERIAL_CHAR('\t'); st.printLabel(); + SERIAL_CHAR('\t'); print_hex_long(drv_status, ':', true); if (drv_status == 0xFFFFFFFF || drv_status == 0) SERIAL_ECHOPGM("\t Bad response!"); SERIAL_EOL(); - break; - } + } break; default: _tmc_parse_drv_status(st, i); break; } } static void tmc_debug_loop(const TMC_debug_enum n OPTARGS_LOGICAL(const bool)) { if (TERN0(HAS_X_AXIS, x)) { - #if AXIS_IS_TMC(X) - tmc_status(stepperX, n); - #endif - #if AXIS_IS_TMC(X2) - tmc_status(stepperX2, n); - #endif + TERN_(X_IS_TRINAMIC, tmc_status(stepperX, n)); + TERN_(X2_IS_TRINAMIC, tmc_status(stepperX2, n)); } if (TERN0(HAS_Y_AXIS, y)) { - #if AXIS_IS_TMC(Y) - tmc_status(stepperY, n); - #endif - #if AXIS_IS_TMC(Y2) - tmc_status(stepperY2, n); - #endif + TERN_(Y_IS_TRINAMIC, tmc_status(stepperY, n)); + TERN_(Y2_IS_TRINAMIC, tmc_status(stepperY2, n)); } if (TERN0(HAS_Z_AXIS, z)) { - #if AXIS_IS_TMC(Z) - tmc_status(stepperZ, n); - #endif - #if AXIS_IS_TMC(Z2) - tmc_status(stepperZ2, n); - #endif - #if AXIS_IS_TMC(Z3) - tmc_status(stepperZ3, n); - #endif - #if AXIS_IS_TMC(Z4) - tmc_status(stepperZ4, n); - #endif + TERN_(Z_IS_TRINAMIC, tmc_status(stepperZ, n)); + TERN_(Z2_IS_TRINAMIC, tmc_status(stepperZ2, n)); + TERN_(Z3_IS_TRINAMIC, tmc_status(stepperZ3, n)); + TERN_(Z4_IS_TRINAMIC, tmc_status(stepperZ4, n)); } - #if AXIS_IS_TMC(I) - if (i) tmc_status(stepperI, n); - #endif - #if AXIS_IS_TMC(J) - if (j) tmc_status(stepperJ, n); - #endif - #if AXIS_IS_TMC(K) - if (k) tmc_status(stepperK, n); - #endif - #if AXIS_IS_TMC(U) - if (u) tmc_status(stepperU, n); - #endif - #if AXIS_IS_TMC(V) - if (v) tmc_status(stepperV, n); - #endif - #if AXIS_IS_TMC(W) - if (w) tmc_status(stepperW, n); - #endif + TERN_(I_IS_TRINAMIC, if (i) tmc_status(stepperI, n)); + TERN_(J_IS_TRINAMIC, if (j) tmc_status(stepperJ, n)); + TERN_(K_IS_TRINAMIC, if (k) tmc_status(stepperK, n)); + TERN_(U_IS_TRINAMIC, if (u) tmc_status(stepperU, n)); + TERN_(V_IS_TRINAMIC, if (v) tmc_status(stepperV, n)); + TERN_(W_IS_TRINAMIC, if (w) tmc_status(stepperW, n)); if (TERN0(HAS_EXTRUDERS, e)) { - #if AXIS_IS_TMC(E0) - tmc_status(stepperE0, n); - #endif - #if AXIS_IS_TMC(E1) - tmc_status(stepperE1, n); - #endif - #if AXIS_IS_TMC(E2) - tmc_status(stepperE2, n); - #endif - #if AXIS_IS_TMC(E3) - tmc_status(stepperE3, n); - #endif - #if AXIS_IS_TMC(E4) - tmc_status(stepperE4, n); - #endif - #if AXIS_IS_TMC(E5) - tmc_status(stepperE5, n); - #endif - #if AXIS_IS_TMC(E6) - tmc_status(stepperE6, n); - #endif - #if AXIS_IS_TMC(E7) - tmc_status(stepperE7, n); - #endif + TERN_(E0_IS_TRINAMIC, tmc_status(stepperE0, n)); + TERN_(E1_IS_TRINAMIC, tmc_status(stepperE1, n)); + TERN_(E2_IS_TRINAMIC, tmc_status(stepperE2, n)); + TERN_(E3_IS_TRINAMIC, tmc_status(stepperE3, n)); + TERN_(E4_IS_TRINAMIC, tmc_status(stepperE4, n)); + TERN_(E5_IS_TRINAMIC, tmc_status(stepperE5, n)); + TERN_(E6_IS_TRINAMIC, tmc_status(stepperE6, n)); + TERN_(E7_IS_TRINAMIC, tmc_status(stepperE7, n)); } SERIAL_EOL(); @@ -857,82 +901,38 @@ static void drv_status_loop(const TMC_drv_status_enum n OPTARGS_LOGICAL(const bool)) { if (TERN0(HAS_X_AXIS, x)) { - #if AXIS_IS_TMC(X) - tmc_parse_drv_status(stepperX, n); - #endif - #if AXIS_IS_TMC(X2) - tmc_parse_drv_status(stepperX2, n); - #endif + TERN_(X_IS_TRINAMIC, tmc_parse_drv_status(stepperX, n)); + TERN_(X2_IS_TRINAMIC, tmc_parse_drv_status(stepperX2, n)); } if (TERN0(HAS_Y_AXIS, y)) { - #if AXIS_IS_TMC(Y) - tmc_parse_drv_status(stepperY, n); - #endif - #if AXIS_IS_TMC(Y2) - tmc_parse_drv_status(stepperY2, n); - #endif + TERN_(Y_IS_TRINAMIC, tmc_parse_drv_status(stepperY, n)); + TERN_(Y2_IS_TRINAMIC, tmc_parse_drv_status(stepperY2, n)); } if (TERN0(HAS_Z_AXIS, z)) { - #if AXIS_IS_TMC(Z) - tmc_parse_drv_status(stepperZ, n); - #endif - #if AXIS_IS_TMC(Z2) - tmc_parse_drv_status(stepperZ2, n); - #endif - #if AXIS_IS_TMC(Z3) - tmc_parse_drv_status(stepperZ3, n); - #endif - #if AXIS_IS_TMC(Z4) - tmc_parse_drv_status(stepperZ4, n); - #endif + TERN_(Z_IS_TRINAMIC, tmc_parse_drv_status(stepperZ, n)); + TERN_(Z2_IS_TRINAMIC, tmc_parse_drv_status(stepperZ2, n)); + TERN_(Z3_IS_TRINAMIC, tmc_parse_drv_status(stepperZ3, n)); + TERN_(Z4_IS_TRINAMIC, tmc_parse_drv_status(stepperZ4, n)); } - #if AXIS_IS_TMC(I) - if (i) tmc_parse_drv_status(stepperI, n); - #endif - #if AXIS_IS_TMC(J) - if (j) tmc_parse_drv_status(stepperJ, n); - #endif - #if AXIS_IS_TMC(K) - if (k) tmc_parse_drv_status(stepperK, n); - #endif - #if AXIS_IS_TMC(U) - if (u) tmc_parse_drv_status(stepperU, n); - #endif - #if AXIS_IS_TMC(V) - if (v) tmc_parse_drv_status(stepperV, n); - #endif - #if AXIS_IS_TMC(W) - if (w) tmc_parse_drv_status(stepperW, n); - #endif + TERN_(I_IS_TRINAMIC, if (i) tmc_parse_drv_status(stepperI, n)); + TERN_(J_IS_TRINAMIC, if (j) tmc_parse_drv_status(stepperJ, n)); + TERN_(K_IS_TRINAMIC, if (k) tmc_parse_drv_status(stepperK, n)); + TERN_(U_IS_TRINAMIC, if (u) tmc_parse_drv_status(stepperU, n)); + TERN_(V_IS_TRINAMIC, if (v) tmc_parse_drv_status(stepperV, n)); + TERN_(W_IS_TRINAMIC, if (w) tmc_parse_drv_status(stepperW, n)); if (TERN0(HAS_EXTRUDERS, e)) { - #if AXIS_IS_TMC(E0) - tmc_parse_drv_status(stepperE0, n); - #endif - #if AXIS_IS_TMC(E1) - tmc_parse_drv_status(stepperE1, n); - #endif - #if AXIS_IS_TMC(E2) - tmc_parse_drv_status(stepperE2, n); - #endif - #if AXIS_IS_TMC(E3) - tmc_parse_drv_status(stepperE3, n); - #endif - #if AXIS_IS_TMC(E4) - tmc_parse_drv_status(stepperE4, n); - #endif - #if AXIS_IS_TMC(E5) - tmc_parse_drv_status(stepperE5, n); - #endif - #if AXIS_IS_TMC(E6) - tmc_parse_drv_status(stepperE6, n); - #endif - #if AXIS_IS_TMC(E7) - tmc_parse_drv_status(stepperE7, n); - #endif + TERN_(E0_IS_TRINAMIC, tmc_parse_drv_status(stepperE0, n)); + TERN_(E1_IS_TRINAMIC, tmc_parse_drv_status(stepperE1, n)); + TERN_(E2_IS_TRINAMIC, tmc_parse_drv_status(stepperE2, n)); + TERN_(E3_IS_TRINAMIC, tmc_parse_drv_status(stepperE3, n)); + TERN_(E4_IS_TRINAMIC, tmc_parse_drv_status(stepperE4, n)); + TERN_(E5_IS_TRINAMIC, tmc_parse_drv_status(stepperE5, n)); + TERN_(E6_IS_TRINAMIC, tmc_parse_drv_status(stepperE6, n)); + TERN_(E7_IS_TRINAMIC, tmc_parse_drv_status(stepperE7, n)); } SERIAL_EOL(); @@ -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) @@ -970,16 +970,16 @@ TMC_REPORT("tstep\t", TMC_TSTEP); TMC_REPORT("PWM thresh.", TMC_TPWMTHRS); TMC_REPORT("[mm/s]\t", TMC_TPWMTHRS_MMS); - TMC_REPORT("OT prewarn", TMC_OTPW); + TMC_REPORT("OT prewarn", TMC_DEBUG_OTPW); #if ENABLED(MONITOR_DRIVER_STATUS) - TMC_REPORT("triggered\n OTP\t", TMC_OTPW_TRIGGERED); + TMC_REPORT("OTPW trig.\t", TMC_OTPW_TRIGGERED); #endif #if HAS_TMC220x - TMC_REPORT("pwm scale sum", TMC_PWM_SCALE_SUM); - TMC_REPORT("pwm scale auto", TMC_PWM_SCALE_AUTO); - TMC_REPORT("pwm offset auto", TMC_PWM_OFS_AUTO); - TMC_REPORT("pwm grad auto", TMC_PWM_GRAD_AUTO); + TMC_REPORT("pwm scale sum", TMC_PWM_SCALE_SUM); + TMC_REPORT("pwm scale auto", TMC_PWM_SCALE_AUTO); + TMC_REPORT("pwm offset auto", TMC_PWM_OFS_AUTO); + TMC_REPORT("pwm grad auto", TMC_PWM_GRAD_AUTO); #endif TMC_REPORT("off time", TMC_TOFF); @@ -988,11 +988,12 @@ TMC_REPORT(" -start\t", TMC_HSTRT); TMC_REPORT("Stallguard thrs", TMC_SGT); TMC_REPORT("uStep count", TMC_MSCNT); + DRV_REPORT("DRVSTATUS", TMC_DRV_CODES); - #if HAS_TMCX1X0 || HAS_TMC220x + #if HAS_TMCX1X0_OR_2240 || HAS_TMC220x DRV_REPORT("sg_result", TMC_SG_RESULT); #endif - #if HAS_TMCX1X0 + #if HAS_TMCX1X0_OR_2240 DRV_REPORT("stallguard", TMC_STALLGUARD); DRV_REPORT("fsactive", TMC_FSACTIVE); #endif @@ -1008,30 +1009,40 @@ DRV_REPORT("150C\t", TMC_T150); DRV_REPORT("143C\t", TMC_T143); DRV_REPORT("120C\t", TMC_T120); + #endif + #if HAS_TMC220x || HAS_DRIVER(TMC2240) DRV_REPORT("s2vsa\t", TMC_S2VSA); DRV_REPORT("s2vsb\t", TMC_S2VSB); #endif - DRV_REPORT("Driver registers:\n",TMC_DRV_STATUS_HEX); + DRV_REPORT("Driver registers:\n", TMC_DRV_STATUS_HEX); + #if HAS_DRIVER(TMC2240) + TMC_REPORT("Analog in (v)", TMC_VAIN); + TMC_REPORT("Supply (v)", TMC_VSUPPLY); + TMC_REPORT("Temp (°C)", TMC_TEMP); + TMC_REPORT("OT pre warn (°C)", TMC_OVERTEMP); + TMC_REPORT("OV threshold (v)", TMC_OVERVOLT_THD); + #endif SERIAL_EOL(); } #define PRINT_TMC_REGISTER(REG_CASE) case TMC_GET_##REG_CASE: print_hex_long(st.REG_CASE(), ':'); break - #if HAS_TMCX1X0 - static void tmc_get_ic_registers(TMC2130Stepper &st, const TMC_get_registers_enum i) { - switch (i) { - PRINT_TMC_REGISTER(TCOOLTHRS); - PRINT_TMC_REGISTER(THIGH); - PRINT_TMC_REGISTER(COOLCONF); - default: SERIAL_CHAR('\t'); break; + #if HAS_TRINAMIC_CONFIG + + template + static void tmc_get_ic_registers(TMC &, const TMC_get_registers_enum) { SERIAL_CHAR('\t'); } + + #if HAS_TMCX1X0 + static void tmc_get_ic_registers(TMC2130Stepper &st, const TMC_get_registers_enum i) { + switch (i) { + PRINT_TMC_REGISTER(TCOOLTHRS); + PRINT_TMC_REGISTER(THIGH); + PRINT_TMC_REGISTER(COOLCONF); + default: SERIAL_CHAR('\t'); break; + } } - } - #endif - #if HAS_TMC220x - static void tmc_get_ic_registers(TMC2208Stepper, const TMC_get_registers_enum) { SERIAL_CHAR('\t'); } - #endif + #endif - #if HAS_TRINAMIC_CONFIG template static void tmc_get_registers(TMC &st, const TMC_get_registers_enum i) { switch (i) { @@ -1051,7 +1062,9 @@ } SERIAL_CHAR('\t'); } - #endif + + #endif // HAS_TRINAMIC_CONFIG + #if HAS_DRIVER(TMC2660) template static void tmc_get_registers(TMCMarlin &st, const TMC_get_registers_enum i) { @@ -1071,88 +1084,44 @@ static void tmc_get_registers(TMC_get_registers_enum n OPTARGS_LOGICAL(const bool)) { if (TERN0(HAS_X_AXIS, x)) { - #if AXIS_IS_TMC(X) - tmc_get_registers(stepperX, n); - #endif - #if AXIS_IS_TMC(X2) - tmc_get_registers(stepperX2, n); - #endif + TERN_(X_IS_TRINAMIC, tmc_get_registers(stepperX, n)); + TERN_(X2_IS_TRINAMIC, tmc_get_registers(stepperX2, n)); } if (TERN0(HAS_Y_AXIS, y)) { - #if AXIS_IS_TMC(Y) - tmc_get_registers(stepperY, n); - #endif - #if AXIS_IS_TMC(Y2) - tmc_get_registers(stepperY2, n); - #endif + TERN_(Y_IS_TRINAMIC, tmc_get_registers(stepperY, n)); + TERN_(Y2_IS_TRINAMIC, tmc_get_registers(stepperY2, n)); } if (TERN0(HAS_Z_AXIS, z)) { - #if AXIS_IS_TMC(Z) - tmc_get_registers(stepperZ, n); - #endif - #if AXIS_IS_TMC(Z2) - tmc_get_registers(stepperZ2, n); - #endif - #if AXIS_IS_TMC(Z3) - tmc_get_registers(stepperZ3, n); - #endif - #if AXIS_IS_TMC(Z4) - tmc_get_registers(stepperZ4, n); - #endif + TERN_(Z_IS_TRINAMIC, tmc_get_registers(stepperZ, n)); + TERN_(Z2_IS_TRINAMIC, tmc_get_registers(stepperZ2, n)); + TERN_(Z3_IS_TRINAMIC, tmc_get_registers(stepperZ3, n)); + TERN_(Z4_IS_TRINAMIC, tmc_get_registers(stepperZ4, n)); } - #if AXIS_IS_TMC(I) - if (i) tmc_get_registers(stepperI, n); - #endif - #if AXIS_IS_TMC(J) - if (j) tmc_get_registers(stepperJ, n); - #endif - #if AXIS_IS_TMC(K) - if (k) tmc_get_registers(stepperK, n); - #endif - #if AXIS_IS_TMC(U) - if (u) tmc_get_registers(stepperU, n); - #endif - #if AXIS_IS_TMC(V) - if (v) tmc_get_registers(stepperV, n); - #endif - #if AXIS_IS_TMC(W) - if (w) tmc_get_registers(stepperW, n); - #endif + TERN_(I_IS_TRINAMIC, if (i) tmc_get_registers(stepperI, n)); + TERN_(J_IS_TRINAMIC, if (j) tmc_get_registers(stepperJ, n)); + TERN_(K_IS_TRINAMIC, if (k) tmc_get_registers(stepperK, n)); + TERN_(U_IS_TRINAMIC, if (u) tmc_get_registers(stepperU, n)); + TERN_(V_IS_TRINAMIC, if (v) tmc_get_registers(stepperV, n)); + TERN_(W_IS_TRINAMIC, if (w) tmc_get_registers(stepperW, n)); if (TERN0(HAS_EXTRUDERS, e)) { - #if AXIS_IS_TMC(E0) - tmc_get_registers(stepperE0, n); - #endif - #if AXIS_IS_TMC(E1) - tmc_get_registers(stepperE1, n); - #endif - #if AXIS_IS_TMC(E2) - tmc_get_registers(stepperE2, n); - #endif - #if AXIS_IS_TMC(E3) - tmc_get_registers(stepperE3, n); - #endif - #if AXIS_IS_TMC(E4) - tmc_get_registers(stepperE4, n); - #endif - #if AXIS_IS_TMC(E5) - tmc_get_registers(stepperE5, n); - #endif - #if AXIS_IS_TMC(E6) - tmc_get_registers(stepperE6, n); - #endif - #if AXIS_IS_TMC(E7) - tmc_get_registers(stepperE7, n); - #endif + TERN_(E0_IS_TRINAMIC, tmc_get_registers(stepperE0, n)); + TERN_(E1_IS_TRINAMIC, tmc_get_registers(stepperE1, n)); + TERN_(E2_IS_TRINAMIC, tmc_get_registers(stepperE2, n)); + TERN_(E3_IS_TRINAMIC, tmc_get_registers(stepperE3, n)); + TERN_(E4_IS_TRINAMIC, tmc_get_registers(stepperE4, n)); + TERN_(E5_IS_TRINAMIC, tmc_get_registers(stepperE5, n)); + TERN_(E6_IS_TRINAMIC, tmc_get_registers(stepperE6, n)); + TERN_(E7_IS_TRINAMIC, tmc_get_registers(stepperE7, n)); } 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); @@ -1203,11 +1172,29 @@ st.TCOOLTHRS(0); } + bool tmc_enable_stallguard(TMC2240Stepper &st) { + const bool stealthchop_was_enabled = st.en_pwm_mode(); + + // TODO: Use StallGuard4 when stealthChop is enabled + // and leave stealthChop state unchanged. + + st.TCOOLTHRS(0xFFFFF); + st.en_pwm_mode(false); + st.diag0_stall(true); + + return stealthchop_was_enabled; + } + void tmc_disable_stallguard(TMC2240Stepper &st, const bool restore_stealth) { + st.TCOOLTHRS(0); + st.en_pwm_mode(restore_stealth); + st.diag0_stall(false); + } + bool tmc_enable_stallguard(TMC2660Stepper) { // TODO return false; } - void tmc_disable_stallguard(TMC2660Stepper, const bool) {}; + void tmc_disable_stallguard(TMC2660Stepper, const bool) { } #endif // USE_SENSORLESS @@ -1232,161 +1219,45 @@ 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)) { - #if AXIS_IS_TMC(X) - axis_connection += test_connection(stepperX); - #endif - #if AXIS_IS_TMC(X2) - axis_connection += test_connection(stepperX2); - #endif + TERN_(X_IS_TRINAMIC, axis_connection += test_connection(stepperX)); + TERN_(X2_IS_TRINAMIC, axis_connection += test_connection(stepperX2)); } if (TERN0(HAS_Y_AXIS, y)) { - #if AXIS_IS_TMC(Y) - axis_connection += test_connection(stepperY); - #endif - #if AXIS_IS_TMC(Y2) - axis_connection += test_connection(stepperY2); - #endif + TERN_(Y_IS_TRINAMIC, axis_connection += test_connection(stepperY)); + TERN_(Y2_IS_TRINAMIC, axis_connection += test_connection(stepperY2)); } if (TERN0(HAS_Z_AXIS, z)) { - #if AXIS_IS_TMC(Z) - axis_connection += test_connection(stepperZ); - #endif - #if AXIS_IS_TMC(Z2) - axis_connection += test_connection(stepperZ2); - #endif - #if AXIS_IS_TMC(Z3) - axis_connection += test_connection(stepperZ3); - #endif - #if AXIS_IS_TMC(Z4) - axis_connection += test_connection(stepperZ4); - #endif + TERN_(Z_IS_TRINAMIC, axis_connection += test_connection(stepperZ)); + TERN_(Z2_IS_TRINAMIC, axis_connection += test_connection(stepperZ2)); + TERN_(Z3_IS_TRINAMIC, axis_connection += test_connection(stepperZ3)); + TERN_(Z4_IS_TRINAMIC, axis_connection += test_connection(stepperZ4)); } - #if AXIS_IS_TMC(I) - if (i) axis_connection += test_connection(stepperI); - #endif - #if AXIS_IS_TMC(J) - if (j) axis_connection += test_connection(stepperJ); - #endif - #if AXIS_IS_TMC(K) - if (k) axis_connection += test_connection(stepperK); - #endif - #if AXIS_IS_TMC(U) - if (u) axis_connection += test_connection(stepperU); - #endif - #if AXIS_IS_TMC(V) - if (v) axis_connection += test_connection(stepperV); - #endif - #if AXIS_IS_TMC(W) - if (w) axis_connection += test_connection(stepperW); - #endif + TERN_(I_IS_TRINAMIC, if (i) axis_connection += test_connection(stepperI)); + TERN_(J_IS_TRINAMIC, if (j) axis_connection += test_connection(stepperJ)); + TERN_(K_IS_TRINAMIC, if (k) axis_connection += test_connection(stepperK)); + TERN_(U_IS_TRINAMIC, if (u) axis_connection += test_connection(stepperU)); + TERN_(V_IS_TRINAMIC, if (v) axis_connection += test_connection(stepperV)); + TERN_(W_IS_TRINAMIC, if (w) axis_connection += test_connection(stepperW)); if (TERN0(HAS_EXTRUDERS, e)) { - #if AXIS_IS_TMC(E0) - axis_connection += test_connection(stepperE0); - #endif - #if AXIS_IS_TMC(E1) - axis_connection += test_connection(stepperE1); - #endif - #if AXIS_IS_TMC(E2) - axis_connection += test_connection(stepperE2); - #endif - #if AXIS_IS_TMC(E3) - axis_connection += test_connection(stepperE3); - #endif - #if AXIS_IS_TMC(E4) - axis_connection += test_connection(stepperE4); - #endif - #if AXIS_IS_TMC(E5) - axis_connection += test_connection(stepperE5); - #endif - #if AXIS_IS_TMC(E6) - axis_connection += test_connection(stepperE6); - #endif - #if AXIS_IS_TMC(E7) - axis_connection += test_connection(stepperE7); - #endif + TERN_(E0_IS_TRINAMIC, axis_connection += test_connection(stepperE0)); + TERN_(E1_IS_TRINAMIC, axis_connection += test_connection(stepperE1)); + TERN_(E2_IS_TRINAMIC, axis_connection += test_connection(stepperE2)); + TERN_(E3_IS_TRINAMIC, axis_connection += test_connection(stepperE3)); + TERN_(E4_IS_TRINAMIC, axis_connection += test_connection(stepperE4)); + TERN_(E5_IS_TRINAMIC, axis_connection += test_connection(stepperE5)); + TERN_(E6_IS_TRINAMIC, axis_connection += test_connection(stepperE6)); + TERN_(E7_IS_TRINAMIC, axis_connection += test_connection(stepperE7)); } if (axis_connection) LCD_MESSAGE(MSG_ERROR_TMC); } #endif // HAS_TRINAMIC_CONFIG - -#if HAS_TMC_SPI - #define SET_CS_PIN(st) OUT_WRITE(st##_CS_PIN, HIGH) - void tmc_init_cs_pins() { - #if AXIS_HAS_SPI(X) - SET_CS_PIN(X); - #endif - #if AXIS_HAS_SPI(Y) - SET_CS_PIN(Y); - #endif - #if AXIS_HAS_SPI(Z) - SET_CS_PIN(Z); - #endif - #if AXIS_HAS_SPI(X2) - SET_CS_PIN(X2); - #endif - #if AXIS_HAS_SPI(Y2) - SET_CS_PIN(Y2); - #endif - #if AXIS_HAS_SPI(Z2) - SET_CS_PIN(Z2); - #endif - #if AXIS_HAS_SPI(Z3) - SET_CS_PIN(Z3); - #endif - #if AXIS_HAS_SPI(Z4) - SET_CS_PIN(Z4); - #endif - #if AXIS_HAS_SPI(I) - SET_CS_PIN(I); - #endif - #if AXIS_HAS_SPI(J) - SET_CS_PIN(J); - #endif - #if AXIS_HAS_SPI(K) - SET_CS_PIN(K); - #endif - #if AXIS_HAS_SPI(U) - SET_CS_PIN(U); - #endif - #if AXIS_HAS_SPI(V) - SET_CS_PIN(V); - #endif - #if AXIS_HAS_SPI(W) - SET_CS_PIN(W); - #endif - #if AXIS_HAS_SPI(E0) - SET_CS_PIN(E0); - #endif - #if AXIS_HAS_SPI(E1) - SET_CS_PIN(E1); - #endif - #if AXIS_HAS_SPI(E2) - SET_CS_PIN(E2); - #endif - #if AXIS_HAS_SPI(E3) - SET_CS_PIN(E3); - #endif - #if AXIS_HAS_SPI(E4) - SET_CS_PIN(E4); - #endif - #if AXIS_HAS_SPI(E5) - SET_CS_PIN(E5); - #endif - #if AXIS_HAS_SPI(E6) - SET_CS_PIN(E6); - #endif - #if AXIS_HAS_SPI(E7) - SET_CS_PIN(E7); - #endif - } -#endif // HAS_TMC_SPI diff --git a/Marlin/src/feature/tmc_util.h b/Marlin/src/feature/tmc_util.h index a0a72058aa2d..556035f08c39 100644 --- a/Marlin/src/feature/tmc_util.h +++ b/Marlin/src/feature/tmc_util.h @@ -29,7 +29,7 @@ #include #include "../module/planner.h" -#define CHOPPER_DEFAULT_12V { 3, -1, 1 } +#define CHOPPER_DEFAULT_12V { 3, -1, 1 } // { toff, hend, hstrt } #define CHOPPER_DEFAULT_19V { 4, 1, 1 } #define CHOPPER_DEFAULT_24V { 4, 2, 1 } #define CHOPPER_DEFAULT_36V { 5, 2, 4 } @@ -77,8 +77,8 @@ class TMCStorage { struct { OPTCODE(HAS_STEALTHCHOP, bool stealthChop_enabled = false) - OPTCODE(HYBRID_THRESHOLD, uint8_t hybrid_thrs = 0) - OPTCODE(USE_SENSORLESS, int16_t homing_thrs = 0) + OPTCODE(HYBRID_THRESHOLD, uint16_t hybrid_thrs = 0) + OPTCODE(USE_SENSORLESS, int16_t homing_thrs = 0) } stored; }; @@ -95,7 +95,7 @@ class TMCMarlin : public TMC, public TMCStorage { TMC(CS, RS, pinMOSI, pinMISO, pinSCK) {} TMCMarlin(const uint16_t CS, const float RS, const uint16_t pinMOSI, const uint16_t pinMISO, const uint16_t pinSCK, const uint8_t axis_chain_index) : - TMC(CS, RS, pinMOSI, pinMISO, pinSCK, axis_chain_index) + TMC(CS, RS, pinMOSI, pinMISO, pinSCK, axis_chain_index) {} uint16_t rms_current() { return TMC::rms_current(); } void rms_current(uint16_t mA) { @@ -124,7 +124,7 @@ class TMCMarlin : public TMC, public TMCStorage { #if ENABLED(HYBRID_THRESHOLD) uint32_t get_pwm_thrs() { - return _tmc_thrs(this->microsteps(), this->TPWMTHRS(), planner.settings.axis_steps_per_mm[AXIS_ID]); + return _tmc_thrs(this->microsteps(), TMC::TPWMTHRS(), planner.settings.axis_steps_per_mm[AXIS_ID]); } void set_pwm_thrs(const uint32_t thrs) { TMC::TPWMTHRS(_tmc_thrs(this->microsteps(), thrs, planner.settings.axis_steps_per_mm[AXIS_ID])); @@ -144,15 +144,13 @@ class TMCMarlin : public TMC, public TMCStorage { #endif #endif - #if ANY(HAS_MARLINUI_MENU, DWIN_LCD_PROUI) - void refresh_stepper_current() { rms_current(this->val_mA); } + void refresh_stepper_current() { rms_current(this->val_mA); } - #if ENABLED(HYBRID_THRESHOLD) - void refresh_hybrid_thrs() { set_pwm_thrs(this->stored.hybrid_thrs); } - #endif - #if USE_SENSORLESS - void refresh_homing_thrs() { homing_threshold(this->stored.homing_thrs); } - #endif + #if ENABLED(HYBRID_THRESHOLD) + void refresh_hybrid_thrs() { set_pwm_thrs(this->stored.hybrid_thrs); } + #endif + #if USE_SENSORLESS + void refresh_homing_thrs() { homing_threshold(this->stored.homing_thrs); } #endif static constexpr int8_t sgt_min = -64, @@ -199,7 +197,7 @@ class TMCMarlin : public TMC220 #if ENABLED(HYBRID_THRESHOLD) uint32_t get_pwm_thrs() { - return _tmc_thrs(this->microsteps(), this->TPWMTHRS(), planner.settings.axis_steps_per_mm[AXIS_ID]); + return _tmc_thrs(this->microsteps(), TMC2208Stepper::TPWMTHRS(), planner.settings.axis_steps_per_mm[AXIS_ID]); } void set_pwm_thrs(const uint32_t thrs) { TMC2208Stepper::TPWMTHRS(_tmc_thrs(this->microsteps(), thrs, planner.settings.axis_steps_per_mm[AXIS_ID])); @@ -207,12 +205,10 @@ class TMCMarlin : public TMC220 } #endif - #if ANY(HAS_MARLINUI_MENU, DWIN_LCD_PROUI) - void refresh_stepper_current() { rms_current(this->val_mA); } + void refresh_stepper_current() { rms_current(this->val_mA); } - #if ENABLED(HYBRID_THRESHOLD) - void refresh_hybrid_thrs() { set_pwm_thrs(this->stored.hybrid_thrs); } - #endif + #if ENABLED(HYBRID_THRESHOLD) + void refresh_hybrid_thrs() { set_pwm_thrs(this->stored.hybrid_thrs); } #endif }; @@ -253,13 +249,14 @@ class TMCMarlin : public TMC220 #if ENABLED(HYBRID_THRESHOLD) uint32_t get_pwm_thrs() { - return _tmc_thrs(this->microsteps(), this->TPWMTHRS(), planner.settings.axis_steps_per_mm[AXIS_ID]); + return _tmc_thrs(this->microsteps(), TMC2209Stepper::TPWMTHRS(), planner.settings.axis_steps_per_mm[AXIS_ID]); } void set_pwm_thrs(const uint32_t thrs) { TMC2209Stepper::TPWMTHRS(_tmc_thrs(this->microsteps(), thrs, planner.settings.axis_steps_per_mm[AXIS_ID])); TERN_(HAS_MARLINUI_MENU, this->stored.hybrid_thrs = thrs); } #endif + #if USE_SENSORLESS int16_t homing_threshold() { return TMC2209Stepper::SGTHRS(); } void homing_threshold(int16_t sgt_val) { @@ -269,21 +266,87 @@ class TMCMarlin : public TMC220 } #endif - #if ANY(HAS_MARLINUI_MENU, DWIN_LCD_PROUI) - void refresh_stepper_current() { rms_current(this->val_mA); } + void refresh_stepper_current() { rms_current(this->val_mA); } - #if ENABLED(HYBRID_THRESHOLD) - void refresh_hybrid_thrs() { set_pwm_thrs(this->stored.hybrid_thrs); } - #endif - #if USE_SENSORLESS - void refresh_homing_thrs() { homing_threshold(this->stored.homing_thrs); } - #endif + #if ENABLED(HYBRID_THRESHOLD) + void refresh_hybrid_thrs() { set_pwm_thrs(this->stored.hybrid_thrs); } + #endif + #if USE_SENSORLESS + void refresh_homing_thrs() { homing_threshold(this->stored.homing_thrs); } #endif static constexpr uint8_t sgt_min = 0, sgt_max = 255; }; +template +class TMCMarlin : public TMC2240Stepper, public TMCStorage { + public: + TMCMarlin(const uint16_t cs_pin, const uint8_t axis_chain_index) : + TMC2240Stepper(cs_pin, axis_chain_index) + {} + TMCMarlin(const uint16_t CS, const uint16_t pinMOSI, const uint16_t pinMISO, const uint16_t pinSCK, const uint8_t axis_chain_index) : + TMC2240Stepper(CS, pinMOSI, pinMISO, pinSCK, axis_chain_index ) + {} + + //uint8_t get_address() { return slave_address; } + uint16_t get_microstep_counter() { return microsteps(); } + + uint16_t rms_current() { return TMC2240Stepper::rms_current(); } + void rms_current(const uint16_t mA) { + this->val_mA = mA; + TMC2240Stepper::rms_current(mA); + } + void rms_current(const uint16_t mA, const float mult) { + this->val_mA = mA; + TMC2240Stepper::rms_current(mA, mult); + } + + #if HAS_STEALTHCHOP + bool get_stealthChop() { return this->en_pwm_mode(); } + bool get_stored_stealthChop() { return this->stored.stealthChop_enabled; } + void refresh_stepping_mode() { this->en_pwm_mode(this->stored.stealthChop_enabled); } + void set_stealthChop(const bool stch) { this->stored.stealthChop_enabled = stch; refresh_stepping_mode(); } + bool toggle_stepping_mode() { set_stealthChop(!this->stored.stealthChop_enabled); return get_stealthChop(); } + #endif + + void set_chopper_times(const chopper_timing_t &ct) { + TMC2240Stepper::toff(ct.toff); + TMC2240Stepper::hysteresis_end(ct.hend); + TMC2240Stepper::hysteresis_start(ct.hstrt); + } + + #if ENABLED(HYBRID_THRESHOLD) + uint32_t get_pwm_thrs() { + return _tmc_thrs(this->microsteps(), TMC2240Stepper::TPWMTHRS(), planner.settings.axis_steps_per_mm[AXIS_ID]); + } + void set_pwm_thrs(const uint32_t thrs) { + TMC2240Stepper::TPWMTHRS(_tmc_thrs(this->microsteps(), thrs, planner.settings.axis_steps_per_mm[AXIS_ID])); + TERN_(HAS_MARLINUI_MENU, this->stored.hybrid_thrs = thrs); + } + #endif + + #if USE_SENSORLESS + int16_t homing_threshold() { return TMC2240Stepper::sgt(); } + void homing_threshold(int16_t sgt_val) { + sgt_val = (int16_t)constrain(sgt_val, sgt_min, sgt_max); + TMC2240Stepper::sgt(sgt_val); + TERN_(HAS_MARLINUI_MENU, this->stored.homing_thrs = sgt_val); + } + #endif + + void refresh_stepper_current() { rms_current(this->val_mA); } + #if ENABLED(HYBRID_THRESHOLD) + void refresh_hybrid_thrs() { set_pwm_thrs(this->stored.hybrid_thrs); } + #endif + #if USE_SENSORLESS + void refresh_homing_thrs() { homing_threshold(this->stored.homing_thrs); } + #endif + + static constexpr int8_t sgt_min = -64, + sgt_max = 63; +}; + template class TMCMarlin : public TMC2660Stepper, public TMCStorage { public: @@ -315,12 +378,10 @@ class TMCMarlin : public TMC266 } #endif - #if ANY(HAS_MARLINUI_MENU, DWIN_LCD_PROUI) - void refresh_stepper_current() { rms_current(this->val_mA); } + void refresh_stepper_current() { rms_current(this->val_mA); } - #if USE_SENSORLESS - void refresh_homing_thrs() { homing_threshold(this->stored.homing_thrs); } - #endif + #if USE_SENSORLESS + void refresh_homing_thrs() { homing_threshold(this->stored.homing_thrs); } #endif static constexpr int8_t sgt_min = -64, @@ -328,14 +389,14 @@ class TMCMarlin : 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 /** @@ -361,6 +422,9 @@ void test_tmc_connection(LOGICAL_AXIS_DECL(const bool, true)); bool tmc_enable_stallguard(TMC2209Stepper &st); void tmc_disable_stallguard(TMC2209Stepper &st, const bool restore_stealth); + bool tmc_enable_stallguard(TMC2240Stepper &st); + void tmc_disable_stallguard(TMC2240Stepper &st, const bool restore_stealth); + bool tmc_enable_stallguard(TMC2660Stepper); void tmc_disable_stallguard(TMC2660Stepper, const bool); @@ -370,7 +434,7 @@ void test_tmc_connection(LOGICAL_AXIS_DECL(const bool, true)); bool TMCMarlin::test_stall_status() { this->switchCSpin(LOW); - // read stallGuard flag from TMC library, will handle HW and SW SPI + // Read stallGuard flag from TMC library, will handle HW and SW SPI TMC2130_n::DRV_STATUS_t drv_status{0}; drv_status.sr = this->DRV_STATUS(); @@ -383,8 +447,30 @@ void test_tmc_connection(LOGICAL_AXIS_DECL(const bool, true)); #endif // USE_SENSORLESS -#endif // HAS_TRINAMIC_CONFIG +#if HAS_HOMING_CURRENT + + // Axes that have a distinct homing current + struct homing_current_t { + OPTCODE(X_HAS_HOME_CURRENT, uint16_t X) + OPTCODE(Y_HAS_HOME_CURRENT, uint16_t Y) + OPTCODE(Z_HAS_HOME_CURRENT, uint16_t Z) + OPTCODE(X2_HAS_HOME_CURRENT, uint16_t X2) + OPTCODE(Y2_HAS_HOME_CURRENT, uint16_t Y2) + OPTCODE(Z2_HAS_HOME_CURRENT, uint16_t Z2) + OPTCODE(Z3_HAS_HOME_CURRENT, uint16_t Z3) + OPTCODE(Z4_HAS_HOME_CURRENT, uint16_t Z4) + OPTCODE(I_HAS_HOME_CURRENT, uint16_t I) + OPTCODE(J_HAS_HOME_CURRENT, uint16_t J) + OPTCODE(K_HAS_HOME_CURRENT, uint16_t K) + OPTCODE(U_HAS_HOME_CURRENT, uint16_t U) + OPTCODE(V_HAS_HOME_CURRENT, uint16_t V) + OPTCODE(W_HAS_HOME_CURRENT, uint16_t W) + }; + + #if ENABLED(EDITABLE_HOMING_CURRENT) + extern homing_current_t homing_current_mA; + #endif -#if HAS_TMC_SPI - void tmc_init_cs_pins(); -#endif +#endif // HAS_HOMING_CURRENT + +#endif // HAS_TRINAMIC_CONFIG diff --git a/Marlin/src/feature/tramming.cpp b/Marlin/src/feature/tramming.cpp index 3721c5eb811a..cd49ed90b373 100644 --- a/Marlin/src/feature/tramming.cpp +++ b/Marlin/src/feature/tramming.cpp @@ -41,7 +41,7 @@ PGM_P const tramming_point_name[] PROGMEM = { REPLIST_1(_NR_TRAM_NAMES, _TRAM_NA void move_to_tramming_wait_pos() { constexpr xyz_pos_t wait_pos = ASSISTED_TRAMMING_WAIT_POSITION; if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("Moving away"); - do_blocking_move_to(wait_pos, XY_PROBE_FEEDRATE_MM_S); + motion.blocking_move(wait_pos, XY_PROBE_FEEDRATE_MM_S); } #endif diff --git a/Marlin/src/feature/tramming.h b/Marlin/src/feature/tramming.h index c8f20f001081..de4c6c020c1b 100644 --- a/Marlin/src/feature/tramming.h +++ b/Marlin/src/feature/tramming.h @@ -24,9 +24,16 @@ #include "../inc/MarlinConfig.h" #include "../module/probe.h" -#if !WITHIN(TRAMMING_SCREW_THREAD, 30, 51) || TRAMMING_SCREW_THREAD % 10 > 1 - #error "TRAMMING_SCREW_THREAD must be equal to 30, 31, 40, 41, 50, or 51." -#endif +enum TrammingThread : uint8_t { + M3_CW = 30, M3_CCW = 31, + M4_CW = 40, M4_CCW = 41, + M5_CW = 50, M5_CCW = 51 +}; + +static_assert( + TRAMMING_SCREW_THREAD < 60 && TRAMMING_SCREW_THREAD % 10 < 2, + "TRAMMING_SCREW_THREAD must be M3_CW, M3_CCW, M4_CW, M4_CCW, M5_CW, or M5_CCW." +); constexpr xy_pos_t tramming_points[] = TRAMMING_POINT_XY; diff --git a/Marlin/src/feature/twibus.h b/Marlin/src/feature/twibus.h index de23abbed53c..b438d10a33c0 100644 --- a/Marlin/src/feature/twibus.h +++ b/Marlin/src/feature/twibus.h @@ -64,7 +64,7 @@ class TWIBus { private: /** * @brief Number of bytes on buffer - * @description Number of bytes in the buffer waiting to be flushed to the bus + * @details Number of bytes in the buffer waiting to be flushed to the bus */ uint8_t buffer_s = 0; @@ -77,7 +77,7 @@ class TWIBus { public: /** * @brief Target device address - * @description The target device address. Persists until changed. + * @details The target device address. Persists until changed. */ uint8_t addr = 0; diff --git a/Marlin/src/feature/x_twist.cpp b/Marlin/src/feature/x_twist.cpp index b8f7e52ab6d5..6b155c49abf5 100644 --- a/Marlin/src/feature/x_twist.cpp +++ b/Marlin/src/feature/x_twist.cpp @@ -30,7 +30,7 @@ XATC xatc; bool XATC::enabled; float XATC::spacing, XATC::start; -xatc_array_t XATC::z_offset; // Initialized by settings.load() +xatc_array_t XATC::z_offset; // Initialized by settings.load void XATC::reset() { constexpr float xzo[] = XATC_Z_OFFSETS; @@ -53,7 +53,7 @@ void XATC::print_points() { SERIAL_EOL(); } -float lerp(const_float_t t, const_float_t a, const_float_t b) { return a + t * (b - a); } +float lerp(const float t, const float a, const float b) { return a + t * (b - a); } float XATC::compensation(const xy_pos_t &raw) { if (!enabled) return 0; diff --git a/Marlin/src/gcode/bedlevel/G26.cpp b/Marlin/src/gcode/bedlevel/G26.cpp index ab940c65acdc..dab99c300c38 100644 --- a/Marlin/src/gcode/bedlevel/G26.cpp +++ b/Marlin/src/gcode/bedlevel/G26.cpp @@ -58,10 +58,10 @@ * * L # Layer Layer height. (Height of nozzle above bed) If not specified .20mm will be used. * - * O # Ooooze How much your nozzle will Ooooze filament while getting in position to print. This - * is over kill, but using this parameter will let you get the very first 'circle' perfect - * so you have a trophy to peel off of the bed and hang up to show how perfectly you have your - * Mesh calibrated. If not specified, a filament length of .3mm is assumed. + * O # Ooze How much your nozzle will Ooooze filament while getting in position to print. If not + * specified, a filament length of .3mm is assumed. This might be overkill, but this + * parameter ensures the very first 'circle' is perfect (providing an ideal trophy to hang + * up to show off your perfectly calibrated Mesh). * * P # Prime Prime the nozzle with specified length of filament. If this parameter is not * given, no prime action will take place. If the parameter specifies an amount, that much @@ -102,10 +102,9 @@ #define G26_OK false #define G26_ERR true -#include "../../gcode/gcode.h" +#include "../gcode.h" #include "../../feature/bedlevel/bedlevel.h" -#include "../../MarlinCore.h" #include "../../module/planner.h" #include "../../module/motion.h" #include "../../module/tool_change.h" @@ -132,11 +131,11 @@ #endif #ifndef G26_XY_FEEDRATE - #define G26_XY_FEEDRATE (PLANNER_XY_FEEDRATE() / 3.0) + #define G26_XY_FEEDRATE (PLANNER_XY_FEEDRATE_MM_S / 3.0) #endif #ifndef G26_XY_FEEDRATE_TRAVEL - #define G26_XY_FEEDRATE_TRAVEL (PLANNER_XY_FEEDRATE() / 1.5) + #define G26_XY_FEEDRATE_TRAVEL (PLANNER_XY_FEEDRATE_MM_S / 1.5) #endif #if CROSSHAIRS_SIZE >= INTERSECTION_CIRCLE_RADIUS @@ -170,33 +169,33 @@ float g26_random_deviation = 0.0; #endif -void move_to(const_float_t rx, const_float_t ry, const_float_t z, const_float_t e_delta) { +void move_to(const float rx, const float ry, const float z, const float e_delta) { static float last_z = -999.99; const xy_pos_t dest = { rx, ry }; - const bool has_xy_component = dest != current_position, // Check if X or Y is involved in the movement. + const bool has_xy_component = dest != motion.position, // Check if X or Y is involved in the movement. has_e_component = e_delta != 0.0; if (z != last_z) { last_z = z; - destination.set(current_position.x, current_position.y, z, current_position.e); + motion.destination.set(motion.position.x, motion.position.y, z, motion.position.e); const feedRate_t fr_mm_s = planner.settings.max_feedrate_mm_s[Z_AXIS] * 0.5f; // Use half of the Z_AXIS max feed rate - prepare_internal_move_to_destination(fr_mm_s); + motion.prepare_internal_move_to_destination(fr_mm_s); } // If X or Y in combination with E is involved do a 'normal' move. // If X or Y with no E is involved do a 'fast' move // Otherwise retract/recover/hop. - destination = dest; - destination.e += e_delta; + motion.destination = dest; + motion.destination.e += e_delta; const feedRate_t fr_mm_s = has_xy_component ? (has_e_component ? feedRate_t(G26_XY_FEEDRATE) : feedRate_t(G26_XY_FEEDRATE_TRAVEL)) : planner.settings.max_feedrate_mm_s[E_AXIS] * 0.666f; - prepare_internal_move_to_destination(fr_mm_s); + motion.prepare_internal_move_to_destination(fr_mm_s); } -void move_to(const xyz_pos_t &where, const_float_t de) { move_to(where.x, where.y, where.z, de); } +void move_to(const xyz_pos_t &where, const float de) { move_to(where.x, where.y, where.z, de); } typedef struct { float extrusion_multiplier = EXTRUSION_MULTIPLIER, @@ -230,8 +229,8 @@ typedef struct { // TODO: Parameterize the Z lift with a define void retract_lift_move(const xyz_pos_t &s) { - retract_filament(destination); - move_to(current_position.x, current_position.y, current_position.z + 0.5f, 0.0f); // Z lift to minimize scraping + retract_filament(motion.destination); + move_to(motion.position.x, motion.position.y, motion.position.z + 0.5f, 0.0f); // Z lift to minimize scraping move_to(s.x, s.y, s.z + 0.5f, 0.0f); // Get to the starting point with no extrusion while lifted } @@ -260,7 +259,7 @@ typedef struct { void print_line_from_here_to_there(const xyz_pos_t &s, const xyz_pos_t &e) { // Distances to the start / end of the line - xy_float_t svec = current_position - s, evec = current_position - e; + xy_float_t svec = motion.position - s, evec = motion.position - e; const float dist_start = HYPOT2(svec.x, svec.y), dist_end = HYPOT2(evec.x, evec.y), @@ -268,8 +267,10 @@ typedef struct { // If the end point of the line is closer to the nozzle, flip the direction, // moving from the end to the start. On very small lines the optimization isn't worth it. - if (dist_end < dist_start && (INTERSECTION_CIRCLE_RADIUS) < ABS(line_length)) - return print_line_from_here_to_there(e, s); + if (dist_end < dist_start && (INTERSECTION_CIRCLE_RADIUS) < ABS(line_length)) { + print_line_from_here_to_there(e, s); + return; + } // Decide whether to retract & lift if (dist_start > 2.0) retract_lift_move(s); @@ -278,7 +279,7 @@ typedef struct { const float e_pos_delta = line_length * g26_e_axis_feedrate * extrusion_multiplier; - recover_filament(destination); + recover_filament(motion.destination); move_to(e, e_pos_delta); // Get to the ending point with an appropriate amount of extrusion } @@ -305,7 +306,7 @@ typedef struct { LIMIT(e.x, X_MIN_POS + 1, X_MAX_POS - 1); #endif - if (position_is_reachable(s) && position_is_reachable(e)) + if (motion.can_reach(s) && motion.can_reach(e)) print_line_from_here_to_there(s, e); } } @@ -340,10 +341,10 @@ typedef struct { // Start heating the active nozzle LCD_MESSAGE_MAX(MSG_G26_HEATING_NOZZLE); ui.quick_feedback(); - thermalManager.setTargetHotend(hotend_temp, active_extruder); + thermalManager.setTargetHotend(hotend_temp, motion.extruder); // Wait for the temperature to stabilize - if (!thermalManager.wait_for_hotend(active_extruder, true OPTARG(G26_CLICK_CAN_CANCEL, true))) + if (!thermalManager.wait_for_hotend(motion.extruder, true OPTARG(G26_CLICK_CAN_CANCEL, true))) return G26_ERR; ui.reset_status(); @@ -368,13 +369,13 @@ typedef struct { LCD_MESSAGE_MAX(MSG_G26_MANUAL_PRIME); ui.chirp(); - destination = current_position; + motion.destination = motion.position; - recover_filament(destination); // Make sure G26 doesn't think the filament is retracted(). + recover_filament(motion.destination); // Make sure G26 doesn't think the filament is retracted(). while (!ui.button_pressed()) { ui.chirp(); - destination.e += 0.25; + motion.destination.e += 0.25; #if ENABLED(PREVENT_LENGTHY_EXTRUDE) Total_Prime += 0.25; if (Total_Prime >= EXTRUDE_MAXLENGTH) { @@ -382,8 +383,8 @@ typedef struct { return G26_ERR; } #endif - prepare_internal_move_to_destination(fr_slow_e); - destination = current_position; + motion.prepare_internal_move_to_destination(fr_slow_e); + motion.destination = motion.position; planner.synchronize(); // Without this synchronize, the purge is more consistent, // but because the planner has a buffer, we won't be able // to stop as quickly. So we put up with the less smooth @@ -401,11 +402,11 @@ typedef struct { { LCD_MESSAGE_MAX(MSG_G26_FIXED_LENGTH); ui.quick_feedback(); - destination = current_position; - destination.e += prime_length; - prepare_internal_move_to_destination(fr_slow_e); - destination.e -= prime_length; - retract_filament(destination); + motion.destination = motion.position; + motion.destination.e += prime_length; + motion.prepare_internal_move_to_destination(fr_slow_e); + motion.destination.e -= prime_length; + retract_filament(motion.destination); } return G26_OK; @@ -499,7 +500,7 @@ void GcodeSuite::G26() { // Don't allow Mesh Validation without homing first, // or if the parameter parsing did not go OK, abort - if (homing_needed_error()) return; + if (motion.homing_needed_error()) return; #if HAS_TOOLCHANGE // Change the tool first, if specified @@ -615,7 +616,7 @@ void GcodeSuite::G26() { // If any preset or temperature was specified if (noztemp) { - if (!WITHIN(noztemp, 165, thermalManager.hotend_max_target(active_extruder))) { + if (!WITHIN(noztemp, 165, thermalManager.hotend_max_target(motion.extruder))) { SERIAL_ECHOLNPGM(GCODE_ERR_MSG("Specified nozzle temperature not plausible.")); return; } @@ -646,10 +647,10 @@ void GcodeSuite::G26() { return; } - // Set a position with 'X' and/or 'Y'. Default: current_position - g26.xy_pos.set(parser.seenval('X') ? RAW_X_POSITION(parser.value_linear_units()) : current_position.x, - parser.seenval('Y') ? RAW_Y_POSITION(parser.value_linear_units()) : current_position.y); - if (!position_is_reachable(g26.xy_pos)) { + // Set a position with 'X' and/or 'Y'. Default: motion.position + g26.xy_pos.set(parser.seenval('X') ? motion.raw_x(parser.value_linear_units()) : motion.position.x, + parser.seenval('Y') ? motion.raw_y(parser.value_linear_units()) : motion.position.y); + if (!motion.can_reach(g26.xy_pos)) { SERIAL_ECHOLNPGM(GCODE_ERR_MSG("Specified X,Y coordinate out of bounds.")); return; } @@ -659,9 +660,9 @@ void GcodeSuite::G26() { */ set_bed_leveling_enabled(!parser.seen_test('D')); - do_z_clearance(Z_CLEARANCE_BETWEEN_PROBES); + motion.do_z_clearance(Z_CLEARANCE_BETWEEN_PROBES); - #if DISABLED(NO_VOLUMETRICS) + #if HAS_VOLUMETRIC_EXTRUSION bool volumetric_was_enabled = parser.volumetric_enabled; parser.volumetric_enabled = false; planner.calculate_volumetric_multipliers(); @@ -669,8 +670,7 @@ void GcodeSuite::G26() { if (g26.turn_on_heaters() != G26_OK) goto LEAVE; - current_position.e = 0.0; - sync_plan_position_e(); + motion.set_and_sync_e(0.0f); if (g26.prime_flag && g26.prime_nozzle() != G26_OK) goto LEAVE; @@ -687,10 +687,10 @@ void GcodeSuite::G26() { circle_flags.reset(); // Move nozzle to the specified height for the first layer - destination = current_position; - destination.z = g26.layer_height; - move_to(destination, 0.0); - move_to(destination, g26.ooze_amount); + motion.destination = motion.position; + motion.destination.z = g26.layer_height; + move_to(motion.destination, 0.0); + move_to(motion.destination, g26.ooze_amount); TERN_(HAS_MARLINUI_MENU, ui.capture()); @@ -718,14 +718,14 @@ void GcodeSuite::G26() { TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(location.pos, ExtUI::G26_START)); do { // Find the nearest confluence - location = g26.find_closest_circle_to_print(g26.continue_with_closest ? xy_pos_t(current_position) : g26.xy_pos); + location = g26.find_closest_circle_to_print(g26.continue_with_closest ? xy_pos_t(motion.position) : g26.xy_pos); if (location.valid()) { TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(location.pos, ExtUI::G26_POINT_START)); const xy_pos_t circle = { bedlevel.get_mesh_x(location.pos.a), bedlevel.get_mesh_y(location.pos.b) }; // If this mesh location is outside the printable radius, skip it. - if (!position_is_reachable(circle)) continue; + if (!motion.can_reach(circle)) continue; // Determine where to start and end the circle, // which is always drawn counter-clockwise. @@ -766,11 +766,11 @@ void GcodeSuite::G26() { } const ab_float_t arc_offset = circle - s; - const xy_float_t dist = current_position - s; // Distance from the start of the actual circle + const xy_float_t dist = motion.position - s; // Distance from the start of the actual circle const float dist_start = HYPOT2(dist.x, dist.y); const xyze_pos_t endpoint = { e.x, e.y, g26.layer_height, - current_position.e + (arc_length * g26_e_axis_feedrate * g26.extrusion_multiplier) + motion.position.e + (arc_length * g26_e_axis_feedrate * g26.extrusion_multiplier) }; if (dist_start > 2.0) { @@ -781,11 +781,11 @@ void GcodeSuite::G26() { s.z = g26.layer_height; move_to(s, 0.0); // Get to the starting point with no extrusion / un-Z lift - g26.recover_filament(destination); + g26.recover_filament(motion.destination); - { REMEMBER(fr, feedrate_mm_s, PLANNER_XY_FEEDRATE() * 0.1f); + { REMEMBER(fr, motion.feedrate_mm_s, PLANNER_XY_FEEDRATE_MM_S * 0.1f); plan_arc(endpoint, arc_offset, false, 0); // Draw a counter-clockwise arc - destination = current_position; + motion.destination = motion.position; } if (TERN0(HAS_MARLINUI_MENU, user_canceled())) goto LEAVE; // Check if the user wants to stop the Mesh Validation @@ -819,7 +819,7 @@ void GcodeSuite::G26() { #if IS_KINEMATIC // Check to make sure this segment is entirely on the bed, skip if not. - if (!position_is_reachable(p) || !position_is_reachable(q)) continue; + if (!motion.can_reach(p) || !motion.can_reach(q)) continue; #elif HAS_ENDSTOPS LIMIT(p.x, X_MIN_POS + 1, X_MAX_POS - 1); // Prevent hitting the endstops LIMIT(p.y, Y_MIN_POS + 1, Y_MAX_POS - 1); @@ -850,11 +850,11 @@ void GcodeSuite::G26() { LCD_MESSAGE_MIN(MSG_G26_LEAVING); TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(location, ExtUI::G26_FINISH)); - g26.retract_filament(destination); - destination.z = Z_CLEARANCE_BETWEEN_PROBES; - move_to(destination, 0); // Raise the nozzle + g26.retract_filament(motion.destination); + motion.destination.z = Z_CLEARANCE_BETWEEN_PROBES; + move_to(motion.destination, 0); // Raise the nozzle - #if DISABLED(NO_VOLUMETRICS) + #if HAS_VOLUMETRIC_EXTRUSION parser.volumetric_enabled = volumetric_was_enabled; planner.calculate_volumetric_multipliers(); #endif @@ -863,7 +863,7 @@ void GcodeSuite::G26() { if (!g26.keep_heaters_on) { TERN_(HAS_HEATED_BED, thermalManager.setTargetBed(0)); - thermalManager.setTargetHotend(active_extruder, 0); + thermalManager.setTargetHotend(motion.extruder, 0); } } diff --git a/Marlin/src/gcode/bedlevel/G35.cpp b/Marlin/src/gcode/bedlevel/G35.cpp index 63a1491f9bb1..dd9553b50868 100644 --- a/Marlin/src/gcode/bedlevel/G35.cpp +++ b/Marlin/src/gcode/bedlevel/G35.cpp @@ -53,7 +53,7 @@ * 41 - Counter-Clockwise M4 * 50 - Clockwise M5 * 51 - Counter-Clockwise M5 - **/ + */ void GcodeSuite::G35() { DEBUG_SECTION(log_G35, "G35", DEBUGGING(LEVELING)); @@ -84,10 +84,10 @@ void GcodeSuite::G35() { probe.use_probing_tool(); // Disable duplication mode on homing - TERN_(HAS_DUPLICATION_MODE, set_duplication_enabled(false)); + TERN_(HAS_DUPLICATION_MODE, motion.set_extruder_duplication(false)); // Home only Z axis when X and Y is trusted, otherwise all axes, if needed before this procedure - if (!all_axes_trusted()) process_subcommands_now(F("G28Z")); + if (!motion.all_axes_trusted()) process_subcommands_now(F("G28Z")); bool err_break = false; @@ -95,9 +95,11 @@ void GcodeSuite::G35() { for (uint8_t i = 0; i < G35_PROBE_COUNT; ++i) { const float z_probed_height = probe.probe_at_point(tramming_points[i], PROBE_PT_RAISE); if (isnan(z_probed_height)) { - SERIAL_ECHO( - F("G35 failed at point "), i + 1, F(" ("), FPSTR(pgm_read_ptr(&tramming_point_name[i])), C(')'), - FPSTR(SP_X_STR), tramming_points[i].x, FPSTR(SP_Y_STR), tramming_points[i].y + SERIAL_ECHOLN( + F("G35 failed at point "), i + 1, + F(" ("), FPSTR(pgm_read_ptr(&tramming_point_name[i])), C(')'), + FPSTR(SP_X_STR), tramming_points[i].x, + FPSTR(SP_Y_STR), tramming_points[i].y ); err_break = true; break; @@ -151,7 +153,7 @@ void GcodeSuite::G35() { move_to_tramming_wait_pos(); // After this operation the Z position needs correction - set_axis_never_homed(Z_AXIS); + motion.set_axis_never_homed(Z_AXIS); } #endif // ASSISTED_TRAMMING diff --git a/Marlin/src/gcode/bedlevel/G42.cpp b/Marlin/src/gcode/bedlevel/G42.cpp index cb5ed9740604..8383dd1a8de5 100644 --- a/Marlin/src/gcode/bedlevel/G42.cpp +++ b/Marlin/src/gcode/bedlevel/G42.cpp @@ -25,49 +25,57 @@ #if HAS_MESH #include "../gcode.h" -#include "../../MarlinCore.h" // for IsRunning() #include "../../module/motion.h" -#include "../../module/probe.h" // for probe.offset #include "../../feature/bedlevel/bedlevel.h" +#if HAS_PROBE_XY_OFFSET + #include "../../module/probe.h" // for probe.offset +#endif + /** * G42: Move X & Y axes to mesh coordinates (I & J) + * + * Parameters: + * F : Feedrate in mm/min + * I : X axis point index + * J : Y axis point index + * P : Flag to put the probe at the given point */ void GcodeSuite::G42() { - if (MOTION_CONDITIONS) { - const bool hasI = parser.seenval('I'); - const int8_t ix = hasI ? parser.value_int() : 0; - const bool hasJ = parser.seenval('J'); - const int8_t iy = hasJ ? parser.value_int() : 0; + if (motion.gcode_motion_ignored()) return; - if ((hasI && !WITHIN(ix, 0, GRID_MAX_POINTS_X - 1)) || (hasJ && !WITHIN(iy, 0, GRID_MAX_POINTS_Y - 1))) { - SERIAL_ECHOLNPGM(STR_ERR_MESH_XY); - return; - } + const bool hasI = parser.seenval('I'); + const int8_t ix = hasI ? parser.value_int() : 0; + const bool hasJ = parser.seenval('J'); + const int8_t iy = hasJ ? parser.value_int() : 0; - // Move to current_position, as modified by I, J, P parameters - destination = current_position; + if ((hasI && !WITHIN(ix, 0, GRID_MAX_POINTS_X - 1)) || (hasJ && !WITHIN(iy, 0, GRID_MAX_POINTS_Y - 1))) { + SERIAL_ECHOLNPGM(STR_ERR_MESH_XY); + return; + } - if (hasI) destination.x = bedlevel.get_mesh_x(ix); - if (hasJ) destination.y = bedlevel.get_mesh_y(iy); + // Move to motion.position, as modified by I, J, P parameters + motion.destination = motion.position; - #if HAS_PROBE_XY_OFFSET - if (parser.boolval('P')) { - if (hasI) destination.x -= probe.offset_xy.x; - if (hasJ) destination.y -= probe.offset_xy.y; - } - #endif + if (hasI) motion.destination.x = bedlevel.get_mesh_x(ix); + if (hasJ) motion.destination.y = bedlevel.get_mesh_y(iy); - const feedRate_t fval = parser.linearval('F'), - fr_mm_s = MMM_TO_MMS(fval > 0 ? fval : 0.0f); + #if HAS_PROBE_XY_OFFSET + if (parser.seen_test('P')) { + if (hasI) motion.destination.x -= probe.offset_xy.x; + if (hasJ) motion.destination.y -= probe.offset_xy.y; + } + #endif - // SCARA kinematic has "safe" XY raw moves - #if IS_SCARA - prepare_internal_fast_move_to_destination(fr_mm_s); - #else - prepare_internal_move_to_destination(fr_mm_s); - #endif - } + const feedRate_t fval = parser.linearval('F'), + fr_mm_s = MMM_TO_MMS(fval > 0 ? fval : 0.0f); + + // SCARA kinematic has "safe" XY raw moves + #if IS_SCARA + motion.prepare_internal_fast_move_to_destination(fr_mm_s); + #else + motion.prepare_internal_move_to_destination(fr_mm_s); + #endif } #endif // HAS_MESH diff --git a/Marlin/src/gcode/bedlevel/M420.cpp b/Marlin/src/gcode/bedlevel/M420.cpp index c495da301850..d6901b71be34 100644 --- a/Marlin/src/gcode/bedlevel/M420.cpp +++ b/Marlin/src/gcode/bedlevel/M420.cpp @@ -27,7 +27,10 @@ #include "../gcode.h" #include "../../feature/bedlevel/bedlevel.h" #include "../../module/planner.h" -#include "../../module/probe.h" + +#if ENABLED(MARLIN_DEV_MODE) + #include "../../module/probe.h" +#endif #if ENABLED(EEPROM_SETTINGS) #include "../../module/settings.h" @@ -42,18 +45,19 @@ /** * M420: Enable/Disable Bed Leveling and/or set the Z fade height. * - * S[bool] Turns leveling on or off - * Z[height] Sets the Z fade height (0 or none to disable) - * V[bool] Verbose - Print the leveling grid + * S Turns leveling on or off + * Z Sets the Z fade height (0 or none to disable) + * V Verbose - Print the leveling grid * * With AUTO_BED_LEVELING_UBL only: * - * L[index] Load UBL mesh from index (0 is default) - * T[map] 0:Human-readable 1:CSV 2:"LCD" 4:Compact + * L Load UBL mesh from index (0 is default) + * T 0:Human-readable 1:CSV 2:"LCD" 4:Compact + * C Adjust Mesh To Mean (and subtract the given offset) * * With mesh-based leveling only: * - * C Center mesh on the mean of the lowest and highest + * C Center Mesh on the Midrange (and subtract the given offset) * * With MARLIN_DEV_MODE: * S2 Create a simple random mesh and enable @@ -79,28 +83,28 @@ void GcodeSuite::M420() { } TERN_(AUTO_BED_LEVELING_BILINEAR, bedlevel.refresh_bed_level()); SERIAL_ECHOPGM("Simulated " STRINGIFY(GRID_MAX_POINTS_X) "x" STRINGIFY(GRID_MAX_POINTS_Y) " mesh "); - SERIAL_ECHOPGM(" (", x_min); - SERIAL_CHAR(','); SERIAL_ECHO(y_min); - SERIAL_ECHOPGM(")-(", x_max); - SERIAL_CHAR(','); SERIAL_ECHO(y_max); - SERIAL_ECHOLNPGM(")"); + SERIAL_ECHOLN(F(" ("), x_min, C(','), y_min, F(")-("), x_max, C(','), y_max, C(')')); } #endif - xyz_pos_t oldpos = current_position; + xyz_pos_t oldpos = motion.position; // If disabling leveling do it right away // (Don't disable for just M420 or M420 V) if (seen_S && !to_enable) set_bed_leveling_enabled(false); + const bool seenV = parser.seen_test('V'); + #if ENABLED(AUTO_BED_LEVELING_UBL) // L to load a mesh from the EEPROM - if (parser.seen('L')) { + const bool seenL = parser.seen('L'); + if (seenL) { - set_bed_leveling_enabled(false); + #if HAS_MESH_STORAGE + + set_bed_leveling_enabled(false); - #if ENABLED(EEPROM_SETTINGS) const int8_t storage_slot = parser.has_value() ? parser.value_int() : bedlevel.storage_slot; const int16_t a = settings.calc_num_meshes(); @@ -126,22 +130,21 @@ void GcodeSuite::M420() { } // L or V display the map info - if (parser.seen("LV")) { + if (seenL || seenV) { bedlevel.display_map(parser.byteval('T')); - SERIAL_ECHOPGM("Mesh is "); - if (!bedlevel.mesh_is_valid()) SERIAL_ECHOPGM("in"); - SERIAL_ECHOLNPGM("valid\nStorage slot: ", bedlevel.storage_slot); + SERIAL_ECHO_TERNARY(bedlevel.mesh_is_valid(), "Mesh is ", "", "in", "valid\n"); + #if HAS_MESH_STORAGE + SERIAL_ECHOLNPGM("Storage slot: ", bedlevel.storage_slot); + #endif } #endif // AUTO_BED_LEVELING_UBL - const bool seenV = parser.seen_test('V'); - #if HAS_MESH if (leveling_is_valid()) { - // Subtract the given value or the mean from all mesh values + // Re-center the mesh Z values around the midrange (or mean), plus any given offset if (parser.seen('C')) { const float cval = parser.value_float(); #if ENABLED(AUTO_BED_LEVELING_UBL) @@ -156,7 +159,7 @@ void GcodeSuite::M420() { // Get the sum and average of all mesh values float mesh_sum = 0; GRID_LOOP(x, y) mesh_sum += bedlevel.z_values[x][y]; - const float zmean = mesh_sum / float(GRID_MAX_POINTS); + const float zmean = mesh_sum / float(GRID_MAX_POINTS) + cval; #else // midrange @@ -225,9 +228,7 @@ void GcodeSuite::M420() { if (to_enable && !planner.leveling_active) SERIAL_ERROR_MSG(STR_ERR_M420_FAILED); - SERIAL_ECHO_START(); - SERIAL_ECHOPGM("Bed Leveling "); - serialprintln_onoff(planner.leveling_active); + SERIAL_ECHO_MSG("Bed Leveling ", ON_OFF(planner.leveling_active)); #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) SERIAL_ECHO_START(); @@ -239,8 +240,8 @@ void GcodeSuite::M420() { #endif // Report change in position - if (oldpos != current_position) - report_current_position(); + if (oldpos != motion.position) + motion.report_position(); } void GcodeSuite::M420_report(const bool forReplay/*=true*/) { @@ -249,14 +250,13 @@ void GcodeSuite::M420_report(const bool forReplay/*=true*/) { report_heading_etc(forReplay, F( TERN(MESH_BED_LEVELING, "Mesh Bed Leveling", TERN(AUTO_BED_LEVELING_UBL, "Unified Bed Leveling", "Auto Bed Leveling")) )); - SERIAL_ECHO( + SERIAL_ECHOLN( F(" M420 S"), planner.leveling_active #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) , FPSTR(SP_Z_STR), LINEAR_UNIT(planner.z_fade_height) #endif - , F(" ; Leveling ") + , F(" ; Leveling "), ON_OFF(planner.leveling_active) ); - serialprintln_onoff(planner.leveling_active); } #endif // HAS_LEVELING diff --git a/Marlin/src/gcode/bedlevel/abl/G29.cpp b/Marlin/src/gcode/bedlevel/abl/G29.cpp index 3126bee47f84..c2a99cb16d32 100644 --- a/Marlin/src/gcode/bedlevel/abl/G29.cpp +++ b/Marlin/src/gcode/bedlevel/abl/G29.cpp @@ -33,6 +33,7 @@ #include "../../../module/motion.h" #include "../../../module/planner.h" #include "../../../module/probe.h" +#include "../../../module/temperature.h" #include "../../queue.h" #if ENABLED(AUTO_BED_LEVELING_LINEAR) @@ -50,14 +51,22 @@ #if ENABLED(EXTENSIBLE_UI) #include "../../../lcd/extui/ui_api.h" #elif ENABLED(DWIN_CREALITY_LCD) - #include "../../../lcd/e3v2/creality/dwin.h" -#elif ENABLED(DWIN_LCD_PROUI) - #include "../../../lcd/e3v2/proui/dwin.h" + #include "../../../lcd/dwin/creality/dwin.h" +#elif ENABLED(SOVOL_SV06_RTS) + #include "../../../lcd/sovol_rts/sovol_rts.h" +#endif + +#if ENABLED(DWIN_LCD_PROUI) + #include "../../../lcd/dwin/proui/meshviewer.h" #endif #define DEBUG_OUT ENABLED(DEBUG_LEVELING_FEATURE) #include "../../../core/debug_out.h" +#if DISABLED(PROBE_MANUALLY) && ENABLED(FT_MOTION) + #include "../../../module/ft_motion.h" +#endif + #if ABL_USES_GRID #if ENABLED(PROBE_Y_FIRST) #define PR_OUTER_VAR abl.meshCount.x @@ -72,14 +81,21 @@ #endif #endif +/** + * @brief Do some things before returning from G29. + * @param retry : true if the G29 can and should be retried. false if the failure is too serious. + * @param did : true if the leveling procedure completed successfully. + */ static void pre_g29_return(const bool retry, const bool did) { if (!retry) { - TERN_(FULL_REPORT_TO_HOST_FEATURE, set_and_report_grblstate(M_IDLE, false)); - } - if (did) { - TERN_(HAS_DWIN_E3V2_BASIC, dwinLevelingDone()); - TERN_(EXTENSIBLE_UI, ExtUI::onLevelingDone()); + TERN_(FULL_REPORT_TO_HOST_FEATURE, motion.set_and_report_grblstate(M_IDLE, false)); } + #if DISABLED(G29_RETRY_AND_RECOVER) + if (!retry || did) { + TERN_(DWIN_CREALITY_LCD, dwinLevelingDone()); + TERN_(EXTENSIBLE_UI, ExtUI::onLevelingDone()); + } + #endif } #define G29_RETURN(retry, did) do{ \ @@ -144,81 +160,72 @@ class G29_State { #endif /** - * G29: Detailed Z probe, probes the bed at 3 or more points. - * Will fail if the printer has not been homed with G28. - * - * Enhanced G29 Auto Bed Leveling Probe Routine - * - * O Auto-level only if needed - * - * D Dry-Run mode. Just evaluate the bed Topology - Don't apply - * or alter the bed level data. Useful to check the topology - * after a first run of G29. - * - * J Jettison current bed leveling data - * - * V Set the verbose level (0-4). Example: "G29 V3" - * - * Parameters With LINEAR leveling only: - * - * P Set the size of the grid that will be probed (P x P points). - * Example: "G29 P4" - * - * X Set the X size of the grid that will be probed (X x Y points). - * Example: "G29 X7 Y5" - * - * Y Set the Y size of the grid that will be probed (X x Y points). - * - * T Generate a Bed Topology Report. Example: "G29 P5 T" for a detailed report. - * This is useful for manual bed leveling and finding flaws in the bed (to - * assist with part placement). - * Not supported by non-linear delta printer bed leveling. - * - * Parameters With LINEAR and BILINEAR leveling only: + * G29: Bed Leveling * - * S Set the XY travel speed between probe points (in units/min) + * Enhanced G29 Auto Bed Leveling Probe Routine. + * Probes the bed at 3 or more points. + * Will fail if the printer has not been homed with G28. * - * H Set bounds to a centered square H x H units in size + * Parameters: + * O Auto-level only if needed (Optional) * - * -or- + * D Dry-Run mode. Just evaluate the bed Topology - + * Don't apply or alter the bed level data. + * Useful to check the topology after a first run of G29. * - * F Set the Front limit of the probing grid - * B Set the Back limit of the probing grid - * L Set the Left limit of the probing grid - * R Set the Right limit of the probing grid + * J Jettison current bed leveling data * - * Parameters with DEBUG_LEVELING_FEATURE only: + * V<0-4> Set the verbose level (0-4) + * Example: G29 V3 * - * C Make a totally fake grid with no actual probing. - * For use in testing when no probing is possible. + * With AUTO_BED_LEVELING_LINEAR: + * P Set the size of the grid that will be probed (P x P points) + * Example: G29 P4 * - * Parameters with BILINEAR leveling only: + * X Set the X size of the grid that will be probed (X x Y points) + * Example: G29 X7 Y5 * - * Z Supply an additional Z probe offset + * Y Set the Y size of the grid that will be probed (X x Y points) * - * Extra parameters with PROBE_MANUALLY: + * T Generate a Bed Topology Report + * Example: G29 P5 T - for a detailed report. + * This is useful for manual bed leveling and finding flaws in the bed + * (to assist with part placement). + * Not supported by non-linear delta printer bed leveling. * - * To do manual probing simply repeat G29 until the procedure is complete. - * The first G29 accepts parameters. 'G29 Q' for status, 'G29 A' to abort. + * With AUTO_BED_LEVELING_LINEAR and AUTO_BED_LEVELING_BILINEAR: + * S Set the XY travel speed between probe points (in units/min) + * H Set bounds to a centered square H x H units in size + * -or- + * F Set the Front limit of the probing grid + * B Set the Back limit of the probing grid + * L Set the Left limit of the probing grid + * R Set the Right limit of the probing grid * - * Q Query leveling and G29 state + * With AUTO_BED_LEVELING_BILINEAR: + * Z Supply additional Z offset to all probe points. + * W Write a mesh point. (If G29 is idle.) + * I Index for mesh point + * J Index for mesh point + * X For mesh point, overrides I + * Y For mesh point, overrides J + * Z For mesh point. If omitted, uses current position's raw Z * - * A Abort current leveling procedure + * With DEBUG_LEVELING_FEATURE: + * C Make a totally fake grid with no actual probing. + * For use in testing when no probing is possible. * - * Extra parameters with BILINEAR only: + * With PROBE_MANUALLY: + * To do manual probing simply repeat G29 until the procedure is complete. + * The first G29 accepts parameters. 'G29 Q' for status, 'G29 A' to abort. * - * W Write a mesh point. (If G29 is idle.) - * I X index for mesh point - * J Y index for mesh point - * X X for mesh point, overrides I - * Y Y for mesh point, overrides J - * Z Z for mesh point. Otherwise, raw current Z. + * Q Query leveling and G29 state + * A Abort current leveling procedure * - * Without PROBE_MANUALLY: - * - * E By default G29 will engage the Z probe, test the bed, then disengage. - * Include "E" to engage/disengage the Z probe for each sample. - * There's no extra effect if you have a fixed Z probe. + * Without PROBE_MANUALLY: + * E By default G29 will engage the Z probe, test the bed, then disengage + * Include "E" to engage/disengage the Z probe for each sample. + * There's no extra effect if you have a fixed Z probe. */ G29_TYPE GcodeSuite::G29() { @@ -254,9 +261,11 @@ G29_TYPE GcodeSuite::G29() { // Send 'N' to force homing before G29 (internal only) if (parser.seen_test('N')) process_subcommands_now(TERN(CAN_SET_LEVELING_AFTER_G28, F("G28L0"), FPSTR(G28_STR))); + else if (ENABLED(DWIN_LCD_PROUI)) + process_subcommands_now(F("G28Z")); // Don't allow auto-leveling without homing first - if (homing_needed_error()) G29_RETURN(false, false); + if (motion.homing_needed_error()) G29_RETURN(false, false); // 3-point leveling gets points from the probe class #if ENABLED(AUTO_BED_LEVELING_3POINT) @@ -270,7 +279,12 @@ G29_TYPE GcodeSuite::G29() { #endif // Set and report "probing" state to host - TERN_(FULL_REPORT_TO_HOST_FEATURE, set_and_report_grblstate(M_PROBE, false)); + TERN_(FULL_REPORT_TO_HOST_FEATURE, motion.set_and_report_grblstate(M_PROBE, false)); + + #if DISABLED(PROBE_MANUALLY) + // Potentially disable Fixed-Time Motion for probing + TERN_(FT_MOTION, FTM_DISABLE_IN_SCOPE()); + #endif /** * On the initial G29 fetch command parameters. @@ -279,6 +293,11 @@ G29_TYPE GcodeSuite::G29() { probe.use_probing_tool(); + #ifdef EVENT_GCODE_BEFORE_G29 + if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("Before G29 G-code: ", EVENT_GCODE_BEFORE_G29); + gcode.process_subcommands_now(F(EVENT_GCODE_BEFORE_G29)); + #endif + #if ANY(PROBE_MANUALLY, AUTO_BED_LEVELING_LINEAR) abl.abl_probe_index = -1; #endif @@ -294,14 +313,14 @@ G29_TYPE GcodeSuite::G29() { G29_RETURN(false, false); } - const float rz = parser.seenval('Z') ? RAW_Z_POSITION(parser.value_linear_units()) : current_position.z; + const float rz = parser.seenval('Z') ? motion.raw_z(parser.value_linear_units()) : motion.position.z; if (!WITHIN(rz, -10, 10)) { SERIAL_ERROR_MSG("Bad Z value"); G29_RETURN(false, false); } - const float rx = RAW_X_POSITION(parser.linearval('X', NAN)), - ry = RAW_Y_POSITION(parser.linearval('Y', NAN)); + const float rx = motion.raw_x(parser.linearval('X', NAN)), + ry = motion.raw_y(parser.linearval('Y', NAN)); int8_t i = parser.byteval('I', -1), j = parser.byteval('J', -1); #pragma GCC diagnostic push @@ -324,7 +343,7 @@ G29_TYPE GcodeSuite::G29() { TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(i, j, rz)); if (abl.reenable) { set_bed_leveling_enabled(true); - report_current_position(); + motion.report_position(); } } G29_RETURN(false, false); @@ -382,9 +401,17 @@ G29_TYPE GcodeSuite::G29() { #endif - #if ABL_USES_GRID + #if HAS_VARIABLE_XY_PROBE_FEEDRATE + static constexpr feedRate_t min_fr_mm_m = XY_PROBE_MIN_FEEDRATE_MM_M; + feedRate_t fr_mm_m = parser.linearval('S', XY_PROBE_FEEDRATE); + if (fr_mm_m < min_fr_mm_m) { + fr_mm_m = min_fr_mm_m; + SERIAL_ECHOLNPGM(GCODE_ERR_MSG("Feedrate (S) too low. (Using ", LINEAR_UNIT(min_fr_mm_m), " units/min)")); + } + motion.xy_probe_feedrate_mm_s = MMM_TO_MMS(fr_mm_m); + #endif - xy_probe_feedrate_mm_s = MMM_TO_MMS(parser.linearval('S', XY_PROBE_FEEDRATE)); + #if ABL_USES_GRID const float x_min = probe.min_x(), x_max = probe.max_x(), y_min = probe.min_y(), y_max = probe.max_y(); @@ -425,29 +452,29 @@ G29_TYPE GcodeSuite::G29() { #if ENABLED(AUTO_BED_LEVELING_3POINT) if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("> 3-point Leveling"); points[0].z = points[1].z = points[2].z = 0; // Probe at 3 arbitrary points - #elif ENABLED(AUTO_BED_LEVELING_BILINEAR) - TERN_(DWIN_LCD_PROUI, dwinLevelingStart()); #endif TERN_(EXTENSIBLE_UI, ExtUI::onLevelingStart()); if (!faux) { - remember_feedrate_scaling_off(); + motion.remember_feedrate_scaling_off(); #if ENABLED(PREHEAT_BEFORE_LEVELING) + #if ENABLED(SOVOL_SV06_RTS) + rts.updateTempE0(); + rts.updateTempBed(); + rts.sendData(1, Wait_VP); + rts.gotoPage(ID_ABL_HeatWait_L, ID_ABL_HeatWait_D); + #endif if (!abl.dryrun) probe.preheat_for_probing(LEVELING_NOZZLE_TEMP, - #if ALL(DWIN_LCD_PROUI, HAS_HEATED_BED) - hmiData.bedLevT - #else - LEVELING_BED_TEMP - #endif + TERN(EXTENSIBLE_UI, ExtUI::getLevelingBedTemp(), LEVELING_BED_TEMP) ); #endif } // Position bed horizontally and Z probe vertically. #if HAS_SAFE_BED_LEVELING - xyze_pos_t safe_position = current_position; + xyze_pos_t safe_position = motion.position; #ifdef SAFE_BED_LEVELING_START_X safe_position.x = SAFE_BED_LEVELING_START_X; #endif @@ -476,7 +503,7 @@ G29_TYPE GcodeSuite::G29() { safe_position.w = SAFE_BED_LEVELING_START_W; #endif - do_blocking_move_to(safe_position); + motion.blocking_move(safe_position); #endif // HAS_SAFE_BED_LEVELING // Disable auto bed leveling during G29. @@ -485,7 +512,7 @@ G29_TYPE GcodeSuite::G29() { // Deploy certain probes before starting probing #if ENABLED(BLTOUCH) || ALL(HAS_Z_SERVO_PROBE, Z_SERVO_INTERMEDIATE_STOW) - do_z_clearance(Z_CLEARANCE_DEPLOY_PROBE); + motion.do_z_clearance(Z_CLEARANCE_DEPLOY_PROBE); #elif HAS_BED_PROBE if (probe.deploy()) { // (returns true on deploy failure) set_bed_leveling_enabled(abl.reenable); @@ -516,7 +543,7 @@ G29_TYPE GcodeSuite::G29() { // Abort current G29 procedure, go back to idle state if (seenA && g29_in_progress) { SERIAL_ECHOLNPGM("Manual G29 aborted"); - SET_SOFT_ENDSTOP_LOOSE(false); + motion.set_soft_endstop_loose(false); set_bed_leveling_enabled(abl.reenable); g29_in_progress = false; TERN_(LCD_BED_LEVELING, ui.wait_for_move = false); @@ -536,9 +563,9 @@ G29_TYPE GcodeSuite::G29() { if (abl.abl_probe_index == 0) { // For the initial G29 S2 save software endstop state - SET_SOFT_ENDSTOP_LOOSE(true); + motion.set_soft_endstop_loose(true); // Move close to the bed before the first point - do_blocking_move_to_z(0); + motion.blocking_move_z(0); } else { @@ -548,7 +575,7 @@ G29_TYPE GcodeSuite::G29() { // For G29 after adjusting Z. // Save the previous Z before going to the next point - abl.measured_z = current_position.z; + abl.measured_z = motion.position.z; #if ENABLED(AUTO_BED_LEVELING_LINEAR) @@ -597,7 +624,7 @@ G29_TYPE GcodeSuite::G29() { TERN_(AUTO_BED_LEVELING_LINEAR, abl.indexIntoAB[abl.meshCount.x][abl.meshCount.y] = abl.abl_probe_index); // Keep looping till a reachable point is found - if (position_is_reachable(abl.probePos)) break; + if (motion.can_reach(abl.probePos)) break; ++abl.abl_probe_index; } @@ -606,14 +633,14 @@ G29_TYPE GcodeSuite::G29() { _manual_goto_xy(abl.probePos); // Can be used here too! // Disable software endstops to allow manual adjustment // If G29 is not completed, they will not be re-enabled - SET_SOFT_ENDSTOP_LOOSE(true); + motion.set_soft_endstop_loose(true); G29_RETURN(false, true); } else { // Leveling done! Fall through to G29 finishing code below SERIAL_ECHOLNPGM("Grid probing done."); // Re-enable software endstops, if needed - SET_SOFT_ENDSTOP_LOOSE(false); + motion.set_soft_endstop_loose(false); } #elif ENABLED(AUTO_BED_LEVELING_3POINT) @@ -624,7 +651,7 @@ G29_TYPE GcodeSuite::G29() { _manual_goto_xy(abl.probePos); // Disable software endstops to allow manual adjustment // If G29 is not completed, they will not be re-enabled - SET_SOFT_ENDSTOP_LOOSE(true); + motion.set_soft_endstop_loose(true); G29_RETURN(false, true); } else { @@ -632,7 +659,7 @@ G29_TYPE GcodeSuite::G29() { SERIAL_ECHOLNPGM("3-point probing done."); // Re-enable software endstops, if needed - SET_SOFT_ENDSTOP_LOOSE(false); + motion.set_soft_endstop_loose(false); if (!abl.dryrun) { vector_3 planeNormal = vector_3::cross(points[0] - points[1], points[2] - points[1]).get_normal(); @@ -661,6 +688,8 @@ G29_TYPE GcodeSuite::G29() { // Outer loop is Y with PROBE_Y_FIRST disabled for (PR_OUTER_VAR = 0; PR_OUTER_VAR < PR_OUTER_SIZE && !isnan(abl.measured_z); PR_OUTER_VAR++) { + if (TERN0(DWIN_LCD_PROUI, hmiFlag.cancel_lev)) break; + int8_t inStart, inStop, inInc; if (zig) { // Zig away from origin @@ -674,7 +703,7 @@ G29_TYPE GcodeSuite::G29() { inInc = -1; // Zag left } - zig ^= true; // zag + FLIP(zig); // zag // An index to print current state grid_count_t pt_index = (PR_OUTER_VAR) * (PR_INNER_SIZE) + 1; @@ -695,7 +724,6 @@ G29_TYPE GcodeSuite::G29() { #if ENABLED(BD_SENSOR_PROBE_NO_STOP) if (PR_INNER_VAR == inStart) { - char tmp_1[32]; // move to the start point of new line abl.measured_z = faux ? 0.001f * random(-100, 101) : probe.probe_at_point(abl.probePos, raise_after, abl.verbose_level); @@ -708,29 +736,26 @@ G29_TYPE GcodeSuite::G29() { abl.probePos = abl.probe_position_lf + abl.gridSpacing * abl.meshCount.asFloat(); // Coordinate that puts the probe at the grid point - abl.probePos -= probe.offset_xy; + const xy_pos_t nozPos = abl.probePos - probe.offset_xy; + if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPGM("BD X", nozPos.x, " Y", nozPos.y); // Put a G1 move into the buffer // TODO: Instead of G1, we can just add the move directly to the planner... // { - // destination = current_position; destination = abl.probePos; - // REMEMBER(fr, feedrate_mm_s, XY_PROBE_FEEDRATE_MM_S); - // prepare_line_to_destination(); + // motion.destination = motion.position; motion.destination = nozPos; + // REMEMBER(fr, motion.feedrate_mm_s, XY_PROBE_FEEDRATE_MM_S); + // motion.prepare_line_to_destination(); // } - sprintf_P(tmp_1, PSTR("G1X%d.%d Y%d.%d F%d"), - int(abl.probePos.x), int(abl.probePos.x * 10) % 10, - int(abl.probePos.y), int(abl.probePos.y * 10) % 10, - XY_PROBE_FEEDRATE + gcode.process_subcommands_now( + MString<31>{}.setf_P(PSTR("G1X%s Y%s F%d"), + p_float_t(nozPos.x, 1), + p_float_t(nozPos.y, 1), + XY_PROBE_FEEDRATE + ) ); - gcode.process_subcommands_now(tmp_1); - - if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPGM("destX: ", abl.probePos.x, " Y:", abl.probePos.y); // Reset the inner counter back to the start PR_INNER_VAR = inStart; - - // Get the coordinate of the start of the row/column - abl.probePos = abl.probe_position_lf + abl.gridSpacing * abl.meshCount.asFloat(); } // Wait around until the real axis position reaches the comparison point @@ -741,12 +766,12 @@ G29_TYPE GcodeSuite::G29() { for (;;) { pos = planner.get_axis_position_mm(axis); if (inInc > 0 ? (pos >= cmp) : (pos <= cmp)) break; - idle_no_sleep(); + marlin.idle_no_sleep(); } //if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM_P(axis == Y_AXIS ? PSTR("Y=") : PSTR("X=", pos); safe_delay(4); - abl.measured_z = current_position.z - bdl.read(); + abl.measured_z = motion.position.z - bdl.read(); if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPGM("x_cur ", planner.get_axis_position_mm(X_AXIS), " z ", abl.measured_z); #else // !BD_SENSOR_PROBE_NO_STOP @@ -776,10 +801,18 @@ G29_TYPE GcodeSuite::G29() { abl.z_values[abl.meshCount.x][abl.meshCount.y] = z; TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(abl.meshCount, z)); + #if ENABLED(SOVOL_SV06_RTS) + if (pt_index <= GRID_MAX_POINTS) rts.sendData(pt_index, AUTO_BED_LEVEL_ICON_VP); + rts.sendData(z * 100.0f, AUTO_BED_LEVEL_1POINT_VP + (pt_index - 1) * 2); + rts.gotoPage(ID_ABL_Wait_L, ID_ABL_Wait_D); + #endif + #endif abl.reenable = false; // Don't re-enable after modifying the mesh - idle_no_sleep(); + marlin.idle_no_sleep(); + + if (TERN0(DWIN_LCD_PROUI, hmiFlag.cancel_lev)) break; } // inner } // outer @@ -813,7 +846,7 @@ G29_TYPE GcodeSuite::G29() { #endif // AUTO_BED_LEVELING_3POINT - TERN_(HAS_STATUS_MESSAGE, ui.reset_status()); + ui.reset_status(); // Stow the probe. No raise for FIX_MOUNTED_PROBE. if (probe.stow()) { @@ -823,17 +856,17 @@ G29_TYPE GcodeSuite::G29() { } #endif // !PROBE_MANUALLY - // - // G29 Finishing Code - // - // Unless this is a dry run, auto bed leveling will - // definitely be enabled after this point. - // - // If code above wants to continue leveling, it should - // return or loop before this point. - // + /** + * G29 Finishing Code + * + * Unless this is a dry run, auto bed leveling will + * definitely be enabled after this point. + * + * If code above wants to continue leveling, it should + * return or loop before this point. + */ - if (DEBUGGING(LEVELING)) DEBUG_POS("> probing complete", current_position); + if (DEBUGGING(LEVELING)) DEBUG_POS("> probing complete", motion.position); #if ENABLED(PROBE_MANUALLY) g29_in_progress = false; @@ -860,12 +893,12 @@ G29_TYPE GcodeSuite::G29() { // For LINEAR leveling calculate matrix, print reports, correct the position /** - * solve the plane equation ax + by + d = z + * Solve the plane equation ax + by + d = z * A is the matrix with rows [x y 1] for all the probed points * B is the vector of the Z positions - * the normal vector to the plane is formed by the coefficients of the + * The normal vector to the plane is formed by the coefficients of the * plane equation in the standard form, which is Vx*x+Vy*y+Vz*z+d = 0 - * so Vx = -a Vy = -b Vz = 1 (we want the vector facing towards positive Z + * so Vx = -a Vy = -b Vz = 1 (we want the vector facing towards positive Z). */ struct { float a, b, d; } plane_equation_coefficients; @@ -945,24 +978,24 @@ G29_TYPE GcodeSuite::G29() { // Correct the current XYZ position based on the tilted plane. // - if (DEBUGGING(LEVELING)) DEBUG_POS("G29 uncorrected XYZ", current_position); + if (DEBUGGING(LEVELING)) DEBUG_POS("G29 uncorrected XYZ", motion.position); - xyze_pos_t converted = current_position; + xyze_pos_t converted = motion.position; planner.force_unapply_leveling(converted); // use conversion machinery // Use the last measured distance to the bed, if possible - if ( NEAR(current_position.x, abl.probePos.x - probe.offset_xy.x) - && NEAR(current_position.y, abl.probePos.y - probe.offset_xy.y) + if ( NEAR(motion.position.x, abl.probePos.x - probe.offset_xy.x) + && NEAR(motion.position.y, abl.probePos.y - probe.offset_xy.y) ) { - const float simple_z = current_position.z - abl.measured_z; + const float simple_z = motion.position.z - abl.measured_z; if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("Probed Z", simple_z, " Matrix Z", converted.z, " Discrepancy ", simple_z - converted.z); converted.z = simple_z; } - // The rotated XY and corrected Z are now current_position - current_position = converted; + // The rotated XY and corrected Z are now motion.position + motion.position = converted; - if (DEBUGGING(LEVELING)) DEBUG_POS("G29 corrected XYZ", current_position); + if (DEBUGGING(LEVELING)) DEBUG_POS("G29 corrected XYZ", motion.position); abl.reenable = true; } @@ -970,7 +1003,7 @@ G29_TYPE GcodeSuite::G29() { // Auto Bed Leveling is complete! Enable if possible. if (abl.reenable) { planner.leveling_active = true; - sync_plan_position(); + motion.sync_plan_position(); } #elif ENABLED(AUTO_BED_LEVELING_BILINEAR) @@ -983,19 +1016,21 @@ G29_TYPE GcodeSuite::G29() { } // !isnan(abl.measured_z) // Restore state after probing - if (!faux) restore_feedrate_and_scaling(); + if (!faux) motion.restore_feedrate_and_scaling(); - TERN_(HAS_BED_PROBE, probe.move_z_after_probing()); + probe.move_z_after_probing(); #ifdef EVENT_GCODE_AFTER_G29 - if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("Z Probe End Script: ", EVENT_GCODE_AFTER_G29); + if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("After G29 G-code: ", EVENT_GCODE_AFTER_G29); planner.synchronize(); process_subcommands_now(F(EVENT_GCODE_AFTER_G29)); #endif + TERN_(SOVOL_SV06_RTS, RTS_AutoBedLevelPage()); + probe.use_probing_tool(false); - report_current_position(); + motion.report_position(); G29_RETURN(isnan(abl.measured_z), true); } diff --git a/Marlin/src/gcode/bedlevel/mbl/G29.cpp b/Marlin/src/gcode/bedlevel/mbl/G29.cpp index b3f03d03ba43..b8087f9ec26c 100644 --- a/Marlin/src/gcode/bedlevel/mbl/G29.cpp +++ b/Marlin/src/gcode/bedlevel/mbl/G29.cpp @@ -40,13 +40,15 @@ #if ENABLED(EXTENSIBLE_UI) #include "../../../lcd/extui/ui_api.h" -#elif ENABLED(DWIN_LCD_PROUI) - #include "../../../lcd/e3v2/proui/dwin.h" #endif #define DEBUG_OUT ENABLED(DEBUG_LEVELING_FEATURE) #include "../../../core/debug_out.h" +#if ENABLED(FT_MOTION) + #include "../../../module/ft_motion.h" +#endif + // Save 130 bytes with non-duplication of PSTR inline void echo_not_entered(const char c) { SERIAL_CHAR(c); SERIAL_ECHOLNPGM(" not entered."); } @@ -65,6 +67,9 @@ inline void echo_not_entered(const char c) { SERIAL_CHAR(c); SERIAL_ECHOLNPGM(" */ void GcodeSuite::G29() { + // Potentially disable Fixed-Time Motion for probing + TERN_(FT_MOTION, FTM_DISABLE_IN_SCOPE()); + DEBUG_SECTION(log_G29, "G29", true); // G29 Q is also available if debugging @@ -84,7 +89,7 @@ void GcodeSuite::G29() { return; } - TERN_(FULL_REPORT_TO_HOST_FEATURE, set_and_report_grblstate(M_PROBE)); + TERN_(FULL_REPORT_TO_HOST_FEATURE, motion.set_and_report_grblstate(M_PROBE)); int8_t ix, iy; ix = iy = 0; @@ -93,7 +98,7 @@ void GcodeSuite::G29() { case MeshReport: SERIAL_ECHOPGM("Mesh Bed Leveling "); if (leveling_is_valid()) { - serialprintln_onoff(planner.leveling_active); + SERIAL_ECHOLN(ON_OFF(planner.leveling_active)); bedlevel.report_mesh(); } else @@ -109,7 +114,7 @@ void GcodeSuite::G29() { // Position bed horizontally and Z probe vertically. #if HAS_SAFE_BED_LEVELING - xyze_pos_t safe_position = current_position; + xyze_pos_t safe_position = motion.position; #ifdef SAFE_BED_LEVELING_START_X safe_position.x = SAFE_BED_LEVELING_START_X; #endif @@ -138,13 +143,12 @@ void GcodeSuite::G29() { safe_position.w = SAFE_BED_LEVELING_START_W; #endif - do_blocking_move_to(safe_position); + motion.blocking_move(safe_position); #endif // HAS_SAFE_BED_LEVELING queue.inject(F("G29S2")); TERN_(EXTENSIBLE_UI, ExtUI::onLevelingStart()); - TERN_(DWIN_LCD_PROUI, dwinLevelingStart()); return; } @@ -158,7 +162,7 @@ void GcodeSuite::G29() { // For each G29 S2... if (mbl_probe_index == 0) { // Move close to the bed before the first point - do_blocking_move_to_z( + motion.blocking_move_z( #ifdef MANUAL_PROBE_START_Z MANUAL_PROBE_START_Z #else @@ -168,29 +172,28 @@ void GcodeSuite::G29() { } else { // Save Z for the previous mesh position - bedlevel.set_zigzag_z(mbl_probe_index - 1, current_position.z); - TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(ix, iy, current_position.z)); - TERN_(DWIN_LCD_PROUI, dwinMeshUpdate(_MIN(mbl_probe_index, GRID_MAX_POINTS), int(GRID_MAX_POINTS), current_position.z)); - SET_SOFT_ENDSTOP_LOOSE(false); + bedlevel.set_zigzag_z(mbl_probe_index - 1, motion.position.z); + TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(ix, iy, motion.position.z)); + motion.set_soft_endstop_loose(false); } // If there's another point to sample, move there with optional lift. if (mbl_probe_index < GRID_MAX_POINTS) { // Disable software endstops to allow manual adjustment // If G29 is left hanging without completion they won't be re-enabled! - SET_SOFT_ENDSTOP_LOOSE(true); + motion.set_soft_endstop_loose(true); bedlevel.zigzag(mbl_probe_index++, ix, iy); _manual_goto_xy({ bedlevel.index_to_xpos[ix], bedlevel.index_to_ypos[iy] }); } else { // Move to the after probing position - current_position.z = ( + motion.position.z = ( #ifdef Z_AFTER_PROBING Z_AFTER_PROBING #else Z_CLEARANCE_BETWEEN_MANUAL_PROBES #endif ); - line_to_current_position(); + motion.goto_current_position(); planner.synchronize(); // After recording the last point, activate home and activate @@ -203,8 +206,8 @@ void GcodeSuite::G29() { set_bed_leveling_enabled(true); #if ENABLED(MESH_G28_REST_ORIGIN) - current_position.z = 0; - line_to_current_position(homing_feedrate(Z_AXIS)); + motion.position.z = 0; + motion.goto_current_position(motion.homing_feedrate(Z_AXIS)); planner.synchronize(); #endif @@ -222,7 +225,7 @@ void GcodeSuite::G29() { } } else - return echo_not_entered('J'); + return echo_not_entered('I'); if (parser.seenval('J')) { iy = parser.value_int(); @@ -237,7 +240,6 @@ void GcodeSuite::G29() { if (parser.seenval('Z')) { bedlevel.z_values[ix][iy] = parser.value_linear_units(); TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(ix, iy, bedlevel.z_values[ix][iy])); - TERN_(DWIN_LCD_PROUI, dwinMeshUpdate(ix, iy, bedlevel.z_values[ix][iy])); } else return echo_not_entered('Z'); @@ -261,9 +263,9 @@ void GcodeSuite::G29() { if (mbl_probe_index > 0) TERN_(HAS_STATUS_MESSAGE, ui.status_printf(0, F(S_FMT " %i/%i"), GET_TEXT(MSG_PROBING_POINT), _MIN(mbl_probe_index, GRID_MAX_POINTS), int(GRID_MAX_POINTS))); } - report_current_position(); + motion.report_position(); - TERN_(FULL_REPORT_TO_HOST_FEATURE, set_and_report_grblstate(M_IDLE)); + TERN_(FULL_REPORT_TO_HOST_FEATURE, motion.set_and_report_grblstate(M_IDLE)); } #endif // MESH_BED_LEVELING diff --git a/Marlin/src/gcode/bedlevel/mbl/M421.cpp b/Marlin/src/gcode/bedlevel/mbl/M421.cpp index e23683d55f34..087c2c10271d 100644 --- a/Marlin/src/gcode/bedlevel/mbl/M421.cpp +++ b/Marlin/src/gcode/bedlevel/mbl/M421.cpp @@ -43,14 +43,14 @@ */ void GcodeSuite::M421() { const bool hasX = parser.seen('X'), hasI = parser.seen('I'); - const int8_t ix = hasI ? parser.value_int() : hasX ? bedlevel.probe_index_x(RAW_X_POSITION(parser.value_linear_units())) : -1; + const int8_t ix = hasI ? parser.value_int() : hasX ? bedlevel.probe_index_x(motion.raw_x(parser.value_linear_units())) : -1; const bool hasY = parser.seen('Y'), hasJ = parser.seen('J'); - const int8_t iy = hasJ ? parser.value_int() : hasY ? bedlevel.probe_index_y(RAW_Y_POSITION(parser.value_linear_units())) : -1; + const int8_t iy = hasJ ? parser.value_int() : hasY ? bedlevel.probe_index_y(motion.raw_y(parser.value_linear_units())) : -1; const bool hasZ = parser.seen('Z'), hasQ = !hasZ && parser.seen('Q'); if (int(hasI && hasJ) + int(hasX && hasY) != 1 || !(hasZ || hasQ)) SERIAL_ERROR_MSG(STR_ERR_M421_PARAMETERS); - else if (ix < 0 || iy < 0) + else if (!WITHIN(ix, 0, GRID_MAX_POINTS_X - 1) || !WITHIN(iy, 0, GRID_MAX_POINTS_Y - 1)) SERIAL_ERROR_MSG(STR_ERR_MESH_XY); else bedlevel.set_z(ix, iy, parser.value_linear_units() + (hasQ ? bedlevel.z_values[ix][iy] : 0)); diff --git a/Marlin/src/gcode/bedlevel/ubl/G29.cpp b/Marlin/src/gcode/bedlevel/ubl/G29.cpp index 90deab3d2e3e..aaac7f145bec 100644 --- a/Marlin/src/gcode/bedlevel/ubl/G29.cpp +++ b/Marlin/src/gcode/bedlevel/ubl/G29.cpp @@ -37,11 +37,11 @@ void GcodeSuite::G29() { - TERN_(FULL_REPORT_TO_HOST_FEATURE, set_and_report_grblstate(M_PROBE)); + TERN_(FULL_REPORT_TO_HOST_FEATURE, motion.set_and_report_grblstate(M_PROBE)); bedlevel.G29(); - TERN_(FULL_REPORT_TO_HOST_FEATURE, set_and_report_grblstate(M_IDLE)); + TERN_(FULL_REPORT_TO_HOST_FEATURE, motion.set_and_report_grblstate(M_IDLE)); } #endif // AUTO_BED_LEVELING_UBL diff --git a/Marlin/src/gcode/bedlevel/ubl/M421.cpp b/Marlin/src/gcode/bedlevel/ubl/M421.cpp index 3a5aa8cb038c..fab13e4d844b 100644 --- a/Marlin/src/gcode/bedlevel/ubl/M421.cpp +++ b/Marlin/src/gcode/bedlevel/ubl/M421.cpp @@ -33,8 +33,6 @@ #if ENABLED(EXTENSIBLE_UI) #include "../../../lcd/extui/ui_api.h" -#elif ENABLED(DWIN_LCD_PROUI) - #include "../../../lcd/e3v2/proui/dwin.h" #endif /** @@ -56,7 +54,7 @@ void GcodeSuite::M421() { hasZ = parser.seen('Z'), hasQ = !hasZ && parser.seen('Q'); - if (hasC) ij = bedlevel.find_closest_mesh_point_of_type(CLOSEST, current_position); + if (hasC) ij = bedlevel.find_closest_mesh_point_of_type(CLOSEST, motion.position); // Test for bad parameter combinations if (int(hasC) + int(hasI && hasJ) != 1 || !(hasZ || hasQ || hasN)) @@ -69,7 +67,6 @@ void GcodeSuite::M421() { float &zval = bedlevel.z_values[ij.x][ij.y]; // Altering this Mesh Point zval = hasN ? NAN : parser.value_linear_units() + (hasQ ? zval : 0); // N=NAN, Z=NEWVAL, or Q=ADDVAL TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(ij.x, ij.y, zval)); // Ping ExtUI in case it's showing the mesh - TERN_(DWIN_LCD_PROUI, dwinMeshUpdate(ij.x, ij.y, zval)); } } diff --git a/Marlin/src/gcode/calibrate/G28.cpp b/Marlin/src/gcode/calibrate/G28.cpp index 55698c942bd4..2bd0149e2449 100644 --- a/Marlin/src/gcode/calibrate/G28.cpp +++ b/Marlin/src/gcode/calibrate/G28.cpp @@ -28,6 +28,10 @@ #include "../../module/planner.h" #include "../../module/stepper.h" // for various +#if HAS_HOMING_CURRENT + #include "../../module/motion.h" // for set/restore_homing_current +#endif + #if HAS_MULTI_HOTEND #include "../../module/tool_change.h" #endif @@ -40,20 +44,26 @@ #include "../../feature/tmc_util.h" #endif -#include "../../module/probe.h" +#if HAS_BED_PROBE + #include "../../module/probe.h" +#endif #if ENABLED(BLTOUCH) #include "../../feature/bltouch.h" #endif +#if ENABLED(FT_MOTION) + #include "../../module/ft_motion.h" +#endif + #include "../../lcd/marlinui.h" #if ENABLED(EXTENSIBLE_UI) #include "../../lcd/extui/ui_api.h" #elif ENABLED(DWIN_CREALITY_LCD) - #include "../../lcd/e3v2/creality/dwin.h" -#elif ENABLED(DWIN_LCD_PROUI) - #include "../../lcd/e3v2/proui/dwin.h" + #include "../../lcd/dwin/creality/dwin.h" +#elif ENABLED(SOVOL_SV06_RTS) + #include "../../lcd/sovol_rts/sovol_rts.h" #endif #if ENABLED(LASER_FEATURE) @@ -68,15 +78,21 @@ static void quick_home_xy() { // Pretend the current position is 0,0 - current_position.set(0.0, 0.0); - sync_plan_position(); + motion.position.set(0.0, 0.0); + motion.sync_plan_position(); - const int x_axis_home_dir = TOOL_X_HOME_DIR(active_extruder); + const int x_axis_home_dir = motion.tool_x_home_dir(); // Use a higher diagonal feedrate so axes move at homing speed - const float minfr = _MIN(homing_feedrate(X_AXIS), homing_feedrate(Y_AXIS)), + const float minfr = _MIN(motion.homing_feedrate(X_AXIS), motion.homing_feedrate(Y_AXIS)), fr_mm_s = HYPOT(minfr, minfr); + // Set homing current to X and Y axis if defined + TERN_(X_HAS_HOME_CURRENT, motion.set_homing_current(X_AXIS)); + #if Y_HAS_HOME_CURRENT && NONE(CORE_IS_XY, MARKFORGED_XY, MARKFORGED_YX) + motion.set_homing_current(Y_AXIS); + #endif + #if ENABLED(SENSORLESS_HOMING) sensorless_t stealth_states { NUM_AXIS_LIST( @@ -89,11 +105,35 @@ }; #endif - do_blocking_move_to_xy(1.5 * max_length(X_AXIS) * x_axis_home_dir, 1.5 * max_length(Y_AXIS) * Y_HOME_DIR, fr_mm_s); + // SCARA should move in angular coordinates + #if IS_SCARA + // This code is similar to Motion::do_homing_move, but for two axes at once + + #if HAS_DIST_MM_ARG + const xyze_float_t cart_dist_mm{0}; + #endif + + abce_pos_t target = planner.get_axis_positions_mm(); + + target[X_AXIS] += 360 * x_axis_home_dir; // Move 360 degrees towards the endstop + target[Y_AXIS] += 360 * Y_HOME_DIR; // Move 360 degrees towards the endstop + planner.buffer_segment(target OPTARG(HAS_DIST_MM_ARG, cart_dist_mm), fr_mm_s, motion.extruder); + planner.synchronize(); + + #else + + motion.blocking_move_xy(1.5 * motion.max_axis_length(X_AXIS) * x_axis_home_dir, 1.5 * motion.max_axis_length(Y_AXIS) * Y_HOME_DIR, fr_mm_s); + + #endif endstops.validate_homing_move(); - current_position.set(0.0, 0.0); + motion.position.set(0.0, 0.0); + + TERN_(X_HAS_HOME_CURRENT, motion.restore_homing_current(X_AXIS)); + #if Y_HAS_HOME_CURRENT && NONE(CORE_IS_XY, MARKFORGED_XY, MARKFORGED_YX) + motion.restore_homing_current(Y_AXIS); + #endif #if ENABLED(SENSORLESS_HOMING) && DISABLED(ENDSTOPS_ALWAYS_ON_DEFAULT) TERN_(X_SENSORLESS, tmc_disable_stallguard(stepperX, stealth_states.x)); @@ -111,30 +151,33 @@ DEBUG_SECTION(log_G28, "home_z_safely", DEBUGGING(LEVELING)); // Disallow Z homing if X or Y homing is needed - if (homing_needed_error(_BV(X_AXIS) | _BV(Y_AXIS))) return; + if (motion.homing_needed_error(_BV(X_AXIS) | _BV(Y_AXIS))) return; - sync_plan_position(); + // Potentially disable Fixed-Time Motion for homing + TERN_(FT_MOTION, FTM_DISABLE_IN_SCOPE()); + + motion.sync_plan_position(); /** * Move the Z probe (or just the nozzle) to the safe homing point * (Z is already at the right height) */ constexpr xy_float_t safe_homing_xy = { Z_SAFE_HOMING_X_POINT, Z_SAFE_HOMING_Y_POINT }; - destination.set(safe_homing_xy, current_position.z); + motion.destination.set(safe_homing_xy, motion.position.z); - TERN_(HOMING_Z_WITH_PROBE, destination -= probe.offset_xy); + TERN_(HOMING_Z_WITH_PROBE, motion.destination -= probe.offset_xy); - if (position_is_reachable(destination)) { + if (motion.can_reach(motion.destination)) { - if (DEBUGGING(LEVELING)) DEBUG_POS("home_z_safely", destination); + if (DEBUGGING(LEVELING)) DEBUG_POS("home_z_safely", motion.destination); // Free the active extruder for movement - TERN_(DUAL_X_CARRIAGE, idex_set_parked(false)); + TERN_(DUAL_X_CARRIAGE, motion.idex_set_parked(false)); TERN_(SENSORLESS_HOMING, safe_delay(500)); // Short delay needed to settle - do_blocking_move_to_xy(destination); - homeaxis(Z_AXIS); + motion.blocking_move_xy(motion.destination); + motion.homeaxis(Z_AXIS); } else { LCD_MESSAGE(MSG_ZPROBE_OUT); @@ -174,18 +217,24 @@ #endif // IMPROVE_HOMING_RELIABILITY /** - * G28: Home all axes according to settings + * G28: Auto Home * - * Parameters + * Home all axes according to settings * - * None Home to all axes with no parameters. + * Parameters: + * None Home all axes * With QUICK_HOME enabled XY will home together, then Z. * * L Force leveling state ON (if possible) or OFF after homing (Requires RESTORE_LEVELING_AFTER_G28 or ENABLE_LEVELING_AFTER_G28) * O Home only if the position is not known and trusted * R Raise by n mm/inches before homing + * H Hold the current X/Y position when executing a home Z, or if + * multiple axes are homed, the position when Z home is executed. + * When using a probe for Z Home, positions close to the edge may + * fail with position unreachable due to probe/nozzle offset. This + * can be used to avoid a model. * - * Cartesian/SCARA parameters + * Cartesian/SCARA parameters: * * X Home to the X endstop * Y Home to the Y endstop @@ -197,10 +246,10 @@ void GcodeSuite::G28() { #if ENABLED(MARLIN_DEV_MODE) if (parser.seen_test('S')) { - LOOP_NUM_AXES(a) set_axis_is_at_home((AxisEnum)a); - sync_plan_position(); + LOOP_NUM_AXES(a) motion.set_axis_is_at_home((AxisEnum)a); + motion.sync_plan_position(); SERIAL_ECHOLNPGM("Simulated Homing"); - report_current_position(); + motion.report_position(); return; } #endif @@ -213,17 +262,17 @@ void GcodeSuite::G28() { #endif // Home (O)nly if position is unknown - if (!axes_should_home() && parser.seen_test('O')) { + if (!motion.axes_should_home() && parser.seen_test('O')) { if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("> homing not needed, skip"); return; } #if ENABLED(FULL_REPORT_TO_HOST_FEATURE) - const M_StateEnum old_grblstate = M_State_grbl; - set_and_report_grblstate(M_HOMING); + const M_StateEnum old_grblstate = motion.M_State_grbl; + motion.set_and_report_grblstate(M_HOMING); #endif - TERN_(HAS_DWIN_E3V2_BASIC, dwinHomingStart()); + TERN_(DWIN_CREALITY_LCD, dwinHomingStart()); TERN_(EXTENSIBLE_UI, ExtUI::onHomingStart()); planner.synchronize(); // Wait for planner moves to finish! @@ -234,11 +283,11 @@ void GcodeSuite::G28() { #if NUM_AXES #if ENABLED(DUAL_X_CARRIAGE) - bool IDEX_saved_duplication_state = extruder_duplication_enabled; - DualXMode IDEX_saved_mode = dual_x_carriage_mode; + bool IDEX_saved_duplication_state = motion.extruder_duplication; + DualXMode IDEX_saved_mode = motion.idex_mode; #endif - SET_SOFT_ENDSTOP_LOOSE(false); // Reset a leftover 'loose' motion state + motion.set_soft_endstop_loose(false); // Reset a leftover 'loose' motion state // Disable the leveling matrix before homing #if CAN_SET_LEVELING_AFTER_G28 @@ -254,92 +303,34 @@ void GcodeSuite::G28() { // Reset to the XY plane TERN_(CNC_WORKSPACE_PLANES, workspace_plane = PLANE_XY); - #define _OR_HAS_CURR_HOME(N) HAS_CURRENT_HOME(N) || - #if MAIN_AXIS_MAP(_OR_HAS_CURR_HOME) MAP(_OR_HAS_CURR_HOME, X2, Y2, Z2, Z3, Z4) 0 - #define HAS_HOMING_CURRENT 1 + #if ENABLED(IMPROVE_HOMING_RELIABILITY) + motion_state_t saved_motion_state = begin_slow_homing(); #endif - #if HAS_HOMING_CURRENT - - #if ENABLED(DEBUG_LEVELING_FEATURE) - auto debug_current = [](FSTR_P const s, const int16_t a, const int16_t b) { - if (DEBUGGING(LEVELING)) { DEBUG_ECHOLN(s, F(" current: "), a, F(" -> "), b); } - }; - #else - #define debug_current(...) - #endif + // Potentially disable Fixed-Time Motion for homing + TERN_(FT_MOTION, FTM_DISABLE_IN_SCOPE()); - #define _SAVE_SET_CURRENT(A) \ - const int16_t saved_current_##A = stepper##A.getMilliamps(); \ - stepper##A.rms_current(A##_CURRENT_HOME); \ - debug_current(F(STR_##A), saved_current_##A, A##_CURRENT_HOME) - - #if HAS_CURRENT_HOME(X) - _SAVE_SET_CURRENT(X); - #endif - #if HAS_CURRENT_HOME(X2) - _SAVE_SET_CURRENT(X2); - #endif - #if HAS_CURRENT_HOME(Y) - _SAVE_SET_CURRENT(Y); - #endif - #if HAS_CURRENT_HOME(Y2) - _SAVE_SET_CURRENT(Y2); - #endif - #if HAS_CURRENT_HOME(Z) - _SAVE_SET_CURRENT(Z); - #endif - #if HAS_CURRENT_HOME(Z2) - _SAVE_SET_CURRENT(Z2); - #endif - #if HAS_CURRENT_HOME(Z3) - _SAVE_SET_CURRENT(Z3); - #endif - #if HAS_CURRENT_HOME(Z4) - _SAVE_SET_CURRENT(Z4); - #endif - #if HAS_CURRENT_HOME(I) - _SAVE_SET_CURRENT(I); - #endif - #if HAS_CURRENT_HOME(J) - _SAVE_SET_CURRENT(J); - #endif - #if HAS_CURRENT_HOME(K) - _SAVE_SET_CURRENT(K); - #endif - #if HAS_CURRENT_HOME(U) - _SAVE_SET_CURRENT(U); - #endif - #if HAS_CURRENT_HOME(V) - _SAVE_SET_CURRENT(V); - #endif - #if HAS_CURRENT_HOME(W) - _SAVE_SET_CURRENT(W); - #endif - #if SENSORLESS_STALLGUARD_DELAY - safe_delay(SENSORLESS_STALLGUARD_DELAY); // Short delay needed to settle - #endif - #endif // HAS_HOMING_CURRENT - - #if ENABLED(IMPROVE_HOMING_RELIABILITY) - motion_state_t saved_motion_state = begin_slow_homing(); + #if ENABLED(CNC_COORDINATE_SYSTEMS) + const int8_t old_coordinate_system = active_coordinate_system; + (void)select_coordinate_system(-1); #endif // Always home with tool 0 active #if HAS_MULTI_HOTEND #if DISABLED(DELTA) || ENABLED(DELTA_HOME_TO_SAFE_ZONE) - const uint8_t old_tool_index = active_extruder; + const uint8_t old_tool_index = motion.extruder; #endif // PARKING_EXTRUDER homing requires different handling of movement / solenoid activation, depending on the side of homing #if ENABLED(PARKING_EXTRUDER) - const bool pe_final_change_must_unpark = parking_extruder_unpark_after_homing(old_tool_index, X_HOME_DIR + 1 == old_tool_index * 2); + const bool homed_towards_tool = old_tool_index == TERN(X_HOME_TO_MIN, 0, 1), + pe_final_change_must_unpark = parking_extruder_unpark_after_homing(old_tool_index, homed_towards_tool); #endif tool_change(0, true); #endif - TERN_(HAS_DUPLICATION_MODE, set_duplication_enabled(false)); + TERN_(HAS_DUPLICATION_MODE, motion.set_extruder_duplication(false)); - remember_feedrate_scaling_off(); + motion.remember_feedrate_scaling_off(); endstops.enable(true); // Enable endstops for next homing move @@ -363,9 +354,9 @@ void GcodeSuite::G28() { #else // !DELTA && !AXEL_TPARA - #define _UNSAFE(A) (homeZ && TERN0(Z_SAFE_HOMING, axes_should_home(_BV(A##_AXIS)))) + #define _UNSAFE(A) TERN0(Z_SAFE_HOMING, homeZZ && motion.axis_should_home(_AXIS(A))) - const bool homeZ = TERN0(HAS_Z_AXIS, parser.seen_test('Z')), + const bool homeZZ = TERN0(HAS_Z_AXIS, parser.seen_test('Z')), NUM_AXIS_LIST_( // Other axes should be homed before Z safe-homing needX = _UNSAFE(X), needY = _UNSAFE(Y), needZ = false, // UNUSED needI = _UNSAFE(I), needJ = _UNSAFE(J), needK = _UNSAFE(K), @@ -374,7 +365,7 @@ void GcodeSuite::G28() { NUM_AXIS_LIST_( // Home each axis if needed or flagged homeX = needX || parser.seen_test('X'), homeY = needY || parser.seen_test('Y'), - homeZZ = homeZ, + homeZ = homeZZ, homeI = needI || parser.seen_test(AXIS4_NAME), homeJ = needJ || parser.seen_test(AXIS5_NAME), homeK = needK || parser.seen_test(AXIS6_NAME), homeU = needU || parser.seen_test(AXIS7_NAME), homeV = needV || parser.seen_test(AXIS8_NAME), homeW = needW || parser.seen_test(AXIS9_NAME) @@ -396,11 +387,11 @@ void GcodeSuite::G28() { #if HAS_Z_AXIS - UNUSED(needZ); UNUSED(homeZZ); + UNUSED(needZ); // Z may home first, e.g., when homing away from the bed. // This is also permitted when homing with a Z endstop. - if (TERN0(HOME_Z_FIRST, doZ)) homeaxis(Z_AXIS); + if (TERN0(HOME_Z_FIRST, doZ)) motion.homeaxis(Z_AXIS); // 'R' to specify a specific raise. 'R0' indicates no raise, e.g., for recovery.resume // When 'R0' is used, there should already be adequate clearance, e.g., from homing Z to max. @@ -410,7 +401,7 @@ void GcodeSuite::G28() { float z_homing_height = seenR ? parser.value_linear_units() : Z_CLEARANCE_FOR_HOMING; // Check for any lateral motion that might require clearance - const bool may_skate = seenR NUM_AXIS_GANG(|| doX, || doY, || TERN0(Z_SAFE_HOMING, doZ), || doI, || doJ, || doK, || doU, || doV, || doW); + const bool may_skate = seenR || NUM_AXIS_ANY(doX, doY, TERN0(Z_SAFE_HOMING, doZ), doI, doJ, doK, doU, doV, doW); if (seenR && z_homing_height == 0) { if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("R0 = No Z raise"); @@ -419,15 +410,15 @@ void GcodeSuite::G28() { bool with_probe = ENABLED(HOMING_Z_WITH_PROBE); // Raise above the current Z (which should be synced in the planner) // The "height" for Z is a coordinate. But if Z is not trusted/homed make it relative. - if (seenR || !TERN(HOME_AFTER_DEACTIVATE, axis_is_trusted, axis_was_homed)(Z_AXIS)) { - z_homing_height += current_position.z; + if (seenR || !(motion.z_min_trusted || motion.axis_should_home(Z_AXIS))) { + z_homing_height += motion.position.z; with_probe = false; } if (may_skate) { // Apply Z clearance before doing any lateral motion if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("Raise Z before homing:"); - do_z_clearance(z_homing_height, with_probe); + motion.do_z_clearance(z_homing_height, with_probe); } } @@ -442,51 +433,38 @@ void GcodeSuite::G28() { #if HAS_Y_AXIS // Home Y (before X) if (ENABLED(HOME_Y_BEFORE_X) && (doY || TERN0(CODEPENDENT_XY_HOMING, doX))) - homeaxis(Y_AXIS); + motion.homeaxis(Y_AXIS); #endif // Home X #if HAS_X_AXIS if (doX || (doY && ENABLED(CODEPENDENT_XY_HOMING) && DISABLED(HOME_Y_BEFORE_X))) { - #if ENABLED(DUAL_X_CARRIAGE) - - // Always home the 2nd (right) extruder first - active_extruder = 1; - homeaxis(X_AXIS); - - // Remember this extruder's position for later tool change - inactive_extruder_x = current_position.x; - - // Home the 1st (left) extruder - active_extruder = 0; - homeaxis(X_AXIS); - - // Consider the active extruder to be in its "parked" position - idex_set_parked(); - + motion.idex_home_x(); #else - homeaxis(X_AXIS); + #if ENABLED(SCARA) && DISABLED(HOME_Y_BEFORE_X) + DISABLE_AXIS_Y(); // Allow elbow to be dragged around freely during shoulder homing + #endif + motion.homeaxis(X_AXIS); #endif } #endif // HAS_X_AXIS #if ALL(FOAMCUTTER_XYUV, HAS_I_AXIS) // Home I (after X) - if (doI) homeaxis(I_AXIS); + if (doI) motion.homeaxis(I_AXIS); #endif #if HAS_Y_AXIS // Home Y (after X) - if (DISABLED(HOME_Y_BEFORE_X) && doY) - homeaxis(Y_AXIS); + if (DISABLED(HOME_Y_BEFORE_X) && doY) motion.homeaxis(Y_AXIS); #endif #if ALL(FOAMCUTTER_XYUV, HAS_J_AXIS) // Home J (after Y) - if (doJ) homeaxis(J_AXIS); + if (doJ) motion.homeaxis(J_AXIS); #endif TERN_(IMPROVE_HOMING_RELIABILITY, end_slow_homing(saved_motion_state)); @@ -494,7 +472,7 @@ void GcodeSuite::G28() { #if ENABLED(FOAMCUTTER_XYUV) // Skip homing of unused Z axis for foamcutters - if (doZ) set_axis_is_at_home(Z_AXIS); + if (doZ) motion.set_axis_is_at_home(Z_AXIS); #elif HAS_Z_AXIS @@ -507,9 +485,14 @@ void GcodeSuite::G28() { #endif #if ENABLED(Z_SAFE_HOMING) - if (TERN1(POWER_LOSS_RECOVERY, !parser.seen_test('H'))) home_z_safely(); else homeaxis(Z_AXIS); + // H means hold the current X/Y position when probing. + // Otherwise move to the define safe X/Y position before homing Z. + if (!parser.seen_test('H')) + home_z_safely(); + else + motion.homeaxis(Z_AXIS); #else - homeaxis(Z_AXIS); + motion.homeaxis(Z_AXIS); #endif #if ANY(Z_HOME_TO_MIN, ALLOW_Z_AFTER_HOMING) @@ -519,19 +502,19 @@ void GcodeSuite::G28() { #endif SECONDARY_AXIS_CODE( - if (doI) homeaxis(I_AXIS), - if (doJ) homeaxis(J_AXIS), - if (doK) homeaxis(K_AXIS), - if (doU) homeaxis(U_AXIS), - if (doV) homeaxis(V_AXIS), - if (doW) homeaxis(W_AXIS) + if (doI) motion.homeaxis(I_AXIS), + if (doJ) motion.homeaxis(J_AXIS), + if (doK) motion.homeaxis(K_AXIS), + if (doU) motion.homeaxis(U_AXIS), + if (doV) motion.homeaxis(V_AXIS), + if (doW) motion.homeaxis(W_AXIS) ); #endif // HAS_Z_AXIS - sync_plan_position(); + motion.sync_plan_position(); - #endif + #endif // !DELTA && !AXEL_TPARA /** * Preserve DXC mode across a G28 for IDEX printers in DXC_DUPLICATION_MODE. @@ -540,31 +523,13 @@ void GcodeSuite::G28() { * IDEX specific commands in it. */ #if ENABLED(DUAL_X_CARRIAGE) - - if (idex_is_duplicating()) { - + if (motion.idex_is_duplicating()) { TERN_(IMPROVE_HOMING_RELIABILITY, saved_motion_state = begin_slow_homing()); - - // Always home the 2nd (right) extruder first - active_extruder = 1; - homeaxis(X_AXIS); - - // Remember this extruder's position for later tool change - inactive_extruder_x = current_position.x; - - // Home the 1st (left) extruder - active_extruder = 0; - homeaxis(X_AXIS); - - // Consider the active extruder to be parked - idex_set_parked(); - - dual_x_carriage_mode = IDEX_saved_mode; - set_duplication_enabled(IDEX_saved_duplication_state); - + motion.idex_home_x(); + motion.idex_mode = IDEX_saved_mode; + motion.set_extruder_duplication(IDEX_saved_duplication_state); TERN_(IMPROVE_HOMING_RELIABILITY, end_slow_homing(saved_motion_state)); } - #endif // DUAL_X_CARRIAGE endstops.not_homing(); @@ -572,63 +537,14 @@ void GcodeSuite::G28() { // Clear endstop state for polled stallGuard endstops TERN_(SPI_ENDSTOPS, endstops.clear_endstop_state()); - #if HAS_HOMING_CURRENT - if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("Restore driver current..."); - #if HAS_CURRENT_HOME(X) - stepperX.rms_current(saved_current_X); - #endif - #if HAS_CURRENT_HOME(X2) - stepperX2.rms_current(saved_current_X2); - #endif - #if HAS_CURRENT_HOME(Y) - stepperY.rms_current(saved_current_Y); - #endif - #if HAS_CURRENT_HOME(Y2) - stepperY2.rms_current(saved_current_Y2); - #endif - #if HAS_CURRENT_HOME(Z) - stepperZ.rms_current(saved_current_Z); - #endif - #if HAS_CURRENT_HOME(Z2) - stepperZ2.rms_current(saved_current_Z2); - #endif - #if HAS_CURRENT_HOME(Z3) - stepperZ3.rms_current(saved_current_Z3); - #endif - #if HAS_CURRENT_HOME(Z4) - stepperZ4.rms_current(saved_current_Z4); - #endif - #if HAS_CURRENT_HOME(I) - stepperI.rms_current(saved_current_I); - #endif - #if HAS_CURRENT_HOME(J) - stepperJ.rms_current(saved_current_J); - #endif - #if HAS_CURRENT_HOME(K) - stepperK.rms_current(saved_current_K); - #endif - #if HAS_CURRENT_HOME(U) - stepperU.rms_current(saved_current_U); - #endif - #if HAS_CURRENT_HOME(V) - stepperV.rms_current(saved_current_V); - #endif - #if HAS_CURRENT_HOME(W) - stepperW.rms_current(saved_current_W); - #endif - #if SENSORLESS_STALLGUARD_DELAY - safe_delay(SENSORLESS_STALLGUARD_DELAY); // Short delay needed to settle - #endif - #endif // HAS_HOMING_CURRENT - // Move to a height where we can use the full xy-area - TERN_(DELTA_HOME_TO_SAFE_ZONE, do_blocking_move_to_z(delta_clip_start_height)); + TERN_(DELTA_HOME_TO_SAFE_ZONE, motion.blocking_move_z(delta_clip_start_height)); #if HAS_Z_AXIS // Move to the configured Z only if Z was homed to MIN, because machines that // home to MAX historically expect 'G28 Z' to be safe to use at the end of a // print, and do_move_after_z_homing is not very nuanced. - if (finalRaiseZ) do_move_after_z_homing(); + if (finalRaiseZ) motion.do_move_after_z_homing(); #endif TERN_(CAN_SET_LEVELING_AFTER_G28, if (leveling_restore_state) set_bed_leveling_enabled()); @@ -639,11 +555,14 @@ void GcodeSuite::G28() { #endif #ifdef XY_AFTER_HOMING - if (!axes_should_home(_BV(X_AXIS) | _BV(Y_AXIS))) - do_blocking_move_to(xy_pos_t(XY_AFTER_HOMING)); + if (!motion.axes_should_home(_BV(X_AXIS) | _BV(Y_AXIS))) + motion.blocking_move(xy_pos_t(XY_AFTER_HOMING)); #endif - restore_feedrate_and_scaling(); + motion.restore_feedrate_and_scaling(); + + // Reload workspace offsets + TERN_(CNC_COORDINATE_SYSTEMS, (void)select_coordinate_system(old_coordinate_system)); if (ENABLED(NANODLP_Z_SYNC) && (ENABLED(NANODLP_ALL_AXIS) || TERN0(HAS_Z_AXIS, doZ))) SERIAL_ECHOLNPGM(STR_Z_MOVE_COMP); @@ -652,12 +571,13 @@ void GcodeSuite::G28() { ui.refresh(); - TERN_(HAS_DWIN_E3V2_BASIC, dwinHomingDone()); + TERN_(SOVOL_SV06_RTS, RTS_MoveAxisHoming()); + TERN_(DWIN_CREALITY_LCD, dwinHomingDone()); TERN_(EXTENSIBLE_UI, ExtUI::onHomingDone()); - report_current_position(); + motion.report_position(); - TERN_(FULL_REPORT_TO_HOST_FEATURE, set_and_report_grblstate(old_grblstate)); + TERN_(FULL_REPORT_TO_HOST_FEATURE, motion.set_and_report_grblstate(old_grblstate)); #ifdef EVENT_GCODE_AFTER_HOMING gcode.process_subcommands_now(F(EVENT_GCODE_AFTER_HOMING)); diff --git a/Marlin/src/gcode/calibrate/G33.cpp b/Marlin/src/gcode/calibrate/G33.cpp index 59e0db132a71..0561bbe64d5b 100644 --- a/Marlin/src/gcode/calibrate/G33.cpp +++ b/Marlin/src/gcode/calibrate/G33.cpp @@ -41,8 +41,8 @@ constexpr uint8_t _7P_STEP = 1, // 7-point step - to change number of calibration points _4P_STEP = _7P_STEP * 2, // 4-point step - NPP = _7P_STEP * 6; // number of calibration points on the radius -enum CalEnum : char { // the 7 main calibration points - add definitions if needed + NPP = _7P_STEP * 6; // Number of calibration points on the radius +enum CalEnum : char { // The 7 main calibration points - add definitions if needed CEN = 0, __A = 1, _AB = __A + _7P_STEP, @@ -62,18 +62,27 @@ enum CalEnum : char { // the 7 main calibration points - float lcd_probe_pt(const xy_pos_t &xy); void ac_home() { + #if ENABLED(CNC_COORDINATE_SYSTEMS) + // Temporarily switch to native machine coordinate system for homing + old_coordinate_system = active_coordinate_system; + select_coordinate_system(-1); + #endif + endstops.enable(true); - TERN_(SENSORLESS_HOMING, endstops.set_z_sensorless_current(true)); + TERN_(IMPROVE_HOMING_RELIABILITY, planner.enable_stall_prevention(true)); home_delta(); - TERN_(SENSORLESS_HOMING, endstops.set_z_sensorless_current(false)); + TERN_(IMPROVE_HOMING_RELIABILITY, planner.enable_stall_prevention(false)); endstops.not_homing(); + + // Restore old coordinate system after homing + TERN_(CNC_COORDINATE_SYSTEMS, select_coordinate_system(old_coordinate_system)); } void ac_setup(const bool reset_bed) { TERN_(HAS_BED_PROBE, probe.use_probing_tool()); planner.synchronize(); - remember_feedrate_scaling_off(); + motion.remember_feedrate_scaling_off(); #if HAS_LEVELING if (reset_bed) reset_bed_level(); // After full calibration bed-level data is no longer valid @@ -81,13 +90,13 @@ void ac_setup(const bool reset_bed) { } void ac_cleanup() { - TERN_(DELTA_HOME_TO_SAFE_ZONE, do_blocking_move_to_z(delta_clip_start_height)); + TERN_(DELTA_HOME_TO_SAFE_ZONE, motion.blocking_move_z(delta_clip_start_height)); TERN_(HAS_BED_PROBE, probe.stow()); - restore_feedrate_and_scaling(); + motion.restore_feedrate_and_scaling(); TERN_(HAS_BED_PROBE, probe.use_probing_tool(false)); } -void print_signed_float(FSTR_P const prefix, const_float_t f) { +void print_signed_float(FSTR_P const prefix, const float f) { SERIAL_ECHO(F(" "), prefix, C(':')); serial_offset(f); } @@ -154,7 +163,7 @@ static float std_dev_points(float z_pt[NPP + 1], const bool _0p_cal, const bool S2 += sq(z_pt[rad]); N++; } - return LROUND(SQRT(S2 / N) * 1000.0f) / 1000.0f + 0.00001f; + return LROUND(SQRT(S2 / N) * 1000.0f) * 0.001f + 0.00001f; } } return 0.00001f; @@ -197,13 +206,13 @@ static bool probe_calibration_points(float z_pt[NPP + 1], const int8_t probe_poi if (!_0p_calibration) { - if (!_7p_no_intermediates && !_7p_4_intermediates && !_7p_11_intermediates) { // probe the center + if (!_7p_no_intermediates && !_7p_4_intermediates && !_7p_11_intermediates) { // Probe the center const xy_pos_t center{0}; z_pt[CEN] += calibration_probe(center, stow_after_each, probe_at_offset); if (isnan(z_pt[CEN])) return false; } - if (_7p_calibration) { // probe extra center points + if (_7p_calibration) { // Probe extra center points const float start = _7p_9_center ? float(_CA) + _7P_STEP / 3.0f : _7p_6_center ? float(_CA) : float(__C), steps = _7p_9_center ? _4P_STEP / 3.0f : _7p_6_center ? _7P_STEP : _4P_STEP; I_LOOP_CAL_PT(rad, start, steps) { @@ -216,7 +225,7 @@ static bool probe_calibration_points(float z_pt[NPP + 1], const int8_t probe_poi z_pt[CEN] /= float(_7p_2_intermediates ? 7 : probe_points); } - if (!_1p_calibration) { // probe the radius + if (!_1p_calibration) { // Probe the radius const CalEnum start = _4p_opposite_points ? _AB : __A; const float steps = _7p_14_intermediates ? _7P_STEP / 15.0f : // 15r * 6 + 10c = 100 _7p_11_intermediates ? _7P_STEP / 12.0f : // 12r * 6 + 9c = 81 @@ -241,23 +250,24 @@ static bool probe_calibration_points(float z_pt[NPP + 1], const int8_t probe_poi z_pt[uint8_t(LROUND(rad - interpol + NPP - 1)) % NPP + 1] += z_temp * sq(cos(RADIANS(interpol * 90))); z_pt[uint8_t(LROUND(rad - interpol)) % NPP + 1] += z_temp * sq(sin(RADIANS(interpol * 90))); } - zig_zag = !zig_zag; + FLIP(zig_zag); } if (_7p_intermed_points) LOOP_CAL_RAD(rad) z_pt[rad] /= _7P_STEP / steps; - do_blocking_move_to_xy(0.0f, 0.0f); + motion.blocking_move_xy(0.0f, 0.0f); } } return true; } /** - * kinematics routines and auto tune matrix scaling parameters: - * see https://github.com/LVD-AC/Marlin-AC/tree/1.1.x-AC/documentation for - * - formulae for approximative forward kinematics in the end-stop displacement matrix - * - definition of the matrix scaling parameters + * Kinematics routines and auto tune matrix scaling parameters + * + * NOTE: See https://github.com/LVD-AC/Marlin-AC/tree/1.1.x-AC/documentation for: + * - Formula for approximative forward kinematics in the end-stop displacement matrix + * - Definition of the matrix scaling parameters */ static void reverse_kinematics_probe_points(float z_pt[NPP + 1], abc_float_t mm_at_pt_axis[NPP + 1], const float dcr) { xyz_pos_t pos{0}; @@ -267,7 +277,7 @@ static void reverse_kinematics_probe_points(float z_pt[NPP + 1], abc_float_t mm_ r = (rad == CEN ? 0.0f : dcr); pos.set(cos(a) * r, sin(a) * r, z_pt[rad]); inverse_kinematics(pos); - mm_at_pt_axis[rad] = delta; + mm_at_pt_axis[rad] = motion.delta; } } @@ -314,7 +324,7 @@ static void calc_kinematics_diff_probe_points(float z_pt[NPP + 1], const float d static float auto_tune_h(const float dcr) { const float r_quot = dcr / delta_radius; - return RECIPROCAL(r_quot / (2.0f / 3.0f)); // (2/3)/CR + return RECIPROCAL(r_quot * (3.0f / 2.0f)); // (2/3)/CR } static float auto_tune_r(const float dcr) { @@ -346,47 +356,47 @@ static float auto_tune_a(const float dcr) { } /** - * G33 - Delta '1-4-7-point' Auto-Calibration - * Calibrate height, z_offset, endstops, delta radius, and tower angles. + * G33: Delta Auto Calibration * - * Parameters: - * - * Pn Number of probe points: - * P0 Normalizes calibration. - * P1 Calibrates height only with center probe. - * P2 Probe center and towers. Calibrate height, endstops and delta radius. - * P3 Probe all positions: center, towers and opposite towers. Calibrate all. - * P4-P10 Probe all positions at different intermediate locations and average them. + * Calibrate height, z_offset, endstops, delta radius, and tower angles. * - * Rn.nn Temporary reduce the probe grid by the specified amount (mm) + * Parameters: + * P Number of probe points: + * P0 Normalizes end-stops and tower angle corrections only (no probing) + * P1 Probe center and set height only + * P2 Probe center and towers. Set height, endstops, and delta radius + * P3 Probe all positions - center, towers and opposite towers. Set all + * P4-P10 Probe all positions with intermediate locations, averaging them * - * T Don't calibrate tower angle corrections + * R Temporarily reduce the size of the probe grid by the specified amount * - * Cn.nn Calibration precision; when omitted calibrates to maximum precision + * T Disable tower angle corrections calibration (P3-P7) * - * Fn Force to run at least n iterations and take the best result + * C Calibration precision; if omitted iterations stop at best achievable precision * - * Vn Verbose level: - * V0 Dry-run mode. Report settings and probe results. No calibration. - * V1 Report start and end settings only - * V2 Report settings at each iteration - * V3 Report settings and probe results + * F<1-30> Run (ā€œforceā€) this number of iterations and take the best result * - * E Engage the probe for each point + * V Verbose level: + * V0 Dry-run mode. Report settings and probe results. No calibration + * V1 Report start and end settings only + * V2 Report settings at each iteration + * V3 Report settings and probe results * - * O Probe at offsetted probe positions (this is wrong but it seems to work) + * E Engage the probe for each point * - * With SENSORLESS_PROBING: - * Use these flags to calibrate stall sensitivity: (e.g., `G33 P1 Y Z` to calibrate X only.) - * X Don't activate stallguard on X. - * Y Don't activate stallguard on Y. - * Z Don't activate stallguard on Z. + * O Probe at probe-offset-relative positions instead of the required kinematic points * - * S Save offset_sensorless_adj + * With HAS_DELTA_SENSORLESS_PROBING: + * Use these flags to calibrate stall sensitivity: + * Example: G33 P1 Y Z - to calibrate X only + * X Don't activate StallGuard on X + * Y Don't activate StallGuard on Y + * Z Don't activate StallGuard on Z + * S Save offset_sensorless_adj */ void GcodeSuite::G33() { - TERN_(FULL_REPORT_TO_HOST_FEATURE, set_and_report_grblstate(M_PROBE)); + TERN_(FULL_REPORT_TO_HOST_FEATURE, motion.set_and_report_grblstate(M_PROBE)); const int8_t probe_points = parser.intval('P', DELTA_CALIBRATION_DEFAULT_POINTS); if (!WITHIN(probe_points, 0, 10)) { @@ -481,15 +491,15 @@ void GcodeSuite::G33() { caltower({ false, true, false }); // B caltower({ false, false, true }); // C - probe.test_sensitivity = { true, true, true }; // reset to all + probe.test_sensitivity = { true, true, true }; // Reset to all } #endif - do { // start iterations + do { // Start iterations float z_at_pt[NPP + 1] = { 0.0f }; - test_precision = zero_std_dev_old != 999.0f ? (zero_std_dev + zero_std_dev_old) / 2.0f : zero_std_dev; + test_precision = zero_std_dev_old != 999.0f ? (zero_std_dev + zero_std_dev_old) * 0.5f : zero_std_dev; iterations++; // Probe the points @@ -505,11 +515,11 @@ void GcodeSuite::G33() { if ((zero_std_dev < test_precision || iterations <= force_iterations) && zero_std_dev > calibration_precision) { #if !HAS_BED_PROBE - test_precision = 0.0f; // forced end + test_precision = 0.0f; // Forced end #endif if (zero_std_dev < zero_std_dev_min) { - // set roll-back point + // Set roll-back point e_old = delta_endstop_adj; r_old = delta_radius; h_old = delta_height; @@ -520,19 +530,20 @@ void GcodeSuite::G33() { float r_delta = 0.0f; /** - * convergence matrices: - * see https://github.com/LVD-AC/Marlin-AC/tree/1.1.x-AC/documentation for - * - definition of the matrix scaling parameters - * - matrices for 4 and 7 point calibration + * Convergence matrices + * + * NOTE: See https://github.com/LVD-AC/Marlin-AC/tree/1.1.x-AC/documentation for: + * - Definition of the matrix scaling parameters + * - Matrices for 4 and 7 point calibration */ - #define ZP(N,I) ((N) * z_at_pt[I] / 4.0f) // 4.0 = divider to normalize to integers + #define ZP(N,I) ((N) * z_at_pt[I] * 0.25f) // 4.0 = divider to normalize to integers #define Z12(I) ZP(12, I) #define Z4(I) ZP(4, I) #define Z2(I) ZP(2, I) #define Z1(I) ZP(1, I) #define Z0(I) ZP(0, I) - // calculate factors + // Calculate factors if (_7p_9_center) dcr *= 0.9f; h_factor = auto_tune_h(dcr); r_factor = auto_tune_r(dcr); @@ -541,22 +552,22 @@ void GcodeSuite::G33() { switch (probe_points) { case 0: - test_precision = 0.0f; // forced end + test_precision = 0.0f; // Forced end break; case 1: - test_precision = 0.0f; // forced end + test_precision = 0.0f; // Forced end LOOP_NUM_AXES(axis) e_delta[axis] = +Z4(CEN); break; case 2: - if (towers_set) { // see 4 point calibration (towers) matrix + if (towers_set) { // See 4 point calibration (towers) matrix e_delta.set((+Z4(__A) -Z2(__B) -Z2(__C)) * h_factor +Z4(CEN), (-Z2(__A) +Z4(__B) -Z2(__C)) * h_factor +Z4(CEN), (-Z2(__A) -Z2(__B) +Z4(__C)) * h_factor +Z4(CEN)); r_delta = (+Z4(__A) +Z4(__B) +Z4(__C) -Z12(CEN)) * r_factor; } - else { // see 4 point calibration (opposites) matrix + else { // See 4 point calibration (opposites) matrix e_delta.set((-Z4(_BC) +Z2(_CA) +Z2(_AB)) * h_factor +Z4(CEN), (+Z2(_BC) -Z4(_CA) +Z2(_AB)) * h_factor +Z4(CEN), (+Z2(_BC) +Z2(_CA) -Z4(_AB)) * h_factor +Z4(CEN)); @@ -564,13 +575,13 @@ void GcodeSuite::G33() { } break; - default: // see 7 point calibration (towers & opposites) matrix + default: // See 7 point calibration (towers & opposites) matrix e_delta.set((+Z2(__A) -Z1(__B) -Z1(__C) -Z2(_BC) +Z1(_CA) +Z1(_AB)) * h_factor +Z4(CEN), (-Z1(__A) +Z2(__B) -Z1(__C) +Z1(_BC) -Z2(_CA) +Z1(_AB)) * h_factor +Z4(CEN), (-Z1(__A) -Z1(__B) +Z2(__C) +Z1(_BC) +Z1(_CA) -Z2(_AB)) * h_factor +Z4(CEN)); r_delta = (+Z2(__A) +Z2(__B) +Z2(__C) +Z2(_BC) +Z2(_CA) +Z2(_AB) -Z12(CEN)) * r_factor; - if (towers_set) { // see 7 point tower angle calibration (towers & opposites) matrix + if (towers_set) { // See 7 point tower angle calibration (towers & opposites) matrix t_delta.set((+Z0(__A) -Z4(__B) +Z4(__C) +Z0(_BC) -Z4(_CA) +Z4(_AB) +Z0(CEN)) * a_factor, (+Z4(__A) +Z0(__B) -Z4(__C) +Z4(_BC) +Z0(_CA) -Z4(_AB) +Z0(CEN)) * a_factor, (-Z4(__A) +Z4(__B) +Z0(__C) -Z4(_BC) +Z4(_CA) +Z0(_AB) +Z0(CEN)) * a_factor); @@ -582,14 +593,14 @@ void GcodeSuite::G33() { delta_tower_angle_trim += t_delta; } else if (zero_std_dev >= test_precision) { - // roll back + // Roll back delta_endstop_adj = e_old; delta_radius = r_old; delta_height = h_old; delta_tower_angle_trim = a_old; } - if (verbose_level != 0) { // !dry run + if (verbose_level != 0) { // !Dry-run // Normalize angles to least-squares if (_angle_results) { @@ -598,7 +609,7 @@ void GcodeSuite::G33() { LOOP_NUM_AXES(axis) delta_tower_angle_trim[axis] -= a_sum / 3.0f; } - // adjust delta_height and endstops by the max amount + // Adjust delta_height and endstops by the max amount const float z_temp = _MAX(delta_endstop_adj.a, delta_endstop_adj.b, delta_endstop_adj.c); delta_height -= z_temp; LOOP_NUM_AXES(axis) delta_endstop_adj[axis] -= z_temp; @@ -606,7 +617,7 @@ void GcodeSuite::G33() { recalc_delta_settings(); NOMORE(zero_std_dev_min, zero_std_dev); - // print report + // Print report if (verbose_level == 3 || verbose_level == 0) { print_calibration_results(z_at_pt, _tower_results, _opposite_results); @@ -620,7 +631,7 @@ void GcodeSuite::G33() { #endif } - if (verbose_level != 0) { // !dry run + if (verbose_level != 0) { // !Dry-run if ((zero_std_dev >= test_precision && iterations > force_iterations) || zero_std_dev <= calibration_precision) { // end iterations SERIAL_ECHOPGM("Calibration OK"); SERIAL_ECHO_SP(32); @@ -634,7 +645,7 @@ void GcodeSuite::G33() { } SERIAL_EOL(); - MString<20> msg(F("Calibration sd:")); + MString<21> msg(F("Calibration sd:")); if (zero_std_dev_min < 1) msg.appendf(F("0.%03i"), (int)LROUND(zero_std_dev_min * 1000.0f)); else @@ -657,7 +668,7 @@ void GcodeSuite::G33() { print_calibration_settings(_endstop_results, _angle_results); } } - else { // dry run + else { // Dry-run FSTR_P const enddryrun = F("End DRY-RUN"); SERIAL_ECHO(enddryrun); SERIAL_ECHO_SP(35); @@ -675,7 +686,7 @@ void GcodeSuite::G33() { ac_cleanup(); - TERN_(FULL_REPORT_TO_HOST_FEATURE, set_and_report_grblstate(M_IDLE)); + TERN_(FULL_REPORT_TO_HOST_FEATURE, motion.set_and_report_grblstate(M_IDLE)); #if HAS_DELTA_SENSORLESS_PROBING probe.test_sensitivity = { true, true, true }; #endif diff --git a/Marlin/src/gcode/calibrate/G34.cpp b/Marlin/src/gcode/calibrate/G34.cpp index 9a0cb0054b72..a8d5eef9d643 100644 --- a/Marlin/src/gcode/calibrate/G34.cpp +++ b/Marlin/src/gcode/calibrate/G34.cpp @@ -40,7 +40,9 @@ #include "../../core/debug_out.h" /** - * G34 - Align the ends of the X gantry. See https://youtu.be/3jAFQdTk8iw + * G34: Mechanical Gantry Calibration + * + * Align the ends of the X gantry. See https://youtu.be/3jAFQdTk8iw * * - The carriage moves to GANTRY_CALIBRATION_SAFE_POSITION, also called the ā€œpounceā€ position. * - If possible, the Z stepper current is reduced to the value specified by 'S' @@ -53,17 +55,17 @@ * - The machine is re-homed, according to GANTRY_CALIBRATION_COMMANDS_POST. * * Parameters: - * [S] - Current value to use for the raise move. (Default: GANTRY_CALIBRATION_CURRENT) - * [Z] - Extra distance past Z_MAX_POS to move the Z axis. (Default: GANTRY_CALIBRATION_EXTRA_HEIGHT) + * S Current value to use for the raise move. (Default: GANTRY_CALIBRATION_CURRENT) + * Z Extra distance past Z_MAX_POS to move the Z axis. (Default: GANTRY_CALIBRATION_EXTRA_HEIGHT) */ void GcodeSuite::G34() { // Home before the alignment procedure - home_if_needed(); + motion.home_if_needed(); TERN_(HAS_LEVELING, TEMPORARY_BED_LEVELING_STATE(false)); - SET_SOFT_ENDSTOP_LOOSE(true); + motion.set_soft_endstop_loose(true); TemporaryGlobalEndstopsState unlock_z(false); #ifdef GANTRY_CALIBRATION_COMMANDS_PRE @@ -75,7 +77,7 @@ void GcodeSuite::G34() { // Move XY to safe position if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("Parking XY"); const xy_pos_t safe_pos = GANTRY_CALIBRATION_SAFE_POSITION; - do_blocking_move_to_xy(safe_pos, MMM_TO_MMS(GANTRY_CALIBRATION_XY_PARK_FEEDRATE)); + motion.blocking_move_xy(safe_pos, MMM_TO_MMS(GANTRY_CALIBRATION_XY_PARK_FEEDRATE)); #endif const float move_distance = parser.intval('Z', GANTRY_CALIBRATION_EXTRA_HEIGHT), @@ -84,7 +86,7 @@ void GcodeSuite::G34() { // Move Z to pounce position if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("Setting Z Pounce"); - do_blocking_move_to_z(zpounce, homing_feedrate(Z_AXIS)); + motion.blocking_move_z(zpounce, motion.homing_feedrate(Z_AXIS)); // Store current motor settings, then apply reduced value @@ -95,44 +97,43 @@ void GcodeSuite::G34() { #if HAS_MOTOR_CURRENT_SPI const uint16_t target_current = parser.intval('S', GANTRY_CALIBRATION_CURRENT); - const uint32_t previous_current = stepper.motor_current_setting[Z_AXIS]; + const uint32_t previous_current_Z = stepper.motor_current_setting[Z_AXIS]; stepper.set_digipot_current(Z_AXIS, target_current); #elif HAS_MOTOR_CURRENT_PWM const uint16_t target_current = parser.intval('S', GANTRY_CALIBRATION_CURRENT); - const uint32_t previous_current = stepper.motor_current_setting[1]; // Z + const uint32_t previous_current_Z = stepper.motor_current_setting[1]; // Z stepper.set_digipot_current(1, target_current); #elif HAS_MOTOR_CURRENT_DAC const float target_current = parser.floatval('S', GANTRY_CALIBRATION_CURRENT); - const float previous_current = dac_amps(Z_AXIS, target_current); + const float previous_current_Z = dac_amps(Z_AXIS, target_current); stepper_dac.set_current_value(Z_AXIS, target_current); #elif HAS_MOTOR_CURRENT_I2C const uint16_t target_current = parser.intval('S', GANTRY_CALIBRATION_CURRENT); - previous_current = dac_amps(Z_AXIS); + const float previous_current_Z = dac_amps(Z_AXIS); digipot_i2c.set_current(Z_AXIS, target_current) #elif HAS_TRINAMIC_CONFIG const uint16_t target_current = parser.intval('S', GANTRY_CALIBRATION_CURRENT); - static uint16_t previous_current_arr[NUM_Z_STEPPERS]; - #if AXIS_IS_TMC(Z) - previous_current_arr[0] = stepperZ.getMilliamps(); + #if Z_IS_TRINAMIC + static uint16_t previous_current_Z = stepperZ.getMilliamps(); stepperZ.rms_current(target_current); #endif - #if AXIS_IS_TMC(Z2) - previous_current_arr[1] = stepperZ2.getMilliamps(); + #if Z2_IS_TRINAMIC + static uint16_t previous_current_Z2 = stepperZ2.getMilliamps(); stepperZ2.rms_current(target_current); #endif - #if AXIS_IS_TMC(Z3) - previous_current_arr[2] = stepperZ3.getMilliamps(); + #if Z3_IS_TRINAMIC + static uint16_t previous_current_Z3 = stepperZ3.getMilliamps(); stepperZ3.rms_current(target_current); #endif - #if AXIS_IS_TMC(Z4) - previous_current_arr[3] = stepperZ4.getMilliamps(); + #if Z4_IS_TRINAMIC + static uint16_t previous_current_Z4 = stepperZ4.getMilliamps(); stepperZ4.rms_current(target_current); #endif #endif // Do Final Z move to adjust if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("Final Z Move"); - do_blocking_move_to_z(zgrind, MMM_TO_MMS(GANTRY_CALIBRATION_FEEDRATE)); + motion.blocking_move_z(zgrind, MMM_TO_MMS(GANTRY_CALIBRATION_FEEDRATE)); #if _REDUCE_CURRENT // Reset current to original values @@ -140,38 +141,30 @@ void GcodeSuite::G34() { #endif #if HAS_MOTOR_CURRENT_SPI - stepper.set_digipot_current(Z_AXIS, previous_current); + stepper.set_digipot_current(Z_AXIS, previous_current_Z); #elif HAS_MOTOR_CURRENT_PWM - stepper.set_digipot_current(1, previous_current); + stepper.set_digipot_current(1, previous_current_Z); #elif HAS_MOTOR_CURRENT_DAC - stepper_dac.set_current_value(Z_AXIS, previous_current); + stepper_dac.set_current_value(Z_AXIS, previous_current_Z); #elif HAS_MOTOR_CURRENT_I2C - digipot_i2c.set_current(Z_AXIS, previous_current) + digipot_i2c.set_current(Z_AXIS, previous_current_Z) #elif HAS_TRINAMIC_CONFIG - #if AXIS_IS_TMC(Z) - stepperZ.rms_current(previous_current_arr[0]); - #endif - #if AXIS_IS_TMC(Z2) - stepperZ2.rms_current(previous_current_arr[1]); - #endif - #if AXIS_IS_TMC(Z3) - stepperZ3.rms_current(previous_current_arr[2]); - #endif - #if AXIS_IS_TMC(Z4) - stepperZ4.rms_current(previous_current_arr[3]); - #endif + TERN_(Z_IS_TRINAMIC, stepperZ.rms_current(previous_current_Z)); + TERN_(Z2_IS_TRINAMIC, stepperZ2.rms_current(previous_current_Z2)); + TERN_(Z3_IS_TRINAMIC, stepperZ3.rms_current(previous_current_Z3)); + TERN_(Z4_IS_TRINAMIC, stepperZ4.rms_current(previous_current_Z4)); #endif // Back off end plate, back to normal motion range if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("Z Backoff"); - do_blocking_move_to_z(zpounce, MMM_TO_MMS(GANTRY_CALIBRATION_FEEDRATE)); + motion.blocking_move_z(zpounce, MMM_TO_MMS(GANTRY_CALIBRATION_FEEDRATE)); #ifdef GANTRY_CALIBRATION_COMMANDS_POST if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("Running Post Commands"); process_subcommands_now(F(GANTRY_CALIBRATION_COMMANDS_POST)); #endif - SET_SOFT_ENDSTOP_LOOSE(false); + motion.set_soft_endstop_loose(false); } #endif // MECHANICAL_GANTRY_CALIBRATION diff --git a/Marlin/src/gcode/calibrate/G34_M422.cpp b/Marlin/src/gcode/calibrate/G34_M422.cpp index 8b94dd53921b..d84b84031c1a 100644 --- a/Marlin/src/gcode/calibrate/G34_M422.cpp +++ b/Marlin/src/gcode/calibrate/G34_M422.cpp @@ -56,23 +56,24 @@ #endif /** - * G34: Z-Stepper automatic alignment + * G34: Z Steppers Auto-Alignment * - * Manual stepper lock controls (reset by G28): - * L Unlock all steppers - * Z<1-4> Z stepper to lock / unlock - * S 0=UNLOCKED 1=LOCKED. If omitted, assume LOCKED. + * Parameters: + * Manual stepper lock controls (reset by G28): + * L Unlock all steppers + * Z Target specific Z stepper to lock/unlock (1-4) + * S Lock state; 0=UNLOCKED 1=LOCKED. If omitted, assume LOCKED * - * Examples: - * G34 Z1 ; Lock Z1 - * G34 L Z2 ; Unlock all, then lock Z2 - * G34 Z2 S0 ; Unlock Z2 + * With Z_STEPPER_AUTO_ALIGN: + * I Number of test iterations. If omitted, Z_STEPPER_ALIGN_ITERATIONS. (1-30) + * T Target Accuracy factor. If omitted, Z_STEPPER_ALIGN_ACC. (0.01-1.0) + * A Provide an Amplification value. If omitted, Z_STEPPER_ALIGN_AMP. (0.5-2.0) + * R Recalculate points based on current probe offsets * - * With Z_STEPPER_AUTO_ALIGN: - * I Number of tests. If omitted, Z_STEPPER_ALIGN_ITERATIONS. - * T Target Accuracy factor. If omitted, Z_STEPPER_ALIGN_ACC. - * A Provide an Amplification value. If omitted, Z_STEPPER_ALIGN_AMP. - * R Flag to recalculate points based on current probe offsets + * Example: + * G34 Z1 ; Lock Z1 + * G34 L Z2 ; Unlock all, then lock Z2 + * G34 Z2 S0 ; Unlock Z2 */ void GcodeSuite::G34() { @@ -115,8 +116,8 @@ void GcodeSuite::G34() { } const float z_auto_align_accuracy = parser.floatval('T', Z_STEPPER_ALIGN_ACC); - if (!WITHIN(z_auto_align_accuracy, 0.01f, 1.0f)) { - SERIAL_ECHOLNPGM(GCODE_ERR_MSG("(T)arget accuracy out of bounds (0.01-1.0).")); + if (!WITHIN(z_auto_align_accuracy, 0.001f, 1.0f)) { + SERIAL_ECHOLNPGM(GCODE_ERR_MSG("(T)arget accuracy out of bounds (0.001-1.0).")); break; } @@ -142,7 +143,12 @@ void GcodeSuite::G34() { probe.use_probing_tool(); - TERN_(HAS_DUPLICATION_MODE, set_duplication_enabled(false)); + #ifdef EVENT_GCODE_BEFORE_G34 + if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("Before G34 G-code: ", F(EVENT_GCODE_BEFORE_G34)); + gcode.process_subcommands_now(F(EVENT_GCODE_BEFORE_G34)); + #endif + + TERN_(HAS_DUPLICATION_MODE, motion.set_extruder_duplication(false)); // Compute a worst-case clearance height to probe from. After the first // iteration this will be re-calculated based on the actual bed position @@ -161,7 +167,7 @@ void GcodeSuite::G34() { )); // Home before the alignment procedure - home_if_needed(); + motion.home_if_needed(); #if !HAS_Z_STEPPER_ALIGN_STEPPER_XY float last_z_align_move[NUM_Z_STEPPERS] = ARRAY_N_1(NUM_Z_STEPPERS, 10000.0f); @@ -211,8 +217,28 @@ void GcodeSuite::G34() { // Probe a Z height for each stepper. // Probing sanity check is disabled, as it would trigger even in normal cases because - // current_position.z has been manually altered in the "dirty trick" above. - const float z_probed_height = probe.probe_at_point(DIFF_TERN(HAS_HOME_OFFSET, ppos, xy_pos_t(home_offset)), raise_after, 0, true, false, (Z_PROBE_LOW_POINT) - z_probe * 0.5f, z_probe * 0.5f); + // motion.position.z has been manually altered in the "dirty trick" above. + + const float minz = (Z_PROBE_LOW_POINT) - (z_probe * 0.5f); + + if (DEBUGGING(LEVELING)) { + DEBUG_ECHOPGM("Z_PROBE_LOW_POINT: " STRINGIFY(Z_PROBE_LOW_POINT)); + DEBUG_ECHOLNPGM(" z_probe: ", p_float_t(z_probe, 3), + " Probe Tgt: ", p_float_t(minz, 3)); + } + + const float z_probed_height = probe.probe_at_point( + DIFF_TERN(HAS_HOME_OFFSET, ppos, xy_pos_t(motion.home_offset)), // xy + raise_after, // raise_after + (DEBUGGING(LEVELING) || DEBUGGING(INFO)) ? 3 : 0, // verbose_level + true, false, // probe_relative, sanity_check + minz, // z_min_point + Z_TWEEN_SAFE_CLEARANCE // z_clearance + ); + + if (DEBUGGING(LEVELING)) + DEBUG_ECHOLN(F("Probing X"), ppos.x, FPSTR(SP_Y_STR), ppos.y, F(" Height = "), z_probed_height); + if (isnan(z_probed_height)) { SERIAL_ECHOLNPGM(STR_ERR_PROBING_FAILED); LCD_MESSAGE(MSG_LCD_PROBING_FAILED); @@ -236,7 +262,12 @@ void GcodeSuite::G34() { // Adapt the next probe clearance height based on the new measurements. // Safe_height = lowest distance to bed (= highest measurement) plus highest measured misalignment. z_maxdiff = z_measured_max - z_measured_min; - z_probe = (Z_TWEEN_SAFE_CLEARANCE + zoffs) + z_measured_max + z_maxdiff; //Not sure we need z_maxdiff, but leaving it in for safety. + + // The intent of the line below seems to be to clamp the probe depth on successive iterations of G34, but in reality if the amplification + // factor is not completely accurate, this was causing probing to fail as the probe stopped fractions of a mm from the trigger point + // on the second iteration very reliably. This may be restored with an uncertainty factor at some point, however its usefulness after + // all probe points have seen a successful probe is questionable. + //z_probe = (Z_TWEEN_SAFE_CLEARANCE + zoffs) + z_measured_max + z_maxdiff; // Not sure we need z_maxdiff, but leaving it in for safety. #if HAS_Z_STEPPER_ALIGN_STEPPER_XY // Replace the initial values in z_measured with calculated heights at @@ -276,7 +307,7 @@ void GcodeSuite::G34() { SERIAL_EOL(); - SString<15 + TERN0(TRIPLE_Z, 30) + TERN0(QUAD_Z, 45)> msg(F("1:2="), p_float_t(ABS(z_measured[1] - z_measured[0]), 3)); + SString<15 + TERN0(TRIPLE_Z, 30) + TERN0(QUAD_Z, 45)> msg(F("2-1="), p_float_t(ABS(z_measured[1] - z_measured[0]), 3)); #if TRIPLE_Z msg.append(F(" 3-2="), p_float_t(ABS(z_measured[2] - z_measured[1]), 3)) .append(F(" 3-1="), p_float_t(ABS(z_measured[2] - z_measured[0]), 3)); @@ -290,7 +321,7 @@ void GcodeSuite::G34() { msg.echoln(); ui.set_status(msg); - auto decreasing_accuracy = [](const_float_t v1, const_float_t v2) { + auto decreasing_accuracy = [](const float v1, const float v2) { if (v1 < v2 * 0.7f) { SERIAL_ECHOLNPGM("Decreasing Accuracy Detected."); LCD_MESSAGE(MSG_DECREASING_ACCURACY); @@ -339,7 +370,7 @@ void GcodeSuite::G34() { if (decreasing_accuracy(last_z_align_move[zstepper], z_align_abs)) { if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("> Z", zstepper + 1, " last_z_align_move = ", last_z_align_move[zstepper]); if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("> Z", zstepper + 1, " z_align_abs = ", z_align_abs); - adjustment_reverse = !adjustment_reverse; + FLIP(adjustment_reverse); } // Remember the alignment for the next iteration, but only if steppers move, @@ -359,13 +390,13 @@ void GcodeSuite::G34() { // Decreasing accuracy was detected so move was inverted. // Will match reversed Z steppers on dual steppers. Triple will need more work to map. if (adjustment_reverse) { - z_align_move = -z_align_move; + z_align_move *= -1; if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("> Z", zstepper + 1, " correction reversed to ", z_align_move); } #endif // Do a move to correct part of the misalignment for the current stepper - do_blocking_move_to_z(amplification * z_align_move + current_position.z); + motion.blocking_move_z(amplification * z_align_move + motion.position.z); } // for (zstepper) // Back to normal stepper operations @@ -387,7 +418,7 @@ void GcodeSuite::G34() { SERIAL_ECHOLNPGM("G34 aborted."); else { SERIAL_ECHOLNPGM("Did ", iteration + (iteration != z_auto_align_iterations), " of ", z_auto_align_iterations); - SERIAL_ECHOLNPGM("Accuracy: ", p_float_t(z_maxdiff, 2)); + SERIAL_ECHOLNPGM("Accuracy: ", p_float_t(z_maxdiff, 3)); } // Stow the probe because the last call to probe.probe_at_point(...) @@ -401,8 +432,22 @@ void GcodeSuite::G34() { // Use the probed height from the last iteration to determine the Z height. // z_measured_min is used, because all steppers are aligned to z_measured_min. // Ideally, this would be equal to the 'z_probe * 0.5f' which was added earlier. - current_position.z -= z_measured_min - (Z_TWEEN_SAFE_CLEARANCE + zoffs); //we shouldn't want to subtract the clearance from here right? (Depends if we added it further up) - sync_plan_position(); + if (DEBUGGING(LEVELING)) + DEBUG_ECHOLNPGM( + "z_measured_min: ", p_float_t(z_measured_min, 3), + "Z_TWEEN_SAFE_CLEARANCE: ", p_float_t(Z_TWEEN_SAFE_CLEARANCE, 3), + "zoffs: ", p_float_t(zoffs, 3) + ); + + if (!err_break) + motion.position.z -= z_measured_min - (Z_TWEEN_SAFE_CLEARANCE + zoffs); // We shouldn't want to subtract the clearance from here right? (Depends if we added it further up) + motion.sync_plan_position(); + #endif + + #ifdef EVENT_GCODE_AFTER_G34 + if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("After G34 G-code: ", F(EVENT_GCODE_AFTER_G34)); + planner.synchronize(); + process_subcommands_now(F(EVENT_GCODE_AFTER_G34)); #endif probe.use_probing_tool(false); diff --git a/Marlin/src/gcode/calibrate/G425.cpp b/Marlin/src/gcode/calibrate/G425.cpp index 992d7c38e621..69ebba7bd356 100644 --- a/Marlin/src/gcode/calibrate/G425.cpp +++ b/Marlin/src/gcode/calibrate/G425.cpp @@ -100,7 +100,9 @@ enum side_t : uint8_t { TOP, RIGHT, FRONT, LEFT, BACK, NUM_SIDES, - LIST_N(DOUBLE(SECONDARY_AXES), IMINIMUM, IMAXIMUM, JMINIMUM, JMAXIMUM, KMINIMUM, KMAXIMUM, UMINIMUM, UMAXIMUM, VMINIMUM, VMAXIMUM, WMINIMUM, WMAXIMUM) + LIST_N(DOUBLE(SECONDARY_AXES), + IMINIMUM, IMAXIMUM, JMINIMUM, JMAXIMUM, KMINIMUM, KMAXIMUM, + UMINIMUM, UMAXIMUM, VMINIMUM, VMAXIMUM, WMINIMUM, WMAXIMUM) }; static constexpr xyz_pos_t true_center CALIBRATION_OBJECT_CENTER; @@ -143,7 +145,7 @@ struct measurements_t { #endif inline void calibration_move() { - do_blocking_move_to((xyz_pos_t)current_position, MMM_TO_MMS(CALIBRATION_FEEDRATE_TRAVEL)); + motion.blocking_move((xyz_pos_t)motion.position, MMM_TO_MMS(CALIBRATION_FEEDRATE_TRAVEL)); } /** @@ -154,17 +156,17 @@ inline void calibration_move() { */ inline void park_above_object(measurements_t &m, const float uncertainty) { // Move to safe distance above calibration object - current_position.z = m.obj_center.z + dimensions.z / 2 + uncertainty; + motion.position.z = m.obj_center.z + dimensions.z / 2 + uncertainty; calibration_move(); // Move to center of calibration object in XY - current_position = xy_pos_t(m.obj_center); + motion.position = xy_pos_t(m.obj_center); calibration_move(); } #if HAS_MULTI_HOTEND inline void set_nozzle(measurements_t &m, const uint8_t extruder) { - if (extruder != active_extruder) { + if (extruder != motion.extruder) { park_above_object(m, CALIBRATION_MEASUREMENT_UNKNOWN); tool_change(extruder); } @@ -175,26 +177,12 @@ inline void park_above_object(measurements_t &m, const float uncertainty) { inline void normalize_hotend_offsets() { for (uint8_t e = 1; e < HOTENDS; ++e) - hotend_offset[e] -= hotend_offset[0]; - hotend_offset[0].reset(); + motion.hotend_offset[e] -= motion.hotend_offset[0]; + motion.hotend_offset[0].reset(); } #endif -#if !PIN_EXISTS(CALIBRATION) - #include "../../module/probe.h" -#endif - -inline bool read_calibration_pin() { - return ( - #if PIN_EXISTS(CALIBRATION) - READ(CALIBRATION_PIN) != CALIBRATION_PIN_INVERTING - #else - PROBE_TRIGGERED() - #endif - ); -} - /** * Move along axis in the specified dir until the probe value becomes stop_state, * then return the axis value. @@ -205,18 +193,18 @@ inline bool read_calibration_pin() { * fast in - Fast vs. precise measurement */ float measuring_movement(const AxisEnum axis, const int dir, const bool stop_state, const bool fast) { - const float step = fast ? 0.25 : CALIBRATION_MEASUREMENT_RESOLUTION; const feedRate_t mms = fast ? MMM_TO_MMS(CALIBRATION_FEEDRATE_FAST) : MMM_TO_MMS(CALIBRATION_FEEDRATE_SLOW); const float limit = fast ? 50 : 5; - destination = current_position; - for (float travel = 0; travel < limit; travel += step) { - destination[axis] += dir * step; - do_blocking_move_to((xyz_pos_t)destination, mms); - planner.synchronize(); - if (read_calibration_pin() == stop_state) break; - } - return destination[axis]; + motion.destination = motion.position; + motion.destination[axis] += dir * limit; + endstops.enable_calibration_probe(true, stop_state); + motion.blocking_move((xyz_pos_t)motion.destination, mms); + endstops.enable_calibration_probe(false); + endstops.hit_on_purpose(); + motion.set_current_from_steppers_for_axis(axis); + motion.sync_plan_position(); + return motion.position[axis]; } /** @@ -233,7 +221,7 @@ inline float measure(const AxisEnum axis, const int dir, const bool stop_state, const bool fast = uncertainty == CALIBRATION_MEASUREMENT_UNKNOWN; // Save the current position of the specified axis - const float start_pos = current_position[axis]; + const float start_pos = motion.position[axis]; // Take a measurement. Only the specified axis will be affected. const float measured_pos = measuring_movement(axis, dir, stop_state, fast); @@ -245,9 +233,9 @@ inline float measure(const AxisEnum axis, const int dir, const bool stop_state, } // Move back to the starting position - destination = current_position; - destination[axis] = start_pos; - do_blocking_move_to((xyz_pos_t)destination, MMM_TO_MMS(CALIBRATION_FEEDRATE_TRAVEL)); + motion.destination = motion.position; + motion.destination[axis] = start_pos; + motion.blocking_move((xyz_pos_t)motion.destination, MMM_TO_MMS(CALIBRATION_FEEDRATE_TRAVEL)); return measured_pos; } @@ -309,7 +297,7 @@ inline void probe_side(measurements_t &m, const float uncertainty, const side_t if (probe_top_at_edge) { #if AXIS_CAN_CALIBRATE(Z) // Probe top nearest the side we are probing - current_position[axis] = m.obj_center[axis] + (-dir) * (dimensions[axis] / 2 - m.nozzle_outer_dimension[axis]); + motion.position[axis] = m.obj_center[axis] + (-dir) * (dimensions[axis] / 2 - m.nozzle_outer_dimension[axis]); calibration_move(); m.obj_side[TOP] = measure(Z_AXIS, -1, true, &m.backlash[TOP], uncertainty); m.obj_center.z = m.obj_side[TOP] - dimensions.z / 2; @@ -318,11 +306,11 @@ inline void probe_side(measurements_t &m, const float uncertainty, const side_t if ((AXIS_CAN_CALIBRATE(X) && axis == X_AXIS) || (AXIS_CAN_CALIBRATE(Y) && axis == Y_AXIS)) { // Move to safe distance to the side of the calibration object - current_position[axis] = m.obj_center[axis] + (-dir) * (dimensions[axis] / 2 + m.nozzle_outer_dimension[axis] / 2 + uncertainty); + motion.position[axis] = m.obj_center[axis] + (-dir) * (dimensions[axis] / 2 + m.nozzle_outer_dimension[axis] / 2 + uncertainty); calibration_move(); // Plunge below the side of the calibration object and measure - current_position.z = m.obj_side[TOP] - (CALIBRATION_NOZZLE_TIP_HEIGHT) * 0.7f; + motion.position.z = m.obj_side[TOP] - (CALIBRATION_NOZZLE_TIP_HEIGHT) * 0.7f; calibration_move(); const float measurement = measure(axis, dir, true, &m.backlash[side], uncertainty); m.obj_center[axis] = measurement + dir * (dimensions[axis] / 2 + m.nozzle_outer_dimension[axis] / 2); @@ -346,10 +334,22 @@ inline void probe_sides(measurements_t &m, const float uncertainty) { probe_side(m, uncertainty, TOP); #endif - TERN_(CALIBRATION_MEASURE_RIGHT, probe_side(m, uncertainty, RIGHT, probe_top_at_edge)); + /** + * Allow Y axis to probe and compute values before X axis (or remaining arbitrary axes) + * to assist with centering in calibration object. Lulzbot saw issues with higher uncertainty + * values where the nozzle was catching on the edges of the cube, and this was intended to help + * ensure the probe object remained centered. + */ TERN_(CALIBRATION_MEASURE_FRONT, probe_side(m, uncertainty, FRONT, probe_top_at_edge)); - TERN_(CALIBRATION_MEASURE_LEFT, probe_side(m, uncertainty, LEFT, probe_top_at_edge)); TERN_(CALIBRATION_MEASURE_BACK, probe_side(m, uncertainty, BACK, probe_top_at_edge)); + + #if HAS_Y_CENTER + m.obj_center.y = (m.obj_side[FRONT] + m.obj_side[BACK]) / 2; + m.nozzle_outer_dimension.y = m.obj_side[BACK] - m.obj_side[FRONT] - dimensions.y; + #endif + + TERN_(CALIBRATION_MEASURE_LEFT, probe_side(m, uncertainty, LEFT, probe_top_at_edge)); + TERN_(CALIBRATION_MEASURE_RIGHT, probe_side(m, uncertainty, RIGHT, probe_top_at_edge)); TERN_(CALIBRATION_MEASURE_IMIN, probe_side(m, uncertainty, IMINIMUM, probe_top_at_edge)); TERN_(CALIBRATION_MEASURE_IMAX, probe_side(m, uncertainty, IMAXIMUM, probe_top_at_edge)); TERN_(CALIBRATION_MEASURE_JMIN, probe_side(m, uncertainty, JMINIMUM, probe_top_at_edge)); @@ -365,7 +365,6 @@ inline void probe_sides(measurements_t &m, const float uncertainty) { // Compute the measured center of the calibration object. TERN_(HAS_X_CENTER, m.obj_center.x = (m.obj_side[LEFT] + m.obj_side[RIGHT]) / 2); - TERN_(HAS_Y_CENTER, m.obj_center.y = (m.obj_side[FRONT] + m.obj_side[BACK]) / 2); TERN_(HAS_I_CENTER, m.obj_center.i = (m.obj_side[IMINIMUM] + m.obj_side[IMAXIMUM]) / 2); TERN_(HAS_J_CENTER, m.obj_center.j = (m.obj_side[JMINIMUM] + m.obj_side[JMAXIMUM]) / 2); TERN_(HAS_K_CENTER, m.obj_center.k = (m.obj_side[KMINIMUM] + m.obj_side[KMAXIMUM]) / 2); @@ -376,7 +375,6 @@ inline void probe_sides(measurements_t &m, const float uncertainty) { // Compute the outside diameter of the nozzle at the height // at which it makes contact with the calibration object TERN_(HAS_X_CENTER, m.nozzle_outer_dimension.x = m.obj_side[RIGHT] - m.obj_side[LEFT] - dimensions.x); - TERN_(HAS_Y_CENTER, m.nozzle_outer_dimension.y = m.obj_side[BACK] - m.obj_side[FRONT] - dimensions.y); park_above_object(m, uncertainty); @@ -470,109 +468,61 @@ inline void probe_sides(measurements_t &m, const float uncertainty) { inline void report_measured_center(const measurements_t &m) { SERIAL_ECHOLNPGM("Center:"); - #if HAS_X_CENTER - SERIAL_ECHOLNPGM_P(SP_X_STR, m.obj_center.x); - #endif - #if HAS_Y_CENTER - SERIAL_ECHOLNPGM_P(SP_Y_STR, m.obj_center.y); - #endif + TERF(HAS_X_CENTER, SERIAL_ECHOLNPGM_P)(SP_X_STR, m.obj_center.x); + TERF(HAS_Y_CENTER, SERIAL_ECHOLNPGM_P)(SP_Y_STR, m.obj_center.y); SERIAL_ECHOLNPGM_P(SP_Z_STR, m.obj_center.z); - #if HAS_I_CENTER - SERIAL_ECHOLNPGM_P(SP_I_STR, m.obj_center.i); - #endif - #if HAS_J_CENTER - SERIAL_ECHOLNPGM_P(SP_J_STR, m.obj_center.j); - #endif - #if HAS_K_CENTER - SERIAL_ECHOLNPGM_P(SP_K_STR, m.obj_center.k); - #endif - #if HAS_U_CENTER - SERIAL_ECHOLNPGM_P(SP_U_STR, m.obj_center.u); - #endif - #if HAS_V_CENTER - SERIAL_ECHOLNPGM_P(SP_V_STR, m.obj_center.v); - #endif - #if HAS_W_CENTER - SERIAL_ECHOLNPGM_P(SP_W_STR, m.obj_center.w); - #endif + TERF(HAS_I_CENTER, SERIAL_ECHOLNPGM_P)(SP_I_STR, m.obj_center.i); + TERF(HAS_J_CENTER, SERIAL_ECHOLNPGM_P)(SP_J_STR, m.obj_center.j); + TERF(HAS_K_CENTER, SERIAL_ECHOLNPGM_P)(SP_K_STR, m.obj_center.k); + TERF(HAS_U_CENTER, SERIAL_ECHOLNPGM_P)(SP_U_STR, m.obj_center.u); + TERF(HAS_V_CENTER, SERIAL_ECHOLNPGM_P)(SP_V_STR, m.obj_center.v); + TERF(HAS_W_CENTER, SERIAL_ECHOLNPGM_P)(SP_W_STR, m.obj_center.w); SERIAL_EOL(); } inline void report_measured_backlash(const measurements_t &m) { SERIAL_ECHOLNPGM("Backlash:"); #if AXIS_CAN_CALIBRATE(X) - #if ENABLED(CALIBRATION_MEASURE_LEFT) - SERIAL_ECHOLNPGM(" Left: ", m.backlash[LEFT]); - #endif - #if ENABLED(CALIBRATION_MEASURE_RIGHT) - SERIAL_ECHOLNPGM(" Right: ", m.backlash[RIGHT]); - #endif + TERF(CALIBRATION_MEASURE_LEFT, SERIAL_ECHOLNPGM)(" Left: ", m.backlash[LEFT]); + TERF(CALIBRATION_MEASURE_RIGHT, SERIAL_ECHOLNPGM)(" Right: ", m.backlash[RIGHT]); #endif #if AXIS_CAN_CALIBRATE(Y) - #if ENABLED(CALIBRATION_MEASURE_FRONT) - SERIAL_ECHOLNPGM(" Front: ", m.backlash[FRONT]); - #endif - #if ENABLED(CALIBRATION_MEASURE_BACK) - SERIAL_ECHOLNPGM(" Back: ", m.backlash[BACK]); - #endif + TERF(CALIBRATION_MEASURE_FRONT, SERIAL_ECHOLNPGM)(" Front: ", m.backlash[FRONT]); + TERF(CALIBRATION_MEASURE_BACK, SERIAL_ECHOLNPGM)(" Back: ", m.backlash[BACK]); #endif #if AXIS_CAN_CALIBRATE(Z) SERIAL_ECHOLNPGM(" Top: ", m.backlash[TOP]); #endif #if AXIS_CAN_CALIBRATE(I) - #if ENABLED(CALIBRATION_MEASURE_IMIN) - SERIAL_ECHOLNPGM(" " STR_I_MIN ": ", m.backlash[IMINIMUM]); - #endif - #if ENABLED(CALIBRATION_MEASURE_IMAX) - SERIAL_ECHOLNPGM(" " STR_I_MAX ": ", m.backlash[IMAXIMUM]); - #endif + TERF(CALIBRATION_MEASURE_IMIN, SERIAL_ECHOLNPGM)(" " STR_I_MIN ": ", m.backlash[IMINIMUM]); + TERF(CALIBRATION_MEASURE_IMAX, SERIAL_ECHOLNPGM)(" " STR_I_MAX ": ", m.backlash[IMAXIMUM]); #endif #if AXIS_CAN_CALIBRATE(J) - #if ENABLED(CALIBRATION_MEASURE_JMIN) - SERIAL_ECHOLNPGM(" " STR_J_MIN ": ", m.backlash[JMINIMUM]); - #endif - #if ENABLED(CALIBRATION_MEASURE_JMAX) - SERIAL_ECHOLNPGM(" " STR_J_MAX ": ", m.backlash[JMAXIMUM]); - #endif + TERF(CALIBRATION_MEASURE_JMIN, SERIAL_ECHOLNPGM)(" " STR_J_MIN ": ", m.backlash[JMINIMUM]); + TERF(CALIBRATION_MEASURE_JMAX, SERIAL_ECHOLNPGM)(" " STR_J_MAX ": ", m.backlash[JMAXIMUM]); #endif #if AXIS_CAN_CALIBRATE(K) - #if ENABLED(CALIBRATION_MEASURE_KMIN) - SERIAL_ECHOLNPGM(" " STR_K_MIN ": ", m.backlash[KMINIMUM]); - #endif - #if ENABLED(CALIBRATION_MEASURE_KMAX) - SERIAL_ECHOLNPGM(" " STR_K_MAX ": ", m.backlash[KMAXIMUM]); - #endif + TERF(CALIBRATION_MEASURE_KMIN, SERIAL_ECHOLNPGM)(" " STR_K_MIN ": ", m.backlash[KMINIMUM]); + TERF(CALIBRATION_MEASURE_KMAX, SERIAL_ECHOLNPGM)(" " STR_K_MAX ": ", m.backlash[KMAXIMUM]); #endif #if AXIS_CAN_CALIBRATE(U) - #if ENABLED(CALIBRATION_MEASURE_UMIN) - SERIAL_ECHOLNPGM(" " STR_U_MIN ": ", m.backlash[UMINIMUM]); - #endif - #if ENABLED(CALIBRATION_MEASURE_UMAX) - SERIAL_ECHOLNPGM(" " STR_U_MAX ": ", m.backlash[UMAXIMUM]); - #endif + TERF(CALIBRATION_MEASURE_UMIN, SERIAL_ECHOLNPGM)(" " STR_U_MIN ": ", m.backlash[UMINIMUM]); + TERF(CALIBRATION_MEASURE_UMAX, SERIAL_ECHOLNPGM)(" " STR_U_MAX ": ", m.backlash[UMAXIMUM]); #endif #if AXIS_CAN_CALIBRATE(V) - #if ENABLED(CALIBRATION_MEASURE_VMIN) - SERIAL_ECHOLNPGM(" " STR_V_MIN ": ", m.backlash[VMINIMUM]); - #endif - #if ENABLED(CALIBRATION_MEASURE_VMAX) - SERIAL_ECHOLNPGM(" " STR_V_MAX ": ", m.backlash[VMAXIMUM]); - #endif + TERF(CALIBRATION_MEASURE_VMIN, SERIAL_ECHOLNPGM)(" " STR_V_MIN ": ", m.backlash[VMINIMUM]); + TERF(CALIBRATION_MEASURE_VMAX, SERIAL_ECHOLNPGM)(" " STR_V_MAX ": ", m.backlash[VMAXIMUM]); #endif #if AXIS_CAN_CALIBRATE(W) - #if ENABLED(CALIBRATION_MEASURE_WMIN) - SERIAL_ECHOLNPGM(" " STR_W_MIN ": ", m.backlash[WMINIMUM]); - #endif - #if ENABLED(CALIBRATION_MEASURE_WMAX) - SERIAL_ECHOLNPGM(" " STR_W_MAX ": ", m.backlash[WMAXIMUM]); - #endif + TERF(CALIBRATION_MEASURE_WMIN, SERIAL_ECHOLNPGM)(" " STR_W_MIN ": ", m.backlash[WMINIMUM]); + TERF(CALIBRATION_MEASURE_WMAX, SERIAL_ECHOLNPGM)(" " STR_W_MAX ": ", m.backlash[WMAXIMUM]); #endif SERIAL_EOL(); } inline void report_measured_positional_error(const measurements_t &m) { SERIAL_CHAR('T'); - SERIAL_ECHO(active_extruder); + SERIAL_ECHO(motion.extruder); SERIAL_ECHOLNPGM(" Positional Error:"); #if HAS_X_CENTER && AXIS_CAN_CALIBRATE(X) SERIAL_ECHOLNPGM_P(SP_X_STR, m.pos_error.x); @@ -606,12 +556,8 @@ inline void probe_sides(measurements_t &m, const float uncertainty) { inline void report_measured_nozzle_dimensions(const measurements_t &m) { SERIAL_ECHOLNPGM("Nozzle Tip Outer Dimensions:"); - #if HAS_X_CENTER - SERIAL_ECHOLNPGM_P(SP_X_STR, m.nozzle_outer_dimension.x); - #endif - #if HAS_Y_CENTER - SERIAL_ECHOLNPGM_P(SP_Y_STR, m.nozzle_outer_dimension.y); - #endif + TERF(HAS_X_CENTER, SERIAL_ECHOLNPGM_P)(SP_X_STR, m.nozzle_outer_dimension.x); + TERF(HAS_Y_CENTER, SERIAL_ECHOLNPGM_P)(SP_Y_STR, m.nozzle_outer_dimension.y); SERIAL_EOL(); UNUSED(m); } @@ -622,7 +568,7 @@ inline void probe_sides(measurements_t &m, const float uncertainty) { // inline void report_hotend_offsets() { for (uint8_t e = 1; e < HOTENDS; ++e) - SERIAL_ECHOLNPGM_P(PSTR("T"), e, PSTR(" Hotend Offset X"), hotend_offset[e].x, SP_Y_STR, hotend_offset[e].y, SP_Z_STR, hotend_offset[e].z); + SERIAL_ECHOLNPGM_P(PSTR("T"), e, PSTR(" Hotend Offset X"), motion.hotend_offset[e].x, SP_Y_STR, motion.hotend_offset[e].y, SP_Z_STR, motion.hotend_offset[e].z); } #endif @@ -727,14 +673,14 @@ inline void calibrate_backlash(measurements_t &m, const float uncertainty) { AXIS_CAN_CALIBRATE(I) * 3, AXIS_CAN_CALIBRATE(J) * 3, AXIS_CAN_CALIBRATE(K) * 3, AXIS_CAN_CALIBRATE(U) * 3, AXIS_CAN_CALIBRATE(V) * 3, AXIS_CAN_CALIBRATE(W) * 3 ); - current_position += move; calibration_move(); - current_position -= move; calibration_move(); + motion.position += move; calibration_move(); + motion.position -= move; calibration_move(); } #endif } inline void update_measurements(measurements_t &m, const AxisEnum axis) { - current_position[axis] += m.pos_error[axis]; + motion.position[axis] += m.pos_error[axis]; m.obj_center[axis] = true_center[axis]; m.pos_error[axis] = 0; } @@ -760,9 +706,10 @@ inline void calibrate_toolhead(measurements_t &m, const float uncertainty, const // Adjust the hotend offset #if HAS_HOTEND_OFFSET - if (ENABLED(HAS_X_CENTER) && AXIS_CAN_CALIBRATE(X)) hotend_offset[extruder].x += m.pos_error.x; - if (ENABLED(HAS_Y_CENTER) && AXIS_CAN_CALIBRATE(Y)) hotend_offset[extruder].y += m.pos_error.y; - if (AXIS_CAN_CALIBRATE(Z)) hotend_offset[extruder].z += m.pos_error.z; + xyz_pos_t &hotoff = motion.active_hotend_offset(); + if (ENABLED(HAS_X_CENTER) && AXIS_CAN_CALIBRATE(X)) hotoff.x += m.pos_error.x; + if (ENABLED(HAS_Y_CENTER) && AXIS_CAN_CALIBRATE(Y)) hotoff.y += m.pos_error.y; + if (AXIS_CAN_CALIBRATE(Z)) hotoff.z += m.pos_error.z; normalize_hotend_offsets(); #endif @@ -780,7 +727,7 @@ inline void calibrate_toolhead(measurements_t &m, const float uncertainty, const TERN_(HAS_V_CENTER, update_measurements(m, V_AXIS)); TERN_(HAS_W_CENTER, update_measurements(m, W_AXIS)); - sync_plan_position(); + motion.sync_plan_position(); } /** @@ -815,7 +762,7 @@ inline void calibrate_all_toolheads(measurements_t &m, const float uncertainty) inline void calibrate_all() { measurements_t m; - TERN_(HAS_HOTEND_OFFSET, reset_hotend_offsets()); + TERN_(HAS_HOTEND_OFFSET, motion.reset_hotend_offsets()); TEMPORARY_BACKLASH_CORRECTION(backlash.all_on); TEMPORARY_BACKLASH_SMOOTHING(0.0f); @@ -833,7 +780,7 @@ inline void calibrate_all() { // Do a slow and precise calibration of the toolheads calibrate_all_toolheads(m, CALIBRATION_MEASUREMENT_UNCERTAIN); - current_position.x = X_CENTER; + motion.position.x = X_CENTER; calibration_move(); // Park nozzle away from calibration object } @@ -853,10 +800,10 @@ void GcodeSuite::G425() { process_subcommands_now(F(CALIBRATION_SCRIPT_PRE)); #endif - if (homing_needed_error()) return; + if (motion.homing_needed_error()) return; TEMPORARY_BED_LEVELING_STATE(false); - SET_SOFT_ENDSTOP_LOOSE(true); + motion.set_soft_endstop_loose(true); measurements_t m; const float uncertainty = parser.floatval('U', CALIBRATION_MEASUREMENT_UNCERTAIN); @@ -864,7 +811,7 @@ void GcodeSuite::G425() { if (parser.seen_test('B')) calibrate_backlash(m, uncertainty); else if (parser.seen_test('T')) - calibrate_toolhead(m, uncertainty, parser.intval('T', active_extruder)); + calibrate_toolhead(m, uncertainty, parser.intval('T', motion.extruder)); #if ENABLED(CALIBRATION_REPORTING) else if (parser.seen('V')) { probe_sides(m, uncertainty); @@ -883,7 +830,7 @@ void GcodeSuite::G425() { else calibrate_all(); - SET_SOFT_ENDSTOP_LOOSE(false); + motion.set_soft_endstop_loose(false); #ifdef CALIBRATION_SCRIPT_POST process_subcommands_now(F(CALIBRATION_SCRIPT_POST)); diff --git a/Marlin/src/gcode/calibrate/G76_M871.cpp b/Marlin/src/gcode/calibrate/G76_M871.cpp index bb69b75d5056..d3fd16625762 100644 --- a/Marlin/src/gcode/calibrate/G76_M871.cpp +++ b/Marlin/src/gcode/calibrate/G76_M871.cpp @@ -38,47 +38,53 @@ #include "../../lcd/marlinui.h" /** - * G76: calibrate probe and/or bed temperature offsets - * Notes: - * - When calibrating probe, bed temperature is held constant. - * Compensation values are deltas to first probe measurement at probe temp. = 30°C. - * - When calibrating bed, probe temperature is held constant. - * Compensation values are deltas to first probe measurement at bed temp. = 60°C. - * - The hotend will not be heated at any time. - * - On my PrÅÆÅ”a MK3S clone I put a piece of paper between the probe and the hotend - * so the hotend fan would not cool my probe constantly. Alternatively you could just - * make sure the fan is not running while running the calibration process. + * G76: Probe Temperature Calibration * - * Probe calibration: - * - Moves probe to cooldown point. - * - Heats up bed to 100°C. - * - Moves probe to probing point (1mm above heatbed). - * - Waits until probe reaches target temperature (30°C). - * - Does a z-probing (=base value) and increases target temperature by 5°C. - * - Waits until probe reaches increased target temperature. - * - Does a z-probing (delta to base value will be a compensation value) and increases target temperature by 5°C. - * - Repeats last two steps until max. temperature reached or timeout (i.e. probe does not heat up any further). - * - Compensation values of higher temperatures will be extrapolated (using linear regression first). - * While this is not exact by any means it is still better than simply using the last compensation value. + * Calibrate probe and/or bed temperature offsets. * - * Bed calibration: - * - Moves probe to cooldown point. - * - Heats up bed to 60°C. - * - Moves probe to probing point (1mm above heatbed). - * - Waits until probe reaches target temperature (30°C). - * - Does a z-probing (=base value) and increases bed temperature by 5°C. - * - Moves probe to cooldown point. - * - Waits until probe is below 30°C and bed has reached target temperature. - * - Moves probe to probing point and waits until it reaches target temperature (30°C). - * - Does a z-probing (delta to base value will be a compensation value) and increases bed temperature by 5°C. - * - Repeats last four points until max. bed temperature reached (110°C) or timeout. - * - Compensation values of higher temperatures will be extrapolated (using linear regression first). - * While this is not exact by any means it is still better than simply using the last compensation value. + * Probe calibration: + * - Moves probe to cooldown point. + * - Heats up bed to 100°C. + * - Moves probe to probing point (1mm above heatbed). + * - Waits until probe reaches target temperature (30°C). + * - Does a z-probing (=base value) and increases target temperature by 5°C. + * - Waits until probe reaches increased target temperature. + * - Does a z-probing (delta to base value will be a compensation value) and increases target temperature by 5°C. + * - Repeats last two steps until max. temperature reached or timeout (i.e. probe does not heat up any further). + * - Compensation values of higher temperatures will be extrapolated (using linear regression first). + * While this is not exact by any means it is still better than simply using the last compensation value. * - * G76 [B | P] - * - no flag - Both calibration procedures will be run. - * - `B` - Run bed temperature calibration. - * - `P` - Run probe temperature calibration. + * Bed calibration: + * - Moves probe to cooldown point. + * - Heats up bed to 60°C. + * - Moves probe to probing point (1mm above heatbed). + * - Waits until probe reaches target temperature (30°C). + * - Does a z-probing (=base value) and increases bed temperature by 5°C. + * - Moves probe to cooldown point. + * - Waits until probe is below 30°C and bed has reached target temperature. + * - Moves probe to probing point and waits until it reaches target temperature (30°C). + * - Does a z-probing (delta to base value will be a compensation value) and increases bed temperature by 5°C. + * - Repeats last four points until max. bed temperature reached (110°C) or timeout. + * - Compensation values of higher temperatures will be extrapolated (using linear regression first). + * While this is not exact by any means it is still better than simply using the last compensation value. + * + * Usage: + * G76 [ B | P ] + * + * Parameters: + * None Run Both calibration procedures + * B Calibrate bed only + * P Calibrate probe only + * + * NOTES: + * - When calibrating probe, bed temperature is held constant. + * Compensation values are deltas to first probe measurement at probe temp. = 30°C. + * - When calibrating bed, probe temperature is held constant. + * Compensation values are deltas to first probe measurement at bed temp. = 60°C. + * - The hotend will not be heated at any time. + * - On my PrÅÆÅ”a MK3S clone I put a piece of paper between the probe and the hotend + * so the hotend fan would not cool my probe constantly. Alternatively you could just + * make sure the fan is not running while running the calibration process. */ #if ALL(PTC_PROBE, PTC_BED) @@ -90,11 +96,11 @@ void GcodeSuite::G76() { auto report_temps = [](millis_t &ntr, millis_t timeout=0) { - idle_no_sleep(); + marlin.idle_no_sleep(); const millis_t ms = millis(); if (ELAPSED(ms, ntr)) { ntr = ms + 1000; - thermalManager.print_heater_states(active_extruder); + thermalManager.print_heater_states(motion.extruder); } return (timeout && ELAPSED(ms, timeout)); }; @@ -144,7 +150,7 @@ if (do_bed_cal || do_probe_cal) { // Ensure park position is reachable - bool reachable = position_is_reachable(parkpos) || WITHIN(parkpos.z, Z_MIN_POS - fslop, Z_MAX_POS + fslop); + bool reachable = motion.can_reach(parkpos) || WITHIN(parkpos.z, Z_MIN_POS - fslop, Z_MAX_POS + fslop); if (!reachable) SERIAL_ECHOLNPGM("!Park"); else { @@ -161,7 +167,7 @@ process_subcommands_now(FPSTR(G28_STR)); } - remember_feedrate_scaling_off(); + motion.remember_feedrate_scaling_off(); /****************************************** * Calibrate bed temperature offsets @@ -192,7 +198,7 @@ report_targets(target_bed, target_probe); // Park nozzle - do_blocking_move_to(parkpos); + motion.blocking_move(parkpos); // Wait for heatbed to reach target temp and probe to cool below target temp if (wait_for_temps(target_bed, target_probe, next_temp_report, millis() + MIN_TO_MS(15))) { @@ -201,7 +207,7 @@ } // Move the nozzle to the probing point and wait for the probe to reach target temp - do_blocking_move_to(noz_pos_xyz); + motion.blocking_move(noz_pos_xyz); say_waiting_for_probe_heating(); SERIAL_EOL(); while (thermalManager.wholeDegProbe() < target_probe) @@ -233,7 +239,7 @@ if (do_probe_cal) { // Park nozzle - do_blocking_move_to(parkpos); + motion.blocking_move(parkpos); // Initialize temperatures const celsius_t target_bed = BED_MAX_TARGET; @@ -252,7 +258,7 @@ bool timeout = false; for (uint8_t idx = 0; idx <= PTC_PROBE_COUNT; idx++) { // Move probe to probing point and wait for it to reach target temperature - do_blocking_move_to(noz_pos_xyz); + motion.blocking_move(noz_pos_xyz); say_waiting_for_probe_heating(); SERIAL_ECHOLNPGM(" Bed:", target_bed, " Probe:", target_probe); @@ -285,32 +291,36 @@ ptc.print_offsets(); } // do_probe_cal - restore_feedrate_and_scaling(); + motion.restore_feedrate_and_scaling(); } #endif // PTC_PROBE && PTC_BED /** - * M871: Report / reset temperature compensation offsets. - * Note: This does not affect values in EEPROM until M500. + * M871: Probe Temperature Config + * + * Report / reset temperature compensation offsets. + * NOTE: This does not affect values in EEPROM until M500. * + * Usage: * M871 [ R | B | P | E ] * - * No Parameters - Print current offset values. + * Parameters: + * None Print current offset values * - * Select only one of these flags: - * R - Reset all offsets to zero (i.e., disable compensation). - * B - Manually set offset for bed - * P - Manually set offset for probe - * E - Manually set offset for extruder + * Select only one of these flags: + * R Reset all offsets to zero (i.e., disable compensation) + * B Manually set offset for bed + * P Manually set offset for probe + * E Manually set offset for extruder * - * With B, P, or E: - * I[index] - Index in the array - * V[value] - Adjustment in µm + * With B, P, or E: + * I Index in the array + * V Adjustment in µm */ void GcodeSuite::M871() { - if (parser.seen('R')) { + if (parser.seen_test('R')) { // Reset z-probe offsets to factory defaults ptc.clear_all_offsets(); SERIAL_ECHOLNPGM("Offsets reset to default."); diff --git a/Marlin/src/gcode/calibrate/M100.cpp b/Marlin/src/gcode/calibrate/M100.cpp index c05fe12fc389..822ce969ecc9 100644 --- a/Marlin/src/gcode/calibrate/M100.cpp +++ b/Marlin/src/gcode/calibrate/M100.cpp @@ -28,39 +28,37 @@ #include "../queue.h" #include "../../libs/hex_print.h" -#include "../../MarlinCore.h" // for idle() - /** - * M100 Free Memory Watcher + * M100: Free Memory Watcher * * This code watches the free memory block between the bottom of the heap and the top of the stack. * This memory block is initialized and watched via the M100 command. * - * M100 I Initializes the free memory block and prints vitals statistics about the area + * Parameters: + * I Initializes the free memory block and prints vitals statistics about the area * - * M100 F Identifies how much of the free memory block remains free and unused. It also - * detects and reports any corruption within the free memory block that may have - * happened due to errant firmware. + * F Identifies how much of the free memory block remains free and unused. It also + * detects and reports any corruption within the free memory block that may have + * happened due to errant firmware. * - * M100 D Does a hex display of the free memory block along with a flag for any errant - * data that does not match the expected value. + * D Does a hex display of the free memory block along with a flag for any errant + * data that does not match the expected value. * - * M100 C x Corrupts x locations within the free memory block. This is useful to check the - * correctness of the M100 F and M100 D commands. + * C x Corrupts x locations within the free memory block. This is useful to check the + * correctness of the M100 F and M100 D commands. * * Also, there are two support functions that can be called from a developer's C code. - * - * uint16_t check_for_free_memory_corruption(PGM_P const free_memory_start); - * void M100_dump_routine(FSTR_P const title, const char * const start, const uintptr_t size); + * uint16_t check_for_free_memory_corruption(PGM_P const free_memory_start); + * void M100_dump_routine(FSTR_P const title, const char * const start, const uintptr_t size); * * Initial version by Roxy-3D */ -#define M100_FREE_MEMORY_DUMPER // Enable for the `M100 D` Dump sub-command -#define M100_FREE_MEMORY_CORRUPTOR // Enable for the `M100 C` Corrupt sub-command +#define M100_FREE_MEMORY_DUMPER // Enable for the 'M100 D' Dump sub-command +#define M100_FREE_MEMORY_CORRUPTOR // Enable for the 'M100 C' Corrupt sub-command #define TEST_BYTE ((char) 0xE5) -#if ANY(__AVR__, IS_32BIT_TEENSY) +#if ANY(__AVR__, IS_32BIT_TEENSY) && !IS_TEENSY_40_41 extern char __bss_end; char *end_bss = &__bss_end, @@ -178,7 +176,7 @@ inline int32_t count_test_bytes(const char * const start_free_memory) { SERIAL_EOL(); start_free_memory += 16; serial_delay(25); - idle(); + marlin.idle(); } } @@ -209,12 +207,12 @@ inline int check_for_free_memory_corruption(FSTR_P const title) { if (end_free_memory < start_free_memory) { SERIAL_ECHOPGM(" end_free_memory < Heap "); //SET_INPUT_PULLUP(63); // if the developer has a switch wired up to their controller board - //safe_delay(5); // this code can be enabled to pause the display as soon as the - //while ( READ(63)) // malfunction is detected. It is currently defaulting to a switch - // idle(); // being on pin-63 which is unassigend and available on most controller - //safe_delay(20); // boards. + //safe_delay(5); // this code can be enabled to pause the display as soon as the + //while ( READ(63)) // malfunction is detected. It is currently defaulting to a switch + // marlin.idle(); // being on pin-63 which is unassigend and available on most controller + //safe_delay(20); // boards. //while ( !READ(63)) - // idle(); + // marlin.idle(); serial_delay(20); #if ENABLED(M100_FREE_MEMORY_DUMPER) M100_dump_routine(F(" Memory corruption detected with end_free_memory Enable/disable/fade-out backlash correction (0.0 to 1.0) - * S Distance over which backlash correction is spread - * X Set the backlash distance on X (0 to disable) - * Y ... on Y - * Z ... on Z - * X If a backlash measurement was done on X, copy that value - * Y ... on Y - * Z ... on Z + * F Enable/disable/fade-out backlash correction (0.0 to 1.0) + * S Distance over which backlash correction is spread + * X Set the backlash distance on X (0 to disable) + * Y ... on Y + * Z ... on Z + * X If a backlash measurement was done on X, copy that value + * Y ... on Y + * Z ... on Z * * Type M425 without any arguments to show active values. */ @@ -94,7 +94,7 @@ void GcodeSuite::M425() { #if ENABLED(MEASURE_BACKLASH_WHEN_PROBING) SERIAL_ECHOPGM(" Average measured backlash (mm):"); if (backlash.has_any_measurement()) { - LOOP_NUM_AXES(a) if (axis_can_calibrate(a) && backlash.has_measurement(AxisEnum(a))) { + LOOP_NUM_AXES(a) if (axis_can_calibrate(a) && backlash.has_measurement((AxisEnum)a)) { SERIAL_ECHOPGM_P((PGM_P)pgm_read_ptr(&SP_AXIS_STR[a]), backlash.get_measurement((AxisEnum)a)); } } @@ -109,25 +109,26 @@ void GcodeSuite::M425_report(const bool forReplay/*=true*/) { TERN_(MARLIN_SMALL_BUILD, return); report_heading_etc(forReplay, F(STR_BACKLASH_COMPENSATION)); - SERIAL_ECHOLNPGM_P( + SERIAL_ECHOPGM_P( PSTR(" M425 F"), backlash.get_correction() #ifdef BACKLASH_SMOOTHING_MM , PSTR(" S"), LINEAR_UNIT(backlash.get_smoothing_mm()) #endif - #if NUM_AXES - , LIST_N(DOUBLE(NUM_AXES), - SP_X_STR, LINEAR_UNIT(backlash.get_distance_mm(X_AXIS)), - SP_Y_STR, LINEAR_UNIT(backlash.get_distance_mm(Y_AXIS)), - SP_Z_STR, LINEAR_UNIT(backlash.get_distance_mm(Z_AXIS)), - SP_I_STR, I_AXIS_UNIT(backlash.get_distance_mm(I_AXIS)), - SP_J_STR, J_AXIS_UNIT(backlash.get_distance_mm(J_AXIS)), - SP_K_STR, K_AXIS_UNIT(backlash.get_distance_mm(K_AXIS)), - SP_U_STR, U_AXIS_UNIT(backlash.get_distance_mm(U_AXIS)), - SP_V_STR, V_AXIS_UNIT(backlash.get_distance_mm(V_AXIS)), - SP_W_STR, W_AXIS_UNIT(backlash.get_distance_mm(W_AXIS)) - ) - #endif ); + #if NUM_AXES + SERIAL_ECHOPGM_P(NUM_AXIS_PAIRED_LIST( + SP_X_STR, LINEAR_UNIT(backlash.get_distance_mm(X_AXIS)), + SP_Y_STR, LINEAR_UNIT(backlash.get_distance_mm(Y_AXIS)), + SP_Z_STR, LINEAR_UNIT(backlash.get_distance_mm(Z_AXIS)), + SP_I_STR, I_AXIS_UNIT(backlash.get_distance_mm(I_AXIS)), + SP_J_STR, J_AXIS_UNIT(backlash.get_distance_mm(J_AXIS)), + SP_K_STR, K_AXIS_UNIT(backlash.get_distance_mm(K_AXIS)), + SP_U_STR, U_AXIS_UNIT(backlash.get_distance_mm(U_AXIS)), + SP_V_STR, V_AXIS_UNIT(backlash.get_distance_mm(V_AXIS)), + SP_W_STR, W_AXIS_UNIT(backlash.get_distance_mm(W_AXIS)) + )); + #endif + SERIAL_EOL(); } #endif // BACKLASH_GCODE diff --git a/Marlin/src/gcode/calibrate/M48.cpp b/Marlin/src/gcode/calibrate/M48.cpp index 8dd951e054b3..32abd335fe74 100644 --- a/Marlin/src/gcode/calibrate/M48.cpp +++ b/Marlin/src/gcode/calibrate/M48.cpp @@ -39,6 +39,10 @@ #include "../../feature/probe_temp_comp.h" #endif +#if ENABLED(FT_MOTION) + #include "../../module/ft_motion.h" +#endif + /** * M48: Z probe repeatability measurement function. * @@ -58,7 +62,7 @@ void GcodeSuite::M48() { - if (homing_needed_error()) return; + if (motion.homing_needed_error()) return; const int8_t verbose_level = parser.byteval('V', 1); if (!WITHIN(verbose_level, 0, 4)) { @@ -76,8 +80,8 @@ void GcodeSuite::M48() { // Test at the current position by default, overridden by X and Y const xy_pos_t test_position = { - parser.linearval('X', current_position.x + probe.offset_xy.x), // If no X use the probe's current X position - parser.linearval('Y', current_position.y + probe.offset_xy.y) // If no Y, ditto + parser.linearval('X', motion.position.x + probe.offset_xy.x), // If no X use the probe's current X position + parser.linearval('Y', motion.position.y + probe.offset_xy.y) // If no Y, ditto }; if (!probe.can_reach(test_position)) { @@ -93,6 +97,10 @@ void GcodeSuite::M48() { SERIAL_ECHOLNPGM(GCODE_ERR_MSG("Legs of movement implausible (0-15).")); return; } + + // Potentially disable Fixed-Time Motion for probing + TERN_(FT_MOTION, FTM_DISABLE_IN_SCOPE()); + if (n_legs == 1) n_legs = 2; // Schizoid motion as an optional stress-test @@ -115,7 +123,7 @@ void GcodeSuite::M48() { TERN_(HAS_PTC, ptc.set_enabled(parser.boolval('C', true))); // Work with reasonable feedrates - remember_feedrate_scaling_off(); + motion.remember_feedrate_scaling_off(); // Working variables float mean = 0.0, // The average of all points so far, used to calculate deviation @@ -124,7 +132,7 @@ void GcodeSuite::M48() { max = -99999.9, // Largest value sampled so far sample_set[n_samples]; // Storage for sampled values - auto dev_report = [](const bool verbose, const_float_t mean, const_float_t sigma, const_float_t min, const_float_t max, const bool final=false) { + auto dev_report = [](const bool verbose, const float mean, const float sigma, const float min, const float max, const bool final=false) { if (verbose) { SERIAL_ECHOPGM("Mean: ", p_float_t(mean, 6)); if (!final) SERIAL_ECHOPGM(" Sigma: ", p_float_t(sigma, 6)); @@ -216,7 +224,7 @@ void GcodeSuite::M48() { if (verbose_level > 3) SERIAL_ECHOLN(F("Going to: X"), next_pos.x, FPSTR(SP_Y_STR), next_pos.y); - do_blocking_move_to_xy(next_pos); + motion.blocking_move_xy(next_pos); } // n_legs loop } // n_legs @@ -261,11 +269,23 @@ void GcodeSuite::M48() { #if HAS_STATUS_MESSAGE // Display M48 results in the status bar - ui.set_status_and_level(MString<30>(GET_TEXT_F(MSG_M48_DEVIATION), F(": "), w_float_t(sigma, 2, 6))); + if (MAX_MESSAGE_SIZE <= 20) { + // 12345678901234567890 + // Deviation: 0.123456 + ui.set_status_and_level(TS(GET_TEXT_F(MSG_M48_DEVIATION), F(": "), w_float_t(sigma, 2, 6))); + } else if (MAX_MESSAGE_SIZE <= 30) { + // 123456789012345678901234567890 + // Dev:0.12345, Max delta:0.12345 + ui.set_status_and_level(TS(GET_TEXT_F(MSG_M48_DEV), ':', w_float_t(sigma, 2, 5), F(", "), GET_TEXT(MSG_M48_MAX_DELTA), ':', w_float_t(_MAX(mean - min, max - mean), 2, 5))); + } else { + // 1234567890123456789012345678901234567890 + // Deviation: 1.23456, Max delta: 1.23456 + ui.set_status_and_level(TS(GET_TEXT_F(MSG_M48_DEVIATION), F(": "), w_float_t(sigma, 2, 6), F(", "), GET_TEXT(MSG_M48_MAX_DELTA), F(": "), w_float_t(_MAX(mean - min, max - mean), 2, 6))); + } #endif } - restore_feedrate_and_scaling(); + motion.restore_feedrate_and_scaling(); // Re-enable bed level correction if it had been on TERN_(HAS_LEVELING, set_bed_leveling_enabled(was_enabled)); @@ -273,7 +293,7 @@ void GcodeSuite::M48() { // Re-enable probe temperature correction TERN_(HAS_PTC, ptc.set_enabled(true)); - report_current_position(); + motion.report_position(); } #endif // Z_MIN_PROBE_REPEATABILITY_TEST diff --git a/Marlin/src/gcode/calibrate/M665.cpp b/Marlin/src/gcode/calibrate/M665.cpp index 5409ff42324e..ac2be7652f38 100644 --- a/Marlin/src/gcode/calibrate/M665.cpp +++ b/Marlin/src/gcode/calibrate/M665.cpp @@ -106,13 +106,13 @@ #if HAS_SCARA_OFFSET - if (parser.seenval('Z')) scara_home_offset.z = parser.value_linear_units(); + if (parser.seenval('Z')) motion.scara_home_offset.z = parser.value_linear_units(); const bool hasA = parser.seenval('A'), hasP = parser.seenval('P'), hasX = parser.seenval('X'); const uint8_t sumAPX = hasA + hasP + hasX; if (sumAPX) { if (sumAPX == 1) - scara_home_offset.a = parser.value_float(); + motion.scara_home_offset.a = parser.value_float(); else { SERIAL_ERROR_MSG("Only one of A, P, or X is allowed."); return; @@ -123,7 +123,7 @@ const uint8_t sumBTY = hasB + hasT + hasY; if (sumBTY) { if (sumBTY == 1) - scara_home_offset.b = parser.value_float(); + motion.scara_home_offset.b = parser.value_float(); else { SERIAL_ERROR_MSG("Only one of B, T, or Y is allowed."); return; @@ -140,9 +140,9 @@ SERIAL_ECHOLNPGM_P( PSTR(" M665 S"), segments_per_second #if HAS_SCARA_OFFSET - , SP_P_STR, scara_home_offset.a - , SP_T_STR, scara_home_offset.b - , SP_Z_STR, LINEAR_UNIT(scara_home_offset.z) + , SP_P_STR, motion.scara_home_offset.a + , SP_T_STR, motion.scara_home_offset.b + , SP_Z_STR, LINEAR_UNIT(motion.scara_home_offset.z) #endif ); } diff --git a/Marlin/src/gcode/calibrate/M666.cpp b/Marlin/src/gcode/calibrate/M666.cpp index 418629015432..2584782cf302 100644 --- a/Marlin/src/gcode/calibrate/M666.cpp +++ b/Marlin/src/gcode/calibrate/M666.cpp @@ -39,7 +39,15 @@ #if ENABLED(DELTA) /** - * M666: Set delta endstop adjustment + * M666: Set Delta endstop adjustments + * + * Adjust the endstop offsets on a Delta printer. + * + * Parameters: + * None Report current offsets + * X Adjustment for the X actuator endstop + * Y Adjustment for the Y actuator endstop + * Z Adjustment for the Z actuator endstop */ void GcodeSuite::M666() { DEBUG_SECTION(log_M666, "M666", DEBUGGING(LEVELING)); @@ -74,14 +82,22 @@ #else /** - * M666: Set Dual Endstops offsets for X, Y, and/or Z. - * With no parameters report current offsets. + * M666: Set Dual Endstop Offsets + * + * Adjust the offsets for dual (or multiple) endstops. + * + * Parameters: + * None Report current offsets + * X Offset for the X axis endstops + * Y Offset for the Y axis endstops + * Z Offset for the Z axis endstops * - * For Triple / Quad Z Endstops: - * Set Z2 Only: M666 S2 Z - * Set Z3 Only: M666 S3 Z - * Set Z4 Only: M666 S4 Z - * Set All: M666 Z + * Example: + * For Triple / Quad Z Endstops: + * M666 S2 Z ; Set Z2 Only + * M666 S3 Z ; Set Z3 Only + * M666 S4 Z ; Set Z4 Only + * M666 Z ; Set All */ void GcodeSuite::M666() { if (!parser.seen_any()) return M666_report(); diff --git a/Marlin/src/gcode/calibrate/M852.cpp b/Marlin/src/gcode/calibrate/M852.cpp index 001160ae72c2..726aedefd8b5 100644 --- a/Marlin/src/gcode/calibrate/M852.cpp +++ b/Marlin/src/gcode/calibrate/M852.cpp @@ -28,12 +28,16 @@ #include "../../module/planner.h" /** - * M852: Get or set the machine skew factors. Reports current values with no arguments. + * M852: Bed Skew Compensation * - * S[xy_factor] - Alias for 'I' - * I[xy_factor] - New XY skew factor - * J[xz_factor] - New XZ skew factor - * K[yz_factor] - New YZ skew factor + * Get or set the machine skew factors; correct for misalignment + * + * Parameters: + * None Report current values + * S Alias for 'I' + * I New XY skew factor + * J New XZ skew factor + * K New YZ skew factor */ void GcodeSuite::M852() { if (!parser.seen("SIJK")) return M852_report(); @@ -85,9 +89,9 @@ void GcodeSuite::M852() { // When skew is changed the current position changes if (setval) { - set_current_from_steppers_for_axis(ALL_AXES_ENUM); - sync_plan_position(); - report_current_position(); + motion.set_current_from_steppers_for_axis(ALL_AXES_ENUM); + motion.sync_plan_position(); + motion.report_position(); } } diff --git a/Marlin/src/gcode/config/M200-M205.cpp b/Marlin/src/gcode/config/M200-M205.cpp index a0466fbeab63..53d0b4b4a89a 100644 --- a/Marlin/src/gcode/config/M200-M205.cpp +++ b/Marlin/src/gcode/config/M200-M205.cpp @@ -24,7 +24,7 @@ #include "../../MarlinCore.h" #include "../../module/planner.h" -#if DISABLED(NO_VOLUMETRICS) +#if HAS_VOLUMETRIC_EXTRUSION /** * M200: Set filament diameter and set E axis units to cubic units @@ -78,10 +78,10 @@ TERN_(MARLIN_SMALL_BUILD, return); if (!forReplay) { - report_heading(forReplay, F(STR_FILAMENT_SETTINGS), false); + report_heading(false, F(STR_FILAMENT_SETTINGS), false); if (!parser.volumetric_enabled) SERIAL_ECHOPGM(" (Disabled):"); SERIAL_EOL(); - report_echo_start(forReplay); + report_echo_start(false); } #if EXTRUDERS == 1 @@ -107,7 +107,7 @@ #endif } -#endif // !NO_VOLUMETRICS +#endif // HAS_VOLUMETRIC_EXTRUSION /** * M201: Set max acceleration in units/s^2 for print moves. @@ -124,8 +124,13 @@ * S : Speed factor percentage. */ void GcodeSuite::M201() { - if (!parser.seen("T" STR_AXES_LOGICAL TERN_(XY_FREQUENCY_LIMIT, "FS"))) + if (!parser.seen("T" STR_AXES_LOGICAL + #ifdef XY_FREQUENCY_LIMIT + "FS" + #endif + )) { return M201_report(); + } const int8_t target_extruder = get_target_extruder_from_command(); if (target_extruder < 0) return; @@ -147,30 +152,37 @@ void GcodeSuite::M201_report(const bool forReplay/*=true*/) { TERN_(MARLIN_SMALL_BUILD, return); report_heading_etc(forReplay, F(STR_MAX_ACCELERATION)); + + bool eol = false; + #if NUM_AXES - SERIAL_ECHOPGM_P( - LIST_N(DOUBLE(NUM_AXES), - PSTR(" M201 X"), LINEAR_UNIT(planner.settings.max_acceleration_mm_per_s2[X_AXIS]), - SP_Y_STR, LINEAR_UNIT(planner.settings.max_acceleration_mm_per_s2[Y_AXIS]), - SP_Z_STR, LINEAR_UNIT(planner.settings.max_acceleration_mm_per_s2[Z_AXIS]), - SP_I_STR, I_AXIS_UNIT(planner.settings.max_acceleration_mm_per_s2[I_AXIS]), - SP_J_STR, J_AXIS_UNIT(planner.settings.max_acceleration_mm_per_s2[J_AXIS]), - SP_K_STR, K_AXIS_UNIT(planner.settings.max_acceleration_mm_per_s2[K_AXIS]), - SP_U_STR, U_AXIS_UNIT(planner.settings.max_acceleration_mm_per_s2[U_AXIS]), - SP_V_STR, V_AXIS_UNIT(planner.settings.max_acceleration_mm_per_s2[V_AXIS]), - SP_W_STR, W_AXIS_UNIT(planner.settings.max_acceleration_mm_per_s2[W_AXIS]) - ) - ); + eol = true; + SERIAL_ECHOPGM_P(NUM_AXIS_PAIRED_LIST( + PSTR(" M201 X"), LINEAR_UNIT(planner.settings.max_acceleration_mm_per_s2[X_AXIS]), + SP_Y_STR, LINEAR_UNIT(planner.settings.max_acceleration_mm_per_s2[Y_AXIS]), + SP_Z_STR, LINEAR_UNIT(planner.settings.max_acceleration_mm_per_s2[Z_AXIS]), + SP_I_STR, I_AXIS_UNIT(planner.settings.max_acceleration_mm_per_s2[I_AXIS]), + SP_J_STR, J_AXIS_UNIT(planner.settings.max_acceleration_mm_per_s2[J_AXIS]), + SP_K_STR, K_AXIS_UNIT(planner.settings.max_acceleration_mm_per_s2[K_AXIS]), + SP_U_STR, U_AXIS_UNIT(planner.settings.max_acceleration_mm_per_s2[U_AXIS]), + SP_V_STR, V_AXIS_UNIT(planner.settings.max_acceleration_mm_per_s2[V_AXIS]), + SP_W_STR, W_AXIS_UNIT(planner.settings.max_acceleration_mm_per_s2[W_AXIS]) + )); #endif #if HAS_EXTRUDERS && DISABLED(DISTINCT_E_FACTORS) + eol = true; SERIAL_ECHOPGM_P(SP_E_STR, VOLUMETRIC_UNIT(planner.settings.max_acceleration_mm_per_s2[E_AXIS])); #endif - #if NUM_AXES || (HAS_EXTRUDERS && DISABLED(DISTINCT_E_FACTORS)) - SERIAL_EOL(); + #ifdef XY_FREQUENCY_LIMIT + eol = true; + SERIAL_ECHOPGM_P(PSTR(" F"), planner.xy_freq_limit_hz); + SERIAL_ECHOPGM_P(PSTR(" S"), (planner.xy_freq_min_speed_factor * 100)); #endif + if (eol) SERIAL_EOL(); + #if ENABLED(DISTINCT_E_FACTORS) for (uint8_t i = 0; i < E_STEPPERS; ++i) { report_echo_start(forReplay); @@ -205,33 +217,34 @@ void GcodeSuite::M203_report(const bool forReplay/*=true*/) { TERN_(MARLIN_SMALL_BUILD, return); report_heading_etc(forReplay, F(STR_MAX_FEEDRATES)); + + bool eol = false; + #if NUM_AXES - SERIAL_ECHOPGM_P( - LIST_N(DOUBLE(NUM_AXES), - PSTR(" M203 X"), LINEAR_UNIT(planner.settings.max_feedrate_mm_s[X_AXIS]), - SP_Y_STR, LINEAR_UNIT(planner.settings.max_feedrate_mm_s[Y_AXIS]), - SP_Z_STR, LINEAR_UNIT(planner.settings.max_feedrate_mm_s[Z_AXIS]), - SP_I_STR, LINEAR_UNIT(planner.settings.max_feedrate_mm_s[I_AXIS]), - SP_J_STR, LINEAR_UNIT(planner.settings.max_feedrate_mm_s[J_AXIS]), - SP_K_STR, LINEAR_UNIT(planner.settings.max_feedrate_mm_s[K_AXIS]), - SP_U_STR, LINEAR_UNIT(planner.settings.max_feedrate_mm_s[U_AXIS]), - SP_V_STR, LINEAR_UNIT(planner.settings.max_feedrate_mm_s[V_AXIS]), - SP_W_STR, LINEAR_UNIT(planner.settings.max_feedrate_mm_s[W_AXIS]) - ) - ); + eol = true; + SERIAL_ECHOPGM_P(NUM_AXIS_PAIRED_LIST( + PSTR(" M203 X"), LINEAR_UNIT(planner.settings.max_feedrate_mm_s[X_AXIS]), + SP_Y_STR, LINEAR_UNIT(planner.settings.max_feedrate_mm_s[Y_AXIS]), + SP_Z_STR, LINEAR_UNIT(planner.settings.max_feedrate_mm_s[Z_AXIS]), + SP_I_STR, LINEAR_UNIT(planner.settings.max_feedrate_mm_s[I_AXIS]), + SP_J_STR, LINEAR_UNIT(planner.settings.max_feedrate_mm_s[J_AXIS]), + SP_K_STR, LINEAR_UNIT(planner.settings.max_feedrate_mm_s[K_AXIS]), + SP_U_STR, LINEAR_UNIT(planner.settings.max_feedrate_mm_s[U_AXIS]), + SP_V_STR, LINEAR_UNIT(planner.settings.max_feedrate_mm_s[V_AXIS]), + SP_W_STR, LINEAR_UNIT(planner.settings.max_feedrate_mm_s[W_AXIS]) + )); #endif #if HAS_EXTRUDERS && DISABLED(DISTINCT_E_FACTORS) + eol = true; SERIAL_ECHOPGM_P(SP_E_STR, VOLUMETRIC_UNIT(planner.settings.max_feedrate_mm_s[E_AXIS])); #endif - #if NUM_AXES || (HAS_EXTRUDERS && DISABLED(DISTINCT_E_FACTORS)) - SERIAL_EOL(); - #endif + if (eol) SERIAL_EOL(); #if ENABLED(DISTINCT_E_FACTORS) for (uint8_t i = 0; i < E_STEPPERS; ++i) { - if (!forReplay) SERIAL_ECHO_START(); + report_echo_start(forReplay); SERIAL_ECHOLNPGM_P( PSTR(" M203 T"), i , SP_E_STR, VOLUMETRIC_UNIT(planner.settings.max_feedrate_mm_s[E_AXIS_N(i)]) @@ -360,7 +373,7 @@ void GcodeSuite::M205_report(const bool forReplay/*=true*/) { , PSTR(" J"), LINEAR_UNIT(planner.junction_deviation_mm) #endif #if ENABLED(CLASSIC_JERK) && NUM_AXES - , LIST_N(DOUBLE(NUM_AXES), + , NUM_AXIS_PAIRED_LIST( SP_X_STR, LINEAR_UNIT(planner.max_jerk.x), SP_Y_STR, LINEAR_UNIT(planner.max_jerk.y), SP_Z_STR, LINEAR_UNIT(planner.max_jerk.z), diff --git a/Marlin/src/gcode/config/M210.cpp b/Marlin/src/gcode/config/M210.cpp new file mode 100644 index 000000000000..0f1ecddec0b2 --- /dev/null +++ b/Marlin/src/gcode/config/M210.cpp @@ -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 . + * + */ + +#include "../../inc/MarlinConfigPre.h" + +#if ENABLED(EDITABLE_HOMING_FEEDRATE) + +#include "../gcode.h" +#include "../../module/motion.h" + +/** + * M210 - Set homing feedrate for one or more axes + * in current units (in/mm) per minute + * + * X[feedrate] Set X axis homing feedrate + * Y[feedrate] Set Y axis homing feedrate + * Z[feedrate] Set Z axis homing feedrate + * A[feedrate] Set I axis homing feedrate (configured axis name applies) + * B[feedrate] Set J axis homing feedrate (configured axis name applies) + * C[feedrate] Set K axis homing feedrate (configured axis name applies) + * U[feedrate] Set U axis homing feedrate (configured axis name applies) + * V[feedrate] Set V axis homing feedrate (configured axis name applies) + * W[feedrate] Set W axis homing feedrate (configured axis name applies) + * + * With no arguments, report the current offsets. + */ +void GcodeSuite::M210() { + if (!parser.seen_any()) + return M210_report(); + + NUM_AXIS_CODE( + if (parser.floatval(AXIS1_PARAM) > 0) motion.homing_feedrate_mm_m.x = parser.value_axis_units(X_AXIS), + if (parser.floatval(AXIS2_PARAM) > 0) motion.homing_feedrate_mm_m.y = parser.value_axis_units(Y_AXIS), + if (parser.floatval(AXIS3_PARAM) > 0) motion.homing_feedrate_mm_m.z = parser.value_axis_units(Z_AXIS), + if (parser.floatval(AXIS4_PARAM) > 0) motion.homing_feedrate_mm_m.i = parser.value_axis_units(I_AXIS), + if (parser.floatval(AXIS5_PARAM) > 0) motion.homing_feedrate_mm_m.j = parser.value_axis_units(J_AXIS), + if (parser.floatval(AXIS6_PARAM) > 0) motion.homing_feedrate_mm_m.k = parser.value_axis_units(K_AXIS), + if (parser.floatval(AXIS7_PARAM) > 0) motion.homing_feedrate_mm_m.u = parser.value_axis_units(U_AXIS), + if (parser.floatval(AXIS8_PARAM) > 0) motion.homing_feedrate_mm_m.v = parser.value_axis_units(V_AXIS), + if (parser.floatval(AXIS9_PARAM) > 0) motion.homing_feedrate_mm_m.w = parser.value_axis_units(W_AXIS) + ); +} + +void GcodeSuite::M210_report(const bool forReplay/*=true*/) { + TERN_(MARLIN_SMALL_BUILD, return); + + report_heading_etc(forReplay, F(STR_HOMING_FEEDRATE)); + + SERIAL_ECHOPGM(" M210"); + SERIAL_ECHOLNPGM_P(NUM_AXIS_PAIRED_LIST( + SP_X_STR, X_AXIS_UNIT(motion.homing_feedrate_mm_m.x), + SP_Y_STR, Y_AXIS_UNIT(motion.homing_feedrate_mm_m.y), + SP_Z_STR, Z_AXIS_UNIT(motion.homing_feedrate_mm_m.z), + SP_I_STR, I_AXIS_UNIT(motion.homing_feedrate_mm_m.i), + SP_J_STR, J_AXIS_UNIT(motion.homing_feedrate_mm_m.j), + SP_K_STR, K_AXIS_UNIT(motion.homing_feedrate_mm_m.k), + SP_U_STR, U_AXIS_UNIT(motion.homing_feedrate_mm_m.u), + SP_V_STR, V_AXIS_UNIT(motion.homing_feedrate_mm_m.v), + SP_W_STR, W_AXIS_UNIT(motion.homing_feedrate_mm_m.w) + )); +} + +#endif // EDITABLE_HOMING_FEEDRATE diff --git a/Marlin/src/gcode/config/M217.cpp b/Marlin/src/gcode/config/M217.cpp index df275c2d3193..d05bb37b5133 100644 --- a/Marlin/src/gcode/config/M217.cpp +++ b/Marlin/src/gcode/config/M217.cpp @@ -31,7 +31,7 @@ #endif #if ENABLED(TOOLCHANGE_MIGRATION_FEATURE) - #include "../../module/motion.h" // for active_extruder + #include "../../module/motion.h" // for motion.extruder #endif /** @@ -133,7 +133,7 @@ void GcodeSuite::M217() { const int16_t lval = parser.value_int(); if (WITHIN(lval, 0, EXTRUDERS - 1)) { migration.last = lval; - migration.automode = (active_extruder < migration.last); + migration.automode = (motion.extruder < migration.last); } } @@ -143,7 +143,7 @@ void GcodeSuite::M217() { if (parser.seen('T')) { // Migrate now if (parser.has_value()) { const int16_t tval = parser.value_int(); - if (WITHIN(tval, 0, EXTRUDERS - 1) && tval != active_extruder) { + if (WITHIN(tval, 0, EXTRUDERS - 1) && tval != motion.extruder) { migration.target = tval + 1; extruder_migration(); migration.target = 0; // disable diff --git a/Marlin/src/gcode/config/M218.cpp b/Marlin/src/gcode/config/M218.cpp index 006f9a1d2c1b..85563dfee0d8 100644 --- a/Marlin/src/gcode/config/M218.cpp +++ b/Marlin/src/gcode/config/M218.cpp @@ -47,32 +47,32 @@ void GcodeSuite::M218() { if (target_extruder < 0) return; #if HAS_X_AXIS - if (parser.seenval('X')) hotend_offset[target_extruder].x = parser.value_linear_units(); + if (parser.seenval('X')) motion.hotend_offset[target_extruder].x = parser.value_linear_units(); #endif #if HAS_Y_AXIS - if (parser.seenval('Y')) hotend_offset[target_extruder].y = parser.value_linear_units(); + if (parser.seenval('Y')) motion.hotend_offset[target_extruder].y = parser.value_linear_units(); #endif #if HAS_Z_AXIS - if (parser.seenval('Z')) hotend_offset[target_extruder].z = parser.value_linear_units(); + if (parser.seenval('Z')) motion.hotend_offset[target_extruder].z = parser.value_linear_units(); #endif #if ENABLED(DELTA) - if (target_extruder == active_extruder) - do_blocking_move_to_xy(current_position, planner.settings.max_feedrate_mm_s[X_AXIS]); + if (target_extruder == motion.extruder) + motion.blocking_move_xy(motion.position, planner.settings.max_feedrate_mm_s[X_AXIS]); #endif } void GcodeSuite::M218_report(const bool forReplay/*=true*/) { TERN_(MARLIN_SMALL_BUILD, return); - report_heading_etc(forReplay, F(STR_HOTEND_OFFSETS)); + report_heading(forReplay, F(STR_HOTEND_OFFSETS)); for (uint8_t e = 1; e < HOTENDS; ++e) { report_echo_start(forReplay); SERIAL_ECHOLNPGM_P( PSTR(" M218 T"), e, - SP_X_STR, LINEAR_UNIT(hotend_offset[e].x), - SP_Y_STR, LINEAR_UNIT(hotend_offset[e].y), - SP_Z_STR, p_float_t(LINEAR_UNIT(hotend_offset[e].z), 3) + SP_X_STR, LINEAR_UNIT(motion.hotend_offset[e].x), + SP_Y_STR, LINEAR_UNIT(motion.hotend_offset[e].y), + SP_Z_STR, p_float_t(LINEAR_UNIT(motion.hotend_offset[e].z), 3) ); } } diff --git a/Marlin/src/gcode/config/M220.cpp b/Marlin/src/gcode/config/M220.cpp index 6797df25d562..6229cc2261b7 100644 --- a/Marlin/src/gcode/config/M220.cpp +++ b/Marlin/src/gcode/config/M220.cpp @@ -24,27 +24,26 @@ #include "../../module/motion.h" /** - * M220: Set speed percentage factor, aka "Feed Rate" + * M220: Set Feedrate Percentage * - * Parameters - * S : Set the feed rate percentage factor + * Parameters: + * None Report the current speed percentage factor + * S Set the feed rate percentage factor * - * Report the current speed percentage factor if no parameter is specified - * - * For MMU2 and MMU2S devices... - * B : Flag to back up the current factor - * R : Flag to restore the last-saved factor + * For MMU2 and MMU2S devices: + * B Back up the current factor + * R Restore the last-saved factor */ void GcodeSuite::M220() { if (!parser.seen_any()) { - SERIAL_ECHOLNPGM("FR:", feedrate_percentage, "%"); + SERIAL_ECHOLNPGM("FR:", motion.feedrate_percentage, "%"); return; } static int16_t backup_feedrate_percentage = 100; - const int16_t now_feedrate_perc = feedrate_percentage; - if (parser.seen_test('R')) feedrate_percentage = backup_feedrate_percentage; + const int16_t now_feedrate_perc = motion.feedrate_percentage; + if (parser.seen_test('R')) motion.feedrate_percentage = backup_feedrate_percentage; if (parser.seen_test('B')) backup_feedrate_percentage = now_feedrate_perc; - if (parser.seenval('S')) feedrate_percentage = parser.value_int(); + if (parser.seenval('S')) motion.feedrate_percentage = parser.value_int(); } diff --git a/Marlin/src/gcode/config/M301.cpp b/Marlin/src/gcode/config/M301.cpp index fe0eef772f38..a47c03a8d4b8 100644 --- a/Marlin/src/gcode/config/M301.cpp +++ b/Marlin/src/gcode/config/M301.cpp @@ -28,22 +28,22 @@ #include "../../module/temperature.h" /** - * M301: Set PID parameters P I D (and optionally C, L) + * M301: Set Hotend PID * - * E[extruder] Default: 0 + * Set PID parameters P I D (and optionally C, L) * - * P[float] Kp term - * I[float] Ki term (unscaled) - * D[float] Kd term (unscaled) + * Parameters: + * E Default: 0 + * P Kp term + * I Ki term (unscaled) + * D Kd term (unscaled) * - * With PID_EXTRUSION_SCALING: + * With PID_EXTRUSION_SCALING: + * C Kc term + * L LPQ length * - * C[float] Kc term - * L[int] LPQ length - * - * With PID_FAN_SCALING: - * - * F[float] Kf term + * With PID_FAN_SCALING: + * F Kf term */ void GcodeSuite::M301() { // multi-extruder PID patch: M301 updates or prints a single extruder's PID values diff --git a/Marlin/src/gcode/config/M302.cpp b/Marlin/src/gcode/config/M302.cpp index 3373e143251c..0648f3edca50 100644 --- a/Marlin/src/gcode/config/M302.cpp +++ b/Marlin/src/gcode/config/M302.cpp @@ -25,12 +25,13 @@ #if ENABLED(PREVENT_COLD_EXTRUSION) #include "../gcode.h" -#include "../../module/temperature.h" -#if ENABLED(DWIN_LCD_PROUI) - #include "../../lcd/e3v2/proui/dwin.h" +#if ENABLED(EXTENSIBLE_UI) + #include "../../lcd/extui/ui_api.h" #endif +#include "../../module/temperature.h" + /** * M302: Allow cold extrudes, or set the minimum extrude temperature * @@ -50,13 +51,14 @@ void GcodeSuite::M302() { const bool seen_S = parser.seen('S'); if (seen_S) { thermalManager.extrude_min_temp = parser.value_celsius(); - thermalManager.allow_cold_extrude = (thermalManager.extrude_min_temp == 0); - TERN_(DWIN_LCD_PROUI, hmiData.extMinT = thermalManager.extrude_min_temp); + TERN_(EXTENSIBLE_UI, ExtUI::onSetMinExtrusionTemp(thermalManager.extrude_min_temp)); } - if (parser.seen('P')) - thermalManager.allow_cold_extrude = (thermalManager.extrude_min_temp == 0) || parser.value_bool(); - else if (!seen_S) { + const bool seen_P = parser.seen('P'); + if (seen_P || seen_S) { + thermalManager.allow_cold_extrude = (thermalManager.extrude_min_temp == 0) || (seen_P && parser.value_bool()); + } + else { // Report current state SERIAL_ECHO_START(); SERIAL_ECHOLN(F("Cold extrudes are "), thermalManager.allow_cold_extrude ? F("en") : F("dis"), F("abled (min temp "), thermalManager.extrude_min_temp, F("C)")); diff --git a/Marlin/src/gcode/config/M43.cpp b/Marlin/src/gcode/config/M43.cpp index 467a31cf44fc..9410fea13668 100644 --- a/Marlin/src/gcode/config/M43.cpp +++ b/Marlin/src/gcode/config/M43.cpp @@ -25,7 +25,6 @@ #if ENABLED(PINS_DEBUGGING) #include "../gcode.h" -#include "../../MarlinCore.h" // for pin_is_protected, wait_for_user #include "../../pins/pinsDebug.h" #include "../../module/endstops.h" @@ -63,34 +62,36 @@ inline void toggle_pins() { for (uint8_t i = start; i <= end; ++i) { pin_t pin = GET_PIN_MAP_PIN_M43(i); - if (!VALID_PIN(pin)) continue; - if (M43_NEVER_TOUCH(i) || (!ignore_protection && pin_is_protected(pin))) { - report_pin_state_extended(pin, ignore_protection, true, F("Untouched ")); + if (!isValidPin(pin)) continue; + if (M43_NEVER_TOUCH(i) || (!ignore_protection && marlin.pin_is_protected(pin))) { + printPinStateExt(pin, ignore_protection, true, F("Untouched ")); SERIAL_EOL(); } else { hal.watchdog_refresh(); - report_pin_state_extended(pin, ignore_protection, true, F("Pulsing ")); - #ifdef __STM32F1__ - const auto prior_mode = _GET_MODE(i); - #else - const bool prior_mode = GET_PINMODE(pin); - #endif + printPinStateExt(pin, ignore_protection, true, F("Pulsing ")); + const auto prior_mode = ( + #ifdef __STM32F1__ + _GET_MODE(i) + #else + getValidPinMode(pin) + #endif + ); #if AVR_AT90USB1286_FAMILY // Teensy IDEs don't know about these pins so must use FASTIO - if (pin == TEENSY_E2) { - SET_OUTPUT(TEENSY_E2); + if (pin == PIN_E2) { + SET_OUTPUT(PIN_E2); for (int16_t j = 0; j < repeat; j++) { - WRITE(TEENSY_E2, LOW); safe_delay(wait); - WRITE(TEENSY_E2, HIGH); safe_delay(wait); - WRITE(TEENSY_E2, LOW); safe_delay(wait); + WRITE(PIN_E2, LOW); safe_delay(wait); + WRITE(PIN_E2, HIGH); safe_delay(wait); + WRITE(PIN_E2, LOW); safe_delay(wait); } } - else if (pin == TEENSY_E3) { - SET_OUTPUT(TEENSY_E3); + else if (pin == PIN_E3) { + SET_OUTPUT(PIN_E3); for (int16_t j = 0; j < repeat; j++) { - WRITE(TEENSY_E3, LOW); safe_delay(wait); - WRITE(TEENSY_E3, HIGH); safe_delay(wait); - WRITE(TEENSY_E3, LOW); safe_delay(wait); + WRITE(PIN_E3, LOW); safe_delay(wait); + WRITE(PIN_E3, HIGH); safe_delay(wait); + WRITE(PIN_E3, LOW); safe_delay(wait); } } else @@ -152,37 +153,36 @@ inline void servo_probe_test() { SET_INPUT_PULLUP(PROBE_TEST_PIN); - // First, check for a probe that recognizes an advanced BLTouch sequence. - // In addition to STOW and DEPLOY, it uses SW MODE (and RESET in the beginning) - // to see if this is one of the following: BLTOUCH Classic 1.2, 1.3, or - // BLTouch Smart 1.0, 2.0, 2.2, 3.0, 3.1. But only if the user has actually - // configured a BLTouch as being present. If the user has not configured this, - // the BLTouch will be detected in the last phase of these tests (see further on). - bool blt = false; - // This code will try to detect a BLTouch probe or clone + /** + * This code will try to detect a BLTouch probe or clone. + * First, check for a probe that recognizes an advanced BLTouch sequence. + * In addition to STOW and DEPLOY, it uses SW MODE (and RESET in the beginning) + * to see if this is one of the following: BLTOUCH Classic 1.2, 1.3, or + * BLTouch Smart 1.0, 2.0, 2.2, 3.0, 3.1. But only if the user has actually + * configured a BLTouch as being present. If the user has not configured this, + * the BLTouch will be detected in the last phase of these tests (see further on). + */ #if ENABLED(BLTOUCH) - SERIAL_ECHOLNPGM(". Check for BLTOUCH"); - bltouch._reset(); - bltouch._stow(); - if (!PROBE_TRIGGERED()) { - bltouch._set_SW_mode(); - if (PROBE_TRIGGERED()) { - bltouch._deploy(); - if (!PROBE_TRIGGERED()) { - bltouch._stow(); - SERIAL_ECHOLNPGM("= BLTouch Classic 1.2, 1.3, Smart 1.0, 2.0, 2.2, 3.0, 3.1 detected."); - // Check for a 3.1 by letting the user trigger it, later - blt = true; - } - } - } + bool blt = false; + do { + SERIAL_ECHOLNPGM(". Check for BLTOUCH"); + bltouch._reset(); + bltouch._stow(); if ( PROBE_TRIGGERED()) break; + bltouch._set_SW_mode(); if (!PROBE_TRIGGERED()) break; + bltouch._deploy(); if ( PROBE_TRIGGERED()) break; + bltouch._stow(); + SERIAL_ECHOLNPGM("= BLTouch Classic 1.2, 1.3, Smart 1.0, 2.0, 2.2, 3.0, 3.1 detected."); + blt = true; // Check for a 3.1 by letting the user trigger it, later + } while(0); + #else + static constexpr bool blt = false; #endif // The following code is common to all kinds of servo probes. // Since it could be a real servo or a BLTouch (any kind) or a clone, // use only "common" functions - i.e. SERVO_MOVE. No bltouch.xxxx stuff. - // If it is already recognised as a being a BLTouch, no need for this test + // If it is already recognized as a being a BLTouch, no need for this test if (!blt) { // DEPLOY and STOW 4 times and see if the signal follows // Then it is a mechanical switch @@ -326,14 +326,14 @@ void GcodeSuite::M43() { bool can_watch = false; for (uint8_t i = first_pin; i <= last_pin; ++i) { pin_t pin = GET_PIN_MAP_PIN_M43(i); - if (!VALID_PIN(pin)) continue; - if (M43_NEVER_TOUCH(i) || (!ignore_protection && pin_is_protected(pin))) continue; + if (!isValidPin(pin)) continue; + if (M43_NEVER_TOUCH(i) || (!ignore_protection && marlin.pin_is_protected(pin))) continue; can_watch = true; pinMode(pin, INPUT_PULLUP); delay(1); /* - if (IS_ANALOG(pin)) - pin_state[pin - first_pin] = analogRead(DIGITAL_PIN_TO_ANALOG_PIN(pin)); // int16_t pin_state[...] + if (isAnalogPin(pin)) + pin_state[pin - first_pin] = analogRead(digitalPinToAnalogIndex(pin)); // int16_t pin_state[...] else //*/ pin_state[i - first_pin] = extDigitalRead(pin); @@ -357,7 +357,7 @@ void GcodeSuite::M43() { #if HAS_RESUME_CONTINUE KEEPALIVE_STATE(PAUSED_FOR_USER); - wait_for_user = true; + marlin.wait_start(); TERN_(HOST_PROMPT_SUPPORT, hostui.continue_prompt(F("M43 Waiting..."))); #if ENABLED(EXTENSIBLE_UI) ExtUI::onUserConfirmRequired(F("M43 Waiting...")); @@ -369,24 +369,24 @@ void GcodeSuite::M43() { for (;;) { for (uint8_t i = first_pin; i <= last_pin; ++i) { const pin_t pin = GET_PIN_MAP_PIN_M43(i); - if (!VALID_PIN(pin)) continue; - if (M43_NEVER_TOUCH(i) || (!ignore_protection && pin_is_protected(pin))) continue; + if (!isValidPin(pin)) continue; + if (M43_NEVER_TOUCH(i) || (!ignore_protection && marlin.pin_is_protected(pin))) continue; const byte val = /* - IS_ANALOG(pin) - ? analogRead(DIGITAL_PIN_TO_ANALOG_PIN(pin)) : // int16_t val + isAnalogPin(pin) + ? analogRead(digitalPinToAnalogIndex(pin)) : // int16_t val : //*/ extDigitalRead(pin); if (val != pin_state[i - first_pin]) { - report_pin_state_extended(pin, ignore_protection, true); + printPinStateExt(pin, ignore_protection, true); pin_state[i - first_pin] = val; } } #if HAS_RESUME_CONTINUE ui.update(); - if (!wait_for_user) break; + if (!marlin.wait_for_user) break; #endif safe_delay(200); @@ -398,7 +398,7 @@ void GcodeSuite::M43() { // Report current state of selected pin(s) for (uint8_t i = first_pin; i <= last_pin; ++i) { const pin_t pin = GET_PIN_MAP_PIN_M43(i); - if (VALID_PIN(pin)) report_pin_state_extended(pin, ignore_protection, true); + if (isValidPin(pin)) printPinStateExt(pin, ignore_protection, true); } } } diff --git a/Marlin/src/gcode/config/M550.cpp b/Marlin/src/gcode/config/M550.cpp new file mode 100644 index 000000000000..ed7dc4915993 --- /dev/null +++ b/Marlin/src/gcode/config/M550.cpp @@ -0,0 +1,58 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2025 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 . + * + */ + +#include "../../inc/MarlinConfig.h" + +#if ENABLED(CONFIGURABLE_MACHINE_NAME) + +#include "../gcode.h" +#include "../../lcd/marlinui.h" + +/** + * M550: Set machine name + * + * Parameters: + * P "" Set the name using the 'P' parameter (RepRapFirmware) + * "" Set the name using the "string" parameter + */ +void GcodeSuite::M550() { + bool did_set = true; + + if (parser.seenval('P')) + marlin.machine_name = parser.value_string(); + else if (TERN(GCODE_QUOTED_STRINGS, false, parser.seen('P'))) + marlin.machine_name = parser.string_arg[0] == 'P' ? &parser.string_arg[1] : parser.string_arg; + else if (parser.has_string()) + marlin.machine_name = parser.string_arg; + else + did_set = false; + + if (did_set) { + marlin.machine_name.trim(); + ui.reset_status(false); + } + else + SERIAL_ECHOLNPGM("RepRap name: ", &marlin.machine_name); + +} + +#endif // CONFIGURABLE_MACHINE_NAME diff --git a/Marlin/src/gcode/config/M575.cpp b/Marlin/src/gcode/config/M575.cpp index 2c12428d982a..dd8f8addb5c5 100644 --- a/Marlin/src/gcode/config/M575.cpp +++ b/Marlin/src/gcode/config/M575.cpp @@ -51,25 +51,25 @@ void GcodeSuite::M575() { switch (baud) { case 2400: case 9600: case 19200: case 38400: case 57600: case 115200: case 250000: case 500000: case 1000000: { + SERIAL_FLUSH(); + const int8_t port = parser.intval('P', -99); const bool set1 = (port == -99 || port == 0); - if (set1) SERIAL_ECHO_MSG(" Serial ", AS_DIGIT(0), " baud rate set to ", baud); + if (set1) { MYSERIAL1.end(); MYSERIAL1.begin(baud); } #if HAS_MULTI_SERIAL const bool set2 = (port == -99 || port == 1); - if (set2) SERIAL_ECHO_MSG(" Serial ", AS_DIGIT(1), " baud rate set to ", baud); + if (set2) { MYSERIAL2.end(); MYSERIAL2.begin(baud); } #ifdef SERIAL_PORT_3 const bool set3 = (port == -99 || port == 2); - if (set3) SERIAL_ECHO_MSG(" Serial ", AS_DIGIT(2), " baud rate set to ", baud); + if (set3) { MYSERIAL3.end(); MYSERIAL3.begin(baud); } #endif #endif - SERIAL_FLUSH(); - - if (set1) { MYSERIAL1.end(); MYSERIAL1.begin(baud); } + if (set1) SERIAL_ECHO_MSG(" Serial ", AS_DIGIT(0), " baud rate set to ", baud); #if HAS_MULTI_SERIAL - if (set2) { MYSERIAL2.end(); MYSERIAL2.begin(baud); } + if (set2) SERIAL_ECHO_MSG(" Serial ", AS_DIGIT(1), " baud rate set to ", baud); #ifdef SERIAL_PORT_3 - if (set3) { MYSERIAL3.end(); MYSERIAL3.begin(baud); } + if (set3) SERIAL_ECHO_MSG(" Serial ", AS_DIGIT(2), " baud rate set to ", baud); #endif #endif diff --git a/Marlin/src/gcode/config/M92.cpp b/Marlin/src/gcode/config/M92.cpp index 2a5eb30f5516..a04307458536 100644 --- a/Marlin/src/gcode/config/M92.cpp +++ b/Marlin/src/gcode/config/M92.cpp @@ -57,7 +57,7 @@ void GcodeSuite::M92() { planner.settings.axis_steps_per_mm[i] = parser.value_per_axis_units((AxisEnum)i); else { #if HAS_EXTRUDERS - const float value = parser.value_per_axis_units((AxisEnum)(E_AXIS_N(target_extruder))); + const float value = parser.value_per_axis_units(AxisEnum(E_AXIS_N(target_extruder))); if (value < 20) { float factor = planner.settings.axis_steps_per_mm[E_AXIS_N(target_extruder)] / value; // increase e constants if M92 E14 is given for netfab. #if ALL(CLASSIC_JERK, HAS_CLASSIC_E_JERK) @@ -101,7 +101,7 @@ void GcodeSuite::M92_report(const bool forReplay/*=true*/, const int8_t e/*=-1*/ report_heading_etc(forReplay, F(STR_STEPS_PER_UNIT)); #if NUM_AXES #define PRINT_EOL - SERIAL_ECHOPGM_P(LIST_N(DOUBLE(NUM_AXES), + SERIAL_ECHOPGM_P(NUM_AXIS_PAIRED_LIST( PSTR(" M92 X"), LINEAR_UNIT(planner.settings.axis_steps_per_mm[X_AXIS]), SP_Y_STR, LINEAR_UNIT(planner.settings.axis_steps_per_mm[Y_AXIS]), SP_Z_STR, LINEAR_UNIT(planner.settings.axis_steps_per_mm[Z_AXIS]), diff --git a/Marlin/src/gcode/control/M108_M112_M410.cpp b/Marlin/src/gcode/control/M108_M112_M410.cpp index 39f9c04e1915..dc7213b98ea7 100644 --- a/Marlin/src/gcode/control/M108_M112_M410.cpp +++ b/Marlin/src/gcode/control/M108_M112_M410.cpp @@ -21,26 +21,21 @@ */ #include "../../inc/MarlinConfig.h" - -#if DISABLED(EMERGENCY_PARSER) - #include "../gcode.h" -#include "../../MarlinCore.h" // for wait_for_heatup, kill, M112_KILL_STR #include "../../module/motion.h" // for quickstop_stepper /** * M108: Stop the waiting for heaters in M109, M190, M303. Does not affect the target temperature. */ void GcodeSuite::M108() { - TERN_(HAS_RESUME_CONTINUE, wait_for_user = false); - wait_for_heatup = false; + marlin.end_waiting(); } /** * M112: Full Shutdown */ void GcodeSuite::M112() { - kill(FPSTR(M112_KILL_STR), nullptr, true); + marlin.kill(FPSTR(M112_KILL_STR), nullptr, true); } /** @@ -50,7 +45,5 @@ void GcodeSuite::M112() { * will be out of sync with the stepper position after this. */ void GcodeSuite::M410() { - quickstop_stepper(); + motion.quickstop_stepper(); } - -#endif // !EMERGENCY_PARSER diff --git a/Marlin/src/gcode/control/M10-M11.cpp b/Marlin/src/gcode/control/M10_M11.cpp similarity index 95% rename from Marlin/src/gcode/control/M10-M11.cpp rename to Marlin/src/gcode/control/M10_M11.cpp index d5a69dcfccfb..8fd299c5465b 100644 --- a/Marlin/src/gcode/control/M10-M11.cpp +++ b/Marlin/src/gcode/control/M10_M11.cpp @@ -20,6 +20,11 @@ * */ +/** + * gcode/control/M10_M11.cpp + * Air Evacuation + */ + #include "../../inc/MarlinConfig.h" #if ENABLED(AIR_EVACUATION) diff --git a/Marlin/src/gcode/control/M17_M18_M84.cpp b/Marlin/src/gcode/control/M17_M18_M84.cpp index 1742d288b3f5..90563889f372 100644 --- a/Marlin/src/gcode/control/M17_M18_M84.cpp +++ b/Marlin/src/gcode/control/M17_M18_M84.cpp @@ -78,7 +78,7 @@ void do_enable(const stepper_flags_t to_enable) { // Enable all flagged axes LOOP_NUM_AXES(a) { if (TEST(shall_enable, a)) { - stepper.enable_axis(AxisEnum(a)); // Mark and enable the requested axis + stepper.enable_axis((AxisEnum)a); // Mark and enable the requested axis DEBUG_ECHOLNPGM("Enabled ", AXIS_CHAR(a), " (", a, ") with overlap ", hex_word(enable_overlap[a]), " ... Enabled: ", hex_word(stepper.axis_enabled.bits)); also_enabled |= enable_overlap[a]; } @@ -153,7 +153,7 @@ void try_to_disable(const stepper_flags_t to_disable) { LOOP_NUM_AXES(a) if (TEST(to_disable.bits, a)) { DEBUG_ECHOPGM("Try to disable ", AXIS_CHAR(a), " (", a, ") with overlap ", hex_word(enable_overlap[a]), " ... "); - if (stepper.disable_axis(AxisEnum(a))) { // Mark the requested axis and request to disable + if (stepper.disable_axis((AxisEnum)a)) { // Mark the requested axis and request to disable DEBUG_ECHOPGM("OK"); still_enabled &= ~(_BV(a) | enable_overlap[a]); // If actually disabled, clear one or more tracked bits } diff --git a/Marlin/src/gcode/control/M211.cpp b/Marlin/src/gcode/control/M211.cpp index 471ca6c448de..07d849c0f217 100644 --- a/Marlin/src/gcode/control/M211.cpp +++ b/Marlin/src/gcode/control/M211.cpp @@ -34,7 +34,7 @@ */ void GcodeSuite::M211() { if (parser.seen('S')) - soft_endstop._enabled = parser.value_bool(); + motion.soft_endstop._enabled = parser.value_bool(); else M211_report(); } @@ -43,12 +43,11 @@ void GcodeSuite::M211_report(const bool forReplay/*=true*/) { TERN_(MARLIN_SMALL_BUILD, return); report_heading_etc(forReplay, F(STR_SOFT_ENDSTOPS)); - SERIAL_ECHOPGM(" M211 S", AS_DIGIT(soft_endstop._enabled), " ; "); - serialprintln_onoff(soft_endstop._enabled); + SERIAL_ECHOLNPGM(" M211 S", AS_DIGIT(motion.soft_endstop._enabled), " ; ", ON_OFF(motion.soft_endstop._enabled)); report_echo_start(forReplay); - const xyz_pos_t l_soft_min = soft_endstop.min.asLogical(), - l_soft_max = soft_endstop.max.asLogical(); + const xyz_pos_t l_soft_min = motion.soft_endstop.min.asLogical(), + l_soft_max = motion.soft_endstop.max.asLogical(); print_xyz(l_soft_min, F(STR_SOFT_MIN), F(" ")); print_xyz(l_soft_max, F(STR_SOFT_MAX)); } diff --git a/Marlin/src/gcode/control/M226.cpp b/Marlin/src/gcode/control/M226.cpp index 4eb3db4bc31f..921ce0d4c272 100644 --- a/Marlin/src/gcode/control/M226.cpp +++ b/Marlin/src/gcode/control/M226.cpp @@ -25,7 +25,6 @@ #if ENABLED(DIRECT_PIN_CONTROL) #include "../gcode.h" -#include "../../MarlinCore.h" // for pin_is_protected and idle() #include "../../module/planner.h" void protected_pin_err(); @@ -40,7 +39,7 @@ void GcodeSuite::M226() { const pin_t pin = GET_PIN_MAP_PIN(pin_number); if (WITHIN(pin_state, -1, 1) && pin > -1) { - if (pin_is_protected(pin)) + if (marlin.pin_is_protected(pin)) protected_pin_err(); else { int target = LOW; @@ -51,7 +50,7 @@ void GcodeSuite::M226() { case 0: target = LOW; break; case -1: target = !extDigitalRead(pin); break; } - while (int(extDigitalRead(pin)) != target) idle(); + while (int(extDigitalRead(pin)) != target) marlin.idle(); } } // pin_state -1 0 1 && pin > -1 } // parser.seen('P') diff --git a/Marlin/src/gcode/control/M3-M5.cpp b/Marlin/src/gcode/control/M3-M5.cpp index ec24cf8a6585..9b6a2fb2f7e3 100644 --- a/Marlin/src/gcode/control/M3-M5.cpp +++ b/Marlin/src/gcode/control/M3-M5.cpp @@ -47,6 +47,7 @@ * * Parameters: * S - Set power. S0 will turn the spindle/laser off. + * O - Set power in PWM units 0-255 * * If no PWM pin is defined then M3/M4 just turns it on or off. * @@ -91,7 +92,11 @@ void GcodeSuite::M3_M4(const bool is_M4) { auto get_s_power = [] { if (parser.seenval('S')) { const float v = parser.value_float(); - cutter.menuPower = cutter.unitPower = TERN(LASER_POWER_TRAP, v, cutter.power_to_range(v)); + cutter.menuPower = cutter.unitPower = TERN(LASER_POWER_TRAP, constrain( v, 0, CUTTER_POWER_MAX), cutter.power_to_range(v)); + } + else if (parser.seenval('O')) { // pwr in PWM units + const float v = parser.value_float(); + cutter.menuPower = cutter.unitPower = CUTTER_PWM_TO_SPWR(constrain(v, 0, 255)); } else if (cutter.cutter_mode == CUTTER_MODE_STANDARD) cutter.menuPower = cutter.unitPower = cutter.cpwr_to_upwr(SPEED_POWER_STARTUP); diff --git a/Marlin/src/gcode/control/M350_M351.cpp b/Marlin/src/gcode/control/M350_M351.cpp index 425abad5291d..76753b2ea9b9 100644 --- a/Marlin/src/gcode/control/M350_M351.cpp +++ b/Marlin/src/gcode/control/M350_M351.cpp @@ -27,7 +27,7 @@ #include "../gcode.h" #include "../../module/stepper.h" -#if NUM_AXES == XYZ && EXTRUDERS >= 1 +#if NUM_AXES == 3 && EXTRUDERS >= 1 #define HAS_M350_B_PARAM 1 // "5th axis" (after E0) for an original XYZEB setup. #endif diff --git a/Marlin/src/gcode/control/M380_M381.cpp b/Marlin/src/gcode/control/M380_M381.cpp index 20d24484ed0f..9b8de3eccf6e 100644 --- a/Marlin/src/gcode/control/M380_M381.cpp +++ b/Marlin/src/gcode/control/M380_M381.cpp @@ -35,9 +35,9 @@ */ void GcodeSuite::M380() { #if ENABLED(MANUAL_SOLENOID_CONTROL) - enable_solenoid(parser.intval('S', active_extruder)); + enable_solenoid(parser.intval('S', motion.extruder)); #else - enable_solenoid(active_extruder); + enable_solenoid(motion.extruder); #endif } @@ -47,7 +47,7 @@ void GcodeSuite::M380() { */ void GcodeSuite::M381() { #if ENABLED(MANUAL_SOLENOID_CONTROL) - disable_solenoid(parser.intval('S', active_extruder)); + disable_solenoid(parser.intval('S', motion.extruder)); #else disable_all_solenoids(); #endif diff --git a/Marlin/src/gcode/control/M42.cpp b/Marlin/src/gcode/control/M42.cpp index b995f208f57a..7979641e0048 100644 --- a/Marlin/src/gcode/control/M42.cpp +++ b/Marlin/src/gcode/control/M42.cpp @@ -37,8 +37,6 @@ #define OUTPUT_OPEN_DRAIN OUTPUT_OPEN_DRAIN #endif -bool pin_is_protected(const pin_t pin); - void protected_pin_err() { SERIAL_ERROR_MSG(STR_ERR_PROTECTED_PIN); } @@ -46,15 +44,16 @@ void protected_pin_err() { /** * M42: Change pin status via G-Code * - * P Pin number (LED if omitted) - * For LPC1768 specify pin P1_02 as M42 P102, - * P1_20 as M42 P120, etc. + * Parameters: + * P Pin number (LED if omitted) + * For LPC1768 specify pin P1_02 as M42 P102, + * P1_20 as M42 P120, etc. * - * S Pin status from 0 - 255 - * I Flag to ignore Marlin's pin protection + * S Pin status from 0-255 + * I Flag to ignore Marlin's pin protection * - * T Pin mode: 0=INPUT 1=OUTPUT 2=INPUT_PULLUP 3=INPUT_PULLDOWN - * 4=INPUT_ANALOG 5=OUTPUT_OPEN_DRAIN + * T Pin mode: 0=INPUT | 1=OUTPUT | 2=INPUT_PULLUP | 3=INPUT_PULLDOWN + * 4=INPUT_ANALOG | 5=OUTPUT_OPEN_DRAIN */ void GcodeSuite::M42() { const int pin_index = PARSED_PIN_INDEX('P', GET_PIN_MAP_INDEX(LED_PIN)); @@ -62,7 +61,7 @@ void GcodeSuite::M42() { const pin_t pin = GET_PIN_MAP_PIN(pin_index); - if (!parser.boolval('I') && pin_is_protected(pin)) return protected_pin_err(); + if (!parser.boolval('I') && marlin.pin_is_protected(pin)) return protected_pin_err(); bool avoidWrite = false; if (parser.seenval('T')) { @@ -88,7 +87,7 @@ void GcodeSuite::M42() { #if HAS_FAN switch (pin) { - #define _CASE(N) case FAN##N##_PIN: thermalManager.fan_speed[N] = pin_status; return; + #define _CASE(N) case PART_COOLING_FAN##N##_PIN: thermalManager.fan_speed[N] = pin_status; return; REPEAT(FAN_COUNT, _CASE) } #endif diff --git a/Marlin/src/gcode/control/M605.cpp b/Marlin/src/gcode/control/M605.cpp index 167cdae4a931..8a4a7712b0d7 100644 --- a/Marlin/src/gcode/control/M605.cpp +++ b/Marlin/src/gcode/control/M605.cpp @@ -64,12 +64,12 @@ planner.synchronize(); if (parser.seenval('S')) { - const DualXMode previous_mode = dual_x_carriage_mode; + const DualXMode previous_mode = motion.idex_mode; - dual_x_carriage_mode = (DualXMode)parser.value_byte(); - idex_set_mirrored_mode(false); + motion.idex_mode = (DualXMode)parser.value_byte(); + motion.idex_set_mirrored_mode(false); - switch (dual_x_carriage_mode) { + switch (motion.idex_mode) { case DXC_FULL_CONTROL_MODE: case DXC_AUTO_PARK_MODE: @@ -77,74 +77,74 @@ case DXC_DUPLICATION_MODE: // Set the X offset, but no less than the safety gap - if (parser.seenval('X')) duplicate_extruder_x_offset = _MAX(parser.value_linear_units(), (X2_MIN_POS) - (X1_MIN_POS)); - if (parser.seenval('R')) duplicate_extruder_temp_offset = parser.value_celsius_diff(); + if (parser.seenval('X')) motion.duplicate_extruder_x_offset = _MAX(parser.value_linear_units(), (X2_MIN_POS) - (X1_MIN_POS)); + if (parser.seenval('R')) motion.duplicate_extruder_temp_offset = parser.value_celsius_diff(); // Always switch back to tool 0 - if (active_extruder != 0) tool_change(0); + if (motion.extruder != 0) tool_change(0); break; case DXC_MIRRORED_MODE: { if (previous_mode != DXC_DUPLICATION_MODE) { SERIAL_ECHOLNPGM("Printer must be in DXC_DUPLICATION_MODE prior to "); SERIAL_ECHOLNPGM("specifying DXC_MIRRORED_MODE."); - dual_x_carriage_mode = DEFAULT_DUAL_X_CARRIAGE_MODE; + motion.idex_mode = DEFAULT_DUAL_X_CARRIAGE_MODE; return; } - idex_set_mirrored_mode(true); + motion.idex_set_mirrored_mode(true); // Do a small 'jog' motion in the X axis - xyze_pos_t dest = current_position; dest.x -= 0.1f; + xyze_pos_t dest = motion.position; dest.x -= 0.1f; for (uint8_t i = 2; --i;) { - planner.buffer_line(dest, feedrate_mm_s, 0); + planner.buffer_line(dest, motion.feedrate_mm_s, 0); dest.x += 0.1f; } } return; default: - dual_x_carriage_mode = DEFAULT_DUAL_X_CARRIAGE_MODE; + motion.idex_mode = DEFAULT_DUAL_X_CARRIAGE_MODE; break; } - idex_set_parked(false); - set_duplication_enabled(false); + motion.idex_set_parked(false); + motion.set_extruder_duplication(false); #ifdef EVENT_GCODE_IDEX_AFTER_MODECHANGE process_subcommands_now(F(EVENT_GCODE_IDEX_AFTER_MODECHANGE)); #endif } else if (!parser.seen('W')) // if no S or W parameter, the DXC mode gets reset to the user's default - dual_x_carriage_mode = DEFAULT_DUAL_X_CARRIAGE_MODE; + motion.idex_mode = DEFAULT_DUAL_X_CARRIAGE_MODE; - #ifdef DEBUG_DXC_MODE + #if ENABLED(DEBUG_DXC_MODE) if (parser.seen('W')) { DEBUG_ECHO_START(); DEBUG_ECHOPGM("Dual X Carriage Mode "); - switch (dual_x_carriage_mode) { + switch (motion.idex_mode) { case DXC_FULL_CONTROL_MODE: DEBUG_ECHOPGM("FULL_CONTROL"); break; case DXC_AUTO_PARK_MODE: DEBUG_ECHOPGM("AUTO_PARK"); break; case DXC_DUPLICATION_MODE: DEBUG_ECHOPGM("DUPLICATION"); break; case DXC_MIRRORED_MODE: DEBUG_ECHOPGM("MIRRORED"); break; } - DEBUG_ECHOPGM("\nActive Ext: ", active_extruder); - if (!active_extruder_parked) DEBUG_ECHOPGM(" NOT ", F(" parked.")); + DEBUG_ECHOPGM("\nActive Ext: ", motion.extruder); + if (!motion.active_extruder_parked) DEBUG_ECHOPGM(" NOT ", F(" parked.")); DEBUG_ECHOLNPGM( - "\nactive_extruder_x_pos: ", current_position.x, - "\ninactive_extruder_x: ", inactive_extruder_x, - "\nextruder_duplication_enabled: ", extruder_duplication_enabled, - "\nduplicate_extruder_x_offset: ", duplicate_extruder_x_offset, - "\nduplicate_extruder_temp_offset: ", duplicate_extruder_temp_offset, - "\ndelayed_move_time: ", delayed_move_time, - "\nX1 Home: ", x_home_pos(0), " X1_MIN_POS=", X1_MIN_POS, " X1_MAX_POS=", X1_MAX_POS, - "\nX2 Home: ", x_home_pos(1), " X2_MIN_POS=", X2_MIN_POS, " X2_MAX_POS=", X2_MAX_POS, + "\nmotion.position.x: ", motion.position.x, + "\nmotion.inactive_extruder_x: ", motion.inactive_extruder_x, + "\nmotion.extruder_duplication: ", motion.extruder_duplication, + "\nmotion.duplicate_extruder_x_offset: ", motion.duplicate_extruder_x_offset, + "\nmotion.duplicate_extruder_temp_offset: ", motion.duplicate_extruder_temp_offset, + "\nmotion.delayed_move_time: ", motion.delayed_move_time, + "\nX1 Home: ", motion.x_home_pos(0), " X1_MIN_POS=", X1_MIN_POS, " X1_MAX_POS=", X1_MAX_POS, + "\nX2 Home: ", motion.x_home_pos(1), " X2_MIN_POS=", X2_MIN_POS, " X2_MAX_POS=", X2_MAX_POS, "\nDEFAULT_DUAL_X_CARRIAGE_MODE=", STRINGIFY(DEFAULT_DUAL_X_CARRIAGE_MODE), - "\toolchange_settings.z_raise=", toolchange_settings.z_raise, + "\ntoolchange_settings.z_raise=", toolchange_settings.z_raise, "\nDEFAULT_DUPLICATION_X_OFFSET=", DEFAULT_DUPLICATION_X_OFFSET ); HOTEND_LOOP() { DEBUG_ECHOPGM_P(SP_T_STR, e); - LOOP_NUM_AXES(a) DEBUG_ECHOPGM(" hotend_offset[", e, "].", C(AXIS_CHAR(a) | 0x20), "=", hotend_offset[e][a]); + LOOP_NUM_AXES(a) DEBUG_ECHOPGM(" hotend_offset[", e, "].", C(AXIS_CHAR(a) | 0x20), "=", motion.hotend_offset[e][a]); DEBUG_EOL(); } DEBUG_EOL(); @@ -155,29 +155,31 @@ #elif ENABLED(MULTI_NOZZLE_DUPLICATION) /** - * M605: Set multi-nozzle duplication mode + * M605: Multi Nozzle Mode * - * S2 - Enable duplication mode - * P[mask] - Bit-mask of nozzles to include in the duplication set. - * A value of 0 disables duplication. - * E[index] - Last nozzle index to include in the duplication set. - * A value of 0 disables duplication. + * Set multi-nozzle duplication mode. + * + * Parameters: + * S2 Enable duplication mode + * P Bit-mask of nozzles to include in the duplication set + * A value of 0 disables duplication + * E Last nozzle index to include in the duplication set + * A value of 0 disables duplication */ void GcodeSuite::M605() { bool ena = false; if (parser.seen("EPS")) { planner.synchronize(); - if (parser.seenval('P')) duplication_e_mask = parser.value_int(); // Set the mask directly - else if (parser.seenval('E')) duplication_e_mask = _BV(parser.value_int() + 1) - 1; // Set the mask by E index - ena = (2 == parser.intval('S', extruder_duplication_enabled ? 2 : 0)); - set_duplication_enabled(ena && (duplication_e_mask >= 3)); + if (parser.seenval('P')) motion.duplication_e_mask = parser.value_int(); // Set the mask directly + else if (parser.seenval('E')) motion.duplication_e_mask = _BV(parser.value_int() + 1) - 1; // Set the mask by E index + ena = (2 == parser.intval('S', motion.extruder_duplication ? 2 : 0)); + motion.set_extruder_duplication(ena && (motion.duplication_e_mask >= 3)); } SERIAL_ECHO_START(); - SERIAL_ECHOPGM(STR_DUPLICATION_MODE); - serialprint_onoff(extruder_duplication_enabled); + SERIAL_ECHOPGM(STR_DUPLICATION_MODE, ON_OFF(motion.extruder_duplication)); if (ena) { SERIAL_ECHOPGM(" ( "); - HOTEND_LOOP() if (TEST(duplication_e_mask, e)) { SERIAL_ECHO(e); SERIAL_CHAR(' '); } + HOTEND_LOOP() if (TEST(motion.duplication_e_mask, e)) { SERIAL_ECHO(e); SERIAL_CHAR(' '); } SERIAL_CHAR(')'); } SERIAL_EOL(); diff --git a/Marlin/src/gcode/control/M80_M81.cpp b/Marlin/src/gcode/control/M80_M81.cpp index 2d3e407446a0..a7653a4037d0 100644 --- a/Marlin/src/gcode/control/M80_M81.cpp +++ b/Marlin/src/gcode/control/M80_M81.cpp @@ -34,7 +34,11 @@ #include "../../feature/power.h" #endif -#if HAS_SUICIDE +#if ENABLED(POWER_LOSS_RECOVERY) + #include "../../feature/powerloss.h" +#endif + +#if ANY(HAS_SUICIDE, CONFIGURABLE_MACHINE_NAME) #include "../../MarlinCore.h" #endif @@ -84,9 +88,15 @@ void GcodeSuite::M81() { ZERO(thermalManager.saved_fan_speed); #endif + TERN_(POWER_LOSS_RECOVERY, recovery.purge()); // Clear PLR on intentional shutdown + safe_delay(1000); // Wait 1 second before switching off - LCD_MESSAGE_F(MACHINE_NAME " " STR_OFF "."); + #if ENABLED(CONFIGURABLE_MACHINE_NAME) + ui.set_status(&MString<30>(&marlin.machine_name, ' ', F(STR_OFF), '.')); + #else + LCD_MESSAGE_F(MACHINE_NAME " " STR_OFF "."); + #endif bool delayed_power_off = false; @@ -112,9 +122,9 @@ void GcodeSuite::M81() { return; } - #if HAS_SUICIDE - suicide(); - #elif ENABLED(PSU_CONTROL) + #if ENABLED(PSU_CONTROL) powerManager.power_off_soon(); + #elif HAS_SUICIDE + marlin.suicide(); #endif } diff --git a/Marlin/src/gcode/control/M993_M994.cpp b/Marlin/src/gcode/control/M993_M994.cpp index bc634ae13c0d..392c37234ed5 100644 --- a/Marlin/src/gcode/control/M993_M994.cpp +++ b/Marlin/src/gcode/control/M993_M994.cpp @@ -49,7 +49,8 @@ void GcodeSuite::M993() { W25QXX.SPI_FLASH_BufferRead(buf, addr, COUNT(buf)); addr += COUNT(buf); card.write(buf, COUNT(buf)); - if (addr % (COUNT(buf) * 10) == 0) SERIAL_CHAR('.'); + if (!(addr % (COUNT(buf) * 10))) SERIAL_CHAR('.'); + if (!(addr % (COUNT(buf) * 32))) hal.watchdog_refresh(); } SERIAL_ECHOLNPGM(" done"); @@ -78,7 +79,8 @@ void GcodeSuite::M994() { card.read(buf, COUNT(buf)); W25QXX.SPI_FLASH_BufferWrite(buf, addr, COUNT(buf)); addr += COUNT(buf); - if (addr % (COUNT(buf) * 10) == 0) SERIAL_CHAR('.'); + if (!(addr % (COUNT(buf) * 10))) SERIAL_CHAR('.'); + if (!(addr % (COUNT(buf) * 32))) hal.watchdog_refresh(); } SERIAL_ECHOLNPGM(" done"); diff --git a/Marlin/src/gcode/control/M997.cpp b/Marlin/src/gcode/control/M997.cpp index 5ac0ba23bbca..c651961902f4 100644 --- a/Marlin/src/gcode/control/M997.cpp +++ b/Marlin/src/gcode/control/M997.cpp @@ -24,8 +24,8 @@ #if ENABLED(PLATFORM_M997_SUPPORT) -#if ENABLED(DWIN_LCD_PROUI) - #include "../../lcd/e3v2/proui/dwin.h" +#if ENABLED(EXTENSIBLE_UI) + #include "../../lcd/extui/ui_api.h" #endif /** @@ -33,7 +33,7 @@ */ void GcodeSuite::M997() { - TERN_(DWIN_LCD_PROUI, dwinRebootScreen()); + TERN_(EXTENSIBLE_UI, ExtUI::onFirmwareFlash()); flashFirmware(parser.intval('S')); diff --git a/Marlin/src/gcode/control/M999.cpp b/Marlin/src/gcode/control/M999.cpp index b7d6db9f2312..863ab8eb9035 100644 --- a/Marlin/src/gcode/control/M999.cpp +++ b/Marlin/src/gcode/control/M999.cpp @@ -23,7 +23,7 @@ #include "../gcode.h" #include "../../lcd/marlinui.h" // for ui.reset_alert_level -#include "../../MarlinCore.h" // for marlin_state +#include "../../MarlinCore.h" // for setState #include "../queue.h" // for flush_and_request_resend /** @@ -36,7 +36,7 @@ * existing command buffer. */ void GcodeSuite::M999() { - marlin_state = MF_RUNNING; + marlin.setState(MF_RUNNING); ui.reset_alert_level(); if (parser.boolval('S')) return; diff --git a/Marlin/src/gcode/control/T.cpp b/Marlin/src/gcode/control/T.cpp index 3c13fe231a79..75f7adea2d5c 100644 --- a/Marlin/src/gcode/control/T.cpp +++ b/Marlin/src/gcode/control/T.cpp @@ -31,7 +31,9 @@ #include "../../module/motion.h" #endif -#if HAS_PRUSA_MMU2 +#if HAS_PRUSA_MMU3 + #include "../../feature/mmu3/mmu3.h" +#elif HAS_PRUSA_MMU2 #include "../../feature/mmu/mmu2.h" #endif @@ -41,21 +43,22 @@ /** * T0-T: Switch tool, usually switching extruders * - * F[units/min] Set the movement feedrate - * S1 Don't move the tool in XY after change + * Parameters: + * F Set the movement feedrate + * S1 Don't move the tool in XY after change * - * For PRUSA_MMU2(S) and EXTENDABLE_EMU_MMU2(S) - * T[n] G-code to extrude at least 38.10 mm at feedrate 19.02 mm/s must follow immediately to load to extruder wheels. - * T? G-code to extrude shouldn't have to follow. Load to extruder wheels is done automatically. - * Tx Same as T?, but nozzle doesn't have to be preheated. Tc requires a preheated nozzle to finish filament load. - * Tc Load to nozzle after filament was prepared by Tc and nozzle is already heated. + * For PRUSA_MMU2(S) and EXTENDABLE_EMU_MMU2(S) + * T G-code to extrude at least 38.10 mm at feedrate 19.02 mm/s must follow immediately to load to extruder wheels. + * T? G-code to extrude shouldn't have to follow. Load to extruder wheels is done automatically. + * Tx Same as T?, but nozzle doesn't have to be preheated. Tc requires a preheated nozzle to finish filament load. + * Tc Load to nozzle after filament was prepared by Tc and nozzle is already heated. */ void GcodeSuite::T(const int8_t tool_index) { #if HAS_MULTI_EXTRUDER // For 'T' with no parameter report the current tool. if (parser.string_arg && *parser.string_arg == '*') { - SERIAL_ECHOLNPGM(STR_ACTIVE_EXTRUDER, active_extruder); + SERIAL_ECHOLNPGM(STR_ACTIVE_EXTRUDER, motion.extruder); return; } #endif @@ -66,7 +69,12 @@ void GcodeSuite::T(const int8_t tool_index) { // Count this command as movement / activity reset_stepper_timeout(); - #if HAS_PRUSA_MMU2 + #if HAS_PRUSA_MMU3 + if (parser.has_string()) { + mmu3.tool_change(parser.string_arg[0], uint8_t(tool_index)); // Special commands T?/Tx/Tc + return; + } + #elif HAS_PRUSA_MMU2 if (parser.string_arg) { mmu2.tool_change(parser.string_arg); // Special commands T?/Tx/Tc return; @@ -76,7 +84,7 @@ void GcodeSuite::T(const int8_t tool_index) { tool_change(tool_index #if HAS_MULTI_EXTRUDER , parser.boolval('S') - || TERN(PARKING_EXTRUDER, false, tool_index == active_extruder) // For PARKING_EXTRUDER motion is decided in tool_change() + || TERN(PARKING_EXTRUDER, false, tool_index == motion.extruder) // For PARKING_EXTRUDER motion is decided in tool_change() #endif ); } diff --git a/Marlin/src/gcode/eeprom/M500-M504.cpp b/Marlin/src/gcode/eeprom/M500-M504.cpp index c3962117e51f..ffa6d5c6860e 100644 --- a/Marlin/src/gcode/eeprom/M500-M504.cpp +++ b/Marlin/src/gcode/eeprom/M500-M504.cpp @@ -22,7 +22,6 @@ #include "../gcode.h" #include "../../module/settings.h" -#include "../../core/serial.h" #include "../../inc/MarlinConfig.h" #if ENABLED(CONFIGURATION_EMBEDDING) diff --git a/Marlin/src/gcode/feature/advance/M900.cpp b/Marlin/src/gcode/feature/advance/M900.cpp index e8a16d952f30..617bb5791696 100644 --- a/Marlin/src/gcode/feature/advance/M900.cpp +++ b/Marlin/src/gcode/feature/advance/M900.cpp @@ -22,22 +22,28 @@ #include "../../../inc/MarlinConfig.h" -#if ENABLED(LIN_ADVANCE) +#if HAS_LIN_ADVANCE_K #include "../../gcode.h" #include "../../../module/planner.h" +#include "../../../module/stepper.h" #if ENABLED(ADVANCE_K_EXTRA) - float other_extruder_advance_K[DISTINCT_E]; + float other_extruder_advance_K[EXTRUDERS]; uint8_t lin_adv_slot = 0; #endif /** * M900: Get or Set Linear Advance K-factor * T Which tool to address - * K Set current advance K factor (Slot 0). - * L Set secondary advance K factor (Slot 1). Requires ADVANCE_K_EXTRA. - * S<0/1> Activate slot 0 or 1. Requires ADVANCE_K_EXTRA. + * K Set current advance K factor (aka Slot 0). + * + * With ADVANCE_K_EXTRA: + * S<0/1> Activate slot 0 or 1. + * L Set secondary advance K factor (Slot 1). + * + * With SMOOTH_LIN_ADVANCE: + * U Set a tau value for LA smoothing */ void GcodeSuite::M900() { @@ -52,45 +58,50 @@ void GcodeSuite::M900() { constexpr uint8_t tool_index = 0; UNUSED(tool_index); #else - const uint8_t tool_index = parser.intval('T', active_extruder); + const uint8_t tool_index = parser.intval('T', motion.extruder); if (tool_index >= EXTRUDERS) { echo_value_oor('T', false); return; } #endif - float &kref = planner.extruder_advance_K[E_INDEX_N(tool_index)], newK = kref; - const float oldK = newK; + const float oldK = planner.get_advance_k(tool_index); + float newK = oldK; + + #if ENABLED(SMOOTH_LIN_ADVANCE) + const float oldU = stepper.get_advance_tau(tool_index); + float newU = oldU; + #endif #if ENABLED(ADVANCE_K_EXTRA) - float &lref = other_extruder_advance_K[E_INDEX_N(tool_index)]; + float &lref = other_extruder_advance_K[tool_index]; - const bool old_slot = TEST(lin_adv_slot, tool_index), // The tool's current slot (0 or 1) - new_slot = parser.boolval('S', old_slot); // The passed slot (default = current) + const bool old_slot = TEST(lin_adv_slot, tool_index), // Each tool uses 1 bit to store its current slot (0 or 1) + new_slot = parser.boolval('S', old_slot); // The new slot (0 or 1) to set for the tool (default = no change) // If a new slot is being selected swap the current and // saved K values. Do here so K/L will apply correctly. if (new_slot != old_slot) { // Not the same slot? SET_BIT_TO(lin_adv_slot, tool_index, new_slot); // Update the slot for the tool - newK = lref; // Get new K value from backup - lref = oldK; // Save K to backup + newK = lref; // Get the backup K value (to apply below) + lref = oldK; // Back up the active K value } // Set the main K value. Apply if the main slot is active. if (parser.seenval('K')) { const float K = parser.value_float(); if (!WITHIN(K, 0, 10)) echo_value_oor('K'); - else if (new_slot) lref = K; // S1 Knn - else newK = K; // S0 Knn + else if (new_slot) lref = K; // S1 Knn (set main K in its backup slot) + else newK = K; // S0 Knn (use main K now) } // Set the extra K value. Apply if the extra slot is active. if (parser.seenval('L')) { const float L = parser.value_float(); if (!WITHIN(L, 0, 10)) echo_value_oor('L'); - else if (!new_slot) lref = L; // S0 Lnn - else newK = L; // S1 Lnn + else if (!new_slot) lref = L; // S0 Lnn (set extra K in its backup slot) + else newK = L; // S1 Lnn (use extra K now) } #else @@ -105,37 +116,59 @@ void GcodeSuite::M900() { #endif - if (newK != oldK) { + #if ENABLED(SMOOTH_LIN_ADVANCE) + if (parser.seenval('U')) { + const float tau = parser.value_float(); + if (WITHIN(tau, 0.0f, 0.5f)) + newU = tau; + else + echo_value_oor('U'); + } + #endif + + if (newK != oldK || TERN0(SMOOTH_LIN_ADVANCE, newU != oldU)) { planner.synchronize(); - kref = newK; + if (newK != oldK) planner.set_advance_k(newK, tool_index); + #if ENABLED(SMOOTH_LIN_ADVANCE) + if (newU != oldU) stepper.set_advance_tau(newU, tool_index); + #endif } if (!parser.seen_any()) { #if ENABLED(ADVANCE_K_EXTRA) - #if DISTINCT_E < 2 - SERIAL_ECHOLNPGM("Advance S", new_slot, " K", kref, "(S", !new_slot, " K", lref, ")"); + #if DISABLED(DISTINCT_E_FACTORS) + SERIAL_ECHOLNPGM("Advance S", new_slot, " K", newK, "(S", !new_slot, " K", lref, ")"); #else EXTRUDER_LOOP() { const bool slot = TEST(lin_adv_slot, e); - SERIAL_ECHOLNPGM("Advance T", e, " S", slot, " K", planner.extruder_advance_K[e], + SERIAL_ECHOLNPGM("Advance T", e, " S", slot, " K", planner.get_advance_k(e), "(S", !slot, " K", other_extruder_advance_K[e], ")"); } #endif - #else + #else // !ADVANCE_K_EXTRA SERIAL_ECHO_START(); - #if DISTINCT_E < 2 - SERIAL_ECHOLNPGM("Advance K=", planner.extruder_advance_K[0]); + #if DISABLED(DISTINCT_E_FACTORS) + SERIAL_ECHOPGM("Advance K=", planner.get_advance_k()); + #if ENABLED(SMOOTH_LIN_ADVANCE) + SERIAL_ECHOPGM(" TAU=", stepper.get_advance_tau()); + #endif + SERIAL_EOL(); #else SERIAL_ECHOPGM("Advance K"); - EXTRUDER_LOOP() SERIAL_ECHO(C(' '), C('0' + e), C(':'), planner.extruder_advance_K[e]); + EXTRUDER_LOOP() SERIAL_ECHO(C(' '), C('0' + e), C(':'), planner.get_advance_k(e)); SERIAL_EOL(); + #if ENABLED(SMOOTH_LIN_ADVANCE) + SERIAL_ECHOPGM("Advance TAU"); + EXTRUDER_LOOP() SERIAL_ECHO(C(' '), C('0' + e), C(':'), stepper.get_advance_tau(e)); + SERIAL_EOL(); + #endif #endif - #endif + #endif // !ADVANCE_K_EXTRA } } @@ -144,15 +177,21 @@ void GcodeSuite::M900_report(const bool forReplay/*=true*/) { TERN_(MARLIN_SMALL_BUILD, return); report_heading(forReplay, F(STR_LINEAR_ADVANCE)); - #if DISTINCT_E < 2 + DISTINCT_E_LOOP() { report_echo_start(forReplay); - SERIAL_ECHOLNPGM(" M900 K", planner.extruder_advance_K[0]); - #else - EXTRUDER_LOOP() { - report_echo_start(forReplay); - SERIAL_ECHOLNPGM(" M900 T", e, " K", planner.extruder_advance_K[e]); - } - #endif + SERIAL_ECHOPGM( + #if ENABLED(DISTINCT_E_FACTORS) + " M900 T", e, " K" + #else + " M900 K" + #endif + ); + SERIAL_ECHO(planner.get_advance_k(e)); + #if ENABLED(SMOOTH_LIN_ADVANCE) + SERIAL_ECHOPGM(" U", stepper.get_advance_tau(e)); + #endif + SERIAL_EOL(); + } } -#endif // LIN_ADVANCE +#endif // HAS_LIN_ADVANCE_K diff --git a/Marlin/src/gcode/feature/camera/M240.cpp b/Marlin/src/gcode/feature/camera/M240.cpp index bb1d3f9eee31..7ce0a1934cac 100644 --- a/Marlin/src/gcode/feature/camera/M240.cpp +++ b/Marlin/src/gcode/feature/camera/M240.cpp @@ -25,16 +25,12 @@ #if ENABLED(PHOTO_GCODE) #include "../../gcode.h" -#include "../../../module/motion.h" // for active_extruder and current_position +#include "../../../module/motion.h" // for motion.extruder and motion.position #if PIN_EXISTS(CHDK) millis_t chdk_timeout; // = 0 #endif -#if defined(PHOTO_POSITION) && PHOTO_DELAY_MS > 0 - #include "../../../MarlinCore.h" // for idle() -#endif - #ifdef PHOTO_RETRACT_MM #define _PHOTO_RETRACT_MM (PHOTO_RETRACT_MM + 0) @@ -47,9 +43,9 @@ #endif #ifdef PHOTO_RETRACT_MM - inline void e_move_m240(const float length, const_feedRate_t fr_mm_s) { - if (length && thermalManager.hotEnoughToExtrude(active_extruder)) - unscaled_e_move(length, fr_mm_s); + inline void e_move_m240(const float length, const feedRate_t fr_mm_s) { + if (length && thermalManager.hotEnoughToExtrude(motion.extruder)) + motion.unscaled_e_move(length, fr_mm_s); } #endif @@ -100,9 +96,9 @@ * M240: Trigger a camera by... * * - CHDK : Emulate a Canon RC-1 with a configurable ON duration. - * https://captain-slow.dk/2014/03/09/3d-printing-timelapses/ + * https://youtube.be/UqZ8Um5MZEA * - PHOTOGRAPH_PIN : Pulse a digital pin 16 times. - * See https://www.doc-diy.net/photo/rc-1_hacked/ + * See https://web.archive.org/web/20250327153953/www.doc-diy.net/photo/rc-1_hacked/ * - PHOTO_SWITCH_POSITION : Bump a physical switch with the X-carriage using a * configured position, delay, and retract length. * @@ -126,14 +122,14 @@ void GcodeSuite::M240() { #ifdef PHOTO_POSITION - if (homing_needed_error()) return; + if (motion.homing_needed_error()) return; const xyz_pos_t old_pos = NUM_AXIS_ARRAY( - current_position.x + parser.linearval('A'), - current_position.y + parser.linearval('B'), - current_position.z, - current_position.i, current_position.j, current_position.k, - current_position.u, current_position.v, current_position.w + motion.position.x + parser.linearval('A'), + motion.position.y + parser.linearval('B'), + motion.position.z, + motion.position.i, motion.position.j, motion.position.k, + motion.position.u, motion.position.v, motion.position.w ); #ifdef PHOTO_RETRACT_MM @@ -147,24 +143,24 @@ void GcodeSuite::M240() { constexpr xyz_pos_t photo_position = PHOTO_POSITION; xyz_pos_t raw = { - parser.seenval('X') ? RAW_X_POSITION(parser.value_linear_units()) : photo_position.x, - parser.seenval('Y') ? RAW_Y_POSITION(parser.value_linear_units()) : photo_position.y, - (parser.seenval('Z') ? parser.value_linear_units() : photo_position.z) + current_position.z + parser.seenval('X') ? motion.raw_x(parser.value_linear_units()) : photo_position.x, + parser.seenval('Y') ? motion.raw_y(parser.value_linear_units()) : photo_position.y, + (parser.seenval('Z') ? parser.value_linear_units() : photo_position.z) + motion.position.z }; - apply_motion_limits(raw); - do_blocking_move_to(raw, fr_mm_s); + motion.apply_limits(raw); + motion.blocking_move(raw, fr_mm_s); #ifdef PHOTO_SWITCH_POSITION constexpr xy_pos_t photo_switch_position = PHOTO_SWITCH_POSITION; const xy_pos_t sraw = { - parser.seenval('I') ? RAW_X_POSITION(parser.value_linear_units()) : photo_switch_position.x, - parser.seenval('J') ? RAW_Y_POSITION(parser.value_linear_units()) : photo_switch_position.y + parser.seenval('I') ? motion.raw_x(parser.value_linear_units()) : photo_switch_position.x, + parser.seenval('J') ? motion.raw_y(parser.value_linear_units()) : photo_switch_position.y }; - do_blocking_move_to_xy(sraw, get_homing_bump_feedrate(X_AXIS)); + motion.blocking_move_xy(sraw, motion.get_homing_bump_feedrate(X_AXIS)); #if PHOTO_SWITCH_MS > 0 safe_delay(parser.intval('D', PHOTO_SWITCH_MS)); #endif - do_blocking_move_to(raw); + motion.blocking_move(raw); #endif #endif @@ -185,9 +181,9 @@ void GcodeSuite::M240() { #ifdef PHOTO_POSITION #if PHOTO_DELAY_MS > 0 const millis_t timeout = millis() + parser.intval('P', PHOTO_DELAY_MS); - while (PENDING(millis(), timeout)) idle(); + while (PENDING(millis(), timeout)) marlin.idle(); #endif - do_blocking_move_to(old_pos, fr_mm_s); + motion.blocking_move(old_pos, fr_mm_s); #ifdef PHOTO_RETRACT_MM e_move_m240(rval, sval); #endif diff --git a/Marlin/src/gcode/feature/cancel/M486.cpp b/Marlin/src/gcode/feature/cancel/M486.cpp index c1e90d1b9610..237654e43314 100644 --- a/Marlin/src/gcode/feature/cancel/M486.cpp +++ b/Marlin/src/gcode/feature/cancel/M486.cpp @@ -41,13 +41,13 @@ void GcodeSuite::M486() { if (parser.seen('T')) { cancelable.reset(); - cancelable.object_count = parser.intval('T', 1); + cancelable.state.object_count = parser.intval('T', 1); } if (parser.seenval('S')) cancelable.set_active_object(parser.value_int()); - if (parser.seen('C')) cancelable.cancel_active_object(); + if (parser.seen_test('C')) cancelable.cancel_active_object(); if (parser.seenval('P')) cancelable.cancel_object(parser.value_int()); diff --git a/Marlin/src/gcode/feature/clean/G12.cpp b/Marlin/src/gcode/feature/clean/G12.cpp index a5e312f8fdfe..2e8d78420763 100644 --- a/Marlin/src/gcode/feature/clean/G12.cpp +++ b/Marlin/src/gcode/feature/clean/G12.cpp @@ -48,7 +48,7 @@ void GcodeSuite::G12() { // Don't allow nozzle cleaning without homing first constexpr main_axes_bits_t clean_axis_mask = main_axes_mask & ~TERN0(NOZZLE_CLEAN_NO_Z, Z_AXIS) & ~TERN0(NOZZLE_CLEAN_NO_Y, Y_AXIS); - if (homing_needed_error(clean_axis_mask)) return; + if (motion.homing_needed_error(clean_axis_mask)) return; #ifdef WIPE_SEQUENCE_COMMANDS if (!parser.seen_any()) { @@ -79,11 +79,11 @@ void GcodeSuite::G12() { TEMPORARY_BED_LEVELING_STATE(!TEST(cleans, Z_AXIS) && planner.leveling_active); #endif - SET_SOFT_ENDSTOP_LOOSE(!parser.boolval('E')); + motion.set_soft_endstop_loose(!parser.boolval('E')); nozzle.clean(pattern, strokes, radius, objects, cleans); - SET_SOFT_ENDSTOP_LOOSE(false); + motion.set_soft_endstop_loose(false); } #endif // NOZZLE_CLEAN_FEATURE diff --git a/Marlin/src/gcode/feature/controllerfan/M710.cpp b/Marlin/src/gcode/feature/controllerfan/M710.cpp index c8b5efa8cb02..6bdfcd9b322c 100644 --- a/Marlin/src/gcode/feature/controllerfan/M710.cpp +++ b/Marlin/src/gcode/feature/controllerfan/M710.cpp @@ -46,24 +46,22 @@ * M710 I127 A1 S255 D160 ; Set controller fan idle speed 50%, AutoMode On, Fan speed 100%, duration to 160 Secs */ void GcodeSuite::M710() { + if (!parser.seen("ADIRS")) return M710_report(); - const bool seenR = parser.seen('R'); - if (seenR) controllerFan.reset(); + if (parser.seen_test('R')) + controllerFan.reset(); - const bool seenS = parser.seenval('S'); - if (seenS) controllerFan.settings.active_speed = parser.value_byte(); + if (parser.seenval('S')) + controllerFan.settings.active_speed = parser.value_byte(); - const bool seenI = parser.seenval('I'); - if (seenI) controllerFan.settings.idle_speed = parser.value_byte(); + if (parser.seenval('I')) + controllerFan.settings.idle_speed = parser.value_byte(); - const bool seenA = parser.seenval('A'); - if (seenA) controllerFan.settings.auto_mode = parser.value_bool(); + if (parser.seenval('A')) + controllerFan.settings.auto_mode = parser.value_bool(); - const bool seenD = parser.seenval('D'); - if (seenD) controllerFan.settings.duration = parser.value_ushort(); - - if (!(seenR || seenS || seenI || seenA || seenD)) - M710_report(); + if (parser.seenval('D')) + controllerFan.settings.duration = parser.value_ushort(); } void GcodeSuite::M710_report(const bool forReplay/*=true*/) { diff --git a/Marlin/src/gcode/feature/digipot/M907-M910.cpp b/Marlin/src/gcode/feature/digipot/M907-M910.cpp index 55d43f1b416f..425d27a72dfb 100644 --- a/Marlin/src/gcode/feature/digipot/M907-M910.cpp +++ b/Marlin/src/gcode/feature/digipot/M907-M910.cpp @@ -46,15 +46,32 @@ * Set percentage of max current for all axes (Requires HAS_DIGIPOT_DAC) */ void GcodeSuite::M907() { + #if HAS_MOTOR_CURRENT_SPI if (!parser.seen("BS" STR_AXES_LOGICAL)) return M907_report(); - if (parser.seenval('S')) for (uint8_t i = 0; i < MOTOR_CURRENT_COUNT; ++i) stepper.set_digipot_current(i, parser.value_int()); - LOOP_LOGICAL_AXES(i) if (parser.seenval(IAXIS_CHAR(i))) stepper.set_digipot_current(i, parser.value_int()); // X Y Z (I J K U V W) E (map to drivers according to DIGIPOT_CHANNELS. Default with NUM_AXES 3: map X Y Z E to X Y Z E0) - // Additional extruders use B,C. - // TODO: Change these parameters because 'E' is used and D should be reserved for debugging. B? + // S - Set current in mA for all axes + if (parser.seenval('S')) + for (uint8_t i = 0; i < MOTOR_CURRENT_COUNT; ++i) + stepper.set_digipot_current(i, parser.value_int()); + + // X Y Z I J K U V W E + // Map to drivers according to pots addresses. + // Default with NUM_AXES 3: map X Y Z E to X Y Z E0. + LOOP_LOGICAL_AXES(i) + if (parser.seenval(IAXIS_CHAR(i))) + stepper.set_digipot_current(i, parser.value_int()); + + /** + * Additional extruders use B,C in this legacy protocol + * TODO: Update to allow for an index with X, Y, Z, E axis to isolate a single stepper + * and use configured axis names instead of IJKUVW. i.e., Match the behavior of + * other G-codes that set stepper-specific parameters. If necessary deprecate G-codes. + * Bonus Points: Standardize a method that all G-codes can use to refer to one or + * more steppers/drivers and apply to various G-codes. + */ #if E_STEPPERS >= 2 if (parser.seenval('B')) stepper.set_digipot_current(E_AXIS + 1, parser.value_int()); #if E_STEPPERS >= 3 @@ -68,58 +85,88 @@ void GcodeSuite::M907() { #define HAS_X_Y_XY_I_J_K_U_V_W 1 #endif - #if HAS_X_Y_XY_I_J_K_U_V_W || ANY_PIN(MOTOR_CURRENT_PWM_E, MOTOR_CURRENT_PWM_Z) + #if ANY(HAS_X_Y_XY_I_J_K_U_V_W, HAS_MOTOR_CURRENT_PWM_E, HAS_MOTOR_CURRENT_PWM_Z) if (!parser.seen("S" #if HAS_X_Y_XY_I_J_K_U_V_W - "XY" SECONDARY_AXIS_GANG("I", "J", "K", "U", "V", "W") - #endif - #if PIN_EXISTS(MOTOR_CURRENT_PWM_Z) - "Z" - #endif - #if PIN_EXISTS(MOTOR_CURRENT_PWM_E) - "E" + NUM_AXIS_GANG("X", "Y",, "I", "J", "K", "U", "V", "W") #endif + TERN_(HAS_MOTOR_CURRENT_PWM_Z, "Z") + TERN_(HAS_MOTOR_CURRENT_PWM_E, "E") )) return M907_report(); - if (parser.seenval('S')) for (uint8_t a = 0; a < MOTOR_CURRENT_COUNT; ++a) stepper.set_digipot_current(a, parser.value_int()); + // S - Set all stepper current to the same value + if (parser.seenval('S')) { + const int16_t v = parser.value_int(); + for (uint8_t a = 0; a < MOTOR_CURRENT_COUNT; ++a) + stepper.set_digipot_current(a, v); + } - #if HAS_X_Y_XY_I_J_K_U_V_W - if (NUM_AXIS_GANG( - parser.seenval('X'), || parser.seenval('Y'), || false, - || parser.seenval('I'), || parser.seenval('J'), || parser.seenval('K'), - || parser.seenval('U'), || parser.seenval('V'), || parser.seenval('W') - )) stepper.set_digipot_current(0, parser.value_int()); - #endif - #if PIN_EXISTS(MOTOR_CURRENT_PWM_Z) - if (parser.seenval('Z')) stepper.set_digipot_current(1, parser.value_int()); - #endif - #if PIN_EXISTS(MOTOR_CURRENT_PWM_E) - if (parser.seenval('E')) stepper.set_digipot_current(2, parser.value_int()); - #endif + // X Y I J K U V W - All aliases to set the current for "most axes." + // Only the value of the last given parameter is used. + if (ENABLED(HAS_X_Y_XY_I_J_K_U_V_W) && (NUM_AXIS_GANG( + parser.seenval('X'), || parser.seenval('Y'), || false, + || parser.seenval('I'), || parser.seenval('J'), || parser.seenval('K'), + || parser.seenval('U'), || parser.seenval('V'), || parser.seenval('W') + ))) + stepper.set_digipot_current(0, parser.value_int()); + + // Z - Set the current just for the Z axis + if (TERN0(HAS_MOTOR_CURRENT_PWM_Z, parser.seenval('Z'))) + stepper.set_digipot_current(1, parser.value_int()); + + // Z - Set the current just for the Extruder + if (TERN0(HAS_MOTOR_CURRENT_PWM_E, parser.seenval('E'))) + stepper.set_digipot_current(2, parser.value_int()); #endif #endif // HAS_MOTOR_CURRENT_PWM #if HAS_MOTOR_CURRENT_I2C - // this one uses actual amps in floating point - if (parser.seenval('S')) for (uint8_t q = 0; q < DIGIPOT_I2C_NUM_CHANNELS; ++q) digipot_i2c.set_current(q, parser.value_float()); - LOOP_LOGICAL_AXES(i) if (parser.seenval(IAXIS_CHAR(i))) digipot_i2c.set_current(i, parser.value_float()); // X Y Z (I J K U V W) E (map to drivers according to pots adresses. Default with NUM_AXES 3 X Y Z E: map to X Y Z E0) + // This current driver takes actual Amps in floating point + // rather than milli-amps or some scalar unit. + + // S - Set the same current in Amps on all channels + if (parser.seenval('S')) { + const float v = parser.value_float(); + for (uint8_t q = 0; q < DIGIPOT_I2C_NUM_CHANNELS; ++q) + digipot_i2c.set_current(q, v); + } + + // X Y Z I J K U V W E + // Map to drivers according to pots addresses. + // Default with NUM_AXES 3: map X Y Z E to X Y Z E0. + LOOP_LOGICAL_AXES(i) + if (parser.seenval(IAXIS_CHAR(i))) + digipot_i2c.set_current(i, parser.value_float()); + // Additional extruders use B,C,D. - // TODO: Change these parameters because 'E' is used and because 'D' should be reserved for debugging. B? + // TODO: Make parameters work like other axis-specific / stepper-specific. See above. #if E_STEPPERS >= 2 for (uint8_t i = E_AXIS + 1; i < _MAX(DIGIPOT_I2C_NUM_CHANNELS, (NUM_AXES + 3)); i++) - if (parser.seenval('B' + i - (E_AXIS + 1))) digipot_i2c.set_current(i, parser.value_float()); + if (parser.seenval('B' + i - (E_AXIS + 1))) + digipot_i2c.set_current(i, parser.value_float()); #endif - #endif + + #endif // HAS_MOTOR_CURRENT_I2C #if HAS_MOTOR_CURRENT_DAC + + // S - Set the same current percentage on all axes if (parser.seenval('S')) { const float dac_percent = parser.value_float(); - LOOP_LOGICAL_AXES(i) stepper_dac.set_current_percent(i, dac_percent); + LOOP_LOGICAL_AXES(i) + stepper_dac.set_current_percent(i, dac_percent); } - LOOP_LOGICAL_AXES(i) if (parser.seenval(IAXIS_CHAR(i))) stepper_dac.set_current_percent(i, parser.value_float()); // X Y Z (I J K U V W) E (map to drivers according to DAC_STEPPER_ORDER. Default with NUM_AXES 3: X Y Z E map to X Y Z E0) + + // X Y Z I J K U V W E + // Map to drivers according to pots addresses. + // Default with NUM_AXES 3: map X Y Z E to X Y Z E0. + LOOP_LOGICAL_AXES(i) + if (parser.seenval(IAXIS_CHAR(i))) + stepper_dac.set_current_percent(i, parser.value_float()); + #endif } @@ -131,9 +178,13 @@ void GcodeSuite::M907() { report_heading_etc(forReplay, F(STR_STEPPER_MOTOR_CURRENTS)); #if HAS_MOTOR_CURRENT_PWM SERIAL_ECHOLNPGM_P( // PWM-based has 3 values: - PSTR(" M907 X"), stepper.motor_current_setting[0] // X, Y, (I, J, K, U, V, W) - , SP_Z_STR, stepper.motor_current_setting[1] // Z - , SP_E_STR, stepper.motor_current_setting[2] // E + PSTR(" M907 X"), stepper.motor_current_setting[0] // X, Y, (I, J, K, U, V, W) + #if HAS_MOTOR_CURRENT_PWM_Z + , SP_Z_STR, stepper.motor_current_setting[1] // Z + #endif + #if HAS_MOTOR_CURRENT_PWM_E + , SP_E_STR, stepper.motor_current_setting[2] // E + #endif ); #elif HAS_MOTOR_CURRENT_SPI SERIAL_ECHOPGM(" M907"); // SPI-based has 5 values: diff --git a/Marlin/src/gcode/feature/filwidth/M404-M407.cpp b/Marlin/src/gcode/feature/filwidth/M404-M407.cpp index ff174ecf1358..5b906760ed00 100644 --- a/Marlin/src/gcode/feature/filwidth/M404-M407.cpp +++ b/Marlin/src/gcode/feature/filwidth/M404-M407.cpp @@ -26,7 +26,6 @@ #include "../../../feature/filwidth.h" #include "../../../module/planner.h" -#include "../../../MarlinCore.h" #include "../../gcode.h" /** diff --git a/Marlin/src/gcode/feature/ft_motion/M493.cpp b/Marlin/src/gcode/feature/ft_motion/M493.cpp index 2da92b8582d6..653da2a2b77a 100644 --- a/Marlin/src/gcode/feature/ft_motion/M493.cpp +++ b/Marlin/src/gcode/feature/ft_motion/M493.cpp @@ -27,42 +27,56 @@ #include "../../gcode.h" #include "../../../module/ft_motion.h" #include "../../../module/stepper.h" +#include "../../../lcd/marlinui.h" + +void say_shaper_type(const AxisEnum a, bool &sep, const char axis_name) { + if (sep) SERIAL_ECHOPGM(" ; "); + SERIAL_CHAR(axis_name, '='); + switch (ftMotion.cfg.shaper[a]) { + default: break; + case ftMotionShaper_NONE: SERIAL_ECHOPGM("None"); break; + TERN_(FTM_SHAPER_ZV, case ftMotionShaper_ZV: SERIAL_ECHOPGM("ZV"); break); + TERN_(FTM_SHAPER_ZVD, case ftMotionShaper_ZVD: SERIAL_ECHOPGM("ZVD"); break); + TERN_(FTM_SHAPER_ZVDD, case ftMotionShaper_ZVDD: SERIAL_ECHOPGM("ZVDD"); break); + TERN_(FTM_SHAPER_ZVDDD, case ftMotionShaper_ZVDDD: SERIAL_ECHOPGM("ZVDDD"); break); + TERN_(FTM_SHAPER_EI, case ftMotionShaper_EI: SERIAL_ECHOPGM("EI"); break); + TERN_(FTM_SHAPER_2HEI, case ftMotionShaper_2HEI: SERIAL_ECHOPGM("2 Hump EI"); break); + TERN_(FTM_SHAPER_3HEI, case ftMotionShaper_3HEI: SERIAL_ECHOPGM("3 Hump EI"); break); + TERN_(FTM_SHAPER_MZV, case ftMotionShaper_MZV: SERIAL_ECHOPGM("MZV"); break); + } + sep = true; +} void say_shaping() { - // FT Enabled - SERIAL_ECHO_TERNARY(ftMotion.cfg.mode, "Fixed-Time Motion ", "en", "dis", "abled"); + const ft_config_t &c = ftMotion.cfg; + + // FT Enabled (No EOL here. Printed after shaping type) + SERIAL_ECHO_TERNARY(c.active, "Fixed-Time Motion ", "en", "dis", "abled"); // FT Shaping - #if HAS_X_AXIS - if (ftMotion.cfg.mode > ftMotionMode_ENABLED) { - SERIAL_ECHOPGM(" with "); - switch (ftMotion.cfg.mode) { - default: break; - case ftMotionMode_ZV: SERIAL_ECHOPGM("ZV"); break; - case ftMotionMode_ZVD: SERIAL_ECHOPGM("ZVD"); break; - case ftMotionMode_ZVDD: SERIAL_ECHOPGM("ZVDD"); break; - case ftMotionMode_ZVDDD: SERIAL_ECHOPGM("ZVDDD"); break; - case ftMotionMode_EI: SERIAL_ECHOPGM("EI"); break; - case ftMotionMode_2HEI: SERIAL_ECHOPGM("2 Hump EI"); break; - case ftMotionMode_3HEI: SERIAL_ECHOPGM("3 Hump EI"); break; - case ftMotionMode_MZV: SERIAL_ECHOPGM("MZV"); break; - //case ftMotionMode_DISCTF: SERIAL_ECHOPGM("discrete transfer functions"); break; - //case ftMotionMode_ULENDO_FBS: SERIAL_ECHOPGM("Ulendo FBS."); return; - } - SERIAL_ECHOPGM(" shaping"); - } - #endif - SERIAL_ECHOLNPGM("."); + const bool is_shaping = AXIS_IS_SHAPING(X) || AXIS_IS_SHAPING(Y) || AXIS_IS_SHAPING(Z) || AXIS_IS_SHAPING(E); + bool sep = false; + if (is_shaping) { + #define STEPPER_E_NAME 'E' + #define _SAY_SHAPER(A) say_shaper_type(_AXIS(A), sep, STEPPER_##A##_NAME); + SERIAL_ECHOPGM(" ("); + SHAPED_CODE(_SAY_SHAPER(A), _SAY_SHAPER(B), _SAY_SHAPER(C), _SAY_SHAPER(E)); + SERIAL_CHAR(')'); + } + SERIAL_EOL(); - const bool z_based = TERN0(HAS_DYNAMIC_FREQ_MM, ftMotion.cfg.dynFreqMode == dynFreqMode_Z_BASED), - g_based = TERN0(HAS_DYNAMIC_FREQ_G, ftMotion.cfg.dynFreqMode == dynFreqMode_MASS_BASED), + // Axis Sync Enabled + if (c.active) SERIAL_ECHO_TERNARY(c.axis_sync_enabled, "Axis Sync. ", "en", "dis", "abled\n"); + + const bool z_based = TERN0(HAS_DYNAMIC_FREQ_MM, c.dynFreqMode == dynFreqMode_Z_BASED), + g_based = TERN0(HAS_DYNAMIC_FREQ_G, c.dynFreqMode == dynFreqMode_MASS_BASED), dynamic = z_based || g_based; // FT Dynamic Frequency Mode - if (ftMotion.cfg.modeHasShaper()) { + if (is_shaping) { #if HAS_DYNAMIC_FREQ SERIAL_ECHOPGM("Dynamic Frequency Mode "); - switch (ftMotion.cfg.dynFreqMode) { + switch (c.dynFreqMode) { default: case dynFreqMode_DISABLED: SERIAL_ECHOPGM("disabled"); break; #if HAS_DYNAMIC_FREQ_MM @@ -76,31 +90,39 @@ void say_shaping() { #endif #if HAS_X_AXIS - SERIAL_ECHO_TERNARY(dynamic, "X/A ", "base dynamic", "static", " compensator frequency: "); - SERIAL_ECHO(p_float_t(ftMotion.cfg.baseFreq[X_AXIS], 2), F("Hz")); - #if HAS_DYNAMIC_FREQ - if (dynamic) SERIAL_ECHO(F(" scaling: "), p_float_t(ftMotion.cfg.dynFreqK[X_AXIS], 2), F("Hz/"), z_based ? F("mm") : F("g")); - #endif - SERIAL_EOL(); + if (ftMotion.cfg.shaper[X_AXIS]) { + SERIAL_CHAR(STEPPER_A_NAME); + SERIAL_ECHO_TERNARY(dynamic, " ", "base dynamic", "static", " shaper frequency: "); + SERIAL_ECHO(p_float_t(c.baseFreq.x, 2), F(" Hz")); + #if HAS_DYNAMIC_FREQ + if (dynamic) SERIAL_ECHO(F(" scaling: "), p_float_t(c.dynFreqK.x, 2), F("Hz/"), z_based ? F("mm") : F("g")); + #endif + SERIAL_EOL(); + } #endif #if HAS_Y_AXIS - SERIAL_ECHO_TERNARY(dynamic, "Y/B ", "base dynamic", "static", " compensator frequency: "); - SERIAL_ECHO(p_float_t(ftMotion.cfg.baseFreq[Y_AXIS], 2), F(" Hz")); - #if HAS_DYNAMIC_FREQ - if (dynamic) SERIAL_ECHO(F(" scaling: "), p_float_t(ftMotion.cfg.dynFreqK[Y_AXIS], 2), F("Hz/"), z_based ? F("mm") : F("g")); - #endif - SERIAL_EOL(); + if (ftMotion.cfg.shaper[Y_AXIS]) { + SERIAL_CHAR(STEPPER_B_NAME); + SERIAL_ECHO_TERNARY(dynamic, " ", "base dynamic", "static", " shaper frequency: "); + SERIAL_ECHO(p_float_t(c.baseFreq.y, 2), F(" Hz")); + #if HAS_DYNAMIC_FREQ + if (dynamic) SERIAL_ECHO(F(" scaling: "), p_float_t(c.dynFreqK.y, 2), F("Hz/"), z_based ? F("mm") : F("g")); + #endif + SERIAL_EOL(); + } #endif - } - #if HAS_EXTRUDERS - SERIAL_ECHO_TERNARY(ftMotion.cfg.linearAdvEna, "Linear Advance ", "en", "dis", "abled"); - if (ftMotion.cfg.linearAdvEna) - SERIAL_ECHOLNPGM(". Gain: ", ftMotion.cfg.linearAdvK); - else - SERIAL_EOL(); - #endif + #if ENABLED(FTM_SHAPER_Z) + if (ftMotion.cfg.shaper[Z_AXIS]) + SERIAL_ECHOLN(C(STEPPER_C_NAME), F(" shaper frequency: "), p_float_t(c.baseFreq.z, 2), F(" Hz")); + #endif + + #if ENABLED(FTM_SHAPER_E) + if (ftMotion.cfg.shaper[E_AXIS]) + SERIAL_ECHOLN(C('E'), F(" shaper frequency: "), p_float_t(c.baseFreq.e, 2), F(" Hz")); + #endif + } } void GcodeSuite::M493_report(const bool forReplay/*=true*/) { @@ -108,290 +130,380 @@ void GcodeSuite::M493_report(const bool forReplay/*=true*/) { report_heading_etc(forReplay, F(STR_FT_MOTION)); const ft_config_t &c = ftMotion.cfg; - SERIAL_ECHOPGM(" M493 S", c.mode); - #if HAS_X_AXIS - SERIAL_ECHOPGM(" A", c.baseFreq[X_AXIS]); - #if HAS_Y_AXIS - SERIAL_ECHOPGM(" B", c.baseFreq[Y_AXIS]); + + SERIAL_ECHOLNPGM( + " M493 S", c.active + #if HAS_DYNAMIC_FREQ + , " D", c.dynFreqMode #endif - #endif + // Axis Synchronization + , " H", c.axis_sync_enabled + ); + #if HAS_DYNAMIC_FREQ - SERIAL_ECHOPGM(" D", c.dynFreqMode); - #if HAS_X_AXIS - SERIAL_ECHOPGM(" F", c.dynFreqK[X_AXIS]); - #if HAS_Y_AXIS - SERIAL_ECHOPGM(" H", c.dynFreqK[Y_AXIS]); - #endif - #endif + #define F_REPORT(A) , F(" F"), c.dynFreqK.A + #else + #define F_REPORT(A) #endif - #if HAS_EXTRUDERS - SERIAL_ECHOPGM(" P", c.linearAdvEna, " K", c.linearAdvK); + #if HAS_FTM_EI_SHAPING + #define Q_REPORT(A) , F(" Q"), c.vtol.A + #else + #define Q_REPORT(A) #endif - SERIAL_EOL(); + #define _REPORT_M493_AXIS(A) \ + SERIAL_ECHOLN(F(" M493 "), C(AXIS_CHAR(_AXIS(A))) \ + , F(" C"), c.shaper.A \ + , F(" A"), c.baseFreq.A \ + F_REPORT(A) \ + , F(" I"), c.zeta.A \ + Q_REPORT(A) \ + ); + // Shaper type for each axis + SHAPED_MAP(_REPORT_M493_AXIS); } /** * M493: Set Fixed-time Motion Control parameters * - * S Set the motion / shaping mode. Shaping requires an X axis, at the minimum. + * S Set Fixed-Time motion mode on or off. Ignored with NO_STANDARD_MOTION. + * 0: Fixed-Time Motion OFF (Standard Motion) + * 1: Fixed-Time Motion ON + * + * V Flag to request version (Version 2+). (No reply = Version < 2) * - * 0: Standard Motion - * 1: Fixed-Time Motion - * 10: ZV : Zero Vibration - * 11: ZVD : Zero Vibration and Derivative - * 12: ZVDD : Zero Vibration, Derivative, and Double Derivative - * 13: ZVDDD : Zero Vibration, Derivative, Double Derivative, and Triple Derivative - * 14: EI : Extra-Intensive - * 15: 2HEI : 2-Hump Extra-Intensive - * 16: 3HEI : 3-Hump Extra-Intensive - * 17: MZV : Mass-based Zero Vibration + * H Enable (1) or Disable (0) Axis Synchronization. + * + * Linear / Pressure Advance: * * P Enable (1) or Disable (0) Linear Advance pressure control * - * K Set Linear Advance gain + * Specifying Axes (for A,C,F,I,Q): + * + * X/Y/Z/E : Flag the axes (or core steppers) on which to apply the given parameters + * If none are given then XY is assumed. * - * D Set Dynamic Frequency mode + * Compensator / Input Shaper: + * + * C Set Compensator Mode (Input Shaper) for the specified axes + * Users / slicers must remember to set the mode for all relevant axes! + * 0: NONE : No input shaper + * 1: ZV : Zero Vibration + * 2: ZVD : Zero Vibration and Derivative + * 3: ZVDD : Zero Vibration, Derivative, and Double Derivative + * 4: ZVDDD : Zero Vibration, Derivative, Double Derivative, and Triple Derivative + * 5: EI : Extra-Intensive + * 6: 2HEI : 2-Hump Extra-Intensive + * 7: 3HEI : 3-Hump Extra-Intensive + * 8: MZV : Mass-based Zero Vibration + * + * A Set static/base frequency for the specified axes only X and Y axes + * I Set damping ratio for the specified axes + * Q Set vibration tolerance (vtol) for the specified axes + * + * Dynamic Frequency Mode: + * + * D Set Dynamic Frequency mode (for all axis compensators) * 0: DISABLED * 1: Z-based (Requires a Z axis) * 2: Mass-based (Requires X and E axes) * - * A Set static/base frequency for the X axis - * F Set frequency scaling for the X axis - * I 0.0 Set damping ratio for the X axis - * Q 0.00 Set the vibration tolerance for the X axis + * F Set frequency scaling for the specified axes * - * B Set static/base frequency for the Y axis - * H Set frequency scaling for the Y axis - * J 0.0 Set damping ratio for the Y axis - * R 0.00 Set the vibration tolerance for the Y axis */ void GcodeSuite::M493() { - struct { bool update_n:1, update_a:1, reset_ft:1, report_h:1; } flag = { false }; + // Request version of FTM. (No response = Version < 2) + if (parser.seen('V') && !parser.has_value()) { + SERIAL_ECHOLNPGM("FTM V" STRINGIFY(FTM_VERSION)); + return; + } + + struct { bool update:1, report:1; } flag = { false }; if (!parser.seen_any()) - flag.report_h = true; - - // Parse 'S' mode parameter. - if (parser.seenval('S')) { - const ftMotionMode_t newmm = (ftMotionMode_t)parser.value_byte(); - - if (newmm != ftMotion.cfg.mode) { - switch (newmm) { - default: SERIAL_ECHOLNPGM("?Invalid control mode [S] value."); return; - #if HAS_X_AXIS - case ftMotionMode_ZV: - case ftMotionMode_ZVD: - case ftMotionMode_ZVDD: - case ftMotionMode_ZVDDD: - case ftMotionMode_EI: - case ftMotionMode_2HEI: - case ftMotionMode_3HEI: - case ftMotionMode_MZV: - //case ftMotionMode_ULENDO_FBS: - //case ftMotionMode_DISCTF: - flag.update_n = flag.update_a = true; - #endif - case ftMotionMode_DISABLED: flag.reset_ft = true; - case ftMotionMode_ENABLED: - ftMotion.cfg.mode = newmm; - flag.report_h = true; - break; + flag.report = true; + + ft_config_t &c = ftMotion.cfg; + + #if HAS_STANDARD_MOTION + // Parse 'S' mode parameter. + if (parser.seen('S')) { + const bool active = parser.value_bool(); + if (active != c.active) { + ftMotion.toggle(); + flag.report = true; } } - } + #endif - #if HAS_EXTRUDERS + #if NUM_AXES_SHAPED > 0 - // Pressure control (linear advance) parameter. - if (parser.seen('P')) { - const bool val = parser.value_bool(); - ftMotion.cfg.linearAdvEna = val; - flag.report_h = true; - SERIAL_ECHO_TERNARY(val, "Linear Advance ", "en", "dis", "abled.\n"); + const bool seenC = parser.seenval('C'); + const ftMotionShaper_t shaperVal = seenC ? (ftMotionShaper_t)parser.value_byte() : ftMotionShaper_NONE; + const bool goodShaper = WITHIN(shaperVal, ftMotionShaper_NONE, ftMotionShaper_MZV); + if (seenC && !goodShaper) { + SERIAL_ECHOLN(F("?Invalid "), F("(C)ompensator value. (0-"), int(ftMotionShaper_MZV)); + return; } + auto set_shaper = [&](const AxisEnum axis, ftMotionShaper_t newsh) { + if (c.setShaper(axis, newsh)) + flag.update = flag.report = true; + }; - // Pressure control (linear advance) gain parameter. - if (parser.seenval('K')) { - const float val = parser.value_float(); - if (val >= 0.0f) { - ftMotion.cfg.linearAdvK = val; - flag.report_h = true; - } - else // Value out of range. - SERIAL_ECHOLNPGM("Linear Advance gain out of range."); - } + #endif // NUM_AXES_SHAPED > 0 - #endif // HAS_EXTRUDERS + // Parse bool 'H' Axis Synchronization parameter. + if (parser.seen('H') && c.setAxisSync(parser.value_bool())) + flag.report = true; #if HAS_DYNAMIC_FREQ // Dynamic frequency mode parameter. if (parser.seenval('D')) { - if (ftMotion.cfg.modeHasShaper()) { - const dynFreqMode_t val = dynFreqMode_t(parser.value_byte()); - switch (val) { - #if HAS_DYNAMIC_FREQ_MM - case dynFreqMode_Z_BASED: - #endif - #if HAS_DYNAMIC_FREQ_G - case dynFreqMode_MASS_BASED: - #endif - case dynFreqMode_DISABLED: - ftMotion.cfg.dynFreqMode = val; - flag.report_h = true; - break; - default: - SERIAL_ECHOLNPGM("?Invalid Dynamic Frequency Mode [D] value."); - break; + if (AXIS_IS_SHAPING(X) || AXIS_IS_SHAPING(Y)) { + switch (c.setDynFreqMode(parser.value_byte())) { + case 0: break; // Same value, no update + case 1: flag.report = true; break; // New value, updated + default: SERIAL_ECHOLN(F("?Invalid "), F("(D)ynamic Frequency Mode value.")); break; } } - else { - SERIAL_ECHOLNPGM("?Wrong shaper for [D] Dynamic Frequency mode."); - } + else + SERIAL_ECHOLNPGM("?Shaper required for (D)ynamic Frequency Mode ", c.dynFreqMode, "."); } - const bool modeUsesDynFreq = ( - TERN0(HAS_DYNAMIC_FREQ_MM, ftMotion.cfg.dynFreqMode == dynFreqMode_Z_BASED) - || TERN0(HAS_DYNAMIC_FREQ_G, ftMotion.cfg.dynFreqMode == dynFreqMode_MASS_BASED) - ); + const bool modeUsesDynFreq = c.modeUsesDynFreq(); #endif // HAS_DYNAMIC_FREQ + // Frequency parameter + const bool seenA = parser.seenval('A'); + const float baseFreqVal = seenA ? parser.value_float() : 0.0f; + const bool goodBaseFreq = seenA && c.goodBaseFreq(baseFreqVal); + if (seenA && !goodBaseFreq) + SERIAL_ECHOLN(F("?Invalid "), F("(A) Base Frequency value. ("), int(FTM_MIN_SHAPE_FREQ), C('-'), int((FTM_FS) / 2), C(')')); + + #if HAS_DYNAMIC_FREQ + // Dynamic Frequency parameter + const bool seenF = parser.seenval('F'); + const float baseDynFreqVal = seenF ? parser.value_float() : 0.0f; + if (seenF && !modeUsesDynFreq) + SERIAL_ECHOLNPGM("?Wrong mode for (F)requency scaling."); + #endif + + // Zeta parameter + const bool seenI = parser.seenval('I'); + const float zetaVal = seenI ? parser.value_float() : 0.0f; + const bool goodZeta = seenI && c.goodZeta(zetaVal); + if (seenI && !goodZeta) + SERIAL_ECHOLN(F("?Invalid "), F("(I) Zeta value. (0.01-" STRINGIFY(FTM_MAX_DAMPENING) ")")); // Zeta out of range + + #if HAS_FTM_EI_SHAPING + // Vibration Tolerance parameter + const bool seenQ = parser.seenval('Q'); + const float vtolVal = seenQ ? parser.value_float() : 0.0f; + const bool goodVtol = seenQ && c.goodVtol(vtolVal); + if (seenQ && !goodVtol) + SERIAL_ECHOLN(F("?Invalid "), F("(Q) Vibration Tolerance value. (0.0-1.0)")); // VTol out of range + #endif + + const bool apply_xy = !parser.seen("XYZE"); + #if HAS_X_AXIS - // Parse frequency parameter (X axis). - if (parser.seenval('A')) { - if (ftMotion.cfg.modeHasShaper()) { - const float val = parser.value_float(); - // TODO: Frequency minimum is dependent on the shaper used; the above check isn't always correct. - if (WITHIN(val, FTM_MIN_SHAPE_FREQ, (FTM_FS) / 2)) { - ftMotion.cfg.baseFreq[X_AXIS] = val; - flag.update_n = flag.reset_ft = flag.report_h = true; - } - else // Frequency out of range. - SERIAL_ECHOLNPGM("Invalid [", C('A'), "] frequency value."); - } - else // Mode doesn't use frequency. - SERIAL_ECHOLNPGM("Wrong mode for [", C('A'), "] frequency."); - } + if (apply_xy || parser.seen_test('X')) { - #if HAS_DYNAMIC_FREQ - // Parse frequency scaling parameter (X axis). - if (parser.seenval('F')) { - if (modeUsesDynFreq) { - ftMotion.cfg.dynFreqK[X_AXIS] = parser.value_float(); - flag.report_h = true; + // Set shaper if given + if (seenC) set_shaper(X_AXIS, shaperVal); + + // Parse X frequency parameter + if (seenA) { + if (AXIS_IS_SHAPING(X)) { + // TODO: Frequency minimum is dependent on the shaper used; the above check isn't always correct. + if (goodBaseFreq && c.setBaseFreq(X_AXIS, baseFreqVal)) + flag.update = flag.report = true; } - else - SERIAL_ECHOLNPGM("Wrong mode for [", C('F'), "] frequency scaling."); + else // Mode doesn't use frequency. + SERIAL_ECHOLNPGM("?Wrong mode for ", C(STEPPER_A_NAME), " (A) frequency."); } - #endif - // Parse zeta parameter (X axis). - if (parser.seenval('I')) { - const float val = parser.value_float(); - if (ftMotion.cfg.modeHasShaper()) { - if (WITHIN(val, 0.01f, 1.0f)) { - ftMotion.cfg.zeta[0] = val; - flag.update_n = flag.update_a = true; + #if HAS_DYNAMIC_FREQ + // Parse X frequency scaling parameter + if (seenF && c.setDynFreqK(X_AXIS, baseDynFreqVal)) + flag.report = true; + #endif + + // Parse X zeta parameter + if (seenI) { + if (AXIS_IS_SHAPING(X)) { + if (goodZeta && c.setZeta(X_AXIS, zetaVal)) + flag.update = true; } else - SERIAL_ECHOLNPGM("Invalid X zeta [", C('I'), "] value."); // Zeta out of range. + SERIAL_ECHOLNPGM("?Wrong mode for ", C(STEPPER_A_NAME), " (I) zeta parameter."); } - else - SERIAL_ECHOLNPGM("Wrong mode for zeta parameter."); - } - // Parse vtol parameter (X axis). - if (parser.seenval('Q')) { - const float val = parser.value_float(); - if (ftMotion.cfg.modeHasShaper() && IS_EI_MODE(ftMotion.cfg.mode)) { - if (WITHIN(val, 0.00f, 1.0f)) { - ftMotion.cfg.vtol[0] = val; - flag.update_a = true; + #if HAS_FTM_EI_SHAPING + // Parse X vtol parameter + if (seenQ) { + if (AXIS_IS_EISHAPING(X)) { + if (goodVtol && c.setVtol(X_AXIS, vtolVal)) + flag.update = true; + } + else + SERIAL_ECHOLNPGM("?Wrong mode for ", C(STEPPER_A_NAME), " (Q) vtol parameter."); } - else - SERIAL_ECHOLNPGM("Invalid X vtol [", C('Q'), "] value."); // VTol out of range. - } - else - SERIAL_ECHOLNPGM("Wrong mode for vtol parameter."); + #endif } #endif // HAS_X_AXIS #if HAS_Y_AXIS - // Parse frequency parameter (Y axis). - if (parser.seenval('B')) { - if (ftMotion.cfg.modeHasShaper()) { - const float val = parser.value_float(); - if (WITHIN(val, FTM_MIN_SHAPE_FREQ, (FTM_FS) / 2)) { - ftMotion.cfg.baseFreq[Y_AXIS] = val; - flag.update_n = flag.reset_ft = flag.report_h = true; + if (apply_xy || parser.seen_test('Y')) { + + // Set shaper if given + if (seenC) set_shaper(Y_AXIS, shaperVal); + + // Parse Y frequency parameter + if (seenA) { + if (AXIS_IS_SHAPING(Y)) { + if (goodBaseFreq && c.setBaseFreq(Y_AXIS, baseFreqVal)) + flag.update = flag.report = true; } - else // Frequency out of range. - SERIAL_ECHOLNPGM("Invalid frequency [", C('B'), "] value."); + else // Mode doesn't use frequency. + SERIAL_ECHOLNPGM("?Wrong mode for ", C(STEPPER_B_NAME), " (A) frequency."); } - else // Mode doesn't use frequency. - SERIAL_ECHOLNPGM("Wrong mode for [", C('B'), "] frequency."); - } - #if HAS_DYNAMIC_FREQ - // Parse frequency scaling parameter (Y axis). - if (parser.seenval('H')) { - if (modeUsesDynFreq) { - ftMotion.cfg.dynFreqK[Y_AXIS] = parser.value_float(); - flag.report_h = true; + #if HAS_DYNAMIC_FREQ + // Parse Y frequency scaling parameter + if (seenF && c.setDynFreqK(Y_AXIS, baseDynFreqVal)) + flag.report = true; + #endif + + // Parse Y zeta parameter + if (seenI) { + if (AXIS_IS_SHAPING(Y)) { + if (goodZeta && c.setZeta(Y_AXIS, zetaVal)) + flag.update = true; } else - SERIAL_ECHOLNPGM("Wrong mode for [", C('H'), "] frequency scaling."); + SERIAL_ECHOLNPGM("?Wrong mode for ", C(STEPPER_B_NAME), " (I) zeta parameter."); } - #endif - // Parse zeta parameter (Y axis). - if (parser.seenval('J')) { - const float val = parser.value_float(); - if (ftMotion.cfg.modeHasShaper()) { - if (WITHIN(val, 0.01f, 1.0f)) { - ftMotion.cfg.zeta[1] = val; - flag.update_n = flag.update_a = true; + #if HAS_FTM_EI_SHAPING + // Parse Y vtol parameter + if (seenQ) { + if (AXIS_IS_EISHAPING(Y)) { + if (goodVtol && c.setVtol(Y_AXIS, vtolVal)) + flag.update = true; + } + else + SERIAL_ECHOLNPGM("?Wrong mode for ", C(STEPPER_B_NAME), " (Q) vtol parameter."); } - else - SERIAL_ECHOLNPGM("Invalid Y zeta [", C('J'), "] value."); // Zeta Out of range - } - else - SERIAL_ECHOLNPGM("Wrong mode for zeta parameter."); + #endif } - // Parse vtol parameter (Y axis). - if (parser.seenval('R')) { - const float val = parser.value_float(); - if (ftMotion.cfg.modeHasShaper() && IS_EI_MODE(ftMotion.cfg.mode)) { - if (WITHIN(val, 0.00f, 1.0f)) { - ftMotion.cfg.vtol[1] = val; - flag.update_a = true; + #endif // HAS_Y_AXIS + + #if ENABLED(FTM_SHAPER_Z) + + if (parser.seen_test('Z')) { + + // Set shaper if given + if (seenC) set_shaper(Z_AXIS, shaperVal); + + // Parse Z frequency parameter + if (seenA) { + if (AXIS_IS_SHAPING(Z)) { + if (goodBaseFreq && c.setBaseFreq(Z_AXIS, baseFreqVal)) + flag.update = flag.report = true; + } + else // Mode doesn't use frequency. + SERIAL_ECHOLNPGM("?Wrong mode for ", C(STEPPER_C_NAME), " (A) frequency."); + } + + #if HAS_DYNAMIC_FREQ + // Parse Z frequency scaling parameter + if (seenF) + SERIAL_ECHOLNPGM("?Wrong axis for (F)requency scaling."); + #endif + + // Parse Z zeta parameter + if (seenI) { + if (AXIS_IS_SHAPING(Z)) { + if (goodZeta && c.setZeta(Z_AXIS, zetaVal)) + flag.update = true; } else - SERIAL_ECHOLNPGM("Invalid Y vtol [", C('R'), "] value."); // VTol out of range. + SERIAL_ECHOLNPGM("?Wrong mode for ", C(STEPPER_C_NAME), " (I) zeta parameter."); } - else - SERIAL_ECHOLNPGM("Wrong mode for vtol parameter."); + + #if HAS_FTM_EI_SHAPING + // Parse Z vtol parameter + if (seenQ) { + if (AXIS_IS_EISHAPING(Z)) { + if (goodVtol && c.setVtol(Z_AXIS, vtolVal)) + flag.update = true; + } + else + SERIAL_ECHOLNPGM("?Wrong mode for ", C(STEPPER_C_NAME), " (Q) vtol parameter."); + } + #endif } - #endif // HAS_Y_AXIS + #endif // FTM_SHAPER_Z - planner.synchronize(); + #if ENABLED(FTM_SHAPER_E) - if (flag.update_n) ftMotion.refreshShapingN(); + if (parser.seen_test('E')) { - if (flag.update_a) ftMotion.updateShapingA(); + // Set shaper if given + if (seenC) set_shaper(E_AXIS, shaperVal); - if (flag.reset_ft) { - stepper.ftMotion_syncPosition(); - ftMotion.reset(); - } + // Parse E frequency parameter + if (seenA) { + if (AXIS_IS_SHAPING(E)) { + if (goodBaseFreq && c.setBaseFreq(E_AXIS, baseFreqVal)) + flag.update = flag.report = true; + } + else // Mode doesn't use frequency. + SERIAL_ECHOLNPGM("?Wrong mode for ", C('E'), " (A) frequency."); + } + + #if HAS_DYNAMIC_FREQ + // Parse E frequency scaling parameter + if (seenF) + SERIAL_ECHOLNPGM("?Wrong axis for (F)requency scaling."); + #endif + + // Parse E zeta parameter + if (seenI) { + if (AXIS_IS_SHAPING(E)) { + if (goodZeta && c.setZeta(E_AXIS, zetaVal)) + flag.update = true; + } + else + SERIAL_ECHOLNPGM("?Wrong mode for ", C('E'), " (I) zeta parameter."); + } + + #if HAS_FTM_EI_SHAPING + // Parse E vtol parameter + if (seenQ) { + if (AXIS_IS_EISHAPING(E)) { + if (goodVtol && c.setVtol(E_AXIS, vtolVal)) + flag.update = true; + } + else + SERIAL_ECHOLNPGM("?Wrong mode for ", C('E'), " (Q) vtol parameter."); + } + #endif + } + + #endif // FTM_SHAPER_E + + if (flag.update || flag.report) + ui.refresh(); - if (flag.report_h) say_shaping(); + if (flag.report) say_shaping(); } #endif // FT_MOTION diff --git a/Marlin/src/gcode/feature/ft_motion/M494.cpp b/Marlin/src/gcode/feature/ft_motion/M494.cpp new file mode 100644 index 000000000000..0c0250f6a6a4 --- /dev/null +++ b/Marlin/src/gcode/feature/ft_motion/M494.cpp @@ -0,0 +1,168 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2025 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 . + * + */ +#include "../../../inc/MarlinConfigPre.h" + +#if ENABLED(FT_MOTION) + +#include "../../gcode.h" +#include "../../../module/ft_motion.h" +#include "../../../module/stepper.h" +#include "../../../module/planner.h" +#include "../../../lcd/marlinui.h" + +void say_ftm_settings() { + #if ANY(FTM_POLYS, FTM_SMOOTHING, FTM_CONSTANT_JOLT) + const ft_config_t &c = ftMotion.cfg; + #endif + + #if HAS_FTM_TRAJECTORY_SELECTION + SERIAL_ECHOLN(F(" Trajectory: "), ftMotion.getTrajectoryName(), C('('), (uint8_t)ftMotion.getTrajectoryType(), C(')')); + #if ENABLED(FTM_POLYS) + if (ftMotion.getTrajectoryType() == TrajectoryType::POLY6) + SERIAL_ECHOLNPGM(" Poly6 Overshoot: ", p_float_t(c.poly6_acceleration_overshoot, 3)); + #endif + #if ENABLED(FTM_CONSTANT_JOLT) + if (ftMotion.getTrajectoryType() == TrajectoryType::CONSTANT_JOLT) + SERIAL_ECHOLNPGM(" Jolt: ", p_float_t(c.jolt / 1000.0f, 1), " m/s^3"); + #endif + #endif + + #if ENABLED(FTM_SMOOTHING) + #define _SMOO_REPORT(A) SERIAL_ECHOLN(F(" "), C(IAXIS_CHAR(_AXIS(A))), F(" smoothing time: "), p_float_t(c.smoothingTime.A, 3), C('s')); + CARTES_MAP(_SMOO_REPORT); + #endif +} + +void GcodeSuite::M494_report(const bool forReplay/*=true*/) { + TERN_(MARLIN_SMALL_BUILD, return); + + report_heading_etc(forReplay, F("FT Motion")); + SERIAL_ECHOPGM(" M494 T", (uint8_t)ftMotion.getTrajectoryType()); + + #if ANY(FTM_POLYS, FTM_SMOOTHING, FTM_CONSTANT_JOLT) + const ft_config_t &c = ftMotion.cfg; + #endif + + #if ENABLED(FTM_SMOOTHING) + SERIAL_ECHOPGM(CARTES_PAIRED_LIST( + " X", c.smoothingTime.X, + " Y", c.smoothingTime.Y, + " Z", c.smoothingTime.Z, + " E", c.smoothingTime.E + )); + #endif + + #if ENABLED(FTM_POLYS) + if (ftMotion.getTrajectoryType() == TrajectoryType::POLY6) + SERIAL_ECHOPGM(" O", c.poly6_acceleration_overshoot); + #endif + + #if ENABLED(FTM_CONSTANT_JOLT) + if (ftMotion.getTrajectoryType() == TrajectoryType::CONSTANT_JOLT) + SERIAL_ECHOPGM(" J", c.jolt / 1000.0f); + #endif + + SERIAL_EOL(); +} + +/** + * M494: Set Fixed-time Motion Control parameters + * + * Parameters: + * T Set trajectory generator type (0=TRAPEZOIDAL, 1=POLY5, 2=POLY6, 3=CONSTANT_JOLT) + * O Set acceleration overshoot for POLY6 (1.25-1.875) + * J Set maximum jolt for CONSTANT_JOLT (m/s³, positive) + * X