diff --git a/.aiderignore b/.aiderignore new file mode 100644 index 0000000000..b80c459446 --- /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/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000000..4f29a10053 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,29 @@ +# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.187.0/containers/python-3/.devcontainer/base.Dockerfile + +# [Choice] Python version: 3, 3.9, 3.8, 3.7, 3.6 +ARG VARIANT="3.9.0-buster" +FROM python:${VARIANT} + +# [Option] Install Node.js +ARG INSTALL_NODE="true" +ARG NODE_VERSION="lts/*" +RUN if [ "${INSTALL_NODE}" = "true" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi + +# [Optional] If your pip requirements rarely change, uncomment this section to add them to the image. +# COPY requirements.txt /tmp/pip-tmp/ +# RUN pip3 --disable-pip-version-check --no-cache-dir install -r /tmp/pip-tmp/requirements.txt \ +# && rm -rf /tmp/pip-tmp + +# [Optional] Uncomment this section to install additional OS packages. +# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ +# && apt-get -y install --no-install-recommends + +# [Optional] Uncomment this line to install global node packages. +# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g " 2>&1 + + +RUN pip install -U https://github.com/platformio/platformio-core/archive/develop.zip +RUN platformio update +# To get the test platforms +RUN pip install PyYaml +#ENV PATH /code/buildroot/bin/:/code/buildroot/tests/:${PATH} diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000000..96d6af1932 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,51 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: +// https://github.com/microsoft/vscode-dev-containers/tree/v0.187.0/containers/python-3 +{ + "name": "Python 3", + "build": { + "dockerfile": "Dockerfile", + "context": "..", + "args": { + // Update 'VARIANT' to pick a Python version: 3, 3.6, 3.7, 3.8, 3.9 + "VARIANT": "3.9.0-buster", + // Options + "INSTALL_NODE": "false", + "NODE_VERSION": "lts/*" + } + }, + + // Set *default* container specific settings.json values on container create. + "settings": { + "python.pythonPath": "/usr/local/bin/python", + "python.languageServer": "Pylance", + "python.linting.enabled": true, + "python.linting.pylintEnabled": true, + "python.formatting.autopep8Path": "/usr/local/py-utils/bin/autopep8", + "python.formatting.blackPath": "/usr/local/py-utils/bin/black", + "python.formatting.yapfPath": "/usr/local/py-utils/bin/yapf", + "python.linting.banditPath": "/usr/local/py-utils/bin/bandit", + "python.linting.flake8Path": "/usr/local/py-utils/bin/flake8", + "python.linting.mypyPath": "/usr/local/py-utils/bin/mypy", + "python.linting.pycodestylePath": "/usr/local/py-utils/bin/pycodestyle", + "python.linting.pydocstylePath": "/usr/local/py-utils/bin/pydocstyle", + "python.linting.pylintPath": "/usr/local/py-utils/bin/pylint" + }, + + // Add the IDs of extensions you want installed when the container is created. + "extensions": [ + "ms-python.python", + "ms-python.vscode-pylance", + "platformio.platformio-ide", + "marlinfirmware.auto-build", + "editorconfig.editorconfig" + ] + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // , "forwardPorts": [] + + // Use 'postCreateCommand' to run commands after the container is created. + // , "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" +} diff --git a/.editorconfig b/.editorconfig index a0fa3eff17..1037e74ef3 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,19 +1,33 @@ # editorconfig.org root = true +[*] +trim_trailing_whitespace = true +insert_final_newline = true + [{*.patch,syntax_test_*}] trim_trailing_whitespace = false -[{*.c,*.cpp,*.h}] -charset = utf-8 - -[{*.c,*.cpp,*.h,Makefile}] -trim_trailing_whitespace = true -insert_final_newline = true +[{*.c,*.cpp,*.h,*.ino,*.py,Makefile}] end_of_line = lf + +[{*.c,*.cpp,*.h,*.ino}] +charset = utf-8 indent_style = space indent_size = 2 -[{*.py,*.conf,*.sublime-project}] +[{Makefile}] +indent_style = tab +indent_size = 2 + +[*.md] +# Two spaces at the end of the line means newline in Markdown +trim_trailing_whitespace = false + +[{*.py}] +indent_style = space +indent_size = 4 + +[{*.conf,*.sublime-project}] indent_style = tab indent_size = 4 diff --git a/.gitattributes b/.gitattributes index 2588229e05..83897cba6e 100644 --- a/.gitattributes +++ b/.gitattributes @@ -17,3 +17,5 @@ *.png binary *.jpg binary *.fon binary +*.bin binary +*.woff binary diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 48beca2c2d..0000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -name: Bug report -about: Report a bug in Marlin -title: "[BUG] (short description)" -labels: '' -assignees: '' - ---- - - - -### Bug Description - - - -### My Configurations - -**Required:** Please include a ZIP file containing your `Configuration.h` and `Configuration_adv.h` files. - -### Steps to Reproduce - - - -1. [First Step] -2. [Second Step] -3. [and so on...] - -**Expected behavior:** [What you expect to happen] - -**Actual behavior:** [What actually happens] - -#### Additional Information - -* Provide pictures or links to videos that clearly demonstrate the issue. -* See [How Can I Contribute](#how-can-i-contribute) for additional guidelines. diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000000..e0ee13af07 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,184 @@ +name: πŸͺ² Report a bug +description: Create a bug report to help improve Marlin Firmware +title: "[BUG] (bug summary)" +labels: ["Bug: Potential ?"] +body: + - type: markdown + attributes: + value: > + Do you want to ask a question? Are you looking for support? Please use one of the [support links](https://github.com/MarlinFirmware/Marlin/issues/new/choose). + + - type: markdown + attributes: + value: | + **Thank you for reporting a bug in Marlin Firmware!** + + ## Before Reporting a Bug + + - Read and understand Marlin's [Code of Conduct](https://github.com/MarlinFirmware/Marlin/blob/bugfix-2.1.x/.github/code_of_conduct.md). You are expected to comply with it, including treating everyone with respect. + + - Test with the [`bugfix-2.1.x` branch](https://github.com/MarlinFirmware/Marlin/archive/bugfix-2.1.x.zip) to see whether the issue still exists. + + ## Instructions + + Please follow the instructions below. Failure to do so may result in your issue being closed. See [Contributing to Marlin](https://github.com/MarlinFirmware/Marlin/blob/bugfix-2.1.x/.github/contributing.md) for additional guidelines. + + 1. Provide a good title starting with [BUG]. + 2. Fill out all sections of this bug report form. + 3. Always attach configuration files so we can build and test your setup. + + - type: dropdown + attributes: + label: Did you test the latest `bugfix-2.1.x` code? + description: >- + Always try the latest code to make sure the issue you are reporting is not already fixed. To download + the latest code just [click this link](https://github.com/MarlinFirmware/Marlin/archive/bugfix-2.1.x.zip). + options: + - Yes, and the problem still exists. + - No, but I will test it now! + validations: + required: true + + - type: markdown + attributes: + value: | + # Bug Details + + - type: textarea + attributes: + label: Bug Description + description: >- + Describe the bug in this section. Tell us what you were trying to do and what + happened that you did not expect. Provide a clear and concise description of the + problem and include as many details as possible. + + When pasting formatted text don't forget to put ` ``` ` (on its own line) before and after to make it readable. + placeholder: | + Marlin doesn't work. + validations: + required: true + + - type: input + attributes: + label: Bug Timeline + description: Is this a new bug or an old issue? When did it first start? + + - type: textarea + attributes: + label: Expected behavior + description: >- + What did you expect to happen? + placeholder: I expected it to move left. + + - type: textarea + attributes: + label: Actual behavior + description: What actually happened instead? + placeholder: It moved right instead of left. + + - type: textarea + attributes: + label: Steps to Reproduce + description: >- + Please describe the steps needed to reproduce the issue. + placeholder: | + 1. [First Step] ... + 2. [Second Step] ... + 3. [and so on] ... + + - type: markdown + attributes: + value: | + # Your Setup + + - type: input + attributes: + label: Version of Marlin Firmware + description: "See the About Menu on the LCD or the output of `M115`. NOTE: For older releases we only patch critical bugs." + validations: + required: true + + - type: input + attributes: + label: Printer model + description: Creality Ender-3, Prusa mini, or Kossel Delta? + + - type: input + attributes: + label: Electronics + description: Stock electronics, upgrade board, or something else? + + - type: input + attributes: + label: LCD/Controller + description: Some Marlin behaviors are determined by the controller. Describe your LCD/Controller model and version. + + - type: input + attributes: + label: Other add-ons + description: Please list any other hardware add-ons that could be involved. + + - type: dropdown + attributes: + label: Bed Leveling + description: What kind of bed leveling compensation are you using? + options: + - UBL Bilinear mesh + - ABL Bilinear mesh + - ABL Linear grid + - ABL 3-point + - MBL Manual Bed Leveling + - No Bed Leveling + + - type: dropdown + attributes: + label: Your Slicer + description: Do you use Slic3r, Prusa Slicer, Simplify3D, IdeaMaker...? + options: + - Slic3r + - Simplify3D + - Prusa Slicer + - IdeaMaker + - Cura + - Other (explain below) + + - type: dropdown + attributes: + label: Host Software + description: Do you use OctoPrint, Repetier Host, Pronterface...? + options: + - SD Card (headless) + - Repetier Host + - OctoPrint + - Pronterface + - Cura + - Same as my slicer + - Other (explain below) + + - type: markdown + attributes: + value: | + # Attachments + + - type: checkboxes + attributes: + label: Don't forget to include + options: + - label: A ZIP file containing your `Configuration.h` and `Configuration_adv.h`. + required: true + + - type: markdown + attributes: + value: | + ### Optional items to include: + - 'Log output from the host. (`M111 S247` for maximum logging.)' + - Images or videos demonstrating the problem, if it helps to make it clear. + - A G-Code file that exposes the problem, if not affecting _all_ G-code. + + - type: textarea + attributes: + label: Additional information & file uploads + description: >- + If you've made any other modifications to the firmware, please describe them in detail. + + When pasting formatted text don't forget to put ` ``` ` (on its own line) before and after to make it readable. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 4b283d2600..a22a1bb82e 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,17 +1,20 @@ blank_issues_enabled: false contact_links: - - name: Marlin Documentation - url: http://marlinfw.org/ + - name: πŸ“– Marlin Documentation + url: https://marlinfw.org/ about: Lots of documentation on installing and using Marlin. - - name: MarlinFirmware Facebook group + - name: πŸ‘€ MarlinFirmware Facebook group url: https://www.facebook.com/groups/1049718498464482 about: Please ask and answer questions here. - - name: Marlin on Discord - url: https://discord.gg/n5NJ59y + - name: πŸ•Ή Marlin on Discord + url: https://discord.com/servers/marlin-firmware-461605380783472640 about: Join the Discord server for support and discussion. - - name: Marlin Discussion Forum - url: http://forums.reprap.org/list.php?415 + - name: πŸ”— Marlin Discussion Forum + url: https://reprap.org/forum/list.php?415 about: A searchable web forum hosted by RepRap dot org. - - name: Marlin Videos on YouTube + - name: πŸ“Ί Marlin Videos on YouTube url: https://www.youtube.com/results?search_query=marlin+firmware about: Tutorials and more from Marlin users all around the world. Great for new users! + - name: πŸ’Έ Want to donate? + url: https://www.thinkyhead.com/donate-to-marlin + about: Please take a look at the various options to support Marlin Firmware's development financially! diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 24de8d0141..0000000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -name: Feature request -about: Request a Feature -title: "[FR] (feature request title)" -labels: 'T: Feature Request' -assignees: '' - ---- - - - -### Description - - - -### Feature Workflow - - - -1. [First Action] -2. [Second Action] -3. [and so on...] - -#### Additional Information - -* Provide pictures or links that demonstrate a similar feature or concept. -* See [How Can I Contribute](#how-can-i-contribute) for additional guidelines. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000000..2e8607142c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,44 @@ +name: ✨ Request a feature +description: Request a new Marlin Firmware feature +title: "[FR] (feature summary)" +labels: ["T: Feature Request"] +body: + - type: markdown + attributes: + value: > + Do you want to ask a question? Are you looking for support? Please use one of the [support links](https://github.com/MarlinFirmware/Marlin/issues/new/choose). + + - type: markdown + attributes: + value: > + **Thank you for requesting a new Marlin Firmware feature!** + + ## Before Requesting a Feature + + - Read and understand Marlin's [Code of Conduct](https://github.com/MarlinFirmware/Marlin/blob/master/.github/code_of_conduct.md). You are expected to comply with it, including treating everyone with respect. + + - Check the latest [`bugfix-2.1.x` branch](https://github.com/MarlinFirmware/Marlin/archive/bugfix-2.1.x.zip) to see if the feature already exists. + + - Before you proceed with your request, please consider if it is necessary to make it into a firmware feature, or if it may be better suited for a slicer or host feature. + + - type: textarea + attributes: + label: Is your feature request related to a problem? Please describe. + description: A clear description of the problem (e.g., "I need X but Marlin can't do it [...]"). + + - type: textarea + attributes: + label: Are you looking for hardware support? + description: Tell us the printer, board, or peripheral that needs support. + + - type: textarea + attributes: + label: Describe the feature you want + description: A clear description of the feature and how you think it should work. + validations: + required: true + + - type: textarea + attributes: + label: Additional context + description: Add any other context or screenshots about the feature request here. diff --git a/.github/code_of_conduct.md b/.github/code_of_conduct.md index 854fed4ec4..5fd9e0c8ee 100644 --- a/.github/code_of_conduct.md +++ b/.github/code_of_conduct.md @@ -28,15 +28,9 @@ Project maintainers are responsible for clarifying the standards of acceptable b Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. -## Scope - -This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. - ## Enforcement -Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [marlinfirmware@github.com](mailto:marlinfirmware@github.com). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by following GitHub's [reporting abuse or spam article](https://docs.github.com/en/communities/maintaining-your-safety-on-github/reporting-abuse-or-spam). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. ## Attribution diff --git a/.github/contributing.md b/.github/contributing.md index 6bc7b5a005..bbf41ea1c8 100644 --- a/.github/contributing.md +++ b/.github/contributing.md @@ -26,20 +26,24 @@ The following is a set of guidelines for contributing to Marlin, hosted by the [ ## Code of Conduct -This project and everyone participating in it is governed by the [Marlin Code of Conduct](code_of_conduct.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to [marlinfirmware@github.com](mailto:marlinfirmware@github.com). +This project and everyone participating in it is governed by the [Marlin Code of Conduct](code_of_conduct.md). By participating, you are expected to uphold this code. Please report unacceptable behavior by following GitHub's [reporting abuse or spam article](https://docs.github.com/en/communities/maintaining-your-safety-on-github/reporting-abuse-or-spam). ## 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. +> [!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. -* [Marlin RepRap forum](https://reprap.org/forum/list.php?415) -* [MarlinFirmware on Facebook](https://www.facebook.com/groups/1049718498464482/) +- [Marlin Documentation](https://marlinfw.org) - Official Marlin documentation +- Facebook Group ["Marlin Firmware"](https://www.facebook.com/groups/1049718498464482/) +- RepRap.org [Marlin Forum](https://forums.reprap.org/list.php?415) +- Facebook Group ["Marlin Firmware for 3D Printers"](https://www.facebook.com/groups/3Dtechtalk/) +- [Marlin Configuration](https://www.youtube.com/results?search_query=marlin+configuration) on YouTube If chat is more your speed, you can join the MarlinFirmware Discord server: -* Use the link https://discord.gg/n5NJ59y to join up as a General User. +* Use the link https://discord.com/servers/marlin-firmware-461605380783472640 to join up as a General User. * Even though our Discord is pretty active, it may take a while for community members to respond — please be patient! * Use the `#general` channel for general questions or discussion about Marlin. * Other channels exist for certain topics or are limited to Patrons. Check the channel list. @@ -50,13 +54,14 @@ If chat is more your speed, you can join the MarlinFirmware Discord server: This section guides you through submitting a Bug Report for Marlin. Following these guidelines helps maintainers and the community understand your report, reproduce the behavior, and find related reports. -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.md), the information it asks for helps us resolve issues faster. +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. +> [!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? -Bugs are tracked as [GitHub issues](https://guides.github.com/features/issues/). Use the New Issue button to create an issue and provide the following information by filling in [the template](issue_template.md). +Bugs are tracked as [GitHub issues](https://guides.github.com/features/issues/). Use the New Issue button to create an issue and provide the following information by filling in [the template](ISSUE_TEMPLATE/bug_report.yml). Explain the problem and include additional details to help maintainers reproduce the problem: @@ -88,12 +93,12 @@ Include details about your configuration and environment: This section guides you through submitting a suggestion for Marlin, including completely new features and minor improvements to existing functionality. Following these guidelines helps maintainers and the community understand your suggestion and find related suggestions. -Before creating a suggestion, please check [this list](#before-submitting-a-suggestion) as you might find out that you don't need to create one. When you are creating an enhancement suggestion, please [include as many details as possible](#how-do-i-submit-a-good-enhancement-suggestion). Fill in [the template](issue_template.md), including the steps that you imagine you would take if the feature you're requesting existed. +Before creating a suggestion, please check [this list](https://github.com/MarlinFirmware/Marlin/issues?q=is%3Aopen+is%3Aissue+label%3A%22T%3A+Feature+Request%22) as you might find out that you don't need to create one. When you are creating an enhancement suggestion, please [include as many details as possible](#how-do-i-submit-a-good-feature-request). Fill in [the template](ISSUE_TEMPLATE/feature_request.yml), including the steps that you imagine you would take if the feature you're requesting existed. #### 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%3Aissue)** 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. +* **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? @@ -116,7 +121,7 @@ Unsure where to begin contributing to Marlin? You can start by looking through t ### Pull Requests -Pull Requests should always be targeted to working branches (e.g., `bugfix-1.1.x` and/or `bugfix-2.0.x`) and never to release branches (e.g., `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. diff --git a/.github/issue_template.md b/.github/issue_template.md deleted file mode 100644 index a211ca5e27..0000000000 --- a/.github/issue_template.md +++ /dev/null @@ -1,11 +0,0 @@ -# NO SUPPORT REQUESTS PLEASE - -Do you want to ask a question? Are you looking for support? Please don't post here. Support Requests posted here will be automatically closed! - -Instead use one of the following options: - -- The Marlin Firmware forum at https://reprap.org/forum/list.php?415 -- The MarlinFirmware Facebook Group at https://www.facebook.com/groups/1049718498464482/ -- The MarlinFirmware Discord Server at https://discord.gg/n5NJ59y. - -Before filing an issue be sure to test the latest "bugfix" branch to see whether the issue is already addressed. diff --git a/.github/lock.yml b/.github/lock.yml deleted file mode 100644 index c5ceff66b0..0000000000 --- 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/pull_request_template.md b/.github/pull_request_template.md index d82fb0f9e3..cd5158b3ce 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,23 +1,33 @@ -### Requirements + ### Description +### Requirements + + + ### Benefits - + ### Configurations - + ### Related Issues - + diff --git a/.github/workflows/auto-label.yml b/.github/workflows/auto-label.yml new file mode 100644 index 0000000000..3cbf110aa0 --- /dev/null +++ b/.github/workflows/auto-label.yml @@ -0,0 +1,41 @@ +# +# auto-label.yml +# - Find all open issues without a label and a title containing "[BUG]". +# - Apply the label "Bug: Potential ?" to these issues. +# + +name: Label Old Bugs + +on: + schedule: + - cron: "30 8 * * *" + +jobs: + autolabel: + name: Auto Label + if: github.repository == 'MarlinFirmware/Marlin' + runs-on: ubuntu-22.04 + steps: + - name: Auto Label for [BUG] + uses: actions/github-script@v7 + with: + script: | + // Get all open issues in this repository + const issueList = await github.rest.issues.listForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open' + }); + // Filter issues without labels that have a title containing '[BUG]'. + const matchingIssues = issueList.data.filter( + issue => issue.title.includes('[BUG]') && issue.labels.length === 0 + ); + // Process the first 50 + for (const issue of matchingIssues.slice(0, 50)) { + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + labels: ['Bug: Potential ?'] + }); + } diff --git a/.github/workflows/bump-date.yml b/.github/workflows/bump-date.yml index 54902da8c9..cb27ef199e 100644 --- a/.github/workflows/bump-date.yml +++ b/.github/workflows/bump-date.yml @@ -7,29 +7,53 @@ name: Bump Distribution Date on: schedule: - - cron: '0 0 * * *' + - cron: '0 */6 * * *' jobs: bump_date: 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@v2 + uses: actions/checkout@v4 with: ref: bugfix-2.0.x - - name: Bump Distribution Date + - name: Bump Date (bugfix-2.0.x) run: | # Inline Bump Script - DIST=$( date +"%Y-%m-%d" ) - eval "sed -E -i 's/(#define +STRING_DISTRIBUTION_DATE) .*$/\1 \"$DIST\"/g' Marlin/src/inc/Version.h" && \ - git config user.name "${GITHUB_ACTOR}" && \ - git config user.email "${GITHUB_ACTOR}@users.noreply.github.com" && \ - git add . && \ - git commit -m "[cron] Bump distribution date ($DIST)" && \ - git push + if [[ ! "$( git log -1 --pretty=%B )" =~ ^\[cron\] ]]; then + DIST=$( date +"%Y-%m-%d" ) + eval "sed -E -i 's/(#define +STRING_DISTRIBUTION_DATE) .*$/\1 \"$DIST\"/g' Marlin/src/inc/Version.h" && \ + eval "sed -E -i 's/(#define +STRING_DISTRIBUTION_DATE) .*$/\1 \"$DIST\"/g' Marlin/Version.h" && \ + git config user.name "${GITHUB_ACTOR}" && \ + git config user.email "${GITHUB_ACTOR}@users.noreply.github.com" && \ + git add . && \ + git commit -m "[cron] Bump distribution date ($DIST)" && \ + git push + fi + exit 0 + + - name: Check out bugfix-2.1.x + uses: actions/checkout@v4 + with: + ref: bugfix-2.1.x + + - name: Bump Date (bugfix-2.1.x) + run: | + # Inline Bump Script + if [[ ! "$( git log -1 --pretty=%B )" =~ ^\[cron\] ]]; then + DIST=$( date +"%Y-%m-%d" ) + eval "sed -E -i 's/(#define +STRING_DISTRIBUTION_DATE) .*$/\1 \"$DIST\"/g' Marlin/src/inc/Version.h" && \ + eval "sed -E -i 's/(#define +STRING_DISTRIBUTION_DATE) .*$/\1 \"$DIST\"/g' Marlin/Version.h" && \ + git config user.name "${GITHUB_ACTOR}" && \ + git config user.email "${GITHUB_ACTOR}@users.noreply.github.com" && \ + git add . && \ + git commit -m "[cron] Bump distribution date ($DIST)" && \ + git push + fi + exit 0 diff --git a/.github/workflows/check-pr.yml b/.github/workflows/check-pr.yml index aa4a2c59c9..a28b7fdd62 100644 --- a/.github/workflows/check-pr.yml +++ b/.github/workflows/check-pr.yml @@ -6,7 +6,8 @@ name: PR Bad Target on: - pull_request: + pull_request_target: + types: [opened] branches: - 1.0.x - 1.1.x @@ -17,17 +18,16 @@ jobs: name: PR Bad Target if: github.repository == 'MarlinFirmware/Marlin' - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - - uses: peter-evans/close-pull@v1 + - uses: superbrothers/close-pull-request@v3 with: - delete-branch: false comment: > Thanks for your contribution! Unfortunately we can't accept PRs directed at release branches. We make patches to the bugfix branches and only later do we push them out as releases. - Please redo this PR starting with the `bugfix-2.0.x` branch and be careful to target `bugfix-2.0.x` when resubmitting the PR. + Please redo this PR starting with the `bugfix-2.1.x` branch and be careful to target `bugfix-2.1.x` when resubmitting the PR. Patches may also target `bugfix-2.0.x` if they are specifically for 2.0.9.x. - It may help to set your fork's default branch to `bugfix-2.0.x`. + It may help to set your fork's default branch to `bugfix-2.1.x`. - See [this page](http://marlinfw.org/docs/development/getting_started_pull_requests.html) for full instructions. + See [this page](https://marlinfw.org/docs/development/getting_started_pull_requests.html) for full instructions. diff --git a/.github/workflows/ci-build-tests.yml b/.github/workflows/ci-build-tests.yml new file mode 100644 index 0000000000..5d53536a27 --- /dev/null +++ b/.github/workflows/ci-build-tests.yml @@ -0,0 +1,214 @@ +# +# ci-build-tests.yml +# Do test builds to catch compile errors +# + +name: CI - Build Tests + +on: + pull_request: + branches: + - bugfix-2.1.x + - 2.1.x + paths-ignore: + - 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: Build Test + if: github.repository == 'MarlinFirmware/Marlin' + + 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 + - mega1280 + - at90usb1286_dfu + + # AVR Extended + - mega2560ext + - melzi_optiboot + - rambo + - sanguino1284p + - sanguino644p + + # SAM3X8E + - DUE + - DUE_archim + + # SAMD21 + - SAMD51_grandcentral_m4 + - SAMD21_minitronics20 + + # ESP32 + - esp32 + - mks_tinybee + + # Teensy 2 + #- at90usb1286_cdc + + # Teensy MK20DX256 + - teensy31 + + # Teensy MK64FX512, MK66FX1M0 + - teensy35 + + # Teensy IMXRT1062DVx6A + - teensy41 + + # STM32F0 + - malyan_M300 + - STM32F070CB_malyan + - STM32F070RB_malyan + + # STM32F1 + - chitu_f103 + - mks_robin + - mks_robin_nano_v1v2 + - PANDA_PI_V29 + - STM32F103RC_btt + - STM32F103RC_fysetc + - STM32F103RE_btt + - STM32F103RE_btt_USB + - STM32F103RE_creality + - STM32F103VE_longer + #- mks_robin_mini + #- mks_robin_nano_v1_3_f4_usbmod + #- mks_robin_nano_v1v2_usbmod + #- STM32F103CB_malyan + #- STM32F103RC_btt_USB + #- STM32F103RE + + # STM32F4 + - ARMED + - BTT_BTT002 + - BTT_GTR_V1_0 + - BTT_SKR_PRO + - FLYF407ZG + - STM32F446VE_fysetc + - LERDGEK + - LERDGEX + - mks_robin_pro2 + - Opulo_Lumen_REV3 + - rumba32 + - STM32F401RC_creality + - STM32F407VE_black + - I3DBEEZ9_V1 + + # STM32F7 + - NUCLEO_F767ZI + - REMRAM_V1 + + # STM32H7 + - BTT_SKR_SE_BX + - STM32H743VI_btt + + # STM32F1 (Maple) + - jgaurora_a5s_a1_maple + - mks_robin_lite_maple + - mks_robin_pro_maple + - STM32F103RC_btt_USB_maple + - STM32F103RC_fysetc_maple + - STM32F103RC_meeb_maple + - STM32F103VE_longer_maple + - STM32F103VE_ZM3E4V2_USB_maple + #- mks_robin_maple + #- mks_robin_nano_v1v2_maple + #- STM32F103RC_btt_maple + #- STM32F103RE_creality_maple + + # STM32G0 + - STM32G0B1RE_btt + + # HC32 + - HC32F460C_aquila_101 + + # GD32F3 + - GD32F303RE_creality_mfl + + # GD32F1 + - GD32F103RC_aquila_mfl + + # LPC176x - Lengthy tests + - LPC1768 + - LPC1769 + + 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-build-v1 + restore-keys: | + ${{ runner.os }}-pip-build- + + - name: Cache PlatformIO + uses: actions/cache@v4 + with: + 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@v5 + with: + python-version: '3.9' + architecture: 'x64' + + - name: Install PlatformIO + run: | + pip install -U platformio + 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 0000000000..30af812dff --- /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 0000000000..aa6d284f06 --- /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 0000000000..367db12e6c --- /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 0000000000..2086fea37a --- /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 new file mode 100644 index 0000000000..ad0ee8ea20 --- /dev/null +++ b/.github/workflows/clean-closed.yml @@ -0,0 +1,40 @@ +# +# clean-closed.yml +# Remove obsolete labels when an Issue or PR is closed +# + +name: Clean Closed + +on: + pull_request: + types: [closed] + issues: + types: [closed] + +jobs: + remove_label: + runs-on: ubuntu-22.04 + + strategy: + matrix: + label: + - "S: Don't Merge" + - "S: Hold for 2.1" + - "S: Please Merge" + - "S: Please Test" + - "help wanted" + - "Bug: Potential ?" + - "Needs: Discussion" + - "Needs: Documentation" + - "Needs: More Data" + - "Needs: Patch" + - "Needs: Testing" + - "Needs: Work" + + steps: + - uses: actions/checkout@v4 + - name: Remove Labels + uses: actions-ecosystem/action-remove-labels@v1 + with: + github_token: ${{ github.token }} + labels: ${{ matrix.label }} diff --git a/.github/workflows/close-stale.yml b/.github/workflows/close-stale.yml index 89d5d65351..4bb488c56f 100644 --- a/.github/workflows/close-stale.yml +++ b/.github/workflows/close-stale.yml @@ -14,14 +14,27 @@ jobs: name: Close Stale Issues if: github.repository == 'MarlinFirmware/Marlin' - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - - uses: actions/stale@v3 + - uses: actions/stale@v9 with: repo-token: ${{ secrets.GITHUB_TOKEN }} - stale-issue-message: 'This issue has had no activity in the last 30 days. Please add a reply if you want to keep this issue active, otherwise it will be automatically closed within 7 days.' - days-before-stale: 30 - days-before-close: 7 + stale-issue-message: | + Greetings from the Marlin AutoBot! + This issue has had no activity for the last 90 days. + Do you still see this issue with the latest `bugfix-2.1.x` code? + Please add a reply within 14 days or this issue will be automatically closed. + To keep a confirmed issue open we can also add a "Bug: Confirmed" tag. + + Disclaimer: This is an open community project with lots of activity and limited + resources. The main project contributors will do a bug sweep ahead of the next + release, but any skilled member of the community may jump in at any time to fix + this issue. That can take a while depending on our busy lives so please be patient, + and take advantage of other resources such as the MarlinFirmware Discord to help + solve the issue. + days-before-stale: 90 + days-before-close: 14 stale-issue-label: 'stale-closing-soon' - exempt-issue-labels: 'T: Feature Request' + exempt-all-assignees: true + exempt-issue-labels: 'Bug: Confirmed !,T: Feature Request,Needs: More Data,Needs: Discussion,Needs: Documentation,Needs: Patch,Needs: Work,Needs: Testing,help wanted,no-locking' diff --git a/.github/workflows/lock-closed.yml b/.github/workflows/lock-closed.yml index 8cdcd7a836..1435a3ef44 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/test-builds.yml b/.github/workflows/test-builds.yml deleted file mode 100644 index b228783799..0000000000 --- a/.github/workflows/test-builds.yml +++ /dev/null @@ -1,123 +0,0 @@ -# -# test-builds.yml -# Do test builds to catch compile errors -# - -name: CI - -on: - pull_request: - branches: - - bugfix-2.0.x - paths-ignore: - - config/** - - data/** - - docs/** - - '**/*.md' - push: - branches: - - bugfix-2.0.x - paths-ignore: - - config/** - - data/** - - docs/** - - '**/*.md' - -jobs: - test_builds: - name: Run All Tests - if: github.repository == 'MarlinFirmware/Marlin' - - runs-on: ubuntu-latest - - strategy: - matrix: - test-platform: - # Base Environments - - - DUE - - esp32 - - linux_native - - mega2560 - - teensy31 - - teensy35 - - teensy41 - - SAMD51_grandcentral_m4 - - # Extended AVR Environments - - - FYSETC_F6_13 - - mega1280 - - rambo - - sanguino1284p - - sanguino644p - - # Extended STM32 Environments - - - STM32F103RC_btt - - STM32F103RC_btt_USB - - STM32F103RE_btt - - STM32F103RE_btt_USB - - STM32F103RC_fysetc - - STM32F103RC_meeb - - jgaurora_a5s_a1 - - STM32F103VE_longer - - STM32F407VE_black - - STM32F401VE_STEVAL - - BIGTREE_BTT002 - - BIGTREE_SKR_PRO - - BIGTREE_GTR_V1_0 - - mks_robin - - mks_robin_stm32 - - ARMED - - FYSETC_S6 - - STM32F070CB_malyan - - STM32F070RB_malyan - - malyan_M300 - - mks_robin_lite - - FLYF407ZG - - rumba32 - - mks_robin_pro - - STM32F103RET6_creality - - LERDGEX - - mks_robin_nano35 - - # Put lengthy tests last - - - LPC1768 - - LPC1769 - - # STM32 with non-STM framework. both broken for now. they should use HAL_STM32 which is working. - - #- STM32F4 - #- STM32F7 - - # Non-working environment tests - #- at90usb1286_cdc - #- at90usb1286_dfu - #- STM32F103CB_malyan - #- mks_robin_mini - - steps: - - - name: Select Python 3.7 - uses: actions/setup-python@v1 - with: - python-version: '3.7' # Version range or exact version of a Python version to use, using semvers version range syntax. - architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified - - - name: Install PlatformIO - run: | - pip install -U https://github.com/platformio/platformio-core/archive/develop.zip - platformio update - - - name: Check out the PR - uses: actions/checkout@v2 - - - name: Run ${{ matrix.test-platform }} Tests - run: | - # Inline tests script - chmod +x buildroot/bin/* - chmod +x buildroot/tests/* - export PATH=./buildroot/bin/:./buildroot/tests/:${PATH} - run_tests . ${{ matrix.test-platform }} diff --git a/.github/workflows/unlock-reopened.yml b/.github/workflows/unlock-reopened.yml index 614ef3fab2..f88e07cdc3 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 c163d339df..87d7ef47d3 --- a/.gitignore +++ b/.gitignore @@ -19,34 +19,28 @@ # along with this program. If not, see . # -# Our automatic versioning scheme generates the following file -# NEVER put it in the repository +# Generated files _Version.h +bdf2u8g.exe +genpages.exe +marlin_config.json +mczip.h +language*.csv +out-csv/ +out-language/ +*.gen +*.sublime-workspace + +# npm +node_modules/ +package.json +package-lock.json -# # OS -# applet/ -*.DS_Store +.DS_Store -# -# Misc -# -*~ -*.orig -*.rej -*.bak -*.idea -*.s -*.i -*.ii -*.swp -tags - -# -# C++ -# -# Compiled Object files +# Compiled C++ Object files *.slo *.lo *.o @@ -77,11 +71,7 @@ tags *.out *.app - -# -# C -# -# Object files +# Compiled C Object files *.o *.ko *.obj @@ -123,33 +113,10 @@ tags .gcc-flags.json /lib/ -# Workaround for Deviot+platformio quirks -Marlin/lib -Marlin/platformio.ini -Marlin/*/platformio.ini -Marlin/*/*/platformio.ini -Marlin/*/*/*/platformio.ini -Marlin/*/*/*/*/platformio.ini -Marlin/.travis.yml -Marlin/*/.travis.yml -Marlin/*/*/.travis.yml -Marlin/*/*/*/.travis.yml -Marlin/*/*/*/*/.travis.yml -Marlin/.gitignore -Marlin/*/.gitignore -Marlin/*/*/.gitignore -Marlin/*/*/*/.gitignore -Marlin/*/*/*/*/.gitignore -Marlin/readme.txt -Marlin/*/readme.txt -Marlin/*/*/readme.txt -Marlin/*/*/*/readme.txt -Marlin/*/*/*/*/readme.txt - # Secure Credentials Configuration_Secure.h -#Visual Studio +# Visual Studio *.sln *.vcxproj *.vcxproj.user @@ -160,27 +127,53 @@ __vm/ .vs/ vc-fileutils.settings -#Visual Studio Code -.vscode -.vscode/.browse.c_cpp.db* -.vscode/c_cpp_properties.json -.vscode/launch.json -.vscode/*.db +# Visual Studio Code +.vscode/* +!.vscode/extensions.json +*.code-workspace -#cmake +# Simulation files +imgui.ini +eeprom.dat +spi_flash.bin +fs.img + +# CMake +buildroot/share/cmake/* CMakeLists.txt +!buildroot/share/cmake/CMakeLists.txt src/CMakeLists.txt CMakeListsPrivate.txt +build/ -#CLion +# CLion cmake-build-* -#Eclipse +# Eclipse .project .cproject .pydevproject .settings .classpath -#Python +# Python __pycache__ + +# IOLogger logs +*_log.csv + +# Misc. +*~ +*.orig +*.rej +*.bak +*.idea +*.i +*.ii +*.swp +tags +*.logs +*.bak +.aider* +!.aiderignore +.env diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000000..62761b99e9 --- /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/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000000..52fe2a0bdb --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,12 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "marlinfirmware.auto-build", + "platformio.platformio-ide" + ], + "unwantedRecommendations": [ + "ms-vscode-remote.remote-containers", + "ms-vscode.cpptools-extension-pack" + ] +} diff --git a/.zed/settings.json b/.zed/settings.json new file mode 100644 index 0000000000..70bb14920c --- /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/Makefile b/Makefile new file mode 100644 index 0000000000..a63b4dc683 --- /dev/null +++ b/Makefile @@ -0,0 +1,176 @@ +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 + 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 + PINS := $(shell find Marlin/src/pins -mindepth 2 -name 'pins_*.h') +endif + +# 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 -j : Reformat all pins files (-j for parallel execution)" + @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 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" + @echo " test. If you set it to ALL it will run all " + @echo " tests, but some of them are broken: use " + @echo " tests-all-* instead to run only the ones that " + @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!" + +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 + +tests-single-local: + @if ! test -n "$(TEST_TARGET)" ; then echo "***ERROR*** Set TEST_TARGET= or use make tests-all-local" ; return 1; fi + export PATH="./buildroot/bin/:./buildroot/tests/:${PATH}" \ + && export VERBOSE_PLATFORMIO=$(VERBOSE_PLATFORMIO) \ + && run_tests . $(TEST_TARGET) "$(ONLY_TEST)" + +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)" + +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 $$($(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) + +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: + @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 . + +.PHONY: $(PINS) format-pins validate-pins + +$(PINS): %: + @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 db266b524b..a443d4b302 100644 --- a/Marlin/Configuration.h +++ b/Marlin/Configuration.h @@ -35,84 +35,58 @@ * * Advanced settings can be found in Configuration_adv.h */ -#define CONFIGURATION_H_VERSION 020007 +#define CONFIGURATION_H_VERSION 02010300 //=========================================================================== //============================= Getting Started ============================= //=========================================================================== /** - * Here are some standard links for getting your machine calibrated: + * Here are some useful links to help get your machine configured and calibrated: * - * https://reprap.org/wiki/Calibration - * https://youtu.be/wAL9d7FgInk - * http://calculator.josefprusa.cz - * https://reprap.org/wiki/Triffid_Hunter%27s_Calibration_Guide - * https://www.thingiverse.com/thing:5573 - * https://sites.google.com/site/repraplogphase/calibration-of-your-reprap - * https://www.thingiverse.com/thing:298812 + * Example Configs: https://github.com/MarlinFirmware/Configurations/branches/all + * + * PrΕ―Ε‘a Calculator: https://blog.prusa3d.com/calculator_3416/ + * + * Calibration Guides: https://reprap.org/wiki/Calibration + * https://reprap.org/wiki/Triffid_Hunter%27s_Calibration_Guide + * 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 + * + * Calibration Objects: https://www.thingiverse.com/thing:5573 + * https://www.thingiverse.com/thing:1278865 */ -//=========================================================================== -//============================= DELTA Printer =============================== -//=========================================================================== -// For a Delta printer start with one of the configuration files in the -// config/examples/delta directory and customize for your machine. -// - -//=========================================================================== -//============================= SCARA Printer =============================== -//=========================================================================== -// For a SCARA printer start with the configuration files in -// config/examples/SCARA and customize for your machine. -// - // @section info // Author info of this build printed to the host during boot and M115 -#define STRING_CONFIG_H_AUTHOR "(none, default config)" // Who made the changes. +#define STRING_CONFIG_H_AUTHOR "(MarlinFirmware)" // Original author or contributor. //#define CUSTOM_VERSION_FILE Version.h // Path from the root directory (no quotes) -/** - * *** VENDORS PLEASE READ *** - * - * Marlin allows you to add a custom boot image for Graphical LCDs. - * With this option Marlin will first show your custom screen followed - * by the standard Marlin logo with version number and web URL. - * - * We encourage you to take advantage of this new feature and we also - * respectfully request that you retain the unmodified Marlin boot screen. - */ - -// Show the Marlin bootscreen on startup. ** ENABLE FOR PRODUCTION ** -#define SHOW_BOOTSCREEN - -// Show the bitmap in Marlin/_Bootscreen.h on startup. -//#define SHOW_CUSTOM_BOOTSCREEN - -// Show the bitmap in Marlin/_Statusscreen.h on the status screen. -//#define CUSTOM_STATUS_SCREEN_IMAGE - // @section machine +// Choose the name from boards.h that matches your setup +#ifndef MOTHERBOARD + #define MOTHERBOARD BOARD_RAMPS_14_EFB +#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 0 /** - * Select a secondary serial port on the board to use for communication with the host. - * :[-1, 0, 1, 2, 3, 4, 5, 6, 7] - */ -//#define SERIAL_PORT_2 -1 - -/** - * This setting determines the communication speed of the printer. + * Serial Port Baud Rate + * This is the default communication speed for all serial ports. + * Set the baud rate defaults for additional serial ports below. * * 250000 works in most cases, but you might try a lower speed if * you commonly experience drop-outs during host printing. @@ -122,21 +96,127 @@ */ #define BAUDRATE 250000 +//#define BAUD_RATE_GCODE // Enable G-code M575 to set the baud rate + +/** + * 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, 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 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 -// Choose the name from boards.h that matches your setup -#ifndef MOTHERBOARD - #define MOTHERBOARD BOARD_RAMPS_14_EFB -#endif - // Name displayed in the LCD "Ready" message and Info menu //#define CUSTOM_MACHINE_NAME "3D Printer" +//#define CONFIGURABLE_MACHINE_NAME // Add G-code M550 to set/report the machine name // Printer's unique ID, used by some programs to differentiate between machines. // Choose your own or use a service like https://www.uuidgenerator.net/version4 //#define MACHINE_UUID "00000000-0000-0000-0000-000000000000" +// @section stepper drivers + +/** + * Stepper Drivers + * + * These settings allow Marlin to tune stepper driver timing and enable advanced options for + * stepper drivers that support them. You may also override timing options in Configuration_adv.h. + * + * Use TMC2208/TMC2208_STANDALONE for TMC2225 drivers and TMC2209/TMC2209_STANDALONE for TMC2226 drivers. + * + * Options: 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 + * :['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 +#define Z_DRIVER_TYPE A4988 +//#define X2_DRIVER_TYPE A4988 +//#define Y2_DRIVER_TYPE A4988 +//#define Z2_DRIVER_TYPE A4988 +//#define Z3_DRIVER_TYPE A4988 +//#define Z4_DRIVER_TYPE A4988 +//#define I_DRIVER_TYPE A4988 +//#define J_DRIVER_TYPE A4988 +//#define K_DRIVER_TYPE A4988 +//#define U_DRIVER_TYPE A4988 +//#define V_DRIVER_TYPE A4988 +//#define W_DRIVER_TYPE A4988 +#define E0_DRIVER_TYPE A4988 +//#define E1_DRIVER_TYPE A4988 +//#define E2_DRIVER_TYPE A4988 +//#define E3_DRIVER_TYPE A4988 +//#define E4_DRIVER_TYPE A4988 +//#define E5_DRIVER_TYPE A4988 +//#define E6_DRIVER_TYPE A4988 +//#define E7_DRIVER_TYPE A4988 + +/** + * Additional Axis Settings + * + * Define AXISn_ROTATES for all axes that rotate or pivot. + * Rotational axis coordinates are expressed in degrees. + * + * AXISn_NAME defines the letter used to refer to the axis in (most) G-code commands. + * By convention the names and roles are typically: + * 'A' : Rotational axis parallel to X + * 'B' : Rotational axis parallel to Y + * 'C' : Rotational axis parallel to Z + * 'U' : Secondary linear axis parallel to X + * 'V' : Secondary linear axis parallel to Y + * 'W' : Secondary linear axis parallel to Z + * + * Regardless of these settings the axes are internally named I, J, K, U, V, W. + */ +#ifdef I_DRIVER_TYPE + #define AXIS4_NAME 'A' // :['A', 'B', 'C', 'U', 'V', 'W'] + #define AXIS4_ROTATES +#endif +#ifdef J_DRIVER_TYPE + #define AXIS5_NAME 'B' // :['B', 'C', 'U', 'V', 'W'] + #define AXIS5_ROTATES +#endif +#ifdef K_DRIVER_TYPE + #define AXIS6_NAME 'C' // :['C', 'U', 'V', 'W'] + #define AXIS6_ROTATES +#endif +#ifdef U_DRIVER_TYPE + #define AXIS7_NAME 'U' // :['U', 'V', 'W'] + //#define AXIS7_ROTATES +#endif +#ifdef V_DRIVER_TYPE + #define AXIS8_NAME 'V' // :['V', 'W'] + //#define AXIS8_ROTATES +#endif +#ifdef W_DRIVER_TYPE + #define AXIS9_NAME 'W' // :['W'] + //#define AXIS9_ROTATES +#endif + // @section extruder // This defines the number of extruders @@ -156,35 +236,6 @@ //#define SINGLENOZZLE_STANDBY_FAN #endif -/** - * PrΕ―Ε‘a MK2 Single Nozzle Multi-Material Multiplexer, and variants. - * - * This device allows one stepper driver on a control board to drive - * two to eight stepper motors, one at a time, in a manner suitable - * for extruders. - * - * This option only allows the multiplexer to switch on tool-change. - * Additional options to configure custom E moves are pending. - */ -//#define MK2_MULTIPLEXER -#if ENABLED(MK2_MULTIPLEXER) - // Override the default DIO selector pins here, if needed. - // Some pins files may provide defaults for these pins. - //#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 -#endif - -/** - * PrΕ―Ε‘a Multi-Material Unit v2 - * - * Requires NOZZLE_PARK_FEATURE to park print head in case MMU unit fails. - * Requires EXTRUDERS = 5 - * - * For additional configuration see Configuration_adv.h - */ -//#define PRUSA_MMU2 - // A dual extruder that uses a single stepper motor //#define SWITCHING_EXTRUDER #if ENABLED(SWITCHING_EXTRUDER) @@ -195,14 +246,26 @@ #endif #endif -// A dual-nozzle that uses a servomotor to raise/lower one (or both) of the nozzles +// Switch extruders by bumping the toolhead. Requires EVENT_GCODE_TOOLCHANGE_#. +//#define MECHANICAL_SWITCHING_EXTRUDER + +/** + * A dual-nozzle that uses a servomotor to raise/lower one (or both) of the nozzles. + * Can be combined with SWITCHING_EXTRUDER. + */ //#define SWITCHING_NOZZLE #if ENABLED(SWITCHING_NOZZLE) #define SWITCHING_NOZZLE_SERVO_NR 0 //#define SWITCHING_NOZZLE_E1_SERVO_NR 1 // If two servos are used, the index of the second - #define SWITCHING_NOZZLE_SERVO_ANGLES { 0, 90 } // Angles for E0, E1 (single servo) or lowered/raised (dual servo) + #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_#. +//#define MECHANICAL_SWITCHING_NOZZLE + /** * Two separate X-carriages with extruders that connect to a moving part * via a solenoid docking mechanism. Requires SOL1_PIN and SOL2_PIN. @@ -219,11 +282,10 @@ */ //#define MAGNETIC_PARKING_EXTRUDER -#if EITHER(PARKING_EXTRUDER, MAGNETIC_PARKING_EXTRUDER) +#if ANY(PARKING_EXTRUDER, MAGNETIC_PARKING_EXTRUDER) #define PARKING_EXTRUDER_PARKING_X { -78, 184 } // X positions for parking the extruders #define PARKING_EXTRUDER_GRAB_DISTANCE 1 // (mm) Distance to move beyond the parking point to grab the extruder - //#define MANUAL_SOLENOID_CONTROL // Manual control of docking solenoids with M380 S / M381 #if ENABLED(PARKING_EXTRUDER) @@ -243,6 +305,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 * @@ -305,6 +379,7 @@ #define MIXING_VIRTUAL_TOOLS 16 // Use the Virtual Tool method with M163 and M164 //#define DIRECT_MIXING_IN_G1 // Allow ABCDHI mix factors in G1 movement commands //#define GRADIENT_MIX // Support for gradient mixing with M166 and LCD + //#define MIXING_PRESETS // Assign 8 default V-tool presets for 2 or 3 MIXING_STEPPERS #if ENABLED(GRADIENT_MIX) //#define GRADIENT_VTOOL // Add M166 T to use a V-tool index as a Gradient alias #endif @@ -317,7 +392,26 @@ //#define HOTEND_OFFSET_Y { 0.0, 5.00 } // (mm) relative Y-offset for each nozzle //#define HOTEND_OFFSET_Z { 0.0, 0.00 } // (mm) relative Z-offset for each nozzle -// @section machine +// @section multi-material + +/** + * Multi-Material Unit + * Set to one of these predefined models: + * + * 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", "PRUSA_MMU3", "EXTENDABLE_EMU_MMU2", "EXTENDABLE_EMU_MMU2S"] + */ +//#define MMU_MODEL PRUSA_MMU3 + +// @section psu control /** * Power Supply Control @@ -329,89 +423,160 @@ //#define PSU_NAME "Power Supply" #if ENABLED(PSU_CONTROL) + //#define MKS_PWC // Using the MKS PWC add-on + //#define PS_OFF_CONFIRM // Confirm dialog when power off + //#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 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 + + //#define PSU_POWERUP_GCODE "M355 S1" // G-code to run after power-on (e.g., case light on) + //#define PSU_POWEROFF_GCODE "M355 S0" // G-code to run before power-off (e.g., case light off) //#define AUTO_POWER_CONTROL // Enable automatic control of the PS_ON pin #if ENABLED(AUTO_POWER_CONTROL) - #define AUTO_POWER_FANS // Turn on PSU if fans need power - #define AUTO_POWER_E_FANS - #define AUTO_POWER_CONTROLLERFAN - #define AUTO_POWER_CHAMBER_FAN - //#define AUTO_POWER_E_TEMP 50 // (Β°C) Turn on PSU over this temperature - //#define AUTO_POWER_CHAMBER_TEMP 30 // (Β°C) Turn on PSU over this temperature - #define POWER_TIMEOUT 30 + #define AUTO_POWER_FANS // Turn on PSU for fans + #define AUTO_POWER_E_FANS // Turn on PSU for E Fans + #define AUTO_POWER_CONTROLLERFAN // Turn on PSU for Controller Fan + #define AUTO_POWER_CHAMBER_FAN // Turn on PSU for Chamber Fan + #define AUTO_POWER_COOLER_FAN // Turn on PSU for Cooler Fan + #define AUTO_POWER_SPINDLE_LASER // Turn on PSU for Spindle/Laser + #define POWER_TIMEOUT 30 // (s) Turn off power if the machine is idle for this duration + //#define POWER_OFF_DELAY 60 // (s) Delay of poweroff after M81 command. Useful to let fans run for extra time. + #endif + #if ANY(AUTO_POWER_CONTROL, POWER_OFF_WAIT_FOR_COOLDOWN) + //#define AUTO_POWER_E_TEMP 50 // (Β°C) PSU on if any extruder is over this temperature + //#define AUTO_POWER_CHAMBER_TEMP 30 // (Β°C) PSU on if the chamber is over this temperature + //#define AUTO_POWER_COOLER_TEMP 26 // (Β°C) PSU on if the cooler is over this temperature #endif #endif //=========================================================================== //============================= Thermal Settings ============================ //=========================================================================== -// @section temperature +// @section temperature sensors /** - * --NORMAL IS 4.7kohm PULLUP!-- 1kohm pullup can be used on hotend sensor, using correct resistor and table + * Temperature Sensors: * - * Temperature sensors available: + * NORMAL IS 4.7kΞ© PULLUP! Hotend sensors can use 1kΞ© pullup with correct resistor and table. * - * -5 : PT100 / PT1000 with MAX31865 (only for sensors 0-1) - * -3 : thermocouple with MAX31855 (only for sensors 0-1) - * -2 : thermocouple with MAX6675 (only for sensors 0-1) - * -4 : thermocouple with AD8495 - * -1 : thermocouple with AD595 + * ================================================================ + * Analog Thermistors - 4.7kΞ© pullup - Normal + * ================================================================ + * 1 : 100kΞ© EPCOS - Best choice for EPCOS thermistors + * 331 : 100kΞ© Same as #1, but 3.3V scaled for MEGA + * 332 : 100kΞ© Same as #1, but 3.3V scaled for DUE + * 2 : 200kΞ© ATC Semitec 204GT-2 + * 202 : 200kΞ© Copymaster 3D + * 3 : ???Ξ© Mendel-parts thermistor + * 4 : 10kΞ© Generic Thermistor !! DO NOT use for a hotend - it gives bad resolution at high temp. !! + * 5 : 100kΞ© ATC Semitec 104GT-2/104NT-4-R025H42G - Used in ParCan, J-Head, and E3D, SliceEngineering 300Β°C + * 501 : 100kΞ© Zonestar - Tronxy X3A + * 502 : 100kΞ© Zonestar - used by hot bed in Zonestar PrΕ―Ε‘a P802M + * 503 : 100kΞ© Zonestar (Z8XM2) Heated Bed thermistor + * 504 : 100kΞ© Zonestar P802QR2 (Part# QWG-104F-B3950) Hotend Thermistor + * 505 : 100kΞ© Zonestar P802QR2 (Part# QWG-104F-3950) Bed Thermistor + * 512 : 100kΞ© RPW-Ultra hotend + * 6 : 100kΞ© EPCOS - Not as accurate as table #1 (created using a fluke thermocouple) + * 7 : 100kΞ© Honeywell 135-104LAG-J01 + * 71 : 100kΞ© Honeywell 135-104LAF-J01 + * 8 : 100kΞ© Vishay 0603 SMD NTCS0603E3104FXT + * 9 : 100kΞ© GE Sensing AL03006-58.2K-97-G1 + * 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Ξ© Hisense 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 + * 15 : 100kΞ© Calibrated for JGAurora A5 hotend + * 17 : 100kΞ© Dagoma NTC white thermistor + * 18 : 200kΞ© ATC Semitec 204GT-2 Dagoma.Fr - MKS_Base_DKU001327 + * 22 : 100kΞ© GTM32 Pro vB - hotend - 4.7kΞ© pullup to 3.3V and 220Ξ© to analog input + * 23 : 100kΞ© GTM32 Pro vB - bed - 4.7kΞ© pullup to 3.3v and 220Ξ© to analog input + * 30 : 100kΞ© Kis3d Silicone heating mat 200W/300W with 6mm precision cast plate (EN AW 5083) NTC100K - beta 3950 + * 60 : 100kΞ© Maker's Tool Works Kapton Bed Thermistor - beta 3950 + * 61 : 100kΞ© Formbot/Vivedino 350Β°C Thermistor - beta 3950 + * 66 : 4.7MΞ© Dyze Design / Trianglelab T-D500 500Β°C High Temperature Thermistor + * 67 : 500kΞ© SliceEngineering 450Β°C Thermistor + * 68 : PT100 Smplifier board from Dyze Design + * 70 : 100kΞ© bq Hephestos 2 + * 75 : 100kΞ© Generic Silicon Heat Pad with NTC100K MGB18-104F39050L32 + * 666 : 200kΞ© Einstart S custom thermistor with 10k pullup. + * 2000 : 100kΞ© Ultimachine Rambo TDK NTCG104LH104KT1 NTC100K motherboard Thermistor + * + * ================================================================ + * Analog Thermistors - 1kΞ© pullup + * Atypical, and requires changing out the 4.7kΞ© pullup for 1kΞ©. + * (but gives greater accuracy and more stable PID) + * ================================================================ + * 51 : 100kΞ© EPCOS (1kΞ© pullup) + * 52 : 200kΞ© ATC Semitec 204GT-2 (1kΞ© pullup) + * 55 : 100kΞ© ATC Semitec 104GT-2 - Used in ParCan & J-Head (1kΞ© pullup) + * + * ================================================================ + * Analog Thermistors - 10kΞ© pullup - Atypical + * ================================================================ + * 99 : 100kΞ© Found on some Wanhao i3 machines with a 10kΞ© pull-up resistor + * + * ================================================================ + * Analog RTDs (Pt100/Pt1000) + * ================================================================ + * 110 : Pt100 with 1kΞ© pullup (atypical) + * 147 : Pt100 with 4.7kΞ© pullup + * 1010 : Pt1000 with 1kΞ© pullup (atypical) + * 1022 : Pt1000 with 2.2kΞ© pullup + * 1047 : Pt1000 with 4.7kΞ© pullup (E3D) + * 20 : Pt100 with circuit in the Ultimainboard V2.x with mainboard ADC reference voltage = INA826 amplifier-board supply voltage. + * NOTE: (1) Must use an ADC input with no pullup. (2) Some INA826 amplifiers are unreliable at 3.3V so consider using sensor 147, 110, or 21. + * 21 : Pt100 with circuit in the Ultimainboard V2.x with 3.3v ADC reference voltage (STM32, LPC176x....) and 5V INA826 amplifier board supply. + * NOTE: ADC pins are not 5V tolerant. Not recommended because it's possible to damage the CPU by going over 500Β°C. + * 201 : Pt100 with circuit in Overlord, similar to Ultimainboard V2.x + * + * ================================================================ + * SPI RTD/Thermocouple Boards + * ================================================================ + * -5 : MAX31865 with Pt100/Pt1000, 2, 3, or 4-wire (only for sensors 0-2 and bed) + * NOTE: You must uncomment/set the MAX31865_*_OHMS_n defines below. + * -3 : MAX31855 with Thermocouple, -200Β°C to +700Β°C (only for sensors 0-2 and bed) + * -2 : MAX6675 with Thermocouple, 0Β°C to +700Β°C (only for sensors 0-2 and bed) + * + * NOTE: Ensure TEMP_n_CS_PIN is set in your pins file for each TEMP_SENSOR_n using an SPI Thermocouple. By default, + * Hardware SPI on the default serial bus is used. If you have also set TEMP_n_SCK_PIN and TEMP_n_MISO_PIN, + * Software SPI will be used on those ports instead. You can force Hardware SPI on the default bus in the + * Configuration_adv.h file. At this time, separate Hardware SPI buses for sensors are not supported. + * + * ================================================================ + * Analog Thermocouple Boards + * ================================================================ + * -4 : AD8495 with Thermocouple + * -1 : AD595 with Thermocouple + * + * ================================================================ + * SoC internal sensor + * ================================================================ + * 100 : SoC internal sensor + * + * ================================================================ + * Custom/Dummy/Other Thermal Sensors + * ================================================================ * 0 : not used - * 1 : 100k thermistor - best choice for EPCOS 100k (4.7k pullup) - * 331 : (3.3V scaled thermistor 1 table for MEGA) - * 332 : (3.3V scaled thermistor 1 table for DUE) - * 2 : 200k thermistor - ATC Semitec 204GT-2 (4.7k pullup) - * 202 : 200k thermistor - Copymaster 3D - * 3 : Mendel-parts thermistor (4.7k pullup) - * 4 : 10k thermistor !! do not use it for a hotend. It gives bad resolution at high temp. !! - * 5 : 100K thermistor - ATC Semitec 104GT-2/104NT-4-R025H42G (Used in ParCan, J-Head, and E3D) (4.7k pullup) - * 501 : 100K Zonestar (Tronxy X3A) Thermistor - * 502 : 100K Zonestar Thermistor used by hot bed in Zonestar PrΕ―Ε‘a P802M - * 512 : 100k RPW-Ultra hotend thermistor (4.7k pullup) - * 6 : 100k EPCOS - Not as accurate as table 1 (created using a fluke thermocouple) (4.7k pullup) - * 7 : 100k Honeywell thermistor 135-104LAG-J01 (4.7k pullup) - * 71 : 100k Honeywell thermistor 135-104LAF-J01 (4.7k pullup) - * 8 : 100k 0603 SMD Vishay NTCS0603E3104FXT (4.7k pullup) - * 9 : 100k GE Sensing AL03006-58.2K-97-G1 (4.7k pullup) - * 10 : 100k RS thermistor 198-961 (4.7k pullup) - * 11 : 100k beta 3950 1% thermistor (Used in Keenovo AC silicone mats and most Wanhao i3 machines) (4.7k pullup) - * 12 : 100k 0603 SMD Vishay NTCS0603E3104FXT (4.7k pullup) (calibrated for Makibox hot bed) - * 13 : 100k Hisens 3950 1% up to 300Β°C for hotend "Simple ONE " & "Hotend "All In ONE" - * 15 : 100k thermistor calibration for JGAurora A5 hotend - * 18 : ATC Semitec 204GT-2 (4.7k pullup) Dagoma.Fr - MKS_Base_DKU001327 - * 20 : Pt100 with circuit in the Ultimainboard V2.x with 5v excitation (AVR) - * 21 : Pt100 with circuit in the Ultimainboard V2.x with 3.3v excitation (STM32 \ LPC176x....) - * 22 : 100k (hotend) with 4.7k pullup to 3.3V and 220R to analog input (as in GTM32 Pro vB) - * 23 : 100k (bed) with 4.7k pullup to 3.3v and 220R to analog input (as in GTM32 Pro vB) - * 30 : Kis3d Silicone heating mat 200W/300W with 6mm precision cast plate (EN AW 5083) NTC100K / B3950 (4.7k pullup) - * 201 : Pt100 with circuit in Overlord, similar to Ultimainboard V2.x - * 60 : 100k Maker's Tool Works Kapton Bed Thermistor beta=3950 - * 61 : 100k Formbot / Vivedino 3950 350C thermistor 4.7k pullup - * 66 : 4.7M High Temperature thermistor from Dyze Design - * 67 : 450C thermistor from SliceEngineering - * 70 : the 100K thermistor found in the bq Hephestos 2 - * 75 : 100k Generic Silicon Heat Pad with NTC 100K MGB18-104F39050L32 thermistor - * 99 : 100k thermistor with a 10K pull-up resistor (found on some Wanhao i3 machines) - * - * 1k ohm pullup tables - This is atypical, and requires changing out the 4.7k pullup for 1k. - * (but gives greater accuracy and more stable PID) - * 51 : 100k thermistor - EPCOS (1k pullup) - * 52 : 200k thermistor - ATC Semitec 204GT-2 (1k pullup) - * 55 : 100k thermistor - ATC Semitec 104GT-2 (Used in ParCan & J-Head) (1k pullup) - * - * 1047 : Pt1000 with 4k7 pullup (E3D) - * 1010 : Pt1000 with 1k pullup (non standard) - * 147 : Pt100 with 4k7 pullup - * 110 : Pt100 with 1k pullup (non standard) - * * 1000 : Custom - Specify parameters in Configuration_adv.h * - * Use these for Testing or Development purposes. NEVER for production machine. + * !!! Use these for Testing or Development purposes. NEVER for production machine. !!! * 998 : Dummy Table that ALWAYS reads 25Β°C or the temperature defined below. * 999 : Dummy Table that ALWAYS reads 100Β°C or the temperature defined below. */ @@ -423,32 +588,71 @@ #define TEMP_SENSOR_5 0 #define TEMP_SENSOR_6 0 #define TEMP_SENSOR_7 0 -#define TEMP_SENSOR_BED 0 +#define TEMP_SENSOR_BED 1 #define TEMP_SENSOR_PROBE 0 #define TEMP_SENSOR_CHAMBER 0 +#define TEMP_SENSOR_COOLER 0 +#define TEMP_SENSOR_BOARD 0 +#define TEMP_SENSOR_SOC 0 +#define TEMP_SENSOR_REDUNDANT 0 // Dummy thermistor constant temperature readings, for use with 998 and 999 -#define DUMMY_THERMISTOR_998_VALUE 25 +#define DUMMY_THERMISTOR_998_VALUE 25 #define DUMMY_THERMISTOR_999_VALUE 100 -// Resistor values when using a MAX31865 (sensor -5) -// Sensor value is typically 100 (PT100) or 1000 (PT1000) -// Calibration value is typically 430 ohm for AdaFruit PT100 modules and 4300 ohm for AdaFruit PT1000 modules. -//#define MAX31865_SENSOR_OHMS 100 -//#define MAX31865_CALIBRATION_OHMS 430 +// 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 +#endif +#if TEMP_SENSOR_IS_MAX_TC(1) + #define MAX31865_SENSOR_OHMS_1 100 + #define MAX31865_CALIBRATION_OHMS_1 430 +#endif +#if TEMP_SENSOR_IS_MAX_TC(2) + #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 -// Use temp sensor 1 as a redundant sensor with sensor 0. If the readings -// from the two sensors differ too much the print will be aborted. -//#define TEMP_SENSOR_1_AS_REDUNDANT -#define MAX_REDUNDANT_TEMP_SENSOR_DIFF 10 +#if HAS_E_TEMP_SENSOR + #define TEMP_RESIDENCY_TIME 10 // (seconds) Time to wait for hotend to "settle" in M109 + #define TEMP_WINDOW 1 // (Β°C) Temperature proximity for the "temperature reached" timer + #define TEMP_HYSTERESIS 3 // (Β°C) Temperature proximity considered "close enough" to the target +#endif -#define TEMP_RESIDENCY_TIME 10 // (seconds) Time to wait for hotend to "settle" in M109 -#define TEMP_WINDOW 1 // (Β°C) Temperature proximity for the "temperature reached" timer -#define TEMP_HYSTERESIS 3 // (Β°C) Temperature proximity considered "close enough" to the target +#if TEMP_SENSOR_BED + #define TEMP_BED_RESIDENCY_TIME 10 // (seconds) Time to wait for bed to "settle" in M190 + #define TEMP_BED_WINDOW 1 // (Β°C) Temperature proximity for the "temperature reached" timer + #define TEMP_BED_HYSTERESIS 3 // (Β°C) Temperature proximity considered "close enough" to the target +#endif -#define TEMP_BED_RESIDENCY_TIME 10 // (seconds) Time to wait for bed to "settle" in M190 -#define TEMP_BED_WINDOW 1 // (Β°C) Temperature proximity for the "temperature reached" timer -#define TEMP_BED_HYSTERESIS 3 // (Β°C) Temperature proximity considered "close enough" to the target +#if TEMP_SENSOR_CHAMBER + #define TEMP_CHAMBER_RESIDENCY_TIME 10 // (seconds) Time to wait for chamber to "settle" in M191 + #define TEMP_CHAMBER_WINDOW 1 // (Β°C) Temperature proximity for the "temperature reached" timer + #define TEMP_CHAMBER_HYSTERESIS 3 // (Β°C) Temperature proximity considered "close enough" to the target +#endif + +/** + * Redundant Temperature Sensor (TEMP_SENSOR_REDUNDANT) + * + * Use a temp sensor as a redundant sensor for another reading. Select an unused temperature sensor, and another + * sensor you'd like it to be redundant for. If the two thermistors differ by TEMP_SENSOR_REDUNDANT_MAX_DIFF (Β°C), + * the print will be aborted. Whichever sensor is selected will have its normal functions disabled; i.e. selecting + * the Bed sensor (-1) will disable bed heating/monitoring. + * + * For selecting source/target use: COOLER, PROBE, BOARD, CHAMBER, BED, E0, E1, E2, E3, E4, E5, E6, E7 + */ +#if TEMP_SENSOR_REDUNDANT + #define TEMP_SENSOR_REDUNDANT_SOURCE E1 // The sensor that will provide the redundant reading. + #define TEMP_SENSOR_REDUNDANT_TARGET E0 // The sensor that we are providing a redundant reading for. + #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. @@ -461,6 +665,7 @@ #define HEATER_6_MINTEMP 5 #define HEATER_7_MINTEMP 5 #define BED_MINTEMP 5 +#define CHAMBER_MINTEMP 5 // Above this temperature the heater will be switched off. // This can protect components from overheating, but NOT from shorts and failures. @@ -474,57 +679,121 @@ #define HEATER_6_MAXTEMP 275 #define HEATER_7_MAXTEMP 275 #define BED_MAXTEMP 150 +#define CHAMBER_MAXTEMP 60 + +/** + * Thermal Overshoot + * During heatup (and printing) the temperature can often "overshoot" the target by many degrees + * (especially before PID tuning). Setting the target temperature too close to MAXTEMP guarantees + * a MAXTEMP shutdown! Use these values to forbid temperatures being set too close to MAXTEMP. + */ +#define HOTEND_OVERSHOOT 15 // (Β°C) Forbid temperatures over MAXTEMP - OVERSHOOT +#define BED_OVERSHOOT 10 // (Β°C) Forbid temperatures over MAXTEMP - OVERSHOOT +#define COOLER_OVERSHOOT 2 // (Β°C) Forbid temperatures closer than OVERSHOOT //=========================================================================== //============================= PID Settings ================================ //=========================================================================== -// PID Tuning Guide here: https://reprap.org/wiki/PID_Tuning -// Comment the following line to disable PID and enable bang-bang. -#define PIDTEMP -#define BANG_MAX 255 // Limits current to nozzle while in bang-bang mode; 255=full current -#define PID_MAX BANG_MAX // Limits current to nozzle while PID is active (see PID_FUNCTIONAL_RANGE below); 255=full current -#define PID_K1 0.95 // Smoothing factor within any PID loop +// @section hotend temp + +/** + * Temperature Control + * + * (NONE) : Bang-bang heating + * PIDTEMP : PID temperature control (~4.1K) + * 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 // 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_EDIT_MENU // Add PID editing to the "Advanced Settings" menu. (~700 bytes of PROGMEM) - //#define PID_AUTOTUNE_MENU // Add PID auto-tuning to the "Advanced Settings" menu. (~250 bytes of PROGMEM) - //#define PID_PARAMS_PER_HOTEND // Uses separate PID parameters for each extruder (useful for mismatched extruders) - // Set/get with gcode: M301 E[extruder number, 0-2] + //#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 between 1 and HOTENDS values per array. - // If fewer than EXTRUDER values are provided, the last element will be repeated. - #define DEFAULT_Kp_LIST { 22.20, 22.20 } - #define DEFAULT_Ki_LIST { 1.08, 1.08 } - #define DEFAULT_Kd_LIST { 114.00, 114.00 } + // 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 } #else - #define DEFAULT_Kp 22.20 - #define DEFAULT_Ki 1.08 - #define DEFAULT_Kd 114.00 + #define DEFAULT_KP 22.20 + #define DEFAULT_KI 1.08 + #define DEFAULT_KD 114.00 #endif -#endif // PIDTEMP +#else + #define BANG_MAX 255 // Limit hotend current while in bang-bang mode; 255=full current +#endif + +/** + * Model Predictive Control for hotend + * + * 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 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) 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? + + // Measured physical constants from M306 + #define MPC_BLOCK_HEAT_CAPACITY { 16.7f } // (J/K) Heat block heat capacities. + #define MPC_SENSOR_RESPONSIVENESS { 0.22f } // (K/s per βˆ†K) Rate of change of sensor temperature from heat block. + #define MPC_AMBIENT_XFER_COEFF { 0.068f } // (W/K) Heat transfer coefficients from heat block to room air with fan off. + #if ENABLED(MPC_INCLUDE_FAN) + #define MPC_AMBIENT_XFER_COEFF_FAN255 { 0.097f } // (W/K) Heat transfer coefficients from heat block to room air with fan on full. + #endif + + // For one fan and multiple hotends MPC needs to know how to apply the fan cooling effect. + #if ENABLED(MPC_INCLUDE_FAN) + //#define MPC_FAN_0_ALL_HOTENDS + //#define MPC_FAN_0_ACTIVE_HOTEND + #endif + + // Filament Heat Capacity (joules/kelvin/mm) + // Set at runtime with M306 H + #define FILAMENT_HEAT_CAPACITY_PERMM { 5.6e-3f } // 0.0056 J/K/mm for 1.75mm PLA (0.0149 J/K/mm for 2.85mm PLA). + // 0.0036 J/K/mm for 1.75mm PETG (0.0094 J/K/mm for 2.85mm PETG). + // 0.00515 J/K/mm for 1.75mm ABS (0.0137 J/K/mm for 2.85mm ABS). + // 0.00522 J/K/mm for 1.75mm Nylon (0.0138 J/K/mm for 2.85mm Nylon). + + // Advanced options + #define MPC_SMOOTHING_FACTOR 0.5f // (0.0...1.0) Noisy temperature sensors may need a lower value for stabilization. + #define MPC_MIN_AMBIENT_CHANGE 1.0f // (K/s) Modeled ambient temperature rate of change, when correcting model inaccuracies. + #define MPC_STEADYSTATE 0.5f // (K/s) Temperature change rate for steady state logic to be enforced. + + #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 //=========================================================================== //====================== PID > Bed Temperature Control ====================== //=========================================================================== -/** - * PID Bed Heating - * - * If this option is enabled set PID constants below. - * If this option is disabled, bang-bang will be used and BED_LIMIT_SWITCHING will enable hysteresis. - * - * The PID frequency will be the same as the extruder PWM. - * If PID_dT is the default, and correct for the hardware/configuration, that means 7.689Hz, - * which is fine for driving a square wave into a resistive load and does not significantly - * impact FET heating. This also works fine on a Fotek SSR-10DA Solid State Relay into a 250W - * heater. If your configuration is significantly different than this and you don't understand - * the issues involved, don't use bed PID until someone else verifies that your hardware works. - */ -//#define PIDTEMPBED - -//#define BED_LIMIT_SWITCHING +// @section bed temp /** * Max Bed Power @@ -534,28 +803,131 @@ */ #define MAX_BED_POWER 255 // limits duty cycle to bed; 255=full current +/** + * PID Bed Heating + * + * The PID frequency will be the same as the extruder PWM. + * If PID_dT is the default, and correct for the hardware/configuration, that means 7.689Hz, + * which is fine for driving a square wave into a resistive load and does not significantly + * impact FET heating. This also works fine on a Fotek SSR-10DA Solid State Relay into a 250W + * heater. If your configuration is significantly different than this and you don't understand + * the issues involved, don't use bed PID until someone else verifies that your hardware works. + * + * With this option disabled, bang-bang will be used. BED_LIMIT_SWITCHING enables hysteresis. + */ +//#define PIDTEMPBED + #if ENABLED(PIDTEMPBED) - //#define MIN_BED_POWER 0 - //#define PID_BED_DEBUG // Sends 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) - #define DEFAULT_bedKp 10.00 - #define DEFAULT_bedKi .023 - #define DEFAULT_bedKd 305.4 + #define DEFAULT_BED_KP 10.00 + #define DEFAULT_BED_KI 0.023 + #define DEFAULT_BED_KD 305.4 // FIND YOUR OWN: "M303 E-1 C8 S90" to run autotune on the bed at 90 degreesC for 8 cycles. -#endif // PIDTEMPBED - -#if EITHER(PIDTEMP, PIDTEMPBED) - //#define PID_DEBUG // Sends debug data to the serial port. Use 'M303 D' to toggle activation. - //#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 - // is more than PID_FUNCTIONAL_RANGE then the PID will be shut off and the heater will be set to min/max. +#else + //#define BED_LIMIT_SWITCHING // Keep the bed temperature within BED_HYSTERESIS of the target #endif -// @section extruder +/** + * 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. +//#define BED_ANNEALING_GCODE + +//=========================================================================== +//==================== PID > Chamber Temperature Control ==================== +//=========================================================================== + +/** + * PID Chamber Heating + * + * If this option is enabled set PID constants below. + * If this option is disabled, bang-bang will be used and CHAMBER_LIMIT_SWITCHING will enable + * hysteresis. + * + * The PID frequency will be the same as the extruder PWM. + * If PID_dT is the default, and correct for the hardware/configuration, that means 7.689Hz, + * which is fine for driving a square wave into a resistive load and does not significantly + * impact FET heating. This also works fine on a Fotek SSR-10DA Solid State Relay into a 200W + * heater. If your configuration is significantly different than this and you don't understand + * the issues involved, don't use chamber PID until someone else verifies that your hardware works. + * @section chamber temp + */ +//#define PIDTEMPCHAMBER +//#define CHAMBER_LIMIT_SWITCHING + +/** + * Max Chamber Power + * Applies to all forms of chamber control (PID, bang-bang, and bang-bang with hysteresis). + * When set to any value below 255, enables a form of PWM to the chamber heater that acts like a divider + * so don't use it unless you are OK with PWM on your heater. (See the comment on enabling PIDTEMPCHAMBER) + */ +#define MAX_CHAMBER_POWER 255 // limits duty cycle to chamber heater; 255=full current + +#if ENABLED(PIDTEMPCHAMBER) + //#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_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 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) + //#define PID_AUTOTUNE_MENU // Add PID auto-tuning to the "Advanced Settings" menu. (~250 bytes of flash) +#endif + +// @section safety /** * Prevent extrusion if the temperature is below EXTRUDE_MINTEMP. @@ -584,7 +956,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 @@ -594,12 +966,13 @@ #define THERMAL_PROTECTION_HOTENDS // Enable thermal protection for all extruders #define THERMAL_PROTECTION_BED // Enable thermal protection for the heated bed #define THERMAL_PROTECTION_CHAMBER // Enable thermal protection for the heated chamber +#define THERMAL_PROTECTION_COOLER // Enable thermal protection for the laser cooling //=========================================================================== //============================= 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 @@ -609,34 +982,261 @@ //#define COREYX //#define COREZX //#define COREZY -//#define MARKFORGED_XY // MarkForged. See https://reprap.org/forum/read.php?152,504042 + +// +// MarkForged Kinematics +// See https://reprap.org/forum/read.php?152,504042 +// +//#define MARKFORGED_XY +//#define MARKFORGED_YX +#if ANY(MARKFORGED_XY, MARKFORGED_YX) + //#define MARKFORGED_INVERSE // Enable for an inverted Markforged kinematics belt path +#endif + +// Enable for a belt style printer with endless "Z" motion +//#define BELTPRINTER + +// 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 + +// Enable for Polargraph Kinematics +//#define POLARGRAPH +#if ENABLED(POLARGRAPH) + #define POLARGRAPH_MAX_BELT_LEN 1035.0 // (mm) Belt length at full extension. Override with M665 H. + #define DEFAULT_SEGMENTS_PER_SECOND 5 // Move segmentation based on duration + #define PEN_UP_DOWN_MENU // Add "Pen Up" and "Pen Down" to the MarlinUI menu +#endif + +// @section delta + +// Enable for DELTA kinematics and configure below +//#define DELTA +#if ENABLED(DELTA) + + // Make delta curves from many straight lines (linear interpolation). + // This is a trade-off between visible corners (not enough segments) + // and processor overload (too many expensive sqrt calls). + #define DEFAULT_SEGMENTS_PER_SECOND 200 + + // After homing move down to a height where XY movement is unconstrained + //#define DELTA_HOME_TO_SAFE_ZONE + + // Delta calibration menu + // Add three-point calibration to the MarlinUI menu. + // See http://minow.blogspot.com/index.html#4918805519571907051 + //#define DELTA_CALIBRATION_MENU + + // G33 Delta Auto-Calibration. Enable EEPROM_SETTINGS to store results. + //#define DELTA_AUTO_CALIBRATION + + #if ENABLED(DELTA_AUTO_CALIBRATION) + // Default number of probe points : n*n (1 -> 7) + #define DELTA_CALIBRATION_DEFAULT_POINTS 4 + #endif + + #if ANY(DELTA_AUTO_CALIBRATION, DELTA_CALIBRATION_MENU) + // Step size for paper-test probing + #define PROBE_MANUALLY_STEP 0.05 // (mm) + #endif + + // Print surface diameter/2 minus unreachable space (avoid collisions with vertical towers). + #define PRINTABLE_RADIUS 140.0 // (mm) + + // Center-to-center distance of the holes in the diagonal push rods. + #define DELTA_DIAGONAL_ROD 250.0 // (mm) + + // Distance between bed and nozzle Z home position + #define DELTA_HEIGHT 250.00 // (mm) Get this value from G33 auto calibrate + + #define DELTA_ENDSTOP_ADJ { 0.0, 0.0, 0.0 } // (mm) Get these values from G33 auto calibrate + + // Horizontal distance bridged by diagonal push rods when effector is centered. + #define DELTA_RADIUS 124.0 // (mm) Get this value from G33 auto calibrate + + // Trim adjustments for individual towers + // tower angle corrections for X and Y tower / rotate XYZ so Z tower angle = 0 + // measured in degrees anticlockwise looking from above the printer + #define DELTA_TOWER_ANGLE_TRIM { 0.0, 0.0, 0.0 } // (mm) Get these values from G33 auto calibrate + + // 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 // DELTA + +// @section scara + +/** + * 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) + // 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) + + // SCARA tower offset (position of Tower 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 DEBUG_SCARA_KINEMATICS + #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) + + #elif ENABLED(MP_SCARA) + + #define SCARA_OFFSET_THETA1 12 // degrees + #define SCARA_OFFSET_THETA2 131 // degrees + + #endif + +#endif + +// @section tpara + +// Enable for TPARA kinematics and configure below +//#define AXEL_TPARA +#if ENABLED(AXEL_TPARA) + #define DEBUG_TPARA_KINEMATICS + #define DEFAULT_SEGMENTS_PER_SECOND 200 + + // Length of inner and outer support arms. Measure arm lengths precisely. + #define TPARA_LINKAGE_1 120 // (mm) + #define TPARA_LINKAGE_2 120 // (mm) + + // 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 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 + // 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 + +/** + * POLAR Kinematics + * developed by Kadir ilkimen for PolarBear CNC and babyBear + * https://github.com/kadirilkimen/Polar-Bear-Cnc-Machine + * https://github.com/kadirilkimen/babyBear-3D-printer + * + * A polar machine can have different configurations. + * This kinematics is only compatible with the following configuration: + * X : Independent linear + * Y or B : Polar + * Z : Independent linear + * + * For example, PolarBear has CoreXZ plus Polar Y or B. + * + * Motion problem for Polar axis near center / origin: + * + * 3D printing: + * Movements very close to the center of the polar axis take more time than others. + * This brief delay results in more material deposition due to the pressure in the nozzle. + * + * Current Kinematics and feedrate scaling deals with this by making the movement as fast + * as possible. It works for slow movements but doesn't work well with fast ones. A more + * complicated extrusion compensation must be implemented. + * + * Ideally, it should estimate that a long rotation near the center is ahead and will cause + * unwanted deposition. Therefore it can compensate the extrusion beforehand. + * + * Laser cutting: + * Same thing would be a problem for laser engraving too. As it spends time rotating at the + * center point, more likely it will burn more material than it should. Therefore similar + * compensation would be implemented for laser-cutting operations. + * + * Milling: + * This shouldn't be a problem for cutting/milling operations. + */ +//#define POLAR +#if ENABLED(POLAR) + #define DEFAULT_SEGMENTS_PER_SECOND 180 // If movement is choppy try lowering this value + #define PRINTABLE_RADIUS 82.0f // (mm) Maximum travel of X axis + + // Movements fall inside POLAR_FAST_RADIUS are assigned the highest possible feedrate + // to compensate unwanted deposition related to the near-origin motion problem. + #define POLAR_FAST_RADIUS 3.0f // (mm) + + // Radius which is unreachable by the tool. + // Needed if the tool is not perfectly aligned to the center of the polar axis. + #define POLAR_CENTER_OFFSET 0.0f // (mm) + + #define FEEDRATE_SCALING // Convert XY feedrate from mm/s to degrees/s on the fly +#endif //=========================================================================== //============================== Endstop Settings =========================== //=========================================================================== -// @section homing - -// Specify here all the endstop connectors that are connected to any endstop or probe. -// Almost all printers will be using one per axis. Probes will use one or more of the -// extra connectors. Leave undefined any used for non-endstop and non-probe purposes. -#define USE_XMIN_PLUG -#define USE_YMIN_PLUG -#define USE_ZMIN_PLUG -//#define USE_XMAX_PLUG -//#define USE_YMAX_PLUG -//#define USE_ZMAX_PLUG +// @section endstops // Enable pullup for all endstops to prevent a floating state #define ENDSTOPPULLUPS #if DISABLED(ENDSTOPPULLUPS) // Disable ENDSTOPPULLUPS to set pullups individually - //#define ENDSTOPPULLUP_XMAX - //#define ENDSTOPPULLUP_YMAX - //#define ENDSTOPPULLUP_ZMAX //#define ENDSTOPPULLUP_XMIN //#define ENDSTOPPULLUP_YMIN //#define ENDSTOPPULLUP_ZMIN + //#define ENDSTOPPULLUP_IMIN + //#define ENDSTOPPULLUP_JMIN + //#define ENDSTOPPULLUP_KMIN + //#define ENDSTOPPULLUP_UMIN + //#define ENDSTOPPULLUP_VMIN + //#define ENDSTOPPULLUP_WMIN + //#define ENDSTOPPULLUP_XMAX + //#define ENDSTOPPULLUP_YMAX + //#define ENDSTOPPULLUP_ZMAX + //#define ENDSTOPPULLUP_IMAX + //#define ENDSTOPPULLUP_JMAX + //#define ENDSTOPPULLUP_KMAX + //#define ENDSTOPPULLUP_UMAX + //#define ENDSTOPPULLUP_VMAX + //#define ENDSTOPPULLUP_WMAX //#define ENDSTOPPULLUP_ZMIN_PROBE #endif @@ -644,56 +1244,50 @@ //#define ENDSTOPPULLDOWNS #if DISABLED(ENDSTOPPULLDOWNS) // Disable ENDSTOPPULLDOWNS to set pulldowns individually - //#define ENDSTOPPULLDOWN_XMAX - //#define ENDSTOPPULLDOWN_YMAX - //#define ENDSTOPPULLDOWN_ZMAX //#define ENDSTOPPULLDOWN_XMIN //#define ENDSTOPPULLDOWN_YMIN //#define ENDSTOPPULLDOWN_ZMIN + //#define ENDSTOPPULLDOWN_IMIN + //#define ENDSTOPPULLDOWN_JMIN + //#define ENDSTOPPULLDOWN_KMIN + //#define ENDSTOPPULLDOWN_UMIN + //#define ENDSTOPPULLDOWN_VMIN + //#define ENDSTOPPULLDOWN_WMIN + //#define ENDSTOPPULLDOWN_XMAX + //#define ENDSTOPPULLDOWN_YMAX + //#define ENDSTOPPULLDOWN_ZMAX + //#define ENDSTOPPULLDOWN_IMAX + //#define ENDSTOPPULLDOWN_JMAX + //#define ENDSTOPPULLDOWN_KMAX + //#define ENDSTOPPULLDOWN_UMAX + //#define ENDSTOPPULLDOWN_VMAX + //#define ENDSTOPPULLDOWN_WMAX //#define ENDSTOPPULLDOWN_ZMIN_PROBE #endif -// Mechanical endstop with COM to ground and NC to Signal uses "false" here (most common setup). -#define X_MIN_ENDSTOP_INVERTING false // Set to true to invert the logic of the endstop. -#define Y_MIN_ENDSTOP_INVERTING false // Set to true to invert the logic of the endstop. -#define Z_MIN_ENDSTOP_INVERTING false // Set to true to invert the logic of the endstop. -#define X_MAX_ENDSTOP_INVERTING false // Set to true to invert the logic of the endstop. -#define Y_MAX_ENDSTOP_INVERTING false // Set to true to invert the logic of the endstop. -#define Z_MAX_ENDSTOP_INVERTING false // Set to true to invert the logic of the endstop. -#define Z_MIN_PROBE_ENDSTOP_INVERTING false // Set to true to invert the logic of the probe. - /** - * Stepper Drivers - * - * These settings allow Marlin to tune stepper driver timing and enable advanced options for - * stepper drivers that support them. You may also override timing options in Configuration_adv.h. - * - * A4988 is assumed for unspecified drivers. - * - * Options: A4988, A5984, DRV8825, LV8729, L6470, L6474, POWERSTEP01, - * TB6560, TB6600, TMC2100, - * TMC2130, TMC2130_STANDALONE, TMC2160, TMC2160_STANDALONE, - * TMC2208, TMC2208_STANDALONE, TMC2209, TMC2209_STANDALONE, - * TMC26X, TMC26X_STANDALONE, TMC2660, TMC2660_STANDALONE, - * TMC5130, TMC5130_STANDALONE, TMC5160, TMC5160_STANDALONE - * :['A4988', 'A5984', 'DRV8825', 'LV8729', 'L6470', 'L6474', 'POWERSTEP01', 'TB6560', 'TB6600', 'TMC2100', 'TMC2130', 'TMC2130_STANDALONE', 'TMC2160', 'TMC2160_STANDALONE', 'TMC2208', 'TMC2208_STANDALONE', 'TMC2209', 'TMC2209_STANDALONE', 'TMC26X', 'TMC26X_STANDALONE', 'TMC2660', 'TMC2660_STANDALONE', 'TMC5130', 'TMC5130_STANDALONE', 'TMC5160', 'TMC5160_STANDALONE'] + * Endstop "Hit" State + * Set to the state (HIGH or LOW) that applies to each endstop. */ -//#define X_DRIVER_TYPE A4988 -//#define Y_DRIVER_TYPE A4988 -//#define Z_DRIVER_TYPE A4988 -//#define X2_DRIVER_TYPE A4988 -//#define Y2_DRIVER_TYPE A4988 -//#define Z2_DRIVER_TYPE A4988 -//#define Z3_DRIVER_TYPE A4988 -//#define Z4_DRIVER_TYPE A4988 -//#define E0_DRIVER_TYPE A4988 -//#define E1_DRIVER_TYPE A4988 -//#define E2_DRIVER_TYPE A4988 -//#define E3_DRIVER_TYPE A4988 -//#define E4_DRIVER_TYPE A4988 -//#define E5_DRIVER_TYPE A4988 -//#define E6_DRIVER_TYPE A4988 -//#define E7_DRIVER_TYPE A4988 +#define X_MIN_ENDSTOP_HIT_STATE HIGH +#define X_MAX_ENDSTOP_HIT_STATE HIGH +#define Y_MIN_ENDSTOP_HIT_STATE HIGH +#define Y_MAX_ENDSTOP_HIT_STATE HIGH +#define Z_MIN_ENDSTOP_HIT_STATE HIGH +#define Z_MAX_ENDSTOP_HIT_STATE HIGH +#define I_MIN_ENDSTOP_HIT_STATE HIGH +#define I_MAX_ENDSTOP_HIT_STATE HIGH +#define J_MIN_ENDSTOP_HIT_STATE HIGH +#define J_MAX_ENDSTOP_HIT_STATE HIGH +#define K_MIN_ENDSTOP_HIT_STATE HIGH +#define K_MAX_ENDSTOP_HIT_STATE HIGH +#define U_MIN_ENDSTOP_HIT_STATE HIGH +#define U_MAX_ENDSTOP_HIT_STATE HIGH +#define V_MIN_ENDSTOP_HIT_STATE HIGH +#define V_MAX_ENDSTOP_HIT_STATE HIGH +#define W_MIN_ENDSTOP_HIT_STATE HIGH +#define W_MAX_ENDSTOP_HIT_STATE HIGH +#define Z_MIN_PROBE_ENDSTOP_HIT_STATE HIGH // Enable this feature if all enabled endstop pins are interrupt-capable. // This will remove the need to poll the interrupt pins, saving many CPU cycles. @@ -737,16 +1331,21 @@ //#define DISTINCT_E_FACTORS /** - * Default Axis Steps Per Unit (steps/mm) - * Override with M92 - * X, Y, Z, E0 [, E1[, E2...]] + * Default Axis Steps Per Unit (linear=steps/mm, rotational=steps/Β°) + * Override with M92 (when enabled below) + * X, Y, Z [, I [, J [, K...]]], E0 [, E1[, E2...]] */ -#define DEFAULT_AXIS_STEPS_PER_UNIT { 80, 80, 4000, 500 } +#define DEFAULT_AXIS_STEPS_PER_UNIT { 80, 80, 400, 500 } /** - * Default Max Feed Rate (mm/s) + * Enable support for M92. Disable to save at least ~530 bytes of flash. + */ +#define EDITABLE_STEPS_PER_UNIT + +/** + * Default Max Feed Rate (linear=mm/s, rotational=Β°/s) * Override with M203 - * X, Y, Z, E0 [, E1[, E2...]] + * X, Y, Z [, I [, J [, K...]]], E0 [, E1[, E2...]] */ #define DEFAULT_MAX_FEEDRATE { 300, 300, 5, 25 } @@ -756,10 +1355,10 @@ #endif /** - * Default Max Acceleration (change/s) change = mm/s + * Default Max Acceleration (speed change with time) (linear=mm/(s^2), rotational=Β°/(s^2)) * (Maximum start speed for accelerated moves) * Override with M201 - * X, Y, Z, E0 [, E1[, E2...]] + * X, Y, Z [, I [, J [, K...]]], E0 [, E1[, E2...]] */ #define DEFAULT_MAX_ACCELERATION { 3000, 3000, 100, 10000 } @@ -769,7 +1368,7 @@ #endif /** - * Default Acceleration (change/s) change = mm/s + * Default Acceleration (speed change with time) (linear=mm/(s^2), rotational=Β°/(s^2)) * Override with M204 * * M204 P Acceleration @@ -782,7 +1381,7 @@ /** * Default Jerk limits (mm/s) - * Override with M205 X Y Z E + * Override with M205 X Y Z . . . E * * "Jerk" specifies the minimum speed change that requires acceleration. * When changing speed and direction, if the difference is less than the @@ -793,6 +1392,13 @@ #define DEFAULT_XJERK 10.0 #define DEFAULT_YJERK 10.0 #define DEFAULT_ZJERK 0.3 + #define DEFAULT_EJERK 5.0 + //#define DEFAULT_IJERK 0.3 + //#define DEFAULT_JJERK 0.3 + //#define DEFAULT_KJERK 0.3 + //#define DEFAULT_UJERK 0.3 + //#define DEFAULT_VJERK 0.3 + //#define DEFAULT_WJERK 0.3 //#define TRAVEL_EXTRA_XYJERK 0.0 // Additional jerk allowance for all travel moves @@ -802,8 +1408,6 @@ #endif #endif -#define DEFAULT_EJERK 5.0 // May be used by Linear Advance - /** * Junction Deviation Factor * @@ -826,6 +1430,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 ============================= @@ -849,19 +1458,17 @@ /** * Z_MIN_PROBE_PIN * - * Define this pin if the probe is not connected to Z_MIN_PIN. - * If not defined the default pin for the selected MOTHERBOARD - * will be used. Most of the time the default is what you want. + * Override this pin only if the probe cannot be connected to + * the default Z_MIN_PROBE_PIN for the selected MOTHERBOARD. * * - The simplest option is to use a free endstop connector. * - Use 5V for powered (usually inductive) sensors. * - * - RAMPS 1.3/1.4 boards may use the 5V, GND, and Aux4->D32 pin: - * - For simple switches connect... - * - normally-closed switches to GND and D32. - * - normally-open switches to 5V and D32. + * - For simple switches... + * - Normally-closed (NC) also connect to GND. + * - Normally-open (NO) also connect to 5V. */ -//#define Z_MIN_PROBE_PIN 32 // Pin 32 is the RAMPS default +//#define Z_MIN_PROBE_PIN -1 /** * Probe Type @@ -876,7 +1483,6 @@ * or (with LCD_BED_LEVELING) the LCD controller. */ //#define PROBE_MANUALLY -//#define MANUAL_PROBE_START_Z 0.2 /** * A Fix-Mounted Probe either doesn't deploy or needs manual deployment. @@ -893,8 +1499,13 @@ /** * Z Servo Probe, such as an endstop switch on a rotating arm. */ -//#define Z_PROBE_SERVO_NR 0 // Defaults to SERVO 0 connector. -//#define Z_SERVO_ANGLES { 70, 0 } // Z Servo Deploy and Stow angles +//#define Z_PROBE_SERVO_NR 0 +#ifdef Z_PROBE_SERVO_NR + //#define Z_SERVO_ANGLES { 70, 0 } // Z Servo Deploy and Stow angles + //#define Z_SERVO_MEASURE_ANGLE 45 // Use if the servo must move to a "free" position for measuring after deploy + //#define Z_SERVO_INTERMEDIATE_STOW // Stow the probe between points + //#define Z_SERVO_DEACTIVATE_AFTER_STOW // Deactivate the servo when probe is stowed +#endif /** * The BLTouch probe uses a Hall effect sensor and emulates a servo. @@ -902,9 +1513,15 @@ //#define BLTOUCH /** - * Pressure sensor with a BLTouch-like interface + * MagLev V4 probe by MDD + * + * This probe is deployed and activated by powering a built-in electromagnet. */ -//#define CREALITY_TOUCH +//#define MAGLEV4 +#if ENABLED(MAGLEV4) + //#define MAGLEV_TRIGGER_PIN 11 // Set to the connected digital output + #define MAGLEV_TRIGGER_DELAY 15 // Changing this risks overheating the coil +#endif /** * Touch-MI Probe by hotends.fr @@ -914,7 +1531,7 @@ * on the right, enable and set TOUCH_MI_DEPLOY_XPOS to the deploy position. * * Also requires: BABYSTEPPING, BABYSTEP_ZPROBE_OFFSET, Z_SAFE_HOMING, - * and a minimum Z_HOMING_HEIGHT of 10. + * and a minimum Z_CLEARANCE_FOR_HOMING of 10. */ //#define TOUCH_MI_PROBE #if ENABLED(TOUCH_MI_PROBE) @@ -923,6 +1540,29 @@ //#define TOUCH_MI_MANUAL_DEPLOY // For manual deploy (LCD menu) #endif +/** + * Bed Distance Sensor + * + * Measures the distance from bed to nozzle with accuracy of 0.01mm. + * For information about this sensor https://github.com/markniu/Bed_Distance_sensor + * Uses I2C port, so it requires I2C library markyue/Panda_SoftMasterI2C. + */ +//#define BD_SENSOR +#if ENABLED(BD_SENSOR) + //#define BD_SENSOR_PROBE_NO_STOP // Probe bed without stopping at each probe point +#endif + +/** + * BIQU MicroProbe + * + * A lightweight, solenoid-driven probe. + * For information about this sensor https://github.com/bigtreetech/MicroProbe + * + * Also requires PROBE_ENABLE_DISABLE + */ +//#define BIQU_MICROPROBE_V1 // Triggers HIGH +//#define BIQU_MICROPROBE_V2 // Triggers LOW + // A probe that is deployed and stowed with a solenoid pin (SOL1_PIN) //#define SOLENOID_PROBE @@ -937,8 +1577,43 @@ #define Z_PROBE_RETRACT_X X_MAX_POS #endif -// Duet Smart Effector (for delta printers) - https://bit.ly/2ul5U7J -// When the pin is defined you can use M672 to set/reset the probe sensivity. +/** + * Magnetically Mounted Probe + * For probes such as Euclid, Klicky, Klackender, etc. + */ +//#define MAG_MOUNTED_PROBE +#if ENABLED(MAG_MOUNTED_PROBE) + #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 + #define MAG_MOUNTED_DEPLOY_4 { PROBE_DEPLOY_FEEDRATE, { 0, 0, 0 } } // Extra move if needed + #define MAG_MOUNTED_DEPLOY_5 { PROBE_DEPLOY_FEEDRATE, { 0, 0, 0 } } // Extra move if needed + #define MAG_MOUNTED_STOW_1 { PROBE_STOW_FEEDRATE, { 245, 114, 20 } } // Move to dock + #define MAG_MOUNTED_STOW_2 { PROBE_STOW_FEEDRATE, { 245, 114, 0 } } // Place probe beside remover + #define MAG_MOUNTED_STOW_3 { PROBE_STOW_FEEDRATE, { 230, 114, 0 } } // Side move to remove probe + #define MAG_MOUNTED_STOW_4 { PROBE_STOW_FEEDRATE, { 210, 114, 20 } } // Side move to remove probe + #define MAG_MOUNTED_STOW_5 { PROBE_STOW_FEEDRATE, { 0, 0, 0 } } // Extra move if needed +#endif + +// Duet Smart Effector (for delta printers) - https://docs.duet3d.com/en/Duet3D_hardware/Accessories/Smart_Effector +// When the pin is defined you can use M672 to set/reset the probe sensitivity. //#define DUET_SMART_EFFECTOR #if ENABLED(DUET_SMART_EFFECTOR) #define SMART_EFFECTOR_MOD_PIN -1 // Connect a GPIO pin to the Smart Effector MOD pin @@ -952,17 +1627,55 @@ */ //#define SENSORLESS_PROBING -// -// For Z_PROBE_ALLEN_KEY see the Delta example configurations. -// +/** + * Allen key retractable z-probe as seen on many Kossel delta printers - https://reprap.org/wiki/Kossel#Autolevel_probe + * Deploys by touching z-axis belt. Retracts by pushing the probe down. + */ +//#define Z_PROBE_ALLEN_KEY +#if ENABLED(Z_PROBE_ALLEN_KEY) + // 2 or 3 sets of coordinates for deploying and retracting the spring loaded touch probe on G29, + // if servo actuated touch probe is not defined. Uncomment as appropriate for your printer/probe. + + #define Z_PROBE_ALLEN_KEY_DEPLOY_1 { 30.0, PRINTABLE_RADIUS, 100.0 } + #define Z_PROBE_ALLEN_KEY_DEPLOY_1_FEEDRATE XY_PROBE_FEEDRATE + + #define Z_PROBE_ALLEN_KEY_DEPLOY_2 { 0.0, PRINTABLE_RADIUS, 100.0 } + #define Z_PROBE_ALLEN_KEY_DEPLOY_2_FEEDRATE (XY_PROBE_FEEDRATE)/10 + + #define Z_PROBE_ALLEN_KEY_DEPLOY_3 { 0.0, (PRINTABLE_RADIUS) * 0.75, 100.0 } + #define Z_PROBE_ALLEN_KEY_DEPLOY_3_FEEDRATE XY_PROBE_FEEDRATE + + #define Z_PROBE_ALLEN_KEY_STOW_1 { -64.0, 56.0, 23.0 } // Move the probe into position + #define Z_PROBE_ALLEN_KEY_STOW_1_FEEDRATE XY_PROBE_FEEDRATE + + #define Z_PROBE_ALLEN_KEY_STOW_2 { -64.0, 56.0, 3.0 } // Push it down + #define Z_PROBE_ALLEN_KEY_STOW_2_FEEDRATE (XY_PROBE_FEEDRATE)/10 + + #define Z_PROBE_ALLEN_KEY_STOW_3 { -64.0, 56.0, 50.0 } // Move it up to clear + #define Z_PROBE_ALLEN_KEY_STOW_3_FEEDRATE XY_PROBE_FEEDRATE + + #define Z_PROBE_ALLEN_KEY_STOW_4 { 0.0, 0.0, 50.0 } + #define Z_PROBE_ALLEN_KEY_STOW_4_FEEDRATE XY_PROBE_FEEDRATE + +#endif // Z_PROBE_ALLEN_KEY /** * Nozzle-to-Probe offsets { X, Y, Z } * - * - Use a caliper or ruler to measure the distance from the tip of + * X and Y offset + * 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 * - For the Z offset use your best known value and adjust at runtime. - * - Probe Offsets can be tuned at runtime with 'M851', LCD menus, babystepping, etc. + * - Common probes trigger below the nozzle and have negative values for Z offset. + * - Probes triggering above the nozzle height are uncommon but do exist. When using + * probes such as this, carefully set Z_CLEARANCE_DEPLOY_PROBE and Z_CLEARANCE_BETWEEN_PROBES + * to avoid collisions during probing. + * + * Tune and Adjust + * - Probe Offsets can be tuned at runtime with 'M851', LCD menus, babystepping, etc. + * - PROBE_OFFSET_WIZARD (Configuration_adv.h) can be used for setting the Z offset. * * Assuming the typical work area orientation: * - Probe to RIGHT of the Nozzle has a Positive X offset @@ -986,20 +1699,66 @@ * | [-] | * O-- FRONT --+ */ -#define NOZZLE_TO_PROBE_OFFSET { 10, 10, 0 } +#define NOZZLE_TO_PROBE_OFFSET { 10, 10, 0 } // (mm) X, Y, Z distance from Nozzle tip to Probe trigger-point + +// Enable and set to use a specific tool for probing. Disable to allow any tool. +#define PROBING_TOOL 0 +#ifdef PROBING_TOOL + //#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 10 -// X and Y axis travel speed (mm/min) between probes -#define XY_PROBE_SPEED (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_SPEED_FAST HOMING_FEEDRATE_Z +// Feedrate for the first approach when double-probing (MULTIPLE_PROBING == 2) +#define Z_PROBE_FEEDRATE_FAST (4*60) // (mm/min) -// Feedrate (mm/min) for the "accurate" probe of each point -#define Z_PROBE_SPEED_SLOW (Z_PROBE_SPEED_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 + * A switch indicating proper deployment, or an optical + * switch triggered when the carriage is near the bed. + */ +//#define PROBE_ACTIVATION_SWITCH +#if ENABLED(PROBE_ACTIVATION_SWITCH) + #define PROBE_ACTIVATION_SWITCH_STATE LOW // State indicating probe is active + //#define PROBE_ACTIVATION_SWITCH_PIN PC6 // Override default pin +#endif + +/** + * Tare Probe (determine zero-point) prior to each probe. + * Useful for a strain gauge or piezo sensor that needs to factor out + * elements such as cables pulling on the carriage. + */ +//#define PROBE_TARE +#if ENABLED(PROBE_TARE) + #define PROBE_TARE_TIME 200 // (ms) Time to hold tare pin + #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 +#endif + +/** + * Probe Enable / Disable + * The probe only provides a triggered signal when enabled. + */ +//#define PROBE_ENABLE_DISABLE +#if ENABLED(PROBE_ENABLE_DISABLE) + //#define PROBE_ENABLE_PIN -1 // Override the default pin here +#endif /** * Multiple Probing @@ -1024,19 +1783,24 @@ * 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 // Z Clearance for Deploy/Stow -#define Z_CLEARANCE_BETWEEN_PROBES 5 // Z Clearance between probe points -#define Z_CLEARANCE_MULTI_PROBE 5 // Z Clearance between multiple probes -//#define Z_AFTER_PROBING 5 // Z position after probing is done +#define Z_CLEARANCE_DEPLOY_PROBE 10 // (mm) Z Clearance for Deploy/Stow +#define Z_CLEARANCE_BETWEEN_PROBES 5 // (mm) Z Clearance between probe points +#define Z_CLEARANCE_MULTI_PROBE 5 // (mm) Z Clearance between multiple probes +#define Z_PROBE_ERROR_TOLERANCE 3 // (mm) Tolerance for early trigger (<= -probe.offset.z + ZPET) +//#define Z_AFTER_PROBING 5 // (mm) Z position after probing is done -#define Z_PROBE_LOW_POINT -2 // Farthest distance below the trigger-point to go before stopping +#define Z_PROBE_LOW_POINT -2 // (mm) Farthest distance below the trigger-point to go before stopping -// For M851 give a range for adjusting the Z probe offset -#define Z_PROBE_OFFSET_RANGE_MIN -20 -#define Z_PROBE_OFFSET_RANGE_MAX 20 +// For M851 provide ranges for adjusting the X, Y, and Z probe offsets +//#define PROBE_OFFSET_XMIN -50 // (mm) +//#define PROBE_OFFSET_XMAX 50 // (mm) +//#define PROBE_OFFSET_YMIN -50 // (mm) +//#define PROBE_OFFSET_YMAX 50 // (mm) +//#define PROBE_OFFSET_ZMIN -20 // (mm) +//#define PROBE_OFFSET_ZMAX 20 // (mm) // Enable the M48 repeatability test to test probe accuracy //#define Z_MIN_PROBE_REPEATABILITY_TEST @@ -1057,40 +1821,64 @@ //#define PROBING_HEATERS_OFF // Turn heaters off when probing #if ENABLED(PROBING_HEATERS_OFF) //#define WAIT_FOR_BED_HEATER // Wait for bed to heat back up between probes (to improve accuracy) + //#define WAIT_FOR_HOTEND // Wait for hotend to heat back up between probes (to improve accuracy & prevent cold extrude) #endif //#define PROBING_FANS_OFF // Turn fans off when probing -//#define PROBING_STEPPERS_OFF // Turn steppers off (unless needed to hold position) when probing +//#define PROBING_ESTEPPERS_OFF // Turn all extruder steppers off when probing +//#define PROBING_STEPPERS_OFF // Turn all steppers off (unless needed to hold position) when probing (including extruders) //#define DELAY_BEFORE_PROBING 200 // (ms) To prevent vibrations from triggering piezo sensors +// Require minimum nozzle and/or bed temperature for probing +//#define PREHEAT_BEFORE_PROBING +#if ENABLED(PREHEAT_BEFORE_PROBING) + #define PROBING_NOZZLE_TEMP 120 // (Β°C) Only applies to E0 at this time + #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 +// :['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! -#define DISABLE_X false -#define DISABLE_Y false -#define DISABLE_Z false - -// Turn off the display blinking that warns about possible accuracy reduction -//#define DISABLE_REDUCED_ACCURACY_WARNING +//#define DISABLE_X +//#define DISABLE_Y +//#define DISABLE_Z +//#define DISABLE_I +//#define DISABLE_J +//#define DISABLE_K +//#define DISABLE_U +//#define DISABLE_V +//#define DISABLE_W // @section extruder -#define DISABLE_E false // Disable the extruder when not stepping -#define DISABLE_INACTIVE_EXTRUDER // Keep only the active extruder enabled +//#define DISABLE_E // Disable the extruder when not stepping +#define DISABLE_OTHER_EXTRUDERS // Keep only the active extruder enabled -// @section machine +// @section stepper drivers // Invert the stepper direction. Change (or reverse the motor connector) if an axis goes the wrong way. #define INVERT_X_DIR false #define INVERT_Y_DIR true #define INVERT_Z_DIR false - -// @section extruder +//#define INVERT_I_DIR false +//#define INVERT_J_DIR false +//#define INVERT_K_DIR false +//#define INVERT_U_DIR false +//#define INVERT_V_DIR false +//#define INVERT_W_DIR false // For direct drive extruder v9 set to true, for geared extruder set to false. #define INVERT_E0_DIR false @@ -1104,34 +1892,76 @@ // @section homing -//#define NO_MOTION_BEFORE_HOMING // Inhibit movement until all axes have been homed +//#define NO_MOTION_BEFORE_HOMING // Inhibit movement until all axes have been homed. Also enable HOME_AFTER_DEACTIVATE for extra safety. +//#define HOME_AFTER_DEACTIVATE // Require rehoming after steppers are deactivated. Also enable NO_MOTION_BEFORE_HOMING for extra safety. -//#define UNKNOWN_Z_NO_RAISE // Don't raise Z (lower the bed) if Z is "unknown." For beds that fall when Z is powered off. +/** + * Set Z_IDLE_HEIGHT if the Z-Axis moves on its own when steppers are disabled. + * - Use a low value (i.e., Z_MIN_POS) if the nozzle falls down to the bed. + * - Use a large value (i.e., Z_MAX_POS) if the bed falls down, away from the nozzle. + */ +//#define Z_IDLE_HEIGHT Z_HOME_POS -//#define Z_HOMING_HEIGHT 4 // (mm) Minimal Z height before homing (G28) for Z clearance above the bed, clamps, ... - // Be sure to have this much clearance over your Z_MAX_POS to prevent grinding. +//#define Z_CLEARANCE_FOR_HOMING 4 // (mm) Minimal Z height before homing (G28) for Z clearance above the bed, clamps, ... + // You'll need this much clearance above Z_MAX_POS to avoid grinding. -//#define Z_AFTER_HOMING 10 // (mm) Height to move to after homing Z +//#define Z_AFTER_HOMING 10 // (mm) Height to move to after homing (if Z was homed) +//#define XY_AFTER_HOMING { 10, 10 } // (mm) Move to an XY position after homing (and raising Z) + +//#define EVENT_GCODE_AFTER_HOMING "M300 P440 S200" // Commands to run after G28 (and move to XY_AFTER_HOMING) // Direction of endstops when homing; 1=MAX, -1=MIN // :[-1,1] #define X_HOME_DIR -1 #define Y_HOME_DIR -1 #define Z_HOME_DIR -1 +//#define I_HOME_DIR -1 +//#define J_HOME_DIR -1 +//#define K_HOME_DIR -1 +//#define U_HOME_DIR -1 +//#define V_HOME_DIR -1 +//#define W_HOME_DIR -1 -// @section machine +/** + * Safety Stops + * If an axis has endstops on both ends the one specified above is used for + * homing, while the other can be used for things like SD_ABORT_ON_ENDSTOP_HIT. + */ +//#define X_SAFETY_STOP +//#define Y_SAFETY_STOP +//#define Z_SAFETY_STOP +//#define I_SAFETY_STOP +//#define J_SAFETY_STOP +//#define K_SAFETY_STOP +//#define U_SAFETY_STOP +//#define V_SAFETY_STOP +//#define W_SAFETY_STOP -// The size of the print bed +// @section geometry + +// The size of the printable area #define X_BED_SIZE 200 #define Y_BED_SIZE 200 -// Travel limits (mm) after homing, corresponding to endstop positions. +// Travel limits (linear=mm, rotational=Β°) after homing, corresponding to endstop positions. #define X_MIN_POS 0 #define Y_MIN_POS 0 #define Z_MIN_POS 0 #define X_MAX_POS X_BED_SIZE #define Y_MAX_POS Y_BED_SIZE #define Z_MAX_POS 200 +//#define I_MIN_POS 0 +//#define I_MAX_POS 50 +//#define J_MIN_POS 0 +//#define J_MAX_POS 50 +//#define K_MIN_POS 0 +//#define K_MAX_POS 50 +//#define U_MIN_POS 0 +//#define U_MAX_POS 50 +//#define V_MIN_POS 0 +//#define V_MAX_POS 50 +//#define W_MIN_POS 0 +//#define W_MAX_POS 50 /** * Software Endstops @@ -1148,6 +1978,12 @@ #define MIN_SOFTWARE_ENDSTOP_X #define MIN_SOFTWARE_ENDSTOP_Y #define MIN_SOFTWARE_ENDSTOP_Z + #define MIN_SOFTWARE_ENDSTOP_I + #define MIN_SOFTWARE_ENDSTOP_J + #define MIN_SOFTWARE_ENDSTOP_K + #define MIN_SOFTWARE_ENDSTOP_U + #define MIN_SOFTWARE_ENDSTOP_V + #define MIN_SOFTWARE_ENDSTOP_W #endif // Max software endstops constrain movement within maximum coordinate bounds @@ -1156,16 +1992,30 @@ #define MAX_SOFTWARE_ENDSTOP_X #define MAX_SOFTWARE_ENDSTOP_Y #define MAX_SOFTWARE_ENDSTOP_Z + #define MAX_SOFTWARE_ENDSTOP_I + #define MAX_SOFTWARE_ENDSTOP_J + #define MAX_SOFTWARE_ENDSTOP_K + #define MAX_SOFTWARE_ENDSTOP_U + #define MAX_SOFTWARE_ENDSTOP_V + #define MAX_SOFTWARE_ENDSTOP_W #endif -#if EITHER(MIN_SOFTWARE_ENDSTOPS, MAX_SOFTWARE_ENDSTOPS) +#if ANY(MIN_SOFTWARE_ENDSTOPS, MAX_SOFTWARE_ENDSTOPS) //#define SOFT_ENDSTOPS_MENU_ITEM // Enable/Disable software endstops from the LCD #endif /** + * @section filament runout sensors + * * Filament Runout Sensors * Mechanical or opto endstops are used to check for the presence of filament. * + * IMPORTANT: Runout will only trigger if Marlin is aware that a print job is running. + * Marlin knows a print job is running when: + * 1. Running a print job from media started with M24. + * 2. The Print Job Timer has been started with M75. + * 3. The heaters were turned on and PRINTJOB_TIMER_AUTOSTART is enabled. + * * RAMPS-based boards use SERVO3_PIN for the first runout sensor. * For other boards you may need to define FIL_RUNOUT_PIN, FIL_RUNOUT2_PIN, etc. */ @@ -1173,12 +2023,49 @@ #if ENABLED(FILAMENT_RUNOUT_SENSOR) #define FIL_RUNOUT_ENABLED_DEFAULT true // Enable the sensor on startup. Override with M412 followed by M500. #define NUM_RUNOUT_SENSORS 1 // Number of sensors, up to one per extruder. Define a FIL_RUNOUT#_PIN for each. + #define FIL_RUNOUT_STATE LOW // Pin state indicating that filament is NOT present. #define FIL_RUNOUT_PULLUP // Use internal pullup for filament runout pins. //#define FIL_RUNOUT_PULLDOWN // Use internal pulldown for filament runout pins. + //#define WATCH_ALL_RUNOUT_SENSORS // Execute runout script on any triggering sensor, not only for the active extruder. + // This is automatically enabled for MIXING_EXTRUDERs. - // Set one or more commands to execute on filament runout. - // (After 'M412 H' Marlin will ask the host to handle the process.) + // Override individually if the runout sensors vary + //#define FIL_RUNOUT1_STATE LOW + //#define FIL_RUNOUT1_PULLUP + //#define FIL_RUNOUT1_PULLDOWN + + //#define FIL_RUNOUT2_STATE LOW + //#define FIL_RUNOUT2_PULLUP + //#define FIL_RUNOUT2_PULLDOWN + + //#define FIL_RUNOUT3_STATE LOW + //#define FIL_RUNOUT3_PULLUP + //#define FIL_RUNOUT3_PULLDOWN + + //#define FIL_RUNOUT4_STATE LOW + //#define FIL_RUNOUT4_PULLUP + //#define FIL_RUNOUT4_PULLDOWN + + //#define FIL_RUNOUT5_STATE LOW + //#define FIL_RUNOUT5_PULLUP + //#define FIL_RUNOUT5_PULLDOWN + + //#define FIL_RUNOUT6_STATE LOW + //#define FIL_RUNOUT6_PULLUP + //#define FIL_RUNOUT6_PULLDOWN + + //#define FIL_RUNOUT7_STATE LOW + //#define FIL_RUNOUT7_PULLUP + //#define FIL_RUNOUT7_PULLDOWN + + //#define FIL_RUNOUT8_STATE LOW + //#define FIL_RUNOUT8_PULLUP + //#define FIL_RUNOUT8_PULLDOWN + + // Commands to execute on filament runout. + // With multiple runout sensors use the %c placeholder for the current tool in commands (e.g., "M600 T%c") + // NOTE: After 'M412 H1' the host handles filament runout and this script does not apply. #define FILAMENT_RUNOUT_SCRIPT "M600" // After a runout is detected, continue printing this length of filament @@ -1191,8 +2078,52 @@ // as the filament moves. (Be sure to set FILAMENT_RUNOUT_DISTANCE_MM // large enough to avoid false positives.) //#define FILAMENT_MOTION_SENSOR - #endif -#endif + + #if ENABLED(FILAMENT_MOTION_SENSOR) + //#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 + + // Override individually if the motion sensors vary + //#define FIL_MOTION1_STATE LOW + //#define FIL_MOTION1_PULLUP + //#define FIL_MOTION1_PULLDOWN + + //#define FIL_MOTION2_STATE LOW + //#define FIL_MOTION2_PULLUP + //#define FIL_MOTION2_PULLDOWN + + //#define FIL_MOTION3_STATE LOW + //#define FIL_MOTION3_PULLUP + //#define FIL_MOTION3_PULLDOWN + + //#define FIL_MOTION4_STATE LOW + //#define FIL_MOTION4_PULLUP + //#define FIL_MOTION4_PULLDOWN + + //#define FIL_MOTION5_STATE LOW + //#define FIL_MOTION5_PULLUP + //#define FIL_MOTION5_PULLDOWN + + //#define FIL_MOTION6_STATE LOW + //#define FIL_MOTION6_PULLUP + //#define FIL_MOTION6_PULLDOWN + + //#define FIL_MOTION7_STATE LOW + //#define FIL_MOTION7_PULLUP + //#define FIL_MOTION7_PULLDOWN + + //#define FIL_MOTION8_STATE LOW + //#define FIL_MOTION8_PULLUP + //#define FIL_MOTION8_PULLDOWN + #endif // FILAMENT_SWITCH_AND_MOTION + #endif // FILAMENT_MOTION_SENSOR + #endif // FILAMENT_RUNOUT_DISTANCE_MM +#endif // FILAMENT_RUNOUT_SENSOR //=========================================================================== //=============================== Bed Leveling ============================== @@ -1239,27 +2170,62 @@ //#define MESH_BED_LEVELING /** - * Normally G28 leaves leveling disabled on completion. Enable - * this option to have G28 restore the prior leveling state. + * 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. + */ +//#define EVENT_GCODE_AFTER_G29 "G1 Z10 F12000\nG1 X15 Y330\nG1 Z0.5\nG1 Z10" + +/** + * Normally G28 leaves leveling disabled on completion. Enable one of + * these options to restore the prior leveling state or to always enable + * leveling immediately after G28. */ //#define RESTORE_LEVELING_AFTER_G28 +//#define ENABLE_LEVELING_AFTER_G28 + +/** + * Auto-leveling needs preheating + */ +//#define PREHEAT_BEFORE_LEVELING +#if ENABLED(PREHEAT_BEFORE_LEVELING) + #define LEVELING_NOZZLE_TEMP 120 // (Β°C) Only applies to E0 at this time + #define LEVELING_BED_TEMP 50 +#endif /** * Enable detailed logging of G28, G29, M48, etc. * Turn on with the command 'M111 S32'. - * NOTE: Requires a lot of PROGMEM! + * NOTE: Requires a lot of flash! */ //#define DEBUG_LEVELING_FEATURE -#if ANY(MESH_BED_LEVELING, AUTO_BED_LEVELING_BILINEAR, AUTO_BED_LEVELING_UBL) - // Gradually reduce leveling correction until a set height is reached, - // at which point movement will be level to the machine's XY plane. - // The height can be set with M420 Z - #define ENABLE_LEVELING_FADE_HEIGHT +#if ANY(MESH_BED_LEVELING, AUTO_BED_LEVELING_UBL, PROBE_MANUALLY) + // Set a height for the start of manual adjustment + #define MANUAL_PROBE_START_Z 0.2 // (mm) Comment out to use the last-measured height +#endif - // For Cartesian machines, instead of dividing moves on mesh boundaries, - // split up moves into short segments like a Delta. This follows the - // contours of the bed more closely than edge-to-edge straight moves. +#if ANY(MESH_BED_LEVELING, AUTO_BED_LEVELING_BILINEAR, AUTO_BED_LEVELING_UBL) + /** + * Gradually reduce leveling correction until a set height is reached, + * at which point movement will be level to the machine's XY plane. + * The height can be set with M420 Z + */ + #define ENABLE_LEVELING_FADE_HEIGHT + #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) + #define DEFAULT_LEVELING_FADE_HEIGHT 10.0 // (mm) Default fade height. + #endif + + /** + * For Cartesian machines, instead of dividing moves on mesh boundaries, + * split up moves into short segments like a Delta. This follows the + * contours of the bed more closely than edge-to-edge straight moves. + */ #define SEGMENT_LEVELED_MOVES #define LEVELED_SEGMENT_LENGTH 5.0 // (mm) Length of all segments (except the last one) @@ -1269,16 +2235,17 @@ //#define G26_MESH_VALIDATION #if ENABLED(G26_MESH_VALIDATION) #define MESH_TEST_NOZZLE_SIZE 0.4 // (mm) Diameter of primary nozzle. - #define MESH_TEST_LAYER_HEIGHT 0.2 // (mm) Default layer height for the G26 Mesh Validation Tool. - #define MESH_TEST_HOTEND_TEMP 205 // (Β°C) Default nozzle temperature for the G26 Mesh Validation Tool. - #define MESH_TEST_BED_TEMP 60 // (Β°C) Default bed temperature for the G26 Mesh Validation Tool. - #define G26_XY_FEEDRATE 20 // (mm/s) Feedrate for XY Moves for the G26 Mesh Validation Tool. + #define MESH_TEST_LAYER_HEIGHT 0.2 // (mm) Default layer height for G26. + #define MESH_TEST_HOTEND_TEMP 205 // (Β°C) Default nozzle temperature for G26. + #define MESH_TEST_BED_TEMP 60 // (Β°C) Default bed temperature for G26. + #define G26_XY_FEEDRATE 20 // (mm/s) Feedrate for G26 XY moves. + #define G26_XY_FEEDRATE_TRAVEL 100 // (mm/s) Feedrate for G26 XY travel moves. #define G26_RETRACT_MULTIPLIER 1.0 // G26 Q (retraction) used by default between mesh test elements. #endif #endif -#if EITHER(AUTO_BED_LEVELING_LINEAR, AUTO_BED_LEVELING_BILINEAR) +#if ANY(AUTO_BED_LEVELING_LINEAR, AUTO_BED_LEVELING_BILINEAR) // Set the number of grid points per dimension. #define GRID_MAX_POINTS_X 3 @@ -1294,7 +2261,7 @@ //#define EXTRAPOLATE_BEYOND_GRID // - // Experimental Subdivision of the grid by Catmull-Rom method. + // Subdivision of the grid by Catmull-Rom method. // Synthesizes intermediate points to produce a more detailed mesh. // //#define ABL_BILINEAR_SUBDIVISION @@ -1317,12 +2284,38 @@ #define GRID_MAX_POINTS_X 10 // Don't use more than 15 points per axis, implementation limited. #define GRID_MAX_POINTS_Y GRID_MAX_POINTS_X + //#define UBL_HILBERT_CURVE // Use Hilbert distribution for less travel when probing multiple points + + //#define UBL_TILT_ON_MESH_POINTS // Use nearest mesh points with G29 J for better Z reference + //#define UBL_TILT_ON_MESH_POINTS_3POINT // Use nearest mesh points with G29 J0 (3-point) + #define UBL_MESH_EDIT_MOVES_Z // Sophisticated users prefer no movement of nozzle #define UBL_SAVE_ACTIVE_ON_M500 // Save the currently active mesh in the current slot on M500 //#define UBL_Z_RAISE_WHEN_OFF_MESH 2.5 // When the nozzle is off the mesh, this value is used // as the Z-Height correction value. + //#define UBL_MESH_WIZARD // Run several commands in a row to get a complete mesh + + /** + * Probing not allowed within the position of an obstacle. + */ + //#define AVOID_OBSTACLES + #if ENABLED(AVOID_OBSTACLES) + #define CLIP_W 23 // Bed clip width, should be padded a few mm over its physical size + #define CLIP_H 14 // Bed clip height, should be padded a few mm over its physical size + + // Obstacle Rectangles defined as { X1, Y1, X2, Y2 } + #define OBSTACLE1 { (X_BED_SIZE) / 4 - (CLIP_W) / 2, 0, (X_BED_SIZE) / 4 + (CLIP_W) / 2, CLIP_H } + #define OBSTACLE2 { (X_BED_SIZE) * 3 / 4 - (CLIP_W) / 2, 0, (X_BED_SIZE) * 3 / 4 + (CLIP_W) / 2, CLIP_H } + #define OBSTACLE3 { (X_BED_SIZE) / 4 - (CLIP_W) / 2, (Y_BED_SIZE) - (CLIP_H), (X_BED_SIZE) / 4 + (CLIP_W) / 2, Y_BED_SIZE } + #define OBSTACLE4 { (X_BED_SIZE) * 3 / 4 - (CLIP_W) / 2, (Y_BED_SIZE) - (CLIP_H), (X_BED_SIZE) * 3 / 4 + (CLIP_W) / 2, Y_BED_SIZE } + + // The probed grid must be inset for G29 J. This is okay, since it is + // only used to compute a linear transformation for the mesh itself. + #define G29J_MESH_TILT_MARGIN ((CLIP_H) + 1) + #endif + #elif ENABLED(MESH_BED_LEVELING) //=========================================================================== @@ -1330,7 +2323,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 @@ -1350,21 +2343,40 @@ #endif // Add a menu item to move between bed corners for manual bed adjustment -//#define LEVEL_BED_CORNERS +//#define LCD_BED_TRAMMING -#if ENABLED(LEVEL_BED_CORNERS) - #define LEVEL_CORNERS_INSET_LFRB { 30, 30, 30, 30 } // (mm) Left, Front, Right, Back insets - #define LEVEL_CORNERS_HEIGHT 0.0 // (mm) Z height of nozzle at leveling points - #define LEVEL_CORNERS_Z_HOP 4.0 // (mm) Z height of nozzle between leveling points - //#define LEVEL_CENTER_TOO // Move to the center after the last corner +#if ENABLED(LCD_BED_TRAMMING) + #define BED_TRAMMING_INSET_LFRB { 30, 30, 30, 30 } // (mm) Left, Front, Right, Back insets + #define BED_TRAMMING_HEIGHT 0.0 // (mm) Z height of nozzle at tramming points + #define BED_TRAMMING_Z_HOP 4.0 // (mm) Z raise between tramming points + //#define BED_TRAMMING_INCLUDE_CENTER // Move to the center after the last corner + //#define BED_TRAMMING_USE_PROBE + #if ENABLED(BED_TRAMMING_USE_PROBE) + #define BED_TRAMMING_PROBE_TOLERANCE 0.1 // (mm) + #define BED_TRAMMING_VERIFY_RAISED // After adjustment triggers the probe, re-probe to verify + //#define BED_TRAMMING_AUDIO_FEEDBACK + #endif + + /** + * Corner Leveling Order + * + * Set 2 or 4 points. When 2 points are given, the 3rd is the center of the opposite edge. + * + * LF Left-Front RF Right-Front + * LB Left-Back RB Right-Back + * + * Examples: + * + * Default {LF,RB,LB,RF} {LF,RF} {LB,LF} + * LB --------- RB LB --------- RB LB --------- RB LB --------- RB + * | 4 3 | | 3 2 | | <3> | | 1 | + * | | | | | | | <3>| + * | 1 2 | | 1 4 | | 1 2 | | 2 | + * LF --------- RF LF --------- RF LF --------- RF LF --------- RF + */ + #define BED_TRAMMING_LEVELING_ORDER { LF, RF, RB, LB } #endif -/** - * Commands to execute at the end of G29 probing. - * Useful to retract or move the Z probe out of the way. - */ -//#define Z_PROBE_END_SCRIPT "G1 Z10 F12000\nG1 X15 Y330\nG1 Z0.5\nG1 Z10" - // @section homing // The center of the bed is at (X=0, Y=0) @@ -1375,26 +2387,33 @@ //#define MANUAL_X_HOME_POS 0 //#define MANUAL_Y_HOME_POS 0 //#define MANUAL_Z_HOME_POS 0 +//#define MANUAL_I_HOME_POS 0 +//#define MANUAL_J_HOME_POS 0 +//#define MANUAL_K_HOME_POS 0 +//#define MANUAL_U_HOME_POS 0 +//#define MANUAL_V_HOME_POS 0 +//#define MANUAL_W_HOME_POS 0 -// Use "Z Safe Homing" to avoid homing with a Z probe outside the bed area. -// -// With this feature enabled: -// -// - Allow Z homing only after X and Y homing AND stepper drivers still enabled. -// - If stepper drivers time out, it will need X and Y homing again before Z homing. -// - Move the Z probe (or nozzle) to a defined XY point before Z Homing. -// - Prevent Z homing when the Z probe is outside bed area. -// +/** + * Use "Z Safe Homing" to avoid homing with a Z probe outside the bed area. + * + * - Moves the Z probe (or nozzle) to a defined XY point before Z homing. + * - Allows Z homing only when XY positions are known and trusted. + * - If stepper drivers sleep, XY homing may be required again before Z homing. + */ //#define Z_SAFE_HOMING #if ENABLED(Z_SAFE_HOMING) - #define Z_SAFE_HOMING_X_POINT X_CENTER // X point for Z homing - #define Z_SAFE_HOMING_Y_POINT Y_CENTER // Y point for Z homing + #define Z_SAFE_HOMING_X_POINT X_CENTER // (mm) X point for Z homing + #define Z_SAFE_HOMING_Y_POINT Y_CENTER // (mm) Y point for Z homing + //#define Z_SAFE_HOMING_POINT_ABSOLUTE // Ignore home offsets (M206) for Z homing position #endif -// Homing speeds (mm/min) -#define HOMING_FEEDRATE_XY (50*60) -#define HOMING_FEEDRATE_Z (4*60) +// Homing speeds (linear=mm/min, rotational=Β°/min) +#define HOMING_FEEDRATE_MM_M { (50*60), (50*60), (4*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 @@ -1437,9 +2456,8 @@ #define XY_DIAG_BD 282.8427124746 #define XY_SIDE_AD 200 - // Or, set the default skew factors directly here - // to override the above measurements: - #define XY_SKEW_FACTOR 0.0 + // Or, set the XY skew factor directly: + //#define XY_SKEW_FACTOR 0.0 //#define SKEW_CORRECTION_FOR_Z #if ENABLED(SKEW_CORRECTION_FOR_Z) @@ -1448,8 +2466,10 @@ #define YZ_DIAG_AC 282.8427124746 #define YZ_DIAG_BD 282.8427124746 #define YZ_SIDE_AD 200 - #define XZ_SKEW_FACTOR 0.0 - #define YZ_SKEW_FACTOR 0.0 + + // Or, set the Z skew factors directly: + //#define XZ_SKEW_FACTOR 0.0 + //#define YZ_SKEW_FACTOR 0.0 #endif // Enable this option for M852 to set skew at runtime @@ -1460,7 +2480,7 @@ //============================= Additional Features =========================== //============================================================================= -// @section extras +// @section eeprom /** * EEPROM @@ -1472,13 +2492,16 @@ * M502 - Revert settings to "factory" defaults. (Follow with M500 to init the EEPROM.) */ //#define EEPROM_SETTINGS // Persistent storage with M500 and M501 -//#define DISABLE_M503 // Saves ~2700 bytes of PROGMEM. Disable for release! -#define EEPROM_CHITCHAT // Give feedback on EEPROM commands. Disable to save PROGMEM. +//#define DISABLE_M503 // Saves ~2700 bytes of flash. Disable for release! +#define EEPROM_CHITCHAT // Give feedback on EEPROM commands. Disable to save flash. #define EEPROM_BOOT_SILENT // Keep M503 quiet and only give errors during first load #if ENABLED(EEPROM_SETTINGS) //#define EEPROM_AUTO_INIT // Init EEPROM automatically on any errors. + //#define EEPROM_INIT_NOW // Init EEPROM on first boot after a new build. #endif +// @section host + // // Host Keepalive // @@ -1489,6 +2512,8 @@ #define DEFAULT_KEEPALIVE_INTERVAL 2 // Number of seconds between "busy" messages. Set with M113. #define BUSY_WHILE_HEATING // Some hosts require "busy" messages even during heating +// @section units + // // G20/G21 Inch mode support // @@ -1499,20 +2524,26 @@ // //#define TEMPERATURE_UNITS_SUPPORT -// @section temperature +// @section temperature presets -// Preheat Constants +// +// 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_CHAMBER 35 #define PREHEAT_1_FAN_SPEED 0 // Value from 0 to 255 #define PREHEAT_2_LABEL "ABS" #define PREHEAT_2_TEMP_HOTEND 240 #define PREHEAT_2_TEMP_BED 110 +#define PREHEAT_2_TEMP_CHAMBER 35 #define PREHEAT_2_FAN_SPEED 0 // Value from 0 to 255 /** + * @section nozzle park + * * Nozzle Park * * Park the nozzle at the given XYZ position on idle or G27. @@ -1528,15 +2559,16 @@ #if ENABLED(NOZZLE_PARK_FEATURE) // Specify a park position as { X, Y, Z_raise } #define NOZZLE_PARK_POINT { (X_MIN_POS + 10), (Y_MAX_POS - 10), 20 } - //#define NOZZLE_PARK_X_ONLY // X move only is required to park - //#define NOZZLE_PARK_Y_ONLY // Y move only is required to park + #define NOZZLE_PARK_MOVE 0 // Park motion: 0 = XY Move, 1 = X Only, 2 = Y Only, 3 = X before Y, 4 = Y before X #define NOZZLE_PARK_Z_RAISE_MIN 2 // (mm) Always raise Z by at least this distance #define NOZZLE_PARK_XY_FEEDRATE 100 // (mm/s) X and Y axes feedrate (also used for delta Z axis) #define NOZZLE_PARK_Z_FEEDRATE 5 // (mm/s) Z axis feedrate (not used for delta printers) #endif /** - * Clean Nozzle Feature -- EXPERIMENTAL + * @section nozzle clean + * + * Clean Nozzle Feature * * Adds the G12 command to perform a nozzle cleaning process. * @@ -1570,28 +2602,33 @@ * Before starting, the nozzle moves to NOZZLE_CLEAN_START_POINT. * * Caveats: The ending Z should be the same as starting Z. - * Attention: EXPERIMENTAL. G-code arguments may change. */ //#define NOZZLE_CLEAN_FEATURE #if ENABLED(NOZZLE_CLEAN_FEATURE) - // Default number of pattern repetitions - #define NOZZLE_CLEAN_STROKES 12 + #define NOZZLE_CLEAN_PATTERN_LINE // Provide 'G12 P0' - a simple linear cleaning pattern + #define NOZZLE_CLEAN_PATTERN_ZIGZAG // Provide 'G12 P1' - a zigzag cleaning pattern + #define NOZZLE_CLEAN_PATTERN_CIRCLE // Provide 'G12 P2' - a circular cleaning pattern - // Default number of triangles - #define NOZZLE_CLEAN_TRIANGLES 3 + // Default pattern to use when 'P' is not provided to G12. One of the enabled options above. + #define NOZZLE_CLEAN_DEFAULT_PATTERN 0 + + #define NOZZLE_CLEAN_STROKES 12 // Default number of pattern repetitions + + #if ENABLED(NOZZLE_CLEAN_PATTERN_ZIGZAG) + #define NOZZLE_CLEAN_TRIANGLES 3 // Default number of triangles + #endif // Specify positions for each tool as { { X, Y, Z }, { X, Y, Z } } // Dual hotend system may use { { -20, (Y_BED_SIZE / 2), (Z_MIN_POS + 1) }, { 420, (Y_BED_SIZE / 2), (Z_MIN_POS + 1) }} #define NOZZLE_CLEAN_START_POINT { { 30, 30, (Z_MIN_POS + 1) } } #define NOZZLE_CLEAN_END_POINT { { 100, 60, (Z_MIN_POS + 1) } } - // Circular pattern radius - #define NOZZLE_CLEAN_CIRCLE_RADIUS 6.5 - // Circular pattern circle fragments number - #define NOZZLE_CLEAN_CIRCLE_FN 10 - // Middle point of circle - #define NOZZLE_CLEAN_CIRCLE_MIDDLE NOZZLE_CLEAN_START_POINT + #if ENABLED(NOZZLE_CLEAN_PATTERN_CIRCLE) + #define NOZZLE_CLEAN_CIRCLE_RADIUS 6.5 // (mm) Circular pattern radius + #define NOZZLE_CLEAN_CIRCLE_FN 10 // Circular pattern circle number of segments + #define NOZZLE_CLEAN_CIRCLE_MIDDLE NOZZLE_CLEAN_START_POINT // Middle point of circle + #endif // Move the nozzle to the initial position after cleaning #define NOZZLE_CLEAN_GOBACK @@ -1602,19 +2639,34 @@ // For a purge/clean station mounted on the X axis //#define NOZZLE_CLEAN_NO_Y + // Require a minimum hotend temperature for cleaning + #define NOZZLE_CLEAN_MIN_TEMP 170 + //#define NOZZLE_CLEAN_HEATUP // Heat up the nozzle instead of skipping wipe + // Explicit wipe G-code script applies to a G12 with no arguments. //#define WIPE_SEQUENCE_COMMANDS "G1 X-17 Y25 Z10 F4000\nG1 Z1\nM114\nG1 X-17 Y25\nG1 X-17 Y95\nG1 X-17 Y25\nG1 X-17 Y95\nG1 X-17 Y25\nG1 X-17 Y95\nG1 X-17 Y25\nG1 X-17 Y95\nG1 X-17 Y25\nG1 X-17 Y95\nG1 X-17 Y25\nG1 X-17 Y95\nG1 Z15\nM400\nG0 X-10.0 Y-9.0" #endif +// @section host + /** * Print Job Timer * - * Automatically start and stop the print job timer on M104/M109/M190. + * Automatically start and stop the print job timer on M104/M109/M140/M190/M141/M191. + * The print job timer will only be stopped if the bed/chamber target temp is + * below BED_MINTEMP/CHAMBER_MINTEMP. * - * M104 (hotend, no wait) - high temp = none, low temp = stop timer - * M109 (hotend, wait) - high temp = start timer, low temp = stop timer - * M190 (bed, wait) - high temp = start timer, low temp = none + * M104 (hotend, no wait) - high temp = none, low temp = stop timer + * M109 (hotend, wait) - high temp = start timer, low temp = stop timer + * M140 (bed, no wait) - high temp = none, low temp = stop timer + * M190 (bed, wait) - high temp = start timer, low temp = none + * M141 (chamber, no wait) - high temp = none, low temp = stop timer + * M191 (chamber, wait) - high temp = start timer, low temp = none + * + * For M104/M109, high temp is anything over EXTRUDE_MINTEMP / 2. + * For M140/M190, high temp is anything over BED_MINTEMP. + * For M141/M191, high temp is anything over CHAMBER_MINTEMP. * * The timer can also be controlled with the following commands: * @@ -1624,6 +2676,8 @@ */ #define PRINTJOB_TIMER_AUTOSTART +// @section stats + /** * Print Counter * @@ -1637,6 +2691,11 @@ * View the current statistics with M78. */ //#define PRINTCOUNTER +#if ENABLED(PRINTCOUNTER) + #define PRINTCOUNTER_SAVE_INTERVAL 60 // (minutes) EEPROM save interval during print. A value of 0 will save stats at end of print. +#endif + +// @section security /** * Password @@ -1663,27 +2722,42 @@ #define PASSWORD_ON_STARTUP #define PASSWORD_UNLOCK_GCODE // Unlock with the M511 P command. Disable to prevent brute-force attack. #define PASSWORD_CHANGE_GCODE // Change the password with M512 P S. - //#define PASSWORD_ON_SD_PRINT_MENU // This does not prevent gcodes from running + //#define PASSWORD_ON_SD_PRINT_MENU // This does not prevent G-codes from running //#define PASSWORD_AFTER_SD_PRINT_END //#define PASSWORD_AFTER_SD_PRINT_ABORT //#include "Configuration_Secure.h" // External file with PASSWORD_DEFAULT_VALUE #endif -//============================================================================= -//============================= LCD and SD support ============================ -//============================================================================= +// @section media -// @section lcd +/** + * 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 /** * LCD LANGUAGE * * Select the language to display on the LCD. These languages are available: * - * en, an, bg, ca, cz, da, de, el, el_gr, es, eu, fi, fr, gl, hr, hu, it, - * jp_kana, ko_KR, nl, pl, pt, pt_br, ro, ru, sk, tr, uk, vi, zh_CN, zh_TW, test + * en, an, bg, ca, cz, da, de, el, el_CY, es, eu, fi, fr, gl, hr, hu, 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', 'el_gr':'Greek (Greece)', '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', 'tr':'Turkish', 'uk':'Ukrainian', 'vi':'Vietnamese', 'zh_CN':'Chinese (Simplified)', 'zh_TW':'Chinese (Traditional)', 'test':'TEST' } + * :{ '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)' } */ #define LCD_LANGUAGE en @@ -1712,37 +2786,12 @@ #define DISPLAY_CHARSET_HD44780 JAPANESE /** - * 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 -/** - * 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: SPI SPEED - * - * Enable one of the following items for a slower SPI transfer speed. - * This may be required to resolve "volume init" errors. - */ -//#define SPI_SPEED SPI_HALF_SPEED -//#define SPI_SPEED SPI_QUARTER_SPEED -//#define SPI_SPEED SPI_EIGHTH_SPEED - -/** - * SD CARD: ENABLE CRC - * - * Use CRC checks and retries on the SD communication. - */ -//#define SD_CHECK_AND_RETRY - /** * LCD Menu Items * @@ -1799,12 +2848,23 @@ // //#define REVERSE_SELECT_DIRECTION +// +// Encoder EMI Noise Filter +// +// This option increases encoder samples to filter out phantom encoder clicks caused by EMI noise. +// +//#define ENCODER_NOISE_FILTER +#if ENABLED(ENCODER_NOISE_FILTER) + #define ENCODER_SAMPLES 10 +#endif + // // Individual Axis Homing // // Add individual axis homing items (Home X, Home Y, and Home Z) to the LCD menu. // //#define INDIVIDUAL_AXIS_HOMING_MENU +//#define INDIVIDUAL_AXIS_HOMING_SUBMENU // // SPEAKER/BUZZER @@ -1824,10 +2884,23 @@ //#define LCD_FEEDBACK_FREQUENCY_DURATION_MS 2 //#define LCD_FEEDBACK_FREQUENCY_HZ 5000 +// +// Tone queue size, used to keep beeps from blocking execution. +// Default is 4, or override here. Costs 4 bytes of SRAM per entry. +// +//#define TONE_QUEUE_LENGTH 4 + +// +// A sequence of tones to play at startup, in pairs of tone (Hz), duration (ms). +// Silence in-between tones. +// +//#define STARTUP_TUNE { 698, 300, 0, 50, 523, 50, 0, 25, 494, 50, 0, 25, 523, 100, 0, 50, 554, 300, 0, 100, 523, 300 } + //============================================================================= //======================== LCD / Controller Selection ========================= //======================== (Character-based LCDs) ========================= //============================================================================= +// @section lcd // // RepRapDiscount Smart Controller. @@ -1837,9 +2910,17 @@ // //#define REPRAP_DISCOUNT_SMART_CONTROLLER +// +// GT2560 (YHCB2004) LCD Display +// +// Requires Testato, Koepel softwarewire library and +// Andriy Golovnya's LiquidCrystal_AIP31068 library. +// +//#define YHCB2004 + // // Original RADDS LCD Display+Encoder+SDCardReader -// http://doku.radds.org/dokumentation/lcd-display/ +// https://web.archive.org/web/20200719145306/doku.radds.org/dokumentation/lcd-display/ // //#define RADDS_DISPLAY @@ -1869,7 +2950,6 @@ // // RigidBot Panel V1.0 -// http://www.inventapart.com/ // //#define RIGIDBOT_PANEL @@ -1879,13 +2959,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. @@ -1906,15 +2988,16 @@ // // Elefu RA Board Control Panel -// 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 // // Sainsmart (YwRobot) LCD Displays // -// These require F.Malpartida's LiquidCrystal_I2C library -// https://bitbucket.org/fmalpartida/new-liquidcrystal/wiki/Home +// These require LiquidCrystal_I2C library: +// https://github.com/MarlinFirmware/New-LiquidCrystal +// https://github.com/fmalpartida/New-LiquidCrystal/wiki // //#define LCD_SAINSMART_I2C_1602 //#define LCD_SAINSMART_I2C_2004 @@ -1947,7 +3030,7 @@ // // -// 2-wire Non-latching LCD SR from https://goo.gl/aJJ4sH +// 2-wire Non-latching LCD SR from https://github.com/fmalpartida/New-LiquidCrystal/wiki/schematics#user-content-ShiftRegister_connection // LCD configuration: https://reprap.org/wiki/SAV_3D_LCD // //#define SAV_3DLCD @@ -1987,9 +3070,14 @@ // //#define REPRAP_DISCOUNT_FULL_GRAPHIC_SMART_CONTROLLER +// +// K.3D Full Graphic Smart Controller +// +//#define K3D_FULL_GRAPHIC_SMART_CONTROLLER + // // ReprapWorld Graphical LCD -// https://reprapworld.com/?products_details&products_id/1218 +// https://reprapworld.com/electronics/3d-printer-modules/autonomous-printing/graphical-lcd-screen-v1-0/ // //#define REPRAPWORLD_GRAPHICAL_LCD @@ -2001,6 +3089,11 @@ //#define VIKI2 //#define miniVIKI +// +// Alfawise Ex8 printer LCD marked as WYH L12864 COG +// +//#define WYH_L12864 + // // MakerLab Mini Panel with graphic // controller and SD support - https://reprap.org/wiki/Mini_panel @@ -2009,7 +3102,7 @@ // // MaKr3d Makr-Panel with graphic controller and SD support. -// https://reprap.org/wiki/MaKr3d_MaKrPanel +// https://reprap.org/wiki/MaKrPanel // //#define MAKRPANEL @@ -2027,7 +3120,7 @@ // // Cartesio UI -// 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 @@ -2048,15 +3141,21 @@ // //#define MKS_MINI_12864 +// +// MKS MINI12864 V3 is an alias for FYSETC_MINI_12864_2_1. Type A/B. NeoPixel RGB Backlight. +// +//#define MKS_MINI_12864_V3 + // // MKS LCD12864A/B with graphic controller and SD support. Follows MKS_MINI_12864 pinout. // https://www.aliexpress.com/item/33018110072.html // -//#define MKS_LCD12864 +//#define MKS_LCD12864A +//#define MKS_LCD12864B // // 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) @@ -2065,11 +3164,21 @@ //#define FYSETC_GENERIC_12864_1_1 // Larger display with basic ON/OFF backlight. // -// Factory display for Creality CR-10 -// https://www.aliexpress.com/item/32833148327.html +// BigTreeTech Mini 12864 V1.0 / V2.0 is an alias for FYSETC_MINI_12864_2_1. Type A/B. NeoPixel RGB Backlight. +// https://github.com/bigtreetech/MINI-12864 // -// This is RAMPS-compatible using a single 10-pin connector. -// (For CR-10 owners who want to replace the Melzi Creality board but retain the display) +//#define BTT_MINI_12864 + +// +// BEEZ MINI 12864 is an alias for FYSETC_MINI_12864_2_1. Type A/B. NeoPixel RGB Backlight. +// +//#define BEEZ_MINI_12864 + +// +// Factory display for Creality CR-10 / CR-7 / Ender-3 +// https://marlinfw.org/docs/hardware/controllers.html#cr10_stockdisplay +// +// Connect to EXP1 on RAMPS and compatible boards. // //#define CR10_STOCKDISPLAY @@ -2079,14 +3188,15 @@ //#define ENDER2_STOCKDISPLAY // -// ANET and Tronxy Graphical Controller -// -// Anet 128x64 full graphics lcd with rotary encoder as used on Anet A6 -// A clone of the RepRapDiscount full graphics display but with -// different pins/wiring (see pins_ANET_10.h). +// ANET and Tronxy 128Γ—64 Full Graphics Controller as used on Anet A6 // //#define ANET_FULL_GRAPHICS_LCD +// +// GUCOCO CTC 128Γ—64 Full Graphics Controller as used on GUCOCO CTC A10S +// +//#define CTC_A10S_A13 + // // AZSMZ 12864 LCD with SD // https://www.aliexpress.com/item/32837222770.html @@ -2099,6 +3209,12 @@ // //#define SILVER_GATE_GLCD_CONTROLLER +// +// eMotion Tech LCD with SD +// https://www.reprap-france.com/produit/1234568748-ecran-graphique-128-x-64-points-2-1 +// +//#define EMOTION_TECH_LCD + //============================================================================= //============================== OLED Displays ============================== //============================================================================= @@ -2123,19 +3239,19 @@ //#define OLED_PANEL_TINYBOY2 // -// MKS OLED 1.3" 128Γ—64 FULL GRAPHICS CONTROLLER +// MKS OLED 1.3" 128Γ—64 Full Graphics Controller // https://reprap.org/wiki/MKS_12864OLED // // 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 +// 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 // @@ -2149,30 +3265,73 @@ //#define OVERLORD_OLED // -// FYSETC OLED 2.42" 128Γ—64 FULL GRAPHICS CONTROLLER with WS2812 RGB +// FYSETC OLED 2.42" 128Γ—64 Full Graphics Controller with WS2812 RGB // Where to find : https://www.aliexpress.com/item/4000345255731.html //#define FYSETC_242_OLED_12864 // Uses the SSD1309 controller +// +// K.3D SSD1309 OLED 2.42" 128Γ—64 Full Graphics Controller +// +//#define K3D_242_OLED_CONTROLLER // Software SPI + //============================================================================= //========================== Extensible UI Displays =========================== //============================================================================= -// -// DGUS Touch Display with DWIN OS. (Choose one.) -// ORIGIN : https://www.aliexpress.com/item/32993409517.html -// FYSETC : https://www.aliexpress.com/item/32961471929.html -// -//#define DGUS_LCD_UI_ORIGIN -//#define DGUS_LCD_UI_FYSETC -//#define DGUS_LCD_UI_HIPRECY +/** + * DGUS Touch Display with DWIN OS. (Choose one.) + * + * ORIGIN (Marlin DWIN_SET) + * - Download https://github.com/coldtobi/Marlin_DGUS_Resources + * - Copy the downloaded DWIN_SET folder to the SD card. + * - Product: https://www.aliexpress.com/item/32993409517.html + * + * FYSETC (Supplier default) + * - Download https://github.com/FYSETC/FYSTLCD-2.0 + * - Copy the downloaded SCREEN folder to the SD card. + * - Product: https://www.aliexpress.com/item/32961471929.html + * + * HIPRECY (Supplier default) + * - Download https://github.com/HiPrecy/Touch-Lcd-LEO + * - Copy the downloaded DWIN_SET folder to the SD card. + * + * MKS (MKS-H43) (Supplier default) + * - Download https://github.com/makerbase-mks/MKS-H43 + * - Copy the downloaded DWIN_SET folder to the SD card. + * - Product: https://www.aliexpress.com/item/1005002008179262.html + * + * RELOADED (T5UID1) + * - Download https://github.com/Neo2003/DGUS-reloaded/releases + * - Copy the downloaded DWIN_SET folder to the SD card. + * + * IA_CREALITY (T5UID1) + * - Download https://github.com/InsanityAutomation/Marlin/raw/CrealityDwin_2.0/TM3D_Combined480272_Landscape_V7.7z + * - Copy the downloaded DWIN_SET folder to the SD card. + * + * E3S1PRO (T5L) + * - Download https://github.com/CrealityOfficial/Ender-3S1/archive/3S1_Plus_Screen.zip + * - Copy the downloaded DWIN_SET folder to the SD card. + * + * Flash display with DGUS Displays for Marlin: + * - Format the SD card to FAT32 with an allocation size of 4kb. + * - Download files as specified for your type of display. + * - Plug the microSD card into the back of the display. + * - Boot the display and wait for the update to complete. + * + * :[ 'ORIGIN', 'FYSETC', 'HYPRECY', 'MKS', 'RELOADED', 'IA_CREALITY', 'E3S1PRO' ] + */ +//#define DGUS_LCD_UI ORIGIN +#if DGUS_UI_IS(MKS) + #define USE_MKS_GREEN_UI +#elif DGUS_UI_IS(IA_CREALITY) + //#define LCD_SCREEN_ROTATE 90 // Portrait Mode or 800x480 displays + //#define IA_CREALITY_BOOT_DELAY 1500 // (ms) +#endif // -// Touch-screen LCD for Malyan M200/M300 printers +// LCD for Malyan M200/M300 printers // //#define MALYAN_LCD -#if ENABLED(MALYAN_LCD) - #define LCD_SERIAL_PORT 1 // Default is 1 for Malyan M200 -#endif // // Touch UI for FTDI EVE (FT800/FT810) displays @@ -2181,18 +3340,36 @@ //#define TOUCH_UI_FTDI_EVE // -// Touch-screen LCD for Anycubic printers +// Touch-screen LCD for Anycubic Chiron +// +//#define ANYCUBIC_LCD_CHIRON + +// +// Touch-screen LCD for Anycubic i3 Mega // //#define ANYCUBIC_LCD_I3MEGA -//#define ANYCUBIC_LCD_CHIRON -#if EITHER(ANYCUBIC_LCD_I3MEGA, ANYCUBIC_LCD_CHIRON) - #define LCD_SERIAL_PORT 3 // Default is 3 for Anycubic - //#define ANYCUBIC_LCD_DEBUG +#if ENABLED(ANYCUBIC_LCD_I3MEGA) + //#define ANYCUBIC_LCD_GCODE_EXT // Add ".gcode" to menu entries for DGUS clone compatibility #endif +// +// Touch-screen LCD for Anycubic Vyper +// +//#define ANYCUBIC_LCD_VYPER + +// +// Sovol SV-06 Resistive Touch Screen +// +//#define SOVOL_SV06_RTS + +// +// 320x240 Nextion 2.8" serial TFT Resistive Touch Screen NX3224T028 +// +//#define NEXTION_TFT + // // Third-party or vendor-customized controller interfaces. -// Sources should be installed in 'src/lcd/extensible_ui'. +// Sources should be installed in 'src/lcd/extui'. // //#define EXTENSIBLE_UI @@ -2205,22 +3382,107 @@ //============================================================================= /** - * TFT Type - Select your Display type - * - * Available options are: - * MKS_TS35_V2_0, - * MKS_ROBIN_TFT24, MKS_ROBIN_TFT28, MKS_ROBIN_TFT32, MKS_ROBIN_TFT35, - * MKS_ROBIN_TFT43, MKS_ROBIN_TFT_V1_1R - * TFT_TRONXY_X5SA, ANYCUBIC_TFT35, LONGER_LK_TFT28 - * TFT_GENERIC - * - * For TFT_GENERIC, you need to configure these 3 options: - * Driver: TFT_DRIVER - * Current Drivers are: AUTO, ST7735, ST7789, ST7796, R61505, ILI9328, ILI9341, ILI9488 - * Resolution: TFT_WIDTH and TFT_HEIGHT - * Interface: TFT_INTERFACE_FSMC or TFT_INTERFACE_SPI + * Specific TFT Model Presets. Enable one of the following options + * or enable TFT_GENERIC and set sub-options. */ + +// +// 480x320, 3.5", SPI Display with Rotary Encoder from MKS +// Usually paired with MKS Robin Nano V2 & V3 +// https://github.com/makerbase-mks/MKS-TFT-Hardware/tree/master/MKS%20TS35 +// +//#define MKS_TS35_V2_0 + +// +// 320x240, 2.4", FSMC Display From MKS +// Usually paired with MKS Robin Nano V1.2 +// +//#define MKS_ROBIN_TFT24 + +// +// 320x240, 2.8", FSMC Display From MKS +// Usually paired with MKS Robin Nano V1.2 +// +//#define MKS_ROBIN_TFT28 + +// +// 320x240, 3.2", FSMC Display From MKS +// Usually paired with MKS Robin Nano V1.2 +// +//#define MKS_ROBIN_TFT32 + +// +// 480x320, 3.5", FSMC Display From MKS +// Usually paired with MKS Robin Nano V1.2 +// +//#define MKS_ROBIN_TFT35 + +// +// 480x272, 4.3", FSMC Display From MKS +// +//#define MKS_ROBIN_TFT43 + +// +// 320x240, 3.2", FSMC Display From MKS +// Usually paired with MKS Robin +// +//#define MKS_ROBIN_TFT_V1_1R + +// +// 480x320, 3.5", FSMC Stock Display from Tronxy +// +//#define TFT_TRONXY_X5SA + +// +// 480x320, 3.5", FSMC Stock Display from AnyCubic +// +//#define ANYCUBIC_TFT35 + +// +// 320x240, 2.8", FSMC Stock Display from Longer/Alfawise +// +//#define LONGER_LK_TFT28 + +// +// 320x240, 2.8", FSMC Stock Display from ET4 +// +//#define ANET_ET4_TFT28 + +// +// 480x320, 3.5", FSMC Stock Display from ET5 +// +//#define ANET_ET5_TFT35 + +// +// 1024x600, 7", RGB Stock Display with Rotary Encoder from BIQU BX +// https://github.com/bigtreetech/BIQU-BX/tree/master/Hardware +// +//#define BIQU_BX_TFT70 + +// +// 480x320, 3.5", SPI Stock Display with Rotary Encoder from BIQU B1 SE Series +// https://github.com/bigtreetech/TFT35-SPI/tree/master/v1 +// +//#define BTT_TFT35_SPI_V1_0 + +// +// Generic TFT with detailed options +// //#define TFT_GENERIC +#if ENABLED(TFT_GENERIC) + // :[ 'AUTO', 'ST7735', 'ST7789', 'ST7796', 'R61505', 'ILI9328', 'ILI9341', 'ILI9488' ] + #define TFT_DRIVER AUTO + + // Interface. Enable one of the following options: + //#define TFT_INTERFACE_FSMC + //#define TFT_INTERFACE_SPI + + // TFT Resolution. Enable one of the following options: + //#define TFT_RES_320x240 + //#define TFT_RES_480x272 + //#define TFT_RES_480x320 + //#define TFT_RES_1024x600 +#endif /** * TFT UI - User Interface Selection. Enable one of the following options: @@ -2236,6 +3498,36 @@ //#define TFT_COLOR_UI //#define TFT_LVGL_UI +#if ENABLED(TFT_COLOR_UI) + /** + * 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 + * + * 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 BLACK_MARLIN + + //#define TFT_SHARED_IO // I/O is shared between TFT display and other devices. Disable async data transfer. + + #define COMPACT_MARLIN_BOOT_LOGO // Use compressed data to save Flash space +#endif + +#if ENABLED(TFT_LVGL_UI) + //#define MKS_WIFI_MODULE // MKS WiFi module +#endif + /** * TFT Rotation. Set to one of the following values: * @@ -2243,6 +3535,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 @@ -2253,22 +3547,44 @@ // // Ender-3 v2 OEM display. A DWIN display with Rotary Encoder. // -//#define DWIN_CREALITY_LCD +//#define DWIN_CREALITY_LCD // Creality UI +//#define DWIN_LCD_PROUI // Pro UI by MRiscoC +//#define DWIN_CREALITY_LCD_JYERSUI // Jyers UI by Jacob Myers +//#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 // -// ADS7843/XPT2046 ADC Touchscreen such as ILI9341 2.8 +// Touch Screen Settings // //#define TOUCH_SCREEN #if ENABLED(TOUCH_SCREEN) - #define BUTTON_DELAY_EDIT 50 // (ms) Button repeat delay for edit screens - #define BUTTON_DELAY_MENU 250 // (ms) Button repeat delay for menus + #define BUTTON_DELAY_EDIT 50 // (ms) Button repeat delay for edit screens + #define BUTTON_DELAY_MENU 250 // (ms) Button repeat delay for menus + + #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 - //#define XPT2046_X_CALIBRATION 12316 - //#define XPT2046_Y_CALIBRATION -8981 - //#define XPT2046_X_OFFSET -43 - //#define XPT2046_Y_OFFSET 257 + //#define TOUCH_CALIBRATION_X 12316 + //#define TOUCH_CALIBRATION_Y -8981 + //#define TOUCH_OFFSET_X -43 + //#define TOUCH_OFFSET_Y 257 + //#define TOUCH_ORIENTATION TOUCH_LANDSCAPE + + #if ALL(TOUCH_SCREEN_CALIBRATION, EEPROM_SETTINGS) + #define TOUCH_CALIBRATION_AUTO_SAVE // Auto save successful calibration values to EEPROM + #endif + + #if ENABLED(TFT_COLOR_UI) + //#define SINGLE_TOUCH_NAVIGATION + #endif #endif // @@ -2276,47 +3592,61 @@ // 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 +// +//#define EASYTHREED_UI //============================================================================= //=============================== Extra Features ============================== //============================================================================= -// @section extras +// @section fans // Set number of user-controlled fans. Disable to use all board-defined fans. // :[1,2,3,4,5,6,7,8] //#define NUM_M106_FANS 1 -// Increase the FAN PWM frequency. Removes the PWM noise but increases heating in the FET/Arduino -//#define FAST_PWM_FAN - -// Use software PWM to drive the fan, as for the heaters. This uses a very low frequency -// which is not as annoying as with the hardware PWM. On the other hand, if this frequency -// is too low, you should also increment SOFT_PWM_SCALE. +/** + * Use software PWM to drive the fan, as for the heaters. This uses a very low frequency + * which is not as annoying as with the hardware PWM. On the other hand, if this frequency + * 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 -// 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) -//#define TEMP_STAT_LEDS +// @section extras // Support for the BariCUDA Paste Extruder //#define BARICUDA +// @section lights + +/** + * 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 //#define BLINKM @@ -2337,51 +3667,66 @@ * luminance values can be set from 0 to 255. * For NeoPixel LED an overall brightness parameter is also available. * - * *** CAUTION *** + * === CAUTION === * LED Strips require a MOSFET Chip between PWM lines and LEDs, * as the Arduino cannot handle the current the LEDs will require. * Failure to follow this precaution can destroy your Arduino! + * * NOTE: A separate 5V power supply is required! The NeoPixel LED needs * more current than the Arduino 5V linear regulator can produce. - * *** CAUTION *** * - * LED Type. Enable only one of the following two options. + * Requires PWM frequency between 50 <> 100Hz (Check HAL or variant) + * Use FAST_PWM_FAN, if possible, to reduce fan noise. */ + +// LED Type. Enable only one of the following two options: //#define RGB_LED //#define RGBW_LED -#if EITHER(RGB_LED, RGBW_LED) +#if ANY(RGB_LED, RGBW_LED) //#define RGB_LED_R_PIN 34 //#define RGB_LED_G_PIN 43 //#define RGB_LED_B_PIN 35 //#define RGB_LED_W_PIN -1 #endif +#if ANY(RGB_LED, RGBW_LED, PCA9632) + //#define RGB_STARTUP_TEST // For PWM pins, fade between all colors + #if ENABLED(RGB_STARTUP_TEST) + #define RGB_STARTUP_TEST_INNER_MS 10 // (ms) Reduce or increase fading speed + #endif +#endif + // Support for Adafruit NeoPixel LED driver //#define NEOPIXEL_LED #if ENABLED(NEOPIXEL_LED) - #define NEOPIXEL_TYPE NEO_GRBW // NEO_GRBW / NEO_GRB - four/three channel driver type (defined in Adafruit_NeoPixel.h) - #define NEOPIXEL_PIN 4 // LED driving pin - //#define NEOPIXEL2_TYPE NEOPIXEL_TYPE - //#define NEOPIXEL2_PIN 5 - #define NEOPIXEL_PIXELS 30 // Number of LEDs in the strip. (Longest strip when NEOPIXEL2_SEPARATE is disabled.) - #define NEOPIXEL_IS_SEQUENTIAL // Sequential display for temperature change - LED by LED. Disable to change all LEDs at once. - #define NEOPIXEL_BRIGHTNESS 127 // Initial brightness (0-255) - //#define NEOPIXEL_STARTUP_TEST // Cycle through colors at startup + #define NEOPIXEL_TYPE NEO_GRBW // NEO_GRBW, NEO_RGBW, NEO_GRB, NEO_RBG, etc. + // See https://github.com/adafruit/Adafruit_NeoPixel/blob/master/Adafruit_NeoPixel.h + //#define NEOPIXEL_PIN 4 // LED driving pin + //#define NEOPIXEL2_TYPE NEOPIXEL_TYPE + //#define NEOPIXEL2_PIN 5 + #define NEOPIXEL_PIXELS 30 // Number of LEDs in the strip. (Longest strip when NEOPIXEL2_SEPARATE is disabled.) + #define NEOPIXEL_IS_SEQUENTIAL // Sequential display for temperature change - LED by LED. Disable to change all LEDs at once. + #define NEOPIXEL_BRIGHTNESS 127 // Initial brightness (0-255) + //#define NEOPIXEL_STARTUP_TEST // Cycle through colors at startup // Support for second Adafruit NeoPixel LED driver controlled with M150 S1 ... //#define NEOPIXEL2_SEPARATE #if ENABLED(NEOPIXEL2_SEPARATE) - #define NEOPIXEL2_PIXELS 15 // Number of LEDs in the second strip - #define NEOPIXEL2_BRIGHTNESS 127 // Initial brightness (0-255) - #define NEOPIXEL2_STARTUP_TEST // Cycle through colors at startup + #define NEOPIXEL2_PIXELS 15 // Number of LEDs in the second strip + #define NEOPIXEL2_BRIGHTNESS 127 // Initial brightness (0-255) + #define NEOPIXEL2_STARTUP_TEST // Cycle through colors at startup + #define NEOPIXEL_M150_DEFAULT -1 // Default strip for M150 without 'S'. Use -1 to set all by default. #else - //#define NEOPIXEL2_INSERIES // Default behavior is NeoPixel 2 in parallel + //#define NEOPIXEL2_INSERIES // Default behavior is NeoPixel 2 in parallel #endif - // Use a single NeoPixel LED for static (background) lighting - //#define NEOPIXEL_BKGD_LED_INDEX 0 // Index of the LED to use - //#define NEOPIXEL_BKGD_COLOR { 255, 255, 255, 0 } // R, G, B, W + // Use some of the NeoPixel LEDs for static (background) lighting + //#define NEOPIXEL_BKGD_INDEX_FIRST 0 // Index of the first background LED + //#define NEOPIXEL_BKGD_INDEX_LAST 5 // Index of the last background LED + //#define NEOPIXEL_BKGD_COLOR { 255, 255, 255, 0 } // R, G, B, W + //#define NEOPIXEL_BKGD_TIMEOUT_COLOR { 25, 25, 25, 0 } // R, G, B, W + //#define NEOPIXEL_BKGD_ALWAYS_ON // Keep the backlight on when other NeoPixels are off #endif /** @@ -2399,6 +3744,8 @@ #define PRINTER_EVENT_LEDS #endif +// @section servos + /** * Number of servos * @@ -2406,9 +3753,9 @@ * Set this manually if there are extra servos needing manual control. * Set to 0 to turn off servo support. */ -//#define NUM_SERVOS 3 // Servo index starts with 0 for M280 command +//#define NUM_SERVOS 3 // Note: Servo index starts with 0 for M280-M282 commands -// (ms) Delay before the next move will start, to give the servo time to reach its target angle. +// (ms) Delay before the next move will start, to give the servo time to reach its target angle. // 300ms is a good value but you can try less delay. // If the servo can't reach the requested position, increase it. #define SERVO_DELAY { 300 } @@ -2418,3 +3765,6 @@ // Edit servo angles with M281 and save to EEPROM with M500 //#define EDITABLE_SERVO_ANGLES + +// Disable servo with M282 to reduce power consumption, noise, and heat when not in use +//#define SERVO_DETACH_GCODE diff --git a/Marlin/Configuration_adv.h b/Marlin/Configuration_adv.h index 634dce608d..e46a3af2de 100644 --- a/Marlin/Configuration_adv.h +++ b/Marlin/Configuration_adv.h @@ -30,7 +30,26 @@ * * Basic settings can be found in Configuration.h */ -#define CONFIGURATION_ADV_H_VERSION 020007 +#define CONFIGURATION_ADV_H_VERSION 02010300 + +// @section develop + +/** + * Configuration Export + * + * Export the configuration as part of the build. (See signature.py) + * Output files are saved with the build (e.g., .pio/build/mega2560). + * + * See `build_all_examples --ini` as an example of config.ini archiving. + * + * 1 = marlin_config.json - Dictionary containing the configuration. + * This file is also generated for CONFIGURATION_EMBEDDING. + * 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 105 // :[1:'JSON', 2:'config.ini', 3:'schema.json', 4:'schema.yml', 5:'Config.h'] //=========================================================================== //============================= Thermal Settings ============================ @@ -54,73 +73,130 @@ // Custom Thermistor 1000 parameters // #if TEMP_SENSOR_0 == 1000 - #define HOTEND0_PULLUP_RESISTOR_OHMS 4700 // Pullup resistor - #define HOTEND0_RESISTANCE_25C_OHMS 100000 // Resistance at 25C - #define HOTEND0_BETA 3950 // Beta value + #define HOTEND0_PULLUP_RESISTOR_OHMS 4700 // Pullup resistor + #define HOTEND0_RESISTANCE_25C_OHMS 100000 // Resistance at 25C + #define HOTEND0_BETA 3950 // Beta value + #define HOTEND0_SH_C_COEFF 0 // Steinhart-Hart C coefficient #endif #if TEMP_SENSOR_1 == 1000 - #define HOTEND1_PULLUP_RESISTOR_OHMS 4700 // Pullup resistor - #define HOTEND1_RESISTANCE_25C_OHMS 100000 // Resistance at 25C - #define HOTEND1_BETA 3950 // Beta value + #define HOTEND1_PULLUP_RESISTOR_OHMS 4700 // Pullup resistor + #define HOTEND1_RESISTANCE_25C_OHMS 100000 // Resistance at 25C + #define HOTEND1_BETA 3950 // Beta value + #define HOTEND1_SH_C_COEFF 0 // Steinhart-Hart C coefficient #endif #if TEMP_SENSOR_2 == 1000 - #define HOTEND2_PULLUP_RESISTOR_OHMS 4700 // Pullup resistor - #define HOTEND2_RESISTANCE_25C_OHMS 100000 // Resistance at 25C - #define HOTEND2_BETA 3950 // Beta value + #define HOTEND2_PULLUP_RESISTOR_OHMS 4700 // Pullup resistor + #define HOTEND2_RESISTANCE_25C_OHMS 100000 // Resistance at 25C + #define HOTEND2_BETA 3950 // Beta value + #define HOTEND2_SH_C_COEFF 0 // Steinhart-Hart C coefficient #endif #if TEMP_SENSOR_3 == 1000 - #define HOTEND3_PULLUP_RESISTOR_OHMS 4700 // Pullup resistor - #define HOTEND3_RESISTANCE_25C_OHMS 100000 // Resistance at 25C - #define HOTEND3_BETA 3950 // Beta value + #define HOTEND3_PULLUP_RESISTOR_OHMS 4700 // Pullup resistor + #define HOTEND3_RESISTANCE_25C_OHMS 100000 // Resistance at 25C + #define HOTEND3_BETA 3950 // Beta value + #define HOTEND3_SH_C_COEFF 0 // Steinhart-Hart C coefficient #endif #if TEMP_SENSOR_4 == 1000 - #define HOTEND4_PULLUP_RESISTOR_OHMS 4700 // Pullup resistor - #define HOTEND4_RESISTANCE_25C_OHMS 100000 // Resistance at 25C - #define HOTEND4_BETA 3950 // Beta value + #define HOTEND4_PULLUP_RESISTOR_OHMS 4700 // Pullup resistor + #define HOTEND4_RESISTANCE_25C_OHMS 100000 // Resistance at 25C + #define HOTEND4_BETA 3950 // Beta value + #define HOTEND4_SH_C_COEFF 0 // Steinhart-Hart C coefficient #endif #if TEMP_SENSOR_5 == 1000 - #define HOTEND5_PULLUP_RESISTOR_OHMS 4700 // Pullup resistor - #define HOTEND5_RESISTANCE_25C_OHMS 100000 // Resistance at 25C - #define HOTEND5_BETA 3950 // Beta value + #define HOTEND5_PULLUP_RESISTOR_OHMS 4700 // Pullup resistor + #define HOTEND5_RESISTANCE_25C_OHMS 100000 // Resistance at 25C + #define HOTEND5_BETA 3950 // Beta value + #define HOTEND5_SH_C_COEFF 0 // Steinhart-Hart C coefficient #endif #if TEMP_SENSOR_6 == 1000 - #define HOTEND6_PULLUP_RESISTOR_OHMS 4700 // Pullup resistor - #define HOTEND6_RESISTANCE_25C_OHMS 100000 // Resistance at 25C - #define HOTEND6_BETA 3950 // Beta value + #define HOTEND6_PULLUP_RESISTOR_OHMS 4700 // Pullup resistor + #define HOTEND6_RESISTANCE_25C_OHMS 100000 // Resistance at 25C + #define HOTEND6_BETA 3950 // Beta value + #define HOTEND6_SH_C_COEFF 0 // Steinhart-Hart C coefficient #endif #if TEMP_SENSOR_7 == 1000 - #define HOTEND7_PULLUP_RESISTOR_OHMS 4700 // Pullup resistor - #define HOTEND7_RESISTANCE_25C_OHMS 100000 // Resistance at 25C - #define HOTEND7_BETA 3950 // Beta value + #define HOTEND7_PULLUP_RESISTOR_OHMS 4700 // Pullup resistor + #define HOTEND7_RESISTANCE_25C_OHMS 100000 // Resistance at 25C + #define HOTEND7_BETA 3950 // Beta value + #define HOTEND7_SH_C_COEFF 0 // Steinhart-Hart C coefficient #endif #if TEMP_SENSOR_BED == 1000 - #define BED_PULLUP_RESISTOR_OHMS 4700 // Pullup resistor - #define BED_RESISTANCE_25C_OHMS 100000 // Resistance at 25C - #define BED_BETA 3950 // Beta value + #define BED_PULLUP_RESISTOR_OHMS 4700 // Pullup resistor + #define BED_RESISTANCE_25C_OHMS 100000 // Resistance at 25C + #define BED_BETA 3950 // Beta value + #define BED_SH_C_COEFF 0 // Steinhart-Hart C coefficient #endif #if TEMP_SENSOR_CHAMBER == 1000 - #define CHAMBER_PULLUP_RESISTOR_OHMS 4700 // Pullup resistor - #define CHAMBER_RESISTANCE_25C_OHMS 100000 // Resistance at 25C - #define CHAMBER_BETA 3950 // Beta value + #define CHAMBER_PULLUP_RESISTOR_OHMS 4700 // Pullup resistor + #define CHAMBER_RESISTANCE_25C_OHMS 100000 // Resistance at 25C + #define CHAMBER_BETA 3950 // Beta value + #define CHAMBER_SH_C_COEFF 0 // Steinhart-Hart C coefficient #endif -// -// Hephestos 2 24V heated bed upgrade kit. -// https://store.bq.com/en/heated-bed-kit-hephestos2 -// +#if TEMP_SENSOR_COOLER == 1000 + #define COOLER_PULLUP_RESISTOR_OHMS 4700 // Pullup resistor + #define COOLER_RESISTANCE_25C_OHMS 100000 // Resistance at 25C + #define COOLER_BETA 3950 // Beta value + #define COOLER_SH_C_COEFF 0 // Steinhart-Hart C coefficient +#endif + +#if TEMP_SENSOR_PROBE == 1000 + #define PROBE_PULLUP_RESISTOR_OHMS 4700 // Pullup resistor + #define PROBE_RESISTANCE_25C_OHMS 100000 // Resistance at 25C + #define PROBE_BETA 3950 // Beta value + #define PROBE_SH_C_COEFF 0 // Steinhart-Hart C coefficient +#endif + +#if TEMP_SENSOR_BOARD == 1000 + #define BOARD_PULLUP_RESISTOR_OHMS 4700 // Pullup resistor + #define BOARD_RESISTANCE_25C_OHMS 100000 // Resistance at 25C + #define BOARD_BETA 3950 // Beta value + #define BOARD_SH_C_COEFF 0 // Steinhart-Hart C coefficient +#endif + +#if TEMP_SENSOR_REDUNDANT == 1000 + #define REDUNDANT_PULLUP_RESISTOR_OHMS 4700 // Pullup resistor + #define REDUNDANT_RESISTANCE_25C_OHMS 100000 // Resistance at 25C + #define REDUNDANT_BETA 3950 // Beta value + #define REDUNDANT_SH_C_COEFF 0 // Steinhart-Hart C coefficient +#endif + +/** + * 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_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. + +//#define MAX31865_USE_AUTO_MODE // Read faster and more often than 1-shot; bias voltage always on; slight effect on RTD temperature. +//#define MAX31865_MIN_SAMPLING_TIME_MSEC 100 // (ms) 1-shot: minimum read interval. Reduces bias voltage effects by leaving sensor unpowered for longer intervals. +//#define MAX31865_IGNORE_INITIAL_FAULTY_READS 10 // Ignore some read faults (keeping the temperature reading) to work around a possible issue (#23439). + +//#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. + * https://www.en3dstudios.com/product/bq-hephestos-2-heated-bed-kit/ + */ //#define HEPHESTOS2_HEATED_BED_KIT #if ENABLED(HEPHESTOS2_HEATED_BED_KIT) - #undef TEMP_SENSOR_BED - #define TEMP_SENSOR_BED 70 #define HEATER_BED_INVERTING true #endif @@ -129,7 +205,7 @@ // #if DISABLED(PIDTEMPBED) #define BED_CHECK_INTERVAL 5000 // (ms) Interval between checks in bang-bang control - #if ENABLED(BED_LIMIT_SWITCHING) + #if ANY(BED_LIMIT_SWITCHING, PELTIER_BED) #define BED_HYSTERESIS 2 // (Β°C) Only set the relevant heater state when ABS(T-target) > BED_HYSTERESIS #endif #endif @@ -137,17 +213,23 @@ // // Heated Chamber options // + #if TEMP_SENSOR_CHAMBER - #define CHAMBER_MINTEMP 5 - #define CHAMBER_MAXTEMP 60 - #define TEMP_CHAMBER_HYSTERESIS 1 // (Β°C) Temperature proximity considered "close enough" to the target - //#define CHAMBER_LIMIT_SWITCHING - //#define HEATER_CHAMBER_PIN 44 // Chamber heater on/off pin + //#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_HYSTERESIS 2 // (Β°C) Only set the relevant heater state when ABS(T-target) > CHAMBER_HYSTERESIS + #endif + #endif //#define CHAMBER_FAN // Enable a fan on the chamber #if ENABLED(CHAMBER_FAN) - #define CHAMBER_FAN_MODE 2 // Fan control mode: 0=Static; 1=Linear increase when temp is higher than target; 2=V-shaped curve. + //#define CHAMBER_FAN_INDEX 2 // Index of a fan to repurpose as the chamber fan. (Default: first unused fan) + #define CHAMBER_FAN_MODE 2 // Fan control mode: 0=Static; 1=Linear increase when temp is higher than target; 2=V-shaped curve; 3=similar to 1 but fan is always on. #if CHAMBER_FAN_MODE == 0 #define CHAMBER_FAN_BASE 255 // Chamber fan PWM (0-255) #elif CHAMBER_FAN_MODE == 1 @@ -156,6 +238,9 @@ #elif CHAMBER_FAN_MODE == 2 #define CHAMBER_FAN_BASE 128 // Minimum chamber fan PWM (0-255) #define CHAMBER_FAN_FACTOR 25 // PWM increase per Β°C difference from target + #elif CHAMBER_FAN_MODE == 3 + #define CHAMBER_FAN_BASE 128 // Base chamber fan PWM (0-255) + #define CHAMBER_FAN_FACTOR 25 // PWM increase per Β°C above target #endif #endif @@ -163,19 +248,56 @@ #if ENABLED(CHAMBER_VENT) #define CHAMBER_VENT_SERVO_NR 1 // Index of the vent servo #define HIGH_EXCESS_HEAT_LIMIT 5 // How much above target temp to consider there is excess heat in the chamber - #define LOW_EXCESS_HEAT_LIMIT 3 + #define LOW_EXCESS_HEAT_LIMIT 3 #define MIN_COOLING_SLOPE_TIME_CHAMBER_VENT 20 #define MIN_COOLING_SLOPE_DEG_CHAMBER_VENT 1.5 #endif #endif +// +// Laser Cooler options +// +#if TEMP_SENSOR_COOLER + #define COOLER_MINTEMP 8 // (Β°C) + #define COOLER_MAXTEMP 26 // (Β°C) + #define COOLER_DEFAULT_TEMP 16 // (Β°C) + #define TEMP_COOLER_HYSTERESIS 1 // (Β°C) Temperature proximity considered "close enough" to the target + #define COOLER_PIN 8 // Laser cooler on/off pin used to control power to the cooling element (e.g., TEC, External chiller via relay) + #define COOLER_INVERTING false + #define TEMP_COOLER_PIN 15 // Laser/Cooler temperature sensor pin. ADC is required. + #define COOLER_FAN // Enable a fan on the cooler, Fan# 0,1,2,3 etc. + #define COOLER_FAN_INDEX 0 // FAN number 0, 1, 2 etc. e.g. + #if ENABLED(COOLER_FAN) + #define COOLER_FAN_BASE 100 // Base Cooler fan PWM (0-255); turns on when Cooler temperature is above the target + #define COOLER_FAN_FACTOR 25 // PWM increase per Β°C above target + #endif +#endif + +// +// Motherboard Sensor options +// +#if TEMP_SENSOR_BOARD + #define THERMAL_PROTECTION_BOARD // Halt the printer if the board sensor leaves the temp range below. + #define BOARD_MINTEMP 8 // (Β°C) + #define BOARD_MAXTEMP 70 // (Β°C) + //#define TEMP_BOARD_PIN -1 // Board temp sensor pin override. +#endif + +// +// SoC Sensor options +// +#if TEMP_SENSOR_SOC + #define THERMAL_PROTECTION_SOC // Halt the printer if the SoC sensor leaves the temp range below. + #define SOC_MAXTEMP 85 // (Β°C) +#endif + /** * Thermal Protection provides additional protection to your printer from damage * and fire. Marlin always includes safe min and max temperature ranges which * 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. @@ -185,13 +307,16 @@ * If you get false positives for "Thermal Runaway", increase * THERMAL_PROTECTION_HYSTERESIS and/or THERMAL_PROTECTION_PERIOD */ -#if ENABLED(THERMAL_PROTECTION_HOTENDS) - #define THERMAL_PROTECTION_PERIOD 40 // Seconds - #define THERMAL_PROTECTION_HYSTERESIS 4 // Degrees Celsius +#if ALL(HAS_HOTEND, THERMAL_PROTECTION_HOTENDS) + #define THERMAL_PROTECTION_PERIOD 40 // (seconds) + #define THERMAL_PROTECTION_HYSTERESIS 4 // (Β°C) - //#define ADAPTIVE_FAN_SLOWING // Slow part cooling fan if temperature drops - #if BOTH(ADAPTIVE_FAN_SLOWING, PIDTEMP) - //#define NO_FAN_SLOWING_IN_PID_TUNING // Don't slow fan speed during M303 + //#define ADAPTIVE_FAN_SLOWING // Slow down the part-cooling fan if the temperature drops + #if ENABLED(ADAPTIVE_FAN_SLOWING) + //#define REPORT_ADAPTIVE_FAN_SLOWING // Report fan slowing activity to the console + #if ANY(MPCTEMP, PIDTEMP) + //#define TEMP_TUNING_MAINTAIN_FAN // Don't slow down the fan speed during M303 or M306 T + #endif #endif /** @@ -206,54 +331,106 @@ * and/or decrease WATCH_TEMP_INCREASE. WATCH_TEMP_INCREASE should not be set * below 2. */ - #define WATCH_TEMP_PERIOD 20 // Seconds - #define WATCH_TEMP_INCREASE 2 // Degrees Celsius + #define WATCH_TEMP_PERIOD 40 // (seconds) + #define WATCH_TEMP_INCREASE 2 // (Β°C) #endif /** * Thermal Protection parameters for the bed are just as above for hotends. */ -#if ENABLED(THERMAL_PROTECTION_BED) - #define THERMAL_PROTECTION_BED_PERIOD 20 // Seconds - #define THERMAL_PROTECTION_BED_HYSTERESIS 2 // Degrees Celsius +#if TEMP_SENSOR_BED && ENABLED(THERMAL_PROTECTION_BED) + #define THERMAL_PROTECTION_BED_PERIOD 20 // (seconds) + #define THERMAL_PROTECTION_BED_HYSTERESIS 2 // (Β°C) /** * As described above, except for the bed (M140/M190/M303). */ - #define WATCH_BED_TEMP_PERIOD 60 // Seconds - #define WATCH_BED_TEMP_INCREASE 2 // Degrees Celsius + #define WATCH_BED_TEMP_PERIOD 60 // (seconds) + #define WATCH_BED_TEMP_INCREASE 2 // (Β°C) #endif /** * Thermal Protection parameters for the heated chamber. */ -#if ENABLED(THERMAL_PROTECTION_CHAMBER) - #define THERMAL_PROTECTION_CHAMBER_PERIOD 20 // Seconds - #define THERMAL_PROTECTION_CHAMBER_HYSTERESIS 2 // Degrees Celsius +#if TEMP_SENSOR_CHAMBER && ENABLED(THERMAL_PROTECTION_CHAMBER) + #define THERMAL_PROTECTION_CHAMBER_PERIOD 20 // (seconds) + #define THERMAL_PROTECTION_CHAMBER_HYSTERESIS 2 // (Β°C) /** * Heated chamber watch settings (M141/M191). */ - #define WATCH_CHAMBER_TEMP_PERIOD 60 // Seconds - #define WATCH_CHAMBER_TEMP_INCREASE 2 // Degrees Celsius + #define WATCH_CHAMBER_TEMP_PERIOD 60 // (seconds) + #define WATCH_CHAMBER_TEMP_INCREASE 2 // (Β°C) +#endif + +/** + * Thermal Protection parameters for the laser cooler. + */ +#if TEMP_SENSOR_COOLER && ENABLED(THERMAL_PROTECTION_COOLER) + #define THERMAL_PROTECTION_COOLER_PERIOD 10 // (seconds) + #define THERMAL_PROTECTION_COOLER_HYSTERESIS 3 // (Β°C) + + /** + * Laser cooling watch settings (M143/M193). + */ + #define WATCH_COOLER_TEMP_PERIOD 60 // (seconds) + #define WATCH_COOLER_TEMP_INCREASE 3 // (Β°C) +#endif + +#if ANY(THERMAL_PROTECTION_HOTENDS, THERMAL_PROTECTION_BED, THERMAL_PROTECTION_CHAMBER, THERMAL_PROTECTION_COOLER) + /** + * Thermal Protection Variance Monitor - EXPERIMENTAL + * Kill the machine on a stuck temperature sensor. + * + * This feature may cause some thermally-stable systems to halt. Be sure to test it thoroughly under + * a variety of conditions. Disable if you get false positives. + * + * This feature ensures that temperature sensors are updating regularly. If sensors die or get "stuck", + * or if Marlin stops reading them, temperatures will remain constant while heaters may still be powered! + * This feature only monitors temperature changes so it should catch any issue, hardware or software. + * + * By default it uses the THERMAL_PROTECTION_*_PERIOD constants (above) for the time window, within which + * at least one temperature change must occur, to indicate that sensor polling is working. If any monitored + * heater's temperature remains totally constant (without even a fractional change) during this period, a + * thermal malfunction error occurs and the printer is halted. + * + * A very stable heater might produce a false positive and halt the printer. In this case, try increasing + * the corresponding THERMAL_PROTECTION_*_PERIOD constant a bit. Keep in mind that uncontrolled heating + * shouldn't be allowed to persist for more than a minute or two. + * + * Be careful to distinguish false positives from real sensor issues before disabling this feature. If the + * heater's temperature appears even slightly higher than expected after restarting, you may have a real + * thermal malfunction. Check the temperature graph in your host for any unusual bumps. + */ + //#define THERMAL_PROTECTION_VARIANCE_MONITOR + #if ENABLED(THERMAL_PROTECTION_VARIANCE_MONITOR) + // Variance detection window to override the THERMAL_PROTECTION...PERIOD settings above. + // Keep in mind that some heaters heat up faster than others. + //#define THERMAL_PROTECTION_VARIANCE_MONITOR_PERIOD 30 // (s) Override all watch periods + #endif #endif #if ENABLED(PIDTEMP) - // Add an experimental additional term to the heater power, proportional to the extrusion speed. + // Add an additional term to the heater power, proportional to the extrusion speed. // 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 experimental additional term to the heater power, proportional to the fan speed. + * 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 fanspeeds of 0% and 100%) + * Constant compensation (use only with fan speeds of 0% and 100%) * --------------------------------------------------------------------- * A good starting point for the Kf-value comes from the calculation: * kf = (power_fan * eff_fan) / power_heater * 255 @@ -280,22 +457,27 @@ //#define PID_FAN_SCALING_ALTERNATIVE_DEFINITION #if ENABLED(PID_FAN_SCALING_ALTERNATIVE_DEFINITION) // The alternative definition is used for an easier configuration. - // Just figure out Kf at fullspeed (255) and PID_FAN_SCALING_MIN_SPEED. - // DEFAULT_Kf and PID_FAN_SCALING_LIN_FACTOR are calculated accordingly. + // 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. - #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 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 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 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 /** @@ -313,13 +495,16 @@ */ #define AUTOTEMP #if ENABLED(AUTOTEMP) - #define AUTOTEMP_OLDWEIGHT 0.98 + #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_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 @@ -331,12 +516,12 @@ * High Temperature Thermistor Support * * Thermistors able to support high temperature tend to have a hard time getting - * good readings at room and lower temperatures. This means HEATER_X_RAW_LO_TEMP + * good readings at room and lower temperatures. This means TEMP_SENSOR_X_RAW_LO_TEMP * will probably be caught when the heating element first turns on during the - * preheating process, which will trigger a min_temp_error as a safety measure + * preheating process, which will trigger a MINTEMP error as a safety measure * and force stop everything. * To circumvent this limitation, we allow for a preheat time (during which, - * min_temp_error won't be triggered) and add a min_temp buffer to handle + * MINTEMP error won't be triggered) and add a min_temp buffer to handle * aberrant readings. * * If you want to enable this feature for your hotend thermistor(s) @@ -344,21 +529,26 @@ */ // The number of consecutive low temperature errors that can occur -// before a min_temp_error is triggered. (Shouldn't be more than 10.) +// before a MINTEMP error is triggered. (Shouldn't be more than 10.) //#define MAX_CONSECUTIVE_LOW_TEMPERATURE_ERROR_ALLOWED 0 -// The number of milliseconds a hotend will preheat before starting to check -// the temperature. This value should NOT be set to the time it takes the -// hot end to reach the target temperature, but the time it takes to reach -// the minimum temperature your thermistor can read. The lower the better/safer. -// This shouldn't need to be more than 30 seconds (30000) -//#define MILLISECONDS_PREHEAT_TIME 0 +/** + * The number of milliseconds a hotend will preheat before starting to check + * the temperature. This value should NOT be set to the time it takes the + * hot end to reach the target temperature, but the time it takes to reach + * the minimum temperature your thermistor can read. The lower the better/safer. + * This shouldn't need to be more than 30 seconds (30000) + */ +//#define PREHEAT_TIME_HOTEND_MS 0 +//#define PREHEAT_TIME_BED_MS 0 // @section extruder -// Extruder runout prevention. -// If the machine is idle and the temperature over MINTEMP -// then extrude some filament every couple of SECONDS. +/** + * Extruder runout prevention. + * If the machine is idle and the temperature over MINTEMP + * then extrude some filament every couple of SECONDS. + */ //#define EXTRUDER_RUNOUT_PREVENT #if ENABLED(EXTRUDER_RUNOUT_PREVENT) #define EXTRUDER_RUNOUT_MINTEMP 190 @@ -388,6 +578,8 @@ #define TEMP_SENSOR_AD8495_OFFSET 0.0 #define TEMP_SENSOR_AD8495_GAIN 1.0 +// @section fans + /** * Controller Fan * To cool down the stepper drivers and MOSFETs. @@ -397,23 +589,39 @@ */ //#define USE_CONTROLLER_FAN #if ENABLED(USE_CONTROLLER_FAN) - //#define CONTROLLER_FAN_PIN -1 // Set a custom pin for the controller fan - //#define CONTROLLER_FAN_USE_Z_ONLY // With this option only the Z axis is considered - //#define CONTROLLER_FAN_IGNORE_Z // Ignore Z stepper. Useful when stepper timeout is disabled. - #define CONTROLLERFAN_SPEED_MIN 0 // (0-255) Minimum speed. (If set below this value the fan is turned off.) - #define CONTROLLERFAN_SPEED_ACTIVE 255 // (0-255) Active speed, used when any motor is enabled - #define CONTROLLERFAN_SPEED_IDLE 0 // (0-255) Idle speed, used when motors are disabled - #define CONTROLLERFAN_IDLE_TIME 60 // (seconds) Extra time to keep the fan running after disabling motors - //#define CONTROLLER_FAN_EDITABLE // Enable M710 configurable settings + //#define CONTROLLER_FAN_PIN -1 // Set a custom pin for the controller fan + //#define CONTROLLER_FAN2_PIN -1 // Set a custom pin for second controller fan + //#define CONTROLLER_FAN_USE_Z_ONLY // With this option only the Z axis is considered + //#define CONTROLLER_FAN_IGNORE_Z // Ignore Z stepper. Useful when stepper timeout is disabled. + #define CONTROLLERFAN_SPEED_MIN 0 // (0-255) Minimum speed. (If set below this value the fan is turned off.) + #define CONTROLLERFAN_SPEED_ACTIVE 255 // (0-255) Active speed, used when any motor is enabled + #define CONTROLLERFAN_SPEED_IDLE 0 // (0-255) Idle speed, used when motors are disabled + #define CONTROLLERFAN_IDLE_TIME 60 // (seconds) Extra time to keep the fan running after disabling motors + + // Use TEMP_SENSOR_BOARD as a trigger for enabling the controller fan + //#define CONTROLLER_FAN_MIN_BOARD_TEMP 40 // (Β°C) Turn on the fan if the board reaches this temperature + + // 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 + #define CONTROLLER_FAN_MENU // Enable the Controller Fan submenu #endif #endif -// When first starting the main fan, run it at full speed for the -// given number of milliseconds. This gets the fan spinning reliably -// before setting a PWM value. (Does not work with software PWM for fan on Sanguinololu) -//#define FAN_KICKSTART_TIME 100 +/** + * Fan Kickstart + * When part cooling or controller fans first start, run at a speed that + * gets it spinning reliably for a short time before setting the requested speed. + * (Does not work on Sanguinololu with FAN_SOFT_PWM.) + */ +//#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 @@ -434,33 +642,50 @@ //#define FAN_MAX_PWM 128 /** - * FAST PWM FAN Settings + * Fan Fast PWM * - * Use to change the FAST FAN PWM frequency (if enabled in Configuration.h) - * Combinations of PWM Modes, prescale values and TOP resolutions are used internally to produce a - * frequency as close as possible to the desired frequency. + * Combinations of PWM Modes, prescale values and TOP resolutions are used internally + * to produce a frequency as close as possible to the desired frequency. * - * FAST_PWM_FAN_FREQUENCY [undefined by default] + * FAST_PWM_FAN_FREQUENCY * Set this to your desired frequency. - * If left undefined this defaults to F = F_CPU/(2*255*1) - * i.e., F = 31.4kHz on 16MHz microcontrollers or F = 39.2kHz on 20MHz microcontrollers. - * These defaults are the same as with the old FAST_PWM_FAN implementation - no migration is required + * For AVR, if left undefined this defaults to F = F_CPU/(2*255*1) + * i.e., F = 31.4kHz on 16MHz micro-controllers or F = 39.2kHz on 20MHz micro-controllers. + * For non AVR, if left undefined this defaults to F = 1Khz. + * This F value is only to protect the hardware from an absence of configuration + * and not to complete it when users are not aware that the frequency must be specifically set to support the target board. + * * NOTE: Setting very low frequencies (< 10 Hz) may result in unexpected timer behavior. + * Setting very high frequencies can damage your hardware. * * USE_OCR2A_AS_TOP [undefined by default] * Boards that use TIMER2 for PWM have limitations resulting in only a few possible frequencies on TIMER2: - * 16MHz MCUs: [62.5KHz, 31.4KHz (default), 7.8KHz, 3.92KHz, 1.95KHz, 977Hz, 488Hz, 244Hz, 60Hz, 122Hz, 30Hz] - * 20MHz MCUs: [78.1KHz, 39.2KHz (default), 9.77KHz, 4.9KHz, 2.44KHz, 1.22KHz, 610Hz, 305Hz, 153Hz, 76Hz, 38Hz] + * 16MHz MCUs: [62.5kHz, 31.4kHz (default), 7.8kHz, 3.92kHz, 1.95kHz, 977Hz, 488Hz, 244Hz, 60Hz, 122Hz, 30Hz] + * 20MHz MCUs: [78.1kHz, 39.2kHz (default), 9.77kHz, 4.9kHz, 2.44kHz, 1.22kHz, 610Hz, 305Hz, 153Hz, 76Hz, 38Hz] * A greater range can be achieved by enabling USE_OCR2A_AS_TOP. But note that this option blocks the use of * PWM on pin OC2A. Only use this option if you don't need PWM on 0C2A. (Check your schematic.) * USE_OCR2A_AS_TOP sacrifices duty cycle control resolution to achieve this broader range of frequencies. */ +//#define FAST_PWM_FAN // Increase the fan PWM frequency. Removes the PWM noise but increases heating in the FET/Arduino #if ENABLED(FAST_PWM_FAN) - //#define FAST_PWM_FAN_FREQUENCY 31400 + //#define FAST_PWM_FAN_FREQUENCY 31400 // Define here to override the defaults below //#define USE_OCR2A_AS_TOP + #ifndef FAST_PWM_FAN_FREQUENCY + #ifdef __AVR__ + #define FAST_PWM_FAN_FREQUENCY ((F_CPU) / (2 * 255 * 1)) + #else + #define FAST_PWM_FAN_FREQUENCY 1000U + #endif + #endif #endif -// @section extruder +/** + * Assign more PWM fans for part cooling, synchronized with Fan 0 + */ +//#define REDUNDANT_PART_COOLING_FAN 1 // Index of the first fan to synchronize with Fan 0 +#ifdef REDUNDANT_PART_COOLING_FAN + //#define NUM_REDUNDANT_FANS 1 // Number of sequential fans to synchronize with Fan 0 +#endif /** * Extruder cooling fans @@ -483,11 +708,48 @@ #define E6_AUTO_FAN_PIN -1 #define E7_AUTO_FAN_PIN -1 #define CHAMBER_AUTO_FAN_PIN -1 +#define COOLER_AUTO_FAN_PIN -1 #define EXTRUDER_AUTO_FAN_TEMPERATURE 50 #define EXTRUDER_AUTO_FAN_SPEED 255 // 255 == full speed #define CHAMBER_AUTO_FAN_TEMPERATURE 30 #define CHAMBER_AUTO_FAN_SPEED 255 +#define COOLER_AUTO_FAN_TEMPERATURE 18 +#define COOLER_AUTO_FAN_SPEED 255 + +/** + * Hotend Cooling Fans tachometers + * + * Define one or more tachometer pins to enable fan speed + * monitoring, and reporting of fan speeds with M123. + * + * NOTE: Only works with fans up to 7000 RPM. + */ +//#define FOURWIRES_FANS // Needed with AUTO_FAN when 4-wire PWM fans are installed +//#define E0_FAN_TACHO_PIN -1 +//#define E0_FAN_TACHO_PULLUP +//#define E0_FAN_TACHO_PULLDOWN +//#define E1_FAN_TACHO_PIN -1 +//#define E1_FAN_TACHO_PULLUP +//#define E1_FAN_TACHO_PULLDOWN +//#define E2_FAN_TACHO_PIN -1 +//#define E2_FAN_TACHO_PULLUP +//#define E2_FAN_TACHO_PULLDOWN +//#define E3_FAN_TACHO_PIN -1 +//#define E3_FAN_TACHO_PULLUP +//#define E3_FAN_TACHO_PULLDOWN +//#define E4_FAN_TACHO_PIN -1 +//#define E4_FAN_TACHO_PULLUP +//#define E4_FAN_TACHO_PULLDOWN +//#define E5_FAN_TACHO_PIN -1 +//#define E5_FAN_TACHO_PULLUP +//#define E5_FAN_TACHO_PULLDOWN +//#define E6_FAN_TACHO_PIN -1 +//#define E6_FAN_TACHO_PULLUP +//#define E6_FAN_TACHO_PULLDOWN +//#define E7_FAN_TACHO_PIN -1 +//#define E7_FAN_TACHO_PULLUP +//#define E7_FAN_TACHO_PULLDOWN /** * Part-Cooling Fan Multiplexer @@ -501,6 +763,7 @@ #define FANMUX2_PIN -1 /** + * @section caselight * M355 Case Light on-off / brightness */ //#define CASE_LIGHT_ENABLE @@ -509,18 +772,23 @@ #define INVERT_CASE_LIGHT false // Set true if Case Light is ON when pin is LOW #define CASE_LIGHT_DEFAULT_ON true // Set default power-up state on #define CASE_LIGHT_DEFAULT_BRIGHTNESS 105 // Set default power-up brightness (0-255, requires PWM pin) - //#define CASE_LIGHT_MAX_PWM 128 // Limit pwm - //#define CASE_LIGHT_MENU // Add Case Light options to the LCD menu //#define CASE_LIGHT_NO_BRIGHTNESS // Disable brightness control. Enable for non-PWM lighting. - //#define CASE_LIGHT_USE_NEOPIXEL // Use NeoPixel LED as case light, requires NEOPIXEL_LED. - #if ENABLED(CASE_LIGHT_USE_NEOPIXEL) - #define CASE_LIGHT_NEOPIXEL_COLOR { 255, 255, 255, 255 } // { Red, Green, Blue, White } + //#define CASE_LIGHT_MAX_PWM 128 // Limit PWM duty cycle (0-255) + //#define CASE_LIGHT_MENU // Add Case Light options to the LCD menu + #if ENABLED(NEOPIXEL_LED) + //#define CASE_LIGHT_USE_NEOPIXEL // Use NeoPixel LED as case light + #endif + #if ANY(RGB_LED, RGBW_LED) + //#define CASE_LIGHT_USE_RGB_LED // Use RGB / RGBW LED as case light + #endif + #if ANY(CASE_LIGHT_USE_NEOPIXEL, CASE_LIGHT_USE_RGB_LED) + #define CASE_LIGHT_DEFAULT_COLOR { 255, 255, 255, 255 } // { Red, Green, Blue, White } #endif #endif -// @section homing +// @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 @@ -535,61 +803,7 @@ //#define CLOSED_LOOP_MOVE_COMPLETE_PIN -1 #endif -/** - * Dual Steppers / Dual Endstops - * - * This section will allow you to use extra E drivers to drive a second motor for X, Y, or Z axes. - * - * For example, set X_DUAL_STEPPER_DRIVERS setting to use a second motor. If the motors need to - * spin in opposite directions set INVERT_X2_VS_X_DIR. If the second motor needs its own endstop - * set X_DUAL_ENDSTOPS. This can adjust for "racking." Use X2_USE_ENDSTOP to set the endstop plug - * that should be used for the second endstop. Extra endstops will appear in the output of 'M119'. - * - * Use X_DUAL_ENDSTOP_ADJUSTMENT to adjust for mechanical imperfection. After homing both motors - * this offset is applied to the X2 motor. To find the offset home the X axis, and measure the error - * in X2. Dual endstop offsets can be set at runtime with 'M666 X Y Z'. - */ - -//#define X_DUAL_STEPPER_DRIVERS -#if ENABLED(X_DUAL_STEPPER_DRIVERS) - #define INVERT_X2_VS_X_DIR true // Set 'true' if X motors should rotate in opposite directions - //#define X_DUAL_ENDSTOPS - #if ENABLED(X_DUAL_ENDSTOPS) - #define X2_USE_ENDSTOP _XMAX_ - #define X2_ENDSTOP_ADJUSTMENT 0 - #endif -#endif - -//#define Y_DUAL_STEPPER_DRIVERS -#if ENABLED(Y_DUAL_STEPPER_DRIVERS) - #define INVERT_Y2_VS_Y_DIR true // Set 'true' if Y motors should rotate in opposite directions - //#define Y_DUAL_ENDSTOPS - #if ENABLED(Y_DUAL_ENDSTOPS) - #define Y2_USE_ENDSTOP _YMAX_ - #define Y2_ENDSTOP_ADJUSTMENT 0 - #endif -#endif - -// -// For Z set the number of stepper drivers -// -#define NUM_Z_STEPPER_DRIVERS 1 // (1-4) Z options change based on how many - -#if NUM_Z_STEPPER_DRIVERS > 1 - //#define Z_MULTI_ENDSTOPS - #if ENABLED(Z_MULTI_ENDSTOPS) - #define Z2_USE_ENDSTOP _XMAX_ - #define Z2_ENDSTOP_ADJUSTMENT 0 - #if NUM_Z_STEPPER_DRIVERS >= 3 - #define Z3_USE_ENDSTOP _YMAX_ - #define Z3_ENDSTOP_ADJUSTMENT 0 - #endif - #if NUM_Z_STEPPER_DRIVERS >= 4 - #define Z4_USE_ENDSTOP _ZMAX_ - #define Z4_ENDSTOP_ADJUSTMENT 0 - #endif - #endif -#endif +// @section idex /** * Dual X Carriage @@ -621,24 +835,100 @@ */ //#define DUAL_X_CARRIAGE #if ENABLED(DUAL_X_CARRIAGE) - #define X1_MIN_POS X_MIN_POS // Set to X_MIN_POS - #define X1_MAX_POS X_BED_SIZE // Set a maximum so the first X-carriage can't hit the parked second X-carriage - #define X2_MIN_POS 80 // Set a minimum to ensure the second X-carriage can't hit the parked first X-carriage - #define X2_MAX_POS 353 // Set this to the distance between toolheads when both heads are homed - #define X2_HOME_DIR 1 // Set to 1. The second X-carriage always homes to the maximum endstop position - #define X2_HOME_POS X2_MAX_POS // Default X2 home position. Set to X2_MAX_POS. - // However: In this mode the HOTEND_OFFSET_X value for the second extruder provides a software - // override for X2_HOME_POS. This also allow recalibration of the distance between the two endstops - // without modifying the firmware (through the "M218 T1 X???" command). - // Remember: you should set the second extruder x-offset to 0 in your slicer. + #define X1_MIN_POS X_MIN_POS // Set to X_MIN_POS + #define X1_MAX_POS X_BED_SIZE // A max coordinate so the X1 carriage can't hit the parked X2 carriage + #define X2_MIN_POS 80 // A min coordinate so the X2 carriage can't hit the parked X1 carriage + #define X2_MAX_POS 353 // The max position of the X2 carriage, typically also the home position + #define X2_HOME_POS X2_MAX_POS // Default X2 home position. Set to X2_MAX_POS. + // NOTE: For Dual X Carriage use M218 T1 Xn to override the X2_HOME_POS. + // This allows recalibration of endstops distance without a rebuild. + // Remember to set the second extruder's X-offset to 0 in your slicer. - // This is the default power-up mode which can be later using M605. + // This is the default power-up mode which can be changed later using M605 S. #define DEFAULT_DUAL_X_CARRIAGE_MODE DXC_AUTO_PARK_MODE // Default x offset in duplication mode (typically set to half print bed width) #define DEFAULT_DUPLICATION_X_OFFSET 100 + + // Default action to execute following M605 mode change commands. Typically G28X to apply new mode. + //#define EVENT_GCODE_IDEX_AFTER_MODECHANGE "G28X" #endif +// @section multi stepper + +/** + * Multi-Stepper / Multi-Endstop + * + * When X2_DRIVER_TYPE is defined, this indicates that the X and X2 motors work in tandem. + * The following explanations for X also apply to Y and Z multi-stepper setups. + * Endstop offsets may be changed by 'M666 X Y Z' and stored to EEPROM. + * + * - Enable INVERT_X2_VS_X_DIR if the X2 motor requires an opposite DIR signal from X. + * + * - Enable X_DUAL_ENDSTOPS if the second motor has its own endstop, with adjustable offset. + * + * - Extra endstops are included in the output of 'M119'. + * + * - Set X_DUAL_ENDSTOP_ADJUSTMENT to the known error in the X2 endstop. + * Applied to the X2 motor on 'G28' / 'G28 X'. + * Get the offset by homing X and measuring the error. + * Also set with 'M666 X' and stored to EEPROM with 'M500'. + * + * - Define the extra endstop pins here to override defaults. No auto-assignment. + */ +#if HAS_X2_STEPPER && DISABLED(DUAL_X_CARRIAGE) + //#define INVERT_X2_VS_X_DIR // X2 direction signal is the opposite of X + //#define X_DUAL_ENDSTOPS // X2 has its own endstop + #if ENABLED(X_DUAL_ENDSTOPS) + //#define X2_STOP_PIN X_MAX_PIN // X2 endstop pin override + #define X2_ENDSTOP_ADJUSTMENT 0 // X2 offset relative to X endstop + #endif +#endif + +#if HAS_Y2_STEPPER + //#define INVERT_Y2_VS_Y_DIR // Y2 direction signal is the opposite of Y + //#define Y_DUAL_ENDSTOPS // Y2 has its own endstop + #if ENABLED(Y_DUAL_ENDSTOPS) + //#define Y2_STOP_PIN Y_MAX_PIN // Y2 endstop pin override + #define Y2_ENDSTOP_ADJUSTMENT 0 // Y2 offset relative to Y endstop + #endif +#endif + +// +// Multi-Z steppers +// +#ifdef Z2_DRIVER_TYPE + //#define INVERT_Z2_VS_Z_DIR // Z2 direction signal is the opposite of Z + + //#define Z_MULTI_ENDSTOPS // Other Z axes have their own endstops + #if ENABLED(Z_MULTI_ENDSTOPS) + //#define Z2_STOP_PIN X_MAX_PIN // Z2 endstop pin override + #define Z2_ENDSTOP_ADJUSTMENT 0 // Z2 offset relative to Z endstop + #endif + #ifdef Z3_DRIVER_TYPE + //#define INVERT_Z3_VS_Z_DIR // Z3 direction signal is the opposite of Z + #if ENABLED(Z_MULTI_ENDSTOPS) + //#define Z3_STOP_PIN Y_MAX_PIN // Z3 endstop pin override + #define Z3_ENDSTOP_ADJUSTMENT 0 // Z3 offset relative to Z endstop + #endif + #endif + #ifdef Z4_DRIVER_TYPE + //#define INVERT_Z4_VS_Z_DIR // Z4 direction signal is the opposite of Z + #if ENABLED(Z_MULTI_ENDSTOPS) + //#define Z4_STOP_PIN Z_MAX_PIN // Z4 endstop pin override + #define Z4_ENDSTOP_ADJUSTMENT 0 // Z4 offset relative to Z endstop + #endif + #endif +#endif + +// Drive the E axis with two synchronized steppers +//#define E_DUAL_STEPPER_DRIVERS +#if ENABLED(E_DUAL_STEPPER_DRIVERS) + //#define INVERT_E1_VS_E0_DIR // E direction signals are opposites +#endif + +// @section extruder + // Activate a solenoid on the active extruder with M380. Disable all with M381. // Define SOL0_PIN, SOL1_PIN, etc., for each extruder that has a solenoid. //#define EXT_SOLENOID @@ -651,15 +941,17 @@ * the position of the toolhead relative to the workspace. */ -//#define SENSORLESS_BACKOFF_MM { 2, 2 } // (mm) Backoff from endstops before sensorless homing +//#define SENSORLESS_BACKOFF_MM { 2, 2, 0 } // (linear=mm, rotational=Β°) Backoff from endstops before sensorless homing -#define HOMING_BUMP_MM { 5, 5, 2 } // (mm) Backoff from endstops after first bump +#define HOMING_BUMP_MM { 5, 5, 2 } // (linear=mm, rotational=Β°) Backoff from endstops after first bump #define HOMING_BUMP_DIVISOR { 2, 2, 4 } // Re-Bump Speed Divisor (Divides the Homing Feedrate) -//#define HOMING_BACKOFF_POST_MM { 2, 2, 2 } // (mm) Backoff from endstops after homing +//#define HOMING_BACKOFF_POST_MM { 2, 2, 2 } // (linear=mm, rotational=Β°) Backoff from endstops after homing +//#define XY_COUNTERPART_BACKOFF_MM 0 // (mm) Backoff X after homing Y, and vice-versa //#define QUICK_HOME // If G28 contains XY do a diagonal move first //#define HOME_Y_BEFORE_X // If G28 contains XY home Y before X +//#define HOME_Z_FIRST // Home Z first. Requires a real endstop (not a probe). //#define CODEPENDENT_XY_HOMING // If X/Y can't home without homing Y/X first // @section bltouch @@ -704,12 +996,15 @@ * Danger: Don't activate 5V mode unless attached to a 5V-tolerant controller! * V3.0 or 3.1: Set default mode to 5V mode at Marlin startup. * If disabled, OD mode is the hard-coded default on 3.0 - * On startup, Marlin will compare its eeprom to this value. If the selected mode - * differs, a mode set eeprom write will be completed at initialization. - * Use the option below to force an eeprom write to a V3.1 probe regardless. + * On startup, Marlin will compare its EEPROM to this value. If the selected mode + * differs, a mode set EEPROM write will be completed at initialization. + * Use the option below to force an EEPROM write to a V3.1 probe regardless. */ //#define BLTOUCH_SET_5V_MODE + // Safety: Enable voltage mode settings in the LCD menu. + //#define BLTOUCH_LCD_VOLTAGE_MENU + /** * Safety: Activate if connecting a probe with an unknown voltage mode. * V3.0: Set a probe into mode selected above at Marlin startup. Required for 5V mode on 3.0 @@ -719,19 +1014,25 @@ //#define BLTOUCH_FORCE_MODE_SET /** - * Use "HIGH SPEED" mode for probing. + * Enable "HIGH SPEED" option for probing. * Danger: Disable if your probe sometimes fails. Only suitable for stable well-adjusted systems. - * This feature was designed for Delta's with very fast Z moves however higher speed cartesians may function - * If the machine cannot raise the probe fast enough after a trigger, it may enter a fault state. + * This feature was designed for Deltabots with very fast Z moves; however, higher speed Cartesians + * might be able to use it. If the machine can't raise Z fast enough the BLTouch may go into ALARM. + * + * Set the default state here, change with 'M401 S' or UI, use M500 to save, M502 to reset. */ - //#define BLTOUCH_HS_MODE + //#define BLTOUCH_HS_MODE true - // Safety: Enable voltage mode settings in the LCD menu. - //#define BLTOUCH_LCD_VOLTAGE_MENU + #ifdef BLTOUCH_HS_MODE + // The probe Z offset (M851 Z) is the height at which the probe triggers. + // This must be large enough to keep the probe pin off the bed and prevent + // it from snagging on the bed clips. + #define BLTOUCH_HS_EXTRA_CLEARANCE 7 // Extra Z Clearance + #endif #endif // BLTOUCH -// @section extras +// @section calibrate /** * Z Steppers Auto-Alignment @@ -739,9 +1040,12 @@ */ //#define Z_STEPPER_AUTO_ALIGN #if ENABLED(Z_STEPPER_AUTO_ALIGN) - // Define probe X and Y positions for Z1, Z2 [, Z3 [, Z4]] - // If not defined, probe limits will be used. - // Override with 'M422 S X Y' + /** + * Define probe X and Y positions for Z1, Z2 [, Z3 [, Z4]] + * These positions are machine-relative and do not shift with the M206 home offset! + * If not defined, probe limits will be used. + * Override with 'M422 S X Y'. + */ //#define Z_STEPPER_ALIGN_XY { { 10, 190 }, { 100, 10 }, { 190, 190 } } /** @@ -767,15 +1071,17 @@ //#define Z_STEPPERS_ORIENTATION 0 #endif - // Provide Z stepper positions for more rapid convergence in bed alignment. - // Requires triple stepper drivers (i.e., set NUM_Z_STEPPER_DRIVERS to 3) - //#define Z_STEPPER_ALIGN_KNOWN_STEPPER_POSITIONS - #if ENABLED(Z_STEPPER_ALIGN_KNOWN_STEPPER_POSITIONS) - // Define Stepper XY positions for Z1, Z2, Z3 corresponding to - // the Z screw positions in the bed carriage. - // Define one position per Z stepper in stepper driver order. - #define Z_STEPPER_ALIGN_STEPPER_XY { { 210.7, 102.5 }, { 152.6, 220.0 }, { 94.5, 102.5 } } - #else + /** + * Z Stepper positions for more rapid convergence in bed alignment. + * Requires 3 or 4 Z steppers. + * + * Define Stepper XY positions for Z1, Z2, Z3... corresponding to the screw + * positions in the bed carriage, with one position per Z stepper in stepper + * driver order. + */ + //#define Z_STEPPER_ALIGN_STEPPER_XY { { 210.7, 102.5 }, { 152.6, 220.0 }, { 94.5, 102.5 } } + + #ifndef Z_STEPPER_ALIGN_STEPPER_XY // Amplification factor. Used to scale the correction step up or down in case // the stepper (spindle) position is farther out than the test point. #define Z_STEPPER_ALIGN_AMP 1.0 // Use a value > 1.0 NOTE: This may cause instability! @@ -785,22 +1091,39 @@ #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) - // Define positions for probing points, use the hotend as reference not the sensor. - #define TRAMMING_POINT_XY { { 20, 20 }, { 200, 20 }, { 200, 200 }, { 20, 200 } } + // Define from 3 to 9 points to probe. + #define TRAMMING_POINT_XY { { 20, 20 }, { 180, 20 }, { 180, 180 }, { 20, 180 } } - // Define positions names for probing points. + // Define position names for probe points. #define TRAMMING_POINT_NAME_1 "Front-Left" #define TRAMMING_POINT_NAME_2 "Front-Right" #define TRAMMING_POINT_NAME_3 "Back-Right" @@ -808,18 +1131,151 @@ #define RESTORE_LEVELING_AFTER_G35 // Enable to restore leveling setup after operation //#define REPORT_TRAMMING_MM // Report Z deviation (mm) for each point relative to the first - //#define ASSISTED_TRAMMING_MENU_ITEM // Add a menu item for Assisted Tramming + + //#define ASSISTED_TRAMMING_WIZARD // Add a Tramming Wizard to the LCD menu + + //#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 -- 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_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 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 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_RESONANCE_TEST // Sine sweep motion for resonance study + + //#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_TRAJECTORY_TYPE TRAPEZOIDAL // Block acceleration profile (TRAPEZOIDAL, POLY5, POLY6) + // TRAPEZOIDAL: Continuous Velocity. Max acceleration is respected. + // POLY5: Like POLY6 with 1.5x but uses less CPU. + // POLY6: Continuous Acceleration (aka S_CURVE). + // POLY trajectories not only reduce resonances without rounding corners, but also + // reduce extruder strain due to linear advance. + + #define FTM_POLY6_ACCELERATION_OVERSHOOT 1.875f // Max acceleration overshoot factor for POLY6 (1.25 to 1.875) + #endif + + /** + * 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 + +#endif // FT_MOTION + +/** + * 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_[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 + */ +//#define INPUT_SHAPING_X +//#define 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.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.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 + #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 + // @section motion #define AXIS_RELATIVE_MODES { false, false, false, false } @@ -827,27 +1283,34 @@ // Add a Duplicate option for well-separated conjoined nozzles //#define MULTI_NOZZLE_DUPLICATION -// By default pololu step drivers require an active high signal. However, some high power drivers require an active low signal as step. -#define INVERT_X_STEP_PIN false -#define INVERT_Y_STEP_PIN false -#define INVERT_Z_STEP_PIN false -#define INVERT_E_STEP_PIN false +// By default stepper drivers require an active-HIGH signal but some high-power drivers require an active-LOW signal to step. +#define STEP_STATE_X HIGH +#define STEP_STATE_Y HIGH +#define STEP_STATE_Z HIGH +#define STEP_STATE_I HIGH +#define STEP_STATE_J HIGH +#define STEP_STATE_K HIGH +#define STEP_STATE_U HIGH +#define STEP_STATE_V HIGH +#define STEP_STATE_W HIGH +#define STEP_STATE_E HIGH /** * Idle Stepper Shutdown - * Set DISABLE_INACTIVE_? 'true' to shut down axis steppers after an idle period. - * The Deactive Time can be overridden with M18 and M84. Set to 0 for No Timeout. + * Enable DISABLE_IDLE_* to shut down axis steppers after an idle period. + * The default timeout duration can be overridden with M18 and M84. Set to 0 for No Timeout. */ -#define DEFAULT_STEPPER_DEACTIVE_TIME 120 -#define DISABLE_INACTIVE_X true -#define DISABLE_INACTIVE_Y true -#define DISABLE_INACTIVE_Z true // Set 'false' if the nozzle could fall onto your printed part! -#define DISABLE_INACTIVE_E true - -// If the Nozzle or Bed falls when the Z stepper is disabled, set its resting position here. -//#define Z_AFTER_DEACTIVATE Z_HOME_POS - -//#define HOME_AFTER_DEACTIVATE // Require rehoming after steppers are deactivated +#define DEFAULT_STEPPER_TIMEOUT_SEC 120 +#define DISABLE_IDLE_X +#define DISABLE_IDLE_Y +#define DISABLE_IDLE_Z // Disable if the nozzle could fall onto your printed part! +//#define DISABLE_IDLE_I +//#define DISABLE_IDLE_J +//#define DISABLE_IDLE_K +//#define DISABLE_IDLE_U +//#define DISABLE_IDLE_V +//#define DISABLE_IDLE_W +#define DISABLE_IDLE_E // Shut down all idle extruders // Default Minimum Feedrates for printing and travel moves #define DEFAULT_MINIMUMFEEDRATE 0.0 // (mm/s) Minimum feedrate. Set with M205 S. @@ -867,18 +1330,13 @@ * 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 // (percent) 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 -// Minimum planner junction speed. Sets the default minimum speed the planner plans for at the end -// of the buffer and all stops. This should not be much greater than zero and should only be changed -// if unwanted behavior is observed on a user's machine when running at very slow speeds. -#define MINIMUM_PLANNER_SPEED 0.05 // (mm/s) - // // Backlash Compensation // Adds extra movement to axes on direction-changes to account for backlash. @@ -887,9 +1345,12 @@ #if ENABLED(BACKLASH_COMPENSATION) // Define values for backlash distance and correction. // If BACKLASH_GCODE is enabled these values are the defaults. - #define BACKLASH_DISTANCE_MM { 0, 0, 0 } // (mm) + #define BACKLASH_DISTANCE_MM { 0, 0, 0 } // (linear=mm, rotational=Β°) One value for each linear axis #define BACKLASH_CORRECTION 0.0 // 0.0 = no correction; 1.0 = full correction + // Add steps for motor direction changes on CORE kinematics + //#define CORE_BACKLASH + // Set BACKLASH_SMOOTHING_MM to spread backlash correction over multiple segments // to reduce print artifacts. (Enabling this is costly in memory and computation!) //#define BACKLASH_SMOOTHING_MM 3 // (mm) @@ -907,13 +1368,13 @@ // increments while checking for the contact to be broken. #define BACKLASH_MEASUREMENT_LIMIT 0.5 // (mm) #define BACKLASH_MEASUREMENT_RESOLUTION 0.005 // (mm) - #define BACKLASH_MEASUREMENT_FEEDRATE Z_PROBE_SPEED_SLOW // (mm/min) + #define BACKLASH_MEASUREMENT_FEEDRATE Z_PROBE_FEEDRATE_SLOW // (mm/min) #endif #endif #endif /** - * Automatic backlash, position and hotend offset calibration + * Automatic backlash, position, and hotend offset calibration * * Enable G425 to run automatic calibration using an electrically- * conductive cube, bolt, or washer mounted on the bed. @@ -931,22 +1392,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 PROGMEM). + // 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. @@ -955,8 +1414,21 @@ #define CALIBRATION_MEASURE_LEFT #define CALIBRATION_MEASURE_BACK + //#define CALIBRATION_MEASURE_IMIN + //#define CALIBRATION_MEASURE_IMAX + //#define CALIBRATION_MEASURE_JMIN + //#define CALIBRATION_MEASURE_JMAX + //#define CALIBRATION_MEASURE_KMIN + //#define CALIBRATION_MEASURE_KMAX + //#define CALIBRATION_MEASURE_UMIN + //#define CALIBRATION_MEASURE_UMAX + //#define CALIBRATION_MEASURE_VMIN + //#define CALIBRATION_MEASURE_VMAX + //#define CALIBRATION_MEASURE_WMIN + //#define CALIBRATION_MEASURE_WMAX + // Probing at the exact top center only works if the center is flat. If - // probing on a screwhead or hollow washer, probe near the edges. + // probing on a screw head or hollow washer, probe near the edges. //#define CALIBRATION_MEASURE_AT_TOP_EDGES // Define the pin to read during calibration @@ -968,6 +1440,12 @@ #endif #endif +/** + * 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] + /** * Adaptive Step Smoothing increases the resolution of multi-axis moves, particularly at step frequencies * below 1kHz (for AVR) or 10kHz (for ARM), where aliasing between axes in multi-axis moves causes audible @@ -991,24 +1469,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) @@ -1017,9 +1495,9 @@ /** * I2C-based DIGIPOTs (e.g., Azteeg X3 Pro) */ -//#define DIGIPOT_MCP4018 // Requires https://github.com/stawel/SlowSoftI2CMaster +//#define DIGIPOT_MCP4018 // Requires https://github.com/felias-fogg/SlowSoftI2CMaster //#define DIGIPOT_MCP4451 -#if EITHER(DIGIPOT_MCP4018, DIGIPOT_MCP4451) +#if ANY(DIGIPOT_MCP4018, DIGIPOT_MCP4451) #define DIGIPOT_I2C_NUM_CHANNELS 8 // 5DPRINT:4 AZTEEG_X3_PRO:8 MKS_SBASE:5 MIGHTYBOARD_REVE:5 // Actual motor currents in Amps. The number of entries must match DIGIPOT_I2C_NUM_CHANNELS. @@ -1048,12 +1526,16 @@ // @section lcd -#if EITHER(ULTIPANEL, EXTENSIBLE_UI) +// 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 SHORT_MANUAL_Z_MOVE 0.025 // (mm) Smallest manual Z move (< 0.1mm) - #if ENABLED(ULTIPANEL) + #define FINE_MANUAL_MOVE 0.025 // (mm) Smallest manual move (< 0.1mm) applying to Z on most machines + #if IS_ULTIPANEL #define MANUAL_E_MOVES_RELATIVE // Display extruder move distance rather than "position" #define ULTIPANEL_FEEDMULTIPLY // Encoder sets the feedrate multiplier on the Status Screen + //#define ULTIPANEL_FLOWPERCENT // Encoder sets the flow percentage on the Status Screen #endif #endif @@ -1071,25 +1553,151 @@ #define FEEDRATE_CHANGE_BEEP_FREQUENCY 440 #endif -#if HAS_LCD_MENU +/** + * 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 + #if ENABLED(PROBE_OFFSET_WIZARD) + /** + * Enable to init the Probe Z-Offset when starting the Wizard. + * Use a height slightly above the estimated nozzle-to-probe Z offset. + * For example, with an offset of -5, consider a starting height of -4. + */ + //#define PROBE_OFFSET_WIZARD_START_Z -4.0 + + // Set a convenient position to do the calibration (probing point and nozzle/bed-distance) + //#define PROBE_OFFSET_WIZARD_XY_POS { X_CENTER, Y_CENTER } + #endif +#endif + +#if HAS_MARLINUI_MENU - // Add Probe Z Offset calibration to the Z Probe Offsets menu #if HAS_BED_PROBE - //#define PROBE_OFFSET_WIZARD - #if ENABLED(PROBE_OFFSET_WIZARD) - #define PROBE_OFFSET_START -4.0 // Estimated nozzle-to-probe Z offset, plus a little extra + + // 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) + /** + * Enable to init the Probe Z-Offset when starting the Wizard. + * Use a height slightly above the estimated nozzle-to-probe Z offset. + * For example, with an offset of -5, consider a starting height of -4. + */ + #define XATC_START_Z 0.0 + #define XATC_MAX_POINTS 3 // Number of points to probe in the wizard + #define XATC_Y_POSITION Y_CENTER // (mm) Y position to probe + #define XATC_Z_OFFSETS { 0, 0, 0 } // Z offsets for X axis sample points #endif + #endif // 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 + /** + * MarlinUI "Move Axis" menu distances. Comma-separated list. + * Values are displayed as-defined, so always use plain numbers here. + * Axis moves <= 1/2 the axis length and Extruder moves <= EXTRUDE_MAXLENGTH + * will be shown in the move submenus. + */ + + #define MANUAL_MOVE_DISTANCE_MM 10, 1.0, 0.1 // (mm) + //#define MANUAL_MOVE_DISTANCE_MM 100, 50, 10, 1.0, 0.1 // (mm) + //#define MANUAL_MOVE_DISTANCE_MM 500, 100, 50, 10, 1.0, 0.1 // (mm) + + // Manual move distances for INCH_MODE_SUPPORT + #define MANUAL_MOVE_DISTANCE_IN 0.100, 0.010, 0.001 // (in) + //#define MANUAL_MOVE_DISTANCE_IN 1.000, 0.500, 0.100, 0.010, 0.001 // (in) + //#define MANUAL_MOVE_DISTANCE_IN 5.000, 1.000, 0.500, 0.100, 0.010, 0.001 // (in) + + // Manual move distances for rotational axes + #define MANUAL_MOVE_DISTANCE_DEG 90, 45, 22.5, 5, 1 // (Β°) + // 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 + + // Add Configuration > Debug Menu > Endstop Test for endstop/probe/runout testing + //#define LCD_ENDSTOP_TEST + +#endif // HAS_MARLINUI_MENU + +#if HAS_DISPLAY + /** + * *** VENDORS PLEASE READ *** + * + * Marlin allows you to add a custom boot image for Graphical LCDs. + * With this option Marlin will first show your custom screen followed + * by the standard Marlin logo with version number and web URL. + * + * We encourage you to take advantage of this new feature and we also + * respectfully request that you retain the unmodified Marlin boot screen. + */ + #define SHOW_BOOTSCREEN // Show the Marlin bootscreen on startup. ** ENABLE FOR PRODUCTION ** + #if ENABLED(SHOW_BOOTSCREEN) + #define BOOTSCREEN_TIMEOUT 3000 // (ms) Total Duration to display the boot screen(s) + #if ANY(HAS_MARLINUI_U8GLIB, TFT_COLOR_UI) + #define BOOT_MARLIN_LOGO_SMALL // Show a smaller Marlin logo on the Boot Screen (saving lots of flash) + #endif + #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, HAS_MARLINUI_HD44780, HAS_GRAPHICAL_TFT) + //#define SHOW_CUSTOM_BOOTSCREEN // Show the bitmap in Marlin/_Bootscreen.h on startup. + #endif + #endif + + #if HAS_MARLINUI_U8GLIB + //#define CUSTOM_STATUS_SCREEN_IMAGE // Show the bitmap in Marlin/_Statusscreen.h on the status screen. + #endif + + //#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 + + // The timeout to return to the status screen from sub-menus + //#define LCD_TIMEOUT_TO_STATUS 15000 // (ms) + + // Scroll a longer status message into view + //#define STATUS_MESSAGE_SCROLLING + + // Apply a timeout to low-priority status messages + //#define STATUS_MESSAGE_TIMEOUT_SEC 30 // (seconds) + + // On the Info Screen, display XY with one decimal place when possible + //#define LCD_DECIMAL_SMALL_XY + + // Show the E position (filament used) during printing + //#define LCD_SHOW_E_TOTAL + + // Display a negative temperature instead of "err" + //#define SHOW_TEMPERATURE_BELOW_ZERO + /** * LED Control Menu * Add LED Control to the LCD menu @@ -1107,67 +1715,82 @@ //#define LED_USER_PRESET_STARTUP // Have the printer display the user preset color on startup #endif #if ENABLED(NEO2_COLOR_PRESETS) - #define NEO2_USER_PRESET_RED 255 // User defined RED value - #define NEO2_USER_PRESET_GREEN 128 // User defined GREEN value - #define NEO2_USER_PRESET_BLUE 0 // User defined BLUE value - #define NEO2_USER_PRESET_WHITE 255 // User defined WHITE value - #define NEO2_USER_PRESET_BRIGHTNESS 255 // User defined intensity - //#define NEO2_USER_PRESET_STARTUP // Have the printer display the user preset color on startup for the second strip + #define NEO2_USER_PRESET_RED 255 // User defined RED value + #define NEO2_USER_PRESET_GREEN 128 // User defined GREEN value + #define NEO2_USER_PRESET_BLUE 0 // User defined BLUE value + #define NEO2_USER_PRESET_WHITE 255 // User defined WHITE value + #define NEO2_USER_PRESET_BRIGHTNESS 255 // User defined intensity + //#define NEO2_USER_PRESET_STARTUP // Have the printer display the user preset color on startup for the second strip #endif #endif -#endif // HAS_LCD_MENU +#endif // HAS_DISPLAY -// Scroll a longer status message into view -//#define STATUS_MESSAGE_SCROLLING - -// On the Info Screen, display XY with one decimal place when possible -//#define LCD_DECIMAL_SMALL_XY - -// The timeout (in ms) to return to the status screen from sub-menus -//#define LCD_TIMEOUT_TO_STATUS 15000 - -// Add an 'M73' G-code to set the current percentage -//#define LCD_SET_PROGRESS_MANUALLY - -// Show the E position (filament used) during printing -//#define LCD_SHOW_E_TOTAL - -#if ENABLED(SHOW_BOOTSCREEN) - #define BOOTSCREEN_TIMEOUT 4000 // (ms) Total Duration to display the boot screen(s) +// 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 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 -#if EITHER(SDSUPPORT, LCD_SET_PROGRESS_MANUALLY) && ANY(HAS_MARLINUI_U8GLIB, HAS_MARLINUI_HD44780, IS_TFTGLCD_PANEL) - //#define SHOW_REMAINING_TIME // Display estimated time to completion - #if ENABLED(SHOW_REMAINING_TIME) - //#define USE_M73_REMAINING_TIME // Use remaining time from M73 command instead of estimation - //#define ROTATE_PROGRESS_DISPLAY // Display (P)rogress, (E)lapsed, and (R)emaining time +// Add 'M73' to set print job progress, overrides Marlin's built-in estimate +//#define SET_PROGRESS_MANUALLY +#if ENABLED(SET_PROGRESS_MANUALLY) + #define SET_PROGRESS_PERCENT // Add 'P' parameter to set percentage done + #define SET_REMAINING_TIME // Add 'R' parameter to set remaining time + //#define SET_INTERACTION_TIME // Add 'C' parameter to set time until next filament change or other user interaction + //#define M73_REPORT // Report M73 values to host + #if ALL(M73_REPORT, HAS_MEDIA) + #define M73_REPORT_SD_ONLY // Report only when printing from SD #endif +#endif - #if HAS_MARLINUI_U8GLIB - //#define PRINT_PROGRESS_SHOW_DECIMALS // Show progress with decimal digits +// LCD Print Progress options. Multiple times may be displayed in turn. +#if HAS_DISPLAY && ANY(HAS_MEDIA, SET_PROGRESS_MANUALLY) + #define SHOW_PROGRESS_PERCENT // Show print progress percentage (doesn't affect progress bar) + #define SHOW_ELAPSED_TIME // Display elapsed printing time (prefix 'E') + //#define SHOW_REMAINING_TIME // Display estimated time to completion (prefix 'R') + #if ENABLED(SET_INTERACTION_TIME) + #define SHOW_INTERACTION_TIME // Display time until next user interaction ('C' = filament change) #endif + //#define PRINT_PROGRESS_SHOW_DECIMALS // Show/report progress with decimal digits, not all UIs support this - #if EITHER(HAS_MARLINUI_HD44780, IS_TFTGLCD_PANEL) + #if ANY(HAS_MARLINUI_HD44780, IS_TFTGLCD_PANEL) //#define LCD_PROGRESS_BAR // Show a progress bar on HD44780 LCDs for SD printing #if ENABLED(LCD_PROGRESS_BAR) #define PROGRESS_BAR_BAR_TIME 2000 // (ms) Amount of time to show the bar #define PROGRESS_BAR_MSG_TIME 3000 // (ms) Amount of time to show the status message - #define PROGRESS_MSG_EXPIRE 0 // (ms) Amount of time to retain the status message (0=forever) + #define PROGRESS_MSG_EXPIRE 0 // (ms) Amount of time to retain the status message (0=forever) //#define PROGRESS_MSG_ONCE // Show the message for MSG_TIME then clear it //#define LCD_PROGRESS_BAR_TEST // Add a menu item to test the progress bar #endif #endif #endif -#if ENABLED(SDSUPPORT) +#if HAS_MEDIA + /** + * SD Card SPI Speed + * May be required to resolve "volume init" errors. + * + * Enable and set to SPI_HALF_SPEED, SPI_QUARTER_SPEED, or SPI_EIGHTH_SPEED + * otherwise full speed will be applied. + * + * :['SPI_HALF_SPEED', 'SPI_QUARTER_SPEED', 'SPI_EIGHTH_SPEED'] + */ + //#define SD_SPI_SPEED SPI_HALF_SPEED // 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 + //#define SD_IGNORE_AT_STARTUP // Don't mount the SD card when starting up //#define SDCARD_READONLY // Read-only SD card (to save over 2K of flash) + //#define GCODE_REPEAT_MARKERS // Enable G-code M808 to set repeat markers and do looping + #define SD_PROCEDURE_DEPTH 1 // Increase if you need more nested M32 calls #define SD_FINISHED_STEPPERRELEASE true // Disable steppers when SD Print is finished @@ -1179,14 +1802,28 @@ #define SD_MENU_CONFIRM_START // Confirm the selected SD file before printing + //#define NO_SD_AUTOSTART // Remove auto#.g file support completely to save some Flash, SRAM //#define MENU_ADDAUTOSTART // Add a menu option to run auto#.g files + //#define ONE_CLICK_PRINT // Prompt to print the newest file on inserted media + //#define BROWSE_MEDIA_ON_INSERT // Open the file browser when media is inserted + + //#define MEDIA_MENU_AT_TOP // Force the media menu to be listed on the top of the main menu + #define EVENT_GCODE_SD_ABORT "G28XY" // G-code to run on SD Abort Print (e.g., "G28XY" or "G27") #if ENABLED(PRINTER_EVENT_LEDS) #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) * @@ -1197,19 +1834,33 @@ */ //#define POWER_LOSS_RECOVERY #if ENABLED(POWER_LOSS_RECOVERY) - #define PLR_ENABLED_DEFAULT false // Power Loss Recovery enabled by default. (Set with 'M413 Sn' & M500) - //#define BACKUP_POWER_SUPPLY // Backup power / UPS to move the steppers on power loss - //#define POWER_LOSS_RECOVER_ZHOME // Z homing is needed for proper recovery. 99.9% of the time this should be disabled! - //#define POWER_LOSS_ZRAISE 2 // (mm) Z axis raise on resume (on power loss with UPS) - //#define POWER_LOSS_PIN 44 // Pin to detect power loss. Set to -1 to disable default pin on boards without module. - //#define POWER_LOSS_STATE HIGH // State of pin indicating power loss - //#define POWER_LOSS_PULL // Set pullup / pulldown as appropriate - //#define POWER_LOSS_PURGE_LEN 20 // (mm) Length of filament to purge on resume - //#define POWER_LOSS_RETRACT_LEN 10 // (mm) Length of filament to retract on fail. Requires backup power. + #define PLR_ENABLED_DEFAULT false // 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 44 // Pin to detect power-loss. Set to -1 to disable default pin on boards without module, or comment to use board default. + //#define POWER_LOSS_STATE HIGH // State of pin indicating power-loss + //#define POWER_LOSS_PULLUP // Set pullup / pulldown as appropriate for your sensor + //#define POWER_LOSS_PULLDOWN + + //#define POWER_LOSS_ZRAISE 2 // (mm) Z axis raise on resume (on power-loss with UPS) + //#define POWER_LOSS_PURGE_LEN 20 // (mm) Length of filament to purge on resume // Without a POWER_LOSS_PIN the following option helps reduce wear on the SD card, // especially with "vase mode" printing. Set too high and vases cannot be continued. - #define POWER_LOSS_MIN_Z_CHANGE 0.05 // (mm) Minimum Z change before saving power-loss data + #define POWER_LOSS_MIN_Z_CHANGE 0.05 // (mm) Minimum Z change before saving power-loss data + + //#define BACKUP_POWER_SUPPLY // Backup power / UPS to move the steppers on power-loss + #if ENABLED(BACKUP_POWER_SUPPLY) + //#define POWER_LOSS_RETRACT_LEN 10 // (mm) Length of filament to retract on fail + #endif + + // Enable if Z homing is needed for proper recovery. 99.9% of the time this should be disabled! + //#define POWER_LOSS_RECOVER_ZHOME + #if ENABLED(POWER_LOSS_RECOVER_ZHOME) + //#define POWER_LOSS_ZHOME_POS { 0, 0 } // Safe XY position to home Z while avoiding objects on the bed + #endif #endif /** @@ -1239,44 +1890,48 @@ // SD Card Sorting options #if ENABLED(SDCARD_SORT_ALPHA) - #define SDSORT_LIMIT 40 // Maximum number of sorted items (10-256). Costs 27 bytes each. - #define FOLDER_SORTING -1 // -1=above 0=none 1=below - #define SDSORT_GCODE false // Allow turning sorting on/off with LCD and M34 G-code. - #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 - // This allows hosts to request long names for files and folders with M33 - //#define LONG_FILENAME_HOST_SUPPORT + // Allow international symbols in long filenames. To display correctly, the + // LCD's font must contain the characters. Check your selected LCD language. + //#define UTF_FILENAME_SUPPORT - // Enable this option to scroll long filenames in the SD card menu - //#define SCROLL_LONG_FILENAMES + //#define LONG_FILENAME_HOST_SUPPORT // Get the long filename of a file/folder with 'M33 ' and list long filenames with 'M20 L' + //#define LONG_FILENAME_WRITE_SUPPORT // Create / delete files with long filenames via M28, M30, and Binary Transfer Protocol + //#define M20_TIMESTAMP_SUPPORT // Include timestamps by adding the 'T' flag to M20 commands - // Leave the heaters on after Stop Print (not recommended!) - //#define SD_ABORT_NO_COOLDOWN + //#define SCROLL_LONG_FILENAMES // Scroll long filenames in the SD card menu + + //#define SD_ABORT_NO_COOLDOWN // Leave the heaters on after Stop Print (not recommended!) /** - * This option allows you to abort SD printing when any endstop is triggered. - * This feature must be enabled with "M540 S1" or from the LCD menu. - * To have any effect, endstops must be enabled during SD printing. + * Abort SD printing when any endstop is triggered. + * This feature is enabled with 'M540 S1' or from the LCD menu. + * Endstops must be activated for this option to work. */ //#define SD_ABORT_ON_ENDSTOP_HIT + #if ENABLED(SD_ABORT_ON_ENDSTOP_HIT) + //#define SD_ABORT_ON_ENDSTOP_HIT_GCODE "G28XY" // G-code to run on endstop hit (e.g., "G28XY" or "G27") + #endif - /** - * This option makes it easier to print the same SD Card file again. - * On print completion the LCD Menu will open with the file selected. - * You can just click to start the print, or navigate elsewhere. - */ - //#define SD_REPRINT_LAST_SELECTED_FILE + //#define SD_REPRINT_LAST_SELECTED_FILE // On print completion open the LCD Menu and select the same file - /** - * Auto-report SdCard status with M27 S - */ - //#define AUTO_REPORT_SD_STATUS + //#define AUTO_REPORT_SD_STATUS // Auto-report media status with 'M27 S' /** * Support for USB thumb drives using an Arduino USB Host Shield or @@ -1288,15 +1943,12 @@ * * 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. */ //#define USB_FLASH_DRIVE_SUPPORT #if ENABLED(USB_FLASH_DRIVE_SUPPORT) - #define USB_CS_PIN SDSS - #define USB_INTR_PIN SD_DETECT_PIN - /** * USB Host Shield Library * @@ -1307,7 +1959,20 @@ * is less tested and is known to interfere with Servos. * [1] This requires USB_INTR_PIN to be interrupt-capable. */ + //#define USE_UHS2_USB //#define USE_UHS3_USB + + #define DISABLE_DUE_SD_MMC // Disable USB Host access to USB Drive to prevent hangs on block access for DUE platform + + /** + * Native USB Host supported by some boards (USB OTG) + */ + //#define USE_OTG_USB_HOST + + #if DISABLED(USE_OTG_USB_HOST) + #define USB_CS_PIN SD_SS_PIN + #define USB_INTR_PIN SD_DETECT_PIN + #endif #endif /** @@ -1326,21 +1991,52 @@ #define SD_FIRMWARE_UPDATE_INACTIVE_VALUE 0xFF #endif + /** + * Enable this option if you have more than ~3K of unused flash space. + * Marlin will embed all settings in the firmware binary as compressed data. + * Use 'M503 C' to write the settings out to the SD Card as 'mc.zip'. + * See docs/ConfigEmbedding.md for details on how to use 'mc-apply.py'. + */ + //#define CONFIGURATION_EMBEDDING + // Add an optimized binary file transfer mode, initiated with 'M28 B1' //#define BINARY_FILE_TRANSFER + #if ENABLED(BINARY_FILE_TRANSFER) + // Include extra facilities (e.g., 'M20 F') supporting firmware upload via BINARY_FILE_TRANSFER + //#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): * * LCD - Use the SD drive in the external LCD controller. - * ONBOARD - Use the SD drive on the control board. (No SD_DETECT_PIN. M21 to init.) + * ONBOARD - Use the SD drive on the control board. * CUSTOM_CABLE - Use a custom cable to access the SD (as defined in a pins file). * * :[ 'LCD', 'ONBOARD', 'CUSTOM_CABLE' ] */ //#define SDCARD_CONNECTION LCD -#endif // SDSUPPORT + // Enable if SD detect is rendered useless (e.g., by using an SD extender) + //#define NO_SD_DETECT + + /** + * Multiple volume support - EXPERIMENTAL. + * Adds 'M21 Pm' / 'M21 S' / 'M21 U' to mount SD Card / USB Drive. + */ + //#define MULTI_VOLUME + #if ENABLED(MULTI_VOLUME) + #define VOLUME_SD_ONBOARD + #define VOLUME_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 /** * By default an onboard SD card reader may be shared as a USB mass- @@ -1361,27 +2057,18 @@ * printing performance versus fast display updates. */ #if HAS_MARLINUI_U8GLIB - // Show SD percentage next to the progress bar - //#define DOGM_SD_PERCENT - // Save many cycles by drawing a hollow frame or no frame on the Info Screen //#define XYZ_NO_FRAME #define XYZ_HOLLOW_FRAME - // Enable to save many cycles by drawing a hollow frame on Menu Screens - #define MENU_HOLLOW_FRAME - - // A bigger font is available for edit items. Costs 3120 bytes of PROGMEM. + // A bigger font is available for edit items. Costs 3120 bytes of flash. // Western only. Not available for Cyrillic, Kana, Turkish, Greek, or Chinese. //#define USE_BIG_EDIT_FONT - // A smaller font may be used on the Info Screen. Costs 2434 bytes of PROGMEM. + // A smaller font may be used on the Info Screen. Costs 2434 bytes of flash. // Western only. Not available for Cyrillic, Kana, Turkish, Greek, or Chinese. //#define USE_SMALL_INFOFONT - // Swap the CW/CCW indicators in the graphics overlay - //#define OVERLAY_GFX_REVERSE - /** * ST7920-based LCDs can emulate a 16 x 4 character display using * the ST7920 character-generator for very fast screen updates. @@ -1394,10 +2081,10 @@ * Set STATUS_EXPIRE_SECONDS to zero to never clear the status. * This will prevent position updates from being displayed. */ - #if ENABLED(U8GLIB_ST7920) + #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) @@ -1406,37 +2093,44 @@ #endif /** - * Status (Info) Screen customizations + * Status (Info) Screen customization * These options may affect code size and screen render time. * Custom status screens can forcibly override these settings. */ //#define STATUS_COMBINE_HEATERS // Use combined heater images instead of separate ones //#define STATUS_HOTEND_NUMBERLESS // Use plain hotend icons instead of numbered ones (with 2+ hotends) - #define STATUS_HOTEND_INVERTED // Show solid nozzle bitmaps when heating (Requires STATUS_HOTEND_ANIM) + #define STATUS_HOTEND_INVERTED // Show solid nozzle bitmaps when heating (Requires STATUS_HOTEND_ANIM for numbered hotends) #define STATUS_HOTEND_ANIM // Use a second bitmap to indicate hotend heating #define STATUS_BED_ANIM // Use a second bitmap to indicate bed heating #define STATUS_CHAMBER_ANIM // Use a second bitmap to indicate chamber heating //#define STATUS_CUTTER_ANIM // Use a second bitmap to indicate spindle / laser active + //#define STATUS_COOLER_ANIM // Use a second bitmap to indicate laser cooling + //#define STATUS_FLOWMETER_ANIM // Use multiple bitmaps to indicate coolant flow //#define STATUS_ALT_BED_BITMAP // Use the alternative bed bitmap //#define STATUS_ALT_FAN_BITMAP // Use the alternative fan bitmap //#define STATUS_FAN_FRAMES 3 // :[0,1,2,3,4] Number of fan animation frames + + // Only one STATUS_HEAT_* option can be enabled //#define STATUS_HEAT_PERCENT // Show heating in a progress bar - //#define BOOT_MARLIN_LOGO_SMALL // Show a smaller Marlin logo on the Boot Screen (saving 399 bytes of flash) - //#define BOOT_MARLIN_LOGO_ANIMATED // Animated Marlin logo. Costs ~‭3260 (or ~940) bytes of PROGMEM. + //#define STATUS_HEAT_POWER // Show heater output power as a vertical bar + +#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 // HAS_MARLINUI_U8GLIB +#endif // // Additional options for DGUS / DWIN displays // #if HAS_DGUS_LCD - #define LCD_SERIAL_PORT 3 #define LCD_BAUDRATE 115200 #define DGUS_RX_BUFFER_SIZE 128 @@ -1445,12 +2139,12 @@ #define DGUS_UPDATE_INTERVAL_MS 500 // (ms) Interval between automatic screen updates - #if EITHER(DGUS_LCD_UI_FYSETC, DGUS_LCD_UI_HIPRECY) + #if DGUS_UI_IS(FYSETC, MKS, HIPRECY) #define DGUS_PRINT_FILENAME // Display the filename during printing #define DGUS_PREHEAT_UI // Display a preheat screen during heatup - #if ENABLED(DGUS_LCD_UI_FYSETC) - //#define DGUS_UI_MOVE_DIS_OPTION // Disabled by default for UI_FYSETC + #if DGUS_UI_IS(FYSETC, MKS) + //#define DGUS_UI_MOVE_DIS_OPTION // Disabled by default for FYSETC and MKS #else #define DGUS_UI_MOVE_DIS_OPTION // Enabled by default for UI_HIPRECY #endif @@ -1466,9 +2160,63 @@ #define DGUS_UI_WAITING_STATUS 10 #define DGUS_UI_WAITING_STATUS_PERIOD 8 // Increase to slower waiting status looping #endif + + #elif DGUS_UI_IS(E3S1PRO) + /** + * The stock Ender-3 S1 Pro/Plus display firmware has rather poor SD file handling. + * + * The autoscroll is mainly useful for status messages, filenames, and the "About" page. + * + * NOTE: The Advanced SD Card option is affected by the stock touchscreen firmware, so + * pages 5 and up will display "4/4". This may get fixed in a screen firmware update. + */ + #define DGUS_SOFTWARE_AUTOSCROLL // Enable long text software auto-scroll + #define DGUS_AUTOSCROLL_START_CYCLES 1 // Refresh cycles without scrolling at the beginning of text strings + #define DGUS_AUTOSCROLL_END_CYCLES 1 // ... at the end of text strings + + #define DGUS_ADVANCED_SDCARD // Allow more than 20 files and navigating directories + #define DGUS_USERCONFIRM // Reuse the SD Card page to show various messages #endif #endif // HAS_DGUS_LCD +// +// Additional options for AnyCubic Chiron TFT displays +// +#if ENABLED(ANYCUBIC_LCD_CHIRON) + // By default the type of panel is automatically detected. + // Enable one of these options if you know the panel type. + //#define CHIRON_TFT_STANDARD + //#define CHIRON_TFT_NEW + + // Enable the longer Anycubic powerup startup tune + //#define AC_DEFAULT_STARTUP_TUNE + + /** + * Display Folders + * By default the file browser lists all G-code files (including those in subfolders) in a flat list. + * Enable this option to display a hierarchical file browser. + * + * NOTES: + * - Without this option it helps to enable SDCARD_SORT_ALPHA so files are sorted before/after folders. + * - When used with the "new" panel, folder names will also have '.gcode' appended to their names. + * This hack is currently required to force the panel to show folders. + */ + #define AC_SD_FOLDER_VIEW +#endif + +// +// Specify additional languages for the UI. Default specified by LCD_LANGUAGE. +// +#if ANY(DOGLCD, TFT_COLOR_UI, TOUCH_UI_FTDI_EVE, IS_DWIN_MARLINUI, ANYCUBIC_LCD_VYPER) + //#define LCD_LANGUAGE_2 fr + //#define LCD_LANGUAGE_3 de + //#define LCD_LANGUAGE_4 es + //#define LCD_LANGUAGE_5 it + #ifdef LCD_LANGUAGE_2 + //#define LCD_LANGUAGE_AUTO_SAVE // Automatically save language to EEPROM on change + #endif +#endif + // // Touch UI for the FTDI Embedded Video Engine (EVE) // @@ -1478,8 +2226,10 @@ //#define LCD_4DSYSTEMS_4DLCD_FT843 // 4D Systems 4.3" (480x272) //#define LCD_HAOYU_FT800CB // Haoyu with 4.3" or 5" (480x272) //#define LCD_HAOYU_FT810CB // Haoyu with 5" (800x480) - //#define LCD_ALEPHOBJECTS_CLCD_UI // Aleph Objects Color LCD UI + //#define LCD_LULZBOT_CLCD_UI // LulzBot Color LCD UI //#define LCD_FYSETC_TFT81050 // FYSETC with 5" (800x480) + //#define LCD_EVE3_50G // Matrix Orbital 5.0", 800x480, BT815 + //#define LCD_EVE2_50G // Matrix Orbital 5.0", 800x480, FT813 // Correct the resolution if not using the stock TFT panel. //#define TOUCH_UI_320x240 @@ -1487,8 +2237,8 @@ //#define TOUCH_UI_800x480 // Mappings for boards with a standard RepRapDiscount Display connector - //#define AO_EXP1_PINMAP // AlephObjects CLCD UI EXP1 mapping - //#define AO_EXP2_PINMAP // AlephObjects CLCD UI EXP2 mapping + //#define AO_EXP1_PINMAP // LulzBot CLCD UI EXP1 mapping + //#define AO_EXP2_PINMAP // LulzBot CLCD UI EXP2 mapping //#define CR10_TFT_PINMAP // Rudolph Riedel's CR10 pin mapping //#define S6_TFT_PINMAP // FYSETC S6 pin mapping //#define F6_TFT_PINMAP // FYSETC F6 pin mapping @@ -1536,18 +2286,14 @@ //#define TOUCH_UI_UTF8_FRACTIONS // ΒΌ Β½ ΒΎ //#define TOUCH_UI_UTF8_SYMBOLS // Β΅ ΒΆ Β¦ Β§ Β¬ #endif + + // Cyrillic character set, costs about 27KiB of flash + //#define TOUCH_UI_UTF8_CYRILLIC_CHARSET #endif // Use a smaller font when labels don't fit buttons #define TOUCH_UI_FIT_TEXT - // Allow language selection from menu at run-time (otherwise use LCD_LANGUAGE) - //#define LCD_LANGUAGE_1 en - //#define LCD_LANGUAGE_2 fr - //#define LCD_LANGUAGE_3 de - //#define LCD_LANGUAGE_4 es - //#define LCD_LANGUAGE_5 it - // Use a numeric passcode for "Screen lock" keypad. // (recommended for smaller displays) //#define TOUCH_UI_PASSCODE @@ -1557,7 +2303,7 @@ // Developer menu (accessed by touching "About Printer" copyright text) //#define TOUCH_UI_DEVELOPER_MENU -#endif +#endif // TOUCH_UI_FTDI_EVE // // Classic UI Options @@ -1571,13 +2317,31 @@ //#define TFT_BTOKMENU_COLOR 0x145F // 00010 100010 11111 Cyan #endif +/** + * Display Sleep + * Enable this option to save energy and prevent OLED pixel burn-in. + */ +//#define DISPLAY_SLEEP_MINUTES 2 // (minutes) Timeout before turning off the screen + +/** + * LCD Backlight Timeout + * Requires a display with a controllable backlight + */ +//#define LCD_BACKLIGHT_TIMEOUT_MINS 1 // (minutes) Timeout before turning off the backlight + +#if defined(DISPLAY_SLEEP_MINUTES) || defined(LCD_BACKLIGHT_TIMEOUT_MINS) + #define EDITABLE_DISPLAY_TIMEOUT // Edit sleep / backlight timeout with M255 S and a menu item +#endif + // // 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 /** @@ -1594,7 +2358,7 @@ //#define WATCHDOG_RESET_MANUAL #endif -// @section lcd +// @section baby-stepping /** * Babystepping enables movement of the axes by tiny increments without changing @@ -1605,20 +2369,20 @@ */ //#define BABYSTEPPING #if ENABLED(BABYSTEPPING) - //#define INTEGRATED_BABYSTEPPING // EXPERIMENTAL integration of babystepping into the Stepper ISR + //#define EP_BABYSTEPPING // M293/M294 babystepping with EMERGENCY_PARSER support //#define BABYSTEP_WITHOUT_HOMING - //#define BABYSTEP_ALWAYS_AVAILABLE // Allow babystepping at all times (not just during movement). + //#define BABYSTEP_ALWAYS_AVAILABLE // Allow babystepping at all times (not just during movement) //#define BABYSTEP_XY // Also enable X/Y Babystepping. Not supported on DELTA! - #define BABYSTEP_INVERT_Z false // Change if Z babysteps should go the other way + //#define BABYSTEP_INVERT_Z // Enable if Z babysteps should go the other way //#define BABYSTEP_MILLIMETER_UNITS // Specify BABYSTEP_MULTIPLICATOR_(XY|Z) in mm instead of micro-steps #define BABYSTEP_MULTIPLICATOR_Z 1 // (steps or mm) Steps or millimeter distance for each Z babystep #define BABYSTEP_MULTIPLICATOR_XY 1 // (steps or mm) Steps or millimeter distance for each XY babystep //#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 doubleclick when printer is idle. + //#define MOVE_Z_WHEN_IDLE // Jump to the move Z menu on double-click when printer is idle. #if ENABLED(MOVE_Z_WHEN_IDLE) #define MOVE_Z_IDLE_MULTIPLICATOR 1 // Multiply 1mm by this factor for the move step size. #endif @@ -1629,7 +2393,7 @@ //#define BABYSTEP_ZPROBE_OFFSET // Combine M851 Z and Babystepping #if ENABLED(BABYSTEP_ZPROBE_OFFSET) //#define BABYSTEP_HOTEND_Z_OFFSET // For multiple hotends, babystep relative Z offsets - //#define BABYSTEP_ZPROBE_GFX_OVERLAY // Enable graphical overlay on Z-offset editor + //#define BABYSTEP_GFX_OVERLAY // Enable graphical overlay on Z-offset editor #endif #endif @@ -1651,26 +2415,76 @@ * See https://marlinfw.org/docs/features/lin_advance.html for full instructions. */ //#define 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. Override with 'M900 T K'. + #else + #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'. +#endif + #if ENABLED(LIN_ADVANCE) - //#define EXTRA_LIN_ADVANCE_K // Enable for second linear advance constants - #define LIN_ADVANCE_K 0.22 // Unit: mm compression per 1mm/s extruder speed - //#define LA_DEBUG // If enabled, this will generate debug information output over USB. - //#define EXPERIMENTAL_SCURVE // Enable this option to permit S-Curve Acceleration + //#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 + +/** + * Nonlinear Extrusion Control + * + * 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 +/** + * Use Safe Bed Leveling coordinates to move axes to a useful position before bed probing. + * For example, after homing a rotational axis the Z probe might not be perpendicular to the bed. + * Choose values the orient the bed horizontally and the Z-probe vertically. + */ +//#define SAFE_BED_LEVELING_START_X 0.0 +//#define SAFE_BED_LEVELING_START_Y 0.0 +//#define SAFE_BED_LEVELING_START_Z 0.0 +//#define SAFE_BED_LEVELING_START_I 0.0 +//#define SAFE_BED_LEVELING_START_J 0.0 +//#define SAFE_BED_LEVELING_START_K 0.0 +//#define SAFE_BED_LEVELING_START_U 0.0 +//#define SAFE_BED_LEVELING_START_V 0.0 +//#define SAFE_BED_LEVELING_START_W 0.0 + /** * Points to probe for all 3-point Leveling procedures. * Override if the automatically selected points are inadequate. */ -#if EITHER(AUTO_BED_LEVELING_3POINT, AUTO_BED_LEVELING_UBL) - //#define PROBE_PT_1_X 15 - //#define PROBE_PT_1_Y 180 - //#define PROBE_PT_2_X 15 - //#define PROBE_PT_2_Y 20 - //#define PROBE_PT_3_X 170 - //#define PROBE_PT_3_Y 20 +#if NEEDS_THREE_PROBE_POINTS + //#define PROBE_PT_1 { 15, 180 } // (mm) { x, y } + //#define PROBE_PT_2 { 15, 20 } + //#define PROBE_PT_3 { 170, 20 } #endif /** @@ -1699,7 +2513,7 @@ //#define PROBING_MARGIN_BACK PROBING_MARGIN #endif -#if EITHER(MESH_BED_LEVELING, AUTO_BED_LEVELING_UBL) +#if ANY(MESH_BED_LEVELING, AUTO_BED_LEVELING_UBL) // Override the mesh area if the automatic (max) area is too large //#define MESH_MIN_X MESH_INSET //#define MESH_MIN_Y MESH_INSET @@ -1707,6 +2521,10 @@ //#define MESH_MAX_Y Y_BED_SIZE - (MESH_INSET) #endif +#if ALL(AUTO_BED_LEVELING_UBL, EEPROM_SETTINGS) + //#define OPTIMIZED_MESH_STORAGE // Store mesh with less precision to save EEPROM space +#endif + /** * Repeatedly attempt G29 leveling until it succeeds. * Stop after G29_MAX_RETRIES attempts. @@ -1725,86 +2543,103 @@ #endif +// @section probes + /** * Thermal Probe Compensation - * Probe measurements are adjusted to compensate for temperature distortion. - * Use G76 to calibrate this feature. Use M871 to set values manually. - * For a more detailed explanation of the process see G76_M871.cpp. + * + * Adjust probe measurements to compensate for distortion associated with the temperature + * of the probe, bed, and/or hotend. + * Use G76 to automatically calibrate this feature for probe and bed temperatures. + * (Extruder temperature/offset values must be calibrated manually.) + * Use M871 to set temperature/offset values manually. + * For more details see https://marlinfw.org/docs/features/probe_temp_compensation.html */ -#if HAS_BED_PROBE && TEMP_SENSOR_PROBE && TEMP_SENSOR_BED - // Enable thermal first layer compensation using bed and probe temperatures - #define PROBE_TEMP_COMPENSATION +//#define PTC_PROBE // Compensate based on probe temperature +//#define PTC_BED // Compensate based on bed temperature +//#define PTC_HOTEND // Compensate based on hotend temperature - // Add additional compensation depending on hotend temperature - // Note: this values cannot be calibrated and have to be set manually - #if ENABLED(PROBE_TEMP_COMPENSATION) +#if ANY(PTC_PROBE, PTC_BED, PTC_HOTEND) + /** + * If the probe is outside the defined range, use linear extrapolation with the closest + * point and the point with index PTC_LINEAR_EXTRAPOLATION. e.g., If set to 4 it will use the + * linear extrapolation between data[0] and data[4] for values below PTC_PROBE_START. + */ + //#define PTC_LINEAR_EXTRAPOLATION 4 + + #if ENABLED(PTC_PROBE) + // Probe temperature calibration generates a table of values starting at PTC_PROBE_START + // (e.g., 30), in steps of PTC_PROBE_RES (e.g., 5) with PTC_PROBE_COUNT (e.g., 10) samples. + #define PTC_PROBE_START 30 // (Β°C) + #define PTC_PROBE_RES 5 // (Β°C) + #define PTC_PROBE_COUNT 10 + #define PTC_PROBE_ZOFFS { 0 } // (Β΅m) Z adjustments per sample + #endif + + #if ENABLED(PTC_BED) + // Bed temperature calibration builds a similar table. + #define PTC_BED_START 60 // (Β°C) + #define PTC_BED_RES 5 // (Β°C) + #define PTC_BED_COUNT 10 + #define PTC_BED_ZOFFS { 0 } // (Β΅m) Z adjustments per sample + #endif + + #if ENABLED(PTC_HOTEND) + // Note: There is no automatic calibration for the hotend. Use M871. + #define PTC_HOTEND_START 180 // (Β°C) + #define PTC_HOTEND_RES 5 // (Β°C) + #define PTC_HOTEND_COUNT 20 + #define PTC_HOTEND_ZOFFS { 0 } // (Β΅m) Z adjustments per sample + #endif + + // G76 options + #if ALL(PTC_PROBE, PTC_BED) // Park position to wait for probe cooldown #define PTC_PARK_POS { 0, 0, 100 } // Probe position to probe and wait for probe to reach target temperature + //#define PTC_PROBE_POS { 12.0f, 7.3f } // Example: MK52 magnetic heatbed #define PTC_PROBE_POS { 90, 100 } - // Enable additional compensation using hotend temperature - // Note: this values cannot be calibrated automatically but have to be set manually - //#define USE_TEMP_EXT_COMPENSATION + // The temperature the probe should be at while taking measurements during + // bed temperature calibration. + #define PTC_PROBE_TEMP 30 // (Β°C) - // Probe temperature calibration generates a table of values starting at PTC_SAMPLE_START - // (e.g. 30), in steps of PTC_SAMPLE_RES (e.g. 5) with PTC_SAMPLE_COUNT (e.g. 10) samples. - - //#define PTC_SAMPLE_START 30.0f - //#define PTC_SAMPLE_RES 5.0f - //#define PTC_SAMPLE_COUNT 10U - - // Bed temperature calibration builds a similar table. - - //#define BTC_SAMPLE_START 60.0f - //#define BTC_SAMPLE_RES 5.0f - //#define BTC_SAMPLE_COUNT 10U - - // The temperature the probe should be at while taking measurements during bed temperature - // calibration. - //#define BTC_PROBE_TEMP 30.0f - - // Height above Z=0.0f to raise the nozzle. Lowering this can help the probe to heat faster. - // Note: the Z=0.0f offset is determined by the probe offset which can be set using M851. - //#define PTC_PROBE_HEATING_OFFSET 0.5f - - // Height to raise the Z-probe between heating and taking the next measurement. Some probes - // may fail to untrigger if they have been triggered for a long time, which can be solved by - // increasing the height the probe is raised to. - //#define PTC_PROBE_RAISE 15U - - // If the probe is outside of the defined range, use linear extrapolation using the closest - // point and the PTC_LINEAR_EXTRAPOLATION'th next point. E.g. if set to 4 it will use data[0] - // and data[4] to perform linear extrapolation for values below PTC_SAMPLE_START. - //#define PTC_LINEAR_EXTRAPOLATION 4 + // Height above Z=0.0 to raise the nozzle. Lowering this can help the probe to heat faster. + // Note: The Z=0.0 offset is determined by the probe Z offset (e.g., as set with M851 Z). + #define PTC_PROBE_HEATING_OFFSET 0.5 // (mm) #endif -#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 // -#define ARC_SUPPORT // Disable this feature to save ~3226 bytes +#define ARC_SUPPORT // Requires ~3226 bytes #if ENABLED(ARC_SUPPORT) - #define MM_PER_ARC_SEGMENT 1 // (mm) Length (or minimum length) of each arc segment - //#define ARC_SEGMENTS_PER_R 1 // Max segment length, MM_PER = Min - #define MIN_ARC_SEGMENTS 24 // Minimum number of segments in a complete circle - //#define ARC_SEGMENTS_PER_SEC 50 // Use feedrate to choose segment length (with MM_PER_ARC_SEGMENT as the minimum) - #define N_ARC_CORRECTION 25 // Number of interpolated segments between corrections - //#define ARC_P_CIRCLES // Enable the 'P' parameter to specify complete circles - //#define CNC_WORKSPACE_PLANES // Allow G2/G3 to operate in XY, ZX, or YZ planes - //#define SF_ARC_FIX // Enable only if using SkeinForge with "Arc Point" fillet procedure + #define MIN_ARC_SEGMENT_MM 0.1 // (mm) Minimum length of each arc segment + #define MAX_ARC_SEGMENT_MM 1.0 // (mm) Maximum length of each arc segment + #define MIN_CIRCLE_SEGMENTS 72 // Minimum number of segments in a complete circle + //#define ARC_SEGMENTS_PER_SEC 50 // Use the feedrate to choose the segment length + #define N_ARC_CORRECTION 25 // Number of interpolated segments between corrections + //#define ARC_P_CIRCLES // Enable the 'P' parameter to specify complete circles + //#define SF_ARC_FIX // Enable only if using SkeinForge with "Arc Point" fillet procedure #endif -// Support for G5 with XYZE destination and IJPQ offsets. Requires ~2666 bytes. -//#define BEZIER_CURVE_SUPPORT +// G5 BΓ©zier Curve Support with XYZE destination and IJPQ offsets +//#define BEZIER_CURVE_SUPPORT // Requires ~2666 bytes + +#if ANY(ARC_SUPPORT, BEZIER_CURVE_SUPPORT) + //#define CNC_WORKSPACE_PLANES // Allow G2/G3/G5 to operate in XY, ZX, or YZ planes +#endif /** * Direct Stepping @@ -1816,6 +2651,8 @@ */ //#define DIRECT_STEPPING +// @section calibrate + /** * G38 Probe Target * @@ -1829,6 +2666,8 @@ #define G38_MINIMUM_MOVE 0.0275 // (mm) Minimum distance that will produce a move. #endif +// @section motion + // Moves (or segments) with fewer steps than this will be joined with the next move #define MIN_STEPS_PER_SEGMENT 6 @@ -1849,27 +2688,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. */ @@ -1884,13 +2724,12 @@ //================================= Buffers ================================= //=========================================================================== -// @section motion +// @section gcode // The number of linear moves that can be in the planner at once. -// The value of BLOCK_BUFFER_SIZE must be a power of 2 (e.g. 8, 16, 32) -#if BOTH(SDSUPPORT, DIRECT_STEPPING) +#if ALL(HAS_MEDIA, DIRECT_STEPPING) #define BLOCK_BUFFER_SIZE 8 -#elif ENABLED(SDSUPPORT) +#elif HAS_MEDIA #define BLOCK_BUFFER_SIZE 16 #else #define BLOCK_BUFFER_SIZE 16 @@ -1902,19 +2741,23 @@ #define MAX_CMD_SIZE 96 #define BUFSIZE 4 -// Transmission to Host Buffer Size -// To save 386 bytes of PROGMEM (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 @@ -1923,10 +2766,7 @@ //#define SERIAL_XON_XOFF #endif -// Add M575 G-code to change the baud rate -//#define BAUD_RATE_GCODE - -#if ENABLED(SDSUPPORT) +#if HAS_MEDIA // Enable this option to collect and display the maximum // RX queue usage after transferring a file to SD. //#define SERIAL_STATS_MAX_RX_QUEUED @@ -1936,6 +2776,12 @@ //#define SERIAL_STATS_DROPPED_RX #endif +// Monitor RX buffer usage +// Dump an error to the serial port if the serial receive buffer overflows. +// If you see these errors, increase the RX_BUFFER_SIZE value. +// Not supported on all platforms. +//#define RX_BUFFER_MONITOR + /** * Emergency Command Parser * @@ -1946,11 +2792,33 @@ */ //#define EMERGENCY_PARSER -// Bad Serial-connections can miss a received command by sending an 'ok' -// Therefore some clients abort after 30 seconds in a timeout. -// Some other clients start sending commands while receiving a 'wait'. -// This "wait" is only sent when the buffer is empty. 1 second is a good value here. -//#define NO_TIMEOUTS 1000 // Milliseconds +/** + * Realtime Reporting (requires EMERGENCY_PARSER) + * + * - Report position and state of the machine (like Grbl). + * - Auto-report position during long moves. + * - Useful for CNC/LASER. + * + * Adds support for commands: + * S000 : Report State and Position while moving. + * P000 : Instant Pause / Hold while moving. + * R000 : Resume from Pause / Hold. + * + * - During Hold all Emergency Parser commands are available, as usual. + * - Enable NANODLP_Z_SYNC and NANODLP_ALL_AXIS for move command end-state reports. + */ +//#define REALTIME_REPORTING_COMMANDS +#if ENABLED(REALTIME_REPORTING_COMMANDS) + //#define FULL_REPORT_TO_HOST_FEATURE // Auto-report the machine status like Grbl CNC +#endif + +/** + * Bad Serial-connections can miss a received command by sending an 'ok' + * Therefore some clients abort after 30 seconds in a timeout. + * Some other clients start sending commands while receiving a 'wait'. + * This "wait" is only sent when the buffer is empty. 1 second is a good value here. + */ +//#define NO_TIMEOUTS 1000 // (ms) // Some clients will have this feature soon. This could make the NO_TIMEOUTS unnecessary. //#define ADVANCED_OK @@ -1962,6 +2830,24 @@ // For serial echo, the number of digits after the decimal point //#define SERIAL_FLOAT_PRECISION 4 +/** + * 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. + * 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. + * + * For clients that use a fixed-width font (like OctoPrint), leave this set to 1.0. + * Otherwise, adjust according to your client and font. + */ +#define PROPORTIONAL_FONT_RATIO 1.0 + // @section extras /** @@ -1973,6 +2859,8 @@ */ //#define EXTRA_FAN_SPEED +// @section firmware retraction + /** * Firmware-based and LCD-controlled retract * @@ -1989,24 +2877,26 @@ */ //#define FWRETRACT #if ENABLED(FWRETRACT) - #define FWRETRACT_AUTORETRACT // Override slicer retractions + #define FWRETRACT_AUTORETRACT // Override slicer retractions #if ENABLED(FWRETRACT_AUTORETRACT) - #define MIN_AUTORETRACT 0.1 // (mm) Don't convert E moves under this length - #define MAX_AUTORETRACT 10.0 // (mm) Don't convert E moves over this length + #define MIN_AUTORETRACT 0.1 // (mm) Don't convert E moves under this length + #define MAX_AUTORETRACT 10.0 // (mm) Don't convert E moves over this length #endif - #define RETRACT_LENGTH 3 // (mm) Default retract length (positive value) - #define RETRACT_LENGTH_SWAP 13 // (mm) Default swap retract length (positive value) - #define RETRACT_FEEDRATE 45 // (mm/s) Default feedrate for retracting - #define RETRACT_ZRAISE 0 // (mm) Default retract Z-raise - #define RETRACT_RECOVER_LENGTH 0 // (mm) Default additional recover length (added to retract length on recover) - #define RETRACT_RECOVER_LENGTH_SWAP 0 // (mm) Default additional swap recover length (added to retract length on recover from toolchange) - #define RETRACT_RECOVER_FEEDRATE 8 // (mm/s) Default feedrate for recovering from retraction - #define RETRACT_RECOVER_FEEDRATE_SWAP 8 // (mm/s) Default feedrate for recovering from swap retraction + #define RETRACT_LENGTH 3 // (mm) Default retract length (positive value) + #define RETRACT_LENGTH_SWAP 13 // (mm) Default swap retract length (positive value) + #define RETRACT_FEEDRATE 45 // (mm/s) Default feedrate for retracting + #define RETRACT_ZRAISE 0 // (mm) Default retract Z-raise + #define RETRACT_RECOVER_LENGTH 0 // (mm) Default additional recover length (added to retract length on recover) + #define RETRACT_RECOVER_LENGTH_SWAP 0 // (mm) Default additional swap recover length (added to retract length on recover from toolchange) + #define RETRACT_RECOVER_FEEDRATE 8 // (mm/s) Default feedrate for recovering from retraction + #define RETRACT_RECOVER_FEEDRATE_SWAP 8 // (mm/s) Default feedrate for recovering from swap retraction #if ENABLED(MIXING_EXTRUDER) - //#define RETRACT_SYNC_MIXING // Retract and restore all mixing steppers simultaneously + //#define RETRACT_SYNC_MIXING // Retract and restore all mixing steppers simultaneously #endif #endif +// @section tool change + /** * Universal tool change settings. * Applies to all types of extruders except where explicitly noted. @@ -2020,6 +2910,28 @@ //#define EVENT_GCODE_AFTER_TOOLCHANGE "G12X" // Extra G-code to run after tool-change #endif + /** + * Extra G-code to run while executing tool-change commands. Can be used to use an additional + * stepper motor (e.g., I axis in Configuration.h) to drive the tool-changer. + */ + //#define EVENT_GCODE_TOOLCHANGE_T0 "G28 A\nG1 A0" // Extra G-code to run while executing tool-change command T0 + //#define EVENT_GCODE_TOOLCHANGE_T1 "G1 A10" // Extra G-code to run while executing tool-change command T1 + //#define EVENT_GCODE_TOOLCHANGE_ALWAYS_RUN // Always execute above G-code sequences. Use with caution! + + /** + * Consider coordinates for EVENT_GCODE_TOOLCHANGE_Tx as relative to T0 + * so that moves in the specified axes are the same for all tools. + */ + //#define TC_GCODE_USE_GLOBAL_X // Use X position relative to Tool 0 + //#define TC_GCODE_USE_GLOBAL_Y // Use Y position relative to Tool 0 + //#define TC_GCODE_USE_GLOBAL_Z // Use Z position relative to Tool 0 + + /** + * Tool Sensors detect when tools have been picked up or dropped. + * Requires the pins TOOL_SENSOR1_PIN, TOOL_SENSOR2_PIN, etc. + */ + //#define TOOL_SENSOR + /** * Retract and prime filament on tool-change to reduce * ooze and stringing and to get cleaner transitions. @@ -2028,26 +2940,30 @@ #if ENABLED(TOOLCHANGE_FILAMENT_SWAP) // Load / Unload #define TOOLCHANGE_FS_LENGTH 12 // (mm) Load / Unload length - #define TOOLCHANGE_FS_EXTRA_RESUME_LENGTH 0 // (mm) Extra length for better restart, fine tune by LCD/Gcode) + #define TOOLCHANGE_FS_EXTRA_RESUME_LENGTH 0 // (mm) Extra length for better restart. Adjust with LCD or M217 B. #define TOOLCHANGE_FS_RETRACT_SPEED (50*60) // (mm/min) (Unloading) #define TOOLCHANGE_FS_UNRETRACT_SPEED (25*60) // (mm/min) (On SINGLENOZZLE or Bowden loading must be slowed down) // Longer prime to clean out a SINGLENOZZLE #define TOOLCHANGE_FS_EXTRA_PRIME 0 // (mm) Extra priming length #define TOOLCHANGE_FS_PRIME_SPEED (4.6*60) // (mm/min) Extra priming feedrate - #define TOOLCHANGE_FS_WIPE_RETRACT 0 // (mm/min) Retract before cooling for less stringing, better wipe, etc. + #define TOOLCHANGE_FS_WIPE_RETRACT 0 // (mm) Cutting retraction out of park, for less stringing, better wipe, etc. Adjust with LCD or M217 G. // Cool after prime to reduce stringing #define TOOLCHANGE_FS_FAN -1 // Fan index or -1 to skip #define TOOLCHANGE_FS_FAN_SPEED 255 // 0-255 #define TOOLCHANGE_FS_FAN_TIME 10 // (seconds) - // Swap uninitialized extruder with TOOLCHANGE_FS_PRIME_SPEED for all lengths (recover + prime) - // (May break filament if not retracted beforehand.) - //#define TOOLCHANGE_FS_INIT_BEFORE_SWAP + // Use TOOLCHANGE_FS_PRIME_SPEED feedrate the first time each extruder is primed + //#define TOOLCHANGE_FS_SLOW_FIRST_PRIME - // Prime on the first T0 (If other, TOOLCHANGE_FS_INIT_BEFORE_SWAP applied) - // Enable it (M217 V[0/1]) before printing, to avoid unwanted priming on host connect + /** + * Prime T0 the first time T0 is sent to the printer: + * [ Power-On -> T0 { Activate & Prime T0 } -> T1 { Retract T0, Activate & Prime T1 } ] + * If disabled, no priming on T0 until switching back to T0 from another extruder: + * [ Power-On -> T0 { T0 Activated } -> T1 { Activate & Prime T1 } -> T0 { Retract T1, Activate & Prime T0 } ] + * Enable with M217 V1 before printing to avoid unwanted priming on host connect. + */ //#define TOOLCHANGE_FS_PRIME_FIRST_USED /** @@ -2061,7 +2977,21 @@ * - Switch to a different nozzle on an extruder jam */ #define TOOLCHANGE_MIGRATION_FEATURE + #if ENABLED(TOOLCHANGE_MIGRATION_FEATURE) + // Override toolchange settings + // By default tool migration uses regular toolchange settings. + // With a prime tower, tool-change swapping/priming occur inside the bed. + // When migrating to a new unprimed tool you can set override values below. + //#define MIGRATION_ZRAISE 0 // (mm) + // Longer prime to clean out + //#define MIGRATION_FS_EXTRA_PRIME 0 // (mm) Extra priming length + //#define MIGRATION_FS_WIPE_RETRACT 0 // (mm) Retract before cooling for less stringing, better wipe, etc. + + // Cool after prime to reduce stringing + //#define MIGRATION_FS_FAN_SPEED 255 // 0-255 + //#define MIGRATION_FS_FAN_TIME 0 // (seconds) + #endif #endif /** @@ -2074,18 +3004,24 @@ #define TOOLCHANGE_PARK_XY_FEEDRATE 6000 // (mm/min) //#define TOOLCHANGE_PARK_X_ONLY // X axis only move //#define TOOLCHANGE_PARK_Y_ONLY // Y axis only move + #if ENABLED(TOOLCHANGE_MIGRATION_FEATURE) + //#define TOOLCHANGE_MIGRATION_DO_PARK // Force park (or no-park) on migration + #endif #endif #endif // HAS_MULTI_EXTRUDER +// @section advanced pause + /** - * Advanced Pause - * Experimental feature for filament change support and for parking the nozzle when paused. - * Adds the GCode M600 for initiating filament change. - * If PARK_HEAD_ON_PAUSE enabled, adds the GCode M125 to pause printing and park the nozzle. + * Advanced Pause for Filament Change + * - Adds the G-code M600 Filament Change to initiate a filament change. + * - This feature is required for the default FILAMENT_RUNOUT_SCRIPT. * - * Requires an LCD display. - * Requires NOZZLE_PARK_FEATURE. - * This feature is required for the default FILAMENT_RUNOUT_SCRIPT. + * Requirements: + * - For Filament Change parking enable and configure NOZZLE_PARK_FEATURE. + * - For user interaction enable an LCD display, HOST_PROMPT_SUPPORT, or EMERGENCY_PARSER. + * + * Enable PARK_HEAD_ON_PAUSE to add the G-code M125 Pause and Park. */ //#define ADVANCED_PAUSE_FEATURE #if ENABLED(ADVANCED_PAUSE_FEATURE) @@ -2124,294 +3060,319 @@ #define PAUSE_PARK_NOZZLE_TIMEOUT 45 // (seconds) Time limit before the nozzle is turned off for safety. #define FILAMENT_CHANGE_ALERT_BEEPS 10 // Number of alert beeps to play when a response is needed. #define PAUSE_PARK_NO_STEPPER_TIMEOUT // Enable for XYZ steppers to stay powered on during filament change. + //#define FILAMENT_CHANGE_RESUME_ON_INSERT // Automatically continue / load filament when runout sensor is triggered again. + //#define PAUSE_REHEAT_FAST_RESUME // Reduce number of waits by not prompting again post-timeout before continuing. //#define PARK_HEAD_ON_PAUSE // Park the nozzle during pause and filament change. //#define HOME_BEFORE_FILAMENT_CHANGE // If needed, home before parking for filament change //#define FILAMENT_LOAD_UNLOAD_GCODES // Add M701/M702 Load/Unload G-codes, plus Load/Unload in the LCD Prepare menu. //#define FILAMENT_UNLOAD_ALL_EXTRUDERS // Allow M702 to unload all extruders above a minimum target temp (as set by M302) + #define CONFIGURE_FILAMENT_CHANGE // Add M603 G-code and menu items. Requires ~1.3K bytes of flash. #endif -// @section tmc - -/** - * TMC26X Stepper Driver options - * - * The TMC26XStepper library is required for this stepper driver. - * https://github.com/MarlinFirmware/TMC26XStepper - */ -#if HAS_DRIVER(TMC26X) - - #if AXIS_DRIVER_TYPE_X(TMC26X) - #define X_MAX_CURRENT 1000 // (mA) - #define X_SENSE_RESISTOR 91 // (mOhms) - #define X_MICROSTEPS 16 // Number of microsteps - #endif - - #if AXIS_DRIVER_TYPE_X2(TMC26X) - #define X2_MAX_CURRENT 1000 - #define X2_SENSE_RESISTOR 91 - #define X2_MICROSTEPS 16 - #endif - - #if AXIS_DRIVER_TYPE_Y(TMC26X) - #define Y_MAX_CURRENT 1000 - #define Y_SENSE_RESISTOR 91 - #define Y_MICROSTEPS 16 - #endif - - #if AXIS_DRIVER_TYPE_Y2(TMC26X) - #define Y2_MAX_CURRENT 1000 - #define Y2_SENSE_RESISTOR 91 - #define Y2_MICROSTEPS 16 - #endif - - #if AXIS_DRIVER_TYPE_Z(TMC26X) - #define Z_MAX_CURRENT 1000 - #define Z_SENSE_RESISTOR 91 - #define Z_MICROSTEPS 16 - #endif - - #if AXIS_DRIVER_TYPE_Z2(TMC26X) - #define Z2_MAX_CURRENT 1000 - #define Z2_SENSE_RESISTOR 91 - #define Z2_MICROSTEPS 16 - #endif - - #if AXIS_DRIVER_TYPE_Z3(TMC26X) - #define Z3_MAX_CURRENT 1000 - #define Z3_SENSE_RESISTOR 91 - #define Z3_MICROSTEPS 16 - #endif - - #if AXIS_DRIVER_TYPE_Z4(TMC26X) - #define Z4_MAX_CURRENT 1000 - #define Z4_SENSE_RESISTOR 91 - #define Z4_MICROSTEPS 16 - #endif - - #if AXIS_DRIVER_TYPE_E0(TMC26X) - #define E0_MAX_CURRENT 1000 - #define E0_SENSE_RESISTOR 91 - #define E0_MICROSTEPS 16 - #endif - - #if AXIS_DRIVER_TYPE_E1(TMC26X) - #define E1_MAX_CURRENT 1000 - #define E1_SENSE_RESISTOR 91 - #define E1_MICROSTEPS 16 - #endif - - #if AXIS_DRIVER_TYPE_E2(TMC26X) - #define E2_MAX_CURRENT 1000 - #define E2_SENSE_RESISTOR 91 - #define E2_MICROSTEPS 16 - #endif - - #if AXIS_DRIVER_TYPE_E3(TMC26X) - #define E3_MAX_CURRENT 1000 - #define E3_SENSE_RESISTOR 91 - #define E3_MICROSTEPS 16 - #endif - - #if AXIS_DRIVER_TYPE_E4(TMC26X) - #define E4_MAX_CURRENT 1000 - #define E4_SENSE_RESISTOR 91 - #define E4_MICROSTEPS 16 - #endif - - #if AXIS_DRIVER_TYPE_E5(TMC26X) - #define E5_MAX_CURRENT 1000 - #define E5_SENSE_RESISTOR 91 - #define E5_MICROSTEPS 16 - #endif - - #if AXIS_DRIVER_TYPE_E6(TMC26X) - #define E6_MAX_CURRENT 1000 - #define E6_SENSE_RESISTOR 91 - #define E6_MICROSTEPS 16 - #endif - - #if AXIS_DRIVER_TYPE_E7(TMC26X) - #define E7_MAX_CURRENT 1000 - #define E7_SENSE_RESISTOR 91 - #define E7_MICROSTEPS 16 - #endif - -#endif // TMC26X - // @section tmc_smart /** - * To use TMC2130, TMC2160, TMC2660, TMC5130, TMC5160 stepper drivers in SPI mode - * connect your SPI pins to the hardware SPI interface on your board and define - * the required CS pins in your `pins_MYBOARD.h` file. (e.g., RAMPS 1.4 uses AUX3 - * pins `X_CS_PIN 53`, `Y_CS_PIN 49`, etc.). - * You may also use software SPI if you wish to use general purpose IO pins. + * Trinamic Smart Drivers * - * To use TMC2208 stepper UART-configurable stepper drivers connect #_SERIAL_TX_PIN - * to the driver side PDN_UART pin with a 1K resistor. - * To use the reading capabilities, also connect #_SERIAL_RX_PIN to PDN_UART without - * a resistor. - * The drivers can also be used with hardware serial. + * 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. + * (See the RAMPS pins, for example.) + * - You can also use Software SPI with GPIO pins instead of Hardware SPI. * - * TMCStepper library is required to use TMC stepper drivers. - * https://github.com/teemuatlut/TMCStepper + * To use TMC220x stepper drivers with Serial UART: + * - Connect PDN_UART to the #_SERIAL_TX_PIN through a 1K resistor. + * For reading capabilities also connect PDN_UART to #_SERIAL_RX_PIN with no resistor. + * Some boards have simple jumper connections! See your board's documentation. + * - These drivers can also be used with Hardware Serial. + * + * The TMCStepper library is required for other TMC stepper drivers. + * https://github.com/teemuatlut/TMCStepper + * + * @section tmc/config */ #if HAS_TRINAMIC_CONFIG #define HOLD_MULTIPLIER 0.5 // Scales down the holding current from run current - #define INTERPOLATE true // Interpolate X/Y/Z_MICROSTEPS to 256 - #if AXIS_IS_TMC(X) + //#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_MICROSTEPS 16 // 0..256 + #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 // <=0 : Not chained. 1 : MCU MOSI connected. 2 : Next in chain, ... + #define X_CHAIN_POS -1 // -1..0: Not chained. 1: MCU MOSI connected. 2: Next in chain, ... + //#define X_INTERPOLATE true // Enable to override 'INTERPOLATE' for the X axis + //#define X_HOLD_MULTIPLIER 0.5 // Enable to override 'HOLD_MULTIPLIER' for the X axis #endif - #if AXIS_IS_TMC(X2) - #define X2_CURRENT 800 - #define X2_CURRENT_HOME X2_CURRENT - #define X2_MICROSTEPS 16 - #define X2_RSENSE 0.11 + #if AXIS_IS_TMC_CONFIG(X2) + #define X2_CURRENT X_CURRENT + #define X2_CURRENT_HOME X_CURRENT_HOME + #define X2_MICROSTEPS X_MICROSTEPS + #define X2_RSENSE X_RSENSE #define X2_CHAIN_POS -1 + //#define X2_INTERPOLATE true + //#define X2_HOLD_MULTIPLIER 0.5 #endif - #if AXIS_IS_TMC(Y) + #if AXIS_IS_TMC_CONFIG(Y) #define Y_CURRENT 800 #define Y_CURRENT_HOME Y_CURRENT #define Y_MICROSTEPS 16 #define Y_RSENSE 0.11 #define Y_CHAIN_POS -1 + //#define Y_INTERPOLATE true + //#define Y_HOLD_MULTIPLIER 0.5 #endif - #if AXIS_IS_TMC(Y2) - #define Y2_CURRENT 800 - #define Y2_CURRENT_HOME Y2_CURRENT - #define Y2_MICROSTEPS 16 - #define Y2_RSENSE 0.11 + #if AXIS_IS_TMC_CONFIG(Y2) + #define Y2_CURRENT Y_CURRENT + #define Y2_CURRENT_HOME Y_CURRENT_HOME + #define Y2_MICROSTEPS Y_MICROSTEPS + #define Y2_RSENSE Y_RSENSE #define Y2_CHAIN_POS -1 + //#define Y2_INTERPOLATE true + //#define Y2_HOLD_MULTIPLIER 0.5 #endif - #if AXIS_IS_TMC(Z) + #if AXIS_IS_TMC_CONFIG(Z) #define Z_CURRENT 800 #define Z_CURRENT_HOME Z_CURRENT #define Z_MICROSTEPS 16 #define Z_RSENSE 0.11 #define Z_CHAIN_POS -1 + //#define Z_INTERPOLATE true + //#define Z_HOLD_MULTIPLIER 0.5 #endif - #if AXIS_IS_TMC(Z2) - #define Z2_CURRENT 800 - #define Z2_CURRENT_HOME Z2_CURRENT - #define Z2_MICROSTEPS 16 - #define Z2_RSENSE 0.11 + #if AXIS_IS_TMC_CONFIG(Z2) + #define Z2_CURRENT Z_CURRENT + #define Z2_CURRENT_HOME Z_CURRENT_HOME + #define Z2_MICROSTEPS Z_MICROSTEPS + #define Z2_RSENSE Z_RSENSE #define Z2_CHAIN_POS -1 + //#define Z2_INTERPOLATE true + //#define Z2_HOLD_MULTIPLIER 0.5 #endif - #if AXIS_IS_TMC(Z3) - #define Z3_CURRENT 800 - #define Z3_CURRENT_HOME Z3_CURRENT - #define Z3_MICROSTEPS 16 - #define Z3_RSENSE 0.11 + #if AXIS_IS_TMC_CONFIG(Z3) + #define Z3_CURRENT Z_CURRENT + #define Z3_CURRENT_HOME Z_CURRENT_HOME + #define Z3_MICROSTEPS Z_MICROSTEPS + #define Z3_RSENSE Z_RSENSE #define Z3_CHAIN_POS -1 + //#define Z3_INTERPOLATE true + //#define Z3_HOLD_MULTIPLIER 0.5 #endif - #if AXIS_IS_TMC(Z4) - #define Z4_CURRENT 800 - #define Z4_CURRENT_HOME Z4_CURRENT - #define Z4_MICROSTEPS 16 - #define Z4_RSENSE 0.11 + #if AXIS_IS_TMC_CONFIG(Z4) + #define Z4_CURRENT Z_CURRENT + #define Z4_CURRENT_HOME Z_CURRENT_HOME + #define Z4_MICROSTEPS Z_MICROSTEPS + #define Z4_RSENSE Z_RSENSE #define Z4_CHAIN_POS -1 + //#define Z4_INTERPOLATE true + //#define Z4_HOLD_MULTIPLIER 0.5 #endif - #if AXIS_IS_TMC(E0) + #if AXIS_IS_TMC_CONFIG(I) + #define I_CURRENT 800 + #define I_CURRENT_HOME I_CURRENT + #define I_MICROSTEPS 16 + #define I_RSENSE 0.11 + #define I_CHAIN_POS -1 + //#define I_INTERPOLATE true + //#define I_HOLD_MULTIPLIER 0.5 + #endif + + #if AXIS_IS_TMC_CONFIG(J) + #define J_CURRENT 800 + #define J_CURRENT_HOME J_CURRENT + #define J_MICROSTEPS 16 + #define J_RSENSE 0.11 + #define J_CHAIN_POS -1 + //#define J_INTERPOLATE true + //#define J_HOLD_MULTIPLIER 0.5 + #endif + + #if AXIS_IS_TMC_CONFIG(K) + #define K_CURRENT 800 + #define K_CURRENT_HOME K_CURRENT + #define K_MICROSTEPS 16 + #define K_RSENSE 0.11 + #define K_CHAIN_POS -1 + //#define K_INTERPOLATE true + //#define K_HOLD_MULTIPLIER 0.5 + #endif + + #if AXIS_IS_TMC_CONFIG(U) + #define U_CURRENT 800 + #define U_CURRENT_HOME U_CURRENT + #define U_MICROSTEPS 8 + #define U_RSENSE 0.11 + #define U_CHAIN_POS -1 + //#define U_INTERPOLATE true + //#define U_HOLD_MULTIPLIER 0.5 + #endif + + #if AXIS_IS_TMC_CONFIG(V) + #define V_CURRENT 800 + #define V_CURRENT_HOME V_CURRENT + #define V_MICROSTEPS 8 + #define V_RSENSE 0.11 + #define V_CHAIN_POS -1 + //#define V_INTERPOLATE true + //#define V_HOLD_MULTIPLIER 0.5 + #endif + + #if AXIS_IS_TMC_CONFIG(W) + #define W_CURRENT 800 + #define W_CURRENT_HOME W_CURRENT + #define W_MICROSTEPS 8 + #define W_RSENSE 0.11 + #define W_CHAIN_POS -1 + //#define W_INTERPOLATE true + //#define W_HOLD_MULTIPLIER 0.5 + #endif + + #if AXIS_IS_TMC_CONFIG(E0) #define E0_CURRENT 800 #define E0_MICROSTEPS 16 #define E0_RSENSE 0.11 #define E0_CHAIN_POS -1 + //#define E0_INTERPOLATE true + //#define E0_HOLD_MULTIPLIER 0.5 #endif - #if AXIS_IS_TMC(E1) - #define E1_CURRENT 800 - #define E1_MICROSTEPS 16 - #define E1_RSENSE 0.11 + #if AXIS_IS_TMC_CONFIG(E1) + #define E1_CURRENT E0_CURRENT + #define E1_MICROSTEPS E0_MICROSTEPS + #define E1_RSENSE E0_RSENSE #define E1_CHAIN_POS -1 + //#define E1_INTERPOLATE true + //#define E1_HOLD_MULTIPLIER 0.5 #endif - #if AXIS_IS_TMC(E2) - #define E2_CURRENT 800 - #define E2_MICROSTEPS 16 - #define E2_RSENSE 0.11 + #if AXIS_IS_TMC_CONFIG(E2) + #define E2_CURRENT E0_CURRENT + #define E2_MICROSTEPS E0_MICROSTEPS + #define E2_RSENSE E0_RSENSE #define E2_CHAIN_POS -1 + //#define E2_INTERPOLATE true + //#define E2_HOLD_MULTIPLIER 0.5 #endif - #if AXIS_IS_TMC(E3) - #define E3_CURRENT 800 - #define E3_MICROSTEPS 16 - #define E3_RSENSE 0.11 + #if AXIS_IS_TMC_CONFIG(E3) + #define E3_CURRENT E0_CURRENT + #define E3_MICROSTEPS E0_MICROSTEPS + #define E3_RSENSE E0_RSENSE #define E3_CHAIN_POS -1 + //#define E3_INTERPOLATE true + //#define E3_HOLD_MULTIPLIER 0.5 #endif - #if AXIS_IS_TMC(E4) - #define E4_CURRENT 800 - #define E4_MICROSTEPS 16 - #define E4_RSENSE 0.11 + #if AXIS_IS_TMC_CONFIG(E4) + #define E4_CURRENT E0_CURRENT + #define E4_MICROSTEPS E0_MICROSTEPS + #define E4_RSENSE E0_RSENSE #define E4_CHAIN_POS -1 + //#define E4_INTERPOLATE true + //#define E4_HOLD_MULTIPLIER 0.5 #endif - #if AXIS_IS_TMC(E5) - #define E5_CURRENT 800 - #define E5_MICROSTEPS 16 - #define E5_RSENSE 0.11 + #if AXIS_IS_TMC_CONFIG(E5) + #define E5_CURRENT E0_CURRENT + #define E5_MICROSTEPS E0_MICROSTEPS + #define E5_RSENSE E0_RSENSE #define E5_CHAIN_POS -1 + //#define E5_INTERPOLATE true + //#define E5_HOLD_MULTIPLIER 0.5 #endif - #if AXIS_IS_TMC(E6) - #define E6_CURRENT 800 - #define E6_MICROSTEPS 16 - #define E6_RSENSE 0.11 + #if AXIS_IS_TMC_CONFIG(E6) + #define E6_CURRENT E0_CURRENT + #define E6_MICROSTEPS E0_MICROSTEPS + #define E6_RSENSE E0_RSENSE #define E6_CHAIN_POS -1 + //#define E6_INTERPOLATE true + //#define E6_HOLD_MULTIPLIER 0.5 #endif - #if AXIS_IS_TMC(E7) - #define E7_CURRENT 800 - #define E7_MICROSTEPS 16 - #define E7_RSENSE 0.11 + #if AXIS_IS_TMC_CONFIG(E7) + #define E7_CURRENT E0_CURRENT + #define E7_MICROSTEPS E0_MICROSTEPS + #define E7_RSENSE E0_RSENSE #define E7_CHAIN_POS -1 + //#define E7_INTERPOLATE true + //#define E7_HOLD_MULTIPLIER 0.5 #endif /** - * Override default SPI pins for TMC2130, TMC2160, TMC2660, TMC5130 and TMC5160 drivers here. + * 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, TMC2240, TMC2660, TMC5130 and TMC5160 drivers here. * The default pins can be found in your board's pins file. */ - //#define X_CS_PIN -1 - //#define Y_CS_PIN -1 - //#define Z_CS_PIN -1 - //#define X2_CS_PIN -1 - //#define Y2_CS_PIN -1 - //#define Z2_CS_PIN -1 - //#define Z3_CS_PIN -1 - //#define E0_CS_PIN -1 - //#define E1_CS_PIN -1 - //#define E2_CS_PIN -1 - //#define E3_CS_PIN -1 - //#define E4_CS_PIN -1 - //#define E5_CS_PIN -1 - //#define E6_CS_PIN -1 - //#define E7_CS_PIN -1 + //#define X_CS_PIN -1 + //#define Y_CS_PIN -1 + //#define Z_CS_PIN -1 + //#define X2_CS_PIN -1 + //#define Y2_CS_PIN -1 + //#define Z2_CS_PIN -1 + //#define Z3_CS_PIN -1 + //#define Z4_CS_PIN -1 + //#define I_CS_PIN -1 + //#define J_CS_PIN -1 + //#define K_CS_PIN -1 + //#define U_CS_PIN -1 + //#define V_CS_PIN -1 + //#define W_CS_PIN -1 + //#define E0_CS_PIN -1 + //#define E1_CS_PIN -1 + //#define E2_CS_PIN -1 + //#define E3_CS_PIN -1 + //#define E4_CS_PIN -1 + //#define E5_CS_PIN -1 + //#define E6_CS_PIN -1 + //#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. */ //#define TMC_USE_SW_SPI - //#define TMC_SW_MOSI -1 - //#define TMC_SW_MISO -1 - //#define TMC_SW_SCK -1 + //#define TMC_SPI_MOSI -1 + //#define TMC_SPI_MISO -1 + //#define TMC_SPI_SCK -1 + + // @section tmc/serial /** * Four TMC2209 drivers can use the same HW/SW serial port with hardware configured addresses. @@ -2425,22 +3386,30 @@ * Set *_SERIAL_TX_PIN and *_SERIAL_RX_PIN to match for all drivers * on the same serial port, either here or in your board's pins file. */ - #define X_SLAVE_ADDRESS 0 - #define Y_SLAVE_ADDRESS 0 - #define Z_SLAVE_ADDRESS 0 - #define X2_SLAVE_ADDRESS 0 - #define Y2_SLAVE_ADDRESS 0 - #define Z2_SLAVE_ADDRESS 0 - #define Z3_SLAVE_ADDRESS 0 - #define Z4_SLAVE_ADDRESS 0 - #define E0_SLAVE_ADDRESS 0 - #define E1_SLAVE_ADDRESS 0 - #define E2_SLAVE_ADDRESS 0 - #define E3_SLAVE_ADDRESS 0 - #define E4_SLAVE_ADDRESS 0 - #define E5_SLAVE_ADDRESS 0 - #define E6_SLAVE_ADDRESS 0 - #define E7_SLAVE_ADDRESS 0 + //#define X_SLAVE_ADDRESS 0 + //#define Y_SLAVE_ADDRESS 0 + //#define Z_SLAVE_ADDRESS 0 + //#define X2_SLAVE_ADDRESS 0 + //#define Y2_SLAVE_ADDRESS 0 + //#define Z2_SLAVE_ADDRESS 0 + //#define Z3_SLAVE_ADDRESS 0 + //#define Z4_SLAVE_ADDRESS 0 + //#define I_SLAVE_ADDRESS 0 + //#define J_SLAVE_ADDRESS 0 + //#define K_SLAVE_ADDRESS 0 + //#define U_SLAVE_ADDRESS 0 + //#define V_SLAVE_ADDRESS 0 + //#define W_SLAVE_ADDRESS 0 + //#define E0_SLAVE_ADDRESS 0 + //#define E1_SLAVE_ADDRESS 0 + //#define E2_SLAVE_ADDRESS 0 + //#define E3_SLAVE_ADDRESS 0 + //#define E4_SLAVE_ADDRESS 0 + //#define E5_SLAVE_ADDRESS 0 + //#define E6_SLAVE_ADDRESS 0 + //#define E7_SLAVE_ADDRESS 0 + + // @section tmc/smart /** * Software enable @@ -2450,14 +3419,24 @@ */ //#define SOFTWARE_DRIVER_ENABLE + // @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. */ - #define STEALTHCHOP_XY - #define STEALTHCHOP_Z - #define STEALTHCHOP_E + #if HAS_STEALTHCHOP + #define STEALTHCHOP_XY + #define STEALTHCHOP_Z + #define STEALTHCHOP_I + #define STEALTHCHOP_J + #define STEALTHCHOP_K + #define STEALTHCHOP_U + #define STEALTHCHOP_V + #define STEALTHCHOP_W + #define STEALTHCHOP_E + #endif /** * Optimize spreadCycle chopper parameters by using predefined parameter sets @@ -2471,10 +3450,34 @@ * CHOPPER_PRUSAMK3_24V // Imported parameters from the official PrΕ―Ε‘a firmware for MK3 (24V) * CHOPPER_MARLIN_119 // Old defaults from Marlin v1.1.9 * - * Define you own with + * Define your own with: * { , , hysteresis_start[1..8] } */ - #define CHOPPER_TIMING CHOPPER_DEFAULT_12V + #define CHOPPER_TIMING CHOPPER_DEFAULT_12V // All axes (override below) + //#define CHOPPER_TIMING_X CHOPPER_TIMING // For X Axes (override below) + //#define CHOPPER_TIMING_X2 CHOPPER_TIMING_X + //#define CHOPPER_TIMING_Y CHOPPER_TIMING // For Y Axes (override below) + //#define CHOPPER_TIMING_Y2 CHOPPER_TIMING_Y + //#define CHOPPER_TIMING_Z CHOPPER_TIMING // For Z Axes (override below) + //#define CHOPPER_TIMING_Z2 CHOPPER_TIMING_Z + //#define CHOPPER_TIMING_Z3 CHOPPER_TIMING_Z + //#define CHOPPER_TIMING_Z4 CHOPPER_TIMING_Z + //#define CHOPPER_TIMING_I CHOPPER_TIMING // For I Axis + //#define CHOPPER_TIMING_J CHOPPER_TIMING // For J Axis + //#define CHOPPER_TIMING_K CHOPPER_TIMING // For K Axis + //#define CHOPPER_TIMING_U CHOPPER_TIMING // For U Axis + //#define CHOPPER_TIMING_V CHOPPER_TIMING // For V Axis + //#define CHOPPER_TIMING_W CHOPPER_TIMING // For W Axis + //#define CHOPPER_TIMING_E CHOPPER_TIMING // For Extruders (override below) + //#define CHOPPER_TIMING_E1 CHOPPER_TIMING_E + //#define CHOPPER_TIMING_E2 CHOPPER_TIMING_E + //#define CHOPPER_TIMING_E3 CHOPPER_TIMING_E + //#define CHOPPER_TIMING_E4 CHOPPER_TIMING_E + //#define CHOPPER_TIMING_E5 CHOPPER_TIMING_E + //#define CHOPPER_TIMING_E6 CHOPPER_TIMING_E + //#define CHOPPER_TIMING_E7 CHOPPER_TIMING_E + + // @section tmc/status /** * Monitor Trinamic drivers @@ -2495,8 +3498,10 @@ #define STOP_ON_ERROR #endif + // @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. @@ -2512,6 +3517,12 @@ #define Z2_HYBRID_THRESHOLD 3 #define Z3_HYBRID_THRESHOLD 3 #define Z4_HYBRID_THRESHOLD 3 + #define I_HYBRID_THRESHOLD 3 // [linear=mm/s, rotational=Β°/s] + #define J_HYBRID_THRESHOLD 3 // [linear=mm/s, rotational=Β°/s] + #define K_HYBRID_THRESHOLD 3 // [linear=mm/s, rotational=Β°/s] + #define U_HYBRID_THRESHOLD 3 // [mm/s] + #define V_HYBRID_THRESHOLD 3 + #define W_HYBRID_THRESHOLD 3 #define E0_HYBRID_THRESHOLD 30 #define E1_HYBRID_THRESHOLD 30 #define E2_HYBRID_THRESHOLD 30 @@ -2524,20 +3535,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 *** Beta feature! *** TMC2130 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. * @@ -2545,10 +3556,11 @@ * homing and adds a guard period for endstop triggering. * * Comment *_STALL_SENSITIVITY to disable sensorless homing for that axis. + * @section tmc/stallguard */ //#define SENSORLESS_HOMING // StallGuard capable drivers only - #if EITHER(SENSORLESS_HOMING, SENSORLESS_PROBING) + #if ANY(SENSORLESS_HOMING, SENSORLESS_PROBING) // TMC2209: 0...255. TMC2130: -64...63 #define X_STALL_SENSITIVITY 8 #define X2_STALL_SENSITIVITY X_STALL_SENSITIVITY @@ -2558,10 +3570,19 @@ //#define Z2_STALL_SENSITIVITY Z_STALL_SENSITIVITY //#define Z3_STALL_SENSITIVITY Z_STALL_SENSITIVITY //#define Z4_STALL_SENSITIVITY Z_STALL_SENSITIVITY - //#define SPI_ENDSTOPS // TMC2130 only + //#define I_STALL_SENSITIVITY 8 + //#define J_STALL_SENSITIVITY 8 + //#define K_STALL_SENSITIVITY 8 + //#define U_STALL_SENSITIVITY 8 + //#define V_STALL_SENSITIVITY 8 + //#define W_STALL_SENSITIVITY 8 + //#define SPI_ENDSTOPS // TMC2130, TMC2240, and TMC5160 //#define IMPROVE_HOMING_RELIABILITY + //#define SENSORLESS_STALLGUARD_DELAY 0 // (ms) Delay to allow drivers to settle #endif + // @section tmc/config + /** * TMC Homing stepper phase. * @@ -2575,14 +3596,13 @@ //#define TMC_HOME_PHASE { 896, 896, 896 } /** - * Beta feature! - * Create a 50/50 square wave step pulse optimal for stepper drivers. + * Step on both rising and falling edge signals (as with a square wave). */ - //#define SQUARE_WAVE_STEPPING + #define EDGE_STEPPING /** * Enable M122 debugging command for TMC stepper drivers. - * M122 S0/1 will enable continous reporting. + * M122 S0/1 will enable continuous reporting. */ //#define TMC_DEBUG @@ -2601,200 +3621,6 @@ #endif // HAS_TRINAMIC_CONFIG -// @section L64XX - -/** - * L64XX Stepper Driver options - * - * Arduino-L6470 library (0.8.0 or higher) is required. - * https://github.com/ameyer/Arduino-L6470 - * - * Requires the following to be defined in your pins_YOUR_BOARD file - * L6470_CHAIN_SCK_PIN - * L6470_CHAIN_MISO_PIN - * L6470_CHAIN_MOSI_PIN - * L6470_CHAIN_SS_PIN - * ENABLE_RESET_L64XX_CHIPS(Q) where Q is 1 to enable and 0 to reset - */ - -#if HAS_L64XX - - //#define L6470_CHITCHAT // Display additional status info - - #if AXIS_IS_L64XX(X) - #define X_MICROSTEPS 128 // Number of microsteps (VALID: 1, 2, 4, 8, 16, 32, 128) - L6474 max is 16 - #define X_OVERCURRENT 2000 // (mA) Current where the driver detects an over current - // L6470 & L6474 - VALID: 375 x (1 - 16) - 6A max - rounds down - // POWERSTEP01: VALID: 1000 x (1 - 32) - 32A max - rounds down - #define X_STALLCURRENT 1500 // (mA) Current where the driver detects a stall (VALID: 31.25 * (1-128) - 4A max - rounds down) - // L6470 & L6474 - VALID: 31.25 * (1-128) - 4A max - rounds down - // POWERSTEP01: VALID: 200 x (1 - 32) - 6.4A max - rounds down - // L6474 - STALLCURRENT setting is used to set the nominal (TVAL) current - #define X_MAX_VOLTAGE 127 // 0-255, Maximum effective voltage seen by stepper - not used by L6474 - #define X_CHAIN_POS -1 // Position in SPI chain, 0=Not in chain, 1=Nearest MOSI - #define X_SLEW_RATE 1 // 0-3, Slew 0 is slowest, 3 is fastest - #endif - - #if AXIS_IS_L64XX(X2) - #define X2_MICROSTEPS 128 - #define X2_OVERCURRENT 2000 - #define X2_STALLCURRENT 1500 - #define X2_MAX_VOLTAGE 127 - #define X2_CHAIN_POS -1 - #define X2_SLEW_RATE 1 - #endif - - #if AXIS_IS_L64XX(Y) - #define Y_MICROSTEPS 128 - #define Y_OVERCURRENT 2000 - #define Y_STALLCURRENT 1500 - #define Y_MAX_VOLTAGE 127 - #define Y_CHAIN_POS -1 - #define Y_SLEW_RATE 1 - #endif - - #if AXIS_IS_L64XX(Y2) - #define Y2_MICROSTEPS 128 - #define Y2_OVERCURRENT 2000 - #define Y2_STALLCURRENT 1500 - #define Y2_MAX_VOLTAGE 127 - #define Y2_CHAIN_POS -1 - #define Y2_SLEW_RATE 1 - #endif - - #if AXIS_IS_L64XX(Z) - #define Z_MICROSTEPS 128 - #define Z_OVERCURRENT 2000 - #define Z_STALLCURRENT 1500 - #define Z_MAX_VOLTAGE 127 - #define Z_CHAIN_POS -1 - #define Z_SLEW_RATE 1 - #endif - - #if AXIS_IS_L64XX(Z2) - #define Z2_MICROSTEPS 128 - #define Z2_OVERCURRENT 2000 - #define Z2_STALLCURRENT 1500 - #define Z2_MAX_VOLTAGE 127 - #define Z2_CHAIN_POS -1 - #define Z2_SLEW_RATE 1 - #endif - - #if AXIS_IS_L64XX(Z3) - #define Z3_MICROSTEPS 128 - #define Z3_OVERCURRENT 2000 - #define Z3_STALLCURRENT 1500 - #define Z3_MAX_VOLTAGE 127 - #define Z3_CHAIN_POS -1 - #define Z3_SLEW_RATE 1 - #endif - - #if AXIS_IS_L64XX(Z4) - #define Z4_MICROSTEPS 128 - #define Z4_OVERCURRENT 2000 - #define Z4_STALLCURRENT 1500 - #define Z4_MAX_VOLTAGE 127 - #define Z4_CHAIN_POS -1 - #define Z4_SLEW_RATE 1 - #endif - - #if AXIS_IS_L64XX(E0) - #define E0_MICROSTEPS 128 - #define E0_OVERCURRENT 2000 - #define E0_STALLCURRENT 1500 - #define E0_MAX_VOLTAGE 127 - #define E0_CHAIN_POS -1 - #define E0_SLEW_RATE 1 - #endif - - #if AXIS_IS_L64XX(E1) - #define E1_MICROSTEPS 128 - #define E1_OVERCURRENT 2000 - #define E1_STALLCURRENT 1500 - #define E1_MAX_VOLTAGE 127 - #define E1_CHAIN_POS -1 - #define E1_SLEW_RATE 1 - #endif - - #if AXIS_IS_L64XX(E2) - #define E2_MICROSTEPS 128 - #define E2_OVERCURRENT 2000 - #define E2_STALLCURRENT 1500 - #define E2_MAX_VOLTAGE 127 - #define E2_CHAIN_POS -1 - #define E2_SLEW_RATE 1 - #endif - - #if AXIS_IS_L64XX(E3) - #define E3_MICROSTEPS 128 - #define E3_OVERCURRENT 2000 - #define E3_STALLCURRENT 1500 - #define E3_MAX_VOLTAGE 127 - #define E3_CHAIN_POS -1 - #define E3_SLEW_RATE 1 - #endif - - #if AXIS_IS_L64XX(E4) - #define E4_MICROSTEPS 128 - #define E4_OVERCURRENT 2000 - #define E4_STALLCURRENT 1500 - #define E4_MAX_VOLTAGE 127 - #define E4_CHAIN_POS -1 - #define E4_SLEW_RATE 1 - #endif - - #if AXIS_IS_L64XX(E5) - #define E5_MICROSTEPS 128 - #define E5_OVERCURRENT 2000 - #define E5_STALLCURRENT 1500 - #define E5_MAX_VOLTAGE 127 - #define E5_CHAIN_POS -1 - #define E5_SLEW_RATE 1 - #endif - - #if AXIS_IS_L64XX(E6) - #define E6_MICROSTEPS 128 - #define E6_OVERCURRENT 2000 - #define E6_STALLCURRENT 1500 - #define E6_MAX_VOLTAGE 127 - #define E6_CHAIN_POS -1 - #define E6_SLEW_RATE 1 - #endif - - #if AXIS_IS_L64XX(E7) - #define E7_MICROSTEPS 128 - #define E7_OVERCURRENT 2000 - #define E7_STALLCURRENT 1500 - #define E7_MAX_VOLTAGE 127 - #define E7_CHAIN_POS -1 - #define E7_SLEW_RATE 1 - #endif - - /** - * Monitor L6470 drivers for error conditions like over temperature and over current. - * In the case of over temperature Marlin can decrease the drive until the error condition clears. - * Other detected conditions can be used to stop the current print. - * Relevant G-codes: - * M906 - I1/2/3/4/5 Set or get motor drive level using axis codes X, Y, Z, E. Report values if no axis codes given. - * I not present or I0 or I1 - X, Y, Z or E0 - * I2 - X2, Y2, Z2 or E1 - * I3 - Z3 or E3 - * I4 - Z4 or E4 - * I5 - E5 - * M916 - Increase drive level until get thermal warning - * M917 - Find minimum current thresholds - * M918 - Increase speed until max or error - * M122 S0/1 - Report driver parameters - */ - //#define MONITOR_L6470_DRIVER_STATUS - - #if ENABLED(MONITOR_L6470_DRIVER_STATUS) - #define KVAL_HOLD_STEP_DOWN 1 - //#define L6470_STOP_ON_ERROR - #endif - -#endif // HAS_L64XX - // @section i2cbus // @@ -2806,9 +3632,8 @@ /** * TWI/I2C BUS * - * This feature is an EXPERIMENTAL feature so it shall not be used on production - * machines. Enabling this will allow you to send and receive I2C data from slave - * devices on the bus. + * This feature is EXPERIMENTAL but may be useful for custom I2C peripherals. + * Enable this to send and receive I2C data from slave devices on the bus. * * ; Example #1 * ; This macro send the string "Marlin" to the slave device with address 0x63 (99) @@ -2836,7 +3661,7 @@ #define I2C_SLAVE_ADDRESS 0 // Set a value from 8 to 127 to act as a slave #endif -// @section extras +// @section photo /** * Photo G-code @@ -2851,11 +3676,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://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 @@ -2879,35 +3704,61 @@ #endif #endif +// @section cnc + /** * Spindle & Laser control * * 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. * * You'll need to select a pin for the ON/OFF function and optionally choose a 0-5V * hardware PWM pin for the speed control and a pin for the rotation direction. * - * See https://marlinfw.org/docs/configuration/laser_spindle.html for more config details. + * See https://marlinfw.org/docs/configuration/2.0.9/laser_spindle.html for more config details. */ //#define SPINDLE_FEATURE //#define LASER_FEATURE -#if EITHER(SPINDLE_FEATURE, LASER_FEATURE) - #define SPINDLE_LASER_ACTIVE_STATE LOW // Set to "HIGH" if the on/off function is active HIGH - #define SPINDLE_LASER_PWM true // Set to "true" if your controller supports setting the speed/power - #define SPINDLE_LASER_PWM_INVERT false // Set to "true" if the speed/power goes up when you want it to go slower +#if ANY(SPINDLE_FEATURE, LASER_FEATURE) + #define SPINDLE_LASER_ACTIVE_STATE LOW // Set to "HIGH" if SPINDLE_LASER_ENA_PIN is active HIGH - #define SPINDLE_LASER_FREQUENCY 2500 // (Hz) Spindle/laser frequency (only on supported HALs: AVR and LPC) + #define SPINDLE_LASER_USE_PWM // Enable if your controller supports setting the speed/power + #if ENABLED(SPINDLE_LASER_USE_PWM) + #define SPINDLE_LASER_PWM_INVERT false // Set to "true" if the speed/power goes up when you want it to go slower + #define SPINDLE_LASER_FREQUENCY 2500 // (Hz) Spindle/laser frequency (only on supported HALs: AVR, ESP32, and LPC) + // ESP32: If SPINDLE_LASER_PWM_PIN is onboard then <=78125Hz. For I2S expander + // the frequency determines the PWM resolution. 2500Hz = 0-100, 977Hz = 0-255, ... + // (250000 / SPINDLE_LASER_FREQUENCY) = max value. + #endif + + //#define AIR_EVACUATION // Cutter Vacuum / Laser Blower motor control with G-codes M10-M11 + #if ENABLED(AIR_EVACUATION) + #define AIR_EVACUATION_ACTIVE LOW // Set to "HIGH" if the on/off function is active HIGH + //#define AIR_EVACUATION_PIN 42 // Override the default Cutter Vacuum or Laser Blower pin + #endif + + //#define AIR_ASSIST // Air Assist control with G-codes M8-M9 + #if ENABLED(AIR_ASSIST) + #define AIR_ASSIST_ACTIVE LOW // Active state on air assist pin + //#define AIR_ASSIST_PIN 44 // Override the default Air Assist pin + #endif + + //#define SPINDLE_SERVO // A servo converting an angle to spindle power + #ifdef SPINDLE_SERVO + #define SPINDLE_SERVO_NR 0 // Index of servo used for spindle control + #define SPINDLE_SERVO_MIN 10 // Minimum angle for servo spindle + #endif /** * Speed / Power can be set ('M3 S') and displayed in terms of: * - PWM255 (S0 - S255) * - PERCENT (S0 - S100) * - RPM (S0 - S50000) Best for use with a spindle + * - SERVO (S0 - S180) */ #define CUTTER_POWER_UNIT PWM255 @@ -2938,94 +3789,112 @@ * Speed/Power = (PWMDC / 255 * 100 - SPEED_POWER_INTERCEPT) / SPEED_POWER_SLOPE * PWMDC = (spdpwr - SPEED_POWER_MIN) / (SPEED_POWER_MAX - SPEED_POWER_MIN) / SPEED_POWER_SLOPE */ - #define SPEED_POWER_INTERCEPT 0 // (%) 0-100 i.e., Minimum power percentage - #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) + #if ENABLED(SPINDLE_LASER_USE_PWM) + #define SPEED_POWER_INTERCEPT 0 // (%) 0-100 i.e., Minimum power percentage + #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 - #define SPEED_POWER_INTERCEPT 0 // (%) 0-100 i.e., Minimum power percentage - #define SPEED_POWER_MIN 0 // (%) 0-100 - #define SPEED_POWER_MAX 100 // (%) 0-100 - #define SPEED_POWER_STARTUP 80 // (%) M3/M4 speed/power default (with no arguments) + #if ENABLED(SPINDLE_LASER_USE_PWM) + #define SPEED_POWER_INTERCEPT 0 // (%) 0-100 i.e., Minimum power percentage + #define SPEED_POWER_MIN 0 // (%) 0-100 + #define SPEED_POWER_MAX 100 // (%) 0-100 + #define SPEED_POWER_STARTUP 80 // (%) M3/M4 speed/power default (with no arguments) + #endif + + // Define the minimum and maximum test pulse time values for a laser test fire function + #define LASER_TEST_PULSE_MIN 1 // (ms) Used with Laser Control Menu + #define LASER_TEST_PULSE_MAX 999 // (ms) Caution: Menu may not show more than 3 characters + + #define SPINDLE_LASER_POWERUP_DELAY 50 // (ms) Delay to allow the spindle/laser to come up to speed/power + #define SPINDLE_LASER_POWERDOWN_DELAY 50 // (ms) Delay to allow the spindle to stop + + /** + * Laser Safety Timeout + * + * The laser should be turned off when there is no movement for a period of time. + * Consider material flammability, cut rate, and G-code order when setting this + * value. Too low and it could turn off during a very slow move; too high and + * the material could ignite. + */ + #define LASER_SAFETY_TIMEOUT_MS 1000 // (ms) /** - * Enable inline laser power to be handled in the planner / stepper routines. - * Inline power is specified by the I (inline) flag in an M3 command (e.g., M3 S20 I) - * or by the 'S' parameter in G0/G1/G2/G3 moves (see LASER_MOVE_POWER). + * Any M3 or G1/2/3/5 command with the 'I' parameter enables continuous inline power mode. * - * This allows the laser to keep in perfect sync with the planner and removes - * the powerup/down delay since lasers require negligible time. + * e.g., 'M3 I' enables continuous inline power which is processed by the planner. + * Power is stored in move blocks and applied when blocks are processed by the Stepper ISR. + * + * 'M4 I' sets dynamic mode which uses the current feedrate to calculate a laser power OCR value. + * + * Any move in dynamic mode will use the current feedrate to calculate the laser power. + * Feed rates are set by the F parameter of a move command e.g. G1 X0 Y10 F6000 + * Laser power would be calculated by bit shifting off 8 LSB's. In binary this is div 256. + * The calculation gives us ocr values from 0 to 255, values over F65535 will be set as 255 . + * More refined power control such as compensation for accel/decel will be addressed in future releases. + * + * M5 I clears inline mode and set power to 0, M5 sets the power output to 0 but leaves inline mode on. */ - #define LASER_POWER_INLINE - #if ENABLED(LASER_POWER_INLINE) - /** - * Scale the laser's power in proportion to the movement rate. - * - * - Sets the entry power proportional to the entry speed over the nominal speed. - * - Ramps the power up every N steps to approximate the speed trapezoid. - * - Due to the limited power resolution this is only approximate. - */ - #define LASER_POWER_INLINE_TRAPEZOID + /** + * Enable M3 commands for laser mode inline power planner syncing. + * This feature enables any M3 S-value to be injected into the block buffers while in + * CUTTER_MODE_CONTINUOUS. The option allows M3 laser power to be committed without waiting + * for a planner synchronization + */ + //#define LASER_POWER_SYNC - /** - * Continuously calculate the current power (nominal_power * current_rate / nominal_rate). - * Required for accurate power with non-trapezoidal acceleration (e.g., S_CURVE_ACCELERATION). - * This is a costly calculation so this option is discouraged on 8-bit AVR boards. - * - * LASER_POWER_INLINE_TRAPEZOID_CONT_PER defines how many step cycles there are between power updates. If your - * board isn't able to generate steps fast enough (and you are using LASER_POWER_INLINE_TRAPEZOID_CONT), increase this. - * Note that when this is zero it means it occurs every cycle; 1 means a delay wait one cycle then run, etc. - */ - //#define LASER_POWER_INLINE_TRAPEZOID_CONT - - /** - * Stepper iterations between power updates. Increase this value if the board - * can't keep up with the processing demands of LASER_POWER_INLINE_TRAPEZOID_CONT. - * Disable (or set to 0) to recalculate power on every stepper iteration. - */ - //#define LASER_POWER_INLINE_TRAPEZOID_CONT_PER 10 - - /** - * Include laser power in G0/G1/G2/G3/G5 commands with the 'S' parameter - */ - //#define LASER_MOVE_POWER - - #if ENABLED(LASER_MOVE_POWER) - // Turn off the laser on G0 moves with no power parameter. - // If a power parameter is provided, use that instead. - //#define LASER_MOVE_G0_OFF - - // Turn off the laser on G28 homing. - //#define LASER_MOVE_G28_OFF - #endif - - /** - * Inline flag inverted - * - * WARNING: M5 will NOT turn off the laser unless another move - * is done (so G-code files must end with 'M5 I'). - */ - //#define LASER_POWER_INLINE_INVERT - - /** - * Continuously apply inline power. ('M3 S3' == 'G1 S3' == 'M3 S3 I') - * - * The laser might do some weird things, so only enable this - * feature if you understand the implications. - */ - //#define LASER_POWER_INLINE_CONTINUOUS - - #else - - #define SPINDLE_LASER_POWERUP_DELAY 50 // (ms) Delay to allow the spindle/laser to come up to speed/power - #define SPINDLE_LASER_POWERDOWN_DELAY 50 // (ms) Delay to allow the spindle to stop + /** + * Scale the laser's power in proportion to the movement rate. + * + * - Sets the entry power proportional to the entry speed over the nominal speed. + * - Ramps the power up every N steps to approximate the speed trapezoid. + * - Due to the limited power resolution this is only approximate. + */ + //#define LASER_POWER_TRAP + // + // Laser I2C Ammeter (High precision INA226 low/high side module) + // + //#define I2C_AMMETER + #if ENABLED(I2C_AMMETER) + #define I2C_AMMETER_IMAX 0.1 // (Amps) Calibration value for the expected current range + #define I2C_AMMETER_SHUNT_RESISTOR 0.1 // (Ohms) Calibration shunt resistor value #endif + + // + // Laser Coolant Flow Meter + // + //#define LASER_COOLANT_FLOW_METER + #if ENABLED(LASER_COOLANT_FLOW_METER) + #define FLOWMETER_PIN 20 // Requires an external interrupt-enabled pin (e.g., RAMPS 2,3,18,19,20,21) + #define FLOWMETER_PPL 5880 // (pulses/liter) Flow meter pulses-per-liter on the input pin + #define FLOWMETER_INTERVAL 1000 // (ms) Flow rate calculation interval in milliseconds + #define FLOWMETER_SAFETY // Prevent running the laser without the minimum flow rate set below + #if ENABLED(FLOWMETER_SAFETY) + #define FLOWMETER_MIN_LITERS_PER_MINUTE 1.5 // (liters/min) Minimum flow required when enabled + #endif + #endif + #endif -#endif +#endif // SPINDLE_FEATURE || LASER_FEATURE + +/** + * Synchronous Laser Control with M106/M107 + * + * Marlin normally applies M106/M107 fan speeds at a time "soon after" processing + * a planner block. This is too inaccurate for a PWM/TTL laser attached to the fan + * header (as with some add-on laser kits). Enable this option to set fan/laser + * speeds with much more exact timing for improved print fidelity. + * + * NOTE: This option sacrifices some cooling fan speed options. + */ +//#define LASER_SYNCHRONOUS_M106_M107 /** * Coolant Control @@ -3042,6 +3911,8 @@ #define COOLANT_FLOOD_INVERT false // Set "true" if the on/off function is reversed #endif +// @section filament width + /** * Filament Width Sensor * @@ -3075,6 +3946,8 @@ //#define FILAMENT_LCD_DISPLAY #endif +// @section power + /** * Power Monitor * Monitor voltage (V) and/or current (A), and -when possible- power (W) @@ -3086,13 +3959,31 @@ */ //#define POWER_MONITOR_CURRENT // Monitor the system current //#define POWER_MONITOR_VOLTAGE // Monitor the system voltage -#if EITHER(POWER_MONITOR_CURRENT, POWER_MONITOR_VOLTAGE) - #define POWER_MONITOR_VOLTS_PER_AMP 0.05000 // Input voltage to the MCU analog pin per amp - DO NOT apply more than ADC_VREF! - #define POWER_MONITOR_CURRENT_OFFSET -1 // Offset value for current sensors with linear function output - #define POWER_MONITOR_VOLTS_PER_VOLT 0.11786 // Input voltage to the MCU analog pin per volt - DO NOT apply more than ADC_VREF! + +#if ENABLED(POWER_MONITOR_CURRENT) + #define POWER_MONITOR_VOLTS_PER_AMP 0.05000 // Input voltage to the MCU analog pin per amp - DO NOT apply more than ADC_VREF! + #define POWER_MONITOR_CURRENT_OFFSET 0 // Offset (in amps) applied to the calculated current #define POWER_MONITOR_FIXED_VOLTAGE 13.6 // Voltage for a current sensor with no voltage sensor (for power display) #endif +#if ENABLED(POWER_MONITOR_VOLTAGE) + #define POWER_MONITOR_VOLTS_PER_VOLT 0.077933 // Input voltage to the MCU analog pin per volt - DO NOT apply more than ADC_VREF! + #define POWER_MONITOR_VOLTAGE_OFFSET 0 // Offset (in volts) applied to the calculated voltage +#endif + +// @section safety + +/** + * Stepper Driver Anti-SNAFU Protection + * + * If the SAFE_POWER_PIN is defined for your board, Marlin will check + * that stepper drivers are properly plugged in before applying power. + * Disable protection if your stepper drivers don't support the feature. + */ +//#define DISABLE_DRIVER_SAFE_POWER_PROTECT + +// @section cnc + /** * CNC Coordinate Systems * @@ -3101,18 +3992,7 @@ */ //#define CNC_COORDINATE_SYSTEMS -/** - * Auto-report temperatures with M155 S - */ -#define AUTO_REPORT_TEMPERATURES - -/** - * Include capabilities in M115 output - */ -#define EXTENDED_CAPABILITIES_REPORT -#if ENABLED(EXTENDED_CAPABILITIES_REPORT) - //#define M115_GEOMETRY_REPORT -#endif +// @section security /** * Expected Printer Check @@ -3121,6 +4001,8 @@ */ //#define EXPECTED_PRINTER_CHECK +// @section volumetrics + /** * Disable all Volumetric extrusion options */ @@ -3145,48 +4027,95 @@ * Use 'M200 [T] L' to override and 'M502' to reset. * A non-zero value activates Volume-based Extrusion Limiting. */ - #define DEFAULT_VOLUMETRIC_EXTRUDER_LIMIT 0.00 // (mm^3/sec) + #define DEFAULT_VOLUMETRIC_EXTRUDER_LIMIT 0.00 // (mm^3/sec) + #define VOLUMETRIC_EXTRUDER_LIMIT_MAX 20 // (mm^3/sec) #endif #endif -/** - * Enable this option for a leaner build of Marlin that removes all - * workspace offsets, simplifying coordinate transformations, leveling, etc. - * - * - M206 and M428 are disabled. - * - G92 will revert to its behavior from Marlin 1.0. - */ -//#define NO_WORKSPACE_OFFSETS +// @section reporting -// Extra options for the M114 "Current Position" report -//#define M114_DETAIL // Use 'M114` for details to check planner calculations +/** + * Extra options for the M114 "Current Position" report + */ +//#define M114_DETAIL // Use 'M114 D' for details to check planner calculations //#define M114_REALTIME // Real current position based on forward kinematics //#define M114_LEGACY // M114 used to synchronize on every call. Enable if needed. +/** + * Auto-report fan speed with M123 S + * Requires fans with tachometer pins + */ +//#define AUTO_REPORT_FANS + //#define REPORT_FAN_CHANGE // Report the new fan speed when changed by M106 (and others) /** - * 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. - * - * For clients that use a fixed-width font (like OctoPrint), leave this set to 1.0. - * Otherwise, adjust according to your client and font. + * Auto-report temperatures with M155 S */ -#define PROPORTIONAL_FONT_RATIO 1.0 +#define AUTO_REPORT_TEMPERATURES +#if ENABLED(AUTO_REPORT_TEMPERATURES) && TEMP_SENSOR_REDUNDANT + //#define AUTO_REPORT_REDUNDANT // Include the "R" sensor in the auto-report +#endif /** - * Spend 28 bytes of SRAM to optimize the GCode parser + * Auto-report position with M154 S + */ +//#define AUTO_REPORT_POSITION +#if ENABLED(AUTO_REPORT_POSITION) + //#define AUTO_REPORT_REAL_POSITION // Auto-report the real position +#endif + +/** + * M115 - Report capabilities. Disable to save ~1150 bytes of flash. + * Some hosts (and serial TFT displays) rely on this feature. + */ +#define CAPABILITIES_REPORT +#if ENABLED(CAPABILITIES_REPORT) + // Include capabilities in M115 output + #define EXTENDED_CAPABILITIES_REPORT + #if ENABLED(EXTENDED_CAPABILITIES_REPORT) + //#define M115_GEOMETRY_REPORT + #endif +#endif + +// @section gcode + +/** + * 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 +/** + * Support for MeatPack G-code compression (https://github.com/scottmudge/OctoPrint-MeatPack) + */ +//#define MEATPACK_ON_SERIAL_PORT_1 +//#define MEATPACK_ON_SERIAL_PORT_2 + //#define GCODE_CASE_INSENSITIVE // Accept G-code sent to the firmware in lowercase //#define REPETIER_GCODE_M360 // Add commands originally from Repetier FW +/** + * Enable M111 debug flags 1=ECHO, 2=INFO, 4=ERRORS (unimplemented). + * Disable to save some flash. Some hosts (Repetier Host) may rely on this feature. + */ +#define DEBUG_FLAGS_GCODE + +/** + * Enable this option for a leaner build of Marlin that removes + * workspace offsets to slightly optimize performance. + * G92 will revert to its behavior from Marlin 1.0. + */ +//#define NO_WORKSPACE_OFFSETS + +/** + * Disable M206 and M428 if you don't need home offsets. + */ +//#define NO_HOME_OFFSETS + /** * CNC G-code options * Support CNC-style G-code dialects used by laser cutters, drawing machine cams, etc. @@ -3212,41 +4141,136 @@ /** * 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 /** - * User-defined menu items that execute custom GCode + * User-defined menu items to run custom G-code. + * Up to 25 may be defined, but the actual number is LCD-dependent. */ -//#define CUSTOM_USER_MENUS -#if ENABLED(CUSTOM_USER_MENUS) - //#define CUSTOM_USER_MENU_TITLE "Custom Commands" - #define USER_SCRIPT_DONE "M117 User Script Done" - #define USER_SCRIPT_AUDIBLE_FEEDBACK - //#define USER_SCRIPT_RETURN // Return to status screen after a script - #define USER_DESC_1 "Home & UBL Info" - #define USER_GCODE_1 "G28\nG29 W" +// @section custom main menu - #define USER_DESC_2 "Preheat for " PREHEAT_1_LABEL - #define USER_GCODE_2 "M140 S" STRINGIFY(PREHEAT_1_TEMP_BED) "\nM104 S" STRINGIFY(PREHEAT_1_TEMP_HOTEND) +// Custom Menu: Main Menu +//#define CUSTOM_MENU_MAIN +#if ENABLED(CUSTOM_MENU_MAIN) + //#define CUSTOM_MENU_MAIN_TITLE "Custom Commands" + #define CUSTOM_MENU_MAIN_SCRIPT_DONE "M117 User Script Done" + #define CUSTOM_MENU_MAIN_SCRIPT_AUDIBLE_FEEDBACK + //#define CUSTOM_MENU_MAIN_SCRIPT_RETURN // Return to status screen after a script + #define CUSTOM_MENU_MAIN_ONLY_IDLE // Only show custom menu when the machine is idle - #define USER_DESC_3 "Preheat for " PREHEAT_2_LABEL - #define USER_GCODE_3 "M140 S" STRINGIFY(PREHEAT_2_TEMP_BED) "\nM104 S" STRINGIFY(PREHEAT_2_TEMP_HOTEND) + #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 USER_DESC_4 "Heat Bed/Home/Level" - #define USER_GCODE_4 "M140 S" STRINGIFY(PREHEAT_2_TEMP_BED) "\nG28\nG29" + #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 USER_DESC_5 "Home & Info" - #define USER_GCODE_5 "G28\nM503" + //#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 + +// Custom Menu: Configuration Menu +//#define CUSTOM_MENU_CONFIG +#if ENABLED(CUSTOM_MENU_CONFIG) + //#define CUSTOM_MENU_CONFIG_TITLE "Custom Commands" + #define CUSTOM_MENU_CONFIG_SCRIPT_DONE "M117 Wireless Script Done" + #define CUSTOM_MENU_CONFIG_SCRIPT_AUDIBLE_FEEDBACK + //#define CUSTOM_MENU_CONFIG_SCRIPT_RETURN // Return to status screen after a script + #define CUSTOM_MENU_CONFIG_ONLY_IDLE // Only show custom menu when the machine is idle + + #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 + +/** + * User-defined buttons to run custom G-code. + * Up to 25 may be defined. + */ +//#define CUSTOM_USER_BUTTONS +#if ENABLED(CUSTOM_USER_BUTTONS) + //#define BUTTON1_PIN -1 + #if PIN_EXISTS(BUTTON1) + #define BUTTON1_HIT_STATE LOW // State of the triggered button. NC=LOW. NO=HIGH. + #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 + #if PIN_EXISTS(BUTTON2) + #define BUTTON2_HIT_STATE LOW + #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 + #if PIN_EXISTS(BUTTON3) + #define BUTTON3_HIT_STATE LOW + #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 + +// @section host + /** * Host Action Commands * @@ -3263,28 +4287,39 @@ */ //#define HOST_ACTION_COMMANDS #if ENABLED(HOST_ACTION_COMMANDS) - //#define HOST_PROMPT_SUPPORT - //#define HOST_START_MENU_ITEM // Add a menu item that tells the host to start + //#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 + #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 #endif +// @section extras + /** * Cancel Objects * * Implement M486 to allow Marlin to skip objects */ //#define CANCEL_OBJECTS +#if ENABLED(CANCEL_OBJECTS) + #define CANCEL_OBJECTS_REPORTING // Emit the current object as a status message +#endif /** * I2C position encoders for closed loop control. * 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/magnetic-encoder-module + * Supplier: https://aus3d.com.au/products/magnetic-encoder-module * Alternative Supplier: https://reliabuild3d.com/ * * Reliabuild encoders have been modified to improve reliability. + * @section i2c encoders */ //#define I2C_POSITION_ENCODERS @@ -3303,7 +4338,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 @@ -3349,13 +4384,14 @@ */ #define I2CPE_MIN_UPD_TIME_MS 4 // (ms) Minimum time between encoder checks. - // Use a rolling average to identify persistant errors that indicate skips, as opposed to vibration and noise. + // Use a rolling average to identify persistent errors that indicate skips, as opposed to vibration and noise. #define I2CPE_ERR_ROLLING_AVERAGE #endif // I2C_POSITION_ENCODERS /** * Analog Joystick(s) + * @section joystick */ //#define JOYSTICK #if ENABLED(JOYSTICK) @@ -3377,9 +4413,10 @@ /** * Mechanical Gantry Calibration - * Modern replacement for the Prusa TMC_Z_CALIBRATION. + * Modern replacement for the PrΕ―Ε‘a TMC_Z_CALIBRATION. * Adds capability to work with any adjustable current drivers. * Implemented as G34 because M915 is deprecated. + * @section calibrate */ //#define MECHANICAL_GANTRY_CALIBRATION #if ENABLED(MECHANICAL_GANTRY_CALIBRATION) @@ -3388,17 +4425,30 @@ #define GANTRY_CALIBRATION_FEEDRATE 500 // Feedrate for correction move //#define GANTRY_CALIBRATION_TO_MIN // Enable to calibrate Z in the MIN direction - //#define GANTRY_CALIBRATION_SAFE_POSITION { X_CENTER, Y_CENTER } // Safe position for nozzle + //#define GANTRY_CALIBRATION_SAFE_POSITION XY_CENTER // Safe position for nozzle //#define GANTRY_CALIBRATION_XY_PARK_FEEDRATE 3000 // XY Park Feedrate - MMM //#define GANTRY_CALIBRATION_COMMANDS_PRE "" #define GANTRY_CALIBRATION_COMMANDS_POST "G28" // G28 highly recommended to ensure an accurate position #endif +/** + * Instant freeze / unfreeze functionality + * Potentially useful for rapid stop that allows being resumed. Halts stepper movement. + * Note this does NOT pause spindles, lasers, fans, heaters or any other auxiliary device. + * @section interface + */ +//#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 +#endif + /** * MAX7219 Debug Matrix * * Add support for a low-cost 8x8 LED Matrix based on the Max7219 chip as a realtime status display. * Requires 3 signal wires. Some useful debug options are included to demonstrate its usage. + * @section debug matrix */ //#define MAX7219_DEBUG #if ENABLED(MAX7219_DEBUG) @@ -3411,43 +4461,62 @@ #define MAX7219_NUMBER_UNITS 1 // Number of Max7219 units in chain. #define MAX7219_ROTATE 0 // Rotate the display clockwise (in multiples of +/- 90Β°) // connector at: right=0 bottom=-90 top=90 left=180 - //#define MAX7219_REVERSE_ORDER // The individual LED matrix units may be in reversed order + //#define MAX7219_REVERSE_ORDER // The order of the LED matrix units may be reversed + //#define MAX7219_REVERSE_EACH // The LEDs in each matrix unit row may be reversed //#define MAX7219_SIDE_BY_SIDE // Big chip+matrix boards can be chained side-by-side /** * Sample debug features * If you add more debug displays, be careful to avoid conflicts! */ - #define MAX7219_DEBUG_PRINTER_ALIVE // Blink corner LED of 8x8 matrix to show that the firmware is functioning - #define MAX7219_DEBUG_PLANNER_HEAD 3 // Show the planner queue head position on this and the next LED matrix row - #define MAX7219_DEBUG_PLANNER_TAIL 5 // Show the planner queue tail position on this and the next LED matrix row + #define MAX7219_DEBUG_PRINTER_ALIVE // Blink corner LED of 8x8 matrix to show that the firmware is functioning + #define MAX7219_DEBUG_PLANNER_HEAD 2 // Show the planner queue head position on this and the next LED matrix row + #define MAX7219_DEBUG_PLANNER_TAIL 4 // Show the planner queue tail position on this and the next LED matrix row - #define MAX7219_DEBUG_PLANNER_QUEUE 0 // Show the current planner queue depth on this and the next LED matrix row - // If you experience stuttering, reboots, etc. this option can reveal how - // tweaks made to the configuration are affecting the printer in real-time. + #define MAX7219_DEBUG_PLANNER_QUEUE 0 // Show the current planner queue depth on this and the next LED matrix row + // If you experience stuttering, reboots, etc. this option can reveal how + // tweaks made to the configuration are affecting the printer in real-time. + #define MAX7219_DEBUG_PROFILE 6 // Display the fraction of CPU time spent in profiled code on this LED matrix + // row. By default idle() is profiled so this shows how "idle" the processor is. + // 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 /** * NanoDLP Sync support * - * Add support for Synchronized Z moves when using with NanoDLP. G0/G1 axis moves will output "Z_move_comp" - * string to enable synchronization with DLP projector exposure. This change will allow to use - * [[WaitForDoneMessage]] instead of populating your gcode with M400 commands + * Support for Synchronized Z moves when used with NanoDLP. G0/G1 axis moves will + * output a "Z_move_comp" string to enable synchronization with DLP projector exposure. + * This feature allows you to use [[WaitForDoneMessage]] instead of M400 commands. + * @section nanodlp */ //#define NANODLP_Z_SYNC #if ENABLED(NANODLP_Z_SYNC) - //#define NANODLP_ALL_AXIS // Enables "Z_move_comp" output on any axis move. - // Default behavior is limited to Z axis only. + //#define NANODLP_ALL_AXIS // Send a "Z_move_comp" report for any axis move (not just Z). #endif /** - * WiFi Support (Espressif ESP32 WiFi) + * Ethernet. Use M552 to enable and set the IP address. + * @section network */ -//#define WIFISUPPORT // Marlin embedded WiFi managenent +#if HAS_ETHERNET + #define MAC_ADDRESS { 0xDE, 0xAD, 0xBE, 0xEF, 0xF0, 0x0D } // A MAC address unique to your network +#endif + +/** + * Native ESP32 board with WiFi or add-on ESP32 WiFi-101 module + */ +//#define WIFISUPPORT // Marlin embedded WiFi management. Not needed for simple WiFi serial port. //#define ESP3D_WIFISUPPORT // ESP3D Library WiFi management (https://github.com/luc-github/ESP3DLib) -#if EITHER(WIFISUPPORT, ESP3D_WIFISUPPORT) - //#define WEBSUPPORT // Start a webserver (which may include auto-discovery) +/** + * Extras for an ESP32-based motherboard with WIFISUPPORT + * These options don't apply to add-on WiFi modules based on ESP32 WiFi101. + */ +#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 @@ -3462,39 +4531,57 @@ //#include "Configuration_Secure.h" // External file with WiFi SSID / Password #endif +// @section multi-material + /** - * PrΕ―Ε‘a Multi-Material Unit v2 + * PrΕ―Ε‘a Multi-Material Unit (MMU) * Enable in Configuration.h + * + * These devices allow a single stepper driver on the board to drive + * multi-material feeders with any number of stepper motors. */ -#if ENABLED(PRUSA_MMU2) +#if HAS_PRUSA_MMU1 + /** + * This option only allows the multiplexer to switch on tool-change. + * Additional options to configure custom E moves are pending. + * + * Override the default DIO selector pins here, if needed. + * Some pins files may provide defaults for these pins. + */ + //#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 - // Serial port used for communication with MMU2. - // For AVR enable the UART port used for the MMU. (e.g., mmuSerial) - // For 32-bit boards check your HAL for available serial ports. (e.g., Serial2) - #define MMU2_SERIAL_PORT 2 - #define MMU2_SERIAL mmuSerial +#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 - // Use hardware reset for MMU if a pin is defined for it - //#define MMU2_RST_PIN 23 + //#define MMU_RST_PIN 23 // Define this pin to use Hardware Reset for MMU2/MMU2S/MMU3 - // Enable if the MMU2 has 12V stepper motors (MMU2 Firmware 1.0.2 and up) - //#define MMU2_MODE_12V + //#define MMU_MENUS // Add an LCD menu for MMU2/MMU2S/MMU3 - // G-code to execute when MMU2 F.I.N.D.A. probe detects filament runout - #define MMU2_FILAMENT_RUNOUT_SCRIPT "M600" + //#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 - // Add an LCD menu for MMU2 - //#define MMU2_MENUS - #if ENABLED(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 + + // 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 \ - { 7.2, 1145 }, \ - { 14.4, 871 }, \ - { 36.0, 1393 }, \ - { 14.4, 871 }, \ - { 50.0, 198 } + { 4.4, 871 }, \ + { 10.0, 1393 }, \ + { 4.4, 871 }, \ + { 10.0, 198 } #define MMU2_RAMMING_SEQUENCE \ { 1.0, 1000 }, \ @@ -3508,38 +4595,37 @@ { 10.0, 700 }, \ { -10.0, 400 }, \ { -50.0, 2000 } - #endif + + #endif // HAS_PRUSA_MMU2 /** - * MMU 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 - * sensor less than 38mm from the gears. - * - * During loading the extruder will stop when the sensor is triggered, then do a last - * 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. + * 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 */ - //#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 - #endif - - /** - * Using a sensor like the MMU2S - * This mode requires a MK3S extruder with a sensor at the extruder idler, like the MMU2S. - * See https://help.prusa3d.com/en/guide/3b-mk3s-mk2-5s-extruder-upgrade_41560, step 11 - */ - //#define PRUSA_MMU2_S_MODE - #if ENABLED(PRUSA_MMU2_S_MODE) + #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 @@ -3548,14 +4634,113 @@ #define MMU2_CAN_LOAD_INCREMENT_SEQUENCE \ { -MMU2_CAN_LOAD_INCREMENT, MMU2_CAN_LOAD_FEEDRATE } + // Continue unloading if sensor detects filament after the initial unload move + //#define MMU_IR_UNLOAD_MOVE + + #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) + + /** + * 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 + * sensor less than 38mm from the gears. + * + * During loading the extruder will stop when the sensor is triggered, then do a last + * 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 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 // PRUSA_MMU2 +#endif // HAS_PRUSA_MMU2 || HAS_PRUSA_MMU3 /** * Advanced Print Counter settings + * @section stats */ #if ENABLED(PRINTCOUNTER) #define SERVICE_WARNING_BUZZES 3 @@ -3585,5 +4770,40 @@ // //#define PINS_DEBUGGING +// +// M265 - I2C Scanner +// +//#define I2C_SCANNER + +// Enable Tests that will run at startup and produce a report +//#define MARLIN_TEST_BUILD + // Enable Marlin dev mode which adds some special commands //#define MARLIN_DEV_MODE + +#if ENABLED(MARLIN_DEV_MODE) + /** + * D576 - Buffer Monitoring + * To help diagnose print quality issues stemming from empty command buffers. + */ + //#define BUFFER_MONITORING +#endif + +/** + * Postmortem Debugging captures misbehavior and outputs the CPU status and backtrace to serial. + * When running in the debugger it will break for debugging. This is useful to help understand + * a crash from a remote location. Requires ~400 bytes of SRAM and 5Kb of flash. + */ +//#define POSTMORTEM_DEBUGGING + +/** + * Software Reset options + */ +//#define SOFT_RESET_VIA_SERIAL // 'KILL' and '^X' commands will soft-reset the controller +//#define SOFT_RESET_ON_KILL // Use a digital button to soft-reset the controller after KILL + +// Report uncleaned reset reason from register r2 instead of MCUSR. Supported by Optiboot on AVR. +//#define OPTIBOOT_RESET_REASON + +// Shrink the build for smaller boards by sacrificing some serial feedback +//#define MARLIN_SMALL_BUILD diff --git a/Marlin/Makefile b/Marlin/Makefile index 49cb960b92..aed2506ac8 100644 --- a/Marlin/Makefile +++ b/Marlin/Makefile @@ -63,8 +63,8 @@ HARDWARE_MOTHERBOARD ?= 1020 ifeq ($(OS),Windows_NT) # Windows - ARDUINO_INSTALL_DIR ?= ${HOME}/Arduino - ARDUINO_USER_DIR ?= ${HOME}/Arduino + ARDUINO_INSTALL_DIR ?= ${HOME}/AppData/Local/Arduino + ARDUINO_USER_DIR ?= ${HOME}/Documents/Arduino else UNAME_S := $(shell uname -s) ifeq ($(UNAME_S),Linux) @@ -82,11 +82,11 @@ endif # Arduino source install directory, and version number # On most linuxes this will be /usr/share/arduino -ARDUINO_INSTALL_DIR ?= ${HOME}/Arduino -ARDUINO_VERSION ?= 106 +ARDUINO_INSTALL_DIR ?= ${HOME}/AppData/Local/Arduino # C:/Users/${USERNAME}/AppData/Local/Arduino +ARDUINO_VERSION ?= 10819 # The installed Libraries are in the User folder -ARDUINO_USER_DIR ?= ${HOME}/Arduino +ARDUINO_USER_DIR ?= ${HOME}/Documents/Arduino # You can optionally set a path to the avr-gcc tools. # Requires a trailing slash. For example, /usr/local/avr-gcc/bin/ @@ -109,8 +109,8 @@ LIQUID_TWI2 ?= 0 # This defines if Wire is needed WIRE ?= 0 -# This defines if Tone is needed (i.e SPEAKER is defined in Configuration.h) -# Disabling this (and SPEAKER) saves approximatively 350 bytes of memory. +# This defines if Tone is needed (i.e., SPEAKER is defined in Configuration.h) +# Disabling this (and SPEAKER) saves approximately 350 bytes of memory. TONE ?= 1 # This defines if U8GLIB is needed (may require RELOC_WORKAROUND) @@ -127,12 +127,12 @@ 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) - @echo This version of GCC is likely broken. Enabling relocation workaround. + $(warning This GCC version $(CC_VER) is likely broken. Enabling relocation workaround.) RELOC_WORKAROUND = 1 endif @@ -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 # @@ -207,11 +218,11 @@ else ifeq ($(HARDWARE_MOTHERBOARD),1105) else ifeq ($(HARDWARE_MOTHERBOARD),1106) # MKS BASE v1.0 else ifeq ($(HARDWARE_MOTHERBOARD),1107) -# MKS v1.4 with A4982 stepper drivers +# MKS BASE v1.4 with Allegro A4982 stepper drivers else ifeq ($(HARDWARE_MOTHERBOARD),1108) -# MKS v1.5 with Allegro A4982 stepper drivers +# MKS BASE v1.5 with Allegro A4982 stepper drivers else ifeq ($(HARDWARE_MOTHERBOARD),1109) -# MKS v1.6 with Allegro A4982 stepper drivers +# MKS BASE v1.6 with Allegro A4982 stepper drivers else ifeq ($(HARDWARE_MOTHERBOARD),1110) # MKS BASE 1.0 with Heroic HR4982 stepper drivers else ifeq ($(HARDWARE_MOTHERBOARD),1111) @@ -219,93 +230,118 @@ else ifeq ($(HARDWARE_MOTHERBOARD),1111) else ifeq ($(HARDWARE_MOTHERBOARD),1112) # MKS GEN L else ifeq ($(HARDWARE_MOTHERBOARD),1113) -# zrib V2.0 control board (Chinese knock off RAMPS replica) -else ifeq ($(HARDWARE_MOTHERBOARD),1114) # BigTreeTech or BIQU KFB2.0 +else ifeq ($(HARDWARE_MOTHERBOARD),1114) +# Zonestar zrib V2.0 (Chinese RAMPS replica) else ifeq ($(HARDWARE_MOTHERBOARD),1115) -# Felix 2.0+ Electronics Board (RAMPS like) +# Zonestar zrib V5.2 (Chinese RAMPS replica) else ifeq ($(HARDWARE_MOTHERBOARD),1116) -# Invent-A-Part RigidBoard +# Zonestar zrib V5.3 (Chinese RAMPS replica) else ifeq ($(HARDWARE_MOTHERBOARD),1117) -# Invent-A-Part RigidBoard V2 +# Felix 2.0+ Electronics Board (RAMPS like) else ifeq ($(HARDWARE_MOTHERBOARD),1118) -# Sainsmart 2-in-1 board +# Invent-A-Part RigidBoard else ifeq ($(HARDWARE_MOTHERBOARD),1119) -# Ultimaker +# Invent-A-Part RigidBoard V2 else ifeq ($(HARDWARE_MOTHERBOARD),1120) -# Ultimaker (Older electronics. Pre 1.5.4. This is rare) +# Sainsmart 2-in-1 board else ifeq ($(HARDWARE_MOTHERBOARD),1121) +# 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),1122) -# Azteeg X3 Pro -else ifeq ($(HARDWARE_MOTHERBOARD),1123) -# Ultimainboard 2.x (Uses TEMP_SENSOR 20) else ifeq ($(HARDWARE_MOTHERBOARD),1124) -# Rumba +# Azteeg X3 Pro else ifeq ($(HARDWARE_MOTHERBOARD),1125) -# Raise3D Rumba +# Ultimainboard 2.x (Uses TEMP_SENSOR 20) else ifeq ($(HARDWARE_MOTHERBOARD),1126) -# Rapide Lite RL200 Rumba +# Rumba else ifeq ($(HARDWARE_MOTHERBOARD),1127) -# Formbot T-Rex 2 Plus +# Raise3D N series Rumba derivative else ifeq ($(HARDWARE_MOTHERBOARD),1128) -# Formbot T-Rex 3 +# Rapide Lite 200 (v1, low-cost RUMBA clone with drv) else ifeq ($(HARDWARE_MOTHERBOARD),1129) -# Formbot Raptor +# Formbot T-Rex 2 Plus else ifeq ($(HARDWARE_MOTHERBOARD),1130) -# Formbot Raptor 2 +# Formbot T-Rex 3 else ifeq ($(HARDWARE_MOTHERBOARD),1131) -# bq ZUM Mega 3D +# Formbot Raptor else ifeq ($(HARDWARE_MOTHERBOARD),1132) -# MakeBoard Mini v2.1.2 is a control board sold by MicroMake +# Formbot Raptor 2 else ifeq ($(HARDWARE_MOTHERBOARD),1133) -# TriGorilla Anycubic version 1.3 based on RAMPS EFB +# bq ZUM Mega 3D else ifeq ($(HARDWARE_MOTHERBOARD),1134) -# TriGorilla Anycubic version 1.4 based on RAMPS EFB +# MakeBoard Mini v2.1.2 by MicroMake else ifeq ($(HARDWARE_MOTHERBOARD),1135) -# TriGorilla Anycubic version 1.4 Rev 1.1 +# TriGorilla Anycubic version 1.3-based on RAMPS EFB else ifeq ($(HARDWARE_MOTHERBOARD),1136) -# Creality: Ender-4, CR-8 +# ... Ver 1.4 else ifeq ($(HARDWARE_MOTHERBOARD),1137) -# Creality: CR10S, CR20, CR-X +# ... Rev 1.1 (new servo pin order) else ifeq ($(HARDWARE_MOTHERBOARD),1138) -# Dagoma F5 +# Creality: Ender-4, CR-8 else ifeq ($(HARDWARE_MOTHERBOARD),1139) -# FYSETC F6 1.3 +# Creality: CR10S, CR20, CR-X else ifeq ($(HARDWARE_MOTHERBOARD),1140) -# FYSETC F6 1.5 +# Creality CR-10 V2, CR-10 V3 else ifeq ($(HARDWARE_MOTHERBOARD),1141) -# Duplicator i3 Plus +# Dagoma F5 else ifeq ($(HARDWARE_MOTHERBOARD),1142) -# VORON +# Dagoma D6 (as found in the Dagoma DiscoUltimate V2 TMC) else ifeq ($(HARDWARE_MOTHERBOARD),1143) -# TRONXY V3 1.0 +# FYSETC F6 1.3 else ifeq ($(HARDWARE_MOTHERBOARD),1144) -# Z-Bolt X Series +# FYSETC F6 1.4 else ifeq ($(HARDWARE_MOTHERBOARD),1145) -# TT OSCAR +# Wanhao Duplicator i3 Plus else ifeq ($(HARDWARE_MOTHERBOARD),1146) -# Overlord/Overlord Pro +# VORON Design else ifeq ($(HARDWARE_MOTHERBOARD),1147) -# ADIMLab Gantry v1 +# Tronxy TRONXY-V3-1.0 else ifeq ($(HARDWARE_MOTHERBOARD),1148) -# ADIMLab Gantry v2 +# Z-Bolt X Series else ifeq ($(HARDWARE_MOTHERBOARD),1149) -# BIQU Tango V1 +# TT OSCAR else ifeq ($(HARDWARE_MOTHERBOARD),1150) -# MKS GEN L V2 +# BIQU Tango V1 else ifeq ($(HARDWARE_MOTHERBOARD),1151) -# MKS GEN L V2.1 +# MKS GEN L V2 else ifeq ($(HARDWARE_MOTHERBOARD),1152) -# Copymaster 3D +# MKS GEN L V2.1 else ifeq ($(HARDWARE_MOTHERBOARD),1153) -# Ortur 4 +# Copymaster 3D else ifeq ($(HARDWARE_MOTHERBOARD),1154) -# Tenlog D3 Hero +# Ortur 4 else ifeq ($(HARDWARE_MOTHERBOARD),1155) +# Tenlog D3 Hero IDEX printer +else ifeq ($(HARDWARE_MOTHERBOARD),1156) +# 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) +# Ramps S 1.2 by Sakul.cz (Power outputs: Hotend0, Hotend1, Hotend2, Bed) +else ifeq ($(HARDWARE_MOTHERBOARD),1159) +# Ramps S 1.2 by Sakul.cz (Power outputs: Hotend, Fan0, Fan1, Bed) +else ifeq ($(HARDWARE_MOTHERBOARD),1160) +# Longer LK1 PRO / Alfawise U20 Pro (PRO version) +else ifeq ($(HARDWARE_MOTHERBOARD),1161) +# Longer LKx PRO / Alfawise Uxx Pro (PRO version) +else ifeq ($(HARDWARE_MOTHERBOARD),1162) +# 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 @@ -323,6 +359,8 @@ else ifeq ($(HARDWARE_MOTHERBOARD),1203) else ifeq ($(HARDWARE_MOTHERBOARD),1204) # abee Scoovo X9H else ifeq ($(HARDWARE_MOTHERBOARD),1205) +# ThinkerV2 +else ifeq ($(HARDWARE_MOTHERBOARD),1206) # # Other ATmega1280, ATmega2560 @@ -356,20 +394,50 @@ else ifeq ($(HARDWARE_MOTHERBOARD),1311) else ifeq ($(HARDWARE_MOTHERBOARD),1312) # Mega controller else ifeq ($(HARDWARE_MOTHERBOARD),1313) -# Geeetech GT2560 Rev B for Mecreator2 +# Geeetech GT2560 Rev A else ifeq ($(HARDWARE_MOTHERBOARD),1314) -# Geeetech GT2560 Rev. A +# Geeetech GT2560 Rev A+ (with auto level probe) else ifeq ($(HARDWARE_MOTHERBOARD),1315) -# Geeetech GT2560 Rev. A+ (with auto level probe) +# Geeetech GT2560 Rev B else ifeq ($(HARDWARE_MOTHERBOARD),1316) -# Geeetech GT2560 Rev B for A10(M/D) +# Geeetech GT2560 Rev B for A10(M/T/D) else ifeq ($(HARDWARE_MOTHERBOARD),1317) -# Geeetech GT2560 Rev B for A20(M/D) +# Geeetech GT2560 Rev B for Mecreator2 else ifeq ($(HARDWARE_MOTHERBOARD),1318) -# Einstart retrofit +# Geeetech GT2560 Rev B for A20(M/T/D) else ifeq ($(HARDWARE_MOTHERBOARD),1319) -# Wanhao 0ne+ i3 Mini +# Geeetech GT2560 Rev B for A10(M/T/D) else ifeq ($(HARDWARE_MOTHERBOARD),1320) +# Geeetech GT2560 Rev B for A20(M/T/D) +else ifeq ($(HARDWARE_MOTHERBOARD),1321) +# Geeetech GT2560 V4.1B for A10(M/T/D) +else ifeq ($(HARDWARE_MOTHERBOARD),1322) +# Einstart retrofit +else ifeq ($(HARDWARE_MOTHERBOARD),1323) +# Wanhao 0ne+ i3 Mini +else ifeq ($(HARDWARE_MOTHERBOARD),1324) +# Wanhao D9 MK2 +else ifeq ($(HARDWARE_MOTHERBOARD),1325) +# Overlord/Overlord Pro +else ifeq ($(HARDWARE_MOTHERBOARD),1326) +# ADIMLab Gantry v1 +else ifeq ($(HARDWARE_MOTHERBOARD),1327) +# ADIMLab Gantry v2 +else ifeq ($(HARDWARE_MOTHERBOARD),1328) +# 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 @@ -403,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 @@ -413,36 +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 +# 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 @@ -553,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 @@ -614,18 +696,18 @@ ifeq ($(HARDWARE_VARIANT), $(filter $(HARDWARE_VARIANT),arduino Teensy Sanguino) # Old libraries (avr-core 1.6.21 < / Arduino < 1.6.8) VPATH += $(ARDUINO_INSTALL_DIR)/hardware/arduino/avr/libraries/SPI # New libraries (avr-core >= 1.6.21 / Arduino >= 1.6.8) - VPATH += $(ARDUINO_INSTALL_DIR)/hardware/arduino/avr/libraries/SPI/src + VPATH += $(ARDUINO_INSTALL_DIR)/packages/arduino/hardware/arduino/avr/1.8.6/libraries/SPI/src endif ifeq ($(IS_MCU),1) - VPATH += $(ARDUINO_INSTALL_DIR)/hardware/arduino/avr/cores/arduino + VPATH += $(ARDUINO_INSTALL_DIR)/packages/arduino/hardware/arduino/avr/1.8.6/cores/arduino # Old libraries (avr-core 1.6.21 < / Arduino < 1.6.8) VPATH += $(ARDUINO_INSTALL_DIR)/hardware/arduino/avr/libraries/SPI VPATH += $(ARDUINO_INSTALL_DIR)/hardware/arduino/avr/libraries/SoftwareSerial # New libraries (avr-core >= 1.6.21 / Arduino >= 1.6.8) - VPATH += $(ARDUINO_INSTALL_DIR)/hardware/arduino/avr/libraries/SPI/src - VPATH += $(ARDUINO_INSTALL_DIR)/hardware/arduino/avr/libraries/SoftwareSerial/src + VPATH += $(ARDUINO_INSTALL_DIR)/packages/arduino/hardware/arduino/avr/1.8.6/libraries/SPI/src + VPATH += $(ARDUINO_INSTALL_DIR)/packages/arduino/hardware/arduino/avr/1.8.6/libraries/SoftwareSerial/src endif VPATH += $(ARDUINO_INSTALL_DIR)/libraries/LiquidCrystal/src @@ -639,17 +721,17 @@ ifeq ($(WIRE), 1) VPATH += $(ARDUINO_INSTALL_DIR)/hardware/arduino/avr/libraries/Wire VPATH += $(ARDUINO_INSTALL_DIR)/hardware/arduino/avr/libraries/Wire/utility # New libraries (avr-core >= 1.6.21 / Arduino >= 1.6.8) - VPATH += $(ARDUINO_INSTALL_DIR)/hardware/arduino/avr/libraries/Wire/src - VPATH += $(ARDUINO_INSTALL_DIR)/hardware/arduino/avr/libraries/Wire/src/utility + VPATH += $(ARDUINO_INSTALL_DIR)/packages/arduino/hardware/avr/1.8.6/libraries/Wire/src + VPATH += $(ARDUINO_INSTALL_DIR)/packages/arduino/hardware/avr/1.8.6/libraries/Wire/src/utility endif ifeq ($(NEOPIXEL), 1) VPATH += $(ARDUINO_INSTALL_DIR)/libraries/Adafruit_NeoPixel endif ifeq ($(U8GLIB), 1) -VPATH += $(ARDUINO_INSTALL_DIR)/libraries/U8glib -VPATH += $(ARDUINO_INSTALL_DIR)/libraries/U8glib/csrc -VPATH += $(ARDUINO_INSTALL_DIR)/libraries/U8glib/cppsrc -VPATH += $(ARDUINO_INSTALL_DIR)/libraries/U8glib/fntsrc +VPATH += $(ARDUINO_INSTALL_DIR)/libraries/U8glib-HAL +VPATH += $(ARDUINO_INSTALL_DIR)/libraries/U8glib-HAL/src +# VPATH += $(ARDUINO_INSTALL_DIR)/libraries/U8glib +# VPATH += $(ARDUINO_INSTALL_DIR)/libraries/U8glib/src endif ifeq ($(TMC), 1) VPATH += $(ARDUINO_INSTALL_DIR)/libraries/TMCStepper/src @@ -658,9 +740,9 @@ endif ifeq ($(HARDWARE_VARIANT), arduino) HARDWARE_SUB_VARIANT ?= mega - VPATH += $(ARDUINO_INSTALL_DIR)/hardware/arduino/avr/variants/$(HARDWARE_SUB_VARIANT) + VPATH += $(ARDUINO_INSTALL_DIR)/packages/arduino/hardware/avr/1.8.6/variants/$(HARDWARE_SUB_VARIANT) else ifeq ($(HARDWARE_VARIANT), Sanguino) - VPATH += $(ARDUINO_INSTALL_DIR)/hardware/marlin/avr/variants/sanguino + VPATH += $(ARDUINO_INSTALL_DIR)/packages/arduino/hardware/avr/1.8.6/variants/sanguino else ifeq ($(HARDWARE_VARIANT), archim) VPATH += $(ARDUINO_INSTALL_DIR)/packages/ultimachine/hardware/sam/1.6.9-b/system/libsam VPATH += $(ARDUINO_INSTALL_DIR)/packages/ultimachine/hardware/sam/1.6.9-b/system/CMSIS/CMSIS/Include/ @@ -676,7 +758,7 @@ else ifeq ($(HARDWARE_VARIANT), archim) LDLIBS = $(ARDUINO_INSTALL_DIR)/packages/ultimachine/hardware/sam/1.6.9-b/variants/archim/libsam_sam3x8e_gcc_rel.a else HARDWARE_SUB_VARIANT ?= standard - VPATH += $(ARDUINO_INSTALL_DIR)/hardware/$(HARDWARE_VARIANT)/variants/$(HARDWARE_SUB_VARIANT) + VPATH += $(ARDUINO_INSTALL_DIR)/packages/arduino/hardware/avr/1.8.6/variants/$(HARDWARE_SUB_VARIANT) endif LIB_SRC = wiring.c \ @@ -691,7 +773,7 @@ endif ifeq ($(HARDWARE_VARIANT), Teensy) LIB_SRC = wiring.c - VPATH += $(ARDUINO_INSTALL_DIR)/hardware/teensy/cores/teensy + VPATH += $(ARDUINO_INSTALL_DIR)/packages/arduino/hardware/teensy/cores/teensy endif LIB_CXXSRC = WMath.cpp WString.cpp Print.cpp SPI.cpp @@ -725,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) @@ -795,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 @@ -838,7 +920,7 @@ AVRDUDE_WRITE_FLASH = -Uflash:w:$(BUILD_DIR)/$(TARGET).hex:i ifeq ($(shell uname -s), Linux) AVRDUDE_CONF = /etc/avrdude/avrdude.conf else - AVRDUDE_CONF = $(ARDUINO_INSTALL_DIR)/hardware/tools/avr/etc/avrdude.conf + AVRDUDE_CONF = $(ARDUINO_INSTALL_DIR)/packages/arduino/tools/avrdude/6.3.0-arduino17/etc/avrdude.conf endif AVRDUDE_FLAGS = -D -C$(AVRDUDE_CONF) \ -p$(PROG_MCU) -P$(AVRDUDE_PORT) -c$(AVRDUDE_PROGRAMMER) \ @@ -953,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) @@ -991,5 +1073,5 @@ clean: .PHONY: all build elf hex eep lss sym program coff extcoff clean depend sizebefore sizeafter -# Automaticaly include the dependency files created by gcc +# Automatically include the dependency files created by gcc -include ${patsubst %.o, %.d, ${OBJ}} diff --git a/Marlin/Marlin.ino b/Marlin/Marlin.ino index 57c825445f..066cc1717d 100644 --- a/Marlin/Marlin.ino +++ b/Marlin/Marlin.ino @@ -2,7 +2,7 @@ Marlin Firmware - (c) 2011-2020 MarlinFirmware + (c) 2011-2024 MarlinFirmware Portions of Marlin are (c) by their respective authors. All code complies with GPLv2 and/or GPLv3 @@ -27,7 +27,7 @@ Configuration - https://github.com/MarlinFirmware/Configurations Example configurations for several printer models. - - https://www.youtube.com/watch?v=3gwWVFtdg-4 + - https://youtu.be/3gwWVFtdg-4 A good 20-minute overview of Marlin configuration by Tom Sanladerer. (Applies to Marlin 1.0.x, so Jerk and Acceleration should be halved.) Also... https://www.google.com/search?tbs=vid%3A1&q=configure+marlin diff --git a/Marlin/Version.h b/Marlin/Version.h index f0b74dbfe0..d257cd11f5 100644 --- a/Marlin/Version.h +++ b/Marlin/Version.h @@ -28,7 +28,7 @@ /** * Marlin release version identifier */ -//#define SHORT_BUILD_VERSION "2.0.7.3" +//#define SHORT_BUILD_VERSION "bugfix-2.1.x" /** * Verbose version identifier which should contain a reference to the location @@ -41,7 +41,14 @@ * here we define this default string as the date where the latest release * version was tagged. */ -//#define STRING_DISTRIBUTION_DATE "2023-07-18" +//#define STRING_DISTRIBUTION_DATE "2026-01-12" + +/** + * 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,10 +58,10 @@ /** * 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 "https://github.com/MarlinFirmware/Marlin" +//#define SOURCE_CODE_URL "github.com/MarlinFirmware/Marlin" /** * Default generic printer UUID. @@ -65,11 +72,11 @@ * The WEBSITE_URL is the location where users can get more information such as * documentation about a specific Marlin release. */ -//#define WEBSITE_URL "https://marlinfw.org" +//#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 new file mode 100644 index 0000000000..fd2b81062a --- /dev/null +++ b/Marlin/config.ini @@ -0,0 +1,254 @@ +# +# Marlin Firmware +# config.ini - Options to apply before the build +# +[config:base] +# +# ini_use_config - A comma-separated list of actions to apply to the Configuration files. +# The actions will be applied in the listed order. +# - none +# Ignore this file and don't apply any configuration options +# +# - base +# Just apply the options in config:base to the configuration +# +# - minimal +# Just apply the options in config:minimal to the configuration +# +# - all +# Apply all 'config:*' sections in this file to the configuration +# +# - another.ini +# Load another INI file with a path relative to this config.ini file (i.e., within Marlin/) +# +# - https://me.myserver.com/path/to/configs +# Fetch configurations from any URL. +# +# - example/Creality/Ender-5 Plus @ bugfix-2.1.x +# Fetch example configuration files from the MarlinFirmware/Configurations repository +# https://raw.githubusercontent.com/MarlinFirmware/Configurations/bugfix-2.1.x/config/examples/Creality/Ender-5%20Plus/ +# +# - example/default @ release-2.0.9.7 +# Fetch default configuration files from the MarlinFirmware/Configurations repository +# https://raw.githubusercontent.com/MarlinFirmware/Configurations/release-2.0.9.7/config/default/ +# +# - [disable] +# Comment out all #defines in both Configuration.h and Configuration_adv.h. This is useful +# to start with a clean slate before applying any config: options, so only the options explicitly +# set in config.ini will be enabled in the configuration. +# +# - [flatten] (Not yet implemented) +# Produce a flattened set of Configuration.h and Configuration_adv.h files with only the enabled +# #defines and no comments. A clean look, but context-free. +# +ini_use_config = none + +# Load all config: sections in this file +;ini_use_config = all +# Disable everything and apply subsequent config:base options +;ini_use_config = [disable], base +# Load config file relative to Marlin/ +;ini_use_config = another.ini +# Download configurations from GitHub +;ini_use_config = example/Creality/Ender-5 Plus @ bugfix-2.1.x +# Download configurations from your server +;ini_use_config = https://me.myserver.com/path/to/configs +# Evaluate config:base and do a config dump +;ini_use_config = base +;config_export = 2 + +[config:minimal] +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 +thermal_protection_period = 40 + +bufsize = 4 +block_buffer_size = 16 +max_cmd_size = 96 + +extruders = 1 +temp_sensor_0 = 1 + +temp_hysteresis = 3 +heater_0_mintemp = 5 +heater_0_maxtemp = 275 + +pidtemp = on +pid_k1 = 0.95 +pid_max = 255 +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 +e0_driver_type = A4988 + +x_bed_size = 200 +x_min_pos = 0 +x_max_pos = X_BED_SIZE + +y_bed_size = 200 +y_min_pos = 0 +y_max_pos = Y_BED_SIZE + +z_min_pos = 0 +z_max_pos = 200 + +x_home_dir = -1 +y_home_dir = -1 +z_home_dir = -1 + +x_min_endstop_hit_state = HIGH +y_min_endstop_hit_state = HIGH +z_min_endstop_hit_state = HIGH + +default_axis_steps_per_unit = { 80, 80, 400, 500 } +axis_relative_modes = { false, false, false, false } +default_max_feedrate = { 300, 300, 5, 25 } +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 = LOW +y_enable_on = LOW +z_enable_on = LOW +e_enable_on = LOW + +invert_x_dir = false +invert_y_dir = true +invert_z_dir = false +invert_e0_dir = false + +step_state_e = HIGH +step_state_x = HIGH +step_state_y = HIGH +step_state_z = HIGH + +proportional_font_ratio = 1.0 +default_nominal_filament_dia = 1.75 + +junction_deviation_mm = 0.013 + +default_acceleration = 3000 +default_travel_acceleration = 3000 +default_retract_acceleration = 3000 + +default_minimumfeedrate = 0.0 +default_mintravelfeedrate = 0.0 + +min_steps_per_segment = 6 +default_minsegmenttime = 20000 + +[config:basic] +hotend_overshoot = 15 +bed_overshoot = 10 +max_bed_power = 255 + +busy_while_heating = on +host_keepalive_feature = on +default_keepalive_interval = 2 +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 + +prevent_cold_extrusion = on +extrude_mintemp = 170 +prevent_lengthy_extrude = on +extrude_maxlength = 200 + +min_software_endstops = on +max_software_endstops = on +min_software_endstop_x = on +min_software_endstop_y = on +min_software_endstop_z = on +max_software_endstop_x = on +max_software_endstop_y = on +max_software_endstop_z = on + +preheat_1_label = "PLA" +preheat_1_temp_hotend = 180 +preheat_1_temp_bed = 70 +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 + +[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 + +default_stepper_timeout_sec = 120 +disable_idle_x = on +disable_idle_y = on +disable_idle_z = on +disable_idle_e = on + +e0_auto_fan_pin = -1 + +faster_gcode_parser = on +debug_flags_gcode = on + +homing_bump_mm = { 5, 5, 2 } + +slowdown = on +slowdown_divisor = 2 +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 diff --git a/Marlin/src/HAL/AVR/HAL.cpp b/Marlin/src/HAL/AVR/HAL.cpp index 58d57c8ee5..1fa7222711 100644 --- a/Marlin/src/HAL/AVR/HAL.cpp +++ b/Marlin/src/HAL/AVR/HAL.cpp @@ -23,56 +23,172 @@ #include "../../inc/MarlinConfig.h" #include "HAL.h" +#include + +#ifdef USBCON + DefaultSerial1 MSerial0(false, Serial); + #ifdef BLUETOOTH + BTSerial btSerial(false, bluetoothSerial); + #endif +#endif // ------------------------ // Public Variables // ------------------------ -//uint8_t MCUSR; +// Don't initialize/override variable (which would happen in .init4) +uint8_t MarlinHAL::reset_reason __attribute__((section(".noinit"))); // ------------------------ // Public functions // ------------------------ -void HAL_init() { +__attribute__((naked)) // Don't output function pro- and epilogue +__attribute__((used)) // Output the function, even if "not used" +__attribute__((section(".init3"))) // Put in an early user definable section +void save_reset_reason() { + #if ENABLED(OPTIBOOT_RESET_REASON) + __asm__ __volatile__( + A("STS %0, r2") + : "=m"(hal.reset_reason) + ); + #else + hal.reset_reason = MCUSR; + #endif + + // Clear within 16ms since WDRF bit enables a 16ms watchdog timer -> Boot loop + hal.clear_reset_source(); + wdt_disable(); +} + +#include "registers.h" + +MarlinHAL::MarlinHAL() { + TERN_(HAL_AVR_DIRTY_INIT, _ATmega_resetperipherals()); // Clean-wipe the device state. +} + +void MarlinHAL::init() { // Init Servo Pins - #define INIT_SERVO(N) OUT_WRITE(SERVO##N##_PIN, LOW) #if HAS_SERVO_0 - INIT_SERVO(0); + OUT_WRITE(SERVO0_PIN, LOW); #endif #if HAS_SERVO_1 - INIT_SERVO(1); + OUT_WRITE(SERVO1_PIN, LOW); #endif #if HAS_SERVO_2 - INIT_SERVO(2); + OUT_WRITE(SERVO2_PIN, LOW); #endif #if HAS_SERVO_3 - INIT_SERVO(3); + OUT_WRITE(SERVO3_PIN, LOW); + #endif + #if HAS_SERVO_4 + OUT_WRITE(SERVO4_PIN, LOW); + #endif + #if HAS_SERVO_5 + OUT_WRITE(SERVO5_PIN, LOW); + #endif + + init_pwm_timers(); // Init user timers to default frequency - 1000HZ + + #if PIN_EXISTS(BEEPER) && ENABLED(HAL_AVR_DIRTY_INIT) && DISABLED(ATMEGA_NO_BEEPFIX) + // Make sure no alternative is locked onto the BEEPER. + // This fixes the issue where the ATmega is constantly beeping. + // 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://youtube.be/jMgCvRXkexk + _ATmega_savePinAlternate(BEEPER_PIN); + + OUT_WRITE(BEEPER_PIN, LOW); #endif } -#if ENABLED(SDSUPPORT) +void MarlinHAL::reboot() { + #if ENABLED(USE_WATCHDOG) + while (1) { /* run out the watchdog */ } + #else + void (*resetFunc)() = 0; // Declare resetFunc() at address 0 + resetFunc(); // Jump to address 0 + #endif +} + +// ------------------------ +// Watchdog Timer +// ------------------------ + +#if ENABLED(USE_WATCHDOG) + + #include + + // Initialize watchdog with 8s timeout, if possible. Otherwise, make it 4s. + void MarlinHAL::watchdog_init() { + #if ENABLED(WATCHDOG_DURATION_8S) && defined(WDTO_8S) + #define WDTO_NS WDTO_8S + #else + #define WDTO_NS WDTO_4S + #endif + #if ENABLED(WATCHDOG_RESET_MANUAL) + // Enable the watchdog timer, but only for the interrupt. + // Take care, as this requires the correct order of operation, with interrupts disabled. + // See the datasheet of any AVR chip for details. + wdt_reset(); + cli(); + _WD_CONTROL_REG = _BV(_WD_CHANGE_BIT) | _BV(WDE); + _WD_CONTROL_REG = _BV(WDIE) | (WDTO_NS & 0x07) | ((WDTO_NS & 0x08) << 2); // WDTO_NS directly does not work. bit 0-2 are consecutive in the register but the highest value bit is at bit 5 + // So worked for up to WDTO_2S + sei(); + wdt_reset(); + #else + wdt_enable(WDTO_NS); // The function handles the upper bit correct. + #endif + //delay(10000); // test it! + } + + //=========================================================================== + //=================================== ISR =================================== + //=========================================================================== + + // Watchdog timer interrupt, called if main program blocks >4sec and manual reset is enabled. + #if ENABLED(WATCHDOG_RESET_MANUAL) + ISR(WDT_vect) { + sei(); // With the interrupt driven serial we need to allow interrupts. + SERIAL_ERROR_MSG(STR_WATCHDOG_FIRED); + marlin.minkill(); // interrupt-safe final kill and infinite loop + } + #endif + + // Reset watchdog. MUST be called at least every 4 seconds after the + // first watchdog_init or AVR will go into emergency procedures. + void MarlinHAL::watchdog_refresh() { wdt_reset(); } + +#endif // USE_WATCHDOG + +// ------------------------ +// Free Memory Accessor +// ------------------------ + +#if HAS_MEDIA #include "../../sd/SdFatUtil.h" int freeMemory() { return SdFatUtil::FreeRam(); } -#else // !SDSUPPORT +#else // !HAS_MEDIA -extern "C" { - extern char __bss_end; - extern char __heap_start; - extern void* __brkval; + extern "C" { + extern char __bss_end; + extern char __heap_start; + extern void* __brkval; - int freeMemory() { - int free_memory; - if ((int)__brkval == 0) - free_memory = ((int)&free_memory) - ((int)&__bss_end); - else - free_memory = ((int)&free_memory) - ((int)__brkval); - return free_memory; + int freeMemory() { + int free_memory; + if ((int)__brkval == 0) + free_memory = ((int)&free_memory) - ((int)&__bss_end); + else + free_memory = ((int)&free_memory) - ((int)__brkval); + return free_memory; + } } -} -#endif // !SDSUPPORT +#endif // !HAS_MEDIA #endif // __AVR__ diff --git a/Marlin/src/HAL/AVR/HAL.h b/Marlin/src/HAL/AVR/HAL.h index 6e0afa8f10..02c5d42beb 100644 --- a/Marlin/src/HAL/AVR/HAL.h +++ b/Marlin/src/HAL/AVR/HAL.h @@ -1,7 +1,9 @@ /** * Marlin 3D Printer Firmware * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com + * + * 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 @@ -19,17 +21,20 @@ */ #pragma once +/** + * HAL for Arduino AVR + */ + #include "../shared/Marduino.h" #include "../shared/HAL_SPI.h" #include "fastio.h" -#include "watchdog.h" #include "math.h" -#ifdef IS_AT90USB +#ifdef USBCON #include #else - #define HardwareSerial_h // Hack to prevent HardwareSerial.h header inclusion #include "MarlinSerial.h" + #define BOARD_NO_NATIVE_USB #endif #include @@ -39,6 +44,19 @@ #include #include +// +// Default graphical display delays +// +#if F_CPU >= 20000000 + #define CPU_ST7920_DELAY_1 150 + #define CPU_ST7920_DELAY_2 0 + #define CPU_ST7920_DELAY_3 150 +#elif F_CPU == 16000000 + #define CPU_ST7920_DELAY_1 125 + #define CPU_ST7920_DELAY_2 0 + #define CPU_ST7920_DELAY_3 188 +#endif + #ifndef pgm_read_ptr // Compatibility for avr-libc 1.8.0-4.1 included with Ubuntu for // Windows Subsystem for Linux on Windows 10 as of 10/18/2019 @@ -61,9 +79,9 @@ #define CRITICAL_SECTION_START() unsigned char _sreg = SREG; cli() #define CRITICAL_SECTION_END() SREG = _sreg #endif -#define ISRS_ENABLED() TEST(SREG, SREG_I) -#define ENABLE_ISRS() sei() -#define DISABLE_ISRS() cli() + +#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() // ------------------------ // Types @@ -71,91 +89,72 @@ typedef int8_t pin_t; -#define SHARED_SERVOS HAS_SERVOS -#define HAL_SERVO_LIB Servo +#define SHARED_SERVOS HAS_SERVOS // Use shared/servos.cpp + +class Servo; +typedef Servo hal_servo_t; // ------------------------ -// Public Variables -// ------------------------ - -//extern uint8_t MCUSR; - // Serial ports -#ifdef IS_AT90USB - #define MYSERIAL0 TERN(BLUETOOTH, bluetoothSerial, Serial) -#else - #if !WITHIN(SERIAL_PORT, -1, 3) - #error "SERIAL_PORT must be from -1 to 3. Please update your configuration." +// ------------------------ + +#ifdef USBCON + #include "../../core/serial_hook.h" + typedef ForwardSerial1Class< decltype(Serial) > DefaultSerial1; + extern DefaultSerial1 MSerial0; + #ifdef BLUETOOTH + typedef ForwardSerial1Class< decltype(bluetoothSerial) > BTSerial; + extern BTSerial btSerial; #endif - #define MYSERIAL0 customizedSerial1 + + #define MYSERIAL1 TERN(BLUETOOTH, btSerial, MSerial0) +#else + #if !WITHIN(SERIAL_PORT, 0, 3) + #error "SERIAL_PORT must be from 0 to 3." + #endif + #define MYSERIAL1 customizedSerial1 #ifdef SERIAL_PORT_2 - #if !WITHIN(SERIAL_PORT_2, -1, 3) - #error "SERIAL_PORT_2 must be from -1 to 3. Please update your configuration." + #if !WITHIN(SERIAL_PORT_2, 0, 3) + #error "SERIAL_PORT_2 must be from 0 to 3." #endif - #define MYSERIAL1 customizedSerial2 + #define MYSERIAL2 customizedSerial2 #endif + + #ifdef SERIAL_PORT_3 + #if !WITHIN(SERIAL_PORT_3, 0, 3) + #error "SERIAL_PORT_3 must be from 0 to 3." + #endif + #define MYSERIAL3 customizedSerial3 + #endif +#endif + +#ifdef MMU_SERIAL_PORT + #if !WITHIN(MMU_SERIAL_PORT, 0, 3) + #error "MMU_SERIAL_PORT must be from 0 to 3" + #endif + #define MMU_SERIAL mmuSerial #endif #ifdef LCD_SERIAL_PORT - #if !WITHIN(LCD_SERIAL_PORT, -1, 3) - #error "LCD_SERIAL_PORT must be from -1 to 3. Please update your configuration." + #if !WITHIN(LCD_SERIAL_PORT, 0, 3) + #error "LCD_SERIAL_PORT must be from 0 to 3." #endif #define LCD_SERIAL lcdSerial - #if HAS_DGUS_LCD - #define SERIAL_GET_TX_BUFFER_FREE() LCD_SERIAL.get_tx_buffer_free() + #if ANY(HAS_DGUS_LCD, EXTENSIBLE_UI) + #define LCD_SERIAL_TX_BUFFER_FREE() LCD_SERIAL.get_tx_buffer_free() #endif #endif -// ------------------------ -// Public functions -// ------------------------ - -void HAL_init(); - -//void cli(); - -//void _delay_ms(const int delay); - -inline void HAL_clear_reset_source() { MCUSR = 0; } -inline uint8_t HAL_get_reset_source() { return MCUSR; } - -inline void HAL_reboot() {} // reboot the board or restart the bootloader - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-function" -extern "C" { - int freeMemory(); -} -#pragma GCC diagnostic pop - +// // ADC -#ifdef DIDR2 - #define HAL_ANALOG_SELECT(ind) do{ if (ind < 8) SBI(DIDR0, ind); else SBI(DIDR2, ind & 0x07); }while(0) -#else - #define HAL_ANALOG_SELECT(ind) SBI(DIDR0, ind); -#endif - -inline void HAL_adc_init() { - ADCSRA = _BV(ADEN) | _BV(ADSC) | _BV(ADIF) | 0x07; - DIDR0 = 0; - #ifdef DIDR2 - DIDR2 = 0; - #endif -} - -#define SET_ADMUX_ADCSRA(ch) ADMUX = _BV(REFS0) | (ch & 0x07); SBI(ADCSRA, ADSC) -#ifdef MUX5 - #define HAL_START_ADC(ch) if (ch > 7) ADCSRB = _BV(MUX5); else ADCSRB = 0; SET_ADMUX_ADCSRA(ch) -#else - #define HAL_START_ADC(ch) ADCSRB = 0; SET_ADMUX_ADCSRA(ch) -#endif - -#define HAL_ADC_VREF 5.0 -#define HAL_ADC_RESOLUTION 10 -#define HAL_READ_ADC() ADC -#define HAL_ADC_READY() !TEST(ADCSRA, ADSC) +// +#define HAL_ADC_VREF_MV 5000 +#define HAL_ADC_RESOLUTION 10 +// +// 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) @@ -169,23 +168,113 @@ inline void HAL_adc_init() { // AVR compatibility #define strtof strtod -#define HAL_CAN_SET_PWM_FREQ // This HAL supports PWM Frequency adjustment +// ------------------------ +// Free Memory Accessor +// ------------------------ -/** - * set_pwm_frequency - * Sets the frequency of the timer corresponding to the provided pin - * as close as possible to the provided desired frequency. Internally - * calculates the required waveform generation mode, prescaler and - * resolution values required and sets the 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 FAN PWM Settings) - */ -void set_pwm_frequency(const pin_t pin, int f_desired); +#pragma GCC diagnostic push +#if GCC_VERSION <= 50000 + #pragma GCC diagnostic ignored "-Wunused-function" +#endif -/** - * set_pwm_duty - * Sets the PWM duty cycle of the provided pin to the provided value - * Optionally allows inverting the duty cycle [default = false] - * Optionally allows changing the maximum size of the provided value to enable finer PWM duty control [default = 255] - */ -void set_pwm_duty(const pin_t pin, const uint16_t v, const uint16_t v_size=255, const bool invert=false); +extern "C" int freeMemory(); + +#pragma GCC diagnostic pop + +// ------------------------ +// MarlinHAL Class +// ------------------------ + +class MarlinHAL { +public: + + // Earliest possible init, before setup() + MarlinHAL(); + + // 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(); // Restart the firmware from 0x0 + + // Interrupts + static bool isr_state() { return TEST(SREG, SREG_I); } + static void isr_on() { sei(); } + static void isr_off() { cli(); } + + static void delay_ms(const int ms) { delay(ms); } + + // Tasks, called from marlin.idle() + static void idletask() {} + + // Reset + static uint8_t reset_reason; + static uint8_t get_reset_source() { return reset_reason; } + static void clear_reset_source() { MCUSR = 0; } + + // Free SRAM + static int freeMemory() { return ::freeMemory(); } + + // + // ADC Methods + // + + // Called by Temperature::init once at startup + static void adc_init() { + ADCSRA = _BV(ADEN) | _BV(ADSC) | _BV(ADIF) | 0x07; + DIDR0 = 0; + #ifdef DIDR2 + DIDR2 = 0; + #endif + } + + // Called by Temperature::init for each sensor at startup + static void adc_enable(const uint8_t ch) { + #ifdef DIDR2 + if (ch > 7) { SBI(DIDR2, ch & 0x07); return; } + #endif + SBI(DIDR0, ch); + } + + // Begin ADC sampling on the given channel. Called from Temperature::isr! + static void adc_start(const uint8_t ch) { + #ifdef MUX5 + ADCSRB = ch > 7 ? _BV(MUX5) : 0; + #else + ADCSRB = 0; + #endif + ADMUX = _BV(REFS0) | (ch & 0x07); + SBI(ADCSRA, ADSC); + } + + // Is the ADC ready for reading? + static bool adc_ready() { return !TEST(ADCSRA, ADSC); } + + // The current value of the ADC register + static __typeof__(ADC) adc_value() { return ADC; } + + /** + * init_pwm_timers + * Set the default frequency for timers 2-5 to 1000HZ + */ + static void init_pwm_timers(); + + /** + * 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/AVR/HAL_SPI.cpp b/Marlin/src/HAL/AVR/HAL_SPI.cpp index 31e589746c..db6a12734d 100644 --- a/Marlin/src/HAL/AVR/HAL_SPI.cpp +++ b/Marlin/src/HAL/AVR/HAL_SPI.cpp @@ -34,21 +34,21 @@ #include "../../inc/MarlinConfig.h" void spiBegin() { - OUT_WRITE(SS_PIN, HIGH); - SET_OUTPUT(SCK_PIN); - SET_INPUT(MISO_PIN); - SET_OUTPUT(MOSI_PIN); - - #if DISABLED(SOFTWARE_SPI) - // SS must be in output mode even it is not chip select - //SET_OUTPUT(SS_PIN); - // set SS high - may be chip select for another SPI device - //#if SET_SPI_SS_HIGH - //WRITE(SS_PIN, HIGH); - //#endif - // set a default rate - spiInit(1); + #if PIN_EXISTS(SD_SS) + // Do not init HIGH for boards with pin 4 used as Fans or Heaters or otherwise, not likely to have multiple SPI devices anyway. + #if defined(__AVR_ATmega644__) || defined(__AVR_ATmega644P__) || defined(__AVR_ATmega644PA__) || defined(__AVR_ATmega1284P__) + // SS must be in output mode even it is not chip select + SET_OUTPUT(SD_SS_PIN); + #else + // set SS high - may be chip select for another SPI device + OUT_WRITE(SD_SS_PIN, HIGH); + #endif #endif + SET_OUTPUT(SD_SCK_PIN); + SET_INPUT(SD_MISO_PIN); + SET_OUTPUT(SD_MOSI_PIN); + + IF_DISABLED(SOFTWARE_SPI, spiInit(SPI_HALF_SPEED)); } #if NONE(SOFTWARE_SPI, FORCE_SOFT_SPI) @@ -74,7 +74,8 @@ void spiBegin() { #elif defined(PRR0) PRR0 #endif - , PRSPI); + , PRSPI + ); SPCR = _BV(SPE) | _BV(MSTR) | (spiRate >> 1); SPSR = spiRate & 1 || spiRate == 6 ? 0 : _BV(SPI2X); @@ -88,7 +89,7 @@ void spiBegin() { } /** SPI read data */ - void spiRead(uint8_t* buf, uint16_t nbyte) { + void spiRead(uint8_t *buf, uint16_t nbyte) { if (nbyte-- == 0) return; SPDR = 0xFF; for (uint16_t i = 0; i < nbyte; i++) { @@ -107,7 +108,7 @@ void spiBegin() { } /** SPI send block */ - void spiSendBlock(uint8_t token, const uint8_t* buf) { + void spiSendBlock(uint8_t token, const uint8_t *buf) { SPDR = token; for (uint16_t i = 0; i < 512; i += 2) { while (!TEST(SPSR, SPIF)) { /* Intentionally left empty */ } @@ -118,7 +119,6 @@ void spiBegin() { while (!TEST(SPSR, SPIF)) { /* Intentionally left empty */ } } - /** begin spi transaction */ void spiBeginTransaction(uint32_t spiClock, uint8_t bitOrder, uint8_t dataMode) { // Based on Arduino SPI library @@ -174,7 +174,6 @@ void spiBegin() { SPSR = clockDiv | 0x01; } - #else // SOFTWARE_SPI || FORCE_SOFT_SPI // ------------------------ @@ -195,19 +194,19 @@ void spiBegin() { // no interrupts during byte receive - about 8Β΅s cli(); // output pin high - like sending 0xFF - WRITE(MOSI_PIN, HIGH); + WRITE(SD_MOSI_PIN, HIGH); - LOOP_L_N(i, 8) { - WRITE(SCK_PIN, HIGH); + for (uint8_t i = 0; i < 8; ++i) { + WRITE(SD_SCK_PIN, HIGH); nop; // adjust so SCK is nice nop; data <<= 1; - if (READ(MISO_PIN)) data |= 1; + if (READ(SD_MISO_PIN)) data |= 1; - WRITE(SCK_PIN, LOW); + WRITE(SD_SCK_PIN, LOW); } sei(); @@ -215,7 +214,7 @@ void spiBegin() { } // Soft SPI read data - void spiRead(uint8_t* buf, uint16_t nbyte) { + void spiRead(uint8_t *buf, uint16_t nbyte) { for (uint16_t i = 0; i < nbyte; i++) buf[i] = spiRec(); } @@ -224,11 +223,11 @@ void spiBegin() { void spiSend(uint8_t data) { // no interrupts during byte send - about 8Β΅s cli(); - LOOP_L_N(i, 8) { - WRITE(SCK_PIN, LOW); - WRITE(MOSI_PIN, data & 0x80); + for (uint8_t i = 0; i < 8; ++i) { + WRITE(SD_SCK_PIN, LOW); + WRITE(SD_MOSI_PIN, data & 0x80); data <<= 1; - WRITE(SCK_PIN, HIGH); + WRITE(SD_SCK_PIN, HIGH); } nop; // hold SCK high for a few ns @@ -236,13 +235,13 @@ void spiBegin() { nop; nop; - WRITE(SCK_PIN, LOW); + WRITE(SD_SCK_PIN, LOW); sei(); } // Soft SPI send block - void spiSendBlock(uint8_t token, const uint8_t* buf) { + void spiSendBlock(uint8_t token, const uint8_t *buf) { spiSend(token); for (uint16_t i = 0; i < 512; i++) spiSend(buf[i]); diff --git a/Marlin/src/HAL/STM32/watchdog.h b/Marlin/src/HAL/AVR/MarlinSPI.h similarity index 87% rename from Marlin/src/HAL/STM32/watchdog.h rename to Marlin/src/HAL/AVR/MarlinSPI.h index 49a0d9c631..0c447ba4cb 100644 --- a/Marlin/src/HAL/STM32/watchdog.h +++ b/Marlin/src/HAL/AVR/MarlinSPI.h @@ -1,6 +1,6 @@ /** * Marlin 3D Printer Firmware - * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] + * Copyright (c) 2021 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] * * Based on Sprinter and grbl. * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm @@ -21,5 +21,6 @@ */ #pragma once -void watchdog_init(); -void HAL_watchdog_refresh(); +#include + +using MarlinSPI = SPIClass; diff --git a/Marlin/src/HAL/AVR/MarlinSerial.cpp b/Marlin/src/HAL/AVR/MarlinSerial.cpp index 8feac32aa7..750776c4be 100644 --- a/Marlin/src/HAL/AVR/MarlinSerial.cpp +++ b/Marlin/src/HAL/AVR/MarlinSerial.cpp @@ -38,10 +38,9 @@ #include "../../inc/MarlinConfig.h" -#if !IS_AT90USB && (defined(UBRRH) || defined(UBRR0H) || defined(UBRR1H) || defined(UBRR2H) || defined(UBRR3H)) +#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" @@ -486,7 +485,7 @@ void MarlinSerial::write(const uint8_t c) { const uint8_t i = (tx_buffer.head + 1) & (Cfg::TX_SIZE - 1); // If global interrupts are disabled (as the result of being called from an ISR)... - if (!ISRS_ENABLED()) { + if (!hal.isr_state()) { // Make room by polling if it is possible to transmit, and do so! while (i == tx_buffer.tail) { @@ -534,7 +533,7 @@ void MarlinSerial::flushTX() { if (!_written) return; // If global interrupts are disabled (as the result of being called from an ISR)... - if (!ISRS_ENABLED()) { + if (!hal.isr_state()) { // Wait until everything was transmitted - We must do polling, as interrupts are disabled while (tx_buffer.head != tx_buffer.tail || !B_TXC) { @@ -556,161 +555,6 @@ void MarlinSerial::flushTX() { } } -/** - * Imports from print.h - */ - -template -void MarlinSerial::print(char c, int base) { - print((long)c, base); -} - -template -void MarlinSerial::print(unsigned char b, int base) { - print((unsigned long)b, base); -} - -template -void MarlinSerial::print(int n, int base) { - print((long)n, base); -} - -template -void MarlinSerial::print(unsigned int n, int base) { - print((unsigned long)n, base); -} - -template -void MarlinSerial::print(long n, int base) { - if (base == 0) write(n); - else if (base == 10) { - if (n < 0) { print('-'); n = -n; } - printNumber(n, 10); - } - else - printNumber(n, base); -} - -template -void MarlinSerial::print(unsigned long n, int base) { - if (base == 0) write(n); - else printNumber(n, base); -} - -template -void MarlinSerial::print(double n, int digits) { - printFloat(n, digits); -} - -template -void MarlinSerial::println() { - print('\r'); - print('\n'); -} - -template -void MarlinSerial::println(const String& s) { - print(s); - println(); -} - -template -void MarlinSerial::println(const char c[]) { - print(c); - println(); -} - -template -void MarlinSerial::println(char c, int base) { - print(c, base); - println(); -} - -template -void MarlinSerial::println(unsigned char b, int base) { - print(b, base); - println(); -} - -template -void MarlinSerial::println(int n, int base) { - print(n, base); - println(); -} - -template -void MarlinSerial::println(unsigned int n, int base) { - print(n, base); - println(); -} - -template -void MarlinSerial::println(long n, int base) { - print(n, base); - println(); -} - -template -void MarlinSerial::println(unsigned long n, int base) { - print(n, base); - println(); -} - -template -void MarlinSerial::println(double n, int digits) { - print(n, digits); - println(); -} - -// Private Methods - -template -void MarlinSerial::printNumber(unsigned long n, uint8_t base) { - if (n) { - unsigned char buf[8 * sizeof(long)]; // Enough space for base 2 - int8_t i = 0; - while (n) { - buf[i++] = n % base; - n /= base; - } - while (i--) - print((char)(buf[i] + (buf[i] < 10 ? '0' : 'A' - 10))); - } - else - print('0'); -} - -template -void MarlinSerial::printFloat(double number, uint8_t digits) { - // Handle negative numbers - if (number < 0.0) { - print('-'); - number = -number; - } - - // Round correctly so that print(1.999, 2) prints as "2.00" - double rounding = 0.5; - LOOP_L_N(i, digits) rounding *= 0.1; - number += rounding; - - // Extract the integer part of the number and print it - unsigned long int_part = (unsigned long)number; - double remainder = number - (double)int_part; - print(int_part); - - // Print the decimal point, but only if there are digits beyond - if (digits) { - print('.'); - // Extract digits from the remainder one at a time - while (digits--) { - remainder *= 10.0; - int toPrint = int(remainder); - print(toPrint); - remainder -= toPrint; - } - } -} - // Hookup ISR handlers ISR(SERIAL_REGNAME(USART, SERIAL_PORT, _RX_vect)) { MarlinSerial>::store_rxd_char(); @@ -720,11 +564,9 @@ ISR(SERIAL_REGNAME(USART, SERIAL_PORT, _UDRE_vect)) { MarlinSerial>::_tx_udr_empty_irq(); } -// Preinstantiate -template class MarlinSerial>; - -// Instantiate -MarlinSerial> customizedSerial1; +// Because of the template definition above, it's required to instantiate the template to have all methods generated +template class MarlinSerial< MarlinSerialCfg >; +MSerialT1 customizedSerial1(MSerialT1::HasEmergencyParser); #ifdef SERIAL_PORT_2 @@ -737,31 +579,41 @@ MarlinSerial> customizedSerial1; MarlinSerial>::_tx_udr_empty_irq(); } - // Preinstantiate - template class MarlinSerial>; + template class MarlinSerial< MarlinSerialCfg >; + MSerialT2 customizedSerial2(MSerialT2::HasEmergencyParser); - // Instantiate - MarlinSerial> customizedSerial2; +#endif // SERIAL_PORT_2 -#endif +#ifdef SERIAL_PORT_3 -#ifdef MMU2_SERIAL_PORT - - ISR(SERIAL_REGNAME(USART, MMU2_SERIAL_PORT, _RX_vect)) { - MarlinSerial>::store_rxd_char(); + // Hookup ISR handlers + ISR(SERIAL_REGNAME(USART, SERIAL_PORT_3, _RX_vect)) { + MarlinSerial>::store_rxd_char(); } - ISR(SERIAL_REGNAME(USART, MMU2_SERIAL_PORT, _UDRE_vect)) { - MarlinSerial>::_tx_udr_empty_irq(); + ISR(SERIAL_REGNAME(USART, SERIAL_PORT_3, _UDRE_vect)) { + MarlinSerial>::_tx_udr_empty_irq(); } - // Preinstantiate - template class MarlinSerial>; + template class MarlinSerial< MarlinSerialCfg >; + MSerialT3 customizedSerial3(MSerialT3::HasEmergencyParser); - // Instantiate - MarlinSerial> mmuSerial; +#endif // SERIAL_PORT_3 -#endif +#ifdef MMU_SERIAL_PORT + + ISR(SERIAL_REGNAME(USART, MMU_SERIAL_PORT, _RX_vect)) { + MarlinSerial>::store_rxd_char(); + } + + ISR(SERIAL_REGNAME(USART, MMU_SERIAL_PORT, _UDRE_vect)) { + MarlinSerial>::_tx_udr_empty_irq(); + } + + template class MarlinSerial< MMU2SerialCfg >; + MSerialMMU2 mmuSerial(MSerialMMU2::HasEmergencyParser); + +#endif // MMU_SERIAL_PORT #ifdef LCD_SERIAL_PORT @@ -773,13 +625,10 @@ MarlinSerial> customizedSerial1; MarlinSerial>::_tx_udr_empty_irq(); } - // Preinstantiate - template class MarlinSerial>; + template class MarlinSerial< LCDSerialCfg >; + MSerialLCD lcdSerial(MSerialLCD::HasEmergencyParser); - // Instantiate - MarlinSerial> lcdSerial; - - #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. @@ -790,13 +639,13 @@ MarlinSerial> customizedSerial1; } #endif -#endif +#endif // LCD_SERIAL_PORT -#endif // !IS_AT90USB && (UBRRH || UBRR0H || UBRR1H || UBRR2H || UBRR3H) +#endif // !USBCON && (UBRRH || UBRR0H || UBRR1H || UBRR2H || UBRR3H) // For AT90USB targets use the UART for BT interfacing -#if BOTH(IS_AT90USB, BLUETOOTH) - HardwareSerial bluetoothSerial; +#if defined(USBCON) && ENABLED(BLUETOOTH) + MSerialBT bluetoothSerial(false); #endif #endif // __AVR__ diff --git a/Marlin/src/HAL/AVR/MarlinSerial.h b/Marlin/src/HAL/AVR/MarlinSerial.h index 3850e2a47e..9ecf3bde22 100644 --- a/Marlin/src/HAL/AVR/MarlinSerial.h +++ b/Marlin/src/HAL/AVR/MarlinSerial.h @@ -34,10 +34,8 @@ #include #include "../../inc/MarlinConfigPre.h" - -#ifndef SERIAL_PORT - #define SERIAL_PORT 0 -#endif +#include "../../core/types.h" +#include "../../core/serial_hook.h" #ifndef USBCON @@ -135,16 +133,8 @@ UART_DECL(3); #endif - #define DEC 10 - #define HEX 16 - #define OCT 8 - #define BIN 2 #define BYTE 0 - // Templated type selector - template struct TypeSelector { typedef T type;} ; - template struct TypeSelector { typedef F type; }; - template class MarlinSerial { protected: @@ -167,7 +157,7 @@ static constexpr B_U2Xx B_U2X = 0; // Base size of type on buffer size - typedef typename TypeSelector<(Cfg::RX_SIZE>256), uint16_t, uint8_t>::type ring_buffer_pos_t; + typedef uvalue_t(Cfg::RX_SIZE - 1) ring_buffer_pos_t; struct ring_buffer_r { volatile ring_buffer_pos_t head, tail; @@ -194,68 +184,38 @@ rx_framing_errors; static ring_buffer_pos_t rx_max_enqueued; - static FORCE_INLINE ring_buffer_pos_t atomic_read_rx_head(); + FORCE_INLINE static ring_buffer_pos_t atomic_read_rx_head(); static volatile bool rx_tail_value_not_stable; static volatile uint16_t rx_tail_value_backup; - static FORCE_INLINE void atomic_set_rx_tail(ring_buffer_pos_t value); - static FORCE_INLINE ring_buffer_pos_t atomic_read_rx_tail(); - - public: + FORCE_INLINE static void atomic_set_rx_tail(ring_buffer_pos_t value); + FORCE_INLINE static ring_buffer_pos_t atomic_read_rx_tail(); + public: FORCE_INLINE static void store_rxd_char(); FORCE_INLINE static void _tx_udr_empty_irq(); - public: - MarlinSerial() {}; - static void begin(const long); - static void end(); - static int peek(); - static int read(); - static void flush(); - static ring_buffer_pos_t available(); - static void write(const uint8_t c); - static void flushTX(); - #if HAS_DGUS_LCD - static ring_buffer_pos_t get_tx_buffer_free(); - #endif + public: + static void begin(const long); + static void end(); + static int peek(); + static int read(); + static void flush(); + static ring_buffer_pos_t available(); + static void write(const uint8_t c); + static void flushTX(); + #if ANY(HAS_DGUS_LCD, EXTENSIBLE_UI) + static ring_buffer_pos_t get_tx_buffer_free(); + #endif - static inline bool emergency_parser_enabled() { return Cfg::EMERGENCYPARSER; } + enum { HasEmergencyParser = Cfg::EMERGENCYPARSER }; + static bool emergency_parser_enabled() { return Cfg::EMERGENCYPARSER; } - FORCE_INLINE static uint8_t dropped() { return Cfg::DROPPED_RX ? rx_dropped_bytes : 0; } - FORCE_INLINE static uint8_t buffer_overruns() { return Cfg::RX_OVERRUNS ? rx_buffer_overruns : 0; } - FORCE_INLINE static uint8_t framing_errors() { return Cfg::RX_FRAMING_ERRORS ? rx_framing_errors : 0; } - FORCE_INLINE static ring_buffer_pos_t rxMaxEnqueued() { return Cfg::MAX_RX_QUEUED ? rx_max_enqueued : 0; } - - FORCE_INLINE static void write(const char* str) { while (*str) write(*str++); } - FORCE_INLINE static void write(const uint8_t* buffer, size_t size) { while (size--) write(*buffer++); } - FORCE_INLINE static void print(const String& s) { for (int i = 0; i < (int)s.length(); i++) write(s[i]); } - FORCE_INLINE static void print(const char* str) { write(str); } - - static void print(char, int = BYTE); - static void print(unsigned char, int = BYTE); - static void print(int, int = DEC); - static void print(unsigned int, int = DEC); - static void print(long, int = DEC); - static void print(unsigned long, int = DEC); - static void print(double, int = 2); - - static void println(const String& s); - static void println(const char[]); - static void println(char, int = BYTE); - static void println(unsigned char, int = BYTE); - static void println(int, int = DEC); - static void println(unsigned int, int = DEC); - static void println(long, int = DEC); - static void println(unsigned long, int = DEC); - static void println(double, int = 2); - static void println(); - operator bool() { return true; } - - private: - static void printNumber(unsigned long, const uint8_t); - static void printFloat(double, uint8_t); + FORCE_INLINE static uint8_t dropped() { return Cfg::DROPPED_RX ? rx_dropped_bytes : 0; } + FORCE_INLINE static uint8_t buffer_overruns() { return Cfg::RX_OVERRUNS ? rx_buffer_overruns : 0; } + FORCE_INLINE static uint8_t framing_errors() { return Cfg::RX_FRAMING_ERRORS ? rx_framing_errors : 0; } + FORCE_INLINE static ring_buffer_pos_t rxMaxEnqueued() { return Cfg::MAX_RX_QUEUED ? rx_max_enqueued : 0; } }; template @@ -270,63 +230,61 @@ static constexpr bool RX_FRAMING_ERRORS = ENABLED(SERIAL_STATS_RX_FRAMING_ERRORS); static constexpr bool MAX_RX_QUEUED = ENABLED(SERIAL_STATS_MAX_RX_QUEUED); }; - extern MarlinSerial> customizedSerial1; + + typedef Serial1Class< MarlinSerial< MarlinSerialCfg > > MSerialT1; + extern MSerialT1 customizedSerial1; #ifdef SERIAL_PORT_2 + typedef Serial1Class< MarlinSerial< MarlinSerialCfg > > MSerialT2; + extern MSerialT2 customizedSerial2; + #endif - extern MarlinSerial> customizedSerial2; - + #ifdef SERIAL_PORT_3 + typedef Serial1Class< MarlinSerial< MarlinSerialCfg > > MSerialT3; + extern MSerialT3 customizedSerial3; #endif #endif // !USBCON -#ifdef MMU2_SERIAL_PORT +#ifdef MMU_SERIAL_PORT template struct MMU2SerialCfg { static constexpr int PORT = serial; + static constexpr unsigned int RX_SIZE = 32; + static constexpr unsigned int TX_SIZE = 32; static constexpr bool XONOFF = false; static constexpr bool EMERGENCYPARSER = false; static constexpr bool DROPPED_RX = false; static constexpr bool RX_FRAMING_ERRORS = false; static constexpr bool MAX_RX_QUEUED = false; - static constexpr unsigned int RX_SIZE = 32; - static constexpr unsigned int TX_SIZE = 32; static constexpr bool RX_OVERRUNS = false; }; - extern MarlinSerial> mmuSerial; + typedef Serial1Class< MarlinSerial< MMU2SerialCfg > > MSerialMMU2; + extern MSerialMMU2 mmuSerial; #endif #ifdef LCD_SERIAL_PORT template struct LCDSerialCfg { - static constexpr int PORT = serial; - static constexpr bool XONOFF = false; - static constexpr bool EMERGENCYPARSER = ENABLED(EMERGENCY_PARSER); - static constexpr bool DROPPED_RX = false; - static constexpr bool RX_FRAMING_ERRORS = false; - static constexpr bool MAX_RX_QUEUED = false; - #if HAS_DGUS_LCD - static constexpr unsigned int RX_SIZE = DGUS_RX_BUFFER_SIZE; - static constexpr unsigned int TX_SIZE = DGUS_TX_BUFFER_SIZE; - static constexpr bool RX_OVERRUNS = ENABLED(SERIAL_STATS_RX_BUFFER_OVERRUNS); - #elif EITHER(ANYCUBIC_LCD_I3MEGA, ANYCUBIC_LCD_CHIRON) - static constexpr unsigned int RX_SIZE = 64; - static constexpr unsigned int TX_SIZE = 128; - static constexpr bool RX_OVERRUNS = false; - #else - static constexpr unsigned int RX_SIZE = 64; - static constexpr unsigned int TX_SIZE = 128; - static constexpr bool RX_OVERRUNS = false - #endif + static constexpr int PORT = serial; + static constexpr unsigned int RX_SIZE = TERN(HAS_DGUS_LCD, DGUS_RX_BUFFER_SIZE, 64); + static constexpr unsigned int TX_SIZE = TERN(HAS_DGUS_LCD, DGUS_TX_BUFFER_SIZE, 128); + static constexpr bool XONOFF = false; + static constexpr bool EMERGENCYPARSER = ENABLED(EMERGENCY_PARSER); + static constexpr bool DROPPED_RX = false; + static constexpr bool RX_FRAMING_ERRORS = false; + static constexpr bool MAX_RX_QUEUED = false; + static constexpr bool RX_OVERRUNS = ALL(HAS_DGUS_LCD, SERIAL_STATS_RX_BUFFER_OVERRUNS); }; - extern MarlinSerial> lcdSerial; - + typedef Serial1Class< MarlinSerial< LCDSerialCfg > > MSerialLCD; + extern MSerialLCD lcdSerial; #endif // Use the UART for Bluetooth in AT90USB configurations -#if BOTH(IS_AT90USB, BLUETOOTH) - extern HardwareSerial bluetoothSerial; +#if defined(USBCON) && ENABLED(BLUETOOTH) + typedef Serial1Class MSerialBT; + extern MSerialBT bluetoothSerial; #endif diff --git a/Marlin/src/HAL/AVR/Servo.cpp b/Marlin/src/HAL/AVR/Servo.cpp index 526352b773..8ce9bc11b8 100644 --- a/Marlin/src/HAL/AVR/Servo.cpp +++ b/Marlin/src/HAL/AVR/Servo.cpp @@ -63,30 +63,28 @@ static volatile int8_t Channel[_Nbr_16timers]; // counter for the servo being pulsed for each timer (or -1 if refresh interval) - /************ static functions common to all instances ***********************/ -static inline void handle_interrupts(timer16_Sequence_t timer, volatile uint16_t* TCNTn, volatile uint16_t* OCRnA) { - if (Channel[timer] < 0) - *TCNTn = 0; // channel set to -1 indicated that refresh interval completed so reset the timer - else { - if (SERVO_INDEX(timer, Channel[timer]) < ServoCount && SERVO(timer, Channel[timer]).Pin.isActive) - extDigitalWrite(SERVO(timer, Channel[timer]).Pin.nbr, LOW); // pulse this channel low if activated - } +static inline void handle_interrupts(const timer16_Sequence_t timer, volatile uint16_t* TCNTn, volatile uint16_t* OCRnA) { + int8_t cho = Channel[timer]; // Handle the prior Channel[timer] first + if (cho < 0) // Channel -1 indicates the refresh interval completed... + *TCNTn = 0; // ...so reset the timer + else if (SERVO_INDEX(timer, cho) < ServoCount) // prior channel handled? + extDigitalWrite(SERVO(timer, cho).Pin.nbr, LOW); // pulse the prior channel LOW - Channel[timer]++; // increment to the next channel - if (SERVO_INDEX(timer, Channel[timer]) < ServoCount && Channel[timer] < SERVOS_PER_TIMER) { - *OCRnA = *TCNTn + SERVO(timer, Channel[timer]).ticks; - if (SERVO(timer, Channel[timer]).Pin.isActive) // check if activated - extDigitalWrite(SERVO(timer, Channel[timer]).Pin.nbr, HIGH); // it's an active channel so pulse it high + Channel[timer] = ++cho; // Handle the next channel (or 0) + if (cho < SERVOS_PER_TIMER && SERVO_INDEX(timer, cho) < ServoCount) { + *OCRnA = *TCNTn + SERVO(timer, cho).ticks; // set compare to current ticks plus duration + if (SERVO(timer, cho).Pin.isActive) // activated? + extDigitalWrite(SERVO(timer, cho).Pin.nbr, HIGH); // yes: pulse HIGH } else { // finished all channels so wait for the refresh period to expire before starting over - if (((unsigned)*TCNTn) + 4 < usToTicks(REFRESH_INTERVAL)) // allow a few ticks to ensure the next OCR1A not missed - *OCRnA = (unsigned int)usToTicks(REFRESH_INTERVAL); - else - *OCRnA = *TCNTn + 4; // at least REFRESH_INTERVAL has elapsed - Channel[timer] = -1; // this will get incremented at the end of the refresh period to start again at the first channel + const unsigned int cval = ((unsigned)*TCNTn) + 32 / (SERVO_TIMER_PRESCALER), // allow 32 cycles to ensure the next OCR1A not missed + ival = (unsigned int)usToTicks(REFRESH_INTERVAL); // at least REFRESH_INTERVAL has elapsed + *OCRnA = max(cval, ival); + + Channel[timer] = -1; // reset the timer counter to 0 on the next call } } @@ -123,91 +121,102 @@ static inline void handle_interrupts(timer16_Sequence_t timer, volatile uint16_t /****************** end of static functions ******************************/ -void initISR(timer16_Sequence_t timer) { - #ifdef _useTimer1 - if (timer == _timer1) { - TCCR1A = 0; // normal counting mode - TCCR1B = _BV(CS11); // set prescaler of 8 - TCNT1 = 0; // clear the timer count - #if defined(__AVR_ATmega8__) || defined(__AVR_ATmega128__) - SBI(TIFR, OCF1A); // clear any pending interrupts; - SBI(TIMSK, OCIE1A); // enable the output compare interrupt - #else - // here if not ATmega8 or ATmega128 - SBI(TIFR1, OCF1A); // clear any pending interrupts; - SBI(TIMSK1, OCIE1A); // enable the output compare interrupt - #endif - #ifdef WIRING - timerAttach(TIMER1OUTCOMPAREA_INT, Timer1Service); - #endif - } - #endif +void initISR(const timer16_Sequence_t timer_index) { + switch (timer_index) { + default: break; - #ifdef _useTimer3 - if (timer == _timer3) { - TCCR3A = 0; // normal counting mode - TCCR3B = _BV(CS31); // set prescaler of 8 - TCNT3 = 0; // clear the timer count - #ifdef __AVR_ATmega128__ - SBI(TIFR, OCF3A); // clear any pending interrupts; - SBI(ETIMSK, OCIE3A); // enable the output compare interrupt - #else - SBI(TIFR3, OCF3A); // clear any pending interrupts; - SBI(TIMSK3, OCIE3A); // enable the output compare interrupt - #endif - #ifdef WIRING - timerAttach(TIMER3OUTCOMPAREA_INT, Timer3Service); // for Wiring platform only - #endif - } - #endif + #ifdef _useTimer1 + case _timer1: + TCCR1A = 0; // normal counting mode + TCCR1B = _BV(CS11); // set prescaler of 8 + TCNT1 = 0; // clear the timer count + #if defined(__AVR_ATmega8__) || defined(__AVR_ATmega128__) + SBI(TIFR, OCF1A); // clear any pending interrupts; + SBI(TIMSK, OCIE1A); // enable the output compare interrupt + #else + // here if not ATmega8 or ATmega128 + SBI(TIFR1, OCF1A); // clear any pending interrupts; + SBI(TIMSK1, OCIE1A); // enable the output compare interrupt + #endif + #ifdef WIRING + timerAttach(TIMER1OUTCOMPAREA_INT, Timer1Service); + #endif + break; + #endif - #ifdef _useTimer4 - if (timer == _timer4) { - TCCR4A = 0; // normal counting mode - TCCR4B = _BV(CS41); // set prescaler of 8 - TCNT4 = 0; // clear the timer count - TIFR4 = _BV(OCF4A); // clear any pending interrupts; - TIMSK4 = _BV(OCIE4A); // enable the output compare interrupt - } - #endif + #ifdef _useTimer3 + case _timer3: + TCCR3A = 0; // normal counting mode + TCCR3B = _BV(CS31); // set prescaler of 8 + TCNT3 = 0; // clear the timer count + #ifdef __AVR_ATmega128__ + SBI(TIFR, OCF3A); // clear any pending interrupts; + SBI(ETIMSK, OCIE3A); // enable the output compare interrupt + #else + SBI(TIFR3, OCF3A); // clear any pending interrupts; + SBI(TIMSK3, OCIE3A); // enable the output compare interrupt + #endif + #ifdef WIRING + timerAttach(TIMER3OUTCOMPAREA_INT, Timer3Service); // for Wiring platform only + #endif + break; + #endif - #ifdef _useTimer5 - if (timer == _timer5) { - TCCR5A = 0; // normal counting mode - TCCR5B = _BV(CS51); // set prescaler of 8 - TCNT5 = 0; // clear the timer count - TIFR5 = _BV(OCF5A); // clear any pending interrupts; - TIMSK5 = _BV(OCIE5A); // enable the output compare interrupt - } - #endif + #ifdef _useTimer4 + case _timer4: + TCCR4A = 0; // normal counting mode + TCCR4B = _BV(CS41); // set prescaler of 8 + TCNT4 = 0; // clear the timer count + TIFR4 = _BV(OCF4A); // clear any pending interrupts; + TIMSK4 = _BV(OCIE4A); // enable the output compare interrupt + break; + #endif + + #ifdef _useTimer5 + case _timer5: + TCCR5A = 0; // normal counting mode + TCCR5B = _BV(CS51); // set prescaler of 8 + TCNT5 = 0; // clear the timer count + TIFR5 = _BV(OCF5A); // clear any pending interrupts; + TIMSK5 = _BV(OCIE5A); // enable the output compare interrupt + break; + #endif + } } -void finISR(timer16_Sequence_t timer) { +void finISR(const timer16_Sequence_t timer_index) { // Disable use of the given timer #ifdef WIRING - if (timer == _timer1) { - CBI( - #if defined(__AVR_ATmega1281__) || defined(__AVR_ATmega2561__) - TIMSK1 - #else - TIMSK - #endif - , OCIE1A); // disable timer 1 output compare interrupt - timerDetach(TIMER1OUTCOMPAREA_INT); - } - else if (timer == _timer3) { - CBI( - #if defined(__AVR_ATmega1281__) || defined(__AVR_ATmega2561__) - TIMSK3 - #else - ETIMSK - #endif - , OCIE3A); // disable the timer3 output compare A interrupt - timerDetach(TIMER3OUTCOMPAREA_INT); + switch (timer_index) { + default: break; + + case _timer1: + CBI( + #if defined(__AVR_ATmega1281__) || defined(__AVR_ATmega2561__) + TIMSK1 + #else + TIMSK + #endif + , OCIE1A // disable timer 1 output compare interrupt + ); + timerDetach(TIMER1OUTCOMPAREA_INT); + break; + + case _timer3: + CBI( + #if defined(__AVR_ATmega1281__) || defined(__AVR_ATmega2561__) + TIMSK3 + #else + ETIMSK + #endif + , OCIE3A // disable the timer3 output compare A interrupt + ); + timerDetach(TIMER3OUTCOMPAREA_INT); + break; } #else // !WIRING // For arduino - in future: call here to a currently undefined function to reset the timer - UNUSED(timer); + UNUSED(timer_index); #endif } diff --git a/Marlin/src/HAL/AVR/eeprom.cpp b/Marlin/src/HAL/AVR/eeprom.cpp index c7906985eb..45c7abd1db 100644 --- a/Marlin/src/HAL/AVR/eeprom.cpp +++ b/Marlin/src/HAL/AVR/eeprom.cpp @@ -23,7 +23,7 @@ #include "../../inc/MarlinConfig.h" -#if EITHER(EEPROM_SETTINGS, SD_FIRMWARE_UPDATE) +#if ANY(EEPROM_SETTINGS, SD_FIRMWARE_UPDATE) /** * PersistentStore for Arduino-style EEPROM interface @@ -35,18 +35,18 @@ #ifndef MARLIN_EEPROM_SIZE #define MARLIN_EEPROM_SIZE size_t(E2END + 1) #endif -size_t PersistentStore::capacity() { return MARLIN_EEPROM_SIZE; } +size_t PersistentStore::capacity() { return MARLIN_EEPROM_SIZE - eeprom_exclude_size; } bool PersistentStore::access_start() { 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 * const p = (uint8_t * const)pos; + uint8_t * const p = (uint8_t * const)REAL_EEPROM_ADDR(pos); uint8_t v = *value; - // EEPROM has only ~100,000 write cycles, - // so only write bytes that have changed! - if (v != eeprom_read_byte(p)) { + 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; @@ -59,9 +59,9 @@ bool PersistentStore::write_data(int &pos, const uint8_t *value, size_t size, ui return false; } -bool PersistentStore::read_data(int &pos, uint8_t* value, size_t size, uint16_t *crc, const bool writing/*=true*/) { +bool PersistentStore::read_data(int &pos, uint8_t *value, size_t size, uint16_t *crc, const bool writing/*=true*/) { do { - uint8_t c = eeprom_read_byte((uint8_t*)pos); + const uint8_t c = eeprom_read_byte((uint8_t*)REAL_EEPROM_ADDR(pos)); if (writing) *value = c; crc16(crc, &c, 1); pos++; diff --git a/Marlin/src/HAL/AVR/endstop_interrupts.h b/Marlin/src/HAL/AVR/endstop_interrupts.h index ae9a605acc..a6813ff277 100644 --- a/Marlin/src/HAL/AVR/endstop_interrupts.h +++ b/Marlin/src/HAL/AVR/endstop_interrupts.h @@ -91,7 +91,6 @@ void endstop_ISR() { endstops.update(); } #endif - // Install Pin change interrupt for a pin. Can be called multiple times. void pciSetup(const int8_t pin) { if (digitalPinHasPCICR(pin)) { @@ -120,142 +119,240 @@ void pciSetup(const int8_t pin) { void setup_endstop_interrupts() { #define _ATTACH(P) attachInterrupt(digitalPinToInterrupt(P), endstop_ISR, CHANGE) - #if HAS_X_MAX + #if USE_X_MAX #if (digitalPinToInterrupt(X_MAX_PIN) != NOT_AN_INTERRUPT) _ATTACH(X_MAX_PIN); #else - static_assert(digitalPinHasPCICR(X_MAX_PIN), "X_MAX_PIN is not interrupt-capable"); + static_assert(digitalPinHasPCICR(X_MAX_PIN), "X_MAX_PIN is not interrupt-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue."); pciSetup(X_MAX_PIN); #endif #endif - #if HAS_X_MIN + #if USE_X_MIN #if (digitalPinToInterrupt(X_MIN_PIN) != NOT_AN_INTERRUPT) _ATTACH(X_MIN_PIN); #else - static_assert(digitalPinHasPCICR(X_MIN_PIN), "X_MIN_PIN is not interrupt-capable"); + static_assert(digitalPinHasPCICR(X_MIN_PIN), "X_MIN_PIN is not interrupt-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue."); pciSetup(X_MIN_PIN); #endif #endif - #if HAS_Y_MAX + #if USE_Y_MAX #if (digitalPinToInterrupt(Y_MAX_PIN) != NOT_AN_INTERRUPT) _ATTACH(Y_MAX_PIN); #else - static_assert(digitalPinHasPCICR(Y_MAX_PIN), "Y_MAX_PIN is not interrupt-capable"); + static_assert(digitalPinHasPCICR(Y_MAX_PIN), "Y_MAX_PIN is not interrupt-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue."); pciSetup(Y_MAX_PIN); #endif #endif - #if HAS_Y_MIN + #if USE_Y_MIN #if (digitalPinToInterrupt(Y_MIN_PIN) != NOT_AN_INTERRUPT) _ATTACH(Y_MIN_PIN); #else - static_assert(digitalPinHasPCICR(Y_MIN_PIN), "Y_MIN_PIN is not interrupt-capable"); + static_assert(digitalPinHasPCICR(Y_MIN_PIN), "Y_MIN_PIN is not interrupt-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue."); pciSetup(Y_MIN_PIN); #endif #endif - #if HAS_Z_MAX + #if USE_Z_MAX #if (digitalPinToInterrupt(Z_MAX_PIN) != NOT_AN_INTERRUPT) _ATTACH(Z_MAX_PIN); #else - static_assert(digitalPinHasPCICR(Z_MAX_PIN), "Z_MAX_PIN is not interrupt-capable"); + static_assert(digitalPinHasPCICR(Z_MAX_PIN), "Z_MAX_PIN is not interrupt-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue."); pciSetup(Z_MAX_PIN); #endif #endif - #if HAS_Z_MIN + #if USE_Z_MIN #if (digitalPinToInterrupt(Z_MIN_PIN) != NOT_AN_INTERRUPT) _ATTACH(Z_MIN_PIN); #else - static_assert(digitalPinHasPCICR(Z_MIN_PIN), "Z_MIN_PIN is not interrupt-capable"); + static_assert(digitalPinHasPCICR(Z_MIN_PIN), "Z_MIN_PIN is not interrupt-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue."); pciSetup(Z_MIN_PIN); #endif #endif - #if HAS_X2_MAX + #if USE_I_MAX + #if (digitalPinToInterrupt(I_MAX_PIN) != NOT_AN_INTERRUPT) + _ATTACH(I_MAX_PIN); + #else + static_assert(digitalPinHasPCICR(I_MAX_PIN), "I_MAX_PIN is not interrupt-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue."); + pciSetup(I_MAX_PIN); + #endif + #elif USE_I_MIN + #if (digitalPinToInterrupt(I_MIN_PIN) != NOT_AN_INTERRUPT) + _ATTACH(I_MIN_PIN); + #else + static_assert(digitalPinHasPCICR(I_MIN_PIN), "I_MIN_PIN is not interrupt-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue."); + pciSetup(I_MIN_PIN); + #endif + #endif + #if USE_J_MAX + #if (digitalPinToInterrupt(J_MAX_PIN) != NOT_AN_INTERRUPT) + _ATTACH(J_MAX_PIN); + #else + static_assert(digitalPinHasPCICR(J_MAX_PIN), "J_MAX_PIN is not interrupt-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue."); + pciSetup(J_MAX_PIN); + #endif + #elif USE_J_MIN + #if (digitalPinToInterrupt(J_MIN_PIN) != NOT_AN_INTERRUPT) + _ATTACH(J_MIN_PIN); + #else + static_assert(digitalPinHasPCICR(J_MIN_PIN), "J_MIN_PIN is not interrupt-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue."); + pciSetup(J_MIN_PIN); + #endif + #endif + #if USE_K_MAX + #if (digitalPinToInterrupt(K_MAX_PIN) != NOT_AN_INTERRUPT) + _ATTACH(K_MAX_PIN); + #else + static_assert(digitalPinHasPCICR(K_MAX_PIN), "K_MAX_PIN is not interrupt-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue."); + pciSetup(K_MAX_PIN); + #endif + #elif USE_K_MIN + #if (digitalPinToInterrupt(K_MIN_PIN) != NOT_AN_INTERRUPT) + _ATTACH(K_MIN_PIN); + #else + static_assert(digitalPinHasPCICR(K_MIN_PIN), "K_MIN_PIN is not interrupt-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue."); + pciSetup(K_MIN_PIN); + #endif + #endif + #if USE_U_MAX + #if (digitalPinToInterrupt(U_MAX_PIN) != NOT_AN_INTERRUPT) + _ATTACH(U_MAX_PIN); + #else + static_assert(digitalPinHasPCICR(U_MAX_PIN), "U_MAX_PIN is not interrupt-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue."); + pciSetup(U_MAX_PIN); + #endif + #elif USE_U_MIN + #if (digitalPinToInterrupt(U_MIN_PIN) != NOT_AN_INTERRUPT) + _ATTACH(U_MIN_PIN); + #else + static_assert(digitalPinHasPCICR(U_MIN_PIN), "U_MIN_PIN is not interrupt-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue."); + pciSetup(U_MIN_PIN); + #endif + #endif + #if USE_V_MAX + #if (digitalPinToInterrupt(V_MAX_PIN) != NOT_AN_INTERRUPT) + _ATTACH(V_MAX_PIN); + #else + static_assert(digitalPinHasPCICR(V_MAX_PIN), "V_MAX_PIN is not interrupt-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue."); + pciSetup(V_MAX_PIN); + #endif + #elif USE_V_MIN + #if (digitalPinToInterrupt(V_MIN_PIN) != NOT_AN_INTERRUPT) + _ATTACH(V_MIN_PIN); + #else + static_assert(digitalPinHasPCICR(V_MIN_PIN), "V_MIN_PIN is not interrupt-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue."); + pciSetup(V_MIN_PIN); + #endif + #endif + #if USE_W_MAX + #if (digitalPinToInterrupt(W_MAX_PIN) != NOT_AN_INTERRUPT) + _ATTACH(W_MAX_PIN); + #else + static_assert(digitalPinHasPCICR(W_MAX_PIN), "W_MAX_PIN is not interrupt-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue."); + pciSetup(W_MAX_PIN); + #endif + #elif USE_W_MIN + #if (digitalPinToInterrupt(W_MIN_PIN) != NOT_AN_INTERRUPT) + _ATTACH(W_MIN_PIN); + #else + static_assert(digitalPinHasPCICR(W_MIN_PIN), "W_MIN_PIN is not interrupt-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue."); + pciSetup(W_MIN_PIN); + #endif + #endif + #if USE_X2_MAX #if (digitalPinToInterrupt(X2_MAX_PIN) != NOT_AN_INTERRUPT) _ATTACH(X2_MAX_PIN); #else - static_assert(digitalPinHasPCICR(X2_MAX_PIN), "X2_MAX_PIN is not interrupt-capable"); + static_assert(digitalPinHasPCICR(X2_MAX_PIN), "X2_MAX_PIN is not interrupt-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue."); pciSetup(X2_MAX_PIN); #endif #endif - #if HAS_X2_MIN + #if USE_X2_MIN #if (digitalPinToInterrupt(X2_MIN_PIN) != NOT_AN_INTERRUPT) _ATTACH(X2_MIN_PIN); #else - static_assert(digitalPinHasPCICR(X2_MIN_PIN), "X2_MIN_PIN is not interrupt-capable"); + static_assert(digitalPinHasPCICR(X2_MIN_PIN), "X2_MIN_PIN is not interrupt-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue."); pciSetup(X2_MIN_PIN); #endif #endif - #if HAS_Y2_MAX + #if USE_Y2_MAX #if (digitalPinToInterrupt(Y2_MAX_PIN) != NOT_AN_INTERRUPT) _ATTACH(Y2_MAX_PIN); #else - static_assert(digitalPinHasPCICR(Y2_MAX_PIN), "Y2_MAX_PIN is not interrupt-capable"); + static_assert(digitalPinHasPCICR(Y2_MAX_PIN), "Y2_MAX_PIN is not interrupt-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue."); pciSetup(Y2_MAX_PIN); #endif #endif - #if HAS_Y2_MIN + #if USE_Y2_MIN #if (digitalPinToInterrupt(Y2_MIN_PIN) != NOT_AN_INTERRUPT) _ATTACH(Y2_MIN_PIN); #else - static_assert(digitalPinHasPCICR(Y2_MIN_PIN), "Y2_MIN_PIN is not interrupt-capable"); + static_assert(digitalPinHasPCICR(Y2_MIN_PIN), "Y2_MIN_PIN is not interrupt-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue."); pciSetup(Y2_MIN_PIN); #endif #endif - #if HAS_Z2_MAX + #if USE_Z2_MAX #if (digitalPinToInterrupt(Z2_MAX_PIN) != NOT_AN_INTERRUPT) _ATTACH(Z2_MAX_PIN); #else - static_assert(digitalPinHasPCICR(Z2_MAX_PIN), "Z2_MAX_PIN is not interrupt-capable"); + static_assert(digitalPinHasPCICR(Z2_MAX_PIN), "Z2_MAX_PIN is not interrupt-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue."); pciSetup(Z2_MAX_PIN); #endif #endif - #if HAS_Z2_MIN + #if USE_Z2_MIN #if (digitalPinToInterrupt(Z2_MIN_PIN) != NOT_AN_INTERRUPT) _ATTACH(Z2_MIN_PIN); #else - static_assert(digitalPinHasPCICR(Z2_MIN_PIN), "Z2_MIN_PIN is not interrupt-capable"); + static_assert(digitalPinHasPCICR(Z2_MIN_PIN), "Z2_MIN_PIN is not interrupt-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue."); pciSetup(Z2_MIN_PIN); #endif #endif - #if HAS_Z3_MAX + #if USE_Z3_MAX #if (digitalPinToInterrupt(Z3_MAX_PIN) != NOT_AN_INTERRUPT) _ATTACH(Z3_MAX_PIN); #else - static_assert(digitalPinHasPCICR(Z3_MAX_PIN), "Z3_MAX_PIN is not interrupt-capable"); + static_assert(digitalPinHasPCICR(Z3_MAX_PIN), "Z3_MAX_PIN is not interrupt-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue."); pciSetup(Z3_MAX_PIN); #endif #endif - #if HAS_Z3_MIN + #if USE_Z3_MIN #if (digitalPinToInterrupt(Z3_MIN_PIN) != NOT_AN_INTERRUPT) _ATTACH(Z3_MIN_PIN); #else - static_assert(digitalPinHasPCICR(Z3_MIN_PIN), "Z3_MIN_PIN is not interrupt-capable"); + static_assert(digitalPinHasPCICR(Z3_MIN_PIN), "Z3_MIN_PIN is not interrupt-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue."); pciSetup(Z3_MIN_PIN); #endif #endif - #if HAS_Z4_MAX + #if USE_Z4_MAX #if (digitalPinToInterrupt(Z4_MAX_PIN) != NOT_AN_INTERRUPT) _ATTACH(Z4_MAX_PIN); #else - static_assert(digitalPinHasPCICR(Z4_MAX_PIN), "Z4_MAX_PIN is not interrupt-capable"); + static_assert(digitalPinHasPCICR(Z4_MAX_PIN), "Z4_MAX_PIN is not interrupt-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue."); pciSetup(Z4_MAX_PIN); #endif #endif - #if HAS_Z4_MIN + #if USE_Z4_MIN #if (digitalPinToInterrupt(Z4_MIN_PIN) != NOT_AN_INTERRUPT) _ATTACH(Z4_MIN_PIN); #else - static_assert(digitalPinHasPCICR(Z4_MIN_PIN), "Z4_MIN_PIN is not interrupt-capable"); + static_assert(digitalPinHasPCICR(Z4_MIN_PIN), "Z4_MIN_PIN is not interrupt-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue."); pciSetup(Z4_MIN_PIN); #endif #endif - #if HAS_Z_MIN_PROBE_PIN + #if USE_Z_MIN_PROBE #if (digitalPinToInterrupt(Z_MIN_PROBE_PIN) != NOT_AN_INTERRUPT) _ATTACH(Z_MIN_PROBE_PIN); #else - static_assert(digitalPinHasPCICR(Z_MIN_PROBE_PIN), "Z_MIN_PROBE_PIN is not interrupt-capable"); + static_assert(digitalPinHasPCICR(Z_MIN_PROBE_PIN), "Z_MIN_PROBE_PIN is not interrupt-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue."); 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 238c1124ad..936f9e5688 100644 --- a/Marlin/src/HAL/AVR/fast_pwm.cpp +++ b/Marlin/src/HAL/AVR/fast_pwm.cpp @@ -21,11 +21,11 @@ */ #ifdef __AVR__ -#include "../../inc/MarlinConfigPre.h" +#include "../../inc/MarlinConfig.h" -#if NEEDS_HARDWARE_PWM // Specific meta-flag for features that mandate PWM - -#include "HAL.h" +//#define DEBUG_AVR_FAST_PWM +#define DEBUG_OUT ENABLED(DEBUG_AVR_FAST_PWM) +#include "../../core/debug_out.h" struct Timer { volatile uint8_t* TCCRnQ[3]; // max 3 TCCR registers per timer @@ -33,250 +33,207 @@ struct Timer { volatile uint16_t* ICRn; // max 1 ICR register per timer uint8_t n; // the timer number [0->5] uint8_t q; // the timer output [0->2] (A->C) + bool isPWM; // True if pin is a "hardware timer" + bool isProtected; // True if timer is protected }; +// Macros for the Timer structure +#define _SET_WGMnQ(T, V) do{ \ + *(T.TCCRnQ)[0] = (*(T.TCCRnQ)[0] & ~(0x3 << 0)) | (( int(V) & 0x3) << 0); \ + *(T.TCCRnQ)[1] = (*(T.TCCRnQ)[1] & ~(0x3 << 3)) | (((int(V) >> 2) & 0x3) << 3); \ + }while(0) + +// Set TCCR CS bits +#define _SET_CSn(T, V) (*(T.TCCRnQ)[1] = (*(T.TCCRnQ[1]) & ~(0x7 << 0)) | ((int(V) & 0x7) << 0)) + +// Set TCCR COM bits +#define _SET_COMnQ(T, Q, V) (*(T.TCCRnQ)[0] = (*(T.TCCRnQ)[0] & ~(0x3 << (6-2*(Q)))) | (int(V) << (6-2*(Q)))) + +// Set OCRnQ register +#define _SET_OCRnQ(T, Q, V) (*(T.OCRnQ)[Q] = int(V) & 0xFFFF) + +// Set ICRn register (one per timer) +#define _SET_ICRn(T, V) (*(T.ICRn) = int(V) & 0xFFFF) + /** - * get_pwm_timer - * Get the timer information and register of the provided pin. - * Return a Timer struct containing this information. - * Used by set_pwm_frequency, set_pwm_duty + * Return a Timer struct describing a pin's timer. */ -Timer get_pwm_timer(const pin_t pin) { +const Timer get_pwm_timer(const pin_t pin) { + uint8_t q = 0; + switch (digitalPinToTimer(pin)) { - // Protect reserved timers (TIMER0 & TIMER1) #ifdef TCCR0A - #if !AVR_AT90USB1286_FAMILY - case TIMER0A: - #endif - case TIMER0B: + IF_DISABLED(AVR_AT90USB1286_FAMILY, case TIMER0A:) #endif #ifdef TCCR1A case TIMER1A: case TIMER1B: #endif - break; - #if defined(TCCR2) || defined(TCCR2A) - #ifdef TCCR2 - case TIMER2: { - Timer timer = { - /*TCCRnQ*/ { &TCCR2, nullptr, nullptr }, - /*OCRnQ*/ { (uint16_t*)&OCR2, nullptr, nullptr }, - /*ICRn*/ nullptr, - /*n, q*/ 2, 0 - }; - } - #elif defined(TCCR2A) - #if ENABLED(USE_OCR2A_AS_TOP) - case TIMER2A: break; // protect TIMER2A - case TIMER2B: { - Timer timer = { - /*TCCRnQ*/ { &TCCR2A, &TCCR2B, nullptr }, - /*OCRnQ*/ { (uint16_t*)&OCR2A, (uint16_t*)&OCR2B, nullptr }, - /*ICRn*/ nullptr, - /*n, q*/ 2, 1 - }; - return timer; - } - #else - case TIMER2B: ++q; - case TIMER2A: { - Timer timer = { - /*TCCRnQ*/ { &TCCR2A, &TCCR2B, nullptr }, - /*OCRnQ*/ { (uint16_t*)&OCR2A, (uint16_t*)&OCR2B, nullptr }, - /*ICRn*/ nullptr, - 2, q - }; - return timer; - } - #endif - #endif + + break; // Protect reserved timers (TIMER0 & TIMER1) + + #ifdef TCCR0A + case TIMER0B: // Protected timer, but allow setting the duty cycle on OCR0B for pin D4 only + return Timer({ { &TCCR0A, nullptr, nullptr }, { (uint16_t*)&OCR0A, (uint16_t*)&OCR0B, nullptr }, nullptr, 0, 1, true, true }); #endif + + #if HAS_TCCR2 + case TIMER2: + return Timer({ { &TCCR2, nullptr, nullptr }, { (uint16_t*)&OCR2, nullptr, nullptr }, nullptr, 2, 0, true, false }); + #elif ENABLED(USE_OCR2A_AS_TOP) + case TIMER2A: break; // Protect TIMER2A since its OCR is used by TIMER2B + case TIMER2B: + return Timer({ { &TCCR2A, &TCCR2B, nullptr }, { (uint16_t*)&OCR2A, (uint16_t*)&OCR2B, nullptr }, nullptr, 2, 1, true, false }); + #elif defined(TCCR2A) + case TIMER2B: ++q; case TIMER2A: + return Timer({ { &TCCR2A, &TCCR2B, nullptr }, { (uint16_t*)&OCR2A, (uint16_t*)&OCR2B, nullptr }, nullptr, 2, q, true, false }); + #endif + #ifdef OCR3C - case TIMER3C: ++q; - case TIMER3B: ++q; - case TIMER3A: { - Timer timer = { - /*TCCRnQ*/ { &TCCR3A, &TCCR3B, &TCCR3C }, - /*OCRnQ*/ { &OCR3A, &OCR3B, &OCR3C }, - /*ICRn*/ &ICR3, - /*n, q*/ 3, q - }; - return timer; - } + case TIMER3C: ++q; case TIMER3B: ++q; case TIMER3A: + return Timer({ { &TCCR3A, &TCCR3B, &TCCR3C }, { &OCR3A, &OCR3B, &OCR3C }, &ICR3, 3, q, true, false }); #elif defined(OCR3B) - case TIMER3B: ++q; - case TIMER3A: { - Timer timer = { - /*TCCRnQ*/ { &TCCR3A, &TCCR3B, nullptr }, - /*OCRnQ*/ { &OCR3A, &OCR3B, nullptr }, - /*ICRn*/ &ICR3, - /*n, q*/ 3, q - }; - return timer; - } + case TIMER3B: ++q; case TIMER3A: + return Timer({ { &TCCR3A, &TCCR3B, nullptr }, { &OCR3A, &OCR3B, nullptr }, &ICR3, 3, q, true, false }); #endif + #ifdef TCCR4A - case TIMER4C: ++q; - case TIMER4B: ++q; - case TIMER4A: { - Timer timer = { - /*TCCRnQ*/ { &TCCR4A, &TCCR4B, &TCCR4C }, - /*OCRnQ*/ { &OCR4A, &OCR4B, &OCR4C }, - /*ICRn*/ &ICR4, - /*n, q*/ 4, q - }; - return timer; - } + case TIMER4C: ++q; case TIMER4B: ++q; case TIMER4A: + return Timer({ { &TCCR4A, &TCCR4B, &TCCR4C }, { &OCR4A, &OCR4B, &OCR4C }, &ICR4, 4, q, true, false }); #endif + #ifdef TCCR5A - case TIMER5C: ++q; - case TIMER5B: ++q; - case TIMER5A: { - Timer timer = { - /*TCCRnQ*/ { &TCCR5A, &TCCR5B, &TCCR5C }, - /*OCRnQ*/ { &OCR5A, &OCR5B, &OCR5C }, - /*ICRn*/ &ICR5, - /*n, q*/ 5, q - }; - return timer; - } + case TIMER5C: ++q; case TIMER5B: ++q; case TIMER5A: + return Timer({ { &TCCR5A, &TCCR5B, &TCCR5C }, { &OCR5A, &OCR5B, &OCR5C }, &ICR5, 5, q, true, false }); #endif } - Timer timer = { - /*TCCRnQ*/ { nullptr, nullptr, nullptr }, - /*OCRnQ*/ { nullptr, nullptr, nullptr }, - /*ICRn*/ nullptr, - 0, 0 - }; - return timer; + + return Timer(); } -void set_pwm_frequency(const pin_t pin, int f_desired) { - Timer timer = get_pwm_timer(pin); - if (timer.n == 0) return; // Don't proceed if protected timer or not recognised - uint16_t size; - if (timer.n == 2) size = 255; else size = 65535; +void MarlinHAL::set_pwm_frequency(const pin_t pin, const uint16_t f_desired) { + DEBUG_ECHOLNPGM("set_pwm_frequency(pin=", pin, ", freq=", f_desired, ")"); + const Timer timer = get_pwm_timer(pin); + if (timer.isProtected || !timer.isPWM) return; // Don't proceed if protected timer or not recognized - uint16_t res = 255; // resolution (TOP value) - uint8_t j = 0; // prescaler index - uint8_t wgm = 1; // waveform generation mode + const bool is_timer2 = timer.n == 2; + const uint16_t maxtop = is_timer2 ? 0xFF : 0xFFFF; + + DEBUG_ECHOLNPGM("maxtop=", maxtop); + + uint16_t res = 0xFF; // resolution (TOP value) + uint8_t j = CS_NONE; // prescaler index + uint8_t wgm = WGM_PWM_PC_8; // waveform generation mode // Calculating the prescaler and resolution to use to achieve closest frequency if (f_desired != 0) { - int f = (F_CPU) / (2 * 1024 * size) + 1; // Initialize frequency as lowest (non-zero) achievable - uint16_t prescaler[] = { 0, 1, 8, /*TIMER2 ONLY*/32, 64, /*TIMER2 ONLY*/128, 256, 1024 }; + constexpr uint16_t prescaler[] = { 1, 8, (32), 64, (128), 256, 1024 }; // (*) are Timer 2 only + uint16_t f = (F_CPU) / (uint32_t(maxtop) << 11) + 1; // Start with the lowest non-zero frequency achievable (for 16MHz, 1 or 31) - // loop over prescaler values - LOOP_S_L_N(i, 1, 8) { - uint16_t res_temp_fast = 255, res_temp_phase_correct = 255; - if (timer.n == 2) { - // No resolution calculation for TIMER2 unless enabled USE_OCR2A_AS_TOP - #if ENABLED(USE_OCR2A_AS_TOP) - const uint16_t rtf = (F_CPU) / (prescaler[i] * f_desired); - res_temp_fast = rtf - 1; - res_temp_phase_correct = rtf / 2; + DEBUG_ECHOLNPGM("f=", f); + DEBUG_ECHOLNPGM("(prescaler loop)"); + for (uint8_t i = 0; i < COUNT(prescaler); ++i) { // Loop through all prescaler values + const uint32_t p = prescaler[i]; // Extend to 32 bits for calculations + DEBUG_ECHOLNPGM("prescaler[", i, "]=", p); + uint16_t res_fast_temp, res_pc_temp; + if (is_timer2) { + #if ENABLED(USE_OCR2A_AS_TOP) // No resolution calculation for TIMER2 unless enabled USE_OCR2A_AS_TOP + const uint16_t rft = (F_CPU) / (p * f_desired); + res_fast_temp = rft - 1; + res_pc_temp = rft / 2; + DEBUG_ECHOLNPGM("(Timer2) res_fast_temp=", res_fast_temp, " res_pc_temp=", res_pc_temp); + #else + res_fast_temp = res_pc_temp = maxtop; + DEBUG_ECHOLNPGM("(Timer2) res_fast_temp=", maxtop, " res_pc_temp=", maxtop); #endif } else { - // Skip TIMER2 specific prescalers when not TIMER2 - if (i == 3 || i == 5) continue; - const uint16_t rtf = (F_CPU) / (prescaler[i] * f_desired); - res_temp_fast = rtf - 1; - res_temp_phase_correct = rtf / 2; + 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); + res_fast_temp = rft - 1; + res_pc_temp = rft / 2; } - LIMIT(res_temp_fast, 1U, size); - LIMIT(res_temp_phase_correct, 1U, size); + LIMIT(res_fast_temp, 1U, maxtop); + LIMIT(res_pc_temp, 1U, maxtop); + // Calculate frequencies of test prescaler and resolution values - const int f_temp_fast = (F_CPU) / (prescaler[i] * (1 + res_temp_fast)), - f_temp_phase_correct = (F_CPU) / (2 * prescaler[i] * res_temp_phase_correct), - f_diff = ABS(f - f_desired), - f_fast_diff = ABS(f_temp_fast - f_desired), - f_phase_diff = ABS(f_temp_phase_correct - f_desired); + const uint16_t f_fast_temp = (F_CPU) / (p * (1 + res_fast_temp)), + f_pc_temp = (F_CPU) / ((p * res_pc_temp) << 1), + f_diff = _MAX(f, f_desired) - _MIN(f, f_desired), + f_fast_diff = _MAX(f_fast_temp, f_desired) - _MIN(f_fast_temp, f_desired), + f_pc_diff = _MAX(f_pc_temp, f_desired) - _MIN(f_pc_temp, f_desired); - // If FAST values are closest to desired f - if (f_fast_diff < f_diff && f_fast_diff <= f_phase_diff) { - // Remember this combination - f = f_temp_fast; - res = res_temp_fast; - j = i; + DEBUG_ECHOLNPGM("f_fast_temp=", f_fast_temp, " f_pc_temp=", f_pc_temp, " f_diff=", f_diff, " f_fast_diff=", f_fast_diff, " f_pc_diff=", f_pc_diff); + + if (f_fast_diff < f_diff && f_fast_diff <= f_pc_diff) { // FAST values are closest to desired f // Set the Wave Generation Mode to FAST PWM - if (timer.n == 2) { - wgm = ( - #if ENABLED(USE_OCR2A_AS_TOP) - WGM2_FAST_PWM_OCR2A - #else - WGM2_FAST_PWM - #endif - ); - } - else wgm = WGM_FAST_PWM_ICRn; + wgm = is_timer2 ? uint8_t(TERN(USE_OCR2A_AS_TOP, WGM2_FAST_PWM_OCR2A, WGM2_FAST_PWM)) : uint8_t(WGM_FAST_PWM_ICRn); + // Remember this combination + f = f_fast_temp; res = res_fast_temp; j = i + 1; + DEBUG_ECHOLNPGM("(FAST) updated f=", f); } - // If PHASE CORRECT values are closes to desired f - else if (f_phase_diff < f_diff) { - f = f_temp_phase_correct; - res = res_temp_phase_correct; - j = i; + else if (f_pc_diff < f_diff) { // PHASE CORRECT values are closes to desired f // Set the Wave Generation Mode to PWM PHASE CORRECT - if (timer.n == 2) { - wgm = ( - #if ENABLED(USE_OCR2A_AS_TOP) - WGM2_PWM_PC_OCR2A - #else - WGM2_PWM_PC - #endif - ); - } - else wgm = WGM_PWM_PC_ICRn; + wgm = is_timer2 ? uint8_t(TERN(USE_OCR2A_AS_TOP, WGM2_PWM_PC_OCR2A, WGM2_PWM_PC)) : uint8_t(WGM_PWM_PC_ICRn); + f = f_pc_temp; res = res_pc_temp; j = i + 1; + DEBUG_ECHOLNPGM("(PHASE) updated f=", f); } - } + } // prescaler loop } - _SET_WGMnQ(timer.TCCRnQ, wgm); - _SET_CSn(timer.TCCRnQ, j); - if (timer.n == 2) { - #if ENABLED(USE_OCR2A_AS_TOP) - _SET_OCRnQ(timer.OCRnQ, 0, res); // Set OCR2A value (TOP) = res - #endif + _SET_WGMnQ(timer, wgm); + _SET_CSn(timer, j); + + if (is_timer2) { + TERN_(USE_OCR2A_AS_TOP, _SET_OCRnQ(timer, 0, res)); // Set OCR2A value (TOP) = res } else - _SET_ICRn(timer.ICRn, res); // Set ICRn value (TOP) = res + _SET_ICRn(timer, res); // Set ICRn value (TOP) = res } -void set_pwm_duty(const pin_t pin, const uint16_t v, const uint16_t v_size/*=255*/, const bool invert/*=false*/) { +void MarlinHAL::set_pwm_duty(const pin_t pin, const uint16_t v, const uint16_t v_size/*=255*/, const bool invert/*=false*/) { // If v is 0 or v_size (max), digitalWrite to LOW or HIGH. - // Note that digitalWrite also disables pwm output for us (sets COM bit to 0) + // Note that digitalWrite also disables PWM output for us (sets COM bit to 0) if (v == 0) digitalWrite(pin, invert); else if (v == v_size) digitalWrite(pin, !invert); else { - Timer timer = get_pwm_timer(pin); - if (timer.n == 0) return; // Don't proceed if protected timer or not recognised - // Set compare output mode to CLEAR -> SET or SET -> CLEAR (if inverted) - _SET_COMnQ(timer.TCCRnQ, (timer.q - #ifdef TCCR2 - + (timer.q == 2) // COM20 is on bit 4 of TCCR2, thus requires q + 1 in the macro - #endif - ), COM_CLEAR_SET + invert - ); - - uint16_t top; - if (timer.n == 2) { // if TIMER2 - top = ( - #if ENABLED(USE_OCR2A_AS_TOP) - *timer.OCRnQ[0] // top = OCR2A - #else - 255 // top = 0xFF (max) - #endif - ); + const Timer timer = get_pwm_timer(pin); + if (timer.isPWM) { + if (timer.n == 0) { + _SET_COMnQ(timer, timer.q, COM_CLEAR_SET); // Only allow a TIMER0B select... + _SET_OCRnQ(timer, timer.q, v); // ...and OCR0B duty update. For output pin D4 no frequency changes are permitted. + } + else if (!timer.isProtected) { + const uint16_t top = timer.n == 2 ? TERN(USE_OCR2A_AS_TOP, *timer.OCRnQ[0], 255) : *timer.ICRn; + _SET_COMnQ(timer, SUM_TERN(HAS_TCCR2, timer.q, timer.q == 2), COM_CLEAR_SET + invert); // COM20 is on bit 4 of TCCR2, so +1 for q==2 + _SET_OCRnQ(timer, timer.q, uint16_t(uint32_t(v) * top / v_size)); // Scale 8/16-bit v to top value + } } else - top = *timer.ICRn; // top = ICRn - - _SET_OCRnQ(timer.OCRnQ, timer.q, v * float(top) / float(v_size)); // Scale 8/16-bit v to top value + digitalWrite(pin, v < v_size / 2 ? LOW : HIGH); } } -#endif // NEEDS_HARDWARE_PWM +void MarlinHAL::init_pwm_timers() { + // Init some timer frequencies to a default 1KHz + const pin_t pwm_pin[] = { + #ifdef __AVR_ATmega2560__ + 10, 5, 6, 46 + #elif defined(__AVR_ATmega1280__) + 12, 31 + #elif defined(__AVR_ATmega644__) || defined(__AVR_ATmega1284__) + 15, 6 + #elif defined(__AVR_AT90USB1286__) || defined(__AVR_mega64) || defined(__AVR_mega128) + 16, 24 + #endif + }; + + for (uint8_t i = 0; i < COUNT(pwm_pin); ++i) + set_pwm_frequency(pwm_pin[i], 1000); +} + #endif // __AVR__ diff --git a/Marlin/src/HAL/AVR/fastio.cpp b/Marlin/src/HAL/AVR/fastio.cpp index b51d7f9768..98fd636ebf 100644 --- a/Marlin/src/HAL/AVR/fastio.cpp +++ b/Marlin/src/HAL/AVR/fastio.cpp @@ -241,11 +241,11 @@ 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 &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. - uint16_t prescaler; // Range of 30.5Hz (65535) 64.5KHz (>31) + uint16_t prescaler; // Range of 30.5Hz (65535) 64.5kHz (>31) if (count >= 255. * 256.) { prescaler = 1024; SET_CS(5, PRESCALER_1024); } else if (count >= 255. * 64.) { prescaler = 256; SET_CS(5, PRESCALER_256); } @@ -254,10 +254,10 @@ uint16_t set_pwm_frequency_hz(const float &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 + OCR5A = pwm_top * ABS(dca); // Update and scale DCs OCR5B = pwm_top * ABS(dcb); OCR5C = pwm_top * ABS(dcc); _SET_COM(5, A, dca ? (dca < 0 ? COM_SET_CLEAR : COM_CLEAR_SET) : COM_NORMAL); // Set compare modes @@ -267,20 +267,20 @@ uint16_t set_pwm_frequency_hz(const float &hz, const float dca, const float dcb, SET_WGM(5, FAST_PWM_ICRn); // Fast PWM with ICR5 as TOP //SERIAL_ECHOLNPGM("Timer 5 Settings:"); - //SERIAL_ECHOLNPAIR(" Prescaler=", prescaler); - //SERIAL_ECHOLNPAIR(" TOP=", ICR5); - //SERIAL_ECHOLNPAIR(" OCR5A=", OCR5A); - //SERIAL_ECHOLNPAIR(" OCR5B=", OCR5B); - //SERIAL_ECHOLNPAIR(" OCR5C=", OCR5C); + //SERIAL_ECHOLNPGM(" Prescaler=", prescaler); + //SERIAL_ECHOLNPGM(" TOP=", ICR5); + //SERIAL_ECHOLNPGM(" OCR5A=", OCR5A); + //SERIAL_ECHOLNPGM(" OCR5B=", OCR5B); + //SERIAL_ECHOLNPGM(" OCR5C=", OCR5C); } else { // Restore the default for Timer 5 SET_WGM(5, PWM_PC_8); // PWM 8-bit (Phase Correct) SET_COMS(5, NORMAL, NORMAL, NORMAL); // Do nothing - SET_CS(5, PRESCALER_64); // 16MHz / 64 = 250KHz + 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.h b/Marlin/src/HAL/AVR/fastio.h index dd01634661..4516d9cd54 100644 --- a/Marlin/src/HAL/AVR/fastio.h +++ b/Marlin/src/HAL/AVR/fastio.h @@ -118,7 +118,7 @@ */ // Waveform Generation Modes -enum WaveGenMode : char { +enum WaveGenMode : uint8_t { WGM_NORMAL, // 0 WGM_PWM_PC_8, // 1 WGM_PWM_PC_9, // 2 @@ -138,19 +138,19 @@ enum WaveGenMode : char { }; // Wavefore Generation Modes (Timer 2 only) -enum WaveGenMode2 : char { - WGM2_NORMAL, // 0 - WGM2_PWM_PC, // 1 - WGM2_CTC_OCR2A, // 2 - WGM2_FAST_PWM, // 3 - WGM2_reserved_1, // 4 - WGM2_PWM_PC_OCR2A, // 5 - WGM2_reserved_2, // 6 - WGM2_FAST_PWM_OCR2A, // 7 +enum WaveGenMode2 : uint8_t { + WGM2_NORMAL, // 0 + WGM2_PWM_PC, // 1 + WGM2_CTC_OCR2A, // 2 + WGM2_FAST_PWM, // 3 + WGM2_reserved_1, // 4 + WGM2_PWM_PC_OCR2A, // 5 + WGM2_reserved_2, // 6 + WGM2_FAST_PWM_OCR2A, // 7 }; // Compare Modes -enum CompareMode : char { +enum CompareMode : uint8_t { COM_NORMAL, // 0 COM_TOGGLE, // 1 Non-PWM: OCnx ... Both PWM (WGM 9,11,14,15): OCnA only ... else NORMAL COM_CLEAR_SET, // 2 Non-PWM: OCnx ... Fast PWM: OCnx/Bottom ... PF-FC: OCnx Up/Down @@ -158,7 +158,7 @@ enum CompareMode : char { }; // Clock Sources -enum ClockSource : char { +enum ClockSource : uint8_t { CS_NONE, // 0 CS_PRESCALER_1, // 1 CS_PRESCALER_8, // 2 @@ -170,7 +170,7 @@ enum ClockSource : char { }; // Clock Sources (Timer 2 only) -enum ClockSource2 : char { +enum ClockSource2 : uint8_t { CS2_NONE, // 0 CS2_PRESCALER_1, // 1 CS2_PRESCALER_8, // 2 @@ -203,40 +203,33 @@ enum ClockSource2 : char { TCCR##T##B = (TCCR##T##B & ~(0x3 << WGM##T##2)) | (((int(V) >> 2) & 0x3) << WGM##T##2); \ }while(0) #define SET_WGM(T,V) _SET_WGM(T,WGM_##V) -// Runtime (see set_pwm_frequency): -#define _SET_WGMnQ(TCCRnQ, V) do{ \ - *(TCCRnQ)[0] = (*(TCCRnQ)[0] & ~(0x3 << 0)) | (( int(V) & 0x3) << 0); \ - *(TCCRnQ)[1] = (*(TCCRnQ)[1] & ~(0x3 << 3)) | (((int(V) >> 2) & 0x3) << 3); \ - }while(0) // Set Clock Select bits // Ex: SET_CS3(PRESCALER_64); +#ifdef TCCR2 + #define HAS_TCCR2 1 +#endif #define _SET_CS(T,V) (TCCR##T##B = (TCCR##T##B & ~(0x7 << CS##T##0)) | ((int(V) & 0x7) << CS##T##0)) #define _SET_CS0(V) _SET_CS(0,V) #define _SET_CS1(V) _SET_CS(1,V) -#ifdef TCCR2 - #define _SET_CS2(V) (TCCR2 = (TCCR2 & ~(0x7 << CS20)) | (int(V) << CS20)) -#else - #define _SET_CS2(V) _SET_CS(2,V) -#endif #define _SET_CS3(V) _SET_CS(3,V) #define _SET_CS4(V) _SET_CS(4,V) #define _SET_CS5(V) _SET_CS(5,V) #define SET_CS0(V) _SET_CS0(CS_##V) #define SET_CS1(V) _SET_CS1(CS_##V) -#ifdef TCCR2 + +#if HAS_TCCR2 + #define _SET_CS2(V) (TCCR2 = (TCCR2 & ~(0x7 << CS20)) | (int(V) << CS20)) #define SET_CS2(V) _SET_CS2(CS2_##V) #else + #define _SET_CS2(V) _SET_CS(2,V) #define SET_CS2(V) _SET_CS2(CS_##V) #endif + #define SET_CS3(V) _SET_CS3(CS_##V) #define SET_CS4(V) _SET_CS4(CS_##V) #define SET_CS5(V) _SET_CS5(CS_##V) #define SET_CS(T,V) SET_CS##T(V) -// Runtime (see set_pwm_frequency) -#define _SET_CSn(TCCRnQ, V) do{ \ - (*(TCCRnQ)[1] = (*(TCCRnQ[1]) & ~(0x7 << 0)) | ((int(V) & 0x7) << 0)); \ - }while(0) // Set Compare Mode bits // Ex: SET_COMS(4,CLEAR_SET,CLEAR_SET,CLEAR_SET); @@ -246,22 +239,6 @@ enum ClockSource2 : char { #define SET_COMB(T,V) SET_COM(T,B,V) #define SET_COMC(T,V) SET_COM(T,C,V) #define SET_COMS(T,V1,V2,V3) do{ SET_COMA(T,V1); SET_COMB(T,V2); SET_COMC(T,V3); }while(0) -// Runtime (see set_pwm_duty) -#define _SET_COMnQ(TCCRnQ, Q, V) do{ \ - (*(TCCRnQ)[0] = (*(TCCRnQ)[0] & ~(0x3 << (6-2*(Q)))) | (int(V) << (6-2*(Q)))); \ - }while(0) - -// Set OCRnQ register -// Runtime (see set_pwm_duty): -#define _SET_OCRnQ(OCRnQ, Q, V) do{ \ - (*(OCRnQ)[(Q)] = (0x0000) | (int(V) & 0xFFFF)); \ - }while(0) - -// Set ICRn register (one per timer) -// Runtime (see set_pwm_frequency) -#define _SET_ICRn(ICRn, V) do{ \ - (*(ICRn) = (0x0000) | (int(V) & 0xFFFF)); \ - }while(0) // Set Noise Canceler bit // Ex: SET_ICNC(2,1) @@ -278,84 +255,6 @@ enum ClockSource2 : char { #define SET_FOCB(T,V) SET_FOC(T,B,V) #define SET_FOCC(T,V) SET_FOC(T,C,V) -#if 0 - -/** - * PWM availability macros - */ - -// Determine which harware PWMs are already in use -#define _PWM_CHK_FAN_B(P) (P == E0_AUTO_FAN_PIN || P == E1_AUTO_FAN_PIN || P == E2_AUTO_FAN_PIN || P == E3_AUTO_FAN_PIN || P == E4_AUTO_FAN_PIN || P == E5_AUTO_FAN_PIN || P == E6_AUTO_FAN_PIN || P == E7_AUTO_FAN_PIN || P == CHAMBER_AUTO_FAN_PIN) -#if PIN_EXISTS(CONTROLLER_FAN) - #define PWM_CHK_FAN_B(P) (_PWM_CHK_FAN_B(P) || P == CONTROLLER_FAN_PIN) -#else - #define PWM_CHK_FAN_B(P) _PWM_CHK_FAN_B(P) -#endif - -#if ANY_PIN(FAN, FAN1, FAN2, FAN3, FAN4, FAN5, FAN6, FAN7) - #if PIN_EXISTS(FAN7) - #define PWM_CHK_FAN_A(P) (P == FAN0_PIN || P == FAN1_PIN || P == FAN2_PIN || P == FAN3_PIN || P == FAN4_PIN || P == FAN5_PIN || P == FAN6_PIN || P == FAN7_PIN) - #elif PIN_EXISTS(FAN6) - #define PWM_CHK_FAN_A(P) (P == FAN0_PIN || P == FAN1_PIN || P == FAN2_PIN || P == FAN3_PIN || P == FAN4_PIN || P == FAN5_PIN || P == FAN6_PIN) - #elif PIN_EXISTS(FAN5) - #define PWM_CHK_FAN_A(P) (P == FAN0_PIN || P == FAN1_PIN || P == FAN2_PIN || P == FAN3_PIN || P == FAN4_PIN || P == FAN5_PIN) - #elif PIN_EXISTS(FAN4) - #define PWM_CHK_FAN_A(P) (P == FAN0_PIN || P == FAN1_PIN || P == FAN2_PIN || P == FAN3_PIN || P == FAN4_PIN) - #elif PIN_EXISTS(FAN3) - #define PWM_CHK_FAN_A(P) (P == FAN0_PIN || P == FAN1_PIN || P == FAN2_PIN || P == FAN3_PIN) - #elif PIN_EXISTS(FAN2) - #define PWM_CHK_FAN_A(P) (P == FAN0_PIN || P == FAN1_PIN || P == FAN2_PIN) - #elif PIN_EXISTS(FAN1) - #define PWM_CHK_FAN_A(P) (P == FAN0_PIN || P == FAN1_PIN) - #else - #define PWM_CHK_FAN_A(P) (P == FAN0_PIN) - #endif -#else - #define PWM_CHK_FAN_A(P) false -#endif - -#if HAS_MOTOR_CURRENT_PWM - #if PIN_EXISTS(MOTOR_CURRENT_PWM_XY) - #define PWM_CHK_MOTOR_CURRENT(P) (P == MOTOR_CURRENT_PWM_E || P == MOTOR_CURRENT_PWM_Z || P == MOTOR_CURRENT_PWM_XY) - #elif PIN_EXISTS(MOTOR_CURRENT_PWM_Z) - #define PWM_CHK_MOTOR_CURRENT(P) (P == MOTOR_CURRENT_PWM_E || P == MOTOR_CURRENT_PWM_Z) - #else - #define PWM_CHK_MOTOR_CURRENT(P) (P == MOTOR_CURRENT_PWM_E) - #endif -#else - #define PWM_CHK_MOTOR_CURRENT(P) false -#endif - -#ifdef NUM_SERVOS - #if AVR_ATmega2560_FAMILY - #define PWM_CHK_SERVO(P) (P == 5 || (NUM_SERVOS > 12 && P == 6) || (NUM_SERVOS > 24 && P == 46)) // PWMS 3A, 4A & 5A - #elif AVR_ATmega2561_FAMILY - #define PWM_CHK_SERVO(P) (P == 5) // PWM3A - #elif AVR_ATmega1284_FAMILY - #define PWM_CHK_SERVO(P) false - #elif AVR_AT90USB1286_FAMILY - #define PWM_CHK_SERVO(P) (P == 16) // PWM3A - #elif AVR_ATmega328_FAMILY - #define PWM_CHK_SERVO(P) false - #endif -#else - #define PWM_CHK_SERVO(P) false -#endif - -#if ENABLED(BARICUDA) - #if HAS_HEATER_1 && HAS_HEATER_2 - #define PWM_CHK_HEATER(P) (P == HEATER_1_PIN || P == HEATER_2_PIN) - #elif HAS_HEATER_1 - #define PWM_CHK_HEATER(P) (P == HEATER_1_PIN) - #endif -#else - #define PWM_CHK_HEATER(P) false -#endif - -#define PWM_CHK(P) (PWM_CHK_HEATER(P) || PWM_CHK_SERVO(P) || PWM_CHK_MOTOR_CURRENT(P) || PWM_CHK_FAN_A(P) || PWM_CHK_FAN_B(P)) - -#endif // PWM_CHK is not used in Marlin - // define which hardware PWMs are available for the current CPU // all timer 1 PWMS deleted from this list because they are never available #if AVR_ATmega2560_FAMILY diff --git a/Marlin/src/HAL/AVR/fastio/fastio_1280.h b/Marlin/src/HAL/AVR/fastio/fastio_1280.h index f482f823e8..57d6181d2e 100644 --- a/Marlin/src/HAL/AVR/fastio/fastio_1280.h +++ b/Marlin/src/HAL/AVR/fastio/fastio_1280.h @@ -27,43 +27,41 @@ * Hardware Pin : 02 03 06 07 01 05 15 16 17 18 23 24 25 26 64 63 13 12 46 45 44 43 78 77 76 75 74 73 72 71 60 59 58 57 56 55 54 53 50 70 52 51 42 41 40 39 38 37 36 35 22 21 20 19 97 96 95 94 93 92 91 90 89 88 87 86 85 84 83 82 | 04 08 09 10 11 14 27 28 29 30 31 32 33 34 47 48 49 61 62 65 66 67 68 69 79 80 81 98 99 100 * 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 */ #include "../fastio.h" -// change for your board -#define DEBUG_LED DIO21 - // UART -#define RXD DIO0 -#define TXD DIO1 +#define RXD 0 +#define TXD 1 // SPI -#define SCK DIO52 -#define MISO DIO50 -#define MOSI DIO51 -#define SS DIO53 +#define MISO 50 +#define MOSI 51 +#define SCK 52 +#define SS 53 // TWI (I2C) -#define SCL DIO21 -#define SDA DIO20 +#define SCL 21 +#define SDA 20 // Timers and PWM -#define OC0A DIO13 -#define OC0B DIO4 -#define OC1A DIO11 -#define OC1B DIO12 -#define OC2A DIO10 -#define OC2B DIO9 -#define OC3A DIO5 -#define OC3B DIO2 -#define OC3C DIO3 -#define OC4A DIO6 -#define OC4B DIO7 -#define OC4C DIO8 -#define OC5A DIO46 -#define OC5B DIO45 -#define OC5C DIO44 +#define OC0A 13 +#define OC0B 4 +#define OC1A 11 +#define OC1B 12 +#define OC2A 10 +#define OC2B 9 +#define OC3A 5 +#define OC3B 2 +#define OC3C 3 +#define OC4A 6 +#define OC4B 7 +#define OC4C 8 +#define OC5A 46 +#define OC5B 45 +#define OC5C 44 // Digital I/O diff --git a/Marlin/src/HAL/AVR/fastio/fastio_1281.h b/Marlin/src/HAL/AVR/fastio/fastio_1281.h index e0bc5e2995..fdff219ec3 100644 --- a/Marlin/src/HAL/AVR/fastio/fastio_1281.h +++ b/Marlin/src/HAL/AVR/fastio/fastio_1281.h @@ -30,32 +30,29 @@ #include "../fastio.h" -// change for your board -#define DEBUG_LED DIO46 - // UART -#define RXD DIO0 -#define TXD DIO1 +#define RXD 0 +#define TXD 1 // SPI -#define SCK DIO10 -#define MISO DIO12 -#define MOSI DIO11 -#define SS DIO16 +#define SCK 10 +#define MISO 12 +#define MOSI 11 +#define SS 16 // TWI (I2C) -#define SCL DIO17 -#define SDA DIO18 +#define SCL 17 +#define SDA 18 // Timers and PWM -#define OC0A DIO9 -#define OC0B DIO4 -#define OC1A DIO7 -#define OC1B DIO8 -#define OC2A DIO6 -#define OC3A DIO5 -#define OC3B DIO2 -#define OC3C DIO3 +#define OC0A 9 +#define OC0B 4 +#define OC1A 7 +#define OC1B 8 +#define OC2A 6 +#define OC3A 5 +#define OC3B 2 +#define OC3C 3 // Digital I/O diff --git a/Marlin/src/HAL/AVR/fastio/fastio_168.h b/Marlin/src/HAL/AVR/fastio/fastio_168.h index 8cfdd1e8f8..36dc552385 100644 --- a/Marlin/src/HAL/AVR/fastio/fastio_168.h +++ b/Marlin/src/HAL/AVR/fastio/fastio_168.h @@ -30,29 +30,27 @@ #include "../fastio.h" -#define DEBUG_LED AIO5 - // UART -#define RXD DIO0 -#define TXD DIO1 +#define RXD 0 +#define TXD 1 // SPI -#define SCK DIO13 -#define MISO DIO12 -#define MOSI DIO11 -#define SS DIO10 +#define SS 10 +#define MOSI 11 +#define MISO 12 +#define SCK 13 // TWI (I2C) #define SCL AIO5 #define SDA AIO4 // Timers and PWM -#define OC0A DIO6 -#define OC0B DIO5 -#define OC1A DIO9 -#define OC1B DIO10 -#define OC2A DIO11 -#define OC2B DIO3 +#define OC0A 6 +#define OC0B 5 +#define OC1A 9 +#define OC1B 10 +#define OC2A 11 +#define OC2B 3 // Digital I/O diff --git a/Marlin/src/HAL/AVR/fastio/fastio_644.h b/Marlin/src/HAL/AVR/fastio/fastio_644.h index f4a9427e0a..e20a3d345e 100644 --- a/Marlin/src/HAL/AVR/fastio/fastio_644.h +++ b/Marlin/src/HAL/AVR/fastio/fastio_644.h @@ -56,34 +56,32 @@ #include "../fastio.h" -#define DEBUG_LED DIO0 - // UART -#define RXD DIO8 -#define TXD DIO9 -#define RXD0 DIO8 -#define TXD0 DIO9 +#define RXD 8 +#define TXD 9 +#define RXD0 8 +#define TXD0 9 -#define RXD1 DIO10 -#define TXD1 DIO11 +#define RXD1 10 +#define TXD1 11 // SPI -#define SCK DIO7 -#define MISO DIO6 -#define MOSI DIO5 -#define SS DIO4 +#define SS 4 +#define MOSI 5 +#define MISO 6 +#define SCK 7 // TWI (I2C) -#define SCL DIO16 -#define SDA DIO17 +#define SCL 16 +#define SDA 17 // Timers and PWM -#define OC0A DIO3 -#define OC0B DIO4 -#define OC1A DIO13 -#define OC1B DIO12 -#define OC2A DIO15 -#define OC2B DIO14 +#define OC0A 3 +#define OC0B 4 +#define OC1A 13 +#define OC1B 12 +#define OC2A 15 +#define OC2B 14 // Digital I/O diff --git a/Marlin/src/HAL/AVR/fastio/fastio_AT90USB.h b/Marlin/src/HAL/AVR/fastio/fastio_AT90USB.h index 51d400b705..a9af519ff3 100644 --- a/Marlin/src/HAL/AVR/fastio/fastio_AT90USB.h +++ b/Marlin/src/HAL/AVR/fastio/fastio_AT90USB.h @@ -26,19 +26,16 @@ * * Logical Pin: 28 29 30 31 32 33 34 35 20 21 22 23 24 25 26 27 10 11 12 13 14 15 16 17 00 01 02 03 04 05 06 07 08 09(46*47)36 37 18 19 38 39 40 41 42 43 44 45 * Port: A0 A1 A2 A3 A4 A5 A6 A7 B0 B1 B2 B3 B4 B5 B6 B7 C0 C1 C2 C3 C4 C5 C6 C7 D0 D1 D2 D3 D4 D5 D6 D7 E0 E1 E2 E3 E4 E5 E6 E7 F0 F1 F2 F3 F4 F5 F6 F7 - * The logical pins 46 and 47 are not supported by Teensyduino, but are supported below as E2 and E3 + * Logical pins 46-47 aren't supported by Teensyduino, but are supported below as E2 and E3 */ #include "../fastio.h" -// change for your board -#define DEBUG_LED DIO31 /* led D5 red */ - // SPI -#define SCK DIO21 // 9 -#define MISO DIO23 // 11 -#define MOSI DIO22 // 10 -#define SS DIO20 // 8 +#define SS 20 // 8 +#define SCK 21 // 9 +#define MOSI 22 // 10 +#define MISO 23 // 11 // Digital I/O @@ -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 @@ -679,7 +676,6 @@ #define PF7_PWM 0 #define PF7_DDR DDRF - /** * Some of the pin mapping functions of the Teensduino extension to the Arduino IDE * do not function the same as the other Arduino extensions. diff --git a/Marlin/src/HAL/AVR/inc/Conditionals_LCD.h b/Marlin/src/HAL/AVR/inc/Conditionals_LCD.h index a611ccd7c4..65b019b261 100644 --- a/Marlin/src/HAL/AVR/inc/Conditionals_LCD.h +++ b/Marlin/src/HAL/AVR/inc/Conditionals_LCD.h @@ -21,6 +21,6 @@ */ #pragma once -#if HAS_SPI_TFT || HAS_FSMC_TFT - #error "Sorry! TFT displays are not available for HAL/AVR." +#ifndef SERIAL_PORT + #define SERIAL_PORT 0 #endif diff --git a/Marlin/src/HAL/STM32_F4_F7/inc/Conditionals_adv.h b/Marlin/src/HAL/AVR/inc/Conditionals_type.h similarity index 92% rename from Marlin/src/HAL/STM32_F4_F7/inc/Conditionals_adv.h rename to Marlin/src/HAL/AVR/inc/Conditionals_type.h index 5f1c4b1601..82f95a1035 100644 --- a/Marlin/src/HAL/STM32_F4_F7/inc/Conditionals_adv.h +++ b/Marlin/src/HAL/AVR/inc/Conditionals_type.h @@ -1,6 +1,6 @@ /** * Marlin 3D Printer Firmware - * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] + * Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] * * Based on Sprinter and grbl. * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm diff --git a/Marlin/src/HAL/AVR/inc/SanityCheck.h b/Marlin/src/HAL/AVR/inc/SanityCheck.h index 731cf92865..08fe21d4f8 100644 --- a/Marlin/src/HAL/AVR/inc/SanityCheck.h +++ b/Marlin/src/HAL/AVR/inc/SanityCheck.h @@ -25,34 +25,91 @@ * Test AVR-specific configuration values for errors at compile-time. */ +#if HAS_SPI_TFT || HAS_FSMC_TFT + #error "Sorry! TFT displays are not available for HAL/AVR." +#endif + +/** + * Check for common serial pin conflicts + */ +#define CHECK_SERIAL_PIN(N) ( \ + X_STOP_PIN == N || Y_STOP_PIN == N || Z_STOP_PIN == N \ + || X_MIN_PIN == N || Y_MIN_PIN == N || Z_MIN_PIN == N \ + || X_MAX_PIN == N || Y_MAX_PIN == N || Z_MAX_PIN == N \ + || X_STEP_PIN == N || Y_STEP_PIN == N || Z_STEP_PIN == N \ + || X_DIR_PIN == N || Y_DIR_PIN == N || Z_DIR_PIN == N \ + || X_ENA_PIN == N || Y_ENA_PIN == N || Z_ENA_PIN == N \ + || BTN_EN1 == N || BTN_EN2 == N || LCD_PINS_EN == N \ +) +#if SERIAL_IN_USE(0) + // D0-D1. No known conflicts. +#endif +#if SERIAL_IN_USE(1) + #if NOT_TARGET(__AVR_ATmega644P__, __AVR_ATmega1284P__) + #if CHECK_SERIAL_PIN(18) || CHECK_SERIAL_PIN(19) + #error "Serial Port 1 pin D18 and/or D19 conflicts with another pin on the board." + #endif + #else + #if CHECK_SERIAL_PIN(10) || CHECK_SERIAL_PIN(11) + #error "Serial Port 1 pin D10 and/or D11 conflicts with another pin on the board." + #endif + #endif +#endif +#if SERIAL_IN_USE(2) && (CHECK_SERIAL_PIN(16) || CHECK_SERIAL_PIN(17)) + #error "Serial Port 2 pin D16 and/or D17 conflicts with another pin on the board." +#endif +#if SERIAL_IN_USE(3) && (CHECK_SERIAL_PIN(14) || CHECK_SERIAL_PIN(15)) + #error "Serial Port 3 pin D14 and/or D15 conflicts with another pin on the board." +#endif +#undef CHECK_SERIAL_PIN + /** * Checks for FAST PWM */ -#if ENABLED(FAST_PWM_FAN) && (ENABLED(USE_OCR2A_AS_TOP) && defined(TCCR2)) - #error "USE_OCR2A_AS_TOP does not apply to devices with a single output TIMER2" +#if ALL(FAST_PWM_FAN, USE_OCR2A_AS_TOP, HAS_TCCR2) + #error "USE_OCR2A_AS_TOP does not apply to devices with a single output TIMER2." +#endif + +/** + * Checks for SOFT PWM + */ +#if HAS_FAN0 && FAN0_PIN == 9 && DISABLED(FAN_SOFT_PWM) && ENABLED(SPEAKER) + #error "FAN0_PIN 9 Hardware PWM uses Timer 2 which conflicts with Arduino AVR Tone Timer (for SPEAKER)." + #error "Disable SPEAKER or enable FAN_SOFT_PWM." #endif /** * Sanity checks for Spindle / Laser PWM */ -#if ENABLED(SPINDLE_LASER_PWM) +#if ENABLED(SPINDLE_LASER_USE_PWM) #include "../ServoTimers.h" // Needed to check timer availability (_useTimer3) #if SPINDLE_LASER_PWM_PIN == 4 || WITHIN(SPINDLE_LASER_PWM_PIN, 11, 13) #error "Counter/Timer for SPINDLE_LASER_PWM_PIN is used by a system interrupt." #elif NUM_SERVOS > 0 && defined(_useTimer3) && (WITHIN(SPINDLE_LASER_PWM_PIN, 2, 3) || SPINDLE_LASER_PWM_PIN == 5) #error "Counter/Timer for SPINDLE_LASER_PWM_PIN is used by the servo system." #endif -#elif defined(SPINDLE_LASER_FREQUENCY) - #error "SPINDLE_LASER_FREQUENCY requires SPINDLE_LASER_PWM." +#elif SPINDLE_LASER_FREQUENCY + #error "SPINDLE_LASER_FREQUENCY requires SPINDLE_LASER_USE_PWM." #endif /** * The Trinamic library includes SoftwareSerial.h, leading to a compile error. */ -#if BOTH(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 -#if BOTH(HAS_TMC_SW_SERIAL, MONITOR_DRIVER_STATUS) +#if ALL(HAS_TMC_SW_SERIAL, MONITOR_DRIVER_STATUS) #error "MONITOR_DRIVER_STATUS causes performance issues when used with SoftwareSerial-connected drivers. Disable MONITOR_DRIVER_STATUS or use hardware serial to continue." #endif + +/** + * Postmortem debugging + */ +#if ENABLED(POSTMORTEM_DEBUGGING) + #error "POSTMORTEM_DEBUGGING is not supported on AVR boards." +#endif + +#if USING_PULLDOWNS + #error "PULLDOWN pin mode is not available on AVR boards." +#endif diff --git a/Marlin/src/HAL/AVR/math.h b/Marlin/src/HAL/AVR/math.h index 7ede4accc0..16848524fa 100644 --- a/Marlin/src/HAL/AVR/math.h +++ b/Marlin/src/HAL/AVR/math.h @@ -27,15 +27,16 @@ // intRes = longIn1 * longIn2 >> 24 // uses: -// A[tmp] to store 0 -// B[tmp] to store bits 16-23 of the 48bit result. The top bit is used to round the two byte result. -// note that the lower two bytes and the upper byte of the 48bit result are not calculated. -// this can cause the result to be out by one as the lower bytes may cause carries into the upper ones. -// B A are bits 24-39 and are the returned value -// C B A is longIn1 -// D C B A is longIn2 +// r1, r0 for the result of mul. +// [tmp1] to store 0. +// [tmp2] to store bits 16-23 of the 56 bit result. The top bit of [tmp2] is used for rounding. +// Note that the lower two bytes and the upper two bytes of the 56 bit result are not calculated. +// This can cause the result to be out by one as the lower bytes may cause carries into the upper ones. +// [intRes] (A B) is bits 24-39 and is the returned value. +// [longIn1] (C B A) is a 24 bit parameter. +// [longIn2] (D C B A) is a 32 bit parameter. // -static FORCE_INLINE uint16_t MultiU24X32toH16(uint32_t longIn1, uint32_t longIn2) { +FORCE_INLINE static uint16_t MultiU24X32toH16(uint32_t longIn1, uint32_t longIn2) { uint8_t tmp1; uint8_t tmp2; uint16_t intRes; @@ -66,11 +67,9 @@ static FORCE_INLINE uint16_t MultiU24X32toH16(uint32_t longIn1, uint32_t longIn2 A("add %[tmp2], r1") A("adc %A[intRes], %[tmp1]") A("adc %B[intRes], %[tmp1]") - A("lsr %[tmp2]") - A("adc %A[intRes], %[tmp1]") - A("adc %B[intRes], %[tmp1]") A("mul %D[longIn2], %A[longIn1]") - A("add %A[intRes], r0") + A("lsl %[tmp2]") + A("adc %A[intRes], r0") A("adc %B[intRes], r1") A("mul %D[longIn2], %B[longIn1]") A("add %B[intRes], r0") @@ -85,29 +84,26 @@ static FORCE_INLINE uint16_t MultiU24X32toH16(uint32_t longIn1, uint32_t longIn2 return intRes; } -// intRes = intIn1 * intIn2 >> 16 +// charRes = charIn1 * charIn2 >> 8 // uses: -// r26 to store 0 -// r27 to store the byte 1 of the 24 bit result -static FORCE_INLINE uint16_t MultiU16X8toH16(uint8_t charIn1, uint16_t intIn2) { - uint8_t tmp; - uint16_t intRes; +// r1, r0 for the result of mul. After the mul, r0 holds bits 0-7 of the 16 bit result, +// and the top bit of r0 is used for rounding. +// [charRes] is bits 8-15 and is the returned value. +// [charIn1] is an 8 bit parameter. +// [charIn2] is an 8 bit parameter. +// +FORCE_INLINE static uint8_t MultiU8X8toH8(uint8_t charIn1, uint8_t charIn2) { + uint8_t charRes; __asm__ __volatile__ ( - A("clr %[tmp]") - A("mul %[charIn1], %B[intIn2]") - A("movw %A[intRes], r0") - A("mul %[charIn1], %A[intIn2]") - A("add %A[intRes], r1") - A("adc %B[intRes], %[tmp]") - A("lsr r0") - A("adc %A[intRes], %[tmp]") - A("adc %B[intRes], %[tmp]") + A("mul %[charIn1], %[charIn2]") + A("mov %[charRes], r1") A("clr r1") - : [intRes] "=&r" (intRes), - [tmp] "=&r" (tmp) + A("lsl r0") + A("adc %[charRes], r1") + : [charRes] "=&r" (charRes) : [charIn1] "d" (charIn1), - [intIn2] "d" (intIn2) + [charIn2] "d" (charIn2) : "cc" ); - return intRes; + return charRes; } diff --git a/Marlin/src/HAL/AVR/pinsDebug.h b/Marlin/src/HAL/AVR/pinsDebug.h index dac6b1b150..c833964a29 100644 --- a/Marlin/src/HAL/AVR/pinsDebug.h +++ b/Marlin/src/HAL/AVR/pinsDebug.h @@ -2,6 +2,9 @@ * Marlin 3D Printer Firmware * Copyright (c) 2020 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 @@ -19,7 +22,23 @@ #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" @@ -36,55 +55,56 @@ #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_Teensy(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 DIGITAL_PIN_TO_ANALOG_PIN(P) int(analogInputToDigitalPin(0) - (P)) - #define IS_ANALOG(P) ((P) >= analogInputToDigitalPin(7) && (P) <= analogInputToDigitalPin(0)) + #define isAnalogPin(P) WITHIN(P, analogInputToDigitalPin(7), analogInputToDigitalPin(0)) + #define digitalPinToAnalogIndex(P) int(isAnalogPin(P) ? (P) - analogInputToDigitalPin(7) : -1) #else - #define DIGITAL_PIN_TO_ANALOG_PIN(P) int((P) - analogInputToDigitalPin(0)) - #define IS_ANALOG(P) ((P) >= analogInputToDigitalPin(0) && ((P) <= analogInputToDigitalPin(15) || (P) <= analogInputToDigitalPin(7))) + #define _ANALOG1(P) WITHIN(P, analogInputToDigitalPin(0), analogInputToDigitalPin(7)) + #define _ANALOG2(P) WITHIN(P, analogInputToDigitalPin(8), analogInputToDigitalPin(15)) + #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) { - char *name_mem_pointer = (char*)pgm_read_ptr(&pin_array[x].name); - LOOP_L_N(y, MAX_NAME_LENGTH) { +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) SERIAL_CHAR(temp_char); else { - LOOP_L_N(i, MAX_NAME_LENGTH - y) SERIAL_CHAR(' '); + for (uint8_t i = 0; i < MAX_NAME_LENGTH - y; ++i) SERIAL_CHAR(' '); break; } } } -#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 @@ -99,18 +119,18 @@ void PRINT_ARRAY_NAME(uint8_t x) { return true; \ } else return false - +#define ABTEST(N) defined(TCCR##N##A) && defined(COM##N##A1) /** * Print a pin's PWM status. * Return true if it's currently a PWM pin. */ -static bool pwm_status(uint8_t pin) { +bool pwm_status(const uint8_t pin) { char buffer[20]; // for the sprintf statements switch (digitalPinToTimer_DEBUG(pin)) { - #if defined(TCCR0A) && defined(COM0A1) + #if ABTEST(0) #ifdef TIMER0A #if !AVR_AT90USB1286_FAMILY // not available in Teensyduino type IDEs PWM_CASE(0, A); @@ -119,20 +139,20 @@ static bool pwm_status(uint8_t pin) { PWM_CASE(0, B); #endif - #if defined(TCCR1A) && defined(COM1A1) + #if ABTEST(1) PWM_CASE(1, A); PWM_CASE(1, B); - #if defined(COM1C1) && defined(TIMER1C) - PWM_CASE(1, C); - #endif + #if defined(COM1C1) && defined(TIMER1C) + PWM_CASE(1, C); + #endif #endif - #if defined(TCCR2A) && defined(COM2A1) + #if ABTEST(2) PWM_CASE(2, A); PWM_CASE(2, B); #endif - #if defined(TCCR3A) && defined(COM3A1) + #if ABTEST(3) PWM_CASE(3, A); PWM_CASE(3, B); #ifdef COM3C1 @@ -146,7 +166,7 @@ static bool pwm_status(uint8_t pin) { PWM_CASE(4, C); #endif - #if defined(TCCR5A) && defined(COM5A1) + #if ABTEST(5) PWM_CASE(5, A); PWM_CASE(5, B); PWM_CASE(5, C); @@ -159,25 +179,23 @@ static bool pwm_status(uint8_t pin) { SERIAL_ECHO_SP(2); } // pwm_status - const volatile uint8_t* const PWM_other[][3] PROGMEM = { { &TCCR0A, &TCCR0B, &TIMSK0 }, { &TCCR1A, &TCCR1B, &TIMSK1 }, - #if defined(TCCR2A) && defined(COM2A1) + #if ABTEST(2) { &TCCR2A, &TCCR2B, &TIMSK2 }, #endif - #if defined(TCCR3A) && defined(COM3A1) + #if ABTEST(3) { &TCCR3A, &TCCR3B, &TIMSK3 }, #endif #ifdef TCCR4A { &TCCR4A, &TCCR4B, &TIMSK4 }, #endif - #if defined(TCCR5A) && defined(COM5A1) + #if ABTEST(5) { &TCCR5A, &TCCR5B, &TIMSK5 }, #endif }; - const volatile uint8_t* const PWM_OCR[][3] PROGMEM = { #ifdef TIMER0A @@ -192,11 +210,11 @@ const volatile uint8_t* const PWM_OCR[][3] PROGMEM = { { (const uint8_t*)&OCR1A, (const uint8_t*)&OCR1B, 0 }, #endif - #if defined(TCCR2A) && defined(COM2A1) + #if ABTEST(2) { &OCR2A, &OCR2B, 0 }, #endif - #if defined(TCCR3A) && defined(COM3A1) + #if ABTEST(3) #ifdef COM3C1 { (const uint8_t*)&OCR3A, (const uint8_t*)&OCR3B, (const uint8_t*)&OCR3C }, #else @@ -208,12 +226,11 @@ const volatile uint8_t* const PWM_OCR[][3] PROGMEM = { { (const uint8_t*)&OCR4A, (const uint8_t*)&OCR4B, (const uint8_t*)&OCR4C }, #endif - #if defined(TCCR5A) && defined(COM5A1) + #if ABTEST(5) { (const uint8_t*)&OCR5A, (const uint8_t*)&OCR5B, (const uint8_t*)&OCR5C }, #endif }; - #define TCCR_A(T) pgm_read_word(&PWM_other[T][0]) #define TCCR_B(T) pgm_read_word(&PWM_other[T][1]) #define TIMSK(T) pgm_read_word(&PWM_other[T][2]) @@ -228,16 +245,16 @@ const volatile uint8_t* const PWM_OCR[][3] PROGMEM = { #define OCR_VAL(T, L) pgm_read_word(&PWM_OCR[T][L]) -static void err_is_counter() { SERIAL_ECHOPGM(" non-standard PWM mode"); } -static void err_is_interrupt() { SERIAL_ECHOPGM(" compare interrupt enabled"); } -static void err_prob_interrupt() { SERIAL_ECHOPGM(" overflow interrupt enabled"); } -static void print_is_also_tied() { SERIAL_ECHOPGM(" is also tied to this pin"); SERIAL_ECHO_SP(14); } +void err_is_counter() { SERIAL_ECHOPGM(" non-standard PWM mode"); } +void err_is_interrupt() { SERIAL_ECHOPGM(" compare interrupt enabled"); } +void err_prob_interrupt() { SERIAL_ECHOPGM(" overflow interrupt enabled"); } +void print_is_also_tied() { SERIAL_ECHOPGM(" is also tied to this pin"); SERIAL_ECHO_SP(14); } -inline void com_print(const uint8_t N, const uint8_t Z) { +void com_print(const uint8_t N, const uint8_t Z) { const uint8_t *TCCRA = (uint8_t*)TCCR_A(N); - SERIAL_ECHOPGM(" COM"); - SERIAL_CHAR('0' + N, Z); - SERIAL_ECHOPAIR(": ", int((*TCCRA >> (6 - Z * 2)) & 0x03)); + SERIAL_ECHOPGM(" COM", AS_DIGIT(N)); + SERIAL_CHAR(Z); + SERIAL_ECHOPGM(": ", int((*TCCRA >> (6 - Z * 2)) & 0x03)); } void timer_prefix(uint8_t T, char L, uint8_t N) { // T - timer L - pwm N - WGM bit layout @@ -247,8 +264,8 @@ void timer_prefix(uint8_t T, char L, uint8_t N) { // T - timer L - pwm N - uint8_t WGM = (((*TCCRB & _BV(WGM_2)) >> 1) | (*TCCRA & (_BV(WGM_0) | _BV(WGM_1)))); if (N == 4) WGM |= ((*TCCRB & _BV(WGM_3)) >> 1); - SERIAL_ECHOPGM(" TIMER"); - SERIAL_CHAR(T + '0', L); + SERIAL_ECHOPGM(" TIMER", AS_DIGIT(T)); + SERIAL_CHAR(L); SERIAL_ECHO_SP(3); if (N == 3) { @@ -259,22 +276,14 @@ void timer_prefix(uint8_t T, char L, uint8_t N) { // T - timer L - pwm N - const uint16_t *OCRVAL16 = (uint16_t*)OCR_VAL(T, L - 'A'); PWM_PRINT(*OCRVAL16); } - SERIAL_ECHOPAIR(" WGM: ", WGM); + SERIAL_ECHOPGM(" WGM: ", WGM); com_print(T,L); - SERIAL_ECHOPAIR(" CS: ", (*TCCRB & (_BV(CS_0) | _BV(CS_1) | _BV(CS_2)) )); - - SERIAL_ECHOPGM(" TCCR"); - SERIAL_CHAR(T + '0'); - SERIAL_ECHOPAIR("A: ", *TCCRA); - - SERIAL_ECHOPGM(" TCCR"); - SERIAL_CHAR(T + '0'); - SERIAL_ECHOPAIR("B: ", *TCCRB); + SERIAL_ECHOPGM(" CS: ", (*TCCRB & (_BV(CS_0) | _BV(CS_1) | _BV(CS_2)) )); + SERIAL_ECHOPGM(" TCCR", AS_DIGIT(T), "A: ", *TCCRA); + SERIAL_ECHOPGM(" TCCR", AS_DIGIT(T), "B: ", *TCCRB); const uint8_t *TMSK = (uint8_t*)TIMSK(T); - SERIAL_ECHOPGM(" TIMSK"); - SERIAL_CHAR(T + '0'); - SERIAL_ECHOPAIR(": ", *TMSK); + SERIAL_ECHOPGM(" TIMSK", AS_DIGIT(T), ": ", *TMSK); const uint8_t OCIE = L - 'A' + 1; if (N == 3) { if (WGM == 0 || WGM == 2 || WGM == 4 || WGM == 6) err_is_counter(); } @@ -283,10 +292,10 @@ void timer_prefix(uint8_t T, char L, uint8_t N) { // T - timer L - pwm N - if (TEST(*TMSK, TOIE)) err_prob_interrupt(); } -static void pwm_details(uint8_t pin) { +void printPinPWM(const uint8_t pin) { switch (digitalPinToTimer_DEBUG(pin)) { - #if defined(TCCR0A) && defined(COM0A1) + #if ABTEST(0) #ifdef TIMER0A #if !AVR_AT90USB1286_FAMILY // not available in Teensyduino type IDEs case TIMER0A: timer_prefix(0, 'A', 3); break; @@ -295,7 +304,7 @@ static void pwm_details(uint8_t pin) { case TIMER0B: timer_prefix(0, 'B', 3); break; #endif - #if defined(TCCR1A) && defined(COM1A1) + #if ABTEST(1) case TIMER1A: timer_prefix(1, 'A', 4); break; case TIMER1B: timer_prefix(1, 'B', 4); break; #if defined(COM1C1) && defined(TIMER1C) @@ -303,12 +312,12 @@ static void pwm_details(uint8_t pin) { #endif #endif - #if defined(TCCR2A) && defined(COM2A1) + #if ABTEST(2) case TIMER2A: timer_prefix(2, 'A', 3); break; case TIMER2B: timer_prefix(2, 'B', 3); break; #endif - #if defined(TCCR3A) && defined(COM3A1) + #if ABTEST(3) case TIMER3A: timer_prefix(3, 'A', 4); break; case TIMER3B: timer_prefix(3, 'B', 4); break; #ifdef COM3C1 @@ -322,7 +331,7 @@ static void pwm_details(uint8_t pin) { case TIMER4C: timer_prefix(4, 'C', 4); break; #endif - #if defined(TCCR5A) && defined(COM5A1) + #if ABTEST(5) case TIMER5A: timer_prefix(5, 'A', 4); break; case TIMER5B: timer_prefix(5, 'B', 4); break; case TIMER5C: timer_prefix(5, 'C', 4); break; @@ -354,50 +363,46 @@ static 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 int8_t pin) { // same as digitalRead except the PWM stop section has been removed + int digitalRead_mod(const pin_t pin) { // same as digitalRead except the PWM stop section has been removed const uint8_t port = digitalPinToPort_DEBUG(pin); return (port != NOT_A_PIN) && (*portInputRegister(port) & digitalPinToBitMask_DEBUG(pin)) ? HIGH : LOW; } #endif -#ifndef PRINT_PORT +void printPinPort(const pin_t pin) { // print port number + #ifdef digitalPinToPort_DEBUG + uint8_t x; + SERIAL_ECHOPGM(" Port: "); + #if AVR_AT90USB1286_FAMILY + x = (pin == PIN_E2 || pin == PIN_E3) ? 'E' : 'A' + digitalPinToPort_DEBUG(pin) - 1; + #else + x = 'A' + digitalPinToPort_DEBUG(pin) - 1; + #endif + SERIAL_CHAR(x); - void print_port(int8_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; - #else - x = digitalPinToPort_DEBUG(pin) + 64; - #endif - SERIAL_CHAR(x); - - #if AVR_AT90USB1286_FAMILY - if (pin == 46) - x = '2'; - else if (pin == 47) - x = '3'; - else { - uint8_t temp = digitalPinToBitMask_DEBUG(pin); - for (x = '0'; x < '9' && temp != 1; x++) temp >>= 1; - } - #else + #if AVR_AT90USB1286_FAMILY + if (pin == PIN_E2) + x = '2'; + else if (pin == PIN_E3) + x = '3'; + else { uint8_t temp = digitalPinToBitMask_DEBUG(pin); for (x = '0'; x < '9' && temp != 1; x++) temp >>= 1; - #endif - SERIAL_CHAR(x); + } #else - SERIAL_ECHO_SP(10); + uint8_t temp = digitalPinToBitMask_DEBUG(pin); + for (x = '0'; x < '9' && temp != 1; x++) temp >>= 1; #endif - } + SERIAL_CHAR(x); + #else + SERIAL_ECHO_SP(10); + #endif +} - #define PRINT_PORT(p) print_port(p) +#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) -#endif - -#define PRINT_PIN(p) do{ sprintf_P(buffer, PSTR("%3d "), 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 051972a861..463a77ec1d 100644 --- a/Marlin/src/HAL/AVR/pinsDebug_Teensyduino.h +++ b/Marlin/src/HAL/AVR/pinsDebug_Teensyduino.h @@ -2,6 +2,9 @@ * Marlin 3D Printer Firmware * Copyright (c) 2020 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 @@ -19,11 +22,10 @@ #pragma once // -// some of the pin mapping functions of the Teensduino extension to the Arduino IDE -// do not function the same as the other Arduino extensions +// Some of the pin mapping functions of the Arduino IDE Teensduino extension +// function differently from other Arduino extensions. // - #define TEENSYDUINO_IDE //digitalPinToTimer(pin) function works like Arduino but Timers are not defined @@ -45,8 +47,6 @@ #define PE 5 #define PF 6 -#undef digitalPinToPort - const uint8_t PROGMEM digital_pin_to_port_PGM[] = { PD, // 0 - PD0 - INT0 - PWM PD, // 1 - PD1 - INT1 - PWM @@ -98,11 +98,11 @@ const uint8_t PROGMEM digital_pin_to_port_PGM[] = { PE, // 47 - PE3 (not defined in teensyduino) }; -#define digitalPinToPort(P) ( pgm_read_byte( digital_pin_to_port_PGM + (P) ) ) +#define digitalPinToPort(P) pgm_read_byte(digital_pin_to_port_PGM[P]) // 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 db3fdf1f76..6565acd523 100644 --- a/Marlin/src/HAL/AVR/pinsDebug_plus_70.h +++ b/Marlin/src/HAL/AVR/pinsDebug_plus_70.h @@ -2,6 +2,9 @@ * Marlin 3D Printer Firmware * Copyright (c) 2020 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 @@ -45,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) ) ) @@ -138,180 +141,179 @@ 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) ) ) - 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.cpp b/Marlin/src/HAL/AVR/registers.cpp new file mode 100644 index 0000000000..08a74c952a --- /dev/null +++ b/Marlin/src/HAL/AVR/registers.cpp @@ -0,0 +1,979 @@ +/** + * 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 + * (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 __AVR__ + +#include "../../inc/MarlinConfigPre.h" + +#if ENABLED(HAL_AVR_DIRTY_INIT) + +#include "registers.h" + +// Since the compiler could be creating multiple copies of function code-graphs for each header inline-inclusion, +// we want to off-load the function definitions that define static memory into this solitary compilation unit. +// This way the ROM is NOT bloated (who knows if the compiler is optimizing same-content constant objects into one?) + +ATmegaPinFunctions _ATmega_getPinFunctions(int pin) { + if (pin < 0) return {}; + + ATmegaPinInfo info = _ATmega_getPinInfo((unsigned int)pin); + + #ifdef __AVR_TRM01__ + if (info.port == eATmegaPort::PORT_A) { + if (info.pinidx == 7) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EXTMEM_AD7 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 6) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EXTMEM_AD6 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 5) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EXTMEM_AD5 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 4) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EXTMEM_AD4 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 3) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EXTMEM_AD3 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 2) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EXTMEM_AD2 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 1) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EXTMEM_AD1 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 0) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EXTMEM_AD0 }; + return { funcs, countof(funcs) }; + } + } + else if (info.port == eATmegaPort::PORT_B) { + if (info.pinidx == 7) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::TOC0A, eATmegaPinFunc::TOC1C, eATmegaPinFunc::PCI7 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 6) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::TOC1B, eATmegaPinFunc::PCI6 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 5) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::TOC1A, eATmegaPinFunc::PCI5 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 4) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::TOC2A, eATmegaPinFunc::PCI4 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 3) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::SPI_MISO, eATmegaPinFunc::PCI3 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 2) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::SPI_MOSI, eATmegaPinFunc::PCI2 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 1) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::SPI_SCK, eATmegaPinFunc::PCI1 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 0) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::SPI_CS, eATmegaPinFunc::PCI0 }; + return { funcs, countof(funcs) }; + } + } + else if (info.port == eATmegaPort::PORT_C) { + if (info.pinidx == 7) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EXTMEM_AD15 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 6) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EXTMEM_AD14 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 5) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EXTMEM_AD13 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 4) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EXTMEM_AD12 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 3) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EXTMEM_AD11 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 2) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EXTMEM_AD10 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 1) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EXTMEM_AD9 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 0) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EXTMEM_AD8 }; + return { funcs, countof(funcs) }; + } + } + else if (info.port == eATmegaPort::PORT_D) { + if (info.pinidx == 7) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::TIMER0_CLKI }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 6) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::TIMER1_CLKI }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 5) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::USART1_CLK }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 4) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::TIMER1_ICP }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 3) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EINT3, eATmegaPinFunc::USART1_TXD }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 2) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EINT2, eATmegaPinFunc::USART1_RXD }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 1) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EINT1, eATmegaPinFunc::TWI_SDA }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 0) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EINT0, eATmegaPinFunc::TWI_CLK }; + return { funcs, countof(funcs) }; + } + } + else if (info.port == eATmegaPort::PORT_E) { + if (info.pinidx == 7) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EINT7, eATmegaPinFunc::TIMER3_ICP, eATmegaPinFunc::CLKO }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 6) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EINT6, eATmegaPinFunc::TIMER3_CLKI }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 5) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EINT5, eATmegaPinFunc::TOC3C }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 4) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EINT4, eATmegaPinFunc::TOC3B }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 3) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::AIN1, eATmegaPinFunc::TOC3A }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 2) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::AIN0, eATmegaPinFunc::USART0_CLK }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 1) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::PDO, eATmegaPinFunc::USART0_TXD }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 0) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::PDI, eATmegaPinFunc::USART0_RXD, eATmegaPinFunc::PCI8 }; + return { funcs, countof(funcs) }; + } + } + else if (info.port == eATmegaPort::PORT_F) { + if (info.pinidx == 7) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::ADC7 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 6) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::ADC6 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 5) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::ADC5 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 4) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::ADC4 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 3) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::ADC3 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 2) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::ADC2 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 1) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::ADC1 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 0) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::ADC0 }; + return { funcs, countof(funcs) }; + } + } + else if (info.port == eATmegaPort::PORT_G) { + if (info.pinidx == 5) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::TOC0B }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 4) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::TOSC1 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 3 ) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::TOSC2 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 2) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EXTMEM_ALE }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 1) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EXTMEM_RD }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 0) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EXTMEM_WR }; + return { funcs, countof(funcs) }; + } + } + else if (info.port == eATmegaPort::PORT_H) { + if (info.pinidx == 7) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::TIMER4_CLKI }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 6) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::TOC2B }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 5) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::TOC4C }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 4) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::TOC4B }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 3) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::TOC4A }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 2) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::USART2_CLK }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 1) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::USART2_TXD }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 0) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::USART2_RXD }; + return { funcs, countof(funcs) }; + } + } + else if (info.port == eATmegaPort::PORT_J) { + if (info.pinidx == 6) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::PCI15 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 5) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::PCI14 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 4) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::PCI13 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 3) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::PCI12 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 2) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::USART3_CLK, eATmegaPinFunc::PCI11 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 1) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::USART3_TXD, eATmegaPinFunc::PCI10 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 0) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::USART3_RXD, eATmegaPinFunc::PCI9 }; + return { funcs, countof(funcs) }; + } + } + else if (info.port == eATmegaPort::PORT_K) { + if (info.pinidx == 7) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::ADC15, eATmegaPinFunc::PCI23 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 6) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::ADC14, eATmegaPinFunc::PCI22 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 5) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::ADC13, eATmegaPinFunc::PCI21 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 4) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::ADC12, eATmegaPinFunc::PCI20 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 3) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::ADC11, eATmegaPinFunc::PCI19 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 2) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::ADC10, eATmegaPinFunc::PCI18 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 1) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::ADC9, eATmegaPinFunc::PCI17 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 0) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::ADC8, eATmegaPinFunc::PCI16 }; + return { funcs, countof(funcs) }; + } + } + else if (info.port == eATmegaPort::PORT_L) { + if (info.pinidx == 5) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::TOC5C }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 4) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::TOC5B }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 3) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::TOC5A }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 2) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::TIMER5_CLKI }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 1) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::TIMER5_ICP }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 0) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::TIMER4_ICP }; + return { funcs, countof(funcs) }; + } + } + #elif defined(__AVR_TRM02__) + if (info.port == eATmegaPort::PORT_A) { + if (info.pinidx == 7) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::PCI7, eATmegaPinFunc::ADC7 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 6) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::PCI6, eATmegaPinFunc::ADC6 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 5) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::PCI5, eATmegaPinFunc::ADC5 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 4) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::PCI4, eATmegaPinFunc::ADC4 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 3) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::PCI3, eATmegaPinFunc::ADC3 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 2) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::PCI2, eATmegaPinFunc::ADC2 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 1) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::PCI1, eATmegaPinFunc::ADC1 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 0) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::PCI0, eATmegaPinFunc::ADC0 }; + return { funcs, countof(funcs) }; + } + } + else if (info.port == eATmegaPort::PORT_B) { + if (info.pinidx == 7) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::SPI_SCK, eATmegaPinFunc::TOC3B, eATmegaPinFunc::PCI15 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 6) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::SPI_MISO, eATmegaPinFunc::TOC3A, eATmegaPinFunc::PCI14 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 5) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::SPI_MOSI, eATmegaPinFunc::TIMER3_ICP, eATmegaPinFunc::PCI13 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 4) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::SPI_CS, eATmegaPinFunc::TOC0B, eATmegaPinFunc::PCI12 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 3) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::AIN1, eATmegaPinFunc::TOC0A, eATmegaPinFunc::PCI11 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 2) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::AIN0, eATmegaPinFunc::EINT2, eATmegaPinFunc::PCI10 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 1) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::TIMER1_ECI, eATmegaPinFunc::CLKO, eATmegaPinFunc::PCI9 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 0) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::TIMER0_ECI, eATmegaPinFunc::USART0_CLK, eATmegaPinFunc::PCI8 }; + return { funcs, countof(funcs) }; + } + } + else if (info.port == eATmegaPort::PORT_C) { + if (info.pinidx == 7) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::TOSC2, eATmegaPinFunc::PCI23 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 6) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::TOSC1, eATmegaPinFunc::PCI22 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 5) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::PCI21 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 4) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::PCI20 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 3) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::PCI19 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 2) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::PCI18 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 1) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::PCI17, eATmegaPinFunc::TWI_SDA }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 0) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::TWI_CLK, eATmegaPinFunc::PCI16 }; + return { funcs, countof(funcs) }; + } + } + else if (info.port == eATmegaPort::PORT_D) { + if (info.pinidx == 7) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::TOC2A, eATmegaPinFunc::PCI31 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 6) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::TIMER1_ICP, eATmegaPinFunc::TOC2B, eATmegaPinFunc::PCI30 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 5) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::TOC1A, eATmegaPinFunc::PCI29 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 4) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::TOC1B, eATmegaPinFunc::USART1_CLK, eATmegaPinFunc::PCI28 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 3) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EINT1, eATmegaPinFunc::USART1_TXD, eATmegaPinFunc::PCI27 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 2) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EINT0, eATmegaPinFunc::USART1_RXD, eATmegaPinFunc::PCI26 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 1) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::USART0_TXD, eATmegaPinFunc::PCI25 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 0) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::USART0_TXD, eATmegaPinFunc::PCI24, eATmegaPinFunc::TIMER3_ECI }; + return { funcs, countof(funcs) }; + } + } + #elif defined(__AVR_TRM03__) + if (info.port == eATmegaPort::PORT_B) { + if (info.pinidx == 7) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::XTAL2, eATmegaPinFunc::TOSC2, eATmegaPinFunc::PCI7 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 6) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::XTAL1, eATmegaPinFunc::TOSC1, eATmegaPinFunc::PCI6 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 5) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::SPI_SCK, eATmegaPinFunc::PCI5 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 4) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::SPI_MISO, eATmegaPinFunc::PCI4 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 3) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::SPI_MOSI, eATmegaPinFunc::TOC2A, eATmegaPinFunc::PCI3 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 2) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::SPI_CS, eATmegaPinFunc::TOC1B, eATmegaPinFunc::PCI2 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 1) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::TOC1A, eATmegaPinFunc::PCI1 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 0) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::TIMER1_ICP, eATmegaPinFunc::CLKO, eATmegaPinFunc::PCI0 }; + return { funcs, countof(funcs) }; + } + } + else if (info.port == eATmegaPort::PORT_C) { + if (info.pinidx == 6) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::PCI14 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 5) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::ADC5, eATmegaPinFunc::TWI_CLK, eATmegaPinFunc::PCI13 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 4) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::ADC4, eATmegaPinFunc::TWI_SDA, eATmegaPinFunc::PCI12 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 3) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::ADC3, eATmegaPinFunc::PCI11 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 2) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::ADC2, eATmegaPinFunc::PCI10 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 1) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::ADC1, eATmegaPinFunc::PCI9 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 0) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::ADC0, eATmegaPinFunc::PCI8 }; + return { funcs, countof(funcs) }; + } + } + else if (info.port == eATmegaPort::PORT_D) { + if (info.pinidx == 7) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::AIN1, eATmegaPinFunc::PCI23 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 6) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::AIN0, eATmegaPinFunc::TOC0A, eATmegaPinFunc::PCI22 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 5) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::TIMER1_ECI, eATmegaPinFunc::TOC0B, eATmegaPinFunc::PCI21 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 4) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::USART_CLK, eATmegaPinFunc::TIMER0_ECI, eATmegaPinFunc::PCI20 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 3) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EINT1, eATmegaPinFunc::TOC2B, eATmegaPinFunc::PCI19 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 2) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EINT0, eATmegaPinFunc::PCI18 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 1) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::USART_TXD, eATmegaPinFunc::PCI17 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 0) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::USART_RXD, eATmegaPinFunc::PCI16 }; + return { funcs, countof(funcs) }; + } + } + #elif defined(__AVR_TRM04__) + if (info.port == eATmegaPort::PORT_A) { + if (info.pinidx == 7) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EXTMEM_AD7 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 6) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EXTMEM_AD6 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 5) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EXTMEM_AD5 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 4) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EXTMEM_AD4 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 3) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EXTMEM_AD3 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 2) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EXTMEM_AD2 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 1) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EXTMEM_AD1 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 0) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EXTMEM_AD0 }; + return { funcs, countof(funcs) }; + } + } + else if (info.port == eATmegaPort::PORT_B) { + if (info.pinidx == 7) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::TOC0A, eATmegaPinFunc::TOC1C, eATmegaPinFunc::PCI7 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 6) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::TOC1B, eATmegaPinFunc::PCI6 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 5) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::TOC1A, eATmegaPinFunc::PCI5 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 4) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::TOC2A, eATmegaPinFunc::PCI4 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 3) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::PDO, eATmegaPinFunc::SPI_MISO, eATmegaPinFunc::PCI3 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 2) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::PDI, eATmegaPinFunc::SPI_MOSI, eATmegaPinFunc::PCI2 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 1) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::SPI_SCK, eATmegaPinFunc::PCI1 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 0) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::SPI_CS, eATmegaPinFunc::PCI0 }; + return { funcs, countof(funcs) }; + } + } + else if (info.port == eATmegaPort::PORT_C) { + if (info.pinidx == 7) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EXTMEM_AD15, eATmegaPinFunc::TIMER3_ICP, eATmegaPinFunc::CLKO }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 6) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EXTMEM_AD14, eATmegaPinFunc::TOC3A }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 5) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EXTMEM_AD13, eATmegaPinFunc::TOC3B }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 4) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EXTMEM_AD12, eATmegaPinFunc::TOC3C }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 3) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EXTMEM_AD11, eATmegaPinFunc::TIMER3_CLKI }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 2) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EXTMEM_AD10 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 1) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EXTMEM_AD9 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 0) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EXTMEM_AD8 }; + return { funcs, countof(funcs) }; + } + } + else if (info.port == eATmegaPort::PORT_D) { + if (info.pinidx == 7) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::TIMER0_CLKI }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 6) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::TIMER1_CLKI }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 5) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::USART1_CLK }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 4) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::TIMER1_ICP }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 3) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EINT3, eATmegaPinFunc::USART1_TXD }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 2) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EINT2, eATmegaPinFunc::USART1_RXD }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 1) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EINT1, eATmegaPinFunc::TWI_SDA, eATmegaPinFunc::TOC2B }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 0) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EINT0, eATmegaPinFunc::TWI_CLK, eATmegaPinFunc::TOC0B }; + return { funcs, countof(funcs) }; + } + } + else if (info.port == eATmegaPort::PORT_E) { + if (info.pinidx == 7) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EINT7, eATmegaPinFunc::AIN1, eATmegaPinFunc::UVCON }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 6) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EINT6, eATmegaPinFunc::AIN0 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 5) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EINT5, eATmegaPinFunc::TOSC2 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 4) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EINT4, eATmegaPinFunc::TOSC2 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 3) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::UID }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 2) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EXTMEM_ALE }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 1) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EXTMEM_RD }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 0) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EXTMEM_WR }; + return { funcs, countof(funcs) }; + } + } + else if (info.port == eATmegaPort::PORT_F) { + if (info.pinidx == 7) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::ADC7 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 6) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::ADC6 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 5) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::ADC5 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 4) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::ADC4 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 3) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::ADC3 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 2) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::ADC2 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 1) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::ADC1 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 0) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::ADC0 }; + return { funcs, countof(funcs) }; + } + } + #elif defined(__AVR_TRM05__) + if (info.port == eATmegaPort::PORT_A) { + if (info.pinidx == 7) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::ADC7, eATmegaPinFunc::PCI7 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 6) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::ADC6, eATmegaPinFunc::PCI6 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 5) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::ADC5, eATmegaPinFunc::PCI5 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 4) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::ADC4, eATmegaPinFunc::PCI4 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 3) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::ADC3, eATmegaPinFunc::PCI3 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 2) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::ADC2, eATmegaPinFunc::PCI2 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 1) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::ADC1, eATmegaPinFunc::PCI1 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 0) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::ADC0, eATmegaPinFunc::PCI0 }; + return { funcs, countof(funcs) }; + } + } + else if (info.port == eATmegaPort::PORT_B) { + if (info.pinidx == 7) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::SPI_SCK, eATmegaPinFunc::PCI15 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 6) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::SPI_MISO, eATmegaPinFunc::PCI14 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 5) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::SPI_MOSI, eATmegaPinFunc::PCI13 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 4) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::SPI_CS, eATmegaPinFunc::TOC0B, eATmegaPinFunc::PCI12 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 3) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::AIN1, eATmegaPinFunc::TOC0A, eATmegaPinFunc::PCI11 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 2) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::AIN0, eATmegaPinFunc::EINT2, eATmegaPinFunc::PCI10 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 1) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::TIMER1_ECI, eATmegaPinFunc::CLKO, eATmegaPinFunc::PCI9 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 0) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::TIMER0_ECI, eATmegaPinFunc::USART0_CLK, eATmegaPinFunc::PCI8 }; + return { funcs, countof(funcs) }; + } + } + else if (info.port == eATmegaPort::PORT_C) { + if (info.pinidx == 7) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::TOSC2, eATmegaPinFunc::PCI23 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 6) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::TOSC1, eATmegaPinFunc::PCI22 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 5) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::PCI21 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 4) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::PCI20 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 3) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::PCI19 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 2) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::PCI18 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 1) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::TWI_SDA, eATmegaPinFunc::PCI17 }; + return { funcs, countof(funcs) }; + } + else if (info.pinidx == 0) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::TWI_CLK, eATmegaPinFunc::PCI16 }; + return { funcs, countof(funcs) }; + } + } + else if (info.port == eATmegaPort::PORT_D) { + if (info.pinidx == 7) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::TOC2A, eATmegaPinFunc::PCI31 }; + return { funcs, countof(funcs) }; + } + if (info.pinidx == 6) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::TIMER1_ICP, eATmegaPinFunc::TOC2B, eATmegaPinFunc::PCI30 }; + return { funcs, countof(funcs) }; + } + if (info.pinidx == 5) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::TOC1A, eATmegaPinFunc::PCI29 }; + return { funcs, countof(funcs) }; + } + if (info.pinidx == 4) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::TOC1B, eATmegaPinFunc::USART1_CLK, eATmegaPinFunc::PCI28 }; + return { funcs, countof(funcs) }; + } + if (info.pinidx == 3) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EINT1, eATmegaPinFunc::USART1_TXD, eATmegaPinFunc::PCI27 }; + return { funcs, countof(funcs) }; + } + if (info.pinidx == 2) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::EINT0, eATmegaPinFunc::USART1_RXD, eATmegaPinFunc::PCI26 }; + return { funcs, countof(funcs) }; + } + if (info.pinidx == 1) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::USART0_TXD, eATmegaPinFunc::PCI25 }; + return { funcs, countof(funcs) }; + } + if (info.pinidx == 0) { + static const eATmegaPinFunc funcs[] = { eATmegaPinFunc::USART0_RXD, eATmegaPinFunc::PCI24 }; + return { funcs, countof(funcs) }; + } + } + #endif + + return ATmegaPinFunctions(); // default and empty. +} + +#endif // HAL_AVR_DIRTY_INIT +#endif // __AVR__ diff --git a/Marlin/src/HAL/AVR/registers.h b/Marlin/src/HAL/AVR/registers.h new file mode 100644 index 0000000000..0eac4888cd --- /dev/null +++ b/Marlin/src/HAL/AVR/registers.h @@ -0,0 +1,5080 @@ +/** + * 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 + * (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 + +// This volatile-nonsense has to be done due to the C++ platform language specialization that specifies, for it's own compiler ideology, +// that memory writes and reads can be optimized across easily-reachable code spaces. This is in contrast to MSVC which specifies that +// memory writes and reads are holy. + +// OVERVIEW OF PREPROCESSOR DEFINITIONS: +// __AVR_ATmega2560__ +// __AVR_ATmega1284P__ +// __AVR_ATmega1280__ +// __AVR_ATmega644__ +// __AVR_ATmega644P__ +// __AVR_ATmega2561__ + +// Contributed by Martin Turski, company owner of EirDev, on the 29th of November, 2022 +// Contact E-Mail: turningtides@outlook.de +// Created specifically for the Marlin FW for AVR backwards-compatibility. +// Please expand this file with details of every supported AVR implementation. +// 1) download the latest technical reference manual +// 2) add the new technical reference manual below using a set of __AVR_*__ preprocessor definitions and a new __AVR_TRM*__ incrementing define +// 3) check which of the existing AVR registers exist on the new implementation and enable them +// 4) add any new register definitions +// 5) add the register memory layout below the definitions +// 6) extend the _ATmega_resetperipherals functions +// 7) extend the _ATmega_savePinAlternate function +// 8) copy the extension idea to _ATmega_restorePinAlternate and finish implementing it +// You need to adjust the eATmegaPort enumeration aswell. + +#if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM03__) || defined(__AVR_TRM04__) || defined(__AVR_TRM05__) + #error "Fatal error: __AVR_TRMn__ already defined! (n: 01|02|03|04|05)" +#endif + +#if defined(__AVR_ATmega2560__) || defined(__AVR_ATmega2561__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega1281__) || defined(__AVR_ATmega640__) + // ATmega2560 technical reference manual date: 28th of November, 2022 + // ATmega640-1280-1281-2560-2561-Datasheet-DS40002211A.pdf + #define __AVR_TRM01__ +#elif defined(__AVR_ATmega164A__) || defined(__AVR_ATmega164PA__) || defined(__AVR_ATmega324A__) || defined(__AVR_ATmega324PA__) || defined(__AVR_ATmega644A__) || defined(__AVR_ATmega644PA__) || defined(__AVR_ATmega1284__) || defined(__AVR_ATmega1284P__) + // ATmega1284 technical reference manual date: 29th of November, 2022 + // ATmega164A_PA-324A_PA-644A_PA-1284_P_Data-Sheet-40002070B.pdf + #define __AVR_TRM02__ +#elif defined(__AVR_ATmega48A__) || defined(__AVR_ATmega48PA__) || defined(__AVR_ATmega88A__) || defined(__AVR_ATmega88PA__) || defined(__AVR_ATmega168A__) || defined(__AVR_ATmega168PA__) || defined(__AVR_ATmega328__) || defined(__AVR_ATmega328P__) + // ATmega328 technical reference manual date: 29th of November, 2022 + // ATmega48A-PA-88A-PA-168A-PA-328-P-DS-DS40002061B.pdf + #define __AVR_TRM03__ +#elif defined(__AVR_AT90USB1287__) || defined(__AVR_AT90USB1286__) || defined(__AVR_AT90USB1286P__) || defined(__AVR_AT90USB646__) || defined(__AVR_AT90USB646P__) || defined(__AVR_AT90USB647__) + // AT90USB1287 technical reference manual ID: 7593D–AVR–07/06 + // Preliminary. + #define __AVR_TRM04__ +#elif defined(__AVR_ATmega164P__) || defined(__AVR_ATmega164V__) || defined(__AVR_ATmega324P__) || defined(__AVR_ATmega324V__) || defined(__AVR_ATmega644P__) || defined(__AVR_ATmega644V__) + // ATmega644P technical reference manual date: 14th of February, 2023 + // ATmega164P-324P-644P-Data-Sheet-40002071A.pdf + #define __AVR_TRM05__ +#endif + +/** + * HELPER FUNCTIONS + */ +namespace AVRHelpers { + + template + struct no_volatile { + typedef T type; + }; + + template + struct no_volatile : public no_volatile {}; + + template + struct voltype { + typedef T type; + }; + template + struct voltype { + typedef uint8_t type; + }; + template + struct voltype { + typedef uint16_t type; + }; + template + struct voltype { + typedef uint32_t type; + }; + + template + inline void dwrite(volatile T& v, const T& V) noexcept { + (volatile typename voltype ::type&)v = (const typename voltype ::type&)V; + } + +} // namespace AVRHelpers + +// As old as the ATmega series of CPU is, the worse the actual libraries making +// use of the MCU likely are. + +// These registers as references do not take program space since they are purely references. + +// It would be great if the old AVR definitions could be wasted in favor of these +// and code be rewritten to use the following more robust definitions. + +struct _bit_reg_t { + uint8_t val; + + bool getValue(uint8_t idx) const volatile { + return ( val & (1 << idx) ); + } + void setValue(uint8_t idx, bool value) volatile { + if (value) + val |= (1 << idx); + else + val &= ~(1 << idx); + } +}; + +#if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM03__) || defined(__AVR_TRM04__) || defined(__AVR_TRM05__) + + typedef _bit_reg_t PIN_reg_t; + typedef _bit_reg_t DDR_reg_t; + typedef _bit_reg_t PORT_reg_t; + + struct PORT_dev_t { + PIN_reg_t _PIN; + DDR_reg_t _DDR; + PORT_reg_t _PORT; + + inline void operator = ( const PORT_dev_t& r ) volatile { + using namespace AVRHelpers; + dwrite(this->_PIN, r._PIN); + dwrite(this->_DDR, r._DDR); + dwrite(this->_PORT, r._PORT); + } + }; + static_assert(sizeof(PORT_dev_t) == 3, "invalid size of ATmega2560 GPIO_dev_t"); + +#endif // __AVR_TRM01__ || __AVR_TRM02__ || __AVR_TRM03__ || __AVR_TRM04__ || __AVR_TRM05__ + +#ifdef __AVR_TRM01__ + + struct _bitG_reg_t { + uint8_t val : 6; + uint8_t reserved1 : 2; + + bool getValue(uint8_t idx) const volatile { + return val & (1 << idx); + } + void setValue(uint8_t idx, bool value) volatile { + if (value) + val |= (1 << idx); + else + val &= ~(1 << idx); + } + }; + typedef _bitG_reg_t PING_reg_t; + typedef _bitG_reg_t DDRG_reg_t; + typedef _bitG_reg_t PORTG_reg_t; + + struct PORTG_dev_t { + PING_reg_t _PIN; + DDRG_reg_t _DDR; + PORTG_reg_t _PORT; + + inline void operator = ( const PORTG_dev_t& r ) volatile { + using namespace AVRHelpers; + dwrite(this->_PIN, r._PIN); + dwrite(this->_DDR, r._DDR); + dwrite(this->_PORT, r._PORT); + } + }; + +#endif + +#ifdef __AVR_TRM03__ + + struct _bitC_reg_t { + uint8_t val : 7; + uint8_t reserved1 : 1; + + bool getValue(uint8_t idx) const volatile { + return ( val & (1 << idx) ); + } + void setValue(uint8_t idx, bool value) volatile { + if (value) + val |= (1 << idx); + else + val &= ~(1 << idx); + } + }; + typedef _bitC_reg_t PINC_reg_t; + typedef _bitC_reg_t DDRC_reg_t; + typedef _bitC_reg_t PORTC_reg_t; + + struct PORTC_dev_t { + PINC_reg_t _PIN; + DDRC_reg_t _DDR; + PORTC_reg_t _PORT; + + inline void operator = ( const PORTC_dev_t& r ) volatile { + this->_PIN = r._PIN; + this->_DDR = r._DDR; + this->_PORT = r._PORT; + } + }; + +#endif + +#if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM03__) || defined(__AVR_TRM04__) || defined(__AVR_TRM05__) + + struct TIFR0_reg_t { + uint8_t _TOV0 : 1; + uint8_t _OCF0A : 1; + uint8_t _OCF0B : 1; + uint8_t reserved1 : 5; + }; + static_assert(sizeof(TIFR0_reg_t) == 1, "invalid size of ATmega2560 TIFR0_reg_t"); + + struct TIFR1_reg_t { + #if defined(__AVR_TRM01__) || defined(__AVR_TRM04__) + uint8_t _TOV1 : 1; + uint8_t _OCF1A : 1; + uint8_t _OCF1B : 1; + uint8_t _OCF1C : 1; + uint8_t reserved1 : 1; + uint8_t _ICF1 : 1; + uint8_t reserved2 : 2; + #elif defined(__AVR_TRM02__) || defined(__AVR_TRM03__) || defined(__AVR_TRM05__) + uint8_t _TOV1 : 1; + uint8_t _OCF1A : 1; + uint8_t _OCF1B : 1; + uint8_t reserved1 : 2; + uint8_t _ICF1 : 1; + uint8_t reserved2 : 2; + #endif + }; + static_assert(sizeof(TIFR1_reg_t) == 1, "invalid size of ATmega2560 TIFR1_reg_t"); + + struct TIFR2_reg_t { + uint8_t _TOV2 : 1; + uint8_t _OCF2A : 1; + uint8_t _OCF2B : 1; + uint8_t reserved1 : 5; + }; + static_assert(sizeof(TIFR2_reg_t) == 1, "invalid size of ATmega2560 TIFR2_reg_t"); + +#endif // __AVR_TRM01__ || __AVR_TRM02__ || __AVR_TRM03__ || __AVR_TRM04__ || __AVR_TRM05__ + +#if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM04__) + + struct TIFR3_reg_t { + #if defined(__AVR_TRM01__) || defined(__AVR_TRM04__) + uint8_t _TOV3 : 1; + uint8_t _OCF3A : 1; + uint8_t _OCF3B : 1; + uint8_t _OCF3C : 1; + uint8_t reserved1 : 1; + uint8_t _ICF3 : 1; + uint8_t reserved2 : 2; + #elif defined(__AVR_TRM02__) + uint8_t _TOV3 : 1; + uint8_t _OCF3A : 1; + uint8_t _OCF3B : 1; + uint8_t reserved1 : 2; + uint8_t _ICF3 : 1; + uint8_t reserved2 : 2; + #endif + }; + static_assert(sizeof(TIFR3_reg_t) == 1, "invalid size of ATmega2560 TIFR3_reg_t"); + +#endif // __AVR_TRM01__ || __AVR_TRM02__ || __AVR_TRM04__ + +#ifdef __AVR_TRM01__ + + struct TIFR4_reg_t { + uint8_t _TOV4 : 1; + uint8_t _OCF4A : 1; + uint8_t _OCF4B : 1; + uint8_t _OCF4C : 1; + uint8_t reserved1 : 1; + uint8_t _ICF4 : 1; + uint8_t reserved2 : 2; + }; + static_assert(sizeof(TIFR4_reg_t) == 1, "invalid size of ATmega2560 TIFR4_reg_t"); + + struct TIFR5_reg_t { + uint8_t _TOV5 : 1; + uint8_t _OCF5A : 1; + uint8_t _OCF5B : 1; + uint8_t _OCF5C : 1; + uint8_t reserved1 : 1; + uint8_t _ICF5 : 1; + uint8_t reserved2 : 2; + }; + static_assert(sizeof(TIFR5_reg_t) == 1, "invalid size of ATmega2560 TIFR5_reg_t"); + +#endif // __AVR_TRM01__ + +#if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM03__) || defined(__AVR_TRM04__) || defined(__AVR_TRM05__) + + struct PCIFR_reg_t { + #if defined(__AVR_TRM01__) || defined(__AVR_TRM03__) + uint8_t _PCIF0 : 1; + uint8_t _PCIF1 : 1; + uint8_t _PCIF2 : 1; + uint8_t reserved1 : 5; + #elif defined(__AVR_TRM02__) || defined(__AVR_TRM05__) + uint8_t _PCIF0 : 1; + uint8_t _PCIF1 : 1; + uint8_t _PCIF2 : 1; + uint8_t _PCIF3 : 1; + uint8_t reserved1 : 4; + #elif defined(__AVR_TRM04__) + uint8_t _PCIF0 : 1; + uint8_t reserved1 : 7; + #endif + }; + static_assert(sizeof(PCIFR_reg_t) == 1, "invalid size of ATmega2560 PCIFR_reg_t"); + + struct EIFR_reg_t { + #if defined(__AVR_TRM01__) || defined(__AVR_TRM04__) + uint8_t _INTF0 : 1; + uint8_t _INTF1 : 1; + uint8_t _INTF2 : 1; + uint8_t _INTF3 : 1; + uint8_t _INTF4 : 1; + uint8_t _INTF5 : 1; + uint8_t _INTF6 : 1; + uint8_t _INTF7 : 1; + #elif defined(__AVR_TRM02__) || defined(__AVR_TRM05__) + uint8_t _INTF0 : 1; + uint8_t _INTF1 : 1; + uint8_t _INTF2 : 1; + uint8_t reserved1 : 5; + #elif defined(__AVR_TRM03__) + uint8_t _INTF0 : 1; + uint8_t _INTF1 : 1; + uint8_t reserved1 : 6; + #endif + }; + static_assert(sizeof(EIFR_reg_t) == 1, "invalid size of ATmega2560 EIFR_reg_t"); + + struct EIMSK_reg_t { + #if defined(__AVR_TRM01__) || defined(__AVR_TRM04__) + uint8_t _INT0 : 1; + uint8_t _INT1 : 1; + uint8_t _INT2 : 1; + uint8_t _INT3 : 1; + uint8_t _INT4 : 1; + uint8_t _INT5 : 1; + uint8_t _INT6 : 1; + uint8_t _INT7 : 1; + #elif defined(__AVR_TRM02__) || defined(__AVR_TRM05__) + uint8_t _INT0 : 1; + uint8_t _INT1 : 1; + uint8_t _INT2 : 1; + uint8_t reserved1 : 5; + #elif defined(__AVR_TRM03__) + uint8_t _INT0 : 1; + uint8_t _INT1 : 1; + uint8_t reserved1 : 6; + #endif + }; + static_assert(sizeof(EIMSK_reg_t) == 1, "invalid size of ATmega2560 EIMSK_reg_t"); + + struct EECR_reg_t { + uint8_t _EERE : 1; + uint8_t _EEPE : 1; + uint8_t _EEMPE : 1; + uint8_t _EERIE : 1; + uint8_t _EEPM0 : 1; + uint8_t _EEPM1 : 1; + uint8_t reserved1 : 2; + }; + static_assert(sizeof(EECR_reg_t) == 1, "invalid size of ATmega2560 EECR_reg_t"); + + struct EEAR_reg_t { + #if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM04__) || defined(__AVR_TRM05__) + uint16_t _EEAR : 12; + uint16_t reserved1 : 4; + #elif defined(__AVR_TRM03__) + #if defined(__AVR_ATmega88A__) || defined(__AVR_ATmega88PA__) || defined(__AVR_ATmega168A__) || defined(__AVR_ATmega168PA__) || defined(__AVR_ATmega328P__) + uint16_t _EEAR : 16; + #else + uint8_t _EEAR : 8; + uint8_t reserved1 : 8; + #endif + #endif + }; + static_assert(sizeof(EEAR_reg_t) == 2, "invalid size of ATmega2560 EEAR_reg_t"); + + struct GTCCR_reg_t { + uint8_t _PSRSYNC : 1; + uint8_t _PSRASY : 1; + uint8_t reserved1 : 5; + uint8_t _TSM : 1; + }; + static_assert(sizeof(GTCCR_reg_t) == 1, "invalid size of ATmega2560 GTCCR_reg_t"); + + struct SPCR_reg_t { + uint8_t _SPR : 2; + uint8_t _CPHA : 1; + uint8_t _CPOL : 1; + uint8_t _MSTR : 1; + uint8_t _DORD : 1; + uint8_t _SPE : 1; + uint8_t _SPIE : 1; + }; + static_assert(sizeof(SPCR_reg_t) == 1, "invalid size of ATmega2560 SPCR_reg_t"); + + struct SPSR_reg_t { + uint8_t _SPI2X : 1; + uint8_t reserved1 : 5; + uint8_t _WCOL : 1; + uint8_t _SPIF : 1; + }; + static_assert(sizeof(SPSR_reg_t) == 1, "invalid size of ATmega2560 SPSR_reg_t"); + + struct ACSR_reg_t { + uint8_t _ACIS : 2; + uint8_t _ACIC : 1; + uint8_t _ACIE : 1; + uint8_t _ACI : 1; + uint8_t _ACO : 1; + uint8_t _ACBG : 1; + uint8_t _ACD : 1; + }; + static_assert(sizeof(ACSR_reg_t) == 1, "invalid size of ATmega2560 ACSR_reg_t"); + + struct SMCR_reg_t { + uint8_t _SE : 1; + uint8_t _SM : 3; + uint8_t reserved1 : 4; + }; + static_assert(sizeof(SMCR_reg_t) == 1, "invalid size of ATmega2560 SMCR_reg_t"); + + struct MCUSR_reg_t { + #if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM04__) || defined(__AVR_TRM05__) + uint8_t _PORF : 1; + uint8_t _EXTRF : 1; + uint8_t _BORF : 1; + uint8_t _WDRF : 1; + uint8_t _JTRF : 1; + uint8_t reserved1 : 3; + #elif defined(__AVR_TRM03__) + uint8_t _PORF : 1; + uint8_t _EXTRF : 1; + uint8_t _BORF : 1; + uint8_t _WDRF : 1; + uint8_t reserved1 : 4; + #endif + }; + static_assert(sizeof(MCUSR_reg_t) == 1, "invalid size of ATmega2560 MCUSR_reg_t"); + + struct MCUCR_reg_t { + #if defined(__AVR_TRM01__) || defined(__AVR_TRM04__) + uint8_t _IVCE : 1; + uint8_t _IVSEL : 1; + uint8_t reserved1 : 2; + uint8_t _PUD : 1; + uint8_t reserved2 : 2; + uint8_t _JTD : 1; + #elif defined(__AVR_TRM02__) || defined(__AVR_TRM05__) + uint8_t _IVCE : 1; + uint8_t _IVSEL : 1; + uint8_t reserved1 : 2; + uint8_t _PUD : 1; + uint8_t _BODSE : 1; + uint8_t _BODS : 1; + uint8_t _JTD : 1; + #elif defined(__AVR_TRM03__) + uint8_t _IVCE : 1; + uint8_t _IVSEL : 1; + uint8_t reserved1 : 2; + uint8_t _PUD : 1; + uint8_t _BODSE : 1; + uint8_t _BODS : 1; + uint8_t reserved2 : 1; + #endif + }; + static_assert(sizeof(MCUCR_reg_t) == 1, "invalid size of ATmega2560 MCUCR_reg_t"); + + struct SPMCSR_reg_t { + #if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM04__) || defined(__AVR_TRM05__) + uint8_t _SPMEN : 1; + uint8_t _PGERS : 1; + uint8_t _PGWRT : 1; + uint8_t _BLBSET : 1; + uint8_t _RWWSRE : 1; + uint8_t _SIGRD : 1; + uint8_t _RWWSB : 1; + uint8_t _SPMIE : 1; + #elif defined(__AVR_TRM03__) + #if defined(__AVR_ATmega88A__) || defined(__AVR_ATmega88PA__) || defined(__AVR_ATmega168A__) || defined(__AVR_ATmega168PA__) || defined(__AVR_ATmega328P__) + uint8_t _SPMEN : 1; + uint8_t _PGERS : 1; + uint8_t _PGWRT : 1; + uint8_t _BLBSET : 1; + uint8_t _RWWSRE : 1; + uint8_t _SIGRD : 1; + uint8_t _RWWSB : 1; + uint8_t _SPMIE : 1; + #else + uint8_t _SPMEN : 1; + uint8_t _PGERS : 1; + uint8_t _PGWRT : 1; + uint8_t _BLBSET : 1; + uint8_t reserved1 : 1; + uint8_t _SIGRD : 1; + uint8_t reserved2 : 1; + uint8_t _SPMIE : 1; + #endif + #endif + }; + static_assert(sizeof(SPMCSR_reg_t) == 1, "invalid size of ATmega2560 SPMCSR_reg_t"); + +#endif // __AVR_TRM01__ || __AVR_TRM02__ || __AVR_TRM03__ || __AVR_TRM04__ + +#if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM04__) || defined(__AVR_TRM05__) + + struct RAMPZ_reg_t { + #if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM04__) + uint8_t _RAMPZ : 2; + uint8_t reserved1 : 6; + #elif defined(__AVR_TRM05__) + uint8_t _RAMPZ : 1; + uint8_t reserved1 : 7; + #endif + }; + static_assert(sizeof(RAMPZ_reg_t) == 1, "invalid size of ATmega2560 RAMPZ_reg_t"); + +#endif // __AVR_TRM01__ || __AVR_TRM02__ || __AVR_TRM04__ || __AVR_TRM05__ + +#ifdef __AVR_TRM01__ + + struct EIND_reg_t { + uint8_t _EIND0 : 1; + uint8_t reserved1 : 7; + }; + static_assert(sizeof(EIND_reg_t) == 1, "invalid size of ATmega2560 EIND_reg_t"); + +#endif // __AVR_TRM01__ + +#if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM03__) || defined(__AVR_TRM04__) || defined(__AVR_TRM05__) + + struct SP_reg_t { + #if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM04__) || defined(__AVR_TRM05__) + uint16_t _SP; + #elif defined(__AVR_TRM03__) + #if defined(__AVR_ATmega88A__) || defined(__AVR_ATmega88PA__) || defined(__AVR_ATmega168A__) || defined(__AVR_ATmega168PA__) || defined(__AVR_ATmega328P__) + uint16_t _SP : 11; + uint16_t reserved1 : 5; + #else + uint16_t _SP : 10; + uint16_t reserved1 : 6; + #endif + #endif + }; + static_assert(sizeof(SP_reg_t) == 2, "invalid size of ATmega2560 SP_reg_t"); + + struct SREG_reg_t { + uint8_t _C : 1; + uint8_t _Z : 1; + uint8_t _N : 1; + uint8_t _V : 1; + uint8_t _S : 1; + uint8_t _H : 1; + uint8_t _T : 1; + uint8_t _I : 1; + }; + static_assert(sizeof(SREG_reg_t) == 1, "invalid size of ATmega2560 SREG_reg_t"); + + struct WDTCSR_reg_t { + uint8_t _WDP0 : 1; + uint8_t _WDP1 : 1; + uint8_t _WDP2 : 1; + uint8_t _WDE : 1; + uint8_t _WDCE : 1; + uint8_t _WDP3 : 1; + uint8_t _WDIE : 1; + uint8_t _WDIF : 1; + }; + static_assert(sizeof(WDTCSR_reg_t) == 1, "invalid size of ATmega2560 WDTCSR_reg_t"); + + struct CLKPR_reg_t { + uint8_t _CLKPS : 4; + uint8_t reserved1 : 3; + uint8_t _CLKPCE : 1; + }; + static_assert(sizeof(CLKPR_reg_t) == 1, "invalid size of ATmega2560 CLKPR_reg_t"); + + struct PRR0_reg_t { + #ifdef __AVR_TRM01__ + uint8_t _PRADC : 1; + uint8_t _PRUSART0 : 1; + uint8_t _PRSPI : 1; + uint8_t _PRTIM1 : 1; + uint8_t reserved1 : 1; + uint8_t _PRTIM0 : 1; + uint8_t _PRTIM2 : 1; + uint8_t _PRTWI : 1; + #elif defined(__AVR_TRM02__) || defined(__AVR_TRM05__) + uint8_t _PRADC : 1; + uint8_t _PRUSART0 : 1; + uint8_t _PRSPI : 1; + uint8_t _PRTIM1 : 1; + uint8_t _PRUSART1 : 1; + uint8_t _PRTIM0 : 1; + uint8_t _PRTIM2 : 1; + uint8_t _PRTWI : 1; + #elif defined(__AVR_TRM03__) + uint8_t _PRADC : 1; + uint8_t _PRUSART0 : 1; + uint8_t _PRSPI : 1; + uint8_t _PRTIM1 : 1; + uint8_t reserved1 : 1; + uint8_t _PRTIM0 : 1; + uint8_t _PRTIM2 : 1; + uint8_t _PRTWI : 1; + #elif defined(__AVR_TRM04__) + uint8_t _PRADC : 1; + uint8_t reserved1 : 1; + uint8_t _PRSPI : 1; + uint8_t _PRTIM1 : 1; + uint8_t reserved2 : 1; + uint8_t _PRTIM0 : 1; + uint8_t _PRTIM2 : 1; + uint8_t _PRTWI : 1; + #endif + }; + static_assert(sizeof(PRR0_reg_t) == 1, "invalid size of ATmega2560 PRR0_reg_t"); + +#endif // __AVR_TRM01__ || __AVR_TRM02__ || __AVR_TRM03__ || __AVR_TRM04__ || __AVR_TRM05__ + +#if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM04__) + + struct PRR1_reg_t { + #ifdef __AVR_TRM01__ + uint8_t _PRUSART1 : 1; + uint8_t _PRUSART2 : 1; + uint8_t _PRUSART3 : 1; + uint8_t _PRTIM3 : 1; + uint8_t _PRTIM4 : 1; + uint8_t _PRTIM5 : 1; + uint8_t reserved1 : 2; + #elif defined(__AVR_TRM02__) + uint8_t _PRTIM3 : 1; + uint8_t reserved1 : 7; + #elif defined(__AVR_TRM04__) + uint8_t _PRUSART1 : 1; + uint8_t reserved1 : 2; + uint8_t _PRTIM3 : 1; + uint8_t reserved2 : 3; + uint8_t _PRUSB : 1; + #endif + }; + static_assert(sizeof(PRR1_reg_t) == 1, "invalid size of ATmega2560 PRR1_reg_t"); + +#endif // __AVR_TRM01__ || __AVR_TRM02__ || __AVR_TRM04__ + +#if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM03__) || defined(__AVR_TRM04__) || defined(__AVR_TRM05__) + + struct PCICR_reg_t { + #if defined(__AVR_TRM01__) || defined(__AVR_TRM03__) + uint8_t _PCIE0 : 1; + uint8_t _PCIE1 : 1; + uint8_t _PCIE2 : 1; + uint8_t reserved1 : 5; + #elif defined(__AVR_TRM02__) || defined(__AVR_TRM05__) + uint8_t _PCIE0 : 1; + uint8_t _PCIE1 : 1; + uint8_t _PCIE2 : 1; + uint8_t _PCIE3 : 1; + uint8_t reserved1 : 4; + #elif defined(__AVR_TRM04__) + uint8_t _PCIE0 : 1; + uint8_t reserved1 : 7; + #endif + }; + static_assert(sizeof(PCICR_reg_t) == 1, "invalid size of ATmega2560 PCICR_reg_t"); + + struct EICRA_reg_t { + #if defined(__AVR_TRM01__) || defined(__AVR_TRM04__) + uint8_t _ISC0 : 2; + uint8_t _ISC1 : 2; + uint8_t _ISC2 : 2; + uint8_t _ISC3 : 2; + #elif defined(__AVR_TRM02__) || defined(__AVR_TRM05__) + uint8_t _ISC0 : 2; + uint8_t _ISC1 : 2; + uint8_t _ISC2 : 2; + uint8_t reserved1 : 2; + #elif defined(__AVR_TRM03__) + uint8_t _ISC0 : 2; + uint8_t _ISC1 : 2; + uint8_t reserved1 : 4; + #endif + }; + static_assert(sizeof(EICRA_reg_t) == 1, "invalid size of ATmega2560 EICRA_reg_t"); + +#endif // __AVR_TRM01__ || __AVR_TRM02__ || __AVR_TRM03__ || __AVR_TRM04__ || __AVR_TRM05__ + +#if defined(__AVR_TRM01__) || defined(__AVR_TRM04__) + + struct EICRB_reg_t { + uint8_t _ISC4 : 2; + uint8_t _ISC5 : 2; + uint8_t _ISC6 : 2; + uint8_t _ISC7 : 2; + }; + static_assert(sizeof(EICRB_reg_t) == 1, "invalid size of ATmega2560 EICRB_reg_t"); + +#endif // __AVR_TRM01__ || __AVR_TRM04__ + +#if defined(__AVR_TRM03__) + + struct _bitPCMSK1_reg_t { + uint8_t val : 7; + uint8_t reserved1 : 1; + + bool getValue(uint8_t idx) { return val & (1 << idx); } + void setValue(uint8_t idx, bool value) { + if (value) + val |= (1 << idx); + else + val &= ~(1 << idx); + } + }; + +#endif // __AVR_TRM03__ + +#if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM03__) || defined(__AVR_TRM04__) || defined(__AVR_TRM05__) + + struct TIMSK0_reg_t { + #ifdef __AVR_TRM01__ + uint8_t _TOIE0 : 1; + uint8_t _OCIE0A : 1; + uint8_t _OCIE0B : 1; + uint8_t _OCIE0C : 1; + uint8_t reserved1 : 1; + uint8_t _ICIE0 : 1; + uint8_t reserved2 : 2; + #elif defined(__AVR_TRM02__) || defined(__AVR_TRM03__) || defined(__AVR_TRM04__) || defined(__AVR_TRM05__) + uint8_t _TOIE0 : 1; + uint8_t _OCIE0A : 1; + uint8_t _OCIE0B : 1; + uint8_t reserved1 : 5; + #endif + }; + static_assert(sizeof(TIMSK0_reg_t) == 1, "invalid size of ATmega2560 TIMSK0_reg_t"); + + struct TIMSK1_reg_t { + #if defined(__AVR_TRM01__) || defined(__AVR_TRM04__) + uint8_t _TOIE1 : 1; + uint8_t _OCIE1A : 1; + uint8_t _OCIE1B : 1; + uint8_t _OCIE1C : 1; + uint8_t reserved1 : 1; + uint8_t _ICIE1: 1; + uint8_t reserved2 : 2; + #elif defined(__AVR_TRM02__) || defined(__AVR_TRM03__) || defined(__AVR_TRM05__) + uint8_t _TOIE1 : 1; + uint8_t _OCIE1A : 1; + uint8_t _OCIE1B : 1; + uint8_t reserved1 : 2; + uint8_t _ICIE1: 1; + uint8_t reserved2 : 2; + #endif + }; + static_assert(sizeof(TIMSK1_reg_t) == 1, "invalid size of ATmega2560 TIMSK1_reg_t"); + + struct TIMSK2_reg_t { + uint8_t _TOIE2 : 1; + uint8_t _OCIE2A : 1; + uint8_t _OCIE2B : 1; + uint8_t reserved1 : 5; + }; + static_assert(sizeof(TIMSK2_reg_t) == 1, "invalid size of ATmega2560 TIMSK2_reg_t"); + +#endif // __AVR_TRM01__ || __AVR_TRM02__ || __AVR_TRM03__ || __AVR_TRM04__ || __AVR_TRM05__ + +#if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM04__) + + struct TIMSK3_reg_t { + #if defined(__AVR_TRM01__) || defined(__AVR_TRM04__) + uint8_t _TOIE3 : 1; + uint8_t _OCIE3A : 1; + uint8_t _OCIE3B : 1; + uint8_t _OCIE3C : 1; + uint8_t reserved1 : 1; + uint8_t _ICIE3 : 1; + uint8_t reserved2 : 2; + #elif defined(__AVR_TRM02__) + uint8_t _TOIE3 : 1; + uint8_t _OCIE3A : 1; + uint8_t _OCIE3B : 1; + uint8_t reserved1 : 2; + uint8_t _ICIE3 : 1; + uint8_t reserved2 : 2; + #endif + }; + static_assert(sizeof(TIMSK3_reg_t) == 1, "invalid size of ATmega2560 TIMSK3_reg_t"); + +#endif // __AVR_TRM01__ || __AVR_TRM02__ || __AVR_TRM04__ + +#ifdef __AVR_TRM01__ + + struct TIMSK4_reg_t { + uint8_t _TOIE4 : 1; + uint8_t _OCIE4A : 1; + uint8_t _OCIE4B : 1; + uint8_t _OCIE4C : 1; + uint8_t reserved1 : 1; + uint8_t _ICIE4 : 1; + uint8_t reserved2 : 2; + }; + static_assert(sizeof(TIMSK4_reg_t) == 1, "invalid size of ATmega2560 TIMSK4_reg_t"); + + struct TIMSK5_reg_t { + uint8_t _TOIE5 : 1; + uint8_t _OCIE5A : 1; + uint8_t _OCIE5B : 1; + uint8_t _OCIE5C : 1; + uint8_t reserved1 : 1; + uint8_t _ICIE5 : 1; + uint8_t reserved2 : 2; + }; + static_assert(sizeof(TIMSK5_reg_t) == 1, "invalid size of ATmega2560 TIMSK5_reg_t"); + +#endif // __AVR_TRM01__ + +#if defined(__AVR_TRM01__) || defined(__AVR_TRM04__) + + struct XMCRA_reg_t { + uint8_t _SRW0 : 2; + uint8_t _SRW1 : 2; + uint8_t _SRL : 3; + uint8_t _SRE : 1; + }; + static_assert(sizeof(XMCRA_reg_t) == 1, "invalid size of ATmega2560 XMCRA_reg_t"); + + struct XMCRB_reg_t { + uint8_t _XMM : 3; + uint8_t reserved1 : 4; + uint8_t _XMBK : 1; + }; + static_assert(sizeof(XMCRB_reg_t) == 1, "invalid size of ATmega2560 XMCRB_reg_t"); + +#endif // __AVR_TRM01__ || __AVR_TRM04__ + +#if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM03__) || defined(__AVR_TRM04__) || defined(__AVR_TRM05__) + + struct ADCSRA_reg_t { + uint8_t _ADPS : 3; + uint8_t _ADIE : 1; + uint8_t _ADIF : 1; + uint8_t _ADATE : 1; + uint8_t _ADSC : 1; + uint8_t _ADEN : 1; + }; + static_assert(sizeof(ADCSRA_reg_t) == 1, "invalid size of ATmega2560 ADCSRA_reg_t"); + + struct ADCSRB_reg_t { + #if defined(__AVR_TRM01__) || defined(__AVR_TRM04__) + uint8_t _ADTS : 3; + uint8_t _MUX5 : 1; + uint8_t reserved1 : 2; + uint8_t _ACME : 1; + uint8_t reserved2 : 1; + #elif defined(__AVR_TRM02__) || defined(__AVR_TRM03__) || defined(__AVR_TRM05__) + uint8_t _ADTS : 3; + uint8_t reserved1 : 3; + uint8_t _ACME : 1; + uint8_t reserved2 : 1; + #endif + }; + static_assert(sizeof(ADCSRB_reg_t) == 1, "invalid size of ATmega2560 ADCSRB_reg_t"); + + struct ADMUX_reg_t { + #if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM04__) || defined(__AVR_TRM05__) + uint8_t _MUX0 : 1; + uint8_t _MUX1 : 1; + uint8_t _MUX2 : 1; + uint8_t _MUX3 : 1; + uint8_t _MUX4 : 1; + uint8_t _ADLAR : 1; + uint8_t _REFS0 : 1; + uint8_t _REFS1 : 1; + #elif defined(__AVR_TRM03__) + uint8_t _MUX0 : 1; + uint8_t _MUX1 : 1; + uint8_t _MUX2 : 1; + uint8_t _MUX3 : 1; + uint8_t reserved1 : 1; + uint8_t _ADLAR : 1; + uint8_t _REFS0 : 1; + uint8_t _REFS1 : 1; + #endif + }; + static_assert(sizeof(ADMUX_reg_t) == 1, "invalid size of ATmega2560 ADMUX_reg_t"); + +#endif // __AVR_TRM01__ || __AVR_TRM02__ || __AVR_TRM03__ || __AVR_TRM04__ + +#ifdef __AVR_TRM01__ + + struct DIDR2_reg_t { + uint8_t _ADC8D : 1; + uint8_t _ADC9D : 1; + uint8_t _ADC10D : 1; + uint8_t _ADC11D : 1; + uint8_t _ADC12D : 1; + uint8_t _ADC13D : 1; + uint8_t _ADC14D : 1; + uint8_t _ADC15D : 1; + }; + static_assert(sizeof(DIDR2_reg_t) == 1, "invalid size of ATmega2560 DIDR2_reg_t"); + +#endif // __AVR_TRM01__ + +#if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM03__) || defined(__AVR_TRM04__) || defined(__AVR_TRM05__) + + struct DIDR0_reg_t { + #if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM04__) || defined(__AVR_TRM05__) + uint8_t _ADC0D : 1; + uint8_t _ADC1D : 1; + uint8_t _ADC2D : 1; + uint8_t _ADC3D : 1; + uint8_t _ADC4D : 1; + uint8_t _ADC5D : 1; + uint8_t _ADC6D : 1; + uint8_t _ADC7D : 1; + #elif defined(__AVR_TRM03__) + uint8_t _ADC0D : 1; + uint8_t _ADC1D : 1; + uint8_t _ADC2D : 1; + uint8_t _ADC3D : 1; + uint8_t _ADC4D : 1; + uint8_t _ADC5D : 1; + uint8_t reserved1 : 2; + #endif + }; + static_assert(sizeof(DIDR0_reg_t) == 1, "invalid size of ATmega2560 DIDR0_reg_t"); + + struct DIDR1_reg_t { + uint8_t _AIN0D : 1; + uint8_t _AIN1D : 1; + uint8_t reserved1 : 6; + }; + static_assert(sizeof(DIDR1_reg_t) == 1, "invalid size of ATmega2560 DIDR1_reg_t"); + + struct TCCRnA_reg_t { + #if defined(__AVR_TRM01__) || defined(__AVR_TRM04__) + uint8_t _WGMn0 : 1; + uint8_t _WGMn1 : 1; + uint8_t _COMnC : 2; + uint8_t _COMnB : 2; + uint8_t _COMnA : 2; + #elif defined(__AVR_TRM02__) || defined(__AVR_TRM03__) || defined(__AVR_TRM05__) + uint8_t _WGMn0 : 1; + uint8_t _WGMn1 : 1; + uint8_t reserved1 : 2; + uint8_t _COMnB : 2; + uint8_t _COMnA : 2; + #endif + }; + static_assert(sizeof(TCCRnA_reg_t) == 1, "invalid size of ATmega2560 TCCRnA_reg_t"); + + struct TCCRnB_reg_t { + uint8_t _CSn : 3; + uint8_t _WGMn2 : 1; + uint8_t _WGMn3 : 1; + uint8_t reserved1 : 1; + uint8_t _ICESn : 1; + uint8_t _ICNCn : 1; + }; + static_assert(sizeof(TCCRnB_reg_t) == 1, "invalid size of ATmega2560 TCCRnB_reg_t"); + + struct TCCRnC_reg_t { + #if defined(__AVR_TRM01__) || defined(__AVR_TRM04__) + uint8_t reserved1 : 5; + uint8_t _FOCnC : 1; + uint8_t _FOCnB : 1; + uint8_t _FOCnA : 1; + #elif defined(__AVR_TRM02__) || defined(__AVR_TRM03__) || defined(__AVR_TRM05__) + uint8_t reserved1 : 6; + uint8_t _FOCnB : 1; + uint8_t _FOCnA : 1; + #endif + }; + static_assert(sizeof(TCCRnC_reg_t) == 1, "invalid size of ATmega2560 TCCRnC_reg_t"); + + struct TIMER_dev_t { + TCCRnA_reg_t _TCCRnA; + TCCRnB_reg_t _TCCRnB; + TCCRnC_reg_t _TCCRnC; + uint8_t reserved1; + uint16_t _TCNTn; + uint16_t _ICRn; + uint16_t _OCRnA; + uint16_t _OCRnB; + #if defined(__AVR_TRM01__) || defined(__AVR_TRM04__) + uint16_t _OCRnC; + #endif + + inline void operator = ( const TIMER_dev_t& r ) volatile { + using namespace AVRHelpers; + dwrite(this->_TCCRnA, r._TCCRnA); + dwrite(this->_TCCRnB, r._TCCRnB); + dwrite(this->_TCCRnC, r._TCCRnC); + this->reserved1 = r.reserved1; + this->_TCNTn = r._TCNTn; + this->_ICRn = r._ICRn; + this->_OCRnA = r._OCRnA; + this->_OCRnB = r._OCRnB; + #if defined(__AVR_TRM01__) || defined(__AVR_TRM04__) + this->_OCRnC = r._OCRnC; + #endif + } + }; + #if defined(__AVR_TRM01__) || defined(__AVR_TRM04__) + static_assert(sizeof(TIMER_dev_t) == 14, "invalid size of ATmega2560 TIMER_dev_t"); + #elif defined(__AVR_TRM02__) || defined(__AVR_TRM03__) || defined(__AVR_TRM05__) + static_assert(sizeof(TIMER_dev_t) == 12, "invalid size of ATmega1284 TIMER_dev_t"); + #endif + + struct TCCRnA_8bit_reg_t { + uint8_t _WGMn0 : 1; + uint8_t _WGMn1 : 1; + uint8_t reserved1 : 2; + uint8_t _COMnB : 2; + uint8_t _COMnA : 2; + }; + static_assert(sizeof(TCCRnA_8bit_reg_t) == 1, "invalid size of ATmega2560 TCCRnA_8bit_reg_t"); + + struct TCCRnB_8bit_reg_t { + uint8_t _CSn : 3; + uint8_t _WGMn2 : 1; + uint8_t reserved1 : 2; + uint8_t _FOCnB : 1; + uint8_t _FOCnA : 1; + }; + static_assert(sizeof(TCCRnB_8bit_reg_t) == 1, "invalid size of ATmega2560 TCCRnB_8bit_reg_t"); + + struct TIMER_8bit_dev_t { + TCCRnA_8bit_reg_t _TCCRnA; + TCCRnB_8bit_reg_t _TCCRnB; + uint8_t _TCNTn; + uint8_t _OCRnA; + uint8_t _OCRnB; + + inline void operator = ( const TIMER_8bit_dev_t& r ) volatile { + using namespace AVRHelpers; + dwrite(this->_TCCRnA, r._TCCRnA); + dwrite(this->_TCCRnB, r._TCCRnB); + this->_TCNTn = r._TCNTn; + this->_OCRnA = r._OCRnA; + this->_OCRnB = r._OCRnB; + } + }; + static_assert(sizeof(TIMER_8bit_dev_t) == 5, "invalid size of ATmega2560 TIMER_8bit_dev_t"); + + struct ASSR_reg_t { + uint8_t _TCR2BUB : 1; + uint8_t _TCR2AUB : 1; + uint8_t _OCR2BUB : 1; + uint8_t _OCR2AUB : 1; + uint8_t _TCN2UB : 1; + uint8_t _AS2 : 1; + uint8_t _EXCLK : 1; + uint8_t reserved1 : 1; + }; + static_assert(sizeof(ASSR_reg_t) == 1, "invalid size of ATmega2560 ASSR_reg_t"); + + struct TWSR_reg_t { + uint8_t _TWPS0 : 1; + uint8_t _TWPS1 : 1; + uint8_t reserved1 : 1; + uint8_t _TWS3 : 1; + uint8_t _TWS4 : 1; + uint8_t _TWS5 : 1; + uint8_t _TWS6 : 1; + uint8_t _TWS7 : 1; + }; + static_assert(sizeof(TWSR_reg_t) == 1, "invalid size of ATmega2560 TWSR_reg_t"); + + struct TWAR_reg_t { + uint8_t _TWGCE : 1; + uint8_t _TWA : 7; + }; + static_assert(sizeof(TWAR_reg_t) == 1, "invalid size of ATmega2560 TWAR_reg_t"); + + struct TWCR_reg_t { + uint8_t _TWIE : 1; + uint8_t reserved1 : 1; + uint8_t _TWEN : 1; + uint8_t _TWWC : 1; + uint8_t _TWSTO : 1; + uint8_t _TWSTA : 1; + uint8_t _TWEA : 1; + uint8_t _TWINT : 1; + }; + static_assert(sizeof(TWCR_reg_t) == 1, "invalid size of ATmega2560 TWCR_reg_t"); + + struct TWAMR_reg_t { + uint8_t reserved1 : 1; + uint8_t _TWAM : 7; + }; + static_assert(sizeof(TWAMR_reg_t) == 1, "invalid size of ATmega2560 TWAMR_reg_t"); + + struct UBRRn_reg_t { + uint16_t _UBRR : 12; + uint16_t reserved1 : 4; + }; + static_assert(sizeof(UBRRn_reg_t) == 2, "invalid size of ATmega2560 UBRRn_reg_t)"); + + struct UCSRnC_reg_t { + #if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM03__) || defined(__AVR_TRM04__) + uint8_t _UCPOL : 1; + uint8_t _UCSZn0 : 1; + uint8_t _UCSZn1 : 1; + uint8_t _USBS : 1; + uint8_t _UPM : 2; + uint8_t _UMSEL : 2; + #elif defined(__AVR_TRM05__) + uint8_t _UCPOL : 1; + uint8_t _UCPHA : 1; + uint8_t _UDORD : 1; + uint8_t reserved1 : 3; + uint8_t _UMSEL : 2; + #endif + }; + static_assert(sizeof(UCSRnC_reg_t) == 1, "invalid size of ATmega2560 UCSRnC_reg_t"); + + struct UCSRnB_reg_t { + uint8_t _TXB8 : 1; + uint8_t _RXB8 : 1; + uint8_t _UCSZn2 : 1; + uint8_t _TXEN : 1; + uint8_t _RXEN : 1; + uint8_t _UDRIE : 1; + uint8_t _TXCIE : 1; + uint8_t _RXCIE : 1; + }; + static_assert(sizeof(UCSRnB_reg_t) == 1, "invalid size of ATmega2560 UCSRnB_reg_t"); + + struct UCSRnA_reg_t { + uint8_t _MPCM : 1; + uint8_t _U2X : 1; + uint8_t _UPE : 1; + uint8_t _DOR : 1; + uint8_t _FE : 1; + uint8_t _UDRE : 1; + uint8_t _TXC : 1; + uint8_t _RXC : 1; + }; + static_assert(sizeof(UCSRnA_reg_t) == 1, "invalid size of ATmega2560 UCSRnA_reg_t"); + + struct USART_dev_t { + UCSRnA_reg_t _UCSRnA; + UCSRnB_reg_t _UCSRnB; + UCSRnC_reg_t _UCSRnC; + uint8_t reserved1; + UBRRn_reg_t _UBRRn; + uint8_t _UDRn; + + inline void operator = ( const USART_dev_t& r ) volatile { + using namespace AVRHelpers; + dwrite(this->_UCSRnA, r._UCSRnA); + dwrite(this->_UCSRnB, r._UCSRnB); + dwrite(this->_UCSRnC, r._UCSRnC); + dwrite(this->reserved1, r.reserved1); + dwrite(this->_UBRRn, r._UBRRn); + dwrite(this->_UDRn, r._UDRn); + } + }; + static_assert(sizeof(USART_dev_t) == 7, "invalid size of ATmega2560 USART_dev_t"); + +#endif // __AVR_TRM01__ || __AVR_TRM02__ || __AVR_TRM03__ || __AVR_TRM04__ + +#ifdef __AVR_TRM04__ + + struct UHCON_reg_t { + uint8_t _SOFEN : 1; + uint8_t _RESET : 1; + uint8_t _RESUME : 1; + uint8_t reserved1 : 5; + }; + static_assert(sizeof(UHCON_reg_t) == 1, "invalid size of ATUSB90 UHCON_reg_t"); + + struct UHINT_reg_t { + uint8_t _DCONNI : 1; + uint8_t _DDISCI : 1; + uint8_t _RSTI : 1; + uint8_t _RSMEDI : 1; + uint8_t _RXRSMI : 1; + uint8_t _HSOFI : 1; + uint8_t _HWUPI : 1; + uint8_t reserved1 : 1; + }; + static_assert(sizeof(UHINT_reg_t) == 1, "invalid size of ATUSB90 UHINT_reg_t"); + + struct UHIEN_reg_t { + uint8_t _SUSPE : 1; + uint8_t _MSOFE : 1; + uint8_t _SOFE : 1; + uint8_t _EORSTE : 1; + uint8_t _WAKEUPE : 1; + uint8_t _EORSME : 1; + uint8_t _UPRSME : 1; + uint8_t reserved1 : 1; + }; + static_assert(sizeof(UHIEN_reg_t) == 1, "invalid size of ATUSB90 UHIEN_reg_t"); + + struct UHADDR_reg_t { + uint8_t _HADD : 7; + uint8_t reserved1 : 1; + }; + static_assert(sizeof(UHADDR_reg_t) == 1, "invalid size of ATUSB90 UHADDR_reg_t"); + + struct UHFNUM_reg_t { + uint16_t _FNUM : 11; + uint16_t reserved1 : 5; + }; + static_assert(sizeof(UHFNUM_reg_t) == 2, "invalid size of ATUSB90 UHFNUM_reg_t"); + + struct UPINTX_reg_t { + uint8_t _RXINI : 1; + uint8_t _RXSTALLI : 1; + uint8_t _TXOUTI : 1; + uint8_t _TXSTPI : 1; + uint8_t _PERRI : 1; + uint8_t _RWAL : 1; + uint8_t _NAKEDI : 1; + uint8_t _FIFOCON : 1; + }; + static_assert(sizeof(UPINTX_reg_t) == 1, "invalid size of ATUSB90 UPINTX_reg_t"); + + struct UPNUM_reg_t { + uint8_t _PNUM : 3; + uint8_t reserved1 : 5; + }; + static_assert(sizeof(UPNUM_reg_t) == 1, "invalid size of ATUSB90 UPNUM_reg_t"); + + struct UPRST_reg_t { + uint8_t _PRST : 7; + uint8_t reserved1 : 1; + }; + static_assert(sizeof(UPRST_reg_t) == 1, "invalid size of ATUSB90 UPRST_reg_t"); + + struct UPCONX_reg_t { + uint8_t _PEN : 1; + uint8_t reserved1 : 2; + uint8_t _RSTDT : 1; + uint8_t _AUTOSW : 1; + uint8_t _INMODE : 1; + uint8_t _PFREEZE : 1; + uint8_t reserved2 : 1; + }; + static_assert(sizeof(UPCONX_reg_t) == 1, "invalid size of ATUSB90 UPCONX_reg_t"); + + struct UPCFG0X_reg_t { + uint8_t _PEPNUM : 4; + uint8_t _PTOKEN : 2; + uint8_t _PTYPE : 2; + }; + static_assert(sizeof(UPCFG0X_reg_t) == 1, "invalid size of ATUSB90 UPCFG0_reg_t"); + + struct UPCFG1X_reg_t { + uint8_t reserved1 : 1; + uint8_t _ALLOC : 1; + uint8_t _PBK : 2; + uint8_t _PSIZE : 3; + uint8_t reserved2 : 1; + }; + static_assert(sizeof(UPCFG1X_reg_t) == 1, "invalid size of ATUSB90 UPCFG1X_reg_t"); + + struct UPSTAX_reg_t { + uint8_t _NBUSYBK : 2; + uint8_t _DTSEQ : 2; + uint8_t reserved1 : 1; + uint8_t _UNDERFI : 1; + uint8_t _OVERFI : 1; + uint8_t _CFGOK : 1; + }; + static_assert(sizeof(UPSTAX_reg_t) == 1, "invalid size of ATUSB90 UPSTAX_reg_t"); + + struct UPIENX_reg_t { + uint8_t _RXINE : 1; + uint8_t _RXSTALLE : 1; + uint8_t _TXOUTE : 1; + uint8_t _TXSTPE : 1; + uint8_t _PERRE : 1; + uint8_t reserved1 : 1; + uint8_t _NAKEDE : 1; + uint8_t _FLERRE : 1; + }; + static_assert(sizeof(UPIENX_reg_t) == 1, "invalid size of ATUSB90 UPIENX_reg_t"); + + struct UHWCON_reg_t { + uint8_t _UVREGE : 1; + uint8_t reserved1 : 3; + uint8_t _UVCONE : 1; + uint8_t reserved2 : 1; + uint8_t _UIDE : 1; + uint8_t _UIMOD : 1; + }; + static_assert(sizeof(UHWCON_reg_t) == 1, "invalid size of ATUSB90 UHWCON_reg_t"); + + struct USBCON_reg_t { + uint8_t _VBUSTE : 1; + uint8_t _IDTE : 1; + uint8_t reserved1 : 2; + uint8_t _OTGPADE : 1; + uint8_t _FRZCLK : 1; + uint8_t _HOST : 1; + uint8_t _USBE : 1; + }; + static_assert(sizeof(USBCON_reg_t) == 1, "invalid size of ATUSB90 USBCON_reg_t"); + + struct USBSTA_reg_t { + uint8_t _VBUS : 1; + uint8_t _ID : 1; + uint8_t reserved1 : 1; + uint8_t _SPEED : 1; + uint8_t reserved2 : 4; + }; + static_assert(sizeof(USBSTA_reg_t) == 1, "invalid size of ATUSB90 USBSTA_reg_t"); + + struct USBINT_reg_t { + uint8_t _VBUSTI : 1; + uint8_t _IDTI : 1; + uint8_t reserved1 : 6; + }; + static_assert(sizeof(USBINT_reg_t) == 1, "invalid size of ATUSB90 USBINT_reg_t"); + + struct UDPADD_reg_t { + uint16_t _DPADD : 11; + uint16_t reserved1 : 4; + uint16_t _DPACC : 1; + }; + static_assert(sizeof(UDPADD_reg_t) == 2, "invalid size of ATUSB90 UDPADD_reg_t"); + + struct OTGCON_reg_t { + uint8_t _VBUSRQC : 1; + uint8_t _VBUSREQ : 1; + uint8_t _VBUSHWC : 1; + uint8_t _SRPSEL : 1; + uint8_t _SRPREQ : 1; + uint8_t _HNPREQ : 1; + uint8_t reserved1 : 1; + uint8_t _zero : 1; + }; + static_assert(sizeof(OTGCON_reg_t) == 1, "invalid size of ATUSB90 OTGCON_reg_t"); + + struct OTGIEN_reg_t { + uint8_t _SRPE : 1; + uint8_t _VBERRE : 1; + uint8_t _BCERRE : 1; + uint8_t _ROLEEXE : 1; + uint8_t _HNPERRE : 1; + uint8_t _STOE : 1; + uint8_t reserved1 : 2; + }; + static_assert(sizeof(OTGIEN_reg_t) == 1, "invalid size of ATUSB90 OTGIEN_reg_t"); + + struct OTGINT_reg_t { + uint8_t _SRPI : 1; + uint8_t _VBERRI : 1; + uint8_t _BCERRI : 1; + uint8_t _ROLEEXI : 1; + uint8_t _HNPERRI : 1; + uint8_t _STOI : 1; + uint8_t reserved1 : 2; + }; + static_assert(sizeof(OTGINT_reg_t) == 1, "invalid size of ATUSB90 OTGINT_reg_t"); + + struct UDCON_reg_t { + uint8_t _DETACH : 1; + uint8_t _RMWKUP : 1; + uint8_t _LSM : 1; + uint8_t reserved1 : 5; + }; + static_assert(sizeof(UDCON_reg_t) == 1, "invalid size of ATUSB90 UDCON_reg_t"); + + struct UDINT_reg_t { + uint8_t _SUSPI : 1; + uint8_t _MSOFI : 1; + uint8_t _SOFI : 1; + uint8_t _EORSTI : 1; + uint8_t _WAKEUPI : 1; + uint8_t _EORSMI : 1; + uint8_t _UPRSMI : 1; + uint8_t reserved1 : 1; + }; + static_assert(sizeof(UDINT_reg_t) == 1, "invalid size of ATUSB90 UDINT_reg_t"); + + struct UDIEN_reg_t { + uint8_t _SUSPE : 1; + uint8_t _MSOFE : 1; + uint8_t _SOFE : 1; + uint8_t _EORSTE : 1; + uint8_t _WAKEUPE : 1; + uint8_t _EORSME : 1; + uint8_t _UPRSME : 1; + uint8_t reserved1 : 1; + }; + static_assert(sizeof(UDIEN_reg_t) == 1, "invalid size of ATUSB90 UDIEN_reg_t"); + + struct UDADDR_reg_t { + uint8_t _UADD : 7; + uint8_t _ADDEN : 1; + }; + static_assert(sizeof(UDADDR_reg_t) == 1, "invalid size of ATUSB90 UADDR_reg_t"); + + struct UDFNUM_reg_t { + uint16_t _FNUM : 11; + uint16_t reserved1 : 5; + }; + static_assert(sizeof(UDFNUM_reg_t) == 2, "invalid size of ATUSB90 UDFNUM_reg_t"); + + struct UDMFN_reg_t { + uint8_t reserved1 : 4; + uint8_t _FNCERR : 1; + uint8_t reserved2 : 3; + }; + static_assert(sizeof(UDMFN_reg_t) == 1, "invalid size of ATUSB90 UDMFN_reg_t"); + + struct UDTST_reg_t { + uint8_t reserved1 : 2; + uint8_t _TSTJ : 1; + uint8_t _TSTK : 1; + uint8_t _TSTPCKT : 1; + uint8_t _OPMODE2 : 1; + uint8_t reserved2 : 2; + }; + static_assert(sizeof(UDTST_reg_t) == 1, "invalid size of ATUSB90 UDTST_reg_t"); + + struct UEINTX_reg_t { + uint8_t _TXINI : 1; + uint8_t _STALLEDI : 1; + uint8_t _RXOUTI : 1; + uint8_t _RXSTPI : 1; + uint8_t _NAKOUTI : 1; + uint8_t _RWAL : 1; + uint8_t _NAKINI : 1; + uint8_t _FIFOCON : 1; + }; + static_assert(sizeof(UEINTX_reg_t) == 1, "invalid size of ATUSB90 UEINTX_reg_t"); + + struct UENUM_reg_t { + uint8_t _EPNUM : 3; + uint8_t reserved1 : 5; + }; + static_assert(sizeof(UENUM_reg_t) == 1, "invalid size of ATUSB90 UENUM_reg_t"); + + struct UERST_reg_t { + uint8_t _EPRST : 7; + uint8_t reserved1 : 1; + }; + static_assert(sizeof(UERST_reg_t) == 1, "invalid size of ATUSB90 UERST_reg_t"); + + struct UECONX_reg_t { + uint8_t _EPEN : 1; + uint8_t reserved1 : 2; + uint8_t _RSTDT : 1; + uint8_t _STALLRQC : 1; + uint8_t _STALLRQ : 1; + uint8_t reserved2 : 2; + }; + static_assert(sizeof(UECONX_reg_t) == 1, "invalid size of ATUSB90 UECONX_reg_t"); + + struct UECFG0X_reg_t { + uint8_t _EPDIR : 1; + uint8_t _NYETSDIS : 1; + uint8_t _AUTOSW : 1; + uint8_t _ISOSW : 1; + uint8_t reserved1 : 2; + uint8_t _EPTYPE : 2; + }; + static_assert(sizeof(UECFG0X_reg_t) == 1, "invalid size of ATUSB90 UECFG0X_reg_t"); + + struct UECFG1X_reg_t { + uint8_t reserved1 : 1; + uint8_t _ALLOC : 1; + uint8_t _EPBK : 2; + uint8_t _EPSIZE : 3; + uint8_t reserved2 : 1; + }; + static_assert(sizeof(UECFG1X_reg_t) == 1, "invalid size of ATUSB90 UECFG1X_reg_t"); + + struct UESTA0X_reg_t { + uint8_t _NBUSYBK : 2; + uint8_t _DTSEQ : 2; + uint8_t _ZLPSEEN : 1; + uint8_t _UNDERFI : 1; + uint8_t _OVERFI : 1; + uint8_t _CFGOK : 1; + }; + static_assert(sizeof(UESTA0X_reg_t) == 1, "invalid size of ATUSB90 UESTA0X_reg_t"); + + struct UESTA1X_reg_t { + uint8_t _CURRBK : 2; + uint8_t _CTRLDIR : 1; + uint8_t reserved1 : 5; + }; + static_assert(sizeof(UESTA1X_reg_t) == 1, "invalid size of ATUSB90 UESTA1X_reg_t"); + + struct UEIENX_reg_t { + uint8_t _TXINE : 1; + uint8_t _STALLEDE : 1; + uint8_t _RXOUTE : 1; + uint8_t _RXSTPE : 1; + uint8_t _NAKOUTE : 1; + uint8_t reserved1 : 1; + uint8_t _NAKINE : 1; + uint8_t _FLERRE : 1; + }; + static_assert(sizeof(UEIENX_reg_t) == 1, "invalid size of ATUSB90 UEIENX_reg_t"); + + struct UEBCX_reg_t { + uint16_t _BYCT : 11; + uint16_t reserved1 : 5; + }; + static_assert(sizeof(UEBCX_reg_t) == 2, "invalid size of ATUSB90 UEBCX_reg_t"); + + struct UEINT_reg_t { + uint8_t _EPINT : 7; + uint8_t reserved1 : 1; + }; + static_assert(sizeof(UEINT_reg_t) == 1, "invalid size of ATUSB90 UEINT_reg_t"); + + struct UPERRX_reg_t { + uint8_t _DATATGL : 1; + uint8_t _DATAPID : 1; + uint8_t _PID : 1; + uint8_t _TIMEOUT : 1; + uint8_t _CRC16 : 1; + uint8_t _COUNTER : 2; + uint8_t reserved1 : 1; + }; + static_assert(sizeof(UPERRX_reg_t) == 1, "invalid size of ATUSB90 UPERRX_reg_t"); + + struct UPBCX_reg_t { + uint16_t _PBYCT : 11; + uint16_t reserved1 : 5; + }; + static_assert(sizeof(UPBCX_reg_t) == 2, "invalid size of ATUSB90 UPBCX_reg_t"); + + struct OTGTCON_reg_t { + uint8_t _VALUE : 2; + uint8_t reserved1 : 3; + uint8_t _PAGE : 2; + uint8_t _one : 1; + }; + static_assert(sizeof(OTGTCON_reg_t) == 1, "invalid size of ATUSB90 OTGTCON_reg_t"); + + struct PLLCSR_reg_t { + uint8_t _PLOCK : 1; + uint8_t _PLLE : 1; + uint8_t _PLLP : 3; + uint8_t reserved1 : 3; + }; + static_assert(sizeof(PLLCSR_reg_t) == 1, "invalid size of ATUSB90 PLLCSR_reg_t"); + +#endif // __AVR_TRM04__ + +/** + * REGISTER MEMORY MAP + */ + +#define __AVR_DEFREG(tn,n,a) static volatile tn& n = *(tn*)a +#define _AVR_DEFREG(n,a) __AVR_DEFREG(n##_reg_t, _##n, a) + +#ifdef __AVR_TRM01__ + // page 399ff of ATmega640-1280-1281-2560-2561-Datasheet-DS40002211A.pdf + + __AVR_DEFREG(PORT_dev_t, _PORTA, 0x20); + __AVR_DEFREG(PORT_dev_t, _PORTB, 0x23); + __AVR_DEFREG(PORT_dev_t, _PORTC, 0x26); + __AVR_DEFREG(PORT_dev_t, _PORTD, 0x29); + __AVR_DEFREG(PORT_dev_t, _PORTE, 0x2C); + __AVR_DEFREG(PORT_dev_t, _PORTF, 0x2F); + __AVR_DEFREG(PORTG_dev_t, _PORTG, 0x32); + __AVR_DEFREG(PORT_dev_t, _PORTH, 0x100); + __AVR_DEFREG(PORT_dev_t, _PORTJ, 0x103); + __AVR_DEFREG(PORT_dev_t, _PORTK, 0x106); + __AVR_DEFREG(PORT_dev_t, _PORTL, 0x109); + __AVR_DEFREG(TIFR0_reg_t, _TIFR0, 0x35); + __AVR_DEFREG(TIFR1_reg_t, _TIFR1, 0x36); + __AVR_DEFREG(TIFR2_reg_t, _TIFR2, 0x37); + __AVR_DEFREG(TIFR3_reg_t, _TIFR3, 0x38); + __AVR_DEFREG(TIFR4_reg_t, _TIFR4, 0x39); + __AVR_DEFREG(TIFR5_reg_t, _TIFR5, 0x3A); + __AVR_DEFREG(PCIFR_reg_t, _PCIFR, 0x3B); + __AVR_DEFREG(EIFR_reg_t, _EIFR, 0x3C); + __AVR_DEFREG(EIMSK_reg_t, _EIMSK, 0x3D); + __AVR_DEFREG(_bit_reg_t, _GPIOR0, 0x3E); + __AVR_DEFREG(EECR_reg_t, _EECR, 0x3F); + __AVR_DEFREG(uint8_t, _EEDR, 0x40); + __AVR_DEFREG(EEAR_reg_t, _EEAR, 0x41); + __AVR_DEFREG(GTCCR_reg_t, _GTCCR, 0x43); + __AVR_DEFREG(TIMER_8bit_dev_t, TIMER0, 0x44); + __AVR_DEFREG(_bit_reg_t, _GPIOR1, 0x4A); + __AVR_DEFREG(_bit_reg_t, _GPIOR2, 0x4B); + __AVR_DEFREG(SPCR_reg_t, _SPCR, 0x4C); + __AVR_DEFREG(SPSR_reg_t, _SPSR, 0x4D); + __AVR_DEFREG(uint8_t, _SPDR, 0x4E); + __AVR_DEFREG(ACSR_reg_t, _ACSR, 0x50); + __AVR_DEFREG(_bit_reg_t, _OCDR, 0x51); + __AVR_DEFREG(SMCR_reg_t, _SMCR, 0x53); + __AVR_DEFREG(MCUSR_reg_t, _MCUSR, 0x54); + __AVR_DEFREG(MCUCR_reg_t, _MCUCR, 0x55); + __AVR_DEFREG(SPMCSR_reg_t, _SPMCSR, 0x57); + __AVR_DEFREG(RAMPZ_reg_t, _RAMPZ, 0x5B); + __AVR_DEFREG(EIND_reg_t, _EIND, 0x5C); + __AVR_DEFREG(SP_reg_t, _SP, 0x5D); + __AVR_DEFREG(SREG_reg_t, _SREG, 0x5F); + __AVR_DEFREG(WDTCSR_reg_t, _WDTCSR, 0x60); + __AVR_DEFREG(CLKPR_reg_t, _CLKPR, 0x61); + __AVR_DEFREG(PRR0_reg_t, _PRR0, 0x64); + __AVR_DEFREG(PRR1_reg_t, _PRR1, 0x65); + __AVR_DEFREG(uint8_t, _OSCCAL, 0x66); + __AVR_DEFREG(PCICR_reg_t, _PCICR, 0x68); + __AVR_DEFREG(EICRA_reg_t, _EICRA, 0x69); + __AVR_DEFREG(EICRB_reg_t, _EICRB, 0x6A); + __AVR_DEFREG(_bit_reg_t, _PCMSK0, 0x6B); + __AVR_DEFREG(_bit_reg_t, _PCMSK1, 0x6C); + __AVR_DEFREG(_bit_reg_t, _PCMSK2, 0x6D); + __AVR_DEFREG(TIMSK0_reg_t, _TIMSK0, 0x6E); + __AVR_DEFREG(TIMSK1_reg_t, _TIMSK1, 0x6F); + __AVR_DEFREG(TIMSK2_reg_t, _TIMSK2, 0x70); + __AVR_DEFREG(TIMSK3_reg_t, _TIMSK3, 0x71); + __AVR_DEFREG(TIMSK4_reg_t, _TIMSK4, 0x72); + __AVR_DEFREG(TIMSK5_reg_t, _TIMSK5, 0x73); + __AVR_DEFREG(XMCRA_reg_t, _XMCRA, 0x74); + __AVR_DEFREG(XMCRB_reg_t, _XMCRB, 0x75); + __AVR_DEFREG(uint16_t, _ADC, 0x78); + __AVR_DEFREG(ADCSRA_reg_t, _ADCSRA, 0x7A); + __AVR_DEFREG(ADCSRB_reg_t, _ADCSRB, 0x7B); + __AVR_DEFREG(ADMUX_reg_t, _ADMUX, 0x7C); + __AVR_DEFREG(DIDR2_reg_t, _DIDR2, 0x7D); + __AVR_DEFREG(DIDR0_reg_t, _DIDR0, 0x7E); + __AVR_DEFREG(DIDR1_reg_t, _DIDR1, 0x7F); + __AVR_DEFREG(TIMER_dev_t, TIMER1, 0x80); + __AVR_DEFREG(TIMER_dev_t, TIMER3, 0x90); + __AVR_DEFREG(TIMER_dev_t, TIMER4, 0xA0); + __AVR_DEFREG(TIMER_dev_t, TIMER5, 0x120); + __AVR_DEFREG(TIMER_8bit_dev_t, _TIMER2, 0xB0); + __AVR_DEFREG(ASSR_reg_t, _ASSR, 0xB6); + __AVR_DEFREG(uint8_t, _TWBR, 0xB8); + __AVR_DEFREG(TWSR_reg_t, _TWSR, 0xB9); + __AVR_DEFREG(TWAR_reg_t, _TWAR, 0xBA); + __AVR_DEFREG(uint8_t, _TWDR, 0xBB); + __AVR_DEFREG(TWCR_reg_t, _TWCR, 0xBC); + __AVR_DEFREG(TWAMR_reg_t, _TWAMR, 0xBD); + __AVR_DEFREG(USART_dev_t, USART0, 0xC0); + __AVR_DEFREG(USART_dev_t, USART1, 0xC8); + __AVR_DEFREG(USART_dev_t, USART2, 0xD0); + __AVR_DEFREG(USART_dev_t, USART3, 0x130); + +#elif defined(__AVR_TRM02__) + // page 637ff of ATmega164A_PA-324A_PA-644A_PA-1284_P_Data-Sheet-40002070B.pdf + __AVR_DEFREG(PORT_dev_t, _PORTA, 0x20); + __AVR_DEFREG(PORT_dev_t, _PORTB, 0x23); + __AVR_DEFREG(PORT_dev_t, _PORTC, 0x26); + __AVR_DEFREG(PORT_dev_t, _PORTD, 0x29); + __AVR_DEFREG(TIFR0_reg_t, _TIFR0, 0x35); + __AVR_DEFREG(TIFR1_reg_t, _TIFR1, 0x36); + __AVR_DEFREG(TIFR2_reg_t, _TIFR2, 0x37); + __AVR_DEFREG(TIFR3_reg_t, _TIFR3, 0x38); + __AVR_DEFREG(PCIFR_reg_t, _PCIFR, 0x3B); + __AVR_DEFREG(EIFR_reg_t, _EIFR, 0x3C); + __AVR_DEFREG(EIMSK_reg_t, _EIMSK, 0x3D); + __AVR_DEFREG(_bit_reg_t, _GPIOR0, 0x3E); + __AVR_DEFREG(EECR_reg_t, _EECR, 0x3F); + __AVR_DEFREG(uint8_t, _EEDR, 0x40); + __AVR_DEFREG(EEAR_reg_t, _EEAR, 0x41); + __AVR_DEFREG(GTCCR_reg_t, _GTCCR, 0x43); + __AVR_DEFREG(TIMER_8bit_dev_t, TIMER0, 0x44); + __AVR_DEFREG(_bit_reg_t, _GPIOR1, 0x4A); + __AVR_DEFREG(_bit_reg_t, _GPIOR2, 0x4B); + __AVR_DEFREG(SPCR_reg_t, _SPCR, 0x4C); + __AVR_DEFREG(SPSR_reg_t, _SPSR, 0x4D); + __AVR_DEFREG(uint8_t, _SPDR, 0x4E); + __AVR_DEFREG(ACSR_reg_t, _ACSR, 0x50); + __AVR_DEFREG(SMCR_reg_t, _SMCR, 0x53); + __AVR_DEFREG(MCUSR_reg_t, _MSUSR, 0x54); + __AVR_DEFREG(MCUCR_reg_t, _MCUCR, 0x55); + __AVR_DEFREG(SPMCSR_reg_t, _SPMCSR, 0x57); + __AVR_DEFREG(SP_reg_t, _SP, 0x5D); + __AVR_DEFREG(SREG_reg_t, _SREG, 0x5F); + __AVR_DEFREG(WDTCSR_reg_t, _WDTCSR, 0x60); + __AVR_DEFREG(CLKPR_reg_t, _CLKPR, 0x61); + __AVR_DEFREG(PRR0_reg_t, _PRR0, 0x64); + __AVR_DEFREG(PRR1_reg_t, _PRR1, 0x65); + __AVR_DEFREG(uint8_t, _OSCCAL, 0x66); + __AVR_DEFREG(PCICR_reg_t, _PCICR, 0x68); + __AVR_DEFREG(EICRA_reg_t, _EICRA, 0x69); + __AVR_DEFREG(_bit_reg_t, _PCMSK0, 0x6B); + __AVR_DEFREG(_bit_reg_t, _PCMSK1, 0x6C); + __AVR_DEFREG(_bit_reg_t, _PCMSK2, 0x6D); + __AVR_DEFREG(TIMSK0_reg_t, _TIMSK0, 0x6E); + __AVR_DEFREG(TIMSK1_reg_t, _TIMSK1, 0x6F); + __AVR_DEFREG(TIMSK2_reg_t, _TIMSK2, 0x70); + __AVR_DEFREG(TIMSK3_reg_t, _TIMSK3, 0x71); + __AVR_DEFREG(_bit_reg_t, _PCMSK3, 0x73); + __AVR_DEFREG(uint16_t, _ADC, 0x78); + __AVR_DEFREG(ADCSRA_reg_t, _ADCSRA, 0x7A); + __AVR_DEFREG(ADCSRB_reg_t, _ADCSRB, 0x7B); + __AVR_DEFREG(ADMUX_reg_t, _ADMUX, 0x7C); + __AVR_DEFREG(DIDR0_reg_t, _DIDR0, 0x7E); + __AVR_DEFREG(DIDR1_reg_t, _DIDR1, 0x7F); + __AVR_DEFREG(TIMER_dev_t, TIMER1, 0x80); + __AVR_DEFREG(TIMER_dev_t, TIMER3, 0x90); + __AVR_DEFREG(TIMER_8bit_dev_t, _TIMER2, 0xB0); + __AVR_DEFREG(ASSR_reg_t, _ASSR, 0xB6); + __AVR_DEFREG(uint8_t, _TWBR, 0xB8); + __AVR_DEFREG(TWSR_reg_t, _TWSR, 0xB9); + __AVR_DEFREG(TWAR_reg_t, _TWAR, 0xBA); + __AVR_DEFREG(uint8_t, _TWDR, 0xBB); + __AVR_DEFREG(TWCR_reg_t, _TWCR, 0xBC); + __AVR_DEFREG(TWAMR_reg_t, _TWAMR, 0xBD); + __AVR_DEFREG(USART_dev_t, USART0, 0xC0); + __AVR_DEFREG(USART_dev_t, USART1, 0xC8); + +#elif defined(__AVR_TRM03__) + // page 621ff of ATmega48A-PA-88A-PA-168A-PA-328-P-DS-DS40002061B.pdf + __AVR_DEFREG(PORT_dev_t, _PORTB, 0x23); + __AVR_DEFREG(PORTC_dev_t, _PORTC, 0x26); + __AVR_DEFREG(PORT_dev_t, _PORTD, 0x29); + __AVR_DEFREG(TIFR0_reg_t, _TIFR0, 0x35); + __AVR_DEFREG(TIFR1_reg_t, _TIFR1, 0x36); + __AVR_DEFREG(TIFR2_reg_t, _TIFR2, 0x37); + __AVR_DEFREG(PCIFR_reg_t, _PCIFR, 0x3B); + __AVR_DEFREG(EIFR_reg_t, _EIFR, 0x3C); + __AVR_DEFREG(EIMSK_reg_t, _EIMSK, 0x3D); + __AVR_DEFREG(_bit_reg_t, _GPIOR0, 0x3E); + __AVR_DEFREG(EECR_reg_t, _EECR, 0x3F); + __AVR_DEFREG(uint8_t, _EEDR, 0x40); + __AVR_DEFREG(EEAR_reg_t, _EEAR, 0x41); + __AVR_DEFREG(GTCCR_reg_t, _GTCCR, 0x43); + __AVR_DEFREG(TIMER_8bit_dev_t, TIMER0, 0x44); + __AVR_DEFREG(_bit_reg_t, _GPIOR1, 0x4A); + __AVR_DEFREG(_bit_reg_t, _GPIOR2, 0x4B); + __AVR_DEFREG(SPCR_reg_t, _SPCR, 0x4C); + __AVR_DEFREG(SPSR_reg_t, _SPSR, 0x4D); + __AVR_DEFREG(uint8_t, _SPDR, 0x4E); + __AVR_DEFREG(ACSR_reg_t, _ACSR, 0x50); + __AVR_DEFREG(SMCR_reg_t, _SMCR, 0x53); + __AVR_DEFREG(MCUSR_reg_t, _MSUCR, 0x54); + __AVR_DEFREG(MCUCR_reg_t, _MCUCR, 0x55); + __AVR_DEFREG(SPMCSR_reg_t, _SPMCSR, 0x57); + __AVR_DEFREG(SP_reg_t, _SP, 0x5D); + __AVR_DEFREG(SREG_reg_t, _SREG, 0x5F); + __AVR_DEFREG(WDTCSR_reg_t, _WDTCSR, 0x60); + __AVR_DEFREG(CLKPR_reg_t, _CLKPR, 0x61); + __AVR_DEFREG(PRR0_reg_t, _PRR0, 0x64); + __AVR_DEFREG(uint8_t, _OSCCAL, 0x66); + __AVR_DEFREG(PCICR_reg_t, _PCICR, 0x68); + __AVR_DEFREG(EICRA_reg_t, _EICRA, 0x69); + __AVR_DEFREG(_bit_reg_t, _PCMSK0, 0x6B); + __AVR_DEFREG(_bitPCMSK1_reg_t, _PCMSK1, 0x6C); + __AVR_DEFREG(_bit_reg_t, _PCMSK2, 0x6D); + __AVR_DEFREG(TIMSK0_reg_t, _TIMSK0, 0x6E); + __AVR_DEFREG(TIMSK1_reg_t, _TIMSK1, 0x6F); + __AVR_DEFREG(TIMSK2_reg_t, _TIMSK2, 0x70); + __AVR_DEFREG(uint16_t, _ADC, 0x78); + __AVR_DEFREG(ADCSRA_reg_t, _ADCSRA, 0x7A); + __AVR_DEFREG(ADCSRB_reg_t, _ADCSRB, 0x7B); + __AVR_DEFREG(ADMUX_reg_t, _ADMUX, 0x7C); + __AVR_DEFREG(DIDR0_reg_t, _DIDR0, 0x7E); + __AVR_DEFREG(DIDR1_reg_t, _DIDR1, 0x7F); + __AVR_DEFREG(TIMER_dev_t, TIMER1, 0x80); + __AVR_DEFREG(TIMER_8bit_dev_t, _TIMER2, 0xB0); + __AVR_DEFREG(ASSR_reg_t, _ASSR, 0xB6); + __AVR_DEFREG(uint8_t, _TWBR, 0xB8); + __AVR_DEFREG(TWSR_reg_t, _TWSR, 0xB9); + __AVR_DEFREG(TWAR_reg_t, _TWAR, 0xBA); + __AVR_DEFREG(uint8_t, _TWDR, 0xBB); + __AVR_DEFREG(TWCR_reg_t, _TWCR, 0xBC); + __AVR_DEFREG(TWAMR_reg_t, _TWAMR, 0xBD); + __AVR_DEFREG(USART_dev_t, USART0, 0xC0); + +#elif defined(__AVR_TRM04__) + __AVR_DEFREG(PORT_dev_t, _PORTA, 0x20); + __AVR_DEFREG(PORT_dev_t, _PORTB, 0x23); + __AVR_DEFREG(PORT_dev_t, _PORTC, 0x26); + __AVR_DEFREG(PORT_dev_t, _PORTD, 0x29); + __AVR_DEFREG(PORT_dev_t, _PORTE, 0x2C); + __AVR_DEFREG(PORT_dev_t, _PORTF, 0x2F); + __AVR_DEFREG(TIFR0_reg_t, _TIFR0, 0x35); + __AVR_DEFREG(TIFR1_reg_t, _TIFR1, 0x36); + __AVR_DEFREG(TIFR2_reg_t, _TIFR2, 0x37); + __AVR_DEFREG(TIFR3_reg_t, _TIFR3, 0x38); + __AVR_DEFREG(PCIFR_reg_t, _PCIFR, 0x3B); + __AVR_DEFREG(EIFR_reg_t, _EIFR, 0x3C); + __AVR_DEFREG(EIMSK_reg_t, _EIMSK, 0x3D); + __AVR_DEFREG(_bit_reg_t, _GPIOR0, 0x3E); + __AVR_DEFREG(EECR_reg_t, _EECR, 0x3F); + __AVR_DEFREG(uint8_t, _EEDR, 0x40); + __AVR_DEFREG(EEAR_reg_t, _EEAR, 0x41); + __AVR_DEFREG(GTCCR_reg_t, _GTCCR, 0x43); + __AVR_DEFREG(TIMER_8bit_dev_t, TIMER0, 0x44); + __AVR_DEFREG(PLLCSR_reg_t, _PLLCSR, 0x49); + __AVR_DEFREG(_bit_reg_t, _GPIOR1, 0x4A); + __AVR_DEFREG(_bit_reg_t, _GPIOR2, 0x4B); + __AVR_DEFREG(SPCR_reg_t, _SPCR, 0x4C); + __AVR_DEFREG(SPSR_reg_t, _SPSR, 0x4D); + __AVR_DEFREG(uint8_t, _SPDR, 0x4E); + __AVR_DEFREG(ACSR_reg_t, _ACSR, 0x50); + __AVR_DEFREG(uint8_t, _OCDR, 0x51); + __AVR_DEFREG(SMCR_reg_t, _SMCR, 0x53); + __AVR_DEFREG(MCUSR_reg_t, _MCUSR, 0x54); + __AVR_DEFREG(MCUCR_reg_t, _MCUCR, 0x55); + __AVR_DEFREG(SPMCSR_reg_t, _SPMCSR, 0x57); + __AVR_DEFREG(RAMPZ_reg_t, _RAMPZ, 0x5B); + __AVR_DEFREG(SP_reg_t, _SP, 0x5D); + __AVR_DEFREG(SREG_reg_t, _SREG, 0x5F); + __AVR_DEFREG(WDTCSR_reg_t, _WDTCSR, 0x60); + __AVR_DEFREG(CLKPR_reg_t, _CLKPR, 0x61); + __AVR_DEFREG(PRR0_reg_t, _PRR0, 0x64); + __AVR_DEFREG(PRR1_reg_t, _PRR1, 0x65); + __AVR_DEFREG(uint8_t, _OSCCAL, 0x66); + __AVR_DEFREG(PCICR_reg_t, _PCICR, 0x68); + __AVR_DEFREG(EICRA_reg_t, _EICRA, 0x69); + __AVR_DEFREG(EICRB_reg_t, _EICRB, 0x6A); + __AVR_DEFREG(_bit_reg_t, _PCMSK0, 0x6B); + __AVR_DEFREG(TIMSK0_reg_t, _TIMSK0, 0x6E); + __AVR_DEFREG(TIMSK1_reg_t, _TIMSK1, 0x6F); + __AVR_DEFREG(TIMSK2_reg_t, _TIMSK2, 0x70); + __AVR_DEFREG(TIMSK3_reg_t, _TIMSK3, 0x71); + __AVR_DEFREG(XMCRA_reg_t, _XMCRA, 0x74); + __AVR_DEFREG(XMCRB_reg_t, _XMCRB, 0x75); + __AVR_DEFREG(uint16_t, _ADC, 0x78); + __AVR_DEFREG(ADCSRA_reg_t, _ADCSRA, 0x7A); + __AVR_DEFREG(ADCSRB_reg_t, _ADCSRB, 0x7B); + __AVR_DEFREG(ADMUX_reg_t, _ADMUX, 0x7C); + __AVR_DEFREG(DIDR0_reg_t, _DIDR0, 0x7E); + __AVR_DEFREG(DIDR1_reg_t, _DIDR1, 0x7F); + __AVR_DEFREG(TIMER_dev_t, TIMER1, 0x80); + __AVR_DEFREG(TIMER_dev_t, TIMER3, 0x90); + __AVR_DEFREG(UHCON_reg_t, _UHCON, 0x9E); + __AVR_DEFREG(UHINT_reg_t, _UHINT, 0x9F); + __AVR_DEFREG(UHIEN_reg_t, _UHIEN, 0xA0); + __AVR_DEFREG(UHADDR_reg_t, _UHADDR, 0xA1); + __AVR_DEFREG(UHFNUM_reg_t, _UHFNUM, 0xA2); + __AVR_DEFREG(uint8_t, _UHFLEN, 0xA4); + __AVR_DEFREG(uint8_t, _UPINRQX, 0xA5); + __AVR_DEFREG(UPINTX_reg_t, _UPINTX, 0xA6); + __AVR_DEFREG(UPNUM_reg_t, _UPNUM, 0xA7); + __AVR_DEFREG(UPRST_reg_t, _UPRST, 0xA8); + __AVR_DEFREG(UPCONX_reg_t, _UPCONX, 0xA9); + _AVR_DEFREG(UPCFG0X, 0xAA); + _AVR_DEFREG(UPCFG1X, 0xAB); + _AVR_DEFREG(UPSTAX, 0xAC); + __AVR_DEFREG(uint8_t, _UPCFG2X, 0xAD); + _AVR_DEFREG(UPIENX, 0xAE); + __AVR_DEFREG(uint8_t, _UPDATX, 0xAF); + __AVR_DEFREG(TIMER_8bit_dev_t, _TIMER2, 0xB0); + __AVR_DEFREG(ASSR_reg_t, _ASSR, 0xB6); + __AVR_DEFREG(uint8_t, _TWBR, 0xB8); + __AVR_DEFREG(TWSR_reg_t, _TWSR, 0xB9); + __AVR_DEFREG(TWAR_reg_t, _TWAR, 0xBA); + __AVR_DEFREG(uint8_t, _TWDR, 0xBB); + __AVR_DEFREG(TWCR_reg_t, _TWCR, 0xBC); + __AVR_DEFREG(TWAMR_reg_t, _TWAMR, 0xBD); + __AVR_DEFREG(USART_dev_t, USART1, 0xC8); + _AVR_DEFREG(UHWCON, 0xD7); + _AVR_DEFREG(USBCON, 0xD8); + _AVR_DEFREG(USBSTA, 0xD9); + _AVR_DEFREG(USBINT, 0xDA); + _AVR_DEFREG(UDPADD, 0xDB); + _AVR_DEFREG(OTGCON, 0xDD); + _AVR_DEFREG(OTGIEN, 0xDE); + _AVR_DEFREG(OTGINT, 0xDF); + _AVR_DEFREG(UDCON, 0xE0); + _AVR_DEFREG(UDINT, 0xE1); + _AVR_DEFREG(UDIEN, 0xE2); + _AVR_DEFREG(UDADDR, 0xE3); + _AVR_DEFREG(UDFNUM, 0xE4); + _AVR_DEFREG(UDMFN, 0xE6); + _AVR_DEFREG(UDTST, 0xE7); + _AVR_DEFREG(UEINTX, 0xE8); + _AVR_DEFREG(UENUM, 0xE9); + _AVR_DEFREG(UERST, 0xEA); + _AVR_DEFREG(UECONX, 0xEB); + _AVR_DEFREG(UECFG0X, 0xEC); + _AVR_DEFREG(UECFG1X, 0xED); + _AVR_DEFREG(UESTA0X, 0xEE); + _AVR_DEFREG(UESTA1X, 0xEF); + _AVR_DEFREG(UEIENX, 0xF0); + __AVR_DEFREG(uint8_t, _UEDATx, 0xF1); + _AVR_DEFREG(UEBCX, 0xF2); + _AVR_DEFREG(UEINT, 0xF4); + _AVR_DEFREG(UPERRX, 0xF5); + _AVR_DEFREG(UPBCX, 0xF6); + __AVR_DEFREG(uint8_t, _UPINT, 0xF8); + _AVR_DEFREG(OTGTCON, 0xF9); +#elif defined(__AVR_TRM05__) + // page 476ff. of ATmega164P-324P-644P-Data-Sheet-40002071A.pdf + __AVR_DEFREG(PORT_dev_t, _PORTA, 0x20); + __AVR_DEFREG(PORT_dev_t, _PORTB, 0x23); + __AVR_DEFREG(PORT_dev_t, _PORTC, 0x26); + __AVR_DEFREG(PORT_dev_t, _PORTD, 0x29); + __AVR_DEFREG(TIFR0_reg_t, _TIFR0, 0x35); + __AVR_DEFREG(TIFR1_reg_t, _TIFR1, 0x36); + __AVR_DEFREG(TIFR2_reg_t, _TIFR2, 0x37); + __AVR_DEFREG(PCIFR_reg_t, _PCIFR, 0x3B); + __AVR_DEFREG(EIFR_reg_t, _EIFR, 0x3C); + __AVR_DEFREG(EIMSK_reg_t, _EIMSK, 0x3D); + __AVR_DEFREG(_bit_reg_t, _GPIOR0, 0x3E); + __AVR_DEFREG(EECR_reg_t, _EECR, 0x3F); + __AVR_DEFREG(uint8_t, _EEDR, 0x40); + __AVR_DEFREG(EEAR_reg_t, _EEAR, 0x41); + __AVR_DEFREG(GTCCR_reg_t, _GTCCR, 0x43); + __AVR_DEFREG(TIMER_8bit_dev_t, TIMER0, 0x44); + __AVR_DEFREG(_bit_reg_t, _GPIOR1, 0x4A); + __AVR_DEFREG(_bit_reg_t, _GPIOR2, 0x4B); + __AVR_DEFREG(SPCR_reg_t, _SPCR, 0x4C); + __AVR_DEFREG(SPSR_reg_t, _SPSR, 0x4D); + __AVR_DEFREG(uint8_t, _SPDR, 0x4E); + __AVR_DEFREG(ACSR_reg_t, _ACSR, 0x50); + __AVR_DEFREG(uint8_t, _OCDR, 0x51); + __AVR_DEFREG(SMCR_reg_t, _SMCR, 0x53); + __AVR_DEFREG(MCUSR_reg_t, _MCUSR, 0x54); + __AVR_DEFREG(MCUCR_reg_t, _MCUCR, 0x55); + __AVR_DEFREG(SPMCSR_reg_t, _SPMCSR, 0x57); + __AVR_DEFREG(RAMPZ_reg_t, _RAMPZ, 0x5B); + __AVR_DEFREG(SP_reg_t, _SP, 0x5D); + __AVR_DEFREG(SREG_reg_t, _SREG, 0x5F); + __AVR_DEFREG(WDTCSR_reg_t, _WDTCSR, 0x60); + __AVR_DEFREG(CLKPR_reg_t, _CLKPR, 0x61); + __AVR_DEFREG(PRR0_reg_t, _PRR0, 0x64); + __AVR_DEFREG(uint8_t, _OSCCAL, 0x66); + __AVR_DEFREG(PCICR_reg_t, _PCICR, 0x68); + __AVR_DEFREG(EICRA_reg_t, _EICRA, 0x69); + __AVR_DEFREG(_bit_reg_t, _PCMSK0, 0x6B); + __AVR_DEFREG(_bit_reg_t, _PCMSK1, 0x6C); + __AVR_DEFREG(_bit_reg_t, _PCMSK2, 0x6D); + __AVR_DEFREG(TIMSK0_reg_t, _TIMSK0, 0x6E); + __AVR_DEFREG(TIMSK1_reg_t, _TIMSK1, 0x6F); + __AVR_DEFREG(TIMSK2_reg_t, _TIMSK2, 0x70); + __AVR_DEFREG(_bit_reg_t, _PCMKS3, 0x73); + __AVR_DEFREG(uint16_t, _ADC, 0x78); + __AVR_DEFREG(ADCSRA_reg_t, _ADCSRA, 0x7A); + __AVR_DEFREG(ADCSRB_reg_t, _ADCSRB, 0x7B); + __AVR_DEFREG(ADMUX_reg_t, _ADMUX, 0x7C); + __AVR_DEFREG(DIDR0_reg_t, _DIDR0, 0x7E); + __AVR_DEFREG(DIDR1_reg_t, _DIDR1, 0x7F); + __AVR_DEFREG(TIMER_dev_t, TIMER1, 0x80); + __AVR_DEFREG(TIMER_8bit_dev_t, _TIMER2, 0xB0); + __AVR_DEFREG(ASSR_reg_t, _ASSR, 0xB6); + __AVR_DEFREG(uint8_t, _TWBR, 0xB8); + __AVR_DEFREG(TWSR_reg_t, _TWSR, 0xB8); + __AVR_DEFREG(TWAR_reg_t, _TWAR, 0xBA); + __AVR_DEFREG(uint8_t, _TWDR, 0xBB); + __AVR_DEFREG(TWCR_reg_t, _TWCR, 0xBC); + __AVR_DEFREG(TWAMR_reg_t, _TWAMR, 0xBD); + __AVR_DEFREG(USART_dev_t, USART0, 0xC0); + __AVR_DEFREG(USART_dev_t, USART1, 0xC8); +#endif + +inline void _ATmega_resetperipherals() { + using namespace AVRHelpers; + + // Due to BOOTLOADER or other board inconsistencies we could get launched into Marlin FW + // with configuration that does not match the reset state in the documentation. That is why + // we should clean-reset the entire device. + #if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM03__) || defined(__AVR_TRM04__) || defined(__AVR_TRM05__) + SREG_reg_t __SREG; + __SREG._C = false; + __SREG._Z = false; + __SREG._N = false; + __SREG._V = false; + __SREG._S = false; + __SREG._H = false; + __SREG._T = false; + __SREG._I = false; + dwrite(_SREG, __SREG); + #endif + + #if defined(__AVR_TRM01__) || defined(__AVR_TRM04__) || defined(__AVR_TRM05__) + _RAMPZ._RAMPZ = 0; + #endif + #ifdef __AVR_TRM01__ + _EIND._EIND0 = false; + #endif + + #if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM03__) || defined(__AVR_TRM05__) + _EEAR._EEAR = 0; + dwrite(_EEDR, (uint8_t)0U); + #endif + + #if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM03__) || defined(__AVR_TRM04__) || defined(__AVR_TRM05__) + EECR_reg_t __EECR; + __EECR._EERE = false; + __EECR._EEPE = false; + __EECR._EEMPE = false; + __EECR._EERIE = false; + __EECR._EEPM0 = 0; + __EECR._EEPM1 = 0; + __EECR.reserved1 = 0; + dwrite(_EECR, __EECR); + #endif + + #if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM03__) || defined(__AVR_TRM04__) || defined(__AVR_TRM05__) + _GPIOR2.val = 0; + _GPIOR1.val = 0; + _GPIOR0.val = 0; + #endif + + #if defined(__AVR_TRM01__) || defined(__AVR_TRM04__) + XMCRA_reg_t __XMCRA; + __XMCRA._SRW0 = 0; + __XMCRA._SRW1 = 0; + __XMCRA._SRL = 0; + __XMCRA._SRE = 0; + dwrite(_XMCRA, __XMCRA); + + XMCRB_reg_t __XMCRB; + __XMCRB._XMM = 0; + __XMCRB.reserved1 = 0; + __XMCRB._XMBK = false; + dwrite(_XMCRB, __XMCRB); + #endif + + #if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM03__) || defined(__AVR_TRM04__) || defined(__AVR_TRM05__) + SMCR_reg_t __SMCR; + __SMCR._SE = false; + __SMCR._SM = 0; + __SMCR.reserved1 = 0; + dwrite(_SMCR, __SMCR); + #endif + + #if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM03__) || defined(__AVR_TRM04__) || defined(__AVR_TRM05__) + PRR0_reg_t __PRR0; + #if defined(__AVR_TRM01__) || defined(__AVR_TRM03__) + __PRR0._PRADC = false; + __PRR0._PRUSART0 = false; + __PRR0._PRSPI = false; + __PRR0._PRTIM1 = false; + __PRR0.reserved1 = false; + __PRR0._PRTIM0 = false; + __PRR0._PRTIM2 = false; + __PRR0._PRTWI = false; + #elif defined(__AVR_TRM02__) + __PRR0._PRADC = false; + __PRR0._PRUSART0 = false; + __PRR0._PRSPI = false; + __PRR0._PRTIM1 = false; + __PRR0._PRUSART1 = false; + __PRR0._PRTIM0 = false; + __PRR0._PRTIM2 = false; + __PRR0._PRTWI = false; + #elif defined(__AVR_TRM04__) + __PRR0._PRADC = false; + __PRR0.reserved1 = false; + __PRR0._PRSPI = false; + __PRR0._PRTIM1 = false; + __PRR0.reserved2 = false; + __PRR0._PRTIM0 = false; + __PRR0._PRTIM2 = false; + __PRR0._PRTWI = false; + #endif + dwrite(_PRR0, __PRR0); + #endif + + #if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM04__) + PRR1_reg_t __PRR1; + #ifdef __AVR_TRM01__ + __PRR1._PRUSART1 = false; + __PRR1._PRUSART2 = false; + __PRR1._PRUSART3 = false; + __PRR1._PRTIM3 = false; + __PRR1._PRTIM4 = false; + __PRR1._PRTIM5 = false; + __PRR1.reserved1 = 0; + #elif defined(__AVR_TRM02__) + __PRR1._PRTIM3 = false; + __PRR1.reserved1 = 0; + #elif defined(__AVR_TRM04__) + __PRR1._PRUSART1 = false; + __PRR1.reserved1 = 0; + #endif + dwrite(_PRR1, __PRR1); + #endif + + #if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM03__) || defined(__AVR_TRM04__) || defined(__AVR_TRM05__) + WDTCSR_reg_t __WDTCSR; + __WDTCSR._WDP0 = 0; + __WDTCSR._WDP1 = 0; + __WDTCSR._WDP2 = 0; + __WDTCSR._WDE = false; + __WDTCSR._WDCE = false; + __WDTCSR._WDP3 = 0; + __WDTCSR._WDIE = false; + __WDTCSR._WDIF = false; + dwrite(_WDTCSR, __WDTCSR); + #endif + + #if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM03__) || defined(__AVR_TRM04__) || defined(__AVR_TRM05__) + _MCUCR._PUD = false; + #endif + + #if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM03__) || defined(__AVR_TRM04__) || defined(__AVR_TRM05__) + PORT_dev_t __PORT; + __PORT._PIN.val = 0; + __PORT._DDR.val = 0; + __PORT._PORT.val = 0; + #endif + + #if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM04__) || defined(__AVR_TRM05__) + dwrite(_PORTA, __PORT); + dwrite(_PORTC, __PORT); + #endif + #if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM03__) || defined(__AVR_TRM04__) || defined(__AVR_TRM05__) + dwrite(_PORTB, __PORT); + dwrite(_PORTD, __PORT); + #endif + #if defined(__AVR_TRM01__) || defined(__AVR_TRM04__) + dwrite(_PORTE, __PORT); + dwrite(_PORTF, __PORT); + #endif + + #ifdef __AVR_TRM01__ + PORTG_dev_t __PORTG; + __PORTG._PIN.val = 0; + __PORTG._PIN.reserved1 = 0; + __PORTG._DDR.val = 0; + __PORTG._DDR.reserved1 = 0; + __PORTG._PORT.val = 0; + __PORTG._PORT.reserved1 = 0; + dwrite(_PORTG, __PORTG); + #endif + + #ifdef __AVR_TRM03__ + PORTC_dev_t __PORTC; + __PORTC._PIN.val = 0; + __PORTC._PIN.reserved1 = 0; + __PORTC._DDR.val = 0; + __PORTC._DDR.reserved1 = 0; + __PORTC._PORT.val = 0; + __PORTC._PORT.reserved1 = 0; + dwrite(_PORTC, __PORTC); + #endif + + #ifdef __AVR_TRM01__ + dwrite(_PORTH, __PORT); + dwrite(_PORTJ, __PORT); + dwrite(_PORTK, __PORT); + dwrite(_PORTL, __PORT); + #endif + + #if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM03__) || defined(__AVR_TRM04__) || defined(__AVR_TRM05__) + EICRA_reg_t __EICRA; + #if defined(__AVR_TRM01__) || defined(__AVR_TRM04__) + __EICRA._ISC0 = 0; + __EICRA._ISC1 = 0; + __EICRA._ISC2 = 0; + __EICRA._ISC3 = 0; + #elif defined(__AVR_TRM02__) || defined(__AVR_TRM05__) + __EICRA._ISC0 = 0; + __EICRA._ISC1 = 0; + __EICRA._ISC2 = 0; + __EICRA.reserved1 = 0; + #elif defined(__AVR_TRM03__) + __EICRA._ISC0 = 0; + __EICRA._ISC1 = 0; + __EICRA.reserved1 = 0; + #endif + dwrite(_EICRA, __EICRA); + #endif + + #if defined(__AVR_TRM01__) || defined(__AVR_TRM04__) + EICRB_reg_t __EICRB; + __EICRB._ISC4 = 0; + __EICRB._ISC5 = 0; + __EICRB._ISC6 = 0; + __EICRB._ISC7 = 0; + dwrite(_EICRB, __EICRB); + #endif + + #if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM03__) || defined(__AVR_TRM04__) || defined(__AVR_TRM05__) + EIMSK_reg_t __EIMSK; + #if defined(__AVR_TRM01__) || defined(__AVR_TRM04__) + __EIMSK._INT0 = false; + __EIMSK._INT1 = false; + __EIMSK._INT2 = false; + __EIMSK._INT3 = false; + __EIMSK._INT4 = false; + __EIMSK._INT5 = false; + __EIMSK._INT6 = false; + __EIMSK._INT7 = false; + #elif defined(__AVR_TRM02__) || defined(__AVR_TRM05__) + __EIMSK._INT0 = false; + __EIMSK._INT1 = false; + __EIMSK._INT2 = false; + __EIMSK.reserved1 = 0; + #elif defined(__AVR_TRM03__) + __EIMSK._INT0 = false; + __EIMSK._INT1 = false; + __EIMSK.reserved1 = 0; + #endif + dwrite(_EIMSK, __EIMSK); + #endif + + #if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM03__) || defined(__AVR_TRM04__) || defined(__AVR_TRM05__) + EIFR_reg_t __EIFR; + #if defined(__AVR_TRM01__) || defined(__AVR_TRM04__) + __EIFR._INTF0 = false; + __EIFR._INTF1 = false; + __EIFR._INTF2 = false; + __EIFR._INTF3 = false; + __EIFR._INTF4 = false; + __EIFR._INTF5 = false; + __EIFR._INTF6 = false; + __EIFR._INTF7 = false; + #elif defined(__AVR_TRM02__) || defined(__AVR_TRM05__) + __EIFR._INTF0 = false; + __EIFR._INTF1 = false; + __EIFR._INTF2 = false; + __EIFR.reserved1 = 0; + #elif defined(__AVR_TRM03__) + __EIFR._INTF0 = false; + __EIFR._INTF1 = false; + __EIFR.reserved1 = 0; + #endif + dwrite(_EIFR, __EIFR); + #endif + + #if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM03__) || defined(__AVR_TRM04__) || defined(__AVR_TRM05__) + PCICR_reg_t __PCICR; + #if defined(__AVR_TRM01__) || defined(__AVR_TRM03__) + __PCICR._PCIE0 = false; + __PCICR._PCIE1 = false; + __PCICR._PCIE2 = false; + __PCICR.reserved1 = 0; + #elif defined(__AVR_TRM02__) || defined(__AVR_TRM05__) + __PCICR._PCIE0 = false; + __PCICR._PCIE1 = false; + __PCICR._PCIE2 = false; + __PCICR._PCIE3 = false; + __PCICR.reserved1 = 0; + #elif defined(__AVR_TRM04__) + __PCICR._PCIE0 = false; + __PCICR.reserved1 = 0; + #endif + dwrite(_PCICR, __PCICR); + #endif + + #if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM03__) || defined(__AVR_TRM04__) || defined(__AVR_TRM05__) + PCIFR_reg_t __PCIFR; + #if defined(__AVR_TRM01__) || defined(__AVR_TRM03__) + __PCIFR._PCIF0 = false; + __PCIFR._PCIF1 = false; + __PCIFR._PCIF2 = false; + __PCIFR.reserved1 = 0; + #elif defined(__AVR_TRM02__) || defined(__AVR_TRM05__) + __PCIFR._PCIF0 = false; + __PCIFR._PCIF1 = false; + __PCIFR._PCIF2 = false; + __PCIFR._PCIF3 = false; + __PCIFR.reserved1 = 0; + #elif defined(__AVR_TRM04__) + __PCIFR._PCIF0 = false; + __PCIFR.reserved1 = 0; + #endif + dwrite(_PCIFR, __PCIFR); + #endif + + #if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM03__) || defined(__AVR_TRM04__) || defined(__AVR_TRM05__) + _PCMSK0.val = 0; + #endif + #if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM03__) || defined(__AVR_TRM05__) + _PCMSK1.val = 0; + _PCMSK2.val = 0; + #endif + #if defined(__AVR_TRM03__) + _PCMSK1.reserved1 = 0; + #endif + #if defined(__AVR_TRM02__) + _PCMSK3.val = 0; + #endif + + #if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM03__) || defined(__AVR_TRM04__) || defined(__AVR_TRM05__) + TIMER_8bit_dev_t __TIMER_8bit; + __TIMER_8bit._TCCRnA._WGMn0 = 0; + __TIMER_8bit._TCCRnA._WGMn1 = 0; + __TIMER_8bit._TCCRnA.reserved1 = 0; + __TIMER_8bit._TCCRnA._COMnB = 0; + __TIMER_8bit._TCCRnA._COMnA = 0; + __TIMER_8bit._TCCRnB._CSn = 0; + __TIMER_8bit._TCCRnB._WGMn2 = 0; + __TIMER_8bit._TCCRnB.reserved1 = 0; + __TIMER_8bit._TCCRnB._FOCnB = false; + __TIMER_8bit._TCCRnB._FOCnA = false, + __TIMER_8bit._TCNTn = 0; + __TIMER_8bit._OCRnA = 0; + __TIMER_8bit._OCRnB = 0; + dwrite(TIMER0, __TIMER_8bit); + #endif + + #if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM03__) || defined(__AVR_TRM04__) || defined(__AVR_TRM05__) + TIMSK0_reg_t __TIMSK0; + __TIMSK0._TOIE0 = false; + __TIMSK0._OCIE0A = false; + __TIMSK0._OCIE0B = false; + __TIMSK0.reserved1 = 0; + dwrite(_TIMSK0, __TIMSK0); + + TIFR0_reg_t __TIFR0; + __TIFR0._TOV0 = false; + __TIFR0._OCF0A = false; + __TIFR0._OCF0B = false; + __TIFR0.reserved1 = 0; + dwrite(_TIFR0, __TIFR0); + #endif + + #if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM03__) || defined(__AVR_TRM04__) || defined(__AVR_TRM05__) + TIMER_dev_t TIMER; + TIMER._TCCRnA._WGMn0 = 0; + TIMER._TCCRnA._WGMn1 = 0; + #if defined(__AVR_TRM01__) || defined(__AVR_TRM04__) + TIMER._TCCRnA._COMnC = 0; + #endif + TIMER._TCCRnA._COMnB = 0; + TIMER._TCCRnA._COMnA = 0; + TIMER._TCCRnB._CSn = 0; + TIMER._TCCRnB._WGMn2 = 0; + TIMER._TCCRnB.reserved1 = 0; + TIMER._TCCRnB._ICESn = 0; + TIMER._TCCRnB._ICNCn = 0; + TIMER._TCCRnC.reserved1 = 0; + #if defined(__AVR_TRM01__) || defined(__AVR_TRM04__) + TIMER._TCCRnC._FOCnC = false; + #endif + TIMER._TCCRnC._FOCnB = false; + TIMER._TCCRnC._FOCnA = false; + TIMER._TCNTn = 0; + TIMER._OCRnA = 0; + TIMER._OCRnB = 0; + #if defined(__AVR_TRM01__) || defined(__AVR_TRM04__) + TIMER._OCRnC = 0; + #endif + TIMER._ICRn = 0; + dwrite(TIMER1, TIMER); + #endif + #if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM04__) + dwrite(TIMER3, TIMER); + #endif + #ifdef __AVR_TRM01__ + dwrite(TIMER4, TIMER); + dwrite(TIMER5, TIMER); + #endif + + #if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM03__) || defined(__AVR_TRM04__) || defined(__AVR_TRM05__) + TIMSK1_reg_t __TIMSK1; + __TIMSK1._TOIE1 = false; + __TIMSK1._OCIE1A = false; + __TIMSK1._OCIE1B = false; + #if defined(__AVR_TRM01__) || defined(__AVR_TRM04__) + __TIMSK1._OCIE1C = false; + #endif + __TIMSK1.reserved1 = 0; + __TIMSK1._ICIE1 = false; + __TIMSK1.reserved2 = 0; + dwrite(_TIMSK1, __TIMSK1); + #endif + + #if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM04__) + TIMSK3_reg_t __TIMSK3; + __TIMSK3._TOIE3 = false; + __TIMSK3._OCIE3A = false; + __TIMSK3._OCIE3B = false; + #if defined(__AVR_TRM01__) || defined(__AVR_TRM04__) + __TIMSK3._OCIE3C = false; + #endif + __TIMSK3.reserved1 = 0; + __TIMSK3._ICIE3 = false; + __TIMSK3.reserved2 = 0; + dwrite(_TIMSK3, __TIMSK3); + #endif + + #ifdef __AVR_TRM01__ + TIMSK4_reg_t __TIMSK4; + __TIMSK4._TOIE4 = false; + __TIMSK4._OCIE4A = false; + __TIMSK4._OCIE4B = false; + __TIMSK4._OCIE4C = false; + __TIMSK4.reserved1 = false; + __TIMSK4._ICIE4 = false; + __TIMSK4.reserved2 = false; + dwrite(_TIMSK4, __TIMSK4); + + TIMSK5_reg_t __TIMSK5; + __TIMSK5._TOIE5 = false; + __TIMSK5._OCIE5A = false; + __TIMSK5._OCIE5B = false; + __TIMSK5._OCIE5C = false; + __TIMSK5.reserved1 = 0; + __TIMSK5._ICIE5 = false; + __TIMSK5.reserved2 = 0; + dwrite(_TIMSK5, __TIMSK5); + #endif + + #if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM03__) || defined(__AVR_TRM04__) || defined(__AVR_TRM05__) + TIFR1_reg_t __TIFR1; + __TIFR1._TOV1 = false; + __TIFR1._OCF1A = false; + __TIFR1._OCF1B = false; + #if defined(__AVR_TRM01__) || defined(__AVR_TRM04__) + __TIFR1._OCF1C = false; + #endif + __TIFR1.reserved1 = 0; + __TIFR1._ICF1 = false; + __TIFR1.reserved2 = 0; + dwrite(_TIFR1, __TIFR1); + #endif + + #if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM04__) + TIFR3_reg_t __TIFR3; + __TIFR3._TOV3 = false; + __TIFR3._OCF3A = false; + __TIFR3._OCF3B = false; + #if defined(__AVR_TRM01__) || defined(__AVR_TRM04__) + __TIFR3._OCF3C = false; + #endif + __TIFR3.reserved1 = 0; + __TIFR3._ICF3 = false; + __TIFR3.reserved2 = 0; + dwrite(_TIFR3, __TIFR3); + #endif + + #ifdef __AVR_TRM01__ + TIFR4_reg_t __TIFR4; + __TIFR4._TOV4 = false; + __TIFR4._OCF4A = false; + __TIFR4._OCF4B = false; + __TIFR4._OCF4C = false; + __TIFR4.reserved1 = 0; + __TIFR4._ICF4 = false; + __TIFR4.reserved2 = 0; + dwrite(_TIFR4, __TIFR4); + + TIFR5_reg_t __TIFR5; + __TIFR5._TOV5 = false; + __TIFR5._OCF5A = false; + __TIFR5._OCF5B = false; + __TIFR5._OCF5C = false; + __TIFR5.reserved1 = 0; + __TIFR5._ICF5 = false; + __TIFR5.reserved2 = 0; + dwrite(_TIFR5, __TIFR5); + #endif + + #if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM03__) || defined(__AVR_TRM04__) || defined(__AVR_TRM05__) + dwrite(_TIMER2, __TIMER_8bit); + #endif + + #if defined(__AV_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM03__) || defined(__AVR_TRM04__) || defined(__AVR_TRM05__) + ASSR_reg_t __ASSR; + __ASSR._TCR2BUB = false; + __ASSR._TCR2AUB = false; + __ASSR._OCR2BUB = false; + __ASSR._OCR2AUB = false; + __ASSR._TCN2UB = false; + __ASSR._AS2 = false; + __ASSR._EXCLK = false; + __ASSR.reserved1 = 0; + dwrite(_ASSR, __ASSR); + #endif + + #if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM03__) || defined(__AVR_TRM04__) || defined(__AVR_TRM05__) + TIMSK2_reg_t __TIMSK2; + __TIMSK2._TOIE2 = false; + __TIMSK2._OCIE2A = false; + __TIMSK2._OCIE2B = false; + __TIMSK2.reserved1 = 0; + dwrite(_TIMSK2, __TIMSK2); + + TIFR2_reg_t __TIFR2; + __TIFR2._TOV2 = false; + __TIFR2._OCF2A = false; + __TIFR2._OCF2B = false; + __TIFR2.reserved1 = 0; + dwrite(_TIFR2, __TIFR2); + #endif + + #if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM03__) || defined(__AVR_TRM04__) || defined(__AVR_TRM05__) + SPCR_reg_t __SPCR; + __SPCR._SPR = 0; + __SPCR._CPHA = 0; + __SPCR._CPOL = 0; + __SPCR._MSTR = 0; + __SPCR._DORD = 0; + __SPCR._SPE = false; + __SPCR._SPIE = false; + dwrite(_SPCR, __SPCR); + + SPSR_reg_t __SPSR; + __SPSR._SPI2X = false; + __SPSR.reserved1 = 0; + __SPSR._WCOL = false; + __SPSR._SPIF = false; + dwrite(_SPSR, __SPSR); + #endif + + #if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM03__) || defined(__AVR_TRM04__) || defined(__AVR_TRM05__) + USART_dev_t USART; + USART._UDRn = 0; + USART._UCSRnA._MPCM = false; + USART._UCSRnA._U2X = false; + USART._UCSRnA._UPE = false; + USART._UCSRnA._DOR = false; + USART._UCSRnA._FE = false; + USART._UCSRnA._UDRE = true; + USART._UCSRnA._TXC = false; + USART._UCSRnA._RXC = false; + USART._UCSRnB._TXB8 = false; + USART._UCSRnB._RXB8 = false; + USART._UCSRnB._UCSZn2 = false; + USART._UCSRnB._TXEN = false; + USART._UCSRnB._RXEN = false; + USART._UCSRnB._UDRIE = false; + USART._UCSRnB._TXCIE = false; + USART._UCSRnB._RXCIE = false; + USART._UCSRnC._UCPOL = false; + #if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM03__) || defined(__AVR_TRM04__) + USART._UCSRnC._UCSZn0 = 1; + USART._UCSRnC._UCSZn1 = 1; + USART._UCSRnC._USBS = false; + USART._UCSRnC._UPM = 0; + USART._UCSRnC._UPM = 0; + USART._UCSRnC._UMSEL = 0; + #elif defined(__AVR_TRM05__) + USART._UCSRnC._UCPOL = 0; + USART._UCSRnC._UCPHA = 0; + USART._UCSRnC._UDORD = 0; + USART._UCSRnC.reserved1 = 0; + USART._UCSRnC._UMSEL = 0; + #endif + USART._UBRRn._UBRR = 0; + USART._UBRRn.reserved1 = 0; + #endif + #if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM03__) || defined(__AVR_TRM05__) + dwrite(USART0, USART); + #endif + #if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM04__) || defined(__AVR_TRM05__) + dwrite(USART1, USART); + #endif + #ifdef __AVR_TRM01__ + dwrite(USART2, USART); + dwrite(USART3, USART); + #endif + + #if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM03__) || defined(__AVR_TRM04__) || defined(__AVR_TRM05__) + dwrite(_TWBR, (uint8_t)0); + + TWCR_reg_t __TWCR; + __TWCR._TWIE = false; + __TWCR.reserved1 = 0; + __TWCR._TWEN = false; + __TWCR._TWWC = false; + __TWCR._TWSTO = false; + __TWCR._TWSTA = false; + __TWCR._TWEA = false; + __TWCR._TWINT = false; + dwrite(_TWCR, __TWCR); + + TWSR_reg_t __TWSR; + __TWSR._TWPS0 = false; + __TWSR._TWPS1 = false; + __TWSR.reserved1 = 0; + __TWSR._TWS3 = 1; + __TWSR._TWS4 = 1; + __TWSR._TWS5 = 1; + __TWSR._TWS6 = 1; + __TWSR._TWS7 = 1; + dwrite(_TWSR, __TWSR); + + dwrite(_TWDR, (uint8_t)0xFF); + + TWAR_reg_t __TWAR; + __TWAR._TWGCE = false; + __TWAR._TWA = 0x7F; + dwrite(_TWAR, __TWAR); + + TWAMR_reg_t __TWAMR; + __TWAMR.reserved1 = false; + __TWAMR._TWAM = 0; + dwrite(_TWAMR, __TWAMR); + #endif + + #if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM03__) || defined(__AVR_TRM04__) || defined(__AVR_TRM05__) + ADCSRB_reg_t __ADCSRB; + __ADCSRB._ADTS = 0; + #ifdef __AVR_TRM01__ + __ADCSRB._MUX5 = 0; + #endif + __ADCSRB.reserved1 = 0; + __ADCSRB._ACME = false; + __ADCSRB.reserved2 = 0; + dwrite(_ADCSRB, __ADCSRB); + + ACSR_reg_t __ACSR; + __ACSR._ACIS = 0; + __ACSR._ACIC = false; + __ACSR._ACIE = false; + __ACSR._ACI = false; + __ACSR._ACO = false; + __ACSR._ACBG = false; + __ACSR._ACD = false; + dwrite(_ACSR, __ACSR); + #endif + + #if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM03__) || defined(__AVR_TRM04__) || defined(__AVR_TRM05__) + DIDR1_reg_t __DIDR1; + __DIDR1._AIN0D = false; + __DIDR1._AIN1D = false; + __DIDR1.reserved1 = false; + dwrite(_DIDR1, __DIDR1); + #endif + + #if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM03__) || defined(__AVR_TRM04__) || defined(__AVR_TRM05__) + ADMUX_reg_t __ADMUX; + __ADMUX._MUX0 = 0; + __ADMUX._MUX1 = 0; + __ADMUX._MUX2 = 0; + __ADMUX._MUX3 = 0; + #if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM04__) || defined(__AVR_TRM05__) + __ADMUX._MUX4 = 0; + #elif defined(__AVR_TRM03__) + __ADMUX.reserved1 = 0; + #endif + __ADMUX._ADLAR = 0; + __ADMUX._REFS0 = 0; + __ADMUX._REFS1 = 0; + dwrite(_ADMUX, __ADMUX); + + ADCSRA_reg_t __ADCSRA; + __ADCSRA._ADPS = 0; + __ADCSRA._ADIE = false; + __ADCSRA._ADIF = false; + __ADCSRA._ADATE = false; + __ADCSRA._ADSC = false; + __ADCSRA._ADEN = false; + dwrite(_ADCSRA, __ADCSRA); + + dwrite(_ADC, (uint16_t)0); + #endif + + #if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM03__) || defined(__AVR_TRM04__) || defined(__AVR_TRM05__) + SPMCSR_reg_t __SPMCSR; + #if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM04__) || defined(__AVR_TRM05__) + __SPMCSR._SPMEN = false; + __SPMCSR._PGERS = false; + __SPMCSR._PGWRT = false; + __SPMCSR._BLBSET = false; + __SPMCSR._RWWSRE = false; + __SPMCSR._SIGRD = false; + __SPMCSR._RWWSB = false; + __SPMCSR._SPMIE = false; + #elif defined(__AVR_TRM03__) + #if defined(__AVR_ATmega88A__) || defined(__AVR_ATmega88PA__) || defined(__AVR_ATmega168A__) || defined(__AVR_ATmega168PA__) || defined(__AVR_ATmega328P__) + __SPMCSR._SPMEN = false; + __SPMCSR._PGERS = false; + __SPMCSR._PGWRT = false; + __SPMCSR._BLBSET = false; + __SPMCSR._RWWSRE = false; + __SPMCSR._SIGRD = false; + __SPMCSR._RWWSB = false; + __SPMCSR._SPMIE = false; + #else + __SPMCSR._SPMEN = false; + __SPMCSR._PGERS = false; + __SPMCSR._PGWRT = false; + __SPMCSR._BLBSET = false; + __SPMCSR.reserved1 = false; + __SPMCSR._SIGRD = false; + __SPMCSR.reserved2 = false; + __SPMCSR._SPMIE = false; + #endif + #endif + dwrite(_SPMCSR, __SPMCSR); + #endif + + // TODO: add the __AVR_TRM04__ initializations, if required (mostly USB related) +} + +struct pin_dev_state_t { + #ifdef __AVR_TRM01__ + uint8_t _SRE : 1; // port A + uint8_t _COM0B : 2; + uint8_t _COM1A : 2; + uint8_t _COM1B : 2; + uint8_t _COM1C : 2; + uint8_t _COM2A : 2; + uint8_t _COM2B : 2; + uint8_t _COM3A : 2; + uint8_t _COM3B : 2; + uint8_t _COM3C : 2; + uint8_t _COM4A : 2; + uint8_t _COM4B : 2; + uint8_t _COM4C : 2; + uint8_t _COM5A : 2; + uint8_t _COM5B : 2; + uint8_t _COM5C : 2; + uint8_t _PCIE0 : 1; + uint8_t _PCIE1 : 1; // INTn + uint8_t _PCIE2 : 1; + uint8_t _SPE : 1; + uint8_t _USART0_RXEN : 1; + uint8_t _USART0_TXEN : 1; + uint8_t _USART1_RXEN : 1; + uint8_t _USART1_TXEN : 1; + uint8_t _USART2_RXEN : 1; + uint8_t _USART2_TXEN : 1; + uint8_t _USART3_RXEN : 1; + uint8_t _USART3_TXEN : 1; + //uint8_t _JTAGEN : 1; + uint8_t _AS2 : 1; + #elif defined(__AVR_TRM02__) + uint8_t _PCIE0 : 1; + uint8_t _PCIE1 : 1; + uint8_t _PCIE2 : 1; + uint8_t _PCIE3 : 1; + uint8_t _ADC7D : 1; + uint8_t _ADC6D : 1; + uint8_t _ADC5D : 1; + uint8_t _ADC4D : 1; + uint8_t _ADC3D : 1; + uint8_t _ADC2D : 1; + uint8_t _ADC1D : 1; + uint8_t _ADC0D : 1; + uint8_t _SPE : 1; + uint8_t _COM0A : 2; + uint8_t _COM0B : 2; + uint8_t _COM2A : 2; + uint8_t _COM2B : 2; + uint8_t _COM1A : 2; + uint8_t _COM1B : 2; + //uint8_t _JTAGEN : 1; + uint8_t _AS2 : 1; + uint8_t _TWEN : 1; + uint8_t _USART1_TXEN : 1; + uint8_t _USART1_RXEN : 1; + uint8_t _USART0_TXEN : 1; + uint8_t _USART0_RXEN : 1; + #elif defined(__AVR_TRM03__) + uint8_t _AS2 : 1; + uint8_t _PCIE0 : 1; + uint8_t _PCIE1 : 1; + uint8_t _PCIE2 : 1; + uint8_t _SPE : 1; + uint8_t _COM2B : 2; + uint8_t _COM2A : 2; + uint8_t _COM1B : 2; + uint8_t _COM1A : 2; + uint8_t _COM0A : 2; + uint8_t _COM0B : 2; + uint8_t _TWEN : 1; + uint8_t _ADC7D : 1; + uint8_t _ADC6D : 1; + uint8_t _ADC5D : 1; + uint8_t _ADC4D : 1; + uint8_t _ADC3D : 1; + uint8_t _ADC2D : 1; + uint8_t _ADC1D : 1; + uint8_t _ADC0D : 1; + uint8_t _UMSEL : 2; + uint8_t _USART0_TXEN : 1; + uint8_t _USART0_RXEN : 1; + #elif defined(__AVR_TRM04__) + uint8_t _SRE : 1; + uint8_t _SPE : 1; + uint8_t _COM0B : 2; + uint8_t _COM1C : 2; + uint8_t _COM1B : 2; + uint8_t _COM1A : 2; + uint8_t _COM2A : 2; + uint8_t _COM2B : 2; + uint8_t _PCIE0 : 1; + uint8_t _USART1_RXEN : 1; + uint8_t _USART1_TXEN : 1; + uint8_t _TWEN : 1; + uint8_t _INT7 : 1; + uint8_t _INT6 : 1; + uint8_t _INT5 : 1; + uint8_t _INT4 : 1; + uint8_t _INT3 : 1; + uint8_t _INT2 : 1; + uint8_t _INT1 : 1; + uint8_t _INT0; + uint8_t _UVCONE : 1; + uint8_t _UIDE : 1; + //uint8_t _JTAGEN : 1; + #elif defined(__AVR_TRM05__) + uint8_t _ADC7D : 1; + uint8_t _ADC6D : 1; + uint8_t _ADC5D : 1; + uint8_t _ADC4D : 1; + uint8_t _ADC3D : 1; + uint8_t _ADC2D : 1; + uint8_t _ADC1D : 1; + uint8_t _ADC0D : 1; + uint8_t _PCIE0 : 1; + uint8_t _PCIE1 : 1; + uint8_t _PCIE2 : 1; + uint8_t _PCIE3 : 1; + uint8_t _SPE : 1; + uint8_t _COM0A : 2; + uint8_t _COM0B : 2; + uint8_t _COM2A : 2; + uint8_t _COM2B : 2; + uint8_t _COM1A : 2; + uint8_t _COM1B : 2; + uint8_t _AS2 : 1; + uint8_t _TWEN : 1; + uint8_t _TXEN1 : 1; + uint8_t _RXEN1 : 1; + uint8_t _TXEN0 : 1; + uint8_t _RXEN0 : 1; + uint8_t _INT2 : 1; + uint8_t _INT1 : 1; + uint8_t _INT0 : 1; + //uint8_t _JTAGEN : 1; + #endif +}; + +// AVR ArduinoCore is written like a hack-job (random peripherals enabled all-the-time). + +enum class eATmegaPort { + #ifdef __AVR_TRM01__ + PORT_A, PORT_B, PORT_C, PORT_D, PORT_E, PORT_F, PORT_G, PORT_H, PORT_J, PORT_K, PORT_L + #elif defined(__AVR_TRM02__) || defined(__AVR_TRM05__) + PORT_A, PORT_B, PORT_C, PORT_D + #elif defined(__AVR_TRM03__) + PORT_B, PORT_C, PORT_D + #elif defined(__AVR_TRM04__) + PORT_A, PORT_B, PORT_C, PORT_D, PORT_E, PORT_F + #endif +}; + +struct ATmegaPinInfo { + eATmegaPort port; + uint8_t pinidx; +}; + +#if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM04__) || defined(__AVR_TRM05__) + #define _SPA_DIO_DDRA (eATmegaPort::PORT_A) +#endif +#if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM03__) || defined(__AVR_TRM04__) || defined(__AVR_TRM05__) + #define _SPA_DIO_DDRB (eATmegaPort::PORT_B) + #define _SPA_DIO_DDRC (eATmegaPort::PORT_C) + #define _SPA_DIO_DDRD (eATmegaPort::PORT_D) +#endif +#if defined(__AVR_TRM01__) || defined(__AVR_TRM04__) + #define _SPA_DIO_DDRE (eATmegaPort::PORT_E) + #define _SPA_DIO_DDRF (eATmegaPort::PORT_F) +#endif +#ifdef __AVR_TRM01__ + #define _SPA_DIO_DDRG (eATmegaPort::PORT_G) + #define _SPA_DIO_DDRH (eATmegaPort::PORT_H) + #define _SPA_DIO_DDRJ (eATmegaPort::PORT_J) + #define _SPA_DIO_DDRK (eATmegaPort::PORT_K) + #define _SPA_DIO_DDRL (eATmegaPort::PORT_L) +#endif + +#define __SPA_IFPORT_STMT(dr) if (ddrp == &D##dr) port = _SPA_DIO_D##dr; + +#ifdef _SPA_DIO_DDRA + #define _SPA_IFPORT_PORTA __SPA_IFPORT_STMT(DRA) +#else + #define _SPA_IFPORT_PORTA +#endif +#ifdef _SPA_DIO_DDRB + #define _SPA_IFPORT_PORTB __SPA_IFPORT_STMT(DRB) +#else + #define _SPA_IFPORT_PORTB +#endif +#ifdef _SPA_DIO_DDRC + #define _SPA_IFPORT_PORTC __SPA_IFPORT_STMT(DRC) +#else + #define _SPA_IFPORT_PORTC +#endif +#ifdef _SPA_DIO_DDRD + #define _SPA_IFPORT_PORTD __SPA_IFPORT_STMT(DRD) +#else + #define _SPA_IFPORT_PORTD +#endif +#ifdef _SPA_DIO_DDRE + #define _SPA_IFPORT_PORTE __SPA_IFPORT_STMT(DRE) +#else + #define _SPA_IFPORT_PORTE +#endif +#ifdef _SPA_DIO_DDRF + #define _SPA_IFPORT_PORTF __SPA_IFPORT_STMT(DRF) +#else + #define _SPA_IFPORT_PORTF +#endif +#ifdef _SPA_DIO_DDRG + #define _SPA_IFPORT_PORTG __SPA_IFPORT_STMT(DRG) +#else + #define _SPA_IFPORT_PORTG +#endif +#ifdef _SPA_DIO_DDRH + #define _SPA_IFPORT_PORTH __SPA_IFPORT_STMT(DRH) +#else + #define _SPA_IFPORT_PORTH +#endif +#ifdef _SPA_DIO_DDRJ + #define _SPA_IFPORT_PORTJ __SPA_IFPORT_STMT(DRJ) +#else + #define _SPA_IFPORT_PORTJ +#endif +#ifdef _SPA_DIO_DDRK + #define _SPA_IFPORT_PORTK __SPA_IFPORT_STMT(DRK) +#else + #define _SPA_IFPORT_PORTK +#endif +#ifdef _SPA_DIO_DDRL + #define _SPA_IFPORT_PORTL __SPA_IFPORT_STMT(DRL) +#else + #define _SPA_IFPORT_PORTL +#endif + +#define _SPA_RESOLVE_DIO(ddr) _SPA_DIO_##ddr +#define _SPA_DIOn_PORTRET(val, n) if (val == n) { \ + auto *ddrp = &DIO##n##_DDR; \ + eATmegaPort port; \ + _SPA_IFPORT_PORTA \ + _SPA_IFPORT_PORTB \ + _SPA_IFPORT_PORTC \ + _SPA_IFPORT_PORTD \ + _SPA_IFPORT_PORTE \ + _SPA_IFPORT_PORTF \ + _SPA_IFPORT_PORTG \ + _SPA_IFPORT_PORTH \ + _SPA_IFPORT_PORTJ \ + _SPA_IFPORT_PORTK \ + _SPA_IFPORT_PORTL \ + return { port, DIO##n##_PIN }; \ + } + +inline ATmegaPinInfo _ATmega_getPinInfo(uint8_t pin) { + #if DIO_NUM > 0 + _SPA_DIOn_PORTRET(pin, 0) + #endif + #if DIO_NUM > 1 + _SPA_DIOn_PORTRET(pin, 1) + #endif + #if DIO_NUM > 2 + _SPA_DIOn_PORTRET(pin, 2) + #endif + #if DIO_NUM > 3 + _SPA_DIOn_PORTRET(pin, 3) + #endif + #if DIO_NUM > 4 + _SPA_DIOn_PORTRET(pin, 4) + #endif + #if DIO_NUM > 5 + _SPA_DIOn_PORTRET(pin, 5) + #endif + #if DIO_NUM > 6 + _SPA_DIOn_PORTRET(pin, 6) + #endif + #if DIO_NUM > 7 + _SPA_DIOn_PORTRET(pin, 7) + #endif + #if DIO_NUM > 8 + _SPA_DIOn_PORTRET(pin, 8) + #endif + #if DIO_NUM > 9 + _SPA_DIOn_PORTRET(pin, 9) + #endif + + #if DIO_NUM > 10 + _SPA_DIOn_PORTRET(pin, 10) + #endif + #if DIO_NUM > 11 + _SPA_DIOn_PORTRET(pin, 11) + #endif + #if DIO_NUM > 12 + _SPA_DIOn_PORTRET(pin, 12) + #endif + #if DIO_NUM > 13 + _SPA_DIOn_PORTRET(pin, 13) + #endif + #if DIO_NUM > 14 + _SPA_DIOn_PORTRET(pin, 14) + #endif + #if DIO_NUM > 15 + _SPA_DIOn_PORTRET(pin, 15) + #endif + #if DIO_NUM > 16 + _SPA_DIOn_PORTRET(pin, 16) + #endif + #if DIO_NUM > 17 + _SPA_DIOn_PORTRET(pin, 17) + #endif + #if DIO_NUM > 18 + _SPA_DIOn_PORTRET(pin, 18) + #endif + #if DIO_NUM > 19 + _SPA_DIOn_PORTRET(pin, 19) + #endif + + #if DIO_NUM > 20 + _SPA_DIOn_PORTRET(pin, 20) + #endif + #if DIO_NUM > 21 + _SPA_DIOn_PORTRET(pin, 21) + #endif + #if DIO_NUM > 22 + _SPA_DIOn_PORTRET(pin, 22) + #endif + #if DIO_NUM > 23 + _SPA_DIOn_PORTRET(pin, 23) + #endif + #if DIO_NUM > 24 + _SPA_DIOn_PORTRET(pin, 24) + #endif + #if DIO_NUM > 25 + _SPA_DIOn_PORTRET(pin, 25) + #endif + #if DIO_NUM > 26 + _SPA_DIOn_PORTRET(pin, 26) + #endif + #if DIO_NUM > 27 + _SPA_DIOn_PORTRET(pin, 27) + #endif + #if DIO_NUM > 28 + _SPA_DIOn_PORTRET(pin, 28) + #endif + #if DIO_NUM > 29 + _SPA_DIOn_PORTRET(pin, 29) + #endif + + #if DIO_NUM > 30 + _SPA_DIOn_PORTRET(pin, 30) + #endif + #if DIO_NUM > 31 + _SPA_DIOn_PORTRET(pin, 31) + #endif + #if DIO_NUM > 32 + _SPA_DIOn_PORTRET(pin, 32) + #endif + #if DIO_NUM > 33 + _SPA_DIOn_PORTRET(pin, 33) + #endif + #if DIO_NUM > 34 + _SPA_DIOn_PORTRET(pin, 34) + #endif + #if DIO_NUM > 35 + _SPA_DIOn_PORTRET(pin, 35) + #endif + #if DIO_NUM > 36 + _SPA_DIOn_PORTRET(pin, 36) + #endif + #if DIO_NUM > 37 + _SPA_DIOn_PORTRET(pin, 37) + #endif + #if DIO_NUM > 38 + _SPA_DIOn_PORTRET(pin, 38) + #endif + #if DIO_NUM > 39 + _SPA_DIOn_PORTRET(pin, 39) + #endif + + #if DIO_NUM > 40 + _SPA_DIOn_PORTRET(pin, 40) + #endif + #if DIO_NUM > 41 + _SPA_DIOn_PORTRET(pin, 41) + #endif + #if DIO_NUM > 42 + _SPA_DIOn_PORTRET(pin, 42) + #endif + #if DIO_NUM > 43 + _SPA_DIOn_PORTRET(pin, 43) + #endif + #if DIO_NUM > 44 + _SPA_DIOn_PORTRET(pin, 44) + #endif + #if DIO_NUM > 45 + _SPA_DIOn_PORTRET(pin, 45) + #endif + #if DIO_NUM > 46 + _SPA_DIOn_PORTRET(pin, 46) + #endif + #if DIO_NUM > 47 + _SPA_DIOn_PORTRET(pin, 47) + #endif + #if DIO_NUM > 48 + _SPA_DIOn_PORTRET(pin, 48) + #endif + #if DIO_NUM > 49 + _SPA_DIOn_PORTRET(pin, 49) + #endif + + #if DIO_NUM > 50 + _SPA_DIOn_PORTRET(pin, 50) + #endif + #if DIO_NUM > 51 + _SPA_DIOn_PORTRET(pin, 51) + #endif + #if DIO_NUM > 52 + _SPA_DIOn_PORTRET(pin, 52) + #endif + #if DIO_NUM > 53 + _SPA_DIOn_PORTRET(pin, 53) + #endif + #if DIO_NUM > 54 + _SPA_DIOn_PORTRET(pin, 54) + #endif + #if DIO_NUM > 55 + _SPA_DIOn_PORTRET(pin, 55) + #endif + #if DIO_NUM > 56 + _SPA_DIOn_PORTRET(pin, 56) + #endif + #if DIO_NUM > 57 + _SPA_DIOn_PORTRET(pin, 57) + #endif + #if DIO_NUM > 58 + _SPA_DIOn_PORTRET(pin, 58) + #endif + #if DIO_NUM > 59 + _SPA_DIOn_PORTRET(pin, 59) + #endif + + #if DIO_NUM > 60 + _SPA_DIOn_PORTRET(pin, 60) + #endif + #if DIO_NUM > 61 + _SPA_DIOn_PORTRET(pin, 61) + #endif + #if DIO_NUM > 62 + _SPA_DIOn_PORTRET(pin, 62) + #endif + #if DIO_NUM > 63 + _SPA_DIOn_PORTRET(pin, 63) + #endif + #if DIO_NUM > 64 + _SPA_DIOn_PORTRET(pin, 64) + #endif + #if DIO_NUM > 65 + _SPA_DIOn_PORTRET(pin, 65) + #endif + #if DIO_NUM > 66 + _SPA_DIOn_PORTRET(pin, 66) + #endif + #if DIO_NUM > 67 + _SPA_DIOn_PORTRET(pin, 67) + #endif + #if DIO_NUM > 68 + _SPA_DIOn_PORTRET(pin, 68) + #endif + #if DIO_NUM > 69 + _SPA_DIOn_PORTRET(pin, 69) + #endif + + #if DIO_NUM > 70 + _SPA_DIOn_PORTRET(pin, 70) + #endif + #if DIO_NUM > 71 + _SPA_DIOn_PORTRET(pin, 71) + #endif + #if DIO_NUM > 72 + _SPA_DIOn_PORTRET(pin, 72) + #endif + #if DIO_NUM > 73 + _SPA_DIOn_PORTRET(pin, 73) + #endif + #if DIO_NUM > 74 + _SPA_DIOn_PORTRET(pin, 74) + #endif + #if DIO_NUM > 75 + _SPA_DIOn_PORTRET(pin, 75) + #endif + #if DIO_NUM > 76 + _SPA_DIOn_PORTRET(pin, 76) + #endif + #if DIO_NUM > 77 + _SPA_DIOn_PORTRET(pin, 77) + #endif + #if DIO_NUM > 78 + _SPA_DIOn_PORTRET(pin, 78) + #endif + #if DIO_NUM > 79 + _SPA_DIOn_PORTRET(pin, 79) + #endif + + #if DIO_NUM > 80 + _SPA_DIOn_PORTRET(pin, 80) + #endif + #if DIO_NUM > 81 + _SPA_DIOn_PORTRET(pin, 81) + #endif + #if DIO_NUM > 82 + _SPA_DIOn_PORTRET(pin, 82) + #endif + #if DIO_NUM > 83 + _SPA_DIOn_PORTRET(pin, 83) + #endif + #if DIO_NUM > 84 + _SPA_DIOn_PORTRET(pin, 84) + #endif + #if DIO_NUM > 85 + _SPA_DIOn_PORTRET(pin, 85) + #endif + #if DIO_NUM > 86 + _SPA_DIOn_PORTRET(pin, 86) + #endif + #if DIO_NUM > 87 + _SPA_DIOn_PORTRET(pin, 87) + #endif + #if DIO_NUM > 88 + _SPA_DIOn_PORTRET(pin, 88) + #endif + #if DIO_NUM > 89 + _SPA_DIOn_PORTRET(pin, 89) + #endif + + // Default. + #if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) || defined(__AVR_TRM04__) || defined(__AVR_TRM05__) + return { eATmegaPort::PORT_A, 0 }; + #elif defined(__AVR_TRM03__) + return { eATmegaPort::PORT_B, 0 }; + #endif +} + +enum class eATmegaPeripheral { + UNDEFINED, + #ifdef __AVR_TRM01__ + PADC, PUSART0, PSPI, PTIM1, PTIM0, PTIM2, PTWI, PUSART1, PUSART2, PUSART3, PTIM3, PTIM4, PTIM5 + #elif defined(__AVR_TRM02__) + PADC, PUSART0, PSPI, PTIM1, PUSART1, PTIM0, PTIM2, PTWI, PTIM3 + #elif defined(__AVR_TRM03__) + PADC, PUSART0, PSPI, PTIM1, PTIM0, PTIM2, PTWI + #elif defined(__AVR_TRM04__) + PADC, PSPI, PTIM1, PTIM0, PTIM2, PTWI, PUSART1, PTIM3, PUSB + #elif defined(__AVR_TRM05__) + PADC, PUSART0, PSPI, PTIM1, PUSART1, PTIM0, PTIM2, PTWI + #endif + , NUM_PERIPHERALS +}; + +enum class eATmegaPinFunc : uint8_t { + #ifdef __AVR_TRM01__ + EXTMEM_AD15, EXTMEM_AD14, EXTMEM_AD13, EXTMEM_AD12, EXTMEM_AD11, EXTMEM_AD10, EXTMEM_AD9, EXTMEM_AD8, + EXTMEM_AD7, EXTMEM_AD6, EXTMEM_AD5, EXTMEM_AD4, EXTMEM_AD3, EXTMEM_AD2, EXTMEM_AD1, EXTMEM_AD0, + EXTMEM_ALE, EXTMEM_RD, EXTMEM_WR, + TOC0A, TOC0B, TOC1A, TOC1B, TOC1C, TOC2A, TOC2B, TOC3C, TOC3B, TOC3A, TOC4C, TOC4B, TOC4A, TOC5C, TOC5B, TOC5A, + EINT7, EINT6, EINT5, EINT4, EINT3, EINT2, EINT1, EINT0, + PCI0, PCI1, PCI2, PCI3, PCI4, PCI5, PCI6, PCI7, + PCI8, PCI9, PCI10, PCI11, PCI12, PCI13, PCI14, PCI15, + PCI16, PCI17, PCI18, PCI19, PCI20, PCI21, PCI22, PCI23, + SPI_MISO, SPI_MOSI, SPI_SCK, SPI_CS, + TOSC1, TOSC2, + TIMER0_CLKI, TIMER1_CLKI, TIMER3_CLKI, TIMER4_CLKI, TIMER5_CLKI, + TIMER1_ICP, TIMER3_ICP, TIMER5_ICP, TIMER4_ICP, + USART0_CLK, USART1_CLK, USART2_CLK, USART3_CLK, + USART0_TXD, USART0_RXD, USART1_TXD, USART1_RXD, USART2_TXD, USART2_RXD, USART3_TXD, USART3_RXD, + TWI_SDA, TWI_CLK, + CLKO, PDO, PDI, + AIN0, AIN1, + ADC15, ADC14, ADC13, ADC12, ADC11, ADC10, ADC9, ADC8, + ADC7, ADC6, ADC5, ADC4, ADC3, ADC2, ADC1, ADC0 + #elif defined(__AVR_TRM02__) + ADC7, ADC6, ADC5, ADC4, ADC3, ADC2, ADC1, ADC0, + SPI_SCK, SPI_MISO, SPI_MOSI, SPI_CS, + PCI31, PCI30, PCI29, PCI28, PCI27, PCI26, PCI25, PCI24, + PCI23, PCI22, PCI21, PCI20, PCI19, PCI18, PCI17, PCI16, + PCI15, PCI14, PCI13, PCI12, PCI11, PCI10, PCI9, PCI8, + PCI7, PCI6, PCI5, PCI4, PCI3, PCI2, PCI1, PCI0, + EINT2, EINT1, EINT0, + TIMER3_ICP, + TIMER3_ECI, TIMER1_ECI, TIMER0_ECI, + TIMER1_ICP, + TOC3B, TOC3A, TOC2A, TOC2B, TOC1A, TOC1B, TOC0B, TOC0A, + AIN1, AIN0, + USART0_CLK, USART1_CLK, + USART0_TXD, USART0_RXD, USART1_TXD, USART1_RXD, + CLKO, + TOSC2, TOSC1, + TWI_SDA, TWI_CLK + #elif defined(__AVR_TRM03__) + ADC5, ADC4, ADC3, ADC2, ADC1, ADC0, + XTAL2, XTAL1, + TOSC2, TOSC1, + SPI_SCK, SPI_MISO, SPI_MOSI, SPI_CS, + TOC2B, TOC2A, TOC1B, TOC1A, TOC0A, TOC0B, + TIMER1_ICP, + TIMER1_ECI, TIMER0_ECI, + TWI_CLK, TWI_SDA, + PCI23, PCI22, PCI21, PCI20, PCI19, PCI18, PCI17, PCI16, + PCI14, PCI13, PCI12, PCI11, PCI10, PCI9, PCI8, + PCI7, PCI6, PCI5, PCI4, PCI3, PCI2, PCI1, PCI0, + CLKO, + AIN1, AIN0, + USART_CLK, + USART_TXD, USART_RXD, + EINT1, EINT0 + #elif defined(__AVR_TRM04__) + EXTMEM_AD15, EXTMEM_AD14, EXTMEM_AD13, EXTMEM_AD12, EXTMEM_AD11, EXTMEM_AD10, EXTMEM_AD9, EXTMEM_AD8, + EXTMEM_AD7, EXTMEM_AD6, EXTMEM_AD5, EXTMEM_AD4, EXTMEM_AD3, EXTMEM_AD2, EXTMEM_AD1, EXTMEM_AD0, + EXTMEM_ALE, EXTMEM_RD, EXTMEM_WR, + TOC0B, TOC0A, TOC1C, TOC1B, TOC1A, TOC2B, TOC2A, TOC3A, TOC3B, TOC3C, + CLKO, PDO, PDI, + SPI_MISO, SPI_MOSI, SPI_SCK, SPI_CS, + TIMER3_ICP, TIMER1_ICP, + TIMER3_CLKI, TIMER0_CLKI, TIMER1_CLKI, + USART1_CLK, USART1_TXD, USART1_RXD, + EINT7, EINT6, EINT5, EINT4, EINT3, EINT2, EINT1, EINT0, + PCI7, PCI6, PCI5, PCI4, PCI3, PCI2, PCI1, PCI0, + TWI_SDA, TWI_CLK, + AIN1, AIN0, + TOSC2, + UID, UVCON, + ADC7, ADC6, ADC5, ADC4, ADC3, ADC2, ADC1, ADC0 + #elif defined(__AVR_TRM05__) + ADC7, ADC6, ADC5, ADC4, ADC3, ADC2, ADC1, ADC0, + PCI31, PCI30, PCI29, PCI28, PCI27, PCI26, PCI25, PCI24, + PCI23, PCI22, PCI21, PCI20, PCI19, PCI18, PCI17, PCI16, + PCI15, PCI14, PCI13, PCI12, PCI11, PCI10, PCI9, PCI8, + PCI7, PCI6, PCI5, PCI4, PCI3, PCI2, PCI1, PCI0, + SPI_SCK, SPI_MISO, SPI_MOSI, SPI_CS, + AIN1, AIN0, + TIMER1_ICP, TIMER0_ICP, + TIMER1_ECI, TIMER0_ECI, + TOC0B, TOC0A, TOC2A, TOC2B, TOC1A, TOC1B, + TOSC2, TOSC1, + //JTAG_TDI, JTAG_TDO, JTAG_TMS, JTAG_TCK, + TWI_CLK, TWI_SDA, + EINT2, EINT1, EINT0, + CLKO, + USART0_CLK, USART0_TXD, USART0_RXD, + USART1_CLK, USART1_TXD, USART1_RXD + #endif + , NUM_FUNCS +}; + +#ifndef countof + #define countof(x) (sizeof(x) / sizeof(*x)) +#endif + +struct ATmegaPinFunctions { + inline ATmegaPinFunctions(const eATmegaPinFunc *funcs, uint8_t cnt) noexcept : funcs(funcs), cnt(cnt) {} + inline ATmegaPinFunctions() = default; + inline ATmegaPinFunctions(const ATmegaPinFunctions&) = default; + + const eATmegaPinFunc *funcs = nullptr; + uint8_t cnt = 0; + + inline bool hasFunc(eATmegaPinFunc query) const { + for (uint8_t n = 0; n < this->cnt; n++) { + eATmegaPinFunc func = this->funcs[n]; + if (func == query) return true; + } + return false; + } + template + inline bool hasFunc(eATmegaPinFunc func, otherItemType&&... items) const { + return hasFunc(func) || hasFunc(((otherItemType&&)items)...); + } + + template + inline void iterate(callbackType&& cb) const { + for (uint8_t n = 0; n < this->cnt; n++) { + eATmegaPinFunc func = this->funcs[n]; + cb(func); + } + } +}; + +ATmegaPinFunctions _ATmega_getPinFunctions(int pin); + +struct ATmegaPinFuncSet { + inline ATmegaPinFuncSet() noexcept { + for (bool& f : this->funcs) f = false; + } + template + inline ATmegaPinFuncSet(eATmegaPinFunc func, funcItemType&&... items) noexcept : ATmegaPinFuncSet() { + add(func, ((funcItemType&&)items)...); + } + template + inline ATmegaPinFuncSet(int pin, funcItemType&&... items) noexcept : ATmegaPinFuncSet() { + addFromPin(pin, ((funcItemType&&)items)...); + } + inline ATmegaPinFuncSet(const ATmegaPinFuncSet&) = default; + + inline void add(eATmegaPinFunc value) noexcept { + this->funcs[(uint8_t)value] = true; + } + template + inline void add(eATmegaPinFunc value, funcItemType&&... items) { + add(value); + add(((eATmegaPinFunc&&)items)...); + } + + inline void addFromPin(int pin) noexcept { + ATmegaPinFunctions funcs = _ATmega_getPinFunctions(pin); + funcs.iterate( + [this]( eATmegaPinFunc func ) noexcept { this->add(func); } + ); + } + template + inline void addFromPin(int pin, itemType&&... items) noexcept { + addFromPin(pin); + addFromPin(((itemType&&)items)...); + } + + inline bool hasFunc(eATmegaPinFunc value) const noexcept { + return this->funcs[(uint8_t)value]; + } + + inline bool hasAnyFunc() const noexcept { return false; } + template + inline bool hasAnyFunc(funcItem&& item, otherFuncItem&&... funcs) const noexcept { + return hasFunc(item) || hasAnyFunc(((otherFuncItem&&)funcs)...); + } + + template + inline void iterate(callbackType&& cb) const { + for (uint8_t n = 1; n < countof(this->funcs); n++) { + const bool& f = this->funcs[n]; + if (f) cb((eATmegaPinFunc)n); + } + } + +private: + bool funcs[(uint8_t)eATmegaPinFunc::NUM_FUNCS]; +}; + +inline void _ATmega_setPeripheralPower(eATmegaPeripheral peri, bool fullPower) { + bool reducePower = (fullPower == false); + switch(peri) { + #ifdef __AVR_TRM01__ + case eATmegaPeripheral::PADC: _PRR0._PRADC = reducePower; break; + case eATmegaPeripheral::PUSART0: _PRR0._PRUSART0 = reducePower; break; + case eATmegaPeripheral::PSPI: _PRR0._PRSPI = reducePower; break; + case eATmegaPeripheral::PTIM1: _PRR0._PRTIM1 = reducePower; break; + case eATmegaPeripheral::PTIM0: _PRR0._PRTIM0 = reducePower; break; + case eATmegaPeripheral::PTIM2: _PRR0._PRTIM2 = reducePower; break; + case eATmegaPeripheral::PTWI: _PRR0._PRTWI = reducePower; break; + case eATmegaPeripheral::PUSART1: _PRR1._PRUSART1 = reducePower; break; + case eATmegaPeripheral::PUSART2: _PRR1._PRUSART2 = reducePower; break; + case eATmegaPeripheral::PUSART3: _PRR1._PRUSART3 = reducePower; break; + case eATmegaPeripheral::PTIM3: _PRR1._PRTIM3 = reducePower; break; + case eATmegaPeripheral::PTIM4: _PRR1._PRTIM4 = reducePower; break; + case eATmegaPeripheral::PTIM5: _PRR1._PRTIM5 = reducePower; break; + #elif defined(__AVR_TRM02__) + case eATmegaPeripheral::PADC: _PRR0._PRADC = reducePower; break; + case eATmegaPeripheral::PUSART0: _PRR0._PRUSART0 = reducePower; break; + case eATmegaPeripheral::PSPI: _PRR0._PRSPI = reducePower; break; + case eATmegaPeripheral::PTIM1: _PRR0._PRTIM1 = reducePower; break; + case eATmegaPeripheral::PUSART1: _PRR0._PRUSART1 = reducePower; break; + case eATmegaPeripheral::PTIM0: _PRR0._PRTIM0 = reducePower; break; + case eATmegaPeripheral::PTIM2: _PRR0._PRTIM2 = reducePower; break; + case eATmegaPeripheral::PTWI: _PRR0._PRTWI = reducePower; break; + case eATmegaPeripheral::PTIM3: _PRR1._PRTIM3 = reducePower; break; + #elif defined(__AVR_TRM03__) + case eATmegaPeripheral::PADC: _PRR0._PRADC = reducePower; break; + case eATmegaPeripheral::PUSART0: _PRR0._PRUSART0 = reducePower; break; + case eATmegaPeripheral::PSPI: _PRR0._PRSPI = reducePower; break; + case eATmegaPeripheral::PTIM1: _PRR0._PRTIM1 = reducePower; break; + case eATmegaPeripheral::PTIM0: _PRR0._PRTIM0 = reducePower; break; + case eATmegaPeripheral::PTIM2: _PRR0._PRTIM2 = reducePower; break; + case eATmegaPeripheral::PTWI: _PRR0._PRTWI = reducePower; break; + #elif defined(__AVR_TRM04__) + case eATmegaPeripheral::PADC: _PRR0._PRADC = reducePower; break; + case eATmegaPeripheral::PSPI: _PRR0._PRSPI = reducePower; break; + case eATmegaPeripheral::PTIM1: _PRR0._PRTIM1 = reducePower; break; + case eATmegaPeripheral::PTIM0: _PRR0._PRTIM0 = reducePower; break; + case eATmegaPeripheral::PTIM2: _PRR0._PRTIM2 = reducePower; break; + case eATmegaPeripheral::PTWI: _PRR0._PRTWI = reducePower; break; + case eATmegaPeripheral::PUSART1: _PRR1._PRUSART1 = reducePower; break; + case eATmegaPeripheral::PTIM3: _PRR1._PRTIM3 = reducePower; break; + case eATmegaPeripheral::PUSB: _PRR1._PRUSB = reducePower; break; + #elif defined(__AVR_TRM05__) + case eATmegaPeripheral::PADC: _PRR0._PRADC = reducePower; break; + case eATmegaPeripheral::PUSART0: _PRR0._PRUSART0 = reducePower; break; + case eATmegaPeripheral::PSPI: _PRR0._PRSPI = reducePower; break; + case eATmegaPeripheral::PTIM1: _PRR0._PRTIM1 = reducePower; break; + case eATmegaPeripheral::PUSART1: _PRR0._PRUSART1 = reducePower; break; + case eATmegaPeripheral::PTIM0: _PRR0._PRTIM0 = reducePower; break; + case eATmegaPeripheral::PTIM2: _PRR0._PRTIM2 = reducePower; break; + case eATmegaPeripheral::PTWI: _PRR0._PRTWI = reducePower; break; + #endif + case eATmegaPeripheral::UNDEFINED: case eATmegaPeripheral::NUM_PERIPHERALS: break; + } +} + +inline bool _ATmega_getPeripheralPower(eATmegaPeripheral peri) { + switch(peri) { + #ifdef __AVR_TRM01__ + case eATmegaPeripheral::PADC: return _PRR0._PRADC == false; + case eATmegaPeripheral::PUSART0: return _PRR0._PRUSART0 == false; + case eATmegaPeripheral::PSPI: return _PRR0._PRSPI == false; + case eATmegaPeripheral::PTIM1: return _PRR0._PRTIM1 == false; + case eATmegaPeripheral::PTIM0: return _PRR0._PRTIM0 == false; + case eATmegaPeripheral::PTIM2: return _PRR0._PRTIM2 == false; + case eATmegaPeripheral::PTWI: return _PRR0._PRTWI == false; + case eATmegaPeripheral::PUSART1: return _PRR1._PRUSART1 == false; + case eATmegaPeripheral::PUSART2: return _PRR1._PRUSART2 == false; + case eATmegaPeripheral::PUSART3: return _PRR1._PRUSART3 == false; + case eATmegaPeripheral::PTIM3: return _PRR1._PRTIM3 == false; + case eATmegaPeripheral::PTIM4: return _PRR1._PRTIM4 == false; + case eATmegaPeripheral::PTIM5: return _PRR1._PRTIM5 == false; + #elif defined(__AVR_TRM02__) + case eATmegaPeripheral::PADC: return _PRR0._PRADC == false; + case eATmegaPeripheral::PUSART0: return _PRR0._PRUSART0 == false; + case eATmegaPeripheral::PSPI: return _PRR0._PRSPI == false; + case eATmegaPeripheral::PTIM1: return _PRR0._PRTIM1 == false; + case eATmegaPeripheral::PUSART1: return _PRR0._PRUSART1 == false; + case eATmegaPeripheral::PTIM0: return _PRR0._PRTIM0 == false; + case eATmegaPeripheral::PTIM2: return _PRR0._PRTIM2 == false; + case eATmegaPeripheral::PTWI: return _PRR0._PRTWI == false; + case eATmegaPeripheral::PTIM3: return _PRR1._PRTIM3 == false; + #elif defined(__AVR_TRM03__) + case eATmegaPeripheral::PADC: return _PRR0._PRADC == false; + case eATmegaPeripheral::PUSART0: return _PRR0._PRUSART0 == false; + case eATmegaPeripheral::PSPI: return _PRR0._PRSPI == false; + case eATmegaPeripheral::PTIM1: return _PRR0._PRTIM1 == false; + case eATmegaPeripheral::PTIM0: return _PRR0._PRTIM0 == false; + case eATmegaPeripheral::PTIM2: return _PRR0._PRTIM2 == false; + case eATmegaPeripheral::PTWI: return _PRR0._PRTWI == false; + #elif defined(__AVR_TRM04__) + case eATmegaPeripheral::PADC: return _PRR0._PRADC == false; + case eATmegaPeripheral::PSPI: return _PRR0._PRSPI == false; + case eATmegaPeripheral::PTIM1: return _PRR0._PRTIM1 == false; + case eATmegaPeripheral::PTIM0: return _PRR0._PRTIM0 == false; + case eATmegaPeripheral::PTIM2: return _PRR0._PRTIM2 == false; + case eATmegaPeripheral::PTWI: return _PRR0._PRTWI == false; + case eATmegaPeripheral::PUSART1: return _PRR1._PRUSART1 == false; + case eATmegaPeripheral::PTIM3: return _PRR1._PRTIM3 == false; + case eATmegaPeripheral::PUSB: return _PRR1._PRUSB == false; + #elif defined(__AVR_TRM05__) + case eATmegaPeripheral::PADC: return _PRR0._PRADC == false; + case eATmegaPeripheral::PUSART0: return _PRR0._PRUSART0 == false; + case eATmegaPeripheral::PSPI: return _PRR0._PRSPI == false; + case eATmegaPeripheral::PTIM1: return _PRR0._PRTIM1 == false; + case eATmegaPeripheral::PUSART1: return _PRR0._PRUSART1 == false; + case eATmegaPeripheral::PTIM0: return _PRR0._PRTIM0 == false; + case eATmegaPeripheral::PTIM2: return _PRR0._PRTIM2 == false; + case eATmegaPeripheral::PTWI: return _PRR0._PRTWI == false; + #endif + case eATmegaPeripheral::UNDEFINED: case eATmegaPeripheral::NUM_PERIPHERALS: break; + } + return false; +} + +inline eATmegaPeripheral _ATmega_getPeripheralForFunc( eATmegaPinFunc func ) { + // In C++20 there is the "using-enum" statement. I wish we had C++20 over here... + //using enum eATmegaPinFunc; + switch(func) { + #ifdef __AVR_TRM01__ + case eATmegaPinFunc::TOC0A: case eATmegaPinFunc::TOC0B: return eATmegaPeripheral::PTIM0; + case eATmegaPinFunc::TOC1A: case eATmegaPinFunc::TOC1B: case eATmegaPinFunc::TOC1C: return eATmegaPeripheral::PTIM1; + case eATmegaPinFunc::TOC2A: case eATmegaPinFunc::TOC2B: return eATmegaPeripheral::PTIM2; + case eATmegaPinFunc::TOC3A: case eATmegaPinFunc::TOC3B: case eATmegaPinFunc::TOC3C: return eATmegaPeripheral::PTIM3; + case eATmegaPinFunc::TOC4A: case eATmegaPinFunc::TOC4B: case eATmegaPinFunc::TOC4C: return eATmegaPeripheral::PTIM4; + case eATmegaPinFunc::TOC5A: case eATmegaPinFunc::TOC5B: case eATmegaPinFunc::TOC5C: return eATmegaPeripheral::PTIM5; + case eATmegaPinFunc::SPI_MISO: case eATmegaPinFunc::SPI_MOSI: case eATmegaPinFunc::SPI_SCK: case eATmegaPinFunc::SPI_CS: return eATmegaPeripheral::PSPI; + case eATmegaPinFunc::TIMER0_CLKI: return eATmegaPeripheral::PTIM0; + case eATmegaPinFunc::TIMER1_CLKI: case eATmegaPinFunc::TIMER1_ICP: return eATmegaPeripheral::PTIM1; + case eATmegaPinFunc::TIMER3_CLKI: case eATmegaPinFunc::TIMER3_ICP: return eATmegaPeripheral::PTIM3; + case eATmegaPinFunc::TIMER4_CLKI: case eATmegaPinFunc::TIMER4_ICP: return eATmegaPeripheral::PTIM4; + case eATmegaPinFunc::TIMER5_CLKI: case eATmegaPinFunc::TIMER5_ICP: return eATmegaPeripheral::PTIM5; + case eATmegaPinFunc::USART0_CLK: case eATmegaPinFunc::USART0_TXD: case eATmegaPinFunc::USART0_RXD: return eATmegaPeripheral::PUSART0; + case eATmegaPinFunc::USART1_CLK: case eATmegaPinFunc::USART1_TXD: case eATmegaPinFunc::USART1_RXD: return eATmegaPeripheral::PUSART1; + case eATmegaPinFunc::USART2_CLK: case eATmegaPinFunc::USART2_TXD: case eATmegaPinFunc::USART2_RXD: return eATmegaPeripheral::PUSART2; + case eATmegaPinFunc::USART3_CLK: case eATmegaPinFunc::USART3_TXD: case eATmegaPinFunc::USART3_RXD: return eATmegaPeripheral::PUSART3; + case eATmegaPinFunc::TWI_SDA: case eATmegaPinFunc::TWI_CLK: return eATmegaPeripheral::PTWI; + case eATmegaPinFunc::ADC15: case eATmegaPinFunc::ADC14: case eATmegaPinFunc::ADC13: case eATmegaPinFunc::ADC12: case eATmegaPinFunc::ADC11: case eATmegaPinFunc::ADC10: case eATmegaPinFunc::ADC9: case eATmegaPinFunc::ADC8: + case eATmegaPinFunc::ADC7: case eATmegaPinFunc::ADC6: case eATmegaPinFunc::ADC5: case eATmegaPinFunc::ADC4: case eATmegaPinFunc::ADC3: case eATmegaPinFunc::ADC2: case eATmegaPinFunc::ADC1: case eATmegaPinFunc::ADC0: + return eATmegaPeripheral::PADC; + #elif defined(__AVR_TRM02__) + case eATmegaPinFunc::ADC7: case eATmegaPinFunc::ADC6: case eATmegaPinFunc::ADC5: case eATmegaPinFunc::ADC4: case eATmegaPinFunc::ADC3: case eATmegaPinFunc::ADC2: case eATmegaPinFunc::ADC1: case eATmegaPinFunc::ADC0: + return eATmegaPeripheral::PADC; + case eATmegaPinFunc::SPI_SCK: case eATmegaPinFunc::SPI_MISO: case eATmegaPinFunc::SPI_MOSI: case eATmegaPinFunc::SPI_CS: return eATmegaPeripheral::PSPI; + case eATmegaPinFunc::TIMER3_ICP: case eATmegaPinFunc::TIMER3_ECI: return eATmegaPeripheral::PTIM3; + case eATmegaPinFunc::TIMER1_ECI: case eATmegaPinFunc::TIMER1_ICP: return eATmegaPeripheral::PTIM1; + case eATmegaPinFunc::TIMER0_ECI: return eATmegaPeripheral::PTIM0; + case eATmegaPinFunc::TOC3B: case eATmegaPinFunc::TOC3A: return eATmegaPeripheral::PTIM3; + case eATmegaPinFunc::TOC2A: case eATmegaPinFunc::TOC2B: return eATmegaPeripheral::PTIM2; + case eATmegaPinFunc::TOC1A: case eATmegaPinFunc::TOC1B: return eATmegaPeripheral::PTIM1; + case eATmegaPinFunc::TOC0B: case eATmegaPinFunc::TOC0A: return eATmegaPeripheral::PTIM0; + case eATmegaPinFunc::USART0_CLK: case eATmegaPinFunc::USART0_TXD: case eATmegaPinFunc::USART0_RXD: return eATmegaPeripheral::PUSART0; + case eATmegaPinFunc::USART1_CLK: case eATmegaPinFunc::USART1_TXD: case eATmegaPinFunc::USART1_RXD: return eATmegaPeripheral::PUSART1; + case eATmegaPinFunc::TWI_SDA: case eATmegaPinFunc::TWI_CLK: return eATmegaPeripheral::PTWI; + #elif defined(__AVR_TRM03__) + case eATmegaPinFunc::ADC5: case eATmegaPinFunc::ADC4: case eATmegaPinFunc::ADC3: case eATmegaPinFunc::ADC2: case eATmegaPinFunc::ADC1: case eATmegaPinFunc::ADC0: + return eATmegaPeripheral::PADC; + case eATmegaPinFunc::SPI_SCK: case eATmegaPinFunc::SPI_MISO: case eATmegaPinFunc::SPI_MOSI: case eATmegaPinFunc::SPI_CS: return eATmegaPeripheral::PSPI; + case eATmegaPinFunc::TOC2B: case eATmegaPinFunc::TOC2A: return eATmegaPeripheral::PTIM2; + case eATmegaPinFunc::TOC1B: case eATmegaPinFunc::TOC1A: return eATmegaPeripheral::PTIM1; + case eATmegaPinFunc::TOC0A: case eATmegaPinFunc::TOC0B: return eATmegaPeripheral::PTIM0; + case eATmegaPinFunc::TIMER1_ICP: case eATmegaPinFunc::TIMER1_ECI: return eATmegaPeripheral::PTIM1; + case eATmegaPinFunc::TIMER0_ECI: return eATmegaPeripheral::PTIM0; + case eATmegaPinFunc::TWI_CLK: case eATmegaPinFunc::TWI_SDA: return eATmegaPeripheral::PTWI; + case eATmegaPinFunc::USART_CLK: case eATmegaPinFunc::USART_TXD: case eATmegaPinFunc::USART_RXD: return eATmegaPeripheral::PUSART0; + #elif defined(__AVR_TRM04__) + case eATmegaPinFunc::TOC0B: case eATmegaPinFunc::TOC0A: return eATmegaPeripheral::PTIM0; + case eATmegaPinFunc::TOC1C: case eATmegaPinFunc::TOC1B: case eATmegaPinFunc::TOC1A: return eATmegaPeripheral::PTIM1; + case eATmegaPinFunc::TOC2B: case eATmegaPinFunc::TOC2A: return eATmegaPeripheral::PTIM2; + case eATmegaPinFunc::TOC3A: case eATmegaPinFunc::TOC3B: case eATmegaPinFunc::TOC3C: return eATmegaPeripheral::PTIM3; + case eATmegaPinFunc::SPI_MISO: case eATmegaPinFunc::SPI_MOSI: case eATmegaPinFunc::SPI_SCK: case eATmegaPinFunc::SPI_CS: return eATmegaPeripheral::PSPI; + case eATmegaPinFunc::TIMER3_ICP: case eATmegaPinFunc::TIMER3_CLKI: return eATmegaPeripheral::PTIM3; + case eATmegaPinFunc::TIMER1_ICP: case eATmegaPinFunc::TIMER1_CLKI: return eATmegaPeripheral::PTIM1; + case eATmegaPinFunc::TIMER0_CLKI: return eATmegaPeripheral::PTIM0; + case eATmegaPinFunc::USART1_CLK: case eATmegaPinFunc::USART1_TXD: case eATmegaPinFunc::USART1_RXD: return eATmegaPeripheral::PUSART1; + case eATmegaPinFunc::TWI_SDA: case eATmegaPinFunc::TWI_CLK: return eATmegaPeripheral::PTWI; + case eATmegaPinFunc::UID: case eATmegaPinFunc::UVCON: return eATmegaPeripheral::PUSB; + case eATmegaPinFunc::ADC7: case eATmegaPinFunc::ADC6: case eATmegaPinFunc::ADC5: case eATmegaPinFunc::ADC4: case eATmegaPinFunc::ADC3: case eATmegaPinFunc::ADC2: case eATmegaPinFunc::ADC1: case eATmegaPinFunc::ADC0: + return eATmegaPeripheral::PADC; + #elif defined(__AVR_TRM05__) + case eATmegaPinFunc::ADC7: case eATmegaPinFunc::ADC6: case eATmegaPinFunc::ADC5: case eATmegaPinFunc::ADC4: case eATmegaPinFunc::ADC3: case eATmegaPinFunc::ADC2: case eATmegaPinFunc::ADC1: case eATmegaPinFunc::ADC0: + return eATmegaPeripheral::PADC; + case eATmegaPinFunc::SPI_MISO: case eATmegaPinFunc::SPI_MOSI: case eATmegaPinFunc::SPI_SCK: case eATmegaPinFunc::SPI_CS: return eATmegaPeripheral::PSPI; + case eATmegaPinFunc::TIMER1_ICP: case eATmegaPinFunc::TIMER1_ECI: return eATmegaPeripheral::PTIM1; + case eATmegaPinFunc::TIMER0_ICP: case eATmegaPinFunc::TIMER0_ECI: return eATmegaPeripheral::PTIM0; + case eATmegaPinFunc::TOC0B: case eATmegaPinFunc::TOC0A: return eATmegaPeripheral::PTIM0; + case eATmegaPinFunc::TOC1A: case eATmegaPinFunc::TOC1B: return eATmegaPeripheral::PTIM1; + case eATmegaPinFunc::TOC2A: case eATmegaPinFunc::TOC2B: return eATmegaPeripheral::PTIM2; + case eATmegaPinFunc::TWI_CLK: case eATmegaPinFunc::TWI_SDA: return eATmegaPeripheral::PTWI; + case eATmegaPinFunc::USART0_CLK: case eATmegaPinFunc::USART0_TXD: case eATmegaPinFunc::USART0_RXD: return eATmegaPeripheral::PUSART0; + case eATmegaPinFunc::USART1_CLK: case eATmegaPinFunc::USART1_TXD: case eATmegaPinFunc::USART1_RXD: return eATmegaPeripheral::PUSART1; + #endif + // There are quite some pin functions that have no peripheral assignment, and that is OK! + default: break; + } + return eATmegaPeripheral::UNDEFINED; +} + +struct ATmegaPeripheralSet { + inline ATmegaPeripheralSet() noexcept { + for (bool& f : this->funcs) f = false; + } + template + inline ATmegaPeripheralSet(funcItemType&&... items) noexcept : ATmegaPinFuncSet() { + add(((eATmegaPinFunc&&)items)...); + } + inline ATmegaPeripheralSet(const ATmegaPeripheralSet&) = default; + + inline void add(eATmegaPeripheral value) noexcept { + this->funcs[(uint8_t)value] = true; + } + template + inline void add(eATmegaPeripheral value, funcItemType&&... items) noexcept { + add(value); + add(((funcItemType&&)items)...); + } + + inline bool hasItem(eATmegaPeripheral value) const noexcept { + return this->funcs[(uint8_t)value]; + } + template + inline bool hasItem(eATmegaPeripheral&& item, otherFuncItem&&... funcs) const noexcept { + return hasItem(item) || hasItem(((otherFuncItem&&)funcs)...); + } + + template + inline void iterate(callbackType&& cb) const { + for (uint8_t n = 1; n < countof(funcs); n++) { + const bool& f = this->funcs[n]; + if (f) cb( (eATmegaPeripheral)n ); + } + } + + inline void fromPinFuncs(const ATmegaPinFuncSet& funcSet) { + funcSet.iterate( + [this]( eATmegaPinFunc func ) noexcept { + this->add( _ATmega_getPeripheralForFunc(func) ); + } + ); + } + +private: + bool funcs[(uint8_t)eATmegaPeripheral::NUM_PERIPHERALS]; +}; + +struct ATmegaPeripheralPowerGate { + inline ATmegaPeripheralPowerGate(ATmegaPeripheralSet& periSet) noexcept : periSet(periSet) { + periSet.iterate( + [this]( eATmegaPeripheral peri ) noexcept { + this->states[(uint8_t)peri] = _ATmega_getPeripheralPower(peri); + _ATmega_setPeripheralPower(peri, true); + } + ); + } + inline ATmegaPeripheralPowerGate(const ATmegaPeripheralPowerGate&) = delete; + + inline ~ATmegaPeripheralPowerGate() { + periSet.iterate( + [this]( eATmegaPeripheral peri ) noexcept { + _ATmega_setPeripheralPower(peri, this->states[(uint8_t)peri]); + } + ); + } + + inline ATmegaPeripheralPowerGate& operator = (const ATmegaPeripheralPowerGate&) = delete; + +private: + ATmegaPeripheralSet& periSet; + bool states[(uint8_t)eATmegaPeripheral::NUM_PERIPHERALS]; +}; + +inline pin_dev_state_t _ATmega_savePinAlternates(const ATmegaPinFuncSet& funcSet) { + // TODO: the manual states that registers of power-reduced peripherals cannot be read or written, and that + // the resources (GPIO pins) remain occupied during power-reduction. This is a serious problem and we should + // add power-reduction awareness to this logic! + + pin_dev_state_t state; + + ATmegaPeripheralSet periSet; + periSet.fromPinFuncs(funcSet); + + ATmegaPeripheralPowerGate pgate(periSet); + + #ifdef __AVR_TRM01__ + // See page 75ff of ATmega2560 technical reference manual. + if (funcSet.hasAnyFunc( + eATmegaPinFunc::EXTMEM_AD15, eATmegaPinFunc::EXTMEM_AD14, eATmegaPinFunc::EXTMEM_AD13, eATmegaPinFunc::EXTMEM_AD12, eATmegaPinFunc::EXTMEM_AD11, eATmegaPinFunc::EXTMEM_AD10, eATmegaPinFunc::EXTMEM_AD9, eATmegaPinFunc::EXTMEM_AD8, + eATmegaPinFunc::EXTMEM_AD7, eATmegaPinFunc::EXTMEM_AD6, eATmegaPinFunc::EXTMEM_AD5, eATmegaPinFunc::EXTMEM_AD4, eATmegaPinFunc::EXTMEM_AD3, eATmegaPinFunc::EXTMEM_AD2, eATmegaPinFunc::EXTMEM_AD1, eATmegaPinFunc::EXTMEM_AD0, + eATmegaPinFunc::EXTMEM_ALE, eATmegaPinFunc::EXTMEM_RD, eATmegaPinFunc::EXTMEM_WR + )) { + state._SRE = _XMCRA._SRE; + _XMCRA._SRE = false; + } + if (funcSet.hasAnyFunc( + eATmegaPinFunc::PCI0, eATmegaPinFunc::PCI1, eATmegaPinFunc::PCI2, eATmegaPinFunc::PCI3, eATmegaPinFunc::PCI4, eATmegaPinFunc::PCI5, eATmegaPinFunc::PCI6, eATmegaPinFunc::PCI7 + )) { + state._PCIE0 = _PCICR._PCIE0; + _PCICR._PCIE0 = false; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC1C)) { + state._COM1C = TIMER1._TCCRnA._COMnC; + TIMER1._TCCRnA._COMnC = 0; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC1B)) { + state._COM1B = TIMER1._TCCRnA._COMnB; + TIMER1._TCCRnA._COMnB = 0; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC1A)) { + state._COM1A = TIMER1._TCCRnA._COMnA; + TIMER1._TCCRnA._COMnA = 0; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC2A)) { + state._COM2A = _TIMER2._TCCRnA._COMnA; + _TIMER2._TCCRnA._COMnA = 0; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::USART1_TXD, eATmegaPinFunc::USART1_CLK)) { + state._USART1_TXEN = USART1._UCSRnB._TXEN; + USART1._UCSRnB._TXEN = false; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::USART1_RXD, eATmegaPinFunc::USART1_CLK)) { + state._USART1_RXEN = USART1._UCSRnB._RXEN; + USART1._UCSRnB._RXEN = false; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC3C)) { + state._COM3C = TIMER3._TCCRnA._COMnC; + TIMER3._TCCRnA._COMnC = 0; + } + // There is an error in the technical reference manual signal mapping table + // of ATmega2560 where is says that pin 3 is mapped to OC3B, but the list + // says OC3A. + if (funcSet.hasFunc(eATmegaPinFunc::TOC3B)) { + state._COM3B = TIMER3._TCCRnA._COMnB; + TIMER3._TCCRnA._COMnB = 0; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC3A)) { + state._COM3A = TIMER3._TCCRnA._COMnA; + TIMER3._TCCRnA._COMnA = 0; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::USART0_RXD, eATmegaPinFunc::USART0_CLK)) { + state._USART0_RXEN = USART0._UCSRnB._RXEN; + USART0._UCSRnB._RXEN = false; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::USART0_TXD, eATmegaPinFunc::USART0_CLK)) { + state._USART0_TXEN = USART0._UCSRnB._TXEN; + USART0._UCSRnB._TXEN = false; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::PCI15, eATmegaPinFunc::PCI14, eATmegaPinFunc::PCI13, eATmegaPinFunc::PCI12, eATmegaPinFunc::PCI11, eATmegaPinFunc::PCI10, eATmegaPinFunc::PCI9, eATmegaPinFunc::PCI8)) { + state._PCIE1 = _PCICR._PCIE1; + _PCICR._PCIE1 = false; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::TOC0B)) { + state._COM0B = TIMER0._TCCRnA._COMnB; + TIMER0._TCCRnA._COMnB = 0; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::TOSC1, eATmegaPinFunc::TOSC2)) { + state._AS2 = _ASSR._AS2; + _ASSR._AS2 = false; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC2B)) { + state._COM2B = _TIMER2._TCCRnA._COMnB; + _TIMER2._TCCRnA._COMnB = 0; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC4C)) { + state._COM4C = TIMER4._TCCRnA._COMnC; + TIMER4._TCCRnA._COMnC = 0; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC4B)) { + state._COM4B = TIMER4._TCCRnA._COMnB; + TIMER4._TCCRnA._COMnB = 0; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC4A)) { + state._COM4A = TIMER4._TCCRnA._COMnA; + TIMER4._TCCRnA._COMnA = 0; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::USART2_RXD, eATmegaPinFunc::USART2_CLK)) { + state._USART2_RXEN = USART2._UCSRnB._RXEN; + USART2._UCSRnB._RXEN = false; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::USART2_TXD, eATmegaPinFunc::USART2_CLK)) { + state._USART2_TXEN = USART2._UCSRnB._TXEN; + USART2._UCSRnB._TXEN = false; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::USART3_TXD, eATmegaPinFunc::USART3_CLK)) { + state._USART3_RXEN = USART3._UCSRnB._RXEN; + USART3._UCSRnB._RXEN = false; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::USART3_TXD, eATmegaPinFunc::USART3_CLK)) { + state._USART3_TXEN = USART3._UCSRnB._TXEN; + USART3._UCSRnB._TXEN = false; + } + if (funcSet.hasAnyFunc( + eATmegaPinFunc::PCI23, eATmegaPinFunc::PCI22, eATmegaPinFunc::PCI21, eATmegaPinFunc::PCI20, eATmegaPinFunc::PCI19, eATmegaPinFunc::PCI18, eATmegaPinFunc::PCI17, eATmegaPinFunc::PCI16 + )) { + state._PCIE2 = _PCICR._PCIE2; + _PCICR._PCIE2 = false; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC5C)) { + state._COM5C = TIMER5._TCCRnA._COMnC; + TIMER5._TCCRnA._COMnC = 0; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC5B)) { + state._COM5B = TIMER5._TCCRnA._COMnB; + TIMER5._TCCRnA._COMnB = 0; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC5A)) { + state._COM5A = TIMER5._TCCRnA._COMnA; + TIMER5._TCCRnA._COMnA = 0; + } + #elif defined(__AVR_TRM02__) + if (funcSet.hasAnyFunc(eATmegaPinFunc::PCI7, eATmegaPinFunc::PCI6, eATmegaPinFunc::PCI5, eATmegaPinFunc::PCI4, eATmegaPinFunc::PCI3, eATmegaPinFunc::PCI2, eATmegaPinFunc::PCI1, eATmegaPinFunc::PCI0)) { + state._PCIE0 = _PCICR._PCIE0; + _PCICR._PCIE0 = false; + } + if (funcSet.hasFunc(eATmegaPinFunc::ADC7)) { + state._ADC7D = _DIDR0._ADC7D; + _DIDR0._ADC7D = false; + } + if (funcSet.hasFunc(eATmegaPinFunc::ADC6)) { + state._ADC6D = _DIDR0._ADC6D; + _DIDR0._ADC6D = false; + } + if (funcSet.hasFunc(eATmegaPinFunc::ADC5)) { + state._ADC5D = _DIDR0._ADC5D; + _DIDR0._ADC5D = false; + } + if (funcSet.hasFunc(eATmegaPinFunc::ADC4)) { + state._ADC4D = _DIDR0._ADC4D; + _DIDR0._ADC4D = false; + } + if (funcSet.hasFunc(eATmegaPinFunc::ADC3)) { + state._ADC3D = _DIDR0._ADC3D; + _DIDR0._ADC3D = false; + } + if (funcSet.hasFunc(eATmegaPinFunc::ADC2)) { + state._ADC2D = _DIDR0._ADC2D; + _DIDR0._ADC2D = false; + } + if (funcSet.hasFunc(eATmegaPinFunc::ADC1)) { + state._ADC1D = _DIDR0._ADC1D; + _DIDR0._ADC1D = false; + } + if (funcSet.hasFunc(eATmegaPinFunc::ADC0)) { + state._ADC0D = _DIDR0._ADC0D; + _DIDR0._ADC0D = false; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::PCI15, eATmegaPinFunc::PCI14, eATmegaPinFunc::PCI13, eATmegaPinFunc::PCI12, eATmegaPinFunc::PCI11, eATmegaPinFunc::PCI10, eATmegaPinFunc::PCI9, eATmegaPinFunc::PCI8)) { + state._PCIE1 = _PCICR._PCIE1; + _PCICR._PCIE1 = false; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::SPI_SCK, eATmegaPinFunc::SPI_MISO, eATmegaPinFunc::SPI_MOSI, eATmegaPinFunc::SPI_CS)) { + state._SPE = _SPCR._SPE; + _SPCR._SPE = false; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC0A)) { + state._COM0A = TIMER0._TCCRnA._COMnA; + TIMER0._TCCRnA._COMnA = 0; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC0B)) { + state._COM0B = TIMER0._TCCRnA._COMnB; + TIMER0._TCCRnA._COMnB = 0; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::TOSC1, eATmegaPinFunc::TOSC2)) { + state._AS2 = _ASSR._AS2; + _ASSR._AS2 = false; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::PCI23, eATmegaPinFunc::PCI22, eATmegaPinFunc::PCI21, eATmegaPinFunc::PCI20, eATmegaPinFunc::PCI19, eATmegaPinFunc::PCI18, eATmegaPinFunc::PCI17, eATmegaPinFunc::PCI16)) { + state._PCIE2 = _PCICR._PCIE2; + _PCICR._PCIE2 = false; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::PCI31, eATmegaPinFunc::PCI30, eATmegaPinFunc::PCI29, eATmegaPinFunc::PCI28, eATmegaPinFunc::PCI27, eATmegaPinFunc::PCI26, eATmegaPinFunc::PCI25, eATmegaPinFunc::PCI24)) { + state._PCIE3 = _PCICR._PCIE3; + _PCICR._PCIE3 = false; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC2A)) { + state._COM2A = _TIMER2._TCCRnA._COMnA; + _TIMER2._TCCRnA._COMnA = 0; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC2B)) { + state._COM2B = _TIMER2._TCCRnA._COMnB; + _TIMER2._TCCRnA._COMnB = 0; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC1A)) { + state._COM1A = TIMER1._TCCRnA._COMnA; + TIMER1._TCCRnA._COMnA = 0; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC1B)) { + state._COM1B = TIMER1._TCCRnA._COMnB; + TIMER1._TCCRnA._COMnB = 0; + } + if (funcSet.hasFunc(eATmegaPinFunc::USART1_TXD)) { + state._USART1_TXEN = USART1._UCSRnB._TXEN; + USART1._UCSRnB._TXEN = false; + } + if (funcSet.hasFunc(eATmegaPinFunc::USART1_RXD)) { + state._USART1_RXEN = USART1._UCSRnB._RXEN; + USART1._UCSRnB._RXEN = false; + } + if (funcSet.hasFunc(eATmegaPinFunc::USART0_TXD)) { + state._USART0_TXEN = USART0._UCSRnB._TXEN; + USART0._UCSRnB._TXEN = false; + } + // There is a bug in the ATmega164A technical reference manual where + // it says that pin 0 is mapped to USART1 RXD in the signal mapping table + // but the associated list says USART0 RXD. + if (funcSet.hasFunc(eATmegaPinFunc::USART0_RXD)) { + state._USART0_RXEN = USART0._UCSRnB._RXEN; + USART0._UCSRnB._RXEN = false; + } + #elif defined(__AVR_TRM03__) + if (funcSet.hasAnyFunc(eATmegaPinFunc::PCI7, eATmegaPinFunc::PCI6, eATmegaPinFunc::PCI5, eATmegaPinFunc::PCI4, eATmegaPinFunc::PCI3, eATmegaPinFunc::PCI2, eATmegaPinFunc::PCI1, eATmegaPinFunc::PCI0)) { + state._PCIE0 = _PCICR._PCIE0; + _PCICR._PCIE0 = false; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::TOSC1, eATmegaPinFunc::TOSC2)) { + state._AS2 = _ASSR._AS2; + _ASSR._AS2 = false; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::SPI_SCK, eATmegaPinFunc::SPI_MISO, eATmegaPinFunc::SPI_MOSI, eATmegaPinFunc::SPI_CS)) { + state._SPE = _SPCR._SPE; + _SPCR._SPE = false; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC2A)) { + state._COM2A = _TIMER2._TCCRnA._COMnA; + _TIMER2._TCCRnA._COMnA = 0; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC1B)) { + state._COM1B = TIMER1._TCCRnA._COMnB; + TIMER1._TCCRnA._COMnB = 0; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC1A)) { + state._COM1A = TIMER1._TCCRnA._COMnA; + TIMER1._TCCRnA._COMnA = 0; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::PCI14, eATmegaPinFunc::PCI13, eATmegaPinFunc::PCI12, eATmegaPinFunc::PCI11, eATmegaPinFunc::PCI10, eATmegaPinFunc::PCI9, eATmegaPinFunc::PCI8)) { + state._PCIE1 = _PCICR._PCIE1; + _PCICR._PCIE1 = false; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::TWI_CLK, eATmegaPinFunc::TWI_SDA)) { + state._TWEN = _TWCR._TWEN; + _TWCR._TWEN = false; + } + if (funcSet.hasFunc(eATmegaPinFunc::ADC5)) { + state._ADC5D = _DIDR0._ADC5D; + _DIDR0._ADC5D = false; + } + if (funcSet.hasFunc(eATmegaPinFunc::ADC4)) { + state._ADC4D = _DIDR0._ADC4D; + _DIDR0._ADC4D = false; + } + if (funcSet.hasFunc(eATmegaPinFunc::ADC3)) { + state._ADC3D = _DIDR0._ADC3D; + _DIDR0._ADC3D = false; + } + if (funcSet.hasFunc(eATmegaPinFunc::ADC2)) { + state._ADC2D = _DIDR0._ADC2D; + _DIDR0._ADC2D = false; + } + if (funcSet.hasFunc(eATmegaPinFunc::ADC1)) { + state._ADC1D = _DIDR0._ADC1D; + _DIDR0._ADC1D = false; + } + if (funcSet.hasFunc(eATmegaPinFunc::ADC0)) { + state._ADC0D = _DIDR0._ADC0D; + _DIDR0._ADC0D = false; + } + // There is a bug in the ATmega48A technical reference manual where pin 2 + // is said to be mapped to PCIE1 but logically it should be PCIE2 instead. + // The real mapping can be read in the documentation of the PCICR register. + if (funcSet.hasAnyFunc(eATmegaPinFunc::PCI23, eATmegaPinFunc::PCI22, eATmegaPinFunc::PCI21, eATmegaPinFunc::PCI20, eATmegaPinFunc::PCI19, eATmegaPinFunc::PCI18, eATmegaPinFunc::PCI17, eATmegaPinFunc::PCI16)) { + state._PCIE2 = _PCICR._PCIE2; + _PCICR._PCIE2 = false; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC0A)) { + state._COM0A = TIMER0._TCCRnA._COMnA; + TIMER0._TCCRnA._COMnA = 0; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC0B)) { + state._COM0B = TIMER0._TCCRnA._COMnB; + TIMER0._TCCRnA._COMnB = 0; + } + if (funcSet.hasFunc(eATmegaPinFunc::USART_CLK)) { + state._UMSEL = USART0._UCSRnC._UMSEL; + USART0._UCSRnC._UMSEL = 0; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC2B)) { + state._COM2B = _TIMER2._TCCRnA._COMnB; + _TIMER2._TCCRnA._COMnB = 0; + } + if (funcSet.hasFunc(eATmegaPinFunc::USART_TXD)) { + state._USART0_TXEN = USART0._UCSRnB._TXEN; + USART0._UCSRnB._TXEN = false; + } + if (funcSet.hasFunc(eATmegaPinFunc::USART_RXD)) { + state._USART0_RXEN = USART0._UCSRnB._RXEN; + USART0._UCSRnB._RXEN = false; + } + #elif defined(__AVR_TRM04__) + if (funcSet.hasAnyFunc( + eATmegaPinFunc::EXTMEM_AD15, eATmegaPinFunc::EXTMEM_AD14, eATmegaPinFunc::EXTMEM_AD13, eATmegaPinFunc::EXTMEM_AD12, eATmegaPinFunc::EXTMEM_AD11, eATmegaPinFunc::EXTMEM_AD10, eATmegaPinFunc::EXTMEM_AD9, eATmegaPinFunc::EXTMEM_AD8, + eATmegaPinFunc::EXTMEM_AD7, eATmegaPinFunc::EXTMEM_AD6, eATmegaPinFunc::EXTMEM_AD5, eATmegaPinFunc::EXTMEM_AD4, eATmegaPinFunc::EXTMEM_AD3, eATmegaPinFunc::EXTMEM_AD2, eATmegaPinFunc::EXTMEM_AD1, eATmegaPinFunc::EXTMEM_AD0, + eATmegaPinFunc::EXTMEM_ALE, eATmegaPinFunc::EXTMEM_RD, eATmegaPinFunc::EXTMEM_WR + )) { + state._SRE = _XMCRA._SRE; + _XMCRA._SRE = false; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC1C)) { + state._COM1C = TIMER1._TCCRnA._COMnC; + TIMER1._TCCRnA._COMnC = 0; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC1B)) { + state._COM1B = TIMER1._TCCRnA._COMnB; + TIMER1._TCCRnA._COMnB = 0; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC1A)) { + state._COM1A = TIMER1._TCCRnA._COMnA; + TIMER1._TCCRnA._COMnA = 0; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC2A)) { + state._COM2A = _TIMER2._TCCRnA._COMnA; + _TIMER2._TCCRnA._COMnA = 0; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::SPI_MISO, eATmegaPinFunc::SPI_MOSI, eATmegaPinFunc::SPI_SCK, eATmegaPinFunc::SPI_CS)) { + state._SPE = _SPCR._SPE; + _SPCR._SPE = false; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::PCI7, eATmegaPinFunc::PCI6, eATmegaPinFunc::PCI5, eATmegaPinFunc::PCI4, eATmegaPinFunc::PCI3, eATmegaPinFunc::PCI2, eATmegaPinFunc::PCI1, eATmegaPinFunc::PCI0)) { + state._PCIE0 = _PCICR._PCIE0; + _PCICR._PCIE0 = false; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::USART1_CLK, eATmegaPinFunc::USART1_TXD)) { + state._USART1_TXEN = USART1._UCSRnB._TXEN; + USART1._UCSRnB._TXEN = false; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::USART1_CLK, eATmegaPinFunc::USART1_RXD)) { + state._USART1_RXEN = USART1._UCSRnB._RXEN; + USART1._UCSRnB._RXEN = false; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::TWI_SDA, eATmegaPinFunc::TWI_CLK)) { + state._TWEN = _TWCR._TWEN; + _TWCR._TWEN = false; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC2B)) { + state._COM2B = _TIMER2._TCCRnA._COMnB; + _TIMER2._TCCRnA._COMnB = 0; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC0B)) { + state._COM0B = TIMER0._TCCRnA._COMnB; + TIMER0._TCCRnA._COMnB = 0; + } + if (funcSet.hasFunc(eATmegaPinFunc::EINT3)) { + state._INT3 = _EIMSK._INT3; + _EIMSK._INT3 = false; + } + if (funcSet.hasFunc(eATmegaPinFunc::EINT2)) { + state._INT2 = _EIMSK._INT2; + _EIMSK._INT2 = false; + } + if (funcSet.hasFunc(eATmegaPinFunc::EINT1)) { + state._INT1 = _EIMSK._INT1; + _EIMSK._INT1 = false; + } + if (funcSet.hasFunc(eATmegaPinFunc::EINT0)) { + state._INT0 = _EIMSK._INT0; + _EIMSK._INT0 = false; + } + if (funcSet.hasFunc(eATmegaPinFunc::UVCON)) { + state._UVCONE = _UHWCON._UVCONE; + _UHWCON._UVCONE = false; + } + if (funcSet.hasFunc(eATmegaPinFunc::UID)) { + state._UIDE = _UHWCON._UIDE; + _UHWCON._UIDE = false; + } + if (funcSet.hasFunc(eATmegaPinFunc::EINT7)) { + state._INT7 = _EIMSK._INT7; + _EIMSK._INT7 = false; + } + if (funcSet.hasFunc(eATmegaPinFunc::EINT6)) { + state._INT6 = _EIMSK._INT6; + _EIMSK._INT6 = false; + } + if (funcSet.hasFunc(eATmegaPinFunc::EINT5)) { + state._INT5 = _EIMSK._INT5; + _EIMSK._INT5 = false; + } + if (funcSet.hasFunc(eATmegaPinFunc::EINT4)) { + state._INT4 = _EIMSK._INT4; + _EIMSK._INT4 = false; + } + #elif defined(__AVR_TRM05__) + if (funcSet.hasAnyFunc(eATmegaPinFunc::PCI7, eATmegaPinFunc::PCI6, eATmegaPinFunc::PCI5, eATmegaPinFunc::PCI4, eATmegaPinFunc::PCI3, eATmegaPinFunc::PCI2, eATmegaPinFunc::PCI1, eATmegaPinFunc::PCI0)) { + state._PCIE0 = _PCICR._PCIE0; + _PCICR._PCIE0 = false; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::PCI15, eATmegaPinFunc::PCI14, eATmegaPinFunc::PCI13, eATmegaPinFunc::PCI12, eATmegaPinFunc::PCI11, eATmegaPinFunc::PCI10, eATmegaPinFunc::PCI9, eATmegaPinFunc::PCI8)) { + state._PCIE1 = _PCICR._PCIE1; + _PCICR._PCIE1 = false; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::PCI23, eATmegaPinFunc::PCI22, eATmegaPinFunc::PCI21, eATmegaPinFunc::PCI20, eATmegaPinFunc::PCI19, eATmegaPinFunc::PCI18, eATmegaPinFunc::PCI17, eATmegaPinFunc::PCI16)) { + state._PCIE2 = _PCICR._PCIE2; + _PCICR._PCIE2 = false; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::PCI31, eATmegaPinFunc::PCI30, eATmegaPinFunc::PCI29, eATmegaPinFunc::PCI28, eATmegaPinFunc::PCI27, eATmegaPinFunc::PCI26, eATmegaPinFunc::PCI25, eATmegaPinFunc::PCI24)) { + state._PCIE3 = _PCICR._PCIE3; + _PCICR._PCIE3 = false; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::SPI_SCK, eATmegaPinFunc::SPI_MISO, eATmegaPinFunc::SPI_MOSI, eATmegaPinFunc::SPI_CS)) { + state._SPE = _SPCR._SPE; + _SPCR._SPE = false; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC0B)) { + state._COM0B = TIMER0._TCCRnA._COMnB; + TIMER0._TCCRnA._COMnB = 0; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC0A)) { + state._COM0A = TIMER0._TCCRnA._COMnA; + TIMER0._TCCRnA._COMnA = 0; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC2A)) { + state._COM2A = _TIMER2._TCCRnA._COMnA; + _TIMER2._TCCRnA._COMnA = 0; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC2B)) { + state._COM2B = _TIMER2._TCCRnA._COMnB; + _TIMER2._TCCRnA._COMnB = 0; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC1A)) { + state._COM1A = TIMER1._TCCRnA._COMnA; + TIMER1._TCCRnA._COMnA = 0; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC1B)) { + state._COM1B = TIMER1._TCCRnA._COMnB; + TIMER1._TCCRnA._COMnB = 0; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::TWI_CLK, eATmegaPinFunc::TWI_SDA)) { + state._TWEN = _TWCR._TWEN; + _TWCR._TWEN = false; + } + if (funcSet.hasFunc(eATmegaPinFunc::EINT2)) { + state._INT2 = _EIMSK._INT2; + _EIMSK._INT2 = false; + } + if (funcSet.hasFunc(eATmegaPinFunc::EINT1)) { + state._INT1 = _EIMSK._INT1; + _EIMSK._INT1 = false; + } + if (funcSet.hasFunc(eATmegaPinFunc::EINT0)) { + state._INT0 = _EIMSK._INT0; + _EIMSK._INT0 = false; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::USART0_CLK, eATmegaPinFunc::USART0_TXD)) { + state._TXEN0 = USART0._UCSRnB._TXEN; + USART0._UCSRnB._TXEN = false; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::USART0_CLK, eATmegaPinFunc::USART0_RXD)) { + state._RXEN0 = USART0._UCSRnB._RXEN; + USART0._UCSRnB._RXEN = false; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::USART1_CLK, eATmegaPinFunc::USART1_TXD)) { + state._TXEN1 = USART1._UCSRnB._TXEN; + USART1._UCSRnB._TXEN = false; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::USART1_CLK, eATmegaPinFunc::USART1_RXD)) { + state._RXEN1 = USART1._UCSRnB._RXEN; + USART1._UCSRnB._RXEN = false; + } + #endif + + return state; +} + +inline void _ATmega_restorePinAlternates(const ATmegaPinFuncSet& funcSet, const pin_dev_state_t& state) { + ATmegaPeripheralSet periSet; + periSet.fromPinFuncs(funcSet); + + ATmegaPeripheralPowerGate pgate(periSet); + + #ifdef __AVR_TRM01__ + // See page 75ff of ATmega2560 technical reference manual. + if (funcSet.hasAnyFunc( + eATmegaPinFunc::EXTMEM_AD15, eATmegaPinFunc::EXTMEM_AD14, eATmegaPinFunc::EXTMEM_AD13, eATmegaPinFunc::EXTMEM_AD12, eATmegaPinFunc::EXTMEM_AD11, eATmegaPinFunc::EXTMEM_AD10, eATmegaPinFunc::EXTMEM_AD9, eATmegaPinFunc::EXTMEM_AD8, + eATmegaPinFunc::EXTMEM_AD7, eATmegaPinFunc::EXTMEM_AD6, eATmegaPinFunc::EXTMEM_AD5, eATmegaPinFunc::EXTMEM_AD4, eATmegaPinFunc::EXTMEM_AD3, eATmegaPinFunc::EXTMEM_AD2, eATmegaPinFunc::EXTMEM_AD1, eATmegaPinFunc::EXTMEM_AD0, + eATmegaPinFunc::EXTMEM_ALE, eATmegaPinFunc::EXTMEM_RD, eATmegaPinFunc::EXTMEM_WR + )) { + _XMCRA._SRE = state._SRE; + } + if (funcSet.hasAnyFunc( + eATmegaPinFunc::PCI0, eATmegaPinFunc::PCI1, eATmegaPinFunc::PCI2, eATmegaPinFunc::PCI3, eATmegaPinFunc::PCI4, eATmegaPinFunc::PCI5, eATmegaPinFunc::PCI6, eATmegaPinFunc::PCI7 + )) { + _PCICR._PCIE0 = state._PCIE0; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC1C)) { + TIMER1._TCCRnA._COMnC = state._COM1C; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC1B)) { + TIMER1._TCCRnA._COMnB = state._COM1B; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC1A)) { + TIMER1._TCCRnA._COMnA = state._COM1A; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC2A)) { + _TIMER2._TCCRnA._COMnA = state._COM2A; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::USART1_TXD, eATmegaPinFunc::USART1_CLK)) { + USART1._UCSRnB._TXEN = state._USART1_TXEN; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::USART1_RXD, eATmegaPinFunc::USART1_CLK)) { + USART1._UCSRnB._RXEN = state._USART1_RXEN; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC3C)) { + TIMER3._TCCRnA._COMnC = state._COM3C; + } + // There is an error in the technical reference manual signal mapping table + // of ATmega2560 where is says that pin 3 is mapped to OC3B, but the list + // says OC3A. + if (funcSet.hasFunc(eATmegaPinFunc::TOC3B)) { + TIMER3._TCCRnA._COMnB = state._COM3B; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC3A)) { + TIMER3._TCCRnA._COMnA = state._COM3A; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::USART0_RXD, eATmegaPinFunc::USART0_CLK)) { + USART0._UCSRnB._RXEN = state._USART0_RXEN; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::USART0_TXD, eATmegaPinFunc::USART0_CLK)) { + USART0._UCSRnB._TXEN = state._USART0_TXEN; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::PCI15, eATmegaPinFunc::PCI14, eATmegaPinFunc::PCI13, eATmegaPinFunc::PCI12, eATmegaPinFunc::PCI11, eATmegaPinFunc::PCI10, eATmegaPinFunc::PCI9, eATmegaPinFunc::PCI8)) { + _PCICR._PCIE1 = state._PCIE1; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::TOC0B)) { + TIMER0._TCCRnA._COMnB = state._COM0B; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::TOSC1, eATmegaPinFunc::TOSC2)) { + _ASSR._AS2 = state._AS2; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC2B)) { + _TIMER2._TCCRnA._COMnB = state._COM2B; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC4C)) { + TIMER4._TCCRnA._COMnC = state._COM4C; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC4B)) { + TIMER4._TCCRnA._COMnB = state._COM4B; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC4A)) { + TIMER4._TCCRnA._COMnA = state._COM4A; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::USART2_RXD, eATmegaPinFunc::USART2_CLK)) { + USART2._UCSRnB._RXEN = state._USART2_RXEN; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::USART2_TXD, eATmegaPinFunc::USART2_CLK)) { + USART2._UCSRnB._TXEN = state._USART2_TXEN; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::USART3_TXD, eATmegaPinFunc::USART3_CLK)) { + USART3._UCSRnB._RXEN = state._USART3_RXEN; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::USART3_TXD, eATmegaPinFunc::USART3_CLK)) { + USART3._UCSRnB._TXEN = state._USART3_TXEN; + } + if (funcSet.hasAnyFunc( + eATmegaPinFunc::PCI23, eATmegaPinFunc::PCI22, eATmegaPinFunc::PCI21, eATmegaPinFunc::PCI20, eATmegaPinFunc::PCI19, eATmegaPinFunc::PCI18, eATmegaPinFunc::PCI17, eATmegaPinFunc::PCI16 + )) { + _PCICR._PCIE2 = state._PCIE2; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC5C)) { + TIMER5._TCCRnA._COMnC = state._COM5C; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC5B)) { + TIMER5._TCCRnA._COMnB = state._COM5B; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC5A)) { + TIMER5._TCCRnA._COMnA = state._COM5A; + } + #elif defined(__AVR_TRM02__) + if (funcSet.hasAnyFunc(eATmegaPinFunc::PCI7, eATmegaPinFunc::PCI6, eATmegaPinFunc::PCI5, eATmegaPinFunc::PCI4, eATmegaPinFunc::PCI3, eATmegaPinFunc::PCI2, eATmegaPinFunc::PCI1, eATmegaPinFunc::PCI0)) { + _PCICR._PCIE0 = state._PCIE0; + } + if (funcSet.hasFunc(eATmegaPinFunc::ADC7)) { + _DIDR0._ADC7D = state._ADC7D; + } + if (funcSet.hasFunc(eATmegaPinFunc::ADC6)) { + _DIDR0._ADC6D = state._ADC6D; + } + if (funcSet.hasFunc(eATmegaPinFunc::ADC5)) { + _DIDR0._ADC5D = state._ADC5D; + } + if (funcSet.hasFunc(eATmegaPinFunc::ADC4)) { + _DIDR0._ADC4D = state._ADC4D; + } + if (funcSet.hasFunc(eATmegaPinFunc::ADC3)) { + _DIDR0._ADC3D = state._ADC3D; + } + if (funcSet.hasFunc(eATmegaPinFunc::ADC2)) { + _DIDR0._ADC2D = state._ADC2D; + } + if (funcSet.hasFunc(eATmegaPinFunc::ADC1)) { + _DIDR0._ADC1D = state._ADC1D; + } + if (funcSet.hasFunc(eATmegaPinFunc::ADC0)) { + _DIDR0._ADC0D = state._ADC0D; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::PCI15, eATmegaPinFunc::PCI14, eATmegaPinFunc::PCI13, eATmegaPinFunc::PCI12, eATmegaPinFunc::PCI11, eATmegaPinFunc::PCI10, eATmegaPinFunc::PCI9, eATmegaPinFunc::PCI8)) { + _PCICR._PCIE1 = state._PCIE1; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::SPI_SCK, eATmegaPinFunc::SPI_MISO, eATmegaPinFunc::SPI_MOSI, eATmegaPinFunc::SPI_CS)) { + _SPCR._SPE = state._SPE; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC0A)) { + TIMER0._TCCRnA._COMnA = state._COM0A; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC0B)) { + TIMER0._TCCRnA._COMnB = state._COM0B; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::TOSC1, eATmegaPinFunc::TOSC2)) { + _ASSR._AS2 = state._AS2; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::PCI23, eATmegaPinFunc::PCI22, eATmegaPinFunc::PCI21, eATmegaPinFunc::PCI20, eATmegaPinFunc::PCI19, eATmegaPinFunc::PCI18, eATmegaPinFunc::PCI17, eATmegaPinFunc::PCI16)) { + _PCICR._PCIE2 = state._PCIE2; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::PCI31, eATmegaPinFunc::PCI30, eATmegaPinFunc::PCI29, eATmegaPinFunc::PCI28, eATmegaPinFunc::PCI27, eATmegaPinFunc::PCI26, eATmegaPinFunc::PCI25, eATmegaPinFunc::PCI24)) { + _PCICR._PCIE3 = state._PCIE3; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC2A)) { + _TIMER2._TCCRnA._COMnA = state._COM2A; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC2B)) { + _TIMER2._TCCRnA._COMnB = state._COM2B; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC1A)) { + TIMER1._TCCRnA._COMnA = state._COM1A; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC1B)) { + TIMER1._TCCRnA._COMnB = state._COM1B; + } + if (funcSet.hasFunc(eATmegaPinFunc::USART1_TXD)) { + USART1._UCSRnB._TXEN = state._USART1_TXEN; + } + if (funcSet.hasFunc(eATmegaPinFunc::USART1_RXD)) { + USART1._UCSRnB._RXEN = state._USART1_RXEN; + } + if (funcSet.hasFunc(eATmegaPinFunc::USART0_TXD)) { + USART0._UCSRnB._TXEN = state._USART0_TXEN; + } + // There is a bug in the ATmega164A technical reference manual where + // it says that pin 0 is mapped to USART1 RXD in the signal mapping table + // but the associated list says USART0 RXD. + if (funcSet.hasFunc(eATmegaPinFunc::USART0_RXD)) { + USART0._UCSRnB._RXEN = state._USART0_RXEN; + } + #elif defined(__AVR_TRM03__) + if (funcSet.hasAnyFunc(eATmegaPinFunc::PCI7, eATmegaPinFunc::PCI6, eATmegaPinFunc::PCI5, eATmegaPinFunc::PCI4, eATmegaPinFunc::PCI3, eATmegaPinFunc::PCI2, eATmegaPinFunc::PCI1, eATmegaPinFunc::PCI0)) { + _PCICR._PCIE0 = state._PCIE0; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::TOSC1, eATmegaPinFunc::TOSC2)) { + _ASSR._AS2 = state._AS2; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::SPI_SCK, eATmegaPinFunc::SPI_MISO, eATmegaPinFunc::SPI_MOSI, eATmegaPinFunc::SPI_CS)) { + _SPCR._SPE = state._SPE; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC2A)) { + _TIMER2._TCCRnA._COMnA = state._COM2A; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC1B)) { + TIMER1._TCCRnA._COMnB = state._COM1B; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC1A)) { + TIMER1._TCCRnA._COMnA = state._COM1A; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::PCI14, eATmegaPinFunc::PCI13, eATmegaPinFunc::PCI12, eATmegaPinFunc::PCI11, eATmegaPinFunc::PCI10, eATmegaPinFunc::PCI9, eATmegaPinFunc::PCI8)) { + _PCICR._PCIE1 = state._PCIE1; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::TWI_CLK, eATmegaPinFunc::TWI_SDA)) { + _TWCR._TWEN = state._TWEN; + } + if (funcSet.hasFunc(eATmegaPinFunc::ADC5)) { + _DIDR0._ADC5D = state._ADC5D; + } + if (funcSet.hasFunc(eATmegaPinFunc::ADC4)) { + _DIDR0._ADC4D = state._ADC4D; + } + if (funcSet.hasFunc(eATmegaPinFunc::ADC3)) { + _DIDR0._ADC3D = state._ADC3D; + } + if (funcSet.hasFunc(eATmegaPinFunc::ADC2)) { + _DIDR0._ADC2D = state._ADC2D; + } + if (funcSet.hasFunc(eATmegaPinFunc::ADC1)) { + _DIDR0._ADC1D = state._ADC1D; + } + if (funcSet.hasFunc(eATmegaPinFunc::ADC0)) { + _DIDR0._ADC0D = state._ADC0D; + } + // There is a bug in the ATmega48A technical reference manual where pin 2 + // is said to be mapped to PCIE1 but logically it should be PCIE2 instead. + // The real mapping can be read in the documentation of the PCICR register. + if (funcSet.hasAnyFunc(eATmegaPinFunc::PCI23, eATmegaPinFunc::PCI22, eATmegaPinFunc::PCI21, eATmegaPinFunc::PCI20, eATmegaPinFunc::PCI19, eATmegaPinFunc::PCI18, eATmegaPinFunc::PCI17, eATmegaPinFunc::PCI16)) { + _PCICR._PCIE2 = state._PCIE2; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC0A)) { + TIMER0._TCCRnA._COMnA = state._COM0A; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC0B)) { + TIMER0._TCCRnA._COMnB = state._COM0B; + } + if (funcSet.hasFunc(eATmegaPinFunc::USART_CLK)) { + USART0._UCSRnC._UMSEL = state._UMSEL; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC2B)) { + _TIMER2._TCCRnA._COMnB = state._COM2B; + } + if (funcSet.hasFunc(eATmegaPinFunc::USART_TXD)) { + USART0._UCSRnB._TXEN = state._USART0_TXEN; + } + if (funcSet.hasFunc(eATmegaPinFunc::USART_RXD)) { + USART0._UCSRnB._RXEN = state._USART0_RXEN; + } + #elif defined(__AVR_TRM04__) + if (funcSet.hasAnyFunc( + eATmegaPinFunc::EXTMEM_AD15, eATmegaPinFunc::EXTMEM_AD14, eATmegaPinFunc::EXTMEM_AD13, eATmegaPinFunc::EXTMEM_AD12, eATmegaPinFunc::EXTMEM_AD11, eATmegaPinFunc::EXTMEM_AD10, eATmegaPinFunc::EXTMEM_AD9, eATmegaPinFunc::EXTMEM_AD8, + eATmegaPinFunc::EXTMEM_AD7, eATmegaPinFunc::EXTMEM_AD6, eATmegaPinFunc::EXTMEM_AD5, eATmegaPinFunc::EXTMEM_AD4, eATmegaPinFunc::EXTMEM_AD3, eATmegaPinFunc::EXTMEM_AD2, eATmegaPinFunc::EXTMEM_AD1, eATmegaPinFunc::EXTMEM_AD0, + eATmegaPinFunc::EXTMEM_ALE, eATmegaPinFunc::EXTMEM_RD, eATmegaPinFunc::EXTMEM_WR + )) { + _XMCRA._SRE = state._SRE; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC1C)) { + TIMER1._TCCRnA._COMnC = state._COM1C; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC1B)) { + TIMER1._TCCRnA._COMnB = state._COM1B; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC1A)) { + TIMER1._TCCRnA._COMnA = state._COM1A; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC2A)) { + _TIMER2._TCCRnA._COMnA = state._COM2A; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::SPI_MISO, eATmegaPinFunc::SPI_MOSI, eATmegaPinFunc::SPI_SCK, eATmegaPinFunc::SPI_CS)) { + _SPCR._SPE = state._SPE; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::PCI7, eATmegaPinFunc::PCI6, eATmegaPinFunc::PCI5, eATmegaPinFunc::PCI4, eATmegaPinFunc::PCI3, eATmegaPinFunc::PCI2, eATmegaPinFunc::PCI1, eATmegaPinFunc::PCI0)) { + _PCICR._PCIE0 = state._PCIE0; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::USART1_CLK, eATmegaPinFunc::USART1_TXD)) { + USART1._UCSRnB._TXEN = state._USART1_TXEN; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::USART1_CLK, eATmegaPinFunc::USART1_RXD)) { + USART1._UCSRnB._RXEN = state._USART1_RXEN; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::TWI_SDA, eATmegaPinFunc::TWI_CLK)) { + _TWCR._TWEN = state._TWEN; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC2B)) { + _TIMER2._TCCRnA._COMnB = state._COM2B; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC0B)) { + TIMER0._TCCRnA._COMnB = state._COM0B; + } + if (funcSet.hasFunc(eATmegaPinFunc::EINT3)) { + _EIMSK._INT3 = state._INT3; + } + if (funcSet.hasFunc(eATmegaPinFunc::EINT2)) { + _EIMSK._INT2 = state._INT2; + } + if (funcSet.hasFunc(eATmegaPinFunc::EINT1)) { + _EIMSK._INT1 = state._INT1; + } + if (funcSet.hasFunc(eATmegaPinFunc::EINT0)) { + _EIMSK._INT0 = state._INT0; + } + if (funcSet.hasFunc(eATmegaPinFunc::UVCON)) { + _UHWCON._UVCONE = state._UVCONE; + } + if (funcSet.hasFunc(eATmegaPinFunc::UID)) { + _UHWCON._UIDE = state._UIDE; + } + if (funcSet.hasFunc(eATmegaPinFunc::EINT7)) { + _EIMSK._INT7 = state._INT7; + } + if (funcSet.hasFunc(eATmegaPinFunc::EINT6)) { + _EIMSK._INT6 = state._INT6; + } + if (funcSet.hasFunc(eATmegaPinFunc::EINT5)) { + _EIMSK._INT5 = state._INT5; + } + if (funcSet.hasFunc(eATmegaPinFunc::EINT4)) { + _EIMSK._INT4 = state._INT4; + } + #elif defined(__AVR_TRM05__) + if (funcSet.hasAnyFunc(eATmegaPinFunc::PCI7, eATmegaPinFunc::PCI6, eATmegaPinFunc::PCI5, eATmegaPinFunc::PCI4, eATmegaPinFunc::PCI3, eATmegaPinFunc::PCI2, eATmegaPinFunc::PCI1, eATmegaPinFunc::PCI0)) { + _PCICR._PCIE0 = state._PCIE0; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::PCI15, eATmegaPinFunc::PCI14, eATmegaPinFunc::PCI13, eATmegaPinFunc::PCI12, eATmegaPinFunc::PCI11, eATmegaPinFunc::PCI10, eATmegaPinFunc::PCI9, eATmegaPinFunc::PCI8)) { + _PCICR._PCIE1 = state._PCIE1; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::PCI23, eATmegaPinFunc::PCI22, eATmegaPinFunc::PCI21, eATmegaPinFunc::PCI20, eATmegaPinFunc::PCI19, eATmegaPinFunc::PCI18, eATmegaPinFunc::PCI17, eATmegaPinFunc::PCI16)) { + _PCICR._PCIE2 = state._PCIE2; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::PCI31, eATmegaPinFunc::PCI30, eATmegaPinFunc::PCI29, eATmegaPinFunc::PCI28, eATmegaPinFunc::PCI27, eATmegaPinFunc::PCI26, eATmegaPinFunc::PCI25, eATmegaPinFunc::PCI24)) { + _PCICR._PCIE3 = state._PCIE3; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::SPI_SCK, eATmegaPinFunc::SPI_MISO, eATmegaPinFunc::SPI_MOSI, eATmegaPinFunc::SPI_CS)) { + _SPCR._SPE = state._SPE; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC0B)) { + TIMER0._TCCRnA._COMnB = state._COM0B; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC0A)) { + TIMER0._TCCRnA._COMnA = state._COM0A; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC2A)) { + _TIMER2._TCCRnA._COMnA = state._COM2A; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC2B)) { + _TIMER2._TCCRnA._COMnB = state._COM2B; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC1A)) { + TIMER1._TCCRnA._COMnA = state._COM1A; + } + if (funcSet.hasFunc(eATmegaPinFunc::TOC1B)) { + TIMER1._TCCRnA._COMnB = state._COM1B; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::TWI_CLK, eATmegaPinFunc::TWI_SDA)) { + _TWCR._TWEN = state._TWEN; + } + if (funcSet.hasFunc(eATmegaPinFunc::EINT2)) { + _EIMSK._INT2 = state._INT2; + } + if (funcSet.hasFunc(eATmegaPinFunc::EINT1)) { + _EIMSK._INT1 = state._INT1; + } + if (funcSet.hasFunc(eATmegaPinFunc::EINT0)) { + _EIMSK._INT0 = state._INT0; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::USART0_CLK, eATmegaPinFunc::USART0_TXD)) { + USART0._UCSRnB._TXEN = state._TXEN0; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::USART0_CLK, eATmegaPinFunc::USART0_RXD)) { + USART0._UCSRnB._RXEN = state._RXEN0; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::USART1_CLK, eATmegaPinFunc::USART1_TXD)) { + USART1._UCSRnB._TXEN = state._TXEN1; + } + if (funcSet.hasAnyFunc(eATmegaPinFunc::USART1_CLK, eATmegaPinFunc::USART1_RXD)) { + USART1._UCSRnB._RXEN = state._RXEN1; + } + #endif +} + +inline pin_dev_state_t _ATmega_savePinAlternate(uint8_t pin) { + return _ATmega_savePinAlternates({pin}); +} + +inline void _ATmega_restorePinAlternate(uint8_t pin, const pin_dev_state_t& state) { + _ATmega_restorePinAlternate({pin}, state); +} + +#ifndef LOW + #define LOW 0 +#endif +#ifndef HIGH + #define HIGH 1 +#endif + +inline void _ATmega_digitalWrite(int pin, int state) { + if (pin < 0) return; + + ATmegaPinInfo info = _ATmega_getPinInfo((unsigned int)pin); + + #ifdef __AVR_TRM01__ + if (info.port == eATmegaPort::PORT_A) { + _PORTA._PORT.setValue(info.pinidx, state == HIGH); + } + else if (info.port == eATmegaPort::PORT_B) { + _PORTB._PORT.setValue(info.pinidx, state == HIGH); + } + else if (info.port == eATmegaPort::PORT_C) { + _PORTC._PORT.setValue(info.pinidx, state == HIGH); + } + else if (info.port == eATmegaPort::PORT_D) { + _PORTD._PORT.setValue(info.pinidx, state == HIGH); + } + else if (info.port == eATmegaPort::PORT_E) { + _PORTE._PORT.setValue(info.pinidx, state == HIGH); + } + else if (info.port == eATmegaPort::PORT_F) { + _PORTF._PORT.setValue(info.pinidx, state == HIGH); + } + else if (info.port == eATmegaPort::PORT_G) { + _PORTG._PORT.setValue(info.pinidx, state == HIGH); + } + else if (info.port == eATmegaPort::PORT_H) { + _PORTH._PORT.setValue(info.pinidx, state == HIGH); + } + else if (info.port == eATmegaPort::PORT_J) { + _PORTJ._PORT.setValue(info.pinidx, state == HIGH); + } + else if (info.port == eATmegaPort::PORT_K) { + _PORTK._PORT.setValue(info.pinidx, state == HIGH); + } + else if (info.port == eATmegaPort::PORT_L) { + _PORTL._PORT.setValue(info.pinidx, state == HIGH); + } + #elif defined(__AVR_TRM02__) || defined(__AVR_TRM05__) + if (info.port == eATmegaPort::PORT_A) { + _PORTA._PORT.setValue(info.pinidx, state == HIGH); + } + else if (info.port == eATmegaPort::PORT_B) { + _PORTB._PORT.setValue(info.pinidx, state == HIGH); + } + else if (info.port == eATmegaPort::PORT_C) { + _PORTC._PORT.setValue(info.pinidx, state == HIGH); + } + else if (info.port == eATmegaPort::PORT_D) { + _PORTD._PORT.setValue(info.pinidx, state == HIGH); + } + #elif defined(__AVR_TRM03__) + if (info.port == eATmegaPort::PORT_B) { + _PORTB._PORT.setValue(info.pinidx, state == HIGH); + } + else if (info.port == eATmegaPort::PORT_C) { + _PORTC._PORT.setValue(info.pinidx, state == HIGH); + } + else if (info.port == eATmegaPort::PORT_D) { + _PORTD._PORT.setValue(info.pinidx, state == HIGH); + } + #elif defined(__AVR_TRM04__) + if (info.port == eATmegaPort::PORT_A) { + _PORTA._PORT.setValue(info.pinidx, state == HIGH); + } + else if (info.port == eATmegaPort::PORT_B) { + _PORTB._PORT.setValue(info.pinidx, state == HIGH); + } + else if (info.port == eATmegaPort::PORT_C) { + _PORTC._PORT.setValue(info.pinidx, state == HIGH); + } + else if (info.port == eATmegaPort::PORT_D) { + _PORTD._PORT.setValue(info.pinidx, state == HIGH); + } + else if (info.port == eATmegaPort::PORT_E) { + _PORTE._PORT.setValue(info.pinidx, state == HIGH); + } + else if (info.port == eATmegaPort::PORT_F) { + _PORTF._PORT.setValue(info.pinidx, state == HIGH); + } + #endif +} + +inline int _ATmega_digitalRead(int pin) { + int value = LOW; + + if (pin < 0) return value; + + ATmegaPinInfo info = _ATmega_getPinInfo((unsigned int)pin); + + #ifdef __AVR_TRM01__ + if (info.port == eATmegaPort::PORT_A) { + value = _PORTA._PIN.getValue(info.pinidx); + } + else if (info.port == eATmegaPort::PORT_B) { + value = _PORTB._PIN.getValue(info.pinidx); + } + else if (info.port == eATmegaPort::PORT_C) { + value = _PORTC._PIN.getValue(info.pinidx); + } + else if (info.port == eATmegaPort::PORT_D) { + value = _PORTD._PIN.getValue(info.pinidx); + } + else if (info.port == eATmegaPort::PORT_E) { + value = _PORTE._PIN.getValue(info.pinidx); + } + else if (info.port == eATmegaPort::PORT_F) { + value = _PORTF._PIN.getValue(info.pinidx); + } + else if (info.port == eATmegaPort::PORT_G) { + value = _PORTG._PIN.getValue(info.pinidx); + } + else if (info.port == eATmegaPort::PORT_H) { + value = _PORTH._PIN.getValue(info.pinidx); + } + else if (info.port == eATmegaPort::PORT_J) { + value = _PORTJ._PIN.getValue(info.pinidx); + } + else if (info.port == eATmegaPort::PORT_K) { + value = _PORTK._PIN.getValue(info.pinidx); + } + else if (info.port == eATmegaPort::PORT_L) { + value = _PORTL._PIN.getValue(info.pinidx); + } + #elif defined(__AVR_TRM02__) || defined(__AVR_TRM05__) + if (info.port == eATmegaPort::PORT_A) { + value = _PORTA._PIN.getValue(info.pinidx); + } + else if (info.port == eATmegaPort::PORT_B) { + value = _PORTB._PIN.getValue(info.pinidx); + } + else if (info.port == eATmegaPort::PORT_C) { + value = _PORTC._PIN.getValue(info.pinidx); + } + else if (info.port == eATmegaPort::PORT_D) { + value = _PORTD._PIN.getValue(info.pinidx); + } + #elif defined(__AVR_TRM03__) + if (info.port == eATmegaPort::PORT_B) { + value = _PORTB._PIN.getValue(info.pinidx); + } + else if (info.port == eATmegaPort::PORT_C) { + value = _PORTC._PIN.getValue(info.pinidx); + } + else if (info.port == eATmegaPort::PORT_D) { + value = _PORTD._PIN.getValue(info.pinidx); + } + #elif defined(__AVR_TRM04__) + if (info.port == eATmegaPort::PORT_A) { + value = _PORTA._PIN.getValue(info.pinidx); + } + else if (info.port == eATmegaPort::PORT_B) { + value = _PORTB._PIN.getValue(info.pinidx); + } + else if (info.port == eATmegaPort::PORT_C) { + value = _PORTC._PIN.getValue(info.pinidx); + } + else if (info.port == eATmegaPort::PORT_D) { + value = _PORTD._PIN.getValue(info.pinidx); + } + else if (info.port == eATmegaPort::PORT_E) { + value = _PORTE._PIN.getValue(info.pinidx); + } + else if (info.port == eATmegaPort::PORT_F) { + value = _PORTF._PIN.getValue(info.pinidx); + } + #endif + + return value; +} + +#ifndef OUTPUT + #define OUTPUT 1 +#endif +#ifndef INPUT + #define INPUT 0 +#endif + +inline void _ATmega_pinMode(int pin, int mode) { + if (pin < 0) return; + + ATmegaPinInfo info = _ATmega_getPinInfo((unsigned int)pin); + + #ifdef __AVR_TRM01__ + if (info.port == eATmegaPort::PORT_A) { + _PORTA._DDR.setValue(info.pinidx, mode == OUTPUT); + } + else if (info.port == eATmegaPort::PORT_B) { + _PORTB._DDR.setValue(info.pinidx, mode == OUTPUT); + } + else if (info.port == eATmegaPort::PORT_C) { + _PORTC._DDR.setValue(info.pinidx, mode == OUTPUT); + } + else if (info.port == eATmegaPort::PORT_D) { + _PORTD._DDR.setValue(info.pinidx, mode == OUTPUT); + } + else if (info.port == eATmegaPort::PORT_E) { + _PORTE._DDR.setValue(info.pinidx, mode == OUTPUT); + } + else if (info.port == eATmegaPort::PORT_F) { + _PORTF._DDR.setValue(info.pinidx, mode == OUTPUT); + } + else if (info.port == eATmegaPort::PORT_G) { + _PORTG._DDR.setValue(info.pinidx, mode == OUTPUT); + } + else if (info.port == eATmegaPort::PORT_H) { + _PORTH._DDR.setValue(info.pinidx, mode == OUTPUT); + } + else if (info.port == eATmegaPort::PORT_J) { + _PORTJ._DDR.setValue(info.pinidx, mode == OUTPUT); + } + else if (info.port == eATmegaPort::PORT_K) { + _PORTK._DDR.setValue(info.pinidx, mode == OUTPUT); + } + else if (info.port == eATmegaPort::PORT_L) { + _PORTL._DDR.setValue(info.pinidx, mode == OUTPUT); + } + #elif defined(__AVR_TRM02__) || defined(__AVR_TRM05__) + if (info.port == eATmegaPort::PORT_A) { + _PORTA._DDR.setValue(info.pinidx, mode == OUTPUT); + } + else if (info.port == eATmegaPort::PORT_B) { + _PORTB._DDR.setValue(info.pinidx, mode == OUTPUT); + } + else if (info.port == eATmegaPort::PORT_C) { + _PORTC._DDR.setValue(info.pinidx, mode == OUTPUT); + } + else if (info.port == eATmegaPort::PORT_D) { + _PORTD._DDR.setValue(info.pinidx, mode == OUTPUT); + } + #elif defined(__AVR_TRM03__) + if (info.port == eATmegaPort::PORT_B) { + _PORTB._DDR.setValue(info.pinidx, mode == OUTPUT); + } + else if (info.port == eATmegaPort::PORT_C) { + _PORTC._DDR.setValue(info.pinidx, mode == OUTPUT); + } + else if (info.port == eATmegaPort::PORT_D) { + _PORTD._DDR.setValue(info.pinidx, mode == OUTPUT); + } + #elif defined(__AVR_TRM04__) + if (info.port == eATmegaPort::PORT_A) { + _PORTA._DDR.setValue(info.pinidx, mode == OUTPUT); + } + else if (info.port == eATmegaPort::PORT_B) { + _PORTB._DDR.setValue(info.pinidx, mode == OUTPUT); + } + else if (info.port == eATmegaPort::PORT_C) { + _PORTC._DDR.setValue(info.pinidx, mode == OUTPUT); + } + else if (info.port == eATmegaPort::PORT_D) { + _PORTD._DDR.setValue(info.pinidx, mode == OUTPUT); + } + else if (info.port == eATmegaPort::PORT_E) { + _PORTE._DDR.setValue(info.pinidx, mode == OUTPUT); + } + else if (info.port == eATmegaPort::PORT_F) { + _PORTF._DDR.setValue(info.pinidx, mode == OUTPUT); + } + #endif +} + +#if defined(__AVR_TRM01__) || defined(__AVR_TRM02__) + struct _ATmega_efuse { + uint8_t _BODLEVEL : 3; + uint8_t reserved1 : 5; + }; + + struct _ATmega_hfuse { + uint8_t _BOOTRST : 1; + uint8_t _BOOTSZ : 2; + uint8_t _EESAVE : 1; + uint8_t _WDTON : 1; + uint8_t _SPIEN : 1; + uint8_t _JTAGEN : 1; + uint8_t _OCDEN : 1; + }; + + struct _ATmega_lfuse { + uint8_t _CKSEL : 4; + uint8_t _SUT0 : 1; + uint8_t _SUT1 : 1; + uint8_t _CKOUT : 1; + uint8_t _CKDIV8 : 1; + }; + + #ifndef AVR_DEFAULT_LFUSE_VALUE + #define AVR_DEFAULT_LFUSE_VALUE 0xFF + #endif + #ifndef AVR_DEFAULT_HFUSE_VALUE + #define AVR_DEFAULT_HFUSE_VALUE 0x99 + #endif + #ifndef AVR_DEFAULT_LFUSE_VALUE + #define AVR_DEFAULT_LFUSE_VALUE 0x62 + #endif + +#elif defined(__AVR_TRM03__) + #if defined(__AVR_ATmega48A__) || defined(__AVR_ATmega48PA__) + struct _ATmega_efuse { + uint8_t _SELFPRGEN : 1; + uint8_t reserved1 : 7; + }; + + #ifndef AVR_DEFAULT_EFUSE_VALUE + #define AVR_DEFAULT_EFUSE_VALUE 0xFF + #endif + + #elif defined(__AVR_ATmega88A__) || defined(__AVR_ATmega88PA__) || defined(__AVR_ATmega168A__) || defined(__AVR_ATmega168PA__) + struct _ATmega_efuse { + uint8_t _BOOTRST : 1; + uint8_t _BOOTSZ : 2; + uint8_t reserved1 : 5; + }; + + #ifndef AVR_DEFAULT_EFUSE_VALUE + #define AVR_DEFAULT_EFUSE_VALUE 0xF9 + #endif + + #else // defined(__AVR_ATmega328__) || defined(__AVR_ATmega328P__) + struct _ATmega_efuse { + uint8_t _BODLEVEL : 3; + uint8_t reserved1 : 5; + }; + + #ifndef AVR_DEFAULT_EFUSE_VALUE + #define AVR_DEFAULT_EFUSE_VALUE 0xFF + #endif + + #endif + + #if defined(__AVR_ATmega48A__) || defined(__AVR_ATmega48PA__) || defined(__AVR_ATmega88A__) || defined(__AVR_ATmega88PA__) || defined(__AVR_ATmega168A__) || defined(__AVR_ATmega168PA__) + struct _ATmega_hfuse { + uint8_t _BODLEVEL : 3; + uint8_t _EESAVE : 1; + uint8_t _WDTON : 1; + uint8_t _SPIEN : 1; + uint8_t _DWEN : 1; + uint8_t _RSTDISBL : 1; + }; + + #ifndef AVR_DEFAULT_HFUSE_VALUE + #define AVR_DEFAULT_HFUSE_VALUE 0xCF + #endif + + #else // defined(__AVR_ATmega328__) || defined(__AVR_ATmega328P__) + struct _ATmega_hfuse { + uint8_t _BOOTRST : 1; + uint8_t _BOOTSZ : 2; + uint8_t _EESAVE : 1; + uint8_t _WDTON : 1; + uint8_t _SPIEN : 1; + uint8_t _DWEN : 1; + uint8_t _RSTDISBL : 1; + }; + + #ifndef AVR_DEFAULT_HFUSE_VALUE + #define AVR_DEFAULT_HFUSE_VALUE 0xC9 + #endif + + #endif + + struct _ATmega_lfuse { + uint8_t _CKSEL : 4; + uint8_t _SUT0 : 1; + uint8_t _SUT1 : 1; + uint8_t _CKOUT : 1; + uint8_t _CKDIV8 : 1; + }; + + #ifndef AVR_DEFAULT_LFUSE_VALUE + #define AVR_DEFAULT_LFUSE_VALUE 0xC9 + #endif + +#elif defined(__AVR_TRM04__) + struct _ATmega_efuse { + uint8_t _BODLEVEL : 3; + uint8_t _HWBE : 1; + uint8_t reserved1 : 4; + }; + + struct _ATmega_hfuse { + uint8_t _BOOTRST : 1; + uint8_t _BOOTSZ : 2; + uint8_t _EESAVE : 1; + uint8_t _WDTON : 1; + uint8_t _SPIEN : 1; + uint8_t _JTAGEN : 1; + uint8_t _OCDEN : 1; + }; + + struct _ATmega_lfuse { + uint8_t _CKSEL : 4; + uint8_t _SUT0 : 1; + uint8_t _SUT1 : 1; + uint8_t _CKOUT : 1; + uint8_t _CKDIV8 : 1; + }; + + // Default values if not already defined. + #ifndef AVR_DEFAULT_EFUSE_VALUE + #define AVR_DEFAULT_EFUSE_VALUE 0xF3 + #endif + #ifndef AVR_DEFAULT_HFUSE_VALUE + #define AVR_DEFAULT_HFUSE_VALUE 0x99 + #endif + #ifndef AVR_DEFAULT_LFUSE_VALUE + #define AVR_DEFAULT_LFUSE_VALUE 0x62 + #endif + +#elif defined(__AVR_TRM05__) + struct _ATmega_efuse { + uint8_t _BODLEVEL0 : 1; + uint8_t _BODLEVEL1 : 1; + uint8_t _BODLEVEL2 : 1; + uint8_t reserved1 : 5; + }; + + struct _ATmega_hfuse { + uint8_t _BOOTRST : 1; + uint8_t _BOOTSZ : 2; + uint8_t _EESAVE : 1; + uint8_t _WDTON : 1; + uint8_t _SPIEN : 1; + uint8_t _JTAGEN : 1; + uint8_t _OCDEN : 1; + }; + + struct _ATmega_lfuse { + uint8_t _CKSEL : 4; + uint8_t _SUT0 : 1; + uint8_t _SUT1 : 1; + uint8_t _CKOUT : 1; + uint8_t _CKDIV8 : 1; + }; + + #ifndef AVR_DEFAULT_EFUSE_VALUE + #define AVR_DEFAULT_EFUSE_VALUE 0xFF + #endif + #ifndef AVR_DEFAULT_HFUSE_VALUE + #define AVR_DEFAULT_HFUSE_VALUE 0x88 + #endif + #ifndef AVR_DEFAULT_LFUSE_VALUE + #define AVR_DEFAULT_LFUSE_VALUE 0x62 + #endif +#endif + +struct ATmega_efuse : public _ATmega_efuse { + inline ATmega_efuse(uint8_t val = 0) { *(uint8_t*)this = val; } + inline ATmega_efuse(const ATmega_efuse&) = default; +}; +struct ATmega_hfuse : public _ATmega_hfuse { + inline ATmega_hfuse(uint8_t val = 0) { *(uint8_t*)this = val; } + inline ATmega_hfuse(const ATmega_hfuse&) = default; +}; +struct ATmega_lfuse : public _ATmega_lfuse { + inline ATmega_lfuse(uint8_t val = 0) { *(uint8_t*)this = val; } + inline ATmega_lfuse(const ATmega_lfuse&) = default; +}; diff --git a/Marlin/src/HAL/AVR/spi_pins.h b/Marlin/src/HAL/AVR/spi_pins.h index f3fa78e2bf..934b6e3b14 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 SCK_PIN - #define SCK_PIN AVR_SCK_PIN +#ifndef SD_SCK_PIN + #define SD_SCK_PIN _PIN_SPI_SCK #endif -#ifndef MISO_PIN - #define MISO_PIN AVR_MISO_PIN +#ifndef SD_MISO_PIN + #define SD_MISO_PIN _PIN_SPI_MISO #endif -#ifndef MOSI_PIN - #define MOSI_PIN AVR_MOSI_PIN -#endif -#ifndef SS_PIN - #define SS_PIN AVR_SS_PIN +#ifndef SD_MOSI_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 82eb8b14b1..1800da0985 100644 --- a/Marlin/src/HAL/AVR/timers.h +++ b/Marlin/src/HAL/AVR/timers.h @@ -1,7 +1,9 @@ /** * Marlin 3D Printer Firmware * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com + * + * 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 @@ -26,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 @@ -34,37 +36,36 @@ typedef uint16_t hal_timer_t; #define HAL_TIMER_RATE ((F_CPU) / 8) // i.e., 2MHz or 2.5MHz -#ifndef STEP_TIMER_NUM - #define STEP_TIMER_NUM 1 +#ifndef MF_TIMER_STEP + #define MF_TIMER_STEP 1 #endif -#ifndef PULSE_TIMER_NUM - #define PULSE_TIMER_NUM STEP_TIMER_NUM +#ifndef MF_TIMER_PULSE + #define MF_TIMER_PULSE MF_TIMER_STEP #endif -#ifndef TEMP_TIMER_NUM - #define TEMP_TIMER_NUM 0 +#ifndef MF_TIMER_TEMP + #define MF_TIMER_TEMP 0 #endif -#define TEMP_TIMER_FREQUENCY ((F_CPU) / 64.0 / 256.0) +#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) // Cannot be of type double +#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 // 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() SBI(TIMSK1, OCIE1A) #define DISABLE_STEPPER_DRIVER_INTERRUPT() CBI(TIMSK1, OCIE1A) #define STEPPER_ISR_ENABLED() TEST(TIMSK1, OCIE1A) -#define ENABLE_TEMPERATURE_INTERRUPT() SBI(TIMSK0, OCIE0B) -#define DISABLE_TEMPERATURE_INTERRUPT() CBI(TIMSK0, OCIE0B) -#define TEMPERATURE_ISR_ENABLED() TEST(TIMSK0, OCIE0B) +#define ENABLE_TEMPERATURE_INTERRUPT() SBI(TIMSK0, OCIE0A) +#define DISABLE_TEMPERATURE_INTERRUPT() CBI(TIMSK0, OCIE0A) +#define TEMPERATURE_ISR_ENABLED() TEST(TIMSK0, OCIE0A) FORCE_INLINE void HAL_timer_start(const uint8_t timer_num, const uint32_t) { switch (timer_num) { - case STEP_TIMER_NUM: + case MF_TIMER_STEP: // waveform generation = 0100 = CTC SET_WGM(1, CTC_OCRnA); @@ -84,10 +85,10 @@ FORCE_INLINE void HAL_timer_start(const uint8_t timer_num, const uint32_t) { TCNT1 = 0; break; - case TEMP_TIMER_NUM: + case MF_TIMER_TEMP: // Use timer0 for temperature measurement // Interleave temperature interrupt with millies interrupt - OCR0B = 128; + OCR0A = 128; break; } } @@ -109,12 +110,12 @@ 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(TIMER_NUM) -#define HAL_timer_isr_epilogue(TIMER_NUM) +inline void HAL_timer_isr_prologue(const uint8_t) {} +inline void HAL_timer_isr_epilogue(const uint8_t) {} -/* 18 cycles maximum latency */ #ifndef HAL_STEP_TIMER_ISR +/* 18 cycles maximum latency */ #define HAL_STEP_TIMER_ISR() \ extern "C" void TIMER1_COMPA_vect() __attribute__ ((signal, naked, used, externally_visible)); \ extern "C" void TIMER1_COMPA_vect_bottom() asm ("TIMER1_COMPA_vect_bottom") __attribute__ ((used, externally_visible, noinline)); \ @@ -180,7 +181,7 @@ void TIMER1_COMPA_vect() { \ : \ : [timsk0] "i" ((uint16_t)&TIMSK0), \ [timsk1] "i" ((uint16_t)&TIMSK1), \ - [msk0] "M" ((uint8_t)(1<. + * + */ +#pragma once + +/** + * AVR LCD-specific defines + */ + +uint8_t u8g_com_HAL_AVR_sw_sp_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr); +#define U8G_COM_HAL_SW_SPI_FN u8g_com_HAL_AVR_sw_sp_fn 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 89% 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 cb95a48ccc..f4fa1eb428 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,17 +55,17 @@ #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 +#include -uint8_t u8g_bitData, u8g_bitNotData, u8g_bitClock, u8g_bitNotClock; -volatile uint8_t *u8g_outData, *u8g_outClock; +static uint8_t u8g_bitData, u8g_bitNotData, u8g_bitClock, u8g_bitNotClock; +static volatile uint8_t *u8g_outData, *u8g_outClock; static void u8g_com_arduino_init_shift_out(uint8_t dataPin, uint8_t clockPin) { u8g_outData = portOutputRegister(digitalPinToPort(dataPin)); @@ -88,7 +88,7 @@ void u8g_spiSend_sw_AVR_mode_0(uint8_t val) { volatile uint8_t *outData = u8g_outData, *outClock = u8g_outClock; U8G_ATOMIC_START(); - LOOP_L_N(i, 8) { + for (uint8_t i = 0; i < 8; ++i) { if (val & 0x80) *outData |= bitData; else @@ -108,7 +108,7 @@ void u8g_spiSend_sw_AVR_mode_3(uint8_t val) { volatile uint8_t *outData = u8g_outData, *outClock = u8g_outClock; U8G_ATOMIC_START(); - LOOP_L_N(i, 8) { + for (uint8_t i = 0; i < 8; ++i) { *outClock &= bitNotClock; if (val & 0x80) *outData |= bitData; @@ -120,8 +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 @@ -144,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/AVR/watchdog.cpp b/Marlin/src/HAL/AVR/watchdog.cpp deleted file mode 100644 index 3f10c4adff..0000000000 --- a/Marlin/src/HAL/AVR/watchdog.cpp +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Marlin 3D Printer Firmware - * Copyright (c) 2020 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 __AVR__ - -#include "../../inc/MarlinConfig.h" - -#if ENABLED(USE_WATCHDOG) - -#include "watchdog.h" - -#include "../../MarlinCore.h" - -// Initialize watchdog with 8s timeout, if possible. Otherwise, make it 4s. -void watchdog_init() { - #if ENABLED(WATCHDOG_DURATION_8S) && defined(WDTO_8S) - #define WDTO_NS WDTO_8S - #else - #define WDTO_NS WDTO_4S - #endif - #if ENABLED(WATCHDOG_RESET_MANUAL) - // Enable the watchdog timer, but only for the interrupt. - // Take care, as this requires the correct order of operation, with interrupts disabled. - // See the datasheet of any AVR chip for details. - wdt_reset(); - cli(); - _WD_CONTROL_REG = _BV(_WD_CHANGE_BIT) | _BV(WDE); - _WD_CONTROL_REG = _BV(WDIE) | (WDTO_NS & 0x07) | ((WDTO_NS & 0x08) << 2); // WDTO_NS directly does not work. bit 0-2 are consecutive in the register but the highest value bit is at bit 5 - // So worked for up to WDTO_2S - sei(); - wdt_reset(); - #else - wdt_enable(WDTO_NS); // The function handles the upper bit correct. - #endif - //delay(10000); // test it! -} - -//=========================================================================== -//=================================== ISR =================================== -//=========================================================================== - -// Watchdog timer interrupt, called if main program blocks >4sec and manual reset is enabled. -#if ENABLED(WATCHDOG_RESET_MANUAL) - 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 - } -#endif - -#endif // USE_WATCHDOG -#endif // __AVR__ diff --git a/Marlin/src/HAL/DUE/DebugMonitor.cpp b/Marlin/src/HAL/DUE/DebugMonitor.cpp deleted file mode 100644 index 79759151d8..0000000000 --- a/Marlin/src/HAL/DUE/DebugMonitor.cpp +++ /dev/null @@ -1,342 +0,0 @@ -/** - * Marlin 3D Printer Firmware - * Copyright (c) 2020 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 ARDUINO_ARCH_SAM - -#include "../../core/macros.h" -#include "../../core/serial.h" - -#include "../shared/backtrace/unwinder.h" -#include "../shared/backtrace/unwmemaccess.h" - -#include - -// Debug monitor that dumps to the Programming port all status when -// an exception or WDT timeout happens - And then resets the board - -// All the Monitor routines must run with interrupts disabled and -// under an ISR execution context. That is why we cannot reuse the -// Serial interrupt routines or any C runtime, as we don't know the -// state we are when running them - -// A SW memory barrier, to ensure GCC does not overoptimize loops -#define sw_barrier() __asm__ volatile("": : :"memory"); - -// (re)initialize UART0 as a monitor output to 250000,n,8,1 -static void TXBegin() { - - // Disable UART interrupt in NVIC - NVIC_DisableIRQ( UART_IRQn ); - - // We NEED memory barriers to ensure Interrupts are actually disabled! - // ( https://dzone.com/articles/nvic-disabling-interrupts-on-arm-cortex-m-and-the ) - __DSB(); - __ISB(); - - // Disable clock - pmc_disable_periph_clk( ID_UART ); - - // Configure PMC - pmc_enable_periph_clk( ID_UART ); - - // Disable PDC channel - UART->UART_PTCR = UART_PTCR_RXTDIS | UART_PTCR_TXTDIS; - - // Reset and disable receiver and transmitter - UART->UART_CR = UART_CR_RSTRX | UART_CR_RSTTX | UART_CR_RXDIS | UART_CR_TXDIS; - - // Configure mode: 8bit, No parity, 1 bit stop - UART->UART_MR = UART_MR_CHMODE_NORMAL | US_MR_CHRL_8_BIT | US_MR_NBSTOP_1_BIT | UART_MR_PAR_NO; - - // Configure baudrate (asynchronous, no oversampling) to BAUDRATE bauds - UART->UART_BRGR = (SystemCoreClock / (BAUDRATE << 4)); - - // Enable receiver and transmitter - UART->UART_CR = UART_CR_RXEN | UART_CR_TXEN; -} - -// Send character through UART with no interrupts -static void TX(char c) { - while (!(UART->UART_SR & UART_SR_TXRDY)) { WDT_Restart(WDT); sw_barrier(); }; - UART->UART_THR = c; -} - -// Send String through UART -static void TX(const char* s) { - while (*s) TX(*s++); -} - -static void TXDigit(uint32_t d) { - if (d < 10) TX((char)(d+'0')); - else if (d < 16) TX((char)(d+'A'-10)); - else TX('?'); -} - -// Send Hex number thru UART -static void TXHex(uint32_t v) { - TX("0x"); - for (uint8_t i = 0; i < 8; i++, v <<= 4) - TXDigit((v >> 28) & 0xF); -} - -// Send Decimal number thru UART -static void TXDec(uint32_t v) { - if (!v) { - TX('0'); - return; - } - - char nbrs[14]; - char *p = &nbrs[0]; - while (v != 0) { - *p++ = '0' + (v % 10); - v /= 10; - } - do { - p--; - TX(*p); - } while (p != &nbrs[0]); -} - -// Dump a backtrace entry -static bool UnwReportOut(void* ctx, const UnwReport* bte) { - int* p = (int*)ctx; - - (*p)++; - TX('#'); TXDec(*p); TX(" : "); - TX(bte->name?bte->name:"unknown"); TX('@'); TXHex(bte->function); - TX('+'); TXDec(bte->address - bte->function); - TX(" PC:");TXHex(bte->address); TX('\n'); - return true; -} - -#ifdef UNW_DEBUG - void UnwPrintf(const char* format, ...) { - char dest[256]; - va_list argptr; - va_start(argptr, format); - vsprintf(dest, format, argptr); - va_end(argptr); - TX(&dest[0]); - } -#endif - -/* Table of function pointers for passing to the unwinder */ -static const UnwindCallbacks UnwCallbacks = { - UnwReportOut, - UnwReadW, - UnwReadH, - UnwReadB - #ifdef UNW_DEBUG - , UnwPrintf - #endif -}; - -/** - * HardFaultHandler_C: - * This is called from the HardFault_HandlerAsm with a pointer the Fault stack - * as the parameter. We can then read the values from the stack and place them - * into local variables for ease of reading. - * We then read the various Fault Status and Address Registers to help decode - * cause of the fault. - * The function ends with a BKPT instruction to force control back into the debugger - */ -extern "C" -void HardFault_HandlerC(unsigned long *sp, unsigned long lr, unsigned long cause) { - - static const char* causestr[] = { - "NMI","Hard","Mem","Bus","Usage","Debug","WDT","RSTC" - }; - - UnwindFrame btf; - - // Dump report to the Programming port (interrupts are DISABLED) - TXBegin(); - TX("\n\n## Software Fault detected ##\n"); - TX("Cause: "); TX(causestr[cause]); TX('\n'); - - TX("R0 : "); TXHex(((unsigned long)sp[0])); TX('\n'); - TX("R1 : "); TXHex(((unsigned long)sp[1])); TX('\n'); - TX("R2 : "); TXHex(((unsigned long)sp[2])); TX('\n'); - TX("R3 : "); TXHex(((unsigned long)sp[3])); TX('\n'); - TX("R12 : "); TXHex(((unsigned long)sp[4])); TX('\n'); - TX("LR : "); TXHex(((unsigned long)sp[5])); TX('\n'); - TX("PC : "); TXHex(((unsigned long)sp[6])); TX('\n'); - TX("PSR : "); TXHex(((unsigned long)sp[7])); TX('\n'); - - // Configurable Fault Status Register - // Consists of MMSR, BFSR and UFSR - TX("CFSR : "); TXHex((*((volatile unsigned long *)(0xE000ED28)))); TX('\n'); - - // Hard Fault Status Register - TX("HFSR : "); TXHex((*((volatile unsigned long *)(0xE000ED2C)))); TX('\n'); - - // Debug Fault Status Register - TX("DFSR : "); TXHex((*((volatile unsigned long *)(0xE000ED30)))); TX('\n'); - - // Auxiliary Fault Status Register - TX("AFSR : "); TXHex((*((volatile unsigned long *)(0xE000ED3C)))); TX('\n'); - - // Read the Fault Address Registers. These may not contain valid values. - // Check BFARVALID/MMARVALID to see if they are valid values - // MemManage Fault Address Register - TX("MMAR : "); TXHex((*((volatile unsigned long *)(0xE000ED34)))); TX('\n'); - - // Bus Fault Address Register - TX("BFAR : "); TXHex((*((volatile unsigned long *)(0xE000ED38)))); TX('\n'); - - TX("ExcLR: "); TXHex(lr); TX('\n'); - TX("ExcSP: "); TXHex((unsigned long)sp); TX('\n'); - - btf.sp = ((unsigned long)sp) + 8*4; // The original stack pointer - btf.fp = btf.sp; - btf.lr = ((unsigned long)sp[5]); - btf.pc = ((unsigned long)sp[6]) | 1; // Force Thumb, as CORTEX only support it - - // Perform a backtrace - TX("\nBacktrace:\n\n"); - int ctr = 0; - UnwindStart(&btf, &UnwCallbacks, &ctr); - - // Disable all NVIC interrupts - NVIC->ICER[0] = 0xFFFFFFFF; - NVIC->ICER[1] = 0xFFFFFFFF; - - // Relocate VTOR table to default position - SCB->VTOR = 0; - - // Disable USB - otg_disable(); - - // Restart watchdog - WDT_Restart(WDT); - - // Reset controller - NVIC_SystemReset(); - for (;;) WDT_Restart(WDT); -} - -__attribute__((naked)) void NMI_Handler() { - __asm__ __volatile__ ( - ".syntax unified" "\n\t" - A("tst lr, #4") - A("ite eq") - A("mrseq r0, msp") - A("mrsne r0, psp") - A("mov r1,lr") - A("mov r2,#0") - A("b HardFault_HandlerC") - ); -} - -__attribute__((naked)) void HardFault_Handler() { - __asm__ __volatile__ ( - ".syntax unified" "\n\t" - A("tst lr, #4") - A("ite eq") - A("mrseq r0, msp") - A("mrsne r0, psp") - A("mov r1,lr") - A("mov r2,#1") - A("b HardFault_HandlerC") - ); -} - -__attribute__((naked)) void MemManage_Handler() { - __asm__ __volatile__ ( - ".syntax unified" "\n\t" - A("tst lr, #4") - A("ite eq") - A("mrseq r0, msp") - A("mrsne r0, psp") - A("mov r1,lr") - A("mov r2,#2") - A("b HardFault_HandlerC") - ); -} - -__attribute__((naked)) void BusFault_Handler() { - __asm__ __volatile__ ( - ".syntax unified" "\n\t" - A("tst lr, #4") - A("ite eq") - A("mrseq r0, msp") - A("mrsne r0, psp") - A("mov r1,lr") - A("mov r2,#3") - A("b HardFault_HandlerC") - ); -} - -__attribute__((naked)) void UsageFault_Handler() { - __asm__ __volatile__ ( - ".syntax unified" "\n\t" - A("tst lr, #4") - A("ite eq") - A("mrseq r0, msp") - A("mrsne r0, psp") - A("mov r1,lr") - A("mov r2,#4") - A("b HardFault_HandlerC") - ); -} - -__attribute__((naked)) void DebugMon_Handler() { - __asm__ __volatile__ ( - ".syntax unified" "\n\t" - A("tst lr, #4") - A("ite eq") - A("mrseq r0, msp") - A("mrsne r0, psp") - A("mov r1,lr") - A("mov r2,#5") - A("b HardFault_HandlerC") - ); -} - -/* This is NOT an exception, it is an interrupt handler - Nevertheless, the framing is the same */ -__attribute__((naked)) void WDT_Handler() { - __asm__ __volatile__ ( - ".syntax unified" "\n\t" - A("tst lr, #4") - A("ite eq") - A("mrseq r0, msp") - A("mrsne r0, psp") - A("mov r1,lr") - A("mov r2,#6") - A("b HardFault_HandlerC") - ); -} - -__attribute__((naked)) void RSTC_Handler() { - __asm__ __volatile__ ( - ".syntax unified" "\n\t" - A("tst lr, #4") - A("ite eq") - A("mrseq r0, msp") - A("mrsne r0, psp") - A("mov r1,lr") - A("mov r2,#7") - A("b HardFault_HandlerC") - ); -} - -#endif // ARDUINO_ARCH_SAM diff --git a/Marlin/src/HAL/DUE/HAL.cpp b/Marlin/src/HAL/DUE/HAL.cpp index 6ce85a4643..19a174259a 100644 --- a/Marlin/src/HAL/DUE/HAL.cpp +++ b/Marlin/src/HAL/DUE/HAL.cpp @@ -1,7 +1,9 @@ /** * Marlin 3D Printer Firmware * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com + * + * 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 @@ -25,7 +27,6 @@ #ifdef ARDUINO_ARCH_SAM #include "../../inc/MarlinConfig.h" -#include "HAL.h" #include #include "usb/usb_task.h" @@ -34,36 +35,33 @@ // Public Variables // ------------------------ -uint16_t HAL_adc_result; +uint16_t MarlinHAL::adc_result; // ------------------------ // Public functions // ------------------------ -// HAL initialization task -void HAL_init() { - // Initialize the USB stack - #if ENABLED(SDSUPPORT) - OUT_WRITE(SDSS, HIGH); // Try to set SDSS inactive before any other SPI users start up +#if ENABLED(POSTMORTEM_DEBUGGING) + extern void install_min_serial(); +#endif + +void MarlinHAL::init() { + #if HAS_MEDIA + OUT_WRITE(SD_SS_PIN, HIGH); // Try to set SDSS inactive before any other SPI users start up #endif - usb_task_init(); + usb_task_init(); // Initialize the USB stack + TERN_(POSTMORTEM_DEBUGGING, install_min_serial()); // Install the min serial handler } -// HAL idle task -void HAL_idletask() { - // Perform USB stack housekeeping - usb_task_idle(); +void MarlinHAL::init_board() { + #ifdef BOARD_INIT + BOARD_INIT(); + #endif } -// Disable interrupts -void cli() { noInterrupts(); } +void MarlinHAL::idletask() { usb_task_idle(); } // Perform USB stack housekeeping -// Enable interrupts -void sei() { interrupts(); } - -void HAL_clear_reset_source() { } - -uint8_t HAL_get_reset_source() { +uint8_t MarlinHAL::get_reset_source() { switch ((RSTC->RSTC_SR >> 8) & 0x07) { case 0: return RST_POWER_ON; case 1: return RST_BACKUP; @@ -74,11 +72,110 @@ uint8_t HAL_get_reset_source() { } } -void _delay_ms(const int delay_ms) { - // Todo: port for Due? - delay(delay_ms); +void MarlinHAL::reboot() { rstc_start_software_reset(RSTC); } + +// ------------------------ +// Watchdog Timer +// ------------------------ + +#if ENABLED(USE_WATCHDOG) + + // Initialize watchdog - On SAM3X, Watchdog was already configured + // and enabled or disabled at startup, so no need to reconfigure it + // here. + void MarlinHAL::watchdog_init() { WDT_Restart(WDT); } // Reset watchdog to start clean + + // Reset watchdog. MUST be called at least every 4 seconds after the + // first watchdog_init or AVR will go into emergency procedures. + void MarlinHAL::watchdog_refresh() { watchdogReset(); } + +#endif + +// Override Arduino runtime to either config or disable the watchdog +// +// We need to configure the watchdog as soon as possible in the boot +// process, because watchdog initialization at hardware reset on SAM3X8E +// is unreliable, and there is risk of unintended resets if we delay +// that initialization to a later time. +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); + + // Calculate timeout value in WDT counter ticks: This assumes + // the slow clock is running at 32.768 kHz watchdog + // frequency is therefore 32768 / 128 = 256 Hz + timeout = (timeout << 8) / 1000; + if (timeout == 0) + timeout = 1; + else if (timeout > 0xFFF) + timeout = 0xFFF; + + // We want to enable the watchdog with the specified timeout + 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. + + // Configure WDT to only trigger an interrupt + value |= WDT_MR_WDFIEN; // Enable WDT fault interrupt. + + // Disable WDT interrupt (just in case, to avoid triggering it!) + NVIC_DisableIRQ(WDT_IRQn); + + // We NEED memory barriers to ensure Interrupts are actually disabled! + // ( https://dzone.com/articles/nvic-disabling-interrupts-on-arm-cortex-m-and-the ) + __DSB(); + __ISB(); + + // Initialize WDT with the given parameters + WDT_Enable(WDT, value); + + // Configure and enable WDT interrupt. + NVIC_ClearPendingIRQ(WDT_IRQn); + NVIC_SetPriority(WDT_IRQn, 0); // Use highest priority, so we detect all kinds of lockups + NVIC_EnableIRQ(WDT_IRQn); + + #else + + // a WDT fault triggers a reset + value |= WDT_MR_WDRSTEN; + + // Initialize WDT with the given parameters + WDT_Enable(WDT, value); + + #endif + + // Reset the watchdog + WDT_Restart(WDT); + + #else + + // Make sure to completely disable the Watchdog + WDT_Disable(WDT); + + #endif } +// ------------------------ +// Free Memory Accessor +// ------------------------ + extern "C" { extern unsigned int _ebss; // end of bss section } @@ -90,16 +187,21 @@ int freeMemory() { } // ------------------------ -// ADC +// Serial Ports // ------------------------ -void HAL_adc_start_conversion(const uint8_t ch) { - HAL_adc_result = analogRead(ch); -} - -uint16_t HAL_adc_get_result() { - // nop - return HAL_adc_result; -} +// Forward the default serial ports +#if USING_HW_SERIAL0 + DefaultSerial1 MSerial0(false, Serial); +#endif +#if USING_HW_SERIAL1 + DefaultSerial2 MSerial1(false, Serial1); +#endif +#if USING_HW_SERIAL2 + DefaultSerial3 MSerial2(false, Serial2); +#endif +#if USING_HW_SERIAL3 + DefaultSerial4 MSerial3(false, Serial3); +#endif #endif // ARDUINO_ARCH_SAM diff --git a/Marlin/src/HAL/DUE/HAL.h b/Marlin/src/HAL/DUE/HAL.h index 88ace59575..f83668ca9d 100644 --- a/Marlin/src/HAL/DUE/HAL.h +++ b/Marlin/src/HAL/DUE/HAL.h @@ -1,9 +1,9 @@ /** * Marlin 3D Printer Firmware - * * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com - * Copyright (c) 2015-2016 Nico Tonnhofer wurstnase.reprap@gmail.com + * + * 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 @@ -32,105 +32,48 @@ #include "../shared/math_32bit.h" #include "../shared/HAL_SPI.h" #include "fastio.h" -#include "watchdog.h" #include -#define _MSERIAL(X) Serial##X -#define MSERIAL(X) _MSERIAL(X) -#define Serial0 Serial - -// Define MYSERIAL0/1 before MarlinSerial includes! -#if SERIAL_PORT == -1 || ENABLED(EMERGENCY_PARSER) - #define MYSERIAL0 customizedSerial1 -#elif WITHIN(SERIAL_PORT, 0, 3) - #define MYSERIAL0 MSERIAL(SERIAL_PORT) -#else - #error "The required SERIAL_PORT must be from -1 to 3. Please update your configuration." -#endif - -#ifdef SERIAL_PORT_2 - #if SERIAL_PORT_2 == -1 || ENABLED(EMERGENCY_PARSER) - #define MYSERIAL1 customizedSerial2 - #elif WITHIN(SERIAL_PORT_2, 0, 3) - #define MYSERIAL1 MSERIAL(SERIAL_PORT_2) - #else - #error "SERIAL_PORT_2 must be from -1 to 3. Please update your configuration." - #endif -#endif - -#ifdef LCD_SERIAL_PORT - #if LCD_SERIAL_PORT == -1 - #define LCD_SERIAL lcdSerial - #elif WITHIN(LCD_SERIAL_PORT, 0, 3) - #define LCD_SERIAL MSERIAL(LCD_SERIAL_PORT) - #else - #error "LCD_SERIAL_PORT must be from -1 to 3. Please update your configuration." - #endif -#endif +// +// Serial Ports +// #include "MarlinSerial.h" #include "MarlinSerialUSB.h" -// On AVR this is in math.h? -#define square(x) ((x)*(x)) - -#ifndef strncpy_P - #define strncpy_P(dest, src, num) strncpy((dest), (src), (num)) -#endif - -// Fix bug in pgm_read_ptr -#undef pgm_read_ptr -#define pgm_read_ptr(addr) (*((void**)(addr))) -#undef pgm_read_word -#define pgm_read_word(addr) (*((uint16_t*)(addr))) +// ------------------------ +// Types +// ------------------------ typedef int8_t pin_t; -#define SHARED_SERVOS HAS_SERVOS -#define HAL_SERVO_LIB Servo +#define SHARED_SERVOS HAS_SERVOS // Use shared/servos.cpp + +class Servo; +typedef Servo hal_servo_t; // // Interrupts // -#define CRITICAL_SECTION_START() uint32_t primask = __get_PRIMASK(); __disable_irq() -#define CRITICAL_SECTION_END() if (!primask) __enable_irq() -#define ISRS_ENABLED() (!__get_PRIMASK()) -#define ENABLE_ISRS() __enable_irq() -#define DISABLE_ISRS() __disable_irq() +#define sei() interrupts() +#define cli() noInterrupts() -void cli(); // Disable interrupts -void sei(); // Enable interrupts - -void HAL_clear_reset_source(); // clear reset reason -uint8_t HAL_get_reset_source(); // get reset reason - -inline void HAL_reboot() {} // reboot the board or restart the bootloader +#define CRITICAL_SECTION_START() const bool _irqon = hal.isr_state(); hal.isr_off() +#define CRITICAL_SECTION_END() if (_irqon) hal.isr_on() // // ADC // -extern uint16_t HAL_adc_result; // result of last ADC conversion +#define HAL_ADC_VREF_MV 3300 +#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 -#define HAL_ANALOG_SELECT(ch) - -inline void HAL_adc_init() {}//todo - -#define HAL_ADC_VREF 3.3 -#define HAL_ADC_RESOLUTION 10 -#define HAL_START_ADC(ch) HAL_adc_start_conversion(ch) -#define HAL_READ_ADC() HAL_adc_result -#define HAL_ADC_READY() true - -void HAL_adc_start_conversion(const uint8_t ch); -uint16_t HAL_adc_get_result(); - // -// Pin Map +// Pin Mapping for M42, M43, M226 // #define GET_PIN_MAP_PIN(index) index #define GET_PIN_MAP_INDEX(pin) pin @@ -139,23 +82,18 @@ uint16_t HAL_adc_get_result(); // // Tone // -void toneInit(); void tone(const pin_t _pin, const unsigned int frequency, const unsigned long duration=0); void noTone(const pin_t _pin); -// Enable hooks into idle and setup for HAL -#define HAL_IDLETASK 1 -void HAL_idletask(); -void HAL_init(); - -// -// Utility functions -// -void _delay_ms(const int delay); +// ------------------------ +// Class Utilities +// ------------------------ #pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-function" -int freeMemory(); +#if GCC_VERSION <= 50000 + #pragma GCC diagnostic ignored "-Wunused-function" +#endif + #pragma GCC diagnostic pop #ifdef __cplusplus @@ -165,3 +103,73 @@ char *dtostrf(double __val, signed char __width, unsigned char __prec, char *__s #ifdef __cplusplus } #endif + +// Return free RAM between end of heap (or end bss) and whatever is current +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() 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 + + // 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(); + + // Reset + static uint8_t get_reset_source(); + static void clear_reset_source() {} + + // Free SRAM + static int freeMemory() { return ::freeMemory(); } + + // + // ADC Methods + // + + static uint16_t adc_result; + + // 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 uint8_t /*ch*/) {} + + // Begin ADC sampling on the given channel. Called from Temperature::isr! + static void adc_start(const uint8_t ch) { adc_result = analogRead(ch); } + + // Is the ADC ready for reading? + static bool adc_ready() { return true; } + + // The 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. + * No inverting the duty cycle in this HAL. + * No changing the maximum size of the provided value to enable finer PWM duty control in this HAL. + */ + static void set_pwm_duty(const pin_t pin, const uint16_t v, const uint16_t=255, const bool=false) { + analogWrite(pin, v); + } + +}; diff --git a/Marlin/src/HAL/DUE/HAL_SPI.cpp b/Marlin/src/HAL/DUE/HAL_SPI.cpp index 0451d8bcc4..e44549357e 100644 --- a/Marlin/src/HAL/DUE/HAL_SPI.cpp +++ b/Marlin/src/HAL/DUE/HAL_SPI.cpp @@ -31,8 +31,6 @@ /** * HAL for Arduino Due and compatible (SAM3X8E) - * - * For ARDUINO_ARCH_SAM */ #ifdef ARDUINO_ARCH_SAM @@ -44,7 +42,7 @@ // Public functions // ------------------------ -#if EITHER(DUE_SOFTWARE_SPI, FORCE_SOFT_SPI) +#if ANY(SOFTWARE_SPI, FORCE_SOFT_SPI) // ------------------------ // Software SPI @@ -56,8 +54,8 @@ #pragma GCC optimize (3) typedef uint8_t (*pfnSpiTransfer)(uint8_t b); - typedef void (*pfnSpiRxBlock)(uint8_t* buf, uint32_t nbyte); - typedef void (*pfnSpiTxBlock)(const uint8_t* buf, uint32_t nbyte); + typedef void (*pfnSpiRxBlock)(uint8_t *buf, uint32_t nbyte); + typedef void (*pfnSpiTxBlock)(const uint8_t *buf, uint32_t nbyte); /* ---------------- Macros to be able to access definitions from asm */ #define _PORT(IO) DIO ## IO ## _WPORT @@ -69,10 +67,10 @@ // run at ~8 .. ~10Mhz - Tx version (Rx data discarded) static uint8_t spiTransferTx0(uint8_t bout) { // using Mode 0 - uint32_t MOSI_PORT_PLUS30 = ((uint32_t) PORT(MOSI_PIN)) + 0x30; /* SODR of port */ - uint32_t MOSI_MASK = PIN_MASK(MOSI_PIN); - uint32_t SCK_PORT_PLUS30 = ((uint32_t) PORT(SCK_PIN)) + 0x30; /* SODR of port */ - uint32_t SCK_MASK = PIN_MASK(SCK_PIN); + uint32_t MOSI_PORT_PLUS30 = ((uint32_t) PORT(SD_MOSI_PIN)) + 0x30; /* SODR of port */ + uint32_t MOSI_MASK = PIN_MASK(SD_MOSI_PIN); + uint32_t SCK_PORT_PLUS30 = ((uint32_t) PORT(SD_SCK_PIN)) + 0x30; /* SODR of port */ + uint32_t SCK_MASK = PIN_MASK(SD_SCK_PIN); uint32_t idx = 0; /* Negate bout, as the assembler requires a negated value */ @@ -154,9 +152,9 @@ static uint8_t spiTransferRx0(uint8_t) { // using Mode 0 uint32_t bin = 0; uint32_t work = 0; - uint32_t BITBAND_MISO_PORT = BITBAND_ADDRESS( ((uint32_t)PORT(MISO_PIN))+0x3C, PIN_SHIFT(MISO_PIN)); /* PDSR of port in bitband area */ - uint32_t SCK_PORT_PLUS30 = ((uint32_t) PORT(SCK_PIN)) + 0x30; /* SODR of port */ - uint32_t SCK_MASK = PIN_MASK(SCK_PIN); + 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); /* The software SPI routine */ __asm__ __volatile__( @@ -210,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 ) @@ -225,36 +223,36 @@ static uint8_t spiTransfer1(uint8_t b) { // using Mode 0 int bits = 8; do { - WRITE(MOSI_PIN, b & 0x80); + WRITE(SD_MOSI_PIN, b & 0x80); b <<= 1; // little setup time - WRITE(SCK_PIN, HIGH); + WRITE(SD_SCK_PIN, HIGH); DELAY_NS(125); // 10 cycles @ 84mhz - b |= (READ(MISO_PIN) != 0); + b |= (READ(SD_MISO_PIN) != 0); - WRITE(SCK_PIN, LOW); + WRITE(SD_SCK_PIN, LOW); DELAY_NS(125); // 10 cycles @ 84mhz } while (--bits); return b; } // all the others - static uint32_t spiDelayCyclesX4 = (F_CPU) / 1000000; // 4Β΅s => 125khz + static uint16_t spiDelayNS = 4000; // 4000ns => 125khz static uint8_t spiTransferX(uint8_t b) { // using Mode 0 int bits = 8; do { - WRITE(MOSI_PIN, b & 0x80); + WRITE(SD_MOSI_PIN, b & 0x80); b <<= 1; // little setup time - WRITE(SCK_PIN, HIGH); - __delay_4cycles(spiDelayCyclesX4); + WRITE(SD_SCK_PIN, HIGH); + DELAY_NS_VAR(spiDelayNS); - b |= (READ(MISO_PIN) != 0); + b |= (READ(SD_MISO_PIN) != 0); - WRITE(SCK_PIN, LOW); - __delay_4cycles(spiDelayCyclesX4); + WRITE(SD_SCK_PIN, LOW); + DELAY_NS_VAR(spiDelayNS); } while (--bits); return b; } @@ -270,11 +268,11 @@ static pfnSpiTransfer spiTransferTx = (pfnSpiTransfer)spiTransferX; // Block transfers run at ~8 .. ~10Mhz - Tx version (Rx data discarded) - static void spiTxBlock0(const uint8_t* ptr, uint32_t todo) { - uint32_t MOSI_PORT_PLUS30 = ((uint32_t) PORT(MOSI_PIN)) + 0x30; /* SODR of port */ - uint32_t MOSI_MASK = PIN_MASK(MOSI_PIN); - uint32_t SCK_PORT_PLUS30 = ((uint32_t) PORT(SCK_PIN)) + 0x30; /* SODR of port */ - uint32_t SCK_MASK = PIN_MASK(SCK_PIN); + static void spiTxBlock0(const uint8_t *ptr, uint32_t todo) { + uint32_t MOSI_PORT_PLUS30 = ((uint32_t) PORT(SD_MOSI_PIN)) + 0x30; /* SODR of port */ + uint32_t MOSI_MASK = PIN_MASK(SD_MOSI_PIN); + uint32_t SCK_PORT_PLUS30 = ((uint32_t) PORT(SD_SCK_PIN)) + 0x30; /* SODR of port */ + uint32_t SCK_MASK = PIN_MASK(SD_SCK_PIN); uint32_t work = 0; uint32_t txval = 0; @@ -349,12 +347,12 @@ ); } - static void spiRxBlock0(uint8_t* ptr, uint32_t todo) { + 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(MISO_PIN))+0x3C, PIN_SHIFT(MISO_PIN)); /* PDSR of port in bitband area */ - uint32_t SCK_PORT_PLUS30 = ((uint32_t) PORT(SCK_PIN)) + 0x30; /* SODR of port */ - uint32_t SCK_MASK = PIN_MASK(SCK_PIN); + 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); /* The software SPI routine */ __asm__ __volatile__( @@ -414,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 ) @@ -425,48 +423,48 @@ ); } - static void spiTxBlockX(const uint8_t* buf, uint32_t todo) { + static void spiTxBlockX(const uint8_t *buf, uint32_t todo) { do { (void)spiTransferTx(*buf++); } while (--todo); } - static void spiRxBlockX(uint8_t* buf, uint32_t todo) { + static void spiRxBlockX(uint8_t *buf, uint32_t todo) { do { *buf++ = spiTransferRx(0xFF); } while (--todo); } - // Pointers to generic functions for block tranfers + // Pointers to generic functions for block transfers static pfnSpiTxBlock spiTxBlock = (pfnSpiTxBlock)spiTxBlockX; static pfnSpiRxBlock spiRxBlock = (pfnSpiRxBlock)spiRxBlockX; #if MB(ALLIGATOR) - #define _SS_WRITE(S) WRITE(SS_PIN, S) + #define _SS_WRITE(S) WRITE(SD_SS_PIN, S) #else #define _SS_WRITE(S) NOOP #endif void spiBegin() { - SET_OUTPUT(SS_PIN); + SET_OUTPUT(SD_SS_PIN); _SS_WRITE(HIGH); - SET_OUTPUT(SCK_PIN); - SET_INPUT(MISO_PIN); - SET_OUTPUT(MOSI_PIN); + SET_OUTPUT(SD_SCK_PIN); + SET_INPUT(SD_MISO_PIN); + SET_OUTPUT(SD_MOSI_PIN); } uint8_t spiRec() { _SS_WRITE(LOW); - WRITE(MOSI_PIN, HIGH); // Output 1s 1 + WRITE(SD_MOSI_PIN, HIGH); // Output 1s 1 uint8_t b = spiTransferRx(0xFF); _SS_WRITE(HIGH); return b; } - void spiRead(uint8_t* buf, uint16_t nbyte) { + void spiRead(uint8_t *buf, uint16_t nbyte) { if (nbyte) { _SS_WRITE(LOW); - WRITE(MOSI_PIN, HIGH); // Output 1s 1 + WRITE(SD_MOSI_PIN, HIGH); // Output 1s 1 spiRxBlock(buf, nbyte); _SS_WRITE(HIGH); } @@ -478,7 +476,7 @@ _SS_WRITE(HIGH); } - void spiSendBlock(uint8_t token, const uint8_t* buf) { + void spiSendBlock(uint8_t token, const uint8_t *buf) { _SS_WRITE(LOW); (void)spiTransferTx(token); spiTxBlock(buf, 512); @@ -510,7 +508,7 @@ spiRxBlock = (pfnSpiRxBlock)spiRxBlockX; break; default: - spiDelayCyclesX4 = ((F_CPU) / 1000000) >> (6 - spiRate); + spiDelayNS = 4000 >> (6 - spiRate); // spiRate of 2 gives the maximum error with current CPU spiTransferTx = (pfnSpiTransfer)spiTransferX; spiTransferRx = (pfnSpiTransfer)spiTransferX; spiTxBlock = (pfnSpiTxBlock)spiTxBlockX; @@ -519,8 +517,8 @@ } _SS_WRITE(HIGH); - WRITE(MOSI_PIN, HIGH); - WRITE(SCK_PIN, LOW); + WRITE(SD_MOSI_PIN, HIGH); + WRITE(SD_SCK_PIN, LOW); } /** Begin SPI transaction, set clock, bit order, data mode */ @@ -575,40 +573,35 @@ // Configure SPI pins PIO_Configure( - g_APinDescription[SCK_PIN].pPort, - g_APinDescription[SCK_PIN].ulPinType, - g_APinDescription[SCK_PIN].ulPin, - g_APinDescription[SCK_PIN].ulPinConfiguration); + g_APinDescription[SD_SCK_PIN].pPort, + g_APinDescription[SD_SCK_PIN].ulPinType, + g_APinDescription[SD_SCK_PIN].ulPin, + g_APinDescription[SD_SCK_PIN].ulPinConfiguration); PIO_Configure( - g_APinDescription[MOSI_PIN].pPort, - g_APinDescription[MOSI_PIN].ulPinType, - g_APinDescription[MOSI_PIN].ulPin, - g_APinDescription[MOSI_PIN].ulPinConfiguration); + g_APinDescription[SD_MOSI_PIN].pPort, + g_APinDescription[SD_MOSI_PIN].ulPinType, + g_APinDescription[SD_MOSI_PIN].ulPin, + g_APinDescription[SD_MOSI_PIN].ulPinConfiguration); PIO_Configure( - g_APinDescription[MISO_PIN].pPort, - g_APinDescription[MISO_PIN].ulPinType, - g_APinDescription[MISO_PIN].ulPin, - g_APinDescription[MISO_PIN].ulPinConfiguration); + g_APinDescription[SD_MISO_PIN].pPort, + g_APinDescription[SD_MISO_PIN].ulPinType, + g_APinDescription[SD_MISO_PIN].ulPin, + g_APinDescription[SD_MISO_PIN].ulPinConfiguration); // set master mode, peripheral select, fault detection SPI_Configure(SPI0, ID_SPI0, SPI_MR_MSTR | SPI_MR_MODFDIS | SPI_MR_PS); SPI_Enable(SPI0); - SET_OUTPUT(DAC0_SYNC); + SET_OUTPUT(DAC0_SYNC_PIN); #if HAS_MULTI_EXTRUDER - SET_OUTPUT(DAC1_SYNC); - WRITE(DAC1_SYNC, HIGH); + OUT_WRITE(DAC1_SYNC_PIN, HIGH); #endif - SET_OUTPUT(SPI_EEPROM1_CS); - SET_OUTPUT(SPI_EEPROM2_CS); - SET_OUTPUT(SPI_FLASH_CS); - WRITE(DAC0_SYNC, HIGH); - WRITE(SPI_EEPROM1_CS, HIGH); - WRITE(SPI_EEPROM2_CS, HIGH); - WRITE(SPI_FLASH_CS, HIGH); - WRITE(SS_PIN, HIGH); - - OUT_WRITE(SDSS, LOW); + WRITE(DAC0_SYNC_PIN, HIGH); + OUT_WRITE(SPI_EEPROM1_CS_PIN, HIGH); + OUT_WRITE(SPI_EEPROM2_CS_PIN, HIGH); + OUT_WRITE(SPI_FLASH_CS_PIN, HIGH); + OUT_WRITE(SD_SS_PIN, HIGH); + WRITE(SD_SS_PIN, LOW); PIO_Configure( g_APinDescription[SPI_PIN].pPort, @@ -645,7 +638,7 @@ } // Read from SPI into buffer - void spiRead(uint8_t* buf, uint16_t nbyte) { + void spiRead(uint8_t *buf, uint16_t nbyte) { if (!nbyte) return; --nbyte; for (int i = 0; i < nbyte; i++) { @@ -668,7 +661,7 @@ //DELAY_US(1U); } - void spiSend(const uint8_t* buf, size_t nbyte) { + void spiSend(const uint8_t *buf, size_t nbyte) { if (!nbyte) return; --nbyte; for (size_t i = 0; i < nbyte; i++) { @@ -689,7 +682,7 @@ FLUSH_RX(); } - void spiSend(uint32_t chan, const uint8_t* buf, size_t nbyte) { + void spiSend(uint32_t chan, const uint8_t *buf, size_t nbyte) { if (!nbyte) return; --nbyte; for (size_t i = 0; i < nbyte; i++) { @@ -702,7 +695,7 @@ } // Write from buffer to SPI - void spiSendBlock(uint8_t token, const uint8_t* buf) { + void spiSendBlock(uint8_t token, const uint8_t *buf) { SPI0->SPI_TDR = (uint32_t)token | SPI_PCS(SPI_CHAN); WHILE_TX(0); //WHILE_RX(0); @@ -773,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; @@ -801,19 +794,19 @@ uint8_t spiRec() { return (uint8_t)spiTransfer(0xFF); } - void spiRead(uint8_t* buf, uint16_t nbyte) { + void spiRead(uint8_t *buf, uint16_t nbyte) { for (int i = 0; i < nbyte; i++) buf[i] = spiTransfer(0xFF); } void spiSend(uint8_t data) { spiTransfer(data); } - void spiSend(const uint8_t* buf, size_t nbyte) { + void spiSend(const uint8_t *buf, size_t nbyte) { for (uint16_t i = 0; i < nbyte; i++) spiTransfer(buf[i]); } - void spiSendBlock(uint8_t token, const uint8_t* buf) { + void spiSendBlock(uint8_t token, const uint8_t *buf) { spiTransfer(token); for (uint16_t i = 0; i < 512; i++) spiTransfer(buf[i]); diff --git a/Marlin/src/HAL/DUE/InterruptVectors.cpp b/Marlin/src/HAL/DUE/InterruptVectors.cpp index e4e0ce99f2..70795d1c30 100644 --- a/Marlin/src/HAL/DUE/InterruptVectors.cpp +++ b/Marlin/src/HAL/DUE/InterruptVectors.cpp @@ -41,7 +41,7 @@ practice, we need alignment to 256 bytes to make this work in all cases */ __attribute__ ((aligned(256))) -static DeviceVectors ram_tab = { nullptr }; +static DeviceVectors ram_tab[61] = { nullptr }; /** * This function checks if the exception/interrupt table is already in SRAM or not. diff --git a/Marlin/src/HAL/DUE/MarlinSPI.h b/Marlin/src/HAL/DUE/MarlinSPI.h new file mode 100644 index 0000000000..0c447ba4cb --- /dev/null +++ b/Marlin/src/HAL/DUE/MarlinSPI.h @@ -0,0 +1,26 @@ +/** + * 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 + +#include + +using MarlinSPI = SPIClass; diff --git a/Marlin/src/HAL/DUE/MarlinSerial.cpp b/Marlin/src/HAL/DUE/MarlinSerial.cpp index c9a372eeb1..2e80b4c8d1 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 }; @@ -382,7 +381,7 @@ void MarlinSerial::flush() { } template -void MarlinSerial::write(const uint8_t c) { +size_t MarlinSerial::write(const uint8_t c) { _written = true; if (Cfg::TX_SIZE == 0) { @@ -400,13 +399,13 @@ void MarlinSerial::write(const uint8_t c) { // XOFF char at the RX isr, but it is properly handled there if (!(HWUART->UART_IMR & UART_IMR_TXRDY) && (HWUART->UART_SR & UART_SR_TXRDY)) { HWUART->UART_THR = c; - return; + return 1; } const uint8_t i = (tx_buffer.head + 1) & (Cfg::TX_SIZE - 1); // If global interrupts are disabled (as the result of being called from an ISR)... - if (!ISRS_ENABLED()) { + if (!hal.isr_state()) { // Make room by polling if it is possible to transmit, and do so! while (i == tx_buffer.tail) { @@ -428,6 +427,7 @@ void MarlinSerial::write(const uint8_t c) { // Enable TX isr - Non atomic, but it will eventually enable TX isr HWUART->UART_IER = UART_IER_TXRDY; } + return 1; } template @@ -453,7 +453,7 @@ void MarlinSerial::flushTX() { if (!_written) return; // If global interrupts are disabled (as the result of being called from an ISR)... - if (!ISRS_ENABLED()) { + if (!hal.isr_state()) { // Wait until everything was transmitted - We must do polling, as interrupts are disabled while (tx_buffer.head != tx_buffer.tail || !(HWUART->UART_SR & UART_SR_TXEMPTY)) { @@ -473,169 +473,20 @@ void MarlinSerial::flushTX() { } } -/** - * Imports from print.h - */ - -template -void MarlinSerial::print(char c, int base) { - print((long)c, base); -} - -template -void MarlinSerial::print(unsigned char b, int base) { - print((unsigned long)b, base); -} - -template -void MarlinSerial::print(int n, int base) { - print((long)n, base); -} - -template -void MarlinSerial::print(unsigned int n, int base) { - print((unsigned long)n, base); -} - -template -void MarlinSerial::print(long n, int base) { - if (base == 0) write(n); - else if (base == 10) { - if (n < 0) { print('-'); n = -n; } - printNumber(n, 10); - } - else - printNumber(n, base); -} - -template -void MarlinSerial::print(unsigned long n, int base) { - if (base == 0) write(n); - else printNumber(n, base); -} - -template -void MarlinSerial::print(double n, int digits) { - printFloat(n, digits); -} - -template -void MarlinSerial::println() { - print('\r'); - print('\n'); -} - -template -void MarlinSerial::println(const String& s) { - print(s); - println(); -} - -template -void MarlinSerial::println(const char c[]) { - print(c); - println(); -} - -template -void MarlinSerial::println(char c, int base) { - print(c, base); - println(); -} - -template -void MarlinSerial::println(unsigned char b, int base) { - print(b, base); - println(); -} - -template -void MarlinSerial::println(int n, int base) { - print(n, base); - println(); -} - -template -void MarlinSerial::println(unsigned int n, int base) { - print(n, base); - println(); -} - -template -void MarlinSerial::println(long n, int base) { - print(n, base); - println(); -} - -template -void MarlinSerial::println(unsigned long n, int base) { - print(n, base); - println(); -} - -template -void MarlinSerial::println(double n, int digits) { - print(n, digits); - println(); -} - -// Private Methods -template -void MarlinSerial::printNumber(unsigned long n, uint8_t base) { - if (n) { - unsigned char buf[8 * sizeof(long)]; // Enough space for base 2 - int8_t i = 0; - while (n) { - buf[i++] = n % base; - n /= base; - } - while (i--) - print((char)(buf[i] + (buf[i] < 10 ? '0' : 'A' - 10))); - } - else - print('0'); -} - -template -void MarlinSerial::printFloat(double number, uint8_t digits) { - // Handle negative numbers - if (number < 0.0) { - print('-'); - number = -number; - } - - // Round correctly so that print(1.999, 2) prints as "2.00" - double rounding = 0.5; - LOOP_L_N(i, digits) rounding *= 0.1; - number += rounding; - - // Extract the integer part of the number and print it - unsigned long int_part = (unsigned long)number; - double remainder = number - (double)int_part; - print(int_part); - - // Print the decimal point, but only if there are digits beyond - if (digits) { - print('.'); - // Extract digits from the remainder one at a time - while (digits--) { - remainder *= 10.0; - int toPrint = int(remainder); - print(toPrint); - remainder -= toPrint; - } - } -} - // If not using the USB port as serial port -#if SERIAL_PORT >= 0 - template class MarlinSerial>; // Define - MarlinSerial> customizedSerial1; // Instantiate +#if defined(SERIAL_PORT) && SERIAL_PORT >= 0 + template class MarlinSerial< MarlinSerialCfg >; + MSerialT1 customizedSerial1(MarlinSerialCfg::EMERGENCYPARSER); #endif #if defined(SERIAL_PORT_2) && SERIAL_PORT_2 >= 0 - template class MarlinSerial>; // Define - MarlinSerial> customizedSerial2; // Instantiate + template class MarlinSerial< MarlinSerialCfg >; + MSerialT2 customizedSerial2(MarlinSerialCfg::EMERGENCYPARSER); +#endif + +#if defined(SERIAL_PORT_3) && SERIAL_PORT_3 >= 0 + template class MarlinSerial< MarlinSerialCfg >; + MSerialT3 customizedSerial3(MarlinSerialCfg::EMERGENCYPARSER); #endif #endif // ARDUINO_ARCH_SAM diff --git a/Marlin/src/HAL/DUE/MarlinSerial.h b/Marlin/src/HAL/DUE/MarlinSerial.h index a194eba2f3..cee0857d2b 100644 --- a/Marlin/src/HAL/DUE/MarlinSerial.h +++ b/Marlin/src/HAL/DUE/MarlinSerial.h @@ -30,11 +30,23 @@ #include #include "../../inc/MarlinConfigPre.h" +#include "../../core/types.h" +#include "../../core/serial_hook.h" -#define DEC 10 -#define HEX 16 -#define OCT 8 -#define BIN 2 +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 @@ -56,10 +68,6 @@ // #error "TX_BUFFER_SIZE must be 0, a power of 2 greater than 1, and no greater than 256." //#endif -// Templated type selector -template struct TypeSelector { typedef T type;} ; -template struct TypeSelector { typedef F type; }; - // Templated structure wrapper template struct StructWrapper { constexpr StructWrapper(int) {} @@ -80,7 +88,7 @@ protected: static constexpr int HWUART_IRQ_ID = IRQ_IDS[Cfg::PORT]; // Base size of type on buffer size - typedef typename TypeSelector<(Cfg::RX_SIZE>256), uint16_t, uint8_t>::type ring_buffer_pos_t; + typedef uvalue_t(Cfg::RX_SIZE - 1) ring_buffer_pos_t; struct ring_buffer_r { volatile ring_buffer_pos_t head, tail; @@ -119,44 +127,15 @@ public: static int read(); static void flush(); static ring_buffer_pos_t available(); - static void write(const uint8_t c); + static size_t write(const uint8_t c); static void flushTX(); - static inline bool emergency_parser_enabled() { return Cfg::EMERGENCYPARSER; } + static bool emergency_parser_enabled() { return Cfg::EMERGENCYPARSER; } FORCE_INLINE static uint8_t dropped() { return Cfg::DROPPED_RX ? rx_dropped_bytes : 0; } FORCE_INLINE static uint8_t buffer_overruns() { return Cfg::RX_OVERRUNS ? rx_buffer_overruns : 0; } FORCE_INLINE static uint8_t framing_errors() { return Cfg::RX_FRAMING_ERRORS ? rx_framing_errors : 0; } FORCE_INLINE static ring_buffer_pos_t rxMaxEnqueued() { return Cfg::MAX_RX_QUEUED ? rx_max_enqueued : 0; } - - FORCE_INLINE static void write(const char* str) { while (*str) write(*str++); } - FORCE_INLINE static void write(const uint8_t* buffer, size_t size) { while (size--) write(*buffer++); } - FORCE_INLINE static void print(const String& s) { for (int i = 0; i < (int)s.length(); i++) write(s[i]); } - FORCE_INLINE static void print(const char* str) { write(str); } - - static void print(char, int = 0); - static void print(unsigned char, int = 0); - static void print(int, int = DEC); - static void print(unsigned int, int = DEC); - static void print(long, int = DEC); - static void print(unsigned long, int = DEC); - static void print(double, int = 2); - - static void println(const String& s); - static void println(const char[]); - static void println(char, int = 0); - static void println(unsigned char, int = 0); - static void println(int, int = DEC); - static void println(unsigned int, int = DEC); - static void println(long, int = DEC); - static void println(unsigned long, int = DEC); - static void println(double, int = 2); - static void println(); - operator bool() { return true; } - -private: - static void printNumber(unsigned long, const uint8_t); - static void printFloat(double, uint8_t); }; // Serial port configuration @@ -173,10 +152,17 @@ struct MarlinSerialCfg { static constexpr bool MAX_RX_QUEUED = ENABLED(SERIAL_STATS_MAX_RX_QUEUED); }; -#if SERIAL_PORT >= 0 - extern MarlinSerial> customizedSerial1; +#if defined(SERIAL_PORT) && SERIAL_PORT >= 0 + typedef Serial1Class< MarlinSerial< MarlinSerialCfg > > MSerialT1; + extern MSerialT1 customizedSerial1; #endif #if defined(SERIAL_PORT_2) && SERIAL_PORT_2 >= 0 - extern MarlinSerial> customizedSerial2; + typedef Serial1Class< MarlinSerial< MarlinSerialCfg > > MSerialT2; + extern MSerialT2 customizedSerial2; +#endif + +#if defined(SERIAL_PORT_3) && SERIAL_PORT_3 >= 0 + typedef Serial1Class< MarlinSerial< MarlinSerialCfg > > MSerialT3; + extern MSerialT3 customizedSerial3; #endif diff --git a/Marlin/src/HAL/DUE/MarlinSerialUSB.cpp b/Marlin/src/HAL/DUE/MarlinSerialUSB.cpp index a41dbfeb7a..8de2dc7924 100644 --- a/Marlin/src/HAL/DUE/MarlinSerialUSB.cpp +++ b/Marlin/src/HAL/DUE/MarlinSerialUSB.cpp @@ -19,13 +19,13 @@ * along with this program. If not, see . * */ +#ifdef ARDUINO_ARCH_SAM /** * MarlinSerial_Due.cpp - Hardware serial library for Arduino DUE * Copyright (c) 2017 Eduardo JosΓ© Tagle. All right reserved * Based on MarlinSerial for AVR, copyright (c) 2006 Nicholas Zambetti. All right reserved. */ -#ifdef ARDUINO_ARCH_SAM #include "../../inc/MarlinConfig.h" @@ -33,10 +33,6 @@ #include "MarlinSerialUSB.h" -#if ENABLED(EMERGENCY_PARSER) - #include "../../feature/e_parser.h" -#endif - // Imports from Atmel USB Stack/CDC implementation extern "C" { bool usb_task_cdc_isenabled(); @@ -45,15 +41,11 @@ extern "C" { int udi_cdc_getc(); bool udi_cdc_is_tx_ready(); int udi_cdc_putc(int value); -}; +} // Pending character static int pending_char = -1; -#if ENABLED(EMERGENCY_PARSER) - static EmergencyParser::State emergency_state; // = EP_RESET -#endif - // Public Methods void MarlinSerialUSB::begin(const long) {} @@ -73,7 +65,7 @@ int MarlinSerialUSB::peek() { pending_char = udi_cdc_getc(); - TERN_(EMERGENCY_PARSER, emergency_parser.update(emergency_state, (char)pending_char)); + TERN_(EMERGENCY_PARSER, emergency_parser.update(static_cast(this)->emergency_state, (char)pending_char)); return pending_char; } @@ -95,29 +87,27 @@ int MarlinSerialUSB::read() { int c = udi_cdc_getc(); - TERN_(EMERGENCY_PARSER, emergency_parser.update(emergency_state, (char)c)); + TERN_(EMERGENCY_PARSER, emergency_parser.update(static_cast(this)->emergency_state, (char)c)); return c; } -bool MarlinSerialUSB::available() { - /* If Pending chars */ - return pending_char >= 0 || - /* or USB CDC enumerated and configured on the PC side and some - bytes where sent to us */ - (usb_task_cdc_isenabled() && udi_cdc_is_rx_ready()); +int MarlinSerialUSB::available() { + if (pending_char > 0) return pending_char; + return pending_char == 0 || + // or USB CDC enumerated and configured on the PC side and some bytes where sent to us */ + (usb_task_cdc_isenabled() && udi_cdc_is_rx_ready()); } void MarlinSerialUSB::flush() { } -void MarlinSerialUSB::flushTX() { } -void MarlinSerialUSB::write(const uint8_t c) { +size_t MarlinSerialUSB::write(const uint8_t c) { /* Do not even bother sending anything if USB CDC is not enumerated or not configured on the PC side or there is no program on the PC listening to our messages */ if (!usb_task_cdc_isenabled() || !usb_task_cdc_dtr_active()) - return; + return 0; /* Wait until the PC has read the pending to be sent data */ while (usb_task_cdc_isenabled() && @@ -129,161 +119,23 @@ void MarlinSerialUSB::write(const uint8_t c) { or not configured on the PC side or there is no program on the PC listening to our messages at this point */ if (!usb_task_cdc_isenabled() || !usb_task_cdc_dtr_active()) - return; + return 0; // Fifo full // udi_cdc_signal_overrun(); udi_cdc_putc(c); -} - -/** - * Imports from print.h - */ - -void MarlinSerialUSB::print(char c, int base) { - print((long)c, base); -} - -void MarlinSerialUSB::print(unsigned char b, int base) { - print((unsigned long)b, base); -} - -void MarlinSerialUSB::print(int n, int base) { - print((long)n, base); -} - -void MarlinSerialUSB::print(unsigned int n, int base) { - print((unsigned long)n, base); -} - -void MarlinSerialUSB::print(long n, int base) { - if (base == 0) - write(n); - else if (base == 10) { - if (n < 0) { - print('-'); - n = -n; - } - printNumber(n, 10); - } - else - printNumber(n, base); -} - -void MarlinSerialUSB::print(unsigned long n, int base) { - if (base == 0) write(n); - else printNumber(n, base); -} - -void MarlinSerialUSB::print(double n, int digits) { - printFloat(n, digits); -} - -void MarlinSerialUSB::println() { - print('\r'); - print('\n'); -} - -void MarlinSerialUSB::println(const String& s) { - print(s); - println(); -} - -void MarlinSerialUSB::println(const char c[]) { - print(c); - println(); -} - -void MarlinSerialUSB::println(char c, int base) { - print(c, base); - println(); -} - -void MarlinSerialUSB::println(unsigned char b, int base) { - print(b, base); - println(); -} - -void MarlinSerialUSB::println(int n, int base) { - print(n, base); - println(); -} - -void MarlinSerialUSB::println(unsigned int n, int base) { - print(n, base); - println(); -} - -void MarlinSerialUSB::println(long n, int base) { - print(n, base); - println(); -} - -void MarlinSerialUSB::println(unsigned long n, int base) { - print(n, base); - println(); -} - -void MarlinSerialUSB::println(double n, int digits) { - print(n, digits); - println(); -} - -// Private Methods - -void MarlinSerialUSB::printNumber(unsigned long n, uint8_t base) { - if (n) { - unsigned char buf[8 * sizeof(long)]; // Enough space for base 2 - int8_t i = 0; - while (n) { - buf[i++] = n % base; - n /= base; - } - while (i--) - print((char)(buf[i] + (buf[i] < 10 ? '0' : 'A' - 10))); - } - else - print('0'); -} - -void MarlinSerialUSB::printFloat(double number, uint8_t digits) { - // Handle negative numbers - if (number < 0.0) { - print('-'); - number = -number; - } - - // Round correctly so that print(1.999, 2) prints as "2.00" - double rounding = 0.5; - LOOP_L_N(i, digits) - rounding *= 0.1; - - number += rounding; - - // Extract the integer part of the number and print it - unsigned long int_part = (unsigned long)number; - double remainder = number - (double)int_part; - print(int_part); - - // Print the decimal point, but only if there are digits beyond - if (digits) { - print('.'); - // Extract digits from the remainder one at a time - while (digits--) { - remainder *= 10.0; - int toPrint = int(remainder); - print(toPrint); - remainder -= toPrint; - } - } + return 1; } // Preinstantiate #if SERIAL_PORT == -1 - MarlinSerialUSB customizedSerial1; + MSerialT1 customizedSerial1(TERN0(EMERGENCY_PARSER, true)); #endif #if SERIAL_PORT_2 == -1 - MarlinSerialUSB customizedSerial2; + MSerialT2 customizedSerial2(TERN0(EMERGENCY_PARSER, true)); +#endif +#if SERIAL_PORT_3 == -1 + MSerialT3 customizedSerial3(TERN0(EMERGENCY_PARSER, true)); #endif #endif // HAS_USB_SERIAL diff --git a/Marlin/src/HAL/DUE/MarlinSerialUSB.h b/Marlin/src/HAL/DUE/MarlinSerialUSB.h index 2e3622e553..6da1ef8c08 100644 --- a/Marlin/src/HAL/DUE/MarlinSerialUSB.h +++ b/Marlin/src/HAL/DUE/MarlinSerialUSB.h @@ -27,73 +27,39 @@ */ #include "../../inc/MarlinConfig.h" - -#if HAS_USB_SERIAL +#include "../../core/serial_hook.h" #include -#define DEC 10 -#define HEX 16 -#define OCT 8 -#define BIN 2 - -class MarlinSerialUSB { - -public: - MarlinSerialUSB() {}; - static void begin(const long); - static void end(); - static int peek(); - static int read(); - static void flush(); - static void flushTX(); - static bool available(); - static void write(const uint8_t c); +struct MarlinSerialUSB { + void begin(const long); + void end(); + int peek(); + int read(); + void flush(); + int available(); + size_t write(const uint8_t c); #if ENABLED(SERIAL_STATS_DROPPED_RX) - FORCE_INLINE static uint32_t dropped() { return 0; } + FORCE_INLINE uint32_t dropped() { return 0; } #endif #if ENABLED(SERIAL_STATS_MAX_RX_QUEUED) - FORCE_INLINE static int rxMaxEnqueued() { return 0; } + FORCE_INLINE int rxMaxEnqueued() { return 0; } #endif - - FORCE_INLINE static void write(const char* str) { while (*str) write(*str++); } - FORCE_INLINE static void write(const uint8_t* buffer, size_t size) { while (size--) write(*buffer++); } - FORCE_INLINE static void print(const String& s) { for (int i = 0; i < (int)s.length(); i++) write(s[i]); } - FORCE_INLINE static void print(const char* str) { write(str); } - - static void print(char, int = 0); - static void print(unsigned char, int = 0); - static void print(int, int = DEC); - static void print(unsigned int, int = DEC); - static void print(long, int = DEC); - static void print(unsigned long, int = DEC); - static void print(double, int = 2); - - static void println(const String& s); - static void println(const char[]); - static void println(char, int = 0); - static void println(unsigned char, int = 0); - static void println(int, int = DEC); - static void println(unsigned int, int = DEC); - static void println(long, int = DEC); - static void println(unsigned long, int = DEC); - static void println(double, int = 2); - static void println(); - operator bool() { return true; } - -private: - static void printNumber(unsigned long, const uint8_t); - static void printFloat(double, uint8_t); }; #if SERIAL_PORT == -1 - extern MarlinSerialUSB customizedSerial1; + typedef Serial1Class MSerialT1; + extern MSerialT1 customizedSerial1; #endif #if SERIAL_PORT_2 == -1 - extern MarlinSerialUSB customizedSerial2; + typedef Serial1Class MSerialT2; + extern MSerialT2 customizedSerial2; #endif -#endif // HAS_USB_SERIAL +#if SERIAL_PORT_3 == -1 + typedef Serial1Class MSerialT3; + extern MSerialT3 customizedSerial3; +#endif diff --git a/Marlin/src/HAL/DUE/MinSerial.cpp b/Marlin/src/HAL/DUE/MinSerial.cpp new file mode 100644 index 0000000000..505a712aa9 --- /dev/null +++ b/Marlin/src/HAL/DUE/MinSerial.cpp @@ -0,0 +1,91 @@ +/** + * 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 . + * + */ +#ifdef ARDUINO_ARCH_SAM + +#include "../../inc/MarlinConfigPre.h" + +#if ENABLED(POSTMORTEM_DEBUGGING) + +#include "../shared/MinSerial.h" + +#include + +static void TXBegin() { + // Disable UART interrupt in NVIC + NVIC_DisableIRQ( UART_IRQn ); + + // We NEED memory barriers to ensure Interrupts are actually disabled! + // ( https://dzone.com/articles/nvic-disabling-interrupts-on-arm-cortex-m-and-the ) + __DSB(); + __ISB(); + + // Disable clock + pmc_disable_periph_clk( ID_UART ); + + // Configure PMC + pmc_enable_periph_clk( ID_UART ); + + // Disable PDC channel + UART->UART_PTCR = UART_PTCR_RXTDIS | UART_PTCR_TXTDIS; + + // Reset and disable receiver and transmitter + UART->UART_CR = UART_CR_RSTRX | UART_CR_RSTTX | UART_CR_RXDIS | UART_CR_TXDIS; + + // Configure mode: 8bit, No parity, 1 bit stop + UART->UART_MR = UART_MR_CHMODE_NORMAL | US_MR_CHRL_8_BIT | US_MR_NBSTOP_1_BIT | UART_MR_PAR_NO; + + // Configure baudrate (asynchronous, no oversampling) to BAUDRATE bauds + UART->UART_BRGR = (SystemCoreClock / (BAUDRATE << 4)); + + // Enable receiver and transmitter + UART->UART_CR = UART_CR_RXEN | UART_CR_TXEN; +} + +// A SW memory barrier, to ensure GCC does not overoptimize loops +#define sw_barrier() __asm__ volatile("": : :"memory"); +static void TX(char c) { + while (!(UART->UART_SR & UART_SR_TXRDY)) { WDT_Restart(WDT); sw_barrier(); }; + UART->UART_THR = c; +} + +void install_min_serial() { + HAL_min_serial_init = &TXBegin; + HAL_min_serial_out = &TX; +} + +#if DISABLED(DYNAMIC_VECTORTABLE) + extern "C" { + __attribute__((naked)) void JumpHandler_ASM() { + __asm__ __volatile__ ( + "b CommonHandler_ASM\n" + ); + } + void __attribute__((naked, alias("JumpHandler_ASM"))) HardFault_Handler(); + void __attribute__((naked, alias("JumpHandler_ASM"))) BusFault_Handler(); + void __attribute__((naked, alias("JumpHandler_ASM"))) UsageFault_Handler(); + void __attribute__((naked, alias("JumpHandler_ASM"))) MemManage_Handler(); + void __attribute__((naked, alias("JumpHandler_ASM"))) NMI_Handler(); + } +#endif + +#endif // POSTMORTEM_DEBUGGING +#endif // ARDUINO_ARCH_SAM diff --git a/Marlin/src/HAL/DUE/Servo.cpp b/Marlin/src/HAL/DUE/Servo.cpp index 5524aa9cef..2f72d66b9b 100644 --- a/Marlin/src/HAL/DUE/Servo.cpp +++ b/Marlin/src/HAL/DUE/Servo.cpp @@ -47,12 +47,12 @@ #include "../shared/servo.h" #include "../shared/servo_private.h" -static volatile int8_t Channel[_Nbr_16timers]; // counter for the servo being pulsed for each timer (or -1 if refresh interval) +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(timer16_Sequence_t timer, Tc *pTc, uint8_t channel); +void Servo_Handler(const timer16_Sequence_t, Tc*, const uint8_t); #ifdef _useTimer1 void HANDLER_FOR_TIMER1() { Servo_Handler(_timer1, TC_FOR_TIMER1, CHANNEL_FOR_TIMER1); } @@ -70,88 +70,92 @@ void Servo_Handler(timer16_Sequence_t timer, Tc *pTc, uint8_t channel); void HANDLER_FOR_TIMER5() { Servo_Handler(_timer5, TC_FOR_TIMER5, CHANNEL_FOR_TIMER5); } #endif -void Servo_Handler(timer16_Sequence_t timer, Tc *tc, uint8_t channel) { - // clear interrupt - tc->TC_CHANNEL[channel].TC_SR; - if (Channel[timer] < 0) - tc->TC_CHANNEL[channel].TC_CCR |= TC_CCR_SWTRG; // channel set to -1 indicated that refresh interval completed so reset the timer - else if (SERVO_INDEX(timer, Channel[timer]) < ServoCount && SERVO(timer, Channel[timer]).Pin.isActive) - extDigitalWrite(SERVO(timer, Channel[timer]).Pin.nbr, LOW); // pulse this channel low if activated +void Servo_Handler(const timer16_Sequence_t timer, Tc *tc, const uint8_t channel) { + static int8_t Channel[_Nbr_16timers]; // Servo counters to pulse (or -1 for refresh interval) + int8_t cho = Channel[timer]; // Handle the prior Channel[timer] first + if (cho < 0) { // Channel -1 indicates the refresh interval completed... + tc->TC_CHANNEL[channel].TC_CCR |= TC_CCR_SWTRG; // ...so reset the timer + if (DisablePending[timer]) { + // Disabling only after the full servo period expires prevents + // pulses being too close together if immediately re-enabled. + DisablePending.clear(timer); + TC_Stop(tc, channel); + tc->TC_CHANNEL[channel].TC_SR; // clear interrupt + return; + } + } + else if (SERVO_INDEX(timer, cho) < ServoCount) // prior channel handled? + extDigitalWrite(SERVO(timer, cho).Pin.nbr, LOW); // pulse the prior channel LOW - Channel[timer]++; // increment to the next channel - if (SERVO_INDEX(timer, Channel[timer]) < ServoCount && Channel[timer] < SERVOS_PER_TIMER) { - tc->TC_CHANNEL[channel].TC_RA = tc->TC_CHANNEL[channel].TC_CV + SERVO(timer,Channel[timer]).ticks; - if (SERVO(timer,Channel[timer]).Pin.isActive) // check if activated - extDigitalWrite(SERVO(timer, Channel[timer]).Pin.nbr, HIGH); // its an active channel so pulse it high + Channel[timer] = ++cho; // go to the next channel (or 0) + if (cho < SERVOS_PER_TIMER && SERVO_INDEX(timer, cho) < ServoCount) { + tc->TC_CHANNEL[channel].TC_RA = tc->TC_CHANNEL[channel].TC_CV + SERVO(timer, cho).ticks; + if (SERVO(timer, cho).Pin.isActive) // activated? + extDigitalWrite(SERVO(timer, cho).Pin.nbr, HIGH); // yes: pulse HIGH } else { // finished all channels so wait for the refresh period to expire before starting over - tc->TC_CHANNEL[channel].TC_RA = - tc->TC_CHANNEL[channel].TC_CV < usToTicks(REFRESH_INTERVAL) - 4 - ? (unsigned int)usToTicks(REFRESH_INTERVAL) // allow a few ticks to ensure the next OCR1A not missed - : tc->TC_CHANNEL[channel].TC_CV + 4; // at least REFRESH_INTERVAL has elapsed - Channel[timer] = -1; // this will get incremented at the end of the refresh period to start again at the first channel + const unsigned int cval = tc->TC_CHANNEL[channel].TC_CV + 128 / (SERVO_TIMER_PRESCALER), // allow 128 cycles to ensure the next CV not missed + ival = (unsigned int)usToTicks(REFRESH_INTERVAL); // at least REFRESH_INTERVAL has elapsed + tc->TC_CHANNEL[channel].TC_RA = max(cval, ival); + + Channel[timer] = -1; // reset the timer CCR on the next call } + + tc->TC_CHANNEL[channel].TC_SR; // clear interrupt } static void _initISR(Tc *tc, uint32_t channel, uint32_t id, IRQn_Type irqn) { pmc_enable_periph_clk(id); TC_Configure(tc, channel, - TC_CMR_TCCLKS_TIMER_CLOCK3 | // MCK/32 - TC_CMR_WAVE | // Waveform mode - TC_CMR_WAVSEL_UP_RC ); // Counter running up and reset when equals to RC + TC_CMR_WAVE // Waveform mode + | TC_CMR_WAVSEL_UP_RC // Counter running up and reset when equal to RC + | (SERVO_TIMER_PRESCALER == 2 ? TC_CMR_TCCLKS_TIMER_CLOCK1 : 0) // MCK/2 + | (SERVO_TIMER_PRESCALER == 8 ? TC_CMR_TCCLKS_TIMER_CLOCK2 : 0) // MCK/8 + | (SERVO_TIMER_PRESCALER == 32 ? TC_CMR_TCCLKS_TIMER_CLOCK3 : 0) // MCK/32 + | (SERVO_TIMER_PRESCALER == 128 ? TC_CMR_TCCLKS_TIMER_CLOCK4 : 0) // MCK/128 + ); - /* 84MHz, MCK/32, for 1.5ms: 3937 */ - TC_SetRA(tc, channel, 2625); // 1ms + // Wait 1ms before the first ISR + TC_SetRA(tc, channel, (F_CPU) / (SERVO_TIMER_PRESCALER) / 1000UL); // 1ms - /* Configure and enable interrupt */ + // Configure and enable interrupt NVIC_EnableIRQ(irqn); - // TC_IER_CPAS: RA Compare - tc->TC_CHANNEL[channel].TC_IER = TC_IER_CPAS; + tc->TC_CHANNEL[channel].TC_IER = TC_IER_CPAS; // TC_IER_CPAS: RA Compare // Enables the timer clock and performs a software reset to start the counting TC_Start(tc, channel); } -void initISR(timer16_Sequence_t timer) { - #ifdef _useTimer1 - if (timer == _timer1) - _initISR(TC_FOR_TIMER1, CHANNEL_FOR_TIMER1, ID_TC_FOR_TIMER1, IRQn_FOR_TIMER1); - #endif - #ifdef _useTimer2 - if (timer == _timer2) - _initISR(TC_FOR_TIMER2, CHANNEL_FOR_TIMER2, ID_TC_FOR_TIMER2, IRQn_FOR_TIMER2); - #endif - #ifdef _useTimer3 - if (timer == _timer3) - _initISR(TC_FOR_TIMER3, CHANNEL_FOR_TIMER3, ID_TC_FOR_TIMER3, IRQn_FOR_TIMER3); - #endif - #ifdef _useTimer4 - if (timer == _timer4) - _initISR(TC_FOR_TIMER4, CHANNEL_FOR_TIMER4, ID_TC_FOR_TIMER4, IRQn_FOR_TIMER4); - #endif - #ifdef _useTimer5 - if (timer == _timer5) - _initISR(TC_FOR_TIMER5, CHANNEL_FOR_TIMER5, ID_TC_FOR_TIMER5, IRQn_FOR_TIMER5); - #endif +void initISR(const timer16_Sequence_t timer_index) { + CRITICAL_SECTION_START(); + const bool disable_soon = DisablePending[timer_index]; + DisablePending.clear(timer_index); + CRITICAL_SECTION_END(); + + if (!disable_soon) switch (timer_index) { + default: break; + #ifdef _useTimer1 + case _timer1: return _initISR(TC_FOR_TIMER1, CHANNEL_FOR_TIMER1, ID_TC_FOR_TIMER1, IRQn_FOR_TIMER1); + #endif + #ifdef _useTimer2 + case _timer2: return _initISR(TC_FOR_TIMER2, CHANNEL_FOR_TIMER2, ID_TC_FOR_TIMER2, IRQn_FOR_TIMER2); + #endif + #ifdef _useTimer3 + case _timer3: return _initISR(TC_FOR_TIMER3, CHANNEL_FOR_TIMER3, ID_TC_FOR_TIMER3, IRQn_FOR_TIMER3); + #endif + #ifdef _useTimer4 + case _timer4: return _initISR(TC_FOR_TIMER4, CHANNEL_FOR_TIMER4, ID_TC_FOR_TIMER4, IRQn_FOR_TIMER4); + #endif + #ifdef _useTimer5 + case _timer5: return _initISR(TC_FOR_TIMER5, CHANNEL_FOR_TIMER5, ID_TC_FOR_TIMER5, IRQn_FOR_TIMER5); + #endif + } } -void finISR(timer16_Sequence_t) { - #ifdef _useTimer1 - TC_Stop(TC_FOR_TIMER1, CHANNEL_FOR_TIMER1); - #endif - #ifdef _useTimer2 - TC_Stop(TC_FOR_TIMER2, CHANNEL_FOR_TIMER2); - #endif - #ifdef _useTimer3 - TC_Stop(TC_FOR_TIMER3, CHANNEL_FOR_TIMER3); - #endif - #ifdef _useTimer4 - TC_Stop(TC_FOR_TIMER4, CHANNEL_FOR_TIMER4); - #endif - #ifdef _useTimer5 - TC_Stop(TC_FOR_TIMER5, CHANNEL_FOR_TIMER5); - #endif +void finISR(const timer16_Sequence_t timer_index) { + // Timer is disabled from the ISR, to ensure proper final pulse length. + DisablePending.set(timer_index); } #endif // HAS_SERVOS diff --git a/Marlin/src/HAL/DUE/ServoTimers.h b/Marlin/src/HAL/DUE/ServoTimers.h index c32c938253..95bd404c80 100644 --- a/Marlin/src/HAL/DUE/ServoTimers.h +++ b/Marlin/src/HAL/DUE/ServoTimers.h @@ -37,7 +37,7 @@ #define _useTimer5 #define TRIM_DURATION 2 // compensation ticks to trim adjust for digitalWrite delays -#define SERVO_TIMER_PRESCALER 32 // timer prescaler +#define SERVO_TIMER_PRESCALER 2 // timer prescaler /* TC0, chan 0 => TC0_Handler diff --git a/Marlin/src/HAL/DUE/Tone.cpp b/Marlin/src/HAL/DUE/Tone.cpp index 9beb602223..743cae9d70 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 @@ -35,20 +35,20 @@ static pin_t tone_pin; volatile static int32_t toggles; -void tone(const pin_t _pin, const unsigned int frequency, const unsigned long duration) { +void tone(const pin_t _pin, const unsigned int frequency, const unsigned long duration/*=0*/) { tone_pin = _pin; toggles = 2 * frequency * duration / 1000; - HAL_timer_start(TONE_TIMER_NUM, 2 * frequency); + HAL_timer_start(MF_TIMER_TONE, 2 * frequency); } void noTone(const pin_t _pin) { - HAL_timer_disable_interrupt(TONE_TIMER_NUM); + HAL_timer_disable_interrupt(MF_TIMER_TONE); extDigitalWrite(_pin, LOW); } HAL_TONE_TIMER_ISR() { static uint8_t pin_state = 0; - HAL_timer_isr_prologue(TONE_TIMER_NUM); + HAL_timer_isr_prologue(MF_TIMER_TONE); if (toggles) { toggles--; diff --git a/Marlin/src/HAL/DUE/eeprom_flash.cpp b/Marlin/src/HAL/DUE/eeprom/eeprom_flash.cpp similarity index 92% rename from Marlin/src/HAL/DUE/eeprom_flash.cpp rename to Marlin/src/HAL/DUE/eeprom/eeprom_flash.cpp index 6f38da0967..b33d15e106 100644 --- a/Marlin/src/HAL/DUE/eeprom_flash.cpp +++ b/Marlin/src/HAL/DUE/eeprom/eeprom_flash.cpp @@ -1,10 +1,9 @@ /** * Marlin 3D Printer Firmware - * * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com - * Copyright (c) 2015-2016 Nico Tonnhofer wurstnase.reprap@gmail.com - * Copyright (c) 2016 Victor Perez victor_pv@hotmail.com + * + * 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 @@ -22,7 +21,7 @@ */ #ifdef ARDUINO_ARCH_SAM -#include "../../inc/MarlinConfig.h" +#include "../../../inc/MarlinConfig.h" #if ENABLED(FLASH_EEPROM_EMULATION) @@ -133,13 +132,13 @@ 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) { +static void ee_Dump(const int page, const void *data) { #ifdef EE_EMU_DEBUG - const uint8_t* c = (const uint8_t*) data; + const uint8_t *c = (const uint8_t*) data; char buffer[80]; sprintf_P(buffer, PSTR("Page: %d (0x%04x)\n"), page, page); @@ -181,7 +180,7 @@ static void ee_Dump(const int page, const void* data) { * @param data (pointer to the data buffer) */ __attribute__ ((long_call, section (".ramfunc"))) -static bool ee_PageWrite(uint16_t page, const void* data) { +static bool ee_PageWrite(uint16_t page, const void *data) { uint16_t i; uint32_t addrflash = uint32_t(getFlashStorage(page)); @@ -199,10 +198,9 @@ static bool ee_PageWrite(uint16_t page, const void* data) { for (i = 0; i > 2; i++) pageContents[i] = (((uint32_t*)data)[i]) | (~(pageContents[i] ^ ((uint32_t*)data)[i])); - DEBUG_ECHO_START(); - DEBUG_ECHOLNPAIR("EEPROM PageWrite ", page); - DEBUG_ECHOLNPAIR(" in FLASH address ", (uint32_t)addrflash); - DEBUG_ECHOLNPAIR(" base address ", (uint32_t)getFlashStorage(0)); + DEBUG_ECHO_MSG("EEPROM PageWrite ", page); + DEBUG_ECHOLNPGM(" in FLASH address ", (uint32_t)addrflash); + DEBUG_ECHOLNPGM(" base address ", (uint32_t)getFlashStorage(0)); DEBUG_FLUSH(); // Get the page relative to the start of the EFC controller, and the EFC controller to use @@ -245,8 +243,7 @@ static bool ee_PageWrite(uint16_t page, const void* data) { // Reenable interrupts __enable_irq(); - DEBUG_ECHO_START(); - DEBUG_ECHOLNPAIR("EEPROM Unlock failure for page ", page); + DEBUG_ECHO_MSG("EEPROM Unlock failure for page ", page); return false; } @@ -270,8 +267,7 @@ static bool ee_PageWrite(uint16_t page, const void* data) { // Reenable interrupts __enable_irq(); - DEBUG_ECHO_START(); - DEBUG_ECHOLNPAIR("EEPROM Write failure for page ", page); + DEBUG_ECHO_MSG("EEPROM Write failure for page ", page); return false; } @@ -286,17 +282,16 @@ static bool ee_PageWrite(uint16_t page, const void* data) { if (memcmp(getFlashStorage(page),data,PageSize)) { #ifdef EE_EMU_DEBUG - DEBUG_ECHO_START(); - DEBUG_ECHOLNPAIR("EEPROM Verify Write failure for page ", page); + DEBUG_ECHO_MSG("EEPROM Verify Write failure for page ", page); ee_Dump( page, (uint32_t *)addrflash); ee_Dump(-page, data); // Calculate count of changed bits - uint32_t* p1 = (uint32_t*)addrflash; - uint32_t* p2 = (uint32_t*)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) { @@ -306,7 +301,7 @@ static bool ee_PageWrite(uint16_t page, const void* data) { } } } - DEBUG_ECHOLNPAIR("--> Differing bits: ", count); + DEBUG_ECHOLNPGM("--> Differing bits: ", count); #endif return false; @@ -325,10 +320,9 @@ static bool ee_PageErase(uint16_t page) { uint16_t i; uint32_t addrflash = uint32_t(getFlashStorage(page)); - DEBUG_ECHO_START(); - DEBUG_ECHOLNPAIR("EEPROM PageErase ", page); - DEBUG_ECHOLNPAIR(" in FLASH address ", (uint32_t)addrflash); - DEBUG_ECHOLNPAIR(" base address ", (uint32_t)getFlashStorage(0)); + DEBUG_ECHO_MSG("EEPROM PageErase ", page); + DEBUG_ECHOLNPGM(" in FLASH address ", (uint32_t)addrflash); + DEBUG_ECHOLNPGM(" base address ", (uint32_t)getFlashStorage(0)); DEBUG_FLUSH(); // Get the page relative to the start of the EFC controller, and the EFC controller to use @@ -370,8 +364,7 @@ static bool ee_PageErase(uint16_t page) { // Reenable interrupts __enable_irq(); - DEBUG_ECHO_START(); - DEBUG_ECHOLNPAIR("EEPROM Unlock failure for page ",page); + DEBUG_ECHO_MSG("EEPROM Unlock failure for page ",page); return false; } @@ -394,8 +387,7 @@ static bool ee_PageErase(uint16_t page) { // Reenable interrupts __enable_irq(); - DEBUG_ECHO_START(); - DEBUG_ECHOLNPAIR("EEPROM Erase failure for page ",page); + DEBUG_ECHO_MSG("EEPROM Erase failure for page ",page); return false; } @@ -410,8 +402,7 @@ static bool ee_PageErase(uint16_t page) { uint32_t * aligned_src = (uint32_t *) addrflash; for (i = 0; i < PageSize >> 2; i++) { if (*aligned_src++ != 0xFFFFFFFF) { - DEBUG_ECHO_START(); - DEBUG_ECHOLNPAIR("EEPROM Verify Erase failure for page ",page); + DEBUG_ECHO_MSG("EEPROM Verify Erase failure for page ",page); ee_Dump(page, (uint32_t *)addrflash); return false; } @@ -470,7 +461,7 @@ static uint8_t ee_Read(uint32_t address, bool excludeRAMBuffer=false) { for (int page = curPage - 1; page >= 0; --page) { // Get a pointer to the flash page - uint8_t* pflash = (uint8_t*)getFlashStorage(page + curGroup * PagesPerGroup); + uint8_t *pflash = (uint8_t*)getFlashStorage(page + curGroup * PagesPerGroup); uint16_t i = 0; while (i <= (PageSize - 4)) { /* (PageSize - 4) because otherwise, there is not enough room for data and headers */ @@ -550,7 +541,7 @@ static uint32_t ee_GetAddrRange(uint32_t address, bool excludeRAMBuffer=false) { for (int page = curPage - 1; page >= 0; --page) { // Get a pointer to the flash page - uint8_t* pflash = (uint8_t*)getFlashStorage(page + curGroup * PagesPerGroup); + uint8_t *pflash = (uint8_t*)getFlashStorage(page + curGroup * PagesPerGroup); uint16_t i = 0; while (i <= (PageSize - 4)) { /* (PageSize - 4) because otherwise, there is not enough room for data and headers */ @@ -589,7 +580,7 @@ static uint32_t ee_GetAddrRange(uint32_t address, bool excludeRAMBuffer=false) { } static bool ee_IsPageClean(int page) { - uint32_t* pflash = (uint32_t*) getFlashStorage(page); + uint32_t *pflash = (uint32_t*) getFlashStorage(page); for (uint16_t i = 0; i < (PageSize >> 2); ++i) if (*pflash++ != 0xFFFFFFFF) return false; return true; @@ -599,7 +590,7 @@ static bool ee_Flush(uint32_t overrideAddress = 0xFFFFFFFF, uint8_t overrideData // Check if RAM buffer has something to be written bool isEmpty = true; - uint32_t* p = (uint32_t*) &buffer[0]; + uint32_t *p = (uint32_t*) &buffer[0]; for (uint16_t j = 0; j < (PageSize >> 2); j++) { if (*p++ != 0xFFFFFFFF) { isEmpty = false; @@ -921,8 +912,7 @@ static void ee_Init() { // If all groups seem to be used, default to first group if (curGroup >= GroupCount) curGroup = 0; - DEBUG_ECHO_START(); - DEBUG_ECHOLNPAIR("EEPROM Current Group: ",curGroup); + DEBUG_ECHO_MSG("EEPROM Current Group: ",curGroup); DEBUG_FLUSH(); // Now, validate that all the other group pages are empty @@ -931,8 +921,7 @@ static void ee_Init() { for (int page = 0; page < PagesPerGroup; page++) { if (!ee_IsPageClean(grp * PagesPerGroup + page)) { - DEBUG_ECHO_START(); - DEBUG_ECHOLNPAIR("EEPROM Page ", page, " not clean on group ", grp); + DEBUG_ECHO_MSG("EEPROM Page ", page, " not clean on group ", grp); DEBUG_FLUSH(); ee_PageErase(grp * PagesPerGroup + page); } @@ -948,15 +937,13 @@ static void ee_Init() { } } - DEBUG_ECHO_START(); - DEBUG_ECHOLNPAIR("EEPROM Active page: ", curPage); + DEBUG_ECHO_MSG("EEPROM Active page: ", curPage); DEBUG_FLUSH(); // Make sure the pages following the first clean one are also clean for (int page = curPage + 1; page < PagesPerGroup; page++) { if (!ee_IsPageClean(curGroup * PagesPerGroup + page)) { - DEBUG_ECHO_START(); - DEBUG_ECHOLNPAIR("EEPROM Page ", page, " not clean on active group ", curGroup); + DEBUG_ECHO_MSG("EEPROM Page ", page, " not clean on active group ", curGroup); DEBUG_FLUSH(); ee_Dump(curGroup * PagesPerGroup + page, getFlashStorage(curGroup * PagesPerGroup + page)); ee_PageErase(curGroup * PagesPerGroup + page); @@ -966,24 +953,23 @@ 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 #endif -size_t PersistentStore::capacity() { return MARLIN_EEPROM_SIZE; } +size_t PersistentStore::capacity() { return MARLIN_EEPROM_SIZE - eeprom_exclude_size; } bool PersistentStore::access_start() { ee_Init(); return true; } bool PersistentStore::access_finish() { ee_Flush(); 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 * const p = (uint8_t * const)pos; + uint8_t * const p = (uint8_t * const)REAL_EEPROM_ADDR(pos); uint8_t v = *value; - // EEPROM has only ~100,000 write cycles, - // so only write bytes that have changed! - if (v != ee_Read(uint32_t(p))) { + if (v != ee_Read(uint32_t(p))) { // EEPROM has only ~100,000 write cycles, so only write bytes that have changed! ee_Write(uint32_t(p), v); - delay(2); + if (++written & 0x7F) delay(2); else safe_delay(2); // Avoid triggering watchdog during long EEPROM writes if (ee_Read(uint32_t(p)) != v) { SERIAL_ECHO_MSG(STR_ERR_EEPROM_WRITE); return true; @@ -996,9 +982,9 @@ bool PersistentStore::write_data(int &pos, const uint8_t *value, size_t size, ui return false; } -bool PersistentStore::read_data(int &pos, uint8_t* value, size_t size, uint16_t *crc, const bool writing/*=true*/) { +bool PersistentStore::read_data(int &pos, uint8_t *value, size_t size, uint16_t *crc, const bool writing/*=true*/) { do { - uint8_t c = ee_Read(uint32_t(pos)); + uint8_t c = ee_Read(uint32_t(REAL_EEPROM_ADDR(pos))); if (writing) *value = c; crc16(crc, &c, 1); pos++; diff --git a/Marlin/src/HAL/DUE/eeprom_wired.cpp b/Marlin/src/HAL/DUE/eeprom/eeprom_wired.cpp similarity index 72% rename from Marlin/src/HAL/DUE/eeprom_wired.cpp rename to Marlin/src/HAL/DUE/eeprom/eeprom_wired.cpp index 4599d6a7cd..cf9233816c 100644 --- a/Marlin/src/HAL/DUE/eeprom_wired.cpp +++ b/Marlin/src/HAL/DUE/eeprom/eeprom_wired.cpp @@ -1,10 +1,9 @@ /** * Marlin 3D Printer Firmware - * * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com - * Copyright (c) 2015-2016 Nico Tonnhofer wurstnase.reprap@gmail.com - * Copyright (c) 2016 Victor Perez victor_pv@hotmail.com + * + * 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 @@ -22,7 +21,7 @@ */ #ifdef ARDUINO_ARCH_SAM -#include "../../inc/MarlinConfig.h" +#include "../../../inc/MarlinConfig.h" #if USE_WIRED_EEPROM @@ -31,25 +30,24 @@ * 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." #endif -size_t PersistentStore::capacity() { return MARLIN_EEPROM_SIZE; } +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 * const p = (uint8_t * const)pos; + uint8_t * const p = (uint8_t * const)REAL_EEPROM_ADDR(pos); uint8_t v = *value; - // EEPROM has only ~100,000 write cycles, - // so only write bytes that have changed! - if (v != eeprom_read_byte(p)) { + 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); - delay(2); + 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; @@ -62,9 +60,9 @@ bool PersistentStore::write_data(int &pos, const uint8_t *value, size_t size, ui return false; } -bool PersistentStore::read_data(int &pos, uint8_t* value, size_t size, uint16_t *crc, const bool writing/*=true*/) { +bool PersistentStore::read_data(int &pos, uint8_t *value, size_t size, uint16_t *crc, const bool writing/*=true*/) { do { - uint8_t c = eeprom_read_byte((uint8_t*)pos); + const uint8_t c = eeprom_read_byte((uint8_t*)REAL_EEPROM_ADDR(pos)); if (writing) *value = c; crc16(crc, &c, 1); pos++; diff --git a/Marlin/src/HAL/DUE/endstop_interrupts.h b/Marlin/src/HAL/DUE/endstop_interrupts.h index 999ada5127..798ca4f0cb 100644 --- a/Marlin/src/HAL/DUE/endstop_interrupts.h +++ b/Marlin/src/HAL/DUE/endstop_interrupts.h @@ -47,21 +47,34 @@ void endstop_ISR() { endstops.update(); } void setup_endstop_interrupts() { #define _ATTACH(P) attachInterrupt(digitalPinToInterrupt(P), endstop_ISR, CHANGE) - TERN_(HAS_X_MAX, _ATTACH(X_MAX_PIN)); - TERN_(HAS_X_MIN, _ATTACH(X_MIN_PIN)); - TERN_(HAS_Y_MAX, _ATTACH(Y_MAX_PIN)); - TERN_(HAS_Y_MIN, _ATTACH(Y_MIN_PIN)); - TERN_(HAS_Z_MAX, _ATTACH(Z_MAX_PIN)); - TERN_(HAS_Z_MIN, _ATTACH(Z_MIN_PIN)); - TERN_(HAS_X2_MAX, _ATTACH(X2_MAX_PIN)); - TERN_(HAS_X2_MIN, _ATTACH(X2_MIN_PIN)); - TERN_(HAS_Y2_MAX, _ATTACH(Y2_MAX_PIN)); - TERN_(HAS_Y2_MIN, _ATTACH(Y2_MIN_PIN)); - TERN_(HAS_Z2_MAX, _ATTACH(Z2_MAX_PIN)); - TERN_(HAS_Z2_MIN, _ATTACH(Z2_MIN_PIN)); - TERN_(HAS_Z3_MAX, _ATTACH(Z3_MAX_PIN)); - TERN_(HAS_Z3_MIN, _ATTACH(Z3_MIN_PIN)); - TERN_(HAS_Z4_MAX, _ATTACH(Z4_MAX_PIN)); - TERN_(HAS_Z4_MIN, _ATTACH(Z4_MIN_PIN)); - TERN_(HAS_Z_MIN_PROBE_PIN, _ATTACH(Z_MIN_PROBE_PIN)); + 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/DUE/fastio.h b/Marlin/src/HAL/DUE/fastio.h index 286319302d..77bc1911d8 100644 --- a/Marlin/src/HAL/DUE/fastio.h +++ b/Marlin/src/HAL/DUE/fastio.h @@ -33,7 +33,7 @@ * For ARDUINO_ARCH_SAM * Note the code here was specifically crafted by disassembling what GCC produces * out of it, so GCC is able to optimize it out as much as possible to the least - * amount of instructions. Be very carefull if you modify them, as "clean code" + * amount of instructions. Be very careful if you modify them, as "clean code" * leads to less efficient compiled code!! */ @@ -50,7 +50,7 @@ #define PWM_PIN(P) WITHIN(P, 2, 13) #ifndef MASK - #define MASK(PIN) (1 << PIN) + #define MASK(PIN) _BV(PIN) #endif /** @@ -163,6 +163,9 @@ #define SET_INPUT(IO) _SET_INPUT(IO) // Set pin as input with pullup (wrapper) #define SET_INPUT_PULLUP(IO) do{ _SET_INPUT(IO); _PULLUP(IO, HIGH); }while(0) +// Set pin as input with pulldown (substitution) +#define SET_INPUT_PULLDOWN SET_INPUT + // Set pin as output (wrapper) - reads the pin and sets the output to that value #define SET_OUTPUT(IO) _SET_OUTPUT(IO) // Set pin as PWM @@ -186,12 +189,12 @@ */ // UART -#define RXD DIO0 -#define TXD DIO1 +#define RXD 0 +#define TXD 1 // TWI (I2C) -#define SCL DIO21 -#define SDA DIO20 +#define SCL 21 +#define SDA 20 /** * pins @@ -477,7 +480,7 @@ #define DIO91_PIN 15 #define DIO91_WPORT PIOB -#if ARDUINO_SAM_ARCHIM +#ifdef ARDUINO_SAM_ARCHIM #define DIO92_PIN 11 #define DIO92_WPORT PIOC diff --git a/Marlin/src/HAL/DUE/fastio/G2_PWM.cpp b/Marlin/src/HAL/DUE/fastio/G2_PWM.cpp index d9fbabce21..745e4205bc 100644 --- a/Marlin/src/HAL/DUE/fastio/G2_PWM.cpp +++ b/Marlin/src/HAL/DUE/fastio/G2_PWM.cpp @@ -25,7 +25,7 @@ * is NOT used to directly toggle pins. The ISR writes to the pin assigned to * that interrupt. * - * All PWMs use the same repetition rate. The G2 needs about 10KHz min in order to + * All PWMs use the same repetition rate. The G2 needs about 10kHz min in order to * not have obvious ripple on the Vref signals. * * The data structures are setup to minimize the computation done by the ISR which @@ -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 dc4edffff8..7fb2aaf7a8 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 @@ -49,7 +46,6 @@ extern volatile uint32_t *SODR_A, *SODR_B, *CODR_A, *CODR_B; #define PWM_MAP_INIT_ROW(IO,ZZ) { ZZ == 'A' ? SODR_A : SODR_B, ZZ == 'A' ? CODR_A : CODR_B, 1 << _PIN(IO) } - #define PWM_MAP_INIT { PWM_MAP_INIT_ROW(MOTOR_CURRENT_PWM_X_PIN, 'B'), \ PWM_MAP_INIT_ROW(MOTOR_CURRENT_PWM_Y_PIN, 'B'), \ PWM_MAP_INIT_ROW(MOTOR_CURRENT_PWM_Z_PIN, 'B'), \ @@ -63,7 +59,7 @@ extern PWM_map ISR_table[NUM_PWMS]; extern uint32_t motor_current_setting[3]; #define IR_BIT(p) (WITHIN(p, 0, 3) ? (p) : (p) + 4) -#define COPY_ACTIVE_TABLE() do{ LOOP_L_N(i, 6) work_table[i] = active_table[i]; }while(0) +#define COPY_ACTIVE_TABLE() do{ for (uint8_t i = 0; i < 6; ++i) work_table[i] = active_table[i]; }while(0) #define PWM_MR0 19999 // base repetition rate minus one count - 20mS #define PWM_PR 24 // prescaler value - prescaler divide by 24 + 1 - 1 MHz output diff --git a/Marlin/src/HAL/DUE/fastio/G2_pins.h b/Marlin/src/HAL/DUE/fastio/G2_pins.h index 80c87bd392..38fcc5e5df 100644 --- a/Marlin/src/HAL/DUE/fastio/G2_pins.h +++ b/Marlin/src/HAL/DUE/fastio/G2_pins.h @@ -168,7 +168,6 @@ const G2_PinDescription G2_g_APinDescription[] = { { PIOB, PIO_PB21, ID_PIOB, PIO_OUTPUT_0, PIO_DEFAULT, PIN_ATTR_DIGITAL, NO_ADC, NO_ADC, NOT_ON_PWM, NOT_ON_TIMER }, // PIN 52 { PIOB, PIO_PB14, ID_PIOB, PIO_OUTPUT_0, PIO_DEFAULT, PIN_ATTR_DIGITAL, NO_ADC, NO_ADC, NOT_ON_PWM, NOT_ON_TIMER }, // PIN 53 - // 54 .. 65 - Analog pins // ---------------------- { PIOA, PIO_PA16X1_AD7, ID_PIOA, PIO_INPUT, PIO_DEFAULT, PIN_ATTR_ANALOG, ADC0, ADC7, NOT_ON_PWM, NOT_ON_TIMER }, // AD0 diff --git a/Marlin/src/HAL/DUE/inc/Conditionals_LCD.h b/Marlin/src/HAL/DUE/inc/Conditionals_LCD.h index 5867414447..5f1c4b1601 100644 --- a/Marlin/src/HAL/DUE/inc/Conditionals_LCD.h +++ b/Marlin/src/HAL/DUE/inc/Conditionals_LCD.h @@ -20,7 +20,3 @@ * */ #pragma once - -#if HAS_SPI_TFT || HAS_FSMC_TFT - #error "Sorry! TFT displays are not available for HAL/DUE." -#endif diff --git a/Marlin/src/HAL/DUE/inc/Conditionals_post.h b/Marlin/src/HAL/DUE/inc/Conditionals_post.h index ce6d3fdde2..295596b78b 100644 --- a/Marlin/src/HAL/DUE/inc/Conditionals_post.h +++ b/Marlin/src/HAL/DUE/inc/Conditionals_post.h @@ -23,6 +23,6 @@ #if USE_FALLBACK_EEPROM #define FLASH_EEPROM_EMULATION -#elif EITHER(I2C_EEPROM, SPI_EEPROM) +#elif ANY(I2C_EEPROM, SPI_EEPROM) #define USE_SHARED_EEPROM 1 #endif diff --git a/Marlin/src/HAL/DUE/inc/Conditionals_type.h b/Marlin/src/HAL/DUE/inc/Conditionals_type.h new file mode 100644 index 0000000000..82f95a1035 --- /dev/null +++ b/Marlin/src/HAL/DUE/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/DUE/inc/SanityCheck.h b/Marlin/src/HAL/DUE/inc/SanityCheck.h index 6880696506..599e2aa464 100644 --- a/Marlin/src/HAL/DUE/inc/SanityCheck.h +++ b/Marlin/src/HAL/DUE/inc/SanityCheck.h @@ -25,6 +25,34 @@ * Test Arduino Due specific configuration values for errors at compile-time. */ +#if HAS_SPI_TFT || HAS_FSMC_TFT + #error "Sorry! TFT displays are not available for HAL/DUE." +#endif + +/** + * Check for common serial pin conflicts + */ +#define CHECK_SERIAL_PIN(N) ( \ + X_STOP_PIN == N || Y_STOP_PIN == N || Z_STOP_PIN == N \ + || X_MIN_PIN == N || Y_MIN_PIN == N || Z_MIN_PIN == N \ + || X_MAX_PIN == N || Y_MAX_PIN == N || Z_MAX_PIN == N \ + || X_STEP_PIN == N || Y_STEP_PIN == N || Z_STEP_PIN == N \ + || X_DIR_PIN == N || Y_DIR_PIN == N || Z_DIR_PIN == N \ + || X_ENA_PIN == N || Y_ENA_PIN == N || Z_ENA_PIN == N \ +) +#if SERIAL_IN_USE(0) // D0-D1. No known conflicts. +#endif +#if SERIAL_IN_USE(1) && (CHECK_SERIAL_PIN(18) || CHECK_SERIAL_PIN(19)) + #error "Serial Port 1 pin D18 and/or D19 conflicts with another pin on the board." +#endif +#if SERIAL_IN_USE(2) && (CHECK_SERIAL_PIN(16) || CHECK_SERIAL_PIN(17)) + #error "Serial Port 2 pin D16 and/or D17 conflicts with another pin on the board." +#endif +#if SERIAL_IN_USE(3) && (CHECK_SERIAL_PIN(14) || CHECK_SERIAL_PIN(15)) + #error "Serial Port 3 pin D14 and/or D15 conflicts with another pin on the board." +#endif +#undef CHECK_SERIAL_PIN + /** * HARDWARE VS. SOFTWARE SPI COMPATIBILITY * @@ -40,22 +68,25 @@ * 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_SW_##P) && (TMC_SW_##P == MOSI_PIN || TMC_SW_##P == MISO_PIN || TMC_SW_##P == SCK_PIN)) - -#if ENABLED(SDSUPPORT) && HAS_DRIVER(TMC2130) - #if ENABLED(TMC_USE_SW_SPI) - #if DISABLED(DUE_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(DUE_SOFTWARE_SPI) +#if HAS_MEDIA && HAS_DRIVER(TMC2130) + #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 - #error "Features requiring Hardware PWM (FAST_PWM_FAN, SPINDLE_LASER_FREQUENCY) are not yet supported on DUE." + #error "Features requiring Hardware PWM (FAST_PWM_FAN, SPINDLE_LASER_FREQUENCY) are not yet supported for HAL/DUE." #endif #if HAS_TMC_SW_SERIAL - #error "TMC220x Software Serial is not supported on this platform." + #error "TMC220x Software Serial is not supported on the DUE platform." +#endif + +#if USING_PULLDOWNS + #error "PULLDOWN pin mode is not available on DUE boards." #endif diff --git a/Marlin/src/HAL/DUE/pinsDebug.h b/Marlin/src/HAL/DUE/pinsDebug.h index a99ca8ecce..e9e364dcec 100644 --- a/Marlin/src/HAL/DUE/pinsDebug.h +++ b/Marlin/src/HAL/DUE/pinsDebug.h @@ -2,6 +2,9 @@ * Marlin 3D Printer Firmware * Copyright (c) 2020 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 @@ -16,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" @@ -50,7 +66,7 @@ * The net result is that both the g_pinStatus[pin] array and the PIO_OSR register * needs to be looked at when determining if a pin is an input or an output. * - * b) Due has only pins 6, 7, 8 & 9 enabled for PWMs. FYI - they run at 1KHz + * b) Due has only pins 6, 7, 8 & 9 enabled for PWMs. FYI - they run at 1kHz * * c) NUM_DIGITAL_PINS does not include the analog pins * @@ -60,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_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 PRINT_PIN(p) do{ sprintf_P(buffer, PSTR("%02d"), p); 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 ? 1 : 0) -#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; @@ -82,14 +98,15 @@ 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_ECHOPAIR("PWM = ", PWM_INTERFACE->PWM_CH_NUM[chan].PWM_CDTY); + SERIAL_ECHOPGM("PWM = ", PWM_INTERFACE->PWM_CH_NUM[chan].PWM_CDTY); } } +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 e28eaf8270..e289c8ced2 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 SCK_PIN 76 - #define MISO_PIN 74 - #define MOSI_PIN 75 -#else - // defaults - #define DUE_SOFTWARE_SPI - #ifndef SCK_PIN - #define SCK_PIN 52 - #endif - #ifndef MISO_PIN - #define MISO_PIN 50 - #endif - #ifndef MOSI_PIN - #define MOSI_PIN 51 - #endif +#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 -/* A.28, A.29, B.21, C.26, C.29 */ -#define SS_PIN SDSS +#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 + #define SOFTWARE_SPI + #ifndef SD_SCK_PIN + #define SD_SCK_PIN 52 + #endif + #ifndef SD_MISO_PIN + #define SD_MISO_PIN 50 + #endif + #ifndef SD_MOSI_PIN + #define SD_MOSI_PIN 51 + #endif +#endif diff --git a/Marlin/src/HAL/DUE/timers.cpp b/Marlin/src/HAL/DUE/timers.cpp index 9b937d1a7c..e388255c08 100644 --- a/Marlin/src/HAL/DUE/timers.cpp +++ b/Marlin/src/HAL/DUE/timers.cpp @@ -1,9 +1,9 @@ /** * Marlin 3D Printer Firmware - * * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com - * Copyright (c) 2015-2016 Nico Tonnhofer wurstnase.reprap@gmail.com + * + * 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 @@ -42,7 +42,7 @@ // Private Variables // ------------------------ -const tTimerConfig TimerConfig [NUM_HARDWARE_TIMERS] = { +const tTimerConfig timer_config[NUM_HARDWARE_TIMERS] = { { TC0, 0, TC0_IRQn, 3}, // 0 - [servo timer5] { TC0, 1, TC1_IRQn, 0}, // 1 { TC0, 2, TC2_IRQn, 2}, // 2 - stepper @@ -66,9 +66,9 @@ const tTimerConfig TimerConfig [NUM_HARDWARE_TIMERS] = { */ void HAL_timer_start(const uint8_t timer_num, const uint32_t frequency) { - Tc *tc = TimerConfig[timer_num].pTimerRegs; - IRQn_Type irq = TimerConfig[timer_num].IRQ_Id; - uint32_t channel = TimerConfig[timer_num].channel; + Tc *tc = timer_config[timer_num].pTimerRegs; + IRQn_Type irq = timer_config[timer_num].IRQ_Id; + uint32_t channel = timer_config[timer_num].channel; // Disable interrupt, just in case it was already enabled NVIC_DisableIRQ(irq); @@ -86,13 +86,20 @@ void HAL_timer_start(const uint8_t timer_num, const uint32_t frequency) { pmc_set_writeprotect(false); pmc_enable_periph_clk((uint32_t)irq); - NVIC_SetPriority(irq, TimerConfig [timer_num].priority); + NVIC_SetPriority(irq, timer_config[timer_num].priority); // wave mode, reset counter on match with RC, - TC_Configure(tc, channel, TC_CMR_WAVE | TC_CMR_WAVSEL_UP_RC | TC_CMR_TCCLKS_TIMER_CLOCK1); + TC_Configure(tc, channel, + TC_CMR_WAVE + | TC_CMR_WAVSEL_UP_RC + | (HAL_TIMER_PRESCALER == 2 ? TC_CMR_TCCLKS_TIMER_CLOCK1 : 0) + | (HAL_TIMER_PRESCALER == 8 ? TC_CMR_TCCLKS_TIMER_CLOCK2 : 0) + | (HAL_TIMER_PRESCALER == 32 ? TC_CMR_TCCLKS_TIMER_CLOCK3 : 0) + | (HAL_TIMER_PRESCALER == 128 ? TC_CMR_TCCLKS_TIMER_CLOCK4 : 0) + ); // Set compare value - TC_SetRC(tc, channel, VARIANT_MCK / 2 / frequency); + TC_SetRC(tc, channel, VARIANT_MCK / (HAL_TIMER_PRESCALER) / frequency); // And start timer TC_Start(tc, channel); @@ -105,12 +112,12 @@ void HAL_timer_start(const uint8_t timer_num, const uint32_t frequency) { } void HAL_timer_enable_interrupt(const uint8_t timer_num) { - IRQn_Type irq = TimerConfig[timer_num].IRQ_Id; + IRQn_Type irq = timer_config[timer_num].IRQ_Id; NVIC_EnableIRQ(irq); } void HAL_timer_disable_interrupt(const uint8_t timer_num) { - IRQn_Type irq = TimerConfig[timer_num].IRQ_Id; + IRQn_Type irq = timer_config[timer_num].IRQ_Id; NVIC_DisableIRQ(irq); // We NEED memory barriers to ensure Interrupts are actually disabled! @@ -121,11 +128,11 @@ void HAL_timer_disable_interrupt(const uint8_t timer_num) { // missing from CMSIS: Check if interrupt is enabled or not static bool NVIC_GetEnabledIRQ(IRQn_Type IRQn) { - return (NVIC->ISER[(uint32_t)(IRQn) >> 5] & (1 << ((uint32_t)(IRQn) & 0x1F))) != 0; + return TEST(NVIC->ISER[uint32_t(IRQn) >> 5], uint32_t(IRQn) & 0x1F); } bool HAL_timer_interrupt_enabled(const uint8_t timer_num) { - IRQn_Type irq = TimerConfig[timer_num].IRQ_Id; + IRQn_Type irq = timer_config[timer_num].IRQ_Id; return NVIC_GetEnabledIRQ(irq); } diff --git a/Marlin/src/HAL/DUE/timers.h b/Marlin/src/HAL/DUE/timers.h index 0e1ea07cc2..f892eac8f5 100644 --- a/Marlin/src/HAL/DUE/timers.h +++ b/Marlin/src/HAL/DUE/timers.h @@ -1,8 +1,9 @@ /** * Marlin 3D Printer Firmware - * * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com + * + * 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 @@ -33,39 +34,39 @@ #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 ((F_CPU) / 2) // frequency of timers peripherals +#define HAL_TIMER_PRESCALER 2 +#define HAL_TIMER_RATE ((F_CPU) / (HAL_TIMER_PRESCALER)) // frequency of timers peripherals -#ifndef STEP_TIMER_NUM - #define STEP_TIMER_NUM 2 // Timer Index for Stepper +#ifndef MF_TIMER_STEP + #define MF_TIMER_STEP 2 // Timer Index for Stepper #endif -#ifndef PULSE_TIMER_NUM - #define PULSE_TIMER_NUM STEP_TIMER_NUM +#ifndef MF_TIMER_PULSE + #define MF_TIMER_PULSE MF_TIMER_STEP #endif -#ifndef TEMP_TIMER_NUM - #define TEMP_TIMER_NUM 4 // Timer Index for Temperature +#ifndef MF_TIMER_TEMP + #define MF_TIMER_TEMP 4 // Timer Index for Temperature #endif -#ifndef TONE_TIMER_NUM - #define TONE_TIMER_NUM 6 // index of timer to use for beeper tones +#ifndef MF_TIMER_TONE + #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_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(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) #ifndef HAL_STEP_TIMER_ISR #define HAL_STEP_TIMER_ISR() void TC2_Handler() @@ -92,7 +93,7 @@ typedef struct { // Public Variables // ------------------------ -extern const tTimerConfig TimerConfig[]; +extern const tTimerConfig timer_config[]; // ------------------------ // Public functions @@ -101,17 +102,17 @@ extern const tTimerConfig TimerConfig[]; 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) { - const tTimerConfig * const pConfig = &TimerConfig[timer_num]; + const tTimerConfig * const pConfig = &timer_config[timer_num]; pConfig->pTimerRegs->TC_CHANNEL[pConfig->channel].TC_RC = compare; } FORCE_INLINE static hal_timer_t HAL_timer_get_compare(const uint8_t timer_num) { - const tTimerConfig * const pConfig = &TimerConfig[timer_num]; + const tTimerConfig * const pConfig = &timer_config[timer_num]; return pConfig->pTimerRegs->TC_CHANNEL[pConfig->channel].TC_RC; } FORCE_INLINE static hal_timer_t HAL_timer_get_count(const uint8_t timer_num) { - const tTimerConfig * const pConfig = &TimerConfig[timer_num]; + const tTimerConfig * const pConfig = &timer_config[timer_num]; return pConfig->pTimerRegs->TC_CHANNEL[pConfig->channel].TC_CV; } @@ -120,9 +121,9 @@ void HAL_timer_disable_interrupt(const uint8_t timer_num); bool HAL_timer_interrupt_enabled(const uint8_t timer_num); FORCE_INLINE static void HAL_timer_isr_prologue(const uint8_t timer_num) { - const tTimerConfig * const pConfig = &TimerConfig[timer_num]; + const tTimerConfig * const pConfig = &timer_config[timer_num]; // Reading the status register clears the interrupt flag pConfig->pTimerRegs->TC_CHANNEL[pConfig->channel].TC_SR; } -#define HAL_timer_isr_epilogue(TIMER_NUM) +inline void HAL_timer_isr_epilogue(const uint8_t) {} diff --git a/Marlin/src/HAL/DUE/u8g/LCD_defines.h b/Marlin/src/HAL/DUE/u8g/LCD_defines.h new file mode 100644 index 0000000000..e85f13514f --- /dev/null +++ b/Marlin/src/HAL/DUE/u8g/LCD_defines.h @@ -0,0 +1,34 @@ +/** + * 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 + * (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 + +/** + * DUE (SAM3X8E) LCD-specific defines + */ + +uint8_t u8g_com_HAL_DUE_sw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr); +uint8_t u8g_com_HAL_DUE_shared_hw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr); +uint8_t u8g_com_HAL_DUE_ST7920_sw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr); + +#define U8G_COM_HAL_SW_SPI_FN u8g_com_HAL_DUE_sw_spi_fn +#define U8G_COM_HAL_HW_SPI_FN u8g_com_HAL_DUE_shared_hw_spi_fn +#define U8G_COM_ST7920_HAL_SW_SPI u8g_com_HAL_DUE_ST7920_sw_spi_fn diff --git a/Marlin/src/HAL/DUE/dogm/u8g_com_HAL_DUE_shared_hw_spi.cpp b/Marlin/src/HAL/DUE/u8g/u8g_com_HAL_DUE_shared_hw_spi.cpp similarity index 93% rename from Marlin/src/HAL/DUE/dogm/u8g_com_HAL_DUE_shared_hw_spi.cpp rename to Marlin/src/HAL/DUE/u8g/u8g_com_HAL_DUE_shared_hw_spi.cpp index be4b49c0f9..68f6a5c1a7 100644 --- a/Marlin/src/HAL/DUE/dogm/u8g_com_HAL_DUE_shared_hw_spi.cpp +++ b/Marlin/src/HAL/DUE/u8g/u8g_com_HAL_DUE_shared_hw_spi.cpp @@ -20,7 +20,6 @@ * */ - /** * Based on u8g_com_msp430_hw_spi.c * @@ -60,16 +59,15 @@ #if HAS_MARLINUI_U8GLIB -#include +#include #include "../../../MarlinCore.h" -void spiBegin(); -void spiInit(uint8_t spiRate); -void spiSend(uint8_t b); -void spiSend(const uint8_t* buf, size_t n); +#ifndef LCD_SPI_SPEED + #define LCD_SPI_SPEED SPI_QUARTER_SPEED +#endif -#include "../../shared/Marduino.h" +#include "../../shared/HAL_SPI.h" #include "../fastio.h" void u8g_SetPIOutput_DUE_hw_spi(u8g_t *u8g, uint8_t pin_index) { @@ -100,11 +98,7 @@ uint8_t u8g_com_HAL_DUE_shared_hw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_va spiBegin(); - #ifndef SPI_SPEED - #define SPI_SPEED SPI_FULL_SPEED // use same SPI speed as SD card - #endif - spiInit(2); - + spiInit(LCD_SPI_SPEED); break; case U8G_COM_MSG_ADDRESS: /* define cmd (arg_val = 0) or data mode (arg_val = 1) */ diff --git a/Marlin/src/HAL/DUE/dogm/u8g_com_HAL_DUE_st7920_sw_spi.cpp b/Marlin/src/HAL/DUE/u8g/u8g_com_HAL_DUE_st7920_sw_spi.cpp similarity index 97% rename from Marlin/src/HAL/DUE/dogm/u8g_com_HAL_DUE_st7920_sw_spi.cpp rename to Marlin/src/HAL/DUE/u8g/u8g_com_HAL_DUE_st7920_sw_spi.cpp index 47060d6a5b..8268cf307e 100644 --- a/Marlin/src/HAL/DUE/dogm/u8g_com_HAL_DUE_st7920_sw_spi.cpp +++ b/Marlin/src/HAL/DUE/u8g/u8g_com_HAL_DUE_st7920_sw_spi.cpp @@ -57,11 +57,12 @@ #include "../../../inc/MarlinConfigPre.h" -#if ENABLED(U8GLIB_ST7920) +#if IS_U8GLIB_ST7920 +#include "../../../inc/MarlinConfig.h" #include "../../shared/Delay.h" -#include +#include #include "u8g_com_HAL_DUE_sw_spi_shared.h" @@ -145,7 +146,7 @@ uint8_t u8g_com_HAL_DUE_ST7920_sw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_va } #if ENABLED(LIGHTWEIGHT_UI) - #include "../../../lcd/ultralcd.h" + #include "../../../lcd/marlinui.h" #include "../../shared/HAL_ST7920.h" #define ST7920_CS_PIN LCD_PINS_RS @@ -181,5 +182,5 @@ uint8_t u8g_com_HAL_DUE_ST7920_sw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_va } #endif // LIGHTWEIGHT_UI -#endif // U8GLIB_ST7920 +#endif // IS_U8GLIB_ST7920 #endif // ARDUINO_ARCH_SAM diff --git a/Marlin/src/HAL/DUE/dogm/u8g_com_HAL_DUE_sw_spi.cpp b/Marlin/src/HAL/DUE/u8g/u8g_com_HAL_DUE_sw_spi.cpp similarity index 86% rename from Marlin/src/HAL/DUE/dogm/u8g_com_HAL_DUE_sw_spi.cpp rename to Marlin/src/HAL/DUE/u8g/u8g_com_HAL_DUE_sw_spi.cpp index ea7204fa36..a8f4dd2b03 100644 --- a/Marlin/src/HAL/DUE/dogm/u8g_com_HAL_DUE_sw_spi.cpp +++ b/Marlin/src/HAL/DUE/u8g/u8g_com_HAL_DUE_sw_spi.cpp @@ -57,19 +57,16 @@ #include "../../../inc/MarlinConfigPre.h" -#if HAS_MARLINUI_U8GLIB && DISABLED(U8GLIB_ST7920) - -#undef SPI_SPEED -#define SPI_SPEED 2 // About 2 MHz +#if HAS_MARLINUI_U8GLIB && !IS_U8GLIB_ST7920 #include "u8g_com_HAL_DUE_sw_spi_shared.h" #include "../../shared/Marduino.h" #include "../../shared/Delay.h" -#include +#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 @@ -99,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); @@ -144,5 +141,5 @@ uint8_t u8g_com_HAL_DUE_sw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void return 1; } -#endif // HAS_MARLINUI_U8GLIB && !U8GLIB_ST7920 +#endif // HAS_MARLINUI_U8GLIB && !IS_U8GLIB_ST7920 #endif // ARDUINO_ARCH_SAM diff --git a/Marlin/src/HAL/DUE/dogm/u8g_com_HAL_DUE_sw_spi_shared.cpp b/Marlin/src/HAL/DUE/u8g/u8g_com_HAL_DUE_sw_spi_shared.cpp similarity index 96% rename from Marlin/src/HAL/DUE/dogm/u8g_com_HAL_DUE_sw_spi_shared.cpp rename to Marlin/src/HAL/DUE/u8g/u8g_com_HAL_DUE_sw_spi_shared.cpp index 615a386c35..86c8a48470 100644 --- a/Marlin/src/HAL/DUE/dogm/u8g_com_HAL_DUE_sw_spi_shared.cpp +++ b/Marlin/src/HAL/DUE/u8g/u8g_com_HAL_DUE_sw_spi_shared.cpp @@ -59,9 +59,10 @@ #if HAS_MARLINUI_U8GLIB +#include "../../../inc/MarlinConfig.h" #include "../../shared/Delay.h" -#include +#include #include "u8g_com_HAL_DUE_sw_spi_shared.h" @@ -80,7 +81,7 @@ Pio *SCK_pPio, *MOSI_pPio; uint32_t SCK_dwMask, MOSI_dwMask; void u8g_spiSend_sw_DUE_mode_0(uint8_t val) { // 3MHz - LOOP_L_N(i, 8) { + for (uint8_t i = 0; i < 8; ++i) { if (val & 0x80) MOSI_pPio->PIO_SODR = MOSI_dwMask; else @@ -94,7 +95,7 @@ void u8g_spiSend_sw_DUE_mode_0(uint8_t val) { // 3MHz } void u8g_spiSend_sw_DUE_mode_3(uint8_t val) { // 3.5MHz - LOOP_L_N(i, 8) { + for (uint8_t i = 0; i < 8; ++i) { SCK_pPio->PIO_CODR = SCK_dwMask; DELAY_NS(50); if (val & 0x80) diff --git a/Marlin/src/HAL/DUE/dogm/u8g_com_HAL_DUE_sw_spi_shared.h b/Marlin/src/HAL/DUE/u8g/u8g_com_HAL_DUE_sw_spi_shared.h similarity index 98% rename from Marlin/src/HAL/DUE/dogm/u8g_com_HAL_DUE_sw_spi_shared.h rename to Marlin/src/HAL/DUE/u8g/u8g_com_HAL_DUE_sw_spi_shared.h index f076c503ca..45231fd091 100644 --- a/Marlin/src/HAL/DUE/dogm/u8g_com_HAL_DUE_sw_spi_shared.h +++ b/Marlin/src/HAL/DUE/u8g/u8g_com_HAL_DUE_sw_spi_shared.h @@ -23,7 +23,7 @@ #include "../../../inc/MarlinConfigPre.h" #include "../../shared/Marduino.h" -#include +#include void u8g_SetPIOutput_DUE(u8g_t *u8g, uint8_t pin_index); void u8g_SetPILevel_DUE(u8g_t *u8g, uint8_t pin_index, uint8_t level); diff --git a/Marlin/src/HAL/DUE/upload_extra_script.py b/Marlin/src/HAL/DUE/upload_extra_script.py index d52a0a3642..729b1d67de 100644 --- a/Marlin/src/HAL/DUE/upload_extra_script.py +++ b/Marlin/src/HAL/DUE/upload_extra_script.py @@ -4,15 +4,16 @@ # Windows: bossac.exe # Other: leave unchanged # +import pioutil +if pioutil.is_pio_build(): + import platform + current_OS = platform.system() -import platform -current_OS = platform.system() + if current_OS == 'Windows': -if current_OS == 'Windows': + env = pioutil.env - Import("env") - - # Use bossac.exe on Windows - env.Replace( - UPLOADCMD="bossac --info --unlock --write --verify --reset --erase -U false --boot $SOURCE" - ) + # Use bossac.exe on Windows + env.Replace( + UPLOADCMD="bossac --info --unlock --write --verify --reset --erase -U false --boot $SOURCE" + ) diff --git a/Marlin/src/HAL/DUE/usb/.editorconfig b/Marlin/src/HAL/DUE/usb/.editorconfig new file mode 100644 index 0000000000..f2c26de5ee --- /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/README.md b/Marlin/src/HAL/DUE/usb/README.md new file mode 100644 index 0000000000..2548135aca --- /dev/null +++ b/Marlin/src/HAL/DUE/usb/README.md @@ -0,0 +1,29 @@ +# USB Files Source Documentation + +## Source + +We sourced the USB files in Marlin from the Atmel ASF (Advanced Software Framework). The framework provides a variety of examples which were utilized in this project. + +Atmel doesn't provide these files in a source repository but they can be extracted from ASF, which can be downloaded from Atmel. + +[Advanced Software Framework](https://www.microchip.com/en-us/tools-resources/develop/libraries/advanced-software-framework) + +## Modifications + +The files are mostly unmodified except for minor cosmetic changes but some more significant changes were needed. + +The changes that prompted the addition of this README file are listed below. Other changes may have been made prior to this. + +1. Modified `uotghs_device_due.c` to resolve race conditions that could leave interrupts asserted when freezing the peripheral clock, resulting in hangs and watchdog resets due to the ensuing interrupt storm. + +## Version Information + +We don't know the exact version of ASF used as the source. However, the copyright information in the files indicates they are from 2015. + +## Upgrade Considerations + +We looked at the ASF 3.52.0 files released in 2022 but saw no immediate benefits to justify an upgrade. It's important to note that the files in Marlin don't follow the same folder structure as the files in ASF, which complicates the process of comparing and applying updated files. + +When these files are updated it's important to carefully compare them to Marlin's versions so any improvements in the Marlin sources are brought forward. + +It would be best to make Marlin's directory structure align with ASF or at least document the source of each file to ease future updates. diff --git a/Marlin/src/HAL/DUE/usb/arduino_due_x.h b/Marlin/src/HAL/DUE/usb/arduino_due_x.h index d3b333fb34..e7b6f3dcb3 100644 --- a/Marlin/src/HAL/DUE/usb/arduino_due_x.h +++ b/Marlin/src/HAL/DUE/usb/arduino_due_x.h @@ -71,7 +71,7 @@ /* ------------------------------------------------------------------------ */ /** - * \page arduino_due_x_board_info "Arduino Due/X - Board informations" + * \page arduino_due_x_board_info "Arduino Due/X - Board information" * This page lists several definition related to the board description. * */ diff --git a/Marlin/src/HAL/DUE/usb/compiler.h b/Marlin/src/HAL/DUE/usb/compiler.h index f89e554c45..27c554cdb7 100644 --- a/Marlin/src/HAL/DUE/usb/compiler.h +++ b/Marlin/src/HAL/DUE/usb/compiler.h @@ -142,7 +142,6 @@ */ #define COMPILER_PACK_RESET() COMPILER_PRAGMA(pack()) - /** * \brief Set aligned boundary. */ @@ -283,7 +282,6 @@ typedef double F64; //!< 64-bit floating-point number. typedef uint32_t iram_size_t; //! @} - /*! \name Status Types */ //! @{ @@ -291,7 +289,6 @@ typedef bool Status_bool_t; //!< Boolean status. typedef U8 Status_t; //!< 8-bit-coded status. //! @} - /*! \name Aliasing Aggregate Types */ //! @{ @@ -462,7 +459,6 @@ typedef struct #endif //! @} - #ifndef __ASSEMBLY__ // not for assembling. //! \name Optimization Control @@ -581,7 +577,6 @@ typedef struct //! @} - /*! \name Zero-Bit Counting * * Under GCC, __builtin_clz and __builtin_ctz behave like macros when @@ -692,7 +687,6 @@ typedef struct //! @} - /*! \name Bit Reversing */ //! @{ @@ -732,7 +726,6 @@ typedef struct //! @} - /*! \name Alignment */ //! @{ @@ -798,7 +791,6 @@ typedef struct */ #define Long_call(addr) ((*(void (*)(void))(addr))()) - /*! \name MCU Endianism Handling * ARM is MCU little endianism. */ @@ -868,7 +860,6 @@ typedef struct #define CPU_TO_BE32(x) swap32(x) //! @} - /*! \name Endianism Conversion * * The same considerations as for clz and ctz apply here but GCC's @@ -955,7 +946,6 @@ typedef struct //! @} - /*! \name Target Abstraction */ //! @{ @@ -997,7 +987,6 @@ typedef U8 Byte; //!< 8-bit unsigned integer. #endif // #ifndef __ASSEMBLY__ - #ifdef __ICCARM__ #define SHORTENUM __packed #elif defined(__GNUC__) @@ -1059,7 +1048,7 @@ static inline void convert_64_bit_to_byte_array(uint64_t value, uint8_t *data) while (val_index < 8) { data[val_index++] = value & 0xFF; - value = value >> 8; + value >>= 8; } } diff --git a/Marlin/src/HAL/DUE/usb/conf_access.h b/Marlin/src/HAL/DUE/usb/conf_access.h index f401685223..0ea5fe2287 100644 --- a/Marlin/src/HAL/DUE/usb/conf_access.h +++ b/Marlin/src/HAL/DUE/usb/conf_access.h @@ -81,7 +81,6 @@ #define LUN_0_NAME "\"SD/MMC Card\"" //! @} - /*! \name Actions Associated with Memory Accesses * * Write here the action to associate with each memory access. @@ -112,5 +111,4 @@ #define GLOBAL_WR_PROTECT false //!< Management of a global write protection. //! @} - #endif // _CONF_ACCESS_H_ diff --git a/Marlin/src/HAL/DUE/usb/conf_clock.h b/Marlin/src/HAL/DUE/usb/conf_clock.h index 97e70e99a5..0c7815ee4d 100644 --- a/Marlin/src/HAL/DUE/usb/conf_clock.h +++ b/Marlin/src/HAL/DUE/usb/conf_clock.h @@ -96,5 +96,4 @@ // - UPLL frequency: 480MHz // - USB clock: 480 / 1 = 480MHz - #endif /* CONF_CLOCK_H_INCLUDED */ diff --git a/Marlin/src/HAL/DUE/usb/conf_usb.h b/Marlin/src/HAL/DUE/usb/conf_usb.h index 4de9e347e2..fb4ef34241 100644 --- a/Marlin/src/HAL/DUE/usb/conf_usb.h +++ b/Marlin/src/HAL/DUE/usb/conf_usb.h @@ -88,7 +88,6 @@ #endif //@} - /** * USB Device Callbacks definitions (Optional) * @{ @@ -101,7 +100,7 @@ #define USB_DEVICE_SPECIFIC_REQUEST() usb_task_other_requests() //@} -#if ENABLED(SDSUPPORT) +#if HAS_MEDIA /** * USB Device low level configuration * When only one interface is used, these configurations are defined by the class module. @@ -150,7 +149,6 @@ //@} - /** * USB Interface Configuration * @{ @@ -185,7 +183,7 @@ //! Enable id string of interface to add an extra USB string #define UDI_CDC_IAD_STRING_ID 4 -#if ENABLED(SDSUPPORT) +#if HAS_MEDIA /** * USB CDC low level configuration * In standalone these configurations are defined by the CDC module. @@ -210,7 +208,6 @@ //@} //@} - /** * Configuration of MSC interface * @{ @@ -245,7 +242,6 @@ //@} - /** * Description of Composite Device * @{ diff --git a/Marlin/src/HAL/DUE/usb/ctrl_access.c b/Marlin/src/HAL/DUE/usb/ctrl_access.c index 99f97f62cb..ec0e4adc4b 100644 --- a/Marlin/src/HAL/DUE/usb/ctrl_access.c +++ b/Marlin/src/HAL/DUE/usb/ctrl_access.c @@ -63,12 +63,11 @@ #include "compiler.h" #include "preprocessor.h" #ifdef FREERTOS_USED -#include "FreeRTOS.h" -#include "semphr.h" +#include +#include #endif #include "ctrl_access.h" - //_____ D E F I N I T I O N S ______________________________________________ #ifdef FREERTOS_USED @@ -112,7 +111,6 @@ static xSemaphoreHandle ctrl_access_semphr = NULL; #endif // FREERTOS_USED - #if MAX_LUN /*! \brief Initializes an entry of the LUN descriptor table. @@ -242,17 +240,14 @@ static const struct #endif - #if GLOBAL_WR_PROTECT == true bool g_wr_protect; #endif - /*! \name Control Interface */ //! @{ - #ifdef FREERTOS_USED bool ctrl_access_init(void) @@ -270,7 +265,6 @@ bool ctrl_access_init(void) return true; } - /*! \brief Locks accesses to LUNs. * * \return \c true if the access was successfully locked, else \c false. @@ -288,7 +282,6 @@ static bool ctrl_access_lock(void) #endif // FREERTOS_USED - U8 get_nb_lun(void) { #if MEM_USB == ENABLE @@ -309,13 +302,11 @@ U8 get_nb_lun(void) #endif } - U8 get_cur_lun(void) { return LUN_ID_0; } - Ctrl_status mem_test_unit_ready(U8 lun) { Ctrl_status status; @@ -337,7 +328,6 @@ Ctrl_status mem_test_unit_ready(U8 lun) return status; } - Ctrl_status mem_read_capacity(U8 lun, U32 *u32_nb_sector) { Ctrl_status status; @@ -359,7 +349,6 @@ Ctrl_status mem_read_capacity(U8 lun, U32 *u32_nb_sector) return status; } - U8 mem_sector_size(U8 lun) { U8 sector_size; @@ -381,7 +370,6 @@ U8 mem_sector_size(U8 lun) return sector_size; } - bool mem_unload(U8 lun, bool unload) { bool unloaded; @@ -433,7 +421,6 @@ bool mem_wr_protect(U8 lun) return wr_protect; } - bool mem_removal(U8 lun) { bool removal; @@ -458,7 +445,6 @@ bool mem_removal(U8 lun) return removal; } - const char *mem_name(U8 lun) { #if MAX_LUN==0 @@ -475,17 +461,14 @@ const char *mem_name(U8 lun) #endif } - //! @} - #if ACCESS_USB == true /*! \name MEM <-> USB Interface */ //! @{ - Ctrl_status memory_2_usb(U8 lun, U32 addr, U16 nb_sector) { Ctrl_status status; @@ -505,7 +488,6 @@ Ctrl_status memory_2_usb(U8 lun, U32 addr, U16 nb_sector) return status; } - Ctrl_status usb_2_memory(U8 lun, U32 addr, U16 nb_sector) { Ctrl_status status; @@ -525,19 +507,16 @@ Ctrl_status usb_2_memory(U8 lun, U32 addr, U16 nb_sector) return status; } - //! @} #endif // ACCESS_USB == true - #if ACCESS_MEM_TO_RAM == true /*! \name MEM <-> RAM Interface */ //! @{ - Ctrl_status memory_2_ram(U8 lun, U32 addr, void *ram) { Ctrl_status status; @@ -564,7 +543,6 @@ Ctrl_status memory_2_ram(U8 lun, U32 addr, void *ram) return status; } - Ctrl_status ram_2_memory(U8 lun, U32 addr, const void *ram) { Ctrl_status status; @@ -591,19 +569,16 @@ Ctrl_status ram_2_memory(U8 lun, U32 addr, const void *ram) return status; } - //! @} #endif // ACCESS_MEM_TO_RAM == true - #if ACCESS_STREAM == true /*! \name Streaming MEM <-> MEM Interface */ //! @{ - #if ACCESS_MEM_TO_MEM == true #include "fat.h" @@ -625,21 +600,18 @@ Ctrl_status stream_mem_to_mem(U8 src_lun, U32 src_addr, U8 dest_lun, U32 dest_ad #endif // ACCESS_MEM_TO_MEM == true - Ctrl_status stream_state(U8 id) { UNUSED(id); return CTRL_GOOD; } - U16 stream_stop(U8 id) { UNUSED(id); return 0; } - //! @} #endif // ACCESS_STREAM diff --git a/Marlin/src/HAL/DUE/usb/ctrl_access.h b/Marlin/src/HAL/DUE/usb/ctrl_access.h index b33839076e..d9cb05da74 100644 --- a/Marlin/src/HAL/DUE/usb/ctrl_access.h +++ b/Marlin/src/HAL/DUE/usb/ctrl_access.h @@ -56,7 +56,6 @@ * Support and FAQ: visit Atmel Support */ - #ifndef _CTRL_ACCESS_H_ #define _CTRL_ACCESS_H_ @@ -89,7 +88,6 @@ typedef enum CTRL_BUSY = FAIL + 2 //!< Memory not initialized or changed. } Ctrl_status; - // FYI: Each Logical Unit Number (LUN) corresponds to a memory. // Check LUN defines. @@ -136,7 +134,6 @@ typedef enum #define LUN_ID_USB (MAX_LUN) //!< First dynamic LUN (USB host mass storage). //! @} - // Include LUN header files. #if LUN_0 == ENABLE #include LUN_0_INCLUDE @@ -166,13 +163,11 @@ typedef enum #include LUN_USB_INCLUDE #endif - // Check the configuration of write protection in conf_access.h. #ifndef GLOBAL_WR_PROTECT #error GLOBAL_WR_PROTECT must be defined as true or false in conf_access.h #endif - #if GLOBAL_WR_PROTECT == true //! Write protect. @@ -180,7 +175,6 @@ extern bool g_wr_protect; #endif - /*! \name Control Interface */ //! @{ @@ -279,7 +273,6 @@ extern const char *mem_name(U8 lun); //! @} - #if ACCESS_USB == true /*! \name MEM <-> USB Interface @@ -310,7 +303,6 @@ extern Ctrl_status usb_2_memory(U8 lun, U32 addr, U16 nb_sector); #endif // ACCESS_USB == true - #if ACCESS_MEM_TO_RAM == true /*! \name MEM <-> RAM Interface @@ -341,7 +333,6 @@ extern Ctrl_status ram_2_memory(U8 lun, U32 addr, const void *ram); #endif // ACCESS_MEM_TO_RAM == true - #if ACCESS_STREAM == true /*! \name Streaming MEM <-> MEM Interface diff --git a/Marlin/src/HAL/DUE/usb/genclk.h b/Marlin/src/HAL/DUE/usb/genclk.h index cde03bc0d1..45eba5873f 100644 --- a/Marlin/src/HAL/DUE/usb/genclk.h +++ b/Marlin/src/HAL/DUE/usb/genclk.h @@ -74,17 +74,17 @@ extern "C" { //@{ enum genclk_source { - GENCLK_PCK_SRC_SLCK_RC = 0, //!< Internal 32kHz RC oscillator as PCK source clock - GENCLK_PCK_SRC_SLCK_XTAL = 1, //!< External 32kHz crystal oscillator as PCK source clock - GENCLK_PCK_SRC_SLCK_BYPASS = 2, //!< External 32kHz bypass oscillator as PCK source clock - GENCLK_PCK_SRC_MAINCK_4M_RC = 3, //!< Internal 4MHz RC oscillator as PCK source clock - GENCLK_PCK_SRC_MAINCK_8M_RC = 4, //!< Internal 8MHz RC oscillator as PCK source clock - GENCLK_PCK_SRC_MAINCK_12M_RC = 5, //!< Internal 12MHz RC oscillator as PCK source clock - GENCLK_PCK_SRC_MAINCK_XTAL = 6, //!< External crystal oscillator as PCK source clock - GENCLK_PCK_SRC_MAINCK_BYPASS = 7, //!< External bypass oscillator as PCK source clock - GENCLK_PCK_SRC_PLLACK = 8, //!< Use PLLACK as PCK source clock - GENCLK_PCK_SRC_PLLBCK = 9, //!< Use PLLBCK as PCK source clock - GENCLK_PCK_SRC_MCK = 10, //!< Use Master Clk as PCK source clock + GENCLK_PCK_SRC_SLCK_RC = 0, //!< Internal 32kHz RC oscillator as PCK source clock + GENCLK_PCK_SRC_SLCK_XTAL = 1, //!< External 32kHz crystal oscillator as PCK source clock + GENCLK_PCK_SRC_SLCK_BYPASS = 2, //!< External 32kHz bypass oscillator as PCK source clock + GENCLK_PCK_SRC_MAINCK_4M_RC = 3, //!< Internal 4MHz RC oscillator as PCK source clock + GENCLK_PCK_SRC_MAINCK_8M_RC = 4, //!< Internal 8MHz RC oscillator as PCK source clock + GENCLK_PCK_SRC_MAINCK_12M_RC = 5, //!< Internal 12MHz RC oscillator as PCK source clock + GENCLK_PCK_SRC_MAINCK_XTAL = 6, //!< External crystal oscillator as PCK source clock + GENCLK_PCK_SRC_MAINCK_BYPASS = 7, //!< External bypass oscillator as PCK source clock + GENCLK_PCK_SRC_PLLACK = 8, //!< Use PLLACK as PCK source clock + GENCLK_PCK_SRC_PLLBCK = 9, //!< Use PLLBCK as PCK source clock + GENCLK_PCK_SRC_MCK = 10, //!< Use Master Clk as PCK source clock }; //@} @@ -93,176 +93,162 @@ enum genclk_source { //@{ enum genclk_divider { - GENCLK_PCK_PRES_1 = PMC_PCK_PRES_CLK_1, //!< Set PCK clock prescaler to 1 - GENCLK_PCK_PRES_2 = PMC_PCK_PRES_CLK_2, //!< Set PCK clock prescaler to 2 - GENCLK_PCK_PRES_4 = PMC_PCK_PRES_CLK_4, //!< Set PCK clock prescaler to 4 - GENCLK_PCK_PRES_8 = PMC_PCK_PRES_CLK_8, //!< Set PCK clock prescaler to 8 - GENCLK_PCK_PRES_16 = PMC_PCK_PRES_CLK_16, //!< Set PCK clock prescaler to 16 - GENCLK_PCK_PRES_32 = PMC_PCK_PRES_CLK_32, //!< Set PCK clock prescaler to 32 - GENCLK_PCK_PRES_64 = PMC_PCK_PRES_CLK_64, //!< Set PCK clock prescaler to 64 + GENCLK_PCK_PRES_1 = PMC_PCK_PRES_CLK_1, //!< Set PCK clock prescaler to 1 + GENCLK_PCK_PRES_2 = PMC_PCK_PRES_CLK_2, //!< Set PCK clock prescaler to 2 + GENCLK_PCK_PRES_4 = PMC_PCK_PRES_CLK_4, //!< Set PCK clock prescaler to 4 + GENCLK_PCK_PRES_8 = PMC_PCK_PRES_CLK_8, //!< Set PCK clock prescaler to 8 + GENCLK_PCK_PRES_16 = PMC_PCK_PRES_CLK_16, //!< Set PCK clock prescaler to 16 + GENCLK_PCK_PRES_32 = PMC_PCK_PRES_CLK_32, //!< Set PCK clock prescaler to 32 + GENCLK_PCK_PRES_64 = PMC_PCK_PRES_CLK_64, //!< Set PCK clock prescaler to 64 }; //@} struct genclk_config { - uint32_t ctrl; + uint32_t ctrl; }; -static inline void genclk_config_defaults(struct genclk_config *p_cfg, - uint32_t ul_id) -{ - ul_id = ul_id; - p_cfg->ctrl = 0; +static inline void genclk_config_defaults(struct genclk_config *p_cfg, uint32_t ul_id) { + ul_id = ul_id; + p_cfg->ctrl = 0; } -static inline void genclk_config_read(struct genclk_config *p_cfg, - uint32_t ul_id) -{ - p_cfg->ctrl = PMC->PMC_PCK[ul_id]; +static inline void genclk_config_read(struct genclk_config *p_cfg, uint32_t ul_id) { + p_cfg->ctrl = PMC->PMC_PCK[ul_id]; } -static inline void genclk_config_write(const struct genclk_config *p_cfg, - uint32_t ul_id) -{ - PMC->PMC_PCK[ul_id] = p_cfg->ctrl; +static inline void genclk_config_write(const struct genclk_config *p_cfg, uint32_t ul_id) { + PMC->PMC_PCK[ul_id] = p_cfg->ctrl; } //! \name Programmable Clock Source and Prescaler configuration //@{ -static inline void genclk_config_set_source(struct genclk_config *p_cfg, - enum genclk_source e_src) -{ - p_cfg->ctrl &= (~PMC_PCK_CSS_Msk); +static inline void genclk_config_set_source(struct genclk_config *p_cfg, enum genclk_source e_src) { + p_cfg->ctrl &= (~PMC_PCK_CSS_Msk); - switch (e_src) { - case GENCLK_PCK_SRC_SLCK_RC: - case GENCLK_PCK_SRC_SLCK_XTAL: - case GENCLK_PCK_SRC_SLCK_BYPASS: - p_cfg->ctrl |= (PMC_PCK_CSS_SLOW_CLK); - break; + switch (e_src) { + case GENCLK_PCK_SRC_SLCK_RC: + case GENCLK_PCK_SRC_SLCK_XTAL: + case GENCLK_PCK_SRC_SLCK_BYPASS: + p_cfg->ctrl |= (PMC_PCK_CSS_SLOW_CLK); + break; - case GENCLK_PCK_SRC_MAINCK_4M_RC: - case GENCLK_PCK_SRC_MAINCK_8M_RC: - case GENCLK_PCK_SRC_MAINCK_12M_RC: - case GENCLK_PCK_SRC_MAINCK_XTAL: - case GENCLK_PCK_SRC_MAINCK_BYPASS: - p_cfg->ctrl |= (PMC_PCK_CSS_MAIN_CLK); - break; + case GENCLK_PCK_SRC_MAINCK_4M_RC: + case GENCLK_PCK_SRC_MAINCK_8M_RC: + case GENCLK_PCK_SRC_MAINCK_12M_RC: + case GENCLK_PCK_SRC_MAINCK_XTAL: + case GENCLK_PCK_SRC_MAINCK_BYPASS: + p_cfg->ctrl |= (PMC_PCK_CSS_MAIN_CLK); + break; - case GENCLK_PCK_SRC_PLLACK: - p_cfg->ctrl |= (PMC_PCK_CSS_PLLA_CLK); - break; + case GENCLK_PCK_SRC_PLLACK: + p_cfg->ctrl |= (PMC_PCK_CSS_PLLA_CLK); + break; - case GENCLK_PCK_SRC_PLLBCK: - p_cfg->ctrl |= (PMC_PCK_CSS_UPLL_CLK); - break; + case GENCLK_PCK_SRC_PLLBCK: + p_cfg->ctrl |= (PMC_PCK_CSS_UPLL_CLK); + break; - case GENCLK_PCK_SRC_MCK: - p_cfg->ctrl |= (PMC_PCK_CSS_MCK); - break; - } + case GENCLK_PCK_SRC_MCK: + p_cfg->ctrl |= (PMC_PCK_CSS_MCK); + break; + } } -static inline void genclk_config_set_divider(struct genclk_config *p_cfg, - uint32_t e_divider) -{ - p_cfg->ctrl &= ~PMC_PCK_PRES_Msk; - p_cfg->ctrl |= e_divider; +static inline void genclk_config_set_divider(struct genclk_config *p_cfg, uint32_t e_divider) { + p_cfg->ctrl &= ~PMC_PCK_PRES_Msk; + p_cfg->ctrl |= e_divider; } //@} -static inline void genclk_enable(const struct genclk_config *p_cfg, - uint32_t ul_id) -{ - PMC->PMC_PCK[ul_id] = p_cfg->ctrl; - pmc_enable_pck(ul_id); +static inline void genclk_enable(const struct genclk_config *p_cfg, uint32_t ul_id) { + PMC->PMC_PCK[ul_id] = p_cfg->ctrl; + pmc_enable_pck(ul_id); } -static inline void genclk_disable(uint32_t ul_id) -{ - pmc_disable_pck(ul_id); +static inline void genclk_disable(uint32_t ul_id) { + pmc_disable_pck(ul_id); } -static inline void genclk_enable_source(enum genclk_source e_src) -{ - switch (e_src) { - case GENCLK_PCK_SRC_SLCK_RC: - if (!osc_is_ready(OSC_SLCK_32K_RC)) { - osc_enable(OSC_SLCK_32K_RC); - osc_wait_ready(OSC_SLCK_32K_RC); - } - break; +static inline void genclk_enable_source(enum genclk_source e_src) { + switch (e_src) { + case GENCLK_PCK_SRC_SLCK_RC: + if (!osc_is_ready(OSC_SLCK_32K_RC)) { + osc_enable(OSC_SLCK_32K_RC); + osc_wait_ready(OSC_SLCK_32K_RC); + } + break; - case GENCLK_PCK_SRC_SLCK_XTAL: - if (!osc_is_ready(OSC_SLCK_32K_XTAL)) { - osc_enable(OSC_SLCK_32K_XTAL); - osc_wait_ready(OSC_SLCK_32K_XTAL); - } - break; + case GENCLK_PCK_SRC_SLCK_XTAL: + if (!osc_is_ready(OSC_SLCK_32K_XTAL)) { + osc_enable(OSC_SLCK_32K_XTAL); + osc_wait_ready(OSC_SLCK_32K_XTAL); + } + break; - case GENCLK_PCK_SRC_SLCK_BYPASS: - if (!osc_is_ready(OSC_SLCK_32K_BYPASS)) { - osc_enable(OSC_SLCK_32K_BYPASS); - osc_wait_ready(OSC_SLCK_32K_BYPASS); - } - break; + case GENCLK_PCK_SRC_SLCK_BYPASS: + if (!osc_is_ready(OSC_SLCK_32K_BYPASS)) { + osc_enable(OSC_SLCK_32K_BYPASS); + osc_wait_ready(OSC_SLCK_32K_BYPASS); + } + break; - case GENCLK_PCK_SRC_MAINCK_4M_RC: - if (!osc_is_ready(OSC_MAINCK_4M_RC)) { - osc_enable(OSC_MAINCK_4M_RC); - osc_wait_ready(OSC_MAINCK_4M_RC); - } - break; + case GENCLK_PCK_SRC_MAINCK_4M_RC: + if (!osc_is_ready(OSC_MAINCK_4M_RC)) { + osc_enable(OSC_MAINCK_4M_RC); + osc_wait_ready(OSC_MAINCK_4M_RC); + } + break; - case GENCLK_PCK_SRC_MAINCK_8M_RC: - if (!osc_is_ready(OSC_MAINCK_8M_RC)) { - osc_enable(OSC_MAINCK_8M_RC); - osc_wait_ready(OSC_MAINCK_8M_RC); - } - break; + case GENCLK_PCK_SRC_MAINCK_8M_RC: + if (!osc_is_ready(OSC_MAINCK_8M_RC)) { + osc_enable(OSC_MAINCK_8M_RC); + osc_wait_ready(OSC_MAINCK_8M_RC); + } + break; - case GENCLK_PCK_SRC_MAINCK_12M_RC: - if (!osc_is_ready(OSC_MAINCK_12M_RC)) { - osc_enable(OSC_MAINCK_12M_RC); - osc_wait_ready(OSC_MAINCK_12M_RC); - } - break; + case GENCLK_PCK_SRC_MAINCK_12M_RC: + if (!osc_is_ready(OSC_MAINCK_12M_RC)) { + osc_enable(OSC_MAINCK_12M_RC); + osc_wait_ready(OSC_MAINCK_12M_RC); + } + break; - case GENCLK_PCK_SRC_MAINCK_XTAL: - if (!osc_is_ready(OSC_MAINCK_XTAL)) { - osc_enable(OSC_MAINCK_XTAL); - osc_wait_ready(OSC_MAINCK_XTAL); - } - break; + case GENCLK_PCK_SRC_MAINCK_XTAL: + if (!osc_is_ready(OSC_MAINCK_XTAL)) { + osc_enable(OSC_MAINCK_XTAL); + osc_wait_ready(OSC_MAINCK_XTAL); + } + break; - case GENCLK_PCK_SRC_MAINCK_BYPASS: - if (!osc_is_ready(OSC_MAINCK_BYPASS)) { - osc_enable(OSC_MAINCK_BYPASS); - osc_wait_ready(OSC_MAINCK_BYPASS); - } - break; + case GENCLK_PCK_SRC_MAINCK_BYPASS: + if (!osc_is_ready(OSC_MAINCK_BYPASS)) { + osc_enable(OSC_MAINCK_BYPASS); + osc_wait_ready(OSC_MAINCK_BYPASS); + } + break; -#ifdef CONFIG_PLL0_SOURCE - case GENCLK_PCK_SRC_PLLACK: - pll_enable_config_defaults(0); - break; -#endif + #ifdef CONFIG_PLL0_SOURCE + case GENCLK_PCK_SRC_PLLACK: + pll_enable_config_defaults(0); + break; + #endif -#ifdef CONFIG_PLL1_SOURCE - case GENCLK_PCK_SRC_PLLBCK: - pll_enable_config_defaults(1); - break; -#endif + #ifdef CONFIG_PLL1_SOURCE + case GENCLK_PCK_SRC_PLLBCK: + pll_enable_config_defaults(1); + break; + #endif - case GENCLK_PCK_SRC_MCK: - break; + case GENCLK_PCK_SRC_MCK: + break; - default: - Assert(false); - break; - } + default: + Assert(false); + break; + } } //! @} diff --git a/Marlin/src/HAL/DUE/usb/mrepeat.h b/Marlin/src/HAL/DUE/usb/mrepeat.h index 8363d9cde3..10a8237545 100644 --- a/Marlin/src/HAL/DUE/usb/mrepeat.h +++ b/Marlin/src/HAL/DUE/usb/mrepeat.h @@ -57,7 +57,6 @@ #include "preprocessor.h" - //! Maximal number of repetitions supported by MREPEAT. #define MREPEAT_LIMIT 256 diff --git a/Marlin/src/HAL/DUE/usb/osc.h b/Marlin/src/HAL/DUE/usb/osc.h index 953bcbbed1..1585018ed8 100644 --- a/Marlin/src/HAL/DUE/usb/osc.h +++ b/Marlin/src/HAL/DUE/usb/osc.h @@ -62,28 +62,28 @@ extern "C" { * should be defined by the board code, otherwise default value are used. */ #ifndef BOARD_FREQ_SLCK_XTAL -# warning The board slow clock xtal frequency has not been defined. -# define BOARD_FREQ_SLCK_XTAL (32768UL) + #warning The board slow clock xtal frequency has not been defined. + #define BOARD_FREQ_SLCK_XTAL (32768UL) #endif #ifndef BOARD_FREQ_SLCK_BYPASS -# warning The board slow clock bypass frequency has not been defined. -# define BOARD_FREQ_SLCK_BYPASS (32768UL) + #warning The board slow clock bypass frequency has not been defined. + #define BOARD_FREQ_SLCK_BYPASS (32768UL) #endif #ifndef BOARD_FREQ_MAINCK_XTAL -# warning The board main clock xtal frequency has not been defined. -# define BOARD_FREQ_MAINCK_XTAL (12000000UL) + #warning The board main clock xtal frequency has not been defined. + #define BOARD_FREQ_MAINCK_XTAL (12000000UL) #endif #ifndef BOARD_FREQ_MAINCK_BYPASS -# warning The board main clock bypass frequency has not been defined. -# define BOARD_FREQ_MAINCK_BYPASS (12000000UL) + #warning The board main clock bypass frequency has not been defined. + #define BOARD_FREQ_MAINCK_BYPASS (12000000UL) #endif #ifndef BOARD_OSC_STARTUP_US -# warning The board main clock xtal startup time has not been defined. -# define BOARD_OSC_STARTUP_US (15625UL) + #warning The board main clock xtal startup time has not been defined. + #define BOARD_OSC_STARTUP_US (15625UL) #endif /** @@ -115,122 +115,116 @@ extern "C" { #define OSC_MAINCK_BYPASS_HZ BOARD_FREQ_MAINCK_BYPASS //!< External bypass oscillator. //@} -static inline void osc_enable(uint32_t ul_id) -{ - switch (ul_id) { - case OSC_SLCK_32K_RC: - break; +static inline void osc_enable(uint32_t ul_id) { + switch (ul_id) { + case OSC_SLCK_32K_RC: + break; - case OSC_SLCK_32K_XTAL: - pmc_switch_sclk_to_32kxtal(PMC_OSC_XTAL); - break; + case OSC_SLCK_32K_XTAL: + pmc_switch_sclk_to_32kxtal(PMC_OSC_XTAL); + break; - case OSC_SLCK_32K_BYPASS: - pmc_switch_sclk_to_32kxtal(PMC_OSC_BYPASS); - break; + case OSC_SLCK_32K_BYPASS: + pmc_switch_sclk_to_32kxtal(PMC_OSC_BYPASS); + break; + case OSC_MAINCK_4M_RC: + pmc_switch_mainck_to_fastrc(CKGR_MOR_MOSCRCF_4_MHz); + break; - case OSC_MAINCK_4M_RC: - pmc_switch_mainck_to_fastrc(CKGR_MOR_MOSCRCF_4_MHz); - break; + case OSC_MAINCK_8M_RC: + pmc_switch_mainck_to_fastrc(CKGR_MOR_MOSCRCF_8_MHz); + break; - case OSC_MAINCK_8M_RC: - pmc_switch_mainck_to_fastrc(CKGR_MOR_MOSCRCF_8_MHz); - break; + case OSC_MAINCK_12M_RC: + pmc_switch_mainck_to_fastrc(CKGR_MOR_MOSCRCF_12_MHz); + break; - case OSC_MAINCK_12M_RC: - pmc_switch_mainck_to_fastrc(CKGR_MOR_MOSCRCF_12_MHz); - break; + case OSC_MAINCK_XTAL: + pmc_switch_mainck_to_xtal(PMC_OSC_XTAL/*, + pmc_us_to_moscxtst(BOARD_OSC_STARTUP_US, + OSC_SLCK_32K_RC_HZ)*/); + break; - - case OSC_MAINCK_XTAL: - pmc_switch_mainck_to_xtal(PMC_OSC_XTAL/*, - pmc_us_to_moscxtst(BOARD_OSC_STARTUP_US, - OSC_SLCK_32K_RC_HZ)*/); - break; - - case OSC_MAINCK_BYPASS: - pmc_switch_mainck_to_xtal(PMC_OSC_BYPASS/*, - pmc_us_to_moscxtst(BOARD_OSC_STARTUP_US, - OSC_SLCK_32K_RC_HZ)*/); - break; - } + case OSC_MAINCK_BYPASS: + pmc_switch_mainck_to_xtal(PMC_OSC_BYPASS/*, + pmc_us_to_moscxtst(BOARD_OSC_STARTUP_US, + OSC_SLCK_32K_RC_HZ)*/); + break; + } } -static inline void osc_disable(uint32_t ul_id) -{ - switch (ul_id) { - case OSC_SLCK_32K_RC: - case OSC_SLCK_32K_XTAL: - case OSC_SLCK_32K_BYPASS: - break; +static inline void osc_disable(uint32_t ul_id) { + switch (ul_id) { + case OSC_SLCK_32K_RC: + case OSC_SLCK_32K_XTAL: + case OSC_SLCK_32K_BYPASS: + break; - case OSC_MAINCK_4M_RC: - case OSC_MAINCK_8M_RC: - case OSC_MAINCK_12M_RC: - pmc_osc_disable_fastrc(); - break; + case OSC_MAINCK_4M_RC: + case OSC_MAINCK_8M_RC: + case OSC_MAINCK_12M_RC: + pmc_osc_disable_fastrc(); + break; - case OSC_MAINCK_XTAL: - pmc_osc_disable_xtal(PMC_OSC_XTAL); - break; + case OSC_MAINCK_XTAL: + pmc_osc_disable_xtal(PMC_OSC_XTAL); + break; - case OSC_MAINCK_BYPASS: - pmc_osc_disable_xtal(PMC_OSC_BYPASS); - break; - } + case OSC_MAINCK_BYPASS: + pmc_osc_disable_xtal(PMC_OSC_BYPASS); + break; + } } -static inline bool osc_is_ready(uint32_t ul_id) -{ - switch (ul_id) { - case OSC_SLCK_32K_RC: - return 1; +static inline bool osc_is_ready(uint32_t ul_id) { + switch (ul_id) { + case OSC_SLCK_32K_RC: + return 1; - case OSC_SLCK_32K_XTAL: - case OSC_SLCK_32K_BYPASS: - return pmc_osc_is_ready_32kxtal(); + case OSC_SLCK_32K_XTAL: + case OSC_SLCK_32K_BYPASS: + return pmc_osc_is_ready_32kxtal(); - case OSC_MAINCK_4M_RC: - case OSC_MAINCK_8M_RC: - case OSC_MAINCK_12M_RC: - case OSC_MAINCK_XTAL: - case OSC_MAINCK_BYPASS: - return pmc_osc_is_ready_mainck(); - } + case OSC_MAINCK_4M_RC: + case OSC_MAINCK_8M_RC: + case OSC_MAINCK_12M_RC: + case OSC_MAINCK_XTAL: + case OSC_MAINCK_BYPASS: + return pmc_osc_is_ready_mainck(); + } - return 0; + return 0; } -static inline uint32_t osc_get_rate(uint32_t ul_id) -{ - switch (ul_id) { - case OSC_SLCK_32K_RC: - return OSC_SLCK_32K_RC_HZ; +static inline uint32_t osc_get_rate(uint32_t ul_id) { + switch (ul_id) { + case OSC_SLCK_32K_RC: + return OSC_SLCK_32K_RC_HZ; - case OSC_SLCK_32K_XTAL: - return BOARD_FREQ_SLCK_XTAL; + case OSC_SLCK_32K_XTAL: + return BOARD_FREQ_SLCK_XTAL; - case OSC_SLCK_32K_BYPASS: - return BOARD_FREQ_SLCK_BYPASS; + case OSC_SLCK_32K_BYPASS: + return BOARD_FREQ_SLCK_BYPASS; - case OSC_MAINCK_4M_RC: - return OSC_MAINCK_4M_RC_HZ; + case OSC_MAINCK_4M_RC: + return OSC_MAINCK_4M_RC_HZ; - case OSC_MAINCK_8M_RC: - return OSC_MAINCK_8M_RC_HZ; + case OSC_MAINCK_8M_RC: + return OSC_MAINCK_8M_RC_HZ; - case OSC_MAINCK_12M_RC: - return OSC_MAINCK_12M_RC_HZ; + case OSC_MAINCK_12M_RC: + return OSC_MAINCK_12M_RC_HZ; - case OSC_MAINCK_XTAL: - return BOARD_FREQ_MAINCK_XTAL; + case OSC_MAINCK_XTAL: + return BOARD_FREQ_MAINCK_XTAL; - case OSC_MAINCK_BYPASS: - return BOARD_FREQ_MAINCK_BYPASS; - } + case OSC_MAINCK_BYPASS: + return BOARD_FREQ_MAINCK_BYPASS; + } - return 0; + return 0; } /** @@ -241,11 +235,10 @@ static inline uint32_t osc_get_rate(uint32_t ul_id) * * \param id A number identifying the oscillator to wait for. */ -static inline void osc_wait_ready(uint8_t id) -{ - while (!osc_is_ready(id)) { - /* Do nothing */ - } +static inline void osc_wait_ready(uint8_t id) { + while (!osc_is_ready(id)) { + /* Do nothing */ + } } //! @} diff --git a/Marlin/src/HAL/DUE/usb/pll.h b/Marlin/src/HAL/DUE/usb/pll.h index 8eaf27672b..d25a1f65d0 100644 --- a/Marlin/src/HAL/DUE/usb/pll.h +++ b/Marlin/src/HAL/DUE/usb/pll.h @@ -77,22 +77,22 @@ extern "C" { #define PLL_COUNT 0x3FU enum pll_source { - PLL_SRC_MAINCK_4M_RC = OSC_MAINCK_4M_RC, //!< Internal 4MHz RC oscillator. - PLL_SRC_MAINCK_8M_RC = OSC_MAINCK_8M_RC, //!< Internal 8MHz RC oscillator. - PLL_SRC_MAINCK_12M_RC = OSC_MAINCK_12M_RC, //!< Internal 12MHz RC oscillator. - PLL_SRC_MAINCK_XTAL = OSC_MAINCK_XTAL, //!< External crystal oscillator. - PLL_SRC_MAINCK_BYPASS = OSC_MAINCK_BYPASS, //!< External bypass oscillator. - PLL_NR_SOURCES, //!< Number of PLL sources. + PLL_SRC_MAINCK_4M_RC = OSC_MAINCK_4M_RC, //!< Internal 4MHz RC oscillator. + PLL_SRC_MAINCK_8M_RC = OSC_MAINCK_8M_RC, //!< Internal 8MHz RC oscillator. + PLL_SRC_MAINCK_12M_RC = OSC_MAINCK_12M_RC, //!< Internal 12MHz RC oscillator. + PLL_SRC_MAINCK_XTAL = OSC_MAINCK_XTAL, //!< External crystal oscillator. + PLL_SRC_MAINCK_BYPASS = OSC_MAINCK_BYPASS, //!< External bypass oscillator. + PLL_NR_SOURCES, //!< Number of PLL sources. }; struct pll_config { - uint32_t ctrl; + uint32_t ctrl; }; #define pll_get_default_rate(pll_id) \ - ((osc_get_rate(CONFIG_PLL##pll_id##_SOURCE) \ - * CONFIG_PLL##pll_id##_MUL) \ - / CONFIG_PLL##pll_id##_DIV) + ((osc_get_rate(CONFIG_PLL##pll_id##_SOURCE) \ + * CONFIG_PLL##pll_id##_MUL) \ + / CONFIG_PLL##pll_id##_DIV) /* Force UTMI PLL parameters (Hardware defined) */ #ifdef CONFIG_PLL1_SOURCE @@ -113,145 +113,130 @@ struct pll_config { * is hidden in this implementation. Use mul as mul effective value. */ static inline void pll_config_init(struct pll_config *p_cfg, - enum pll_source e_src, uint32_t ul_div, uint32_t ul_mul) -{ - uint32_t vco_hz; + enum pll_source e_src, uint32_t ul_div, uint32_t ul_mul) { + uint32_t vco_hz; - Assert(e_src < PLL_NR_SOURCES); + Assert(e_src < PLL_NR_SOURCES); - if (ul_div == 0 && ul_mul == 0) { /* Must only be true for UTMI PLL */ - p_cfg->ctrl = CKGR_UCKR_UPLLCOUNT(PLL_COUNT); - } else { /* PLLA */ - /* Calculate internal VCO frequency */ - vco_hz = osc_get_rate(e_src) / ul_div; - Assert(vco_hz >= PLL_INPUT_MIN_HZ); - Assert(vco_hz <= PLL_INPUT_MAX_HZ); + if (ul_div == 0 && ul_mul == 0) { /* Must only be true for UTMI PLL */ + p_cfg->ctrl = CKGR_UCKR_UPLLCOUNT(PLL_COUNT); + } + else { /* PLLA */ + /* Calculate internal VCO frequency */ + vco_hz = osc_get_rate(e_src) / ul_div; + Assert(vco_hz >= PLL_INPUT_MIN_HZ); + Assert(vco_hz <= PLL_INPUT_MAX_HZ); - vco_hz *= ul_mul; - Assert(vco_hz >= PLL_OUTPUT_MIN_HZ); - Assert(vco_hz <= PLL_OUTPUT_MAX_HZ); + vco_hz *= ul_mul; + Assert(vco_hz >= PLL_OUTPUT_MIN_HZ); + Assert(vco_hz <= PLL_OUTPUT_MAX_HZ); - /* PMC hardware will automatically make it mul+1 */ - p_cfg->ctrl = CKGR_PLLAR_MULA(ul_mul - 1) | CKGR_PLLAR_DIVA(ul_div) | CKGR_PLLAR_PLLACOUNT(PLL_COUNT); - } + /* PMC hardware will automatically make it mul+1 */ + p_cfg->ctrl = CKGR_PLLAR_MULA(ul_mul - 1) | CKGR_PLLAR_DIVA(ul_div) | CKGR_PLLAR_PLLACOUNT(PLL_COUNT); + } } -#define pll_config_defaults(cfg, pll_id) \ - pll_config_init(cfg, \ - CONFIG_PLL##pll_id##_SOURCE, \ - CONFIG_PLL##pll_id##_DIV, \ - CONFIG_PLL##pll_id##_MUL) +#define pll_config_defaults(cfg, pll_id) \ + pll_config_init(cfg, \ + CONFIG_PLL##pll_id##_SOURCE, \ + CONFIG_PLL##pll_id##_DIV, \ + CONFIG_PLL##pll_id##_MUL) -static inline void pll_config_read(struct pll_config *p_cfg, uint32_t ul_pll_id) -{ - Assert(ul_pll_id < NR_PLLS); - - if (ul_pll_id == PLLA_ID) { - p_cfg->ctrl = PMC->CKGR_PLLAR; - } else { - p_cfg->ctrl = PMC->CKGR_UCKR; - } +static inline void pll_config_read(struct pll_config *p_cfg, uint32_t ul_pll_id) { + Assert(ul_pll_id < NR_PLLS); + p_cfg->ctrl = ul_pll_id == PLLA_ID ? PMC->CKGR_PLLAR : PMC->CKGR_UCKR; } -static inline void pll_config_write(const struct pll_config *p_cfg, uint32_t ul_pll_id) -{ - Assert(ul_pll_id < NR_PLLS); +static inline void pll_config_write(const struct pll_config *p_cfg, uint32_t ul_pll_id) { + Assert(ul_pll_id < NR_PLLS); - if (ul_pll_id == PLLA_ID) { - pmc_disable_pllack(); // Always stop PLL first! - PMC->CKGR_PLLAR = CKGR_PLLAR_ONE | p_cfg->ctrl; - } else { - PMC->CKGR_UCKR = p_cfg->ctrl; - } + if (ul_pll_id == PLLA_ID) { + pmc_disable_pllack(); // Always stop PLL first! + PMC->CKGR_PLLAR = CKGR_PLLAR_ONE | p_cfg->ctrl; + } + else + PMC->CKGR_UCKR = p_cfg->ctrl; } -static inline void pll_enable(const struct pll_config *p_cfg, uint32_t ul_pll_id) -{ - Assert(ul_pll_id < NR_PLLS); +static inline void pll_enable(const struct pll_config *p_cfg, uint32_t ul_pll_id) { + Assert(ul_pll_id < NR_PLLS); - if (ul_pll_id == PLLA_ID) { - pmc_disable_pllack(); // Always stop PLL first! - PMC->CKGR_PLLAR = CKGR_PLLAR_ONE | p_cfg->ctrl; - } else { - PMC->CKGR_UCKR = p_cfg->ctrl | CKGR_UCKR_UPLLEN; - } + if (ul_pll_id == PLLA_ID) { + pmc_disable_pllack(); // Always stop PLL first! + PMC->CKGR_PLLAR = CKGR_PLLAR_ONE | p_cfg->ctrl; + } + else + PMC->CKGR_UCKR = p_cfg->ctrl | CKGR_UCKR_UPLLEN; } /** * \note This will only disable the selected PLL, not the underlying oscillator (mainck). */ -static inline void pll_disable(uint32_t ul_pll_id) -{ - Assert(ul_pll_id < NR_PLLS); +static inline void pll_disable(uint32_t ul_pll_id) { + Assert(ul_pll_id < NR_PLLS); - if (ul_pll_id == PLLA_ID) { - pmc_disable_pllack(); - } else { - PMC->CKGR_UCKR &= ~CKGR_UCKR_UPLLEN; - } + if (ul_pll_id == PLLA_ID) + pmc_disable_pllack(); + else + PMC->CKGR_UCKR &= ~CKGR_UCKR_UPLLEN; } -static inline uint32_t pll_is_locked(uint32_t ul_pll_id) -{ - Assert(ul_pll_id < NR_PLLS); +static inline uint32_t pll_is_locked(uint32_t ul_pll_id) { + Assert(ul_pll_id < NR_PLLS); - if (ul_pll_id == PLLA_ID) { - return pmc_is_locked_pllack(); - } else { - return pmc_is_locked_upll(); - } + if (ul_pll_id == PLLA_ID) + return pmc_is_locked_pllack(); + else + return pmc_is_locked_upll(); } -static inline void pll_enable_source(enum pll_source e_src) -{ - switch (e_src) { - case PLL_SRC_MAINCK_4M_RC: - case PLL_SRC_MAINCK_8M_RC: - case PLL_SRC_MAINCK_12M_RC: - case PLL_SRC_MAINCK_XTAL: - case PLL_SRC_MAINCK_BYPASS: - osc_enable(e_src); - osc_wait_ready(e_src); - break; +static inline void pll_enable_source(enum pll_source e_src) { + switch (e_src) { + case PLL_SRC_MAINCK_4M_RC: + case PLL_SRC_MAINCK_8M_RC: + case PLL_SRC_MAINCK_12M_RC: + case PLL_SRC_MAINCK_XTAL: + case PLL_SRC_MAINCK_BYPASS: + osc_enable(e_src); + osc_wait_ready(e_src); + break; - default: - Assert(false); - break; - } + default: + Assert(false); + break; + } } -static inline void pll_enable_config_defaults(unsigned int ul_pll_id) -{ - struct pll_config pllcfg; +static inline void pll_enable_config_defaults(unsigned int ul_pll_id) { + struct pll_config pllcfg; - if (pll_is_locked(ul_pll_id)) { - return; // Pll already running - } - switch (ul_pll_id) { -#ifdef CONFIG_PLL0_SOURCE - case 0: - pll_enable_source(CONFIG_PLL0_SOURCE); - pll_config_init(&pllcfg, - CONFIG_PLL0_SOURCE, - CONFIG_PLL0_DIV, - CONFIG_PLL0_MUL); - break; -#endif -#ifdef CONFIG_PLL1_SOURCE - case 1: - pll_enable_source(CONFIG_PLL1_SOURCE); - pll_config_init(&pllcfg, - CONFIG_PLL1_SOURCE, - CONFIG_PLL1_DIV, - CONFIG_PLL1_MUL); - break; -#endif - default: - Assert(false); - break; - } - pll_enable(&pllcfg, ul_pll_id); - while (!pll_is_locked(ul_pll_id)); + if (pll_is_locked(ul_pll_id)) return; // Pll already running + + switch (ul_pll_id) { + #ifdef CONFIG_PLL0_SOURCE + case 0: + pll_enable_source(CONFIG_PLL0_SOURCE); + pll_config_init(&pllcfg, + CONFIG_PLL0_SOURCE, + CONFIG_PLL0_DIV, + CONFIG_PLL0_MUL); + break; + #endif + #ifdef CONFIG_PLL1_SOURCE + case 1: + pll_enable_source(CONFIG_PLL1_SOURCE); + pll_config_init(&pllcfg, + CONFIG_PLL1_SOURCE, + CONFIG_PLL1_DIV, + CONFIG_PLL1_MUL); + break; + #endif + default: + Assert(false); + break; + } + pll_enable(&pllcfg, ul_pll_id); + while (!pll_is_locked(ul_pll_id)); } /** @@ -264,15 +249,12 @@ static inline void pll_enable_config_defaults(unsigned int ul_pll_id) * \retval STATUS_OK The PLL is now locked. * \retval ERR_TIMEOUT Timed out waiting for PLL to become locked. */ -static inline int pll_wait_for_lock(unsigned int pll_id) -{ - Assert(pll_id < NR_PLLS); +static inline int pll_wait_for_lock(unsigned int pll_id) { + Assert(pll_id < NR_PLLS); - while (!pll_is_locked(pll_id)) { - /* Do nothing */ - } + while (!pll_is_locked(pll_id)) { /* Do nothing */ } - return 0; + return 0; } //! @} diff --git a/Marlin/src/HAL/DUE/usb/preprocessor.h b/Marlin/src/HAL/DUE/usb/preprocessor.h index c12d01cb64..fe796c4fb8 100644 --- a/Marlin/src/HAL/DUE/usb/preprocessor.h +++ b/Marlin/src/HAL/DUE/usb/preprocessor.h @@ -51,5 +51,4 @@ #include "stringz.h" #include "mrepeat.h" - #endif // _PREPROCESSOR_H_ diff --git a/Marlin/src/HAL/DUE/usb/sbc_protocol.h b/Marlin/src/HAL/DUE/usb/sbc_protocol.h index ab845739fd..cdd4caa3cd 100644 --- a/Marlin/src/HAL/DUE/usb/sbc_protocol.h +++ b/Marlin/src/HAL/DUE/usb/sbc_protocol.h @@ -57,7 +57,6 @@ #ifndef _SBC_PROTOCOL_H_ #define _SBC_PROTOCOL_H_ - /** * \ingroup usb_msc_protocol * \defgroup usb_sbc_protocol SCSI Block Commands protocol definitions @@ -81,82 +80,81 @@ //@{ enum scsi_sbc_mode { - SCSI_MS_MODE_RW_ERR_RECOV = 0x01, //!< Read-Write Error Recovery mode page - SCSI_MS_MODE_FORMAT_DEVICE = 0x03, //!< Format Device mode page - SCSI_MS_MODE_FLEXIBLE_DISK = 0x05, //!< Flexible Disk mode page - SCSI_MS_MODE_CACHING = 0x08, //!< Caching mode page + SCSI_MS_MODE_RW_ERR_RECOV = 0x01, //!< Read-Write Error Recovery mode page + SCSI_MS_MODE_FORMAT_DEVICE = 0x03, //!< Format Device mode page + SCSI_MS_MODE_FLEXIBLE_DISK = 0x05, //!< Flexible Disk mode page + SCSI_MS_MODE_CACHING = 0x08, //!< Caching mode page }; - //! \name SBC-2 Device-Specific Parameter //@{ -#define SCSI_MS_SBC_WP 0x80 //!< Write Protected -#define SCSI_MS_SBC_DPOFUA 0x10 //!< DPO and FUA supported +#define SCSI_MS_SBC_WP 0x80 //!< Write Protected +#define SCSI_MS_SBC_DPOFUA 0x10 //!< DPO and FUA supported //@} /** * \brief SBC-2 Short LBA mode parameter block descriptor */ struct sbc_slba_block_desc { - be32_t nr_blocks; //!< Number of Blocks - be32_t block_len; //!< Block Length -#define SBC_SLBA_BLOCK_LEN_MASK 0x00FFFFFFU //!< Mask reserved bits + be32_t nr_blocks; //!< Number of Blocks + be32_t block_len; //!< Block Length +#define SBC_SLBA_BLOCK_LEN_MASK 0x00FFFFFFU //!< Mask reserved bits }; /** * \brief SBC-2 Caching mode page */ struct sbc_caching_mode_page { - uint8_t page_code; - uint8_t page_length; - uint8_t flags2; -#define SBC_MP_CACHE_IC (1 << 7) //!< Initiator Control -#define SBC_MP_CACHE_ABPF (1 << 6) //!< Abort Pre-Fetch -#define SBC_MP_CACHE_CAP (1 << 5) //!< Catching Analysis Permitted -#define SBC_MP_CACHE_DISC (1 << 4) //!< Discontinuity -#define SBC_MP_CACHE_SIZE (1 << 3) //!< Size enable -#define SBC_MP_CACHE_WCE (1 << 2) //!< Write back Cache Enable -#define SBC_MP_CACHE_MF (1 << 1) //!< Multiplication Factor -#define SBC_MP_CACHE_RCD (1 << 0) //!< Read Cache Disable - uint8_t retention; - be16_t dis_pf_transfer_len; - be16_t min_prefetch; - be16_t max_prefetch; - be16_t max_prefetch_ceil; - uint8_t flags12; -#define SBC_MP_CACHE_FSW (1 << 7) //!< Force Sequential Write -#define SBC_MP_CACHE_LBCSS (1 << 6) //!< Logical Blk Cache Seg Sz -#define SBC_MP_CACHE_DRA (1 << 5) //!< Disable Read-Ahead -#define SBC_MP_CACHE_NV_DIS (1 << 0) //!< Non-Volatile Cache Disable - uint8_t nr_cache_segments; - be16_t cache_segment_size; - uint8_t reserved[4]; + uint8_t page_code; + uint8_t page_length; + uint8_t flags2; +#define SBC_MP_CACHE_IC (1 << 7) //!< Initiator Control +#define SBC_MP_CACHE_ABPF (1 << 6) //!< Abort Pre-Fetch +#define SBC_MP_CACHE_CAP (1 << 5) //!< Catching Analysis Permitted +#define SBC_MP_CACHE_DISC (1 << 4) //!< Discontinuity +#define SBC_MP_CACHE_SIZE (1 << 3) //!< Size enable +#define SBC_MP_CACHE_WCE (1 << 2) //!< Write back Cache Enable +#define SBC_MP_CACHE_MF (1 << 1) //!< Multiplication Factor +#define SBC_MP_CACHE_RCD (1 << 0) //!< Read Cache Disable + uint8_t retention; + be16_t dis_pf_transfer_len; + be16_t min_prefetch; + be16_t max_prefetch; + be16_t max_prefetch_ceil; + uint8_t flags12; +#define SBC_MP_CACHE_FSW (1 << 7) //!< Force Sequential Write +#define SBC_MP_CACHE_LBCSS (1 << 6) //!< Logical Blk Cache Seg Sz +#define SBC_MP_CACHE_DRA (1 << 5) //!< Disable Read-Ahead +#define SBC_MP_CACHE_NV_DIS (1 << 0) //!< Non-Volatile Cache Disable + uint8_t nr_cache_segments; + be16_t cache_segment_size; + uint8_t reserved[4]; }; /** * \brief SBC-2 Read-Write Error Recovery mode page */ struct sbc_rdwr_error_recovery_mode_page { - uint8_t page_code; - uint8_t page_length; -#define SPC_MP_RW_ERR_RECOV_PAGE_LENGTH 0x0A - uint8_t flags1; -#define SBC_MP_RW_ERR_RECOV_AWRE (1 << 7) -#define SBC_MP_RW_ERR_RECOV_ARRE (1 << 6) -#define SBC_MP_RW_ERR_RECOV_TB (1 << 5) -#define SBC_MP_RW_ERR_RECOV_RC (1 << 4) -#define SBC_MP_RW_ERR_RECOV_ERR (1 << 3) -#define SBC_MP_RW_ERR_RECOV_PER (1 << 2) -#define SBC_MP_RW_ERR_RECOV_DTE (1 << 1) -#define SBC_MP_RW_ERR_RECOV_DCR (1 << 0) - uint8_t read_retry_count; - uint8_t correction_span; - uint8_t head_offset_count; - uint8_t data_strobe_offset_count; - uint8_t flags2; - uint8_t write_retry_count; - uint8_t flags3; - be16_t recovery_time_limit; + uint8_t page_code; + uint8_t page_length; + #define SPC_MP_RW_ERR_RECOV_PAGE_LENGTH 0x0A + uint8_t flags1; + #define SBC_MP_RW_ERR_RECOV_AWRE (1 << 7) + #define SBC_MP_RW_ERR_RECOV_ARRE (1 << 6) + #define SBC_MP_RW_ERR_RECOV_TB (1 << 5) + #define SBC_MP_RW_ERR_RECOV_RC (1 << 4) + #define SBC_MP_RW_ERR_RECOV_ERR (1 << 3) + #define SBC_MP_RW_ERR_RECOV_PER (1 << 2) + #define SBC_MP_RW_ERR_RECOV_DTE (1 << 1) + #define SBC_MP_RW_ERR_RECOV_DCR (1 << 0) + uint8_t read_retry_count; + uint8_t correction_span; + uint8_t head_offset_count; + uint8_t data_strobe_offset_count; + uint8_t flags2; + uint8_t write_retry_count; + uint8_t flags3; + be16_t recovery_time_limit; }; //@} @@ -164,8 +162,8 @@ struct sbc_rdwr_error_recovery_mode_page { * \brief SBC-2 READ CAPACITY (10) parameter data */ struct sbc_read_capacity10_data { - be32_t max_lba; //!< LBA of last logical block - be32_t block_len; //!< Number of bytes in the last logical block + be32_t max_lba; //!< LBA of last logical block + be32_t block_len; //!< Number of bytes in the last logical block }; //@} 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 ea2936359d..74cfd9b39b 100644 --- a/Marlin/src/HAL/DUE/usb/sd_mmc_spi_mem.cpp +++ b/Marlin/src/HAL/DUE/usb/sd_mmc_spi_mem.cpp @@ -6,11 +6,11 @@ #include "../../../inc/MarlinConfig.h" -#if ENABLED(SDSUPPORT) +#if HAS_MEDIA #include "../../../sd/cardreader.h" extern "C" { -#include "sd_mmc_spi_mem.h" + #include "sd_mmc_spi_mem.h" } #define SD_MMC_BLOCK_SIZE 512 @@ -18,30 +18,29 @@ extern "C" { void sd_mmc_spi_mem_init() { } -Ctrl_status sd_mmc_spi_test_unit_ready() { - #ifdef 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; -} - -// 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; - *nb_sector = card.getSd2Card().cardSize() - 1; - return CTRL_GOOD; +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 (!IS_SD_INSERTED() || IS_SD_PRINTING() || IS_SD_FILE_OPEN() || !card.isMounted()); +bool sd_mmc_spi_removal() { return !media_ready(); } + +Ctrl_status sd_mmc_spi_test_unit_ready() { + #if ENABLED(DISABLE_DUE_SD_MMC) + return CTRL_NO_PRESENT; + #endif + 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 (sd_mmc_spi_removal()) return CTRL_NO_PRESENT; + *nb_sector = card.diskIODriver()->cardSize() - 1; + return CTRL_GOOD; } #if ACCESS_USB == true @@ -58,79 +57,79 @@ 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 { char buffer[80]; sprintf_P(buffer, PSTR("SDRD: %d @ 0x%08x\n"), nb_sector, addr); - PORT_REDIRECT(0); + PORT_REDIRECT(SERIAL_PORTMASK(0)); SERIAL_ECHO(buffer); } #endif // Start reading - if (!card.getSd2Card().readStart(addr)) + if (!card.diskIODriver()->readStart(addr)) return CTRL_FAIL; // For each specified sector while (nb_sector--) { // Read a sector - card.getSd2Card().readData(sector_buf); + card.diskIODriver()->readData(sector_buf); // RAM -> USB - if (!udi_msc_trans_block(true, sector_buf, SD_MMC_BLOCK_SIZE, NULL)) { - card.getSd2Card().readStop(); + if (!udi_msc_trans_block(true, sector_buf, SD_MMC_BLOCK_SIZE, nullptr)) { + card.diskIODriver()->readStop(); return CTRL_FAIL; } } // Stop reading - card.getSd2Card().readStop(); + card.diskIODriver()->readStop(); // Done return CTRL_GOOD; } 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 { char buffer[80]; sprintf_P(buffer, PSTR("SDWR: %d @ 0x%08x\n"), nb_sector, addr); - PORT_REDIRECT(0); + PORT_REDIRECT(SERIAL_PORTMASK(0)); SERIAL_ECHO(buffer); } #endif - if (!card.getSd2Card().writeStart(addr, nb_sector)) + if (!card.diskIODriver()->writeStart(addr, nb_sector)) return CTRL_FAIL; // For each specified sector while (nb_sector--) { // USB -> RAM - if (!udi_msc_trans_block(false, sector_buf, SD_MMC_BLOCK_SIZE, NULL)) { - card.getSd2Card().writeStop(); + if (!udi_msc_trans_block(false, sector_buf, SD_MMC_BLOCK_SIZE, nullptr)) { + card.diskIODriver()->writeStop(); return CTRL_FAIL; } // Write a sector - card.getSd2Card().writeData(sector_buf); + card.diskIODriver()->writeData(sector_buf); } // Stop writing - card.getSd2Card().writeStop(); + card.diskIODriver()->writeStop(); // Done return CTRL_GOOD; @@ -138,5 +137,5 @@ Ctrl_status sd_mmc_spi_usb_write_10(uint32_t addr, uint16_t nb_sector) { #endif // ACCESS_USB == true -#endif // SDSUPPORT +#endif // HAS_MEDIA #endif // ARDUINO_ARCH_SAM 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 d77e4f9523..464d106e52 100644 --- a/Marlin/src/HAL/DUE/usb/sd_mmc_spi_mem.h +++ b/Marlin/src/HAL/DUE/usb/sd_mmc_spi_mem.h @@ -45,7 +45,6 @@ * Support and FAQ: visit Atmel Support */ - #ifndef _SD_MMC_SPI_MEM_H_ #define _SD_MMC_SPI_MEM_H_ @@ -63,22 +62,19 @@ #error sd_mmc_spi_mem.h is #included although SD_MMC_SPI_MEM is disabled #endif - #include "ctrl_access.h" - //_____ D E F I N I T I O N S ______________________________________________ #define SD_MMC_REMOVED 0 #define SD_MMC_INSERTED 1 #define SD_MMC_REMOVING 2 - -//---- CONTROL FONCTIONS ---- +//---- CONTROL FUNCTIONS ---- //! //! @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. @@ -91,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. @@ -102,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 * @@ -113,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. @@ -124,17 +120,16 @@ 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 FONCTIONS ---- +//---- ACCESS DATA FUNCTIONS ---- #if ACCESS_USB == true // Standard functions for open in read/write mode the device @@ -152,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 //! @@ -166,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/spc_protocol.h b/Marlin/src/HAL/DUE/usb/spc_protocol.h index d67cc5c788..808c388f4f 100644 --- a/Marlin/src/HAL/DUE/usb/spc_protocol.h +++ b/Marlin/src/HAL/DUE/usb/spc_protocol.h @@ -59,23 +59,23 @@ //! \name SCSI commands defined by SPC-2 //@{ -#define SPC_TEST_UNIT_READY 0x00 -#define SPC_REQUEST_SENSE 0x03 -#define SPC_INQUIRY 0x12 -#define SPC_MODE_SELECT6 0x15 -#define SPC_MODE_SENSE6 0x1A -#define SPC_SEND_DIAGNOSTIC 0x1D -#define SPC_PREVENT_ALLOW_MEDIUM_REMOVAL 0x1E -#define SPC_MODE_SENSE10 0x5A -#define SPC_REPORT_LUNS 0xA0 +#define SPC_TEST_UNIT_READY 0x00 +#define SPC_REQUEST_SENSE 0x03 +#define SPC_INQUIRY 0x12 +#define SPC_MODE_SELECT6 0x15 +#define SPC_MODE_SENSE6 0x1A +#define SPC_SEND_DIAGNOSTIC 0x1D +#define SPC_PREVENT_ALLOW_MEDIUM_REMOVAL 0x1E +#define SPC_MODE_SENSE10 0x5A +#define SPC_REPORT_LUNS 0xA0 //@} //! \brief May be set in byte 0 of the INQUIRY CDB //@{ //! Enable Vital Product Data -#define SCSI_INQ_REQ_EVPD 0x01 +#define SCSI_INQ_REQ_EVPD 0x01 //! Command Support Data specified by the PAGE OR OPERATION CODE field -#define SCSI_INQ_REQ_CMDT 0x02 +#define SCSI_INQ_REQ_CMDT 0x02 //@} COMPILER_PACK_SET(1) @@ -84,110 +84,110 @@ COMPILER_PACK_SET(1) * \brief SCSI Standard Inquiry data structure */ struct scsi_inquiry_data { - uint8_t pq_pdt; //!< Peripheral Qual / Peripheral Dev Type -#define SCSI_INQ_PQ_CONNECTED 0x00 //!< Peripheral connected -#define SCSI_INQ_PQ_NOT_CONN 0x20 //!< Peripheral not connected -#define SCSI_INQ_PQ_NOT_SUPP 0x60 //!< Peripheral not supported -#define SCSI_INQ_DT_DIR_ACCESS 0x00 //!< Direct Access (SBC) -#define SCSI_INQ_DT_SEQ_ACCESS 0x01 //!< Sequential Access -#define SCSI_INQ_DT_PRINTER 0x02 //!< Printer -#define SCSI_INQ_DT_PROCESSOR 0x03 //!< Processor device -#define SCSI_INQ_DT_WRITE_ONCE 0x04 //!< Write-once device -#define SCSI_INQ_DT_CD_DVD 0x05 //!< CD/DVD device -#define SCSI_INQ_DT_OPTICAL 0x07 //!< Optical Memory -#define SCSI_INQ_DT_MC 0x08 //!< Medium Changer -#define SCSI_INQ_DT_ARRAY 0x0C //!< Storage Array Controller -#define SCSI_INQ_DT_ENCLOSURE 0x0D //!< Enclosure Services -#define SCSI_INQ_DT_RBC 0x0E //!< Simplified Direct Access -#define SCSI_INQ_DT_OCRW 0x0F //!< Optical card reader/writer -#define SCSI_INQ_DT_BCC 0x10 //!< Bridge Controller Commands -#define SCSI_INQ_DT_OSD 0x11 //!< Object-based Storage -#define SCSI_INQ_DT_NONE 0x1F //!< No Peripheral - uint8_t flags1; //!< Flags (byte 1) -#define SCSI_INQ_RMB 0x80 //!< Removable Medium - uint8_t version; //!< Version -#define SCSI_INQ_VER_NONE 0x00 //!< No standards conformance -#define SCSI_INQ_VER_SPC 0x03 //!< SCSI Primary Commands (link to SBC) -#define SCSI_INQ_VER_SPC2 0x04 //!< SCSI Primary Commands - 2 (link to SBC-2) -#define SCSI_INQ_VER_SPC3 0x05 //!< SCSI Primary Commands - 3 (link to SBC-2) -#define SCSI_INQ_VER_SPC4 0x06 //!< SCSI Primary Commands - 4 (link to SBC-3) - uint8_t flags3; //!< Flags (byte 3) -#define SCSI_INQ_NORMACA 0x20 //!< Normal ACA Supported -#define SCSI_INQ_HISUP 0x10 //!< Hierarchal LUN addressing -#define SCSI_INQ_RSP_SPC2 0x02 //!< SPC-2 / SPC-3 response format - uint8_t addl_len; //!< Additional Length (n-4) -#define SCSI_INQ_ADDL_LEN(tot) ((tot)-5) //!< Total length is \a tot - uint8_t flags5; //!< Flags (byte 5) -#define SCSI_INQ_SCCS 0x80 - uint8_t flags6; //!< Flags (byte 6) -#define SCSI_INQ_BQUE 0x80 -#define SCSI_INQ_ENCSERV 0x40 -#define SCSI_INQ_MULTIP 0x10 -#define SCSI_INQ_MCHGR 0x08 -#define SCSI_INQ_ADDR16 0x01 - uint8_t flags7; //!< Flags (byte 7) -#define SCSI_INQ_WBUS16 0x20 -#define SCSI_INQ_SYNC 0x10 -#define SCSI_INQ_LINKED 0x08 -#define SCSI_INQ_CMDQUE 0x02 - uint8_t vendor_id[8]; //!< T10 Vendor Identification - uint8_t product_id[16]; //!< Product Identification - uint8_t product_rev[4]; //!< Product Revision Level + uint8_t pq_pdt; //!< Peripheral Qual / Peripheral Dev Type + #define SCSI_INQ_PQ_CONNECTED 0x00 //!< Peripheral connected + #define SCSI_INQ_PQ_NOT_CONN 0x20 //!< Peripheral not connected + #define SCSI_INQ_PQ_NOT_SUPP 0x60 //!< Peripheral not supported + #define SCSI_INQ_DT_DIR_ACCESS 0x00 //!< Direct Access (SBC) + #define SCSI_INQ_DT_SEQ_ACCESS 0x01 //!< Sequential Access + #define SCSI_INQ_DT_PRINTER 0x02 //!< Printer + #define SCSI_INQ_DT_PROCESSOR 0x03 //!< Processor device + #define SCSI_INQ_DT_WRITE_ONCE 0x04 //!< Write-once device + #define SCSI_INQ_DT_CD_DVD 0x05 //!< CD/DVD device + #define SCSI_INQ_DT_OPTICAL 0x07 //!< Optical Memory + #define SCSI_INQ_DT_MC 0x08 //!< Medium Changer + #define SCSI_INQ_DT_ARRAY 0x0C //!< Storage Array Controller + #define SCSI_INQ_DT_ENCLOSURE 0x0D //!< Enclosure Services + #define SCSI_INQ_DT_RBC 0x0E //!< Simplified Direct Access + #define SCSI_INQ_DT_OCRW 0x0F //!< Optical card reader/writer + #define SCSI_INQ_DT_BCC 0x10 //!< Bridge Controller Commands + #define SCSI_INQ_DT_OSD 0x11 //!< Object-based Storage + #define SCSI_INQ_DT_NONE 0x1F //!< No Peripheral + uint8_t flags1; //!< Flags (byte 1) + #define SCSI_INQ_RMB 0x80 //!< Removable Medium + uint8_t version; //!< Version + #define SCSI_INQ_VER_NONE 0x00 //!< No standards conformance + #define SCSI_INQ_VER_SPC 0x03 //!< SCSI Primary Commands (link to SBC) + #define SCSI_INQ_VER_SPC2 0x04 //!< SCSI Primary Commands - 2 (link to SBC-2) + #define SCSI_INQ_VER_SPC3 0x05 //!< SCSI Primary Commands - 3 (link to SBC-2) + #define SCSI_INQ_VER_SPC4 0x06 //!< SCSI Primary Commands - 4 (link to SBC-3) + uint8_t flags3; //!< Flags (byte 3) + #define SCSI_INQ_NORMACA 0x20 //!< Normal ACA Supported + #define SCSI_INQ_HISUP 0x10 //!< Hierarchal LUN addressing + #define SCSI_INQ_RSP_SPC2 0x02 //!< SPC-2 / SPC-3 response format + uint8_t addl_len; //!< Additional Length (n-4) + #define SCSI_INQ_ADDL_LEN(tot) ((tot)-5) //!< Total length is \a tot + uint8_t flags5; //!< Flags (byte 5) + #define SCSI_INQ_SCCS 0x80 + uint8_t flags6; //!< Flags (byte 6) + #define SCSI_INQ_BQUE 0x80 + #define SCSI_INQ_ENCSERV 0x40 + #define SCSI_INQ_MULTIP 0x10 + #define SCSI_INQ_MCHGR 0x08 + #define SCSI_INQ_ADDR16 0x01 + uint8_t flags7; //!< Flags (byte 7) + #define SCSI_INQ_WBUS16 0x20 + #define SCSI_INQ_SYNC 0x10 + #define SCSI_INQ_LINKED 0x08 + #define SCSI_INQ_CMDQUE 0x02 + uint8_t vendor_id[8]; //!< T10 Vendor Identification + uint8_t product_id[16]; //!< Product Identification + uint8_t product_rev[4]; //!< Product Revision Level }; /** * \brief SCSI Standard Request sense data structure */ struct scsi_request_sense_data { - /* 1st byte: REQUEST SENSE response flags*/ - uint8_t valid_reponse_code; -#define SCSI_SENSE_VALID 0x80 //!< Indicates the INFORMATION field contains valid information -#define SCSI_SENSE_RESPONSE_CODE_MASK 0x7F -#define SCSI_SENSE_CURRENT 0x70 //!< Response code 70h (current errors) -#define SCSI_SENSE_DEFERRED 0x71 + /* 1st byte: REQUEST SENSE response flags*/ + uint8_t valid_reponse_code; + #define SCSI_SENSE_VALID 0x80 //!< Indicates the INFORMATION field contains valid information + #define SCSI_SENSE_RESPONSE_CODE_MASK 0x7F + #define SCSI_SENSE_CURRENT 0x70 //!< Response code 70h (current errors) + #define SCSI_SENSE_DEFERRED 0x71 - /* 2nd byte */ - uint8_t obsolete; + /* 2nd byte */ + uint8_t obsolete; - /* 3rd byte */ - uint8_t sense_flag_key; -#define SCSI_SENSE_FILEMARK 0x80 //!< Indicates that the current command has read a filemark or setmark. -#define SCSI_SENSE_EOM 0x40 //!< Indicates that an end-of-medium condition exists. -#define SCSI_SENSE_ILI 0x20 //!< Indicates that the requested logical block length did not match the logical block length of the data on the medium. -#define SCSI_SENSE_RESERVED 0x10 //!< Reserved -#define SCSI_SENSE_KEY(x) (x&0x0F) //!< Sense Key + /* 3rd byte */ + uint8_t sense_flag_key; + #define SCSI_SENSE_FILEMARK 0x80 //!< Indicates that the current command has read a filemark or setmark. + #define SCSI_SENSE_EOM 0x40 //!< Indicates that an end-of-medium condition exists. + #define SCSI_SENSE_ILI 0x20 //!< Indicates that the requested logical block length did not match the logical block length of the data on the medium. + #define SCSI_SENSE_RESERVED 0x10 //!< Reserved + #define SCSI_SENSE_KEY(x) (x&0x0F) //!< Sense Key - /* 4th to 7th bytes - INFORMATION field */ - uint8_t information[4]; + /* 4th to 7th bytes - INFORMATION field */ + uint8_t information[4]; - /* 8th byte - ADDITIONAL SENSE LENGTH field */ - uint8_t AddSenseLen; -#define SCSI_SENSE_ADDL_LEN(total_len) ((total_len) - 8) + /* 8th byte - ADDITIONAL SENSE LENGTH field */ + uint8_t AddSenseLen; + #define SCSI_SENSE_ADDL_LEN(total_len) ((total_len) - 8) - /* 9th to 12th byte - COMMAND-SPECIFIC INFORMATION field */ - uint8_t CmdSpecINFO[4]; + /* 9th to 12th byte - COMMAND-SPECIFIC INFORMATION field */ + uint8_t CmdSpecINFO[4]; - /* 13th byte - ADDITIONAL SENSE CODE field */ - uint8_t AddSenseCode; + /* 13th byte - ADDITIONAL SENSE CODE field */ + uint8_t AddSenseCode; - /* 14th byte - ADDITIONAL SENSE CODE QUALIFIER field */ - uint8_t AddSnsCodeQlfr; + /* 14th byte - ADDITIONAL SENSE CODE QUALIFIER field */ + uint8_t AddSnsCodeQlfr; - /* 15th byte - FIELD REPLACEABLE UNIT CODE field */ - uint8_t FldReplUnitCode; + /* 15th byte - FIELD REPLACEABLE UNIT CODE field */ + uint8_t FldReplUnitCode; - /* 16th byte */ - uint8_t SenseKeySpec[3]; -#define SCSI_SENSE_SKSV 0x80 //!< Indicates the SENSE-KEY SPECIFIC field contains valid information + /* 16th byte */ + uint8_t SenseKeySpec[3]; + #define SCSI_SENSE_SKSV 0x80 //!< Indicates the SENSE-KEY SPECIFIC field contains valid information }; COMPILER_PACK_RESET() /* Vital Product Data page codes */ enum scsi_vpd_page_code { - SCSI_VPD_SUPPORTED_PAGES = 0x00, - SCSI_VPD_UNIT_SERIAL_NUMBER = 0x80, - SCSI_VPD_DEVICE_IDENTIFICATION = 0x83, + SCSI_VPD_SUPPORTED_PAGES = 0x00, + SCSI_VPD_UNIT_SERIAL_NUMBER = 0x80, + SCSI_VPD_DEVICE_IDENTIFICATION = 0x83, }; #define SCSI_VPD_HEADER_SIZE 4 @@ -200,37 +200,36 @@ enum scsi_vpd_page_code { #define SCSI_VPD_ID_TYPE_T10 1 - /* Sense keys */ enum scsi_sense_key { - SCSI_SK_NO_SENSE = 0x0, - SCSI_SK_RECOVERED_ERROR = 0x1, - SCSI_SK_NOT_READY = 0x2, - SCSI_SK_MEDIUM_ERROR = 0x3, - SCSI_SK_HARDWARE_ERROR = 0x4, - SCSI_SK_ILLEGAL_REQUEST = 0x5, - SCSI_SK_UNIT_ATTENTION = 0x6, - SCSI_SK_DATA_PROTECT = 0x7, - SCSI_SK_BLANK_CHECK = 0x8, - SCSI_SK_VENDOR_SPECIFIC = 0x9, - SCSI_SK_COPY_ABORTED = 0xA, - SCSI_SK_ABORTED_COMMAND = 0xB, - SCSI_SK_VOLUME_OVERFLOW = 0xD, - SCSI_SK_MISCOMPARE = 0xE, + SCSI_SK_NO_SENSE = 0x0, + SCSI_SK_RECOVERED_ERROR = 0x1, + SCSI_SK_NOT_READY = 0x2, + SCSI_SK_MEDIUM_ERROR = 0x3, + SCSI_SK_HARDWARE_ERROR = 0x4, + SCSI_SK_ILLEGAL_REQUEST = 0x5, + SCSI_SK_UNIT_ATTENTION = 0x6, + SCSI_SK_DATA_PROTECT = 0x7, + SCSI_SK_BLANK_CHECK = 0x8, + SCSI_SK_VENDOR_SPECIFIC = 0x9, + SCSI_SK_COPY_ABORTED = 0xA, + SCSI_SK_ABORTED_COMMAND = 0xB, + SCSI_SK_VOLUME_OVERFLOW = 0xD, + SCSI_SK_MISCOMPARE = 0xE, }; /* Additional Sense Code / Additional Sense Code Qualifier pairs */ enum scsi_asc_ascq { - SCSI_ASC_NO_ADDITIONAL_SENSE_INFO = 0x0000, - SCSI_ASC_LU_NOT_READY_REBUILD_IN_PROGRESS = 0x0405, - SCSI_ASC_WRITE_ERROR = 0x0C00, - SCSI_ASC_UNRECOVERED_READ_ERROR = 0x1100, - SCSI_ASC_INVALID_COMMAND_OPERATION_CODE = 0x2000, - SCSI_ASC_INVALID_FIELD_IN_CDB = 0x2400, - SCSI_ASC_WRITE_PROTECTED = 0x2700, - SCSI_ASC_NOT_READY_TO_READY_CHANGE = 0x2800, - SCSI_ASC_MEDIUM_NOT_PRESENT = 0x3A00, - SCSI_ASC_INTERNAL_TARGET_FAILURE = 0x4400, + SCSI_ASC_NO_ADDITIONAL_SENSE_INFO = 0x0000, + SCSI_ASC_LU_NOT_READY_REBUILD_IN_PROGRESS = 0x0405, + SCSI_ASC_WRITE_ERROR = 0x0C00, + SCSI_ASC_UNRECOVERED_READ_ERROR = 0x1100, + SCSI_ASC_INVALID_COMMAND_OPERATION_CODE = 0x2000, + SCSI_ASC_INVALID_FIELD_IN_CDB = 0x2400, + SCSI_ASC_WRITE_PROTECTED = 0x2700, + SCSI_ASC_NOT_READY_TO_READY_CHANGE = 0x2800, + SCSI_ASC_MEDIUM_NOT_PRESENT = 0x3A00, + SCSI_ASC_INTERNAL_TARGET_FAILURE = 0x4400, }; /** @@ -240,9 +239,9 @@ enum scsi_asc_ascq { * that are applicable to all SCSI devices. */ enum scsi_spc_mode { - SCSI_MS_MODE_VENDOR_SPEC = 0x00, - SCSI_MS_MODE_INFEXP = 0x1C, // Informational exceptions control page - SCSI_MS_MODE_ALL = 0x3F, + SCSI_MS_MODE_VENDOR_SPEC = 0x00, + SCSI_MS_MODE_INFEXP = 0x1C, // Informational exceptions control page + SCSI_MS_MODE_ALL = 0x3F, }; /** @@ -250,51 +249,45 @@ enum scsi_spc_mode { * See chapter 8.3.8 */ struct spc_control_page_info_execpt { - uint8_t page_code; - uint8_t page_length; -#define SPC_MP_INFEXP_PAGE_LENGTH 0x0A - uint8_t flags1; -#define SPC_MP_INFEXP_PERF (1<<7) //!< Initiator Control -#define SPC_MP_INFEXP_EBF (1<<5) //!< Caching Analysis Permitted -#define SPC_MP_INFEXP_EWASC (1<<4) //!< Discontinuity -#define SPC_MP_INFEXP_DEXCPT (1<<3) //!< Size enable -#define SPC_MP_INFEXP_TEST (1<<2) //!< Writeback Cache Enable -#define SPC_MP_INFEXP_LOGERR (1<<0) //!< Log errors bit - uint8_t mrie; -#define SPC_MP_INFEXP_MRIE_NO_REPORT 0x00 -#define SPC_MP_INFEXP_MRIE_ASYNC_EVENT 0x01 -#define SPC_MP_INFEXP_MRIE_GEN_UNIT 0x02 -#define SPC_MP_INFEXP_MRIE_COND_RECOV_ERROR 0x03 -#define SPC_MP_INFEXP_MRIE_UNCOND_RECOV_ERROR 0x04 -#define SPC_MP_INFEXP_MRIE_NO_SENSE 0x05 -#define SPC_MP_INFEXP_MRIE_ONLY_REPORT 0x06 - be32_t interval_timer; - be32_t report_count; + uint8_t page_code; + uint8_t page_length; + #define SPC_MP_INFEXP_PAGE_LENGTH 0x0A + uint8_t flags1; + #define SPC_MP_INFEXP_PERF (1<<7) //!< Initiator Control + #define SPC_MP_INFEXP_EBF (1<<5) //!< Caching Analysis Permitted + #define SPC_MP_INFEXP_EWASC (1<<4) //!< Discontinuity + #define SPC_MP_INFEXP_DEXCPT (1<<3) //!< Size enable + #define SPC_MP_INFEXP_TEST (1<<2) //!< Writeback Cache Enable + #define SPC_MP_INFEXP_LOGERR (1<<0) //!< Log errors bit + uint8_t mrie; + #define SPC_MP_INFEXP_MRIE_NO_REPORT 0x00 + #define SPC_MP_INFEXP_MRIE_ASYNC_EVENT 0x01 + #define SPC_MP_INFEXP_MRIE_GEN_UNIT 0x02 + #define SPC_MP_INFEXP_MRIE_COND_RECOV_ERROR 0x03 + #define SPC_MP_INFEXP_MRIE_UNCOND_RECOV_ERROR 0x04 + #define SPC_MP_INFEXP_MRIE_NO_SENSE 0x05 + #define SPC_MP_INFEXP_MRIE_ONLY_REPORT 0x06 + be32_t interval_timer; + be32_t report_count; }; - enum scsi_spc_mode_sense_pc { - SCSI_MS_SENSE_PC_CURRENT = 0, - SCSI_MS_SENSE_PC_CHANGEABLE = 1, - SCSI_MS_SENSE_PC_DEFAULT = 2, - SCSI_MS_SENSE_PC_SAVED = 3, + SCSI_MS_SENSE_PC_CURRENT = 0, + SCSI_MS_SENSE_PC_CHANGEABLE = 1, + SCSI_MS_SENSE_PC_DEFAULT = 2, + SCSI_MS_SENSE_PC_SAVED = 3, }; - - -static inline bool scsi_mode_sense_dbd_is_set(const uint8_t * cdb) -{ - return (cdb[1] >> 3) & 1; +static inline bool scsi_mode_sense_dbd_is_set(const uint8_t * cdb) { + return (cdb[1] >> 3) & 1; } -static inline uint8_t scsi_mode_sense_get_page_code(const uint8_t * cdb) -{ - return cdb[2] & 0x3F; +static inline uint8_t scsi_mode_sense_get_page_code(const uint8_t * cdb) { + return cdb[2] & 0x3F; } -static inline uint8_t scsi_mode_sense_get_pc(const uint8_t * cdb) -{ - return cdb[2] >> 6; +static inline uint8_t scsi_mode_sense_get_pc(const uint8_t * cdb) { + return cdb[2] >> 6; } /** @@ -302,10 +295,10 @@ static inline uint8_t scsi_mode_sense_get_pc(const uint8_t * cdb) * SENSE(6) */ struct scsi_mode_param_header6 { - uint8_t mode_data_length; //!< Number of bytes after this - uint8_t medium_type; //!< Medium Type - uint8_t device_specific_parameter; //!< Defined by command set - uint8_t block_descriptor_length; //!< Length of block descriptors + uint8_t mode_data_length; //!< Number of bytes after this + uint8_t medium_type; //!< Medium Type + uint8_t device_specific_parameter; //!< Defined by command set + uint8_t block_descriptor_length; //!< Length of block descriptors }; /** @@ -313,23 +306,23 @@ struct scsi_mode_param_header6 { * SENSE(10) */ struct scsi_mode_param_header10 { - be16_t mode_data_length; //!< Number of bytes after this - uint8_t medium_type; //!< Medium Type - uint8_t device_specific_parameter; //!< Defined by command set - uint8_t flags4; //!< LONGLBA in bit 0 - uint8_t reserved; - be16_t block_descriptor_length; //!< Length of block descriptors + be16_t mode_data_length; //!< Number of bytes after this + uint8_t medium_type; //!< Medium Type + uint8_t device_specific_parameter; //!< Defined by command set + uint8_t flags4; //!< LONGLBA in bit 0 + uint8_t reserved; + be16_t block_descriptor_length; //!< Length of block descriptors }; /** * \brief SCSI Page_0 Mode Page header (SPF not set) */ struct scsi_mode_page_0_header { - uint8_t page_code; -#define SCSI_PAGE_CODE_PS (1 << 7) //!< Parameters Saveable -#define SCSI_PAGE_CODE_SPF (1 << 6) //!< SubPage Format - uint8_t page_length; //!< Number of bytes after this -#define SCSI_MS_PAGE_LEN(total) ((total) - 2) + uint8_t page_code; +#define SCSI_PAGE_CODE_PS (1 << 7) //!< Parameters Saveable +#define SCSI_PAGE_CODE_SPF (1 << 6) //!< SubPage Format + uint8_t page_length; //!< Number of bytes after this +#define SCSI_MS_PAGE_LEN(total) ((total) - 2) }; //@} diff --git a/Marlin/src/HAL/DUE/usb/sysclk.h b/Marlin/src/HAL/DUE/usb/sysclk.h index 16db8c86d3..4001366868 100644 --- a/Marlin/src/HAL/DUE/usb/sysclk.h +++ b/Marlin/src/HAL/DUE/usb/sysclk.h @@ -71,7 +71,7 @@ * \subsection sysclk_quickstart_use_case_1_setup_steps Initialization code * Add to the application initialization code: * \code - sysclk_init(); + sysclk_init(); \endcode * * \subsection sysclk_quickstart_use_case_1_setup_steps_workflow Workflow @@ -82,15 +82,15 @@ * Add or uncomment the following in your conf_clock.h header file, commenting out all other * definitions of the same symbol(s): * \code - #define CONFIG_SYSCLK_SOURCE SYSCLK_SRC_PLLACK + #define CONFIG_SYSCLK_SOURCE SYSCLK_SRC_PLLACK - // Fpll0 = (Fclk * PLL_mul) / PLL_div - #define CONFIG_PLL0_SOURCE PLL_SRC_MAINCK_XTAL - #define CONFIG_PLL0_MUL (84000000UL / BOARD_FREQ_MAINCK_XTAL) - #define CONFIG_PLL0_DIV 1 + // Fpll0 = (Fclk * PLL_mul) / PLL_div + #define CONFIG_PLL0_SOURCE PLL_SRC_MAINCK_XTAL + #define CONFIG_PLL0_MUL (84000000UL / BOARD_FREQ_MAINCK_XTAL) + #define CONFIG_PLL0_DIV 1 - // Fbus = Fsys / BUS_div - #define CONFIG_SYSCLK_PRES SYSCLK_PRES_1 + // Fbus = Fsys / BUS_div + #define CONFIG_SYSCLK_PRES SYSCLK_PRES_1 \endcode * * \subsection sysclk_quickstart_use_case_1_example_workflow Workflow @@ -100,14 +100,14 @@ * \code #define CONFIG_PLL0_SOURCE PLL_SRC_MAINCK_XTAL \endcode * -# Configure the PLL module to multiply the external fast crystal oscillator frequency up to 84MHz: * \code - #define CONFIG_PLL0_MUL (84000000UL / BOARD_FREQ_MAINCK_XTAL) - #define CONFIG_PLL0_DIV 1 + #define CONFIG_PLL0_MUL (84000000UL / BOARD_FREQ_MAINCK_XTAL) + #define CONFIG_PLL0_DIV 1 \endcode * \note For user boards, \c BOARD_FREQ_MAINCK_XTAL should be defined in the board \c conf_board.h configuration * file as the frequency of the fast crystal attached to the microcontroller. * -# Configure the main clock to run at the full 84MHz, disable scaling of the main system clock speed: * \code - #define CONFIG_SYSCLK_PRES SYSCLK_PRES_1 + #define CONFIG_SYSCLK_PRES SYSCLK_PRES_1 \endcode * \note Some dividers are powers of two, while others are integer division factors. Refer to the * formulas in the conf_clock.h template commented above each division define. @@ -136,7 +136,7 @@ extern "C" { * initialization. */ #ifndef CONFIG_SYSCLK_SOURCE -# define CONFIG_SYSCLK_SOURCE SYSCLK_SRC_MAINCK_4M_RC + #define CONFIG_SYSCLK_SOURCE SYSCLK_SRC_MAINCK_4M_RC #endif /** * \def CONFIG_SYSCLK_PRES @@ -149,7 +149,7 @@ extern "C" { * after initialization. */ #ifndef CONFIG_SYSCLK_PRES -# define CONFIG_SYSCLK_PRES 0 + #define CONFIG_SYSCLK_PRES 0 #endif //@} @@ -197,7 +197,7 @@ extern "C" { * USB is not required. */ #ifdef __DOXYGEN__ -# define CONFIG_USBCLK_SOURCE + #define CONFIG_USBCLK_SOURCE #endif /** @@ -209,10 +209,9 @@ extern "C" { * defined. */ #ifdef __DOXYGEN__ -# define CONFIG_USBCLK_DIV + #define CONFIG_USBCLK_DIV #endif - extern void sysclk_enable_usb(void); extern void sysclk_disable_usb(void); diff --git a/Marlin/src/HAL/DUE/usb/udc.c b/Marlin/src/HAL/DUE/usb/udc.c index 60bf0cfff3..f6a4243d1d 100644 --- a/Marlin/src/HAL/DUE/usb/udc.c +++ b/Marlin/src/HAL/DUE/usb/udc.c @@ -83,7 +83,6 @@ static usb_iface_desc_t UDC_DESC_STORAGE *udc_ptr_iface; //! @} - //! \name Internal structure to store the USB device main strings //! @{ diff --git a/Marlin/src/HAL/DUE/usb/udc.h b/Marlin/src/HAL/DUE/usb/udc.h index 8d92eb5c03..e8c0e7fbea 100644 --- a/Marlin/src/HAL/DUE/usb/udc.h +++ b/Marlin/src/HAL/DUE/usb/udc.h @@ -144,15 +144,15 @@ extern "C" { * \code #define USB_DEVICE_ATTACH_AUTO_DISABLE \endcode * User C file contains: * \code - // Authorize VBUS monitoring - if (!udc_include_vbus_monitoring()) { - // Implement custom VBUS monitoring via GPIO or other - } - Event_VBUS_present() // VBUS interrupt or GPIO interrupt or other - { - // Attach USB Device - udc_attach(); - } + // Authorize VBUS monitoring + if (!udc_include_vbus_monitoring()) { + // Implement custom VBUS monitoring via GPIO or other + } + Event_VBUS_present() // VBUS interrupt or GPIO interrupt or other + { + // Attach USB Device + udc_attach(); + } \endcode * * - Case of battery charging. conf_usb.h file contains define @@ -160,21 +160,20 @@ extern "C" { * \code #define USB_DEVICE_ATTACH_AUTO_DISABLE \endcode * User C file contains: * \code - Event VBUS present() // VBUS interrupt or GPIO interrupt or .. - { - // Authorize battery charging, but wait key press to start USB. - } - Event Key press() - { - // Stop batteries charging - // Start USB - udc_attach(); - } + Event VBUS present() // VBUS interrupt or GPIO interrupt or .. + { + // Authorize battery charging, but wait key press to start USB. + } + Event Key press() + { + // Stop batteries charging + // Start USB + udc_attach(); + } \endcode */ -static inline bool udc_include_vbus_monitoring(void) -{ - return udd_include_vbus_monitoring(); +static inline bool udc_include_vbus_monitoring(void) { + return udd_include_vbus_monitoring(); } /*! \brief Start the USB Device stack @@ -192,32 +191,26 @@ void udc_stop(void); * then it will attach device when an acceptable Vbus * level from the host is detected. */ -static inline void udc_attach(void) -{ - udd_attach(); +static inline void udc_attach(void) { + udd_attach(); } - /** * \brief Detaches the device from the bus * * The driver must remove pull-up on USB line D- or D+. */ -static inline void udc_detach(void) -{ - udd_detach(); +static inline void udc_detach(void) { + udd_detach(); } - /*! \brief The USB driver sends a resume signal called \e "Upstream Resume" * This is authorized only when the remote wakeup feature is enabled by host. */ -static inline void udc_remotewakeup(void) -{ - udd_send_remotewakeup(); +static inline void udc_remotewakeup(void) { + udd_send_remotewakeup(); } - /** * \brief Returns a pointer on the current interface descriptor * @@ -236,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 @@ -296,23 +289,23 @@ usb_iface_desc_t UDC_DESC_STORAGE *udc_get_interface_desc(void); * * for AVR and SAM3/4 devices, add to the initialization code: * \code - sysclk_init(); - irq_initialize_vectors(); - cpu_irq_enable(); - board_init(); - sleepmgr_init(); // Optional + sysclk_init(); + irq_initialize_vectors(); + cpu_irq_enable(); + board_init(); + sleepmgr_init(); // Optional \endcode * * For SAMD devices, add to the initialization code: * \code - system_init(); - irq_initialize_vectors(); - cpu_irq_enable(); - sleepmgr_init(); // Optional + system_init(); + irq_initialize_vectors(); + cpu_irq_enable(); + sleepmgr_init(); // Optional \endcode * Add to the main IDLE loop: * \code - sleepmgr_enter_sleep(); // Optional + sleepmgr_enter_sleep(); // Optional \endcode * */ @@ -324,20 +317,20 @@ usb_iface_desc_t UDC_DESC_STORAGE *udc_get_interface_desc(void); * * Content of conf_usb.h: * \code - #define USB_DEVICE_VENDOR_ID 0x03EB - #define USB_DEVICE_PRODUCT_ID 0xXXXX - #define USB_DEVICE_MAJOR_VERSION 1 - #define USB_DEVICE_MINOR_VERSION 0 - #define USB_DEVICE_POWER 100 - #define USB_DEVICE_ATTR USB_CONFIG_ATTR_BUS_POWERED + #define USB_DEVICE_VENDOR_ID 0x03EB + #define USB_DEVICE_PRODUCT_ID 0xXXXX + #define USB_DEVICE_MAJOR_VERSION 1 + #define USB_DEVICE_MINOR_VERSION 0 + #define USB_DEVICE_POWER 100 + #define USB_DEVICE_ATTR USB_CONFIG_ATTR_BUS_POWERED \endcode * * Add to application C-file: * \code - void usb_init(void) - { - udc_start(); - } + void usb_init(void) + { + udc_start(); + } \endcode */ @@ -349,17 +342,17 @@ usb_iface_desc_t UDC_DESC_STORAGE *udc_get_interface_desc(void); * -# Ensure that conf_usb.h is available and contains the following configuration * which is the main USB device configuration: * - \code // Vendor ID provided by USB org (ATMEL 0x03EB) - #define USB_DEVICE_VENDOR_ID 0x03EB // Type Word - // Product ID (Atmel PID referenced in usb_atmel.h) - #define USB_DEVICE_PRODUCT_ID 0xXXXX // Type Word - // Major version of the device - #define USB_DEVICE_MAJOR_VERSION 1 // Type Byte - // Minor version of the device - #define USB_DEVICE_MINOR_VERSION 0 // Type Byte - // Maximum device power (mA) - #define USB_DEVICE_POWER 100 // Type 9-bits - // USB attributes to enable features - #define USB_DEVICE_ATTR USB_CONFIG_ATTR_BUS_POWERED // Flags \endcode + #define USB_DEVICE_VENDOR_ID 0x03EB // Type Word + // Product ID (Atmel PID referenced in usb_atmel.h) + #define USB_DEVICE_PRODUCT_ID 0xXXXX // Type Word + // Major version of the device + #define USB_DEVICE_MAJOR_VERSION 1 // Type Byte + // Minor version of the device + #define USB_DEVICE_MINOR_VERSION 0 // Type Byte + // Maximum device power (mA) + #define USB_DEVICE_POWER 100 // Type 9-bits + // USB attributes to enable features + #define USB_DEVICE_ATTR USB_CONFIG_ATTR_BUS_POWERED // Flags \endcode * -# Call the USB device stack start function to enable stack and start USB: * - \code udc_start(); \endcode * \note In case of USB dual roles (Device and Host) managed through USB OTG connector @@ -372,90 +365,90 @@ usb_iface_desc_t UDC_DESC_STORAGE *udc_get_interface_desc(void); * * Content of XMEGA conf_clock.h: * \code - // Configuration based on internal RC: - // USB clock need of 48Mhz - #define CONFIG_USBCLK_SOURCE USBCLK_SRC_RCOSC - #define CONFIG_OSC_RC32_CAL 48000000UL - #define CONFIG_OSC_AUTOCAL_RC32MHZ_REF_OSC OSC_ID_USBSOF - // CPU clock need of clock > 12MHz to run with USB (Here 24MHz) - #define CONFIG_SYSCLK_SOURCE SYSCLK_SRC_RC32MHZ - #define CONFIG_SYSCLK_PSADIV SYSCLK_PSADIV_2 - #define CONFIG_SYSCLK_PSBCDIV SYSCLK_PSBCDIV_1_1 + // Configuration based on internal RC: + // USB clock need of 48Mhz + #define CONFIG_USBCLK_SOURCE USBCLK_SRC_RCOSC + #define CONFIG_OSC_RC32_CAL 48000000UL + #define CONFIG_OSC_AUTOCAL_RC32MHZ_REF_OSC OSC_ID_USBSOF + // CPU clock need of clock > 12MHz to run with USB (Here 24MHz) + #define CONFIG_SYSCLK_SOURCE SYSCLK_SRC_RC32MHZ + #define CONFIG_SYSCLK_PSADIV SYSCLK_PSADIV_2 + #define CONFIG_SYSCLK_PSBCDIV SYSCLK_PSBCDIV_1_1 \endcode * * Content of conf_clock.h for AT32UC3A0, AT32UC3A1, AT32UC3B devices (USBB): * \code - // Configuration based on 12MHz external OSC: - #define CONFIG_PLL1_SOURCE PLL_SRC_OSC0 - #define CONFIG_PLL1_MUL 8 - #define CONFIG_PLL1_DIV 2 - #define CONFIG_USBCLK_SOURCE USBCLK_SRC_PLL1 - #define CONFIG_USBCLK_DIV 1 // Fusb = Fsys/(2 ^ USB_div) + // Configuration based on 12MHz external OSC: + #define CONFIG_PLL1_SOURCE PLL_SRC_OSC0 + #define CONFIG_PLL1_MUL 8 + #define CONFIG_PLL1_DIV 2 + #define CONFIG_USBCLK_SOURCE USBCLK_SRC_PLL1 + #define CONFIG_USBCLK_DIV 1 // Fusb = Fsys/(2 ^ USB_div) \endcode * * Content of conf_clock.h for AT32UC3A3, AT32UC3A4 devices (USBB with high speed support): * \code - // Configuration based on 12MHz external OSC: - #define CONFIG_USBCLK_SOURCE USBCLK_SRC_OSC0 - #define CONFIG_USBCLK_DIV 1 // Fusb = Fsys/(2 ^ USB_div) + // Configuration based on 12MHz external OSC: + #define CONFIG_USBCLK_SOURCE USBCLK_SRC_OSC0 + #define CONFIG_USBCLK_DIV 1 // Fusb = Fsys/(2 ^ USB_div) \endcode * * Content of conf_clock.h for AT32UC3C, ATUCXXD, ATUCXXL3U, ATUCXXL4U devices (USBC): * \code - // Configuration based on 12MHz external OSC: - #define CONFIG_PLL1_SOURCE PLL_SRC_OSC0 - #define CONFIG_PLL1_MUL 8 - #define CONFIG_PLL1_DIV 2 - #define CONFIG_USBCLK_SOURCE USBCLK_SRC_PLL1 - #define CONFIG_USBCLK_DIV 1 // Fusb = Fsys/(2 ^ USB_div) - // CPU clock need of clock > 25MHz to run with USBC - #define CONFIG_SYSCLK_SOURCE SYSCLK_SRC_PLL1 + // Configuration based on 12MHz external OSC: + #define CONFIG_PLL1_SOURCE PLL_SRC_OSC0 + #define CONFIG_PLL1_MUL 8 + #define CONFIG_PLL1_DIV 2 + #define CONFIG_USBCLK_SOURCE USBCLK_SRC_PLL1 + #define CONFIG_USBCLK_DIV 1 // Fusb = Fsys/(2 ^ USB_div) + // CPU clock need of clock > 25MHz to run with USBC + #define CONFIG_SYSCLK_SOURCE SYSCLK_SRC_PLL1 \endcode * * Content of conf_clock.h for SAM3S, SAM3SD, SAM4S devices (UPD: USB Peripheral Device): * \code - // PLL1 (B) Options (Fpll = (Fclk * PLL_mul) / PLL_div) - #define CONFIG_PLL1_SOURCE PLL_SRC_MAINCK_XTAL - #define CONFIG_PLL1_MUL 16 - #define CONFIG_PLL1_DIV 2 - // USB Clock Source Options (Fusb = FpllX / USB_div) - #define CONFIG_USBCLK_SOURCE USBCLK_SRC_PLL1 - #define CONFIG_USBCLK_DIV 2 + // PLL1 (B) Options (Fpll = (Fclk * PLL_mul) / PLL_div) + #define CONFIG_PLL1_SOURCE PLL_SRC_MAINCK_XTAL + #define CONFIG_PLL1_MUL 16 + #define CONFIG_PLL1_DIV 2 + // USB Clock Source Options (Fusb = FpllX / USB_div) + #define CONFIG_USBCLK_SOURCE USBCLK_SRC_PLL1 + #define CONFIG_USBCLK_DIV 2 \endcode * * Content of conf_clock.h for SAM3U device (UPDHS: USB Peripheral Device High Speed): * \code - // USB Clock Source fixed at UPLL. + // USB Clock Source fixed at UPLL. \endcode * * Content of conf_clock.h for SAM3X, SAM3A devices (UOTGHS: USB OTG High Speed): * \code - // USB Clock Source fixed at UPLL. - #define CONFIG_USBCLK_SOURCE USBCLK_SRC_UPLL - #define CONFIG_USBCLK_DIV 1 + // USB Clock Source fixed at UPLL. + #define CONFIG_USBCLK_SOURCE USBCLK_SRC_UPLL + #define CONFIG_USBCLK_DIV 1 \endcode * * Content of conf_clocks.h for SAMD devices (USB): * \code - // System clock bus configuration - # define CONF_CLOCK_FLASH_WAIT_STATES 2 + // System clock bus configuration + # define CONF_CLOCK_FLASH_WAIT_STATES 2 - // USB Clock Source fixed at DFLL. - // SYSTEM_CLOCK_SOURCE_DFLL configuration - Digital Frequency Locked Loop - # define CONF_CLOCK_DFLL_ENABLE true - # define CONF_CLOCK_DFLL_LOOP_MODE SYSTEM_CLOCK_DFLL_LOOP_MODE_USB_RECOVERY - # define CONF_CLOCK_DFLL_ON_DEMAND true + // USB Clock Source fixed at DFLL. + // SYSTEM_CLOCK_SOURCE_DFLL configuration - Digital Frequency Locked Loop + # define CONF_CLOCK_DFLL_ENABLE true + # define CONF_CLOCK_DFLL_LOOP_MODE SYSTEM_CLOCK_DFLL_LOOP_MODE_USB_RECOVERY + # define CONF_CLOCK_DFLL_ON_DEMAND true - // Set this to true to configure the GCLK when running clocks_init. - // If set to false, none of the GCLK generators will be configured in clocks_init(). - # define CONF_CLOCK_CONFIGURE_GCLK true + // Set this to true to configure the GCLK when running clocks_init. + // If set to false, none of the GCLK generators will be configured in clocks_init(). + # define CONF_CLOCK_CONFIGURE_GCLK true - // Configure GCLK generator 0 (Main Clock) - # define CONF_CLOCK_GCLK_0_ENABLE true - # define CONF_CLOCK_GCLK_0_RUN_IN_STANDBY true - # define CONF_CLOCK_GCLK_0_CLOCK_SOURCE SYSTEM_CLOCK_SOURCE_DFLL - # define CONF_CLOCK_GCLK_0_PRESCALER 1 - # define CONF_CLOCK_GCLK_0_OUTPUT_ENABLE false + // Configure GCLK generator 0 (Main Clock) + # define CONF_CLOCK_GCLK_0_ENABLE true + # define CONF_CLOCK_GCLK_0_RUN_IN_STANDBY true + # define CONF_CLOCK_GCLK_0_CLOCK_SOURCE SYSTEM_CLOCK_SOURCE_DFLL + # define CONF_CLOCK_GCLK_0_PRESCALER 1 + # define CONF_CLOCK_GCLK_0_OUTPUT_ENABLE false \endcode */ @@ -474,34 +467,34 @@ usb_iface_desc_t UDC_DESC_STORAGE *udc_get_interface_desc(void); * \subsection udc_use_case_1_usage_code Example code * Content of conf_usb.h: * \code - #if // Low speed - #define USB_DEVICE_LOW_SPEED - // #define USB_DEVICE_HS_SUPPORT + #if // Low speed + #define USB_DEVICE_LOW_SPEED + // #define USB_DEVICE_HS_SUPPORT - #elif // Full speed - // #define USB_DEVICE_LOW_SPEED - // #define USB_DEVICE_HS_SUPPORT + #elif // Full speed + // #define USB_DEVICE_LOW_SPEED + // #define USB_DEVICE_HS_SUPPORT - #elif // High speed - // #define USB_DEVICE_LOW_SPEED - #define USB_DEVICE_HS_SUPPORT + #elif // High speed + // #define USB_DEVICE_LOW_SPEED + #define USB_DEVICE_HS_SUPPORT - #endif + #endif \endcode * * \subsection udc_use_case_1_usage_flow Workflow * -# Ensure that conf_usb.h is available and contains the following parameters * required for a USB device low speed (1.5Mbit/s): * - \code #define USB_DEVICE_LOW_SPEED - //#define USB_DEVICE_HS_SUPPORT \endcode + //#define USB_DEVICE_HS_SUPPORT \endcode * -# Ensure that conf_usb.h contains the following parameters * required for a USB device full speed (12Mbit/s): * - \code //#define USB_DEVICE_LOW_SPEED - //#define USB_DEVICE_HS_SUPPORT \endcode + //#define USB_DEVICE_HS_SUPPORT \endcode * -# Ensure that conf_usb.h contains the following parameters * required for a USB device high speed (480Mbit/s): * - \code //#define USB_DEVICE_LOW_SPEED - #define USB_DEVICE_HS_SUPPORT \endcode + #define USB_DEVICE_HS_SUPPORT \endcode */ /** @@ -518,20 +511,20 @@ usb_iface_desc_t UDC_DESC_STORAGE *udc_get_interface_desc(void); * \subsection udc_use_case_2_usage_code Example code * Content of conf_usb.h: * \code - #define USB_DEVICE_MANUFACTURE_NAME "Manufacture name" - #define USB_DEVICE_PRODUCT_NAME "Product name" - #define USB_DEVICE_SERIAL_NAME "12...EF" + #define USB_DEVICE_MANUFACTURE_NAME "Manufacture name" + #define USB_DEVICE_PRODUCT_NAME "Product name" + #define USB_DEVICE_SERIAL_NAME "12...EF" \endcode * * \subsection udc_use_case_2_usage_flow Workflow * -# Ensure that conf_usb.h is available and contains the following parameters * required to enable different USB strings: * - \code // Static ASCII name for the manufacture - #define USB_DEVICE_MANUFACTURE_NAME "Manufacture name" \endcode + #define USB_DEVICE_MANUFACTURE_NAME "Manufacture name" \endcode * - \code // Static ASCII name for the product - #define USB_DEVICE_PRODUCT_NAME "Product name" \endcode + #define USB_DEVICE_PRODUCT_NAME "Product name" \endcode * - \code // Static ASCII name to enable and set a serial number - #define USB_DEVICE_SERIAL_NAME "12...EF" \endcode + #define USB_DEVICE_SERIAL_NAME "12...EF" \endcode */ /** @@ -548,42 +541,42 @@ usb_iface_desc_t UDC_DESC_STORAGE *udc_get_interface_desc(void); * \subsection udc_use_case_3_usage_code Example code * Content of conf_usb.h: * \code - #define USB_DEVICE_ATTR \ - (USB_CONFIG_ATTR_REMOTE_WAKEUP | USB_CONFIG_ATTR_..._POWERED) - #define UDC_REMOTEWAKEUP_ENABLE() my_callback_remotewakeup_enable() - extern void my_callback_remotewakeup_enable(void); - #define UDC_REMOTEWAKEUP_DISABLE() my_callback_remotewakeup_disable() - extern void my_callback_remotewakeup_disable(void); + #define USB_DEVICE_ATTR \ + (USB_CONFIG_ATTR_REMOTE_WAKEUP | USB_CONFIG_ATTR_..._POWERED) + #define UDC_REMOTEWAKEUP_ENABLE() my_callback_remotewakeup_enable() + extern void my_callback_remotewakeup_enable(void); + #define UDC_REMOTEWAKEUP_DISABLE() my_callback_remotewakeup_disable() + extern void my_callback_remotewakeup_disable(void); \endcode * * Add to application C-file: * \code - void my_callback_remotewakeup_enable(void) - { - // Enable application wakeup events (e.g. enable GPIO interrupt) - } - void my_callback_remotewakeup_disable(void) - { - // Disable application wakeup events (e.g. disable GPIO interrupt) - } + void my_callback_remotewakeup_enable(void) + { + // Enable application wakeup events (e.g. enable GPIO interrupt) + } + void my_callback_remotewakeup_disable(void) + { + // Disable application wakeup events (e.g. disable GPIO interrupt) + } - void my_interrupt_event(void) - { - udc_remotewakeup(); - } + void my_interrupt_event(void) + { + udc_remotewakeup(); + } \endcode * * \subsection udc_use_case_3_usage_flow Workflow * -# Ensure that conf_usb.h is available and contains the following parameters * required to enable remote wakeup feature: * - \code // Authorizes the remote wakeup feature - #define USB_DEVICE_ATTR (USB_CONFIG_ATTR_REMOTE_WAKEUP | USB_CONFIG_ATTR_..._POWERED) \endcode + #define USB_DEVICE_ATTR (USB_CONFIG_ATTR_REMOTE_WAKEUP | USB_CONFIG_ATTR_..._POWERED) \endcode * - \code // Define callback called when the host enables the remotewakeup feature - #define UDC_REMOTEWAKEUP_ENABLE() my_callback_remotewakeup_enable() - extern void my_callback_remotewakeup_enable(void); \endcode + #define UDC_REMOTEWAKEUP_ENABLE() my_callback_remotewakeup_enable() + extern void my_callback_remotewakeup_enable(void); \endcode * - \code // Define callback called when the host disables the remotewakeup feature - #define UDC_REMOTEWAKEUP_DISABLE() my_callback_remotewakeup_disable() - extern void my_callback_remotewakeup_disable(void); \endcode + #define UDC_REMOTEWAKEUP_DISABLE() my_callback_remotewakeup_disable() + extern void my_callback_remotewakeup_disable(void); \endcode * -# Send a remote wakeup (USB upstream): * - \code udc_remotewakeup(); \endcode */ @@ -603,40 +596,40 @@ usb_iface_desc_t UDC_DESC_STORAGE *udc_get_interface_desc(void); * \subsection udc_use_case_5_usage_code Example code * Content of conf_usb.h: * \code - #define USB_DEVICE_ATTR (USB_CONFIG_ATTR_BUS_POWERED) - #define UDC_SUSPEND_EVENT() user_callback_suspend_action() - extern void user_callback_suspend_action(void) - #define UDC_RESUME_EVENT() user_callback_resume_action() - extern void user_callback_resume_action(void) + #define USB_DEVICE_ATTR (USB_CONFIG_ATTR_BUS_POWERED) + #define UDC_SUSPEND_EVENT() user_callback_suspend_action() + extern void user_callback_suspend_action(void) + #define UDC_RESUME_EVENT() user_callback_resume_action() + extern void user_callback_resume_action(void) \endcode * * Add to application C-file: * \code - void user_callback_suspend_action(void) - { - // Disable hardware component to reduce power consumption - } - void user_callback_resume_action(void) - { - // Re-enable hardware component - } + void user_callback_suspend_action(void) + { + // Disable hardware component to reduce power consumption + } + void user_callback_resume_action(void) + { + // Re-enable hardware component + } \endcode * * \subsection udc_use_case_5_usage_flow Workflow * -# Ensure that conf_usb.h is available and contains the following parameters: * - \code // Authorizes the BUS power feature - #define USB_DEVICE_ATTR (USB_CONFIG_ATTR_BUS_POWERED) \endcode + #define USB_DEVICE_ATTR (USB_CONFIG_ATTR_BUS_POWERED) \endcode * - \code // Define callback called when the host suspend the USB line - #define UDC_SUSPEND_EVENT() user_callback_suspend_action() - extern void user_callback_suspend_action(void); \endcode + #define UDC_SUSPEND_EVENT() user_callback_suspend_action() + extern void user_callback_suspend_action(void); \endcode * - \code // Define callback called when the host or device resume the USB line - #define UDC_RESUME_EVENT() user_callback_resume_action() - extern void user_callback_resume_action(void); \endcode + #define UDC_RESUME_EVENT() user_callback_resume_action() + extern void user_callback_resume_action(void); \endcode * -# Reduce power consumption in suspend mode (max. 2.5mA on Vbus): * - \code void user_callback_suspend_action(void) - { - turn_off_components(); - } \endcode + { + turn_off_components(); + } \endcode */ /** @@ -654,44 +647,42 @@ usb_iface_desc_t UDC_DESC_STORAGE *udc_get_interface_desc(void); * \subsection udc_use_case_6_usage_code Example code * Content of conf_usb.h: * \code - #define USB_DEVICE_SERIAL_NAME - #define USB_DEVICE_GET_SERIAL_NAME_POINTER serial_number - #define USB_DEVICE_GET_SERIAL_NAME_LENGTH 12 - extern uint8_t serial_number[]; + #define USB_DEVICE_SERIAL_NAME + #define USB_DEVICE_GET_SERIAL_NAME_POINTER serial_number + #define USB_DEVICE_GET_SERIAL_NAME_LENGTH 12 + extern uint8_t serial_number[]; \endcode * * Add to application C-file: * \code - uint8_t serial_number[USB_DEVICE_GET_SERIAL_NAME_LENGTH]; + uint8_t serial_number[USB_DEVICE_GET_SERIAL_NAME_LENGTH]; - void init_build_usb_serial_number(void) - { - serial_number[0] = 'A'; - serial_number[1] = 'B'; - ... - serial_number[USB_DEVICE_GET_SERIAL_NAME_LENGTH-1] = 'C'; - } \endcode + void init_build_usb_serial_number(void) + { + serial_number[0] = 'A'; + serial_number[1] = 'B'; + ... + serial_number[USB_DEVICE_GET_SERIAL_NAME_LENGTH-1] = 'C'; + } \endcode * * \subsection udc_use_case_6_usage_flow Workflow * -# Ensure that conf_usb.h is available and contains the following parameters * required to enable a USB serial number strings dynamically: * - \code #define USB_DEVICE_SERIAL_NAME // Define this empty - #define USB_DEVICE_GET_SERIAL_NAME_POINTER serial_number // Give serial array pointer - #define USB_DEVICE_GET_SERIAL_NAME_LENGTH 12 // Give size of serial array - extern uint8_t serial_number[]; // Declare external serial array \endcode + #define USB_DEVICE_GET_SERIAL_NAME_POINTER serial_number // Give serial array pointer + #define USB_DEVICE_GET_SERIAL_NAME_LENGTH 12 // Give size of serial array + extern uint8_t serial_number[]; // Declare external serial array \endcode * -# Before start USB stack, initialize the serial array * - \code - uint8_t serial_number[USB_DEVICE_GET_SERIAL_NAME_LENGTH]; + uint8_t serial_number[USB_DEVICE_GET_SERIAL_NAME_LENGTH]; - void init_build_usb_serial_number(void) - { - serial_number[0] = 'A'; - serial_number[1] = 'B'; - ... - serial_number[USB_DEVICE_GET_SERIAL_NAME_LENGTH-1] = 'C'; - } \endcode + void init_build_usb_serial_number(void) + { + serial_number[0] = 'A'; + serial_number[1] = 'B'; + ... + serial_number[USB_DEVICE_GET_SERIAL_NAME_LENGTH-1] = 'C'; + } \endcode */ - - #endif // _UDC_H_ diff --git a/Marlin/src/HAL/DUE/usb/udc_desc.h b/Marlin/src/HAL/DUE/usb/udc_desc.h index 052ca08eca..f1f328d035 100644 --- a/Marlin/src/HAL/DUE/usb/udc_desc.h +++ b/Marlin/src/HAL/DUE/usb/udc_desc.h @@ -78,50 +78,47 @@ extern "C" { * For Mega application used "code". */ #define UDC_DESC_STORAGE - // Descriptor storage in internal RAM + // Descriptor storage in internal RAM #if (defined UDC_DATA_USE_HRAM_SUPPORT) -# if defined(__GNUC__) -# define UDC_DATA(x) COMPILER_WORD_ALIGNED __attribute__((__section__(".data_hram0"))) -# define UDC_BSS(x) COMPILER_ALIGNED(x) __attribute__((__section__(".bss_hram0"))) -# elif defined(__ICCAVR32__) -# define UDC_DATA(x) COMPILER_ALIGNED(x) __data32 -# define UDC_BSS(x) COMPILER_ALIGNED(x) __data32 -# endif -#else -# define UDC_DATA(x) COMPILER_ALIGNED(x) -# define UDC_BSS(x) COMPILER_ALIGNED(x) + #if defined(__GNUC__) + #define UDC_DATA(x) COMPILER_WORD_ALIGNED __attribute__((__section__(".data_hram0"))) + #define UDC_BSS(x) COMPILER_ALIGNED(x) __attribute__((__section__(".bss_hram0"))) +#elif defined(__ICCAVR32__) + #define UDC_DATA(x) COMPILER_ALIGNED(x) __data32 + #define UDC_BSS(x) COMPILER_ALIGNED(x) __data32 +#endif +#else + #define UDC_DATA(x) COMPILER_ALIGNED(x) + #define UDC_BSS(x) COMPILER_ALIGNED(x) #endif - - /** * \brief Configuration descriptor and UDI link for one USB speed */ typedef struct { - //! USB configuration descriptor - usb_conf_desc_t UDC_DESC_STORAGE *desc; - //! Array of UDI API pointer - udi_api_t UDC_DESC_STORAGE *UDC_DESC_STORAGE * udi_apis; + //! USB configuration descriptor + usb_conf_desc_t UDC_DESC_STORAGE *desc; + //! Array of UDI API pointer + udi_api_t UDC_DESC_STORAGE *UDC_DESC_STORAGE * udi_apis; } udc_config_speed_t; - /** * \brief All information about the USB Device */ typedef struct { - //! USB device descriptor for low or full speed - usb_dev_desc_t UDC_DESC_STORAGE *confdev_lsfs; - //! USB configuration descriptor and UDI API pointers for low or full speed - udc_config_speed_t UDC_DESC_STORAGE *conf_lsfs; -#ifdef USB_DEVICE_HS_SUPPORT - //! USB device descriptor for high speed - usb_dev_desc_t UDC_DESC_STORAGE *confdev_hs; - //! USB device qualifier, only use in high speed mode - usb_dev_qual_desc_t UDC_DESC_STORAGE *qualifier; - //! USB configuration descriptor and UDI API pointers for high speed - udc_config_speed_t UDC_DESC_STORAGE *conf_hs; -#endif - usb_dev_bos_desc_t UDC_DESC_STORAGE *conf_bos; + //! USB device descriptor for low or full speed + usb_dev_desc_t UDC_DESC_STORAGE *confdev_lsfs; + //! USB configuration descriptor and UDI API pointers for low or full speed + udc_config_speed_t UDC_DESC_STORAGE *conf_lsfs; + #ifdef USB_DEVICE_HS_SUPPORT + //! USB device descriptor for high speed + usb_dev_desc_t UDC_DESC_STORAGE *confdev_hs; + //! USB device qualifier, only use in high speed mode + usb_dev_qual_desc_t UDC_DESC_STORAGE *qualifier; + //! USB configuration descriptor and UDI API pointers for high speed + udc_config_speed_t UDC_DESC_STORAGE *conf_hs; + #endif + usb_dev_bos_desc_t UDC_DESC_STORAGE *conf_bos; } udc_config_t; //! Global variables of USB Device Descriptor and UDI links diff --git a/Marlin/src/HAL/DUE/usb/udd.h b/Marlin/src/HAL/DUE/usb/udd.h index 7ec8c03dee..4e482784e1 100644 --- a/Marlin/src/HAL/DUE/usb/udd.h +++ b/Marlin/src/HAL/DUE/usb/udd.h @@ -71,8 +71,8 @@ typedef uint8_t udd_ep_id_t; //! \brief Endpoint transfer status //! Returned in parameters of callback register via udd_ep_run routine. typedef enum { - UDD_EP_TRANSFER_OK = 0, - UDD_EP_TRANSFER_ABORT = 1, + UDD_EP_TRANSFER_OK = 0, + UDD_EP_TRANSFER_ABORT = 1, } udd_ep_status_t; /** @@ -82,41 +82,37 @@ typedef enum { * It can be updated by udc_process_setup() from UDC or *setup() from UDIs. */ typedef struct { - //! Data received in USB SETUP packet - //! Note: The swap of "req.wValues" from uin16_t to le16_t is done by UDD. - usb_setup_req_t req; + //! Data received in USB SETUP packet + //! Note: The swap of "req.wValues" from uin16_t to le16_t is done by UDD. + usb_setup_req_t req; - //! Point to buffer to send or fill with data following SETUP packet - //! This buffer must be word align for DATA IN phase (use prefix COMPILER_WORD_ALIGNED for buffer) - uint8_t *payload; + //! Point to buffer to send or fill with data following SETUP packet + //! This buffer must be word align for DATA IN phase (use prefix COMPILER_WORD_ALIGNED for buffer) + uint8_t *payload; - //! Size of buffer to send or fill, and content the number of byte transfered - uint16_t payload_size; + //! Size of buffer to send or fill, and content the number of byte transferred + uint16_t payload_size; - //! Callback called after reception of ZLP from setup request - void (*callback)(void); + //! Callback called after reception of ZLP from setup request + void (*callback)(void); - //! Callback called when the buffer given (.payload) is full or empty. - //! This one return false to abort data transfer, or true with a new buffer in .payload. - bool (*over_under_run)(void); + //! Callback called when the buffer given (.payload) is full or empty. + //! This one return false to abort data transfer, or true with a new buffer in .payload. + bool (*over_under_run)(void); } udd_ctrl_request_t; extern udd_ctrl_request_t udd_g_ctrlreq; //! Return true if the setup request \a udd_g_ctrlreq indicates IN data transfer -#define Udd_setup_is_in() \ - (USB_REQ_DIR_IN == (udd_g_ctrlreq.req.bmRequestType & USB_REQ_DIR_MASK)) +#define Udd_setup_is_in() (USB_REQ_DIR_IN == (udd_g_ctrlreq.req.bmRequestType & USB_REQ_DIR_MASK)) //! Return true if the setup request \a udd_g_ctrlreq indicates OUT data transfer -#define Udd_setup_is_out() \ - (USB_REQ_DIR_OUT == (udd_g_ctrlreq.req.bmRequestType & USB_REQ_DIR_MASK)) +#define Udd_setup_is_out() (USB_REQ_DIR_OUT == (udd_g_ctrlreq.req.bmRequestType & USB_REQ_DIR_MASK)) //! Return the type of the SETUP request \a udd_g_ctrlreq. \see usb_reqtype. -#define Udd_setup_type() \ - (udd_g_ctrlreq.req.bmRequestType & USB_REQ_TYPE_MASK) +#define Udd_setup_type() (udd_g_ctrlreq.req.bmRequestType & USB_REQ_TYPE_MASK) //! Return the recipient of the SETUP request \a udd_g_ctrlreq. \see usb_recipient -#define Udd_setup_recipient() \ - (udd_g_ctrlreq.req.bmRequestType & USB_REQ_RECIP_MASK) +#define Udd_setup_recipient() (udd_g_ctrlreq.req.bmRequestType & USB_REQ_RECIP_MASK) /** * \brief End of halt callback function type. @@ -132,10 +128,9 @@ typedef void (*udd_callback_halt_cleared_t)(void); * * \param status UDD_EP_TRANSFER_OK, if transfer is complete * \param status UDD_EP_TRANSFER_ABORT, if transfer is aborted - * \param n number of data transfered + * \param n number of data transferred */ -typedef void (*udd_callback_trans_t) (udd_ep_status_t status, - iram_size_t nb_transfered, udd_ep_id_t ep); +typedef void (*udd_callback_trans_t) (udd_ep_status_t status, iram_size_t nb_transferred, udd_ep_id_t ep); /** * \brief Authorizes the VBUS event @@ -218,7 +213,6 @@ void udd_send_remotewakeup(void); */ void udd_set_setup_payload( uint8_t *payload, uint16_t payload_size ); - /** * \name Endpoint Management * @@ -239,8 +233,7 @@ void udd_set_setup_payload( uint8_t *payload, uint16_t payload_size ); * * \return \c 1 if the endpoint is enabled, otherwise \c 0. */ -bool udd_ep_alloc(udd_ep_id_t ep, uint8_t bmAttributes, - uint16_t MaxEndpointSize); +bool udd_ep_alloc(udd_ep_id_t ep, uint8_t bmAttributes, uint16_t MaxEndpointSize); /** * \brief Disables an endpoint @@ -294,8 +287,7 @@ bool udd_ep_clear_halt(udd_ep_id_t ep); * * \return \c 1 if the register is accepted, otherwise \c 0. */ -bool udd_ep_wait_stall_clear(udd_ep_id_t ep, - udd_callback_halt_cleared_t callback); +bool udd_ep_wait_stall_clear(udd_ep_id_t ep, udd_callback_halt_cleared_t callback); /** * \brief Allows to receive or send data on an endpoint @@ -303,7 +295,7 @@ bool udd_ep_wait_stall_clear(udd_ep_id_t ep, * The driver uses a specific DMA USB to transfer data * from internal RAM to endpoint, if this one is available. * When the transfer is finished or aborted (stall, reset, ...), the \a callback is called. - * The \a callback returns the transfer status and eventually the number of byte transfered. + * The \a callback returns the transfer status and eventually the number of byte transferred. * Note: The control endpoint is not authorized. * * \param ep The ID of the endpoint to use @@ -321,9 +313,8 @@ bool udd_ep_wait_stall_clear(udd_ep_id_t ep, * * \return \c 1 if function was successfully done, otherwise \c 0. */ -bool udd_ep_run(udd_ep_id_t ep, bool b_shortpacket, - uint8_t * buf, iram_size_t buf_size, - udd_callback_trans_t callback); +bool udd_ep_run(udd_ep_id_t ep, bool b_shortpacket, uint8_t * buf, iram_size_t buf_size, udd_callback_trans_t callback); + /** * \brief Aborts transfer on going on endpoint * @@ -339,7 +330,6 @@ void udd_ep_abort(udd_ep_id_t ep); //@} - /** * \name High speed test mode management * @@ -352,7 +342,6 @@ void udd_test_mode_se0_nak(void); void udd_test_mode_packet(void); //@} - /** * \name UDC callbacks to provide for UDD * diff --git a/Marlin/src/HAL/DUE/usb/udi.h b/Marlin/src/HAL/DUE/usb/udi.h index febf03b718..bc5de086f3 100644 --- a/Marlin/src/HAL/DUE/usb/udi.h +++ b/Marlin/src/HAL/DUE/usb/udi.h @@ -72,57 +72,57 @@ extern "C" { * selected by UDC. */ typedef struct { - /** - * \brief Enable the interface. - * - * This function is called when the host selects a configuration - * to which this interface belongs through a Set Configuration - * request, and when the host selects an alternate setting of - * this interface through a Set Interface request. - * - * \return \c 1 if function was successfully done, otherwise \c 0. - */ - bool (*enable)(void); + /** + * \brief Enable the interface. + * + * This function is called when the host selects a configuration + * to which this interface belongs through a Set Configuration + * request, and when the host selects an alternate setting of + * this interface through a Set Interface request. + * + * \return \c 1 if function was successfully done, otherwise \c 0. + */ + bool (*enable)(void); - /** - * \brief Disable the interface. - * - * This function is called when this interface is currently - * active, and - * - the host selects any configuration through a Set - * Configuration request, or - * - the host issues a USB reset, or - * - the device is detached from the host (i.e. Vbus is no - * longer present) - */ - void (*disable)(void); + /** + * \brief Disable the interface. + * + * This function is called when this interface is currently + * active, and + * - the host selects any configuration through a Set + * Configuration request, or + * - the host issues a USB reset, or + * - the device is detached from the host (i.e. Vbus is no + * longer present) + */ + void (*disable)(void); - /** - * \brief Handle a control request directed at an interface. - * - * This function is called when this interface is currently - * active and the host sends a SETUP request - * with this interface as the recipient. - * - * Use udd_g_ctrlreq to decode and response to SETUP request. - * - * \return \c 1 if this interface supports the SETUP request, otherwise \c 0. - */ - bool (*setup)(void); + /** + * \brief Handle a control request directed at an interface. + * + * This function is called when this interface is currently + * active and the host sends a SETUP request + * with this interface as the recipient. + * + * Use udd_g_ctrlreq to decode and response to SETUP request. + * + * \return \c 1 if this interface supports the SETUP request, otherwise \c 0. + */ + bool (*setup)(void); - /** - * \brief Returns the current setting of the selected interface. - * - * This function is called when UDC when know alternate setting of selected interface. - * - * \return alternate setting of selected interface - */ - uint8_t (*getsetting)(void); + /** + * \brief Returns the current setting of the selected interface. + * + * This function is called when UDC when know alternate setting of selected interface. + * + * \return alternate setting of selected interface + */ + uint8_t (*getsetting)(void); - /** - * \brief To signal that a SOF is occurred - */ - void (*sof_notify)(void); + /** + * \brief To signal that a SOF is occurred + */ + void (*sof_notify)(void); } udi_api_t; //@} diff --git a/Marlin/src/HAL/DUE/usb/udi_cdc.c b/Marlin/src/HAL/DUE/usb/udi_cdc.c index cbe23dbb68..26788570c6 100644 --- a/Marlin/src/HAL/DUE/usb/udi_cdc.c +++ b/Marlin/src/HAL/DUE/usb/udi_cdc.c @@ -162,7 +162,7 @@ static void udi_cdc_ctrl_state_notify(uint8_t port, udd_ep_id_t ep); * * \param status UDD_EP_TRANSFER_OK, if transfer finished * \param status UDD_EP_TRANSFER_ABORT, if transfer aborted - * \param n number of data transfered + * \param n number of data transferred */ static void udi_cdc_serial_state_msg_sent(udd_ep_status_t status, iram_size_t n, udd_ep_id_t ep); @@ -200,7 +200,7 @@ static void udi_cdc_data_received(udd_ep_status_t status, iram_size_t n, udd_ep_ * * \param status UDD_EP_TRANSFER_OK, if transfer finished * \param status UDD_EP_TRANSFER_ABORT, if transfer aborted - * \param n number of data transfered + * \param n number of data transferred */ static void udi_cdc_data_sent(udd_ep_status_t status, iram_size_t n, udd_ep_id_t ep); @@ -457,7 +457,6 @@ void udi_cdc_data_sof_notify(void) #endif } - // ------------------------ //------- Internal routines to control serial line @@ -520,7 +519,6 @@ static void udi_cdc_ctrl_state_change(uint8_t port, bool b_set, le16_t bit_mask) udi_cdc_ctrl_state_notify(port, ep_comm); } - static void udi_cdc_ctrl_state_notify(uint8_t port, udd_ep_id_t ep) { #if UDI_CDC_PORT_NB == 1 // To optimize code @@ -542,7 +540,6 @@ static void udi_cdc_ctrl_state_notify(uint8_t port, udd_ep_id_t ep) } } - static void udi_cdc_serial_state_msg_sent(udd_ep_status_t status, iram_size_t n, udd_ep_id_t ep) { uint8_t port; @@ -578,11 +575,9 @@ static void udi_cdc_serial_state_msg_sent(udd_ep_status_t status, iram_size_t n, udi_cdc_ctrl_state_notify(port, ep); } - // ------------------------ //------- Internal routines to process data transfer - static bool udi_cdc_rx_start(uint8_t port) { irqflags_t flags; @@ -632,7 +627,6 @@ static bool udi_cdc_rx_start(uint8_t port) udi_cdc_data_received); } - static void udi_cdc_data_received(udd_ep_status_t status, iram_size_t n, udd_ep_id_t ep) { uint8_t buf_sel_trans; @@ -668,7 +662,6 @@ static void udi_cdc_data_received(udd_ep_status_t status, iram_size_t n, udd_ep_ udi_cdc_rx_start(port); } - static void udi_cdc_data_sent(udd_ep_status_t status, iram_size_t n, udd_ep_id_t ep) { uint8_t port; @@ -700,7 +693,6 @@ static void udi_cdc_data_sent(udd_ep_status_t status, iram_size_t n, udd_ep_id_t udi_cdc_tx_send(port); } - static void udi_cdc_tx_send(uint8_t port) { irqflags_t flags; @@ -780,11 +772,9 @@ static void udi_cdc_tx_send(uint8_t port) udi_cdc_data_sent); } - // ------------------------ //------- Application interface - //------- Application interface void udi_cdc_ctrl_signal_dcd(bool b_set) diff --git a/Marlin/src/HAL/DUE/usb/udi_cdc.h b/Marlin/src/HAL/DUE/usb/udi_cdc.h index 0ecf7bb00e..e9c6abbbb2 100644 --- a/Marlin/src/HAL/DUE/usb/udi_cdc.h +++ b/Marlin/src/HAL/DUE/usb/udi_cdc.h @@ -92,21 +92,20 @@ extern UDC_DESC_STORAGE udi_api_t udi_api_cdc_data; * descriptors for the CDC Communication Class interface. */ typedef struct { - //! Standard interface descriptor - usb_iface_desc_t iface; - //! CDC Header functional descriptor - usb_cdc_hdr_desc_t header; - //! CDC Abstract Control Model functional descriptor - usb_cdc_acm_desc_t acm; - //! CDC Union functional descriptor - usb_cdc_union_desc_t union_desc; - //! CDC Call Management functional descriptor - usb_cdc_call_mgmt_desc_t call_mgmt; - //! Notification endpoint descriptor - usb_ep_desc_t ep_notify; + //! Standard interface descriptor + usb_iface_desc_t iface; + //! CDC Header functional descriptor + usb_cdc_hdr_desc_t header; + //! CDC Abstract Control Model functional descriptor + usb_cdc_acm_desc_t acm; + //! CDC Union functional descriptor + usb_cdc_union_desc_t union_desc; + //! CDC Call Management functional descriptor + usb_cdc_call_mgmt_desc_t call_mgmt; + //! Notification endpoint descriptor + usb_ep_desc_t ep_notify; } udi_cdc_comm_desc_t; - /** * \brief Data Class interface descriptor * @@ -114,14 +113,13 @@ typedef struct { * CDC Data Class interface. */ typedef struct { - //! Standard interface descriptor - usb_iface_desc_t iface; - //! Data IN/OUT endpoint descriptors - usb_ep_desc_t ep_in; - usb_ep_desc_t ep_out; + //! Standard interface descriptor + usb_iface_desc_t iface; + //! Data IN/OUT endpoint descriptors + usb_ep_desc_t ep_in; + usb_ep_desc_t ep_out; } udi_cdc_data_desc_t; - //! CDC communication endpoints size for all speeds #define UDI_CDC_COMM_EP_SIZE 64 //! CDC data endpoints size for FS speed (8B, 16B, 32B, 64B) @@ -136,13 +134,13 @@ typedef struct { //@{ //! By default no string associated to these interfaces #ifndef UDI_CDC_IAD_STRING_ID_0 -#define UDI_CDC_IAD_STRING_ID_0 0 + #define UDI_CDC_IAD_STRING_ID_0 0 #endif #ifndef UDI_CDC_COMM_STRING_ID_0 -#define UDI_CDC_COMM_STRING_ID_0 0 + #define UDI_CDC_COMM_STRING_ID_0 0 #endif #ifndef UDI_CDC_DATA_STRING_ID_0 -#define UDI_CDC_DATA_STRING_ID_0 0 + #define UDI_CDC_DATA_STRING_ID_0 0 #endif #define UDI_CDC_IAD_DESC_0 UDI_CDC_IAD_DESC(0) #define UDI_CDC_COMM_DESC_0 UDI_CDC_COMM_DESC(0) @@ -151,13 +149,13 @@ typedef struct { //! By default no string associated to these interfaces #ifndef UDI_CDC_IAD_STRING_ID_1 -#define UDI_CDC_IAD_STRING_ID_1 0 + #define UDI_CDC_IAD_STRING_ID_1 0 #endif #ifndef UDI_CDC_COMM_STRING_ID_1 -#define UDI_CDC_COMM_STRING_ID_1 0 + #define UDI_CDC_COMM_STRING_ID_1 0 #endif #ifndef UDI_CDC_DATA_STRING_ID_1 -#define UDI_CDC_DATA_STRING_ID_1 0 + #define UDI_CDC_DATA_STRING_ID_1 0 #endif #define UDI_CDC_IAD_DESC_1 UDI_CDC_IAD_DESC(1) #define UDI_CDC_COMM_DESC_1 UDI_CDC_COMM_DESC(1) @@ -166,13 +164,13 @@ typedef struct { //! By default no string associated to these interfaces #ifndef UDI_CDC_IAD_STRING_ID_2 -#define UDI_CDC_IAD_STRING_ID_2 0 + #define UDI_CDC_IAD_STRING_ID_2 0 #endif #ifndef UDI_CDC_COMM_STRING_ID_2 -#define UDI_CDC_COMM_STRING_ID_2 0 + #define UDI_CDC_COMM_STRING_ID_2 0 #endif #ifndef UDI_CDC_DATA_STRING_ID_2 -#define UDI_CDC_DATA_STRING_ID_2 0 + #define UDI_CDC_DATA_STRING_ID_2 0 #endif #define UDI_CDC_IAD_DESC_2 UDI_CDC_IAD_DESC(2) #define UDI_CDC_COMM_DESC_2 UDI_CDC_COMM_DESC(2) @@ -181,13 +179,13 @@ typedef struct { //! By default no string associated to these interfaces #ifndef UDI_CDC_IAD_STRING_ID_3 -#define UDI_CDC_IAD_STRING_ID_3 0 + #define UDI_CDC_IAD_STRING_ID_3 0 #endif #ifndef UDI_CDC_COMM_STRING_ID_3 -#define UDI_CDC_COMM_STRING_ID_3 0 + #define UDI_CDC_COMM_STRING_ID_3 0 #endif #ifndef UDI_CDC_DATA_STRING_ID_3 -#define UDI_CDC_DATA_STRING_ID_3 0 + #define UDI_CDC_DATA_STRING_ID_3 0 #endif #define UDI_CDC_IAD_DESC_3 UDI_CDC_IAD_DESC(3) #define UDI_CDC_COMM_DESC_3 UDI_CDC_COMM_DESC(3) @@ -196,13 +194,13 @@ typedef struct { //! By default no string associated to these interfaces #ifndef UDI_CDC_IAD_STRING_ID_4 -#define UDI_CDC_IAD_STRING_ID_4 0 + #define UDI_CDC_IAD_STRING_ID_4 0 #endif #ifndef UDI_CDC_COMM_STRING_ID_4 -#define UDI_CDC_COMM_STRING_ID_4 0 + #define UDI_CDC_COMM_STRING_ID_4 0 #endif #ifndef UDI_CDC_DATA_STRING_ID_4 -#define UDI_CDC_DATA_STRING_ID_4 0 + #define UDI_CDC_DATA_STRING_ID_4 0 #endif #define UDI_CDC_IAD_DESC_4 UDI_CDC_IAD_DESC(4) #define UDI_CDC_COMM_DESC_4 UDI_CDC_COMM_DESC(4) @@ -211,13 +209,13 @@ typedef struct { //! By default no string associated to these interfaces #ifndef UDI_CDC_IAD_STRING_ID_5 -#define UDI_CDC_IAD_STRING_ID_5 0 + #define UDI_CDC_IAD_STRING_ID_5 0 #endif #ifndef UDI_CDC_COMM_STRING_ID_5 -#define UDI_CDC_COMM_STRING_ID_5 0 + #define UDI_CDC_COMM_STRING_ID_5 0 #endif #ifndef UDI_CDC_DATA_STRING_ID_5 -#define UDI_CDC_DATA_STRING_ID_5 0 + #define UDI_CDC_DATA_STRING_ID_5 0 #endif #define UDI_CDC_IAD_DESC_5 UDI_CDC_IAD_DESC(5) #define UDI_CDC_COMM_DESC_5 UDI_CDC_COMM_DESC(5) @@ -226,13 +224,13 @@ typedef struct { //! By default no string associated to these interfaces #ifndef UDI_CDC_IAD_STRING_ID_6 -#define UDI_CDC_IAD_STRING_ID_6 0 + #define UDI_CDC_IAD_STRING_ID_6 0 #endif #ifndef UDI_CDC_COMM_STRING_ID_6 -#define UDI_CDC_COMM_STRING_ID_6 0 + #define UDI_CDC_COMM_STRING_ID_6 0 #endif #ifndef UDI_CDC_DATA_STRING_ID_6 -#define UDI_CDC_DATA_STRING_ID_6 0 + #define UDI_CDC_DATA_STRING_ID_6 0 #endif #define UDI_CDC_IAD_DESC_6 UDI_CDC_IAD_DESC(6) #define UDI_CDC_COMM_DESC_6 UDI_CDC_COMM_DESC(6) @@ -240,7 +238,6 @@ typedef struct { #define UDI_CDC_DATA_DESC_6_HS UDI_CDC_DATA_DESC_HS(6) //@} - //! Content of CDC IAD interface descriptor for all speeds #define UDI_CDC_IAD_DESC(port) { \ .bLength = sizeof(usb_iad_desc_t),\ @@ -270,7 +267,7 @@ typedef struct { .call_mgmt.bDescriptorType = CDC_CS_INTERFACE,\ .call_mgmt.bDescriptorSubtype = CDC_SCS_CALL_MGMT,\ .call_mgmt.bmCapabilities = \ - CDC_CALL_MGMT_SUPPORTED | CDC_CALL_MGMT_OVER_DCI,\ + CDC_CALL_MGMT_SUPPORTED | CDC_CALL_MGMT_OVER_DCI,\ .acm.bFunctionLength = sizeof(usb_cdc_acm_desc_t),\ .acm.bDescriptorType = CDC_CS_INTERFACE,\ .acm.bDescriptorSubtype = CDC_SCS_ACM,\ @@ -610,40 +607,37 @@ iram_size_t udi_cdc_multi_write_buf(uint8_t port, const void* buf, iram_size_t s * \subsection udi_cdc_basic_use_case_usage_code Example code * Content of conf_usb.h: * \code - #define UDI_CDC_ENABLE_EXT(port) my_callback_cdc_enable() - extern bool my_callback_cdc_enable(void); - #define UDI_CDC_DISABLE_EXT(port) my_callback_cdc_disable() - extern void my_callback_cdc_disable(void); - #define UDI_CDC_LOW_RATE + #define UDI_CDC_ENABLE_EXT(port) my_callback_cdc_enable() + extern bool my_callback_cdc_enable(void); + #define UDI_CDC_DISABLE_EXT(port) my_callback_cdc_disable() + extern void my_callback_cdc_disable(void); + #define UDI_CDC_LOW_RATE - #define UDI_CDC_DEFAULT_RATE 115200 - #define UDI_CDC_DEFAULT_STOPBITS CDC_STOP_BITS_1 - #define UDI_CDC_DEFAULT_PARITY CDC_PAR_NONE - #define UDI_CDC_DEFAULT_DATABITS 8 + #define UDI_CDC_DEFAULT_RATE 115200 + #define UDI_CDC_DEFAULT_STOPBITS CDC_STOP_BITS_1 + #define UDI_CDC_DEFAULT_PARITY CDC_PAR_NONE + #define UDI_CDC_DEFAULT_DATABITS 8 - #include "udi_cdc_conf.h" // At the end of conf_usb.h file + #include "udi_cdc_conf.h" // At the end of conf_usb.h file \endcode * * Add to application C-file: * \code - static bool my_flag_autorize_cdc_transfert = false; - bool my_callback_cdc_enable(void) - { - my_flag_autorize_cdc_transfert = true; - return true; - } - void my_callback_cdc_disable(void) - { - my_flag_autorize_cdc_transfert = false; - } + static bool my_flag_autorize_cdc_transfert = false; + bool my_callback_cdc_enable(void) { + my_flag_autorize_cdc_transfert = true; + return true; + } + void my_callback_cdc_disable(void) { + my_flag_autorize_cdc_transfert = false; + } - void task(void) - { - if (my_flag_autorize_cdc_transfert) { - udi_cdc_putc('A'); - udi_cdc_getc(); - } - } + void task(void) { + if (my_flag_autorize_cdc_transfert) { + udi_cdc_putc('A'); + udi_cdc_getc(); + } + } \endcode * * \subsection udi_cdc_basic_use_case_setup_flow Workflow @@ -652,14 +646,14 @@ iram_size_t udi_cdc_multi_write_buf(uint8_t port, const void* buf, iram_size_t s * - \code #define USB_DEVICE_SERIAL_NAME "12...EF" // Disk SN for CDC \endcode * \note The USB serial number is mandatory when a CDC interface is used. * - \code #define UDI_CDC_ENABLE_EXT(port) my_callback_cdc_enable() - extern bool my_callback_cdc_enable(void); \endcode + extern bool my_callback_cdc_enable(void); \endcode * \note After the device enumeration (detecting and identifying USB devices), * the USB host starts the device configuration. When the USB CDC interface * from the device is accepted by the host, the USB host enables this interface and the * UDI_CDC_ENABLE_EXT() callback function is called and return true. * Thus, when this event is received, the data transfer on CDC interface are authorized. * - \code #define UDI_CDC_DISABLE_EXT(port) my_callback_cdc_disable() - extern void my_callback_cdc_disable(void); \endcode + extern void my_callback_cdc_disable(void); \endcode * \note When the USB device is unplugged or is reset by the USB host, the USB * interface is disabled and the UDI_CDC_DISABLE_EXT() callback function * is called. Thus, the data transfer must be stopped on CDC interface. @@ -667,19 +661,19 @@ iram_size_t udi_cdc_multi_write_buf(uint8_t port, const void* buf, iram_size_t s * \note Define it when the transfer CDC Device to Host is a low rate * (<512000 bauds) to reduce CDC buffers size. * - \code #define UDI_CDC_DEFAULT_RATE 115200 - #define UDI_CDC_DEFAULT_STOPBITS CDC_STOP_BITS_1 - #define UDI_CDC_DEFAULT_PARITY CDC_PAR_NONE - #define UDI_CDC_DEFAULT_DATABITS 8 \endcode + #define UDI_CDC_DEFAULT_STOPBITS CDC_STOP_BITS_1 + #define UDI_CDC_DEFAULT_PARITY CDC_PAR_NONE + #define UDI_CDC_DEFAULT_DATABITS 8 \endcode * \note Default configuration of communication port at startup. * -# Send or wait data on CDC line: * - \code // Waits and gets a value on CDC line - int udi_cdc_getc(void); - // Reads a RAM buffer on CDC line - iram_size_t udi_cdc_read_buf(int* buf, iram_size_t size); - // Puts a byte on CDC line - int udi_cdc_putc(int value); - // Writes a RAM buffer on CDC line - iram_size_t udi_cdc_write_buf(const int* buf, iram_size_t size); \endcode + int udi_cdc_getc(void); + // Reads a RAM buffer on CDC line + iram_size_t udi_cdc_read_buf(int *buf, iram_size_t size); + // Puts a byte on CDC line + int udi_cdc_putc(int value); + // Writes a RAM buffer on CDC line + iram_size_t udi_cdc_write_buf(const int *buf, iram_size_t size); \endcode * * \section udi_cdc_use_cases Advanced use cases * For more advanced use of the UDI CDC module, see the following use cases: @@ -713,90 +707,90 @@ iram_size_t udi_cdc_multi_write_buf(uint8_t port, const void* buf, iram_size_t s * \subsection udi_cdc_use_case_composite_usage_code Example code * Content of conf_usb.h: * \code - #define USB_DEVICE_EP_CTRL_SIZE 64 - #define USB_DEVICE_NB_INTERFACE (X+2) - #define USB_DEVICE_MAX_EP (X+3) + #define USB_DEVICE_EP_CTRL_SIZE 64 + #define USB_DEVICE_NB_INTERFACE (X+2) + #define USB_DEVICE_MAX_EP (X+3) - #define UDI_CDC_DATA_EP_IN_0 (1 | USB_EP_DIR_IN) // TX - #define UDI_CDC_DATA_EP_OUT_0 (2 | USB_EP_DIR_OUT) // RX - #define UDI_CDC_COMM_EP_0 (3 | USB_EP_DIR_IN) // Notify endpoint - #define UDI_CDC_COMM_IFACE_NUMBER_0 X+0 - #define UDI_CDC_DATA_IFACE_NUMBER_0 X+1 + #define UDI_CDC_DATA_EP_IN_0 (1 | USB_EP_DIR_IN) // TX + #define UDI_CDC_DATA_EP_OUT_0 (2 | USB_EP_DIR_OUT) // RX + #define UDI_CDC_COMM_EP_0 (3 | USB_EP_DIR_IN) // Notify endpoint + #define UDI_CDC_COMM_IFACE_NUMBER_0 X+0 + #define UDI_CDC_DATA_IFACE_NUMBER_0 X+1 - #define UDI_COMPOSITE_DESC_T \ - usb_iad_desc_t udi_cdc_iad; \ - udi_cdc_comm_desc_t udi_cdc_comm; \ - udi_cdc_data_desc_t udi_cdc_data; \ - ... - #define UDI_COMPOSITE_DESC_FS \ - .udi_cdc_iad = UDI_CDC_IAD_DESC_0, \ - .udi_cdc_comm = UDI_CDC_COMM_DESC_0, \ - .udi_cdc_data = UDI_CDC_DATA_DESC_0_FS, \ - ... - #define UDI_COMPOSITE_DESC_HS \ - .udi_cdc_iad = UDI_CDC_IAD_DESC_0, \ - .udi_cdc_comm = UDI_CDC_COMM_DESC_0, \ - .udi_cdc_data = UDI_CDC_DATA_DESC_0_HS, \ - ... - #define UDI_COMPOSITE_API \ - &udi_api_cdc_comm, \ - &udi_api_cdc_data, \ - ... + #define UDI_COMPOSITE_DESC_T \ + usb_iad_desc_t udi_cdc_iad; \ + udi_cdc_comm_desc_t udi_cdc_comm; \ + udi_cdc_data_desc_t udi_cdc_data; \ + ... + #define UDI_COMPOSITE_DESC_FS \ + .udi_cdc_iad = UDI_CDC_IAD_DESC_0, \ + .udi_cdc_comm = UDI_CDC_COMM_DESC_0, \ + .udi_cdc_data = UDI_CDC_DATA_DESC_0_FS, \ + ... + #define UDI_COMPOSITE_DESC_HS \ + .udi_cdc_iad = UDI_CDC_IAD_DESC_0, \ + .udi_cdc_comm = UDI_CDC_COMM_DESC_0, \ + .udi_cdc_data = UDI_CDC_DATA_DESC_0_HS, \ + ... + #define UDI_COMPOSITE_API \ + &udi_api_cdc_comm, \ + &udi_api_cdc_data, \ + ... \endcode * * \subsection udi_cdc_use_case_composite_usage_flow Workflow * -# Ensure that conf_usb.h is available and contains the following parameters * required for a USB composite device configuration: * - \code // Endpoint control size, This must be: - // - 8, 16, 32 or 64 for full speed device (8 is recommended to save RAM) - // - 64 for a high speed device - #define USB_DEVICE_EP_CTRL_SIZE 64 - // Total Number of interfaces on this USB device. - // Add 2 for CDC. - #define USB_DEVICE_NB_INTERFACE (X+2) - // Total number of endpoints on this USB device. - // This must include each endpoint for each interface. - // Add 3 for CDC. - #define USB_DEVICE_MAX_EP (X+3) \endcode + // - 8, 16, 32 or 64 for full speed device (8 is recommended to save RAM) + // - 64 for a high speed device + #define USB_DEVICE_EP_CTRL_SIZE 64 + // Total Number of interfaces on this USB device. + // Add 2 for CDC. + #define USB_DEVICE_NB_INTERFACE (X+2) + // Total number of endpoints on this USB device. + // This must include each endpoint for each interface. + // Add 3 for CDC. + #define USB_DEVICE_MAX_EP (X+3) \endcode * -# Ensure that conf_usb.h contains the description of * composite device: * - \code // The endpoint numbers chosen by you for the CDC. - // The endpoint numbers starting from 1. - #define UDI_CDC_DATA_EP_IN_0 (1 | USB_EP_DIR_IN) // TX - #define UDI_CDC_DATA_EP_OUT_0 (2 | USB_EP_DIR_OUT) // RX - #define UDI_CDC_COMM_EP_0 (3 | USB_EP_DIR_IN) // Notify endpoint - // The interface index of an interface starting from 0 - #define UDI_CDC_COMM_IFACE_NUMBER_0 X+0 - #define UDI_CDC_DATA_IFACE_NUMBER_0 X+1 \endcode + // The endpoint numbers starting from 1. + #define UDI_CDC_DATA_EP_IN_0 (1 | USB_EP_DIR_IN) // TX + #define UDI_CDC_DATA_EP_OUT_0 (2 | USB_EP_DIR_OUT) // RX + #define UDI_CDC_COMM_EP_0 (3 | USB_EP_DIR_IN) // Notify endpoint + // The interface index of an interface starting from 0 + #define UDI_CDC_COMM_IFACE_NUMBER_0 X+0 + #define UDI_CDC_DATA_IFACE_NUMBER_0 X+1 \endcode * -# Ensure that conf_usb.h contains the following parameters * required for a USB composite device configuration: * - \code // USB Interfaces descriptor structure - #define UDI_COMPOSITE_DESC_T \ - ... - usb_iad_desc_t udi_cdc_iad; \ - udi_cdc_comm_desc_t udi_cdc_comm; \ - udi_cdc_data_desc_t udi_cdc_data; \ - ... - // USB Interfaces descriptor value for Full Speed - #define UDI_COMPOSITE_DESC_FS \ - ... - .udi_cdc_iad = UDI_CDC_IAD_DESC_0, \ - .udi_cdc_comm = UDI_CDC_COMM_DESC_0, \ - .udi_cdc_data = UDI_CDC_DATA_DESC_0_FS, \ - ... - // USB Interfaces descriptor value for High Speed - #define UDI_COMPOSITE_DESC_HS \ - ... - .udi_cdc_iad = UDI_CDC_IAD_DESC_0, \ - .udi_cdc_comm = UDI_CDC_COMM_DESC_0, \ - .udi_cdc_data = UDI_CDC_DATA_DESC_0_HS, \ - ... - // USB Interface APIs - #define UDI_COMPOSITE_API \ - ... - &udi_api_cdc_comm, \ - &udi_api_cdc_data, \ - ... \endcode + #define UDI_COMPOSITE_DESC_T \ + ... + usb_iad_desc_t udi_cdc_iad; \ + udi_cdc_comm_desc_t udi_cdc_comm; \ + udi_cdc_data_desc_t udi_cdc_data; \ + ... + // USB Interfaces descriptor value for Full Speed + #define UDI_COMPOSITE_DESC_FS \ + ... + .udi_cdc_iad = UDI_CDC_IAD_DESC_0, \ + .udi_cdc_comm = UDI_CDC_COMM_DESC_0, \ + .udi_cdc_data = UDI_CDC_DATA_DESC_0_FS, \ + ... + // USB Interfaces descriptor value for High Speed + #define UDI_COMPOSITE_DESC_HS \ + ... + .udi_cdc_iad = UDI_CDC_IAD_DESC_0, \ + .udi_cdc_comm = UDI_CDC_COMM_DESC_0, \ + .udi_cdc_data = UDI_CDC_DATA_DESC_0_HS, \ + ... + // USB Interface APIs + #define UDI_COMPOSITE_API \ + ... + &udi_api_cdc_comm, \ + &udi_api_cdc_data, \ + ... \endcode * - \note The descriptors order given in the four lists above must be the * same as the order defined by all interface indexes. The interface index * orders are defined through UDI_X_IFACE_NUMBER defines.\n diff --git a/Marlin/src/HAL/DUE/usb/udi_cdc_conf.h b/Marlin/src/HAL/DUE/usb/udi_cdc_conf.h index d406a87743..e61b8cbaad 100644 --- a/Marlin/src/HAL/DUE/usb/udi_cdc_conf.h +++ b/Marlin/src/HAL/DUE/usb/udi_cdc_conf.h @@ -106,7 +106,7 @@ extern "C" { */ //@{ # if UDI_CDC_PORT_NB > 2 -# error USBB, UDP, UDPHS and UOTGHS interfaces have not enought endpoints. +# error USBB, UDP, UDPHS and UOTGHS interfaces have not enough endpoints. # endif #define UDI_CDC_DATA_EP_IN_0 (1 | USB_EP_DIR_IN) // TX #define UDI_CDC_DATA_EP_OUT_0 (2 | USB_EP_DIR_OUT) // RX diff --git a/Marlin/src/HAL/DUE/usb/udi_cdc_desc.c b/Marlin/src/HAL/DUE/usb/udi_cdc_desc.c index 97c334e2a8..bcae362cef 100644 --- a/Marlin/src/HAL/DUE/usb/udi_cdc_desc.c +++ b/Marlin/src/HAL/DUE/usb/udi_cdc_desc.c @@ -51,7 +51,7 @@ #include "udc_desc.h" #include "udi_cdc.h" -#if DISABLED(SDSUPPORT) +#if !HAS_MEDIA /** * \defgroup udi_cdc_group_single_desc USB device descriptors for a single interface @@ -109,7 +109,6 @@ UDC_DESC_STORAGE usb_dev_desc_t udc_device_desc = { .bNumConfigurations = 1 }; - #ifdef USB_DEVICE_HS_SUPPORT //! USB Device Qualifier Descriptor for HS COMPILER_WORD_ALIGNED @@ -256,6 +255,6 @@ UDC_DESC_STORAGE udc_config_t udc_config = { //@} //@} -#endif // SDSUPPORT +#endif // HAS_MEDIA #endif // ARDUINO_ARCH_SAM diff --git a/Marlin/src/HAL/DUE/usb/udi_composite_desc.c b/Marlin/src/HAL/DUE/usb/udi_composite_desc.c index da74fbe60d..8fa5acbb3f 100644 --- a/Marlin/src/HAL/DUE/usb/udi_composite_desc.c +++ b/Marlin/src/HAL/DUE/usb/udi_composite_desc.c @@ -50,7 +50,7 @@ #include "udd.h" #include "udc_desc.h" -#if ENABLED(SDSUPPORT) +#if HAS_MEDIA /** * \defgroup udi_group_desc Descriptors for a USB Device @@ -93,7 +93,6 @@ UDC_DESC_STORAGE usb_dev_desc_t udc_device_desc = { .bNumConfigurations = 1 }; - #ifdef USB_DEVICE_HS_SUPPORT //! USB Device Qualifier Descriptor for HS COMPILER_WORD_ALIGNED @@ -147,7 +146,6 @@ UDC_DESC_STORAGE udc_desc_t udc_desc_hs = { }; #endif - /** * \name UDC structures which contains all USB Device definitions */ @@ -189,4 +187,4 @@ UDC_DESC_STORAGE udc_config_t udc_config = { #endif // ARDUINO_ARCH_SAM -#endif // SDSUPPORT +#endif // HAS_MEDIA diff --git a/Marlin/src/HAL/DUE/usb/udi_msc.c b/Marlin/src/HAL/DUE/usb/udi_msc.c index b7c3bb5ea0..56664f4bf7 100644 --- a/Marlin/src/HAL/DUE/usb/udi_msc.c +++ b/Marlin/src/HAL/DUE/usb/udi_msc.c @@ -57,7 +57,7 @@ #include "ctrl_access.h" #include -#if ENABLED(SDSUPPORT) +#if HAS_MEDIA #ifndef UDI_MSC_NOTIFY_TRANS_EXT # define UDI_MSC_NOTIFY_TRANS_EXT() @@ -86,7 +86,6 @@ UDC_DESC_STORAGE udi_api_t udi_api_msc = { }; //@} - /** * \ingroup udi_msc_group * \defgroup udi_msc_group_internal Implementation of UDI MSC @@ -137,7 +136,6 @@ volatile bool udi_msc_b_reset_trans = true; //@} - /** * \name Internal routines */ @@ -173,7 +171,7 @@ static void udi_msc_cbw_wait(void); * * \param status UDD_EP_TRANSFER_OK, if transfer is finished * \param status UDD_EP_TRANSFER_ABORT, if transfer is aborted - * \param nb_received number of data transfered + * \param nb_received number of data transferred */ static void udi_msc_cbw_received(udd_ep_status_t status, iram_size_t nb_received, udd_ep_id_t ep); @@ -190,7 +188,6 @@ static void udi_msc_cbw_received(udd_ep_status_t status, static bool udi_msc_cbw_validate(uint32_t alloc_len, uint8_t dir_flag); //@} - /** * \name Routines to process small data packet */ @@ -211,13 +208,12 @@ static void udi_msc_data_send(uint8_t * buffer, uint8_t buf_size); * * \param status UDD_EP_TRANSFER_OK, if transfer finish * \param status UDD_EP_TRANSFER_ABORT, if transfer aborted - * \param nb_sent number of data transfered + * \param nb_sent number of data transferred */ static void udi_msc_data_sent(udd_ep_status_t status, iram_size_t nb_sent, udd_ep_id_t ep); //@} - /** * \name Routines to process CSW packet */ @@ -244,13 +240,12 @@ void udi_msc_csw_send(void); * * \param status UDD_EP_TRANSFER_OK, if transfer is finished * \param status UDD_EP_TRANSFER_ABORT, if transfer is aborted - * \param nb_sent number of data transfered + * \param nb_sent number of data transferred */ static void udi_msc_csw_sent(udd_ep_status_t status, iram_size_t nb_sent, udd_ep_id_t ep); //@} - /** * \name Routines manage sense data */ @@ -307,7 +302,6 @@ static void udi_msc_sense_fail_cdb_invalid(void); static void udi_msc_sense_command_invalid(void); //@} - /** * \name Routines manage SCSI Commands */ @@ -372,9 +366,7 @@ static void udi_msc_sbc_trans(bool b_read); //@} - -bool udi_msc_enable(void) -{ +bool udi_msc_enable(void) { uint8_t lun; udi_msc_b_trans_req = false; udi_msc_b_cbw_invalid = false; @@ -397,18 +389,14 @@ bool udi_msc_enable(void) return true; } - -void udi_msc_disable(void) -{ +void udi_msc_disable(void) { udi_msc_b_trans_req = false; udi_msc_b_ack_trans = true; udi_msc_b_reset_trans = true; UDI_MSC_DISABLE_EXT(); } - -bool udi_msc_setup(void) -{ +bool udi_msc_setup(void) { if (Udd_setup_is_in()) { // Requests Interface GET if (Udd_setup_type() == USB_REQ_TYPE_CLASS) { @@ -451,35 +439,30 @@ bool udi_msc_setup(void) return false; // Not supported request } -uint8_t udi_msc_getsetting(void) -{ +uint8_t udi_msc_getsetting(void) { return 0; // MSC don't have multiple alternate setting } - // ------------------------ //------- Routines to process CBW packet -static void udi_msc_cbw_invalid(void) -{ +static void udi_msc_cbw_invalid(void) { if (!udi_msc_b_cbw_invalid) - return; // Don't re-stall endpoint if error reseted by setup + return; // Don't re-stall endpoint if error reset by setup udd_ep_set_halt(UDI_MSC_EP_OUT); // If stall cleared then re-stall it. Only Setup MSC Reset can clear it udd_ep_wait_stall_clear(UDI_MSC_EP_OUT, udi_msc_cbw_invalid); } -static void udi_msc_csw_invalid(void) -{ +static void udi_msc_csw_invalid(void) { if (!udi_msc_b_cbw_invalid) - return; // Don't re-stall endpoint if error reseted by setup + return; // Don't re-stall endpoint if error reset by setup udd_ep_set_halt(UDI_MSC_EP_IN); // If stall cleared then re-stall it. Only Setup MSC Reset can clear it udd_ep_wait_stall_clear(UDI_MSC_EP_IN, udi_msc_csw_invalid); } -static void udi_msc_cbw_wait(void) -{ +static void udi_msc_cbw_wait(void) { // Register buffer and callback on OUT endpoint if (!udd_ep_run(UDI_MSC_EP_OUT, true, (uint8_t *) & udi_msc_cbw, @@ -490,10 +473,8 @@ static void udi_msc_cbw_wait(void) } } - static void udi_msc_cbw_received(udd_ep_status_t status, - iram_size_t nb_received, udd_ep_id_t ep) -{ + iram_size_t nb_received, udd_ep_id_t ep) { UNUSED(ep); // Check status of transfer if (UDD_EP_TRANSFER_OK != status) { @@ -582,9 +563,7 @@ static void udi_msc_cbw_received(udd_ep_status_t status, } } - -static bool udi_msc_cbw_validate(uint32_t alloc_len, uint8_t dir_flag) -{ +static bool udi_msc_cbw_validate(uint32_t alloc_len, uint8_t dir_flag) { /* * The following cases should result in a phase error: * - Case 2: Hn < Di @@ -612,12 +591,10 @@ static bool udi_msc_cbw_validate(uint32_t alloc_len, uint8_t dir_flag) return true; } - // ------------------------ //------- Routines to process small data packet -static void udi_msc_data_send(uint8_t * buffer, uint8_t buf_size) -{ +static void udi_msc_data_send(uint8_t * buffer, uint8_t buf_size) { // Sends data on IN endpoint if (!udd_ep_run(UDI_MSC_EP_IN, true, buffer, buf_size, udi_msc_data_sent)) { @@ -627,10 +604,8 @@ static void udi_msc_data_send(uint8_t * buffer, uint8_t buf_size) } } - static void udi_msc_data_sent(udd_ep_status_t status, iram_size_t nb_sent, - udd_ep_id_t ep) -{ + udd_ep_id_t ep) { UNUSED(ep); if (UDD_EP_TRANSFER_OK != status) { // Error protocol @@ -644,12 +619,10 @@ static void udi_msc_data_sent(udd_ep_status_t status, iram_size_t nb_sent, udi_msc_csw_process(); } - // ------------------------ //------- Routines to process CSW packet -static void udi_msc_csw_process(void) -{ +static void udi_msc_csw_process(void) { if (0 != udi_msc_csw.dCSWDataResidue) { // Residue not NULL // then STALL next request from USB host on corresponding endpoint @@ -664,9 +637,7 @@ static void udi_msc_csw_process(void) udi_msc_csw_send(); } - -void udi_msc_csw_send(void) -{ +void udi_msc_csw_send(void) { // Sends CSW on IN endpoint if (!udd_ep_run(UDI_MSC_EP_IN, false, (uint8_t *) & udi_msc_csw, @@ -678,10 +649,8 @@ void udi_msc_csw_send(void) } } - static void udi_msc_csw_sent(udd_ep_status_t status, iram_size_t nb_sent, - udd_ep_id_t ep) -{ + udd_ep_id_t ep) { UNUSED(ep); UNUSED(status); UNUSED(nb_sent); @@ -690,20 +659,17 @@ static void udi_msc_csw_sent(udd_ep_status_t status, iram_size_t nb_sent, udi_msc_cbw_wait(); } - // ------------------------ //------- Routines manage sense data -static void udi_msc_clear_sense(void) -{ +static void udi_msc_clear_sense(void) { memset((uint8_t*)&udi_msc_sense, 0, sizeof(struct scsi_request_sense_data)); udi_msc_sense.valid_reponse_code = SCSI_SENSE_VALID | SCSI_SENSE_CURRENT; udi_msc_sense.AddSenseLen = SCSI_SENSE_ADDL_LEN(sizeof(udi_msc_sense)); } static void udi_msc_sense_fail(uint8_t sense_key, uint16_t add_sense, - uint32_t lba) -{ + uint32_t lba) { udi_msc_clear_sense(); udi_msc_csw.bCSWStatus = USB_CSW_STATUS_FAIL; udi_msc_sense.sense_flag_key = sense_key; @@ -715,53 +681,39 @@ static void udi_msc_sense_fail(uint8_t sense_key, uint16_t add_sense, udi_msc_sense.AddSnsCodeQlfr = add_sense; } -static void udi_msc_sense_pass(void) -{ +static void udi_msc_sense_pass(void) { udi_msc_clear_sense(); udi_msc_csw.bCSWStatus = USB_CSW_STATUS_PASS; } - -static void udi_msc_sense_fail_not_present(void) -{ +static void udi_msc_sense_fail_not_present(void) { udi_msc_sense_fail(SCSI_SK_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT, 0); } -static void udi_msc_sense_fail_busy_or_change(void) -{ - udi_msc_sense_fail(SCSI_SK_UNIT_ATTENTION, - SCSI_ASC_NOT_READY_TO_READY_CHANGE, 0); +static void udi_msc_sense_fail_busy_or_change(void) { + udi_msc_sense_fail(SCSI_SK_UNIT_ATTENTION, SCSI_ASC_NOT_READY_TO_READY_CHANGE, 0); } -static void udi_msc_sense_fail_hardware(void) -{ - udi_msc_sense_fail(SCSI_SK_HARDWARE_ERROR, - SCSI_ASC_NO_ADDITIONAL_SENSE_INFO, 0); +static void udi_msc_sense_fail_hardware(void) { + udi_msc_sense_fail(SCSI_SK_HARDWARE_ERROR, SCSI_ASC_NO_ADDITIONAL_SENSE_INFO, 0); } -static void udi_msc_sense_fail_protected(void) -{ +static void udi_msc_sense_fail_protected(void) { udi_msc_sense_fail(SCSI_SK_DATA_PROTECT, SCSI_ASC_WRITE_PROTECTED, 0); } -static void udi_msc_sense_fail_cdb_invalid(void) -{ - udi_msc_sense_fail(SCSI_SK_ILLEGAL_REQUEST, - SCSI_ASC_INVALID_FIELD_IN_CDB, 0); +static void udi_msc_sense_fail_cdb_invalid(void) { + udi_msc_sense_fail(SCSI_SK_ILLEGAL_REQUEST, SCSI_ASC_INVALID_FIELD_IN_CDB, 0); } -static void udi_msc_sense_command_invalid(void) -{ - udi_msc_sense_fail(SCSI_SK_ILLEGAL_REQUEST, - SCSI_ASC_INVALID_COMMAND_OPERATION_CODE, 0); +static void udi_msc_sense_command_invalid(void) { + udi_msc_sense_fail(SCSI_SK_ILLEGAL_REQUEST, SCSI_ASC_INVALID_COMMAND_OPERATION_CODE, 0); } - // ------------------------ //------- Routines manage SCSI Commands -static void udi_msc_spc_requestsense(void) -{ +static void udi_msc_spc_requestsense(void) { uint8_t length = udi_msc_cbw.CDB[4]; // Can't send more than sense data length @@ -774,9 +726,7 @@ static void udi_msc_spc_requestsense(void) udi_msc_data_send((uint8_t*)&udi_msc_sense, length); } - -static void udi_msc_spc_inquiry(void) -{ +static void udi_msc_spc_inquiry(void) { uint8_t length, i; UDC_DATA(4) // Constant inquiry data for all LUNs @@ -835,9 +785,7 @@ static void udi_msc_spc_inquiry(void) udi_msc_data_send((uint8_t *) & udi_msc_inquiry_data, length); } - -static bool udi_msc_spc_testunitready_global(void) -{ +static bool udi_msc_spc_testunitready_global(void) { switch (mem_test_unit_ready(udi_msc_cbw.bCBWLUN)) { case CTRL_GOOD: return true; // Don't change sense data @@ -855,9 +803,7 @@ static bool udi_msc_spc_testunitready_global(void) return false; } - -static void udi_msc_spc_testunitready(void) -{ +static void udi_msc_spc_testunitready(void) { if (udi_msc_spc_testunitready_global()) { // LUN ready, then update sense data with status pass udi_msc_sense_pass(); @@ -866,9 +812,7 @@ static void udi_msc_spc_testunitready(void) udi_msc_csw_process(); } - -static void udi_msc_spc_mode_sense(bool b_sense10) -{ +static void udi_msc_spc_mode_sense(bool b_sense10) { // Union of all mode sense structures union sense_6_10 { struct { @@ -943,9 +887,7 @@ static void udi_msc_spc_mode_sense(bool b_sense10) udi_msc_data_send((uint8_t *) & sense, request_lgt); } - -static void udi_msc_spc_prevent_allow_medium_removal(void) -{ +static void udi_msc_spc_prevent_allow_medium_removal(void) { uint8_t prevent = udi_msc_cbw.CDB[4]; if (0 == prevent) { udi_msc_sense_pass(); @@ -955,9 +897,7 @@ static void udi_msc_spc_prevent_allow_medium_removal(void) udi_msc_csw_process(); } - -static void udi_msc_sbc_start_stop(void) -{ +static void udi_msc_sbc_start_stop(void) { bool start = 0x1 & udi_msc_cbw.CDB[4]; bool loej = 0x2 & udi_msc_cbw.CDB[4]; if (loej) { @@ -967,9 +907,7 @@ static void udi_msc_sbc_start_stop(void) udi_msc_csw_process(); } - -static void udi_msc_sbc_read_capacity(void) -{ +static void udi_msc_sbc_read_capacity(void) { UDC_BSS(4) static struct sbc_read_capacity10_data udi_msc_capacity; if (!udi_msc_cbw_validate(sizeof(udi_msc_capacity), @@ -1003,9 +941,7 @@ static void udi_msc_sbc_read_capacity(void) sizeof(udi_msc_capacity)); } - -static void udi_msc_sbc_trans(bool b_read) -{ +static void udi_msc_sbc_trans(bool b_read) { uint32_t trans_size; if (!b_read) { @@ -1038,9 +974,7 @@ static void udi_msc_sbc_trans(bool b_read) UDI_MSC_NOTIFY_TRANS_EXT(); } - -bool udi_msc_process_trans(void) -{ +bool udi_msc_process_trans(void) { Ctrl_status status; if (!udi_msc_b_trans_req) @@ -1084,10 +1018,8 @@ bool udi_msc_process_trans(void) return true; } - static void udi_msc_trans_ack(udd_ep_status_t status, iram_size_t n, - udd_ep_id_t ep) -{ + udd_ep_id_t ep) { UNUSED(ep); UNUSED(n); // Update variable to signal the end of transfer @@ -1095,10 +1027,8 @@ static void udi_msc_trans_ack(udd_ep_status_t status, iram_size_t n, udi_msc_b_ack_trans = true; } - bool udi_msc_trans_block(bool b_read, uint8_t * block, iram_size_t block_size, - void (*callback) (udd_ep_status_t status, iram_size_t n, udd_ep_id_t ep)) -{ + void (*callback) (udd_ep_status_t status, iram_size_t n, udd_ep_id_t ep)) { if (!udi_msc_b_ack_trans) return false; // No possible, transfer on going @@ -1127,6 +1057,6 @@ bool udi_msc_trans_block(bool b_read, uint8_t * block, iram_size_t block_size, //@} -#endif // SDSUPPORT +#endif // HAS_MEDIA #endif // ARDUINO_ARCH_SAM diff --git a/Marlin/src/HAL/DUE/usb/udi_msc.h b/Marlin/src/HAL/DUE/usb/udi_msc.h index 730dbc8eec..0ede4d6a83 100644 --- a/Marlin/src/HAL/DUE/usb/udi_msc.h +++ b/Marlin/src/HAL/DUE/usb/udi_msc.h @@ -77,9 +77,9 @@ extern UDC_DESC_STORAGE udi_api_t udi_api_msc; //! Interface descriptor structure for MSC typedef struct { - usb_iface_desc_t iface; - usb_ep_desc_t ep_in; - usb_ep_desc_t ep_out; + usb_iface_desc_t iface; + usb_ep_desc_t ep_in; + usb_ep_desc_t ep_out; } udi_msc_desc_t; //! By default no string associated to this interface @@ -94,32 +94,32 @@ typedef struct { //! Content of MSC interface descriptor for all speeds #define UDI_MSC_DESC \ - .iface.bLength = sizeof(usb_iface_desc_t),\ - .iface.bDescriptorType = USB_DT_INTERFACE,\ - .iface.bInterfaceNumber = UDI_MSC_IFACE_NUMBER,\ - .iface.bAlternateSetting = 0,\ - .iface.bNumEndpoints = 2,\ - .iface.bInterfaceClass = MSC_CLASS,\ - .iface.bInterfaceSubClass = MSC_SUBCLASS_TRANSPARENT,\ - .iface.bInterfaceProtocol = MSC_PROTOCOL_BULK,\ - .iface.iInterface = UDI_MSC_STRING_ID,\ - .ep_in.bLength = sizeof(usb_ep_desc_t),\ - .ep_in.bDescriptorType = USB_DT_ENDPOINT,\ - .ep_in.bEndpointAddress = UDI_MSC_EP_IN,\ - .ep_in.bmAttributes = USB_EP_TYPE_BULK,\ - .ep_in.bInterval = 0,\ - .ep_out.bLength = sizeof(usb_ep_desc_t),\ - .ep_out.bDescriptorType = USB_DT_ENDPOINT,\ - .ep_out.bEndpointAddress = UDI_MSC_EP_OUT,\ - .ep_out.bmAttributes = USB_EP_TYPE_BULK,\ - .ep_out.bInterval = 0, + .iface.bLength = sizeof(usb_iface_desc_t),\ + .iface.bDescriptorType = USB_DT_INTERFACE,\ + .iface.bInterfaceNumber = UDI_MSC_IFACE_NUMBER,\ + .iface.bAlternateSetting = 0,\ + .iface.bNumEndpoints = 2,\ + .iface.bInterfaceClass = MSC_CLASS,\ + .iface.bInterfaceSubClass = MSC_SUBCLASS_TRANSPARENT,\ + .iface.bInterfaceProtocol = MSC_PROTOCOL_BULK,\ + .iface.iInterface = UDI_MSC_STRING_ID,\ + .ep_in.bLength = sizeof(usb_ep_desc_t),\ + .ep_in.bDescriptorType = USB_DT_ENDPOINT,\ + .ep_in.bEndpointAddress = UDI_MSC_EP_IN,\ + .ep_in.bmAttributes = USB_EP_TYPE_BULK,\ + .ep_in.bInterval = 0,\ + .ep_out.bLength = sizeof(usb_ep_desc_t),\ + .ep_out.bDescriptorType = USB_DT_ENDPOINT,\ + .ep_out.bEndpointAddress = UDI_MSC_EP_OUT,\ + .ep_out.bmAttributes = USB_EP_TYPE_BULK,\ + .ep_out.bInterval = 0, //! Content of MSC interface descriptor for full speed only #define UDI_MSC_DESC_FS {\ - UDI_MSC_DESC \ - .ep_in.wMaxPacketSize = LE16(UDI_MSC_EPS_SIZE_FS),\ - .ep_out.wMaxPacketSize = LE16(UDI_MSC_EPS_SIZE_FS),\ - } + UDI_MSC_DESC \ + .ep_in.wMaxPacketSize = LE16(UDI_MSC_EPS_SIZE_FS),\ + .ep_out.wMaxPacketSize = LE16(UDI_MSC_EPS_SIZE_FS),\ + } //! Content of MSC interface descriptor for high speed only #define UDI_MSC_DESC_HS {\ @@ -129,7 +129,6 @@ typedef struct { } //@} - /** * \ingroup udi_group * \defgroup udi_msc_group USB Device Interface (UDI) for Mass Storage Class (MSC) @@ -163,14 +162,13 @@ bool udi_msc_process_trans(void); * \return \c 1 if function was successfully done, otherwise \c 0. */ bool udi_msc_trans_block(bool b_read, uint8_t * block, iram_size_t block_size, - void (*callback) (udd_ep_status_t status, iram_size_t n, udd_ep_id_t ep)); + void (*callback) (udd_ep_status_t status, iram_size_t n, udd_ep_id_t ep)); //@} #ifdef __cplusplus } #endif - /** * \page udi_msc_quickstart Quick start guide for USB device Mass Storage module (UDI MSC) * @@ -200,35 +198,32 @@ bool udi_msc_trans_block(bool b_read, uint8_t * block, iram_size_t block_size, * \subsection udi_msc_basic_use_case_usage_code Example code * Content of conf_usb.h: * \code - #define USB_DEVICE_SERIAL_NAME "12...EF" // Disk SN for MSC - #define UDI_MSC_GLOBAL_VENDOR_ID \ - 'A', 'T', 'M', 'E', 'L', ' ', ' ', ' ' - #define UDI_MSC_GLOBAL_PRODUCT_VERSION \ - '1', '.', '0', '0' - #define UDI_MSC_ENABLE_EXT() my_callback_msc_enable() - extern bool my_callback_msc_enable(void); - #define UDI_MSC_DISABLE_EXT() my_callback_msc_disable() - extern void my_callback_msc_disable(void); - #include "udi_msc_conf.h" // At the end of conf_usb.h file + #define USB_DEVICE_SERIAL_NAME "12...EF" // Disk SN for MSC + #define UDI_MSC_GLOBAL_VENDOR_ID \ + 'A', 'T', 'M', 'E', 'L', ' ', ' ', ' ' + #define UDI_MSC_GLOBAL_PRODUCT_VERSION \ + '1', '.', '0', '0' + #define UDI_MSC_ENABLE_EXT() my_callback_msc_enable() + extern bool my_callback_msc_enable(void); + #define UDI_MSC_DISABLE_EXT() my_callback_msc_disable() + extern void my_callback_msc_disable(void); + #include "udi_msc_conf.h" // At the end of conf_usb.h file \endcode * * Add to application C-file: * \code - static bool my_flag_autorize_msc_transfert = false; - bool my_callback_msc_enable(void) - { - my_flag_autorize_msc_transfert = true; - return true; - } - void my_callback_msc_disable(void) - { - my_flag_autorize_msc_transfert = false; - } + static bool my_flag_autorize_msc_transfert = false; + bool my_callback_msc_enable(void) { + my_flag_autorize_msc_transfert = true; + return true; + } + void my_callback_msc_disable(void) { + my_flag_autorize_msc_transfert = false; + } - void task(void) - { - udi_msc_process_trans(); - } + void task(void) { + udi_msc_process_trans(); + } \endcode * * \subsection udi_msc_basic_use_case_setup_flow Workflow @@ -237,14 +232,14 @@ bool udi_msc_trans_block(bool b_read, uint8_t * block, iram_size_t block_size, * - \code #define USB_DEVICE_SERIAL_NAME "12...EF" // Disk SN for MSC \endcode * \note The USB serial number is mandatory when a MSC interface is used. * - \code //! Vendor name and Product version of MSC interface - #define UDI_MSC_GLOBAL_VENDOR_ID \ - 'A', 'T', 'M', 'E', 'L', ' ', ' ', ' ' - #define UDI_MSC_GLOBAL_PRODUCT_VERSION \ - '1', '.', '0', '0' \endcode + #define UDI_MSC_GLOBAL_VENDOR_ID \ + 'A', 'T', 'M', 'E', 'L', ' ', ' ', ' ' + #define UDI_MSC_GLOBAL_PRODUCT_VERSION \ + '1', '.', '0', '0' \endcode * \note The USB MSC interface requires a vendor ID (8 ASCII characters) * and a product version (4 ASCII characters). * - \code #define UDI_MSC_ENABLE_EXT() my_callback_msc_enable() - extern bool my_callback_msc_enable(void); \endcode + extern bool my_callback_msc_enable(void); \endcode * \note After the device enumeration (detecting and identifying USB devices), * the USB host starts the device configuration. When the USB MSC interface * from the device is accepted by the host, the USB host enables this interface and the @@ -252,7 +247,7 @@ bool udi_msc_trans_block(bool b_read, uint8_t * block, iram_size_t block_size, * Thus, when this event is received, the tasks which call * udi_msc_process_trans() must be enabled. * - \code #define UDI_MSC_DISABLE_EXT() my_callback_msc_disable() - extern void my_callback_msc_disable(void); \endcode + extern void my_callback_msc_disable(void); \endcode * \note When the USB device is unplugged or is reset by the USB host, the USB * interface is disabled and the UDI_MSC_DISABLE_EXT() callback function * is called. Thus, it is recommended to disable the task which is called udi_msc_process_trans(). @@ -261,15 +256,15 @@ bool udi_msc_trans_block(bool b_read, uint8_t * block, iram_size_t block_size, * must be done outside USB interrupt routine. This is done in the MSC process * ("udi_msc_process_trans()") called by main loop: * - \code * void task(void) { - udi_msc_process_trans(); - } \endcode + udi_msc_process_trans(); + } \endcode * -# The MSC speed depends on task periodicity. To get the best speed * the notification callback "UDI_MSC_NOTIFY_TRANS_EXT" can be used to wakeup * this task (Example, through a mutex): * - \code #define UDI_MSC_NOTIFY_TRANS_EXT() msc_notify_trans() - void msc_notify_trans(void) { - wakeup_my_task(); - } \endcode + void msc_notify_trans(void) { + wakeup_my_task(); + } \endcode * * \section udi_msc_use_cases Advanced use cases * For more advanced use of the UDI MSC module, see the following use cases: @@ -302,72 +297,72 @@ bool udi_msc_trans_block(bool b_read, uint8_t * block, iram_size_t block_size, * \subsection udi_msc_use_case_composite_usage_code Example code * Content of conf_usb.h: * \code - #define USB_DEVICE_EP_CTRL_SIZE 64 - #define USB_DEVICE_NB_INTERFACE (X+1) - #define USB_DEVICE_MAX_EP (X+2) + #define USB_DEVICE_EP_CTRL_SIZE 64 + #define USB_DEVICE_NB_INTERFACE (X+1) + #define USB_DEVICE_MAX_EP (X+2) - #define UDI_MSC_EP_IN (X | USB_EP_DIR_IN) - #define UDI_MSC_EP_OUT (Y | USB_EP_DIR_OUT) - #define UDI_MSC_IFACE_NUMBER X + #define UDI_MSC_EP_IN (X | USB_EP_DIR_IN) + #define UDI_MSC_EP_OUT (Y | USB_EP_DIR_OUT) + #define UDI_MSC_IFACE_NUMBER X - #define UDI_COMPOSITE_DESC_T \ - udi_msc_desc_t udi_msc; \ - ... - #define UDI_COMPOSITE_DESC_FS \ - .udi_msc = UDI_MSC_DESC, \ - ... - #define UDI_COMPOSITE_DESC_HS \ - .udi_msc = UDI_MSC_DESC, \ - ... - #define UDI_COMPOSITE_API \ - &udi_api_msc, \ - ... + #define UDI_COMPOSITE_DESC_T \ + udi_msc_desc_t udi_msc; \ + ... + #define UDI_COMPOSITE_DESC_FS \ + .udi_msc = UDI_MSC_DESC, \ + ... + #define UDI_COMPOSITE_DESC_HS \ + .udi_msc = UDI_MSC_DESC, \ + ... + #define UDI_COMPOSITE_API \ + &udi_api_msc, \ + ... \endcode * * \subsection udi_msc_use_case_composite_usage_flow Workflow * -# Ensure that conf_usb.h is available and contains the following parameters * required for a USB composite device configuration: * - \code // Endpoint control size, This must be: - // - 8, 16, 32 or 64 for full speed device (8 is recommended to save RAM) - // - 64 for a high speed device - #define USB_DEVICE_EP_CTRL_SIZE 64 - // Total Number of interfaces on this USB device. - // Add 1 for MSC. - #define USB_DEVICE_NB_INTERFACE (X+1) - // Total number of endpoints on this USB device. - // This must include each endpoint for each interface. - // Add 2 for MSC. - #define USB_DEVICE_MAX_EP (X+2) \endcode + // - 8, 16, 32 or 64 for full speed device (8 is recommended to save RAM) + // - 64 for a high speed device + #define USB_DEVICE_EP_CTRL_SIZE 64 + // Total Number of interfaces on this USB device. + // Add 1 for MSC. + #define USB_DEVICE_NB_INTERFACE (X+1) + // Total number of endpoints on this USB device. + // This must include each endpoint for each interface. + // Add 2 for MSC. + #define USB_DEVICE_MAX_EP (X+2) \endcode * -# Ensure that conf_usb.h contains the description of * composite device: * - \code // The endpoint numbers chosen by you for the MSC. - // The endpoint numbers starting from 1. - #define UDI_MSC_EP_IN (X | USB_EP_DIR_IN) - #define UDI_MSC_EP_OUT (Y | USB_EP_DIR_OUT) - // The interface index of an interface starting from 0 - #define UDI_MSC_IFACE_NUMBER X \endcode + // The endpoint numbers starting from 1. + #define UDI_MSC_EP_IN (X | USB_EP_DIR_IN) + #define UDI_MSC_EP_OUT (Y | USB_EP_DIR_OUT) + // The interface index of an interface starting from 0 + #define UDI_MSC_IFACE_NUMBER X \endcode * -# Ensure that conf_usb.h contains the following parameters * required for a USB composite device configuration: * - \code // USB Interfaces descriptor structure - #define UDI_COMPOSITE_DESC_T \ - ... - udi_msc_desc_t udi_msc; \ - ... - // USB Interfaces descriptor value for Full Speed - #define UDI_COMPOSITE_DESC_FS \ - ... - .udi_msc = UDI_MSC_DESC_FS, \ - ... - // USB Interfaces descriptor value for High Speed - #define UDI_COMPOSITE_DESC_HS \ - ... - .udi_msc = UDI_MSC_DESC_HS, \ - ... - // USB Interface APIs - #define UDI_COMPOSITE_API \ - ... - &udi_api_msc, \ - ... \endcode + #define UDI_COMPOSITE_DESC_T \ + ... + udi_msc_desc_t udi_msc; \ + ... + // USB Interfaces descriptor value for Full Speed + #define UDI_COMPOSITE_DESC_FS \ + ... + .udi_msc = UDI_MSC_DESC_FS, \ + ... + // USB Interfaces descriptor value for High Speed + #define UDI_COMPOSITE_DESC_HS \ + ... + .udi_msc = UDI_MSC_DESC_HS, \ + ... + // USB Interface APIs + #define UDI_COMPOSITE_API \ + ... + &udi_api_msc, \ + ... \endcode * - \note The descriptors order given in the four lists above must be the * same as the order defined by all interface indexes. The interface index * orders are defined through UDI_X_IFACE_NUMBER defines. diff --git a/Marlin/src/HAL/DUE/usb/uotghs_device_due.c b/Marlin/src/HAL/DUE/usb/uotghs_device_due.c index e13232a39c..5635f2ba0c 100644 --- a/Marlin/src/HAL/DUE/usb/uotghs_device_due.c +++ b/Marlin/src/HAL/DUE/usb/uotghs_device_due.c @@ -116,6 +116,23 @@ //#define dbg_print printf #define dbg_print(...) +// Marlin modification: Redefine the otg_freeze_clock and otg_unfreeze_clock macros +// to add memory barriers to ensure that any accesses to USB registers aren't re-ordered +// to occur while the clock is frozen. +#undef otg_freeze_clock +#undef otg_unfreeze_clock + +#define otg_freeze_clock() do { \ + __DSB(); \ + Set_bits(UOTGHS->UOTGHS_CTRL, UOTGHS_CTRL_FRZCLK); \ +} while (0) + +#define otg_unfreeze_clock() \ +do { \ + Clr_bits(UOTGHS->UOTGHS_CTRL, UOTGHS_CTRL_FRZCLK); \ + __DSB(); \ +} while (0) + /** * \ingroup udd_group * \defgroup udd_udphs_group USB On-The-Go High-Speed Port for device mode (UOTGHS) @@ -276,7 +293,6 @@ # endif #endif - /** * \name Power management routine. */ @@ -293,7 +309,6 @@ static bool udd_b_idle; //! State of sleep manager static bool udd_b_sleep_initialized = false; - /*! \brief Authorize or not the CPU powerdown mode * * \param b_enable true to authorize idle mode @@ -321,11 +336,10 @@ static void udd_sleep_mode(bool b_idle) //@} - /** * \name Control endpoint low level management routine. * - * This function performs control endpoint mangement. + * This function performs control endpoint management. * It handle the SETUP/DATA/HANDSHAKE phases of a control transaction. */ //@{ @@ -393,24 +407,22 @@ static void udd_ctrl_send_zlp_out(void); //! \brief Call callback associated to setup request static void udd_ctrl_endofrequest(void); - /** * \brief Main interrupt routine for control endpoint * - * This switchs control endpoint events to correct sub function. + * This switches control endpoint events to correct sub function. * - * \return \c 1 if an event about control endpoint is occured, otherwise \c 0. + * \return \c 1 if an event about control endpoint is occurred, otherwise \c 0. */ static bool udd_ctrl_interrupt(void); //@} - /** * \name Management of bulk/interrupt/isochronous endpoints * * The UDD manages the data transfer on endpoints: - * - Start data tranfer on endpoint with USB Device DMA + * - Start data transfer on endpoint with USB Device DMA * - Send a ZLP packet if requested * - Call callback registered to signal end of transfer * The transfer abort and stall feature are supported. @@ -431,7 +443,7 @@ typedef struct { uint8_t *buf; //! Size of buffer to send or fill iram_size_t buf_size; - //!< Size of data transfered + //!< Size of data transferred iram_size_t buf_cnt; //!< Size of data loaded (or prepared for DMA) last time iram_size_t buf_load; @@ -443,7 +455,6 @@ typedef struct { uint8_t stall_requested:1; } udd_ep_job_t; - //! Array to register a job on bulk/interrupt/isochronous endpoint static udd_ep_job_t udd_ep_job[USB_DEVICE_MAX_EP]; @@ -486,7 +497,7 @@ static void udd_ep_finish_job(udd_ep_job_t * ptr_job, bool b_abort, uint8_t ep_n #ifdef UDD_EP_DMA_SUPPORTED /** - * \brief Start the next transfer if necessary or complet the job associated. + * \brief Start the next transfer if necessary or complete the job associated. * * \param ep endpoint number without direction flag */ @@ -496,16 +507,15 @@ static void udd_ep_finish_job(udd_ep_job_t * ptr_job, bool b_abort, uint8_t ep_n /** * \brief Main interrupt routine for bulk/interrupt/isochronous endpoints * - * This switchs endpoint events to correct sub function. + * This switches endpoint events to correct sub function. * - * \return \c 1 if an event about bulk/interrupt/isochronous endpoints has occured, otherwise \c 0. + * \return \c 1 if an event about bulk/interrupt/isochronous endpoints has occurred, otherwise \c 0. */ static bool udd_ep_interrupt(void); #endif // (0!=USB_DEVICE_MAX_EP) //@} - // ------------------------ //--- INTERNAL ROUTINES TO MANAGED GLOBAL EVENTS @@ -513,14 +523,14 @@ 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) * * Note: * Here, the global interrupt mask is not clear when an USB interrupt is enabled - * because this one can not be occured during the USB ISR (=during INTX is masked). + * because this one can not be occurred during the USB ISR (=during INTX is masked). * See Technical reference $3.8.3 Masking interrupt requests in peripheral modules. */ #ifdef UHD_ENABLE @@ -611,6 +621,18 @@ ISR(UDD_USB_INT_FUN) // The wakeup interrupt is automatic acked when a suspend occur udd_disable_wake_up_interrupt(); udd_enable_suspend_interrupt(); + + // Marlin modification: The RESET, SOF, and MSOF interrupts were previously + // enabled in udd_attach, which caused a race condition where they could + // be raised and unclearable with the clock is frozen. They are now + // enabled here, after the clock has been unfrozen in response to the wake + // interrupt. + udd_enable_reset_interrupt(); + udd_enable_sof_interrupt(); +#ifdef USB_DEVICE_HS_SUPPORT + udd_enable_msof_interrupt(); +#endif + udd_sleep_mode(true); // Enter in IDLE mode #ifdef UDC_RESUME_EVENT UDC_RESUME_EVENT(); @@ -642,13 +664,11 @@ udd_interrupt_sof_end: return; } - bool udd_include_vbus_monitoring(void) { return true; } - void udd_enable(void) { irqflags_t flags; @@ -735,7 +755,6 @@ void udd_enable(void) cpu_irq_restore(flags); } - void udd_disable(void) { irqflags_t flags; @@ -776,6 +795,27 @@ void udd_disable(void) cpu_irq_restore(flags); } +// Marlin modification: The original implementation did not use a memory +// barrier between disabling and clearing interrupts. This sometimes +// allowed interrupts to remain raised and unclearable after the clock +// was frozen. This helper was added to ensure that memory barriers +// are used consistently from all places where interrupts are disabled. +static void disable_and_ack_sync_interrupts() +{ + // Disable USB line events + udd_disable_reset_interrupt(); + udd_disable_sof_interrupt(); +#ifdef USB_DEVICE_HS_SUPPORT + udd_disable_msof_interrupt(); +#endif + __DSB(); + udd_ack_reset(); + udd_ack_sof(); +#ifdef USB_DEVICE_HS_SUPPORT + udd_ack_msof(); +#endif + __DSB(); +} void udd_attach(void) { @@ -787,7 +827,7 @@ void udd_attach(void) udd_sleep_mode(true); otg_unfreeze_clock(); - // This section of clock check can be improved with a chek of + // This section of clock check can be improved with a check of // USB clock source via sysclk() // Check USB clock because the source can be a PLL while (!Is_otg_clock_usable()); @@ -796,17 +836,16 @@ void udd_attach(void) udd_attach_device(); // Enable USB line events - udd_enable_reset_interrupt(); udd_enable_suspend_interrupt(); udd_enable_wake_up_interrupt(); - udd_enable_sof_interrupt(); -#ifdef USB_DEVICE_HS_SUPPORT - udd_enable_msof_interrupt(); -#endif - // Reset following interupts flag - udd_ack_reset(); - udd_ack_sof(); - udd_ack_msof(); + + // Marlin modification: The RESET, SOF, and MSOF interrupts were previously + // enabled here, which caused a race condition where they could be raised + // and unclearable with the clock is frozen. They are now enabled in the + // wake interrupt handler, after the clock has been unfrozen. They are now + // explicitly disabled here to ensure that they cannot be raised before + // the clock is frozen. + disable_and_ack_sync_interrupts(); // The first suspend interrupt must be forced // The first suspend interrupt is not detected else raise it @@ -817,18 +856,22 @@ void udd_attach(void) cpu_irq_restore(flags); } - void udd_detach(void) { otg_unfreeze_clock(); // Detach device from the bus udd_detach_device(); + + // Marlin modification: Added the explicit disabling of the RESET, SOF, and + // MSOF interrupts here, to ensure that they cannot be raised after the + // clock is frozen. + disable_and_ack_sync_interrupts(); + otg_freeze_clock(); udd_sleep_mode(false); } - bool udd_is_high_speed(void) { #ifdef USB_DEVICE_HS_SUPPORT @@ -838,7 +881,6 @@ bool udd_is_high_speed(void) #endif } - void udd_set_address(uint8_t address) { udd_disable_address(); @@ -846,13 +888,11 @@ void udd_set_address(uint8_t address) udd_enable_address(); } - uint8_t udd_getaddress(void) { return udd_get_configured_address(); } - uint16_t udd_get_frame_number(void) { return udd_frame_number(); @@ -875,14 +915,12 @@ void udd_send_remotewakeup(void) } } - void udd_set_setup_payload(uint8_t *payload, uint16_t payload_size) { udd_g_ctrlreq.payload = payload; udd_g_ctrlreq.payload_size = payload_size; } - #if (0 != USB_DEVICE_MAX_EP) bool udd_ep_alloc(udd_ep_id_t ep, uint8_t bmAttributes, uint16_t MaxEndpointSize) @@ -902,7 +940,7 @@ bool udd_ep_alloc(udd_ep_id_t ep, uint8_t bmAttributes, } dbg_print("alloc(%x, %d) ", ep, MaxEndpointSize); - // Bank choise + // Bank choice switch (bmAttributes & USB_EP_TYPE_MASK) { case USB_EP_TYPE_ISOCHRONOUS: nb_bank = UDD_ISOCHRONOUS_NB_BANK(ep); @@ -1006,7 +1044,6 @@ bool udd_ep_alloc(udd_ep_id_t ep, uint8_t bmAttributes, return true; } - void udd_ep_free(udd_ep_id_t ep) { uint8_t ep_index = ep & USB_EP_ADDR_MASK; @@ -1019,14 +1056,12 @@ void udd_ep_free(udd_ep_id_t ep) udd_ep_job[ep_index - 1].stall_requested = false; } - bool udd_ep_is_halted(udd_ep_id_t ep) { uint8_t ep_index = ep & USB_EP_ADDR_MASK; return Is_udd_endpoint_stall_requested(ep_index); } - bool udd_ep_set_halt(udd_ep_id_t ep) { uint8_t ep_index = ep & USB_EP_ADDR_MASK; @@ -1067,7 +1102,6 @@ bool udd_ep_set_halt(udd_ep_id_t ep) return true; } - bool udd_ep_clear_halt(udd_ep_id_t ep) { uint8_t ep_index = ep & USB_EP_ADDR_MASK; @@ -1108,7 +1142,6 @@ bool udd_ep_clear_halt(udd_ep_id_t ep) return true; } - bool udd_ep_run(udd_ep_id_t ep, bool b_shortpacket, uint8_t * buf, iram_size_t buf_size, udd_callback_trans_t callback) @@ -1175,7 +1208,6 @@ bool udd_ep_run(udd_ep_id_t ep, bool b_shortpacket, #endif } - void udd_ep_abort(udd_ep_id_t ep) { uint8_t ep_index = ep & USB_EP_ADDR_MASK; @@ -1204,7 +1236,6 @@ void udd_ep_abort(udd_ep_id_t ep) udd_ep_abort_job(ep); } - bool udd_ep_wait_stall_clear(udd_ep_id_t ep, udd_callback_halt_cleared_t callback) { @@ -1228,7 +1259,7 @@ bool udd_ep_wait_stall_clear(udd_ep_id_t ep, if (Is_udd_endpoint_stall_requested(ep) || ptr_job->stall_requested) { - // Endpoint halted then registes the callback + // Endpoint halted then registers the callback ptr_job->busy = true; ptr_job->call_nohalt = callback; } else { @@ -1239,7 +1270,6 @@ bool udd_ep_wait_stall_clear(udd_ep_id_t ep, } #endif // (0 != USB_DEVICE_MAX_EP) - #ifdef USB_DEVICE_HS_SUPPORT void udd_test_mode_j(void) @@ -1248,20 +1278,17 @@ void udd_test_mode_j(void) udd_enable_hs_test_mode_j(); } - void udd_test_mode_k(void) { udd_enable_hs_test_mode(); udd_enable_hs_test_mode_k(); } - void udd_test_mode_se0_nak(void) { udd_enable_hs_test_mode(); } - void udd_test_mode_packet(void) { uint8_t i; @@ -1305,8 +1332,6 @@ void udd_test_mode_packet(void) } #endif // USB_DEVICE_HS_SUPPORT - - // ------------------------ //--- INTERNAL ROUTINES TO MANAGED THE CONTROL ENDPOINT @@ -1356,7 +1381,6 @@ static void udd_ctrl_init(void) udd_ep_control_state = UDD_EPCTRL_SETUP; } - static void udd_ctrl_setup_received(void) { irqflags_t flags; @@ -1386,7 +1410,7 @@ static void udd_ctrl_setup_received(void) // Decode setup request if (udc_process_setup() == false) { - // Setup request unknow then stall it + // Setup request unknown then stall it udd_ctrl_stall_data(); udd_ack_setup_received(0); return; @@ -1418,7 +1442,6 @@ static void udd_ctrl_setup_received(void) } } - static void udd_ctrl_in_sent(void) { static bool b_shortpacket = false; @@ -1447,7 +1470,7 @@ static void udd_ctrl_in_sent(void) udd_ctrl_prev_payload_buf_cnt += udd_ctrl_payload_buf_cnt; if ((udd_g_ctrlreq.req.wLength == udd_ctrl_prev_payload_buf_cnt) || b_shortpacket) { - // All data requested are transfered or a short packet has been sent + // All data requested are transferred or a short packet has been sent // then it is the end of data phase. // Generate an OUT ZLP for handshake phase. udd_ctrl_send_zlp_out(); @@ -1502,7 +1525,6 @@ static void udd_ctrl_in_sent(void) cpu_irq_restore(flags); } - static void udd_ctrl_out_received(void) { irqflags_t flags; @@ -1516,7 +1538,7 @@ static void udd_ctrl_out_received(void) // End of SETUP request: // - Data IN Phase aborted, // - or last Data IN Phase hidden by ZLP OUT sending quiclky, - // - or ZLP OUT received normaly. + // - or ZLP OUT received normally. udd_ctrl_endofrequest(); } else { // Protocol error during SETUP request @@ -1544,8 +1566,8 @@ static void udd_ctrl_out_received(void) (udd_ctrl_prev_payload_buf_cnt + udd_ctrl_payload_buf_cnt))) { // End of reception because it is a short packet - // Before send ZLP, call intermediat calback - // in case of data receiv generate a stall + // Before send ZLP, call intermediate callback + // 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()) { @@ -1565,7 +1587,7 @@ static void udd_ctrl_out_received(void) if (udd_g_ctrlreq.payload_size == udd_ctrl_payload_buf_cnt) { // Overrun then request a new payload buffer if (!udd_g_ctrlreq.over_under_run) { - // No callback availabled to request a new payload buffer + // No callback available to request a new payload buffer udd_ctrl_stall_data(); // Ack reception of OUT to replace NAK by a STALL udd_ack_out_received(0); @@ -1593,7 +1615,6 @@ static void udd_ctrl_out_received(void) cpu_irq_restore(flags); } - static void udd_ctrl_underflow(void) { if (Is_udd_out_received(0)) @@ -1610,7 +1631,6 @@ static void udd_ctrl_underflow(void) } } - static void udd_ctrl_overflow(void) { if (Is_udd_in_send(0)) @@ -1626,7 +1646,6 @@ static void udd_ctrl_overflow(void) } } - static void udd_ctrl_stall_data(void) { // Stall all packets on IN & OUT control endpoint @@ -1634,7 +1653,6 @@ static void udd_ctrl_stall_data(void) udd_enable_stall_handshake(0); } - static void udd_ctrl_send_zlp_in(void) { irqflags_t flags; @@ -1652,7 +1670,6 @@ static void udd_ctrl_send_zlp_in(void) cpu_irq_restore(flags); } - static void udd_ctrl_send_zlp_out(void) { irqflags_t flags; @@ -1668,7 +1685,6 @@ static void udd_ctrl_send_zlp_out(void) cpu_irq_restore(flags); } - static void udd_ctrl_endofrequest(void) { // If a callback is registered then call it @@ -1677,7 +1693,6 @@ static void udd_ctrl_endofrequest(void) } } - static bool udd_ctrl_interrupt(void) { @@ -1728,7 +1743,6 @@ static bool udd_ctrl_interrupt(void) return false; } - // ------------------------ //--- INTERNAL ROUTINES TO MANAGED THE BULK/INTERRUPT/ISOCHRONOUS ENDPOINTS @@ -1743,7 +1757,6 @@ static void udd_ep_job_table_reset(void) } } - static void udd_ep_job_table_kill(void) { uint8_t i; @@ -1754,7 +1767,6 @@ static void udd_ep_job_table_kill(void) } } - static void udd_ep_abort_job(udd_ep_id_t ep) { ep &= USB_EP_ADDR_MASK; @@ -1763,7 +1775,6 @@ static void udd_ep_abort_job(udd_ep_id_t ep) udd_ep_finish_job(&udd_ep_job[ep - 1], true, ep); } - static void udd_ep_finish_job(udd_ep_job_t * ptr_job, bool b_abort, uint8_t ep_num) { if (ptr_job->busy == false) { @@ -1797,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) { @@ -1805,7 +1816,7 @@ static void udd_ep_trans_done(udd_ep_id_t ep) // transfer size of UDD_ENDPOINT_MAX_TRANS Bytes next_trans = UDD_ENDPOINT_MAX_TRANS; - // Set 0 to tranfer the maximum + // Set 0 to transfer the maximum udd_dma_ctrl = UOTGHS_DEVDMACONTROL_BUFF_LENGTH(0); } else { udd_dma_ctrl = UOTGHS_DEVDMACONTROL_BUFF_LENGTH(next_trans); @@ -1834,7 +1845,6 @@ static void udd_ep_trans_done(udd_ep_id_t ep) udd_dma_ctrl |= UOTGHS_DEVDMACONTROL_END_BUFFIT | UOTGHS_DEVDMACONTROL_CHANN_ENB; - // Disable IRQs to have a short sequence // between read of EOT_STA and DMA enable flags = cpu_irq_save(); @@ -1850,7 +1860,7 @@ static void udd_ep_trans_done(udd_ep_id_t ep) } cpu_irq_restore(flags); - // Here a ZLP has been recieved + // Here a ZLP has been received // and the DMA transfer must be not started. // It is the end of transfer ptr_job->buf_size = ptr_job->buf_cnt; @@ -1991,13 +2001,13 @@ static bool udd_ep_interrupt(void) } dbg_print("dma%x: ", ep); udd_disable_endpoint_dma_interrupt(ep); - // Save number of data no transfered + // Save number of data no transferred nb_remaining = (udd_endpoint_dma_get_status(ep) & UOTGHS_DEVDMASTATUS_BUFF_COUNT_Msk) >> UOTGHS_DEVDMASTATUS_BUFF_COUNT_Pos; if (nb_remaining) { // Transfer no complete (short packet or ZLP) then: - // Update number of data transfered + // Update number of data transferred ptr_job->buf_cnt -= nb_remaining; // Set transfer complete to stop the transfer ptr_job->buf_size = ptr_job->buf_cnt; @@ -2043,6 +2053,12 @@ static bool udd_ep_interrupt(void) dbg_print("I "); udd_disable_in_send_interrupt(ep); // One bank is free then send a ZLP + + // Marlin modification: Add a barrier to ensure in_send is disabled + // before it is cleared. This was not an observed problem, but + // other interrupts were seen to misbehave without this barrier. + __DSB(); + udd_ack_in_send(ep); udd_ack_fifocon(ep); udd_ep_finish_job(ptr_job, false, ep); @@ -2056,7 +2072,7 @@ static bool udd_ep_interrupt(void) udd_disable_endpoint_interrupt(ep); Assert(ptr_job->stall_requested); - // A stall has been requested during backgound transfer + // A stall has been requested during background transfer ptr_job->stall_requested = false; udd_disable_endpoint_bank_autoswitch(ep); udd_enable_stall_handshake(ep); diff --git a/Marlin/src/HAL/DUE/usb/uotghs_device_due.h b/Marlin/src/HAL/DUE/usb/uotghs_device_due.h index 6df26d63df..99ad492c1f 100644 --- a/Marlin/src/HAL/DUE/usb/uotghs_device_due.h +++ b/Marlin/src/HAL/DUE/usb/uotghs_device_due.h @@ -129,7 +129,6 @@ extern "C" { #define Is_udd_vbus_transition() (Tst_bits(UOTGHS->UOTGHS_SR, UOTGHS_SR_VBUSTI)) //! @} - //! @name UOTGHS device attach control //! These macros manage the UOTGHS Device attach. //! @{ @@ -141,7 +140,6 @@ extern "C" { #define Is_udd_detached() (Tst_bits(UOTGHS->UOTGHS_DEVCTRL, UOTGHS_DEVCTRL_DETACH)) //! @} - //! @name UOTGHS device bus events control //! These macros manage the UOTGHS Device bus events. //! @{ @@ -246,7 +244,6 @@ extern "C" { #define udd_get_configured_address() (Rd_bitfield(UOTGHS->UOTGHS_DEVCTRL, UOTGHS_DEVCTRL_UADD_Msk)) //! @} - //! @name UOTGHS Device endpoint drivers //! These macros manage the common features of the endpoints. //! @{ @@ -330,7 +327,6 @@ extern "C" { #define udd_data_toggle(ep) (Rd_bitfield(UOTGHS_ARRAY(UOTGHS_DEVEPTISR[0], ep), UOTGHS_DEVEPTISR_DTSEQ_Msk)) //! @} - //! @name UOTGHS Device control endpoint //! These macros control the endpoints. //! @{ @@ -530,7 +526,6 @@ extern "C" { //! Tests if IN sending interrupt is enabled #define Is_udd_in_send_interrupt_enabled(ep) (Tst_bits(UOTGHS_ARRAY(UOTGHS_DEVEPTIMR[0], ep), UOTGHS_DEVEPTIMR_TXINE)) - //! Get 64-, 32-, 16- or 8-bit access to FIFO data register of selected endpoint. //! @param ep Endpoint of which to access FIFO data register //! @param scale Data scale in bits: 64, 32, 16 or 8 @@ -652,7 +647,6 @@ typedef struct { //! @} //! @} - /// @cond 0 /**INDENT-OFF**/ #ifdef __cplusplus diff --git a/Marlin/src/HAL/DUE/usb/uotghs_otg.h b/Marlin/src/HAL/DUE/usb/uotghs_otg.h index eca5e938bb..8c12a3e291 100644 --- a/Marlin/src/HAL/DUE/usb/uotghs_otg.h +++ b/Marlin/src/HAL/DUE/usb/uotghs_otg.h @@ -53,7 +53,6 @@ extern "C" { #endif - //! \ingroup usb_group //! \defgroup otg_group UOTGHS OTG Driver //! UOTGHS low-level driver for OTG features @@ -74,7 +73,6 @@ bool otg_dual_enable(void); */ void otg_dual_disable(void); - //! @name UOTGHS OTG ID pin management //! The ID pin come from the USB OTG connector (A and B receptable) and //! allows to select the USB mode host or device. @@ -127,13 +125,13 @@ void otg_dual_disable(void); //! These macros allows to enable/disable pad and UOTGHS hardware //! @{ //! Reset USB macro -#define otg_reset() \ - do { \ - UOTGHS->UOTGHS_CTRL = 0; \ - while( UOTGHS->UOTGHS_SR & 0x3FFF) {\ - UOTGHS->UOTGHS_SCR = 0xFFFFFFFF;\ - } \ - } while (0) +#define otg_reset() \ + do { \ + UOTGHS->UOTGHS_CTRL = 0; \ + while( UOTGHS->UOTGHS_SR & 0x3FFF) { \ + UOTGHS->UOTGHS_SCR = 0xFFFFFFFF; \ + } \ + } while (0) //! Enable USB macro #define otg_enable() (Set_bits(UOTGHS->UOTGHS_CTRL, UOTGHS_CTRL_USBE)) //! Disable USB macro @@ -157,15 +155,14 @@ void otg_dual_disable(void); //! Configure time-out of specified OTG timer #define otg_configure_timeout(timer, timeout) (Set_bits(UOTGHS->UOTGHS_CTRL, UOTGHS_CTRL_UNLOCK),\ - Wr_bitfield(UOTGHS->UOTGHS_CTRL, UOTGHS_CTRL_TIMPAGE_Msk, timer),\ - Wr_bitfield(UOTGHS->UOTGHS_CTRL, UOTGHS_CTRL_TIMVALUE_Msk, timeout),\ - Clr_bits(UOTGHS->UOTGHS_CTRL, UOTGHS_CTRL_UNLOCK)) + Wr_bitfield(UOTGHS->UOTGHS_CTRL, UOTGHS_CTRL_TIMPAGE_Msk, timer),\ + Wr_bitfield(UOTGHS->UOTGHS_CTRL, UOTGHS_CTRL_TIMVALUE_Msk, timeout),\ + Clr_bits(UOTGHS->UOTGHS_CTRL, UOTGHS_CTRL_UNLOCK)) //! Get configured time-out of specified OTG timer #define otg_get_timeout(timer) (Set_bits(UOTGHS->UOTGHS_CTRL, UOTGHS_CTRL_UNLOCK),\ - Wr_bitfield(UOTGHS->UOTGHS_CTRL, UOTGHS_CTRL_TIMPAGE_Msk, timer),\ - Clr_bits(UOTGHS->UOTGHS_CTRL, UOTGHS_CTRL_UNLOCK),\ - Rd_bitfield(UOTGHS->UOTGHS_CTRL, UOTGHS_CTRL_TIMVALUE_Msk)) - + Wr_bitfield(UOTGHS->UOTGHS_CTRL, UOTGHS_CTRL_TIMPAGE_Msk, timer),\ + Clr_bits(UOTGHS->UOTGHS_CTRL, UOTGHS_CTRL_UNLOCK),\ + Rd_bitfield(UOTGHS->UOTGHS_CTRL, UOTGHS_CTRL_TIMVALUE_Msk)) //! Get the dual-role device state of the internal USB finite state machine of the UOTGHS controller #define otg_get_fsm_drd_state() (Rd_bitfield(UOTGHS->UOTGHS_FSM, UOTGHS_FSM_DRDSTATE_Msk)) diff --git a/Marlin/src/HAL/DUE/usb/usb_protocol.h b/Marlin/src/HAL/DUE/usb/usb_protocol.h index ea51a86896..9bf0a1ba60 100644 --- a/Marlin/src/HAL/DUE/usb/usb_protocol.h +++ b/Marlin/src/HAL/DUE/usb/usb_protocol.h @@ -108,17 +108,17 @@ * \brief Standard USB requests (bRequest) */ enum usb_reqid { - USB_REQ_GET_STATUS = 0, - USB_REQ_CLEAR_FEATURE = 1, - USB_REQ_SET_FEATURE = 3, - USB_REQ_SET_ADDRESS = 5, - USB_REQ_GET_DESCRIPTOR = 6, - USB_REQ_SET_DESCRIPTOR = 7, - USB_REQ_GET_CONFIGURATION = 8, - USB_REQ_SET_CONFIGURATION = 9, - USB_REQ_GET_INTERFACE = 10, - USB_REQ_SET_INTERFACE = 11, - USB_REQ_SYNCH_FRAME = 12, + USB_REQ_GET_STATUS = 0, + USB_REQ_CLEAR_FEATURE = 1, + USB_REQ_SET_FEATURE = 3, + USB_REQ_SET_ADDRESS = 5, + USB_REQ_GET_DESCRIPTOR = 6, + USB_REQ_SET_DESCRIPTOR = 7, + USB_REQ_GET_CONFIGURATION = 8, + USB_REQ_SET_CONFIGURATION = 9, + USB_REQ_GET_INTERFACE = 10, + USB_REQ_SET_INTERFACE = 11, + USB_REQ_SYNCH_FRAME = 12, }; /** @@ -126,9 +126,9 @@ enum usb_reqid { * */ enum usb_device_status { - USB_DEV_STATUS_BUS_POWERED = 0, - USB_DEV_STATUS_SELF_POWERED = 1, - USB_DEV_STATUS_REMOTEWAKEUP = 2 + USB_DEV_STATUS_BUS_POWERED = 0, + USB_DEV_STATUS_SELF_POWERED = 1, + USB_DEV_STATUS_REMOTEWAKEUP = 2 }; /** @@ -136,7 +136,7 @@ enum usb_device_status { * */ enum usb_interface_status { - USB_IFACE_STATUS_RESERVED = 0 + USB_IFACE_STATUS_RESERVED = 0 }; /** @@ -144,7 +144,7 @@ enum usb_interface_status { * */ enum usb_endpoint_status { - USB_EP_STATUS_HALTED = 1, + USB_EP_STATUS_HALTED = 1, }; /** @@ -153,11 +153,11 @@ enum usb_endpoint_status { * \note valid for SetFeature request. */ enum usb_device_feature { - USB_DEV_FEATURE_REMOTE_WAKEUP = 1, //!< Remote wakeup enabled - USB_DEV_FEATURE_TEST_MODE = 2, //!< USB test mode - USB_DEV_FEATURE_OTG_B_HNP_ENABLE = 3, - USB_DEV_FEATURE_OTG_A_HNP_SUPPORT = 4, - USB_DEV_FEATURE_OTG_A_ALT_HNP_SUPPORT = 5 + USB_DEV_FEATURE_REMOTE_WAKEUP = 1, //!< Remote wakeup enabled + USB_DEV_FEATURE_TEST_MODE = 2, //!< USB test mode + USB_DEV_FEATURE_OTG_B_HNP_ENABLE = 3, + USB_DEV_FEATURE_OTG_A_HNP_SUPPORT = 4, + USB_DEV_FEATURE_OTG_A_ALT_HNP_SUPPORT = 5 }; /** @@ -166,54 +166,54 @@ enum usb_device_feature { * \note valid for USB_DEV_FEATURE_TEST_MODE request. */ enum usb_device_hs_test_mode { - USB_DEV_TEST_MODE_J = 1, - USB_DEV_TEST_MODE_K = 2, - USB_DEV_TEST_MODE_SE0_NAK = 3, - USB_DEV_TEST_MODE_PACKET = 4, - USB_DEV_TEST_MODE_FORCE_ENABLE = 5, + USB_DEV_TEST_MODE_J = 1, + USB_DEV_TEST_MODE_K = 2, + USB_DEV_TEST_MODE_SE0_NAK = 3, + USB_DEV_TEST_MODE_PACKET = 4, + USB_DEV_TEST_MODE_FORCE_ENABLE = 5, }; /** * \brief Standard USB endpoint feature/status flags */ enum usb_endpoint_feature { - USB_EP_FEATURE_HALT = 0, + USB_EP_FEATURE_HALT = 0, }; /** * \brief Standard USB Test Mode Selectors */ enum usb_test_mode_selector { - USB_TEST_J = 0x01, - USB_TEST_K = 0x02, - USB_TEST_SE0_NAK = 0x03, - USB_TEST_PACKET = 0x04, - USB_TEST_FORCE_ENABLE = 0x05, + USB_TEST_J = 0x01, + USB_TEST_K = 0x02, + USB_TEST_SE0_NAK = 0x03, + USB_TEST_PACKET = 0x04, + USB_TEST_FORCE_ENABLE = 0x05, }; /** * \brief Standard USB descriptor types */ enum usb_descriptor_type { - USB_DT_DEVICE = 1, - USB_DT_CONFIGURATION = 2, - USB_DT_STRING = 3, - USB_DT_INTERFACE = 4, - USB_DT_ENDPOINT = 5, - USB_DT_DEVICE_QUALIFIER = 6, - USB_DT_OTHER_SPEED_CONFIGURATION = 7, - USB_DT_INTERFACE_POWER = 8, - USB_DT_OTG = 9, - USB_DT_IAD = 0x0B, - USB_DT_BOS = 0x0F, - USB_DT_DEVICE_CAPABILITY = 0x10, + USB_DT_DEVICE = 1, + USB_DT_CONFIGURATION = 2, + USB_DT_STRING = 3, + USB_DT_INTERFACE = 4, + USB_DT_ENDPOINT = 5, + USB_DT_DEVICE_QUALIFIER = 6, + USB_DT_OTHER_SPEED_CONFIGURATION = 7, + USB_DT_INTERFACE_POWER = 8, + USB_DT_OTG = 9, + USB_DT_IAD = 0x0B, + USB_DT_BOS = 0x0F, + USB_DT_DEVICE_CAPABILITY = 0x10, }; /** * \brief USB Device Capability types */ enum usb_capability_type { - USB_DC_USB20_EXTENSION = 0x02, + USB_DC_USB20_EXTENSION = 0x02, }; /** @@ -221,7 +221,7 @@ enum usb_capability_type { * To fill bmAttributes field of usb_capa_ext_desc_t structure. */ enum usb_capability_extension_attr { - USB_DC_EXT_LPM = 0x00000002, + USB_DC_EXT_LPM = 0x00000002, }; #define HIRD_50_US 0 @@ -254,18 +254,18 @@ enum usb_capability_extension_attr { * \brief Standard USB endpoint transfer types */ enum usb_ep_type { - USB_EP_TYPE_CONTROL = 0x00, - USB_EP_TYPE_ISOCHRONOUS = 0x01, - USB_EP_TYPE_BULK = 0x02, - USB_EP_TYPE_INTERRUPT = 0x03, - USB_EP_TYPE_MASK = 0x03, + USB_EP_TYPE_CONTROL = 0x00, + USB_EP_TYPE_ISOCHRONOUS = 0x01, + USB_EP_TYPE_BULK = 0x02, + USB_EP_TYPE_INTERRUPT = 0x03, + USB_EP_TYPE_MASK = 0x03, }; /** * \brief Standard USB language IDs for string descriptors */ enum usb_langid { - USB_LANGID_EN_US = 0x0409, //!< English (United States) + USB_LANGID_EN_US = 0x0409, //!< English (United States) }; /** @@ -308,31 +308,31 @@ COMPILER_PACK_SET(1) * The data payload of SETUP packets always follows this structure. */ typedef struct { - uint8_t bmRequestType; - uint8_t bRequest; - le16_t wValue; - le16_t wIndex; - le16_t wLength; + uint8_t bmRequestType; + uint8_t bRequest; + le16_t wValue; + le16_t wIndex; + le16_t wLength; } usb_setup_req_t; /** * \brief Standard USB device descriptor structure */ typedef struct { - uint8_t bLength; - uint8_t bDescriptorType; - le16_t bcdUSB; - uint8_t bDeviceClass; - uint8_t bDeviceSubClass; - uint8_t bDeviceProtocol; - uint8_t bMaxPacketSize0; - le16_t idVendor; - le16_t idProduct; - le16_t bcdDevice; - uint8_t iManufacturer; - uint8_t iProduct; - uint8_t iSerialNumber; - uint8_t bNumConfigurations; + uint8_t bLength; + uint8_t bDescriptorType; + le16_t bcdUSB; + uint8_t bDeviceClass; + uint8_t bDeviceSubClass; + uint8_t bDeviceProtocol; + uint8_t bMaxPacketSize0; + le16_t idVendor; + le16_t idProduct; + le16_t bcdDevice; + uint8_t iManufacturer; + uint8_t iProduct; + uint8_t iSerialNumber; + uint8_t bNumConfigurations; } usb_dev_desc_t; /** @@ -344,15 +344,15 @@ typedef struct { * the device was operating at full speed.) */ typedef struct { - uint8_t bLength; - uint8_t bDescriptorType; - le16_t bcdUSB; - uint8_t bDeviceClass; - uint8_t bDeviceSubClass; - uint8_t bDeviceProtocol; - uint8_t bMaxPacketSize0; - uint8_t bNumConfigurations; - uint8_t bReserved; + uint8_t bLength; + uint8_t bDescriptorType; + le16_t bcdUSB; + uint8_t bDeviceClass; + uint8_t bDeviceSubClass; + uint8_t bDeviceProtocol; + uint8_t bMaxPacketSize0; + uint8_t bNumConfigurations; + uint8_t bReserved; } usb_dev_qual_desc_t; /** @@ -368,23 +368,22 @@ typedef struct { * The descriptor type in the GetDescriptor() request is set to BOS. */ typedef struct { - uint8_t bLength; - uint8_t bDescriptorType; - le16_t wTotalLength; - uint8_t bNumDeviceCaps; + uint8_t bLength; + uint8_t bDescriptorType; + le16_t wTotalLength; + uint8_t bNumDeviceCaps; } usb_dev_bos_desc_t; - /** * \brief USB Device Capabilities - USB 2.0 Extension Descriptor structure * * Defines the set of USB 1.1-specific device level capabilities. */ typedef struct { - uint8_t bLength; - uint8_t bDescriptorType; - uint8_t bDevCapabilityType; - le32_t bmAttributes; + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDevCapabilityType; + le32_t bmAttributes; } usb_dev_capa_ext_desc_t; /** @@ -393,40 +392,38 @@ typedef struct { * The BOS descriptor and capabilities descriptors for LPM. */ typedef struct { - usb_dev_bos_desc_t bos; - usb_dev_capa_ext_desc_t capa_ext; + usb_dev_bos_desc_t bos; + usb_dev_capa_ext_desc_t capa_ext; } usb_dev_lpm_desc_t; /** * \brief Standard USB Interface Association Descriptor structure */ typedef struct { - uint8_t bLength; //!< size of this descriptor in bytes - uint8_t bDescriptorType; //!< INTERFACE descriptor type - uint8_t bFirstInterface; //!< Number of interface - uint8_t bInterfaceCount; //!< value to select alternate setting - uint8_t bFunctionClass; //!< Class code assigned by the USB - uint8_t bFunctionSubClass;//!< Sub-class code assigned by the USB - uint8_t bFunctionProtocol;//!< Protocol code assigned by the USB - uint8_t iFunction; //!< Index of string descriptor + uint8_t bLength; //!< size of this descriptor in bytes + uint8_t bDescriptorType; //!< INTERFACE descriptor type + uint8_t bFirstInterface; //!< Number of interface + uint8_t bInterfaceCount; //!< value to select alternate setting + uint8_t bFunctionClass; //!< Class code assigned by the USB + uint8_t bFunctionSubClass;//!< Sub-class code assigned by the USB + uint8_t bFunctionProtocol;//!< Protocol code assigned by the USB + uint8_t iFunction; //!< Index of string descriptor } usb_association_desc_t; - /** * \brief Standard USB configuration descriptor structure */ typedef struct { - uint8_t bLength; - uint8_t bDescriptorType; - le16_t wTotalLength; - uint8_t bNumInterfaces; - uint8_t bConfigurationValue; - uint8_t iConfiguration; - uint8_t bmAttributes; - uint8_t bMaxPower; + uint8_t bLength; + uint8_t bDescriptorType; + le16_t wTotalLength; + uint8_t bNumInterfaces; + uint8_t bConfigurationValue; + uint8_t iConfiguration; + uint8_t bmAttributes; + uint8_t bMaxPower; } usb_conf_desc_t; - #define USB_CONFIG_ATTR_MUST_SET (1 << 7) //!< Must always be set #define USB_CONFIG_ATTR_BUS_POWERED (0 << 6) //!< Bus-powered #define USB_CONFIG_ATTR_SELF_POWERED (1 << 6) //!< Self-powered @@ -438,55 +435,54 @@ typedef struct { * \brief Standard USB association descriptor structure */ typedef struct { - uint8_t bLength; //!< Size of this descriptor in bytes - uint8_t bDescriptorType; //!< Interface descriptor type - uint8_t bFirstInterface; //!< Number of interface - uint8_t bInterfaceCount; //!< value to select alternate setting - uint8_t bFunctionClass; //!< Class code assigned by the USB - uint8_t bFunctionSubClass; //!< Sub-class code assigned by the USB - uint8_t bFunctionProtocol; //!< Protocol code assigned by the USB - uint8_t iFunction; //!< Index of string descriptor + uint8_t bLength; //!< Size of this descriptor in bytes + uint8_t bDescriptorType; //!< Interface descriptor type + uint8_t bFirstInterface; //!< Number of interface + uint8_t bInterfaceCount; //!< value to select alternate setting + uint8_t bFunctionClass; //!< Class code assigned by the USB + uint8_t bFunctionSubClass; //!< Sub-class code assigned by the USB + uint8_t bFunctionProtocol; //!< Protocol code assigned by the USB + uint8_t iFunction; //!< Index of string descriptor } usb_iad_desc_t; /** * \brief Standard USB interface descriptor structure */ typedef struct { - uint8_t bLength; - uint8_t bDescriptorType; - uint8_t bInterfaceNumber; - uint8_t bAlternateSetting; - uint8_t bNumEndpoints; - uint8_t bInterfaceClass; - uint8_t bInterfaceSubClass; - uint8_t bInterfaceProtocol; - uint8_t iInterface; + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bInterfaceNumber; + uint8_t bAlternateSetting; + uint8_t bNumEndpoints; + uint8_t bInterfaceClass; + uint8_t bInterfaceSubClass; + uint8_t bInterfaceProtocol; + uint8_t iInterface; } usb_iface_desc_t; /** * \brief Standard USB endpoint descriptor structure */ typedef struct { - uint8_t bLength; - uint8_t bDescriptorType; - uint8_t bEndpointAddress; - uint8_t bmAttributes; - le16_t wMaxPacketSize; - uint8_t bInterval; + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bEndpointAddress; + uint8_t bmAttributes; + le16_t wMaxPacketSize; + uint8_t bInterval; } usb_ep_desc_t; - /** * \brief A standard USB string descriptor structure */ typedef struct { - uint8_t bLength; - uint8_t bDescriptorType; + uint8_t bLength; + uint8_t bDescriptorType; } usb_str_desc_t; typedef struct { - usb_str_desc_t desc; - le16_t string[1]; + usb_str_desc_t desc; + le16_t string[1]; } usb_str_lgid_desc_t; COMPILER_PACK_RESET() diff --git a/Marlin/src/HAL/DUE/usb/usb_protocol_cdc.h b/Marlin/src/HAL/DUE/usb/usb_protocol_cdc.h index d594db52e3..769e7589bc 100644 --- a/Marlin/src/HAL/DUE/usb/usb_protocol_cdc.h +++ b/Marlin/src/HAL/DUE/usb/usb_protocol_cdc.h @@ -58,42 +58,42 @@ * \name Possible values of class */ //@{ -#define CDC_CLASS_DEVICE 0x02 //!< USB Communication Device Class -#define CDC_CLASS_COMM 0x02 //!< CDC Communication Class Interface -#define CDC_CLASS_DATA 0x0A //!< CDC Data Class Interface +#define CDC_CLASS_DEVICE 0x02 //!< USB Communication Device Class +#define CDC_CLASS_COMM 0x02 //!< CDC Communication Class Interface +#define CDC_CLASS_DATA 0x0A //!< CDC Data Class Interface #define CDC_CLASS_MULTI 0xEF //!< CDC Multi-interface Function //@} //! \name USB CDC Subclass IDs //@{ -#define CDC_SUBCLASS_DLCM 0x01 //!< Direct Line Control Model -#define CDC_SUBCLASS_ACM 0x02 //!< Abstract Control Model -#define CDC_SUBCLASS_TCM 0x03 //!< Telephone Control Model -#define CDC_SUBCLASS_MCCM 0x04 //!< Multi-Channel Control Model -#define CDC_SUBCLASS_CCM 0x05 //!< CAPI Control Model -#define CDC_SUBCLASS_ETH 0x06 //!< Ethernet Networking Control Model -#define CDC_SUBCLASS_ATM 0x07 //!< ATM Networking Control Model +#define CDC_SUBCLASS_DLCM 0x01 //!< Direct Line Control Model +#define CDC_SUBCLASS_ACM 0x02 //!< Abstract Control Model +#define CDC_SUBCLASS_TCM 0x03 //!< Telephone Control Model +#define CDC_SUBCLASS_MCCM 0x04 //!< Multi-Channel Control Model +#define CDC_SUBCLASS_CCM 0x05 //!< CAPI Control Model +#define CDC_SUBCLASS_ETH 0x06 //!< Ethernet Networking Control Model +#define CDC_SUBCLASS_ATM 0x07 //!< ATM Networking Control Model //@} //! \name USB CDC Communication Interface Protocol IDs //@{ -#define CDC_PROTOCOL_V25TER 0x01 //!< Common AT commands +#define CDC_PROTOCOL_V25TER 0x01 //!< Common AT commands //@} //! \name USB CDC Data Interface Protocol IDs //@{ -#define CDC_PROTOCOL_I430 0x30 //!< ISDN BRI -#define CDC_PROTOCOL_HDLC 0x31 //!< HDLC -#define CDC_PROTOCOL_TRANS 0x32 //!< Transparent -#define CDC_PROTOCOL_Q921M 0x50 //!< Q.921 management protocol -#define CDC_PROTOCOL_Q921 0x51 //!< Q.931 [sic] Data link protocol -#define CDC_PROTOCOL_Q921TM 0x52 //!< Q.921 TEI-multiplexor -#define CDC_PROTOCOL_V42BIS 0x90 //!< Data compression procedures -#define CDC_PROTOCOL_Q931 0x91 //!< Euro-ISDN protocol control -#define CDC_PROTOCOL_V120 0x92 //!< V.24 rate adaption to ISDN -#define CDC_PROTOCOL_CAPI20 0x93 //!< CAPI Commands -#define CDC_PROTOCOL_HOST 0xFD //!< Host based driver +#define CDC_PROTOCOL_I430 0x30 //!< ISDN BRI +#define CDC_PROTOCOL_HDLC 0x31 //!< HDLC +#define CDC_PROTOCOL_TRANS 0x32 //!< Transparent +#define CDC_PROTOCOL_Q921M 0x50 //!< Q.921 management protocol +#define CDC_PROTOCOL_Q921 0x51 //!< Q.931 [sic] Data link protocol +#define CDC_PROTOCOL_Q921TM 0x52 //!< Q.921 TEI-multiplexor +#define CDC_PROTOCOL_V42BIS 0x90 //!< Data compression procedures +#define CDC_PROTOCOL_Q931 0x91 //!< Euro-ISDN protocol control +#define CDC_PROTOCOL_V120 0x92 //!< V.24 rate adaption to ISDN +#define CDC_PROTOCOL_CAPI20 0x93 //!< CAPI Commands +#define CDC_PROTOCOL_HOST 0xFD //!< Host based driver /** * \brief Describes the Protocol Unit Functional Descriptors [sic] * on Communication Class Interface @@ -103,16 +103,16 @@ //! \name USB CDC Functional Descriptor Types //@{ -#define CDC_CS_INTERFACE 0x24 //!< Interface Functional Descriptor -#define CDC_CS_ENDPOINT 0x25 //!< Endpoint Functional Descriptor +#define CDC_CS_INTERFACE 0x24 //!< Interface Functional Descriptor +#define CDC_CS_ENDPOINT 0x25 //!< Endpoint Functional Descriptor //@} //! \name USB CDC Functional Descriptor Subtypes //@{ -#define CDC_SCS_HEADER 0x00 //!< Header Functional Descriptor -#define CDC_SCS_CALL_MGMT 0x01 //!< Call Management -#define CDC_SCS_ACM 0x02 //!< Abstract Control Management -#define CDC_SCS_UNION 0x06 //!< Union Functional Descriptor +#define CDC_SCS_HEADER 0x00 //!< Header Functional Descriptor +#define CDC_SCS_CALL_MGMT 0x01 //!< Call Management +#define CDC_SCS_ACM 0x02 //!< Abstract Control Management +#define CDC_SCS_UNION 0x06 //!< Union Functional Descriptor //@} //! \name USB CDC Request IDs @@ -168,42 +168,40 @@ COMPILER_PACK_SET(1) //! \name USB CDC Descriptors //@{ - //! CDC Header Functional Descriptor typedef struct { - uint8_t bFunctionLength; - uint8_t bDescriptorType; - uint8_t bDescriptorSubtype; - le16_t bcdCDC; + uint8_t bFunctionLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + le16_t bcdCDC; } usb_cdc_hdr_desc_t; //! CDC Call Management Functional Descriptor typedef struct { - uint8_t bFunctionLength; - uint8_t bDescriptorType; - uint8_t bDescriptorSubtype; - uint8_t bmCapabilities; - uint8_t bDataInterface; + uint8_t bFunctionLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint8_t bmCapabilities; + uint8_t bDataInterface; } usb_cdc_call_mgmt_desc_t; //! CDC ACM Functional Descriptor typedef struct { - uint8_t bFunctionLength; - uint8_t bDescriptorType; - uint8_t bDescriptorSubtype; - uint8_t bmCapabilities; + uint8_t bFunctionLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint8_t bmCapabilities; } usb_cdc_acm_desc_t; //! CDC Union Functional Descriptor typedef struct { - uint8_t bFunctionLength; - uint8_t bDescriptorType; - uint8_t bDescriptorSubtype; - uint8_t bMasterInterface; - uint8_t bSlaveInterface0; + uint8_t bFunctionLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint8_t bMasterInterface; + uint8_t bSlaveInterface0; } usb_cdc_union_desc_t; - //! \name USB CDC Call Management Capabilities //@{ //! Device handles call management itself @@ -235,24 +233,24 @@ typedef struct { //@{ //! Line Coding structure typedef struct { - le32_t dwDTERate; - uint8_t bCharFormat; - uint8_t bParityType; - uint8_t bDataBits; + le32_t dwDTERate; + uint8_t bCharFormat; + uint8_t bParityType; + uint8_t bDataBits; } usb_cdc_line_coding_t; //! Possible values of bCharFormat enum cdc_char_format { - CDC_STOP_BITS_1 = 0, //!< 1 stop bit - CDC_STOP_BITS_1_5 = 1, //!< 1.5 stop bits - CDC_STOP_BITS_2 = 2, //!< 2 stop bits + CDC_STOP_BITS_1 = 0, //!< 1 stop bit + CDC_STOP_BITS_1_5 = 1, //!< 1.5 stop bits + CDC_STOP_BITS_2 = 2, //!< 2 stop bits }; //! Possible values of bParityType enum cdc_parity { - CDC_PAR_NONE = 0, //!< No parity - CDC_PAR_ODD = 1, //!< Odd parity - CDC_PAR_EVEN = 2, //!< Even parity - CDC_PAR_MARK = 3, //!< Parity forced to 0 (space) - CDC_PAR_SPACE = 4, //!< Parity forced to 1 (mark) + CDC_PAR_NONE = 0, //!< No parity + CDC_PAR_ODD = 1, //!< Odd parity + CDC_PAR_EVEN = 2, //!< Even parity + CDC_PAR_MARK = 3, //!< Parity forced to 0 (space) + CDC_PAR_SPACE = 4, //!< Parity forced to 1 (mark) }; //@} @@ -262,7 +260,7 @@ enum cdc_parity { //! Control signal structure typedef struct { - uint16_t value; + uint16_t value; } usb_cdc_control_signal_t; //! \name Possible values in usb_cdc_control_signal_t @@ -278,16 +276,15 @@ typedef struct { //@} //@} - //! \name USB CDC notification message //@{ typedef struct { - uint8_t bmRequestType; - uint8_t bNotification; - le16_t wValue; - le16_t wIndex; - le16_t wLength; + uint8_t bmRequestType; + uint8_t bNotification; + le16_t wValue; + le16_t wIndex; + le16_t wLength; } usb_cdc_notify_msg_t; //! \name USB CDC serial state @@ -295,8 +292,8 @@ typedef struct { //! Hardware handshake support (cdc spec 1.1 chapter 6.3.5) typedef struct { - usb_cdc_notify_msg_t header; - le16_t value; + usb_cdc_notify_msg_t header; + le16_t value; } usb_cdc_notify_serial_state_t; //! \name Possible values in usb_cdc_notify_serial_state_t diff --git a/Marlin/src/HAL/DUE/usb/usb_protocol_msc.h b/Marlin/src/HAL/DUE/usb/usb_protocol_msc.h index 0fef308046..227a13dc53 100644 --- a/Marlin/src/HAL/DUE/usb/usb_protocol_msc.h +++ b/Marlin/src/HAL/DUE/usb/usb_protocol_msc.h @@ -47,7 +47,6 @@ #ifndef _USB_PROTOCOL_MSC_H_ #define _USB_PROTOCOL_MSC_H_ - /** * \ingroup usb_protocol_group * \defgroup usb_msc_protocol USB Mass Storage Class (MSC) protocol definitions @@ -59,7 +58,7 @@ * \name Possible Class value */ //@{ -#define MSC_CLASS 0x08 +#define MSC_CLASS 0x08 //@} /** @@ -71,12 +70,12 @@ * operating systems like Windows XP. */ //@{ -#define MSC_SUBCLASS_RBC 0x01 //!< Reduced Block Commands -#define MSC_SUBCLASS_ATAPI 0x02 //!< CD/DVD devices -#define MSC_SUBCLASS_QIC_157 0x03 //!< Tape devices -#define MSC_SUBCLASS_UFI 0x04 //!< Floppy disk drives -#define MSC_SUBCLASS_SFF_8070I 0x05 //!< Floppy disk drives -#define MSC_SUBCLASS_TRANSPARENT 0x06 //!< Determined by INQUIRY +#define MSC_SUBCLASS_RBC 0x01 //!< Reduced Block Commands +#define MSC_SUBCLASS_ATAPI 0x02 //!< CD/DVD devices +#define MSC_SUBCLASS_QIC_157 0x03 //!< Tape devices +#define MSC_SUBCLASS_UFI 0x04 //!< Floppy disk drives +#define MSC_SUBCLASS_SFF_8070I 0x05 //!< Floppy disk drives +#define MSC_SUBCLASS_TRANSPARENT 0x06 //!< Determined by INQUIRY //@} /** @@ -84,21 +83,19 @@ * \note Only the BULK protocol should be used in new designs. */ //@{ -#define MSC_PROTOCOL_CBI 0x00 //!< Command/Bulk/Interrupt -#define MSC_PROTOCOL_CBI_ALT 0x01 //!< W/o command completion -#define MSC_PROTOCOL_BULK 0x50 //!< Bulk-only +#define MSC_PROTOCOL_CBI 0x00 //!< Command/Bulk/Interrupt +#define MSC_PROTOCOL_CBI_ALT 0x01 //!< W/o command completion +#define MSC_PROTOCOL_BULK 0x50 //!< Bulk-only //@} - /** * \brief MSC USB requests (bRequest) */ enum usb_reqid_msc { - USB_REQ_MSC_BULK_RESET = 0xFF, //!< Mass Storage Reset - USB_REQ_MSC_GET_MAX_LUN = 0xFE //!< Get Max LUN + USB_REQ_MSC_BULK_RESET = 0xFF, //!< Mass Storage Reset + USB_REQ_MSC_GET_MAX_LUN = 0xFE //!< Get Max LUN }; - COMPILER_PACK_SET(1) /** @@ -106,38 +103,37 @@ COMPILER_PACK_SET(1) */ //@{ struct usb_msc_cbw { - le32_t dCBWSignature; //!< Must contain 'USBC' - le32_t dCBWTag; //!< Unique command ID - le32_t dCBWDataTransferLength; //!< Number of bytes to transfer - uint8_t bmCBWFlags; //!< Direction in bit 7 - uint8_t bCBWLUN; //!< Logical Unit Number - uint8_t bCBWCBLength; //!< Number of valid CDB bytes - uint8_t CDB[16]; //!< SCSI Command Descriptor Block + le32_t dCBWSignature; //!< Must contain 'USBC' + le32_t dCBWTag; //!< Unique command ID + le32_t dCBWDataTransferLength; //!< Number of bytes to transfer + uint8_t bmCBWFlags; //!< Direction in bit 7 + uint8_t bCBWLUN; //!< Logical Unit Number + uint8_t bCBWCBLength; //!< Number of valid CDB bytes + uint8_t CDB[16]; //!< SCSI Command Descriptor Block }; -#define USB_CBW_SIGNATURE 0x55534243 //!< dCBWSignature value -#define USB_CBW_DIRECTION_IN (1<<7) //!< Data from device to host -#define USB_CBW_DIRECTION_OUT (0<<7) //!< Data from host to device -#define USB_CBW_LUN_MASK 0x0F //!< Valid bits in bCBWLUN -#define USB_CBW_LEN_MASK 0x1F //!< Valid bits in bCBWCBLength +#define USB_CBW_SIGNATURE 0x55534243 //!< dCBWSignature value +#define USB_CBW_DIRECTION_IN (1<<7) //!< Data from device to host +#define USB_CBW_DIRECTION_OUT (0<<7) //!< Data from host to device +#define USB_CBW_LUN_MASK 0x0F //!< Valid bits in bCBWLUN +#define USB_CBW_LEN_MASK 0x1F //!< Valid bits in bCBWCBLength //@} - /** * \name A Command Status Wrapper (CSW). */ //@{ struct usb_msc_csw { - le32_t dCSWSignature; //!< Must contain 'USBS' - le32_t dCSWTag; //!< Same as dCBWTag - le32_t dCSWDataResidue; //!< Number of bytes not transfered - uint8_t bCSWStatus; //!< Status code + le32_t dCSWSignature; //!< Must contain 'USBS' + le32_t dCSWTag; //!< Same as dCBWTag + le32_t dCSWDataResidue; //!< Number of bytes not transferred + uint8_t bCSWStatus; //!< Status code }; -#define USB_CSW_SIGNATURE 0x55534253 //!< dCSWSignature value -#define USB_CSW_STATUS_PASS 0x00 //!< Command Passed -#define USB_CSW_STATUS_FAIL 0x01 //!< Command Failed -#define USB_CSW_STATUS_PE 0x02 //!< Phase Error +#define USB_CSW_SIGNATURE 0x55534253 //!< dCSWSignature value +#define USB_CSW_STATUS_PASS 0x00 //!< Command Passed +#define USB_CSW_STATUS_FAIL 0x01 //!< Command Failed +#define USB_CSW_STATUS_PE 0x02 //!< Phase Error //@} COMPILER_PACK_RESET() diff --git a/Marlin/src/HAL/DUE/usb/usb_task.c b/Marlin/src/HAL/DUE/usb/usb_task.c index 66bdb265d8..6f027f83a1 100644 --- a/Marlin/src/HAL/DUE/usb/usb_task.c +++ b/Marlin/src/HAL/DUE/usb/usb_task.c @@ -51,18 +51,18 @@ #include "conf_usb.h" #include "udc.h" -#if ENABLED(SDSUPPORT) +#if HAS_MEDIA static volatile bool main_b_msc_enable = false; #endif static volatile bool main_b_cdc_enable = false; static volatile bool main_b_dtr_active = false; void usb_task_idle(void) { - #if ENABLED(SDSUPPORT) + #if HAS_MEDIA // Attend SD card access from the USB MSD -- Prioritize access to improve speed int delay = 2; while (main_b_msc_enable && --delay > 0) { - if (udi_msc_process_trans()) delay = 10000; + if (udi_msc_process_trans()) delay = 20; // Reset the watchdog, just to be sure REG_WDT_CR = WDT_CR_WDRSTT | WDT_CR_KEY(0xA5); @@ -70,7 +70,7 @@ void usb_task_idle(void) { #endif } -#if ENABLED(SDSUPPORT) +#if HAS_MEDIA bool usb_task_msc_enable(void) { return ((main_b_msc_enable = true)); } void usb_task_msc_disable(void) { main_b_msc_enable = false; } bool usb_task_msc_isenabled(void) { return main_b_msc_enable; } @@ -206,13 +206,13 @@ static USB_MicrosoftExtendedPropertiesDescriptor microsoft_extended_properties_d bool usb_task_extra_string(void) { static uint8_t udi_msft_magic[] = "MSFT100\xEE"; static uint8_t udi_cdc_name[] = "CDC interface"; - #if ENABLED(SDSUPPORT) + #if HAS_MEDIA static uint8_t udi_msc_name[] = "MSC interface"; #endif struct extra_strings_desc_t { usb_str_desc_t header; - #if ENABLED(SDSUPPORT) + #if HAS_MEDIA le16_t string[Max(Max(sizeof(udi_cdc_name) - 1, sizeof(udi_msc_name) - 1), sizeof(udi_msft_magic) - 1)]; #else le16_t string[Max(sizeof(udi_cdc_name) - 1, sizeof(udi_msft_magic) - 1)]; @@ -231,7 +231,7 @@ bool usb_task_extra_string(void) { str_lgt = sizeof(udi_cdc_name) - 1; str = udi_cdc_name; break; - #if ENABLED(SDSUPPORT) + #if HAS_MEDIA case UDI_MSC_STRING_ID: str_lgt = sizeof(udi_msc_name) - 1; str = udi_msc_name; @@ -264,7 +264,7 @@ bool usb_task_extra_string(void) { ** Handle device requests that the ASF stack doesn't */ bool usb_task_other_requests(void) { - uint8_t* ptr = 0; + uint8_t *ptr = 0; uint16_t size = 0; if (Udd_setup_type() == USB_REQ_TYPE_VENDOR) { @@ -322,7 +322,7 @@ void usb_task_init(void) { char *sptr; // Patch in the filament diameter - sprintf_P(diam, PSTR("%d"), (int)((DEFAULT_NOMINAL_FILAMENT_DIA) * 1000.0)); + itoa((int)((DEFAULT_NOMINAL_FILAMENT_DIA) * 1000), diam, 10); // And copy it to the proper place, expanding it to unicode sptr = &diam[0]; diff --git a/Marlin/src/HAL/DUE/watchdog.cpp b/Marlin/src/HAL/DUE/watchdog.cpp deleted file mode 100644 index 0f46971830..0000000000 --- a/Marlin/src/HAL/DUE/watchdog.cpp +++ /dev/null @@ -1,114 +0,0 @@ -/** - * Marlin 3D Printer Firmware - * Copyright (c) 2020 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 ARDUINO_ARCH_SAM - -#include "../../inc/MarlinConfig.h" -#include "../../MarlinCore.h" -#include "watchdog.h" - -// Override Arduino runtime to either config or disable the watchdog -// -// We need to configure the watchdog as soon as possible in the boot -// process, because watchdog initialization at hardware reset on SAM3X8E -// is unreliable, and there is risk of unintended resets if we delay -// that initialization to a later time. -void watchdogSetup() { - - #if ENABLED(USE_WATCHDOG) - - // 4 seconds timeout - uint32_t timeout = 4000; - - // Calculate timeout value in WDT counter ticks: This assumes - // the slow clock is running at 32.768 kHz watchdog - // frequency is therefore 32768 / 128 = 256 Hz - timeout = (timeout << 8) / 1000; - if (timeout == 0) - timeout = 1; - else if (timeout > 0xFFF) - 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. - - #if ENABLED(WATCHDOG_RESET_MANUAL) - // We enable the watchdog timer, but only for the interrupt. - - // Configure WDT to only trigger an interrupt - value |= WDT_MR_WDFIEN; // Enable WDT fault interrupt. - - // Disable WDT interrupt (just in case, to avoid triggering it!) - NVIC_DisableIRQ(WDT_IRQn); - - // We NEED memory barriers to ensure Interrupts are actually disabled! - // ( https://dzone.com/articles/nvic-disabling-interrupts-on-arm-cortex-m-and-the ) - __DSB(); - __ISB(); - - // Initialize WDT with the given parameters - WDT_Enable(WDT, value); - - // Configure and enable WDT interrupt. - NVIC_ClearPendingIRQ(WDT_IRQn); - NVIC_SetPriority(WDT_IRQn, 0); // Use highest priority, so we detect all kinds of lockups - NVIC_EnableIRQ(WDT_IRQn); - - #else - - // a WDT fault triggers a reset - value |= WDT_MR_WDRSTEN; - - // Initialize WDT with the given parameters - WDT_Enable(WDT, value); - - #endif - - // Reset the watchdog - WDT_Restart(WDT); - - #else - - // Make sure to completely disable the Watchdog - WDT_Disable(WDT); - - #endif -} - -#if ENABLED(USE_WATCHDOG) - // Initialize watchdog - On SAM3X, Watchdog was already configured - // and enabled or disabled at startup, so no need to reconfigure it - // here. - void watchdog_init() { - // Reset watchdog to start clean - WDT_Restart(WDT); - } -#endif // USE_WATCHDOG - -#endif diff --git a/Marlin/src/HAL/ESP32/FlushableHardwareSerial.cpp b/Marlin/src/HAL/ESP32/FlushableHardwareSerial.cpp index d4b2f42c53..145662215a 100644 --- a/Marlin/src/HAL/ESP32/FlushableHardwareSerial.cpp +++ b/Marlin/src/HAL/ESP32/FlushableHardwareSerial.cpp @@ -20,14 +20,10 @@ * */ -#include "FlushableHardwareSerial.h" - #ifdef ARDUINO_ARCH_ESP32 -FlushableHardwareSerial::FlushableHardwareSerial(int uart_nr) - : HardwareSerial(uart_nr) -{} +#include "FlushableHardwareSerial.h" -FlushableHardwareSerial flushableSerial(0); +Serial1Class flushableSerial(false, 0); -#endif // ARDUINO_ARCH_ESP32 +#endif diff --git a/Marlin/src/HAL/ESP32/FlushableHardwareSerial.h b/Marlin/src/HAL/ESP32/FlushableHardwareSerial.h index b43caea13c..012dda8626 100644 --- a/Marlin/src/HAL/ESP32/FlushableHardwareSerial.h +++ b/Marlin/src/HAL/ESP32/FlushableHardwareSerial.h @@ -21,17 +21,14 @@ */ #pragma once -#ifdef ARDUINO_ARCH_ESP32 - #include +#include "../shared/Marduino.h" +#include "../../core/serial_hook.h" + class FlushableHardwareSerial : public HardwareSerial { public: - FlushableHardwareSerial(int uart_nr); - - inline void flushTX() { /* No need to flush the hardware serial, but defined here for compatibility. */ } + FlushableHardwareSerial(int uart_nr) : HardwareSerial(uart_nr) {} }; -extern FlushableHardwareSerial flushableSerial; - -#endif // ARDUINO_ARCH_ESP32 +extern Serial1Class flushableSerial; diff --git a/Marlin/src/HAL/ESP32/HAL.cpp b/Marlin/src/HAL/ESP32/HAL.cpp index 1e00df5177..acfec29b2f 100644 --- a/Marlin/src/HAL/ESP32/HAL.cpp +++ b/Marlin/src/HAL/ESP32/HAL.cpp @@ -28,23 +28,31 @@ #include #include +#if ENABLED(USE_ESP32_TASK_WDT) + #include +#endif + #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 +#if ENABLED(ESP3D_WIFISUPPORT) + DefaultSerial1 MSerial0(false, Serial2Socket); +#endif + // ------------------------ // Externs // ------------------------ -portMUX_TYPE spinlock = portMUX_INITIALIZER_UNLOCKED; +portMUX_TYPE MarlinHAL::spinlock = portMUX_INITIALIZER_UNLOCKED; // ------------------------ // Local defines @@ -56,7 +64,8 @@ portMUX_TYPE spinlock = portMUX_INITIALIZER_UNLOCKED; // Public Variables // ------------------------ -uint16_t HAL_adc_result; +uint16_t MarlinHAL::adc_result; +pwm_pin_t MarlinHAL::pwm_pin_data[MAX_EXPANDER_BITS]; // ------------------------ // Private Variables @@ -65,9 +74,16 @@ uint16_t HAL_adc_result; esp_adc_cal_characteristics_t characteristics[ADC_ATTEN_MAX]; adc_atten_t attenuations[ADC1_CHANNEL_MAX] = {}; uint32_t thresholds[ADC_ATTEN_MAX]; -volatile int numPWMUsed = 0, - pwmPins[MAX_PWM_PINS], - pwmValues[MAX_PWM_PINS]; + +volatile int numPWMUsed = 0; +volatile struct { pin_t pin; int value; } pwmState[MAX_PWM_PINS]; + +pin_t chan_pin[CHANNEL_MAX_NUM + 1] = { 0 }; // PWM capable IOpins - not 0 or >33 on ESP32 + +struct { + uint32_t freq; // ledcReadFreq doesn't work if a duty hasn't been set yet! + uint16_t res; +} pwmInfo[(CHANNEL_MAX_NUM + 1) / 2]; // ------------------------ // Public functions @@ -86,10 +102,26 @@ volatile int numPWMUsed = 0, #endif -void HAL_init() { i2s_init(); } +#if ENABLED(USE_ESP32_EXIO) -void HAL_init_board() { + HardwareSerial YSerial2(2); + void Write_EXIO(uint8_t IO, uint8_t v) { + if (hal.isr_state()) { + hal.isr_off(); + YSerial2.write(0x80 | (((char)v) << 5) | (IO - 100)); + hal.isr_on(); + } + else + YSerial2.write(0x80 | (((char)v) << 5) | (IO - 100)); + } + +#endif + +void MarlinHAL::init_board() { + #if ENABLED(USE_ESP32_TASK_WDT) + esp_task_wdt_init(10, true); + #endif #if ENABLED(ESP3D_WIFISUPPORT) esp3dlib.init(); #elif ENABLED(WIFISUPPORT) @@ -122,37 +154,70 @@ void HAL_init_board() { #endif #endif + // Initialize the i2s peripheral only if the I2S stepper stream is enabled. + // The following initialization is performed after Serial1 and Serial2 are defined as + // their native pins might conflict with the i2s stream even when they are remapped. + #if ENABLED(USE_ESP32_EXIO) + YSerial2.begin(460800 * 3, SERIAL_8N1, 16, 17); + #elif ENABLED(I2S_STEPPER_STREAM) + i2s_init(); + #endif } -void HAL_idletask() { - #if BOTH(WIFISUPPORT, OTASUPPORT) +void MarlinHAL::idletask() { + #if ALL(WIFISUPPORT, OTASUPPORT) OTA_handle(); #endif TERN_(ESP3D_WIFISUPPORT, esp3dlib.idletask()); } -void HAL_clear_reset_source() { } +uint8_t MarlinHAL::get_reset_source() { return rtc_get_reset_reason(1); } -uint8_t HAL_get_reset_source() { return rtc_get_reset_reason(1); } - -void _delay_ms(int delay_ms) { delay(delay_ms); } +void MarlinHAL::reboot() { ESP.restart(); } // return free memory between end of heap (or end bss) and whatever is current -int freeMemory() { return ESP.getFreeHeap(); } +int MarlinHAL::freeMemory() { return ESP.getFreeHeap(); } + +// ------------------------ +// Watchdog Timer +// ------------------------ + +#if ENABLED(USE_WATCHDOG) + + #define WDT_TIMEOUT_US TERN(WATCHDOG_DURATION_8S, 8000000, 4000000) // 4 or 8 second timeout + + extern "C" { + esp_err_t esp_task_wdt_reset(); + } + + void watchdogSetup() { + // do whatever. don't remove this function. + } + + void MarlinHAL::watchdog_init() { + // TODO + } + + // Reset watchdog. + void MarlinHAL::watchdog_refresh() { esp_task_wdt_reset(); } + +#endif // ------------------------ // 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; } @@ -164,22 +229,26 @@ void adc1_set_attenuation(adc1_channel_t chan, adc_atten_t atten) { } } -void HAL_adc_init() { +void MarlinHAL::adc_init() { // Configure ADC adc1_config_width(ADC_WIDTH_12Bit); // Configure channels only if used as (re-)configuring a pin for ADC that is used elsewhere might have adverse effects - TERN_(HAS_TEMP_ADC_0, adc1_set_attenuation(get_channel(TEMP_0_PIN), ADC_ATTEN_11db)); - TERN_(HAS_TEMP_ADC_1, adc1_set_attenuation(get_channel(TEMP_1_PIN), ADC_ATTEN_11db)); - TERN_(HAS_TEMP_ADC_2, adc1_set_attenuation(get_channel(TEMP_2_PIN), ADC_ATTEN_11db)); - TERN_(HAS_TEMP_ADC_3, adc1_set_attenuation(get_channel(TEMP_3_PIN), ADC_ATTEN_11db)); - TERN_(HAS_TEMP_ADC_4, adc1_set_attenuation(get_channel(TEMP_4_PIN), ADC_ATTEN_11db)); - 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_(FILAMENT_WIDTH_SENSOR, adc1_set_attenuation(get_channel(FILWIDTH_PIN), ADC_ATTEN_11db)); + TERN_(HAS_TEMP_ADC_0, adc1_set_attenuation(get_channel(TEMP_0_PIN), ADC_ATTEN_11db)); + TERN_(HAS_TEMP_ADC_1, adc1_set_attenuation(get_channel(TEMP_1_PIN), ADC_ATTEN_11db)); + TERN_(HAS_TEMP_ADC_2, adc1_set_attenuation(get_channel(TEMP_2_PIN), ADC_ATTEN_11db)); + TERN_(HAS_TEMP_ADC_3, adc1_set_attenuation(get_channel(TEMP_3_PIN), ADC_ATTEN_11db)); + TERN_(HAS_TEMP_ADC_4, adc1_set_attenuation(get_channel(TEMP_4_PIN), ADC_ATTEN_11db)); + 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_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. @@ -193,11 +262,16 @@ void HAL_adc_init() { } } -void HAL_adc_start_conversion(const uint8_t adc_pin) { - const adc1_channel_t chan = get_channel(adc_pin); +#ifndef ADC_REFERENCE_VOLTAGE + #define ADC_REFERENCE_VOLTAGE 3.3 +#endif + +void MarlinHAL::adc_start(const pin_t pin) { + const adc1_channel_t chan = get_channel(pin); uint32_t mv; esp_adc_cal_get_voltage((adc_channel_t)chan, &characteristics[attenuations[chan]], &mv); - HAL_adc_result = mv * 1023.0 / 3300.0; + + adc_result = (mv * 1023) * (1000.f / (ADC_REFERENCE_VOLTAGE)); // Change the attenuation level based on the new reading adc_atten_t atten; @@ -214,25 +288,104 @@ void HAL_adc_start_conversion(const uint8_t adc_pin) { adc1_set_attenuation(chan, atten); } -void analogWrite(pin_t pin, int value) { - // Use ledc hardware for internal pins - if (pin < 34) { - static int cnt_channel = 1, pin_to_channel[40] = { 0 }; - if (pin_to_channel[pin] == 0) { - ledcAttachPin(pin, cnt_channel); - ledcSetup(cnt_channel, 490, 8); - ledcWrite(cnt_channel, value); - pin_to_channel[pin] = cnt_channel++; +// ------------------------ +// PWM +// ------------------------ + +int8_t channel_for_pin(const uint8_t pin) { + for (int i = 0; i <= CHANNEL_MAX_NUM; i++) + if (chan_pin[i] == pin) return i; + return -1; +} + +// get PWM channel for pin - if none then attach a new one +// return -1 if fail or invalid pin#, channel # (0-15) if success +int8_t get_pwm_channel(const pin_t pin, const uint32_t freq, const uint16_t res) { + if (!WITHIN(pin, 1, MAX_PWM_IOPIN)) return -1; // Not a hardware PWM pin! + int8_t cid = channel_for_pin(pin); + if (cid >= 0) return cid; + + // Find an empty adjacent channel (same timer & freq/res) + for (int i = 0; i <= CHANNEL_MAX_NUM; i++) { + if (chan_pin[i] == 0) { + if (chan_pin[i ^ 0x1] != 0) { + if (pwmInfo[i / 2].freq == freq && pwmInfo[i / 2].res == res) { + chan_pin[i] = pin; // Allocate PWM to this channel + ledcAttachPin(pin, i); + return i; + } + } + else if (cid == -1) // Pair of empty channels? + cid = i & 0xFE; // Save lower channel number } - ledcWrite(pin_to_channel[pin], value); + } + // not attached, is an empty timer slot avail? + if (cid >= 0) { + chan_pin[cid] = pin; + pwmInfo[cid / 2].freq = freq; + pwmInfo[cid / 2].res = res; + ledcSetup(cid, freq, res); + ledcAttachPin(pin, cid); + } + return cid; // -1 if no channel avail +} + +void MarlinHAL::set_pwm_duty(const pin_t pin, const uint16_t v, const uint16_t v_size/*=_BV(PWM_RESOLUTION)-1*/, const bool invert/*=false*/) { + #if ENABLED(I2S_STEPPER_STREAM) + if (pin > 127) { + const uint8_t pinlo = pin & 0x7F; + pwm_pin_t &pindata = pwm_pin_data[pinlo]; + const uint32_t duty = map(invert ? v_size - v : v, 0, v_size, 0, pindata.pwm_cycle_ticks); + if (duty == 0 || duty == pindata.pwm_cycle_ticks) { // max or min (i.e., on/off) + pindata.pwm_duty_ticks = 0; // turn off PWM for this pin + duty ? SBI32(i2s_port_data, pinlo) : CBI32(i2s_port_data, pinlo); // set pin level + } + else + pindata.pwm_duty_ticks = duty; // PWM duty count = # of 4Β΅s ticks per full PWM cycle + + return; + } + #endif + + const int8_t cid = get_pwm_channel(pin, PWM_FREQUENCY, PWM_RESOLUTION); + if (cid >= 0) { + const uint32_t duty = map(invert ? v_size - v : v, 0, v_size, 0, _BV(PWM_RESOLUTION)-1); + ledcWrite(cid, duty); + } +} + +int8_t MarlinHAL::set_pwm_frequency(const pin_t pin, const uint32_t f_desired) { + #if ENABLED(I2S_STEPPER_STREAM) + if (pin > 127) { + pwm_pin_data[pin & 0x7F].pwm_cycle_ticks = 1000000UL / f_desired / 4; // # of 4Β΅s ticks per full PWM cycle + return 0; + } + #endif + + const int8_t cid = channel_for_pin(pin); + if (cid >= 0) { + if (f_desired == ledcReadFreq(cid)) return cid; // no freq change + ledcDetachPin(chan_pin[cid]); + chan_pin[cid] = 0; // remove old freq channel + } + return get_pwm_channel(pin, f_desired, PWM_RESOLUTION); // try for new one +} + +// use hardware PWM if avail, if not then ISR +void analogWrite(const pin_t pin, const uint16_t value, const uint32_t freq/*=PWM_FREQUENCY*/, const uint16_t res/*=8*/) { // always 8 bit resolution! + // Use ledc hardware for internal pins + const int8_t cid = get_pwm_channel(pin, freq, res); + if (cid >= 0) { + ledcWrite(cid, value); // set duty value return; } + // not a hardware PWM pin OR no PWM channels available int idx = -1; // Search Pin for (int i = 0; i < numPWMUsed; ++i) - if (pwmPins[i] == pin) { idx = i; break; } + if (pwmState[i].pin == pin) { idx = i; break; } // not found ? if (idx < 0) { @@ -241,34 +394,34 @@ void analogWrite(pin_t pin, int value) { // Take new slot for pin idx = numPWMUsed; - pwmPins[idx] = pin; + pwmState[idx].pin = pin; // Start timer on first use - if (idx == 0) HAL_timer_start(PWM_TIMER_NUM, PWM_TIMER_FREQUENCY); + if (idx == 0) HAL_timer_start(MF_TIMER_PWM, PWM_TIMER_FREQUENCY); ++numPWMUsed; } // Use 7bit internal value - add 1 to have 100% high at 255 - pwmValues[idx] = (value + 1) / 2; + pwmState[idx].value = (value + 1) / 2; } // Handle PWM timer interrupt HAL_PWM_TIMER_ISR() { - HAL_timer_isr_prologue(PWM_TIMER_NUM); + HAL_timer_isr_prologue(MF_TIMER_PWM); static uint8_t count = 0; for (int i = 0; i < numPWMUsed; ++i) { if (count == 0) // Start of interval - WRITE(pwmPins[i], pwmValues[i] ? HIGH : LOW); - else if (pwmValues[i] == count) // End of duration - WRITE(pwmPins[i], LOW); + digitalWrite(pwmState[i].pin, pwmState[i].value ? HIGH : LOW); + else if (pwmState[i].value == count) // End of duration + digitalWrite(pwmState[i].pin, LOW); } // 128 for 7 Bit resolution count = (count + 1) & 0x7F; - HAL_timer_isr_epilogue(PWM_TIMER_NUM); + HAL_timer_isr_epilogue(MF_TIMER_PWM); } #endif // ARDUINO_ARCH_ESP32 diff --git a/Marlin/src/HAL/ESP32/HAL.h b/Marlin/src/HAL/ESP32/HAL.h index ebc16c9525..f48eabea0d 100644 --- a/Marlin/src/HAL/ESP32/HAL.h +++ b/Marlin/src/HAL/ESP32/HAL.h @@ -1,7 +1,9 @@ /** * Marlin 3D Printer Firmware * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com + * + * 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 @@ -32,15 +34,14 @@ #include "../shared/HAL_SPI.h" #include "fastio.h" -#include "watchdog.h" #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" @@ -49,27 +50,27 @@ // Defines // ------------------------ -extern portMUX_TYPE spinlock; +#define MYSERIAL1 flushableSerial -#define MYSERIAL0 flushableSerial - -#if EITHER(WIFISUPPORT, ESP3D_WIFISUPPORT) - #if ENABLED(ESP3D_WIFISUPPORT) - #define MYSERIAL1 Serial2Socket - #else - #define MYSERIAL1 webSocketSerial - #endif +#if ENABLED(ESP3D_WIFISUPPORT) + typedef ForwardSerial1Class< decltype(Serial2Socket) > DefaultSerial1; + extern DefaultSerial1 MSerial0; + #define MYSERIAL2 MSerial0 +#elif ENABLED(WIFISUPPORT) + #define MYSERIAL2 webSocketSerial #endif -#define CRITICAL_SECTION_START() portENTER_CRITICAL(&spinlock) -#define CRITICAL_SECTION_END() portEXIT_CRITICAL(&spinlock) -#define ISRS_ENABLED() (spinlock.owner == portMUX_FREE_VAL) -#define ENABLE_ISRS() if (spinlock.owner != portMUX_FREE_VAL) portEXIT_CRITICAL(&spinlock) -#define DISABLE_ISRS() portENTER_CRITICAL(&spinlock) +#define CRITICAL_SECTION_START() portENTER_CRITICAL(&hal.spinlock) +#define CRITICAL_SECTION_END() portEXIT_CRITICAL(&hal.spinlock) -// Fix bug in pgm_read_ptr -#undef pgm_read_ptr -#define pgm_read_ptr(addr) (*(addr)) +#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 +#ifndef MAX_EXPANDER_BITS + #define MAX_EXPANDER_BITS 32 // I2S expander bit width (max 32) +#endif // ------------------------ // Types @@ -77,59 +78,37 @@ extern portMUX_TYPE spinlock; typedef int16_t pin_t; -#define HAL_SERVO_LIB Servo +typedef struct pwm_pin { + uint32_t pwm_cycle_ticks = 1000000UL / (PWM_FREQUENCY) / 4; // # ticks per pwm cycle + uint32_t pwm_tick_count = 0; // current tick count + uint32_t pwm_duty_ticks = 0; // # of ticks for current duty cycle +} pwm_pin_t; -// ------------------------ -// Public Variables -// ------------------------ - -/** result of last ADC conversion */ -extern uint16_t HAL_adc_result; +class Servo; +typedef Servo hal_servo_t; // ------------------------ // Public functions // ------------------------ -// clear reset reason -void HAL_clear_reset_source(); - -// reset reason -uint8_t HAL_get_reset_source(); - -inline void HAL_reboot() {} // reboot the board or restart the bootloader - -void _delay_ms(int delay); - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-function" -int freeMemory(); -#pragma GCC diagnostic pop - -void analogWrite(pin_t pin, int value); - -// ADC -#define HAL_ANALOG_SELECT(pin) - -void HAL_adc_init(); - -#define HAL_ADC_VREF 3.3 -#define HAL_ADC_RESOLUTION 10 -#define HAL_START_ADC(pin) HAL_adc_start_conversion(pin) -#define HAL_READ_ADC() HAL_adc_result -#define HAL_ADC_READY() true - -void HAL_adc_start_conversion(const uint8_t adc_pin); +// +// Tone +// +void tone(const pin_t _pin, const unsigned int frequency, const unsigned long duration=0); +void noTone(const pin_t _pin); +int8_t get_pwm_channel(const pin_t pin, const uint32_t freq, const uint16_t res); +void analogWrite(const pin_t pin, const uint16_t value, const uint32_t freq=PWM_FREQUENCY, const uint16_t res=8); +// +// 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) -// Enable hooks into idle and setup for HAL -#define HAL_IDLETASK 1 -#define BOARD_INIT() HAL_init_board(); -void HAL_idletask(); -void HAL_init(); -void HAL_init_board(); +#if ENABLED(USE_ESP32_EXIO) + void Write_EXIO(uint8_t IO, uint8_t v); +#endif // // Delay in cycles (used by DELAY_NS / DELAY_US) @@ -171,3 +150,94 @@ FORCE_INLINE static void DELAY_CYCLES(uint32_t x) { } } + +// ------------------------ +// Class Utilities +// ------------------------ + +#pragma GCC diagnostic push +#if GCC_VERSION <= 50000 + #pragma GCC diagnostic ignored "-Wunused-function" +#endif + +int freeMemory(); + +#pragma GCC diagnostic pop + +// ------------------------ +// MarlinHAL Class +// ------------------------ + +#define HAL_ADC_VREF_MV 3300 +#define HAL_ADC_RESOLUTION 10 + +class MarlinHAL { +public: + + // Earliest possible init, before setup() + MarlinHAL() {} + + // 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(); // Restart the firmware + + // Interrupts + static portMUX_TYPE spinlock; + static bool isr_state() { return spinlock.owner == portMUX_FREE_VAL; } + 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); } + + // Tasks, called from marlin.idle() + static void idletask(); + + // Reset + static uint8_t get_reset_source(); + static void clear_reset_source() {} + + // Free SRAM + static int freeMemory(); + + static pwm_pin_t pwm_pin_data[MAX_EXPANDER_BITS]; + + // + // ADC Methods + // + + static uint16_t adc_result; + + // 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 bool adc_ready() { return true; } + + // The current value of the ADC register + static uint16_t adc_value() { return adc_result; } + + /** + * If not already allocated, allocate a hardware PWM channel + * to the pin and set the duty cycle.. + * 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); + + /** + * Allocate and set the frequency of a hardware PWM pin + * Returns -1 if no pin available. + */ + static int8_t set_pwm_frequency(const pin_t pin, const uint32_t f_desired); + +}; diff --git a/Marlin/src/HAL/ESP32/HAL_SPI.cpp b/Marlin/src/HAL/ESP32/HAL_SPI.cpp index 8e5875fc38..e5302bb905 100644 --- a/Marlin/src/HAL/ESP32/HAL_SPI.cpp +++ b/Marlin/src/HAL/ESP32/HAL_SPI.cpp @@ -4,7 +4,6 @@ * * Based on Sprinter and grbl. * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm - * Copyright (c) 2017 Victor Perez * * 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 @@ -53,11 +52,9 @@ static SPISettings spiConfig; // ------------------------ void spiBegin() { - #if !PIN_EXISTS(SS) - #error "SS_PIN not defined!" + #if HAS_MEDIA && PIN_EXISTS(SD_SS) + OUT_WRITE(SD_SS_PIN, HIGH); #endif - - OUT_WRITE(SS_PIN, HIGH); } void spiInit(uint8_t spiRate) { @@ -85,7 +82,7 @@ uint8_t spiRec() { return returnByte; } -void spiRead(uint8_t* buf, uint16_t nbyte) { +void spiRead(uint8_t *buf, uint16_t nbyte) { SPI.beginTransaction(spiConfig); SPI.transferBytes(0, buf, nbyte); SPI.endTransaction(); @@ -97,7 +94,7 @@ void spiSend(uint8_t b) { SPI.endTransaction(); } -void spiSendBlock(uint8_t token, const uint8_t* buf) { +void spiSendBlock(uint8_t token, const uint8_t *buf) { SPI.beginTransaction(spiConfig); SPI.transfer(token); SPI.writeBytes(const_cast(buf), 512); diff --git a/Marlin/src/HAL/ESP32/MarlinSPI.h b/Marlin/src/HAL/ESP32/MarlinSPI.h new file mode 100644 index 0000000000..0c447ba4cb --- /dev/null +++ b/Marlin/src/HAL/ESP32/MarlinSPI.h @@ -0,0 +1,26 @@ +/** + * 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 + +#include + +using MarlinSPI = SPIClass; diff --git a/Marlin/src/HAL/ESP32/Servo.cpp b/Marlin/src/HAL/ESP32/Servo.cpp index fcf5848581..3a2c11832d 100644 --- a/Marlin/src/HAL/ESP32/Servo.cpp +++ b/Marlin/src/HAL/ESP32/Servo.cpp @@ -31,20 +31,18 @@ // so we only allocate servo channels up high to avoid side effects with regards to analogWrite (fans, leds, laser pwm etc.) int Servo::channel_next_free = 12; -Servo::Servo() { - channel = channel_next_free++; -} +Servo::Servo() {} int8_t Servo::attach(const int inPin) { - if (channel >= CHANNEL_MAX_NUM) return -1; if (inPin > 0) pin = inPin; - - ledcSetup(channel, 50, 16); // channel X, 50 Hz, 16-bit depth - ledcAttachPin(pin, channel); - return true; + channel = get_pwm_channel(pin, 50U, 16U); + return channel; // -1 if no PWM avail. } -void Servo::detach() { ledcDetachPin(pin); } +// leave channel connected to servo - set duty to zero +void Servo::detach() { + if (channel >= 0) ledcWrite(channel, 0); +} int Servo::read() { return degrees; } @@ -52,7 +50,7 @@ void Servo::write(int inDegrees) { degrees = constrain(inDegrees, MIN_ANGLE, MAX_ANGLE); int us = map(degrees, MIN_ANGLE, MAX_ANGLE, MIN_PULSE_WIDTH, MAX_PULSE_WIDTH); int duty = map(us, 0, TAU_USEC, 0, MAX_COMPARE); - ledcWrite(channel, duty); + if (channel >= 0) ledcWrite(channel, duty); // don't save duty for servos! } void Servo::move(const int value) { diff --git a/Marlin/src/HAL/ESP32/Servo.h b/Marlin/src/HAL/ESP32/Servo.h index b0d9294527..1dbb416a83 100644 --- a/Marlin/src/HAL/ESP32/Servo.h +++ b/Marlin/src/HAL/ESP32/Servo.h @@ -30,8 +30,7 @@ class Servo { MAX_PULSE_WIDTH = 2400, // Longest pulse sent to a servo TAU_MSEC = 20, TAU_USEC = (TAU_MSEC * 1000), - MAX_COMPARE = ((1 << 16) - 1), // 65535 - CHANNEL_MAX_NUM = 16; + MAX_COMPARE = _BV(16) - 1; // 65535 public: Servo(); diff --git a/Marlin/src/HAL/STM32F1/watchdog.cpp b/Marlin/src/HAL/ESP32/Tone.cpp similarity index 53% rename from Marlin/src/HAL/STM32F1/watchdog.cpp rename to Marlin/src/HAL/ESP32/Tone.cpp index ca91a6fe43..1bb0840129 100644 --- a/Marlin/src/HAL/STM32F1/watchdog.cpp +++ b/Marlin/src/HAL/ESP32/Tone.cpp @@ -5,6 +5,8 @@ * Based on Sprinter and grbl. * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm * + * Copypaste of SAMD51 HAL developed by Giuliano Zaro (AKA GMagician) + * * 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 @@ -21,41 +23,37 @@ */ /** - * HAL for stm32duino.com based on Libmaple and compatible (STM32F1) + * Description: Tone function for ESP32 + * Derived from https://forum.arduino.cc/t/arduino-due-and-tone/133302/13 */ -#ifdef __STM32F1__ +#ifdef ARDUINO_ARCH_ESP32 #include "../../inc/MarlinConfig.h" +#include "HAL.h" -#if ENABLED(USE_WATCHDOG) +static pin_t tone_pin; +volatile static int32_t toggles; -#include -#include "watchdog.h" - -void HAL_watchdog_refresh() { - #if DISABLED(PINS_DEBUGGING) && PIN_EXISTS(LED) - TOGGLE(LED_PIN); // heartbeat indicator - #endif - iwdg_feed(); +void tone(const pin_t _pin, const unsigned int frequency, const unsigned long duration/*=0*/) { + tone_pin = _pin; + toggles = 2 * frequency * duration / 1000; + HAL_timer_start(MF_TIMER_TONE, 2 * frequency); } -void watchdogSetup() { - // do whatever. don't remove this function. +void noTone(const pin_t _pin) { + HAL_timer_disable_interrupt(MF_TIMER_TONE); + WRITE(_pin, LOW); } -/** - * @brief Initialized the independent hardware watchdog. - * - * @return No return - * - * @details The watchdog clock is 40Khz. We need a 4 seconds interval, so use a /256 preescaler and 625 reload value (counts down to 0) - */ -void watchdog_init() { - #if DISABLED(DISABLE_WATCHDOG_INIT) - iwdg_init(IWDG_PRE_256, STM32F1_WD_RELOAD); - #endif +HAL_TONE_TIMER_ISR() { + HAL_timer_isr_prologue(MF_TIMER_TONE); + + if (toggles) { + toggles--; + TOGGLE(tone_pin); + } + else noTone(tone_pin); // turn off interrupt } -#endif // USE_WATCHDOG -#endif // __STM32F1__ +#endif // ARDUINO_ARCH_ESP32 diff --git a/Marlin/src/HAL/ESP32/eeprom.cpp b/Marlin/src/HAL/ESP32/eeprom.cpp index 1bf687c6fe..ca4a806361 100644 --- a/Marlin/src/HAL/ESP32/eeprom.cpp +++ b/Marlin/src/HAL/ESP32/eeprom.cpp @@ -31,24 +31,28 @@ #ifndef MARLIN_EEPROM_SIZE #define MARLIN_EEPROM_SIZE 0x1000 // 4KB #endif -size_t PersistentStore::capacity() { return MARLIN_EEPROM_SIZE; } +size_t PersistentStore::capacity() { return MARLIN_EEPROM_SIZE - eeprom_exclude_size; } bool PersistentStore::access_start() { return EEPROM.begin(MARLIN_EEPROM_SIZE); } bool PersistentStore::access_finish() { EEPROM.end(); return true; } bool PersistentStore::write_data(int &pos, const uint8_t *value, size_t size, uint16_t *crc) { for (size_t i = 0; i < size; i++) { - EEPROM.write(pos++, value[i]); + const int p = REAL_EEPROM_ADDR(pos); + EEPROM.write(p, value[i]); crc16(crc, &value[i], 1); + ++pos; } return false; } -bool PersistentStore::read_data(int &pos, uint8_t* value, size_t size, uint16_t *crc, const bool writing/*=true*/) { +bool PersistentStore::read_data(int &pos, uint8_t *value, size_t size, uint16_t *crc, const bool writing/*=true*/) { for (size_t i = 0; i < size; i++) { - uint8_t c = EEPROM.read(pos++); + const int p = REAL_EEPROM_ADDR(pos); + uint8_t c = EEPROM.read(p); if (writing) value[i] = c; crc16(crc, &c, 1); + ++pos; } return false; } diff --git a/Marlin/src/HAL/ESP32/endstop_interrupts.h b/Marlin/src/HAL/ESP32/endstop_interrupts.h index 743ccd99c9..8b88f1f41f 100644 --- a/Marlin/src/HAL/ESP32/endstop_interrupts.h +++ b/Marlin/src/HAL/ESP32/endstop_interrupts.h @@ -42,21 +42,34 @@ void ICACHE_RAM_ATTR endstop_ISR() { endstops.update(); } void setup_endstop_interrupts() { #define _ATTACH(P) attachInterrupt(digitalPinToInterrupt(P), endstop_ISR, CHANGE) - TERN_(HAS_X_MAX, _ATTACH(X_MAX_PIN)); - TERN_(HAS_X_MIN, _ATTACH(X_MIN_PIN)); - TERN_(HAS_Y_MAX, _ATTACH(Y_MAX_PIN)); - TERN_(HAS_Y_MIN, _ATTACH(Y_MIN_PIN)); - TERN_(HAS_Z_MAX, _ATTACH(Z_MAX_PIN)); - TERN_(HAS_Z_MIN, _ATTACH(Z_MIN_PIN)); - TERN_(HAS_X2_MAX, _ATTACH(X2_MAX_PIN)); - TERN_(HAS_X2_MIN, _ATTACH(X2_MIN_PIN)); - TERN_(HAS_Y2_MAX, _ATTACH(Y2_MAX_PIN)); - TERN_(HAS_Y2_MIN, _ATTACH(Y2_MIN_PIN)); - TERN_(HAS_Z2_MAX, _ATTACH(Z2_MAX_PIN)); - TERN_(HAS_Z2_MIN, _ATTACH(Z2_MIN_PIN)); - TERN_(HAS_Z3_MAX, _ATTACH(Z3_MAX_PIN)); - TERN_(HAS_Z3_MIN, _ATTACH(Z3_MIN_PIN)); - TERN_(HAS_Z4_MAX, _ATTACH(Z4_MAX_PIN)); - TERN_(HAS_Z4_MIN, _ATTACH(Z4_MIN_PIN)); - TERN_(HAS_Z_MIN_PROBE_PIN, _ATTACH(Z_MIN_PROBE_PIN)); + 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/ESP32/esp32.csv b/Marlin/src/HAL/ESP32/esp32.csv new file mode 100644 index 0000000000..8f6e101f02 --- /dev/null +++ b/Marlin/src/HAL/ESP32/esp32.csv @@ -0,0 +1,6 @@ +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x5000, +otadata, data, ota, 0xe000, 0x2000, +app0, app, ota_0, 0x10000, 0x180000, +app1, app, ota_1, 0x190000, 0x180000, +spiffs, data, spiffs, 0x310000, 0xF0000, diff --git a/Marlin/src/HAL/ESP32/fastio.h b/Marlin/src/HAL/ESP32/fastio.h index 2ded3a5f62..a85423d768 100644 --- a/Marlin/src/HAL/ESP32/fastio.h +++ b/Marlin/src/HAL/ESP32/fastio.h @@ -37,21 +37,34 @@ // 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) -// Read a pin wrapper -#define READ(IO) (IS_I2S_EXPANDER_PIN(IO) ? i2s_state(I2S_EXPANDER_PIN_INDEX(IO)) : digitalRead(IO)) +#if ENABLED(USE_ESP32_EXIO) + // Read a pin wrapper + #define READ(IO) digitalRead(IO) + // Write to a pin wrapper + #define WRITE(IO, v) (IO >= 100 ? Write_EXIO(IO, v) : digitalWrite(IO, v)) +#else + // Read a pin wrapper + #define READ(IO) (IS_I2S_EXPANDER_PIN(IO) ? i2s_state(I2S_EXPANDER_PIN_INDEX(IO)) : digitalRead(IO)) + // Write to a pin wrapper + #define WRITE(IO, v) (IS_I2S_EXPANDER_PIN(IO) ? i2s_write(I2S_EXPANDER_PIN_INDEX(IO), v) : digitalWrite(IO, v)) +#endif -// Write to a pin wrapper -#define WRITE(IO, v) (IS_I2S_EXPANDER_PIN(IO) ? i2s_write(I2S_EXPANDER_PIN_INDEX(IO), v) : digitalWrite(IO, v)) - -// Set pin as input wrapper +// Set pin as input wrapper (0x80 | (v << 5) | (IO - 100)) #define SET_INPUT(IO) _SET_INPUT(IO) // Set pin as input with pullup wrapper #define SET_INPUT_PULLUP(IO) do{ _SET_INPUT(IO); _PULLUP(IO, HIGH); }while(0) +// Set pin as input with pulldown (substitution) +#define SET_INPUT_PULLDOWN SET_INPUT + // Set pin as output wrapper #define SET_OUTPUT(IO) do{ _SET_OUTPUT(IO); }while(0) @@ -61,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 99b2f755e5..f7c01730b3 100644 --- a/Marlin/src/HAL/ESP32/i2s.cpp +++ b/Marlin/src/HAL/ESP32/i2s.cpp @@ -23,6 +23,8 @@ #include "../../inc/MarlinConfigPre.h" +#if DISABLED(USE_ESP32_EXIO) + #include "i2s.h" #include "../shared/Marduino.h" @@ -32,6 +34,10 @@ #include #include "../../module/stepper.h" +#if ENABLED(FT_MOTION) + #include "../../module/ft_motion.h" +#endif + #define DMA_BUF_COUNT 8 // number of DMA buffers to store data #define DMA_BUF_LEN 4092 // maximum size in bytes #define I2S_SAMPLE_SIZE 4 // 4 bytes, 32 bits per sample @@ -62,12 +68,9 @@ uint32_t i2s_port_data = 0; #define I2S_EXIT_CRITICAL() portEXIT_CRITICAL(&i2s_spinlock[i2s_num]) static inline void gpio_matrix_out_check(uint32_t gpio, uint32_t signal_idx, bool out_inv, bool oen_inv) { - //if pin = -1, do not need to configure - if (gpio != -1) { - PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[gpio], PIN_FUNC_GPIO); - gpio_set_direction((gpio_num_t)gpio, (gpio_mode_t)GPIO_MODE_DEF_OUTPUT); - gpio_matrix_out(gpio, signal_idx, out_inv, oen_inv); - } + PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[gpio], PIN_FUNC_GPIO); + gpio_set_direction((gpio_num_t)gpio, (gpio_mode_t)GPIO_MODE_DEF_OUTPUT); + gpio_matrix_out(gpio, signal_idx, out_inv, oen_inv); } static esp_err_t i2s_reset_fifo(i2s_port_t i2s_num) { @@ -135,26 +138,61 @@ static void IRAM_ATTR i2s_intr_handler_default(void *arg) { if (high_priority_task_awoken == pdTRUE) portYIELD_FROM_ISR(); - // clear interrupt - I2S0.int_clr.val = I2S0.int_st.val; //clear pending interrupt + // Clear pending interrupt + I2S0.int_clr.val = I2S0.int_st.val; } -void stepperTask(void* parameter) { - uint32_t remaining = 0; +void stepperTask(void *parameter) { + uint32_t nextMainISR = 0; + #if ENABLED(LIN_ADVANCE) + uint32_t nextAdvanceISR = stepper.LA_ADV_NEVER; + #endif - while (1) { + for (;;) { xQueueReceive(dma.queue, &dma.current, portMAX_DELAY); dma.rw_pos = 0; + const bool using_ftMotion = TERN0(FT_MOTION, ftMotion.cfg.active); + while (dma.rw_pos < DMA_SAMPLE_COUNT) { - // Fill with the port data post pulse_phase until the next step - if (remaining) { - i2s_push_sample(); - remaining--; + + if (using_ftMotion) { + + #if ENABLED(FT_MOTION) + if (!nextMainISR) stepper.ftMotion_stepper(); + nextMainISR = 0; + #endif + } else { - Stepper::pulse_phase_isr(); - remaining = Stepper::block_phase_isr(); + + #if HAS_STANDARD_MOTION + + 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; + } + #endif + else + i2s_push_sample(); + + nextMainISR--; + + #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 + } } } @@ -184,7 +222,7 @@ int i2s_init() { // Allocate the array of pointers to the buffers dma.buffers = (uint32_t **)malloc(sizeof(uint32_t*) * DMA_BUF_COUNT); - if (dma.buffers == nullptr) return -1; + if (!dma.buffers) return -1; // Allocate each buffer that can be used by the DMA controller for (int buf_idx = 0; buf_idx < DMA_BUF_COUNT; buf_idx++) { @@ -194,7 +232,7 @@ int i2s_init() { // Allocate the array of DMA descriptors dma.desc = (lldesc_t**) malloc(sizeof(lldesc_t*) * DMA_BUF_COUNT); - if (dma.desc == nullptr) return -1; + if (!dma.desc) return -1; // Allocate each DMA descriptor that will be used by the DMA controller for (int buf_idx = 0; buf_idx < DMA_BUF_COUNT; buf_idx++) { @@ -254,13 +292,7 @@ int i2s_init() { I2S0.fifo_conf.dscr_en = 0; - I2S0.conf_chan.tx_chan_mod = ( - #if ENABLED(I2S_STEPPER_SPLIT_STREAM) - 4 - #else - 0 - #endif - ); + I2S0.conf_chan.tx_chan_mod = TERN(I2S_STEPPER_SPLIT_STREAM, 4, 0); I2S0.fifo_conf.tx_fifo_mod = 0; I2S0.conf.tx_mono = 0; @@ -311,9 +343,16 @@ int i2s_init() { xTaskCreatePinnedToCore(stepperTask, "StepperTask", 10000, nullptr, 1, nullptr, CONFIG_ARDUINO_RUNNING_CORE); // run I2S stepper task on same core as rest of Marlin // Route the i2s pins to the appropriate GPIO - gpio_matrix_out_check(I2S_DATA, I2S0O_DATA_OUT23_IDX, 0, 0); - gpio_matrix_out_check(I2S_BCK, I2S0O_BCK_OUT_IDX, 0, 0); - gpio_matrix_out_check(I2S_WS, I2S0O_WS_OUT_IDX, 0, 0); + // If a pin is not defined, no need to configure + #if defined(I2S_DATA) && I2S_DATA >= 0 + gpio_matrix_out_check(I2S_DATA, I2S0O_DATA_OUT23_IDX, 0, 0); + #endif + #if defined(I2S_BCK) && I2S_BCK >= 0 + gpio_matrix_out_check(I2S_BCK, I2S0O_BCK_OUT_IDX, 0, 0); + #endif + #if defined(I2S_WS) && I2S_WS >= 0 + gpio_matrix_out_check(I2S_WS, I2S0O_WS_OUT_IDX, 0, 0); + #endif // Start the I2S peripheral return i2s_start(I2S_NUM_0); @@ -337,7 +376,28 @@ uint8_t i2s_state(uint8_t pin) { } void i2s_push_sample() { + // Every 4Β΅s (when space in DMA buffer) toggle each expander PWM output using + // the current duty cycle/frequency so they sync with any steps (once + // through the DMA/FIFO buffers). PWM signal inversion handled by other functions + for (uint8_t p = 0; p < MAX_EXPANDER_BITS; ++p) { + if (hal.pwm_pin_data[p].pwm_duty_ticks > 0) { // pin has active pwm? + if (hal.pwm_pin_data[p].pwm_tick_count == 0) { + if (TEST32(i2s_port_data, p)) { // hi->lo + CBI32(i2s_port_data, p); + hal.pwm_pin_data[p].pwm_tick_count = hal.pwm_pin_data[p].pwm_cycle_ticks - hal.pwm_pin_data[p].pwm_duty_ticks; + } + else { // lo->hi + SBI32(i2s_port_data, p); + hal.pwm_pin_data[p].pwm_tick_count = hal.pwm_pin_data[p].pwm_duty_ticks; + } + } + else + hal.pwm_pin_data[p].pwm_tick_count--; + } + } + dma.current[dma.rw_pos++] = i2s_port_data; } +#endif // !USE_ESP32_EXIO #endif // ARDUINO_ARCH_ESP32 diff --git a/Marlin/src/HAL/ESP32/inc/Conditionals_LCD.h b/Marlin/src/HAL/ESP32/inc/Conditionals_LCD.h index 4da600179d..07d2efe0bd 100644 --- a/Marlin/src/HAL/ESP32/inc/Conditionals_LCD.h +++ b/Marlin/src/HAL/ESP32/inc/Conditionals_LCD.h @@ -21,6 +21,6 @@ */ #pragma once -#if HAS_SPI_TFT || HAS_FSMC_TFT - #error "Sorry! TFT displays are not available for HAL/ESP32." +#if ANY(MKS_MINI_12864, FYSETC_MINI_12864_2_1) + #define U8G_HW_SPI_ESP32 1 #endif diff --git a/Marlin/src/HAL/ESP32/inc/Conditionals_adv.h b/Marlin/src/HAL/ESP32/inc/Conditionals_adv.h index 5f1c4b1601..3ca806897a 100644 --- a/Marlin/src/HAL/ESP32/inc/Conditionals_adv.h +++ b/Marlin/src/HAL/ESP32/inc/Conditionals_adv.h @@ -20,3 +20,10 @@ * */ #pragma once + +// +// Board-specific options need to be defined before HAL.h +// +#if MB(MKS_TINYBEE) + #define MAX_EXPANDER_BITS 24 // TinyBee has 3 x HC595 +#endif diff --git a/Marlin/src/HAL/ESP32/inc/Conditionals_type.h b/Marlin/src/HAL/ESP32/inc/Conditionals_type.h new file mode 100644 index 0000000000..82f95a1035 --- /dev/null +++ b/Marlin/src/HAL/ESP32/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/ESP32/inc/SanityCheck.h b/Marlin/src/HAL/ESP32/inc/SanityCheck.h index f57a6c5910..dd199c390d 100644 --- a/Marlin/src/HAL/ESP32/inc/SanityCheck.h +++ b/Marlin/src/HAL/ESP32/inc/SanityCheck.h @@ -21,18 +21,45 @@ */ #pragma once +#if HAS_SPI_TFT || HAS_FSMC_TFT + #error "Sorry! TFT displays are not available for HAL/ESP32." +#endif + #if ENABLED(EMERGENCY_PARSER) #error "EMERGENCY_PARSER is not yet implemented for ESP32. 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 on ESP32." +#if ENABLED(SPINDLE_LASER_USE_PWM) && SPINDLE_LASER_FREQUENCY > 78125 + #error "SPINDLE_LASER_FREQUENCY maximum value is 78125Hz for ESP32." +#endif +#if ENABLED(FAST_PWM_FAN) && FAST_PWM_FAN_FREQUENCY > 78125 + #error "FAST_PWM_FREQUENCY maximum value is 78125Hz for ESP32." #endif #if HAS_TMC_SW_SERIAL - #error "TMC220x Software Serial is not supported on this platform." + #error "TMC220x Software Serial is not supported on ESP32." #endif -#if BOTH(WIFISUPPORT, ESP3D_WIFISUPPORT) +#if ALL(WIFISUPPORT, ESP3D_WIFISUPPORT) #error "Only enable one WiFi option, either WIFISUPPORT or ESP3D_WIFISUPPORT." #endif + +#if ENABLED(POSTMORTEM_DEBUGGING) + #error "POSTMORTEM_DEBUGGING is not yet supported on ESP32." +#endif + +#if MB(MKS_TINYBEE) && ENABLED(FAST_PWM_FAN) + #error "FAST_PWM_FAN is not available on TinyBee." +#endif + +#if USING_PULLDOWNS + #error "PULLDOWN pin mode is not available on ESP32 boards." +#endif + +#if ALL(I2S_STEPPER_STREAM, LIN_ADVANCE) && DISABLED(EXPERIMENTAL_I2S_LA) + #error "I2S stream is currently incompatible with LIN_ADVANCE." +#endif + +#if ALL(I2S_STEPPER_STREAM, PRINTCOUNTER) && PRINTCOUNTER_SAVE_INTERVAL > 0 && DISABLED(PRINTCOUNTER_SYNC) + #error "PRINTCOUNTER_SAVE_INTERVAL may cause issues on ESP32 with an I2S expander. Define PRINTCOUNTER_SYNC in Configuration.h for an imperfect solution." +#endif diff --git a/Marlin/src/HAL/ESP32/pinsDebug.h b/Marlin/src/HAL/ESP32/pinsDebug.h new file mode 100644 index 0000000000..42304b2a0b --- /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 15f8f2ab6b..b50ea725ab 100644 --- a/Marlin/src/HAL/ESP32/spi_pins.h +++ b/Marlin/src/HAL/ESP32/spi_pins.h @@ -2,6 +2,9 @@ * Marlin 3D Printer Firmware * Copyright (c) 2020 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,16 @@ */ #pragma once -#define SS_PIN SDSS -#define SCK_PIN 18 -#define MISO_PIN 19 -#define 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 3300aea8a8..a2996a860f 100644 --- a/Marlin/src/HAL/ESP32/timers.cpp +++ b/Marlin/src/HAL/ESP32/timers.cpp @@ -41,11 +41,11 @@ static timg_dev_t *TG[2] = {&TIMERG0, &TIMERG1}; -const tTimerConfig TimerConfig [NUM_HARDWARE_TIMERS] = { +const tTimerConfig timer_config[NUM_HARDWARE_TIMERS] = { { TIMER_GROUP_0, TIMER_0, STEPPER_TIMER_PRESCALE, stepTC_Handler }, // 0 - Stepper { TIMER_GROUP_0, TIMER_1, TEMP_TIMER_PRESCALE, tempTC_Handler }, // 1 - Temperature { TIMER_GROUP_1, TIMER_0, PWM_TIMER_PRESCALE, pwmTC_Handler }, // 2 - PWM - { TIMER_GROUP_1, TIMER_1, 1, nullptr }, // 3 + { TIMER_GROUP_1, TIMER_1, TONE_TIMER_PRESCALE, toneTC_Handler }, // 3 - Tone }; // ------------------------ @@ -53,7 +53,7 @@ const tTimerConfig TimerConfig [NUM_HARDWARE_TIMERS] = { // ------------------------ void IRAM_ATTR timer_isr(void *para) { - const tTimerConfig& timer = TimerConfig[(int)para]; + const tTimerConfig& timer = timer_config[(int)para]; // Retrieve the interrupt status and the counter value // from the timer that reported the interrupt @@ -78,11 +78,11 @@ 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, uint32_t frequency) { - const tTimerConfig timer = TimerConfig[timer_num]; +void HAL_timer_start(const uint8_t timer_num, const uint32_t frequency) { + const tTimerConfig timer = timer_config[timer_num]; timer_config_t config; config.divider = timer.divider; @@ -90,7 +90,7 @@ void HAL_timer_start(const uint8_t timer_num, 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); @@ -111,12 +111,12 @@ void HAL_timer_start(const uint8_t timer_num, uint32_t frequency) { /** * Set the upper value of the timer, when the timer reaches this upper value the * interrupt should be triggered and the counter reset - * @param timer_num timer number to set the count to - * @param count threshold at which the interrupt is triggered + * @param timer_num timer number to set the compare value to + * @param compare threshold at which the interrupt is triggered */ -void HAL_timer_set_compare(const uint8_t timer_num, hal_timer_t count) { - const tTimerConfig timer = TimerConfig[timer_num]; - timer_set_alarm_value(timer.group, timer.idx, count); +void HAL_timer_set_compare(const uint8_t timer_num, const hal_timer_t compare) { + const tTimerConfig timer = timer_config[timer_num]; + timer_set_alarm_value(timer.group, timer.idx, compare); } /** @@ -125,7 +125,7 @@ void HAL_timer_set_compare(const uint8_t timer_num, hal_timer_t count) { * @return the timer current threshold for the alarm to be triggered */ hal_timer_t HAL_timer_get_compare(const uint8_t timer_num) { - const tTimerConfig timer = TimerConfig[timer_num]; + const tTimerConfig timer = timer_config[timer_num]; uint64_t alarm_value; timer_get_alarm_value(timer.group, timer.idx, &alarm_value); @@ -139,7 +139,7 @@ hal_timer_t HAL_timer_get_compare(const uint8_t timer_num) { * @return the current counter of the alarm */ hal_timer_t HAL_timer_get_count(const uint8_t timer_num) { - const tTimerConfig timer = TimerConfig[timer_num]; + const tTimerConfig timer = timer_config[timer_num]; uint64_t counter_value; timer_get_counter_value(timer.group, timer.idx, &counter_value); return counter_value; @@ -150,7 +150,7 @@ hal_timer_t HAL_timer_get_count(const uint8_t timer_num) { * @param timer_num timer number to enable interrupts on */ void HAL_timer_enable_interrupt(const uint8_t timer_num) { - //const tTimerConfig timer = TimerConfig[timer_num]; + //const tTimerConfig timer = timer_config[timer_num]; //timer_enable_intr(timer.group, timer.idx); } @@ -159,12 +159,12 @@ void HAL_timer_enable_interrupt(const uint8_t timer_num) { * @param timer_num timer number to disable interrupts on */ void HAL_timer_disable_interrupt(const uint8_t timer_num) { - //const tTimerConfig timer = TimerConfig[timer_num]; + //const tTimerConfig timer = timer_config[timer_num]; //timer_disable_intr(timer.group, timer.idx); } bool HAL_timer_interrupt_enabled(const uint8_t timer_num) { - const tTimerConfig timer = TimerConfig[timer_num]; + const tTimerConfig timer = timer_config[timer_num]; return TG[timer.group]->int_ena.val | BIT(timer_num); } diff --git a/Marlin/src/HAL/ESP32/timers.h b/Marlin/src/HAL/ESP32/timers.h index d722670f33..d656e92224 100644 --- a/Marlin/src/HAL/ESP32/timers.h +++ b/Marlin/src/HAL/ESP32/timers.h @@ -24,49 +24,51 @@ #include #include -// Includes needed to get I2S_STEPPER_STREAM. Note that pins.h -// is included in case this header is being included early. -#include "../../inc/MarlinConfig.h" -#include "../../pins/pins.h" - // ------------------------ // Defines // ------------------------ -// #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 STEP_TIMER_NUM - #define STEP_TIMER_NUM 0 // Timer Index for Stepper +#ifndef MF_TIMER_STEP + #define MF_TIMER_STEP 0 // Timer Index for Stepper #endif -#ifndef PULSE_TIMER_NUM - #define PULSE_TIMER_NUM STEP_TIMER_NUM +#ifndef MF_TIMER_PULSE + #define MF_TIMER_PULSE MF_TIMER_STEP // Timer Index for Pulse interval #endif -#ifndef TEMP_TIMER_NUM - #define TEMP_TIMER_NUM 1 // Timer Index for Temperature +#ifndef MF_TIMER_TEMP + #define MF_TIMER_TEMP 1 // Timer Index for Temperature #endif -#ifndef PWM_TIMER_NUM - #define PWM_TIMER_NUM 2 // index of timer to use for PWM outputs +#ifndef MF_TIMER_PWM + #define MF_TIMER_PWM 2 // Timer Index for PWM outputs +#endif +#ifndef MF_TIMER_TONE + #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 TEMP_TIMER_PRESCALE 1000 // prescaler for setting Temp timer, 72Khz -#define TEMP_TIMER_FREQUENCY 1000 // temperature interrupt frequency +#define PULSE_TIMER_RATE STEPPER_TIMER_RATE // (Hz) Frequency of Pulse Timer +#define PULSE_TIMER_PRESCALE STEPPER_TIMER_PRESCALE + +#define TONE_TIMER_PRESCALE 1000 // Arbitrary value, no idea what i'm doing here #define PWM_TIMER_PRESCALE 10 #if ENABLED(FAST_PWM_FAN) @@ -76,16 +78,12 @@ 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 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(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_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) #ifndef HAL_TEMP_TIMER_ISR #define HAL_TEMP_TIMER_ISR() extern "C" void tempTC_Handler() @@ -96,10 +94,16 @@ typedef uint64_t hal_timer_t; #ifndef HAL_PWM_TIMER_ISR #define HAL_PWM_TIMER_ISR() extern "C" void pwmTC_Handler() #endif +#ifndef HAL_TONE_TIMER_ISR + #define HAL_TONE_TIMER_ISR() extern "C" void toneTC_Handler() +#endif -extern "C" void tempTC_Handler(); -extern "C" void stepTC_Handler(); -extern "C" void pwmTC_Handler(); +extern "C" { + void tempTC_Handler(); + void stepTC_Handler(); + void pwmTC_Handler(); + void toneTC_Handler(); +} // ------------------------ // Types @@ -116,13 +120,13 @@ typedef struct { // Public Variables // ------------------------ -extern const tTimerConfig TimerConfig[]; +extern const tTimerConfig timer_config[]; // ------------------------ // Public functions // ------------------------ -void HAL_timer_start (const uint8_t timer_num, uint32_t frequency); +void HAL_timer_start(const uint8_t timer_num, const uint32_t frequency); void HAL_timer_set_compare(const uint8_t timer_num, const hal_timer_t count); hal_timer_t HAL_timer_get_compare(const uint8_t timer_num); hal_timer_t HAL_timer_get_count(const uint8_t timer_num); @@ -131,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(TIMER_NUM) -#define HAL_timer_isr_epilogue(TIMER_NUM) +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/LCD_defines.h b/Marlin/src/HAL/ESP32/u8g/LCD_defines.h new file mode 100644 index 0000000000..3696d60d7c --- /dev/null +++ b/Marlin/src/HAL/ESP32/u8g/LCD_defines.h @@ -0,0 +1,29 @@ +/** + * 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 + * (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 + +/** + * ESP32 LCD-specific defines + */ + +uint8_t u8g_esp32_hw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr); +#define U8G_COM_HAL_HW_SPI_FN u8g_esp32_hw_spi_fn diff --git a/Marlin/src/HAL/ESP32/u8g_esp32_spi.cpp b/Marlin/src/HAL/ESP32/u8g_esp32_spi.cpp new file mode 100644 index 0000000000..42903da9d3 --- /dev/null +++ b/Marlin/src/HAL/ESP32/u8g_esp32_spi.cpp @@ -0,0 +1,104 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2022 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] + * + * Based on Sprinter and grbl. + * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm + * + * Copypaste of SAMD51 HAL developed by Giuliano Zaro (AKA GMagician) + * + * 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_ESP32 + +#include "../../inc/MarlinConfig.h" + +#if U8G_HW_SPI_ESP32 + +#include +#include "../shared/HAL_SPI.h" +#include "HAL.h" +#include "SPI.h" + +#if HAS_MEDIA + #include "../../sd/cardreader.h" + #if ENABLED(ESP3D_WIFISUPPORT) + #include + #endif +#endif + +static SPISettings spiConfig; + +#ifndef LCD_SPI_SPEED + #ifdef SD_SPI_SPEED + #define LCD_SPI_SPEED SD_SPI_SPEED // Assume SPI speed shared with SD + #else + #define LCD_SPI_SPEED SPI_FULL_SPEED // Use full speed if SD speed is not supplied + #endif +#endif + +uint8_t u8g_esp32_hw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr) { + static uint8_t msgInitCount = 2; // Ignore all messages until 2nd U8G_COM_MSG_INIT + + #if ENABLED(PAUSE_LCD_FOR_BUSY_SD) + if (card.flag.saving || card.flag.logging || TERN0(ESP3D_WIFISUPPORT, sd_busy_lock == true)) return 0; + #endif + + if (msgInitCount) { + if (msg == U8G_COM_MSG_INIT) msgInitCount--; + if (msgInitCount) return -1; + } + + switch (msg) { + case U8G_COM_MSG_STOP: break; + + case U8G_COM_MSG_INIT: + OUT_WRITE(DOGLCD_CS, HIGH); + OUT_WRITE(DOGLCD_A0, HIGH); + OUT_WRITE(LCD_RESET_PIN, HIGH); + u8g_Delay(5); + spiBegin(); + spiInit(LCD_SPI_SPEED); + break; + + case U8G_COM_MSG_ADDRESS: /* define cmd (arg_val = 0) or data mode (arg_val = 1) */ + WRITE(DOGLCD_A0, arg_val ? HIGH : LOW); + break; + + case U8G_COM_MSG_CHIP_SELECT: /* arg_val == 0 means HIGH level of U8G_PI_CS */ + WRITE(DOGLCD_CS, arg_val ? LOW : HIGH); + break; + + case U8G_COM_MSG_RESET: + WRITE(LCD_RESET_PIN, arg_val); + break; + + case U8G_COM_MSG_WRITE_BYTE: + spiSend((uint8_t)arg_val); + break; + + case U8G_COM_MSG_WRITE_SEQ: + uint8_t *ptr = (uint8_t*) arg_ptr; + while (arg_val > 0) { + spiSend(*ptr++); + arg_val--; + } + break; + } + return 1; +} + +#endif // U8G_HW_SPI_ESP32 +#endif // ARDUINO_ARCH_ESP32 diff --git a/Marlin/src/HAL/ESP32/WebSocketSerial.cpp b/Marlin/src/HAL/ESP32/wifi/WebSocketSerial.cpp similarity index 93% rename from Marlin/src/HAL/ESP32/WebSocketSerial.cpp rename to Marlin/src/HAL/ESP32/wifi/WebSocketSerial.cpp index ca7f47a1f8..9c8933289d 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) @@ -29,7 +29,7 @@ #include "wifi.h" #include -WebSocketSerial webSocketSerial; +MSerialWebSocketT webSocketSerial(false); AsyncWebSocket ws("/ws"); // TODO Move inside the class. // RingBuffer impl @@ -137,16 +137,12 @@ size_t WebSocketSerial::write(const uint8_t c) { return ret; } -size_t WebSocketSerial::write(const uint8_t* buffer, size_t size) { +size_t WebSocketSerial::write(const uint8_t *buffer, size_t size) { size_t written = 0; for (size_t i = 0; i < size; i++) written += write(buffer[i]); return written; } -void WebSocketSerial::flushTX() { - // No need to do anything as there's no benefit to sending partial lines over the websocket connection. -} - #endif // WIFISUPPORT #endif // ARDUINO_ARCH_ESP32 diff --git a/Marlin/src/HAL/ESP32/WebSocketSerial.h b/Marlin/src/HAL/ESP32/wifi/WebSocketSerial.h similarity index 86% rename from Marlin/src/HAL/ESP32/WebSocketSerial.h rename to Marlin/src/HAL/ESP32/wifi/WebSocketSerial.h index 7a25c6dc5e..3d2fdf1e6a 100644 --- a/Marlin/src/HAL/ESP32/WebSocketSerial.h +++ b/Marlin/src/HAL/ESP32/wifi/WebSocketSerial.h @@ -21,7 +21,8 @@ */ #pragma once -#include "../../inc/MarlinConfig.h" +#include "../../../inc/MarlinConfig.h" +#include "../../../core/serial_hook.h" #include @@ -53,7 +54,7 @@ public: ring_buffer_pos_t read(uint8_t *buffer); void flush(); ring_buffer_pos_t write(const uint8_t c); - ring_buffer_pos_t write(const uint8_t* buffer, ring_buffer_pos_t size); + ring_buffer_pos_t write(const uint8_t *buffer, ring_buffer_pos_t size); }; class WebSocketSerial: public Stream { @@ -68,11 +69,8 @@ public: int peek(); int read(); void flush(); - void flushTX(); size_t write(const uint8_t c); - size_t write(const uint8_t* buffer, size_t size); - - operator bool() { return true; } + size_t write(const uint8_t *buffer, size_t size); #if ENABLED(SERIAL_STATS_DROPPED_RX) FORCE_INLINE uint32_t dropped() { return 0; } @@ -83,4 +81,5 @@ public: #endif }; -extern WebSocketSerial webSocketSerial; +typedef Serial1Class MSerialWebSocketT; +extern MSerialWebSocketT webSocketSerial; diff --git a/Marlin/src/HAL/ESP32/ota.cpp b/Marlin/src/HAL/ESP32/wifi/ota.cpp similarity index 90% rename from Marlin/src/HAL/ESP32/ota.cpp rename to Marlin/src/HAL/ESP32/wifi/ota.cpp index 69a3e25e56..03508840a5 100644 --- a/Marlin/src/HAL/ESP32/ota.cpp +++ b/Marlin/src/HAL/ESP32/wifi/ota.cpp @@ -1,7 +1,9 @@ /** * Marlin 3D Printer Firmware * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com + * + * 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 @@ -20,11 +22,15 @@ #ifdef ARDUINO_ARCH_ESP32 -#include "../../inc/MarlinConfigPre.h" - -#if BOTH(WIFISUPPORT, OTASUPPORT) - #include + +#undef ENABLED +#undef DISABLED + +#include "../../../inc/MarlinConfigPre.h" + +#if ALL(WIFISUPPORT, OTASUPPORT) + #include #include #include @@ -50,7 +56,7 @@ void OTA_init() { }) .onError([](ota_error_t error) { Serial.printf("Error[%u]: ", error); - char *str; + const char *str = "unknown"; switch (error) { case OTA_AUTH_ERROR: str = "Auth Failed"; break; case OTA_BEGIN_ERROR: str = "Begin Failed"; break; diff --git a/Marlin/src/HAL/ESP32/ota.h b/Marlin/src/HAL/ESP32/wifi/ota.h similarity index 90% rename from Marlin/src/HAL/ESP32/ota.h rename to Marlin/src/HAL/ESP32/wifi/ota.h index 546ace82db..a91d04dbb7 100644 --- a/Marlin/src/HAL/ESP32/ota.h +++ b/Marlin/src/HAL/ESP32/wifi/ota.h @@ -1,7 +1,9 @@ /** * Marlin 3D Printer Firmware * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com + * + * 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 diff --git a/Marlin/src/HAL/ESP32/spiffs.cpp b/Marlin/src/HAL/ESP32/wifi/spiffs.cpp similarity index 91% rename from Marlin/src/HAL/ESP32/spiffs.cpp rename to Marlin/src/HAL/ESP32/wifi/spiffs.cpp index a0e713bff0..ba891acda9 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 BOTH(WIFISUPPORT, WEBSUPPORT) +#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 92% rename from Marlin/src/HAL/ESP32/web.cpp rename to Marlin/src/HAL/ESP32/wifi/web.cpp index 7a27707a3e..f08dfd87c0 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 BOTH(WIFISUPPORT, WEBSUPPORT) +#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 87% rename from Marlin/src/HAL/ESP32/wifi.cpp rename to Marlin/src/HAL/ESP32/wifi/wifi.cpp index f4cf5a606a..1f31ca1bb9 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 @@ -59,7 +60,7 @@ void wifi_init() { MDNS.addService("http", "tcp", 80); - SERIAL_ECHOLNPAIR("Successfully connected to WiFi with SSID '" WIFI_SSID "', hostname: '" WIFI_HOSTNAME "', IP address: ", WiFi.localIP().toString().c_str()); + SERIAL_ECHOLNPGM("Successfully connected to WiFi with SSID '" WIFI_SSID "', hostname: '" WIFI_HOSTNAME "', IP address: ", WiFi.localIP().toString().c_str()); } #endif // WIFISUPPORT 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/HAL.cpp b/Marlin/src/HAL/GD32_MFL/HAL.cpp new file mode 100644 index 0000000000..9ba20784f0 --- /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 0000000000..6195212028 --- /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 0000000000..d0731f9e84 --- /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 0000000000..95ea8bea25 --- /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 0000000000..6b33ce0e61 --- /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 0000000000..2cc79c8853 --- /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 0000000000..61800eda1c --- /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 0000000000..6cbcbc049b --- /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 0000000000..80cff46ff8 --- /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 0000000000..b36cbfe44d --- /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 0000000000..1053c80a41 --- /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 0000000000..765c997e1f --- /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 0000000000..8860e53a62 --- /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 0000000000..175dec3959 --- /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 0000000000..9fba673efc --- /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 0000000000..11020f3e52 --- /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 0000000000..379ecfa7f0 --- /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 0000000000..6df84f6f92 --- /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 0000000000..a3db122682 --- /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 0000000000..92cc457df5 --- /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 0000000000..366765dcd5 --- /dev/null +++ b/Marlin/src/HAL/GD32_MFL/inc/SanityCheck.h @@ -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 . + * + */ +#pragma once + +// 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 0000000000..be7e4ce1ba --- /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 0000000000..2e7ba4dfd9 --- /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 0000000000..de28c40809 --- /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 0000000000..a474c1977e --- /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 0000000000..a39e8c7a66 --- /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 0000000000..c8a5836838 --- /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 0000000000..bd78fba5b9 --- /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 0000000000..3bcfdd464b --- /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 0000000000..8aff77aa96 --- /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 0000000000..720d958779 --- /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 5eca2f7eac..a211dfe259 100644 --- a/Marlin/src/HAL/HAL.h +++ b/Marlin/src/HAL/HAL.h @@ -23,14 +23,13 @@ #include "platforms.h" -#include HAL_PATH(.,HAL.h) - -#ifdef SERIAL_PORT_2 - #define NUM_SERIAL 2 -#else - #define NUM_SERIAL 1 +#ifndef GCC_VERSION + #define GCC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) #endif +#include HAL_PATH(.., HAL.h) +extern MarlinHAL hal; + #define HAL_ADC_RANGE _BV(HAL_ADC_RESOLUTION) #ifndef I2C_ADDRESS @@ -46,7 +45,3 @@ #ifndef PGMSTR #define PGMSTR(NAM,STR) const char NAM[] = STR #endif - -inline void watchdog_refresh() { - TERN_(USE_WATCHDOG, HAL_watchdog_refresh()); -} diff --git a/Marlin/src/HAL/HC32/HAL.cpp b/Marlin/src/HAL/HC32/HAL.cpp new file mode 100644 index 0000000000..a74d21e6fd --- /dev/null +++ b/Marlin/src/HAL/HC32/HAL.cpp @@ -0,0 +1,56 @@ +/** + * 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 + * (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_HC32 + +#include "HAL.h" +#include +#include + +// +// Emergency Parser +// +#if ENABLED(EMERGENCY_PARSER) + +extern "C" void core_hook_usart_rx_irq(uint8_t ch, uint8_t usart) { + // Only handle receive on host serial ports + if (false + #ifdef SERIAL_PORT + || usart != SERIAL_PORT + #endif + #ifdef SERIAL_PORT_2 + || usart != SERIAL_PORT_2 + #endif + #ifdef SERIAL_PORT_3 + || usart != SERIAL_PORT_3 + #endif + ) { + return; + } + + // Submit character to emergency parser + if (MYSERIAL1.emergency_parser_enabled()) + emergency_parser.update(MYSERIAL1.emergency_state, ch); +} + +#endif // EMERGENCY_PARSER +#endif // ARDUINO_ARCH_HC32 diff --git a/Marlin/src/HAL/HC32/HAL.h b/Marlin/src/HAL/HC32/HAL.h new file mode 100644 index 0000000000..f751dea091 --- /dev/null +++ b/Marlin/src/HAL/HC32/HAL.h @@ -0,0 +1,119 @@ +/** + * 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 + * (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 . + * + */ + +/** + * HAL for HC32F460 based boards + * + * Note: MarlinHAL class is in MarlinHAL.h/cpp + */ + +#define CPU_32_BIT + +#include "../../inc/MarlinConfig.h" + +#include "../shared/Marduino.h" +#include "../shared/math_32bit.h" +#include "../shared/HAL_SPI.h" + +#include "fastio.h" +#include "timers.h" + +// +// Serial Ports +// + +#include "MarlinSerial.h" + +// +// Emergency Parser +// +#if ENABLED(EMERGENCY_PARSER) + extern "C" void usart_rx_irq_hook(uint8_t ch, uint8_t usart); +#endif + +// +// Misc. Defines +// +#define square(x) ((x) * (x)) + +#ifndef strncpy_P + #define strncpy_P(dest, src, num) strncpy((dest), (src), (num)) +#endif + +// +// Misc. Functions +// +#ifndef analogInputToDigitalPin + #define analogInputToDigitalPin(p) pin_t(p) +#endif + +#define CRITICAL_SECTION_START() \ + const bool irqon = !__get_PRIMASK(); \ + __disable_irq(); \ + __DSB(); +#define CRITICAL_SECTION_END() \ + __DSB(); \ + if (irqon) __enable_irq(); + +#define cli() __disable_irq() +#define sei() __enable_irq() + +// bss_end alias +#define __bss_end __bss_end__ + +// Fix bug in pgm_read_ptr +#undef pgm_read_ptr +#define pgm_read_ptr(addr) (*(addr)) + +// +// ADC +// +#define HAL_ADC_VREF_MV 3300 +#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 +// +#include "MarlinHAL.h" diff --git a/Marlin/src/HAL/HC32/MarlinHAL.cpp b/Marlin/src/HAL/HC32/MarlinHAL.cpp new file mode 100644 index 0000000000..f1d85f1694 --- /dev/null +++ b/Marlin/src/HAL/HC32/MarlinHAL.cpp @@ -0,0 +1,309 @@ +/** + * 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 + * (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 . + * + */ + +/** + * HAL for HC32F460, based heavily on the legacy implementation and STM32F1 + */ +#ifdef ARDUINO_ARCH_HC32 + +#include "../../inc/MarlinConfig.h" + +#include "HAL.h" // Includes MarlinHAL.h +#include +#include + +#if TEMP_SENSOR_SOC + #include +#endif + +extern "C" char *_sbrk(int incr); + +#if ENABLED(POSTMORTEM_DEBUGGING) + // From MinSerial.cpp + extern void install_min_serial(); +#endif + +#if ENABLED(MARLIN_DEV_MODE) + inline void HAL_clock_frequencies_dump() { + // 1. dump all clock frequencies + update_system_clock_frequencies(); + SERIAL_ECHOPGM( + "-- clocks dump -- \nSYS=", SYSTEM_CLOCK_FREQUENCIES.system, + "\nHCLK=", SYSTEM_CLOCK_FREQUENCIES.hclk, + "\nPCLK0=", SYSTEM_CLOCK_FREQUENCIES.pclk0, + "\nPCLK1=", SYSTEM_CLOCK_FREQUENCIES.pclk1, + "\nPCLK2=", SYSTEM_CLOCK_FREQUENCIES.pclk2, + "\nPCLK3=", SYSTEM_CLOCK_FREQUENCIES.pclk3, + "\nPCLK4=", SYSTEM_CLOCK_FREQUENCIES.pclk4, + "\nEXCLK=", SYSTEM_CLOCK_FREQUENCIES.exclk, + "\nF_CPU=", F_CPU + ); + + // 2. dump current system clock source + en_clk_sys_source_t clkSrc = CLK_GetSysClkSource(); + SERIAL_ECHOPGM("\nSYSCLK="); + switch (clkSrc) { + case ClkSysSrcHRC: SERIAL_ECHOPGM("HRC"); break; + case ClkSysSrcMRC: SERIAL_ECHOPGM("MRC"); break; + case ClkSysSrcLRC: SERIAL_ECHOPGM("LRC"); break; + case ClkSysSrcXTAL: SERIAL_ECHOPGM("XTAL"); break; + case ClkSysSrcXTAL32: SERIAL_ECHOPGM("XTAL32"); break; + case CLKSysSrcMPLL: SERIAL_ECHOPGM("MPLL"); + + // 3. if MPLL is used, dump MPLL settings: + // (derived from CLK_SetPllSource and CLK_MpllConfig) + // source + switch (M4_SYSREG->CMU_PLLCFGR_f.PLLSRC) { + case ClkPllSrcXTAL: SERIAL_ECHOPGM(",XTAL"); break; + case ClkPllSrcHRC: SERIAL_ECHOPGM(",HRC"); break; + default: break; + } + + // PLL multipliers and dividers + SERIAL_ECHOPGM( + "\nP=", M4_SYSREG->CMU_PLLCFGR_f.MPLLP + 1UL, + "\nQ=", M4_SYSREG->CMU_PLLCFGR_f.MPLLQ + 1UL, + "\nR=", M4_SYSREG->CMU_PLLCFGR_f.MPLLR + 1UL, + "\nN=", M4_SYSREG->CMU_PLLCFGR_f.MPLLN + 1UL, + "\nM=", M4_SYSREG->CMU_PLLCFGR_f.MPLLM + 1UL + ); + break; + default: break; + } + + // Done + SERIAL_ECHOPGM("\n--\n"); + } +#endif // MARLIN_DEV_MODE + +// +// MarlinHAL class implementation +// + +pin_t MarlinHAL::last_adc_pin; + +#if TEMP_SENSOR_SOC + float MarlinHAL::soc_temp = 0; +#endif + +MarlinHAL::MarlinHAL() {} + +void MarlinHAL::watchdog_init() { + TERN_(USE_WATCHDOG, WDT.begin(5000)); // Reset on 5 second timeout +} + +void MarlinHAL::watchdog_refresh() { + TERN_(USE_WATCHDOG, WDT.reload()); +} + +void MarlinHAL::init() { + NVIC_SetPriorityGrouping(0x3); + + // Print clock frequencies to host serial + TERN_(MARLIN_DEV_MODE, HAL_clock_frequencies_dump()); + + // 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() {} + +void MarlinHAL::reboot() { + NVIC_SystemReset(); +} + +bool MarlinHAL::isr_state() { + return !__get_PRIMASK(); +} + +void MarlinHAL::isr_on() { + __enable_irq(); +} + +void MarlinHAL::isr_off() { + __disable_irq(); +} + +void MarlinHAL::delay_ms(const int ms) { + delay(ms); +} + +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 + stc_rmu_rstcause_t rstCause; + RMU_GetResetCause(&rstCause); + + // Map reset cause code to those expected by Marlin + // - Reset causes are flags, so multiple can be set + TERN_(MARLIN_DEV_MODE, printf("-- Reset Cause -- \n")); + uint8_t cause = 0; + #define MAP_CAUSE(from, to) \ + if (rstCause.from == Set) { \ + TERN_(MARLIN_DEV_MODE, printf(" - " STRINGIFY(from) "\n")); \ + cause |= to; \ + } + + // Power on + MAP_CAUSE(enPowerOn, RST_POWER_ON) // Power on reset + + // External + MAP_CAUSE(enRstPin, RST_EXTERNAL) // Reset pin + MAP_CAUSE(enPvd1, RST_EXTERNAL) // Program voltage detection reset + MAP_CAUSE(enPvd2, RST_EXTERNAL) // " + + // Brown out + MAP_CAUSE(enBrownOut, RST_BROWN_OUT) // Brown out reset + + // Wdt + MAP_CAUSE(enWdt, RST_WATCHDOG) // Watchdog reset + MAP_CAUSE(enSwdt, RST_WATCHDOG) // Special WDT reset + + // Software + MAP_CAUSE(enPowerDown, RST_SOFTWARE) // MCU power down (?) + MAP_CAUSE(enSoftware, RST_SOFTWARE) // Software reset (e.g. NVIC_SystemReset()) + + // Misc. + MAP_CAUSE(enMpuErr, RST_BACKUP) // MPU error + MAP_CAUSE(enRamParityErr, RST_BACKUP) // RAM parity error + MAP_CAUSE(enRamEcc, RST_BACKUP) // RAM ecc error + MAP_CAUSE(enClkFreqErr, RST_BACKUP) // Clock frequency failure + MAP_CAUSE(enXtalErr, RST_BACKUP) // XTAL failure + + #undef MAP_CAUSE + return cause; +} + +void MarlinHAL::clear_reset_source() { + RMU_ClrResetFlag(); +} + +int MarlinHAL::freeMemory() { + volatile char top; + return &top - _sbrk(0); +} + +void MarlinHAL::adc_init() { + analogReadResolution(HAL_ADC_RESOLUTION); +} + +void MarlinHAL::adc_enable(const pin_t pin) { + #if TEMP_SENSOR_SOC + if (pin == TEMP_SOC_PIN) { + // Start OTS, min. 1s between reads + ChipTemperature.begin(); + ChipTemperature.setMinimumReadDeltaMillis(1000); + return; + } + #endif + + // Just set pin mode to analog + pinMode(pin, INPUT_ANALOG); +} + +void MarlinHAL::adc_start(const pin_t pin) { + MarlinHAL::last_adc_pin = pin; + + #if TEMP_SENSOR_SOC + if (pin == TEMP_SOC_PIN) { + // Read OTS + float temp; + if (ChipTemperature.read(temp)) + MarlinHAL::soc_temp = temp; + return; + } + #endif + + CORE_ASSERT(IS_GPIO_PIN(pin), "adc_start: invalid pin") + + analogReadAsync(pin); +} + +bool MarlinHAL::adc_ready() { + #if TEMP_SENSOR_SOC + if (MarlinHAL::last_adc_pin == TEMP_SOC_PIN) return true; + #endif + + CORE_ASSERT(IS_GPIO_PIN(MarlinHAL::last_adc_pin), "adc_ready: invalid pin") + + return getAnalogReadComplete(MarlinHAL::last_adc_pin); +} + +uint16_t MarlinHAL::adc_value() { + #if TEMP_SENSOR_SOC + if (MarlinHAL::last_adc_pin == TEMP_SOC_PIN) + return OTS_FLOAT_TO_ADC_READING(MarlinHAL::soc_temp); + #endif + + // Read conversion result + CORE_ASSERT(IS_GPIO_PIN(MarlinHAL::last_adc_pin), "adc_value: invalid pin") + + return getAnalogReadValue(MarlinHAL::last_adc_pin); +} + +void MarlinHAL::set_pwm_duty(const pin_t pin, const uint16_t value, const uint16_t scale, const bool invert) { + // Invert value if requested + const uint16_t val = invert ? scale - value : value; + + // AnalogWrite the value, core handles the rest + // Pin mode should be set by Marlin by calling SET_PWM() before calling this function + analogWriteScaled(pin, val, scale); +} + +void MarlinHAL::set_pwm_frequency(const pin_t pin, const uint16_t f_desired) { + // TODO set_pwm_frequency is not implemented yet + panic("set_pwm_frequency is not implemented yet\n"); +} + +void flashFirmware(const int16_t) { MarlinHAL::reboot(); } + +#endif // ARDUINO_ARCH_HC32 diff --git a/Marlin/src/HAL/HC32/MarlinHAL.h b/Marlin/src/HAL/HC32/MarlinHAL.h new file mode 100644 index 0000000000..ba3ac4c731 --- /dev/null +++ b/Marlin/src/HAL/HC32/MarlinHAL.h @@ -0,0 +1,133 @@ +/** + * 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 + * (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 + +typedef gpio_pin_t pin_t; + +#if TEMP_SENSOR_SOC + /** + * Convert ots measurement float to uint16_t for adc_value() + * + * @note returns float as integer in degrees C * 10, if T > 0 + */ + #define OTS_FLOAT_TO_ADC_READING(T) ((T) > 0 ? ((uint16_t)((T) * 10.0f)) : 0) + + /** + * Convert adc_value() uint16_t to ots measurement float + * + * @note see OTS_FLOAT_TO_ADC_READING for inverse + * + * @note RAW is oversampled by OVERSAMPLENR, so we need to divide first + */ + #define TEMP_SOC_SENSOR(RAW) ((float)(((RAW) / OVERSAMPLENR) / 10)) +#endif + +/** + * HAL class for Marlin on HC32F460 + */ +class MarlinHAL { +public: + // Earliest possible init, before setup() + MarlinHAL(); + + // Watchdog + static void watchdog_init(); + static void watchdog_refresh(); + + 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(); + static void isr_on(); + static void isr_off(); + + static void delay_ms(const int ms); + + // Tasks, called from marlin.idle() + static void idletask(); + + // Reset + static uint8_t get_reset_source(); + static void clear_reset_source(); + + // Free SRAM + static int 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 bool adc_ready(); + + // 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 maximum size of the provided value to enable finer PWM duty control [default = 255] + * The timer must be pre-configured with set_pwm_frequency() if the default frequency is not desired. + */ + static void set_pwm_duty(const pin_t pin, const uint16_t value, const uint16_t scale = 255, 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); + +private: + /** + * Pin number of the last pin that was used with adc_start() + */ + static pin_t last_adc_pin; + + #if TEMP_SENSOR_SOC + /** + * On-chip temperature sensor value + */ + static float soc_temp; + #endif +}; + +// M997: Trigger a firmware update from SD card (after upload). +// On HC32F460, a reboot is enough to do this. +#ifndef PLATFORM_M997_SUPPORT + #define PLATFORM_M997_SUPPORT +#endif + +void flashFirmware(const int16_t); diff --git a/Marlin/src/HAL/HC32/MarlinSerial.cpp b/Marlin/src/HAL/HC32/MarlinSerial.cpp new file mode 100644 index 0000000000..11d4abfab9 --- /dev/null +++ b/Marlin/src/HAL/HC32/MarlinSerial.cpp @@ -0,0 +1,162 @@ +/** + * 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 + * (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_HC32 + +#include "../../inc/MarlinConfig.h" +#include "MarlinSerial.h" +#include + +/** + * Not every MarlinSerial instance should handle emergency parsing, as + * it would not make sense to parse G-Code from TMC responses + */ +constexpr bool serial_handles_emergency(int port) { + return false + #ifdef SERIAL_PORT + || (SERIAL_PORT) == port + #endif + #ifdef SERIAL_PORT_2 + || (SERIAL_PORT_2) == port + #endif + #ifdef LCD_SERIAL_PORT + || (LCD_SERIAL_PORT) == port + #endif + ; +} + +// +// Define serial ports +// + +// 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); + +// 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)) + +DEFINE_SERIAL_MARLIN(MSerial1, 1); +DEFINE_SERIAL_MARLIN(MSerial2, 2); + +// TODO: remove this warning when SERIAL_DMA has been tested some more +#if ENABLED(SERIAL_DMA) + #warning "SERIAL_DMA may be unstable on HC32F460." +#endif + +// +// Serial port assertions +// + +// Check the type of each serial port by passing it to a template function. +// HardwareSerial is known to sometimes hang the controller when an error occurs, +// so this case will fail the static assert. All other classes are assumed to be ok. +template +constexpr bool IsSerialClassAllowed(const T &) { return true; } +constexpr bool IsSerialClassAllowed(const HardwareSerial &) { return false; } +constexpr bool IsSerialClassAllowed(const Usart &) { return false; } + +// If you encounter this error, replace SerialX with MSerialX, for example MSerial3. +#define CHECK_CFG_SERIAL(A) static_assert(IsSerialClassAllowed(A), STRINGIFY(A) " is defined incorrectly"); +#define CHECK_AXIS_SERIAL(A) static_assert(IsSerialClassAllowed(A##_HARDWARE_SERIAL), STRINGIFY(A) "_HARDWARE_SERIAL must be defined in the form MSerial1, rather than Serial1"); + +// Non-TMC ports were already validated in HAL.h, so do not require verbose error messages. +#ifdef MYSERIAL1 + CHECK_CFG_SERIAL(MYSERIAL1); +#endif +#ifdef MYSERIAL2 + CHECK_CFG_SERIAL(MYSERIAL2); +#endif +#ifdef LCD_SERIAL + CHECK_CFG_SERIAL(LCD_SERIAL); +#endif +#if AXIS_HAS_HW_SERIAL(X) + CHECK_AXIS_SERIAL(X); +#endif +#if AXIS_HAS_HW_SERIAL(X2) + CHECK_AXIS_SERIAL(X2); +#endif +#if AXIS_HAS_HW_SERIAL(Y) + CHECK_AXIS_SERIAL(Y); +#endif +#if AXIS_HAS_HW_SERIAL(Y2) + CHECK_AXIS_SERIAL(Y2); +#endif +#if AXIS_HAS_HW_SERIAL(Z) + CHECK_AXIS_SERIAL(Z); +#endif +#if AXIS_HAS_HW_SERIAL(Z2) + CHECK_AXIS_SERIAL(Z2); +#endif +#if AXIS_HAS_HW_SERIAL(Z3) + CHECK_AXIS_SERIAL(Z3); +#endif +#if AXIS_HAS_HW_SERIAL(Z4) + CHECK_AXIS_SERIAL(Z4); +#endif +#if AXIS_HAS_HW_SERIAL(I) + CHECK_AXIS_SERIAL(I); +#endif +#if AXIS_HAS_HW_SERIAL(J) + CHECK_AXIS_SERIAL(J); +#endif +#if AXIS_HAS_HW_SERIAL(K) + CHECK_AXIS_SERIAL(K); +#endif +#if AXIS_HAS_HW_SERIAL(E0) + CHECK_AXIS_SERIAL(E0); +#endif +#if AXIS_HAS_HW_SERIAL(E1) + CHECK_AXIS_SERIAL(E1); +#endif +#if AXIS_HAS_HW_SERIAL(E2) + CHECK_AXIS_SERIAL(E2); +#endif +#if AXIS_HAS_HW_SERIAL(E3) + CHECK_AXIS_SERIAL(E3); +#endif +#if AXIS_HAS_HW_SERIAL(E4) + CHECK_AXIS_SERIAL(E4); +#endif +#if AXIS_HAS_HW_SERIAL(E5) + CHECK_AXIS_SERIAL(E5); +#endif +#if AXIS_HAS_HW_SERIAL(E6) + CHECK_AXIS_SERIAL(E6); +#endif +#if AXIS_HAS_HW_SERIAL(E7) + CHECK_AXIS_SERIAL(E7); +#endif + +#endif // ARDUINO_ARCH_HC32 diff --git a/Marlin/src/HAL/HC32/MarlinSerial.h b/Marlin/src/HAL/HC32/MarlinSerial.h new file mode 100644 index 0000000000..1a97805a51 --- /dev/null +++ b/Marlin/src/HAL/HC32/MarlinSerial.h @@ -0,0 +1,94 @@ +/** + * 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 + * (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 "../../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_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 + #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 + } + + #if defined(UART_RX_IRQ_PRIO) || defined(UART_TX_IRQ_PRIO) || defined(UART_RX_DMA_IRQ_PRIO) + void setPriority() { + #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) { + Usart::begin(baud); + setPriority(); + } + + void begin(uint32_t baud, uint8_t config) { + Usart::begin(baud, config); + setPriority(); + } + + 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; diff --git a/Marlin/src/HAL/HC32/MinSerial.cpp b/Marlin/src/HAL/HC32/MinSerial.cpp new file mode 100644 index 0000000000..93017ee0df --- /dev/null +++ b/Marlin/src/HAL/HC32/MinSerial.cpp @@ -0,0 +1,151 @@ +/** + * 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 + * (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_HC32 + +#include "../../inc/MarlinConfig.h" +#include + +#if ANY(POSTMORTEM_DEBUGGING, PANIC_ENABLE) + +#include + +// +// Shared by both panic and PostMortem debugging +// +static void minserial_begin() { + #if !WITHIN(SERIAL_PORT, 1, 3) + #warning "MinSerial requires a physical UART port for output." + #warning "Disabling MinSerial because the used serial port is not a HW port." + #else + + // Prepare usart_sync configuration + const stc_usart_uart_init_t usart_config = { + .enClkMode = UsartIntClkCkNoOutput, + .enClkDiv = UsartClkDiv_1, + .enDataLength = UsartDataBits8, + .enDirection = UsartDataLsbFirst, + .enStopBit = UsartOneStopBit, + .enParity = UsartParityNone, + .enSampleMode = UsartSampleBit8, + .enDetectMode = UsartStartBitFallEdge, + .enHwFlow = UsartRtsEnable, + }; + + // Initializes usart_sync driver + #define __USART_SYNC_INIT(port_no, baud, config) \ + usart_sync_init(M4_USART##port_no, \ + BOARD_USART##port_no##_TX_PIN, \ + baud, \ + config); + #define USART_SYNC_INIT(port_no, baud, config) __USART_SYNC_INIT(port_no, baud, config) + + // This will reset the baudrate to what is defined in Configuration.h, + // ignoring any changes made with e.g. M575. + // keeping the dynamic baudrate would require re-calculating the baudrate + // using the register values, which is a pain... + + // TODO: retain dynamic baudrate in MinSerial init + // -> see USART_SetBaudrate(), needs to be inverted + USART_SYNC_INIT(SERIAL_PORT, BAUDRATE, &usart_config); + + #undef USART_SYNC_INIT + #undef __USART_SYNC_INIT + #endif +} + +static void minserial_putc(char c) { + #if WITHIN(SERIAL_PORT, 1, 3) + #define __USART_SYNC_PUTC(port_no, ch) usart_sync_putc(M4_USART##port_no, ch); + #define USART_SYNC_PUTC(port_no, ch) __USART_SYNC_PUTC(port_no, ch) + + USART_SYNC_PUTC(SERIAL_PORT, c); + + #undef USART_SYNC_PUTC + #undef __USART_SYNC_PUTC + #endif +} + +// +// Panic only +// +#ifdef PANIC_ENABLE + +void panic_begin() { + minserial_begin(); + panic_puts("\n\nPANIC:\n"); +} + +void panic_puts(const char *str) { + while (*str) minserial_putc(*str++); +} + +#endif // PANIC_ENABLE + +// +// PostMortem debugging only +// +#if ENABLED(POSTMORTEM_DEBUGGING) + +#include "../shared/MinSerial.h" +#include + +void fault_handlers_init() { + // Enable cpu traps: + // - Divide by zero + // - Unaligned access + SCB->CCR |= SCB_CCR_DIV_0_TRP_Msk; //| SCB_CCR_UNALIGN_TRP_Msk; +} + +void install_min_serial() { + HAL_min_serial_init = &minserial_begin; + HAL_min_serial_out = &minserial_putc; +} + +extern "C" { + __attribute__((naked)) 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 // POSTMORTEM_DEBUGGING || PANIC_ENABLE + +// +// Panic_end is always required to print the '!!' to the host +// +void panic_end() { + // Print '!!' to signal error to host + // Do it 10x so it's not missed + for (uint_fast8_t i = 10; i--;) panic_printf("\n!!\n"); + + // Then, reset the board + NVIC_SystemReset(); +} + +#endif // ARDUINO_ARCH_HC32 diff --git a/Marlin/src/HAL/HC32/README.md b/Marlin/src/HAL/HC32/README.md new file mode 100644 index 0000000000..fe265dcf24 --- /dev/null +++ b/Marlin/src/HAL/HC32/README.md @@ -0,0 +1,107 @@ +# HC32F460 HAL + +This document provides notes on the HAL for the HC32F460 MCU. + +## Adding support for a new board + +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](//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_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 + +If the board contains a bootloader you'll need to find the application address. This is the address the bootloader jumps to after it's done. You can find this address in a few different ways: + +#### 1. Using log messages + +If you're lucky, the bootloader may print the app start address on the serial output during boot. To check for this, use your favorite serial monitor to observe the serial output when you power on the board. Look for a message like "Jumping to 0xC000" or "GotoApp->addr=0xC000". This line would be printed before Marlin's "start" message. + +Example: + +``` +[...] +version 1.2 +sdio init success! +Disk init +Tips ------ None Firmware file +GotoApp->addr=0xC000 + +start +[...] +``` + +#### 2. Using published source code + +If the vendor has published Marlin source code that includes the bootloader, you can search the bootloader source code for the address. Begin your search with the following steps: + +1. Find the code that sets the vector table offset + +The vector table offset is usually set using a line like this: + +```c +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](//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 + +If the repository contains a linker script, look at the memory regions, specifically a region named `FLASH` or similar. The `ORIGIN` of that region will be the application start address. + +**Example:** + +```ld +MEMORY +{ + FLASH (rx): ORIGIN = 0x0000C000, LENGTH = 512K + OTP (rx): ORIGIN = 0x03000C00, LENGTH = 1020 + RAM (rwx): ORIGIN = 0x1FFF8000, LENGTH = 188K + RET_RAM (rwx): ORIGIN = 0x200F0000, LENGTH = 4K +} +``` + +> [!NOTE] 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 + +Due to uncertain licensing (w/r/t STMicro), documentation for the HC32F460 is only available upon request. Documentation includes the following: + +- Datasheet, user manual, reference manual +- Application notes for the DDL +- DDL source code +- IDE support packages (Keil, IAR, ...) including .svd files +- Programming software +- Emulator / debugger drivers +- Development board documentation and schematics +- Errata documents +- (Limited) sales information +- Full Voxelab firmware source code +- Documents in Chinese or English (machine translated) + +Contact me on Discord (@shadow578) if you need it. + +## Dependencies + +This HAL depends on the following projects: + +- [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](//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.cpp b/Marlin/src/HAL/HC32/Servo.cpp new file mode 100644 index 0000000000..ce78572ef5 --- /dev/null +++ b/Marlin/src/HAL/HC32/Servo.cpp @@ -0,0 +1,84 @@ +/** + * 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 + * (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_HC32 + +#include "../../inc/MarlinConfig.h" + +#if HAS_SERVOS + +#include "Servo.h" + +static uint8_t servoCount = 0; +static MarlinServo *servos[NUM_SERVOS] = {0}; + +constexpr uint32_t servoDelays[] = SERVO_DELAY; +static_assert(COUNT(servoDelays) == NUM_SERVOS, "SERVO_DELAY must be an array NUM_SERVOS long."); + +// +// MarlinServo impl +// +MarlinServo::MarlinServo() { + this->channel = servoCount++; + servos[this->channel] = this; +} + +int8_t MarlinServo::attach(const pin_t apin) { + // Use last pin if pin not given + if (apin >= 0) this->pin = apin; + + // If attached, do nothing but no fail + if (this->servo.attached()) return 0; + + // Attach + const uint8_t rc = this->servo.attach(this->pin); + return rc == INVALID_SERVO ? -1 : rc; +} + +void MarlinServo::detach() { + this->servo.detach(); +} + +bool MarlinServo::attached() { + return this->servo.attached(); +} + +void MarlinServo::write(servo_angle_t angle) { + this->angle = angle; + this->servo.write(angle); +} + +void MarlinServo::move(servo_angle_t angle) { + // Attach with pin=-1 to use last pin attach() was called with + if (attach(-1) < 0) return; // Attach failed + + write(angle); + safe_delay(servoDelays[this->channel]); + TERN_(DEACTIVATE_SERVOS_AFTER_MOVE, detach()); +} + +servo_angle_t MarlinServo::read() { + return TERN(OPTIMISTIC_SERVO_READ, this->angle, this->servo.read()); +} + +#endif // HAS_SERVOS +#endif // ARDUINO_ARCH_HC32 diff --git a/Marlin/src/HAL/HC32/Servo.h b/Marlin/src/HAL/HC32/Servo.h new file mode 100644 index 0000000000..0a9715494c --- /dev/null +++ b/Marlin/src/HAL/HC32/Servo.h @@ -0,0 +1,97 @@ +/** + * 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 + * (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" + +#include + +/** + * return last written value in servo.read instead of calculated value + */ +#define OPTIMISTIC_SERVO_READ + +/** + * @brief servo lib wrapper for marlin + */ +class MarlinServo { +public: + 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 + */ + int8_t attach(const pin_t apin); + + /** + * @brief detach servo + */ + void detach(); + + /** + * @brief is servo attached? + */ + bool attached(); + + /** + * @brief set servo angle + * @param angle new angle + */ + void write(servo_angle_t angle); + + /** + * @brief attach servo, move to angle, delay then detach + * @param angle angle to move to + */ + void move(servo_angle_t angle); + + /** + * @brief read current angle + * @return current angle betwwne 0 and 180 degrees + */ + servo_angle_t read(); + +private: + /** + * @brief internal servo object, provided by arduino core + */ + Servo servo; + + /** + * @brief virtual servo channel + */ + uint8_t channel; + + /** + * @brief pin the servo attached to last + */ + pin_t pin; + + /** + * @brief last known servo angle + */ + servo_angle_t angle; +}; + +// Alias for marlin HAL +typedef MarlinServo hal_servo_t; diff --git a/Marlin/src/HAL/HC32/app_config.h b/Marlin/src/HAL/HC32/app_config.h new file mode 100644 index 0000000000..b971903bba --- /dev/null +++ b/Marlin/src/HAL/HC32/app_config.h @@ -0,0 +1,72 @@ +/** + * 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 + +// +// 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 0000000000..a8943c819e 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/eeprom_bl24cxx.cpp b/Marlin/src/HAL/HC32/eeprom/eeprom_bl24cxx.cpp new file mode 100644 index 0000000000..a53a7c4062 --- /dev/null +++ b/Marlin/src/HAL/HC32/eeprom/eeprom_bl24cxx.cpp @@ -0,0 +1,86 @@ +/** + * 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 + * (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. + */ +#ifdef ARDUINO_ARCH_HC32 + +#include "../../../inc/MarlinConfig.h" + +#if ENABLED(IIC_BL24CXX_EEPROM) + +#include "../../shared/eeprom_api.h" +#include "../../shared/eeprom_if.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) { + 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); + 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_HC32 diff --git a/Marlin/src/HAL/HC32/eeprom/eeprom_if_iic.cpp b/Marlin/src/HAL/HC32/eeprom/eeprom_if_iic.cpp new file mode 100644 index 0000000000..0a161f23f2 --- /dev/null +++ b/Marlin/src/HAL/HC32/eeprom/eeprom_if_iic.cpp @@ -0,0 +1,51 @@ +/** + * 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 + * (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. + */ +#ifdef ARDUINO_ARCH_HC32 + +#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, unsigned char 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_HC32 diff --git a/Marlin/src/HAL/HC32/eeprom/eeprom_sdcard.cpp b/Marlin/src/HAL/HC32/eeprom/eeprom_sdcard.cpp new file mode 100644 index 0000000000..759ea5722d --- /dev/null +++ b/Marlin/src/HAL/HC32/eeprom/eeprom_sdcard.cpp @@ -0,0 +1,95 @@ +/** + * 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 + * (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 . + * + */ + +/** + * Implementation of EEPROM settings in SD Card + */ +#ifdef ARDUINO_ARCH_HC32 + +#include "../../../inc/MarlinConfig.h" + +#if ENABLED(SDCARD_EEPROM_EMULATION) + +#include "../../shared/eeprom_api.h" +#include "../../../sd/cardreader.h" + +#define EEPROM_FILENAME "eeprom.dat" + +#ifndef MARLIN_EEPROM_SIZE + #define MARLIN_EEPROM_SIZE 0x1000 // 4KB +#endif + +size_t PersistentStore::capacity() { return MARLIN_EEPROM_SIZE - eeprom_exclude_size; } + +#define _ALIGN(x) __attribute__((aligned(x))) +static char _ALIGN(4) HAL_eeprom_data[MARLIN_EEPROM_SIZE]; + +bool PersistentStore::access_start() { + if (!card.isMounted()) return false; + + MediaFile file, root = card.getroot(); + if (!file.open(&root, EEPROM_FILENAME, O_RDONLY)) + return true; // False aborts the save + + int bytes_read = file.read(HAL_eeprom_data, MARLIN_EEPROM_SIZE); + if (bytes_read < 0) return false; + + for (; bytes_read < long(MARLIN_EEPROM_SIZE); bytes_read++) + HAL_eeprom_data[bytes_read] = 0xFF; + + file.close(); + return true; +} + +bool PersistentStore::access_finish() { + if (!card.isMounted()) return false; + + MediaFile file, root = card.getroot(); + int bytes_written = 0; + if (file.open(&root, EEPROM_FILENAME, O_CREAT | O_WRITE | O_TRUNC)) { + bytes_written = file.write(HAL_eeprom_data, MARLIN_EEPROM_SIZE); + file.close(); + } + + return (bytes_written == MARLIN_EEPROM_SIZE); +} + +bool PersistentStore::write_data(int &pos, const uint8_t *value, size_t size, uint16_t *crc) { + for (size_t i = 0; i < size; i++) HAL_eeprom_data[pos + i] = value[i]; + + crc16(crc, value, size); + pos += size; + return false; +} + +bool PersistentStore::read_data(int &pos, uint8_t *value, const size_t size, uint16_t *crc, const bool writing /*=true*/) { + for (size_t i = 0; i < size; i++) { + const uint8_t c = HAL_eeprom_data[pos + i]; + if (writing) value[i] = c; + crc16(crc, &c, 1); + } + pos += size; + return false; +} + +#endif // SDCARD_EEPROM_EMULATION +#endif // ARDUINO_ARCH_HC32 diff --git a/Marlin/src/HAL/HC32/eeprom/eeprom_wired.cpp b/Marlin/src/HAL/HC32/eeprom/eeprom_wired.cpp new file mode 100644 index 0000000000..997180bd98 --- /dev/null +++ b/Marlin/src/HAL/HC32/eeprom/eeprom_wired.cpp @@ -0,0 +1,95 @@ +/** + * 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 + * (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_HC32 + +#include "../../../inc/MarlinConfig.h" + +#if USE_WIRED_EEPROM + +#warning "SPI / I2C EEPROM has not been tested on HC32F460." + +/** + * 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 + #error "MARLIN_EEPROM_SIZE is required for I2C / SPI EEPROM." +#endif +size_t PersistentStore::capacity() { return MARLIN_EEPROM_SIZE - eeprom_exclude_size; } + +bool PersistentStore::access_finish() { return true; } + +bool PersistentStore::access_start() { + eeprom_init(); + #if ENABLED(SPI_EEPROM) + #if SPI_CHAN_EEPROM1 == 1 + SET_OUTPUT(BOARD_SPI1_SCK_PIN); + SET_OUTPUT(BOARD_SPI1_MOSI_PIN); + SET_INPUT(BOARD_SPI1_MISO_PIN); + SET_OUTPUT(SPI_EEPROM1_CS); + #endif + + spiInit(0); + #endif + return true; +} + +bool PersistentStore::write_data(int &pos, const uint8_t *value, size_t size, uint16_t *crc) { + while (size--) { + uint8_t * const p = (uint8_t * const)REAL_EEPROM_ADDR(pos); + uint8_t v = *value; + // 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); + 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) *value = c; + + crc16(crc, &c, 1); + pos++; + value++; + } while (--size); + + return false; +} + +#endif // USE_WIRED_EEPROM +#endif // ARDUINO_ARCH_HC32 diff --git a/Marlin/src/HAL/HC32/endstop_interrupts.cpp b/Marlin/src/HAL/HC32/endstop_interrupts.cpp new file mode 100644 index 0000000000..06c4d0e186 --- /dev/null +++ b/Marlin/src/HAL/HC32/endstop_interrupts.cpp @@ -0,0 +1,118 @@ +/** + * 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 + * (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_HC32 + +#include "endstop_interrupts.h" +#include "../../module/endstops.h" +#include + +#define ENDSTOP_IRQ_PRIORITY DDL_IRQ_PRIORITY_06 + +// +// IRQ handler +// +void endstopIRQHandler() { + bool flag = false; + + // Check all irq flags + #define CHECK(name) TERN_(USE_##name, flag |= checkIRQFlag(name##_PIN, /*clear*/ true)) + + CHECK(X_MAX); + CHECK(X_MIN); + + CHECK(X2_MAX); + CHECK(X2_MIN); + + CHECK(Y_MAX); + CHECK(Y_MIN); + + CHECK(Y2_MAX); + CHECK(Y2_MIN); + + CHECK(Z_MAX); + CHECK(Z_MIN); + + CHECK(Z2_MAX); + CHECK(Z2_MIN); + + CHECK(Z3_MAX); + CHECK(Z3_MIN); + + CHECK(Z4_MAX); + CHECK(Z4_MIN); + + CHECK(Z_MIN_PROBE); + + // Update endstops + if (flag) endstops.update(); + + #undef CHECK +} + +// +// HAL functions +// +void setup_endstop_interrupts() { + #define SETUP(name) TERN_(USE_##name, attachInterrupt(name##_PIN, endstopIRQHandler, CHANGE); setInterruptPriority(name##_PIN, ENDSTOP_IRQ_PRIORITY)) + + SETUP(X_MAX); + SETUP(X_MIN); + + SETUP(X2_MAX); + SETUP(X2_MIN); + + SETUP(Y_MAX); + SETUP(Y_MIN); + + SETUP(Y2_MAX); + SETUP(Y2_MIN); + + SETUP(Z_MAX); + SETUP(Z_MIN); + + SETUP(Z2_MAX); + SETUP(Z2_MIN); + + SETUP(Z3_MAX); + SETUP(Z3_MIN); + + SETUP(Z4_MAX); + SETUP(Z4_MIN); + + 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, USE_CALIBRATION) +#if ENDSTOPS_INTERRUPTS_COUNT > 10 + #error "Too many endstop interrupts! HC32F460 only supports 10 endstop interrupts." +#elif ENDSTOPS_INTERRUPTS_COUNT == 0 + #error "No endstop interrupts are enabled! Comment out this line to continue." +#endif + +#endif // ARDUINO_ARCH_HC32 diff --git a/Marlin/src/HAL/HC32/endstop_interrupts.h b/Marlin/src/HAL/HC32/endstop_interrupts.h new file mode 100644 index 0000000000..124f6f1a1b --- /dev/null +++ b/Marlin/src/HAL/HC32/endstop_interrupts.h @@ -0,0 +1,48 @@ +/** + * 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 + * (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 + +/** + * Endstop interrupts for HC32F460 based targets. + * + * On HC32F460, all pins support external interrupt capability, with some restrictions. + * See the documentation of WInterrupts#attachInterrupt() for details. + * + * TL;DR + * any 16 pins can be used, but only one pin per EXTI line (so PA0 and PB0 are no-good). + */ + +/** + * Endstop Interrupts + * + * Without endstop interrupts the endstop pins must be polled continually in + * the temperature-ISR via endstops.update(), most of the time finding no change. + * With this feature endstops.update() is called only when we know that at + * least one endstop has changed state, saving valuable CPU cycles. + * + * This feature only works when all used endstop pins can generate an 'external interrupt'. + * + * Test whether pins issue interrupts on your board by flashing 'pin_interrupt_test.ino'. + * (Located in Marlin/buildroot/share/pin_interrupt_test/pin_interrupt_test.ino) + */ + +void setup_endstop_interrupts(); diff --git a/Marlin/src/HAL/HC32/fastio.h b/Marlin/src/HAL/HC32/fastio.h new file mode 100644 index 0000000000..af46866172 --- /dev/null +++ b/Marlin/src/HAL/HC32/fastio.h @@ -0,0 +1,69 @@ +/** + * 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 + * (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 HC32F460 + * These use GPIO functions instead of Direct Port Manipulation. + */ +#include +#include +#include + +#define READ(IO) (GPIO_GetBit(IO) ? HIGH : LOW) +#define WRITE(IO, V) (((V) > 0) ? GPIO_SetBits(IO) : GPIO_ResetBits(IO)) +#define TOGGLE(IO) (GPIO_Toggle(IO)) + +#define _GET_MODE(IO) getPinMode(IO) +#define _SET_MODE(IO, M) pinMode(IO, M) +#define _SET_OUTPUT(IO) _SET_MODE(IO, OUTPUT) + +#define OUT_WRITE(IO, V) \ + do { \ + _SET_OUTPUT(IO); \ + WRITE(IO, V); \ + } while (0) + +#define SET_INPUT(IO) _SET_MODE(IO, INPUT_FLOATING) +#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_PWM(IO) _SET_MODE(IO, OUTPUT_PWM) + +#define IS_INPUT(IO) ( \ + _GET_MODE(IO) == INPUT || \ + _GET_MODE(IO) == INPUT_FLOATING || \ + _GET_MODE(IO) == INPUT_ANALOG || \ + _GET_MODE(IO) == INPUT_PULLUP || \ + _GET_MODE(IO) == INPUT_PULLDOWN) + +#define IS_OUTPUT(IO) ( \ + _GET_MODE(IO) == OUTPUT || \ + _GET_MODE(IO) == OUTPUT_PWM || \ + _GET_MODE(IO) == OUTPUT_OPEN_DRAIN) + +#define PWM_PIN(IO) isAnalogWritePin(IO) + +#define extDigitalRead(IO) digitalRead(IO) +#define extDigitalWrite(IO, V) digitalWrite(IO, V) + +#define NO_COMPILE_TIME_PWM // Can't check for PWM at compile time diff --git a/Marlin/src/HAL/HC32/inc/Conditionals_LCD.h b/Marlin/src/HAL/HC32/inc/Conditionals_LCD.h new file mode 100644 index 0000000000..8266da4b2a --- /dev/null +++ b/Marlin/src/HAL/HC32/inc/Conditionals_LCD.h @@ -0,0 +1,26 @@ +/** + * 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 + * (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_HC32 1 +#endif diff --git a/Marlin/src/HAL/HC32/inc/Conditionals_adv.h b/Marlin/src/HAL/HC32/inc/Conditionals_adv.h new file mode 100644 index 0000000000..06f15e5105 --- /dev/null +++ b/Marlin/src/HAL/HC32/inc/Conditionals_adv.h @@ -0,0 +1,26 @@ +/** + * 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 + * (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 + +#ifndef TEMP_SOC_PIN + #define TEMP_SOC_PIN 0xFF // Dummy that is not a valid GPIO, HAL checks for this +#endif diff --git a/Marlin/src/HAL/HC32/inc/Conditionals_post.h b/Marlin/src/HAL/HC32/inc/Conditionals_post.h new file mode 100644 index 0000000000..bcffd5a8d5 --- /dev/null +++ b/Marlin/src/HAL/HC32/inc/Conditionals_post.h @@ -0,0 +1,34 @@ +/** + * 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 + * (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 EEPROM, Flash emulation, or SRAM emulation is available 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 + +// Allow SD support to be disabled +#if !HAS_MEDIA + #undef ONBOARD_SDIO +#endif diff --git a/Marlin/src/HAL/HC32/inc/Conditionals_type.h b/Marlin/src/HAL/HC32/inc/Conditionals_type.h new file mode 100644 index 0000000000..82f95a1035 --- /dev/null +++ b/Marlin/src/HAL/HC32/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/HC32/inc/SanityCheck.h b/Marlin/src/HAL/HC32/inc/SanityCheck.h new file mode 100644 index 0000000000..c6b3b221df --- /dev/null +++ b/Marlin/src/HAL/HC32/inc/SanityCheck.h @@ -0,0 +1,107 @@ +/** + * 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 + * (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 !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." +#endif + +#if ENABLED(FAST_PWM_FAN) + #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." +#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(SERIAL_STATS_MAX_RX_QUEUED) + #error "SERIAL_STATS_MAX_RX_QUEUED is not supported on this platform." +#elif ENABLED(SERIAL_STATS_DROPPED_RX) + #error "SERIAL_STATS_DROPPED_RX is not supported on this platform." +#endif + +#if ENABLED(NEOPIXEL_LED) && DISABLED(MKS_MINI_12864_V3) + #error "NEOPIXEL_LED (Adafruit NeoPixel) is not supported for HC32F460. Comment out this line to proceed at your own risk!" +#endif + +// Emergency Parser needs at least one serial with HardwareSerial. +#if ENABLED(EMERGENCY_PARSER) && ((SERIAL_PORT == -1 && !defined(SERIAL_PORT_2)) || (SERIAL_PORT_2 == -1 && !defined(SERIAL_PORT))) + #error "EMERGENCY_PARSER is only supported by HardwareSerial on HC32F460." +#endif + +#if TEMP_SENSOR_SOC + #ifndef TEMP_SOC_PIN + #error "TEMP_SOC_PIN must be defined to use TEMP_SENSOR_SOC." + #elif IS_GPIO_PIN(TEMP_SOC_PIN) + #error "TEMP_SOC_PIN must not be a valid GPIO pin to avoid conflicts." + #endif +#endif + +#if ENABLED(POSTMORTEM_DEBUGGING) && !defined(CORE_DISABLE_FAULT_HANDLER) + #error "POSTMORTEM_DEBUGGING requires CORE_DISABLE_FAULT_HANDLER to be set." +#endif + +#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 new file mode 100644 index 0000000000..9f8e23ce44 --- /dev/null +++ b/Marlin/src/HAL/HC32/pinsDebug.h @@ -0,0 +1,183 @@ +/** + * 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 + * (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 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 + +#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 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 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 a hang / reset / disconnect in M43 Toggle and Watch utils +// +#ifndef M43_NEVER_TOUCH + // Don't touch any of the following pins: + // - Host serial pins, and + // - Pins that could be connected to oscillators (see datasheet, Table 2.1): + // - XTAL = PH0, PH1 + // - XTAL32 = PC14, PC15 + #define IS_HOST_USART_PIN(Q) (Q == BOARD_USART2_TX_PIN || Q == BOARD_USART2_RX_PIN) + #define IS_OSC_PIN(Q) (Q == PH0 || Q == PH1 || Q == PC14 || Q == PC15) + + #define M43_NEVER_TOUCH(Q) (IS_HOST_USART_PIN(Q) || IS_OSC_PIN(Q)) +#endif + +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); +} + +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); + + return false; +} + +bool getValidPinMode(const pin_t pin) { + return isValidPin(pin) && !IS_INPUT(pin); +} + +bool getPinIsDigitalByIndex(const int16_t index) { + const pin_t pin = getPinByIndex(index); + return (!isAnalogPin(pin)); +} + +/** + * @brief print pin PWM status + * @return true if pin is currently a PWM pin, false otherwise + */ +bool pwm_status(const pin_t pin) { + // Get timer assignment for pin + timera_config_t *unit; + en_timera_channel_t channel; + en_port_func_t port_function; + if (!timera_get_assignment(pin, unit, channel, port_function) || unit == nullptr) { + // No pwm pin or no unit assigned + return false; + } + + // A pin that is PWM output is: + // - Assigned to a timerA unit (tested above) + // - Unit is initialized + // - Channel is active + // - PinMode is OUTPUT_PWM + return timera_is_unit_initialized(unit) && timera_is_channel_active(unit, channel) && getPinMode(pin) == OUTPUT_PWM; +} + +void printPinPWM(const pin_t pin) { + // Get timer assignment for pin + timera_config_t *unit; + en_timera_channel_t channel; + en_port_func_t port_function; + if (!timera_get_assignment(pin, unit, channel, port_function) || unit == nullptr) + return; // No pwm pin or no unit assigned + + // Print timer assignment of pin, eg. "TimerA1Ch2 Func4" + SERIAL_ECHOPGM("TimerA", TIMERA_REG_TO_X(unit->peripheral.register_base), + "Ch", TIMERA_CHANNEL_TO_X(channel), + " Func", int(port_function)); + SERIAL_ECHO_SP(3); // 3 spaces + + // Print timer unit state, eg. "1/16 PERAR=1234" OR "N/A" + if (timera_is_unit_initialized(unit)) { + // Unit initialized, print + // - Timer clock divider + // - Timer period value (PERAR) + const uint8_t clock_divider = timera_clk_div_to_n(unit->state.base_init->enClkDiv); + const uint16_t period = TIMERA_GetPeriodValue(unit->peripheral.register_base); + SERIAL_ECHOPGM("1/", clock_divider, " PERAR=", period); + } + else { + // Unit not initialized + SERIAL_ECHOPGM("N/A"); + return; + } + + SERIAL_ECHO_SP(3); // 3 spaces + + // Print timer channel state, e.g. "CMPAR=1234" OR "N/A" + if (timera_is_channel_active(unit, channel)) { + // Channel active, print + // - Channel compare value + const uint16_t compare = TIMERA_GetCompareValue(unit->peripheral.register_base, channel); + SERIAL_ECHOPGM("CMPAR=", compare); + } + else { + // Channel inactive + SERIAL_ECHOPGM("N/A"); + } +} + +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]; + sprintf_P(buffer, PSTR("P%c%hd "), port, gbit); + if (gbit < 10) { + SERIAL_CHAR(' '); + } + + SERIAL_ECHO(buffer); +} diff --git a/Marlin/src/HAL/HC32/printf_retarget.cpp b/Marlin/src/HAL/HC32/printf_retarget.cpp new file mode 100644 index 0000000000..8a48aca3d4 --- /dev/null +++ b/Marlin/src/HAL/HC32/printf_retarget.cpp @@ -0,0 +1,55 @@ +/** + * 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 + * (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_HC32 +#ifdef REDIRECT_PRINTF_TO_SERIAL + +#ifndef __GNUC__ + #error "only GCC is supported" +#endif + +#include "../../inc/MarlinConfig.h" + +/** + * @brief implementation of _write that redirects everything to the host serial(s) + * @param file file descriptor. don't care + * @param ptr pointer to the data to write + * @param len length of the data to write + * @return number of bytes written + */ +extern "C" int _write(int file, char *ptr, int len) { + //SERIAL_ECHO_START(); // echo: + for (int i = 0; i < len; i++) SERIAL_CHAR(ptr[i]); + return len; +} + +/** + * @brief implementation of _isatty that always returns 1 + * @param file file descriptor. don't care + * @return everything is a tty. there are no files to be had + */ +extern "C" int _isatty(int file) { + return 1; +} + +#endif // REDIRECT_PRINTF_TO_SERIAL +#endif // ARDUINO_ARCH_HC32 diff --git a/Marlin/src/HAL/HC32/sdio.cpp b/Marlin/src/HAL/HC32/sdio.cpp new file mode 100644 index 0000000000..3c4038f92d --- /dev/null +++ b/Marlin/src/HAL/HC32/sdio.cpp @@ -0,0 +1,146 @@ +/** + * 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 + * (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_HC32 + +#include "sdio.h" +#include +#include + +// +// SDIO configuration +// + +#define SDIO_PERIPHERAL M4_SDIOC1 + +// Use DMA2 channel 0 +#define SDIO_DMA_PERIPHERAL M4_DMA2 +#define SDIO_DMA_CHANNEL DmaCh0 + +// SDIO read/write operation retries and timeouts +#define SDIO_READ_RETRIES 3 +#define SDIO_READ_TIMEOUT 100 // ms + +#define SDIO_WRITE_RETRIES 1 +#define SDIO_WRITE_TIMEOUT 100 // ms + +// +// HAL functions +// + +#define WITH_RETRY(retries, fn) \ + for (int retry = 0; retry < (retries); retry++) { \ + MarlinHAL::watchdog_refresh(); \ + yield(); \ + fn \ + } + +stc_sd_handle_t *handle = nullptr; + +bool SDIO_Init() { + // Configure SDIO pins + GPIO_SetFunc(BOARD_SDIO_D0, Func_Sdio); + GPIO_SetFunc(BOARD_SDIO_D1, Func_Sdio); + GPIO_SetFunc(BOARD_SDIO_D2, Func_Sdio); + GPIO_SetFunc(BOARD_SDIO_D3, Func_Sdio); + GPIO_SetFunc(BOARD_SDIO_CLK, Func_Sdio); + 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; + //handle->pstcCardInitCfg = cardConf; // assigned in SDCARD_Init + + // Initialize sd card + 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 != 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); + }) + + return false; +} + +bool SDIO_WriteBlock(uint32_t block, const uint8_t *src) { + 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); + }) + + return false; +} + +bool SDIO_IsReady() { + CORE_ASSERT(handle != nullptr, "SDIO not initialized", return false); + return bool(handle->stcCardStatus.READY_FOR_DATA); +} + +uint32_t SDIO_GetCardSize() { + 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); + + // If the card is bigger than ~4Gb (maximum a 32bit integer can hold), clamp to the maximum value of a 32 bit integer + return _MAX(cardSizeBytes, UINT32_MAX); +} + +#endif // ARDUINO_ARCH_HC32 diff --git a/Marlin/src/HAL/STM32_F4_F7/timers.h b/Marlin/src/HAL/HC32/sdio.h similarity index 74% rename from Marlin/src/HAL/STM32_F4_F7/timers.h rename to Marlin/src/HAL/HC32/sdio.h index 4e8c81783e..89d4b061b1 100644 --- a/Marlin/src/HAL/STM32_F4_F7/timers.h +++ b/Marlin/src/HAL/HC32/sdio.h @@ -1,7 +1,6 @@ /** * Marlin 3D Printer Firmware - * - * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] + * Copyright (c) 2023 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com * Copyright (c) 2017 Victor Perez * @@ -20,9 +19,10 @@ * */ #pragma once +#include "../../inc/MarlinConfig.h" -#ifdef STM32F4 - #include "STM32F4/timers.h" -#else - #include "STM32F7/timers.h" -#endif +bool SDIO_Init(); +bool SDIO_ReadBlock(uint32_t block, uint8_t *dst); +bool SDIO_WriteBlock(uint32_t block, const uint8_t *src); +bool SDIO_IsReady(); +uint32_t SDIO_GetCardSize(); diff --git a/Marlin/src/HAL/HC32/spi_pins.h b/Marlin/src/HAL/HC32/spi_pins.h new file mode 100644 index 0000000000..5f1a94e920 --- /dev/null +++ b/Marlin/src/HAL/HC32/spi_pins.h @@ -0,0 +1,22 @@ +/** + * 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 + * (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/HC32/sysclock.cpp b/Marlin/src/HAL/HC32/sysclock.cpp new file mode 100644 index 0000000000..e98395c42e --- /dev/null +++ b/Marlin/src/HAL/HC32/sysclock.cpp @@ -0,0 +1,242 @@ +/** + * 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 + * (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 . + * + */ + +/** + * HC32f460 system clock configuration + */ + +#ifdef ARDUINO_ARCH_HC32 + +#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(); + + // 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(); + + #if BOARD_XTAL_FREQUENCY == 16000000 + #warning "HC32F460 with 16 MHz XTAL has not been tested." + #endif + + #else // HRC (16 MHz) + + constexpr uint32_t mpll_input_clock = 16000000; + + sysclock_configure_hrc(); + + // HRC could have been configured by ICG to 20 MHz + // TODO: handle gracefully if HRC is not 16 MHz + if (1UL != (HRC_FREQ_MON() & 1UL)) { + panic("HRC is not 16 MHz"); + } + + #ifdef BOARD_XTAL_FREQUENCY + #warning "No valid XTAL frequency defined, falling back to HRC." + #endif + + #endif + + // 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, before switch + power_mode_update_pre(F_SYSTEM_CLOCK); + + // Switch to MPLL-P as system clock source + CLK_SetSysClkSource(CLKSysSrcMPLL); + + // Set power mode, after switch + power_mode_update_post(F_SYSTEM_CLOCK); + + // Verify clocks match expected values (at runtime) + #if ENABLED(MARLIN_DEV_MODE) || ENABLED(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 0000000000..783d5677e9 --- /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 new file mode 100644 index 0000000000..b8eab34cf7 --- /dev/null +++ b/Marlin/src/HAL/HC32/timers.cpp @@ -0,0 +1,54 @@ +/** + * 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 + * (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_HC32 + +#include "timers.h" +#include + +/** + * Timer0 Unit 2 Channel A is used for Temperature interrupts + */ +Timer0 temp_timer(&TIMER02A_config, &Temp_Handler); + +/** + * Timer0 Unit 2 Channel B is used for Step interrupts + */ +Timer0 step_timer(&TIMER02B_config, &Step_Handler); + +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_ch->start(frequency, TEMP_TIMER_PRESCALE); + timer_ch->setCallbackPriority(TEMP_TIMER_PRIORITY); + } + else if (timer_ch == MF_TIMER_STEP) { + CORE_DEBUG_PRINTF("HAL_timer_start: step timer, f=%ld\n", long(frequency)); + timer_ch->start(frequency, STEPPER_TIMER_PRESCALE); + timer_ch->setCallbackPriority(STEP_TIMER_PRIORITY); + } + else { + CORE_ASSERT_FAIL("HAL_timer_start: invalid timer_ch") + } +} + +#endif // ARDUINO_ARCH_HC32 diff --git a/Marlin/src/HAL/HC32/timers.h b/Marlin/src/HAL/HC32/timers.h new file mode 100644 index 0000000000..8a4465e2d8 --- /dev/null +++ b/Marlin/src/HAL/HC32/timers.h @@ -0,0 +1,131 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2023 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] + * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com + * Copyright (c) 2017 Victor Perez + * + * 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 "sysclock.h" + +// +// Timer Types +// +typedef Timer0 *timer_channel_t; +typedef uint16_t hal_timer_t; +#define HAL_TIMER_TYPE_MAX hal_timer_t(UINT16_MAX) + +// +// Timer instances +// +extern Timer0 temp_timer; +extern Timer0 step_timer; + +// +// Timer Configurations +// + +/** + * 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 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 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 + +#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 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_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_ch) { + timer_ch->resume(); +} + +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_ch) { + return timer_ch->isPaused(); +} + +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_ch) { + return timer_ch->getCount(); +} + +MARLIN_HAL_TIMER_INLINE_ATTR void HAL_timer_isr_prologue(const timer_channel_t timer_ch) { + timer_ch->clearInterruptFlag(); +} + +inline void HAL_timer_isr_epilogue(const timer_channel_t) {} + +// +// HAL function aliases +// +#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); + +// +// HAL ISR callbacks +// +void Step_Handler(); +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 diff --git a/Marlin/src/HAL/HC32/u8g/LCD_defines.h b/Marlin/src/HAL/HC32/u8g/LCD_defines.h new file mode 100644 index 0000000000..cb5a6812ef --- /dev/null +++ b/Marlin/src/HAL/HC32/u8g/LCD_defines.h @@ -0,0 +1,29 @@ +/** + * 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 + * (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 + +/** + * HC32 LCD-specific defines + */ + +uint8_t u8g_com_HAL_HC32_sw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr); +#define U8G_COM_HAL_SW_SPI_FN u8g_com_HAL_HC32_sw_spi_fn 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 new file mode 100644 index 0000000000..97f4b85eaf --- /dev/null +++ b/Marlin/src/HAL/HC32/u8g/u8g_com_HAL_HC32_sw_spi.cpp @@ -0,0 +1,163 @@ +/** + * 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 + * (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_HC32 + +#include "../../../inc/MarlinConfig.h" + +#if U8G_SW_SPI_HC32 + +#warning "Software SPI for U8Glib is experimental on HC32F460. Please share your experiences at https://github.com/shadow578/Marlin-H32/issues/35" + +#include +#include "../../shared/HAL_SPI.h" + +#ifndef LCD_SPI_SPEED + #define LCD_SPI_SPEED SPI_FULL_SPEED // Fastest + //#define LCD_SPI_SPEED SPI_QUARTER_SPEED // Slower +#endif + +static uint8_t SPI_speed = LCD_SPI_SPEED; + +static inline uint8_t swSpiTransfer_mode_0(uint8_t b, const uint8_t spi_speed, const pin_t miso_pin=-1) { + for (i = 0; i < 8; ++i) { + if (spi_speed == 0) { + WRITE(DOGLCD_MOSI, !!(b & 0x80)); + WRITE(DOGLCD_SCK, HIGH); + b <<= 1; + if (miso_pin >= 0 && READ(miso_pin)) b |= 1; + WRITE(DOGLCD_SCK, LOW); + } + else { + const uint8_t state = (b & 0x80) ? HIGH : LOW; + for (j = 0; j < spi_speed; ++j) WRITE(DOGLCD_MOSI, state); + for (j = 0; j < spi_speed + (miso_pin >= 0 ? 0 : 1; ++j)) WRITE(DOGLCD_SCK, HIGH); + + b <<= 1; + if (miso_pin >= 0 && READ(miso_pin)) b |= 1; + + for (j = 0; j < spi_speed; ++j) WRITE(DOGLCD_SCK, LOW); + } + } + return b; +} + +static inline uint8_t swSpiTransfer_mode_3(uint8_t b, const uint8_t spi_speed, const pin_t miso_pin=-1) { + for (i = 0; i < 8; ++i) { + const uint8_t state = (b & 0x80) ? HIGH : LOW; + if (spi_speed == 0) { + WRITE(DOGLCD_SCK, LOW); + WRITE(DOGLCD_MOSI, state); + WRITE(DOGLCD_MOSI, state); // need some setup time + WRITE(DOGLCD_SCK, HIGH); + } + else { + for (j = 0; j < spi_speed + (miso_pin >= 0 ? 0 : 1); ++j) WRITE(DOGLCD_SCK, LOW); + for (j = 0; j < spi_speed; ++j) WRITE(DOGLCD_MOSI, state); + for (j = 0; j < spi_speed; ++j) WRITE(DOGLCD_SCK, HIGH); + } + b <<= 1; + if (miso_pin >= 0 && READ(miso_pin)) b |= 1; + } + return b; +} + +static void u8g_sw_spi_shift_out(uint8_t val) { + #if U8G_SPI_USE_MODE_3 + swSpiTransfer_mode_3(val, SPI_speed); + #else + swSpiTransfer_mode_0(val, SPI_speed); + #endif +} + +static uint8_t swSpiInit(const uint8_t spi_speed) { + #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); + return spi_speed; +} + +uint8_t u8g_com_HAL_HC32_sw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr) { + switch (msg) { + case U8G_COM_MSG_INIT: + SPI_speed = swSpiInit(LCD_SPI_SPEED); + 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); + } + 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 // U8G_SW_SPI_HC32 +#endif // ARDUINO_ARCH_HC32 diff --git a/Marlin/src/HAL/LINUX/HAL.cpp b/Marlin/src/HAL/LINUX/HAL.cpp index d7d7c2d2b4..8a9028c8cc 100644 --- a/Marlin/src/HAL/LINUX/HAL.cpp +++ b/Marlin/src/HAL/LINUX/HAL.cpp @@ -19,63 +19,88 @@ * along with this program. If not, see . * */ + #ifdef __PLAT_LINUX__ #include "../../inc/MarlinConfig.h" #include "../shared/Delay.h" -HalSerial usb_serial; +// ------------------------ +// Serial ports +// ------------------------ + +MSerialT usb_serial(TERN0(EMERGENCY_PARSER, true)); // U8glib required functions -extern "C" void u8g_xMicroDelay(uint16_t val) { - DELAY_US(val); -} -extern "C" void u8g_MicroDelay() { - u8g_xMicroDelay(1); -} -extern "C" void u8g_10MicroDelay() { - u8g_xMicroDelay(10); -} -extern "C" void u8g_Delay(uint16_t val) { - delay(val); +extern "C" { + void u8g_xMicroDelay(uint16_t val) { DELAY_US(val); } + void u8g_MicroDelay() { u8g_xMicroDelay(1); } + void u8g_10MicroDelay() { u8g_xMicroDelay(10); } + void u8g_Delay(uint16_t val) { delay(val); } } + //************************// // return free heap space -int freeMemory() { - return 0; -} +int freeMemory() { return 0; } // ------------------------ // ADC // ------------------------ -void HAL_adc_init() { +uint8_t MarlinHAL::active_ch = 0; +uint16_t MarlinHAL::adc_value() { + const pin_t pin = analogInputToDigitalPin(active_ch); + if (!isValidPin(pin)) return 0; + return uint16_t((Gpio::get(pin) >> 2) & 0x3FF); // return 10bit value as Marlin expects } -void HAL_adc_enable_channel(const uint8_t ch) { +void MarlinHAL::reboot() { /* Reset the application state and GPIO */ } -} +// ------------------------ +// BSD String +// ------------------------ -uint8_t active_ch = 0; -void HAL_adc_start_conversion(const uint8_t ch) { - active_ch = ch; -} +/** + * Copyright (c) 1998, 2015 Todd C. Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ -bool HAL_adc_finished() { - return true; -} +#ifndef HAS_LIBBSD -uint16_t HAL_adc_get_result() { - pin_t pin = analogInputToDigitalPin(active_ch); - if (!VALID_PIN(pin)) return 0; - uint16_t data = ((Gpio::get(pin) >> 2) & 0x3FF); - return data; // return 10bit value as Marlin expects -} + /** + * Copy string src to buffer dst of size dsize. At most dsize-1 + * chars will be copied. Always NUL terminates (unless dsize == 0). + * Returns strlen(src); if retval >= dsize, truncation occurred. + */ + size_t MarlinHAL::_strlcpy(char *dst, const char *src, size_t dsize) { + const char *osrc = src; + size_t nleft = dsize; -void HAL_pwm_init() { + // Copy as many bytes as will fit. + if (nleft != 0) while (--nleft != 0) if ((*dst++ = *src++) == '\0') break; -} + // Not enough room in dst, add NUL and traverse rest of src. + if (nleft == 0) { + if (dsize != 0) *dst = '\0'; // NUL-terminate dst + while (*src++) { /* nada */ } + } + + return (src - osrc - 1); // count does not include NUL + } + +#endif // HAS_LIBBSD #endif // __PLAT_LINUX__ diff --git a/Marlin/src/HAL/LINUX/HAL.h b/Marlin/src/HAL/LINUX/HAL.h index 2e545e03d6..66e4036fdc 100644 --- a/Marlin/src/HAL/LINUX/HAL.h +++ b/Marlin/src/HAL/LINUX/HAL.h @@ -1,9 +1,9 @@ /** * Marlin 3D Printer Firmware - * * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com - * Copyright (c) 2015-2016 Nico Tonnhofer wurstnase.reprap@gmail.com + * + * 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 @@ -21,25 +21,47 @@ */ #pragma once -#define CPU_32_BIT +#include "../../inc/MarlinConfigPre.h" -#define F_CPU 100000000 -#define SystemCoreClock F_CPU #include #include #include +#ifdef HAS_LIBBSD + #include +#endif + #undef min #undef max - #include -void _printf (const char *format, ...); +#include "hardware/Clock.h" +#include "../shared/Marduino.h" +#include "../shared/math_32bit.h" +#include "../shared/HAL_SPI.h" +#include "fastio.h" +#include "serial.h" + +// ------------------------ +// Defines +// ------------------------ + +#define CPU_32_BIT +#define SHARED_SERVOS HAS_SERVOS // Use shared/servos.cpp + +#define F_CPU 100000000UL +#define SystemCoreClock F_CPU + +#define DELAY_CYCLES(x) Clock::delayCycles(x) + +#define CPU_ST7920_DELAY_1 600 +#define CPU_ST7920_DELAY_2 750 +#define CPU_ST7920_DELAY_3 750 + +void _printf(const char *format, ...); void _putc(uint8_t c); uint8_t _getc(); -//extern "C" volatile uint32_t _millis; - //arduino: Print.h #define DEC 10 #define HEX 16 @@ -49,66 +71,109 @@ uint8_t _getc(); #define B01 1 #define B10 2 -#include "hardware/Clock.h" +// ------------------------ +// Serial ports +// ------------------------ -#include "../shared/Marduino.h" -#include "../shared/math_32bit.h" -#include "../shared/HAL_SPI.h" -#include "fastio.h" -#include "watchdog.h" -#include "serial.h" - -#define SHARED_SERVOS HAS_SERVOS - -extern HalSerial usb_serial; -#define MYSERIAL0 usb_serial - -#define ST7920_DELAY_1 DELAY_NS(600) -#define ST7920_DELAY_2 DELAY_NS(750) -#define ST7920_DELAY_3 DELAY_NS(750) +extern MSerialT usb_serial; +#define MYSERIAL1 usb_serial // // Interrupts // #define CRITICAL_SECTION_START() #define CRITICAL_SECTION_END() -#define ISRS_ENABLED() -#define ENABLE_ISRS() -#define DISABLE_ISRS() - -inline void HAL_init() {} - -// Utility functions -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-function" -int freeMemory(); -#pragma GCC diagnostic pop // ADC -#define HAL_ADC_VREF 5.0 -#define HAL_ADC_RESOLUTION 10 -#define HAL_ANALOG_SELECT(ch) HAL_adc_enable_channel(ch) -#define HAL_START_ADC(ch) HAL_adc_start_conversion(ch) -#define HAL_READ_ADC() HAL_adc_get_result() -#define HAL_ADC_READY() true +#define HAL_ADC_VREF_MV 5000 +#define HAL_ADC_RESOLUTION 10 -void HAL_adc_init(); -void HAL_adc_enable_channel(const uint8_t ch); -void HAL_adc_start_conversion(const uint8_t ch); -uint16_t HAL_adc_get_result(); +// ------------------------ +// Class Utilities +// ------------------------ -// Reset source -inline void HAL_clear_reset_source(void) {} -inline uint8_t HAL_get_reset_source(void) { return RST_POWER_ON; } - -inline void HAL_reboot() {} // reboot the board or restart the bootloader - -/* ---------------- Delay in cycles */ -FORCE_INLINE static void DELAY_CYCLES(uint64_t x) { - Clock::delayCycles(x); -} - -// Add strcmp_P if missing -#ifndef strcmp_P - #define strcmp_P(a, b) strcmp((a), (b)) +#pragma GCC diagnostic push +#if GCC_VERSION <= 50000 + #pragma GCC diagnostic ignored "-Wunused-function" #endif + +int freeMemory(); + +#pragma GCC diagnostic pop + +// ------------------------ +// MarlinHAL Class +// ------------------------ + +class MarlinHAL { +public: + + // Earliest possible init, before setup() + MarlinHAL() {} + + // Watchdog + static void watchdog_init() {} + static void watchdog_refresh() {} + + static void init() {} // Called early in setup() + static void init_board() {} // Called less early in setup() + static void reboot(); // Reset the application state and GPIO + + // Interrupts + static bool isr_state() { return true; } + static void isr_on() {} + static void isr_off() {} + + static void delay_ms(const int ms) { delay(ms); } + + // Tasks, called from marlin.idle() + static void idletask() {} + + // Reset + static constexpr uint8_t reset_reason = RST_POWER_ON; + static uint8_t get_reset_source() { return reset_reason; } + static void clear_reset_source() {} + + // Free SRAM + static int freeMemory() { return ::freeMemory(); } + + // + // ADC Methods + // + + static uint8_t active_ch; + + // 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 uint8_t) {} + + // Begin ADC sampling on the given channel + static void adc_start(const uint8_t ch) { active_ch = ch; } + + // Is the ADC ready for reading? + static bool adc_ready() { return true; } + + // The current value of the ADC register + static uint16_t adc_value(); + + /** + * Set the PWM duty cycle for the pin to the given value. + * No option to change the resolution or invert the duty cycle. + */ + 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_frequency(const pin_t, int) {} + + #ifndef HAS_LIBBSD + /** + * Redirect missing strlcpy here + */ + static size_t _strlcpy(char *dst, const char *src, size_t dsize); + #define strlcpy hal._strlcpy + #endif + +}; diff --git a/Marlin/src/HAL/LINUX/MarlinSPI.h b/Marlin/src/HAL/LINUX/MarlinSPI.h new file mode 100644 index 0000000000..0c447ba4cb --- /dev/null +++ b/Marlin/src/HAL/LINUX/MarlinSPI.h @@ -0,0 +1,26 @@ +/** + * 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 + +#include + +using MarlinSPI = SPIClass; diff --git a/Marlin/src/HAL/LINUX/arduino.cpp b/Marlin/src/HAL/LINUX/arduino.cpp index 4b56d02a38..0a09a11091 100644 --- a/Marlin/src/HAL/LINUX/arduino.cpp +++ b/Marlin/src/HAL/LINUX/arduino.cpp @@ -31,12 +31,8 @@ void cli() { } // Disable void sei() { } // Enable // Time functions -void _delay_ms(const int delay_ms) { - delay(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 @@ -51,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 967ca851ab..f665600d5e 100644 --- a/Marlin/src/HAL/LINUX/eeprom.cpp +++ b/Marlin/src/HAL/LINUX/eeprom.cpp @@ -35,17 +35,17 @@ uint8_t buffer[MARLIN_EEPROM_SIZE]; char filename[] = "eeprom.dat"; -size_t PersistentStore::capacity() { return MARLIN_EEPROM_SIZE; } +size_t PersistentStore::capacity() { return MARLIN_EEPROM_SIZE - eeprom_exclude_size; } bool PersistentStore::access_start() { const char eeprom_erase_value = 0xFF; FILE * eeprom_file = fopen(filename, "rb"); - if (eeprom_file == nullptr) return false; + if (!eeprom_file) return false; 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 { @@ -59,7 +59,7 @@ bool PersistentStore::access_start() { bool PersistentStore::access_finish() { FILE * eeprom_file = fopen(filename, "wb"); - if (eeprom_file == nullptr) return false; + if (!eeprom_file) return false; fwrite(buffer, sizeof(uint8_t), sizeof(buffer), eeprom_file); fclose(eeprom_file); return true; @@ -69,34 +69,34 @@ bool PersistentStore::write_data(int &pos, const uint8_t *value, size_t size, ui std::size_t bytes_written = 0; for (std::size_t i = 0; i < size; i++) { - buffer[pos+i] = value[i]; - bytes_written ++; + buffer[pos + i] = value[i]; + bytes_written++; } crc16(crc, value, size); - pos = pos + size; + pos += size; return (bytes_written != size); // return true for any error } -bool PersistentStore::read_data(int &pos, uint8_t* value, const size_t size, uint16_t *crc, const bool writing/*=true*/) { +bool PersistentStore::read_data(int &pos, uint8_t *value, const size_t size, uint16_t *crc, const bool writing/*=true*/) { std::size_t bytes_read = 0; if (writing) { for (std::size_t i = 0; i < size; i++) { - value[i] = buffer[pos+i]; - bytes_read ++; + value[i] = buffer[pos + i]; + bytes_read++; } crc16(crc, value, size); } else { uint8_t temp[size]; for (std::size_t i = 0; i < size; i++) { - temp[i] = buffer[pos+i]; - bytes_read ++; + temp[i] = buffer[pos + i]; + bytes_read++; } crc16(crc, temp, size); } - pos = pos + size; + pos += size; return bytes_read != size; // return true for any error } diff --git a/Marlin/src/HAL/LINUX/hardware/Gpio.h b/Marlin/src/HAL/LINUX/hardware/Gpio.h index 9255ec1dfc..f946be6484 100644 --- a/Marlin/src/HAL/LINUX/hardware/Gpio.h +++ b/Marlin/src/HAL/LINUX/hardware/Gpio.h @@ -40,7 +40,7 @@ struct GpioEvent { pin_type pin_id; GpioEvent::Type event; - GpioEvent(uint64_t timestamp, pin_type pin_id, GpioEvent::Type event){ + GpioEvent(uint64_t timestamp, pin_type pin_id, GpioEvent::Type event) { this->timestamp = timestamp; this->pin_id = pin_id; this->event = event; @@ -86,10 +86,10 @@ public: GpioEvent::Type evt_type = value > 1 ? GpioEvent::SET_VALUE : value > pin_map[pin].value ? GpioEvent::RISE : value < pin_map[pin].value ? GpioEvent::FALL : GpioEvent::NOP; pin_map[pin].value = value; GpioEvent evt(Clock::nanos(), pin, evt_type); - if (pin_map[pin].cb != nullptr) { + if (pin_map[pin].cb) { pin_map[pin].cb->interrupt(evt); } - if (Gpio::logger != nullptr) Gpio::logger->log(evt); + if (Gpio::logger) Gpio::logger->log(evt); } static uint16_t get(pin_type pin) { @@ -105,8 +105,8 @@ public: if (!valid_pin(pin)) return; pin_map[pin].mode = value; GpioEvent evt(Clock::nanos(), pin, GpioEvent::Type::SETM); - if (pin_map[pin].cb != nullptr) pin_map[pin].cb->interrupt(evt); - if (Gpio::logger != nullptr) Gpio::logger->log(evt); + if (pin_map[pin].cb) pin_map[pin].cb->interrupt(evt); + if (Gpio::logger) Gpio::logger->log(evt); } static uint8_t getMode(pin_type pin) { @@ -118,8 +118,8 @@ public: if (!valid_pin(pin)) return; pin_map[pin].dir = value; GpioEvent evt(Clock::nanos(), pin, GpioEvent::Type::SETD); - if (pin_map[pin].cb != nullptr) pin_map[pin].cb->interrupt(evt); - if (Gpio::logger != nullptr) Gpio::logger->log(evt); + if (pin_map[pin].cb) pin_map[pin].cb->interrupt(evt); + if (Gpio::logger) Gpio::logger->log(evt); } static uint8_t getDir(pin_type pin) { diff --git a/Marlin/src/HAL/LINUX/hardware/Heater.cpp b/Marlin/src/HAL/LINUX/hardware/Heater.cpp index 70df816182..44f11986c9 100644 --- a/Marlin/src/HAL/LINUX/hardware/Heater.cpp +++ b/Marlin/src/HAL/LINUX/hardware/Heater.cpp @@ -54,7 +54,7 @@ void Heater::update() { } void Heater::interrupt(GpioEvent ev) { - // ununsed + // unused } #endif // __PLAT_LINUX__ diff --git a/Marlin/src/HAL/LINUX/hardware/Heater.h b/Marlin/src/HAL/LINUX/hardware/Heater.h index b17078d0b7..6d590ce6c5 100644 --- a/Marlin/src/HAL/LINUX/hardware/Heater.h +++ b/Marlin/src/HAL/LINUX/hardware/Heater.h @@ -26,8 +26,8 @@ struct LowpassFilter { uint64_t data_delay = 0; uint16_t update(uint16_t value) { - data_delay = data_delay - (data_delay >> 6) + value; - return (uint16_t)(data_delay >> 6); + data_delay += value - (data_delay >> 6); + return uint16_t(data_delay >> 6); } }; diff --git a/Marlin/src/HAL/LINUX/hardware/LinearAxis.cpp b/Marlin/src/HAL/LINUX/hardware/LinearAxis.cpp index c5b3ccc986..e122ef3666 100644 --- a/Marlin/src/HAL/LINUX/hardware/LinearAxis.cpp +++ b/Marlin/src/HAL/LINUX/hardware/LinearAxis.cpp @@ -51,7 +51,7 @@ void LinearAxis::update() { } void LinearAxis::interrupt(GpioEvent ev) { - if (ev.pin_id == step_pin && !Gpio::pin_map[enable_pin].value){ + if (ev.pin_id == step_pin && !Gpio::pin_map[enable_pin].value) { if (ev.event == GpioEvent::RISE) { last_update = ev.timestamp; position += -1 + 2 * Gpio::pin_map[dir_pin].value; diff --git a/Marlin/src/HAL/LINUX/hardware/Timer.cpp b/Marlin/src/HAL/LINUX/hardware/Timer.cpp index 9f0d6a8f3a..013690a404 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/hardware/Timer.h b/Marlin/src/HAL/LINUX/hardware/Timer.h index 757efdcdbd..1b3b800dca 100644 --- a/Marlin/src/HAL/LINUX/hardware/Timer.h +++ b/Marlin/src/HAL/LINUX/hardware/Timer.h @@ -52,7 +52,7 @@ public: return (*(intptr_t*)timerid); } - static void handler(int sig, siginfo_t *si, void *uc){ + static void handler(int sig, siginfo_t *si, void *uc) { Timer* _this = (Timer*)si->si_value.sival_ptr; _this->avg_error += (Clock::nanos() - _this->start_time) - _this->period; //high_resolution_clock is also limited in precision, but best we have _this->avg_error /= 2; //very crude precision analysis (actually within +-500ns usually) diff --git a/Marlin/src/HAL/LINUX/inc/Conditionals_LCD.h b/Marlin/src/HAL/LINUX/inc/Conditionals_LCD.h index 99a6fc2753..5f1c4b1601 100644 --- a/Marlin/src/HAL/LINUX/inc/Conditionals_LCD.h +++ b/Marlin/src/HAL/LINUX/inc/Conditionals_LCD.h @@ -20,7 +20,3 @@ * */ #pragma once - -#if HAS_SPI_TFT || HAS_FSMC_TFT - #error "Sorry! TFT displays are not available for HAL/LINUX." -#endif diff --git a/Marlin/src/HAL/LINUX/inc/Conditionals_type.h b/Marlin/src/HAL/LINUX/inc/Conditionals_type.h new file mode 100644 index 0000000000..82f95a1035 --- /dev/null +++ b/Marlin/src/HAL/LINUX/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/LINUX/inc/SanityCheck.h b/Marlin/src/HAL/LINUX/inc/SanityCheck.h index 84167c97a1..861bade10f 100644 --- a/Marlin/src/HAL/LINUX/inc/SanityCheck.h +++ b/Marlin/src/HAL/LINUX/inc/SanityCheck.h @@ -26,14 +26,22 @@ */ // Emulating RAMPS -#if ENABLED(SPINDLE_LASER_PWM) && !(SPINDLE_LASER_PWM_PIN == 4 || SPINDLE_LASER_PWM_PIN == 6 || SPINDLE_LASER_PWM_PIN == 11) +#if ENABLED(SPINDLE_LASER_USE_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(FAST_PWM_FAN) || SPINDLE_LASER_FREQUENCY - #error "Features requiring Hardware PWM (FAST_PWM_FAN, SPINDLE_LASER_FREQUENCY) are not yet supported on LINUX." + #error "Features requiring Hardware PWM (FAST_PWM_FAN, SPINDLE_LASER_FREQUENCY) are not yet supported for HAL/LINUX." +#endif + +#if HAS_SPI_TFT || HAS_FSMC_TFT + #error "Sorry! TFT displays are not available for HAL/LINUX." #endif #if HAS_TMC_SW_SERIAL - #error "TMC220x Software Serial is not supported on this platform." + #error "TMC220x Software Serial is not supported for HAL/LINUX." +#endif + +#if ENABLED(POSTMORTEM_DEBUGGING) + #error "POSTMORTEM_DEBUGGING is not yet supported for HAL/LINUX." #endif diff --git a/Marlin/src/HAL/LINUX/include/Arduino.h b/Marlin/src/HAL/LINUX/include/Arduino.h index e28b474ede..87148a40b2 100644 --- a/Marlin/src/HAL/LINUX/include/Arduino.h +++ b/Marlin/src/HAL/LINUX/include/Arduino.h @@ -59,47 +59,25 @@ typedef uint8_t byte; #endif #define sq(v) ((v) * (v)) -#define square(v) sq(v) #define constrain(value, arg_min, arg_max) ((value) < (arg_min) ? (arg_min) :((value) > (arg_max) ? (arg_max) : (value))) -//Interrupts +// Interrupts void cli(); // Disable void sei(); // Enable void attachInterrupt(uint32_t pin, void (*callback)(), uint32_t mode); void detachInterrupt(uint32_t pin); -extern "C" void GpioEnableInt(uint32_t port, uint32_t pin, uint32_t mode); -extern "C" void GpioDisableInt(uint32_t port, uint32_t pin); -// Program Memory -#define pgm_read_ptr(addr) (*((void**)(addr))) -#define pgm_read_byte_near(addr) (*((uint8_t*)(addr))) -#define pgm_read_float_near(addr) (*((float*)(addr))) -#define pgm_read_word_near(addr) (*((uint16_t*)(addr))) -#define pgm_read_dword_near(addr) (*((uint32_t*)(addr))) -#define pgm_read_byte(addr) pgm_read_byte_near(addr) -#define pgm_read_float(addr) pgm_read_float_near(addr) -#define pgm_read_word(addr) pgm_read_word_near(addr) -#define pgm_read_dword(addr) pgm_read_dword_near(addr) - -using std::memcpy; -#define memcpy_P memcpy -#define sprintf_P sprintf -#define strstr_P strstr -#define strncpy_P strncpy -#define vsnprintf_P vsnprintf -#define strcpy_P strcpy -#define snprintf_P snprintf -#define strlen_P strlen +extern "C" { + void GpioEnableInt(uint32_t port, uint32_t pin, uint32_t mode); + void GpioDisableInt(uint32_t port, uint32_t pin); +} // Time functions -extern "C" { - void delay(const int milis); -} -void _delay_ms(const int delay); +extern "C" void delay(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.cpp b/Marlin/src/HAL/LINUX/include/pinmapping.cpp index 870ab3a96e..5823668cd5 100644 --- a/Marlin/src/HAL/LINUX/include/pinmapping.cpp +++ b/Marlin/src/HAL/LINUX/include/pinmapping.cpp @@ -25,43 +25,6 @@ #include "../../../gcode/parser.h" -uint8_t analog_offset = NUM_DIGITAL_PINS - NUM_ANALOG_INPUTS; - -// Get the digital pin for an analog index -pin_t analogInputToDigitalPin(const int8_t p) { - return (WITHIN(p, 0, NUM_ANALOG_INPUTS) ? analog_offset + p : P_NC); -} - -// Return the index of a pin number -int16_t GET_PIN_MAP_INDEX(const pin_t pin) { - return pin; -} - -// Test whether the pin is valid -bool VALID_PIN(const pin_t p) { - return WITHIN(p, 0, NUM_DIGITAL_PINS); -} - -// Get the analog index for a digital pin -int8_t DIGITAL_PIN_TO_ANALOG_PIN(const pin_t p) { - return (WITHIN(p, analog_offset, NUM_DIGITAL_PINS) ? p - analog_offset : P_NC); -} - -// Test whether the pin is PWM -bool PWM_PIN(const pin_t p) { - return false; -} - -// Test whether the pin is interruptable -bool INTERRUPT_PIN(const pin_t p) { - return false; -} - -// Get the pin number at the given index -pin_t GET_PIN_MAP_PIN(const int16_t ind) { - return ind; -} - int16_t PARSED_PIN_INDEX(const char code, const int16_t dval) { return parser.intval(code, dval); } diff --git a/Marlin/src/HAL/LINUX/include/pinmapping.h b/Marlin/src/HAL/LINUX/include/pinmapping.h index 98f4b812e8..9147ef82de 100644 --- a/Marlin/src/HAL/LINUX/include/pinmapping.h +++ b/Marlin/src/HAL/LINUX/include/pinmapping.h @@ -34,26 +34,32 @@ constexpr uint8_t NUM_ANALOG_INPUTS = 16; #define HAL_SENSITIVE_PINS +constexpr uint8_t analog_offset = NUM_DIGITAL_PINS - NUM_ANALOG_INPUTS; + // Get the digital pin for an analog index -pin_t analogInputToDigitalPin(const int8_t p); - -// Return the index of a pin number -int16_t GET_PIN_MAP_INDEX(const pin_t pin); - -// Test whether the pin is valid -bool VALID_PIN(const pin_t p); +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 -int8_t DIGITAL_PIN_TO_ANALOG_PIN(const pin_t p); +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 isValidPin(const pin_t pin) { return WITHIN(pin, 0, NUM_DIGITAL_PINS - 1); } // Test whether the pin is PWM -bool PWM_PIN(const pin_t p); +constexpr bool PWM_PIN(const pin_t) { return false; } -// Test whether the pin is interruptable -bool INTERRUPT_PIN(const pin_t p); +// Test whether the pin is interruptible +constexpr bool INTERRUPT_PIN(const pin_t) { return false; } // Get the pin number at the given index -pin_t GET_PIN_MAP_PIN(const int16_t 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/include/serial.h b/Marlin/src/HAL/LINUX/include/serial.h index e916249389..ebae066c3a 100644 --- a/Marlin/src/HAL/LINUX/include/serial.h +++ b/Marlin/src/HAL/LINUX/include/serial.h @@ -25,6 +25,7 @@ #if ENABLED(EMERGENCY_PARSER) #include "../../../feature/e_parser.h" #endif +#include "../../../core/serial_hook.h" #include #include @@ -73,19 +74,11 @@ private: volatile uint32_t index_read; }; -class HalSerial { -public: - - #if ENABLED(EMERGENCY_PARSER) - EmergencyParser::State emergency_state; - static inline bool emergency_parser_enabled() { return true; } - #endif - +struct HalSerial { HalSerial() { host_connected = true; } void begin(int32_t) {} - - void end() {} + void end() {} int peek() { uint8_t value; @@ -100,7 +93,7 @@ public: return transmit_buffer.write(c); } - operator bool() { return host_connected; } + bool connected() { return host_connected; } uint16_t available() { return (uint16_t)receive_buffer.available(); @@ -117,92 +110,9 @@ public: while (transmit_buffer.available()) { /* nada */ } } - void printf(const char *format, ...) { - static char buffer[256]; - va_list vArgs; - va_start(vArgs, format); - int length = vsnprintf((char *) buffer, 256, (char const *) format, vArgs); - va_end(vArgs); - if (length > 0 && length < 256) { - if (host_connected) { - for (int i = 0; i < length;) { - if (transmit_buffer.write(buffer[i])) { - ++i; - } - } - } - } - } - - #define DEC 10 - #define HEX 16 - #define OCT 8 - #define BIN 2 - - void print_bin(uint32_t value, uint8_t num_digits) { - uint32_t mask = 1 << (num_digits -1); - for (uint8_t i = 0; i < num_digits; i++) { - if (!(i % 4) && i) write(' '); - if (!(i % 16) && i) write(' '); - if (value & mask) write('1'); - else write('0'); - value <<= 1; - } - } - - void print(const char value[]) { printf("%s" , value); } - void print(char value, int nbase = 0) { - if (nbase == BIN) print_bin(value, 8); - else if (nbase == OCT) printf("%3o", value); - else if (nbase == HEX) printf("%2X", value); - else if (nbase == DEC ) printf("%d", value); - else printf("%c" , value); - } - void print(unsigned char value, int nbase = 0) { - if (nbase == BIN) print_bin(value, 8); - else if (nbase == OCT) printf("%3o", value); - else if (nbase == HEX) printf("%2X", value); - else printf("%u" , value); - } - void print(int value, int nbase = 0) { - if (nbase == BIN) print_bin(value, 16); - else if (nbase == OCT) printf("%6o", value); - else if (nbase == HEX) printf("%4X", value); - else printf("%d", value); - } - void print(unsigned int value, int nbase = 0) { - if (nbase == BIN) print_bin(value, 16); - else if (nbase == OCT) printf("%6o", value); - else if (nbase == HEX) printf("%4X", value); - else printf("%u" , value); - } - void print(long value, int nbase = 0) { - if (nbase == BIN) print_bin(value, 32); - else if (nbase == OCT) printf("%11o", value); - else if (nbase == HEX) printf("%8X", value); - else printf("%ld" , value); - } - void print(unsigned long value, int nbase = 0) { - if (nbase == BIN) print_bin(value, 32); - else if (nbase == OCT) printf("%11o", value); - else if (nbase == HEX) printf("%8X", value); - else printf("%lu" , value); - } - void print(float value, int round = 6) { printf("%f" , value); } - void print(double value, int round = 6) { printf("%f" , value); } - - void println(const char value[]) { printf("%s\n" , value); } - void println(char value, int nbase = 0) { print(value, nbase); println(); } - void println(unsigned char value, int nbase = 0) { print(value, nbase); println(); } - void println(int value, int nbase = 0) { print(value, nbase); println(); } - void println(unsigned int value, int nbase = 0) { print(value, nbase); println(); } - void println(long value, int nbase = 0) { print(value, nbase); println(); } - void println(unsigned long value, int nbase = 0) { print(value, nbase); println(); } - void println(float value, int round = 6) { printf("%f\n" , value); } - void println(double value, int round = 6) { printf("%f\n" , value); } - void println() { print('\n'); } - volatile RingBuffer receive_buffer; volatile RingBuffer transmit_buffer; volatile bool host_connected; }; + +typedef Serial1Class MSerialT; diff --git a/Marlin/src/HAL/LINUX/main.cpp b/Marlin/src/HAL/LINUX/main.cpp index 481f059030..27a066d619 100644 --- a/Marlin/src/HAL/LINUX/main.cpp +++ b/Marlin/src/HAL/LINUX/main.cpp @@ -1,8 +1,10 @@ /** * Marlin 3D Printer Firmware - * * Copyright (c) 2020 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 @@ -17,24 +19,27 @@ * along with this program. If not, see . * */ + #ifdef __PLAT_LINUX__ +#ifndef UNIT_TEST -extern void setup(); -extern void loop(); - -#include - -#include -#include +//#define GPIO_LOGGING // Full GPIO and Positional Logging #include "../../inc/MarlinConfig.h" -#include -#include #include "../shared/Delay.h" #include "hardware/IOLoggerCSV.h" #include "hardware/Heater.h" #include "hardware/LinearAxis.h" +#include +#include +#include +#include +#include + +extern void setup(); +extern void loop(); + // simple stdout / stdin implementation for fake serial port void write_serial_thread() { for (;;) { @@ -64,8 +69,6 @@ void simulation_loop() { LinearAxis z_axis(Z_ENABLE_PIN, Z_DIR_PIN, Z_STEP_PIN, Z_MIN_PIN, Z_MAX_PIN); LinearAxis extruder0(E0_ENABLE_PIN, E0_DIR_PIN, E0_STEP_PIN, P_NC, P_NC); - //#define GPIO_LOGGING // Full GPIO and Positional Logging - #ifdef GPIO_LOGGING IOLoggerCSV logger("all_gpio_log.csv"); Gpio::attachLogger(&logger); @@ -88,7 +91,7 @@ void simulation_loop() { #ifdef GPIO_LOGGING if (x_axis.position != x || y_axis.position != y || z_axis.position != z) { - uint64_t update = MAX3(x_axis.last_update, y_axis.last_update, z_axis.last_update); + uint64_t update = _MAX(x_axis.last_update, y_axis.last_update, z_axis.last_update); position_log << update << ", " << x_axis.position << ", " << y_axis.position << ", " << z_axis.position << std::endl; position_log.flush(); x = x_axis.position; @@ -107,8 +110,8 @@ int main() { std::thread write_serial (write_serial_thread); std::thread read_serial (read_serial_thread); - #ifdef MYSERIAL0 - MYSERIAL0.begin(BAUDRATE); + #ifdef MYSERIAL1 + MYSERIAL1.begin(BAUDRATE); SERIAL_ECHOLNPGM("x86_64 Initialized"); SERIAL_FLUSHTX(); #endif @@ -133,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 a93ceddc61..aacb626105 100644 --- a/Marlin/src/HAL/LINUX/pinsDebug.h +++ b/Marlin/src/HAL/LINUX/pinsDebug.h @@ -2,6 +2,9 @@ * Marlin 3D Printer Firmware * Copyright (c) 2020 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 @@ -16,44 +19,62 @@ * along with this program. If not, see . * */ +#pragma once /** - * Support routines for X86_64 + * 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) */ -/** - * Translation of routines & variables used by pinsDebug.h - */ +#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 NUMBER_PINS_TOTAL NUM_DIGITAL_PINS -#define pwm_details(pin) pin = pin // do nothing // print PWM details -#define pwm_status(pin) false //Print a pin's PWM status. Return true if it's currently a PWM pin. -#define IS_ANALOG(P) (DIGITAL_PIN_TO_ANALOG_PIN(P) >= 0 ? 1 : 0) -#define digitalRead_mod(p) digitalRead(p) -#define PRINT_PORT(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 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)); } -int8_t get_pin_mode(pin_t pin) { - if (!VALID_PIN(pin)) return -1; - return 0; -} +#define isAnalogPin(P) (digitalPinToAnalogIndex(P) >= 0) -bool GET_PINMODE(pin_t pin) { - int8_t pin_mode = get_pin_mode(pin); - if (pin_mode == -1 || pin_mode == ADC_pin_mode(pin)) // found an invalid pin or active analog pin +#define digitalRead_mod(P) digitalRead(P) + +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; - return (Gpio::getMode(pin) != 0); //input/output state + return (Gpio::getMode(pin) != 0); // Input/output state } -bool GET_ARRAY_IS_DIGITAL(pin_t pin) { - return (!IS_ANALOG(pin) || get_pin_mode(pin) != ADC_pin_mode(pin)); -} +void printPinPWM(const pin_t) {} +bool pwm_status(const pin_t) { return false; } + +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/servo_private.h b/Marlin/src/HAL/LINUX/servo_private.h index bcc8d2037f..11e1c2b93c 100644 --- a/Marlin/src/HAL/LINUX/servo_private.h +++ b/Marlin/src/HAL/LINUX/servo_private.h @@ -60,7 +60,6 @@ #define INVALID_SERVO 255 // flag indicating an invalid servo index - // Types typedef struct { diff --git a/Marlin/src/HAL/LINUX/spi_pins.h b/Marlin/src/HAL/LINUX/spi_pins.h index 01ba28e5b6..221145e4b3 100644 --- a/Marlin/src/HAL/LINUX/spi_pins.h +++ b/Marlin/src/HAL/LINUX/spi_pins.h @@ -21,34 +21,20 @@ */ #pragma once -#include "../../core/macros.h" -#include "../../inc/MarlinConfigPre.h" - -#if BOTH(HAS_MARLINUI_U8GLIB, SDSUPPORT) && (LCD_PINS_D4 == SCK_PIN || LCD_PINS_ENABLE == MOSI_PIN || DOGLCD_SCK == SCK_PIN || DOGLCD_MOSI == MOSI_PIN) - #define LPC_SOFTWARE_SPI // If the SD card and LCD adapter share the same SPI pins, then software SPI is currently - // needed due to the speed and mode required for communicating with each device being different. - // This requirement can be removed if the SPI access to these devices is updated to use - // spiBeginTransaction. +#if ALL(HAS_MARLINUI_U8GLIB, HAS_MEDIA) && (LCD_PINS_D4 == SD_SCK_PIN || LCD_PINS_EN == SD_MOSI_PIN || DOGLCD_SCK == SD_SCK_PIN || DOGLCD_MOSI == SD_MOSI_PIN) + #define SOFTWARE_SPI // If the SD card and LCD adapter share the same SPI pins, then software SPI is currently + // needed due to the speed and mode required for communicating with each device being different. + // This requirement can be removed if the SPI access to these devices is updated to use + // spiBeginTransaction. #endif -/** onboard SD card */ -//#define SCK_PIN P0_07 -//#define MISO_PIN P0_08 -//#define MOSI_PIN P0_09 -//#define SS_PIN P0_06 -/** external */ -#ifndef SCK_PIN - #define SCK_PIN 50 +// External SD +#ifndef SD_SCK_PIN + #define SD_SCK_PIN 50 #endif -#ifndef MISO_PIN - #define MISO_PIN 51 +#ifndef SD_MISO_PIN + #define SD_MISO_PIN 51 #endif -#ifndef MOSI_PIN - #define MOSI_PIN 52 -#endif -#ifndef SS_PIN - #define SS_PIN 53 -#endif -#ifndef SDSS - #define SDSS SS_PIN +#ifndef SD_MOSI_PIN + #define SD_MOSI_PIN 52 #endif diff --git a/Marlin/src/HAL/LINUX/timers.cpp b/Marlin/src/HAL/LINUX/timers.cpp index 66d80f2518..a8ab403197 100644 --- a/Marlin/src/HAL/LINUX/timers.cpp +++ b/Marlin/src/HAL/LINUX/timers.cpp @@ -1,9 +1,9 @@ /** * Marlin 3D Printer Firmware - * * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com - * Copyright (c) 2015-2016 Nico Tonnhofer wurstnase.reprap@gmail.com + * + * 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 diff --git a/Marlin/src/HAL/LINUX/timers.h b/Marlin/src/HAL/LINUX/timers.h index 1beaea95ab..39c76f02b5 100644 --- a/Marlin/src/HAL/LINUX/timers.h +++ b/Marlin/src/HAL/LINUX/timers.h @@ -1,8 +1,9 @@ /** * Marlin 3D Printer Firmware - * * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com + * + * 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 @@ -33,37 +34,36 @@ #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 -#ifndef STEP_TIMER_NUM - #define STEP_TIMER_NUM 0 // Timer Index for Stepper +#ifndef MF_TIMER_STEP + #define MF_TIMER_STEP 0 // Timer Index for Stepper #endif -#ifndef PULSE_TIMER_NUM - #define PULSE_TIMER_NUM STEP_TIMER_NUM +#ifndef MF_TIMER_PULSE + #define MF_TIMER_PULSE MF_TIMER_STEP #endif -#ifndef TEMP_TIMER_NUM - #define TEMP_TIMER_NUM 1 // Timer Index for Temperature +#ifndef MF_TIMER_TEMP + #define MF_TIMER_TEMP 1 // Timer Index for Temperature #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(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) #ifndef HAL_STEP_TIMER_ISR #define HAL_STEP_TIMER_ISR() extern "C" void TIMER0_IRQHandler() @@ -77,7 +77,6 @@ typedef uint32_t hal_timer_t; #define HAL_PWM_TIMER_ISR() extern "C" void TIMER3_IRQHandler() #define HAL_PWM_TIMER_IRQn - void HAL_timer_init(); void HAL_timer_start(const uint8_t timer_num, const uint32_t frequency); @@ -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(TIMER_NUM) -#define HAL_timer_isr_epilogue(TIMER_NUM) +inline void HAL_timer_isr_prologue(const uint8_t) {} +inline void HAL_timer_isr_epilogue(const uint8_t) {} diff --git a/Marlin/src/HAL/LINUX/u8g/LCD_defines.h b/Marlin/src/HAL/LINUX/u8g/LCD_defines.h new file mode 100644 index 0000000000..6caa0240ca --- /dev/null +++ b/Marlin/src/HAL/LINUX/u8g/LCD_defines.h @@ -0,0 +1,26 @@ +/** + * 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 + * (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 + +/** + * Linux LCD-specific defines + */ diff --git a/Marlin/src/HAL/LPC1768/DebugMonitor.cpp b/Marlin/src/HAL/LPC1768/DebugMonitor.cpp deleted file mode 100644 index 783b10cfac..0000000000 --- a/Marlin/src/HAL/LPC1768/DebugMonitor.cpp +++ /dev/null @@ -1,322 +0,0 @@ -/** - * Marlin 3D Printer Firmware - * Copyright (c) 2020 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 TARGET_LPC1768 - -#include "../../core/macros.h" -#include "../../core/serial.h" -#include - -#include "../shared/backtrace/unwinder.h" -#include "../shared/backtrace/unwmemaccess.h" -#include "watchdog.h" -#include - - -// Debug monitor that dumps to the Programming port all status when -// an exception or WDT timeout happens - And then resets the board - -// All the Monitor routines must run with interrupts disabled and -// under an ISR execution context. That is why we cannot reuse the -// Serial interrupt routines or any C runtime, as we don't know the -// state we are when running them - -// A SW memory barrier, to ensure GCC does not overoptimize loops -#define sw_barrier() __asm__ volatile("": : :"memory"); - -// (re)initialize UART0 as a monitor output to 250000,n,8,1 -static void TXBegin() { -} - -// Send character through UART with no interrupts -static void TX(char c) { - _DBC(c); -} - -// Send String through UART -static void TX(const char* s) { - while (*s) TX(*s++); -} - -static void TXDigit(uint32_t d) { - if (d < 10) TX((char)(d+'0')); - else if (d < 16) TX((char)(d+'A'-10)); - else TX('?'); -} - -// Send Hex number thru UART -static void TXHex(uint32_t v) { - TX("0x"); - for (uint8_t i = 0; i < 8; i++, v <<= 4) - TXDigit((v >> 28) & 0xF); -} - -// Send Decimal number thru UART -static void TXDec(uint32_t v) { - if (!v) { - TX('0'); - return; - } - - char nbrs[14]; - char *p = &nbrs[0]; - while (v != 0) { - *p++ = '0' + (v % 10); - v /= 10; - } - do { - p--; - TX(*p); - } while (p != &nbrs[0]); -} - -// Dump a backtrace entry -static bool UnwReportOut(void* ctx, const UnwReport* bte) { - int* p = (int*)ctx; - - (*p)++; - TX('#'); TXDec(*p); TX(" : "); - TX(bte->name?bte->name:"unknown"); TX('@'); TXHex(bte->function); - TX('+'); TXDec(bte->address - bte->function); - TX(" PC:");TXHex(bte->address); TX('\n'); - return true; -} - -#ifdef UNW_DEBUG - void UnwPrintf(const char* format, ...) { - char dest[256]; - va_list argptr; - va_start(argptr, format); - vsprintf(dest, format, argptr); - va_end(argptr); - TX(&dest[0]); - } -#endif - -/* Table of function pointers for passing to the unwinder */ -static const UnwindCallbacks UnwCallbacks = { - UnwReportOut, - UnwReadW, - UnwReadH, - UnwReadB - #ifdef UNW_DEBUG - ,UnwPrintf - #endif -}; - - -/** - * HardFaultHandler_C: - * This is called from the HardFault_HandlerAsm with a pointer the Fault stack - * as the parameter. We can then read the values from the stack and place them - * into local variables for ease of reading. - * We then read the various Fault Status and Address Registers to help decode - * cause of the fault. - * The function ends with a BKPT instruction to force control back into the debugger - */ -extern "C" -void HardFault_HandlerC(unsigned long *sp, unsigned long lr, unsigned long cause) { - - static const char* causestr[] = { - "NMI","Hard","Mem","Bus","Usage","Debug","WDT","RSTC" - }; - - UnwindFrame btf; - - // Dump report to the Programming port (interrupts are DISABLED) - TXBegin(); - TX("\n\n## Software Fault detected ##\n"); - TX("Cause: "); TX(causestr[cause]); TX('\n'); - - TX("R0 : "); TXHex(((unsigned long)sp[0])); TX('\n'); - TX("R1 : "); TXHex(((unsigned long)sp[1])); TX('\n'); - TX("R2 : "); TXHex(((unsigned long)sp[2])); TX('\n'); - TX("R3 : "); TXHex(((unsigned long)sp[3])); TX('\n'); - TX("R12 : "); TXHex(((unsigned long)sp[4])); TX('\n'); - TX("LR : "); TXHex(((unsigned long)sp[5])); TX('\n'); - TX("PC : "); TXHex(((unsigned long)sp[6])); TX('\n'); - TX("PSR : "); TXHex(((unsigned long)sp[7])); TX('\n'); - - // Configurable Fault Status Register - // Consists of MMSR, BFSR and UFSR - TX("CFSR : "); TXHex((*((volatile unsigned long *)(0xE000ED28)))); TX('\n'); - - // Hard Fault Status Register - TX("HFSR : "); TXHex((*((volatile unsigned long *)(0xE000ED2C)))); TX('\n'); - - // Debug Fault Status Register - TX("DFSR : "); TXHex((*((volatile unsigned long *)(0xE000ED30)))); TX('\n'); - - // Auxiliary Fault Status Register - TX("AFSR : "); TXHex((*((volatile unsigned long *)(0xE000ED3C)))); TX('\n'); - - // Read the Fault Address Registers. These may not contain valid values. - // Check BFARVALID/MMARVALID to see if they are valid values - // MemManage Fault Address Register - TX("MMAR : "); TXHex((*((volatile unsigned long *)(0xE000ED34)))); TX('\n'); - - // Bus Fault Address Register - TX("BFAR : "); TXHex((*((volatile unsigned long *)(0xE000ED38)))); TX('\n'); - - TX("ExcLR: "); TXHex(lr); TX('\n'); - TX("ExcSP: "); TXHex((unsigned long)sp); TX('\n'); - - btf.sp = ((unsigned long)sp) + 8*4; // The original stack pointer - btf.fp = btf.sp; - btf.lr = ((unsigned long)sp[5]); - btf.pc = ((unsigned long)sp[6]) | 1; // Force Thumb, as CORTEX only support it - - // Perform a backtrace - TX("\nBacktrace:\n\n"); - int ctr = 0; - UnwindStart(&btf, &UnwCallbacks, &ctr); - - // Disable all NVIC interrupts - NVIC->ICER[0] = 0xFFFFFFFF; - NVIC->ICER[1] = 0xFFFFFFFF; - - // Relocate VTOR table to default position - SCB->VTOR = 0; - - // Clear cause of reset to prevent entering smoothie bootstrap - HAL_clear_reset_source(); - - // Restart watchdog - #if ENABLED(USE_WATCHDOG) - //WDT_Restart(WDT); - watchdog_init(); - #endif - - // Reset controller - NVIC_SystemReset(); - - // Nothing below here is compiled because NVIC_SystemReset loops forever - - for (;;) { TERN_(USE_WATCHDOG, watchdog_init()); } -} - -extern "C" { -__attribute__((naked)) void NMI_Handler() { - __asm__ __volatile__ ( - ".syntax unified" "\n\t" - A("tst lr, #4") - A("ite eq") - A("mrseq r0, msp") - A("mrsne r0, psp") - A("mov r1,lr") - A("mov r2,#0") - A("b HardFault_HandlerC") - ); -} - -__attribute__((naked)) void HardFault_Handler() { - __asm__ __volatile__ ( - ".syntax unified" "\n\t" - A("tst lr, #4") - A("ite eq") - A("mrseq r0, msp") - A("mrsne r0, psp") - A("mov r1,lr") - A("mov r2,#1") - A("b HardFault_HandlerC") - ); -} - -__attribute__((naked)) void MemManage_Handler() { - __asm__ __volatile__ ( - ".syntax unified" "\n\t" - A("tst lr, #4") - A("ite eq") - A("mrseq r0, msp") - A("mrsne r0, psp") - A("mov r1,lr") - A("mov r2,#2") - A("b HardFault_HandlerC") - ); -} - -__attribute__((naked)) void BusFault_Handler() { - __asm__ __volatile__ ( - ".syntax unified" "\n\t" - A("tst lr, #4") - A("ite eq") - A("mrseq r0, msp") - A("mrsne r0, psp") - A("mov r1,lr") - A("mov r2,#3") - A("b HardFault_HandlerC") - ); -} - -__attribute__((naked)) void UsageFault_Handler() { - __asm__ __volatile__ ( - ".syntax unified" "\n\t" - A("tst lr, #4") - A("ite eq") - A("mrseq r0, msp") - A("mrsne r0, psp") - A("mov r1,lr") - A("mov r2,#4") - A("b HardFault_HandlerC") - ); -} - -__attribute__((naked)) void DebugMon_Handler() { - __asm__ __volatile__ ( - ".syntax unified" "\n\t" - A("tst lr, #4") - A("ite eq") - A("mrseq r0, msp") - A("mrsne r0, psp") - A("mov r1,lr") - A("mov r2,#5") - A("b HardFault_HandlerC") - ); -} - -/* This is NOT an exception, it is an interrupt handler - Nevertheless, the framing is the same */ -__attribute__((naked)) void WDT_IRQHandler() { - __asm__ __volatile__ ( - ".syntax unified" "\n\t" - A("tst lr, #4") - A("ite eq") - A("mrseq r0, msp") - A("mrsne r0, psp") - A("mov r1,lr") - A("mov r2,#6") - A("b HardFault_HandlerC") - ); -} - -__attribute__((naked)) void RSTC_Handler() { - __asm__ __volatile__ ( - ".syntax unified" "\n\t" - A("tst lr, #4") - A("ite eq") - A("mrseq r0, msp") - A("mrsne r0, psp") - A("mov r1,lr") - A("mov r2,#7") - A("b HardFault_HandlerC") - ); -} -} -#endif // TARGET_LPC1768 diff --git a/Marlin/src/HAL/LPC1768/HAL.cpp b/Marlin/src/HAL/LPC1768/HAL.cpp index 939f1e8a94..4b0e255f37 100644 --- a/Marlin/src/HAL/LPC1768/HAL.cpp +++ b/Marlin/src/HAL/LPC1768/HAL.cpp @@ -23,28 +23,28 @@ #include "../../inc/MarlinConfig.h" #include "../shared/Delay.h" -#include "../../../gcode/parser.h" +#include "../../core/millis_t.h" -#if ENABLED(USE_WATCHDOG) - #include "watchdog.h" -#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include -uint32_t HAL_adc_reading = 0; +uint32_t MarlinHAL::adc_result = 0; +pin_t MarlinHAL::adc_pin = 0; // U8glib required functions -extern "C" void u8g_xMicroDelay(uint16_t val) { - DELAY_US(val); +extern "C" { + void u8g_xMicroDelay(uint16_t val) { DELAY_US(val); } + void u8g_MicroDelay() { u8g_xMicroDelay(1); } + void u8g_10MicroDelay() { u8g_xMicroDelay(10); } + void u8g_Delay(uint16_t val) { delay(val); } } -extern "C" void u8g_MicroDelay() { - u8g_xMicroDelay(1); -} -extern "C" void u8g_10MicroDelay() { - u8g_xMicroDelay(10); -} -extern "C" void u8g_Delay(uint16_t val) { - delay(val); -} -//************************// // return free heap space int freeMemory() { @@ -57,7 +57,200 @@ int freeMemory() { return result; } -// scan command line for code +extern "C" { + #include + int isLPC1769(); + void disk_timerproc(); +} + +extern uint32_t MSC_SD_Init(uint8_t pdrv); + +void SysTick_Callback() { disk_timerproc(); } + +TERN_(POSTMORTEM_DEBUGGING, extern void install_min_serial()); + +void MarlinHAL::init() { + + // Init LEDs + #if PIN_EXISTS(LED) + SET_DIR_OUTPUT(LED_PIN); + WRITE_PIN_CLR(LED_PIN); + #if PIN_EXISTS(LED2) + SET_DIR_OUTPUT(LED2_PIN); + WRITE_PIN_CLR(LED2_PIN); + #if PIN_EXISTS(LED3) + SET_DIR_OUTPUT(LED3_PIN); + WRITE_PIN_CLR(LED3_PIN); + #if PIN_EXISTS(LED4) + SET_DIR_OUTPUT(LED4_PIN); + WRITE_PIN_CLR(LED4_PIN); + #endif + #endif + #endif + + // Flash status LED 3 times to indicate Marlin has started booting + for (uint8_t i = 0; i < 6; ++i) { + TOGGLE(LED_PIN); + delay(100); + } + #endif + + // Init Servo Pins + #define INIT_SERVO(N) OUT_WRITE(SERVO##N##_PIN, LOW) + #if HAS_SERVO_0 + INIT_SERVO(0); + #endif + #if HAS_SERVO_1 + INIT_SERVO(1); + #endif + #if HAS_SERVO_2 + INIT_SERVO(2); + #endif + #if HAS_SERVO_3 + INIT_SERVO(3); + #endif + #if HAS_SERVO_4 + INIT_SERVO(4); + #endif + #if HAS_SERVO_5 + INIT_SERVO(5); + #endif + + //debug_frmwrk_init(); + //_DBG("\n\nDebug running\n"); + // Initialize the SD card chip select pins as soon as possible + #if PIN_EXISTS(SD_SS) + OUT_WRITE(SD_SS_PIN, HIGH); + #endif + + #if PIN_EXISTS(ONBOARD_SD_CS) && ONBOARD_SD_CS_PIN != SD_SS_PIN + OUT_WRITE(ONBOARD_SD_CS_PIN, HIGH); + #endif + + #ifdef LPC1768_ENABLE_CLKOUT_12M + /** + * CLKOUTCFG register + * bit 8 (CLKOUT_EN) = enables CLKOUT signal. Disabled for now to prevent glitch when enabling GPIO. + * bits 7:4 (CLKOUTDIV) = set to 0 for divider setting of /1 + * bits 3:0 (CLKOUTSEL) = set to 1 to select main crystal oscillator as CLKOUT source + */ + LPC_SC->CLKOUTCFG = (0<<8)|(0<<4)|(1<<0); + // set P1.27 pin to function 01 (CLKOUT) + PINSEL_CFG_Type PinCfg; + PinCfg.Portnum = 1; + PinCfg.Pinnum = 27; + PinCfg.Funcnum = 1; // function 01 (CLKOUT) + PinCfg.OpenDrain = 0; // not open drain + PinCfg.Pinmode = 2; // no pull-up/pull-down + PINSEL_ConfigPin(&PinCfg); + // now set CLKOUT_EN bit + SBI(LPC_SC->CLKOUTCFG, 8); + #endif + + USB_Init(); // USB Initialization + USB_Connect(false); // USB clear connection + delay(1000); // Give OS time to notice + USB_Connect(true); + + TERN_(HAS_SD_HOST_DRIVE, MSC_SD_Init(0)); // Enable USB SD card access + + const millis_t usb_timeout = millis() + 2000; + while (!USB_Configuration && PENDING(millis(), usb_timeout)) { + delay(50); + idletask(); + #if PIN_EXISTS(LED) + TOGGLE(LED_PIN); // Flash quickly during USB initialization + #endif + } + + HAL_timer_init(); + + TERN_(POSTMORTEM_DEBUGGING, install_min_serial()); // Install the min serial handler +} + +#include "../../sd/cardreader.h" + +// HAL idle task +void MarlinHAL::idletask() { + #if HAS_SHARED_MEDIA + // 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 + MSC_Release_Lock(); + #endif + // Perform USB stack housekeeping + MSC_RunDeferredCommands(); +} + +void MarlinHAL::reboot() { NVIC_SystemReset(); } + +uint8_t MarlinHAL::get_reset_source() { + #if ENABLED(USE_WATCHDOG) + if (watchdog_timed_out()) return RST_WATCHDOG; + #endif + return RST_POWER_ON; +} + +void MarlinHAL::clear_reset_source() { watchdog_clear_timeout_flag(); } + +void flashFirmware(const int16_t) { + delay(500); // Give OS time to disconnect + USB_Connect(false); // USB clear connection + delay(1000); // Give OS time to notice + hal.reboot(); +} + +#if ENABLED(USE_WATCHDOG) + + #include + + #define WDT_TIMEOUT_US TERN(WATCHDOG_DURATION_8S, 8000000, 4000000) // 4 or 8 second timeout + + void MarlinHAL::watchdog_init() { + #if ENABLED(WATCHDOG_RESET_MANUAL) + // We enable the watchdog timer, but only for the interrupt. + + // Configure WDT to only trigger an interrupt + // Disable WDT interrupt (just in case, to avoid triggering it!) + NVIC_DisableIRQ(WDT_IRQn); + + // We NEED memory barriers to ensure Interrupts are actually disabled! + // ( https://dzone.com/articles/nvic-disabling-interrupts-on-arm-cortex-m-and-the ) + __DSB(); + __ISB(); + + // Configure WDT to only trigger an interrupt + // Initialize WDT with the given parameters + WDT_Init(WDT_CLKSRC_IRC, WDT_MODE_INT_ONLY); + + // Configure and enable WDT interrupt. + NVIC_ClearPendingIRQ(WDT_IRQn); + NVIC_SetPriority(WDT_IRQn, 0); // Use highest priority, so we detect all kinds of lockups + NVIC_EnableIRQ(WDT_IRQn); + #else + WDT_Init(WDT_CLKSRC_IRC, WDT_MODE_RESET); + #endif + WDT_Start(WDT_TIMEOUT_US); + } + + void MarlinHAL::watchdog_refresh() { + WDT_Feed(); + #if DISABLED(PINS_DEBUGGING) && PIN_EXISTS(LED) + TOGGLE(LED_PIN); // heartbeat indicator + #endif + } + + // Timeout state + bool MarlinHAL::watchdog_timed_out() { return TEST(WDT_ReadTimeOutFlag(), 0); } + void MarlinHAL::watchdog_clear_timeout_flag() { WDT_ClrTimeOutFlag(); } + +#endif // USE_WATCHDOG + +#include "../../../gcode/parser.h" + +// For M42/M43, scan command line for pin code // return index into pin map array if found and the pin is valid. // return dval if not found or not a valid pin. int16_t PARSED_PIN_INDEX(const char code, const int16_t dval) { @@ -66,17 +259,4 @@ int16_t PARSED_PIN_INDEX(const char code, const int16_t dval) { return ind > -1 ? ind : dval; } -void flashFirmware(const int16_t) { NVIC_SystemReset(); } - -void HAL_clear_reset_source(void) { - TERN_(USE_WATCHDOG, watchdog_clear_timeout_flag()); -} - -uint8_t HAL_get_reset_source(void) { - #if ENABLED(USE_WATCHDOG) - if (watchdog_timed_out()) return RST_WATCHDOG; - #endif - return RST_POWER_ON; -} - #endif // TARGET_LPC1768 diff --git a/Marlin/src/HAL/LPC1768/HAL.h b/Marlin/src/HAL/LPC1768/HAL.h index cb637e715d..052d6637c8 100644 --- a/Marlin/src/HAL/LPC1768/HAL.h +++ b/Marlin/src/HAL/LPC1768/HAL.h @@ -1,9 +1,9 @@ /** * Marlin 3D Printer Firmware - * * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com - * Copyright (c) 2015-2016 Nico Tonnhofer wurstnase.reprap@gmail.com + * + * 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 @@ -28,8 +28,6 @@ #define CPU_32_BIT -void HAL_init(); - #include #include #include @@ -40,80 +38,25 @@ extern "C" volatile uint32_t _millis; #include "../shared/math_32bit.h" #include "../shared/HAL_SPI.h" #include "fastio.h" -#include "watchdog.h" -#include "MarlinSerial.h" #include #include -#include - -// i2c uses 8-bit shifted address -#define I2C_ADDRESS(A) uint8_t((A) << 1) // -// Default graphical display delays +// Serial Ports // -#ifndef ST7920_DELAY_1 - #define ST7920_DELAY_1 DELAY_NS(600) -#endif -#ifndef ST7920_DELAY_2 - #define ST7920_DELAY_2 DELAY_NS(750) -#endif -#ifndef ST7920_DELAY_3 - #define ST7920_DELAY_3 DELAY_NS(750) -#endif -#define _MSERIAL(X) MSerial##X -#define MSERIAL(X) _MSERIAL(X) -#define MSerial0 MSerial - -#if SERIAL_PORT == -1 - #define MYSERIAL0 UsbSerial -#elif WITHIN(SERIAL_PORT, 0, 3) - #define MYSERIAL0 MSERIAL(SERIAL_PORT) -#else - #error "SERIAL_PORT must be from -1 to 3. Please update your configuration." -#endif - -#ifdef SERIAL_PORT_2 - #if SERIAL_PORT_2 == -1 - #define MYSERIAL1 UsbSerial - #elif WITHIN(SERIAL_PORT_2, 0, 3) - #define MYSERIAL1 MSERIAL(SERIAL_PORT_2) - #else - #error "SERIAL_PORT_2 must be from -1 to 3. Please update your configuration." - #endif -#endif - -#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 -1 to 3. Please update your configuration." - #endif -#endif +#include "MarlinSerial.h" // // Interrupts // -#define CRITICAL_SECTION_START() uint32_t primask = __get_PRIMASK(); __disable_irq() -#define CRITICAL_SECTION_END() if (!primask) __enable_irq() -#define ISRS_ENABLED() (!__get_PRIMASK()) -#define ENABLE_ISRS() __enable_irq() -#define DISABLE_ISRS() __disable_irq() + +#define CRITICAL_SECTION_START() const bool irqon = !__get_PRIMASK(); __disable_irq() +#define CRITICAL_SECTION_END() if (irqon) __enable_irq() // -// Utility functions -// -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-function" -int freeMemory(); -#pragma GCC diagnostic pop - -// -// ADC API +// ADC // #define ADC_MEDIAN_FILTER_SIZE (23) // Higher values increase step delay (phase shift), @@ -127,33 +70,22 @@ int freeMemory(); // K = 6, 565 samples, 500Hz sample rate, 1.13s convergence on full range step // Memory usage per ADC channel (bytes): 4 (32 Bytes for 8 channels) -#define HAL_ADC_VREF 3.3 // ADC voltage reference +#define HAL_ADC_VREF_MV 3300 // ADC voltage reference #define HAL_ADC_RESOLUTION 12 // 15 bit maximum, raw temperature is stored as int16_t #define HAL_ADC_FILTERED // Disable oversampling done in Marlin as ADC values already filtered in HAL -using FilteredADC = LPC176x::ADC; -extern uint32_t HAL_adc_reading; -[[gnu::always_inline]] inline void HAL_start_adc(const pin_t pin) { - HAL_adc_reading = FilteredADC::read(pin) >> (16 - HAL_ADC_RESOLUTION); // returns 16bit value, reduce to required bits -} -[[gnu::always_inline]] inline uint16_t HAL_read_adc() { - return HAL_adc_reading; -} - -#define HAL_adc_init() -#define HAL_ANALOG_SELECT(pin) FilteredADC::enable_channel(pin) -#define HAL_START_ADC(pin) HAL_start_adc(pin) -#define HAL_READ_ADC() HAL_read_adc() -#define HAL_ADC_READY() (true) +// +// Pin Mapping for M42, M43, M226 +// // 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; } @@ -172,37 +104,112 @@ 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_IDLETASK 1 -void HAL_idletask(); +// ------------------------ +// Defines +// ------------------------ -#define PLATFORM_M997_SUPPORT +#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 -/** - * set_pwm_frequency - * Set the frequency of the timer corresponding to the provided pin - * All Hardware PWM pins run at the same frequency and all - * Software PWM pins run at the same frequency - */ -void set_pwm_frequency(const pin_t pin, int f_desired); +// Default graphical display delays +#define CPU_ST7920_DELAY_1 600 +#define CPU_ST7920_DELAY_2 750 +#define CPU_ST7920_DELAY_3 750 -/** - * set_pwm_duty - * Set the PWM duty cycle of the provided pin to the provided value - * Optionally allows inverting the duty cycle [default = false] - * Optionally allows changing the maximum size of the provided value to enable finer PWM duty control [default = 255] - */ -void set_pwm_duty(const pin_t pin, const uint16_t v, const uint16_t v_size=255, const bool invert=false); +// ------------------------ +// Free Memory Accessor +// ------------------------ -// Reset source -void HAL_clear_reset_source(void); -uint8_t HAL_get_reset_source(void); - -inline void HAL_reboot() {} // reboot the board or restart the bootloader - -// Add strcmp_P if missing -#ifndef strcmp_P - #define strcmp_P(a, b) strcmp((a), (b)) +#pragma GCC diagnostic push +#if GCC_VERSION <= 50000 + #pragma GCC diagnostic ignored "-Wunused-function" #endif + +int freeMemory(); + +#pragma GCC diagnostic pop + +// ------------------------ +// MarlinHAL Class +// ------------------------ + +class MarlinHAL { +public: + + // Earliest possible init, before setup() + MarlinHAL() {} + + 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(ms); } + + // Watchdog + static void watchdog_init() IF_DISABLED(USE_WATCHDOG, {}); + static void watchdog_refresh() IF_DISABLED(USE_WATCHDOG, {}); + static bool watchdog_timed_out() IF_DISABLED(USE_WATCHDOG, { return false; }); + static void watchdog_clear_timeout_flag() IF_DISABLED(USE_WATCHDOG, {}); + + // Tasks, called from marlin.idle() + static void idletask(); + + // Reset + static uint8_t get_reset_source(); + static void clear_reset_source(); + + // Free SRAM + static int freeMemory() { return ::freeMemory(); } + + // + // ADC Methods + // + + using FilteredADC = LPC176x::ADC; + + // 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) { + FilteredADC::enable_channel(pin); + } + + // Begin ADC sampling on the given pin. Called from Temperature::isr! + static uint32_t adc_result; + static pin_t adc_pin; + + static void adc_start(const pin_t pin) { adc_pin = pin; } + + // Is the ADC ready for reading? + static bool adc_ready() { return LPC176x::adc_hardware.done(LPC176x::pin_get_adc_channel(adc_pin)); } + + // The current value of the ADC register + static uint16_t adc_value() { + adc_result = FilteredADC::read(adc_pin) >> (16 - HAL_ADC_RESOLUTION); // returns 16bit value, reduce to required bits + return uint16_t(adc_result); + } + + /** + * 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 corresponding to the provided pin + * All Hardware PWM pins will run at the same frequency and + * All Software PWM pins will 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/LPC1768/HAL_SPI.cpp b/Marlin/src/HAL/LPC1768/HAL_SPI.cpp index b3d2908ac9..15ace81dc2 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 @@ -55,27 +51,29 @@ #include #include +#include "../shared/HAL_SPI.h" + // ------------------------ // Public functions // ------------------------ -#if ENABLED(LPC_SOFTWARE_SPI) - - #include +#if ENABLED(SOFTWARE_SPI) // Software SPI - static uint8_t SPI_speed = 0; + #include + + static uint8_t SPI_speed = SPI_FULL_SPEED; static uint8_t spiTransfer(uint8_t b) { - return swSpiTransfer(b, SPI_speed, SCK_PIN, MISO_PIN, MOSI_PIN); + return swSpiTransfer(b, SPI_speed, SD_SCK_PIN, SD_MISO_PIN, SD_MOSI_PIN); } void spiBegin() { - swSpiBegin(SCK_PIN, MISO_PIN, MOSI_PIN); + swSpiBegin(SD_SCK_PIN, SD_MISO_PIN, SD_MOSI_PIN); } void spiInit(uint8_t spiRate) { - SPI_speed = swSpiInit(spiRate, SCK_PIN, MOSI_PIN); + SPI_speed = swSpiInit(spiRate, SD_SCK_PIN, SD_MOSI_PIN); } uint8_t spiRec() { return spiTransfer(0xFF); } @@ -87,12 +85,12 @@ void spiSend(uint8_t b) { (void)spiTransfer(b); } - void spiSend(const uint8_t* buf, size_t nbyte) { + void spiSend(const uint8_t *buf, size_t nbyte) { for (uint16_t i = 0; i < nbyte; i++) (void)spiTransfer(buf[i]); } - void spiSendBlock(uint8_t token, const uint8_t* buf) { + void spiSendBlock(uint8_t token, const uint8_t *buf) { (void)spiTransfer(token); for (uint16_t i = 0; i < 512; i++) (void)spiTransfer(buf[i]); @@ -100,14 +98,18 @@ #else - void spiBegin() { // setup SCK, MOSI & MISO pins for SSP0 - spiInit(SPI_SPEED); - } + #ifdef SD_SPI_SPEED + #define INIT_SPI_SPEED SD_SPI_SPEED + #else + #define INIT_SPI_SPEED SPI_FULL_SPEED + #endif + + void spiBegin() { spiInit(INIT_SPI_SPEED); } // Set up SCK, MOSI & MISO pins for SSP0 void spiInit(uint8_t spiRate) { - #if MISO_PIN == BOARD_SPI1_MISO_PIN + #if SD_MISO_PIN == BOARD_SPI1_MISO_PIN SPI.setModule(1); - #elif MISO_PIN == BOARD_SPI2_MISO_PIN + #elif SD_MISO_PIN == BOARD_SPI2_MISO_PIN SPI.setModule(2); #endif SPI.setDataSize(DATA_SIZE_8BIT); @@ -123,15 +125,13 @@ void spiSend(uint8_t b) { doio(b); } - void spiSend(const uint8_t* buf, size_t nbyte) { + void spiSend(const uint8_t *buf, size_t nbyte) { for (uint16_t i = 0; i < nbyte; i++) doio(buf[i]); } - void spiSend(uint32_t chan, byte b) { - } + void spiSend(uint32_t chan, byte b) {} - void spiSend(uint32_t chan, const uint8_t* buf, size_t nbyte) { - } + void spiSend(uint32_t chan, const uint8_t *buf, size_t nbyte) {} // Read single byte from SPI uint8_t spiRec() { return doio(0xFF); } @@ -143,24 +143,21 @@ for (uint16_t i = 0; i < nbyte; i++) buf[i] = doio(0xFF); } - uint8_t spiTransfer(uint8_t b) { - return doio(b); - } + uint8_t spiTransfer(uint8_t b) { return doio(b); } // Write from buffer to SPI - void spiSendBlock(uint8_t token, const uint8_t* buf) { + void spiSendBlock(uint8_t token, const uint8_t *buf) { (void)spiTransfer(token); for (uint16_t i = 0; i < 512; i++) (void)spiTransfer(buf[i]); } - /** Begin SPI transaction, set clock, bit order, data mode */ + // Begin SPI transaction, set clock, bit order, data mode void spiBeginTransaction(uint32_t spiClock, uint8_t bitOrder, uint8_t dataMode) { - // TODO: to be implemented - + // TODO: Implement this method } -#endif // LPC_SOFTWARE_SPI +#endif // SOFTWARE_SPI /** * @brief Wait until TXE (tx empty) flag is set and BSY (busy) flag unset. @@ -201,6 +198,15 @@ SPIClass::SPIClass(uint8_t device) { GPDMA_Init(); } +SPIClass::SPIClass(pin_t mosi, pin_t miso, pin_t sclk, pin_t ssel) { + #if BOARD_NR_SPI >= 1 + if (mosi == BOARD_SPI1_MOSI_PIN) SPIClass(1); + #endif + #if BOARD_NR_SPI >= 2 + if (mosi == BOARD_SPI2_MOSI_PIN) SPIClass(2); + #endif +} + void SPIClass::begin() { // Init the SPI pins in the first begin call if ((_currentSetting->spi_d == LPC_SSP0 && spiInitialised[0] == false) || @@ -263,8 +269,9 @@ uint16_t SPIClass::transfer16(const uint16_t data) { } void SPIClass::end() { - // SSP_Cmd(_currentSetting->spi_d, DISABLE); // stop device or SSP_DeInit? - SSP_DeInit(_currentSetting->spi_d); + // Neither is needed for Marlin + //SSP_Cmd(_currentSetting->spi_d, DISABLE); + //SSP_DeInit(_currentSetting->spi_d); } void SPIClass::send(uint8_t data) { @@ -307,8 +314,16 @@ void SPIClass::dmaSend(void *buf, uint16_t length, bool minc) { // Enable DMA GPDMA_ChannelCmd(0, ENABLE); + /** + * Observed behaviour on normal data transfer completion (SKR 1.3 board / LPC1768 MCU) + * GPDMA_STAT_INTTC flag is SET + * GPDMA_STAT_INTERR flag is NOT SET + * GPDMA_STAT_RAWINTTC flag is NOT SET + * GPDMA_STAT_RAWINTERR flag is SET + */ + // Wait for data transfer - while (!GPDMA_IntGetStatus(GPDMA_STAT_RAWINTTC, 0) && !GPDMA_IntGetStatus(GPDMA_STAT_RAWINTERR, 0)) { } + while (!GPDMA_IntGetStatus(GPDMA_STAT_INTTC, 0) && !GPDMA_IntGetStatus(GPDMA_STAT_INTERR, 0)) {} // Clear err and int GPDMA_ClearIntPending (GPDMA_STATCLR_INTTC, 0); @@ -322,6 +337,43 @@ void SPIClass::dmaSend(void *buf, uint16_t length, bool minc) { SSP_DMACmd(_currentSetting->spi_d, SSP_DMA_TX, DISABLE); } +void SPIClass::dmaSendAsync(void *buf, uint16_t length, bool minc) { + //TODO: LPC dma can only write 0xFFF bytes at once. + GPDMA_Channel_CFG_Type GPDMACfg; + + /* Configure GPDMA channel 0 -------------------------------------------------------------*/ + /* DMA Channel 0 */ + GPDMACfg.ChannelNum = 0; + // Source memory + GPDMACfg.SrcMemAddr = (uint32_t)buf; + // Destination memory - Not used + GPDMACfg.DstMemAddr = 0; + // Transfer size + GPDMACfg.TransferSize = length; + // Transfer width + GPDMACfg.TransferWidth = (_currentSetting->dataSize == DATA_SIZE_16BIT) ? GPDMA_WIDTH_HALFWORD : GPDMA_WIDTH_BYTE; + // Transfer type + GPDMACfg.TransferType = GPDMA_TRANSFERTYPE_M2P; + // Source connection - unused + GPDMACfg.SrcConn = 0; + // Destination connection + GPDMACfg.DstConn = (_currentSetting->spi_d == LPC_SSP0) ? GPDMA_CONN_SSP0_Tx : GPDMA_CONN_SSP1_Tx; + + GPDMACfg.DMALLI = 0; + + // Enable dma on SPI + SSP_DMACmd(_currentSetting->spi_d, SSP_DMA_TX, ENABLE); + + // Only increase memory if minc is true + GPDMACfg.MemoryIncrease = (minc ? GPDMA_DMACCxControl_SI : 0); + + // Setup channel with given parameter + GPDMA_Setup(&GPDMACfg); + + // Enable DMA + GPDMA_ChannelCmd(0, ENABLE); +} + uint16_t SPIClass::read() { return SSP_ReceiveData(_currentSetting->spi_d); } @@ -330,25 +382,15 @@ void SPIClass::read(uint8_t *buf, uint32_t len) { for (uint16_t i = 0; i < len; i++) buf[i] = transfer(0xFF); } -void SPIClass::setClock(uint32_t clock) { - _currentSetting->clock = clock; -} +void SPIClass::setClock(uint32_t clock) { _currentSetting->clock = clock; } -void SPIClass::setModule(uint8_t device) { - _currentSetting = &_settings[device - 1];// SPI channels are called 1 2 and 3 but the array is zero indexed -} +void SPIClass::setModule(uint8_t device) { _currentSetting = &_settings[device - 1]; } // SPI channels are called 1, 2, and 3 but the array is zero-indexed -void SPIClass::setBitOrder(uint8_t bitOrder) { - _currentSetting->bitOrder = bitOrder; -} +void SPIClass::setBitOrder(uint8_t bitOrder) { _currentSetting->bitOrder = bitOrder; } -void SPIClass::setDataMode(uint8_t dataMode) { - _currentSetting->dataMode = dataMode; -} +void SPIClass::setDataMode(uint8_t dataMode) { _currentSetting->dataMode = dataMode; } -void SPIClass::setDataSize(uint32_t ds) { - _currentSetting->dataSize = ds; -} +void SPIClass::setDataSize(uint32_t dataSize) { _currentSetting->dataSize = dataSize; } /** * Set up/tear down @@ -356,8 +398,8 @@ void SPIClass::setDataSize(uint32_t ds) { void SPIClass::updateSettings() { //SSP_DeInit(_currentSetting->spi_d); //todo: need force de init?! - // divide PCLK by 2 for SSP0 - CLKPWR_SetPCLKDiv(_currentSetting->spi_d == LPC_SSP0 ? CLKPWR_PCLKSEL_SSP0 : CLKPWR_PCLKSEL_SSP1, CLKPWR_PCLKSEL_CCLK_DIV_2); + // Divide PCLK by 2 for SSP0 + //CLKPWR_SetPCLKDiv(_currentSetting->spi_d == LPC_SSP0 ? CLKPWR_PCLKSEL_SSP0 : CLKPWR_PCLKSEL_SSP1, CLKPWR_PCLKSEL_CCLK_DIV_2); SSP_CFG_Type HW_SPI_init; // data structure to hold init values SSP_ConfigStructInit(&HW_SPI_init); // set values for SPI mode @@ -396,9 +438,9 @@ void SPIClass::updateSettings() { SSP_Init(_currentSetting->spi_d, &HW_SPI_init); // puts the values into the proper bits in the SSP0 registers } -#if MISO_PIN == BOARD_SPI1_MISO_PIN +#if SD_MISO_PIN == BOARD_SPI1_MISO_PIN SPIClass SPI(1); -#elif MISO_PIN == BOARD_SPI2_MISO_PIN +#elif SD_MISO_PIN == BOARD_SPI2_MISO_PIN SPIClass SPI(2); #endif diff --git a/Marlin/src/HAL/LPC1768/MarlinSPI.h b/Marlin/src/HAL/LPC1768/MarlinSPI.h new file mode 100644 index 0000000000..fab245f904 --- /dev/null +++ b/Marlin/src/HAL/LPC1768/MarlinSPI.h @@ -0,0 +1,45 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2020 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 + +/** + * Marlin currently requires 3 SPI classes: + * + * SPIClass: + * This class is normally provided by frameworks and has a semi-default interface. + * This is needed because some libraries reference it globally. + * + * SPISettings: + * Container for SPI configs for SPIClass. As above, libraries may reference it globally. + * + * These two classes are often provided by frameworks so we cannot extend them to add + * useful methods for Marlin. + * + * MarlinSPI: + * Provides the default SPIClass interface plus some Marlin goodies such as a simplified + * interface for SPI DMA transfer. + * + */ + +using MarlinSPI = SPIClass; diff --git a/Marlin/src/HAL/LPC1768/MarlinSerial.cpp b/Marlin/src/HAL/LPC1768/MarlinSerial.cpp index 5374e005d3..291eba68a7 100644 --- a/Marlin/src/HAL/LPC1768/MarlinSerial.cpp +++ b/Marlin/src/HAL/LPC1768/MarlinSerial.cpp @@ -21,35 +21,53 @@ */ #ifdef TARGET_LPC1768 -#include "../../inc/MarlinConfigPre.h" #include "MarlinSerial.h" -#if USING_SERIAL_0 - MarlinSerial MSerial(LPC_UART0); - extern "C" void UART0_IRQHandler() { - MSerial.IRQHandler(); - } +#include "../../inc/MarlinConfig.h" + +DefaultSerial1 USBSerial(false, UsbSerial); + +#if USING_HW_SERIAL0 + MarlinSerial _MSerial0(LPC_UART0); + MSerialT MSerial0(true, _MSerial0); + extern "C" void UART0_IRQHandler() { _MSerial0.IRQHandler(); } +#endif +#if USING_HW_SERIAL1 + MarlinSerial _MSerial1((LPC_UART_TypeDef *) LPC_UART1); + MSerialT MSerial1(true, _MSerial1); + extern "C" void UART1_IRQHandler() { _MSerial1.IRQHandler(); } +#endif +#if USING_HW_SERIAL2 + MarlinSerial _MSerial2(LPC_UART2); + MSerialT MSerial2(true, _MSerial2); + extern "C" void UART2_IRQHandler() { _MSerial2.IRQHandler(); } +#endif +#if USING_HW_SERIAL3 + MarlinSerial _MSerial3(LPC_UART3); + MSerialT MSerial3(true, _MSerial3); + extern "C" void UART3_IRQHandler() { _MSerial3.IRQHandler(); } #endif -#if USING_SERIAL_1 - MarlinSerial MSerial1((LPC_UART_TypeDef *) LPC_UART1); - extern "C" void UART1_IRQHandler() { - MSerial1.IRQHandler(); - } -#endif +#if ENABLED(EMERGENCY_PARSER) -#if USING_SERIAL_2 - MarlinSerial MSerial2(LPC_UART2); - extern "C" void UART2_IRQHandler() { - MSerial2.IRQHandler(); + bool MarlinSerial::recv_callback(const char c) { + // Need to figure out which serial port we are and react in consequence (Marlin does not have CONTAINER_OF macro) + if (false) {} + #if USING_HW_SERIAL0 + else if (this == &_MSerial0) emergency_parser.update(MSerial0.emergency_state, c); + #endif + #if USING_HW_SERIAL1 + else if (this == &_MSerial1) emergency_parser.update(MSerial1.emergency_state, c); + #endif + #if USING_HW_SERIAL2 + else if (this == &_MSerial2) emergency_parser.update(MSerial2.emergency_state, c); + #endif + #if USING_HW_SERIAL3 + else if (this == &_MSerial3) emergency_parser.update(MSerial3.emergency_state, c); + #endif + return true; } -#endif -#if USING_SERIAL_3 - MarlinSerial MSerial3(LPC_UART3); - extern "C" void UART3_IRQHandler() { - MSerial3.IRQHandler(); - } #endif #endif // TARGET_LPC1768 diff --git a/Marlin/src/HAL/LPC1768/MarlinSerial.h b/Marlin/src/HAL/LPC1768/MarlinSerial.h index 8d6b64378a..04110e4aff 100644 --- a/Marlin/src/HAL/LPC1768/MarlinSerial.h +++ b/Marlin/src/HAL/LPC1768/MarlinSerial.h @@ -21,6 +21,7 @@ */ #pragma once +#include #include #include @@ -28,40 +29,44 @@ #if ENABLED(EMERGENCY_PARSER) #include "../../feature/e_parser.h" #endif +#include "../../core/serial_hook.h" -#ifndef SERIAL_PORT - #define SERIAL_PORT 0 -#endif -#ifndef RX_BUFFER_SIZE - #define RX_BUFFER_SIZE 128 -#endif -#ifndef TX_BUFFER_SIZE - #define TX_BUFFER_SIZE 32 +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) - #if ENABLED(EMERGENCY_PARSER) - , emergency_state(EmergencyParser::State::EP_RESET) - #endif - { } + MarlinSerial(LPC_UART_TypeDef *UARTx) : HardwareSerial(UARTx) { } void end() {} - #if ENABLED(EMERGENCY_PARSER) - bool recv_callback(const char c) override { - emergency_parser.update(emergency_state, c); - return true; // do not discard character - } + uint8_t availableForWrite(void) { /* flushTX(); */ return TX_BUFFER_SIZE; } - EmergencyParser::State emergency_state; - static inline bool emergency_parser_enabled() { return true; } + #if ENABLED(EMERGENCY_PARSER) + bool recv_callback(const char c) override; #endif }; -extern MarlinSerial MSerial; -extern MarlinSerial MSerial1; -extern MarlinSerial MSerial2; -extern MarlinSerial MSerial3; +// On LPC176x framework, HardwareSerial does not implement the same interface as Arduino's Serial, so overloads +// of 'available' and 'read' method are not used in this multiple inheritance scenario. +// Instead, use a ForwardSerial here that adapts the interface. +typedef ForwardSerial1Class MSerialT; +extern MSerialT MSerial0; +extern MSerialT MSerial1; +extern MSerialT MSerial2; +extern MSerialT MSerial3; + +// Consequently, we can't use a RuntimeSerial either. The workaround would be to use +// a RuntimeSerial> type here. Ignore for now until it's actually required. +#if ENABLED(SERIAL_RUNTIME_HOOK) + #error "SERIAL_RUNTIME_HOOK is not yet supported for LPC176x." +#endif diff --git a/Marlin/src/HAL/LPC1768/MinSerial.cpp b/Marlin/src/HAL/LPC1768/MinSerial.cpp new file mode 100644 index 0000000000..368bcb5259 --- /dev/null +++ b/Marlin/src/HAL/LPC1768/MinSerial.cpp @@ -0,0 +1,51 @@ +/** + * 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 . + * + */ +#ifdef TARGET_LPC1768 + +#include "../../inc/MarlinConfig.h" +#include "HAL.h" + +#if ENABLED(POSTMORTEM_DEBUGGING) + +#include "../shared/MinSerial.h" +#include + +static void TX(char c) { _DBC(c); } +void install_min_serial() { HAL_min_serial_out = &TX; } + +#if DISABLED(DYNAMIC_VECTORTABLE) + extern "C" { + __attribute__((naked)) void JumpHandler_ASM() { + __asm__ __volatile__ ( + "b CommonHandler_ASM\n" + ); + } + void __attribute__((naked, alias("JumpHandler_ASM"))) HardFault_Handler(); + void __attribute__((naked, alias("JumpHandler_ASM"))) BusFault_Handler(); + void __attribute__((naked, alias("JumpHandler_ASM"))) UsageFault_Handler(); + void __attribute__((naked, alias("JumpHandler_ASM"))) MemManage_Handler(); + void __attribute__((naked, alias("JumpHandler_ASM"))) NMI_Handler(); + } +#endif + +#endif // POSTMORTEM_DEBUGGING +#endif // TARGET_LPC1768 diff --git a/Marlin/src/HAL/LPC1768/Servo.h b/Marlin/src/HAL/LPC1768/Servo.h index eb12fd20f4..d7f0a2a555 100644 --- a/Marlin/src/HAL/LPC1768/Servo.h +++ b/Marlin/src/HAL/LPC1768/Servo.h @@ -39,7 +39,6 @@ * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -#pragma once /** * Based on "servo.h - Interrupt driven Servo library for Arduino using 16 bit timers - @@ -50,6 +49,8 @@ #include +#include "../../MarlinCore.h" + class libServo: public Servo { public: void move(const int value) { @@ -65,4 +66,5 @@ class libServo: public Servo { } }; -#define HAL_SERVO_LIB libServo +class libServo; +typedef libServo hal_servo_t; diff --git a/Marlin/src/HAL/LPC1768/eeprom_flash.cpp b/Marlin/src/HAL/LPC1768/eeprom/eeprom_flash.cpp similarity index 86% rename from Marlin/src/HAL/LPC1768/eeprom_flash.cpp rename to Marlin/src/HAL/LPC1768/eeprom/eeprom_flash.cpp index 2558486375..e24b0fdbc0 100644 --- a/Marlin/src/HAL/LPC1768/eeprom_flash.cpp +++ b/Marlin/src/HAL/LPC1768/eeprom/eeprom_flash.cpp @@ -25,7 +25,7 @@ * Emulate EEPROM storage using Flash Memory * * Use a single 32K flash sector to store EEPROM data. To reduce the - * number of erase operations a simple "levelling" scheme is used that + * number of erase operations a simple "leveling" scheme is used that * maintains a number of EEPROM "slots" within the larger flash sector. * Each slot is used in turn and the entire sector is only erased when all * slots have been used. @@ -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 @@ -61,7 +61,7 @@ static uint8_t ram_eeprom[MARLIN_EEPROM_SIZE] __attribute__((aligned(4))) = {0}; static bool eeprom_dirty = false; static int current_slot = 0; -size_t PersistentStore::capacity() { return MARLIN_EEPROM_SIZE; } +size_t PersistentStore::capacity() { return MARLIN_EEPROM_SIZE - eeprom_exclude_size; } bool PersistentStore::access_start() { uint32_t first_nblank_loc, first_nblank_val; @@ -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; @@ -112,16 +112,18 @@ bool PersistentStore::access_finish() { } bool PersistentStore::write_data(int &pos, const uint8_t *value, size_t size, uint16_t *crc) { - for (size_t i = 0; i < size; i++) ram_eeprom[pos + i] = value[i]; + const int p = REAL_EEPROM_ADDR(pos); + for (size_t i = 0; i < size; i++) ram_eeprom[p + i] = value[i]; eeprom_dirty = true; crc16(crc, value, size); pos += size; return false; // return true for any error } -bool PersistentStore::read_data(int &pos, uint8_t* value, size_t size, uint16_t *crc, const bool writing/*=true*/) { +bool PersistentStore::read_data(int &pos, uint8_t *value, size_t size, uint16_t *crc, const bool writing/*=true*/) { + const int p = REAL_EEPROM_ADDR(pos); const uint8_t * const buff = writing ? &value[0] : &ram_eeprom[pos]; - if (writing) for (size_t i = 0; i < size; i++) value[i] = ram_eeprom[pos + i]; + if (writing) for (size_t i = 0; i < size; i++) value[i] = ram_eeprom[p + i]; crc16(crc, buff, size); pos += size; return false; // return true for any error diff --git a/Marlin/src/HAL/LPC1768/eeprom_sdcard.cpp b/Marlin/src/HAL/LPC1768/eeprom/eeprom_sdcard.cpp similarity index 83% rename from Marlin/src/HAL/LPC1768/eeprom_sdcard.cpp rename to Marlin/src/HAL/LPC1768/eeprom/eeprom_sdcard.cpp index 9f2475f490..5bf2d353b6 100644 --- a/Marlin/src/HAL/LPC1768/eeprom_sdcard.cpp +++ b/Marlin/src/HAL/LPC1768/eeprom/eeprom_sdcard.cpp @@ -1,10 +1,9 @@ /** * Marlin 3D Printer Firmware - * * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com - * Copyright (c) 2015-2016 Nico Tonnhofer wurstnase.reprap@gmail.com - * Copyright (c) 2016 Victor Perez victor_pv@hotmail.com + * + * 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 @@ -20,13 +19,20 @@ * along with this program. If not, see . * */ + +/** + * Implementation of EEPROM settings in SD Card + */ + #ifdef TARGET_LPC1768 -#include "../../inc/MarlinConfig.h" +#include "../../../inc/MarlinConfig.h" #if ENABLED(SDCARD_EEPROM_EMULATION) -#include "../shared/eeprom_api.h" +//#define DEBUG_SD_EEPROM_EMULATION + +#include "../../shared/eeprom_api.h" #include #include @@ -38,25 +44,27 @@ FATFS fat_fs; FIL eeprom_file; bool eeprom_file_open = false; +#define EEPROM_FILENAME "eeprom.dat" #ifndef MARLIN_EEPROM_SIZE #define MARLIN_EEPROM_SIZE size_t(0x1000) // 4KiB of Emulated EEPROM #endif -size_t PersistentStore::capacity() { return MARLIN_EEPROM_SIZE; } + +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(); return false; } - FRESULT res = f_open(&eeprom_file, "eeprom.dat", FA_OPEN_ALWAYS | FA_WRITE | FA_READ); + FRESULT res = f_open(&eeprom_file, EEPROM_FILENAME, FA_OPEN_ALWAYS | FA_WRITE | FA_READ); if (res) MSC_Release_Lock(); if (res == FR_OK) { 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++; @@ -81,19 +89,14 @@ bool PersistentStore::access_finish() { // This extra chit-chat goes away soon, but is helpful for now // to see errors that are happening in read_data / write_data static void debug_rw(const bool write, int &pos, const uint8_t *value, const size_t size, const FRESULT s, const size_t total=0) { - PGM_P const rw_str = write ? PSTR("write") : PSTR("read"); - SERIAL_CHAR(' '); - serialprintPGM(rw_str); - SERIAL_ECHOLNPAIR("_data(", pos, ",", int(value), ",", int(size), ", ...)"); - if (total) { - SERIAL_ECHOPGM(" f_"); - serialprintPGM(rw_str); - SERIAL_ECHOPAIR("()=", int(s), "\n size=", int(size), "\n bytes_"); - serialprintPGM(write ? PSTR("written=") : PSTR("read=")); - SERIAL_ECHOLN(total); - } - else - SERIAL_ECHOLNPAIR(" f_lseek()=", int(s)); + #if ENABLED(DEBUG_SD_EEPROM_EMULATION) + FSTR_P const rw_str = write ? F("write") : F("read"); + SERIAL_ECHOLN(C(' '), rw_str, F("_data("), pos, C(','), *value, C(','), size, F(", ...)")); + if (total) + SERIAL_ECHOLN(F(" f_"), rw_str, F("()="), s, F("\n size="), size, F("\n bytes_"), write ? F("written=") : F("read="), total); + else + SERIAL_ECHOLNPGM(" f_lseek()=", s); + #endif } // File function return codes for type FRESULT. This goes away soon, but @@ -143,7 +146,7 @@ bool PersistentStore::write_data(int &pos, const uint8_t *value, size_t size, ui return bytes_written != size; // return true for any error } -bool PersistentStore::read_data(int &pos, uint8_t* value, const size_t size, uint16_t *crc, const bool writing/*=true*/) { +bool PersistentStore::read_data(int &pos, uint8_t *value, const size_t size, uint16_t *crc, const bool writing/*=true*/) { if (!eeprom_file_open) return true; UINT bytes_read = 0; FRESULT s; diff --git a/Marlin/src/HAL/LPC1768/eeprom_wired.cpp b/Marlin/src/HAL/LPC1768/eeprom/eeprom_wired.cpp similarity index 74% rename from Marlin/src/HAL/LPC1768/eeprom_wired.cpp rename to Marlin/src/HAL/LPC1768/eeprom/eeprom_wired.cpp index 16c15eaf00..6a5d90bd02 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,45 +30,41 @@ * 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 // 32KB‬ + #define MARLIN_EEPROM_SIZE 0x8000 // 32K #endif -size_t PersistentStore::capacity() { return MARLIN_EEPROM_SIZE; } +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; - - // EEPROM has only ~100,000 write cycles, - // so only write bytes that have changed! - uint8_t * const p = (uint8_t * const)pos; - if (v != eeprom_read_byte(p)) { + uint8_t * const p = (uint8_t * const)REAL_EEPROM_ADDR(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*/) { +bool PersistentStore::read_data(int &pos, uint8_t *value, size_t size, uint16_t *crc, const bool writing/*=true*/) { do { // Read from external EEPROM - const uint8_t c = eeprom_read_byte((uint8_t*)pos); - + const uint8_t c = eeprom_read_byte((uint8_t*)REAL_EEPROM_ADDR(pos)); if (writing) *value = c; crc16(crc, &c, 1); pos++; diff --git a/Marlin/src/HAL/LPC1768/endstop_interrupts.h b/Marlin/src/HAL/LPC1768/endstop_interrupts.h index b0d0c0ec5c..2c75fe6986 100644 --- a/Marlin/src/HAL/LPC1768/endstop_interrupts.h +++ b/Marlin/src/HAL/LPC1768/endstop_interrupts.h @@ -44,82 +44,178 @@ void setup_endstop_interrupts() { #define _ATTACH(P) attachInterrupt(digitalPinToInterrupt(P), endstop_ISR, CHANGE) #define LPC1768_PIN_INTERRUPT_M(pin) ((pin >> 0x5 & 0x7) == 0 || (pin >> 0x5 & 0x7) == 2) - #if HAS_X_MAX + #if USE_X_MAX #if !LPC1768_PIN_INTERRUPT_M(X_MAX_PIN) - #error "X_MAX_PIN is not INTERRUPT-capable." + #error "X_MAX_PIN is not INTERRUPT-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." #endif _ATTACH(X_MAX_PIN); #endif - #if HAS_X_MIN + #if USE_X_MIN #if !LPC1768_PIN_INTERRUPT_M(X_MIN_PIN) - #error "X_MIN_PIN is not INTERRUPT-capable." + #error "X_MIN_PIN is not INTERRUPT-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." #endif _ATTACH(X_MIN_PIN); #endif - #if HAS_Y_MAX + #if USE_Y_MAX #if !LPC1768_PIN_INTERRUPT_M(Y_MAX_PIN) - #error "Y_MAX_PIN is not INTERRUPT-capable." + #error "Y_MAX_PIN is not INTERRUPT-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." #endif _ATTACH(Y_MAX_PIN); #endif - #if HAS_Y_MIN + #if USE_Y_MIN #if !LPC1768_PIN_INTERRUPT_M(Y_MIN_PIN) - #error "Y_MIN_PIN is not INTERRUPT-capable." + #error "Y_MIN_PIN is not INTERRUPT-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." #endif _ATTACH(Y_MIN_PIN); #endif - #if HAS_Z_MAX + #if USE_Z_MAX #if !LPC1768_PIN_INTERRUPT_M(Z_MAX_PIN) - #error "Z_MAX_PIN is not INTERRUPT-capable." + #error "Z_MAX_PIN is not INTERRUPT-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." #endif _ATTACH(Z_MAX_PIN); #endif - #if HAS_Z_MIN + #if USE_Z_MIN #if !LPC1768_PIN_INTERRUPT_M(Z_MIN_PIN) - #error "Z_MIN_PIN is not INTERRUPT-capable." + #error "Z_MIN_PIN is not INTERRUPT-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." #endif _ATTACH(Z_MIN_PIN); #endif - #if HAS_Z2_MAX + #if USE_X2_MAX + #if !LPC1768_PIN_INTERRUPT_M(X2_MAX_PIN) + #error "X2_MAX_PIN is not INTERRUPT-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #endif + _ATTACH(X2_MAX_PIN); + #endif + #if USE_X2_MIN + #if !LPC1768_PIN_INTERRUPT_M(X2_MIN_PIN) + #error "X2_MIN_PIN is not INTERRUPT-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #endif + _ATTACH(X2_MIN_PIN); + #endif + #if USE_Y2_MAX + #if !LPC1768_PIN_INTERRUPT_M(Y2_MAX_PIN) + #error "Y2_MAX_PIN is not INTERRUPT-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #endif + _ATTACH(Y2_MAX_PIN); + #endif + #if USE_Y2_MIN + #if !LPC1768_PIN_INTERRUPT_M(Y2_MIN_PIN) + #error "Y2_MIN_PIN is not INTERRUPT-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #endif + _ATTACH(Y2_MIN_PIN); + #endif + #if USE_Z2_MAX #if !LPC1768_PIN_INTERRUPT_M(Z2_MAX_PIN) - #error "Z2_MAX_PIN is not INTERRUPT-capable." + #error "Z2_MAX_PIN is not INTERRUPT-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." #endif _ATTACH(Z2_MAX_PIN); #endif - #if HAS_Z2_MIN + #if USE_Z2_MIN #if !LPC1768_PIN_INTERRUPT_M(Z2_MIN_PIN) - #error "Z2_MIN_PIN is not INTERRUPT-capable." + #error "Z2_MIN_PIN is not INTERRUPT-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." #endif _ATTACH(Z2_MIN_PIN); #endif - #if HAS_Z3_MAX + #if USE_Z3_MAX #if !LPC1768_PIN_INTERRUPT_M(Z3_MAX_PIN) - #error "Z3_MIN_PIN is not INTERRUPT-capable." + #error "Z3_MIN_PIN is not INTERRUPT-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." #endif _ATTACH(Z3_MAX_PIN); #endif - #if HAS_Z3_MIN + #if USE_Z3_MIN #if !LPC1768_PIN_INTERRUPT_M(Z3_MIN_PIN) - #error "Z3_MIN_PIN is not INTERRUPT-capable." + #error "Z3_MIN_PIN is not INTERRUPT-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." #endif _ATTACH(Z3_MIN_PIN); #endif - #if HAS_Z4_MAX + #if USE_Z4_MAX #if !LPC1768_PIN_INTERRUPT_M(Z4_MAX_PIN) - #error "Z4_MIN_PIN is not INTERRUPT-capable." + #error "Z4_MIN_PIN is not INTERRUPT-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." #endif _ATTACH(Z4_MAX_PIN); #endif - #if HAS_Z4_MIN + #if USE_Z4_MIN #if !LPC1768_PIN_INTERRUPT_M(Z4_MIN_PIN) - #error "Z4_MIN_PIN is not INTERRUPT-capable." + #error "Z4_MIN_PIN is not INTERRUPT-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." #endif _ATTACH(Z4_MIN_PIN); #endif - #if HAS_Z_MIN_PROBE_PIN + #if USE_Z_MIN_PROBE #if !LPC1768_PIN_INTERRUPT_M(Z_MIN_PROBE_PIN) - #error "Z_MIN_PROBE_PIN is not INTERRUPT-capable." + #error "Z_MIN_PROBE_PIN is not INTERRUPT-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." #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." + #endif + _ATTACH(I_MAX_PIN); + #elif USE_I_MIN + #if !LPC1768_PIN_INTERRUPT_M(I_MIN_PIN) + #error "I_MIN_PIN is not INTERRUPT-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #endif + _ATTACH(I_MIN_PIN); + #endif + #if USE_J_MAX + #if !LPC1768_PIN_INTERRUPT_M(J_MAX_PIN) + #error "J_MAX_PIN is not INTERRUPT-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #endif + _ATTACH(J_MAX_PIN); + #elif USE_J_MIN + #if !LPC1768_PIN_INTERRUPT_M(J_MIN_PIN) + #error "J_MIN_PIN is not INTERRUPT-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #endif + _ATTACH(J_MIN_PIN); + #endif + #if USE_K_MAX + #if !LPC1768_PIN_INTERRUPT_M(K_MAX_PIN) + #error "K_MAX_PIN is not INTERRUPT-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #endif + _ATTACH(K_MAX_PIN); + #elif USE_K_MIN + #if !LPC1768_PIN_INTERRUPT_M(K_MIN_PIN) + #error "K_MIN_PIN is not INTERRUPT-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #endif + _ATTACH(K_MIN_PIN); + #endif + #if USE_U_MAX + #if !LPC1768_PIN_INTERRUPT_M(U_MAX_PIN) + #error "U_MAX_PIN is not INTERRUPT-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #endif + _ATTACH(U_MAX_PIN); + #elif USE_U_MIN + #if !LPC1768_PIN_INTERRUPT_M(U_MIN_PIN) + #error "U_MIN_PIN is not INTERRUPT-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #endif + _ATTACH(U_MIN_PIN); + #endif + #if USE_V_MAX + #if !LPC1768_PIN_INTERRUPT_M(V_MAX_PIN) + #error "V_MAX_PIN is not INTERRUPT-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #endif + _ATTACH(V_MAX_PIN); + #elif USE_V_MIN + #if !LPC1768_PIN_INTERRUPT_M(V_MIN_PIN) + #error "V_MIN_PIN is not INTERRUPT-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #endif + _ATTACH(V_MIN_PIN); + #endif + #if USE_W_MAX + #if !LPC1768_PIN_INTERRUPT_M(W_MAX_PIN) + #error "W_MAX_PIN is not INTERRUPT-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #endif + _ATTACH(W_MAX_PIN); + #elif USE_W_MIN + #if !LPC1768_PIN_INTERRUPT_M(W_MIN_PIN) + #error "W_MIN_PIN is not INTERRUPT-capable. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #endif + _ATTACH(W_MIN_PIN); + #endif } diff --git a/Marlin/src/HAL/LPC1768/fast_pwm.cpp b/Marlin/src/HAL/LPC1768/fast_pwm.cpp index dd440b5e77..7cb212f5f9 100644 --- a/Marlin/src/HAL/LPC1768/fast_pwm.cpp +++ b/Marlin/src/HAL/LPC1768/fast_pwm.cpp @@ -21,19 +21,19 @@ */ #ifdef TARGET_LPC1768 -#include "../../inc/MarlinConfigPre.h" - -#if NEEDS_HARDWARE_PWM // Specific meta-flag for features that mandate PWM - +#include "../../inc/MarlinConfig.h" #include -void set_pwm_frequency(const pin_t pin, int f_desired) { +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)) { + 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) { LPC176x::pwm_set_frequency(pin, f_desired); } -void set_pwm_duty(const pin_t pin, const uint16_t v, const uint16_t v_size/*=255*/, const bool invert/*=false*/) { - LPC176x::pwm_write_ratio(pin, invert ? 1.0f - (float)v / v_size : (float)v / v_size); -} - -#endif // NEEDS_HARDWARE_PWM #endif // TARGET_LPC1768 diff --git a/Marlin/src/HAL/LPC1768/fastio.h b/Marlin/src/HAL/LPC1768/fastio.h index c553ffb182..4858334080 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_LCD.h b/Marlin/src/HAL/LPC1768/inc/Conditionals_LCD.h index 32ef908d63..65b019b261 100644 --- a/Marlin/src/HAL/LPC1768/inc/Conditionals_LCD.h +++ b/Marlin/src/HAL/LPC1768/inc/Conditionals_LCD.h @@ -21,6 +21,6 @@ */ #pragma once -#if HAS_FSMC_TFT - #error "Sorry! FSMC TFT displays are not current available for HAL/LPC1768." +#ifndef SERIAL_PORT + #define SERIAL_PORT 0 #endif diff --git a/Marlin/src/HAL/LPC1768/inc/Conditionals_adv.h b/Marlin/src/HAL/LPC1768/inc/Conditionals_adv.h index 5f1c4b1601..9d04c4f787 100644 --- a/Marlin/src/HAL/LPC1768/inc/Conditionals_adv.h +++ b/Marlin/src/HAL/LPC1768/inc/Conditionals_adv.h @@ -20,3 +20,14 @@ * */ #pragma once + +#if DISABLED(NO_SD_HOST_DRIVE) + #define HAS_SD_HOST_DRIVE 1 +#endif + +#ifndef RX_BUFFER_SIZE + #define RX_BUFFER_SIZE 128 +#endif +#ifndef TX_BUFFER_SIZE + #define TX_BUFFER_SIZE 32 +#endif diff --git a/Marlin/src/HAL/LPC1768/inc/Conditionals_post.h b/Marlin/src/HAL/LPC1768/inc/Conditionals_post.h index ce6d3fdde2..a1b4dd5099 100644 --- a/Marlin/src/HAL/LPC1768/inc/Conditionals_post.h +++ b/Marlin/src/HAL/LPC1768/inc/Conditionals_post.h @@ -23,6 +23,12 @@ #if USE_FALLBACK_EEPROM #define FLASH_EEPROM_EMULATION -#elif EITHER(I2C_EEPROM, SPI_EEPROM) +#elif ANY(I2C_EEPROM, SPI_EEPROM) #define USE_SHARED_EEPROM 1 #endif + +// LPC1768 boards seem to lose steps when saving to EEPROM during print (issue #20785) +// TODO: Which other boards are incompatible? +#if ALL(MCU_LPC1768, FLASH_EEPROM_EMULATION) && PRINTCOUNTER_SAVE_INTERVAL > 0 + #define PRINTCOUNTER_SYNC +#endif diff --git a/Marlin/src/HAL/LPC1768/inc/Conditionals_type.h b/Marlin/src/HAL/LPC1768/inc/Conditionals_type.h new file mode 100644 index 0000000000..82f95a1035 --- /dev/null +++ b/Marlin/src/HAL/LPC1768/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/LPC1768/inc/SanityCheck.h b/Marlin/src/HAL/LPC1768/inc/SanityCheck.h index f5051d32a1..5a5617f4ac 100644 --- a/Marlin/src/HAL/LPC1768/inc/SanityCheck.h +++ b/Marlin/src/HAL/LPC1768/inc/SanityCheck.h @@ -24,14 +24,14 @@ #if PIO_PLATFORM_VERSION < 1001 #error "nxplpc-arduino-lpc176x package is out of date, Please update the PlatformIO platforms, frameworks and libraries. You may need to remove the platform and let it reinstall automatically." #endif -#if PIO_FRAMEWORK_VERSION < 2005 +#if PIO_FRAMEWORK_VERSION < 2006 #error "framework-arduino-lpc176x package is out of date, Please update the PlatformIO platforms, frameworks and libraries." #endif /** * Detect an old pins file by checking for old ADC pins values. */ -#define _OLD_TEMP_PIN(P) PIN_EXISTS(P) && _CAT(P,_PIN) <= 7 && _CAT(P,_PIN) != 2 && _CAT(P,_PIN) != 3 +#define _OLD_TEMP_PIN(P) PIN_EXISTS(P) && _CAT(P,_PIN) <= 7 && !WITHIN(_CAT(P,_PIN), TERN(LPC1768_IS_SKRV1_3, 0, 2), 3) // Include P0_00 and P0_01 for SKR V1.3 board #if _OLD_TEMP_PIN(TEMP_BED) #error "TEMP_BED_PIN must be defined using the Pn_nn or Pn_nn_An format. (See the included pins files)." #elif _OLD_TEMP_PIN(TEMP_0) @@ -67,16 +67,20 @@ static_assert(!(NUM_SERVOS && ENABLED(FAST_PWM_FAN)), "BLTOUCH and Servos are in * Test LPC176x-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) +//#if ENABLED(SPINDLE_LASER_USE_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 MB(RAMPS_14_RE_ARM_EFB, RAMPS_14_RE_ARM_EEB, RAMPS_14_RE_ARM_EFF, RAMPS_14_RE_ARM_EEF, RAMPS_14_RE_ARM_SF) - #if ENABLED(REPRAP_DISCOUNT_FULL_GRAPHIC_SMART_CONTROLLER) && HAS_DRIVER(TMC2130) && DISABLED(TMC_USE_SW_SPI) + #if IS_RRD_FG_SC && HAS_DRIVER(TMC2130) && DISABLED(TMC_USE_SW_SPI) #error "Re-ARM with REPRAP_DISCOUNT_FULL_GRAPHIC_SMART_CONTROLLER and TMC2130 requires TMC_USE_SW_SPI." #endif #endif +#if HAS_FSMC_TFT + #error "Sorry! FSMC TFT displays are not current available for HAL/LPC1768." +#endif + static_assert(DISABLED(BAUD_RATE_GCODE), "BAUD_RATE_GCODE is not yet supported on LPC176x."); /** @@ -92,13 +96,13 @@ static_assert(DISABLED(BAUD_RATE_GCODE), "BAUD_RATE_GCODE is not yet supported o #define ANY_TX(N,V...) DO(IS_TX##N,||,V) #define ANY_RX(N,V...) DO(IS_RX##N,||,V) -#if USING_SERIAL_0 +#if USING_HW_SERIAL0 #define IS_TX0(P) (P == P0_02) #define IS_RX0(P) (P == P0_03) - #if IS_TX0(TMC_SW_MISO) || IS_RX0(TMC_SW_MOSI) + #if IS_TX0(TMC_SPI_MISO) || IS_RX0(TMC_SPI_MOSI) #error "Serial port pins (0) conflict with Trinamic SPI pins!" - #elif ENABLED(MK2_MULTIPLEXER) && (IS_TX0(E_MUX1_PIN) || IS_RX0(E_MUX0_PIN)) - #error "Serial port pins (0) conflict with MK2 multiplexer pins!" + #elif HAS_PRUSA_MMU1 && (IS_TX0(E_MUX1_PIN) || IS_RX0(E_MUX0_PIN)) + #error "Serial port pins (0) conflict with Multi-Material-Unit multiplexer pins!" #elif (AXIS_HAS_SPI(X) && IS_TX0(X_CS_PIN)) || (AXIS_HAS_SPI(Y) && IS_RX0(Y_CS_PIN)) #error "Serial port pins (0) conflict with X/Y axis SPI pins!" #endif @@ -106,18 +110,18 @@ static_assert(DISABLED(BAUD_RATE_GCODE), "BAUD_RATE_GCODE is not yet supported o #undef IS_RX0 #endif -#if USING_SERIAL_1 +#if USING_HW_SERIAL1 #define IS_TX1(P) (P == P0_15) #define IS_RX1(P) (P == P0_16) #define _IS_TX1_1 IS_TX1 #define _IS_RX1_1 IS_RX1 - #if IS_TX1(TMC_SW_SCK) + #if IS_TX1(TMC_SPI_SCK) #error "Serial port pins (1) conflict with other pins!" - #elif HAS_WIRED_LCD + #elif HAS_ROTARY_ENCODER #if IS_TX1(BTN_EN2) || IS_RX1(BTN_EN1) #error "Serial port pins (1) conflict with Encoder Buttons!" - #elif ANY_TX(1, SCK_PIN, LCD_PINS_D4, DOGLCD_SCK, LCD_RESET_PIN, LCD_PINS_RS, SHIFT_CLK) \ - || ANY_RX(1, LCD_SDSS, LCD_PINS_RS, MISO_PIN, DOGLCD_A0, SS_PIN, LCD_SDSS, DOGLCD_CS, LCD_RESET_PIN, LCD_BACKLIGHT_PIN) + #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_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 @@ -127,7 +131,7 @@ static_assert(DISABLED(BAUD_RATE_GCODE), "BAUD_RATE_GCODE is not yet supported o #undef _IS_RX1_1 #endif -#if USING_SERIAL_2 +#if USING_HW_SERIAL2 #define IS_TX2(P) (P == P0_10) #define IS_RX2(P) (P == P0_11) #define _IS_TX2_1 IS_TX2 @@ -144,9 +148,9 @@ static_assert(DISABLED(BAUD_RATE_GCODE), "BAUD_RATE_GCODE is not yet supported o #error "Serial port pins (2) conflict with Z4 pins!" #elif ANY_RX(2, X_DIR_PIN, Y_DIR_PIN) #error "Serial port pins (2) conflict with other pins!" - #elif Y_HOME_DIR < 0 && IS_TX2(Y_STOP_PIN) + #elif Y_HOME_TO_MIN && IS_TX2(Y_STOP_PIN) #error "Serial port pins (2) conflict with Y endstop pin!" - #elif HAS_CUSTOM_PROBE_PIN && IS_TX2(Z_MIN_PROBE_PIN) + #elif USE_Z_MIN_PROBE && IS_TX2(Z_MIN_PROBE_PIN) #error "Serial port pins (2) conflict with probe pin!" #elif ANY_TX(2, X_ENABLE_PIN, Y_ENABLE_PIN) || ANY_RX(2, X_DIR_PIN, Y_DIR_PIN) #error "Serial port pins (2) conflict with X/Y stepper pins!" @@ -161,7 +165,7 @@ static_assert(DISABLED(BAUD_RATE_GCODE), "BAUD_RATE_GCODE is not yet supported o #undef _IS_RX2_1 #endif -#if USING_SERIAL_3 +#if USING_HW_SERIAL3 #define PIN_IS_TX3(P) (PIN_EXISTS(P) && P##_PIN == P0_00) #define PIN_IS_RX3(P) (P##_PIN == P0_01) #if PIN_IS_TX3(X_MIN) || PIN_IS_RX3(X_MAX) @@ -197,7 +201,7 @@ static_assert(DISABLED(BAUD_RATE_GCODE), "BAUD_RATE_GCODE is not yet supported o #if USEDI2CDEV_M == 0 // P0_27 [D57] (AUX-1) .......... P0_28 [D58] (AUX-1) #define PIN_IS_SDA0(P) (P##_PIN == P0_27) #define IS_SCL0(P) (P == P0_28) - #if ENABLED(SDSUPPORT) && PIN_IS_SDA0(SD_DETECT) + #if HAS_MEDIA && PIN_IS_SDA0(SD_DETECT) #error "SDA0 overlaps with SD_DETECT_PIN!" #elif PIN_IS_SDA0(E0_AUTO_FAN) #error "SDA0 overlaps with E0_AUTO_FAN_PIN!" @@ -205,10 +209,10 @@ static_assert(DISABLED(BAUD_RATE_GCODE), "BAUD_RATE_GCODE is not yet supported o #error "SDA0 overlaps with BEEPER_PIN!" #elif IS_SCL0(BTN_ENC) #error "SCL0 overlaps with Encoder Button!" - #elif IS_SCL0(SS_PIN) - #error "SCL0 overlaps with SS_PIN!" - #elif IS_SCL0(LCD_SDSS) - #error "SCL0 overlaps with LCD_SDSS!" + #elif IS_SCL0(SD_SS_PIN) + #error "SCL0 overlaps with SD_SS_PIN!" + #elif IS_SCL0(LCD_SDSS_PIN) + #error "SCL0 overlaps with LCD_SDSS_PIN!" #endif #undef PIN_IS_SDA0 #undef IS_SCL0 @@ -237,7 +241,7 @@ static_assert(DISABLED(BAUD_RATE_GCODE), "BAUD_RATE_GCODE is not yet supported o #define PIN_IS_SCL2(P) (P##_PIN == P0_11) #if PIN_IS_SDA2(Y_STOP) #error "i2c SDA2 overlaps with Y endstop pin!" - #elif HAS_CUSTOM_PROBE_PIN && PIN_IS_SDA2(Z_MIN_PROBE) + #elif USE_Z_MIN_PROBE && PIN_IS_SDA2(Z_MIN_PROBE) #error "i2c SDA2 overlaps with Z probe pin!" #elif PIN_IS_SDA2(X_ENABLE) || PIN_IS_SDA2(Y_ENABLE) #error "i2c SDA2 overlaps with X/Y ENABLE pin!" @@ -270,7 +274,7 @@ static_assert(DISABLED(BAUD_RATE_GCODE), "BAUD_RATE_GCODE is not yet supported o #endif #if ENABLED(SERIAL_STATS_MAX_RX_QUEUED) - #error "SERIAL_STATS_MAX_RX_QUEUED is not supported on this platform." + #error "SERIAL_STATS_MAX_RX_QUEUED is not supported on LPC176x." #elif ENABLED(SERIAL_STATS_DROPPED_RX) - #error "SERIAL_STATS_DROPPED_RX is not supported on this platform." + #error "SERIAL_STATS_DROPPED_RX is not supported on LPX176x." #endif diff --git a/Marlin/src/HAL/LPC1768/include/SPI.h b/Marlin/src/HAL/LPC1768/include/SPI.h index 9da2a32556..03d34becd8 100644 --- a/Marlin/src/HAL/LPC1768/include/SPI.h +++ b/Marlin/src/HAL/LPC1768/include/SPI.h @@ -37,13 +37,14 @@ #define DATA_SIZE_8BIT SSP_DATABIT_8 #define DATA_SIZE_16BIT SSP_DATABIT_16 -#define SPI_CLOCK_DIV2 8333333 //(SCR: 2) desired: 8,000,000 actual: 8,333,333 +4.2% SPI_FULL_SPEED -#define SPI_CLOCK_DIV4 4166667 //(SCR: 5) desired: 4,000,000 actual: 4,166,667 +4.2% SPI_HALF_SPEED -#define SPI_CLOCK_DIV8 2083333 //(SCR: 11) desired: 2,000,000 actual: 2,083,333 +4.2% SPI_QUARTER_SPEED -#define SPI_CLOCK_DIV16 1000000 //(SCR: 24) desired: 1,000,000 actual: 1,000,000 SPI_EIGHTH_SPEED -#define SPI_CLOCK_DIV32 500000 //(SCR: 49) desired: 500,000 actual: 500,000 SPI_SPEED_5 -#define SPI_CLOCK_DIV64 250000 //(SCR: 99) desired: 250,000 actual: 250,000 SPI_SPEED_6 -#define SPI_CLOCK_DIV128 125000 //(SCR:199) desired: 125,000 actual: 125,000 Default from HAL.h +#define SPI_CLOCK_MAX_TFT 30000000UL +#define SPI_CLOCK_DIV2 8333333 //(SCR: 2) desired: 8,000,000 actual: 8,333,333 +4.2% SPI_FULL_SPEED +#define SPI_CLOCK_DIV4 4166667 //(SCR: 5) desired: 4,000,000 actual: 4,166,667 +4.2% SPI_HALF_SPEED +#define SPI_CLOCK_DIV8 2083333 //(SCR: 11) desired: 2,000,000 actual: 2,083,333 +4.2% SPI_QUARTER_SPEED +#define SPI_CLOCK_DIV16 1000000 //(SCR: 24) desired: 1,000,000 actual: 1,000,000 SPI_EIGHTH_SPEED +#define SPI_CLOCK_DIV32 500000 //(SCR: 49) desired: 500,000 actual: 500,000 SPI_SPEED_5 +#define SPI_CLOCK_DIV64 250000 //(SCR: 99) desired: 250,000 actual: 250,000 SPI_SPEED_6 +#define SPI_CLOCK_DIV128 125000 //(SCR:199) desired: 125,000 actual: 125,000 Default from HAL.h #define SPI_CLOCK_MAX SPI_CLOCK_DIV2 @@ -76,7 +77,7 @@ public: //uint32_t spiRate() const { return spi_speed; } - static inline uint32_t spiRate2Clock(uint32_t spiRate) { + static uint32_t spiRate2Clock(uint32_t spiRate) { uint32_t Marlin_speed[7]; // CPSR is always 2 Marlin_speed[0] = 8333333; //(SCR: 2) desired: 8,000,000 actual: 8,333,333 +4.2% SPI_FULL_SPEED Marlin_speed[1] = 4166667; //(SCR: 5) desired: 4,000,000 actual: 4,166,667 +4.2% SPI_HALF_SPEED @@ -125,6 +126,11 @@ public: */ SPIClass(uint8_t spiPortNumber); + /** + * Init using pins + */ + SPIClass(pin_t mosi, pin_t miso, pin_t sclk, pin_t ssel = (pin_t)-1); + /** * Select and configure the current selected SPI device to use */ @@ -149,6 +155,7 @@ public: void read(uint8_t *buf, uint32_t len); void dmaSend(void *buf, uint16_t length, bool minc); + void dmaSendAsync(void *buf, uint16_t length, bool minc); /** * @brief Sets the number of the SPI peripheral to be used by 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 f442ab71c0..281c920b12 100644 --- a/Marlin/src/HAL/LPC1768/include/digipot_mcp4451_I2C_routines.c +++ b/Marlin/src/HAL/LPC1768/include/digipot_mcp4451_I2C_routines.c @@ -22,14 +22,14 @@ /** * 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 #include "../../../inc/MarlinConfigPre.h" -#if MB(MKS_SBASE) +#if ENABLED(DIGIPOT_MCP4451) && MB(MKS_SBASE) #ifdef __cplusplus extern "C" { @@ -37,35 +37,6 @@ #include "digipot_mcp4451_I2C_routines.h" -// These two routines are exact copies of the lpc17xx_i2c.c routines. Couldn't link to -// to the lpc17xx_i2c.c routines so had to copy them into this file & rename them. - -static uint32_t _I2C_Start(LPC_I2C_TypeDef *I2Cx) { - // Reset STA, STO, SI - I2Cx->I2CONCLR = I2C_I2CONCLR_SIC|I2C_I2CONCLR_STOC|I2C_I2CONCLR_STAC; - - // Enter to Master Transmitter mode - I2Cx->I2CONSET = I2C_I2CONSET_STA; - - // Wait for complete - while (!(I2Cx->I2CONSET & I2C_I2CONSET_SI)); - I2Cx->I2CONCLR = I2C_I2CONCLR_STAC; - return (I2Cx->I2STAT & I2C_STAT_CODE_BITMASK); -} - -static void _I2C_Stop(LPC_I2C_TypeDef *I2Cx) { - // Make sure start bit is not active - if (I2Cx->I2CONSET & I2C_I2CONSET_STA) - I2Cx->I2CONCLR = I2C_I2CONCLR_STAC; - - I2Cx->I2CONSET = I2C_I2CONSET_STO|I2C_I2CONSET_AA; - I2Cx->I2CONCLR = I2C_I2CONCLR_SIC; -} - -I2C_M_SETUP_Type transferMCfg; - -#define I2C_status (LPC_I2C1->I2STAT & I2C_STAT_CODE_BITMASK) - uint8_t digipot_mcp4451_start(uint8_t sla) { // send slave address and write bit // Sometimes TX data ACK or NAK status is returned. That mean the start state didn't // happen which means only the value of the slave address was send. Keep looping until @@ -102,5 +73,5 @@ uint8_t digipot_mcp4451_send_byte(uint8_t data) { } #endif -#endif // MB(MKS_SBASE) +#endif // DIGIPOT_MCP4451 && MKS_SBASE #endif // 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 9b6c62b052..4da863389a 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/include/i2c_util.c b/Marlin/src/HAL/LPC1768/include/i2c_util.c index e52fb7c4de..4e24f23236 100644 --- a/Marlin/src/HAL/LPC1768/include/i2c_util.c +++ b/Marlin/src/HAL/LPC1768/include/i2c_util.c @@ -63,6 +63,32 @@ void configure_i2c(const uint8_t clock_option) { I2C_Cmd(I2CDEV_M, I2C_MASTER_MODE, ENABLE); } +////////////////////////////////////////////////////////////////////////////////////// +// These two routines are exact copies of the lpc17xx_i2c.c routines. Couldn't link to +// to the lpc17xx_i2c.c routines so had to copy them into this file & rename them. + +uint32_t _I2C_Start(LPC_I2C_TypeDef *I2Cx) { + // Reset STA, STO, SI + I2Cx->I2CONCLR = I2C_I2CONCLR_SIC|I2C_I2CONCLR_STOC|I2C_I2CONCLR_STAC; + + // Enter to Master Transmitter mode + I2Cx->I2CONSET = I2C_I2CONSET_STA; + + // Wait for complete + while (!(I2Cx->I2CONSET & I2C_I2CONSET_SI)); + I2Cx->I2CONCLR = I2C_I2CONCLR_STAC; + return (I2Cx->I2STAT & I2C_STAT_CODE_BITMASK); +} + +void _I2C_Stop(LPC_I2C_TypeDef *I2Cx) { + /* Make sure start bit is not active */ + if (I2Cx->I2CONSET & I2C_I2CONSET_STA) + I2Cx->I2CONCLR = I2C_I2CONCLR_STAC; + + I2Cx->I2CONSET = I2C_I2CONSET_STO|I2C_I2CONSET_AA; + I2Cx->I2CONCLR = I2C_I2CONCLR_SIC; +} + #ifdef __cplusplus } #endif diff --git a/Marlin/src/HAL/LPC1768/include/i2c_util.h b/Marlin/src/HAL/LPC1768/include/i2c_util.h index a57f68a407..1f1c19f279 100644 --- a/Marlin/src/HAL/LPC1768/include/i2c_util.h +++ b/Marlin/src/HAL/LPC1768/include/i2c_util.h @@ -51,6 +51,11 @@ void configure_i2c(const uint8_t clock_option); +uint32_t _I2C_Start(LPC_I2C_TypeDef *I2Cx); +void _I2C_Stop(LPC_I2C_TypeDef *I2Cx); + +#define I2C_status (LPC_I2C1->I2STAT & I2C_STAT_CODE_BITMASK) + #ifdef __cplusplus } #endif diff --git a/Marlin/src/HAL/LPC1768/main.cpp b/Marlin/src/HAL/LPC1768/main.cpp deleted file mode 100644 index 0b4045cb99..0000000000 --- a/Marlin/src/HAL/LPC1768/main.cpp +++ /dev/null @@ -1,160 +0,0 @@ -/** - * Marlin 3D Printer Firmware - * Copyright (c) 2020 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 TARGET_LPC1768 - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -extern "C" { - #include -} - -#include "../../sd/cardreader.h" -#include "../../inc/MarlinConfig.h" -#include "../../core/millis_t.h" - -extern uint32_t MSC_SD_Init(uint8_t pdrv); -extern "C" int isLPC1769(); -extern "C" void disk_timerproc(); - -void SysTick_Callback() { disk_timerproc(); } - -void HAL_init() { - - // Init LEDs - #if PIN_EXISTS(LED) - SET_DIR_OUTPUT(LED_PIN); - WRITE_PIN_CLR(LED_PIN); - #if PIN_EXISTS(LED2) - SET_DIR_OUTPUT(LED2_PIN); - WRITE_PIN_CLR(LED2_PIN); - #if PIN_EXISTS(LED3) - SET_DIR_OUTPUT(LED3_PIN); - WRITE_PIN_CLR(LED3_PIN); - #if PIN_EXISTS(LED4) - SET_DIR_OUTPUT(LED4_PIN); - WRITE_PIN_CLR(LED4_PIN); - #endif - #endif - #endif - - // Flash status LED 3 times to indicate Marlin has started booting - LOOP_L_N(i, 6) { - TOGGLE(LED_PIN); - delay(100); - } - #endif - - // Init Servo Pins - #define INIT_SERVO(N) OUT_WRITE(SERVO##N##_PIN, LOW) - #if HAS_SERVO_0 - INIT_SERVO(0); - #endif - #if HAS_SERVO_1 - INIT_SERVO(1); - #endif - #if HAS_SERVO_2 - INIT_SERVO(2); - #endif - #if HAS_SERVO_3 - INIT_SERVO(3); - #endif - - //debug_frmwrk_init(); - //_DBG("\n\nDebug running\n"); - // Initialize the SD card chip select pins as soon as possible - #if PIN_EXISTS(SS) - OUT_WRITE(SS_PIN, HIGH); - #endif - - #if PIN_EXISTS(ONBOARD_SD_CS) && ONBOARD_SD_CS_PIN != SS_PIN - OUT_WRITE(ONBOARD_SD_CS_PIN, HIGH); - #endif - - #ifdef LPC1768_ENABLE_CLKOUT_12M - /** - * CLKOUTCFG register - * bit 8 (CLKOUT_EN) = enables CLKOUT signal. Disabled for now to prevent glitch when enabling GPIO. - * bits 7:4 (CLKOUTDIV) = set to 0 for divider setting of /1 - * bits 3:0 (CLKOUTSEL) = set to 1 to select main crystal oscillator as CLKOUT source - */ - LPC_SC->CLKOUTCFG = (0<<8)|(0<<4)|(1<<0); - // set P1.27 pin to function 01 (CLKOUT) - PINSEL_CFG_Type PinCfg; - PinCfg.Portnum = 1; - PinCfg.Pinnum = 27; - PinCfg.Funcnum = 1; // function 01 (CLKOUT) - PinCfg.OpenDrain = 0; // not open drain - PinCfg.Pinmode = 2; // no pull-up/pull-down - PINSEL_ConfigPin(&PinCfg); - // now set CLKOUT_EN bit - LPC_SC->CLKOUTCFG |= (1<<8); - #endif - - USB_Init(); // USB Initialization - USB_Connect(FALSE); // USB clear connection - delay(1000); // Give OS time to notice - USB_Connect(TRUE); - - #if DISABLED(NO_SD_HOST_DRIVE) - MSC_SD_Init(0); // Enable USB SD card access - #endif - - const millis_t usb_timeout = millis() + 2000; - while (!USB_Configuration && PENDING(millis(), usb_timeout)) { - delay(50); - HAL_idletask(); - #if PIN_EXISTS(LED) - TOGGLE(LED_PIN); // Flash quickly during USB initialization - #endif - } - - HAL_timer_init(); -} - -// HAL idle task -void HAL_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()) - if (card.isMounted()) - MSC_Aquire_Lock(); - else - MSC_Release_Lock(); - #endif - // Perform USB stack housekeeping - MSC_RunDeferredCommands(); -} - -#endif // TARGET_LPC1768 diff --git a/Marlin/src/HAL/LPC1768/pinsDebug.h b/Marlin/src/HAL/LPC1768/pinsDebug.h index f80551604f..5baeadd0dd 100644 --- a/Marlin/src/HAL/LPC1768/pinsDebug.h +++ b/Marlin/src/HAL/LPC1768/pinsDebug.h @@ -2,6 +2,9 @@ * Marlin 3D Printer Firmware * Copyright (c) 2020 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 @@ -16,38 +19,51 @@ * 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 pwm_details(pin) pin = pin // do nothing // print PWM details -#define pwm_status(pin) false //Print a pin's PWM status. Return true if it's currently a PWM pin. -#define IS_ANALOG(P) (DIGITAL_PIN_TO_ANALOG_PIN(P) >= 0 ? 1 : 0) -#define digitalRead_mod(p) extDigitalRead(p) -#define PRINT_PORT(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("%d.%02d"), LPC176x::pin_port(p), LPC176x::pin_bit(p)); SERIAL_ECHO(buffer); }while(0) -#define MULTI_NAME_PAD 16 // space needed to be pretty if not first name assigned to a pin +#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 #ifndef M43_NEVER_TOUCH #define M43_NEVER_TOUCH(Q) ((Q) == P0_29 || (Q) == P0_30 || (Q) == P2_09) // USB pins #endif -bool GET_PINMODE(const pin_t pin) { - if (!LPC176x::pin_is_valid(pin) || LPC176x::pin_adc_enabled(pin)) // found an invalid pin or active analog 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); } -bool GET_ARRAY_IS_DIGITAL(const pin_t pin) { - return (!LPC176x::pin_has_adc(pin) || !LPC176x::pin_adc_enabled(pin)); -} +#define getPinIsDigitalByIndex(x) ((bool) pin_array[x].is_digital) + +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 b4da5d4df2..036bf0e023 100644 --- a/Marlin/src/HAL/LPC1768/spi_pins.h +++ b/Marlin/src/HAL/LPC1768/spi_pins.h @@ -21,34 +21,25 @@ */ #pragma once -#include "../../core/macros.h" - -#if BOTH(SDSUPPORT, HAS_MARLINUI_U8GLIB) && (LCD_PINS_D4 == SCK_PIN || LCD_PINS_ENABLE == MOSI_PIN || DOGLCD_SCK == SCK_PIN || DOGLCD_MOSI == MOSI_PIN) - #define LPC_SOFTWARE_SPI // If the SD card and LCD adapter share the same SPI pins, then software SPI is currently - // needed due to the speed and mode required for communicating with each device being different. - // This requirement can be removed if the SPI access to these devices is updated to use - // spiBeginTransaction. +#if ALL(HAS_MARLINUI_U8GLIB, HAS_MEDIA) && (LCD_PINS_D4 == SD_SCK_PIN || LCD_PINS_EN == SD_MOSI_PIN || DOGLCD_SCK == SD_SCK_PIN || DOGLCD_MOSI == SD_MOSI_PIN) + #define SOFTWARE_SPI // If the SD card and LCD adapter share the same SPI pins, then software SPI is currently + // needed due to the speed and mode required for communicating with each device being different. + // This requirement can be removed if the SPI access to these devices is updated to use + // spiBeginTransaction. #endif -/** onboard SD card */ -//#define SCK_PIN P0_07 -//#define MISO_PIN P0_08 -//#define MOSI_PIN P0_09 -//#define SS_PIN P0_06 -/** external */ -#ifndef SCK_PIN - #define SCK_PIN P0_15 +// 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 -#ifndef MISO_PIN - #define MISO_PIN P0_17 +#ifndef SD_MISO_PIN + #define SD_MISO_PIN P0_17 #endif -#ifndef MOSI_PIN - #define MOSI_PIN P0_18 -#endif -#ifndef SS_PIN - #define SS_PIN P1_23 -#endif -#if !defined(SDSS) || SDSS == P_NC // gets defaulted in pins.h - #undef SDSS - #define SDSS SS_PIN +#ifndef SD_MOSI_PIN + #define SD_MOSI_PIN P0_18 #endif diff --git a/Marlin/src/HAL/LPC1768/tft/tft_spi.cpp b/Marlin/src/HAL/LPC1768/tft/tft_spi.cpp index 84907acd07..beaadaf519 100644 --- a/Marlin/src/HAL/LPC1768/tft/tft_spi.cpp +++ b/Marlin/src/HAL/LPC1768/tft/tft_spi.cpp @@ -20,95 +20,58 @@ * */ +#ifdef TARGET_LPC1768 + #include "../../../inc/MarlinConfig.h" #if HAS_SPI_TFT #include "tft_spi.h" -//TFT_SPI tft; +SPIClass TFT_SPI::SPIx(TFT_SPI_DEVICE); -SPIClass TFT_SPI::SPIx(1); - -#define TFT_CS_H WRITE(TFT_CS_PIN, HIGH) -#define TFT_CS_L WRITE(TFT_CS_PIN, LOW) - -#define TFT_DC_H WRITE(TFT_DC_PIN, HIGH) -#define TFT_DC_L WRITE(TFT_DC_PIN, LOW) - -#define TFT_RST_H WRITE(TFT_RESET_PIN, HIGH) -#define TFT_RST_L WRITE(TFT_RESET_PIN, LOW) - -#define TFT_BLK_H WRITE(TFT_BACKLIGHT_PIN, HIGH) -#define TFT_BLK_L WRITE(TFT_BACKLIGHT_PIN, LOW) - -void TFT_SPI::Init() { +void TFT_SPI::init() { #if PIN_EXISTS(TFT_RESET) - SET_OUTPUT(TFT_RESET_PIN); - TFT_RST_H; + OUT_WRITE(TFT_RESET_PIN, HIGH); delay(100); #endif #if PIN_EXISTS(TFT_BACKLIGHT) - SET_OUTPUT(TFT_BACKLIGHT_PIN); - TFT_BLK_H; + OUT_WRITE(TFT_BACKLIGHT_PIN, HIGH); #endif - SET_OUTPUT(TFT_DC_PIN); - SET_OUTPUT(TFT_CS_PIN); + OUT_WRITE(TFT_DC_PIN, HIGH); + OUT_WRITE(TFT_CS_PIN, HIGH); - TFT_DC_H; - TFT_CS_H; - - /** - * STM32F1 APB2 = 72MHz, APB1 = 36MHz, max SPI speed of this MCU if 18Mhz - * STM32F1 has 3 SPI ports, SPI1 in APB2, SPI2/SPI3 in APB1 - * so the minimum prescale of SPI1 is DIV4, SPI2/SPI3 is DIV2 - */ - #if 0 - #if SPI_DEVICE == 1 - #define SPI_CLOCK_MAX SPI_CLOCK_DIV4 - #else - #define SPI_CLOCK_MAX SPI_CLOCK_DIV2 - #endif - uint8_t clock; - uint8_t spiRate = SPI_FULL_SPEED; - switch (spiRate) { - case SPI_FULL_SPEED: clock = SPI_CLOCK_MAX ; break; - case SPI_HALF_SPEED: clock = SPI_CLOCK_DIV4 ; break; - case SPI_QUARTER_SPEED: clock = SPI_CLOCK_DIV8 ; break; - case SPI_EIGHTH_SPEED: clock = SPI_CLOCK_DIV16; break; - case SPI_SPEED_5: clock = SPI_CLOCK_DIV32; break; - case SPI_SPEED_6: clock = SPI_CLOCK_DIV64; break; - default: clock = SPI_CLOCK_DIV2; // Default from the SPI library - } - #endif - - #if TFT_MISO_PIN == BOARD_SPI1_MISO_PIN - SPIx.setModule(1); - #elif TFT_MISO_PIN == BOARD_SPI2_MISO_PIN - SPIx.setModule(2); - #endif - SPIx.setClock(SPI_CLOCK_MAX); + SPIx.setModule(TFT_SPI_DEVICE); + SPIx.setClock(SPI_CLOCK_MAX_TFT); SPIx.setBitOrder(MSBFIRST); SPIx.setDataMode(SPI_MODE0); } -void TFT_SPI::DataTransferBegin(uint16_t DataSize) { - SPIx.setDataSize(DataSize); +void TFT_SPI::dataTransferBegin(uint16_t dataSize) { + SPIx.setDataSize(dataSize); SPIx.begin(); - TFT_CS_L; + WRITE(TFT_CS_PIN, LOW); } -uint32_t TFT_SPI::GetID() { +#ifdef TFT_DEFAULT_DRIVER + #include "../../../lcd/tft_io/tft_ids.h" +#endif + +uint32_t TFT_SPI::getID() { uint32_t id; - id = ReadID(LCD_READ_ID); + id = readID(LCD_READ_ID); if ((id & 0xFFFF) == 0 || (id & 0xFFFF) == 0xFFFF) - id = ReadID(LCD_READ_ID4); + id = readID(LCD_READ_ID4); + #ifdef TFT_DEFAULT_DRIVER + if ((id & 0xFFFF) == 0 || (id & 0xFFFF) == 0xFFFF) + id = TFT_DEFAULT_DRIVER; + #endif return id; } -uint32_t TFT_SPI::ReadID(uint16_t Reg) { +uint32_t TFT_SPI::readID(const uint16_t inReg) { uint32_t data = 0; #if PIN_EXISTS(TFT_MISO) @@ -116,38 +79,78 @@ uint32_t TFT_SPI::ReadID(uint16_t Reg) { SPIx.setDataSize(DATASIZE_8BIT); SPIx.setClock(SPI_CLOCK_DIV64); SPIx.begin(); - TFT_CS_L; - WriteReg(Reg); + WRITE(TFT_CS_PIN, LOW); + writeReg(inReg); - LOOP_L_N(i, 4) { + for (uint8_t i = 0; i < 4; ++i) { SPIx.read((uint8_t*)&d, 1); data = (data << 8) | d; } - DataTransferEnd(); - SPIx.setClock(SPI_CLOCK_MAX); + dataTransferEnd(); + SPIx.setClock(SPI_CLOCK_MAX_TFT); #endif return data >> 7; } bool TFT_SPI::isBusy() { + #define __IS_DMA_CONFIGURED(__HANDLE__) ((__HANDLE__)->DMACCSrcAddr != 0) + + // DMA Channel 0 is hardcoded in dmaSendAsync() and dmaSend() + if (!__IS_DMA_CONFIGURED(LPC_GPDMACH0)) return false; + + if (GPDMA_IntGetStatus(GPDMA_STAT_INTERR, 0)) { + // You should not be here - DMA transfer error flag is set + // Abort DMA transfer and release SPI + } + else { + // Check if DMA transfer completed flag is set + if (!GPDMA_IntGetStatus(GPDMA_STAT_INTTC, 0)) return true; + // Check if SPI TX butter is empty and SPI is idle + if ((SSP_GetStatus(LPC_SSPx, SSP_STAT_TXFIFO_EMPTY) == RESET) || (SSP_GetStatus(LPC_SSPx, SSP_STAT_BUSY) == SET)) return true; + } + + abort(); return false; } -void TFT_SPI::Abort() { - DataTransferEnd(); +void TFT_SPI::abort() { + // DMA Channel 0 is hardcoded in dmaSendAsync() and dmaSend() + + // Disable DMA + GPDMA_ChannelCmd(0, DISABLE); + + // Clear ERR and TC + GPDMA_ClearIntPending(GPDMA_STATCLR_INTTC, 0); + GPDMA_ClearIntPending(GPDMA_STATCLR_INTERR, 0); + + // Disable DMA on SPI + SSP_DMACmd(LPC_SSPx, SSP_DMA_TX, DISABLE); + + // Deconfigure DMA Channel 0 + LPC_GPDMACH0->DMACCControl = 0U; + LPC_GPDMACH0->DMACCConfig = 0U; + LPC_GPDMACH0->DMACCSrcAddr = 0U; + LPC_GPDMACH0->DMACCDestAddr = 0U; + + dataTransferEnd(); } -void TFT_SPI::Transmit(uint16_t Data) { - SPIx.transfer(Data); +void TFT_SPI::transmit(uint16_t data) { SPIx.transfer(data); } + +void TFT_SPI::transmit(uint32_t memoryIncrease, uint16_t *data, uint16_t count) { + dataTransferBegin(DATASIZE_16BIT); + SPIx.dmaSend(data, count, memoryIncrease); + abort(); } -void TFT_SPI::TransmitDMA(uint32_t MemoryIncrease, uint16_t *Data, uint16_t Count) { - DataTransferBegin(DATASIZE_16BIT); //16 - TFT_DC_H; - SPIx.dmaSend(Data, Count, MemoryIncrease); - DataTransferEnd(); +void TFT_SPI::transmitDMA(uint32_t memoryIncrease, uint16_t *data, uint16_t count) { + dataTransferBegin(DATASIZE_16BIT); + SPIx.dmaSendAsync(data, count, memoryIncrease); + + TERN_(TFT_SHARED_IO, while (isBusy())); } #endif // HAS_SPI_TFT +#endif // TARGET_LPC1768 diff --git a/Marlin/src/HAL/LPC1768/tft/tft_spi.h b/Marlin/src/HAL/LPC1768/tft/tft_spi.h index 4753fdbae9..6d5829fc73 100644 --- a/Marlin/src/HAL/LPC1768/tft/tft_spi.h +++ b/Marlin/src/HAL/LPC1768/tft/tft_spi.h @@ -27,6 +27,18 @@ #include // #include +#define IS_SPI(N) (BOARD_NR_SPI >= N && (TFT_SCK_PIN == BOARD_SPI##N##_SCK_PIN) && (TFT_MOSI_PIN == BOARD_SPI##N##_MOSI_PIN) && (TFT_MISO_PIN == BOARD_SPI##N##_MISO_PIN)) +#if IS_SPI(1) + #define TFT_SPI_DEVICE 1 + #define LPC_SSPx LPC_SSP0 +#elif IS_SPI(2) + #define TFT_SPI_DEVICE 2 + #define LPC_SSPx LPC_SSP1 +#else + #error "Invalid TFT SPI configuration." +#endif +#undef IS_SPI + #ifndef LCD_READ_ID #define LCD_READ_ID 0x04 // Read display identification information (0xD3 on ILI9341) #endif @@ -34,44 +46,44 @@ #define LCD_READ_ID4 0xD3 // Read display identification information (0xD3 on ILI9341) #endif -#define DATASIZE_8BIT SSP_DATABIT_8 -#define DATASIZE_16BIT SSP_DATABIT_16 -#define TFT_IO_DRIVER TFT_SPI +#define DATASIZE_8BIT SSP_DATABIT_8 +#define DATASIZE_16BIT SSP_DATABIT_16 +#define TFT_IO_DRIVER TFT_SPI +#define DMA_MAX_WORDS 0xFFF -#define DMA_MINC_ENABLE 1 -#define DMA_MINC_DISABLE 0 +#define DMA_MINC_ENABLE 1 +#define DMA_MINC_DISABLE 0 class TFT_SPI { private: - static uint32_t ReadID(uint16_t Reg); - static void Transmit(uint16_t Data); - static void TransmitDMA(uint32_t MemoryIncrease, uint16_t *Data, uint16_t Count); + static uint32_t readID(const uint16_t inReg); + static void transmit(uint16_t data); + static void transmit(uint32_t memoryIncrease, uint16_t *data, uint16_t count); + static void transmitDMA(uint32_t memoryIncrease, uint16_t *data, uint16_t count); public: static SPIClass SPIx; - static void Init(); - static uint32_t GetID(); + static void init(); + static uint32_t getID(); static bool isBusy(); - static void Abort(); + static void abort(); - static void DataTransferBegin(uint16_t DataWidth = DATASIZE_16BIT); - static void DataTransferEnd() { OUT_WRITE(TFT_CS_PIN, HIGH); SPIx.end(); }; - static void DataTransferAbort(); + static void dataTransferBegin(uint16_t dataWidth=DATASIZE_16BIT); + static void dataTransferEnd() { WRITE(TFT_CS_PIN, HIGH); SSP_Cmd(LPC_SSPx, DISABLE); }; + static void dataTransferAbort(); - static void WriteData(uint16_t Data) { Transmit(Data); } - static void WriteReg(uint16_t Reg) { OUT_WRITE(TFT_A0_PIN, LOW); Transmit(Reg); OUT_WRITE(TFT_A0_PIN, HIGH); } + static void writeData(uint16_t data) { transmit(data); } + static void writeReg(const uint16_t inReg) { WRITE(TFT_DC_PIN, LOW); transmit(inReg); WRITE(TFT_DC_PIN, HIGH); } - static void WriteSequence(uint16_t *Data, uint16_t Count) { TransmitDMA(DMA_MINC_ENABLE, Data, Count); } - // static void WriteMultiple(uint16_t Color, uint16_t Count) { static uint16_t Data; Data = Color; TransmitDMA(DMA_MINC_DISABLE, &Data, Count); } - static void WriteMultiple(uint16_t Color, uint32_t Count) { - static uint16_t Data; Data = Color; - //LPC dma can only write 0xFFF bytes at once. - #define MAX_DMA_SIZE (0xFFF - 1) - while (Count > 0) { - TransmitDMA(DMA_MINC_DISABLE, &Data, Count > MAX_DMA_SIZE ? MAX_DMA_SIZE : Count); - Count = Count > MAX_DMA_SIZE ? Count - MAX_DMA_SIZE : 0; + static void writeSequence_DMA(uint16_t *data, uint16_t count) { transmitDMA(DMA_MINC_ENABLE, data, count); } + static void writeMultiple_DMA(uint16_t color, uint16_t count) { static uint16_t data; data = color; transmitDMA(DMA_MINC_DISABLE, &data, count); } + + static void writeSequence(uint16_t *data, uint16_t count) { transmit(DMA_MINC_ENABLE, data, count); } + static void writeMultiple(uint16_t color, uint32_t count) { + while (count > 0) { + transmit(DMA_MINC_DISABLE, &color, count > DMA_MAX_WORDS ? DMA_MAX_WORDS : count); + count = count > DMA_MAX_WORDS ? count - DMA_MAX_WORDS : 0; } - #undef MAX_DMA_SIZE } }; diff --git a/Marlin/src/HAL/LPC1768/tft/xpt2046.cpp b/Marlin/src/HAL/LPC1768/tft/xpt2046.cpp index 5f96630043..a737266c68 100644 --- a/Marlin/src/HAL/LPC1768/tft/xpt2046.cpp +++ b/Marlin/src/HAL/LPC1768/tft/xpt2046.cpp @@ -1,6 +1,9 @@ /** * Marlin 3D Printer Firmware - * Copyright (c) 2019 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] + * Copyright (c) 2020 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 @@ -17,9 +20,11 @@ * */ +#ifdef TARGET_LPC1768 + #include "../../../inc/MarlinConfig.h" -#if HAS_TFT_XPT2046 || HAS_TOUCH_XPT2046 +#if HAS_TFT_XPT2046 || HAS_RES_TOUCH_BUTTONS #include "xpt2046.h" #include @@ -40,10 +45,12 @@ uint16_t delta(uint16_t a, uint16_t b) { return a > b ? a - b : b - a; } } #endif -void XPT2046::Init() { - SET_INPUT(TOUCH_MISO_PIN); - SET_OUTPUT(TOUCH_MOSI_PIN); - SET_OUTPUT(TOUCH_SCK_PIN); +void XPT2046::init() { + #if DISABLED(TOUCH_BUTTONS_HW_SPI) + SET_INPUT(TOUCH_MISO_PIN); + SET_OUTPUT(TOUCH_MOSI_PIN); + SET_OUTPUT(TOUCH_SCK_PIN); + #endif OUT_WRITE(TOUCH_CS_PIN, HIGH); #if PIN_EXISTS(TOUCH_INT) @@ -67,9 +74,8 @@ bool XPT2046::isTouched() { ); } -bool XPT2046::getRawPoint(int16_t *x, int16_t *y) { - if (isBusy()) return false; - if (!isTouched()) return false; +bool XPT2046::getRawPoint(int16_t * const x, int16_t * const y) { + if (isBusy() || !isTouched()) return false; *x = getRawData(XPT2046_X); *y = getRawData(XPT2046_Y); return isTouched(); @@ -78,7 +84,7 @@ bool XPT2046::getRawPoint(int16_t *x, int16_t *y) { uint16_t XPT2046::getRawData(const XPTCoordinate coordinate) { uint16_t data[3]; - DataTransferBegin(); + dataTransferBegin(); TERN_(TOUCH_BUTTONS_HW_SPI, SPIx.begin()); for (uint16_t i = 0; i < 3 ; i++) { @@ -87,7 +93,7 @@ uint16_t XPT2046::getRawData(const XPTCoordinate coordinate) { } TERN_(TOUCH_BUTTONS_HW_SPI, SPIx.end()); - DataTransferEnd(); + dataTransferEnd(); uint16_t delta01 = delta(data[0], data[1]), delta02 = delta(data[0], data[2]), @@ -100,18 +106,18 @@ uint16_t XPT2046::getRawData(const XPTCoordinate coordinate) { } uint16_t XPT2046::IO(uint16_t data) { - return TERN(TOUCH_BUTTONS_HW_SPI, HardwareIO, SoftwareIO)(data); + return TERN(TOUCH_BUTTONS_HW_SPI, hardwareIO, softwareIO)(data); } extern uint8_t spiTransfer(uint8_t b); #if ENABLED(TOUCH_BUTTONS_HW_SPI) - uint16_t XPT2046::HardwareIO(uint16_t data) { + uint16_t XPT2046::hardwareIO(uint16_t data) { return SPIx.transfer(data & 0xFF); } #endif -uint16_t XPT2046::SoftwareIO(uint16_t data) { +uint16_t XPT2046::softwareIO(uint16_t data) { uint16_t result = 0; for (uint8_t j = 0x80; j; j >>= 1) { @@ -125,4 +131,5 @@ uint16_t XPT2046::SoftwareIO(uint16_t data) { return result; } -#endif // HAS_TFT_XPT2046 +#endif // HAS_TFT_XPT2046 || HAS_RES_TOUCH_BUTTONS +#endif // TARGET_LPC1768 diff --git a/Marlin/src/HAL/LPC1768/tft/xpt2046.h b/Marlin/src/HAL/LPC1768/tft/xpt2046.h index 019f75efce..9a19e3c98d 100644 --- a/Marlin/src/HAL/LPC1768/tft/xpt2046.h +++ b/Marlin/src/HAL/LPC1768/tft/xpt2046.h @@ -1,6 +1,9 @@ /** * Marlin 3D Printer Firmware - * Copyright (c) 2019 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] + * Copyright (c) 2020 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 @@ -25,16 +28,16 @@ #endif #ifndef TOUCH_MISO_PIN - #define TOUCH_MISO_PIN MISO_PIN + #define TOUCH_MISO_PIN SD_MISO_PIN #endif #ifndef TOUCH_MOSI_PIN - #define TOUCH_MOSI_PIN MOSI_PIN + #define TOUCH_MOSI_PIN SD_MOSI_PIN #endif #ifndef TOUCH_SCK_PIN - #define TOUCH_SCK_PIN SCK_PIN + #define TOUCH_SCK_PIN SD_SCK_PIN #endif #ifndef TOUCH_CS_PIN - #define TOUCH_CS_PIN CS_PIN + #define TOUCH_CS_PIN SD_SS_PIN #endif #ifndef TOUCH_INT_PIN #define TOUCH_INT_PIN -1 @@ -51,7 +54,7 @@ enum XPTCoordinate : uint8_t { XPT2046_Z2 = 0x40 | XPT2046_CONTROL | XPT2046_DFR_MODE, }; -#if !defined(XPT2046_Z1_THRESHOLD) +#ifndef XPT2046_Z1_THRESHOLD #define XPT2046_Z1_THRESHOLD 10 #endif @@ -62,12 +65,12 @@ private: static uint16_t getRawData(const XPTCoordinate coordinate); static bool isTouched(); - static inline void DataTransferBegin() { WRITE(TOUCH_CS_PIN, LOW); }; - static inline void DataTransferEnd() { WRITE(TOUCH_CS_PIN, HIGH); }; + static void dataTransferBegin() { WRITE(TOUCH_CS_PIN, LOW); }; + static void dataTransferEnd() { WRITE(TOUCH_CS_PIN, HIGH); }; #if ENABLED(TOUCH_BUTTONS_HW_SPI) - static uint16_t HardwareIO(uint16_t data); + static uint16_t hardwareIO(uint16_t data); #endif - static uint16_t SoftwareIO(uint16_t data); + static uint16_t softwareIO(uint16_t data); static uint16_t IO(uint16_t data = 0); public: @@ -75,6 +78,6 @@ public: static SPIClass SPIx; #endif - static void Init(); - static bool getRawPoint(int16_t *x, int16_t *y); + static void init(); + static bool getRawPoint(int16_t * const x, int16_t * const y); }; diff --git a/Marlin/src/HAL/LPC1768/timers.cpp b/Marlin/src/HAL/LPC1768/timers.cpp index a7a40584da..b541ab6e6a 100644 --- a/Marlin/src/HAL/LPC1768/timers.cpp +++ b/Marlin/src/HAL/LPC1768/timers.cpp @@ -1,9 +1,9 @@ /** * Marlin 3D Printer Firmware - * * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com - * Copyright (c) 2015-2016 Nico Tonnhofer wurstnase.reprap@gmail.com + * + * 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 @@ -40,7 +40,7 @@ void HAL_timer_init() { void HAL_timer_start(const uint8_t timer_num, const uint32_t frequency) { switch (timer_num) { - case 0: + case MF_TIMER_STEP: LPC_TIM0->MCR = _BV(SBIT_MR0I) | _BV(SBIT_MR0R); // Match on MR0, reset on MR0, interrupts when NVIC enables them LPC_TIM0->MR0 = uint32_t(STEPPER_TIMER_RATE) / frequency; // Match value (period) to set frequency LPC_TIM0->TCR = _BV(SBIT_CNTEN); // Counter Enable @@ -49,7 +49,7 @@ void HAL_timer_start(const uint8_t timer_num, const uint32_t frequency) { NVIC_EnableIRQ(TIMER0_IRQn); break; - case 1: + case MF_TIMER_TEMP: LPC_TIM1->MCR = _BV(SBIT_MR0I) | _BV(SBIT_MR0R); // Match on MR0, reset on MR0, interrupts when NVIC enables them LPC_TIM1->MR0 = uint32_t(TEMP_TIMER_RATE) / frequency; LPC_TIM1->TCR = _BV(SBIT_CNTEN); // Counter Enable diff --git a/Marlin/src/HAL/LPC1768/timers.h b/Marlin/src/HAL/LPC1768/timers.h index e6744fb005..3aec2418d2 100644 --- a/Marlin/src/HAL/LPC1768/timers.h +++ b/Marlin/src/HAL/LPC1768/timers.h @@ -1,8 +1,9 @@ /** * Marlin 3D Printer Firmware - * * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com + * + * 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 @@ -56,51 +57,50 @@ #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 STEP_TIMER_NUM - #define STEP_TIMER_NUM 0 // Timer Index for Stepper +#ifndef MF_TIMER_STEP + #define MF_TIMER_STEP 0 // Timer Index for Stepper #endif -#ifndef PULSE_TIMER_NUM - #define PULSE_TIMER_NUM STEP_TIMER_NUM +#ifndef MF_TIMER_PULSE + #define MF_TIMER_PULSE MF_TIMER_STEP #endif -#ifndef TEMP_TIMER_NUM - #define TEMP_TIMER_NUM 1 // Timer Index for Temperature +#ifndef MF_TIMER_TEMP + #define MF_TIMER_TEMP 1 // Timer Index for Temperature #endif -#ifndef PWM_TIMER_NUM - #define PWM_TIMER_NUM 3 // Timer Index for PWM +#ifndef MF_TIMER_PWM + #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(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) #ifndef HAL_STEP_TIMER_ISR - #define HAL_STEP_TIMER_ISR() _HAL_TIMER_ISR(STEP_TIMER_NUM) + #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(TEMP_TIMER_NUM) + #define HAL_TEMP_TIMER_ISR() _HAL_TIMER_ISR(MF_TIMER_TEMP) #endif // Timer references by index -#define STEP_TIMER_PTR _HAL_TIMER(STEP_TIMER_NUM) -#define TEMP_TIMER_PTR _HAL_TIMER(TEMP_TIMER_NUM) +#define STEP_TIMER_PTR _HAL_TIMER(MF_TIMER_STEP) +#define TEMP_TIMER_PTR _HAL_TIMER(MF_TIMER_TEMP) // ------------------------ // Public functions @@ -110,38 +110,38 @@ 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 0: STEP_TIMER_PTR->MR0 = compare; break; // Stepper Timer Match Register 0 - case 1: TEMP_TIMER_PTR->MR0 = compare; break; // Temp Timer Match Register 0 + 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 } } FORCE_INLINE static hal_timer_t HAL_timer_get_compare(const uint8_t timer_num) { switch (timer_num) { - case 0: return STEP_TIMER_PTR->MR0; // Stepper Timer Match Register 0 - case 1: return TEMP_TIMER_PTR->MR0; // Temp Timer Match Register 0 + case MF_TIMER_STEP: return STEP_TIMER_PTR->MR0; // Stepper Timer Match Register 0 + case MF_TIMER_TEMP: return TEMP_TIMER_PTR->MR0; // Temp Timer Match Register 0 } return 0; } FORCE_INLINE static hal_timer_t HAL_timer_get_count(const uint8_t timer_num) { switch (timer_num) { - case 0: return STEP_TIMER_PTR->TC; // Stepper Timer Count - case 1: return TEMP_TIMER_PTR->TC; // Temp Timer Count + case MF_TIMER_STEP: return STEP_TIMER_PTR->TC; // Stepper Timer Count + case MF_TIMER_TEMP: return TEMP_TIMER_PTR->TC; // Temp Timer Count } return 0; } FORCE_INLINE static void HAL_timer_enable_interrupt(const uint8_t timer_num) { switch (timer_num) { - case 0: NVIC_EnableIRQ(TIMER0_IRQn); break; // Enable interrupt handler - case 1: NVIC_EnableIRQ(TIMER1_IRQn); break; // Enable interrupt handler + case MF_TIMER_STEP: NVIC_EnableIRQ(TIMER0_IRQn); break; // Enable interrupt handler + case MF_TIMER_TEMP: NVIC_EnableIRQ(TIMER1_IRQn); break; // Enable interrupt handler } } FORCE_INLINE static void HAL_timer_disable_interrupt(const uint8_t timer_num) { switch (timer_num) { - case 0: NVIC_DisableIRQ(TIMER0_IRQn); break; // Disable interrupt handler - case 1: NVIC_DisableIRQ(TIMER1_IRQn); break; // Disable interrupt handler + case MF_TIMER_STEP: NVIC_DisableIRQ(TIMER0_IRQn); break; // Disable interrupt handler + case MF_TIMER_TEMP: NVIC_DisableIRQ(TIMER1_IRQn); break; // Disable interrupt handler } // We NEED memory barriers to ensure Interrupts are actually disabled! @@ -152,22 +152,22 @@ FORCE_INLINE static void HAL_timer_disable_interrupt(const uint8_t timer_num) { // This function is missing from CMSIS FORCE_INLINE static bool NVIC_GetEnableIRQ(IRQn_Type IRQn) { - return (NVIC->ISER[((uint32_t)IRQn) >> 5] & (1 << ((uint32_t)IRQn) & 0x1F)) != 0; + return TEST(NVIC->ISER[uint32_t(IRQn) >> 5], uint32_t(IRQn) & 0x1F); } FORCE_INLINE static bool HAL_timer_interrupt_enabled(const uint8_t timer_num) { switch (timer_num) { - case 0: return NVIC_GetEnableIRQ(TIMER0_IRQn); // Check if interrupt is enabled or not - case 1: return NVIC_GetEnableIRQ(TIMER1_IRQn); // Check if interrupt is enabled or not + case MF_TIMER_STEP: return NVIC_GetEnableIRQ(TIMER0_IRQn); // Check if interrupt is enabled or not + case MF_TIMER_TEMP: return NVIC_GetEnableIRQ(TIMER1_IRQn); // Check if interrupt is enabled or not } return false; } FORCE_INLINE static void HAL_timer_isr_prologue(const uint8_t timer_num) { switch (timer_num) { - case 0: SBI(STEP_TIMER_PTR->IR, SBIT_CNTEN); break; - case 1: SBI(TEMP_TIMER_PTR->IR, SBIT_CNTEN); break; + case MF_TIMER_STEP: SBI(STEP_TIMER_PTR->IR, SBIT_CNTEN); break; + case MF_TIMER_TEMP: SBI(TEMP_TIMER_PTR->IR, SBIT_CNTEN); break; } } -#define HAL_timer_isr_epilogue(TIMER_NUM) +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 a48a820dc4..3fc973e9f5 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" { @@ -36,40 +36,7 @@ extern int millis(); ////////////////////////////////////////////////////////////////////////////////////// -// These two routines are exact copies of the lpc17xx_i2c.c routines. Couldn't link to -// to the lpc17xx_i2c.c routines so had to copy them into this file & rename them. - -static uint32_t _I2C_Start(LPC_I2C_TypeDef *I2Cx) { - // Reset STA, STO, SI - I2Cx->I2CONCLR = I2C_I2CONCLR_SIC|I2C_I2CONCLR_STOC|I2C_I2CONCLR_STAC; - - // Enter to Master Transmitter mode - I2Cx->I2CONSET = I2C_I2CONSET_STA; - - // Wait for complete - while (!(I2Cx->I2CONSET & I2C_I2CONSET_SI)); - I2Cx->I2CONCLR = I2C_I2CONCLR_STAC; - return (I2Cx->I2STAT & I2C_STAT_CODE_BITMASK); -} - -static void _I2C_Stop (LPC_I2C_TypeDef *I2Cx) { - /* Make sure start bit is not active */ - if (I2Cx->I2CONSET & I2C_I2CONSET_STA) - I2Cx->I2CONCLR = I2C_I2CONCLR_STAC; - - I2Cx->I2CONSET = I2C_I2CONSET_STO|I2C_I2CONSET_AA; - I2Cx->I2CONCLR = I2C_I2CONCLR_SIC; -} - -////////////////////////////////////////////////////////////////////////////////////// - -#define I2CDEV_S_ADDR 0x78 // from SSD1306 //actual address is 0x3C - shift left 1 with LSB set to 0 to indicate write - -#define BUFFER_SIZE 0x1 // only do single byte transfers with LCDs - -I2C_M_SETUP_Type transferMCfg; - -#define I2C_status (LPC_I2C1->I2STAT & I2C_STAT_CODE_BITMASK) +#define I2CDEV_S_ADDR 0x78 // From SSD1306 (actual address is 0x3C - shift left 1 with LSB set to 0 to indicate write) // Send slave address and write bit uint8_t u8g_i2c_start(const uint8_t sla) { @@ -115,7 +82,6 @@ uint8_t u8g_i2c_send_byte(uint8_t data) { void u8g_i2c_stop() { } - #ifdef __cplusplus } #endif diff --git a/Marlin/src/HAL/LPC1768/u8g/LCD_defines.h b/Marlin/src/HAL/LPC1768/u8g/LCD_defines.h index d2260037b6..5b58f1223a 100644 --- a/Marlin/src/HAL/LPC1768/u8g/LCD_defines.h +++ b/Marlin/src/HAL/LPC1768/u8g/LCD_defines.h @@ -25,25 +25,22 @@ * LPC1768 LCD-specific defines */ -// The following are optional depending on the platform. +#ifndef U8G_HAL_LINKS -// definitions of HAL specific com and device drivers. -uint8_t u8g_com_HAL_LPC1768_hw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr); -uint8_t u8g_com_HAL_LPC1768_sw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr); -uint8_t u8g_com_HAL_LPC1768_ST7920_hw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr); -uint8_t u8g_com_HAL_LPC1768_ST7920_sw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr); -uint8_t u8g_com_HAL_LPC1768_ssd_hw_i2c_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr); + #define U8G_COM_SSD_I2C_HAL u8g_com_arduino_ssd_i2c_fn // See U8glib-HAL +#else -// connect U8g com generic com names to the desired driver -#define U8G_COM_HW_SPI u8g_com_HAL_LPC1768_hw_spi_fn // use LPC1768 specific hardware SPI routine -#define U8G_COM_SW_SPI u8g_com_HAL_LPC1768_sw_spi_fn // use LPC1768 specific software SPI routine -#define U8G_COM_ST7920_HW_SPI u8g_com_HAL_LPC1768_ST7920_hw_spi_fn -#define U8G_COM_ST7920_SW_SPI u8g_com_HAL_LPC1768_ST7920_sw_spi_fn -#define U8G_COM_SSD_I2C u8g_com_HAL_LPC1768_ssd_hw_i2c_fn + uint8_t u8g_com_HAL_LPC1768_hw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr); + uint8_t u8g_com_HAL_LPC1768_sw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr); + uint8_t u8g_com_HAL_LPC1768_ST7920_hw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr); + uint8_t u8g_com_HAL_LPC1768_ST7920_sw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr); + uint8_t u8g_com_HAL_LPC1768_ssd_hw_i2c_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr); -// let these default for now -#define U8G_COM_PARALLEL u8g_com_null_fn -#define U8G_COM_T6963 u8g_com_null_fn -#define U8G_COM_FAST_PARALLEL u8g_com_null_fn -#define U8G_COM_UC_I2C u8g_com_null_fn + #define U8G_COM_HAL_SW_SPI_FN u8g_com_HAL_LPC1768_sw_spi_fn + #define U8G_COM_HAL_HW_SPI_FN u8g_com_HAL_LPC1768_hw_spi_fn + #define U8G_COM_ST7920_HAL_SW_SPI u8g_com_HAL_LPC1768_ST7920_sw_spi_fn + #define U8G_COM_ST7920_HAL_HW_SPI u8g_com_HAL_LPC1768_ST7920_hw_spi_fn + #define U8G_COM_SSD_I2C_HAL u8g_com_HAL_LPC1768_ssd_hw_i2c_fn + +#endif diff --git a/Marlin/src/HAL/LPC1768/u8g/LCD_pin_routines.c b/Marlin/src/HAL/LPC1768/u8g/LCD_pin_routines.c index 466fc80203..51ad7e095b 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 d60d93dadd..45e0610fb1 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_hw_spi.cpp b/Marlin/src/HAL/LPC1768/u8g/u8g_com_HAL_LPC1768_hw_spi.cpp index 057e10e0f5..406fc4840c 100644 --- a/Marlin/src/HAL/LPC1768/u8g/u8g_com_HAL_LPC1768_hw_spi.cpp +++ b/Marlin/src/HAL/LPC1768/u8g/u8g_com_HAL_LPC1768_hw_spi.cpp @@ -59,13 +59,16 @@ #if HAS_MARLINUI_U8GLIB -#include +#include #include "../../shared/HAL_SPI.h" -void spiBegin(); -void spiInit(uint8_t spiRate); -void spiSend(uint8_t b); -void spiSend(const uint8_t* buf, size_t n); +#ifndef LCD_SPI_SPEED + #ifdef SD_SPI_SPEED + #define LCD_SPI_SPEED SD_SPI_SPEED // Assume SPI speed shared with SD + #else + #define LCD_SPI_SPEED SPI_FULL_SPEED // Use full speed if SD speed is not supplied + #endif +#endif uint8_t u8g_com_HAL_LPC1768_hw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr) { switch (msg) { @@ -81,10 +84,7 @@ uint8_t u8g_com_HAL_LPC1768_hw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, u8g_SetPIOutput(u8g, U8G_PI_RESET); u8g_Delay(5); spiBegin(); - #ifndef SPI_SPEED - #define SPI_SPEED SPI_FULL_SPEED // use same SPI speed as SD card - #endif - spiInit(SPI_SPEED); + spiInit(LCD_SPI_SPEED); break; case U8G_COM_MSG_ADDRESS: /* define cmd (arg_val = 0) or data mode (arg_val = 1) */ @@ -125,5 +125,4 @@ uint8_t u8g_com_HAL_LPC1768_hw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, } #endif // HAS_MARLINUI_U8GLIB - #endif // TARGET_LPC1768 diff --git a/Marlin/src/HAL/LPC1768/u8g/u8g_com_HAL_LPC1768_ssd_hw_i2c.cpp b/Marlin/src/HAL/LPC1768/u8g/u8g_com_HAL_LPC1768_ssd_hw_i2c.cpp index 6f7efba4ae..3dea365ac7 100644 --- a/Marlin/src/HAL/LPC1768/u8g/u8g_com_HAL_LPC1768_ssd_hw_i2c.cpp +++ b/Marlin/src/HAL/LPC1768/u8g/u8g_com_HAL_LPC1768_ssd_hw_i2c.cpp @@ -79,7 +79,7 @@ #if HAS_MARLINUI_U8GLIB -#include +#include #define I2C_SLA (0x3C*2) //#define I2C_CMD_MODE 0x080 @@ -194,5 +194,4 @@ uint8_t u8g_com_HAL_LPC1768_ssd_hw_i2c_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_v } #endif // HAS_MARLINUI_U8GLIB - #endif // TARGET_LPC1768 diff --git a/Marlin/src/HAL/LPC1768/u8g/u8g_com_HAL_LPC1768_st7920_hw_spi.cpp b/Marlin/src/HAL/LPC1768/u8g/u8g_com_HAL_LPC1768_st7920_hw_spi.cpp index 592e27f6c0..c029dc0680 100644 --- a/Marlin/src/HAL/LPC1768/u8g/u8g_com_HAL_LPC1768_st7920_hw_spi.cpp +++ b/Marlin/src/HAL/LPC1768/u8g/u8g_com_HAL_LPC1768_st7920_hw_spi.cpp @@ -59,14 +59,14 @@ #if HAS_MARLINUI_U8GLIB -#include +#include #include "../../shared/HAL_SPI.h" #include "../../shared/Delay.h" void spiBegin(); void spiInit(uint8_t spiRate); void spiSend(uint8_t b); -void spiSend(const uint8_t* buf, size_t n); +void spiSend(const uint8_t *buf, size_t n); static uint8_t rs_last_state = 255; @@ -134,5 +134,4 @@ uint8_t u8g_com_HAL_LPC1768_ST7920_hw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t ar } #endif // HAS_MARLINUI_U8GLIB - #endif // TARGET_LPC1768 diff --git a/Marlin/src/HAL/LPC1768/u8g/u8g_com_HAL_LPC1768_st7920_sw_spi.cpp b/Marlin/src/HAL/LPC1768/u8g/u8g_com_HAL_LPC1768_st7920_sw_spi.cpp index 10d8494162..e159ebaa0c 100644 --- a/Marlin/src/HAL/LPC1768/u8g/u8g_com_HAL_LPC1768_st7920_sw_spi.cpp +++ b/Marlin/src/HAL/LPC1768/u8g/u8g_com_HAL_LPC1768_st7920_sw_spi.cpp @@ -57,14 +57,16 @@ #include "../../../inc/MarlinConfigPre.h" -#if ENABLED(U8GLIB_ST7920) +#if IS_U8GLIB_ST7920 -#include +#include #include #include "../../shared/Delay.h" +#include "../../shared/HAL_SPI.h" -#undef SPI_SPEED -#define SPI_SPEED 3 // About 1 MHz +#ifndef LCD_SPI_SPEED + #define LCD_SPI_SPEED SPI_EIGHTH_SPEED // About 1 MHz +#endif static pin_t SCK_pin_ST7920_HAL, MOSI_pin_ST7920_HAL_HAL; static uint8_t SPI_speed = 0; @@ -92,7 +94,7 @@ uint8_t u8g_com_HAL_LPC1768_ST7920_sw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t ar u8g_SetPIOutput(u8g, U8G_PI_MOSI); u8g_Delay(5); - SPI_speed = swSpiInit(SPI_SPEED, SCK_pin_ST7920_HAL, MOSI_pin_ST7920_HAL_HAL); + SPI_speed = swSpiInit(LCD_SPI_SPEED, SCK_pin_ST7920_HAL, MOSI_pin_ST7920_HAL_HAL); u8g_SetPILevel(u8g, U8G_PI_CS, 0); u8g_SetPILevel(u8g, U8G_PI_SCK, 0); @@ -141,5 +143,5 @@ uint8_t u8g_com_HAL_LPC1768_ST7920_sw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t ar return 1; } -#endif // U8GLIB_ST7920 +#endif // IS_U8GLIB_ST7920 #endif // TARGET_LPC1768 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 ca9d6ecfbe..77e5870de7 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 @@ -57,23 +57,25 @@ #include "../../../inc/MarlinConfigPre.h" -#if HAS_MARLINUI_U8GLIB && DISABLED(U8GLIB_ST7920) +#if HAS_MARLINUI_U8GLIB && !IS_U8GLIB_ST7920 #include +#include "../../shared/HAL_SPI.h" -#undef SPI_SPEED -#define SPI_SPEED 2 // About 2 MHz +#ifndef LCD_SPI_SPEED + #define LCD_SPI_SPEED SPI_QUARTER_SPEED // About 2 MHz +#endif #include #include #include #include -#include +#include uint8_t swSpiTransfer_mode_0(uint8_t b, const uint8_t spi_speed, const pin_t sck_pin, const pin_t miso_pin, const pin_t mosi_pin ) { - LOOP_L_N(i, 8) { + for (uint8_t i = 0; i < 8; ++i) { if (spi_speed == 0) { LPC176x::gpio_set(mosi_pin, !!(b & 0x80)); LPC176x::gpio_set(sck_pin, HIGH); @@ -83,16 +85,16 @@ uint8_t swSpiTransfer_mode_0(uint8_t b, const uint8_t spi_speed, const pin_t sck } else { const uint8_t state = (b & 0x80) ? HIGH : LOW; - LOOP_L_N(j, spi_speed) + for (uint8_t j = 0; j < spi_speed; ++j) LPC176x::gpio_set(mosi_pin, state); - LOOP_L_N(j, spi_speed + (miso_pin >= 0 ? 0 : 1)) + for (uint8_t j = 0; j < spi_speed + (miso_pin >= 0 ? 0 : 1); ++j) LPC176x::gpio_set(sck_pin, HIGH); b <<= 1; if (miso_pin >= 0 && LPC176x::gpio_get(miso_pin)) b |= 1; - LOOP_L_N(j, spi_speed) + for (uint8_t j = 0; j < spi_speed; ++j) LPC176x::gpio_set(sck_pin, LOW); } } @@ -102,7 +104,7 @@ uint8_t swSpiTransfer_mode_0(uint8_t b, const uint8_t spi_speed, const pin_t sck uint8_t swSpiTransfer_mode_3(uint8_t b, const uint8_t spi_speed, const pin_t sck_pin, const pin_t miso_pin, const pin_t mosi_pin ) { - LOOP_L_N(i, 8) { + for (uint8_t i = 0; i < 8; ++i) { const uint8_t state = (b & 0x80) ? HIGH : LOW; if (spi_speed == 0) { LPC176x::gpio_set(sck_pin, LOW); @@ -111,13 +113,13 @@ uint8_t swSpiTransfer_mode_3(uint8_t b, const uint8_t spi_speed, const pin_t sck LPC176x::gpio_set(sck_pin, HIGH); } else { - LOOP_L_N(j, spi_speed + (miso_pin >= 0 ? 0 : 1)) + for (uint8_t j = 0; j < spi_speed + (miso_pin >= 0 ? 0 : 1); ++j) LPC176x::gpio_set(sck_pin, LOW); - LOOP_L_N(j, spi_speed) + for (uint8_t j = 0; j < spi_speed; ++j) LPC176x::gpio_set(mosi_pin, state); - LOOP_L_N(j, spi_speed) + for (uint8_t j = 0; j < spi_speed; ++j) LPC176x::gpio_set(sck_pin, HIGH); } b <<= 1; @@ -129,8 +131,8 @@ 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_HAL_LPC1768_shift_out(uint8_t dataPin, uint8_t clockPin, uint8_t val) { - #if EITHER(FYSETC_MINI_12864, MKS_MINI_12864) +static void u8g_sw_spi_shift_out(uint8_t dataPin, uint8_t clockPin, uint8_t val) { + #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); @@ -145,7 +147,7 @@ uint8_t u8g_com_HAL_LPC1768_sw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, u8g_SetPIOutput(u8g, U8G_PI_CS); u8g_SetPIOutput(u8g, U8G_PI_A0); if (U8G_PIN_NONE != u8g->pin_list[U8G_PI_RESET]) u8g_SetPIOutput(u8g, U8G_PI_RESET); - SPI_speed = swSpiInit(SPI_SPEED, u8g->pin_list[U8G_PI_SCK], u8g->pin_list[U8G_PI_MOSI]); + SPI_speed = swSpiInit(LCD_SPI_SPEED, u8g->pin_list[U8G_PI_SCK], u8g->pin_list[U8G_PI_MOSI]); u8g_SetPILevel(u8g, U8G_PI_SCK, 0); u8g_SetPILevel(u8g, U8G_PI_MOSI, 0); break; @@ -158,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 EITHER(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); @@ -174,13 +176,13 @@ uint8_t u8g_com_HAL_LPC1768_sw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, break; case U8G_COM_MSG_WRITE_BYTE: - u8g_sw_spi_HAL_LPC1768_shift_out(u8g->pin_list[U8G_PI_MOSI], u8g->pin_list[U8G_PI_SCK], arg_val); + u8g_sw_spi_shift_out(u8g->pin_list[U8G_PI_MOSI], u8g->pin_list[U8G_PI_SCK], arg_val); break; case U8G_COM_MSG_WRITE_SEQ: { uint8_t *ptr = (uint8_t *)arg_ptr; while (arg_val > 0) { - u8g_sw_spi_HAL_LPC1768_shift_out(u8g->pin_list[U8G_PI_MOSI], u8g->pin_list[U8G_PI_SCK], *ptr++); + u8g_sw_spi_shift_out(u8g->pin_list[U8G_PI_MOSI], u8g->pin_list[U8G_PI_SCK], *ptr++); arg_val--; } } @@ -189,7 +191,7 @@ uint8_t u8g_com_HAL_LPC1768_sw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, case U8G_COM_MSG_WRITE_SEQ_P: { uint8_t *ptr = (uint8_t *)arg_ptr; while (arg_val > 0) { - u8g_sw_spi_HAL_LPC1768_shift_out(u8g->pin_list[U8G_PI_MOSI], u8g->pin_list[U8G_PI_SCK], u8g_pgm_read(ptr)); + u8g_sw_spi_shift_out(u8g->pin_list[U8G_PI_MOSI], u8g->pin_list[U8G_PI_SCK], u8g_pgm_read(ptr)); ptr++; arg_val--; } @@ -203,5 +205,5 @@ uint8_t u8g_com_HAL_LPC1768_sw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, return 1; } -#endif // HAS_MARLINUI_U8GLIB && !U8GLIB_ST7920 +#endif // HAS_MARLINUI_U8GLIB && !IS_U8GLIB_ST7920 #endif // TARGET_LPC1768 diff --git a/Marlin/src/HAL/LPC1768/upload_extra_script.py b/Marlin/src/HAL/LPC1768/upload_extra_script.py index 9b0d0617a0..6755245436 100755 --- a/Marlin/src/HAL/LPC1768/upload_extra_script.py +++ b/Marlin/src/HAL/LPC1768/upload_extra_script.py @@ -1,145 +1,142 @@ # -# sets output_port +# upload_extra_script.py +# set the output_port # if target_filename is found then that drive is used # else if target_drive is found then that drive is used # from __future__ import print_function -target_filename = "FIRMWARE.CUR" -target_drive = "REARM" +import pioutil +if pioutil.is_pio_build(): -import os -import getpass -import platform + target_filename = "FIRMWARE.CUR" + target_drive = "REARM" -current_OS = platform.system() -Import("env") + import platform + current_OS = platform.system() -def print_error(e): - print('\nUnable to find destination disk (%s)\n' \ - 'Please select it in platformio.ini using the upload_port keyword ' \ - '(https://docs.platformio.org/en/latest/projectconf/section_env_upload.html) ' \ - 'or copy the firmware (.pio/build/%s/firmware.bin) manually to the appropriate disk\n' \ - %(e, env.get('PIOENV'))) + env = pioutil.env -try: - if current_OS == 'Windows': - # - # 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 - # + def print_error(e): + print('\nUnable to find destination disk (%s)\n' \ + 'Please select it in platformio.ini using the upload_port keyword ' \ + '(https://docs.platformio.org/en/latest/projectconf/section_env_upload.html) ' \ + 'or copy the firmware (.pio/build/%s/firmware.bin) manually to the appropriate disk\n' \ + %(e, env.get('PIOENV'))) - # - # get all drives on this computer - # - import subprocess - # typical result (string): 'Drives: C:\ D:\ E:\ F:\ G:\ H:\ I:\ J:\ K:\ L:\ M:\ Y:\ Z:\' - driveStr = str(subprocess.check_output("fsutil fsinfo drives")) - # typical result (string): 'C:\ D:\ E:\ F:\ G:\ H:\ I:\ J:\ K:\ L:\ M:\ Y:\ Z:\' - # driveStr = driveStr.strip().lstrip('Drives: ') <- Doesn't work in other Languages as English. In German is "Drives:" = "Laufwerke:" - FirstFound = driveStr.find(':',0,-1) # Find the first ":" and - driveStr = driveStr[FirstFound + 1 : -1] # truncate to the rest - # typical result (array of stings): ['C:\\', 'D:\\', 'E:\\', 'F:\\', - # 'G:\\', 'H:\\', 'I:\\', 'J:\\', 'K:\\', 'L:\\', 'M:\\', 'Y:\\', 'Z:\\'] - drives = driveStr.split() + def before_upload(source, target, env): + try: + from pathlib import Path + # + # Find a disk for upload + # + upload_disk = 'Disk not found' + target_file_found = False + target_drive_found = False + if current_OS == 'Windows': + # + # 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 + from ctypes import windll + from pathlib import PureWindowsPath - upload_disk = 'Disk not found' - target_file_found = False - target_drive_found = False - for drive in drives: - final_drive_name = drive.strip().rstrip('\\') # typical result (string): 'C:' - try: - volume_info = str(subprocess.check_output('cmd /C dir ' + final_drive_name, stderr=subprocess.STDOUT)) - except Exception as e: - continue - else: - if target_drive in volume_info and target_file_found == False: # set upload if not found target file yet - target_drive_found = True - upload_disk = final_drive_name - if target_filename in volume_info: - if target_file_found == False: - upload_disk = final_drive_name - target_file_found = True + # 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: + if bitmask & 1: + drives.append(letter) + bitmask >>= 1 - # - # set upload_port to drive if found - # + for drive in drives: + final_drive_name = drive + ':' + # print ('disc check: {}'.format(final_drive_name)) + try: + 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: # set upload + upload_disk = PureWindowsPath(final_drive_name) + 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 + break - if target_file_found == True or target_drive_found == True: - env.Replace( - UPLOAD_PORT=upload_disk - ) - print('upload disk: ', upload_disk) - else: - print_error('Autodetect Error') + elif current_OS == 'Linux': + # + # platformio.ini will accept this for a Linux upload port designation: 'upload_port = /media/media_name/drive' + # + import getpass + user = getpass.getuser() + mpath = Path('/media', user) + drives = [ x for x in mpath.iterdir() if x.is_dir() ] + if target_drive in drives: # If target drive is found, use it. + target_drive_found = True + upload_disk = mpath / target_drive + else: + for drive in drives: + try: + fpath = mpath / drive + filenames = [ x.name for x in fpath.iterdir() if x.is_file() ] + except: + continue + else: + if target_filename in filenames: + upload_disk = mpath / drive + target_file_found = True + break + # + # set upload_port to drive if found + # - elif current_OS == 'Linux': - # - # platformio.ini will accept this for a Linux upload port designation: 'upload_port = /media/media_name/drive' - # - upload_disk = 'Disk not found' - target_file_found = False - target_drive_found = False - drives = os.listdir(os.path.join(os.sep, 'media', getpass.getuser())) - if target_drive in drives: # If target drive is found, use it. - target_drive_found = True - upload_disk = os.path.join(os.sep, 'media', getpass.getuser(), target_drive) + os.sep - else: - for drive in drives: - try: - files = os.listdir(os.path.join(os.sep, 'media', getpass.getuser(), drive)) - except: - continue - else: - if target_filename in files: - upload_disk = os.path.join(os.sep, 'media', getpass.getuser(), drive) + os.sep - target_file_found = True - break - # - # set upload_port to drive if found - # + if target_file_found or target_drive_found: + env.Replace( + UPLOAD_FLAGS="-P$UPLOAD_PORT" + ) - if target_file_found or target_drive_found: - env.Replace( - UPLOAD_FLAGS="-P$UPLOAD_PORT", - UPLOAD_PORT=upload_disk - ) - print('upload disk: ', upload_disk) - else: - print_error('Autodetect Error') + elif current_OS == 'Darwin': # MAC + # + # platformio.ini will accept this for a OSX upload port designation: 'upload_port = /media/media_name/drive' + # + dpath = Path('/Volumes') # human readable names + drives = [ x for x in dpath.iterdir() if x.is_dir() ] + if target_drive in drives and not target_file_found: # set upload if not found target file yet + target_drive_found = True + upload_disk = dpath / target_drive + for drive in drives: + try: + fpath = dpath / drive # will get an error if the drive is protected + filenames = [ x.name for x in fpath.iterdir() if x.is_file() ] + except: + continue + else: + if target_filename in filenames: + upload_disk = dpath / drive + target_file_found = True + break - elif current_OS == 'Darwin': # MAC - # - # platformio.ini will accept this for a OSX upload port designation: 'upload_port = /media/media_name/drive' - # - upload_disk = 'Disk not found' - drives = os.listdir('/Volumes') # human readable names - target_file_found = False - target_drive_found = False - if target_drive in drives and target_file_found == False: # set upload if not found target file yet - target_drive_found = True - upload_disk = '/Volumes/' + target_drive + '/' - for drive in drives: - try: - filenames = os.listdir('/Volumes/' + drive + '/') # will get an error if the drive is protected - except: - continue - else: - if target_filename in filenames: - if target_file_found == False: - upload_disk = '/Volumes/' + drive + '/' - target_file_found = True - # - # set upload_port to drive if found - # + # + # Set upload_port to drive if found + # + if target_file_found or target_drive_found: + env.Replace(UPLOAD_PORT=str(upload_disk)) + print('\nUpload disk: ', upload_disk, '\n') + else: + print_error('Autodetect Error') - if target_file_found == True or target_drive_found == True: - env.Replace( - UPLOAD_PORT=upload_disk - ) - print('\nupload disk: ', upload_disk, '\n') - else: - print_error('Autodetect Error') + except Exception as e: + print_error(str(e)) -except Exception as e: - print_error(str(e)) + env.AddPreAction("upload", before_upload) diff --git a/Marlin/src/HAL/LPC1768/usb_serial.cpp b/Marlin/src/HAL/LPC1768/usb_serial.cpp index d225ce4188..3c1fce54f9 100644 --- a/Marlin/src/HAL/LPC1768/usb_serial.cpp +++ b/Marlin/src/HAL/LPC1768/usb_serial.cpp @@ -29,8 +29,8 @@ EmergencyParser::State emergency_state; -bool CDC_RecvCallback(const char buffer) { - emergency_parser.update(emergency_state, buffer); +bool CDC_RecvCallback(const char c) { + emergency_parser.update(emergency_state, c); return true; } diff --git a/Marlin/src/HAL/LPC1768/watchdog.cpp b/Marlin/src/HAL/LPC1768/watchdog.cpp deleted file mode 100644 index 3cd22d6651..0000000000 --- a/Marlin/src/HAL/LPC1768/watchdog.cpp +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Marlin 3D Printer Firmware - * Copyright (c) 2020 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 TARGET_LPC1768 - -#include "../../inc/MarlinConfig.h" - -#if ENABLED(USE_WATCHDOG) - -#include -#include "watchdog.h" - -void watchdog_init() { - #if ENABLED(WATCHDOG_RESET_MANUAL) - // We enable the watchdog timer, but only for the interrupt. - - // Configure WDT to only trigger an interrupt - // Disable WDT interrupt (just in case, to avoid triggering it!) - NVIC_DisableIRQ(WDT_IRQn); - - // We NEED memory barriers to ensure Interrupts are actually disabled! - // ( https://dzone.com/articles/nvic-disabling-interrupts-on-arm-cortex-m-and-the ) - __DSB(); - __ISB(); - - // Configure WDT to only trigger an interrupt - // Initialize WDT with the given parameters - WDT_Init(WDT_CLKSRC_IRC, WDT_MODE_INT_ONLY); - - // Configure and enable WDT interrupt. - NVIC_ClearPendingIRQ(WDT_IRQn); - NVIC_SetPriority(WDT_IRQn, 0); // Use highest priority, so we detect all kinds of lockups - NVIC_EnableIRQ(WDT_IRQn); - #else - WDT_Init(WDT_CLKSRC_IRC, WDT_MODE_RESET); - #endif - WDT_Start(WDT_TIMEOUT); -} - -void HAL_watchdog_refresh() { - WDT_Feed(); - #if DISABLED(PINS_DEBUGGING) && PIN_EXISTS(LED) - TOGGLE(LED_PIN); // heartbeat indicator - #endif -} - -// Timeout state -bool watchdog_timed_out() { return TEST(WDT_ReadTimeOutFlag(), 0); } -void watchdog_clear_timeout_flag() { WDT_ClrTimeOutFlag(); } - -#endif // USE_WATCHDOG -#endif // TARGET_LPC1768 diff --git a/Marlin/src/HAL/LPC1768/win_usb_driver/lpc176x_usb_driver.inf b/Marlin/src/HAL/LPC1768/win_usb_driver/lpc176x_usb_driver.inf index 4732eb8552..37d9a617db 100644 --- a/Marlin/src/HAL/LPC1768/win_usb_driver/lpc176x_usb_driver.inf +++ b/Marlin/src/HAL/LPC1768/win_usb_driver/lpc176x_usb_driver.inf @@ -8,14 +8,12 @@ DriverVer =04/14/2008, 5.1.2600.5512 [Manufacturer] %PROVIDER%=DeviceList,ntamd64 - [DeviceList] %DESCRIPTION%=LPC1768USB, USB\VID_1D50&PID_6029&MI_00 [DeviceList.ntamd64] %DESCRIPTION%=LPC1768USB, USB\VID_1D50&PID_6029&MI_00 - [LPC1768USB] include=mdmcpq.inf CopyFiles=FakeModemCopyFileSection @@ -28,9 +26,8 @@ AddService=usbser, 0x00000002, LowerFilter_Service_Inst [SerialPropPageAddReg] HKR,,EnumPropPages32,,"MsPorts.dll,SerialPortPropPageProvider" - [Strings] PROVIDER = "marlinfw.org" DRIVER.SVC = "Marlin USB Driver" DESCRIPTION= "Marlin USB Serial" -COMPOSITE = "Marlin USB VCOM" \ No newline at end of file +COMPOSITE = "Marlin USB VCOM" diff --git a/Marlin/src/HAL/NATIVE_SIM/HAL.cpp b/Marlin/src/HAL/NATIVE_SIM/HAL.cpp new file mode 100644 index 0000000000..18c225fb3f --- /dev/null +++ b/Marlin/src/HAL/NATIVE_SIM/HAL.cpp @@ -0,0 +1,67 @@ +/** + * 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 + * (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 . + * + */ + +/** + * Copyright (c) 1998, 2015 Todd C. Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifdef __PLAT_NATIVE_SIM__ + +#ifndef HAS_LIBBSD + + #include "HAL.h" + + /** + * Copy string src to buffer dst of size dsize. At most dsize-1 + * chars will be copied. Always NUL terminates (unless dsize == 0). + * Returns strlen(src); if retval >= dsize, truncation occurred. + */ + size_t MarlinHAL::_strlcpy(char *dst, const char *src, size_t dsize) { + const char *osrc = src; + size_t nleft = dsize; + + // Copy as many bytes as will fit. + if (nleft != 0) while (--nleft != 0) if ((*dst++ = *src++) == '\0') break; + + // Not enough room in dst, add NUL and traverse rest of src. + if (nleft == 0) { + if (dsize != 0) *dst = '\0'; // NUL-terminate dst + while (*src++) { /* nada */ } + } + + return (src - osrc - 1); // count does not include NUL + } + +#endif // HAS_LIBBSD +#endif // __PLAT_NATIVE_SIM__ diff --git a/Marlin/src/HAL/NATIVE_SIM/HAL.h b/Marlin/src/HAL/NATIVE_SIM/HAL.h new file mode 100644 index 0000000000..b3b66d54d3 --- /dev/null +++ b/Marlin/src/HAL/NATIVE_SIM/HAL.h @@ -0,0 +1,253 @@ +/** + * 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 + +#include +#include +#undef min +#undef max +#include +#include "pinmapping.h" + +void _printf (const char *format, ...); +void _putc(uint8_t c); +uint8_t _getc(); + +//arduino: Print.h +#define DEC 10 +#define HEX 16 +#define OCT 8 +#define BIN 2 +//arduino: binary.h (weird defines) +#define B01 1 +#define B10 2 + +#include "../shared/Marduino.h" +#include "../shared/math_32bit.h" +#include "../shared/HAL_SPI.h" +#include "fastio.h" +#include "serial.h" + +// ------------------------ +// Defines +// ------------------------ + +#define CPU_32_BIT + +class Servo; +typedef Servo hal_servo_t; + +#define F_CPU 100000000 +#define SystemCoreClock F_CPU + +#define CPU_ST7920_DELAY_1 600 +#define CPU_ST7920_DELAY_2 750 +#define CPU_ST7920_DELAY_3 750 + +// ------------------------ +// Serial ports +// ------------------------ + +extern MSerialT serial_stream_0; +extern MSerialT serial_stream_1; +extern MSerialT serial_stream_2; +extern MSerialT serial_stream_3; + +#define _MSERIAL(X) serial_stream_##X + +#define SERIAL_INDEX_MIN 0 +#define SERIAL_INDEX_MAX 3 +#include "../shared/serial_ports.h" + +// ------------------------ +// Interrupts +// ------------------------ + +#define CRITICAL_SECTION_START() +#define CRITICAL_SECTION_END() + +// ------------------------ +// ADC +// ------------------------ + +#define HAL_ADC_VREF_MV 5000 +#define HAL_ADC_RESOLUTION 10 + +/* ---------------- Delay in cycles */ + +#define DELAY_CYCLES(x) Kernel::delayCycles(x) +#define SYSTEM_YIELD() Kernel::yield() + +// Maple Compatibility +typedef void (*systickCallback_t)(void); +void systick_attach_callback(systickCallback_t cb); +extern volatile uint32_t systick_uptime_millis; + +// Marlin uses strstr in constexpr context, this is not supported, workaround by defining constexpr versions of the required functions. +#define strstr(a, b) strstr_constexpr((a), (b)) + +constexpr inline std::size_t strlen_constexpr(const char* str) { + // https://github.com/gcc-mirror/gcc/blob/5c7634a0e5f202935aa6c11b6ea953b8bf80a00a/libstdc%2B%2B-v3/include/bits/char_traits.h#L329 + if (str != nullptr) { + std::size_t i = 0; + while (str[i] != '\0') ++i; + return i; + } + return 0; +} + +constexpr inline int strncmp_constexpr(const char* lhs, const char* rhs, std::size_t count) { + // https://github.com/gcc-mirror/gcc/blob/13b9cbfc32fe3ac4c81c4dd9c42d141c8fb95db4/libstdc%2B%2B-v3/include/bits/char_traits.h#L655 + if (lhs == nullptr || rhs == nullptr) + return rhs != nullptr ? -1 : 1; + + for (std::size_t i = 0; i < count; ++i) + if (lhs[i] != rhs[i]) + return lhs[i] < rhs[i] ? -1 : 1; + else if (lhs[i] == '\0') + return 0; + + return 0; +} + +constexpr inline const char* strstr_constexpr(const char* str, const char* target) { + // https://github.com/freebsd/freebsd/blob/master/sys/libkern/strstr.c + if (char c = target != nullptr ? *target++ : '\0'; c != '\0' && str != nullptr) { + std::size_t len = strlen_constexpr(target); + do { + char sc = {}; + do { + if ((sc = *str++) == '\0') return nullptr; + } while (sc != c); + } while (strncmp_constexpr(str, target, len) != 0); + --str; + } + return str; +} + +constexpr inline char* strstr_constexpr(char* str, const char* target) { + // https://github.com/freebsd/freebsd/blob/master/sys/libkern/strstr.c + if (char c = target != nullptr ? *target++ : '\0'; c != '\0' && str != nullptr) { + std::size_t len = strlen_constexpr(target); + do { + char sc = {}; + do { + if ((sc = *str++) == '\0') return nullptr; + } while (sc != c); + } while (strncmp_constexpr(str, target, len) != 0); + --str; + } + return str; +} + +// ------------------------ +// Free Memory Accessor +// ------------------------ + +#pragma GCC diagnostic push +#if GCC_VERSION <= 50000 + #pragma GCC diagnostic ignored "-Wunused-function" +#endif + +int freeMemory(); + +#pragma GCC diagnostic pop + +// ------------------------ +// MarlinHAL Class +// ------------------------ + +class MarlinHAL { +public: + + // Earliest possible init, before setup() + MarlinHAL() {} + + // Watchdog + static void watchdog_init(); + static void watchdog_refresh(); + + 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 true; } + static void isr_on() {} + static void isr_off() {} + + static void delay_ms(const int ms) { delay(ms); } + + // Tasks, called from marlin.idle() + static void idletask(); + + // Reset + static constexpr uint8_t reset_reason = RST_POWER_ON; + static uint8_t get_reset_source() { return reset_reason; } + static void clear_reset_source() {} + + // Free SRAM + static int freeMemory() { return ::freeMemory(); } + + // + // ADC Methods + // + + static uint8_t active_ch; + + // 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 uint8_t ch); + + // Begin ADC sampling on the given channel. Called from Temperature::isr! + static void adc_start(const uint8_t ch); + + // Is the ADC ready for reading? + static bool adc_ready(); + + // The current value of the ADC register + static uint16_t adc_value(); + + /** + * 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] + */ + 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) {} + + #ifndef HAS_LIBBSD + /** + * Redirect missing strlcpy here + */ + static size_t _strlcpy(char *dst, const char *src, size_t dsize); + #define strlcpy hal._strlcpy + #endif + +}; diff --git a/Marlin/src/HAL/NATIVE_SIM/MarlinSPI.h b/Marlin/src/HAL/NATIVE_SIM/MarlinSPI.h new file mode 100644 index 0000000000..0c447ba4cb --- /dev/null +++ b/Marlin/src/HAL/NATIVE_SIM/MarlinSPI.h @@ -0,0 +1,26 @@ +/** + * 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 + +#include + +using MarlinSPI = SPIClass; diff --git a/Marlin/src/HAL/NATIVE_SIM/Servo.cpp b/Marlin/src/HAL/NATIVE_SIM/Servo.cpp new file mode 100644 index 0000000000..a15eda1bf6 --- /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 0000000000..428871142c --- /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 0000000000..e74c312274 --- /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 new file mode 100644 index 0000000000..e0b7af09f9 --- /dev/null +++ b/Marlin/src/HAL/NATIVE_SIM/fastio.h @@ -0,0 +1,113 @@ +/** + * 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 + +/** + * Fast I/O Routines for X86_64 + */ + +#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) + +#define SET_MODE(IO, mode) Gpio::setMode(IO, mode) + +#define WRITE_PIN_SET(IO) Gpio::set(IO) +#define WRITE_PIN_CLR(IO) Gpio::clear(IO) + +#define READ_PIN(IO) Gpio::get(IO) +#define WRITE_PIN(IO,V) Gpio::set(IO, V) + +/** + * Magic I/O routines + * + * Now you can simply SET_OUTPUT(STEP); WRITE(STEP, HIGH); WRITE(STEP, LOW); + * + * Why double up on these macros? see https://gcc.gnu.org/onlinedocs/cpp/Stringification.html + */ + +/// Read a pin +#define _READ(IO) READ_PIN(IO) + +/// Write to a pin +#define _WRITE(IO,V) WRITE_PIN(IO,V) + +/// toggle a pin +#define _TOGGLE(IO) _WRITE(IO, !READ(IO)) + +/// set pin as input +#define _SET_INPUT(IO) SET_DIR_INPUT(IO) + +/// set pin as output +#define _SET_OUTPUT(IO) SET_DIR_OUTPUT(IO) + +/// set pin as input with pullup mode +#define _PULLUP(IO,V) pinMode(IO, (V) ? INPUT_PULLUP : INPUT) + +/// set pin as input with pulldown mode +#define _PULLDOWN(IO,V) pinMode(IO, (V) ? INPUT_PULLDOWN : INPUT) + +// hg42: all pins can be input or output (I hope) +// hg42: undefined pins create compile error (IO, is no pin) +// hg42: currently not used, but was used by pinsDebug + +/// check if pin is an input +#define _IS_INPUT(IO) (IO >= 0) + +/// check if pin is an output +#define _IS_OUTPUT(IO) (IO >= 0) + +/// Read a pin wrapper +#define READ(IO) _READ(IO) + +/// Write to a pin wrapper +#define WRITE(IO,V) _WRITE(IO,V) + +/// toggle a pin wrapper +#define TOGGLE(IO) _TOGGLE(IO) + +/// set pin as input wrapper +#define SET_INPUT(IO) _SET_INPUT(IO) +/// set pin as input with pullup wrapper +#define SET_INPUT_PULLUP(IO) do{ _SET_INPUT(IO); _PULLUP(IO, HIGH); }while(0) +/// set pin as input with pulldown wrapper +#define SET_INPUT_PULLDOWN(IO) do{ _SET_INPUT(IO); _PULLDOWN(IO, HIGH); }while(0) +/// set pin as output wrapper - reads the pin and sets the output to that value +#define SET_OUTPUT(IO) do{ _WRITE(IO, _READ(IO)); _SET_OUTPUT(IO); }while(0) +// set pin as PWM +#define SET_PWM(IO) SET_OUTPUT(IO) + +/// check if pin is an input wrapper +#define IS_INPUT(IO) _IS_INPUT(IO) +/// check if pin is an output wrapper +#define IS_OUTPUT(IO) _IS_OUTPUT(IO) + +// Shorthand +#define OUT_WRITE(IO,V) do{ SET_OUTPUT(IO); WRITE(IO,V); }while(0) + +// digitalRead/Write wrappers +#define extDigitalRead(IO) digitalRead(IO) +#define extDigitalWrite(IO,V) digitalWrite(IO,V) diff --git a/Marlin/src/HAL/NATIVE_SIM/inc/Conditionals_LCD.h b/Marlin/src/HAL/NATIVE_SIM/inc/Conditionals_LCD.h new file mode 100644 index 0000000000..1ac02f1182 --- /dev/null +++ b/Marlin/src/HAL/NATIVE_SIM/inc/Conditionals_LCD.h @@ -0,0 +1,22 @@ +/** + * 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 diff --git a/Marlin/src/HAL/NATIVE_SIM/inc/Conditionals_adv.h b/Marlin/src/HAL/NATIVE_SIM/inc/Conditionals_adv.h new file mode 100644 index 0000000000..69b6b4848f --- /dev/null +++ b/Marlin/src/HAL/NATIVE_SIM/inc/Conditionals_adv.h @@ -0,0 +1,31 @@ +/** + * 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 + +// Add strcmp_P if missing +#ifndef strcmp_P + #define strcmp_P(a, b) strcmp((a), (b)) +#endif + +#ifndef strcat_P + #define strcat_P(dest, src) strcat((dest), (src)) +#endif diff --git a/Marlin/src/HAL/NATIVE_SIM/inc/Conditionals_post.h b/Marlin/src/HAL/NATIVE_SIM/inc/Conditionals_post.h new file mode 100644 index 0000000000..1ac02f1182 --- /dev/null +++ b/Marlin/src/HAL/NATIVE_SIM/inc/Conditionals_post.h @@ -0,0 +1,22 @@ +/** + * 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 diff --git a/Marlin/src/HAL/NATIVE_SIM/inc/Conditionals_type.h b/Marlin/src/HAL/NATIVE_SIM/inc/Conditionals_type.h new file mode 100644 index 0000000000..82f95a1035 --- /dev/null +++ b/Marlin/src/HAL/NATIVE_SIM/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/STM32_F4_F7/inc/SanityCheck.h b/Marlin/src/HAL/NATIVE_SIM/inc/SanityCheck.h similarity index 61% rename from Marlin/src/HAL/STM32_F4_F7/inc/SanityCheck.h rename to Marlin/src/HAL/NATIVE_SIM/inc/SanityCheck.h index 9bb36f3bbb..615e5254c9 100644 --- a/Marlin/src/HAL/STM32_F4_F7/inc/SanityCheck.h +++ b/Marlin/src/HAL/NATIVE_SIM/inc/SanityCheck.h @@ -1,6 +1,6 @@ /** * Marlin 3D Printer Firmware - * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] + * Copyright (c) 2021 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] * * Based on Sprinter and grbl. * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm @@ -22,20 +22,22 @@ #pragma once /** - * Test STM32F4/7-specific configuration values for errors at compile-time. + * Test X86_64-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(EMERGENCY_PARSER) - #error "EMERGENCY_PARSER is not yet implemented for STM32F4/7. Disable EMERGENCY_PARSER to continue." +// Emulating RAMPS +#if ENABLED(SPINDLE_LASER_USE_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(FAST_PWM_FAN) || SPINDLE_LASER_FREQUENCY - #error "Features requiring Hardware PWM (FAST_PWM_FAN, SPINDLE_LASER_FREQUENCY) are not yet supported on STM32F4/F7." + #error "Features requiring Hardware PWM (FAST_PWM_FAN, SPINDLE_LASER_FREQUENCY) are not yet supported for HAL/LINUX." #endif #if HAS_TMC_SW_SERIAL - #error "TMC220x Software Serial is not supported on this platform." + #error "TMC220x Software Serial is not supported on LINUX." +#endif + +#if ENABLED(POSTMORTEM_DEBUGGING) + #error "POSTMORTEM_DEBUGGING is not yet supported on LINUX." #endif diff --git a/Marlin/src/HAL/NATIVE_SIM/pinsDebug.cpp b/Marlin/src/HAL/NATIVE_SIM/pinsDebug.cpp new file mode 100644 index 0000000000..1354c79b31 --- /dev/null +++ b/Marlin/src/HAL/NATIVE_SIM/pinsDebug.cpp @@ -0,0 +1,49 @@ +/** + * 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 . + * + */ + +#ifdef __PLAT_NATIVE_SIM__ + +#include "../../inc/MarlinConfig.h" +#include "pinsDebug.h" + +int8_t ADC_pin_mode(const pin_t) { return -1; } + +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; + + return (Gpio::getMode(pin) != 0); // Input/output state +} + +// 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 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 new file mode 100644 index 0000000000..752802d438 --- /dev/null +++ b/Marlin/src/HAL/NATIVE_SIM/pinsDebug.h @@ -0,0 +1,61 @@ +/** + * 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 + +/** + * 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 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(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/spi_pins.h b/Marlin/src/HAL/NATIVE_SIM/spi_pins.h new file mode 100644 index 0000000000..0e83e13692 --- /dev/null +++ b/Marlin/src/HAL/NATIVE_SIM/spi_pins.h @@ -0,0 +1,45 @@ +/** + * 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 + +#if ALL(HAS_MARLINUI_U8GLIB, HAS_MEDIA) && (LCD_PINS_D4 == SD_SCK_PIN || LCD_PINS_EN == SD_MOSI_PIN || DOGLCD_SCK == SD_SCK_PIN || DOGLCD_MOSI == SD_MOSI_PIN) + #define SOFTWARE_SPI // If the SD card and LCD adapter share the same SPI pins, then software SPI is currently + // needed due to the speed and mode required for communicating with each device being different. + // This requirement can be removed if the SPI access to these devices is updated to use + // spiBeginTransaction. +#endif + +// 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 50 +#endif +#ifndef SD_MISO_PIN + #define SD_MISO_PIN 51 +#endif +#ifndef SD_MOSI_PIN + #define SD_MOSI_PIN 52 +#endif diff --git a/Marlin/src/HAL/NATIVE_SIM/tft/tft_spi.h b/Marlin/src/HAL/NATIVE_SIM/tft/tft_spi.h new file mode 100644 index 0000000000..944b8267f6 --- /dev/null +++ b/Marlin/src/HAL/NATIVE_SIM/tft/tft_spi.h @@ -0,0 +1,67 @@ +/** + * 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 + +#include "../../../inc/MarlinConfig.h" + +#ifndef LCD_READ_ID + #define LCD_READ_ID 0x04 // Read display identification information (0xD3 on ILI9341) +#endif +#ifndef LCD_READ_ID4 + #define LCD_READ_ID4 0xD3 // Read display identification information (0xD3 on ILI9341) +#endif + +#define DATASIZE_8BIT 8 +#define DATASIZE_16BIT 16 +#define TFT_IO_DRIVER TFT_SPI +#define DMA_MAX_WORDS 0xFFFF + +#define DMA_MINC_ENABLE 1 +#define DMA_MINC_DISABLE 0 + +class TFT_SPI { +private: + static uint32_t readID(const uint16_t inReg); + static void transmit(uint16_t data); + static void transmitDMA(uint32_t memoryIncrease, uint16_t *data, uint16_t count); + +public: + // static SPIClass SPIx; + + static void init(); + static uint32_t getID(); + static bool isBusy(); + static void abort(); + + static void dataTransferBegin(uint16_t dataWidth=DATASIZE_16BIT); + static void dataTransferEnd(); + static void dataTransferAbort(); + + static void writeData(uint16_t data); + static void writeReg(const uint16_t inReg); + + static void writeSequence_DMA(uint16_t *data, uint16_t count) { writeSequence(data, count); } + static void writeMultiple_DMA(uint16_t color, uint16_t count) { writeMultiple(color, count); } + + static void writeSequence(uint16_t *data, uint16_t count); + static void writeMultiple(uint16_t color, uint32_t count); +}; diff --git a/Marlin/src/HAL/NATIVE_SIM/tft/xpt2046.h b/Marlin/src/HAL/NATIVE_SIM/tft/xpt2046.h new file mode 100644 index 0000000000..d37f74c774 --- /dev/null +++ b/Marlin/src/HAL/NATIVE_SIM/tft/xpt2046.h @@ -0,0 +1,83 @@ +/** + * 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 + +#include "../../../inc/MarlinConfig.h" + +#if ENABLED(TOUCH_BUTTONS_HW_SPI) + #include +#endif + +#ifndef TOUCH_MISO_PIN + #define TOUCH_MISO_PIN SD_MISO_PIN +#endif +#ifndef TOUCH_MOSI_PIN + #define TOUCH_MOSI_PIN SD_MOSI_PIN +#endif +#ifndef TOUCH_SCK_PIN + #define TOUCH_SCK_PIN SD_SCK_PIN +#endif +#ifndef TOUCH_CS_PIN + #define TOUCH_CS_PIN SD_SS_PIN +#endif +#ifndef TOUCH_INT_PIN + #define TOUCH_INT_PIN -1 +#endif + +#define XPT2046_DFR_MODE 0x00 +#define XPT2046_SER_MODE 0x04 +#define XPT2046_CONTROL 0x80 + +enum XPTCoordinate : uint8_t { + XPT2046_X = 0x10 | XPT2046_CONTROL | XPT2046_DFR_MODE, + XPT2046_Y = 0x50 | XPT2046_CONTROL | XPT2046_DFR_MODE, + XPT2046_Z1 = 0x30 | XPT2046_CONTROL | XPT2046_DFR_MODE, + XPT2046_Z2 = 0x40 | XPT2046_CONTROL | XPT2046_DFR_MODE, +}; + +#ifndef XPT2046_Z1_THRESHOLD + #define XPT2046_Z1_THRESHOLD 10 +#endif + +class XPT2046 { +private: + static bool isBusy() { return false; } + + static uint16_t getRawData(const XPTCoordinate coordinate); + static bool isTouched(); + + static void dataTransferBegin(); + static void dataTransferEnd(); + #if ENABLED(TOUCH_BUTTONS_HW_SPI) + static uint16_t hardwareIO(uint16_t data); + #endif + static uint16_t softwareIO(uint16_t data); + static uint16_t IO(uint16_t data = 0); + +public: + #if ENABLED(TOUCH_BUTTONS_HW_SPI) + static SPIClass SPIx; + #endif + + static void init(); + static bool getRawPoint(int16_t * const x, int16_t * const y); +}; diff --git a/Marlin/src/HAL/NATIVE_SIM/timers.h b/Marlin/src/HAL/NATIVE_SIM/timers.h new file mode 100644 index 0000000000..be59bb8676 --- /dev/null +++ b/Marlin/src/HAL/NATIVE_SIM/timers.h @@ -0,0 +1,91 @@ +/** + * 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 + +/** + * HAL timers for Linux X86_64 + */ + +#include + +// ------------------------ +// Defines +// ------------------------ + +#define FORCE_INLINE __attribute__((always_inline)) inline + +typedef uint64_t hal_timer_t; +#define HAL_TIMER_TYPE_MAX hal_timer_t(UINT64_MAX) + +#define HAL_TIMER_RATE ((SystemCoreClock) / 4) // frequency of timers peripherals + +#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_SYSTICK + #define MF_TIMER_SYSTICK 2 // Timer Index for Systick +#endif +#define SYSTICK_TIMER_FREQUENCY 1000 + +#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 // (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 // (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() extern "C" void TIMER0_IRQHandler() +#endif +#ifndef HAL_TEMP_TIMER_ISR + #define HAL_TEMP_TIMER_ISR() extern "C" void TIMER1_IRQHandler() +#endif + +void HAL_timer_init(); +void HAL_timer_start(const uint8_t timer_num, const uint32_t frequency); + +void HAL_timer_set_compare(const uint8_t timer_num, const hal_timer_t compare); +hal_timer_t HAL_timer_get_compare(const uint8_t timer_num); +hal_timer_t HAL_timer_get_count(const uint8_t timer_num); + +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); + +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 new file mode 100644 index 0000000000..8033b79e3d --- /dev/null +++ b/Marlin/src/HAL/NATIVE_SIM/u8g/LCD_I2C_routines.cpp @@ -0,0 +1,52 @@ +/** + * 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 . + * + */ + +// adapted from I2C/master/master.c example +// https://www-users.york.ac.uk/~pcc1/MCP/HAPR-Course-web/CMSIS/examples/html/master_8c_source.html + +#ifdef __PLAT_NATIVE_SIM__ + +#include + +#ifdef __cplusplus + extern "C" { +#endif + +uint8_t u8g_i2c_start(const uint8_t sla) { + return 1; +} + +void u8g_i2c_init(const uint8_t clock_option) { +} + +uint8_t u8g_i2c_send_byte(uint8_t data) { + return 1; +} + +void u8g_i2c_stop() { +} + +#ifdef __cplusplus + } +#endif + +#endif // __PLAT_NATIVE_SIM__ diff --git a/Marlin/src/HAL/NATIVE_SIM/u8g/LCD_I2C_routines.h b/Marlin/src/HAL/NATIVE_SIM/u8g/LCD_I2C_routines.h new file mode 100644 index 0000000000..ec263b6dd3 --- /dev/null +++ b/Marlin/src/HAL/NATIVE_SIM/u8g/LCD_I2C_routines.h @@ -0,0 +1,36 @@ +/** + * 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 + +#ifdef __cplusplus + extern "C" { +#endif + +void u8g_i2c_init(const uint8_t clock_options); +//uint8_t u8g_i2c_wait(uint8_t mask, uint8_t pos); +uint8_t u8g_i2c_start(uint8_t sla); +uint8_t u8g_i2c_send_byte(uint8_t data); +void u8g_i2c_stop(); + +#ifdef __cplusplus + } +#endif diff --git a/Marlin/src/HAL/NATIVE_SIM/u8g/LCD_defines.h b/Marlin/src/HAL/NATIVE_SIM/u8g/LCD_defines.h new file mode 100644 index 0000000000..63701ca334 --- /dev/null +++ b/Marlin/src/HAL/NATIVE_SIM/u8g/LCD_defines.h @@ -0,0 +1,34 @@ +/** + * 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 + +/** + * Native/Simulator LCD-specific defines + */ + +void usleep(uint64_t microsec); + +uint8_t u8g_com_sw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr); +uint8_t u8g_com_ST7920_sw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr); + +#define U8G_COM_HAL_SW_SPI_FN u8g_com_sw_spi_fn +#define U8G_COM_ST7920_HAL_SW_SPI u8g_com_ST7920_sw_spi_fn diff --git a/Marlin/src/HAL/NATIVE_SIM/u8g/LCD_delay.h b/Marlin/src/HAL/NATIVE_SIM/u8g/LCD_delay.h new file mode 100644 index 0000000000..297361cd44 --- /dev/null +++ b/Marlin/src/HAL/NATIVE_SIM/u8g/LCD_delay.h @@ -0,0 +1,43 @@ +/** + * 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 + +/** + * LCD delay routines - used by all the drivers. + * + * These are based on the LPC1768 routines. + * + * Couldn't just call exact copies because the overhead + * results in a one microsecond delay taking about 4Β΅S. + */ + +#ifdef __cplusplus + extern "C" { +#endif + +void U8g_delay(int msec); +void u8g_MicroDelay(); +void u8g_10MicroDelay(); + +#ifdef __cplusplus + } +#endif diff --git a/Marlin/src/HAL/NATIVE_SIM/u8g/LCD_pin_routines.cpp b/Marlin/src/HAL/NATIVE_SIM/u8g/LCD_pin_routines.cpp new file mode 100644 index 0000000000..9cb33694a3 --- /dev/null +++ b/Marlin/src/HAL/NATIVE_SIM/u8g/LCD_pin_routines.cpp @@ -0,0 +1,51 @@ +/** + * 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 . + * + */ + +/** + * Low level pin manipulation routines - used by all the drivers. + * + * These are based on the LPC1768 pinMode, digitalRead & digitalWrite routines. + * + * 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 25% of the CPU's time. + */ + +#ifdef __PLAT_NATIVE_SIM__ + +#include "../fastio.h" +#include "LCD_pin_routines.h" + +#ifdef __cplusplus + extern "C" { +#endif + +void u8g_SetPinOutput(uint8_t internal_pin_number) { SET_DIR_OUTPUT(internal_pin_number); } +void u8g_SetPinInput(uint8_t internal_pin_number) { SET_DIR_INPUT(internal_pin_number); } +void u8g_SetPinLevel(uint8_t pin, uint8_t pin_status) { WRITE_PIN(pin, pin_status); } +uint8_t u8g_GetPinLevel(uint8_t pin) { return READ_PIN(pin); } + +#ifdef __cplusplus + } +#endif + +#endif // __PLAT_NATIVE_SIM__ diff --git a/Marlin/src/lcd/extui/lib/mks_ui/draw_printing.h b/Marlin/src/HAL/NATIVE_SIM/u8g/LCD_pin_routines.h similarity index 55% rename from Marlin/src/lcd/extui/lib/mks_ui/draw_printing.h rename to Marlin/src/HAL/NATIVE_SIM/u8g/LCD_pin_routines.h index b7d464e4f0..ab73635d28 100644 --- a/Marlin/src/lcd/extui/lib/mks_ui/draw_printing.h +++ b/Marlin/src/HAL/NATIVE_SIM/u8g/LCD_pin_routines.h @@ -1,6 +1,6 @@ /** * Marlin 3D Printer Firmware - * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] + * Copyright (c) 2021 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] * * Based on Sprinter and grbl. * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm @@ -21,32 +21,25 @@ */ #pragma once +/** + * Low level pin manipulation routines - used by all the drivers. + * + * These are based on the LPC1768 pinMode, digitalRead & digitalWrite routines. + * + * 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 25% of the CPU's time. + */ + #ifdef __cplusplus - extern "C" { /* C-declarations for C++ */ + extern "C" { #endif -#define IDLE 0 -#define WORKING 1 -#define PAUSING 2 -#define PAUSED 3 -#define REPRINTING 4 -#define REPRINTED 5 -#define RESUMING 6 -#define STOP 7 +void u8g_SetPinOutput(uint8_t internal_pin_number); +void u8g_SetPinInput(uint8_t internal_pin_number); +void u8g_SetPinLevel(uint8_t pin, uint8_t pin_status); +uint8_t u8g_GetPinLevel(uint8_t pin); -extern void lv_draw_printing(void); -extern void lv_clear_printing(); -extern void disp_ext_temp(); -extern void disp_bed_temp(); -extern void disp_fan_speed(); -extern void disp_print_time(); -extern void disp_fan_Zpos(); -extern void reset_print_time(); -extern void start_print_time(); -extern void stop_print_time(); -extern void setProBarRate(); - -//extern void disp_temp_ready_print(); #ifdef __cplusplus - } /* C-declarations for C++ */ + } #endif 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 new file mode 100644 index 0000000000..b07bc1644d --- /dev/null +++ b/Marlin/src/HAL/NATIVE_SIM/u8g/u8g_com_st7920_sw_spi.cpp @@ -0,0 +1,200 @@ +/** + * 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 . + * + */ + +/** + * Based on u8g_com_st7920_hw_spi.c + * + * Universal 8bit Graphics Library + * + * Copyright (c) 2011, olikraus@gmail.com + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this list + * of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifdef __PLAT_NATIVE_SIM__ + +#include "../../../inc/MarlinConfig.h" + +#if IS_U8GLIB_ST7920 + +#include +#include "../../shared/Delay.h" + +#undef SPI_SPEED +#define SPI_SPEED 6 +#define SPI_DELAY_CYCLES (1 + SPI_SPEED * 10) + +static pin_t SCK_pin_ST7920_HAL, MOSI_pin_ST7920_HAL_HAL; +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(sck_pin, TERN(U8G_SPI_USE_MODE_3, LOW, HIGH)); + DELAY_CYCLES(SPI_SPEED); + 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, TERN(U8G_SPI_USE_MODE_3, HIGH, LOW)); + DELAY_CYCLES(SPI_SPEED); + } + return b; +} + +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, TERN(U8G_SPI_USE_MODE_3, HIGH, LOW)); + return spiRate; +} + +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 ? 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 & 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); +} + +#ifdef __cplusplus + extern "C" { +#endif + +uint8_t u8g_com_ST7920_sw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr) { + switch (msg) { + case U8G_COM_MSG_INIT: + SCK_pin_ST7920_HAL = u8g->pin_list[U8G_PI_SCK]; + MOSI_pin_ST7920_HAL_HAL = u8g->pin_list[U8G_PI_MOSI]; + + u8g_SetPIOutput(u8g, U8G_PI_CS); + u8g_SetPIOutput(u8g, U8G_PI_SCK); + u8g_SetPIOutput(u8g, U8G_PI_MOSI); + u8g_Delay(5); + + SPI_speed = swSpiInit(SPI_SPEED, SCK_pin_ST7920_HAL, MOSI_pin_ST7920_HAL_HAL); + + u8g_SetPILevel(u8g, U8G_PI_CS, 0); + u8g_SetPILevel(u8g, U8G_PI_SCK, 0); + u8g_SetPILevel(u8g, U8G_PI_MOSI, 0); + + u8g->pin_list[U8G_PI_A0_STATE] = 0; /* initial RS state: command mode */ + break; + + case U8G_COM_MSG_STOP: + break; + + case U8G_COM_MSG_RESET: + if (U8G_PIN_NONE != u8g->pin_list[U8G_PI_RESET]) u8g_SetPILevel(u8g, U8G_PI_RESET, arg_val); + break; + + case U8G_COM_MSG_ADDRESS: /* define cmd (arg_val = 0) or data mode (arg_val = 1) */ + u8g->pin_list[U8G_PI_A0_STATE] = arg_val; + break; + + case U8G_COM_MSG_CHIP_SELECT: + if (U8G_PIN_NONE != u8g->pin_list[U8G_PI_CS]) u8g_SetPILevel(u8g, U8G_PI_CS, arg_val); //note: the st7920 has an active high chip select + break; + + case U8G_COM_MSG_WRITE_BYTE: + u8g_com_st7920_write_byte_sw_spi(u8g->pin_list[U8G_PI_A0_STATE], arg_val); + break; + + case U8G_COM_MSG_WRITE_SEQ: { + uint8_t *ptr = (uint8_t*) arg_ptr; + while (arg_val > 0) { + u8g_com_st7920_write_byte_sw_spi(u8g->pin_list[U8G_PI_A0_STATE], *ptr++); + arg_val--; + } + } + break; + + case U8G_COM_MSG_WRITE_SEQ_P: { + uint8_t *ptr = (uint8_t*) arg_ptr; + while (arg_val > 0) { + u8g_com_st7920_write_byte_sw_spi(u8g->pin_list[U8G_PI_A0_STATE], *ptr++); + arg_val--; + } + } + break; + } + return 1; +} + +#ifdef __cplusplus + } +#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 new file mode 100644 index 0000000000..fd11e5d767 --- /dev/null +++ b/Marlin/src/HAL/NATIVE_SIM/u8g/u8g_com_sw_spi.cpp @@ -0,0 +1,218 @@ +/** + * 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 . + * + */ + +/** + * Based on u8g_com_std_sw_spi.c + * + * Universal 8bit Graphics Library + * + * Copyright (c) 2015, olikraus@gmail.com + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this list + * of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifdef __PLAT_NATIVE_SIM__ + +#include "../../../inc/MarlinConfig.h" + +#if HAS_MARLINUI_U8GLIB && !IS_U8GLIB_ST7920 + +#undef SPI_SPEED +#define SPI_SPEED 2 // About 2 MHz + +#include +#include + +#ifdef __cplusplus + extern "C" { +#endif + +uint8_t swSpiTransfer_mode_0(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) { + if (spi_speed == 0) { + WRITE_PIN(mosi_pin, !!(b & 0x80)); + WRITE_PIN(sck_pin, HIGH); + b <<= 1; + if (miso_pin >= 0 && READ_PIN(miso_pin)) b |= 1; + WRITE_PIN(sck_pin, LOW); + } + else { + const uint8_t state = (b & 0x80) ? HIGH : LOW; + for (uint8_t j = 0; j < spi_speed; ++j) + WRITE_PIN(mosi_pin, state); + + for (uint8_t j = 0; j < spi_speed + (miso_pin >= 0 ? 0 : 1); ++j) + WRITE_PIN(sck_pin, HIGH); + + b <<= 1; + if (miso_pin >= 0 && READ_PIN(miso_pin)) b |= 1; + + for (uint8_t j = 0; j < spi_speed; ++j) + WRITE_PIN(sck_pin, LOW); + } + } + + return b; +} + +uint8_t swSpiTransfer_mode_3(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) { + const uint8_t state = (b & 0x80) ? HIGH : LOW; + if (spi_speed == 0) { + WRITE_PIN(sck_pin, LOW); + WRITE_PIN(mosi_pin, state); + WRITE_PIN(mosi_pin, state); // need some setup time + WRITE_PIN(sck_pin, HIGH); + } + else { + for (uint8_t j = 0; j < spi_speed + (miso_pin >= 0 ? 0 : 1); ++j) + WRITE_PIN(sck_pin, LOW); + + for (uint8_t j = 0; j < spi_speed; ++j) + WRITE_PIN(mosi_pin, state); + + for (uint8_t j = 0; j < spi_speed; ++j) + WRITE_PIN(sck_pin, HIGH); + } + b <<= 1; + if (miso_pin >= 0 && READ_PIN(miso_pin)) b |= 1; + } + + return b; +} + +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; +} + +static void u8g_sw_spi_shift_out(uint8_t dataPin, uint8_t clockPin, uint8_t val) { + #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); + #endif +} + +uint8_t u8g_com_sw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr) { + switch (msg) { + case U8G_COM_MSG_INIT: + u8g_SetPIOutput(u8g, U8G_PI_SCK); + u8g_SetPIOutput(u8g, U8G_PI_MOSI); + u8g_SetPIOutput(u8g, U8G_PI_CS); + u8g_SetPIOutput(u8g, U8G_PI_A0); + if (U8G_PIN_NONE != u8g->pin_list[U8G_PI_RESET]) u8g_SetPIOutput(u8g, U8G_PI_RESET); + SPI_speed = swSpiInit(SPI_SPEED, u8g->pin_list[U8G_PI_SCK], u8g->pin_list[U8G_PI_MOSI]); + u8g_SetPILevel(u8g, U8G_PI_SCK, 0); + u8g_SetPILevel(u8g, U8G_PI_MOSI, 0); + break; + + case U8G_COM_MSG_STOP: + break; + + case U8G_COM_MSG_RESET: + if (U8G_PIN_NONE != u8g->pin_list[U8G_PI_RESET]) u8g_SetPILevel(u8g, U8G_PI_RESET, arg_val); + break; + + case U8G_COM_MSG_CHIP_SELECT: + #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 + } + #else + u8g_SetPILevel(u8g, U8G_PI_CS, !arg_val); + #endif + break; + + case U8G_COM_MSG_WRITE_BYTE: + u8g_sw_spi_shift_out(u8g->pin_list[U8G_PI_MOSI], u8g->pin_list[U8G_PI_SCK], 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(u8g->pin_list[U8G_PI_MOSI], u8g->pin_list[U8G_PI_SCK], *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->pin_list[U8G_PI_MOSI], u8g->pin_list[U8G_PI_SCK], u8g_pgm_read(ptr)); + ptr++; + arg_val--; + } + } + break; + + case U8G_COM_MSG_ADDRESS: /* define cmd (arg_val = 0) or data mode (arg_val = 1) */ + u8g_SetPILevel(u8g, U8G_PI_A0, arg_val); + break; + } + return 1; +} + +#ifdef __cplusplus + } +#endif + +#elif NONE(TFT_COLOR_UI, TFT_CLASSIC_UI, TFT_LVGL_UI, HAS_MARLINUI_HD44780) && HAS_MARLINUI_U8GLIB + + #include + uint8_t u8g_com_sw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr) {return 0;} + +#endif // HAS_MARLINUI_U8GLIB && !IS_U8GLIB_ST7920 + +#endif // __PLAT_NATIVE_SIM__ diff --git a/Marlin/src/HAL/RP2040/HAL.cpp b/Marlin/src/HAL/RP2040/HAL.cpp new file mode 100644 index 0000000000..51f68648bc --- /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 0000000000..a49b343899 --- /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 0000000000..dc332053bc --- /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 0000000000..c88b6d1e5b --- /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 0000000000..5e3bf0c148 --- /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 0000000000..f407a838df --- /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 0000000000..4f9f70b8c9 --- /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 0000000000..2b1b2a1744 --- /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 0000000000..031610fd1d --- /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/TEENSY31_32/watchdog.cpp b/Marlin/src/HAL/RP2040/arduino_extras.cpp similarity index 69% rename from Marlin/src/HAL/TEENSY31_32/watchdog.cpp rename to Marlin/src/HAL/RP2040/arduino_extras.cpp index 9f7b70d9f9..cdc0a0abf2 100644 --- a/Marlin/src/HAL/TEENSY31_32/watchdog.cpp +++ b/Marlin/src/HAL/RP2040/arduino_extras.cpp @@ -1,6 +1,6 @@ /** * Marlin 3D Printer Firmware - * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] + * Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] * * Based on Sprinter and grbl. * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm @@ -19,20 +19,15 @@ * along with this program. If not, see . * */ -#ifdef __MK20DX256__ +#ifdef __PLAT_RP2040__ -#include "../../inc/MarlinConfig.h" +#include -#if ENABLED(USE_WATCHDOG) - -#include "watchdog.h" - -void watchdog_init() { - WDOG_TOVALH = 0; - WDOG_TOVALL = 4000; - WDOG_STCTRLH = WDOG_STCTRLH_WDOGEN; +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 // USE_WATCHDOG - -#endif // __MK20DX256__ +#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 0000000000..9794140212 --- /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 0000000000..4c5c9db2e3 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 0000000000..bf52109173 --- /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/STM32/eeprom_wired.cpp b/Marlin/src/HAL/RP2040/eeprom/eeprom_wired.cpp similarity index 73% rename from Marlin/src/HAL/STM32/eeprom_wired.cpp rename to Marlin/src/HAL/RP2040/eeprom/eeprom_wired.cpp index 8c46e45f03..7a5ca86c4c 100644 --- a/Marlin/src/HAL/STM32/eeprom_wired.cpp +++ b/Marlin/src/HAL/RP2040/eeprom/eeprom_wired.cpp @@ -1,10 +1,9 @@ /** * Marlin 3D Printer Firmware + * Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] * - * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com - * Copyright (c) 2015-2016 Nico Tonnhofer wurstnase.reprap@gmail.com - * Copyright (c) 2016 Victor Perez victor_pv@hotmail.com + * 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 @@ -20,9 +19,11 @@ * along with this program. If not, see . * */ -#if defined(ARDUINO_ARCH_STM32) && !defined(STM32GENERIC) +#include "../../platforms.h" -#include "../../inc/MarlinConfig.h" +#ifdef __PLAT_RP2040__ + +#include "../../../inc/MarlinConfig.h" #if USE_WIRED_EEPROM @@ -31,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) @@ -43,29 +44,26 @@ 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; - - // EEPROM has only ~100,000 write cycles, - // so only write bytes that have changed! uint8_t * const p = (uint8_t * const)pos; - if (v != eeprom_read_byte(p)) { + 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*/) { +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); @@ -78,4 +76,4 @@ bool PersistentStore::read_data(int &pos, uint8_t* value, size_t size, uint16_t } #endif // USE_WIRED_EEPROM -#endif // ARDUINO_ARCH_STM32 && !STM32GENERIC +#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 0000000000..af538406b8 --- /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 0000000000..1349a1d59e --- /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 != NULL) 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 0000000000..fa77106cef --- /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 0000000000..e84d2e7778 --- /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 0000000000..82f95a1035 --- /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 0000000000..b96b3baa64 --- /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 0000000000..ef7853b987 --- /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 0000000000..82f95a1035 --- /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 0000000000..e4b1d7ee8d --- /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 0000000000..b0de2241e5 --- /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 0000000000..1c13f5578d --- /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 0000000000..8f58089fb1 --- /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) != -1; +} + +#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 0000000000..e6ee840b55 --- /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 0000000000..e182fb88dc --- /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 0000000000..88d5af2983 --- /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, NULL, &HAL_timer_0); + break; + case 1: + result = alarm_pool_add_repeating_timer_us(HAL_timer_pool_1, us, HAL_timer_repeating_1_callback, NULL, &HAL_timer_1); + break; + case 2: + result = alarm_pool_add_repeating_timer_us(HAL_timer_pool_2, us, HAL_timer_repeating_2_callback, NULL, &HAL_timer_2); + break; + case 3: + result = alarm_pool_add_repeating_timer_us(HAL_timer_pool_3, us, HAL_timer_repeating_3_callback, NULL, &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 0000000000..8d9fde57a8 --- /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, false); + break; + case 1: + alarm_pool_add_alarm_in_us(HAL_timer_pool_1, compare, HAL_timer_alarm_pool_1_callback, 0, false); + break; + case 2: + alarm_pool_add_alarm_in_us(HAL_timer_pool_2, compare, HAL_timer_alarm_pool_2_callback, 0, false); + break; + case 3: + alarm_pool_add_alarm_in_us(HAL_timer_pool_3, compare, HAL_timer_alarm_pool_3_callback, 0, false); + 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 0000000000..acdd94b477 --- /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 0000000000..ea42bf190d --- /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/HAL.cpp b/Marlin/src/HAL/SAMD21/HAL.cpp new file mode 100644 index 0000000000..0a95366cc4 --- /dev/null +++ b/Marlin/src/HAL/SAMD21/HAL.cpp @@ -0,0 +1,209 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2022 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 . + * + */ + +/** + * SAMD21 HAL developed by Bart Meijer (brupje) + * Based on SAMD51 HAL by Giuliano Zaro (AKA GMagician) + */ +#ifdef __SAMD21__ + +#include "../../inc/MarlinConfig.h" + +#include + +#if USING_HW_SERIALUSB + DefaultSerial1 MSerialUSB(false, SerialUSB); +#endif +#if USING_HW_SERIAL0 + DefaultSerial2 MSerial1(false, Serial1); +#endif +#if USING_HW_SERIAL1 + DefaultSerial3 MSerial2(false, Serial2); +#endif + +#define WDT_CONFIG_PER_7_Val 0x9u +#define WDT_CONFIG_PER_Pos 0 +#define WDT_CONFIG_PER_7 (WDT_CONFIG_PER_7_Val << WDT_CONFIG_PER_Pos) + +#if ENABLED(USE_WATCHDOG) + + #define WDT_TIMEOUT_REG TERN(WATCHDOG_DURATION_8S, WDT_CONFIG_PER_CYC8192, WDT_CONFIG_PER_CYC4096) // 4 or 8 second timeout + + void MarlinHAL::watchdog_init() { + // Set up the generic clock (GCLK2) used to clock the watchdog timer at 1.024kHz + GCLK->GENDIV.reg = GCLK_GENDIV_DIV(4) | // Divide the 32.768kHz clock source by divisor 32, where 2^(4 + 1): 32.768kHz/32=1.024kHz + GCLK_GENDIV_ID(2); // Select Generic Clock (GCLK) 2 + while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization + + REG_GCLK_GENCTRL = GCLK_GENCTRL_DIVSEL | // Set to divide by 2^(GCLK_GENDIV_DIV(4) + 1) + GCLK_GENCTRL_IDC | // Set the duty cycle to 50/50 HIGH/LOW + GCLK_GENCTRL_GENEN | // Enable GCLK2 + GCLK_GENCTRL_SRC_OSCULP32K | // Set the clock source to the ultra low power oscillator (OSCULP32K) + GCLK_GENCTRL_ID(2); // Select GCLK2 + while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization + + // Feed GCLK2 to WDT (Watchdog Timer) + REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | // Enable GCLK2 to the WDT + GCLK_CLKCTRL_GEN_GCLK2 | // Select GCLK2 + GCLK_CLKCTRL_ID_WDT; // Feed the GCLK2 to the WDT + while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization + + WDT->CONFIG.bit.PER = WDT_CONFIG_PER_7; // Set the WDT reset timeout to 4 seconds + while (WDT->STATUS.bit.SYNCBUSY); // Wait for synchronization + REG_WDT_CTRL = WDT_CTRL_ENABLE; // Enable the WDT in normal mode + while (WDT->STATUS.bit.SYNCBUSY); // Wait for synchronization + } + + // Reset watchdog. MUST be called at least every 4 seconds after the + // first watchdog_init or SAMD will go into emergency procedures. + void MarlinHAL::watchdog_refresh() { + WDT->CLEAR.reg = WDT_CLEAR_CLEAR_KEY; + while (WDT->STATUS.bit.SYNCBUSY); + } + +#endif + +// ------------------------ +// Types +// ------------------------ + +// ------------------------ +// Private Variables +// ------------------------ + +// ------------------------ +// Private functions +// ------------------------ + +void MarlinHAL::dma_init() {} + +// ------------------------ +// Public functions +// ------------------------ + +// HAL initialization task +void MarlinHAL::init() { + TERN_(DMA_IS_REQUIRED, dma_init()); + #if HAS_MEDIA + #if HAS_SD_DETECT && SD_CONNECTION_IS(ONBOARD) + SET_INPUT_PULLUP(SD_DETECT_PIN); + #endif + OUT_WRITE(SD_SS_PIN, HIGH); // Try to set SDSS inactive before any other SPI users start up + #endif +} + +#pragma push_macro("WDT") +#undef WDT // Required to be able to use '.bit.WDT'. Compiler wrongly replace struct field with WDT define +uint8_t MarlinHAL::get_reset_source() { + + return 0; +} +#pragma pop_macro("WDT") + +void MarlinHAL::reboot() { NVIC_SystemReset(); } + +extern "C" { + void * _sbrk(int incr); + extern unsigned int __bss_end__; // end of bss section +} + +// Return free memory between end of heap (or end bss) and whatever is current +int freeMemory() { + int free_memory, heap_end = (int)_sbrk(0); + return (int)&free_memory - (heap_end ?: (int)&__bss_end__); +} + +// ------------------------ +// ADC +// ------------------------ + +uint16_t MarlinHAL::adc_result; + +void MarlinHAL::adc_init() { + /* thanks to https://www.eevblog.com/forum/microcontrollers/samd21g18-adc-with-resrdy-interrupts-only-reads-once-or-twice/ */ + + ADC->CTRLA.bit.ENABLE = false; + while(ADC->STATUS.bit.SYNCBUSY); + + // load chip corrections + uint32_t bias = (*((uint32_t *) ADC_FUSES_BIASCAL_ADDR) & ADC_FUSES_BIASCAL_Msk) >> ADC_FUSES_BIASCAL_Pos; + uint32_t linearity = (*((uint32_t *) ADC_FUSES_LINEARITY_0_ADDR) & ADC_FUSES_LINEARITY_0_Msk) >> ADC_FUSES_LINEARITY_0_Pos; + linearity |= ((*((uint32_t *) ADC_FUSES_LINEARITY_1_ADDR) & ADC_FUSES_LINEARITY_1_Msk) >> ADC_FUSES_LINEARITY_1_Pos) << 5; + + /* Wait for bus synchronization. */ + while (ADC->STATUS.bit.SYNCBUSY) {}; + + ADC->CALIB.reg = ADC_CALIB_BIAS_CAL(bias) | ADC_CALIB_LINEARITY_CAL(linearity); + + /* Wait for bus synchronization. */ + while (ADC->STATUS.bit.SYNCBUSY) {}; + + ADC->CTRLA.bit.SWRST = true; + while(ADC->STATUS.bit.SYNCBUSY); + + ADC->REFCTRL.reg = ADC_REFCTRL_REFSEL_INTVCC1; + ADC->AVGCTRL.reg = ADC_AVGCTRL_SAMPLENUM_32| ADC_AVGCTRL_ADJRES(4);; + + ADC->CTRLB.reg = ADC_CTRLB_PRESCALER_DIV128 | + ADC_CTRLB_RESSEL_16BIT | + ADC_CTRLB_FREERUN; + while(ADC->STATUS.bit.SYNCBUSY); + + ADC->SAMPCTRL.bit.SAMPLEN = 0x00; + while(ADC->STATUS.bit.SYNCBUSY); + + ADC->INPUTCTRL.reg = ADC_INPUTCTRL_INPUTSCAN(HAL_ADC_AIN_LEN) // scan (INPUTSCAN + NUM_EXTUDERS - 1) pins + | ADC_INPUTCTRL_GAIN_DIV2 |ADC_INPUTCTRL_MUXNEG_GND| HAL_ADC_AIN_START ; /* set to first AIN */ + + while(ADC->STATUS.bit.SYNCBUSY); + + ADC->INTENSET.reg |= ADC_INTENSET_RESRDY; // enable Result Ready ADC interrupts + while (ADC->STATUS.bit.SYNCBUSY); + + NVIC_EnableIRQ(ADC_IRQn); // enable ADC interrupts + + NVIC_SetPriority(ADC_IRQn, 3); + + ADC->CTRLA.bit.ENABLE = true; +} + +volatile uint32_t adc_results[HAL_ADC_AIN_NUM_SENSORS]; + +void ADC_Handler() { + while(ADC->STATUS.bit.SYNCBUSY == 1); + int pos = ADC->INPUTCTRL.bit.INPUTOFFSET; + + adc_results[pos] = ADC->RESULT.reg; /* Read the value. */ + ADC->INTFLAG.reg = ADC_INTENSET_RESRDY; /* Clear the data ready flag. */ +} + +void MarlinHAL::adc_start(const pin_t pin) { + /* due to the way INPUTOFFSET works, the last sensor is the first position in the array + and we want the ADC_handler interrupt to be as simple possible, so we do the calculation here. + */ + unsigned int pos = PIN_TO_INPUTCTRL(pin) - HAL_ADC_AIN_START + 1; + if (pos == HAL_ADC_AIN_NUM_SENSORS) pos = 0; + adc_result = adc_results[pos]; // 16-bit resolution + //adc_result = 0xFFFF; +} + +#endif // __SAMD21__ diff --git a/Marlin/src/HAL/SAMD21/HAL.h b/Marlin/src/HAL/SAMD21/HAL.h new file mode 100644 index 0000000000..5545630ce3 --- /dev/null +++ b/Marlin/src/HAL/SAMD21/HAL.h @@ -0,0 +1,189 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2022 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 + +/** + * SAMD21 HAL developed by Bart Meijer (brupje) + * Based on SAMD51 HAL by Giuliano Zaro (AKA GMagician) + */ + +#define CPU_32_BIT + +#include "../shared/Marduino.h" +#include "../shared/math_32bit.h" +#include "../shared/HAL_SPI.h" +#include "fastio.h" + +// ------------------------ +// Serial ports +// ------------------------ + +#include "../../core/serial_hook.h" +typedef ForwardSerial1Class< decltype(SerialUSB) > DefaultSerial1; +extern DefaultSerial1 MSerialUSB; + +typedef ForwardSerial1Class< decltype(Serial1) > DefaultSerial2; +typedef ForwardSerial1Class< decltype(Serial2) > DefaultSerial3; + +extern DefaultSerial2 MSerial0; +extern DefaultSerial3 MSerial1; + +#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 1 +#define USB_SERIAL_PORT(...) MSerialUSB +#include "../shared/serial_ports.h" + +typedef int8_t pin_t; + +#define SHARED_SERVOS HAS_SERVOS // Use shared/servos.cpp + +class Servo; +typedef Servo hal_servo_t; + +// +// Interrupts +// +#define CRITICAL_SECTION_START() const bool irqon = !__get_PRIMASK(); __disable_irq() +#define CRITICAL_SECTION_END() if (irqon) __enable_irq() + +#define cli() __disable_irq() // Disable interrupts +#define sei() __enable_irq() // Enable interrupts + +// +// ADC +// + +#define HAL_ADC_FILTERED 1 // Disable Marlin's oversampling. The HAL filters ADC values. +#define HAL_ADC_VREF_MV 3300 +#define HAL_ADC_RESOLUTION 12 +#define HAL_ADC_AIN_START ADC_INPUTCTRL_MUXPOS_PIN3 +#define HAL_ADC_AIN_NUM_SENSORS 3 +#define HAL_ADC_AIN_LEN HAL_ADC_AIN_NUM_SENSORS-1 + +// +// 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) + +// +// Tone +// +void tone(const pin_t _pin, const unsigned int frequency, const unsigned long duration=0); +void noTone(const pin_t _pin); + +// ------------------------ +// Class Utilities +// ------------------------ + +#pragma GCC diagnostic push +#if GCC_VERSION <= 50000 + #pragma GCC diagnostic ignored "-Wunused-function" +#endif + +#ifdef __cplusplus + extern "C" { +#endif + +char *dtostrf(double __val, signed char __width, unsigned char __prec, char *__s); + +extern "C" int freeMemory(); + +#ifdef __cplusplus + } +#endif + +#pragma GCC diagnostic pop + +// ------------------------ +// MarlinHAL Class +// ------------------------ + +class MarlinHAL { +public: + + // Earliest possible init, before setup() + MarlinHAL() {} + + // 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(); // 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 marlin.idle() + static void idletask() {} + + // Reset + static uint8_t get_reset_source(); + static void clear_reset_source() {} + + // Free SRAM + static int freeMemory() { return ::freeMemory(); } + + // + // ADC Methods + // + + static uint16_t adc_result; + + // 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 uint8_t ch) {} + + // 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 bool adc_ready() { return true; } + + // The 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. + * 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); + } + +private: + static void dma_init(); +}; diff --git a/Marlin/src/HAL/SAMD21/HAL_SPI.cpp b/Marlin/src/HAL/SAMD21/HAL_SPI.cpp new file mode 100644 index 0000000000..d41f868cdb --- /dev/null +++ b/Marlin/src/HAL/SAMD21/HAL_SPI.cpp @@ -0,0 +1,147 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2022 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 . + * + */ + +/** + * SAMD21 HAL developed by Bart Meijer (brupje) + * Based on SAMD51 HAL by Giuliano Zaro (AKA GMagician) + */ + +/** + * Hardware and software SPI implementations are included in this file. + * + * Control of the slave select pin(s) is handled by the calling routines and + * SAMD21 let hardware SPI handling to remove SS from its logic. + */ + +#ifdef __SAMD21__ + +// -------------------------------------------------------------------------- +// Includes +// -------------------------------------------------------------------------- + +#include "../../inc/MarlinConfig.h" +#include + +// -------------------------------------------------------------------------- +// Public functions +// -------------------------------------------------------------------------- + +#if ANY(SOFTWARE_SPI, FORCE_SOFT_SPI) + + // ------------------------ + // Software SPI + // ------------------------ + #error "Software SPI not supported for SAMD21. Use Hardware SPI." + +#else // !SOFTWARE_SPI + + static SPISettings spiConfig; + + // ------------------------ + // Hardware SPI + // ------------------------ + void spiBegin() { + spiInit(SPI_HALF_SPEED); + } + + void spiInit(uint8_t spiRate) { + // Use Marlin datarates + uint32_t clock; + switch (spiRate) { + case SPI_FULL_SPEED: clock = 8000000; break; + case SPI_HALF_SPEED: clock = 4000000; break; + case SPI_QUARTER_SPEED: clock = 2000000; break; + case SPI_EIGHTH_SPEED: clock = 1000000; break; + case SPI_SIXTEENTH_SPEED: clock = 500000; break; + case SPI_SPEED_5: clock = 250000; break; + case SPI_SPEED_6: clock = 125000; break; + default: clock = 4000000; break; // Default from the SPI library + } + spiConfig = SPISettings(clock, MSBFIRST, SPI_MODE0); + SPI.begin(); + } + + /** + * @brief Receives a single byte from the SPI port. + * + * @return Byte received + * + * @details + */ + uint8_t spiRec() { + SPI.beginTransaction(spiConfig); + uint8_t returnByte = SPI.transfer(0xFF); + SPI.endTransaction(); + return returnByte; + } + + /** + * @brief Receives 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 + */ + void spiRead(uint8_t *buf, uint16_t nbyte) { + if (nbyte == 0) return; + memset(buf, 0xFF, nbyte); + + SPI.beginTransaction(spiConfig); + SPI.transfer(buf, nbyte); + SPI.endTransaction(); + } + + /** + * @brief Sends a single byte on SPI port + * + * @param b Byte to send + * + * @details + */ + void spiSend(uint8_t b) { + SPI.beginTransaction(spiConfig); + SPI.transfer(b); + SPI.endTransaction(); + } + + /** + * @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 Uses DMA + */ + void spiSendBlock(uint8_t token, const uint8_t *buf) { + SPI.beginTransaction(spiConfig); + SPI.transfer(token); + SPI.transfer((uint8_t*)buf, 512); + SPI.endTransaction(); + } + + void spiBeginTransaction(uint32_t spiClock, uint8_t bitOrder, uint8_t dataMode) { + spiConfig = SPISettings(spiClock, (BitOrder)bitOrder, dataMode); + SPI.beginTransaction(spiConfig); + } +#endif // !SOFTWARE_SPI + +#endif // __SAMD21__ diff --git a/Marlin/src/HAL/TEENSY35_36/watchdog.h b/Marlin/src/HAL/SAMD21/MarlinSPI.h similarity index 78% rename from Marlin/src/HAL/TEENSY35_36/watchdog.h rename to Marlin/src/HAL/SAMD21/MarlinSPI.h index 981b1f0bd2..7b5392793e 100644 --- a/Marlin/src/HAL/TEENSY35_36/watchdog.h +++ b/Marlin/src/HAL/SAMD21/MarlinSPI.h @@ -1,6 +1,6 @@ /** * Marlin 3D Printer Firmware - * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] + * Copyright (c) 2022 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] * * Based on Sprinter and grbl. * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm @@ -19,12 +19,13 @@ * along with this program. If not, see . * */ + +/** + * SAMD21 HAL developed by Bart Meijer (brupje) + * Based on SAMD51 HAL by Giuliano Zaro (AKA GMagician) + */ #pragma once -void watchdog_init(); +#include -inline void HAL_watchdog_refresh() { - // Watchdog refresh sequence - WDOG_REFRESH = 0xA602; - WDOG_REFRESH = 0xB480; -} +using MarlinSPI = SPIClass; diff --git a/Marlin/src/HAL/SAMD21/SAMD21.h b/Marlin/src/HAL/SAMD21/SAMD21.h new file mode 100644 index 0000000000..b64c5ce725 --- /dev/null +++ b/Marlin/src/HAL/SAMD21/SAMD21.h @@ -0,0 +1,64 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2022 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 + +/** + * SAMD21 HAL developed by Bart Meijer (brupje) + * Based on SAMD51 HAL by Giuliano Zaro (AKA GMagician) + */ + +#define SYNC(sc) while (sc) { \ + asm(""); \ + } + +// Get SAMD port/pin from specified arduino pin +#define GET_SAMD_PORT(P) _GET_SAMD_PORT(PIN_TO_SAMD_PIN(P)) +#define GET_SAMD_PIN(P) _GET_SAMD_PIN(PIN_TO_SAMD_PIN(P)) + +// Get external interrupt line associated to specified arduino pin +#define PIN_TO_EILINE(P) _SAMDPORTPIN_TO_EILINE(GET_SAMD_PORT(P), GET_SAMD_PIN(P)) + +// Get adc/ain associated to specified arduino pin +#define PIN_TO_ADC(P) (ANAPIN_TO_ADCAIN(P) >> 8) + +// Private defines +#define PIN_TO_SAMD_PIN(P) DIO##P##_PIN + +#define _GET_SAMD_PORT(P) ((P) >> 5) +#define _GET_SAMD_PIN(P) ((P) & 0x1F) + +// Get external interrupt line +#define _SAMDPORTPIN_TO_EILINE(P,B) ((P == 0 && WITHIN(B, 0, 31) && B != 26 && B != 28 && B != 29) ? (B) & 0xF \ + : (P == 1 && (WITHIN(B, 0, 25) || WITHIN(B, 30, 31))) ? (B) & 0xF \ + : (P == 1 && WITHIN(B, 26, 29)) ? 12 + (B) - 26 \ + : (P == 2 && (WITHIN(B, 0, 6) || WITHIN(B, 10, 31)) && B != 29) ? (B) & 0xF \ + : (P == 2 && B == 7) ? 9 \ + : (P == 3 && WITHIN(B, 0, 1)) ? (B) \ + : (P == 3 && WITHIN(B, 8, 12)) ? 3 + (B) - 8 \ + : (P == 3 && WITHIN(B, 20, 21)) ? 10 + (B) - 20 \ + : -1) + +#define A2_AIN 3 +#define A3_AIN 4 +#define A4_AIN 5 +#define PIN_TO_AIN(P) A##P##_AIN +#define AIN_TO_RESULT(P) ( (P - HAL_ADC_AIN_START == HAL_ADC_AIN_NUM_SENSORS-1) ? 0 : (P - HAL_ADC_AIN_START + 1) ) diff --git a/Marlin/src/HAL/SAMD21/Servo.cpp b/Marlin/src/HAL/SAMD21/Servo.cpp new file mode 100644 index 0000000000..704d0a2904 --- /dev/null +++ b/Marlin/src/HAL/SAMD21/Servo.cpp @@ -0,0 +1,219 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2022 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 . + * + */ + +/** + * SAMD21 HAL developed by Bart Meijer (brupje) + * Based on SAMD51 HAL by Giuliano Zaro (AKA GMagician) + */ + +/** + * This comes from Arduino library which at the moment is buggy and uncompilable + */ + +#ifdef __SAMD21__ + +#include "../../inc/MarlinConfig.h" + +#if HAS_SERVOS + +#include "../shared/servo.h" +#include "../shared/servo_private.h" +#include "SAMD21.h" + +#define __TC_GCLK_ID(t) TC##t##_GCLK_ID +#define _TC_GCLK_ID(t) __TC_GCLK_ID(t) +#define TC_GCLK_ID _TC_GCLK_ID(SERVO_TC) + +#define _TC_PRESCALER(d) TC_CTRLA_PRESCALER_DIV##d##_Val +#define TC_PRESCALER(d) _TC_PRESCALER(d) + +#define __SERVO_IRQn(t) TC##t##_IRQn +#define _SERVO_IRQn(t) __SERVO_IRQn(t) +#define SERVO_IRQn _SERVO_IRQn(SERVO_TC) + +#define HAL_SERVO_TIMER_ISR() TC_HANDLER(SERVO_TC) + +#define TIMER_TCCHANNEL(t) ((t) & 1) +#define TC_COUNTER_START_VAL 0xFFFF + +static volatile int8_t currentServoIndex[_Nbr_16timers]; // index for the servo being pulsed for each timer (or -1 if refresh interval) + +FORCE_INLINE static uint16_t getTimerCount() { + Tcc * const tc = timer_config[SERVO_TC].pTcc; + + tc->CTRLBSET.reg = TCC_CTRLBCLR_CMD_READSYNC; + SYNC(tc->STATUS.reg & TC_STATUS_SYNCBUSY); + + return tc->COUNT.bit.COUNT; +} + +// ---------------------------- +// Interrupt handler for the TC +// ---------------------------- +HAL_SERVO_TIMER_ISR() { + Tcc * const tc = timer_config[SERVO_TC].pTcc; + const timer16_Sequence_t timer = + #ifndef _useTimer1 + _timer2 + #elif !defined(_useTimer2) + _timer1 + #else + (tc->INTFLAG.reg & tc->INTENSET.reg & TC_INTFLAG_MC0) ? _timer1 : _timer2 + #endif + ; + const uint8_t tcChannel = TIMER_TCCHANNEL(timer); + + int8_t cho = currentServoIndex[timer]; // Handle the prior servo first + if (cho < 0) { // Servo -1 indicates the refresh interval completed... + #if defined(_useTimer1) && defined(_useTimer2) + if (currentServoIndex[timer ^ 1] >= 0) { + // Wait for both channels + // Clear the interrupt + tc->INTFLAG.reg = (tcChannel == 0) ? TC_INTFLAG_MC0 : TC_INTFLAG_MC1; + return; + } + #endif + tc->COUNT.reg = TC_COUNTER_START_VAL; // ...so reset the timer + SYNC(tc->STATUS.reg & TC_STATUS_SYNCBUSY); + } + else if (SERVO_INDEX(timer, cho) < ServoCount) // prior channel handled? + digitalWrite(SERVO(timer, cho).Pin.nbr, LOW); // pulse the prior channel LOW + + currentServoIndex[timer] = ++cho; // go to the next channel (or 0) + if (cho < SERVOS_PER_TIMER && SERVO_INDEX(timer, cho) < ServoCount) { + if (SERVO(timer, cho).Pin.isActive) // activated? + digitalWrite(SERVO(timer, cho).Pin.nbr, HIGH); // yes: pulse HIGH + + tc->CC[tcChannel].reg = getTimerCount() - (uint16_t)SERVO(timer, cho).ticks; + } + else { + // finished all channels so wait for the refresh period to expire before starting over + currentServoIndex[timer] = -1; // reset the timer COUNT.reg on the next call + const uint16_t cval = getTimerCount() - 256 / (SERVO_TIMER_PRESCALER), // allow 256 cycles to ensure the next CV not missed + ival = (TC_COUNTER_START_VAL) - (uint16_t)usToTicks(REFRESH_INTERVAL); // at least REFRESH_INTERVAL has elapsed + tc->CC[tcChannel].reg = min(cval, ival); + } + if (tcChannel == 0) { + SYNC(tc->SYNCBUSY.bit.CC0); + tc->INTFLAG.reg = TC_INTFLAG_MC0; // Clear the interrupt + } + else { + SYNC(tc->SYNCBUSY.bit.CC1); + tc->INTFLAG.reg = TC_INTFLAG_MC1; // Clear the interrupt + } +} + +void initISR(const timer16_Sequence_t timer) { + Tcc * const tc = timer_config[SERVO_TC].pTcc; + const uint8_t tcChannel = TIMER_TCCHANNEL(timer); + + static bool initialized = false; // Servo TC has been initialized + if (!initialized) { + NVIC_DisableIRQ(SERVO_IRQn); + + // Disable the timer + tc->CTRLA.bit.ENABLE = false; + SYNC(tc->STATUS.reg & TC_STATUS_SYNCBUSY); + + // Select GCLK0 as timer/counter input clock source + GCLK->CLKCTRL.reg =(GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID(TCC0_GCLK_ID)); + SYNC (GCLK->STATUS.bit.SYNCBUSY); + + // Reset the timer + tc->CTRLA.bit.SWRST = true; + SYNC(tc->CTRLA.bit.SWRST); + + // Set timer counter mode to 16 bits + tc->CTRLA.reg = TC_CTRLA_MODE_COUNT16; + + // Set timer counter mode as normal PWM + tc->WAVE.bit.WAVEGEN = TCC_WAVE_WAVEGEN_NPWM_Val; + + // Set the prescaler factor + tc->CTRLA.bit.PRESCALER = TC_PRESCALER(SERVO_TIMER_PRESCALER); + + // Count down + tc->CTRLBSET.reg = TCC_CTRLBCLR_DIR; + SYNC(tc->SYNCBUSY.bit.CTRLB); + + // Reset all servo indexes + memset((void *)currentServoIndex, 0xFF, sizeof(currentServoIndex)); + + // Configure interrupt request + NVIC_ClearPendingIRQ(SERVO_IRQn); + NVIC_SetPriority(SERVO_IRQn, 5); + NVIC_EnableIRQ(SERVO_IRQn); + + initialized = true; + } + + if (!tc->CTRLA.bit.ENABLE) { + // Reset the timer counter + tc->COUNT.reg = TC_COUNTER_START_VAL; + SYNC(tc->STATUS.reg & TC_STATUS_SYNCBUSY); + + // Enable the timer and start it + tc->CTRLA.bit.ENABLE = true; + SYNC(tc->STATUS.reg & TC_STATUS_SYNCBUSY); + } + // First interrupt request after 1 ms + tc->CC[tcChannel].reg = getTimerCount() - (uint16_t)usToTicks(1000UL); + + if (tcChannel == 0 ) { + SYNC(tc->SYNCBUSY.bit.CC0); + + // Clear pending match interrupt + tc->INTFLAG.reg = TC_INTENSET_MC0; + // Enable the match channel interrupt request + tc->INTENSET.reg = TC_INTENSET_MC0; + } + else { + SYNC(tc->SYNCBUSY.bit.CC1); + + // Clear pending match interrupt + tc->INTFLAG.reg = TC_INTENSET_MC1; + // Enable the match channel interrupt request + tc->INTENSET.reg = TC_INTENSET_MC1; + } +} + +void finISR(const timer16_Sequence_t timer_index) { + Tcc * const tc = timer_config[SERVO_TC].pTcc; + const uint8_t tcChannel = TIMER_TCCHANNEL(timer_index); + + // Disable the match channel interrupt request + tc->INTENCLR.reg = (tcChannel == 0) ? TC_INTENCLR_MC0 : TC_INTENCLR_MC1; + + if (true + #if defined(_useTimer1) && defined(_useTimer2) + && (tc->INTENCLR.reg & (TC_INTENCLR_MC0|TC_INTENCLR_MC1)) == 0 + #endif + ) { + // Disable the timer if not used + tc->CTRLA.bit.ENABLE = false; + SYNC(tc->STATUS.reg & TC_STATUS_SYNCBUSY); + } +} + +#endif // HAS_SERVOS + +#endif // __SAMD21__ diff --git a/Marlin/src/HAL/SAMD21/ServoTimers.h b/Marlin/src/HAL/SAMD21/ServoTimers.h new file mode 100644 index 0000000000..8980547683 --- /dev/null +++ b/Marlin/src/HAL/SAMD21/ServoTimers.h @@ -0,0 +1,45 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2022 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 + +/** + * SAMD21 HAL developed by Bart Meijer (brupje) + * Based on SAMD51 HAL by Giuliano Zaro (AKA GMagician) + */ + +#define _useTimer1 +#define _useTimer2 + +#define TRIM_DURATION 5 // compensation ticks to trim adjust for digitalWrite delays +#define SERVO_TIMER_PRESCALER 64 // timer prescaler factor to 64 (avoid overflowing 16-bit clock counter, at 120MHz this is 1831 ticks per millisecond + +#define SERVO_TC 3 + +typedef enum { + #ifdef _useTimer1 + _timer1, + #endif + #ifdef _useTimer2 + _timer2, + #endif + _Nbr_16timers +} timer16_Sequence_t; diff --git a/Marlin/src/HAL/SAMD21/eeprom/QSPIFlash.cpp b/Marlin/src/HAL/SAMD21/eeprom/QSPIFlash.cpp new file mode 100644 index 0000000000..2a93226a6f --- /dev/null +++ b/Marlin/src/HAL/SAMD21/eeprom/QSPIFlash.cpp @@ -0,0 +1,82 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2022 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 . + * + */ + +/** + * SAMD21 HAL developed by Bart Meijer (brupje) + * Based on SAMD51 HAL by Giuliano Zaro (AKA GMagician) + */ +#include "../../../inc/MarlinConfig.h" + +#if ENABLED(QSPI_EEPROM) + +#include "QSPIFlash.h" + +#define INVALID_ADDR 0xFFFFFFFF +#define SECTOR_OF(a) (a & ~(SFLASH_SECTOR_SIZE - 1)) +#define OFFSET_OF(a) (a & (SFLASH_SECTOR_SIZE - 1)) + +Adafruit_SPIFlashBase * QSPIFlash::_flashBase = nullptr; +uint8_t QSPIFlash::_buf[SFLASH_SECTOR_SIZE]; +uint32_t QSPIFlash::_addr = INVALID_ADDR; + +void QSPIFlash::begin() { + if (_flashBase) return; + + _flashBase = new Adafruit_SPIFlashBase(new Adafruit_FlashTransport_QSPI()); + _flashBase->begin(nullptr); +} + +size_t QSPIFlash::size() { + return _flashBase->size(); +} + +uint8_t QSPIFlash::readByte(const uint32_t address) { + if (SECTOR_OF(address) == _addr) return _buf[OFFSET_OF(address)]; + + return _flashBase->read8(address); +} + +void QSPIFlash::writeByte(const uint32_t address, const uint8_t value) { + uint32_t const sector_addr = SECTOR_OF(address); + + // Page changes, flush old and update new cache + if (sector_addr != _addr) { + flush(); + _addr = sector_addr; + + // read a whole page from flash + _flashBase->readBuffer(sector_addr, _buf, SFLASH_SECTOR_SIZE); + } + + _buf[OFFSET_OF(address)] = value; +} + +void QSPIFlash::flush() { + if (_addr == INVALID_ADDR) return; + + _flashBase->eraseSector(_addr / SFLASH_SECTOR_SIZE); + _flashBase->writeBuffer(_addr, _buf, SFLASH_SECTOR_SIZE); + + _addr = INVALID_ADDR; +} + +#endif // QSPI_EEPROM diff --git a/Marlin/src/HAL/SAMD51/QSPIFlash.h b/Marlin/src/HAL/SAMD21/eeprom/QSPIFlash.h similarity index 99% rename from Marlin/src/HAL/SAMD51/QSPIFlash.h rename to Marlin/src/HAL/SAMD21/eeprom/QSPIFlash.h index db4abec91c..58822fe05f 100644 --- a/Marlin/src/HAL/SAMD51/QSPIFlash.h +++ b/Marlin/src/HAL/SAMD21/eeprom/QSPIFlash.h @@ -25,7 +25,6 @@ * * Derived from Adafruit_SPIFlash class with no SdFat references */ - #pragma once #include diff --git a/Marlin/src/HAL/SAMD21/eeprom/eeprom_flash.cpp b/Marlin/src/HAL/SAMD21/eeprom/eeprom_flash.cpp new file mode 100644 index 0000000000..0b5323cda4 --- /dev/null +++ b/Marlin/src/HAL/SAMD21/eeprom/eeprom_flash.cpp @@ -0,0 +1,145 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2022 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 . + * + */ + +/** + * SAMD21 HAL developed by Bart Meijer (brupje) + * Based on SAMD51 HAL by Giuliano Zaro (AKA GMagician) + */ +#ifdef __SAMD21__ + +#include "../../../inc/MarlinConfig.h" + +#if ENABLED(FLASH_EEPROM_EMULATION) + +#define TOTAL_FLASH_SIZE (MARLIN_EEPROM_SIZE+255)/256*256 + +/* reserve flash memory */ +static const uint8_t flashdata[TOTAL_FLASH_SIZE] __attribute__((__aligned__(256))) { }; \ + +#include "../../shared/eeprom_api.h" + +size_t PersistentStore::capacity() { return MARLIN_EEPROM_SIZE - eeprom_exclude_size; } + +/* +size_t PersistentStore::capacity() { return MARLIN_EEPROM_SIZE - eeprom_exclude_size; } + const uint8_t psz = NVMCTRL->SEESTAT.bit.PSZ, + sblk = NVMCTRL->SEESTAT.bit.SBLK; + + return ( + (!psz && !sblk) ? 0 + : (psz <= 2) ? (0x200 << psz) + : (sblk == 1 || psz == 3) ? 4096 + : (sblk == 2 || psz == 4) ? 8192 + : (sblk <= 4 || psz == 5) ? 16384 + : (sblk >= 9 && psz == 7) ? 65536 + : 32768 + ) - eeprom_exclude_size; +} +*/ + +uint32_t PAGE_SIZE; +uint32_t ROW_SIZE; +bool hasWritten = false; +uint8_t * buffer; + +void _erase(const volatile void *flash_ptr) { + NVMCTRL->ADDR.reg = ((uint32_t)flash_ptr) / 2; + NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMDEX_KEY | NVMCTRL_CTRLA_CMD_ER; + while (!NVMCTRL->INTFLAG.bit.READY) { } + +} + +void erase(const volatile void *flash_ptr, uint32_t size) { + const uint8_t *ptr = (const uint8_t *)flash_ptr; + while (size > ROW_SIZE) { + _erase(ptr); + ptr += ROW_SIZE; + size -= ROW_SIZE; + } + _erase(ptr); +} + +bool PersistentStore::access_start() { + /* clear page buffer*/ + 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); + ROW_SIZE= PAGE_SIZE * 4; + /*NVMCTRL->SEECFG.reg = NVMCTRL_SEECFG_WMODE_BUFFERED; // Buffered mode and segment reallocation active + if (NVMCTRL->SEESTAT.bit.RLOCK) + NVMCTRL_CMD(NVMCTRL_CTRLB_CMD_USEE); */ // Unlock E2P data write access + // erase(&flashdata[0], TOTAL_FLASH_SIZE); + return true; +} + +bool PersistentStore::access_finish() { + if (hasWritten) { + erase(&flashdata[0], TOTAL_FLASH_SIZE); + + NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMDEX_KEY | NVMCTRL_CTRLA_CMD_PBC; + while (NVMCTRL->INTFLAG.bit.READY == 0) { } + + NVMCTRL->CTRLB.bit.MANW = 0; + + volatile uint32_t *dst_addr = (volatile uint32_t *) &flashdata; + + uint32_t *pointer = (uint32_t *) buffer; + for (uint32_t i = 0; i < TOTAL_FLASH_SIZE; i += 4) { + *dst_addr = (uint32_t) *pointer; + pointer++; + dst_addr ++; + } + + // Execute "WP" Write Page + NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMDEX_KEY | NVMCTRL_CTRLA_CMD_WP; + while (NVMCTRL->INTFLAG.bit.READY == 0) { } + + free(buffer); + hasWritten = false; + } + return true; +} + +bool PersistentStore::write_data(int &pos, const uint8_t *value, size_t size, uint16_t *crc) { + if (!hasWritten) { + // init temp buffer + buffer = (uint8_t *) malloc(MARLIN_EEPROM_SIZE); + hasWritten = true; + } + + memcpy(buffer + REAL_EEPROM_ADDR(pos), value, size); + pos += size; + return false; +} + +bool PersistentStore::read_data(int &pos, uint8_t *value, size_t size, uint16_t *crc, const bool writing/*=true*/) { + volatile uint8_t *dst_addr = (volatile uint8_t *) &flashdata; + dst_addr += REAL_EEPROM_ADDR(pos); + + memcpy(value, (const void *)dst_addr, size); + pos += size; + return false; +} + +#endif // FLASH_EEPROM_EMULATION +#endif // __SAMD21__ diff --git a/Marlin/src/HAL/SAMD21/eeprom/eeprom_qspi.cpp b/Marlin/src/HAL/SAMD21/eeprom/eeprom_qspi.cpp new file mode 100644 index 0000000000..8bd1bd3539 --- /dev/null +++ b/Marlin/src/HAL/SAMD21/eeprom/eeprom_qspi.cpp @@ -0,0 +1,79 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2022 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 . + * + */ + +/** + * SAMD21 HAL developed by Bart Meijer (brupje) + * Based on SAMD51 HAL by Giuliano Zaro (AKA GMagician) + */ +#ifdef __SAMD21__ + +#include "../../../inc/MarlinConfig.h" + +#if ENABLED(QSPI_EEPROM) + +#error "QSPI_EEPROM emulation Not implemented on SAMD21" + +#include "../../shared/eeprom_api.h" + +#include "QSPIFlash.h" + +static bool initialized; + +size_t PersistentStore::capacity() { return qspi.size() - eeprom_exclude_size; } + +bool PersistentStore::access_start() { + if (!initialized) { + qspi.begin(); + initialized = true; + } + return true; +} + +bool PersistentStore::access_finish() { + qspi.flush(); + return true; +} + +bool PersistentStore::write_data(int &pos, const uint8_t *value, size_t size, uint16_t *crc) { + while (size--) { + const uint8_t v = *value; + qspi.writeByte(REAL_EEPROM_ADDR(pos), v); + 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*/) { + while (size--) { + const uint8_t c = qspi.readByte(REAL_EEPROM_ADDR(pos)); + if (writing) *value = c; + crc16(crc, &c, 1); + pos++; + value++; + } + return false; +} + +#endif // QSPI_EEPROM +#endif // __SAMD21__ diff --git a/Marlin/src/HAL/SAMD21/eeprom/eeprom_wired.cpp b/Marlin/src/HAL/SAMD21/eeprom/eeprom_wired.cpp new file mode 100644 index 0000000000..82c701ebb1 --- /dev/null +++ b/Marlin/src/HAL/SAMD21/eeprom/eeprom_wired.cpp @@ -0,0 +1,82 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2022 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 . + * + */ + +/** + * SAMD21 HAL developed by Bart Meijer (brupje) + * Based on SAMD51 HAL by Giuliano Zaro (AKA GMagician) + */ +#ifdef __SAMD21__ + +#include "../../../inc/MarlinConfig.h" + +#if USE_WIRED_EEPROM + +#error "USE_WIRED_EEPROM emulation Not implemented on SAMD21" +/** + * 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 + #error "MARLIN_EEPROM_SIZE is required for I2C / SPI 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--) { + const uint8_t v = *value; + uint8_t * const p = (uint8_t * const)REAL_EEPROM_ADDR(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*/) { + while (size--) { + const uint8_t c = eeprom_read_byte((uint8_t*)REAL_EEPROM_ADDR(pos)); + if (writing) *value = c; + crc16(crc, &c, 1); + pos++; + value++; + } + return false; +} + +#endif // USE_WIRED_EEPROM +#endif // __SAMD21__ diff --git a/Marlin/src/HAL/SAMD21/endstop_interrupts.h b/Marlin/src/HAL/SAMD21/endstop_interrupts.h new file mode 100644 index 0000000000..8d8f9cd06f --- /dev/null +++ b/Marlin/src/HAL/SAMD21/endstop_interrupts.h @@ -0,0 +1,291 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2022 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 + +/** + * SAMD21 HAL developed by Bart Meijer (brupje) + * Based on SAMD51 HAL by Giuliano Zaro (AKA GMagician) + */ + +/** + * Endstop interrupts for ATMEL SAMD21 based targets. + * + * 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 + * connects to what is called an EXTINT line, and only one pin per EXTINT line can be used for external + * interrupts at a time + */ + +/** + * Endstop Interrupts + * + * Without endstop interrupts the endstop pins must be polled continually in + * the temperature-ISR via endstops.update(), most of the time finding no change. + * With this feature endstops.update() is called only when we know that at + * least one endstop has changed state, saving valuable CPU cycles. + * + * This feature only works when all used endstop pins can generate an 'external interrupt'. + * + * Test whether pins issue interrupts on your board by flashing 'pin_interrupt_test.ino'. + * (Located in Marlin/buildroot/share/pin_interrupt_test/pin_interrupt_test.ino) + */ + +#include "../../module/endstops.h" + +#define MATCH_EILINE(P1,P2) (P1 != P2 && PIN_TO_EILINE(P1) == PIN_TO_EILINE(P2)) +#define MATCH_X_MAX_EILINE(P) TERN0(USE_X_MAX, DEFER4(MATCH_EILINE)(P, X_MAX_PIN)) +#define MATCH_X_MIN_EILINE(P) TERN0(USE_X_MIN, DEFER4(MATCH_EILINE)(P, X_MIN_PIN)) +#define MATCH_Y_MAX_EILINE(P) TERN0(USE_Y_MAX, DEFER4(MATCH_EILINE)(P, Y_MAX_PIN)) +#define MATCH_Y_MIN_EILINE(P) TERN0(USE_Y_MIN, DEFER4(MATCH_EILINE)(P, Y_MIN_PIN)) +#define MATCH_Z_MAX_EILINE(P) TERN0(USE_Z_MAX, DEFER4(MATCH_EILINE)(P, Z_MAX_PIN)) +#define MATCH_Z_MIN_EILINE(P) TERN0(USE_Z_MIN, DEFER4(MATCH_EILINE)(P, Z_MIN_PIN)) +#define MATCH_I_MAX_EILINE(P) TERN0(USE_I_MAX, DEFER4(MATCH_EILINE)(P, I_MAX_PIN)) +#define MATCH_I_MIN_EILINE(P) TERN0(USE_I_MIN, DEFER4(MATCH_EILINE)(P, I_MIN_PIN)) +#define MATCH_J_MAX_EILINE(P) TERN0(USE_J_MAX, DEFER4(MATCH_EILINE)(P, J_MAX_PIN)) +#define MATCH_J_MIN_EILINE(P) TERN0(USE_J_MIN, DEFER4(MATCH_EILINE)(P, J_MIN_PIN)) +#define MATCH_K_MAX_EILINE(P) TERN0(USE_K_MAX, DEFER4(MATCH_EILINE)(P, K_MAX_PIN)) +#define MATCH_K_MIN_EILINE(P) TERN0(USE_K_MIN, DEFER4(MATCH_EILINE)(P, K_MIN_PIN)) +#define MATCH_U_MAX_EILINE(P) TERN0(USE_U_MAX, DEFER4(MATCH_EILINE)(P, U_MAX_PIN)) +#define MATCH_U_MIN_EILINE(P) TERN0(USE_U_MIN, DEFER4(MATCH_EILINE)(P, U_MIN_PIN)) +#define MATCH_V_MAX_EILINE(P) TERN0(USE_V_MAX, DEFER4(MATCH_EILINE)(P, V_MAX_PIN)) +#define MATCH_V_MIN_EILINE(P) TERN0(USE_V_MIN, DEFER4(MATCH_EILINE)(P, V_MIN_PIN)) +#define MATCH_W_MAX_EILINE(P) TERN0(USE_W_MAX, DEFER4(MATCH_EILINE)(P, W_MAX_PIN)) +#define MATCH_W_MIN_EILINE(P) TERN0(USE_W_MIN, DEFER4(MATCH_EILINE)(P, W_MIN_PIN)) +#define MATCH_X2_MAX_EILINE(P) TERN0(USE_X2_MAX, DEFER4(MATCH_EILINE)(P, X2_MAX_PIN)) +#define MATCH_X2_MIN_EILINE(P) TERN0(USE_X2_MIN, DEFER4(MATCH_EILINE)(P, X2_MIN_PIN)) +#define MATCH_Y2_MAX_EILINE(P) TERN0(USE_Y2_MAX, DEFER4(MATCH_EILINE)(P, Y2_MAX_PIN)) +#define MATCH_Y2_MIN_EILINE(P) TERN0(USE_Y2_MIN, DEFER4(MATCH_EILINE)(P, Y2_MIN_PIN)) +#define MATCH_Z2_MAX_EILINE(P) TERN0(USE_Z2_MAX, DEFER4(MATCH_EILINE)(P, Z2_MAX_PIN)) +#define MATCH_Z2_MIN_EILINE(P) TERN0(USE_Z2_MIN, DEFER4(MATCH_EILINE)(P, Z2_MIN_PIN)) +#define MATCH_Z3_MAX_EILINE(P) TERN0(USE_Z3_MAX, DEFER4(MATCH_EILINE)(P, Z3_MAX_PIN)) +#define MATCH_Z3_MIN_EILINE(P) TERN0(USE_Z3_MIN, DEFER4(MATCH_EILINE)(P, Z3_MIN_PIN)) +#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) \ + && !MATCH_Y_MAX_EILINE(P) && !MATCH_Y_MIN_EILINE(P) \ + && !MATCH_Z_MAX_EILINE(P) && !MATCH_Z_MIN_EILINE(P) \ + && !MATCH_I_MAX_EILINE(P) && !MATCH_I_MIN_EILINE(P) \ + && !MATCH_J_MAX_EILINE(P) && !MATCH_J_MIN_EILINE(P) \ + && !MATCH_K_MAX_EILINE(P) && !MATCH_K_MIN_EILINE(P) \ + && !MATCH_U_MAX_EILINE(P) && !MATCH_U_MIN_EILINE(P) \ + && !MATCH_V_MAX_EILINE(P) && !MATCH_V_MIN_EILINE(P) \ + && !MATCH_W_MAX_EILINE(P) && !MATCH_W_MIN_EILINE(P) \ + && !MATCH_X2_MAX_EILINE(P) && !MATCH_X2_MIN_EILINE(P) \ + && !MATCH_Y2_MAX_EILINE(P) && !MATCH_Y2_MIN_EILINE(P) \ + && !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_CALIBRATION_EILINE(P) ) + +// One ISR for all EXT-Interrupts +void endstop_ISR() { endstops.update(); } + +void setup_endstop_interrupts() { + #define _ATTACH(P) attachInterrupt(P, endstop_ISR, CHANGE) + #if USE_X_MAX + #if !AVAILABLE_EILINE(X_MAX_PIN) + #error "X_MAX_PIN has no EXTINT line available. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #endif + _ATTACH(X_MAX_PIN); + #endif + #if USE_X_MIN + #if !AVAILABLE_EILINE(X_MIN_PIN) + #error "X_MIN_PIN has no EXTINT line available. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #endif + _ATTACH(X_MIN_PIN); + #endif + #if USE_Y_MAX + #if !AVAILABLE_EILINE(Y_MAX_PIN) + #error "Y_MAX_PIN has no EXTINT line available. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #endif + _ATTACH(Y_MAX_PIN); + #endif + #if USE_Y_MIN + #if !AVAILABLE_EILINE(Y_MIN_PIN) + #error "Y_MIN_PIN has no EXTINT line available. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #endif + _ATTACH(Y_MIN_PIN); + #endif + #if USE_Z_MAX + #if !AVAILABLE_EILINE(Z_MAX_PIN) + #error "Z_MAX_PIN has no EXTINT line available. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #endif + _ATTACH(Z_MAX_PIN); + #endif + #if USE_Z_MIN + #if !AVAILABLE_EILINE(Z_MIN_PIN) + #error "Z_MIN_PIN has no EXTINT line available. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #endif + _ATTACH(Z_MIN_PIN); + #endif + #if USE_X2_MAX + #if !AVAILABLE_EILINE(X2_MAX_PIN) + #error "X2_MAX_PIN has no EXTINT line available. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #endif + _ATTACH(X2_MAX_PIN); + #endif + #if USE_X2_MIN + #if !AVAILABLE_EILINE(X2_MIN_PIN) + #error "X2_MIN_PIN has no EXTINT line available. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #endif + _ATTACH(X2_MIN_PIN); + #endif + #if USE_Y2_MAX + #if !AVAILABLE_EILINE(Y2_MAX_PIN) + #error "Y2_MAX_PIN has no EXTINT line available. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #endif + _ATTACH(Y2_MAX_PIN); + #endif + #if USE_Y2_MIN + #if !AVAILABLE_EILINE(Y2_MIN_PIN) + #error "Y2_MIN_PIN has no EXTINT line available. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #endif + _ATTACH(Y2_MIN_PIN); + #endif + #if USE_Z2_MAX + #if !AVAILABLE_EILINE(Z2_MAX_PIN) + #error "Z2_MAX_PIN has no EXTINT line available. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #endif + _ATTACH(Z2_MAX_PIN); + #endif + #if USE_Z2_MIN + #if !AVAILABLE_EILINE(Z2_MIN_PIN) + #error "Z2_MIN_PIN has no EXTINT line available. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #endif + _ATTACH(Z2_MIN_PIN); + #endif + #if USE_Z3_MAX + #if !AVAILABLE_EILINE(Z3_MAX_PIN) + #error "Z3_MAX_PIN has no EXTINT line available. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #endif + _ATTACH(Z3_MAX_PIN); + #endif + #if USE_Z3_MIN + #if !AVAILABLE_EILINE(Z3_MIN_PIN) + #error "Z3_MIN_PIN has no EXTINT line available. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #endif + _ATTACH(Z3_MIN_PIN); + #endif + #if USE_Z4_MAX + #if !AVAILABLE_EILINE(Z4_MAX_PIN) + #error "Z4_MAX_PIN has no EXTINT line available. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #endif + _ATTACH(Z4_MAX_PIN); + #endif + #if USE_Z4_MIN + #if !AVAILABLE_EILINE(Z4_MIN_PIN) + #error "Z4_MIN_PIN has no EXTINT line available. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #endif + _ATTACH(Z4_MIN_PIN); + #endif + #if USE_Z_MIN_PROBE + #if !AVAILABLE_EILINE(Z_MIN_PROBE_PIN) + #error "Z_MIN_PROBE_PIN has no EXTINT line available. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #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." + #endif + attachInterrupt(I_MAX_PIN, endstop_ISR, CHANGE); + #endif + #if USE_I_MIN + #if !AVAILABLE_EILINE(I_MIN_PIN) + #error "I_MIN_PIN has no EXTINT line available. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #endif + attachInterrupt(I_MIN_PIN, endstop_ISR, CHANGE); + #endif + #if USE_J_MAX + #if !AVAILABLE_EILINE(J_MAX_PIN) + #error "J_MAX_PIN has no EXTINT line available. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #endif + attachInterrupt(J_MAX_PIN, endstop_ISR, CHANGE); + #endif + #if USE_J_MIN + #if !AVAILABLE_EILINE(J_MIN_PIN) + #error "J_MIN_PIN has no EXTINT line available. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #endif + attachInterrupt(J_MIN_PIN, endstop_ISR, CHANGE); + #endif + #if USE_K_MAX + #if !AVAILABLE_EILINE(K_MAX_PIN) + #error "K_MAX_PIN has no EXTINT line available. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #endif + attachInterrupt(K_MAX_PIN, endstop_ISR, CHANGE); + #endif + #if USE_K_MIN + #if !AVAILABLE_EILINE(K_MIN_PIN) + #error "K_MIN_PIN has no EXTINT line available. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #endif + attachInterrupt(K_MIN_PIN, endstop_ISR, CHANGE); + #endif + #if USE_U_MAX + #if !AVAILABLE_EILINE(U_MAX_PIN) + #error "U_MAX_PIN has no EXTINT line available. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #endif + attachInterrupt(U_MAX_PIN, endstop_ISR, CHANGE); + #endif + #if USE_U_MIN + #if !AVAILABLE_EILINE(U_MIN_PIN) + #error "U_MIN_PIN has no EXTINT line available. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #endif + attachInterrupt(U_MIN_PIN, endstop_ISR, CHANGE); + #endif + #if USE_V_MAX + #if !AVAILABLE_EILINE(V_MAX_PIN) + #error "V_MAX_PIN has no EXTINT line available. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #endif + attachInterrupt(V_MAX_PIN, endstop_ISR, CHANGE); + #endif + #if USE_V_MIN + #if !AVAILABLE_EILINE(V_MIN_PIN) + #error "V_MIN_PIN has no EXTINT line available. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #endif + attachInterrupt(V_MIN_PIN, endstop_ISR, CHANGE); + #endif + #if USE_W_MAX + #if !AVAILABLE_EILINE(W_MAX_PIN) + #error "W_MAX_PIN has no EXTINT line available. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #endif + attachInterrupt(W_MAX_PIN, endstop_ISR, CHANGE); + #endif + #if USE_W_MIN + #if !AVAILABLE_EILINE(W_MIN_PIN) + #error "W_MIN_PIN has no EXTINT line available. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #endif + attachInterrupt(W_MIN_PIN, endstop_ISR, CHANGE); + #endif +} diff --git a/Marlin/src/HAL/SAMD21/fastio.h b/Marlin/src/HAL/SAMD21/fastio.h new file mode 100644 index 0000000000..32cc557528 --- /dev/null +++ b/Marlin/src/HAL/SAMD21/fastio.h @@ -0,0 +1,215 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2022 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 + +/** + * SAMD21 HAL developed by Bart Meijer (brupje) + * Based on SAMD51 HAL by Giuliano Zaro (AKA GMagician) + */ + +/** + * Fast IO functions for SAMD21 + */ + +#include "SAMD21.h" + +/** + * Utility functions + */ + +#ifndef MASK + #define MASK(PIN) _BV(PIN) +#endif + +/** + * Magic I/O routines + * + * Now you can simply SET_OUTPUT(IO); WRITE(IO, HIGH); WRITE(IO, LOW); + */ + +// Read a pin +#define READ(IO) ((PORT->Group[(EPortType)GET_SAMD_PORT(IO)].IN.reg & MASK(GET_SAMD_PIN(IO))) != 0) + +// Write to a pin +#define WRITE(IO,V) do{ \ + const EPortType port = (EPortType)GET_SAMD_PORT(IO); \ + const uint32_t mask = MASK(GET_SAMD_PIN(IO)); \ + \ + if (V) PORT->Group[port].OUTSET.reg = mask; \ + else PORT->Group[port].OUTCLR.reg = mask; \ + }while(0) + +// Toggle a pin +#define TOGGLE(IO) PORT->Group[(EPortType)GET_SAMD_PORT(IO)].OUTTGL.reg = MASK(GET_SAMD_PIN(IO)); + +// Set pin as input +#define SET_INPUT(IO) do{ \ + const EPortType port = (EPortType)GET_SAMD_PORT(IO); \ + const uint32_t pin = GET_SAMD_PIN(IO); \ + \ + PORT->Group[port].PINCFG[pin].reg = (uint8_t)(PORT_PINCFG_INEN); \ + PORT->Group[port].DIRCLR.reg = MASK(pin); \ + }while(0) +// Set pin as input with pullup +#define SET_INPUT_PULLUP(IO) do{ \ + const EPortType port = (EPortType)GET_SAMD_PORT(IO); \ + const uint32_t pin = GET_SAMD_PIN(IO); \ + const uint32_t mask = MASK(pin); \ + \ + PORT->Group[port].PINCFG[pin].reg = (uint8_t)(PORT_PINCFG_INEN | PORT_PINCFG_PULLEN); \ + PORT->Group[port].DIRCLR.reg = mask; \ + PORT->Group[port].OUTSET.reg = mask; \ + }while(0) +// Set pin as input with pulldown +#define SET_INPUT_PULLDOWN(IO) do{ \ + const EPortType port = (EPortType)GET_SAMD_PORT(IO); \ + const uint32_t pin = GET_SAMD_PIN(IO); \ + const uint32_t mask = MASK(pin); \ + \ + PORT->Group[port].PINCFG[pin].reg = (uint8_t)(PORT_PINCFG_INEN | PORT_PINCFG_PULLEN); \ + PORT->Group[port].DIRCLR.reg = mask; \ + PORT->Group[port].OUTCLR.reg = mask; \ + }while(0) +// Set pin as output (push pull) +#define SET_OUTPUT(IO) do{ \ + const EPortType port = (EPortType)GET_SAMD_PORT(IO); \ + const uint32_t pin = GET_SAMD_PIN(IO); \ + \ + PORT->Group[port].DIRSET.reg = MASK(pin); \ + PORT->Group[port].PINCFG[pin].reg = 0; \ + }while(0) +// Set pin as output (open drain) +#define SET_OUTPUT_OD(IO) do{ \ + const EPortType port = (EPortType)GET_SAMD_PORT(IO); \ + const uint32_t pin = GET_SAMD_PIN(IO); \ + \ + PORT->Group[port].PINCFG[pin].reg = (uint8_t)(PORT_PINCFG_PULLEN); \ + PORT->Group[port].DIRCLR.reg = MASK(pin); \ + }while(0) +// Set pin as PWM (push pull) +#define SET_PWM SET_OUTPUT +// Set pin as PWM (open drain) +#define SET_PWM_OD SET_OUTPUT_OD + +// check if pin is an output +#define IS_OUTPUT(IO) ((PORT->Group[(EPortType)GET_SAMD_PORT(IO)].DIR.reg & MASK(GET_SAMD_PIN(IO))) \ + || (PORT->Group[(EPortType)GET_SAMD_PORT(IO)].PINCFG[GET_SAMD_PIN(IO)].reg & (PORT_PINCFG_INEN | PORT_PINCFG_PULLEN)) == PORT_PINCFG_PULLEN) +// check if pin is an input +#define IS_INPUT(IO) !IS_OUTPUT(IO) + +// Shorthand +#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) + +// digitalRead/Write wrappers +#define extDigitalRead(IO) digitalRead(IO) +#define extDigitalWrite(IO,V) digitalWrite(IO,V) + +/** + * Ports and functions + * Added as necessary or if I feel like it- not a comprehensive list! + */ + +/** + * Some of these share the same source and so can't be used in the same time + */ +#define PWM_PIN(P) (WITHIN(P, 2, 13) || WITHIN(P, 22, 23) || WITHIN(P, 44, 45) || P == 48) + +// Return fulfilled ADCx->INPUTCTRL.reg +#define PIN_TO_INPUTCTRL(P) ( (P == 0) ? ADC_INPUTCTRL_MUXPOS_PIN0 \ + : ((P) == 1) ? ADC_INPUTCTRL_MUXPOS_PIN1 \ + : ((P) == 2) ? ADC_INPUTCTRL_MUXPOS_PIN3 \ + : ((P) == 3) ? ADC_INPUTCTRL_MUXPOS_PIN4 \ + : ((P) == 4) ? ADC_INPUTCTRL_MUXPOS_PIN5 \ + : ((P) == 5) ? ADC_INPUTCTRL_MUXPOS_PIN5 \ + : ((P) == 6) ? ADC_INPUTCTRL_MUXPOS_PIN6 \ + : ((P) == 7) ? ADC_INPUTCTRL_MUXPOS_PIN7 \ + : ((P) == 8) ? ADC_INPUTCTRL_MUXPOS_PIN8 \ + : ((P) == 9) ? ADC_INPUTCTRL_MUXPOS_PIN9 \ + : ((P) == 10) ? ADC_INPUTCTRL_MUXPOS_PIN10 \ + : ((P) == 11) ? ADC_INPUTCTRL_MUXPOS_PIN11 \ + : ((P) == 12) ? ADC_INPUTCTRL_MUXPOS_PIN12 \ + : ((P) == 13) ? ADC_INPUTCTRL_MUXPOS_PIN13 \ + : ((P) == 14) ? ADC_INPUTCTRL_MUXPOS_PIN14 \ + : ADC_INPUTCTRL_MUXPOS_PIN15) + +#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 + */ + +// PORTA +#define DIO28_PIN PIN_PA02 // A0 +#define DIO56_PIN PIN_PA03 // A13 +#define DIO31_PIN PIN_PA04 // A13 +#define DIO32_PIN PIN_PA05 // A1 +#define DIO8_PIN PIN_PA06 // A14 +#define DIO9_PIN PIN_PA07 // A15 +#define DIO4_PIN PIN_PA08 // A15 +#define DIO3_PIN PIN_PA09 // A15 +#define DIO1_PIN PIN_PA10 +#define DIO0_PIN PIN_PA11 +#define DIO18_PIN PIN_PA12 +#define DIO52_PIN PIN_PA13 +#define DIO2_PIN PIN_PA14 +#define DIO5_PIN PIN_PA15 +#define DIO11_PIN PIN_PA16 +#define DIO13_PIN PIN_PA17 +#define DIO10_PIN PIN_PA18 +#define DIO12_PIN PIN_PA19 +#define DIO6_PIN PIN_PA20 +#define DIO07_PIN PIN_PA21 +#define DIO34_PIN PIN_PA22 +#define DIO35_PIN PIN_PA23 +#define DIO42_PIN PIN_PA24 +#define DIO43_PIN PIN_PA25 + +#define DIO40_PIN PIN_PA27 + +#define DIO26_PIN PIN_PB00 +#define DIO27_PIN PIN_PB01 // A0 +#define DIO33_PIN PIN_PB02 +#define DIO39_PIN PIN_PB03 +#define DIO14_PIN PIN_PB04 +#define DIO15_PIN PIN_PB05 +#define DIO16_PIN PIN_PB06 +#define DIO17_PIN PIN_PB07 +#define DIO29_PIN PIN_PB08 +#define DIO30_PIN PIN_PB09 +#define DIO37_PIN PIN_PB10 +#define DIO38_PIN PIN_PB11 +#define DIO36_PIN PIN_PB12 +#define DIO19_PIN PIN_PB13 +#define DIO20_PIN PIN_PB14 +#define DIO21_PIN PIN_PB15 +#define DIO22_PIN PIN_PB16 +#define DIO23_PIN PIN_PB17 + +#define DIO44_PIN PIN_PB22 +#define DIO45_PIN PIN_PB23 +#define DIO24_PIN PIN_PB30 +#define DIO25_PIN PIN_PB31 + +#define DIO53_PIN PIN_PA21 +#define DIO54_PIN PIN_PA06 +#define DIO55_PIN PIN_PA07 diff --git a/Marlin/src/HAL/SAMD21/inc/Conditionals_LCD.h b/Marlin/src/HAL/SAMD21/inc/Conditionals_LCD.h new file mode 100644 index 0000000000..570baf7e95 --- /dev/null +++ b/Marlin/src/HAL/SAMD21/inc/Conditionals_LCD.h @@ -0,0 +1,22 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2022 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/SAMD21/inc/Conditionals_adv.h b/Marlin/src/HAL/SAMD21/inc/Conditionals_adv.h new file mode 100644 index 0000000000..d6a3c4fe0b --- /dev/null +++ b/Marlin/src/HAL/SAMD21/inc/Conditionals_adv.h @@ -0,0 +1,27 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2022 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 . + * + */ + +/** + * SAMD21 HAL developed by Bart Meijer (brupje) + * Based on SAMD51 HAL by Giuliano Zaro (AKA GMagician) + */ +#pragma once diff --git a/Marlin/src/HAL/SAMD21/inc/Conditionals_post.h b/Marlin/src/HAL/SAMD21/inc/Conditionals_post.h new file mode 100644 index 0000000000..87d3350c94 --- /dev/null +++ b/Marlin/src/HAL/SAMD21/inc/Conditionals_post.h @@ -0,0 +1,33 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2022 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 . + * + */ + +/** + * SAMD21 HAL developed by Bart Meijer (brupje) + * Based on SAMD51 HAL by Giuliano Zaro (AKA GMagician) + */ +#pragma once + +#if USE_FALLBACK_EEPROM + #define FLASH_EEPROM_EMULATION +#elif ANY(I2C_EEPROM, SPI_EEPROM) + #define USE_SHARED_EEPROM 1 +#endif diff --git a/Marlin/src/HAL/SAMD21/inc/Conditionals_type.h b/Marlin/src/HAL/SAMD21/inc/Conditionals_type.h new file mode 100644 index 0000000000..82f95a1035 --- /dev/null +++ b/Marlin/src/HAL/SAMD21/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/SAMD21/inc/SanityCheck.h b/Marlin/src/HAL/SAMD21/inc/SanityCheck.h new file mode 100644 index 0000000000..83fafc9689 --- /dev/null +++ b/Marlin/src/HAL/SAMD21/inc/SanityCheck.h @@ -0,0 +1,54 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2022 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 . + * + */ + +/** + * SAMD21 HAL developed by Bart Meijer (brupje) + * Based on SAMD51 HAL by Giuliano Zaro (AKA GMagician) + */ + +/** + * Test SAMD21 specific configuration values for errors at compile-time. + */ + +#if HAS_SPI_TFT || HAS_FSMC_TFT + #error "Sorry! TFT displays are not available for HAL/SAMD21." +#endif + +#if SERVO_TC == MF_TIMER_RTC + #error "Servos can't use RTC timer" +#endif + +#if ENABLED(EMERGENCY_PARSER) + #error "EMERGENCY_PARSER is not yet implemented for SAMD21. Disable EMERGENCY_PARSER to continue." +#endif + +#if ENABLED(ONBOARD_SDIO) + #error "ONBOARD_SDIO is not supported on SAMD21." +#endif + +#if ENABLED(FAST_PWM_FAN) + #error "Features requiring Hardware PWM (FAST_PWM_FAN) are not yet supported for HAL/SAMD21." +#endif + +#if ENABLED(POSTMORTEM_DEBUGGING) + #error "POSTMORTEM_DEBUGGING is not yet supported on SAMD21." +#endif diff --git a/Marlin/src/HAL/SAMD21/pinsDebug.h b/Marlin/src/HAL/SAMD21/pinsDebug.h new file mode 100644 index 0000000000..387516aa79 --- /dev/null +++ b/Marlin/src/HAL/SAMD21/pinsDebug.h @@ -0,0 +1,175 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2022 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 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 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) != -1) +#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 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 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 + * ----------------+--------+------- + * 0 | PB25 | "RX0" + * 1 | PB24 | "TX0" + * 2 | PC18 | + * 3 | PC19 | + * 4 | PC20 | + * 5 | PC21 | + * 6 | PD20 | + * 7 | PD21 | + * 8 | PB18 | + * 9 | PB2 | + * 10 | PB22 | + * 11 | PB23 | + * 12 | PB0 | "A16" + * 13 | PB1 | LED AMBER "L" / "A17" + * 14 | PB16 | "TX3" + * 15 | PB17 | "RX3" + * 16 | PC22 | "TX2" + * 17 | PC23 | "RX2" + * 18 | PB12 | "TX1" / "A18" + * 19 | PB13 | "RX1" + * 20 | PB20 | "SDA" + * 21 | PB21 | "SCL" + * 22 | PD12 | + * 23 | PA15 | + * 24 | PC17 | + * 25 | PC16 | + * 26 | PA12 | + * 27 | PA13 | + * 28 | PA14 | + * 29 | PB19 | + * 30 | PA23 | + * 31 | PA22 | + * 32 | PA21 | + * 33 | PA20 | + * 34 | PA19 | + * 35 | PA18 | + * 36 | PA17 | + * 37 | PA16 | + * 38 | PB15 | + * 39 | PB14 | + * 40 | PC13 | + * 41 | PC12 | + * 42 | PC15 | + * 43 | PC14 | + * 44 | PC11 | + * 45 | PC10 | + * 46 | PC6 | + * 47 | PC7 | + * 48 | PC4 | + * 49 | PC5 | + * 50 | PD11 | + * 51 | PD8 | + * 52 | PD9 | + * 53 | PD10 | + * 54 | PB5 | "A8" + * 55 | PB6 | "A9" + * 56 | PB7 | "A10" + * 57 | PB8 | "A11" + * 58 | PB9 | "A12" + * 69 | PA4 | "A13" + * 60 | PA6 | "A14" + * 61 | PA7 | "A15" + * 62 | PB17 | + * 63 | PB20 | + * 64 | PD11 | + * 65 | PD8 | + * 66 | PD9 | + * 67 | PA2 | "A0" / "DAC0" + * 68 | PA5 | "A1" / "DAC1" + * 69 | PB3 | "A2" + * 70 | PC0 | "A3" + * 71 | PC1 | "A4" + * 72 | PC2 | "A5" + * 73 | PC3 | "A6" + * 74 | PB4 | "A7" + * 75 | PC31 | LED GREEN "RX" + * 76 | PC30 | LED GREEN "TX" + * 77 | PA27 | USB: Host enable + * 78 | PA24 | USB: D- + * 79 | PA25 | USB: D+ + * 80 | PB29 | SD: MISO + * 81 | PB27 | SD: SCK + * 82 | PB26 | SD: MOSI + * 83 | PB28 | SD: CS + * 84 | PA3 | AREF + * 85 | PA2 | DAC0 (Duplicate) + * 86 | PA5 | DAC1 (Duplicate) + * 87 | PB1 | LED AMBER "L" (Duplicate) + * 88 | PC24 | NeoPixel + * 89 | PB10 | QSPI: SCK + * 90 | PB11 | QSPI: CS + * 91 | PA8 | QSPI: IO0 + * 92 | PA9 | QSPI: IO1 + * 93 | PA10 | QSPI: IO2 + * 94 | PA11 | QSPI: IO3 + * 95 | PB31 | SD: DETECT + */ diff --git a/Marlin/src/HAL/SAMD21/spi_pins.h b/Marlin/src/HAL/SAMD21/spi_pins.h new file mode 100644 index 0000000000..1186c3de48 --- /dev/null +++ b/Marlin/src/HAL/SAMD21/spi_pins.h @@ -0,0 +1,47 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2022 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 + +/** + * SAMD21 HAL developed by Bart Meijer (brupje) + * Based on SAMD51 HAL by Giuliano Zaro (AKA GMagician) + */ + +/** + * SAMD21 Default SPI Pins + * + * SS SCK MISO MOSI + * +-------------------------+ + * SPI | 53 52 50 51 | + * SPI1 | 83 81 80 82 | + * +-------------------------+ + * Any pin can be used for Chip Select (SD_SS_PIN) + */ +#ifndef SD_SCK_PIN + #define SD_SCK_PIN 38 +#endif +#ifndef SD_MISO_PIN + #define SD_MISO_PIN 36 +#endif +#ifndef SD_MOSI_PIN + #define SD_MOSI_PIN 37 +#endif diff --git a/Marlin/src/HAL/SAMD21/timers.cpp b/Marlin/src/HAL/SAMD21/timers.cpp new file mode 100644 index 0000000000..21a1767fc0 --- /dev/null +++ b/Marlin/src/HAL/SAMD21/timers.cpp @@ -0,0 +1,217 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2022 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 . + * + */ + +/** + * SAMD21 HAL developed by Bart Meijer (brupje) + * Based on SAMD51 HAL by Giuliano Zaro (AKA GMagician) + */ +#ifdef __SAMD21__ + +// -------------------------------------------------------------------------- +// Includes +// -------------------------------------------------------------------------- + +#include "../../inc/MarlinConfig.h" +#include "ServoTimers.h" // for SERVO_TC + +// -------------------------------------------------------------------------- +// Local defines +// -------------------------------------------------------------------------- + +#define NUM_HARDWARE_TIMERS 9 + +// -------------------------------------------------------------------------- +// Private Variables +// -------------------------------------------------------------------------- + +const tTimerConfig timer_config[NUM_HARDWARE_TIMERS] = { + { {.pTcc=TCC0}, TimerType::tcc, TCC0_IRQn, TC_PRIORITY(0) }, // 0 - stepper (assigned priority 2) + { {.pTcc=TCC1}, TimerType::tcc, TCC1_IRQn, TC_PRIORITY(1) }, // 1 - stepper (needed by 32 bit timers) + { {.pTcc=TCC2}, TimerType::tcc, TCC2_IRQn, 5 }, // 2 - tone (reserved by framework and fixed assigned priority 5) + { {.pTc=TC3}, TimerType::tc, TC3_IRQn, TC_PRIORITY(3) }, // 3 - servo (assigned priority 1) + { {.pTc=TC4}, TimerType::tc, TC4_IRQn, TC_PRIORITY(4) }, // 4 - software serial (no interrupts used) + { {.pTc=TC5}, TimerType::tc, TC5_IRQn, TC_PRIORITY(5) }, + { {.pTc=TC6}, TimerType::tc, TC6_IRQn, TC_PRIORITY(6) }, + { {.pTc=TC7}, TimerType::tc, TC7_IRQn, TC_PRIORITY(7) }, + { {.pRtc=RTC}, TimerType::rtc, RTC_IRQn, TC_PRIORITY(8) } // 8 - temperature (assigned priority 6) +}; + +// -------------------------------------------------------------------------- +// Private functions +// -------------------------------------------------------------------------- + +FORCE_INLINE void Disable_Irq(IRQn_Type irq) { + NVIC_DisableIRQ(irq); + + // We NEED memory barriers to ensure Interrupts are actually disabled! + // ( https://dzone.com/articles/nvic-disabling-interrupts-on-arm-cortex-m-and-the ) + __DSB(); + __ISB(); +} + +static bool tcIsSyncing(Tc * tc) { + return tc->COUNT32.STATUS.reg & TC_STATUS_SYNCBUSY; +} + +static void tcReset( Tc * tc) { + tc->COUNT32.CTRLA.reg = TC_CTRLA_SWRST; + while (tcIsSyncing(tc)) {} + while (tc->COUNT32.CTRLA.bit.SWRST) {} +} + +// -------------------------------------------------------------------------- +// Public functions +// -------------------------------------------------------------------------- + +void HAL_timer_start(const uint8_t timer_num, const uint32_t frequency) { + IRQn_Type irq = timer_config[timer_num].IRQ_Id; + + // Disable interrupt, just in case it was already enabled + NVIC_DisableIRQ(irq); + NVIC_ClearPendingIRQ(irq); + + if (timer_num == MF_TIMER_RTC) { + + // https://github.com/arduino-libraries/RTCZero + Rtc * const rtc = timer_config[timer_num].pRtc; + PM->APBAMASK.reg |= PM_APBAMASK_RTC; + + GCLK->CLKCTRL.reg = (uint32_t)((GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK4 | (RTC_GCLK_ID << GCLK_CLKCTRL_ID_Pos))); + while (GCLK->STATUS.bit.SYNCBUSY) {} + + GCLK->GENCTRL.reg = (GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_OSCULP32K | GCLK_GENCTRL_ID(4) | GCLK_GENCTRL_DIVSEL ); + while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY) {} + + GCLK->GENDIV.reg = GCLK_GENDIV_ID(4); + GCLK->GENDIV.bit.DIV=4; + while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY) {} + + // Disable timer interrupt + rtc->MODE0.INTENCLR.reg = RTC_MODE0_INTENCLR_CMP0; + SYNC(rtc->MODE0.STATUS.bit.SYNCBUSY); + + while(rtc->MODE0.STATUS.bit.SYNCBUSY) {} + + // Stop timer, just in case, to be able to reconfigure it + rtc->MODE0.CTRL.reg = + RTC_MODE0_CTRL_MODE_COUNT32 | // Mode 0 = 32-bits counter + RTC_MODE0_CTRL_PRESCALER_DIV1024; // Divisor = 1024 + + while(rtc->MODE0.STATUS.bit.SYNCBUSY) {} + + // Mode, reset counter on match + rtc->MODE0.CTRL.reg = RTC_MODE0_CTRL_MODE_COUNT32 | RTC_MODE0_CTRL_MATCHCLR; + + // Set compare value + rtc->MODE0.COMP[0].reg = (32768 + frequency / 2) / frequency; + SYNC(rtc->MODE0.STATUS.bit.SYNCBUSY); + + // Enable interrupt on compare + rtc->MODE0.INTFLAG.reg = RTC_MODE0_INTFLAG_CMP0; // reset pending interrupt + rtc->MODE0.INTENSET.reg = RTC_MODE0_INTENSET_CMP0; // enable compare 0 interrupt + + // And start timer + rtc->MODE0.CTRL.bit.ENABLE = true; + SYNC(rtc->MODE0.STATUS.bit.SYNCBUSY); + + } + else if (timer_config[timer_num].type==TimerType::tcc) { + + Tcc * const tc = timer_config[timer_num].pTcc; + + PM->APBCMASK.reg |= PM_APBCMASK_TCC0; + GCLK->CLKCTRL.reg =(GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID(TCC0_GCLK_ID)); + SYNC (GCLK->STATUS.bit.SYNCBUSY); + + tc->CTRLA.reg = TCC_CTRLA_SWRST; + SYNC (tc->SYNCBUSY.reg & TCC_SYNCBUSY_SWRST) {} + + SYNC (tc->CTRLA.bit.SWRST); + + tc->CTRLA.reg &= ~(TCC_CTRLA_ENABLE); // disable TC module + + tc->CTRLA.reg |= TCC_WAVE_WAVEGEN_MFRQ; + tc->CTRLA.reg |= TCC_CTRLA_PRESCALER_DIV2; + tc->CC[0].reg = (HAL_TIMER_RATE) / frequency; + tc->INTENSET.reg = TCC_INTFLAG_MC0; + tc->CTRLA.reg |= TCC_CTRLA_ENABLE; + tc->INTFLAG.reg = 0xFF; + SYNC ( tc->STATUS.reg & TC_STATUS_SYNCBUSY); + + } + else { + Tc * const tc = timer_config[timer_num].pTc; + + // Disable timer interrupt + 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)); + SYNC (GCLK->STATUS.bit.SYNCBUSY); + + tcReset(tc); // reset TC + + // Set Timer counter 5 Mode to 16 bits, it will become a 16bit counter ('mode1' in the datasheet) + tc->COUNT32.CTRLA.reg |= TC_CTRLA_MODE_COUNT32; + // Set TC waveform generation mode to 'match 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 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) + //this is how we fine-tune the frequency, make it count to a lower or higher value + //system clock should be 1MHz (8MHz/8) at Reset by default + tc->COUNT32.CC[0].reg = (uint16_t) (HAL_TIMER_RATE / frequency); + while (tcIsSyncing(tc)) {} + + // Enable the TC interrupt request + tc->COUNT32.INTENSET.bit.MC0 = 1; + while (tcIsSyncing(tc)) {} + } + + NVIC_SetPriority(irq, timer_config[timer_num].priority); + NVIC_EnableIRQ(irq); +} + +void HAL_timer_enable_interrupt(const uint8_t timer_num) { + const IRQn_Type irq = timer_config[timer_num].IRQ_Id; + NVIC_EnableIRQ(irq); +} + +void HAL_timer_disable_interrupt(const uint8_t timer_num) { + const IRQn_Type irq = timer_config[timer_num].IRQ_Id; + Disable_Irq(irq); +} + +// missing from CMSIS: Check if interrupt is enabled or not +static bool NVIC_GetEnabledIRQ(IRQn_Type IRQn) { + return TEST(NVIC->ISER[uint32_t(IRQn) >> 5], uint32_t(IRQn) & 0x1F); +} + +bool HAL_timer_interrupt_enabled(const uint8_t timer_num) { + const IRQn_Type irq = timer_config[timer_num].IRQ_Id; + return NVIC_GetEnabledIRQ(irq); +} + +#endif // __SAMD21__ diff --git a/Marlin/src/HAL/SAMD21/timers.h b/Marlin/src/HAL/SAMD21/timers.h new file mode 100644 index 0000000000..dfae605595 --- /dev/null +++ b/Marlin/src/HAL/SAMD21/timers.h @@ -0,0 +1,158 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2022 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 + +/** + * SAMD21 HAL developed by Bart Meijer (brupje) + * Based on SAMD51 HAL by Giuliano Zaro (AKA GMagician) + */ + +#include + +// -------------------------------------------------------------------------- +// Defines +// -------------------------------------------------------------------------- + +typedef uint32_t hal_timer_t; +#define HAL_TIMER_TYPE_MAX hal_timer_t(UINT32_MAX) + +#define HAL_TIMER_RATE F_CPU // frequency of timers peripherals + +#define MF_TIMER_RTC 8 // This is not a TC but a RTC + +#ifndef MF_TIMER_STEP + #define MF_TIMER_STEP 4 // 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 MF_TIMER_RTC // Timer Index for Temperature +#endif + +#define TEMP_TIMER_FREQUENCY 1000 // (Hz) Temperature ISR frequency + +#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 // (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) + +#define TC_PRIORITY(t) ( t == SERVO_TC ? 1 \ + : (t == MF_TIMER_STEP || t == MF_TIMER_PULSE) ? 2 \ + : (t == MF_TIMER_TEMP) ? 6 : 7 ) + +#define _TC_HANDLER(t) void TC##t##_Handler() +#define TC_HANDLER(t) _TC_HANDLER(t) +#ifndef HAL_STEP_TIMER_ISR + #define HAL_STEP_TIMER_ISR() TC_HANDLER(MF_TIMER_STEP) +#endif +#if MF_TIMER_STEP != MF_TIMER_PULSE + #define HAL_PULSE_TIMER_ISR() TC_HANDLER(MF_TIMER_PULSE) +#endif +#if MF_TIMER_TEMP == MF_TIMER_RTC + #define HAL_TEMP_TIMER_ISR() void RTC_Handler() +#else + #define HAL_TEMP_TIMER_ISR() TC_HANDLER(MF_TIMER_TEMP) +#endif + +// -------------------------------------------------------------------------- +// Types +// -------------------------------------------------------------------------- +typedef enum { tcc, tc, rtc } TimerType; + +typedef struct { + union { + Tc *pTc; + Tcc *pTcc; + Rtc *pRtc; + }; + TimerType type; + IRQn_Type IRQ_Id; + uint8_t priority; +} tTimerConfig; + +// -------------------------------------------------------------------------- +// Public Variables +// -------------------------------------------------------------------------- + +extern const tTimerConfig timer_config[]; + +// -------------------------------------------------------------------------- +// Public functions +// -------------------------------------------------------------------------- + +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) { + // Should never be called with timer MF_TIMER_RTC + Tc * const tc = timer_config[timer_num].pTc; + tc->COUNT32.CC[0].reg = compare; +} + +FORCE_INLINE static hal_timer_t HAL_timer_get_compare(const uint8_t timer_num) { + // Should never be called with timer MF_TIMER_RTC + Tc * const tc = timer_config[timer_num].pTc; + return (hal_timer_t)tc->COUNT32.CC[0].reg; +} + +FORCE_INLINE static hal_timer_t HAL_timer_get_count(const uint8_t timer_num) { + // Should never be called with timer MF_TIMER_RTC + Tc * const tc = timer_config[timer_num].pTc; + tc->COUNT32.READREQ.reg = TC_READREQ_RREQ; + // Request a read synchronization + SYNC (tc->COUNT32.STATUS.bit.SYNCBUSY); + //SYNC(tc->COUNT32.STATUS.bit.SYNCBUSY ); + return tc->COUNT32.COUNT.reg; +} + +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); + +FORCE_INLINE static void HAL_timer_isr_prologue(const uint8_t timer_num) { + if (timer_num == MF_TIMER_RTC) { + 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) { + Tcc * const tc = timer_config[timer_num].pTcc; + // Clear interrupt flag + tc->INTFLAG.reg = TCC_INTFLAG_OVF; + } + else { + Tc * const tc = timer_config[timer_num].pTc; + // Clear interrupt flag + tc->COUNT32.INTFLAG.bit.MC0 = 1; + } +} + +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 new file mode 100644 index 0000000000..7cbedd84fc --- /dev/null +++ b/Marlin/src/HAL/SAMD21/u8g/LCD_I2C_routines.cpp @@ -0,0 +1,32 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2022 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 . + * + */ + +/** + * SAMD21 HAL developed by Bart Meijer (brupje) + * Based on SAMD51 HAL by Giuliano Zaro (AKA GMagician) + */ +// adapted from I2C/master/master.c example +// https://www-users.york.ac.uk/~pcc1/MCP/HAPR-Course-web/CMSIS/examples/html/master_8c_source.html + +#ifdef __SAMD21__ + +#endif // __SAMD21__ diff --git a/Marlin/src/HAL/SAMD21/u8g/LCD_I2C_routines.h b/Marlin/src/HAL/SAMD21/u8g/LCD_I2C_routines.h new file mode 100644 index 0000000000..d6a3c4fe0b --- /dev/null +++ b/Marlin/src/HAL/SAMD21/u8g/LCD_I2C_routines.h @@ -0,0 +1,27 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2022 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 . + * + */ + +/** + * SAMD21 HAL developed by Bart Meijer (brupje) + * Based on SAMD51 HAL by Giuliano Zaro (AKA GMagician) + */ +#pragma once diff --git a/Marlin/src/HAL/SAMD21/u8g/LCD_defines.h b/Marlin/src/HAL/SAMD21/u8g/LCD_defines.h new file mode 100644 index 0000000000..d6fe93117b --- /dev/null +++ b/Marlin/src/HAL/SAMD21/u8g/LCD_defines.h @@ -0,0 +1,29 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2022 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 + +/** + * SAMD21 LCD-specific defines + */ + +uint8_t u8g_com_samd21_st7920_hw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr); +#define U8G_COM_ST7920_HAL_HW_SPI u8g_com_samd21_st7920_hw_spi_fn diff --git a/Marlin/src/HAL/STM32/watchdog.cpp b/Marlin/src/HAL/SAMD21/u8g/LCD_pin_routines.c similarity index 58% rename from Marlin/src/HAL/STM32/watchdog.cpp rename to Marlin/src/HAL/SAMD21/u8g/LCD_pin_routines.c index cc18553149..b2079fef9b 100644 --- a/Marlin/src/HAL/STM32/watchdog.cpp +++ b/Marlin/src/HAL/SAMD21/u8g/LCD_pin_routines.c @@ -1,6 +1,6 @@ /** * Marlin 3D Printer Firmware - * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] + * Copyright (c) 2022 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] * * Based on Sprinter and grbl. * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm @@ -19,29 +19,24 @@ * along with this program. If not, see . * */ -#if defined(ARDUINO_ARCH_STM32) && !defined(STM32GENERIC) -#include "../../inc/MarlinConfigPre.h" +/** + * SAMD21 HAL developed by Bart Meijer (brupje) + * Based on SAMD51 HAL by Giuliano Zaro (AKA GMagician) + */ -#if ENABLED(USE_WATCHDOG) +/** + * Low level pin manipulation routines - used by all the drivers. + * + * These are based on the SAMD51 pinMode, digitalRead & digitalWrite routines. + * + * 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 25% of the CPU's time. + */ - #include "../../inc/MarlinConfig.h" +#ifdef __SAMD21__ - #include "watchdog.h" - #include +#include - void watchdog_init() { - #if DISABLED(DISABLE_WATCHDOG_INIT) - IWatchdog.begin(4000000); // 4 sec timeout - #endif - } - - void HAL_watchdog_refresh() { - IWatchdog.reload(); - #if DISABLED(PINS_DEBUGGING) && PIN_EXISTS(LED) - TOGGLE(LED_PIN); // heartbeat indicator - #endif - } - -#endif // USE_WATCHDOG -#endif // ARDUINO_ARCH_STM32 && !STM32GENERIC +#endif // __SAMD21__ diff --git a/Marlin/src/HAL/SAMD21/u8g/LCD_pin_routines.h b/Marlin/src/HAL/SAMD21/u8g/LCD_pin_routines.h new file mode 100644 index 0000000000..a4bf2800fe --- /dev/null +++ b/Marlin/src/HAL/SAMD21/u8g/LCD_pin_routines.h @@ -0,0 +1,42 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2022 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 . + * + */ + +/** + * SAMD21 HAL developed by Bart Meijer (brupje) + * Based on SAMD51 HAL by Giuliano Zaro (AKA GMagician) + */ +#pragma once + +/** + * Low level pin manipulation routines - used by all the drivers. + * + * These are based on the SAMD51 pinMode, digitalRead & digitalWrite routines. + * + * 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 25% of the CPU's time. + */ + +void u8g_SetPinOutput(uint8_t internal_pin_number); +void u8g_SetPinInput(uint8_t internal_pin_number); +void u8g_SetPinLevel(uint8_t pin, uint8_t pin_status); +uint8_t u8g_GetPinLevel(uint8_t pin); diff --git a/Marlin/src/HAL/SAMD21/u8g/u8g_com_HAL_samd21_shared_hw_spi.cpp b/Marlin/src/HAL/SAMD21/u8g/u8g_com_HAL_samd21_shared_hw_spi.cpp new file mode 100644 index 0000000000..2e2b0d8f8d --- /dev/null +++ b/Marlin/src/HAL/SAMD21/u8g/u8g_com_HAL_samd21_shared_hw_spi.cpp @@ -0,0 +1,157 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2022 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 . + * + */ + +/** + * SAMD21 HAL developed by Bart Meijer (brupje) + * Based on SAMD51 HAL by Giuliano Zaro (AKA GMagician) + */ + +/** + * Based on u8g_com_msp430_hw_spi.c + * + * Universal 8bit Graphics Library + * + * Copyright (c) 2012, olikraus@gmail.com + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this list + * of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifdef __SAMD21__ + +#include "../../../inc/MarlinConfigPre.h" + +#if HAS_MARLINUI_U8GLIB + +#include +#include "SPI.h" + +#include "../../shared/HAL_SPI.h" + +#ifndef LCD_SPI_SPEED + #define LCD_SPI_SPEED SPI_HALF_SPEED +#endif + +void u8g_SetPIOutput(u8g_t *u8g, uint8_t pin_index) { + if (u8g->pin_list[pin_index]!= U8G_PIN_NONE) + pinMode(u8g->pin_list[pin_index],OUTPUT); +} + +void u8g_SetPILevel(u8g_t *u8g, uint8_t pin_index, uint8_t level) { + if (u8g->pin_list[pin_index]!= U8G_PIN_NONE) + digitalWrite(u8g->pin_list[pin_index],level); +} + +uint8_t u8g_com_samd21_st7920_hw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr) { + + switch (msg) { + case U8G_COM_MSG_STOP: + break; + + case U8G_COM_MSG_INIT: + u8g_SetPIOutput(u8g, U8G_PI_CS); + u8g_SetPIOutput(u8g, U8G_PI_A0); + u8g_SetPIOutput(u8g, U8G_PI_RESET); + + u8g_SetPILevel(u8g, U8G_PI_CS, LOW); + + spiBegin(); + u8g->pin_list[U8G_PI_A0_STATE] = 0; + break; + + case U8G_COM_MSG_ADDRESS: // define cmd (arg_val = 0) or data mode (arg_val = 1) + u8g_SetPILevel(u8g, U8G_PI_A0, arg_val); + u8g->pin_list[U8G_PI_A0_STATE] = arg_val; + break; + + case U8G_COM_MSG_CHIP_SELECT: // arg_val == 1 means chip selected, but ST7920 is active high, so needs inverting + u8g_SetPILevel(u8g, U8G_PI_CS, arg_val ? HIGH : LOW); + break; + + case U8G_COM_MSG_RESET: + u8g_SetPILevel(u8g, U8G_PI_RESET, arg_val); + break; + + case U8G_COM_MSG_WRITE_BYTE: + spiBeginTransaction(LCD_SPI_SPEED, MSBFIRST, SPI_MODE0); + + if (u8g->pin_list[U8G_PI_A0_STATE] == 0) { // command + SPI.transfer(0x0f8); u8g->pin_list[U8G_PI_A0_STATE] = 2; + } + else if (u8g->pin_list[U8G_PI_A0_STATE] == 1) { // data + SPI.transfer(0x0fa); u8g->pin_list[U8G_PI_A0_STATE] = 2; + } + + SPI.transfer(arg_val & 0x0f0); + SPI.transfer(arg_val << 4); + SPI.endTransaction(); + break; + + case U8G_COM_MSG_WRITE_SEQ: + spiBeginTransaction(LCD_SPI_SPEED, MSBFIRST, SPI_MODE0); + + if (u8g->pin_list[U8G_PI_A0_STATE] == 0 ) { // command + SPI.transfer(0x0f8); u8g->pin_list[U8G_PI_A0_STATE] = 2; + } + else if (u8g->pin_list[U8G_PI_A0_STATE] == 1) { // data + SPI.transfer(0x0fa); u8g->pin_list[U8G_PI_A0_STATE] = 2; + } + + uint8_t *ptr = (uint8_t*)arg_ptr; + while (arg_val > 0) { + SPI.transfer((*ptr) & 0x0f0); + SPI.transfer((*ptr) << 4); + ptr++; + arg_val--; + } + + SPI.endTransaction(); + break; + } + return 1; +} + +#endif // HAS_MARLINUI_U8GLIB + +#endif // __SAMD21__ diff --git a/Marlin/src/HAL/SAMD51/HAL.cpp b/Marlin/src/HAL/SAMD51/HAL.cpp index 9f24d30071..aea908707b 100644 --- a/Marlin/src/HAL/SAMD51/HAL.cpp +++ b/Marlin/src/HAL/SAMD51/HAL.cpp @@ -1,8 +1,9 @@ /** * Marlin 3D Printer Firmware - * * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * SAMD51 HAL developed by Giuliano Zaro (AKA GMagician) + * + * 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 @@ -18,38 +19,69 @@ * along with this program. If not, see . * */ + +/** + * SAMD51 HAL developed by Giuliano Zaro (AKA GMagician) + */ #ifdef __SAMD51__ #include "../../inc/MarlinConfig.h" #include #include -// ------------------------ -// Local defines -// ------------------------ +#ifdef ADAFRUIT_GRAND_CENTRAL_M4 + #if USING_HW_SERIALUSB + DefaultSerial1 MSerial0(false, Serial); + #endif + #if USING_HW_SERIAL0 + DefaultSerial2 MSerial1(false, Serial1); + #endif + #if USING_HW_SERIAL1 + DefaultSerial3 MSerial2(false, Serial2); + #endif + #if USING_HW_SERIAL2 + DefaultSerial4 MSerial3(false, Serial3); + #endif + #if USING_HW_SERIAL3 + DefaultSerial5 MSerial4(false, Serial4); + #endif +#endif -#define GET_TEMP_0_ADC() TERN(HAS_TEMP_ADC_0, PIN_TO_ADC(TEMP_0_PIN), -1) -#define GET_TEMP_1_ADC() TERN(HAS_TEMP_ADC_1, PIN_TO_ADC(TEMP_1_PIN), -1) -#define GET_TEMP_2_ADC() TERN(HAS_TEMP_ADC_2, PIN_TO_ADC(TEMP_2_PIN), -1) -#define GET_TEMP_3_ADC() TERN(HAS_TEMP_ADC_3, PIN_TO_ADC(TEMP_3_PIN), -1) -#define GET_TEMP_4_ADC() TERN(HAS_TEMP_ADC_4, PIN_TO_ADC(TEMP_4_PIN), -1) -#define GET_TEMP_5_ADC() TERN(HAS_TEMP_ADC_5, PIN_TO_ADC(TEMP_5_PIN), -1) -#define GET_TEMP_6_ADC() TERN(HAS_TEMP_ADC_6, PIN_TO_ADC(TEMP_6_PIN), -1) -#define GET_TEMP_7_ADC() TERN(HAS_TEMP_ADC_7, PIN_TO_ADC(TEMP_7_PIN), -1) -#define GET_PROBE_ADC() TERN(HAS_TEMP_PROBE, PIN_TO_ADC(TEMP_PROBE_PIN), -1) -#define GET_BED_ADC() TERN(HAS_TEMP_ADC_BED, PIN_TO_ADC(TEMP_BED_PIN), -1) -#define GET_CHAMBER_ADC() TERN(HAS_TEMP_ADC_CHAMBER, PIN_TO_ADC(TEMP_CHAMBER_PIN), -1) -#define GET_FILAMENT_WIDTH_ADC() TERN(FILAMENT_WIDTH_SENSOR, PIN_TO_ADC(FILWIDTH_PIN), -1) -#define GET_BUTTONS_ADC() TERN(HAS_ADC_BUTTONS, PIN_TO_ADC(ADC_KEYPAD_PIN), -1) +#define GET_TEMP_0_ADC() TERN(HAS_TEMP_ADC_0, PIN_TO_ADC(TEMP_0_PIN), -1) +#define GET_TEMP_1_ADC() TERN(HAS_TEMP_ADC_1, PIN_TO_ADC(TEMP_1_PIN), -1) +#define GET_TEMP_2_ADC() TERN(HAS_TEMP_ADC_2, PIN_TO_ADC(TEMP_2_PIN), -1) +#define GET_TEMP_3_ADC() TERN(HAS_TEMP_ADC_3, PIN_TO_ADC(TEMP_3_PIN), -1) +#define GET_TEMP_4_ADC() TERN(HAS_TEMP_ADC_4, PIN_TO_ADC(TEMP_4_PIN), -1) +#define GET_TEMP_5_ADC() TERN(HAS_TEMP_ADC_5, PIN_TO_ADC(TEMP_5_PIN), -1) +#define GET_TEMP_6_ADC() TERN(HAS_TEMP_ADC_6, PIN_TO_ADC(TEMP_6_PIN), -1) +#define GET_TEMP_7_ADC() TERN(HAS_TEMP_ADC_7, PIN_TO_ADC(TEMP_7_PIN), -1) +#define GET_BED_ADC() TERN(HAS_TEMP_ADC_BED, PIN_TO_ADC(TEMP_BED_PIN), -1) +#define GET_CHAMBER_ADC() TERN(HAS_TEMP_ADC_CHAMBER, PIN_TO_ADC(TEMP_CHAMBER_PIN), -1) +#define GET_PROBE_ADC() TERN(HAS_TEMP_ADC_PROBE, PIN_TO_ADC(TEMP_PROBE_PIN), -1) +#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(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) +#define GET_JOY_ADC_Z() TERN(HAS_JOY_ADC_Z, PIN_TO_ADC(JOY_Z_PIN), -1) +#define GET_POWERMON_ADC_CURRENT() TERN(POWER_MONITOR_CURRENT, PIN_TO_ADC(POWER_MONITOR_CURRENT_PIN), -1) +#define GET_POWERMON_ADC_VOLTS() TERN(POWER_MONITOR_VOLTAGE, PIN_TO_ADC(POWER_MONITOR_VOLTAGE_PIN), -1) #define IS_ADC_REQUIRED(n) ( \ GET_TEMP_0_ADC() == n || GET_TEMP_1_ADC() == n || GET_TEMP_2_ADC() == n || GET_TEMP_3_ADC() == n \ || GET_TEMP_4_ADC() == n || GET_TEMP_5_ADC() == n || GET_TEMP_6_ADC() == n || GET_TEMP_7_ADC() == n \ - || GET_PROBE_ADC() == n \ - || GET_BED_ADC() == n \ - || GET_CHAMBER_ADC() == n \ - || GET_FILAMENT_WIDTH_ADC() == n \ - || GET_BUTTONS_ADC() == n \ + || GET_BED_ADC() == n \ + || GET_CHAMBER_ADC() == n \ + || GET_PROBE_ADC() == n \ + || GET_COOLER_ADC() == n \ + || GET_BOARD_ADC() == n || GET_SOC_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 \ ) #if IS_ADC_REQUIRED(0) @@ -69,6 +101,179 @@ #define DMA_IS_REQUIRED 1 #endif +enum ADCIndex { + #if GET_TEMP_0_ADC() == 0 + TEMP_0, + #endif + #if GET_TEMP_1_ADC() == 0 + TEMP_1, + #endif + #if GET_TEMP_2_ADC() == 0 + TEMP_2, + #endif + #if GET_TEMP_3_ADC() == 0 + TEMP_3, + #endif + #if GET_TEMP_4_ADC() == 0 + TEMP_4, + #endif + #if GET_TEMP_5_ADC() == 0 + TEMP_5, + #endif + #if GET_TEMP_6_ADC() == 0 + TEMP_6, + #endif + #if GET_TEMP_7_ADC() == 0 + TEMP_7, + #endif + #if GET_BED_ADC() == 0 + TEMP_BED, + #endif + #if GET_CHAMBER_ADC() == 0 + TEMP_CHAMBER, + #endif + #if GET_PROBE_ADC() == 0 + TEMP_PROBE, + #endif + #if GET_COOLER_ADC() == 0 + TEMP_COOLER, + #endif + #if GET_BOARD_ADC() == 0 + TEMP_BOARD, + #endif + #if GET_SOC_ADC() == 0 + TEMP_SOC, + #endif + #if GET_FILAMENT_WIDTH_ADC() == 0 + FILWIDTH, + #endif + #if GET_FILAMENT2_WIDTH_ADC() == 0 + FILWIDTH2, + #endif + #if GET_BUTTONS_ADC() == 0 + ADC_KEY, + #endif + #if GET_JOY_ADC_X() == 0 + JOY_X, + #endif + #if GET_JOY_ADC_Y() == 0 + JOY_Y, + #endif + #if GET_JOY_ADC_Z() == 0 + JOY_Z, + #endif + #if GET_POWERMON_ADC_CURRENT() == 0 + POWERMON_CURRENT, + #endif + #if GET_POWERMON_ADC_VOLTS() == 0 + POWERMON_VOLTAGE, + #endif + + // Indexes for ADC1 after those for ADC0 + + #if GET_TEMP_0_ADC() == 1 + TEMP_0, + #endif + #if GET_TEMP_1_ADC() == 1 + TEMP_1, + #endif + #if GET_TEMP_2_ADC() == 1 + TEMP_2, + #endif + #if GET_TEMP_3_ADC() == 1 + TEMP_3, + #endif + #if GET_TEMP_4_ADC() == 1 + TEMP_4, + #endif + #if GET_TEMP_5_ADC() == 1 + TEMP_5, + #endif + #if GET_TEMP_6_ADC() == 1 + TEMP_6, + #endif + #if GET_TEMP_7_ADC() == 1 + TEMP_7, + #endif + #if GET_BED_ADC() == 1 + TEMP_BED, + #endif + #if GET_CHAMBER_ADC() == 1 + TEMP_CHAMBER, + #endif + #if GET_PROBE_ADC() == 1 + TEMP_PROBE, + #endif + #if GET_COOLER_ADC() == 1 + TEMP_COOLER, + #endif + #if GET_BOARD_ADC() == 1 + TEMP_BOARD, + #endif + #if GET_SOC_ADC() == 1 + TEMP_SOC, + #endif + #if GET_FILAMENT_WIDTH_ADC() == 1 + FILWIDTH, + #endif + #if GET_FILAMENT2_WIDTH_ADC() == 1 + FILWIDTH2, + #endif + #if GET_BUTTONS_ADC() == 1 + ADC_KEY, + #endif + #if GET_JOY_ADC_X() == 1 + JOY_X, + #endif + #if GET_JOY_ADC_Y() == 1 + JOY_Y, + #endif + #if GET_JOY_ADC_Z() == 1 + JOY_Z, + #endif + #if GET_POWERMON_ADC_CURRENT() == 1 + POWERMON_CURRENT, + #endif + #if GET_POWERMON_ADC_VOLTS() == 1 + POWERMON_VOLTAGE, + #endif + ADC_COUNT +}; + +#if ENABLED(USE_WATCHDOG) + + #define WDT_TIMEOUT_REG TERN(WATCHDOG_DURATION_8S, WDT_CONFIG_PER_CYC8192, WDT_CONFIG_PER_CYC4096) // 4 or 8 second timeout + + void MarlinHAL::watchdog_init() { + // The low-power oscillator used by the WDT runs at 32,768 Hz with + // a 1:32 prescale, thus 1024 Hz, though probably not super precise. + + // Setup WDT clocks + MCLK->APBAMASK.bit.OSC32KCTRL_ = true; + MCLK->APBAMASK.bit.WDT_ = true; + OSC32KCTRL->OSCULP32K.bit.EN1K = true; // Enable out 1K (this is what WDT uses) + + WDT->CTRLA.bit.ENABLE = false; // Disable watchdog for config + SYNC(WDT->SYNCBUSY.bit.ENABLE); + + WDT->INTENCLR.reg = WDT_INTENCLR_EW; // Disable early warning interrupt + WDT->CONFIG.reg = WDT_TIMEOUT_REG; // Set a 4s or 8s period for chip reset + + hal.watchdog_refresh(); + + WDT->CTRLA.reg = WDT_CTRLA_ENABLE; // Start watchdog now in normal mode + SYNC(WDT->SYNCBUSY.bit.ENABLE); + } + + // Reset watchdog. MUST be called at least every 4 seconds after the + // first watchdog_init or SAMD will go into emergency procedures. + void MarlinHAL::watchdog_refresh() { + SYNC(WDT->SYNCBUSY.bit.CLEAR); // Test first if previous is 'ongoing' to save time waiting for command execution + WDT->CLEAR.reg = WDT_CLEAR_CLEAR_KEY; + } + +#endif + // ------------------------ // Types // ------------------------ @@ -78,7 +283,7 @@ // Struct must be 32 bits aligned because of DMA accesses but fields needs to be 8 bits packed typedef struct __attribute__((aligned(4), packed)) { ADC_INPUTCTRL_Type INPUTCTRL; - } HAL_DMA_DAC_Registers; // DMA transfered registers + } HAL_DMA_DAC_Registers; // DMA transferred registers #endif @@ -86,12 +291,10 @@ // Private Variables // ------------------------ -uint16_t HAL_adc_result; - #if ADC_IS_REQUIRED // Pins used by ADC inputs. Order must be ADC0 inputs first then ADC1 - const uint8_t adc_pins[] = { + static constexpr uint8_t adc_pins[ADC_COUNT] = { // ADC0 pins #if GET_TEMP_0_ADC() == 0 TEMP_0_PIN, @@ -117,22 +320,51 @@ uint16_t HAL_adc_result; #if GET_TEMP_7_ADC() == 0 TEMP_7_PIN, #endif - #if GET_PROBE_ADC() == 0 - TEMP_PROBE_PIN, - #endif #if GET_BED_ADC() == 0 TEMP_BED_PIN, #endif #if GET_CHAMBER_ADC() == 0 TEMP_CHAMBER_PIN, #endif + #if GET_PROBE_ADC() == 0 + TEMP_PROBE_PIN, + #endif + #if GET_COOLER_ADC() == 0 + TEMP_COOLER_PIN, + #endif + #if GET_BOARD_ADC() == 0 + TEMP_BOARD_PIN, + #endif + #if GET_SOC_ADC() == 0 + TEMP_SOC_PIN, + #endif #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 - // ADC1 pins + #if GET_JOY_ADC_X() == 0 + JOY_X_PIN, + #endif + #if GET_JOY_ADC_Y() == 0 + JOY_Y_PIN, + #endif + #if GET_JOY_ADC_Z() == 0 + JOY_Z_PIN, + #endif + #if GET_POWERMON_ADC_CURRENT() == 0 + POWER_MONITOR_CURRENT_PIN, + #endif + #if GET_POWERMON_ADC_VOLTS() == 0 + POWER_MONITOR_VOLTAGE_PIN, + #endif + + // Pins for ADC1 after ADC0 + #if GET_TEMP_0_ADC() == 1 TEMP_0_PIN, #endif @@ -157,30 +389,56 @@ uint16_t HAL_adc_result; #if GET_TEMP_7_ADC() == 1 TEMP_7_PIN, #endif - #if GET_PROBE_ADC() == 1 - TEMP_PROBE_PIN, - #endif #if GET_BED_ADC() == 1 TEMP_BED_PIN, #endif #if GET_CHAMBER_ADC() == 1 TEMP_CHAMBER_PIN, #endif + #if GET_PROBE_ADC() == 1 + TEMP_PROBE_PIN, + #endif + #if GET_COOLER_ADC() == 1 + TEMP_COOLER_PIN, + #endif + #if GET_BOARD_ADC() == 1 + TEMP_BOARD_PIN, + #endif + #if GET_SOC_ADC() == 1 + TEMP_SOC_PIN, + #endif #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 + #if GET_JOY_ADC_X() == 1 + JOY_X_PIN, + #endif + #if GET_JOY_ADC_Y() == 1 + JOY_Y_PIN, + #endif + #if GET_JOY_ADC_Z() == 1 + JOY_Z_PIN, + #endif + #if GET_POWERMON_ADC_CURRENT() == 1 + POWER_MONITOR_CURRENT_PIN, + #endif + #if GET_POWERMON_ADC_VOLTS() == 1 + POWER_MONITOR_VOLTAGE_PIN, + #endif }; - uint16_t HAL_adc_results[COUNT(adc_pins)]; + static uint16_t adc_results[ADC_COUNT]; #if ADC0_IS_REQUIRED - Adafruit_ZeroDMA adc0DMAProgram, - adc0DMARead; + Adafruit_ZeroDMA adc0DMAProgram, adc0DMARead; - const HAL_DMA_DAC_Registers adc0_dma_regs_list[] = { + static constexpr HAL_DMA_DAC_Registers adc0_dma_regs_list[ADC_COUNT] = { #if GET_TEMP_0_ADC() == 0 { PIN_TO_INPUTCTRL(TEMP_0_PIN) }, #endif @@ -205,31 +463,57 @@ uint16_t HAL_adc_result; #if GET_TEMP_7_ADC() == 0 { PIN_TO_INPUTCTRL(TEMP_7_PIN) }, #endif - #if GET_PROBE_ADC() == 0 - { PIN_TO_INPUTCTRL(TEMP_PROBE_PIN) }, - #endif #if GET_BED_ADC() == 0 { PIN_TO_INPUTCTRL(TEMP_BED_PIN) }, #endif #if GET_CHAMBER_ADC() == 0 { PIN_TO_INPUTCTRL(TEMP_CHAMBER_PIN) }, #endif + #if GET_PROBE_ADC() == 0 + { PIN_TO_INPUTCTRL(TEMP_PROBE_PIN) }, + #endif + #if GET_COOLER_ADC() == 0 + { PIN_TO_INPUTCTRL(TEMP_COOLER_PIN) }, + #endif + #if GET_BOARD_ADC() == 0 + { PIN_TO_INPUTCTRL(TEMP_BOARD_PIN) }, + #endif + #if GET_SOC_ADC() == 0 + { PIN_TO_INPUTCTRL(TEMP_SOC_PIN) }, + #endif #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 + #if GET_JOY_ADC_X() == 0 + { PIN_TO_INPUTCTRL(JOY_X_PIN) }, + #endif + #if GET_JOY_ADC_Y() == 0 + { PIN_TO_INPUTCTRL(JOY_Y_PIN) }, + #endif + #if GET_JOY_ADC_Z() == 0 + { PIN_TO_INPUTCTRL(JOY_Z_PIN) }, + #endif + #if GET_POWERMON_ADC_CURRENT() == 0 + { PIN_TO_INPUTCTRL(POWER_MONITOR_CURRENT_PIN) }, + #endif + #if GET_POWERMON_ADC_VOLTS() == 0 + { PIN_TO_INPUTCTRL(POWER_MONITOR_VOLTAGE_PIN) }, + #endif }; #define ADC0_AINCOUNT COUNT(adc0_dma_regs_list) #endif // ADC0_IS_REQUIRED #if ADC1_IS_REQUIRED - Adafruit_ZeroDMA adc1DMAProgram, - adc1DMARead; + Adafruit_ZeroDMA adc1DMAProgram, adc1DMARead; - const HAL_DMA_DAC_Registers adc1_dma_regs_list[] = { + static constexpr HAL_DMA_DAC_Registers adc1_dma_regs_list[ADC_COUNT] = { #if GET_TEMP_0_ADC() == 1 { PIN_TO_INPUTCTRL(TEMP_0_PIN) }, #endif @@ -254,21 +538,48 @@ uint16_t HAL_adc_result; #if GET_TEMP_7_ADC() == 1 { PIN_TO_INPUTCTRL(TEMP_7_PIN) }, #endif - #if GET_PROBE_ADC() == 1 - { PIN_TO_INPUTCTRL(TEMP_PROBE_PIN) }, - #endif #if GET_BED_ADC() == 1 { PIN_TO_INPUTCTRL(TEMP_BED_PIN) }, #endif #if GET_CHAMBER_ADC() == 1 { PIN_TO_INPUTCTRL(TEMP_CHAMBER_PIN) }, #endif + #if GET_PROBE_ADC() == 1 + { PIN_TO_INPUTCTRL(TEMP_PROBE_PIN) }, + #endif + #if GET_COOLER_ADC() == 1 + { PIN_TO_INPUTCTRL(TEMP_COOLER_PIN) }, + #endif + #if GET_BOARD_ADC() == 1 + { PIN_TO_INPUTCTRL(TEMP_BOARD_PIN) }, + #endif + #if GET_SOC_ADC() == 1 + { PIN_TO_INPUTCTRL(TEMP_SOC_PIN) }, + #endif #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 + #if GET_JOY_ADC_X() == 1 + { PIN_TO_INPUTCTRL(JOY_X_PIN) }, + #endif + #if GET_JOY_ADC_Y() == 1 + { PIN_TO_INPUTCTRL(JOY_Y_PIN) }, + #endif + #if GET_JOY_ADC_Z() == 1 + { PIN_TO_INPUTCTRL(JOY_Z_PIN) }, + #endif + #if GET_POWERMON_ADC_CURRENT() == 1 + { PIN_TO_INPUTCTRL(POWER_MONITOR_CURRENT_PIN) }, + #endif + #if GET_POWERMON_ADC_VOLTS() == 1 + { PIN_TO_INPUTCTRL(POWER_MONITOR_VOLTAGE_PIN) }, + #endif }; #define ADC1_AINCOUNT COUNT(adc1_dma_regs_list) @@ -280,9 +591,10 @@ uint16_t HAL_adc_result; // Private functions // ------------------------ -#if DMA_IS_REQUIRED +void MarlinHAL::dma_init() { + + #if DMA_IS_REQUIRED - void dma_init() { DmacDescriptor *descriptor; #if ADC0_IS_REQUIRED @@ -300,7 +612,7 @@ uint16_t HAL_adc_result; DMA_ADDRESS_INCREMENT_STEP_SIZE_1, // STEPSIZE DMA_STEPSEL_SRC // STEPSEL ); - if (descriptor != nullptr) + if (descriptor) descriptor->BTCTRL.bit.EVOSEL = DMA_EVENT_OUTPUT_BEAT; adc0DMAProgram.startJob(); } @@ -311,7 +623,7 @@ uint16_t HAL_adc_result; if (adc0DMARead.allocate() == DMA_STATUS_OK) { adc0DMARead.addDescriptor( (void *)&ADC0->RESULT.reg, // SRC - &HAL_adc_results, // DEST + &adc_results, // DEST ADC0_AINCOUNT, // CNT DMA_BEAT_SIZE_HWORD, false, // SRCINC @@ -337,7 +649,7 @@ uint16_t HAL_adc_result; DMA_ADDRESS_INCREMENT_STEP_SIZE_1, // STEPSIZE DMA_STEPSEL_SRC // STEPSEL ); - if (descriptor != nullptr) + if (descriptor) descriptor->BTCTRL.bit.EVOSEL = DMA_EVENT_OUTPUT_BEAT; adc1DMAProgram.startJob(); } @@ -348,7 +660,7 @@ uint16_t HAL_adc_result; if (adc1DMARead.allocate() == DMA_STATUS_OK) { adc1DMARead.addDescriptor( (void *)&ADC1->RESULT.reg, // SRC - &HAL_adc_results[ADC0_AINCOUNT], // DEST + &adc_results[ADC0_AINCOUNT], // DEST ADC1_AINCOUNT, // CNT DMA_BEAT_SIZE_HWORD, false, // SRCINC @@ -361,36 +673,28 @@ uint16_t HAL_adc_result; #endif DMAC->PRICTRL0.bit.RRLVLEN0 = true; // Activate round robin for DMA channels required by ADCs - } -#endif // DMA_IS_REQUIRED + #endif // DMA_IS_REQUIRED +} // ------------------------ // Public functions // ------------------------ // HAL initialization task -void HAL_init() { +void MarlinHAL::init() { TERN_(DMA_IS_REQUIRED, dma_init()); - #if ENABLED(SDSUPPORT) - #if SD_CONNECTION_IS(ONBOARD) && PIN_EXISTS(SD_DETECT) + #if HAS_MEDIA + #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 } -// HAL idle task -/* -void HAL_idletask() { -} -*/ - -void HAL_clear_reset_source() { } - #pragma push_macro("WDT") #undef WDT // Required to be able to use '.bit.WDT'. Compiler wrongly replace struct field with WDT define -uint8_t HAL_get_reset_source() { +uint8_t MarlinHAL::get_reset_source() { RSTC_RCAUSE_Type resetCause; resetCause.reg = REG_RSTC_RCAUSE; @@ -404,6 +708,8 @@ uint8_t HAL_get_reset_source() { } #pragma pop_macro("WDT") +void MarlinHAL::reboot() { NVIC_SystemReset(); } + extern "C" { void * _sbrk(int incr); @@ -420,14 +726,16 @@ int freeMemory() { // ADC // ------------------------ -void HAL_adc_init() { - #if ADC_IS_REQUIRED - memset(HAL_adc_results, 0xFF, sizeof(HAL_adc_results)); // Fill result with invalid values +uint16_t MarlinHAL::adc_result; - LOOP_L_N(pi, COUNT(adc_pins)) +void MarlinHAL::adc_init() { + #if ADC_IS_REQUIRED + memset(adc_results, 0xFF, sizeof(adc_results)); // Fill result with invalid values + + for (uint8_t pi = 0; pi < COUNT(adc_pins); ++pi) pinPeripheral(adc_pins[pi], PIO_ANALOG); - LOOP_S_LE_N(ai, FIRST_ADC, LAST_ADC) { + for (uint8_t ai = FIRST_ADC; ai <= LAST_ADC; ++ai) { Adc* adc = ((Adc*[])ADC_INSTS)[ai]; // ADC clock setup @@ -457,17 +765,13 @@ void HAL_adc_init() { #endif // ADC_IS_REQUIRED } -void HAL_adc_start_conversion(const uint8_t adc_pin) { +void MarlinHAL::adc_start(const pin_t pin) { #if ADC_IS_REQUIRED - LOOP_L_N(pi, COUNT(adc_pins)) { - if (adc_pin == adc_pins[pi]) { - HAL_adc_result = HAL_adc_results[pi]; - return; - } - } + for (uint8_t pi = 0; pi < COUNT(adc_pins); ++pi) + if (pin == adc_pins[pi]) { adc_result = adc_results[pi]; return; } #endif - HAL_adc_result = 0xFFFF; + adc_result = 0xFFFF; } #endif // __SAMD51__ diff --git a/Marlin/src/HAL/SAMD51/HAL.h b/Marlin/src/HAL/SAMD51/HAL.h index 7cb3635bd7..65dcce966d 100644 --- a/Marlin/src/HAL/SAMD51/HAL.h +++ b/Marlin/src/HAL/SAMD51/HAL.h @@ -1,8 +1,9 @@ /** * Marlin 3D Printer Firmware - * * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * SAMD51 HAL developed by Giuliano Zaro (AKA GMagician) + * + * 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 @@ -20,97 +21,47 @@ */ #pragma once +/** + * SAMD51 HAL developed by Giuliano Zaro (AKA GMagician) + */ + #define CPU_32_BIT #include "../shared/Marduino.h" #include "../shared/math_32bit.h" #include "../shared/HAL_SPI.h" #include "fastio.h" -#include "watchdog.h" #ifdef ADAFRUIT_GRAND_CENTRAL_M4 #include "MarlinSerial_AGCM4.h" - - // Serial ports - - // MYSERIAL0 required before MarlinSerial includes! - - #define __MSERIAL(X) Serial##X - #define _MSERIAL(X) __MSERIAL(X) - #define MSERIAL(X) _MSERIAL(INCREMENT(X)) - - #if SERIAL_PORT == -1 - #define MYSERIAL0 Serial - #elif WITHIN(SERIAL_PORT, 0, 3) - #define MYSERIAL0 MSERIAL(SERIAL_PORT) - #else - #error "SERIAL_PORT must be from -1 to 3. Please update your configuration." - #endif - - #ifdef SERIAL_PORT_2 - #if SERIAL_PORT_2 == -1 - #define MYSERIAL1 Serial - #elif WITHIN(SERIAL_PORT_2, 0, 3) - #define MYSERIAL1 MSERIAL(SERIAL_PORT_2) - #else - #error "SERIAL_PORT_2 must be from -1 to 3. Please update your configuration." - #endif - #endif - - #ifdef LCD_SERIAL_PORT - #if LCD_SERIAL_PORT == -1 - #define LCD_SERIAL Serial - #elif WITHIN(LCD_SERIAL_PORT, 0, 3) - #define LCD_SERIAL MSERIAL(LCD_SERIAL_PORT) - #else - #error "LCD_SERIAL_PORT must be from -1 to 3. Please update your configuration." - #endif - #endif - -#endif // ADAFRUIT_GRAND_CENTRAL_M4 +#endif typedef int8_t pin_t; -#define SHARED_SERVOS HAS_SERVOS -#define HAL_SERVO_LIB Servo +#define SHARED_SERVOS HAS_SERVOS // Use shared/servos.cpp + +class Servo; +typedef Servo hal_servo_t; // // Interrupts // -#define CRITICAL_SECTION_START() uint32_t primask = __get_PRIMASK(); __disable_irq() -#define CRITICAL_SECTION_END() if (!primask) __enable_irq() -#define ISRS_ENABLED() (!__get_PRIMASK()) -#define ENABLE_ISRS() __enable_irq() -#define DISABLE_ISRS() __disable_irq() +#define CRITICAL_SECTION_START() const bool irqon = !__get_PRIMASK(); __disable_irq() +#define CRITICAL_SECTION_END() if (irqon) __enable_irq() -#define cli() __disable_irq() // Disable interrupts -#define sei() __enable_irq() // Enable interrupts - -void HAL_clear_reset_source(); // clear reset reason -uint8_t HAL_get_reset_source(); // get reset reason - -inline void HAL_reboot() {} // reboot the board or restart the bootloader +#define cli() __disable_irq() // Disable interrupts +#define sei() __enable_irq() // Enable interrupts // // ADC // -extern uint16_t HAL_adc_result; // Most recent ADC conversion - -#define HAL_ANALOG_SELECT(pin) - -void HAL_adc_init(); //#define HAL_ADC_FILTERED // Disable Marlin's oversampling. The HAL filters ADC values. -#define HAL_ADC_VREF 3.3 +#define HAL_ADC_VREF_MV 3300 #define HAL_ADC_RESOLUTION 10 // ... 12 -#define HAL_START_ADC(pin) HAL_adc_start_conversion(pin) -#define HAL_READ_ADC() HAL_adc_result -#define HAL_ADC_READY() true - -void HAL_adc_start_conversion(const uint8_t adc_pin); // -// Pin Map +// Pin Mapping for M42, M43, M226 // #define GET_PIN_MAP_PIN(index) index #define GET_PIN_MAP_INDEX(pin) pin @@ -119,31 +70,97 @@ void HAL_adc_start_conversion(const uint8_t adc_pin); // // Tone // -void toneInit(); void tone(const pin_t _pin, const unsigned int frequency, const unsigned long duration=0); void noTone(const pin_t _pin); -// Enable hooks into idle and setup for HAL -void HAL_init(); -/* -#define HAL_IDLETASK 1 -void HAL_idletask(); -*/ - -// -// Utility functions -// -FORCE_INLINE void _delay_ms(const int delay_ms) { delay(delay_ms); } +// ------------------------ +// Class Utilities +// ------------------------ #pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-function" -int freeMemory(); -#pragma GCC diagnostic pop +#if GCC_VERSION <= 50000 + #pragma GCC diagnostic ignored "-Wunused-function" +#endif #ifdef __cplusplus extern "C" { #endif + char *dtostrf(double __val, signed char __width, unsigned char __prec, char *__s); + +extern "C" int freeMemory(); + #ifdef __cplusplus } #endif + +#pragma GCC diagnostic pop + +// ------------------------ +// MarlinHAL Class +// ------------------------ + +class MarlinHAL { +public: + + // Earliest possible init, before setup() + MarlinHAL() {} + + // 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(); // 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 marlin.idle() + static void idletask() {} + + // Reset + static uint8_t get_reset_source(); + static void clear_reset_source() {} + + // Free SRAM + static int freeMemory() { return ::freeMemory(); } + + // + // ADC Methods + // + + static uint16_t adc_result; + + // 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 uint8_t ch) {} + + // 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 bool adc_ready() { return true; } + + // The 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. + * 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); + } + +private: + static void dma_init(); +}; diff --git a/Marlin/src/HAL/SAMD51/HAL_SPI.cpp b/Marlin/src/HAL/SAMD51/HAL_SPI.cpp index c3acd38237..63d3971965 100644 --- a/Marlin/src/HAL/SAMD51/HAL_SPI.cpp +++ b/Marlin/src/HAL/SAMD51/HAL_SPI.cpp @@ -1,8 +1,9 @@ /** * Marlin 3D Printer Firmware - * * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * SAMD51 HAL developed by Giuliano Zaro (AKA GMagician) + * + * 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 @@ -19,6 +20,10 @@ * */ +/** + * SAMD51 HAL developed by Giuliano Zaro (AKA GMagician) + */ + /** * Hardware and software SPI implementations are included in this file. * @@ -39,7 +44,7 @@ // Public functions // -------------------------------------------------------------------------- -#if EITHER(SOFTWARE_SPI, FORCE_SOFT_SPI) +#if ANY(SOFTWARE_SPI, FORCE_SOFT_SPI) // ------------------------ // Software SPI @@ -103,7 +108,7 @@ * @param nbyte Number of bytes to receive. * @return Nothing */ - void spiRead(uint8_t* buf, uint16_t nbyte) { + void spiRead(uint8_t *buf, uint16_t nbyte) { if (nbyte == 0) return; memset(buf, 0xFF, nbyte); sdSPI.beginTransaction(spiConfig); @@ -132,7 +137,7 @@ * * @details Uses DMA */ - void spiSendBlock(uint8_t token, const uint8_t* buf) { + void spiSendBlock(uint8_t token, const uint8_t *buf) { sdSPI.beginTransaction(spiConfig); sdSPI.transfer(token); sdSPI.transfer((uint8_t*)buf, nullptr, 512); diff --git a/Marlin/src/HAL/SAMD51/MarlinSPI.h b/Marlin/src/HAL/SAMD51/MarlinSPI.h new file mode 100644 index 0000000000..0c447ba4cb --- /dev/null +++ b/Marlin/src/HAL/SAMD51/MarlinSPI.h @@ -0,0 +1,26 @@ +/** + * 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 + +#include + +using MarlinSPI = SPIClass; diff --git a/Marlin/src/HAL/SAMD51/MarlinSerial_AGCM4.cpp b/Marlin/src/HAL/SAMD51/MarlinSerial_AGCM4.cpp index abc5f3acbf..baa7a38503 100644 --- a/Marlin/src/HAL/SAMD51/MarlinSerial_AGCM4.cpp +++ b/Marlin/src/HAL/SAMD51/MarlinSerial_AGCM4.cpp @@ -1,8 +1,9 @@ /** * Marlin 3D Printer Firmware - * * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * SAMD51 HAL developed by Giuliano Zaro (AKA GMagician) + * + * 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 @@ -18,33 +19,37 @@ * along with this program. If not, see . * */ + +/** + * SAMD51 HAL developed by Giuliano Zaro (AKA GMagician) + */ #ifdef ADAFRUIT_GRAND_CENTRAL_M4 /** - * Framework doesn't define some serial to save sercom resources + * Framework doesn't define some serials to save sercom resources * hence if these are used I need to define them */ #include "../../inc/MarlinConfig.h" -#if SERIAL_PORT == 1 || SERIAL_PORT_2 == 1 - Uart Serial2(&sercom4, PIN_SERIAL2_RX, PIN_SERIAL2_TX, PAD_SERIAL2_RX, PAD_SERIAL2_TX); +#if USING_HW_SERIAL1 + UartT Serial2(false, &sercom4, PIN_SERIAL2_RX, PIN_SERIAL2_TX, PAD_SERIAL2_RX, PAD_SERIAL2_TX); void SERCOM4_0_Handler() { Serial2.IrqHandler(); } void SERCOM4_1_Handler() { Serial2.IrqHandler(); } void SERCOM4_2_Handler() { Serial2.IrqHandler(); } void SERCOM4_3_Handler() { Serial2.IrqHandler(); } #endif -#if SERIAL_PORT == 2 || SERIAL_PORT_2 == 2 - Uart Serial3(&sercom1, PIN_SERIAL3_RX, PIN_SERIAL3_TX, PAD_SERIAL3_RX, PAD_SERIAL3_TX); +#if USING_HW_SERIAL2 + UartT Serial3(false, &sercom1, PIN_SERIAL3_RX, PIN_SERIAL3_TX, PAD_SERIAL3_RX, PAD_SERIAL3_TX); void SERCOM1_0_Handler() { Serial3.IrqHandler(); } void SERCOM1_1_Handler() { Serial3.IrqHandler(); } void SERCOM1_2_Handler() { Serial3.IrqHandler(); } void SERCOM1_3_Handler() { Serial3.IrqHandler(); } #endif -#if SERIAL_PORT == 3 || SERIAL_PORT_2 == 3 - Uart Serial4(&sercom5, PIN_SERIAL4_RX, PIN_SERIAL4_TX, PAD_SERIAL4_RX, PAD_SERIAL4_TX); +#if USING_HW_SERIAL3 + UartT Serial4(false, &sercom5, PIN_SERIAL4_RX, PIN_SERIAL4_TX, PAD_SERIAL4_RX, PAD_SERIAL4_TX); void SERCOM5_0_Handler() { Serial4.IrqHandler(); } void SERCOM5_1_Handler() { Serial4.IrqHandler(); } void SERCOM5_2_Handler() { Serial4.IrqHandler(); } diff --git a/Marlin/src/HAL/SAMD51/MarlinSerial_AGCM4.h b/Marlin/src/HAL/SAMD51/MarlinSerial_AGCM4.h index f3821d8d5a..7827c1f958 100644 --- a/Marlin/src/HAL/SAMD51/MarlinSerial_AGCM4.h +++ b/Marlin/src/HAL/SAMD51/MarlinSerial_AGCM4.h @@ -1,8 +1,9 @@ /** * Marlin 3D Printer Firmware - * * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * SAMD51 HAL developed by Giuliano Zaro (AKA GMagician) + * + * 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 @@ -20,6 +21,35 @@ */ #pragma once -extern Uart Serial2; -extern Uart Serial3; -extern Uart Serial4; +/** + * SAMD51 HAL developed by Giuliano Zaro (AKA GMagician) + */ + +#include "../../core/serial_hook.h" + +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/SAMD51.h b/Marlin/src/HAL/SAMD51/SAMD51.h index 783956140d..8cc19d7155 100644 --- a/Marlin/src/HAL/SAMD51/SAMD51.h +++ b/Marlin/src/HAL/SAMD51/SAMD51.h @@ -1,8 +1,9 @@ /** * Marlin 3D Printer Firmware - * * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * SAMD51 HAL developed by Giuliano Zaro (AKA GMagician) + * + * 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 @@ -20,6 +21,10 @@ */ #pragma once +/** + * SAMD51 HAL developed by Giuliano Zaro (AKA GMagician) + */ + #define SYNC(sc) while (sc) { \ asm(""); \ } diff --git a/Marlin/src/HAL/SAMD51/Servo.cpp b/Marlin/src/HAL/SAMD51/Servo.cpp index 9bab8e89be..059955e11a 100644 --- a/Marlin/src/HAL/SAMD51/Servo.cpp +++ b/Marlin/src/HAL/SAMD51/Servo.cpp @@ -1,8 +1,9 @@ /** * Marlin 3D Printer Firmware - * * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * SAMD51 HAL developed by Giuliano Zaro (AKA GMagician) + * + * 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 @@ -19,6 +20,10 @@ * */ +/** + * SAMD51 HAL developed by Giuliano Zaro (AKA GMagician) + */ + /** * This comes from Arduino library which at the moment is buggy and uncompilable */ @@ -49,11 +54,10 @@ #define TIMER_TCCHANNEL(t) ((t) & 1) #define TC_COUNTER_START_VAL 0xFFFF - static volatile int8_t currentServoIndex[_Nbr_16timers]; // index for the servo being pulsed for each timer (or -1 if refresh interval) FORCE_INLINE static uint16_t getTimerCount() { - Tc * const tc = TimerConfig[SERVO_TC].pTc; + Tc * const tc = timer_config[SERVO_TC].pTc; tc->COUNT16.CTRLBSET.reg = TC_CTRLBCLR_CMD_READSYNC; SYNC(tc->COUNT16.SYNCBUSY.bit.CTRLB || tc->COUNT16.SYNCBUSY.bit.COUNT); @@ -65,7 +69,7 @@ FORCE_INLINE static uint16_t getTimerCount() { // Interrupt handler for the TC // ---------------------------- HAL_SERVO_TIMER_ISR() { - Tc * const tc = TimerConfig[SERVO_TC].pTc; + Tc * const tc = timer_config[SERVO_TC].pTc; const timer16_Sequence_t timer = #ifndef _useTimer1 _timer2 @@ -77,7 +81,8 @@ HAL_SERVO_TIMER_ISR() { ; const uint8_t tcChannel = TIMER_TCCHANNEL(timer); - if (currentServoIndex[timer] < 0) { + int8_t cho = currentServoIndex[timer]; // Handle the prior servo first + if (cho < 0) { // Servo -1 indicates the refresh interval completed... #if defined(_useTimer1) && defined(_useTimer2) if (currentServoIndex[timer ^ 1] >= 0) { // Wait for both channels @@ -86,46 +91,38 @@ HAL_SERVO_TIMER_ISR() { return; } #endif - tc->COUNT16.COUNT.reg = TC_COUNTER_START_VAL; + tc->COUNT16.COUNT.reg = TC_COUNTER_START_VAL; // ...so reset the timer SYNC(tc->COUNT16.SYNCBUSY.bit.COUNT); } - else if (SERVO_INDEX(timer, currentServoIndex[timer]) < ServoCount && SERVO(timer, currentServoIndex[timer]).Pin.isActive) - digitalWrite(SERVO(timer, currentServoIndex[timer]).Pin.nbr, LOW); // pulse this channel low if activated + else if (SERVO_INDEX(timer, cho) < ServoCount) // prior channel handled? + digitalWrite(SERVO(timer, cho).Pin.nbr, LOW); // pulse the prior channel LOW - // Select the next servo controlled by this timer - currentServoIndex[timer]++; + currentServoIndex[timer] = ++cho; // go to the next channel (or 0) + if (cho < SERVOS_PER_TIMER && SERVO_INDEX(timer, cho) < ServoCount) { + if (SERVO(timer, cho).Pin.isActive) // activated? + digitalWrite(SERVO(timer, cho).Pin.nbr, HIGH); // yes: pulse HIGH - if (SERVO_INDEX(timer, currentServoIndex[timer]) < ServoCount && currentServoIndex[timer] < SERVOS_PER_TIMER) { - if (SERVO(timer, currentServoIndex[timer]).Pin.isActive) // check if activated - digitalWrite(SERVO(timer, currentServoIndex[timer]).Pin.nbr, HIGH); // it's an active channel so pulse it high - - tc->COUNT16.CC[tcChannel].reg = getTimerCount() - (uint16_t)SERVO(timer, currentServoIndex[timer]).ticks; + tc->COUNT16.CC[tcChannel].reg = getTimerCount() - (uint16_t)SERVO(timer, cho).ticks; } else { // finished all channels so wait for the refresh period to expire before starting over - currentServoIndex[timer] = -1; // this will get incremented at the end of the refresh period to start again at the first channel - - const uint16_t tcCounterValue = getTimerCount(); - - if ((TC_COUNTER_START_VAL - tcCounterValue) + 4UL < usToTicks(REFRESH_INTERVAL)) // allow a few ticks to ensure the next OCR1A not missed - tc->COUNT16.CC[tcChannel].reg = TC_COUNTER_START_VAL - (uint16_t)usToTicks(REFRESH_INTERVAL); - else - tc->COUNT16.CC[tcChannel].reg = (uint16_t)(tcCounterValue - 4UL); // at least REFRESH_INTERVAL has elapsed + currentServoIndex[timer] = -1; // reset the timer COUNT.reg on the next call + const uint16_t cval = getTimerCount() - 256 / (SERVO_TIMER_PRESCALER), // allow 256 cycles to ensure the next CV not missed + ival = (TC_COUNTER_START_VAL) - (uint16_t)usToTicks(REFRESH_INTERVAL); // at least REFRESH_INTERVAL has elapsed + tc->COUNT16.CC[tcChannel].reg = min(cval, ival); } if (tcChannel == 0) { SYNC(tc->COUNT16.SYNCBUSY.bit.CC0); - // Clear the interrupt - tc->COUNT16.INTFLAG.reg = TC_INTFLAG_MC0; + tc->COUNT16.INTFLAG.reg = TC_INTFLAG_MC0; // Clear the interrupt } else { SYNC(tc->COUNT16.SYNCBUSY.bit.CC1); - // Clear the interrupt - tc->COUNT16.INTFLAG.reg = TC_INTFLAG_MC1; + tc->COUNT16.INTFLAG.reg = TC_INTFLAG_MC1; // Clear the interrupt } } -void initISR(timer16_Sequence_t timer) { - Tc * const tc = TimerConfig[SERVO_TC].pTc; +void initISR(const timer16_Sequence_t timer) { + Tc * const tc = timer_config[SERVO_TC].pTc; const uint8_t tcChannel = TIMER_TCCHANNEL(timer); static bool initialized = false; // Servo TC has been initialized @@ -201,9 +198,9 @@ void initISR(timer16_Sequence_t timer) { } } -void finISR(timer16_Sequence_t timer) { - Tc * const tc = TimerConfig[SERVO_TC].pTc; - const uint8_t tcChannel = TIMER_TCCHANNEL(timer); +void finISR(const timer16_Sequence_t timer_index) { + Tc * const tc = timer_config[SERVO_TC].pTc; + const uint8_t tcChannel = TIMER_TCCHANNEL(timer_index); // Disable the match channel interrupt request tc->COUNT16.INTENCLR.reg = (tcChannel == 0) ? TC_INTENCLR_MC0 : TC_INTENCLR_MC1; diff --git a/Marlin/src/HAL/SAMD51/ServoTimers.h b/Marlin/src/HAL/SAMD51/ServoTimers.h index 948d515356..47e0a190aa 100644 --- a/Marlin/src/HAL/SAMD51/ServoTimers.h +++ b/Marlin/src/HAL/SAMD51/ServoTimers.h @@ -1,8 +1,9 @@ /** * Marlin 3D Printer Firmware - * * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * SAMD51 HAL developed by Giuliano Zaro (AKA GMagician) + * + * 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 @@ -20,6 +21,10 @@ */ #pragma once +/** + * SAMD51 HAL developed by Giuliano Zaro (AKA GMagician) + */ + #define _useTimer1 #define _useTimer2 diff --git a/Marlin/src/HAL/SAMD51/QSPIFlash.cpp b/Marlin/src/HAL/SAMD51/eeprom/QSPIFlash.cpp similarity index 95% rename from Marlin/src/HAL/SAMD51/QSPIFlash.cpp rename to Marlin/src/HAL/SAMD51/eeprom/QSPIFlash.cpp index 307eb3fd45..191da1f30c 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) @@ -35,10 +35,10 @@ uint8_t QSPIFlash::_buf[SFLASH_SECTOR_SIZE]; uint32_t QSPIFlash::_addr = INVALID_ADDR; void QSPIFlash::begin() { - if (_flashBase != nullptr) return; + if (_flashBase) return; _flashBase = new Adafruit_SPIFlashBase(new Adafruit_FlashTransport_QSPI()); - _flashBase->begin(NULL); + _flashBase->begin(nullptr); } size_t QSPIFlash::size() { diff --git a/Marlin/src/HAL/SAMD51/eeprom/QSPIFlash.h b/Marlin/src/HAL/SAMD51/eeprom/QSPIFlash.h new file mode 100644 index 0000000000..58822fe05f --- /dev/null +++ b/Marlin/src/HAL/SAMD51/eeprom/QSPIFlash.h @@ -0,0 +1,49 @@ +/** + * @file QSPIFlash.h + * + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach and Dean Miller for Adafruit Industries LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * Derived from Adafruit_SPIFlash class with no SdFat references + */ +#pragma once + +#include + +// This class extends Adafruit_SPIFlashBase by adding caching support. +// +// This class will use 4096 Bytes of RAM as a block cache. +class QSPIFlash { + public: + static void begin(); + static size_t size(); + static uint8_t readByte(const uint32_t address); + static void writeByte(const uint32_t address, const uint8_t v); + static void flush(); + + private: + static Adafruit_SPIFlashBase * _flashBase; + static uint8_t _buf[SFLASH_SECTOR_SIZE]; + static uint32_t _addr; +}; + +extern QSPIFlash qspi; diff --git a/Marlin/src/HAL/SAMD51/eeprom_flash.cpp b/Marlin/src/HAL/SAMD51/eeprom/eeprom_flash.cpp similarity index 93% rename from Marlin/src/HAL/SAMD51/eeprom_flash.cpp rename to Marlin/src/HAL/SAMD51/eeprom/eeprom_flash.cpp index 429ef1c2d4..2387a0f99e 100644 --- a/Marlin/src/HAL/SAMD51/eeprom_flash.cpp +++ b/Marlin/src/HAL/SAMD51/eeprom/eeprom_flash.cpp @@ -1,8 +1,9 @@ /** * Marlin 3D Printer Firmware - * * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * SAMD51 HAL developed by Giuliano Zaro (AKA GMagician) + * + * 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 @@ -18,13 +19,17 @@ * along with this program. If not, see . * */ + +/** + * SAMD51 HAL developed by Giuliano Zaro (AKA GMagician) + */ #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); \ @@ -79,7 +84,7 @@ bool PersistentStore::write_data(int &pos, const uint8_t *value, size_t size, ui return false; } -bool PersistentStore::read_data(int &pos, uint8_t* value, size_t size, uint16_t *crc, const bool writing/*=true*/) { +bool PersistentStore::read_data(int &pos, uint8_t *value, size_t size, uint16_t *crc, const bool writing/*=true*/) { while (size--) { SYNC(NVMCTRL->SEESTAT.bit.BUSY); uint8_t c = ((volatile uint8_t *)SEEPROM_ADDR)[pos]; diff --git a/Marlin/src/HAL/SAMD51/eeprom_qspi.cpp b/Marlin/src/HAL/SAMD51/eeprom/eeprom_qspi.cpp similarity index 78% rename from Marlin/src/HAL/SAMD51/eeprom_qspi.cpp rename to Marlin/src/HAL/SAMD51/eeprom/eeprom_qspi.cpp index b403f7939f..e829c28e26 100644 --- a/Marlin/src/HAL/SAMD51/eeprom_qspi.cpp +++ b/Marlin/src/HAL/SAMD51/eeprom/eeprom_qspi.cpp @@ -1,8 +1,9 @@ /** * Marlin 3D Printer Firmware - * * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * SAMD51 HAL developed by Giuliano Zaro (AKA GMagician) + * + * 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 @@ -18,19 +19,23 @@ * along with this program. If not, see . * */ + +/** + * SAMD51 HAL developed by Giuliano Zaro (AKA GMagician) + */ #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" static bool initialized; -size_t PersistentStore::capacity() { return qspi.size(); } +size_t PersistentStore::capacity() { return qspi.size() - eeprom_exclude_size; } bool PersistentStore::access_start() { if (!initialized) { @@ -48,7 +53,7 @@ bool PersistentStore::access_finish() { bool PersistentStore::write_data(int &pos, const uint8_t *value, size_t size, uint16_t *crc) { while (size--) { const uint8_t v = *value; - qspi.writeByte(pos, v); + qspi.writeByte(REAL_EEPROM_ADDR(pos), v); crc16(crc, &v, 1); pos++; value++; @@ -56,9 +61,9 @@ bool PersistentStore::write_data(int &pos, const uint8_t *value, size_t size, ui return false; } -bool PersistentStore::read_data(int &pos, uint8_t* value, size_t size, uint16_t *crc, const bool writing/*=true*/) { +bool PersistentStore::read_data(int &pos, uint8_t *value, size_t size, uint16_t *crc, const bool writing/*=true*/) { while (size--) { - uint8_t c = qspi.readByte(pos); + const uint8_t c = qspi.readByte(REAL_EEPROM_ADDR(pos)); if (writing) *value = c; crc16(crc, &c, 1); pos++; diff --git a/Marlin/src/HAL/SAMD51/eeprom_wired.cpp b/Marlin/src/HAL/SAMD51/eeprom/eeprom_wired.cpp similarity index 72% rename from Marlin/src/HAL/SAMD51/eeprom_wired.cpp rename to Marlin/src/HAL/SAMD51/eeprom/eeprom_wired.cpp index 3283195897..fc1eb09a0c 100644 --- a/Marlin/src/HAL/SAMD51/eeprom_wired.cpp +++ b/Marlin/src/HAL/SAMD51/eeprom/eeprom_wired.cpp @@ -1,8 +1,9 @@ /** * Marlin 3D Printer Firmware - * * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * SAMD51 HAL developed by Giuliano Zaro (AKA GMagician) + * + * 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 @@ -18,9 +19,13 @@ * along with this program. If not, see . * */ + +/** + * SAMD51 HAL developed by Giuliano Zaro (AKA GMagician) + */ #ifdef __SAMD51__ -#include "../../inc/MarlinConfig.h" +#include "../../../inc/MarlinConfig.h" #if USE_WIRED_EEPROM @@ -29,24 +34,25 @@ * 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." #endif -size_t PersistentStore::capacity() { return MARLIN_EEPROM_SIZE; } +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--) { const uint8_t v = *value; - uint8_t * const p = (uint8_t * const)pos; - if (v != eeprom_read_byte(p)) { + uint8_t * const p = (uint8_t * const)REAL_EEPROM_ADDR(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); - delay(2); + 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; @@ -59,9 +65,9 @@ bool PersistentStore::write_data(int &pos, const uint8_t *value, size_t size, ui return false; } -bool PersistentStore::read_data(int &pos, uint8_t* value, size_t size, uint16_t *crc, const bool writing/*=true*/) { +bool PersistentStore::read_data(int &pos, uint8_t *value, size_t size, uint16_t *crc, const bool writing/*=true*/) { while (size--) { - uint8_t c = eeprom_read_byte((uint8_t*)pos); + const uint8_t c = eeprom_read_byte((uint8_t*)REAL_EEPROM_ADDR(pos)); if (writing) *value = c; crc16(crc, &c, 1); pos++; diff --git a/Marlin/src/HAL/SAMD51/endstop_interrupts.h b/Marlin/src/HAL/SAMD51/endstop_interrupts.h index daac773387..4e342f754f 100644 --- a/Marlin/src/HAL/SAMD51/endstop_interrupts.h +++ b/Marlin/src/HAL/SAMD51/endstop_interrupts.h @@ -1,8 +1,9 @@ /** * Marlin 3D Printer Firmware - * * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * SAMD51 HAL developed by Giuliano Zaro (AKA GMagician) + * + * 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 @@ -20,13 +21,17 @@ */ #pragma once +/** + * SAMD51 HAL developed by Giuliano Zaro (AKA GMagician) + */ + /** * Endstop interrupts for ATMEL SAMD51 based targets. * * 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 */ @@ -47,162 +52,215 @@ #include "../../module/endstops.h" -#define MATCH_EILINE(P1,P2) (P1 != P2 && PIN_TO_EILINE(P1) == PIN_TO_EILINE(P2)) -#if HAS_X_MAX - #define MATCH_X_MAX_EILINE(P) MATCH_EILINE(P, X_MAX_PIN) -#else - #define MATCH_X_MAX_EILINE(P) false -#endif -#if HAS_X_MIN - #define MATCH_X_MIN_EILINE(P) MATCH_EILINE(P, X_MIN_PIN) -#else - #define MATCH_X_MIN_EILINE(P) false -#endif -#if HAS_Y_MAX - #define MATCH_Y_MAX_EILINE(P) MATCH_EILINE(P, Y_MAX_PIN) -#else - #define MATCH_Y_MAX_EILINE(P) false -#endif -#if HAS_Y_MIN - #define MATCH_Y_MIN_EILINE(P) MATCH_EILINE(P, Y_MIN_PIN) -#else - #define MATCH_Y_MIN_EILINE(P) false -#endif -#if HAS_Z_MAX - #define MATCH_Z_MAX_EILINE(P) MATCH_EILINE(P, Z_MAX_PIN) -#else - #define MATCH_Z_MAX_EILINE(P) false -#endif -#if HAS_Z_MIN - #define MATCH_Z_MIN_EILINE(P) MATCH_EILINE(P, Z_MIN_PIN) -#else - #define MATCH_Z_MIN_EILINE(P) false -#endif -#if HAS_Z2_MAX - #define MATCH_Z2_MAX_EILINE(P) MATCH_EILINE(P, Z2_MAX_PIN) -#else - #define MATCH_Z2_MAX_EILINE(P) false -#endif -#if HAS_Z2_MIN - #define MATCH_Z2_MIN_EILINE(P) MATCH_EILINE(P, Z2_MIN_PIN) -#else - #define MATCH_Z2_MIN_EILINE(P) false -#endif -#if HAS_Z3_MAX - #define MATCH_Z3_MAX_EILINE(P) MATCH_EILINE(P, Z3_MAX_PIN) -#else - #define MATCH_Z3_MAX_EILINE(P) false -#endif -#if HAS_Z3_MIN - #define MATCH_Z3_MIN_EILINE(P) MATCH_EILINE(P, Z3_MIN_PIN) -#else - #define MATCH_Z3_MIN_EILINE(P) false -#endif -#if HAS_Z4_MAX - #define MATCH_Z4_MAX_EILINE(P) MATCH_EILINE(P, Z4_MAX_PIN) -#else - #define MATCH_Z4_MAX_EILINE(P) false -#endif -#if HAS_Z4_MIN - #define MATCH_Z4_MIN_EILINE(P) MATCH_EILINE(P, Z4_MIN_PIN) -#else - #define MATCH_Z4_MIN_EILINE(P) false -#endif -#if HAS_Z_MIN_PROBE_PIN - #define MATCH_Z_MIN_PROBE_EILINE(P) MATCH_EILINE(P, Z_MIN_PROBE_PIN) -#else - #define MATCH_Z_MIN_PROBE_EILINE(P) false -#endif -#define AVAILABLE_EILINE(P) (PIN_TO_EILINE(P) != -1 \ - && !MATCH_X_MAX_EILINE(P) && !MATCH_X_MIN_EILINE(P) \ - && !MATCH_Y_MAX_EILINE(P) && !MATCH_Y_MIN_EILINE(P) \ - && !MATCH_Z_MAX_EILINE(P) && !MATCH_Z_MIN_EILINE(P) \ - && !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)) +#define MATCH_EILINE(P1,P2) (P1 != P2 && PIN_TO_EILINE(P1) == PIN_TO_EILINE(P2)) +#define MATCH_X_MAX_EILINE(P) TERN0(USE_X_MAX, DEFER4(MATCH_EILINE)(P, X_MAX_PIN)) +#define MATCH_X_MIN_EILINE(P) TERN0(USE_X_MIN, DEFER4(MATCH_EILINE)(P, X_MIN_PIN)) +#define MATCH_Y_MAX_EILINE(P) TERN0(USE_Y_MAX, DEFER4(MATCH_EILINE)(P, Y_MAX_PIN)) +#define MATCH_Y_MIN_EILINE(P) TERN0(USE_Y_MIN, DEFER4(MATCH_EILINE)(P, Y_MIN_PIN)) +#define MATCH_Z_MAX_EILINE(P) TERN0(USE_Z_MAX, DEFER4(MATCH_EILINE)(P, Z_MAX_PIN)) +#define MATCH_Z_MIN_EILINE(P) TERN0(USE_Z_MIN, DEFER4(MATCH_EILINE)(P, Z_MIN_PIN)) +#define MATCH_I_MAX_EILINE(P) TERN0(USE_I_MAX, DEFER4(MATCH_EILINE)(P, I_MAX_PIN)) +#define MATCH_I_MIN_EILINE(P) TERN0(USE_I_MIN, DEFER4(MATCH_EILINE)(P, I_MIN_PIN)) +#define MATCH_J_MAX_EILINE(P) TERN0(USE_J_MAX, DEFER4(MATCH_EILINE)(P, J_MAX_PIN)) +#define MATCH_J_MIN_EILINE(P) TERN0(USE_J_MIN, DEFER4(MATCH_EILINE)(P, J_MIN_PIN)) +#define MATCH_K_MAX_EILINE(P) TERN0(USE_K_MAX, DEFER4(MATCH_EILINE)(P, K_MAX_PIN)) +#define MATCH_K_MIN_EILINE(P) TERN0(USE_K_MIN, DEFER4(MATCH_EILINE)(P, K_MIN_PIN)) +#define MATCH_U_MAX_EILINE(P) TERN0(USE_U_MAX, DEFER4(MATCH_EILINE)(P, U_MAX_PIN)) +#define MATCH_U_MIN_EILINE(P) TERN0(USE_U_MIN, DEFER4(MATCH_EILINE)(P, U_MIN_PIN)) +#define MATCH_V_MAX_EILINE(P) TERN0(USE_V_MAX, DEFER4(MATCH_EILINE)(P, V_MAX_PIN)) +#define MATCH_V_MIN_EILINE(P) TERN0(USE_V_MIN, DEFER4(MATCH_EILINE)(P, V_MIN_PIN)) +#define MATCH_W_MAX_EILINE(P) TERN0(USE_W_MAX, DEFER4(MATCH_EILINE)(P, W_MAX_PIN)) +#define MATCH_W_MIN_EILINE(P) TERN0(USE_W_MIN, DEFER4(MATCH_EILINE)(P, W_MIN_PIN)) +#define MATCH_X2_MAX_EILINE(P) TERN0(USE_X2_MAX, DEFER4(MATCH_EILINE)(P, X2_MAX_PIN)) +#define MATCH_X2_MIN_EILINE(P) TERN0(USE_X2_MIN, DEFER4(MATCH_EILINE)(P, X2_MIN_PIN)) +#define MATCH_Y2_MAX_EILINE(P) TERN0(USE_Y2_MAX, DEFER4(MATCH_EILINE)(P, Y2_MAX_PIN)) +#define MATCH_Y2_MIN_EILINE(P) TERN0(USE_Y2_MIN, DEFER4(MATCH_EILINE)(P, Y2_MIN_PIN)) +#define MATCH_Z2_MAX_EILINE(P) TERN0(USE_Z2_MAX, DEFER4(MATCH_EILINE)(P, Z2_MAX_PIN)) +#define MATCH_Z2_MIN_EILINE(P) TERN0(USE_Z2_MIN, DEFER4(MATCH_EILINE)(P, Z2_MIN_PIN)) +#define MATCH_Z3_MAX_EILINE(P) TERN0(USE_Z3_MAX, DEFER4(MATCH_EILINE)(P, Z3_MAX_PIN)) +#define MATCH_Z3_MIN_EILINE(P) TERN0(USE_Z3_MIN, DEFER4(MATCH_EILINE)(P, Z3_MIN_PIN)) +#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) \ + && !MATCH_Y_MAX_EILINE(P) && !MATCH_Y_MIN_EILINE(P) \ + && !MATCH_Z_MAX_EILINE(P) && !MATCH_Z_MIN_EILINE(P) \ + && !MATCH_I_MAX_EILINE(P) && !MATCH_I_MIN_EILINE(P) \ + && !MATCH_J_MAX_EILINE(P) && !MATCH_J_MIN_EILINE(P) \ + && !MATCH_K_MAX_EILINE(P) && !MATCH_K_MIN_EILINE(P) \ + && !MATCH_U_MAX_EILINE(P) && !MATCH_U_MIN_EILINE(P) \ + && !MATCH_V_MAX_EILINE(P) && !MATCH_V_MIN_EILINE(P) \ + && !MATCH_W_MAX_EILINE(P) && !MATCH_W_MIN_EILINE(P) \ + && !MATCH_X2_MAX_EILINE(P) && !MATCH_X2_MIN_EILINE(P) \ + && !MATCH_Y2_MAX_EILINE(P) && !MATCH_Y2_MIN_EILINE(P) \ + && !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_CALIBRATION_EILINE(P) ) // One ISR for all EXT-Interrupts void endstop_ISR() { endstops.update(); } void setup_endstop_interrupts() { #define _ATTACH(P) attachInterrupt(P, endstop_ISR, CHANGE) - #if HAS_X_MAX + #if USE_X_MAX #if !AVAILABLE_EILINE(X_MAX_PIN) - #error "X_MAX_PIN has no EXTINT line available." + #error "X_MAX_PIN has no EXTINT line available. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." #endif _ATTACH(X_MAX_PIN); #endif - #if HAS_X_MIN + #if USE_X_MIN #if !AVAILABLE_EILINE(X_MIN_PIN) - #error "X_MIN_PIN has no EXTINT line available." + #error "X_MIN_PIN has no EXTINT line available. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." #endif _ATTACH(X_MIN_PIN); #endif - #if HAS_Y_MAX + #if USE_Y_MAX #if !AVAILABLE_EILINE(Y_MAX_PIN) - #error "Y_MAX_PIN has no EXTINT line available." + #error "Y_MAX_PIN has no EXTINT line available. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." #endif _ATTACH(Y_MAX_PIN); #endif - #if HAS_Y_MIN + #if USE_Y_MIN #if !AVAILABLE_EILINE(Y_MIN_PIN) - #error "Y_MIN_PIN has no EXTINT line available." + #error "Y_MIN_PIN has no EXTINT line available. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." #endif _ATTACH(Y_MIN_PIN); #endif - #if HAS_Z_MAX + #if USE_Z_MAX #if !AVAILABLE_EILINE(Z_MAX_PIN) - #error "Z_MAX_PIN has no EXTINT line available." + #error "Z_MAX_PIN has no EXTINT line available. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." #endif _ATTACH(Z_MAX_PIN); #endif - #if HAS_Z_MIN + #if USE_Z_MIN #if !AVAILABLE_EILINE(Z_MIN_PIN) - #error "Z_MIN_PIN has no EXTINT line available." + #error "Z_MIN_PIN has no EXTINT line available. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." #endif _ATTACH(Z_MIN_PIN); #endif - #if HAS_Z2_MAX + #if USE_Z2_MAX #if !AVAILABLE_EILINE(Z2_MAX_PIN) - #error "Z2_MAX_PIN has no EXTINT line available." + #error "Z2_MAX_PIN has no EXTINT line available. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." #endif _ATTACH(Z2_MAX_PIN); #endif - #if HAS_Z2_MIN + #if USE_Z2_MIN #if !AVAILABLE_EILINE(Z2_MIN_PIN) - #error "Z2_MIN_PIN has no EXTINT line available." + #error "Z2_MIN_PIN has no EXTINT line available. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." #endif _ATTACH(Z2_MIN_PIN); #endif - #if HAS_Z3_MAX + #if USE_Z3_MAX #if !AVAILABLE_EILINE(Z3_MAX_PIN) - #error "Z3_MAX_PIN has no EXTINT line available." + #error "Z3_MAX_PIN has no EXTINT line available. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." #endif _ATTACH(Z3_MAX_PIN); #endif - #if HAS_Z3_MIN + #if USE_Z3_MIN #if !AVAILABLE_EILINE(Z3_MIN_PIN) - #error "Z3_MIN_PIN has no EXTINT line available." + #error "Z3_MIN_PIN has no EXTINT line available. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." #endif _ATTACH(Z3_MIN_PIN); #endif - #if HAS_Z4_MAX + #if USE_Z4_MAX #if !AVAILABLE_EILINE(Z4_MAX_PIN) - #error "Z4_MAX_PIN has no EXTINT line available." + #error "Z4_MAX_PIN has no EXTINT line available. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." #endif _ATTACH(Z4_MAX_PIN); #endif - #if HAS_Z4_MIN + #if USE_Z4_MIN #if !AVAILABLE_EILINE(Z4_MIN_PIN) - #error "Z4_MIN_PIN has no EXTINT line available." + #error "Z4_MIN_PIN has no EXTINT line available. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." #endif _ATTACH(Z4_MIN_PIN); #endif - #if HAS_Z_MIN_PROBE_PIN + #if USE_Z_MIN_PROBE #if !AVAILABLE_EILINE(Z_MIN_PROBE_PIN) - #error "Z_MIN_PROBE_PIN has no EXTINT line available." + #error "Z_MIN_PROBE_PIN has no EXTINT line available. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." #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." + #endif + attachInterrupt(I_MAX_PIN, endstop_ISR, CHANGE); + #endif + #if USE_I_MIN + #if !AVAILABLE_EILINE(I_MIN_PIN) + #error "I_MIN_PIN has no EXTINT line available. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #endif + attachInterrupt(I_MIN_PIN, endstop_ISR, CHANGE); + #endif + #if USE_J_MAX + #if !AVAILABLE_EILINE(J_MAX_PIN) + #error "J_MAX_PIN has no EXTINT line available. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #endif + attachInterrupt(J_MAX_PIN, endstop_ISR, CHANGE); + #endif + #if USE_J_MIN + #if !AVAILABLE_EILINE(J_MIN_PIN) + #error "J_MIN_PIN has no EXTINT line available. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #endif + attachInterrupt(J_MIN_PIN, endstop_ISR, CHANGE); + #endif + #if USE_K_MAX + #if !AVAILABLE_EILINE(K_MAX_PIN) + #error "K_MAX_PIN has no EXTINT line available. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #endif + attachInterrupt(K_MAX_PIN, endstop_ISR, CHANGE); + #endif + #if USE_K_MIN + #if !AVAILABLE_EILINE(K_MIN_PIN) + #error "K_MIN_PIN has no EXTINT line available. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #endif + attachInterrupt(K_MIN_PIN, endstop_ISR, CHANGE); + #endif + #if USE_U_MAX + #if !AVAILABLE_EILINE(U_MAX_PIN) + #error "U_MAX_PIN has no EXTINT line available. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #endif + attachInterrupt(U_MAX_PIN, endstop_ISR, CHANGE); + #endif + #if USE_U_MIN + #if !AVAILABLE_EILINE(U_MIN_PIN) + #error "U_MIN_PIN has no EXTINT line available. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #endif + attachInterrupt(U_MIN_PIN, endstop_ISR, CHANGE); + #endif + #if USE_V_MAX + #if !AVAILABLE_EILINE(V_MAX_PIN) + #error "V_MAX_PIN has no EXTINT line available. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #endif + attachInterrupt(V_MAX_PIN, endstop_ISR, CHANGE); + #endif + #if USE_V_MIN + #if !AVAILABLE_EILINE(V_MIN_PIN) + #error "V_MIN_PIN has no EXTINT line available. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #endif + attachInterrupt(V_MIN_PIN, endstop_ISR, CHANGE); + #endif + #if USE_W_MAX + #if !AVAILABLE_EILINE(W_MAX_PIN) + #error "W_MAX_PIN has no EXTINT line available. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #endif + attachInterrupt(W_MAX_PIN, endstop_ISR, CHANGE); + #endif + #if USE_W_MIN + #if !AVAILABLE_EILINE(W_MIN_PIN) + #error "W_MIN_PIN has no EXTINT line available. Disable ENDSTOP_INTERRUPTS_FEATURE to continue." + #endif + attachInterrupt(W_MIN_PIN, endstop_ISR, CHANGE); + #endif } diff --git a/Marlin/src/HAL/SAMD51/fastio.h b/Marlin/src/HAL/SAMD51/fastio.h index c456dfce30..1a67b6ce20 100644 --- a/Marlin/src/HAL/SAMD51/fastio.h +++ b/Marlin/src/HAL/SAMD51/fastio.h @@ -1,8 +1,9 @@ /** * Marlin 3D Printer Firmware - * * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * SAMD51 HAL developed by Giuliano Zaro (AKA GMagician) + * + * 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 @@ -20,6 +21,10 @@ */ #pragma once +/** + * SAMD51 HAL developed by Giuliano Zaro (AKA GMagician) + */ + /** * Fast IO functions for SAMD51 */ @@ -31,7 +36,7 @@ */ #ifndef MASK - #define MASK(PIN) (1 << PIN) + #define MASK(PIN) _BV(PIN) #endif /** @@ -125,13 +130,13 @@ #ifdef ADAFRUIT_GRAND_CENTRAL_M4 - /* + /** * Adafruit Grand Central M4 has a lot of PWMs the availables are listed here. * Some of these share the same source and so can't be used in the same time */ #define PWM_PIN(P) (WITHIN(P, 2, 13) || WITHIN(P, 22, 23) || WITHIN(P, 44, 45) || P == 48) - // Return fullfilled ADCx->INPUTCTRL.reg + // Return fulfilled ADCx->INPUTCTRL.reg #define PIN_TO_INPUTCTRL(P) ( (PIN_TO_AIN(P) == 0) ? ADC_INPUTCTRL_MUXPOS_AIN0 \ : (PIN_TO_AIN(P) == 1) ? ADC_INPUTCTRL_MUXPOS_AIN1 \ : (PIN_TO_AIN(P) == 2) ? ADC_INPUTCTRL_MUXPOS_AIN2 \ @@ -169,9 +174,9 @@ : (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/inc/Conditionals_LCD.h b/Marlin/src/HAL/SAMD51/inc/Conditionals_LCD.h index 932348c52f..5f1c4b1601 100644 --- a/Marlin/src/HAL/SAMD51/inc/Conditionals_LCD.h +++ b/Marlin/src/HAL/SAMD51/inc/Conditionals_LCD.h @@ -20,7 +20,3 @@ * */ #pragma once - -#if HAS_SPI_TFT || HAS_FSMC_TFT - #error "Sorry! TFT displays are not available for HAL/SAMD51." -#endif diff --git a/Marlin/src/HAL/SAMD51/inc/Conditionals_post.h b/Marlin/src/HAL/SAMD51/inc/Conditionals_post.h index ce6d3fdde2..295596b78b 100644 --- a/Marlin/src/HAL/SAMD51/inc/Conditionals_post.h +++ b/Marlin/src/HAL/SAMD51/inc/Conditionals_post.h @@ -23,6 +23,6 @@ #if USE_FALLBACK_EEPROM #define FLASH_EEPROM_EMULATION -#elif EITHER(I2C_EEPROM, SPI_EEPROM) +#elif ANY(I2C_EEPROM, SPI_EEPROM) #define USE_SHARED_EEPROM 1 #endif diff --git a/Marlin/src/HAL/SAMD51/inc/Conditionals_type.h b/Marlin/src/HAL/SAMD51/inc/Conditionals_type.h new file mode 100644 index 0000000000..82f95a1035 --- /dev/null +++ b/Marlin/src/HAL/SAMD51/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/SAMD51/inc/SanityCheck.h b/Marlin/src/HAL/SAMD51/inc/SanityCheck.h index 5d610acac8..4719ac6eb8 100644 --- a/Marlin/src/HAL/SAMD51/inc/SanityCheck.h +++ b/Marlin/src/HAL/SAMD51/inc/SanityCheck.h @@ -1,8 +1,9 @@ /** * Marlin 3D Printer Firmware - * * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * SAMD51 HAL developed by Giuliano Zaro (AKA GMagician) + * + * 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 @@ -18,11 +19,20 @@ * along with this program. If not, see . * */ +#pragma once + +/** + * SAMD51 HAL developed by Giuliano Zaro (AKA GMagician) + */ /** * Test SAMD51 specific configuration values for errors at compile-time. */ +#if HAS_SPI_TFT || HAS_FSMC_TFT + #error "Sorry! TFT displays are not available for HAL/SAMD51." +#endif + #if ENABLED(FLASH_EEPROM_EMULATION) #warning "Did you activate the SmartEEPROM? See https://github.com/GMagician/SAMD51-SmartEEprom-Manager/releases" #endif @@ -31,11 +41,12 @@ #error "No custom SD drive cable defined for this board." #endif -#if defined(MAX6675_SCK_PIN) && defined(MAX6675_DO_PIN) && (MAX6675_SCK_PIN == SCK1 || MAX6675_DO_PIN == MISO1) +#if (defined(TEMP_0_SCK_PIN) && defined(TEMP_0_MISO_PIN) && (TEMP_0_SCK_PIN == SCK1 || TEMP_0_MISO_PIN == MISO1)) || \ + (defined(TEMP_1_SCK_PIN) && defined(TEMP_1_MISO_PIN) && (TEMP_1_SCK_PIN == SCK1 || TEMP_1_MISO_PIN == MISO1)) #error "OnBoard SPI BUS can't be shared with other devices." #endif -#if SERVO_TC == RTC_TIMER_NUM +#if SERVO_TC == MF_TIMER_RTC #error "Servos can't use RTC timer" #endif @@ -43,10 +54,14 @@ #error "EMERGENCY_PARSER is not yet implemented for SAMD51. Disable EMERGENCY_PARSER to continue." #endif -#if ENABLED(SDIO_SUPPORT) - #error "SDIO_SUPPORT is not supported on SAMD51." +#if ENABLED(ONBOARD_SDIO) + #error "ONBOARD_SDIO is not supported on SAMD51." #endif #if ENABLED(FAST_PWM_FAN) || SPINDLE_LASER_FREQUENCY - #error "Features requiring Hardware PWM (FAST_PWM_FAN, SPINDLE_LASER_FREQUENCY) are not yet supported on SAMD51." + #error "Features requiring Hardware PWM (FAST_PWM_FAN, SPINDLE_LASER_FREQUENCY) are not yet supported for HAL/SAMD51." +#endif + +#if ENABLED(POSTMORTEM_DEBUGGING) + #error "POSTMORTEM_DEBUGGING is not yet supported on AGCM4." #endif diff --git a/Marlin/src/HAL/SAMD51/pinsDebug.h b/Marlin/src/HAL/SAMD51/pinsDebug.h index 81376db79a..1518c615c9 100644 --- a/Marlin/src/HAL/SAMD51/pinsDebug.h +++ b/Marlin/src/HAL/SAMD51/pinsDebug.h @@ -1,8 +1,9 @@ /** * Marlin 3D Printer Firmware - * * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * SAMD51 HAL developed by Giuliano Zaro (AKA GMagician) + * + * 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 @@ -20,37 +21,58 @@ */ #pragma once +/** + * 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 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) != -1) +#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_ECHOPAIR("PWM = ", duty); + //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 5a9b1275ef..a2d5b72ec4 100644 --- a/Marlin/src/HAL/SAMD51/spi_pins.h +++ b/Marlin/src/HAL/SAMD51/spi_pins.h @@ -1,8 +1,9 @@ /** * Marlin 3D Printer Firmware - * * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * SAMD51 HAL developed by Giuliano Zaro (AKA GMagician) + * + * 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 @@ -20,9 +21,13 @@ */ #pragma once +/** + * SAMD51 HAL developed by Giuliano Zaro (AKA GMagician) + */ + #ifdef ADAFRUIT_GRAND_CENTRAL_M4 - /* + /** * AGCM4 Default SPI Pins * * SS SCK MISO MOSI @@ -30,19 +35,16 @@ * SPI | 53 52 50 51 | * SPI1 | 83 81 80 82 | * +-------------------------+ - * Any pin can be used for Chip Select (SS_PIN) + * Any pin can be used for Chip Select (SD_SS_PIN) */ - #ifndef SCK_PIN - #define SCK_PIN 52 + #ifndef SD_SCK_PIN + #define SD_SCK_PIN 52 #endif - #ifndef MISO_PIN - #define MISO_PIN 50 + #ifndef SD_MISO_PIN + #define SD_MISO_PIN 50 #endif - #ifndef MOSI_PIN - #define MOSI_PIN 51 - #endif - #ifndef SDSS - #define SDSS 53 + #ifndef SD_MOSI_PIN + #define SD_MOSI_PIN 51 #endif #else @@ -50,5 +52,3 @@ #error "Unsupported board!" #endif - -#define SS_PIN SDSS diff --git a/Marlin/src/HAL/SAMD51/timers.cpp b/Marlin/src/HAL/SAMD51/timers.cpp index a68af2e074..7a211eb36a 100644 --- a/Marlin/src/HAL/SAMD51/timers.cpp +++ b/Marlin/src/HAL/SAMD51/timers.cpp @@ -1,8 +1,9 @@ /** * Marlin 3D Printer Firmware - * * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * SAMD51 HAL developed by Giuliano Zaro (AKA GMagician) + * + * 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 @@ -18,6 +19,10 @@ * along with this program. If not, see . * */ + +/** + * SAMD51 HAL developed by Giuliano Zaro (AKA GMagician) + */ #ifdef __SAMD51__ // -------------------------------------------------------------------------- @@ -31,13 +36,13 @@ // Local defines // -------------------------------------------------------------------------- -#define NUM_HARDWARE_TIMERS 8 +#define NUM_HARDWARE_TIMERS 9 // -------------------------------------------------------------------------- // Private Variables // -------------------------------------------------------------------------- -const tTimerConfig TimerConfig[NUM_HARDWARE_TIMERS+1] = { +const tTimerConfig timer_config[NUM_HARDWARE_TIMERS] = { { {.pTc=TC0}, TC0_IRQn, TC_PRIORITY(0) }, // 0 - stepper (assigned priority 2) { {.pTc=TC1}, TC1_IRQn, TC_PRIORITY(1) }, // 1 - stepper (needed by 32 bit timers) { {.pTc=TC2}, TC2_IRQn, 5 }, // 2 - tone (reserved by framework and fixed assigned priority 5) @@ -67,19 +72,19 @@ FORCE_INLINE void Disable_Irq(IRQn_Type irq) { // -------------------------------------------------------------------------- void HAL_timer_start(const uint8_t timer_num, const uint32_t frequency) { - IRQn_Type irq = TimerConfig[timer_num].IRQ_Id; + IRQn_Type irq = timer_config[timer_num].IRQ_Id; // Disable interrupt, just in case it was already enabled Disable_Irq(irq); - if (timer_num == RTC_TIMER_NUM) { - Rtc * const rtc = TimerConfig[timer_num].pRtc; + if (timer_num == MF_TIMER_RTC) { + Rtc * const rtc = timer_config[timer_num].pRtc; // Disable timer interrupt rtc->MODE0.INTENCLR.reg = RTC_MODE0_INTENCLR_CMP0; // RTC clock setup - OSC32KCTRL->RTCCTRL.reg = OSC32KCTRL_RTCCTRL_RTCSEL_XOSC32K; // External 32.768KHz oscillator + OSC32KCTRL->RTCCTRL.reg = OSC32KCTRL_RTCCTRL_RTCSEL_XOSC32K; // External 32.768kHz oscillator // Stop timer, just in case, to be able to reconfigure it rtc->MODE0.CTRLA.bit.ENABLE = false; @@ -101,13 +106,13 @@ void HAL_timer_start(const uint8_t timer_num, const uint32_t frequency) { SYNC(rtc->MODE0.SYNCBUSY.bit.ENABLE); } else { - Tc * const tc = TimerConfig[timer_num].pTc; + Tc * const tc = timer_config[timer_num].pTc; // Disable timer interrupt tc->COUNT32.INTENCLR.reg = TC_INTENCLR_OVF; // disable overflow interrupt // TCn clock setup - const uint8_t clockID = GCLK_CLKCTRL_IDs[TCC_INST_NUM + timer_num]; // TC clock are preceeded by TCC ones + const uint8_t clockID = GCLK_CLKCTRL_IDs[TCC_INST_NUM + timer_num]; // TC clock are preceded by TCC ones GCLK->PCHCTRL[clockID].bit.CHEN = false; SYNC(GCLK->PCHCTRL[clockID].bit.CHEN); GCLK->PCHCTRL[clockID].reg = GCLK_PCHCTRL_GEN_GCLK0 | GCLK_PCHCTRL_CHEN; // 120MHz startup code programmed @@ -141,27 +146,27 @@ void HAL_timer_start(const uint8_t timer_num, const uint32_t frequency) { } // Finally, enable IRQ - NVIC_SetPriority(irq, TimerConfig[timer_num].priority); + NVIC_SetPriority(irq, timer_config[timer_num].priority); NVIC_EnableIRQ(irq); } void HAL_timer_enable_interrupt(const uint8_t timer_num) { - const IRQn_Type irq = TimerConfig[timer_num].IRQ_Id; + const IRQn_Type irq = timer_config[timer_num].IRQ_Id; NVIC_EnableIRQ(irq); } void HAL_timer_disable_interrupt(const uint8_t timer_num) { - const IRQn_Type irq = TimerConfig[timer_num].IRQ_Id; + const IRQn_Type irq = timer_config[timer_num].IRQ_Id; Disable_Irq(irq); } // missing from CMSIS: Check if interrupt is enabled or not static bool NVIC_GetEnabledIRQ(IRQn_Type IRQn) { - return (NVIC->ISER[(uint32_t)(IRQn) >> 5] & (1 << ((uint32_t)(IRQn) & 0x1F))) != 0; + return TEST(NVIC->ISER[uint32_t(IRQn) >> 5], uint32_t(IRQn) & 0x1F); } bool HAL_timer_interrupt_enabled(const uint8_t timer_num) { - const IRQn_Type irq = TimerConfig[timer_num].IRQ_Id; + const IRQn_Type irq = timer_config[timer_num].IRQ_Id; return NVIC_GetEnabledIRQ(irq); } diff --git a/Marlin/src/HAL/SAMD51/timers.h b/Marlin/src/HAL/SAMD51/timers.h index dc6e38b730..2682891cdc 100644 --- a/Marlin/src/HAL/SAMD51/timers.h +++ b/Marlin/src/HAL/SAMD51/timers.h @@ -1,8 +1,9 @@ /** * Marlin 3D Printer Firmware - * * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * SAMD51 HAL developed by Giuliano Zaro (AKA GMagician) + * + * 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 @@ -20,62 +21,65 @@ */ #pragma once +/** + * SAMD51 HAL developed by Giuliano Zaro (AKA GMagician) + */ + #include // -------------------------------------------------------------------------- // Defines // -------------------------------------------------------------------------- -#define RTC_TIMER_NUM 8 // This is not a TC but a RTC 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 -#ifndef STEP_TIMER_NUM - #define STEP_TIMER_NUM 0 // Timer Index for Stepper +#define MF_TIMER_RTC 8 // This is not a TC but a RTC + +#ifndef MF_TIMER_STEP + #define MF_TIMER_STEP 0 // Timer Index for Stepper #endif -#ifndef PULSE_TIMER_NUM - #define PULSE_TIMER_NUM STEP_TIMER_NUM +#ifndef MF_TIMER_PULSE + #define MF_TIMER_PULSE MF_TIMER_STEP #endif -#ifndef TEMP_TIMER_NUM - #define TEMP_TIMER_NUM RTC_TIMER_NUM // Timer Index for Temperature +#ifndef MF_TIMER_TEMP + #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(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) -#define TC_PRIORITY(t) t == SERVO_TC ? 1 \ - : (t == STEP_TIMER_NUM || t == PULSE_TIMER_NUM) ? 2 \ - : (t == TEMP_TIMER_NUM) ? 6 \ - : 7 +#define TC_PRIORITY(t) ( t == SERVO_TC ? 1 \ + : (t == MF_TIMER_STEP || t == MF_TIMER_PULSE) ? 2 \ + : (t == MF_TIMER_TEMP) ? 6 : 7 ) #define _TC_HANDLER(t) void TC##t##_Handler() #define TC_HANDLER(t) _TC_HANDLER(t) #ifndef HAL_STEP_TIMER_ISR - #define HAL_STEP_TIMER_ISR() TC_HANDLER(STEP_TIMER_NUM) + #define HAL_STEP_TIMER_ISR() TC_HANDLER(MF_TIMER_STEP) #endif -#if STEP_TIMER_NUM != PULSE_TIMER_NUM - #define HAL_PULSE_TIMER_ISR() TC_HANDLER(PULSE_TIMER_NUM) +#if MF_TIMER_STEP != MF_TIMER_PULSE + #define HAL_PULSE_TIMER_ISR() TC_HANDLER(MF_TIMER_PULSE) #endif -#if TEMP_TIMER_NUM == RTC_TIMER_NUM +#if MF_TIMER_TEMP == MF_TIMER_RTC #define HAL_TEMP_TIMER_ISR() void RTC_Handler() #else - #define HAL_TEMP_TIMER_ISR() TC_HANDLER(TEMP_TIMER_NUM) + #define HAL_TEMP_TIMER_ISR() TC_HANDLER(MF_TIMER_TEMP) #endif // -------------------------------------------------------------------------- @@ -95,7 +99,7 @@ typedef struct { // Public Variables // -------------------------------------------------------------------------- -extern const tTimerConfig TimerConfig[]; +extern const tTimerConfig timer_config[]; // -------------------------------------------------------------------------- // Public functions @@ -104,20 +108,20 @@ extern const tTimerConfig TimerConfig[]; 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) { - // Should never be called with timer RTC_TIMER_NUM - Tc * const tc = TimerConfig[timer_num].pTc; + // Should never be called with timer MF_TIMER_RTC + Tc * const tc = timer_config[timer_num].pTc; tc->COUNT32.CC[0].reg = compare; } FORCE_INLINE static hal_timer_t HAL_timer_get_compare(const uint8_t timer_num) { - // Should never be called with timer RTC_TIMER_NUM - Tc * const tc = TimerConfig[timer_num].pTc; + // Should never be called with timer MF_TIMER_RTC + Tc * const tc = timer_config[timer_num].pTc; return (hal_timer_t)tc->COUNT32.CC[0].reg; } FORCE_INLINE static hal_timer_t HAL_timer_get_count(const uint8_t timer_num) { - // Should never be called with timer RTC_TIMER_NUM - Tc * const tc = TimerConfig[timer_num].pTc; + // Should never be called with timer MF_TIMER_RTC + Tc * const tc = timer_config[timer_num].pTc; tc->COUNT32.CTRLBSET.reg = TC_CTRLBCLR_CMD_READSYNC; SYNC(tc->COUNT32.SYNCBUSY.bit.CTRLB || tc->COUNT32.SYNCBUSY.bit.COUNT); return tc->COUNT32.COUNT.reg; @@ -128,16 +132,16 @@ void HAL_timer_disable_interrupt(const uint8_t timer_num); bool HAL_timer_interrupt_enabled(const uint8_t timer_num); FORCE_INLINE static void HAL_timer_isr_prologue(const uint8_t timer_num) { - if (timer_num == RTC_TIMER_NUM) { - Rtc * const rtc = TimerConfig[timer_num].pRtc; + if (timer_num == MF_TIMER_RTC) { + Rtc * const rtc = timer_config[timer_num].pRtc; // Clear interrupt flag rtc->MODE0.INTFLAG.reg = RTC_MODE0_INTFLAG_CMP0; } else { - Tc * const tc = TimerConfig[timer_num].pTc; + Tc * const tc = timer_config[timer_num].pTc; // Clear interrupt flag tc->COUNT32.INTFLAG.reg = TC_INTFLAG_OVF; } } -#define HAL_timer_isr_epilogue(timer_num) +inline void HAL_timer_isr_epilogue(const uint8_t) {} diff --git a/Marlin/src/HAL/SAMD51/u8g/LCD_defines.h b/Marlin/src/HAL/SAMD51/u8g/LCD_defines.h new file mode 100644 index 0000000000..658a2c0fc1 --- /dev/null +++ b/Marlin/src/HAL/SAMD51/u8g/LCD_defines.h @@ -0,0 +1,29 @@ +/** + * 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 + * (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 + +/** + * SAMD51 LCD-specific defines + */ + +#define U8G_COM_HAL_HW_SPI_FN u8g_com_samd51_hw_spi_fn // See U8glib-HAL +#define U8G_COM_ST7920_HAL_HW_SPI u8g_com_samd51_st7920_hw_spi_fn // See U8glib-HAL diff --git a/Marlin/src/HAL/SAMD51/watchdog.cpp b/Marlin/src/HAL/SAMD51/watchdog.cpp deleted file mode 100644 index ebc8dffe13..0000000000 --- a/Marlin/src/HAL/SAMD51/watchdog.cpp +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Marlin 3D Printer Firmware - * - * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * SAMD51 HAL developed by Giuliano Zaro (AKA GMagician) - * - * 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 __SAMD51__ - -#include "../../inc/MarlinConfig.h" - -#if ENABLED(USE_WATCHDOG) - - #include "watchdog.h" - - void watchdog_init() { - // The low-power oscillator used by the WDT runs at 32,768 Hz with - // a 1:32 prescale, thus 1024 Hz, though probably not super precise. - - // Setup WDT clocks - MCLK->APBAMASK.bit.OSC32KCTRL_ = true; - MCLK->APBAMASK.bit.WDT_ = true; - OSC32KCTRL->OSCULP32K.bit.EN1K = true; // Enable out 1K (this is what WDT uses) - - WDT->CTRLA.bit.ENABLE = false; // Disable watchdog for config - SYNC(WDT->SYNCBUSY.bit.ENABLE); - - WDT->INTENCLR.reg = WDT_INTENCLR_EW; // Disable early warning interrupt - WDT->CONFIG.reg = WDT_CONFIG_PER_CYC4096; // Set at least 4s period for chip reset - - HAL_watchdog_refresh(); - - WDT->CTRLA.reg = WDT_CTRLA_ENABLE; // Start watchdog now in normal mode - SYNC(WDT->SYNCBUSY.bit.ENABLE); - } - -#endif // USE_WATCHDOG - -#endif // __SAMD51__ diff --git a/Marlin/src/HAL/SAMD51/watchdog.h b/Marlin/src/HAL/SAMD51/watchdog.h deleted file mode 100644 index 2cd4788229..0000000000 --- a/Marlin/src/HAL/SAMD51/watchdog.h +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Marlin 3D Printer Firmware - * - * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * SAMD51 HAL developed by Giuliano Zaro (AKA GMagician) - * - * 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 - -// Initialize watchdog with a 4 second interrupt time -void watchdog_init(); - -// Reset watchdog. MUST be called at least every 4 seconds after the -// first watchdog_init or SAMD will go into emergency procedures. -inline void HAL_watchdog_refresh() { - SYNC(WDT->SYNCBUSY.bit.CLEAR); // Test first if previous is 'ongoing' to save time waiting for command execution - WDT->CLEAR.reg = WDT_CLEAR_CLEAR_KEY; -} diff --git a/Marlin/src/HAL/STM32/HAL.cpp b/Marlin/src/HAL/STM32/HAL.cpp index 83604b1104..018fb48881 100644 --- a/Marlin/src/HAL/STM32/HAL.cpp +++ b/Marlin/src/HAL/STM32/HAL.cpp @@ -1,10 +1,9 @@ /** * Marlin 3D Printer Firmware - * * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com - * Copyright (c) 2015-2016 Nico Tonnhofer wurstnase.reprap@gmail.com - * Copyright (c) 2017 Victor Perez + * + * 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 @@ -20,14 +19,19 @@ * along with this program. If not, see . * */ -#if defined(ARDUINO_ARCH_STM32) && !defined(STM32GENERIC) +#include "../platforms.h" -#include "HAL.h" -#include "usb_serial.h" +#ifdef HAL_STM32 #include "../../inc/MarlinConfig.h" #include "../shared/Delay.h" +#include "usb_serial.h" + +#ifdef USBCON + DefaultSerial1 MSerialUSB(false, SerialUSB); +#endif + #if ENABLED(SRAM_EEPROM_EMULATION) #if STM32F7xx #include @@ -38,33 +42,35 @@ #endif #endif +#if HAS_SD_HOST_DRIVE + #include "sd/msc_sd.h" + #include +#endif + // ------------------------ // Public Variables // ------------------------ -uint16_t HAL_adc_result; +uint16_t MarlinHAL::adc_result; // ------------------------ // Public functions // ------------------------ -// Needed for DELAY_NS() / DELAY_US() on CORTEX-M7 -#if (defined(__arm__) || defined(__thumb__)) && __CORTEX_M == 7 - // HAL pre-initialization task - // Force the preinit function to run between the premain() and main() function - // of the STM32 arduino core - __attribute__((constructor (102))) - void HAL_preinit() { - enableCycleCounter(); - } +#if ENABLED(POSTMORTEM_DEBUGGING) + extern void install_min_serial(); #endif // HAL initialization task -void HAL_init() { - FastIO_init(); +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 ENABLED(SDSUPPORT) && DISABLED(SDIO_SUPPORT) && (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) @@ -81,13 +87,34 @@ void HAL_init() { SetTimerInterruptPriorities(); - TERN_(EMERGENCY_PARSER, USB_Hook_init()); + #if ENABLED(EMERGENCY_PARSER) && ANY(USBD_USE_CDC, USBD_USE_CDC_MSC) + 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 } -void HAL_clear_reset_source() { __HAL_RCC_CLEAR_RESET_FLAGS(); } +// HAL idle task +void MarlinHAL::idletask() { + #if HAS_SHARED_MEDIA + // Stm32duino currently doesn't have a "loop/idle" method + CDC_resume_receive(); + CDC_continue_transmit(); + #endif +} -uint8_t HAL_get_reset_source() { - return +void MarlinHAL::reboot() { NVIC_SystemReset(); } + +uint8_t MarlinHAL::get_reset_source() { + return ( #ifdef RCC_FLAG_IWDGRST // Some sources may not exist... RESET != __HAL_RCC_GET_FLAG(RCC_FLAG_IWDGRST) ? RST_WATCHDOG : #endif @@ -107,29 +134,48 @@ uint8_t HAL_get_reset_source() { RESET != __HAL_RCC_GET_FLAG(RCC_FLAG_PORRST) ? RST_POWER_ON : #endif 0 - ; + ); } -void _delay_ms(const int delay_ms) { delay(delay_ms); } +void MarlinHAL::clear_reset_source() { __HAL_RCC_CLEAR_RESET_FLAGS(); } + +// ------------------------ +// Watchdog Timer +// ------------------------ + +#if ENABLED(USE_WATCHDOG) + + #define WDT_TIMEOUT_US TERN(WATCHDOG_DURATION_8S, 8000000, 4000000) // 4 or 8 second timeout + + #include + + void MarlinHAL::watchdog_init() { + IF_DISABLED(DISABLE_WATCHDOG_INIT, IWatchdog.begin(WDT_TIMEOUT_US)); + } + + void MarlinHAL::watchdog_refresh() { + IWatchdog.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 } -// ------------------------ -// ADC -// ------------------------ - -// TODO: Make sure this doesn't cause any delay -void HAL_adc_start_conversion(const uint8_t adc_pin) { HAL_adc_result = analogRead(adc_pin); } -uint16_t HAL_adc_get_result() { return HAL_adc_result; } - -// Reset the system (to initiate a firmware flash) -void flashFirmware(const int16_t) { NVIC_SystemReset(); } +// Reset the system to initiate a firmware flash +WEAK void flashFirmware(const int16_t) { hal.reboot(); } // Maple Compatibility +volatile uint32_t systick_uptime_millis = 0; systickCallback_t systick_user_callback; void systick_attach_callback(systickCallback_t cb) { systick_user_callback = cb; } -void HAL_SYSTICK_Callback() { if (systick_user_callback) systick_user_callback(); } +void HAL_SYSTICK_Callback() { + systick_uptime_millis++; + if (systick_user_callback) systick_user_callback(); +} -#endif // ARDUINO_ARCH_STM32 && !STM32GENERIC +#endif // HAL_STM32 diff --git a/Marlin/src/HAL/STM32/HAL.h b/Marlin/src/HAL/STM32/HAL.h index a1f7515d6b..24889c872a 100644 --- a/Marlin/src/HAL/STM32/HAL.h +++ b/Marlin/src/HAL/STM32/HAL.h @@ -1,10 +1,9 @@ /** * Marlin 3D Printer Firmware - * * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com - * Copyright (c) 2015-2016 Nico Tonnhofer wurstnase.reprap@gmail.com - * Copyright (c) 2017 Victor Perez + * + * 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 @@ -24,124 +23,105 @@ #define CPU_32_BIT -#include "../../core/macros.h" -#include "../shared/Marduino.h" -#include "../shared/math_32bit.h" -#include "../shared/HAL_SPI.h" -#include "fastio.h" -#include "watchdog.h" -#include "MarlinSerial.h" - #include "../../inc/MarlinConfigPre.h" -#include +#include "../shared/math_32bit.h" +#include "../shared/HAL_SPI.h" +#include "temp_soc.h" +#include "fastio.h" +#include "Servo.h" -#ifdef USBCON - #include -#endif +// +// Default graphical display delays +// +#define CPU_ST7920_DELAY_1 300 +#define CPU_ST7920_DELAY_2 40 +#define CPU_ST7920_DELAY_3 340 -// ------------------------ -// Defines -// ------------------------ -#define _MSERIAL(X) MSerial##X -#define MSERIAL(X) _MSERIAL(X) +// +// Serial Ports +// -#if SERIAL_PORT == -1 - #define MYSERIAL0 SerialUSB -#elif WITHIN(SERIAL_PORT, 1, 6) - #define MYSERIAL0 MSERIAL(SERIAL_PORT) -#else - #error "SERIAL_PORT must be -1 or from 1 to 6. Please update your configuration." -#endif - -#ifdef SERIAL_PORT_2 - #if SERIAL_PORT_2 == -1 - #define MYSERIAL1 SerialUSB - #elif WITHIN(SERIAL_PORT_2, 1, 6) - #define MYSERIAL1 MSERIAL(SERIAL_PORT_2) - #else - #error "SERIAL_PORT_2 must be -1 or from 1 to 6. Please update your configuration." - #endif -#endif - -#ifdef LCD_SERIAL_PORT - #if LCD_SERIAL_PORT == -1 - #define LCD_SERIAL SerialUSB - #elif WITHIN(LCD_SERIAL_PORT, 1, 6) - #define LCD_SERIAL MSERIAL(LCD_SERIAL_PORT) - #else - #error "LCD_SERIAL_PORT must be -1 or from 1 to 6. Please update your configuration." - #endif - #if HAS_DGUS_LCD - #define SERIAL_GET_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 -#define CRITICAL_SECTION_START() uint32_t primask = __get_PRIMASK(); __disable_irq() -#define CRITICAL_SECTION_END() if (!primask) __enable_irq() -#define ISRS_ENABLED() (!__get_PRIMASK()) -#define ENABLE_ISRS() __enable_irq() -#define DISABLE_ISRS() __disable_irq() +// +// 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() -// On AVR this is in math.h? -#define square(x) ((x)*(x)) - -#ifndef strncpy_P - #define strncpy_P(dest, src, num) strncpy((dest), (src), (num)) -#endif - -// Fix bug in pgm_read_ptr -#undef pgm_read_ptr -#define pgm_read_ptr(addr) (*(addr)) - // ------------------------ // Types // ------------------------ -typedef int16_t pin_t; +typedef int32_t pin_t; // Parity with platform/ststm32 -#define HAL_SERVO_LIB libServo +class libServo; +typedef libServo hal_servo_t; +#define PAUSE_SERVO_OUTPUT() libServo::pause_all_servos() +#define RESUME_SERVO_OUTPUT() libServo::resume_all_servos() // ------------------------ -// Public Variables +// ADC // ------------------------ -// result of last ADC conversion -extern uint16_t HAL_adc_result; +#ifdef ADC_RESOLUTION + #define HAL_ADC_RESOLUTION ADC_RESOLUTION +#else + #define HAL_ADC_RESOLUTION 12 +#endif + +#define HAL_ADC_VREF_MV 3300 + +// +// 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) + +#ifdef STM32F1xx + #define JTAG_DISABLE() AFIO_DBGAFR_CONFIG(AFIO_MAPR_SWJ_CFG_JTAGDISABLE) + #define JTAGSWD_DISABLE() AFIO_DBGAFR_CONFIG(AFIO_MAPR_SWJ_CFG_DISABLE) + #define JTAGSWD_RESET() AFIO_DBGAFR_CONFIG(AFIO_MAPR_SWJ_CFG_RESET); // Reset: FULL SWD+JTAG +#endif + +#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 // ------------------------ -// Public functions +// Class Utilities // ------------------------ // Memory related #define __bss_end __bss_end__ -// Enable hooks into setup for HAL -void HAL_init(); - -// Clear reset reason -void HAL_clear_reset_source(); - -// Reset reason -uint8_t HAL_get_reset_source(); - -inline void HAL_reboot() {} // reboot the board or restart the bootloader - -void _delay_ms(const int delay); - extern "C" char* _sbrk(int incr); #pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-function" +#if GCC_VERSION <= 50000 + #pragma GCC diagnostic ignored "-Wunused-function" +#endif static inline int freeMemory() { volatile char top; @@ -150,37 +130,75 @@ static inline int freeMemory() { #pragma GCC diagnostic pop -// -// ADC -// +// ------------------------ +// MarlinHAL Class +// ------------------------ -#define HAL_ANALOG_SELECT(pin) pinMode(pin, INPUT) +class MarlinHAL { +public: -inline void HAL_adc_init() {} + // Earliest possible init, before setup() + MarlinHAL() {} -#define HAL_ADC_VREF 3.3 -#define HAL_ADC_RESOLUTION 10 -#define HAL_START_ADC(pin) HAL_adc_start_conversion(pin) -#define HAL_READ_ADC() HAL_adc_result -#define HAL_ADC_READY() true + // Watchdog + static void watchdog_init() IF_DISABLED(USE_WATCHDOG, {}); + static void watchdog_refresh() IF_DISABLED(USE_WATCHDOG, {}); -void HAL_adc_start_conversion(const uint8_t adc_pin); + static void init(); // Called early in setup() + static void init_board() {} // Called less early in setup() + static void reboot(); // Restart the firmware from 0x0 -uint16_t HAL_adc_get_result(); + // Interrupts + static bool isr_state() { return !__get_PRIMASK(); } + static void isr_on() { sei(); } + static void isr_off() { cli(); } -#define GET_PIN_MAP_PIN(index) index -#define GET_PIN_MAP_INDEX(pin) pin -#define PARSED_PIN_INDEX(code, dval) parser.intval(code, dval) + static void delay_ms(const int ms) { delay(ms); } -#ifdef STM32F1xx - #define JTAG_DISABLE() AFIO_DBGAFR_CONFIG(AFIO_MAPR_SWJ_CFG_JTAGDISABLE) - #define JTAGSWD_DISABLE() AFIO_DBGAFR_CONFIG(AFIO_MAPR_SWJ_CFG_DISABLE) -#endif + // Tasks, called from marlin.idle() + static void idletask(); -#define PLATFORM_M997_SUPPORT -void flashFirmware(const int16_t); + // Reset + static uint8_t get_reset_source(); + static void clear_reset_source(); -// Maple Compatibility -typedef void (*systickCallback_t)(void); -void systick_attach_callback(systickCallback_t cb); -void HAL_SYSTICK_Callback(); + // Free SRAM + static int freeMemory() { return ::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); } + + // Begin ADC sampling on the given pin. Called from Temperature::isr! + static void adc_start(const pin_t pin) { adc_result = analogRead(pin); } + + // Is the ADC ready for reading? + static bool adc_ready() { return true; } + + // The 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 v, const uint16_t v_size=255, 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/STM32/HAL_SPI.cpp b/Marlin/src/HAL/STM32/HAL_SPI.cpp index f947e6ef32..b220ae6a78 100644 --- a/Marlin/src/HAL/STM32/HAL_SPI.cpp +++ b/Marlin/src/HAL/STM32/HAL_SPI.cpp @@ -4,7 +4,6 @@ * * Based on Sprinter and grbl. * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm - * Copyright (c) 2017 Victor Perez * * 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 @@ -20,7 +19,9 @@ * along with this program. If not, see . * */ -#if defined(ARDUINO_ARCH_STM32) && !defined(STM32GENERIC) +#include "../platforms.h" + +#ifdef HAL_STM32 #include "../../inc/MarlinConfig.h" @@ -36,7 +37,7 @@ static SPISettings spiConfig; // Public functions // ------------------------ -#if ENABLED(SOFTWARE_SPI) +#if ANY(SOFTWARE_SPI, FORCE_SOFT_SPI) // ------------------------ // Software SPI @@ -45,26 +46,37 @@ static SPISettings spiConfig; #include "../shared/Delay.h" void spiBegin(void) { - OUT_WRITE(SS_PIN, HIGH); - OUT_WRITE(SCK_PIN, HIGH); - SET_INPUT(MISO_PIN); - OUT_WRITE(MOSI_PIN, HIGH); + #if PIN_EXISTS(SD_SS) + OUT_WRITE(SD_SS_PIN, HIGH); + #endif + OUT_WRITE(SD_SCK_PIN, HIGH); + SET_INPUT(SD_MISO_PIN); + OUT_WRITE(SD_MOSI_PIN, HIGH); } - static uint16_t delay_STM32_soft_spi; + // 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: delay_STM32_soft_spi = 125; break; // desired: 8,000,000 actual: ~1.1M - case SPI_HALF_SPEED: delay_STM32_soft_spi = 125; break; // desired: 4,000,000 actual: ~1.1M - case SPI_QUARTER_SPEED:delay_STM32_soft_spi = 250; break; // desired: 2,000,000 actual: ~890K - case SPI_EIGHTH_SPEED: delay_STM32_soft_spi = 500; break; // desired: 1,000,000 actual: ~590K - case SPI_SPEED_5: delay_STM32_soft_spi = 1000; break; // desired: 500,000 actual: ~360K - case SPI_SPEED_6: delay_STM32_soft_spi = 2000; break; // desired: 250,000 actual: ~210K - default: delay_STM32_soft_spi = 4000; break; // desired: 125,000 actual: ~123K + 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 @@ -72,15 +84,15 @@ static SPISettings spiConfig; uint8_t HAL_SPI_STM32_SpiTransfer_Mode_3(uint8_t b) { // using Mode 3 for (uint8_t bits = 8; bits--;) { - WRITE(SCK_PIN, LOW); - WRITE(MOSI_PIN, b & 0x80); + WRITE(SD_SCK_PIN, LOW); + WRITE(SD_MOSI_PIN, b & 0x80); - DELAY_NS(delay_STM32_soft_spi); - WRITE(SCK_PIN, HIGH); - DELAY_NS(delay_STM32_soft_spi); + delaySPIFunc(); + WRITE(SD_SCK_PIN, HIGH); + delaySPIFunc(); b <<= 1; // little setup time - b |= (READ(MISO_PIN) != 0); + b |= (READ(SD_MISO_PIN) != 0); } DELAY_NS(125); return b; @@ -88,9 +100,9 @@ static SPISettings spiConfig; // Soft SPI receive byte uint8_t spiRec() { - DISABLE_ISRS(); // No interrupts during byte receive + hal.isr_off(); // No interrupts during byte receive const uint8_t data = HAL_SPI_STM32_SpiTransfer_Mode_3(0xFF); - ENABLE_ISRS(); // Enable interrupts + hal.isr_on(); // Enable interrupts return data; } @@ -102,9 +114,9 @@ static SPISettings spiConfig; // Soft SPI send byte void spiSend(uint8_t data) { - DISABLE_ISRS(); // No interrupts during byte send + hal.isr_off(); // No interrupts during byte send HAL_SPI_STM32_SpiTransfer_Mode_3(data); // Don't care what is received - ENABLE_ISRS(); // Enable interrupts + hal.isr_on(); // Enable interrupts } // Soft SPI send block @@ -132,8 +144,8 @@ static SPISettings spiConfig; * @details Only configures SS pin since stm32duino creates and initialize the SPI object */ void spiBegin() { - #if PIN_EXISTS(SS) - OUT_WRITE(SS_PIN, HIGH); + #if PIN_EXISTS(SD_SS) + OUT_WRITE(SD_SS_PIN, HIGH); #endif } @@ -153,12 +165,9 @@ static SPISettings spiConfig; } spiConfig = SPISettings(clock, MSBFIRST, SPI_MODE0); - #if ENABLED(CUSTOM_SPI_PINS) - SPI.setMISO(MISO_PIN); - SPI.setMOSI(MOSI_PIN); - SPI.setSCLK(SCK_PIN); - SPI.setSSEL(SS_PIN); - #endif + SPI.setMISO(SD_MISO_PIN); + SPI.setMOSI(SD_MOSI_PIN); + SPI.setSCLK(SD_SCK_PIN); SPI.begin(); } @@ -184,7 +193,7 @@ static SPISettings spiConfig; * * @details Uses DMA */ - void spiRead(uint8_t* buf, uint16_t nbyte) { + void spiRead(uint8_t *buf, uint16_t nbyte) { if (nbyte == 0) return; memset(buf, 0xFF, nbyte); SPI.transfer(buf, nbyte); @@ -209,7 +218,7 @@ static SPISettings spiConfig; * * @details Use DMA */ - void spiSendBlock(uint8_t token, const uint8_t* buf) { + void spiSendBlock(uint8_t token, const uint8_t *buf) { uint8_t rxBuf[512]; SPI.transfer(token); SPI.transfer((uint8_t*)buf, &rxBuf, 512); @@ -217,4 +226,4 @@ static SPISettings spiConfig; #endif // SOFTWARE_SPI -#endif // ARDUINO_ARCH_STM32 && !STM32GENERIC +#endif // HAL_STM32 diff --git a/Marlin/src/HAL/STM32/HardwareSerial.cpp b/Marlin/src/HAL/STM32/HardwareSerial.cpp new file mode 100644 index 0000000000..58360cc31e --- /dev/null +++ b/Marlin/src/HAL/STM32/HardwareSerial.cpp @@ -0,0 +1,529 @@ +/** + * 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 + * Copyright (c) 2017 Victor Perez + * + * 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 . + * + */ + +// +// HAL_HardwareSerial Class. Adapted from Arduino HardwareSerial. +// + +#include "../platforms.h" + +#ifdef HAL_STM32 + +#include "../../inc/MarlinConfig.h" + +#if ENABLED(SERIAL_DMA) && defined(HAL_UART_MODULE_ENABLED) && !defined(HAL_UART_MODULE_ONLY) + +#include +#include "HardwareSerial.h" +#include "uart.h" + +// 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 +#ifndef PIN_SERIAL1_RX + #define PIN_SERIAL1_RX PA10 +#endif +#ifndef PIN_SERIAL2_TX + #define PIN_SERIAL2_TX PA2 +#endif +#ifndef PIN_SERIAL2_RX + #define PIN_SERIAL2_RX PA3 +#endif +#ifndef PIN_SERIAL3_TX + #define PIN_SERIAL3_TX PB10 +#endif +#ifndef PIN_SERIAL3_RX + #define PIN_SERIAL3_RX PB11 +#endif +#ifndef PIN_SERIAL4_TX + #define PIN_SERIAL4_TX PC10 +#endif +#ifndef PIN_SERIAL4_RX + #define PIN_SERIAL4_RX PC11 +#endif +#ifndef PIN_SERIAL5_TX + #define PIN_SERIAL5_TX PC12 +#endif +#ifndef PIN_SERIAL5_RX + #define PIN_SERIAL5_RX PD2 +#endif +#ifndef PIN_SERIAL6_TX + #define PIN_SERIAL6_TX PC6 +#endif +#ifndef PIN_SERIAL6_RX + #define PIN_SERIAL6_RX PC7 +#endif + +// 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 + HAL_HardwareSerial HSerial1(USART1); + void serialEvent1() __attribute__((weak)); +#endif +#ifdef USING_HW_SERIAL2 + HAL_HardwareSerial HSerial2(USART2); + void serialEvent2() __attribute__((weak)); +#endif +#ifdef USING_HW_SERIAL3 + HAL_HardwareSerial HSerial3(USART3); + void serialEvent3() __attribute__((weak)); +#endif +#ifdef USING_HW_SERIAL4 + #ifdef USART4 + HAL_HardwareSerial HSerial4(USART4); + #else + HAL_HardwareSerial HSerial4(UART4); + #endif + void serialEvent4() __attribute__((weak)); +#endif +#ifdef USING_HW_SERIAL5 + #ifdef USART5 + HAL_HardwareSerial HSerial5(USART5); + #else + HAL_HardwareSerial HSerial5(UART5); + #endif + void serialEvent5() __attribute__((weak)); +#endif +#ifdef USING_HW_SERIAL6 + #ifdef USART6 + HAL_HardwareSerial HSerial6(USART6); + #else + HAL_HardwareSerial HSerial6(UART6); + #endif + void serialEvent6() __attribute__((weak)); +#endif + +// Constructors //////////////////////////////////////////////////////////////// + +HAL_HardwareSerial::HAL_HardwareSerial(void *peripheral) { + if (peripheral == USART1) { + setRx(PIN_SERIAL1_RX); + setTx(PIN_SERIAL1_TX); + _uart_index = 0; + + #ifdef DMA2_Stream2 // F2 / F4 / F7 / H7 + RX_DMA = { USART1, 2, DMA2_Stream2 }; // USART, DMA controller no., DMA stream + #endif + #ifdef DMA1_Channel5 // F0 / F1 + RX_DMA = { USART1, 1, DMA1_Channel5 }; // USART, DMA controller no., DMA channel + #endif + } + else if (peripheral == USART2) { + setRx(PIN_SERIAL2_RX); + setTx(PIN_SERIAL2_TX); + _uart_index = 1; + #ifdef DMA1_Stream5 + RX_DMA = { USART2, 1, DMA1_Stream5 }; + #endif + #ifdef DMA1_Channel6 + RX_DMA = { USART2, 1, DMA1_Channel6 }; + #endif + } + else if (peripheral == USART3) { + setRx(PIN_SERIAL3_RX); + setTx(PIN_SERIAL3_TX); + _uart_index = 2; + #ifdef DMA1_Stream1 + RX_DMA = { USART3, 1, DMA1_Stream1 }; + #endif + #ifdef DMA1_Channel3 // F0 has no support for UART3, requires system remapping + RX_DMA = { USART3, 1, DMA1_Channel3 }; + #endif + } + + #ifdef USART4 // Only F2 / F4 / F7 + else if (peripheral == USART4) { + #ifdef DMA1_Stream2 + RX_DMA = { USART4, 1, DMA1_Stream2 }; + #endif + setRx(PIN_SERIAL4_RX); + setTx(PIN_SERIAL4_TX); + _uart_index = 3; + } + #endif + + #ifdef UART4 + else if (peripheral == UART4) { + #ifdef DMA1_Stream2 + RX_DMA = { UART4, 1, DMA1_Stream2 }; + #endif + #ifdef DMA2_Channel3 // STM32F0xx has only 3 UARTs + RX_DMA = { UART4, 2, DMA2_Channel3 }; + #endif + setRx(PIN_SERIAL4_RX); + setTx(PIN_SERIAL4_TX); + _uart_index = 3; + } + #endif + + #ifdef UART5 // Only F2 / F4 / F7 / H7 + else if (peripheral == UART5) { + #ifdef DMA1_Stream0 + RX_DMA = { UART5, 1, DMA1_Stream0 }; + #endif + setRx(PIN_SERIAL5_RX); + setTx(PIN_SERIAL5_TX); + _uart_index = 4; + } + #endif + + #ifdef USART6 // Only F2 / F4 / F7 / H7 + else if (peripheral == USART6) { + #ifdef DMA2_Stream1 + RX_DMA = { USART6, 2, DMA2_Stream1 }; + #endif + setRx(PIN_SERIAL6_RX); + setTx(PIN_SERIAL6_TX); + _uart_index = 5; + } + #endif + + 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); + } + + init(_serial.pin_rx, _serial.pin_tx); +} + +void HAL_HardwareSerial::setRx(uint32_t _rx) { + _serial.pin_rx = digitalPinToPinName(_rx); +} + +void HAL_HardwareSerial::setTx(uint32_t _tx) { + _serial.pin_tx = digitalPinToPinName(_tx); +} + +void HAL_HardwareSerial::init(PinName _rx, PinName _tx) { + _serial.pin_rx = _rx; + _serial.rx_buff = _rx_buffer; + _serial.rx_head = _serial.rx_tail = 0; + + _serial.pin_tx = _tx; + _serial.tx_buff = _tx_buffer; + _serial.tx_head = _serial.tx_tail = 0; +} + +// Actual interrupt handlers ////////////////////////////////////////////////////////////// + +/** + * @brief Read receive byte from uart + * @param obj : pointer to serial_t structure + * @retval last character received + */ + +#if DISABLED(STM32H7xx) + + 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 ////////////////////////////////////////////////////////////// + +void HAL_HardwareSerial::begin(unsigned long baud, uint8_t config) { + uint32_t databits = 0, stopbits = 0, parity = 0; + + _baud = baud; + _config = config; + + // Manage databits + switch (config & 0x07) { + case 0x02: databits = 6; break; + case 0x04: databits = 7; break; + case 0x06: databits = 8; break; + default: databits = 0; break; + } + + if ((config & 0x30) == 0x30) { + parity = UART_PARITY_ODD; + databits++; + } + else if ((config & 0x20) == 0x20) { + parity = UART_PARITY_EVEN; + databits++; + } + else + parity = UART_PARITY_NONE; + + stopbits = (config & 0x08) == 0x08 ? UART_STOPBITS_2 : UART_STOPBITS_1; + + switch (databits) { + #ifdef UART_WORDLENGTH_7B + case 7: databits = UART_WORDLENGTH_7B; break; + #endif + case 8: databits = UART_WORDLENGTH_8B; break; + case 9: databits = UART_WORDLENGTH_9B; break; + default: + case 0: Error_Handler(); break; + } + + uart_init(&_serial, (uint32_t)baud, databits, parity, stopbits); + Serial_DMA_Read_Enable(); // Start the circular DMA serial reading process, no callback needed +} + +void HAL_HardwareSerial::end() { + flush(); // Wait for transmission of outgoing data + uart_deinit(&_serial); + _serial.rx_head = _serial.rx_tail; // Clear any received data +} + +// Update buffer head for DMA progress +void HAL_HardwareSerial::update_rx_head() { + + #if ENABLED(EMERGENCY_PARSER) + static uint32_t flag = 0; + while (flag != _serial.rx_head) { // send all available data to emergency parser immediately + emergency_parser.update(static_cast(this)->emergency_state, _serial.rx_buff[flag]); + flag = (flag + 1) % RX_BUFFER_SIZE; + } + #endif + + #if ANY(STM32F2xx, STM32F4xx, STM32F7xx, STM32H7xx) + _serial.rx_head = RX_BUFFER_SIZE - RX_DMA.dma_streamRX->NDTR; + #endif + + #if ANY(STM32F0xx, STM32F1xx) + _serial.rx_head = RX_BUFFER_SIZE - RX_DMA.dma_channelRX->CNDTR; + #endif + +} + +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; + + 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 + + unsigned char c = _serial.rx_buff[_serial.rx_tail]; + _serial.rx_tail = (rx_buffer_index_t)(_serial.rx_tail + 1) % RX_BUFFER_SIZE; + return c; +} + +size_t HAL_HardwareSerial::write(uint8_t c) { // Interrupt based writing + tx_buffer_index_t i = (_serial.tx_head + 1) % TX_BUFFER_SIZE; + + // If the output buffer is full, there's nothing for it other than to + // wait for the interrupt handler to empty it a bit + while (i == _serial.tx_tail) { /* nada */ } // NOP, let the interrupt free up space for us + + _serial.tx_buff[_serial.tx_head] = c; + _serial.tx_head = i; + + #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; +} + +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, STM32H7xx) + + void HAL_HardwareSerial::Serial_DMA_Read_Enable() { + + 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); // DMA stream Peripheral Address Register = USART Data Register + #endif + + 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 = 4 << DMA_SxCR_CHSEL_Pos; // DMA stream Channel Selection, always use channel 4 + + #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 || STM32H7xx + +#if ANY(STM32F0xx, STM32F1xx) + + void HAL_HardwareSerial::Serial_DMA_Read_Enable() { + + 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 &= ~USART_CR1_UE; // DMA stream clear/disable + while (RX_DMA.dma_channelRX->CCR & DMA_CCR_EN) { /* just wait for DMA to complete */ } + + // Clear/disable UART and DMA + RX_DMA.uart->CR1 = 0; // UART clear CR1, disable DMA + + // 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.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 + +#endif // SERIAL_DMA && HAL_UART_MODULE_ENABLED && !HAL_UART_MODULE_ONLY +#endif // HAL_STM32 diff --git a/Marlin/src/HAL/STM32/HardwareSerial.h b/Marlin/src/HAL/STM32/HardwareSerial.h new file mode 100644 index 0000000000..cf97637278 --- /dev/null +++ b/Marlin/src/HAL/STM32/HardwareSerial.h @@ -0,0 +1,83 @@ +/** + * 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 + * Copyright (c) 2017 Victor Perez + * + * 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 + +// +// HAL_HardwareSerial Class. Adapted from Arduino HardwareSerial. +// + +#if RX_BUFFER_SIZE == 0 + #undef RX_BUFFER_SIZE + #define RX_BUFFER_SIZE 128 +#endif + +#if TX_BUFFER_SIZE == 0 + #undef TX_BUFFER_SIZE + #define TX_BUFFER_SIZE 64 +#endif + +typedef struct { + USART_TypeDef * uart; + uint32_t DMA_ID; // DMA1=1; DM2=2; BDMA=3 + #if ANY(STM32F0xx, STM32F1xx) // F0 / F1 + DMA_Channel_TypeDef * dma_channelRX; + #else // F2 / F4 / F7 / H7 + DMA_Stream_TypeDef * dma_streamRX; + #endif +} DMA_CFG; + +class HAL_HardwareSerial : public Stream { + protected: + // Don't put any members after these buffers, since only the first + // 32 bytes of this struct can be accessed quickly using the ldd instruction. + unsigned char _rx_buffer[RX_BUFFER_SIZE]; + unsigned char _tx_buffer[TX_BUFFER_SIZE]; + + serial_t _serial; + + public: + HAL_HardwareSerial(void *peripheral); + void begin(unsigned long, uint8_t); + void end(); + virtual int available(); + virtual int read(); + virtual int peek(); + virtual size_t write(uint8_t); + virtual void flush(); + operator bool() { return true; } + + void setRx(uint32_t _rx); + void setTx(uint32_t _tx); + + static int _tx_complete_irq(serial_t *obj); // Interrupt handler + + private: + uint8_t _uart_index; + bool _rx_enabled; + uint8_t _config; + unsigned long _baud; + void init(PinName _rx, PinName _tx); + void update_rx_head(); + DMA_CFG RX_DMA; + void Serial_DMA_Read_Enable(); +}; diff --git a/Marlin/src/HAL/STM32/MarlinSPI.cpp b/Marlin/src/HAL/STM32/MarlinSPI.cpp new file mode 100644 index 0000000000..f7c603d77e --- /dev/null +++ b/Marlin/src/HAL/STM32/MarlinSPI.cpp @@ -0,0 +1,174 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2020 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" + +#if defined(HAL_STM32) && !defined(STM32H7xx) + +#include "MarlinSPI.h" + +static void spi_init(spi_t *obj, uint32_t speed, spi_mode_e mode, uint8_t msb, uint32_t dataSize) { + spi_init(obj, speed, mode, msb); + // spi_init set 8bit always + // TODO: copy the code from spi_init and handle data size, to avoid double init always!! + if (dataSize != SPI_DATASIZE_8BIT) { + obj->handle.Init.DataSize = dataSize; + HAL_SPI_Init(&obj->handle); + __HAL_SPI_ENABLE(&obj->handle); + } +} + +void MarlinSPI::setClockDivider(uint8_t _div) { + _speed = spi_getClkFreq(&_spi);// / _div; + _clockDivider = _div; +} + +void MarlinSPI::begin(void) { + //TODO: only call spi_init if any parameter changed!! + spi_init(&_spi, _speed, _dataMode, _bitOrder, _dataSize); +} + +void MarlinSPI::setupDma(SPI_HandleTypeDef &_spiHandle, DMA_HandleTypeDef &_dmaHandle, uint32_t direction, bool minc) { + _dmaHandle.Init.Direction = direction; + _dmaHandle.Init.PeriphInc = DMA_PINC_DISABLE; + _dmaHandle.Init.Mode = DMA_NORMAL; + _dmaHandle.Init.Priority = DMA_PRIORITY_LOW; + _dmaHandle.Init.MemInc = minc ? DMA_MINC_ENABLE : DMA_MINC_DISABLE; + + if (_dataSize == DATA_SIZE_8BIT) { + _dmaHandle.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; + _dmaHandle.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; + } + else { + _dmaHandle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; + _dmaHandle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; + } + #ifdef STM32F4xx + _dmaHandle.Init.FIFOMode = DMA_FIFOMODE_DISABLE; + #endif + + // start DMA hardware + // TODO: check if hardware is already enabled + #ifdef SPI1_BASE + if (_spiHandle.Instance == SPI1) { + #ifdef STM32F1xx + __HAL_RCC_DMA1_CLK_ENABLE(); + _dmaHandle.Instance = (direction == DMA_MEMORY_TO_PERIPH) ? DMA1_Channel3 : DMA1_Channel2; + #elif defined(STM32F4xx) + __HAL_RCC_DMA2_CLK_ENABLE(); + _dmaHandle.Init.Channel = DMA_CHANNEL_3; + _dmaHandle.Instance = (direction == DMA_MEMORY_TO_PERIPH) ? DMA2_Stream3 : DMA2_Stream0; + #endif + } + #endif + #ifdef SPI2_BASE + if (_spiHandle.Instance == SPI2) { + #ifdef STM32F1xx + __HAL_RCC_DMA1_CLK_ENABLE(); + _dmaHandle.Instance = (direction == DMA_MEMORY_TO_PERIPH) ? DMA1_Channel5 : DMA1_Channel4; + #elif defined(STM32F4xx) + __HAL_RCC_DMA1_CLK_ENABLE(); + _dmaHandle.Init.Channel = DMA_CHANNEL_0; + _dmaHandle.Instance = (direction == DMA_MEMORY_TO_PERIPH) ? DMA1_Stream4 : DMA1_Stream3; + #endif + } + #endif + #ifdef SPI3_BASE + if (_spiHandle.Instance == SPI3) { + #ifdef STM32F1xx + __HAL_RCC_DMA2_CLK_ENABLE(); + _dmaHandle.Instance = (direction == DMA_MEMORY_TO_PERIPH) ? DMA2_Channel2 : DMA2_Channel1; + #elif defined(STM32F4xx) + __HAL_RCC_DMA1_CLK_ENABLE(); + _dmaHandle.Init.Channel = DMA_CHANNEL_0; + _dmaHandle.Instance = (direction == DMA_MEMORY_TO_PERIPH) ? DMA1_Stream5 : DMA1_Stream2; + #endif + } + #endif + + HAL_DMA_Init(&_dmaHandle); +} + +byte MarlinSPI::transfer(uint8_t _data) { + uint8_t rxData = 0xFF; + HAL_SPI_TransmitReceive(&_spi.handle, &_data, &rxData, 1, HAL_MAX_DELAY); + return rxData; +} + +__STATIC_INLINE void LL_SPI_EnableDMAReq_RX(SPI_TypeDef *SPIx) { SET_BIT(SPIx->CR2, SPI_CR2_RXDMAEN); } +__STATIC_INLINE void LL_SPI_EnableDMAReq_TX(SPI_TypeDef *SPIx) { SET_BIT(SPIx->CR2, SPI_CR2_TXDMAEN); } + +uint8_t MarlinSPI::dmaTransfer(const void *transmitBuf, void *receiveBuf, uint16_t length) { + const uint8_t ff = 0xFF; + + //if (!LL_SPI_IsEnabled(_spi.handle)) // only enable if disabled + __HAL_SPI_ENABLE(&_spi.handle); + + if (receiveBuf) { + setupDma(_spi.handle, _dmaRx, DMA_PERIPH_TO_MEMORY, true); + HAL_DMA_Start(&_dmaRx, (uint32_t)&(_spi.handle.Instance->DR), (uint32_t)receiveBuf, length); + LL_SPI_EnableDMAReq_RX(_spi.handle.Instance); // Enable Rx DMA Request + } + + // check for 2 lines transfer + bool mincTransmit = true; + if (transmitBuf == nullptr && _spi.handle.Init.Direction == SPI_DIRECTION_2LINES && _spi.handle.Init.Mode == SPI_MODE_MASTER) { + transmitBuf = &ff; + mincTransmit = false; + } + + if (transmitBuf) { + setupDma(_spi.handle, _dmaTx, DMA_MEMORY_TO_PERIPH, mincTransmit); + HAL_DMA_Start(&_dmaTx, (uint32_t)transmitBuf, (uint32_t)&(_spi.handle.Instance->DR), length); + LL_SPI_EnableDMAReq_TX(_spi.handle.Instance); // Enable Tx DMA Request + } + + if (transmitBuf) { + HAL_DMA_PollForTransfer(&_dmaTx, HAL_DMA_FULL_TRANSFER, HAL_MAX_DELAY); + HAL_DMA_Abort(&_dmaTx); + HAL_DMA_DeInit(&_dmaTx); + } + + // while ((_spi.handle.Instance->SR & SPI_FLAG_RXNE) != SPI_FLAG_RXNE) {} + + if (receiveBuf) { + HAL_DMA_PollForTransfer(&_dmaRx, HAL_DMA_FULL_TRANSFER, HAL_MAX_DELAY); + HAL_DMA_Abort(&_dmaRx); + HAL_DMA_DeInit(&_dmaRx); + } + + return 1; +} + +uint8_t MarlinSPI::dmaSend(const void * transmitBuf, uint16_t length, bool minc) { + setupDma(_spi.handle, _dmaTx, DMA_MEMORY_TO_PERIPH, minc); + HAL_DMA_Start(&_dmaTx, (uint32_t)transmitBuf, (uint32_t)&(_spi.handle.Instance->DR), length); + __HAL_SPI_ENABLE(&_spi.handle); + LL_SPI_EnableDMAReq_TX(_spi.handle.Instance); // Enable Tx DMA Request + HAL_DMA_PollForTransfer(&_dmaTx, HAL_DMA_FULL_TRANSFER, HAL_MAX_DELAY); + HAL_DMA_Abort(&_dmaTx); + // DeInit objects + HAL_DMA_DeInit(&_dmaTx); + return 1; +} + +#endif // HAL_STM32 && !STM32H7xx diff --git a/Marlin/src/HAL/STM32/MarlinSPI.h b/Marlin/src/HAL/STM32/MarlinSPI.h new file mode 100644 index 0000000000..fbd3585ff4 --- /dev/null +++ b/Marlin/src/HAL/STM32/MarlinSPI.h @@ -0,0 +1,107 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2020 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 "HAL.h" +#include + +extern "C" { + #include +} + +/** + * Marlin currently requires 3 SPI classes: + * + * SPIClass: + * This class is normally provided by frameworks and has a semi-default interface. + * This is needed because some libraries reference it globally. + * + * SPISettings: + * Container for SPI configs for SPIClass. As above, libraries may reference it globally. + * + * These two classes are often provided by frameworks so we cannot extend them to add + * useful methods for Marlin. + * + * MarlinSPI: + * Provides the default SPIClass interface plus some Marlin goodies such as a simplified + * interface for SPI DMA transfer. + * + */ + +#define DATA_SIZE_8BIT SPI_DATASIZE_8BIT +#define DATA_SIZE_16BIT SPI_DATASIZE_16BIT + +class MarlinSPI { +public: + MarlinSPI() : MarlinSPI(NC, NC, NC, NC) {} + + MarlinSPI(pin_t mosi, pin_t miso, pin_t sclk, pin_t ssel = (pin_t)NC) : _mosiPin(mosi), _misoPin(miso), _sckPin(sclk), _ssPin(ssel) { + _spi.pin_miso = digitalPinToPinName(_misoPin); + _spi.pin_mosi = digitalPinToPinName(_mosiPin); + _spi.pin_sclk = digitalPinToPinName(_sckPin); + _spi.pin_ssel = digitalPinToPinName(_ssPin); + _dataSize = DATA_SIZE_8BIT; + _bitOrder = MSBFIRST; + _dataMode = SPI_MODE_0; + _spi.handle.State = HAL_SPI_STATE_RESET; + setClockDivider(SPI_SPEED_CLOCK_DIV2_MHZ); + } + + void begin(void); + void end(void) {} + + byte transfer(uint8_t _data); + uint8_t dmaTransfer(const void *transmitBuf, void *receiveBuf, uint16_t length); + uint8_t dmaSend(const void * transmitBuf, uint16_t length, bool minc = true); + + /* These methods are deprecated and kept for compatibility. + * Use SPISettings with SPI.beginTransaction() to configure SPI parameters. + */ + void setBitOrder(BitOrder _order) { _bitOrder = _order; } + + void setDataMode(uint8_t _mode) { + 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; + } + } + + void setClockDivider(uint8_t _div); + +private: + void setupDma(SPI_HandleTypeDef &_spiHandle, DMA_HandleTypeDef &_dmaHandle, uint32_t direction, bool minc = false); + + spi_t _spi; + DMA_HandleTypeDef _dmaTx; + DMA_HandleTypeDef _dmaRx; + BitOrder _bitOrder; + spi_mode_e _dataMode; + uint8_t _clockDivider; + uint32_t _speed; + uint32_t _dataSize; + pin_t _mosiPin; + pin_t _misoPin; + pin_t _sckPin; + pin_t _ssPin; +}; diff --git a/Marlin/src/HAL/STM32/MarlinSerial.cpp b/Marlin/src/HAL/STM32/MarlinSerial.cpp index a146664366..862678373f 100644 --- a/Marlin/src/HAL/STM32/MarlinSerial.cpp +++ b/Marlin/src/HAL/STM32/MarlinSerial.cpp @@ -2,6 +2,9 @@ * Marlin 3D Printer Firmware * Copyright (c) 2020 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 @@ -17,7 +20,9 @@ * */ -#if defined(ARDUINO_ARCH_STM32) && !defined(STM32GENERIC) +#include "../platforms.h" + +#ifdef HAL_STM32 #include "../../inc/MarlinConfig.h" #include "MarlinSerial.h" @@ -29,58 +34,99 @@ #ifndef USART4 #define USART4 UART4 #endif - #ifndef USART5 #define USART5 UART5 #endif - -#define DECLARE_SERIAL_PORT(ser_num) \ - void _rx_complete_irq_ ## ser_num (serial_t * obj); \ - MarlinSerial MSerial ## ser_num (USART ## ser_num, &_rx_complete_irq_ ## ser_num); \ - void _rx_complete_irq_ ## ser_num (serial_t * obj) { MSerial ## ser_num ._rx_complete_irq(obj); } - -#define DECLARE_SERIAL_PORT_EXP(ser_num) DECLARE_SERIAL_PORT(ser_num) - -#if defined(SERIAL_PORT) && SERIAL_PORT >= 0 - DECLARE_SERIAL_PORT_EXP(SERIAL_PORT) +#ifndef USART6 + #define USART6 UART6 +#endif +#ifndef USART7 + #define USART7 UART7 +#endif +#ifndef USART8 + #define USART8 UART8 +#endif +#ifndef USART9 + #define USART9 UART9 #endif -#if defined(SERIAL_PORT_2) && SERIAL_PORT_2 >= 0 - DECLARE_SERIAL_PORT_EXP(SERIAL_PORT_2) +#if ENABLED(SERIAL_DMA) + #define DECLARE_SERIAL_PORT(ser_num) \ + MSerialT MSerial ## ser_num (true, USART ## ser_num); +#else + #define DECLARE_SERIAL_PORT(ser_num) \ + void _rx_complete_irq_ ## ser_num (serial_t * obj); \ + MSerialT MSerial ## ser_num (true, USART ## ser_num, &_rx_complete_irq_ ## ser_num); \ + void _rx_complete_irq_ ## ser_num (serial_t * obj) { MSerial ## ser_num ._rx_complete_irq(obj); } #endif -#if defined(LCD_SERIAL_PORT) && LCD_SERIAL_PORT >= 0 - DECLARE_SERIAL_PORT_EXP(LCD_SERIAL_PORT) +#if USING_HW_SERIAL1 + DECLARE_SERIAL_PORT(1) +#endif +#if USING_HW_SERIAL2 + DECLARE_SERIAL_PORT(2) +#endif +#if USING_HW_SERIAL3 + DECLARE_SERIAL_PORT(3) +#endif +#if USING_HW_SERIAL4 + DECLARE_SERIAL_PORT(4) +#endif +#if USING_HW_SERIAL5 + DECLARE_SERIAL_PORT(5) +#endif +#if USING_HW_SERIAL6 + DECLARE_SERIAL_PORT(6) +#endif +#if USING_HW_SERIAL7 + DECLARE_SERIAL_PORT(7) +#endif +#if USING_HW_SERIAL8 + DECLARE_SERIAL_PORT(8) +#endif +#if USING_HW_SERIAL9 + DECLARE_SERIAL_PORT(9) +#endif +#if USING_HW_SERIAL10 + DECLARE_SERIAL_PORT(10) +#endif +#if USING_HW_SERIALLP1 + DECLARE_SERIAL_PORT(LP1) #endif void MarlinSerial::begin(unsigned long baud, uint8_t config) { - HardwareSerial::begin(baud, config); - // Replace the IRQ callback with the one we have defined - TERN_(EMERGENCY_PARSER, _serial.rx_callback = _rx_callback); + #if ENABLED(SERIAL_DMA) + HAL_HardwareSerial::begin(baud, config); + #else + HardwareSerial::begin(baud, config); + // Replace the IRQ callback with the one we have defined + TERN_(EMERGENCY_PARSER, _serial.rx_callback = _rx_callback); + #endif } -// This function is Copyright (c) 2006 Nicholas Zambetti. -void MarlinSerial::_rx_complete_irq(serial_t *obj) { - // No Parity error, read byte and store it in the buffer if there is room - unsigned char c; +#if DISABLED(SERIAL_DMA) - if (uart_getc(obj, &c) == 0) { + // This function Copyright (c) 2006 Nicholas Zambetti. + void MarlinSerial::_rx_complete_irq(serial_t *obj) { + // No Parity error, read byte and store it in the buffer if there is room + unsigned char c; + if (uart_getc(obj, &c) == 0) { - rx_buffer_index_t i = (unsigned int)(obj->rx_head + 1) % SERIAL_RX_BUFFER_SIZE; + rx_buffer_index_t i = (unsigned int)(obj->rx_head + 1) % SERIAL_RX_BUFFER_SIZE; - // if we should be storing the received character into the location - // just before the tail (meaning that the head would advance to the - // current location of the tail), we're about to overflow the buffer - // and so we don't write the character or advance the head. - if (i != obj->rx_tail) { - obj->rx_buff[obj->rx_head] = c; - obj->rx_head = i; + // If tail overlaps head the buffer is overflowed + // so don't write the character or advance the head. + if (i != obj->rx_tail) { + obj->rx_buff[obj->rx_head] = c; + obj->rx_head = i; + } + + #if ENABLED(EMERGENCY_PARSER) + emergency_parser.update(static_cast(this)->emergency_state, c); + #endif } - - #if ENABLED(EMERGENCY_PARSER) - emergency_parser.update(emergency_state, c); - #endif } -} -#endif // ARDUINO_ARCH_STM32 && !STM32GENERIC +#endif // !SERIAL_DMA + +#endif // HAL_STM32 diff --git a/Marlin/src/HAL/STM32/MarlinSerial.h b/Marlin/src/HAL/STM32/MarlinSerial.h index 3611cc78d7..73ab77d8d4 100644 --- a/Marlin/src/HAL/STM32/MarlinSerial.h +++ b/Marlin/src/HAL/STM32/MarlinSerial.h @@ -2,6 +2,9 @@ * Marlin 3D Printer Firmware * Copyright (c) 2020 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 @@ -24,41 +27,64 @@ #include "../../feature/e_parser.h" #endif -typedef void (*usart_rx_callback_t)(serial_t * obj); +#if ENABLED(SERIAL_DMA) + #include "HardwareSerial.h" +#endif -class MarlinSerial : public HardwareSerial { -public: - MarlinSerial(void* peripheral, usart_rx_callback_t rx_callback) : - HardwareSerial(peripheral), _rx_callback(rx_callback) - #if ENABLED(EMERGENCY_PARSER) - , emergency_state(EmergencyParser::State::EP_RESET) - #endif - { } +#include "../../core/serial_hook.h" - #if ENABLED(EMERGENCY_PARSER) - static inline bool emergency_parser_enabled() { return true; } - #endif +#ifdef USBCON + #include + typedef ForwardSerial1Class< decltype(SerialUSB) > DefaultSerial1; + extern DefaultSerial1 MSerialUSB; + #define USB_SERIAL_PORT(...) MSerialUSB +#endif - void begin(unsigned long baud, uint8_t config); - inline void begin(unsigned long baud) { begin(baud, SERIAL_8N1); } +#define SERIAL_INDEX_MIN 1 +#define SERIAL_INDEX_MAX 9 +#include "../shared/serial_ports.h" - void _rx_complete_irq(serial_t* obj); +#if defined(LCD_SERIAL_PORT) && ANY(HAS_DGUS_LCD, EXTENSIBLE_UI) + #define LCD_SERIAL_TX_BUFFER_FREE() LCD_SERIAL.availableForWrite() +#endif -protected: - usart_rx_callback_t _rx_callback; - #if ENABLED(EMERGENCY_PARSER) - EmergencyParser::State emergency_state; - #endif -}; +#if ENABLED(SERIAL_DMA) -extern MarlinSerial MSerial1; -extern MarlinSerial MSerial2; -extern MarlinSerial MSerial3; -extern MarlinSerial MSerial4; -extern MarlinSerial MSerial5; -extern MarlinSerial MSerial6; -extern MarlinSerial MSerial7; -extern MarlinSerial MSerial8; -extern MarlinSerial MSerial9; -extern MarlinSerial MSerial10; -extern MarlinSerial MSerialLP1; + struct MarlinSerial : public HAL_HardwareSerial { + MarlinSerial(void *peripheral) : HAL_HardwareSerial(peripheral) { } + void begin(unsigned long baud, uint8_t config); + inline void begin(unsigned long baud) { begin(baud, SERIAL_8N1); } + }; + +#else // Arduino non-DMA + + typedef void (*usart_rx_callback_t)(serial_t * obj); + + struct MarlinSerial : public HardwareSerial { + MarlinSerial(void *peripheral, usart_rx_callback_t rx_callback) + : HardwareSerial(peripheral), _rx_callback(rx_callback) { } + + void begin(unsigned long baud, uint8_t config); + 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; + }; + +#endif + +typedef Serial1Class MSerialT; +extern MSerialT MSerial1; +extern MSerialT MSerial2; +extern MSerialT MSerial3; +extern MSerialT MSerial4; +extern MSerialT MSerial5; +extern MSerialT MSerial6; +extern MSerialT MSerial7; +extern MSerialT MSerial8; +extern MSerialT MSerial9; +extern MSerialT MSerial10; +extern MSerialT MSerialLP1; diff --git a/Marlin/src/HAL/STM32/MinSerial.cpp b/Marlin/src/HAL/STM32/MinSerial.cpp new file mode 100644 index 0000000000..a27bc35a14 --- /dev/null +++ b/Marlin/src/HAL/STM32/MinSerial.cpp @@ -0,0 +1,155 @@ +/** + * 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 . + * + */ +#include "../platforms.h" + +#ifdef HAL_STM32 + +#include "../../inc/MarlinConfigPre.h" + +#if ENABLED(POSTMORTEM_DEBUGGING) + +#include "../shared/MinSerial.h" + +/* Instruction Synchronization Barrier */ +#define isb() __asm__ __volatile__ ("isb" : : : "memory") + +/* Data Synchronization Barrier */ +#define dsb() __asm__ __volatile__ ("dsb" : : : "memory") + +// Dumb mapping over the registers of a USART device on STM32 +struct USARTMin { + volatile uint32_t SR; + volatile uint32_t DR; + volatile uint32_t BRR; + volatile uint32_t CR1; + volatile uint32_t CR2; +}; + +#if WITHIN(SERIAL_PORT, 1, 9) + // Depending on the CPU, the serial port is different for USART1 + static const uintptr_t regsAddr[] = { + TERN(STM32F1xx, 0x40013800, 0x40011000), // USART1 + 0x40004400, // USART2 + 0x40004800, // USART3 + 0x40004C00, // UART4_BASE + 0x40005000, // UART5_BASE + 0x40011400, // USART6 + 0x40007800, // UART7_BASE + 0x40007C00, // UART8_BASE + 0x40011800 // UART9_BASE + }; + static USARTMin * regs = (USARTMin*)regsAddr[SERIAL_PORT - 1]; +#endif + +static void TXBegin() { + #if !WITHIN(SERIAL_PORT, 1, 6) + #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 + // This is common between STM32F1/STM32F2 and STM32F4 + const int nvicUART[] = { /* NVIC_USART1 */ 37, /* NVIC_USART2 */ 38, /* NVIC_USART3 */ 39, /* NVIC_UART4 */ 52, /* NVIC_UART5 */ 53, /* NVIC_USART6 */ 71 }; + int nvicIndex = nvicUART[SERIAL_PORT - 1]; + + struct NVICMin { + volatile uint32_t ISER[32]; + volatile uint32_t ICER[32]; + }; + + NVICMin *nvicBase = (NVICMin*)0xE000E100; + SBI32(nvicBase->ICER[nvicIndex >> 5], nvicIndex & 0x1F); + + // We NEED memory barriers to ensure Interrupts are actually disabled! + // ( https://dzone.com/articles/nvic-disabling-interrupts-on-arm-cortex-m-and-the ) + dsb(); + isb(); + + // Example for USART1 disable: (RCC->APB2ENR &= ~(RCC_APB2ENR_USART1EN)) + // Too difficult to reimplement here, let's query the STM32duino macro here + #if SERIAL_PORT == 1 + __HAL_RCC_USART1_CLK_DISABLE(); + __HAL_RCC_USART1_CLK_ENABLE(); + #elif SERIAL_PORT == 2 + __HAL_RCC_USART2_CLK_DISABLE(); + __HAL_RCC_USART2_CLK_ENABLE(); + #elif SERIAL_PORT == 3 + __HAL_RCC_USART3_CLK_DISABLE(); + __HAL_RCC_USART3_CLK_ENABLE(); + #elif SERIAL_PORT == 4 + __HAL_RCC_UART4_CLK_DISABLE(); // BEWARE: UART4 and not USART4 here + __HAL_RCC_UART4_CLK_ENABLE(); + #elif SERIAL_PORT == 5 + __HAL_RCC_UART5_CLK_DISABLE(); // BEWARE: UART5 and not USART5 here + __HAL_RCC_UART5_CLK_ENABLE(); + #elif SERIAL_PORT == 6 + __HAL_RCC_USART6_CLK_DISABLE(); + __HAL_RCC_USART6_CLK_ENABLE(); + #endif + + uint32_t brr = regs->BRR; + regs->CR1 = 0; // Reset the USART + regs->CR2 = 0; // 1 stop bit + + // If we don't touch the BRR (baudrate register), we don't need to recompute. + regs->BRR = brr; + + regs->CR1 = _BV(3) | _BV(13); // 8 bits, no parity, 1 stop bit (TE | UE) + #endif +} + +// A SW memory barrier, to ensure GCC does not overoptimize loops +#define sw_barrier() __asm__ volatile("": : :"memory"); +static void TX(char c) { + #if WITHIN(SERIAL_PORT, 1, 9) + constexpr uint32_t usart_sr_txe = _BV(7); + while (!(regs->SR & usart_sr_txe)) { + hal.watchdog_refresh(); + sw_barrier(); + } + regs->DR = c; + #else + // Let's hope a mystical guru will fix this, one day by writing interrupt-free USB CDC ACM code (or, at least, by polling the registers since interrupt will be queued but will never trigger) + // For now, it's completely lost to oblivion. + #endif +} + +void install_min_serial() { + HAL_min_serial_init = &TXBegin; + HAL_min_serial_out = &TX; +} + +#if NONE(DYNAMIC_VECTORTABLE, STM32F0xx, STM32G0xx) // Cortex M0 can't jump to a symbol that's too far from the current function, so we work around this in exception_arm.cpp + extern "C" { + __attribute__((naked)) 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 + +#endif // POSTMORTEM_DEBUGGING +#endif // HAL_STM32 diff --git a/Marlin/src/HAL/STM32/README.md b/Marlin/src/HAL/STM32/README.md index 7680df6654..cf8aa50d50 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/Sd2Card_sdio_stm32duino.cpp b/Marlin/src/HAL/STM32/Sd2Card_sdio_stm32duino.cpp deleted file mode 100644 index 6e73e87c21..0000000000 --- a/Marlin/src/HAL/STM32/Sd2Card_sdio_stm32duino.cpp +++ /dev/null @@ -1,321 +0,0 @@ -/** - * Marlin 3D Printer Firmware - * Copyright (c) 2020 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(SDIO_SUPPORT) && !defined(STM32GENERIC) - -#include -#include - -#if NONE(STM32F103xE, STM32F103xG, STM32F4xx, STM32F7xx) - #error "ERROR - Only STM32F103xE, STM32F103xG, STM32F4xx or STM32F7xx CPUs supported" -#endif - -#ifdef USBD_USE_CDC_COMPOSITE - - // use USB drivers - - extern "C" { int8_t SD_MSC_Read(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len); - int8_t SD_MSC_Write(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len); - extern SD_HandleTypeDef hsd; - } - - bool SDIO_Init() { - return hsd.State == HAL_SD_STATE_READY; // return pass/fail status - } - - bool SDIO_ReadBlock(uint32_t block, uint8_t *src) { - int8_t status = SD_MSC_Read(0, (uint8_t*)src, block, 1); // read one 512 byte block - return (bool) status; - } - - bool SDIO_WriteBlock(uint32_t block, const uint8_t *src) { - int8_t status = SD_MSC_Write(0, (uint8_t*)src, block, 1); // write one 512 byte block - return (bool) status; - } - -#else // !USBD_USE_CDC_COMPOSITE - - // use local drivers - #if defined(STM32F103xE) || defined(STM32F103xG) - #include - #include - #elif defined(STM32F4xx) - #include - #include - #include - #include - #elif defined(STM32F7xx) - #include - #include - #include - #include - #else - #error "ERROR - Only STM32F103xE, STM32F103xG, STM32F4xx or STM32F7xx CPUs supported" - #endif - - SD_HandleTypeDef hsd; // create SDIO structure - - /* - SDIO_INIT_CLK_DIV is 118 - SDIO clock frequency is 48MHz / (TRANSFER_CLOCK_DIV + 2) - SDIO init clock frequency should not exceed 400KHz = 48MHz / (118 + 2) - - Default TRANSFER_CLOCK_DIV is 2 (118 / 40) - Default SDIO clock frequency is 48MHz / (2 + 2) = 12 MHz - This might be too fast for stable SDIO operations - - MKS Robin board seems to have stable SDIO with BusWide 1bit and ClockDiv 8 i.e. 4.8MHz SDIO clock frequency - Additional testing is required as there are clearly some 4bit initialization problems - - Add -DTRANSFER_CLOCK_DIV=8 to build parameters to improve SDIO stability - */ - - #ifndef TRANSFER_CLOCK_DIV - #define TRANSFER_CLOCK_DIV (uint8_t(SDIO_INIT_CLK_DIV) / 40) - #endif - - #ifndef USBD_OK - #define USBD_OK 0 - #endif - - void go_to_transfer_speed() { - SD_InitTypeDef Init; - - /* Default SDIO peripheral configuration for SD card initialization */ - Init.ClockEdge = hsd.Init.ClockEdge; - Init.ClockBypass = hsd.Init.ClockBypass; - Init.ClockPowerSave = hsd.Init.ClockPowerSave; - Init.BusWide = hsd.Init.BusWide; - Init.HardwareFlowControl = hsd.Init.HardwareFlowControl; - Init.ClockDiv = TRANSFER_CLOCK_DIV; - - /* Initialize SDIO peripheral interface with default configuration */ - SDIO_Init(hsd.Instance, Init); - } - - void SD_LowLevel_Init(void) { - uint32_t tempreg; - - __HAL_RCC_SDIO_CLK_ENABLE(); - __HAL_RCC_GPIOC_CLK_ENABLE(); //enable GPIO clocks - __HAL_RCC_GPIOD_CLK_ENABLE(); //enable GPIO clocks - - GPIO_InitTypeDef GPIO_InitStruct; - - GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; - GPIO_InitStruct.Pull = 1; //GPIO_NOPULL; - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; - - #if DISABLED(STM32F1xx) - GPIO_InitStruct.Alternate = GPIO_AF12_SDIO; - #endif - - GPIO_InitStruct.Pin = GPIO_PIN_8 | GPIO_PIN_12; // D0 & SCK - HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); - - #if PINS_EXIST(SDIO_D1, SDIO_D2, SDIO_D3) // define D1-D3 only if have a four bit wide SDIO bus - GPIO_InitStruct.Pin = GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11; // D1-D3 - HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); - #endif - - // Configure PD.02 CMD line - GPIO_InitStruct.Pin = GPIO_PIN_2; - HAL_GPIO_Init(GPIOD, &GPIO_InitStruct); - - - #if DISABLED(STM32F1xx) - // TODO: use __HAL_RCC_SDIO_RELEASE_RESET() and __HAL_RCC_SDIO_CLK_ENABLE(); - RCC->APB2RSTR &= ~RCC_APB2RSTR_SDIORST_Msk; // take SDIO out of reset - RCC->APB2ENR |= RCC_APB2RSTR_SDIORST_Msk; // enable SDIO clock - // Enable the DMA2 Clock - #endif - - //Initialize the SDIO (with initial <400Khz Clock) - tempreg = 0; //Reset value - tempreg |= SDIO_CLKCR_CLKEN; // Clock enabled - tempreg |= (uint32_t)0x76; // Clock Divider. Clock = 48000 / (118 + 2) = 400Khz - // Keep the rest at 0 => HW_Flow Disabled, Rising Clock Edge, Disable CLK ByPass, Bus Width = 0, Power save Disable - SDIO->CLKCR = tempreg; - - // Power up the SDIO - SDIO->POWER = 0x03; - } - - void HAL_SD_MspInit(SD_HandleTypeDef *hsd) { // application specific init - UNUSED(hsd); /* Prevent unused argument(s) compilation warning */ - __HAL_RCC_SDIO_CLK_ENABLE(); // turn on SDIO clock - } - - constexpr uint8_t SD_RETRY_COUNT = TERN(SD_CHECK_AND_RETRY, 3, 1); - - bool SDIO_Init() { - //init SDIO and get SD card info - - uint8_t retryCnt = SD_RETRY_COUNT; - - bool status; - hsd.Instance = SDIO; - hsd.State = (HAL_SD_StateTypeDef) 0; // HAL_SD_STATE_RESET - - /* - hsd.Init.ClockEdge = SDIO_CLOCK_EDGE_RISING; - hsd.Init.ClockBypass = SDIO_CLOCK_BYPASS_DISABLE; - hsd.Init.ClockPowerSave = SDIO_CLOCK_POWER_SAVE_DISABLE; - hsd.Init.BusWide = SDIO_BUS_WIDE_1B; - hsd.Init.HardwareFlowControl = SDIO_HARDWARE_FLOW_CONTROL_DISABLE; - hsd.Init.ClockDiv = 8; - */ - - SD_LowLevel_Init(); - - uint8_t retry_Cnt = retryCnt; - for (;;) { - TERN_(USE_WATCHDOG, HAL_watchdog_refresh()); - status = (bool) HAL_SD_Init(&hsd); - if (!status) break; - if (!--retry_Cnt) return false; // return failing status if retries are exhausted - } - - go_to_transfer_speed(); - - #if PINS_EXIST(SDIO_D1, SDIO_D2, SDIO_D3) // go to 4 bit wide mode if pins are defined - retry_Cnt = retryCnt; - for (;;) { - TERN_(USE_WATCHDOG, HAL_watchdog_refresh()); - if (!HAL_SD_ConfigWideBusOperation(&hsd, SDIO_BUS_WIDE_4B)) break; // some cards are only 1 bit wide so a pass here is not required - if (!--retry_Cnt) break; - } - if (!retry_Cnt) { // wide bus failed, go back to one bit wide mode - hsd.State = (HAL_SD_StateTypeDef) 0; // HAL_SD_STATE_RESET - SD_LowLevel_Init(); - retry_Cnt = retryCnt; - for (;;) { - TERN_(USE_WATCHDOG, HAL_watchdog_refresh()); - status = (bool) HAL_SD_Init(&hsd); - if (!status) break; - if (!--retry_Cnt) return false; // return failing status if retries are exhausted - } - } - #endif - - return true; - } - /* - void init_SDIO_pins(void) { - GPIO_InitTypeDef GPIO_InitStruct = {0}; - - // SDIO GPIO Configuration - // PC8 ------> SDIO_D0 - // PC12 ------> SDIO_CK - // PD2 ------> SDIO_CMD - - GPIO_InitStruct.Pin = GPIO_PIN_8; - GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; - GPIO_InitStruct.Pull = GPIO_NOPULL; - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; - GPIO_InitStruct.Alternate = GPIO_AF12_SDIO; - HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); - - GPIO_InitStruct.Pin = GPIO_PIN_12; - GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; - GPIO_InitStruct.Pull = GPIO_NOPULL; - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; - GPIO_InitStruct.Alternate = GPIO_AF12_SDIO; - HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); - - GPIO_InitStruct.Pin = GPIO_PIN_2; - GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; - GPIO_InitStruct.Pull = GPIO_NOPULL; - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; - GPIO_InitStruct.Alternate = GPIO_AF12_SDIO; - HAL_GPIO_Init(GPIOD, &GPIO_InitStruct); - } - */ - //bool SDIO_init() { return (bool) (SD_SDIO_Init() ? 1 : 0);} - //bool SDIO_Init_C() { return (bool) (SD_SDIO_Init() ? 1 : 0);} - - bool SDIO_ReadBlock(uint32_t block, uint8_t *dst) { - hsd.Instance = SDIO; - uint8_t retryCnt = SD_RETRY_COUNT; - - bool status; - for (;;) { - TERN_(USE_WATCHDOG, HAL_watchdog_refresh()); - status = (bool) HAL_SD_ReadBlocks(&hsd, (uint8_t*)dst, block, 1, 1000); // read one 512 byte block with 500mS timeout - status |= (bool) HAL_SD_GetCardState(&hsd); // make sure all is OK - if (!status) break; // return passing status - if (!--retryCnt) break; // return failing status if retries are exhausted - } - return status; - - /* - return (bool) ((status_read | status_card) ? 1 : 0); - - if (SDIO_GetCardState() != SDIO_CARD_TRANSFER) return false; - if (blockAddress >= SdCard.LogBlockNbr) return false; - if ((0x03 & (uint32_t)data)) return false; // misaligned data - - if (SdCard.CardType != CARD_SDHC_SDXC) { blockAddress *= 512U; } - - if (!SDIO_CmdReadSingleBlock(blockAddress)) { - SDIO_CLEAR_FLAG(SDIO_ICR_CMD_FLAGS); - dma_disable(SDIO_DMA_DEV, SDIO_DMA_CHANNEL); - return false; - } - - while (!SDIO_GET_FLAG(SDIO_STA_DATAEND | SDIO_STA_TRX_ERROR_FLAGS)) {} - - dma_disable(SDIO_DMA_DEV, SDIO_DMA_CHANNEL); - - if (SDIO->STA & SDIO_STA_RXDAVL) { - while (SDIO->STA & SDIO_STA_RXDAVL) (void)SDIO->FIFO; - SDIO_CLEAR_FLAG(SDIO_ICR_CMD_FLAGS | SDIO_ICR_DATA_FLAGS); - return false; - } - - if (SDIO_GET_FLAG(SDIO_STA_TRX_ERROR_FLAGS)) { - SDIO_CLEAR_FLAG(SDIO_ICR_CMD_FLAGS | SDIO_ICR_DATA_FLAGS); - return false; - } - SDIO_CLEAR_FLAG(SDIO_ICR_CMD_FLAGS | SDIO_ICR_DATA_FLAGS); - */ - - return true; - } - - bool SDIO_WriteBlock(uint32_t block, const uint8_t *src) { - hsd.Instance = SDIO; - uint8_t retryCnt = SD_RETRY_COUNT; - bool status; - for (;;) { - status = (bool) HAL_SD_WriteBlocks(&hsd, (uint8_t*)src, block, 1, 500); // write one 512 byte block with 500mS timeout - status |= (bool) HAL_SD_GetCardState(&hsd); // make sure all is OK - if (!status) break; // return passing status - if (!--retryCnt) break; // return failing status if retries are exhausted - } - return status; - } - -#endif // !USBD_USE_CDC_COMPOSITE -#endif // SDIO_SUPPORT diff --git a/Marlin/src/HAL/STM32/Servo.cpp b/Marlin/src/HAL/STM32/Servo.cpp index 1cf117a056..eb535b1eb1 100644 --- a/Marlin/src/HAL/STM32/Servo.cpp +++ b/Marlin/src/HAL/STM32/Servo.cpp @@ -4,7 +4,6 @@ * * Based on Sprinter and grbl. * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm - * Copyright (c) 2017 Victor Perez * * 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 @@ -20,7 +19,9 @@ * along with this program. If not, see . * */ -#if defined(ARDUINO_ARCH_STM32) && !defined(STM32GENERIC) +#include "../platforms.h" + +#ifdef HAL_STM32 #include "../../inc/MarlinConfig.h" @@ -37,9 +38,9 @@ static_assert(COUNT(servoDelay) == NUM_SERVOS, "SERVO_DELAY must be an array NUM // 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(), TIM_IRQ_PRIO, TIM_IRQ_SUBPRIO); -// This must be called after the STM32 Servo class has intialized 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 +// 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'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); @@ -107,4 +108,4 @@ void libServo::setInterruptPriority(uint32_t preemptPriority, uint32_t subPriori } #endif // HAS_SERVOS -#endif // ARDUINO_ARCH_STM32 && !STM32GENERIC +#endif // HAL_STM32 diff --git a/Marlin/src/HAL/STM32/Servo.h b/Marlin/src/HAL/STM32/Servo.h index 1527e753b6..95ecb5d977 100644 --- a/Marlin/src/HAL/STM32/Servo.h +++ b/Marlin/src/HAL/STM32/Servo.h @@ -4,7 +4,6 @@ * * Based on Sprinter and grbl. * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm - * Copyright (c) 2017 Victor Perez * * 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 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 0000000000..68c6430538 --- /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/eeprom_bl24cxx.cpp b/Marlin/src/HAL/STM32/eeprom/eeprom_bl24cxx.cpp new file mode 100644 index 0000000000..8240f15d3a --- /dev/null +++ b/Marlin/src/HAL/STM32/eeprom/eeprom_bl24cxx.cpp @@ -0,0 +1,84 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2020 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 HAL_STM32 + +/** + * PersistentStore for Arduino-style EEPROM interface + * with simple implementations supplied by Marlin. + */ + +#include "../../../inc/MarlinConfig.h" + +#if ENABLED(IIC_BL24CXX_EEPROM) + +#include "../../shared/eeprom_if.h" +#include "../../shared/eeprom_api.h" + +// +// PersistentStore +// + +#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); + 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 { + 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 // HAL_STM32 diff --git a/Marlin/src/HAL/STM32/eeprom_flash.cpp b/Marlin/src/HAL/STM32/eeprom/eeprom_flash.cpp similarity index 59% rename from Marlin/src/HAL/STM32/eeprom_flash.cpp rename to Marlin/src/HAL/STM32/eeprom/eeprom_flash.cpp index 0933b9f4e8..5a9062e956 100644 --- a/Marlin/src/HAL/STM32/eeprom_flash.cpp +++ b/Marlin/src/HAL/STM32/eeprom/eeprom_flash.cpp @@ -1,10 +1,9 @@ /** * Marlin 3D Printer Firmware - * * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com - * Copyright (c) 2015-2016 Nico Tonnhofer wurstnase.reprap@gmail.com - * Copyright (c) 2016 Victor Perez victor_pv@hotmail.com + * + * 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 @@ -20,22 +19,19 @@ * along with this program. If not, see . * */ -#if defined(ARDUINO_ARCH_STM32) && !defined(STM32GENERIC) +#include "../../platforms.h" -#include "../../inc/MarlinConfig.h" +#ifdef HAL_STM32 + +#include "../../../inc/MarlinConfig.h" #if ENABLED(FLASH_EEPROM_EMULATION) -#include "../shared/eeprom_api.h" +#include "../../shared/eeprom_api.h" -#if HAS_SERVOS - #include "Servo.h" - #define PAUSE_SERVO_OUTPUT() libServo::pause_all_servos() - #define RESUME_SERVO_OUTPUT() libServo::resume_all_servos() -#else - #define PAUSE_SERVO_OUTPUT() - #define RESUME_SERVO_OUTPUT() -#endif +// Better: "utility/stm32_eeprom.h", but only after updating stm32duino to 2.0.0 +// Use EEPROM.h for compatibility, for now. +#include /** * The STM32 HAL supports chips that deal with "pages" and some with "sectors" and some that @@ -54,10 +50,10 @@ #if ENABLED(FLASH_EEPROM_LEVELING) - #include "stm32_def.h" + #include #define DEBUG_OUT ENABLED(EEPROM_CHITCHAT) - #include "src/core/debug_out.h" + #include "../../../core/debug_out.h" #ifndef MARLIN_EEPROM_SIZE #define MARLIN_EEPROM_SIZE 0x1000 // 4KB @@ -67,22 +63,24 @@ #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 - #define FLASH_ADDRESS_START (FLASH_END - ((FLASH_SECTOR_TOTAL - (FLASH_SECTOR)) * (FLASH_UNIT_SIZE)) + 1) + #ifndef FLASH_ADDRESS_START + #define FLASH_ADDRESS_START (FLASH_END - ((FLASH_SECTOR_TOTAL - (FLASH_SECTOR)) * (FLASH_UNIT_SIZE)) + 1) + #endif #define FLASH_ADDRESS_END (FLASH_ADDRESS_START + FLASH_UNIT_SIZE - 1) #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) @@ -90,30 +88,32 @@ 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"); static_assert(IS_POWER_OF_2(FLASH_UNIT_SIZE), "FLASH_UNIT_SIZE should be a power of 2, please check your chip's spec sheet"); -#endif +#endif // FLASH_EEPROM_LEVELING static bool eeprom_data_written = false; #ifndef MARLIN_EEPROM_SIZE #define MARLIN_EEPROM_SIZE size_t(E2END + 1) #endif -size_t PersistentStore::capacity() { return MARLIN_EEPROM_SIZE; } +size_t PersistentStore::capacity() { return MARLIN_EEPROM_SIZE - eeprom_exclude_size; } bool PersistentStore::access_start() { + EEPROM.begin(); // Avoid STM32 EEPROM.h warning (do nothing) + #if ENABLED(FLASH_EEPROM_LEVELING) if (current_slot == -1 || eeprom_data_written) { // This must be the first time since power on that we have accessed the storage, or someone // loaded and called write_data and never called access_finish. // Lets go looking for the slot that holds our configuration. - if (eeprom_data_written) DEBUG_ECHOLN("Dangling EEPROM write_data"); + if (eeprom_data_written) DEBUG_ECHOLNPGM("Dangling EEPROM write_data"); uint32_t address = FLASH_ADDRESS_START; while (address <= FLASH_ADDRESS_END) { uint32_t address_value = (*(__IO uint32_t*)address); @@ -124,15 +124,15 @@ bool PersistentStore::access_start() { address += sizeof(uint32_t); } if (current_slot == -1) { - // We didn't find anything, so we'll just intialize to empty - for (int i = 0; i < MARLIN_EEPROM_SIZE; i++) ram_eeprom[i] = EMPTY_UINT8; + // We didn't find anything, so we'll just initialize to empty + 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]; - DEBUG_ECHOLNPAIR("EEPROM loaded from slot ", current_slot, "."); + 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,71 +174,92 @@ bool PersistentStore::access_finish() { EraseInitStruct.NbSectors = 1; current_slot = EEPROM_SLOTS - 1; - UNLOCK_FLASH(); - PAUSE_SERVO_OUTPUT(); - DISABLE_ISRS(); + 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(); status = HAL_FLASHEx_Erase(&EraseInitStruct, &SectorError); - ENABLE_ISRS(); - RESUME_SERVO_OUTPUT(); + hal.isr_on(); + TERN_(HAS_PAUSE_SERVO_OUTPUT, RESUME_SERVO_OUTPUT()); if (status != HAL_OK) { - DEBUG_ECHOLNPAIR("HAL_FLASHEx_Erase=", status); - DEBUG_ECHOLNPAIR("GetError=", HAL_FLASH_GetError()); - DEBUG_ECHOLNPAIR("SectorError=", SectorError); - LOCK_FLASH(); + DEBUG_ECHOLNPGM("HAL_FLASHEx_Erase=", status); + DEBUG_ECHOLNPGM("GetError=", HAL_FLASH_GetError()); + DEBUG_ECHOLNPGM("SectorError=", SectorError); + 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; - uint32_t address = SLOT_ADDRESS(current_slot); - uint32_t address_end = address + MARLIN_EEPROM_SIZE; - uint32_t data = 0; + uint32_t offset = 0, + address = SLOT_ADDRESS(current_slot), + address_end = address + MARLIN_EEPROM_SIZE; bool success = true; while (address < address_end) { - memcpy(&data, ram_eeprom + offset, sizeof(uint32_t)); - 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_ECHOLNPAIR("HAL_FLASH_Program=", status); - DEBUG_ECHOLNPAIR("GetError=", HAL_FLASH_GetError()); - DEBUG_ECHOLNPAIR("address=", address); + DEBUG_ECHOLNPGM("HAL_FLASH_Program=", status); + DEBUG_ECHOLNPGM("GetError=", HAL_FLASH_GetError()); + DEBUG_ECHOLNPGM("address=", address); success = false; break; } } - LOCK_FLASH(); + if (flash_unlocked) { + HAL_FLASH_Lock(); + flash_unlocked = false; + } if (success) { eeprom_data_written = false; - DEBUG_ECHOLNPAIR("EEPROM saved to slot ", current_slot, "."); + DEBUG_ECHOLNPGM("EEPROM saved to slot ", current_slot, "."); } return success; - #else + #else // !FLASH_EEPROM_LEVELING + // The following was written for the STM32F4 but may work with other MCUs as well. // Most STM32F4 flash does not allow reading from flash during erase operations. // This takes about a second on a STM32F407 with a 128kB sector used as EEPROM. // Interrupts during this time can have unpredictable results, such as killing Servo // output. Servo output still glitches with interrupts disabled, but recovers after the // erase. - PAUSE_SERVO_OUTPUT(); - DISABLE_ISRS(); + TERN_(HAS_PAUSE_SERVO_OUTPUT, PAUSE_SERVO_OUTPUT()); + hal.isr_off(); eeprom_buffer_flush(); - ENABLE_ISRS(); - RESUME_SERVO_OUTPUT(); + hal.isr_on(); + TERN_(HAS_PAUSE_SERVO_OUTPUT, RESUME_SERVO_OUTPUT()); eeprom_data_written = false; - #endif + + #endif // !FLASH_EEPROM_LEVELING } return true; @@ -243,14 +268,15 @@ bool PersistentStore::access_finish() { bool PersistentStore::write_data(int &pos, const uint8_t *value, size_t size, uint16_t *crc) { while (size--) { uint8_t v = *value; + const int p = REAL_EEPROM_ADDR(pos); #if ENABLED(FLASH_EEPROM_LEVELING) - if (v != ram_eeprom[pos]) { - ram_eeprom[pos] = v; + if (v != ram_eeprom[p]) { + ram_eeprom[p] = v; eeprom_data_written = true; } #else - if (v != eeprom_buffered_read_byte(pos)) { - eeprom_buffered_write_byte(pos, v); + if (v != eeprom_buffered_read_byte(p)) { + eeprom_buffered_write_byte(p, v); eeprom_data_written = true; } #endif @@ -261,9 +287,10 @@ bool PersistentStore::write_data(int &pos, const uint8_t *value, size_t size, ui return false; } -bool PersistentStore::read_data(int &pos, uint8_t* value, size_t size, uint16_t *crc, const bool writing/*=true*/) { +bool PersistentStore::read_data(int &pos, uint8_t *value, size_t size, uint16_t *crc, const bool writing/*=true*/) { do { - const uint8_t c = TERN(FLASH_EEPROM_LEVELING, ram_eeprom[pos], eeprom_buffered_read_byte(pos)); + const int p = REAL_EEPROM_ADDR(pos); + const uint8_t c = TERN(FLASH_EEPROM_LEVELING, ram_eeprom[p], eeprom_buffered_read_byte(p)); if (writing) *value = c; crc16(crc, &c, 1); pos++; @@ -273,4 +300,4 @@ bool PersistentStore::read_data(int &pos, uint8_t* value, size_t size, uint16_t } #endif // FLASH_EEPROM_EMULATION -#endif // ARDUINO_ARCH_STM32 && !STM32GENERIC +#endif // HAL_STM32 diff --git a/Marlin/src/HAL/STM32/eeprom/eeprom_if_iic.cpp b/Marlin/src/HAL/STM32/eeprom/eeprom_if_iic.cpp new file mode 100644 index 0000000000..2733c8f283 --- /dev/null +++ b/Marlin/src/HAL/STM32/eeprom/eeprom_if_iic.cpp @@ -0,0 +1,56 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2020 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 HAL_STM32 + +/** + * Platform-independent Arduino functions for I2C EEPROM. + * Enable USE_SHARED_EEPROM if not supplied by the framework. + */ + +#include "../../../inc/MarlinConfig.h" + +#if ENABLED(IIC_BL24CXX_EEPROM) + +#include "../../../libs/BL24CXX.h" +#include "../../shared/eeprom_if.h" + +void eeprom_init() { BL24CXX::init(); } + +// ------------------------ +// Public functions +// ------------------------ + +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 // HAL_STM32 diff --git a/Marlin/src/HAL/STM32/eeprom_sdcard.cpp b/Marlin/src/HAL/STM32/eeprom/eeprom_sdcard.cpp similarity index 83% rename from Marlin/src/HAL/STM32/eeprom_sdcard.cpp rename to Marlin/src/HAL/STM32/eeprom/eeprom_sdcard.cpp index 711a83ed5b..64da3745d1 100644 --- a/Marlin/src/HAL/STM32/eeprom_sdcard.cpp +++ b/Marlin/src/HAL/STM32/eeprom/eeprom_sdcard.cpp @@ -20,25 +20,27 @@ * */ +#include "../../platforms.h" + +#ifdef HAL_STM32 + /** * Implementation of EEPROM settings in SD Card */ -#if defined(ARDUINO_ARCH_STM32) && !defined(STM32GENERIC) - -#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" #ifndef MARLIN_EEPROM_SIZE #define MARLIN_EEPROM_SIZE 0x1000 // 4KB #endif -size_t PersistentStore::capacity() { return MARLIN_EEPROM_SIZE; } +size_t PersistentStore::capacity() { return MARLIN_EEPROM_SIZE - eeprom_exclude_size; } #define _ALIGN(x) __attribute__ ((aligned(x))) static char _ALIGN(4) HAL_eeprom_data[MARLIN_EEPROM_SIZE]; @@ -46,13 +48,13 @@ static char _ALIGN(4) HAL_eeprom_data[MARLIN_EEPROM_SIZE]; bool PersistentStore::access_start() { if (!card.isMounted()) return false; - SdFile file, root = card.getroot(); + MediaFile file, root = card.getroot(); if (!file.open(&root, EEPROM_FILENAME, O_RDONLY)) return true; 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; @@ -61,7 +63,7 @@ bool PersistentStore::access_start() { bool PersistentStore::access_finish() { if (!card.isMounted()) return false; - SdFile file, root = card.getroot(); + MediaFile file, root = card.getroot(); int bytes_written = 0; if (file.open(&root, EEPROM_FILENAME, O_CREAT | O_WRITE | O_TRUNC)) { bytes_written = file.write(HAL_eeprom_data, MARLIN_EEPROM_SIZE); @@ -78,7 +80,7 @@ bool PersistentStore::write_data(int &pos, const uint8_t *value, size_t size, ui return false; } -bool PersistentStore::read_data(int &pos, uint8_t* value, const size_t size, uint16_t *crc, const bool writing/*=true*/) { +bool PersistentStore::read_data(int &pos, uint8_t *value, const size_t size, uint16_t *crc, const bool writing/*=true*/) { for (size_t i = 0; i < size; i++) { uint8_t c = HAL_eeprom_data[pos + i]; if (writing) value[i] = c; @@ -89,4 +91,4 @@ bool PersistentStore::read_data(int &pos, uint8_t* value, const size_t size, uin } #endif // SDCARD_EEPROM_EMULATION -#endif // STM32 && !STM32GENERIC +#endif // HAL_STM32 diff --git a/Marlin/src/HAL/STM32/eeprom_sram.cpp b/Marlin/src/HAL/STM32/eeprom/eeprom_sram.cpp similarity index 78% rename from Marlin/src/HAL/STM32/eeprom_sram.cpp rename to Marlin/src/HAL/STM32/eeprom/eeprom_sram.cpp index 5f6f26f9c6..a9d62ec29c 100644 --- a/Marlin/src/HAL/STM32/eeprom_sram.cpp +++ b/Marlin/src/HAL/STM32/eeprom/eeprom_sram.cpp @@ -1,10 +1,9 @@ /** * Marlin 3D Printer Firmware - * * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com - * Copyright (c) 2015-2016 Nico Tonnhofer wurstnase.reprap@gmail.com - * Copyright (c) 2016 Victor Perez victor_pv@hotmail.com + * + * 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 @@ -20,19 +19,21 @@ * along with this program. If not, see . * */ -#if defined(ARDUINO_ARCH_STM32) && !defined(STM32GENERIC) +#include "../../platforms.h" -#include "../../inc/MarlinConfig.h" +#ifdef HAL_STM32 + +#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 #endif -size_t PersistentStore::capacity() { return MARLIN_EEPROM_SIZE; } +size_t PersistentStore::capacity() { return MARLIN_EEPROM_SIZE - eeprom_exclude_size; } bool PersistentStore::access_start() { return true; } bool PersistentStore::access_finish() { return true; } @@ -52,7 +53,7 @@ bool PersistentStore::write_data(int &pos, const uint8_t *value, size_t size, ui return false; } -bool PersistentStore::read_data(int &pos, uint8_t* value, size_t size, uint16_t *crc, const bool writing/*=true*/) { +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 = ( *(__IO uint8_t *)(BKPSRAM_BASE + ((uint8_t*)pos)) ); @@ -65,4 +66,4 @@ bool PersistentStore::read_data(int &pos, uint8_t* value, size_t size, uint16_t } #endif // SRAM_EEPROM_EMULATION -#endif // ARDUINO_ARCH_STM32 && !STM32GENERIC +#endif // HAL_STM32 diff --git a/Marlin/src/HAL/STM32_F4_F7/eeprom_wired.cpp b/Marlin/src/HAL/STM32/eeprom/eeprom_wired.cpp similarity index 63% rename from Marlin/src/HAL/STM32_F4_F7/eeprom_wired.cpp rename to Marlin/src/HAL/STM32/eeprom/eeprom_wired.cpp index c0d82dbd07..fa45e6c40d 100644 --- a/Marlin/src/HAL/STM32_F4_F7/eeprom_wired.cpp +++ b/Marlin/src/HAL/STM32/eeprom/eeprom_wired.cpp @@ -1,10 +1,9 @@ /** * Marlin 3D Printer Firmware - * * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com - * Copyright (c) 2015-2016 Nico Tonnhofer wurstnase.reprap@gmail.com - * Copyright (c) 2016 Victor Perez victor_pv@hotmail.com + * + * 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 @@ -20,9 +19,11 @@ * along with this program. If not, see . * */ -#if defined(STM32GENERIC) && (defined(STM32F4) || defined(STM32F7)) +#include "../../platforms.h" -#include "../../inc/MarlinConfig.h" +#ifdef HAL_STM32 + +#include "../../../inc/MarlinConfig.h" #if USE_WIRED_EEPROM @@ -31,25 +32,25 @@ * 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." + #define MARLIN_EEPROM_SIZE size_t(E2END + 1) #endif -size_t PersistentStore::capacity() { return MARLIN_EEPROM_SIZE; } +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 * const p = (uint8_t * const)pos; uint8_t v = *value; - // EEPROM has only ~100,000 write cycles, - // so only write bytes that have changed! - if (v != eeprom_read_byte(p)) { + uint8_t * const p = (uint8_t * const)REAL_EEPROM_ADDR(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; @@ -62,9 +63,10 @@ bool PersistentStore::write_data(int &pos, const uint8_t *value, size_t size, ui return false; } -bool PersistentStore::read_data(int &pos, uint8_t* value, size_t size, uint16_t *crc, const bool writing/*=true*/) { +bool PersistentStore::read_data(int &pos, uint8_t *value, size_t size, uint16_t *crc, const bool writing/*=true*/) { do { - uint8_t c = eeprom_read_byte((uint8_t*)pos); + // Read from either external EEPROM, program flash or Backup SRAM + const uint8_t c = eeprom_read_byte((uint8_t*)REAL_EEPROM_ADDR(pos)); if (writing) *value = c; crc16(crc, &c, 1); pos++; @@ -74,4 +76,4 @@ bool PersistentStore::read_data(int &pos, uint8_t* value, size_t size, uint16_t } #endif // USE_WIRED_EEPROM -#endif // STM32GENERIC && (STM32F4 || STM32F7) +#endif // HAL_STM32 diff --git a/Marlin/src/HAL/STM32/endstop_interrupts.h b/Marlin/src/HAL/STM32/endstop_interrupts.h index fdff8cc644..e17b8a4c8e 100644 --- a/Marlin/src/HAL/STM32/endstop_interrupts.h +++ b/Marlin/src/HAL/STM32/endstop_interrupts.h @@ -4,7 +4,6 @@ * * Based on Sprinter and grbl. * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm - * Copyright (c) 2017 Victor Perez * * 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 @@ -29,21 +28,34 @@ void endstop_ISR() { endstops.update(); } void setup_endstop_interrupts() { #define _ATTACH(P) attachInterrupt(P, endstop_ISR, CHANGE) - TERN_(HAS_X_MAX, _ATTACH(X_MAX_PIN)); - TERN_(HAS_X_MIN, _ATTACH(X_MIN_PIN)); - TERN_(HAS_Y_MAX, _ATTACH(Y_MAX_PIN)); - TERN_(HAS_Y_MIN, _ATTACH(Y_MIN_PIN)); - TERN_(HAS_Z_MAX, _ATTACH(Z_MAX_PIN)); - TERN_(HAS_Z_MIN, _ATTACH(Z_MIN_PIN)); - TERN_(HAS_X2_MAX, _ATTACH(X2_MAX_PIN)); - TERN_(HAS_X2_MIN, _ATTACH(X2_MIN_PIN)); - TERN_(HAS_Y2_MAX, _ATTACH(Y2_MAX_PIN)); - TERN_(HAS_Y2_MIN, _ATTACH(Y2_MIN_PIN)); - TERN_(HAS_Z2_MAX, _ATTACH(Z2_MAX_PIN)); - TERN_(HAS_Z2_MIN, _ATTACH(Z2_MIN_PIN)); - TERN_(HAS_Z3_MAX, _ATTACH(Z3_MAX_PIN)); - TERN_(HAS_Z3_MIN, _ATTACH(Z3_MIN_PIN)); - TERN_(HAS_Z4_MAX, _ATTACH(Z4_MAX_PIN)); - TERN_(HAS_Z4_MIN, _ATTACH(Z4_MIN_PIN)); - TERN_(HAS_Z_MIN_PROBE_PIN, _ATTACH(Z_MIN_PROBE_PIN)); + 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/STM32/fast_pwm.cpp b/Marlin/src/HAL/STM32/fast_pwm.cpp new file mode 100644 index 0000000000..a0d8ecc612 --- /dev/null +++ b/Marlin/src/HAL/STM32/fast_pwm.cpp @@ -0,0 +1,88 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2020 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 HAL_STM32 + +#include "../../inc/MarlinConfig.h" + +// Array to support sticky frequency sets per timer +static uint16_t timer_freq[TIMER_NUM]; + +void MarlinHAL::set_pwm_duty(const pin_t pin, const uint16_t v, const uint16_t v_size/*=255*/, const bool invert/*=false*/) { + const uint16_t duty = invert ? v_size - v : v; + if (PWM_PIN(pin)) { + const PinName pin_name = digitalPinToPinName(pin); + TIM_TypeDef * const Instance = (TIM_TypeDef *)pinmap_peripheral(pin_name, PinMap_PWM); + + const timer_index_t index = get_timer_index(Instance); + const bool needs_freq = (HardwareTimer_Handle[index] == nullptr); + if (needs_freq) // A new instance must be set to the default frequency of PWM_FREQUENCY + HardwareTimer_Handle[index]->__this = new HardwareTimer((TIM_TypeDef *)pinmap_peripheral(pin_name, PinMap_PWM)); + + HardwareTimer * const HT = (HardwareTimer *)(HardwareTimer_Handle[index]->__this); + const uint32_t channel = STM_PIN_CHANNEL(pinmap_function(pin_name, PinMap_PWM)); + const TimerModes_t previousMode = HT->getMode(channel); + if (previousMode != TIMER_OUTPUT_COMPARE_PWM1) + HT->setMode(channel, TIMER_OUTPUT_COMPARE_PWM1, pin); + + if (needs_freq && timer_freq[index] == 0) // If the timer is unconfigured and no freq is set then default PWM_FREQUENCY + set_pwm_frequency(pin_name, PWM_FREQUENCY); // Set the frequency and save the value to the assigned index no. + + // Note the resolution is sticky here, the input can be upto 16 bits and that would require RESOLUTION_16B_COMPARE_FORMAT (16) + // If such a need were to manifest then we would need to calc the resolution based on the v_size parameter and add code for it. + HT->setCaptureCompare(channel, duty, RESOLUTION_8B_COMPARE_FORMAT); // Set the duty, the calc is done in the library :) + pinmap_pinout(pin_name, PinMap_PWM); // Make sure the pin output state is set. + if (previousMode != TIMER_OUTPUT_COMPARE_PWM1) HT->resume(); + } + else { + pinMode(pin, OUTPUT); + digitalWrite(pin, duty < v_size / 2 ? LOW : HIGH); + } +} + +void MarlinHAL::set_pwm_frequency(const pin_t pin, const uint16_t f_desired) { + if (!PWM_PIN(pin)) return; // Don't proceed if no hardware timer + const PinName pin_name = digitalPinToPinName(pin); + TIM_TypeDef * const Instance = (TIM_TypeDef *)pinmap_peripheral(pin_name, PinMap_PWM); // Get HAL timer instance + const timer_index_t index = get_timer_index(Instance); + + // Protect used timers. + #ifdef STEP_TIMER + if (index == TIMER_INDEX(STEP_TIMER)) return; + #endif + #ifdef TEMP_TIMER + if (index == TIMER_INDEX(TEMP_TIMER)) return; + #endif + #if defined(PULSE_TIMER) && MF_TIMER_PULSE != MF_TIMER_STEP + if (index == TIMER_INDEX(PULSE_TIMER)) return; + #endif + + if (HardwareTimer_Handle[index] == nullptr) // If frequency is set before duty we need to create a handle here. + HardwareTimer_Handle[index]->__this = new HardwareTimer((TIM_TypeDef *)pinmap_peripheral(pin_name, PinMap_PWM)); + HardwareTimer * const HT = (HardwareTimer *)(HardwareTimer_Handle[index]->__this); + HT->setOverflow(f_desired, HERTZ_FORMAT); + timer_freq[index] = f_desired; // Save the last frequency so duty will not set the default for this timer number. +} + +#endif // HAL_STM32 diff --git a/Marlin/src/HAL/STM32/fastio.cpp b/Marlin/src/HAL/STM32/fastio.cpp index 0d55579d88..f8501545a0 100644 --- a/Marlin/src/HAL/STM32/fastio.cpp +++ b/Marlin/src/HAL/STM32/fastio.cpp @@ -4,7 +4,6 @@ * * Based on Sprinter and grbl. * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm - * Copyright (c) 2017 Victor Perez * * 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 @@ -20,15 +19,17 @@ * along with this program. If not, see . * */ -#if defined(ARDUINO_ARCH_STM32) && !defined(STM32GENERIC) +#include "../platforms.h" + +#ifdef HAL_STM32 #include "../../inc/MarlinConfig.h" -GPIO_TypeDef* FastIOPortMap[LastPort + 1]; +GPIO_TypeDef* FastIOPortMap[LastPort + 1] = { 0 }; void FastIO_init() { - LOOP_L_N(i, NUM_DIGITAL_PINS) + for (uint8_t i = 0; i < NUM_DIGITAL_PINS; ++i) FastIOPortMap[STM_PORT(digitalPin[i])] = get_GPIO_Port(STM_PORT(digitalPin[i])); } -#endif +#endif // HAL_STM32 diff --git a/Marlin/src/HAL/STM32/fastio.h b/Marlin/src/HAL/STM32/fastio.h index ea28b8f3bf..a92bbc492e 100644 --- a/Marlin/src/HAL/STM32/fastio.h +++ b/Marlin/src/HAL/STM32/fastio.h @@ -4,7 +4,6 @@ * * Based on Sprinter and grbl. * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm - * Copyright (c) 2017 Victor Perez * * 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 @@ -38,6 +37,7 @@ extern GPIO_TypeDef * FastIOPortMap[]; // ------------------------ void FastIO_init(); // Must be called before using fast io macros +#define FASTIO_INIT() FastIO_init() // ------------------------ // Defines @@ -59,7 +59,7 @@ void FastIO_init(); // Must be called before using fast io macros #endif #define _READ(IO) bool(READ_BIT(FastIOPortMap[STM_PORT(digitalPinToPinName(IO))]->IDR, _BV32(STM_PIN(digitalPinToPinName(IO))))) -#define _TOGGLE(IO) (FastIOPortMap[STM_PORT(digitalPinToPinName(IO))]->ODR ^= _BV32(STM_PIN(digitalPinToPinName(IO)))) +#define _TOGGLE(IO) TBI32(FastIOPortMap[STM_PORT(digitalPinToPinName(IO))]->ODR, STM_PIN(digitalPinToPinName(IO))) #define _GET_MODE(IO) #define _SET_MODE(IO,M) pinMode(IO, M) @@ -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 5f1c4b1601..f345b925bb 100644 --- a/Marlin/src/HAL/STM32/inc/Conditionals_adv.h +++ b/Marlin/src/HAL/STM32/inc/Conditionals_adv.h @@ -20,3 +20,13 @@ * */ #pragma once + +#if ALL(HAS_MEDIA, USBD_USE_CDC_MSC) && DISABLED(NO_SD_HOST_DRIVE) + #define HAS_SD_HOST_DRIVE 1 +#endif + +// Fix F_CPU not being a compile-time constant in STSTM32 framework +#ifdef BOARD_F_CPU + #undef F_CPU + #define F_CPU BOARD_F_CPU +#endif diff --git a/Marlin/src/HAL/STM32/inc/Conditionals_post.h b/Marlin/src/HAL/STM32/inc/Conditionals_post.h index 18826e11d2..8d72e720c1 100644 --- a/Marlin/src/HAL/STM32/inc/Conditionals_post.h +++ b/Marlin/src/HAL/STM32/inc/Conditionals_post.h @@ -24,6 +24,11 @@ // If no real or emulated EEPROM selected, fall back to SD emulation #if USE_FALLBACK_EEPROM #define SDCARD_EEPROM_EMULATION -#elif EITHER(I2C_EEPROM, SPI_EEPROM) +#elif ANY(I2C_EEPROM, SPI_EEPROM) #define USE_SHARED_EEPROM 1 #endif + +// Some STM32F4 boards may lose steps when saving to EEPROM during print (PR #17946) +#if ALL(STM32F4xx, FLASH_EEPROM_EMULATION) && PRINTCOUNTER_SAVE_INTERVAL > 0 + #define PRINTCOUNTER_SYNC +#endif diff --git a/Marlin/src/HAL/STM32/inc/Conditionals_type.h b/Marlin/src/HAL/STM32/inc/Conditionals_type.h new file mode 100644 index 0000000000..82f95a1035 --- /dev/null +++ b/Marlin/src/HAL/STM32/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/STM32/inc/SanityCheck.h b/Marlin/src/HAL/STM32/inc/SanityCheck.h index 30d0750d90..616f96eba0 100644 --- a/Marlin/src/HAL/STM32/inc/SanityCheck.h +++ b/Marlin/src/HAL/STM32/inc/SanityCheck.h @@ -24,15 +24,11 @@ /** * Test STM32-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) +//#if ENABLED(SPINDLE_LASER_USE_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(FAST_PWM_FAN) || SPINDLE_LASER_FREQUENCY - #error "Features requiring Hardware PWM (FAST_PWM_FAN, SPINDLE_LASER_FREQUENCY) are not yet supported on STM32." -#endif - -#if ENABLED(SDCARD_EEPROM_EMULATION) && DISABLED(SDSUPPORT) +#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." @@ -40,17 +36,80 @@ #error "SDCARD_EEPROM_EMULATION requires SDSUPPORT. Enable SDSUPPORT or choose another EEPROM emulation." #endif -#if defined(STM32F4xx) && BOTH(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 !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) - #error "SERIAL_STATS_MAX_RX_QUEUED is not supported on this platform." + #error "SERIAL_STATS_MAX_RX_QUEUED is not supported on STM32." #elif ENABLED(SERIAL_STATS_DROPPED_RX) - #error "SERIAL_STATS_DROPPED_RX is not supported on this platform." + #error "SERIAL_STATS_DROPPED_RX is not supported on STM32." #endif + +#if ANY(TFT_COLOR_UI, TFT_LVGL_UI, TFT_CLASSIC_UI) && NOT_TARGET(STM32H7xx, STM32F4xx, STM32F1xx) + #error "TFT_COLOR_UI, TFT_LVGL_UI and TFT_CLASSIC_UI are currently only supported on STM32H7, STM32F4 and STM32F1 hardware." +#endif + +#if TEMP_SENSOR_SOC && defined(ATEMP) && TEMP_SOC_PIN != ATEMP + #error "TEMP_SENSOR_SOC requires 'TEMP_SOC_PIN ATEMP' on STM32." +#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(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 +#if SERIAL_IN_USE(5) + #if CHECK_SERIAL_PIN(TX,5) + #error "Serial Port 5 TX IO pins conflict with another pin on the board." + #endif + #if CHECK_SERIAL_PIN(RX,5) + #error "Serial Port 5 RX IO pins conflict with another pin on the board." + #endif +#endif +#if SERIAL_IN_USE(6) + #if CHECK_SERIAL_PIN(TX,6) + #error "Serial Port 6 TX IO pins conflict with another pin on the board." + #endif + #if CHECK_SERIAL_PIN(RX,6) + #error "Serial Port 6 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/STM32/pinsDebug.h b/Marlin/src/HAL/STM32/pinsDebug.h index ec08e3fd75..89e2514af0 100644 --- a/Marlin/src/HAL/STM32/pinsDebug.h +++ b/Marlin/src/HAL/STM32/pinsDebug.h @@ -2,6 +2,9 @@ * Marlin 3D Printer Firmware * Copyright (c) 2020 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,17 +21,281 @@ */ #pragma once -#if !(defined(NUM_DIGITAL_PINS) || defined(BOARD_NR_GPIO_PINS)) - #error "M43 not supported for this board" +/** + * 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 + // Only in ST's Arduino core (STM32duino, STM32Core) + #error "Expected NUM_DIGITAL_PINS not found" #endif -// Strange - STM32F4 comes to HAL_STM32 rather than HAL_STM32F4 for these files -#ifdef STM32F4 - #ifdef NUM_DIGITAL_PINS // Only in ST's Arduino core (STM32duino, STM32Core) - #include "pinsDebug_STM32duino.h" - #elif defined(BOARD_NR_GPIO_PINS) // Only in STM32GENERIC (Maple) - #include "pinsDebug_STM32GENERIC.h" +/** + * 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: + * 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 + * 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. + */ + +//////////////////////////////////////////////////////// +// +// make a list of the Arduino pin numbers in the Port/Pin order +// + +#define _PIN_ADD(NAME_ALPHA, ARDUINO_NUM) { NAME_ALPHA, ARDUINO_NUM }, +#define PIN_ADD(NAME) _PIN_ADD(#NAME, NAME) + +typedef struct { + char Port_pin_alpha[5]; + pin_t Ard_num; +} XrefInfo; + +const XrefInfo pin_xref[] PROGMEM = { + #include "pins_Xref.h" +}; + +//////////////////////////////////////////////////////////// + +#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 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 +#endif +#ifndef NUM_ANALOG_LAST + #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 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(x) pin_xref[x].Ard_num + +#ifndef M43_NEVER_TOUCH + #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) { + 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 + } #else - #error "M43 not supported for this board" + #define M43_NEVER_TOUCH(index) _M43_NEVER_TOUCH(index) #endif #endif + +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); + switch (mode) { + case LL_GPIO_MODE_ANALOG: return MODE_PIN_ANALOG; + case LL_GPIO_MODE_INPUT: return MODE_PIN_INPUT; + case LL_GPIO_MODE_OUTPUT: return MODE_PIN_OUTPUT; + case LL_GPIO_MODE_ALTERNATE: return MODE_PIN_ALT; + TERN_(STM32F1xx, case LL_GPIO_MODE_FLOATING:) + default: return 0; + } +} + +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 pin) { + if (WITHIN(pin, NUM_ANALOG_FIRST, NUM_ANALOG_LAST)) + return pin - NUM_ANALOG_FIRST; + + const int8_t ind = digitalPinToAnalogIndex(pin); + return (ind < NUM_ANALOG_INPUTS) ? ind : -1; +} + +bool isAnalogPin(const pin_t pin) { + return get_pin_mode(pin) == MODE_PIN_ANALOG; +} + +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 printPinPort(const pin_t pin) { + char buffer[16]; + 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; + 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(pin); + if (Port_pin >= 0) { + sprintf_P(buffer, PSTR(" (A%d) "), Port_pin); + SERIAL_ECHO(buffer); + if (Port_pin < 10) SERIAL_CHAR(' '); + } + else + SERIAL_ECHO_SP(7); + + // Print number to be used with M42 + int calc_p = pin; + if (pin > NUM_DIGITAL_PINS) { + calc_p -= NUM_ANALOG_FIRST; + if (calc_p > 7) calc_p += 8; + } + SERIAL_ECHO(F(" M42 P"), calc_p, C(' ')); + if (calc_p < 100) { + SERIAL_CHAR(' '); + if (calc_p < 10) + SERIAL_CHAR(' '); + } +} + +bool pwm_status(const pin_t pin) { + return get_pin_mode(pin) == MODE_PIN_ALT; +} + +void printPinPWM(const pin_t pin) { + #ifndef STM32F1xx + if (pwm_status(pin)) { + uint32_t alt_all = 0; + 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; + switch (PORT_ALPHA(dp)) { // get alt function + case 'A' : alt_all = GPIOA->AFR[ind]; break; + case 'B' : alt_all = GPIOB->AFR[ind]; break; + case 'C' : alt_all = GPIOC->AFR[ind]; break; + case 'D' : alt_all = GPIOD->AFR[ind]; break; + #ifdef PE_0 + case 'E' : alt_all = GPIOE->AFR[ind]; break; + #elif defined(PF_0) + case 'F' : alt_all = GPIOF->AFR[ind]; break; + #elif defined(PG_0) + case 'G' : alt_all = GPIOG->AFR[ind]; break; + #elif defined(PH_0) + case 'H' : alt_all = GPIOH->AFR[ind]; break; + #elif defined(PI_0) + case 'I' : alt_all = GPIOI->AFR[ind]; break; + #elif defined(PJ_0) + case 'J' : alt_all = GPIOJ->AFR[ind]; break; + #elif defined(PK_0) + case 'K' : alt_all = GPIOK->AFR[ind]; break; + #elif defined(PL_0) + case 'L' : alt_all = GPIOL->AFR[ind]; break; + #endif + } + if (over_7) pin_number -= 8; + + uint8_t alt_func = (alt_all >> (4 * pin_number)) & 0x0F; + SERIAL_ECHOPGM("Alt Function: ", alt_func); + if (alt_func < 10) SERIAL_CHAR(' '); + SERIAL_ECHOPGM(" - "); + switch (alt_func) { + case 0 : SERIAL_ECHOPGM("system (misc. I/O)"); break; + case 1 : SERIAL_ECHOPGM("TIM1/TIM2 (probably PWM)"); break; + case 2 : SERIAL_ECHOPGM("TIM3..5 (probably PWM)"); break; + case 3 : SERIAL_ECHOPGM("TIM8..11 (probably PWM)"); break; + case 4 : SERIAL_ECHOPGM("I2C1..3"); break; + case 5 : SERIAL_ECHOPGM("SPI1/SPI2"); break; + case 6 : SERIAL_ECHOPGM("SPI3"); break; + case 7 : SERIAL_ECHOPGM("USART1..3"); break; + case 8 : SERIAL_ECHOPGM("USART4..6"); break; + case 9 : SERIAL_ECHOPGM("CAN1/CAN2, TIM12..14 (probably PWM)"); break; + case 10 : SERIAL_ECHOPGM("OTG"); break; + case 11 : SERIAL_ECHOPGM("ETH"); break; + case 12 : SERIAL_ECHOPGM("FSMC, SDIO, OTG"); break; + case 13 : SERIAL_ECHOPGM("DCMI"); break; + case 14 : SERIAL_ECHOPGM("unused (shouldn't see this)"); break; + case 15 : SERIAL_ECHOPGM("EVENTOUT"); break; + } + } + #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 +} // printPinPWM diff --git a/Marlin/src/HAL/STM32/pinsDebug_STM32GENERIC.h b/Marlin/src/HAL/STM32/pinsDebug_STM32GENERIC.h deleted file mode 100644 index 9069d9f7bd..0000000000 --- a/Marlin/src/HAL/STM32/pinsDebug_STM32GENERIC.h +++ /dev/null @@ -1,125 +0,0 @@ -/** - * Marlin 3D Printer Firmware - * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * - * 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 - -/** - * Support routines for STM32GENERIC (Maple) - */ - -/** - * Translation of routines & variables used by pinsDebug.h - */ - -#ifdef BOARD_NR_GPIO_PINS // Only in STM32GENERIC (Maple) - -#ifdef __STM32F1__ - #include "../STM32F1/fastio.h" -#elif defined(STM32F4) || defined(STM32F7) - #include "../STM32_F4_F7/fastio.h" -#else - #include "fastio.h" -#endif - -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 pwm_status(pin) PWM_PIN(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_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 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 -#ifndef M43_NEVER_TOUCH - #define M43_NEVER_TOUCH(Q) (Q >= 9 && Q <= 12) // SERIAL/USB pins PA9(TX) PA10(RX) -#endif - -static inline int8_t get_pin_mode(pin_t pin) { - return VALID_PIN(pin) ? _GET_MODE(pin) : -1; -} - -static inline pin_t DIGITAL_PIN_TO_ANALOG_PIN(pin_t pin) { - if (!VALID_PIN(pin)) return -1; - int8_t adc_channel = int8_t(PIN_MAP[pin].adc_channel); - #ifdef NUM_ANALOG_INPUTS - if (adc_channel >= NUM_ANALOG_INPUTS) adc_channel = ADCx; - #endif - return pin_t(adc_channel); -} - -static inline bool IS_ANALOG(pin_t pin) { - if (!VALID_PIN(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; - #endif - return _GET_MODE(pin) == GPIO_INPUT_ANALOG && !M43_NEVER_TOUCH(pin); - } - return false; -} - -static inline bool GET_PINMODE(const pin_t pin) { - return VALID_PIN(pin) && !IS_INPUT(pin); -} - -static inline bool GET_ARRAY_IS_DIGITAL(const int16_t array_pin) { - const pin_t pin = GET_ARRAY_PIN(array_pin); - return (!IS_ANALOG(pin) - #ifdef NUM_ANALOG_INPUTS - || PIN_MAP[pin].adc_channel >= NUM_ANALOG_INPUTS - #endif - ); -} - -#include "../../inc/MarlinConfig.h" // Allow pins/pins.h to set density - -static inline void pwm_details(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; - const char num = ( - #if EITHER(STM32_HIGH_DENSITY, STM32_XL_DENSITY) - tdev == &timer8 ? '8' : - tdev == &timer5 ? '5' : - #endif - tdev == &timer4 ? '4' : - tdev == &timer3 ? '3' : - tdev == &timer2 ? '2' : - tdev == &timer1 ? '1' : '?' - ); - char buffer[10]; - sprintf_P(buffer, PSTR(" TIM%c CH%c"), num, ('0' + channel)); - SERIAL_ECHO(buffer); - } -} - -static inline void print_port(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]; - sprintf_P(buffer, PSTR("P%c%hd "), port, gbit); - if (gbit < 10) SERIAL_CHAR(' '); - SERIAL_ECHO(buffer); -} - -#endif // BOARD_NR_GPIO_PINS diff --git a/Marlin/src/HAL/STM32/pinsDebug_STM32duino.h b/Marlin/src/HAL/STM32/pinsDebug_STM32duino.h deleted file mode 100644 index 71480153a7..0000000000 --- a/Marlin/src/HAL/STM32/pinsDebug_STM32duino.h +++ /dev/null @@ -1,273 +0,0 @@ -/** - * Marlin 3D Printer Firmware - * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * - * 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 - -#ifdef NUM_DIGITAL_PINS // Only in ST's Arduino core (STM32duino, STM32Core) - -/** - * 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. - */ - -//////////////////////////////////////////////////////// -// -// make a list of the Arduino pin numbers in the Port/Pin order -// - -#define _PIN_ADD_2(NAME_ALPHA, ARDUINO_NUM) { {NAME_ALPHA}, ARDUINO_NUM }, -#define _PIN_ADD(NAME_ALPHA, ARDUINO_NUM) { NAME_ALPHA, ARDUINO_NUM }, -#define PIN_ADD(NAME) _PIN_ADD(#NAME, NAME) - -typedef struct { - char Port_pin_alpha[5]; - pin_t Ard_num; -} XrefInfo; - -const XrefInfo pin_xref[] PROGMEM = { - #include "pins_Xref.h" -}; - -//////////////////////////////////////////////////////////// - -#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 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 NUMBER_PINS_TOTAL NUM_DIGITAL_PINS -#define VALID_PIN(ANUM) ((ANUM) >= 0 && (ANUM) < NUMBER_PINS_TOTAL) -#define digitalRead_mod(Ard_num) extDigitalRead(Ard_num) // must use Arduino pin numbers when doing reads -#define PRINT_PIN(Q) -#define PRINT_PORT(ANUM) port_print(ANUM) -#define DIGITAL_PIN_TO_ANALOG_PIN(ANUM) -1 // will report analog pin number in the print port routine -#define GET_PIN_MAP_PIN_M43(Index) pin_xref[Index].Ard_num - -// 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 MULTI_NAME_PAD 33 // space needed to be pretty if not first name assigned to a pin - -#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) - - 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 - } - #else - #define M43_NEVER_TOUCH(Index) _M43_NEVER_TOUCH(Index) - #endif -#endif - -uint8_t get_pin_mode(const pin_t Ard_num) { - uint32_t mode_all = 0; - const PinName dp = digitalPinToPinName(Ard_num); - switch (PORT_ALPHA(dp)) { - case 'A' : mode_all = GPIOA->MODER; break; - case 'B' : mode_all = GPIOB->MODER; break; - case 'C' : mode_all = GPIOC->MODER; break; - case 'D' : mode_all = GPIOD->MODER; break; - #ifdef PE_0 - case 'E' : mode_all = GPIOE->MODER; break; - #elif defined(PF_0) - case 'F' : mode_all = GPIOF->MODER; break; - #elif defined(PG_0) - case 'G' : mode_all = GPIOG->MODER; break; - #elif defined(PH_0) - case 'H' : mode_all = GPIOH->MODER; break; - #elif defined(PI_0) - case 'I' : mode_all = GPIOI->MODER; break; - #elif defined(PJ_0) - case 'J' : mode_all = GPIOJ->MODER; break; - #elif defined(PK_0) - case 'K' : mode_all = GPIOK->MODER; break; - #elif defined(PL_0) - case 'L' : mode_all = GPIOL->MODER; break; - #endif - } - return (mode_all >> (2 * uint8_t(PIN_NUM(dp)))) & 0x03; -} - -bool GET_PINMODE(const pin_t Ard_num) { - const uint8_t pin_mode = get_pin_mode(Ard_num); - return pin_mode == MODE_PIN_OUTPUT || pin_mode == MODE_PIN_ALT; // assume all alt definitions are PWM -} - -int8_t digital_pin_to_analog_pin(pin_t Ard_num) { - Ard_num -= NUM_ANALOG_FIRST; - return (Ard_num >= 0 && Ard_num < NUM_ANALOG_INPUTS) ? Ard_num : -1; -} - -bool IS_ANALOG(const pin_t Ard_num) { - return get_pin_mode(Ard_num) == MODE_PIN_ANALOG; -} - -bool is_digital(const pin_t x) { - const uint8_t pin_mode = get_pin_mode(pin_array[x].pin); - return pin_mode == MODE_PIN_INPUT || pin_mode == MODE_PIN_OUTPUT; -} - -void port_print(const pin_t Ard_num) { - char buffer[16]; - pin_t Index; - for (Index = 0; Index < NUMBER_PINS_TOTAL; Index++) - if (Ard_num == GET_PIN_MAP_PIN_M43(Index)) break; - - 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); - if (Port_pin >= 0) { - sprintf_P(buffer, PSTR(" (A%d) "), Port_pin); - SERIAL_ECHO(buffer); - if (Port_pin < 10) SERIAL_CHAR(' '); - } - else - SERIAL_ECHO_SP(7); - - // Print number to be used with M42 - sprintf_P(buffer, PSTR(" M42 P%d "), Ard_num); - SERIAL_ECHO(buffer); - if (Ard_num < 10) SERIAL_CHAR(' '); - if (Ard_num < 100) SERIAL_CHAR(' '); -} - -bool pwm_status(const pin_t Ard_num) { - return get_pin_mode(Ard_num) == MODE_PIN_ALT; -} - -void pwm_details(const pin_t Ard_num) { - if (pwm_status(Ard_num)) { - uint32_t alt_all = 0; - const PinName dp = digitalPinToPinName(Ard_num); - pin_t pin_number = uint8_t(PIN_NUM(dp)); - const bool over_7 = pin_number >= 8; - const uint8_t ind = over_7 ? 1 : 0; - switch (PORT_ALPHA(dp)) { // get alt function - case 'A' : alt_all = GPIOA->AFR[ind]; break; - case 'B' : alt_all = GPIOB->AFR[ind]; break; - case 'C' : alt_all = GPIOC->AFR[ind]; break; - case 'D' : alt_all = GPIOD->AFR[ind]; break; - #ifdef PE_0 - case 'E' : alt_all = GPIOE->AFR[ind]; break; - #elif defined (PF_0) - case 'F' : alt_all = GPIOF->AFR[ind]; break; - #elif defined (PG_0) - case 'G' : alt_all = GPIOG->AFR[ind]; break; - #elif defined (PH_0) - case 'H' : alt_all = GPIOH->AFR[ind]; break; - #elif defined (PI_0) - case 'I' : alt_all = GPIOI->AFR[ind]; break; - #elif defined (PJ_0) - case 'J' : alt_all = GPIOJ->AFR[ind]; break; - #elif defined (PK_0) - case 'K' : alt_all = GPIOK->AFR[ind]; break; - #elif defined (PL_0) - case 'L' : alt_all = GPIOL->AFR[ind]; break; - #endif - } - if (over_7) pin_number -= 8; - - uint8_t alt_func = (alt_all >> (4 * pin_number)) & 0x0F; - SERIAL_ECHOPAIR("Alt Function: ", alt_func); - if (alt_func < 10) SERIAL_CHAR(' '); - SERIAL_ECHOPGM(" - "); - switch (alt_func) { - case 0 : SERIAL_ECHOPGM("system (misc. I/O)"); break; - case 1 : SERIAL_ECHOPGM("TIM1/TIM2 (probably PWM)"); break; - case 2 : SERIAL_ECHOPGM("TIM3..5 (probably PWM)"); break; - case 3 : SERIAL_ECHOPGM("TIM8..11 (probably PWM)"); break; - case 4 : SERIAL_ECHOPGM("I2C1..3"); break; - case 5 : SERIAL_ECHOPGM("SPI1/SPI2"); break; - case 6 : SERIAL_ECHOPGM("SPI3"); break; - case 7 : SERIAL_ECHOPGM("USART1..3"); break; - case 8 : SERIAL_ECHOPGM("USART4..6"); break; - case 9 : SERIAL_ECHOPGM("CAN1/CAN2, TIM12..14 (probably PWM)"); break; - case 10 : SERIAL_ECHOPGM("OTG"); break; - case 11 : SERIAL_ECHOPGM("ETH"); break; - case 12 : SERIAL_ECHOPGM("FSMC, SDIO, OTG"); break; - case 13 : SERIAL_ECHOPGM("DCMI"); break; - case 14 : SERIAL_ECHOPGM("unused (shouldn't see this)"); break; - case 15 : SERIAL_ECHOPGM("EVENTOUT"); break; - } - } -} // pwm_details - -#endif // NUM_DIGITAL_PINS diff --git a/Marlin/src/HAL/STM32/sd/msc_sd.cpp b/Marlin/src/HAL/STM32/sd/msc_sd.cpp new file mode 100644 index 0000000000..9bb65aab4a --- /dev/null +++ b/Marlin/src/HAL/STM32/sd/msc_sd.cpp @@ -0,0 +1,161 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2021 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] + * Copyright (c) 2019 BigTreeTech [https://github.com/bigtreetech] + * + * 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 HAL_STM32 + +#include "../../../inc/MarlinConfigPre.h" + +#if HAS_SD_HOST_DRIVE + +#include "../../../sd/cardreader.h" + +#include "msc_sd.h" + +#include +#include +#include + +#define BLOCK_SIZE 512 +#define PRODUCT_ID 0x29 + +#ifndef SD_MULTIBLOCK_RETRY_CNT + #define SD_MULTIBLOCK_RETRY_CNT 1 +#elif SD_MULTIBLOCK_RETRY_CNT < 1 + #error "SD_MULTIBLOCK_RETRY_CNT must be greater than or equal to 1." +#endif + +class Sd2CardUSBMscHandler : public USBMscHandler { +public: + DiskIODriver* diskIODriver() { + // 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) + return &card.media_driver_usbFlash; + #endif + #else + return card.diskIODriver(); + #endif + } + + bool GetCapacity(uint32_t *pBlockNum, uint16_t *pBlockSize) { + *pBlockNum = diskIODriver()->cardSize(); + *pBlockSize = BLOCK_SIZE; + return true; + } + + bool Write(uint8_t *pBuf, uint32_t blkAddr, uint16_t blkLen) { + auto sd2card = diskIODriver(); + // single block + if (blkLen == 1) { + hal.watchdog_refresh(); + return sd2card->writeBlock(blkAddr, pBuf); + } + + // multi block optimization + bool done = false; + for (uint16_t rcount = SD_MULTIBLOCK_RETRY_CNT; !done && rcount--;) { + uint8_t *cBuf = pBuf; + sd2card->writeStart(blkAddr, blkLen); + bool okay = true; // Assume success + for (uint32_t i = blkLen; i--;) { + hal.watchdog_refresh(); + if (!sd2card->writeData(cBuf)) { // Write. Did it fail? + sd2card->writeStop(); // writeStop for new writeStart + okay = false; // Failed, so retry + break; // Go to while... below + } + cBuf += BLOCK_SIZE; + } + done = okay; // Done if no error occurred + } + + if (done) sd2card->writeStop(); + return done; + } + + bool Read(uint8_t *pBuf, uint32_t blkAddr, uint16_t blkLen) { + auto sd2card = diskIODriver(); + // single block + if (blkLen == 1) { + hal.watchdog_refresh(); + return sd2card->readBlock(blkAddr, pBuf); + } + + // multi block optimization + bool done = false; + for (uint16_t rcount = SD_MULTIBLOCK_RETRY_CNT; !done && rcount--;) { + uint8_t *cBuf = pBuf; + sd2card->readStart(blkAddr); + bool okay = true; // Assume success + for (uint32_t i = blkLen; i--;) { + hal.watchdog_refresh(); + if (!sd2card->readData(cBuf)) { // Read. Did it fail? + sd2card->readStop(); // readStop for new readStart + okay = false; // Failed, so retry + break; // Go to while... below + } + cBuf += BLOCK_SIZE; + } + done = okay; // Done if no error occurred + } + + if (done) sd2card->readStop(); + return done; + } + + bool IsReady() { return diskIODriver()->isReady(); } +}; + +Sd2CardUSBMscHandler usbMscHandler; + +/* USB Mass storage Standard Inquiry Data */ +uint8_t Marlin_STORAGE_Inquirydata[] = { /* 36 */ + /* LUN 0 */ + 0x00, + 0x80, + 0x02, + 0x02, + (STANDARD_INQUIRY_DATA_LEN - 5), + 0x00, + 0x00, + 0x00, + 'M', 'A', 'R', 'L', 'I', 'N', ' ', ' ', /* Manufacturer : 8 bytes */ + 'P', 'r', 'o', 'd', 'u', 'c', 't', ' ', /* Product : 16 Bytes */ + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + '0', '.', '0', '1', /* Version : 4 Bytes */ +}; + +USBMscHandler *pSingleMscHandler = &usbMscHandler; + +void MSC_SD_init() { + USBDevice.end(); + delay(200); + USBDevice.registerMscHandlers(1, &pSingleMscHandler, Marlin_STORAGE_Inquirydata); + USBDevice.begin(); +} + +#endif // HAS_SD_HOST_DRIVE +#endif // HAL_STM32 diff --git a/Marlin/src/HAL/STM32/sd/msc_sd.h b/Marlin/src/HAL/STM32/sd/msc_sd.h new file mode 100644 index 0000000000..20e738223c --- /dev/null +++ b/Marlin/src/HAL/STM32/sd/msc_sd.h @@ -0,0 +1,25 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2021 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] + * Copyright (c) 2019 BigTreeTech [https://github.com/bigtreetech] + * + * 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/STM32/sd/usb_host.cpp b/Marlin/src/HAL/STM32/sd/usb_host.cpp new file mode 100644 index 0000000000..f411771c8a --- /dev/null +++ b/Marlin/src/HAL/STM32/sd/usb_host.cpp @@ -0,0 +1,118 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2020 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 HAL_STM32 + +#include "../../../inc/MarlinConfig.h" + +#if ALL(USE_OTG_USB_HOST, USBHOST) + +#include "usb_host.h" +#include +#include + +USBH_HandleTypeDef hUsbHost; +USBHost usb; +BulkStorage bulk(&usb); + +static void USBH_UserProcess(USBH_HandleTypeDef *phost, uint8_t id) { + switch(id) { + case HOST_USER_SELECT_CONFIGURATION: + //SERIAL_ECHOLNPGM("APPLICATION_SELECT_CONFIGURATION"); + break; + case HOST_USER_DISCONNECTION: + //SERIAL_ECHOLNPGM("APPLICATION_DISCONNECT"); + usb.setUsbTaskState(USB_STATE_INIT); + break; + case HOST_USER_CLASS_ACTIVE: + //SERIAL_ECHOLNPGM("APPLICATION_READY"); + usb.setUsbTaskState(USB_STATE_RUNNING); + break; + case HOST_USER_CONNECTION: + break; + default: + break; + } +} + +bool USBHost::start() { + if (USBH_Init(&hUsbHost, USBH_UserProcess, TERN(USE_USB_HS_IN_FS, HOST_HS, HOST_FS)) != USBH_OK) { + SERIAL_ECHOLNPGM("Error: USBH_Init"); + return false; + } + if (USBH_RegisterClass(&hUsbHost, USBH_MSC_CLASS) != USBH_OK) { + SERIAL_ECHOLNPGM("Error: USBH_RegisterClass"); + return false; + } + if (USBH_Start(&hUsbHost) != USBH_OK) { + SERIAL_ECHOLNPGM("Error: USBH_Start"); + return false; + } + return true; +} + +void USBHost::Task() { + USBH_Process(&hUsbHost); +} + +uint8_t USBHost::getUsbTaskState() { + return usb_task_state; +} + +void USBHost::setUsbTaskState(uint8_t state) { + usb_task_state = state; + if (usb_task_state == USB_STATE_RUNNING) { + MSC_LUNTypeDef info; + USBH_MSC_GetLUNInfo(&hUsbHost, usb.lun, &info); + 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); + } +}; + +bool BulkStorage::LUNIsGood(uint8_t t) { + return USBH_MSC_IsReady(&hUsbHost) && USBH_MSC_UnitIsReady(&hUsbHost, t); +} + +uint32_t BulkStorage::GetCapacity(uint8_t lun) { + return usb->block_count; +} + +uint16_t BulkStorage::GetSectorSize(uint8_t lun) { + return usb->block_size; +} + +uint8_t BulkStorage::Read(uint8_t lun, uint32_t addr, uint16_t bsize, uint8_t blocks, uint8_t *buf) { + return USBH_MSC_Read(&hUsbHost, lun, addr, buf, blocks) != USBH_OK; +} + +uint8_t BulkStorage::Write(uint8_t lun, uint32_t addr, uint16_t bsize, uint8_t blocks, const uint8_t * buf) { + return USBH_MSC_Write(&hUsbHost, lun, addr, const_cast(buf), blocks) != USBH_OK; +} + +#endif // USE_OTG_USB_HOST && USBHOST +#endif // HAL_STM32 diff --git a/Marlin/src/HAL/STM32/sd/usb_host.h b/Marlin/src/HAL/STM32/sd/usb_host.h new file mode 100644 index 0000000000..c0001c0d75 --- /dev/null +++ b/Marlin/src/HAL/STM32/sd/usb_host.h @@ -0,0 +1,60 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2020 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 + +typedef enum { + USB_STATE_INIT, + USB_STATE_ERROR, + USB_STATE_RUNNING, +} usb_state_t; + +class USBHost { +public: + bool start(); + void Task(); + uint8_t getUsbTaskState(); + void setUsbTaskState(uint8_t state); + uint8_t regRd(uint8_t reg) { return 0x0; }; + uint8_t usb_task_state = USB_STATE_INIT; + uint8_t lun = 0; + uint32_t capacity = 0; + uint16_t block_size = 0; + uint32_t block_count = 0; +}; + +class BulkStorage { +public: + BulkStorage(USBHost *usb) : usb(usb) {}; + + bool LUNIsGood(uint8_t t); + uint32_t GetCapacity(uint8_t lun); + uint16_t GetSectorSize(uint8_t lun); + uint8_t Read(uint8_t lun, uint32_t addr, uint16_t bsize, uint8_t blocks, uint8_t *buf); + uint8_t Write(uint8_t lun, uint32_t addr, uint16_t bsize, uint8_t blocks, const uint8_t * buf); + + USBHost *usb; +}; + +extern USBHost usb; +extern BulkStorage bulk; diff --git a/Marlin/src/HAL/STM32/sdio.cpp b/Marlin/src/HAL/STM32/sdio.cpp new file mode 100644 index 0000000000..89df16628f --- /dev/null +++ b/Marlin/src/HAL/STM32/sdio.cpp @@ -0,0 +1,457 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2020 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 HAL_STM32 + +#include "../../inc/MarlinConfig.h" + +#if ENABLED(ONBOARD_SDIO) + +#include "sdio.h" + +#include +#include + +#if defined(STM32F103xE) || defined(STM32F103xG) + #include + #include +#elif defined(STM32F4xx) + #include + #include + #include + #include +#elif defined(STM32F7xx) + #include + #include + #include + #include +#elif defined(STM32H7xx) + #define SDIO_FOR_STM32H7 + #include + #include + #include + #include +#else + #error "SDIO is only supported with STM32F103xE, STM32F103xG, STM32F4xx, STM32F7xx, and STM32H7xx." +#endif + +// SDIO Max Clock (naming from STM Manual, don't change) +#define SDIOCLK 48000000 + +// Target Clock, configurable. Default is 18MHz, from STM32F1 +#ifndef SDIO_CLOCK + #define SDIO_CLOCK 18000000 // 18 MHz +#endif + +SD_HandleTypeDef hsd; // SDIO structure + +static uint32_t clock_to_divider(uint32_t clk) { + #ifdef SDIO_FOR_STM32H7 + // SDMMC_CK frequency = sdmmc_ker_ck / [2 * CLKDIV]. + uint32_t sdmmc_clk = HAL_RCCEx_GetPeriphCLKFreq(RCC_PERIPHCLK_SDMMC); + return sdmmc_clk / (2U * SDIO_CLOCK) + (sdmmc_clk % (2U * SDIO_CLOCK) != 0); + #else + // limit the SDIO master clock to 8/3 of PCLK2. See STM32 Manuals + // Also limited to no more than 48Mhz (SDIOCLK). + const uint32_t pclk2 = HAL_RCC_GetPCLK2Freq(); + clk = min(clk, (uint32_t)(pclk2 * 8 / 3)); + clk = min(clk, (uint32_t)SDIOCLK); + // Round up divider, so we don't run the card over the speed supported, + // and subtract by 2, because STM32 will add 2, as written in the manual: + // SDIO_CK frequency = SDIOCLK / [CLKDIV + 2] + return pclk2 / clk + (pclk2 % clk != 0) - 2; + #endif +} + +// Start the SDIO clock +void HAL_SD_MspInit(SD_HandleTypeDef *hsd) { + UNUSED(hsd); + #ifdef SDIO_FOR_STM32H7 + pinmap_pinout(PC_12, PinMap_SD); + pinmap_pinout(PD_2, PinMap_SD); + pinmap_pinout(PC_8, PinMap_SD); + #if PINS_EXIST(SDIO_D1, SDIO_D2, SDIO_D3) // Define D1-D3 only for 4-bit wide SDIO bus + pinmap_pinout(PC_9, PinMap_SD); + pinmap_pinout(PC_10, PinMap_SD); + pinmap_pinout(PC_11, PinMap_SD); + #endif + __HAL_RCC_SDMMC1_CLK_ENABLE(); + HAL_NVIC_EnableIRQ(SDMMC1_IRQn); + #else + __HAL_RCC_SDIO_CLK_ENABLE(); + #endif +} + +#ifdef SDIO_FOR_STM32H7 + + #define SD_TIMEOUT 1000 // ms + + extern "C" void SDMMC1_IRQHandler(void) { HAL_SD_IRQHandler(&hsd); } + + uint8_t waitingRxCplt = 0, waitingTxCplt = 0; + void HAL_SD_TxCpltCallback(SD_HandleTypeDef *hsdio) { waitingTxCplt = 0; } + void HAL_SD_RxCpltCallback(SD_HandleTypeDef *hsdio) { waitingRxCplt = 0; } + + void HAL_SD_MspDeInit(SD_HandleTypeDef *hsd) { + __HAL_RCC_SDMMC1_FORCE_RESET(); delay(10); + __HAL_RCC_SDMMC1_RELEASE_RESET(); delay(10); + } + + bool SDIO_Init() { + HAL_StatusTypeDef sd_state = HAL_OK; + if (hsd.Instance == SDMMC1) HAL_SD_DeInit(&hsd); + + // HAL SD initialization + hsd.Instance = SDMMC1; + hsd.Init.ClockEdge = SDMMC_CLOCK_EDGE_RISING; + hsd.Init.ClockPowerSave = SDMMC_CLOCK_POWER_SAVE_DISABLE; + hsd.Init.BusWide = SDMMC_BUS_WIDE_1B; + hsd.Init.HardwareFlowControl = SDMMC_HARDWARE_FLOW_CONTROL_DISABLE; + hsd.Init.ClockDiv = clock_to_divider(SDIO_CLOCK); + sd_state = HAL_SD_Init(&hsd); + + #if PINS_EXIST(SDIO_D1, SDIO_D2, SDIO_D3) + if (sd_state == HAL_OK) + sd_state = HAL_SD_ConfigWideBusOperation(&hsd, SDMMC_BUS_WIDE_4B); + #endif + + return (sd_state == HAL_OK); + } + +#else // !SDIO_FOR_STM32H7 + + #define SD_TIMEOUT 500 // ms + + // SDIO retries, configurable. Default is 3, from STM32F1 + #ifndef SDIO_READ_RETRIES + #define SDIO_READ_RETRIES 3 + #endif + + // F4 supports one DMA for RX and another for TX, but Marlin will never + // do read and write at same time, so we use the same DMA for both. + DMA_HandleTypeDef hdma_sdio; + + #ifdef STM32F1xx + #define DMA_IRQ_HANDLER DMA2_Channel4_5_IRQHandler + #elif defined(STM32F4xx) + #define DMA_IRQ_HANDLER DMA2_Stream3_IRQHandler + #else + #error "Unknown STM32 architecture." + #endif + + extern "C" void SDIO_IRQHandler(void) { HAL_SD_IRQHandler(&hsd); } + extern "C" void DMA_IRQ_HANDLER(void) { HAL_DMA_IRQHandler(&hdma_sdio); } + + /* + SDIO_INIT_CLK_DIV is 118 + SDIO clock frequency is 48MHz / (TRANSFER_CLOCK_DIV + 2) + SDIO init clock frequency should not exceed 400kHz = 48MHz / (118 + 2) + + Default TRANSFER_CLOCK_DIV is 2 (118 / 40) + Default SDIO clock frequency is 48MHz / (2 + 2) = 12 MHz + This might be too fast for stable SDIO operations + + MKS Robin SDIO seems stable with BusWide 1bit and ClockDiv 8 (i.e., 4.8MHz SDIO clock frequency) + More testing is required as there are clearly some 4bit init problems. + */ + + void go_to_transfer_speed() { + /* Default SDIO peripheral configuration for SD card initialization */ + hsd.Init.ClockEdge = hsd.Init.ClockEdge; + hsd.Init.ClockBypass = hsd.Init.ClockBypass; + hsd.Init.ClockPowerSave = hsd.Init.ClockPowerSave; + hsd.Init.BusWide = hsd.Init.BusWide; + hsd.Init.HardwareFlowControl = hsd.Init.HardwareFlowControl; + hsd.Init.ClockDiv = clock_to_divider(SDIO_CLOCK); + + /* Initialize SDIO peripheral interface with default configuration */ + SDIO_Init(hsd.Instance, hsd.Init); + } + + void SD_LowLevel_Init() { + uint32_t tempreg; + + // Enable GPIO clocks + __HAL_RCC_GPIOC_CLK_ENABLE(); + __HAL_RCC_GPIOD_CLK_ENABLE(); + + GPIO_InitTypeDef GPIO_InitStruct; + + GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; + GPIO_InitStruct.Pull = 1; // GPIO_NOPULL + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; + + #if DISABLED(STM32F1xx) + GPIO_InitStruct.Alternate = GPIO_AF12_SDIO; + #endif + + GPIO_InitStruct.Pin = GPIO_PIN_8 | GPIO_PIN_12; // D0 & SCK + HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); + + #if PINS_EXIST(SDIO_D1, SDIO_D2, SDIO_D3) // define D1-D3 only if have a four bit wide SDIO bus + GPIO_InitStruct.Pin = GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11; // D1-D3 + HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); + #endif + + // Configure PD.02 CMD line + GPIO_InitStruct.Pin = GPIO_PIN_2; + HAL_GPIO_Init(GPIOD, &GPIO_InitStruct); + + // Setup DMA + #ifdef STM32F1xx + hdma_sdio.Init.Mode = DMA_NORMAL; + hdma_sdio.Instance = DMA2_Channel4; + HAL_NVIC_EnableIRQ(DMA2_Channel4_5_IRQn); + #elif defined(STM32F4xx) + hdma_sdio.Init.Mode = DMA_PFCTRL; + hdma_sdio.Instance = DMA2_Stream3; + hdma_sdio.Init.Channel = DMA_CHANNEL_4; + hdma_sdio.Init.FIFOMode = DMA_FIFOMODE_ENABLE; + hdma_sdio.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL; + hdma_sdio.Init.MemBurst = DMA_MBURST_INC4; + hdma_sdio.Init.PeriphBurst = DMA_PBURST_INC4; + HAL_NVIC_EnableIRQ(DMA2_Stream3_IRQn); + #endif + HAL_NVIC_EnableIRQ(SDIO_IRQn); + hdma_sdio.Init.PeriphInc = DMA_PINC_DISABLE; + 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_MEDIUM; + __HAL_LINKDMA(&hsd, hdmarx, hdma_sdio); + __HAL_LINKDMA(&hsd, hdmatx, hdma_sdio); + + #ifdef STM32F1xx + __HAL_RCC_SDIO_CLK_ENABLE(); + __HAL_RCC_DMA2_CLK_ENABLE(); + #else + __HAL_RCC_SDIO_FORCE_RESET(); delay(2); + __HAL_RCC_SDIO_RELEASE_RESET(); delay(2); + __HAL_RCC_SDIO_CLK_ENABLE(); + + __HAL_RCC_DMA2_FORCE_RESET(); delay(2); + __HAL_RCC_DMA2_RELEASE_RESET(); delay(2); + __HAL_RCC_DMA2_CLK_ENABLE(); + #endif + + // Initialize the SDIO (with initial <400Khz Clock) + tempreg = 0 // Reset value + | SDIO_CLKCR_CLKEN // Clock enabled + | SDIO_INIT_CLK_DIV; // Clock Divider. Clock = 48000 / (118 + 2) = 400Khz + // Keep the rest at 0 => HW_Flow Disabled, Rising Clock Edge, Disable CLK ByPass, Bus Width = 0, Power save Disable + SDIO->CLKCR = tempreg; + + // Power up the SDIO + SDIO_PowerState_ON(SDIO); + hsd.Instance = SDIO; + } + + bool SDIO_Init() { + uint8_t retryCnt = SDIO_READ_RETRIES; + + bool status; + hsd.Instance = SDIO; + hsd.State = HAL_SD_STATE_RESET; + + SD_LowLevel_Init(); + + uint8_t retry_Cnt = retryCnt; + for (;;) { + hal.watchdog_refresh(); + status = (bool) HAL_SD_Init(&hsd); + if (!status) break; + if (!--retry_Cnt) return false; // return failing status if retries are exhausted + } + + go_to_transfer_speed(); + + hsd.Init.ClockPowerSave = SDIO_CLOCK_POWER_SAVE_ENABLE; + hsd.Init.ClockDiv = 8; + + #if PINS_EXIST(SDIO_D1, SDIO_D2, SDIO_D3) // go to 4 bit wide mode if pins are defined + retry_Cnt = retryCnt; + for (;;) { + hal.watchdog_refresh(); + if (!HAL_SD_ConfigWideBusOperation(&hsd, SDIO_BUS_WIDE_4B)) break; // some cards are only 1 bit wide so a pass here is not required + if (!--retry_Cnt) break; + } + if (!retry_Cnt) { // wide bus failed, go back to one bit wide mode + hsd.State = (HAL_SD_StateTypeDef) 0; // HAL_SD_STATE_RESET + SD_LowLevel_Init(); + retry_Cnt = retryCnt; + for (;;) { + hal.watchdog_refresh(); + status = (bool) HAL_SD_Init(&hsd); + if (!status) break; + if (!--retry_Cnt) return false; // return failing status if retries are exhausted + } + go_to_transfer_speed(); + } + #endif + + return true; + } + + /** + * @brief Read or Write a block + * @details Read or Write a block with SDIO + * + * @param block The block index + * @param src The data buffer source for a write + * @param dst The data buffer destination for a read + * + * @return true on success + */ + static bool SDIO_ReadWriteBlock_DMA(uint32_t block, const uint8_t *src, uint8_t *dst) { + if (HAL_SD_GetCardState(&hsd) != HAL_SD_CARD_TRANSFER) return false; + + hal.watchdog_refresh(); + + HAL_StatusTypeDef ret; + if (src) { + hdma_sdio.Init.Direction = DMA_MEMORY_TO_PERIPH; + HAL_DMA_Init(&hdma_sdio); + ret = HAL_SD_WriteBlocks_DMA(&hsd, (uint8_t*)src, block, 1); + } + else { + hdma_sdio.Init.Direction = DMA_PERIPH_TO_MEMORY; + HAL_DMA_Init(&hdma_sdio); + ret = HAL_SD_ReadBlocks_DMA(&hsd, (uint8_t*)dst, block, 1); + } + + if (ret != HAL_OK) { + HAL_DMA_Abort_IT(&hdma_sdio); + HAL_DMA_DeInit(&hdma_sdio); + return false; + } + + millis_t timeout = millis() + SD_TIMEOUT; + // Wait the transfer + while (hsd.State != HAL_SD_STATE_READY) { + if (ELAPSED(millis(), timeout)) { + HAL_DMA_Abort_IT(&hdma_sdio); + HAL_DMA_DeInit(&hdma_sdio); + return false; + } + } + + while (__HAL_DMA_GET_FLAG(&hdma_sdio, __HAL_DMA_GET_TC_FLAG_INDEX(&hdma_sdio)) != 0 + || __HAL_DMA_GET_FLAG(&hdma_sdio, __HAL_DMA_GET_TE_FLAG_INDEX(&hdma_sdio)) != 0) { /* nada */ } + + HAL_DMA_Abort_IT(&hdma_sdio); + HAL_DMA_DeInit(&hdma_sdio); + + timeout = millis() + SD_TIMEOUT; + while (HAL_SD_GetCardState(&hsd) != HAL_SD_CARD_TRANSFER) if (ELAPSED(millis(), timeout)) return false; + + return true; + } + +#endif // !SDIO_FOR_STM32H7 + +/** + * @brief Read a block + * @details Read a block from media with SDIO + * + * @param block The block index + * @param src The block buffer + * + * @return true on success + */ +bool SDIO_ReadBlock(uint32_t block, uint8_t *dst) { + #ifdef SDIO_FOR_STM32H7 + + uint32_t timeout = HAL_GetTick() + SD_TIMEOUT; + + while (HAL_SD_GetCardState(&hsd) != HAL_SD_CARD_TRANSFER) + if (HAL_GetTick() >= timeout) return false; + + waitingRxCplt = 1; + if (HAL_SD_ReadBlocks_DMA(&hsd, (uint8_t*)dst, block, 1) != HAL_OK) + return false; + + timeout = HAL_GetTick() + SD_TIMEOUT; + while (waitingRxCplt) + if (HAL_GetTick() >= timeout) return false; + + return true; + + #else + + uint8_t retries = SDIO_READ_RETRIES; + while (retries--) if (SDIO_ReadWriteBlock_DMA(block, nullptr, dst)) return true; + return false; + + #endif +} + +/** + * @brief Write a block + * @details Write a block to media with SDIO + * + * @param block The block index + * @param src The block data + * + * @return true on success + */ +bool SDIO_WriteBlock(uint32_t block, const uint8_t *src) { + #ifdef SDIO_FOR_STM32H7 + + uint32_t timeout = HAL_GetTick() + SD_TIMEOUT; + + while (HAL_SD_GetCardState(&hsd) != HAL_SD_CARD_TRANSFER) + if (HAL_GetTick() >= timeout) return false; + + waitingTxCplt = 1; + if (HAL_SD_WriteBlocks_DMA(&hsd, (uint8_t*)src, block, 1) != HAL_OK) + return false; + + timeout = HAL_GetTick() + SD_TIMEOUT; + while (waitingTxCplt) + if (HAL_GetTick() >= timeout) return false; + + return true; + + #else + + uint8_t retries = SDIO_READ_RETRIES; + while (retries--) { + if (SDIO_ReadWriteBlock_DMA(block, src, nullptr)) return true; + delay(10); + } + return false; + + #endif +} + +bool SDIO_IsReady() { + return hsd.State == HAL_SD_STATE_READY; +} + +uint32_t SDIO_GetCardSize() { + return (uint32_t)(hsd.SdCard.BlockNbr) * (hsd.SdCard.BlockSize); +} + +#endif // ONBOARD_SDIO +#endif // HAL_STM32 diff --git a/Marlin/src/HAL/STM32/sdio.h b/Marlin/src/HAL/STM32/sdio.h new file mode 100644 index 0000000000..cf5539c3c7 --- /dev/null +++ b/Marlin/src/HAL/STM32/sdio.h @@ -0,0 +1,29 @@ +/** + * 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 + +#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 diff --git a/Marlin/src/HAL/STM32/spi_pins.h b/Marlin/src/HAL/STM32/spi_pins.h index 176e2a7b20..da6fa0d160 100644 --- a/Marlin/src/HAL/STM32/spi_pins.h +++ b/Marlin/src/HAL/STM32/spi_pins.h @@ -2,6 +2,9 @@ * Marlin 3D Printer Firmware * Copyright (c) 2020 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 @@ -21,15 +24,12 @@ /** * Define SPI Pins: SCK, MISO, MOSI, SS */ -#ifndef SCK_PIN - #define SCK_PIN PIN_SPI_SCK +#ifndef SD_SCK_PIN + #define SD_SCK_PIN PIN_SPI_SCK #endif -#ifndef MISO_PIN - #define MISO_PIN PIN_SPI_MISO +#ifndef SD_MISO_PIN + #define SD_MISO_PIN PIN_SPI_MISO #endif -#ifndef MOSI_PIN - #define MOSI_PIN PIN_SPI_MOSI -#endif -#ifndef SS_PIN - #define SS_PIN PIN_SPI_SS +#ifndef SD_MOSI_PIN + #define SD_MOSI_PIN PIN_SPI_MOSI #endif diff --git a/Marlin/src/HAL/STM32/temp_soc.h b/Marlin/src/HAL/STM32/temp_soc.h new file mode 100644 index 0000000000..cc165dd5e4 --- /dev/null +++ b/Marlin/src/HAL/STM32/temp_soc.h @@ -0,0 +1,346 @@ +/** + * 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 + * (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 + +#ifdef STM32F0xx + /* + STM32F030x4 STM32F030x6 STM32F030x8 STM32F030xC https://www.st.com/resource/en/datasheet/stm32f030f4.pdf + --> TS_CAL2 not defined in datasheet + STM32F031x4 STM32F031x6 https://www.st.com/resource/en/datasheet/stm32f031c4.pdf + STM32F038x6 https://www.st.com/resource/en/datasheet/stm32f038c6.pdf + STM32F042x4 STM32F042x6 https://www.st.com/resource/en/datasheet/stm32f042c4.pdf + STM32F048C6 STM32F048G6 STM32F048T6 https://www.st.com/resource/en/datasheet/stm32f048c6.pdf + STM32F051x4 STM32F051x6 STM32F051x8 https://www.st.com/resource/en/datasheet/dm00039193.pdf + STM32F058C8 STM32F058R8 STM32F058T8 https://www.st.com/resource/en/datasheet/stm32f058c8.pdf + STM32F070CB STM32F070RB STM32F070C6 STM32F070F6 https://www.st.com/resource/en/datasheet/stm32f070c6.pdf + --> TS_CAL2 not defined in datasheet + STM32F071x8 STM32F071xB https://www.st.com/resource/en/datasheet/stm32f071cb.pdf + STM32F072x8 STM32F072xB https://www.st.com/resource/en/datasheet/stm32f072c8.pdf + STM32F078CB STM32F078RB STM32F078VB https://www.st.com/resource/en/datasheet/stm32f078cb.pdf + STM32F091xB STM32F091xC https://www.st.com/resource/en/datasheet/stm32f091cc.pdf + STM32F098CC STM32F098RC STM32F098VC https://www.st.com/resource/en/datasheet/stm32f098cc.pdf + */ + #define TS_CAL1_TEMP 30 // Calibration temperature of TS_CAL1 (see specific SoC datasheet) + #define TS_CAL1_REGOFFSET 0x1FFFF7B8 // Memory address of TS_CAL1 for STM32F030x4/x6/x8/xC (see specific SoC datasheet) + #define TS_CAL2_TEMP 110 // Calibration temperature of TS_CAL2 (see specific SoC datasheet) + #define TS_CAL2_REGOFFSET 0x1FFFF7C2 // Memory address of TS_CAL2 for STM32F030x4/x6/x8/xC (see specific SoC datasheet) + +#elif defined(STM32F1xx) + /* + STM32F100xC STM32F100xD STM32F100xE https://www.st.com/resource/en/datasheet/stm32f100rc.pdf + --> V=1.41 + STM32F100x4 STM32F100x6 STM32F100x8 STM32F100xB https://www.st.com/resource/en/datasheet/stm32f100cb.pdf + --> V=1.41 + STM32F101x8 STM32F101xB https://www.st.com/resource/en/datasheet/stm32f101r8.pdf + STM32F101xC STM32F101xD STM32F101xE https://www.st.com/resource/en/datasheet/stm32f101rc.pdf + STM32F101x4 STM32F101x6 https://www.st.com/resource/en/datasheet/stm32f101c4.pdf + STM32F101xF STM32F101xG https://www.st.com/resource/en/datasheet/stm32f101vf.pdf + STM32F102x8 STM32F102xB https://www.st.com/resource/en/datasheet/stm32f102c8.pdf + --> V=1.42 / Slope=4.35 + STM32F102x4 STM32F102x6 https://www.st.com/resource/en/datasheet/stm32f102c4.pdf + --> V=1.42 / Slope=4.35 + STM32F103x8 STM32F103xB https://www.st.com/resource/en/datasheet/stm32f103c8.pdf + STM32F103xC STM32F103xD STM32F103xE https://www.st.com/resource/en/datasheet/stm32f103rc.pdf + STM32F103x4 STM32F103x6 https://www.st.com/resource/en/datasheet/stm32f103c4.pdf + STM32F103xF STM32F103xG https://www.st.com/resource/en/datasheet/stm32f103rg.pdf + STM32F105xx STM32F107xx https://www.st.com/resource/en/datasheet/stm32f105r8.pdf + */ + #define TS_TYPICAL_V 1.43 + #define TS_TYPICAL_TEMP 25 + #define TS_TYPICAL_SLOPE 4.3 + +#elif defined(STM32F2xx) + /* + STM32F205xx STM32F207xx https://www.st.com/resource/en/datasheet/stm32f205rb.pdf + STM32F215xx STM32F217xx https://www.st.com/resource/en/datasheet/stm32f215re.pdf + */ + #define TS_TYPICAL_V 0.76 + #define TS_TYPICAL_TEMP 25 + #define TS_TYPICAL_SLOPE 2.5 + +#elif defined(STM32F3xx) + /* + STM32F301x6 STM32F301x8 https://www.st.com/resource/en/datasheet/stm32f301c6.pdf + STM32F302xD STM32F302xE https://www.st.com/resource/en/datasheet/stm32f302re.pdf + STM32F302x6 STM32F302x8 https://www.st.com/resource/en/datasheet/stm32f302r6.pdf + STM32F302xB STM32F302xC https://www.st.com/resource/en/datasheet/stm32f302cb.pdf + STM32F303xD STM32F303xE https://www.st.com/resource/en/datasheet/stm32f303re.pdf + STM32F303xB STM32F303xC https://www.st.com/resource/en/datasheet/stm32f303cb.pdf + STM32F303x6/x8 https://www.st.com/resource/en/datasheet/stm32f303c6.pdf + STM32F334x4 STM32F334x6 STM32F334x8 https://www.st.com/resource/en/datasheet/stm32f334k4.pdf + STM32F373xx https://www.st.com/resource/en/datasheet/stm32f373cc.pdf + STM32F358xC https://www.st.com/resource/en/datasheet/stm32f358cc.pdf + STM32F378xx https://www.st.com/resource/en/datasheet/stm32f378cc.pdf + STM32F318C8 STM32F318K8 https://www.st.com/resource/en/datasheet/stm32f318c8.pdf + STM32F328C8 https://www.st.com/resource/en/datasheet/stm32f328c8.pdf + STM32F398VE https://www.st.com/resource/en/datasheet/stm32f398ve.pdf + */ + #define TS_CAL1_TEMP 30 + #define TS_CAL1_REGOFFSET 0x1FFFF7B8 + #define TS_CAL2_TEMP 110 + #define TS_CAL2_REGOFFSET 0x1FFFF7C2 + +#elif defined(STM32F4xx) + /* + STM32F401xD STM32F401xE https://www.st.com/resource/en/datasheet/stm32f401re.pdf + STM32F411xC STM32F411xE https://www.st.com/resource/en/datasheet/stm32f411ce.pdf + STM32F446xC/E https://www.st.com/resource/en/datasheet/stm32f446mc.pdf + STM32F479xx https://www.st.com/resource/en/datasheet/stm32f479ai.pdf + STM32F412xE STM32F412xG https://www.st.com/resource/en/datasheet/stm32f412ce.pdf + STM32F410x8 STM32F410xB https://www.st.com/resource/en/datasheet/stm32f410cb.pdf + STM32F469xx https://www.st.com/resource/en/datasheet/stm32f469ae.pdf + STM32F423xH https://www.st.com/resource/en/datasheet/stm32f423ch.pdf + STM32F413xG STM32F413xH https://www.st.com/resource/en/datasheet/stm32f413cg.pdf + STM32F415xx STM32F417xx https://www.st.com/resource/en/datasheet/stm32f415rg.pdf + STM32F405xx STM32F407xx https://www.st.com/resource/en/datasheet/stm32f405rg.pdf + STM32F427xx STM32F429xx https://www.st.com/resource/en/datasheet/stm32f427vg.pdf + STM32F437xx STM32F439xx https://www.st.com/resource/en/datasheet/stm32f437vg.pdf + STM32F401xB STM32F401xC https://www.st.com/resource/en/datasheet/stm32f401cb.pdf + */ + #define TS_CAL1_TEMP 30 + #define TS_CAL1_REGOFFSET 0x1FFF7A2C + #define TS_CAL2_TEMP 110 + #define TS_CAL2_REGOFFSET 0x1FFF7A2E + +#elif defined(STM32F7xx) + /* + STM32F756xx https://www.st.com/resource/en/datasheet/stm32f756bg.pdf + STM32F745xx STM32F746xx https://www.st.com/resource/en/datasheet/stm32f745ie.pdf + STM32F777xx STM32F778Ax STM32F779xx https://www.st.com/resource/en/datasheet/stm32f777bi.pdf + STM32F765xx STM32F767xx STM32F768Ax STM32F769xx https://www.st.com/resource/en/datasheet/stm32f765bi.pdf + STM32F722xx STM32F723xx https://www.st.com/resource/en/datasheet/stm32f722ic.pdf + --> TS_CAL1/2 = 0x1FF07A2C / 0x1FF07A2E + STM32F732xx STM32F733xx https://www.st.com/resource/en/datasheet/stm32f732ie.pdf + --> TS_CAL1/2 = 0x1FF07A2C / 0x1FF07A2E + STM32F750x8 https://www.st.com/resource/en/datasheet/stm32f750n8.pdf + STM32F730x8 https://www.st.com/resource/en/datasheet/stm32f730i8.pdf + --> TS_CAL1/2 = 0x1FF07A2C / 0x1FF07A2E + */ + #define TS_CAL1_TEMP 30 + #define TS_CAL1_REGOFFSET 0x1FF0F44C + #define TS_CAL2_TEMP 110 + #define TS_CAL2_REGOFFSET 0x1FF0F44E + +#elif defined(STM32G0xx) + /* + STM32G030x6/x8 https://www.st.com/resource/en/datasheet/stm32g030c6.pdf + --> TS_CAL2 not defined in datasheet + STM32G050x6/x8 https://www.st.com/resource/en/datasheet/stm32g050c6.pdf + STM32G0B0KE/CE/RE/VE https://www.st.com/resource/en/datasheet/stm32g0b0ce.pdf + --> TS_CAL2 not defined in datasheet + STM32G081xB https://www.st.com/resource/en/datasheet/stm32g081cb.pdf + STM32G071x8/xB https://www.st.com/resource/en/datasheet/stm32g071c8.pdf + STM32G031x4/x6/x8 https://www.st.com/resource/en/datasheet/stm32g031c6.pdf + STM32G041x6/x8 https://www.st.com/resource/en/datasheet/stm32g041c8.pdf + STM32G051x6/x8 https://www.st.com/resource/en/datasheet/stm32g051c6.pdf + STM32G061x6/x8 https://www.st.com/resource/en/datasheet/stm32g061c6.pdf + STM32G0B1xB/xC/xE https://www.st.com/resource/en/datasheet/stm32g0b1cc.pdf + STM32G0C1xC/xE https://www.st.com/resource/en/datasheet/stm32g0c1cc.pdf + */ + #define TS_CAL1_TEMP 30 + #define TS_CAL1_REGOFFSET 0x1FFF75A8 + #define TS_CAL2_TEMP 130 + #define TS_CAL2_REGOFFSET 0x1FFF75CA + +#elif defined(STM32G4xx) + /* + STM32G431x6 STM32G431x8 STM32G431xB https://www.st.com/resource/en/datasheet/stm32g431c6.pdf + STM32G441xB https://www.st.com/resource/en/datasheet/stm32g441cb.pdf + STM32G491xC STM32G491xE https://www.st.com/resource/en/datasheet/stm32g491cc.pdf + STM32G4A1xE https://www.st.com/resource/en/datasheet/stm32g4a1ce.pdf + STM32G473xB STM32G473xC STM32G473xE https://www.st.com/resource/en/datasheet/stm32g473cb.pdf + STM32G483xE https://www.st.com/resource/en/datasheet/stm32g483ce.pdf + --> TS_CAL1/TS_CAL2 not defined in datasheet + STM32G474xB STM32G474xC STM32G474xE https://www.st.com/resource/en/datasheet/stm32g474cb.pdf + STM32G484xE https://www.st.com/resource/en/datasheet/stm32g484ce.pdf + */ + #define TS_CAL1_TEMP 30 + #define TS_CAL1_REGOFFSET 0x1FFF75A8 + #define TS_CAL2_TEMP 130 + #define TS_CAL2_REGOFFSET 0x1FFF75CA + +#elif defined(STM32H7xx) + /* + STM32H7A3xI/G + --> TS_CAL1/2 = 0x08FFF814 / 0x08FFF818 + STM32H7B0xB + --> TS_CAL1/2 = 0x08FFF814 / 0x08FFF818 + STM32H7B3xI + --> TS_CAL1/2 = 0x08FFF814 / 0x08FFF818 + STM32H725xE/G + STM32H735xG + STM32H723VE STM32H723VG STM32H723ZE STM32H723ZG + STM32H730AB STM32H730IB STM32H730VB STM32H730ZB + STM32H733VG STM32H733ZG + STM32H742xI/G STM32H743xI/G + --> CAL2_TEMP = 110 + STM32H745xI/G + STM32H747xI/G + STM32H753xI + STM32H755xI + STM32H757xI + STM32H750VB STM32H750ZB STM32H750IB STM32H750XB + --> CAL2_TEMP = 110 + */ + #define TS_CAL1_TEMP 30 + #define TS_CAL1_REGOFFSET 0x1FF1E820 + #define TS_CAL2_TEMP 130 + #define TS_CAL2_REGOFFSET 0x1FF1E840 + +#elif defined(STM32L0xx) + /* + STM32L010RB + --> TS_CAL1/TS_CAL2 not defined in datasheet + STM32L010F4 STM32L010K4 + --> TS_CAL1/TS_CAL2 not defined in datasheet + STM32L010C6 + --> TS_CAL1/TS_CAL2 not defined in datasheet + STM32L010K8 STM32L010R8 + --> TS_CAL1/TS_CAL2 not defined in datasheet + STM32L011x3 STM32L011x4 + --> TS_CAL1 not defined in datasheet + STM32L021D4 STM32L021F4 STM32L021G4 STM32L021K4 + --> TS_CAL1 not defined in datasheet + STM32L031x4 STM32L031x6 + STM32L041x6 + STM32L051x6 STM32L051x8 + STM32L071x8 STM32L071xB STM32L071xZ + STM32L081CB STM32L081CZ STM32L081KZ + STM32L052x6 STM32L052x8 + STM32L062K8 STM32L062T8 STM32L062C8 + STM32L072x8 STM32L072xB STM32L072xZ + STM32L082KB STM32L082KZ STM32L082CZ + STM32L053C6 STM32L053C8 STM32L053R6 STM32L053R8 + STM32L063C8 STM32L063R8 + STM32L073x8 STM32L073xB STM32L073xZ + STM32L083x8 STM32L083xB STM32L083xZ + */ + #define TS_CAL1_TEMP 30 + #define TS_CAL1_REGOFFSET 0x1FF8007A + #define TS_CAL2_TEMP 130 + #define TS_CAL2_REGOFFSET 0x1FF8007E + +#elif defined(STM32L1xx) + /* + STM32L100x6/8/B-A + --> TS_CAL1/TS_CAL2 not defined in datasheet + STM32L100RC + --> TS_CAL1/TS_CAL2 not defined in datasheet + STM32L100C6 STM32L100R8/RB + --> TS_CAL1/TS_CAL2 not defined in datasheet + STM32L151x6/8/B-A STM32L152x6/8/B-A + --> TS_CAL1/2 = 0x08FFF814 / 0x08FFF818 + STM32L151xD STM32L152xD + STM32L151VD-X STM32L152VD-X + STM32L15xCC STM32L15xRC STM32L15xUC STM32L15xVC + STM32L15xQC STM32L15xRC-A STM32L15xVC-A STM32L15xZC + STM32L162xE + STM32L162VD STM32L162ZD STM32L162QD STM32L162RD + STM32L162VC STM32L162RC + STM32L162VD-X + STM32L162QC STM32L162VC-A STM32L162ZC STM32L162RC-A + */ + #define TS_CAL1_TEMP 30 + #define TS_CAL1_REGOFFSET 0x1FF800FA + #define TS_CAL2_TEMP 110 + #define TS_CAL2_REGOFFSET 0x1FF800FE + +#elif defined(STM32L4xx) + /* + STM32L431xx + STM32L451xx + STM32L471xx + --> CAL2_TEMP = 110 + STM32L412xx + STM32L422xx + STM32L432KB STM32L432KC + STM32L442KC + STM32L452xx + STM32L462CE STM32L462RE STM32L462VE + STM32L433xx + STM32L443CC STM32L443RC STM32L443VC + STM32L475xx + --> CAL2_TEMP = 110 + STM32L476xx + --> CAL2_TEMP = 110 + STM32L486xx : + --> CAL2_TEMP = 110 + STM32L496xx + STM32L4A6xG + */ + #define TS_CAL1_TEMP 30 + #define TS_CAL1_REGOFFSET 0x1FFF75A8 + #define TS_CAL2_TEMP 130 + #define TS_CAL2_REGOFFSET 0x1FFF75CA + +#elif defined(STM32MP1xx) + /* + STM32MP131A STM32MP131D + STM32MP131C STM32MP131F + STM32MP133A STM32MP133D + STM32MP133C STM32MP133F + STM32MP135A STM32MP135D + STM32MP135C STM32MP135F + STM32MP151A/D + STM32MP151C/F + STM32MP153A/D + STM32MP153C/F + STM32MP157A/D + STM32MP157C/F + */ + // BSEC -> RCC + //#define TS_CAL1_TEMP 30 + //#define TS_CAL1_REGOFFSET 0x5C00 525C[15:0] + //#define TS_CAL2_TEMP 130 + //#define TS_CAL2_REGOFFSET 0x5C00 525C[31:16] + +#elif defined(STM32WBxx) + /* + STM32WB10CC + STM32WB50CG STM32WB30CE + STM32WB15CC + STM32WB55xx STM32WB35xx + */ + #define TS_CAL1_TEMP 30 + #define TS_CAL1_REGOFFSET 0x1FFF75A8 + #define TS_CAL2_TEMP 130 + #define TS_CAL2_REGOFFSET 0x1FFF75CA +#endif + +// TODO implement voltage scaling (calibrated Vrefint) and ADC resolution scaling (when applicable) + +/** + * When provided in datasheet, the use of calibrated values (TS_CAL1, TS_CAL2) should always be preferred over typical values. + * Typical values may result in important variation from the actual temperature. + * + * If calibrated values are not provided in datasheet, it is encouraged to calibrate your specific chip yourself. + */ +#if defined(TS_CAL1_TEMP) && defined(TS_CAL1_REGOFFSET) && defined(TS_CAL2_TEMP) && defined(TS_CAL2_REGOFFSET) + + #define READMEMORY(ADDR) (*((uint16_t const *)(ADDR))) + #define TEMP_SOC_SENSOR(RAW) (float((TS_CAL2_TEMP) - (TS_CAL1_TEMP)) / (READMEMORY(TS_CAL2_REGOFFSET) - READMEMORY(TS_CAL1_REGOFFSET)) * ((RAW) / float(OVERSAMPLENR) - READMEMORY(TS_CAL1_REGOFFSET)) + (TS_CAL1_TEMP)) + +#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.0f) + TS_TYPICAL_TEMP) + +#endif diff --git a/Marlin/src/HAL/STM32/tft/gt911.cpp b/Marlin/src/HAL/STM32/tft/gt911.cpp new file mode 100644 index 0000000000..e57bccfef3 --- /dev/null +++ b/Marlin/src/HAL/STM32/tft/gt911.cpp @@ -0,0 +1,208 @@ +/** + * 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 . + * + */ +#include "../../platforms.h" + +#ifdef HAL_STM32 + +#include "../../../inc/MarlinConfig.h" + +#if ENABLED(TFT_TOUCH_DEVICE_GT911) + +#include "gt911.h" +#include "pinconfig.h" + +SW_IIC::SW_IIC(uint16_t sda, uint16_t scl) { + scl_pin = scl; + sda_pin = sda; +} + +// Software I2C hardware io init +void SW_IIC::init() { + OUT_WRITE(scl_pin, HIGH); + OUT_WRITE(sda_pin, HIGH); +} + +// Software I2C start signal +void SW_IIC::start() { + write_sda(HIGH); // SDA = 1 + write_scl(HIGH); // SCL = 1 + iic_delay(2); + write_sda(LOW); // SDA = 0 + iic_delay(1); + write_scl(LOW); // SCL = 0 // keep SCL low, avoid false stop caused by level jump caused by SDA switching IN/OUT +} + +// Software I2C stop signal +void SW_IIC::stop() { + write_scl(LOW); // SCL = 0 + iic_delay(2); + write_sda(LOW); // SDA = 0 + iic_delay(2); + write_scl(HIGH); // SCL = 1 + iic_delay(2); + write_sda(HIGH); // SDA = 1 +} + +// Software I2C sends ACK or NACK signal +void SW_IIC::send_ack(bool ack) { + write_sda(ack ? LOW : HIGH); // SDA = !ack + iic_delay(2); + write_scl(HIGH); // SCL = 1 + iic_delay(2); + write_scl(LOW); // SCL = 0 +} + +// Software I2C read ACK or NACK signal +bool SW_IIC::read_ack() { + bool error = 0; + set_sda_in(); + + iic_delay(2); + + write_scl(HIGH); // SCL = 1 + error = read_sda(); + + iic_delay(2); + + write_scl(LOW); // SCL = 0 + + set_sda_out(); + return error; +} + +void SW_IIC::send_byte(uint8_t txd) { + for (uint8_t i = 0; i < 8; ++i) { + write_sda(txd & 0x80); // write data bit + txd <<= 1; + iic_delay(1); + write_scl(HIGH); // SCL = 1 + iic_delay(2); + write_scl(LOW); // SCL = 0 + iic_delay(1); + } + + read_ack(); // wait ack +} + +uint8_t SW_IIC::read_byte(bool ack) { + uint8_t data = 0; + + set_sda_in(); + for (uint8_t i = 0; i < 8; ++i) { + write_scl(HIGH); // SCL = 1 + iic_delay(1); + data <<= 1; + if (read_sda()) data++; + write_scl(LOW); // SCL = 0 + iic_delay(2); + } + set_sda_out(); + + send_ack(ack); + + return data; +} + +GT911_REG_MAP GT911::reg; +SW_IIC GT911::sw_iic = SW_IIC(GT911_SW_I2C_SDA_PIN, GT911_SW_I2C_SCL_PIN); + +void GT911::write_reg(uint16_t reg, uint8_t reg_len, uint8_t* w_data, uint8_t w_len) { + sw_iic.start(); + sw_iic.send_byte(gt911_slave_address); // Set IIC Slave address + for (uint8_t i = 0; i < reg_len; ++i) { // Set reg address + uint8_t r = (reg >> (8 * (reg_len - 1 - i))) & 0xFF; + sw_iic.send_byte(r); + } + + for (uint8_t i = 0; i < w_len; ++i) { // Write data to reg + sw_iic.send_byte(w_data[i]); + } + sw_iic.stop(); +} + +void GT911::read_reg(uint16_t reg, uint8_t reg_len, uint8_t* r_data, uint8_t r_len) { + sw_iic.start(); + sw_iic.send_byte(gt911_slave_address); // Set IIC Slave address + for (uint8_t i = 0; i < reg_len; ++i) { // Set reg address + uint8_t r = (reg >> (8 * (reg_len - 1 - i))) & 0xFF; + sw_iic.send_byte(r); + } + + sw_iic.start(); + sw_iic.send_byte(gt911_slave_address + 1); // Set read mode + + for (uint8_t i = 0; i < r_len; ++i) + r_data[i] = sw_iic.read_byte(1); // Read data from reg + + sw_iic.stop(); +} + +void GT911::init() { + OUT_WRITE(GT911_RST_PIN, LOW); + OUT_WRITE(GT911_INT_PIN, LOW); + delay(11); + WRITE(GT911_INT_PIN, HIGH); + delayMicroseconds(110); + WRITE(GT911_RST_PIN, HIGH); + delay(6); + WRITE(GT911_INT_PIN, LOW); + delay(55); + SET_INPUT(GT911_INT_PIN); + + sw_iic.init(); + + uint8_t clear_reg = 0x00; + write_reg(0x814E, 2, &clear_reg, 1); // Reset to 0 for start +} + +bool GT911::getFirstTouchPoint(int16_t *x, int16_t *y) { + read_reg(0x814E, 2, ®.REG.status, 1); + + if (reg.REG.status >= 0x80 && reg.REG.status <= 0x85) { + read_reg(0x8150, 2, reg.map + 2, 38); + uint8_t clear_reg = 0x00; + write_reg(0x814E, 2, &clear_reg, 1); // Reset to 0 for start + // First touch point + *x = ((reg.REG.point[0].xh & 0x0F) << 8) | reg.REG.point[0].xl; + *y = ((reg.REG.point[0].yh & 0x0F) << 8) | reg.REG.point[0].yl; + return true; + } + return false; +} + +bool GT911::getRawPoint(int16_t * const x, int16_t * const y) { + static bool touched = false; + static int16_t read_x = 0, read_y = 0; + static millis_t next_time = 0; + + if (ELAPSED(millis(), next_time)) { + touched = getFirstTouchPoint(&read_x, &read_y); + next_time = millis() + 20; + } + + *x = read_x; + *y = read_y; + return touched; +} + +#endif // TFT_TOUCH_DEVICE_GT911 +#endif // HAL_STM32 diff --git a/Marlin/src/HAL/STM32/tft/gt911.h b/Marlin/src/HAL/STM32/tft/gt911.h new file mode 100644 index 0000000000..989517c183 --- /dev/null +++ b/Marlin/src/HAL/STM32/tft/gt911.h @@ -0,0 +1,96 @@ +/** + * 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 + +#include "../../../inc/MarlinConfig.h" + +#define GT911_SLAVE_ADDRESS 0x28 + +#if !PIN_EXISTS(GT911_RST) + #error "GT911_RST_PIN is not defined." +#elif !PIN_EXISTS(GT911_INT) + #error "GT911_INT_PIN is not defined." +#elif !PIN_EXISTS(GT911_SW_I2C_SCL) + #error "GT911_SW_I2C_SCL_PIN is not defined." +#elif !PIN_EXISTS(GT911_SW_I2C_SDA) + #error "GT911_SW_I2C_SDA_PIN is not defined." +#endif + +class SW_IIC { + private: + uint16_t scl_pin; + uint16_t sda_pin; + void write_scl(bool level) { WRITE(scl_pin, level); } + void write_sda(bool level) { WRITE(sda_pin, level); } + bool read_sda() { return READ(sda_pin); } + void set_sda_out() { SET_OUTPUT(sda_pin); } + void set_sda_in() { SET_INPUT_PULLUP(sda_pin); } + static void iic_delay(uint8_t t) { delayMicroseconds(t); } + + public: + SW_IIC(uint16_t sda, uint16_t scl); + // setSCL/SDA have to be called before begin() + void setSCL(uint16_t scl) { scl_pin = scl; } + void setSDA(uint16_t sda) { sda_pin = sda; } + void init(); // Initialize the IO port of IIC + void start(); // Send IIC start signal + void stop(); // Send IIC stop signal + void send_byte(uint8_t txd); // IIC sends a byte + uint8_t read_byte(bool ack); // IIC reads a byte + void send_ack(bool ack); // IIC sends ACK or NACK signal + bool read_ack(); +}; + +typedef struct __attribute__((__packed__)) { + uint8_t xl; + uint8_t xh; + uint8_t yl; + uint8_t yh; + uint8_t sizel; + uint8_t sizeh; + uint8_t reserved; + uint8_t track_id; +} GT911_POINT; + +typedef union __attribute__((__packed__)) { + uint8_t map[42]; + struct { + uint8_t status; // 0x814E + uint8_t track_id; // 0x814F + + GT911_POINT point[5]; // [0]:0x8150 - 0x8157 / [1]:0x8158 - 0x815F / [2]:0x8160 - 0x8167 / [3]:0x8168 - 0x816F / [4]:0x8170 - 0x8177 + } REG; +} GT911_REG_MAP; + +class GT911 { + private: + static const uint8_t gt911_slave_address = GT911_SLAVE_ADDRESS; + static GT911_REG_MAP reg; + static SW_IIC sw_iic; + static void write_reg(uint16_t reg, uint8_t reg_len, uint8_t* w_data, uint8_t w_len); + static void read_reg(uint16_t reg, uint8_t reg_len, uint8_t* r_data, uint8_t r_len); + + public: + static void init(); + static bool getFirstTouchPoint(int16_t *x, int16_t *y); + static bool getRawPoint(int16_t * const x, int16_t * const y); +}; diff --git a/Marlin/src/HAL/STM32/tft/tft_fsmc.cpp b/Marlin/src/HAL/STM32/tft/tft_fsmc.cpp index 3a080d5e27..70caef6778 100644 --- a/Marlin/src/HAL/STM32/tft/tft_fsmc.cpp +++ b/Marlin/src/HAL/STM32/tft/tft_fsmc.cpp @@ -20,6 +20,10 @@ * */ +#include "../../platforms.h" + +#ifdef HAL_STM32 + #include "../../../inc/MarlinConfig.h" #if HAS_FSMC_TFT @@ -31,150 +35,171 @@ SRAM_HandleTypeDef TFT_FSMC::SRAMx; DMA_HandleTypeDef TFT_FSMC::DMAtx; LCD_CONTROLLER_TypeDef *TFT_FSMC::LCD; -void TFT_FSMC::Init() { +void TFT_FSMC::init() { uint32_t controllerAddress; + FMC_OR_FSMC(NORSRAM_TimingTypeDef) timing, extTiming; - #if PIN_EXISTS(TFT_RESET) - OUT_WRITE(TFT_RESET_PIN, HIGH); - HAL_Delay(100); + uint32_t nsBank = (uint32_t)pinmap_peripheral(digitalPinToPinName(TFT_CS_PIN), pinMap_FSMC_CS); + + // Perform the SRAM1 memory initialization sequence + SRAMx.Instance = FMC_OR_FSMC(NORSRAM_DEVICE); + SRAMx.Extended = FMC_OR_FSMC(NORSRAM_EXTENDED_DEVICE); + + // SRAMx.Init + SRAMx.Init.NSBank = nsBank; + SRAMx.Init.DataAddressMux = FMC_OR_FSMC(DATA_ADDRESS_MUX_DISABLE); + SRAMx.Init.MemoryType = FMC_OR_FSMC(MEMORY_TYPE_SRAM); + #ifdef STM32F446xx + SRAMx.Init.MemoryDataWidth = TERN(TFT_INTERFACE_FMC_8BIT, FMC_NORSRAM_MEM_BUS_WIDTH_8, FMC_NORSRAM_MEM_BUS_WIDTH_16); + #else + SRAMx.Init.MemoryDataWidth = TERN(TFT_INTERFACE_FSMC_8BIT, FSMC_NORSRAM_MEM_BUS_WIDTH_8, FSMC_NORSRAM_MEM_BUS_WIDTH_16); + #endif + SRAMx.Init.BurstAccessMode = FMC_OR_FSMC(BURST_ACCESS_MODE_DISABLE); + SRAMx.Init.WaitSignalPolarity = FMC_OR_FSMC(WAIT_SIGNAL_POLARITY_LOW); + SRAMx.Init.WrapMode = FMC_OR_FSMC(WRAP_MODE_DISABLE); + SRAMx.Init.WaitSignalActive = FMC_OR_FSMC(WAIT_TIMING_BEFORE_WS); + SRAMx.Init.WriteOperation = FMC_OR_FSMC(WRITE_OPERATION_ENABLE); + SRAMx.Init.WaitSignal = FMC_OR_FSMC(WAIT_SIGNAL_DISABLE); + SRAMx.Init.ExtendedMode = FMC_OR_FSMC(EXTENDED_MODE_ENABLE); + SRAMx.Init.AsynchronousWait = FMC_OR_FSMC(ASYNCHRONOUS_WAIT_DISABLE); + SRAMx.Init.WriteBurst = FMC_OR_FSMC(WRITE_BURST_DISABLE); + #if defined(STM32F446xx) || defined(STM32F4xx) + SRAMx.Init.PageSize = FMC_OR_FSMC(PAGE_SIZE_NONE); #endif - #if PIN_EXISTS(TFT_BACKLIGHT) - OUT_WRITE(TFT_BACKLIGHT_PIN, HIGH); + // Read Timing - relatively slow to ensure ID information is correctly read from TFT controller + // Can be decreased from 15-15-24 to 4-4-8 with risk of stability loss + timing.AddressSetupTime = 15; + timing.AddressHoldTime = 15; + timing.DataSetupTime = 24; + timing.BusTurnAroundDuration = 0; + timing.CLKDivision = 16; + timing.DataLatency = 17; + timing.AccessMode = FMC_OR_FSMC(ACCESS_MODE_A); + + // Write Timing + // Can be decreased from 8-15-8 to 0-0-1 with risk of stability loss + extTiming.AddressSetupTime = 8; + extTiming.AddressHoldTime = 15; + extTiming.DataSetupTime = 8; + extTiming.BusTurnAroundDuration = 0; + extTiming.CLKDivision = 16; + extTiming.DataLatency = 17; + extTiming.AccessMode = FMC_OR_FSMC(ACCESS_MODE_A); + + #ifdef STM32F446xx + __HAL_RCC_FMC_CLK_ENABLE(); + #else + __HAL_RCC_FSMC_CLK_ENABLE(); #endif - FSMC_NORSRAM_TimingTypeDef Timing, ExtTiming; - - uint32_t NSBank = (uint32_t)pinmap_peripheral(digitalPinToPinName(TFT_CS_PIN), PinMap_FSMC_CS); - - SRAMx.Instance = FSMC_NORSRAM_DEVICE; - SRAMx.Extended = FSMC_NORSRAM_EXTENDED_DEVICE; - /* SRAMx.Init */ - SRAMx.Init.NSBank = NSBank; - SRAMx.Init.DataAddressMux = FSMC_DATA_ADDRESS_MUX_DISABLE; - SRAMx.Init.MemoryType = FSMC_MEMORY_TYPE_SRAM; - SRAMx.Init.MemoryDataWidth = FSMC_NORSRAM_MEM_BUS_WIDTH_16; - SRAMx.Init.BurstAccessMode = FSMC_BURST_ACCESS_MODE_DISABLE; - SRAMx.Init.WaitSignalPolarity = FSMC_WAIT_SIGNAL_POLARITY_LOW; - SRAMx.Init.WrapMode = FSMC_WRAP_MODE_DISABLE; - SRAMx.Init.WaitSignalActive = FSMC_WAIT_TIMING_BEFORE_WS; - SRAMx.Init.WriteOperation = FSMC_WRITE_OPERATION_ENABLE; - SRAMx.Init.WaitSignal = FSMC_WAIT_SIGNAL_DISABLE; - SRAMx.Init.ExtendedMode = FSMC_EXTENDED_MODE_ENABLE; - SRAMx.Init.AsynchronousWait = FSMC_ASYNCHRONOUS_WAIT_DISABLE; - SRAMx.Init.WriteBurst = FSMC_WRITE_BURST_DISABLE; - #ifdef STM32F4xx - SRAMx.Init.PageSize = FSMC_PAGE_SIZE_NONE; - #endif - /* Read Timing - relatively slow to ensure ID information is correctly read from TFT controller */ - /* Can be decreases from 15-15-24 to 4-4-8 with risk of stability loss */ - Timing.AddressSetupTime = 15; - Timing.AddressHoldTime = 15; - Timing.DataSetupTime = 24; - Timing.BusTurnAroundDuration = 0; - Timing.CLKDivision = 16; - Timing.DataLatency = 17; - Timing.AccessMode = FSMC_ACCESS_MODE_A; - /* Write Timing */ - /* Can be decreases from 8-15-8 to 0-0-1 with risk of stability loss */ - ExtTiming.AddressSetupTime = 8; - ExtTiming.AddressHoldTime = 15; - ExtTiming.DataSetupTime = 8; - ExtTiming.BusTurnAroundDuration = 0; - ExtTiming.CLKDivision = 16; - ExtTiming.DataLatency = 17; - ExtTiming.AccessMode = FSMC_ACCESS_MODE_A; - - __HAL_RCC_FSMC_CLK_ENABLE(); - - for (uint16_t i = 0; PinMap_FSMC[i].pin != NC; i++) - pinmap_pinout(PinMap_FSMC[i].pin, PinMap_FSMC); - pinmap_pinout(digitalPinToPinName(TFT_CS_PIN), PinMap_FSMC_CS); - pinmap_pinout(digitalPinToPinName(TFT_RS_PIN), PinMap_FSMC_RS); + for (uint16_t i = 0; pinMap_FSMC[i].pin != NC; i++) + pinmap_pinout(pinMap_FSMC[i].pin, pinMap_FSMC); + pinmap_pinout(digitalPinToPinName(TFT_CS_PIN), pinMap_FSMC_CS); + pinmap_pinout(digitalPinToPinName(TFT_RS_PIN), pinMap_FSMC_RS); controllerAddress = FSMC_BANK1_1; #ifdef PF0 - switch (NSBank) { - case FSMC_NORSRAM_BANK2: controllerAddress = FSMC_BANK1_2 ; break; - case FSMC_NORSRAM_BANK3: controllerAddress = FSMC_BANK1_3 ; break; - case FSMC_NORSRAM_BANK4: controllerAddress = FSMC_BANK1_4 ; break; + switch (nsBank) { + case FMC_OR_FSMC(NORSRAM_BANK2): controllerAddress = FSMC_BANK1_2; break; + case FMC_OR_FSMC(NORSRAM_BANK3): controllerAddress = FSMC_BANK1_3; break; + case FMC_OR_FSMC(NORSRAM_BANK4): controllerAddress = FSMC_BANK1_4; break; } #endif - controllerAddress |= (uint32_t)pinmap_peripheral(digitalPinToPinName(TFT_RS_PIN), PinMap_FSMC_RS); + controllerAddress |= (uint32_t)pinmap_peripheral(digitalPinToPinName(TFT_RS_PIN), pinMap_FSMC_RS); - HAL_SRAM_Init(&SRAMx, &Timing, &ExtTiming); + HAL_SRAM_Init(&SRAMx, &timing, &extTiming); __HAL_RCC_DMA2_CLK_ENABLE(); #ifdef STM32F1xx - DMAtx.Instance = DMA2_Channel1; + DMAtx.Instance = DMA2_Channel1; #elif defined(STM32F4xx) - DMAtx.Instance = DMA2_Stream0; - DMAtx.Init.Channel = DMA_CHANNEL_0; - DMAtx.Init.FIFOMode = DMA_FIFOMODE_ENABLE; - DMAtx.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL; - DMAtx.Init.MemBurst = DMA_MBURST_SINGLE; - DMAtx.Init.PeriphBurst = DMA_PBURST_SINGLE; + DMAtx.Instance = DMA2_Stream0; + DMAtx.Init.Channel = DMA_CHANNEL_0; + DMAtx.Init.FIFOMode = DMA_FIFOMODE_ENABLE; + DMAtx.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL; + DMAtx.Init.MemBurst = DMA_MBURST_SINGLE; + DMAtx.Init.PeriphBurst = DMA_PBURST_SINGLE; #endif - DMAtx.Init.Direction = DMA_MEMORY_TO_MEMORY; - DMAtx.Init.MemInc = DMA_MINC_DISABLE; - DMAtx.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; - DMAtx.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; - DMAtx.Init.Mode = DMA_NORMAL; - DMAtx.Init.Priority = DMA_PRIORITY_HIGH; + DMAtx.Init.Direction = DMA_MEMORY_TO_MEMORY; + DMAtx.Init.MemInc = DMA_MINC_DISABLE; + DMAtx.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; + DMAtx.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; + DMAtx.Init.Mode = DMA_NORMAL; + 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() { - uint32_t id; - WriteReg(0x0000); - id = LCD->RAM; - - if (id == 0) - id = ReadID(LCD_READ_ID); - if ((id & 0xFFFF) == 0 || (id & 0xFFFF) == 0xFFFF) - id = ReadID(LCD_READ_ID4); +uint32_t TFT_FSMC::getID() { + writeReg(0); + uint32_t id = LCD->RAM; + if (id == 0) id = readID(LCD_READ_ID); + if ((id & 0xFFFF) == 0 || (id & 0xFFFF) == 0xFFFF) id = readID(LCD_READ_ID4); return id; } - uint32_t TFT_FSMC::ReadID(uint16_t Reg) { - uint32_t id; - WriteReg(Reg); - id = LCD->RAM; // dummy read - id = Reg << 24; - id |= (LCD->RAM & 0x00FF) << 16; - id |= (LCD->RAM & 0x00FF) << 8; - id |= LCD->RAM & 0x00FF; - return id; - } - -bool TFT_FSMC::isBusy() { - if (__IS_DMA_ENABLED(&DMAtx)) - if (__HAL_DMA_GET_FLAG(&DMAtx, __HAL_DMA_GET_TC_FLAG_INDEX(&DMAtx)) != 0 || __HAL_DMA_GET_FLAG(&DMAtx, __HAL_DMA_GET_TE_FLAG_INDEX(&DMAtx)) != 0) - Abort(); - return __IS_DMA_ENABLED(&DMAtx); +uint32_t TFT_FSMC::readID(tft_data_t inReg) { + writeReg(inReg); + uint32_t id = LCD->RAM; // dummy read + id = inReg << 24; + id |= (LCD->RAM & 0x00FF) << 16; + id |= (LCD->RAM & 0x00FF) << 8; + id |= (LCD->RAM & 0x00FF); + return id; } -void TFT_FSMC::TransmitDMA(uint32_t MemoryIncrease, uint16_t *Data, uint16_t Count) { - DMAtx.Init.PeriphInc = MemoryIncrease; - HAL_DMA_Init(&DMAtx); - - __HAL_DMA_CLEAR_FLAG(&DMAtx, __HAL_DMA_GET_TC_FLAG_INDEX(&DMAtx)); - __HAL_DMA_CLEAR_FLAG(&DMAtx, __HAL_DMA_GET_TE_FLAG_INDEX(&DMAtx)); - +bool TFT_FSMC::isBusy() { #ifdef STM32F1xx - DMAtx.Instance->CNDTR = Count; - DMAtx.Instance->CPAR = (uint32_t)Data; - DMAtx.Instance->CMAR = (uint32_t)&(LCD->RAM); + #define __IS_DMA_ENABLED(__HANDLE__) ((__HANDLE__)->Instance->CCR & DMA_CCR_EN) + #define __IS_DMA_CONFIGURED(__HANDLE__) ((__HANDLE__)->Instance->CPAR != 0) #elif defined(STM32F4xx) - DMAtx.Instance->NDTR = Count; - DMAtx.Instance->PAR = (uint32_t)Data; - DMAtx.Instance->M0AR = (uint32_t)&(LCD->RAM); + #define __IS_DMA_ENABLED(__HANDLE__) ((__HANDLE__)->Instance->CR & DMA_SxCR_EN) + #define __IS_DMA_CONFIGURED(__HANDLE__) ((__HANDLE__)->Instance->PAR != 0) #endif - __HAL_DMA_ENABLE(&DMAtx); + + #ifdef __IS_DMA_CONFIGURED + if (!__IS_DMA_CONFIGURED(&DMAtx)) return false; + #endif + + // Check if DMA transfer error or transfer complete flags are set + if ((__HAL_DMA_GET_FLAG(&DMAtx, __HAL_DMA_GET_TE_FLAG_INDEX(&DMAtx)) == 0) && (__HAL_DMA_GET_FLAG(&DMAtx, __HAL_DMA_GET_TC_FLAG_INDEX(&DMAtx)) == 0)) return true; + + __DSB(); + abort(); + return false; +} + +void TFT_FSMC::abort() { + HAL_DMA_Abort(&DMAtx); // Abort DMA transfer if any + HAL_DMA_DeInit(&DMAtx); // Deconfigure DMA +} + +void TFT_FSMC::transmitDMA(uint32_t memoryIncrease, uint16_t *data, uint16_t count) { + 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) { + 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); + abort(); } #endif // HAS_FSMC_TFT +#endif // HAL_STM32 diff --git a/Marlin/src/HAL/STM32/tft/tft_fsmc.h b/Marlin/src/HAL/STM32/tft/tft_fsmc.h index cbec7613ef..2647923e8e 100644 --- a/Marlin/src/HAL/STM32/tft/tft_fsmc.h +++ b/Marlin/src/HAL/STM32/tft/tft_fsmc.h @@ -21,36 +21,42 @@ */ #pragma once +#include "../../../inc/MarlinConfig.h" + #ifdef STM32F1xx #include "stm32f1xx_hal.h" #elif defined(STM32F4xx) #include "stm32f4xx_hal.h" #else - #error FSMC TFT is currently only supported on STM32F1 and STM32F4 hardware. + #error "FSMC/FMC TFT is currently only supported on STM32F1 and STM32F4 hardware." #endif #ifndef LCD_READ_ID - #define LCD_READ_ID 0x04 // Read display identification information (0xD3 on ILI9341) + #define LCD_READ_ID 0x04 // Read display identification information (0xD3 on ILI9341) #endif #ifndef LCD_READ_ID4 #define LCD_READ_ID4 0xD3 // Read display identification information (0xD3 on ILI9341) #endif -#define DATASIZE_8BIT SPI_DATASIZE_8BIT -#define DATASIZE_16BIT SPI_DATASIZE_16BIT -#define TFT_IO_DRIVER TFT_FSMC +#define DATASIZE_8BIT SPI_DATASIZE_8BIT +#define DATASIZE_16BIT SPI_DATASIZE_16BIT +#define TFT_IO_DRIVER TFT_FSMC +#define DMA_MAX_WORDS 0xFFFF -#ifdef STM32F1xx - #define __IS_DMA_ENABLED(__HANDLE__) ((__HANDLE__)->Instance->CCR & DMA_CCR_EN) -#elif defined(STM32F4xx) - #define __IS_DMA_ENABLED(__HANDLE__) ((__HANDLE__)->Instance->CR & DMA_SxCR_EN) -#endif +#define TFT_DATASIZE TERN(TFT_INTERFACE_FSMC_8BIT, DATASIZE_8BIT, DATASIZE_16BIT) +typedef TERN(TFT_INTERFACE_FSMC_8BIT, uint8_t, uint16_t) tft_data_t; typedef struct { - __IO uint16_t REG; - __IO uint16_t RAM; + __IO tft_data_t REG; + __IO tft_data_t RAM; } LCD_CONTROLLER_TypeDef; +#ifdef STM32F446xx + #define FMC_OR_FSMC(N) _CAT(FMC_, N) +#else + #define FMC_OR_FSMC(N) _CAT(FSMC_, N) +#endif + class TFT_FSMC { private: static SRAM_HandleTypeDef SRAMx; @@ -58,31 +64,43 @@ class TFT_FSMC { static LCD_CONTROLLER_TypeDef *LCD; - static uint32_t ReadID(uint16_t Reg); - static void Transmit(uint16_t Data) { LCD->RAM = Data; __DSB(); } - static void TransmitDMA(uint32_t MemoryIncrease, uint16_t *Data, uint16_t Count); + static uint32_t readID(tft_data_t inReg); + static void transmit(tft_data_t data) { LCD->RAM = data; __DSB(); } + static void transmit(uint32_t memoryIncrease, uint16_t *data, uint16_t count); + static void transmitDMA(uint32_t memoryIncrease, uint16_t *data, uint16_t count); public: - static void Init(); - static uint32_t GetID(); + static void init(); + static uint32_t getID(); static bool isBusy(); - static void Abort() { __HAL_DMA_DISABLE(&DMAtx); } + static void abort(); - static void DataTransferBegin(uint16_t DataWidth = DATASIZE_16BIT) {} - static void DataTransferEnd() {}; + static void dataTransferBegin(uint16_t dataWidth=TFT_DATASIZE) {} + static void dataTransferEnd() {} - static void WriteData(uint16_t Data) { Transmit(Data); } - static void WriteReg(uint16_t Reg) { LCD->REG = Reg; __DSB(); } + static void writeData(uint16_t data) { transmit(tft_data_t(data)); } + static void writeReg(const uint16_t inReg) { LCD->REG = tft_data_t(inReg); __DSB(); } - static void WriteSequence(uint16_t *Data, uint16_t Count) { TransmitDMA(DMA_PINC_ENABLE, Data, Count); } - static void WriteMultiple(uint16_t Color, uint16_t Count) { static uint16_t Data; Data = Color; TransmitDMA(DMA_PINC_DISABLE, &Data, Count); } + static void writeSequence_DMA(uint16_t *data, uint16_t count) { transmitDMA(DMA_PINC_ENABLE, data, count); } + static void writeMultiple_DMA(uint16_t color, uint16_t count) { static uint16_t data; data = color; transmitDMA(DMA_PINC_DISABLE, &data, count); } + + static void writeSequence(uint16_t *data, uint16_t count) { transmit(DMA_PINC_ENABLE, data, count); } + static void writeMultiple(uint16_t color, uint32_t count) { + while (count > 0) { + transmit(DMA_MINC_DISABLE, &color, count > DMA_MAX_WORDS ? DMA_MAX_WORDS : count); + count = count > DMA_MAX_WORDS ? count - DMA_MAX_WORDS : 0; + } + } }; - #ifdef STM32F1xx #define FSMC_PIN_DATA STM_PIN_DATA(STM_MODE_AF_PP, GPIO_NOPULL, AFIO_NONE) #elif defined(STM32F4xx) - #define FSMC_PIN_DATA STM_PIN_DATA(STM_MODE_AF_PP, GPIO_NOPULL, GPIO_AF12_FSMC) + #ifdef STM32F446xx + #define FSMC_PIN_DATA STM_PIN_DATA(STM_MODE_AF_PP, GPIO_NOPULL, GPIO_AF12_FMC) + #else + #define FSMC_PIN_DATA STM_PIN_DATA(STM_MODE_AF_PP, GPIO_NOPULL, GPIO_AF12_FSMC) + #endif #define FSMC_BANK1_1 0x60000000U #define FSMC_BANK1_2 0x64000000U #define FSMC_BANK1_3 0x68000000U @@ -91,41 +109,47 @@ class TFT_FSMC { #error No configuration for this MCU #endif -const PinMap PinMap_FSMC[] = { - {PD_14, FSMC_NORSRAM_DEVICE, FSMC_PIN_DATA}, // FSMC_D00 - {PD_15, FSMC_NORSRAM_DEVICE, FSMC_PIN_DATA}, // FSMC_D01 - {PD_0, FSMC_NORSRAM_DEVICE, FSMC_PIN_DATA}, // FSMC_D02 - {PD_1, FSMC_NORSRAM_DEVICE, FSMC_PIN_DATA}, // FSMC_D03 - {PE_7, FSMC_NORSRAM_DEVICE, FSMC_PIN_DATA}, // FSMC_D04 - {PE_8, FSMC_NORSRAM_DEVICE, FSMC_PIN_DATA}, // FSMC_D05 - {PE_9, FSMC_NORSRAM_DEVICE, FSMC_PIN_DATA}, // FSMC_D06 - {PE_10, FSMC_NORSRAM_DEVICE, FSMC_PIN_DATA}, // FSMC_D07 - {PE_11, FSMC_NORSRAM_DEVICE, FSMC_PIN_DATA}, // FSMC_D08 - {PE_12, FSMC_NORSRAM_DEVICE, FSMC_PIN_DATA}, // FSMC_D09 - {PE_13, FSMC_NORSRAM_DEVICE, FSMC_PIN_DATA}, // FSMC_D10 - {PE_14, FSMC_NORSRAM_DEVICE, FSMC_PIN_DATA}, // FSMC_D11 - {PE_15, FSMC_NORSRAM_DEVICE, FSMC_PIN_DATA}, // FSMC_D12 - {PD_8, FSMC_NORSRAM_DEVICE, FSMC_PIN_DATA}, // FSMC_D13 - {PD_9, FSMC_NORSRAM_DEVICE, FSMC_PIN_DATA}, // FSMC_D14 - {PD_10, FSMC_NORSRAM_DEVICE, FSMC_PIN_DATA}, // FSMC_D15 - {PD_4, FSMC_NORSRAM_DEVICE, FSMC_PIN_DATA}, // FSMC_NOE - {PD_5, FSMC_NORSRAM_DEVICE, FSMC_PIN_DATA}, // FSMC_NWE +const PinMap pinMap_FSMC[] = { + {PD_14, FMC_OR_FSMC(NORSRAM_DEVICE), FSMC_PIN_DATA}, // FSMC_D00 + {PD_15, FMC_OR_FSMC(NORSRAM_DEVICE), FSMC_PIN_DATA}, // FSMC_D01 + {PD_0, FMC_OR_FSMC(NORSRAM_DEVICE), FSMC_PIN_DATA}, // FSMC_D02 + {PD_1, FMC_OR_FSMC(NORSRAM_DEVICE), FSMC_PIN_DATA}, // FSMC_D03 + {PE_7, FMC_OR_FSMC(NORSRAM_DEVICE), FSMC_PIN_DATA}, // FSMC_D04 + {PE_8, FMC_OR_FSMC(NORSRAM_DEVICE), FSMC_PIN_DATA}, // FSMC_D05 + {PE_9, FMC_OR_FSMC(NORSRAM_DEVICE), FSMC_PIN_DATA}, // FSMC_D06 + {PE_10, FMC_OR_FSMC(NORSRAM_DEVICE), FSMC_PIN_DATA}, // FSMC_D07 + #if DISABLED(TFT_INTERFACE_FSMC_8BIT) + {PE_11, FMC_OR_FSMC(NORSRAM_DEVICE), FSMC_PIN_DATA}, // FSMC_D08 + {PE_12, FMC_OR_FSMC(NORSRAM_DEVICE), FSMC_PIN_DATA}, // FSMC_D09 + {PE_13, FMC_OR_FSMC(NORSRAM_DEVICE), FSMC_PIN_DATA}, // FSMC_D10 + {PE_14, FMC_OR_FSMC(NORSRAM_DEVICE), FSMC_PIN_DATA}, // FSMC_D11 + {PE_15, FMC_OR_FSMC(NORSRAM_DEVICE), FSMC_PIN_DATA}, // FSMC_D12 + {PD_8, FMC_OR_FSMC(NORSRAM_DEVICE), FSMC_PIN_DATA}, // FSMC_D13 + {PD_9, FMC_OR_FSMC(NORSRAM_DEVICE), FSMC_PIN_DATA}, // FSMC_D14 + {PD_10, FMC_OR_FSMC(NORSRAM_DEVICE), FSMC_PIN_DATA}, // FSMC_D15 + #endif + {PD_4, FMC_OR_FSMC(NORSRAM_DEVICE), FSMC_PIN_DATA}, // FSMC_NOE + {PD_5, FMC_OR_FSMC(NORSRAM_DEVICE), FSMC_PIN_DATA}, // FSMC_NWE {NC, NP, 0} }; -const PinMap PinMap_FSMC_CS[] = { - {PD_7, (void *)FSMC_NORSRAM_BANK1, FSMC_PIN_DATA}, // FSMC_NE1 +const PinMap pinMap_FSMC_CS[] = { + {PD_7, (void *)FMC_OR_FSMC(NORSRAM_BANK1), FSMC_PIN_DATA}, // FSMC_NE1 #ifdef PF0 - {PG_9, (void *)FSMC_NORSRAM_BANK2, FSMC_PIN_DATA}, // FSMC_NE2 - {PG_10, (void *)FSMC_NORSRAM_BANK3, FSMC_PIN_DATA}, // FSMC_NE3 - {PG_12, (void *)FSMC_NORSRAM_BANK4, FSMC_PIN_DATA}, // FSMC_NE4 + {PG_9, (void *)FMC_OR_FSMC(NORSRAM_BANK2), FSMC_PIN_DATA}, // FSMC_NE2 + {PG_10, (void *)FMC_OR_FSMC(NORSRAM_BANK3), FSMC_PIN_DATA}, // FSMC_NE3 + {PG_12, (void *)FMC_OR_FSMC(NORSRAM_BANK4), FSMC_PIN_DATA}, // FSMC_NE4 #endif {NC, NP, 0} }; -#define FSMC_RS(A) (void *)((2 << A) - 2) +#if ENABLED(TFT_INTERFACE_FSMC_8BIT) + #define FSMC_RS(A) (void *)((2 << (A-1)) - 1) +#else + #define FSMC_RS(A) (void *)((2 << A) - 2) +#endif -const PinMap PinMap_FSMC_RS[] = { +const PinMap pinMap_FSMC_RS[] = { #ifdef PF0 {PF_0, FSMC_RS( 0), FSMC_PIN_DATA}, // FSMC_A0 {PF_1, FSMC_RS( 1), FSMC_PIN_DATA}, // FSMC_A1 diff --git a/Marlin/src/HAL/STM32/tft/tft_ltdc.cpp b/Marlin/src/HAL/STM32/tft/tft_ltdc.cpp new file mode 100644 index 0000000000..3bbc39f20a --- /dev/null +++ b/Marlin/src/HAL/STM32/tft/tft_ltdc.cpp @@ -0,0 +1,385 @@ +/** + * 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 . + * + */ +#include "../../platforms.h" + +#ifdef HAL_STM32 + +#include "../../../inc/MarlinConfig.h" + +#if HAS_LTDC_TFT + +#include "tft_ltdc.h" +#include "pinconfig.h" + +#define FRAME_BUFFER_ADDRESS 0XC0000000 // SDRAM address + +#define SDRAM_TIMEOUT ((uint32_t)0xFFFF) +#define REFRESH_COUNT ((uint32_t)0x02A5) // SDRAM refresh counter + +#define SDRAM_MODEREG_BURST_LENGTH_1 ((uint16_t)0x0000) +#define SDRAM_MODEREG_BURST_LENGTH_2 ((uint16_t)0x0001) +#define SDRAM_MODEREG_BURST_LENGTH_4 ((uint16_t)0x0002) +#define SDRAM_MODEREG_BURST_LENGTH_8 ((uint16_t)0x0004) +#define SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL ((uint16_t)0x0000) +#define SDRAM_MODEREG_BURST_TYPE_INTERLEAVED ((uint16_t)0x0008) +#define SDRAM_MODEREG_CAS_LATENCY_2 ((uint16_t)0x0020) +#define SDRAM_MODEREG_CAS_LATENCY_3 ((uint16_t)0x0030) +#define SDRAM_MODEREG_OPERATING_MODE_STANDARD ((uint16_t)0x0000) +#define SDRAM_MODEREG_WRITEBURST_MODE_PROGRAMMED ((uint16_t)0x0000) +#define SDRAM_MODEREG_WRITEBURST_MODE_SINGLE ((uint16_t)0x0200) + +void SDRAM_Initialization_Sequence(SDRAM_HandleTypeDef *hsdram, FMC_SDRAM_CommandTypeDef *Command) { + + __IO uint32_t tmpmrd =0; + /* Step 1: Configure a clock configuration enable command */ + Command->CommandMode = FMC_SDRAM_CMD_CLK_ENABLE; + Command->CommandTarget = FMC_SDRAM_CMD_TARGET_BANK1; + Command->AutoRefreshNumber = 1; + Command->ModeRegisterDefinition = 0; + /* Send the command */ + HAL_SDRAM_SendCommand(hsdram, Command, SDRAM_TIMEOUT); + + /* Step 2: Insert 100 us minimum delay */ + /* Inserted delay is equal to 1 ms due to systick time base unit (ms) */ + HAL_Delay(1); + + /* Step 3: Configure a PALL (precharge all) command */ + Command->CommandMode = FMC_SDRAM_CMD_PALL; + Command->CommandTarget = FMC_SDRAM_CMD_TARGET_BANK1; + Command->AutoRefreshNumber = 1; + Command->ModeRegisterDefinition = 0; + /* Send the command */ + HAL_SDRAM_SendCommand(hsdram, Command, SDRAM_TIMEOUT); + + /* Step 4 : Configure a Auto-Refresh command */ + Command->CommandMode = FMC_SDRAM_CMD_AUTOREFRESH_MODE; + Command->CommandTarget = FMC_SDRAM_CMD_TARGET_BANK1; + Command->AutoRefreshNumber = 8; + Command->ModeRegisterDefinition = 0; + /* Send the command */ + HAL_SDRAM_SendCommand(hsdram, Command, SDRAM_TIMEOUT); + + /* Step 5: Program the external memory mode register */ + tmpmrd = (uint32_t)(SDRAM_MODEREG_BURST_LENGTH_1 | + SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL | + SDRAM_MODEREG_CAS_LATENCY_2 | + SDRAM_MODEREG_OPERATING_MODE_STANDARD | + SDRAM_MODEREG_WRITEBURST_MODE_SINGLE); + + Command->CommandMode = FMC_SDRAM_CMD_LOAD_MODE; + Command->CommandTarget = FMC_SDRAM_CMD_TARGET_BANK1; + Command->AutoRefreshNumber = 1; + Command->ModeRegisterDefinition = tmpmrd; + /* Send the command */ + HAL_SDRAM_SendCommand(hsdram, Command, SDRAM_TIMEOUT); + + /* Step 6: Set the refresh rate counter */ + /* Set the device refresh rate */ + HAL_SDRAM_ProgramRefreshRate(hsdram, REFRESH_COUNT); +} + +void SDRAM_Config() { + + __HAL_RCC_SYSCFG_CLK_ENABLE(); + __HAL_RCC_FMC_CLK_ENABLE(); + + SDRAM_HandleTypeDef hsdram; + FMC_SDRAM_TimingTypeDef SDRAM_Timing; + FMC_SDRAM_CommandTypeDef command; + + /* Configure the SDRAM device */ + hsdram.Instance = FMC_SDRAM_DEVICE; + hsdram.Init.SDBank = FMC_SDRAM_BANK1; + hsdram.Init.ColumnBitsNumber = FMC_SDRAM_COLUMN_BITS_NUM_9; + hsdram.Init.RowBitsNumber = FMC_SDRAM_ROW_BITS_NUM_13; + hsdram.Init.MemoryDataWidth = FMC_SDRAM_MEM_BUS_WIDTH_16; + hsdram.Init.InternalBankNumber = FMC_SDRAM_INTERN_BANKS_NUM_4; + hsdram.Init.CASLatency = FMC_SDRAM_CAS_LATENCY_2; + hsdram.Init.WriteProtection = FMC_SDRAM_WRITE_PROTECTION_DISABLE; + hsdram.Init.SDClockPeriod = FMC_SDRAM_CLOCK_PERIOD_2; + hsdram.Init.ReadBurst = FMC_SDRAM_RBURST_ENABLE; + hsdram.Init.ReadPipeDelay = FMC_SDRAM_RPIPE_DELAY_0; + + /* Timing configuration for 100Mhz as SDRAM clock frequency (System clock is up to 200Mhz) */ + SDRAM_Timing.LoadToActiveDelay = 2; + SDRAM_Timing.ExitSelfRefreshDelay = 8; + SDRAM_Timing.SelfRefreshTime = 6; + SDRAM_Timing.RowCycleDelay = 6; + SDRAM_Timing.WriteRecoveryTime = 2; + SDRAM_Timing.RPDelay = 2; + SDRAM_Timing.RCDDelay = 2; + + /* Initialize the SDRAM controller */ + if (HAL_SDRAM_Init(&hsdram, &SDRAM_Timing) != HAL_OK) + { + /* Initialization Error */ + } + + /* Program the SDRAM external device */ + SDRAM_Initialization_Sequence(&hsdram, &command); +} + +void LTDC_Config() { + + __HAL_RCC_LTDC_CLK_ENABLE(); + __HAL_RCC_DMA2D_CLK_ENABLE(); + + RCC_PeriphCLKInitTypeDef PeriphClkInitStruct; + + /* The PLL3R is configured to provide the LTDC PCLK clock */ + /* PLL3_VCO Input = HSE_VALUE / PLL3M = 25Mhz / 5 = 5 Mhz */ + /* PLL3_VCO Output = PLL3_VCO Input * PLL3N = 5Mhz * 160 = 800 Mhz */ + /* PLLLCDCLK = PLL3_VCO Output/PLL3R = 800Mhz / 16 = 50Mhz */ + /* LTDC clock frequency = PLLLCDCLK = 50 Mhz */ + PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_LTDC; + PeriphClkInitStruct.PLL3.PLL3M = 5; + PeriphClkInitStruct.PLL3.PLL3N = 160; + PeriphClkInitStruct.PLL3.PLL3FRACN = 0; + PeriphClkInitStruct.PLL3.PLL3P = 2; + PeriphClkInitStruct.PLL3.PLL3Q = 2; + PeriphClkInitStruct.PLL3.PLL3R = (800 / LTDC_LCD_CLK); + PeriphClkInitStruct.PLL3.PLL3VCOSEL = RCC_PLL3VCOWIDE; + PeriphClkInitStruct.PLL3.PLL3RGE = RCC_PLL3VCIRANGE_2; + HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct); + + LTDC_HandleTypeDef hltdc_F; + LTDC_LayerCfgTypeDef pLayerCfg; + + /* LTDC Initialization -------------------------------------------------------*/ + + /* Polarity configuration */ + /* Initialize the horizontal synchronization polarity as active low */ + hltdc_F.Init.HSPolarity = LTDC_HSPOLARITY_AL; + /* Initialize the vertical synchronization polarity as active low */ + hltdc_F.Init.VSPolarity = LTDC_VSPOLARITY_AL; + /* Initialize the data enable polarity as active low */ + hltdc_F.Init.DEPolarity = LTDC_DEPOLARITY_AL; + /* Initialize the pixel clock polarity as input pixel clock */ + hltdc_F.Init.PCPolarity = LTDC_PCPOLARITY_IPC; + + /* Timing configuration */ + hltdc_F.Init.HorizontalSync = (LTDC_LCD_HSYNC - 1); + hltdc_F.Init.VerticalSync = (LTDC_LCD_VSYNC - 1); + hltdc_F.Init.AccumulatedHBP = (LTDC_LCD_HSYNC + LTDC_LCD_HBP - 1); + hltdc_F.Init.AccumulatedVBP = (LTDC_LCD_VSYNC + LTDC_LCD_VBP - 1); + hltdc_F.Init.AccumulatedActiveH = (TFT_HEIGHT + LTDC_LCD_VSYNC + LTDC_LCD_VBP - 1); + hltdc_F.Init.AccumulatedActiveW = (TFT_WIDTH + LTDC_LCD_HSYNC + LTDC_LCD_HBP - 1); + hltdc_F.Init.TotalHeigh = (TFT_HEIGHT + LTDC_LCD_VSYNC + LTDC_LCD_VBP + LTDC_LCD_VFP - 1); + hltdc_F.Init.TotalWidth = (TFT_WIDTH + LTDC_LCD_HSYNC + LTDC_LCD_HBP + LTDC_LCD_HFP - 1); + + /* Configure R,G,B component values for LCD background color : all black background */ + hltdc_F.Init.Backcolor.Blue = 0; + hltdc_F.Init.Backcolor.Green = 0; + hltdc_F.Init.Backcolor.Red = 0; + + hltdc_F.Instance = LTDC; + + /* Layer0 Configuration ------------------------------------------------------*/ + + /* Windowing configuration */ + pLayerCfg.WindowX0 = 0; + pLayerCfg.WindowX1 = TFT_WIDTH; + pLayerCfg.WindowY0 = 0; + pLayerCfg.WindowY1 = TFT_HEIGHT; + + /* Pixel Format configuration*/ + pLayerCfg.PixelFormat = LTDC_PIXEL_FORMAT_RGB565; + + /* Start Address configuration : frame buffer is located at SDRAM memory */ + pLayerCfg.FBStartAdress = (uint32_t)(FRAME_BUFFER_ADDRESS); + + /* Alpha constant (255 == totally opaque) */ + pLayerCfg.Alpha = 255; + + /* Default Color configuration (configure A,R,G,B component values) : no background color */ + pLayerCfg.Alpha0 = 0; /* fully transparent */ + pLayerCfg.Backcolor.Blue = 0; + pLayerCfg.Backcolor.Green = 0; + pLayerCfg.Backcolor.Red = 0; + + /* Configure blending factors */ + pLayerCfg.BlendingFactor1 = LTDC_BLENDING_FACTOR1_CA; + pLayerCfg.BlendingFactor2 = LTDC_BLENDING_FACTOR2_CA; + + /* Configure the number of lines and number of pixels per line */ + pLayerCfg.ImageWidth = TFT_WIDTH; + pLayerCfg.ImageHeight = TFT_HEIGHT; + + /* Configure the LTDC */ + if (HAL_LTDC_Init(&hltdc_F) != HAL_OK) + { + /* Initialization Error */ + } + + /* Configure the Layer*/ + if (HAL_LTDC_ConfigLayer(&hltdc_F, &pLayerCfg, 0) != HAL_OK) + { + /* Initialization Error */ + } +} + +uint16_t TFT_LTDC::x_min = 0; +uint16_t TFT_LTDC::x_max = 0; +uint16_t TFT_LTDC::y_min = 0; +uint16_t TFT_LTDC::y_max = 0; +uint16_t TFT_LTDC::x_cur = 0; +uint16_t TFT_LTDC::y_cur = 0; +uint8_t TFT_LTDC::reg = 0; +volatile uint16_t* TFT_LTDC::framebuffer = (volatile uint16_t* )FRAME_BUFFER_ADDRESS; + +void TFT_LTDC::init() { + + // SDRAM pins init + for (uint16_t i = 0; pinMap_SDRAM[i].pin != NC; i++) + pinmap_pinout(pinMap_SDRAM[i].pin, pinMap_SDRAM); + + // SDRAM peripheral config + SDRAM_Config(); + + // LTDC pins init + for (uint16_t i = 0; pinMap_LTDC[i].pin != NC; i++) + pinmap_pinout(pinMap_LTDC[i].pin, pinMap_LTDC); + + // LTDC peripheral config + LTDC_Config(); +} + +uint32_t TFT_LTDC::getID() { + return 0xABAB; +} + +uint32_t TFT_LTDC::readID(const tft_data_t inReg) { + return 0xABAB; +} + +bool TFT_LTDC::isBusy() { + return false; +} + +uint16_t TFT_LTDC::readPoint(uint16_t x, uint16_t y) { + return framebuffer[(TFT_WIDTH * y) + x]; +} + +void TFT_LTDC::drawPoint(uint16_t x, uint16_t y, uint16_t color) { + framebuffer[(TFT_WIDTH * y) + x] = color; +} + +void TFT_LTDC::drawRect(uint16_t sx, uint16_t sy, uint16_t ex, uint16_t ey, uint16_t color) { + + if (sx == ex || sy == ey) return; + + uint16_t offline = TFT_WIDTH - (ex - sx); + uint32_t addr = (uint32_t)&framebuffer[(TFT_WIDTH * sy) + sx]; + + CBI(DMA2D->CR, 0); + DMA2D->CR = 3 << 16; + DMA2D->OPFCCR = 0X02; + DMA2D->OOR = offline; + DMA2D->OMAR = addr; + DMA2D->NLR = (ey - sy) | ((ex - sx) << 16); + DMA2D->OCOLR = color; + SBI(DMA2D->CR, 0); + + uint32_t timeout = 0; + while (!TEST(DMA2D->ISR, 1)) { + timeout++; + if (timeout > 0x1FFFFF) break; + } + SBI(DMA2D->IFCR, 1); +} + +void TFT_LTDC::drawImage(uint16_t sx, uint16_t sy, uint16_t ex, uint16_t ey, uint16_t *colors) { + + if (sx == ex || sy == ey) return; + + uint16_t offline = TFT_WIDTH - (ex - sx); + uint32_t addr = (uint32_t)&framebuffer[(TFT_WIDTH * sy) + sx]; + + CBI(DMA2D->CR, 0); + DMA2D->CR = 0 << 16; + DMA2D->FGPFCCR = 0X02; + DMA2D->FGOR = 0; + DMA2D->OOR = offline; + DMA2D->FGMAR = (uint32_t)colors; + DMA2D->OMAR = addr; + DMA2D->NLR = (ey - sy) | ((ex - sx) << 16); + SBI(DMA2D->CR, 0); + + uint32_t timeout = 0; + while (!TEST(DMA2D->ISR, 1)) { + timeout++; + if (timeout > 0x1FFFFF) break; + } + SBI(DMA2D->IFCR, 1); +} + +void TFT_LTDC::writeData(uint16_t data) { + switch (reg) { + case 0x01: x_cur = x_min = data; return; + case 0x02: x_max = data; return; + case 0x03: y_cur = y_min = data; return; + case 0x04: y_max = data; return; + } + transmit(data); +} + +void TFT_LTDC::transmit(tft_data_t data) { + drawPoint(x_cur, y_cur, data); + x_cur++; + if (x_cur > x_max) { + x_cur = x_min; + y_cur++; + if (y_cur > y_max) y_cur = y_min; + } +} + +void TFT_LTDC::transmit(uint32_t memoryIncrease, uint16_t *data, uint16_t count) { + + while (x_cur != x_min && count) { + transmit(*data); + if (memoryIncrease == DMA_PINC_ENABLE) data++; + count--; + } + + uint16_t width = x_max - x_min + 1; + uint16_t height = count / width; + uint16_t x_end_cnt = count - (width * height); + + if (height) { + if (memoryIncrease == DMA_PINC_ENABLE) { + drawImage(x_min, y_cur, x_min + width, y_cur + height, data); + data += width * height; + } + else + drawRect(x_min, y_cur, x_min + width, y_cur + height, *data); + y_cur += height; + } + + while (x_end_cnt) { + transmit(*data); + if (memoryIncrease == DMA_PINC_ENABLE) data++; + x_end_cnt--; + } +} + +#endif // HAS_LTDC_TFT +#endif // HAL_STM32 diff --git a/Marlin/src/HAL/STM32/tft/tft_ltdc.h b/Marlin/src/HAL/STM32/tft/tft_ltdc.h new file mode 100644 index 0000000000..90cc58d8a5 --- /dev/null +++ b/Marlin/src/HAL/STM32/tft/tft_ltdc.h @@ -0,0 +1,158 @@ +/** + * 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 + +#include "../../../inc/MarlinConfig.h" + +#ifdef STM32H7xx + #include "stm32h7xx_hal.h" +#else + #error "LTDC TFT is currently only supported on STM32H7 hardware." +#endif + +#define DATASIZE_8BIT SPI_DATASIZE_8BIT +#define DATASIZE_16BIT SPI_DATASIZE_16BIT +#define TFT_IO_DRIVER TFT_LTDC +#define DMA_MAX_WORDS 0xFFFF + +#define TFT_DATASIZE DATASIZE_16BIT +typedef uint16_t tft_data_t; + +class TFT_LTDC { + private: + static volatile uint16_t *framebuffer; + static uint16_t x_min, x_max, y_min, y_max, x_cur, y_cur; + static uint8_t reg; + + static uint32_t readID(const tft_data_t inReg); + + static uint16_t readPoint(uint16_t x, uint16_t y); + static void drawPoint(uint16_t x, uint16_t y, uint16_t color); + static void drawRect(uint16_t sx, uint16_t sy, uint16_t ex, uint16_t ey, uint16_t color); + static void drawImage(uint16_t sx, uint16_t sy, uint16_t ex, uint16_t ey, uint16_t *colors); + static void transmit(tft_data_t data); + static void transmit(uint32_t memoryIncrease, uint16_t *data, uint16_t count); + + public: + static void init(); + static uint32_t getID(); + static bool isBusy(); + static void abort() { /*__HAL_DMA_DISABLE(&DMAtx);*/ } + + static void dataTransferBegin(uint16_t dataWidth=TFT_DATASIZE) {} + static void dataTransferEnd() {}; + + static void writeData(uint16_t data); + static void writeReg(const uint16_t inReg) { reg = inReg; } + + // Non-blocking DMA data transfer is not implemented for LTDC interface + inline static void writeSequence_DMA(uint16_t *data, uint16_t count) { writeSequence(data, count); } + inline static void writeMultiple_DMA(uint16_t color, uint16_t count) { writeMultiple(color, count); } + + static void writeSequence(uint16_t *data, uint16_t count) { transmit(DMA_PINC_ENABLE, data, count); } + static void writeMultiple(uint16_t color, uint32_t count) { + while (count > 0) { + transmit(DMA_PINC_DISABLE, &color, count > DMA_MAX_WORDS ? DMA_MAX_WORDS : count); + count = count > DMA_MAX_WORDS ? count - DMA_MAX_WORDS : 0; + } + } +}; + +const PinMap pinMap_LTDC[] = { + {PF_10, LTDC, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF14_LTDC)}, // LCD_DE + {PG_7, LTDC, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF14_LTDC)}, // LCD_CLK + {PI_9, LTDC, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF14_LTDC)}, // LCD_VSYNC + {PI_10, LTDC, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF14_LTDC)}, // LCD_HSYNC + + {PG_6, LTDC, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF14_LTDC)}, // LCD_R7 + {PH_12, LTDC, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF14_LTDC)}, // LCD_R6 + {PH_11, LTDC, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF14_LTDC)}, // LCD_R5 + {PH_10, LTDC, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF14_LTDC)}, // LCD_R4 + {PH_9, LTDC, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF14_LTDC)}, // LCD_R3 + + {PI_2, LTDC, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF14_LTDC)}, // LCD_G7 + {PI_1, LTDC, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF14_LTDC)}, // LCD_G6 + {PI_0, LTDC, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF14_LTDC)}, // LCD_G5 + {PH_15, LTDC, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF14_LTDC)}, // LCD_G4 + {PH_14, LTDC, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF14_LTDC)}, // LCD_G3 + {PH_13, LTDC, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF14_LTDC)}, // LCD_G2 + + {PI_7, LTDC, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF14_LTDC)}, // LCD_B7 + {PI_6, LTDC, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF14_LTDC)}, // LCD_B6 + {PI_5, LTDC, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF14_LTDC)}, // LCD_B5 + {PI_4, LTDC, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF14_LTDC)}, // LCD_B4 + {PG_11, LTDC, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF14_LTDC)}, // LCD_B3 + {NC, NP, 0} +}; + +const PinMap pinMap_SDRAM[] = { + {PC_0, FMC_Bank1_R, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF12_FMC)}, // FMC_SDNWE + {PC_2, FMC_Bank1_R, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF12_FMC)}, // FMC_SDNE0 + {PC_3, FMC_Bank1_R, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF12_FMC)}, // FMC_SDCKE0 + {PE_0, FMC_Bank1_R, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF12_FMC)}, // FMC_NBL0 + {PE_1, FMC_Bank1_R, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF12_FMC)}, // FMC_NBL1 + {PF_11, FMC_Bank1_R, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF12_FMC)}, // FMC_SDNRAS + {PG_8, FMC_Bank1_R, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF12_FMC)}, // FMC_SDCLK + {PG_15, FMC_Bank1_R, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF12_FMC)}, // FMC_SDNCAS + {PG_4, FMC_Bank1_R, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF12_FMC)}, // FMC_BA0 + {PG_5, FMC_Bank1_R, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF12_FMC)}, // FMC_BA1 + {PD_14, FMC_Bank1_R, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF12_FMC)}, // FMC_D0 + {PD_15, FMC_Bank1_R, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF12_FMC)}, // FMC_D1 + {PD_0, FMC_Bank1_R, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF12_FMC)}, // FMC_D2 + {PD_1, FMC_Bank1_R, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF12_FMC)}, // FMC_D3 + {PE_7, FMC_Bank1_R, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF12_FMC)}, // FMC_D4 + {PE_8, FMC_Bank1_R, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF12_FMC)}, // FMC_D5 + {PE_9, FMC_Bank1_R, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF12_FMC)}, // FMC_D6 + {PE_10, FMC_Bank1_R, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF12_FMC)}, // FMC_D7 + {PE_11, FMC_Bank1_R, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF12_FMC)}, // FMC_D8 + {PE_12, FMC_Bank1_R, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF12_FMC)}, // FMC_D9 + {PE_13, FMC_Bank1_R, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF12_FMC)}, // FMC_D10 + {PE_14, FMC_Bank1_R, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF12_FMC)}, // FMC_D11 + {PE_15, FMC_Bank1_R, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF12_FMC)}, // FMC_D12 + {PD_8, FMC_Bank1_R, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF12_FMC)}, // FMC_D13 + {PD_9, FMC_Bank1_R, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF12_FMC)}, // FMC_D14 + {PD_10, FMC_Bank1_R, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF12_FMC)}, // FMC_D15 + {PF_0, FMC_Bank1_R, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF12_FMC)}, // FMC_A0 + {PF_1, FMC_Bank1_R, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF12_FMC)}, // FMC_A1 + {PF_2, FMC_Bank1_R, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF12_FMC)}, // FMC_A2 + {PF_3, FMC_Bank1_R, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF12_FMC)}, // FMC_A3 + {PF_4, FMC_Bank1_R, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF12_FMC)}, // FMC_A4 + {PF_5, FMC_Bank1_R, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF12_FMC)}, // FMC_A5 + {PF_12, FMC_Bank1_R, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF12_FMC)}, // FMC_A6 + {PF_13, FMC_Bank1_R, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF12_FMC)}, // FMC_A7 + {PF_14, FMC_Bank1_R, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF12_FMC)}, // FMC_A8 + {PF_15, FMC_Bank1_R, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF12_FMC)}, // FMC_A9 + {PG_0, FMC_Bank1_R, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF12_FMC)}, // FMC_A10 + {PG_1, FMC_Bank1_R, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF12_FMC)}, // FMC_A11 + {PG_2, FMC_Bank1_R, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF12_FMC)}, // FMC_A12 + {NC, NP, 0} +}; + +const PinMap PinMap_QUADSPI[] = { + {PB_2, QUADSPI, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF9_QUADSPI)}, // QUADSPI_CLK + {PB_10, QUADSPI, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF9_QUADSPI)}, // QUADSPI_BK1_NCS + {PF_6, QUADSPI, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF9_QUADSPI)}, // QUADSPI_BK1_IO3 + {PF_7, QUADSPI, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF9_QUADSPI)}, // QUADSPI_BK1_IO2 + {PF_8, QUADSPI, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF10_QUADSPI)}, // QUADSPI_BK1_IO0 + {PF_9, QUADSPI, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF10_QUADSPI)}, // QUADSPI_BK1_IO1 + {NC, NP, 0} +}; diff --git a/Marlin/src/HAL/STM32/tft/tft_spi.cpp b/Marlin/src/HAL/STM32/tft/tft_spi.cpp index d3eb4ba8db..71ad14534f 100644 --- a/Marlin/src/HAL/STM32/tft/tft_spi.cpp +++ b/Marlin/src/HAL/STM32/tft/tft_spi.cpp @@ -20,6 +20,10 @@ * */ +#include "../../platforms.h" + +#ifdef HAL_STM32 + #include "../../../inc/MarlinConfig.h" #if HAS_SPI_TFT @@ -27,42 +31,32 @@ #include "tft_spi.h" #include "pinconfig.h" +//#define DEBUG_TFT_IO +#define DEBUG_OUT ENABLED(DEBUG_TFT_IO) +#include "../../../core/debug_out.h" + SPI_HandleTypeDef TFT_SPI::SPIx; DMA_HandleTypeDef TFT_SPI::DMAtx; -void TFT_SPI::Init() { +void TFT_SPI::init() { SPI_TypeDef *spiInstance; - #if PIN_EXISTS(TFT_RESET) - OUT_WRITE(TFT_RESET_PIN, HIGH); - HAL_Delay(100); - #endif - - #if PIN_EXISTS(TFT_BACKLIGHT) - OUT_WRITE(TFT_BACKLIGHT_PIN, HIGH); - #endif - OUT_WRITE(TFT_A0_PIN, HIGH); OUT_WRITE(TFT_CS_PIN, HIGH); if ((spiInstance = (SPI_TypeDef *)pinmap_peripheral(digitalPinToPinName(TFT_SCK_PIN), PinMap_SPI_SCLK)) == NP) return; if (spiInstance != (SPI_TypeDef *)pinmap_peripheral(digitalPinToPinName(TFT_MOSI_PIN), PinMap_SPI_MOSI)) return; - #if PIN_EXISTS(TFT_MISO) && (TFT_MISO_PIN != TFT_MOSI_PIN) - if (spiInstance != (SPI_TypeDef *)pinmap_peripheral(digitalPinToPinName(TFT_MISO_PIN), PinMap_SPI_MISO)) return; + #if PIN_EXISTS(TFT_MISO) + // Check these pins in code because they are sometimes defined as analog pin references + if ((TFT_MISO_PIN != TFT_MOSI_PIN) && (spiInstance != (SPI_TypeDef *)pinmap_peripheral(digitalPinToPinName(TFT_MISO_PIN), PinMap_SPI_MISO))) return; #endif SPIx.Instance = spiInstance; SPIx.State = HAL_SPI_STATE_RESET; SPIx.Init.NSS = SPI_NSS_SOFT; SPIx.Init.Mode = SPI_MODE_MASTER; - SPIx.Init.Direction = - #if TFT_MISO_PIN == TFT_MOSI_PIN - SPI_DIRECTION_1LINE; - #else - SPI_DIRECTION_2LINES; - #endif - SPIx.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2; + SPIx.Init.Direction = (TFT_MISO_PIN == TFT_MOSI_PIN) ? SPI_DIRECTION_1LINE : SPI_DIRECTION_2LINES; SPIx.Init.CLKPhase = SPI_PHASE_1EDGE; SPIx.Init.CLKPolarity = SPI_POLARITY_LOW; SPIx.Init.DataSize = SPI_DATASIZE_8BIT; @@ -71,142 +65,314 @@ void TFT_SPI::Init() { SPIx.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; SPIx.Init.CRCPolynomial = 10; + #ifndef STM32H7xx + SPIx.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2; // 18 MBit/s for F103, 21 MBit/s for F407, 25 MBit/s for F411 + #else + SPIx.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4; // 20 MBit/s for H743 + SPIx.Init.NSSPMode = SPI_NSS_PULSE_ENABLE; + SPIx.Init.NSSPolarity = SPI_NSS_POLARITY_LOW; + SPIx.Init.FifoThreshold = SPI_FIFO_THRESHOLD_01DATA; + SPIx.Init.MasterSSIdleness = SPI_MASTER_SS_IDLENESS_00CYCLE; + SPIx.Init.MasterInterDataIdleness = SPI_MASTER_INTERDATA_IDLENESS_00CYCLE; + SPIx.Init.MasterReceiverAutoSusp = SPI_MASTER_RX_AUTOSUSP_DISABLE; + SPIx.Init.MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_ENABLE; + SPIx.Init.IOSwap = SPI_IO_SWAP_DISABLE; + #endif + pinmap_pinout(digitalPinToPinName(TFT_SCK_PIN), PinMap_SPI_SCLK); pinmap_pinout(digitalPinToPinName(TFT_MOSI_PIN), PinMap_SPI_MOSI); - #if PIN_EXISTS(TFT_MISO) && (TFT_MISO_PIN != TFT_MOSI_PIN) - pinmap_pinout(digitalPinToPinName(TFT_MISO_PIN), PinMap_SPI_MISO); + #if PIN_EXISTS(TFT_MISO) + // Check these pins in code because they are sometimes defined as analog pin references + if (TFT_MISO_PIN != TFT_MOSI_PIN) pinmap_pinout(digitalPinToPinName(TFT_MISO_PIN), PinMap_SPI_MISO); #endif - pin_PullConfig(get_GPIO_Port(STM_PORT(digitalPinToPinName(TFT_SCK_PIN))), STM_LL_GPIO_PIN(digitalPinToPinName(TFT_SCK_PIN)), GPIO_PULLDOWN); + + //pin_PullConfig(get_GPIO_Port(STM_PORT(digitalPinToPinName(TFT_SCK_PIN))), STM_LL_GPIO_PIN(digitalPinToPinName(TFT_SCK_PIN)), GPIO_PULLDOWN); #ifdef SPI1_BASE if (SPIx.Instance == SPI1) { __HAL_RCC_SPI1_CLK_ENABLE(); - __HAL_RCC_DMA1_CLK_ENABLE(); - SPIx.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4; - DMAtx.Instance = DMA1_Channel3; + #ifdef STM32F1xx + __HAL_RCC_DMA1_CLK_ENABLE(); + DMAtx.Instance = DMA1_Channel3; + SPIx.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4; // SPI1 clock on F1 and F4 is two times faster than SPI2 and SPI3 clock + #elif defined(STM32F4xx) + __HAL_RCC_DMA2_CLK_ENABLE(); + DMAtx.Instance = DMA2_Stream3; + DMAtx.Init.Channel = DMA_CHANNEL_3; + SPIx.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4; // SPI1 clock on F1 and F4 is two times faster than SPI2 and SPI3 clock + #elif defined(STM32H7xx) + __HAL_RCC_DMA1_CLK_ENABLE(); + DMAtx.Instance = DMA1_Stream4; + DMAtx.Init.Request = DMA_REQUEST_SPI1_TX; + #endif } #endif #ifdef SPI2_BASE if (SPIx.Instance == SPI2) { __HAL_RCC_SPI2_CLK_ENABLE(); - __HAL_RCC_DMA1_CLK_ENABLE(); - DMAtx.Instance = DMA1_Channel5; + #ifdef STM32F1xx + __HAL_RCC_DMA1_CLK_ENABLE(); + DMAtx.Instance = DMA1_Channel5; + #elif defined(STM32F4xx) + __HAL_RCC_DMA1_CLK_ENABLE(); + DMAtx.Instance = DMA1_Stream4; + DMAtx.Init.Channel = DMA_CHANNEL_0; + #elif defined(STM32H7xx) + __HAL_RCC_DMA1_CLK_ENABLE(); + DMAtx.Instance = DMA1_Stream4; + DMAtx.Init.Request = DMA_REQUEST_SPI2_TX; + #endif } #endif #ifdef SPI3_BASE if (SPIx.Instance == SPI3) { __HAL_RCC_SPI3_CLK_ENABLE(); - __HAL_RCC_DMA2_CLK_ENABLE(); - DMAtx.Instance = DMA2_Channel2; + #ifdef STM32F1xx + __HAL_RCC_DMA2_CLK_ENABLE(); + DMAtx.Instance = DMA2_Channel2; + #elif defined(STM32F4xx) + __HAL_RCC_DMA1_CLK_ENABLE(); + DMAtx.Instance = DMA1_Stream5; + DMAtx.Init.Channel = DMA_CHANNEL_0; + #elif defined(STM32H7xx) + __HAL_RCC_DMA1_CLK_ENABLE(); + DMAtx.Instance = DMA1_Stream4; + DMAtx.Init.Request = DMA_REQUEST_SPI3_TX; + #endif } #endif - HAL_SPI_Init(&SPIx); - DMAtx.Init.Direction = DMA_MEMORY_TO_PERIPH; DMAtx.Init.PeriphInc = DMA_PINC_DISABLE; DMAtx.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; DMAtx.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; DMAtx.Init.Mode = DMA_NORMAL; DMAtx.Init.Priority = DMA_PRIORITY_LOW; + #if ANY(STM32F4xx, STM32H7xx) + DMAtx.Init.FIFOMode = DMA_FIFOMODE_DISABLE; + #endif } -void TFT_SPI::DataTransferBegin(uint16_t DataSize) { - SPIx.Init.DataSize = DataSize == DATASIZE_8BIT ? SPI_DATASIZE_8BIT : SPI_DATASIZE_16BIT; +void TFT_SPI::dataTransferBegin(uint16_t dataSize) { + SPIx.Init.DataSize = dataSize; HAL_SPI_Init(&SPIx); WRITE(TFT_CS_PIN, LOW); } -uint32_t TFT_SPI::GetID() { - uint32_t id; - id = ReadID(LCD_READ_ID); +#include "../../../lcd/tft_io/tft_ids.h" + +uint32_t TFT_SPI::getID() { + DEBUG_ECHOLNPGM("TFT_SPI::getID()"); + + uint32_t id = readID(LCD_READ_ID); + #if ENABLED(DEBUG_TFT_IO) + char debug_register[3], debug_value[5]; + sprintf_P(debug_register, PSTR("%02X"), LCD_READ_ID); + sprintf_P(debug_value, PSTR("%04X"), uint16_t(id)); + DEBUG_ECHOLNPGM(" readID(0x", debug_register, ") : 0x", debug_value); + #endif + + if ((id & 0xFFFF) == 0 || (id & 0xFFFF) == 0xFFFF) { + id = readID(LCD_READ_ID4); + #if ENABLED(DEBUG_TFT_IO) + sprintf_P(debug_register, PSTR("%02X"), LCD_READ_ID4); + sprintf_P(debug_value, PSTR("%04X"), uint16_t(id)); + DEBUG_ECHOLNPGM(" readID(0x", debug_register, ") : 0x", debug_value); + #endif + } + + #ifdef TFT_DEFAULT_DRIVER + if ((id & 0xFFFF) == 0 || (id & 0xFFFF) == 0xFFFF) { + id = TFT_DEFAULT_DRIVER; + #if ENABLED(DEBUG_TFT_IO) + sprintf_P(debug_value, PSTR("%04X"), uint16_t(id)); + DEBUG_ECHOLNPGM(" Fallback to TFT_DEFAULT_DRIVER : 0x", debug_value); + #endif + } + #endif - if ((id & 0xFFFF) == 0 || (id & 0xFFFF) == 0xFFFF) - id = ReadID(LCD_READ_ID4); return id; } -uint32_t TFT_SPI::ReadID(uint16_t Reg) { - #if !PIN_EXISTS(TFT_MISO) - return 0; - #else +uint32_t TFT_SPI::readID(const uint16_t inReg) { + uint32_t data = 0; + #if PIN_EXISTS(TFT_MISO) uint32_t BaudRatePrescaler = SPIx.Init.BaudRatePrescaler; - uint32_t i, Data = 0; + SPIx.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_64; - SPIx.Init.BaudRatePrescaler = SPIx.Instance == SPI1 ? SPI_BAUDRATEPRESCALER_8 : SPI_BAUDRATEPRESCALER_4; - DataTransferBegin(DATASIZE_8BIT); - WriteReg(Reg); + dataTransferBegin(DATASIZE_8BIT); + writeReg(inReg); if (SPIx.Init.Direction == SPI_DIRECTION_1LINE) SPI_1LINE_RX(&SPIx); - __HAL_SPI_ENABLE(&SPIx); - for (i = 0; i < 4; i++) { - #if TFT_MISO_PIN != TFT_MOSI_PIN - //if (hspi->Init.Direction == SPI_DIRECTION_2LINES) { - while ((SPIx.Instance->SR & SPI_FLAG_TXE) != SPI_FLAG_TXE) {} + #ifdef STM32H7xx + for (uint32_t i = 0; i < 4; i++) { + MODIFY_REG(SPIx.Instance->CR2, SPI_CR2_TSIZE, 1); + __HAL_SPI_ENABLE(&SPIx); + SET_BIT(SPIx.Instance->CR1, SPI_CR1_CSTART); + + if (SPIx.Init.Direction == SPI_DIRECTION_2LINES) SPIx.Instance->TXDR = 0; + while (!__HAL_SPI_GET_FLAG(&SPIx, SPI_FLAG_EOT)) { /* nada */ } + data = (data << 8) | SPIx.Instance->RXDR; + __HAL_SPI_DISABLE(&SPIx); + __HAL_SPI_CLEAR_EOTFLAG(&SPIx); + __HAL_SPI_CLEAR_TXTFFLAG(&SPIx); + } + #else + __HAL_SPI_ENABLE(&SPIx); + for (uint32_t i = 0; i < 4; i++) { + if (SPIx.Init.Direction == SPI_DIRECTION_2LINES) { + while (!__HAL_SPI_GET_FLAG(&SPIx, SPI_FLAG_TXE)) { /* nada */ } SPIx.Instance->DR = 0; - //} - #endif - while ((SPIx.Instance->SR & SPI_FLAG_RXNE) != SPI_FLAG_RXNE) {} - Data = (Data << 8) | SPIx.Instance->DR; - } + } + while (!__HAL_SPI_GET_FLAG(&SPIx, SPI_FLAG_RXNE)) { /* nada */ } + data = (data << 8) | SPIx.Instance->DR; + } + #endif - __HAL_SPI_DISABLE(&SPIx); - DataTransferEnd(); - - SPIx.Init.BaudRatePrescaler = BaudRatePrescaler; - - return Data >> 7; + dataTransferEnd(); + #if DISABLED(DEBUG_TFT_IO) + SPIx.Init.BaudRatePrescaler = BaudRatePrescaler; + #endif #endif + + DEBUG_ECHOLNPGM(" raw data : ", data); + return data >> 7; } bool TFT_SPI::isBusy() { - if (DMAtx.Instance->CCR & DMA_CCR_EN) - if (__HAL_DMA_GET_FLAG(&DMAtx, __HAL_DMA_GET_TC_FLAG_INDEX(&DMAtx)) != 0 || __HAL_DMA_GET_FLAG(&DMAtx, __HAL_DMA_GET_TE_FLAG_INDEX(&DMAtx)) != 0) - Abort(); - return DMAtx.Instance->CCR & DMA_CCR_EN; -} - -void TFT_SPI::Abort() { - __HAL_DMA_DISABLE(&DMAtx); - DataTransferEnd(); -} - -void TFT_SPI::Transmit(uint16_t Data) { - #if TFT_MISO_PIN == TFT_MOSI_PIN - SPI_1LINE_TX(&SPIx); + #ifdef STM32F1xx + #define __IS_DMA_ENABLED(__HANDLE__) ((__HANDLE__)->Instance->CCR & DMA_CCR_EN) + #define __IS_DMA_CONFIGURED(__HANDLE__) ((__HANDLE__)->Instance->CPAR != 0) + #elif defined(STM32F4xx) + #define __IS_DMA_ENABLED(__HANDLE__) ((__HANDLE__)->Instance->CR & DMA_SxCR_EN) + #define __IS_DMA_CONFIGURED(__HANDLE__) ((__HANDLE__)->Instance->PAR != 0) + #elif defined(STM32H7xx) + #define __IS_DMA_ENABLED(__HANDLE__) (((DMA_Stream_TypeDef *)((__HANDLE__)->Instance))->CR & DMA_SxCR_EN) + #define __IS_DMA_CONFIGURED(__HANDLE__) (((DMA_Stream_TypeDef *)((__HANDLE__)->Instance))->PAR != 0) #endif - __HAL_SPI_ENABLE(&SPIx); + if (!__IS_DMA_CONFIGURED(&DMAtx)) return false; - SPIx.Instance->DR = Data; + if (__HAL_DMA_GET_FLAG(&DMAtx, __HAL_DMA_GET_TE_FLAG_INDEX(&DMAtx))) { + // You should not be here - DMA transfer error flag is set + // Abort DMA transfer and release SPI + } + else { + // Check if DMA transfer completed flag is set + if (__HAL_DMA_GET_FLAG(&DMAtx, __HAL_DMA_GET_TC_FLAG_INDEX(&DMAtx)) == 0) return true; + #ifdef STM32H7xx + // Check if SPI data transfer is completed + if (!__HAL_SPI_GET_FLAG(&SPIx, SPI_FLAG_EOT)) return true; + #else + // Check if SPI transmit butter is empty and SPI is idle + if ((!__HAL_SPI_GET_FLAG(&SPIx, SPI_FLAG_TXE)) || (__HAL_SPI_GET_FLAG(&SPIx, SPI_FLAG_BSY))) return true; + #endif + } - while ((SPIx.Instance->SR & SPI_FLAG_TXE) != SPI_FLAG_TXE) {} - while ((SPIx.Instance->SR & SPI_FLAG_BSY) == SPI_FLAG_BSY) {} - - #if TFT_MISO_PIN != TFT_MOSI_PIN - __HAL_SPI_CLEAR_OVRFLAG(&SPIx); /* Clear overrun flag in 2 Lines communication mode because received is not read */ - #endif + abort(); + return true; } -void TFT_SPI::TransmitDMA(uint32_t MemoryIncrease, uint16_t *Data, uint16_t Count) { - DMAtx.Init.MemInc = MemoryIncrease; +void TFT_SPI::abort() { + HAL_DMA_Abort(&DMAtx); // Abort DMA transfer if any + HAL_DMA_DeInit(&DMAtx); + + #ifdef STM32H7xx + CLEAR_BIT(SPIx.Instance->CFG1, SPI_CFG1_TXDMAEN); + __HAL_SPI_CLEAR_EOTFLAG(&SPIx); + __HAL_SPI_CLEAR_TXTFFLAG(&SPIx); + #else + CLEAR_BIT(SPIx.Instance->CR2, SPI_CR2_TXDMAEN); + #endif + + dataTransferEnd(); // Stop SPI and deselect CS +} + +void TFT_SPI::transmit(uint16_t data) { + if (SPIx.Init.Direction == SPI_DIRECTION_1LINE) SPI_1LINE_TX(&SPIx); + + #ifdef STM32H7xx + MODIFY_REG(SPIx.Instance->CR2, SPI_CR2_TSIZE, 1); + __HAL_SPI_ENABLE(&SPIx); + SET_BIT(SPIx.Instance->CR1, SPI_CR1_CSTART); + + SPIx.Instance->TXDR = data; + + while (!__HAL_SPI_GET_FLAG(&SPIx, SPI_SR_EOT)) { /* nada */ } + + __HAL_SPI_CLEAR_EOTFLAG(&SPIx); + __HAL_SPI_CLEAR_TXTFFLAG(&SPIx); + __HAL_SPI_DISABLE(&SPIx); + #else + __HAL_SPI_ENABLE(&SPIx); + SPIx.Instance->DR = data; + while (!__HAL_SPI_GET_FLAG(&SPIx, SPI_FLAG_TXE)) { /* nada */ } // Wait for data transfer to actually start + while ( __HAL_SPI_GET_FLAG(&SPIx, SPI_FLAG_BSY)) { /* nada */ } // Wait until SPI is idle + #endif + + if (SPIx.Init.Direction == SPI_DIRECTION_2LINES) __HAL_SPI_CLEAR_OVRFLAG(&SPIx); // Clear overrun flag in 2 Lines communication mode because received data is not read +} + +void TFT_SPI::transmitDMA(uint32_t memoryIncrease, uint16_t *data, uint16_t count) { + DMAtx.Init.MemInc = memoryIncrease; HAL_DMA_Init(&DMAtx); - DataTransferBegin(); + if (SPIx.Init.Direction == SPI_DIRECTION_1LINE) SPI_1LINE_TX(&SPIx); - #if TFT_MISO_PIN == TFT_MOSI_PIN - SPI_1LINE_TX(&SPIx); + dataTransferBegin(); + + #ifdef STM32H7xx + HAL_DMA_Start(&DMAtx, (uint32_t)data, (uint32_t)&(SPIx.Instance->TXDR), count); + + CLEAR_BIT(SPIx.Instance->CFG1, SPI_CFG1_TXDMAEN); + MODIFY_REG(SPIx.Instance->CR2, SPI_CR2_TSIZE, count); + SET_BIT(SPIx.Instance->CFG1, SPI_CFG1_TXDMAEN); // Enable Tx DMA Request + __HAL_SPI_ENABLE(&SPIx); + SET_BIT(SPIx.Instance->CR1, SPI_CR1_CSTART); + #else + HAL_DMA_Start(&DMAtx, (uint32_t)data, (uint32_t)&(SPIx.Instance->DR), count); + + __HAL_SPI_ENABLE(&SPIx); + SET_BIT(SPIx.Instance->CR2, SPI_CR2_TXDMAEN); // Enable Tx DMA Request #endif - DMAtx.DmaBaseAddress->IFCR = (DMA_ISR_GIF1 << DMAtx.ChannelIndex); - DMAtx.Instance->CNDTR = Count; - DMAtx.Instance->CPAR = (uint32_t)&(SPIx.Instance->DR); - DMAtx.Instance->CMAR = (uint32_t)Data; - __HAL_DMA_ENABLE(&DMAtx); - __HAL_SPI_ENABLE(&SPIx); - - SET_BIT(SPIx.Instance->CR2, SPI_CR2_TXDMAEN); /* Enable Tx DMA Request */ + TERN_(TFT_SHARED_IO, while (isBusy())); } +void TFT_SPI::transmit(uint32_t memoryIncrease, uint16_t *data, uint16_t count) { + transmitDMA(memoryIncrease, data, count); + + HAL_DMA_PollForTransfer(&DMAtx, HAL_DMA_FULL_TRANSFER, HAL_MAX_DELAY); + #ifdef STM32H7xx + while (!__HAL_SPI_GET_FLAG(&SPIx, SPI_SR_EOT)) { /* nada */ } + #else + while (!__HAL_SPI_GET_FLAG(&SPIx, SPI_FLAG_TXE)) { /* nada */ } + while (__HAL_SPI_GET_FLAG(&SPIx, SPI_FLAG_BSY)) { /* nada */ } + #endif + abort(); +} + +#if ENABLED(USE_SPI_DMA_TC) + void TFT_SPI::transmitDMA_IT(uint32_t memoryIncrease, uint16_t *data, uint16_t count) { + + DMAtx.Init.MemInc = memoryIncrease; + HAL_DMA_Init(&DMAtx); + + if (SPIx.Init.Direction == SPI_DIRECTION_1LINE) SPI_1LINE_TX(&SPIx); + + dataTransferBegin(); + + HAL_NVIC_SetPriority(DMA2_Stream3_IRQn, 5, 0); + HAL_NVIC_EnableIRQ(DMA2_Stream3_IRQn); + HAL_DMA_Start_IT(&DMAtx, (uint32_t)data, (uint32_t)&(SPIx.Instance->DR), count); + __HAL_SPI_ENABLE(&SPIx); + + SET_BIT(SPIx.Instance->CR2, SPI_CR2_TXDMAEN); // Enable Tx DMA Request + } + + extern "C" void DMA2_Stream3_IRQHandler(void) { TFT_SPI::DMA_IRQHandler(); } +#endif + #endif // HAS_SPI_TFT +#endif // HAL_STM32 diff --git a/Marlin/src/HAL/STM32/tft/tft_spi.h b/Marlin/src/HAL/STM32/tft/tft_spi.h index d477b58c00..6345c91f6c 100644 --- a/Marlin/src/HAL/STM32/tft/tft_spi.h +++ b/Marlin/src/HAL/STM32/tft/tft_spi.h @@ -25,8 +25,10 @@ #include "stm32f1xx_hal.h" #elif defined(STM32F4xx) #include "stm32f4xx_hal.h" +#elif defined(STM32H7xx) + #include "stm32h7xx_hal.h" #else - #error SPI TFT is currently only supported on STM32F1 and STM32F4 hardware. + #error SPI TFT is currently only supported on STM32F1, STM32F4 and STM32H7 hardware. #endif #ifndef LCD_READ_ID @@ -36,32 +38,51 @@ #define LCD_READ_ID4 0xD3 // Read display identification information (0xD3 on ILI9341) #endif -#define DATASIZE_8BIT SPI_DATASIZE_8BIT -#define DATASIZE_16BIT SPI_DATASIZE_16BIT -#define TFT_IO_DRIVER TFT_SPI +#define DATASIZE_8BIT SPI_DATASIZE_8BIT +#define DATASIZE_16BIT SPI_DATASIZE_16BIT +#define DATASIZE_32BIT SPI_DATASIZE_32BIT +#define TFT_IO_DRIVER TFT_SPI +#define DMA_MAX_WORDS 0xFFFF class TFT_SPI { private: static SPI_HandleTypeDef SPIx; static DMA_HandleTypeDef DMAtx; - static uint32_t ReadID(uint16_t Reg); - static void Transmit(uint16_t Data); - static void TransmitDMA(uint32_t MemoryIncrease, uint16_t *Data, uint16_t Count); + static uint32_t readID(const uint16_t inReg); + static void transmit(uint16_t data); + static void transmit(uint32_t memoryIncrease, uint16_t *data, uint16_t count); + static void transmitDMA(uint32_t memoryIncrease, uint16_t *data, uint16_t count); + #if ENABLED(USE_SPI_DMA_TC) + static void transmitDMA_IT(uint32_t memoryIncrease, uint16_t *data, uint16_t count); + #endif public: - static void Init(); - static uint32_t GetID(); + static void init(); + static uint32_t getID(); static bool isBusy(); - static void Abort(); + static void abort(); - static void DataTransferBegin(uint16_t DataWidth = DATASIZE_16BIT); - static void DataTransferEnd() { WRITE(TFT_CS_PIN, HIGH); }; - static void DataTransferAbort(); + static void dataTransferBegin(uint16_t dataWidth=DATASIZE_16BIT); + static void dataTransferEnd() { WRITE(TFT_CS_PIN, HIGH); __HAL_SPI_DISABLE(&SPIx); }; + static void dataTransferAbort(); - static void WriteData(uint16_t Data) { Transmit(Data); } - static void WriteReg(uint16_t Reg) { WRITE(TFT_A0_PIN, LOW); Transmit(Reg); WRITE(TFT_A0_PIN, HIGH); } + static void writeData(uint16_t data) { transmit(data); } + static void writeReg(const uint16_t inReg) { WRITE(TFT_A0_PIN, LOW); transmit(inReg); WRITE(TFT_A0_PIN, HIGH); } - static void WriteSequence(uint16_t *Data, uint16_t Count) { TransmitDMA(DMA_MINC_ENABLE, Data, Count); } - static void WriteMultiple(uint16_t Color, uint16_t Count) { static uint16_t Data; Data = Color; TransmitDMA(DMA_MINC_DISABLE, &Data, Count); } + static void writeSequence_DMA(uint16_t *data, uint16_t count) { transmitDMA(DMA_MINC_ENABLE, data, count); } + static void writeMultiple_DMA(uint16_t color, uint16_t count) { static uint16_t data; data = color; transmitDMA(DMA_MINC_DISABLE, &data, count); } + + #if ENABLED(USE_SPI_DMA_TC) + static void writeSequenceIT(uint16_t *data, uint16_t count) { transmitDMA_IT(DMA_MINC_ENABLE, data, count); } + inline static void DMA_IRQHandler() { HAL_DMA_IRQHandler(&TFT_SPI::DMAtx); } + #endif + + static void writeSequence(uint16_t *data, uint16_t count) { transmit(DMA_MINC_ENABLE, data, count); } + static void writeMultiple(uint16_t color, uint32_t count) { + while (count > 0) { + transmit(DMA_MINC_DISABLE, &color, count > DMA_MAX_WORDS ? DMA_MAX_WORDS : count); + count = count > DMA_MAX_WORDS ? count - DMA_MAX_WORDS : 0; + } + } }; diff --git a/Marlin/src/HAL/STM32/tft/xpt2046.cpp b/Marlin/src/HAL/STM32/tft/xpt2046.cpp index 921e377a9f..c5645ad79c 100644 --- a/Marlin/src/HAL/STM32/tft/xpt2046.cpp +++ b/Marlin/src/HAL/STM32/tft/xpt2046.cpp @@ -1,6 +1,9 @@ /** * Marlin 3D Printer Firmware - * Copyright (c) 2019 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] + * Copyright (c) 2020 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 @@ -17,9 +20,13 @@ * */ +#include "../../platforms.h" + +#ifdef HAL_STM32 + #include "../../../inc/MarlinConfig.h" -#if HAS_TFT_XPT2046 +#if HAS_TFT_XPT2046 || HAS_RES_TOUCH_BUTTONS #include "xpt2046.h" #include "pinconfig.h" @@ -27,9 +34,8 @@ uint16_t delta(uint16_t a, uint16_t b) { return a > b ? a - b : b - a; } SPI_HandleTypeDef XPT2046::SPIx; -DMA_HandleTypeDef XPT2046::DMAtx; -void XPT2046::Init() { +void XPT2046::init() { SPI_TypeDef *spiInstance; OUT_WRITE(TOUCH_CS_PIN, HIGH); @@ -50,7 +56,6 @@ void XPT2046::Init() { SPIx.Init.NSS = SPI_NSS_SOFT; SPIx.Init.Mode = SPI_MODE_MASTER; SPIx.Init.Direction = SPI_DIRECTION_2LINES; - SPIx.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8; SPIx.Init.CLKPhase = SPI_PHASE_2EDGE; SPIx.Init.CLKPolarity = SPI_POLARITY_HIGH; SPIx.Init.DataSize = SPI_DATASIZE_8BIT; @@ -59,6 +64,20 @@ void XPT2046::Init() { SPIx.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; SPIx.Init.CRCPolynomial = 10; + #ifndef STM32H7xx + SPIx.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8; // 4.5 MBit/s for F103 and 5.25 MBit/s for F407 + #else + SPIx.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16; // 5 MBit/s for H743 + SPIx.Init.NSSPMode = SPI_NSS_PULSE_ENABLE; + SPIx.Init.NSSPolarity = SPI_NSS_POLARITY_LOW; + SPIx.Init.FifoThreshold = SPI_FIFO_THRESHOLD_01DATA; + SPIx.Init.MasterSSIdleness = SPI_MASTER_SS_IDLENESS_00CYCLE; + SPIx.Init.MasterInterDataIdleness = SPI_MASTER_INTERDATA_IDLENESS_00CYCLE; + SPIx.Init.MasterReceiverAutoSusp = SPI_MASTER_RX_AUTOSUSP_DISABLE; + SPIx.Init.MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_ENABLE; + SPIx.Init.IOSwap = SPI_IO_SWAP_DISABLE; + #endif + pinmap_pinout(digitalPinToPinName(TOUCH_SCK_PIN), PinMap_SPI_SCLK); pinmap_pinout(digitalPinToPinName(TOUCH_MOSI_PIN), PinMap_SPI_MOSI); pinmap_pinout(digitalPinToPinName(TOUCH_MISO_PIN), PinMap_SPI_MISO); @@ -67,43 +86,24 @@ void XPT2046::Init() { if (SPIx.Instance == SPI1) { __HAL_RCC_SPI1_CLK_ENABLE(); SPIx.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16; - #ifdef STM32F1xx - DMAtx.Instance = DMA1_Channel3; - #elif defined(STM32F4xx) - DMAtx.Instance = DMA2_Stream3; // DMA2_Stream5 - #endif - //SERIAL_ECHO_MSG(" Touch Screen on SPI1"); } #endif #ifdef SPI2_BASE if (SPIx.Instance == SPI2) { __HAL_RCC_SPI2_CLK_ENABLE(); - #ifdef STM32F1xx - DMAtx.Instance = DMA1_Channel5; - #elif defined(STM32F4xx) - DMAtx.Instance = DMA1_Stream4; - #endif - //SERIAL_ECHO_MSG(" Touch Screen on SPI2"); } #endif #ifdef SPI3_BASE if (SPIx.Instance == SPI3) { __HAL_RCC_SPI3_CLK_ENABLE(); - #ifdef STM32F1xx - DMAtx.Instance = DMA2_Channel2; - #elif defined(STM32F4xx) - DMAtx.Instance = DMA1_Stream5; // DMA1_Stream7 - #endif - //SERIAL_ECHO_MSG(" Touch Screen on SPI3"); } #endif } else { - SPIx.Instance = NULL; + SPIx.Instance = nullptr; SET_INPUT(TOUCH_MISO_PIN); SET_OUTPUT(TOUCH_MOSI_PIN); SET_OUTPUT(TOUCH_SCK_PIN); - //SERIAL_ECHO_MSG(" Touch Screen on Software SPI"); } getRawData(XPT2046_Z1); @@ -119,9 +119,8 @@ bool XPT2046::isTouched() { ); } -bool XPT2046::getRawPoint(int16_t *x, int16_t *y) { - if (isBusy()) return false; - if (!isTouched()) return false; +bool XPT2046::getRawPoint(int16_t * const x, int16_t * const y) { + if (isBusy() || !isTouched()) return false; *x = getRawData(XPT2046_X); *y = getRawData(XPT2046_Y); return isTouched(); @@ -130,14 +129,14 @@ bool XPT2046::getRawPoint(int16_t *x, int16_t *y) { uint16_t XPT2046::getRawData(const XPTCoordinate coordinate) { uint16_t data[3]; - DataTransferBegin(); + dataTransferBegin(); for (uint16_t i = 0; i < 3 ; i++) { IO(coordinate); data[i] = (IO() << 4) | (IO() >> 4); } - DataTransferEnd(); + dataTransferEnd(); uint16_t delta01 = delta(data[0], data[1]); uint16_t delta02 = delta(data[0], data[2]); @@ -153,17 +152,34 @@ uint16_t XPT2046::getRawData(const XPTCoordinate coordinate) { return (data[0] + data[1]) >> 1; } -uint16_t XPT2046::HardwareIO(uint16_t data) { - __HAL_SPI_ENABLE(&SPIx); - while ((SPIx.Instance->SR & SPI_FLAG_TXE) != SPI_FLAG_TXE) {} - SPIx.Instance->DR = data; - while ((SPIx.Instance->SR & SPI_FLAG_RXNE) != SPI_FLAG_RXNE) {} - __HAL_SPI_DISABLE(&SPIx); +uint16_t XPT2046::hardwareIO(uint16_t data) { + #ifdef STM32H7xx + MODIFY_REG(SPIx.Instance->CR2, SPI_CR2_TSIZE, 1); + __HAL_SPI_ENABLE(&SPIx); + SET_BIT(SPIx.Instance->CR1, SPI_CR1_CSTART); - return SPIx.Instance->DR; + SPIx.Instance->TXDR = data; + + while (!__HAL_SPI_GET_FLAG(&SPIx, SPI_SR_EOT)) {} + data = SPIx.Instance->RXDR; + + __HAL_SPI_DISABLE(&SPIx); + __HAL_SPI_CLEAR_EOTFLAG(&SPIx); + __HAL_SPI_CLEAR_TXTFFLAG(&SPIx); + + return data; + #else + __HAL_SPI_ENABLE(&SPIx); + while ((SPIx.Instance->SR & SPI_FLAG_TXE) != SPI_FLAG_TXE) {} + SPIx.Instance->DR = data; + while ((SPIx.Instance->SR & SPI_FLAG_RXNE) != SPI_FLAG_RXNE) {} + __HAL_SPI_DISABLE(&SPIx); + + return SPIx.Instance->DR; + #endif } -uint16_t XPT2046::SoftwareIO(uint16_t data) { +uint16_t XPT2046::softwareIO(uint16_t data) { uint16_t result = 0; for (uint8_t j = 0x80; j > 0; j >>= 1) { @@ -183,3 +199,4 @@ uint16_t XPT2046::SoftwareIO(uint16_t data) { } #endif // HAS_TFT_XPT2046 +#endif // HAL_STM32 diff --git a/Marlin/src/HAL/STM32/tft/xpt2046.h b/Marlin/src/HAL/STM32/tft/xpt2046.h index 7a6d8439c5..f3d3e53291 100644 --- a/Marlin/src/HAL/STM32/tft/xpt2046.h +++ b/Marlin/src/HAL/STM32/tft/xpt2046.h @@ -1,6 +1,9 @@ /** * Marlin 3D Printer Firmware - * Copyright (c) 2019 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] + * Copyright (c) 2020 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 @@ -19,9 +22,13 @@ #pragma once #ifdef STM32F1xx - #include + #include "stm32f1xx_hal.h" #elif defined(STM32F4xx) - #include + #include "stm32f4xx_hal.h" +#elif defined(STM32H7xx) + #include "stm32h7xx_hal.h" +#else + #error SPI Touch Screen is currently only supported on STM32F1, STM32F4 and STM32H7 hardware. #endif #include "../../../inc/MarlinConfig.h" @@ -42,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 @@ -53,34 +64,26 @@ enum XPTCoordinate : uint8_t { XPT2046_Z2 = 0x40 | XPT2046_CONTROL | XPT2046_DFR_MODE, }; -#if !defined(XPT2046_Z1_THRESHOLD) +#ifndef XPT2046_Z1_THRESHOLD #define XPT2046_Z1_THRESHOLD 10 #endif -#ifdef STM32F1xx - #define __IS_DMA_ENABLED(__HANDLE__) ((__HANDLE__)->Instance->CCR & DMA_CCR_EN) -#elif defined(STM32F4xx) - #define __IS_DMA_ENABLED(__HANDLE__) ((__HANDLE__)->Instance->CR & DMA_SxCR_EN) -#endif - - class XPT2046 { private: static SPI_HandleTypeDef SPIx; - static DMA_HandleTypeDef DMAtx; - static bool isBusy() { return SPIx.Instance ? __IS_DMA_ENABLED(&DMAtx) : false; } + static bool isBusy() { return false; } static uint16_t getRawData(const XPTCoordinate coordinate); static bool isTouched(); - static inline void DataTransferBegin() { if (SPIx.Instance) { HAL_SPI_Init(&SPIx); } WRITE(TOUCH_CS_PIN, LOW); }; - static inline void DataTransferEnd() { WRITE(TOUCH_CS_PIN, HIGH); }; - static uint16_t HardwareIO(uint16_t data); - static uint16_t SoftwareIO(uint16_t data); - static uint16_t IO(uint16_t data = 0) { return SPIx.Instance ? HardwareIO(data) : SoftwareIO(data); } + static void dataTransferBegin() { if (SPIx.Instance) { HAL_SPI_Init(&SPIx); } WRITE(TOUCH_CS_PIN, LOW); }; + static void dataTransferEnd() { WRITE(TOUCH_CS_PIN, HIGH); }; + static uint16_t hardwareIO(uint16_t data); + static uint16_t softwareIO(uint16_t data); + static uint16_t IO(uint16_t data = 0) { return SPIx.Instance ? hardwareIO(data) : softwareIO(data); } public: - static void Init(); - static bool getRawPoint(int16_t *x, int16_t *y); + static void init(); + static bool getRawPoint(int16_t * const x, int16_t * const y); }; diff --git a/Marlin/src/HAL/STM32/timers.cpp b/Marlin/src/HAL/STM32/timers.cpp index c0ba19abe5..5b58a915f0 100644 --- a/Marlin/src/HAL/STM32/timers.cpp +++ b/Marlin/src/HAL/STM32/timers.cpp @@ -1,9 +1,9 @@ /** * Marlin 3D Printer Firmware - * * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com - * Copyright (c) 2015-2016 Nico Tonnhofer wurstnase.reprap@gmail.com + * + * 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 @@ -19,7 +19,9 @@ * along with this program. If not, see . * */ -#if defined(ARDUINO_ARCH_STM32) && !defined(STM32GENERIC) +#include "../platforms.h" + +#ifdef HAL_STM32 #include "../../inc/MarlinConfig.h" @@ -27,8 +29,6 @@ // Local defines // ------------------------ -#define NUM_HARDWARE_TIMERS 2 - // Default timer priorities. Override by specifying alternate priorities in the board pins file. // The TONE timer is not present here, as it currently cannot be set programmatically. It is set // by defining TIM_IRQ_PRIO in the variant.h or platformio.ini file, which adjusts the default @@ -67,27 +67,20 @@ #endif #endif -#ifdef STM32F0xx - #define MCU_TIMER_RATE (F_CPU) // Frequency of timer peripherals +#if defined(STM32F0xx) || defined(STM32G0xx) #define MCU_STEP_TIMER 16 #define MCU_TEMP_TIMER 17 #elif defined(STM32F1xx) - #define MCU_TIMER_RATE (F_CPU) #define MCU_STEP_TIMER 4 #define MCU_TEMP_TIMER 2 #elif defined(STM32F401xC) || defined(STM32F401xE) - #define MCU_TIMER_RATE (F_CPU / 2) - #define MCU_STEP_TIMER 9 + #define MCU_STEP_TIMER 9 // STM32F401 has no TIM6, TIM7, or TIM8 #define MCU_TEMP_TIMER 10 -#elif defined(STM32F4xx) || defined(STM32F7xx) - #define MCU_TIMER_RATE (F_CPU / 2) - #define MCU_STEP_TIMER 6 // STM32F401 has no TIM6, TIM7, or TIM8 +#elif defined(STM32F4xx) || defined(STM32F7xx) || defined(STM32H7xx) + #define MCU_STEP_TIMER 6 #define MCU_TEMP_TIMER 14 // TIM7 is consumed by Software Serial if used. #endif -#ifndef HAL_TIMER_RATE - #define HAL_TIMER_RATE MCU_TIMER_RATE -#endif #ifndef STEP_TIMER #define STEP_TIMER MCU_STEP_TIMER #endif @@ -100,26 +93,34 @@ #define STEP_TIMER_DEV _TIMER_DEV(STEP_TIMER) #define TEMP_TIMER_DEV _TIMER_DEV(TEMP_TIMER) -#define __TIMER_IRQ_NAME(X) TIM##X##_IRQn -#define _TIMER_IRQ_NAME(X) __TIMER_IRQ_NAME(X) -#define STEP_TIMER_IRQ_NAME _TIMER_IRQ_NAME(STEP_TIMER) -#define TEMP_TIMER_IRQ_NAME _TIMER_IRQ_NAME(TEMP_TIMER) +// -------------------------------------------------------------------------- +// Local defines +// -------------------------------------------------------------------------- -// ------------------------ +#define NUM_HARDWARE_TIMERS 2 + +// -------------------------------------------------------------------------- // Private Variables -// ------------------------ +// -------------------------------------------------------------------------- -HardwareTimer *timer_instance[NUM_HARDWARE_TIMERS] = { NULL }; +HardwareTimer *timer_instance[NUM_HARDWARE_TIMERS] = { nullptr }; // ------------------------ // Public functions // ------------------------ +uint32_t GetStepperTimerClkFreq() { + // Timer input clocks vary between devices, and in some cases between timers on the same device. + // Retrieve at runtime to ensure device compatibility. Cache result to avoid repeated overhead. + static uint32_t clkfreq = timer_instance[MF_TIMER_STEP]->getTimerClkFreq(); + return clkfreq; +} + // frequency is in Hertz void HAL_timer_start(const uint8_t timer_num, const uint32_t frequency) { if (!HAL_timer_initialized(timer_num)) { switch (timer_num) { - case STEP_TIMER_NUM: // STEPPER TIMER - use a 32bit timer if possible + case MF_TIMER_STEP: // STEPPER TIMER - use a 32bit timer if possible timer_instance[timer_num] = new HardwareTimer(STEP_TIMER_DEV); /* Set the prescaler to the final desired value. * This will change the effective ISR callback frequency but when @@ -136,9 +137,9 @@ 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 TEMP_TIMER_NUM: // TEMP TIMER - any available 16bit timer + case MF_TIMER_TEMP: // TEMP TIMER - any available 16bit timer timer_instance[timer_num] = new HardwareTimer(TEMP_TIMER_DEV); // The prescale factor is computed automatically for HERTZ_FORMAT timer_instance[timer_num]->setOverflow(frequency, HERTZ_FORMAT); @@ -158,10 +159,10 @@ void HAL_timer_start(const uint8_t timer_num, const uint32_t frequency) { // These calls can be removed and replaced with // timer_instance[timer_num]->setInterruptPriority switch (timer_num) { - case STEP_TIMER_NUM: + case MF_TIMER_STEP: timer_instance[timer_num]->setInterruptPriority(STEP_TIMER_IRQ_PRIO, 0); break; - case TEMP_TIMER_NUM: + case MF_TIMER_TEMP: timer_instance[timer_num]->setInterruptPriority(TEMP_TIMER_IRQ_PRIO, 0); break; } @@ -171,10 +172,10 @@ void HAL_timer_start(const uint8_t timer_num, const uint32_t frequency) { void HAL_timer_enable_interrupt(const uint8_t timer_num) { if (HAL_timer_initialized(timer_num) && !timer_instance[timer_num]->hasInterrupt()) { switch (timer_num) { - case STEP_TIMER_NUM: + case MF_TIMER_STEP: timer_instance[timer_num]->attachInterrupt(Step_Handler); break; - case TEMP_TIMER_NUM: + case MF_TIMER_TEMP: timer_instance[timer_num]->attachInterrupt(Temp_Handler); break; } @@ -194,87 +195,132 @@ void SetTimerInterruptPriorities() { TERN_(HAS_SERVOS, libServo::setInterruptPriority(SERVO_TIMER_IRQ_PRIO, 0)); } -// This is a terrible hack to replicate the behavior used in the framework's SoftwareSerial.cpp -// to choose a serial timer. It will select TIM7 on most boards used by Marlin, but this is more -// resiliant to new MCUs which may not have a TIM7. Best practice is to explicitly specify -// TIMER_SERIAL to avoid relying on framework selections which may not be predictable. -#if !defined(TIMER_SERIAL) - #if defined (TIM18_BASE) - #define TIMER_SERIAL TIM18 - #elif defined (TIM7_BASE) - #define TIMER_SERIAL TIM7 - #elif defined (TIM6_BASE) - #define TIMER_SERIAL TIM6 - #elif defined (TIM22_BASE) - #define TIMER_SERIAL TIM22 - #elif defined (TIM21_BASE) - #define TIMER_SERIAL TIM21 - #elif defined (TIM17_BASE) - #define TIMER_SERIAL TIM17 - #elif defined (TIM16_BASE) - #define TIMER_SERIAL TIM16 - #elif defined (TIM15_BASE) - #define TIMER_SERIAL TIM15 - #elif defined (TIM14_BASE) - #define TIMER_SERIAL TIM14 - #elif defined (TIM13_BASE) - #define TIMER_SERIAL TIM13 - #elif defined (TIM11_BASE) - #define TIMER_SERIAL TIM11 - #elif defined (TIM10_BASE) - #define TIMER_SERIAL TIM10 - #elif defined (TIM12_BASE) - #define TIMER_SERIAL TIM12 - #elif defined (TIM19_BASE) - #define TIMER_SERIAL TIM19 - #elif defined (TIM9_BASE) - #define TIMER_SERIAL TIM9 - #elif defined (TIM5_BASE) - #define TIMER_SERIAL TIM5 - #elif defined (TIM4_BASE) - #define TIMER_SERIAL TIM4 - #elif defined (TIM3_BASE) - #define TIMER_SERIAL TIM3 - #elif defined (TIM2_BASE) - #define TIMER_SERIAL TIM2 - #elif defined (TIM20_BASE) - #define TIMER_SERIAL TIM20 - #elif defined (TIM8_BASE) - #define TIMER_SERIAL TIM8 - #elif defined (TIM1_BASE) - #define TIMER_SERIAL TIM1 - #else - #error No suitable timer found for SoftwareSerial, define TIMER_SERIAL in variant.h +// ------------------------ +// Detect timer conflicts +// ------------------------ + +// This list serves two purposes. Firstly, it facilitates build-time mapping between +// variant-defined timer names (such as TIM1) and timer numbers. It also replicates +// the order of timers used in the framework's SoftwareSerial.cpp. The first timer in +// this list will be automatically used by SoftwareSerial if it is not already defined +// in the board's variant or compiler options. +static constexpr struct {uintptr_t base_address; int timer_number;} stm32_timer_map[] = { + #ifdef TIM18_BASE + { uintptr_t(TIM18), 18 }, #endif + #ifdef TIM7_BASE + { uintptr_t(TIM7), 7 }, + #endif + #ifdef TIM6_BASE + { uintptr_t(TIM6), 6 }, + #endif + #ifdef TIM22_BASE + { uintptr_t(TIM22), 22 }, + #endif + #ifdef TIM21_BASE + { uintptr_t(TIM21), 21 }, + #endif + #ifdef TIM17_BASE + { uintptr_t(TIM17), 17 }, + #endif + #ifdef TIM16_BASE + { uintptr_t(TIM16), 16 }, + #endif + #ifdef TIM15_BASE + { uintptr_t(TIM15), 15 }, + #endif + #ifdef TIM14_BASE + { uintptr_t(TIM14), 14 }, + #endif + #ifdef TIM13_BASE + { uintptr_t(TIM13), 13 }, + #endif + #ifdef TIM11_BASE + { uintptr_t(TIM11), 11 }, + #endif + #ifdef TIM10_BASE + { uintptr_t(TIM10), 10 }, + #endif + #ifdef TIM12_BASE + { uintptr_t(TIM12), 12 }, + #endif + #ifdef TIM19_BASE + { uintptr_t(TIM19), 19 }, + #endif + #ifdef TIM9_BASE + { uintptr_t(TIM9), 9 }, + #endif + #ifdef TIM5_BASE + { uintptr_t(TIM5), 5 }, + #endif + #ifdef TIM4_BASE + { uintptr_t(TIM4), 4 }, + #endif + #ifdef TIM3_BASE + { uintptr_t(TIM3), 3 }, + #endif + #ifdef TIM2_BASE + { uintptr_t(TIM2), 2 }, + #endif + #ifdef TIM20_BASE + { uintptr_t(TIM20), 20 }, + #endif + #ifdef TIM8_BASE + { uintptr_t(TIM8), 8 }, + #endif + #ifdef TIM1_BASE + { uintptr_t(TIM1), 1 } + #endif +}; + +// Convert from a timer base address to its integer timer number. +static constexpr int get_timer_num_from_base_address(uintptr_t base_address) { + for (const auto &timer : stm32_timer_map) + if (timer.base_address == base_address) return timer.timer_number; + return 0; +} + +// The platform's SoftwareSerial.cpp will use the first timer from stm32_timer_map. +#if HAS_TMC_SW_SERIAL && !defined(TIMER_SERIAL) + #define TIMER_SERIAL (stm32_timer_map[0].base_address) #endif -// Place all timers used into an array, then recursively check for duplicates during compilation. -// This does not currently account for timers used for PWM, such as for fans. -// Timers are actually pointers. Convert to integers to simplify constexpr logic. -static constexpr uintptr_t timers_in_use[] = { - uintptr_t(TEMP_TIMER_DEV), // Override in pins file - uintptr_t(STEP_TIMER_DEV), // Override in pins file +// constexpr doesn't like using the base address pointers that timers evaluate to. +// We can get away with casting them to uintptr_t, if we do so inside an array. +// GCC will not currently do it directly to a uintptr_t. +TERN_(HAS_TMC_SW_SERIAL, static constexpr uintptr_t timer_serial[] = {uintptr_t(TIMER_SERIAL)}); +TERN_(SPEAKER, static constexpr uintptr_t timer_tone[] = {uintptr_t(TIMER_TONE)}); +TERN_(HAS_SERVOS, static constexpr uintptr_t timer_servo[] = {uintptr_t(TIMER_SERVO)}); + +enum TimerPurpose { TP_SERIAL, TP_TONE, TP_SERVO, TP_STEP, TP_TEMP }; + +// List of timers, to enable checking for conflicts. +// Includes the purpose of each timer 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 - uintptr_t(TIMER_SERIAL), // Set in variant.h, or as a define in platformio.h if not present in variant.h + { TP_SERIAL, get_timer_num_from_base_address(timer_serial[0]) }, // Set in variant.h, or as a define in platformio.h if not present in variant.h #endif #if ENABLED(SPEAKER) - uintptr_t(TIMER_TONE), // Set in variant.h, or as a define in platformio.h if not present in variant.h + { TP_TONE, get_timer_num_from_base_address(timer_tone[0]) }, // Set in variant.h, or as a define in platformio.h if not present in variant.h #endif #if HAS_SERVOS - uintptr_t(TIMER_SERVO), // Set in variant.h, or as a define in platformio.h if not present in variant.h + { TP_SERVO, get_timer_num_from_base_address(timer_servo[0]) }, // Set in variant.h, or as a define in platformio.h if not present in variant.h #endif - }; + { TP_STEP, STEP_TIMER }, + { TP_TEMP, TEMP_TIMER }, +}; -static constexpr bool verify_no_duplicate_timers() { - LOOP_L_N(i, COUNT(timers_in_use)) - LOOP_S_L_N(j, i + 1, COUNT(timers_in_use)) - if (timers_in_use[i] == timers_in_use[j]) return false; +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, VS Code can evaluate the array when hovering over it, -// making it easy to identify the conflicting timers. -static_assert(verify_no_duplicate_timers(), "One or more timer conflict detected"); +// If this assertion fails at compile time, review the timers_in_use array. +// If default_envs is defined properly in platformio.ini, VS Code 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_STM32 && !STM32GENERIC +#endif // HAL_STM32 diff --git a/Marlin/src/HAL/STM32/timers.h b/Marlin/src/HAL/STM32/timers.h index 5515219ead..a6c9204a77 100644 --- a/Marlin/src/HAL/STM32/timers.h +++ b/Marlin/src/HAL/STM32/timers.h @@ -1,9 +1,9 @@ /** * Marlin 3D Printer Firmware - * * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com - * Copyright (c) 2017 Victor Perez + * + * 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 @@ -21,15 +21,12 @@ */ #pragma once -#include #include "../../inc/MarlinConfig.h" // ------------------------ // Defines // ------------------------ -#define FORCE_INLINE __attribute__((always_inline)) inline - // STM32 timers may be 16 or 32 bit. Limiting HAL_TIMER_TYPE_MAX to 16 bits // avoids issues with STM32F0 MCUs, which seem to pause timers if UINT32_MAX // is written to the register. STM32F4 timers do not manifest this issue, @@ -41,35 +38,39 @@ // 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) -#ifndef STEP_TIMER_NUM - #define STEP_TIMER_NUM 0 // Timer Index for Stepper -#endif -#ifndef PULSE_TIMER_NUM - #define PULSE_TIMER_NUM STEP_TIMER_NUM -#endif -#ifndef TEMP_TIMER_NUM - #define TEMP_TIMER_NUM 1 // Timer Index for Temperature +// Marlin timer_instance[] content (unrelated to timer selection) +#define MF_TIMER_STEP 0 // Timer Index for Stepper +#define MF_TIMER_TEMP 1 // Timer Index for Temperature +#define MF_TIMER_PULSE MF_TIMER_STEP + +#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. + +#ifndef HAL_TIMER_RATE + extern uint32_t GetStepperTimerClkFreq(); + #define HAL_TIMER_RATE GetStepperTimerClkFreq() #endif -#define TEMP_TIMER_FREQUENCY 1000 // Temperature::isr() is expected to be called at around 1kHz +// 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 -#define STEPPER_TIMER_PRESCALE ((HAL_TIMER_RATE)/(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(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) extern void Step_Handler(); extern void Temp_Handler(); @@ -102,7 +103,7 @@ void SetTimerInterruptPriorities(); // FORCE_INLINE because these are used in performance-critical situations FORCE_INLINE bool HAL_timer_initialized(const uint8_t timer_num) { - return timer_instance[timer_num] != NULL; + return timer_instance[timer_num] != nullptr; } FORCE_INLINE static hal_timer_t HAL_timer_get_count(const uint8_t timer_num) { return HAL_timer_initialized(timer_num) ? timer_instance[timer_num]->getCount() : 0; @@ -120,5 +121,5 @@ FORCE_INLINE static void HAL_timer_set_compare(const uint8_t timer_num, const ha } } -#define HAL_timer_isr_prologue(TIMER_NUM) -#define HAL_timer_isr_epilogue(TIMER_NUM) +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 new file mode 100644 index 0000000000..96f73002a5 --- /dev/null +++ b/Marlin/src/HAL/STM32/u8g/LCD_defines.h @@ -0,0 +1,35 @@ +/** + * 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 + * (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 + +/** + * STM32 LCD-specific defines + */ + +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 + +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 0000000000..72abe1a656 --- /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 2dd1bef12c..1ca8b19976 100644 --- a/Marlin/src/HAL/STM32/usb_serial.cpp +++ b/Marlin/src/HAL/STM32/usb_serial.cpp @@ -2,6 +2,9 @@ * Marlin 3D Printer Firmware * Copyright (c) 2020 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 @@ -17,11 +20,13 @@ * */ -#if defined(ARDUINO_ARCH_STM32) && !defined(STM32GENERIC) +#include "../platforms.h" + +#ifdef HAL_STM32 #include "../../inc/MarlinConfigPre.h" -#if ENABLED(EMERGENCY_PARSER) +#if ENABLED(EMERGENCY_PARSER) && ANY(USBD_USE_CDC, USBD_USE_CDC_MSC) #include "usb_serial.h" #include "../../feature/e_parser.h" @@ -51,5 +56,5 @@ void USB_Hook_init() { USBD_CDC_fops.Receive = USBD_CDC_Receive_hook; } -#endif // EMERGENCY_PARSER -#endif // ARDUINO_ARCH_STM32 && !STM32GENERIC +#endif // EMERGENCY_PARSER && (USBD_USE_CDC || USBD_USE_CDC_MSC) +#endif // HAL_STM32 diff --git a/Marlin/src/HAL/STM32/usb_serial.h b/Marlin/src/HAL/STM32/usb_serial.h index ca61b9ed23..3edb6fd618 100644 --- a/Marlin/src/HAL/STM32/usb_serial.h +++ b/Marlin/src/HAL/STM32/usb_serial.h @@ -2,6 +2,9 @@ * Marlin 3D Printer Firmware * Copyright (c) 2020 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/STM32F1/HAL.cpp b/Marlin/src/HAL/STM32F1/HAL.cpp index cd1efc1659..fc1680cbf4 100644 --- a/Marlin/src/HAL/STM32F1/HAL.cpp +++ b/Marlin/src/HAL/STM32F1/HAL.cpp @@ -1,10 +1,9 @@ /** * Marlin 3D Printer Firmware - * * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com - * Copyright (c) 2015-2016 Nico Tonnhofer wurstnase.reprap@gmail.com - * Copyright (c) 2017 Victor Perez + * + * 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 @@ -30,205 +29,197 @@ #include "../../inc/MarlinConfig.h" #include "HAL.h" +#include "adc.h" +uint16_t adc_results[ADC_COUNT]; + +// ------------------------ +// Serial ports +// ------------------------ + +#if defined(SERIAL_USB) && !HAS_SD_HOST_DRIVE + + USBSerial SerialUSB; + DefaultSerial1 MSerial0(true, SerialUSB); + + #if ENABLED(EMERGENCY_PARSER) + #include "../libmaple/usb/stm32f1/usb_reg_map.h" + #include "libmaple/usb_cdcacm.h" + // The original callback is not called (no way to retrieve address). + // That callback detects a special STM32 reset sequence: this functionality is not essential + // as M997 achieves the same. + void my_rx_callback(unsigned int, void*) { + // max length of 16 is enough to contain all emergency commands + uint8 buf[16]; + + //rx is usbSerialPart.endpoints[2] + uint16 len = usb_get_ep_rx_count(USB_CDCACM_RX_ENDP); + uint32 total = usb_cdcacm_data_available(); + + if (len == 0 || total == 0 || !WITHIN(total, len, COUNT(buf))) + return; + + // cannot get character by character due to bug in composite_cdcacm_peek_ex + len = usb_cdcacm_peek(buf, total); + + for (uint32 i = 0; i < len; i++) + emergency_parser.update(MSerial0.emergency_state, buf[i + total - len]); + } + #endif + +#endif // SERIAL_USB && !HAS_SD_HOST_DRIVE + +// ------------------------ +// Watchdog Timer +// ------------------------ + +#if ENABLED(USE_WATCHDOG) + + #include + + void watchdogSetup() { + // do whatever. don't remove this function. + } + + /** + * The watchdog clock is 40Khz. So for a 4s or 8s interval use a /256 preescaler and 625 or 1250 reload value (counts down to 0). + */ + #define STM32F1_WD_RELOAD TERN(WATCHDOG_DURATION_8S, 1250, 625) // 4 or 8 second timeout + + /** + * @brief Initialize the independent hardware watchdog. + * + * @return No return + * + * @details The watchdog clock is 40Khz. So for a 4s or 8s interval use a /256 preescaler and 625 or 1250 reload value (counts down to 0). + */ + void MarlinHAL::watchdog_init() { + #if DISABLED(DISABLE_WATCHDOG_INIT) + iwdg_init(IWDG_PRE_256, STM32F1_WD_RELOAD); + #endif + } + + // Reset watchdog. MUST be called every 4 or 8 seconds after the + // first watchdog_init or the STM32F1 will reset. + void MarlinHAL::watchdog_refresh() { + #if DISABLED(PINS_DEBUGGING) && PIN_EXISTS(LED) + TOGGLE(LED_PIN); // heartbeat indicator + #endif + iwdg_feed(); + } + +#endif // USE_WATCHDOG + +// ------------------------ +// ADC +// ------------------------ + +// Watch out for recursion here! Our pin_t is signed, so pass through to Arduino -> analogRead(uint8_t) + +uint16_t analogRead(const pin_t pin) { + const bool is_analog = _GET_MODE(pin) == GPIO_INPUT_ANALOG; + return is_analog ? analogRead(uint8_t(pin)) : 0; +} + +// Wrapper to maple unprotected analogWrite +void analogWrite(const pin_t pin, int pwm_val8) { + if (PWM_PIN(pin)) analogWrite(uint8_t(pin), pwm_val8); +} + +uint16_t MarlinHAL::adc_result; + +#ifndef VOXELAB_N32 + #include -// ------------------------ -// Types -// ------------------------ +// 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_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 + adc.calibrate(); + adc.setSampleRate((F_CPU > 72000000) ? ADC_SMPR_71_5 : ADC_SMPR_41_5); // 71.5 or 41.5 ADC cycles + adc.setPins((uint8_t *)adc_pins, ADC_COUNT); + adc.setDMA(adc_results, uint16_t(ADC_COUNT), uint32_t(DMA_MINC_MODE | DMA_CIRC_MODE), nullptr); + adc.setScanMode(); + adc.setContinuous(); + adc.startConversion(); +} -#define __I -#define __IO volatile - typedef struct { - __I uint32_t CPUID; /*!< Offset: 0x000 (R/ ) CPUID Base Register */ - __IO uint32_t ICSR; /*!< Offset: 0x004 (R/W) Interrupt Control and State Register */ - __IO uint32_t VTOR; /*!< Offset: 0x008 (R/W) Vector Table Offset Register */ - __IO uint32_t AIRCR; /*!< Offset: 0x00C (R/W) Application Interrupt and Reset Control Register */ - __IO uint32_t SCR; /*!< Offset: 0x010 (R/W) System Control Register */ - __IO uint32_t CCR; /*!< Offset: 0x014 (R/W) Configuration Control Register */ - __IO uint8_t SHP[12]; /*!< Offset: 0x018 (R/W) System Handlers Priority Registers (4-7, 8-11, 12-15) */ - __IO uint32_t SHCSR; /*!< Offset: 0x024 (R/W) System Handler Control and State Register */ - __IO uint32_t CFSR; /*!< Offset: 0x028 (R/W) Configurable Fault Status Register */ - __IO uint32_t HFSR; /*!< Offset: 0x02C (R/W) HardFault Status Register */ - __IO uint32_t DFSR; /*!< Offset: 0x030 (R/W) Debug Fault Status Register */ - __IO uint32_t MMFAR; /*!< Offset: 0x034 (R/W) MemManage Fault Address Register */ - __IO uint32_t BFAR; /*!< Offset: 0x038 (R/W) BusFault Address Register */ - __IO uint32_t AFSR; /*!< Offset: 0x03C (R/W) Auxiliary Fault Status Register */ - __I uint32_t PFR[2]; /*!< Offset: 0x040 (R/ ) Processor Feature Register */ - __I uint32_t DFR; /*!< Offset: 0x048 (R/ ) Debug Feature Register */ - __I uint32_t ADR; /*!< Offset: 0x04C (R/ ) Auxiliary Feature Register */ - __I uint32_t MMFR[4]; /*!< Offset: 0x050 (R/ ) Memory Model Feature Register */ - __I uint32_t ISAR[5]; /*!< Offset: 0x060 (R/ ) Instruction Set Attributes Register */ - uint32_t RESERVED0[5]; - __IO uint32_t CPACR; /*!< Offset: 0x088 (R/W) Coprocessor Access Control Register */ - } SCB_Type; +#endif // !VOXELAB_N32 -// ------------------------ -// Local defines -// ------------------------ - -#define SCS_BASE (0xE000E000UL) /*!< System Control Space Base Address */ -#define SCB_BASE (SCS_BASE + 0x0D00UL) /*!< System Control Block Base Address */ - -#define SCB ((SCB_Type *) SCB_BASE ) /*!< SCB configuration struct */ - -/* SCB Application Interrupt and Reset Control Register Definitions */ -#define SCB_AIRCR_VECTKEY_Pos 16 /*!< SCB AIRCR: VECTKEY Position */ -#define SCB_AIRCR_VECTKEY_Msk (0xFFFFUL << SCB_AIRCR_VECTKEY_Pos) /*!< SCB AIRCR: VECTKEY Mask */ - -#define SCB_AIRCR_PRIGROUP_Pos 8 /*!< SCB AIRCR: PRIGROUP Position */ -#define SCB_AIRCR_PRIGROUP_Msk (7UL << SCB_AIRCR_PRIGROUP_Pos) /*!< SCB AIRCR: PRIGROUP Mask */ - -// ------------------------ -// Public Variables -// ------------------------ - -#if (defined(SERIAL_USB) && !defined(USE_USB_COMPOSITE)) - USBSerial SerialUSB; -#endif - -uint16_t HAL_adc_result; - -// ------------------------ -// Private Variables -// ------------------------ -STM32ADC adc(ADC1); - -const uint8_t adc_pins[] = { - #if HAS_TEMP_ADC_0 - TEMP_0_PIN, - #endif - #if HAS_TEMP_ADC_PROBE - TEMP_PROBE_PIN, - #endif - #if HAS_HEATED_BED - TEMP_BED_PIN, - #endif - #if HAS_TEMP_CHAMBER - TEMP_CHAMBER_PIN, - #endif - #if HAS_TEMP_ADC_1 - TEMP_1_PIN, - #endif - #if HAS_TEMP_ADC_2 - TEMP_2_PIN, - #endif - #if HAS_TEMP_ADC_3 - TEMP_3_PIN, - #endif - #if HAS_TEMP_ADC_4 - TEMP_4_PIN, - #endif - #if HAS_TEMP_ADC_5 - TEMP_5_PIN, - #endif - #if HAS_TEMP_ADC_6 - TEMP_6_PIN, - #endif - #if HAS_TEMP_ADC_7 - TEMP_7_PIN, - #endif - #if ENABLED(FILAMENT_WIDTH_SENSOR) - FILWIDTH_PIN, - #endif - #if ENABLED(ADC_KEYPAD) - ADC_KEYPAD_PIN, - #endif - #if HAS_JOY_ADC_X - JOY_X_PIN, - #endif - #if HAS_JOY_ADC_Y - JOY_Y_PIN, - #endif - #if HAS_JOY_ADC_Z - JOY_Z_PIN, - #endif - #if ENABLED(POWER_MONITOR_CURRENT) - POWER_MONITOR_CURRENT_PIN, - #endif - #if ENABLED(POWER_MONITOR_VOLTAGE) - POWER_MONITOR_VOLTAGE_PIN, - #endif -}; - -enum TempPinIndex : char { - #if HAS_TEMP_ADC_0 - TEMP_0, - #endif - #if HAS_TEMP_ADC_PROBE - TEMP_PROBE, - #endif - #if HAS_HEATED_BED - TEMP_BED, - #endif - #if HAS_TEMP_CHAMBER - TEMP_CHAMBER, - #endif - #if HAS_TEMP_ADC_1 - TEMP_1, - #endif - #if HAS_TEMP_ADC_2 - TEMP_2, - #endif - #if HAS_TEMP_ADC_3 - TEMP_3, - #endif - #if HAS_TEMP_ADC_4 - TEMP_4, - #endif - #if HAS_TEMP_ADC_5 - TEMP_5, - #endif - #if HAS_TEMP_ADC_6 - TEMP_6, - #endif - #if HAS_TEMP_ADC_7 - TEMP_7, - #endif - #if ENABLED(FILAMENT_WIDTH_SENSOR) - FILWIDTH, - #endif - #if ENABLED(ADC_KEYPAD) - ADC_KEY, - #endif - #if HAS_JOY_ADC_X - JOY_X, - #endif - #if HAS_JOY_ADC_Y - JOY_Y, - #endif - #if HAS_JOY_ADC_Z - JOY_Z, - #endif - #if ENABLED(POWER_MONITOR_CURRENT) - POWERMON_CURRENT, - #endif - #if ENABLED(POWER_MONITOR_VOLTAGE) - POWERMON_VOLTS, - #endif - ADC_PIN_COUNT -}; - -uint16_t HAL_adc_results[ADC_PIN_COUNT]; - -// ------------------------ -// Private functions -// ------------------------ -static void NVIC_SetPriorityGrouping(uint32_t PriorityGroup) { - uint32_t reg_value; - uint32_t PriorityGroupTmp = (PriorityGroup & (uint32_t)0x07); /* only values 0..7 are used */ - - reg_value = SCB->AIRCR; /* read old register configuration */ - reg_value &= ~(SCB_AIRCR_VECTKEY_Msk | SCB_AIRCR_PRIGROUP_Msk); /* clear bits to change */ - reg_value = (reg_value | - ((uint32_t)0x5FA << SCB_AIRCR_VECTKEY_Pos) | - (PriorityGroupTmp << 8)); /* Insert write key and priorty group */ - SCB->AIRCR = reg_value; +void MarlinHAL::adc_start(const pin_t pin) { + #define __TCASE(N,I) case N: pin_index = I; break; + #define _TCASE(C,N,I) TERN_(C, __TCASE(N, I)) + 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_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 } // ------------------------ // Public functions // ------------------------ +void NVIC_SetPriorityGrouping(uint32_t PriorityGroup) { + uint32_t reg_value; + uint32_t PriorityGroupTmp = (PriorityGroup & (uint32_t)0x07); // only values 0..7 are used + + reg_value = SCB->AIRCR; // read old register configuration + reg_value &= ~(SCB_AIRCR_VECTKEY_Msk | SCB_AIRCR_PRIGROUP_Msk); // clear bits to change + reg_value = (reg_value | + ((uint32_t)0x5FA << SCB_AIRCR_VECTKEY_Pos) | + (PriorityGroupTmp << 8)); // Insert write key & priority group + SCB->AIRCR = reg_value; +} + +void flashFirmware(const int16_t) { hal.reboot(); } + // // Leave PA11/PA12 intact if USBSerial is not used // @@ -246,175 +237,47 @@ static void NVIC_SetPriorityGrouping(uint32_t PriorityGroup) { } } #endif -void HAL_init() { +TERN_(POSTMORTEM_DEBUGGING, extern void install_min_serial()); + +// ------------------------ +// MarlinHAL class +// ------------------------ + +void MarlinHAL::init() { NVIC_SetPriorityGrouping(0x3); #if PIN_EXISTS(LED) OUT_WRITE(LED_PIN, LOW); #endif - #ifdef USE_USB_COMPOSITE + #if HAS_SD_HOST_DRIVE MSC_SD_init(); + #elif ALL(SERIAL_USB, EMERGENCY_PARSER) + usb_cdcacm_set_hooks(USB_CDCACM_HOOK_RX, my_rx_callback); #endif #if PIN_EXISTS(USB_CONNECT) OUT_WRITE(USB_CONNECT_PIN, !USB_CONNECT_INVERTING); // USB clear connection - delay(1000); // Give OS time to notice - OUT_WRITE(USB_CONNECT_PIN, USB_CONNECT_INVERTING); + 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 } // HAL idle task -void HAL_idletask() { - #ifdef USE_USB_COMPOSITE - #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()) - /* copy from lpc1768 framework, should be fixed later for process HAS_SHARED_MEDIA*/ - #endif - // process USB mass storage device class loop - MarlinMSC.loop(); +void MarlinHAL::idletask() { + #if HAS_SHARED_MEDIA + /** + * When Marlin is using the SD card it should be locked to prevent it being + * accessed from a PC over USB. + * 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. + */ + /* Copied from LPC1768 framework. Should be fixed later to process HAS_SD_HOST_DRIVE */ + //if (!drive_locked()) // TODO + MarlinMSC.loop(); // Process USB mass storage device class loop #endif } -void HAL_clear_reset_source() { } - -/** - * TODO: Check this and change or remove. - */ -uint8_t HAL_get_reset_source() { return RST_POWER_ON; } - -void _delay_ms(const int delay_ms) { delay(delay_ms); } - -extern "C" { - extern unsigned int _ebss; // end of bss section -} - -/** - * TODO: Change this to correct it for libmaple - */ - -// return free memory between end of heap (or end bss) and whatever is current - -/* -#include -//extern caddr_t _sbrk(int incr); -#ifndef CONFIG_HEAP_END -extern char _lm_heap_end; -#define CONFIG_HEAP_END ((caddr_t)&_lm_heap_end) -#endif - -extern "C" { - static int freeMemory() { - char top = 't'; - return &top - reinterpret_cast(sbrk(0)); - } - int freeMemory() { - int free_memory; - int heap_end = (int)_sbrk(0); - free_memory = ((int)&free_memory) - ((int)heap_end); - return free_memory; - } -} -*/ - -// ------------------------ -// ADC -// ------------------------ -// Init the AD in continuous capture mode -void HAL_adc_init() { - // configure the ADC - adc.calibrate(); - #if F_CPU > 72000000 - adc.setSampleRate(ADC_SMPR_71_5); // 71.5 ADC cycles - #else - adc.setSampleRate(ADC_SMPR_41_5); // 41.5 ADC cycles - #endif - adc.setPins((uint8_t *)adc_pins, ADC_PIN_COUNT); - adc.setDMA(HAL_adc_results, (uint16_t)ADC_PIN_COUNT, (uint32_t)(DMA_MINC_MODE | DMA_CIRC_MODE), nullptr); - adc.setScanMode(); - adc.setContinuous(); - adc.startConversion(); -} - -void HAL_adc_start_conversion(const uint8_t adc_pin) { - //TEMP_PINS pin_index; - TempPinIndex pin_index; - switch (adc_pin) { - default: return; - #if HAS_TEMP_ADC_0 - case TEMP_0_PIN: pin_index = TEMP_0; break; - #endif - #if HAS_TEMP_ADC_PROBE - case TEMP_PROBE_PIN: pin_index = TEMP_PROBE; break; - #endif - #if HAS_HEATED_BED - case TEMP_BED_PIN: pin_index = TEMP_BED; break; - #endif - #if HAS_TEMP_CHAMBER - case TEMP_CHAMBER_PIN: pin_index = TEMP_CHAMBER; break; - #endif - #if HAS_TEMP_ADC_1 - case TEMP_1_PIN: pin_index = TEMP_1; break; - #endif - #if HAS_TEMP_ADC_2 - case TEMP_2_PIN: pin_index = TEMP_2; break; - #endif - #if HAS_TEMP_ADC_3 - case TEMP_3_PIN: pin_index = TEMP_3; break; - #endif - #if HAS_TEMP_ADC_4 - case TEMP_4_PIN: pin_index = TEMP_4; break; - #endif - #if HAS_TEMP_ADC_5 - case TEMP_5_PIN: pin_index = TEMP_5; break; - #endif - #if HAS_TEMP_ADC_6 - case TEMP_6_PIN: pin_index = TEMP_6; break; - #endif - #if HAS_TEMP_ADC_7 - case TEMP_7_PIN: pin_index = TEMP_7; break; - #endif - #if HAS_JOY_ADC_X - case JOY_X_PIN: pin_index = JOY_X; break; - #endif - #if HAS_JOY_ADC_Y - case JOY_Y_PIN: pin_index = JOY_Y; break; - #endif - #if HAS_JOY_ADC_Z - case JOY_Z_PIN: pin_index = JOY_Z; break; - #endif - #if ENABLED(FILAMENT_WIDTH_SENSOR) - case FILWIDTH_PIN: pin_index = FILWIDTH; break; - #endif - #if ENABLED(ADC_KEYPAD) - case ADC_KEYPAD_PIN: pin_index = ADC_KEY; break; - #endif - #if ENABLED(POWER_MONITOR_CURRENT) - case POWER_MONITOR_CURRENT_PIN: pin_index = POWERMON_CURRENT; break; - #endif - #if ENABLED(POWER_MONITOR_VOLTAGE) - case POWER_MONITOR_VOLTAGE_PIN: pin_index = POWERMON_VOLTS; break; - #endif - } - HAL_adc_result = (HAL_adc_results[(int)pin_index] >> 2) & 0x3FF; // shift to get 10 bits only. -} - -uint16_t HAL_adc_get_result() { return HAL_adc_result; } - -uint16_t analogRead(pin_t pin) { - const bool is_analog = _GET_MODE(pin) == GPIO_INPUT_ANALOG; - return is_analog ? analogRead(uint8_t(pin)) : 0; -} - -// Wrapper to maple unprotected analogWrite -void analogWrite(pin_t pin, int pwm_val8) { - if (PWM_PIN(pin)) - analogWrite(uint8_t(pin), pwm_val8); -} - -void flashFirmware(const int16_t) { nvic_sys_reset(); } +void MarlinHAL::reboot() { nvic_sys_reset(); } #endif // __STM32F1__ diff --git a/Marlin/src/HAL/STM32F1/HAL.h b/Marlin/src/HAL/STM32F1/HAL.h index c10dea0eaf..2c7321403f 100644 --- a/Marlin/src/HAL/STM32F1/HAL.h +++ b/Marlin/src/HAL/STM32F1/HAL.h @@ -1,10 +1,9 @@ /** * Marlin 3D Printer Firmware - * * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com - * Copyright (c) 2015-2016 Nico Tonnhofer wurstnase.reprap@gmail.com - * Copyright (c) 2017 Victor Perez + * + * 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 @@ -34,117 +33,54 @@ #include "../shared/HAL_SPI.h" #include "fastio.h" -#include "watchdog.h" - #include #include #include "../../inc/MarlinConfigPre.h" -#ifdef USE_USB_COMPOSITE - #include "msc_sd.h" +#if HAS_SD_HOST_DRIVE + #include "sd/msc_sd.h" #endif -#include "MarlinSerial.h" - // ------------------------ // Defines // ------------------------ +// +// Default graphical display delays +// +#define CPU_ST7920_DELAY_1 300 +#define CPU_ST7920_DELAY_2 40 +#define CPU_ST7920_DELAY_3 340 + #ifndef STM32_FLASH_SIZE - #if EITHER(MCU_STM32F103RE, MCU_STM32F103VE) + #if ANY(MCU_STM32F103RE, MCU_STM32F103VE, MCU_STM32F103ZE) #define STM32_FLASH_SIZE 512 #else #define STM32_FLASH_SIZE 256 #endif #endif -#ifdef SERIAL_USB - #ifndef USE_USB_COMPOSITE - #define UsbSerial Serial - #else - #define UsbSerial MarlinCompositeSerial - #endif -#endif +// +// Serial Ports +// -#define _MSERIAL(X) MSerial##X -#define MSERIAL(X) _MSERIAL(X) - -#if EITHER(STM32_HIGH_DENSITY, STM32_XL_DENSITY) - #define NUM_UARTS 5 -#else - #define NUM_UARTS 3 -#endif - -#if SERIAL_PORT == -1 - #define MYSERIAL0 UsbSerial -#elif WITHIN(SERIAL_PORT, 1, NUM_UARTS) - #define MYSERIAL0 MSERIAL(SERIAL_PORT) -#elif NUM_UARTS == 5 - #error "SERIAL_PORT must be -1 or from 1 to 5. Please update your configuration." -#else - #error "SERIAL_PORT must be -1 or from 1 to 3. Please update your configuration." -#endif - -#ifdef SERIAL_PORT_2 - #if SERIAL_PORT_2 == -1 - #define MYSERIAL1 UsbSerial - #elif WITHIN(SERIAL_PORT_2, 1, NUM_UARTS) - #define MYSERIAL1 MSERIAL(SERIAL_PORT_2) - #elif NUM_UARTS == 5 - #error "SERIAL_PORT_2 must be -1 or from 1 to 5. Please update your configuration." - #else - #error "SERIAL_PORT_2 must be -1 or from 1 to 3. Please update your configuration." - #endif -#endif - -#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) - #elif NUM_UARTS == 5 - #error "LCD_SERIAL_PORT must be -1 or from 1 to 5. Please update your configuration." - #else - #error "LCD_SERIAL_PORT must be -1 or from 1 to 3. Please update your configuration." - #endif -#endif - -// Set interrupt grouping for this MCU -void HAL_init(); -#define HAL_IDLETASK 1 -void HAL_idletask(); +#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 - #define digitalPinHasPWM(P) (PIN_MAP[P].timer_device != nullptr) + #define digitalPinHasPWM(P) !!PIN_MAP[P].timer_device #define NO_COMPILE_TIME_PWM #endif -#define CRITICAL_SECTION_START() uint32_t primask = __get_primask(); (void)__iCliRetVal() -#define CRITICAL_SECTION_END() if (!primask) (void)__iSeiRetVal() -#define ISRS_ENABLED() (!__get_primask()) -#define ENABLE_ISRS() ((void)__iSeiRetVal()) -#define DISABLE_ISRS() ((void)__iCliRetVal()) - -// On AVR this is in math.h? -#define square(x) ((x)*(x)) - -#ifndef strncpy_P - #define strncpy_P(dest, src, num) strncpy((dest), (src), (num)) -#endif - -// Fix bug in pgm_read_ptr -#undef pgm_read_ptr -#define pgm_read_ptr(addr) (*(addr)) - +// Reset Reason #define RST_POWER_ON 1 #define RST_EXTERNAL 2 #define RST_BROWN_OUT 4 @@ -160,87 +96,186 @@ void HAL_idletask(); typedef int8_t pin_t; // ------------------------ -// Public Variables +// Interrupts // ------------------------ -// Result of last ADC conversion -extern uint16_t HAL_adc_result; - -// ------------------------ -// Public functions -// ------------------------ - -// Disable interrupts +#define CRITICAL_SECTION_START() const bool irqon = !__get_primask(); (void)__iCliRetVal() +#define CRITICAL_SECTION_END() if (!irqon) (void)__iSeiRetVal() #define cli() noInterrupts() - -// Enable interrupts #define sei() interrupts() -// Memory related -#define __bss_end __bss_end__ - -// Clear reset reason -void HAL_clear_reset_source(); - -// Reset reason -uint8_t HAL_get_reset_source(); - -inline void HAL_reboot() {} // reboot the board or restart the bootloader - -void _delay_ms(const int delay); - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-function" - -/* -extern "C" { - int freeMemory(); -} -*/ - -extern "C" char* _sbrk(int incr); - -/* -static int freeMemory() { - volatile int top; - top = (int)((char*)&top - reinterpret_cast(_sbrk(0))); - return top; -} -*/ - -static int freeMemory() { - volatile char top; - return &top - reinterpret_cast(_sbrk(0)); -} - -#pragma GCC diagnostic pop - -// +// ------------------------ // ADC +// ------------------------ + +#ifdef ADC_RESOLUTION + #define HAL_ADC_RESOLUTION ADC_RESOLUTION +#else + #define HAL_ADC_RESOLUTION 12 +#endif + +#define HAL_ADC_VREF_MV 3300 + +uint16_t analogRead(const pin_t pin); // need hal.adc_enable() first +void analogWrite(const pin_t pin, int pwm_val8); // PWM only! mul by 257 in maple!? + +// +// Pin Mapping for M42, M43, M226 // - -#define HAL_ANALOG_SELECT(pin) pinMode(pin, INPUT_ANALOG); - -void HAL_adc_init(); - -#define HAL_ADC_VREF 3.3 -#define HAL_ADC_RESOLUTION 10 -#define HAL_START_ADC(pin) HAL_adc_start_conversion(pin) -#define HAL_READ_ADC() HAL_adc_result -#define HAL_ADC_READY() true - -void HAL_adc_start_conversion(const uint8_t adc_pin); -uint16_t HAL_adc_get_result(); - -uint16_t analogRead(pin_t pin); // need HAL_ANALOG_SELECT() first -void analogWrite(pin_t pin, int pwm_val8); // PWM only! mul by 257 in maple!? - #define GET_PIN_MAP_PIN(index) index #define GET_PIN_MAP_INDEX(pin) pin #define PARSED_PIN_INDEX(code, dval) parser.intval(code, dval) -#define JTAG_DISABLE() afio_cfg_debug_ports(AFIO_DEBUG_SW_ONLY) +#define JTAG_DISABLE() afio_cfg_debug_ports(AFIO_DEBUG_SW_ONLY) #define JTAGSWD_DISABLE() afio_cfg_debug_ports(AFIO_DEBUG_NONE) -#define PLATFORM_M997_SUPPORT +#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 +#ifndef PWM_FREQUENCY + #define PWM_FREQUENCY 1000 // Default PWM Frequency +#endif + +// ------------------------ +// Class Utilities +// ------------------------ + +// Memory related +#define __bss_end __bss_end__ + +extern "C" char* _sbrk(int incr); + +void NVIC_SetPriorityGrouping(uint32_t PriorityGroup); + +#pragma GCC diagnostic push +#if GCC_VERSION <= 50000 + #pragma GCC diagnostic ignored "-Wunused-function" +#endif + +static inline int freeMemory() { + volatile char top; + return &top - _sbrk(0); +} + +#pragma GCC diagnostic pop + +// ------------------------ +// MarlinHAL Class +// ------------------------ + +class MarlinHAL { +public: + + // Earliest possible init, before setup() + MarlinHAL() {} + + // 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(); // Restart the firmware from 0x0 + + // Interrupts + static bool isr_state() { return !__get_primask(); } + static void isr_on() { ((void)__iSeiRetVal()); } + static void isr_off() { ((void)__iCliRetVal()); } + + static void delay_ms(const int ms) { delay(ms); } + + // Tasks, called from marlin.idle() + static void idletask(); + + // Reset + static uint8_t get_reset_source() { return RST_POWER_ON; } + static void clear_reset_source() {} + + // Free SRAM + static int freeMemory() { return ::freeMemory(); } + + // + // ADC Methods + // + + static uint16_t adc_result; + + // 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) { pinMode(pin, INPUT_ANALOG); } + + // 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 bool adc_ready() { return true; } + + // The 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] + * The timer must be pre-configured with set_pwm_frequency() if the default frequency is not desired. + */ + static void set_pwm_duty(const pin_t pin, const uint16_t v, const uint16_t=255, const bool=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); + +}; + +// ------------------------ +// Types +// ------------------------ + +#define __I +#define __IO volatile + typedef struct { + __I uint32_t CPUID; /*!< Offset: 0x000 (R/ ) CPUID Base Register */ + __IO uint32_t ICSR; /*!< Offset: 0x004 (R/W) Interrupt Control and State Register */ + __IO uint32_t VTOR; /*!< Offset: 0x008 (R/W) Vector Table Offset Register */ + __IO uint32_t AIRCR; /*!< Offset: 0x00C (R/W) Application Interrupt and Reset Control Register */ + __IO uint32_t SCR; /*!< Offset: 0x010 (R/W) System Control Register */ + __IO uint32_t CCR; /*!< Offset: 0x014 (R/W) Configuration Control Register */ + __IO uint8_t SHP[12]; /*!< Offset: 0x018 (R/W) System Handlers Priority Registers (4-7, 8-11, 12-15) */ + __IO uint32_t SHCSR; /*!< Offset: 0x024 (R/W) System Handler Control and State Register */ + __IO uint32_t CFSR; /*!< Offset: 0x028 (R/W) Configurable Fault Status Register */ + __IO uint32_t HFSR; /*!< Offset: 0x02C (R/W) HardFault Status Register */ + __IO uint32_t DFSR; /*!< Offset: 0x030 (R/W) Debug Fault Status Register */ + __IO uint32_t MMFAR; /*!< Offset: 0x034 (R/W) MemManage Fault Address Register */ + __IO uint32_t BFAR; /*!< Offset: 0x038 (R/W) BusFault Address Register */ + __IO uint32_t AFSR; /*!< Offset: 0x03C (R/W) Auxiliary Fault Status Register */ + __I uint32_t PFR[2]; /*!< Offset: 0x040 (R/ ) Processor Feature Register */ + __I uint32_t DFR; /*!< Offset: 0x048 (R/ ) Debug Feature Register */ + __I uint32_t ADR; /*!< Offset: 0x04C (R/ ) Auxiliary Feature Register */ + __I uint32_t MMFR[4]; /*!< Offset: 0x050 (R/ ) Memory Model Feature Register */ + __I uint32_t ISAR[5]; /*!< Offset: 0x060 (R/ ) Instruction Set Attributes Register */ + uint32_t RESERVED0[5]; + __IO uint32_t CPACR; /*!< Offset: 0x088 (R/W) Coprocessor Access Control Register */ + } SCB_Type; + +// ------------------------ +// System Control Space +// ------------------------ + +#define SCS_BASE (0xE000E000UL) /*!< System Control Space Base Address */ +#define SCB_BASE (SCS_BASE + 0x0D00UL) /*!< System Control Block Base Address */ + +#define SCB ((SCB_Type *) SCB_BASE ) /*!< SCB configuration struct */ + +/* SCB Application Interrupt and Reset Control Register Definitions */ +#define SCB_AIRCR_VECTKEY_Pos 16 /*!< SCB AIRCR: VECTKEY Position */ +#define SCB_AIRCR_VECTKEY_Msk (0xFFFFUL << SCB_AIRCR_VECTKEY_Pos) /*!< SCB AIRCR: VECTKEY Mask */ + +#define SCB_AIRCR_PRIGROUP_Pos 8 /*!< SCB AIRCR: PRIGROUP Position */ +#define SCB_AIRCR_PRIGROUP_Msk (7UL << SCB_AIRCR_PRIGROUP_Pos) /*!< SCB AIRCR: PRIGROUP Mask */ diff --git a/Marlin/src/HAL/STM32F1/HAL_N32.cpp b/Marlin/src/HAL/STM32F1/HAL_N32.cpp new file mode 100644 index 0000000000..e45cbbbfff --- /dev/null +++ b/Marlin/src/HAL/STM32F1/HAL_N32.cpp @@ -0,0 +1,641 @@ +/** + * 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 + * (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 . + * + */ + +/** + * HAL for stm32duino.com based on Libmaple and compatible (STM32F1) + * Specifically for VOXELAB_N32. TODO: Rework for generic N32 MCU. + */ + +#if defined(__STM32F1__) && defined(VOXELAB_N32) + +#include "../../inc/MarlinConfig.h" +#include "HAL_N32.h" +#include "HAL.h" + +#include "adc.h" + +void ADC_Init(ADC_Module* NS_ADCx, ADC_InitType* ADC_InitStruct) { + uint32_t tmpreg1 = 0; + uint8_t tmpreg2 = 0; + + /*---------------------------- ADCx CTRL1 Configuration -----------------*/ + /* Get the ADCx CTRL1 value */ + tmpreg1 = NS_ADCx->CTRL1; + /* Clear DUALMOD and SCAN bits */ + tmpreg1 &= CTRL1_CLR_MASK; + /* Configure ADCx: Dual mode and scan conversion mode */ + /* Set DUALMOD bits according to WorkMode value */ + /* Set SCAN bit according to MultiChEn value */ + tmpreg1 |= (uint32_t)(ADC_InitStruct->WorkMode | ((uint32_t)ADC_InitStruct->MultiChEn << 8)); + /* Write to ADCx CTRL1 */ + NS_ADCx->CTRL1 = tmpreg1; + + /*---------------------------- ADCx CTRL2 Configuration -----------------*/ + /* Get the ADCx CTRL2 value */ + tmpreg1 = NS_ADCx->CTRL2; + /* Clear CONT, ALIGN and EXTSEL bits */ + tmpreg1 &= CTRL2_CLR_MASK; + /* Configure ADCx: external trigger event and continuous conversion mode */ + /* Set ALIGN bit according to DatAlign value */ + /* Set EXTSEL bits according to ExtTrigSelect value */ + /* Set CONT bit according to ContinueConvEn value */ + tmpreg1 |= (uint32_t)(ADC_InitStruct->DatAlign | ADC_InitStruct->ExtTrigSelect + | ((uint32_t)ADC_InitStruct->ContinueConvEn << 1)); + /* Write to ADCx CTRL2 */ + NS_ADCx->CTRL2 = tmpreg1; + + /*---------------------------- ADCx RSEQ1 Configuration -----------------*/ + /* Get the ADCx RSEQ1 value */ + tmpreg1 = NS_ADCx->RSEQ1; + /* Clear L bits */ + tmpreg1 &= RSEQ1_CLR_MASK; + /* Configure ADCx: regular channel sequence length */ + /* Set L bits according to ChsNumber value */ + tmpreg2 |= (uint8_t)(ADC_InitStruct->ChsNumber - (uint8_t)1); + tmpreg1 |= (uint32_t)tmpreg2 << 20; + /* Write to ADCx RSEQ1 */ + NS_ADCx->RSEQ1 = tmpreg1; +} + +/**================================================================ + * ADC reset + ================================================================*/ +void ADC_DeInit(ADC_Module* NS_ADCx) { + uint32_t reg_temp; + + if (NS_ADCx == NS_ADC1) { + /* Enable ADC1 reset state */ + reg_temp = ADC_RCC_AHBPRST; + reg_temp |= RCC_AHB_PERIPH_ADC1; + ADC_RCC_AHBPRST = reg_temp; // ADC module reunion position + ADC_RCC_AHBPRST = 0x00000000; // ADC module reset and clear + } + else if (NS_ADCx == NS_ADC2) { + /* Enable ADC2 reset state */ + reg_temp = ADC_RCC_AHBPRST; + reg_temp |= RCC_AHB_PERIPH_ADC2; + ADC_RCC_AHBPRST = reg_temp; // ADC module reunion position + ADC_RCC_AHBPRST = 0x00000000; // ADC module reset and clear + } + else if (NS_ADCx == NS_ADC3) { + /* Enable ADC2 reset state */ + reg_temp = ADC_RCC_AHBPRST; + reg_temp |= RCC_AHB_PERIPH_ADC3; + ADC_RCC_AHBPRST = reg_temp; // ADC module reunion position + ADC_RCC_AHBPRST = 0x00000000; // ADC module reset and clear + } + else if (NS_ADCx == NS_ADC4) { + /* Enable ADC3 reset state */ + reg_temp = ADC_RCC_AHBPRST; + reg_temp |= RCC_AHB_PERIPH_ADC4; + ADC_RCC_AHBPRST = reg_temp; // ADC module reunion position + ADC_RCC_AHBPRST = 0x00000000; // ADC module reset and clear + } +} + +/**================================================================ + * ADC module enable + ================================================================*/ +void ADC_Enable(ADC_Module* NS_ADCx, uint32_t Cmd) { + if (Cmd) + /* Set the AD_ON bit to wake up the ADC from power down mode */ + NS_ADCx->CTRL2 |= CTRL2_AD_ON_SET; + else + /* Disable the selected ADC peripheral */ + NS_ADCx->CTRL2 &= CTRL2_AD_ON_RESET; +} + +/**================================================================ + * Get the ADC status logo bit + ================================================================*/ +uint32_t ADC_GetFlagStatusNew(ADC_Module* NS_ADCx, uint8_t ADC_FLAG_NEW) { + uint32_t bitstatus = 0; + + /* Check the status of the specified ADC flag */ + if ((NS_ADCx->CTRL3 & ADC_FLAG_NEW) != (uint8_t)0) + /* ADC_FLAG_NEW is set */ + bitstatus = 1; + else + /* ADC_FLAG_NEW is reset */ + bitstatus = 0; + /* Return the ADC_FLAG_NEW status */ + return bitstatus; +} + +/**================================================================ + * Open ADC calibration + ================================================================*/ +void ADC_StartCalibration(ADC_Module* NS_ADCx) { + /* Enable the selected ADC calibration process */ + if (NS_ADCx->CALFACT == 0) + NS_ADCx->CTRL2 |= CTRL2_CAL_SET; +} + +/**================================================================ + * Enable ADC DMA + ================================================================*/ +void ADC_EnableDMA(ADC_Module* NS_ADCx, uint32_t Cmd) { + if (Cmd != 0) + /* Enable the selected ADC DMA request */ + NS_ADCx->CTRL2 |= CTRL2_DMA_SET; + else + /* Disable the selected ADC DMA request */ + NS_ADCx->CTRL2 &= CTRL2_DMA_RESET; +} + +/**================================================================ + * Configure ADC interrupt enable enable + ================================================================*/ +void ADC_ConfigInt(ADC_Module* NS_ADCx, uint16_t ADC_IT, uint32_t Cmd) { + uint8_t itmask = 0; + + /* Get the ADC IT index */ + itmask = (uint8_t)ADC_IT; + if (Cmd != 0) + /* Enable the selected ADC interrupts */ + NS_ADCx->CTRL1 |= itmask; + else + /* Disable the selected ADC interrupts */ + NS_ADCx->CTRL1 &= (~(uint32_t)itmask); +} + +/**================================================================ + * Get ADC calibration status + ================================================================*/ +uint32_t ADC_GetCalibrationStatus(ADC_Module* NS_ADCx) { + uint32_t bitstatus = 0; + + /* Check the status of CAL bit */ + if ((NS_ADCx->CTRL2 & CTRL2_CAL_SET) != (uint32_t)0) + /* CAL bit is set: calibration on going */ + bitstatus = 1; + else + /* CAL bit is reset: end of calibration */ + bitstatus = 0; + + if (NS_ADCx->CALFACT != 0) + bitstatus = 0; + /* Return the CAL bit status */ + return bitstatus; +} + +/**================================================================ + * Configure the ADC channel + ================================================================*/ +void ADC_ConfigRegularChannel(ADC_Module* NS_ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime) { + uint32_t tmpreg1 = 0, tmpreg2 = 0; + + if (ADC_Channel == ADC_CH_18) { + tmpreg1 = NS_ADCx->SAMPT3; + tmpreg1 &= (~0x00000007); + tmpreg1 |= ADC_SampleTime; + NS_ADCx->SAMPT3 = tmpreg1; + } + else if (ADC_Channel > ADC_CH_9) { /* if ADC_CH_10 ... ADC_CH_17 is selected */ + /* Get the old register value */ + tmpreg1 = NS_ADCx->SAMPT1; + /* Calculate the mask to clear */ + tmpreg2 = SAMPT1_SMP_SET << (3 * (ADC_Channel - 10)); + /* Clear the old channel sample time */ + tmpreg1 &= ~tmpreg2; + /* Calculate the mask to set */ + tmpreg2 = (uint32_t)ADC_SampleTime << (3 * (ADC_Channel - 10)); + /* Set the new channel sample time */ + tmpreg1 |= tmpreg2; + /* Store the new register value */ + NS_ADCx->SAMPT1 = tmpreg1; + } + else { /* ADC_Channel include in ADC_Channel_[0..9] */ + /* Get the old register value */ + tmpreg1 = NS_ADCx->SAMPT2; + /* Calculate the mask to clear */ + tmpreg2 = SAMPT2_SMP_SET << (3 * ADC_Channel); + /* Clear the old channel sample time */ + tmpreg1 &= ~tmpreg2; + /* Calculate the mask to set */ + tmpreg2 = (uint32_t)ADC_SampleTime << (3 * ADC_Channel); + /* Set the new channel sample time */ + tmpreg1 |= tmpreg2; + /* Store the new register value */ + NS_ADCx->SAMPT2 = tmpreg1; + } + /* For Rank 1 to 6 */ + if (Rank < 7) { + /* Get the old register value */ + tmpreg1 = NS_ADCx->RSEQ3; + /* Calculate the mask to clear */ + tmpreg2 = SQR3_SEQ_SET << (5 * (Rank - 1)); + /* Clear the old SQx bits for the selected rank */ + tmpreg1 &= ~tmpreg2; + /* Calculate the mask to set */ + tmpreg2 = (uint32_t)ADC_Channel << (5 * (Rank - 1)); + /* Set the SQx bits for the selected rank */ + tmpreg1 |= tmpreg2; + /* Store the new register value */ + NS_ADCx->RSEQ3 = tmpreg1; + } + /* For Rank 7 to 12 */ + else if (Rank < 13) { + /* Get the old register value */ + tmpreg1 = NS_ADCx->RSEQ2; + /* Calculate the mask to clear */ + tmpreg2 = SQR2_SEQ_SET << (5 * (Rank - 7)); + /* Clear the old SQx bits for the selected rank */ + tmpreg1 &= ~tmpreg2; + /* Calculate the mask to set */ + tmpreg2 = (uint32_t)ADC_Channel << (5 * (Rank - 7)); + /* Set the SQx bits for the selected rank */ + tmpreg1 |= tmpreg2; + /* Store the new register value */ + NS_ADCx->RSEQ2 = tmpreg1; + } + /* For Rank 13 to 16 */ + else { + /* Get the old register value */ + tmpreg1 = NS_ADCx->RSEQ1; + /* Calculate the mask to clear */ + tmpreg2 = SQR1_SEQ_SET << (5 * (Rank - 13)); + /* Clear the old SQx bits for the selected rank */ + tmpreg1 &= ~tmpreg2; + /* Calculate the mask to set */ + tmpreg2 = (uint32_t)ADC_Channel << (5 * (Rank - 13)); + /* Set the SQx bits for the selected rank */ + tmpreg1 |= tmpreg2; + /* Store the new register value */ + NS_ADCx->RSEQ1 = tmpreg1; + } +} + +/**================================================================ + * Start ADC conversion + ================================================================*/ +void ADC_EnableSoftwareStartConv(ADC_Module* NS_ADCx, uint32_t Cmd) { + if (Cmd != 0) + /* Enable the selected ADC conversion on external event and start the selected + ADC conversion */ + NS_ADCx->CTRL2 |= CTRL2_EXT_TRIG_SWSTART_SET; + else + /* Disable the selected ADC conversion on external event and stop the selected + ADC conversion */ + NS_ADCx->CTRL2 &= CTRL2_EXT_TRIG_SWSTART_RESET; +} + +/**================================================================ + * Get the ADC status logo bit + ================================================================*/ +uint32_t ADC_GetFlagStatus(ADC_Module* NS_ADCx, uint8_t ADC_FLAG) { + uint32_t bitstatus = 0; + + /* Check the status of the specified ADC flag */ + if ((NS_ADCx->STS & ADC_FLAG) != (uint8_t)0) + /* ADC_FLAG is set */ + bitstatus = 1; + else + /* ADC_FLAG is reset */ + bitstatus = 0; + /* Return the ADC_FLAG status */ + return bitstatus; +} + +/**================================================================ + * Clear status logo bit + ================================================================*/ +void ADC_ClearFlag(ADC_Module* NS_ADCx, uint8_t ADC_FLAG) { + /* Clear the selected ADC flags */ + NS_ADCx->STS &= ~(uint32_t)ADC_FLAG; +} + +/**================================================================ + * Get ADC sampling value + ================================================================*/ +uint16_t ADC_GetDat(ADC_Module* NS_ADCx) { + /* Return the selected ADC conversion value */ + return (uint16_t)NS_ADCx->DAT; +} + +/**================================================================ + * Initialize ADC clock + ================================================================*/ + +void enable_adc_clk(uint8_t cmd) { + uint32_t reg_temp; + if (cmd) { + /** Make PWR clock */ + reg_temp = ADC_RCC_APB1PCLKEN; + reg_temp |= RCC_APB1Periph_PWR; + ADC_RCC_APB1PCLKEN = reg_temp; + + /** Enable expansion mode */ + reg_temp = NS_PWR_CR3; + reg_temp |= 0x00000001; + NS_PWR_CR3 = reg_temp; + + /** Make ADC clock */ + reg_temp = ADC_RCC_AHBPCLKEN; + reg_temp |= ( RCC_AHB_PERIPH_ADC1 | + RCC_AHB_PERIPH_ADC2 | + RCC_AHB_PERIPH_ADC3 | + RCC_AHB_PERIPH_ADC4 ); + ADC_RCC_AHBPCLKEN = reg_temp; + + /** Reset */ + reg_temp = ADC_RCC_AHBPRST; + reg_temp |= ( RCC_AHB_PERIPH_ADC1 | + RCC_AHB_PERIPH_ADC2 | + RCC_AHB_PERIPH_ADC3 | + RCC_AHB_PERIPH_ADC4 ); + ADC_RCC_AHBPRST = reg_temp; // ADC module reunion position + ADC_RCC_AHBPRST &= ~reg_temp; // ADC module reset and clear + + /** Set ADC 1M clock */ + reg_temp = ADC_RCC_CFG2; + reg_temp &= CFG2_ADC1MSEL_RESET_MASK; // HSI as an ADC 1M clock + reg_temp &= CFG2_ADC1MPRES_RESET_MASK; + reg_temp |= 7 << 11; // Adc1m 8m / 8 = 1m + + /** Set the ADC PLL frequency split coefficient */ + reg_temp &= CFG2_ADCPLLPRES_RESET_MASK; + reg_temp |= RCC_ADCPLLCLK_DIV4; // ADC PLL frequency split coefficient + + /** Set the ADC HCLK frequency frequency coefficient */ + reg_temp &= CFG2_ADCHPRES_RESET_MASK; + reg_temp |= RCC_ADCHCLK_DIV4; // ADC HCLK frequency split coefficient + ADC_RCC_CFG2 = reg_temp; // Write to register + } + else { + /** Turn off the ADC clock */ + reg_temp = ADC_RCC_AHBPCLKEN; + reg_temp &= ~( RCC_AHB_PERIPH_ADC1 | + RCC_AHB_PERIPH_ADC2 | + RCC_AHB_PERIPH_ADC3 | + RCC_AHB_PERIPH_ADC4 ); + ADC_RCC_AHBPCLKEN = reg_temp; + } + +} + +/**================================================================ + * Initialize ADC peripheral parameters + ================================================================*/ +void ADC_Initial(ADC_Module* NS_ADCx) { + ADC_InitType ADC_InitStructure; + + /* ADC configuration ------------------------------------------------------*/ + ADC_InitStructure.WorkMode = ADC_WORKMODE_INDEPENDENT; // Independent mode + ADC_InitStructure.MultiChEn = 1; // Multi-channel enable + ADC_InitStructure.ContinueConvEn = 1; // Continuous enable + ADC_InitStructure.ExtTrigSelect = ADC_EXT_TRIGCONV_NONE; // Non-trigger + ADC_InitStructure.DatAlign = ADC_DAT_ALIGN_R; // Right alignment + ADC_InitStructure.ChsNumber = 2; // Scan channel number + ADC_Init(NS_ADCx, &ADC_InitStructure); + + /* ADC regular channel14 configuration */ + ADC_ConfigRegularChannel(NS_ADCx, ADC2_Channel_05_PC4, 2, ADC_SAMP_TIME_55CYCLES5); + ADC_ConfigRegularChannel(NS_ADCx, ADC2_Channel_12_PC5, 1, ADC_SAMP_TIME_55CYCLES5); + + /** 使能ADC DMA */ + ADC_EnableDMA(NS_ADCx, 1); + + /* Enable ADC */ + ADC_Enable(NS_ADCx, 1); + while(ADC_GetFlagStatusNew(NS_ADCx, ADC_FLAG_RDY) == 0); + + /* Start ADC calibration */ + ADC_StartCalibration(NS_ADCx); + while (ADC_GetCalibrationStatus(NS_ADCx)); + + /* Start ADC Software Conversion */ + ADC_EnableSoftwareStartConv(NS_ADCx, 1); +} + +/**================================================================ + * Single independent sampling + ================================================================*/ +uint16_t ADC_GetData(ADC_Module* NS_ADCx, uint8_t ADC_Channel) { + uint16_t dat; + + /** Set channel parameters */ + ADC_ConfigRegularChannel(NS_ADCx, ADC_Channel, 1, ADC_SAMP_TIME_239CYCLES5); + + /* Start ADC Software Conversion */ + ADC_EnableSoftwareStartConv(NS_ADCx, 1); + while(ADC_GetFlagStatus(NS_ADCx, ADC_FLAG_ENDC) == 0); + + ADC_ClearFlag(NS_ADCx, ADC_FLAG_ENDC); + ADC_ClearFlag(NS_ADCx, ADC_FLAG_STR); + dat = ADC_GetDat(NS_ADCx); + return dat; +} + +void DMA_DeInit(DMA_ChannelType* DMAyChx) { + + /* Disable the selected DMAy Channelx */ + DMAyChx->CHCFG &= (uint16_t)(~DMA_CHCFG1_CHEN); + + /* Reset DMAy Channelx control register */ + DMAyChx->CHCFG = 0; + + /* Reset DMAy Channelx remaining bytes register */ + DMAyChx->TXNUM = 0; + + /* Reset DMAy Channelx peripheral address register */ + DMAyChx->PADDR = 0; + + /* Reset DMAy Channelx memory address register */ + DMAyChx->MADDR = 0; + + if (DMAyChx == DMA1_CH1) { + /* Reset interrupt pending bits for DMA1 Channel1 */ + DMA1->INTCLR |= DMA1_CH1_INT_MASK; + } + else if (DMAyChx == DMA1_CH2) { + /* Reset interrupt pending bits for DMA1 Channel2 */ + DMA1->INTCLR |= DMA1_CH2_INT_MASK; + } + else if (DMAyChx == DMA1_CH3) { + /* Reset interrupt pending bits for DMA1 Channel3 */ + DMA1->INTCLR |= DMA1_CH3_INT_MASK; + } + else if (DMAyChx == DMA1_CH4) { + /* Reset interrupt pending bits for DMA1 Channel4 */ + DMA1->INTCLR |= DMA1_CH4_INT_MASK; + } + else if (DMAyChx == DMA1_CH5) { + /* Reset interrupt pending bits for DMA1 Channel5 */ + DMA1->INTCLR |= DMA1_CH5_INT_MASK; + } + else if (DMAyChx == DMA1_CH6) { + /* Reset interrupt pending bits for DMA1 Channel6 */ + DMA1->INTCLR |= DMA1_CH6_INT_MASK; + } + else if (DMAyChx == DMA1_CH7) { + /* Reset interrupt pending bits for DMA1 Channel7 */ + DMA1->INTCLR |= DMA1_CH7_INT_MASK; + } + else if (DMAyChx == DMA1_CH8) { + /* Reset interrupt pending bits for DMA1 Channel8 */ + DMA1->INTCLR |= DMA1_CH8_INT_MASK; + } + else if (DMAyChx == DMA2_CH1) { + /* Reset interrupt pending bits for DMA2 Channel1 */ + DMA2->INTCLR |= DMA2_CH1_INT_MASK; + } + else if (DMAyChx == DMA2_CH2) { + /* Reset interrupt pending bits for DMA2 Channel2 */ + DMA2->INTCLR |= DMA2_CH2_INT_MASK; + } + else if (DMAyChx == DMA2_CH3) { + /* Reset interrupt pending bits for DMA2 Channel3 */ + DMA2->INTCLR |= DMA2_CH3_INT_MASK; + } + else if (DMAyChx == DMA2_CH4) { + /* Reset interrupt pending bits for DMA2 Channel4 */ + DMA2->INTCLR |= DMA2_CH4_INT_MASK; + } + else if (DMAyChx == DMA2_CH5) { + /* Reset interrupt pending bits for DMA2 Channel5 */ + DMA2->INTCLR |= DMA2_CH5_INT_MASK; + } + else if (DMAyChx == DMA2_CH6) { + /* Reset interrupt pending bits for DMA2 Channel6 */ + DMA2->INTCLR |= DMA2_CH6_INT_MASK; + } + else if (DMAyChx == DMA2_CH7) { + /* Reset interrupt pending bits for DMA2 Channel7 */ + DMA2->INTCLR |= DMA2_CH7_INT_MASK; + } + else if (DMAyChx == DMA2_CH8) + /* Reset interrupt pending bits for DMA2 Channel8 */ + DMA2->INTCLR |= DMA2_CH8_INT_MASK; +} + +void DMA_Init(DMA_ChannelType* DMAyChx, DMA_InitType* DMA_InitParam) { + uint32_t tmpregister = 0; + + /*--------------------------- DMAy Channelx CHCFG Configuration --------------*/ + /* Get the DMAyChx CHCFG value */ + tmpregister = DMAyChx->CHCFG; + /* Clear MEM2MEM, PL, MSIZE, PSIZE, MINC, PINC, CIRC and DIR bits */ + tmpregister &= CCR_CLEAR_Mask; + /* Configure DMAy Channelx: data transfer, data size, priority level and mode */ + /* Set DIR bit according to Direction value */ + /* Set CIRC bit according to CircularMode value */ + /* Set PINC bit according to PeriphInc value */ + /* Set MINC bit according to DMA_MemoryInc value */ + /* Set PSIZE bits according to PeriphDataSize value */ + /* Set MSIZE bits according to MemDataSize value */ + /* Set PL bits according to Priority value */ + /* Set the MEM2MEM bit according to Mem2Mem value */ + tmpregister |= DMA_InitParam->Direction | DMA_InitParam->CircularMode | DMA_InitParam->PeriphInc + | DMA_InitParam->DMA_MemoryInc | DMA_InitParam->PeriphDataSize | DMA_InitParam->MemDataSize + | DMA_InitParam->Priority | DMA_InitParam->Mem2Mem; + + /* Write to DMAy Channelx CHCFG */ + DMAyChx->CHCFG = tmpregister; + + /*--------------------------- DMAy Channelx TXNUM Configuration --------------*/ + /* Write to DMAy Channelx TXNUM */ + DMAyChx->TXNUM = DMA_InitParam->BufSize; + + /*--------------------------- DMAy Channelx PADDR Configuration --------------*/ + /* Write to DMAy Channelx PADDR */ + DMAyChx->PADDR = DMA_InitParam->PeriphAddr; + + /*--------------------------- DMAy Channelx MADDR Configuration --------------*/ + /* Write to DMAy Channelx MADDR */ + DMAyChx->MADDR = DMA_InitParam->MemAddr; +} + +void DMA_EnableChannel(DMA_ChannelType* DMAyChx, uint32_t Cmd) { + if (Cmd != 0) { + /* Enable the selected DMAy Channelx */ + DMAyChx->CHCFG |= DMA_CHCFG1_CHEN; + } + else { + /* Disable the selected DMAy Channelx */ + DMAyChx->CHCFG &= (uint16_t)(~DMA_CHCFG1_CHEN); + } +} + +/**================================================================ + * Initialize the DMA of ADC + ================================================================*/ +void ADC_DMA_init() { + DMA_InitType DMA_InitStructure; + uint32_t reg_temp; + + /** Make DMA clock */ + reg_temp = ADC_RCC_AHBPCLKEN; + reg_temp |= ( RCC_AHB_PERIPH_DMA1 | + RCC_AHB_PERIPH_DMA2 ); + ADC_RCC_AHBPCLKEN = reg_temp; + + /* DMA channel configuration*/ + DMA_DeInit(USE_DMA_CH); + DMA_InitStructure.PeriphAddr = (uint32_t)&USE_ADC->DAT; + DMA_InitStructure.MemAddr = (uint32_t)adc_results; + DMA_InitStructure.Direction = DMA_DIR_PERIPH_SRC; // Peripheral-> memory + DMA_InitStructure.BufSize = 2; + DMA_InitStructure.PeriphInc = DMA_PERIPH_INC_DISABLE; + DMA_InitStructure.DMA_MemoryInc = DMA_MEM_INC_ENABLE; // Memory ++ + DMA_InitStructure.PeriphDataSize = DMA_PERIPH_DATA_SIZE_HALFWORD; + DMA_InitStructure.MemDataSize = DMA_MemoryDataSize_HalfWord; + DMA_InitStructure.CircularMode = DMA_MODE_CIRCULAR; + DMA_InitStructure.Priority = DMA_PRIORITY_HIGH; + DMA_InitStructure.Mem2Mem = DMA_M2M_DISABLE; + DMA_Init(USE_DMA_CH, &DMA_InitStructure); + + /* Enable DMA channel1 */ + DMA_EnableChannel(USE_DMA_CH, 1); +} + +/**============================================================================= + * n32g452 - end + ==============================================================================*/ + +#define NS_PINRT(V...) do{ SERIAL_ECHO_START(); SERIAL_ECHOLNPGM(V); }while(0) + +// Init the AD in continuous capture mode +void MarlinHAL::adc_init() { + uint32_t reg_temp; + + //SERIAL_ECHO_MSG("\r\n n32g45x HAL_adc_init\r\n"); + + // GPIO settings + reg_temp = ADC_RCC_APB2PCLKEN; + reg_temp |= 0x0F; // Make PORT mouth clock + ADC_RCC_APB2PCLKEN = reg_temp; + + //reg_temp = NS_GPIOC_PL_CFG; + //reg_temp &= 0XFF00FFFF; + //NS_GPIOC_PL_CFG = reg_temp; // PC4/5 analog input + + enable_adc_clk(1); // Make ADC clock + ADC_DMA_init(); // DMA initialization + ADC_Initial(NS_ADC2); // ADC initialization + + delay(2); + //NS_PINRT("get adc1 = ", adc_results[0], "\r\n"); + //NS_PINRT("get adc2 = ", adc_results[1], "\r\n"); +} + +#endif // __STM32F1__ && VOXELAB_N32 diff --git a/Marlin/src/HAL/STM32F1/HAL_N32.h b/Marlin/src/HAL/STM32F1/HAL_N32.h new file mode 100644 index 0000000000..719368f6f1 --- /dev/null +++ b/Marlin/src/HAL/STM32F1/HAL_N32.h @@ -0,0 +1,891 @@ +/** + * 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 + * (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 . + * + */ + +/** + * HAL for stm32duino.com based on Libmaple and compatible (STM32F1) + * Specifically for VOXELAB_N32 (N32G452). TODO: Rework for generic N32 MCU. + */ + +#include + +typedef struct { + uint32_t WorkMode; + uint32_t MultiChEn; + uint32_t ContinueConvEn; + uint32_t ExtTrigSelect; + uint32_t DatAlign; + uint8_t ChsNumber; +} ADC_InitType; + +typedef struct { + __IO uint32_t STS; + __IO uint32_t CTRL1; + __IO uint32_t CTRL2; + __IO uint32_t SAMPT1; + __IO uint32_t SAMPT2; + __IO uint32_t JOFFSET1; + __IO uint32_t JOFFSET2; + __IO uint32_t JOFFSET3; + __IO uint32_t JOFFSET4; + __IO uint32_t WDGHIGH; + __IO uint32_t WDGLOW; + __IO uint32_t RSEQ1; + __IO uint32_t RSEQ2; + __IO uint32_t RSEQ3; + __IO uint32_t JSEQ; + __IO uint32_t JDAT1; + __IO uint32_t JDAT2; + __IO uint32_t JDAT3; + __IO uint32_t JDAT4; + __IO uint32_t DAT; + __IO uint32_t DIFSEL; + __IO uint32_t CALFACT; + __IO uint32_t CTRL3; + __IO uint32_t SAMPT3; +} ADC_Module; + +#define NS_ADC1_BASE ((uint32_t)0x40020800) +#define NS_ADC2_BASE ((uint32_t)0x40020c00) +#define NS_ADC3_BASE ((uint32_t)0x40021800) +#define NS_ADC4_BASE ((uint32_t)0x40021c00) + +#define NS_ADC1 ((ADC_Module*)NS_ADC1_BASE) +#define NS_ADC2 ((ADC_Module*)NS_ADC2_BASE) +#define NS_ADC3 ((ADC_Module*)NS_ADC3_BASE) +#define NS_ADC4 ((ADC_Module*)NS_ADC4_BASE) + +#define ADC1_Channel_01_PA0 ((uint8_t)0x01) +#define ADC1_Channel_02_PA1 ((uint8_t)0x02) +#define ADC1_Channel_03_PA6 ((uint8_t)0x03) +#define ADC1_Channel_04_PA3 ((uint8_t)0x04) +#define ADC1_Channel_05_PF4 ((uint8_t)0x05) +#define ADC1_Channel_06_PC0 ((uint8_t)0x06) +#define ADC1_Channel_07_PC1 ((uint8_t)0x07) +#define ADC1_Channel_08_PC2 ((uint8_t)0x08) +#define ADC1_Channel_09_PC3 ((uint8_t)0x09) +#define ADC1_Channel_10_PF2 ((uint8_t)0x0A) +#define ADC1_Channel_11_PA2 ((uint8_t)0x0B) + +#define ADC2_Channel_01_PA4 ((uint8_t)0x01) +#define ADC2_Channel_02_PA5 ((uint8_t)0x02) +#define ADC2_Channel_03_PB1 ((uint8_t)0x03) +#define ADC2_Channel_04_PA7 ((uint8_t)0x04) +#define ADC2_Channel_05_PC4 ((uint8_t)0x05) +#define ADC2_Channel_06_PC0 ((uint8_t)0x06) +#define ADC2_Channel_07_PC1 ((uint8_t)0x07) +#define ADC2_Channel_08_PC2 ((uint8_t)0x08) +#define ADC2_Channel_09_PC3 ((uint8_t)0x09) +#define ADC2_Channel_10_PF2 ((uint8_t)0x0A) +#define ADC2_Channel_11_PA2 ((uint8_t)0x0B) +#define ADC2_Channel_12_PC5 ((uint8_t)0x0C) +#define ADC2_Channel_13_PB2 ((uint8_t)0x0D) + +#define ADC3_Channel_01_PB11 ((uint8_t)0x01) +#define ADC3_Channel_02_PE9 ((uint8_t)0x02) +#define ADC3_Channel_03_PE13 ((uint8_t)0x03) +#define ADC3_Channel_04_PE12 ((uint8_t)0x04) +#define ADC3_Channel_05_PB13 ((uint8_t)0x05) +#define ADC3_Channel_06_PE8 ((uint8_t)0x06) +#define ADC3_Channel_07_PD10 ((uint8_t)0x07) +#define ADC3_Channel_08_PD11 ((uint8_t)0x08) +#define ADC3_Channel_09_PD12 ((uint8_t)0x09) +#define ADC3_Channel_10_PD13 ((uint8_t)0x0A) +#define ADC3_Channel_11_PD14 ((uint8_t)0x0B) +#define ADC3_Channel_12_PB0 ((uint8_t)0x0C) +#define ADC3_Channel_13_PE7 ((uint8_t)0x0D) +#define ADC3_Channel_14_PE10 ((uint8_t)0x0E) +#define ADC3_Channel_15_PE11 ((uint8_t)0x0F) + +#define ADC4_Channel_01_PE14 ((uint8_t)0x01) +#define ADC4_Channel_02_PE15 ((uint8_t)0x02) +#define ADC4_Channel_03_PB12 ((uint8_t)0x03) +#define ADC4_Channel_04_PB14 ((uint8_t)0x04) +#define ADC4_Channel_05_PB15 ((uint8_t)0x05) +#define ADC4_Channel_06_PE8 ((uint8_t)0x06) +#define ADC4_Channel_07_PD10 ((uint8_t)0x07) +#define ADC4_Channel_08_PD11 ((uint8_t)0x08) +#define ADC4_Channel_09_PD12 ((uint8_t)0x09) +#define ADC4_Channel_10_PD13 ((uint8_t)0x0A) +#define ADC4_Channel_11_PD14 ((uint8_t)0x0B) +#define ADC4_Channel_12_PD8 ((uint8_t)0x0C) +#define ADC4_Channel_13_PD9 ((uint8_t)0x0D) + +#define ADC_RCC_BASE ((uint32_t)0x40021000) +#define ADC_RCC_CTRL *((uint32_t*)(ADC_RCC_BASE + 0x00)) +#define ADC_RCC_CFG *((uint32_t*)(ADC_RCC_BASE + 0x04)) +#define ADC_RCC_CLKINT *((uint32_t*)(ADC_RCC_BASE + 0x08)) +#define ADC_RCC_APB2PRST *((uint32_t*)(ADC_RCC_BASE + 0x0c)) +#define ADC_RCC_APB1PRST *((uint32_t*)(ADC_RCC_BASE + 0x10)) +#define ADC_RCC_AHBPCLKEN *((uint32_t*)(ADC_RCC_BASE + 0x14)) +#define ADC_RCC_APB2PCLKEN *((uint32_t*)(ADC_RCC_BASE + 0x18)) +#define ADC_RCC_APB1PCLKEN *((uint32_t*)(ADC_RCC_BASE + 0x1c)) +#define ADC_RCC_BDCTRL *((uint32_t*)(ADC_RCC_BASE + 0x20)) +#define ADC_RCC_CTRLSTS *((uint32_t*)(ADC_RCC_BASE + 0x24)) +#define ADC_RCC_AHBPRST *((uint32_t*)(ADC_RCC_BASE + 0x28)) +#define ADC_RCC_CFG2 *((uint32_t*)(ADC_RCC_BASE + 0x2c)) +#define ADC_RCC_CFG3 *((uint32_t*)(ADC_RCC_BASE + 0x30)) + +#define NS_PWR_CR3 *((uint32_t*)(0x40007000 + 0x0C)) +#define RCC_APB1Periph_PWR ((uint32_t)0x10000000) + +/////////////////////////////// +#define NS_GPIOA_BASE ((uint32_t)0x40010800) +#define NS_GPIOA_PL_CFG *((uint32_t*)(NS_GPIOA_BASE + 0x00)) +#define NS_GPIOA_PH_CFG *((uint32_t*)(NS_GPIOA_BASE + 0x04)) + +#define NS_GPIOC_BASE ((uint32_t)0x40011000) +#define NS_GPIOC_PL_CFG *((uint32_t*)(NS_GPIOC_BASE + 0x00)) +#define NS_GPIOC_PH_CFG *((uint32_t*)(NS_GPIOC_BASE + 0x04)) + +/* CFG2 register bit mask */ +#define CFG2_TIM18CLKSEL_SET_MASK ((uint32_t)0x20000000) +#define CFG2_TIM18CLKSEL_RESET_MASK ((uint32_t)0xDFFFFFFF) +#define CFG2_RNGCPRES_SET_MASK ((uint32_t)0x1F000000) +#define CFG2_RNGCPRES_RESET_MASK ((uint32_t)0xE0FFFFFF) +#define CFG2_ADC1MSEL_SET_MASK ((uint32_t)0x00000400) +#define CFG2_ADC1MSEL_RESET_MASK ((uint32_t)0xFFFFFBFF) +#define CFG2_ADC1MPRES_SET_MASK ((uint32_t)0x0000F800) +#define CFG2_ADC1MPRES_RESET_MASK ((uint32_t)0xFFFF07FF) +#define CFG2_ADCPLLPRES_SET_MASK ((uint32_t)0x000001F0) +#define CFG2_ADCPLLPRES_RESET_MASK ((uint32_t)0xFFFFFE0F) +#define CFG2_ADCHPRES_SET_MASK ((uint32_t)0x0000000F) +#define CFG2_ADCHPRES_RESET_MASK ((uint32_t)0xFFFFFFF0) + +#define RCC_ADCPLLCLK_DISABLE ((uint32_t)0xFFFFFEFF) +#define RCC_ADCPLLCLK_DIV1 ((uint32_t)0x00000100) +#define RCC_ADCPLLCLK_DIV2 ((uint32_t)0x00000110) +#define RCC_ADCPLLCLK_DIV4 ((uint32_t)0x00000120) +#define RCC_ADCPLLCLK_DIV6 ((uint32_t)0x00000130) +#define RCC_ADCPLLCLK_DIV8 ((uint32_t)0x00000140) +#define RCC_ADCPLLCLK_DIV10 ((uint32_t)0x00000150) +#define RCC_ADCPLLCLK_DIV12 ((uint32_t)0x00000160) +#define RCC_ADCPLLCLK_DIV16 ((uint32_t)0x00000170) +#define RCC_ADCPLLCLK_DIV32 ((uint32_t)0x00000180) +#define RCC_ADCPLLCLK_DIV64 ((uint32_t)0x00000190) +#define RCC_ADCPLLCLK_DIV128 ((uint32_t)0x000001A0) +#define RCC_ADCPLLCLK_DIV256 ((uint32_t)0x000001B0) +#define RCC_ADCPLLCLK_DIV_OTHERS ((uint32_t)0x000001C0) + +#define RCC_ADCHCLK_DIV1 ((uint32_t)0x00000000) +#define RCC_ADCHCLK_DIV2 ((uint32_t)0x00000001) +#define RCC_ADCHCLK_DIV4 ((uint32_t)0x00000002) +#define RCC_ADCHCLK_DIV6 ((uint32_t)0x00000003) +#define RCC_ADCHCLK_DIV8 ((uint32_t)0x00000004) +#define RCC_ADCHCLK_DIV10 ((uint32_t)0x00000005) +#define RCC_ADCHCLK_DIV12 ((uint32_t)0x00000006) +#define RCC_ADCHCLK_DIV16 ((uint32_t)0x00000007) +#define RCC_ADCHCLK_DIV32 ((uint32_t)0x00000008) +#define RCC_ADCHCLK_DIV_OTHERS ((uint32_t)0x00000008) + +#define SAMPT1_SMP_SET ((uint32_t)0x00000007) +#define SAMPT2_SMP_SET ((uint32_t)0x00000007) + +#define SQR4_SEQ_SET ((uint32_t)0x0000001F) +#define SQR3_SEQ_SET ((uint32_t)0x0000001F) +#define SQR2_SEQ_SET ((uint32_t)0x0000001F) +#define SQR1_SEQ_SET ((uint32_t)0x0000001F) + +#define CTRL1_CLR_MASK ((uint32_t)0xFFF0FEFF) +#define RSEQ1_CLR_MASK ((uint32_t)0xFF0FFFFF) +#define CTRL2_CLR_MASK ((uint32_t)0xFFF1F7FD) + +#define ADC_CH_0 ((uint8_t)0x00) +#define ADC_CH_1 ((uint8_t)0x01) +#define ADC_CH_2 ((uint8_t)0x02) +#define ADC_CH_3 ((uint8_t)0x03) +#define ADC_CH_4 ((uint8_t)0x04) +#define ADC_CH_5 ((uint8_t)0x05) +#define ADC_CH_6 ((uint8_t)0x06) +#define ADC_CH_7 ((uint8_t)0x07) +#define ADC_CH_8 ((uint8_t)0x08) +#define ADC_CH_9 ((uint8_t)0x09) +#define ADC_CH_10 ((uint8_t)0x0A) +#define ADC_CH_11 ((uint8_t)0x0B) +#define ADC_CH_12 ((uint8_t)0x0C) +#define ADC_CH_13 ((uint8_t)0x0D) +#define ADC_CH_14 ((uint8_t)0x0E) +#define ADC_CH_15 ((uint8_t)0x0F) +#define ADC_CH_16 ((uint8_t)0x10) +#define ADC_CH_17 ((uint8_t)0x11) +#define ADC_CH_18 ((uint8_t)0x12) + +#define ADC_WORKMODE_INDEPENDENT ((uint32_t)0x00000000) +#define ADC_WORKMODE_REG_INJECT_SIMULT ((uint32_t)0x00010000) +#define ADC_WORKMODE_REG_SIMULT_ALTER_TRIG ((uint32_t)0x00020000) +#define ADC_WORKMODE_INJ_SIMULT_FAST_INTERL ((uint32_t)0x00030000) +#define ADC_WORKMODE_INJ_SIMULT_SLOW_INTERL ((uint32_t)0x00040000) +#define ADC_WORKMODE_INJ_SIMULT ((uint32_t)0x00050000) +#define ADC_WORKMODE_REG_SIMULT ((uint32_t)0x00060000) +#define ADC_WORKMODE_FAST_INTERL ((uint32_t)0x00070000) +#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_DAT_ALIGN_R ((uint32_t)0x00000000) +#define ADC_DAT_ALIGN_L ((uint32_t)0x00000800) + +#define ADC_FLAG_RDY ((uint8_t)0x20) +#define ADC_FLAG_PD_RDY ((uint8_t)0x40) + +#define CTRL2_AD_ON_SET ((uint32_t)0x00000001) +#define CTRL2_AD_ON_RESET ((uint32_t)0xFFFFFFFE) + +#define CTRL2_CAL_SET ((uint32_t)0x00000004) + +/* ADC Software start mask */ +#define CTRL2_EXT_TRIG_SWSTART_SET ((uint32_t)0x00500000) +#define CTRL2_EXT_TRIG_SWSTART_RESET ((uint32_t)0xFFAFFFFF) + +#define ADC_SAMP_TIME_1CYCLES5 ((uint8_t)0x00) +#define ADC_SAMP_TIME_7CYCLES5 ((uint8_t)0x01) +#define ADC_SAMP_TIME_13CYCLES5 ((uint8_t)0x02) +#define ADC_SAMP_TIME_28CYCLES5 ((uint8_t)0x03) +#define ADC_SAMP_TIME_41CYCLES5 ((uint8_t)0x04) +#define ADC_SAMP_TIME_55CYCLES5 ((uint8_t)0x05) +#define ADC_SAMP_TIME_71CYCLES5 ((uint8_t)0x06) +#define ADC_SAMP_TIME_239CYCLES5 ((uint8_t)0x07) + +#define ADC_FLAG_AWDG ((uint8_t)0x01) +#define ADC_FLAG_ENDC ((uint8_t)0x02) +#define ADC_FLAG_JENDC ((uint8_t)0x04) +#define ADC_FLAG_JSTR ((uint8_t)0x08) +#define ADC_FLAG_STR ((uint8_t)0x10) +#define ADC_FLAG_EOC_ANY ((uint8_t)0x20) +#define ADC_FLAG_JEOC_ANY ((uint8_t)0x40) + +/* ADC DMA mask */ +#define CTRL2_DMA_SET ((uint32_t)0x00000100) +#define CTRL2_DMA_RESET ((uint32_t)0xFFFFFEFF) + +typedef struct { + uint32_t PeriphAddr; + uint32_t MemAddr; + uint32_t Direction; + uint32_t BufSize; + uint32_t PeriphInc; + uint32_t DMA_MemoryInc; + uint32_t PeriphDataSize; + uint32_t MemDataSize; + uint32_t CircularMode; + uint32_t Priority; + uint32_t Mem2Mem; +} DMA_InitType; + +typedef struct { + __IO uint32_t CHCFG; + __IO uint32_t TXNUM; + __IO uint32_t PADDR; + __IO uint32_t MADDR; + __IO uint32_t CHSEL; +} DMA_ChannelType; + +#define DMA_DIR_PERIPH_DST ((uint32_t)0x00000010) +#define DMA_DIR_PERIPH_SRC ((uint32_t)0x00000000) + +#define DMA_PERIPH_INC_ENABLE ((uint32_t)0x00000040) +#define DMA_PERIPH_INC_DISABLE ((uint32_t)0x00000000) + +#define DMA_MEM_INC_ENABLE ((uint32_t)0x00000080) +#define DMA_MEM_INC_DISABLE ((uint32_t)0x00000000) + +#define DMA_PERIPH_DATA_SIZE_BYTE ((uint32_t)0x00000000) +#define DMA_PERIPH_DATA_SIZE_HALFWORD ((uint32_t)0x00000100) +#define DMA_PERIPH_DATA_SIZE_WORD ((uint32_t)0x00000200) + +#define DMA_MemoryDataSize_Byte ((uint32_t)0x00000000) +#define DMA_MemoryDataSize_HalfWord ((uint32_t)0x00000400) +#define DMA_MemoryDataSize_Word ((uint32_t)0x00000800) + +#define DMA_MODE_CIRCULAR ((uint32_t)0x00000020) +#define DMA_MODE_NORMAL ((uint32_t)0x00000000) + +#define DMA_M2M_ENABLE ((uint32_t)0x00004000) +#define DMA_M2M_DISABLE ((uint32_t)0x00000000) + +#define RCC_AHB_PERIPH_DMA1 ((uint32_t)0x00000001) +#define RCC_AHB_PERIPH_DMA2 ((uint32_t)0x00000002) + +/******************* Bit definition for DMA_CHCFG1 register *******************/ +#define DMA_CHCFG1_CHEN ((uint16_t)0x0001) //!< Channel enable +#define DMA_CHCFG1_TXCIE ((uint16_t)0x0002) //!< Transfer complete interrupt enable +#define DMA_CHCFG1_HTXIE ((uint16_t)0x0004) //!< Half Transfer interrupt enable +#define DMA_CHCFG1_ERRIE ((uint16_t)0x0008) //!< Transfer error interrupt enable +#define DMA_CHCFG1_DIR ((uint16_t)0x0010) //!< Data transfer direction +#define DMA_CHCFG1_CIRC ((uint16_t)0x0020) //!< Circular mode +#define DMA_CHCFG1_PINC ((uint16_t)0x0040) //!< Peripheral increment mode +#define DMA_CHCFG1_MINC ((uint16_t)0x0080) //!< Memory increment mode + +#define NS_DMA1_BASE (0x40020000) +#define DMA1_CH1_BASE (NS_DMA1_BASE + 0x0008) +#define DMA1_CH2_BASE (NS_DMA1_BASE + 0x001C) +#define DMA1_CH3_BASE (NS_DMA1_BASE + 0x0030) +#define DMA1_CH4_BASE (NS_DMA1_BASE + 0x0044) +#define DMA1_CH5_BASE (NS_DMA1_BASE + 0x0058) +#define DMA1_CH6_BASE (NS_DMA1_BASE + 0x006C) +#define DMA1_CH7_BASE (NS_DMA1_BASE + 0x0080) +#define DMA1_CH8_BASE (NS_DMA1_BASE + 0x0094) + +#define NS_DMA2_BASE (0x40020400) +#define DMA2_CH1_BASE (NS_DMA2_BASE + 0x008) +#define DMA2_CH2_BASE (NS_DMA2_BASE + 0x01C) +#define DMA2_CH3_BASE (NS_DMA2_BASE + 0x0030) +#define DMA2_CH4_BASE (NS_DMA2_BASE + 0x0044) +#define DMA2_CH5_BASE (NS_DMA2_BASE + 0x0058) +#define DMA2_CH6_BASE (NS_DMA2_BASE + 0x006C) +#define DMA2_CH7_BASE (NS_DMA2_BASE + 0x0080) +#define DMA2_CH8_BASE (NS_DMA2_BASE + 0x0094) + +#define DMA1 ((DMA_Module*)NS_DMA1_BASE) +#define DMA2 ((DMA_Module*)NS_DMA2_BASE) +#define DMA1_CH1 ((DMA_ChannelType*)DMA1_CH1_BASE) +#define DMA1_CH2 ((DMA_ChannelType*)DMA1_CH2_BASE) +#define DMA1_CH3 ((DMA_ChannelType*)DMA1_CH3_BASE) +#define DMA1_CH4 ((DMA_ChannelType*)DMA1_CH4_BASE) +#define DMA1_CH5 ((DMA_ChannelType*)DMA1_CH5_BASE) +#define DMA1_CH6 ((DMA_ChannelType*)DMA1_CH6_BASE) +#define DMA1_CH7 ((DMA_ChannelType*)DMA1_CH7_BASE) +#define DMA1_CH8 ((DMA_ChannelType*)DMA1_CH8_BASE) +#define DMA2_CH1 ((DMA_ChannelType*)DMA2_CH1_BASE) +#define DMA2_CH2 ((DMA_ChannelType*)DMA2_CH2_BASE) +#define DMA2_CH3 ((DMA_ChannelType*)DMA2_CH3_BASE) +#define DMA2_CH4 ((DMA_ChannelType*)DMA2_CH4_BASE) +#define DMA2_CH5 ((DMA_ChannelType*)DMA2_CH5_BASE) +#define DMA2_CH6 ((DMA_ChannelType*)DMA2_CH6_BASE) +#define DMA2_CH7 ((DMA_ChannelType*)DMA2_CH7_BASE) +#define DMA2_CH8 ((DMA_ChannelType*)DMA2_CH8_BASE) + +/******************************************************************************/ +/* */ +/* DMA Controller */ +/* */ +/******************************************************************************/ + +/******************* Bit definition for DMA_INTSTS register ********************/ +#define DMA_INTSTS_GLBF1 ((uint32_t)0x00000001) //!< Channel 1 Global interrupt flag +#define DMA_INTSTS_TXCF1 ((uint32_t)0x00000002) //!< Channel 1 Transfer Complete flag +#define DMA_INTSTS_HTXF1 ((uint32_t)0x00000004) //!< Channel 1 Half Transfer flag +#define DMA_INTSTS_ERRF1 ((uint32_t)0x00000008) //!< Channel 1 Transfer Error flag +#define DMA_INTSTS_GLBF2 ((uint32_t)0x00000010) //!< Channel 2 Global interrupt flag +#define DMA_INTSTS_TXCF2 ((uint32_t)0x00000020) //!< Channel 2 Transfer Complete flag +#define DMA_INTSTS_HTXF2 ((uint32_t)0x00000040) //!< Channel 2 Half Transfer flag +#define DMA_INTSTS_ERRF2 ((uint32_t)0x00000080) //!< Channel 2 Transfer Error flag +#define DMA_INTSTS_GLBF3 ((uint32_t)0x00000100) //!< Channel 3 Global interrupt flag +#define DMA_INTSTS_TXCF3 ((uint32_t)0x00000200) //!< Channel 3 Transfer Complete flag +#define DMA_INTSTS_HTXF3 ((uint32_t)0x00000400) //!< Channel 3 Half Transfer flag +#define DMA_INTSTS_ERRF3 ((uint32_t)0x00000800) //!< Channel 3 Transfer Error flag +#define DMA_INTSTS_GLBF4 ((uint32_t)0x00001000) //!< Channel 4 Global interrupt flag +#define DMA_INTSTS_TXCF4 ((uint32_t)0x00002000) //!< Channel 4 Transfer Complete flag +#define DMA_INTSTS_HTXF4 ((uint32_t)0x00004000) //!< Channel 4 Half Transfer flag +#define DMA_INTSTS_ERRF4 ((uint32_t)0x00008000) //!< Channel 4 Transfer Error flag +#define DMA_INTSTS_GLBF5 ((uint32_t)0x00010000) //!< Channel 5 Global interrupt flag +#define DMA_INTSTS_TXCF5 ((uint32_t)0x00020000) //!< Channel 5 Transfer Complete flag +#define DMA_INTSTS_HTXF5 ((uint32_t)0x00040000) //!< Channel 5 Half Transfer flag +#define DMA_INTSTS_ERRF5 ((uint32_t)0x00080000) //!< Channel 5 Transfer Error flag +#define DMA_INTSTS_GLBF6 ((uint32_t)0x00100000) //!< Channel 6 Global interrupt flag +#define DMA_INTSTS_TXCF6 ((uint32_t)0x00200000) //!< Channel 6 Transfer Complete flag +#define DMA_INTSTS_HTXF6 ((uint32_t)0x00400000) //!< Channel 6 Half Transfer flag +#define DMA_INTSTS_ERRF6 ((uint32_t)0x00800000) //!< Channel 6 Transfer Error flag +#define DMA_INTSTS_GLBF7 ((uint32_t)0x01000000) //!< Channel 7 Global interrupt flag +#define DMA_INTSTS_TXCF7 ((uint32_t)0x02000000) //!< Channel 7 Transfer Complete flag +#define DMA_INTSTS_HTXF7 ((uint32_t)0x04000000) //!< Channel 7 Half Transfer flag +#define DMA_INTSTS_ERRF7 ((uint32_t)0x08000000) //!< Channel 7 Transfer Error flag +#define DMA_INTSTS_GLBF8 ((uint32_t)0x10000000) //!< Channel 7 Global interrupt flag +#define DMA_INTSTS_TXCF8 ((uint32_t)0x20000000) //!< Channel 7 Transfer Complete flag +#define DMA_INTSTS_HTXF8 ((uint32_t)0x40000000) //!< Channel 7 Half Transfer flag +#define DMA_INTSTS_ERRF8 ((uint32_t)0x80000000) //!< Channel 7 Transfer Error flag + +/******************* Bit definition for DMA_INTCLR register *******************/ +#define DMA_INTCLR_CGLBF1 ((uint32_t)0x00000001) //!< Channel 1 Global interrupt clear +#define DMA_INTCLR_CTXCF1 ((uint32_t)0x00000002) //!< Channel 1 Transfer Complete clear +#define DMA_INTCLR_CHTXF1 ((uint32_t)0x00000004) //!< Channel 1 Half Transfer clear +#define DMA_INTCLR_CERRF1 ((uint32_t)0x00000008) //!< Channel 1 Transfer Error clear +#define DMA_INTCLR_CGLBF2 ((uint32_t)0x00000010) //!< Channel 2 Global interrupt clear +#define DMA_INTCLR_CTXCF2 ((uint32_t)0x00000020) //!< Channel 2 Transfer Complete clear +#define DMA_INTCLR_CHTXF2 ((uint32_t)0x00000040) //!< Channel 2 Half Transfer clear +#define DMA_INTCLR_CERRF2 ((uint32_t)0x00000080) //!< Channel 2 Transfer Error clear +#define DMA_INTCLR_CGLBF3 ((uint32_t)0x00000100) //!< Channel 3 Global interrupt clear +#define DMA_INTCLR_CTXCF3 ((uint32_t)0x00000200) //!< Channel 3 Transfer Complete clear +#define DMA_INTCLR_CHTXF3 ((uint32_t)0x00000400) //!< Channel 3 Half Transfer clear +#define DMA_INTCLR_CERRF3 ((uint32_t)0x00000800) //!< Channel 3 Transfer Error clear +#define DMA_INTCLR_CGLBF4 ((uint32_t)0x00001000) //!< Channel 4 Global interrupt clear +#define DMA_INTCLR_CTXCF4 ((uint32_t)0x00002000) //!< Channel 4 Transfer Complete clear +#define DMA_INTCLR_CHTXF4 ((uint32_t)0x00004000) //!< Channel 4 Half Transfer clear +#define DMA_INTCLR_CERRF4 ((uint32_t)0x00008000) //!< Channel 4 Transfer Error clear +#define DMA_INTCLR_CGLBF5 ((uint32_t)0x00010000) //!< Channel 5 Global interrupt clear +#define DMA_INTCLR_CTXCF5 ((uint32_t)0x00020000) //!< Channel 5 Transfer Complete clear +#define DMA_INTCLR_CHTXF5 ((uint32_t)0x00040000) //!< Channel 5 Half Transfer clear +#define DMA_INTCLR_CERRF5 ((uint32_t)0x00080000) //!< Channel 5 Transfer Error clear +#define DMA_INTCLR_CGLBF6 ((uint32_t)0x00100000) //!< Channel 6 Global interrupt clear +#define DMA_INTCLR_CTXCF6 ((uint32_t)0x00200000) //!< Channel 6 Transfer Complete clear +#define DMA_INTCLR_CHTXF6 ((uint32_t)0x00400000) //!< Channel 6 Half Transfer clear +#define DMA_INTCLR_CERRF6 ((uint32_t)0x00800000) //!< Channel 6 Transfer Error clear +#define DMA_INTCLR_CGLBF7 ((uint32_t)0x01000000) //!< Channel 7 Global interrupt clear +#define DMA_INTCLR_CTXCF7 ((uint32_t)0x02000000) //!< Channel 7 Transfer Complete clear +#define DMA_INTCLR_CHTXF7 ((uint32_t)0x04000000) //!< Channel 7 Half Transfer clear +#define DMA_INTCLR_CERRF7 ((uint32_t)0x08000000) //!< Channel 7 Transfer Error clear +#define DMA_INTCLR_CGLBF8 ((uint32_t)0x10000000) //!< Channel 7 Global interrupt clear +#define DMA_INTCLR_CTXCF8 ((uint32_t)0x20000000) //!< Channel 7 Transfer Complete clear +#define DMA_INTCLR_CHTXF8 ((uint32_t)0x40000000) //!< Channel 7 Half Transfer clear +#define DMA_INTCLR_CERRF8 ((uint32_t)0x80000000) //!< Channel 7 Transfer Error clear + +/******************* Bit definition for DMA_CHCFG1 register *******************/ +#define DMA_CHCFG1_CHEN ((uint16_t)0x0001) //!< Channel enable +#define DMA_CHCFG1_TXCIE ((uint16_t)0x0002) //!< Transfer complete interrupt enable +#define DMA_CHCFG1_HTXIE ((uint16_t)0x0004) //!< Half Transfer interrupt enable +#define DMA_CHCFG1_ERRIE ((uint16_t)0x0008) //!< Transfer error interrupt enable +#define DMA_CHCFG1_DIR ((uint16_t)0x0010) //!< Data transfer direction +#define DMA_CHCFG1_CIRC ((uint16_t)0x0020) //!< Circular mode +#define DMA_CHCFG1_PINC ((uint16_t)0x0040) //!< Peripheral increment mode +#define DMA_CHCFG1_MINC ((uint16_t)0x0080) //!< Memory increment mode + +#define DMA_CHCFG1_PSIZE ((uint16_t)0x0300) //!< PSIZE[1:0] bits (Peripheral size) +#define DMA_CHCFG1_PSIZE_0 ((uint16_t)0x0100) //!< Bit 0 +#define DMA_CHCFG1_PSIZE_1 ((uint16_t)0x0200) //!< Bit 1 + +#define DMA_CHCFG1_MSIZE ((uint16_t)0x0C00) //!< MSIZE[1:0] bits (Memory size) +#define DMA_CHCFG1_MSIZE_0 ((uint16_t)0x0400) //!< Bit 0 +#define DMA_CHCFG1_MSIZE_1 ((uint16_t)0x0800) //!< Bit 1 + +#define DMA_CHCFG1_PRIOLVL ((uint16_t)0x3000) //!< PL[1:0] bits(Channel Priority level) +#define DMA_CHCFG1_PRIOLVL_0 ((uint16_t)0x1000) //!< Bit 0 +#define DMA_CHCFG1_PRIOLVL_1 ((uint16_t)0x2000) //!< Bit 1 + +#define DMA_CHCFG1_MEM2MEM ((uint16_t)0x4000) //!< Memory to memory mode + +/******************* Bit definition for DMA_CHCFG2 register *******************/ +#define DMA_CHCFG2_CHEN ((uint16_t)0x0001) //!< Channel enable +#define DMA_CHCFG2_TXCIE ((uint16_t)0x0002) //!< Transfer complete interrupt enable +#define DMA_CHCFG2_HTXIE ((uint16_t)0x0004) //!< Half Transfer interrupt enable +#define DMA_CHCFG2_ERRIE ((uint16_t)0x0008) //!< Transfer error interrupt enable +#define DMA_CHCFG2_DIR ((uint16_t)0x0010) //!< Data transfer direction +#define DMA_CHCFG2_CIRC ((uint16_t)0x0020) //!< Circular mode +#define DMA_CHCFG2_PINC ((uint16_t)0x0040) //!< Peripheral increment mode +#define DMA_CHCFG2_MINC ((uint16_t)0x0080) //!< Memory increment mode + +#define DMA_CHCFG2_PSIZE ((uint16_t)0x0300) //!< PSIZE[1:0] bits (Peripheral size) +#define DMA_CHCFG2_PSIZE_0 ((uint16_t)0x0100) //!< Bit 0 +#define DMA_CHCFG2_PSIZE_1 ((uint16_t)0x0200) //!< Bit 1 + +#define DMA_CHCFG2_MSIZE ((uint16_t)0x0C00) //!< MSIZE[1:0] bits (Memory size) +#define DMA_CHCFG2_MSIZE_0 ((uint16_t)0x0400) //!< Bit 0 +#define DMA_CHCFG2_MSIZE_1 ((uint16_t)0x0800) //!< Bit 1 + +#define DMA_CHCFG2_PRIOLVL ((uint16_t)0x3000) //!< PL[1:0] bits (Channel Priority level) +#define DMA_CHCFG2_PRIOLVL_0 ((uint16_t)0x1000) //!< Bit 0 +#define DMA_CHCFG2_PRIOLVL_1 ((uint16_t)0x2000) //!< Bit 1 + +#define DMA_CHCFG2_MEM2MEM ((uint16_t)0x4000) //!< Memory to memory mode + +/******************* Bit definition for DMA_CHCFG3 register *******************/ +#define DMA_CHCFG3_CHEN ((uint16_t)0x0001) //!< Channel enable +#define DMA_CHCFG3_TXCIE ((uint16_t)0x0002) //!< Transfer complete interrupt enable +#define DMA_CHCFG3_HTXIE ((uint16_t)0x0004) //!< Half Transfer interrupt enable +#define DMA_CHCFG3_ERRIE ((uint16_t)0x0008) //!< Transfer error interrupt enable +#define DMA_CHCFG3_DIR ((uint16_t)0x0010) //!< Data transfer direction +#define DMA_CHCFG3_CIRC ((uint16_t)0x0020) //!< Circular mode +#define DMA_CHCFG3_PINC ((uint16_t)0x0040) //!< Peripheral increment mode +#define DMA_CHCFG3_MINC ((uint16_t)0x0080) //!< Memory increment mode + +#define DMA_CHCFG3_PSIZE ((uint16_t)0x0300) //!< PSIZE[1:0] bits (Peripheral size) +#define DMA_CHCFG3_PSIZE_0 ((uint16_t)0x0100) //!< Bit 0 +#define DMA_CHCFG3_PSIZE_1 ((uint16_t)0x0200) //!< Bit 1 + +#define DMA_CHCFG3_MSIZE ((uint16_t)0x0C00) //!< MSIZE[1:0] bits (Memory size) +#define DMA_CHCFG3_MSIZE_0 ((uint16_t)0x0400) //!< Bit 0 +#define DMA_CHCFG3_MSIZE_1 ((uint16_t)0x0800) //!< Bit 1 + +#define DMA_CHCFG3_PRIOLVL ((uint16_t)0x3000) //!< PL[1:0] bits (Channel Priority level) +#define DMA_CHCFG3_PRIOLVL_0 ((uint16_t)0x1000) //!< Bit 0 +#define DMA_CHCFG3_PRIOLVL_1 ((uint16_t)0x2000) //!< Bit 1 + +#define DMA_CHCFG3_MEM2MEM ((uint16_t)0x4000) //!< Memory to memory mode + +/*!<****************** Bit definition for DMA_CHCFG4 register *******************/ +#define DMA_CHCFG4_CHEN ((uint16_t)0x0001) //!< Channel enable +#define DMA_CHCFG4_TXCIE ((uint16_t)0x0002) //!< Transfer complete interrupt enable +#define DMA_CHCFG4_HTXIE ((uint16_t)0x0004) //!< Half Transfer interrupt enable +#define DMA_CHCFG4_ERRIE ((uint16_t)0x0008) //!< Transfer error interrupt enable +#define DMA_CHCFG4_DIR ((uint16_t)0x0010) //!< Data transfer direction +#define DMA_CHCFG4_CIRC ((uint16_t)0x0020) //!< Circular mode +#define DMA_CHCFG4_PINC ((uint16_t)0x0040) //!< Peripheral increment mode +#define DMA_CHCFG4_MINC ((uint16_t)0x0080) //!< Memory increment mode + +#define DMA_CHCFG4_PSIZE ((uint16_t)0x0300) //!< PSIZE[1:0] bits (Peripheral size) +#define DMA_CHCFG4_PSIZE_0 ((uint16_t)0x0100) //!< Bit 0 +#define DMA_CHCFG4_PSIZE_1 ((uint16_t)0x0200) //!< Bit 1 + +#define DMA_CHCFG4_MSIZE ((uint16_t)0x0C00) //!< MSIZE[1:0] bits (Memory size) +#define DMA_CHCFG4_MSIZE_0 ((uint16_t)0x0400) //!< Bit 0 +#define DMA_CHCFG4_MSIZE_1 ((uint16_t)0x0800) //!< Bit 1 + +#define DMA_CHCFG4_PRIOLVL ((uint16_t)0x3000) //!< PL[1:0] bits (Channel Priority level) +#define DMA_CHCFG4_PRIOLVL_0 ((uint16_t)0x1000) //!< Bit 0 +#define DMA_CHCFG4_PRIOLVL_1 ((uint16_t)0x2000) //!< Bit 1 + +#define DMA_CHCFG4_MEM2MEM ((uint16_t)0x4000) //!< Memory to memory mode + +/****************** Bit definition for DMA_CHCFG5 register *******************/ +#define DMA_CHCFG5_CHEN ((uint16_t)0x0001) //!< Channel enable +#define DMA_CHCFG5_TXCIE ((uint16_t)0x0002) //!< Transfer complete interrupt enable +#define DMA_CHCFG5_HTXIE ((uint16_t)0x0004) //!< Half Transfer interrupt enable +#define DMA_CHCFG5_ERRIE ((uint16_t)0x0008) //!< Transfer error interrupt enable +#define DMA_CHCFG5_DIR ((uint16_t)0x0010) //!< Data transfer direction +#define DMA_CHCFG5_CIRC ((uint16_t)0x0020) //!< Circular mode +#define DMA_CHCFG5_PINC ((uint16_t)0x0040) //!< Peripheral increment mode +#define DMA_CHCFG5_MINC ((uint16_t)0x0080) //!< Memory increment mode + +#define DMA_CHCFG5_PSIZE ((uint16_t)0x0300) //!< PSIZE[1:0] bits (Peripheral size) +#define DMA_CHCFG5_PSIZE_0 ((uint16_t)0x0100) //!< Bit 0 +#define DMA_CHCFG5_PSIZE_1 ((uint16_t)0x0200) //!< Bit 1 + +#define DMA_CHCFG5_MSIZE ((uint16_t)0x0C00) //!< MSIZE[1:0] bits (Memory size) +#define DMA_CHCFG5_MSIZE_0 ((uint16_t)0x0400) //!< Bit 0 +#define DMA_CHCFG5_MSIZE_1 ((uint16_t)0x0800) //!< Bit 1 + +#define DMA_CHCFG5_PRIOLVL ((uint16_t)0x3000) //!< PL[1:0] bits (Channel Priority level) +#define DMA_CHCFG5_PRIOLVL_0 ((uint16_t)0x1000) //!< Bit 0 +#define DMA_CHCFG5_PRIOLVL_1 ((uint16_t)0x2000) //!< Bit 1 + +#define DMA_CHCFG5_MEM2MEM ((uint16_t)0x4000) //!< Memory to memory mode enable + +/******************* Bit definition for DMA_CHCFG6 register *******************/ +#define DMA_CHCFG6_CHEN ((uint16_t)0x0001) //!< Channel enable +#define DMA_CHCFG6_TXCIE ((uint16_t)0x0002) //!< Transfer complete interrupt enable +#define DMA_CHCFG6_HTXIE ((uint16_t)0x0004) //!< Half Transfer interrupt enable +#define DMA_CHCFG6_ERRIE ((uint16_t)0x0008) //!< Transfer error interrupt enable +#define DMA_CHCFG6_DIR ((uint16_t)0x0010) //!< Data transfer direction +#define DMA_CHCFG6_CIRC ((uint16_t)0x0020) //!< Circular mode +#define DMA_CHCFG6_PINC ((uint16_t)0x0040) //!< Peripheral increment mode +#define DMA_CHCFG6_MINC ((uint16_t)0x0080) //!< Memory increment mode + +#define DMA_CHCFG6_PSIZE ((uint16_t)0x0300) //!< PSIZE[1:0] bits (Peripheral size) +#define DMA_CHCFG6_PSIZE_0 ((uint16_t)0x0100) //!< Bit 0 +#define DMA_CHCFG6_PSIZE_1 ((uint16_t)0x0200) //!< Bit 1 + +#define DMA_CHCFG6_MSIZE ((uint16_t)0x0C00) //!< MSIZE[1:0] bits (Memory size) +#define DMA_CHCFG6_MSIZE_0 ((uint16_t)0x0400) //!< Bit 0 +#define DMA_CHCFG6_MSIZE_1 ((uint16_t)0x0800) //!< Bit 1 + +#define DMA_CHCFG6_PRIOLVL ((uint16_t)0x3000) //!< PL[1:0] bits (Channel Priority level) +#define DMA_CHCFG6_PRIOLVL_0 ((uint16_t)0x1000) //!< Bit 0 +#define DMA_CHCFG6_PRIOLVL_1 ((uint16_t)0x2000) //!< Bit 1 + +#define DMA_CHCFG6_MEM2MEM ((uint16_t)0x4000) //!< Memory to memory mode + +/******************* Bit definition for DMA_CHCFG7 register *******************/ +#define DMA_CHCFG7_CHEN ((uint16_t)0x0001) //!< Channel enable +#define DMA_CHCFG7_TXCIE ((uint16_t)0x0002) //!< Transfer complete interrupt enable +#define DMA_CHCFG7_HTXIE ((uint16_t)0x0004) //!< Half Transfer interrupt enable +#define DMA_CHCFG7_ERRIE ((uint16_t)0x0008) //!< Transfer error interrupt enable +#define DMA_CHCFG7_DIR ((uint16_t)0x0010) //!< Data transfer direction +#define DMA_CHCFG7_CIRC ((uint16_t)0x0020) //!< Circular mode +#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_MSIZE ((uint16_t)0x0C00) //!< MSIZE[1:0] bits (Memory size) +#define DMA_CHCFG7_MSIZE_0 ((uint16_t)0x0400) //!< Bit 0 +#define DMA_CHCFG7_MSIZE_1 ((uint16_t)0x0800) //!< Bit 1 + +#define DMA_CHCFG7_PRIOLVL ((uint16_t)0x3000) //!< PL[1:0] bits (Channel Priority level) +#define DMA_CHCFG7_PRIOLVL_0 ((uint16_t)0x1000) //!< Bit 0 +#define DMA_CHCFG7_PRIOLVL_1 ((uint16_t)0x2000) //!< Bit 1 + +#define DMA_CHCFG7_MEM2MEM ((uint16_t)0x4000) //!< Memory to memory mode enable + +/******************* Bit definition for DMA_CHCFG8 register *******************/ +#define DMA_CHCFG8_CHEN ((uint16_t)0x0001) //!< Channel enable +#define DMA_CHCFG8_TXCIE ((uint16_t)0x0002) //!< Transfer complete interrupt enable +#define DMA_CHCFG8_HTXIE ((uint16_t)0x0004) //!< Half Transfer interrupt enable +#define DMA_CHCFG8_ERRIE ((uint16_t)0x0008) //!< Transfer error interrupt enable +#define DMA_CHCFG8_DIR ((uint16_t)0x0010) //!< Data transfer direction +#define DMA_CHCFG8_CIRC ((uint16_t)0x0020) //!< Circular mode +#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_MSIZE ((uint16_t)0x0C00) //!< MSIZE[1:0] bits (Memory size) +#define DMA_CHCFG8_MSIZE_0 ((uint16_t)0x0400) //!< Bit 0 +#define DMA_CHCFG8_MSIZE_1 ((uint16_t)0x0800) //!< Bit 1 + +#define DMA_CHCFG8_PRIOLVL ((uint16_t)0x3000) //!< PL[1:0] bits (Channel Priority level) +#define DMA_CHCFG8_PRIOLVL_0 ((uint16_t)0x1000) //!< Bit 0 +#define DMA_CHCFG8_PRIOLVL_1 ((uint16_t)0x2000) //!< Bit 1 + +#define DMA_CHCFG8_MEM2MEM ((uint16_t)0x4000) //!< Memory to memory mode enable + +/****************** Bit definition for DMA_TXNUM1 register ******************/ +#define DMA_TXNUM1_NDTX ((uint16_t)0xFFFF) //!< Number of data to Transfer + +/****************** Bit definition for DMA_TXNUM2 register ******************/ +#define DMA_TXNUM2_NDTX ((uint16_t)0xFFFF) //!< Number of data to Transfer + +/****************** Bit definition for DMA_TXNUM3 register ******************/ +#define DMA_TXNUM3_NDTX ((uint16_t)0xFFFF) //!< Number of data to Transfer + +/****************** Bit definition for DMA_TXNUM4 register ******************/ +#define DMA_TXNUM4_NDTX ((uint16_t)0xFFFF) //!< Number of data to Transfer + +/****************** Bit definition for DMA_TXNUM5 register ******************/ +#define DMA_TXNUM5_NDTX ((uint16_t)0xFFFF) //!< Number of data to Transfer + +/****************** Bit definition for DMA_TXNUM6 register ******************/ +#define DMA_TXNUM6_NDTX ((uint16_t)0xFFFF) //!< Number of data to Transfer + +/****************** Bit definition for DMA_TXNUM7 register ******************/ +#define DMA_TXNUM7_NDTX ((uint16_t)0xFFFF) //!< Number of data to Transfer + +/****************** Bit definition for DMA_TXNUM8 register ******************/ +#define DMA_TXNUM8_NDTX ((uint16_t)0xFFFF) //!< Number of data to Transfer + +/****************** Bit definition for DMA_PADDR1 register *******************/ +#define DMA_PADDR1_ADDR ((uint32_t)0xFFFFFFFF) //!< Peripheral Address + +/****************** Bit definition for DMA_PADDR2 register *******************/ +#define DMA_PADDR2_ADDR ((uint32_t)0xFFFFFFFF) //!< Peripheral Address + +/****************** Bit definition for DMA_PADDR3 register *******************/ +#define DMA_PADDR3_ADDR ((uint32_t)0xFFFFFFFF) //!< Peripheral Address + +/****************** Bit definition for DMA_PADDR4 register *******************/ +#define DMA_PADDR4_ADDR ((uint32_t)0xFFFFFFFF) //!< Peripheral Address + +/****************** Bit definition for DMA_PADDR5 register *******************/ +#define DMA_PADDR5_ADDR ((uint32_t)0xFFFFFFFF) //!< Peripheral Address + +/****************** Bit definition for DMA_PADDR6 register *******************/ +#define DMA_PADDR6_ADDR ((uint32_t)0xFFFFFFFF) //!< Peripheral Address + +/****************** Bit definition for DMA_PADDR7 register *******************/ +#define DMA_PADDR7_ADDR ((uint32_t)0xFFFFFFFF) //!< Peripheral Address + +/****************** Bit definition for DMA_PADDR8 register *******************/ +#define DMA_PADDR8_ADDR ((uint32_t)0xFFFFFFFF) //!< Peripheral Address + +/****************** Bit definition for DMA_MADDR1 register *******************/ +#define DMA_MADDR1_ADDR ((uint32_t)0xFFFFFFFF) //!< Memory Address + +/****************** Bit definition for DMA_MADDR2 register *******************/ +#define DMA_MADDR2_ADDR ((uint32_t)0xFFFFFFFF) //!< Memory Address + +/****************** Bit definition for DMA_MADDR3 register *******************/ +#define DMA_MADDR3_ADDR ((uint32_t)0xFFFFFFFF) //!< Memory Address + +/****************** Bit definition for DMA_MADDR4 register *******************/ +#define DMA_MADDR4_ADDR ((uint32_t)0xFFFFFFFF) //!< Memory Address + +/****************** Bit definition for DMA_MADDR5 register *******************/ +#define DMA_MADDR5_ADDR ((uint32_t)0xFFFFFFFF) //!< Memory Address + +/****************** Bit definition for DMA_MADDR6 register *******************/ +#define DMA_MADDR6_ADDR ((uint32_t)0xFFFFFFFF) //!< Memory Address + +/****************** Bit definition for DMA_MADDR7 register *******************/ +#define DMA_MADDR7_ADDR ((uint32_t)0xFFFFFFFF) //!< Memory Address + +/****************** Bit definition for DMA_MADDR8 register *******************/ +#define DMA_MADDR8_ADDR ((uint32_t)0xFFFFFFFF) //!< Memory Address + +/****************** Bit definition for DMA_CHSEL1 register *******************/ +#define DMA_CHSEL1_CH_SEL ((uint32_t)0x0000003F) //!< Channel Select + +/****************** Bit definition for DMA_CHSEL2 register *******************/ +#define DMA_CHSEL2_CH_SEL ((uint32_t)0x0000003F) //!< Channel Select + +/****************** Bit definition for DMA_CHSEL3 register *******************/ +#define DMA_CHSEL3_CH_SEL ((uint32_t)0x0000003F) //!< Channel Select + +/****************** Bit definition for DMA_CHSEL4 register *******************/ +#define DMA_CHSEL4_CH_SEL ((uint32_t)0x0000003F) //!< Channel Select + +/****************** Bit definition for DMA_CHSEL5 register *******************/ +#define DMA_CHSEL5_CH_SEL ((uint32_t)0x0000003F) //!< Channel Select + +/****************** Bit definition for DMA_CHSEL6 register *******************/ +#define DMA_CHSEL6_CH_SEL ((uint32_t)0x0000003F) //!< Channel Select + +/****************** Bit definition for DMA_CHSEL7 register *******************/ +#define DMA_CHSEL7_CH_SEL ((uint32_t)0x0000003F) //!< Channel Select + +/****************** Bit definition for DMA_CHSEL8 register *******************/ +#define DMA_CHSEL8_CH_SEL ((uint32_t)0x0000003F) //!< Channel Select + +/****************** Bit definition for DMA_CHMAPEN register *******************/ +#define DMA_CHMAPEN_MAP_EN ((uint32_t)0x00000001) //!< Channel Map Enable + +/* DMA1 Channelx interrupt pending bit masks */ +#define DMA1_CH1_INT_MASK ((uint32_t)(DMA_INTSTS_GLBF1 | DMA_INTSTS_TXCF1 | DMA_INTSTS_HTXF1 | DMA_INTSTS_ERRF1)) +#define DMA1_CH2_INT_MASK ((uint32_t)(DMA_INTSTS_GLBF2 | DMA_INTSTS_TXCF2 | DMA_INTSTS_HTXF2 | DMA_INTSTS_ERRF2)) +#define DMA1_CH3_INT_MASK ((uint32_t)(DMA_INTSTS_GLBF3 | DMA_INTSTS_TXCF3 | DMA_INTSTS_HTXF3 | DMA_INTSTS_ERRF3)) +#define DMA1_CH4_INT_MASK ((uint32_t)(DMA_INTSTS_GLBF4 | DMA_INTSTS_TXCF4 | DMA_INTSTS_HTXF4 | DMA_INTSTS_ERRF4)) +#define DMA1_CH5_INT_MASK ((uint32_t)(DMA_INTSTS_GLBF5 | DMA_INTSTS_TXCF5 | DMA_INTSTS_HTXF5 | DMA_INTSTS_ERRF5)) +#define DMA1_CH6_INT_MASK ((uint32_t)(DMA_INTSTS_GLBF6 | DMA_INTSTS_TXCF6 | DMA_INTSTS_HTXF6 | DMA_INTSTS_ERRF6)) +#define DMA1_CH7_INT_MASK ((uint32_t)(DMA_INTSTS_GLBF7 | DMA_INTSTS_TXCF7 | DMA_INTSTS_HTXF7 | DMA_INTSTS_ERRF7)) +#define DMA1_CH8_INT_MASK ((uint32_t)(DMA_INTSTS_GLBF8 | DMA_INTSTS_TXCF8 | DMA_INTSTS_HTXF8 | DMA_INTSTS_ERRF8)) + +/* DMA2 Channelx interrupt pending bit masks */ +#define DMA2_CH1_INT_MASK ((uint32_t)(DMA_INTSTS_GLBF1 | DMA_INTSTS_TXCF1 | DMA_INTSTS_HTXF1 | DMA_INTSTS_ERRF1)) +#define DMA2_CH2_INT_MASK ((uint32_t)(DMA_INTSTS_GLBF2 | DMA_INTSTS_TXCF2 | DMA_INTSTS_HTXF2 | DMA_INTSTS_ERRF2)) +#define DMA2_CH3_INT_MASK ((uint32_t)(DMA_INTSTS_GLBF3 | DMA_INTSTS_TXCF3 | DMA_INTSTS_HTXF3 | DMA_INTSTS_ERRF3)) +#define DMA2_CH4_INT_MASK ((uint32_t)(DMA_INTSTS_GLBF4 | DMA_INTSTS_TXCF4 | DMA_INTSTS_HTXF4 | DMA_INTSTS_ERRF4)) +#define DMA2_CH5_INT_MASK ((uint32_t)(DMA_INTSTS_GLBF5 | DMA_INTSTS_TXCF5 | DMA_INTSTS_HTXF5 | DMA_INTSTS_ERRF5)) +#define DMA2_CH6_INT_MASK ((uint32_t)(DMA_INTSTS_GLBF6 | DMA_INTSTS_TXCF6 | DMA_INTSTS_HTXF6 | DMA_INTSTS_ERRF6)) +#define DMA2_CH7_INT_MASK ((uint32_t)(DMA_INTSTS_GLBF7 | DMA_INTSTS_TXCF7 | DMA_INTSTS_HTXF7 | DMA_INTSTS_ERRF7)) +#define DMA2_CH8_INT_MASK ((uint32_t)(DMA_INTSTS_GLBF8 | DMA_INTSTS_TXCF8 | DMA_INTSTS_HTXF8 | DMA_INTSTS_ERRF8)) + +typedef struct { + __IO uint32_t INTSTS; + __IO uint32_t INTCLR; + __IO DMA_ChannelType DMA_Channel[8]; + __IO uint32_t CHMAPEN; +} DMA_Module; + +#define RCC_AHB_PERIPH_ADC1 ((uint32_t)0x00001000) +#define RCC_AHB_PERIPH_ADC2 ((uint32_t)0x00002000) +#define RCC_AHB_PERIPH_ADC3 ((uint32_t)0x00004000) +#define RCC_AHB_PERIPH_ADC4 ((uint32_t)0x00008000) + +void ADC_Init(ADC_Module* NS_ADCx, ADC_InitType* ADC_InitStruct); + +/**================================================================ + * ADC reset + ================================================================*/ +void ADC_DeInit(ADC_Module* NS_ADCx); + +/**================================================================ + * ADC module enable + ================================================================*/ +void ADC_Enable(ADC_Module* NS_ADCx, uint32_t Cmd); + +/**================================================================ + * Get the ADC status logo bit + ================================================================*/ +uint32_t ADC_GetFlagStatusNew(ADC_Module* NS_ADCx, uint8_t ADC_FLAG_NEW); + +/**================================================================ + * Open ADC calibration + ================================================================*/ +void ADC_StartCalibration(ADC_Module* NS_ADCx); + +/**================================================================ + * Enable ADC DMA + ================================================================*/ +void ADC_EnableDMA(ADC_Module* NS_ADCx, uint32_t Cmd); + +/**================================================================ + * Configure ADC interrupt enable + ================================================================*/ +void ADC_ConfigInt(ADC_Module* NS_ADCx, uint16_t ADC_IT, uint32_t Cmd); + +/**================================================================ + * Get ADC calibration status + ================================================================*/ +uint32_t ADC_GetCalibrationStatus(ADC_Module* NS_ADCx); + +/**================================================================ + * Configure the ADC channel + ================================================================*/ +void ADC_ConfigRegularChannel(ADC_Module* NS_ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime); + +/**================================================================ + * Start ADC conversion + ================================================================*/ +void ADC_EnableSoftwareStartConv(ADC_Module* NS_ADCx, uint32_t Cmd); + +/**================================================================ + * Get the ADC status logo bit + ================================================================*/ +uint32_t ADC_GetFlagStatus(ADC_Module* NS_ADCx, uint8_t ADC_FLAG); + +/**================================================================ + * Clear status logo bit + ================================================================*/ +void ADC_ClearFlag(ADC_Module* NS_ADCx, uint8_t ADC_FLAG); + +/**================================================================ + * Get ADC sampling value + ================================================================*/ +uint16_t ADC_GetDat(ADC_Module* NS_ADCx); + +//////////////////////////////////////////////////////////////////////////////// + +typedef struct { + __IO uint32_t CR; /* Completely compatible */ + __IO uint32_t CFGR; /* Not compatible: ADC frequency is not set here */ + __IO uint32_t CIR; /* Completely compatible */ + + __IO uint32_t APB2RSTR; /* Completely compatible */ + __IO uint32_t APB1RSTR; /* Completely compatible */ + + __IO uint32_t AHBENR; /* Not compatible: ADC clock enables settings here */ + __IO uint32_t APB2ENR; /* Not compatible: ADC clock enables to be here */ + __IO uint32_t APB1ENR; /* compatible */ + __IO uint32_t BDCR; /* compatible */ + __IO uint32_t CSR; /* compatible */ + + __IO uint32_t AHBRSTR; /* Not compatible, ADC reset here settings */ + __IO uint32_t CFGR2; /* Not compatible, ADC clock settings here */ + __IO uint32_t CFGR3; /* Not compatible, add a new register */ + +} RCC_TypeDef; + +#define RCC ((RCC_TypeDef *) ADC_RCC_BASE) + +/**================================================================ + * Initialize ADC clock + ================================================================*/ + +void enable_adc_clk(uint8_t cmd); + +/**================================================================ + * Initialize ADC peripheral parameters + ================================================================*/ +void ADC_Initial(ADC_Module* NS_ADCx); + +/**================================================================ + * Single independent sampling + ================================================================*/ +uint16_t ADC_GetData(ADC_Module* NS_ADCx, uint8_t ADC_Channel); + +void DMA_DeInit(DMA_ChannelType* DMAyChx); + +#define CCR_CLEAR_Mask ((uint32_t)0xFFFF800F) + +void DMA_Init(DMA_ChannelType* DMAyChx, DMA_InitType* DMA_InitParam); + +void DMA_EnableChannel(DMA_ChannelType* DMAyChx, uint32_t Cmd); + +#define USE_ADC NS_ADC2 +#define USE_DMA_CH DMA1_CH8 + +/**================================================================ + * Initialize the DMA of ADC + ================================================================*/ +void ADC_DMA_init(); diff --git a/Marlin/src/HAL/STM32F1/HAL_SPI.cpp b/Marlin/src/HAL/STM32F1/HAL_SPI.cpp index 76b1c3e246..b5b57536f3 100644 --- a/Marlin/src/HAL/STM32F1/HAL_SPI.cpp +++ b/Marlin/src/HAL/STM32F1/HAL_SPI.cpp @@ -4,7 +4,6 @@ * * Based on Sprinter and grbl. * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm - * Copyright (c) 2017 Victor Perez * * 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 @@ -61,8 +60,8 @@ * @details Only configures SS pin since libmaple creates and initialize the SPI object */ void spiBegin() { - #if PIN_EXISTS(SS) - OUT_WRITE(SS_PIN, HIGH); + #if PIN_EXISTS(SD_SS) + OUT_WRITE(SD_SS_PIN, HIGH); #endif } @@ -123,7 +122,7 @@ uint8_t spiRec() { * * @details Uses DMA */ -void spiRead(uint8_t* buf, uint16_t nbyte) { +void spiRead(uint8_t *buf, uint16_t nbyte) { SPI.dmaTransfer(0, const_cast(buf), nbyte); } @@ -146,7 +145,7 @@ void spiSend(uint8_t b) { * * @details Use DMA */ -void spiSendBlock(uint8_t token, const uint8_t* buf) { +void spiSendBlock(uint8_t token, const uint8_t *buf) { SPI.send(token); SPI.dmaSend(const_cast(buf), 512); } @@ -160,7 +159,7 @@ uint8_t spiRec(uint32_t chan) { return SPI.transfer(0xFF); } void spiSend(uint32_t chan, byte b) { SPI.send(b); } // Write buffer to specified SPI channel -void spiSend(uint32_t chan, const uint8_t* buf, size_t n) { +void spiSend(uint32_t chan, const uint8_t *buf, size_t n) { for (size_t p = 0; p < n; p++) spiSend(chan, buf[p]); } diff --git a/Marlin/src/HAL/STM32F1/MarlinSPI.h b/Marlin/src/HAL/STM32F1/MarlinSPI.h new file mode 100644 index 0000000000..fab245f904 --- /dev/null +++ b/Marlin/src/HAL/STM32F1/MarlinSPI.h @@ -0,0 +1,45 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2020 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 + +/** + * Marlin currently requires 3 SPI classes: + * + * SPIClass: + * This class is normally provided by frameworks and has a semi-default interface. + * This is needed because some libraries reference it globally. + * + * SPISettings: + * Container for SPI configs for SPIClass. As above, libraries may reference it globally. + * + * These two classes are often provided by frameworks so we cannot extend them to add + * useful methods for Marlin. + * + * MarlinSPI: + * Provides the default SPIClass interface plus some Marlin goodies such as a simplified + * interface for SPI DMA transfer. + * + */ + +using MarlinSPI = SPIClass; diff --git a/Marlin/src/HAL/STM32F1/MarlinSerial.cpp b/Marlin/src/HAL/STM32F1/MarlinSerial.cpp index ebf11cb429..3b26b630df 100644 --- a/Marlin/src/HAL/STM32F1/MarlinSerial.cpp +++ b/Marlin/src/HAL/STM32F1/MarlinSerial.cpp @@ -28,7 +28,7 @@ // Copied from ~/.platformio/packages/framework-arduinoststm32-maple/STM32F1/system/libmaple/usart_private.h // Changed to handle Emergency Parser -static inline __always_inline void my_usart_irq(ring_buffer *rb, ring_buffer *wb, usart_reg_map *regs, MarlinSerial &serial) { +FORCE_INLINE void my_usart_irq(ring_buffer *rb, ring_buffer *wb, usart_reg_map *regs, MSerialT &serial) { /* Handle RXNEIE and TXEIE interrupts. * RXNE signifies availability of a byte in DR. * @@ -60,7 +60,7 @@ static inline __always_inline void my_usart_irq(ring_buffer *rb, ring_buffer *wb } else if (srflags & USART_SR_ORE) { // overrun and empty data, just do a dummy read to clear ORE - // and prevent a raise condition where a continous interrupt stream (due to ORE set) occurs + // and prevent a raise condition where a continuous interrupt stream (due to ORE set) occurs // (see chapter "Overrun error" ) in STM32 reference manual regs->DR; } @@ -75,9 +75,9 @@ static inline __always_inline void my_usart_irq(ring_buffer *rb, ring_buffer *wb } // Not every MarlinSerial port should handle emergency parsing. -// It would not make sense to parse GCode from TMC responses, for example. +// It would not make sense to parse G-Code from TMC responses, for example. constexpr bool serial_handles_emergency(int port) { - return false + return (false #ifdef SERIAL_PORT || (SERIAL_PORT) == port #endif @@ -87,23 +87,23 @@ constexpr bool serial_handles_emergency(int port) { #ifdef LCD_SERIAL_PORT || (LCD_SERIAL_PORT) == port #endif - ; + ); } -#define DEFINE_HWSERIAL_MARLIN(name, n) \ - MarlinSerial name(USART##n, \ - BOARD_USART##n##_TX_PIN, \ - BOARD_USART##n##_RX_PIN, \ - serial_handles_emergency(n)); \ - extern "C" void __irq_usart##n(void) { \ +#define DEFINE_HWSERIAL_MARLIN(name, n) \ + MSerialT name(serial_handles_emergency(n),\ + USART##n, \ + BOARD_USART##n##_TX_PIN, \ + BOARD_USART##n##_RX_PIN); \ + extern "C" void __irq_usart##n(void) { \ my_usart_irq(USART##n->rb, USART##n->wb, USART##n##_BASE, MSerial##n); \ } #define DEFINE_HWSERIAL_UART_MARLIN(name, n) \ - MarlinSerial name(UART##n, \ + MSerialT name(serial_handles_emergency(n), \ + UART##n, \ BOARD_USART##n##_TX_PIN, \ - BOARD_USART##n##_RX_PIN, \ - serial_handles_emergency(n)); \ + BOARD_USART##n##_RX_PIN); \ extern "C" void __irq_usart##n(void) { \ my_usart_irq(UART##n->rb, UART##n->wb, UART##n##_BASE, MSerial##n); \ } @@ -111,10 +111,12 @@ constexpr bool serial_handles_emergency(int port) { // Instantiate all UARTs even if they are not needed // This avoids a bunch of logic to figure out every serial // port which may be in use on the system. -DEFINE_HWSERIAL_MARLIN(MSerial1, 1); +#if DISABLED(MKS_WIFI_MODULE) + DEFINE_HWSERIAL_MARLIN(MSerial1, 1); +#endif DEFINE_HWSERIAL_MARLIN(MSerial2, 2); DEFINE_HWSERIAL_MARLIN(MSerial3, 3); -#if EITHER(STM32_HIGH_DENSITY, STM32_XL_DENSITY) +#if ANY(STM32_HIGH_DENSITY, STM32_XL_DENSITY) DEFINE_HWSERIAL_UART_MARLIN(MSerial4, 4); DEFINE_HWSERIAL_UART_MARLIN(MSerial5, 5); #endif @@ -132,12 +134,12 @@ constexpr bool IsSerialClassAllowed(const HardwareSerial&) { return false; } // If you encounter this error, replace SerialX with MSerialX, for example MSerial3. // Non-TMC ports were already validated in HAL.h, so do not require verbose error messages. -#ifdef MYSERIAL0 - CHECK_CFG_SERIAL(MYSERIAL0); -#endif #ifdef MYSERIAL1 CHECK_CFG_SERIAL(MYSERIAL1); #endif +#ifdef MYSERIAL2 + CHECK_CFG_SERIAL(MYSERIAL2); +#endif #ifdef LCD_SERIAL CHECK_CFG_SERIAL(LCD_SERIAL); #endif @@ -165,6 +167,15 @@ constexpr bool IsSerialClassAllowed(const HardwareSerial&) { return false; } #if AXIS_HAS_HW_SERIAL(Z4) CHECK_AXIS_SERIAL(Z4); #endif +#if AXIS_HAS_HW_SERIAL(I) + CHECK_AXIS_SERIAL(I); +#endif +#if AXIS_HAS_HW_SERIAL(J) + CHECK_AXIS_SERIAL(J); +#endif +#if AXIS_HAS_HW_SERIAL(K) + CHECK_AXIS_SERIAL(K); +#endif #if AXIS_HAS_HW_SERIAL(E0) CHECK_AXIS_SERIAL(E0); #endif diff --git a/Marlin/src/HAL/STM32F1/MarlinSerial.h b/Marlin/src/HAL/STM32F1/MarlinSerial.h index 6aa94b64ff..c2dc5bd571 100644 --- a/Marlin/src/HAL/STM32F1/MarlinSerial.h +++ b/Marlin/src/HAL/STM32F1/MarlinSerial.h @@ -26,28 +26,36 @@ #include #include "../../inc/MarlinConfigPre.h" -#if ENABLED(EMERGENCY_PARSER) - #include "../../feature/e_parser.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 -class MarlinSerial : public HardwareSerial { -public: - #if ENABLED(EMERGENCY_PARSER) - const bool ep_enabled; - EmergencyParser::State emergency_state; - inline bool emergency_parser_enabled() { return ep_enabled; } - #endif - - MarlinSerial(struct usart_dev *usart_device, uint8 tx_pin, uint8 rx_pin, bool TERN_(EMERGENCY_PARSER, ep_capable)) : - HardwareSerial(usart_device, tx_pin, rx_pin) - #if ENABLED(EMERGENCY_PARSER) - , ep_enabled(ep_capable) - , emergency_state(EmergencyParser::State::EP_RESET) - #endif - { } +struct MarlinSerial : public HardwareSerial { + MarlinSerial(struct usart_dev *usart_device, uint8 tx_pin, uint8 rx_pin) : HardwareSerial(usart_device, tx_pin, rx_pin) { } #ifdef UART_IRQ_PRIO // Shadow the parent methods to set IRQ priority after begin() @@ -62,10 +70,12 @@ public: #endif }; -extern MarlinSerial MSerial1; -extern MarlinSerial MSerial2; -extern MarlinSerial MSerial3; -#if EITHER(STM32_HIGH_DENSITY, STM32_XL_DENSITY) - extern MarlinSerial MSerial4; - extern MarlinSerial MSerial5; +typedef Serial1Class MSerialT; + +extern MSerialT MSerial1; +extern MSerialT MSerial2; +extern MSerialT MSerial3; +#if ANY(STM32_HIGH_DENSITY, STM32_XL_DENSITY) + extern MSerialT MSerial4; + extern MSerialT MSerial5; #endif diff --git a/Marlin/src/HAL/STM32F1/MinSerial.cpp b/Marlin/src/HAL/STM32F1/MinSerial.cpp new file mode 100644 index 0000000000..0d9a611d7e --- /dev/null +++ b/Marlin/src/HAL/STM32F1/MinSerial.cpp @@ -0,0 +1,116 @@ +/** + * 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 . + * + */ +#ifdef __STM32F1__ + +#include "../../inc/MarlinConfigPre.h" + +#if ENABLED(POSTMORTEM_DEBUGGING) + +#include "../shared/MinSerial.h" + +#include +#include +#include + +/* Instruction Synchronization Barrier */ +#define isb() __asm__ __volatile__ ("isb" : : : "memory") + +/* Data Synchronization Barrier */ +#define dsb() __asm__ __volatile__ ("dsb" : : : "memory") + +static void TXBegin() { + #if !WITHIN(SERIAL_PORT, 1, 6) + #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 + // We use MYSERIAL1 here, so we need to figure out how to get the linked register + struct usart_dev* dev = MYSERIAL1.c_dev(); + + // Or use this if removing libmaple + // int irq = dev->irq_num; + // int nvicUART[] = { NVIC_USART1 /* = 37 */, NVIC_USART2 /* = 38 */, NVIC_USART3 /* = 39 */, NVIC_UART4 /* = 52 */, NVIC_UART5 /* = 53 */ }; + // Disabling irq means setting the bit in the NVIC ICER register located at + // Disable UART interrupt in NVIC + nvic_irq_disable(dev->irq_num); + + // Use this if removing libmaple + //SBI(NVIC_BASE->ICER[1], irq - 32); + + // We NEED memory barriers to ensure Interrupts are actually disabled! + // ( https://dzone.com/articles/nvic-disabling-interrupts-on-arm-cortex-m-and-the ) + dsb(); + isb(); + + rcc_clk_disable(dev->clk_id); + rcc_clk_enable(dev->clk_id); + + usart_reg_map *regs = dev->regs; + regs->CR1 = 0; // Reset the USART + regs->CR2 = 0; // 1 stop bit + + // If we don't touch the BRR (baudrate register), we don't need to recompute. Else we would need to call + usart_set_baud_rate(dev, 0, BAUDRATE); + + regs->CR1 = (USART_CR1_TE | USART_CR1_UE); // 8 bits, no parity, 1 stop bit + #endif +} + +// A SW memory barrier, to ensure GCC does not overoptimize loops +#define sw_barrier() __asm__ volatile("": : :"memory"); +static void TX(char c) { + #if WITHIN(SERIAL_PORT, 1, 6) + struct usart_dev* dev = MYSERIAL1.c_dev(); + while (!(dev->regs->SR & USART_SR_TXE)) { + hal.watchdog_refresh(); + sw_barrier(); + } + dev->regs->DR = c; + #endif +} + +void install_min_serial() { + HAL_min_serial_init = &TXBegin; + HAL_min_serial_out = &TX; +} + +#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__ ( + "b CommonHandler_ASM\n" + ); + } + void __attribute__((naked, alias("JumpHandler_ASM"), nothrow)) __exc_hardfault(); + void __attribute__((naked, alias("JumpHandler_ASM"), nothrow)) __exc_busfault(); + void __attribute__((naked, alias("JumpHandler_ASM"), nothrow)) __exc_usagefault(); + void __attribute__((naked, alias("JumpHandler_ASM"), nothrow)) __exc_memmanage(); + void __attribute__((naked, alias("JumpHandler_ASM"), nothrow)) __exc_nmi(); + void __attribute__((naked, alias("JumpHandler_ASM"), nothrow)) __stm32reservedexception7(); + void __attribute__((naked, alias("JumpHandler_ASM"), nothrow)) __stm32reservedexception8(); + void __attribute__((naked, alias("JumpHandler_ASM"), nothrow)) __stm32reservedexception9(); + void __attribute__((naked, alias("JumpHandler_ASM"), nothrow)) __stm32reservedexception10(); + void __attribute__((naked, alias("JumpHandler_ASM"), nothrow)) __stm32reservedexception13(); +} +#endif + +#endif // POSTMORTEM_DEBUGGING +#endif // __STM32F1__ diff --git a/Marlin/src/HAL/STM32F1/README.md b/Marlin/src/HAL/STM32F1/README.md index b5bd5141fb..e289f35433 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.cpp b/Marlin/src/HAL/STM32F1/SPI.cpp index 0452cf6293..ab111ddbf0 100644 --- a/Marlin/src/HAL/STM32F1/SPI.cpp +++ b/Marlin/src/HAL/STM32F1/SPI.cpp @@ -91,6 +91,14 @@ static const spi_pins board_spi_pins[] __FLASH__ = { static void *_spi3_this; #endif +/** + * @brief Wait until TXE (tx empty) flag is set and BSY (busy) flag unset. + */ +static inline void waitSpiTxEnd(spi_dev *spi_d) { + while (spi_is_tx_empty(spi_d) == 0) { /* nada */ } // wait until TXE=1 + while (spi_is_busy(spi_d) != 0) { /* nada */ } // wait until BSY=0 +} + /** * Constructor */ @@ -147,6 +155,18 @@ SPIClass::SPIClass(uint32_t spi_num) { _currentSetting->state = SPI_STATE_IDLE; } +SPIClass::SPIClass(int8_t mosi, int8_t miso, int8_t sclk, int8_t ssel) : SPIClass(1) { + #if BOARD_NR_SPI >= 1 + if (mosi == BOARD_SPI1_MOSI_PIN) setModule(1); + #endif + #if BOARD_NR_SPI >= 2 + if (mosi == BOARD_SPI2_MOSI_PIN) setModule(2); + #endif + #if BOARD_NR_SPI >= 3 + if (mosi == BOARD_SPI3_MOSI_PIN) setModule(3); + #endif +} + /** * Set up/tear down */ @@ -351,8 +371,8 @@ uint16_t SPIClass::transfer16(uint16_t data) const { /** * Roger Clark and Victor Perez, 2015 * Performs a DMA SPI transfer with at least a receive buffer. - * If a TX buffer is not provided, FF is sent over and over for the lenght of the transfer. - * On exit TX buffer is not modified, and RX buffer cotains the received data. + * If a TX buffer is not provided, FF is sent over and over for the length of the transfer. + * On exit TX buffer is not modified, and RX buffer contains the received data. * Still in progress. */ void SPIClass::dmaTransferSet(const void *transmitBuf, void *receiveBuf) { @@ -497,7 +517,6 @@ uint8_t SPIClass::dmaSendAsync(const void * transmitBuf, uint16_t length, bool m return b; } - /** * New functions added to manage callbacks. * Victor Perez 2017 @@ -506,23 +525,22 @@ void SPIClass::onReceive(void(*callback)()) { _currentSetting->receiveCallback = callback; if (callback) { switch (_currentSetting->spi_d->clk_id) { - #if BOARD_NR_SPI >= 1 - case RCC_SPI1: - dma_attach_interrupt(_currentSetting->spiDmaDev, _currentSetting->spiRxDmaChannel, &SPIClass::_spi1EventCallback); - break; - #endif - #if BOARD_NR_SPI >= 2 - case RCC_SPI2: - dma_attach_interrupt(_currentSetting->spiDmaDev, _currentSetting->spiRxDmaChannel, &SPIClass::_spi2EventCallback); - break; - #endif - #if BOARD_NR_SPI >= 3 - case RCC_SPI3: - dma_attach_interrupt(_currentSetting->spiDmaDev, _currentSetting->spiRxDmaChannel, &SPIClass::_spi3EventCallback); - break; - #endif - default: - ASSERT(0); + #if BOARD_NR_SPI >= 1 + case RCC_SPI1: + dma_attach_interrupt(_currentSetting->spiDmaDev, _currentSetting->spiRxDmaChannel, &SPIClass::_spi1EventCallback); + break; + #endif + #if BOARD_NR_SPI >= 2 + case RCC_SPI2: + dma_attach_interrupt(_currentSetting->spiDmaDev, _currentSetting->spiRxDmaChannel, &SPIClass::_spi2EventCallback); + break; + #endif + #if BOARD_NR_SPI >= 3 + case RCC_SPI3: + dma_attach_interrupt(_currentSetting->spiDmaDev, _currentSetting->spiRxDmaChannel, &SPIClass::_spi3EventCallback); + break; + #endif + default: ASSERT(0); } } else { @@ -534,23 +552,22 @@ void SPIClass::onTransmit(void(*callback)()) { _currentSetting->transmitCallback = callback; if (callback) { switch (_currentSetting->spi_d->clk_id) { - #if BOARD_NR_SPI >= 1 - case RCC_SPI1: - dma_attach_interrupt(_currentSetting->spiDmaDev, _currentSetting->spiTxDmaChannel, &SPIClass::_spi1EventCallback); - break; - #endif - #if BOARD_NR_SPI >= 2 - case RCC_SPI2: - dma_attach_interrupt(_currentSetting->spiDmaDev, _currentSetting->spiTxDmaChannel, &SPIClass::_spi2EventCallback); - break; - #endif - #if BOARD_NR_SPI >= 3 - case RCC_SPI3: - dma_attach_interrupt(_currentSetting->spiDmaDev, _currentSetting->spiTxDmaChannel, &SPIClass::_spi3EventCallback); - break; - #endif - default: - ASSERT(0); + #if BOARD_NR_SPI >= 1 + case RCC_SPI1: + dma_attach_interrupt(_currentSetting->spiDmaDev, _currentSetting->spiTxDmaChannel, &SPIClass::_spi1EventCallback); + break; + #endif + #if BOARD_NR_SPI >= 2 + case RCC_SPI2: + dma_attach_interrupt(_currentSetting->spiDmaDev, _currentSetting->spiTxDmaChannel, &SPIClass::_spi2EventCallback); + break; + #endif + #if BOARD_NR_SPI >= 3 + case RCC_SPI3: + dma_attach_interrupt(_currentSetting->spiDmaDev, _currentSetting->spiTxDmaChannel, &SPIClass::_spi3EventCallback); + break; + #endif + default: ASSERT(0); } } else { @@ -656,7 +673,7 @@ static const spi_pins* dev_to_spi_pins(spi_dev *dev) { #if BOARD_NR_SPI >= 3 case RCC_SPI3: return board_spi_pins + 2; #endif - default: return NULL; + default: return nullptr; } } diff --git a/Marlin/src/HAL/STM32F1/SPI.h b/Marlin/src/HAL/STM32F1/SPI.h index 0d20a46577..72063df699 100644 --- a/Marlin/src/HAL/STM32F1/SPI.h +++ b/Marlin/src/HAL/STM32F1/SPI.h @@ -33,6 +33,17 @@ #include #include +#include "../../core/macros.h" // for PIN_EXISTS + +// Number of SPI ports +#if PIN_EXISTS(BOARD_SPI3_SCK) + #define BOARD_NR_SPI 3 +#elif PIN_EXISTS(BOARD_SPI2_SCK) + #define BOARD_NR_SPI 2 +#elif PIN_EXISTS(BOARD_SPI1_SCK) + #define BOARD_NR_SPI 1 +#endif + // SPI_HAS_TRANSACTION means SPI has // - beginTransaction() // - endTransaction() @@ -49,7 +60,7 @@ #define SPI_CLOCK_DIV128 SPI_BAUD_PCLK_DIV_128 #define SPI_CLOCK_DIV256 SPI_BAUD_PCLK_DIV_256 -/* +/** * Roger Clark. 20150106 * Commented out redundant AVR defined * @@ -138,13 +149,13 @@ private: spi_dev *spi_d; dma_channel spiRxDmaChannel, spiTxDmaChannel; dma_dev* spiDmaDev; - void (*receiveCallback)() = NULL; - void (*transmitCallback)() = NULL; + void (*receiveCallback)() = nullptr; + void (*transmitCallback)() = nullptr; friend class SPIClass; }; -/* +/** * Kept for compat. */ static const uint8_t ff = 0xFF; @@ -163,6 +174,11 @@ public: */ SPIClass(uint32_t spiPortNumber); + /** + * Init using pins + */ + SPIClass(int8_t mosi, int8_t miso, int8_t sclk, int8_t ssel=-1); + /** * @brief Equivalent to begin(SPI_1_125MHZ, MSBFIRST, 0). */ @@ -219,7 +235,7 @@ public: void onReceive(void(*)()); void onTransmit(void(*)()); - /* + /** * I/O */ @@ -300,7 +316,7 @@ public: uint8_t dmaSendRepeat(uint16_t length); uint8_t dmaSendAsync(const void * transmitBuf, uint16_t length, bool minc = 1); - /* + /** * Pin accessors */ @@ -384,7 +400,7 @@ private: void updateSettings(); - /* + /** * Functions added for DMA transfers with Callback. * Experimental. */ @@ -409,12 +425,4 @@ private: */ }; -/** - * @brief Wait until TXE (tx empty) flag is set and BSY (busy) flag unset. - */ -static inline void waitSpiTxEnd(spi_dev *spi_d) { - while (spi_is_tx_empty(spi_d) == 0) { /* nada */ } // wait until TXE=1 - while (spi_is_busy(spi_d) != 0) { /* nada */ } // wait until BSY=0 -} - extern SPIClass SPI; diff --git a/Marlin/src/HAL/STM32F1/Servo.cpp b/Marlin/src/HAL/STM32F1/Servo.cpp index e1ee831493..00caec287b 100644 --- a/Marlin/src/HAL/STM32F1/Servo.cpp +++ b/Marlin/src/HAL/STM32F1/Servo.cpp @@ -4,7 +4,6 @@ * * Based on Sprinter and grbl. * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm - * Copyright (c) 2017 Victor Perez * * 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 @@ -30,8 +29,6 @@ uint8_t ServoCount = 0; #include "Servo.h" -//#include "Servo.h" - #include #include #include @@ -45,7 +42,7 @@ uint8_t ServoCount = 0; * * This uses the smallest prescaler that allows an overflow < 2^16. */ -#define MAX_OVERFLOW UINT16_MAX //((1 << 16) - 1) +#define MAX_OVERFLOW UINT16_MAX // _BV(16) - 1 #define CYC_MSEC (1000 * CYCLES_PER_MICROSECOND) #define TAU_MSEC 20 #define TAU_USEC (TAU_MSEC * 1000) @@ -60,7 +57,7 @@ uint8_t ServoCount = 0; #define US_TO_ANGLE(us) int16_t(map((us), SERVO_DEFAULT_MIN_PW, SERVO_DEFAULT_MAX_PW, minAngle, maxAngle)) void libServo::servoWrite(uint8_t inPin, uint16_t duty_cycle) { - #ifdef SERVO0_TIMER_NUM + #ifdef MF_TIMER_SERVO0 if (servoIndex == 0) { pwmSetDuty(duty_cycle); return; @@ -74,7 +71,7 @@ void libServo::servoWrite(uint8_t inPin, uint16_t duty_cycle) { libServo::libServo() { servoIndex = ServoCount < MAX_SERVOS ? ServoCount++ : INVALID_SERVO; - timer_set_interrupt_priority(SERVO0_TIMER_NUM, SERVO0_TIMER_IRQ_PRIO); + HAL_timer_set_interrupt_priority(MF_TIMER_SERVO0, SERVO0_TIMER_IRQ_PRIO); } bool libServo::attach(const int32_t inPin, const int32_t inMinAngle, const int32_t inMaxAngle) { @@ -85,7 +82,7 @@ bool libServo::attach(const int32_t inPin, const int32_t inMinAngle, const int32 maxAngle = inMaxAngle; angle = -1; - #ifdef SERVO0_TIMER_NUM + #ifdef MF_TIMER_SERVO0 if (servoIndex == 0 && setupSoftPWM(inPin)) { pin = inPin; // set attached() return true; @@ -119,7 +116,7 @@ bool libServo::detach() { int32_t libServo::read() const { if (attached()) { - #ifdef SERVO0_TIMER_NUM + #ifdef MF_TIMER_SERVO0 if (servoIndex == 0) return angle; #endif timer_dev *tdev = PIN_MAP[pin].timer_device; @@ -141,35 +138,35 @@ void libServo::move(const int32_t value) { } } -#ifdef SERVO0_TIMER_NUM +#ifdef MF_TIMER_SERVO0 extern "C" void Servo_IRQHandler() { - static timer_dev *tdev = get_timer_dev(SERVO0_TIMER_NUM); + static timer_dev *tdev = HAL_get_timer_dev(MF_TIMER_SERVO0); uint16_t SR = timer_get_status(tdev); if (SR & TIMER_SR_CC1IF) { // channel 1 off #ifdef SERVO0_PWM_OD - OUT_WRITE_OD(SERVO0_PIN, 1); // off + OUT_WRITE_OD(SERVO0_PIN, HIGH); // off #else - OUT_WRITE(SERVO0_PIN, 0); + OUT_WRITE(SERVO0_PIN, LOW); #endif timer_reset_status_bit(tdev, TIMER_SR_CC1IF_BIT); } if (SR & TIMER_SR_CC2IF) { // channel 2 resume #ifdef SERVO0_PWM_OD - OUT_WRITE_OD(SERVO0_PIN, 0); // on + OUT_WRITE_OD(SERVO0_PIN, LOW); // on #else - OUT_WRITE(SERVO0_PIN, 1); + OUT_WRITE(SERVO0_PIN, HIGH); #endif timer_reset_status_bit(tdev, TIMER_SR_CC2IF_BIT); } } bool libServo::setupSoftPWM(const int32_t inPin) { - timer_dev *tdev = get_timer_dev(SERVO0_TIMER_NUM); + timer_dev *tdev = HAL_get_timer_dev(MF_TIMER_SERVO0); if (!tdev) return false; #ifdef SERVO0_PWM_OD - OUT_WRITE_OD(inPin, 1); + OUT_WRITE_OD(inPin, HIGH); #else - OUT_WRITE(inPin, 0); + OUT_WRITE(inPin, LOW); #endif timer_pause(tdev); @@ -189,7 +186,7 @@ void libServo::move(const int32_t value) { } void libServo::pwmSetDuty(const uint16_t duty_cycle) { - timer_dev *tdev = get_timer_dev(SERVO0_TIMER_NUM); + timer_dev *tdev = HAL_get_timer_dev(MF_TIMER_SERVO0); timer_set_compare(tdev, 1, duty_cycle); timer_generate_update(tdev); if (duty_cycle) { @@ -200,15 +197,15 @@ void libServo::move(const int32_t value) { timer_disable_irq(tdev, 1); timer_disable_irq(tdev, 2); #ifdef SERVO0_PWM_OD - OUT_WRITE_OD(pin, 1); // off + OUT_WRITE_OD(pin, HIGH); // off #else - OUT_WRITE(pin, 0); + OUT_WRITE(pin, LOW); #endif } } void libServo::pauseSoftPWM() { // detach - timer_dev *tdev = get_timer_dev(SERVO0_TIMER_NUM); + timer_dev *tdev = HAL_get_timer_dev(MF_TIMER_SERVO0); timer_pause(tdev); pwmSetDuty(0); } diff --git a/Marlin/src/HAL/STM32F1/Servo.h b/Marlin/src/HAL/STM32F1/Servo.h index b6143de81d..ffafc23833 100644 --- a/Marlin/src/HAL/STM32F1/Servo.h +++ b/Marlin/src/HAL/STM32F1/Servo.h @@ -4,7 +4,6 @@ * * Based on Sprinter and grbl. * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm - * Copyright (c) 2017 Victor Perez * * 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 @@ -35,7 +34,8 @@ #define SERVO_DEFAULT_MIN_ANGLE 0 #define SERVO_DEFAULT_MAX_ANGLE 180 -#define HAL_SERVO_LIB libServo +class libServo; +typedef libServo hal_servo_t; class libServo { public: diff --git a/Marlin/src/HAL/STM32F1/SoftwareSerial.cpp b/Marlin/src/HAL/STM32F1/SoftwareSerial.cpp deleted file mode 100644 index 993403cf72..0000000000 --- a/Marlin/src/HAL/STM32F1/SoftwareSerial.cpp +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Marlin 3D Printer Firmware - * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * - * 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 . - * - */ -#if defined(__STM32F1__) && !defined(HAVE_SW_SERIAL) - -/** - * Empty class for Software Serial implementation (Custom RX/TX pins) - * - * TODO: Optionally use https://github.com/FYSETC/SoftwareSerialM if TMC UART is wanted - */ - -#include "SoftwareSerial.h" - -// Constructor - -SoftwareSerial::SoftwareSerial(int8_t RX_pin, int8_t TX_pin) {} - -// Public - -void SoftwareSerial::begin(const uint32_t baudrate) { -} - -bool SoftwareSerial::available() { - return false; -} - -uint8_t SoftwareSerial::read() { - return 0; -} - -uint16_t SoftwareSerial::write(uint8_t byte) { - return 0; -} - -void SoftwareSerial::flush() {} - -void SoftwareSerial::listen() { - listening = true; -} - -void SoftwareSerial::stopListening() { - listening = false; -} - -#endif // __STM32F1__ diff --git a/Marlin/src/HAL/STM32F1/SoftwareSerial.h b/Marlin/src/HAL/STM32F1/SoftwareSerial.h deleted file mode 100644 index 1c8058665a..0000000000 --- a/Marlin/src/HAL/STM32F1/SoftwareSerial.h +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Marlin 3D Printer Firmware - * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * - * 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 - -#ifndef HAVE_SW_SERIAL - #define SW_SERIAL_PLACEHOLDER 1 -#endif - -class SoftwareSerial { -public: - SoftwareSerial(int8_t RX_pin, int8_t TX_pin); - - void begin(const uint32_t baudrate); - - bool available(); - - uint8_t read(); - uint16_t write(uint8_t byte); - void flush(); - - void listen(); - void stopListening(); - -protected: - bool listening; -}; diff --git a/Marlin/src/HAL/STM32F1/adc.h b/Marlin/src/HAL/STM32F1/adc.h new file mode 100644 index 0000000000..a2f5652de0 --- /dev/null +++ b/Marlin/src/HAL/STM32F1/adc.h @@ -0,0 +1,58 @@ +/** + * 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 + * (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 + +/** + * HAL for stm32duino.com based on Libmaple and compatible (STM32F1) + * + * adc.h - Define enumerated indices for enabled ADC Features + */ + +#include "../../inc/MarlinConfig.h" + +enum ADCIndex : uint8_t { + OPTITEM(HAS_TEMP_ADC_0, TEMP_0 ) + OPTITEM(HAS_TEMP_ADC_1, TEMP_1 ) + OPTITEM(HAS_TEMP_ADC_2, TEMP_2 ) + OPTITEM(HAS_TEMP_ADC_3, TEMP_3 ) + OPTITEM(HAS_TEMP_ADC_4, TEMP_4 ) + OPTITEM(HAS_TEMP_ADC_5, TEMP_5 ) + OPTITEM(HAS_TEMP_ADC_6, TEMP_6 ) + OPTITEM(HAS_TEMP_ADC_7, TEMP_7 ) + OPTITEM(HAS_TEMP_ADC_BED, TEMP_BED ) + OPTITEM(HAS_TEMP_ADC_CHAMBER, TEMP_CHAMBER ) + OPTITEM(HAS_TEMP_ADC_PROBE, TEMP_PROBE ) + OPTITEM(HAS_TEMP_ADC_COOLER, TEMP_COOLER ) + OPTITEM(HAS_TEMP_ADC_BOARD, TEMP_BOARD ) + OPTITEM(HAS_TEMP_ADC_SOC, TEMP_SOC ) + 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 ) + OPTITEM(HAS_JOY_ADC_Z, JOY_Z ) + OPTITEM(POWER_MONITOR_CURRENT, POWERMON_CURRENT ) + OPTITEM(POWER_MONITOR_VOLTAGE, POWERMON_VOLTAGE ) + ADC_COUNT +}; + +extern uint16_t adc_results[ADC_COUNT]; diff --git a/Marlin/src/HAL/STM32F1/build_flags.py b/Marlin/src/HAL/STM32F1/build_flags.py deleted file mode 100755 index 98c871a1d8..0000000000 --- a/Marlin/src/HAL/STM32F1/build_flags.py +++ /dev/null @@ -1,53 +0,0 @@ -from __future__ import print_function -import sys - -#dynamic build flags for generic compile options -if __name__ == "__main__": - args = " ".join([ "-std=gnu11", - "-Os", - "-mcpu=cortex-m3", - "-mthumb", - - "-fsigned-char", - "-fno-move-loop-invariants", - "-fno-strict-aliasing", - - "--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: - 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 76% rename from Marlin/src/HAL/STM32F1/eeprom_bl24cxx.cpp rename to Marlin/src/HAL/STM32F1/eeprom/eeprom_bl24cxx.cpp index 658b7cd4a6..9e96a741bf 100644 --- a/Marlin/src/HAL/STM32F1/eeprom_bl24cxx.cpp +++ b/Marlin/src/HAL/STM32F1/eeprom/eeprom_bl24cxx.cpp @@ -19,20 +19,19 @@ * along with this program. If not, see . * */ +#ifdef __STM32F1__ /** * PersistentStore for Arduino-style EEPROM interface * with simple implementations supplied by Marlin. */ -#ifdef __STM32F1__ - -#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 @@ -42,20 +41,19 @@ #error "MARLIN_EEPROM_SIZE is required for IIC_BL24CXX_EEPROM." #endif -size_t PersistentStore::capacity() { return MARLIN_EEPROM_SIZE; } +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)pos; - // EEPROM has only ~100,000 write cycles, - // so only write bytes that have changed! - if (v != eeprom_read_byte(p)) { + uint8_t * const p = (uint8_t * const)REAL_EEPROM_ADDR(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); - delay(2); + 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; @@ -68,10 +66,9 @@ bool PersistentStore::write_data(int &pos, const uint8_t *value, size_t size, ui return false; } -bool PersistentStore::read_data(int &pos, uint8_t* value, size_t size, uint16_t *crc, const bool writing/*=true*/) { +bool PersistentStore::read_data(int &pos, uint8_t *value, size_t size, uint16_t *crc, const bool writing/*=true*/) { do { - uint8_t * const p = (uint8_t * const)pos; - uint8_t c = eeprom_read_byte(p); + const uint8_t c = eeprom_read_byte((uint8_t*)REAL_EEPROM_ADDR(pos)); if (writing) *value = c; crc16(crc, &c, 1); pos++; diff --git a/Marlin/src/HAL/STM32F1/eeprom_flash.cpp b/Marlin/src/HAL/STM32F1/eeprom/eeprom_flash.cpp similarity index 77% rename from Marlin/src/HAL/STM32F1/eeprom_flash.cpp rename to Marlin/src/HAL/STM32F1/eeprom/eeprom_flash.cpp index 8db8c8638b..2292c02203 100644 --- a/Marlin/src/HAL/STM32F1/eeprom_flash.cpp +++ b/Marlin/src/HAL/STM32F1/eeprom/eeprom_flash.cpp @@ -1,10 +1,9 @@ /** * Marlin 3D Printer Firmware - * * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com - * Copyright (c) 2015-2016 Nico Tonnhofer wurstnase.reprap@gmail.com - * Copyright (c) 2016 Victor Perez victor_pv@hotmail.com + * + * 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 @@ -29,33 +28,33 @@ #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; } +size_t PersistentStore::capacity() { return MARLIN_EEPROM_SIZE - eeprom_exclude_size; } 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; @@ -81,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); } @@ -101,7 +100,7 @@ bool PersistentStore::write_data(int &pos, const uint8_t *value, size_t size, ui return false; // return true for any error } -bool PersistentStore::read_data(int &pos, uint8_t* value, const size_t size, uint16_t *crc, const bool writing/*=true*/) { +bool PersistentStore::read_data(int &pos, uint8_t *value, const size_t size, uint16_t *crc, const bool writing/*=true*/) { const uint8_t * const buff = writing ? &value[0] : &ram_eeprom[pos]; if (writing) for (size_t i = 0; i < size; i++) value[i] = ram_eeprom[pos + i]; crc16(crc, buff, size); diff --git a/Marlin/src/HAL/STM32F1/eeprom_if_iic.cpp b/Marlin/src/HAL/STM32F1/eeprom/eeprom_if_iic.cpp similarity index 86% rename from Marlin/src/HAL/STM32F1/eeprom_if_iic.cpp rename to Marlin/src/HAL/STM32F1/eeprom/eeprom_if_iic.cpp index ccc3fc537f..e7e7fc1db1 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(); } @@ -40,9 +40,9 @@ void eeprom_init() { BL24CXX::init(); } // Public functions // ------------------------ -void eeprom_write_byte(uint8_t *pos, unsigned char value) { +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 85% rename from Marlin/src/HAL/STM32F1/eeprom_sdcard.cpp rename to Marlin/src/HAL/STM32F1/eeprom/eeprom_sdcard.cpp index 11959191f6..e41c6e0aa0 100644 --- a/Marlin/src/HAL/STM32F1/eeprom_sdcard.cpp +++ b/Marlin/src/HAL/STM32F1/eeprom/eeprom_sdcard.cpp @@ -27,19 +27,19 @@ #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" #ifndef MARLIN_EEPROM_SIZE #define MARLIN_EEPROM_SIZE 0x1000 // 4KB #endif -size_t PersistentStore::capacity() { return MARLIN_EEPROM_SIZE; } +size_t PersistentStore::capacity() { return MARLIN_EEPROM_SIZE - eeprom_exclude_size; } #define _ALIGN(x) __attribute__ ((aligned(x))) // SDIO uint32_t* compat. static char _ALIGN(4) HAL_eeprom_data[MARLIN_EEPROM_SIZE]; @@ -47,13 +47,13 @@ static char _ALIGN(4) HAL_eeprom_data[MARLIN_EEPROM_SIZE]; bool PersistentStore::access_start() { if (!card.isMounted()) return false; - SdFile file, root = card.getroot(); + MediaFile file, root = card.getroot(); if (!file.open(&root, EEPROM_FILENAME, O_RDONLY)) return true; // false aborts the save 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; @@ -62,7 +62,7 @@ bool PersistentStore::access_start() { bool PersistentStore::access_finish() { if (!card.isMounted()) return false; - SdFile file, root = card.getroot(); + MediaFile file, root = card.getroot(); int bytes_written = 0; if (file.open(&root, EEPROM_FILENAME, O_CREAT | O_WRITE | O_TRUNC)) { bytes_written = file.write(HAL_eeprom_data, MARLIN_EEPROM_SIZE); @@ -79,7 +79,7 @@ bool PersistentStore::write_data(int &pos, const uint8_t *value, size_t size, ui return false; } -bool PersistentStore::read_data(int &pos, uint8_t* value, const size_t size, uint16_t *crc, const bool writing/*=true*/) { +bool PersistentStore::read_data(int &pos, uint8_t *value, const size_t size, uint16_t *crc, const bool writing/*=true*/) { for (size_t i = 0; i < size; i++) { uint8_t c = HAL_eeprom_data[pos + i]; if (writing) value[i] = c; diff --git a/Marlin/src/HAL/STM32F1/eeprom_wired.cpp b/Marlin/src/HAL/STM32F1/eeprom/eeprom_wired.cpp similarity index 73% rename from Marlin/src/HAL/STM32F1/eeprom_wired.cpp rename to Marlin/src/HAL/STM32F1/eeprom/eeprom_wired.cpp index fffd6ccaf0..a1464ab983 100644 --- a/Marlin/src/HAL/STM32F1/eeprom_wired.cpp +++ b/Marlin/src/HAL/STM32F1/eeprom/eeprom_wired.cpp @@ -1,8 +1,10 @@ /** * Marlin 3D Printer Firmware - * * Copyright (c) 2020 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 @@ -24,17 +26,17 @@ #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." #endif -size_t PersistentStore::capacity() { return MARLIN_EEPROM_SIZE; } +size_t PersistentStore::capacity() { return MARLIN_EEPROM_SIZE - eeprom_exclude_size; } bool PersistentStore::access_finish() { return true; } @@ -45,7 +47,7 @@ bool PersistentStore::access_start() { SET_OUTPUT(BOARD_SPI1_SCK_PIN); SET_OUTPUT(BOARD_SPI1_MOSI_PIN); SET_INPUT(BOARD_SPI1_MISO_PIN); - SET_OUTPUT(SPI_EEPROM1_CS); + SET_OUTPUT(SPI_EEPROM1_CS_PIN); #endif spiInit(0); #endif @@ -53,13 +55,13 @@ bool PersistentStore::access_start() { } bool PersistentStore::write_data(int &pos, const uint8_t *value, size_t size, uint16_t *crc) { + uint16_t written = 0; while (size--) { - uint8_t * const p = (uint8_t * const)pos; + uint8_t * const p = (uint8_t * const)REAL_EEPROM_ADDR(pos); uint8_t v = *value; - // EEPROM has only ~100,000 write cycles, - // so only write bytes that have changed! - if (v != eeprom_read_byte(p)) { + 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; @@ -72,9 +74,9 @@ bool PersistentStore::write_data(int &pos, const uint8_t *value, size_t size, ui return false; } -bool PersistentStore::read_data(int &pos, uint8_t* value, size_t size, uint16_t *crc, const bool writing/*=true*/) { +bool PersistentStore::read_data(int &pos, uint8_t *value, size_t size, uint16_t *crc, const bool writing/*=true*/) { do { - uint8_t c = eeprom_read_byte((uint8_t*)pos); + const uint8_t c = eeprom_read_byte((uint8_t*)REAL_EEPROM_ADDR(pos)); if (writing && value) *value = c; crc16(crc, &c, 1); pos++; diff --git a/Marlin/src/HAL/STM32F1/endstop_interrupts.h b/Marlin/src/HAL/STM32F1/endstop_interrupts.h index bcb07d991d..f8f21a4c6d 100644 --- a/Marlin/src/HAL/STM32F1/endstop_interrupts.h +++ b/Marlin/src/HAL/STM32F1/endstop_interrupts.h @@ -4,7 +4,6 @@ * * Based on Sprinter and grbl. * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm - * Copyright (c) 2017 Victor Perez * * 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 @@ -28,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 */ @@ -54,21 +53,34 @@ void endstop_ISR() { endstops.update(); } void setup_endstop_interrupts() { #define _ATTACH(P) attachInterrupt(P, endstop_ISR, CHANGE) - TERN_(HAS_X_MAX, _ATTACH(X_MAX_PIN)); - TERN_(HAS_X_MIN, _ATTACH(X_MIN_PIN)); - TERN_(HAS_Y_MAX, _ATTACH(Y_MAX_PIN)); - TERN_(HAS_Y_MIN, _ATTACH(Y_MIN_PIN)); - TERN_(HAS_Z_MAX, _ATTACH(Z_MAX_PIN)); - TERN_(HAS_Z_MIN, _ATTACH(Z_MIN_PIN)); - TERN_(HAS_X2_MAX, _ATTACH(X2_MAX_PIN)); - TERN_(HAS_X2_MIN, _ATTACH(X2_MIN_PIN)); - TERN_(HAS_Y2_MAX, _ATTACH(Y2_MAX_PIN)); - TERN_(HAS_Y2_MIN, _ATTACH(Y2_MIN_PIN)); - TERN_(HAS_Z2_MAX, _ATTACH(Z2_MAX_PIN)); - TERN_(HAS_Z2_MIN, _ATTACH(Z2_MIN_PIN)); - TERN_(HAS_Z3_MAX, _ATTACH(Z3_MAX_PIN)); - TERN_(HAS_Z3_MIN, _ATTACH(Z3_MIN_PIN)); - TERN_(HAS_Z4_MAX, _ATTACH(Z4_MAX_PIN)); - TERN_(HAS_Z4_MIN, _ATTACH(Z4_MIN_PIN)); - TERN_(HAS_Z_MIN_PROBE_PIN, _ATTACH(Z_MIN_PROBE_PIN)); + 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/STM32F1/fast_pwm.cpp b/Marlin/src/HAL/STM32F1/fast_pwm.cpp new file mode 100644 index 0000000000..c3f96f0f92 --- /dev/null +++ b/Marlin/src/HAL/STM32F1/fast_pwm.cpp @@ -0,0 +1,85 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2020 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 __STM32F1__ + +#include "../../inc/MarlinConfig.h" + +#include + +#define NR_TIMERS TERN(STM32_XL_DENSITY, 14, 8) // Maple timers, 14 for STM32_XL_DENSITY (F/G chips), 8 for HIGH density (C D E) + +static uint16_t timer_freq[NR_TIMERS]; + +inline uint8_t timer_and_index_for_pin(const pin_t pin, timer_dev **timer_ptr) { + *timer_ptr = PIN_MAP[pin].timer_device; + for (uint8_t i = 0; i < NR_TIMERS; i++) if (*timer_ptr == HAL_get_timer_dev(i)) + return i; + return 0; +} + +void MarlinHAL::set_pwm_duty(const pin_t pin, const uint16_t v, const uint16_t v_size/*=255*/, const bool invert/*=false*/) { + const uint16_t duty = invert ? v_size - v : v; + if (PWM_PIN(pin)) { + timer_dev *timer; + if (timer_freq[timer_and_index_for_pin(pin, &timer)] == 0) + set_pwm_frequency(pin, PWM_FREQUENCY); + const uint8_t channel = PIN_MAP[pin].timer_channel; + timer_set_compare(timer, channel, duty); + timer_set_mode(timer, channel, TIMER_PWM); // PWM Output Mode + } + else { + pinMode(pin, OUTPUT); + digitalWrite(pin, duty < v_size / 2 ? LOW : HIGH); + } +} + +void MarlinHAL::set_pwm_frequency(const pin_t pin, const uint16_t f_desired) { + if (!PWM_PIN(pin)) return; // Don't proceed if no hardware timer + + timer_dev *timer; + timer_freq[timer_and_index_for_pin(pin, &timer)] = f_desired; + + // Protect used timers + if (timer == HAL_get_timer_dev(MF_TIMER_TEMP)) return; + if (timer == HAL_get_timer_dev(MF_TIMER_STEP)) return; + #if MF_TIMER_PULSE != MF_TIMER_STEP + if (timer == HAL_get_timer_dev(MF_TIMER_PULSE)) return; + #endif + + if (!(timer->regs.bas->SR & TIMER_CR1_CEN)) // Ensure the timer is enabled + timer_init(timer); + + const uint8_t channel = PIN_MAP[pin].timer_channel; + timer_set_mode(timer, channel, TIMER_PWM); + // Preload (resolution) cannot be equal to duty of 255 otherwise it may not result in digital off or on. + uint16_t preload = 254; + int32_t prescaler = (HAL_TIMER_RATE) / (preload + 1) / f_desired - 1; + if (prescaler > 65535) { // For low frequencies increase prescaler + prescaler = 65535; + preload = (HAL_TIMER_RATE) / (prescaler + 1) / f_desired - 1; + } + if (prescaler < 0) return; // Too high frequency + timer_set_reload(timer, preload); + timer_set_prescaler(timer, prescaler); +} + +#endif // __STM32F1__ diff --git a/Marlin/src/HAL/STM32F1/fastio.h b/Marlin/src/HAL/STM32F1/fastio.h index e0e2e03a1c..8bb986b466 100644 --- a/Marlin/src/HAL/STM32F1/fastio.h +++ b/Marlin/src/HAL/STM32F1/fastio.h @@ -4,7 +4,6 @@ * * Based on Sprinter and grbl. * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm - * Copyright (c) 2017 Victor Perez * * 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 @@ -29,9 +28,9 @@ #include -#define READ(IO) (PIN_MAP[IO].gpio_device->regs->IDR & (1U << PIN_MAP[IO].gpio_bit) ? HIGH : LOW) -#define WRITE(IO,V) (PIN_MAP[IO].gpio_device->regs->BSRR = (1U << PIN_MAP[IO].gpio_bit) << ((V) ? 0 : 16)) -#define TOGGLE(IO) (PIN_MAP[IO].gpio_device->regs->ODR = PIN_MAP[IO].gpio_device->regs->ODR ^ (1U << PIN_MAP[IO].gpio_bit)) +#define READ(IO) (PIN_MAP[IO].gpio_device->regs->IDR & _BV32(PIN_MAP[IO].gpio_bit) ? HIGH : LOW) +#define WRITE(IO,V) (PIN_MAP[IO].gpio_device->regs->BSRR = _BV32(PIN_MAP[IO].gpio_bit) << ((V) ? 0 : 16)) +#define TOGGLE(IO) TBI32(PIN_MAP[IO].gpio_device->regs->ODR, PIN_MAP[IO].gpio_bit) #define _GET_MODE(IO) gpio_get_mode(PIN_MAP[IO].gpio_device, PIN_MAP[IO].gpio_bit) #define _SET_MODE(IO,M) gpio_set_mode(PIN_MAP[IO].gpio_device, PIN_MAP[IO].gpio_bit, M) @@ -45,13 +44,14 @@ #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) #define IS_INPUT(IO) (_GET_MODE(IO) == GPIO_INPUT_FLOATING || _GET_MODE(IO) == GPIO_INPUT_ANALOG || _GET_MODE(IO) == GPIO_INPUT_PU || _GET_MODE(IO) == GPIO_INPUT_PD) #define IS_OUTPUT(IO) (_GET_MODE(IO) == GPIO_OUTPUT_PP || _GET_MODE(IO) == GPIO_OUTPUT_OD) -#define PWM_PIN(IO) (PIN_MAP[IO].timer_device != nullptr) +#define PWM_PIN(IO) !!PIN_MAP[IO].timer_device // digitalRead/Write wrappers #define extDigitalRead(IO) digitalRead(IO) diff --git a/Marlin/src/HAL/STM32F1/inc/Conditionals_LCD.h b/Marlin/src/HAL/STM32F1/inc/Conditionals_LCD.h index f52e6fec2b..5f1c4b1601 100644 --- a/Marlin/src/HAL/STM32F1/inc/Conditionals_LCD.h +++ b/Marlin/src/HAL/STM32F1/inc/Conditionals_LCD.h @@ -20,8 +20,3 @@ * */ #pragma once - -#if ENABLED(USE_USB_COMPOSITE) - //#warning "SD_CHECK_AND_RETRY isn't needed with USE_USB_COMPOSITE." - #undef SD_CHECK_AND_RETRY -#endif diff --git a/Marlin/src/HAL/STM32F1/inc/Conditionals_adv.h b/Marlin/src/HAL/STM32F1/inc/Conditionals_adv.h index 5f1c4b1601..0fe7924765 100644 --- a/Marlin/src/HAL/STM32F1/inc/Conditionals_adv.h +++ b/Marlin/src/HAL/STM32F1/inc/Conditionals_adv.h @@ -20,3 +20,11 @@ * */ #pragma once + +#ifdef USE_USB_COMPOSITE + //#warning "SD_CHECK_AND_RETRY isn't needed with USE_USB_COMPOSITE." + #undef SD_CHECK_AND_RETRY + #if DISABLED(NO_SD_HOST_DRIVE) + #define HAS_SD_HOST_DRIVE 1 + #endif +#endif diff --git a/Marlin/src/HAL/STM32F1/inc/Conditionals_post.h b/Marlin/src/HAL/STM32F1/inc/Conditionals_post.h index 656fbe1ce2..f130f5cad8 100644 --- a/Marlin/src/HAL/STM32F1/inc/Conditionals_post.h +++ b/Marlin/src/HAL/STM32F1/inc/Conditionals_post.h @@ -24,11 +24,11 @@ // If no real EEPROM, Flash emulation, or SRAM emulation is available fall back to SD emulation #if USE_FALLBACK_EEPROM #define SDCARD_EEPROM_EMULATION -#elif EITHER(I2C_EEPROM, SPI_EEPROM) +#elif ANY(I2C_EEPROM, SPI_EEPROM) #define USE_SHARED_EEPROM 1 #endif -// Allow SDSUPPORT to be disabled -#if DISABLED(SDSUPPORT) - #undef SDIO_SUPPORT +// Allow for no media drives +#if !HAS_MEDIA + #undef ONBOARD_SDIO #endif diff --git a/Marlin/src/HAL/STM32F1/inc/Conditionals_type.h b/Marlin/src/HAL/STM32F1/inc/Conditionals_type.h new file mode 100644 index 0000000000..82f95a1035 --- /dev/null +++ b/Marlin/src/HAL/STM32F1/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/STM32F1/inc/SanityCheck.h b/Marlin/src/HAL/STM32F1/inc/SanityCheck.h index 9d5026fbab..1da42dcc8f 100644 --- a/Marlin/src/HAL/STM32F1/inc/SanityCheck.h +++ b/Marlin/src/HAL/STM32F1/inc/SanityCheck.h @@ -25,16 +25,7 @@ * Test STM32F1-specific configuration values for errors at compile-time. */ -#if ENABLED(FAST_PWM_FAN) || SPINDLE_LASER_FREQUENCY - #error "Features requiring Hardware PWM (FAST_PWM_FAN, SPINDLE_LASER_FREQUENCY) are not yet supported on STM32F1." -#endif - -#if !defined(HAVE_SW_SERIAL) && HAS_TMC_SW_SERIAL - #warning "With TMC2208/9 consider using SoftwareSerialM with HAVE_SW_SERIAL and appropriate SS_TIMER." - #error "Missing SoftwareSerial implementation." -#endif - -#if ENABLED(SDCARD_EEPROM_EMULATION) && DISABLED(SDSUPPORT) +#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." @@ -43,11 +34,18 @@ #endif #if ENABLED(SERIAL_STATS_MAX_RX_QUEUED) - #error "SERIAL_STATS_MAX_RX_QUEUED is not supported on this platform." + #error "SERIAL_STATS_MAX_RX_QUEUED is not supported on the STM32F1 platform." #elif ENABLED(SERIAL_STATS_DROPPED_RX) - #error "SERIAL_STATS_DROPPED_RX is not supported on this platform." + #error "SERIAL_STATS_DROPPED_RX is not supported on the STM32F1 platform." #endif -#if ENABLED(NEOPIXEL_LED) +#if ENABLED(NEOPIXEL_LED) && DISABLED(FYSETC_MINI_12864_2_1) #error "NEOPIXEL_LED (Adafruit NeoPixel) is not supported for HAL/STM32F1. Comment out this line to proceed at your own risk!" #endif + +// Emergency Parser needs at least one serial with HardwareSerial or USBComposite. +// The USBSerial maple don't allow any hook to implement EMERGENCY_PARSER. +// And copy all USBSerial code to marlin space to support EMERGENCY_PARSER, when we have another options, don't worth it. +#if ENABLED(EMERGENCY_PARSER) && !defined(USE_USB_COMPOSITE) && ((SERIAL_PORT == -1 && !defined(SERIAL_PORT_2)) || (SERIAL_PORT_2 == -1 && !defined(SERIAL_PORT))) + #error "EMERGENCY_PARSER is only supported by HardwareSerial or USBComposite in HAL/STM32F1." +#endif diff --git a/Marlin/src/HAL/STM32F1/maple_win_usb_driver/maple_serial.inf b/Marlin/src/HAL/STM32F1/maple_win_usb_driver/maple_serial.inf index c39f4ce0ed..0df30d2c42 100644 --- a/Marlin/src/HAL/STM32F1/maple_win_usb_driver/maple_serial.inf +++ b/Marlin/src/HAL/STM32F1/maple_win_usb_driver/maple_serial.inf @@ -48,7 +48,6 @@ ServiceBinary=%12%\usbser.sys ; String Definitions ;------------------------------------------------------------------------------ - [Strings] STM = "LeafLabs" MFGNAME = "LeafLabs" diff --git a/Marlin/src/HAL/STM32F1/msc_sd.h b/Marlin/src/HAL/STM32F1/msc_sd.h deleted file mode 100644 index 1e4e4c44b1..0000000000 --- a/Marlin/src/HAL/STM32F1/msc_sd.h +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Marlin 3D Printer Firmware - * - * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * Copyright (c) 2019 BigTreeTech [https://github.com/bigtreetech] - * - * 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. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ -#pragma once - -#include - -#include "../../inc/MarlinConfigPre.h" -#if ENABLED(EMERGENCY_PARSER) - #include "../../feature/e_parser.h" -#endif - -class MarlinUSBCompositeSerial : public USBCompositeSerial { -public: - MarlinUSBCompositeSerial() : USBCompositeSerial() - #if ENABLED(EMERGENCY_PARSER) - , emergency_state(EmergencyParser::State::EP_RESET) - #endif - { } - - #if ENABLED(EMERGENCY_PARSER) - EmergencyParser::State emergency_state; - inline bool emergency_parser_enabled() { return true; } - #endif -}; - -extern USBMassStorage MarlinMSC; -extern MarlinUSBCompositeSerial MarlinCompositeSerial; - -void MSC_SD_init(); diff --git a/Marlin/src/HAL/STM32F1/onboard_sd.cpp b/Marlin/src/HAL/STM32F1/onboard_sd.cpp deleted file mode 100644 index 9c2b128ddc..0000000000 --- a/Marlin/src/HAL/STM32F1/onboard_sd.cpp +++ /dev/null @@ -1,558 +0,0 @@ -/** - * STM32F1: MMCv3/SDv1/SDv2 (SPI mode) control module - * - * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * Copyright (c) 2019 BigTreeTech [https://github.com/bigtreetech] - * Copyright (C) 2015, ChaN, all right reserved. - * - * This software is a free software and there is NO WARRANTY. - * No restriction on use. You can use, modify and redistribute it for - * personal, non-profit or commercial products UNDER YOUR RESPONSIBILITY. - * Redistributions of source code must retain the above copyright notice. - */ - -#ifdef __STM32F1__ - -#include "../../inc/MarlinConfig.h" - -#if SD_CONNECTION_IS(ONBOARD) - -#include "onboard_sd.h" -#include "SPI.h" -#include "fastio.h" - -#if HAS_SHARED_MEDIA - #ifndef ONBOARD_SPI_DEVICE - #define ONBOARD_SPI_DEVICE SPI_DEVICE - #endif - #define ONBOARD_SD_SPI SPI -#else - SPIClass OnboardSPI(ONBOARD_SPI_DEVICE); - #define ONBOARD_SD_SPI OnboardSPI -#endif - -#if ONBOARD_SPI_DEVICE == 1 - #define SPI_CLOCK_MAX SPI_BAUD_PCLK_DIV_4 -#else - #define SPI_CLOCK_MAX SPI_BAUD_PCLK_DIV_2 -#endif - -#define CS_LOW() WRITE(ONBOARD_SD_CS_PIN, LOW) /* Set OnboardSPI cs low */ -#define CS_HIGH() WRITE(ONBOARD_SD_CS_PIN, HIGH) /* Set OnboardSPI cs high */ - -#define FCLK_FAST() ONBOARD_SD_SPI.setClockDivider(SPI_CLOCK_MAX) -#define FCLK_SLOW() ONBOARD_SD_SPI.setClockDivider(SPI_BAUD_PCLK_DIV_256) - -/*-------------------------------------------------------------------------- - Module Private Functions ----------------------------------------------------------------------------*/ - -/* MMC/SD command */ -#define CMD0 (0) /* GO_IDLE_STATE */ -#define CMD1 (1) /* SEND_OP_COND (MMC) */ -#define ACMD41 (0x80+41) /* SEND_OP_COND (SDC) */ -#define CMD8 (8) /* SEND_IF_COND */ -#define CMD9 (9) /* SEND_CSD */ -#define CMD10 (10) /* SEND_CID */ -#define CMD12 (12) /* STOP_TRANSMISSION */ -#define ACMD13 (0x80+13) /* SD_STATUS (SDC) */ -#define CMD16 (16) /* SET_BLOCKLEN */ -#define CMD17 (17) /* READ_SINGLE_BLOCK */ -#define CMD18 (18) /* READ_MULTIPLE_BLOCK */ -#define CMD23 (23) /* SET_BLOCK_COUNT (MMC) */ -#define ACMD23 (0x80+23) /* SET_WR_BLK_ERASE_COUNT (SDC) */ -#define CMD24 (24) /* WRITE_BLOCK */ -#define CMD25 (25) /* WRITE_MULTIPLE_BLOCK */ -#define CMD32 (32) /* ERASE_ER_BLK_START */ -#define CMD33 (33) /* ERASE_ER_BLK_END */ -#define CMD38 (38) /* ERASE */ -#define CMD48 (48) /* READ_EXTR_SINGLE */ -#define CMD49 (49) /* WRITE_EXTR_SINGLE */ -#define CMD55 (55) /* APP_CMD */ -#define CMD58 (58) /* READ_OCR */ - -static volatile DSTATUS Stat = STA_NOINIT; /* Physical drive status */ -static volatile UINT timeout; -static BYTE CardType; /* Card type flags */ - -/*-----------------------------------------------------------------------*/ -/* Send/Receive data to the MMC (Platform dependent) */ -/*-----------------------------------------------------------------------*/ - -/* Exchange a byte */ -static BYTE xchg_spi ( - BYTE dat /* Data to send */ -) { - BYTE returnByte = ONBOARD_SD_SPI.transfer(dat); - return returnByte; -} - -/* Receive multiple byte */ -static void rcvr_spi_multi ( - BYTE *buff, /* Pointer to data buffer */ - UINT btr /* Number of bytes to receive (16, 64 or 512) */ -) { - ONBOARD_SD_SPI.dmaTransfer(0, const_cast(buff), btr); -} - -#if _DISKIO_WRITE - - /* Send multiple bytes */ - static void xmit_spi_multi ( - const BYTE *buff, /* Pointer to the data */ - UINT btx /* Number of bytes to send (multiple of 16) */ - ) { - ONBOARD_SD_SPI.dmaSend(const_cast(buff), btx); - } - -#endif // _DISKIO_WRITE - -/*-----------------------------------------------------------------------*/ -/* Wait for card ready */ -/*-----------------------------------------------------------------------*/ - -static int wait_ready ( /* 1:Ready, 0:Timeout */ - UINT wt /* Timeout [ms] */ -) { - BYTE d; - - timeout = millis() + wt; - do { - d = xchg_spi(0xFF); - /* This loop takes a while. Insert rot_rdq() here for multitask environment. */ - } while (d != 0xFF && (timeout > millis())); /* Wait for card goes ready or timeout */ - - return (d == 0xFF) ? 1 : 0; -} - -/*-----------------------------------------------------------------------*/ -/* Deselect card and release SPI */ -/*-----------------------------------------------------------------------*/ - -static void deselect() { - CS_HIGH(); /* CS = H */ - xchg_spi(0xFF); /* Dummy clock (force DO hi-z for multiple slave SPI) */ -} - -/*-----------------------------------------------------------------------*/ -/* Select card and wait for ready */ -/*-----------------------------------------------------------------------*/ - -static int select() { /* 1:OK, 0:Timeout */ - CS_LOW(); /* CS = L */ - xchg_spi(0xFF); /* Dummy clock (force DO enabled) */ - - if (wait_ready(500)) return 1; /* Leading busy check: Wait for card ready */ - - deselect(); /* Timeout */ - return 0; -} - -/*-----------------------------------------------------------------------*/ -/* Control SPI module (Platform dependent) */ -/*-----------------------------------------------------------------------*/ - -static void power_on() { /* Enable SSP module and attach it to I/O pads */ - ONBOARD_SD_SPI.setModule(ONBOARD_SPI_DEVICE); - ONBOARD_SD_SPI.begin(); - ONBOARD_SD_SPI.setBitOrder(MSBFIRST); - ONBOARD_SD_SPI.setDataMode(SPI_MODE0); - OUT_WRITE(ONBOARD_SD_CS_PIN, HIGH); /* Set CS# high */ -} - -static void power_off() { /* Disable SPI function */ - select(); /* Wait for card ready */ - deselect(); -} - -/*-----------------------------------------------------------------------*/ -/* Receive a data packet from the MMC */ -/*-----------------------------------------------------------------------*/ - -static int rcvr_datablock ( /* 1:OK, 0:Error */ - BYTE *buff, /* Data buffer */ - UINT btr /* Data block length (byte) */ -) { - BYTE token; - - timeout = millis() + 200; - do { /* Wait for DataStart token in timeout of 200ms */ - token = xchg_spi(0xFF); - /* This loop will take a while. Insert rot_rdq() here for multitask environment. */ - } while ((token == 0xFF) && (timeout > millis())); - if (token != 0xFE) return 0; /* Function fails if invalid DataStart token or timeout */ - - rcvr_spi_multi(buff, btr); /* Store trailing data to the buffer */ - xchg_spi(0xFF); xchg_spi(0xFF); /* Discard CRC */ - - return 1; /* Function succeeded */ -} - -/*-----------------------------------------------------------------------*/ -/* Send a data packet to the MMC */ -/*-----------------------------------------------------------------------*/ - -#if _DISKIO_WRITE - - static int xmit_datablock ( /* 1:OK, 0:Failed */ - const BYTE *buff, /* Ponter to 512 byte data to be sent */ - BYTE token /* Token */ - ) { - BYTE resp; - - if (!wait_ready(500)) return 0; /* Leading busy check: Wait for card ready to accept data block */ - - xchg_spi(token); /* Send token */ - if (token == 0xFD) return 1; /* Do not send data if token is StopTran */ - - xmit_spi_multi(buff, 512); /* Data */ - xchg_spi(0xFF); xchg_spi(0xFF); /* Dummy CRC */ - - resp = xchg_spi(0xFF); /* Receive data resp */ - - return (resp & 0x1F) == 0x05 ? 1 : 0; /* Data was accepted or not */ - - /* Busy check is done at next transmission */ - } - -#endif // _DISKIO_WRITE - -/*-----------------------------------------------------------------------*/ -/* Send a command packet to the MMC */ -/*-----------------------------------------------------------------------*/ - -static BYTE send_cmd ( /* Return value: R1 resp (bit7==1:Failed to send) */ - BYTE cmd, /* Command index */ - DWORD arg /* Argument */ -) { - BYTE n, res; - - if (cmd & 0x80) { /* Send a CMD55 prior to ACMD */ - cmd &= 0x7F; - res = send_cmd(CMD55, 0); - if (res > 1) return res; - } - - /* Select the card and wait for ready except to stop multiple block read */ - if (cmd != CMD12) { - deselect(); - if (!select()) return 0xFF; - } - - /* Send command packet */ - xchg_spi(0x40 | cmd); /* Start + command index */ - xchg_spi((BYTE)(arg >> 24)); /* Argument[31..24] */ - xchg_spi((BYTE)(arg >> 16)); /* Argument[23..16] */ - xchg_spi((BYTE)(arg >> 8)); /* Argument[15..8] */ - xchg_spi((BYTE)arg); /* Argument[7..0] */ - n = 0x01; /* Dummy CRC + Stop */ - if (cmd == CMD0) n = 0x95; /* Valid CRC for CMD0(0) */ - if (cmd == CMD8) n = 0x87; /* Valid CRC for CMD8(0x1AA) */ - xchg_spi(n); - - /* Receive command resp */ - if (cmd == CMD12) xchg_spi(0xFF); /* Diacard following one byte when CMD12 */ - n = 10; /* Wait for response (10 bytes max) */ - do - res = xchg_spi(0xFF); - while ((res & 0x80) && --n); - - return res; /* Return received response */ -} - -/*-------------------------------------------------------------------------- - Public Functions ----------------------------------------------------------------------------*/ - -/*-----------------------------------------------------------------------*/ -/* Initialize disk drive */ -/*-----------------------------------------------------------------------*/ - -DSTATUS disk_initialize ( - BYTE drv /* Physical drive number (0) */ -) { - BYTE n, cmd, ty, ocr[4]; - - if (drv) return STA_NOINIT; /* Supports only drive 0 */ - power_on(); /* Initialize SPI */ - - if (Stat & STA_NODISK) return Stat; /* Is a card existing in the soket? */ - - FCLK_SLOW(); - for (n = 10; n; n--) xchg_spi(0xFF); /* Send 80 dummy clocks */ - - ty = 0; - if (send_cmd(CMD0, 0) == 1) { /* Put the card SPI state */ - timeout = millis() + 1000; /* Initialization timeout = 1 sec */ - if (send_cmd(CMD8, 0x1AA) == 1) { /* Is the catd SDv2? */ - for (n = 0; n < 4; n++) ocr[n] = xchg_spi(0xFF); /* Get 32 bit return value of R7 resp */ - if (ocr[2] == 0x01 && ocr[3] == 0xAA) { /* Does the card support 2.7-3.6V? */ - while ((timeout > millis()) && send_cmd(ACMD41, 1UL << 30)) ; /* Wait for end of initialization with ACMD41(HCS) */ - if ((timeout > millis()) && send_cmd(CMD58, 0) == 0) { /* Check CCS bit in the OCR */ - for (n = 0; n < 4; n++) ocr[n] = xchg_spi(0xFF); - ty = (ocr[0] & 0x40) ? CT_SD2 | CT_BLOCK : CT_SD2; /* Check if the card is SDv2 */ - } - } - } else { /* Not an SDv2 card */ - if (send_cmd(ACMD41, 0) <= 1) { /* SDv1 or MMCv3? */ - ty = CT_SD1; cmd = ACMD41; /* SDv1 (ACMD41(0)) */ - } else { - ty = CT_MMC; cmd = CMD1; /* MMCv3 (CMD1(0)) */ - } - while ((timeout > millis()) && send_cmd(cmd, 0)) ; /* Wait for the card leaves idle state */ - if (!(timeout > millis()) || send_cmd(CMD16, 512) != 0) /* Set block length: 512 */ - ty = 0; - } - } - CardType = ty; /* Card type */ - deselect(); - - if (ty) { /* OK */ - FCLK_FAST(); /* Set fast clock */ - Stat &= ~STA_NOINIT; /* Clear STA_NOINIT flag */ - } else { /* Failed */ - power_off(); - Stat = STA_NOINIT; - } - - return Stat; -} - -/*-----------------------------------------------------------------------*/ -/* Get disk status */ -/*-----------------------------------------------------------------------*/ - -DSTATUS disk_status ( - BYTE drv /* Physical drive number (0) */ -) { - if (drv) return STA_NOINIT; /* Supports only drive 0 */ - return Stat; /* Return disk status */ -} - -/*-----------------------------------------------------------------------*/ -/* Read sector(s) */ -/*-----------------------------------------------------------------------*/ - -DRESULT disk_read ( - BYTE drv, /* Physical drive number (0) */ - BYTE *buff, /* Pointer to the data buffer to store read data */ - DWORD sector, /* Start sector number (LBA) */ - UINT count /* Number of sectors to read (1..128) */ -) { - BYTE cmd; - - if (drv || !count) return RES_PARERR; /* Check parameter */ - if (Stat & STA_NOINIT) return RES_NOTRDY; /* Check if drive is ready */ - if (!(CardType & CT_BLOCK)) sector *= 512; /* LBA ot BA conversion (byte addressing cards) */ - FCLK_FAST(); - cmd = count > 1 ? CMD18 : CMD17; /* READ_MULTIPLE_BLOCK : READ_SINGLE_BLOCK */ - if (send_cmd(cmd, sector) == 0) { - do { - if (!rcvr_datablock(buff, 512)) break; - buff += 512; - } while (--count); - if (cmd == CMD18) send_cmd(CMD12, 0); /* STOP_TRANSMISSION */ - } - deselect(); - - return count ? RES_ERROR : RES_OK; /* Return result */ -} - -/*-----------------------------------------------------------------------*/ -/* Write sector(s) */ -/*-----------------------------------------------------------------------*/ - -#if _DISKIO_WRITE - - DRESULT disk_write( - BYTE drv, /* Physical drive number (0) */ - const BYTE *buff, /* Ponter to the data to write */ - DWORD sector, /* Start sector number (LBA) */ - UINT count /* Number of sectors to write (1..128) */ - ) { - if (drv || !count) return RES_PARERR; /* Check parameter */ - if (Stat & STA_NOINIT) return RES_NOTRDY; /* Check drive status */ - if (Stat & STA_PROTECT) return RES_WRPRT; /* Check write protect */ - FCLK_FAST(); - if (!(CardType & CT_BLOCK)) sector *= 512; /* LBA ==> BA conversion (byte addressing cards) */ - - if (count == 1) { /* Single sector write */ - if ((send_cmd(CMD24, sector) == 0) /* WRITE_BLOCK */ - && xmit_datablock(buff, 0xFE)) { - count = 0; - } - } - else { /* Multiple sector write */ - if (CardType & CT_SDC) send_cmd(ACMD23, count); /* Predefine number of sectors */ - if (send_cmd(CMD25, sector) == 0) { /* WRITE_MULTIPLE_BLOCK */ - do { - if (!xmit_datablock(buff, 0xFC)) break; - buff += 512; - } while (--count); - if (!xmit_datablock(0, 0xFD)) count = 1; /* STOP_TRAN token */ - } - } - deselect(); - - return count ? RES_ERROR : RES_OK; /* Return result */ - } - -#endif // _DISKIO_WRITE - -/*-----------------------------------------------------------------------*/ -/* Miscellaneous drive controls other than data read/write */ -/*-----------------------------------------------------------------------*/ - -#if _DISKIO_IOCTL - - DRESULT disk_ioctl ( - BYTE drv, /* Physical drive number (0) */ - BYTE cmd, /* Control command code */ - void *buff /* Pointer to the conrtol data */ - ) { - DRESULT res; - BYTE n, csd[16], *ptr = (BYTE *)buff; - DWORD *dp, st, ed, csize; - #if _DISKIO_ISDIO - SDIO_CMD *sdio = buff; - BYTE rc, *buf; - UINT dc; - #endif - - if (drv) return RES_PARERR; /* Check parameter */ - if (Stat & STA_NOINIT) return RES_NOTRDY; /* Check if drive is ready */ - - res = RES_ERROR; - FCLK_FAST(); - switch (cmd) { - case CTRL_SYNC: /* Wait for end of internal write process of the drive */ - if (select()) res = RES_OK; - break; - - case GET_SECTOR_COUNT: /* Get drive capacity in unit of sector (DWORD) */ - if ((send_cmd(CMD9, 0) == 0) && rcvr_datablock(csd, 16)) { - if ((csd[0] >> 6) == 1) { /* SDC ver 2.00 */ - csize = csd[9] + ((WORD)csd[8] << 8) + ((DWORD)(csd[7] & 63) << 16) + 1; - *(DWORD*)buff = csize << 10; - } else { /* SDC ver 1.XX or MMC ver 3 */ - n = (csd[5] & 15) + ((csd[10] & 128) >> 7) + ((csd[9] & 3) << 1) + 2; - csize = (csd[8] >> 6) + ((WORD)csd[7] << 2) + ((WORD)(csd[6] & 3) << 10) + 1; - *(DWORD*)buff = csize << (n - 9); - } - res = RES_OK; - } - break; - - case GET_BLOCK_SIZE: /* Get erase block size in unit of sector (DWORD) */ - if (CardType & CT_SD2) { /* SDC ver 2.00 */ - if (send_cmd(ACMD13, 0) == 0) { /* Read SD status */ - xchg_spi(0xFF); - if (rcvr_datablock(csd, 16)) { /* Read partial block */ - for (n = 64 - 16; n; n--) xchg_spi(0xFF); /* Purge trailing data */ - *(DWORD*)buff = 16UL << (csd[10] >> 4); - res = RES_OK; - } - } - } else { /* SDC ver 1.XX or MMC */ - if ((send_cmd(CMD9, 0) == 0) && rcvr_datablock(csd, 16)) { /* Read CSD */ - if (CardType & CT_SD1) { /* SDC ver 1.XX */ - *(DWORD*)buff = (((csd[10] & 63) << 1) + ((WORD)(csd[11] & 128) >> 7) + 1) << ((csd[13] >> 6) - 1); - } else { /* MMC */ - *(DWORD*)buff = ((WORD)((csd[10] & 124) >> 2) + 1) * (((csd[11] & 3) << 3) + ((csd[11] & 224) >> 5) + 1); - } - res = RES_OK; - } - } - break; - - case CTRL_TRIM: /* Erase a block of sectors (used when _USE_TRIM in ffconf.h is 1) */ - if (!(CardType & CT_SDC)) break; /* Check if the card is SDC */ - if (disk_ioctl(drv, MMC_GET_CSD, csd)) break; /* Get CSD */ - if (!(csd[0] >> 6) && !(csd[10] & 0x40)) break; /* Check if sector erase can be applied to the card */ - dp = (DWORD *)buff; st = dp[0]; ed = dp[1]; /* Load sector block */ - if (!(CardType & CT_BLOCK)) { - st *= 512; ed *= 512; - } - if (send_cmd(CMD32, st) == 0 && send_cmd(CMD33, ed) == 0 && send_cmd(CMD38, 0) == 0 && wait_ready(30000)) { /* Erase sector block */ - res = RES_OK; /* FatFs does not check result of this command */ - } - break; - - /* Following commands are never used by FatFs module */ - - case MMC_GET_TYPE: /* Get MMC/SDC type (BYTE) */ - *ptr = CardType; - res = RES_OK; - break; - - case MMC_GET_CSD: /* Read CSD (16 bytes) */ - if (send_cmd(CMD9, 0) == 0 && rcvr_datablock(ptr, 16)) { /* READ_CSD */ - res = RES_OK; - } - break; - - case MMC_GET_CID: /* Read CID (16 bytes) */ - if (send_cmd(CMD10, 0) == 0 && rcvr_datablock(ptr, 16)) { /* READ_CID */ - res = RES_OK; - } - break; - - case MMC_GET_OCR: /* Read OCR (4 bytes) */ - if (send_cmd(CMD58, 0) == 0) { /* READ_OCR */ - for (n = 4; n; n--) *ptr++ = xchg_spi(0xFF); - res = RES_OK; - } - break; - - case MMC_GET_SDSTAT: /* Read SD status (64 bytes) */ - if (send_cmd(ACMD13, 0) == 0) { /* SD_STATUS */ - xchg_spi(0xFF); - if (rcvr_datablock(ptr, 64)) res = RES_OK; - } - break; - - #if _DISKIO_ISDIO - - case ISDIO_READ: - sdio = buff; - if (send_cmd(CMD48, 0x80000000 | sdio->func << 28 | sdio->addr << 9 | ((sdio->ndata - 1) & 0x1FF)) == 0) { - for (Timer1 = 1000; (rc = xchg_spi(0xFF)) == 0xFF && Timer1; ) ; - if (rc == 0xFE) { - for (buf = sdio->data, dc = sdio->ndata; dc; dc--) *buf++ = xchg_spi(0xFF); - for (dc = 514 - sdio->ndata; dc; dc--) xchg_spi(0xFF); - res = RES_OK; - } - } - break; - case ISDIO_WRITE: - sdio = buff; - if (send_cmd(CMD49, 0x80000000 | sdio->func << 28 | sdio->addr << 9 | ((sdio->ndata - 1) & 0x1FF)) == 0) { - xchg_spi(0xFF); xchg_spi(0xFE); - for (buf = sdio->data, dc = sdio->ndata; dc; dc--) xchg_spi(*buf++); - for (dc = 514 - sdio->ndata; dc; dc--) xchg_spi(0xFF); - if ((xchg_spi(0xFF) & 0x1F) == 0x05) res = RES_OK; - } - break; - case ISDIO_MRITE: - sdio = buff; - if (send_cmd(CMD49, 0x84000000 | sdio->func << 28 | sdio->addr << 9 | sdio->ndata >> 8) == 0) { - xchg_spi(0xFF); xchg_spi(0xFE); - xchg_spi(sdio->ndata); - for (dc = 513; dc; dc--) xchg_spi(0xFF); - if ((xchg_spi(0xFF) & 0x1F) == 0x05) res = RES_OK; - } - break; - - #endif // _DISKIO_ISDIO - - default: res = RES_PARERR; - } - - deselect(); - return res; - } - -#endif // _DISKIO_IOCTL - -#endif // SD_CONNECTION_IS(ONBOARD) -#endif // __STM32F1__ diff --git a/Marlin/src/HAL/STM32F1/pinsDebug.h b/Marlin/src/HAL/STM32F1/pinsDebug.h index 2d63ebd770..9191614587 100644 --- a/Marlin/src/HAL/STM32F1/pinsDebug.h +++ b/Marlin/src/HAL/STM32F1/pinsDebug.h @@ -2,6 +2,9 @@ * Marlin 3D Printer Firmware * Copyright (c) 2020 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,10 +21,113 @@ */ #pragma once -#ifdef NUM_DIGITAL_PINS // Only in ST's Arduino core (STM32duino, STM32Core) - #include "../STM32/pinsDebug_STM32duino.h" -#elif defined(BOARD_NR_GPIO_PINS) // Only in STM32GENERIC (Maple) - #include "../STM32/pinsDebug_STM32GENERIC.h" -#else - #error "M43 not supported for this board" +/** + * 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 + #error "Expected BOARD_NR_GPIO_PINS not found" #endif + +#include "fastio.h" + +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 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 +#ifndef M43_NEVER_TOUCH + #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 isValidPin(pin) ? _GET_MODE(pin) : -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; + #endif + return adc_channel; +} + +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; + #endif + return _GET_MODE(pin) == GPIO_INPUT_ANALOG && !M43_NEVER_TOUCH(pin); + } + return false; +} + +bool getValidPinMode(const pin_t pin) { + return isValidPin(pin) && !IS_INPUT(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 + ); +} + +#include "../../inc/MarlinConfig.h" // Allow pins/pins.h to set density + +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; + const char num = ( + #if ANY(STM32_HIGH_DENSITY, STM32_XL_DENSITY) + tdev == &timer8 ? '8' : + tdev == &timer5 ? '5' : + #endif + tdev == &timer4 ? '4' : + tdev == &timer3 ? '3' : + tdev == &timer2 ? '2' : + tdev == &timer1 ? '1' : '?' + ); + char buffer[10]; + sprintf_P(buffer, PSTR(" TIM%c CH%c"), num, ('0' + channel)); + SERIAL_ECHO(buffer); + } +} + +bool pwm_status(const pin_t pin) { return PWM_PIN(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]; + sprintf_P(buffer, PSTR("P%c%hd "), port, gbit); + if (gbit < 10) SERIAL_CHAR(' '); + SERIAL_ECHO(buffer); +} diff --git a/Marlin/src/HAL/STM32F1/msc_sd.cpp b/Marlin/src/HAL/STM32F1/sd/msc_sd.cpp similarity index 58% rename from Marlin/src/HAL/STM32F1/msc_sd.cpp rename to Marlin/src/HAL/STM32F1/sd/msc_sd.cpp index 4f44f2ee90..5f1ba30c8a 100644 --- a/Marlin/src/HAL/STM32F1/msc_sd.cpp +++ b/Marlin/src/HAL/STM32F1/sd/msc_sd.cpp @@ -1,29 +1,42 @@ /** * Marlin 3D Printer Firmware - * * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] * Copyright (c) 2019 BigTreeTech [https://github.com/bigtreetech] * + * 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 . * */ -#if defined(__STM32F1__) && defined(USE_USB_COMPOSITE) +#ifdef __STM32F1__ + +#include "../../../inc/MarlinConfigPre.h" + +#if HAS_SD_HOST_DRIVE #include "msc_sd.h" -#include "SPI.h" +#include "../SPI.h" + +#include #define PRODUCT_ID 0x29 USBMassStorage MarlinMSC; -MarlinUSBCompositeSerial MarlinCompositeSerial; +Serial1Class MarlinCompositeSerial(true); -#include "../../inc/MarlinConfig.h" +#include "../../../inc/MarlinConfig.h" #if SD_CONNECTION_IS(ONBOARD) @@ -39,14 +52,28 @@ MarlinUSBCompositeSerial MarlinCompositeSerial; #endif #if ENABLED(EMERGENCY_PARSER) - void (*real_rx_callback)(void); - void my_rx_callback(void) { - real_rx_callback(); - int len = MarlinCompositeSerial.available(); - while (len-- > 0) // >0 because available() may return a negative value - emergency_parser.update(MarlinCompositeSerial.emergency_state, MarlinCompositeSerial.peek()); + // The original callback is not called (no way to retrieve address). + // That callback detects a special STM32 reset sequence: this functionality is not essential + // as M997 achieves the same. + void my_rx_callback(unsigned int, void*) { + // max length of 16 is enough to contain all emergency commands + uint8 buf[16]; + + //rx is usbSerialPart.endpoints[2] + uint16 len = usb_get_ep_rx_count(usbSerialPart.endpoints[2].address); + uint32 total = composite_cdcacm_data_available(); + + if (len == 0 || total == 0 || !WITHIN(total, len, COUNT(buf))) + return; + + // cannot get character by character due to bug in composite_cdcacm_peek_ex + len = composite_cdcacm_peek(buf, total); + + for (uint32 i = 0; i < len; i++) + emergency_parser.update(MarlinCompositeSerial.emergency_state, buf[i+total-len]); } + #endif void MSC_SD_init() { @@ -71,10 +98,9 @@ void MSC_SD_init() { MarlinCompositeSerial.registerComponent(); USBComposite.begin(); #if ENABLED(EMERGENCY_PARSER) - //rx is usbSerialPart.endpoints[2] - real_rx_callback = usbSerialPart.endpoints[2].callback; - usbSerialPart.endpoints[2].callback = my_rx_callback; + composite_cdcacm_set_hooks(USBHID_CDCACM_HOOK_RX, my_rx_callback); #endif } -#endif // __STM32F1__ && USE_USB_COMPOSITE +#endif // HAS_SD_HOST_DRIVE +#endif // __STM32F1__ diff --git a/Marlin/src/HAL/AVR/watchdog.h b/Marlin/src/HAL/STM32F1/sd/msc_sd.h similarity index 75% rename from Marlin/src/HAL/AVR/watchdog.h rename to Marlin/src/HAL/STM32F1/sd/msc_sd.h index a16c88b35e..871d98eaf2 100644 --- a/Marlin/src/HAL/AVR/watchdog.h +++ b/Marlin/src/HAL/STM32F1/sd/msc_sd.h @@ -1,6 +1,7 @@ /** * Marlin 3D Printer Firmware * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] + * Copyright (c) 2019 BigTreeTech [https://github.com/bigtreetech] * * Based on Sprinter and grbl. * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm @@ -21,11 +22,12 @@ */ #pragma once -#include +#include -// Initialize watchdog with a 4 second interrupt time -void watchdog_init(); +#include "../../../inc/MarlinConfigPre.h" +#include "../../../core/serial_hook.h" -// Reset watchdog. MUST be called at least every 4 seconds after the -// first watchdog_init or AVR will go into emergency procedures. -inline void HAL_watchdog_refresh() { wdt_reset(); } +extern USBMassStorage MarlinMSC; +extern Serial1Class MarlinCompositeSerial; + +void MSC_SD_init(); diff --git a/Marlin/src/HAL/STM32F1/sd/onboard_sd.cpp b/Marlin/src/HAL/STM32F1/sd/onboard_sd.cpp new file mode 100644 index 0000000000..ace1204b75 --- /dev/null +++ b/Marlin/src/HAL/STM32F1/sd/onboard_sd.cpp @@ -0,0 +1,571 @@ +/** + * STM32F1: MMCv3/SDv1/SDv2 (SPI mode) control module + * + * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] + * Copyright (c) 2019 BigTreeTech [https://github.com/bigtreetech] + * Copyright (C) 2015, ChaN, all right reserved. + * + * This software is a free software and there is NO WARRANTY. + * No restriction on use. You can use, modify and redistribute it for + * personal, non-profit or commercial products UNDER YOUR RESPONSIBILITY. + * Redistributions of source code must retain the above copyright notice. + */ + +#ifdef __STM32F1__ + +#include "../../../inc/MarlinConfig.h" + +#if SD_CONNECTION_IS(ONBOARD) + +#include "onboard_sd.h" +#include "../SPI.h" +#include "../fastio.h" + +#ifndef ONBOARD_SPI_DEVICE + #define ONBOARD_SPI_DEVICE SPI_DEVICE +#endif + +#if HAS_SD_HOST_DRIVE + #define ONBOARD_SD_SPI SPI +#else + SPIClass OnboardSPI(ONBOARD_SPI_DEVICE); + #define ONBOARD_SD_SPI OnboardSPI +#endif + +#if ONBOARD_SPI_DEVICE == 1 + #define SPI_CLOCK_MAX SPI_BAUD_PCLK_DIV_4 +#else + #define SPI_CLOCK_MAX SPI_BAUD_PCLK_DIV_2 +#endif + +#if PIN_EXISTS(ONBOARD_SD_CS) && ONBOARD_SD_CS_PIN != SD_SS_PIN + #define CS_LOW() WRITE(ONBOARD_SD_CS_PIN, LOW) // Set OnboardSPI cs low + #define CS_HIGH() WRITE(ONBOARD_SD_CS_PIN, HIGH) // Set OnboardSPI cs high +#else + #define CS_LOW() + #define CS_HIGH() +#endif + +#define FCLK_FAST() ONBOARD_SD_SPI.setClockDivider(SPI_CLOCK_MAX) +#define FCLK_SLOW() ONBOARD_SD_SPI.setClockDivider(SPI_BAUD_PCLK_DIV_256) + +/*-------------------------------------------------------------------------- + Module Private Functions +---------------------------------------------------------------------------*/ + +/* MMC/SD command */ +#define CMD0 (0) // GO_IDLE_STATE +#define CMD1 (1) // SEND_OP_COND (MMC) +#define ACMD41 (0x80+41) // SEND_OP_COND (SDC) +#define CMD8 (8) // SEND_IF_COND +#define CMD9 (9) // SEND_CSD +#define CMD10 (10) // SEND_CID +#define CMD12 (12) // STOP_TRANSMISSION +#define ACMD13 (0x80+13) // SD_STATUS (SDC) +#define CMD16 (16) // SET_BLOCKLEN +#define CMD17 (17) // READ_SINGLE_BLOCK +#define CMD18 (18) // READ_MULTIPLE_BLOCK +#define CMD23 (23) // SET_BLOCK_COUNT (MMC) +#define ACMD23 (0x80+23) // SET_WR_BLK_ERASE_COUNT (SDC) +#define CMD24 (24) // WRITE_BLOCK +#define CMD25 (25) // WRITE_MULTIPLE_BLOCK +#define CMD32 (32) // ERASE_ER_BLK_START +#define CMD33 (33) // ERASE_ER_BLK_END +#define CMD38 (38) // ERASE +#define CMD48 (48) // READ_EXTR_SINGLE +#define CMD49 (49) // WRITE_EXTR_SINGLE +#define CMD55 (55) // APP_CMD +#define CMD58 (58) // READ_OCR + +static volatile DSTATUS Stat = STA_NOINIT; // Physical drive status +static volatile UINT timeout; +static BYTE CardType; // Card type flags + +/*-----------------------------------------------------------------------*/ +/* Send/Receive data to the MMC (Platform dependent) */ +/*-----------------------------------------------------------------------*/ + +/* Exchange a byte */ +static BYTE xchg_spi ( + BYTE dat // Data to send +) { + BYTE returnByte = ONBOARD_SD_SPI.transfer(dat); + return returnByte; +} + +/* Receive multiple byte */ +static void rcvr_spi_multi ( + BYTE *buff, // Pointer to data buffer + UINT btr // Number of bytes to receive (16, 64 or 512) +) { + ONBOARD_SD_SPI.dmaTransfer(0, const_cast(buff), btr); +} + +#if _DISKIO_WRITE + + // Send multiple bytes + static void xmit_spi_multi ( + const BYTE *buff, // Pointer to the data + UINT btx // Number of bytes to send (multiple of 16) + ) { + ONBOARD_SD_SPI.dmaSend(const_cast(buff), btx); + } + +#endif // _DISKIO_WRITE + +/*-----------------------------------------------------------------------*/ +/* Wait for card ready */ +/*-----------------------------------------------------------------------*/ + +static int wait_ready ( // 1:Ready, 0:Timeout + UINT wt // Timeout [ms] +) { + BYTE d; + timeout = millis() + wt; + do { + d = xchg_spi(0xFF); + // This loop takes a while. Insert rot_rdq() here for multitask environment. + } while (d != 0xFF && (timeout > millis())); // Wait for card goes ready or timeout + + return (d == 0xFF) ? 1 : 0; +} + +/*-----------------------------------------------------------------------*/ +/* Deselect card and release SPI */ +/*-----------------------------------------------------------------------*/ + +static void deselect() { + CS_HIGH(); // CS = H + xchg_spi(0xFF); // Dummy clock (force DO hi-z for multiple slave SPI) +} + +/*-----------------------------------------------------------------------*/ +/* Select card and wait for ready */ +/*-----------------------------------------------------------------------*/ + +static int select() { // 1:OK, 0:Timeout + CS_LOW(); // CS = L + xchg_spi(0xFF); // Dummy clock (force DO enabled) + + if (wait_ready(500)) return 1; // Leading busy check: Wait for card ready + + deselect(); // Timeout + return 0; +} + +/*-----------------------------------------------------------------------*/ +/* Control SPI module (Platform dependent) */ +/*-----------------------------------------------------------------------*/ + +// Enable SSP module and attach it to I/O pads +static void sd_power_on() { + ONBOARD_SD_SPI.setModule(ONBOARD_SPI_DEVICE); + ONBOARD_SD_SPI.begin(); + ONBOARD_SD_SPI.setBitOrder(MSBFIRST); + ONBOARD_SD_SPI.setDataMode(SPI_MODE0); + CS_HIGH(); +} + +// Disable SPI function +static void sd_power_off() { + select(); // Wait for card ready + deselect(); +} + +/*-----------------------------------------------------------------------*/ +/* Receive a data packet from the MMC */ +/*-----------------------------------------------------------------------*/ + +static int rcvr_datablock ( // 1:OK, 0:Error + BYTE *buff, // Data buffer + UINT btr // Data block length (byte) +) { + BYTE token; + + timeout = millis() + 200; + do { // Wait for DataStart token in timeout of 200ms + token = xchg_spi(0xFF); + // This loop will take a while. Insert rot_rdq() here for multitask environment. + } while ((token == 0xFF) && (timeout > millis())); + if (token != 0xFE) return 0; // Function fails if invalid DataStart token or timeout + + rcvr_spi_multi(buff, btr); // Store trailing data to the buffer + xchg_spi(0xFF); xchg_spi(0xFF); // Discard CRC + + return 1; // Function succeeded +} + +/*-----------------------------------------------------------------------*/ +/* Send a data packet to the MMC */ +/*-----------------------------------------------------------------------*/ + +#if _DISKIO_WRITE + + static int xmit_datablock( // 1:OK, 0:Failed + const BYTE *buff, // Pointer to 512 byte data to be sent + BYTE token // Token + ) { + BYTE resp; + + if (!wait_ready(500)) return 0; // Leading busy check: Wait for card ready to accept data block + + xchg_spi(token); // Send token + if (token == 0xFD) return 1; // Do not send data if token is StopTran + + xmit_spi_multi(buff, 512); // Data + xchg_spi(0xFF); xchg_spi(0xFF); // Dummy CRC + + resp = xchg_spi(0xFF); // Receive data resp + + return (resp & 0x1F) == 0x05 ? 1 : 0; // Data was accepted or not + + // Busy check is done at next transmission + } + +#endif // _DISKIO_WRITE + +/*-----------------------------------------------------------------------*/ +/* Send a command packet to the MMC */ +/*-----------------------------------------------------------------------*/ + +static BYTE send_cmd( // Return value: R1 resp (bit7==1:Failed to send) + BYTE cmd, // Command index + DWORD arg // Argument +) { + BYTE n, res; + + if (cmd & 0x80) { // Send a CMD55 prior to ACMD + cmd &= 0x7F; + res = send_cmd(CMD55, 0); + if (res > 1) return res; + } + + // Select the card and wait for ready except to stop multiple block read + if (cmd != CMD12) { + deselect(); + if (!select()) return 0xFF; + } + + // Send command packet + xchg_spi(0x40 | cmd); // Start + command index + xchg_spi((BYTE)(arg >> 24)); // Argument[31..24] + xchg_spi((BYTE)(arg >> 16)); // Argument[23..16] + xchg_spi((BYTE)(arg >> 8)); // Argument[15..8] + xchg_spi((BYTE)arg); // Argument[7..0] + n = 0x01; // Dummy CRC + Stop + if (cmd == CMD0) n = 0x95; // Valid CRC for CMD0(0) + if (cmd == CMD8) n = 0x87; // Valid CRC for CMD8(0x1AA) + xchg_spi(n); + + // Receive command response + if (cmd == CMD12) xchg_spi(0xFF); // Discard the following byte when CMD12 + n = 10; // Wait for response (10 bytes max) + do + res = xchg_spi(0xFF); + while ((res & 0x80) && --n); + + return res; // Return received response +} + +/*-------------------------------------------------------------------------- + Public Functions +---------------------------------------------------------------------------*/ + +/*-----------------------------------------------------------------------*/ +/* Initialize disk drive */ +/*-----------------------------------------------------------------------*/ + +DSTATUS disk_initialize ( + BYTE drv // Physical drive number (0) +) { + BYTE n, cmd, ty, ocr[4]; + + if (drv) return STA_NOINIT; // Supports only drive 0 + sd_power_on(); // Initialize SPI + + if (Stat & STA_NODISK) return Stat; // Is a card existing in the socket? + + FCLK_SLOW(); + for (n = 10; n; n--) xchg_spi(0xFF); // Send 80 dummy clocks + + ty = 0; + if (send_cmd(CMD0, 0) == 1) { // Put the card SPI state + timeout = millis() + 1000; // Initialization timeout = 1 sec + if (send_cmd(CMD8, 0x1AA) == 1) { // Is the catd SDv2? + for (n = 0; n < 4; n++) ocr[n] = xchg_spi(0xFF); // Get 32 bit return value of R7 resp + if (ocr[2] == 0x01 && ocr[3] == 0xAA) { // Does the card support 2.7-3.6V? + while ((timeout > millis()) && send_cmd(ACMD41, 1UL << 30)); // Wait for end of initialization with ACMD41(HCS) + if ((timeout > millis()) && send_cmd(CMD58, 0) == 0) { // Check CCS bit in the OCR + for (n = 0; n < 4; n++) ocr[n] = xchg_spi(0xFF); + ty = (ocr[0] & 0x40) ? CT_SD2 | CT_BLOCK : CT_SD2; // Check if the card is SDv2 + } + } + } + else { // Not an SDv2 card + if (send_cmd(ACMD41, 0) <= 1) { // SDv1 or MMCv3? + ty = CT_SD1; cmd = ACMD41; // SDv1 (ACMD41(0)) + } + else { + ty = CT_MMC; cmd = CMD1; // MMCv3 (CMD1(0)) + } + while ((timeout > millis()) && send_cmd(cmd, 0)); // Wait for the card leaves idle state + if (!(timeout > millis()) || send_cmd(CMD16, 512) != 0) // Set block length: 512 + ty = 0; + } + } + CardType = ty; // Card type + deselect(); + + if (ty) { // OK + FCLK_FAST(); // Set fast clock + Stat &= ~STA_NOINIT; // Clear STA_NOINIT flag + } + else { // Failed + sd_power_off(); + Stat = STA_NOINIT; + } + + return Stat; +} + +/*-----------------------------------------------------------------------*/ +/* Get disk status */ +/*-----------------------------------------------------------------------*/ + +DSTATUS disk_status ( + BYTE drv // Physical drive number (0) +) { + if (drv) return STA_NOINIT; // Supports only drive 0 + return Stat; // Return disk status +} + +/*-----------------------------------------------------------------------*/ +/* Read sector(s) */ +/*-----------------------------------------------------------------------*/ + +DRESULT disk_read ( + BYTE drv, // Physical drive number (0) + BYTE *buff, // Pointer to the data buffer to store read data + DWORD sector, // Start sector number (LBA) + UINT count // Number of sectors to read (1..128) +) { + BYTE cmd; + + if (drv || !count) return RES_PARERR; // Check parameter + if (Stat & STA_NOINIT) return RES_NOTRDY; // Check if drive is ready + if (!(CardType & CT_BLOCK)) sector *= 512; // LBA ot BA conversion (byte addressing cards) + FCLK_FAST(); + cmd = count > 1 ? CMD18 : CMD17; // READ_MULTIPLE_BLOCK : READ_SINGLE_BLOCK + if (send_cmd(cmd, sector) == 0) { + do { + if (!rcvr_datablock(buff, 512)) break; + buff += 512; + } while (--count); + if (cmd == CMD18) send_cmd(CMD12, 0); // STOP_TRANSMISSION + } + deselect(); + + return count ? RES_ERROR : RES_OK; // Return result +} + +/*-----------------------------------------------------------------------*/ +/* Write sector(s) */ +/*-----------------------------------------------------------------------*/ + +#if _DISKIO_WRITE + + DRESULT disk_write( + BYTE drv, // Physical drive number (0) + const BYTE *buff, // Pointer to the data to write + DWORD sector, // Start sector number (LBA) + UINT count // Number of sectors to write (1..128) + ) { + if (drv || !count) return RES_PARERR; // Check parameter + if (Stat & STA_NOINIT) return RES_NOTRDY; // Check drive status + if (Stat & STA_PROTECT) return RES_WRPRT; // Check write protect + FCLK_FAST(); + if (!(CardType & CT_BLOCK)) sector *= 512; // LBA ==> BA conversion (byte addressing cards) + + if (count == 1) { // Single sector write + if ((send_cmd(CMD24, sector) == 0) // WRITE_BLOCK + && xmit_datablock(buff, 0xFE)) { + count = 0; + } + } + else { // Multiple sector write + if (CardType & CT_SDC) send_cmd(ACMD23, count); // Predefine number of sectors + if (send_cmd(CMD25, sector) == 0) { // WRITE_MULTIPLE_BLOCK + do { + if (!xmit_datablock(buff, 0xFC)) break; + buff += 512; + } while (--count); + if (!xmit_datablock(0, 0xFD)) count = 1; // STOP_TRAN token + } + } + deselect(); + + return count ? RES_ERROR : RES_OK; // Return result + } + +#endif // _DISKIO_WRITE + +/*-----------------------------------------------------------------------*/ +/* Miscellaneous drive controls other than data read/write */ +/*-----------------------------------------------------------------------*/ + +#if _DISKIO_IOCTL + + DRESULT disk_ioctl ( + BYTE drv, // Physical drive number (0) + BYTE cmd, // Control command code + void *buff // Pointer to the conrtol data + ) { + DRESULT res; + BYTE n, csd[16], *ptr = (BYTE *)buff; + DWORD *dp, st, ed, csize; + #if _DISKIO_ISDIO + SDIO_CMD *sdio = buff; + BYTE rc, *buf; + UINT dc; + #endif + + if (drv) return RES_PARERR; // Check parameter + if (Stat & STA_NOINIT) return RES_NOTRDY; // Check if drive is ready + + res = RES_ERROR; + FCLK_FAST(); + switch (cmd) { + case CTRL_SYNC: // Wait for end of internal write process of the drive + if (select()) res = RES_OK; + break; + + case GET_SECTOR_COUNT: // Get drive capacity in unit of sector (DWORD) + if ((send_cmd(CMD9, 0) == 0) && rcvr_datablock(csd, 16)) { + if ((csd[0] >> 6) == 1) { // SDC ver 2.00 + csize = csd[9] + ((WORD)csd[8] << 8) + ((DWORD)(csd[7] & 63) << 16) + 1; + *(DWORD*)buff = csize << 10; + } + else { // SDC ver 1.XX or MMC ver 3 + n = (csd[5] & 15) + ((csd[10] & 128) >> 7) + ((csd[9] & 3) << 1) + 2; + csize = (csd[8] >> 6) + ((WORD)csd[7] << 2) + ((WORD)(csd[6] & 3) << 10) + 1; + *(DWORD*)buff = csize << (n - 9); + } + res = RES_OK; + } + break; + + case GET_BLOCK_SIZE: // Get erase block size in unit of sector (DWORD) + if (CardType & CT_SD2) { // SDC ver 2.00 + if (send_cmd(ACMD13, 0) == 0) { // Read SD status + xchg_spi(0xFF); + if (rcvr_datablock(csd, 16)) { // Read partial block + for (n = 64 - 16; n; n--) xchg_spi(0xFF); // Purge trailing data + *(DWORD*)buff = 16UL << (csd[10] >> 4); + res = RES_OK; + } + } + } + else { // SDC ver 1.XX or MMC + if ((send_cmd(CMD9, 0) == 0) && rcvr_datablock(csd, 16)) { // Read CSD + if (CardType & CT_SD1) { // SDC ver 1.XX + *(DWORD*)buff = (((csd[10] & 63) << 1) + ((WORD)(csd[11] & 128) >> 7) + 1) << ((csd[13] >> 6) - 1); + } + else { // MMC + *(DWORD*)buff = ((WORD)((csd[10] & 124) >> 2) + 1) * (((csd[11] & 3) << 3) + ((csd[11] & 224) >> 5) + 1); + } + res = RES_OK; + } + } + break; + + case CTRL_TRIM: // Erase a block of sectors (used when _USE_TRIM in ffconf.h is 1) + if (!(CardType & CT_SDC)) break; // Check if the card is SDC + if (disk_ioctl(drv, MMC_GET_CSD, csd)) break; // Get CSD + if (!(csd[0] >> 6) && !(csd[10] & 0x40)) break; // Check if sector erase can be applied to the card + dp = (DWORD *)buff; st = dp[0]; ed = dp[1]; // Load sector block + if (!(CardType & CT_BLOCK)) { + st *= 512; ed *= 512; + } + if (send_cmd(CMD32, st) == 0 && send_cmd(CMD33, ed) == 0 && send_cmd(CMD38, 0) == 0 && wait_ready(30000)) { // Erase sector block + res = RES_OK; // FatFs does not check result of this command + } + break; + + // The following commands are never used by FatFs module + + case MMC_GET_TYPE: // Get MMC/SDC type (BYTE) + *ptr = CardType; + res = RES_OK; + break; + + case MMC_GET_CSD: // Read CSD (16 bytes) + if (send_cmd(CMD9, 0) == 0 && rcvr_datablock(ptr, 16)) { + res = RES_OK; + } + break; + + case MMC_GET_CID: // Read CID (16 bytes) + if (send_cmd(CMD10, 0) == 0 && rcvr_datablock(ptr, 16)) { + res = RES_OK; + } + break; + + case MMC_GET_OCR: // Read OCR (4 bytes) + if (send_cmd(CMD58, 0) == 0) { + for (n = 4; n; n--) *ptr++ = xchg_spi(0xFF); + res = RES_OK; + } + break; + + case MMC_GET_SDSTAT: // Read SD status (64 bytes) + if (send_cmd(ACMD13, 0) == 0) { + xchg_spi(0xFF); + if (rcvr_datablock(ptr, 64)) res = RES_OK; + } + break; + + #if _DISKIO_ISDIO + + case ISDIO_READ: + sdio = buff; + if (send_cmd(CMD48, 0x80000000 | sdio->func << 28 | sdio->addr << 9 | ((sdio->ndata - 1) & 0x1FF)) == 0) { + for (Timer1 = 1000; (rc = xchg_spi(0xFF)) == 0xFF && Timer1; ) ; + if (rc == 0xFE) { + for (buf = sdio->data, dc = sdio->ndata; dc; dc--) *buf++ = xchg_spi(0xFF); + for (dc = 514 - sdio->ndata; dc; dc--) xchg_spi(0xFF); + res = RES_OK; + } + } + break; + case ISDIO_WRITE: + sdio = buff; + if (send_cmd(CMD49, 0x80000000 | sdio->func << 28 | sdio->addr << 9 | ((sdio->ndata - 1) & 0x1FF)) == 0) { + xchg_spi(0xFF); xchg_spi(0xFE); + for (buf = sdio->data, dc = sdio->ndata; dc; dc--) xchg_spi(*buf++); + for (dc = 514 - sdio->ndata; dc; dc--) xchg_spi(0xFF); + if ((xchg_spi(0xFF) & 0x1F) == 0x05) res = RES_OK; + } + break; + case ISDIO_MRITE: + sdio = buff; + if (send_cmd(CMD49, 0x84000000 | sdio->func << 28 | sdio->addr << 9 | sdio->ndata >> 8) == 0) { + xchg_spi(0xFF); xchg_spi(0xFE); + xchg_spi(sdio->ndata); + for (dc = 513; dc; dc--) xchg_spi(0xFF); + if ((xchg_spi(0xFF) & 0x1F) == 0x05) res = RES_OK; + } + break; + + #endif // _DISKIO_ISDIO + + default: res = RES_PARERR; + } + + deselect(); + return res; + } + +#endif // _DISKIO_IOCTL + +#endif // SD_CONNECTION_IS(ONBOARD) +#endif // __STM32F1__ diff --git a/Marlin/src/HAL/STM32F1/onboard_sd.h b/Marlin/src/HAL/STM32F1/sd/onboard_sd.h similarity index 75% rename from Marlin/src/HAL/STM32F1/onboard_sd.h rename to Marlin/src/HAL/STM32F1/sd/onboard_sd.h index 1dc7ec5b3b..be1d1d0a6b 100644 --- a/Marlin/src/HAL/STM32F1/onboard_sd.h +++ b/Marlin/src/HAL/STM32F1/sd/onboard_sd.h @@ -1,14 +1,34 @@ -/*----------------------------------------------------------------------- -/ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] -/ * Copyright (c) 2019 BigTreeTech [https://github.com/bigtreetech] -/ * Low level disk interface module include file (C)ChaN, 2015 -/-----------------------------------------------------------------------*/ - +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2020 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 +/*----------------------------------------------------------------------- +/ * Copyright (c) 2019 BigTreeTech [https://github.com/bigtreetech] +/ * Low level disk interface module include file (c) ChaN, 2015 +/-----------------------------------------------------------------------*/ + #define _DISKIO_WRITE 1 /* 1: Enable disk_write function */ -#define _DISKIO_IOCTL 1 /* 1: Enable disk_ioctl fucntion */ -#define _DISKIO_ISDIO 0 /* 1: Enable iSDIO control fucntion */ +#define _DISKIO_IOCTL 1 /* 1: Enable disk_ioctl function */ +#define _DISKIO_ISDIO 0 /* 1: Enable iSDIO control function */ typedef unsigned char BYTE; typedef unsigned short WORD; @@ -27,7 +47,6 @@ typedef enum { RES_PARERR /* 4: Invalid Parameter */ } DRESULT; - #if _DISKIO_ISDIO /* Command structure for iSDIO ioctl command */ typedef struct { @@ -48,7 +67,7 @@ DRESULT disk_read(BYTE pdrv, BYTE* buff, DWORD sector, UINT count); DRESULT disk_write(BYTE pdrv, const BYTE* buff, DWORD sector, UINT count); #endif #if _DISKIO_IOCTL - DRESULT disk_ioctl(BYTE pdrv, BYTE cmd, void* buff); + DRESULT disk_ioctl(BYTE pdrv, BYTE cmd, void *buff); #endif /* Disk Status Bits (DSTATUS) */ @@ -56,7 +75,7 @@ DRESULT disk_read(BYTE pdrv, BYTE* buff, DWORD sector, UINT count); #define STA_NODISK 0x02 /* No medium in the drive */ #define STA_PROTECT 0x04 /* Write protected */ -/* Command code for disk_ioctrl fucntion */ +/* Command code for disk_ioctrl function */ /* Generic command (Used by FatFs) */ #define CTRL_SYNC 0 /* Complete pending write process (needed at _FS_READONLY == 0) */ diff --git a/Marlin/src/HAL/STM32F1/sdio.cpp b/Marlin/src/HAL/STM32F1/sd/sdio.cpp similarity index 94% rename from Marlin/src/HAL/STM32F1/sdio.cpp rename to Marlin/src/HAL/STM32F1/sd/sdio.cpp index ffa6db1206..d657fd3b0e 100644 --- a/Marlin/src/HAL/STM32F1/sdio.cpp +++ b/Marlin/src/HAL/STM32F1/sd/sdio.cpp @@ -4,7 +4,6 @@ * * Based on Sprinter and grbl. * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm - * Copyright (c) 2017 Victor Perez * * 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 @@ -20,13 +19,13 @@ * 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 EITHER(STM32_HIGH_DENSITY, STM32_XL_DENSITY) +#if ANY(STM32_HIGH_DENSITY, STM32_XL_DENSITY) #include "sdio.h" @@ -136,12 +135,17 @@ bool SDIO_ReadBlock_DMA(uint32_t blockAddress, uint8_t *data) { } bool SDIO_ReadBlock(uint32_t blockAddress, uint8_t *data) { - uint32_t retries = SDIO_READ_RETRIES; - while (retries--) if (SDIO_ReadBlock_DMA(blockAddress, data)) return true; + uint8_t retries = SDIO_READ_RETRIES; + while (retries--) { + if (SDIO_ReadBlock_DMA(blockAddress, data)) return true; + #if SD_RETRY_DELAY_MS + delay(SD_RETRY_DELAY_MS); + #endif + } 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; @@ -184,6 +188,10 @@ bool SDIO_WriteBlock(uint32_t blockAddress, const uint8_t *data) { inline uint32_t SDIO_GetCardState() { return SDIO_CmdSendStatus(SdCard.RelCardAdd << 16U) ? (SDIO_GetResponse(SDIO_RESP1) >> 9U) & 0x0FU : SDIO_CARD_ERROR; } +// No F1 board with SDIO + MSC using Maple, that I aware of... +bool SDIO_IsReady() { return true; } +uint32_t SDIO_GetCardSize() { return 0; } + // ------------------------ // SD Commands and Responses // ------------------------ @@ -211,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(); } @@ -300,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 97% rename from Marlin/src/HAL/STM32F1/sdio.h rename to Marlin/src/HAL/STM32F1/sd/sdio.h index 8777299f01..ffcd354915 100644 --- a/Marlin/src/HAL/STM32F1/sdio.h +++ b/Marlin/src/HAL/STM32F1/sd/sdio.h @@ -1,9 +1,9 @@ /** * Marlin 3D Printer Firmware - * * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com - * Copyright (c) 2017 Victor Perez + * + * 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 @@ -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 @@ -60,7 +60,6 @@ #define ACMD41_SD_APP_OP_COND (uint16_t)(SDMMC_ACMD_SD_APP_OP_COND | SDIO_CMD_WAIT_SHORT_RESP) #define ACMD42_SD_APP_SET_CLR_CARD_DETECT (uint16_t)(SDMMC_ACMD_SD_APP_SET_CLR_CARD_DETECT | SDIO_CMD_WAIT_SHORT_RESP) - #define SDMMC_ALLZERO 0x00000000U #define SDMMC_OCR_ERRORBITS 0xFDFFE008U diff --git a/Marlin/src/HAL/STM32F1/spi_pins.h b/Marlin/src/HAL/STM32F1/spi_pins.h index 8f2b324f64..d9c5e1f8c6 100644 --- a/Marlin/src/HAL/STM32F1/spi_pins.h +++ b/Marlin/src/HAL/STM32F1/spi_pins.h @@ -2,6 +2,9 @@ * Marlin 3D Printer Firmware * Copyright (c) 2020 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 @@ -31,28 +34,19 @@ * SPI2 | PB12 PB13 PB14 PB15 | * SPI3 | PA15 PB3 PB4 PB5 | * +-----------------------------+ - * Any pin can be used for Chip Select (SS_PIN) + * Any pin can be used for Chip Select (SD_SS_PIN) * SPI1 is enabled by default */ -#ifndef SCK_PIN - #define SCK_PIN PA5 +#ifndef SD_SCK_PIN + #define SD_SCK_PIN PA5 #endif -#ifndef MISO_PIN - #define MISO_PIN PA6 +#ifndef SD_MISO_PIN + #define SD_MISO_PIN PA6 #endif -#ifndef MOSI_PIN - #define MOSI_PIN PA7 +#ifndef SD_MOSI_PIN + #define SD_MOSI_PIN PA7 #endif -#ifndef SS_PIN - #define SS_PIN PA4 -#endif -#undef SDSS -#define SDSS SS_PIN -#if ENABLED(ENABLE_SPI3) - #define SPI_DEVICE 3 -#elif ENABLED(ENABLE_SPI2) - #define SPI_DEVICE 2 -#else +#ifndef SPI_DEVICE #define SPI_DEVICE 1 #endif diff --git a/Marlin/src/HAL/STM32F1/tft/tft_fsmc.cpp b/Marlin/src/HAL/STM32F1/tft/tft_fsmc.cpp index 5b52fb416f..73b0e7efb9 100644 --- a/Marlin/src/HAL/STM32F1/tft/tft_fsmc.cpp +++ b/Marlin/src/HAL/STM32F1/tft/tft_fsmc.cpp @@ -20,6 +20,8 @@ * */ +#ifdef __STM32F1__ + #include "../../../inc/MarlinConfig.h" #if HAS_FSMC_TFT @@ -84,8 +86,7 @@ __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() { +void TFT_FSMC::init() { uint8_t cs = FSMC_CS_PIN, rs = FSMC_RS_PIN; uint32_t controllerAddress; @@ -97,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; @@ -179,35 +181,35 @@ void TFT_FSMC::Init() { LCD = (LCD_CONTROLLER_TypeDef*)controllerAddress; } -void TFT_FSMC::Transmit(uint16_t Data) { - LCD->RAM = Data; +void TFT_FSMC::transmit(uint16_t data) { + LCD->RAM = data; __DSB(); } -void TFT_FSMC::WriteReg(uint16_t Reg) { - LCD->REG = Reg; +void TFT_FSMC::writeReg(const uint16_t inReg) { + LCD->REG = inReg; __DSB(); } -uint32_t TFT_FSMC::GetID() { +uint32_t TFT_FSMC::getID() { uint32_t id; - WriteReg(0x0000); + writeReg(0x0000); id = LCD->RAM; if (id == 0) - id = ReadID(LCD_READ_ID); + id = readID(LCD_READ_ID); if ((id & 0xFFFF) == 0 || (id & 0xFFFF) == 0xFFFF) - id = ReadID(LCD_READ_ID4); + id = readID(LCD_READ_ID4); if ((id & 0xFF00) == 0 && (id & 0xFF) != 0) - id = ReadID(LCD_READ_ID4); + id = readID(LCD_READ_ID4); return id; } - uint32_t TFT_FSMC::ReadID(uint16_t Reg) { + uint32_t TFT_FSMC::readID(const uint16_t inReg) { uint32_t id; - WriteReg(Reg); + writeReg(inReg); id = LCD->RAM; // dummy read - id = Reg << 24; + id = inReg << 24; id |= (LCD->RAM & 0x00FF) << 16; id |= (LCD->RAM & 0x00FF) << 8; id |= LCD->RAM & 0x00FF; @@ -215,23 +217,52 @@ uint32_t TFT_FSMC::GetID() { } bool TFT_FSMC::isBusy() { + #define __IS_DMA_CONFIGURED(__DMAx__, __CHx__) (dma_channel_regs(__DMAx__, __CHx__)->CPAR != 0) + + if (!__IS_DMA_CONFIGURED(FSMC_DMA_DEV, FSMC_DMA_CHANNEL)) return false; + + // Check if DMA transfer error or transfer complete flags are set + if ((dma_get_isr_bits(FSMC_DMA_DEV, FSMC_DMA_CHANNEL) & (DMA_ISR_TCIF | DMA_ISR_TEIF)) == 0) return true; + + __DSB(); + abort(); return false; } -void TFT_FSMC::Abort() { +void TFT_FSMC::abort() { + dma_channel_reg_map *channel_regs = dma_channel_regs(FSMC_DMA_DEV, FSMC_DMA_CHANNEL); + dma_disable(FSMC_DMA_DEV, FSMC_DMA_CHANNEL); // Abort DMA transfer if any + + // Deconfigure DMA + channel_regs->CCR = 0U; + channel_regs->CNDTR = 0U; + channel_regs->CMAR = 0U; + channel_regs->CPAR = 0U; } -void TFT_FSMC::TransmitDMA(uint32_t MemoryIncrease, uint16_t *Data, uint16_t Count) { +void TFT_FSMC::transmitDMA(uint32_t memoryIncrease, uint16_t *data, uint16_t count) { + // TODO: HAL STM32 uses DMA2_Channel1 for FSMC on STM32F1 + dma_setup_transfer(FSMC_DMA_DEV, FSMC_DMA_CHANNEL, data, DMA_SIZE_16BITS, &LCD->RAM, DMA_SIZE_16BITS, DMA_MEM_2_MEM | memoryIncrease); + dma_set_num_transfers(FSMC_DMA_DEV, FSMC_DMA_CHANNEL, count); + dma_clear_isr_bits(FSMC_DMA_DEV, FSMC_DMA_CHANNEL); + dma_enable(FSMC_DMA_DEV, FSMC_DMA_CHANNEL); + + TERN_(TFT_SHARED_IO, while (isBusy())); +} + +void TFT_FSMC::transmit(uint32_t memoryIncrease, uint16_t *data, uint16_t count) { #if defined(FSMC_DMA_DEV) && defined(FSMC_DMA_CHANNEL) - dma_setup_transfer(FSMC_DMA_DEV, FSMC_DMA_CHANNEL, Data, DMA_SIZE_16BITS, &LCD->RAM, DMA_SIZE_16BITS, DMA_MEM_2_MEM | MemoryIncrease); - dma_set_num_transfers(FSMC_DMA_DEV, FSMC_DMA_CHANNEL, Count); + dma_setup_transfer(FSMC_DMA_DEV, FSMC_DMA_CHANNEL, data, DMA_SIZE_16BITS, &LCD->RAM, DMA_SIZE_16BITS, DMA_MEM_2_MEM | memoryIncrease); + dma_set_num_transfers(FSMC_DMA_DEV, FSMC_DMA_CHANNEL, count); dma_clear_isr_bits(FSMC_DMA_DEV, FSMC_DMA_CHANNEL); dma_enable(FSMC_DMA_DEV, FSMC_DMA_CHANNEL); - while ((dma_get_isr_bits(FSMC_DMA_DEV, FSMC_DMA_CHANNEL) & 0x0A) == 0) {}; - dma_disable(FSMC_DMA_DEV, FSMC_DMA_CHANNEL); + while ((dma_get_isr_bits(FSMC_DMA_DEV, FSMC_DMA_CHANNEL) & (DMA_CCR_TEIE | DMA_CCR_TCIE)) == 0) {} + abort(); #endif } #endif // HAS_FSMC_TFT + +#endif // __STM32F1__ diff --git a/Marlin/src/HAL/STM32F1/tft/tft_fsmc.h b/Marlin/src/HAL/STM32F1/tft/tft_fsmc.h index 11eb1ffa84..214acf127e 100644 --- a/Marlin/src/HAL/STM32F1/tft/tft_fsmc.h +++ b/Marlin/src/HAL/STM32F1/tft/tft_fsmc.h @@ -22,7 +22,7 @@ #pragma once #ifndef LCD_READ_ID - #define LCD_READ_ID 0x04 // Read display identification information (0xD3 on ILI9341) + #define LCD_READ_ID 0x04 // Read display identification information (0xD3 on ILI9341) #endif #ifndef LCD_READ_ID4 #define LCD_READ_ID4 0xD3 // Read display identification information (0xD3 on ILI9341) @@ -30,9 +30,20 @@ #include -#define DATASIZE_8BIT DMA_SIZE_8BITS -#define DATASIZE_16BIT DMA_SIZE_16BITS -#define TFT_IO_DRIVER TFT_FSMC +#ifndef FSMC_DMA_DEV + #define FSMC_DMA_DEV DMA2 +#endif +#ifndef FSMC_DMA_CHANNEL + #define FSMC_DMA_CHANNEL DMA_CH5 +#endif + +#define DATASIZE_8BIT DMA_SIZE_8BITS +#define DATASIZE_16BIT DMA_SIZE_16BITS +#define TFT_IO_DRIVER TFT_FSMC +#define DMA_MAX_WORDS 0xFFFF + +#define DMA_PINC_ENABLE DMA_PINC_MODE +#define DMA_PINC_DISABLE 0 typedef struct { __IO uint16_t REG; @@ -43,29 +54,31 @@ class TFT_FSMC { private: static LCD_CONTROLLER_TypeDef *LCD; - static uint32_t ReadID(uint16_t Reg); - static void Transmit(uint16_t Data); - static void TransmitDMA(uint32_t MemoryIncrease, uint16_t *Data, uint16_t Count); + static uint32_t readID(const uint16_t inReg); + static void transmit(uint16_t data); + static void transmit(uint32_t memoryIncrease, uint16_t *data, uint16_t count); + static void transmitDMA(uint32_t memoryIncrease, uint16_t *data, uint16_t count); public: - static void Init(); - static uint32_t GetID(); + static void init(); + static uint32_t getID(); static bool isBusy(); - static void Abort(); + static void abort(); - static void DataTransferBegin(uint16_t DataWidth = DATASIZE_16BIT) {}; - static void DataTransferEnd() {}; + static void dataTransferBegin(uint16_t dataWidth=DATASIZE_16BIT) {}; + static void dataTransferEnd() {}; - static void WriteData(uint16_t Data) { Transmit(Data); } - static void WriteReg(uint16_t Reg); + static void writeData(uint16_t data) { transmit(data); } + static void writeReg(const uint16_t inReg); - static void WriteSequence(uint16_t *Data, uint16_t Count) { TransmitDMA(DMA_PINC_MODE, Data, Count); } - static void WriteMultiple(uint16_t Color, uint16_t Count) { static uint16_t Data; Data = Color; TransmitDMA(DMA_CIRC_MODE, &Data, Count); } - static void WriteMultiple(uint16_t Color, uint32_t Count) { - static uint16_t Data; Data = Color; - while (Count > 0) { - TransmitDMA(DMA_CIRC_MODE, &Data, Count > 0xFFFF ? 0xFFFF : Count); - Count = Count > 0xFFFF ? Count - 0xFFFF : 0; + static void writeSequence_DMA(uint16_t *data, uint16_t count) { transmitDMA(DMA_PINC_ENABLE, data, count); } + static void writeMultiple_DMA(uint16_t color, uint16_t count) { static uint16_t data; data = color; transmitDMA(DMA_PINC_DISABLE, &data, count); } + + static void writeSequence(uint16_t *data, uint16_t count) { transmit(DMA_PINC_ENABLE, data, count); } + static void writeMultiple(uint16_t color, uint32_t count) { + while (count > 0) { + transmit(DMA_PINC_DISABLE, &color, count > DMA_MAX_WORDS ? DMA_MAX_WORDS : count); + count = count > DMA_MAX_WORDS ? count - DMA_MAX_WORDS : 0; } } }; diff --git a/Marlin/src/HAL/STM32F1/tft/tft_spi.cpp b/Marlin/src/HAL/STM32F1/tft/tft_spi.cpp index 1095389946..f26103b25d 100644 --- a/Marlin/src/HAL/STM32F1/tft/tft_spi.cpp +++ b/Marlin/src/HAL/STM32F1/tft/tft_spi.cpp @@ -20,49 +20,35 @@ * */ +#ifdef __STM32F1__ + #include "../../../inc/MarlinConfig.h" #if HAS_SPI_TFT #include "tft_spi.h" -// TFT_SPI tft; +SPIClass TFT_SPI::SPIx(TFT_SPI_DEVICE); -SPIClass TFT_SPI::SPIx(1); - -#define TFT_CS_H OUT_WRITE(TFT_CS_PIN, HIGH) -#define TFT_CS_L OUT_WRITE(TFT_CS_PIN, LOW) - -#define TFT_DC_H OUT_WRITE(TFT_DC_PIN, HIGH) -#define TFT_DC_L OUT_WRITE(TFT_DC_PIN, LOW) - -#define TFT_RST_H OUT_WRITE(TFT_RST_PIN, HIGH) -#define TFT_RST_L OUT_WRITE(TFT_RST_PIN, LOW) - -#define TFT_BLK_H OUT_WRITE(TFT_BACKLIGHT_PIN, HIGH) -#define TFT_BLK_L OUT_WRITE(TFT_BACKLIGHT_PIN, LOW) - -void TFT_SPI::Init() { +void TFT_SPI::init() { #if PIN_EXISTS(TFT_RESET) - // OUT_WRITE(TFT_RESET_PIN, HIGH); - TFT_RST_H; + OUT_WRITE(TFT_RESET_PIN, HIGH); delay(100); #endif #if PIN_EXISTS(TFT_BACKLIGHT) - // OUT_WRITE(TFT_BACKLIGHT_PIN, HIGH); - TFT_BLK_H; + OUT_WRITE(TFT_BACKLIGHT_PIN, HIGH); #endif - TFT_DC_H; - TFT_CS_H; + OUT_WRITE(TFT_DC_PIN, HIGH); + OUT_WRITE(TFT_CS_PIN, HIGH); /** * STM32F1 APB2 = 72MHz, APB1 = 36MHz, max SPI speed of this MCU if 18Mhz * STM32F1 has 3 SPI ports, SPI1 in APB2, SPI2/SPI3 in APB1 * so the minimum prescale of SPI1 is DIV4, SPI2/SPI3 is DIV2 */ - #if SPI_DEVICE == 1 + #if TFT_SPI_DEVICE == 1 #define SPI_CLOCK_MAX SPI_CLOCK_DIV4 #else #define SPI_CLOCK_MAX SPI_CLOCK_DIV2 @@ -78,72 +64,107 @@ void TFT_SPI::Init() { case SPI_SPEED_6: clock = SPI_CLOCK_DIV64; break; default: clock = SPI_CLOCK_DIV2; // Default from the SPI library } - SPIx.setModule(1); + SPIx.setModule(TFT_SPI_DEVICE); SPIx.setClockDivider(clock); SPIx.setBitOrder(MSBFIRST); SPIx.setDataMode(SPI_MODE0); } -void TFT_SPI::DataTransferBegin(uint16_t DataSize) { - SPIx.setDataSize(DataSize); +void TFT_SPI::dataTransferBegin(uint16_t dataSize) { + SPIx.setDataSize(dataSize); SPIx.begin(); - TFT_CS_L; + WRITE(TFT_CS_PIN, LOW); } -uint32_t TFT_SPI::GetID() { - uint32_t id; - id = ReadID(LCD_READ_ID); +#ifdef TFT_DEFAULT_DRIVER + #include "../../../lcd/tft_io/tft_ids.h" +#endif - if ((id & 0xFFFF) == 0 || (id & 0xFFFF) == 0xFFFF) - id = ReadID(LCD_READ_ID4); +uint32_t TFT_SPI::getID() { + uint32_t id; + id = readID(LCD_READ_ID); + if ((id & 0xFFFF) == 0 || (id & 0xFFFF) == 0xFFFF) { + id = readID(LCD_READ_ID4); + #ifdef TFT_DEFAULT_DRIVER + if ((id & 0xFFFF) == 0 || (id & 0xFFFF) == 0xFFFF) + id = TFT_DEFAULT_DRIVER; + #endif + } return id; } -uint32_t TFT_SPI::ReadID(uint16_t Reg) { - #if !PIN_EXISTS(TFT_MISO) - return 0; - #else - uint8_t d = 0; - uint32_t data = 0; - SPIx.setClockDivider(SPI_CLOCK_DIV16); - DataTransferBegin(DATASIZE_8BIT); - WriteReg(Reg); +uint32_t TFT_SPI::readID(const uint16_t inReg) { + uint32_t data = 0; - LOOP_L_N(i, 4) { - SPIx.read((uint8_t*)&d, 1); + #if PIN_EXISTS(TFT_MISO) + SPIx.setClockDivider(SPI_CLOCK_DIV16); + dataTransferBegin(DATASIZE_8BIT); + writeReg(inReg); + + for (uint8_t i = 0; i < 4; ++i) { + uint8_t d; + SPIx.read(&d, 1); data = (data << 8) | d; } - DataTransferEnd(); + dataTransferEnd(); SPIx.setClockDivider(SPI_CLOCK_MAX); - - return data >> 7; #endif + + return data >> 7; } bool TFT_SPI::isBusy() { + #define __IS_DMA_CONFIGURED(__DMAx__, __CHx__) (dma_channel_regs(__DMAx__, __CHx__)->CPAR != 0) + + if (!__IS_DMA_CONFIGURED(DMAx, DMA_CHx)) return false; + + if (dma_get_isr_bits(DMAx, DMA_CHx) & DMA_ISR_TEIF) { + // You should not be here - DMA transfer error flag is set + // Abort DMA transfer and release SPI + } + else { + // Check if DMA transfer completed flag is set + if (!(dma_get_isr_bits(DMAx, DMA_CHx) & DMA_ISR_TCIF)) return true; + // Check if SPI TX butter is empty and SPI is idle + if (!(SPIdev->regs->SR & SPI_SR_TXE) || (SPIdev->regs->SR & SPI_SR_BSY)) return true; + } + + abort(); return false; } -void TFT_SPI::Abort() { - DataTransferEnd(); +void TFT_SPI::abort() { + dma_channel_reg_map *channel_regs = dma_channel_regs(DMAx, DMA_CHx); + + dma_disable(DMAx, DMA_CHx); // Abort DMA transfer if any + spi_tx_dma_disable(SPIdev); + + // Deconfigure DMA + channel_regs->CCR = 0U; + channel_regs->CNDTR = 0U; + channel_regs->CMAR = 0U; + channel_regs->CPAR = 0U; + + dataTransferEnd(); } -void TFT_SPI::Transmit(uint16_t Data) { - SPIx.send(Data); +void TFT_SPI::transmit(uint16_t data) { SPIx.send(data); } + +void TFT_SPI::transmitDMA(uint32_t memoryIncrease, uint16_t *data, uint16_t count) { + dataTransferBegin(); + SPIx.dmaSendAsync(data, count, memoryIncrease == DMA_MINC_ENABLE); + + TERN_(TFT_SHARED_IO, while (isBusy())); } -void TFT_SPI::TransmitDMA(uint32_t MemoryIncrease, uint16_t *Data, uint16_t Count) { - DataTransferBegin(); - TFT_DC_H; - if (MemoryIncrease == DMA_MINC_ENABLE) { - SPIx.dmaSend(Data, Count, true); - } - else { - SPIx.dmaSend(Data, Count, false); - } - - DataTransferEnd(); +void TFT_SPI::transmit(uint32_t memoryIncrease, uint16_t *data, uint16_t count) { + WRITE(TFT_DC_PIN, HIGH); + dataTransferBegin(); + SPIx.dmaSend(data, count, memoryIncrease == DMA_MINC_ENABLE); + dataTransferEnd(); } #endif // HAS_SPI_TFT + +#endif // __STM32F1__ diff --git a/Marlin/src/HAL/STM32F1/tft/tft_spi.h b/Marlin/src/HAL/STM32F1/tft/tft_spi.h index da9a8e0c22..af53f352be 100644 --- a/Marlin/src/HAL/STM32F1/tft/tft_spi.h +++ b/Marlin/src/HAL/STM32F1/tft/tft_spi.h @@ -25,6 +25,27 @@ #include +#define IS_SPI(N) (BOARD_NR_SPI >= N && (TFT_SCK_PIN == BOARD_SPI##N##_SCK_PIN) && (TFT_MOSI_PIN == BOARD_SPI##N##_MOSI_PIN) && (TFT_MISO_PIN == BOARD_SPI##N##_MISO_PIN)) +#if IS_SPI(1) + #define TFT_SPI_DEVICE 1 + #define SPIdev SPI1 + #define DMAx DMA1 + #define DMA_CHx DMA_CH3 +#elif IS_SPI(2) + #define TFT_SPI_DEVICE 2 + #define SPIdev SPI2 + #define DMAx DMA1 + #define DMA_CHx DMA_CH5 +#elif IS_SPI(3) + #define TFT_SPI_DEVICE 3 + #define SPIdev SPI3 + #define DMAx DMA2 + #define DMA_CHx DMA_CH2 +#else + #error "Invalid TFT SPI configuration." +#endif +#undef IS_SPI + #ifndef LCD_READ_ID #define LCD_READ_ID 0x04 // Read display identification information (0xD3 on ILI9341) #endif @@ -32,41 +53,44 @@ #define LCD_READ_ID4 0xD3 // Read display identification information (0xD3 on ILI9341) #endif -#define DATASIZE_8BIT DATA_SIZE_8BIT -#define DATASIZE_16BIT DATA_SIZE_16BIT -#define TFT_IO_DRIVER TFT_SPI +#define DATASIZE_8BIT DATA_SIZE_8BIT +#define DATASIZE_16BIT DATA_SIZE_16BIT +#define TFT_IO_DRIVER TFT_SPI +#define DMA_MAX_WORDS 0xFFFF -#define DMA_MINC_ENABLE 1 -#define DMA_MINC_DISABLE 0 +#define DMA_MINC_ENABLE DMA_MINC_MODE +#define DMA_MINC_DISABLE 0 class TFT_SPI { private: - static uint32_t ReadID(uint16_t Reg); - static void Transmit(uint16_t Data); - static void TransmitDMA(uint32_t MemoryIncrease, uint16_t *Data, uint16_t Count); + static uint32_t readID(const uint16_t inReg); + static void transmit(uint16_t data); + static void transmit(uint32_t memoryIncrease, uint16_t *data, uint16_t count); + static void transmitDMA(uint32_t memoryIncrease, uint16_t *data, uint16_t count); public: static SPIClass SPIx; - static void Init(); - static uint32_t GetID(); + static void init(); + static uint32_t getID(); static bool isBusy(); - static void Abort(); + static void abort(); - static void DataTransferBegin(uint16_t DataWidth = DATA_SIZE_16BIT); - static void DataTransferEnd() { WRITE(TFT_CS_PIN, HIGH); SPIx.end(); }; - static void DataTransferAbort(); + static void dataTransferBegin(uint16_t dataWidth=DATA_SIZE_16BIT); + static void dataTransferEnd() { WRITE(TFT_CS_PIN, HIGH); SPIx.end(); }; + static void dataTransferAbort(); - static void WriteData(uint16_t Data) { Transmit(Data); } - static void WriteReg(uint16_t Reg) { WRITE(TFT_A0_PIN, LOW); Transmit(Reg); WRITE(TFT_A0_PIN, HIGH); } + static void writeData(uint16_t data) { transmit(data); } + static void writeReg(const uint16_t inReg) { WRITE(TFT_DC_PIN, LOW); transmit(inReg); WRITE(TFT_DC_PIN, HIGH); } - static void WriteSequence(uint16_t *Data, uint16_t Count) { TransmitDMA(DMA_MINC_ENABLE, Data, Count); } - static void WriteMultiple(uint16_t Color, uint16_t Count) { static uint16_t Data; Data = Color; TransmitDMA(DMA_MINC_DISABLE, &Data, Count); } - static void WriteMultiple(uint16_t Color, uint32_t Count) { - static uint16_t Data; Data = Color; - while (Count > 0) { - TransmitDMA(DMA_MINC_DISABLE, &Data, Count > 0xFFFF ? 0xFFFF : Count); - Count = Count > 0xFFFF ? Count - 0xFFFF : 0; + static void writeSequence_DMA(uint16_t *data, uint16_t count) { transmitDMA(DMA_MINC_ENABLE, data, count); } + static void writeMultiple_DMA(uint16_t color, uint16_t count) { static uint16_t data; data = color; transmitDMA(DMA_MINC_DISABLE, &data, count); } + + static void writeSequence(uint16_t *data, uint16_t count) { transmit(DMA_MINC_ENABLE, data, count); } + static void writeMultiple(uint16_t color, uint32_t count) { + while (count > 0) { + transmit(DMA_MINC_DISABLE, &color, count > DMA_MAX_WORDS ? DMA_MAX_WORDS : count); + count = count > DMA_MAX_WORDS ? count - DMA_MAX_WORDS : 0; } } }; diff --git a/Marlin/src/HAL/STM32F1/tft/xpt2046.cpp b/Marlin/src/HAL/STM32F1/tft/xpt2046.cpp index 616d05fead..475290de45 100644 --- a/Marlin/src/HAL/STM32F1/tft/xpt2046.cpp +++ b/Marlin/src/HAL/STM32F1/tft/xpt2046.cpp @@ -1,6 +1,9 @@ /** * Marlin 3D Printer Firmware - * Copyright (c) 2019 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] + * Copyright (c) 2020 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 @@ -17,9 +20,11 @@ * */ +#ifdef __STM32F1__ + #include "../../../inc/MarlinConfig.h" -#if HAS_TFT_XPT2046 || HAS_TOUCH_XPT2046 +#if HAS_TFT_XPT2046 || HAS_RES_TOUCH_BUTTONS #include "xpt2046.h" #include @@ -54,7 +59,7 @@ uint16_t delta(uint16_t a, uint16_t b) { return a > b ? a - b : b - a; } } #endif // TOUCH_BUTTONS_HW_SPI -void XPT2046::Init() { +void XPT2046::init() { SET_INPUT(TOUCH_MISO_PIN); SET_OUTPUT(TOUCH_MOSI_PIN); SET_OUTPUT(TOUCH_SCK_PIN); @@ -81,9 +86,8 @@ bool XPT2046::isTouched() { ); } -bool XPT2046::getRawPoint(int16_t *x, int16_t *y) { - if (isBusy()) return false; - if (!isTouched()) return false; +bool XPT2046::getRawPoint(int16_t * const x, int16_t * const y) { + if (isBusy() || !isTouched()) return false; *x = getRawData(XPT2046_X); *y = getRawData(XPT2046_Y); return isTouched(); @@ -92,7 +96,7 @@ bool XPT2046::getRawPoint(int16_t *x, int16_t *y) { uint16_t XPT2046::getRawData(const XPTCoordinate coordinate) { uint16_t data[3]; - DataTransferBegin(); + dataTransferBegin(); TERN_(TOUCH_BUTTONS_HW_SPI, SPIx.begin()); for (uint16_t i = 0; i < 3 ; i++) { @@ -101,7 +105,7 @@ uint16_t XPT2046::getRawData(const XPTCoordinate coordinate) { } TERN_(TOUCH_BUTTONS_HW_SPI, SPIx.end()); - DataTransferEnd(); + dataTransferEnd(); uint16_t delta01 = delta(data[0], data[1]), delta02 = delta(data[0], data[2]), @@ -114,17 +118,17 @@ uint16_t XPT2046::getRawData(const XPTCoordinate coordinate) { } uint16_t XPT2046::IO(uint16_t data) { - return TERN(TOUCH_BUTTONS_HW_SPI, HardwareIO, SoftwareIO)(data); + return TERN(TOUCH_BUTTONS_HW_SPI, hardwareIO, softwareIO)(data); } #if ENABLED(TOUCH_BUTTONS_HW_SPI) - uint16_t XPT2046::HardwareIO(uint16_t data) { + uint16_t XPT2046::hardwareIO(uint16_t data) { uint16_t result = SPIx.transfer(data); return result; } #endif -uint16_t XPT2046::SoftwareIO(uint16_t data) { +uint16_t XPT2046::softwareIO(uint16_t data) { uint16_t result = 0; for (uint8_t j = 0x80; j; j >>= 1) { @@ -138,4 +142,6 @@ uint16_t XPT2046::SoftwareIO(uint16_t data) { return result; } -#endif // HAS_TFT_XPT2046 +#endif // HAS_TFT_XPT2046 || HAS_RES_TOUCH_BUTTONS + +#endif // __STM32F1__ diff --git a/Marlin/src/HAL/STM32F1/tft/xpt2046.h b/Marlin/src/HAL/STM32F1/tft/xpt2046.h index 019f75efce..9a19e3c98d 100644 --- a/Marlin/src/HAL/STM32F1/tft/xpt2046.h +++ b/Marlin/src/HAL/STM32F1/tft/xpt2046.h @@ -1,6 +1,9 @@ /** * Marlin 3D Printer Firmware - * Copyright (c) 2019 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] + * Copyright (c) 2020 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 @@ -25,16 +28,16 @@ #endif #ifndef TOUCH_MISO_PIN - #define TOUCH_MISO_PIN MISO_PIN + #define TOUCH_MISO_PIN SD_MISO_PIN #endif #ifndef TOUCH_MOSI_PIN - #define TOUCH_MOSI_PIN MOSI_PIN + #define TOUCH_MOSI_PIN SD_MOSI_PIN #endif #ifndef TOUCH_SCK_PIN - #define TOUCH_SCK_PIN SCK_PIN + #define TOUCH_SCK_PIN SD_SCK_PIN #endif #ifndef TOUCH_CS_PIN - #define TOUCH_CS_PIN CS_PIN + #define TOUCH_CS_PIN SD_SS_PIN #endif #ifndef TOUCH_INT_PIN #define TOUCH_INT_PIN -1 @@ -51,7 +54,7 @@ enum XPTCoordinate : uint8_t { XPT2046_Z2 = 0x40 | XPT2046_CONTROL | XPT2046_DFR_MODE, }; -#if !defined(XPT2046_Z1_THRESHOLD) +#ifndef XPT2046_Z1_THRESHOLD #define XPT2046_Z1_THRESHOLD 10 #endif @@ -62,12 +65,12 @@ private: static uint16_t getRawData(const XPTCoordinate coordinate); static bool isTouched(); - static inline void DataTransferBegin() { WRITE(TOUCH_CS_PIN, LOW); }; - static inline void DataTransferEnd() { WRITE(TOUCH_CS_PIN, HIGH); }; + static void dataTransferBegin() { WRITE(TOUCH_CS_PIN, LOW); }; + static void dataTransferEnd() { WRITE(TOUCH_CS_PIN, HIGH); }; #if ENABLED(TOUCH_BUTTONS_HW_SPI) - static uint16_t HardwareIO(uint16_t data); + static uint16_t hardwareIO(uint16_t data); #endif - static uint16_t SoftwareIO(uint16_t data); + static uint16_t softwareIO(uint16_t data); static uint16_t IO(uint16_t data = 0); public: @@ -75,6 +78,6 @@ public: static SPIClass SPIx; #endif - static void Init(); - static bool getRawPoint(int16_t *x, int16_t *y); + static void init(); + static bool getRawPoint(int16_t * const x, int16_t * const y); }; diff --git a/Marlin/src/HAL/STM32F1/timers.cpp b/Marlin/src/HAL/STM32F1/timers.cpp index 8c2df1e216..141dc80b80 100644 --- a/Marlin/src/HAL/STM32F1/timers.cpp +++ b/Marlin/src/HAL/STM32F1/timers.cpp @@ -1,9 +1,9 @@ /** * Marlin 3D Printer Firmware - * * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com - * Copyright (c) 2015-2016 Nico Tonnhofer wurstnase.reprap@gmail.com + * + * 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 @@ -47,10 +47,7 @@ * TODO: Calculate Timer prescale value, so we get the 32bit to adjust */ - - - -void timer_set_interrupt_priority(uint_fast8_t timer_num, uint_fast8_t priority) { +void HAL_timer_set_interrupt_priority(uint_fast8_t timer_num, uint_fast8_t priority) { nvic_irq_num irq_num; switch (timer_num) { case 1: irq_num = NVIC_TIMER1_CC; break; @@ -73,7 +70,6 @@ void timer_set_interrupt_priority(uint_fast8_t timer_num, uint_fast8_t priority) nvic_irq_set_priority(irq_num, priority); } - void HAL_timer_start(const uint8_t timer_num, const uint32_t frequency) { /** * Give the Stepper ISR a higher priority (lower number) @@ -81,29 +77,29 @@ void HAL_timer_start(const uint8_t timer_num, const uint32_t frequency) { */ switch (timer_num) { - case STEP_TIMER_NUM: + case MF_TIMER_STEP: timer_pause(STEP_TIMER_DEV); timer_set_mode(STEP_TIMER_DEV, STEP_TIMER_CHAN, TIMER_OUTPUT_COMPARE); // counter timer_set_count(STEP_TIMER_DEV, 0); 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); - timer_set_interrupt_priority(STEP_TIMER_NUM, STEP_TIMER_IRQ_PRIO); + HAL_timer_set_interrupt_priority(MF_TIMER_STEP, STEP_TIMER_IRQ_PRIO); timer_generate_update(STEP_TIMER_DEV); timer_resume(STEP_TIMER_DEV); break; - case TEMP_TIMER_NUM: + case MF_TIMER_TEMP: timer_pause(TEMP_TIMER_DEV); timer_set_mode(TEMP_TIMER_DEV, TEMP_TIMER_CHAN, TIMER_OUTPUT_COMPARE); 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); - timer_set_interrupt_priority(TEMP_TIMER_NUM, TEMP_TIMER_IRQ_PRIO); + HAL_timer_set_interrupt_priority(MF_TIMER_TEMP, TEMP_TIMER_IRQ_PRIO); timer_generate_update(TEMP_TIMER_DEV); timer_resume(TEMP_TIMER_DEV); break; @@ -112,31 +108,31 @@ void HAL_timer_start(const uint8_t timer_num, const uint32_t frequency) { void HAL_timer_enable_interrupt(const uint8_t timer_num) { switch (timer_num) { - case STEP_TIMER_NUM: ENABLE_STEPPER_DRIVER_INTERRUPT(); break; - case TEMP_TIMER_NUM: ENABLE_TEMPERATURE_INTERRUPT(); break; + case MF_TIMER_STEP: ENABLE_STEPPER_DRIVER_INTERRUPT(); break; + case MF_TIMER_TEMP: ENABLE_TEMPERATURE_INTERRUPT(); break; } } void HAL_timer_disable_interrupt(const uint8_t timer_num) { switch (timer_num) { - case STEP_TIMER_NUM: DISABLE_STEPPER_DRIVER_INTERRUPT(); break; - case TEMP_TIMER_NUM: DISABLE_TEMPERATURE_INTERRUPT(); break; + case MF_TIMER_STEP: DISABLE_STEPPER_DRIVER_INTERRUPT(); break; + case MF_TIMER_TEMP: DISABLE_TEMPERATURE_INTERRUPT(); break; } } -static inline bool timer_irq_enabled(const timer_dev * const dev, const uint8_t interrupt) { +static inline bool HAL_timer_irq_enabled(const timer_dev * const dev, const uint8_t interrupt) { return bool(*bb_perip(&(dev->regs).gen->DIER, interrupt)); } bool HAL_timer_interrupt_enabled(const uint8_t timer_num) { switch (timer_num) { - case STEP_TIMER_NUM: return timer_irq_enabled(STEP_TIMER_DEV, STEP_TIMER_CHAN); - case TEMP_TIMER_NUM: return timer_irq_enabled(TEMP_TIMER_DEV, TEMP_TIMER_CHAN); + case MF_TIMER_STEP: return HAL_timer_irq_enabled(STEP_TIMER_DEV, STEP_TIMER_CHAN); + case MF_TIMER_TEMP: return HAL_timer_irq_enabled(TEMP_TIMER_DEV, TEMP_TIMER_CHAN); } return false; } -timer_dev* get_timer_dev(int number) { +timer_dev* HAL_get_timer_dev(int number) { switch (number) { #if STM32_HAVE_TIMER(1) case 1: return &timer1; diff --git a/Marlin/src/HAL/STM32F1/timers.h b/Marlin/src/HAL/STM32F1/timers.h index 6f360f6bfc..3d6650a1a2 100644 --- a/Marlin/src/HAL/STM32F1/timers.h +++ b/Marlin/src/HAL/STM32F1/timers.h @@ -1,9 +1,9 @@ /** * Marlin 3D Printer Firmware - * * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com - * Copyright (c) 2017 Victor Perez + * + * 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 @@ -25,9 +25,10 @@ * HAL for stm32duino.com based on Libmaple and compatible (STM32F1) */ -#include +#include "../../inc/MarlinConfig.h" +#include "HAL.h" + #include -#include "../../core/boards.h" // ------------------------ // Defines @@ -37,10 +38,9 @@ * TODO: Check and confirm what timer we will use for each Temps and stepper driving. * We should probable drive temps with PWM. */ -#define FORCE_INLINE __attribute__((always_inline)) inline 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 @@ -65,57 +65,56 @@ typedef uint16_t hal_timer_t; * - Otherwise it uses Timer 8 on boards with STM32_HIGH_DENSITY * or Timer 4 on other boards. */ -#ifndef STEP_TIMER_NUM +#ifndef MF_TIMER_STEP #if defined(MCU_STM32F103CB) || defined(MCU_STM32F103C8) - #define STEP_TIMER_NUM 4 // For C8/CB boards, use timer 4 + #define MF_TIMER_STEP 4 // For C8/CB boards, use timer 4 #else - #define STEP_TIMER_NUM 5 // for other boards, five is fine. + #define MF_TIMER_STEP 5 // for other boards, five is fine. #endif #endif -#ifndef PULSE_TIMER_NUM - #define PULSE_TIMER_NUM STEP_TIMER_NUM +#ifndef MF_TIMER_PULSE + #define MF_TIMER_PULSE MF_TIMER_STEP #endif -#ifndef TEMP_TIMER_NUM - #define TEMP_TIMER_NUM 2 // Timer Index for Temperature - //#define TEMP_TIMER_NUM 4 // 2->4, Timer 2 for Stepper Current PWM +#ifndef MF_TIMER_TEMP + #define MF_TIMER_TEMP 2 // Timer Index for Temperature + //#define MF_TIMER_TEMP 4 // 2->4, Timer 2 for Stepper Current PWM #endif -#if MB(BTT_SKR_MINI_E3_V1_0, BTT_SKR_E3_DIP, BTT_SKR_MINI_E3_V1_2, MKS_ROBIN_LITE) - // SKR Mini E3 boards use PA8 as FAN_PIN, so TIMER 1 is used for Fan PWM. +#if MB(BTT_SKR_MINI_E3_V1_0, BTT_SKR_E3_DIP, BTT_SKR_MINI_E3_V1_2, MKS_ROBIN_LITE, MKS_ROBIN_E3D, MKS_ROBIN_E3, VOXELAB_AQUILA) + // SKR Mini E3 boards use PA8 as FAN0_PIN, so TIMER 1 is used for Fan PWM. #ifdef STM32_HIGH_DENSITY - #define SERVO0_TIMER_NUM 8 // tone.cpp uses Timer 4 + #define MF_TIMER_SERVO0 8 // tone.cpp uses Timer 4 #else - #define SERVO0_TIMER_NUM 3 // tone.cpp uses Timer 8 + #define MF_TIMER_SERVO0 3 // tone.cpp uses Timer 8 #endif #else - #define SERVO0_TIMER_NUM 1 // SERVO0 or BLTOUCH + #define MF_TIMER_SERVO0 1 // SERVO0 or BLTOUCH #endif #define STEP_TIMER_IRQ_PRIO 2 #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_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 -timer_dev* get_timer_dev(int number); -#define TIMER_DEV(num) get_timer_dev(num) -#define STEP_TIMER_DEV TIMER_DEV(STEP_TIMER_NUM) -#define TEMP_TIMER_DEV TIMER_DEV(TEMP_TIMER_NUM) +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(STEP_TIMER_NUM) +#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)) @@ -129,15 +128,17 @@ timer_dev* get_timer_dev(int number); #define HAL_STEP_TIMER_ISR() extern "C" void stepTC_Handler() #endif -extern "C" void tempTC_Handler(); -extern "C" void stepTC_Handler(); +extern "C" { + void tempTC_Handler(); + void stepTC_Handler(); +} // ------------------------ // Public Variables // ------------------------ -//static HardwareTimer StepperTimer(STEP_TIMER_NUM); -//static HardwareTimer TempTimer(TEMP_TIMER_NUM); +//static HardwareTimer StepperTimer(MF_TIMER_STEP); +//static HardwareTimer TempTimer(MF_TIMER_TEMP); // ------------------------ // Public functions @@ -161,13 +162,13 @@ bool HAL_timer_interrupt_enabled(const uint8_t timer_num); FORCE_INLINE static void HAL_timer_set_compare(const uint8_t timer_num, const hal_timer_t compare) { switch (timer_num) { - case STEP_TIMER_NUM: + case MF_TIMER_STEP: // NOTE: WE have set ARPE = 0, which means the Auto reload register is not preloaded // and there is no need to use any compare, as in the timer mode used, setting ARR to the compare value - // will result in exactly the same effect, ie trigerring an interrupt, and on top, set counter to 0 + // will result in exactly the same effect, ie triggering an interrupt, and on top, set counter to 0 timer_set_reload(STEP_TIMER_DEV, compare); // We reload direct ARR as needed during counting up break; - case TEMP_TIMER_NUM: + case MF_TIMER_TEMP: timer_set_compare(TEMP_TIMER_DEV, TEMP_TIMER_CHAN, compare); break; } @@ -175,18 +176,18 @@ FORCE_INLINE static void HAL_timer_set_compare(const uint8_t timer_num, const ha FORCE_INLINE static void HAL_timer_isr_prologue(const uint8_t timer_num) { switch (timer_num) { - case STEP_TIMER_NUM: - // No counter to clear - timer_generate_update(STEP_TIMER_DEV); - return; - case TEMP_TIMER_NUM: - timer_set_count(TEMP_TIMER_DEV, 0); - timer_generate_update(TEMP_TIMER_DEV); - return; + case MF_TIMER_STEP: + // No counter to clear + timer_generate_update(STEP_TIMER_DEV); + return; + case MF_TIMER_TEMP: + timer_set_count(TEMP_TIMER_DEV, 0); + timer_generate_update(TEMP_TIMER_DEV); + return; } } -#define HAL_timer_isr_epilogue(TIMER_NUM) +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 @@ -194,6 +195,6 @@ FORCE_INLINE static void timer_no_ARR_preload_ARPE(timer_dev *dev) { bb_peri_set_bit(&(dev->regs).gen->CR1, TIMER_CR1_ARPE_BIT, 0); } -void timer_set_interrupt_priority(uint_fast8_t timer_num, uint_fast8_t priority); +void HAL_timer_set_interrupt_priority(uint_fast8_t timer_num, uint_fast8_t priority); #define TIMER_OC_NO_PRELOAD 0 // Need to disable preload also on compare registers. diff --git a/Marlin/src/HAL/STM32F1/u8g/LCD_defines.h b/Marlin/src/HAL/STM32F1/u8g/LCD_defines.h new file mode 100644 index 0000000000..9d0137ae6a --- /dev/null +++ b/Marlin/src/HAL/STM32F1/u8g/LCD_defines.h @@ -0,0 +1,34 @@ +/** + * 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 + * (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 + +/** + * 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); // 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 +#define U8G_COM_HAL_HW_SPI_FN u8g_com_stm32duino_hw_spi_fn // See U8glib-HAL +#define U8G_COM_ST7920_HAL_SW_SPI u8g_com_std_sw_spi_fn // See U8glib-HAL +#define U8G_COM_ST7920_HAL_HW_SPI u8g_com_stm32duino_hw_spi_fn // See U8glib-HAL 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 70% 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 60596054e8..28302edaf5 100644 --- a/Marlin/src/HAL/STM32F1/dogm/u8g_com_stm32duino_swspi.cpp +++ b/Marlin/src/HAL/STM32F1/u8g/u8g_com_stm32duino_swspi.cpp @@ -2,6 +2,9 @@ * Marlin 3D Printer Firmware * Copyright (c) 2020 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 @@ -16,22 +19,25 @@ * along with this program. If not, see . * */ + #ifdef __STM32F1__ #include "../../../inc/MarlinConfig.h" -#if BOTH(HAS_MARLINUI_U8GLIB, FORCE_SOFT_SPI) +#if ALL(HAS_MARLINUI_U8GLIB, FORCE_SOFT_SPI) -#include +#include +#include "../../shared/HAL_SPI.h" -#undef SPI_SPEED -#define SPI_SPEED 0 // Fastest -//#define SPI_SPEED 2 // Slower +#ifndef LCD_SPI_SPEED + #define LCD_SPI_SPEED SPI_FULL_SPEED // Fastest + //#define LCD_SPI_SPEED SPI_QUARTER_SPEED // Slower +#endif -static uint8_t SPI_speed = SPI_SPEED; +static uint8_t SPI_speed = LCD_SPI_SPEED; static inline uint8_t swSpiTransfer_mode_0(uint8_t b, const uint8_t spi_speed, const pin_t miso_pin=-1) { - LOOP_L_N(i, 8) { + for (uint8_t i = 0; i < 8; ++i) { if (spi_speed == 0) { WRITE(DOGLCD_MOSI, !!(b & 0x80)); WRITE(DOGLCD_SCK, HIGH); @@ -41,16 +47,16 @@ static inline uint8_t swSpiTransfer_mode_0(uint8_t b, const uint8_t spi_speed, c } else { const uint8_t state = (b & 0x80) ? HIGH : LOW; - LOOP_L_N(j, spi_speed) + for (uint8_t j = 0; j < spi_speed; ++j) WRITE(DOGLCD_MOSI, state); - LOOP_L_N(j, spi_speed + (miso_pin >= 0 ? 0 : 1)) + for (uint8_t j = 0; j < spi_speed + (miso_pin >= 0 ? 0 : 1); ++j) WRITE(DOGLCD_SCK, HIGH); b <<= 1; if (miso_pin >= 0 && READ(miso_pin)) b |= 1; - LOOP_L_N(j, spi_speed) + for (uint8_t j = 0; j < spi_speed; ++j) WRITE(DOGLCD_SCK, LOW); } } @@ -58,7 +64,7 @@ static inline uint8_t swSpiTransfer_mode_0(uint8_t b, const uint8_t spi_speed, c } static inline uint8_t swSpiTransfer_mode_3(uint8_t b, const uint8_t spi_speed, const pin_t miso_pin=-1) { - LOOP_L_N(i, 8) { + for (uint8_t i = 0; i < 8; ++i) { const uint8_t state = (b & 0x80) ? HIGH : LOW; if (spi_speed == 0) { WRITE(DOGLCD_SCK, LOW); @@ -67,13 +73,13 @@ static inline uint8_t swSpiTransfer_mode_3(uint8_t b, const uint8_t spi_speed, c WRITE(DOGLCD_SCK, HIGH); } else { - LOOP_L_N(j, spi_speed + (miso_pin >= 0 ? 0 : 1)) + for (uint8_t j = 0; j < spi_speed + (miso_pin >= 0 ? 0 : 1); ++j) WRITE(DOGLCD_SCK, LOW); - LOOP_L_N(j, spi_speed) + for (uint8_t j = 0; j < spi_speed; ++j) WRITE(DOGLCD_MOSI, state); - LOOP_L_N(j, spi_speed) + for (uint8_t j = 0; j < spi_speed; ++j) WRITE(DOGLCD_SCK, HIGH); } b <<= 1; @@ -82,8 +88,8 @@ static inline uint8_t swSpiTransfer_mode_3(uint8_t b, const uint8_t spi_speed, c return b; } -static void u8g_sw_spi_HAL_STM32F1_shift_out(uint8_t val) { - #if ENABLED(FYSETC_MINI_12864) +static void u8g_sw_spi_shift_out(uint8_t val) { + #if U8G_SPI_USE_MODE_3 swSpiTransfer_mode_3(val, SPI_speed); #else swSpiTransfer_mode_0(val, SPI_speed); @@ -104,7 +110,7 @@ static uint8_t swSpiInit(const uint8_t spi_speed) { uint8_t u8g_com_HAL_STM32F1_sw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr) { switch (msg) { case U8G_COM_MSG_INIT: - SPI_speed = swSpiInit(SPI_SPEED); + SPI_speed = swSpiInit(LCD_SPI_SPEED); break; case U8G_COM_MSG_STOP: @@ -117,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); @@ -133,13 +139,13 @@ uint8_t u8g_com_HAL_STM32F1_sw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, break; case U8G_COM_MSG_WRITE_BYTE: - u8g_sw_spi_HAL_STM32F1_shift_out(arg_val); + 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_HAL_STM32F1_shift_out(*ptr++); + u8g_sw_spi_shift_out(*ptr++); arg_val--; } } break; @@ -147,7 +153,7 @@ uint8_t u8g_com_HAL_STM32F1_sw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, case U8G_COM_MSG_WRITE_SEQ_P: { uint8_t *ptr = (uint8_t *)arg_ptr; while (arg_val > 0) { - u8g_sw_spi_HAL_STM32F1_shift_out(u8g_pgm_read(ptr)); + u8g_sw_spi_shift_out(u8g_pgm_read(ptr)); ptr++; arg_val--; } diff --git a/Marlin/src/HAL/STM32_F4_F7/HAL.cpp b/Marlin/src/HAL/STM32_F4_F7/HAL.cpp deleted file mode 100644 index b4629d2afd..0000000000 --- a/Marlin/src/HAL/STM32_F4_F7/HAL.cpp +++ /dev/null @@ -1,95 +0,0 @@ -/** - * Marlin 3D Printer Firmware - * - * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com - * Copyright (c) 2015-2016 Nico Tonnhofer wurstnase.reprap@gmail.com - * Copyright (c) 2017 Victor Perez - * - * 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 . - * - */ -#if defined(STM32GENERIC) && (defined(STM32F4) || defined(STM32F7)) - -#include "HAL.h" - -//#include - -// ------------------------ -// Public Variables -// ------------------------ - -uint16_t HAL_adc_result; - -// ------------------------ -// Public functions -// ------------------------ - -/* VGPV Done with defines -// disable interrupts -void cli() { noInterrupts(); } - -// enable interrupts -void sei() { interrupts(); } -*/ - -void HAL_clear_reset_source() { __HAL_RCC_CLEAR_RESET_FLAGS(); } - -uint8_t HAL_get_reset_source() { - if (__HAL_RCC_GET_FLAG(RCC_FLAG_IWDGRST) != RESET) return RST_WATCHDOG; - if (__HAL_RCC_GET_FLAG(RCC_FLAG_SFTRST) != RESET) return RST_SOFTWARE; - if (__HAL_RCC_GET_FLAG(RCC_FLAG_PINRST) != RESET) return RST_EXTERNAL; - if (__HAL_RCC_GET_FLAG(RCC_FLAG_PORRST) != RESET) return RST_POWER_ON; - return 0; -} - -void _delay_ms(const int delay_ms) { delay(delay_ms); } - -extern "C" { - extern unsigned int _ebss; // end of bss section -} - -// return free memory between end of heap (or end bss) and whatever is current - -/* -#include -//extern caddr_t _sbrk(int incr); -#ifndef CONFIG_HEAP_END -extern char _lm_heap_end; -#define CONFIG_HEAP_END ((caddr_t)&_lm_heap_end) -#endif - -extern "C" { - static int freeMemory() { - char top = 't'; - return &top - reinterpret_cast(sbrk(0)); - } - int freeMemory() { - int free_memory; - int heap_end = (int)_sbrk(0); - free_memory = ((int)&free_memory) - ((int)heap_end); - return free_memory; - } -} -*/ - -// ------------------------ -// ADC -// ------------------------ - -void HAL_adc_start_conversion(const uint8_t adc_pin) { HAL_adc_result = analogRead(adc_pin); } - -uint16_t HAL_adc_get_result() { return HAL_adc_result; } - -#endif // STM32GENERIC && (STM32F4 || STM32F7) diff --git a/Marlin/src/HAL/STM32_F4_F7/HAL.h b/Marlin/src/HAL/STM32_F4_F7/HAL.h deleted file mode 100644 index 00a65de792..0000000000 --- a/Marlin/src/HAL/STM32_F4_F7/HAL.h +++ /dev/null @@ -1,199 +0,0 @@ -/** - * Marlin 3D Printer Firmware - * - * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com - * Copyright (c) 2015-2016 Nico Tonnhofer wurstnase.reprap@gmail.com - * Copyright (c) 2017 Victor Perez - * - * 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 "../../inc/MarlinConfigPre.h" - -#include "../shared/Marduino.h" -#include "../shared/math_32bit.h" -#include "../shared/HAL_SPI.h" - -#include "fastio.h" -#include "watchdog.h" - -#include - -#if defined(STM32F4) && USBCON - #include -#endif - -// ------------------------ -// Defines -// ------------------------ - -// Serial override -//extern HalSerial usb_serial; - -#define _MSERIAL(X) SerialUART##X -#define MSERIAL(X) _MSERIAL(X) -#define SerialUART0 Serial1 - -#if defined(STM32F4) && SERIAL_PORT == 0 - #error "SERIAL_PORT cannot be 0. (Port 0 does not exist.) Please update your configuration." -#elif SERIAL_PORT == -1 - #define MYSERIAL0 SerialUSB -#elif WITHIN(SERIAL_PORT, 0, 6) - #define MYSERIAL0 MSERIAL(SERIAL_PORT) -#else - #error "SERIAL_PORT must be from -1 to 6. Please update your configuration." -#endif - -#ifdef SERIAL_PORT_2 - #if defined(STM32F4) && SERIAL_PORT_2 == 0 - #error "SERIAL_PORT_2 cannot be 0. (Port 0 does not exist.) Please update your configuration." - #elif SERIAL_PORT_2 == -1 - #define MYSERIAL1 SerialUSB - #elif WITHIN(SERIAL_PORT_2, 0, 6) - #define MYSERIAL1 MSERIAL(SERIAL_PORT_2) - #else - #error "SERIAL_PORT_2 must be from -1 to 6. Please update your configuration." - #endif -#endif - -#ifdef LCD_SERIAL_PORT - #if defined(STM32F4) && LCD_SERIAL_PORT == 0 - #error "LCD_SERIAL_PORT cannot be 0. (Port 0 does not exist.) Please update your configuration." - #elif LCD_SERIAL_PORT == -1 - #define LCD_SERIAL SerialUSB - #elif WITHIN(LCD_SERIAL_PORT, 0, 6) - #define LCD_SERIAL MSERIAL(LCD_SERIAL_PORT) - #else - #error "LCD_SERIAL_PORT must be from -1 to 6. Please update your configuration." - #endif -#endif - -/** - * 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 ISRS_ENABLED() (!__get_PRIMASK()) -#define ENABLE_ISRS() __enable_irq() -#define DISABLE_ISRS() __disable_irq() -#define cli() __disable_irq() -#define sei() __enable_irq() - -// On AVR this is in math.h? -#define square(x) ((x)*(x)) - -#ifndef strncpy_P - #define strncpy_P(dest, src, num) strncpy((dest), (src), (num)) -#endif - -// Fix bug in pgm_read_ptr -#undef pgm_read_ptr -#define pgm_read_ptr(addr) (*(addr)) - -// ------------------------ -// Types -// ------------------------ - -typedef int8_t pin_t; - -#ifdef STM32F4 - #define HAL_SERVO_LIB libServo -#endif - -// ------------------------ -// Public Variables -// ------------------------ - -// Result of last ADC conversion -extern uint16_t HAL_adc_result; - -// ------------------------ -// Public functions -// ------------------------ - -// Memory related -#define __bss_end __bss_end__ - -inline void HAL_init() {} - -// Clear reset reason -void HAL_clear_reset_source(); - -// Reset reason -uint8_t HAL_get_reset_source(); - -inline void HAL_reboot() {} // reboot the board or restart the bootloader - -void _delay_ms(const int delay); - -/* -extern "C" { - int freeMemory(); -} -*/ - -extern "C" char* _sbrk(int incr); - -/* -int freeMemory() { - volatile int top; - top = (int)((char*)&top - reinterpret_cast(_sbrk(0))); - return top; -} -*/ - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-function" - -static inline int freeMemory() { - volatile char top; - return &top - reinterpret_cast(_sbrk(0)); -} - -#pragma GCC diagnostic pop - -// -// ADC -// - -#define HAL_ANALOG_SELECT(pin) pinMode(pin, INPUT) - -inline void HAL_adc_init() {} - -#define HAL_ADC_VREF 3.3 -#define HAL_ADC_RESOLUTION 10 -#define HAL_START_ADC(pin) HAL_adc_start_conversion(pin) -#define HAL_READ_ADC() HAL_adc_result -#define HAL_ADC_READY() true - -void HAL_adc_start_conversion(const uint8_t adc_pin); -uint16_t HAL_adc_get_result(); - -#define GET_PIN_MAP_PIN(index) index -#define GET_PIN_MAP_INDEX(pin) pin -#define PARSED_PIN_INDEX(code, dval) parser.intval(code, dval) - -#ifdef STM32F4 - #define JTAG_DISABLE() afio_cfg_debug_ports(AFIO_DEBUG_SW_ONLY) - #define JTAGSWD_DISABLE() afio_cfg_debug_ports(AFIO_DEBUG_NONE) -#endif diff --git a/Marlin/src/HAL/STM32_F4_F7/HAL_SPI.cpp b/Marlin/src/HAL/STM32_F4_F7/HAL_SPI.cpp deleted file mode 100644 index ebd0b4cee7..0000000000 --- a/Marlin/src/HAL/STM32_F4_F7/HAL_SPI.cpp +++ /dev/null @@ -1,164 +0,0 @@ -/** - * Marlin 3D Printer Firmware - * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * - * Based on Sprinter and grbl. - * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm - * Copyright (c) 2017 Victor Perez - * - * 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 . - * - */ -#if defined(STM32GENERIC) && (defined(STM32F4) || defined(STM32F7)) - -/** - * Software SPI functions originally from Arduino Sd2Card Library - * Copyright (c) 2009 by William Greiman - */ - -/** - * Adapted to the Marlin STM32F4/7 HAL - */ - -#include "../../inc/MarlinConfig.h" - -#include -#include -#include "../shared/HAL_SPI.h" -#include "spi_pins.h" - -// ------------------------ -// Public Variables -// ------------------------ - -static SPISettings spiConfig; - -// ------------------------ -// Public functions -// ------------------------ - -#if ENABLED(SOFTWARE_SPI) - // ------------------------ - // Software SPI - // ------------------------ - #error "Software SPI not supported for STM32F4/7. Use Hardware SPI." -#else - -// ------------------------ -// Hardware SPI -// ------------------------ - -/** - * VGPV SPI speed start and F_CPU/2, by default 72/2 = 36Mhz - */ - -/** - * @brief Begin SPI port setup - * - * @return Nothing - * - * @details Only configures SS pin since libmaple creates and initialize the SPI object - */ -void spiBegin() { - #if !defined(SS_PIN) || SS_PIN < 0 - #error "SS_PIN not defined!" - #endif - - OUT_WRITE(SS_PIN, HIGH); -} - -/** 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 libarary - } - spiConfig = SPISettings(clock, MSBFIRST, SPI_MODE0); - SPI.begin(); -} - -/** - * @brief Receives a single byte from the SPI port. - * - * @return Byte received - * - * @details - */ -uint8_t spiRec() { - SPI.beginTransaction(spiConfig); - uint8_t returnByte = SPI.transfer(0xFF); - SPI.endTransaction(); - return returnByte; -} - -/** - * @brief Receives 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) { - SPI.beginTransaction(spiConfig); - #ifndef STM32GENERIC - SPI.dmaTransfer(0, const_cast(buf), nbyte); - #else - SPI.transfer((uint8_t*)buf, nbyte); - #endif - SPI.endTransaction(); -} - -/** - * @brief Sends a single byte on SPI port - * - * @param b Byte to send - * - * @details - */ -void spiSend(uint8_t b) { - SPI.beginTransaction(spiConfig); - SPI.transfer(b); - SPI.endTransaction(); -} - -/** - * @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) { - SPI.beginTransaction(spiConfig); - SPI.transfer(token); - #ifndef STM32GENERIC - SPI.dmaSend(const_cast(buf), 512); - #else - SPI.transfer((uint8_t*)buf, nullptr, 512); - #endif - SPI.endTransaction(); -} - -#endif // SOFTWARE_SPI -#endif // STM32GENERIC && (STM32F4 || STM32F7) diff --git a/Marlin/src/HAL/STM32_F4_F7/README.md b/Marlin/src/HAL/STM32_F4_F7/README.md deleted file mode 100644 index 3b5a9ab02e..0000000000 --- a/Marlin/src/HAL/STM32_F4_F7/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# This HAL is for... - - - STM32F407 MCU with STM32Generic Arduino core by danieleff. - - STM32F765 board "The Borg" with STM32Generic. - -See the `README.md` files in HAL_STM32F4 and HAL_STM32F7 for the specifics of those hals. diff --git a/Marlin/src/HAL/STM32_F4_F7/STM32F4/README.md b/Marlin/src/HAL/STM32_F4_F7/STM32F4/README.md deleted file mode 100644 index 10396e875b..0000000000 --- a/Marlin/src/HAL/STM32_F4_F7/STM32F4/README.md +++ /dev/null @@ -1,12 +0,0 @@ -# This HAL is for the STM32F407 MCU used with STM32Generic Arduino core by danieleff. - -# Arduino core is located at: - -https://github.com/danieleff/STM32GENERIC - -Unzip it into [Arduino]/hardware folder - -# This HAL is in development. - -This HAL is a modified version of Chris Barr's Picoprint STM32F4 HAL. - diff --git a/Marlin/src/HAL/STM32_F4_F7/STM32F4/timers.cpp b/Marlin/src/HAL/STM32_F4_F7/STM32F4/timers.cpp deleted file mode 100644 index dc41f89131..0000000000 --- a/Marlin/src/HAL/STM32_F4_F7/STM32F4/timers.cpp +++ /dev/null @@ -1,115 +0,0 @@ -/** - * Marlin 3D Printer Firmware - * - * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com - * Copyright (c) 2015-2016 Nico Tonnhofer wurstnase.reprap@gmail.com - * - * 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 . - * - */ -#if defined(STM32GENERIC) && defined(STM32F4) - -#include "../../../inc/MarlinConfig.h" - -// ------------------------ -// Local defines -// ------------------------ - -#define NUM_HARDWARE_TIMERS 2 -#define STEP_TIMER_IRQ_ID TIM5_IRQn -#define TEMP_TIMER_IRQ_ID TIM7_IRQn - -// ------------------------ -// Private Variables -// ------------------------ - -stm32_timer_t TimerHandle[NUM_HARDWARE_TIMERS]; - -// ------------------------ -// Public functions -// ------------------------ - -bool timers_initialized[NUM_HARDWARE_TIMERS] = {false}; - -void HAL_timer_start(const uint8_t timer_num, const uint32_t frequency) { - - if (!timers_initialized[timer_num]) { - constexpr uint32_t step_prescaler = STEPPER_TIMER_PRESCALE - 1, - temp_prescaler = TEMP_TIMER_PRESCALE - 1; - switch (timer_num) { - case STEP_TIMER_NUM: - // STEPPER TIMER TIM5 - use a 32bit timer - __HAL_RCC_TIM5_CLK_ENABLE(); - TimerHandle[timer_num].handle.Instance = TIM5; - TimerHandle[timer_num].handle.Init.Prescaler = step_prescaler; - TimerHandle[timer_num].handle.Init.CounterMode = TIM_COUNTERMODE_UP; - TimerHandle[timer_num].handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; - TimerHandle[timer_num].callback = (uint32_t)TC5_Handler; - HAL_NVIC_SetPriority(STEP_TIMER_IRQ_ID, 1, 0); - break; - - case TEMP_TIMER_NUM: - // TEMP TIMER TIM7 - any available 16bit Timer (1 already used for PWM) - __HAL_RCC_TIM7_CLK_ENABLE(); - TimerHandle[timer_num].handle.Instance = TIM7; - TimerHandle[timer_num].handle.Init.Prescaler = temp_prescaler; - TimerHandle[timer_num].handle.Init.CounterMode = TIM_COUNTERMODE_UP; - TimerHandle[timer_num].handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; - TimerHandle[timer_num].callback = (uint32_t)TC7_Handler; - HAL_NVIC_SetPriority(TEMP_TIMER_IRQ_ID, 2, 0); - break; - } - timers_initialized[timer_num] = true; - } - - TimerHandle[timer_num].handle.Init.Period = (((HAL_TIMER_RATE) / TimerHandle[timer_num].handle.Init.Prescaler) / frequency) - 1; - if (HAL_TIM_Base_Init(&TimerHandle[timer_num].handle) == HAL_OK) - HAL_TIM_Base_Start_IT(&TimerHandle[timer_num].handle); -} - -extern "C" void TIM5_IRQHandler() { - ((void(*)())TimerHandle[0].callback)(); -} -extern "C" void TIM7_IRQHandler() { - ((void(*)())TimerHandle[1].callback)(); -} - -void HAL_timer_enable_interrupt(const uint8_t timer_num) { - switch (timer_num) { - case STEP_TIMER_NUM: HAL_NVIC_EnableIRQ(STEP_TIMER_IRQ_ID); break; - case TEMP_TIMER_NUM: HAL_NVIC_EnableIRQ(TEMP_TIMER_IRQ_ID); break; - } -} - -void HAL_timer_disable_interrupt(const uint8_t timer_num) { - switch (timer_num) { - case STEP_TIMER_NUM: HAL_NVIC_DisableIRQ(STEP_TIMER_IRQ_ID); break; - case TEMP_TIMER_NUM: HAL_NVIC_DisableIRQ(TEMP_TIMER_IRQ_ID); break; - } - // We NEED memory barriers to ensure Interrupts are actually disabled! - // ( https://dzone.com/articles/nvic-disabling-interrupts-on-arm-cortex-m-and-the ) - __DSB(); - __ISB(); -} - -bool HAL_timer_interrupt_enabled(const uint8_t timer_num) { - switch (timer_num) { - case STEP_TIMER_NUM: return NVIC->ISER[(uint32_t)((int32_t)STEP_TIMER_IRQ_ID) >> 5] & (uint32_t)(1 << ((uint32_t)((int32_t)STEP_TIMER_IRQ_ID) & (uint32_t)0x1F)); - case TEMP_TIMER_NUM: return NVIC->ISER[(uint32_t)((int32_t)TEMP_TIMER_IRQ_ID) >> 5] & (uint32_t)(1 << ((uint32_t)((int32_t)TEMP_TIMER_IRQ_ID) & (uint32_t)0x1F)); - } - return false; -} - -#endif // STM32GENERIC && STM32F4 diff --git a/Marlin/src/HAL/STM32_F4_F7/STM32F4/timers.h b/Marlin/src/HAL/STM32_F4_F7/STM32F4/timers.h deleted file mode 100644 index a4a7ad82cc..0000000000 --- a/Marlin/src/HAL/STM32_F4_F7/STM32F4/timers.h +++ /dev/null @@ -1,134 +0,0 @@ -/** - * Marlin 3D Printer Firmware - * - * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com - * Copyright (c) 2017 Victor Perez - * - * 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 - -// ------------------------ -// Defines -// ------------------------ - -#define FORCE_INLINE __attribute__((always_inline)) inline - -#define hal_timer_t uint32_t // TODO: One is 16-bit, one 32-bit - does this need to be checked? -#define HAL_TIMER_TYPE_MAX 0xFFFF - -#define HAL_TIMER_RATE (HAL_RCC_GetSysClockFreq() / 2) // frequency of timer peripherals - -#ifndef STEP_TIMER_NUM - #define STEP_TIMER_NUM 0 // Timer Index for Stepper -#endif -#ifndef PULSE_TIMER_NUM - #define PULSE_TIMER_NUM STEP_TIMER_NUM -#endif -#ifndef TEMP_TIMER_NUM - #define TEMP_TIMER_NUM 1 // Timer Index for Temperature -#endif - -#define TEMP_TIMER_PRESCALE 1000 // prescaler for setting Temp timer, 72Khz -#define TEMP_TIMER_FREQUENCY 1000 // temperature interrupt frequency - -#define STEPPER_TIMER_PRESCALE 54 // was 40,prescaler for setting stepper timer, 2Mhz -#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 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(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_TEMPERATURE_INTERRUPT() HAL_timer_enable_interrupt(TEMP_TIMER_NUM) -#define DISABLE_TEMPERATURE_INTERRUPT() HAL_timer_disable_interrupt(TEMP_TIMER_NUM) - -// TODO change this - -#ifdef STM32GENERIC - #define TC_TIMER_ARGS -#else - #define TC_TIMER_ARGS stimer_t *htim -#endif - -extern void TC5_Handler(TC_TIMER_ARGS); -extern void TC7_Handler(TC_TIMER_ARGS); -#ifndef HAL_STEP_TIMER_ISR - #define HAL_STEP_TIMER_ISR() void TC5_Handler(TC_TIMER_ARGS) -#endif -#ifndef HAL_TEMP_TIMER_ISR - #define HAL_TEMP_TIMER_ISR() void TC7_Handler(TC_TIMER_ARGS) -#endif - -// ------------------------ -// Types -// ------------------------ - -#ifdef STM32GENERIC - typedef struct { - TIM_HandleTypeDef handle; - uint32_t callback; - } tTimerConfig; - typedef tTimerConfig stm32_timer_t; -#else - typedef stimer_t stm32_timer_t; -#endif - -// ------------------------ -// Public Variables -// ------------------------ - -extern stm32_timer_t TimerHandle[]; - -// ------------------------ -// Public functions -// ------------------------ - -void HAL_timer_start(const uint8_t timer_num, const uint32_t frequency); -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); - -FORCE_INLINE static uint32_t HAL_timer_get_count(const uint8_t timer_num) { - return __HAL_TIM_GET_COUNTER(&TimerHandle[timer_num].handle); -} - -FORCE_INLINE static void HAL_timer_set_compare(const uint8_t timer_num, const uint32_t compare) { - __HAL_TIM_SET_AUTORELOAD(&TimerHandle[timer_num].handle, compare); - if (HAL_timer_get_count(timer_num) >= compare) - TimerHandle[timer_num].handle.Instance->EGR |= TIM_EGR_UG; // Generate an immediate update interrupt -} - -FORCE_INLINE static hal_timer_t HAL_timer_get_compare(const uint8_t timer_num) { - return __HAL_TIM_GET_AUTORELOAD(&TimerHandle[timer_num].handle); -} - -#ifdef STM32GENERIC - FORCE_INLINE static void HAL_timer_isr_prologue(const uint8_t timer_num) { - if (__HAL_TIM_GET_FLAG(&TimerHandle[timer_num].handle, TIM_FLAG_UPDATE) == SET) - __HAL_TIM_CLEAR_FLAG(&TimerHandle[timer_num].handle, TIM_FLAG_UPDATE); - } -#else - #define HAL_timer_isr_prologue(TIMER_NUM) -#endif - -#define HAL_timer_isr_epilogue(TIMER_NUM) diff --git a/Marlin/src/HAL/STM32_F4_F7/STM32F7/README.md b/Marlin/src/HAL/STM32_F4_F7/STM32F7/README.md deleted file mode 100644 index 23155b425e..0000000000 --- a/Marlin/src/HAL/STM32_F4_F7/STM32F7/README.md +++ /dev/null @@ -1,27 +0,0 @@ -# This HAL is for the STM32F765 board "The Borg" used with STM32Generic Arduino core by danieleff. - -# Original core is located at: - -https://github.com/danieleff/STM32GENERIC - -but I haven't committed the changes needed for the Borg there yet, so please use: - -https://github.com/Spawn32/STM32GENERIC - -Unzip it into [Arduino]/hardware folder - - -Download the latest GNU ARM Embedded Toolchain: - -https://developer.arm.com/open-source/gnu-toolchain/gnu-rm/downloads - -(The one in Arduino doesn't support STM32F7). - -Change compiler.path in platform.txt to point to the one you downloaded. - -# This HAL is in development. -# Currently only tested on "The Borg". - -You will also need the latest Arduino 1.9.0-beta or newer. - -This HAL is a modified version of Chris Barr's Picoprint STM32F4 HAL, so shouldn't be to hard to get it to work on a F4. diff --git a/Marlin/src/HAL/STM32_F4_F7/STM32F7/TMC2660.cpp b/Marlin/src/HAL/STM32_F4_F7/STM32F7/TMC2660.cpp deleted file mode 100644 index e67808c3c4..0000000000 --- a/Marlin/src/HAL/STM32_F4_F7/STM32F7/TMC2660.cpp +++ /dev/null @@ -1,898 +0,0 @@ -/** - * TMC26XStepper.cpp - - TMC26X Stepper library for Wiring/Arduino - * - * based on the stepper library by Tom Igoe, et. al. - * - * Copyright (c) 2011, Interactive Matter, Marcus Nowotny - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#if defined(STM32GENERIC) && defined(STM32F7) - -#include "../../../inc/MarlinConfigPre.h" - -#if HAS_DRIVER(TMC2660) - -#include -#include -#include "TMC2660.h" - -#include "../../../inc/MarlinConfig.h" -#include "../../../MarlinCore.h" -#include "../../../module/stepper/indirection.h" -#include "../../../module/printcounter.h" -#include "../../../libs/duration_t.h" -#include "../../../libs/hex_print.h" - -//some default values used in initialization -#define DEFAULT_MICROSTEPPING_VALUE 32 - -//TMC26X register definitions -#define DRIVER_CONTROL_REGISTER 0x0UL -#define CHOPPER_CONFIG_REGISTER 0x80000UL -#define COOL_STEP_REGISTER 0xA0000ul -#define STALL_GUARD2_LOAD_MEASURE_REGISTER 0xC0000ul -#define DRIVER_CONFIG_REGISTER 0xE0000ul - -#define REGISTER_BIT_PATTERN 0xFFFFFul - -//definitions for the driver control register -#define MICROSTEPPING_PATTERN 0xFul -#define STEP_INTERPOLATION 0x200UL -#define DOUBLE_EDGE_STEP 0x100UL -#define VSENSE 0x40UL -#define READ_MICROSTEP_POSTION 0x0UL -#define READ_STALL_GUARD_READING 0x10UL -#define READ_STALL_GUARD_AND_COOL_STEP 0x20UL -#define READ_SELECTION_PATTERN 0x30UL - -//definitions for the chopper config register -#define CHOPPER_MODE_STANDARD 0x0UL -#define CHOPPER_MODE_T_OFF_FAST_DECAY 0x4000UL -#define T_OFF_PATTERN 0xFul -#define RANDOM_TOFF_TIME 0x2000UL -#define BLANK_TIMING_PATTERN 0x18000UL -#define BLANK_TIMING_SHIFT 15 -#define HYSTERESIS_DECREMENT_PATTERN 0x1800UL -#define HYSTERESIS_DECREMENT_SHIFT 11 -#define HYSTERESIS_LOW_VALUE_PATTERN 0x780UL -#define HYSTERESIS_LOW_SHIFT 7 -#define HYSTERESIS_START_VALUE_PATTERN 0x78UL -#define HYSTERESIS_START_VALUE_SHIFT 4 -#define T_OFF_TIMING_PATERN 0xFul - -//definitions for cool step register -#define MINIMUM_CURRENT_FOURTH 0x8000UL -#define CURRENT_DOWN_STEP_SPEED_PATTERN 0x6000UL -#define SE_MAX_PATTERN 0xF00ul -#define SE_CURRENT_STEP_WIDTH_PATTERN 0x60UL -#define SE_MIN_PATTERN 0xFul - -//definitions for StallGuard2 current register -#define STALL_GUARD_FILTER_ENABLED 0x10000UL -#define STALL_GUARD_TRESHHOLD_VALUE_PATTERN 0x17F00ul -#define CURRENT_SCALING_PATTERN 0x1Ful -#define STALL_GUARD_CONFIG_PATTERN 0x17F00ul -#define STALL_GUARD_VALUE_PATTERN 0x7F00ul - -//definitions for the input from the TMC2660 -#define STATUS_STALL_GUARD_STATUS 0x1UL -#define STATUS_OVER_TEMPERATURE_SHUTDOWN 0x2UL -#define STATUS_OVER_TEMPERATURE_WARNING 0x4UL -#define STATUS_SHORT_TO_GROUND_A 0x8UL -#define STATUS_SHORT_TO_GROUND_B 0x10UL -#define STATUS_OPEN_LOAD_A 0x20UL -#define STATUS_OPEN_LOAD_B 0x40UL -#define STATUS_STAND_STILL 0x80UL -#define READOUT_VALUE_PATTERN 0xFFC00ul - -#define CPU_32_BIT - -//default values -#define INITIAL_MICROSTEPPING 0x3UL //32th microstepping - -SPIClass SPI_6(SPI6, SPI6_MOSI_PIN, SPI6_MISO_PIN, SPI6_SCK_PIN); - -#define STEPPER_SPI SPI_6 - -//debuging output - -//#define TMC_DEBUG1 - -uint8_t current_scaling = 0; - -/** - * Constructor - * number_of_steps - the steps per rotation - * cs_pin - the SPI client select pin - * dir_pin - the pin where the direction pin is connected - * step_pin - the pin where the step pin is connected - */ -TMC26XStepper::TMC26XStepper(const int16_t in_steps, int16_t cs_pin, int16_t dir_pin, int16_t step_pin, uint16_t current, uint16_t resistor) { - // We are not started yet - started = false; - - // By default cool step is not enabled - cool_step_enabled = false; - - // Save the pins for later use - this->cs_pin = cs_pin; - this->dir_pin = dir_pin; - this->step_pin = step_pin; - - // Store the current sense resistor value for later use - this->resistor = resistor; - - // Initizalize our status values - this->steps_left = 0; - this->direction = 0; - - // Initialize register values - driver_control_register_value = DRIVER_CONTROL_REGISTER | INITIAL_MICROSTEPPING; - chopper_config_register = CHOPPER_CONFIG_REGISTER; - - // Setting the default register values - driver_control_register_value = DRIVER_CONTROL_REGISTER|INITIAL_MICROSTEPPING; - microsteps = _BV(INITIAL_MICROSTEPPING); - chopper_config_register = CHOPPER_CONFIG_REGISTER; - cool_step_register_value = COOL_STEP_REGISTER; - stallguard2_current_register_value = STALL_GUARD2_LOAD_MEASURE_REGISTER; - driver_configuration_register_value = DRIVER_CONFIG_REGISTER | READ_STALL_GUARD_READING; - - // Set the current - setCurrent(current); - // Set to a conservative start value - setConstantOffTimeChopper(7, 54, 13,12,1); - // Set a nice microstepping value - setMicrosteps(DEFAULT_MICROSTEPPING_VALUE); - // Save the number of steps - number_of_steps = in_steps; -} - - -/** - * start & configure the stepper driver - * just must be called. - */ -void TMC26XStepper::start() { - - #ifdef TMC_DEBUG1 - SERIAL_ECHOLNPGM("\n TMC26X stepper library"); - SERIAL_ECHOPAIR("\n CS pin: ", cs_pin); - SERIAL_ECHOPAIR("\n DIR pin: ", dir_pin); - SERIAL_ECHOPAIR("\n STEP pin: ", step_pin); - SERIAL_PRINTF("\n current scaling: %d", current_scaling); - SERIAL_PRINTF("\n Resistor: %d", resistor); - //SERIAL_PRINTF("\n current: %d", current); - SERIAL_ECHOPAIR("\n Microstepping: ", microsteps); - #endif - - //set the pins as output & its initial value - pinMode(step_pin, OUTPUT); - pinMode(dir_pin, OUTPUT); - pinMode(cs_pin, OUTPUT); - extDigitalWrite(step_pin, LOW); - extDigitalWrite(dir_pin, LOW); - extDigitalWrite(cs_pin, HIGH); - - STEPPER_SPI.begin(); - STEPPER_SPI.beginTransaction(SPISettings(4000000, MSBFIRST, SPI_MODE3)); - - //set the initial values - send262(driver_control_register_value); - send262(chopper_config_register); - send262(cool_step_register_value); - send262(stallguard2_current_register_value); - send262(driver_configuration_register_value); - - //save that we are in running mode - started = true; -} - -/** - * Mark the driver as unstarted to be able to start it again - */ -void TMC26XStepper::un_start() { started = false; } - - -/** - * Sets the speed in revs per minute - */ -void TMC26XStepper::setSpeed(uint16_t whatSpeed) { - this->speed = whatSpeed; - this->step_delay = 60UL * sq(1000UL) / ((uint32_t)this->number_of_steps * (uint32_t)whatSpeed * (uint32_t)this->microsteps); - #ifdef TMC_DEBUG0 // crashes - SERIAL_ECHOPAIR("\nStep delay in micros: ", this->step_delay); - #endif - // Update the next step time - this->next_step_time = this->last_step_time + this->step_delay; -} - -uint16_t TMC26XStepper::getSpeed() { return this->speed; } - -/** - * Moves the motor steps_to_move steps. - * Negative indicates the reverse direction. - */ -char TMC26XStepper::step(int16_t steps_to_move) { - if (this->steps_left == 0) { - this->steps_left = ABS(steps_to_move); // how many steps to take - - // determine direction based on whether steps_to_move is + or -: - if (steps_to_move > 0) - this->direction = 1; - else if (steps_to_move < 0) - this->direction = 0; - return 0; - } - return -1; -} - -char TMC26XStepper::move() { - // decrement the number of steps, moving one step each time: - if (this->steps_left > 0) { - uint32_t time = micros(); - // move only if the appropriate delay has passed: - - // rem if (time >= this->next_step_time) { - - if (ABS(time - this->last_step_time) > this->step_delay) { - // increment or decrement the step number, - // depending on direction: - if (this->direction == 1) - extDigitalWrite(step_pin, HIGH); - else { - extDigitalWrite(dir_pin, HIGH); - extDigitalWrite(step_pin, HIGH); - } - // get the timeStamp of when you stepped: - this->last_step_time = time; - this->next_step_time = time + this->step_delay; - // decrement the steps left: - steps_left--; - //disable the step & dir pins - extDigitalWrite(step_pin, LOW); - extDigitalWrite(dir_pin, LOW); - } - return -1; - } - return 0; -} - -char TMC26XStepper::isMoving() { return this->steps_left > 0; } - -uint16_t TMC26XStepper::getStepsLeft() { return this->steps_left; } - -char TMC26XStepper::stop() { - //note to self if the motor is currently moving - char state = isMoving(); - //stop the motor - this->steps_left = 0; - this->direction = 0; - //return if it was moving - return state; -} - -void TMC26XStepper::setCurrent(uint16_t current) { - uint8_t current_scaling = 0; - //calculate the current scaling from the max current setting (in mA) - float mASetting = (float)current, - resistor_value = (float)this->resistor; - // remove vsense flag - this->driver_configuration_register_value &= ~(VSENSE); - // Derived from I = (cs + 1) / 32 * (Vsense / Rsense) - // leading to cs = 32 * R * I / V (with V = 0,31V oder 0,165V and I = 1000 * current) - // with Rsense = 0,15 - // for vsense = 0,310V (VSENSE not set) - // or vsense = 0,165V (VSENSE set) - current_scaling = (byte)((resistor_value * mASetting * 32.0 / (0.31 * sq(1000.0))) - 0.5); //theoretically - 1.0 for better rounding it is 0.5 - - // Check if the current scalingis too low - if (current_scaling < 16) { - // Set the csense bit to get a use half the sense voltage (to support lower motor currents) - this->driver_configuration_register_value |= VSENSE; - // and recalculate the current setting - current_scaling = (byte)((resistor_value * mASetting * 32.0 / (0.165 * sq(1000.0))) - 0.5); //theoretically - 1.0 for better rounding it is 0.5 - #ifdef TMC_DEBUG0 // crashes - SERIAL_ECHOPAIR("\nCS (Vsense=1): ",current_scaling); - #endif - } - #ifdef TMC_DEBUG0 // crashes - else - SERIAL_ECHOPAIR("\nCS: ", current_scaling); - #endif - - // do some sanity checks - NOMORE(current_scaling, 31); - - // delete the old value - stallguard2_current_register_value &= ~(CURRENT_SCALING_PATTERN); - // set the new current scaling - stallguard2_current_register_value |= current_scaling; - // if started we directly send it to the motor - if (started) { - send262(driver_configuration_register_value); - send262(stallguard2_current_register_value); - } -} - -uint16_t TMC26XStepper::getCurrent() { - // Calculate the current according to the datasheet to be on the safe side. - // This is not the fastest but the most accurate and illustrative way. - float result = (float)(stallguard2_current_register_value & CURRENT_SCALING_PATTERN), - resistor_value = (float)this->resistor, - voltage = (driver_configuration_register_value & VSENSE) ? 0.165 : 0.31; - result = (result + 1.0) / 32.0 * voltage / resistor_value * sq(1000.0); - return (uint16_t)result; -} - -void TMC26XStepper::setStallGuardThreshold(char stallguard_threshold, char stallguard_filter_enabled) { - // We just have 5 bits - LIMIT(stallguard_threshold, -64, 63); - - // Add trim down to 7 bits - stallguard_threshold &= 0x7F; - // Delete old StallGuard settings - stallguard2_current_register_value &= ~(STALL_GUARD_CONFIG_PATTERN); - if (stallguard_filter_enabled) - stallguard2_current_register_value |= STALL_GUARD_FILTER_ENABLED; - - // Set the new StallGuard threshold - stallguard2_current_register_value |= (((uint32_t)stallguard_threshold << 8) & STALL_GUARD_CONFIG_PATTERN); - // If started we directly send it to the motor - if (started) send262(stallguard2_current_register_value); -} - -char TMC26XStepper::getStallGuardThreshold() { - uint32_t stallguard_threshold = stallguard2_current_register_value & STALL_GUARD_VALUE_PATTERN; - //shift it down to bit 0 - stallguard_threshold >>= 8; - //convert the value to an int16_t to correctly handle the negative numbers - char result = stallguard_threshold; - //check if it is negative and fill it up with leading 1 for proper negative number representation - //rem if (result & _BV(6)) { - - if (TEST(result, 6)) result |= 0xC0; - return result; -} - -char TMC26XStepper::getStallGuardFilter() { - if (stallguard2_current_register_value & STALL_GUARD_FILTER_ENABLED) - return -1; - return 0; -} - -/** - * Set the number of microsteps per step. - * 0,2,4,8,16,32,64,128,256 is supported - * any value in between will be mapped to the next smaller value - * 0 and 1 set the motor in full step mode - */ -void TMC26XStepper::setMicrosteps(const int16_t in_steps) { - uint16_t setting_pattern; - - if (in_steps >= 256) setting_pattern = 0; - else if (in_steps >= 128) setting_pattern = 1; - else if (in_steps >= 64) setting_pattern = 2; - else if (in_steps >= 32) setting_pattern = 3; - else if (in_steps >= 16) setting_pattern = 4; - else if (in_steps >= 8) setting_pattern = 5; - else if (in_steps >= 4) setting_pattern = 6; - else if (in_steps >= 2) setting_pattern = 7; - else if (in_steps <= 1) setting_pattern = 8; // 1 and 0 lead to full step - - microsteps = _BV(8 - setting_pattern); - - #ifdef TMC_DEBUG0 // crashes - SERIAL_ECHOPAIR("\n Microstepping: ", microsteps); - #endif - - // Delete the old value - this->driver_control_register_value &= 0x000FFFF0UL; - - // Set the new value - this->driver_control_register_value |= setting_pattern; - - // If started we directly send it to the motor - if (started) send262(driver_control_register_value); - - // Recalculate the stepping delay by simply setting the speed again - this->setSpeed(this->speed); -} - -/** - * returns the effective number of microsteps at the moment - */ -int16_t TMC26XStepper::getMicrosteps() { return microsteps; } - -/** - * constant_off_time: The off time setting controls the minimum chopper frequency. - * For most applications an off time within the range of 5ΞΌs to 20ΞΌs will fit. - * 2...15: off time setting - * - * blank_time: Selects the comparator blank time. This time needs to safely cover the switching event and the - * duration of the ringing on the sense resistor. For - * 0: min. setting 3: max. setting - * - * fast_decay_time_setting: Fast decay time setting. With CHM=1, these bits control the portion of fast decay for each chopper cycle. - * 0: slow decay only - * 1...15: duration of fast decay phase - * - * sine_wave_offset: Sine wave offset. With CHM=1, these bits control the sine wave offset. - * A positive offset corrects for zero crossing error. - * -3..-1: negative offset 0: no offset 1...12: positive offset - * - * use_current_comparator: Selects usage of the current comparator for termination of the fast decay cycle. - * If current comparator is enabled, it terminates the fast decay cycle in case the current - * reaches a higher negative value than the actual positive value. - * 1: enable comparator termination of fast decay cycle - * 0: end by time only - */ -void TMC26XStepper::setConstantOffTimeChopper(char constant_off_time, char blank_time, char fast_decay_time_setting, char sine_wave_offset, uint8_t use_current_comparator) { - // Perform some sanity checks - LIMIT(constant_off_time, 2, 15); - - // Save the constant off time - this->constant_off_time = constant_off_time; - - // Calculate the value acc to the clock cycles - const char blank_value = blank_time >= 54 ? 3 : - blank_time >= 36 ? 2 : - blank_time >= 24 ? 1 : 0; - - LIMIT(fast_decay_time_setting, 0, 15); - LIMIT(sine_wave_offset, -3, 12); - - // Shift the sine_wave_offset - sine_wave_offset += 3; - - // Calculate the register setting - // First of all delete all the values for this - chopper_config_register &= ~(_BV(12) | BLANK_TIMING_PATTERN | HYSTERESIS_DECREMENT_PATTERN | HYSTERESIS_LOW_VALUE_PATTERN | HYSTERESIS_START_VALUE_PATTERN | T_OFF_TIMING_PATERN); - // Set the constant off pattern - chopper_config_register |= CHOPPER_MODE_T_OFF_FAST_DECAY; - // Set the blank timing value - chopper_config_register |= ((uint32_t)blank_value) << BLANK_TIMING_SHIFT; - // Setting the constant off time - chopper_config_register |= constant_off_time; - // Set the fast decay time - // Set msb - chopper_config_register |= (((uint32_t)(fast_decay_time_setting & 0x8)) << HYSTERESIS_DECREMENT_SHIFT); - // Other bits - chopper_config_register |= (((uint32_t)(fast_decay_time_setting & 0x7)) << HYSTERESIS_START_VALUE_SHIFT); - // Set the sine wave offset - chopper_config_register |= (uint32_t)sine_wave_offset << HYSTERESIS_LOW_SHIFT; - // Using the current comparator? - if (!use_current_comparator) - chopper_config_register |= _BV(12); - - // If started we directly send it to the motor - if (started) { - // rem send262(driver_control_register_value); - send262(chopper_config_register); - } -} - -/** - * constant_off_time: The off time setting controls the minimum chopper frequency. - * For most applications an off time within the range of 5ΞΌs to 20ΞΌs will fit. - * 2...15: off time setting - * - * blank_time: Selects the comparator blank time. This time needs to safely cover the switching event and the - * duration of the ringing on the sense resistor. For - * 0: min. setting 3: max. setting - * - * hysteresis_start: Hysteresis start setting. Please remark, that this value is an offset to the hysteresis end value HEND. - * 1...8 - * - * hysteresis_end: Hysteresis end setting. Sets the hysteresis end value after a number of decrements. Decrement interval time is controlled by HDEC. - * The sum HSTRT+HEND must be <16. At a current setting CS of max. 30 (amplitude reduced to 240), the sum is not limited. - * -3..-1: negative HEND 0: zero HEND 1...12: positive HEND - * - * hysteresis_decrement: Hysteresis decrement setting. This setting determines the slope of the hysteresis during on time and during fast decay time. - * 0: fast decrement 3: very slow decrement - */ - -void TMC26XStepper::setSpreadCycleChopper(char constant_off_time, char blank_time, char hysteresis_start, char hysteresis_end, char hysteresis_decrement) { - // Perform some sanity checks - LIMIT(constant_off_time, 2, 15); - - // Save the constant off time - this->constant_off_time = constant_off_time; - - // Calculate the value acc to the clock cycles - const char blank_value = blank_time >= 54 ? 3 : - blank_time >= 36 ? 2 : - blank_time >= 24 ? 1 : 0; - - LIMIT(hysteresis_start, 1, 8); - hysteresis_start--; - - LIMIT(hysteresis_start, -3, 12); - - // Shift the hysteresis_end - hysteresis_end += 3; - - LIMIT(hysteresis_decrement, 0, 3); - - //first of all delete all the values for this - chopper_config_register &= ~(CHOPPER_MODE_T_OFF_FAST_DECAY | BLANK_TIMING_PATTERN | HYSTERESIS_DECREMENT_PATTERN | HYSTERESIS_LOW_VALUE_PATTERN | HYSTERESIS_START_VALUE_PATTERN | T_OFF_TIMING_PATERN); - - //set the blank timing value - chopper_config_register |= ((uint32_t)blank_value) << BLANK_TIMING_SHIFT; - //setting the constant off time - chopper_config_register |= constant_off_time; - //set the hysteresis_start - chopper_config_register |= ((uint32_t)hysteresis_start) << HYSTERESIS_START_VALUE_SHIFT; - //set the hysteresis end - chopper_config_register |= ((uint32_t)hysteresis_end) << HYSTERESIS_LOW_SHIFT; - //set the hystereis decrement - chopper_config_register |= ((uint32_t)blank_value) << BLANK_TIMING_SHIFT; - //if started we directly send it to the motor - if (started) { - //rem send262(driver_control_register_value); - send262(chopper_config_register); - } -} - -/** - * In a constant off time chopper scheme both coil choppers run freely, i.e. are not synchronized. - * The frequency of each chopper mainly depends on the coil current and the position dependant motor coil inductivity, thus it depends on the microstep position. - * With some motors a slightly audible beat can occur between the chopper frequencies, especially when they are near to each other. This typically occurs at a - * few microstep positions within each quarter wave. This effect normally is not audible when compared to mechanical noise generated by ball bearings, etc. - * Further factors which can cause a similar effect are a poor layout of sense resistor GND connection. - * Hint: A common factor, which can cause motor noise, is a bad PCB layout causing coupling of both sense resistor voltages - * (please refer to sense resistor layout hint in chapter 8.1). - * In order to minimize the effect of a beat between both chopper frequencies, an internal random generator is provided. - * It modulates the slow decay time setting when switched on by the RNDTF bit. The RNDTF feature further spreads the chopper spectrum, - * reducing electromagnetic emission on single frequencies. - */ -void TMC26XStepper::setRandomOffTime(char value) { - if (value) - chopper_config_register |= RANDOM_TOFF_TIME; - else - chopper_config_register &= ~(RANDOM_TOFF_TIME); - //if started we directly send it to the motor - if (started) { - //rem send262(driver_control_register_value); - send262(chopper_config_register); - } -} - -void TMC26XStepper::setCoolStepConfiguration( - uint16_t lower_SG_threshold, - uint16_t SG_hysteresis, - uint8_t current_decrement_step_size, - uint8_t current_increment_step_size, - uint8_t lower_current_limit -) { - // Sanitize the input values - NOMORE(lower_SG_threshold, 480); - // Divide by 32 - lower_SG_threshold >>= 5; - NOMORE(SG_hysteresis, 480); - // Divide by 32 - SG_hysteresis >>= 5; - NOMORE(current_decrement_step_size, 3); - NOMORE(current_increment_step_size, 3); - NOMORE(lower_current_limit, 1); - - // Store the lower level in order to enable/disable the cool step - this->cool_step_lower_threshold=lower_SG_threshold; - // If cool step is not enabled we delete the lower value to keep it disabled - if (!this->cool_step_enabled) lower_SG_threshold = 0; - // The good news is that we can start with a complete new cool step register value - // And simply set the values in the register - cool_step_register_value = ((uint32_t)lower_SG_threshold) - | (((uint32_t)SG_hysteresis) << 8) - | (((uint32_t)current_decrement_step_size) << 5) - | (((uint32_t)current_increment_step_size) << 13) - | (((uint32_t)lower_current_limit) << 15) - | COOL_STEP_REGISTER; // Register signature - - if (started) send262(cool_step_register_value); -} - -void TMC26XStepper::setCoolStepEnabled(boolean enabled) { - // Simply delete the lower limit to disable the cool step - cool_step_register_value &= ~SE_MIN_PATTERN; - // And set it to the proper value if cool step is to be enabled - if (enabled) - cool_step_register_value |= this->cool_step_lower_threshold; - // And save the enabled status - this->cool_step_enabled = enabled; - // Save the register value - if (started) send262(cool_step_register_value); -} - -boolean TMC26XStepper::isCoolStepEnabled() { return this->cool_step_enabled; } - -uint16_t TMC26XStepper::getCoolStepLowerSgThreshold() { - // We return our internally stored value - in order to provide the correct setting even if cool step is not enabled - return this->cool_step_lower_threshold<<5; -} - -uint16_t TMC26XStepper::getCoolStepUpperSgThreshold() { - return uint8_t((cool_step_register_value & SE_MAX_PATTERN) >> 8) << 5; -} - -uint8_t TMC26XStepper::getCoolStepCurrentIncrementSize() { - return uint8_t((cool_step_register_value & CURRENT_DOWN_STEP_SPEED_PATTERN) >> 13); -} - -uint8_t TMC26XStepper::getCoolStepNumberOfSGReadings() { - return uint8_t((cool_step_register_value & SE_CURRENT_STEP_WIDTH_PATTERN) >> 5); -} - -uint8_t TMC26XStepper::getCoolStepLowerCurrentLimit() { - return uint8_t((cool_step_register_value & MINIMUM_CURRENT_FOURTH) >> 15); -} - -void TMC26XStepper::setEnabled(boolean enabled) { - //delete the t_off in the chopper config to get sure - chopper_config_register &= ~(T_OFF_PATTERN); - if (enabled) { - //and set the t_off time - chopper_config_register |= this->constant_off_time; - } - //if not enabled we don't have to do anything since we already delete t_off from the register - if (started) send262(chopper_config_register); -} - -boolean TMC26XStepper::isEnabled() { return !!(chopper_config_register & T_OFF_PATTERN); } - -/** - * reads a value from the TMC26X status register. The value is not obtained directly but can then - * be read by the various status routines. - */ -void TMC26XStepper::readStatus(char read_value) { - uint32_t old_driver_configuration_register_value = driver_configuration_register_value; - //reset the readout configuration - driver_configuration_register_value &= ~(READ_SELECTION_PATTERN); - //this now equals TMC26X_READOUT_POSITION - so we just have to check the other two options - if (read_value == TMC26X_READOUT_STALLGUARD) - driver_configuration_register_value |= READ_STALL_GUARD_READING; - else if (read_value == TMC26X_READOUT_CURRENT) - driver_configuration_register_value |= READ_STALL_GUARD_AND_COOL_STEP; - - //all other cases are ignored to prevent funny values - //check if the readout is configured for the value we are interested in - if (driver_configuration_register_value != old_driver_configuration_register_value) { - //because then we need to write the value twice - one time for configuring, second time to get the value, see below - send262(driver_configuration_register_value); - } - //write the configuration to get the last status - send262(driver_configuration_register_value); -} - -int16_t TMC26XStepper::getMotorPosition() { - //we read it out even if we are not started yet - perhaps it is useful information for somebody - readStatus(TMC26X_READOUT_POSITION); - return getReadoutValue(); -} - -//reads the StallGuard setting from last status -//returns -1 if StallGuard information is not present -int16_t TMC26XStepper::getCurrentStallGuardReading() { - //if we don't yet started there cannot be a StallGuard value - if (!started) return -1; - //not time optimal, but solution optiomal: - //first read out the StallGuard value - readStatus(TMC26X_READOUT_STALLGUARD); - return getReadoutValue(); -} - -uint8_t TMC26XStepper::getCurrentCSReading() { - //if we don't yet started there cannot be a StallGuard value - if (!started) return 0; - //not time optimal, but solution optiomal: - //first read out the StallGuard value - readStatus(TMC26X_READOUT_CURRENT); - return (getReadoutValue() & 0x1F); -} - -uint16_t TMC26XStepper::getCurrentCurrent() { - float result = (float)getCurrentCSReading(), - resistor_value = (float)this->resistor, - voltage = (driver_configuration_register_value & VSENSE)? 0.165 : 0.31; - result = (result + 1.0) / 32.0 * voltage / resistor_value * sq(1000.0); - return (uint16_t)result; -} - -/** - * Return true if the StallGuard threshold has been reached - */ -boolean TMC26XStepper::isStallGuardOverThreshold() { - if (!this->started) return false; - return (driver_status_result & STATUS_STALL_GUARD_STATUS); -} - -/** - * returns if there is any over temperature condition: - * OVER_TEMPERATURE_PREWARING if pre warning level has been reached - * OVER_TEMPERATURE_SHUTDOWN if the temperature is so hot that the driver is shut down - * Any of those levels are not too good. - */ -char TMC26XStepper::getOverTemperature() { - if (!this->started) return 0; - - if (driver_status_result & STATUS_OVER_TEMPERATURE_SHUTDOWN) - return TMC26X_OVERTEMPERATURE_SHUTDOWN; - - if (driver_status_result & STATUS_OVER_TEMPERATURE_WARNING) - return TMC26X_OVERTEMPERATURE_PREWARING; - - return 0; -} - -// Is motor channel A shorted to ground -boolean TMC26XStepper::isShortToGroundA() { - if (!this->started) return false; - return (driver_status_result & STATUS_SHORT_TO_GROUND_A); -} - -// Is motor channel B shorted to ground -boolean TMC26XStepper::isShortToGroundB() { - if (!this->started) return false; - return (driver_status_result & STATUS_SHORT_TO_GROUND_B); -} - -// Is motor channel A connected -boolean TMC26XStepper::isOpenLoadA() { - if (!this->started) return false; - return (driver_status_result & STATUS_OPEN_LOAD_A); -} - -// Is motor channel B connected -boolean TMC26XStepper::isOpenLoadB() { - if (!this->started) return false; - return (driver_status_result & STATUS_OPEN_LOAD_B); -} - -// Is chopper inactive since 2^20 clock cycles - defaults to ~0,08s -boolean TMC26XStepper::isStandStill() { - if (!this->started) return false; - return (driver_status_result & STATUS_STAND_STILL); -} - -//is chopper inactive since 2^20 clock cycles - defaults to ~0,08s -boolean TMC26XStepper::isStallGuardReached() { - if (!this->started) return false; - return (driver_status_result & STATUS_STALL_GUARD_STATUS); -} - -//reads the StallGuard setting from last status -//returns -1 if StallGuard information is not present -int16_t TMC26XStepper::getReadoutValue() { - return (int)(driver_status_result >> 10); -} - -int16_t TMC26XStepper::getResistor() { return this->resistor; } - -boolean TMC26XStepper::isCurrentScalingHalfed() { - return !!(this->driver_configuration_register_value & VSENSE); -} -/** - * version() returns the version of the library: - */ -int16_t TMC26XStepper::version() { return 1; } - -void TMC26XStepper::debugLastStatus() { - #ifdef TMC_DEBUG1 - if (this->started) { - if (this->getOverTemperature()&TMC26X_OVERTEMPERATURE_PREWARING) - SERIAL_ECHOLNPGM("\n WARNING: Overtemperature Prewarning!"); - else if (this->getOverTemperature()&TMC26X_OVERTEMPERATURE_SHUTDOWN) - SERIAL_ECHOLNPGM("\n ERROR: Overtemperature Shutdown!"); - - if (this->isShortToGroundA()) - SERIAL_ECHOLNPGM("\n ERROR: SHORT to ground on channel A!"); - - if (this->isShortToGroundB()) - SERIAL_ECHOLNPGM("\n ERROR: SHORT to ground on channel B!"); - - if (this->isOpenLoadA()) - SERIAL_ECHOLNPGM("\n ERROR: Channel A seems to be unconnected!"); - - if (this->isOpenLoadB()) - SERIAL_ECHOLNPGM("\n ERROR: Channel B seems to be unconnected!"); - - if (this->isStallGuardReached()) - SERIAL_ECHOLNPGM("\n INFO: Stall Guard level reached!"); - - if (this->isStandStill()) - SERIAL_ECHOLNPGM("\n INFO: Motor is standing still."); - - uint32_t readout_config = driver_configuration_register_value & READ_SELECTION_PATTERN; - const int16_t value = getReadoutValue(); - if (readout_config == READ_MICROSTEP_POSTION) { - SERIAL_ECHOPAIR("\n Microstep position phase A: ", value); - } - else if (readout_config == READ_STALL_GUARD_READING) { - SERIAL_ECHOPAIR("\n Stall Guard value:", value); - } - else if (readout_config == READ_STALL_GUARD_AND_COOL_STEP) { - SERIAL_ECHOPAIR("\n Approx Stall Guard: ", value & 0xF); - SERIAL_ECHOPAIR("\n Current level", value & 0x1F0); - } - } - #endif -} - -/** - * send register settings to the stepper driver via SPI - * returns the current status - */ -inline void TMC26XStepper::send262(uint32_t datagram) { - uint32_t i_datagram; - - //preserver the previous spi mode - //uint8_t oldMode = SPCR & SPI_MODE_MASK; - - //if the mode is not correct set it to mode 3 - //if (oldMode != SPI_MODE3) { - // SPI.setDataMode(SPI_MODE3); - //} - - //select the TMC driver - extDigitalWrite(cs_pin, LOW); - - //ensure that only valid bist are set (0-19) - //datagram &=REGISTER_BIT_PATTERN; - - #ifdef TMC_DEBUG1 - //SERIAL_PRINTF("Sending "); - //SERIAL_PRINTF("Sending ", datagram,HEX); - //SERIAL_ECHOPAIR("\n\nSending \n", print_hex_long(datagram)); - SERIAL_PRINTF("\n\nSending %x", datagram); - #endif - - //write/read the values - i_datagram = STEPPER_SPI.transfer((datagram >> 16) & 0xFF); - i_datagram <<= 8; - i_datagram |= STEPPER_SPI.transfer((datagram >> 8) & 0xFF); - i_datagram <<= 8; - i_datagram |= STEPPER_SPI.transfer((datagram) & 0xFF); - i_datagram >>= 4; - - #ifdef TMC_DEBUG1 - //SERIAL_PRINTF("Received "); - //SERIAL_PRINTF("Received ", i_datagram,HEX); - //SERIAL_ECHOPAIR("\n\nReceived \n", i_datagram); - SERIAL_PRINTF("\n\nReceived %x", i_datagram); - debugLastStatus(); - #endif - - //deselect the TMC chip - extDigitalWrite(cs_pin, HIGH); - - //restore the previous SPI mode if neccessary - //if the mode is not correct set it to mode 3 - //if (oldMode != SPI_MODE3) { - // SPI.setDataMode(oldMode); - //} - - //store the datagram as status result - driver_status_result = i_datagram; -} - -#endif // HAS_DRIVER(TMC2660) - -#endif // STM32GENERIC && STM32F7 diff --git a/Marlin/src/HAL/STM32_F4_F7/STM32F7/TMC2660.h b/Marlin/src/HAL/STM32_F4_F7/STM32F7/TMC2660.h deleted file mode 100644 index 208c3bc7e0..0000000000 --- a/Marlin/src/HAL/STM32_F4_F7/STM32F7/TMC2660.h +++ /dev/null @@ -1,593 +0,0 @@ -/** - * TMC26XStepper.h - - TMC26X Stepper library for Wiring/Arduino - * - * based on the stepper library by Tom Igoe, et. al. - * - * Copyright (c) 2011, Interactive Matter, Marcus Nowotny - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -#pragma once - -#include - -//! return value for TMC26XStepper.getOverTemperature() if there is a overtemperature situation in the TMC chip -/*! - * This warning indicates that the TMC chip is too warm. - * It is still working but some parameters may be inferior. - * You should do something against it. - */ -#define TMC26X_OVERTEMPERATURE_PREWARING 1 -//! return value for TMC26XStepper.getOverTemperature() if there is a overtemperature shutdown in the TMC chip -/*! - * This warning indicates that the TMC chip is too warm to operate and has shut down to prevent damage. - * It will stop working until it cools down again. - * If you encouter this situation you must do something against it. Like reducing the current or improving the PCB layout - * and/or heat management. - */ -#define TMC26X_OVERTEMPERATURE_SHUTDOWN 2 - -//which values can be read out -/*! - * Selects to readout the microstep position from the motor. - *\sa readStatus() - */ -#define TMC26X_READOUT_POSITION 0 -/*! - * Selects to read out the StallGuard value of the motor. - *\sa readStatus() - */ -#define TMC26X_READOUT_STALLGUARD 1 -/*! - * Selects to read out the current current setting (acc. to CoolStep) and the upper bits of the StallGuard value from the motor. - *\sa readStatus(), setCurrent() - */ -#define TMC26X_READOUT_CURRENT 3 - -/*! - * Define to set the minimum current for CoolStep operation to 1/2 of the selected CS minium. - *\sa setCoolStepConfiguration() - */ -#define COOL_STEP_HALF_CS_LIMIT 0 -/*! - * Define to set the minimum current for CoolStep operation to 1/4 of the selected CS minium. - *\sa setCoolStepConfiguration() - */ -#define COOL_STEP_QUARTDER_CS_LIMIT 1 - -/*! - * \class TMC26XStepper - * \brief Class representing a TMC26X stepper driver - * - * To use one of these drivers in your code create an object of its class: - * \code - * TMC26XStepper tmc_stepper = TMC26XStepper(200,1,2,3,500); - * \endcode - * see TMC26XStepper(int16_t number_of_steps, int16_t cs_pin, int16_t dir_pin, int16_t step_pin, uint16_t rms_current) - * - * Keep in mind that you need to start the driver with start() in order to configure the TMC26X. - * - * The most important function is move(). It checks if the motor requires a step. It's important to call move() as - * often as possible in loop(). I suggest using a very fast loop routine and always call move() at the beginning or end. - * - * To move you must set a movement speed with setSpeed(). The speed is a positive value, setting the RPM. - * - * To really move the motor you have to call step() to tell the driver to move the motor the given number - * of steps in the given direction. Positive values move the motor in one direction, negative values in the other. - * - * You can check with isMoving() if the motor is still moving or stop it abruptly with stop(). - */ -class TMC26XStepper { - public: - /*! - * \brief Create a new representation of a stepper motor connected to a TMC26X stepper driver - * - * Main constructor. If in doubt use this. All parameters must be provided as described below. - * - * \param number_of_steps Number of steps the motor has per rotation. - * \param cs_pin Arduino pin connected to the Client Select Pin (!CS) of the TMC26X for SPI. - * \param dir_pin Arduino pin connected to the DIR input of the TMC26X. - * \param step_pin Arduino pin connected to the STEP pin of the TMC26X. - * \param rms_current Maximum current to provide to the motor in mA (!). A value of 200 will send up to 200mA to the motor. - * \param resistor Current sense resistor in milli-Ohm, defaults to 0.15 Ohm (or 150 milli-Ohm) as in the TMC260 Arduino Shield. - * - * You must also call TMC26XStepper.start() to configure the stepper driver for use. - * - * By default the Constant Off Time chopper is used. See TMC26XStepper.setConstantOffTimeChopper() for details. - * This should work on most motors (YMMV). You may want to configure and use the Spread Cycle Chopper. See setSpreadCycleChopper(). - * - * By default a microstepping of 1/32 is used to provide a smooth motor run while still giving a good progression per step. - * Change stepping by sending setMicrosteps() a different value. - * \sa start(), setMicrosteps() - */ - TMC26XStepper(const int16_t in_steps, int16_t cs_pin, int16_t dir_pin, int16_t step_pin, uint16_t current, uint16_t resistor=100); //resistor=150 - - /*! - * \brief Configure and start the TMC26X stepper driver. Before this is called the stepper driver is nonfunctional. - * - * Configure the TMC26X stepper driver for the given values via SPI. - * Most member functions are non-functional if the driver has not been started, - * therefore it is best to call this in setup(). - */ - void start(); - - /*! - * \brief Reset the stepper in unconfigured mode. - * - * Allows start to be called again. It doesn't change the internal stepper - * configuration or the desired configuration. It just marks the stepper as - * not-yet-started. The stepper doesn't need to be reconfigured before - * starting again, and is not reset to any factory settings. - * It must be reset intentionally. - * (Hint: Normally you do not need this function) - */ - void un_start(); - - /*! - * \brief Set the rotation speed in RPM. - * \param whatSpeed the desired speed in RPM. - */ - void setSpeed(uint16_t whatSpeed); - - /*! - * \brief Report the currently selected speed in RPM. - * \sa setSpeed() - */ - uint16_t getSpeed(); - - /*! - * \brief Set the number of microsteps in 2^i values (rounded) up to 256 - * - * This method sets the number of microsteps per step in 2^i interval. - * It accepts 1, 2, 4, 16, 32, 64, 128 or 256 as valid microsteps. - * Other values will be rounded down to the next smaller value (e.g., 3 gives a microstepping of 2). - * You can always check the current microstepping with getMicrosteps(). - */ - void setMicrosteps(const int16_t in_steps); - - /*! - * \brief Return the effective current number of microsteps selected. - * - * Always returns the effective number of microsteps. - * This may be different from the micro-steps set in setMicrosteps() since it is rounded to 2^i. - * - * \sa setMicrosteps() - */ - int16_t getMicrosteps(); - - /*! - * \brief Initiate a movement with the given number of steps. Positive values move in one direction, negative in the other. - * - * \param number_of_steps The number of steps to move the motor. - * \return 0 if the motor was not moving and moves now. -1 if the motor is moving and the new steps could not be set. - * - * If the previous movement is incomplete the function returns -1 and doesn't change the steps to move the motor. - * If the motor does not move it returns 0. - * - * The movement direction is determined by the sign of the steps parameter. The motor direction in machine space - * cannot be determined, as it depends on the construction of the motor and how it functions in the drive system. - * - * For safety, verify with isMoving() or even use stop() to stop the motor before giving it new step directions. - * \sa isMoving(), getStepsLeft(), stop() - */ - char step(int16_t number_of_steps); - - /*! - * \brief Central movement method. Must be called as often as possible in the loop function and is very fast. - * - * Check if the motor still has to move and whether the wait-to-step interval has expired, and manages the - * number of steps remaining to fulfill the current move command. - * - * This function is implemented to be as fast as possible, so call it as often as possible in your loop. - * It should be invoked with as frequently and with as much regularity as possible. - * - * This can be called even when the motor is known not to be moving. It will simply return. - * - * The frequency with which this function is called determines the top stepping speed of the motor. - * It is recommended to call this using a hardware timer to ensure regular invocation. - * \sa step() - */ - char move(); - - /*! - * \brief Check whether the last movement command is done. - * \return 0 if the motor stops, -1 if the motor is moving. - * - * Used to determine if the motor is ready for new movements. - *\sa step(), move() - */ - char isMoving(); - - /*! - * \brief Get the number of steps left in the current movement. - * \return The number of steps left in the movement. Always positive. - */ - uint16_t getStepsLeft(); - - /*! - * \brief Stop the motor immediately. - * \return -1 if the motor was moving and is really stoped or 0 if it was not moving at all. - * - * This method directly and abruptly stops the motor and may be used as an emergency stop. - */ - char stop(); - - /*! - * \brief Set and configure the classical Constant Off Timer Chopper - * \param constant_off_time The off time setting controls the minimum chopper frequency. For most applications an off time within the range of 5ΞΌs to 20ΞΌs will fit. Setting this parameter to zero completely disables all driver transistors and the motor can free-wheel. 0: chopper off, 1:15: off time setting (1 will work with minimum blank time of 24 clocks) - * \param blank_time Comparator blank time. This duration needs to safely cover the duration of the switching event and the ringing on the sense resistor. For most low current drivers, a setting of 1 or 2 is good. For high current applications with large MOSFETs, a setting of 2 or 3 will be required. 0 (min setting) … (3) amx setting - * \param fast_decay_time_setting Fast decay time setting. Controls the portion of fast decay for each chopper cycle. 0: slow decay only, 1…15: duration of fast decay phase - * \param sine_wave_offset Sine wave offset. Controls the sine wave offset. A positive offset corrects for zero crossing error. -3…-1: negative offset, 0: no offset,1…12: positive offset - * \param use_curreent_comparator Selects usage of the current comparator for termination of the fast decay cycle. If current comparator is enabled, it terminates the fast decay cycle in case the current reaches a higher negative value than the actual positive value. (0 disable, -1 enable). - * - * The classic constant off time chopper uses a fixed portion of fast decay following each on phase. - * While the duration of the on time is determined by the chopper comparator, the fast decay time needs - * to be set by the user in a way, that the current decay is enough for the driver to be able to follow - * the falling slope of the sine wave, and on the other hand it should not be too long, in order to minimize - * motor current ripple and power dissipation. This best can be tuned using an oscilloscope or - * trying out motor smoothness at different velocities. A good starting value is a fast decay time setting - * similar to the slow decay time setting. - * After tuning of the fast decay time, the offset should be determined, in order to have a smooth zero transition. - * This is necessary, because the fast decay phase leads to the absolute value of the motor current being lower - * than the target current (see figure 17). If the zero offset is too low, the motor stands still for a short - * moment during current zero crossing, if it is set too high, it makes a larger microstep. - * Typically, a positive offset setting is required for optimum operation. - * - * \sa setSpreadCycleChoper() for other alternatives. - * \sa setRandomOffTime() for spreading the noise over a wider spectrum - */ - void setConstantOffTimeChopper(char constant_off_time, char blank_time, char fast_decay_time_setting, char sine_wave_offset, uint8_t use_current_comparator); - - /*! - * \brief Sets and configures with spread cycle chopper. - * \param constant_off_time The off time setting controls the minimum chopper frequency. For most applications an off time within the range of 5ΞΌs to 20ΞΌs will fit. Setting this parameter to zero completely disables all driver transistors and the motor can free-wheel. 0: chopper off, 1:15: off time setting (1 will work with minimum blank time of 24 clocks) - * \param blank_time Selects the comparator blank time. This time needs to safely cover the switching event and the duration of the ringing on the sense resistor. For most low current drivers, a setting of 1 or 2 is good. For high current applications with large MOSFETs, a setting of 2 or 3 will be required. 0 (min setting) … (3) amx setting - * \param hysteresis_start Hysteresis start setting. Please remark, that this value is an offset to the hysteresis end value. 1 … 8 - * \param hysteresis_end Hysteresis end setting. Sets the hysteresis end value after a number of decrements. Decrement interval time is controlled by hysteresis_decrement. The sum hysteresis_start + hysteresis_end must be <16. At a current setting CS of max. 30 (amplitude reduced to 240), the sum is not limited. - * \param hysteresis_decrement Hysteresis decrement setting. This setting determines the slope of the hysteresis during on time and during fast decay time. 0 (fast decrement) … 3 (slow decrement). - * - * The spreadCycle chopper scheme (pat.fil.) is a precise and simple to use chopper principle, which automatically determines - * the optimum fast decay portion for the motor. Anyhow, a number of settings can be made in order to optimally fit the driver - * to the motor. - * Each chopper cycle is comprised of an on-phase, a slow decay phase, a fast decay phase and a second slow decay phase. - * The slow decay phases limit the maximum chopper frequency and are important for low motor and driver power dissipation. - * The hysteresis start setting limits the chopper frequency by forcing the driver to introduce a minimum amount of - * current ripple into the motor coils. The motor inductivity determines the ability to follow a changing motor current. - * The duration of the on- and fast decay phase needs to cover at least the blank time, because the current comparator is - * disabled during this time. - * - * \sa setRandomOffTime() for spreading the noise over a wider spectrum - */ - void setSpreadCycleChopper(char constant_off_time, char blank_time, char hysteresis_start, char hysteresis_end, char hysteresis_decrement); - - /*! - * \brief Use random off time for noise reduction (0 for off, -1 for on). - * \param value 0 for off, -1 for on - * - * In a constant off time chopper scheme both coil choppers run freely, i.e. are not synchronized. - * The frequency of each chopper mainly depends on the coil current and the position dependant motor coil inductivity, - * thus it depends on the microstep position. With some motors a slightly audible beat can occur between the chopper - * frequencies, especially when they are near to each other. This typically occurs at a few microstep positions within - * each quarter wave. - * This effect normally is not audible when compared to mechanical noise generated by ball bearings, - * etc. Further factors which can cause a similar effect are a poor layout of sense resistor GND connection. - * In order to minimize the effect of a beat between both chopper frequencies, an internal random generator is provided. - * It modulates the slow decay time setting when switched on. The random off time feature further spreads the chopper spectrum, - * reducing electromagnetic emission on single frequencies. - */ - void setRandomOffTime(char value); - - /*! - * \brief set the maximum motor current in mA (1000 is 1 Amp) - * Keep in mind this is the maximum peak Current. The RMS current will be 1/sqrt(2) smaller. The actual current can also be smaller - * by employing CoolStep. - * \param current the maximum motor current in mA - * \sa getCurrent(), getCurrentCurrent() - */ - void setCurrent(uint16_t current); - - /*! - * \brief readout the motor maximum current in mA (1000 is an Amp) - * This is the maximum current. to get the current current - which may be affected by CoolStep us getCurrentCurrent() - * \return the maximum motor current in milli amps - * \sa getCurrentCurrent() - */ - uint16_t getCurrent(); - - /*! - * \brief set the StallGuard threshold in order to get sensible StallGuard readings. - * \param stallguard_threshold -64 … 63 the StallGuard threshold - * \param stallguard_filter_enabled 0 if the filter is disabled, -1 if it is enabled - * - * The StallGuard threshold is used to optimize the StallGuard reading to sensible values. It should be at 0 at - * the maximum allowable load on the otor (but not before). = is a good starting point (and the default) - * If you get Stall Gaurd readings of 0 without any load or with too little laod increase the value. - * If you get readings of 1023 even with load decrease the setting. - * - * If you switch on the filter the StallGuard reading is only updated each 4th full step to reduce the noise in the - * reading. - * - * \sa getCurrentStallGuardReading() to read out the current value. - */ - void setStallGuardThreshold(char stallguard_threshold, char stallguard_filter_enabled); - - /*! - * \brief reads out the StallGuard threshold - * \return a number between -64 and 63. - */ - char getStallGuardThreshold(); - - /*! - * \brief returns the current setting of the StallGuard filter - * \return 0 if not set, -1 if set - */ - char getStallGuardFilter(); - - /*! - * \brief This method configures the CoolStep smart energy operation. You must have a proper StallGuard configuration for the motor situation (current, voltage, speed) in rder to use this feature. - * \param lower_SG_threshold Sets the lower threshold for stallGuard2TM reading. Below this value, the motor current becomes increased. Allowed values are 0...480 - * \param SG_hysteresis Sets the distance between the lower and the upper threshold for stallGuard2TM reading. Above the upper threshold (which is lower_SG_threshold+SG_hysteresis+1) the motor current becomes decreased. Allowed values are 0...480 - * \param current_decrement_step_size Sets the current decrement steps. If the StallGuard value is above the threshold the current gets decremented by this step size. 0...32 - * \param current_increment_step_size Sets the current increment step. The current becomes incremented for each measured stallGuard2TM value below the lower threshold. 0...8 - * \param lower_current_limit Sets the lower motor current limit for coolStepTM operation by scaling the CS value. Values can be COOL_STEP_HALF_CS_LIMIT, COOL_STEP_QUARTER_CS_LIMIT - * The CoolStep smart energy operation automatically adjust the current sent into the motor according to the current load, - * read out by the StallGuard in order to provide the optimum torque with the minimal current consumption. - * You configure the CoolStep current regulator by defining upper and lower bounds of StallGuard readouts. If the readout is above the - * limit the current gets increased, below the limit the current gets decreased. - * You can specify the upper an lower threshold of the StallGuard readout in order to adjust the current. You can also set the number of - * StallGuard readings neccessary above or below the limit to get a more stable current adjustement. - * The current adjustement itself is configured by the number of steps the current gests in- or decreased and the absolut minimum current - * (1/2 or 1/4th otf the configured current). - * \sa COOL_STEP_HALF_CS_LIMIT, COOL_STEP_QUARTER_CS_LIMIT - */ - void setCoolStepConfiguration(uint16_t lower_SG_threshold, uint16_t SG_hysteresis, uint8_t current_decrement_step_size, - uint8_t current_increment_step_size, uint8_t lower_current_limit); - - /*! - * \brief enables or disables the CoolStep smart energy operation feature. It must be configured before enabling it. - * \param enabled true if CoolStep should be enabled, false if not. - * \sa setCoolStepConfiguration() - */ - void setCoolStepEnabled(boolean enabled); - - - /*! - * \brief check if the CoolStep feature is enabled - * \sa setCoolStepEnabled() - */ - boolean isCoolStepEnabled(); - - /*! - * \brief returns the lower StallGuard threshold for the CoolStep operation - * \sa setCoolStepConfiguration() - */ - uint16_t getCoolStepLowerSgThreshold(); - - /*! - * \brief returns the upper StallGuard threshold for the CoolStep operation - * \sa setCoolStepConfiguration() - */ - uint16_t getCoolStepUpperSgThreshold(); - - /*! - * \brief returns the number of StallGuard readings befor CoolStep adjusts the motor current. - * \sa setCoolStepConfiguration() - */ - uint8_t getCoolStepNumberOfSGReadings(); - - /*! - * \brief returns the increment steps for the current for the CoolStep operation - * \sa setCoolStepConfiguration() - */ - uint8_t getCoolStepCurrentIncrementSize(); - - /*! - * \brief returns the absolut minium current for the CoolStep operation - * \sa setCoolStepConfiguration() - * \sa COOL_STEP_HALF_CS_LIMIT, COOL_STEP_QUARTER_CS_LIMIT - */ - uint8_t getCoolStepLowerCurrentLimit(); - - /*! - * \brief Get the current microstep position for phase A - * \return The current microstep position for phase A 0…255 - * - * Keep in mind that this routine reads and writes a value via SPI - so this may take a bit time. - */ - int16_t getMotorPosition(); - - /*! - * \brief Reads the current StallGuard value. - * \return The current StallGuard value, lesser values indicate higher load, 0 means stall detected. - * Keep in mind that this routine reads and writes a value via SPI - so this may take a bit time. - * \sa setStallGuardThreshold() for tuning the readout to sensible ranges. - */ - int16_t getCurrentStallGuardReading(); - - /*! - * \brief Reads the current current setting value as fraction of the maximum current - * Returns values between 0 and 31, representing 1/32 to 32/32 (=1) - * \sa setCoolStepConfiguration() - */ - uint8_t getCurrentCSReading(); - - - /*! - *\brief a convenience method to determine if the current scaling uses 0.31V or 0.165V as reference. - *\return false if 0.13V is the reference voltage, true if 0.165V is used. - */ - boolean isCurrentScalingHalfed(); - - /*! - * \brief Reads the current current setting value and recalculates the absolute current in mA (1A would be 1000). - * This method calculates the currently used current setting (either by setting or by CoolStep) and reconstructs - * the current in mA by usinge the VSENSE and resistor value. This method uses floating point math - so it - * may not be the fastest. - * \sa getCurrentCSReading(), getResistor(), isCurrentScalingHalfed(), getCurrent() - */ - uint16_t getCurrentCurrent(); - - /*! - * \brief checks if there is a StallGuard warning in the last status - * \return 0 if there was no warning, -1 if there was some warning. - * Keep in mind that this method does not enforce a readout but uses the value of the last status readout. - * You may want to use getMotorPosition() or getCurrentStallGuardReading() to enforce an updated status readout. - * - * \sa setStallGuardThreshold() for tuning the readout to sensible ranges. - */ - boolean isStallGuardOverThreshold(); - - /*! - * \brief Return over temperature status of the last status readout - * return 0 is everything is OK, TMC26X_OVERTEMPERATURE_PREWARING if status is reached, TMC26X_OVERTEMPERATURE_SHUTDOWN is the chip is shutdown, -1 if the status is unknown. - * Keep in mind that this method does not enforce a readout but uses the value of the last status readout. - * You may want to use getMotorPosition() or getCurrentStallGuardReading() to enforce an updated status readout. - */ - char getOverTemperature(); - - /*! - * \brief Is motor channel A shorted to ground detected in the last status readout. - * \return true is yes, false if not. - * Keep in mind that this method does not enforce a readout but uses the value of the last status readout. - * You may want to use getMotorPosition() or getCurrentStallGuardReading() to enforce an updated status readout. - */ - - boolean isShortToGroundA(); - - /*! - * \brief Is motor channel B shorted to ground detected in the last status readout. - * \return true is yes, false if not. - * Keep in mind that this method does not enforce a readout but uses the value of the last status readout. - * You may want to use getMotorPosition() or getCurrentStallGuardReading() to enforce an updated status readout. - */ - boolean isShortToGroundB(); - /*! - * \brief iIs motor channel A connected according to the last statu readout. - * \return true is yes, false if not. - * Keep in mind that this method does not enforce a readout but uses the value of the last status readout. - * You may want to use getMotorPosition() or getCurrentStallGuardReading() to enforce an updated status readout. - */ - boolean isOpenLoadA(); - - /*! - * \brief iIs motor channel A connected according to the last statu readout. - * \return true is yes, false if not. - * Keep in mind that this method does not enforce a readout but uses the value of the last status readout. - * You may want to use getMotorPosition() or getCurrentStallGuardReading() to enforce an updated status readout. - */ - boolean isOpenLoadB(); - - /*! - * \brief Is chopper inactive since 2^20 clock cycles - defaults to ~0,08s - * \return true is yes, false if not. - * Keep in mind that this method does not enforce a readout but uses the value of the last status readout. - * You may want to use getMotorPosition() or getCurrentStallGuardReading() to enforce an updated status readout. - */ - boolean isStandStill(); - - /*! - * \brief checks if there is a StallGuard warning in the last status - * \return 0 if there was no warning, -1 if there was some warning. - * Keep in mind that this method does not enforce a readout but uses the value of the last status readout. - * You may want to use getMotorPosition() or getCurrentStallGuardReading() to enforce an updated status readout. - * - * \sa isStallGuardOverThreshold() - * TODO why? - * - * \sa setStallGuardThreshold() for tuning the readout to sensible ranges. - */ - boolean isStallGuardReached(); - - /*! - *\brief enables or disables the motor driver bridges. If disabled the motor can run freely. If enabled not. - *\param enabled a boolean value true if the motor should be enabled, false otherwise. - */ - void setEnabled(boolean enabled); - - /*! - *\brief checks if the output bridges are enabled. If the bridges are not enabled the motor can run freely - *\return true if the bridges and by that the motor driver are enabled, false if not. - *\sa setEnabled() - */ - boolean isEnabled(); - - /*! - * \brief Manually read out the status register - * This function sends a byte to the motor driver in order to get the current readout. The parameter read_value - * seletcs which value will get returned. If the read_vlaue changes in respect to the previous readout this method - * automatically send two bytes to the motor: one to set the redout and one to get the actual readout. So this method - * may take time to send and read one or two bits - depending on the previous readout. - * \param read_value selects which value to read out (0..3). You can use the defines TMC26X_READOUT_POSITION, TMC_262_READOUT_STALLGUARD, or TMC_262_READOUT_CURRENT - * \sa TMC26X_READOUT_POSITION, TMC_262_READOUT_STALLGUARD, TMC_262_READOUT_CURRENT - */ - void readStatus(char read_value); - - /*! - * \brief Returns the current sense resistor value in milliohm. - * The default value of ,15 Ohm will return 150. - */ - int16_t getResistor(); - - /*! - * \brief Prints out all the information that can be found in the last status read out - it does not force a status readout. - * The result is printed via Serial - */ - void debugLastStatus(); - - /*! - * \brief library version - * \return the version number as int. - */ - int16_t version(); - - private: - uint16_t steps_left; // The steps the motor has to do to complete the movement - int16_t direction; // Direction of rotation - uint32_t step_delay; // Delay between steps, in ms, based on speed - int16_t number_of_steps; // Total number of steps this motor can take - uint16_t speed; // Store the current speed in order to change the speed after changing microstepping - uint16_t resistor; // Current sense resitor value in milliohm - - uint32_t last_step_time, // Timestamp (ms) of the last step - next_step_time; // Timestamp (ms) of the next step - - // Driver control register copies to easily set & modify the registers - uint32_t driver_control_register_value, - chopper_config_register, - cool_step_register_value, - stallguard2_current_register_value, - driver_configuration_register_value, - driver_status_result; // The driver status result - - // Helper routione to get the top 10 bit of the readout - inline int16_t getReadoutValue(); - - // The pins for the stepper driver - uint8_t cs_pin, step_pin, dir_pin; - - // Status values - boolean started; // If the stepper has been started yet - int16_t microsteps; // The current number of micro steps - char constant_off_time; // We need to remember this value in order to enable and disable the motor - uint8_t cool_step_lower_threshold; // we need to remember the threshold to enable and disable the CoolStep feature - boolean cool_step_enabled; // We need to remember this to configure the coolstep if it si enabled - - // SPI sender - inline void send262(uint32_t datagram); -}; diff --git a/Marlin/src/HAL/STM32_F4_F7/STM32F7/timers.cpp b/Marlin/src/HAL/STM32_F4_F7/STM32F7/timers.cpp deleted file mode 100644 index f7ded7454d..0000000000 --- a/Marlin/src/HAL/STM32_F4_F7/STM32F7/timers.cpp +++ /dev/null @@ -1,129 +0,0 @@ -/** - * Marlin 3D Printer Firmware - * - * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com - * Copyright (c) 2015-2016 Nico Tonnhofer wurstnase.reprap@gmail.com - * - * 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 . - * - */ -#if defined(STM32GENERIC) && defined(STM32F7) - -#include "../../../inc/MarlinConfig.h" - -// ------------------------ -// Local defines -// ------------------------ - -#define NUM_HARDWARE_TIMERS 2 - -//#define PRESCALER 1 - -// ------------------------ -// Private Variables -// ------------------------ - -tTimerConfig timerConfig[NUM_HARDWARE_TIMERS]; - -// ------------------------ -// Public functions -// ------------------------ - -bool timers_initialized[NUM_HARDWARE_TIMERS] = { false }; - -void HAL_timer_start(const uint8_t timer_num, const uint32_t frequency) { - - if (!timers_initialized[timer_num]) { - switch (timer_num) { - case STEP_TIMER_NUM: - //STEPPER TIMER TIM5 //use a 32bit timer - __HAL_RCC_TIM5_CLK_ENABLE(); - timerConfig[0].timerdef.Instance = TIM5; - timerConfig[0].timerdef.Init.Prescaler = (STEPPER_TIMER_PRESCALE); - timerConfig[0].timerdef.Init.CounterMode = TIM_COUNTERMODE_UP; - timerConfig[0].timerdef.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; - timerConfig[0].IRQ_Id = TIM5_IRQn; - timerConfig[0].callback = (uint32_t)TC5_Handler; - HAL_NVIC_SetPriority(timerConfig[0].IRQ_Id, 1, 0); - #if PIN_EXISTS(STEPPER_ENABLE) - OUT_WRITE(STEPPER_ENABLE_PIN, HIGH); - #endif - break; - case TEMP_TIMER_NUM: - //TEMP TIMER TIM7 // any available 16bit Timer (1 already used for PWM) - __HAL_RCC_TIM7_CLK_ENABLE(); - timerConfig[1].timerdef.Instance = TIM7; - timerConfig[1].timerdef.Init.Prescaler = (TEMP_TIMER_PRESCALE); - timerConfig[1].timerdef.Init.CounterMode = TIM_COUNTERMODE_UP; - timerConfig[1].timerdef.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; - timerConfig[1].IRQ_Id = TIM7_IRQn; - timerConfig[1].callback = (uint32_t)TC7_Handler; - HAL_NVIC_SetPriority(timerConfig[1].IRQ_Id, 2, 0); - break; - } - timers_initialized[timer_num] = true; - } - - timerConfig[timer_num].timerdef.Init.Period = (((HAL_TIMER_RATE) / timerConfig[timer_num].timerdef.Init.Prescaler) / frequency) - 1; - - if (HAL_TIM_Base_Init(&timerConfig[timer_num].timerdef) == HAL_OK) - HAL_TIM_Base_Start_IT(&timerConfig[timer_num].timerdef); -} - -//forward the interrupt -extern "C" void TIM5_IRQHandler() { - ((void(*)())timerConfig[0].callback)(); -} -extern "C" void TIM7_IRQHandler() { - ((void(*)())timerConfig[1].callback)(); -} - -void HAL_timer_set_compare(const uint8_t timer_num, const uint32_t compare) { - __HAL_TIM_SetAutoreload(&timerConfig[timer_num].timerdef, compare); -} - -void HAL_timer_enable_interrupt(const uint8_t timer_num) { - HAL_NVIC_EnableIRQ(timerConfig[timer_num].IRQ_Id); -} - -void HAL_timer_disable_interrupt(const uint8_t timer_num) { - HAL_NVIC_DisableIRQ(timerConfig[timer_num].IRQ_Id); - - // We NEED memory barriers to ensure Interrupts are actually disabled! - // ( https://dzone.com/articles/nvic-disabling-interrupts-on-arm-cortex-m-and-the ) - __DSB(); - __ISB(); -} - -hal_timer_t HAL_timer_get_compare(const uint8_t timer_num) { - return __HAL_TIM_GetAutoreload(&timerConfig[timer_num].timerdef); -} - -uint32_t HAL_timer_get_count(const uint8_t timer_num) { - return __HAL_TIM_GetCounter(&timerConfig[timer_num].timerdef); -} - -void HAL_timer_isr_prologue(const uint8_t timer_num) { - if (__HAL_TIM_GET_FLAG(&timerConfig[timer_num].timerdef, TIM_FLAG_UPDATE) == SET) { - __HAL_TIM_CLEAR_FLAG(&timerConfig[timer_num].timerdef, TIM_FLAG_UPDATE); - } -} - -bool HAL_timer_interrupt_enabled(const uint8_t timer_num) { - const uint32_t IRQ_Id = uint32_t(timerConfig[timer_num].IRQ_Id); - return NVIC->ISER[IRQ_Id >> 5] & _BV32(IRQ_Id & 0x1F); -} - -#endif // STM32GENERIC && STM32F7 diff --git a/Marlin/src/HAL/STM32_F4_F7/STM32F7/timers.h b/Marlin/src/HAL/STM32_F4_F7/STM32F7/timers.h deleted file mode 100644 index d2f78259d6..0000000000 --- a/Marlin/src/HAL/STM32_F4_F7/STM32F7/timers.h +++ /dev/null @@ -1,107 +0,0 @@ -/** - * Marlin 3D Printer Firmware - * - * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com - * Copyright (c) 2017 Victor Perez - * - * 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 - -// ------------------------ -// Defines -// ------------------------ - -#define FORCE_INLINE __attribute__((always_inline)) inline - -#define hal_timer_t uint32_t // TODO: One is 16-bit, one 32-bit - does this need to be checked? -#define HAL_TIMER_TYPE_MAX 0xFFFF - -#define HAL_TIMER_RATE (HAL_RCC_GetSysClockFreq() / 2) // frequency of timer peripherals - -#ifndef STEP_TIMER_NUM - #define STEP_TIMER_NUM 0 // Timer Index for Stepper -#endif -#ifndef PULSE_TIMER_NUM - #define PULSE_TIMER_NUM STEP_TIMER_NUM -#endif -#ifndef TEMP_TIMER_NUM - #define TEMP_TIMER_NUM 1 // Timer Index for Temperature -#endif - -#define TEMP_TIMER_FREQUENCY 1000 // temperature interrupt frequency -#define TEMP_TIMER_PRESCALE 1000 // prescaler for setting Temp timer, 72Khz - -#define STEPPER_TIMER_PRESCALE 54 // was 40,prescaler for setting stepper timer, 2Mhz -#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 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(STEP_TIMER_NUM) -#define DISABLE_STEPPER_DRIVER_INTERRUPT() HAL_timer_disable_interrupt(STEP_TIMER_NUM) - -#define ENABLE_TEMPERATURE_INTERRUPT() HAL_timer_enable_interrupt(TEMP_TIMER_NUM) -#define DISABLE_TEMPERATURE_INTERRUPT() HAL_timer_disable_interrupt(TEMP_TIMER_NUM) - -#define STEPPER_ISR_ENABLED() HAL_timer_interrupt_enabled(STEP_TIMER_NUM) -#define TEMP_ISR_ENABLED() HAL_timer_interrupt_enabled(TEMP_TIMER_NUM) - -// TODO change this - -extern void TC5_Handler(); -extern void TC7_Handler(); -#ifndef HAL_STEP_TIMER_ISR - #define HAL_STEP_TIMER_ISR() void TC5_Handler() -#endif -#ifndef HAL_TEMP_TIMER_ISR - #define HAL_TEMP_TIMER_ISR() void TC7_Handler() -#endif - -// ------------------------ -// Types -// ------------------------ - -typedef struct { - TIM_HandleTypeDef timerdef; - IRQn_Type IRQ_Id; - uint32_t callback; -} tTimerConfig; - -// ------------------------ -// Public Variables -// ------------------------ - -//extern const tTimerConfig timerConfig[]; - -// ------------------------ -// Public functions -// ------------------------ - -void HAL_timer_start(const uint8_t timer_num, const uint32_t frequency); -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); - -void HAL_timer_set_compare(const uint8_t timer_num, const uint32_t compare); -hal_timer_t HAL_timer_get_compare(const uint8_t timer_num); -uint32_t HAL_timer_get_count(const uint8_t timer_num); -void HAL_timer_isr_prologue(const uint8_t timer_num); -#define HAL_timer_isr_epilogue(TIMER_NUM) diff --git a/Marlin/src/HAL/STM32_F4_F7/eeprom_emul.cpp b/Marlin/src/HAL/STM32_F4_F7/eeprom_emul.cpp deleted file mode 100644 index e0726c7cd5..0000000000 --- a/Marlin/src/HAL/STM32_F4_F7/eeprom_emul.cpp +++ /dev/null @@ -1,535 +0,0 @@ -/** - ****************************************************************************** - * @file eeprom_emul.cpp - * @author MCD Application Team - * @version V1.2.6 - * @date 04-November-2016 - * @brief This file provides all the EEPROM emulation firmware functions. - ****************************************************************************** - * @attention - * - * Copyright Β© 2016 STMicroelectronics International N.V. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted, provided that the following conditions are met: - * - * 1. Redistribution of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. Neither the name of STMicroelectronics nor the names of other - * contributors to this software may be used to endorse or promote products - * derived from this software without specific written permission. - * 4. This software, including modifications and/or derivative works of this - * software, must execute solely and exclusively on microcontroller or - * microprocessor devices manufactured by or for STMicroelectronics. - * 5. Redistribution and use of this software other than as permitted under - * this license is void and will automatically terminate your rights under - * this license. - * - * THIS SOFTWARE IS PROVIDED BY STMICROELECTRONICS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS, IMPLIED OR STATUTORY WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A - * PARTICULAR PURPOSE AND NON-INFRINGEMENT OF THIRD PARTY INTELLECTUAL PROPERTY - * RIGHTS ARE DISCLAIMED TO THE FULLEST EXTENT PERMITTED BY LAW. IN NO EVENT - * SHALL STMICROELECTRONICS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, - * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, - * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - ****************************************************************************** - */ -/** @addtogroup EEPROM_Emulation - * @{ - */ -#if defined(STM32GENERIC) && (defined(STM32F4) || defined(STM32F7)) - -#include "../../inc/MarlinConfig.h" - -#if ENABLED(FLASH_EEPROM_EMULATION) - -/* Includes ------------------------------------------------------------------*/ -#include "eeprom_emul.h" - -/* Private variables ---------------------------------------------------------*/ - -/* Global variable used to store variable value in read sequence */ -uint16_t DataVar = 0; - -/* Virtual address defined by the user: 0xFFFF value is prohibited */ -uint16_t VirtAddVarTab[NB_OF_VAR]; - -/* Private function prototypes -----------------------------------------------*/ - -static HAL_StatusTypeDef EE_Format(); -static uint16_t EE_FindValidPage(uint8_t Operation); -static uint16_t EE_VerifyPageFullWriteVariable(uint16_t VirtAddress, uint16_t Data); -static uint16_t EE_PageTransfer(uint16_t VirtAddress, uint16_t Data); -static uint16_t EE_VerifyPageFullyErased(uint32_t Address); - - /** - * @brief Restore the pages to a known good state in case of page's status - * corruption after a power loss. - * @param None. - * @retval - Flash error code: on write Flash error - * - FLASH_COMPLETE: on success - */ - -/* Private functions ---------------------------------------------------------*/ - -uint16_t EE_Initialize() { - /* Get Page0 and Page1 status */ - uint16_t PageStatus0 = (*(__IO uint16_t*)PAGE0_BASE_ADDRESS), - PageStatus1 = (*(__IO uint16_t*)PAGE1_BASE_ADDRESS); - - FLASH_EraseInitTypeDef pEraseInit; - pEraseInit.TypeErase = TYPEERASE_SECTORS; - pEraseInit.Sector = PAGE0_ID; - pEraseInit.NbSectors = 1; - pEraseInit.VoltageRange = VOLTAGE_RANGE; - - HAL_StatusTypeDef FlashStatus; // = HAL_OK - - /* Check for invalid header states and repair if necessary */ - uint32_t SectorError; - switch (PageStatus0) { - case ERASED: - if (PageStatus1 == VALID_PAGE) { /* Page0 erased, Page1 valid */ - /* Erase Page0 */ - if (!EE_VerifyPageFullyErased(PAGE0_BASE_ADDRESS)) { - /* As the last operation, simply return the result */ - return HAL_FLASHEx_Erase(&pEraseInit, &SectorError); - } - } - else if (PageStatus1 == RECEIVE_DATA) { /* Page0 erased, Page1 receive */ - /* Erase Page0 */ - if (!EE_VerifyPageFullyErased(PAGE0_BASE_ADDRESS)) { - HAL_StatusTypeDef fStat = HAL_FLASHEx_Erase(&pEraseInit, &SectorError); - /* If erase operation was failed, a Flash error code is returned */ - if (fStat != HAL_OK) return fStat; - } - /* Mark Page1 as valid */ - /* As the last operation, simply return the result */ - return HAL_FLASH_Program(TYPEPROGRAM_HALFWORD, PAGE1_BASE_ADDRESS, VALID_PAGE); - } - else { /* First EEPROM access (Page0&1 are erased) or invalid state -> format EEPROM */ - /* Erase both Page0 and Page1 and set Page0 as valid page */ - /* As the last operation, simply return the result */ - return EE_Format(); - } - break; - - case RECEIVE_DATA: - if (PageStatus1 == VALID_PAGE) { /* Page0 receive, Page1 valid */ - /* Transfer data from Page1 to Page0 */ - int16_t x = -1; - for (uint16_t VarIdx = 0; VarIdx < NB_OF_VAR; VarIdx++) { - if (( *(__IO uint16_t*)(PAGE0_BASE_ADDRESS + 6)) == VirtAddVarTab[VarIdx]) - x = VarIdx; - if (VarIdx != x) { - /* Read the last variables' updates */ - uint16_t ReadStatus = EE_ReadVariable(VirtAddVarTab[VarIdx], &DataVar); - /* In case variable corresponding to the virtual address was found */ - if (ReadStatus != 0x1) { - /* Transfer the variable to the Page0 */ - uint16_t EepromStatus = EE_VerifyPageFullWriteVariable(VirtAddVarTab[VarIdx], DataVar); - /* If program operation was failed, a Flash error code is returned */ - if (EepromStatus != HAL_OK) return EepromStatus; - } - } - } - /* Mark Page0 as valid */ - FlashStatus = HAL_FLASH_Program(TYPEPROGRAM_HALFWORD, PAGE0_BASE_ADDRESS, VALID_PAGE); - /* If program operation was failed, a Flash error code is returned */ - if (FlashStatus != HAL_OK) return FlashStatus; - pEraseInit.Sector = PAGE1_ID; - pEraseInit.NbSectors = 1; - pEraseInit.VoltageRange = VOLTAGE_RANGE; - /* Erase Page1 */ - if (!EE_VerifyPageFullyErased(PAGE1_BASE_ADDRESS)) { - /* As the last operation, simply return the result */ - return HAL_FLASHEx_Erase(&pEraseInit, &SectorError); - } - } - else if (PageStatus1 == ERASED) { /* Page0 receive, Page1 erased */ - pEraseInit.Sector = PAGE1_ID; - pEraseInit.NbSectors = 1; - pEraseInit.VoltageRange = VOLTAGE_RANGE; - /* Erase Page1 */ - if (!EE_VerifyPageFullyErased(PAGE1_BASE_ADDRESS)) { - HAL_StatusTypeDef fStat = HAL_FLASHEx_Erase(&pEraseInit, &SectorError); - /* If erase operation was failed, a Flash error code is returned */ - if (fStat != HAL_OK) return fStat; - } - /* Mark Page0 as valid */ - /* As the last operation, simply return the result */ - return HAL_FLASH_Program(TYPEPROGRAM_HALFWORD, PAGE0_BASE_ADDRESS, VALID_PAGE); - } - else { /* Invalid state -> format eeprom */ - /* Erase both Page0 and Page1 and set Page0 as valid page */ - /* As the last operation, simply return the result */ - return EE_Format(); - } - break; - - case VALID_PAGE: - if (PageStatus1 == VALID_PAGE) { /* Invalid state -> format eeprom */ - /* Erase both Page0 and Page1 and set Page0 as valid page */ - FlashStatus = EE_Format(); - /* If erase/program operation was failed, a Flash error code is returned */ - if (FlashStatus != HAL_OK) return FlashStatus; - } - else if (PageStatus1 == ERASED) { /* Page0 valid, Page1 erased */ - pEraseInit.Sector = PAGE1_ID; - pEraseInit.NbSectors = 1; - pEraseInit.VoltageRange = VOLTAGE_RANGE; - /* Erase Page1 */ - if (!EE_VerifyPageFullyErased(PAGE1_BASE_ADDRESS)) { - FlashStatus = HAL_FLASHEx_Erase(&pEraseInit, &SectorError); - /* If erase operation was failed, a Flash error code is returned */ - if (FlashStatus != HAL_OK) return FlashStatus; - } - } - else { /* Page0 valid, Page1 receive */ - /* Transfer data from Page0 to Page1 */ - int16_t x = -1; - for (uint16_t VarIdx = 0; VarIdx < NB_OF_VAR; VarIdx++) { - if ((*(__IO uint16_t*)(PAGE1_BASE_ADDRESS + 6)) == VirtAddVarTab[VarIdx]) - x = VarIdx; - - if (VarIdx != x) { - /* Read the last variables' updates */ - uint16_t ReadStatus = EE_ReadVariable(VirtAddVarTab[VarIdx], &DataVar); - /* In case variable corresponding to the virtual address was found */ - if (ReadStatus != 0x1) { - /* Transfer the variable to the Page1 */ - uint16_t EepromStatus = EE_VerifyPageFullWriteVariable(VirtAddVarTab[VarIdx], DataVar); - /* If program operation was failed, a Flash error code is returned */ - if (EepromStatus != HAL_OK) return EepromStatus; - } - } - } - /* Mark Page1 as valid */ - FlashStatus = HAL_FLASH_Program(TYPEPROGRAM_HALFWORD, PAGE1_BASE_ADDRESS, VALID_PAGE); - /* If program operation was failed, a Flash error code is returned */ - if (FlashStatus != HAL_OK) return FlashStatus; - pEraseInit.Sector = PAGE0_ID; - pEraseInit.NbSectors = 1; - pEraseInit.VoltageRange = VOLTAGE_RANGE; - /* Erase Page0 */ - if (!EE_VerifyPageFullyErased(PAGE0_BASE_ADDRESS)) { - /* As the last operation, simply return the result */ - return HAL_FLASHEx_Erase(&pEraseInit, &SectorError); - } - } - break; - - default: /* Any other state -> format eeprom */ - /* Erase both Page0 and Page1 and set Page0 as valid page */ - /* As the last operation, simply return the result */ - return EE_Format(); - } - - return HAL_OK; -} - -/** - * @brief Verify if specified page is fully erased. - * @param Address: page address - * This parameter can be one of the following values: - * @arg PAGE0_BASE_ADDRESS: Page0 base address - * @arg PAGE1_BASE_ADDRESS: Page1 base address - * @retval page fully erased status: - * - 0: if Page not erased - * - 1: if Page erased - */ -uint16_t EE_VerifyPageFullyErased(uint32_t Address) { - uint32_t ReadStatus = 1; - /* Check each active page address starting from end */ - while (Address <= PAGE0_END_ADDRESS) { - /* Get the current location content to be compared with virtual address */ - uint16_t AddressValue = (*(__IO uint16_t*)Address); - /* Compare the read address with the virtual address */ - if (AddressValue != ERASED) { - /* In case variable value is read, reset ReadStatus flag */ - ReadStatus = 0; - break; - } - /* Next address location */ - Address += 4; - } - /* Return ReadStatus value: (0: Page not erased, 1: Sector erased) */ - return ReadStatus; -} - -/** - * @brief Returns the last stored variable data, if found, which correspond to - * the passed virtual address - * @param VirtAddress: Variable virtual address - * @param Data: Global variable contains the read variable value - * @retval Success or error status: - * - 0: if variable was found - * - 1: if the variable was not found - * - NO_VALID_PAGE: if no valid page was found. - */ -uint16_t EE_ReadVariable(uint16_t VirtAddress, uint16_t* Data) { - uint16_t ReadStatus = 1; - - /* Get active Page for read operation */ - uint16_t ValidPage = EE_FindValidPage(READ_FROM_VALID_PAGE); - - /* Check if there is no valid page */ - if (ValidPage == NO_VALID_PAGE) return NO_VALID_PAGE; - - /* Get the valid Page start and end Addresses */ - uint32_t PageStartAddress = uint32_t(EEPROM_START_ADDRESS) + uint32_t(ValidPage * (PAGE_SIZE)), - Address = PageStartAddress + PAGE_SIZE - 2; - - /* Check each active page address starting from end */ - while (Address > PageStartAddress + 2) { - /* Get the current location content to be compared with virtual address */ - uint16_t AddressValue = (*(__IO uint16_t*)Address); - - /* Compare the read address with the virtual address */ - if (AddressValue == VirtAddress) { - /* Get content of Address-2 which is variable value */ - *Data = (*(__IO uint16_t*)(Address - 2)); - /* In case variable value is read, reset ReadStatus flag */ - ReadStatus = 0; - break; - } - else /* Next address location */ - Address -= 4; - } - /* Return ReadStatus value: (0: variable exist, 1: variable doesn't exist) */ - return ReadStatus; -} - -/** - * @brief Writes/upadtes variable data in EEPROM. - * @param VirtAddress: Variable virtual address - * @param Data: 16 bit data to be written - * @retval Success or error status: - * - FLASH_COMPLETE: on success - * - PAGE_FULL: if valid page is full - * - NO_VALID_PAGE: if no valid page was found - * - Flash error code: on write Flash error - */ -uint16_t EE_WriteVariable(uint16_t VirtAddress, uint16_t Data) { - /* Write the variable virtual address and value in the EEPROM */ - uint16_t Status = EE_VerifyPageFullWriteVariable(VirtAddress, Data); - - /* In case the EEPROM active page is full */ - if (Status == PAGE_FULL) /* Perform Page transfer */ - Status = EE_PageTransfer(VirtAddress, Data); - - /* Return last operation status */ - return Status; -} - -/** - * @brief Erases PAGE and PAGE1 and writes VALID_PAGE header to PAGE - * @param None - * @retval Status of the last operation (Flash write or erase) done during - * EEPROM formatting - */ -static HAL_StatusTypeDef EE_Format() { - FLASH_EraseInitTypeDef pEraseInit; - pEraseInit.TypeErase = FLASH_TYPEERASE_SECTORS; - pEraseInit.Sector = PAGE0_ID; - pEraseInit.NbSectors = 1; - pEraseInit.VoltageRange = VOLTAGE_RANGE; - - HAL_StatusTypeDef FlashStatus; // = HAL_OK - - /* Erase Page0 */ - if (!EE_VerifyPageFullyErased(PAGE0_BASE_ADDRESS)) { - uint32_t SectorError; - FlashStatus = HAL_FLASHEx_Erase(&pEraseInit, &SectorError); - /* If erase operation was failed, a Flash error code is returned */ - if (FlashStatus != HAL_OK) return FlashStatus; - } - /* Set Page0 as valid page: Write VALID_PAGE at Page0 base address */ - FlashStatus = HAL_FLASH_Program(TYPEPROGRAM_HALFWORD, PAGE0_BASE_ADDRESS, VALID_PAGE); - /* If program operation was failed, a Flash error code is returned */ - if (FlashStatus != HAL_OK) return FlashStatus; - - pEraseInit.Sector = PAGE1_ID; - /* Erase Page1 */ - if (!EE_VerifyPageFullyErased(PAGE1_BASE_ADDRESS)) { - /* As the last operation, just return the result code */ - uint32_t SectorError; - return HAL_FLASHEx_Erase(&pEraseInit, &SectorError); - } - - return HAL_OK; -} - -/** - * @brief Find valid Page for write or read operation - * @param Operation: operation to achieve on the valid page. - * This parameter can be one of the following values: - * @arg READ_FROM_VALID_PAGE: read operation from valid page - * @arg WRITE_IN_VALID_PAGE: write operation from valid page - * @retval Valid page number (PAGE or PAGE1) or NO_VALID_PAGE in case - * of no valid page was found - */ -static uint16_t EE_FindValidPage(uint8_t Operation) { - /* Get Page0 and Page1 actual status */ - uint16_t PageStatus0 = (*(__IO uint16_t*)PAGE0_BASE_ADDRESS), - PageStatus1 = (*(__IO uint16_t*)PAGE1_BASE_ADDRESS); - - /* Write or read operation */ - switch (Operation) { - case WRITE_IN_VALID_PAGE: /* ---- Write operation ---- */ - if (PageStatus1 == VALID_PAGE) { - /* Page0 receiving data */ - return (PageStatus0 == RECEIVE_DATA) ? PAGE0 : PAGE1; - } - else if (PageStatus0 == VALID_PAGE) { - /* Page1 receiving data */ - return (PageStatus1 == RECEIVE_DATA) ? PAGE1 : PAGE0; - } - else - return NO_VALID_PAGE; /* No valid Page */ - - case READ_FROM_VALID_PAGE: /* ---- Read operation ---- */ - if (PageStatus0 == VALID_PAGE) - return PAGE0; /* Page0 valid */ - else if (PageStatus1 == VALID_PAGE) - return PAGE1; /* Page1 valid */ - else - return NO_VALID_PAGE; /* No valid Page */ - - default: - return PAGE0; /* Page0 valid */ - } -} - -/** - * @brief Verify if active page is full and Writes variable in EEPROM. - * @param VirtAddress: 16 bit virtual address of the variable - * @param Data: 16 bit data to be written as variable value - * @retval Success or error status: - * - FLASH_COMPLETE: on success - * - PAGE_FULL: if valid page is full - * - NO_VALID_PAGE: if no valid page was found - * - Flash error code: on write Flash error - */ -static uint16_t EE_VerifyPageFullWriteVariable(uint16_t VirtAddress, uint16_t Data) { - /* Get valid Page for write operation */ - uint16_t ValidPage = EE_FindValidPage(WRITE_IN_VALID_PAGE); - - /* Check if there is no valid page */ - if (ValidPage == NO_VALID_PAGE) return NO_VALID_PAGE; - - /* Get the valid Page start and end Addresses */ - uint32_t Address = uint32_t(EEPROM_START_ADDRESS) + uint32_t(ValidPage * (PAGE_SIZE)), - PageEndAddress = Address + PAGE_SIZE - 1; - - /* Check each active page address starting from begining */ - while (Address < PageEndAddress) { - /* Verify if Address and Address+2 contents are 0xFFFFFFFF */ - if ((*(__IO uint32_t*)Address) == 0xFFFFFFFF) { - /* Set variable data */ - HAL_StatusTypeDef FlashStatus = HAL_FLASH_Program(TYPEPROGRAM_HALFWORD, Address, Data); - /* If program operation was failed, a Flash error code is returned */ - if (FlashStatus != HAL_OK) return FlashStatus; - /* Set variable virtual address, return status */ - return HAL_FLASH_Program(TYPEPROGRAM_HALFWORD, Address + 2, VirtAddress); - } - else /* Next address location */ - Address += 4; - } - - /* Return PAGE_FULL in case the valid page is full */ - return PAGE_FULL; -} - -/** - * @brief Transfers last updated variables data from the full Page to - * an empty one. - * @param VirtAddress: 16 bit virtual address of the variable - * @param Data: 16 bit data to be written as variable value - * @retval Success or error status: - * - FLASH_COMPLETE: on success - * - PAGE_FULL: if valid page is full - * - NO_VALID_PAGE: if no valid page was found - * - Flash error code: on write Flash error - */ -static uint16_t EE_PageTransfer(uint16_t VirtAddress, uint16_t Data) { - /* Get active Page for read operation */ - uint16_t ValidPage = EE_FindValidPage(READ_FROM_VALID_PAGE); - uint32_t NewPageAddress = EEPROM_START_ADDRESS; - uint16_t OldPageId = 0; - - if (ValidPage == PAGE1) { /* Page1 valid */ - /* New page address where variable will be moved to */ - NewPageAddress = PAGE0_BASE_ADDRESS; - /* Old page ID where variable will be taken from */ - OldPageId = PAGE1_ID; - } - else if (ValidPage == PAGE0) { /* Page0 valid */ - /* New page address where variable will be moved to */ - NewPageAddress = PAGE1_BASE_ADDRESS; - /* Old page ID where variable will be taken from */ - OldPageId = PAGE0_ID; - } - else - return NO_VALID_PAGE; /* No valid Page */ - - /* Set the new Page status to RECEIVE_DATA status */ - HAL_StatusTypeDef FlashStatus = HAL_FLASH_Program(TYPEPROGRAM_HALFWORD, NewPageAddress, RECEIVE_DATA); - /* If program operation was failed, a Flash error code is returned */ - if (FlashStatus != HAL_OK) return FlashStatus; - - /* Write the variable passed as parameter in the new active page */ - uint16_t EepromStatus = EE_VerifyPageFullWriteVariable(VirtAddress, Data); - /* If program operation was failed, a Flash error code is returned */ - if (EepromStatus != HAL_OK) return EepromStatus; - - /* Transfer process: transfer variables from old to the new active page */ - for (uint16_t VarIdx = 0; VarIdx < NB_OF_VAR; VarIdx++) { - if (VirtAddVarTab[VarIdx] != VirtAddress) { /* Check each variable except the one passed as parameter */ - /* Read the other last variable updates */ - uint16_t ReadStatus = EE_ReadVariable(VirtAddVarTab[VarIdx], &DataVar); - /* In case variable corresponding to the virtual address was found */ - if (ReadStatus != 0x1) { - /* Transfer the variable to the new active page */ - EepromStatus = EE_VerifyPageFullWriteVariable(VirtAddVarTab[VarIdx], DataVar); - /* If program operation was failed, a Flash error code is returned */ - if (EepromStatus != HAL_OK) return EepromStatus; - } - } - } - - FLASH_EraseInitTypeDef pEraseInit; - pEraseInit.TypeErase = TYPEERASE_SECTORS; - pEraseInit.Sector = OldPageId; - pEraseInit.NbSectors = 1; - pEraseInit.VoltageRange = VOLTAGE_RANGE; - - /* Erase the old Page: Set old Page status to ERASED status */ - uint32_t SectorError; - FlashStatus = HAL_FLASHEx_Erase(&pEraseInit, &SectorError); - /* If erase operation was failed, a Flash error code is returned */ - if (FlashStatus != HAL_OK) return FlashStatus; - - /* Set new Page status to VALID_PAGE status */ - /* As the last operation, just return the result code */ - return HAL_FLASH_Program(TYPEPROGRAM_HALFWORD, NewPageAddress, VALID_PAGE); -} - -#endif // FLASH_EEPROM_EMULATION -#endif // STM32GENERIC && (STM32F4 || STM32F7) - -/** - * @} - */ - -/******************* (C) COPYRIGHT 2011 STMicroelectronics *****END OF FILE****/ diff --git a/Marlin/src/HAL/STM32_F4_F7/eeprom_emul.h b/Marlin/src/HAL/STM32_F4_F7/eeprom_emul.h deleted file mode 100644 index 84c4c6e3d2..0000000000 --- a/Marlin/src/HAL/STM32_F4_F7/eeprom_emul.h +++ /dev/null @@ -1,114 +0,0 @@ -/****************************************************************************** - * @file eeprom_emul.h - * @author MCD Application Team - * @version V1.2.6 - * @date 04-November-2016 - * @brief This file contains all the functions prototypes for the EEPROM - * emulation firmware library. - ****************************************************************************** - * @attention - * - * Copyright Β© 2016 STMicroelectronics International N.V. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted, provided that the following conditions are met: - * - * 1. Redistribution of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. Neither the name of STMicroelectronics nor the names of other - * contributors to this software may be used to endorse or promote products - * derived from this software without specific written permission. - * 4. This software, including modifications and/or derivative works of this - * software, must execute solely and exclusively on microcontroller or - * microprocessor devices manufactured by or for STMicroelectronics. - * 5. Redistribution and use of this software other than as permitted under - * this license is void and will automatically terminate your rights under - * this license. - * - * THIS SOFTWARE IS PROVIDED BY STMICROELECTRONICS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS, IMPLIED OR STATUTORY WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A - * PARTICULAR PURPOSE AND NON-INFRINGEMENT OF THIRD PARTY INTELLECTUAL PROPERTY - * RIGHTS ARE DISCLAIMED TO THE FULLEST EXTENT PERMITTED BY LAW. IN NO EVENT - * SHALL STMICROELECTRONICS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, - * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, - * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - ******************************************************************************/ -#pragma once - -// ------------------------ -// Includes -// ------------------------ - -#include "../../inc/MarlinConfig.h" - -/* Exported constants --------------------------------------------------------*/ -/* EEPROM emulation firmware error codes */ -#define EE_OK uint32_t(HAL_OK) -#define EE_ERROR uint32_t(HAL_ERROR) -#define EE_BUSY uint32_t(HAL_BUSY) -#define EE_TIMEOUT uint32_t(HAL_TIMEOUT) - -/* Define the size of the sectors to be used */ -#define PAGE_SIZE uint32_t(0x4000) /* Page size = 16KByte */ - -/* Device voltage range supposed to be [2.7V to 3.6V], the operation will - be done by word */ -#define VOLTAGE_RANGE uint8_t(VOLTAGE_RANGE_3) - -/* EEPROM start address in Flash */ -#ifdef STM32F7 - #define EEPROM_START_ADDRESS uint32_t(0x08100000) /* EEPROM emulation start address: - from sector2 : after 16KByte of used - Flash memory */ -#else - #define EEPROM_START_ADDRESS uint32_t(0x08078000) /* EEPROM emulation start address: - after 480KByte of used Flash memory */ -#endif - -/* Pages 0 and 1 base and end addresses */ -#define PAGE0_BASE_ADDRESS uint32_t(EEPROM_START_ADDRESS + 0x0000) -#define PAGE0_END_ADDRESS uint32_t(EEPROM_START_ADDRESS + PAGE_SIZE - 1) -#define PAGE0_ID FLASH_SECTOR_1 - -#define PAGE1_BASE_ADDRESS uint32_t(EEPROM_START_ADDRESS + 0x4000) -#define PAGE1_END_ADDRESS uint32_t(EEPROM_START_ADDRESS + 2 * (PAGE_SIZE) - 1) -#define PAGE1_ID FLASH_SECTOR_2 - -/* Used Flash pages for EEPROM emulation */ -#define PAGE0 uint16_t(0x0000) -#define PAGE1 uint16_t(0x0001) /* Page nb between PAGE0_BASE_ADDRESS & PAGE1_BASE_ADDRESS*/ - -/* No valid page define */ -#define NO_VALID_PAGE uint16_t(0x00AB) - -/* Page status definitions */ -#define ERASED uint16_t(0xFFFF) /* Page is empty */ -#define RECEIVE_DATA uint16_t(0xEEEE) /* Page is marked to receive data */ -#define VALID_PAGE uint16_t(0x0000) /* Page containing valid data */ - -/* Valid pages in read and write defines */ -#define READ_FROM_VALID_PAGE uint8_t(0x00) -#define WRITE_IN_VALID_PAGE uint8_t(0x01) - -/* Page full define */ -#define PAGE_FULL uint8_t(0x80) - -/* Variables' number */ -#define NB_OF_VAR uint16_t(4096) - -/* Exported functions ------------------------------------------------------- */ -uint16_t EE_Initialize(); -uint16_t EE_ReadVariable(uint16_t VirtAddress, uint16_t* Data); -uint16_t EE_WriteVariable(uint16_t VirtAddress, uint16_t Data); - -/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ diff --git a/Marlin/src/HAL/STM32_F4_F7/eeprom_flash.cpp b/Marlin/src/HAL/STM32_F4_F7/eeprom_flash.cpp deleted file mode 100644 index 00b808fd48..0000000000 --- a/Marlin/src/HAL/STM32_F4_F7/eeprom_flash.cpp +++ /dev/null @@ -1,111 +0,0 @@ -/** - * Marlin 3D Printer Firmware - * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * - * 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 . - * - */ -#if defined(STM32GENERIC) && (defined(STM32F4) || defined(STM32F7)) - -#include "../../inc/MarlinConfig.h" - -#if ENABLED(FLASH_EEPROM_EMULATION) - -#include "../shared/eeprom_api.h" -#include "eeprom_emul.h" - -// FLASH_FLAG_PGSERR (Programming Sequence Error) was renamed to -// FLASH_FLAG_ERSERR (Erasing Sequence Error) in STM32F4/7 - -#ifdef STM32F7 - #define FLASH_FLAG_PGSERR FLASH_FLAG_ERSERR -#else - //#define FLASH_FLAG_PGSERR FLASH_FLAG_ERSERR -#endif - -void ee_write_byte(uint8_t *pos, unsigned char value) { - 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); - - const unsigned eeprom_address = (unsigned)pos; - if (EE_WriteVariable(eeprom_address, uint16_t(value)) != EE_OK) - for (;;) HAL_Delay(1); // Spin forever until watchdog reset - - HAL_FLASH_Lock(); -} - -uint8_t ee_read_byte(uint8_t *pos) { - uint16_t data = 0xFF; - const unsigned eeprom_address = (unsigned)pos; - (void)EE_ReadVariable(eeprom_address, &data); // Data unchanged on error - return uint8_t(data); -} - -#ifndef MARLIN_EEPROM_SIZE - #error "MARLIN_EEPROM_SIZE is required for Flash-based EEPROM." -#endif -size_t PersistentStore::capacity() { return MARLIN_EEPROM_SIZE; } - -bool PersistentStore::access_finish() { return true; } - -bool PersistentStore::access_start() { - static bool ee_initialized = false; - if (!ee_initialized) { - 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); - - /* EEPROM Init */ - if (EE_Initialize() != EE_OK) - for (;;) HAL_Delay(1); // Spin forever until watchdog reset - - HAL_FLASH_Lock(); - ee_initialized = true; - } - return true; -} - -bool PersistentStore::write_data(int &pos, const uint8_t *value, size_t size, uint16_t *crc) { - while (size--) { - uint8_t * const p = (uint8_t * const)pos; - uint8_t v = *value; - // EEPROM has only ~100,000 write cycles, - // so only write bytes that have changed! - if (v != ee_read_byte(p)) { - ee_write_byte(p, v); - if (ee_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 { - uint8_t c = ee_read_byte((uint8_t*)pos); - if (writing) *value = c; - crc16(crc, &c, 1); - pos++; - value++; - } while (--size); - return false; -} - -#endif // FLASH_EEPROM_EMULATION -#endif // STM32GENERIC && (STM32F4 || STM32F7) diff --git a/Marlin/src/HAL/STM32_F4_F7/endstop_interrupts.h b/Marlin/src/HAL/STM32_F4_F7/endstop_interrupts.h deleted file mode 100644 index fdff8cc644..0000000000 --- a/Marlin/src/HAL/STM32_F4_F7/endstop_interrupts.h +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Marlin 3D Printer Firmware - * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * - * Based on Sprinter and grbl. - * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm - * Copyright (c) 2017 Victor Perez - * - * 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_(HAS_X_MAX, _ATTACH(X_MAX_PIN)); - TERN_(HAS_X_MIN, _ATTACH(X_MIN_PIN)); - TERN_(HAS_Y_MAX, _ATTACH(Y_MAX_PIN)); - TERN_(HAS_Y_MIN, _ATTACH(Y_MIN_PIN)); - TERN_(HAS_Z_MAX, _ATTACH(Z_MAX_PIN)); - TERN_(HAS_Z_MIN, _ATTACH(Z_MIN_PIN)); - TERN_(HAS_X2_MAX, _ATTACH(X2_MAX_PIN)); - TERN_(HAS_X2_MIN, _ATTACH(X2_MIN_PIN)); - TERN_(HAS_Y2_MAX, _ATTACH(Y2_MAX_PIN)); - TERN_(HAS_Y2_MIN, _ATTACH(Y2_MIN_PIN)); - TERN_(HAS_Z2_MAX, _ATTACH(Z2_MAX_PIN)); - TERN_(HAS_Z2_MIN, _ATTACH(Z2_MIN_PIN)); - TERN_(HAS_Z3_MAX, _ATTACH(Z3_MAX_PIN)); - TERN_(HAS_Z3_MIN, _ATTACH(Z3_MIN_PIN)); - TERN_(HAS_Z4_MAX, _ATTACH(Z4_MAX_PIN)); - TERN_(HAS_Z4_MIN, _ATTACH(Z4_MIN_PIN)); - TERN_(HAS_Z_MIN_PROBE_PIN, _ATTACH(Z_MIN_PROBE_PIN)); -} diff --git a/Marlin/src/HAL/STM32_F4_F7/fastio.h b/Marlin/src/HAL/STM32_F4_F7/fastio.h deleted file mode 100644 index f42be58354..0000000000 --- a/Marlin/src/HAL/STM32_F4_F7/fastio.h +++ /dev/null @@ -1,310 +0,0 @@ -/** - * Marlin 3D Printer Firmware - * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * - * Based on Sprinter and grbl. - * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm - * Copyright (c) 2017 Victor Perez - * - * 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 STM32F4/7 - * These use GPIO functions instead of Direct Port Manipulation, as on AVR. - */ - -#ifndef PWM - #define PWM OUTPUT -#endif - -#define READ(IO) digitalRead(IO) -#define WRITE(IO,V) digitalWrite(IO,V) - -#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 OUT_WRITE(IO,V) do{ _SET_OUTPUT(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 TOGGLE(IO) OUT_WRITE(IO, !READ(IO)) - -#define IS_INPUT(IO) -#define IS_OUTPUT(IO) - -#define PWM_PIN(P) true - -// digitalRead/Write wrappers -#define extDigitalRead(IO) digitalRead(IO) -#define extDigitalWrite(IO,V) digitalWrite(IO,V) - -// -// Pins Definitions -// -#define PORTA 0 -#define PORTB 1 -#define PORTC 2 -#define PORTD 3 -#define PORTE 4 -#define PORTF 5 -#define PORTG 6 - -#define _STM32_PIN(P,PN) ((PORT##P * 16) + PN) - -#undef PA0 -#define PA0 _STM32_PIN(A, 0) -#undef PA1 -#define PA1 _STM32_PIN(A, 1) -#undef PA2 -#define PA2 _STM32_PIN(A, 2) -#undef PA3 -#define PA3 _STM32_PIN(A, 3) -#undef PA4 -#define PA4 _STM32_PIN(A, 4) -#undef PA5 -#define PA5 _STM32_PIN(A, 5) -#undef PA6 -#define PA6 _STM32_PIN(A, 6) -#undef PA7 -#define PA7 _STM32_PIN(A, 7) -#undef PA8 -#define PA8 _STM32_PIN(A, 8) -#undef PA9 -#define PA9 _STM32_PIN(A, 9) -#undef PA10 -#define PA10 _STM32_PIN(A, 10) -#undef PA11 -#define PA11 _STM32_PIN(A, 11) -#undef PA12 -#define PA12 _STM32_PIN(A, 12) -#undef PA13 -#define PA13 _STM32_PIN(A, 13) -#undef PA14 -#define PA14 _STM32_PIN(A, 14) -#undef PA15 -#define PA15 _STM32_PIN(A, 15) - -#undef PB0 -#define PB0 _STM32_PIN(B, 0) -#undef PB1 -#define PB1 _STM32_PIN(B, 1) -#undef PB2 -#define PB2 _STM32_PIN(B, 2) -#undef PB3 -#define PB3 _STM32_PIN(B, 3) -#undef PB4 -#define PB4 _STM32_PIN(B, 4) -#undef PB5 -#define PB5 _STM32_PIN(B, 5) -#undef PB6 -#define PB6 _STM32_PIN(B, 6) -#undef PB7 -#define PB7 _STM32_PIN(B, 7) -#undef PB8 -#define PB8 _STM32_PIN(B, 8) -#undef PB9 -#define PB9 _STM32_PIN(B, 9) -#undef PB10 -#define PB10 _STM32_PIN(B, 10) -#undef PB11 -#define PB11 _STM32_PIN(B, 11) -#undef PB12 -#define PB12 _STM32_PIN(B, 12) -#undef PB13 -#define PB13 _STM32_PIN(B, 13) -#undef PB14 -#define PB14 _STM32_PIN(B, 14) -#undef PB15 -#define PB15 _STM32_PIN(B, 15) - -#undef PC0 -#define PC0 _STM32_PIN(C, 0) -#undef PC1 -#define PC1 _STM32_PIN(C, 1) -#undef PC2 -#define PC2 _STM32_PIN(C, 2) -#undef PC3 -#define PC3 _STM32_PIN(C, 3) -#undef PC4 -#define PC4 _STM32_PIN(C, 4) -#undef PC5 -#define PC5 _STM32_PIN(C, 5) -#undef PC6 -#define PC6 _STM32_PIN(C, 6) -#undef PC7 -#define PC7 _STM32_PIN(C, 7) -#undef PC8 -#define PC8 _STM32_PIN(C, 8) -#undef PC9 -#define PC9 _STM32_PIN(C, 9) -#undef PC10 -#define PC10 _STM32_PIN(C, 10) -#undef PC11 -#define PC11 _STM32_PIN(C, 11) -#undef PC12 -#define PC12 _STM32_PIN(C, 12) -#undef PC13 -#define PC13 _STM32_PIN(C, 13) -#undef PC14 -#define PC14 _STM32_PIN(C, 14) -#undef PC15 -#define PC15 _STM32_PIN(C, 15) - -#undef PD0 -#define PD0 _STM32_PIN(D, 0) -#undef PD1 -#define PD1 _STM32_PIN(D, 1) -#undef PD2 -#define PD2 _STM32_PIN(D, 2) -#undef PD3 -#define PD3 _STM32_PIN(D, 3) -#undef PD4 -#define PD4 _STM32_PIN(D, 4) -#undef PD5 -#define PD5 _STM32_PIN(D, 5) -#undef PD6 -#define PD6 _STM32_PIN(D, 6) -#undef PD7 -#define PD7 _STM32_PIN(D, 7) -#undef PD8 -#define PD8 _STM32_PIN(D, 8) -#undef PD9 -#define PD9 _STM32_PIN(D, 9) -#undef PD10 -#define PD10 _STM32_PIN(D, 10) -#undef PD11 -#define PD11 _STM32_PIN(D, 11) -#undef PD12 -#define PD12 _STM32_PIN(D, 12) -#undef PD13 -#define PD13 _STM32_PIN(D, 13) -#undef PD14 -#define PD14 _STM32_PIN(D, 14) -#undef PD15 -#define PD15 _STM32_PIN(D, 15) - -#undef PE0 -#define PE0 _STM32_PIN(E, 0) -#undef PE1 -#define PE1 _STM32_PIN(E, 1) -#undef PE2 -#define PE2 _STM32_PIN(E, 2) -#undef PE3 -#define PE3 _STM32_PIN(E, 3) -#undef PE4 -#define PE4 _STM32_PIN(E, 4) -#undef PE5 -#define PE5 _STM32_PIN(E, 5) -#undef PE6 -#define PE6 _STM32_PIN(E, 6) -#undef PE7 -#define PE7 _STM32_PIN(E, 7) -#undef PE8 -#define PE8 _STM32_PIN(E, 8) -#undef PE9 -#define PE9 _STM32_PIN(E, 9) -#undef PE10 -#define PE10 _STM32_PIN(E, 10) -#undef PE11 -#define PE11 _STM32_PIN(E, 11) -#undef PE12 -#define PE12 _STM32_PIN(E, 12) -#undef PE13 -#define PE13 _STM32_PIN(E, 13) -#undef PE14 -#define PE14 _STM32_PIN(E, 14) -#undef PE15 -#define PE15 _STM32_PIN(E, 15) - -#ifdef STM32F7 - - #undef PORTF - #define PORTF 5 - #undef PF0 - #define PF0 _STM32_PIN(F, 0) - #undef PF1 - #define PF1 _STM32_PIN(F, 1) - #undef PF2 - #define PF2 _STM32_PIN(F, 2) - #undef PF3 - #define PF3 _STM32_PIN(F, 3) - #undef PF4 - #define PF4 _STM32_PIN(F, 4) - #undef PF5 - #define PF5 _STM32_PIN(F, 5) - #undef PF6 - #define PF6 _STM32_PIN(F, 6) - #undef PF7 - #define PF7 _STM32_PIN(F, 7) - #undef PF8 - #define PF8 _STM32_PIN(F, 8) - #undef PF9 - #define PF9 _STM32_PIN(F, 9) - #undef PF10 - #define PF10 _STM32_PIN(F, 10) - #undef PF11 - #define PF11 _STM32_PIN(F, 11) - #undef PF12 - #define PF12 _STM32_PIN(F, 12) - #undef PF13 - #define PF13 _STM32_PIN(F, 13) - #undef PF14 - #define PF14 _STM32_PIN(F, 14) - #undef PF15 - #define PF15 _STM32_PIN(F, 15) - - #undef PORTG - #define PORTG 6 - #undef PG0 - #define PG0 _STM32_PIN(G, 0) - #undef PG1 - #define PG1 _STM32_PIN(G, 1) - #undef PG2 - #define PG2 _STM32_PIN(G, 2) - #undef PG3 - #define PG3 _STM32_PIN(G, 3) - #undef PG4 - #define PG4 _STM32_PIN(G, 4) - #undef PG5 - #define PG5 _STM32_PIN(G, 5) - #undef PG6 - #define PG6 _STM32_PIN(G, 6) - #undef PG7 - #define PG7 _STM32_PIN(G, 7) - #undef PG8 - #define PG8 _STM32_PIN(G, 8) - #undef PG9 - #define PG9 _STM32_PIN(G, 9) - #undef PG10 - #define PG10 _STM32_PIN(G, 10) - #undef PG11 - #define PG11 _STM32_PIN(G, 11) - #undef PG12 - #define PG12 _STM32_PIN(G, 12) - #undef PG13 - #define PG13 _STM32_PIN(G, 13) - #undef PG14 - #define PG14 _STM32_PIN(G, 14) - #undef PG15 - #define PG15 _STM32_PIN(G, 15) - -#endif // STM32GENERIC && STM32F7 diff --git a/Marlin/src/HAL/STM32_F4_F7/pinsDebug.h b/Marlin/src/HAL/STM32_F4_F7/pinsDebug.h deleted file mode 100644 index 973abb1b01..0000000000 --- a/Marlin/src/HAL/STM32_F4_F7/pinsDebug.h +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Marlin 3D Printer Firmware - * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * - * 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 - -#ifdef NUM_DIGITAL_PINS // Only in ST's Arduino core (STM32duino, STM32Core) - #include "../STM32/pinsDebug_STM32duino.h" -#elif defined(BOARD_NR_GPIO_PINS) // Only in STM32GENERIC (Maple) - #include "../STM32/pinsDebug_STM32GENERIC.h" -#else - #error "M43 Pins Debugging not supported for this board." -#endif diff --git a/Marlin/src/HAL/STM32_F4_F7/spi_pins.h b/Marlin/src/HAL/STM32_F4_F7/spi_pins.h deleted file mode 100644 index 75a6a2b250..0000000000 --- a/Marlin/src/HAL/STM32_F4_F7/spi_pins.h +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Marlin 3D Printer Firmware - * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * - * 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 SCK_PIN - #define SCK_PIN PA5 -#endif -#ifndef MISO_PIN - #define MISO_PIN PA6 -#endif -#ifndef MOSI_PIN - #define MOSI_PIN PA7 -#endif -#ifndef SS_PIN - #define SS_PIN PA8 -#endif diff --git a/Marlin/src/HAL/TEENSY31_32/HAL.cpp b/Marlin/src/HAL/TEENSY31_32/HAL.cpp index 8c3dd83377..f54025991d 100644 --- a/Marlin/src/HAL/TEENSY31_32/HAL.cpp +++ b/Marlin/src/HAL/TEENSY31_32/HAL.cpp @@ -31,33 +31,36 @@ #include -uint16_t HAL_adc_result; +// ------------------------ +// Serial ports +// ------------------------ -static const uint8_t pin2sc1a[] = { - 5, 14, 8, 9, 13, 12, 6, 7, 15, 4, 0, 19, 3, 31, // 0-13, we treat them as A0-A13 - 5, 14, 8, 9, 13, 12, 6, 7, 15, 4, // 14-23 (A0-A9) - 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, // 24-33 - 0+64, 19+64, 3+64, 31+64, // 34-37 (A10-A13) - 26, 22, 23, 27, 29, 30 // 38-43: temp. sensor, VREF_OUT, A14, bandgap, VREFH, VREFL. A14 isn't connected to anything in Teensy 3.0. -}; +#define _IMPLEMENT_SERIAL(X) DefaultSerial##X MSerial##X(false, Serial##X) +#define IMPLEMENT_SERIAL(X) _IMPLEMENT_SERIAL(X) +#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); -/* - // disable interrupts - void cli() { noInterrupts(); } +// ------------------------ +// MarlinHAL Class +// ------------------------ - // enable interrupts - void sei() { interrupts(); } -*/ +void MarlinHAL::reboot() { _reboot_Teensyduino_(); } -void HAL_adc_init() { - analog_init(); - while (ADC0_SC3 & ADC_SC3_CAL) {}; // Wait for calibration to finish - NVIC_ENABLE_IRQ(IRQ_FTM1); -} - -void HAL_clear_reset_source() { } - -uint8_t HAL_get_reset_source() { +uint8_t MarlinHAL::get_reset_source() { switch (RCM_SRS0) { case 128: return RST_POWER_ON; break; case 64: return RST_EXTERNAL; break; @@ -69,6 +72,55 @@ uint8_t HAL_get_reset_source() { return 0; } +// ------------------------ +// Watchdog Timer +// ------------------------ + +#if ENABLED(USE_WATCHDOG) + + #define WDT_TIMEOUT_MS TERN(WATCHDOG_DURATION_8S, 8000, 4000) // 4 or 8 second timeout + + void MarlinHAL::watchdog_init() { + WDOG_TOVALH = 0; + WDOG_TOVALL = WDT_TIMEOUT_MS; + WDOG_STCTRLH = WDOG_STCTRLH_WDOGEN; + } + + void MarlinHAL::watchdog_refresh() { + // Watchdog refresh sequence + WDOG_REFRESH = 0xA602; + WDOG_REFRESH = 0xB480; + } + +#endif + +// ------------------------ +// ADC +// ------------------------ + +void MarlinHAL::adc_init() { + analog_init(); + while (ADC0_SC3 & ADC_SC3_CAL) {}; // Wait for calibration to finish + NVIC_ENABLE_IRQ(IRQ_FTM1); +} + +void MarlinHAL::adc_start(const pin_t pin) { + static const uint8_t pin2sc1a[] = { + 5, 14, 8, 9, 13, 12, 6, 7, 15, 4, 0, 19, 3, 31, // 0-13, we treat them as A0-A13 + 5, 14, 8, 9, 13, 12, 6, 7, 15, 4, // 14-23 (A0-A9) + 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, // 24-33 + 0+64, 19+64, 3+64, 31+64, // 34-37 (A10-A13) + 26, 22, 23, 27, 29, 30 // 38-43: temp. sensor, VREF_OUT, A14, bandgap, VREFH, VREFL. A14 isn't connected to anything in Teensy 3.0. + }; + ADC0_SC1A = pin2sc1a[pin]; +} + +uint16_t MarlinHAL::adc_value() { return ADC0_RA; } + +// ------------------------ +// Free Memory Accessor +// ------------------------ + extern "C" { extern char __bss_end; extern char __heap_start; @@ -84,8 +136,4 @@ extern "C" { } } -void HAL_adc_start_conversion(const uint8_t adc_pin) { ADC0_SC1A = pin2sc1a[adc_pin]; } - -uint16_t HAL_adc_get_result() { return ADC0_RA; } - #endif // __MK20DX256__ diff --git a/Marlin/src/HAL/TEENSY31_32/HAL.h b/Marlin/src/HAL/TEENSY31_32/HAL.h index 8ab358e9e1..b98ee9eb39 100644 --- a/Marlin/src/HAL/TEENSY31_32/HAL.h +++ b/Marlin/src/HAL/TEENSY31_32/HAL.h @@ -1,9 +1,9 @@ /** * Marlin 3D Printer Firmware - * * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com - * Copyright (c) 2015-2016 Nico Tonnhofer wurstnase.reprap@gmail.com + * + * 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 @@ -32,17 +32,12 @@ #include "../shared/HAL_SPI.h" #include "fastio.h" -#include "watchdog.h" - #include -#define ST7920_DELAY_1 DELAY_NS(600) -#define ST7920_DELAY_2 DELAY_NS(750) -#define ST7920_DELAY_3 DELAY_NS(750) - -//#undef MOTHERBOARD -//#define MOTHERBOARD BOARD_TEENSY31_32 +// ------------------------ +// Defines +// ------------------------ #define IS_32BIT_TEENSY 1 #define IS_TEENSY_31_32 1 @@ -50,75 +45,139 @@ #define IS_TEENSY32 1 #endif -#define _MSERIAL(X) Serial##X -#define MSERIAL(X) _MSERIAL(X) +#define CPU_ST7920_DELAY_1 600 +#define CPU_ST7920_DELAY_2 750 +#define CPU_ST7920_DELAY_3 750 + +// ------------------------ +// Serial ports +// ------------------------ + +#include "../../core/serial_hook.h" + #define Serial0 Serial +#define _DECLARE_SERIAL(X) \ + typedef ForwardSerial1Class DefaultSerial##X; \ + extern DefaultSerial##X MSerial##X +#define DECLARE_SERIAL(X) _DECLARE_SERIAL(X) -#if SERIAL_PORT == -1 - #define MYSERIAL0 SerialUSB -#elif WITHIN(SERIAL_PORT, 0, 3) - #define MYSERIAL0 MSERIAL(SERIAL_PORT) -#endif +typedef ForwardSerial1Class USBSerialType; +extern USBSerialType USBSerial; -#define HAL_SERVO_LIB libServo +#define SERIAL_INDEX_MIN 0 +#define SERIAL_INDEX_MAX 3 +#define USB_SERIAL_PORT(...) USBSerial +#include "../shared/serial_ports.h" + +// ------------------------ +// Types +// ------------------------ + +class libServo; +typedef libServo hal_servo_t; typedef int8_t pin_t; -#ifndef analogInputToDigitalPin - #define analogInputToDigitalPin(p) ((p < 12U) ? (p) + 54U : -1) -#endif +// ------------------------ +// Interrupts +// ------------------------ -#define CRITICAL_SECTION_START() uint32_t primask = __get_PRIMASK(); __disable_irq() -#define CRITICAL_SECTION_END() if (!primask) __enable_irq() -#define ISRS_ENABLED() (!__get_PRIMASK()) -#define ENABLE_ISRS() __enable_irq() -#define DISABLE_ISRS() __disable_irq() - -#ifndef strncpy_P - #define strncpy_P(dest, src, num) strncpy((dest), (src), (num)) -#endif - -// Fix bug in pgm_read_ptr -#undef pgm_read_ptr -#define pgm_read_ptr(addr) (*((void**)(addr))) -// Add type-checking to pgm_read_word -#undef pgm_read_word -#define pgm_read_word(addr) (*((uint16_t*)(addr))) - -inline void HAL_init() {} - -// Clear the reset reason -void HAL_clear_reset_source(); - -// Get the reason for the reset -uint8_t HAL_get_reset_source(); - -inline void HAL_reboot() {} // reboot the board or restart the bootloader - -FORCE_INLINE void _delay_ms(const int delay_ms) { delay(delay_ms); } - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-function" -extern "C" { - int freeMemory(); -} -#pragma GCC diagnostic pop +uint32_t __get_PRIMASK(void); // CMSIS +#define CRITICAL_SECTION_START() const bool irqon = !__get_PRIMASK(); __disable_irq() +#define CRITICAL_SECTION_END() if (irqon) __enable_irq() +// ------------------------ // ADC +// ------------------------ -void HAL_adc_init(); +#ifndef analogInputToDigitalPin + #define analogInputToDigitalPin(p) pin_t((p < 12U) ? (p) + 54U : -1) +#endif -#define HAL_ADC_VREF 3.3 +#define HAL_ADC_VREF_MV 3300 #define HAL_ADC_RESOLUTION 10 -#define HAL_START_ADC(pin) HAL_adc_start_conversion(pin) -#define HAL_READ_ADC() HAL_adc_get_result() -#define HAL_ADC_READY() true - -#define HAL_ANALOG_SELECT(pin) - -void HAL_adc_start_conversion(const uint8_t adc_pin); -uint16_t HAL_adc_get_result(); +// +// 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) + +// ------------------------ +// Class Utilities +// ------------------------ + +#pragma GCC diagnostic push +#if GCC_VERSION <= 50000 + #pragma GCC diagnostic ignored "-Wunused-function" +#endif + +extern "C" int freeMemory(); + +#pragma GCC diagnostic pop + +// ------------------------ +// MarlinHAL Class +// ------------------------ + +class MarlinHAL { +public: + + // Earliest possible init, before setup() + MarlinHAL() {} + + // 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(); // 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() {} + + // 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 ch) {} + + // Begin ADC sampling on the given channel. Called from Temperature::isr! + static void adc_start(const pin_t ch); + + // Is the ADC ready for reading? + static bool adc_ready() { return true; } + + // The current value of the ADC register + static uint16_t adc_value(); + + /** + * 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] + */ + static void set_pwm_duty(const pin_t pin, const uint16_t v, const uint16_t=255, const bool=false) { + analogWrite(pin, v); + } + +}; diff --git a/Marlin/src/HAL/TEENSY31_32/HAL_SPI.cpp b/Marlin/src/HAL/TEENSY31_32/HAL_SPI.cpp index cdb3f4701c..cda7e7d16c 100644 --- a/Marlin/src/HAL/TEENSY31_32/HAL_SPI.cpp +++ b/Marlin/src/HAL/TEENSY31_32/HAL_SPI.cpp @@ -21,11 +21,12 @@ */ #ifdef __MK20DX256__ +#include "../../inc/MarlinConfig.h" #include "HAL.h" + #include #include #include "spi_pins.h" -#include "../../core/macros.h" static SPISettings spiConfig; @@ -35,18 +36,17 @@ static SPISettings spiConfig; // Initialize SPI bus void spiBegin() { - #if !PIN_EXISTS(SS) - #error "SS_PIN not defined!" + #if PIN_EXISTS(SD_SS) + OUT_WRITE(SD_SS_PIN, HIGH); #endif - OUT_WRITE(SS_PIN, HIGH); - SET_OUTPUT(SCK_PIN); - SET_INPUT(MISO_PIN); - SET_OUTPUT(MOSI_PIN); + SET_OUTPUT(SD_SCK_PIN); + SET_INPUT(SD_MISO_PIN); + SET_OUTPUT(SD_MOSI_PIN); #if 0 && DISABLED(SOFTWARE_SPI) // set SS high - may be chip select for another SPI device #if SET_SPI_SS_HIGH - WRITE(SS_PIN, HIGH); + WRITE(SD_SS_PIN, HIGH); #endif // set a default rate spiInit(SPI_HALF_SPEED); // 1 @@ -64,7 +64,7 @@ void spiInit(uint8_t spiRate) { case SPI_EIGHTH_SPEED: clock = 1250000; break; case SPI_SPEED_5: clock = 625000; break; case SPI_SPEED_6: clock = 312500; break; - default: clock = 4000000; // Default from the SPI libarary + default: clock = 4000000; // Default from the SPI library } spiConfig = SPISettings(clock, MSBFIRST, SPI_MODE0); SPI.begin(); @@ -82,7 +82,7 @@ uint8_t spiRec() { } // SPI read data -void spiRead(uint8_t* buf, uint16_t nbyte) { +void spiRead(uint8_t *buf, uint16_t nbyte) { SPI.beginTransaction(spiConfig); SPI.transfer(buf, nbyte); SPI.endTransaction(); @@ -107,7 +107,7 @@ void spiSend(uint8_t b) { } // SPI send block -void spiSendBlock(uint8_t token, const uint8_t* buf) { +void spiSendBlock(uint8_t token, const uint8_t *buf) { SPI.beginTransaction(spiConfig); SPDR = token; for (uint16_t i = 0; i < 512; i += 2) { @@ -120,7 +120,6 @@ void spiSendBlock(uint8_t token, const uint8_t* buf) { SPI.endTransaction(); } - // Begin SPI transaction, set clock, bit order, data mode void spiBeginTransaction(uint32_t spiClock, uint8_t bitOrder, uint8_t dataMode) { spiConfig = SPISettings(spiClock, bitOrder, dataMode); diff --git a/Marlin/src/HAL/TEENSY31_32/MarlinSPI.h b/Marlin/src/HAL/TEENSY31_32/MarlinSPI.h new file mode 100644 index 0000000000..0c447ba4cb --- /dev/null +++ b/Marlin/src/HAL/TEENSY31_32/MarlinSPI.h @@ -0,0 +1,26 @@ +/** + * 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 + +#include + +using MarlinSPI = SPIClass; diff --git a/Marlin/src/HAL/TEENSY31_32/Servo.h b/Marlin/src/HAL/TEENSY31_32/Servo.h index 82b601d956..2da74fd25d 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/eeprom.cpp b/Marlin/src/HAL/TEENSY31_32/eeprom.cpp index cc5c56f7d5..a7e5e590a3 100644 --- a/Marlin/src/HAL/TEENSY31_32/eeprom.cpp +++ b/Marlin/src/HAL/TEENSY31_32/eeprom.cpp @@ -2,6 +2,9 @@ * Marlin 3D Printer Firmware * Copyright (c) 2020 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 @@ -16,35 +19,36 @@ * along with this program. If not, see . * */ + #ifdef __MK20DX256__ -#include "../../inc/MarlinConfig.h" - -#if USE_WIRED_EEPROM - /** * HAL PersistentStore for Teensy 3.2 (MK20DX256) */ +#include "../../inc/MarlinConfig.h" + +#if USE_WIRED_EEPROM + #include "../shared/eeprom_api.h" #include #ifndef MARLIN_EEPROM_SIZE #define MARLIN_EEPROM_SIZE size_t(E2END + 1) #endif -size_t PersistentStore::capacity() { return MARLIN_EEPROM_SIZE; } +size_t PersistentStore::capacity() { return MARLIN_EEPROM_SIZE - eeprom_exclude_size; } bool PersistentStore::access_start() { 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 * const p = (uint8_t * const)pos; + uint8_t * const p = (uint8_t * const)REAL_EEPROM_ADDR(pos); uint8_t v = *value; - // EEPROM has only ~100,000 write cycles, - // so only write bytes that have changed! - if (v != eeprom_read_byte(p)) { + 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; @@ -57,9 +61,9 @@ bool PersistentStore::write_data(int &pos, const uint8_t *value, size_t size, ui return false; } -bool PersistentStore::read_data(int &pos, uint8_t* value, size_t size, uint16_t *crc, const bool writing/*=true*/) { +bool PersistentStore::read_data(int &pos, uint8_t *value, size_t size, uint16_t *crc, const bool writing/*=true*/) { do { - uint8_t c = eeprom_read_byte((uint8_t*)pos); + const uint8_t c = eeprom_read_byte((uint8_t*)REAL_EEPROM_ADDR(pos)); if (writing) *value = c; crc16(crc, &c, 1); pos++; diff --git a/Marlin/src/HAL/TEENSY31_32/endstop_interrupts.h b/Marlin/src/HAL/TEENSY31_32/endstop_interrupts.h index 999ada5127..798ca4f0cb 100644 --- a/Marlin/src/HAL/TEENSY31_32/endstop_interrupts.h +++ b/Marlin/src/HAL/TEENSY31_32/endstop_interrupts.h @@ -47,21 +47,34 @@ void endstop_ISR() { endstops.update(); } void setup_endstop_interrupts() { #define _ATTACH(P) attachInterrupt(digitalPinToInterrupt(P), endstop_ISR, CHANGE) - TERN_(HAS_X_MAX, _ATTACH(X_MAX_PIN)); - TERN_(HAS_X_MIN, _ATTACH(X_MIN_PIN)); - TERN_(HAS_Y_MAX, _ATTACH(Y_MAX_PIN)); - TERN_(HAS_Y_MIN, _ATTACH(Y_MIN_PIN)); - TERN_(HAS_Z_MAX, _ATTACH(Z_MAX_PIN)); - TERN_(HAS_Z_MIN, _ATTACH(Z_MIN_PIN)); - TERN_(HAS_X2_MAX, _ATTACH(X2_MAX_PIN)); - TERN_(HAS_X2_MIN, _ATTACH(X2_MIN_PIN)); - TERN_(HAS_Y2_MAX, _ATTACH(Y2_MAX_PIN)); - TERN_(HAS_Y2_MIN, _ATTACH(Y2_MIN_PIN)); - TERN_(HAS_Z2_MAX, _ATTACH(Z2_MAX_PIN)); - TERN_(HAS_Z2_MIN, _ATTACH(Z2_MIN_PIN)); - TERN_(HAS_Z3_MAX, _ATTACH(Z3_MAX_PIN)); - TERN_(HAS_Z3_MIN, _ATTACH(Z3_MIN_PIN)); - TERN_(HAS_Z4_MAX, _ATTACH(Z4_MAX_PIN)); - TERN_(HAS_Z4_MIN, _ATTACH(Z4_MIN_PIN)); - TERN_(HAS_Z_MIN_PROBE_PIN, _ATTACH(Z_MIN_PROBE_PIN)); + 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/TEENSY31_32/fastio.h b/Marlin/src/HAL/TEENSY31_32/fastio.h index 9a299de9c7..b582c7bfec 100644 --- a/Marlin/src/HAL/TEENSY31_32/fastio.h +++ b/Marlin/src/HAL/TEENSY31_32/fastio.h @@ -28,7 +28,7 @@ */ #ifndef MASK - #define MASK(PIN) (1 << PIN) + #define MASK(PIN) _BV(PIN) #endif #define GPIO_BITBAND_ADDR(reg, bit) (((uint32_t)&(reg) - 0x40000000) * 32 + (bit) * 4 + 0x42000000) @@ -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/inc/Conditionals_LCD.h b/Marlin/src/HAL/TEENSY31_32/inc/Conditionals_LCD.h index 54ec166643..5f1c4b1601 100644 --- a/Marlin/src/HAL/TEENSY31_32/inc/Conditionals_LCD.h +++ b/Marlin/src/HAL/TEENSY31_32/inc/Conditionals_LCD.h @@ -20,7 +20,3 @@ * */ #pragma once - -#if HAS_SPI_TFT || HAS_FSMC_TFT - #error "Sorry! TFT displays are not available for HAL/TEENSY31_32." -#endif diff --git a/Marlin/src/HAL/TEENSY31_32/inc/Conditionals_type.h b/Marlin/src/HAL/TEENSY31_32/inc/Conditionals_type.h new file mode 100644 index 0000000000..82f95a1035 --- /dev/null +++ b/Marlin/src/HAL/TEENSY31_32/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/TEENSY31_32/inc/SanityCheck.h b/Marlin/src/HAL/TEENSY31_32/inc/SanityCheck.h index 3932ee6a76..c5b25f2cb5 100644 --- a/Marlin/src/HAL/TEENSY31_32/inc/SanityCheck.h +++ b/Marlin/src/HAL/TEENSY31_32/inc/SanityCheck.h @@ -25,14 +25,26 @@ * Test TEENSY35_36 specific configuration values for errors at compile-time. */ +#if HAS_SPI_TFT || HAS_FSMC_TFT + #error "Sorry! TFT displays are not available for Teensy 3.1/3.2." +#endif + #if ENABLED(EMERGENCY_PARSER) #error "EMERGENCY_PARSER is not yet implemented for Teensy 3.1/3.2. 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 on Teensy 3.1/3.2." + #error "Features requiring Hardware PWM (FAST_PWM_FAN, SPINDLE_LASER_FREQUENCY) are not yet supported for Teensy 3.1/3.2." #endif #if HAS_TMC_SW_SERIAL - #error "TMC220x Software Serial is not supported on this platform." + #error "TMC220x Software Serial is not supported for Teensy 3.1/3.2." +#endif + +#if ENABLED(POSTMORTEM_DEBUGGING) + #error "POSTMORTEM_DEBUGGING is not yet supported for Teensy 3.1/3.2." +#endif + +#if USING_PULLDOWNS + #error "PULLDOWN pin mode is not available for Teensy 3.1/3.2." #endif diff --git a/Marlin/src/HAL/TEENSY31_32/pinsDebug.h b/Marlin/src/HAL/TEENSY31_32/pinsDebug.h index d4a91ce801..741909bf0a 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 5754fbfeed..dbd0bb9174 100644 --- a/Marlin/src/HAL/TEENSY31_32/spi_pins.h +++ b/Marlin/src/HAL/TEENSY31_32/spi_pins.h @@ -21,7 +21,6 @@ */ #pragma once -#define SCK_PIN 13 -#define MISO_PIN 12 -#define MOSI_PIN 11 -#define SS_PIN 20 //SDSS // A.28, A.29, B.21, C.26, C.29 +#define SD_SCK_PIN 13 +#define SD_MISO_PIN 12 +#define SD_MOSI_PIN 11 diff --git a/Marlin/src/HAL/TEENSY31_32/timers.cpp b/Marlin/src/HAL/TEENSY31_32/timers.cpp index 7e01a38f89..f217715a3f 100644 --- a/Marlin/src/HAL/TEENSY31_32/timers.cpp +++ b/Marlin/src/HAL/TEENSY31_32/timers.cpp @@ -47,7 +47,7 @@ FORCE_INLINE static void __DSB() { void HAL_timer_start(const uint8_t timer_num, const uint32_t frequency) { switch (timer_num) { - case 0: + case MF_TIMER_STEP: FTM0_MODE = FTM_MODE_WPDIS | FTM_MODE_FTMEN; FTM0_SC = 0x00; // Set this to zero before changing the modulus FTM0_CNT = 0x0000; // Reset the count to zero @@ -56,7 +56,7 @@ void HAL_timer_start(const uint8_t timer_num, const uint32_t frequency) { FTM0_SC = (FTM_SC_CLKS(0b1) & FTM_SC_CLKS_MASK) | (FTM_SC_PS(FTM0_TIMER_PRESCALE_BITS) & FTM_SC_PS_MASK); // Bus clock 60MHz divided by prescaler 8 FTM0_C0SC = FTM_CSC_CHIE | FTM_CSC_MSA | FTM_CSC_ELSA; break; - case 1: + case MF_TIMER_TEMP: FTM1_MODE = FTM_MODE_WPDIS | FTM_MODE_FTMEN; // Disable write protection, Enable FTM1 FTM1_SC = 0x00; // Set this to zero before changing the modulus FTM1_CNT = 0x0000; // Reset the count to zero @@ -70,15 +70,15 @@ void HAL_timer_start(const uint8_t timer_num, const uint32_t frequency) { void HAL_timer_enable_interrupt(const uint8_t timer_num) { switch (timer_num) { - case 0: NVIC_ENABLE_IRQ(IRQ_FTM0); break; - case 1: NVIC_ENABLE_IRQ(IRQ_FTM1); break; + case MF_TIMER_STEP: NVIC_ENABLE_IRQ(IRQ_FTM0); break; + case MF_TIMER_TEMP: NVIC_ENABLE_IRQ(IRQ_FTM1); break; } } void HAL_timer_disable_interrupt(const uint8_t timer_num) { switch (timer_num) { - case 0: NVIC_DISABLE_IRQ(IRQ_FTM0); break; - case 1: NVIC_DISABLE_IRQ(IRQ_FTM1); break; + case MF_TIMER_STEP: NVIC_DISABLE_IRQ(IRQ_FTM0); break; + case MF_TIMER_TEMP: NVIC_DISABLE_IRQ(IRQ_FTM1); break; } // We NEED memory barriers to ensure Interrupts are actually disabled! @@ -89,20 +89,20 @@ void HAL_timer_disable_interrupt(const uint8_t timer_num) { bool HAL_timer_interrupt_enabled(const uint8_t timer_num) { switch (timer_num) { - case 0: return NVIC_IS_ENABLED(IRQ_FTM0); - case 1: return NVIC_IS_ENABLED(IRQ_FTM1); + case MF_TIMER_STEP: return NVIC_IS_ENABLED(IRQ_FTM0); + case MF_TIMER_TEMP: return NVIC_IS_ENABLED(IRQ_FTM1); } return false; } void HAL_timer_isr_prologue(const uint8_t timer_num) { switch (timer_num) { - case 0: + case MF_TIMER_STEP: FTM0_CNT = 0x0000; FTM0_SC &= ~FTM_SC_TOF; // Clear FTM Overflow flag FTM0_C0SC &= ~FTM_CSC_CHF; // Clear FTM Channel Compare flag break; - case 1: + case MF_TIMER_TEMP: FTM1_CNT = 0x0000; FTM1_SC &= ~FTM_SC_TOF; // Clear FTM Overflow flag FTM1_C0SC &= ~FTM_CSC_CHF; // Clear FTM Channel Compare flag diff --git a/Marlin/src/HAL/TEENSY31_32/timers.h b/Marlin/src/HAL/TEENSY31_32/timers.h index 135b328830..cdca84ec1c 100644 --- a/Marlin/src/HAL/TEENSY31_32/timers.h +++ b/Marlin/src/HAL/TEENSY31_32/timers.h @@ -34,73 +34,72 @@ #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) -#ifndef STEP_TIMER_NUM - #define STEP_TIMER_NUM 0 // Timer Index for Stepper +#ifndef MF_TIMER_STEP + #define MF_TIMER_STEP 0 // Timer Index for Stepper #endif -#ifndef PULSE_TIMER_NUM - #define PULSE_TIMER_NUM STEP_TIMER_NUM +#ifndef MF_TIMER_PULSE + #define MF_TIMER_PULSE MF_TIMER_STEP #endif -#ifndef TEMP_TIMER_NUM - #define TEMP_TIMER_NUM 1 // Timer Index for Temperature +#ifndef MF_TIMER_TEMP + #define MF_TIMER_TEMP 1 // Timer Index for Temperature #endif #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(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) #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); FORCE_INLINE static void HAL_timer_set_compare(const uint8_t timer_num, const hal_timer_t compare) { switch (timer_num) { - case 0: FTM0_C0V = compare; break; - case 1: FTM1_C0V = compare; break; + case MF_TIMER_STEP: FTM0_C0V = compare; break; + case MF_TIMER_TEMP: FTM1_C0V = compare; break; } } FORCE_INLINE static hal_timer_t HAL_timer_get_compare(const uint8_t timer_num) { switch (timer_num) { - case 0: return FTM0_C0V; - case 1: return FTM1_C0V; + case MF_TIMER_STEP: return FTM0_C0V; + case MF_TIMER_TEMP: return FTM1_C0V; } return 0; } FORCE_INLINE static hal_timer_t HAL_timer_get_count(const uint8_t timer_num) { switch (timer_num) { - case 0: return FTM0_CNT; - case 1: return FTM1_CNT; + case MF_TIMER_STEP: return FTM0_CNT; + case MF_TIMER_TEMP: return FTM1_CNT; } return 0; } @@ -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(TIMER_NUM) +inline void HAL_timer_isr_epilogue(const uint8_t) {} diff --git a/Marlin/src/HAL/TEENSY31_32/u8g/LCD_defines.h b/Marlin/src/HAL/TEENSY31_32/u8g/LCD_defines.h new file mode 100644 index 0000000000..a13e7e837f --- /dev/null +++ b/Marlin/src/HAL/TEENSY31_32/u8g/LCD_defines.h @@ -0,0 +1,26 @@ +/** + * 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 + * (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 + +/** + * Teensy 3.1/3.2 LCD-specific defines + */ diff --git a/Marlin/src/HAL/TEENSY35_36/HAL.cpp b/Marlin/src/HAL/TEENSY35_36/HAL.cpp index 92907353b8..f41582be30 100644 --- a/Marlin/src/HAL/TEENSY35_36/HAL.cpp +++ b/Marlin/src/HAL/TEENSY35_36/HAL.cpp @@ -31,42 +31,36 @@ #include -uint16_t HAL_adc_result, HAL_adc_select; +// ------------------------ +// Serial ports +// ------------------------ -static const uint8_t pin2sc1a[] = { - 5, 14, 8, 9, 13, 12, 6, 7, 15, 4, 3, 19+128, 14+128, 15+128, // 0-13 -> A0-A13 - 5, 14, 8, 9, 13, 12, 6, 7, 15, 4, // 14-23 are A0-A9 - 255, 255, 255, 255, 255, 255, 255, // 24-30 are digital only - 14+128, 15+128, 17, 18, 4+128, 5+128, 6+128, 7+128, 17+128, // 31-39 are A12-A20 - 255, 255, 255, 255, 255, 255, 255, 255, 255, // 40-48 are digital only - 10+128, 11+128, // 49-50 are A23-A24 - 255, 255, 255, 255, 255, 255, 255, // 51-57 are digital only - 255, 255, 255, 255, 255, 255, // 58-63 (sd card pins) are digital only - 3, 19+128, // 64-65 are A10-A11 - 23, 23+128,// 66-67 are A21-A22 (DAC pins) - 1, 1+128, // 68-69 are A25-A26 (unused USB host port on Teensy 3.5) - 26, // 70 is Temperature Sensor - 18+128 // 71 is Vref -}; +#define _IMPLEMENT_SERIAL(X) DefaultSerial##X MSerial##X(false, Serial##X) +#define IMPLEMENT_SERIAL(X) _IMPLEMENT_SERIAL(X) +#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); -/* - // disable interrupts - void cli() { noInterrupts(); } +// ------------------------ +// MarlinHAL Class +// ------------------------ - // enable interrupts - void sei() { interrupts(); } -*/ +void MarlinHAL::reboot() { _reboot_Teensyduino_(); } -void HAL_adc_init() { - analog_init(); - while (ADC0_SC3 & ADC_SC3_CAL) {}; // Wait for calibration to finish - while (ADC1_SC3 & ADC_SC3_CAL) {}; // Wait for calibration to finish - NVIC_ENABLE_IRQ(IRQ_FTM1); -} - -void HAL_clear_reset_source() { } - -uint8_t HAL_get_reset_source() { +uint8_t MarlinHAL::get_reset_source() { switch (RCM_SRS0) { case 128: return RST_POWER_ON; break; case 64: return RST_EXTERNAL; break; @@ -78,6 +72,83 @@ uint8_t HAL_get_reset_source() { return 0; } +// ------------------------ +// Watchdog Timer +// ------------------------ + +#if ENABLED(USE_WATCHDOG) + + #define WDT_TIMEOUT_MS TERN(WATCHDOG_DURATION_8S, 8000, 4000) // 4 or 8 second timeout + + void MarlinHAL::watchdog_init() { + WDOG_TOVALH = 0; + WDOG_TOVALL = WDT_TIMEOUT_MS; + WDOG_STCTRLH = WDOG_STCTRLH_WDOGEN; + } + + void MarlinHAL::watchdog_refresh() { + // Watchdog refresh sequence + WDOG_REFRESH = 0xA602; + WDOG_REFRESH = 0xB480; + } + +#endif + +// ------------------------ +// ADC +// ------------------------ + +int8_t MarlinHAL::adc_select; + +void MarlinHAL::adc_init() { + analog_init(); + while (ADC0_SC3 & ADC_SC3_CAL) { /* Wait for calibration to finish */ } + while (ADC1_SC3 & ADC_SC3_CAL) { /* Wait for calibration to finish */ } + NVIC_ENABLE_IRQ(IRQ_FTM1); +} + +void MarlinHAL::adc_start(const pin_t adc_pin) { + static const uint8_t pin2sc1a[] = { + 5, 14, 8, 9, 13, 12, 6, 7, 15, 4, 3, 19+128, 14+128, 15+128, // 0-13 -> A0-A13 + 5, 14, 8, 9, 13, 12, 6, 7, 15, 4, // 14-23 are A0-A9 + 255, 255, 255, 255, 255, 255, 255, // 24-30 are digital only + 14+128, 15+128, 17, 18, 4+128, 5+128, 6+128, 7+128, 17+128, // 31-39 are A12-A20 + 255, 255, 255, 255, 255, 255, 255, 255, 255, // 40-48 are digital only + 10+128, 11+128, // 49-50 are A23-A24 + 255, 255, 255, 255, 255, 255, 255, // 51-57 are digital only + 255, 255, 255, 255, 255, 255, // 58-63 (sd card pins) are digital only + 3, 19+128, // 64-65 are A10-A11 + 23, 23+128,// 66-67 are A21-A22 (DAC pins) + 1, 1+128, // 68-69 are A25-A26 (unused USB host port on Teensy 3.5) + 26, // 70 is Temperature Sensor + 18+128 // 71 is Vref + }; + const uint16_t pin = pin2sc1a[adc_pin]; + if (pin == 0xFF) { + adc_select = -1; // Digital only + } + else if (pin & 0x80) { + adc_select = 1; + ADC1_SC1A = pin & 0x7F; + } + else { + adc_select = 0; + ADC0_SC1A = pin; + } +} + +uint16_t MarlinHAL::adc_value() { + switch (adc_select) { + case 0: return ADC0_RA; + case 1: return ADC1_RA; + } + return 0; +} + +// ------------------------ +// Free Memory Accessor +// ------------------------ + extern "C" { extern char __bss_end; extern char __heap_start; @@ -93,28 +164,4 @@ extern "C" { } } -void HAL_adc_start_conversion(const uint8_t adc_pin) { - const uint16_t pin = pin2sc1a[adc_pin]; - if (pin == 0xFF) { - // Digital only - HAL_adc_select = -1; - } - else if (pin & 0x80) { - HAL_adc_select = 1; - ADC1_SC1A = pin & 0x7F; - } - else { - HAL_adc_select = 0; - ADC0_SC1A = pin; - } -} - -uint16_t HAL_adc_get_result() { - switch (HAL_adc_select) { - case 0: return ADC0_RA; - case 1: return ADC1_RA; - } - return 0; -} - #endif // __MK64FX512__ || __MK66FX1M0__ diff --git a/Marlin/src/HAL/TEENSY35_36/HAL.h b/Marlin/src/HAL/TEENSY35_36/HAL.h index 2b735d6224..85d02cec8c 100644 --- a/Marlin/src/HAL/TEENSY35_36/HAL.h +++ b/Marlin/src/HAL/TEENSY35_36/HAL.h @@ -1,9 +1,9 @@ /** * Marlin 3D Printer Firmware - * * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com - * Copyright (c) 2015-2016 Nico Tonnhofer wurstnase.reprap@gmail.com + * + * 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 @@ -32,15 +32,10 @@ #include "../shared/HAL_SPI.h" #include "fastio.h" -#include "watchdog.h" #include #include -#define ST7920_DELAY_1 DELAY_NS(600) -#define ST7920_DELAY_2 DELAY_NS(750) -#define ST7920_DELAY_3 DELAY_NS(750) - // ------------------------ // Defines // ------------------------ @@ -53,78 +48,143 @@ #define IS_TEENSY35 1 #endif -#define _MSERIAL(X) Serial##X -#define MSERIAL(X) _MSERIAL(X) -#define Serial0 Serial - -#if SERIAL_PORT == -1 - #define MYSERIAL0 SerialUSB -#elif WITHIN(SERIAL_PORT, 0, 3) - #define MYSERIAL0 MSERIAL(SERIAL_PORT) -#endif - -#define HAL_SERVO_LIB libServo - -typedef int8_t pin_t; - -#ifndef analogInputToDigitalPin - #define analogInputToDigitalPin(p) ((p < 12U) ? (p) + 54U : -1) -#endif - -#define CRITICAL_SECTION_START() uint32_t primask = __get_primask(); __disable_irq() -#define CRITICAL_SECTION_END() if (!primask) __enable_irq() -#define ISRS_ENABLED() (!__get_primask()) -#define ENABLE_ISRS() __enable_irq() -#define DISABLE_ISRS() __disable_irq() +#define CPU_ST7920_DELAY_1 600 +#define CPU_ST7920_DELAY_2 750 +#define CPU_ST7920_DELAY_3 750 #undef sq #define sq(x) ((x)*(x)) -#ifndef strncpy_P - #define strncpy_P(dest, src, num) strncpy((dest), (src), (num)) +// ------------------------ +// Serial ports +// ------------------------ + +#include "../../core/serial_hook.h" + +#define Serial0 Serial +#define _DECLARE_SERIAL(X) \ + typedef ForwardSerial1Class DefaultSerial##X; \ + extern DefaultSerial##X MSerial##X +#define DECLARE_SERIAL(X) _DECLARE_SERIAL(X) + +typedef ForwardSerial1Class USBSerialType; +extern USBSerialType USBSerial; + +#define SERIAL_INDEX_MIN 0 +#define SERIAL_INDEX_MAX 3 +#define USB_SERIAL_PORT(...) USBSerial +#include "../shared/serial_ports.h" + +// ------------------------ +// Types +// ------------------------ + +class libServo; +typedef libServo hal_servo_t; + +typedef int8_t pin_t; + +// ------------------------ +// Interrupts +// ------------------------ + +#define CRITICAL_SECTION_START() const bool irqon = !__get_primask(); __disable_irq() +#define CRITICAL_SECTION_END() if (irqon) __enable_irq() + +// ------------------------ +// ADC +// ------------------------ + +#ifndef analogInputToDigitalPin + #define analogInputToDigitalPin(p) pin_t((p < 12U) ? (p) + 54U : -1) #endif -// Fix bug in pgm_read_ptr -#undef pgm_read_ptr -#define pgm_read_ptr(addr) (*((void**)(addr))) -// Add type-checking to pgm_read_word -#undef pgm_read_word -#define pgm_read_word(addr) (*((uint16_t*)(addr))) - -inline void HAL_init() {} - -// Clear reset reason -void HAL_clear_reset_source(); - -// Reset reason -uint8_t HAL_get_reset_source(); - -inline void HAL_reboot() {} // reboot the board or restart the bootloader - -FORCE_INLINE void _delay_ms(const int delay_ms) { delay(delay_ms); } - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-function" -extern "C" { - int freeMemory(); -} -#pragma GCC diagnostic pop - -// ADC - -void HAL_adc_init(); - -#define HAL_ADC_VREF 3.3 +#define HAL_ADC_VREF_MV 3300 #define HAL_ADC_RESOLUTION 10 -#define HAL_START_ADC(pin) HAL_adc_start_conversion(pin) -#define HAL_READ_ADC() HAL_adc_get_result() -#define HAL_ADC_READY() true - -#define HAL_ANALOG_SELECT(pin) - -void HAL_adc_start_conversion(const uint8_t adc_pin); -uint16_t HAL_adc_get_result(); +// +// 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) + +// ------------------------ +// Free Memory Accessor +// ------------------------ + +#pragma GCC diagnostic push +#if GCC_VERSION <= 50000 + #pragma GCC diagnostic ignored "-Wunused-function" +#endif + +extern "C" int freeMemory(); + +#pragma GCC diagnostic pop + +// ------------------------ +// MarlinHAL Class +// ------------------------ + +class MarlinHAL { +public: + + // Earliest possible init, before setup() + MarlinHAL() {} + + // 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(); // Restart the firmware from 0x0 + + // Interrupts + static bool isr_state() { return true; } + 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() {} + + // Reset + static uint8_t get_reset_source(); + static void clear_reset_source() {} + + // Free SRAM + static int freeMemory() { return ::freeMemory(); } + + // + // ADC Methods + // + + static int8_t adc_select; + + // 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) {} + + // 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 bool adc_ready() { return true; } + + // The current value of the ADC register + static uint16_t adc_value(); + + /** + * 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] + */ + static void set_pwm_duty(const pin_t pin, const uint16_t v, const uint16_t=255, const bool=false) { + analogWrite(pin, v); + } + +}; diff --git a/Marlin/src/HAL/TEENSY35_36/HAL_SPI.cpp b/Marlin/src/HAL/TEENSY35_36/HAL_SPI.cpp index b36900a321..d80f57b2c4 100644 --- a/Marlin/src/HAL/TEENSY35_36/HAL_SPI.cpp +++ b/Marlin/src/HAL/TEENSY35_36/HAL_SPI.cpp @@ -26,27 +26,27 @@ #if defined(__MK64FX512__) || defined(__MK66FX1M0__) +#include "../../inc/MarlinConfig.h" #include "HAL.h" + #include #include #include "spi_pins.h" -#include "../../core/macros.h" static SPISettings spiConfig; void spiBegin() { - #if !PIN_EXISTS(SS) - #error "SS_PIN not defined!" + #if PIN_EXISTS(SD_SS) + OUT_WRITE(SD_SS_PIN, HIGH); #endif - OUT_WRITE(SS_PIN, HIGH); - SET_OUTPUT(SCK_PIN); - SET_INPUT(MISO_PIN); - SET_OUTPUT(MOSI_PIN); + SET_OUTPUT(SD_SCK_PIN); + SET_INPUT(SD_MISO_PIN); + SET_OUTPUT(SD_MOSI_PIN); #if 0 && DISABLED(SOFTWARE_SPI) // set SS high - may be chip select for another SPI device #if SET_SPI_SS_HIGH - WRITE(SS_PIN, HIGH); + WRITE(SD_SS_PIN, HIGH); #endif // set a default rate spiInit(SPI_HALF_SPEED); // 1 @@ -64,7 +64,7 @@ void spiInit(uint8_t spiRate) { case SPI_SPEED_5: clock = 625000; break; case SPI_SPEED_6: clock = 312500; break; default: - clock = 4000000; // Default from the SPI libarary + clock = 4000000; // Default from the SPI library } spiConfig = SPISettings(clock, MSBFIRST, SPI_MODE0); SPI.begin(); @@ -80,7 +80,7 @@ uint8_t spiRec() { //return SPDR; } -void spiRead(uint8_t* buf, uint16_t nbyte) { +void spiRead(uint8_t *buf, uint16_t nbyte) { SPI.beginTransaction(spiConfig); SPI.transfer(buf, nbyte); SPI.endTransaction(); @@ -103,7 +103,7 @@ void spiSend(uint8_t b) { //while (!TEST(SPSR, SPIF)) { /* Intentionally left empty */ } } -void spiSendBlock(uint8_t token, const uint8_t* buf) { +void spiSendBlock(uint8_t token, const uint8_t *buf) { SPI.beginTransaction(spiConfig); SPDR = token; for (uint16_t i = 0; i < 512; i += 2) { diff --git a/Marlin/src/HAL/TEENSY35_36/MarlinSPI.h b/Marlin/src/HAL/TEENSY35_36/MarlinSPI.h new file mode 100644 index 0000000000..0c447ba4cb --- /dev/null +++ b/Marlin/src/HAL/TEENSY35_36/MarlinSPI.h @@ -0,0 +1,26 @@ +/** + * 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 + +#include + +using MarlinSPI = SPIClass; diff --git a/Marlin/src/HAL/TEENSY35_36/Servo.h b/Marlin/src/HAL/TEENSY35_36/Servo.h index 719011f102..c37567a813 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/eeprom.cpp b/Marlin/src/HAL/TEENSY35_36/eeprom.cpp index ccbdc6b116..977cd70ee8 100644 --- a/Marlin/src/HAL/TEENSY35_36/eeprom.cpp +++ b/Marlin/src/HAL/TEENSY35_36/eeprom.cpp @@ -1,10 +1,9 @@ /** * Marlin 3D Printer Firmware - * * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com - * Copyright (c) 2015-2016 Nico Tonnhofer wurstnase.reprap@gmail.com - * Copyright (c) 2016 Victor Perez victor_pv@hotmail.com + * + * 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 @@ -36,19 +35,19 @@ #ifndef MARLIN_EEPROM_SIZE #define MARLIN_EEPROM_SIZE size_t(E2END + 1) #endif -size_t PersistentStore::capacity() { return MARLIN_EEPROM_SIZE; } +size_t PersistentStore::capacity() { return MARLIN_EEPROM_SIZE - eeprom_exclude_size; } bool PersistentStore::access_start() { 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 * const p = (uint8_t * const)pos; + uint8_t * const p = (uint8_t * const)REAL_EEPROM_ADDR(pos); uint8_t v = *value; - // EEPROM has only ~100,000 write cycles, - // so only write bytes that have changed! - if (v != eeprom_read_byte(p)) { + 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; @@ -61,9 +60,9 @@ bool PersistentStore::write_data(int &pos, const uint8_t *value, size_t size, ui return false; } -bool PersistentStore::read_data(int &pos, uint8_t* value, size_t size, uint16_t *crc, const bool writing/*=true*/) { +bool PersistentStore::read_data(int &pos, uint8_t *value, size_t size, uint16_t *crc, const bool writing/*=true*/) { do { - uint8_t c = eeprom_read_byte((uint8_t*)pos); + const uint8_t c = eeprom_read_byte((uint8_t*)REAL_EEPROM_ADDR(pos)); if (writing) *value = c; crc16(crc, &c, 1); pos++; diff --git a/Marlin/src/HAL/TEENSY35_36/endstop_interrupts.h b/Marlin/src/HAL/TEENSY35_36/endstop_interrupts.h index 87e6a7507a..a938041b5a 100644 --- a/Marlin/src/HAL/TEENSY35_36/endstop_interrupts.h +++ b/Marlin/src/HAL/TEENSY35_36/endstop_interrupts.h @@ -46,21 +46,34 @@ void endstop_ISR() { endstops.update(); } */ void setup_endstop_interrupts() { #define _ATTACH(P) attachInterrupt(digitalPinToInterrupt(P), endstop_ISR, CHANGE) - TERN_(HAS_X_MAX, _ATTACH(X_MAX_PIN)); - TERN_(HAS_X_MIN, _ATTACH(X_MIN_PIN)); - TERN_(HAS_Y_MAX, _ATTACH(Y_MAX_PIN)); - TERN_(HAS_Y_MIN, _ATTACH(Y_MIN_PIN)); - TERN_(HAS_Z_MAX, _ATTACH(Z_MAX_PIN)); - TERN_(HAS_Z_MIN, _ATTACH(Z_MIN_PIN)); - TERN_(HAS_X2_MAX, _ATTACH(X2_MAX_PIN)); - TERN_(HAS_X2_MIN, _ATTACH(X2_MIN_PIN)); - TERN_(HAS_Y2_MAX, _ATTACH(Y2_MAX_PIN)); - TERN_(HAS_Y2_MIN, _ATTACH(Y2_MIN_PIN)); - TERN_(HAS_Z2_MAX, _ATTACH(Z2_MAX_PIN)); - TERN_(HAS_Z2_MIN, _ATTACH(Z2_MIN_PIN)); - TERN_(HAS_Z3_MAX, _ATTACH(Z3_MAX_PIN)); - TERN_(HAS_Z3_MIN, _ATTACH(Z3_MIN_PIN)); - TERN_(HAS_Z4_MAX, _ATTACH(Z4_MAX_PIN)); - TERN_(HAS_Z4_MIN, _ATTACH(Z4_MIN_PIN)); - TERN_(HAS_Z_MIN_PROBE_PIN, _ATTACH(Z_MIN_PROBE_PIN)); + 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/TEENSY35_36/fastio.h b/Marlin/src/HAL/TEENSY35_36/fastio.h index 9a299de9c7..b582c7bfec 100644 --- a/Marlin/src/HAL/TEENSY35_36/fastio.h +++ b/Marlin/src/HAL/TEENSY35_36/fastio.h @@ -28,7 +28,7 @@ */ #ifndef MASK - #define MASK(PIN) (1 << PIN) + #define MASK(PIN) _BV(PIN) #endif #define GPIO_BITBAND_ADDR(reg, bit) (((uint32_t)&(reg) - 0x40000000) * 32 + (bit) * 4 + 0x42000000) @@ -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/Conditionals_LCD.h b/Marlin/src/HAL/TEENSY35_36/inc/Conditionals_LCD.h index 632ee533ac..5f1c4b1601 100644 --- a/Marlin/src/HAL/TEENSY35_36/inc/Conditionals_LCD.h +++ b/Marlin/src/HAL/TEENSY35_36/inc/Conditionals_LCD.h @@ -20,7 +20,3 @@ * */ #pragma once - -#if HAS_SPI_TFT || HAS_FSMC_TFT - #error "Sorry! TFT displays are not available for HAL/TEENSY35_36." -#endif diff --git a/Marlin/src/HAL/TEENSY35_36/inc/Conditionals_type.h b/Marlin/src/HAL/TEENSY35_36/inc/Conditionals_type.h new file mode 100644 index 0000000000..82f95a1035 --- /dev/null +++ b/Marlin/src/HAL/TEENSY35_36/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/TEENSY35_36/inc/SanityCheck.h b/Marlin/src/HAL/TEENSY35_36/inc/SanityCheck.h index ee80e42696..8f2651d3ed 100644 --- a/Marlin/src/HAL/TEENSY35_36/inc/SanityCheck.h +++ b/Marlin/src/HAL/TEENSY35_36/inc/SanityCheck.h @@ -25,14 +25,30 @@ * Test TEENSY35_36 specific configuration values for errors at compile-time. */ +#if HAS_SPI_TFT || HAS_FSMC_TFT + #error "Sorry! TFT displays are not available for Teensy 3.5/3.6." +#endif + #if ENABLED(EMERGENCY_PARSER) #error "EMERGENCY_PARSER is not yet implemented for Teensy 3.5/3.6. 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 on Teensy 3.5/3.6." + #error "Features requiring Hardware PWM (FAST_PWM_FAN, SPINDLE_LASER_FREQUENCY) are not yet supported for Teensy 3.5/3.6." #endif #if HAS_TMC_SW_SERIAL - #error "TMC220x Software Serial is not supported on this platform." + #error "TMC220x Software Serial is not supported for Teensy 3.5/3.6." +#endif + +#if ENABLED(POSTMORTEM_DEBUGGING) + #error "POSTMORTEM_DEBUGGING is not yet supported for Teensy 3.5/3.6." +#endif + +#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 e529fa93be..f0f61b8bbe 100644 --- a/Marlin/src/HAL/TEENSY35_36/pinsDebug.h +++ b/Marlin/src/HAL/TEENSY35_36/pinsDebug.h @@ -2,6 +2,9 @@ * Marlin 3D Printer Firmware * Copyright (c) 2020 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 @@ -19,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 @@ -50,14 +69,23 @@ #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) -void HAL_print_analog_pin(char buffer[], int8_t pin) { +#define isAnalogPin(P) ((P) >= analogInputToDigitalPin(0) && (P) <= analogInputToDigitalPin(9)) || ((P) >= analogInputToDigitalPin(12) && (P) <= analogInputToDigitalPin(20)) + +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)); } -void HAL_analog_pin_state(char buffer[], int8_t pin) { +void analog_pin_state(char buffer[], int8_t pin) { if (pin <= 23) sprintf_P(buffer, PSTR("Analog in =% 5d"), analogRead(pin - 14)); else if (pin <= 39) sprintf_P(buffer, PSTR("Analog in =% 5d"), analogRead(pin - 19)); } @@ -74,7 +102,7 @@ void HAL_analog_pin_state(char buffer[], int8_t pin) { * Print a pin's PWM status. * Return true if it's currently a PWM pin. */ -bool HAL_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); @@ -105,4 +133,6 @@ bool HAL_pwm_status(int8_t pin) { SERIAL_ECHOPGM(" "); } -static void HAL_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 c76344d075..d898a923f5 100644 --- a/Marlin/src/HAL/TEENSY35_36/spi_pins.h +++ b/Marlin/src/HAL/TEENSY35_36/spi_pins.h @@ -25,7 +25,6 @@ * HAL SPI Pins for Teensy 3.5 (MK64FX512) and Teensy 3.6 (MK66FX1M0) */ -#define SCK_PIN 13 -#define MISO_PIN 12 -#define MOSI_PIN 11 -#define SS_PIN 20 // SDSS // A.28, A.29, B.21, C.26, C.29 +#define SD_SCK_PIN 13 +#define SD_MISO_PIN 12 +#define SD_MOSI_PIN 11 diff --git a/Marlin/src/HAL/TEENSY35_36/timers.cpp b/Marlin/src/HAL/TEENSY35_36/timers.cpp index 8067d091dd..39095fbd77 100644 --- a/Marlin/src/HAL/TEENSY35_36/timers.cpp +++ b/Marlin/src/HAL/TEENSY35_36/timers.cpp @@ -47,7 +47,7 @@ FORCE_INLINE static void __DSB() { void HAL_timer_start(const uint8_t timer_num, const uint32_t frequency) { switch (timer_num) { - case 0: + case MF_TIMER_STEP: FTM0_MODE = FTM_MODE_WPDIS | FTM_MODE_FTMEN; FTM0_SC = 0x00; // Set this to zero before changing the modulus FTM0_CNT = 0x0000; // Reset the count to zero @@ -56,7 +56,7 @@ void HAL_timer_start(const uint8_t timer_num, const uint32_t frequency) { FTM0_SC = (FTM_SC_CLKS(0b1) & FTM_SC_CLKS_MASK) | (FTM_SC_PS(FTM0_TIMER_PRESCALE_BITS) & FTM_SC_PS_MASK); // Bus clock 60MHz divided by prescaler 8 FTM0_C0SC = FTM_CSC_CHIE | FTM_CSC_MSA | FTM_CSC_ELSA; break; - case 1: + case MF_TIMER_TEMP: FTM1_MODE = FTM_MODE_WPDIS | FTM_MODE_FTMEN; // Disable write protection, Enable FTM1 FTM1_SC = 0x00; // Set this to zero before changing the modulus FTM1_CNT = 0x0000; // Reset the count to zero @@ -70,15 +70,15 @@ void HAL_timer_start(const uint8_t timer_num, const uint32_t frequency) { void HAL_timer_enable_interrupt(const uint8_t timer_num) { switch (timer_num) { - case 0: NVIC_ENABLE_IRQ(IRQ_FTM0); break; - case 1: NVIC_ENABLE_IRQ(IRQ_FTM1); break; + case MF_TIMER_STEP: NVIC_ENABLE_IRQ(IRQ_FTM0); break; + case MF_TIMER_TEMP: NVIC_ENABLE_IRQ(IRQ_FTM1); break; } } void HAL_timer_disable_interrupt(const uint8_t timer_num) { switch (timer_num) { - case 0: NVIC_DISABLE_IRQ(IRQ_FTM0); break; - case 1: NVIC_DISABLE_IRQ(IRQ_FTM1); break; + case MF_TIMER_STEP: NVIC_DISABLE_IRQ(IRQ_FTM0); break; + case MF_TIMER_TEMP: NVIC_DISABLE_IRQ(IRQ_FTM1); break; } // We NEED memory barriers to ensure Interrupts are actually disabled! @@ -89,20 +89,20 @@ void HAL_timer_disable_interrupt(const uint8_t timer_num) { bool HAL_timer_interrupt_enabled(const uint8_t timer_num) { switch (timer_num) { - case 0: return NVIC_IS_ENABLED(IRQ_FTM0); - case 1: return NVIC_IS_ENABLED(IRQ_FTM1); + case MF_TIMER_STEP: return NVIC_IS_ENABLED(IRQ_FTM0); + case MF_TIMER_TEMP: return NVIC_IS_ENABLED(IRQ_FTM1); } return false; } void HAL_timer_isr_prologue(const uint8_t timer_num) { switch (timer_num) { - case 0: + case MF_TIMER_STEP: FTM0_CNT = 0x0000; FTM0_SC &= ~FTM_SC_TOF; // Clear FTM Overflow flag FTM0_C0SC &= ~FTM_CSC_CHF; // Clear FTM Channel Compare flag break; - case 1: + case MF_TIMER_TEMP: FTM1_CNT = 0x0000; FTM1_SC &= ~FTM_SC_TOF; // Clear FTM Overflow flag FTM1_C0SC &= ~FTM_CSC_CHF; // Clear FTM Channel Compare flag diff --git a/Marlin/src/HAL/TEENSY35_36/timers.h b/Marlin/src/HAL/TEENSY35_36/timers.h index 5c623cd801..1a5a8460c6 100644 --- a/Marlin/src/HAL/TEENSY35_36/timers.h +++ b/Marlin/src/HAL/TEENSY35_36/timers.h @@ -1,8 +1,9 @@ /** * Marlin 3D Printer Firmware * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com - * Copyright (c) 2015-2016 Nico Tonnhofer wurstnase.reprap@gmail.com + * + * 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 @@ -33,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 @@ -45,61 +46,60 @@ typedef uint32_t hal_timer_t; #define HAL_TIMER_RATE (FTM0_TIMER_RATE) -#ifndef STEP_TIMER_NUM - #define STEP_TIMER_NUM 0 // Timer Index for Stepper +#ifndef MF_TIMER_STEP + #define MF_TIMER_STEP 0 // Timer Index for Stepper #endif -#ifndef PULSE_TIMER_NUM - #define PULSE_TIMER_NUM STEP_TIMER_NUM +#ifndef MF_TIMER_PULSE + #define MF_TIMER_PULSE MF_TIMER_STEP #endif -#ifndef TEMP_TIMER_NUM - #define TEMP_TIMER_NUM 1 // Timer Index for Temperature +#ifndef MF_TIMER_TEMP + #define MF_TIMER_TEMP 1 // Timer Index for Temperature #endif #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(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) #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() //void TC3_Handler() #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() //void TC4_Handler() #endif 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 0: FTM0_C0V = compare; break; - case 1: FTM1_C0V = compare; break; + case MF_TIMER_STEP: FTM0_C0V = compare; break; + case MF_TIMER_TEMP: FTM1_C0V = compare; break; } } FORCE_INLINE static hal_timer_t HAL_timer_get_compare(const uint8_t timer_num) { switch (timer_num) { - case 0: return FTM0_C0V; - case 1: return FTM1_C0V; + case MF_TIMER_STEP: return FTM0_C0V; + case MF_TIMER_TEMP: return FTM1_C0V; } return 0; } FORCE_INLINE static hal_timer_t HAL_timer_get_count(const uint8_t timer_num) { switch (timer_num) { - case 0: return FTM0_CNT; - case 1: return FTM1_CNT; + case MF_TIMER_STEP: return FTM0_CNT; + case MF_TIMER_TEMP: return FTM1_CNT; } return 0; } @@ -109,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(TIMER_NUM) +inline void HAL_timer_isr_epilogue(const uint8_t) {} diff --git a/Marlin/src/HAL/TEENSY35_36/u8g/LCD_defines.h b/Marlin/src/HAL/TEENSY35_36/u8g/LCD_defines.h new file mode 100644 index 0000000000..511085781c --- /dev/null +++ b/Marlin/src/HAL/TEENSY35_36/u8g/LCD_defines.h @@ -0,0 +1,26 @@ +/** + * 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 + * (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 + +/** + * Teensy 3.5/3.6 LCD-specific defines + */ diff --git a/Marlin/src/HAL/TEENSY40_41/HAL.cpp b/Marlin/src/HAL/TEENSY40_41/HAL.cpp index 5b1b4272f5..3a1f06095b 100644 --- a/Marlin/src/HAL/TEENSY40_41/HAL.cpp +++ b/Marlin/src/HAL/TEENSY40_41/HAL.cpp @@ -26,142 +26,192 @@ #ifdef __IMXRT1062__ +#include "../../inc/MarlinConfig.h" #include "HAL.h" + #include "../shared/Delay.h" #include "timers.h" - #include -uint16_t HAL_adc_result, HAL_adc_select; +// ------------------------ +// Serial ports +// ------------------------ -static const uint8_t pin2sc1a[] = { - 0x07, // 0/A0 AD_B1_02 - 0x08, // 1/A1 AD_B1_03 - 0x0C, // 2/A2 AD_B1_07 - 0x0B, // 3/A3 AD_B1_06 - 0x06, // 4/A4 AD_B1_01 - 0x05, // 5/A5 AD_B1_00 - 0x0F, // 6/A6 AD_B1_10 - 0x00, // 7/A7 AD_B1_11 - 0x0D, // 8/A8 AD_B1_08 - 0x0E, // 9/A9 AD_B1_09 - 0x01, // 24/A10 AD_B0_12 - 0x02, // 25/A11 AD_B0_13 - 0x83, // 26/A12 AD_B1_14 - only on ADC2, 3 - 0x84, // 27/A13 AD_B1_15 - only on ADC2, 4 - 0x07, // 14/A0 AD_B1_02 - 0x08, // 15/A1 AD_B1_03 - 0x0C, // 16/A2 AD_B1_07 - 0x0B, // 17/A3 AD_B1_06 - 0x06, // 18/A4 AD_B1_01 - 0x05, // 19/A5 AD_B1_00 - 0x0F, // 20/A6 AD_B1_10 - 0x00, // 21/A7 AD_B1_11 - 0x0D, // 22/A8 AD_B1_08 - 0x0E, // 23/A9 AD_B1_09 - 0x01, // 24/A10 AD_B0_12 - 0x02, // 25/A11 AD_B0_13 - 0x83, // 26/A12 AD_B1_14 - only on ADC2, 3 - 0x84, // 27/A13 AD_B1_15 - only on ADC2, 4 - #ifdef ARDUINO_TEENSY41 - 0xFF, // 28 - 0xFF, // 29 - 0xFF, // 30 - 0xFF, // 31 - 0xFF, // 32 - 0xFF, // 33 - 0xFF, // 34 - 0xFF, // 35 - 0xFF, // 36 - 0xFF, // 37 - 0x81, // 38/A14 AD_B1_12 - only on ADC2, 1 - 0x82, // 39/A15 AD_B1_13 - only on ADC2, 2 - 0x09, // 40/A16 AD_B1_04 - 0x0A, // 41/A17 AD_B1_05 - #endif -}; +#define _IMPLEMENT_SERIAL(X) DefaultSerial##X MSerial##X(false, Serial##X) +#define IMPLEMENT_SERIAL(X) _IMPLEMENT_SERIAL(X) +#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); -/* -// disable interrupts -void cli() { noInterrupts(); } +// ------------------------ +// FastIO +// ------------------------ -// enable interrupts -void sei() { interrupts(); } -*/ - -void HAL_adc_init() { - analog_init(); - while (ADC1_GC & ADC_GC_CAL) ; - while (ADC2_GC & ADC_GC_CAL) ; -} - -void HAL_clear_reset_source() { - uint32_t reset_source = SRC_SRSR; - SRC_SRSR = reset_source; - } - -uint8_t HAL_get_reset_source() { - switch (SRC_SRSR & 0xFF) { - case 1: return RST_POWER_ON; break; - case 2: return RST_SOFTWARE; break; - case 4: return RST_EXTERNAL; break; - // case 8: return RST_BROWN_OUT; break; - case 16: return RST_WATCHDOG; break; - case 64: return RST_JTAG; break; - // case 128: return RST_OVERTEMP; break; - } - return 0; -} - -#define __bss_end _ebss - -extern "C" { - extern char __bss_end; - extern char __heap_start; - extern void* __brkval; - - // Doesn't work on Teensy 4.x - uint32_t freeMemory() { - uint32_t free_memory; - if ((uint32_t)__brkval == 0) - free_memory = ((uint32_t)&free_memory) - ((uint32_t)&__bss_end); - else - free_memory = ((uint32_t)&free_memory) - ((uint32_t)__brkval); - return free_memory; - } -} - -void HAL_adc_start_conversion(const uint8_t adc_pin) { - const uint16_t pin = pin2sc1a[adc_pin]; - if (pin == 0xFF) { - HAL_adc_select = -1; // Digital only - } - else if (pin & 0x80) { - HAL_adc_select = 1; - ADC2_HC0 = pin & 0x7F; - } - else { - HAL_adc_select = 0; - ADC1_HC0 = pin; - } -} - -uint16_t HAL_adc_get_result() { - switch (HAL_adc_select) { - case 0: - while (!(ADC1_HS & ADC_HS_COCO0)) ; // wait - return ADC1_R0; - case 1: - while (!(ADC2_HS & ADC_HS_COCO0)) ; // wait - return ADC2_R0; - } - return 0; -} - -bool is_output(uint8_t pin) { +bool is_output(pin_t pin) { const struct digital_pin_bitband_and_config_table_struct *p; p = digital_pin_to_info_PGM + pin; return (*(p->reg + 1) & p->mask); } +// ------------------------ +// MarlinHAL Class +// ------------------------ + +void MarlinHAL::reboot() { _reboot_Teensyduino_(); } + +uint8_t MarlinHAL::get_reset_source() { + switch (SRC_SRSR & 0xFF) { + case 1: return RST_POWER_ON; break; + case 2: return RST_SOFTWARE; break; + case 4: return RST_EXTERNAL; break; + //case 8: return RST_BROWN_OUT; break; + case 16: return RST_WATCHDOG; break; + case 64: return RST_JTAG; break; + //case 128: return RST_OVERTEMP; break; + } + return 0; +} + +void MarlinHAL::clear_reset_source() { + uint32_t reset_source = SRC_SRSR; + SRC_SRSR = reset_source; +} + +// ------------------------ +// Watchdog Timer +// ------------------------ + +#if ENABLED(USE_WATCHDOG) + + #define WDT_TIMEOUT TERN(WATCHDOG_DURATION_8S, 8, 4) // 4 or 8 second timeout + + constexpr uint8_t timeoutval = (WDT_TIMEOUT - 0.5f) * 2.0f; + + void MarlinHAL::watchdog_init() { + CCM_CCGR3 |= CCM_CCGR3_WDOG1(3); // enable WDOG1 clocks + WDOG1_WMCR = 0; // disable power down PDE + WDOG1_WCR |= WDOG_WCR_SRS | WDOG_WCR_WT(timeoutval); + WDOG1_WCR |= WDOG_WCR_WDE | WDOG_WCR_WDT | WDOG_WCR_SRE; + } + + void MarlinHAL::watchdog_refresh() { + // Watchdog refresh sequence + WDOG1_WSR = 0x5555; + WDOG1_WSR = 0xAAAA; + } + +#endif + +// ------------------------ +// ADC +// ------------------------ + +int8_t MarlinHAL::adc_select; + +void MarlinHAL::adc_init() { + analog_init(); + while (ADC1_GC & ADC_GC_CAL) { /* wait */ } + while (ADC2_GC & ADC_GC_CAL) { /* wait */ } +} + +void MarlinHAL::adc_start(const pin_t adc_pin) { + static const uint8_t pin2sc1a[] = { + 0x07, // 0/A0 AD_B1_02 + 0x08, // 1/A1 AD_B1_03 + 0x0C, // 2/A2 AD_B1_07 + 0x0B, // 3/A3 AD_B1_06 + 0x06, // 4/A4 AD_B1_01 + 0x05, // 5/A5 AD_B1_00 + 0x0F, // 6/A6 AD_B1_10 + 0x00, // 7/A7 AD_B1_11 + 0x0D, // 8/A8 AD_B1_08 + 0x0E, // 9/A9 AD_B1_09 + 0x01, // 24/A10 AD_B0_12 + 0x02, // 25/A11 AD_B0_13 + 0x83, // 26/A12 AD_B1_14 - only on ADC2, 3 + 0x84, // 27/A13 AD_B1_15 - only on ADC2, 4 + 0x07, // 14/A0 AD_B1_02 + 0x08, // 15/A1 AD_B1_03 + 0x0C, // 16/A2 AD_B1_07 + 0x0B, // 17/A3 AD_B1_06 + 0x06, // 18/A4 AD_B1_01 + 0x05, // 19/A5 AD_B1_00 + 0x0F, // 20/A6 AD_B1_10 + 0x00, // 21/A7 AD_B1_11 + 0x0D, // 22/A8 AD_B1_08 + 0x0E, // 23/A9 AD_B1_09 + 0x01, // 24/A10 AD_B0_12 + 0x02, // 25/A11 AD_B0_13 + 0x83, // 26/A12 AD_B1_14 - only on ADC2, 3 + 0x84, // 27/A13 AD_B1_15 - only on ADC2, 4 + #ifdef ARDUINO_TEENSY41 + 0xFF, // 28 + 0xFF, // 29 + 0xFF, // 30 + 0xFF, // 31 + 0xFF, // 32 + 0xFF, // 33 + 0xFF, // 34 + 0xFF, // 35 + 0xFF, // 36 + 0xFF, // 37 + 0x81, // 38/A14 AD_B1_12 - only on ADC2, 1 + 0x82, // 39/A15 AD_B1_13 - only on ADC2, 2 + 0x09, // 40/A16 AD_B1_04 + 0x0A, // 41/A17 AD_B1_05 + #endif + }; + const uint16_t pin = pin2sc1a[adc_pin]; + if (pin == 0xFF) { + adc_select = -1; // Digital only + } + else if (pin & 0x80) { + adc_select = 1; + ADC2_HC0 = pin & 0x7F; + } + else { + adc_select = 0; + ADC1_HC0 = pin; + } +} + +uint16_t MarlinHAL::adc_value() { + switch (adc_select) { + case 0: + while (!(ADC1_HS & ADC_HS_COCO0)) { /* wait */ } + return ADC1_R0; + case 1: + while (!(ADC2_HS & ADC_HS_COCO0)) { /* wait */ } + return ADC2_R0; + } + return 0; +} + +// ------------------------ +// Free Memory Accessor +// ------------------------ + +extern "C" { + // 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; + + uint32_t freeMemory() { + return (char *)&_heap_end - __brkval; + } +} + #endif // __IMXRT1062__ diff --git a/Marlin/src/HAL/TEENSY40_41/HAL.h b/Marlin/src/HAL/TEENSY40_41/HAL.h index 75c10e9395..fc75539e9b 100644 --- a/Marlin/src/HAL/TEENSY40_41/HAL.h +++ b/Marlin/src/HAL/TEENSY40_41/HAL.h @@ -1,9 +1,9 @@ /** * Marlin 3D Printer Firmware - * * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com - * Copyright (c) 2015-2016 Nico Tonnhofer wurstnase.reprap@gmail.com + * + * 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 @@ -32,14 +32,13 @@ #include "../shared/HAL_SPI.h" #include "fastio.h" -#include "watchdog.h" #include #include -//#define ST7920_DELAY_1 DELAY_NS(600) -//#define ST7920_DELAY_2 DELAY_NS(750) -//#define ST7920_DELAY_3 DELAY_NS(750) +#if HAS_ETHERNET + #include "../../feature/ethernet.h" +#endif // ------------------------ // Defines @@ -51,98 +50,158 @@ #define IS_TEENSY41 1 #endif -#define _MSERIAL(X) Serial##X -#define MSERIAL(X) _MSERIAL(X) -#define Serial0 Serial - -#if SERIAL_PORT == -1 - #define MYSERIAL0 SerialUSB -#elif WITHIN(SERIAL_PORT, 0, 8) - #define MYSERIAL0 MSERIAL(SERIAL_PORT) -#else - #error "The required SERIAL_PORT must be from -1 to 8. Please update your configuration." -#endif - -#ifdef SERIAL_PORT_2 - #if SERIAL_PORT_2 == -1 - #define MYSERIAL1 usbSerial - #elif WITHIN(SERIAL_PORT_2, 0, 8) - #define MYSERIAL1 MSERIAL(SERIAL_PORT_2) - #else - #error "SERIAL_PORT_2 must be from -1 to 8. Please update your configuration." - #endif -#endif - -#define HAL_SERVO_LIB libServo - -typedef int8_t pin_t; - -#ifndef analogInputToDigitalPin - #define analogInputToDigitalPin(p) ((p < 12U) ? (p) + 54U : -1) -#endif - -#define CRITICAL_SECTION_START() uint32_t primask = __get_primask(); __disable_irq() -#define CRITICAL_SECTION_END() if (!primask) __enable_irq() -#define ISRS_ENABLED() (!__get_primask()) -#define ENABLE_ISRS() __enable_irq() -#define DISABLE_ISRS() __disable_irq() +#define CPU_ST7920_DELAY_1 600 +#define CPU_ST7920_DELAY_2 750 +#define CPU_ST7920_DELAY_3 750 #undef sq #define sq(x) ((x)*(x)) -#ifndef strncpy_P - #define strncpy_P(dest, src, num) strncpy((dest), (src), (num)) -#endif - // Don't place string constants in PROGMEM #undef PSTR #define PSTR(str) ({static const char *data = (str); &data[0];}) -// Fix bug in pgm_read_ptr -#undef pgm_read_ptr -#define pgm_read_ptr(addr) (*((void**)(addr))) -// Add type-checking to pgm_read_word -#undef pgm_read_word -#define pgm_read_word(addr) (*((uint16_t*)(addr))) +#define HAL_CAN_SET_PWM_FREQ -// Enable hooks into idle and setup for HAL -#define HAL_IDLETASK 1 -FORCE_INLINE void HAL_idletask() {} -FORCE_INLINE void HAL_init() {} +// ------------------------ +// Serial ports +// ------------------------ -// Clear reset reason -void HAL_clear_reset_source(); +#include "../../core/serial_hook.h" -// Reset reason -uint8_t HAL_get_reset_source(); +#define Serial0 Serial +#define _DECLARE_SERIAL(X) \ + typedef ForwardSerial1Class DefaultSerial##X; \ + extern DefaultSerial##X MSerial##X +#define DECLARE_SERIAL(X) _DECLARE_SERIAL(X) -FORCE_INLINE void _delay_ms(const int delay_ms) { delay(delay_ms); } +typedef ForwardSerial1Class USBSerialType; +extern USBSerialType USBSerial; -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-function" -extern "C" { - uint32_t freeMemory(); -} -#pragma GCC diagnostic pop +#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 +// ------------------------ + +class libServo; +typedef libServo hal_servo_t; + +typedef int8_t pin_t; + +// ------------------------ +// Interrupts +// ------------------------ + +#define CRITICAL_SECTION_START() const bool irqon = !__get_primask(); __disable_irq() +#define CRITICAL_SECTION_END() if (irqon) __enable_irq() + +// ------------------------ // ADC +// ------------------------ -void HAL_adc_init(); +#ifndef analogInputToDigitalPin + #define analogInputToDigitalPin(p) pin_t((p < 12U) ? (p) + 54U : -1) +#endif -#define HAL_ADC_VREF 3.3 +#define HAL_ADC_VREF_MV 3300 #define HAL_ADC_RESOLUTION 10 #define HAL_ADC_FILTERED // turn off ADC oversampling -#define HAL_START_ADC(pin) HAL_adc_start_conversion(pin) -#define HAL_READ_ADC() HAL_adc_get_result() -#define HAL_ADC_READY() true - -#define HAL_ANALOG_SELECT(pin) - -void HAL_adc_start_conversion(const uint8_t adc_pin); -uint16_t HAL_adc_get_result(); +// +// 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) -bool is_output(uint8_t pin); +// FastIO +bool is_output(pin_t pin); + +// ------------------------ +// Free Memory Accessor +// ------------------------ + +#pragma GCC diagnostic push +#if GCC_VERSION <= 50000 + #pragma GCC diagnostic ignored "-Wunused-function" +#endif + +extern "C" uint32_t freeMemory(); + +#pragma GCC diagnostic pop + +// ------------------------ +// MarlinHAL Class +// ------------------------ + +class MarlinHAL { +public: + + // Earliest possible init, before setup() + MarlinHAL() {} + + // 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(); // 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() {} + + // Reset + static uint8_t get_reset_source(); + static void clear_reset_source(); + + // Free SRAM + static int freeMemory() { return ::freeMemory(); } + + // + // ADC Methods + // + + static int8_t adc_select; + + // 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 bool adc_ready() { return true; } + + // 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 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/HAL_SPI.cpp b/Marlin/src/HAL/TEENSY40_41/HAL_SPI.cpp index 20b472aa35..9dcb812faf 100644 --- a/Marlin/src/HAL/TEENSY40_41/HAL_SPI.cpp +++ b/Marlin/src/HAL/TEENSY40_41/HAL_SPI.cpp @@ -26,11 +26,12 @@ #ifdef __IMXRT1062__ +#include "../../inc/MarlinConfig.h" #include "HAL.h" + #include #include #include "spi_pins.h" -#include "../../core/macros.h" static SPISettings spiConfig; @@ -50,20 +51,17 @@ static SPISettings spiConfig; // ------------------------ void spiBegin() { - #ifndef SS_PIN - #error "SS_PIN is not defined!" + #if PIN_EXISTS(SD_SS) + OUT_WRITE(SD_SS_PIN, HIGH); #endif - - OUT_WRITE(SS_PIN, HIGH); - - //SET_OUTPUT(SCK_PIN); - //SET_INPUT(MISO_PIN); - //SET_OUTPUT(MOSI_PIN); + //SET_OUTPUT(SD_SCK_PIN); + //SET_INPUT(SD_MISO_PIN); + //SET_OUTPUT(SD_MOSI_PIN); #if 0 && DISABLED(SOFTWARE_SPI) // set SS high - may be chip select for another SPI device #if SET_SPI_SS_HIGH - WRITE(SS_PIN, HIGH); + WRITE(SD_SS_PIN, HIGH); #endif // set a default rate spiInit(SPI_HALF_SPEED); // 1 @@ -81,7 +79,7 @@ void spiInit(uint8_t spiRate) { case SPI_SPEED_5: clock = 625000; break; case SPI_SPEED_6: clock = 312500; break; default: - clock = 4000000; // Default from the SPI libarary + clock = 4000000; // Default from the SPI library } spiConfig = SPISettings(clock, MSBFIRST, SPI_MODE0); SPI.begin(); @@ -97,7 +95,7 @@ uint8_t spiRec() { //return SPDR; } -void spiRead(uint8_t* buf, uint16_t nbyte) { +void spiRead(uint8_t *buf, uint16_t nbyte) { SPI.beginTransaction(spiConfig); SPI.transfer(buf, nbyte); SPI.endTransaction(); @@ -120,7 +118,7 @@ void spiSend(uint8_t b) { //while (!TEST(SPSR, SPIF)) { /* Intentionally left empty */ } } -void spiSendBlock(uint8_t token, const uint8_t* buf) { +void spiSendBlock(uint8_t token, const uint8_t *buf) { SPI.beginTransaction(spiConfig); SPDR = token; for (uint16_t i = 0; i < 512; i += 2) { diff --git a/Marlin/src/HAL/TEENSY40_41/MarlinSPI.h b/Marlin/src/HAL/TEENSY40_41/MarlinSPI.h new file mode 100644 index 0000000000..0c447ba4cb --- /dev/null +++ b/Marlin/src/HAL/TEENSY40_41/MarlinSPI.h @@ -0,0 +1,26 @@ +/** + * 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 + +#include + +using MarlinSPI = SPIClass; diff --git a/Marlin/src/HAL/TEENSY40_41/Servo.h b/Marlin/src/HAL/TEENSY40_41/Servo.h index 699fd700c9..fceb9465ea 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/eeprom.cpp b/Marlin/src/HAL/TEENSY40_41/eeprom.cpp index 030a8c38af..357fed47b0 100644 --- a/Marlin/src/HAL/TEENSY40_41/eeprom.cpp +++ b/Marlin/src/HAL/TEENSY40_41/eeprom.cpp @@ -1,10 +1,9 @@ /** * Marlin 3D Printer Firmware - * * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com - * Copyright (c) 2015-2016 Nico Tonnhofer wurstnase.reprap@gmail.com - * Copyright (c) 2016 Victor Perez victor_pv@hotmail.com + * + * 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 @@ -22,33 +21,33 @@ */ #ifdef __IMXRT1062__ -#include "../../inc/MarlinConfig.h" - -#if USE_WIRED_EEPROM - /** * HAL PersistentStore for Teensy 4.0 (IMXRT1062DVL6A) / 4.1 (IMXRT1062DVJ6A) */ +#include "../../inc/MarlinConfig.h" + +#if USE_WIRED_EEPROM + #include "../shared/eeprom_api.h" #include #ifndef MARLIN_EEPROM_SIZE #define MARLIN_EEPROM_SIZE size_t(E2END + 1) #endif -size_t PersistentStore::capacity() { return MARLIN_EEPROM_SIZE; } +size_t PersistentStore::capacity() { return MARLIN_EEPROM_SIZE - eeprom_exclude_size; } bool PersistentStore::access_start() { 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 * const p = (uint8_t * const)pos; + uint8_t * const p = (uint8_t * const)REAL_EEPROM_ADDR(pos); uint8_t v = *value; - // EEPROM has only ~100,000 write cycles, - // so only write bytes that have changed! - if (v != eeprom_read_byte(p)) { + 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; @@ -61,9 +60,9 @@ bool PersistentStore::write_data(int &pos, const uint8_t *value, size_t size, ui return false; } -bool PersistentStore::read_data(int &pos, uint8_t* value, size_t size, uint16_t *crc, const bool writing/*=true*/) { +bool PersistentStore::read_data(int &pos, uint8_t *value, size_t size, uint16_t *crc, const bool writing/*=true*/) { do { - uint8_t c = eeprom_read_byte((uint8_t*)pos); + const uint8_t c = eeprom_read_byte((uint8_t*)REAL_EEPROM_ADDR(pos)); if (writing) *value = c; crc16(crc, &c, 1); pos++; diff --git a/Marlin/src/HAL/TEENSY40_41/endstop_interrupts.h b/Marlin/src/HAL/TEENSY40_41/endstop_interrupts.h index a05e911668..f59a9409c8 100644 --- a/Marlin/src/HAL/TEENSY40_41/endstop_interrupts.h +++ b/Marlin/src/HAL/TEENSY40_41/endstop_interrupts.h @@ -46,21 +46,34 @@ void endstop_ISR() { endstops.update(); } */ void setup_endstop_interrupts() { #define _ATTACH(P) attachInterrupt(digitalPinToInterrupt(P), endstop_ISR, CHANGE) - TERN_(HAS_X_MAX, _ATTACH(X_MAX_PIN)); - TERN_(HAS_X_MIN, _ATTACH(X_MIN_PIN)); - TERN_(HAS_Y_MAX, _ATTACH(Y_MAX_PIN)); - TERN_(HAS_Y_MIN, _ATTACH(Y_MIN_PIN)); - TERN_(HAS_Z_MAX, _ATTACH(Z_MAX_PIN)); - TERN_(HAS_Z_MIN, _ATTACH(Z_MIN_PIN)); - TERN_(HAS_X2_MAX, _ATTACH(X2_MAX_PIN)); - TERN_(HAS_X2_MIN, _ATTACH(X2_MIN_PIN)); - TERN_(HAS_Y2_MAX, _ATTACH(Y2_MAX_PIN)); - TERN_(HAS_Y2_MIN, _ATTACH(Y2_MIN_PIN)); - TERN_(HAS_Z2_MAX, _ATTACH(Z2_MAX_PIN)); - TERN_(HAS_Z2_MIN, _ATTACH(Z2_MIN_PIN)); - TERN_(HAS_Z3_MAX, _ATTACH(Z3_MAX_PIN)); - TERN_(HAS_Z3_MIN, _ATTACH(Z3_MIN_PIN)); - TERN_(HAS_Z4_MAX, _ATTACH(Z4_MAX_PIN)); - TERN_(HAS_Z4_MIN, _ATTACH(Z4_MIN_PIN)); - TERN_(HAS_Z_MIN_PROBE_PIN, _ATTACH(Z_MIN_PROBE_PIN)); + 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/TEENSY40_41/fast_pwm.cpp b/Marlin/src/HAL/TEENSY40_41/fast_pwm.cpp new file mode 100644 index 0000000000..21531d115d --- /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/fastio.h b/Marlin/src/HAL/TEENSY40_41/fastio.h index 52f991dfb8..179f044c9b 100644 --- a/Marlin/src/HAL/TEENSY40_41/fastio.h +++ b/Marlin/src/HAL/TEENSY40_41/fastio.h @@ -4,7 +4,6 @@ * * Based on Sprinter and grbl. * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm - * Copyright (c) 2017 Victor Perez * * 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 diff --git a/Marlin/src/HAL/TEENSY40_41/inc/Conditionals_LCD.h b/Marlin/src/HAL/TEENSY40_41/inc/Conditionals_LCD.h index 6a8540927b..5f1c4b1601 100644 --- a/Marlin/src/HAL/TEENSY40_41/inc/Conditionals_LCD.h +++ b/Marlin/src/HAL/TEENSY40_41/inc/Conditionals_LCD.h @@ -20,7 +20,3 @@ * */ #pragma once - -#if HAS_SPI_TFT || HAS_FSMC_TFT - #error "Sorry! TFT displays are not available for HAL/TEENSY40_41." -#endif diff --git a/Marlin/src/HAL/TEENSY40_41/inc/Conditionals_type.h b/Marlin/src/HAL/TEENSY40_41/inc/Conditionals_type.h new file mode 100644 index 0000000000..82f95a1035 --- /dev/null +++ b/Marlin/src/HAL/TEENSY40_41/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/TEENSY40_41/inc/SanityCheck.h b/Marlin/src/HAL/TEENSY40_41/inc/SanityCheck.h index fbfe7b0fc3..7b1c466f23 100644 --- a/Marlin/src/HAL/TEENSY40_41/inc/SanityCheck.h +++ b/Marlin/src/HAL/TEENSY40_41/inc/SanityCheck.h @@ -25,14 +25,34 @@ * Test TEENSY41 specific configuration values for errors at compile-time. */ +#if HAS_SPI_TFT || HAS_FSMC_TFT + #error "Sorry! TFT displays are not available for Teensy 4.0/4.1." +#endif + #if ENABLED(EMERGENCY_PARSER) #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 on Teensy 4.0/4.1." +#if HAS_TMC_SW_SERIAL + #error "TMC220x Software Serial is not supported for Teensy 4.0/4.1." #endif -#if HAS_TMC_SW_SERIAL - #error "TMC220x Software Serial is not supported on this platform." +#if ENABLED(POSTMORTEM_DEBUGGING) + #error "POSTMORTEM_DEBUGGING is not yet supported for Teensy 4.0/4.1." +#endif + +#if ENABLED(SERIAL_STATS_MAX_RX_QUEUED) || ENABLED(SERIAL_STATS_DROPPED_RX) || ENABLED(SERIAL_STATS_RX_FRAMING_ERRORS) || ENABLED(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 4ad62d00fe..3307b3f71f 100644 --- a/Marlin/src/HAL/TEENSY40_41/pinsDebug.h +++ b/Marlin/src/HAL/TEENSY40_41/pinsDebug.h @@ -2,6 +2,9 @@ * Marlin 3D Printer Firmware * Copyright (c) 2020 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 @@ -19,24 +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_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 PRINT_PIN(p) do{ sprintf_P(buffer, PSTR("%02d"), p); 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 ? 1 : 0) -#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 pwm_status(pin) HAL_pwm_status(pin) -#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 { @@ -116,12 +134,12 @@ const struct pwm_pin_info_struct pwm_pin_info[] = { #endif }; -void HAL_print_analog_pin(char buffer[], int8_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)); } -void HAL_analog_pin_state(char buffer[], int8_t pin) { +void analog_pin_state(char buffer[], const pin_t pin) { if (pin <= 23) sprintf_P(buffer, PSTR("Analog in =% 5d"), analogRead(pin - 14)); else if (pin <= 41) sprintf_P(buffer, PSTR("Analog in =% 5d"), analogRead(pin - 24)); } @@ -132,14 +150,14 @@ void HAL_analog_pin_state(char buffer[], int8_t pin) { * Print a pin's PWM status. * Return true if it's currently a PWM pin. */ -bool HAL_pwm_status(int8_t pin) { +bool pwm_status(const pin_t pin) { char buffer[20]; // for the sprintf statements const struct pwm_pin_info_struct *info; - if (pin >= CORE_NUM_DIGITAL) return 0; - info = pwm_pin_info + pin; + if (pin >= CORE_NUM_DIGITAL) return false; - if (info->type == 0) return 0; + info = pwm_pin_info + pin; + if (info->type == 0) return false; /* TODO decode pwm value from timers */ // for now just indicate if output is set as pwm @@ -147,4 +165,6 @@ bool HAL_pwm_status(int8_t pin) { return (*(portConfigRegister(pin)) == info->muxval); } -static void pwm_details(uint8_t pin) { /* TODO */ } +void printPinPWM(const pin_t) { /* TODO */ } + +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 d6f8d41bf6..fc1c2a7fed 100644 --- a/Marlin/src/HAL/TEENSY40_41/spi_pins.h +++ b/Marlin/src/HAL/TEENSY40_41/spi_pins.h @@ -25,7 +25,6 @@ * HAL SPI Pins for Teensy 4.0 (IMXRT1062DVL6A) / 4.1 (IMXRT1062DVJ6A) */ -#define SCK_PIN 13 -#define MISO_PIN 12 -#define MOSI_PIN 11 -#define SS_PIN 20 // SDSS // A.28, A.29, B.21, C.26, C.29 +#define SD_SCK_PIN 13 +#define SD_MISO_PIN 12 +#define SD_MOSI_PIN 11 diff --git a/Marlin/src/HAL/TEENSY40_41/timers.cpp b/Marlin/src/HAL/TEENSY40_41/timers.cpp index 81c9b08c17..011f9fe31c 100644 --- a/Marlin/src/HAL/TEENSY40_41/timers.cpp +++ b/Marlin/src/HAL/TEENSY40_41/timers.cpp @@ -30,62 +30,100 @@ void HAL_timer_start(const uint8_t timer_num, const uint32_t frequency) { switch (timer_num) { - case 0: - CCM_CSCMR1 &= ~CCM_CSCMR1_PERCLK_CLK_SEL; // turn off 24mhz mode + + // + // Step Timer – GPT1 - Compare Interrupt OCR1 - Reset Mode + // + case MF_TIMER_STEP: + // 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 - 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 + // Disable timer, clear all status bits + GPT1_CR = 0; // Disable timer + GPT1_SR = 0x3F; // Clear all prior status - OUT_WRITE(15, HIGH); + // Prescaler = 2 => 75MHz counting clock + GPT1_PR = GPT1_TIMER_PRESCALE - 1; + + 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; - case 1: - CCM_CSCMR1 &= ~CCM_CSCMR1_PERCLK_CLK_SEL; // turn off 24mhz mode + + // + // Temperature Timer – GPT2 - Compare Interrupt OCR1 - Reset Mode + // + case MF_TIMER_TEMP: + // 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 - 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 + // Disable timer, clear all status bits + GPT2_CR = 0; // Disable timer + GPT2_SR = 0x3F; // Clear all prior status - OUT_WRITE(14, HIGH); + // Prescaler = 10 => 15MHz counting clock + GPT2_PR = GPT2_TIMER_PRESCALE - 1; + + 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; } } void HAL_timer_enable_interrupt(const uint8_t timer_num) { switch (timer_num) { - case 0: - NVIC_ENABLE_IRQ(IRQ_GPT1); - break; - case 1: - NVIC_ENABLE_IRQ(IRQ_GPT2); - break; + case MF_TIMER_STEP: NVIC_ENABLE_IRQ(IRQ_GPT1); break; + case MF_TIMER_TEMP: NVIC_ENABLE_IRQ(IRQ_GPT2); break; } } void HAL_timer_disable_interrupt(const uint8_t timer_num) { switch (timer_num) { - case 0: NVIC_DISABLE_IRQ(IRQ_GPT1); break; - case 1: NVIC_DISABLE_IRQ(IRQ_GPT2); break; + case MF_TIMER_STEP: NVIC_DISABLE_IRQ(IRQ_GPT1); break; + 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"); @@ -93,20 +131,16 @@ void HAL_timer_disable_interrupt(const uint8_t timer_num) { bool HAL_timer_interrupt_enabled(const uint8_t timer_num) { switch (timer_num) { - case 0: return (NVIC_IS_ENABLED(IRQ_GPT1)); - case 1: return (NVIC_IS_ENABLED(IRQ_GPT2)); + case MF_TIMER_STEP: return (NVIC_IS_ENABLED(IRQ_GPT1)); + case MF_TIMER_TEMP: return (NVIC_IS_ENABLED(IRQ_GPT2)); } return false; } void HAL_timer_isr_prologue(const uint8_t timer_num) { switch (timer_num) { - case 0: - GPT1_SR = GPT_IR_OF1IE; // clear OF3 bit - break; - case 1: - GPT2_SR = GPT_IR_OF1IE; // clear OF3 bit - break; + 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 7e4cd080cb..66d00b1788 100644 --- a/Marlin/src/HAL/TEENSY40_41/timers.h +++ b/Marlin/src/HAL/TEENSY40_41/timers.h @@ -1,8 +1,9 @@ /** * Marlin 3D Printer Firmware * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com - * Copyright (c) 2015-2016 Nico Tonnhofer wurstnase.reprap@gmail.com + * + * 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 @@ -33,79 +34,85 @@ #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 STEP_TIMER_NUM - #define STEP_TIMER_NUM 0 // Timer Index for Stepper +#ifndef MF_TIMER_STEP + #define MF_TIMER_STEP 0 // Timer Index for Stepper #endif -#ifndef PULSE_TIMER_NUM - #define PULSE_TIMER_NUM STEP_TIMER_NUM +#ifndef MF_TIMER_PULSE + #define MF_TIMER_PULSE MF_TIMER_STEP #endif -#ifndef TEMP_TIMER_NUM - #define TEMP_TIMER_NUM 1 // Timer Index for Temperature +#ifndef MF_TIMER_TEMP + #define MF_TIMER_TEMP 1 // Timer Index for Temperature #endif #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(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) #ifndef HAL_STEP_TIMER_ISR - #define HAL_STEP_TIMER_ISR() extern "C" void stepTC_Handler() // GPT1_Handler() + #define HAL_STEP_TIMER_ISR() extern "C" void stepTC_Handler() // GPT1_Handler() #endif #ifndef HAL_TEMP_TIMER_ISR - #define HAL_TEMP_TIMER_ISR() extern "C" void tempTC_Handler() // GPT2_Handler() + #define HAL_TEMP_TIMER_ISR() extern "C" void tempTC_Handler() // GPT2_Handler() #endif -extern "C" void stepTC_Handler(); -extern "C" void tempTC_Handler(); +extern "C" { + void stepTC_Handler(); + void tempTC_Handler(); +} 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 0: + 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 1: + 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; } } FORCE_INLINE static hal_timer_t HAL_timer_get_compare(const uint8_t timer_num) { switch (timer_num) { - case 0: return GPT1_OCR1; - case 1: return GPT2_OCR1; + case MF_TIMER_STEP: return GPT1_OCR1; + case MF_TIMER_TEMP: return GPT2_OCR1; } return 0; } FORCE_INLINE static hal_timer_t HAL_timer_get_count(const uint8_t timer_num) { switch (timer_num) { - case 0: return GPT1_CNT; - case 1: return GPT2_CNT; + case MF_TIMER_STEP: return GPT1_CNT; + case MF_TIMER_TEMP: return GPT2_CNT; } return 0; } @@ -115,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(TIMER_NUM) +inline void HAL_timer_isr_epilogue(const uint8_t) {} diff --git a/Marlin/src/HAL/TEENSY40_41/u8g/LCD_defines.h b/Marlin/src/HAL/TEENSY40_41/u8g/LCD_defines.h new file mode 100644 index 0000000000..349f8c0399 --- /dev/null +++ b/Marlin/src/HAL/TEENSY40_41/u8g/LCD_defines.h @@ -0,0 +1,26 @@ +/** + * 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 + * (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 + +/** + * Teensy 4.0/4.1 LCD-specific defines + */ diff --git a/Marlin/src/HAL/platforms.h b/Marlin/src/HAL/platforms.h index ef17d19170..e7eea44308 100644 --- a/Marlin/src/HAL/platforms.h +++ b/Marlin/src/HAL/platforms.h @@ -24,29 +24,40 @@ #define XSTR(V...) #V #ifdef __AVR__ - #define HAL_PATH(PATH, NAME) XSTR(PATH/AVR/NAME) + #define HAL_PATH(PATH, NAME) XSTR(PATH/HAL/AVR/NAME) #elif defined(ARDUINO_ARCH_SAM) - #define HAL_PATH(PATH, NAME) XSTR(PATH/DUE/NAME) + #define HAL_PATH(PATH, NAME) XSTR(PATH/HAL/DUE/NAME) #elif defined(__MK20DX256__) - #define HAL_PATH(PATH, NAME) XSTR(PATH/TEENSY31_32/NAME) + #define HAL_PATH(PATH, NAME) XSTR(PATH/HAL/TEENSY31_32/NAME) #elif defined(__MK64FX512__) || defined(__MK66FX1M0__) - #define HAL_PATH(PATH, NAME) XSTR(PATH/TEENSY35_36/NAME) + #define HAL_PATH(PATH, NAME) XSTR(PATH/HAL/TEENSY35_36/NAME) #elif defined(__IMXRT1062__) - #define HAL_PATH(PATH, NAME) XSTR(PATH/TEENSY40_41/NAME) + #define HAL_PATH(PATH, NAME) XSTR(PATH/HAL/TEENSY40_41/NAME) #elif defined(TARGET_LPC1768) - #define HAL_PATH(PATH, NAME) XSTR(PATH/LPC1768/NAME) + #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/STM32F1/NAME) -#elif defined(STM32GENERIC) && (defined(STM32F4) || defined(STM32F7)) - #define HAL_PATH(PATH, NAME) XSTR(PATH/STM32_F4_F7/NAME) + #define HAL_PATH(PATH, NAME) XSTR(PATH/HAL/STM32F1/NAME) #elif defined(ARDUINO_ARCH_STM32) - #define HAL_PATH(PATH, NAME) XSTR(PATH/STM32/NAME) + #ifndef HAL_STM32 + #define HAL_STM32 + #endif + #define HAL_PATH(PATH, NAME) XSTR(PATH/HAL/STM32/NAME) #elif defined(ARDUINO_ARCH_ESP32) - #define HAL_PATH(PATH, NAME) XSTR(PATH/ESP32/NAME) + #define HAL_PATH(PATH, NAME) XSTR(PATH/HAL/ESP32/NAME) #elif defined(__PLAT_LINUX__) - #define HAL_PATH(PATH, NAME) XSTR(PATH/LINUX/NAME) + #define HAL_PATH(PATH, NAME) XSTR(PATH/HAL/LINUX/NAME) +#elif defined(__PLAT_NATIVE_SIM__) + #define HAL_PATH(PATH, NAME) XSTR(PATH/HAL/NATIVE_SIM/NAME) #elif defined(__SAMD51__) - #define HAL_PATH(PATH, NAME) XSTR(PATH/SAMD51/NAME) + #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 new file mode 100644 index 0000000000..5aa3a235fd --- /dev/null +++ b/Marlin/src/HAL/shared/Delay.cpp @@ -0,0 +1,171 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2020 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 "Delay.h" + +#include "../../inc/MarlinConfig.h" + +#if defined(__arm__) || defined(__thumb__) + + static uint32_t ASM_CYCLES_PER_ITERATION = 4; // Initial bet which will be adjusted in calibrate_delay_loop + + // Simple assembler loop counting down + void delay_asm(uint32_t cy) { + cy = _MAX(cy / ASM_CYCLES_PER_ITERATION, 1U); // Zero is forbidden here + __asm__ __volatile__( + A(".syntax unified") // is to prevent CM0,CM1 non-unified syntax + L("1") + A("subs %[cnt],#1") + A("bne 1b") + : [cnt]"+r"(cy) // output: +r means input+output + : // input: + : "cc" // clobbers: + ); + } + + // We can't use CMSIS since it's not available on all platform, so fallback to hardcoded register values + #define HW_REG(X) *(volatile uint32_t *)(X) + #define _DWT_CTRL 0xE0001000 + #define _DWT_CYCCNT 0xE0001004 // CYCCNT is 32bits, takes 37s or so to wrap. + #define _DEM_CR 0xE000EDFC + #define _LAR 0xE0001FB0 + + // Use hardware cycle counter instead, it's much safer + void delay_dwt(uint32_t count) { + // Reuse the ASM_CYCLES_PER_ITERATION variable to avoid wasting another useless variable + uint32_t start = HW_REG(_DWT_CYCCNT) - ASM_CYCLES_PER_ITERATION, elapsed; + do { + elapsed = HW_REG(_DWT_CYCCNT) - start; + } while (elapsed < count); + } + + // Pointer to asm function, calling the functions has a ~20 cycles overhead + DelayImpl DelayCycleFnc = delay_asm; + + void calibrate_delay_loop() { + // Check if we have a working DWT implementation in the CPU (see https://developer.arm.com/documentation/ddi0439/b/Data-Watchpoint-and-Trace-Unit/DWT-Programmers-Model) + if (!HW_REG(_DWT_CTRL)) { + // No DWT present, so fallback to plain old ASM nop counting + // Unfortunately, we don't exactly know how many iteration it'll take to decrement a counter in a loop + // It depends on the CPU architecture, the code current position (flash vs SRAM) + // So, instead of wild guessing and making mistake, instead + // compute it once for all + ASM_CYCLES_PER_ITERATION = 1; + // We need to fetch some reference clock before waiting + cli(); + uint32_t start = micros(); + delay_asm(1000); // On a typical CPU running in MHz, waiting 1000 "unknown cycles" means it'll take between 1ms to 6ms, that's perfectly acceptable + uint32_t end = micros(); + sei(); + uint32_t expectedCycles = (end - start) * ((F_CPU) / 1000000UL); // Convert microseconds to cycles + // Finally compute the right scale + ASM_CYCLES_PER_ITERATION = (uint32_t)(expectedCycles / 1000); + + // No DWT present, likely a Cortex M0 so NOP counting is our best bet here + DelayCycleFnc = delay_asm; + } + else { + // Enable DWT counter + // 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 + #endif + HW_REG(_DWT_CYCCNT) = 0; // Clear DWT cycle counter + HW_REG(_DWT_CTRL) = HW_REG(_DWT_CTRL) | 1; // Enable DWT cycle counter + + // Then calibrate the constant offset from the counter + ASM_CYCLES_PER_ITERATION = 0; + uint32_t s = HW_REG(_DWT_CYCCNT); + uint32_t e = HW_REG(_DWT_CYCCNT); // (e - s) contains the number of cycle required to read the cycle counter + delay_dwt(0); + uint32_t f = HW_REG(_DWT_CYCCNT); // (f - e) contains the delay to call the delay function + the time to read the cycle counter + ASM_CYCLES_PER_ITERATION = (f - e) - (e - s); + + // Use safer DWT function + DelayCycleFnc = delay_dwt; + } + } + + #if ENABLED(MARLIN_DEV_MODE) + void dump_delay_accuracy_check() { + auto report_call_time = [](FSTR_P const name, FSTR_P const unit, const uint32_t cycles, const uint32_t total, const bool do_flush=true) { + SERIAL_ECHOLN(F("Calling "), name, F(" for "), cycles, C(' '), unit, F(" took: "), total, C(' '), unit); + if (do_flush) SERIAL_FLUSHTX(); + }; + + uint32_t s, e; + + SERIAL_ECHOLNPGM("Computed delay calibration value: ", ASM_CYCLES_PER_ITERATION); + SERIAL_FLUSH(); + // Display the results of the calibration above + constexpr uint32_t testValues[] = { 1, 5, 10, 20, 50, 100, 150, 200, 350, 500, 750, 1000 }; + for (auto i : testValues) { + s = micros(); DELAY_US(i); e = micros(); + report_call_time(F("delay"), F("us"), i, e - s); + } + + if (HW_REG(_DWT_CTRL)) { + static FSTR_P cyc = F("cycles"); + static FSTR_P dcd = F("DELAY_CYCLES directly "); + + for (auto i : testValues) { + s = HW_REG(_DWT_CYCCNT); DELAY_CYCLES(i); e = HW_REG(_DWT_CYCCNT); + report_call_time(F("runtime delay"), cyc, i, e - s); + } + + // Measure the delay to call a real function compared to a function pointer + s = HW_REG(_DWT_CYCCNT); delay_dwt(1); e = HW_REG(_DWT_CYCCNT); + report_call_time(F("delay_dwt"), cyc, 1, e - s); + + s = HW_REG(_DWT_CYCCNT); DELAY_CYCLES( 1); e = HW_REG(_DWT_CYCCNT); + report_call_time(dcd, cyc, 1, e - s, false); + + s = HW_REG(_DWT_CYCCNT); DELAY_CYCLES( 5); e = HW_REG(_DWT_CYCCNT); + report_call_time(dcd, cyc, 5, e - s, false); + + s = HW_REG(_DWT_CYCCNT); DELAY_CYCLES(10); e = HW_REG(_DWT_CYCCNT); + report_call_time(dcd, cyc, 10, e - s, false); + + s = HW_REG(_DWT_CYCCNT); DELAY_CYCLES(20); e = HW_REG(_DWT_CYCCNT); + report_call_time(dcd, cyc, 20, e - s, false); + + s = HW_REG(_DWT_CYCCNT); DELAY_CYCLES(50); e = HW_REG(_DWT_CYCCNT); + report_call_time(dcd, cyc, 50, e - s, false); + + s = HW_REG(_DWT_CYCCNT); DELAY_CYCLES(100); e = HW_REG(_DWT_CYCCNT); + report_call_time(dcd, cyc, 100, e - s, false); + + s = HW_REG(_DWT_CYCCNT); DELAY_CYCLES(200); e = HW_REG(_DWT_CYCCNT); + report_call_time(dcd, cyc, 200, e - s, false); + } + } + #endif // MARLIN_DEV_MODE + +#else + + void calibrate_delay_loop() {} + #if ENABLED(MARLIN_DEV_MODE) + void dump_delay_accuracy_check() { SERIAL_ECHOPGM("N/A on this platform"); } + #endif + +#endif diff --git a/Marlin/src/HAL/shared/Delay.h b/Marlin/src/HAL/shared/Delay.h index d98e960848..eeaf4c59fc 100644 --- a/Marlin/src/HAL/shared/Delay.h +++ b/Marlin/src/HAL/shared/Delay.h @@ -21,6 +21,8 @@ */ #pragma once +#include "../../inc/MarlinConfigPre.h" + /** * Busy wait delay cycles routines: * @@ -31,122 +33,149 @@ #include "../../core/macros.h" +void calibrate_delay_loop(); + #if defined(__arm__) || defined(__thumb__) - #if __CORTEX_M == 7 + // We want to have delay_cycle function with the lowest possible overhead, so we adjust at the function at runtime based on the current CPU best feature + typedef void (*DelayImpl)(uint32_t); + extern DelayImpl DelayCycleFnc; - // Cortex-M3 through M7 can use the cycle counter of the DWT unit - // https://www.anthonyvh.com/2017/05/18/cortex_m-cycle_counter/ + // I've measured 36 cycles on my system to call the cycle waiting method, but it shouldn't change much to have a bit more margin, it only consume a bit more flash + #define TRIP_POINT_FOR_CALLING_FUNCTION 40 - FORCE_INLINE static void enableCycleCounter() { - CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; - - #if __CORTEX_M == 7 - DWT->LAR = 0xC5ACCE55; // Unlock DWT on the M7 - #endif - - DWT->CYCCNT = 0; - DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; + // A simple recursive template class that output exactly one 'nop' of code per recursion + template struct NopWriter { + FORCE_INLINE static void build() { + __asm__ __volatile__("nop"); + NopWriter::build(); } + }; + // End the loop + template <> struct NopWriter<0> { FORCE_INLINE static void build() {} }; - FORCE_INLINE volatile uint32_t getCycleCount() { return DWT->CYCCNT; } - - FORCE_INLINE static void DELAY_CYCLES(const uint32_t x) { - const uint32_t endCycles = getCycleCount() + x; - while (PENDING(getCycleCount(), endCycles)) {} - } - - #else - - // https://blueprints.launchpad.net/gcc-arm-embedded/+spec/delay-cycles - - #define nop() __asm__ __volatile__("nop;\n\t":::) - - FORCE_INLINE static void __delay_4cycles(uint32_t cy) { // +1 cycle - #if ARCH_PIPELINE_RELOAD_CYCLES < 2 - #define EXTRA_NOP_CYCLES A("nop") - #else - #define EXTRA_NOP_CYCLES "" - #endif - - __asm__ __volatile__( - A(".syntax unified") // is to prevent CM0,CM1 non-unified syntax - L("1") - A("subs %[cnt],#1") - EXTRA_NOP_CYCLES - A("bne 1b") - : [cnt]"+r"(cy) // output: +r means input+output - : // input: - : "cc" // clobbers: - ); - } - - // Delay in cycles - FORCE_INLINE static void DELAY_CYCLES(uint32_t x) { - - if (__builtin_constant_p(x)) { - #define MAXNOPS 4 - - if (x <= (MAXNOPS)) { - switch (x) { case 4: nop(); case 3: nop(); case 2: nop(); case 1: nop(); } - } - else { // because of +1 cycle inside delay_4cycles - const uint32_t rem = (x - 1) % (MAXNOPS); - switch (rem) { case 3: nop(); case 2: nop(); case 1: nop(); } - if ((x = (x - 1) / (MAXNOPS))) - __delay_4cycles(x); // if need more then 4 nop loop is more optimal - } - #undef MAXNOPS + namespace Private { + // Split recursing template in 2 different class so we don't reach the maximum template instantiation depth limit + template struct Helper { + FORCE_INLINE static void build() { + DelayCycleFnc(N - 2); // Approximative cost of calling the function (might be off by one or 2 cycles) } - else if ((x >>= 2)) - __delay_4cycles(x); - } - #undef nop + }; + template struct Helper { + FORCE_INLINE static void build() { + NopWriter::build(); + } + }; + + template <> struct Helper { + FORCE_INLINE static void build() {} + }; + + } + // Select a behavior based on the constexpr'ness of the parameter + // If called with a compile-time parameter, then write as many NOP as required to reach the asked cycle count + // (there is some tripping point here to start looping when it's more profitable than gruntly executing NOPs) + // If not called from a compile-time parameter, fallback to a runtime loop counting version instead + template + struct SmartDelay { + FORCE_INLINE SmartDelay(int) { + if (Cycles == 0) return; + Private::Helper::build(); + } + }; + // Runtime version below. There is no way this would run under less than ~TRIP_POINT_FOR_CALLING_FUNCTION cycles + template + struct SmartDelay { + FORCE_INLINE SmartDelay(int v) { DelayCycleFnc(v); } + }; + + #define DELAY_CYCLES(X) do { SmartDelay _smrtdly_X(X); } while(0) + + #if GCC_VERSION <= 70000 + #define DELAY_CYCLES_VAR(X) DelayCycleFnc(X) + #else + #define DELAY_CYCLES_VAR DELAY_CYCLES #endif + // 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((unsigned long)(x) * ((F_CPU) / 1000000UL)) + #elif defined(__AVR__) - - #define nop() __asm__ __volatile__("nop;\n\t":::) - - FORCE_INLINE static void __delay_4cycles(uint8_t cy) { - __asm__ __volatile__( - L("1") - A("dec %[cnt]") - A("nop") - A("brne 1b") - : [cnt] "+r"(cy) // output: +r means input+output - : // input: - : "cc" // clobbers: - ); + FORCE_INLINE static void __delay_up_to_3c(uint8_t cycles) { + switch (cycles) { + case 3: + __asm__ __volatile__(A("RJMP .+0") A("NOP")); + break; + case 2: + __asm__ __volatile__(A("RJMP .+0")); + break; + case 1: + __asm__ __volatile__(A("NOP")); + break; + } } // Delay in cycles - FORCE_INLINE static void DELAY_CYCLES(uint16_t x) { - - if (__builtin_constant_p(x)) { - #define MAXNOPS 4 - - if (x <= (MAXNOPS)) { - switch (x) { case 4: nop(); case 3: nop(); case 2: nop(); case 1: nop(); } + FORCE_INLINE static void DELAY_CYCLES(uint16_t cycles) { + if (__builtin_constant_p(cycles)) { + if (cycles <= 3) { + __delay_up_to_3c(cycles); + } + else if (cycles == 4) { + __delay_up_to_3c(2); + __delay_up_to_3c(2); } else { - const uint32_t rem = (x) % (MAXNOPS); - switch (rem) { case 3: nop(); case 2: nop(); case 1: nop(); } - if ((x = (x) / (MAXNOPS))) - __delay_4cycles(x); // if need more then 4 nop loop is more optimal + cycles -= 1 + 4; // Compensate for the first LDI (1) and the first round (4) + __delay_up_to_3c(cycles % 4); + + cycles /= 4; + // The following code burns [1 + 4 * (rounds+1)] cycles + uint16_t dummy; + __asm__ __volatile__( + // "manually" load counter from constants, otherwise the compiler may optimize this part away + A("LDI %A[rounds], %[l]") // 1c + A("LDI %B[rounds], %[h]") // 1c (compensating the non branching BRCC) + L("1") + A("SBIW %[rounds], 1") // 2c + A("BRCC 1b") // 2c when branching, else 1c (end of loop) + : // Outputs ... + [rounds] "=w" (dummy) // Restrict to a wo (=) 16 bit register pair (w) + : // Inputs ... + [l] "M" (cycles%256), // Restrict to 0..255 constant (M) + [h] "M" (cycles/256) // Restrict to 0..255 constant (M) + :// Clobbers ... + "cc" // Indicate we are modifying flags like Carry (cc) + ); } - - #undef MAXNOPS } - else if ((x >>= 2)) - __delay_4cycles(x); + else { + __asm__ __volatile__( + L("1") + A("SBIW %[cycles], 4") // 2c + A("BRCC 1b") // 2c when branching, else 1c (end of loop) + : [cycles] "+w" (cycles) // output: Restrict to a rw (+) 16 bit register pair (w) + : // input: - + : "cc" // clobbers: We are modifying flags like Carry (cc) + ); + } } - #undef nop -#elif defined(__PLAT_LINUX__) || defined(ESP32) + // Delay in microseconds + #define DELAY_US(x) DELAY_CYCLES((unsigned long)(x) * ((F_CPU) / 1000000UL)) - // specified inside platform + #define DELAY_CYCLES_VAR DELAY_CYCLES + +#elif defined(ESP32) || defined(__PLAT_LINUX__) || defined(__PLAT_NATIVE_SIM__) + + // DELAY_CYCLES specified inside platform + + // Delay in microseconds + #define DELAY_US(x) DELAY_CYCLES((unsigned long)(x) * ((F_CPU) / 1000000UL)) + + #define DELAY_CYCLES_VAR DELAY_CYCLES #else @@ -154,8 +183,40 @@ #endif -// Delay in nanoseconds -#define DELAY_NS(x) DELAY_CYCLES( (x) * (F_CPU / 1000000UL) / 1000UL ) +/************************************************************** + * Delay in nanoseconds. Requires the F_CPU macro. + * These macros follow avr-libc delay conventions. + * + * For AVR there are three possible operation modes, due to its + * slower clock speeds and thus coarser delay resolution. For + * example, when F_CPU = 16000000 the resolution is 62.5ns. + * + * Round up (default) + * Round up the delay according to the CPU clock resolution. + * e.g., 100 will give a delay of 2 cycles (125ns). + * + * Round down (DELAY_NS_ROUND_DOWN) + * Round down the delay according to the CPU clock resolution. + * e.g., 100 will be rounded down to 1 cycle (62.5ns). + * + * Nearest (DELAY_NS_ROUND_CLOSEST) + * Round the delay to the nearest number of clock cycles. + * e.g., 165 will be rounded up to 3 cycles (187.5ns) because + * it's closer to the requested delay than 2 cycle (125ns). + */ -// Delay in microseconds -#define DELAY_US(x) DELAY_CYCLES( (x) * (F_CPU / 1000000UL) ) +#ifndef __AVR__ + #undef DELAY_NS_ROUND_DOWN + #undef DELAY_NS_ROUND_CLOSEST +#endif + +#if ENABLED(DELAY_NS_ROUND_DOWN) + #define _NS_TO_CYCLES(x) ( (x) * ((F_CPU) / 1000000UL) / 1000UL) // floor +#elif ENABLED(DELAY_NS_ROUND_CLOSEST) + #define _NS_TO_CYCLES(x) (((x) * ((F_CPU) / 1000000UL) + 500) / 1000UL) // round +#else + #define _NS_TO_CYCLES(x) (((x) * ((F_CPU) / 1000000UL) + 999) / 1000UL) // "ceil" +#endif + +#define DELAY_NS(x) DELAY_CYCLES(_NS_TO_CYCLES(x)) +#define DELAY_NS_VAR(x) DELAY_CYCLES_VAR(_NS_TO_CYCLES(x)) diff --git a/Marlin/src/HAL/LINUX/watchdog.cpp b/Marlin/src/HAL/shared/HAL.cpp similarity index 79% rename from Marlin/src/HAL/LINUX/watchdog.cpp rename to Marlin/src/HAL/shared/HAL.cpp index c15b0e311c..4d92aedd9a 100644 --- a/Marlin/src/HAL/LINUX/watchdog.cpp +++ b/Marlin/src/HAL/shared/HAL.cpp @@ -1,6 +1,6 @@ /** * Marlin 3D Printer Firmware - * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] + * Copyright (c) 2022 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] * * Based on Sprinter and grbl. * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm @@ -19,17 +19,18 @@ * along with this program. If not, see . * */ -#ifdef __PLAT_LINUX__ + +/** + * HAL/shared/HAL.cpp + */ #include "../../inc/MarlinConfig.h" -#if ENABLED(USE_WATCHDOG) +MarlinHAL hal; -#include "watchdog.h" +#if ENABLED(SOFT_RESET_VIA_SERIAL) -void watchdog_init() {} -void HAL_watchdog_refresh() {} + // Global for use by e_parser.h + void HAL_reboot() { hal.reboot(); } #endif - -#endif // __PLAT_LINUX__ diff --git a/Marlin/src/HAL/shared/HAL_SPI.h b/Marlin/src/HAL/shared/HAL_SPI.h index 59af554806..6611f9ec4e 100644 --- a/Marlin/src/HAL/shared/HAL_SPI.h +++ b/Marlin/src/HAL/shared/HAL_SPI.h @@ -71,10 +71,10 @@ void spiSend(uint8_t b); uint8_t spiRec(); // Read from SPI into buffer -void spiRead(uint8_t* buf, uint16_t nbyte); +void spiRead(uint8_t *buf, uint16_t nbyte); // Write token and then write from 512 byte buffer to SPI (for SD card) -void spiSendBlock(uint8_t token, const uint8_t* buf); +void spiSendBlock(uint8_t token, const uint8_t *buf); // Begin SPI transaction, set clock, bit order, data mode void spiBeginTransaction(uint32_t spiClock, uint8_t bitOrder, uint8_t dataMode); @@ -87,7 +87,7 @@ void spiBeginTransaction(uint32_t spiClock, uint8_t bitOrder, uint8_t dataMode); void spiSend(uint32_t chan, byte b); // Write buffer to specified SPI channel -void spiSend(uint32_t chan, const uint8_t* buf, size_t n); +void spiSend(uint32_t chan, const uint8_t *buf, size_t n); // Read single byte from specified SPI channel uint8_t spiRec(uint32_t chan); diff --git a/Marlin/src/HAL/shared/HAL_ST7920.h b/Marlin/src/HAL/shared/HAL_ST7920.h index 4e362f96ba..305736c3a5 100644 --- a/Marlin/src/HAL/shared/HAL_ST7920.h +++ b/Marlin/src/HAL/shared/HAL_ST7920.h @@ -27,7 +27,7 @@ * (bypassing U8G), it will allow the LIGHTWEIGHT_UI to operate. */ -#if BOTH(HAS_MARLINUI_U8GLIB, LIGHTWEIGHT_UI) +#if ALL(HAS_MARLINUI_U8GLIB, LIGHTWEIGHT_UI) void ST7920_cs(); void ST7920_ncs(); void ST7920_set_cmd(); diff --git a/Marlin/src/HAL/shared/HAL_spi_L6470.cpp b/Marlin/src/HAL/shared/HAL_spi_L6470.cpp deleted file mode 100644 index bd85dbe7bd..0000000000 --- a/Marlin/src/HAL/shared/HAL_spi_L6470.cpp +++ /dev/null @@ -1,139 +0,0 @@ -/** - * Marlin 3D Printer Firmware - * Copyright (c) 2020 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 . - * - */ - -/** - * Software L6470 SPI functions originally from Arduino Sd2Card Library - * Copyright (c) 2009 by William Greiman - */ - -#include "../../inc/MarlinConfig.h" - -#if HAS_L64XX - -#include "Delay.h" - -#include "../../core/serial.h" -#include "../../libs/L64XX/L64XX_Marlin.h" - -// Make sure GCC optimizes this file. -// Note that this line triggers a bug in GCC which is fixed by casting. -// See the note below. -#pragma GCC optimize (3) - -// run at ~4Mhz -inline uint8_t L6470_SpiTransfer_Mode_0(uint8_t b) { // using Mode 0 - for (uint8_t bits = 8; bits--;) { - WRITE(L6470_CHAIN_MOSI_PIN, b & 0x80); - b <<= 1; // little setup time - - WRITE(L6470_CHAIN_SCK_PIN, HIGH); - DELAY_NS(125); // 10 cycles @ 84mhz - - b |= (READ(L6470_CHAIN_MISO_PIN) != 0); - - WRITE(L6470_CHAIN_SCK_PIN, LOW); - DELAY_NS(125); // 10 cycles @ 84mhz - } - return b; -} - -inline uint8_t L6470_SpiTransfer_Mode_3(uint8_t b) { // using Mode 3 - for (uint8_t bits = 8; bits--;) { - WRITE(L6470_CHAIN_SCK_PIN, LOW); - WRITE(L6470_CHAIN_MOSI_PIN, b & 0x80); - - DELAY_NS(125); // 10 cycles @ 84mhz - WRITE(L6470_CHAIN_SCK_PIN, HIGH); - DELAY_NS(125); // Need more delay for fast CPUs - - b <<= 1; // little setup time - b |= (READ(L6470_CHAIN_MISO_PIN) != 0); - } - DELAY_NS(125); // 10 cycles @ 84mhz - return b; -} - -/** - * L64XX methods for SPI init and transfer - */ -void L64XX_Marlin::spi_init() { - OUT_WRITE(L6470_CHAIN_SS_PIN, HIGH); - OUT_WRITE(L6470_CHAIN_SCK_PIN, HIGH); - OUT_WRITE(L6470_CHAIN_MOSI_PIN, HIGH); - SET_INPUT(L6470_CHAIN_MISO_PIN); - - #if PIN_EXISTS(L6470_BUSY) - SET_INPUT(L6470_BUSY_PIN); - #endif - - OUT_WRITE(L6470_CHAIN_MOSI_PIN, HIGH); -} - -uint8_t L64XX_Marlin::transfer_single(uint8_t data, int16_t ss_pin) { - // First device in chain has data sent last - extDigitalWrite(ss_pin, LOW); - - DISABLE_ISRS(); // Disable interrupts during SPI transfer (can't allow partial command to chips) - const uint8_t data_out = L6470_SpiTransfer_Mode_3(data); - ENABLE_ISRS(); // Enable interrupts - - extDigitalWrite(ss_pin, HIGH); - return data_out; -} - -uint8_t L64XX_Marlin::transfer_chain(uint8_t data, int16_t ss_pin, uint8_t chain_position) { - uint8_t data_out = 0; - - // first device in chain has data sent last - extDigitalWrite(ss_pin, LOW); - - for (uint8_t i = L64XX::chain[0]; !L64xxManager.spi_abort && i >= 1; i--) { // Send data unless aborted - DISABLE_ISRS(); // Disable interrupts during SPI transfer (can't allow partial command to chips) - const uint8_t temp = L6470_SpiTransfer_Mode_3(uint8_t(i == chain_position ? data : dSPIN_NOP)); - ENABLE_ISRS(); // Enable interrupts - if (i == chain_position) data_out = temp; - } - - extDigitalWrite(ss_pin, HIGH); - return data_out; -} - -/** - * Platform-supplied L6470 buffer transfer method - */ -void L64XX_Marlin::transfer(uint8_t L6470_buf[], const uint8_t length) { - // First device in chain has its data sent last - - if (spi_active) { // Interrupted SPI transfer so need to - WRITE(L6470_CHAIN_SS_PIN, HIGH); // guarantee min high of 650ns - DELAY_US(1); - } - - WRITE(L6470_CHAIN_SS_PIN, LOW); - for (uint8_t i = length; i >= 1; i--) - L6470_SpiTransfer_Mode_3(uint8_t(L6470_buf[i])); - WRITE(L6470_CHAIN_SS_PIN, HIGH); -} - -#pragma GCC reset_options - -#endif // HAS_L64XX diff --git a/Marlin/src/HAL/shared/Marduino.h b/Marlin/src/HAL/shared/Marduino.h index 3003f3cc28..0e2a021a3c 100644 --- a/Marlin/src/HAL/shared/Marduino.h +++ b/Marlin/src/HAL/shared/Marduino.h @@ -28,9 +28,9 @@ #undef DISABLED // Redefined by ESP32 #undef M_PI // Redefined by all #undef _BV // Redefined by some -#undef sq // Redefined by teensy3/wiring.h #undef SBI // Redefined by arduino/const_functions.h #undef CBI // Redefined by arduino/const_functions.h +#undef sq // Redefined by teensy3/wiring.h #undef UNUSED // Redefined by stm32f4xx_hal_def.h #include // NOTE: If included earlier then this line is a NOOP @@ -39,19 +39,17 @@ #define DISABLED(V...) DO(DIS,&&,V) #undef _BV -#define _BV(b) (1UL << (b)) +#define _BV(b) (1 << (b)) +#ifndef SBI + #define SBI(A,B) (A |= _BV(B)) +#endif +#ifndef CBI + #define CBI(A,B) (A &= ~_BV(B)) +#endif #undef sq #define sq(x) ((x)*(x)) -#ifndef SBI - #define SBI(A,B) (A |= (1 << (B))) -#endif - -#ifndef CBI - #define CBI(A,B) (A &= ~(1 << (B))) -#endif - #ifndef __AVR__ #ifndef strchr_P // Some platforms define a macro (DUE, teensy35) inline const char* strchr_P(const char *s, int c) { return strchr(s,c); } @@ -83,3 +81,16 @@ #ifndef UNUSED #define UNUSED(x) ((void)(x)) #endif + +#ifndef FORCE_INLINE + #define FORCE_INLINE __attribute__((always_inline)) inline +#endif + +#include "progmem.h" + +class __FlashStringHelper; +typedef const __FlashStringHelper* FSTR_P; +#ifndef FPSTR + #define FPSTR(S) (reinterpret_cast(S)) +#endif +#define FTOP(S) (reinterpret_cast(S)) diff --git a/Marlin/src/HAL/shared/MinSerial.cpp b/Marlin/src/HAL/shared/MinSerial.cpp new file mode 100644 index 0000000000..2e718d83dc --- /dev/null +++ b/Marlin/src/HAL/shared/MinSerial.cpp @@ -0,0 +1,33 @@ +/** + * 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 . + * + */ +#include "MinSerial.h" + +#if ENABLED(POSTMORTEM_DEBUGGING) + +void HAL_min_serial_init_default() {} +void HAL_min_serial_out_default(char ch) { SERIAL_CHAR(ch); } +void (*HAL_min_serial_init)() = &HAL_min_serial_init_default; +void (*HAL_min_serial_out)(char) = &HAL_min_serial_out_default; + +bool MinSerial::force_using_default_output = false; + +#endif diff --git a/Marlin/src/HAL/shared/MinSerial.h b/Marlin/src/HAL/shared/MinSerial.h new file mode 100644 index 0000000000..3089b8aa06 --- /dev/null +++ b/Marlin/src/HAL/shared/MinSerial.h @@ -0,0 +1,79 @@ +/** + * 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 + +#include "../../core/serial.h" +#include + +// Serial stuff here +// Inside an exception handler, the CPU state is not safe, we can't expect the handler to resume +// and the software to continue. UART communication can't rely on later callback/interrupt as it might never happen. +// So, you need to provide some method to send one byte to the usual UART with the interrupts disabled +// By default, the method uses SERIAL_CHAR but it's 100% guaranteed to break (couldn't be worse than nothing...)7 +extern void (*HAL_min_serial_init)(); +extern void (*HAL_min_serial_out)(char ch); + +struct MinSerial { + static bool force_using_default_output; + // Serial output + static void TX(char ch) { + if (force_using_default_output) + SERIAL_CHAR(ch); + else + HAL_min_serial_out(ch); + } + // Send String through UART + static void TX(const char *s) { while (*s) TX(*s++); } + // Send a digit through UART + static void TXDigit(uint32_t d) { + if (d < 10) TX((char)(d+'0')); + else if (d < 16) TX((char)(d+'A'-10)); + else TX('?'); + } + + // Send Hex number through UART + static void TXHex(uint32_t v) { + TX("0x"); + for (uint8_t i = 0; i < 8; i++, v <<= 4) + TXDigit((v >> 28) & 0xF); + } + + // Send Decimal number through UART + static void TXDec(uint32_t v) { + if (!v) { + TX('0'); + return; + } + + char nbrs[14]; + char *p = &nbrs[0]; + while (v != 0) { + *p++ = '0' + (v % 10); + v /= 10; + } + do { + p--; + TX(*p); + } while (p != &nbrs[0]); + } + static void init() { if (!force_using_default_output) HAL_min_serial_init(); } +}; diff --git a/Marlin/src/HAL/shared/backtrace/backtrace.cpp b/Marlin/src/HAL/shared/backtrace/backtrace.cpp index 6cf5e055e1..33e8e65154 100644 --- a/Marlin/src/HAL/shared/backtrace/backtrace.cpp +++ b/Marlin/src/HAL/shared/backtrace/backtrace.cpp @@ -25,30 +25,32 @@ #include "unwinder.h" #include "unwmemaccess.h" -#include "../../../core/serial.h" +#include "../MinSerial.h" #include // Dump a backtrace entry -static bool UnwReportOut(void* ctx, const UnwReport* bte) { +static bool UnwReportOut(void *ctx, const UnwReport *bte) { int *p = (int*)ctx; (*p)++; - SERIAL_CHAR('#'); SERIAL_PRINT(*p, DEC); SERIAL_ECHOPGM(" : "); - SERIAL_ECHOPGM(bte->name ? bte->name : "unknown"); SERIAL_ECHOPGM("@0x"); SERIAL_PRINT(bte->function, HEX); - SERIAL_CHAR('+'); SERIAL_PRINT(bte->address - bte->function,DEC); - SERIAL_ECHOPGM(" PC:"); SERIAL_PRINT(bte->address,HEX); SERIAL_CHAR('\n'); + const uint32_t a = bte->address, f = bte->function; + MinSerial::TX('#'); MinSerial::TXDec(*p); MinSerial::TX(" : "); + MinSerial::TX(bte->name?:"unknown"); MinSerial::TX('@'); MinSerial::TXHex(f); + MinSerial::TX('+'); MinSerial::TXDec(a - f); + MinSerial::TX(" PC:"); MinSerial::TXHex(a); + MinSerial::TX('\n'); return true; } #ifdef UNW_DEBUG - void UnwPrintf(const char* format, ...) { + void UnwPrintf(const char *format, ...) { char dest[256]; va_list argptr; va_start(argptr, format); vsprintf(dest, format, argptr); va_end(argptr); - TX(&dest[0]); + MinSerial::TX(&dest[0]); } #endif @@ -63,10 +65,10 @@ static const UnwindCallbacks UnwCallbacks = { #endif }; +// Perform a backtrace to the serial port void backtrace() { - UnwindFrame btf; - uint32_t sp = 0, lr = 0, pc = 0; + unsigned long sp = 0, lr = 0, pc = 0; // Capture the values of the registers to perform the traceback __asm__ __volatile__ ( @@ -79,6 +81,12 @@ void backtrace() { :: ); + backtrace_ex(sp, lr, pc); +} + +void backtrace_ex(unsigned long sp, unsigned long lr, unsigned long pc) { + UnwindFrame btf; + // Fill the traceback structure btf.sp = sp; btf.fp = btf.sp; @@ -86,7 +94,7 @@ void backtrace() { btf.pc = pc | 1; // Force Thumb, as CORTEX only support it // Perform a backtrace - SERIAL_ERROR_MSG("Backtrace:"); + MinSerial::TX("Backtrace:"); int ctr = 0; UnwindStart(&btf, &UnwCallbacks, &ctr); } @@ -95,4 +103,4 @@ void backtrace() { void backtrace() {} -#endif +#endif // __arm__ || __thumb__ diff --git a/Marlin/src/HAL/shared/backtrace/backtrace.h b/Marlin/src/HAL/shared/backtrace/backtrace.h index fccadedaa5..5d2ba601a0 100644 --- a/Marlin/src/HAL/shared/backtrace/backtrace.h +++ b/Marlin/src/HAL/shared/backtrace/backtrace.h @@ -23,3 +23,6 @@ // Perform a backtrace to the serial port void backtrace(); + +// Perform a backtrace to the serial port +void backtrace_ex(unsigned long sp, unsigned long lr, unsigned long pc); diff --git a/Marlin/src/HAL/shared/backtrace/unwarm.cpp b/Marlin/src/HAL/shared/backtrace/unwarm.cpp index cdc9c06c61..823f54c157 100644 --- a/Marlin/src/HAL/shared/backtrace/unwarm.cpp +++ b/Marlin/src/HAL/shared/backtrace/unwarm.cpp @@ -7,7 +7,7 @@ * for free and use it as they wish, with or without modifications, and in * any context, commercially or otherwise. The only limitation is that I * don't guarantee that the software is fit for any purpose or accept any - * liability for it's use or misuse - this software is without warranty. + * liability for its use or misuse - this software is without warranty. *************************************************************************** * File Description: Utility functions and glue for ARM unwinding sub-modules. **************************************************************************/ @@ -33,8 +33,9 @@ void UnwPrintf(const char *format, ...) { va_list args; - va_start( args, format ); - vprintf(format, args ); + va_start(args, format); + vprintf(format, args); + va_end(args); } #endif @@ -49,7 +50,6 @@ void UnwInvalidateRegisterFile(RegData *regFile) { } while (t < 13); } - /** * Initialize the data used for unwinding. */ @@ -128,7 +128,6 @@ bool UnwReportRetAddr(UnwState * const state, uint32_t addr) { return state->cb->report((void *)state->reportData, &entry); } - /** * Write some register to memory. * This will store some register and meta data onto the virtual stack. diff --git a/Marlin/src/HAL/shared/backtrace/unwarm.h b/Marlin/src/HAL/shared/backtrace/unwarm.h index 3128e354cc..8e432ebe77 100644 --- a/Marlin/src/HAL/shared/backtrace/unwarm.h +++ b/Marlin/src/HAL/shared/backtrace/unwarm.h @@ -4,9 +4,9 @@ * This program is PUBLIC DOMAIN. * This means that there is no copyright and anyone is able to take a copy * for free and use it as they wish, with or without modifications, and in - * any context, commerically or otherwise. The only limitation is that I + * any context, commercially or otherwise. The only limitation is that I * don't guarantee that the software is fit for any purpose or accept any - * liablity for it's use or misuse - this software is without warranty. + * liability for its use or misuse - this software is without warranty. *************************************************************************** * File Description: Internal interface between the ARM unwinding sub-modules. **************************************************************************/ @@ -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 @@ -41,7 +41,6 @@ typedef enum { REG_VAL_ARITHMETIC = 0x80 } RegValOrigin; - /** Type for tracking information about a register. * This stores the register value, as well as other data that helps unwinding. */ @@ -56,7 +55,6 @@ typedef struct { int o; /* (RegValOrigin) */ } RegData; - /** Structure used to track reads and writes to memory. * This structure is used as a hash to store a small number of writes * to memory. @@ -81,7 +79,6 @@ typedef struct { uint8_t tracked[(MEM_HASH_SIZE + 7) / 8]; } MemData; - /** Structure that is used to keep track of unwinding meta-data. * This data is passed between all the unwinding functions. */ diff --git a/Marlin/src/HAL/shared/backtrace/unwarm_arm.cpp b/Marlin/src/HAL/shared/backtrace/unwarm_arm.cpp index 0ec9bd56af..e6064f65da 100644 --- a/Marlin/src/HAL/shared/backtrace/unwarm_arm.cpp +++ b/Marlin/src/HAL/shared/backtrace/unwarm_arm.cpp @@ -7,7 +7,7 @@ * for free and use it as they wish, with or without modifications, and in * any context, commercially or otherwise. The only limitation is that I * don't guarantee that the software is fit for any purpose or accept any - * liability for it's use or misuse - this software is without warranty. + * liability for its use or misuse - this software is without warranty. *************************************************************************** * File Description: Abstract interpreter for ARM mode. **************************************************************************/ @@ -43,10 +43,9 @@ static bool isDataProc(uint32_t instr) { } UnwResult UnwStartArm(UnwState * const state) { - bool found = false; uint16_t t = UNW_MAX_INSTR_COUNT; - do { + for (;;) { uint32_t instr; /* Attempt to read the instruction */ @@ -415,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"); @@ -432,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 { @@ -527,7 +526,7 @@ UnwResult UnwStartArm(UnwState * const state) { if (--t == 0) return UNWIND_EXHAUSTED; - } while (!found); + } return UNWIND_UNSUPPORTED; } diff --git a/Marlin/src/HAL/shared/backtrace/unwarm_thumb.cpp b/Marlin/src/HAL/shared/backtrace/unwarm_thumb.cpp index 26ca8b2604..0c6a70649d 100644 --- a/Marlin/src/HAL/shared/backtrace/unwarm_thumb.cpp +++ b/Marlin/src/HAL/shared/backtrace/unwarm_thumb.cpp @@ -7,7 +7,7 @@ * for free and use it as they wish, with or without modifications, and in * any context, commercially or otherwise. The only limitation is that I * don't guarantee that the software is fit for any purpose or accept any - * liability for it's use or misuse - this software is without warranty. + * liability for its use or misuse - this software is without warranty. *************************************************************************** * File Description: Abstract interpretation for Thumb mode. **************************************************************************/ @@ -30,12 +30,11 @@ static int32_t signExtend11(const uint16_t value) { } UnwResult UnwStartThumb(UnwState * const state) { - bool found = false; uint16_t t = UNW_MAX_INSTR_COUNT; uint32_t lastJumpAddr = 0; // Last JUMP address, to try to detect infinite loops bool loopDetected = false; // If a loop was detected - do { + for (;;) { uint16_t instr; /* Attempt to read the instruction */ @@ -1059,7 +1058,7 @@ UnwResult UnwStartThumb(UnwState * const state) { if (--t == 0) return UNWIND_EXHAUSTED; - } while (!found); + } return UNWIND_SUCCESS; } diff --git a/Marlin/src/HAL/shared/backtrace/unwarmbytab.cpp b/Marlin/src/HAL/shared/backtrace/unwarmbytab.cpp index bfc062af20..bd87b6731c 100644 --- a/Marlin/src/HAL/shared/backtrace/unwarmbytab.cpp +++ b/Marlin/src/HAL/shared/backtrace/unwarmbytab.cpp @@ -55,7 +55,12 @@ static const char *UnwTabGetFunctionName(const UnwindCallbacks *cb, uint32_t add return nullptr; if ((flag_word & 0xFF000000) == 0xFF000000) { - return (const char *)(address - 4 - (flag_word & 0x00FFFFFF)); + const uint32_t fn_name_addr = address - 4 - (flag_word & 0x00FFFFFF); + + // Ensure the address is readable to avoid returning a bogus pointer + uint8_t dummy = 0; + if (cb->readB(fn_name_addr, &dummy)) + return (const char *)fn_name_addr; } return nullptr; } @@ -128,24 +133,21 @@ static UnwResult UnwTabStateInit(const UnwindCallbacks *cb, UnwTabState *ucb, ui * Execute unwinding instructions */ static UnwResult UnwTabExecuteInstructions(const UnwindCallbacks *cb, UnwTabState *ucb) { - int instruction; - uint32_t mask; - uint32_t reg; - uint32_t vsp; + uint32_t mask, reg, vsp; /* Consume all instruction byte */ while ((instruction = UnwTabGetNextInstruction(cb, ucb)) != -1) { if ((instruction & 0xC0) == 0x00) { // ARM_EXIDX_CMD_DATA_POP - /* vsp = vsp + (xxxxxx << 2) + 4 */ + /* vsp += (xxxxxx << 2) + 4 */ ucb->vrs[13] += ((instruction & 0x3F) << 2) + 4; - } else - if ((instruction & 0xC0) == 0x40) { // ARM_EXIDX_CMD_DATA_PUSH - /* vsp = vsp - (xxxxxx << 2) - 4 */ + } + else if ((instruction & 0xC0) == 0x40) { // ARM_EXIDX_CMD_DATA_PUSH + /* vsp -= (xxxxxx << 2) - 4 */ ucb->vrs[13] -= ((instruction & 0x3F) << 2) - 4; - } else - if ((instruction & 0xF0) == 0x80) { + } + else if ((instruction & 0xF0) == 0x80) { /* pop under mask {r15-r12},{r11-r4} or refuse to unwind */ instruction = instruction << 8 | UnwTabGetNextInstruction(cb, ucb); @@ -171,17 +173,17 @@ static UnwResult UnwTabExecuteInstructions(const UnwindCallbacks *cb, UnwTabStat } /* Patch up the vrs sp if it was in the mask */ - if ((instruction & (1 << (13 - 4))) != 0) + if (instruction & (1 << (13 - 4))) ucb->vrs[13] = vsp; - - } else - if ((instruction & 0xF0) == 0x90 && // ARM_EXIDX_CMD_REG_TO_SP - instruction != 0x9D && - instruction != 0x9F) { + } + else if ((instruction & 0xF0) == 0x90 // ARM_EXIDX_CMD_REG_TO_SP + && instruction != 0x9D + && instruction != 0x9F + ) { /* vsp = r[nnnn] */ ucb->vrs[13] = ucb->vrs[instruction & 0x0F]; - } else - if ((instruction & 0xF0) == 0xA0) { // ARM_EXIDX_CMD_REG_POP + } + else if ((instruction & 0xF0) == 0xA0) { // ARM_EXIDX_CMD_REG_POP /* pop r4-r[4+nnn] or pop r4-r[4+nnn], r14*/ vsp = ucb->vrs[13]; @@ -204,8 +206,8 @@ static UnwResult UnwTabExecuteInstructions(const UnwindCallbacks *cb, UnwTabStat ucb->vrs[13] = vsp; - } else - if (instruction == 0xB0) { // ARM_EXIDX_CMD_FINISH + } + else if (instruction == 0xB0) { // ARM_EXIDX_CMD_FINISH /* finished */ if (ucb->vrs[15] == 0) ucb->vrs[15] = ucb->vrs[14]; @@ -213,8 +215,8 @@ static UnwResult UnwTabExecuteInstructions(const UnwindCallbacks *cb, UnwTabStat /* All done unwinding */ return UNWIND_SUCCESS; - } else - if (instruction == 0xB1) { // ARM_EXIDX_CMD_REG_POP + } + else if (instruction == 0xB1) { // ARM_EXIDX_CMD_REG_POP /* pop register under mask {r3,r2,r1,r0} */ vsp = ucb->vrs[13]; mask = UnwTabGetNextInstruction(cb, ucb); @@ -234,16 +236,15 @@ static UnwResult UnwTabExecuteInstructions(const UnwindCallbacks *cb, UnwTabStat } ucb->vrs[13] = (uint32_t)vsp; - } else - if (instruction == 0xB2) { // ARM_EXIDX_CMD_DATA_POP + } + else if (instruction == 0xB2) { // ARM_EXIDX_CMD_DATA_POP /* vps = vsp + 0x204 + (uleb128 << 2) */ ucb->vrs[13] += 0x204 + (UnwTabGetNextInstruction(cb, ucb) << 2); - - } else - if (instruction == 0xB3 || // ARM_EXIDX_CMD_VFP_POP - instruction == 0xC8 || - instruction == 0xC9) { - + } + else if (instruction == 0xB3 // ARM_EXIDX_CMD_VFP_POP + || instruction == 0xC8 + || instruction == 0xC9 + ) { /* pop VFP double-precision registers */ vsp = ucb->vrs[13]; @@ -266,27 +267,20 @@ static UnwResult UnwTabExecuteInstructions(const UnwindCallbacks *cb, UnwTabStat } ucb->vrs[13] = vsp; - - } else - if ((instruction & 0xF8) == 0xB8 || - (instruction & 0xF8) == 0xD0) { - + } + else if ((instruction & 0xF8) == 0xB8 || (instruction & 0xF8) == 0xD0) { /* Pop VFP double precision registers D[8]-D[8+nnn] */ ucb->vrs[14] = 0x80 | (instruction & 0x07); - - if ((instruction & 0xF8) == 0xD0) { + if ((instruction & 0xF8) == 0xD0) ucb->vrs[14] = 1 << 17; - } - - } else + } + else return UNWIND_UNSUPPORTED_DWARF_INSTR; } - return UNWIND_SUCCESS; } static inline __attribute__((always_inline)) uint32_t read_psp() { - /* Read the current PSP and return its value as a pointer */ uint32_t psp; diff --git a/Marlin/src/HAL/shared/backtrace/unwarmbytab.h b/Marlin/src/HAL/shared/backtrace/unwarmbytab.h index 77a1c82dbd..53aeca2594 100644 --- a/Marlin/src/HAL/shared/backtrace/unwarmbytab.h +++ b/Marlin/src/HAL/shared/backtrace/unwarmbytab.h @@ -5,9 +5,9 @@ * This program is PUBLIC DOMAIN. * This means that there is no copyright and anyone is able to take a copy * for free and use it as they wish, with or without modifications, and in - * any context, commerically or otherwise. The only limitation is that I + * any context, commercially or otherwise. The only limitation is that I * don't guarantee that the software is fit for any purpose or accept any - * liablity for it's use or misuse - this software is without warranty. + * liability for its use or misuse - this software is without warranty. *************************************************************************** * File Description: Interface to the memory tracking sub-system. **************************************************************************/ diff --git a/Marlin/src/HAL/shared/backtrace/unwarmmem.cpp b/Marlin/src/HAL/shared/backtrace/unwarmmem.cpp index cf9ac414c4..24023200e1 100644 --- a/Marlin/src/HAL/shared/backtrace/unwarmmem.cpp +++ b/Marlin/src/HAL/shared/backtrace/unwarmmem.cpp @@ -5,9 +5,9 @@ * This program is PUBLIC DOMAIN. * This means that there is no copyright and anyone is able to take a copy * for free and use it as they wish, with or without modifications, and in - * any context, commerically or otherwise. The only limitation is that I + * any context, commercially or otherwise. The only limitation is that I * don't guarantee that the software is fit for any purpose or accept any - * liablity for it's use or misuse - this software is without warranty. + * liability for its use or misuse - this software is without warranty. *************************************************************************** * File Description: Implementation of the memory tracking sub-system. **************************************************************************/ diff --git a/Marlin/src/HAL/shared/backtrace/unwarmmem.h b/Marlin/src/HAL/shared/backtrace/unwarmmem.h index 588618b34f..eb4579a761 100644 --- a/Marlin/src/HAL/shared/backtrace/unwarmmem.h +++ b/Marlin/src/HAL/shared/backtrace/unwarmmem.h @@ -5,9 +5,9 @@ * This program is PUBLIC DOMAIN. * This means that there is no copyright and anyone is able to take a copy * for free and use it as they wish, with or without modifications, and in - * any context, commerically or otherwise. The only limitation is that I + * any context, commercially or otherwise. The only limitation is that I * don't guarantee that the software is fit for any purpose or accept any - * liablity for it's use or misuse - this software is without warranty. + * liability for its use or misuse - this software is without warranty. *************************************************************************** * File Description: Interface to the memory tracking sub-system. **************************************************************************/ diff --git a/Marlin/src/HAL/shared/backtrace/unwinder.cpp b/Marlin/src/HAL/shared/backtrace/unwinder.cpp index e63af1ed25..aedfa2404d 100644 --- a/Marlin/src/HAL/shared/backtrace/unwinder.cpp +++ b/Marlin/src/HAL/shared/backtrace/unwinder.cpp @@ -7,7 +7,7 @@ * for free and use it as they wish, with or without modifications, and in * any context, commercially or otherwise. The only limitation is that I * don't guarantee that the software is fit for any purpose or accept any - * liability for it's use or misuse - this software is without warranty. + * liability for its use or misuse - this software is without warranty. *************************************************************************** * File Description: Implementation of the interface into the ARM unwinder. **************************************************************************/ @@ -28,7 +28,7 @@ extern "C" const UnwTabEntry __exidx_end[]; // Detect if unwind information is present or not static int HasUnwindTableInfo() { - // > 16 because there are default entries we can't supress + // > 16 because there are default entries we can't suppress return ((char*)(&__exidx_end) - (char*)(&__exidx_start)) > 16 ? 1 : 0; } diff --git a/Marlin/src/HAL/shared/backtrace/unwinder.h b/Marlin/src/HAL/shared/backtrace/unwinder.h index cae1379513..9280e2f36e 100644 --- a/Marlin/src/HAL/shared/backtrace/unwinder.h +++ b/Marlin/src/HAL/shared/backtrace/unwinder.h @@ -5,9 +5,9 @@ * This program is PUBLIC DOMAIN. * This means that there is no copyright and anyone is able to take a copy * for free and use it as they wish, with or without modifications, and in - * any context, commerically or otherwise. The only limitation is that I + * any context, commercially or otherwise. The only limitation is that I * don't guarantee that the software is fit for any purpose or accept any - * liablity for it's use or misuse - this software is without warranty. + * liability for its use or misuse - this software is without warranty. **************************************************************************/ /** \file * Interface to the ARM stack unwinding module. @@ -114,7 +114,7 @@ typedef struct { * report function maybe called again in future. If false is returned, * unwinding will stop with UnwindStart() returning UNWIND_TRUNCATED. */ -typedef bool (*UnwindReportFunc)(void* data, const UnwReport* bte); +typedef bool (*UnwindReportFunc)(void *data, const UnwReport *bte); /** Structure that holds memory callback function pointers. */ diff --git a/Marlin/src/HAL/shared/backtrace/unwmemaccess.cpp b/Marlin/src/HAL/shared/backtrace/unwmemaccess.cpp index c93494d485..da1cff4fcc 100644 --- a/Marlin/src/HAL/shared/backtrace/unwmemaccess.cpp +++ b/Marlin/src/HAL/shared/backtrace/unwmemaccess.cpp @@ -7,7 +7,7 @@ * for free and use it as they wish, with or without modifications, and in * any context, commercially or otherwise. The only limitation is that I * don't guarantee that the software is fit for any purpose or accept any - * liability for it's use or misuse - this software is without warranty. + * liability for its use or misuse - this software is without warranty. *************************************************************************** * File Description: Utility functions to access memory **************************************************************************/ @@ -41,27 +41,16 @@ #define START_FLASH_ADDR 0x00000000 #define END_FLASH_ADDR 0x00080000 -#elif 0 - - // For STM32F103CBT6 - // SRAM (0x20000000 - 0x20005000) (20kb) - // FLASH (0x00000000 - 0x00020000) (128kb) - // - #define START_SRAM_ADDR 0x20000000 - #define END_SRAM_ADDR 0x20005000 - #define START_FLASH_ADDR 0x00000000 - #define END_FLASH_ADDR 0x00020000 - -#elif defined(__STM32F1__) || defined(STM32F1xx) || defined(STM32F0xx) +#elif defined(__STM32F1__) || defined(STM32F1xx) || defined(STM32F0xx) || defined(STM32G0xx) // For STM32F103ZET6/STM32F103VET6/STM32F0xx // SRAM (0x20000000 - 0x20010000) (64kb) - // FLASH (0x00000000 - 0x00080000) (512kb) + // FLASH (0x08000000 - 0x08080000) (512kb) // #define START_SRAM_ADDR 0x20000000 #define END_SRAM_ADDR 0x20010000 - #define START_FLASH_ADDR 0x00000000 - #define END_FLASH_ADDR 0x00080000 + #define START_FLASH_ADDR 0x08000000 + #define END_FLASH_ADDR 0x08080000 #elif defined(STM32F4) || defined(STM32F4xx) @@ -74,17 +63,6 @@ #define START_FLASH_ADDR 0x08000000 #define END_FLASH_ADDR 0x08080000 -#elif MB(THE_BORG) - - // For STM32F765 in BORG - // SRAM (0x20000000 - 0x20080000) (512kb) - // FLASH (0x08000000 - 0x08100000) (1024kb) - // - #define START_SRAM_ADDR 0x20000000 - #define END_SRAM_ADDR 0x20080000 - #define START_FLASH_ADDR 0x08000000 - #define END_FLASH_ADDR 0x08100000 - #elif MB(REMRAM_V1, NUCLEO_F767ZI) // For STM32F765VI in RemRam v1 @@ -153,20 +131,57 @@ #define START_FLASH_ADDR 0x00000000 #define END_FLASH_ADDR 0x00100000 +#else + // Generic ARM code, that's testing if an access to the given address would cause a fault or not + // It can't guarantee an address is in RAM or Flash only, but we usually don't care + + #define NVIC_FAULT_STAT 0xE000ED28 // Configurable Fault Status reg. + #define NVIC_CFG_CTRL 0xE000ED14 // Configuration Control Register + #define NVIC_FAULT_STAT_BFARV 0x00008000 // BFAR is valid + #define NVIC_CFG_CTRL_BFHFNMIGN 0x00000100 // Ignore bus fault in NMI/fault + #define HW_REG(X) (*((volatile unsigned long *)(X))) + + static bool validate_addr(uint32_t read_address) { + bool works = true; + uint32_t intDisabled = 0; + // Read current interrupt state + __asm__ __volatile__ ("MRS %[result], PRIMASK\n\t" : [result]"=r"(intDisabled) :: ); // 0 is int enabled, 1 for disabled + + // Clear bus fault indicator first (write 1 to clear) + HW_REG(NVIC_FAULT_STAT) |= NVIC_FAULT_STAT_BFARV; + // Ignore bus fault interrupt + HW_REG(NVIC_CFG_CTRL) |= NVIC_CFG_CTRL_BFHFNMIGN; + // Disable interrupts if not disabled previously + if (!intDisabled) __asm__ __volatile__ ("CPSID f"); + // Probe address + *(volatile uint32_t*)read_address; + // Check if a fault happened + if ((HW_REG(NVIC_FAULT_STAT) & NVIC_FAULT_STAT_BFARV) != 0) + works = false; + // Enable interrupts again if previously disabled + if (!intDisabled) __asm__ __volatile__ ("CPSIE f"); + // Enable fault interrupt flag + HW_REG(NVIC_CFG_CTRL) &= ~NVIC_CFG_CTRL_BFHFNMIGN; + + return works; + } + #endif -static bool validate_addr(uint32_t addr) { +#ifdef START_SRAM_ADDR + static bool validate_addr(uint32_t addr) { - // Address must be in SRAM range - if (addr >= START_SRAM_ADDR && addr < END_SRAM_ADDR) - return true; + // Address must be in SRAM range + if (addr >= START_SRAM_ADDR && addr < END_SRAM_ADDR) + return true; - // Or in FLASH range - if (addr >= START_FLASH_ADDR && addr < END_FLASH_ADDR) - return true; + // Or in FLASH range + if (addr >= START_FLASH_ADDR && addr < END_FLASH_ADDR) + return true; - return false; -} + return false; + } +#endif bool UnwReadW(const uint32_t a, uint32_t *v) { if (!validate_addr(a)) diff --git a/Marlin/src/HAL/shared/backtrace/unwmemaccess.h b/Marlin/src/HAL/shared/backtrace/unwmemaccess.h index fe42bd9485..b911e343dc 100644 --- a/Marlin/src/HAL/shared/backtrace/unwmemaccess.h +++ b/Marlin/src/HAL/shared/backtrace/unwmemaccess.h @@ -5,9 +5,9 @@ * This program is PUBLIC DOMAIN. * This means that there is no copyright and anyone is able to take a copy * for free and use it as they wish, with or without modifications, and in - * any context, commerically or otherwise. The only limitation is that I + * any context, commercially or otherwise. The only limitation is that I * don't guarantee that the software is fit for any purpose or accept any - * liablity for it's use or misuse - this software is without warranty. + * liability for its use or misuse - this software is without warranty. *************************************************************************** * File Description: Utility functions to access memory **************************************************************************/ diff --git a/Marlin/src/HAL/shared/cpu_exception/exception_arm.cpp b/Marlin/src/HAL/shared/cpu_exception/exception_arm.cpp new file mode 100644 index 0000000000..43a086589b --- /dev/null +++ b/Marlin/src/HAL/shared/cpu_exception/exception_arm.cpp @@ -0,0 +1,375 @@ +/** + * 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 . + * + */ + +/*************************************************************************** + * ARM CPU Exception handler + ***************************************************************************/ + +#if defined(__arm__) || defined(__thumb__) + +/* + On ARM CPUs exception handling is quite powerful. + + By default, upon a crash, the CPU enters the handlers that have a higher priority than any other interrupts, + so, in effect, no (real) interrupt can "interrupt" the handler (it's acting like if interrupts were disabled). + + If the handler is not called as re-entrant (that is, if the crash is not happening inside an interrupt or an handler), + then it'll patch the return address to a dumping function (resume_from_fault) and save the crash state. + The CPU will exit the handler and, as such, re-allow the other interrupts, and jump to the dumping function. + In this function, the usual serial port (USB / HW) will be used to dump the crash (no special configuration required). + + The only case where it requires hardware UART is when it's crashing in an interrupt or a crash handler. + In that case, instead of returning to the resume_from_fault function (and thus, re-enabling interrupts), + it jumps to this function directly (so with interrupts disabled), after changing the behavior of the serial output + wrapper to use the HW uart (and in effect, calling MinSerial::init which triggers a warning if you are using + a USB serial port). + + In the case you have a USB serial port, this part will be disabled, and only that part (so that's the reason for + the warning). + This means that you can't have a crash report if the crash happens in an interrupt or an handler if you are using + a USB serial port since it's physically impossible. + You will get a crash report in all other cases. +*/ + +#include "exception_hook.h" +#include "../backtrace/backtrace.h" +#include "../MinSerial.h" + +#define HW_REG(X) (*((volatile unsigned long *)(X))) + +// Default function use the CPU VTOR register to get the vector table. +// Accessing the CPU VTOR register is done in assembly since it's the only way that's common to all current tool +unsigned long get_vtor() { return HW_REG(0xE000ED08); } // Even if it looks like an error, it is not an error +void * hook_get_hardfault_vector_address(unsigned vtor) { return (void*)(vtor + 0x03); } +void * hook_get_memfault_vector_address(unsigned vtor) { return (void*)(vtor + 0x04); } +void * hook_get_busfault_vector_address(unsigned vtor) { return (void*)(vtor + 0x05); } +void * hook_get_usagefault_vector_address(unsigned vtor) { return (void*)(vtor + 0x06); } +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-hardfault-debug +struct __attribute__((packed)) ContextStateFrame { + uint32_t r0; + uint32_t r1; + uint32_t r2; + uint32_t r3; + uint32_t r12; + uint32_t lr; + uint32_t pc; + uint32_t xpsr; +}; + +struct __attribute__((packed)) ContextSavedFrame { + uint32_t R0; + uint32_t R1; + uint32_t R2; + uint32_t R3; + uint32_t R12; + uint32_t LR; + uint32_t PC; + uint32_t XPSR; + + uint32_t CFSR; + uint32_t HFSR; + uint32_t DFSR; + uint32_t AFSR; + uint32_t MMAR; + uint32_t BFAR; + + uint32_t ESP; + uint32_t ELR; +}; + +#if NONE(STM32F0xx, STM32G0xx) + extern "C" + __attribute__((naked)) void CommonHandler_ASM() { + __asm__ __volatile__ ( + // Bit 2 of LR tells which stack pointer to use (either main or process, only main should be used anyway) + "tst lr, #4\n" + "ite eq\n" + "mrseq r0, msp\n" + "mrsne r0, psp\n" + // Save the LR in use when being interrupted + "mov r1, lr\n" + // Get the exception number from the ICSR register + "ldr r2, =0xE000ED00\n" + "ldr r2, [r2, #4]\n" + "b CommonHandler_C\n" + ); + } +#else // Cortex M0 does not support conditional mov and testing with a constant, so let's have a specific handler for it + extern "C" + __attribute__((naked)) void CommonHandler_ASM() { + __asm__ __volatile__ ( + ".syntax unified\n" + // Save the LR in use when being interrupted + "mov r1, lr\n" + // Get the exception number from the ICSR register + "ldr r2, =0xE000ED00\n" + "ldr r2, [r2, #4]\n" + "movs r0, #4\n" + "tst r1, r0\n" + "beq _MSP\n" + "mrs r0, psp\n" + "b CommonHandler_C\n" + "_MSP:\n" + "mrs r0, msp\n" + "b CommonHandler_C\n" + ); + } + + #if DISABLED(DYNAMIC_VECTORTABLE) // Cortex M0 requires the handler's address to be within 32kB to the actual function to be able to branch to it + extern "C" { + void __attribute__((naked, alias("CommonHandler_ASM"), nothrow)) __exc_hardfault(); + void __attribute__((naked, alias("CommonHandler_ASM"), nothrow)) __exc_busfault(); + void __attribute__((naked, alias("CommonHandler_ASM"), nothrow)) __exc_usagefault(); + void __attribute__((naked, alias("CommonHandler_ASM"), nothrow)) __exc_memmanage(); + void __attribute__((naked, alias("CommonHandler_ASM"), nothrow)) __exc_nmi(); + void __attribute__((naked, alias("CommonHandler_ASM"), nothrow)) __stm32reservedexception7(); + void __attribute__((naked, alias("CommonHandler_ASM"), nothrow)) __stm32reservedexception8(); + void __attribute__((naked, alias("CommonHandler_ASM"), nothrow)) __stm32reservedexception9(); + void __attribute__((naked, alias("CommonHandler_ASM"), nothrow)) __stm32reservedexception10(); + void __attribute__((naked, alias("CommonHandler_ASM"), nothrow)) __stm32reservedexception13(); + } + //TODO When going off from libmaple, you'll need to replace those by the one from STM32/HAL_MinSerial.cpp + #endif +#endif + +// Must be a macro to avoid creating a function frame +#define HALT_IF_DEBUGGING() \ + do { \ + if (HW_REG(0xE000EDF0) & _BV(0)) { \ + __asm("bkpt 1"); \ + } \ +} while (0) + +// Resume from a fault (if possible) so we can still use interrupt based code for serial output +// In that case, we will not jump back to the faulty code, but instead to a dumping code and then a +// basic loop with watchdog calling or manual resetting +static ContextSavedFrame savedFrame; +static uint8_t lastCause; +bool resume_from_fault() { + static const char* causestr[] = { "Thread", "Rsvd", "NMI", "Hard", "Mem", "Bus", "Usage", "7", "8", "9", "10", "SVC", "Dbg", "13", "PendSV", "SysTk", "IRQ" }; + // Reinit the serial link (might only work if implemented in each of your boards) + MinSerial::init(); + + MinSerial::TX("\n\n## Software Fault detected ##\n"); + MinSerial::TX("Cause: "); MinSerial::TX(causestr[min(lastCause, (uint8_t)16)]); MinSerial::TX('\n'); + + MinSerial::TX("R0 : "); MinSerial::TXHex(savedFrame.R0); MinSerial::TX('\n'); + MinSerial::TX("R1 : "); MinSerial::TXHex(savedFrame.R1); MinSerial::TX('\n'); + MinSerial::TX("R2 : "); MinSerial::TXHex(savedFrame.R2); MinSerial::TX('\n'); + MinSerial::TX("R3 : "); MinSerial::TXHex(savedFrame.R3); MinSerial::TX('\n'); + MinSerial::TX("R12 : "); MinSerial::TXHex(savedFrame.R12); MinSerial::TX('\n'); + MinSerial::TX("LR : "); MinSerial::TXHex(savedFrame.LR); MinSerial::TX('\n'); + MinSerial::TX("PC : "); MinSerial::TXHex(savedFrame.PC); MinSerial::TX('\n'); + MinSerial::TX("PSR : "); MinSerial::TXHex(savedFrame.XPSR); MinSerial::TX('\n'); + + // Configurable Fault Status Register + // Consists of MMSR, BFSR and UFSR + MinSerial::TX("CFSR : "); MinSerial::TXHex(savedFrame.CFSR); MinSerial::TX('\n'); + + // Hard Fault Status Register + MinSerial::TX("HFSR : "); MinSerial::TXHex(savedFrame.HFSR); MinSerial::TX('\n'); + + // Debug Fault Status Register + MinSerial::TX("DFSR : "); MinSerial::TXHex(savedFrame.DFSR); MinSerial::TX('\n'); + + // Auxiliary Fault Status Register + MinSerial::TX("AFSR : "); MinSerial::TXHex(savedFrame.AFSR); MinSerial::TX('\n'); + + // Read the Fault Address Registers. These may not contain valid values. + // Check BFARVALID/MMARVALID to see if they are valid values + // MemManage Fault Address Register + MinSerial::TX("MMAR : "); MinSerial::TXHex(savedFrame.MMAR); MinSerial::TX('\n'); + + // Bus Fault Address Register + MinSerial::TX("BFAR : "); MinSerial::TXHex(savedFrame.BFAR); MinSerial::TX('\n'); + + MinSerial::TX("ExcLR: "); MinSerial::TXHex(savedFrame.ELR); MinSerial::TX('\n'); + MinSerial::TX("ExcSP: "); MinSerial::TXHex(savedFrame.ESP); MinSerial::TX('\n'); + + // The stack pointer is pushed by 8 words upon entering an exception, so we need to revert this + backtrace_ex(savedFrame.ESP + 8*4, savedFrame.LR, savedFrame.PC); + + // Call the last resort function here + hook_last_resort_func(); + + const uint32_t start = millis(), end = start + 100; // 100ms should be enough + // We need to wait for the serial buffers to be output but we don't know for how long + // So we'll just need to refresh the watchdog for a while and then stop for the system to reboot + uint32_t last = start; + while (PENDING(last, end)) { + hal.watchdog_refresh(); + while (millis() == last) { /* nada */ } + last = millis(); + MinSerial::TX('.'); + } + + // Reset now by reinstantiating the bootloader's vector table + HW_REG(0xE000ED08) = 0; + // Restart watchdog + #if DISABLED(USE_WATCHDOG) + // No watchdog, let's perform ARMv7 reset instead by writing to AIRCR register with VECTKEY set to SYSRESETREQ + HW_REG(0xE000ED0C) = (HW_REG(0xE000ED0C) & 0x0000FFFF) | 0x05FA0004; + #endif + + while(1) {} // Bad luck, nothing worked +} + +// Make sure the compiler does not optimize the frame argument away +extern "C" +__attribute__((optimize("O0"))) +void CommonHandler_C(ContextStateFrame * frame, unsigned long lr, unsigned long cause) { + + // If you are using it'll stop here + HALT_IF_DEBUGGING(); + + // Save the state to backtrace later on (don't call memcpy here since the stack can be corrupted) + savedFrame.R0 = frame->r0; + savedFrame.R1 = frame->r1; + savedFrame.R2 = frame->r2; + savedFrame.R3 = frame->r3; + savedFrame.R12 = frame->r12; + savedFrame.LR = frame->lr; + savedFrame.PC = frame->pc; + savedFrame.XPSR= frame->xpsr; + lastCause = cause & 0x1FF; + + volatile uint32_t &CFSR = HW_REG(0xE000ED28); + savedFrame.CFSR = CFSR; + savedFrame.HFSR = HW_REG(0xE000ED2C); + savedFrame.DFSR = HW_REG(0xE000ED30); + savedFrame.AFSR = HW_REG(0xE000ED3C); + savedFrame.MMAR = HW_REG(0xE000ED34); + savedFrame.BFAR = HW_REG(0xE000ED38); + savedFrame.ESP = (unsigned long)frame; // Even on return, this should not be overwritten by the CPU + savedFrame.ELR = lr; + + // First check if we can resume from this exception to our own handler safely + // If we can, then we don't need to disable interrupts and the usual serial code + // can be used + + //const uint32_t non_usage_fault_mask = 0x0000FFFF; + //const bool non_usage_fault_occurred = (CFSR & non_usage_fault_mask) != 0; + // the bottom 8 bits of the xpsr hold the exception number of the + // executing exception or 0 if the processor is in Thread mode + const bool faulted_from_exception = ((frame->xpsr & 0xFF) != 0); + if (!faulted_from_exception) { // Not sure about the non_usage_fault, we want to try anyway, don't we ? && !non_usage_fault_occurred) + // Try to resume to our handler here + CFSR |= CFSR; // The ARM programmer manual says you must write to 1 all fault bits to clear them so this instruction is correct + + frame->pc = (uint32_t)resume_from_fault; // Patch where to return to + frame->lr = 0xDEADBEEF; // If our handler returns (it shouldn't), let's make it trigger an exception immediately + frame->xpsr = _BV(24); // Need to clean the PSR register to thumb II only + MinSerial::force_using_default_output = true; + return; // The CPU will resume in our handler hopefully, and we'll try to use default serial output + } + + // Sorry, we need to emergency code here since the fault is too dangerous to recover from + MinSerial::force_using_default_output = false; + resume_from_fault(); +} + +void hook_cpu_exceptions() { + #if ENABLED(DYNAMIC_VECTORTABLE) + // On ARM 32bits CPU, the vector table is like this: + // 0x0C => Hardfault + // 0x10 => MemFault + // 0x14 => BusFault + // 0x18 => UsageFault + + // Unfortunately, it's usually run from flash, and we can't write to flash here directly to hook our instruction + // We could set an hardware breakpoint, and hook on the fly when it's being called, but this + // is hard to get right and would probably break debugger when attached + + // So instead, we'll allocate a new vector table filled with the previous value except + // for the fault we are interested in. + // Now, comes the issue to figure out what is the current vector table size + // There is nothing telling us what is the vector table as it's per-cpu vendor specific. + // BUT: we are being called at the end of the setup, so we assume the setup is done + // Thus, we can read the current vector table until we find an address that's not in flash, and it would mark the + // end of the vector table (skipping the fist entry obviously) + // The position of the program in flash is expected to be at 0x08xxx xxxx on all known platform for ARM and the + // flash size is available via register 0x1FFFF7E0 on STM32 family, but it's not the case for all ARM boards + // (accessing this register might trigger a fault if it's not implemented). + + // So we'll simply mask the top 8 bits of the first handler as an hint of being in the flash or not -that's poor and will + // probably break if the flash happens to be more than 128MB, but in this case, we are not magician, we need help from outside. + + unsigned long *vecAddr = (unsigned long*)get_vtor(); + SERIAL_ECHOPGM("Vector table addr: "); + SERIAL_PRINTLN(get_vtor(), PrintBase::Hex); + + #ifdef VECTOR_TABLE_SIZE + uint32_t vec_size = VECTOR_TABLE_SIZE; + alignas(128) static unsigned long vectable[VECTOR_TABLE_SIZE] ; + #else + #ifndef IS_IN_FLASH + #define IS_IN_FLASH(X) (((unsigned long)X & 0xFF000000) == 0x08000000) + #endif + + // When searching for the end of the vector table, this acts as a limit not to overcome + #ifndef VECTOR_TABLE_SENTINEL + #define VECTOR_TABLE_SENTINEL 80 + #endif + + // Find the vector table size + uint32_t vec_size = 1; + while (IS_IN_FLASH(vecAddr[vec_size]) && vec_size < VECTOR_TABLE_SENTINEL) + vec_size++; + + // We failed to find a valid vector table size, let's abort hooking up + if (vec_size == VECTOR_TABLE_SENTINEL) return; + // Poor method that's wasting RAM here, but allocating with malloc and alignment would be worst + // 128 bytes alignment is required for writing the VTOR register + alignas(128) static unsigned long vectable[VECTOR_TABLE_SENTINEL]; + + SERIAL_ECHOPGM("Detected vector table size: "); + SERIAL_PRINTLN(vec_size, PrintBase::Hex); + #endif + + uint32_t defaultFaultHandler = vecAddr[(unsigned)7]; + // Copy the current vector table into the new table + for (uint32_t i = 0; i < vec_size; i++) { + vectable[i] = vecAddr[i]; + // Replace all default handler by our own handler + if (vectable[i] == defaultFaultHandler) + vectable[i] = (unsigned long)&CommonHandler_ASM; + } + + // Let's hook now with our functions + vectable[(unsigned long)hook_get_hardfault_vector_address(0)] = (unsigned long)&CommonHandler_ASM; + vectable[(unsigned long)hook_get_memfault_vector_address(0)] = (unsigned long)&CommonHandler_ASM; + vectable[(unsigned long)hook_get_busfault_vector_address(0)] = (unsigned long)&CommonHandler_ASM; + vectable[(unsigned long)hook_get_usagefault_vector_address(0)] = (unsigned long)&CommonHandler_ASM; + + // Finally swap with our own vector table + // This is supposed to be atomic, but let's do that with interrupt disabled + + HW_REG(0xE000ED08) = (unsigned long)vectable | _BV32(29); // 29th bit is for telling the CPU the table is now in SRAM (should be present already) + + SERIAL_ECHOLNPGM("Installed fault handlers"); + #endif +} + +#endif // __arm__ || __thumb__ diff --git a/Marlin/src/HAL/shared/cpu_exception/exception_hook.cpp b/Marlin/src/HAL/shared/cpu_exception/exception_hook.cpp new file mode 100644 index 0000000000..93e80f3e85 --- /dev/null +++ b/Marlin/src/HAL/shared/cpu_exception/exception_hook.cpp @@ -0,0 +1,28 @@ +/** + * 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 . + * + */ +#include "exception_hook.h" + +void * __attribute__((weak)) hook_get_hardfault_vector_address(unsigned) { return 0; } +void * __attribute__((weak)) hook_get_memfault_vector_address(unsigned) { return 0; } +void * __attribute__((weak)) hook_get_busfault_vector_address(unsigned) { return 0; } +void * __attribute__((weak)) hook_get_usagefault_vector_address(unsigned) { return 0; } +void __attribute__((weak)) hook_last_resort_func() {} diff --git a/Marlin/src/HAL/shared/cpu_exception/exception_hook.h b/Marlin/src/HAL/shared/cpu_exception/exception_hook.h new file mode 100644 index 0000000000..70d9434704 --- /dev/null +++ b/Marlin/src/HAL/shared/cpu_exception/exception_hook.h @@ -0,0 +1,54 @@ +/** + * 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 + +/* Here is the expected behavior of a system producing a CPU exception with this hook installed: + 1. Before the system is crashed + 1.1 Upon validation (not done yet in this code, but we could be using DEBUG flags here to allow/disallow hooking) + 1.2 Install the hook by overwriting the vector table exception handler with the hooked function + 2. Upon system crash (for example, by a dereference of a NULL pointer or anything else) + 2.1 The CPU triggers its exception and jump into the vector table for the exception type + 2.2 Instead of finding the default handler, it finds the updated pointer to our hook + 2.3 The CPU jumps into our hook function (likely a naked function to keep all information about crash point intact) + 2.4 The hook (naked) function saves the important registers (stack pointer, program counter, current mode) and jumps to a common exception handler (in C) + 2.5 The common exception handler dumps the registers on the serial link and perform a backtrace around the crashing point + 2.6 Once the backtrace is performed the last resort function is called (platform specific). + On some platform with a LCD screen, this might display the crash information as a QR code or as text for the + user to capture by taking a picture + 2.7 The CPU is reset and/or halted by triggering a debug breakpoint if a debugger is attached */ + +// Hook into CPU exception interrupt table to call the backtracing code upon an exception +// Most platform will simply do nothing here, but those who can will install/overwrite the default exception handler +// with a more performant exception handler +void hook_cpu_exceptions(); + +// Some platform might deal without a hard fault handler, in that case, return 0 in your platform here or skip implementing it +void * __attribute__((weak)) hook_get_hardfault_vector_address(unsigned base_address); +// Some platform might deal without a memory management fault handler, in that case, return 0 in your platform here or skip implementing it +void * __attribute__((weak)) hook_get_memfault_vector_address(unsigned base_address); +// Some platform might deal without a bus fault handler, in that case, return 0 in your platform here or skip implementing it +void * __attribute__((weak)) hook_get_busfault_vector_address(unsigned base_address); +// Some platform might deal without a usage fault handler, in that case, return 0 in your platform here or skip implementing it +void * __attribute__((weak)) hook_get_usagefault_vector_address(unsigned base_address); + +// Last resort function that can be called after the exception handler was called. +void __attribute__((weak)) hook_last_resort_func(); diff --git a/Marlin/src/HAL/shared/eeprom_api.cpp b/Marlin/src/HAL/shared/eeprom_api.cpp index 47cfa5a2db..62a8f2afbc 100644 --- a/Marlin/src/HAL/shared/eeprom_api.cpp +++ b/Marlin/src/HAL/shared/eeprom_api.cpp @@ -1,10 +1,9 @@ /** * Marlin 3D Printer Firmware - * * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com - * Copyright (c) 2015-2016 Nico Tonnhofer wurstnase.reprap@gmail.com - * Copyright (c) 2016 Victor Perez victor_pv@hotmail.com + * + * 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 @@ -22,7 +21,7 @@ */ #include "../../inc/MarlinConfigPre.h" -#if EITHER(EEPROM_SETTINGS, SD_FIRMWARE_UPDATE) +#if ANY(EEPROM_SETTINGS, SD_FIRMWARE_UPDATE) #include "eeprom_api.h" PersistentStore persistentStore; diff --git a/Marlin/src/HAL/shared/eeprom_api.h b/Marlin/src/HAL/shared/eeprom_api.h index 6445f7a4aa..33d80695db 100644 --- a/Marlin/src/HAL/shared/eeprom_api.h +++ b/Marlin/src/HAL/shared/eeprom_api.h @@ -1,10 +1,9 @@ /** * Marlin 3D Printer Firmware - * * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com - * Copyright (c) 2015-2016 Nico Tonnhofer wurstnase.reprap@gmail.com - * Copyright (c) 2016 Victor Perez victor_pv@hotmail.com + * + * 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 @@ -27,6 +26,19 @@ #include "../../libs/crc16.h" +// For testing. Define with -DEEPROM_EXCL_ZONE=916,926 in INI files. +//#define EEPROM_EXCL_ZONE 916,926 // Test a range +//#define EEPROM_EXCL_ZONE 333 // Test a single byte + +#ifdef EEPROM_EXCL_ZONE + static constexpr int eeprom_exclude_zone[] = { EEPROM_EXCL_ZONE }, + eeprom_exclude_size = eeprom_exclude_zone[COUNT(eeprom_exclude_zone) - 1] - eeprom_exclude_zone[0] + 1; + #define REAL_EEPROM_ADDR(A) (A < eeprom_exclude_zone[0] ? (A) : (A) + eeprom_exclude_size) +#else + #define REAL_EEPROM_ADDR(A) (A) + static constexpr int eeprom_exclude_size = 0; +#endif + class PersistentStore { public: @@ -45,11 +57,11 @@ public: // Read one or more bytes of data and update the CRC // Return 'true' on read error - static bool read_data(int &pos, uint8_t* value, size_t size, uint16_t *crc, const bool writing=true); + static bool read_data(int &pos, uint8_t *value, size_t size, uint16_t *crc, const bool writing=true); // Write one or more bytes of data // Return 'true' on write error - static inline bool write_data(const int pos, const uint8_t* value, const size_t size=sizeof(uint8_t)) { + static bool write_data(const int pos, const uint8_t *value, const size_t size=sizeof(uint8_t)) { int data_pos = pos; uint16_t crc = 0; return write_data(data_pos, value, size, &crc); @@ -57,11 +69,11 @@ public: // Write a single byte of data // Return 'true' on write error - static inline bool write_data(const int pos, const uint8_t value) { return write_data(pos, &value); } + static bool write_data(const int pos, const uint8_t value) { return write_data(pos, &value); } // Read one or more bytes of data // Return 'true' on read error - static inline bool read_data(const int pos, uint8_t* value, const size_t size=1) { + static bool read_data(const int pos, uint8_t *value, const size_t size=1) { int data_pos = pos; uint16_t crc = 0; return read_data(data_pos, value, size, &crc); diff --git a/Marlin/src/HAL/shared/eeprom_if.h b/Marlin/src/HAL/shared/eeprom_if.h index e44da801df..8b9791e1f8 100644 --- a/Marlin/src/HAL/shared/eeprom_if.h +++ b/Marlin/src/HAL/shared/eeprom_if.h @@ -1,9 +1,9 @@ /** * Marlin 3D Printer Firmware - * * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com - * Copyright (c) 2015-2016 Nico Tonnhofer wurstnase.reprap@gmail.com + * + * 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 @@ -25,5 +25,5 @@ // EEPROM // void eeprom_init(); -void eeprom_write_byte(uint8_t *pos, unsigned char value); +void eeprom_write_byte(uint8_t *pos, uint8_t value); uint8_t eeprom_read_byte(uint8_t *pos); diff --git a/Marlin/src/HAL/shared/eeprom_if_i2c.cpp b/Marlin/src/HAL/shared/eeprom_if_i2c.cpp index cc22bf7d98..bba9c626a4 100644 --- a/Marlin/src/HAL/shared/eeprom_if_i2c.cpp +++ b/Marlin/src/HAL/shared/eeprom_if_i2c.cpp @@ -30,9 +30,22 @@ #if ENABLED(I2C_EEPROM) #include "eeprom_if.h" -#include -void eeprom_init() { Wire.begin(); } +#if ENABLED(SOFT_I2C_EEPROM) + #include + SlowSoftWire eWire = SlowSoftWire(I2C_SDA_PIN, I2C_SCL_PIN, true); +#else + #include + #define eWire Wire +#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 ENABLED(USE_SHARED_EEPROM) @@ -49,14 +62,30 @@ static constexpr uint8_t eeprom_device_address = I2C_ADDRESS(EEPROM_DEVICE_ADDRE // Public functions // ------------------------ -void eeprom_write_byte(uint8_t *pos, unsigned char value) { - const unsigned eeprom_address = (unsigned)pos; +#define SMALL_EEPROM (MARLIN_EEPROM_SIZE <= 2048) - Wire.beginTransmission(eeprom_device_address); - Wire.write(int(eeprom_address >> 8)); // MSB - Wire.write(int(eeprom_address & 0xFF)); // LSB - Wire.write(value); - Wire.endTransmission(); +// Combine Address high bits into the device address on <=16Kbit (2K) and >512Kbit (64K) EEPROMs. +// Note: MARLIN_EEPROM_SIZE is specified in bytes, whereas EEPROM model numbers refer to bits. +// e.g., The "16" in BL24C16 indicates a 16Kbit (2KB) size. +static uint8_t _eeprom_calc_device_address(uint8_t * const pos) { + const unsigned eeprom_address = (unsigned)pos; + return (SMALL_EEPROM || MARLIN_EEPROM_SIZE > 65536) + ? uint8_t(eeprom_device_address | ((eeprom_address >> (SMALL_EEPROM ? 8 : 16)) & 0x07)) + : eeprom_device_address; +} + +static void _eeprom_begin(uint8_t * const pos) { + const unsigned eeprom_address = (unsigned)pos; + eWire.beginTransmission(_eeprom_calc_device_address(pos)); + if (!SMALL_EEPROM) + eWire.write(uint8_t((eeprom_address >> 8) & 0xFF)); // Address High, if needed + eWire.write(uint8_t(eeprom_address & 0xFF)); // Address Low +} + +void eeprom_write_byte(uint8_t *pos, uint8_t value) { + _eeprom_begin(pos); + eWire.write(value); + eWire.endTransmission(); // wait for write cycle to complete // this could be done more efficiently with "acknowledge polling" @@ -64,14 +93,10 @@ void eeprom_write_byte(uint8_t *pos, unsigned char value) { } uint8_t eeprom_read_byte(uint8_t *pos) { - const unsigned eeprom_address = (unsigned)pos; - - Wire.beginTransmission(eeprom_device_address); - Wire.write(int(eeprom_address >> 8)); // MSB - Wire.write(int(eeprom_address & 0xFF)); // LSB - Wire.endTransmission(); - Wire.requestFrom(eeprom_device_address, (byte)1); - return Wire.available() ? Wire.read() : 0xFF; + _eeprom_begin(pos); + eWire.endTransmission(); + eWire.requestFrom(_eeprom_calc_device_address(pos), (byte)1); + return eWire.available() ? eWire.read() : 0xFF; } #endif // USE_SHARED_EEPROM diff --git a/Marlin/src/HAL/shared/eeprom_if_spi.cpp b/Marlin/src/HAL/shared/eeprom_if_spi.cpp index a341fef9de..6aa23e823a 100644 --- a/Marlin/src/HAL/shared/eeprom_if_spi.cpp +++ b/Marlin/src/HAL/shared/eeprom_if_spi.cpp @@ -43,45 +43,42 @@ void eeprom_init() {} #define EEPROM_WRITE_DELAY 7 #endif -uint8_t eeprom_read_byte(uint8_t* pos) { - uint8_t v; - uint8_t eeprom_temp[3]; - - // set read location - // begin transmission from device - eeprom_temp[0] = CMD_READ; - eeprom_temp[1] = ((unsigned)pos>>8) & 0xFF; // addr High - eeprom_temp[2] = (unsigned)pos& 0xFF; // addr Low - WRITE(SPI_EEPROM1_CS, HIGH); - WRITE(SPI_EEPROM1_CS, LOW); +static void _eeprom_begin(uint8_t * const pos, const uint8_t cmd) { + const uint8_t eeprom_temp[3] = { + cmd, + (unsigned(pos) >> 8) & 0xFF, // Address High + unsigned(pos) & 0xFF // Address Low + }; + WRITE(SPI_EEPROM1_CS_PIN, HIGH); // Usually free already + WRITE(SPI_EEPROM1_CS_PIN, LOW); // Activate the Bus spiSend(SPI_CHAN_EEPROM1, eeprom_temp, 3); + // Leave the Bus in-use +} + +uint8_t eeprom_read_byte(uint8_t *pos) { + _eeprom_begin(pos, CMD_READ); // Set read location and begin transmission + + const uint8_t v = spiRec(SPI_CHAN_EEPROM1); // After READ a value sits on the Bus + + WRITE(SPI_EEPROM1_CS_PIN, HIGH); // Done with device - v = spiRec(SPI_CHAN_EEPROM1); - WRITE(SPI_EEPROM1_CS, HIGH); return v; } -void eeprom_write_byte(uint8_t* pos, uint8_t value) { - uint8_t eeprom_temp[3]; +void eeprom_write_byte(uint8_t *pos, uint8_t value) { + const uint8_t eeprom_temp = CMD_WREN; + WRITE(SPI_EEPROM1_CS_PIN, LOW); + spiSend(SPI_CHAN_EEPROM1, &eeprom_temp, 1); // Write Enable - /*write enable*/ - eeprom_temp[0] = CMD_WREN; - WRITE(SPI_EEPROM1_CS, LOW); - spiSend(SPI_CHAN_EEPROM1, eeprom_temp, 1); - WRITE(SPI_EEPROM1_CS, HIGH); - delay(1); + WRITE(SPI_EEPROM1_CS_PIN, HIGH); // Done with the Bus + delay(1); // For a small amount of time - /*write addr*/ - eeprom_temp[0] = CMD_WRITE; - eeprom_temp[1] = ((unsigned)pos>>8) & 0xFF; //addr High - eeprom_temp[2] = (unsigned)pos & 0xFF; //addr Low - WRITE(SPI_EEPROM1_CS, LOW); - spiSend(SPI_CHAN_EEPROM1, eeprom_temp, 3); + _eeprom_begin(pos, CMD_WRITE); // Set write address and begin transmission - spiSend(SPI_CHAN_EEPROM1, value); - WRITE(SPI_EEPROM1_CS, HIGH); - delay(EEPROM_WRITE_DELAY); // wait for page write to complete + spiSend(SPI_CHAN_EEPROM1, value); // Send the value to be written + WRITE(SPI_EEPROM1_CS_PIN, HIGH); // Done with the Bus + delay(EEPROM_WRITE_DELAY); // Give page write time to complete } #endif // USE_SHARED_EEPROM -#endif // I2C_EEPROM +#endif // SPI_EEPROM diff --git a/Marlin/src/HAL/shared/esp_wifi.cpp b/Marlin/src/HAL/shared/esp_wifi.cpp index a55f5ca39f..8a6ac2dfa9 100644 --- a/Marlin/src/HAL/shared/esp_wifi.cpp +++ b/Marlin/src/HAL/shared/esp_wifi.cpp @@ -21,6 +21,9 @@ */ #include "../../inc/MarlinConfig.h" + +#if ENABLED(WIFISUPPORT) + #include "Delay.h" void esp_wifi_init(void) { // init ESP01 WIFI module pins @@ -41,3 +44,5 @@ void esp_wifi_init(void) { // init ESP01 WIFI module pi OUT_WRITE(ESP_WIFI_MODULE_ENABLE_PIN, HIGH); #endif } + +#endif // WIFISUPPORT diff --git a/Marlin/src/HAL/shared/fauxpins.h b/Marlin/src/HAL/shared/fauxpins.h new file mode 100644 index 0000000000..924bfba02a --- /dev/null +++ b/Marlin/src/HAL/shared/fauxpins.h @@ -0,0 +1,367 @@ +/** + * 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 + * (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 + +// +// Faux pins for Dependency Check +// + +// +// STM32 Pin Names +// +#define PA0 0x10 +#define PA1 0x11 +#define PA2 0x12 +#define PA3 0x13 +#define PA4 0x14 +#define PA5 0x15 +#define PA6 0x16 +#define PA7 0x17 +#define PA8 0x18 +#define PA9 0x19 +#define PA10 0x1A +#define PA11 0x1B +#define PA12 0x1C +#define PA13 0x1D +#define PA14 0x1E +#define PA15 0x1F + +#define PB0 0x20 +#define PB1 0x21 +#define PB2 0x22 +#define PB3 0x23 +#define PB4 0x24 +#define PB5 0x25 +#define PB6 0x26 +#define PB7 0x27 +#define PB8 0x28 +#define PB9 0x29 +#define PB10 0x2A +#define PB11 0x2B +#define PB12 0x2C +#define PB13 0x2D +#define PB14 0x2E +#define PB15 0x2F + +#define PC0 0x30 +#define PC1 0x31 +#define PC2 0x32 +#define PC3 0x33 +#define PC4 0x34 +#define PC5 0x35 +#define PC6 0x36 +#define PC7 0x37 +#define PC8 0x38 +#define PC9 0x39 +#define PC10 0x3A +#define PC11 0x3B +#define PC12 0x3C +#define PC13 0x3D +#define PC14 0x3E +#define PC15 0x3F + +#define PD0 0x40 +#define PD1 0x41 +#define PD2 0x42 +#define PD3 0x43 +#define PD4 0x44 +#define PD5 0x45 +#define PD6 0x46 +#define PD7 0x47 +#define PD8 0x48 +#define PD9 0x49 +#define PD10 0x4A +#define PD11 0x4B +#define PD12 0x4C +#define PD13 0x4D +#define PD14 0x4E +#define PD15 0x4F + +#define PE0 0x50 +#define PE1 0x51 +#define PE2 0x52 +#define PE3 0x53 +#define PE4 0x54 +#define PE5 0x55 +#define PE6 0x56 +#define PE7 0x57 +#define PE8 0x58 +#define PE9 0x59 +#define PE10 0x5A +#define PE11 0x5B +#define PE12 0x5C +#define PE13 0x5D +#define PE14 0x5E +#define PE15 0x5F + +#define PF0 0x60 +#define PF1 0x61 +#define PF2 0x62 +#define PF3 0x63 +#define PF4 0x64 +#define PF5 0x65 +#define PF6 0x66 +#define PF7 0x67 +#define PF8 0x68 +#define PF9 0x69 +#define PF10 0x6A +#define PF11 0x6B +#define PF12 0x6C +#define PF13 0x6D +#define PF14 0x6E +#define PF15 0x6F + +#define PG0 0x70 +#define PG1 0x71 +#define PG2 0x72 +#define PG3 0x73 +#define PG4 0x74 +#define PG5 0x75 +#define PG6 0x76 +#define PG7 0x77 +#define PG8 0x78 +#define PG9 0x79 +#define PG10 0x7A +#define PG11 0x7B +#define PG12 0x7C +#define PG13 0x7D +#define PG14 0x7E +#define PG15 0x7F + +#define PH0 0x80 +#define PH1 0x81 +#define PH2 0x82 +#define PH3 0x83 +#define PH4 0x84 +#define PH5 0x85 +#define PH6 0x86 +#define PH7 0x87 +#define PH8 0x88 +#define PH9 0x89 +#define PH10 0x8A +#define PH11 0x8B +#define PH12 0x8C +#define PH13 0x8D +#define PH14 0x8E +#define PH15 0x8F + +#define PI0 0x90 +#define PI1 0x91 +#define PI2 0x92 +#define PI3 0x93 +#define PI4 0x94 +#define PI5 0x95 +#define PI6 0x96 +#define PI7 0x97 +#define PI8 0x98 +#define PI9 0x99 +#define PI10 0x9A +#define PI11 0x9B +#define PI12 0x9C +#define PI13 0x9D +#define PI14 0x9E +#define PI15 0x9F + +#define PJ0 0xA0 +#define PJ1 0xA1 +#define PJ2 0xA2 +#define PJ3 0xA3 +#define PJ4 0xA4 +#define PJ5 0xA5 +#define PJ6 0xA6 +#define PJ7 0xA7 +#define PJ8 0xA8 +#define PJ9 0xA9 +#define PJ10 0xAA +#define PJ11 0xAB +#define PJ12 0xAC +#define PJ13 0xAD +#define PJ14 0xAE +#define PJ15 0xAF + +// +// LPC Pin Names +// +#define P0_00 100 +#define P0_01 101 +#define P0_02 102 +#define P0_03 103 +#define P0_04 104 +#define P0_05 105 +#define P0_06 106 +#define P0_07 107 +#define P0_08 108 +#define P0_09 109 +#define P0_10 110 +#define P0_11 111 +#define P0_12 112 +#define P0_13 113 +#define P0_14 114 +#define P0_15 115 +#define P0_16 116 +#define P0_17 117 +#define P0_18 118 +#define P0_19 119 +#define P0_20 120 +#define P0_21 121 +#define P0_22 122 +#define P0_23 123 +#define P0_24 124 +#define P0_25 125 +#define P0_26 126 +#define P0_27 127 +#define P0_28 128 +#define P0_29 129 +#define P0_30 130 +#define P0_31 131 + +#define P1_00 200 +#define P1_01 201 +#define P1_02 202 +#define P1_03 203 +#define P1_04 204 +#define P1_05 205 +#define P1_06 206 +#define P1_07 207 +#define P1_08 208 +#define P1_09 209 +#define P1_10 210 +#define P1_11 211 +#define P1_12 212 +#define P1_13 213 +#define P1_14 214 +#define P1_15 215 +#define P1_16 216 +#define P1_17 217 +#define P1_18 218 +#define P1_19 219 +#define P1_20 220 +#define P1_21 221 +#define P1_22 222 +#define P1_23 223 +#define P1_24 224 +#define P1_25 225 +#define P1_26 226 +#define P1_27 227 +#define P1_28 228 +#define P1_29 229 +#define P1_30 230 +#define P1_31 231 + +#define P2_00 300 +#define P2_01 301 +#define P2_02 302 +#define P2_03 303 +#define P2_04 304 +#define P2_05 305 +#define P2_06 306 +#define P2_07 307 +#define P2_08 308 +#define P2_09 309 +#define P2_10 310 +#define P2_11 311 +#define P2_12 312 +#define P2_13 313 +#define P2_14 314 +#define P2_15 315 +#define P2_16 316 +#define P2_17 317 +#define P2_18 318 +#define P2_19 319 +#define P2_20 320 +#define P2_21 321 +#define P2_22 322 +#define P2_23 323 +#define P2_24 324 +#define P2_25 325 +#define P2_26 326 +#define P2_27 327 +#define P2_28 328 +#define P2_29 329 +#define P2_30 330 +#define P2_31 331 + +#define P3_00 400 +#define P3_01 401 +#define P3_02 402 +#define P3_03 403 +#define P3_04 404 +#define P3_05 405 +#define P3_06 406 +#define P3_07 407 +#define P3_08 408 +#define P3_09 409 +#define P3_10 410 +#define P3_11 411 +#define P3_12 412 +#define P3_13 413 +#define P3_14 414 +#define P3_15 415 +#define P3_16 416 +#define P3_17 417 +#define P3_18 418 +#define P3_19 419 +#define P3_20 420 +#define P3_21 421 +#define P3_22 422 +#define P3_23 423 +#define P3_24 424 +#define P3_25 425 +#define P3_26 426 +#define P3_27 427 +#define P3_28 428 +#define P3_29 429 +#define P3_30 430 +#define P3_31 431 + +#define P4_00 500 +#define P4_01 501 +#define P4_02 502 +#define P4_03 503 +#define P4_04 504 +#define P4_05 505 +#define P4_06 506 +#define P4_07 507 +#define P4_08 508 +#define P4_09 509 +#define P4_10 510 +#define P4_11 511 +#define P4_12 512 +#define P4_13 513 +#define P4_14 514 +#define P4_15 515 +#define P4_16 516 +#define P4_17 517 +#define P4_18 518 +#define P4_19 519 +#define P4_20 520 +#define P4_21 521 +#define P4_22 522 +#define P4_23 523 +#define P4_24 524 +#define P4_25 525 +#define P4_26 526 +#define P4_27 527 +#define P4_28 528 +#define P4_29 529 +#define P4_30 530 +#define P4_31 531 diff --git a/Marlin/src/HAL/shared/math_32bit.h b/Marlin/src/HAL/shared/math_32bit.h index 87e9e6406e..daf82e0650 100644 --- a/Marlin/src/HAL/shared/math_32bit.h +++ b/Marlin/src/HAL/shared/math_32bit.h @@ -22,10 +22,11 @@ #pragma once #include "../../core/macros.h" +#include /** * Math helper functions for 32 bit CPUs */ -static FORCE_INLINE uint32_t MultiU32X24toH32(uint32_t longIn1, uint32_t longIn2) { +FORCE_INLINE static uint32_t MultiU32X24toH32(uint32_t longIn1, uint32_t longIn2) { return ((uint64_t)longIn1 * longIn2 + 0x00800000) >> 24; } diff --git a/Marlin/src/HAL/shared/progmem.h b/Marlin/src/HAL/shared/progmem.h new file mode 100644 index 0000000000..b3bd5c32fd --- /dev/null +++ b/Marlin/src/HAL/shared/progmem.h @@ -0,0 +1,190 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2020 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 + +#ifndef __AVR__ +#ifndef __PGMSPACE_H_ +// This define should prevent reading the system pgmspace.h if included elsewhere +// This is not normally needed. +#define __PGMSPACE_H_ 1 +#endif + +#ifndef PROGMEM +#define PROGMEM +#endif +#ifndef PGM_P +#define PGM_P const char * +#endif +#ifndef PSTR +#define PSTR(str) (str) +#endif +#ifndef F +class __FlashStringHelper; +#define F(string_literal) (reinterpret_cast(PSTR(string_literal))) +#endif +#ifndef _SFR_BYTE +#define _SFR_BYTE(n) (n) +#endif +#ifndef memchr_P +#define memchr_P(str, c, len) memchr((str), (c), (len)) +#endif +#ifndef memcmp_P +#define memcmp_P(a, b, n) memcmp((a), (b), (n)) +#endif +#ifndef memcpy_P +#define memcpy_P(dest, src, num) memcpy((dest), (src), (num)) +#endif +#ifndef memmem_P +#define memmem_P(a, alen, b, blen) memmem((a), (alen), (b), (blen)) +#endif +#ifndef memrchr_P +#define memrchr_P(str, val, len) memrchr((str), (val), (len)) +#endif +#ifndef strcat_P +#define strcat_P(dest, src) strcat((dest), (src)) +#endif +#ifndef strchr_P +#define strchr_P(str, c) strchr((str), (c)) +#endif +#ifndef strchrnul_P +#define strchrnul_P(str, c) strchrnul((str), (c)) +#endif +#ifndef strcmp_P +#define strcmp_P(a, b) strcmp((a), (b)) +#endif +#ifndef strcpy_P +#define strcpy_P(dest, src) strcpy((dest), (src)) +#endif +#ifndef strcasecmp_P +#define strcasecmp_P(a, b) strcasecmp((a), (b)) +#endif +#ifndef strcasestr_P +#define strcasestr_P(a, b) strcasestr((a), (b)) +#endif +#ifndef strlcat_P +#define strlcat_P(dest, src, len) strlcat((dest), (src), (len)) +#endif +#ifndef strlcpy_P +#define strlcpy_P(dest, src, len) strlcpy((dest), (src), (len)) +#endif +#ifndef strlen_P +#define strlen_P(s) strlen((const char *)(s)) +#endif +#ifndef strnlen_P +#define strnlen_P(str, len) strnlen((str), (len)) +#endif +#ifndef strncmp_P +#define strncmp_P(a, b, n) strncmp((a), (b), (n)) +#endif +#ifndef strncasecmp_P +#define strncasecmp_P(a, b, n) strncasecmp((a), (b), (n)) +#endif +#ifndef strncat_P +#define strncat_P(a, b, n) strncat((a), (b), (n)) +#endif +#ifndef strncpy_P +#define strncpy_P(a, b, n) strncpy((a), (b), (n)) +#endif +#ifndef strpbrk_P +#define strpbrk_P(str, chrs) strpbrk((str), (chrs)) +#endif +#ifndef strrchr_P +#define strrchr_P(str, c) strrchr((str), (c)) +#endif +#ifndef strsep_P +#define strsep_P(pstr, delim) strsep((pstr), (delim)) +#endif +#ifndef strspn_P +#define strspn_P(str, chrs) strspn((str), (chrs)) +#endif +#ifndef strstr_P +#define strstr_P(a, b) strstr((a), (b)) +#endif +#ifndef sprintf_P +#define sprintf_P(s, ...) sprintf((s), __VA_ARGS__) +#endif +#ifndef vfprintf_P +#define vfprintf_P(s, ...) vfprintf((s), __VA_ARGS__) +#endif +#ifndef printf_P +#define printf_P(...) printf(__VA_ARGS__) +#endif +#ifndef snprintf_P +#define snprintf_P(s, n, ...) snprintf((s), (n), __VA_ARGS__) +#endif +#ifndef vsprintf_P +#define vsprintf_P(s, ...) vsprintf((s),__VA_ARGS__) +#endif +#ifndef vsnprintf_P +#define vsnprintf_P(s, n, ...) vsnprintf((s), (n),__VA_ARGS__) +#endif +#ifndef fprintf_P +#define fprintf_P(s, ...) fprintf((s), __VA_ARGS__) +#endif + +#ifndef pgm_read_byte +#define pgm_read_byte(addr) (*(const unsigned char *)(addr)) +#endif +#ifndef pgm_read_word +#define pgm_read_word(addr) (*(const unsigned short *)(addr)) +#endif +#ifndef pgm_read_dword +#define pgm_read_dword(addr) (*(const unsigned long *)(addr)) +#endif +#ifndef pgm_read_float +#define pgm_read_float(addr) (*(const float *)(addr)) +#endif + +#ifndef pgm_read_byte_near +#define pgm_read_byte_near(addr) pgm_read_byte(addr) +#endif +#ifndef pgm_read_word_near +#define pgm_read_word_near(addr) pgm_read_word(addr) +#endif +#ifndef pgm_read_dword_near +#define pgm_read_dword_near(addr) pgm_read_dword(addr) +#endif +#ifndef pgm_read_float_near +#define pgm_read_float_near(addr) pgm_read_float(addr) +#endif +#ifndef pgm_read_byte_far +#define pgm_read_byte_far(addr) pgm_read_byte(addr) +#endif +#ifndef pgm_read_word_far +#define pgm_read_word_far(addr) pgm_read_word(addr) +#endif +#ifndef pgm_read_dword_far +#define pgm_read_dword_far(addr) pgm_read_dword(addr) +#endif +#ifndef pgm_read_float_far +#define pgm_read_float_far(addr) pgm_read_float(addr) +#endif + +#ifndef pgm_read_pointer +#define pgm_read_pointer +#endif + +// Fix bug in pgm_read_ptr +#undef pgm_read_ptr +#define pgm_read_ptr(addr) (*((void**)(addr))) + +#endif // __AVR__ diff --git a/Marlin/src/HAL/shared/serial_ports.h b/Marlin/src/HAL/shared/serial_ports.h new file mode 100644 index 0000000000..2daeea34e3 --- /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.cpp b/Marlin/src/HAL/shared/servo.cpp index cfec6f3017..543bc8e873 100644 --- a/Marlin/src/HAL/shared/servo.cpp +++ b/Marlin/src/HAL/shared/servo.cpp @@ -60,14 +60,14 @@ ServoInfo_t servo_info[MAX_SERVOS]; // static array of servo info structures uint8_t ServoCount = 0; // the total number of attached servos -#define SERVO_MIN(v) (MIN_PULSE_WIDTH - (v) * 4) // minimum value in uS for this servo -#define SERVO_MAX(v) (MAX_PULSE_WIDTH - (v) * 4) // maximum value in uS for this servo +#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 /************ static functions common to all instances ***********************/ -static boolean isTimerActive(timer16_Sequence_t timer) { +static bool anyTimerChannelActive(const timer16_Sequence_t timer) { // returns true if any servo is active on this timer - LOOP_L_N(channel, SERVOS_PER_TIMER) { + for (uint8_t channel = 0; channel < SERVOS_PER_TIMER; ++channel) { if (SERVO(timer, channel).Pin.isActive) return true; } @@ -101,22 +101,23 @@ int8_t Servo::attach(const int inPin, const int inMin, const int inMax) { max = (MAX_PULSE_WIDTH - inMax) / 4; // initialize the timer if it has not already been initialized - timer16_Sequence_t timer = SERVO_INDEX_TO_TIMER(servoIndex); - if (!isTimerActive(timer)) initISR(timer); - servo_info[servoIndex].Pin.isActive = true; // this must be set after the check for isTimerActive + const timer16_Sequence_t timer = SERVO_INDEX_TO_TIMER(servoIndex); + if (!anyTimerChannelActive(timer)) initISR(timer); + servo_info[servoIndex].Pin.isActive = true; // this must be set after the check for anyTimerChannelActive return servoIndex; } void Servo::detach() { servo_info[servoIndex].Pin.isActive = false; - timer16_Sequence_t timer = SERVO_INDEX_TO_TIMER(servoIndex); - if (!isTimerActive(timer)) finISR(timer); + const timer16_Sequence_t timer = SERVO_INDEX_TO_TIMER(servoIndex); + if (!anyTimerChannelActive(timer)) finISR(timer); + //pinMode(servo_info[servoIndex].Pin.nbr, INPUT); // set servo pin to input } 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(min), SERVO_MAX(max)); + value = map(constrain(value, 0, 180), 0, 180, SERVO_MIN_US(min), SERVO_MAX_US(max)); writeMicroseconds(value); } @@ -125,8 +126,8 @@ void Servo::writeMicroseconds(int value) { byte channel = servoIndex; if (channel < MAX_SERVOS) { // ensure channel is valid // ensure pulse width is valid - value = constrain(value, SERVO_MIN(min), SERVO_MAX(max)) - (TRIM_DURATION); - value = usToTicks(value); // convert to ticks after compensating for interrupt overhead - 12 Aug 2009 + LIMIT(value, SERVO_MIN_US(min), SERVO_MAX_US(max)); + value = usToTicks(value - (TRIM_DURATION)); // convert to ticks after compensating for interrupt overhead - 12 Aug 2009 CRITICAL_SECTION_START(); servo_info[channel].ticks = value; @@ -135,7 +136,7 @@ void Servo::writeMicroseconds(int value) { } // return the value as degrees -int Servo::read() { return map(readMicroseconds() + 1, SERVO_MIN(min), SERVO_MAX(max), 0, 180); } +int Servo::read() { return map(readMicroseconds() + 1, SERVO_MIN_US(min), SERVO_MAX_US(max), 0, 180); } int Servo::readMicroseconds() { return (servoIndex == INVALID_SERVO) ? 0 : ticksToUs(servo_info[servoIndex].ticks) + (TRIM_DURATION); diff --git a/Marlin/src/HAL/shared/servo.h b/Marlin/src/HAL/shared/servo.h index 6d850da851..85e645146c 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), @@ -74,21 +74,27 @@ #include "../TEENSY40_41/Servo.h" #elif defined(TARGET_LPC1768) #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(STM32GENERIC) && defined(STM32F4) - #include "../STM32_F4_F7/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__) + #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 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/HAL/shared/servo_private.h b/Marlin/src/HAL/shared/servo_private.h index d85d8da8ba..8fd5ab2d88 100644 --- a/Marlin/src/HAL/shared/servo_private.h +++ b/Marlin/src/HAL/shared/servo_private.h @@ -49,8 +49,10 @@ #include "../DUE/ServoTimers.h" #elif defined(__SAMD51__) #include "../SAMD51/ServoTimers.h" +#elif defined(__SAMD21__) + #include "../SAMD21/ServoTimers.h" #else - #error "This library only supports boards with an AVR, SAM3X or SAMD51 processor." + #error "This library only supports boards with an AVR, SAM3X, SAMD21 or SAMD51 processor." #endif // Macros @@ -70,10 +72,10 @@ #define ticksToUs(_ticks) (unsigned(_ticks) * (SERVO_TIMER_PRESCALER) / clockCyclesPerMicrosecond()) // convenience macros -#define SERVO_INDEX_TO_TIMER(_servo_nbr) ((timer16_Sequence_t)(_servo_nbr / (SERVOS_PER_TIMER))) // returns the timer controlling this servo -#define SERVO_INDEX_TO_CHANNEL(_servo_nbr) (_servo_nbr % (SERVOS_PER_TIMER)) // returns the index of the servo on this timer -#define SERVO_INDEX(_timer,_channel) ((_timer*(SERVOS_PER_TIMER)) + _channel) // macro to access servo index by timer and channel -#define SERVO(_timer,_channel) (servo_info[SERVO_INDEX(_timer,_channel)]) // macro to access servo class by timer and channel +#define SERVO_INDEX_TO_TIMER(_servo_nbr) timer16_Sequence_t(_servo_nbr / (SERVOS_PER_TIMER)) // the timer controlling this servo +#define SERVO_INDEX_TO_CHANNEL(_servo_nbr) (_servo_nbr % (SERVOS_PER_TIMER)) // the index of the servo on this timer +#define SERVO_INDEX(_timer,_channel) ((_timer*(SERVOS_PER_TIMER)) + _channel) // servo index by timer and channel +#define SERVO(_timer,_channel) servo_info[SERVO_INDEX(_timer,_channel)] // servo class by timer and channel // Types @@ -94,5 +96,5 @@ extern ServoInfo_t servo_info[MAX_SERVOS]; // Public functions -extern void initISR(timer16_Sequence_t timer); -extern void finISR(timer16_Sequence_t timer); +void initISR(const timer16_Sequence_t timer_index); +void finISR(const timer16_Sequence_t timer_index); diff --git a/Marlin/src/MarlinCore.cpp b/Marlin/src/MarlinCore.cpp index 3a20cf3ee3..6d672e3b8c 100644 --- a/Marlin/src/MarlinCore.cpp +++ b/Marlin/src/MarlinCore.cpp @@ -30,51 +30,62 @@ #include "MarlinCore.h" -#if ENABLED(MARLIN_DEV_MODE) - #warning "WARNING! Disable MARLIN_DEV_MODE for the final build!" -#endif - #include "HAL/shared/Delay.h" #include "HAL/shared/esp_wifi.h" +#include "HAL/shared/cpu_exception/exception_hook.h" + +#if ENABLED(WIFISUPPORT) + #include "HAL/shared/esp_wifi.h" +#endif #ifdef ARDUINO #include #endif #include -#include "core/utility.h" +#include "module/endstops.h" #include "module/motion.h" #include "module/planner.h" -#include "module/endstops.h" -#include "module/temperature.h" -#include "module/settings.h" #include "module/printcounter.h" // PrintCounter or Stopwatch - +#include "module/settings.h" #include "module/stepper.h" -#include "module/stepper/indirection.h" +#include "module/temperature.h" +#if ENABLED(FT_MOTION) + #include "module/ft_motion.h" +#endif #include "gcode/gcode.h" #include "gcode/parser.h" #include "gcode/queue.h" +#include "feature/pause.h" #include "sd/cardreader.h" -#include "lcd/ultralcd.h" -#if HAS_TOUCH_XPT2046 +#include "lcd/marlinui.h" +#if HAS_TOUCH_BUTTONS #include "lcd/touch/touch_buttons.h" #endif #if HAS_TFT_LVGL_UI - #include "lcd/extui/lib/mks_ui/tft_lvgl_configuration.h" - #include "lcd/extui/lib/mks_ui/draw_ui.h" - #include "lcd/extui/lib/mks_ui/mks_hardware_test.h" + #include "lcd/extui/mks_ui/tft_lvgl_configuration.h" + #include "lcd/extui/mks_ui/draw_ui.h" + #include "lcd/extui/mks_ui/mks_hardware.h" #include #endif -#if ENABLED(DWIN_CREALITY_LCD) - #include "lcd/dwin/e3v2/dwin.h" - #include "lcd/dwin/dwin_lcd.h" - #include "lcd/dwin/e3v2/rotary_encoder.h" +#if HAS_DWIN_E3V2 + #include "lcd/dwin/common/encoder.h" + #if ENABLED(DWIN_CREALITY_LCD) + #include "lcd/dwin/creality/dwin.h" + #elif ENABLED(DWIN_CREALITY_LCD_JYERSUI) + #include "lcd/dwin/jyersui/dwin.h" + #elif ENABLED(SOVOL_SV06_RTS) + #include "lcd/sovol_rts/sovol_rts.h" + #endif +#endif + +#if HAS_ETHERNET + #include "feature/ethernet.h" #endif #if ENABLED(IIC_BL24CXX_EEPROM) @@ -89,7 +100,7 @@ #include "feature/host_actions.h" #endif -#if USE_BEEPER +#if HAS_BEEPER #include "libs/buzzer.h" #endif @@ -117,6 +128,10 @@ #include "feature/bltouch.h" #endif +#if ENABLED(BD_SENSOR) + #include "feature/bedlevel/bdl/bdl.h" +#endif + #if ENABLED(POLL_JOG) #include "feature/joystick.h" #endif @@ -125,48 +140,42 @@ #include "module/servo.h" #endif -#if ENABLED(HAS_MOTOR_CURRENT_DAC) +#if HAS_MOTOR_CURRENT_DAC #include "feature/dac/stepper_dac.h" #endif #if ENABLED(EXPERIMENTAL_I2CBUS) #include "feature/twibus.h" - TWIBus i2c; #endif #if ENABLED(I2C_POSITION_ENCODERS) #include "feature/encoder_i2c.h" #endif -#if HAS_TRINAMIC_CONFIG && 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 ENABLED(SDSUPPORT) - 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) + #include "module/polargraph.h" #elif IS_SCARA #include "module/scara.h" +#elif ENABLED(POLAR) + #include "module/polar.h" #endif #if HAS_LEVELING #include "feature/bedlevel/bedlevel.h" #endif -#if BOTH(ADVANCED_PAUSE_FEATURE, PAUSE_PARK_NO_STEPPER_TIMEOUT) - #include "feature/pause.h" +#if ENABLED(GCODE_REPEAT_MARKERS) + #include "feature/repeat.h" #endif #if ENABLED(POWER_LOSS_RECOVERY) @@ -181,7 +190,7 @@ #include "feature/runout.h" #endif -#if HAS_Z_SERVO_PROBE +#if ANY(PROBE_TARE, HAS_Z_SERVO_PROBE) #include "module/probe.h" #endif @@ -201,94 +210,112 @@ #include "feature/fanmux.h" #endif -#if DO_SWITCH_EXTRUDER || ANY(SWITCHING_NOZZLE, PARKING_EXTRUDER, MAGNETIC_PARKING_EXTRUDER, ELECTROMAGNETIC_SWITCHING_TOOLHEAD, SWITCHING_TOOLHEAD) +#if HAS_TOOLCHANGE #include "module/tool_change.h" #endif +#if HAS_FANCHECK + #include "feature/fancheck.h" +#endif + #if ENABLED(USE_CONTROLLER_FAN) #include "feature/controllerfan.h" #endif -#if ENABLED(PRUSA_MMU2) - #include "feature/mmu2/mmu2.h" -#endif - -#if HAS_L64XX - #include "libs/L64XX/L64XX_Marlin.h" +#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) #include "feature/password/password.h" #endif -PGMSTR(NUL_STR, ""); -PGMSTR(M112_KILL_STR, "M112 Shutdown"); -PGMSTR(G28_STR, "G28"); -PGMSTR(M21_STR, "M21"); -PGMSTR(M23_STR, "M23 %s"); -PGMSTR(M24_STR, "M24"); -PGMSTR(SP_P_STR, " P"); PGMSTR(SP_T_STR, " T"); -PGMSTR(X_STR, "X"); PGMSTR(Y_STR, "Y"); PGMSTR(Z_STR, "Z"); PGMSTR(E_STR, "E"); -PGMSTR(X_LBL, "X:"); PGMSTR(Y_LBL, "Y:"); PGMSTR(Z_LBL, "Z:"); PGMSTR(E_LBL, "E:"); -PGMSTR(SP_A_STR, " A"); PGMSTR(SP_B_STR, " B"); PGMSTR(SP_C_STR, " C"); -PGMSTR(SP_X_STR, " X"); PGMSTR(SP_Y_STR, " Y"); PGMSTR(SP_Z_STR, " Z"); PGMSTR(SP_E_STR, " E"); -PGMSTR(SP_X_LBL, " X:"); PGMSTR(SP_Y_LBL, " Y:"); PGMSTR(SP_Z_LBL, " Z:"); PGMSTR(SP_E_LBL, " E:"); +#if DGUS_LCD_UI_MKS + #include "lcd/extui/dgus/DGUSScreenHandler.h" +#endif -MarlinState marlin_state = MF_INITIALIZING; +#if HAS_DRIVER_SAFE_POWER_PROTECT + #include "feature/stepper_driver_safety.h" +#endif + +#if ENABLED(PSU_CONTROL) + #include "feature/power.h" +#endif + +#if ENABLED(EASYTHREED_UI) + #include "feature/easythreed_ui.h" +#endif + +#if ENABLED(MARLIN_TEST_BUILD) + #include "tests/marlin_tests.h" +#endif + +#if HAS_RS485_SERIAL + #include "feature/rs485.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*/) { - TERN(ADVANCED_PAUSE_FEATURE,,UNUSED(no_sleep)); + 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); } #endif -#if PIN_EXISTS(CHDK) - extern millis_t chdk_timeout; -#endif - -#if ENABLED(I2C_POSITION_ENCODERS) - I2CPositionEncodersMgr I2CPEM; -#endif - /** * *************************************************************************** * ******************************** FUNCTIONS ******************************** * *************************************************************************** */ -void setup_killpin() { - #if HAS_KILL - #if KILL_PIN_STATE - SET_INPUT_PULLDOWN(KILL_PIN); - #else - SET_INPUT_PULLUP(KILL_PIN); - #endif - #endif -} - -void setup_powerhold() { - #if HAS_SUICIDE - OUT_WRITE(SUICIDE_PIN, !SUICIDE_PIN_INVERTING); - #endif - #if ENABLED(PSU_CONTROL) - powersupply_on = ENABLED(PSU_DEFAULT_OFF); - if (ENABLED(PSU_DEFAULT_OFF)) PSU_OFF(); else PSU_ON(); - #endif -} - /** * Stepper Reset (RigidBoard, et.al.) */ @@ -297,18 +324,6 @@ void setup_powerhold() { void enableStepperDrivers() { SET_INPUT(STEPPER_RESET_PIN); } // Set to input, allowing pullups to pull the pin high #endif -#if ENABLED(EXPERIMENTAL_I2CBUS) && I2C_SLAVE_ADDRESS > 0 - - void i2c_on_receive(int bytes) { // just echo all bytes received to serial - i2c.receive(bytes); - } - - void i2c_on_request() { // just send dummy data for now - i2c.reply("Hello World!\n"); - } - -#endif - /** * Sensitive pin test for M42, M226 */ @@ -317,161 +332,86 @@ void setup_powerhold() { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wnarrowing" +#pragma GCC diagnostic ignored "-Wsign-compare" -bool pin_is_protected(const pin_t pin) { - static const pin_t sensitive_pins[] PROGMEM = SENSITIVE_PINS; - LOOP_L_N(i, COUNT(sensitive_pins)) { - pin_t sensitive_pin; - memcpy_P(&sensitive_pin, &sensitive_pins[i], sizeof(pin_t)); - if (pin == sensitive_pin) return true; - } +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 -void protected_pin_err() { - SERIAL_ERROR_MSG(STR_ERR_PROTECTED_PIN); -} - -void quickstop_stepper() { - planner.quick_stop(); - planner.synchronize(); - set_current_from_steppers_for_axis(ALL_AXES); - sync_plan_position(); -} - -void enable_e_steppers() { - #define _ENA_E(N) ENABLE_AXIS_E##N(); - REPEAT(E_STEPPERS, _ENA_E) -} - -void enable_all_steppers() { - TERN_(AUTO_POWER_CONTROL, powerManager.power_on()); - ENABLE_AXIS_X(); - ENABLE_AXIS_Y(); - ENABLE_AXIS_Z(); - enable_e_steppers(); -} - -void disable_e_steppers() { - #define _DIS_E(N) DISABLE_AXIS_E##N(); - REPEAT(E_STEPPERS, _DIS_E) -} - -void disable_e_stepper(const uint8_t e) { - #define _CASE_DIS_E(N) case N: DISABLE_AXIS_E##N(); break; - switch (e) { - REPEAT(EXTRUDERS, _CASE_DIS_E) - } -} - -void disable_all_steppers() { - DISABLE_AXIS_X(); - DISABLE_AXIS_Y(); - DISABLE_AXIS_Z(); - disable_e_steppers(); -} - -#if ENABLED(G29_RETRY_AND_RECOVER) - - void event_probe_failure() { - #ifdef ACTION_ON_G29_FAILURE - host_action(PSTR(ACTION_ON_G29_FAILURE)); - #endif - #ifdef G29_FAILURE_COMMANDS - gcode.process_subcommands_now_P(PSTR(G29_FAILURE_COMMANDS)); - #endif - #if ENABLED(G29_HALT_ON_FAILURE) - #ifdef ACTION_ON_CANCEL - host_action_cancel(); - #endif - kill(GET_TEXT(MSG_LCD_PROBING_FAILED)); - #endif - } - - void event_probe_recover() { - TERN_(HOST_PROMPT_SUPPORT, host_prompt_do(PROMPT_INFO, PSTR("G29 Retrying"), DISMISS_STR)); - #ifdef ACTION_ON_G29_RECOVER - host_action(PSTR(ACTION_ON_G29_RECOVER)); - #endif - #ifdef G29_RECOVER_COMMANDS - gcode.process_subcommands_now_P(PSTR(G29_RECOVER_COMMANDS)); - #endif - } - -#endif - -#if ENABLED(ADVANCED_PAUSE_FEATURE) - #include "feature/pause.h" -#else - constexpr bool did_pause_print = false; -#endif - -/** - * A Print Job exists when the timer is running or SD printing - */ -bool printJobOngoing() { - return print_job_timer.isRunning() || IS_SD_PRINTING(); +bool Marlin::printer_busy() { + return planner.has_blocks_queued() || printingIsActive(); } /** - * Printing is active when the print job timer is running + * A Print Job exists when the timer is running or SD is printing */ -bool printingIsActive() { - return !did_pause_print && (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 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); - #if BOTH(LCD_SET_PROGRESS_MANUALLY, USE_M73_REMAINING_TIME) - ui.reset_remaining_time(); - #endif + TERN_(SET_REMAINING_TIME, ui.reset_remaining_time()); + TERN_(HAS_PRUSA_MMU3, MMU3::operation_statistics.reset_per_print_stats()); } print_job_timer.start(); } -#if ENABLED(SDSUPPORT) +#if HAS_MEDIA inline void abortSDPrinting() { - card.endFilePrint(TERN_(SD_RESORT, true)); + IF_DISABLED(NO_SD_AUTOSTART, card.autofile_cancel()); + card.abortFilePrintNow(TERN_(SD_RESORT, true)); + queue.clear(); quickstop_stepper(); - print_job_timer.stop(); - #if DISABLED(SD_ABORT_NO_COOLDOWN) - thermalManager.disable_all_heaters(); - #endif - #if !HAS_CUTTER - thermalManager.zero_fan_speeds(); - #else - cutter.kill(); // Full cutter shutdown including ISR control - #endif - wait_for_heatup = false; + + print_job_timer.abort(); + + IF_DISABLED(SD_ABORT_NO_COOLDOWN, thermalManager.disable_all_heaters()); + + TERN(HAS_CUTTER, cutter.kill(), thermalManager.zero_fan_speeds()); // Full cutter shutdown including ISR control + + marlin.heatup_done(); + TERN_(POWER_LOSS_RECOVERY, recovery.purge()); + #ifdef EVENT_GCODE_SD_ABORT - queue.inject_P(PSTR(EVENT_GCODE_SD_ABORT)); + queue.inject(F(EVENT_GCODE_SD_ABORT)); #endif TERN_(PASSWORD_AFTER_SD_PRINT_ABORT, password.lock_machine()); } inline void finishSDPrinting() { - if (queue.enqueue_one_P(PSTR("M1001"))) { - marlin_state = MF_RUNNING; + if (queue.enqueue_one(F("M1001"))) { // Keep trying until it gets queued + marlin.setState(MF_RUNNING); // Signal to stop trying TERN_(PASSWORD_AFTER_SD_PRINT_END, password.lock_machine()); + TERN_(DGUS_LCD_UI_MKS, screen.sdPrintingFinished()); } } -#endif // SDSUPPORT +#endif // HAS_MEDIA /** * Minimal management of Marlin's core activities: @@ -481,57 +421,66 @@ void startOrResumeJob() { * - Check if CHDK_PIN needs to go LOW * - Check for KILL button held down * - Check for HOME button held down + * - Check for CUSTOM USER button held down * - Check if cooling fan needs to be switched on * - 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 ignore_stepper_queue=false) { +void Marlin::manage_inactivity(const bool no_stepper_sleep/*=false*/) { - if (queue.length < BUFSIZE) queue.get_available_commands(); + queue.get_available_commands(); const millis_t ms = millis(); - // Prevent steppers timing-out in the middle of M600 - // unless PAUSE_PARK_NO_STEPPER_TIMEOUT is disabled - const bool parked_or_ignoring = ignore_stepper_queue || - (BOTH(ADVANCED_PAUSE_FEATURE, PAUSE_PARK_NO_STEPPER_TIMEOUT) && did_pause_print); + // Prevent steppers timing-out + const bool do_reset_timeout = no_stepper_sleep + || TERN0(PAUSE_PARK_NO_STEPPER_TIMEOUT, did_pause_print); // Reset both the M18/M84 activity timeout and the M85 max 'kill' timeout - if (parked_or_ignoring) gcode.reset_stepper_timeout(ms); + if (do_reset_timeout) gcode.reset_stepper_timeout(ms); if (gcode.stepper_max_timed_out(ms)) { SERIAL_ERROR_START(); - SERIAL_ECHOLNPAIR(STR_KILL_INACTIVE_TIME, parser.command_ptr); + SERIAL_ECHOLN(F(STR_KILL_PRE), F(STR_KILL_INACTIVE_TIME), parser.command_ptr); kill(); } - // M18 / M94 : Handle steppers inactive time timeout - if (gcode.stepper_inactive_time) { + const bool has_blocks = planner.has_blocks_queued(); // Any moves in the planner? + if (has_blocks) gcode.reset_stepper_timeout(ms); // Reset timeout for M18/M84, M85 max 'kill', and laser. - static bool already_shutdown_steppers; // = false + // M18 / M84 : Handle steppers inactive time timeout + #if HAS_DISABLE_IDLE_AXES + if (gcode.stepper_inactive_time) { - // Any moves in the planner? Resets both the M18/M84 - // activity timeout and the M85 max 'kill' timeout - if (planner.has_blocks_queued()) - gcode.reset_stepper_timeout(ms); - else if (!parked_or_ignoring && gcode.stepper_inactive_timeout()) { - if (!already_shutdown_steppers) { - already_shutdown_steppers = true; // L6470 SPI will consume 99% of free time without this + static bool already_shutdown_steppers; // = false - // Individual axes will be disabled if configured - if (ENABLED(DISABLE_INACTIVE_X)) DISABLE_AXIS_X(); - if (ENABLED(DISABLE_INACTIVE_Y)) DISABLE_AXIS_Y(); - if (ENABLED(DISABLE_INACTIVE_Z)) DISABLE_AXIS_Z(); - if (ENABLED(DISABLE_INACTIVE_E)) disable_e_steppers(); + if (!has_blocks && !do_reset_timeout && gcode.stepper_inactive_timeout()) { + if (!already_shutdown_steppers) { + already_shutdown_steppers = true; - TERN_(AUTO_BED_LEVELING_UBL, ubl.steppers_were_disabled()); + // Individual axes will be disabled if configured + TERN_(DISABLE_IDLE_X, stepper.disable_axis(X_AXIS)); + TERN_(DISABLE_IDLE_Y, stepper.disable_axis(Y_AXIS)); + TERN_(DISABLE_IDLE_Z, stepper.disable_axis(Z_AXIS)); + TERN_(DISABLE_IDLE_I, stepper.disable_axis(I_AXIS)); + TERN_(DISABLE_IDLE_J, stepper.disable_axis(J_AXIS)); + TERN_(DISABLE_IDLE_K, stepper.disable_axis(K_AXIS)); + TERN_(DISABLE_IDLE_U, stepper.disable_axis(U_AXIS)); + TERN_(DISABLE_IDLE_V, stepper.disable_axis(V_AXIS)); + TERN_(DISABLE_IDLE_W, stepper.disable_axis(W_AXIS)); + TERN_(DISABLE_IDLE_E, stepper.disable_e_steppers()); + + TERN_(AUTO_BED_LEVELING_UBL, bedlevel.steppers_were_disabled()); + } } + else + already_shutdown_steppers = false; } - else - already_shutdown_steppers = false; - } + #endif - #if PIN_EXISTS(CHDK) // Check if pin should be set to LOW (after M240 set it HIGH) + #if ENABLED(PHOTO_GCODE) && PIN_EXISTS(CHDK) + // Check if CHDK should be set to LOW (after M240 set it HIGH) + extern millis_t chdk_timeout; if (chdk_timeout && ELAPSED(ms, chdk_timeout)) { chdk_timeout = 0; WRITE(CHDK_PIN, LOW); @@ -540,11 +489,16 @@ inline void manage_inactivity(const bool ignore_stepper_queue=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) @@ -554,58 +508,204 @@ inline void manage_inactivity(const bool ignore_stepper_queue=false) { // KILL the machine // ---------------------------------------------------------------- if (killCount >= KILL_DELAY) { - SERIAL_ERROR_MSG(STR_KILL_BUTTON); + SERIAL_ERROR_START(); + SERIAL_ECHOLN(F(STR_KILL_PRE), F(STR_KILL_BUTTON)); kill(); } #endif + #if ENABLED(FREEZE_FEATURE) + stepper.frozen = READ(FREEZE_PIN) == FREEZE_STATE; + #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 - const millis_t ms = millis(); + 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_MESSAGEPGM(MSG_AUTO_HOME); - queue.enqueue_now_P(G28_STR); + LCD_MESSAGE(MSG_AUTO_HOME); + queue.inject_P(G28_STR); } } #endif + #if ENABLED(CUSTOM_USER_BUTTONS) + // Handle a custom user button if defined + const bool printer_not_busy = !printingIsActive(); + #define HAS_CUSTOM_USER_BUTTON(N) (PIN_EXISTS(BUTTON##N) && defined(BUTTON##N##_HIT_STATE) && defined(BUTTON##N##_GCODE)) + #define HAS_BETTER_USER_BUTTON(N) HAS_CUSTOM_USER_BUTTON(N) && defined(BUTTON##N##_DESC) + #define _CHECK_CUSTOM_USER_BUTTON(N, CODE) do{ \ + 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) \ + ) { \ + if (ELAPSED(ms, next_cub_ms_##N)) { \ + next_cub_ms_##N = ms + CUB_DEBOUNCE_DELAY_##N; \ + CODE; \ + 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()); \ + } \ + } \ + }while(0) + + #define CHECK_CUSTOM_USER_BUTTON(N) _CHECK_CUSTOM_USER_BUTTON(N, NOOP) + #define CHECK_BETTER_USER_BUTTON(N) _CHECK_CUSTOM_USER_BUTTON(N, if (strlen(BUTTON##N##_DESC)) LCD_MESSAGE_F(BUTTON##N##_DESC)) + + #if HAS_BETTER_USER_BUTTON(1) + CHECK_BETTER_USER_BUTTON(1); + #elif HAS_CUSTOM_USER_BUTTON(1) + CHECK_CUSTOM_USER_BUTTON(1); + #endif + #if HAS_BETTER_USER_BUTTON(2) + CHECK_BETTER_USER_BUTTON(2); + #elif HAS_CUSTOM_USER_BUTTON(2) + CHECK_CUSTOM_USER_BUTTON(2); + #endif + #if HAS_BETTER_USER_BUTTON(3) + CHECK_BETTER_USER_BUTTON(3); + #elif HAS_CUSTOM_USER_BUTTON(3) + CHECK_CUSTOM_USER_BUTTON(3); + #endif + #if HAS_BETTER_USER_BUTTON(4) + CHECK_BETTER_USER_BUTTON(4); + #elif HAS_CUSTOM_USER_BUTTON(4) + CHECK_CUSTOM_USER_BUTTON(4); + #endif + #if HAS_BETTER_USER_BUTTON(5) + CHECK_BETTER_USER_BUTTON(5); + #elif HAS_CUSTOM_USER_BUTTON(5) + CHECK_CUSTOM_USER_BUTTON(5); + #endif + #if HAS_BETTER_USER_BUTTON(6) + CHECK_BETTER_USER_BUTTON(6); + #elif HAS_CUSTOM_USER_BUTTON(6) + CHECK_CUSTOM_USER_BUTTON(6); + #endif + #if HAS_BETTER_USER_BUTTON(7) + CHECK_BETTER_USER_BUTTON(7); + #elif HAS_CUSTOM_USER_BUTTON(7) + CHECK_CUSTOM_USER_BUTTON(7); + #endif + #if HAS_BETTER_USER_BUTTON(8) + CHECK_BETTER_USER_BUTTON(8); + #elif HAS_CUSTOM_USER_BUTTON(8) + CHECK_CUSTOM_USER_BUTTON(8); + #endif + #if HAS_BETTER_USER_BUTTON(9) + CHECK_BETTER_USER_BUTTON(9); + #elif HAS_CUSTOM_USER_BUTTON(9) + CHECK_CUSTOM_USER_BUTTON(9); + #endif + #if HAS_BETTER_USER_BUTTON(10) + CHECK_BETTER_USER_BUTTON(10); + #elif HAS_CUSTOM_USER_BUTTON(10) + CHECK_CUSTOM_USER_BUTTON(10); + #endif + #if HAS_BETTER_USER_BUTTON(11) + CHECK_BETTER_USER_BUTTON(11); + #elif HAS_CUSTOM_USER_BUTTON(11) + CHECK_CUSTOM_USER_BUTTON(11); + #endif + #if HAS_BETTER_USER_BUTTON(12) + CHECK_BETTER_USER_BUTTON(12); + #elif HAS_CUSTOM_USER_BUTTON(12) + CHECK_CUSTOM_USER_BUTTON(12); + #endif + #if HAS_BETTER_USER_BUTTON(13) + CHECK_BETTER_USER_BUTTON(13); + #elif HAS_CUSTOM_USER_BUTTON(13) + CHECK_CUSTOM_USER_BUTTON(13); + #endif + #if HAS_BETTER_USER_BUTTON(14) + CHECK_BETTER_USER_BUTTON(14); + #elif HAS_CUSTOM_USER_BUTTON(14) + CHECK_CUSTOM_USER_BUTTON(14); + #endif + #if HAS_BETTER_USER_BUTTON(15) + CHECK_BETTER_USER_BUTTON(15); + #elif HAS_CUSTOM_USER_BUTTON(15) + CHECK_CUSTOM_USER_BUTTON(15); + #endif + #if HAS_BETTER_USER_BUTTON(16) + CHECK_BETTER_USER_BUTTON(16); + #elif HAS_CUSTOM_USER_BUTTON(16) + CHECK_CUSTOM_USER_BUTTON(16); + #endif + #if HAS_BETTER_USER_BUTTON(17) + CHECK_BETTER_USER_BUTTON(17); + #elif HAS_CUSTOM_USER_BUTTON(17) + CHECK_CUSTOM_USER_BUTTON(17); + #endif + #if HAS_BETTER_USER_BUTTON(18) + CHECK_BETTER_USER_BUTTON(18); + #elif HAS_CUSTOM_USER_BUTTON(18) + CHECK_CUSTOM_USER_BUTTON(18); + #endif + #if HAS_BETTER_USER_BUTTON(19) + CHECK_BETTER_USER_BUTTON(19); + #elif HAS_CUSTOM_USER_BUTTON(19) + CHECK_CUSTOM_USER_BUTTON(19); + #endif + #if HAS_BETTER_USER_BUTTON(20) + CHECK_BETTER_USER_BUTTON(20); + #elif HAS_CUSTOM_USER_BUTTON(20) + CHECK_CUSTOM_USER_BUTTON(20); + #endif + #if HAS_BETTER_USER_BUTTON(21) + CHECK_BETTER_USER_BUTTON(21); + #elif HAS_CUSTOM_USER_BUTTON(21) + CHECK_CUSTOM_USER_BUTTON(21); + #endif + #if HAS_BETTER_USER_BUTTON(22) + CHECK_BETTER_USER_BUTTON(22); + #elif HAS_CUSTOM_USER_BUTTON(22) + CHECK_CUSTOM_USER_BUTTON(22); + #endif + #if HAS_BETTER_USER_BUTTON(23) + CHECK_BETTER_USER_BUTTON(23); + #elif HAS_CUSTOM_USER_BUTTON(23) + CHECK_CUSTOM_USER_BUTTON(23); + #endif + #if HAS_BETTER_USER_BUTTON(24) + CHECK_BETTER_USER_BUTTON(24); + #elif HAS_CUSTOM_USER_BUTTON(24) + CHECK_CUSTOM_USER_BUTTON(24); + #endif + #if HAS_BETTER_USER_BUTTON(25) + CHECK_BETTER_USER_BUTTON(25); + #elif HAS_CUSTOM_USER_BUTTON(25) + CHECK_CUSTOM_USER_BUTTON(25); + #endif + #endif + + TERN_(EASYTHREED_UI, easythreed_ui.run()); + TERN_(USE_CONTROLLER_FAN, controllerFan.update()); // Check if fan should be turned on to cool stepper drivers down - TERN_(AUTO_POWER_CONTROL, powerManager.check()); + TERN_(AUTO_POWER_CONTROL, powerManager.check(!ui.on_status_screen() || printJobOngoing() || printingIsPaused())); 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(active_extruder) > (EXTRUDER_RUNOUT_MINTEMP) + && ELAPSED(ms, gcode.previous_move_ms, SEC_TO_MS(EXTRUDER_RUNOUT_SECONDS)) && !planner.has_blocks_queued() ) { - #if ENABLED(SWITCHING_EXTRUDER) - bool oldstatus; - switch (active_extruder) { - default: oldstatus = E0_ENABLE_READ(); ENABLE_AXIS_E0(); break; - #if E_STEPPERS > 1 - case 2: case 3: oldstatus = E1_ENABLE_READ(); ENABLE_AXIS_E1(); break; - #if E_STEPPERS > 2 - case 4: case 5: oldstatus = E2_ENABLE_READ(); ENABLE_AXIS_E2(); break; - #if E_STEPPERS > 3 - case 6: case 7: oldstatus = E3_ENABLE_READ(); ENABLE_AXIS_E3(); break; - #endif // E_STEPPERS > 3 - #endif // E_STEPPERS > 2 - #endif // E_STEPPERS > 1 - } - #else // !SWITCHING_EXTRUDER - bool oldstatus; - switch (active_extruder) { - default: - #define _CASE_EN(N) case N: oldstatus = E##N##_ENABLE_READ(); ENABLE_AXIS_E##N(); break; - REPEAT(E_STEPPERS, _CASE_EN); - } - #endif + const int8_t e_stepper = TERN(HAS_SWITCHING_EXTRUDER, active_extruder >> 1, active_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; @@ -614,22 +714,7 @@ inline void manage_inactivity(const bool ignore_stepper_queue=false) { planner.set_e_position_mm(olde); planner.synchronize(); - #if ENABLED(SWITCHING_EXTRUDER) - switch (active_extruder) { - default: oldstatus = E0_ENABLE_WRITE(oldstatus); break; - #if E_STEPPERS > 1 - case 2: case 3: oldstatus = E1_ENABLE_WRITE(oldstatus); break; - #if E_STEPPERS > 2 - case 4: case 5: oldstatus = E2_ENABLE_WRITE(oldstatus); break; - #endif // E_STEPPERS > 2 - #endif // E_STEPPERS > 1 - } - #else // !SWITCHING_EXTRUDER - switch (active_extruder) { - #define _CASE_RESTORE(N) case N: E##N##_ENABLE_WRITE(oldstatus); break; - REPEAT(E_STEPPERS, _CASE_RESTORE); - } - #endif // !SWITCHING_EXTRUDER + if (e_off) stepper.DISABLE_EXTRUDER(e_stepper); gcode.reset_stepper_timeout(ms); } @@ -637,11 +722,12 @@ inline void manage_inactivity(const bool ignore_stepper_queue=false) { #if ENABLED(DUAL_X_CARRIAGE) // handle delayed move timeout - if (delayed_move_time && ELAPSED(ms, delayed_move_time + 1000UL) && IsRunning()) { + if (delayed_move_time && ELAPSED(ms, delayed_move_time) && isRunning()) { // travel moves have been received so enact them - delayed_move_time = 0xFFFFFFFFUL; // force moves to be done + delayed_move_time = UINT32_MAX; // force moves to be done destination = current_position; prepare_line_to_destination(); + planner.synchronize(); } #endif @@ -649,8 +735,6 @@ inline void manage_inactivity(const bool ignore_stepper_queue=false) { TERN_(MONITOR_DRIVER_STATUS, monitor_tmc_drivers()); - TERN_(MONITOR_L6470_DRIVER_STATUS, L64xxManager.monitor_driver()); - // Limit check_axes_activity frequency to 10Hz static millis_t next_check_axes_ms = 0; if (ELAPSED(ms, next_check_axes_ms)) { @@ -667,7 +751,12 @@ inline void manage_inactivity(const bool ignore_stepper_queue=false) { WRITE(FET_SAFETY_PIN, FET_SAFETY_INVERTED); } #endif -} + +} // Marlin::manage_inactivity() + +#if ALL(EP_BABYSTEPPING, EMERGENCY_PARSER) + #include "feature/babystep.h" +#endif /** * Standard idle routine keeps the machine alive: @@ -691,46 +780,59 @@ inline void manage_inactivity(const bool ignore_stepper_queue=false) { * - Update the PrΕ―Ε‘a MMU2 * - Handle Joystick jogging */ -void idle(TERN_(ADVANCED_PAUSE_FEATURE, 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("Marlin::idle() call depth: ", idle_depth); + #endif + + // Bed Distance Sensor task + TERN_(BD_SENSOR, bdl.process()); // Core Marlin activities - manage_inactivity(TERN_(ADVANCED_PAUSE_FEATURE, no_stepper_sleep)); + manage_inactivity(no_stepper_sleep); // Manage Heaters (and Watchdog) - thermalManager.manage_heater(); + thermalManager.task(); // Max7219 heartbeat, animation, etc TERN_(MAX7219_DEBUG, max7219.idle_tasks()); // Return if setup() isn't completed - if (marlin_state == MF_INITIALIZING) return; + if (is(MF_INITIALIZING)) goto IDLE_DONE; + + // TODO: Still causing errors + TERN_(TOOL_SENSOR, (void)check_tool_sensor_stats(active_extruder, true)); // Handle filament runout sensors - TERN_(HAS_FILAMENT_SENSOR, runout.run()); + #if HAS_FILAMENT_SENSOR + if (TERN1(HAS_PRUSA_MMU2, !mmu2.enabled()) && TERN1(HAS_PRUSA_MMU3, !mmu3.enabled())) + runout.run(); + #endif // Run HAL idle tasks - #ifdef HAL_IDLETASK - HAL_idletask(); - #endif + hal.idletask(); + + // Check network connection + TERN_(HAS_ETHERNET, ethernet.check()); // Handle Power-Loss Recovery #if ENABLED(POWER_LOSS_RECOVERY) && PIN_EXISTS(POWER_LOSS) - if (printJobOngoing()) recovery.outage(); + if (card.isStillPrinting()) recovery.outage(); #endif // Run StallGuard endstop checks #if ENABLED(SPI_ENDSTOPS) - if (endstops.tmc_spi_homing.any - && TERN1(IMPROVE_HOMING_RELIABILITY, ELAPSED(millis(), sg_guard_period)) - ) LOOP_L_N(i, 4) // Read SGT 4 times per idle loop - if (endstops.tmc_spi_homing_check()) break; + if (endstops.tmc_spi_homing.any && TERN1(IMPROVE_HOMING_RELIABILITY, ELAPSED(millis(), sg_guard_period))) + for (uint8_t i = 0; i < 4; ++i) if (endstops.tmc_spi_homing_check()) break; // Read SGT 4 times per idle loop #endif // Handle SD Card insert / remove - TERN_(SDSUPPORT, card.manage_media()); - - // Handle USB Flash Drive insert / remove - TERN_(USB_FLASH_DRIVE_SUPPORT, Sd2Card::idle()); + TERN_(HAS_MEDIA, card.manage_media()); // Announce Host Keepalive state (if any) TERN_(HOST_KEEPALIVE_FEATURE, gcode.host_keepalive()); @@ -739,13 +841,18 @@ void idle(TERN_(ADVANCED_PAUSE_FEATURE, bool no_stepper_sleep/*=false*/)) { TERN_(PRINTCOUNTER, print_job_timer.tick()); // Update the Beeper queue - TERN_(USE_BEEPER, buzzer.tick()); + TERN_(HAS_BEEPER, buzzer.tick()); // Handle UI input / draw events - TERN(DWIN_CREALITY_LCD, DWIN_Update(), ui.update()); + #if ENABLED(SOVOL_SV06_RTS) + RTS_Update(); + #else + ui.update(); + #endif // Run i2c Position Encoders #if ENABLED(I2C_POSITION_ENCODERS) + { static millis_t i2cpem_next_update_ms; if (planner.has_blocks_queued()) { const millis_t ms = millis(); @@ -754,60 +861,82 @@ void idle(TERN_(ADVANCED_PAUSE_FEATURE, bool no_stepper_sleep/*=false*/)) { i2cpem_next_update_ms = ms + I2CPE_MIN_UPD_TIME_MS; } } + } #endif // Auto-report Temperatures / SD Status #if HAS_AUTO_REPORTING if (!gcode.autoreport_paused) { - TERN_(AUTO_REPORT_TEMPERATURES, thermalManager.auto_report_temperatures()); - TERN_(AUTO_REPORT_SD_STATUS, card.auto_report_sd_status()); + 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_(BUFFER_MONITORING, queue.auto_report_buffer_statistics()); } #endif // Update the PrΕ―Ε‘a MMU2 - TERN_(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()); - #if HAS_TFT_LVGL_UI - LV_TASK_HANDLER(); - #endif -} + // 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(PGM_P const lcd_error/*=nullptr*/, PGM_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 - SERIAL_ERROR_MSG(STR_ERR_KILLED); + // Echo the LCD message to serial for extra context + if (lcd_error) { SERIAL_ECHO_START(); SERIAL_ECHOLN(lcd_error); } #if HAS_DISPLAY - ui.kill_screen(lcd_error ?: GET_TEXT(MSG_KILLED), lcd_component ?: NUL_STR); + ui.kill_screen(lcd_error ?: GET_TEXT_F(MSG_KILLED), lcd_component ?: FPSTR(NUL_STR)); #else - UNUSED(lcd_error); - UNUSED(lcd_component); + UNUSED(lcd_error); UNUSED(lcd_component); #endif - #if HAS_TFT_LVGL_UI - lv_draw_error_message(lcd_error); - #endif + TERN_(HAS_TFT_LVGL_UI, lv_draw_error_message(lcd_error)); + + // "Error:Printer halted. kill() called!" + SERIAL_ERROR_MSG(STR_ERR_KILLED); #ifdef ACTION_ON_KILL - host_action_kill(); + hostui.kill(); #endif 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); @@ -823,49 +952,53 @@ void minkill(const bool steppers_off/*=false*/) { TERN_(HAS_CUTTER, cutter.kill()); // Reiterate cutter shutdown // Power off all steppers (for M112) or just the E steppers - steppers_off ? disable_all_steppers() : disable_e_steppers(); + steppers_off ? stepper.disable_all_steppers() : stepper.disable_e_steppers(); - TERN_(PSU_CONTROL, PSU_OFF()); + TERN_(PSU_CONTROL, powerManager.power_off()); TERN_(HAS_SUICIDE, suicide()); - #if HAS_KILL + #if ANY(HAS_KILL, SOFT_RESET_ON_KILL) - // Wait for kill to be released - while (kill_state()) watchdog_refresh(); + // Wait for both KILL and ENC to be released + while (TERN0(HAS_KILL, kill_state()) || TERN0(SOFT_RESET_ON_KILL, ui.button_pressed())) + hal.watchdog_refresh(); - // Wait for kill to be pressed - while (!kill_state()) watchdog_refresh(); + // Wait for either KILL or ENC to be pressed again + while (TERN1(HAS_KILL, !kill_state()) && TERN1(SOFT_RESET_ON_KILL, !ui.button_pressed())) + hal.watchdog_refresh(); - void (*resetFunc)() = 0; // Declare resetFunc() at address 0 - resetFunc(); // Jump to address 0 + // Reboot the board + hal.reboot(); #else - for (;;) watchdog_refresh(); // Wait for reset + 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(); - #if ENABLED(PROBING_FANS_OFF) - if (thermalManager.fans_paused) thermalManager.set_fans_paused(false); // put things back the way they were + #if ANY(PROBING_FANS_OFF, ADVANCED_PAUSE_FANS_PAUSE) + thermalManager.set_fans_paused(false); // Un-pause fans for safety #endif - if (IsRunning()) { + if (!isStopped()) { SERIAL_ERROR_MSG(STR_ERR_STOPPED); - LCD_MESSAGEPGM(MSG_STOPPED); - safe_delay(350); // allow enough time for messages to get out before stopping - marlin_state = MF_STOPPED; + LCD_MESSAGE(MSG_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) @@ -892,6 +1025,24 @@ inline void tmc_standby_setup() { #if PIN_EXISTS(Z4_STDBY) SET_INPUT_PULLDOWN(Z4_STDBY_PIN); #endif + #if PIN_EXISTS(I_STDBY) + SET_INPUT_PULLDOWN(I_STDBY_PIN); + #endif + #if PIN_EXISTS(J_STDBY) + SET_INPUT_PULLDOWN(J_STDBY_PIN); + #endif + #if PIN_EXISTS(K_STDBY) + SET_INPUT_PULLDOWN(K_STDBY_PIN); + #endif + #if PIN_EXISTS(U_STDBY) + SET_INPUT_PULLDOWN(U_STDBY_PIN); + #endif + #if PIN_EXISTS(V_STDBY) + SET_INPUT_PULLDOWN(V_STDBY_PIN); + #endif + #if PIN_EXISTS(W_STDBY) + SET_INPUT_PULLDOWN(W_STDBY_PIN); + #endif #if PIN_EXISTS(E0_STDBY) SET_INPUT_PULLDOWN(E0_STDBY_PIN); #endif @@ -916,37 +1067,115 @@ inline void tmc_standby_setup() { #if PIN_EXISTS(E7_STDBY) SET_INPUT_PULLDOWN(E7_STDBY_PIN); #endif -} +} // tmc_standby_setup() /** - * Marlin entry-point: Set up before the program loop - * - Set up the kill pin, filament runout, power hold - * - Start the serial port + * Marlin Firmware entry-point. Abandon Hope All Ye Who Enter Here. + * Setup before the program loop: + * + * - Call any special pre-init set for the board + * - Put TMC drivers into Low Power Standby mode + * - Init the serial ports (so setup can be debugged) + * - Set up the kill and suicide pins + * - Prepare (disable) board JTAG and Debug ports + * - Init serial for a connected MKS TFT with WiFi + * - Install Marlin custom Exception Handlers, if set. + * - Init Marlin's HAL interfaces (for SPI, i2c, etc.) + * - Init some optional hardware and features: + * β€’ MAX Thermocouple pins + * β€’ Duet Smart Effector + * β€’ Filament Runout Sensor + * β€’ TMC220x Stepper Drivers (Serial) + * β€’ PSU control + * β€’ Power-loss Recovery + * β€’ Stepper Driver Reset: DISABLE + * β€’ TMC Stepper Drivers (SPI) + * β€’ Run hal.init_board() for additional pins setup + * β€’ ESP WiFi + * - Get the Reset Reason and report it * - Print startup messages and diagnostics - * - Get EEPROM or default settings - * - Initialize managers for: - * β€’ temperature - * β€’ planner - * β€’ watchdog - * β€’ stepper - * β€’ photo pin - * β€’ servos - * β€’ LCD controller - * β€’ Digipot I2C - * β€’ Z probe sled - * β€’ status LEDs - * β€’ Max7219 + * - Calibrate the HAL DELAY for precise timing + * - Init the buzzer, possibly a custom timer + * - Init more optional hardware: + * β€’ Color LED illumination + * β€’ NeoPixel illumination + * β€’ Controller Fan + * β€’ Creality DWIN LCD (show boot image) + * β€’ Tare the Probe if possible + * - Mount the (most likely external) SD Card + * - Load settings from EEPROM (or use defaults) + * - Init the Ethernet Port + * - Init Touch Buttons (for emulated DOGLCD) + * - Adjust the (certainly wrong) current position by the home offset + * - Init the Planner::position (steps) based on current (native) position + * - Initialize more managers and peripherals: + * β€’ Temperatures + * β€’ Print Job Timer + * β€’ Endstops and Endstop Interrupts + * β€’ Stepper ISR - Kind of Important! + * β€’ Servos + * β€’ Servo-based Probe + * β€’ Photograph Pin + * β€’ Laser/Spindle tool Power / PWM + * β€’ Coolant Control + * β€’ Bed Probe + * β€’ Stepper Driver Reset: ENABLE + * β€’ Digipot I2C - Stepper driver current control + * β€’ Stepper DAC - Stepper driver current control + * β€’ Solenoid (probe, or for other use) + * β€’ Home Pin + * β€’ Custom User Buttons + * β€’ Red/Blue Status LEDs + * β€’ Case Light + * β€’ Prusa MMU filament changer + * β€’ Fan Multiplexer + * β€’ Mixing Extruder + * β€’ BLTouch Probe + * β€’ I2C Position Encoders + * β€’ Custom I2C Bus handlers + * β€’ Enhanced tools or extruders: + * β€’ Switching Extruder + * β€’ Switching Nozzle + * β€’ Parking Extruder + * β€’ Magnetic Parking Extruder + * β€’ Switching Toolhead + * β€’ Electromagnetic Switching Toolhead + * β€’ Watchdog Timer - Also Kind of Important! + * β€’ Closed Loop Controller + * - Run Startup Commands, if defined + * - Tell host to close Host Prompts + * - Test Trinamic driver connections + * - Init Prusa MMU2 filament changer + * - Init and test BL24Cxx EEPROM + * - Init Creality DWIN encoder, show faux progress bar + * - Reset Status Message / Show Service Messages + * - Init MAX7219 LED Matrix + * - Init Direct Stepping (Klipper-style motion control) + * - Init TFT LVGL UI (with 3D Graphics) + * - Apply Password Lock - Hold for Authentication + * - Open Touch Screen Calibration screen, if not calibrated + * - Set Marlin to RUNNING State */ void setup() { + #ifdef FASTIO_INIT + FASTIO_INIT(); + #endif + + #ifdef BOARD_PREINIT + BOARD_PREINIT(); // Low-level init (before serial init) + #endif tmc_standby_setup(); // TMC Low Power Standby pins must be set early or they're not usable + // Check startup - does nothing if bootloader sets MCUSR to 0 + const byte mcu = hal.get_reset_source(); + hal.clear_reset_source(); + #if ENABLED(MARLIN_DEV_MODE) auto log_current_ms = [&](PGM_P const msg) { SERIAL_ECHO_START(); - SERIAL_CHAR('['); SERIAL_ECHO(millis()); SERIAL_ECHOPGM("] "); - serialprintPGM(msg); - SERIAL_EOL(); + TSS('[', millis(), F("] ")).echo(); + SERIAL_ECHOLNPGM_P(msg); }; #define SETUP_LOG(M) log_current_ms(PSTR(M)) #else @@ -954,38 +1183,90 @@ void setup() { #endif #define SETUP_RUN(C) do{ SETUP_LOG(STRINGIFY(C)); C; }while(0) - #if EITHER(DISABLE_DEBUG, DISABLE_JTAG) - // Disable any hardware debug to free up pins for IO - #if ENABLED(DISABLE_DEBUG) && defined(JTAGSWD_DISABLE) - JTAGSWD_DISABLE(); - #elif defined(JTAG_DISABLE) - JTAG_DISABLE(); + MYSERIAL1.begin(BAUDRATE); + 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 + #endif + MYSERIAL2.begin(BAUDRATE_2); + serial_connect_timeout = millis() + 1000UL; + while (!MYSERIAL2.connected() && PENDING(millis(), serial_connect_timeout)) { /*nada*/ } + #ifdef SERIAL_PORT_3 + #ifndef BAUDRATE_3 + #define BAUDRATE_3 BAUDRATE + #endif + MYSERIAL3.begin(BAUDRATE_3); + serial_connect_timeout = millis() + 1000UL; + while (!MYSERIAL3.connected() && PENDING(millis(), serial_connect_timeout)) { /*nada*/ } + #endif + #endif + SERIAL_ECHOLNPGM("start"); + + // Set up these pins early to prevent suicide + #if HAS_KILL + SETUP_LOG("KILL_PIN"); + #if KILL_PIN_STATE + SET_INPUT_PULLDOWN(KILL_PIN); #else - #error "DISABLE_(DEBUG|JTAG) is not supported for the selected MCU/Board." + SET_INPUT_PULLUP(KILL_PIN); #endif #endif - MYSERIAL0.begin(BAUDRATE); - uint32_t serial_connect_timeout = millis() + 1000UL; - while (!MYSERIAL0 && PENDING(millis(), serial_connect_timeout)) { /*nada*/ } - #if HAS_MULTI_SERIAL - MYSERIAL1.begin(BAUDRATE); - serial_connect_timeout = millis() + 1000UL; - while (!MYSERIAL1 && PENDING(millis(), serial_connect_timeout)) { /*nada*/ } - #endif - SERIAL_ECHO_MSG("start"); - - #if BOTH(HAS_TFT_LVGL_UI, USE_WIFI_FUNCTION) - mks_esp_wifi_init(); - WIFISERIAL.begin(WIFI_BAUDRATE); - serial_connect_timeout = millis() + 1000UL; - while (/*!WIFISERIAL && */PENDING(millis(), serial_connect_timeout)) { /*nada*/ } + #if ENABLED(FREEZE_FEATURE) + SETUP_LOG("FREEZE_PIN"); + #if FREEZE_STATE + SET_INPUT_PULLDOWN(FREEZE_PIN); + #else + SET_INPUT_PULLUP(FREEZE_PIN); + #endif #endif - SETUP_RUN(HAL_init()); + #if HAS_SUICIDE + SETUP_LOG("SUICIDE_PIN"); + OUT_WRITE(SUICIDE_PIN, !SUICIDE_PIN_STATE); + #endif - #if HAS_L64XX - SETUP_RUN(L64xxManager.init()); // Set up SPI, init drivers + #ifdef JTAGSWD_RESET + SETUP_LOG("JTAGSWD_RESET"); + JTAGSWD_RESET(); + #endif + + // Disable any hardware debug to free up pins for IO + #if ENABLED(DISABLE_DEBUG) && defined(JTAGSWD_DISABLE) + delay(10); + SETUP_LOG("JTAGSWD_DISABLE"); + JTAGSWD_DISABLE(); + #elif ENABLED(DISABLE_JTAG) && defined(JTAG_DISABLE) + delay(10); + SETUP_LOG("JTAG_DISABLE"); + JTAG_DISABLE(); + #endif + + TERN_(DYNAMIC_VECTORTABLE, hook_cpu_exceptions()); // If supported, install Marlin exception handlers at runtime + + SETUP_RUN(hal.init()); + + // Init and disable SPI thermocouples; this is still needed + #if TEMP_SENSOR_IS_MAX_TC(0) || (TEMP_SENSOR_IS_MAX_TC(REDUNDANT) && REDUNDANT_TEMP_MATCH(SOURCE, E0)) + OUT_WRITE(TEMP_0_CS_PIN, HIGH); // Disable + #endif + #if TEMP_SENSOR_IS_MAX_TC(1) || (TEMP_SENSOR_IS_MAX_TC(REDUNDANT) && REDUNDANT_TEMP_MATCH(SOURCE, E1)) + OUT_WRITE(TEMP_1_CS_PIN, HIGH); + #endif + #if TEMP_SENSOR_IS_MAX_TC(2) || (TEMP_SENSOR_IS_MAX_TC(REDUNDANT) && REDUNDANT_TEMP_MATCH(SOURCE, E2)) + OUT_WRITE(TEMP_2_CS_PIN, HIGH); + #endif + #if TEMP_SENSOR_IS_MAX_TC(BED) + OUT_WRITE(TEMP_BED_CS_PIN, HIGH); #endif #if ENABLED(DUET_SMART_EFFECTOR) && PIN_EXISTS(SMART_EFFECTOR_MOD) @@ -996,22 +1277,10 @@ void setup() { SETUP_RUN(runout.setup()); #endif - #if ENABLED(POWER_LOSS_RECOVERY) - SETUP_RUN(recovery.setup()); - #endif - - SETUP_RUN(setup_killpin()); - - #if HAS_TMC220x + #if HAS_TMC_UART SETUP_RUN(tmc_serial_begin()); #endif - SETUP_RUN(setup_powerhold()); - - #if HAS_STEPPER_RESET - SETUP_RUN(disableStepperDrivers()); - #endif - #if HAS_TMC_SPI #if DISABLED(TMC_USE_SW_SPI) SETUP_RUN(SPI.begin()); @@ -1019,37 +1288,48 @@ void setup() { SETUP_RUN(tmc_init_cs_pins()); #endif - #ifdef BOARD_INIT - SETUP_LOG("BOARD_INIT"); - BOARD_INIT(); + #if ENABLED(PSU_CONTROL) + SETUP_LOG("PSU_CONTROL"); + powerManager.init(); #endif - SETUP_RUN(esp_wifi_init()); + #if ENABLED(POWER_LOSS_RECOVERY) + SETUP_RUN(recovery.setup()); + #endif - // Check startup - does nothing if bootloader sets MCUSR to 0 - const byte mcu = HAL_get_reset_source(); - if (mcu & RST_POWER_ON) SERIAL_ECHOLNPGM(STR_POWERUP); - if (mcu & RST_EXTERNAL) SERIAL_ECHOLNPGM(STR_EXTERNAL_RESET); + #if HAS_STEPPER_RESET + SETUP_RUN(disableStepperDrivers()); + #endif + + SETUP_RUN(hal.init_board()); + + #if ENABLED(WIFISUPPORT) + SETUP_RUN(esp_wifi_init()); + #endif + + // Report Reset Reason + if (mcu & RST_POWER_ON) SERIAL_ECHOLNPGM(STR_POWERUP); + if (mcu & RST_EXTERNAL) SERIAL_ECHOLNPGM(STR_EXTERNAL_RESET); if (mcu & RST_BROWN_OUT) SERIAL_ECHOLNPGM(STR_BROWNOUT_RESET); - if (mcu & RST_WATCHDOG) SERIAL_ECHOLNPGM(STR_WATCHDOG_RESET); - if (mcu & RST_SOFTWARE) SERIAL_ECHOLNPGM(STR_SOFTWARE_RESET); - HAL_clear_reset_source(); + if (mcu & RST_WATCHDOG) SERIAL_ECHOLNPGM(STR_WATCHDOG_RESET); + if (mcu & RST_SOFTWARE) SERIAL_ECHOLNPGM(STR_SOFTWARE_RESET); - serialprintPGM(GET_TEXT(MSG_MARLIN)); - SERIAL_CHAR(' '); - SERIAL_ECHOLNPGM(SHORT_BUILD_VERSION); - SERIAL_EOL(); - #if defined(STRING_DISTRIBUTION_DATE) && defined(STRING_CONFIG_H_AUTHOR) + // Identify myself as Marlin x.x.x + SERIAL_ECHOLNPGM("Marlin " SHORT_BUILD_VERSION); + #ifdef STRING_DISTRIBUTION_DATE SERIAL_ECHO_MSG( " Last Updated: " STRING_DISTRIBUTION_DATE " | Author: " STRING_CONFIG_H_AUTHOR ); #endif - SERIAL_ECHO_MSG("Compiled: " __DATE__); - SERIAL_ECHO_MSG(STR_FREE_MEMORY, freeMemory(), STR_PLANNER_BUFFER_BYTES, (int)sizeof(block_t) * (BLOCK_BUFFER_SIZE)); + SERIAL_ECHO_MSG(" Compiled: " __DATE__); + SERIAL_ECHO_MSG(STR_FREE_MEMORY, hal.freeMemory(), STR_PLANNER_BUFFER_BYTES, sizeof(block_t) * (BLOCK_BUFFER_SIZE)); + + // Some HAL need precise delay adjustment + calibrate_delay_loop(); // Init buzzer pin(s) - #if USE_BEEPER + #if HAS_BEEPER SETUP_RUN(buzzer.init()); #endif @@ -1066,35 +1346,62 @@ void setup() { SETUP_RUN(controllerFan.setup()); #endif + TERN_(HAS_FANCHECK, fan_check.init()); + // UI must be initialized before EEPROM // (because EEPROM code calls the UI). - - #if ENABLED(DWIN_CREALITY_LCD) - delay(800); // Required delay (since boot?) - SERIAL_ECHOPGM("\nDWIN handshake "); - if (DWIN_Handshake()) SERIAL_ECHOLNPGM("ok."); else SERIAL_ECHOLNPGM("error."); - DWIN_Frame_SetDir(1); // Orientation 90Β° - DWIN_UpdateLCD(); // Show bootscreen (first image) + #if ENABLED(SOVOL_SV06_RTS) + SETUP_RUN(RTS_Update()); #else SETUP_RUN(ui.init()); - #if HAS_WIRED_LCD && ENABLED(SHOW_BOOTSCREEN) - SETUP_RUN(ui.show_bootscreen()); - #endif - SETUP_RUN(ui.reset_status()); // Load welcome message early. (Retained if no errors exist.) #endif - #if BOTH(SDSUPPORT, SDCARD_EEPROM_EMULATION) - SETUP_RUN(card.mount()); // Mount media with settings before first_load + #if PIN_EXISTS(SAFE_POWER) + #if HAS_DRIVER_SAFE_POWER_PROTECT + SETUP_RUN(stepper_driver_backward_check()); + #else + SETUP_LOG("SAFE_POWER"); + OUT_WRITE(SAFE_POWER_PIN, HIGH); + #endif + #endif + + #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 + + // 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 HAS_TOUCH_XPT2046 - SETUP_RUN(touch.init()); + #if ENABLED(CONFIGURABLE_MACHINE_NAME) + SETUP_RUN(ui.reset_status(false)); // machine_name Initialized by settings.load() #endif - TERN_(HAS_M206_COMMAND, current_position += home_offset); // Init current position based on home_offset + #if ENABLED(PROBE_TARE) + SETUP_RUN(probe.tare_init()); + #endif + + #if HAS_ETHERNET + SETUP_RUN(ethernet.init()); + #endif + + #if HAS_TOUCH_BUTTONS + SETUP_RUN(touchBt.init()); + #endif + + TERN_(HAS_HOME_OFFSET, current_position += home_offset); // Init current position based on home_offset sync_plan_position(); // Vital to init stepper/planner equivalent for current_position @@ -1104,6 +1411,10 @@ void setup() { SETUP_RUN(endstops.init()); // Init endstops and pullups + #if ENABLED(DELTA) && !HAS_SOFTWARE_ENDSTOPS + SETUP_RUN(refresh_delta_clip_start_height()); // Init safe delta height without soft endstops + #endif + SETUP_RUN(stepper.init()); // Init stepper. This enables interrupts! #if HAS_SERVOS @@ -1130,6 +1441,9 @@ void setup() { #endif #if HAS_BED_PROBE + #if PIN_EXISTS(PROBE_ENABLE) + OUT_WRITE(PROBE_ENABLE_PIN, LOW); // Disable + #endif SETUP_RUN(endstops.enable_z_probe(false)); #endif @@ -1141,11 +1455,11 @@ void setup() { SETUP_RUN(digipot_i2c.init()); #endif - #if ENABLED(HAS_MOTOR_CURRENT_DAC) + #if HAS_MOTOR_CURRENT_DAC SETUP_RUN(stepper_dac.init()); #endif - #if EITHER(Z_PROBE_SLED, SOLENOID_PROBE) && HAS_SOLENOID_1 + #if ANY(Z_PROBE_SLED, SOLENOID_PROBE) && HAS_SOLENOID_1 OUT_WRITE(SOL1_PIN, LOW); // OFF #endif @@ -1153,26 +1467,99 @@ void setup() { SET_INPUT_PULLUP(HOME_PIN); #endif + #if ENABLED(CUSTOM_USER_BUTTONS) + #define INIT_CUSTOM_USER_BUTTON_PIN(N) do{ SET_INPUT(BUTTON##N##_PIN); WRITE(BUTTON##N##_PIN, !BUTTON##N##_HIT_STATE); }while(0) + + #if HAS_CUSTOM_USER_BUTTON(1) + INIT_CUSTOM_USER_BUTTON_PIN(1); + #endif + #if HAS_CUSTOM_USER_BUTTON(2) + INIT_CUSTOM_USER_BUTTON_PIN(2); + #endif + #if HAS_CUSTOM_USER_BUTTON(3) + INIT_CUSTOM_USER_BUTTON_PIN(3); + #endif + #if HAS_CUSTOM_USER_BUTTON(4) + INIT_CUSTOM_USER_BUTTON_PIN(4); + #endif + #if HAS_CUSTOM_USER_BUTTON(5) + INIT_CUSTOM_USER_BUTTON_PIN(5); + #endif + #if HAS_CUSTOM_USER_BUTTON(6) + INIT_CUSTOM_USER_BUTTON_PIN(6); + #endif + #if HAS_CUSTOM_USER_BUTTON(7) + INIT_CUSTOM_USER_BUTTON_PIN(7); + #endif + #if HAS_CUSTOM_USER_BUTTON(8) + INIT_CUSTOM_USER_BUTTON_PIN(8); + #endif + #if HAS_CUSTOM_USER_BUTTON(9) + INIT_CUSTOM_USER_BUTTON_PIN(9); + #endif + #if HAS_CUSTOM_USER_BUTTON(10) + INIT_CUSTOM_USER_BUTTON_PIN(10); + #endif + #if HAS_CUSTOM_USER_BUTTON(11) + INIT_CUSTOM_USER_BUTTON_PIN(11); + #endif + #if HAS_CUSTOM_USER_BUTTON(12) + INIT_CUSTOM_USER_BUTTON_PIN(12); + #endif + #if HAS_CUSTOM_USER_BUTTON(13) + INIT_CUSTOM_USER_BUTTON_PIN(13); + #endif + #if HAS_CUSTOM_USER_BUTTON(14) + INIT_CUSTOM_USER_BUTTON_PIN(14); + #endif + #if HAS_CUSTOM_USER_BUTTON(15) + INIT_CUSTOM_USER_BUTTON_PIN(15); + #endif + #if HAS_CUSTOM_USER_BUTTON(16) + INIT_CUSTOM_USER_BUTTON_PIN(16); + #endif + #if HAS_CUSTOM_USER_BUTTON(17) + INIT_CUSTOM_USER_BUTTON_PIN(17); + #endif + #if HAS_CUSTOM_USER_BUTTON(18) + INIT_CUSTOM_USER_BUTTON_PIN(18); + #endif + #if HAS_CUSTOM_USER_BUTTON(19) + INIT_CUSTOM_USER_BUTTON_PIN(19); + #endif + #if HAS_CUSTOM_USER_BUTTON(20) + INIT_CUSTOM_USER_BUTTON_PIN(20); + #endif + #if HAS_CUSTOM_USER_BUTTON(21) + INIT_CUSTOM_USER_BUTTON_PIN(21); + #endif + #if HAS_CUSTOM_USER_BUTTON(22) + INIT_CUSTOM_USER_BUTTON_PIN(22); + #endif + #if HAS_CUSTOM_USER_BUTTON(23) + INIT_CUSTOM_USER_BUTTON_PIN(23); + #endif + #if HAS_CUSTOM_USER_BUTTON(24) + INIT_CUSTOM_USER_BUTTON_PIN(24); + #endif + #if HAS_CUSTOM_USER_BUTTON(25) + INIT_CUSTOM_USER_BUTTON_PIN(25); + #endif + #endif + #if PIN_EXISTS(STAT_LED_RED) OUT_WRITE(STAT_LED_RED_PIN, LOW); // OFF #endif - #if PIN_EXISTS(STAT_LED_BLUE) OUT_WRITE(STAT_LED_BLUE_PIN, LOW); // OFF #endif #if ENABLED(CASE_LIGHT_ENABLE) - #if DISABLED(CASE_LIGHT_USE_NEOPIXEL) - if (PWM_PIN(CASE_LIGHT_PIN)) SET_PWM(CASE_LIGHT_PIN); else SET_OUTPUT(CASE_LIGHT_PIN); - #endif - SETUP_RUN(caselight.update_brightness()); + SETUP_RUN(caselight.init()); #endif - #if ENABLED(MK2_MULTIPLEXER) - SETUP_LOG("MK2_MULTIPLEXER"); - SET_OUTPUT(E_MUX0_PIN); - SET_OUTPUT(E_MUX1_PIN); - SET_OUTPUT(E_MUX2_PIN); + #if HAS_PRUSA_MMU1 + SETUP_RUN(mmu_init()); #endif #if HAS_FANMUX @@ -1187,6 +1574,10 @@ void setup() { SETUP_RUN(bltouch.init(/*set_voltage=*/true)); #endif + #if ENABLED(MAGLEV4) + OUT_WRITE(MAGLEV_TRIGGER_PIN, LOW); + #endif + #if ENABLED(I2C_POSITION_ENCODERS) SETUP_RUN(I2CPEM.init()); #endif @@ -1212,24 +1603,18 @@ void setup() { #endif #endif - #if ENABLED(MAGNETIC_PARKING_EXTRUDER) - SETUP_RUN(mpe_settings_init()); - #endif - #if ENABLED(PARKING_EXTRUDER) SETUP_RUN(pe_solenoid_init()); - #endif - - #if ENABLED(SWITCHING_TOOLHEAD) + #elif ENABLED(MAGNETIC_PARKING_EXTRUDER) + SETUP_RUN(mpe_settings_init()); + #elif ENABLED(SWITCHING_TOOLHEAD) SETUP_RUN(swt_init()); - #endif - - #if ENABLED(ELECTROMAGNETIC_SWITCHING_TOOLHEAD) + #elif ENABLED(ELECTROMAGNETIC_SWITCHING_TOOLHEAD) SETUP_RUN(est_init()); #endif #if ENABLED(USE_WATCHDOG) - SETUP_RUN(watchdog_init()); // Reinit watchdog after HAL_get_reset_source call + SETUP_RUN(hal.watchdog_init()); // Reinit watchdog after hal.get_reset_source call #endif #if ENABLED(EXTERNAL_CLOSED_LOOP_CONTROLLER) @@ -1238,18 +1623,22 @@ void setup() { #ifdef STARTUP_COMMANDS SETUP_LOG("STARTUP_COMMANDS"); - queue.inject_P(PSTR(STARTUP_COMMANDS)); + queue.inject(F(STARTUP_COMMANDS)); #endif #if ENABLED(HOST_PROMPT_SUPPORT) - SETUP_RUN(host_action_prompt_end()); + SETUP_RUN(hostui.prompt_end()); #endif - #if HAS_TRINAMIC_CONFIG && DISABLED(PSU_DEFAULT_OFF) - SETUP_RUN(test_tmc_connection(true, true, true, true)); + #if HAS_DRIVER_SAFE_POWER_PROTECT + SETUP_RUN(stepper_driver_backward_report()); #endif - #if ENABLED(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 @@ -1260,13 +1649,13 @@ void setup() { #endif #if ENABLED(DWIN_CREALITY_LCD) - Encoder_Configuration(); - HMI_Init(); - HMI_StartFrame(true); + SETUP_RUN(dwinInitScreen()); + #elif ENABLED(SOVOL_SV06_RTS) + SETUP_RUN(rts.init()); #endif #if HAS_SERVICE_INTERVALS && DISABLED(DWIN_CREALITY_LCD) - ui.reset_status(true); // Show service messages or keep current status + SETUP_RUN(ui.reset_status(true)); // Show service messages or keep current status #endif #if ENABLED(MAX7219_DEBUG) @@ -1278,25 +1667,65 @@ void setup() { #endif #if HAS_TFT_LVGL_UI - #if ENABLED(SDSUPPORT) + #if HAS_MEDIA if (!card.isMounted()) SETUP_RUN(card.mount()); // Mount SD to load graphics and fonts #endif SETUP_RUN(tft_lvgl_init()); #endif + #if ALL(HAS_WIRED_LCD, SHOW_BOOTSCREEN) + const millis_t elapsed = millis() - bootscreen_ms; + #if ENABLED(MARLIN_DEV_MODE) + SERIAL_ECHOLNPGM("elapsed=", elapsed); + #endif + SETUP_RUN(ui.bootscreen_completion(elapsed)); + #endif + #if ENABLED(PASSWORD_ON_STARTUP) SETUP_RUN(password.lock_machine()); // Will not proceed until correct password provided #endif - marlin_state = MF_RUNNING; + #if ALL(HAS_MARLINUI_MENU, TOUCH_SCREEN_CALIBRATION) && ANY(TFT_CLASSIC_UI, TFT_COLOR_UI) + SETUP_RUN(ui.check_touch_calibration()); + #endif + + #if ENABLED(EASYTHREED_UI) + SETUP_RUN(easythreed_ui.init()); + #endif + + #if HAS_TRINAMIC_CONFIG && DISABLED(PSU_DEFAULT_OFF) + SETUP_RUN(test_tmc_connection()); + #endif + + #if ENABLED(BD_SENSOR) + 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.setState(MF_RUNNING); + + #ifdef STARTUP_TUNE + // Play a short startup tune before continuing. + constexpr uint16_t tune[] = STARTUP_TUNE; + for (uint8_t i = 0; i < COUNT(tune) - 1; i += 2) BUZZ(tune[i + 1], tune[i]); + #endif 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. @@ -1308,19 +1737,24 @@ void setup() { */ void loop() { do { - idle(); + marlin.idle(); - #if ENABLED(SDSUPPORT) - card.checkautostart(); + #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(); + #if ANY(POWER_OFF_TIMER, POWER_OFF_WAIT_FOR_COOLDOWN) + powerManager.checkAutoPowerOff(); + #endif + endstops.event_handler(); TERN_(HAS_TFT_LVGL_UI, printer_state_polling()); + TERN_(MARLIN_TEST_BUILD, runPeriodicTests()); + } while (ENABLED(__AVR__)); // Loop forever on slower (AVR) boards } diff --git a/Marlin/src/MarlinCore.h b/Marlin/src/MarlinCore.h index 69afc7f30e..35482983ec 100644 --- a/Marlin/src/MarlinCore.h +++ b/Marlin/src/MarlinCore.h @@ -23,103 +23,96 @@ #include "inc/MarlinConfig.h" -#ifdef DEBUG_GCODE_PARSER - #include "gcode/parser.h" -#endif - #include #include #include -void stop(); - -// Pass true to keep steppers from timing out -void idle(TERN_(ADVANCED_PAUSE_FEATURE, bool no_stepper_sleep=false)); -inline void idle_no_sleep() { idle(TERN_(ADVANCED_PAUSE_FEATURE, true)); } - -#if ENABLED(EXPERIMENTAL_I2CBUS) - #include "feature/twibus.h" - extern TWIBus i2c; -#endif - -#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 - -/** - * The axis order in all axis related arrays is X, Y, Z, E - */ -void enable_e_steppers(); -void enable_all_steppers(); -void disable_e_stepper(const uint8_t e); -void disable_e_steppers(); -void disable_all_steppers(); - -void kill(PGM_P const lcd_error=nullptr, PGM_P const lcd_component=nullptr, const bool steppers_off=false); -void minkill(const bool steppers_off=false); - -void quickstop_stepper(); - // Global State of the firmware enum MarlinState : uint8_t { - MF_INITIALIZING = 0, - MF_RUNNING = _BV(0), - MF_PAUSED = _BV(1), - MF_WAITING = _BV(2), - MF_STOPPED = _BV(3), - MF_SD_COMPLETE = _BV(4), - MF_KILLED = _BV(7) + MF_INITIALIZING = 0, + MF_STOPPED, + MF_KILLED, + MF_RUNNING, + MF_SD_COMPLETE, + MF_PAUSED, + MF_WAITING, }; -extern MarlinState marlin_state; -inline bool IsRunning() { return marlin_state == MF_RUNNING; } -inline bool IsStopped() { return marlin_state != MF_RUNNING; } +typedef bool (*testFunc_t)(); -bool printingIsActive(); -bool printingIsPaused(); -void startOrResumeJob(); +// Delay ensuring that temperatures are updated and the watchdog is kept alive +void safe_delay(millis_t ms); -extern bool wait_for_heatup; +// Singleton for Marlin global data and methods -#if HAS_RESUME_CONTINUE - extern bool wait_for_user; - void wait_for_user_response(millis_t ms=0, const bool no_sleep=false); -#endif - -#if ENABLED(PSU_CONTROL) - extern bool powersupply_on; - #define PSU_PIN_ON() do{ OUT_WRITE(PS_ON_PIN, PSU_ACTIVE_STATE); powersupply_on = true; }while(0) - #define PSU_PIN_OFF() do{ OUT_WRITE(PS_ON_PIN, !PSU_ACTIVE_STATE); powersupply_on = false; }while(0) - #if ENABLED(AUTO_POWER_CONTROL) - #define PSU_ON() powerManager.power_on() - #define PSU_OFF() powerManager.power_off() - #else - #define PSU_ON() PSU_PIN_ON() - #define PSU_OFF() PSU_PIN_OFF() +class Marlin { +public: + #if ENABLED(CONFIGURABLE_MACHINE_NAME) + static MString<64> machine_name; #endif -#endif -bool pin_is_protected(const pin_t pin); -void protected_pin_err(); + 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; } -#if HAS_SUICIDE - inline void suicide() { OUT_WRITE(SUICIDE_PIN, SUICIDE_PIN_INVERTING); } -#endif + static bool printingIsActive(); + static bool printJobOngoing(); + static bool printingIsPaused(); + static void startOrResumeJob(); -#if HAS_KILL - #ifndef KILL_PIN_STATE - #define KILL_PIN_STATE LOW + static bool printer_busy(); + + static void stop(); + + // Maintain all important activities + static void manage_inactivity(const bool no_stepper_sleep=false); + + // Pass true to keep steppers from timing out + static void idle(const bool no_stepper_sleep=false); + static void idle_no_sleep() { idle(true); } + + 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_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 -#if ENABLED(G29_RETRY_AND_RECOVER) - void event_probe_recover(); - void event_probe_failure(); -#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; } -extern const char NUL_STR[], M112_KILL_STR[], G28_STR[], M21_STR[], M23_STR[], M24_STR[], - SP_A_STR[], SP_B_STR[], SP_C_STR[], - SP_P_STR[], SP_T_STR[], SP_X_STR[], SP_Y_STR[], SP_Z_STR[], SP_E_STR[], - X_LBL[], Y_LBL[], Z_LBL[], E_LBL[], SP_X_LBL[], SP_Y_LBL[], SP_Z_LBL[], SP_E_LBL[]; + // 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 da7d15ec33..b99fcad89a 100644 --- a/Marlin/src/core/boards.h +++ b/Marlin/src/core/boards.h @@ -21,8 +21,14 @@ */ #pragma once +/** + * Whenever changes are made to this file, please update Marlin/Makefile + * and _data/boards.yml in the MarlinDocumentation repo. + */ + #include "macros.h" +#define BOARD_ERROR -2 #define BOARD_UNKNOWN -1 // @@ -49,6 +55,12 @@ #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 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 // @@ -68,47 +80,60 @@ #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 control board (Chinese knock off RAMPS replica) -#define BOARD_FELIX2 1116 // Felix 2.0+ Electronics Board (RAMPS like) -#define BOARD_RIGIDBOARD 1117 // Invent-A-Part RigidBoard -#define BOARD_RIGIDBOARD_V2 1118 // Invent-A-Part RigidBoard V2 -#define BOARD_SAINSMART_2IN1 1119 // Sainsmart 2-in-1 board -#define BOARD_ULTIMAKER 1120 // Ultimaker -#define BOARD_ULTIMAKER_OLD 1121 // Ultimaker (Older electronics. Pre 1.5.4. This is rare) -#define BOARD_AZTEEG_X3 1122 // Azteeg X3 -#define BOARD_AZTEEG_X3_PRO 1123 // Azteeg X3 Pro -#define BOARD_ULTIMAIN_2 1124 // Ultimainboard 2.x (Uses TEMP_SENSOR 20) -#define BOARD_RUMBA 1125 // Rumba -#define BOARD_RUMBA_RAISE3D 1126 // Raise3D N series Rumba derivative -#define BOARD_RL200 1127 // Rapide Lite 200 (v1, low-cost RUMBA clone with drv) -#define BOARD_FORMBOT_TREX2PLUS 1128 // Formbot T-Rex 2 Plus -#define BOARD_FORMBOT_TREX3 1129 // Formbot T-Rex 3 -#define BOARD_FORMBOT_RAPTOR 1130 // Formbot Raptor -#define BOARD_FORMBOT_RAPTOR2 1131 // Formbot Raptor 2 -#define BOARD_BQ_ZUM_MEGA_3D 1132 // bq ZUM Mega 3D -#define BOARD_MAKEBOARD_MINI 1133 // MakeBoard Mini v2.1.2 is a control board sold by MicroMake -#define BOARD_TRIGORILLA_13 1134 // TriGorilla Anycubic version 1.3-based on RAMPS EFB -#define BOARD_TRIGORILLA_14 1135 // ... Ver 1.4 -#define BOARD_TRIGORILLA_14_11 1136 // ... Rev 1.1 (new servo pin order) -#define BOARD_RAMPS_ENDER_4 1137 // Creality: Ender-4, CR-8 -#define BOARD_RAMPS_CREALITY 1138 // Creality: CR10S, CR20, CR-X -#define BOARD_RAMPS_DAGOMA 1139 // Dagoma F5 -#define BOARD_FYSETC_F6_13 1140 // FYSETC F6 1.3 -#define BOARD_FYSETC_F6_14 1141 // FYSETC F6 1.4 -#define BOARD_DUPLICATOR_I3_PLUS 1142 // Wanhao Duplicator i3 Plus -#define BOARD_VORON 1143 // VORON Design -#define BOARD_TRONXY_V3_1_0 1144 // Tronxy TRONXY-V3-1.0 -#define BOARD_Z_BOLT_X_SERIES 1145 // Z-Bolt X Series -#define BOARD_TT_OSCAR 1146 // TT OSCAR -#define BOARD_OVERLORD 1147 // Overlord/Overlord Pro -#define BOARD_HJC2560C_REV1 1148 // ADIMLab Gantry v1 -#define BOARD_HJC2560C_REV2 1149 // ADIMLab Gantry v2 -#define BOARD_TANGO 1150 // BIQU Tango V1 -#define BOARD_MKS_GEN_L_V2 1151 // MKS GEN L V2 -#define BOARD_MKS_GEN_L_V21 1152 // MKS GEN L V2.1 -#define BOARD_COPYMASTER_3D 1153 // Copymaster 3D -#define BOARD_ORTUR_4 1154 // Ortur 4 -#define BOARD_TENLOG_D3_HERO 1155 // Tenlog D3 Hero IDEX printer +#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 +#define BOARD_COPYMASTER_3D 1154 // Copymaster 3D +#define BOARD_ORTUR_4 1155 // Ortur 4 +#define BOARD_TENLOG_D3_HERO 1156 // Tenlog D3 Hero IDEX printer +#define BOARD_TENLOG_MB1_V23 1157 // Tenlog D3, D5, D6 IDEX Printer +#define BOARD_RAMPS_S_12_EEFB 1158 // Ramps S 1.2 by Sakul.cz (Power outputs: Hotend0, Hotend1, Fan, Bed) +#define BOARD_RAMPS_S_12_EEEB 1159 // Ramps S 1.2 by Sakul.cz (Power outputs: Hotend0, Hotend1, Hotend2, Bed) +#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_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 @@ -120,6 +145,7 @@ #define BOARD_EINSY_RAMBO 1203 // Einsy Rambo #define BOARD_EINSY_RETRO 1204 // Einsy Retro #define BOARD_SCOOVO_X9H 1205 // abee Scoovo X9H +#define BOARD_RAMBO_THINKERV2 1206 // ThinkerV2 // // Other ATmega1280, ATmega2560 @@ -139,17 +165,28 @@ #define BOARD_ELEFU_3 1311 // Elefu Ra Board (v3) #define BOARD_LEAPFROG 1312 // Leapfrog #define BOARD_MEGACONTROLLER 1313 // Mega controller -#define BOARD_GT2560_REV_A 1314 // Geeetech GT2560 Rev. A -#define BOARD_GT2560_REV_A_PLUS 1315 // Geeetech GT2560 Rev. A+ (with auto level probe) -#define BOARD_GT2560_V3 1316 // Geeetech GT2560 Rev B for A10(M/D) -#define BOARD_GT2560_V3_MC2 1317 // Geeetech GT2560 Rev B for Mecreator2 -#define BOARD_GT2560_V3_A20 1318 // Geeetech GT2560 Rev B for A20(M/D) -#define BOARD_EINSTART_S 1319 // Einstart retrofit -#define BOARD_WANHAO_ONEPLUS 1320 // Wanhao 0ne+ i3 Mini -#define BOARD_LEAPFROG_XEED2015 1321 // Leapfrog Xeed 2015 -#define BOARD_PICA_REVB 1322 // PICA Shield (original version) -#define BOARD_PICA 1323 // PICA Shield (rev C or later) -#define BOARD_INTAMSYS40 1324 // Intamsys 4.0 (Funmat HT) +#define BOARD_GT2560_REV_A 1314 // Geeetech GT2560 Rev A +#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_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.) // // ATmega1281, ATmega2561 @@ -167,13 +204,14 @@ #define BOARD_MELZI 1502 // Melzi #define BOARD_MELZI_V2 1503 // Melzi V2 #define BOARD_MELZI_MAKR3D 1504 // Melzi with ATmega1284 (MaKr3d version) -#define BOARD_MELZI_CREALITY 1505 // Melzi Creality3D board (for CR-10 etc) -#define BOARD_MELZI_MALYAN 1506 // Melzi Malyan M150 board -#define BOARD_MELZI_TRONXY 1507 // Tronxy X5S -#define BOARD_STB_11 1508 // STB V1.1 -#define BOARD_AZTEEG_X1 1509 // Azteeg X1 -#define BOARD_ANET_10 1510 // Anet 1.0 (Melzi clone) -#define BOARD_ZMIB_V2 1511 // ZoneStar ZMIB V2 +#define BOARD_MELZI_CREALITY 1505 // Melzi Creality3D (for CR-10 etc) +#define BOARD_MELZI_CREALITY_ENDER2 1506 // Melzi Creality3D (for Ender-2) +#define BOARD_MELZI_MALYAN 1507 // Melzi Malyan M150 +#define BOARD_MELZI_TRONXY 1508 // Tronxy X5S +#define BOARD_STB_11 1509 // STB V1.1 +#define BOARD_AZTEEG_X1 1510 // Azteeg X1 +#define BOARD_ANET_10 1511 // Anet 1.0 (Melzi clone) +#define BOARD_ZMIB_V2 1512 // ZoneStar ZMIB V2 // // Other ATmega644P, ATmega644, ATmega1284P @@ -183,12 +221,12 @@ #define BOARD_GEN3_PLUS 1601 // Gen3+ #define BOARD_GEN6 1602 // Gen6 #define BOARD_GEN6_DELUXE 1603 // Gen6 deluxe -#define BOARD_GEN7_CUSTOM 1604 // Gen7 custom (Alfons3 Version) "https://github.com/Alfons3/Generation_7_Electronics" +#define BOARD_GEN7_CUSTOM 1604 // Gen7 custom (Alfons3 Version) https://github.com/Alfons3/Generation_7_Electronics #define BOARD_GEN7_12 1605 // Gen7 v1.1, v1.2 #define BOARD_GEN7_13 1606 // Gen7 v1.3 #define BOARD_GEN7_14 1607 // Gen7 v1.4 -#define BOARD_OMCA_A 1608 // Alpha OMCA board -#define BOARD_OMCA 1609 // Final OMCA board +#define BOARD_OMCA_A 1608 // Alpha OMCA +#define BOARD_OMCA 1609 // Final OMCA #define BOARD_SETHI 1610 // Sethi 3D_1 // @@ -205,7 +243,7 @@ #define BOARD_5DPRINT 1707 // 5DPrint D8 Driver Board // -// LPC1768 ARM Cortex M3 +// LPC1768 ARM Cortex-M3 // #define BOARD_RAMPS_14_RE_ARM_EFB 2000 // Re-ARM with RAMPS 1.4 (Power outputs: Hotend, Fan, Bed) @@ -213,40 +251,43 @@ #define BOARD_RAMPS_14_RE_ARM_EFF 2002 // Re-ARM with RAMPS 1.4 (Power outputs: Hotend, Fan0, Fan1) #define BOARD_RAMPS_14_RE_ARM_EEF 2003 // Re-ARM with RAMPS 1.4 (Power outputs: Hotend0, Hotend1, Fan) #define BOARD_RAMPS_14_RE_ARM_SF 2004 // Re-ARM with RAMPS 1.4 (Power outputs: Spindle, Controller Fan) -#define BOARD_MKS_SBASE 2005 // MKS-Sbase (Power outputs: Hotend0, Hotend1, Bed, Fan) +#define BOARD_MKS_SBASE 2005 // MKS-Sbase #define BOARD_AZSMZ_MINI 2006 // AZSMZ Mini -#define BOARD_BIQU_BQ111_A4 2007 // BIQU BQ111-A4 (Power outputs: Hotend, Fan, Bed) -#define BOARD_SELENA_COMPACT 2008 // Selena Compact (Power outputs: Hotend0, Hotend1, Bed0, Bed1, Fan0, Fan1) -#define BOARD_BIQU_B300_V1_0 2009 // BIQU B300_V1.0 (Power outputs: Hotend0, Fan, Bed, SPI Driver) -#define BOARD_MKS_SGEN_L 2010 // MKS-SGen-L (Power outputs: Hotend0, Hotend1, Bed, Fan) -#define BOARD_GMARSH_X6_REV1 2011 // GMARSH X6 board, revision 1 prototype -#define BOARD_BTT_SKR_V1_1 2012 // BigTreeTech SKR v1.1 (Power outputs: Hotend0, Hotend1, Fan, Bed) -#define BOARD_BTT_SKR_V1_3 2013 // BigTreeTech SKR v1.3 (Power outputs: Hotend0, Hotend1, Fan, Bed) -#define BOARD_BTT_SKR_V1_4 2014 // BigTreeTech SKR v1.4 (Power outputs: Hotend0, Hotend1, Fan, Bed) +#define BOARD_BIQU_BQ111_A4 2007 // BIQU BQ111-A4 +#define BOARD_SELENA_COMPACT 2008 // Selena Compact +#define BOARD_BIQU_B300_V1_0 2009 // BIQU B300_V1.0 +#define BOARD_MKS_SGEN_L 2010 // MKS-SGen-L +#define BOARD_GMARSH_X6_REV1 2011 // GMARSH X6, revision 1 prototype +#define BOARD_BTT_SKR_V1_1 2012 // BigTreeTech SKR v1.1 +#define BOARD_BTT_SKR_V1_3 2013 // BigTreeTech SKR v1.3 +#define BOARD_BTT_SKR_V1_4 2014 // BigTreeTech SKR v1.4 +#define BOARD_EMOTRONIC 2015 // eMotion-Tech eMotronic // -// LPC1769 ARM Cortex M3 +// LPC1769 ARM Cortex-M3 // -#define BOARD_MKS_SGEN 2500 // MKS-SGen (Power outputs: Hotend0, Hotend1, Bed, Fan) -#define BOARD_AZTEEG_X5_GT 2501 // Azteeg X5 GT (Power outputs: Hotend0, Hotend1, Bed, Fan) -#define BOARD_AZTEEG_X5_MINI 2502 // Azteeg X5 Mini (Power outputs: Hotend0, Bed, Fan) -#define BOARD_AZTEEG_X5_MINI_WIFI 2503 // Azteeg X5 Mini Wifi (Power outputs: Hotend0, Bed, Fan) +#define BOARD_MKS_SGEN 2500 // MKS-SGen +#define BOARD_AZTEEG_X5_GT 2501 // Azteeg X5 GT +#define BOARD_AZTEEG_X5_MINI 2502 // Azteeg X5 Mini +#define BOARD_AZTEEG_X5_MINI_WIFI 2503 // Azteeg X5 Mini Wifi #define BOARD_COHESION3D_REMIX 2504 // Cohesion3D ReMix #define BOARD_COHESION3D_MINI 2505 // Cohesion3D Mini #define BOARD_SMOOTHIEBOARD 2506 // Smoothieboard #define BOARD_TH3D_EZBOARD 2507 // TH3D EZBoard v1.0 -#define BOARD_BTT_SKR_V1_4_TURBO 2508 // BigTreeTech SKR v1.4 TURBO (Power outputs: Hotend0, Hotend1, Fan, Bed) -#define BOARD_MKS_SGEN_L_V2 2509 // MKS SGEN_L V2 (Power outputs: Hotend0, Hotend1, Bed, Fan) -#define BOARD_BTT_SKR_E3_TURBO 2510 // BigTreeTech SKR E3 Turbo (Power outputs: Hotend0, Hotend1, Bed, Fan0, Fan1) +#define BOARD_BTT_SKR_V1_4_TURBO 2508 // BigTreeTech SKR v1.4 TURBO +#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 +// SAM3X8E ARM Cortex-M3 // #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) @@ -264,121 +305,282 @@ #define BOARD_RAMPS4DUE_EFF 3017 // RAMPS4DUE (Power outputs: Hotend, Fan0, Fan1) #define BOARD_RAMPS4DUE_EEF 3018 // RAMPS4DUE (Power outputs: Hotend0, Hotend1, Fan) #define BOARD_RAMPS4DUE_SF 3019 // RAMPS4DUE (Power outputs: Spindle, Controller Fan) -#define BOARD_RURAMPS4D_11 3020 // RuRAMPS4Duo v1.1 (Power outputs: Hotend0, Hotend1, Hotend2, Fan0, Fan1, Bed) -#define BOARD_RURAMPS4D_13 3021 // RuRAMPS4Duo v1.3 (Power outputs: Hotend0, Hotend1, Hotend2, Fan0, Fan1, Bed) +#define BOARD_RURAMPS4D_11 3020 // RuRAMPS4Duo v1.1 +#define BOARD_RURAMPS4D_13 3021 // RuRAMPS4Duo v1.3 #define BOARD_ULTRATRONICS_PRO 3022 // ReprapWorld Ultratronics Pro V1.0 #define BOARD_ARCHIM1 3023 // UltiMachine Archim1 (with DRV8825 drivers) #define BOARD_ARCHIM2 3024 // UltiMachine Archim2 (with TMC2130 drivers) #define BOARD_ALLIGATOR 3025 // Alligator Board R2 #define BOARD_CNCONTROLS_15D 3026 // Cartesio CN Controls V15 on DUE +#define BOARD_KRATOS32 3027 // K.3D Kratos32 (Arduino Due Shield) // -// SAM3X8C ARM Cortex M3 +// SAM3X8C ARM Cortex-M3 // -#define BOARD_PRINTRBOARD_G2 3100 // PRINTRBOARD G2 +#define BOARD_PRINTRBOARD_G2 3100 // Printrboard G2 #define BOARD_ADSK 3101 // Arduino DUE Shield Kit (ADSK) +// +// STM32 ARM Cortex-M0/+ +// + +#define BOARD_BTT_EBB42_V1_1 4000 // BigTreeTech EBB42 V1.1 (STM32G0B1CB) +#define BOARD_BTT_SKR_MINI_E3_V3_0 4001 // BigTreeTech SKR Mini E3 V3.0 (STM32G0B0RE / STM32G0B1RE) +#define BOARD_BTT_MANTA_E3_EZ_V1_0 4002 // BigTreeTech Manta E3 EZ V1.0 (STM32G0B1RE) +#define BOARD_BTT_MANTA_M4P_V2_1 4003 // BigTreeTech Manta M4P V2.1 (STM32G0B0RE) +#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 4000 // STM32F070CB STM32F0 controller -#define BOARD_MALYAN_M300 4001 // STM32F070-based delta -#define BOARD_STM32F103RE 4002 // STM32F103RE Libmaple-based STM32F1 controller -#define BOARD_MALYAN_M200 4003 // STM32C8T6 Libmaple-based STM32F1 controller -#define BOARD_STM3R_MINI 4004 // STM32F103RE Libmaple-based STM32F1 controller -#define BOARD_GTM32_PRO_VB 4005 // STM32F103VET6 controller -#define BOARD_GTM32_MINI 4006 // STM32F103VET6 controller -#define BOARD_GTM32_MINI_A30 4007 // STM32F103VET6 controller -#define BOARD_GTM32_REV_B 4008 // STM32F103VET6 controller -#define BOARD_MORPHEUS 4009 // STM32F103C8 / STM32F103CB Libmaple-based STM32F1 controller -#define BOARD_CHITU3D 4010 // Chitu3D (STM32F103RET6) -#define BOARD_MKS_ROBIN 4011 // MKS Robin (STM32F103ZET6) -#define BOARD_MKS_ROBIN_MINI 4012 // MKS Robin Mini (STM32F103VET6) -#define BOARD_MKS_ROBIN_NANO 4013 // MKS Robin Nano (STM32F103VET6) -#define BOARD_MKS_ROBIN_NANO_V2 4014 // MKS Robin Nano V2 (STM32F103VET6) -#define BOARD_MKS_ROBIN_LITE 4015 // MKS Robin Lite/Lite2 (STM32F103RCT6) -#define BOARD_MKS_ROBIN_LITE3 4016 // MKS Robin Lite3 (STM32F103RCT6) -#define BOARD_MKS_ROBIN_PRO 4017 // MKS Robin Pro (STM32F103ZET6) -#define BOARD_MKS_ROBIN_E3 4018 // MKS Robin E3 (STM32F103RCT6) -#define BOARD_MKS_ROBIN_E3D 4019 // MKS Robin E3D (STM32F103RCT6) -#define BOARD_MKS_ROBIN_E3P 4020 // MKS Robin E3p (STM32F103VET6) -#define BOARD_BTT_SKR_MINI_V1_1 4021 // BigTreeTech SKR Mini v1.1 (STM32F103RC) -#define BOARD_BTT_SKR_MINI_E3_V1_0 4022 // BigTreeTech SKR Mini E3 (STM32F103RC) -#define BOARD_BTT_SKR_MINI_E3_V1_2 4023 // BigTreeTech SKR Mini E3 V1.2 (STM32F103RC) -#define BOARD_BTT_SKR_MINI_E3_V2_0 4024 // BigTreeTech SKR Mini E3 V2.0 (STM32F103RC) -#define BOARD_BTT_SKR_E3_DIP 4025 // BigTreeTech SKR E3 DIP V1.0 (STM32F103RC / STM32F103RE) -#define BOARD_JGAURORA_A5S_A1 4026 // JGAurora A5S A1 (STM32F103ZET6) -#define BOARD_FYSETC_AIO_II 4027 // FYSETC AIO_II -#define BOARD_FYSETC_CHEETAH 4028 // FYSETC Cheetah -#define BOARD_FYSETC_CHEETAH_V12 4029 // FYSETC Cheetah V1.2 -#define BOARD_LONGER3D_LK 4030 // Alfawise U20/U20+/U30 (Longer3D LK1/2) / STM32F103VET6 -#define BOARD_CCROBOT_MEEB_3DP 4031 // ccrobot-online.com MEEB_3DP (STM32F103RC) -#define BOARD_CHITU3D_V5 4032 // Chitu3D TronXY X5SA V5 Board -#define BOARD_CHITU3D_V6 4033 // Chitu3D TronXY X5SA V5 Board -#define BOARD_CREALITY_V4 4034 // Creality v4.x (STM32F103RE) -#define BOARD_CREALITY_V427 4035 // Creality v4.2.7 (STM32F103RE) -#define BOARD_TRIGORILLA_PRO 4036 // Trigorilla Pro (STM32F103ZET6) -#define BOARD_FLY_MINI 4037 // FLY MINI (STM32F103RCT6) +#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 // -#define BOARD_TEENSY31_32 4100 // Teensy3.1 and Teensy3.2 -#define BOARD_TEENSY35_36 4101 // Teensy3.5 and Teensy3.6 +#define BOARD_TEENSY31_32 5100 // Teensy3.1 and Teensy3.2 +#define BOARD_TEENSY35_36 5101 // Teensy3.5 and Teensy3.6 // // STM32 ARM Cortex-M4F // -#define BOARD_BEAST 4200 // STM32F4xxVxT6 Libmaple-based STM32F4 controller -#define BOARD_GENERIC_STM32F4 4201 // STM32 STM32GENERIC-based STM32F4 controller -#define BOARD_ARMED 4202 // Arm'ed STM32F4-based controller -#define BOARD_RUMBA32_V1_0 4203 // RUMBA32 STM32F446VET6 based controller from Aus3D -#define BOARD_RUMBA32_V1_1 4204 // RUMBA32 STM32F446VET6 based controller from Aus3D -#define BOARD_RUMBA32_MKS 4205 // RUMBA32 STM32F446VET6 based controller from Makerbase -#define BOARD_BLACK_STM32F407VE 4206 // BLACK_STM32F407VE -#define BOARD_BLACK_STM32F407ZE 4207 // BLACK_STM32F407ZE -#define BOARD_STEVAL_3DP001V1 4208 // STEVAL-3DP001V1 3D PRINTER BOARD -#define BOARD_BTT_SKR_PRO_V1_1 4209 // BigTreeTech SKR Pro v1.1 (STM32F407ZG) -#define BOARD_BTT_SKR_PRO_V1_2 4210 // BigTreeTech SKR Pro v1.2 (STM32F407ZG) -#define BOARD_BTT_BTT002_V1_0 4211 // BigTreeTech BTT002 v1.0 (STM32F407VG) -#define BOARD_BTT_GTR_V1_0 4212 // BigTreeTech GTR v1.0 (STM32F407IGT) -#define BOARD_LERDGE_K 4213 // Lerdge K (STM32F407ZG) -#define BOARD_LERDGE_S 4214 // Lerdge S (STM32F407VE) -#define BOARD_LERDGE_X 4215 // Lerdge X (STM32F407VE) -#define BOARD_VAKE403D 4216 // VAkE 403D (STM32F446VET6) -#define BOARD_FYSETC_S6 4217 // FYSETC S6 board -#define BOARD_FYSETC_S6_V2_0 4218 // FYSETC S6 v2.0 board -#define BOARD_FLYF407ZG 4219 // FLYF407ZG board (STM32F407ZG) -#define BOARD_MKS_ROBIN2 4220 // MKS_ROBIN2 (STM32F407ZE) +#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_BLACKBEEZMINI_V1 5252 // BlackBeezMini V1 (STM32F401CCU6) +#define BOARD_XTLW_CLIMBER_8TH 5253 // XTLW Climber-8th (STM32F407VGT6) +#define BOARD_FLY_RRF_E3_V1 5254 // Fly RRF E3 V1.0 (STM32F407VG) +#define BOARD_FLY_SUPER8 5255 // Fly SUPER8 (STM32F407ZGT6) +#define BOARD_FLY_D8 5256 // FLY D8 (STM32F407VG) +#define BOARD_FLY_CDY_V3 5257 // FLY CDY V3 (STM32F407VGT6) +#define BOARD_ZNP_ROBIN_NANO 5258 // Elegoo Neptune 2 v1.2 board +#define BOARD_ZNP_ROBIN_NANO_V1_3 5259 // Elegoo Neptune 2 v1.3 board +#define BOARD_MKS_NEPTUNE_X 5260 // Elegoo Neptune X +#define BOARD_MKS_NEPTUNE_3 5261 // Elegoo Neptune 3 // -// ARM Cortex M7 +// 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_THE_BORG 5000 // THE-BORG (Power outputs: Hotend0, Hotend1, Bed, Fan) -#define BOARD_REMRAM_V1 5001 // RemRam v1 -#define BOARD_TEENSY41 5002 // Teensy 4.1 -#define BOARD_T41U5XBB 5003 // T41U5XBB Teensy 4.1 breakout board -#define BOARD_NUCLEO_F767ZI 5004 // ST NUCLEO-F767ZI Dev Board +#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 // -#define BOARD_ESPRESSIF_ESP32 6000 // Generic ESP32 -#define BOARD_MRR_ESPA 6001 // MRR ESPA board based on ESP32 (native pins only) -#define BOARD_MRR_ESPE 6002 // MRR ESPE board based on ESP32 (with I2S stepper stream) -#define BOARD_E4D_BOX 6003 // E4d@BOX +#define BOARD_ESPRESSIF_ESP32 7000 // Generic ESP32 +#define BOARD_MRR_ESPA 7001 // MRR ESPA based on ESP32 (native pins only) +#define BOARD_MRR_ESPE 7002 // MRR ESPE based on ESP32 (with I2S stepper stream) +#define BOARD_E4D_BOX 7003 // E4d@BOX +#define BOARD_RESP32_CUSTOM 7004 // Rutilea ESP32 custom board +#define BOARD_FYSETC_E4 7005 // FYSETC E4 +#define BOARD_PANDA_ZHU 7006 // Panda_ZHU +#define BOARD_PANDA_M4 7007 // Panda_M4 +#define BOARD_MKS_TINYBEE 7008 // MKS TinyBee based on ESP32 (with I2S stepper stream) +#define BOARD_ENWI_ESPNP 7009 // enwi ESPNP based on ESP32 (with I2S stepper stream) +#define BOARD_GODI_CONTROLLER_V1_0 7010 // Godi Controller based on ESP32 32-Bit V1.0 +#define BOARD_MM_JOKER 7011 // MagicMaker JOKER based on ESP32 (with I2S stepper stream) // -// SAMD51 ARM Cortex M4 +// SAMD51 ARM Cortex-M4 // -#define BOARD_AGCM4_RAMPS_144 6100 // RAMPS 1.4.4 +#define BOARD_AGCM4_RAMPS_144 7100 // RAMPS 1.4.4 +#define BOARD_BRICOLEMON_V1_0 7101 // Bricolemon +#define BOARD_BRICOLEMON_LITE_V1_0 7102 // Bricolemon Lite + +// +// SAMD21 ARM Cortex-M4 +// + +#define BOARD_MINITRONICS20 7103 // Minitronics v2.0 + +// +// HC32 ARM Cortex-M4 +// + +#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 @@ -390,9 +592,7 @@ // Simulations // -#define BOARD_LINUX_RAMPS 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) - -#define IS_MELZI MB(MELZI, MELZI_CREALITY, MELZI_MAKR3D, MELZI_MALYAN, MELZI_TRONXY, MELZI_V2) diff --git a/Marlin/src/core/bug_on.h b/Marlin/src/core/bug_on.h new file mode 100644 index 0000000000..8ff565ff73 --- /dev/null +++ b/Marlin/src/core/bug_on.h @@ -0,0 +1,39 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2021 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] + * + * Copyright (c) 2021 X-Ryl669 [https://blog.cyril.by] + * + * 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 + +// We need SERIAL_ECHOPGM and macros.h +#include "serial.h" + +#if ENABLED(POSTMORTEM_DEBUGGING) + // 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 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 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 +#endif diff --git a/Marlin/src/core/debug_out.h b/Marlin/src/core/debug_out.h index 6ae1b9d8bb..8f14f05c79 100644 --- a/Marlin/src/core/debug_out.h +++ b/Marlin/src/core/debug_out.h @@ -27,26 +27,20 @@ // #undef DEBUG_SECTION -#undef DEBUG_PRINT_P #undef DEBUG_ECHO_START #undef DEBUG_ERROR_START +#undef DEBUG_WARN_START #undef DEBUG_CHAR #undef DEBUG_ECHO -#undef DEBUG_DECIMAL -#undef DEBUG_ECHO_F #undef DEBUG_ECHOLN #undef DEBUG_ECHOPGM #undef DEBUG_ECHOLNPGM -#undef DEBUG_ECHOPAIR -#undef DEBUG_ECHOPAIR_P -#undef DEBUG_ECHOPAIR_F -#undef DEBUG_ECHOPAIR_F_P -#undef DEBUG_ECHOLNPAIR -#undef DEBUG_ECHOLNPAIR_P -#undef DEBUG_ECHOLNPAIR_F -#undef DEBUG_ECHOLNPAIR_F_P +#undef DEBUG_ECHOPGM_P +#undef DEBUG_ECHOLNPGM_P #undef DEBUG_ECHO_MSG #undef DEBUG_ERROR_MSG +#undef DEBUG_WARN_MSG +#undef DEBUG_ECHO_TERNARY #undef DEBUG_EOL #undef DEBUG_FLUSH #undef DEBUG_POS @@ -57,28 +51,24 @@ #if DEBUG_OUT #include "debug_section.h" - #define DEBUG_SECTION(N,S,D) SectionLog N(PSTR(S),D) + #define DEBUG_SECTION(N,S,D) SectionLog N(F(S),D) - #define DEBUG_PRINT_P(P) serialprintPGM(P) #define DEBUG_ECHO_START SERIAL_ECHO_START #define DEBUG_ERROR_START SERIAL_ERROR_START + #define DEBUG_WARN_START SERIAL_WARN_START #define DEBUG_CHAR SERIAL_CHAR #define DEBUG_ECHO SERIAL_ECHO - #define DEBUG_DECIMAL SERIAL_DECIMAL - #define DEBUG_ECHO_F SERIAL_ECHO_F #define DEBUG_ECHOLN SERIAL_ECHOLN #define DEBUG_ECHOPGM SERIAL_ECHOPGM #define DEBUG_ECHOLNPGM SERIAL_ECHOLNPGM - #define DEBUG_ECHOPAIR SERIAL_ECHOPAIR - #define DEBUG_ECHOPAIR_P SERIAL_ECHOPAIR_P - #define DEBUG_ECHOPAIR_F SERIAL_ECHOPAIR_F - #define DEBUG_ECHOPAIR_F_P SERIAL_ECHOPAIR_F_P - #define DEBUG_ECHOLNPAIR SERIAL_ECHOLNPAIR - #define DEBUG_ECHOLNPAIR_P SERIAL_ECHOLNPAIR_P - #define DEBUG_ECHOLNPAIR_F SERIAL_ECHOLNPAIR_F - #define DEBUG_ECHOLNPAIR_F_P SERIAL_ECHOLNPAIR_F_P + #define DEBUG_ECHOPGM SERIAL_ECHOPGM + #define DEBUG_ECHOPGM_P SERIAL_ECHOPGM_P + #define DEBUG_ECHOLNPGM SERIAL_ECHOLNPGM + #define DEBUG_ECHOLNPGM_P SERIAL_ECHOLNPGM_P #define DEBUG_ECHO_MSG SERIAL_ECHO_MSG #define DEBUG_ERROR_MSG SERIAL_ERROR_MSG + #define DEBUG_WARN_MSG SERIAL_WARN_MSG + #define DEBUG_ECHO_TERNARY SERIAL_ECHO_TERNARY #define DEBUG_EOL SERIAL_EOL #define DEBUG_FLUSH SERIAL_FLUSH #define DEBUG_POS SERIAL_POS @@ -89,26 +79,20 @@ #else #define DEBUG_SECTION(...) NOOP - #define DEBUG_PRINT_P(P) NOOP #define DEBUG_ECHO_START() NOOP #define DEBUG_ERROR_START() NOOP + #define DEBUG_WARN_START() NOOP #define DEBUG_CHAR(...) NOOP #define DEBUG_ECHO(...) NOOP - #define DEBUG_DECIMAL(...) NOOP - #define DEBUG_ECHO_F(...) NOOP #define DEBUG_ECHOLN(...) NOOP #define DEBUG_ECHOPGM(...) NOOP #define DEBUG_ECHOLNPGM(...) NOOP - #define DEBUG_ECHOPAIR(...) NOOP - #define DEBUG_ECHOPAIR_P(...) NOOP - #define DEBUG_ECHOPAIR_F(...) NOOP - #define DEBUG_ECHOPAIR_F_P(...) NOOP - #define DEBUG_ECHOLNPAIR(...) NOOP - #define DEBUG_ECHOLNPAIR_P(...) NOOP - #define DEBUG_ECHOLNPAIR_F(...) NOOP - #define DEBUG_ECHOLNPAIR_F_P(...) NOOP + #define DEBUG_ECHOPGM_P(...) NOOP + #define DEBUG_ECHOLNPGM_P(...) NOOP #define DEBUG_ECHO_MSG(...) NOOP #define DEBUG_ERROR_MSG(...) NOOP + #define DEBUG_WARN_MSG(...) NOOP + #define DEBUG_ECHO_TERNARY(...) NOOP #define DEBUG_EOL() NOOP #define DEBUG_FLUSH() NOOP #define DEBUG_POS(...) NOOP diff --git a/Marlin/src/core/debug_section.h b/Marlin/src/core/debug_section.h index 7f39bc7424..6319515b71 100644 --- a/Marlin/src/core/debug_section.h +++ b/Marlin/src/core/debug_section.h @@ -26,24 +26,21 @@ class SectionLog { public: - SectionLog(PGM_P const msg=nullptr, bool inbug=true) { - the_msg = msg; - if ((debug = inbug)) echo_msg(PSTR(">>>")); + SectionLog(FSTR_P const fmsg=nullptr, bool inbug=true) { + the_msg = fmsg; + if ((debug = inbug)) echo_msg(F(">>>")); } - ~SectionLog() { if (debug) echo_msg(PSTR("<<<")); } + ~SectionLog() { if (debug) echo_msg(F("<<<")); } private: - PGM_P the_msg; + FSTR_P the_msg; bool debug; - void echo_msg(PGM_P const pre) { - serialprintPGM(pre); - if (the_msg) { - SERIAL_CHAR(' '); - serialprintPGM(the_msg); - } + void echo_msg(FSTR_P const fpre) { + SERIAL_ECHO(fpre); + if (the_msg) SERIAL_ECHO(C(' '), the_msg); SERIAL_CHAR(' '); - print_xyz(current_position); + print_xyz(xyz_pos_t(current_position)); } }; diff --git a/Marlin/src/core/drivers.h b/Marlin/src/core/drivers.h index 3a0e620923..80980380a5 100644 --- a/Marlin/src/core/drivers.h +++ b/Marlin/src/core/drivers.h @@ -30,10 +30,6 @@ #define _A5984 0x5984 #define _DRV8825 0x8825 #define _LV8729 0x8729 -#define _L6470 0x6470 -#define _L6474 0x6474 -#define _L6480 0x6480 -#define _POWERSTEP01 0xF00D #define _TB6560 0x6560 #define _TB6600 0x6600 #define _TMC2100 0x2100 @@ -45,8 +41,7 @@ #define _TMC2208_STANDALONE 0x2208B #define _TMC2209 0x2209A #define _TMC2209_STANDALONE 0x2209B -#define _TMC26X 0x2600A -#define _TMC26X_STANDALONE 0x2600B +#define _TMC2240 0x2240A #define _TMC2660 0x2660A #define _TMC2660_STANDALONE 0x2660B #define _TMC5130 0x5130A @@ -60,12 +55,18 @@ #define AXIS_DRIVER_TYPE_X(T) _AXIS_DRIVER_TYPE(X,T) #define AXIS_DRIVER_TYPE_Y(T) _AXIS_DRIVER_TYPE(Y,T) #define AXIS_DRIVER_TYPE_Z(T) _AXIS_DRIVER_TYPE(Z,T) +#define AXIS_DRIVER_TYPE_I(T) _AXIS_DRIVER_TYPE(I,T) +#define AXIS_DRIVER_TYPE_J(T) _AXIS_DRIVER_TYPE(J,T) +#define AXIS_DRIVER_TYPE_K(T) _AXIS_DRIVER_TYPE(K,T) +#define AXIS_DRIVER_TYPE_U(T) _AXIS_DRIVER_TYPE(U,T) +#define AXIS_DRIVER_TYPE_V(T) _AXIS_DRIVER_TYPE(V,T) +#define AXIS_DRIVER_TYPE_W(T) _AXIS_DRIVER_TYPE(W,T) -#define AXIS_DRIVER_TYPE_X2(T) (EITHER(X_DUAL_STEPPER_DRIVERS, DUAL_X_CARRIAGE) && _AXIS_DRIVER_TYPE(X2,T)) -#define AXIS_DRIVER_TYPE_Y2(T) (ENABLED(Y_DUAL_STEPPER_DRIVERS) && _AXIS_DRIVER_TYPE(Y2,T)) -#define AXIS_DRIVER_TYPE_Z2(T) (NUM_Z_STEPPER_DRIVERS >= 2 && _AXIS_DRIVER_TYPE(Z2,T)) -#define AXIS_DRIVER_TYPE_Z3(T) (NUM_Z_STEPPER_DRIVERS >= 3 && _AXIS_DRIVER_TYPE(Z3,T)) -#define AXIS_DRIVER_TYPE_Z4(T) (NUM_Z_STEPPER_DRIVERS >= 4 && _AXIS_DRIVER_TYPE(Z4,T)) +#define AXIS_DRIVER_TYPE_X2(T) (HAS_X2_STEPPER && _AXIS_DRIVER_TYPE(X2,T)) +#define AXIS_DRIVER_TYPE_Y2(T) (HAS_Y2_STEPPER && _AXIS_DRIVER_TYPE(Y2,T)) +#define AXIS_DRIVER_TYPE_Z2(T) (NUM_Z_STEPPERS >= 2 && _AXIS_DRIVER_TYPE(Z2,T)) +#define AXIS_DRIVER_TYPE_Z3(T) (NUM_Z_STEPPERS >= 3 && _AXIS_DRIVER_TYPE(Z3,T)) +#define AXIS_DRIVER_TYPE_Z4(T) (NUM_Z_STEPPERS >= 4 && _AXIS_DRIVER_TYPE(Z4,T)) #define AXIS_DRIVER_TYPE_E(N,T) (E_STEPPERS > N && _AXIS_DRIVER_TYPE(E##N,T)) #define AXIS_DRIVER_TYPE_E0(T) AXIS_DRIVER_TYPE_E(0,T) @@ -83,6 +84,8 @@ #define HAS_E_DRIVER(T) (0 RREPEAT2(E_STEPPERS, _OR_ADTE, T)) #define HAS_DRIVER(T) ( AXIS_DRIVER_TYPE_X(T) || AXIS_DRIVER_TYPE_Y(T) || AXIS_DRIVER_TYPE_Z(T) \ + || AXIS_DRIVER_TYPE_I(T) || AXIS_DRIVER_TYPE_J(T) || AXIS_DRIVER_TYPE_K(T) \ + || AXIS_DRIVER_TYPE_U(T) || AXIS_DRIVER_TYPE_V(T) || AXIS_DRIVER_TYPE_W(T) \ || AXIS_DRIVER_TYPE_X2(T) || AXIS_DRIVER_TYPE_Y2(T) || AXIS_DRIVER_TYPE_Z2(T) \ || AXIS_DRIVER_TYPE_Z3(T) || AXIS_DRIVER_TYPE_Z4(T) || HAS_E_DRIVER(T) ) @@ -94,16 +97,17 @@ // 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(TMC26X_STANDALONE) || HAS_DRIVER(TMC2660_STANDALONE) \ + || HAS_DRIVER(TMC2660_STANDALONE) \ || HAS_DRIVER(TMC5130_STANDALONE) || HAS_DRIVER(TMC5160_STANDALONE) ) #define HAS_TRINAMIC_STANDALONE 1 #endif @@ -111,20 +115,33 @@ #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 + // 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,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) ) @@ -135,27 +152,32 @@ #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) #define E_AXIS_HAS(T) (0 _OR_EAH(0,T) _OR_EAH(1,T) _OR_EAH(2,T) _OR_EAH(3,T) _OR_EAH(4,T) _OR_EAH(5,T) _OR_EAH(6,T) _OR_EAH(7,T)) -#define ANY_AXIS_HAS(T) ( AXIS_HAS_##T(X) || AXIS_HAS_##T(Y) || AXIS_HAS_##T(Z) \ - || AXIS_HAS_##T(X2) || AXIS_HAS_##T(Y2) || AXIS_HAS_##T(Z2) \ - || AXIS_HAS_##T(Z3) || AXIS_HAS_##T(Z4) || E_AXIS_HAS(T) ) +#define ANY_AXIS_HAS(T) ( AXIS_HAS_##T(X) || AXIS_HAS_##T(X2) \ + || AXIS_HAS_##T(Y) || AXIS_HAS_##T(Y2) \ + || AXIS_HAS_##T(Z) || AXIS_HAS_##T(Z2) || AXIS_HAS_##T(Z3) || AXIS_HAS_##T(Z4) \ + || AXIS_HAS_##T(I) || AXIS_HAS_##T(J) || AXIS_HAS_##T(K) \ + || AXIS_HAS_##T(U) || AXIS_HAS_##T(V) || AXIS_HAS_##T(W) \ + || E_AXIS_HAS(T) ) #if ANY_AXIS_HAS(STEALTHCHOP) #define HAS_STEALTHCHOP 1 @@ -175,23 +197,22 @@ #if ANY_AXIS_HAS(SPI) #define HAS_TMC_SPI 1 #endif - -// -// TMC26XX Stepper Drivers -// -#if HAS_DRIVER(TMC26X) - #define HAS_TMC26X 1 +#if HAS_STALLGUARD || HAS_DRIVER(TMC2160_STANDALONE) || HAS_DRIVER(TMC2130_STANDALONE) \ + || HAS_DRIVER(TMC2209_STANDALONE) || HAS_DRIVER(TMC2660_STANDALONE) \ + || HAS_DRIVER(TMC5130_STANDALONE) || HAS_DRIVER(TMC5160_STANDALONE) + #define HAS_DIAG_PINS 1 #endif -// -// L64XX Stepper Drivers -// +// 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 -#if HAS_DRIVER(L6470) || HAS_DRIVER(L6474) || HAS_DRIVER(L6480) || HAS_DRIVER(POWERSTEP01) - #define HAS_L64XX 1 -#endif -#if HAS_L64XX && !HAS_DRIVER(L6474) - #define HAS_L64XX_NOT_L6474 1 -#endif - -#define AXIS_IS_L64XX(A) (AXIS_DRIVER_TYPE_##A(L6470) || AXIS_DRIVER_TYPE_##A(L6474) || AXIS_DRIVER_TYPE_##A(L6480) || AXIS_DRIVER_TYPE_##A(POWERSTEP01)) +#define _DRIVER_THRS(V) CAT(THRS_, V) +#define STEPPER_MAX_THRS(S) _DRIVER_THRS(S##_DRIVER_TYPE) diff --git a/Marlin/src/core/endianness.h b/Marlin/src/core/endianness.h new file mode 100644 index 0000000000..8fa8e40078 --- /dev/null +++ b/Marlin/src/core/endianness.h @@ -0,0 +1,76 @@ +/** + * 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 + * (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 "../core/types.h" +#include "../core/macros.h" + +#ifdef __cplusplus + +namespace Endianness { + static constexpr uint32_t _dword = 0x01020304; + static constexpr uint8_t _lsb = (const uint8_t&)_dword; + + static constexpr bool cpuIsLittleEndian = _lsb == 0x04; + static constexpr bool cpuIsBigEndian = _lsb == 0x01; + static_assert(cpuIsLittleEndian ^ cpuIsBigEndian, "Unknown CPU endianness"); + + // constexpr byte swapping for integral types + template static constexpr typename Private::enable_if::value, T>::type swap(T V, T swappedV=(T)0, size_t byteIndex=0) { + return byteIndex == sizeof(T) + ? swappedV + : swap((T)(V >> 8), (swappedV << 8) | (V & (T)0xFF), byteIndex + 1); + } + + // constexpr byte swapping for types derived from integral types (e.g. enums) + template static constexpr typename Private::enable_if< + Private::is_same::type>::value, T>::type swap(T V) { return (T)swap((uint16_t)V); } + template static constexpr typename Private::enable_if< + Private::is_same::type>::value, T>::type swap(T V) { return (T)swap((uint32_t)V); } + template static constexpr typename Private::enable_if< + Private::is_same::type>::value, T>::type swap(T V) { return (T)swap((uint64_t)V); } + + // Generic byte swapping + // CANNOT be used to initialize constexpr declarations + template static constexpr typename Private::enable_if::value && !Private::is_enum::value, T>::type swap(T V) { + union { + T val; + char byte[sizeof(T)]; + } src{}, dst{}; + + src.val = V; + for (uint8_t i = 0; i < sizeof(T); ++i) dst.byte[i] = src.byte[sizeof(T) - i - 1]; + return dst.val; + } + + // Convert to / from known endianness, depending on the host endianness + template static constexpr T toBE(T V) { return cpuIsLittleEndian ? swap(V) : V; } + template static constexpr T toLE(T V) { return cpuIsLittleEndian ? V : swap(V); } + template static constexpr T fromBE(T V) { return cpuIsLittleEndian ? swap(V) : V; } + template static constexpr T fromLE(T V) { return cpuIsLittleEndian ? V : swap(V); } + + // Reads a big/little endian from a pointer and converts it to the host endianness + template static constexpr T fromBE_P(void* V) { return fromBE(*(T*)V); } + template static constexpr T fromLE_P(void* V) { return fromLE(*(T*)V); } +}; + +#endif // __cplusplus diff --git a/Marlin/src/core/language.h b/Marlin/src/core/language.h index 60d9aa6b72..837f3885b9 100644 --- a/Marlin/src/core/language.h +++ b/Marlin/src/core/language.h @@ -48,13 +48,14 @@ // cz Czech // da Danish // de German -// el Greek -// el_gr Greek (Greece) +// el Greek (Greece) +// el_CY Greek (Cyprus) // en English // es Spanish // eu Basque-Euskera // fi Finnish // fr French +// fr_na French without accents (DWIN T5UID1 touchscreen) // gl Galician // hr Croatian // hu Hungarian @@ -68,6 +69,7 @@ // ro Romanian // ru Russian // sk Slovak +// sv Swedish // tr Turkish // uk Ukrainian // vi Vietnamese @@ -86,12 +88,9 @@ #undef MACHINE_NAME #define MACHINE_NAME DEFAULT_MACHINE_NAME #endif +#define MACHINE_NAME_SUBST TERN(CONFIGURABLE_MACHINE_NAME, "$", MACHINE_NAME) -#ifndef MACHINE_UUID - #define MACHINE_UUID DEFAULT_MACHINE_UUID -#endif - -#define MARLIN_WEBSITE_URL "https://marlinfw.org" +#define MARLIN_WEBSITE_URL "marlinfw.org" //#if !defined(STRING_SPLASH_LINE3) && defined(WEBSITE_URL) // #define STRING_SPLASH_LINE3 WEBSITE_URL @@ -104,6 +103,7 @@ #define STR_ENQUEUEING "enqueueing \"" #define STR_POWERUP "PowerUp" +#define STR_POWEROFF "PowerOff" #define STR_EXTERNAL_RESET " External Reset" #define STR_BROWNOUT_RESET " Brown out Reset" #define STR_WATCHDOG_RESET " Watchdog Reset" @@ -129,32 +129,19 @@ #define STR_COUNT_A " Count A:" #define STR_WATCHDOG_FIRED "Watchdog timeout. Reset required." #define STR_ERR_KILLED "Printer halted. kill() called!" +#define STR_FLOWMETER_FAULT "Coolant flow fault. Flowmeter safety is active. Attention required." #define STR_ERR_STOPPED "Printer stopped due to errors. Fix the error and use M999 to restart. (Temperature is reset. Set it after restarting)" +#define STR_ERR_SERIAL_MISMATCH "Serial status mismatch" #define STR_BUSY_PROCESSING "busy: processing" #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: " -#define STR_X_MIN "x_min" -#define STR_X_MAX "x_max" -#define STR_X2_MIN "x2_min" -#define STR_X2_MAX "x2_max" -#define STR_Y_MIN "y_min" -#define STR_Y_MAX "y_max" -#define STR_Y2_MIN "y2_min" -#define STR_Y2_MAX "y2_max" -#define STR_Z_MIN "z_min" -#define STR_Z_MAX "z_max" -#define STR_Z2_MIN "z2_min" -#define STR_Z2_MAX "z2_max" -#define STR_Z3_MIN "z3_min" -#define STR_Z3_MAX "z3_max" -#define STR_Z4_MIN "z4_min" -#define STR_Z4_MAX "z4_max" -#define STR_Z_PROBE "z_probe" -#define STR_FILAMENT_RUNOUT_SENSOR "filament" +#define STR_ERR_FANSPEED "Fan speed E" + #define STR_PROBE_OFFSET "Probe Offset" #define STR_SKEW_MIN "min_skew_factor: " #define STR_SKEW_MAX "max_skew_factor: " @@ -165,28 +152,28 @@ #define STR_ERR_ARC_ARGS "G2/G3 bad parameters" #define STR_ERR_PROTECTED_PIN "Protected Pin" #define STR_ERR_M420_FAILED "Failed to enable Bed Leveling" -#define STR_ERR_M428_TOO_FAR "Too far from reference point" +#define STR_ERR_M428_TOO_FAR "Too far from MIN/MAX" #define STR_ERR_M303_DISABLED "PIDTEMP disabled" #define STR_M119_REPORT "Reporting endstop status" #define STR_ON "ON" #define STR_OFF "OFF" #define STR_ENDSTOP_HIT "TRIGGERED" #define STR_ENDSTOP_OPEN "open" -#define STR_HOTEND_OFFSET "Hotend offsets:" #define STR_DUPLICATION_MODE "Duplication mode: " -#define STR_SOFT_ENDSTOPS "Soft endstops: " #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." #define STR_SD_CANT_OPEN_SUBDIR "Cannot open subdir " #define STR_SD_INIT_FAIL "No SD card" #define STR_SD_VOL_INIT_FAIL "volume.init failed" #define STR_SD_OPENROOT_FAIL "openRoot failed" #define STR_SD_CARD_OK "SD card ok" +#define STR_SD_CARD_RELEASED "SD card released" #define STR_SD_WORKDIR_FAIL "workDir open failed" #define STR_SD_OPEN_FILE_FAIL "open failed, File: " #define STR_SD_FILE_OPENED "File opened: " @@ -204,6 +191,8 @@ #define STR_ERR_LONG_EXTRUDE_STOP " too long extrusion prevented" #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" @@ -212,16 +201,22 @@ #define STR_FILAMENT_CHANGE_INSERT_M108 "Insert filament and send M108" #define STR_FILAMENT_CHANGE_WAIT_M108 "Send M108 to resume" -#define STR_STOP_BLTOUCH "!! STOP called because of BLTouch error - restart with M999" -#define STR_STOP_UNHOMED "!! STOP called because of unhomed error - restart with M999" -#define STR_KILL_INACTIVE_TIME "!! KILL caused by too much inactive time - current command: " -#define STR_KILL_BUTTON "!! KILL caused by KILL button/pin" +#define STR_STOP_PRE "!! STOP called because of " +#define STR_STOP_POST " error - restart with M999" +#define STR_STOP_BLTOUCH "BLTouch" +#define STR_STOP_UNHOMED "unhomed" +#define STR_KILL_PRE "!! KILL caused by " +#define STR_KILL_INACTIVE_TIME "too much inactive time - current command: " +#define STR_KILL_BUTTON "KILL button/pin" // temperature.cpp strings -#define STR_PID_AUTOTUNE_START "PID Autotune start" -#define STR_PID_BAD_EXTRUDER_NUM "PID Autotune failed! Bad extruder number" -#define STR_PID_TEMP_TOO_HIGH "PID Autotune failed! Temperature too high" -#define STR_PID_TIMEOUT "PID Autotune failed! timeout" +#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" +#define STR_PID_TEMP_TOO_HIGH " failed! Temperature too high" +#define STR_PID_TIMEOUT " failed! timeout" #define STR_BIAS " bias: " #define STR_D_COLON " d: " #define STR_T_MIN " min: " @@ -232,23 +227,39 @@ #define STR_KP " Kp: " #define STR_KI " Ki: " #define STR_KD " Kd: " -#define STR_PID_AUTOTUNE_FINISHED "PID Autotune finished! Put the last Kp, Ki and Kd constants from below into Configuration.h" +#define STR_PID_AUTOTUNE_FINISHED " finished! Put the last Kp, Ki and Kd constants from below into Configuration.h" #define STR_PID_DEBUG " PID_DEBUG " #define STR_PID_DEBUG_INPUT ": Input " #define STR_PID_DEBUG_OUTPUT " Output " -#define STR_PID_DEBUG_PTERM " pTerm " -#define STR_PID_DEBUG_ITERM " iTerm " -#define STR_PID_DEBUG_DTERM " dTerm " -#define STR_PID_DEBUG_CTERM " cTerm " #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" +#define STR_MPC_COOLING_TO_AMBIENT "Cooling to ambient" +#define STR_MPC_HEATING_PAST_200 "Heating to over 200C" +#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" +#define STR_MOTHERBOARD "motherboard" +#define STR_SOC "soc" +#define STR_PROBE "probe" +#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 ")" #define STR_REDUNDANCY "Heater switched off. Temperature difference between temp sensors is too high !" #define STR_T_HEATING_FAILED "Heating failed" #define STR_T_THERMAL_RUNAWAY "Thermal Runaway" +#define STR_T_THERMAL_MALFUNCTION "Thermal Malfunction" #define STR_T_MAXTEMP "MAXTEMP triggered" #define STR_T_MINTEMP "MINTEMP triggered" #define STR_ERR_PROBING_FAILED "Probing Failed" @@ -262,8 +273,9 @@ #define STR_DEBUG_ERRORS "ERRORS" #define STR_DEBUG_DRYRUN "DRYRUN" #define STR_DEBUG_COMMUNICATION "COMMUNICATION" -#define STR_DEBUG_LEVELING "LEVELING" +#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" @@ -271,14 +283,71 @@ #define STR_REMINDER_SAVE_SETTINGS "Remember to save!" #define STR_PASSWORD_SET "Password is " -// LCD Menu Messages - -#define LANGUAGE_DATA_INCL_(M) STRINGIFY_(fontdata/langdata_##M.h) -#define LANGUAGE_DATA_INCL(M) LANGUAGE_DATA_INCL_(M) - -#define LANGUAGE_INCL_(M) STRINGIFY_(../lcd/language/language_##M.h) -#define LANGUAGE_INCL(M) LANGUAGE_INCL_(M) +// Settings Report Strings +#define STR_Z_AUTO_ALIGN "Z Auto-Align" +#define STR_BACKLASH_COMPENSATION "Backlash compensation" +#define STR_FT_MOTION "Fixed-Time Motion" +#define STR_S_SEG_PER_SEC "S" +#define STR_DELTA_SETTINGS "Delta (L R H S XYZ ABC)" +#define STR_SCARA_SETTINGS "SCARA" +#define STR_POLAR_SETTINGS "Polar" +#define STR_POLARGRAPH_SETTINGS "Polargraph" +#define STR_SCARA_P_T_Z "P T Z" +#define STR_ENDSTOP_ADJUSTMENT "Endstop adjustment" +#define STR_SKEW_FACTOR "Skew Factor" +#define STR_FILAMENT_SETTINGS "Filament settings" +#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" +#define STR_STEPS_PER_UNIT "Steps per unit" +#define STR_LINEAR_ADVANCE "Linear Advance" +#define STR_NONLINEAR_EXTRUSION "Nonlinear Extrusion" +#define STR_CONTROLLER_FAN "Controller Fan" +#define STR_STEPPER_MOTOR_CURRENTS "Stepper motor currents" +#define STR_RETRACT_S_F_Z "Retract (S F Z)" +#define STR_RECOVER_S_F "Recover (S F)" +#define STR_AUTO_RETRACT_S "Auto-Retract (S)" +#define STR_FILAMENT_LOAD_UNLOAD "Filament load/unload" +#define STR_POWER_LOSS_RECOVERY "Power-loss recovery" +#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_HOME_OFFSET "Home offset" +#define STR_SOFT_ENDSTOPS "Soft endstops" +#define STR_MATERIAL_HEATUP "Material heatup parameters" +#define STR_LCD_CONTRAST "LCD Contrast" +#define STR_LCD_BRIGHTNESS "LCD Brightness" +#define STR_DISPLAY_SLEEP "Display Sleep" +#define STR_UI_LANGUAGE "UI Language" +#define STR_Z_PROBE_OFFSET "Z-Probe Offset" +#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" @@ -288,22 +357,198 @@ #define STR_B "B" #define STR_C "C" #else - #define STR_A "X" - #define STR_B "Y" - #define STR_C "Z" + #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 CORE_IS_XY || CORE_IS_XZ + #define STEPPER_A_NAME 'A' +#else + #define STEPPER_A_NAME 'X' +#endif +#if CORE_IS_XY || CORE_IS_YZ + #define STEPPER_B_NAME 'B' +#else + #define STEPPER_B_NAME 'Y' +#endif +#if CORE_IS_XZ || CORE_IS_YZ + #define STEPPER_C_NAME 'C' +#else + #define STEPPER_C_NAME 'Z' #endif -#define STR_X2 "X2" -#define STR_Y2 "Y2" -#define STR_Z2 "Z2" -#define STR_Z3 "Z3" -#define STR_Z4 "Z4" -#define LCD_STR_A STR_A -#define LCD_STR_B STR_B -#define LCD_STR_C STR_C -#define LCD_STR_E STR_E +// +// Endstop Names used by Endstops::report_states +// +#if HAS_X_AXIS + #define STR_X_MIN "x_min" + #define STR_X_MAX "x_max" + #define STR_X2_MIN "x2_min" + #define STR_X2_MAX "x2_max" +#endif -#if EITHER(HAS_MARLINUI_HD44780, IS_TFTGLCD_PANEL) +#if HAS_Y_AXIS + #define STR_Y_MIN "y_min" + #define STR_Y_MAX "y_max" + #define STR_Y2_MIN "y2_min" + #define STR_Y2_MAX "y2_max" +#endif + +#if HAS_Z_AXIS + #define STR_Z_MIN "z_min" + #define STR_Z_MAX "z_max" + #define STR_Z2_MIN "z2_min" + #define STR_Z2_MAX "z2_max" + #define STR_Z3_MIN "z3_min" + #define STR_Z3_MAX "z3_max" + #define STR_Z4_MIN "z4_min" + #define STR_Z4_MAX "z4_max" +#endif + +#define STR_Z_PROBE "z_probe" +#define STR_PROBE_EN "probe_en" +#define STR_FILAMENT "filament" +#define STR_CALIBRATION "calibration" + +// Extra Axis and Endstop Names +#if HAS_I_AXIS + #if AXIS4_NAME == 'A' + #define STR_I "A" + #define STR_I_MIN "a_min" + #define STR_I_MAX "a_max" + #elif AXIS4_NAME == 'B' + #define STR_I "B" + #define STR_I_MIN "b_min" + #define STR_I_MAX "b_max" + #elif AXIS4_NAME == 'C' + #define STR_I "C" + #define STR_I_MIN "c_min" + #define STR_I_MAX "c_max" + #elif AXIS4_NAME == 'U' + #define STR_I "U" + #define STR_I_MIN "u_min" + #define STR_I_MAX "u_max" + #elif AXIS4_NAME == 'V' + #define STR_I "V" + #define STR_I_MIN "v_min" + #define STR_I_MAX "v_max" + #elif AXIS4_NAME == 'W' + #define STR_I "W" + #define STR_I_MIN "w_min" + #define STR_I_MAX "w_max" + #else + #error "AXIS4_NAME can only be one of 'A', 'B', 'C', 'U', 'V', or 'W'." + #endif +#else + #define STR_I "" +#endif + +#if HAS_J_AXIS + #if AXIS5_NAME == 'B' + #define STR_J "B" + #define STR_J_MIN "b_min" + #define STR_J_MAX "b_max" + #elif AXIS5_NAME == 'C' + #define STR_J "C" + #define STR_J_MIN "c_min" + #define STR_J_MAX "c_max" + #elif AXIS5_NAME == 'U' + #define STR_J "U" + #define STR_J_MIN "u_min" + #define STR_J_MAX "u_max" + #elif AXIS5_NAME == 'V' + #define STR_J "V" + #define STR_J_MIN "v_min" + #define STR_J_MAX "v_max" + #elif AXIS5_NAME == 'W' + #define STR_J "W" + #define STR_J_MIN "w_min" + #define STR_J_MAX "w_max" + #else + #error "AXIS5_NAME can only be one of 'B', 'C', 'U', 'V', or 'W'." + #endif +#else + #define STR_J "" +#endif + +#if HAS_K_AXIS + #if AXIS6_NAME == 'C' + #define STR_K "C" + #define STR_K_MIN "c_min" + #define STR_K_MAX "c_max" + #elif AXIS6_NAME == 'U' + #define STR_K "U" + #define STR_K_MIN "u_min" + #define STR_K_MAX "u_max" + #elif AXIS6_NAME == 'V' + #define STR_K "V" + #define STR_K_MIN "v_min" + #define STR_K_MAX "v_max" + #elif AXIS6_NAME == 'W' + #define STR_K "W" + #define STR_K_MIN "w_min" + #define STR_K_MAX "w_max" + #else + #error "AXIS6_NAME can only be one of 'C', 'U', 'V', or 'W'." + #endif +#else + #define STR_K "" +#endif + +#if HAS_U_AXIS + #if AXIS7_NAME == 'U' + #define STR_U "U" + #define STR_U_MIN "u_min" + #define STR_U_MAX "u_max" + #elif AXIS7_NAME == 'V' + #define STR_U "V" + #define STR_U_MIN "v_min" + #define STR_U_MAX "v_max" + #elif AXIS7_NAME == 'W' + #define STR_U "W" + #define STR_U_MIN "w_min" + #define STR_U_MAX "w_max" + #else + #error "AXIS7_NAME can only be one of 'U', 'V', or 'W'." + #endif +#else + #define STR_U "" +#endif + +#if HAS_V_AXIS + #if AXIS8_NAME == 'V' + #define STR_V "V" + #define STR_V_MIN "v_min" + #define STR_V_MAX "v_max" + #elif AXIS8_NAME == 'W' + #define STR_V "W" + #define STR_V_MIN "w_min" + #define STR_V_MAX "w_max" + #else + #error "AXIS8_NAME can only be one of 'V', or 'W'." + #endif +#else + #define STR_V "" +#endif + +#if HAS_W_AXIS + #if AXIS9_NAME == 'W' + #define STR_W "W" + #define STR_W_MIN "w_min" + #define STR_W_MAX "w_max" + #else + #error "AXIS9_NAME can only be 'W'." + #endif +#else + #define STR_W "" +#endif + +#if ANY(HAS_MARLINUI_HD44780, IS_TFTGLCD_PANEL) // Custom characters defined in the first 8 characters of the LCD #define LCD_STR_BEDTEMP "\x00" // Print only as a char. This will have 'unexpected' results when used in a string! @@ -351,34 +596,46 @@ */ #if ENABLED(NUMBER_TOOLS_FROM_0) #define LCD_FIRST_TOOL 0 - #define LCD_STR_N0 "0" - #define LCD_STR_N1 "1" - #define LCD_STR_N2 "2" - #define LCD_STR_N3 "3" - #define LCD_STR_N4 "4" - #define LCD_STR_N5 "5" - #define LCD_STR_N6 "6" - #define LCD_STR_N7 "7" + #define STR_N0 "0" + #define STR_N1 "1" + #define STR_N2 "2" + #define STR_N3 "3" + #define STR_N4 "4" + #define STR_N5 "5" + #define STR_N6 "6" + #define STR_N7 "7" #else #define LCD_FIRST_TOOL 1 - #define LCD_STR_N0 "1" - #define LCD_STR_N1 "2" - #define LCD_STR_N2 "3" - #define LCD_STR_N3 "4" - #define LCD_STR_N4 "5" - #define LCD_STR_N5 "6" - #define LCD_STR_N6 "7" - #define LCD_STR_N7 "8" + #define STR_N0 "1" + #define STR_N1 "2" + #define STR_N2 "3" + #define STR_N3 "4" + #define STR_N4 "5" + #define STR_N5 "6" + #define STR_N6 "7" + #define STR_N7 "8" #endif -#define LCD_STR_E0 "E" LCD_STR_N0 -#define LCD_STR_E1 "E" LCD_STR_N1 -#define LCD_STR_E2 "E" LCD_STR_N2 -#define LCD_STR_E3 "E" LCD_STR_N3 -#define LCD_STR_E4 "E" LCD_STR_N4 -#define LCD_STR_E5 "E" LCD_STR_N5 -#define LCD_STR_E6 "E" LCD_STR_N6 -#define LCD_STR_E7 "E" LCD_STR_N7 +#define STR_E0 STR_E STR_N0 +#define STR_E1 STR_E STR_N1 +#define STR_E2 STR_E STR_N2 +#define STR_E3 STR_E STR_N3 +#define STR_E4 STR_E STR_N4 +#define STR_E5 STR_E STR_N5 +#define STR_E6 STR_E STR_N6 +#define STR_E7 STR_E STR_N7 + +// Include localized LCD Menu Messages + +#define LANGUAGE_DATA_INCL_(M) STRINGIFY_(fontdata/langdata_##M.h) +#define LANGUAGE_DATA_INCL(M) LANGUAGE_DATA_INCL_(M) + +#define LANGUAGE_INCL_(M) STRINGIFY_(../lcd/language/language_##M.h) +#define LANGUAGE_INCL(M) LANGUAGE_INCL_(M) + +// Use superscripts, if possible. Evaluated at point of use. +#define SUPERSCRIPT_TWO TERN(NOT_EXTENDED_ISO10646_1_5X7, "^2", "Β²") +#define SUPERSCRIPT_THREE TERN(NOT_EXTENDED_ISO10646_1_5X7, "^3", "Β³") #include "multi_language.h" // Allow multiple languages diff --git a/Marlin/src/core/macros.h b/Marlin/src/core/macros.h index 21bb32c4cf..4f6b1df98b 100644 --- a/Marlin/src/core/macros.h +++ b/Marlin/src/core/macros.h @@ -21,51 +21,27 @@ */ #pragma once -#if !defined(__has_include) +#ifndef __has_include #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 _XMIN_ 100 -#define _YMIN_ 200 -#define _ZMIN_ 300 -#define _XMAX_ 101 -#define _YMAX_ 201 -#define _ZMAX_ 301 -#define _XDIAG_ 102 -#define _YDIAG_ 202 -#define _ZDIAG_ 302 -#define _E0DIAG_ 400 -#define _E1DIAG_ 401 -#define _E2DIAG_ 402 -#define _E3DIAG_ 403 -#define _E4DIAG_ 404 -#define _E5DIAG_ 405 -#define _E6DIAG_ 406 -#define _E7DIAG_ 407 - #define _FORCE_INLINE_ __attribute__((__always_inline__)) __inline__ #define FORCE_INLINE __attribute__((always_inline)) inline +#define NO_INLINE __attribute__((noinline)) #define _UNUSED __attribute__((unused)) -#define _O0 __attribute__((optimize("O0"))) -#define _Os __attribute__((optimize("Os"))) -#define _O1 __attribute__((optimize("O1"))) -#define _O2 __attribute__((optimize("O2"))) -#define _O3 __attribute__((optimize("O3"))) +#define __O0 __attribute__((optimize("O0"))) // No optimization and less debug info +#define __Og __attribute__((optimize("Og"))) // Optimize the debugging experience +#define __Os __attribute__((optimize("Os"))) // Optimize for size +#define __O1 __attribute__((optimize("O1"))) // Try to reduce size and cycles; nothing that takes a lot of time to compile +#define __O2 __attribute__((optimize("O2"))) // Optimize even more +#define __O3 __attribute__((optimize("O3"))) // Optimize yet more + +#define IS_CONSTEXPR(...) __builtin_constant_p(__VA_ARGS__) // Only valid solution with C++14. Should use std::is_constant_evaluated() in C++20 instead #ifndef UNUSED - #if defined(ARDUINO_ARCH_STM32) && !defined(STM32GENERIC) - #define UNUSED(X) (void)X - #else - #define UNUSED(x) ((void)(x)) - #endif + #define UNUSED(x) ((void)(x)) #endif // Clock speed factors @@ -73,60 +49,59 @@ #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 - #define SBI(A,B) (A |= (1 << (B))) + #define SBI(A,B) (A |= _BV(B)) #endif - #ifndef CBI - #define CBI(A,B) (A &= ~(1 << (B))) + #define CBI(A,B) (A &= ~_BV(B)) #endif - #define TBI(N,B) (N ^= _BV(B)) - #define _BV32(b) (1UL << (b)) #define TEST32(n,b) !!((n)&_BV32(b)) #define SBI32(n,b) (n |= _BV32(b)) #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 // C++11 solution that is standards compliant. - template static inline constexpr void NOLESS(V& v, const N n) { + template static constexpr void NOLESS(V& v, const N n) { if (n > v) v = n; } - template static inline constexpr void NOMORE(V& v, const N n) { + template static constexpr void NOMORE(V& v, const N n) { if (n < v) v = n; } - template static inline constexpr void LIMIT(V& v, const N1 n1, const N2 n2) { + template static constexpr void LIMIT(V& v, const N1 n1, const N2 n2) { if (n1 > v) v = n1; else if (n2 < v) v = n2; } @@ -135,27 +110,27 @@ #define NOLESS(v, n) \ do{ \ - __typeof__(n) _n = (n); \ + __typeof__(v) _n = (n); \ if (_n > v) v = _n; \ }while(0) #define NOMORE(v, n) \ do{ \ - __typeof__(n) _n = (n); \ + __typeof__(v) _n = (n); \ if (_n < v) v = _n; \ }while(0) #define LIMIT(v, n1, n2) \ do{ \ - __typeof__(n1) _n1 = (n1); \ - __typeof__(n2) _n2 = (n2); \ + __typeof__(v) _n1 = (n1); \ + __typeof__(v) _n2 = (n2); \ if (_n1 > v) v = _n1; \ else if (_n2 < v) v = _n2; \ }while(0) #endif -// Macros to chain up to 12 conditions +// Macros to chain up to 40 conditions #define _DO_1(W,C,A) (_##W##_1(A)) #define _DO_2(W,C,A,B) (_##W##_1(A) C _##W##_1(B)) #define _DO_3(W,C,A,V...) (_##W##_1(A) C _DO_2(W,C,V)) @@ -168,38 +143,95 @@ #define _DO_10(W,C,A,V...) (_##W##_1(A) C _DO_9(W,C,V)) #define _DO_11(W,C,A,V...) (_##W##_1(A) C _DO_10(W,C,V)) #define _DO_12(W,C,A,V...) (_##W##_1(A) C _DO_11(W,C,V)) +#define _DO_13(W,C,A,V...) (_##W##_1(A) C _DO_12(W,C,V)) +#define _DO_14(W,C,A,V...) (_##W##_1(A) C _DO_13(W,C,V)) +#define _DO_15(W,C,A,V...) (_##W##_1(A) C _DO_14(W,C,V)) +#define _DO_16(W,C,A,V...) (_##W##_1(A) C _DO_15(W,C,V)) +#define _DO_17(W,C,A,V...) (_##W##_1(A) C _DO_16(W,C,V)) +#define _DO_18(W,C,A,V...) (_##W##_1(A) C _DO_17(W,C,V)) +#define _DO_19(W,C,A,V...) (_##W##_1(A) C _DO_18(W,C,V)) +#define _DO_20(W,C,A,V...) (_##W##_1(A) C _DO_19(W,C,V)) +#define _DO_21(W,C,A,V...) (_##W##_1(A) C _DO_20(W,C,V)) +#define _DO_22(W,C,A,V...) (_##W##_1(A) C _DO_21(W,C,V)) +#define _DO_23(W,C,A,V...) (_##W##_1(A) C _DO_22(W,C,V)) +#define _DO_24(W,C,A,V...) (_##W##_1(A) C _DO_23(W,C,V)) +#define _DO_25(W,C,A,V...) (_##W##_1(A) C _DO_24(W,C,V)) +#define _DO_26(W,C,A,V...) (_##W##_1(A) C _DO_25(W,C,V)) +#define _DO_27(W,C,A,V...) (_##W##_1(A) C _DO_26(W,C,V)) +#define _DO_28(W,C,A,V...) (_##W##_1(A) C _DO_27(W,C,V)) +#define _DO_29(W,C,A,V...) (_##W##_1(A) C _DO_28(W,C,V)) +#define _DO_30(W,C,A,V...) (_##W##_1(A) C _DO_29(W,C,V)) +#define _DO_31(W,C,A,V...) (_##W##_1(A) C _DO_30(W,C,V)) +#define _DO_32(W,C,A,V...) (_##W##_1(A) C _DO_31(W,C,V)) +#define _DO_33(W,C,A,V...) (_##W##_1(A) C _DO_32(W,C,V)) +#define _DO_34(W,C,A,V...) (_##W##_1(A) C _DO_33(W,C,V)) +#define _DO_35(W,C,A,V...) (_##W##_1(A) C _DO_34(W,C,V)) +#define _DO_36(W,C,A,V...) (_##W##_1(A) C _DO_35(W,C,V)) +#define _DO_37(W,C,A,V...) (_##W##_1(A) C _DO_36(W,C,V)) +#define _DO_38(W,C,A,V...) (_##W##_1(A) C _DO_37(W,C,V)) +#define _DO_39(W,C,A,V...) (_##W##_1(A) C _DO_38(W,C,V)) +#define _DO_40(W,C,A,V...) (_##W##_1(A) C _DO_39(W,C,V)) #define __DO_N(W,C,N,V...) _DO_##N(W,C,V) #define _DO_N(W,C,N,V...) __DO_N(W,C,N,V) -#define DO(W,C,V...) _DO_N(W,C,NUM_ARGS(V),V) +#define DO(W,C,V...) (_DO_N(W,C,NUM_ARGS(V),V)) -// Macros to support option testing +// Concatenate symbol names, without or with pre-expansion #define _CAT(a,V...) a##V #define CAT(a,V...) _CAT(a,V) +// Recognize "true" values: blank, 1, 0x1, true #define _ISENA_ ~,1 #define _ISENA_1 ~,1 #define _ISENA_0x1 ~,1 #define _ISENA_true ~,1 #define _ISENA(V...) IS_PROBE(V) +// Macros to evaluate simple option switches #define _ENA_1(O) _ISENA(CAT(_IS,CAT(ENA_, O))) #define _DIS_1(O) NOT(_ENA_1(O)) #define ENABLED(V...) DO(ENA,&&,V) #define DISABLED(V...) DO(DIS,&&,V) +#define ANY(V...) !DISABLED(V) +#define ALL(V...) ENABLED(V) +#define NONE(V...) DISABLED(V) +#define COUNT_ENABLED(V...) DO(ENA,+,V) +#define MANY(V...) (COUNT_ENABLED(V) > 1) -#define TERN(O,A,B) _TERN(_ENA_1(O),B,A) // OPTION converted to '0' or '1' -#define TERN0(O,A) _TERN(_ENA_1(O),0,A) // OPTION converted to A or '0' -#define TERN1(O,A) _TERN(_ENA_1(O),1,A) // OPTION converted to A or '1' -#define TERN_(O,A) _TERN(_ENA_1(O),,A) // OPTION converted to A or '' +// Ternary pre-compiler macros conceal non-emitted content from the compiler +#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(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) -#define ANY(V...) !DISABLED(V) -#define NONE(V...) DISABLED(V) -#define ALL(V...) ENABLED(V) -#define BOTH(V1,V2) ALL(V1,V2) -#define EITHER(V1,V2) ANY(V1,V2) +// "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,DEFER(_OPTITEM)(A)) +#define _OPTARG(A...) , A +#define OPTARG(O,A...) TERN_(O,DEFER(_OPTARG)(A)) +#define _OPTCODE(A) 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. +#define PLUS_TERN0(O,A) _TERN(_ENA_1(O),,+ (A)) // OPTION ? '+ (A)' : '' +#define MINUS_TERN0(O,A) _TERN(_ENA_1(O),,- (A)) // OPTION ? '- (A)' : '' +#define MUL_TERN1(O,A) _TERN(_ENA_1(O),,* (A)) // OPTION ? '* (A)' : '' +#define DIV_TERN1(O,A) _TERN(_ENA_1(O),,/ (A)) // OPTION ? '/ (A)' : '' +#define SUM_TERN(O,B,A) ((B) PLUS_TERN0(O,A)) // ((B) (OPTION ? '+ (A)' : '')) +#define DIFF_TERN(O,B,A) ((B) MINUS_TERN0(O,A)) // ((B) (OPTION ? '- (A)' : '')) +#define MUL_TERN(O,B,A) ((B) MUL_TERN1(O,A)) // ((B) (OPTION ? '* (A)' : '')) +#define DIV_TERN(O,B,A) ((B) DIV_TERN1(O,A)) // ((B) (OPTION ? '/ (A)' : '')) // Macros to support pins/buttons exist testing #define PIN_EXISTS(PN) (defined(PN##_PIN) && PN##_PIN >= 0) @@ -212,20 +244,83 @@ #define BUTTONS_EXIST(V...) DO(BTNEX,&&,V) #define ANY_BUTTON(V...) DO(BTNEX,||,V) +// Value helper macros #define WITHIN(N,L,H) ((N) >= (L) && (N) <= (H)) +#define ISEOL(C) ((C) == '\n' || (C) == '\r') #define NUMERIC(a) WITHIN(a, '0', '9') #define DECIMAL(a) (NUMERIC(a) || a == '.') #define HEXCHR(a) (NUMERIC(a) ? (a) - '0' : WITHIN(a, 'a', 'f') ? ((a) - 'a' + 10) : WITHIN(a, 'A', 'F') ? ((a) - 'A' + 10) : -1) #define NUMERIC_SIGNED(a) (NUMERIC(a) || (a) == '-' || (a) == '+') #define DECIMAL_SIGNED(a) (DECIMAL(a) || (a) == '-' || (a) == '+') + +// Array shorthand #define COUNT(a) (sizeof(a)/sizeof(*a)) -#define ZERO(a) memset(a,0,sizeof(a)) +#define ZERO(a) memset((void*)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))); \ }while(0) -// Macros for initializing arrays +// Expansion of some code +#define CODE_16( A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,...) A; B; C; D; E; F; G; H; I; J; K; L; M; N; O; P +#define CODE_15( A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,...) A; B; C; D; E; F; G; H; I; J; K; L; M; N; O +#define CODE_14( A,B,C,D,E,F,G,H,I,J,K,L,M,N,...) A; B; C; D; E; F; G; H; I; J; K; L; M; N +#define CODE_13( A,B,C,D,E,F,G,H,I,J,K,L,M,...) A; B; C; D; E; F; G; H; I; J; K; L; M +#define CODE_12( A,B,C,D,E,F,G,H,I,J,K,L,...) A; B; C; D; E; F; G; H; I; J; K; L +#define CODE_11( A,B,C,D,E,F,G,H,I,J,K,...) A; B; C; D; E; F; G; H; I; J; K +#define CODE_10( A,B,C,D,E,F,G,H,I,J,...) A; B; C; D; E; F; G; H; I; J +#define CODE_9( A,B,C,D,E,F,G,H,I,...) A; B; C; D; E; F; G; H; I +#define CODE_8( A,B,C,D,E,F,G,H,...) A; B; C; D; E; F; G; H +#define CODE_7( A,B,C,D,E,F,G,...) A; B; C; D; E; F; G +#define CODE_6( A,B,C,D,E,F,...) A; B; C; D; E; F +#define CODE_5( A,B,C,D,E,...) A; B; C; D; E +#define CODE_4( A,B,C,D,...) A; B; C; D +#define CODE_3( A,B,C,...) A; B; C +#define CODE_2( A,B,...) A; B +#define CODE_1( A,...) A +#define CODE_0(...) +#define _CODE_N(N,V...) CODE_##N(V) +#define CODE_N(N,V...) _CODE_N(N,V) + +// Expansion of some non-delimited content +#define GANG_16(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,...) A B C D E F G H I J K L M N O P +#define GANG_15(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,...) A B C D E F G H I J K L M N O +#define GANG_14(A,B,C,D,E,F,G,H,I,J,K,L,M,N,...) A B C D E F G H I J K L M N +#define GANG_13(A,B,C,D,E,F,G,H,I,J,K,L,M...) A B C D E F G H I J K L M +#define GANG_12(A,B,C,D,E,F,G,H,I,J,K,L...) A B C D E F G H I J K L +#define GANG_11(A,B,C,D,E,F,G,H,I,J,K,...) A B C D E F G H I J K +#define GANG_10(A,B,C,D,E,F,G,H,I,J,...) A B C D E F G H I J +#define GANG_9( A,B,C,D,E,F,G,H,I,...) A B C D E F G H I +#define GANG_8( A,B,C,D,E,F,G,H,...) A B C D E F G H +#define GANG_7( A,B,C,D,E,F,G,...) A B C D E F G +#define GANG_6( A,B,C,D,E,F,...) A B C D E F +#define GANG_5( A,B,C,D,E,...) A B C D E +#define GANG_4( A,B,C,D,...) A B C D +#define GANG_3( A,B,C,...) A B C +#define GANG_2( A,B,...) A B +#define GANG_1( A,...) A +#define GANG_0(...) +#define _GANG_N(N,V...) GANG_##N(V) +#define GANG_N(N,V...) _GANG_N(N,V) +#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 +#define LIST_23(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,...) A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W +#define LIST_22(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,...) A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V +#define LIST_21(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,...) A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U +#define LIST_20(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,...) A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T +#define LIST_19(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,...) A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S +#define LIST_18(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,...) A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R +#define LIST_17(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,...) A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q #define LIST_16(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,...) A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P #define LIST_15(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,...) A,B,C,D,E,F,G,H,I,J,K,L,M,N,O #define LIST_14(A,B,C,D,E,F,G,H,I,J,K,L,M,N,...) A,B,C,D,E,F,G,H,I,J,K,L,M,N @@ -242,26 +337,24 @@ #define LIST_3( A,B,C,...) A,B,C #define LIST_2( A,B,...) A,B #define LIST_1( A,...) A +#define LIST_0(...) #define _LIST_N(N,V...) LIST_##N(V) #define LIST_N(N,V...) _LIST_N(N,V) +#define LIST_N_1(N,K) _LIST_N(N,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K) #define ARRAY_N(N,V...) { _LIST_N(N,V) } +#define ARRAY_N_1(N,K) { LIST_N_1(N,K) } #define _JOIN_1(O) (O) #define JOIN_N(N,C,V...) (DO(JOIN,C,LIST_N(N,V))) -#define LOOP_S_LE_N(VAR, S, N) for (uint8_t VAR=(S); VAR<=(N); VAR++) -#define LOOP_S_L_N(VAR, S, N) for (uint8_t VAR=(S); VAR<(N); VAR++) -#define LOOP_LE_N(VAR, N) LOOP_S_LE_N(VAR, 0, N) -#define LOOP_L_N(VAR, N) LOOP_S_L_N(VAR, 0, N) - #define NOOP (void(0)) #define CEILING(x,y) (((x) + (y) - 1) / (y)) #undef ABS #ifdef __cplusplus - template static inline constexpr const T ABS(const T v) { return v >= 0 ? v : -v; } + template static constexpr const T ABS(const T v) { return v >= 0 ? v : -v; } #else #define ABS(a) ({__typeof__(a) _a = (a); _a >= 0 ? _a : -_a;}) #endif @@ -283,13 +376,18 @@ #define RSQRT(x) (1.0f / sqrtf(x)) #define CEIL(x) ceilf(x) #define FLOOR(x) floorf(x) +#define TRUNC(x) truncf(x) #define LROUND(x) lroundf(x) #define FMOD(x, y) fmodf(x, y) #define HYPOT(x,y) SQRT(HYPOT2(x,y)) // Use NUM_ARGS(__VA_ARGS__) to get the number of variadic arguments -#define _NUM_ARGS(_,Z,Y,X,W,V,U,T,S,R,Q,P,O,N,M,L,K,J,I,H,G,F,E,D,C,B,A,OUT,...) OUT -#define NUM_ARGS(V...) _NUM_ARGS(0,V,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0) +#define _NUM_ARGS(_,n,m,l,k,j,i,h,g,f,e,d,c,b,a,Z,Y,X,W,V,U,T,S,R,Q,P,O,N,M,L,K,J,I,H,G,F,E,D,C,B,A,OUT,...) OUT +#define NUM_ARGS(V...) _NUM_ARGS(0,V,40,39,38,37,36,35,34,33,32,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0) + +// Use TWO_ARGS(__VA_ARGS__) to get whether there are 1, 2, or >2 arguments +#define _TWO_ARGS(_,n,m,l,k,j,i,h,g,f,e,d,c,b,a,Z,Y,X,W,V,U,T,S,R,Q,P,O,N,M,L,K,J,I,H,G,F,E,D,C,B,A,OUT,...) OUT +#define TWO_ARGS(V...) _TWO_ARGS(0,V,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,1,0) #ifdef __cplusplus @@ -299,49 +397,163 @@ extern "C++" { // C++11 solution that is standards compliant. Return type is deduced automatically - template static inline constexpr auto _MIN(const L lhs, const R rhs) -> decltype(lhs + rhs) { + template static constexpr N _MIN(const N val) { return val; } + template static constexpr N _MAX(const N val) { return val; } + template static constexpr auto _MIN(const L lhs, const R rhs) -> decltype(lhs + rhs) { return lhs < rhs ? lhs : rhs; } - template static inline constexpr auto _MAX(const L lhs, const R rhs) -> decltype(lhs + rhs) { + template static constexpr auto _MAX(const L lhs, const R rhs) -> decltype(lhs + rhs) { return lhs > rhs ? lhs : rhs; } - template static inline constexpr const T _MIN(T V, Ts... Vs) { return _MIN(V, _MIN(Vs...)); } - template static inline constexpr const T _MAX(T V, Ts... Vs) { return _MAX(V, _MAX(Vs...)); } + template static constexpr const T _MIN(T V, Ts... Vs) { return _MIN(V, _MIN(Vs...)); } + template static constexpr const T _MAX(T V, Ts... Vs) { return _MAX(V, _MAX(Vs...)); } } #endif + // Allow manipulating enumeration value like flags without ugly cast everywhere + #define ENUM_FLAGS(T) \ + FORCE_INLINE constexpr T operator&(T x, T y) { return static_cast(static_cast(x) & static_cast(y)); } \ + FORCE_INLINE constexpr T operator|(T x, T y) { return static_cast(static_cast(x) | static_cast(y)); } \ + FORCE_INLINE constexpr T operator^(T x, T y) { return static_cast(static_cast(x) ^ static_cast(y)); } \ + FORCE_INLINE constexpr T operator~(T x) { return static_cast(~static_cast(x)); } \ + FORCE_INLINE T & operator&=(T &x, T y) { x = x & y; return x; } \ + FORCE_INLINE T & operator|=(T &x, T y) { x = x | y; return x; } \ + FORCE_INLINE T & operator^=(T &x, T y) { x = x ^ y; return x; } + + // C++11 solution that is standard compliant. is not available on all platform + namespace Private { + template struct enable_if { }; + template struct enable_if { typedef _Tp type; }; + + template struct is_same { enum { value = false }; }; + template struct is_same { enum { value = true }; }; + + template struct first_type_of { typedef T type; }; + template struct first_type_of { typedef T type; }; + + // remove const/volatile type qualifiers + template struct remove_const { typedef T type; }; + template struct remove_const { typedef T type; }; + + template struct remove_volatile { typedef T type; }; + template struct remove_volatile { typedef T type; }; + + template struct remove_cv { typedef typename remove_const::type>::type type; }; + + // test if type is integral + template struct _is_integral { enum { value = false }; }; + template<> struct _is_integral { enum { value = true }; }; + template<> struct _is_integral { enum { value = true }; }; + template<> struct _is_integral { enum { value = true }; }; + template<> struct _is_integral { enum { value = true }; }; + template<> struct _is_integral { enum { value = true }; }; + template<> struct _is_integral { enum { value = true }; }; + template<> struct _is_integral { enum { value = true }; }; + template<> struct _is_integral { enum { value = true }; }; + template<> struct _is_integral { enum { value = true }; }; + template<> struct _is_integral { enum { value = true }; }; + template struct is_integral : public _is_integral::type> {}; + } + + // enum type check and regression to its underlying integral. + namespace Private { + template struct is_enum { enum { value = __is_enum(T) }; }; + + template::value> struct _underlying_type { using type = __underlying_type(T); }; + template struct _underlying_type { }; + + template struct underlying_type : public _underlying_type { }; + } + + // C++11 solution using SFINAE to detect the existence of a member in a class at compile time. + // It creates a HasMember structure containing 'value' set to true if the member exists + #define HAS_MEMBER_IMPL(Member) \ + namespace Private { \ + template struct HasMember_ ## Member { \ + template static Yes& test( decltype(&C::Member) ) ; \ + template static No& test(...); \ + enum { value = sizeof(test(0)) == sizeof(Yes) }; }; \ + } + + // Call the method if it exists, but do nothing if it does not. The method is detected at compile time. + // If the method exists, this is inlined and does not cost anything. Else, an "empty" wrapper is created, returning a default value + #define CALL_IF_EXISTS_IMPL(Return, Method, ...) \ + HAS_MEMBER_IMPL(Method) \ + namespace Private { \ + template FORCE_INLINE typename enable_if::value, Return>::type Call_ ## Method(T * t, Args... a) { return static_cast(t->Method(a...)); } \ + _UNUSED static Return Call_ ## Method(...) { return __VA_ARGS__; } \ + } + #define CALL_IF_EXISTS(Return, That, Method, ...) \ + static_cast(Private::Call_ ## Method(That, ##__VA_ARGS__)) + + // Compile-time string manipulation + namespace CompileTimeString { + // Simple compile-time parser to find the position of the end of a string + constexpr const char* findStringEnd(const char *str) { + return *str ? findStringEnd(str + 1) : str; + } + + // Check whether a string contains a specific character + constexpr bool contains(const char *str, const char ch) { + return *str == ch ? true : (*str ? contains(str + 1, ch) : false); + } + // Find the last position of the specific character (should be called with findStringEnd) + constexpr const char* findLastPos(const char *str, const char ch) { + return *str == ch ? (str + 1) : findLastPos(str - 1, ch); + } + // Compile-time evaluation of the last part of a file path + // Typically used to shorten the path to file in compiled strings + // CompileTimeString::baseName(__FILE__) returns "macros.h" and not /path/to/Marlin/src/core/macros.h + constexpr const char* baseName(const char *str) { + return contains(str, '/') ? findLastPos(findStringEnd(str), '/') : str; + } + + // Find the first occurrence of a character in a string (or return the last position in the string) + constexpr const char* findFirst(const char *str, const char ch) { + return *str == ch || *str == 0 ? (str + 1) : findFirst(str + 1, ch); + } + // Compute the string length at compile time + constexpr unsigned stringLen(const char *str) { + return *str == 0 ? 0 : 1 + stringLen(str + 1); + } + } + + #define ONLY_FILENAME CompileTimeString::baseName(__FILE__) + /** Get the templated type name. This does not depends on RTTI, but on the preprocessor, so it should be quite safe to use even on old compilers. + WARNING: DO NOT RENAME THIS FUNCTION (or change the text inside the function to match what the preprocessor will generate) + The name is chosen very short since the binary will store "const char* gtn(T*) [with T = YourTypeHere]" so avoid long function name here */ + template + inline const char* gtn(T*) { + // It works on GCC by instantiating __PRETTY_FUNCTION__ and parsing the result. So the syntax here is very limited to GCC output + constexpr unsigned verboseChatLen = sizeof("const char* gtn(T*) [with T = ") - 1; + static char templateType[sizeof(__PRETTY_FUNCTION__) - verboseChatLen] = {}; + __builtin_memcpy(templateType, __PRETTY_FUNCTION__ + verboseChatLen, sizeof(__PRETTY_FUNCTION__) - verboseChatLen - 2); + return templateType; + } + #else - #define MIN_2(a,b) ((a)<(b)?(a):(b)) - #define MIN_3(a,V...) MIN_2(a,MIN_2(V)) - #define MIN_4(a,V...) MIN_2(a,MIN_3(V)) - #define MIN_5(a,V...) MIN_2(a,MIN_4(V)) - #define MIN_6(a,V...) MIN_2(a,MIN_5(V)) - #define MIN_7(a,V...) MIN_2(a,MIN_6(V)) - #define MIN_8(a,V...) MIN_2(a,MIN_7(V)) - #define MIN_9(a,V...) MIN_2(a,MIN_8(V)) - #define MIN_10(a,V...) MIN_2(a,MIN_9(V)) #define __MIN_N(N,V...) MIN_##N(V) #define _MIN_N(N,V...) __MIN_N(N,V) - #define _MIN(V...) _MIN_N(NUM_ARGS(V), V) + #define _MIN_N_REF() _MIN_N + #define _MIN(V...) EVAL(_MIN_N(TWO_ARGS(V),V)) + #define MIN_2(a,b) ((a)<(b)?(a):(b)) + #define MIN_3(a,V...) MIN_2(a,DEFER2(_MIN_N_REF)()(TWO_ARGS(V),V)) - #define MAX_2(a,b) ((a)>(b)?(a):(b)) - #define MAX_3(a,V...) MAX_2(a,MAX_2(V)) - #define MAX_4(a,V...) MAX_2(a,MAX_3(V)) - #define MAX_5(a,V...) MAX_2(a,MAX_4(V)) - #define MAX_6(a,V...) MAX_2(a,MAX_5(V)) - #define MAX_7(a,V...) MAX_2(a,MAX_6(V)) - #define MAX_8(a,V...) MAX_2(a,MAX_7(V)) - #define MAX_9(a,V...) MAX_2(a,MAX_8(V)) - #define MAX_10(a,V...) MAX_2(a,MAX_9(V)) #define __MAX_N(N,V...) MAX_##N(V) #define _MAX_N(N,V...) __MAX_N(N,V) - #define _MAX(V...) _MAX_N(NUM_ARGS(V), V) + #define _MAX_N_REF() _MAX_N + #define _MAX(V...) EVAL(_MAX_N(TWO_ARGS(V),V)) + #define MAX_2(a,b) ((a)>(b)?(a):(b)) + #define MAX_3(a,V...) MAX_2(a,DEFER2(_MAX_N_REF)()(TWO_ARGS(V),V)) #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 @@ -359,6 +571,22 @@ #define INC_13 14 #define INC_14 15 #define INC_15 16 +#define INC_16 17 +#define INC_17 18 +#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) @@ -373,6 +601,9 @@ #define ADD8(N) ADD4(ADD4(N)) #define ADD9(N) ADD4(ADD5(N)) #define ADD10(N) ADD5(ADD5(N)) +#define SUM(A,B) _CAT(ADD,A)(B) +#define DOUBLE_(n) ADD##n(n) +#define DOUBLE(n) DOUBLE_(n) // Macros for subtracting #define DEC_0 0 @@ -391,6 +622,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) @@ -422,6 +670,8 @@ // Force define expansion #define EVAL(V...) EVAL16(V) +#define EVAL4096(V...) EVAL2048(EVAL2048(V)) +#define EVAL2048(V...) EVAL1024(EVAL1024(V)) #define EVAL1024(V...) EVAL512(EVAL512(V)) #define EVAL512(V...) EVAL256(EVAL256(V)) #define EVAL256(V...) EVAL128(EVAL128(V)) @@ -437,22 +687,18 @@ #define IS_PROBE(V...) SECOND(V, 0) // Get the second item passed, or 0 #define PROBE() ~, 1 // Second item will be 1 if this is passed #define _NOT_0 PROBE() -#define NOT(x) IS_PROBE(_CAT(_NOT_, x)) // NOT('0') gets '1'. Anything else gets '0'. -#define _BOOL(x) NOT(NOT(x)) // NOT('0') gets '0'. Anything else gets '1'. +#define NOT(x) IS_PROBE(_CAT(_NOT_, x)) // NOT('0') gets '1'. Anything else gets '0'. +#define _BOOL(x) NOT(NOT(x)) // _BOOL('0') gets '0'. Anything else gets '1'. #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 - // Simple Inline IF Macros, friendly to use in other macro definitions #define IF(O, A, B) ((O) ? (A) : (B)) #define IF_0(O, A) IF(O, A, 0) @@ -481,6 +727,7 @@ // Repeat a macro passing S...N-1. #define REPEAT_S(S,N,OP) EVAL(_REPEAT(S,SUB##S(N),OP)) #define REPEAT(N,OP) REPEAT_S(0,N,OP) +#define REPEAT_1(N,OP) REPEAT_S(1,INCREMENT(N),OP) // Repeat a macro passing 0...N-1 plus additional arguments. #define REPEAT2_S(S,N,OP,V...) EVAL(_REPEAT2(S,SUB##S(N),OP,V)) @@ -499,18 +746,58 @@ ( DEFER2(__RREPEAT2)()(ADD1(_RPT_I),SUB1(_RPT_N),_RPT_OP,V) ) \ ( /* Do nothing */ ) #define __RREPEAT2() _RREPEAT2 -#define RREPEAT_S(S,N,OP) EVAL1024(_RREPEAT(S,SUB##S(N),OP)) -#define RREPEAT(N,OP) RREPEAT_S(0,N,OP) -#define RREPEAT2_S(S,N,OP,V...) EVAL1024(_RREPEAT2(S,SUB##S(N),OP,V)) -#define RREPEAT2(N,OP,V...) RREPEAT2_S(0,N,OP,V) +#define RREPEAT_S(S,N,OP) EVAL1024(_RREPEAT(S,SUB##S(N),OP)) +#define RREPEAT(N,OP) RREPEAT_S(0,N,OP) +#define RREPEAT_1(N,OP) RREPEAT_S(1,INCREMENT(N),OP) +#define RREPEAT2_S(S,N,OP,V...) EVAL1024(_RREPEAT2(S,SUB##S(N),OP,V)) +#define RREPEAT2(N,OP,V...) RREPEAT2_S(0,N,OP,V) -// See https://github.com/swansontec/map-macro -#define MAP_OUT -#define MAP_END(...) -#define MAP_GET_END() 0, MAP_END -#define MAP_NEXT0(test, next, ...) next MAP_OUT -#define MAP_NEXT1(test, next) MAP_NEXT0 (test, next, 0) -#define MAP_NEXT(test, next) MAP_NEXT1 (MAP_GET_END test, next) -#define MAP0(f, x, peek, ...) f(x) MAP_NEXT (peek, MAP1) (f, peek, __VA_ARGS__) -#define MAP1(f, x, peek, ...) f(x) MAP_NEXT (peek, MAP0) (f, peek, __VA_ARGS__) -#define MAP(f, ...) EVAL512 (MAP1 (f, __VA_ARGS__, (), 0)) +// Emit a list of N OP(I) items with ascending counter. +#define _REPLIST(_RPT_I,_RPT_N,_RPT_OP) \ + _RPT_OP(_RPT_I) \ + IF_ELSE(SUB1(_RPT_N)) \ + ( , DEFER2(__REPLIST)()(ADD1(_RPT_I),SUB1(_RPT_N),_RPT_OP) ) \ + ( /* Do nothing */ ) +#define __REPLIST() _REPLIST + +// Repeat a macro, comma-separated, passing S...N-1. +#define REPLIST_S(S,N,OP) EVAL(_REPLIST(S,SUB##S(N),OP)) +#define REPLIST(N,OP) REPLIST_S(0,N,OP) +#define REPLIST_1(N,OP) REPLIST_S(1,INCREMENT(N),OP) + +// Call OP(A) with each item as an argument +#define _MAP(_MAP_OP,A,V...) \ + _MAP_OP(A) \ + IF_ELSE(HAS_ARGS(V)) \ + ( DEFER2(__MAP)()(_MAP_OP,V) ) \ + ( /* Do nothing */ ) +#define __MAP() _MAP + +#define MAP(OP,V...) EVAL(_MAP(OP,V)) + +// Emit a list of OP(A) with the given items +#define _MAPLIST(_MAP_OP,A,V...) \ + _MAP_OP(A) \ + IF_ELSE(HAS_ARGS(V)) \ + ( , DEFER2(__MAPLIST)()(_MAP_OP,V) ) \ + ( /* Do nothing */ ) +#define __MAPLIST() _MAPLIST + +#define MAPLIST(OP,V...) EVAL(_MAPLIST(OP,V)) + +// Temperature Sensor Config +#define TEMP_SENSOR(N) TEMP_SENSOR_##N +#define _HAS_E_TEMP(N) || TEMP_SENSOR(N) +#define HAS_E_TEMP_SENSOR (0 REPEAT(EXTRUDERS, _HAS_E_TEMP)) +#define TEMP_SENSOR_IS_MAX_TC(T) (TEMP_SENSOR(T) == -5 || TEMP_SENSOR(T) == -3 || TEMP_SENSOR(T) == -2) + +#define _UI_NONE 0 +#define _UI_ORIGIN 101 +#define _UI_FYSETC 102 +#define _UI_HIPRECY 103 +#define _UI_MKS 104 +#define _UI_RELOADED 105 +#define _UI_IA_CREALITY 106 +#define _UI_E3S1PRO 107 +#define _DGUS_UI_IS(N) || (CAT(_UI_, DGUS_LCD_UI) == CAT(_UI_, N)) +#define DGUS_UI_IS(V...) (0 MAP(_DGUS_UI_IS, V)) diff --git a/Marlin/src/core/millis_t.h b/Marlin/src/core/millis_t.h index 95bc40e1ec..1d3cc853b3 100644 --- a/Marlin/src/core/millis_t.h +++ b/Marlin/src/core/millis_t.h @@ -28,6 +28,9 @@ typedef uint32_t millis_t; #define SEC_TO_MS(N) millis_t((N)*1000UL) #define MIN_TO_MS(N) SEC_TO_MS((N)*60UL) #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 new file mode 100644 index 0000000000..3ed21950de --- /dev/null +++ b/Marlin/src/core/mstring.h @@ -0,0 +1,322 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2022 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 + +/** + * Lightweight string class template providing operators for all common tasks + * and conversion from F() and PSTR() strings into SRAM strings that reside + * on the stack or persistently, with overflow prevention. + * + * Examples: + * + * MString<50> mystr(F("Hello "), intvar, " World"); // "Hello 3 World" + * + * mystr.append(" (", p_float_t(123.4, 2), ')'); // "Hello 3 World (123.40)" + * + * mystr.clear(); + * + * mystr.append(spaces_t(10), repchr_t('-', 5)); // Repeats are sometimes cheaper than strings + * + * mystr.appendf(F(" ... %i/%i"), count, total); // Works like printf, requires F string + * + */ + +#include "types.h" +#include "utility.h" // AXIS_CHAR +#include "../lcd/utf8.h" + +#ifndef DEFAULT_MSTRING_SIZE + #define DEFAULT_MSTRING_SIZE 20 +#endif + +//#define UNSAFE_MSTRING // Don't initialize the string to "" or set a terminating nul +//#define USE_SPRINTF // Use sprintf instead of snprintf +//#define DJB2_HASH // 32-bit hash with Djb2 algorithm +//#define MSTRING_DEBUG // Debug string operations to diagnose memory leaks +//#define FASTER_APPEND // Append without using an intermediate buffer + +// Declare externs for serial debug output +template extern void SERIAL_ECHO(T x); +template extern void SERIAL_ECHOLN(T x); +extern void SERIAL_ECHO(serial_char_t x); +extern void SERIAL_CHAR(char c); + +#define START_OF_UTF8_CHAR(C) (((C) & 0xC0u) != 0x80U) + +#if ENABLED(USE_SPRINTF) + #define SNPRINTF(A, S, V...) sprintf(A, V) + #define SNPRINTF_P(A, S, V...) sprintf_P(A, V) +#else + #define SNPRINTF(V...) snprintf(V) + #define SNPRINTF_P(V...) snprintf_P(V) +#endif + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + +#if DISABLED(UNSAFE_MSTRING) && GCC_VERSION >= 80000 + #pragma GCC diagnostic ignored "-Wstringop-truncation" +#endif + +/** + * @brief MString class template + * @details A class template providing convenient string operators, + * very similar to the Arduino String class, as it turns out. + * + * @tparam SIZE The pre-allocated storage for the string buffer + */ +template +class MString { +protected: + char str[SIZE+1]; +public: + MString() { safety(0); safety(SIZE); } + + template + MString(const T v) { set(v); safety(SIZE); } + + static_assert(SIZE > 0, "Bad SIZE for MString!"); + + void debug(FSTR_P const f) { + #if ENABLED(MSTRING_DEBUG) + SERIAL_ECHOLN(f, C(':'), uintptr_t(str), C(' '), length(), C(' '), str); + #endif + } + + void safety(const int n) { if (SAFE && n <= SIZE) str[n] = '\0'; } + + // Chainable String Setters + MString& set() { str[0] = '\0'; debug(F("clear")); return *this; } + MString& set(char *s) { strlcpy(str, s, SIZE + 1); debug(F("string")); return *this; } + MString& set(const char *s) { return set(const_cast(s)); } + MString& set_P(PGM_P const s) { strlcpy_P(str, s, SIZE + 1); debug(F("pstring")); return *this; } + MString& set(FSTR_P const f) { return set_P(FTOP(f)); } + MString& set(const bool &b) { return set(b ? F("true") : F("false")); } + MString& set(const char c) { str[0] = c; str[1] = '\0'; debug(F("char")); return *this; } + MString& set(const int8_t &i) { SNPRINTF_P(str, SIZE, PSTR("%d"), i); debug(F("int8_t")); return *this; } + MString& set(const short &i) { SNPRINTF_P(str, SIZE, PSTR("%d"), i); debug(F("short")); return *this; } + MString& set(const int &i) { SNPRINTF_P(str, SIZE, PSTR("%d"), i); debug(F("int")); return *this; } + MString& set(const long &l) { SNPRINTF_P(str, SIZE, PSTR("%ld"), l); debug(F("long")); return *this; } + MString& set(const unsigned char &i) { SNPRINTF_P(str, SIZE, PSTR("%u"), i); debug(F("uchar")); return *this; } + MString& set(const unsigned short &i) { SNPRINTF_P(str, SIZE, PSTR("%u"), i); debug(F("ushort")); return *this; } + MString& set(const unsigned int &i) { SNPRINTF_P(str, SIZE, PSTR("%u"), i); debug(F("uint")); return *this; } + MString& set(const unsigned long &l) { SNPRINTF_P(str, SIZE, PSTR("%lu"), l); debug(F("ulong")); return *this; } + MString& set(const float &f) { return set(p_float_t(f, SERIAL_FLOAT_PRECISION)); } + MString& set(const p_float_t &pf) { return set(w_float_t(pf.value, 1, pf.prec)); } + MString& set(const w_float_t &wf) { char f1[20]; return set(dtostrf(wf.value, wf.width, wf.prec, f1)); } + MString& set(const serial_char_t &v) { return set(char(v.c)); } + MString& set(const xyz_pos_t &v) { set(); return append(v); } + MString& set(const xyze_pos_t &v) { set(); return append(v); } + + template + MString& set(const MString &m) { strlcpy(str, &m, SIZE + 1); debug(F("MString")); return *this; } + + MString& setn(char *s, int len) { int c = _MIN(len, SIZE); strlcpy(str, s, c + 1); debug(F("string")); return *this; } + MString& setn(const char *s, int len) { return setn(const_cast(s), len); } + MString& setn_P(PGM_P const s, int len) { int c = _MIN(len, SIZE); strlcpy_P(str, s, c + 1); debug(F("pstring")); return *this; } + MString& setn(FSTR_P const f, int len) { return setn_P(FTOP(f), len); } + + // set(repchr_t('-', 10)) + MString& set(const repchr_t &s) { int c = _MIN(s.count, SIZE); if (c >= 0) { if (c > 0) memset(str, s.asc, c); str[c] = '\0'; } debug(F("repchr_t")); return *this; } + + // set(spaces_t(10)) + MString& set(const spaces_t &s) { repchr_t r(' ', s.count); return set(r); } + + // Set with format string and arguments, like printf + template + 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; } + + template + 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 + MString& append(char *s) { int sz = length(); if (sz < SIZE) strlcpy(str + sz, s, SIZE - sz + 1); debug(F("string")); return *this; } + MString& append(const char *s) { return append(const_cast(s)); } + 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'; } 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); 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); } + MString& append(const int &i) { char buf[12]; sprintf(buf, "%d", i); return append(buf); } + MString& append(const long &l) { char buf[12]; sprintf(buf, "%ld", l); return append(buf); } + MString& append(const unsigned char &i) { char buf[ 5]; sprintf(buf, "%u", i); return append(buf); } + MString& append(const unsigned short &i) { char buf[11]; sprintf(buf, "%u", i); return append(buf); } + MString& append(const unsigned int &i) { char buf[11]; sprintf(buf, "%u", i); return append(buf); } + MString& append(const unsigned long &l) { char buf[11]; sprintf(buf, "%lu", l); return append(buf); } + #endif + MString& append(const float &f) { return append(p_float_t(f, SERIAL_FLOAT_PRECISION)); } + MString& append(const p_float_t &pf) { return append(w_float_t(pf.value, 1, pf.prec)); } + MString& append(const w_float_t &wf) { char f1[20]; return append(dtostrf(wf.value, wf.width, wf.prec, f1)); } + MString& append(const serial_char_t &v) { return append(char(v.c)); } + MString& append(const xyz_pos_t &v) { LOOP_NUM_AXES(i) { if (i) append(' '); append(AXIS_CHAR(i), v[i]); } debug(F("xyz")); return *this; } + MString& append(const xyze_pos_t &v) { LOOP_LOGICAL_AXES(i) { if (i) append(' '); append(AXIS_CHAR(i), v[i]); } debug(F("xyze")); return *this; } + + template + MString& append(const MString &m) { return append(&m); } + + // Append only if the given space is available + MString& appendn(char *s, int len) { int sz = length(), c = _MIN(len, SIZE - sz); if (c > 0) { strlcpy(str + sz, s, c + 1); } debug(F("string")); return *this; } + MString& appendn(const char *s, int len) { return appendn(const_cast(s), len); } + MString& appendn_P(PGM_P const s, int len) { int sz = length(), c = _MIN(len, SIZE - sz); if (c > 0) { strlcpy_P(str + sz, s, c + 1); } debug(F("pstring")); return *this; } + MString& appendn(FSTR_P const f, int len) { return appendn_P(FTOP(f), len); } + + // append(repchr_t('-', 10)) + MString& append(const repchr_t &s) { + const int sz = length(), c = _MIN(s.count, SIZE - sz); + if (c > 0) { memset(str + sz, s.asc, c); str[sz + c] = '\0'; } + debug(F("repchr")); + return *this; + } + + // append(spaces_t(10)) + MString& append(const spaces_t &s) { return append(repchr_t(' ', s.count)); } + + template + MString& appendf_P(PGM_P const pfmt, Args... more) { + int sz = length(); + if (sz < SIZE) SNPRINTF_P(str + sz, SIZE - sz, pfmt, more...); + debug(F("appendf_P")); + return *this; + } + + template + MString& appendf(const char *fmt, Args... more) { + const int sz = length(); + if (sz < SIZE) SNPRINTF(str + sz, SIZE - sz, fmt, more...); + debug(F("appendf")); + return *this; + } + + template + MString& appendf(FSTR_P const fmt, Args... more) { return appendf_P(FTOP(fmt), more...); } + + // Instantiate with a list of things + template + MString(T arg1, Args... more) { set(arg1); append(more...); } + + // Catch unhandled types to prevent infinite recursion + template MString& append(T) { return append(TERN(MSTRING_DEBUG, typeid(T).name(), '?')); } + + // Take a list of any number of arguments and append them to the string + template + MString& append(T arg1, Args... more) { return append(arg1).append(more...); } + + // Take a list of any number of arguments and set them in the string + template + MString& set(T arg1, Args... more) { return set(arg1).append(more...); } + + // Operator = as shorthand for set() + template + MString& operator=(const T &v) { return set(v); } + + // Operator += as shorthand for append() + template + MString& operator+=(const T &v) { return append(v); } + + // Operator + as shorthand for append-to-copy + template + MString operator+(const T &v) { return MString(str, v); } + + #ifndef __AVR__ + MString(const double d) { set(d); } + MString& set(const double &f) { return set(p_double_t(f, SERIAL_FLOAT_PRECISION)); } + MString& set(const p_double_t &pf) { return set(w_double_t(pf.value, 1, pf.prec)); } + MString& set(const w_double_t &wf) { char d1[20]; return set(dtostrf(wf.value, wf.width, wf.prec, d1)); } + MString& append(const double &f) { return append(p_double_t(f, SERIAL_FLOAT_PRECISION)); } + MString& append(const p_double_t &pf) { return append(w_double_t(pf.value, 1, pf.prec)); } + MString& append(const w_double_t &wf) { char d1[20]; return append(dtostrf(wf.value, wf.width, wf.prec, d1)); } + #endif + + // Get the character at a given index + char operator[](const int i) const { return str[i]; } + + // Cast to char* (explicit?) + operator char* () { return str; } + + // Use &mystring as shorthand for mystring.str + char* operator&() { return str; } + + // Return the buffer address (same as &) + char* buffer() { return str; } + + int length() const { return strlen(str); } + int glyphs() { return utf8_strlen(str); } + bool empty() { return !str[0]; } + + // 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; + for (int i = 0; i < sz; i++) hval += (hval << 5) + str[i]; // = hval * 33 + c + #else + hash_t hval = hash_t(sz); + for (int i = 0; i < sz; i++) hval = ((hval << 1) | (hval >> 15)) ^ str[i]; // ROL, XOR + #endif + return hval; + } + + void copyto(char * const dst) const { strcpy(dst, str); } + void copyto(char * const dst, int len) const { strlcpy(dst, str, len + 1); } + + 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) { + const int sz = length(); + if (sz && n <= sz) + for (int i = n; i >= 0; i--) if (START_OF_UTF8_CHAR(str[i])) { str[i] = '\0'; break; } + debug(F("utrunc")); + return *this; + } + +}; + +#pragma GCC diagnostic pop + +// Temporary inline string typically used to compose a G-code command +#ifndef TS_SIZE + #define TS_SIZE 63 +#endif +typedef MString TString; +#define TS(V...) TString(V) diff --git a/Marlin/src/core/multi_language.h b/Marlin/src/core/multi_language.h index 5852c439a8..81a1754d5e 100644 --- a/Marlin/src/core/multi_language.h +++ b/Marlin/src/core/multi_language.h @@ -1,26 +1,35 @@ -/******************** - * multi_language.h * - ********************/ - -/**************************************************************************** - * Written By Marcio Teixeira 2019 - Aleph Objects, Inc. * - * * - * 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. * - * * - * To view a copy of the GNU General Public License, go to the following * - * location: . * - ****************************************************************************/ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2020 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 +/******************************************************* + * multi_language.h * + * By Marcio Teixeira 2019 for Aleph Objects * + *******************************************************/ + +#include "../inc/MarlinConfigPre.h" + typedef const char Language_Str[]; +#define LSTR PROGMEM Language_Str #ifdef LCD_LANGUAGE_5 #define NUM_LANGUAGES 5 @@ -34,21 +43,18 @@ typedef const char Language_Str[]; #define NUM_LANGUAGES 1 #endif -// Setting the unused languages equal to each other allows -// the compiler to optimize away the conditionals - +// Set unused languages equal to each other so the +// compiler can optimize away the conditionals. +#define LCD_LANGUAGE_1 LCD_LANGUAGE #ifndef LCD_LANGUAGE_2 #define LCD_LANGUAGE_2 LCD_LANGUAGE #endif - #ifndef LCD_LANGUAGE_3 #define LCD_LANGUAGE_3 LCD_LANGUAGE_2 #endif - #ifndef LCD_LANGUAGE_4 #define LCD_LANGUAGE_4 LCD_LANGUAGE_3 #endif - #ifndef LCD_LANGUAGE_5 #define LCD_LANGUAGE_5 LCD_LANGUAGE_4 #endif @@ -57,27 +63,35 @@ typedef const char Language_Str[]; #define GET_LANG(LANG) _GET_LANG(LANG) #if NUM_LANGUAGES > 1 - extern uint8_t lang; + #define HAS_MULTI_LANGUAGE 1 + #if HAS_MARLINUI_MENU + #define HAS_MENU_MULTI_LANGUAGE 1 + #endif #define GET_TEXT(MSG) ( \ - lang == 0 ? GET_LANG(LCD_LANGUAGE)::MSG : \ - lang == 1 ? GET_LANG(LCD_LANGUAGE_2)::MSG : \ - lang == 2 ? GET_LANG(LCD_LANGUAGE_3)::MSG : \ - lang == 3 ? GET_LANG(LCD_LANGUAGE_4)::MSG : \ - GET_LANG(LCD_LANGUAGE_5)::MSG \ - ) - #define MAX_LANG_CHARSIZE _MAX(GET_LANG(LCD_LANGUAGE)::CHARSIZE, \ - GET_LANG(LCD_LANGUAGE_2)::CHARSIZE, \ - GET_LANG(LCD_LANGUAGE_3)::CHARSIZE, \ - GET_LANG(LCD_LANGUAGE_4)::CHARSIZE, \ - GET_LANG(LCD_LANGUAGE_5)::CHARSIZE) + ui.language == 4 ? GET_LANG(LCD_LANGUAGE_5)::MSG : \ + ui.language == 3 ? GET_LANG(LCD_LANGUAGE_4)::MSG : \ + ui.language == 2 ? GET_LANG(LCD_LANGUAGE_3)::MSG : \ + ui.language == 1 ? GET_LANG(LCD_LANGUAGE_2)::MSG : \ + GET_LANG(LCD_LANGUAGE )::MSG ) + #define MAX_LANG_CHARSIZE _MAX(GET_LANG(LCD_LANGUAGE )::CHARSIZE, \ + GET_LANG(LCD_LANGUAGE_2)::CHARSIZE, \ + GET_LANG(LCD_LANGUAGE_3)::CHARSIZE, \ + GET_LANG(LCD_LANGUAGE_4)::CHARSIZE, \ + GET_LANG(LCD_LANGUAGE_5)::CHARSIZE ) #else #define GET_TEXT(MSG) GET_LANG(LCD_LANGUAGE)::MSG - #define MAX_LANG_CHARSIZE GET_LANG(LCD_LANGUAGE)::CHARSIZE + #define MAX_LANG_CHARSIZE LANG_CHARSIZE #endif -#define GET_TEXT_F(MSG) (const __FlashStringHelper*)GET_TEXT(MSG) +#define GET_TEXT_F(MSG) FPSTR(GET_TEXT(MSG)) + +#define GET_EN_TEXT(MSG) GET_LANG(en)::MSG +#define GET_EN_TEXT_F(MSG) FPSTR(GET_EN_TEXT(MSG)) #define GET_LANGUAGE_NAME(INDEX) GET_LANG(LCD_LANGUAGE_##INDEX)::LANGUAGE +#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 0d22f7bfc0..852cfc77f6 100644 --- a/Marlin/src/core/serial.cpp +++ b/Marlin/src/core/serial.cpp @@ -23,40 +23,96 @@ #include "serial.h" #include "../inc/MarlinConfig.h" -uint8_t marlin_debug_flags = MARLIN_DEBUG_NONE; - -static PGMSTR(errormagic, "Error:"); -static PGMSTR(echomagic, "echo:"); - -#if HAS_MULTI_SERIAL - int8_t serial_port_index = 0; +#if HAS_ETHERNET + #include "../feature/ethernet.h" #endif -void serialprintPGM(PGM_P str) { - while (const char c = pgm_read_byte(str++)) SERIAL_CHAR(c); +#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); + +// Commonly-used strings in serial output +PGMSTR(SP_A_STR, " A"); PGMSTR(SP_B_STR, " B"); PGMSTR(SP_C_STR, " C"); +PGMSTR(SP_P_STR, " P"); PGMSTR(SP_T_STR, " T"); PGMSTR(NUL_STR, ""); + +#define _N_STR(N) PGMSTR(N##_STR, STR_##N); +#define _N_LBL(N) PGMSTR(N##_LBL, STR_##N ":"); +#define _SP_N_STR(N) PGMSTR(SP_##N##_STR, " " STR_##N); +#define _SP_N_LBL(N) PGMSTR(SP_##N##_LBL, " " STR_##N ":"); +MAP(_N_STR, LOGICAL_AXIS_NAMES); MAP(_SP_N_STR, LOGICAL_AXIS_NAMES); +MAP(_N_LBL, LOGICAL_AXIS_NAMES); MAP(_SP_N_LBL, LOGICAL_AXIS_NAMES); + +// Hook Meatpack if it's enabled on the first leaf +#if ENABLED(MEATPACK_ON_SERIAL_PORT_1) + SerialLeafT1 mpSerial1(false, _SERIAL_LEAF_1); +#endif +#if ENABLED(MEATPACK_ON_SERIAL_PORT_2) + SerialLeafT2 mpSerial2(false, _SERIAL_LEAF_2); +#endif +#if ENABLED(MEATPACK_ON_SERIAL_PORT_3) + SerialLeafT3 mpSerial3(false, _SERIAL_LEAF_3); +#endif + +// Step 2: For multiserial, handle the second serial port as well +#if HAS_MULTI_SERIAL + #if HAS_ETHERNET + // We need a definition here + SerialLeafT2 msSerial2(ethernet.have_telnet_client, MYSERIAL2, false); + #endif + + #define __S_LEAF(N) ,SERIAL_LEAF_##N + #define _S_LEAF(N) __S_LEAF(N) + + SerialOutputT multiSerial( SERIAL_LEAF_1 REPEAT_S(2, INCREMENT(NUM_SERIAL), _S_LEAF) ); + + #undef __S_LEAF + #undef _S_LEAF + +#endif + +// Specializations for float, p_float_t, w_float_t +template <> void SERIAL_ECHO(const float f) { SERIAL_IMPL.print(f, SERIAL_FLOAT_PRECISION); } +template <> void SERIAL_ECHO(const p_float_t pf) { SERIAL_IMPL.print(pf.value, pf.prec); } +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(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'); } + +void SERIAL_ECHO(serial_char_t x) { SERIAL_IMPL.write(x.c); } + +void SERIAL_FLUSH() { SERIAL_IMPL.flush(); } +void SERIAL_FLUSHTX() { SERIAL_IMPL.flushTX(); } + +void SERIAL_ECHO_P(PGM_P pstr) { + while (const char c = pgm_read_byte(pstr++)) SERIAL_CHAR(c); } -void serial_echo_start() { serialprintPGM(echomagic); } -void serial_error_start() { serialprintPGM(errormagic); } +void SERIAL_ECHOLN_P(PGM_P pstr) { SERIAL_ECHO_P(pstr); SERIAL_EOL(); } -void serial_echopair_PGM(PGM_P const s_P, const char *v) { serialprintPGM(s_P); SERIAL_ECHO(v); } -void serial_echopair_PGM(PGM_P const s_P, char v) { serialprintPGM(s_P); SERIAL_CHAR(v); } -void serial_echopair_PGM(PGM_P const s_P, int v) { serialprintPGM(s_P); SERIAL_ECHO(v); } -void serial_echopair_PGM(PGM_P const s_P, long v) { serialprintPGM(s_P); SERIAL_ECHO(v); } -void serial_echopair_PGM(PGM_P const s_P, float v) { serialprintPGM(s_P); SERIAL_DECIMAL(v); } -void serial_echopair_PGM(PGM_P const s_P, double v) { serialprintPGM(s_P); SERIAL_DECIMAL(v); } -void serial_echopair_PGM(PGM_P const s_P, unsigned int v) { serialprintPGM(s_P); SERIAL_ECHO(v); } -void serial_echopair_PGM(PGM_P const s_P, unsigned long v) { serialprintPGM(s_P); SERIAL_ECHO(v); } +void SERIAL_ECHO_START() { SERIAL_ECHO(F("echo:")); } +void SERIAL_ERROR_START() { SERIAL_ECHO(F("Error:")); } +void SERIAL_WARN_START() { SERIAL_ECHO(F("Warning:")); } -void serial_spaces(uint8_t count) { count *= (PROPORTIONAL_FONT_RATIO); while (count--) SERIAL_CHAR(' '); } +void SERIAL_ECHO_SP(uint8_t count) { count *= (PROPORTIONAL_FONT_RATIO); while (count--) SERIAL_CHAR(' '); } -void serial_ternary(const bool onoff, PGM_P const pre, PGM_P const on, PGM_P const off, PGM_P const post/*=nullptr*/) { - if (pre) serialprintPGM(pre); - serialprintPGM(onoff ? on : off); - if (post) serialprintPGM(post); +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)) + SERIAL_CHAR('+'); + SERIAL_ECHO(v); +} + +void serial_ternary(FSTR_P const pre, const bool onoff, FSTR_P const on, FSTR_P const off, FSTR_P const post/*=nullptr*/) { + if (pre) SERIAL_ECHO(pre); + if (onoff && on) SERIAL_ECHO(on); + if (!onoff && off) SERIAL_ECHO(off); + if (post) SERIAL_ECHO(post); } -void serialprint_onoff(const bool onoff) { serialprintPGM(onoff ? PSTR(STR_ON) : PSTR(STR_OFF)); } -void serialprintln_onoff(const bool onoff) { serialprint_onoff(onoff); SERIAL_EOL(); } -void serialprint_truefalse(const bool tf) { serialprintPGM(tf ? PSTR("true") : PSTR("false")); } void print_bin(uint16_t val) { for (uint8_t i = 16; i--;) { @@ -65,10 +121,26 @@ void print_bin(uint16_t val) { } } -extern const char SP_X_STR[], SP_Y_STR[], SP_Z_STR[]; - -void print_xyz(const float &x, const float &y, const float &z, PGM_P const prefix/*=nullptr*/, PGM_P const suffix/*=nullptr*/) { - if (prefix) serialprintPGM(prefix); - SERIAL_ECHOPAIR_P(SP_X_STR, x, SP_Y_STR, y, SP_Z_STR, z); - if (suffix) serialprintPGM(suffix); else SERIAL_EOL(); +void _print_xyz(NUM_AXIS_ARGS_(const float) FSTR_P const prefix) { + if (prefix) SERIAL_ECHO(prefix); + #if NUM_AXES + 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) 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) 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); + #endif + if (suffix) SERIAL_ECHO(suffix); else SERIAL_EOL(); } diff --git a/Marlin/src/core/serial.h b/Marlin/src/core/serial.h index fc830736a5..6c73d72a22 100644 --- a/Marlin/src/core/serial.h +++ b/Marlin/src/core/serial.h @@ -22,306 +22,311 @@ #pragma once #include "../inc/MarlinConfig.h" +#include "serial_hook.h" -/** - * Define debug bit-masks - */ +#if HAS_MEATPACK + #include "../feature/meatpack.h" +#endif + +// +// Debugging flags for use by M111 +// enum MarlinDebugFlags : uint8_t { MARLIN_DEBUG_NONE = 0, - MARLIN_DEBUG_ECHO = _BV(0), ///< Echo commands in order as they are processed - MARLIN_DEBUG_INFO = _BV(1), ///< Print messages for code that has debug output - MARLIN_DEBUG_ERRORS = _BV(2), ///< Not implemented - MARLIN_DEBUG_DRYRUN = _BV(3), ///< Ignore temperature setting and E movement commands - MARLIN_DEBUG_COMMUNICATION = _BV(4), ///< Not implemented - #if ENABLED(DEBUG_LEVELING_FEATURE) - MARLIN_DEBUG_LEVELING = _BV(5), ///< Print detailed output for homing and leveling - MARLIN_DEBUG_MESH_ADJUST = _BV(6), ///< UBL bed leveling - #else - MARLIN_DEBUG_LEVELING = 0, - MARLIN_DEBUG_MESH_ADJUST = 0, - #endif - MARLIN_DEBUG_ALL = 0xFF + MARLIN_DEBUG_ECHO = TERN0(DEBUG_FLAGS_GCODE, _BV(0)), //!< Echo commands in order as they are processed + MARLIN_DEBUG_INFO = TERN0(DEBUG_FLAGS_GCODE, _BV(1)), //!< Print messages for code that has debug output + MARLIN_DEBUG_ERRORS = TERN0(DEBUG_FLAGS_GCODE, _BV(2)), //!< Not implemented + MARLIN_DEBUG_DRYRUN = _BV(3), //!< Ignore temperature setting and E movement commands + MARLIN_DEBUG_COMMUNICATION = TERN0(DEBUG_FLAGS_GCODE, _BV(4)), //!< Not implemented + MARLIN_DEBUG_LEVELING = TERN0(DEBUG_LEVELING_FEATURE, _BV(5)), //!< Print detailed output for homing and leveling + MARLIN_DEBUG_MESH_ADJUST = TERN0(DEBUG_LEVELING_FEATURE, _BV(6)), //!< UBL bed leveling + MARLIN_DEBUG_ALL = MARLIN_DEBUG_ECHO|MARLIN_DEBUG_INFO|MARLIN_DEBUG_ERRORS|MARLIN_DEBUG_COMMUNICATION|MARLIN_DEBUG_LEVELING|MARLIN_DEBUG_MESH_ADJUST }; extern uint8_t marlin_debug_flags; #define DEBUGGING(F) (marlin_debug_flags & (MARLIN_DEBUG_## F)) -#define SERIAL_BOTH 0x7F -#if HAS_MULTI_SERIAL - extern int8_t serial_port_index; - #define _PORT_REDIRECT(n,p) REMEMBER(n,serial_port_index,p) - #define _PORT_RESTORE(n) RESTORE(n) +// +// Serial redirection +// +// Step 1: Find out what the first serial leaf is +#if HAS_MULTI_SERIAL && defined(SERIAL_CATCHALL) + #define _SERIAL_LEAF_1 MYSERIAL +#else + #define _SERIAL_LEAF_1 MYSERIAL1 +#endif +// Hook Meatpack if it's enabled on the first leaf +#if ENABLED(MEATPACK_ON_SERIAL_PORT_1) + typedef MeatpackSerial SerialLeafT1; + extern SerialLeafT1 mpSerial1; + #define SERIAL_LEAF_1 mpSerial1 +#else + #define SERIAL_LEAF_1 _SERIAL_LEAF_1 +#endif + +// Step 2: For multiserial wrap all serial ports in a single +// interface with the ability to output to multiple serial ports. +#if HAS_MULTI_SERIAL + #define _PORT_REDIRECT(n,p) REMEMBER(n,multiSerial.portMask,p) + #define _PORT_RESTORE(n) RESTORE(n) + #define SERIAL_ASSERT(P) if (multiSerial.portMask!=(P)) { debugger(); } + // If we have a catchall, use that directly #ifdef SERIAL_CATCHALL - #define SERIAL_OUT(WHAT, V...) (void)CAT(MYSERIAL,SERIAL_CATCHALL).WHAT(V) + #define _SERIAL_LEAF_2 SERIAL_CATCHALL + #elif HAS_ETHERNET + typedef ConditionalSerial SerialLeafT2; // We need to create an instance here + extern SerialLeafT2 msSerial2; + #define _SERIAL_LEAF_2 msSerial2 #else - #define SERIAL_OUT(WHAT, V...) do{ \ - if (!serial_port_index || serial_port_index == SERIAL_BOTH) (void)MYSERIAL0.WHAT(V); \ - if ( serial_port_index) (void)MYSERIAL1.WHAT(V); \ - }while(0) + #define _SERIAL_LEAF_2 MYSERIAL2 // Don't create a useless instance here, directly use the existing instance #endif - #define SERIAL_ASSERT(P) if(serial_port_index!=(P)){ debugger(); } + // Nothing complicated here + #define _SERIAL_LEAF_3 MYSERIAL3 + + // Hook Meatpack if it's enabled on the second leaf + #if ENABLED(MEATPACK_ON_SERIAL_PORT_2) + typedef MeatpackSerial SerialLeafT2; + extern SerialLeafT2 mpSerial2; + #define SERIAL_LEAF_2 mpSerial2 + #else + #define SERIAL_LEAF_2 _SERIAL_LEAF_2 + #endif + + // Hook Meatpack if it's enabled on the third leaf + #if ENABLED(MEATPACK_ON_SERIAL_PORT_3) + typedef MeatpackSerial SerialLeafT3; + extern SerialLeafT3 mpSerial3; + #define SERIAL_LEAF_3 mpSerial3 + #else + #define SERIAL_LEAF_3 _SERIAL_LEAF_3 + #endif + + #define __S_MULTI(N) decltype(SERIAL_LEAF_##N), + #define _S_MULTI(N) __S_MULTI(N) + + typedef MultiSerial< REPEAT_1(NUM_SERIAL, _S_MULTI) 0> SerialOutputT; + + #undef __S_MULTI + #undef _S_MULTI + + extern SerialOutputT multiSerial; + #define SERIAL_IMPL multiSerial #else - #define _PORT_REDIRECT(n,p) NOOP - #define _PORT_RESTORE(n) NOOP - #define SERIAL_OUT(WHAT, V...) (void)MYSERIAL0.WHAT(V) - #define SERIAL_ASSERT(P) NOOP + #define _PORT_REDIRECT(n,p) NOOP + #define _PORT_RESTORE(n) NOOP + #define SERIAL_ASSERT(P) NOOP + #define SERIAL_IMPL SERIAL_LEAF_1 #endif -#define PORT_REDIRECT(p) _PORT_REDIRECT(1,p) -#define PORT_RESTORE() _PORT_RESTORE(1) - -#define SERIAL_ECHO(x) SERIAL_OUT(print, x) -#define SERIAL_ECHO_F(V...) SERIAL_OUT(print, V) -#define SERIAL_ECHOLN(x) SERIAL_OUT(println, x) -#define SERIAL_PRINT(x,b) SERIAL_OUT(print, x, b) -#define SERIAL_PRINTLN(x,b) SERIAL_OUT(println, x, b) -#define SERIAL_PRINTF(V...) SERIAL_OUT(printf, V) -#define SERIAL_FLUSH() SERIAL_OUT(flush) - -#ifdef ARDUINO_ARCH_STM32 - #define SERIAL_FLUSHTX() SERIAL_OUT(flush) -#elif TX_BUFFER_SIZE > 0 - #define SERIAL_FLUSHTX() SERIAL_OUT(flushTX) -#else - #define SERIAL_FLUSHTX() -#endif - -// Print up to 10 chars from a list -#define __CHAR_N(N,V...) _CHAR_##N(V) -#define _CHAR_N(N,V...) __CHAR_N(N,V) -#define _CHAR_1(c) SERIAL_OUT(write, c) -#define _CHAR_2(a,b) do{ _CHAR_1(a); _CHAR_1(b); }while(0) -#define _CHAR_3(a,V...) do{ _CHAR_1(a); _CHAR_2(V); }while(0) -#define _CHAR_4(a,V...) do{ _CHAR_1(a); _CHAR_3(V); }while(0) -#define _CHAR_5(a,V...) do{ _CHAR_1(a); _CHAR_4(V); }while(0) -#define _CHAR_6(a,V...) do{ _CHAR_1(a); _CHAR_5(V); }while(0) -#define _CHAR_7(a,V...) do{ _CHAR_1(a); _CHAR_6(V); }while(0) -#define _CHAR_8(a,V...) do{ _CHAR_1(a); _CHAR_7(V); }while(0) -#define _CHAR_9(a,V...) do{ _CHAR_1(a); _CHAR_8(V); }while(0) -#define _CHAR_10(a,V...) do{ _CHAR_1(a); _CHAR_9(V); }while(0) - -#define SERIAL_CHAR(V...) _CHAR_N(NUM_ARGS(V),V) - -// Print up to 12 pairs of values. Odd elements auto-wrapped in PSTR(). -#define __SEP_N(N,V...) _SEP_##N(V) -#define _SEP_N(N,V...) __SEP_N(N,V) -#define _SEP_1(PRE) SERIAL_ECHOPGM(PRE) -#define _SEP_2(PRE,V) serial_echopair_PGM(PSTR(PRE),V) -#define _SEP_3(a,b,c) do{ _SEP_2(a,b); SERIAL_ECHOPGM(c); }while(0) -#define _SEP_4(a,b,V...) do{ _SEP_2(a,b); _SEP_2(V); }while(0) -#define _SEP_5(a,b,V...) do{ _SEP_2(a,b); _SEP_3(V); }while(0) -#define _SEP_6(a,b,V...) do{ _SEP_2(a,b); _SEP_4(V); }while(0) -#define _SEP_7(a,b,V...) do{ _SEP_2(a,b); _SEP_5(V); }while(0) -#define _SEP_8(a,b,V...) do{ _SEP_2(a,b); _SEP_6(V); }while(0) -#define _SEP_9(a,b,V...) do{ _SEP_2(a,b); _SEP_7(V); }while(0) -#define _SEP_10(a,b,V...) do{ _SEP_2(a,b); _SEP_8(V); }while(0) -#define _SEP_11(a,b,V...) do{ _SEP_2(a,b); _SEP_9(V); }while(0) -#define _SEP_12(a,b,V...) do{ _SEP_2(a,b); _SEP_10(V); }while(0) -#define _SEP_13(a,b,V...) do{ _SEP_2(a,b); _SEP_11(V); }while(0) -#define _SEP_14(a,b,V...) do{ _SEP_2(a,b); _SEP_12(V); }while(0) -#define _SEP_15(a,b,V...) do{ _SEP_2(a,b); _SEP_13(V); }while(0) -#define _SEP_16(a,b,V...) do{ _SEP_2(a,b); _SEP_14(V); }while(0) -#define _SEP_17(a,b,V...) do{ _SEP_2(a,b); _SEP_15(V); }while(0) -#define _SEP_18(a,b,V...) do{ _SEP_2(a,b); _SEP_16(V); }while(0) -#define _SEP_19(a,b,V...) do{ _SEP_2(a,b); _SEP_17(V); }while(0) -#define _SEP_20(a,b,V...) do{ _SEP_2(a,b); _SEP_18(V); }while(0) -#define _SEP_21(a,b,V...) do{ _SEP_2(a,b); _SEP_19(V); }while(0) -#define _SEP_22(a,b,V...) do{ _SEP_2(a,b); _SEP_20(V); }while(0) -#define _SEP_23(a,b,V...) do{ _SEP_2(a,b); _SEP_21(V); }while(0) -#define _SEP_24(a,b,V...) do{ _SEP_2(a,b); _SEP_22(V); }while(0) - -#define SERIAL_ECHOPAIR(V...) _SEP_N(NUM_ARGS(V),V) - -// Print up to 12 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_1_P(PRE) serialprintPGM(PRE) -#define _SEP_2_P(PRE,V) serial_echopair_PGM(PRE,V) -#define _SEP_3_P(a,b,c) do{ _SEP_2_P(a,b); serialprintPGM(c); }while(0) -#define _SEP_4_P(a,b,V...) do{ _SEP_2_P(a,b); _SEP_2_P(V); }while(0) -#define _SEP_5_P(a,b,V...) do{ _SEP_2_P(a,b); _SEP_3_P(V); }while(0) -#define _SEP_6_P(a,b,V...) do{ _SEP_2_P(a,b); _SEP_4_P(V); }while(0) -#define _SEP_7_P(a,b,V...) do{ _SEP_2_P(a,b); _SEP_5_P(V); }while(0) -#define _SEP_8_P(a,b,V...) do{ _SEP_2_P(a,b); _SEP_6_P(V); }while(0) -#define _SEP_9_P(a,b,V...) do{ _SEP_2_P(a,b); _SEP_7_P(V); }while(0) -#define _SEP_10_P(a,b,V...) do{ _SEP_2_P(a,b); _SEP_8_P(V); }while(0) -#define _SEP_11_P(a,b,V...) do{ _SEP_2_P(a,b); _SEP_9_P(V); }while(0) -#define _SEP_12_P(a,b,V...) do{ _SEP_2_P(a,b); _SEP_10_P(V); }while(0) -#define _SEP_13_P(a,b,V...) do{ _SEP_2_P(a,b); _SEP_11_P(V); }while(0) -#define _SEP_14_P(a,b,V...) do{ _SEP_2_P(a,b); _SEP_12_P(V); }while(0) -#define _SEP_15_P(a,b,V...) do{ _SEP_2_P(a,b); _SEP_13_P(V); }while(0) -#define _SEP_16_P(a,b,V...) do{ _SEP_2_P(a,b); _SEP_14_P(V); }while(0) -#define _SEP_17_P(a,b,V...) do{ _SEP_2_P(a,b); _SEP_15_P(V); }while(0) -#define _SEP_18_P(a,b,V...) do{ _SEP_2_P(a,b); _SEP_16_P(V); }while(0) -#define _SEP_19_P(a,b,V...) do{ _SEP_2_P(a,b); _SEP_17_P(V); }while(0) -#define _SEP_20_P(a,b,V...) do{ _SEP_2_P(a,b); _SEP_18_P(V); }while(0) -#define _SEP_21_P(a,b,V...) do{ _SEP_2_P(a,b); _SEP_19_P(V); }while(0) -#define _SEP_22_P(a,b,V...) do{ _SEP_2_P(a,b); _SEP_20_P(V); }while(0) -#define _SEP_23_P(a,b,V...) do{ _SEP_2_P(a,b); _SEP_21_P(V); }while(0) -#define _SEP_24_P(a,b,V...) do{ _SEP_2_P(a,b); _SEP_22_P(V); }while(0) - -#define SERIAL_ECHOPAIR_P(V...) _SEP_N_P(NUM_ARGS(V),V) - -// Print up to 12 pairs of values followed by newline -#define __SELP_N(N,V...) _SELP_##N(V) -#define _SELP_N(N,V...) __SELP_N(N,V) -#define _SELP_1(PRE) SERIAL_ECHOLNPGM(PRE) -#define _SELP_2(PRE,V) do{ serial_echopair_PGM(PSTR(PRE),V); SERIAL_EOL(); }while(0) -#define _SELP_3(a,b,c) do{ _SEP_2(a,b); SERIAL_ECHOLNPGM(c); }while(0) -#define _SELP_4(a,b,V...) do{ _SEP_2(a,b); _SELP_2(V); }while(0) -#define _SELP_5(a,b,V...) do{ _SEP_2(a,b); _SELP_3(V); }while(0) -#define _SELP_6(a,b,V...) do{ _SEP_2(a,b); _SELP_4(V); }while(0) -#define _SELP_7(a,b,V...) do{ _SEP_2(a,b); _SELP_5(V); }while(0) -#define _SELP_8(a,b,V...) do{ _SEP_2(a,b); _SELP_6(V); }while(0) -#define _SELP_9(a,b,V...) do{ _SEP_2(a,b); _SELP_7(V); }while(0) -#define _SELP_10(a,b,V...) do{ _SEP_2(a,b); _SELP_8(V); }while(0) -#define _SELP_11(a,b,V...) do{ _SEP_2(a,b); _SELP_9(V); }while(0) -#define _SELP_12(a,b,V...) do{ _SEP_2(a,b); _SELP_10(V); }while(0) -#define _SELP_13(a,b,V...) do{ _SEP_2(a,b); _SELP_11(V); }while(0) -#define _SELP_14(a,b,V...) do{ _SEP_2(a,b); _SELP_12(V); }while(0) -#define _SELP_15(a,b,V...) do{ _SEP_2(a,b); _SELP_13(V); }while(0) -#define _SELP_16(a,b,V...) do{ _SEP_2(a,b); _SELP_14(V); }while(0) -#define _SELP_17(a,b,V...) do{ _SEP_2(a,b); _SELP_15(V); }while(0) -#define _SELP_18(a,b,V...) do{ _SEP_2(a,b); _SELP_16(V); }while(0) -#define _SELP_19(a,b,V...) do{ _SEP_2(a,b); _SELP_17(V); }while(0) -#define _SELP_20(a,b,V...) do{ _SEP_2(a,b); _SELP_18(V); }while(0) -#define _SELP_21(a,b,V...) do{ _SEP_2(a,b); _SELP_19(V); }while(0) -#define _SELP_22(a,b,V...) do{ _SEP_2(a,b); _SELP_20(V); }while(0) -#define _SELP_23(a,b,V...) do{ _SEP_2(a,b); _SELP_21(V); }while(0) -#define _SELP_24(a,b,V...) do{ _SEP_2(a,b); _SELP_22(V); }while(0) -#define _SELP_25(a,b,V...) do{ _SEP_2(a,b); _SELP_23(V); }while(0) -#define _SELP_26(a,b,V...) do{ _SEP_2(a,b); _SELP_24(V); }while(0) -#define _SELP_27(a,b,V...) do{ _SEP_2(a,b); _SELP_25(V); }while(0) -#define _SELP_28(a,b,V...) do{ _SEP_2(a,b); _SELP_26(V); }while(0) -#define _SELP_29(a,b,V...) do{ _SEP_2(a,b); _SELP_27(V); }while(0) -#define _SELP_30(a,b,V...) do{ _SEP_2(a,b); _SELP_28(V); }while(0) // Eat two args, pass the rest up - -#define SERIAL_ECHOLNPAIR(V...) _SELP_N(NUM_ARGS(V),V) - -// Print up to 12 pairs of values followed by newline -#define __SELP_N_P(N,V...) _SELP_##N##_P(V) -#define _SELP_N_P(N,V...) __SELP_N_P(N,V) -#define _SELP_1_P(PRE) serialprintPGM(PRE) -#define _SELP_2_P(PRE,V) do{ serial_echopair_PGM(PRE,V); SERIAL_EOL(); }while(0) -#define _SELP_3_P(a,b,c) do{ _SEP_2_P(a,b); serialprintPGM(c); }while(0) -#define _SELP_4_P(a,b,V...) do{ _SEP_2_P(a,b); _SELP_2_P(V); }while(0) -#define _SELP_5_P(a,b,V...) do{ _SEP_2_P(a,b); _SELP_3_P(V); }while(0) -#define _SELP_6_P(a,b,V...) do{ _SEP_2_P(a,b); _SELP_4_P(V); }while(0) -#define _SELP_7_P(a,b,V...) do{ _SEP_2_P(a,b); _SELP_5_P(V); }while(0) -#define _SELP_8_P(a,b,V...) do{ _SEP_2_P(a,b); _SELP_6_P(V); }while(0) -#define _SELP_9_P(a,b,V...) do{ _SEP_2_P(a,b); _SELP_7_P(V); }while(0) -#define _SELP_10_P(a,b,V...) do{ _SEP_2_P(a,b); _SELP_8_P(V); }while(0) -#define _SELP_11_P(a,b,V...) do{ _SEP_2_P(a,b); _SELP_9_P(V); }while(0) -#define _SELP_12_P(a,b,V...) do{ _SEP_2_P(a,b); _SELP_10_P(V); }while(0) -#define _SELP_13_P(a,b,V...) do{ _SEP_2_P(a,b); _SELP_11_P(V); }while(0) -#define _SELP_14_P(a,b,V...) do{ _SEP_2_P(a,b); _SELP_12_P(V); }while(0) -#define _SELP_15_P(a,b,V...) do{ _SEP_2_P(a,b); _SELP_13_P(V); }while(0) -#define _SELP_16_P(a,b,V...) do{ _SEP_2_P(a,b); _SELP_14_P(V); }while(0) -#define _SELP_17_P(a,b,V...) do{ _SEP_2_P(a,b); _SELP_15_P(V); }while(0) -#define _SELP_18_P(a,b,V...) do{ _SEP_2_P(a,b); _SELP_16_P(V); }while(0) -#define _SELP_19_P(a,b,V...) do{ _SEP_2_P(a,b); _SELP_17_P(V); }while(0) -#define _SELP_20_P(a,b,V...) do{ _SEP_2_P(a,b); _SELP_18_P(V); }while(0) -#define _SELP_21_P(a,b,V...) do{ _SEP_2_P(a,b); _SELP_19_P(V); }while(0) -#define _SELP_22_P(a,b,V...) do{ _SEP_2_P(a,b); _SELP_20_P(V); }while(0) -#define _SELP_23_P(a,b,V...) do{ _SEP_2_P(a,b); _SELP_21_P(V); }while(0) -#define _SELP_24_P(a,b,V...) do{ _SEP_2_P(a,b); _SELP_22_P(V); }while(0) -#define _SELP_25_P(a,b,V...) do{ _SEP_2_P(a,b); _SELP_23_P(V); }while(0) -#define _SELP_26_P(a,b,V...) do{ _SEP_2_P(a,b); _SELP_24_P(V); }while(0) -#define _SELP_27_P(a,b,V...) do{ _SEP_2_P(a,b); _SELP_25_P(V); }while(0) -#define _SELP_28_P(a,b,V...) do{ _SEP_2_P(a,b); _SELP_26_P(V); }while(0) -#define _SELP_29_P(a,b,V...) do{ _SEP_2_P(a,b); _SELP_27_P(V); }while(0) -#define _SELP_30_P(a,b,V...) do{ _SEP_2_P(a,b); _SELP_28_P(V); }while(0) // Eat two args, pass the rest up - -#define SERIAL_ECHOLNPAIR_P(V...) _SELP_N_P(NUM_ARGS(V),V) - -// Print up to 20 comma-separated pairs of values -#define __SLST_N(N,V...) _SLST_##N(V) -#define _SLST_N(N,V...) __SLST_N(N,V) -#define _SLST_1(a) SERIAL_ECHO(a) -#define _SLST_2(a,b) do{ SERIAL_ECHO(a); SERIAL_ECHOPAIR(", ",b); }while(0) -#define _SLST_3(a,b,c) do{ SERIAL_ECHO(a); _SEP_2(", ",b); _SLST_1(c); }while(0) -#define _SLST_4(a,b,V...) do{ SERIAL_ECHO(a); _SEP_2(", ",b); _SLST_2(V); }while(0) -#define _SLST_5(a,b,V...) do{ SERIAL_ECHO(a); _SEP_2(", ",b); _SLST_3(V); }while(0) -#define _SLST_6(a,b,V...) do{ SERIAL_ECHO(a); _SEP_2(", ",b); _SLST_4(V); }while(0) -#define _SLST_7(a,b,V...) do{ SERIAL_ECHO(a); _SEP_2(", ",b); _SLST_5(V); }while(0) -#define _SLST_8(a,b,V...) do{ SERIAL_ECHO(a); _SEP_2(", ",b); _SLST_6(V); }while(0) -#define _SLST_9(a,b,V...) do{ SERIAL_ECHO(a); _SEP_2(", ",b); _SLST_7(V); }while(0) -#define _SLST_10(a,b,V...) do{ SERIAL_ECHO(a); _SEP_2(", ",b); _SLST_8(V); }while(0) -#define _SLST_11(a,b,V...) do{ SERIAL_ECHO(a); _SEP_2(", ",b); _SLST_9(V); }while(0) -#define _SLST_12(a,b,V...) do{ SERIAL_ECHO(a); _SEP_2(", ",b); _SLST_10(V); }while(0) -#define _SLST_13(a,b,V...) do{ SERIAL_ECHO(a); _SEP_2(", ",b); _SLST_11(V); }while(0) -#define _SLST_14(a,b,V...) do{ SERIAL_ECHO(a); _SEP_2(", ",b); _SLST_12(V); }while(0) -#define _SLST_15(a,b,V...) do{ SERIAL_ECHO(a); _SEP_2(", ",b); _SLST_13(V); }while(0) -#define _SLST_16(a,b,V...) do{ SERIAL_ECHO(a); _SEP_2(", ",b); _SLST_14(V); }while(0) -#define _SLST_17(a,b,V...) do{ SERIAL_ECHO(a); _SEP_2(", ",b); _SLST_15(V); }while(0) -#define _SLST_18(a,b,V...) do{ SERIAL_ECHO(a); _SEP_2(", ",b); _SLST_16(V); }while(0) -#define _SLST_19(a,b,V...) do{ SERIAL_ECHO(a); _SEP_2(", ",b); _SLST_17(V); }while(0) -#define _SLST_20(a,b,V...) do{ SERIAL_ECHO(a); _SEP_2(", ",b); _SLST_18(V); }while(0) // Eat two args, pass the rest up - -#define SERIAL_ECHOLIST(pre,V...) do{ SERIAL_ECHOPGM(pre); _SLST_N(NUM_ARGS(V),V); }while(0) -#define SERIAL_ECHOLIST_N(N,V...) _SLST_N(N,LIST_N(N,V)) - -#define SERIAL_ECHOPGM_P(P) (serialprintPGM(P)) -#define SERIAL_ECHOLNPGM_P(P) (serialprintPGM(P "\n")) - -#define SERIAL_ECHOPGM(S) (serialprintPGM(PSTR(S))) -#define SERIAL_ECHOLNPGM(S) (serialprintPGM(PSTR(S "\n"))) - -#define SERIAL_ECHOPAIR_F_P(P,V...) do{ serialprintPGM(P); SERIAL_ECHO_F(V); }while(0) -#define SERIAL_ECHOLNPAIR_F_P(V...) do{ SERIAL_ECHOPAIR_F_P(V); SERIAL_EOL(); }while(0) - -#define SERIAL_ECHOPAIR_F(S,V...) SERIAL_ECHOPAIR_F_P(PSTR(S),V) -#define SERIAL_ECHOLNPAIR_F(V...) do{ SERIAL_ECHOPAIR_F(V); SERIAL_EOL(); }while(0) - -#define SERIAL_ECHO_START() serial_echo_start() -#define SERIAL_ERROR_START() serial_error_start() -#define SERIAL_EOL() SERIAL_CHAR('\n') - -#define SERIAL_ECHO_MSG(V...) do{ SERIAL_ECHO_START(); SERIAL_ECHOLNPAIR(V); }while(0) -#define SERIAL_ERROR_MSG(V...) do{ SERIAL_ERROR_START(); SERIAL_ECHOLNPAIR(V); }while(0) - -#define SERIAL_ECHO_SP(C) serial_spaces(C) - -#define SERIAL_ECHO_TERNARY(TF, PRE, ON, OFF, POST) serial_ternary(TF, PSTR(PRE), PSTR(ON), PSTR(OFF), PSTR(POST)) - -#if SERIAL_FLOAT_PRECISION - #define SERIAL_DECIMAL(V) SERIAL_PRINT(V, SERIAL_FLOAT_PRECISION) -#else - #define SERIAL_DECIMAL(V) SERIAL_ECHO(V) -#endif +#define PORT_REDIRECT(p) _PORT_REDIRECT(1,p) +#define PORT_RESTORE() _PORT_RESTORE(1) +#define SERIAL_PORTMASK(P) SerialMask::from(P) // -// Functions for serial printing from PROGMEM. (Saves loads of SRAM.) +// SERIAL_CHAR - Print one or more individual chars // -void serial_echopair_PGM(PGM_P const s_P, const char *v); -void serial_echopair_PGM(PGM_P const s_P, char v); -void serial_echopair_PGM(PGM_P const s_P, int v); -void serial_echopair_PGM(PGM_P const s_P, long v); -void serial_echopair_PGM(PGM_P const s_P, float v); -void serial_echopair_PGM(PGM_P const s_P, double v); -void serial_echopair_PGM(PGM_P const s_P, unsigned int v); -void serial_echopair_PGM(PGM_P const s_P, unsigned long v); -inline void serial_echopair_PGM(PGM_P const s_P, uint8_t v) { serial_echopair_PGM(s_P, (int)v); } -inline void serial_echopair_PGM(PGM_P const s_P, bool v) { serial_echopair_PGM(s_P, (int)v); } -inline void serial_echopair_PGM(PGM_P const s_P, void *v) { serial_echopair_PGM(s_P, (unsigned long)v); } +void SERIAL_CHAR(char a); +template +void SERIAL_CHAR(char a, Args ... args) { SERIAL_IMPL.write(a); SERIAL_CHAR(args ...); } -void serialprintPGM(PGM_P str); -void serial_echo_start(); -void serial_error_start(); -void serial_ternary(const bool onoff, PGM_P const pre, PGM_P const on, PGM_P const off, PGM_P const post=nullptr); -void serialprint_onoff(const bool onoff); -void serialprintln_onoff(const bool onoff); -void serialprint_truefalse(const bool tf); -void serial_spaces(uint8_t count); +/** + * SERIAL_ECHO / SERIAL_ECHOLN - Print a single string or value. + * Any numeric parameter (including char) is printed as a base-10 number. + * A string pointer or literal will be output as a string. + * + * NOTE: Use SERIAL_CHAR to print char as a single character. + */ +template void SERIAL_ECHO(T x) { SERIAL_IMPL.print(x); } +template void SERIAL_ECHOLN(T x) { SERIAL_IMPL.println(x); } + +// Wrapper for ECHO commands to interpret a char +void SERIAL_ECHO(serial_char_t x); +#define AS_DIGIT(n) C('0' + (n)) + +// Print an integer with a numeric base such as PrintBase::Hex +template void SERIAL_PRINT(T x, PrintBase y) { SERIAL_IMPL.print(x, y); } +template void SERIAL_PRINTLN(T x, PrintBase y) { SERIAL_IMPL.println(x, y); } + +// Flush the serial port +void SERIAL_FLUSH(); +void SERIAL_FLUSHTX(); + +// Start an echo: or error: output +void SERIAL_ECHO_START(); +void SERIAL_ERROR_START(); +void SERIAL_WARN_START(); + +// Serial end-of-line +void SERIAL_EOL(); + +// Print a single PROGMEM, PGM_P, or PSTR() string. +void SERIAL_ECHO_P(PGM_P pstr); +void SERIAL_ECHOLN_P(PGM_P pstr); + +// Specializations for float, p_float_t, and w_float_t +template<> void SERIAL_ECHO(const float f); +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(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 +void SERIAL_ECHO(T arg1, Args ... args) { SERIAL_ECHO(arg1); SERIAL_ECHO(args ...); } +template +void SERIAL_ECHOLN(T arg1, Args ... args) { SERIAL_ECHO(arg1); SERIAL_ECHO(args ...); SERIAL_EOL(); } + +// +// SERIAL_ECHOPGM... macros are used to output string-value pairs, wrapping +// all the odd loose string elements as PROGMEM 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 +#define _SEP_1(s) SERIAL_ECHO(F(s)); +#define _SEP_2(s,v) SERIAL_ECHO(F(s),v); +#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 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 +#define _SELP_1(s) SERIAL_ECHO(F(s "\n")); +#define _SELP_2(s,v) SERIAL_ECHOLN(F(s),v); +#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 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 +#define _SEP_1_P(p) SERIAL_ECHO(FPSTR(p)); +#define _SEP_2_P(p,v) SERIAL_ECHO(FPSTR(p),v); +#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 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 +#define _SELP_1_P(p) SERIAL_ECHOLN(FPSTR(p)); +#define _SELP_2_P(p,v) SERIAL_ECHOLN(FPSTR(p),v); +#define _SELP_3_P(p,v,V...) { _SEP_2_P(p,v); DEFER2(_SELP_N_P_REF)()(TWO_ARGS(V),V); } +#define SERIAL_ECHOLNPGM_P(V...) do{ EVAL(_SELP_N_P(TWO_ARGS(V),V)); }while(0) + +#define SERIAL_ECHO_MSG(V...) do{ SERIAL_ECHO_START(); SERIAL_ECHOLNPGM(V); }while(0) +#define SERIAL_ERROR_MSG(V...) do{ SERIAL_ERROR_START(); SERIAL_ECHOLNPGM(V); }while(0) +#define SERIAL_WARN_MSG(V...) do{ SERIAL_WARN_START(); SERIAL_ECHOLNPGM(V); }while(0) + +// Print a prefix, conditional string, and suffix +void serial_ternary(FSTR_P const pre, const bool onoff, FSTR_P const on, FSTR_P const off, FSTR_P const post=nullptr); +// Shorthand to put loose strings in PROGMEM +#define SERIAL_ECHO_TERNARY(TF, PRE, ON, OFF, POST) serial_ternary(F(PRE), TF, F(ON), F(OFF), F(POST)) + +// Print up to 255 spaces +void SERIAL_ECHO_SP(uint8_t count); + +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(const float &x, const float &y, const float &z, PGM_P const prefix=nullptr, PGM_P const suffix=nullptr); -inline void print_xyz(const xyz_pos_t &xyz, PGM_P const prefix=nullptr, PGM_P const suffix=nullptr) { - print_xyz(xyz.x, xyz.y, xyz.z, prefix, suffix); +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); } -#define SERIAL_POS(SUFFIX,VAR) do { print_xyz(VAR, PSTR(" " STRINGIFY(VAR) "="), PSTR(" : " SUFFIX "\n")); }while(0) -#define SERIAL_XYZ(PREFIX,V...) do { print_xyz(V, PSTR(PREFIX), nullptr); }while(0) +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_LC_(xyze) prefix, suffix); +} + +#define SERIAL_POS(SUFFIX,VAR) do { print_xyz(VAR, F(" " STRINGIFY(VAR) "="), F(" : " SUFFIX "\n")); }while(0) +#define SERIAL_XYZ(PREFIX,V...) do { print_xyz(V, F(PREFIX)); }while(0) + +/** + * Extended string that can echo itself to serial + */ +template +class SString : public MString { +public: + typedef MString super; + using super::str; + using super::debug; + + SString() : super() {} + + template + SString(T arg1, Args... more) : super(arg1, more...) {} + + SString& set() { super::set(); return *this; } + + template + 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; } + + template + 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; } + + template + SString& append(const T &v) { super::append(v); return *this; } + + template + SString& set(T arg1, Args... more) { set(arg1).append(more...); return *this; } + + template + SString& append(T arg1, Args... more) { append(arg1).append(more...); return *this; } + + SString& clear() { set(); return *this; } + SString& eol() { append('\n'); return *this; } + SString& trunc(const int &i) { super::trunc(i); return *this; } + + // Extended with methods to print to serial + SString& echo() { SERIAL_ECHO(str); return *this; } + SString& echoln() { SERIAL_ECHOLN(str); return *this; } +}; + +#define TSS(V...) SString<>(V) + +// +// Commonly-used strings in serial output +// + +#define _N_STR(N) N##_STR +#define _N_LBL(N) N##_LBL +#define _N_STR_A(N) _N_STR(N)[] +#define _N_LBL_A(N) _N_LBL(N)[] +#define _SP_N_STR(N) SP_##N##_STR +#define _SP_N_LBL(N) SP_##N##_LBL +#define _SP_N_STR_A(N) _SP_N_STR(N)[] +#define _SP_N_LBL_A(N) _SP_N_LBL(N)[] + +extern const char SP_A_STR[], SP_B_STR[], SP_C_STR[], SP_P_STR[], SP_T_STR[], NUL_STR[], + MAPLIST(_N_STR_A, LOGICAL_AXIS_NAMES), MAPLIST(_SP_N_STR_A, LOGICAL_AXIS_NAMES), + MAPLIST(_N_LBL_A, LOGICAL_AXIS_NAMES), MAPLIST(_SP_N_LBL_A, LOGICAL_AXIS_NAMES); + +PGM_P const SP_AXIS_LBL[] PROGMEM = { MAPLIST(_SP_N_LBL, LOGICAL_AXIS_NAMES) }; +PGM_P const SP_AXIS_STR[] PROGMEM = { MAPLIST(_SP_N_STR, LOGICAL_AXIS_NAMES) }; + +#undef _N_STR +#undef _N_LBL +#undef _N_STR_A +#undef _N_LBL_A +#undef _SP_N_STR +#undef _SP_N_LBL +#undef _SP_N_STR_A +#undef _SP_N_LBL_A diff --git a/Marlin/src/core/serial_base.h b/Marlin/src/core/serial_base.h new file mode 100644 index 0000000000..07bed55b35 --- /dev/null +++ b/Marlin/src/core/serial_base.h @@ -0,0 +1,259 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2020 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 // for size_t + +#if ENABLED(EMERGENCY_PARSER) + #include "../feature/e_parser.h" +#endif + +// Used in multiple places +// You can build it but not manipulate it. +// There are only few places where it's required to access the underlying member: GCodeQueue, SerialMask and MultiSerial +struct serial_index_t { + // A signed index, where -1 is a special case meaning no action (neither output or input) + int8_t index; + + // Check if the index is within the range [a ... b] + constexpr inline bool within(const int8_t a, const int8_t b) const { return WITHIN(index, a, b); } + constexpr inline bool valid() const { return WITHIN(index, 0, 7); } // At most, 8 bits + + // Construction is either from an index + constexpr serial_index_t(const int8_t index) : index(index) {} + + // Default to "no index" + constexpr serial_index_t() : index(-1) {} +}; + +// In order to catch usage errors in code, we make the base to encode number explicit +// If given a number (and not this enum), the compiler will reject the overload, falling back to the (double, digit) version +// We don't want hidden conversion of the first parameter to double, so it has to be as hard to do for the compiler as creating this enum +enum class PrintBase { + Dec = 10, + Hex = 16, + Oct = 8, + Bin = 2 +}; + +// A simple feature list enumeration +enum class SerialFeature { + None = 0x00, + MeatPack = 0x01, //!< Enabled when Meatpack is present + BinaryFileTransfer = 0x02, //!< Enabled for BinaryFile transfer support (in the future) + Virtual = 0x04, //!< Enabled for virtual serial port (like Telnet / Websocket / ...) + Hookable = 0x08, //!< Enabled if the serial class supports a setHook method +}; +ENUM_FLAGS(SerialFeature); + +// flushTX is not implemented in all HAL, so use SFINAE to call the method where it is. +CALL_IF_EXISTS_IMPL(void, flushTX); +CALL_IF_EXISTS_IMPL(bool, connected, true); +CALL_IF_EXISTS_IMPL(SerialFeature, features, SerialFeature::None); + +// A simple forward struct to prevent the compiler from selecting print(double, int) as a default overload +// for any type other than double/float. For double/float, a conversion exists so the call will be invisible. +struct EnsureDouble { + double a; + operator double() { return a; } + // If the compiler breaks on ambiguity here, it's likely because print(X, base) is called with X not a double/float, and + // a base that's not a PrintBase value. This code is made to detect the error. You MUST set a base explicitly like this: + //SERIAL_PRINT(v, PrintBase::Hex) + EnsureDouble(double a) : a(a) {} + EnsureDouble(float a) : a(a) {} +}; + +// Using Curiously-Recurring Template Pattern here to avoid virtual table cost when compiling. +// Since the real serial class is known at compile time, this results in the compiler writing +// a completely efficient code. +template +struct SerialBase { + #if ENABLED(EMERGENCY_PARSER) + const bool ep_enabled; + EmergencyParser::State emergency_state; + inline bool emergency_parser_enabled() { return ep_enabled; } + SerialBase(bool ep_capable) : ep_enabled(ep_capable), emergency_state(EmergencyParser::State::EP_RESET) {} + #else + SerialBase(const bool) {} + #endif + + #define SerialChild static_cast(this) + + // Static dispatch methods below: + // The most important method here is where it all ends to: + void write(uint8_t c) { SerialChild->write(c); } + + // Called when the parser finished processing an instruction, usually build to nothing + void msgDone() const { SerialChild->msgDone(); } + + // Called on initialization + void begin(const long baudRate) { SerialChild->begin(baudRate); } + + // Called on destruction + void end() { SerialChild->end(); } + + /** Check for available data from the port + @param index The port index, usually 0 */ + int available(serial_index_t index=0) const { return SerialChild->available(index); } + + /** Read a value from the port + @param index The port index, usually 0 */ + int read(serial_index_t index=0) { return SerialChild->read(index); } + + /** Combine the features of this serial instance and return it + @param index The port index, usually 0 */ + SerialFeature features(serial_index_t index=0) const { return static_cast(this)->features(index); } + + // Check if the serial port has a feature + bool has_feature(serial_index_t index, SerialFeature flag) const { return (features(index) & flag) != SerialFeature::None; } + + // Check if the serial port is connected (usually bypassed) + bool connected() const { return SerialChild->connected(); } + + // Redirect flush + void flush() { SerialChild->flush(); } + + // Not all implementation have a flushTX, so let's call them only if the child has the implementation + void flushTX() { CALL_IF_EXISTS(void, SerialChild, flushTX); } + + // Glue code here + void write(const char *str) { while (*str) write(*str++); } + void write(const uint8_t *buffer, size_t size) { while (size--) write(*buffer++); } + void print(char *str) { write(str); } + void print(const char *str) { write(str); } + // No default argument to avoid ambiguity + + // Define print for every fundamental integer type, to ensure that all redirect properly + // to the correct underlying implementation. + + // Prints are performed with a single size, to avoid needing multiple print functions. + // The fixed integer size used for prints will be the larger of long or a pointer. + #if __LONG_WIDTH__ >= __INTPTR_WIDTH__ + typedef long int_fixed_print_t; + typedef unsigned long uint_fixed_print_t; + #else + typedef intptr_t int_fixed_print_t; + typedef uintptr_t uint_fixed_print_t; + + FORCE_INLINE void print(intptr_t c, PrintBase base) { printNumber_signed(c, base); } + FORCE_INLINE void print(uintptr_t c, PrintBase base) { printNumber_unsigned(c, base); } + #endif + + FORCE_INLINE void print(char c, PrintBase base) { printNumber_signed(c, base); } + FORCE_INLINE void print(short c, PrintBase base) { printNumber_signed(c, base); } + FORCE_INLINE void print(int c, PrintBase base) { printNumber_signed(c, base); } + FORCE_INLINE void print(long c, PrintBase base) { printNumber_signed(c, base); } + FORCE_INLINE void print(unsigned char c, PrintBase base) { printNumber_unsigned(c, base); } + FORCE_INLINE void print(unsigned short c, PrintBase base) { printNumber_unsigned(c, base); } + FORCE_INLINE void print(unsigned int c, PrintBase base) { printNumber_unsigned(c, base); } + FORCE_INLINE void print(unsigned long c, PrintBase base) { printNumber_unsigned(c, base); } + + void print(EnsureDouble c, int digits) { printFloat(c, digits); } + + // Forward the call to the former's method + + // Default implementation for anything without a specialization + // This handles integers since they are the most common + template + void print(T c) { print(c, PrintBase::Dec); } + + void print(float c) { print(c, 2); } + void print(double c) { print(c, 2); } + + void println(char *s) { print(s); println(); } + void println(const char *s) { print(s); println(); } + void println(float c, int digits) { print(c, digits); println(); } + void println(double c, int digits) { print(c, digits); println(); } + void println() { write('\r'); write('\n'); } + + // Default implementations for types without a specialization. Handles integers. + template + void println(T c, PrintBase base) { print(c, base); println(); } + + template + void println(T c) { println(c, PrintBase::Dec); } + + // Forward the call to the former's method + void println(float c) { println(c, 2); } + void println(double c) { println(c, 2); } + + // Print a number with the given base + NO_INLINE void printNumber_unsigned(uint_fixed_print_t n, PrintBase base) { + if (n) { + unsigned char buf[8 * sizeof(long)]; // Enough space for base 2 + int8_t i = 0; + while (n) { + buf[i++] = n % (uint_fixed_print_t)base; + n /= (uint_fixed_print_t)base; + } + while (i--) write((char)(buf[i] + (buf[i] < 10 ? '0' : 'A' - 10))); + } + else write('0'); + } + + NO_INLINE void printNumber_signed(int_fixed_print_t n, PrintBase base) { + if (base == PrintBase::Dec && n < 0) { + n = -n; // This works because all platforms Marlin's builds on are using 2-complement encoding for negative number + // On such CPU, changing the sign of a number is done by inverting the bits and adding one, so if n = 0x80000000 = -2147483648 then + // -n = 0x7FFFFFFF + 1 => 0x80000000 = 2147483648 (if interpreted as unsigned) or -2147483648 if interpreted as signed. + // On non 2-complement CPU, there would be no possible representation for 2147483648. + write('-'); + } + printNumber_unsigned((uint_fixed_print_t)n, base); + } + + // Print a decimal number + NO_INLINE void printFloat(double number, uint8_t digits) { + // Handle negative numbers + if (number < 0.0) { + write('-'); + number *= -1; + } + + // Round correctly so that print(1.999, 2) prints as "2.00" + double rounding = 0.5; + for (uint8_t i = 0; i < digits; ++i) rounding *= 0.1; + number += rounding; + + // Extract the integer part of the number and print it + unsigned long int_part = (unsigned long)number; + double remainder = number - (double)int_part; + printNumber_unsigned(int_part, PrintBase::Dec); + + // Print the decimal point, but only if there are digits beyond + if (digits) { + write('.'); + // Extract digits from the remainder one at a time + while (digits--) { + remainder *= 10.0; + unsigned long toPrint = (unsigned long)remainder; + printNumber_unsigned(toPrint, PrintBase::Dec); + remainder -= toPrint; + } + } + } +}; + +// All serial instances will be built by chaining the features required +// for the function in the form of a template type definition. diff --git a/Marlin/src/core/serial_hook.h b/Marlin/src/core/serial_hook.h new file mode 100644 index 0000000000..06efce1dc5 --- /dev/null +++ b/Marlin/src/core/serial_hook.h @@ -0,0 +1,308 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2020 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 "serial_base.h" + +// A mask containing a bitmap of the serial port to act upon +// This is written to ensure a serial index is never used as a serial mask +class SerialMask { + uint8_t mask; + + // This constructor is private to ensure you can't convert an index to a mask + // The compiler will stop here if you are mixing index and mask in your code. + // If you need to, you'll have to use the explicit static "from" method here + SerialMask(const serial_index_t); + +public: + inline constexpr bool enabled(const SerialMask PortMask) const { return mask & PortMask.mask; } + inline constexpr SerialMask combine(const SerialMask other) const { return SerialMask(mask | other.mask); } + inline constexpr SerialMask operator<< (const int offset) const { return SerialMask(mask << offset); } + static SerialMask from(const serial_index_t index) { + if (index.valid()) return SerialMask(_BV(index.index)); + return SerialMask(0); // A invalid index mean no output + } + + constexpr SerialMask(const uint8_t mask) : mask(mask) {} + constexpr SerialMask(const SerialMask &rs) : mask(rs.mask) {} // Can't use = default here since not all frameworks support this + + SerialMask& operator=(const SerialMask &rs) { mask = rs.mask; return *this; } + + static constexpr uint8_t All = 0xFF; +}; + +// The most basic serial class: it dispatch to the base serial class with no hook whatsoever. This will compile to nothing but the base serial class +template +struct BaseSerial : public SerialBase< BaseSerial >, public SerialT { + typedef SerialBase< BaseSerial > BaseClassT; + + // It's required to implement a write method here to help compiler disambiguate what method to call + using SerialT::write; + using SerialT::flush; + + void msgDone() {} + + // We don't care about indices here, since if one can call us, it's the right index anyway + int available(serial_index_t) { return (int)SerialT::available(); } + int read(serial_index_t) { return (int)SerialT::read(); } + bool connected() { return CALL_IF_EXISTS(bool, static_cast(this), connected);; } + void flushTX() { CALL_IF_EXISTS(void, static_cast(this), flushTX); } + + SerialFeature features(serial_index_t index) const { return CALL_IF_EXISTS(SerialFeature, static_cast(this), features, index); } + + // Two implementations of the same method exist in both base classes so indicate the right one + using SerialT::available; + using SerialT::read; + using SerialT::begin; + using SerialT::end; + + using BaseClassT::print; + using BaseClassT::println; + + BaseSerial(const bool e) : BaseClassT(e) {} + + // Forward constructor + template + BaseSerial(const bool e, Args... args) : BaseClassT(e), SerialT(args...) {} +}; + +// A serial with a condition checked at runtime for its output +// A bit less efficient than static dispatching but since it's only used for ethernet's serial output right now, it's ok. +template +struct ConditionalSerial : public SerialBase< ConditionalSerial > { + typedef SerialBase< ConditionalSerial > BaseClassT; + + bool & condition; + SerialT & out; + NO_INLINE size_t write(uint8_t c) { if (condition) return out.write(c); return 0; } + void flush() { if (condition) out.flush(); } + void begin(long br) { out.begin(br); } + void end() { out.end(); } + + void msgDone() {} + bool connected() { return CALL_IF_EXISTS(bool, &out, connected); } + void flushTX() { CALL_IF_EXISTS(void, &out, flushTX); } + + int available(serial_index_t) { return (int)out.available(); } + int read(serial_index_t) { return (int)out.read(); } + int available() { return (int)out.available(); } + int read() { return (int)out.read(); } + SerialFeature features(serial_index_t index) const { return CALL_IF_EXISTS(SerialFeature, &out, features, index); } + + ConditionalSerial(bool & conditionVariable, SerialT & out, const bool e) : BaseClassT(e), condition(conditionVariable), out(out) {} +}; + +// A simple forward class that taking a reference to an existing serial instance (likely created in their respective framework) +template +struct ForwardSerial : public SerialBase< ForwardSerial > { + typedef SerialBase< ForwardSerial > BaseClassT; + + SerialT & out; + NO_INLINE size_t write(uint8_t c) { return out.write(c); } + void flush() { out.flush(); } + void begin(long br) { out.begin(br); } + void end() { out.end(); } + + void msgDone() {} + // Existing instances implement Arduino's operator bool, so use that if it's available + bool connected() { return Private::HasMember_connected::value ? CALL_IF_EXISTS(bool, &out, connected) : (bool)out; } + void flushTX() { CALL_IF_EXISTS(void, &out, flushTX); } + + int available(serial_index_t) { return (int)out.available(); } + int read(serial_index_t) { return (int)out.read(); } + int available() { return (int)out.available(); } + int read() { return (int)out.read(); } + SerialFeature features(serial_index_t index) const { return CALL_IF_EXISTS(SerialFeature, &out, features, index); } + + ForwardSerial(const bool e, SerialT & out) : BaseClassT(e), out(out) {} +}; + +// A class that can be hooked and unhooked at runtime, useful to capture the output of the serial interface +template +struct RuntimeSerial : public SerialBase< RuntimeSerial >, public SerialT { + typedef SerialBase< RuntimeSerial > BaseClassT; + typedef void (*WriteHook)(void * userPointer, uint8_t c); + typedef void (*EndOfMessageHook)(void * userPointer); + + WriteHook writeHook; + EndOfMessageHook eofHook; + void * userPointer; + + NO_INLINE size_t write(uint8_t c) { + if (writeHook) writeHook(userPointer, c); + return SerialT::write(c); + } + + NO_INLINE void msgDone() { + if (eofHook) eofHook(userPointer); + } + + int available(serial_index_t) { return (int)SerialT::available(); } + int read(serial_index_t) { return (int)SerialT::read(); } + using SerialT::available; + using SerialT::read; + using SerialT::flush; + using SerialT::begin; + using SerialT::end; + + using BaseClassT::print; + using BaseClassT::println; + + // Underlying implementation might use Arduino's bool operator + bool connected() { + return Private::HasMember_connected::value + ? CALL_IF_EXISTS(bool, static_cast(this), connected) + : static_cast(this)->operator bool(); + } + + void flushTX() { CALL_IF_EXISTS(void, static_cast(this), flushTX); } + + // Append Hookable for this class + SerialFeature features(serial_index_t index) const { return SerialFeature::Hookable | CALL_IF_EXISTS(SerialFeature, static_cast(this), features, index); } + + void setHook(WriteHook writeHook=0, EndOfMessageHook eofHook=0, void * userPointer=0) { + // Order is important here as serial code can be called inside interrupts + // When setting a hook, the user pointer must be set first so if writeHook is called as soon as it's set, it'll be valid + if (userPointer) this->userPointer = userPointer; + this->writeHook = writeHook; + this->eofHook = eofHook; + // Order is important here because of asynchronous access here + // When unsetting a hook, the user pointer must be unset last so that any pending writeHook is still using the old pointer + if (!userPointer) this->userPointer = 0; + } + + RuntimeSerial(const bool e) : BaseClassT(e), writeHook(0), eofHook(0), userPointer(0) {} + + // Forward constructor + template + RuntimeSerial(const bool e, Args... args) : BaseClassT(e), SerialT(args...), writeHook(0), eofHook(0), userPointer(0) {} +}; + +#define _S_CLASS(N) class Serial##N##T, +#define _S_NAME(N) Serial##N##T, + +template < REPEAT(NUM_SERIAL, _S_CLASS) const uint8_t offset=0, const uint8_t step=1 > +struct MultiSerial : public SerialBase< MultiSerial< REPEAT(NUM_SERIAL, _S_NAME) offset, step > > { + typedef SerialBase< MultiSerial< REPEAT(NUM_SERIAL, _S_NAME) offset, step > > BaseClassT; + + #undef _S_CLASS + #undef _S_NAME + + SerialMask portMask; + + #define _S_DECLARE(N) Serial##N##T & serial##N; + REPEAT(NUM_SERIAL, _S_DECLARE); + #undef _S_DECLARE + + static constexpr uint8_t Usage = _BV(step) - 1; // A bit mask containing 'step' bits + + #define _OUT_PORT(N) (Usage << (offset + (step * N))), + static constexpr uint8_t output[] = { REPEAT(NUM_SERIAL, _OUT_PORT) }; + #undef _OUT_PORT + + #define _OUT_MASK(N) | output[N] + static constexpr uint8_t ALL = 0 REPEAT(NUM_SERIAL, _OUT_MASK); + #undef _OUT_MASK + + NO_INLINE void write(uint8_t c) { + #define _S_WRITE(N) if (portMask.enabled(output[N])) serial##N.write(c); + REPEAT(NUM_SERIAL, _S_WRITE); + #undef _S_WRITE + } + NO_INLINE void msgDone() { + #define _S_DONE(N) if (portMask.enabled(output[N])) serial##N.msgDone(); + REPEAT(NUM_SERIAL, _S_DONE); + #undef _S_DONE + } + int available(serial_index_t index) { + uint8_t pos = offset; + #define _S_AVAILABLE(N) if (index.within(pos, pos + step - 1)) return serial##N.available(index); else pos += step; + REPEAT(NUM_SERIAL, _S_AVAILABLE); + #undef _S_AVAILABLE + return false; + } + int read(serial_index_t index) { + uint8_t pos = offset; + #define _S_READ(N) if (index.within(pos, pos + step - 1)) return serial##N.read(index); else pos += step; + REPEAT(NUM_SERIAL, _S_READ); + #undef _S_READ + return -1; + } + void begin(const long br) { + #define _S_BEGIN(N) if (portMask.enabled(output[N])) serial##N.begin(br); + REPEAT(NUM_SERIAL, _S_BEGIN); + #undef _S_BEGIN + } + void end() { + #define _S_END(N) if (portMask.enabled(output[N])) serial##N.end(); + REPEAT(NUM_SERIAL, _S_END); + #undef _S_END + } + bool connected() { + bool ret = true; + #define _S_CONNECTED(N) if (portMask.enabled(output[N]) && !CALL_IF_EXISTS(bool, &serial##N, connected)) ret = false; + REPEAT(NUM_SERIAL, _S_CONNECTED); + #undef _S_CONNECTED + return ret; + } + + using BaseClassT::available; + using BaseClassT::read; + + // Redirect flush + NO_INLINE void flush() { + #define _S_FLUSH(N) if (portMask.enabled(output[N])) serial##N.flush(); + REPEAT(NUM_SERIAL, _S_FLUSH); + #undef _S_FLUSH + } + NO_INLINE void flushTX() { + #define _S_FLUSHTX(N) if (portMask.enabled(output[N])) CALL_IF_EXISTS(void, &serial0, flushTX); + REPEAT(NUM_SERIAL, _S_FLUSHTX); + #undef _S_FLUSHTX + } + + // Forward feature queries + SerialFeature features(serial_index_t index) const { + uint8_t pos = offset; + #define _S_FEATURES(N) if (index.within(pos, pos + step - 1)) return serial##N.features(index); else pos += step; + REPEAT(NUM_SERIAL, _S_FEATURES); + #undef _S_FEATURES + return SerialFeature::None; + } + + #define _S_REFS(N) Serial##N##T & serial##N, + #define _S_INIT(N) ,serial##N (serial##N) + + MultiSerial(REPEAT(NUM_SERIAL, _S_REFS) const SerialMask mask=ALL, const bool e=false) + : BaseClassT(e), portMask(mask) REPEAT(NUM_SERIAL, _S_INIT) {} + +}; + +// Build the actual serial object depending on current configuration +#define Serial1Class TERN(SERIAL_RUNTIME_HOOK, RuntimeSerial, BaseSerial) +#define ForwardSerial1Class TERN(SERIAL_RUNTIME_HOOK, RuntimeSerial, ForwardSerial) +#if HAS_MULTI_SERIAL + #define Serial2Class ConditionalSerial + #if NUM_SERIAL >= 3 + #define Serial3Class ConditionalSerial + #endif +#endif diff --git a/Marlin/src/core/types.h b/Marlin/src/core/types.h index a5b78caabb..0881bc47a1 100644 --- a/Marlin/src/core/types.h +++ b/Marlin/src/core/types.h @@ -26,68 +26,420 @@ #include "../inc/MarlinConfigPre.h" -class __FlashStringHelper; -typedef const __FlashStringHelper *progmem_str; +// +// Conditional type assignment magic. For example... +// +// typename IF<(MYOPT==12), int, float>::type myvar; +// +template struct IF { typedef R type; }; +template struct IF { typedef L type; }; + +#define ALL_AXIS_NAMES X, X2, Y, Y2, Z, Z2, Z3, Z4, I, J, K, U, V, W, E0, E1, E2, E3, E4, E5, E6, E7 + +#define NUM_AXIS_GANG(V...) GANG_N(NUM_AXES, V) +#define NUM_AXIS_CODE(V...) CODE_N(NUM_AXES, V) +#define NUM_AXIS_LIST(V...) LIST_N(NUM_AXES, V) +#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_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(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(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_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 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_ITEM_E(EA) LIST_ITEM_E(EB) + +#if NUM_AXES + #define NUM_AXES_SEP , + #define MAIN_AXIS_MAP(F) MAP(F, MAIN_AXIS_NAMES) + #define MAIN_AXIS_MAP_LC(F) MAP(F, MAIN_AXIS_NAMES_LC) + #define OPTARGS_NUM(T) , NUM_AXIS_ARGS_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 + +#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_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 + +#if LOGICAL_AXES + #define LOGICAL_AXES_SEP , +#else + #define LOGICAL_AXES_SEP +#endif + +#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_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) +#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 + #define XYZ_COUNT 3 + #define XY_COUNT 2 +#elif HAS_Y_AXIS + #define XY_COUNT 2 +#elif HAS_X_AXIS + #define XY_COUNT 1 +#else + #define XY_COUNT 0 +#endif +#ifndef XYZ_COUNT + #define XYZ_COUNT XY_COUNT +#endif +#define XY_LIST(V...) LIST_N(XY_COUNT, V) +#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 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) + +#if HAS_ROTATIONAL_AXES + #define ROTATIONAL_AXIS_GANG(V...) GANG_N(ROTATIONAL_AXES, V) +#endif + +#if HAS_EXTRUDERS + #if NUM_AXES + #define LIST_ITEM_E(N) , N + #define CODE_ITEM_E(N) ; N + #else + #define LIST_ITEM_E(N) N + #define CODE_ITEM_E(N) N + #endif + #define GANG_ITEM_E(N) N +#else + #define LIST_ITEM_E(N) + #define CODE_ITEM_E(N) + #define GANG_ITEM_E(N) +#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) < 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) +#define _RSE(N) N = T(uint32_t(N) >> p) +#define FI FORCE_INLINE + +// Define types based on largest bit width stored value required +#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 + +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 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(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 != 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, false> { + bool b; + FI void reset() { b = false; } + FI void set(const int n, const bool onoff) { onoff ? set(n) : clear(n); } + FI void set(const int) { b = true; } + FI void clear(const int) { b = false; } + FI bool test(const int) const { return b; } + FI bool& operator[](const int) { return b; } + FI bool operator[](const int) const { return b; } + FI int size() const { return sizeof(b); } + FI operator bool() const { return b; } +}; + +typedef Flags<8> flags_8_t; +typedef Flags<16> flags_16_t; + +// Flags for some axis states, with per-axis aliases xyzijkuvwe +typedef struct { + union { + struct Flags flags; + struct { bool LOGICAL_AXIS_LIST(e:1, x:1, y:1, z:1, i:1, j:1, k:1, u:1, v:1, w:1); }; + }; + FI void reset() { flags.reset(); } + FI void set(const int n) { flags.set(n); } + FI void set(const int n, const bool onoff) { flags.set(n, onoff); } + FI void clear(const int n) { flags.clear(n); } + FI bool test(const int n) const { return flags.test(n); } + 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 (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_HEAD, Y_HEAD, and Z_HEAD should be used for axes on Core kinematics // enum AxisEnum : uint8_t { - X_AXIS = 0, A_AXIS = 0, - Y_AXIS = 1, B_AXIS = 1, - Z_AXIS = 2, C_AXIS = 2, - E_AXIS = 3, - X_HEAD = 4, Y_HEAD = 5, Z_HEAD = 6, - E0_AXIS = 3, - E1_AXIS, E2_AXIS, E3_AXIS, E4_AXIS, E5_AXIS, E6_AXIS, E7_AXIS, - ALL_AXES = 0xFE, NO_AXIS = 0xFF + + // Linear axes may be controlled directly or indirectly + NUM_AXIS_LIST_(X_AXIS, Y_AXIS, Z_AXIS, I_AXIS, J_AXIS, K_AXIS, U_AXIS, V_AXIS, W_AXIS) + + #define _EN_ITEM(N) E##N##_AXIS, + REPEAT(EXTRUDERS, _EN_ITEM) + #undef _EN_ITEM + + // Core also keeps toolhead directions + #if ANY(IS_CORE, MARKFORGED_XY, MARKFORGED_YX) + X_HEAD, Y_HEAD, Z_HEAD, + #endif + + // Distinct axes, including all E and Core + NUM_AXIS_HEADS, + + // Most of the time we refer only to the single E_AXIS + #if HAS_EXTRUDERS + E_AXIS = E0_AXIS, + #endif + + // A, B, and C are for DELTA, SCARA, etc. + #if HAS_X_AXIS + A_AXIS = X_AXIS, + #endif + #if HAS_Y_AXIS + B_AXIS = Y_AXIS, + #endif + #if HAS_Z_AXIS + C_AXIS = Z_AXIS, + #endif + + // To refer to all or none + ALL_AXES_ENUM = 0xFE, NO_AXIS_ENUM = 0xFF }; // -// Loop over XYZE axes +// Loop over axes // -#define LOOP_XYZ(VAR) LOOP_S_LE_N(VAR, X_AXIS, Z_AXIS) -#define LOOP_XYZE(VAR) LOOP_S_LE_N(VAR, X_AXIS, E_AXIS) -#define LOOP_XYZE_N(VAR) LOOP_S_L_N(VAR, X_AXIS, XYZE_N) -#define LOOP_ABC(VAR) LOOP_S_LE_N(VAR, A_AXIS, C_AXIS) -#define LOOP_ABCE(VAR) LOOP_S_LE_N(VAR, A_AXIS, E_AXIS) -#define LOOP_ABCE_N(VAR) LOOP_S_L_N(VAR, A_AXIS, XYZE_N) +#define LOOP_ABC(VAR) for (uint8_t VAR = A_AXIS; VAR <= C_AXIS; ++VAR) +#define LOOP_NUM_AXES(VAR) for (uint8_t VAR = 0; VAR < NUM_AXES; ++VAR) +#define LOOP_LOGICAL_AXES(VAR) for (uint8_t VAR = 0; VAR < LOGICAL_AXES; ++VAR) +#define LOOP_DISTINCT_AXES(VAR) for (uint8_t VAR = 0; VAR < DISTINCT_AXES; ++VAR) +#define LOOP_DISTINCT_E(VAR) for (uint8_t VAR = 0; VAR < DISTINCT_E; ++VAR) // -// Conditional type assignment magic. For example... -// -// typename IF<(MYOPT==12), int, float>::type myvar; -// -template -struct IF { typedef R type; }; -template -struct IF { typedef L type; }; - -// -// 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 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; + +// Type large enough to count leveling grid points +typedef IF 255)), uint16_t, uint8_t>::type grid_count_t; + // Conversion macros -#define MMM_TO_MMS(MM_M) feedRate_t(float(MM_M) / 60.0f) -#define MMS_TO_MMM(MM_S) (float(MM_S) * 60.0f) -#define MMS_SCALED(V) ((V) * 0.01f * feedrate_percentage) +#define MMM_TO_MMS(MM_M) feedRate_t(static_cast(MM_M) / 60.0f) +#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(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 +typedef struct WFloat { float value; char width; char prec; + WFloat(float v, char w, char p) : value(v), width(w), prec(p) {} + } w_float_t; +typedef struct PFloat { float value; char prec; + PFloat(float v, char p) : value(v), prec(p) {} + } p_float_t; +typedef struct RepChr { char asc; int8_t count; + RepChr(char a, uint8_t c) : asc(a), count(c) {} + } repchr_t; +typedef struct Spaces { int8_t count; + Spaces(uint8_t c) : count(c) {} + } spaces_t; + +#ifdef __AVR__ + typedef w_float_t w_double_t; + typedef p_float_t p_double_t; +#else + typedef struct WDouble { double value; char width; char prec; + WDouble(double v, char w, char p) : value(v), width(w), prec(p) {} + } w_double_t; + typedef struct PDouble { double value; char prec; + PDouble(double v, char p) : value(v), prec(p) {} + } p_double_t; +#endif // // Coordinates structures for XY, XYZ, XYZE... // -// Helpers -#define _RECIP(N) ((N) ? 1.0f / float(N) : 0.0f) -#define _ABS(N) ((N) < 0 ? -(N) : (N)) -#define _LS(N) (N = (T)(uint32_t(N) << v)) -#define _RS(N) (N = (T)(uint32_t(N) >> v)) -#define FI FORCE_INLINE - // Forward declarations template struct XYval; template struct XYZval; @@ -161,344 +513,839 @@ 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); -void toNative(xy_pos_t &raw); -void toNative(xyz_pos_t &raw); -void toNative(xyze_pos_t &raw); +void toNative(xy_pos_t &lpos); +void toNative(xyz_pos_t &lpos); +void toNative(xyze_pos_t &lpos); // -// XY coordinates, counters, etc. +// Paired XY coordinates, counters, flags, etc. +// Always has XY elements regardless of the number of configured axes. // template struct XYval { union { struct { T x, y; }; + struct { T X, Y; }; struct { T a, b; }; + struct { T A, B; }; T pos[2]; }; - FI void set(const T px) { x = px; } - 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)[XYZ]) { x = arr[0]; y = arr[1]; } - FI void set(const T (&arr)[XYZE]) { x = arr[0]; y = arr[1]; } - #if XYZE_N > XYZE - FI void set(const T (&arr)[XYZE_N]) { x = arr[0]; y = arr[1]; } + + // Set all to 0 + FI void reset() { x = y = 0; } + + // Setters taking struct types and arrays + #if HAS_X_AXIS + FI void set(const T px) { x = px; } #endif - FI void reset() { x = y = 0; } - FI T magnitude() const { return (T)sqrtf(x*x + y*y); } - FI operator T* () { return pos; } - FI operator bool() { return x || y; } - FI XYval copy() const { return *this; } - FI XYval ABS() const { return { T(_ABS(x)), T(_ABS(y)) }; } - FI XYval asInt() { return { int16_t(x), int16_t(y) }; } - FI XYval asInt() const { return { int16_t(x), int16_t(y) }; } - FI XYval asLong() { return { int32_t(x), int32_t(y) }; } - FI XYval asLong() const { return { int32_t(x), int32_t(y) }; } - FI XYval ROUNDL() { return { int32_t(LROUND(x)), int32_t(LROUND(y)) }; } - FI XYval ROUNDL() const { return { int32_t(LROUND(x)), int32_t(LROUND(y)) }; } - FI XYval asFloat() { return { float(x), float(y) }; } - FI XYval asFloat() const { return { float(x), float(y) }; } - FI XYval reciprocal() const { return { _RECIP(x), _RECIP(y) }; } - FI XYval asLogical() const { XYval o = asFloat(); toLogical(o); return o; } - FI XYval asNative() const { XYval o = asFloat(); toNative(o); return o; } - FI operator XYZval() { return { x, y }; } - FI operator XYZval() const { return { x, y }; } - FI operator XYZEval() { return { x, y }; } - FI operator XYZEval() const { return { x, y }; } - FI T& operator[](const int i) { return pos[i]; } - FI const T& operator[](const int i) const { return pos[i]; } - FI XYval& operator= (const T v) { set(v, v ); return *this; } - FI XYval& operator= (const XYZval &rs) { set(rs.x, rs.y); return *this; } - FI XYval& operator= (const XYZEval &rs) { set(rs.x, rs.y); return *this; } - FI XYval operator+ (const XYval &rs) const { XYval ls = *this; ls.x += rs.x; ls.y += rs.y; return ls; } - FI XYval operator+ (const XYval &rs) { XYval ls = *this; ls.x += rs.x; ls.y += rs.y; return ls; } - FI XYval operator- (const XYval &rs) const { XYval ls = *this; ls.x -= rs.x; ls.y -= rs.y; return ls; } - FI XYval operator- (const XYval &rs) { XYval ls = *this; ls.x -= rs.x; ls.y -= rs.y; return ls; } - FI XYval operator* (const XYval &rs) const { XYval ls = *this; ls.x *= rs.x; ls.y *= rs.y; return ls; } - FI XYval operator* (const XYval &rs) { XYval ls = *this; ls.x *= rs.x; ls.y *= rs.y; return ls; } - FI XYval operator/ (const XYval &rs) const { XYval ls = *this; ls.x /= rs.x; ls.y /= rs.y; return ls; } - FI XYval operator/ (const XYval &rs) { XYval ls = *this; ls.x /= rs.x; ls.y /= rs.y; return ls; } - FI XYval operator+ (const XYZval &rs) const { XYval ls = *this; ls.x += rs.x; ls.y += rs.y; return ls; } - FI XYval operator+ (const XYZval &rs) { XYval ls = *this; ls.x += rs.x; ls.y += rs.y; return ls; } - FI XYval operator- (const XYZval &rs) const { XYval ls = *this; ls.x -= rs.x; ls.y -= rs.y; return ls; } - FI XYval operator- (const XYZval &rs) { XYval ls = *this; ls.x -= rs.x; ls.y -= rs.y; return ls; } - FI XYval operator* (const XYZval &rs) const { XYval ls = *this; ls.x *= rs.x; ls.y *= rs.y; return ls; } - FI XYval operator* (const XYZval &rs) { XYval ls = *this; ls.x *= rs.x; ls.y *= rs.y; return ls; } - FI XYval operator/ (const XYZval &rs) const { XYval ls = *this; ls.x /= rs.x; ls.y /= rs.y; return ls; } - FI XYval operator/ (const XYZval &rs) { XYval ls = *this; ls.x /= rs.x; ls.y /= rs.y; return ls; } - FI XYval operator+ (const XYZEval &rs) const { XYval ls = *this; ls.x += rs.x; ls.y += rs.y; return ls; } - FI XYval operator+ (const XYZEval &rs) { XYval ls = *this; ls.x += rs.x; ls.y += rs.y; return ls; } - FI XYval operator- (const XYZEval &rs) const { XYval ls = *this; ls.x -= rs.x; ls.y -= rs.y; return ls; } - FI XYval operator- (const XYZEval &rs) { XYval ls = *this; ls.x -= rs.x; ls.y -= rs.y; return ls; } - FI XYval operator* (const XYZEval &rs) const { XYval ls = *this; ls.x *= rs.x; ls.y *= rs.y; return ls; } - FI XYval operator* (const XYZEval &rs) { XYval ls = *this; ls.x *= rs.x; ls.y *= rs.y; return ls; } - FI XYval operator/ (const XYZEval &rs) const { XYval ls = *this; ls.x /= rs.x; ls.y /= rs.y; return ls; } - FI XYval operator/ (const XYZEval &rs) { XYval ls = *this; ls.x /= rs.x; ls.y /= rs.y; return ls; } - FI XYval operator* (const float &v) const { XYval ls = *this; ls.x *= v; ls.y *= v; return ls; } - FI XYval operator* (const float &v) { XYval ls = *this; ls.x *= v; ls.y *= v; return ls; } - FI XYval operator* (const int &v) const { XYval ls = *this; ls.x *= v; ls.y *= v; return ls; } - FI XYval operator* (const int &v) { XYval ls = *this; ls.x *= v; ls.y *= v; return ls; } - FI XYval operator/ (const float &v) const { XYval ls = *this; ls.x /= v; ls.y /= v; return ls; } - FI XYval operator/ (const float &v) { XYval ls = *this; ls.x /= v; ls.y /= v; return ls; } - FI XYval operator/ (const int &v) const { XYval ls = *this; ls.x /= v; ls.y /= v; return ls; } - FI XYval operator/ (const int &v) { XYval ls = *this; ls.x /= v; ls.y /= v; return ls; } - FI XYval operator>>(const int &v) const { XYval ls = *this; _RS(ls.x); _RS(ls.y); return ls; } - FI XYval operator>>(const int &v) { XYval ls = *this; _RS(ls.x); _RS(ls.y); return ls; } - FI XYval operator<<(const int &v) const { XYval ls = *this; _LS(ls.x); _LS(ls.y); return ls; } - FI XYval operator<<(const int &v) { XYval ls = *this; _LS(ls.x); _LS(ls.y); return ls; } - FI XYval& operator+=(const XYval &rs) { x += rs.x; y += rs.y; return *this; } - FI XYval& operator-=(const XYval &rs) { x -= rs.x; y -= rs.y; return *this; } - FI XYval& operator*=(const XYval &rs) { x *= rs.x; y *= rs.y; return *this; } - FI XYval& operator+=(const XYZval &rs) { x += rs.x; y += rs.y; return *this; } - FI XYval& operator-=(const XYZval &rs) { x -= rs.x; y -= rs.y; return *this; } - FI XYval& operator*=(const XYZval &rs) { x *= rs.x; y *= rs.y; return *this; } - FI XYval& operator+=(const XYZEval &rs) { x += rs.x; y += rs.y; return *this; } - FI XYval& operator-=(const XYZEval &rs) { x -= rs.x; y -= rs.y; return *this; } - FI XYval& operator*=(const XYZEval &rs) { x *= rs.x; y *= rs.y; return *this; } - FI XYval& operator*=(const float &v) { x *= v; y *= v; return *this; } - FI XYval& operator*=(const int &v) { x *= v; y *= v; return *this; } - FI XYval& operator>>=(const int &v) { _RS(x); _RS(y); return *this; } - FI XYval& operator<<=(const int &v) { _LS(x); _LS(y); return *this; } - FI bool operator==(const XYval &rs) { return x == rs.x && y == rs.y; } - FI bool operator==(const XYZval &rs) { return x == rs.x && y == rs.y; } - FI bool operator==(const XYZEval &rs) { 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 x == rs.x && y == rs.y; } - FI bool operator==(const XYZEval &rs) const { return x == rs.x && y == rs.y; } - FI bool operator!=(const XYval &rs) { return !operator==(rs); } - FI bool operator!=(const XYZval &rs) { return !operator==(rs); } - FI bool operator!=(const XYZEval &rs) { return !operator==(rs); } - 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); } - FI XYval operator-() { XYval o = *this; o.x = -x; o.y = -y; return o; } - FI const XYval operator-() const { XYval o = *this; o.x = -x; o.y = -y; return o; } + #if HAS_Y_AXIS + FI void set(const T px, const T py) { x = px; y = py; } + FI void set(const T (&arr)[2]) { x = arr[0]; y = arr[1]; } + #endif + #if NUM_AXES > 2 + FI void set(const T (&arr)[NUM_AXES]) { x = arr[0]; y = arr[1]; } + #endif + #if LOGICAL_AXES > NUM_AXES + FI void set(const T (&arr)[LOGICAL_AXES]) { x = arr[0]; y = arr[1]; } + #if DISTINCT_AXES > LOGICAL_AXES + FI void set(const T (&arr)[DISTINCT_AXES]) { x = arr[0]; y = arr[1]; } + #endif + #endif + + // Length reduced to one dimension + FI constexpr T magnitude() const { return (T)SQRT(x*x + y*y); } + // Pointer to the data as a simple array + 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 + FI constexpr T small() const { return _MIN(x, y); } + // Largest element + 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 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; } + FI XYval asNative() const { XYval o = asFloat(); toNative(o); return o; } + + // Cast to a type with more fields by making a new object + FI constexpr operator XYZval() const { return { x, y }; } + FI constexpr operator XYZEval() const { return { x, y }; } + + // Accessor via an AxisEnum (or any integer) [index] + FI T& operator[](const int n) { return pos[n]; } + FI const T& operator[](const int n) const { return pos[n]; } + + // Assignment operator overrides do the expected thing + FI XYval& operator= (const T v) { set(v, v); return *this; } + FI XYval& operator= (const XYZval &rs) { set(XY_LIST(rs.x, rs.y)); return *this; } + 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) { 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(*) }; } + FI constexpr XYval operator/ (const XYZval &rs) const { return { XY_OP(/) }; } + FI constexpr XYval operator+ (const XYZEval &rs) const { return { XY_OP(+) }; } + FI constexpr XYval operator- (const XYZEval &rs) const { return { XY_OP(-) }; } + FI constexpr XYval operator* (const XYZEval &rs) const { return { XY_OP(*) }; } + FI constexpr XYval operator/ (const XYZEval &rs) const { return { XY_OP(/) }; } + FI constexpr XYval operator* (const float &p) const { return { (T)(x * p), (T)(y * p) }; } + FI constexpr XYval operator* (const int &p) const { return { x * p, y * p }; } + FI constexpr XYval operator/ (const float &p) const { return { (T)(x / p), (T)(y / p) }; } + FI constexpr XYval operator/ (const int &p) const { return { x / p, y / p }; } + FI constexpr XYval operator>>(const int &p) const { return { _RS(x), _RS(y) }; } + FI constexpr XYval operator<<(const int &p) const { return { _LS(x), _LS(y) }; } + FI constexpr XYval operator-() const { return { -x, -y }; } + #undef XY_OP + + // Modifier operators + FI XYval& operator+=(const XYval &rs) { x += rs.x; y += rs.y; return *this; } + FI XYval& operator-=(const XYval &rs) { x -= rs.x; y -= rs.y; return *this; } + FI XYval& operator*=(const XYval &rs) { x *= rs.x; y *= rs.y; return *this; } + FI XYval& operator/=(const XYval &rs) { x /= rs.x; y /= rs.y; return *this; } + FI XYval& operator+=(const XYZval &rs) { XY_CODE(x += rs.x, y += rs.y); return *this; } + FI XYval& operator-=(const XYZval &rs) { XY_CODE(x -= rs.x, y -= rs.y); return *this; } + FI XYval& operator*=(const XYZval &rs) { XY_CODE(x *= rs.x, y *= rs.y); return *this; } + FI XYval& operator/=(const XYZval &rs) { XY_CODE(x /= rs.x, y /= rs.y); return *this; } + FI XYval& operator+=(const XYZEval &rs) { XY_CODE(x += rs.x, y += rs.y); return *this; } + FI XYval& operator-=(const XYZEval &rs) { XY_CODE(x -= rs.x, y -= rs.y); return *this; } + FI XYval& operator*=(const XYZEval &rs) { XY_CODE(x *= rs.x, y *= rs.y); return *this; } + FI XYval& operator/=(const XYZEval &rs) { XY_CODE(x /= rs.x, y /= rs.y); return *this; } + FI XYval& operator*=(const float &p) { x *= p; y *= p; return *this; } + FI XYval& operator*=(const int &p) { x *= p; y *= p; return *this; } + FI XYval& operator>>=(const int &p) { _RSE(x); _RSE(y); return *this; } + FI XYval& operator<<=(const int &p) { _LSE(x); _LSE(y); return *this; } + + // Absolute difference between two objects + FI constexpr XYval diff(const XYZEval &rs) const { return { TERN(HAS_X_AXIS, T(_ABS(x - rs.x)), x), TERN(HAS_Y_AXIS, T(_ABS(y - rs.y)), y) }; } + FI constexpr XYval diff(const XYZval &rs) const { return { TERN(HAS_X_AXIS, T(_ABS(x - rs.x)), x), TERN(HAS_Y_AXIS, T(_ABS(y - rs.y)), y) }; } + FI constexpr XYval diff(const XYval &rs) const { return { T(_ABS(x - rs.x)), T(_ABS(y - rs.y)) }; } + + // 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 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 true XY_GANG(&& x < rs.x, && y < rs.y); } + FI bool operator<=(const XYZval &rs) const { return true XY_GANG(&& x <= rs.x, && y <= rs.y); } + FI bool operator> (const XYZval &rs) const { return true XY_GANG(&& x > rs.x, && y > rs.y); } + FI bool operator>=(const XYZval &rs) const { return true XY_GANG(&& x >= rs.x, && y >= rs.y); } + + FI bool operator< (const XYZEval &rs) const { return true XY_GANG(&& x < rs.x, && y < rs.y); } + FI bool operator<=(const XYZEval &rs) const { return true XY_GANG(&& x <= rs.x, && y <= rs.y); } + FI bool operator> (const XYZEval &rs) const { return true XY_GANG(&& x > rs.x, && y > rs.y); } + FI bool operator>=(const XYZEval &rs) const { return true XY_GANG(&& x >= rs.x, && y >= rs.y); } + }; // -// XYZ coordinates, counters, etc. +// Linear Axes coordinates, counters, flags, etc. +// May have any number of axes according to configuration, including zero axes. // template struct XYZval { union { - struct { T x, y, z; }; - struct { T a, b, c; }; - T pos[3]; + #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]; }; - FI void set(const T px) { x = px; } - FI void set(const T px, const T py) { x = px; y = py; } - FI void set(const T px, const T py, const T pz) { x = px; y = py; z = pz; } - FI void set(const XYval pxy, const T pz) { x = pxy.x; y = pxy.y; z = pz; } - FI void set(const T (&arr)[XY]) { x = arr[0]; y = arr[1]; } - FI void set(const T (&arr)[XYZ]) { x = arr[0]; y = arr[1]; z = arr[2]; } - FI void set(const T (&arr)[XYZE]) { x = arr[0]; y = arr[1]; z = arr[2]; } - #if XYZE_N > XYZE - FI void set(const T (&arr)[XYZE_N]) { x = arr[0]; y = arr[1]; z = arr[2]; } + + // Set all to 0 + 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 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_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 - FI void reset() { x = y = z = 0; } - FI T magnitude() const { return (T)sqrtf(x*x + y*y + z*z); } - FI operator T* () { return pos; } - FI operator bool() { return z || x || y; } - FI XYZval copy() const { XYZval o = *this; return o; } - FI XYZval ABS() const { return { T(_ABS(x)), T(_ABS(y)), T(_ABS(z)) }; } - FI XYZval asInt() { return { int16_t(x), int16_t(y), int16_t(z) }; } - FI XYZval asInt() const { return { int16_t(x), int16_t(y), int16_t(z) }; } - FI XYZval asLong() { return { int32_t(x), int32_t(y), int32_t(z) }; } - FI XYZval asLong() const { return { int32_t(x), int32_t(y), int32_t(z) }; } - FI XYZval ROUNDL() { return { int32_t(LROUND(x)), int32_t(LROUND(y)), int32_t(LROUND(z)) }; } - FI XYZval ROUNDL() const { return { int32_t(LROUND(x)), int32_t(LROUND(y)), int32_t(LROUND(z)) }; } - FI XYZval asFloat() { return { float(x), float(y), float(z) }; } - FI XYZval asFloat() const { return { float(x), float(y), float(z) }; } - FI XYZval reciprocal() const { return { _RECIP(x), _RECIP(y), _RECIP(z) }; } - FI XYZval asLogical() const { XYZval o = asFloat(); toLogical(o); return o; } - FI XYZval asNative() const { XYZval o = asFloat(); toNative(o); return o; } - FI operator XYval&() { return *(XYval*)this; } - FI operator const XYval&() const { return *(const XYval*)this; } - FI operator XYZEval() const { return { x, y, z }; } - FI T& operator[](const int i) { return pos[i]; } - FI const T& operator[](const int i) const { return pos[i]; } - FI XYZval& operator= (const T v) { set(v, v, v ); return *this; } - FI XYZval& operator= (const XYval &rs) { set(rs.x, rs.y ); return *this; } - FI XYZval& operator= (const XYZEval &rs) { set(rs.x, rs.y, rs.z); return *this; } - FI XYZval operator+ (const XYval &rs) const { XYZval ls = *this; ls.x += rs.x; ls.y += rs.y; return ls; } - FI XYZval operator+ (const XYval &rs) { XYZval ls = *this; ls.x += rs.x; ls.y += rs.y; return ls; } - FI XYZval operator- (const XYval &rs) const { XYZval ls = *this; ls.x -= rs.x; ls.y -= rs.y; return ls; } - FI XYZval operator- (const XYval &rs) { XYZval ls = *this; ls.x -= rs.x; ls.y -= rs.y; return ls; } - FI XYZval operator* (const XYval &rs) const { XYZval ls = *this; ls.x *= rs.x; ls.y *= rs.y; return ls; } - FI XYZval operator* (const XYval &rs) { XYZval ls = *this; ls.x *= rs.x; ls.y *= rs.y; return ls; } - FI XYZval operator/ (const XYval &rs) const { XYZval ls = *this; ls.x /= rs.x; ls.y /= rs.y; return ls; } - FI XYZval operator/ (const XYval &rs) { XYZval ls = *this; ls.x /= rs.x; ls.y /= rs.y; return ls; } - FI XYZval operator+ (const XYZval &rs) const { XYZval ls = *this; ls.x += rs.x; ls.y += rs.y; ls.z += rs.z; return ls; } - FI XYZval operator+ (const XYZval &rs) { XYZval ls = *this; ls.x += rs.x; ls.y += rs.y; ls.z += rs.z; return ls; } - FI XYZval operator- (const XYZval &rs) const { XYZval ls = *this; ls.x -= rs.x; ls.y -= rs.y; ls.z -= rs.z; return ls; } - FI XYZval operator- (const XYZval &rs) { XYZval ls = *this; ls.x -= rs.x; ls.y -= rs.y; ls.z -= rs.z; return ls; } - FI XYZval operator* (const XYZval &rs) const { XYZval ls = *this; ls.x *= rs.x; ls.y *= rs.y; ls.z *= rs.z; return ls; } - FI XYZval operator* (const XYZval &rs) { XYZval ls = *this; ls.x *= rs.x; ls.y *= rs.y; ls.z *= rs.z; return ls; } - FI XYZval operator/ (const XYZval &rs) const { XYZval ls = *this; ls.x /= rs.x; ls.y /= rs.y; ls.z /= rs.z; return ls; } - FI XYZval operator/ (const XYZval &rs) { XYZval ls = *this; ls.x /= rs.x; ls.y /= rs.y; ls.z /= rs.z; return ls; } - FI XYZval operator+ (const XYZEval &rs) const { XYZval ls = *this; ls.x += rs.x; ls.y += rs.y; ls.z += rs.z; return ls; } - FI XYZval operator+ (const XYZEval &rs) { XYZval ls = *this; ls.x += rs.x; ls.y += rs.y; ls.z += rs.z; return ls; } - FI XYZval operator- (const XYZEval &rs) const { XYZval ls = *this; ls.x -= rs.x; ls.y -= rs.y; ls.z -= rs.z; return ls; } - FI XYZval operator- (const XYZEval &rs) { XYZval ls = *this; ls.x -= rs.x; ls.y -= rs.y; ls.z -= rs.z; return ls; } - FI XYZval operator* (const XYZEval &rs) const { XYZval ls = *this; ls.x *= rs.x; ls.y *= rs.y; ls.z *= rs.z; return ls; } - FI XYZval operator* (const XYZEval &rs) { XYZval ls = *this; ls.x *= rs.x; ls.y *= rs.y; ls.z *= rs.z; return ls; } - FI XYZval operator/ (const XYZEval &rs) const { XYZval ls = *this; ls.x /= rs.x; ls.y /= rs.y; ls.z /= rs.z; return ls; } - FI XYZval operator/ (const XYZEval &rs) { XYZval ls = *this; ls.x /= rs.x; ls.y /= rs.y; ls.z /= rs.z; return ls; } - FI XYZval operator* (const float &v) const { XYZval ls = *this; ls.x *= v; ls.y *= v; ls.z *= v; return ls; } - FI XYZval operator* (const float &v) { XYZval ls = *this; ls.x *= v; ls.y *= v; ls.z *= v; return ls; } - FI XYZval operator* (const int &v) const { XYZval ls = *this; ls.x *= v; ls.y *= v; ls.z *= v; return ls; } - FI XYZval operator* (const int &v) { XYZval ls = *this; ls.x *= v; ls.y *= v; ls.z *= v; return ls; } - FI XYZval operator/ (const float &v) const { XYZval ls = *this; ls.x /= v; ls.y /= v; ls.z /= v; return ls; } - FI XYZval operator/ (const float &v) { XYZval ls = *this; ls.x /= v; ls.y /= v; ls.z /= v; return ls; } - FI XYZval operator/ (const int &v) const { XYZval ls = *this; ls.x /= v; ls.y /= v; ls.z /= v; return ls; } - FI XYZval operator/ (const int &v) { XYZval ls = *this; ls.x /= v; ls.y /= v; ls.z /= v; return ls; } - FI XYZval operator>>(const int &v) const { XYZval ls = *this; _RS(ls.x); _RS(ls.y); _RS(ls.z); return ls; } - FI XYZval operator>>(const int &v) { XYZval ls = *this; _RS(ls.x); _RS(ls.y); _RS(ls.z); return ls; } - FI XYZval operator<<(const int &v) const { XYZval ls = *this; _LS(ls.x); _LS(ls.y); _LS(ls.z); return ls; } - FI XYZval operator<<(const int &v) { XYZval ls = *this; _LS(ls.x); _LS(ls.y); _LS(ls.z); return ls; } - FI XYZval& operator+=(const XYval &rs) { x += rs.x; y += rs.y; return *this; } - FI XYZval& operator-=(const XYval &rs) { x -= rs.x; y -= rs.y; return *this; } - FI XYZval& operator*=(const XYval &rs) { x *= rs.x; y *= rs.y; return *this; } - FI XYZval& operator/=(const XYval &rs) { x /= rs.x; y /= rs.y; return *this; } - FI XYZval& operator+=(const XYZval &rs) { x += rs.x; y += rs.y; z += rs.z; return *this; } - FI XYZval& operator-=(const XYZval &rs) { x -= rs.x; y -= rs.y; z -= rs.z; return *this; } - FI XYZval& operator*=(const XYZval &rs) { x *= rs.x; y *= rs.y; z *= rs.z; return *this; } - FI XYZval& operator/=(const XYZval &rs) { x /= rs.x; y /= rs.y; z /= rs.z; return *this; } - FI XYZval& operator+=(const XYZEval &rs) { x += rs.x; y += rs.y; z += rs.z; return *this; } - FI XYZval& operator-=(const XYZEval &rs) { x -= rs.x; y -= rs.y; z -= rs.z; return *this; } - FI XYZval& operator*=(const XYZEval &rs) { x *= rs.x; y *= rs.y; z *= rs.z; return *this; } - FI XYZval& operator/=(const XYZEval &rs) { x /= rs.x; y /= rs.y; z /= rs.z; return *this; } - FI XYZval& operator*=(const float &v) { x *= v; y *= v; z *= v; return *this; } - FI XYZval& operator*=(const int &v) { x *= v; y *= v; z *= v; return *this; } - FI XYZval& operator>>=(const int &v) { _RS(x); _RS(y); _RS(z); return *this; } - FI XYZval& operator<<=(const int &v) { _LS(x); _LS(y); _LS(z); return *this; } - FI bool operator==(const XYZEval &rs) { return x == rs.x && y == rs.y && z == rs.z; } - FI bool operator!=(const XYZEval &rs) { return !operator==(rs); } - FI bool operator==(const XYZEval &rs) const { return x == rs.x && y == rs.y && z == rs.z; } - FI bool operator!=(const XYZEval &rs) const { return !operator==(rs); } - FI XYZval operator-() { XYZval o = *this; o.x = -x; o.y = -y; o.z = -z; return o; } - FI const XYZval operator-() const { XYZval o = *this; o.x = -x; o.y = -y; o.z = -z; return o; } + + // Setter for all individual args + 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; } + #endif + #if HAS_I_AXIS + FI void set(const T px, const T py, const T pz) { x = px; y = 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; } + #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; } + #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; } + #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; } + #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; } + #endif + + // Length reduced to one dimension + 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 + 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); } + // Smallest element + 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 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 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 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 reinterpret-cast to types having fewer fields + FI operator XYval&() { return *(XYval*)this; } + FI operator const XYval&() const { return *(const XYval*)this; } + + // Cast to a type with more fields by making a new object + FI constexpr operator XYZEval() const { return NUM_AXIS_ARRAY(x, y, z, i, j, k, u, v, w); } + + // Accessor via an AxisEnum (or any integer) [index] + FI T& operator[](const int n) { return pos[n]; } + FI const T& operator[](const int n) const { return pos[n]; } + + // 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_LC(rs)); return *this; } + + // Override other operators to get intuitive behaviors + 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 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)); } + FI constexpr XYZval operator-() const { return NUM_AXIS_ARRAY(-x, -y, -z, -i, -j, -k, -u, -v, -w); } + + // 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 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 + FI XYZval& operator+=(const XYval &rs) { XY_CODE(x += rs.x, y += rs.y); return *this; } + FI XYZval& operator-=(const XYval &rs) { XY_CODE(x -= rs.x, y -= rs.y); return *this; } + FI XYZval& operator*=(const XYval &rs) { XY_CODE(x *= rs.x, y *= rs.y); return *this; } + FI XYZval& operator/=(const XYval &rs) { XY_CODE(x /= rs.x, y /= rs.y); return *this; } + FI XYZval& operator+=(const XYZval &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 XYZval &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 XYZval &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 XYZval &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 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 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 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 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 ENABLED(HAS_X_AXIS) 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 !operator==(rs); } + + // Exact comparison to a single value + FI bool operator==(const T &p) const { return ENABLED(HAS_X_AXIS) NUM_AXIS_GANG(&& 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 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 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 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 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 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 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 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 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); } + }; // -// XYZE coordinates, counters, etc. +// Logical Axes coordinates, counters, etc. +// May have any number of axes according to configuration, including zero axes. +// When there is no extruder, essentially identical to XYZval. // template struct XYZEval { union { - struct{ T x, y, z, e; }; - struct{ T a, b, c; }; - T pos[4]; + 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]; }; - FI void reset() { x = y = z = e = 0; } - FI T magnitude() const { return (T)sqrtf(x*x + y*y + z*z + e*e); } - FI operator T* () { return pos; } - FI operator bool() { return e || z || x || y; } - FI void set(const T px) { x = px; } - FI void set(const T px, const T py) { x = px; y = py; } - 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, const T pe) { x = px; y = py; z = pz; e = pe; } - FI void set(const XYval pxy) { x = pxy.x; y = pxy.y; } - FI void set(const XYval pxy, const T pz) { x = pxy.x; y = pxy.y; z = pz; } - FI void set(const XYZval pxyz) { x = pxyz.x; y = pxyz.y; z = pxyz.z; } - FI void set(const XYval pxy, const T pz, const T pe) { x = pxy.x; y = pxy.y; z = pz; e = pe; } - FI void set(const XYval pxy, const XYval pze) { x = pxy.x; y = pxy.y; z = pze.z; e = pze.e; } - FI void set(const XYZval pxyz, const T pe) { x = pxyz.x; y = pxyz.y; z = pxyz.z; e = pe; } - FI void set(const T (&arr)[XY]) { x = arr[0]; y = arr[1]; } - FI void set(const T (&arr)[XYZ]) { x = arr[0]; y = arr[1]; z = arr[2]; } - FI void set(const T (&arr)[XYZE]) { x = arr[0]; y = arr[1]; z = arr[2]; e = arr[3]; } - #if XYZE_N > XYZE - FI void set(const T (&arr)[XYZE_N]) { x = arr[0]; y = arr[1]; z = arr[2]; e = arr[3]; } + // Reset all to 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_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_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], 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 - FI XYZEval copy() const { return *this; } - FI XYZEval ABS() const { return { T(_ABS(x)), T(_ABS(y)), T(_ABS(z)), T(_ABS(e)) }; } - FI XYZEval asInt() { return { int16_t(x), int16_t(y), int16_t(z), int16_t(e) }; } - FI XYZEval asInt() const { return { int16_t(x), int16_t(y), int16_t(z), int16_t(e) }; } - FI XYZEval asLong() { return { int32_t(x), int32_t(y), int32_t(z), int32_t(e) }; } - FI XYZEval asLong() const { return { int32_t(x), int32_t(y), int32_t(z), int32_t(e) }; } - FI XYZEval ROUNDL() { return { int32_t(LROUND(x)), int32_t(LROUND(y)), int32_t(LROUND(z)), int32_t(LROUND(e)) }; } - FI XYZEval ROUNDL() const { return { int32_t(LROUND(x)), int32_t(LROUND(y)), int32_t(LROUND(z)), int32_t(LROUND(e)) }; } - FI XYZEval asFloat() { return { float(x), float(y), float(z), float(e) }; } - FI XYZEval asFloat() const { return { float(x), float(y), float(z), float(e) }; } - FI XYZEval reciprocal() const { return { _RECIP(x), _RECIP(y), _RECIP(z), _RECIP(e) }; } - FI XYZEval asLogical() const { XYZEval o = asFloat(); toLogical(o); return o; } - FI XYZEval asNative() const { XYZEval o = asFloat(); toNative(o); return o; } - FI operator XYval&() { return *(XYval*)this; } - FI operator const XYval&() const { return *(const XYval*)this; } - FI operator XYZval&() { return *(XYZval*)this; } - FI operator const XYZval&() const { return *(const XYZval*)this; } - FI T& operator[](const int i) { return pos[i]; } - FI const T& operator[](const int i) const { return pos[i]; } - FI XYZEval& operator= (const T v) { set(v, v, v, v); return *this; } - FI XYZEval& operator= (const XYval &rs) { set(rs.x, rs.y); return *this; } - FI XYZEval& operator= (const XYZval &rs) { set(rs.x, rs.y, rs.z); return *this; } - FI XYZEval operator+ (const XYval &rs) const { XYZEval ls = *this; ls.x += rs.x; ls.y += rs.y; return ls; } - FI XYZEval operator+ (const XYval &rs) { XYZEval ls = *this; ls.x += rs.x; ls.y += rs.y; return ls; } - FI XYZEval operator- (const XYval &rs) const { XYZEval ls = *this; ls.x -= rs.x; ls.y -= rs.y; return ls; } - FI XYZEval operator- (const XYval &rs) { XYZEval ls = *this; ls.x -= rs.x; ls.y -= rs.y; return ls; } - FI XYZEval operator* (const XYval &rs) const { XYZEval ls = *this; ls.x *= rs.x; ls.y *= rs.y; return ls; } - FI XYZEval operator* (const XYval &rs) { XYZEval ls = *this; ls.x *= rs.x; ls.y *= rs.y; return ls; } - FI XYZEval operator/ (const XYval &rs) const { XYZEval ls = *this; ls.x /= rs.x; ls.y /= rs.y; return ls; } - FI XYZEval operator/ (const XYval &rs) { XYZEval ls = *this; ls.x /= rs.x; ls.y /= rs.y; return ls; } - FI XYZEval operator+ (const XYZval &rs) const { XYZEval ls = *this; ls.x += rs.x; ls.y += rs.y; ls.z += rs.z; return ls; } - FI XYZEval operator+ (const XYZval &rs) { XYZEval ls = *this; ls.x += rs.x; ls.y += rs.y; ls.z += rs.z; return ls; } - FI XYZEval operator- (const XYZval &rs) const { XYZEval ls = *this; ls.x -= rs.x; ls.y -= rs.y; ls.z -= rs.z; return ls; } - FI XYZEval operator- (const XYZval &rs) { XYZEval ls = *this; ls.x -= rs.x; ls.y -= rs.y; ls.z -= rs.z; return ls; } - FI XYZEval operator* (const XYZval &rs) const { XYZEval ls = *this; ls.x *= rs.x; ls.y *= rs.y; ls.z *= rs.z; return ls; } - FI XYZEval operator* (const XYZval &rs) { XYZEval ls = *this; ls.x *= rs.x; ls.y *= rs.y; ls.z *= rs.z; return ls; } - FI XYZEval operator/ (const XYZval &rs) const { XYZEval ls = *this; ls.x /= rs.x; ls.y /= rs.y; ls.z /= rs.z; return ls; } - FI XYZEval operator/ (const XYZval &rs) { XYZEval ls = *this; ls.x /= rs.x; ls.y /= rs.y; ls.z /= rs.z; return ls; } - FI XYZEval operator+ (const XYZEval &rs) const { XYZEval ls = *this; ls.x += rs.x; ls.y += rs.y; ls.z += rs.z; ls.e += rs.e; return ls; } - FI XYZEval operator+ (const XYZEval &rs) { XYZEval ls = *this; ls.x += rs.x; ls.y += rs.y; ls.z += rs.z; ls.e += rs.e; return ls; } - FI XYZEval operator- (const XYZEval &rs) const { XYZEval ls = *this; ls.x -= rs.x; ls.y -= rs.y; ls.z -= rs.z; ls.e -= rs.e; return ls; } - FI XYZEval operator- (const XYZEval &rs) { XYZEval ls = *this; ls.x -= rs.x; ls.y -= rs.y; ls.z -= rs.z; ls.e -= rs.e; return ls; } - FI XYZEval operator* (const XYZEval &rs) const { XYZEval ls = *this; ls.x *= rs.x; ls.y *= rs.y; ls.z *= rs.z; ls.e *= rs.e; return ls; } - FI XYZEval operator* (const XYZEval &rs) { XYZEval ls = *this; ls.x *= rs.x; ls.y *= rs.y; ls.z *= rs.z; ls.e *= rs.e; return ls; } - FI XYZEval operator/ (const XYZEval &rs) const { XYZEval ls = *this; ls.x /= rs.x; ls.y /= rs.y; ls.z /= rs.z; ls.e /= rs.e; return ls; } - FI XYZEval operator/ (const XYZEval &rs) { XYZEval ls = *this; ls.x /= rs.x; ls.y /= rs.y; ls.z /= rs.z; ls.e /= rs.e; return ls; } - FI XYZEval operator* (const float &v) const { XYZEval ls = *this; ls.x *= v; ls.y *= v; ls.z *= v; ls.e *= v; return ls; } - FI XYZEval operator* (const float &v) { XYZEval ls = *this; ls.x *= v; ls.y *= v; ls.z *= v; ls.e *= v; return ls; } - FI XYZEval operator* (const int &v) const { XYZEval ls = *this; ls.x *= v; ls.y *= v; ls.z *= v; ls.e *= v; return ls; } - FI XYZEval operator* (const int &v) { XYZEval ls = *this; ls.x *= v; ls.y *= v; ls.z *= v; ls.e *= v; return ls; } - FI XYZEval operator/ (const float &v) const { XYZEval ls = *this; ls.x /= v; ls.y /= v; ls.z /= v; ls.e /= v; return ls; } - FI XYZEval operator/ (const float &v) { XYZEval ls = *this; ls.x /= v; ls.y /= v; ls.z /= v; ls.e /= v; return ls; } - FI XYZEval operator/ (const int &v) const { XYZEval ls = *this; ls.x /= v; ls.y /= v; ls.z /= v; ls.e /= v; return ls; } - FI XYZEval operator/ (const int &v) { XYZEval ls = *this; ls.x /= v; ls.y /= v; ls.z /= v; ls.e /= v; return ls; } - FI XYZEval operator>>(const int &v) const { XYZEval ls = *this; _RS(ls.x); _RS(ls.y); _RS(ls.z); _RS(ls.e); return ls; } - FI XYZEval operator>>(const int &v) { XYZEval ls = *this; _RS(ls.x); _RS(ls.y); _RS(ls.z); _RS(ls.e); return ls; } - FI XYZEval operator<<(const int &v) const { XYZEval ls = *this; _LS(ls.x); _LS(ls.y); _LS(ls.z); _LS(ls.e); return ls; } - FI XYZEval operator<<(const int &v) { XYZEval ls = *this; _LS(ls.x); _LS(ls.y); _LS(ls.z); _LS(ls.e); return ls; } - FI XYZEval& operator+=(const XYval &rs) { x += rs.x; y += rs.y; return *this; } - FI XYZEval& operator-=(const XYval &rs) { x -= rs.x; y -= rs.y; return *this; } - FI XYZEval& operator*=(const XYval &rs) { x *= rs.x; y *= rs.y; return *this; } - FI XYZEval& operator/=(const XYval &rs) { x /= rs.x; y /= rs.y; return *this; } - FI XYZEval& operator+=(const XYZval &rs) { x += rs.x; y += rs.y; z += rs.z; return *this; } - FI XYZEval& operator-=(const XYZval &rs) { x -= rs.x; y -= rs.y; z -= rs.z; return *this; } - FI XYZEval& operator*=(const XYZval &rs) { x *= rs.x; y *= rs.y; z *= rs.z; return *this; } - FI XYZEval& operator/=(const XYZval &rs) { x /= rs.x; y /= rs.y; z /= rs.z; return *this; } - FI XYZEval& operator+=(const XYZEval &rs) { x += rs.x; y += rs.y; z += rs.z; e += rs.e; return *this; } - FI XYZEval& operator-=(const XYZEval &rs) { x -= rs.x; y -= rs.y; z -= rs.z; e -= rs.e; return *this; } - FI XYZEval& operator*=(const XYZEval &rs) { x *= rs.x; y *= rs.y; z *= rs.z; e *= rs.e; return *this; } - FI XYZEval& operator/=(const XYZEval &rs) { x /= rs.x; y /= rs.y; z /= rs.z; e /= rs.e; return *this; } - FI XYZEval& operator*=(const T &v) { x *= v; y *= v; z *= v; e *= v; return *this; } - FI XYZEval& operator>>=(const int &v) { _RS(x); _RS(y); _RS(z); _RS(e); return *this; } - FI XYZEval& operator<<=(const int &v) { _LS(x); _LS(y); _LS(z); _LS(e); return *this; } - FI bool operator==(const XYZval &rs) { return x == rs.x && y == rs.y && z == rs.z; } - FI bool operator!=(const XYZval &rs) { return !operator==(rs); } - FI bool operator==(const XYZval &rs) const { return x == rs.x && y == rs.y && z == rs.z; } - FI bool operator!=(const XYZval &rs) const { return !operator==(rs); } - FI XYZEval operator-() { return { -x, -y, -z, -e }; } - FI const XYZEval operator-() const { return { -x, -y, -z, -e }; } + + // Setter for all individual args + 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; } + #endif + #if HAS_I_AXIS + FI void set(const T px, const T py, const T pz) { x = px; y = 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; } + #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; } + #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; } + #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; } + #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; } + #endif + + // Length reduced to one dimension + 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 + 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); } + // 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 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 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; } + FI operator const XYZval&() const { return *(const XYZval*)this; } + + // Accessor via an AxisEnum (or any integer) [index] + FI T& operator[](const int n) { return pos[n]; } + FI const T& operator[](const int n) const { return pos[n]; } + + // 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_LC(rs)); return *this; } + + // Override other operators to get intuitive behaviors + 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 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)); } + FI constexpr XYZEval operator-() const { return LOGICAL_AXIS_ARRAY(-e, -x, -y, -z, -i, -j, -k, -u, -v, -w); } + + // 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 ); } + + // Modifier operators + FI XYZEval& operator+=(const XYval &rs) { XY_CODE(x += rs.x, y += rs.y); return *this; } + FI XYZEval& operator-=(const XYval &rs) { XY_CODE(x -= rs.x, y -= rs.y); return *this; } + FI XYZEval& operator*=(const XYval &rs) { XY_CODE(x *= rs.x, y *= rs.y); return *this; } + FI XYZEval& operator/=(const XYval &rs) { XY_CODE(x /= rs.x, y /= rs.y); return *this; } + FI XYZEval& operator+=(const XYZval &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 XYZEval& operator-=(const XYZval &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 XYZEval& operator*=(const XYZval &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 XYZEval& operator/=(const XYZval &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 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 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 ENABLED(HAS_X_AXIS) 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 ANY(HAS_X_AXIS, HAS_EXTRUDERS) 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 !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 ENABLED(HAS_X_AXIS) LOGICAL_AXIS_GANG(&& 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 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 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 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 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 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 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 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 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); } + +}; + +#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 ); } + + // 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; } + #endif + #if HAS_Z_AXIS + FI void set(const int n, const T px, const T py) { x[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; } + #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; } + #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; } + #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; } + #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; } + #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; } + #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])); } +}; + +template +struct XYZEarray { + typedef T el[SIZE]; + 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 ); } + + // 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_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 + #if HAS_Y_AXIS + 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; } + #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; } + #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; } + #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; } + #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; } + #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; } + #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; } + #endif + + 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])); } +}; + +// +// Axes mapped to bits in a mask of minimum size, bits_t(NUM_AXIS_HEADS) +// +class AxisBits { +public: + typedef bits_t(NUM_AXIS_HEADS) el; + union { + el bits; + // Axes x, y, z ... e0, e1, e2 ... hx, hy, hz + 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); + #endif + #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; + #endif + }; + // Axes X, Y, Z ... E0, E1, E2 ... HX, HY, HZ + 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); + #endif + #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; + #endif + }; + // a, b, c, e ... ha, hb, hc + 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 + #define _EN_ITEM(N) bool _e##N:1; + 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; + #endif + }; + // A, B, C, E ... HA, HB, HC + 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 + #define _EN_ITEM(N) bool _E##N:1; + 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; + #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 + AxisBits(const el p) { set(p); } + FI void set(const el p) { bits = el(p); } + FI AxisBits& operator=(const el p) { set(p); return *this; } + + FI void reset() { set(0); } + 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) + + // Constructor, setter, and operator= for XYZE type + AxisBits(const xyze_bool_t &p) { set(p); } + FI void set(const xyze_bool_t &p) { + MSET(p.e, p.x, p.y, p.z, p.i, p.j, p.k, p.u, p.v, p.w); + } + FI AxisBits& operator=(const xyze_bool_t &p) { set(p); return *this; } + + // Constructor, setter, and operator= for bool array + AxisBits(const bool (&p)[LOGICAL_AXES]) { set(p); } + FI void set(const bool (&p)[LOGICAL_AXES]) { + MSET(p[E_AXIS], p[X_AXIS], p[Y_AXIS], p[Z_AXIS], + p[I_AXIS], p[J_AXIS], p[K_AXIS], + p[U_AXIS], p[V_AXIS], p[W_AXIS]); + } + FI AxisBits& operator=(const bool (&p)[LOGICAL_AXES]) { set(p); return *this; } + + // Constructor, setter, and operator= for undersized bool arrays + #if LOGICAL_AXES > 1 + AxisBits(const bool (&p)[1]) { set(p); } + FI void set(const bool (&p)[1]) { + MSET(0, p[X_AXIS], 0, 0, 0, 0, 0, 0, 0, 0); + } + FI AxisBits& operator=(const bool (&p)[1]) { set(p); return *this; } + #endif + #if LOGICAL_AXES > 2 + AxisBits(const bool (&p)[2]) { set(p); } + FI void set(const bool (&p)[2]) { + MSET(0, p[X_AXIS], p[Y_AXIS], 0, 0, 0, 0, 0, 0, 0); + } + FI AxisBits& operator=(const bool (&p)[2]) { set(p); return *this; } + #endif + #if LOGICAL_AXES > 3 + AxisBits(const bool (&p)[3]) { set(p); } + FI void set(const bool (&p)[3]) { + MSET(0, p[X_AXIS], p[Y_AXIS], p[Z_AXIS], 0, 0, 0, 0, 0, 0); + } + FI AxisBits& operator=(const bool (&p)[3]) { set(p); return *this; } + #endif + #if LOGICAL_AXES > 4 + AxisBits(const bool (&p)[4]) { set(p); } + FI void set(const bool (&p)[4]) { + MSET(0, p[X_AXIS], p[Y_AXIS], p[Z_AXIS], p[I_AXIS], 0, 0, 0, 0, 0); + } + FI AxisBits& operator=(const bool (&p)[4]) { set(p); return *this; } + #endif + #if LOGICAL_AXES > 5 + AxisBits(const bool (&p)[5]) { set(p); } + FI void set(const bool (&p)[5]) { + MSET(0, p[X_AXIS], p[Y_AXIS], p[Z_AXIS], p[I_AXIS], p[J_AXIS], 0, 0, 0, 0); + } + FI AxisBits& operator=(const bool (&p)[5]) { set(p); return *this; } + #endif + #if LOGICAL_AXES > 6 + AxisBits(const bool (&p)[6]) { set(p); } + FI void set(const bool (&p)[6]) { + MSET(0, p[X_AXIS], p[Y_AXIS], p[Z_AXIS], p[I_AXIS], p[J_AXIS], p[K_AXIS], 0, 0, 0); + } + FI AxisBits& operator=(const bool (&p)[6]) { set(p); return *this; } + #endif + #if LOGICAL_AXES > 7 + AxisBits(const bool (&p)[7]) { set(p); } + FI void set(const bool (&p)[7]) { + MSET(0, p[X_AXIS], p[Y_AXIS], p[Z_AXIS], p[I_AXIS], p[J_AXIS], p[K_AXIS], p[U_AXIS], 0, 0); + } + FI AxisBits& operator=(const bool (&p)[7]) { set(p); return *this; } + #endif + #if LOGICAL_AXES > 8 + AxisBits(const bool (&p)[8]) { set(p); } + FI void set(const bool (&p)[8]) { + MSET(0, p[X_AXIS], p[Y_AXIS], p[Z_AXIS], p[I_AXIS], p[J_AXIS], p[K_AXIS], p[U_AXIS], p[V_AXIS], 0); + } + FI AxisBits& operator=(const bool (&p)[8]) { set(p); return *this; } + #endif + #if LOGICAL_AXES > 9 + AxisBits(const bool (&p)[9]) { set(p); } + FI void set(const bool (&p)[9]) { + MSET(0, p[X_AXIS], p[Y_AXIS], p[Z_AXIS], p[I_AXIS], p[J_AXIS], p[K_AXIS], p[U_AXIS], p[V_AXIS], p[W_AXIS]); + } + FI AxisBits& operator=(const bool (&p)[9]) { set(p); return *this; } + #endif + #undef MSET + + 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 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; } + FI AxisBits& operator&=(const el &p) { bits &= el(p); return *this; } + FI AxisBits& operator^=(const el &p) { bits ^= el(p); return *this; } + + FI AxisBits& operator|=(const AxisBits &p) { bits |= p.bits; return *this; } + FI AxisBits& operator&=(const AxisBits &p) { bits &= p.bits; return *this; } + FI AxisBits& operator^=(const AxisBits &p) { bits ^= p.bits; return *this; } + + FI bool operator==(const AxisBits &p) const { return p.bits == bits; } + FI bool operator!=(const AxisBits &p) const { return p.bits != bits; } + + FI el operator|(const el &p) const { return bits | el(p); } + FI el operator&(const el &p) const { return bits & el(p); } + FI el operator^(const el &p) const { return bits ^ el(p); } + + 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); } + FI operator uint32_t() const { return uint32_t(bits); } + }; #undef _RECIP #undef _ABS #undef _LS #undef _RS +#undef _LSE +#undef _RSE #undef FI -const xyze_char_t axis_codes { 'X', 'Y', 'Z', 'E' }; -#define XYZ_CHAR(A) ((char)('X' + A)) +// 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 f999568167..f6b8b304c0 100644 --- a/Marlin/src/core/utility.cpp +++ b/Marlin/src/core/utility.cpp @@ -22,22 +22,16 @@ #include "utility.h" -#include "../MarlinCore.h" #include "../module/temperature.h" -void safe_delay(millis_t ms) { - while (ms > 50) { - ms -= 50; - delay(50); - thermalManager.manage_heater(); - } - delay(ms); - thermalManager.manage_heater(); // This keeps us safe if too many small safe_delay() calls are made -} +#if ENABLED(MARLIN_DEV_MODE) + MarlinError marlin_error_number; // Error Number - Marlin can beep X times periodically, display, and emit... +#endif // 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) { @@ -51,37 +45,51 @@ void safe_delay(millis_t ms) { #include "../module/probe.h" #include "../module/motion.h" - #include "../module/stepper.h" + #include "../module/planner.h" #include "../libs/numtostr.h" #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, "MarkForged") - 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_(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(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 #if !HAS_PROBE_XY_OFFSET - SERIAL_ECHOPAIR("Probe Offset X0 Y0 Z", probe.offset.z, " ("); + SERIAL_ECHOPGM("Probe Offset X0 Y0 Z", probe.offset.z, " ("); #else - SERIAL_ECHOPAIR_P(PSTR("Probe Offset X"), probe.offset_xy.x, SP_Y_STR, probe.offset_xy.y, SP_Z_STR, probe.offset.z); + SERIAL_ECHOPGM_P(PSTR("Probe Offset X"), probe.offset_xy.x, SP_Y_STR, probe.offset_xy.y, SP_Z_STR, probe.offset.z); if (probe.offset_xy.x > 0) SERIAL_ECHOPGM(" (Right"); else if (probe.offset_xy.x < 0) @@ -92,9 +100,9 @@ void safe_delay(millis_t ms) { SERIAL_ECHOPGM(" (Aligned With"); if (probe.offset_xy.y > 0) - serialprintPGM(ENABLED(IS_SCARA) ? PSTR("-Distal") : PSTR("-Back")); + SERIAL_ECHO(F(TERN(IS_SCARA, "-Distal", "-Back"))); else if (probe.offset_xy.y < 0) - serialprintPGM(ENABLED(IS_SCARA) ? PSTR("-Proximal") : PSTR("-Front")); + SERIAL_ECHO(F(TERN(IS_SCARA, "-Proximal", "-Front"))); else if (probe.offset_xy.x != 0) SERIAL_ECHOPGM("-Center"); @@ -102,7 +110,7 @@ void safe_delay(millis_t ms) { #endif - serialprintPGM(probe.offset.z < 0 ? PSTR("Below") : probe.offset.z > 0 ? PSTR("Above") : PSTR("Same Z as")); + SERIAL_ECHO(probe.offset.z < 0 ? F("Below") : probe.offset.z > 0 ? F("Above") : F("Same Z as")); SERIAL_ECHOLNPGM(" Nozzle)"); #endif @@ -119,30 +127,25 @@ void safe_delay(millis_t ms) { SERIAL_ECHOLNPGM(" (enabled)"); #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) if (planner.z_fade_height) - SERIAL_ECHOLNPAIR("Z Fade: ", planner.z_fade_height); + SERIAL_ECHOLNPGM("Z Fade: ", planner.z_fade_height); #endif #if ABL_PLANAR - SERIAL_ECHOPGM("ABL Adjustment X"); - LOOP_XYZ(a) { - const float v = planner.get_axis_position_mm(AxisEnum(a)) - current_position[a]; - SERIAL_CHAR(' ', XYZ_CHAR(a)); - if (v > 0) SERIAL_CHAR('+'); - SERIAL_DECIMAL(v); + 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]); } #else #if ENABLED(AUTO_BED_LEVELING_UBL) SERIAL_ECHOPGM("UBL Adjustment Z"); - const float rz = ubl.get_z_correction(current_position); #elif ENABLED(AUTO_BED_LEVELING_BILINEAR) SERIAL_ECHOPGM("ABL Adjustment Z"); - const float rz = bilinear_z_offset(current_position); #endif + const float rz = bedlevel.get_z_correction(current_position); SERIAL_ECHO(ftostr43sign(rz, '+')); #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) - if (planner.z_fade_height) { - SERIAL_ECHOPAIR(" (", 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(current_position.z), '+'), C(')')); #endif #endif } @@ -156,13 +159,12 @@ void safe_delay(millis_t ms) { SERIAL_ECHOPGM("Mesh Bed Leveling"); if (planner.leveling_active) { SERIAL_ECHOLNPGM(" (enabled)"); - SERIAL_ECHOPAIR("MBL Adjustment Z", ftostr43sign(mbl.get_z(current_position), '+')); + const float z_offset = bedlevel.get_z_offset(), + z_correction = bedlevel.get_z_correction(current_position); + SERIAL_ECHOPGM("MBL Adjustment Z", ftostr43sign(z_offset + z_correction, '+')); #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) if (planner.z_fade_height) { - SERIAL_ECHOPAIR(" (", ftostr43sign( - mbl.get_z(current_position, 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(current_position.z), '+'), C(')')); } #endif } diff --git a/Marlin/src/core/utility.h b/Marlin/src/core/utility.h index 645a4be807..b8b4de7f28 100644 --- a/Marlin/src/core/utility.h +++ b/Marlin/src/core/utility.h @@ -25,26 +25,23 @@ #include "../core/types.h" #include "../core/millis_t.h" -// Delay that ensures heaters and watchdog are kept alive -void safe_delay(millis_t ms); - #if ENABLED(SERIAL_OVERRUN_PROTECTION) void serial_delay(const millis_t ms); #else inline void serial_delay(const millis_t) {} #endif -#if GRID_MAX_POINTS_X && GRID_MAX_POINTS_Y +#if GRID_MAX_POINTS // 16x16 bit arrays template struct FlagBits { - typename IF<(W>8), uint16_t, uint8_t>::type bits[H]; - void fill() { memset(bits, 0xFF, sizeof(bits)); } - void reset() { memset(bits, 0x00, sizeof(bits)); } - void unmark(const uint8_t x, const uint8_t y) { CBI(bits[y], x); } - void mark(const uint8_t x, const uint8_t y) { SBI(bits[y], x); } - bool marked(const uint8_t x, const uint8_t y) { return TEST(bits[y], x); } + bits_t(W) flags[H]; + void fill() { memset(flags, 0xFF, sizeof(flags)); } + void reset() { memset(flags, 0x00, sizeof(flags)); } + void unmark(const uint8_t x, const uint8_t y) { CBI(flags[y], x); } + void mark(const uint8_t x, const uint8_t y) { SBI(flags[y], x); } + bool marked(const uint8_t x, const uint8_t y) { return TEST(flags[y], x); } inline void unmark(const xy_int8_t &xy) { unmark(xy.x, xy.y); } inline void mark(const xy_int8_t &xy) { mark(xy.x, xy.y); } inline bool marked(const xy_int8_t &xy) { return marked(xy.x, xy.y); } @@ -60,6 +57,11 @@ void safe_delay(millis_t ms); #define log_machine_info() NOOP #endif +/** + * A restorer instance remembers a variable's value before setting a + * new value, then restores the old value when it goes out of scope. + * Put operator= on your type to get extended behavior on value change. + */ template class restorer { T& ref_; @@ -77,3 +79,22 @@ public: // Converts from an uint8_t in the range of 0-255 to an uint8_t // 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; } + +#if ENABLED(MARLIN_DEV_MODE) + enum MarlinError : uint8_t { + ERR_NONE, + ERR_STRING_RANGE, // A string buffer was too small to set the whole blob + ERR_ASSERTION, // An assertion was triggered + ERR_MALFUNCTION, + ERR_MEMORY_LEAK, + ERR_COMMS_SERIAL, + ERR_COMMS_SPI, + ERR_PLANNER_STARVED, + ERR_TMC_SHUTDOWN, + ERR_PROCEDURE_FAILED, + ERR_TOO_WACK, + ERR_PLAID_IN_SUMMER + }; + extern MarlinError marlin_error_number; // Error Number - Marlin can beep, display, and emit... + inline void error(const MarlinError err) { marlin_error_number = err; } +#endif diff --git a/Marlin/src/feature/adc/adc_mcp3426.cpp b/Marlin/src/feature/adc/adc_mcp3426.cpp new file mode 100644 index 0000000000..49bb67ef6d --- /dev/null +++ b/Marlin/src/feature/adc/adc_mcp3426.cpp @@ -0,0 +1,104 @@ +/** + * 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 . + * + */ + +/** + * adc_mcp3426.cpp - library for MicroChip MCP3426 I2C A/D converter + * + * For implementation details, please take a look at the datasheet: + * https://www.microchip.com/en-us/product/MCP3426 + */ + +#include "../../inc/MarlinConfig.h" + +#if ENABLED(HAS_MCP3426_ADC) + +#include "adc_mcp3426.h" + +// Read the ADC value from MCP342X on a specific channel +int16_t MCP3426::ReadValue(uint8_t channel, uint8_t gain, uint8_t address) { + Error = false; + + #if PINS_EXIST(I2C_SCL, I2C_SDA) && DISABLED(SOFT_I2C_EEPROM) + Wire.setSDA(pin_t(I2C_SDA_PIN)); + Wire.setSCL(pin_t(I2C_SCL_PIN)); + #endif + + Wire.begin(); // No address joins the BUS as the master + + Wire.beginTransmission(I2C_ADDRESS(address)); + + // Continuous Conversion Mode, 16 bit, Channel 1, Gain x4 + // 26 = 0b00011000 + // RXXCSSGG + // R = Ready Bit + // XX = Channel (00=1, 01=2, 10=3 (MCP3428), 11=4 (MCP3428)) + // C = Conversion Mode Bit (1= Continuous Conversion Mode (Default)) + // SS = Sample rate, 10=15 samples per second @ 16 bits + // GG = Gain 00 =x1 + uint8_t controlRegister = 0b00011000; + + if (channel == 2) controlRegister |= 0b00100000; // Select channel 2 + + if (gain == 2) + controlRegister |= 0b00000001; + else if (gain == 4) + controlRegister |= 0b00000010; + else if (gain == 8) + controlRegister |= 0b00000011; + + Wire.write(controlRegister); + if (Wire.endTransmission() != 0) { + Error = true; + return 0; + } + + const uint8_t len = 3; + uint8_t buffer[len] = {}; + + do { + Wire.requestFrom(I2C_ADDRESS(address), len); + if (Wire.available() != len) { + Error = true; + return 0; + } + + for (uint8_t i = 0; i < len; ++i) + buffer[i] = Wire.read(); + + // Is conversion ready, if not loop around again + } while ((buffer[2] & 0x80) != 0); + + union TwoBytesToInt16 { + uint8_t bytes[2]; + int16_t integervalue; + }; + TwoBytesToInt16 ConversionUnion; + + ConversionUnion.bytes[1] = buffer[0]; + ConversionUnion.bytes[0] = buffer[1]; + + return ConversionUnion.integervalue; +} + +MCP3426 mcp3426; + +#endif // HAS_MCP3426_ADC diff --git a/Marlin/src/feature/adc/adc_mcp3426.h b/Marlin/src/feature/adc/adc_mcp3426.h new file mode 100644 index 0000000000..af48593369 --- /dev/null +++ b/Marlin/src/feature/adc/adc_mcp3426.h @@ -0,0 +1,38 @@ +/** + * 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 + +/** + * Arduino library for MicroChip MCP3426 I2C A/D converter. + * https://www.microchip.com/en-us/product/MCP3426 + */ + +#include +#include + +class MCP3426 { + public: + int16_t ReadValue(uint8_t channel, uint8_t gain, uint8_t address); + bool Error; +}; + +extern MCP3426 mcp3426; diff --git a/Marlin/src/feature/ammeter.cpp b/Marlin/src/feature/ammeter.cpp new file mode 100644 index 0000000000..71b84f1121 --- /dev/null +++ b/Marlin/src/feature/ammeter.cpp @@ -0,0 +1,54 @@ +/** + * 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 . + * + */ + +#include "../inc/MarlinConfig.h" + +#if ENABLED(I2C_AMMETER) + +#include "ammeter.h" + +#ifndef I2C_AMMETER_IMAX + #define I2C_AMMETER_IMAX 0.500 // Calibration range 500 Milliamps +#endif + +INA226 ina; + +Ammeter ammeter; + +float Ammeter::scale; +float Ammeter::current; + +void Ammeter::init() { + ina.begin(); + ina.configure(INA226_AVERAGES_16, INA226_BUS_CONV_TIME_1100US, INA226_SHUNT_CONV_TIME_1100US, INA226_MODE_SHUNT_BUS_CONT); + ina.calibrate(I2C_AMMETER_SHUNT_RESISTOR, I2C_AMMETER_IMAX); +} + +float Ammeter::read() { + scale = 1; + current = ina.readShuntCurrent(); + if (current <= 0.0001f) current = 0; // Clean up least-significant-bit amplification errors + if (current < 0.1f) scale = 1000; + return current * scale; +} + +#endif // I2C_AMMETER diff --git a/Marlin/src/feature/ammeter.h b/Marlin/src/feature/ammeter.h new file mode 100644 index 0000000000..86f09bb9a1 --- /dev/null +++ b/Marlin/src/feature/ammeter.h @@ -0,0 +1,39 @@ +/** + * 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 + +#include "../inc/MarlinConfigPre.h" + +#include +#include + +class Ammeter { +private: + static float scale; + +public: + static float current; + static void init(); + static float read(); +}; + +extern Ammeter ammeter; diff --git a/Marlin/src/feature/babystep.cpp b/Marlin/src/feature/babystep.cpp index b076881461..d61784517b 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() +#include "../module/motion.h" // for axis_should_home(), BABYSTEP_ALLOWED #include "../module/planner.h" // for axis_steps_per_mm[] #include "../module/stepper.h" @@ -42,26 +41,52 @@ volatile int16_t Babystep::steps[BS_AXIS_IND(Z_AXIS) + 1]; #endif int16_t Babystep::accum; +#if ALL(EP_BABYSTEPPING, EMERGENCY_PARSER) + int16_t Babystep::ep_babysteps; +#endif + 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 &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 mm) { + //if (DISABLED(BABYSTEP_WITHOUT_HOMING) && 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; + TERN_(BABYSTEP_DISPLAY_TOTAL, axis_total[BS_TOTAL_IND(axis)] = distance); + TERN_(BABYSTEP_ALWAYS_AVAILABLE, gcode.reset_stepper_timeout()); + TERN_(BABYSTEPPING, if (has_steps()) stepper.initiateBabystepping()); + } +#endif + +bool Babystep::can_babystep(const AxisEnum axis) { + return (ENABLED(BABYSTEP_WITHOUT_HOMING) || !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; TERN_(BABYSTEP_DISPLAY_TOTAL, axis_total[BS_TOTAL_IND(axis)] += distance); TERN_(BABYSTEP_ALWAYS_AVAILABLE, gcode.reset_stepper_timeout()); - TERN_(INTEGRATED_BABYSTEPPING, if (has_steps()) stepper.initiateBabystepping()); + TERN_(BABYSTEPPING, if (has_steps()) stepper.initiateBabystepping()); } +#if ENABLED(EP_BABYSTEPPING) + // Step Z for M293 / M294 + void Babystep::z_up() { if (BABYSTEP_ALLOWED()) add_steps(Z_AXIS, +BABYSTEP_SIZE_Z); } + void Babystep::z_down() { if (BABYSTEP_ALLOWED()) add_steps(Z_AXIS, -BABYSTEP_SIZE_Z); } +#endif + #endif // BABYSTEPPING diff --git a/Marlin/src/feature/babystep.h b/Marlin/src/feature/babystep.h index f85e5909ca..da330672b2 100644 --- a/Marlin/src/feature/babystep.h +++ b/Marlin/src/feature/babystep.h @@ -23,15 +23,10 @@ #include "../inc/MarlinConfigPre.h" -#if ENABLED(INTEGRATED_BABYSTEPPING) - #define BABYSTEPS_PER_SEC 1000UL - #define BABYSTEP_TICKS ((STEPPER_TIMER_RATE) / (BABYSTEPS_PER_SEC)) -#else - #define BABYSTEPS_PER_SEC 976UL - #define BABYSTEP_TICKS ((TEMP_TIMER_RATE) / (BABYSTEPS_PER_SEC)) -#endif +#define BABYSTEPS_PER_SEC 1000UL +#define BABYSTEP_TICKS ((STEPPER_TIMER_RATE) / (BABYSTEPS_PER_SEC)) -#if IS_CORE || EITHER(BABYSTEP_XY, I2C_POSITION_ENCODERS) +#if ANY(IS_CORE, BABYSTEP_XY, I2C_POSITION_ENCODERS) #define BS_AXIS_IND(A) A #define BS_AXIS(I) AxisEnum(I) #else @@ -52,18 +47,42 @@ public: static volatile int16_t steps[BS_AXIS_IND(Z_AXIS) + 1]; static int16_t accum; // Total babysteps in current edit + #if ALL(EP_BABYSTEPPING, EMERGENCY_PARSER) + static int16_t ep_babysteps; + #endif + #if ENABLED(BABYSTEP_DISPLAY_TOTAL) static int16_t axis_total[BS_TOTAL_IND(Z_AXIS) + 1]; // Total babysteps since G28 - static inline void reset_total(const AxisEnum axis) { + static void reset_total(const AxisEnum axis) { if (TERN1(BABYSTEP_XY, axis == Z_AXIS)) axis_total[BS_TOTAL_IND(axis)] = 0; } #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 &mm); + static void add_mm(const AxisEnum axis, const float mm); - static inline bool has_steps() { + #if ENABLED(EP_BABYSTEPPING) + // Step Z for M293 / M294 + static void z_up(); + static void z_down(); + #if ENABLED(EMERGENCY_PARSER) + // Step Z according to steps accumulated by the EP + FORCE_INLINE static void do_ep_steps() { + if (ep_babysteps) { + if (ep_babysteps > 0) { z_up(); ep_babysteps--; } + else { z_down(); ep_babysteps++; } + } + } + #endif + #endif // EP_BABYSTEPPING + + #if ENABLED(BD_SENSOR) + static void set_mm(const AxisEnum axis, const float mm); + #endif + + static bool has_steps() { return steps[BS_AXIS_IND(X_AXIS)] || steps[BS_AXIS_IND(Y_AXIS)] || steps[BS_AXIS_IND(Z_AXIS)]; } @@ -71,8 +90,8 @@ public: // Called by the Temperature or Stepper ISR to // apply accumulated babysteps to the axes. // - static inline void task() { - LOOP_LE_N(i, BS_AXIS_IND(Z_AXIS)) step_axis(BS_AXIS(i)); + static void task() { + for (uint8_t i = 0; i <= BS_AXIS_IND(Z_AXIS); ++i) step_axis(BS_AXIS(i)); } private: diff --git a/Marlin/src/feature/backlash.cpp b/Marlin/src/feature/backlash.cpp index 867e9cdd21..3b9d78cb2e 100644 --- a/Marlin/src/feature/backlash.cpp +++ b/Marlin/src/feature/backlash.cpp @@ -29,6 +29,9 @@ #include "../module/motion.h" #include "../module/planner.h" +AxisBits Backlash::last_direction_bits; +xyz_long_t Backlash::residual_error{0}; + #ifdef BACKLASH_DISTANCE_MM #if ENABLED(BACKLASH_GCODE) xyz_float_t Backlash::distance_mm = BACKLASH_DISTANCE_MM; @@ -38,7 +41,7 @@ #endif #if ENABLED(BACKLASH_GCODE) - uint8_t Backlash::correction = (BACKLASH_CORRECTION) * 0xFF; + uint8_t Backlash::correction = (BACKLASH_CORRECTION) * all_on; #ifdef BACKLASH_SMOOTHING_MM float Backlash::smoothing_mm = BACKLASH_SMOOTHING_MM; #endif @@ -60,16 +63,29 @@ Backlash backlash; * spread over multiple segments, smoothing out artifacts even more. */ -void Backlash::add_correction_steps(const int32_t &da, const int32_t &db, const int32_t &dc, const uint8_t dm, block_t * const block) { - static uint8_t last_direction_bits; - uint8_t changed_dir = last_direction_bits ^ dm; - // Ignore direction change if no steps are taken in that direction - if (da == 0) CBI(changed_dir, X_AXIS); - if (db == 0) CBI(changed_dir, Y_AXIS); - if (dc == 0) CBI(changed_dir, Z_AXIS); +void Backlash::add_correction_steps(const xyze_long_t &dist, const AxisBits dm, block_t * const block) { + AxisBits changed_dir = last_direction_bits ^ 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; + if (!dist.b) changed_dir.y = false; + if (!dist.c) changed_dir.z = false; + #elif CORE_IS_XY + if (!(dist.a + dist.b)) changed_dir.x = false; + if (!(dist.a - dist.b)) changed_dir.y = false; + if (!dist.c) changed_dir.z = false; + #elif CORE_IS_XZ + if (!(dist.a + dist.c)) changed_dir.x = false; + if (!(dist.a - dist.c)) changed_dir.z = false; + if (!dist.b) changed_dir.y = false; + #elif CORE_IS_YZ + if (!(dist.b + dist.c)) changed_dir.y = false; + if (!(dist.b - dist.c)) changed_dir.z = false; + if (!dist.a) changed_dir.x = false; + #endif last_direction_bits ^= changed_dir; - if (correction == 0) return; + if (!correction && !residual_error) return; #ifdef BACKLASH_SMOOTHING_MM // The segment proportion is a value greater than 0.0 indicating how much residual_error @@ -77,70 +93,146 @@ void Backlash::add_correction_steps(const int32_t &da, const int32_t &db, const // smoothing distance. Since the computation of this proportion involves a floating point // division, defer computation until needed. float segment_proportion = 0; - - // Residual error carried forward across multiple segments, so correction can be applied - // to segments where there is no direction change. - static xyz_long_t residual_error{0}; - #else - // No direction change, no correction. - if (!changed_dir) return; - // No leftover residual error from segment to segment - xyz_long_t residual_error{0}; #endif - const float f_corr = float(correction) / 255.0f; + const float f_corr = float(correction) / all_on; + + bool changed = false; + float millimeters_delta = 0.0f; + #if IS_KINEMATIC + float sqr_stepper_space_mm = 0.0f; + #endif + + LOOP_NUM_AXES(axis) { + TERN_(IS_KINEMATIC, sqr_stepper_space_mm += sq(dist[axis] * planner.mm_per_step[axis])); - LOOP_XYZ(axis) { if (distance_mm[axis]) { - const bool reversing = TEST(dm,axis); + const bool forward = dm[axis]; // When an axis changes direction, add axis backlash to the residual error - if (TEST(changed_dir, axis)) - residual_error[axis] += (reversing ? -f_corr : f_corr) * distance_mm[axis] * planner.settings.axis_steps_per_mm[axis]; + if (changed_dir[axis]) + residual_error[axis] += (forward ? f_corr : -f_corr) * distance_mm[axis] * planner.settings.axis_steps_per_mm[axis]; // Decide how much of the residual error to correct in this segment int32_t error_correction = residual_error[axis]; + #ifdef BACKLASH_SMOOTHING_MM if (error_correction && smoothing_mm != 0) { - // Take up a portion of the residual_error in this segment, but only when - // the current segment travels in the same direction as the correction - if (reversing == (error_correction < 0)) { - if (segment_proportion == 0) - segment_proportion = _MIN(1.0f, block->millimeters / smoothing_mm); - error_correction = CEIL(segment_proportion * error_correction); - } - else - error_correction = 0; // Don't take up any backlash in this segment, as it would subtract steps + // Take up a portion of the residual_error in this segment + if (segment_proportion == 0) segment_proportion = _MIN(1.0f, block->millimeters / smoothing_mm); + error_correction = CEIL(segment_proportion * error_correction); } #endif - // Making a correction reduces the residual error and adds block steps + + // Don't correct backlash in the opposite direction to movement on this axis and for accuracy in + // updating block->millimeters, don't add too many steps to the movement on this axis + if (forward) + LIMIT(error_correction, 0, dist[axis]); + else + LIMIT(error_correction, dist[axis], 0); + + // This correction reduces the residual error and adds block steps if (error_correction) { + changed = true; block->steps[axis] += ABS(error_correction); - residual_error[axis] -= error_correction; + millimeters_delta += dist[axis] * error_correction * sq(planner.mm_per_step[axis]); + #if ENABLED(CORE_BACKLASH) + switch (axis) { + case CORE_AXIS_1: + //block->steps[CORE_AXIS_2] += influence_distance_mm[axis] * planner.settings.axis_steps_per_mm[CORE_AXIS_2]; + //SERIAL_ECHOLNPGM("CORE_AXIS_1 dir change. distance=", distance_mm[axis], " r.err=", residual_error[axis], + // " da=", da, " db=", db, " block->steps[axis]=", block->steps[axis], " err_corr=", error_correction); + break; + case CORE_AXIS_2: + //block->steps[CORE_AXIS_1] += influence_distance_mm[axis] * planner.settings.axis_steps_per_mm[CORE_AXIS_1];; + //SERIAL_ECHOLNPGM("CORE_AXIS_2 dir change. distance=", distance_mm[axis], " r.err=", residual_error[axis], + // " da=", da, " db=", db, " block->steps[axis]=", block->steps[axis], " err_corr=", error_correction); + break; + case NORMAL_AXIS: break; + } + residual_error[axis] = 0; // No residual_error needed for next CORE block, I think... + #else + residual_error[axis] -= error_correction; + #endif } } } + + // If backlash correction steps were added modify block->millimeters with a linear approximation + // See https://github.com/MarlinFirmware/Marlin/pull/26392 + if (changed) + block->millimeters += TERN(IS_KINEMATIC, millimeters_delta * block->millimeters / sqr_stepper_space_mm, millimeters_delta / block->millimeters); } -#if ENABLED(MEASURE_BACKLASH_WHEN_PROBING) - #if HAS_CUSTOM_PROBE_PIN - #define TEST_PROBE_PIN (READ(Z_MIN_PROBE_PIN) != Z_MIN_PROBE_ENDSTOP_INVERTING) - #else - #define TEST_PROBE_PIN (READ(Z_MIN_PIN) != Z_MIN_ENDSTOP_INVERTING) +int32_t Backlash::get_applied_steps(const AxisEnum axis) { + if (axis >= NUM_AXES) return 0; + + const bool forward = last_direction_bits[axis]; + + const int32_t residual_error_axis = residual_error[axis]; + + // 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; + + 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]; + return full_error_axis - residual_error_axis; +} + +class Backlash::StepAdjuster { + private: + xyz_long_t applied_steps; + public: + StepAdjuster() { + LOOP_NUM_AXES(axis) applied_steps[axis] = backlash.get_applied_steps((AxisEnum)axis); + } + ~StepAdjuster() { + // after backlash compensation parameter changes, ensure applied step count does not change + LOOP_NUM_AXES(axis) residual_error[axis] += backlash.get_applied_steps((AxisEnum)axis) - applied_steps[axis]; + } +}; + +#if ENABLED(BACKLASH_GCODE) + + void Backlash::set_correction_uint8(const uint8_t v) { + StepAdjuster adjuster; + correction = v; + } + + void Backlash::set_distance_mm(const AxisEnum axis, const float v) { + StepAdjuster adjuster; + distance_mm[axis] = v; + } + + #ifdef BACKLASH_SMOOTHING_MM + void Backlash::set_smoothing_mm(const float v) { + StepAdjuster adjuster; + smoothing_mm = v; + } #endif +#endif + +#if ENABLED(MEASURE_BACKLASH_WHEN_PROBING) + + #include "../module/probe.h" + // Measure Z backlash by raising nozzle in increments until probe deactivates 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) && TEST_PROBE_PIN) + 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)); // The backlash from all probe points is averaged, so count the number of measurements measured_mm.z += current_position.z - start_height; measured_count.z++; } + #endif #endif // BACKLASH_COMPENSATION diff --git a/Marlin/src/feature/backlash.h b/Marlin/src/feature/backlash.h index 8d00570f99..b4790cb161 100644 --- a/Marlin/src/feature/backlash.h +++ b/Marlin/src/feature/backlash.h @@ -24,21 +24,22 @@ #include "../inc/MarlinConfigPre.h" #include "../module/planner.h" -constexpr uint8_t all_on = 0xFF, all_off = 0x00; - class Backlash { public: + static constexpr uint8_t all_on = 0xFF, all_off = 0x00; + +private: + static AxisBits last_direction_bits; + static xyz_long_t residual_error; + #if ENABLED(BACKLASH_GCODE) - static xyz_float_t distance_mm; static uint8_t correction; + static xyz_float_t distance_mm; #ifdef BACKLASH_SMOOTHING_MM static float smoothing_mm; #endif - - static inline void set_correction(const float &v) { correction = _MAX(0, _MIN(1.0, v)) * all_on; } - static inline float get_correction() { return float(ui8_to_percent(correction)) / 100.0f; } #else - static constexpr uint8_t correction = (BACKLASH_CORRECTION) * 0xFF; + static constexpr uint8_t correction = (BACKLASH_CORRECTION) * all_on; static const xyz_float_t distance_mm; #ifdef BACKLASH_SMOOTHING_MM static constexpr float smoothing_mm = BACKLASH_SMOOTHING_MM; @@ -46,32 +47,50 @@ public: #endif #if ENABLED(MEASURE_BACKLASH_WHEN_PROBING) - private: - static xyz_float_t measured_mm; - static xyz_uint8_t measured_count; - public: - static void measure_with_probe(); + static xyz_float_t measured_mm; + static xyz_uint8_t measured_count; #endif - static inline float get_measurement(const AxisEnum a) { + class StepAdjuster; + +public: + static float get_measurement(const AxisEnum a) { + UNUSED(a); // Return the measurement averaged over all readings return TERN(MEASURE_BACKLASH_WHEN_PROBING , measured_count[a] > 0 ? measured_mm[a] / measured_count[a] : 0 , 0 ); - TERN(MEASURE_BACKLASH_WHEN_PROBING,,UNUSED(a)); } - static inline bool has_measurement(const AxisEnum a) { + static bool has_measurement(const AxisEnum a) { + UNUSED(a); return TERN0(MEASURE_BACKLASH_WHEN_PROBING, measured_count[a] > 0); - TERN(MEASURE_BACKLASH_WHEN_PROBING,,UNUSED(a)); } - static inline bool has_any_measurement() { + static bool has_any_measurement() { return has_measurement(X_AXIS) || has_measurement(Y_AXIS) || has_measurement(Z_AXIS); } - void add_correction_steps(const int32_t &da, const int32_t &db, const int32_t &dc, const uint8_t dm, block_t * const block); + static void add_correction_steps(const xyze_long_t &dist, const AxisBits dm, block_t * const block); + static int32_t get_applied_steps(const AxisEnum axis); + + #if ENABLED(BACKLASH_GCODE) + static void set_correction_uint8(const uint8_t v); + static uint8_t get_correction_uint8() { return correction; } + 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]; } + #ifdef BACKLASH_SMOOTHING_MM + static void set_smoothing_mm(const float v); + static float get_smoothing_mm() { return smoothing_mm; } + #endif + #endif + + #if ENABLED(MEASURE_BACKLASH_WHEN_PROBING) + static void measure_with_probe(); + #endif }; extern Backlash backlash; diff --git a/Marlin/src/feature/bedlevel/abl/abl.cpp b/Marlin/src/feature/bedlevel/abl/bbl.cpp similarity index 62% rename from Marlin/src/feature/bedlevel/abl/abl.cpp rename to Marlin/src/feature/bedlevel/abl/bbl.cpp index a585c1e155..918b06d2b4 100644 --- a/Marlin/src/feature/bedlevel/abl/abl.cpp +++ b/Marlin/src/feature/bedlevel/abl/bbl.cpp @@ -35,23 +35,28 @@ #include "../../../lcd/extui/ui_api.h" #endif -xy_pos_t bilinear_grid_spacing, bilinear_start; -xy_float_t bilinear_grid_factor; -bed_mesh_t z_values; +LevelingBilinear bedlevel; + +xy_pos_t LevelingBilinear::grid_spacing, + LevelingBilinear::grid_start; +xy_float_t LevelingBilinear::grid_factor; +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 */ -static void extrapolate_one_point(const uint8_t x, const uint8_t y, const int8_t xdir, const int8_t ydir) { +void LevelingBilinear::extrapolate_one_point(const uint8_t x, const uint8_t y, const int8_t xdir, const int8_t ydir) { if (!isnan(z_values[x][y])) return; if (DEBUGGING(LEVELING)) { DEBUG_ECHOPGM("Extrapolate ["); if (x < 10) DEBUG_CHAR(' '); - DEBUG_ECHO((int)x); + DEBUG_ECHO(x); DEBUG_CHAR(xdir ? (xdir > 0 ? '+' : '-') : ' '); DEBUG_CHAR(' '); if (y < 10) DEBUG_CHAR(' '); - DEBUG_ECHO((int)y); + DEBUG_ECHO(y); DEBUG_CHAR(ydir ? (ydir > 0 ? '+' : '-') : ' '); DEBUG_ECHOLNPGM("]"); } @@ -85,36 +90,51 @@ static void extrapolate_one_point(const uint8_t x, const uint8_t y, const int8_t //#define EXTRAPOLATE_FROM_EDGE #if ENABLED(EXTRAPOLATE_FROM_EDGE) - #if GRID_MAX_POINTS_X < GRID_MAX_POINTS_Y + #if (GRID_MAX_POINTS_X) < (GRID_MAX_POINTS_Y) #define HALF_IN_X - #elif GRID_MAX_POINTS_Y < GRID_MAX_POINTS_X + #elif (GRID_MAX_POINTS_Y) < (GRID_MAX_POINTS_X) #define HALF_IN_Y #endif #endif +void LevelingBilinear::reset() { + grid_start.reset(); + grid_spacing.reset(); + GRID_LOOP(x, y) { + z_values[x][y] = NAN; + TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(x, y, 0)); + } +} + +void LevelingBilinear::set_grid(const xy_pos_t& _grid_spacing, const xy_pos_t& _grid_start) { + grid_spacing = _grid_spacing; + grid_start = _grid_start; + grid_factor = grid_spacing.reciprocal(); +} + /** * Fill in the unprobed points (corners of circular print surface) * using linear extrapolation, away from the center. */ -void extrapolate_unprobed_bed_level() { +void LevelingBilinear::extrapolate_unprobed_bed_level() { #ifdef HALF_IN_X - constexpr uint8_t ctrx2 = 0, xlen = GRID_MAX_POINTS_X - 1; + constexpr uint8_t ctrx2 = 0, xend = GRID_MAX_POINTS_X - 1; #else - constexpr uint8_t ctrx1 = (GRID_MAX_POINTS_X - 1) / 2, // left-of-center - ctrx2 = (GRID_MAX_POINTS_X) / 2, // right-of-center - xlen = ctrx1; + constexpr uint8_t ctrx1 = (GRID_MAX_CELLS_X) / 2, // left-of-center + ctrx2 = (GRID_MAX_POINTS_X) / 2, // right-of-center + xend = ctrx1; #endif #ifdef HALF_IN_Y - constexpr uint8_t ctry2 = 0, ylen = GRID_MAX_POINTS_Y - 1; + constexpr uint8_t ctry2 = 0, yend = GRID_MAX_POINTS_Y - 1; #else - constexpr uint8_t ctry1 = (GRID_MAX_POINTS_Y - 1) / 2, // top-of-center - ctry2 = (GRID_MAX_POINTS_Y) / 2, // bottom-of-center - ylen = ctry1; + constexpr uint8_t ctry1 = (GRID_MAX_CELLS_Y) / 2, // top-of-center + ctry2 = (GRID_MAX_POINTS_Y) / 2, // bottom-of-center + yend = ctry1; #endif - LOOP_LE_N(xo, xlen) - LOOP_LE_N(yo, ylen) { + for (uint8_t xo = 0; xo <= xend; ++xo) + for (uint8_t yo = 0; yo <= yend; ++yo) { uint8_t x2 = ctrx2 + xo, y2 = ctry2 + yo; #ifndef HALF_IN_X const uint8_t x1 = ctrx1 - xo; @@ -131,40 +151,44 @@ void extrapolate_unprobed_bed_level() { #endif extrapolate_one_point(x2, y2, -1, -1); // right-above - - } - } -void print_bilinear_leveling_grid() { +void LevelingBilinear::print_leveling_grid(const bed_mesh_t* _z_values/*=nullptr*/) { + // print internal grid(s) or just the one passed as a parameter SERIAL_ECHOLNPGM("Bilinear Leveling Grid:"); - print_2d_array(GRID_MAX_POINTS_X, GRID_MAX_POINTS_Y, 3, - [](const uint8_t ix, const uint8_t iy) { return z_values[ix][iy]; } - ); + print_2d_array(GRID_MAX_POINTS_X, GRID_MAX_POINTS_Y, 3, _z_values ? *_z_values[0] : z_values[0]); + + #if ENABLED(ABL_BILINEAR_SUBDIVISION) + if (!_z_values) { + SERIAL_ECHOLNPGM("Subdivided with CATMULL ROM Leveling Grid:"); + print_2d_array(ABL_GRID_POINTS_VIRT_X, ABL_GRID_POINTS_VIRT_Y, 5, z_values_virt[0]); + } + #endif } #if ENABLED(ABL_BILINEAR_SUBDIVISION) - #define ABL_GRID_POINTS_VIRT_X (GRID_MAX_POINTS_X - 1) * (BILINEAR_SUBDIVISIONS) + 1 - #define ABL_GRID_POINTS_VIRT_Y (GRID_MAX_POINTS_Y - 1) * (BILINEAR_SUBDIVISIONS) + 1 #define ABL_TEMP_POINTS_X (GRID_MAX_POINTS_X + 2) #define ABL_TEMP_POINTS_Y (GRID_MAX_POINTS_Y + 2) - float z_values_virt[ABL_GRID_POINTS_VIRT_X][ABL_GRID_POINTS_VIRT_Y]; - xy_pos_t bilinear_grid_spacing_virt; - xy_float_t bilinear_grid_factor_virt; - - void print_bilinear_leveling_grid_virt() { - SERIAL_ECHOLNPGM("Subdivided with CATMULL ROM Leveling Grid:"); - print_2d_array(ABL_GRID_POINTS_VIRT_X, ABL_GRID_POINTS_VIRT_Y, 5, - [](const uint8_t ix, const uint8_t iy) { return z_values_virt[ix][iy]; } - ); - } + float LevelingBilinear::z_values_virt[ABL_GRID_POINTS_VIRT_X][ABL_GRID_POINTS_VIRT_Y]; + xy_pos_t LevelingBilinear::grid_spacing_virt; + xy_float_t LevelingBilinear::grid_factor_virt; #define LINEAR_EXTRAPOLATION(E, I) ((E) * 2 - (I)) - float bed_level_virt_coord(const uint8_t x, const uint8_t y) { + float LevelingBilinear::virt_coord(const uint8_t x, const uint8_t y) { uint8_t ep = 0, ip = 1; + if (x > (GRID_MAX_POINTS_X) + 1 || y > (GRID_MAX_POINTS_Y) + 1) { + // 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 + // making this function more complex by extrapolating two points. + return 0.0; + } if (!x || x == ABL_TEMP_POINTS_X - 1) { if (x) { - ep = GRID_MAX_POINTS_X - 1; - ip = GRID_MAX_POINTS_X - 2; + ep = (GRID_MAX_POINTS_X) - 1; + ip = GRID_MAX_CELLS_X - 1; } if (WITHIN(y, 1, ABL_TEMP_POINTS_Y - 2)) return LINEAR_EXTRAPOLATION( @@ -173,14 +197,14 @@ void print_bilinear_leveling_grid() { ); else return LINEAR_EXTRAPOLATION( - bed_level_virt_coord(ep + 1, y), - bed_level_virt_coord(ip + 1, y) + virt_coord(ep + 1, y), + virt_coord(ip + 1, y) ); } if (!y || y == ABL_TEMP_POINTS_Y - 1) { if (y) { - ep = GRID_MAX_POINTS_Y - 1; - ip = GRID_MAX_POINTS_Y - 2; + ep = (GRID_MAX_POINTS_Y) - 1; + ip = GRID_MAX_CELLS_Y - 1; } if (WITHIN(x, 1, ABL_TEMP_POINTS_X - 2)) return LINEAR_EXTRAPOLATION( @@ -189,14 +213,14 @@ void print_bilinear_leveling_grid() { ); else return LINEAR_EXTRAPOLATION( - bed_level_virt_coord(x, ep + 1), - bed_level_virt_coord(x, ip + 1) + virt_coord(x, ep + 1), + virt_coord(x, ip + 1) ); } return z_values[x - 1][y - 1]; } - static float bed_level_virt_cmr(const float p[4], const uint8_t i, const float t) { + float LevelingBilinear::virt_cmr(const float p[4], const uint8_t i, const float t) { return ( p[i-1] * -t * sq(1 - t) + p[i] * (2 - 5 * sq(t) + 3 * t * sq(t)) @@ -205,69 +229,66 @@ void print_bilinear_leveling_grid() { ) * 0.5f; } - static float bed_level_virt_2cmr(const uint8_t x, const uint8_t y, const float &tx, const float &ty) { + float LevelingBilinear::virt_2cmr(const uint8_t x, const uint8_t y, const float tx, const float ty) { float row[4], column[4]; - LOOP_L_N(i, 4) { - LOOP_L_N(j, 4) { - column[j] = bed_level_virt_coord(i + x - 1, j + y - 1); + for (uint8_t i = 0; i < 4; ++i) { + for (uint8_t j = 0; j < 4; ++j) { + column[j] = virt_coord(i + x - 1, j + y - 1); } - row[i] = bed_level_virt_cmr(column, 1, ty); + row[i] = virt_cmr(column, 1, ty); } - return bed_level_virt_cmr(row, 1, tx); + return virt_cmr(row, 1, tx); } - void bed_level_virt_interpolate() { - bilinear_grid_spacing_virt = bilinear_grid_spacing / (BILINEAR_SUBDIVISIONS); - bilinear_grid_factor_virt = bilinear_grid_spacing_virt.reciprocal(); - LOOP_L_N(y, GRID_MAX_POINTS_Y) - LOOP_L_N(x, GRID_MAX_POINTS_X) - LOOP_L_N(ty, BILINEAR_SUBDIVISIONS) - LOOP_L_N(tx, BILINEAR_SUBDIVISIONS) { + void LevelingBilinear::subdivide_mesh() { + grid_spacing_virt = grid_spacing / (BILINEAR_SUBDIVISIONS); + grid_factor_virt = grid_spacing_virt.reciprocal(); + for (uint8_t y = 0; y < GRID_MAX_POINTS_Y; ++y) + for (uint8_t x = 0; x < GRID_MAX_POINTS_X; ++x) + for (uint8_t ty = 0; ty < BILINEAR_SUBDIVISIONS; ++ty) + for (uint8_t tx = 0; tx < BILINEAR_SUBDIVISIONS; ++tx) { if ((ty && y == (GRID_MAX_POINTS_Y) - 1) || (tx && x == (GRID_MAX_POINTS_X) - 1)) continue; z_values_virt[x * (BILINEAR_SUBDIVISIONS) + tx][y * (BILINEAR_SUBDIVISIONS) + ty] = - bed_level_virt_2cmr( - x + 1, - y + 1, - (float)tx / (BILINEAR_SUBDIVISIONS), - (float)ty / (BILINEAR_SUBDIVISIONS) - ); + virt_2cmr(x + 1, y + 1, (float)tx / (BILINEAR_SUBDIVISIONS), (float)ty / (BILINEAR_SUBDIVISIONS)); } } + #endif // ABL_BILINEAR_SUBDIVISION // Refresh after other values have been updated -void refresh_bed_level() { - bilinear_grid_factor = bilinear_grid_spacing.reciprocal(); - TERN_(ABL_BILINEAR_SUBDIVISION, bed_level_virt_interpolate()); +void LevelingBilinear::refresh_bed_level() { + TERN_(ABL_BILINEAR_SUBDIVISION, subdivide_mesh()); + cached_rel.x = cached_rel.y = -999.999; + cached_g.x = cached_g.y = -99; } #if ENABLED(ABL_BILINEAR_SUBDIVISION) - #define ABL_BG_SPACING(A) bilinear_grid_spacing_virt.A - #define ABL_BG_FACTOR(A) bilinear_grid_factor_virt.A + #define ABL_BG_SPACING(A) grid_spacing_virt.A + #define ABL_BG_FACTOR(A) grid_factor_virt.A #define ABL_BG_POINTS_X ABL_GRID_POINTS_VIRT_X #define ABL_BG_POINTS_Y ABL_GRID_POINTS_VIRT_Y #define ABL_BG_GRID(X,Y) z_values_virt[X][Y] #else - #define ABL_BG_SPACING(A) bilinear_grid_spacing.A - #define ABL_BG_FACTOR(A) bilinear_grid_factor.A + #define ABL_BG_SPACING(A) grid_spacing.A + #define ABL_BG_FACTOR(A) grid_factor.A #define ABL_BG_POINTS_X GRID_MAX_POINTS_X #define ABL_BG_POINTS_Y GRID_MAX_POINTS_Y #define ABL_BG_GRID(X,Y) z_values[X][Y] #endif // Get the Z adjustment for non-linear bed leveling -float bilinear_z_offset(const xy_pos_t &raw) { +float LevelingBilinear::get_z_correction(const xy_pos_t &raw) { static float z1, d2, z3, d4, L, D; - static xy_pos_t prev { -999.999, -999.999 }, ratio; + static xy_pos_t ratio; // Whole units for the grid line indices. Constrained within bounds. - static xy_int8_t thisg, nextg, lastg { -99, -99 }; + static xy_int8_t thisg, nextg; // XY relative to the probed area - xy_pos_t rel = raw - bilinear_start.asFloat(); + xy_pos_t rel = raw - grid_start.asFloat(); #if ENABLED(EXTRAPOLATE_BEYOND_GRID) #define FAR_EDGE_OR_BOX 2 // Keep using the last grid box @@ -275,8 +296,8 @@ float bilinear_z_offset(const xy_pos_t &raw) { #define FAR_EDGE_OR_BOX 1 // Just use the grid far edge #endif - if (prev.x != rel.x) { - prev.x = rel.x; + if (cached_rel.x != rel.x) { + cached_rel.x = rel.x; ratio.x = rel.x * ABL_BG_FACTOR(x); const float gx = constrain(FLOOR(ratio.x), 0, ABL_BG_POINTS_X - (FAR_EDGE_OR_BOX)); ratio.x -= gx; // Subtract whole to get the ratio within the grid box @@ -290,10 +311,10 @@ float bilinear_z_offset(const xy_pos_t &raw) { nextg.x = _MIN(thisg.x + 1, ABL_BG_POINTS_X - 1); } - if (prev.y != rel.y || lastg.x != thisg.x) { + if (cached_rel.y != rel.y || cached_g.x != thisg.x) { - if (prev.y != rel.y) { - prev.y = rel.y; + if (cached_rel.y != rel.y) { + cached_rel.y = rel.y; ratio.y = rel.y * ABL_BG_FACTOR(y); const float gy = constrain(FLOOR(ratio.y), 0, ABL_BG_POINTS_Y - (FAR_EDGE_OR_BOX)); ratio.y -= gy; @@ -307,8 +328,8 @@ float bilinear_z_offset(const xy_pos_t &raw) { nextg.y = _MIN(thisg.y + 1, ABL_BG_POINTS_Y - 1); } - if (lastg != thisg) { - lastg = thisg; + if (cached_g != thisg) { + cached_g = thisg; // Z at the box corners z1 = ABL_BG_GRID(thisg.x, thisg.y); // left-front d2 = ABL_BG_GRID(thisg.x, nextg.y) - z1; // left-back (delta) @@ -328,11 +349,11 @@ float bilinear_z_offset(const xy_pos_t &raw) { /* static float last_offset = 0; if (ABS(last_offset - offset) > 0.2) { - SERIAL_ECHOLNPAIR("Sudden Shift at x=", rel.x, " / ", bilinear_grid_spacing.x, " -> thisg.x=", thisg.x); - SERIAL_ECHOLNPAIR(" y=", rel.y, " / ", bilinear_grid_spacing.y, " -> thisg.y=", thisg.y); - SERIAL_ECHOLNPAIR(" ratio.x=", ratio.x, " ratio.y=", ratio.y); - SERIAL_ECHOLNPAIR(" z1=", z1, " z2=", z2, " z3=", z3, " z4=", z4); - SERIAL_ECHOLNPAIR(" L=", L, " R=", R, " offset=", offset); + SERIAL_ECHOLNPGM("Sudden Shift at x=", rel.x, " / ", grid_spacing.x, " -> thisg.x=", thisg.x); + SERIAL_ECHOLNPGM(" y=", rel.y, " / ", grid_spacing.y, " -> thisg.y=", thisg.y); + SERIAL_ECHOLNPGM(" ratio.x=", ratio.x, " ratio.y=", ratio.y); + SERIAL_ECHOLNPGM(" z1=", z1, " z2=", z2, " z3=", z3, " z4=", z4); + SERIAL_ECHOLNPGM(" L=", L, " R=", R, " offset=", offset); } last_offset = offset; //*/ @@ -342,13 +363,13 @@ float bilinear_z_offset(const xy_pos_t &raw) { #if IS_CARTESIAN && DISABLED(SEGMENT_LEVELED_MOVES) - #define CELL_INDEX(A,V) ((V - bilinear_start.A) * ABL_BG_FACTOR(A)) + #define CELL_INDEX(A,V) ((V - grid_start.A) * ABL_BG_FACTOR(A)) /** * Prepare a bilinear-leveled linear move on Cartesian, * splitting the move where it crosses grid borders. */ - void bilinear_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) }; @@ -376,7 +397,7 @@ float bilinear_z_offset(const xy_pos_t &raw) { // Split on the X grid line CBI(x_splits, gc.x); end = destination; - destination.x = bilinear_start.x + ABL_BG_SPACING(x) * gc.x; + 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); } @@ -385,7 +406,7 @@ float bilinear_z_offset(const xy_pos_t &raw) { // Split on the Y grid line CBI(y_splits, gc.y); end = destination; - destination.y = bilinear_start.y + ABL_BG_SPACING(y) * gc.y; + 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); } @@ -401,11 +422,11 @@ float bilinear_z_offset(const xy_pos_t &raw) { destination.e = LINE_SEGMENT_END(e); // Do the split and look for more borders - bilinear_line_to_destination(scaled_fr_mm_s, x_splits, y_splits); + line_to_destination(scaled_fr_mm_s, x_splits, y_splits); // Restore destination from stack destination = end; - bilinear_line_to_destination(scaled_fr_mm_s, x_splits, y_splits); + line_to_destination(scaled_fr_mm_s, x_splits, y_splits); } #endif // IS_CARTESIAN && !SEGMENT_LEVELED_MOVES diff --git a/Marlin/src/feature/bedlevel/abl/bbl.h b/Marlin/src/feature/bedlevel/abl/bbl.h new file mode 100644 index 0000000000..fb890333dc --- /dev/null +++ b/Marlin/src/feature/bedlevel/abl/bbl.h @@ -0,0 +1,70 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2020 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" + +class LevelingBilinear { +public: + static bed_mesh_t z_values; + static xy_pos_t grid_spacing, grid_start; + +private: + static xy_float_t grid_factor; + static xy_pos_t cached_rel; + static xy_int8_t cached_g; + + static void extrapolate_one_point(const uint8_t x, const uint8_t y, const int8_t xdir, const int8_t ydir); + + #if ENABLED(ABL_BILINEAR_SUBDIVISION) + #define ABL_GRID_POINTS_VIRT_X (GRID_MAX_CELLS_X * (BILINEAR_SUBDIVISIONS) + 1) + #define ABL_GRID_POINTS_VIRT_Y (GRID_MAX_CELLS_Y * (BILINEAR_SUBDIVISIONS) + 1) + + static float z_values_virt[ABL_GRID_POINTS_VIRT_X][ABL_GRID_POINTS_VIRT_Y]; + static xy_pos_t grid_spacing_virt; + static xy_float_t grid_factor_virt; + + 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 tx, const float ty); + static void subdivide_mesh(); + #endif + +public: + static void reset(); + static void set_grid(const xy_pos_t& _grid_spacing, const xy_pos_t& _grid_start); + static void extrapolate_unprobed_bed_level(); + static void print_leveling_grid(const bed_mesh_t *_z_values=nullptr); + static void refresh_bed_level(); + static bool has_mesh() { return !!grid_spacing.x; } + static bool mesh_is_valid() { return has_mesh(); } + static float get_mesh_x(const uint8_t i) { return grid_start.x + i * grid_spacing.x; } + static float get_mesh_y(const uint8_t j) { return grid_start.y + j * grid_spacing.y; } + static float get_z_correction(const xy_pos_t &raw); + 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); + #endif +}; + +extern LevelingBilinear bedlevel; diff --git a/Marlin/src/feature/bedlevel/bdl/bdl.cpp b/Marlin/src/feature/bedlevel/bdl/bdl.cpp new file mode 100644 index 0000000000..45c7792d5c --- /dev/null +++ b/Marlin/src/feature/bedlevel/bdl/bdl.cpp @@ -0,0 +1,259 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2022 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(BD_SENSOR) + +#include "../../../gcode/gcode.h" +#include "../../../module/settings.h" +#include "../../../module/motion.h" +#include "../../../module/planner.h" +#include "../../../module/stepper.h" +#include "../../../module/probe.h" +#include "../../../module/temperature.h" +#include "../../../module/endstops.h" +#include "../../babystep.h" +#include "../../../lcd/marlinui.h" + +// I2C software Master library for segment bed heating and bed distance sensor +#include + +#include "bdl.h" +BDS_Leveling bdl; + +//#define DEBUG_OUT_BD +#define DEBUG_OUT ENABLED(DEBUG_OUT_BD) +#include "../../../core/debug_out.h" + +/** + * 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 +#define CMD_START_READ_CALIBRATE_DATA 1017 +#define CMD_END_READ_CALIBRATE_DATA 1018 +#define CMD_START_CALIBRATE 1019 +#define CMD_END_CALIBRATE 1021 +#define BD_SENSOR_I2C_ADDR 0x3C + +I2C_SegmentBED BD_I2C_SENSOR; +float BDS_Leveling::pos_zero_offset; +int8_t BDS_Leveling::config_state; + +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; + SERIAL_ECHOLNPGM("BD Sensor Zero Offset:", pos_zero_offset); +} + +bool BDS_Leveling::check(const uint16_t data, const bool raw_data/*=false*/, const bool hicheck/*=false*/) { + if (BD_I2C_SENSOR.BD_Check_OddEven(data) == 0) { + SERIAL_ECHOLNPGM("Read Error."); + return true; // error + } + if (raw_data == true) { + if (hicheck && (data & 0x3FF) > 400) + SERIAL_ECHOLNPGM("Bad BD Sensor height! Recommended distance 0.5-2.0mm"); + else if (!good_data(data)) + SERIAL_ECHOLNPGM("Invalid data, please calibrate."); + else + return false; + } + else { + if ((data & 0x3FF) >= (MAX_BD_HEIGHT) * 100 - 10) + SERIAL_ECHOLNPGM("Out of Range."); + else + return false; + } + return true; // error +} + +float BDS_Leveling::interpret(const uint16_t data) { + return (data & 0x3FF) * 0.01f; +} + +float BDS_Leveling::read() { + const uint16_t data = BD_I2C_SENSOR.BD_i2c_read(); + return check(data) ? NAN : interpret(data); +} + +void BDS_Leveling::process() { + 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(); + if (ELAPSED(ms, next_check_ms)) { // timed out (or first run) + // Check at 1KHz, 5Hz, or 20Hz + next_check_ms = ms + (config_state == BDS_HOMING_Z ? 1 : (config_state < BDS_IDLE ? 200 : 50)); + + 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; + tmp = BD_I2C_SENSOR.BD_i2c_read(); + if (BD_I2C_SENSOR.BD_Check_OddEven(tmp) && good_data(tmp)) { + const float z_sensor = interpret(tmp); + #if ENABLED(BABYSTEPPING) + if (config_state > 0) { + if (cur_z < config_state * 0.1f + && old_cur_z == cur_z + && old_buf_z == current_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); + } + else + babystep.set_mm(Z_AXIS, 0); + } + #endif + + old_cur_z = cur_z; + old_buf_z = current_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); + } + #endif + } + else if (config_state == BDS_HOMING_Z) { + SERIAL_ECHOLNPGM("Read:", tmp); + marlin.kill(F("BDsensor connect Err!")); + } + + DEBUG_ECHOLNPGM("BD:", tmp & 0x3FF, " Z:", cur_z, "|", current_position.z); + if (TERN0(DEBUG_OUT_BD, BD_I2C_SENSOR.BD_Check_OddEven(tmp) == 0)) DEBUG_ECHOLNPGM("CRC error"); + + if (!good_data(tmp)) { + BD_I2C_SENSOR.BD_i2c_stop(); + safe_delay(10); + } + + // Read version. Usually used as a connection check + if (config_state == BDS_VERSION) { + config_state = BDS_IDLE; + BD_I2C_SENSOR.BD_i2c_write(CMD_READ_VERSION); + safe_delay(100); + char tmp_1[21]; + for (int i = 0; i < 19; i++) { + tmp_1[i] = BD_I2C_SENSOR.BD_i2c_read() & 0xFF; + safe_delay(50); + } + BD_I2C_SENSOR.BD_i2c_write(CMD_END_READ_CALIBRATE_DATA); + SERIAL_ECHOLNPGM("BD Sensor version:", tmp_1); + if (tmp_1[0] != 'V') SERIAL_ECHOLNPGM("Read Error. Check connection and delay."); + safe_delay(50); + } + // read raw calibrate data + else if (config_state == BDS_READ_RAW) { + BD_I2C_SENSOR.BD_i2c_write(CMD_START_READ_CALIBRATE_DATA); + safe_delay(100); + + for (int i = 0; i < MAX_BD_HEIGHT * 10; i++) { + tmp = BD_I2C_SENSOR.BD_i2c_read(); + SERIAL_ECHOLNPGM("Calibrate data:", i, ",", tmp & 0x3FF); + (void)check(tmp, true, i == 0); + safe_delay(50); + } + BD_I2C_SENSOR.BD_i2c_write(CMD_END_READ_CALIBRATE_DATA); + safe_delay(50); + config_state = BDS_IDLE; + } + else if (config_state <= BDS_CALIBRATE_START) { // Start Calibrate + safe_delay(10); + if (config_state == BDS_CALIBRATE_START) { + config_state = BDS_CALIBRATING; + REMEMBER(gsit, gcode.stepper_inactive_time, MIN_TO_MS(5)); + 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(); + 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(); + //safe_delay(1000); + + while ((planner.get_axis_position_mm(Z_AXIS) - pos_zero_offset) > 0.00001f) { + safe_delay(200); + SERIAL_ECHOLNPGM("waiting cur_z:", planner.get_axis_position_mm(Z_AXIS)); + } + zpos = 0.00001f; + safe_delay(100); + BD_I2C_SENSOR.BD_i2c_write(CMD_START_CALIBRATE); // Begin calibrate + SERIAL_ECHOLNPGM("BD Sensor Calibrating..."); + safe_delay(200); + } + else if ((planner.get_axis_position_mm(Z_AXIS) - pos_zero_offset) < 10.0f) { + if (zpos >= MAX_BD_HEIGHT) { + config_state = BDS_IDLE; + BD_I2C_SENSOR.BD_i2c_write(CMD_END_CALIBRATE); // End calibrate + SERIAL_ECHOLNPGM("BD Sensor calibrated."); + zpos = 7.0f; + 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); + gcode.process_subcommands_now(tmp_1); + SERIAL_ECHO(tmp_1); SERIAL_ECHOLNPGM(", Z:", current_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; + safe_delay(10); + if (!failcount--) break; + } + safe_delay(600); + tmp = uint16_t((zpos + 0.00001f) * 10); + BD_I2C_SENSOR.BD_i2c_write(tmp); + SERIAL_ECHOLNPGM("w:", tmp, ", Z:", zpos); + zpos += 0.1001f; + } + } + } + } +} + +#endif // BD_SENSOR diff --git a/Marlin/src/feature/bedlevel/bdl/bdl.h b/Marlin/src/feature/bedlevel/bdl/bdl.h new file mode 100644 index 0000000000..ed91d7081b --- /dev/null +++ b/Marlin/src/feature/bedlevel/bdl/bdl.h @@ -0,0 +1,52 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2022 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 + +#ifndef BD_SENSOR_HOME_Z_POSITION + #define BD_SENSOR_HOME_Z_POSITION 0.5 +#endif + +enum BDS_State : int8_t { + BDS_IDLE, + BDS_VERSION = -1, + BDS_READ_MM = -2, + BDS_HOMING_Z = -3, + BDS_READ_RAW = -5, + BDS_CALIBRATE_START = -6, + BDS_CALIBRATING = -7 +}; + +class BDS_Leveling { +public: + static int8_t config_state; + static float pos_zero_offset; + static void init(uint8_t _sda, uint8_t _scl, uint16_t delay_s); + static void process(); + static float read(); + static float interpret(const uint16_t data); + static float good_data(const uint16_t data) { return (data & 0x3FF) < 1016; } + static bool check(const uint16_t data, const bool raw_data=false, const bool hicheck=false); +}; + +extern BDS_Leveling bdl; diff --git a/Marlin/src/feature/bedlevel/bedlevel.cpp b/Marlin/src/feature/bedlevel/bedlevel.cpp index 3e9225b7f9..e479e4c70a 100644 --- a/Marlin/src/feature/bedlevel/bedlevel.cpp +++ b/Marlin/src/feature/bedlevel/bedlevel.cpp @@ -27,7 +27,7 @@ #include "bedlevel.h" #include "../../module/planner.h" -#if EITHER(MESH_BED_LEVELING, PROBE_MANUALLY) +#if ANY(MESH_BED_LEVELING, PROBE_MANUALLY) #include "../../module/motion.h" #endif @@ -36,7 +36,7 @@ #endif #if ENABLED(LCD_BED_LEVELING) - #include "../../lcd/ultralcd.h" + #include "../../lcd/marlinui.h" #endif #define DEBUG_OUT ENABLED(DEBUG_LEVELING_FEATURE) @@ -47,48 +47,41 @@ #endif bool leveling_is_valid() { - return TERN1(MESH_BED_LEVELING, mbl.has_mesh()) - && TERN1(AUTO_BED_LEVELING_BILINEAR, !!bilinear_grid_spacing.x) - && TERN1(AUTO_BED_LEVELING_UBL, ubl.mesh_is_valid()); + return TERN1(HAS_MESH, bedlevel.mesh_is_valid()); } /** - * Turn bed leveling on or off, fixing the current - * position as-needed. + * Turn bed leveling on or off, correcting the current position. * * Disable: Current position = physical position * Enable: Current position = "unleveled" physical position */ void set_bed_leveling_enabled(const bool enable/*=true*/) { + DEBUG_SECTION(log_sble, "set_bed_leveling_enabled", DEBUGGING(LEVELING)); const bool can_change = TERN1(AUTO_BED_LEVELING_BILINEAR, !enable || leveling_is_valid()); if (can_change && enable != planner.leveling_active) { + auto _report_leveling = []{ + if (DEBUGGING(LEVELING)) { + if (planner.leveling_active) + DEBUG_POS("Leveling ON", current_position); + else + DEBUG_POS("Leveling OFF", current_position); + } + }; + + _report_leveling(); planner.synchronize(); - #if ENABLED(AUTO_BED_LEVELING_BILINEAR) - // Force bilinear_z_offset to re-calculate next time - const xyz_pos_t reset { -9999.999, -9999.999, 0 }; - (void)bilinear_z_offset(reset); - #endif - - if (planner.leveling_active) { // leveling from on to off - if (DEBUGGING(LEVELING)) DEBUG_POS("Leveling ON", current_position); - // change unleveled current_position to physical current_position without moving steppers. - planner.apply_leveling(current_position); - planner.leveling_active = false; // disable only AFTER calling apply_leveling - if (DEBUGGING(LEVELING)) DEBUG_POS("...Now OFF", current_position); - } - else { // leveling from off to on - if (DEBUGGING(LEVELING)) DEBUG_POS("Leveling OFF", current_position); - planner.leveling_active = true; // enable BEFORE calling unapply_leveling, otherwise ignored - // change physical current_position to unleveled current_position without moving steppers. - planner.unapply_leveling(current_position); - if (DEBUGGING(LEVELING)) DEBUG_POS("...Now ON", current_position); - } + // Get the corrected leveled / unleveled position + planner.apply_modifiers(current_position, true); // Physical position with all modifiers + FLIP(planner.leveling_active); // Toggle leveling between apply and unapply + planner.unapply_modifiers(current_position, true); // Logical position with modifiers removed sync_plan_position(); + _report_leveling(); } } @@ -122,26 +115,12 @@ TemporaryBedLevelingState::TemporaryBedLevelingState(const bool enable) : saved( */ void reset_bed_level() { if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("reset_bed_level"); - #if ENABLED(AUTO_BED_LEVELING_UBL) - ubl.reset(); - #else - set_bed_leveling_enabled(false); - #if ENABLED(MESH_BED_LEVELING) - mbl.reset(); - #elif ENABLED(AUTO_BED_LEVELING_BILINEAR) - bilinear_start.reset(); - bilinear_grid_spacing.reset(); - GRID_LOOP(x, y) { - z_values[x][y] = NAN; - TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(x, y, 0)); - } - #elif ABL_PLANAR - planner.bed_level_matrix.set_to_identity(); - #endif - #endif + IF_DISABLED(AUTO_BED_LEVELING_UBL, set_bed_leveling_enabled(false)); + TERN_(HAS_MESH, bedlevel.reset()); + TERN_(ABL_PLANAR, planner.bed_level_matrix.set_to_identity()); } -#if EITHER(AUTO_BED_LEVELING_BILINEAR, MESH_BED_LEVELING) +#if ANY(AUTO_BED_LEVELING_BILINEAR, MESH_BED_LEVELING) /** * Enable to produce output in JSON format suitable @@ -156,30 +135,30 @@ void reset_bed_level() { /** * Print calibration results for plotting or manual frame adjustment. */ - void print_2d_array(const uint8_t sx, const uint8_t sy, const uint8_t precision, element_2d_fn fn) { + void print_2d_array(const uint8_t sx, const uint8_t sy, const uint8_t precision, const float *values) { #ifndef SCAD_MESH_OUTPUT - LOOP_L_N(x, sx) { - serial_spaces(precision + (x < 10 ? 3 : 2)); - SERIAL_ECHO(int(x)); + for (uint8_t x = 0; x < sx; ++x) { + SERIAL_ECHO_SP(precision + (x < 10 ? 3 : 2)); + SERIAL_ECHO(x); } SERIAL_EOL(); #endif #ifdef SCAD_MESH_OUTPUT SERIAL_ECHOLNPGM("measured_z = ["); // open 2D array #endif - LOOP_L_N(y, sy) { + for (uint8_t y = 0; y < sy; ++y) { #ifdef SCAD_MESH_OUTPUT SERIAL_ECHOPGM(" ["); // open sub-array #else if (y < 10) SERIAL_CHAR(' '); - SERIAL_ECHO(int(y)); + SERIAL_ECHO(y); #endif - LOOP_L_N(x, sx) { + for (uint8_t x = 0; x < sx; ++x) { SERIAL_CHAR(' '); - const float offset = fn(x, y); + const float offset = values[x * sy + y]; if (!isnan(offset)) { if (offset >= 0) SERIAL_CHAR('+'); - SERIAL_ECHO_F(offset, int(precision)); + SERIAL_ECHO(p_float_t(offset, precision)); } else { #ifdef SCAD_MESH_OUTPUT @@ -187,7 +166,7 @@ void reset_bed_level() { SERIAL_CHAR(' '); SERIAL_ECHOPGM("NAN"); #else - LOOP_L_N(i, precision + 3) + for (uint8_t i = 0; i < precision + 3; ++i) SERIAL_CHAR(i ? '=' : ' '); #endif } @@ -196,7 +175,7 @@ void reset_bed_level() { #endif } #ifdef SCAD_MESH_OUTPUT - SERIAL_CHAR(' ', ']'); // close sub-array + SERIAL_ECHOPGM(" ]"); // close sub-array if (y < sy - 1) SERIAL_CHAR(','); #endif SERIAL_EOL(); @@ -209,31 +188,31 @@ void reset_bed_level() { #endif // AUTO_BED_LEVELING_BILINEAR || MESH_BED_LEVELING -#if EITHER(MESH_BED_LEVELING, PROBE_MANUALLY) +#if ANY(MESH_BED_LEVELING, PROBE_MANUALLY) void _manual_goto_xy(const xy_pos_t &pos) { + // Get the resting Z position for after the XY move #ifdef MANUAL_PROBE_START_Z - constexpr float startz = _MAX(0, MANUAL_PROBE_START_Z); - #if MANUAL_PROBE_HEIGHT > 0 - do_blocking_move_to_xy_z(pos, MANUAL_PROBE_HEIGHT); - do_blocking_move_to_z(startz); - #else - do_blocking_move_to_xy_z(pos, startz); - #endif - #elif MANUAL_PROBE_HEIGHT > 0 - const float prev_z = current_position.z; - do_blocking_move_to_xy_z(pos, MANUAL_PROBE_HEIGHT); - do_blocking_move_to_z(prev_z); + constexpr float finalz = _MAX(0, MANUAL_PROBE_START_Z); // If a MANUAL_PROBE_START_Z value is set, always respect it #else - do_blocking_move_to_xy(pos); + #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: + #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 + #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! #endif - - current_position = pos; TERN_(LCD_BED_LEVELING, ui.wait_for_move = false); } -#endif +#endif // MESH_BED_LEVELING || PROBE_MANUALLY #endif // HAS_LEVELING diff --git a/Marlin/src/feature/bedlevel/bedlevel.h b/Marlin/src/feature/bedlevel/bedlevel.h index 995e9d0dbc..5bfa2b7faf 100644 --- a/Marlin/src/feature/bedlevel/bedlevel.h +++ b/Marlin/src/feature/bedlevel/bedlevel.h @@ -23,6 +23,10 @@ #include "../../inc/MarlinConfigPre.h" +#if ANY(RESTORE_LEVELING_AFTER_G28, ENABLE_LEVELING_AFTER_G28) + #define CAN_SET_LEVELING_AFTER_G28 1 +#endif + #if ENABLED(PROBE_MANUALLY) extern bool g29_in_progress; #else @@ -37,7 +41,7 @@ void reset_bed_level(); void set_z_fade_height(const float zfh, const bool do_report=true); #endif -#if EITHER(MESH_BED_LEVELING, PROBE_MANUALLY) +#if ANY(MESH_BED_LEVELING, PROBE_MANUALLY) void _manual_goto_xy(const xy_pos_t &pos); #endif @@ -58,17 +62,14 @@ class TemporaryBedLevelingState { typedef float bed_mesh_t[GRID_MAX_POINTS_X][GRID_MAX_POINTS_Y]; #if ENABLED(AUTO_BED_LEVELING_BILINEAR) - #include "abl/abl.h" + #include "abl/bbl.h" #elif ENABLED(AUTO_BED_LEVELING_UBL) #include "ubl/ubl.h" #elif ENABLED(MESH_BED_LEVELING) #include "mbl/mesh_bed_leveling.h" #endif - #define Z_VALUES(X,Y) Z_VALUES_ARR[X][Y] - #define _GET_MESH_POS(M) { _GET_MESH_X(M.a), _GET_MESH_Y(M.b) } - - #if EITHER(AUTO_BED_LEVELING_BILINEAR, MESH_BED_LEVELING) + #if ANY(AUTO_BED_LEVELING_BILINEAR, MESH_BED_LEVELING) #include @@ -77,7 +78,7 @@ class TemporaryBedLevelingState { /** * Print calibration results for plotting or manual frame adjustment. */ - void print_2d_array(const uint8_t sx, const uint8_t sy, const uint8_t precision, element_2d_fn fn); + void print_2d_array(const uint8_t sx, const uint8_t sy, const uint8_t precision, const float *values); #endif @@ -88,7 +89,7 @@ class TemporaryBedLevelingState { bool valid() const { return pos.x >= 0 && pos.y >= 0; } #if ENABLED(AUTO_BED_LEVELING_UBL) xy_pos_t meshpos() { - return { ubl.mesh_index_to_xpos(pos.x), ubl.mesh_index_to_ypos(pos.y) }; + return { bedlevel.get_mesh_x(pos.x), bedlevel.get_mesh_y(pos.y) }; } #endif operator xy_int8_t&() { return pos; } diff --git a/Marlin/src/feature/bedlevel/hilbert_curve.cpp b/Marlin/src/feature/bedlevel/hilbert_curve.cpp new file mode 100644 index 0000000000..57cbdfb34d --- /dev/null +++ b/Marlin/src/feature/bedlevel/hilbert_curve.cpp @@ -0,0 +1,110 @@ +/********************* + * hilbert_curve.cpp * + *********************/ + +/**************************************************************************** + * Written By Marcio Teixeira 2021 - SynDaver Labs, Inc. * + * * + * 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. * + * * + * To view a copy of the GNU General Public License, go to the following * + * location: . * + ****************************************************************************/ + +#include "../../inc/MarlinConfig.h" + +#if ENABLED(UBL_HILBERT_CURVE) + +#include "bedlevel.h" +#include "hilbert_curve.h" + +constexpr int8_t to_fix(int8_t v) { return v * 2; } +constexpr int8_t to_int(int8_t v) { return v / 2; } +constexpr uint8_t log2(uint8_t n) { return (n > 1) ? 1 + log2(uint8_t(n >> 1)) : 0; } +constexpr uint8_t order(uint8_t n) { return uint8_t(log2(uint8_t(n - 1))) + 1; } +constexpr uint8_t ord = order(_MAX(GRID_MAX_POINTS_X, GRID_MAX_POINTS_Y)); +constexpr uint8_t dim = _BV(ord); + +static inline bool eval_candidate(int8_t x, int8_t y, hilbert_curve::callback_ptr func, void *data) { + // The print bed likely has fewer points than the full Hilbert + // curve, so cull unnecessary points + return x < (GRID_MAX_POINTS_X) && y < (GRID_MAX_POINTS_Y) ? func(x, y, data) : false; +} + +bool hilbert_curve::hilbert(int8_t x, int8_t y, int8_t xi, int8_t xj, int8_t yi, int8_t yj, uint8_t n, hilbert_curve::callback_ptr func, void *data) { + /** + * Hilbert space-filling curve implementation + * + * x and y : coordinates of the bottom left corner + * xi and xj : i and j components of the unit x vector of the frame + * yi and yj : i and j components of the unit y vector of the frame + * + * From: http://www.fundza.com/algorithmic/space_filling/hilbert/basics/index.html + */ + if (n) + return hilbert(x, y, yi/2, yj/2, xi/2, xj/2, n-1, func, data) || + hilbert(x+xi/2, y+xj/2, xi/2, xj/2, yi/2, yj/2, n-1, func, data) || + hilbert(x+xi/2+yi/2, y+xj/2+yj/2, xi/2, xj/2, yi/2, yj/2, n-1, func, data) || + hilbert(x+xi/2+yi, y+xj/2+yj, -yi/2, -yj/2, -xi/2, -xj/2, n-1, func, data); + else + return eval_candidate(to_int(x+(xi+yi)/2), to_int(y+(xj+yj)/2), func, data); +} + +/** + * Calls func(x, y, data) for all points in the Hilbert curve. + * If that function returns true, the search is terminated. + */ +bool hilbert_curve::search(hilbert_curve::callback_ptr func, void *data) { + return hilbert(to_fix(0), to_fix(0),to_fix(dim), to_fix(0), to_fix(0), to_fix(dim), ord, func, data); +} + +/* Helper function for starting the search at a particular point */ + +typedef struct { + uint8_t x, y; + bool found_1st; + hilbert_curve::callback_ptr func; + void *data; +} search_from_t; + +static bool search_from_helper(uint8_t x, uint8_t y, void *data) { + search_from_t *d = (search_from_t *) data; + if (d->x == x && d->y == y) + d->found_1st = true; + return d->found_1st ? d->func(x, y, d->data) : false; +} + +/** + * Same as search, except start at a specific grid intersection point. + */ +bool hilbert_curve::search_from(uint8_t x, uint8_t y, hilbert_curve::callback_ptr func, void *data) { + search_from_t d; + d.x = x; + d.y = y; + d.found_1st = false; + d.func = func; + d.data = data; + // Call twice to allow search to wrap back to the beginning and picked up points prior to the start. + return search(search_from_helper, &d) || search(search_from_helper, &d); +} + +/** + * Like search_from, but takes a bed position and starts from the nearest + * point on the Hilbert curve. + */ +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)); + return search_from(grid_x, grid_y, func, data); +} + +#endif // UBL_HILBERT_CURVE diff --git a/Marlin/src/feature/bedlevel/hilbert_curve.h b/Marlin/src/feature/bedlevel/hilbert_curve.h new file mode 100644 index 0000000000..a5dce8a22d --- /dev/null +++ b/Marlin/src/feature/bedlevel/hilbert_curve.h @@ -0,0 +1,32 @@ +/******************* + * hilbert_curve.h * + *******************/ + +/**************************************************************************** + * Written By Marcio Teixeira 2021 - SynDaver Labs, Inc. * + * * + * 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. * + * * + * To view a copy of the GNU General Public License, go to the following * + * location: . * + ****************************************************************************/ + +#pragma once + +class hilbert_curve { + public: + typedef bool (*callback_ptr)(uint8_t x, uint8_t y, void *data); + static bool search(callback_ptr func, void *data); + static bool search_from(uint8_t x, uint8_t y, callback_ptr func, void *data); + static bool search_from_closest(const xy_pos_t &pos, callback_ptr func, void *data); + private: + static bool hilbert(int8_t x, int8_t y, int8_t xi, int8_t xj, int8_t yi, int8_t yj, uint8_t n, callback_ptr func, void *data); +}; diff --git a/Marlin/src/feature/bedlevel/mbl/mesh_bed_leveling.cpp b/Marlin/src/feature/bedlevel/mbl/mesh_bed_leveling.cpp index 1200c2a1b3..155d34c4df 100644 --- a/Marlin/src/feature/bedlevel/mbl/mesh_bed_leveling.cpp +++ b/Marlin/src/feature/bedlevel/mbl/mesh_bed_leveling.cpp @@ -32,7 +32,7 @@ #include "../../../lcd/extui/ui_api.h" #endif - mesh_bed_leveling mbl; + mesh_bed_leveling bedlevel; float mesh_bed_leveling::z_offset, mesh_bed_leveling::z_values[GRID_MAX_POINTS_X][GRID_MAX_POINTS_Y], @@ -40,9 +40,9 @@ mesh_bed_leveling::index_to_ypos[GRID_MAX_POINTS_Y]; mesh_bed_leveling::mesh_bed_leveling() { - LOOP_L_N(i, GRID_MAX_POINTS_X) + for (uint8_t i = 0; i < GRID_MAX_POINTS_X; ++i) index_to_xpos[i] = MESH_MIN_X + i * (MESH_X_DIST); - LOOP_L_N(i, GRID_MAX_POINTS_Y) + for (uint8_t i = 0; i < GRID_MAX_POINTS_Y; ++i) index_to_ypos[i] = MESH_MIN_Y + i * (MESH_Y_DIST); reset(); } @@ -61,18 +61,18 @@ * 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); - NOMORE(scel.x, GRID_MAX_POINTS_X - 2); - NOMORE(scel.y, GRID_MAX_POINTS_Y - 2); - NOMORE(ecel.x, GRID_MAX_POINTS_X - 2); - NOMORE(ecel.y, GRID_MAX_POINTS_Y - 2); + xy_uint8_t scel = cell_indexes(current_position), ecel = cell_indexes(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); + NOMORE(ecel.y, GRID_MAX_CELLS_Y - 1); // Start and end in the same cell? No split needed. if (scel == ecel) { - line_to_destination(scaled_fr_mm_s); current_position = destination; + line_to_current_position(scaled_fr_mm_s); return; } @@ -80,7 +80,7 @@ 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. @@ -104,8 +104,8 @@ else { // Must already have been split on these border(s) // This should be a rare case. - line_to_destination(scaled_fr_mm_s); current_position = destination; + line_to_current_position(scaled_fr_mm_s); return; } @@ -123,11 +123,8 @@ #endif // IS_CARTESIAN && !SEGMENT_LEVELED_MOVES void mesh_bed_leveling::report_mesh() { - SERIAL_ECHOPAIR_F(STRINGIFY(GRID_MAX_POINTS_X) "x" STRINGIFY(GRID_MAX_POINTS_Y) " mesh. Z offset: ", z_offset, 5); - SERIAL_ECHOLNPGM("\nMeasured points:"); - print_2d_array(GRID_MAX_POINTS_X, GRID_MAX_POINTS_Y, 5, - [](const uint8_t ix, const uint8_t iy) { return z_values[ix][iy]; } - ); + 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 ade7a93140..43dabd3adb 100644 --- a/Marlin/src/feature/bedlevel/mbl/mesh_bed_leveling.h +++ b/Marlin/src/feature/bedlevel/mbl/mesh_bed_leveling.h @@ -32,11 +32,8 @@ enum MeshLevelingState : char { MeshReset // G29 S5 }; -#define MESH_X_DIST (float(MESH_MAX_X - (MESH_MIN_X)) / float(GRID_MAX_POINTS_X - 1)) -#define MESH_Y_DIST (float(MESH_MAX_Y - (MESH_MIN_Y)) / float(GRID_MAX_POINTS_Y - 1)) -#define _GET_MESH_X(I) mbl.index_to_xpos[I] -#define _GET_MESH_Y(J) mbl.index_to_ypos[J] -#define Z_VALUES_ARR mbl.z_values +#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)) class mesh_bed_leveling { public: @@ -56,72 +53,73 @@ public: return false; } - static void set_z(const int8_t px, const int8_t py, const float &z) { z_values[px][py] = z; } + static bool mesh_is_valid() { return has_mesh(); } - static inline void zigzag(const int8_t index, int8_t &px, int8_t &py) { + 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); py = index / (GRID_MAX_POINTS_X); - if (py & 1) px = (GRID_MAX_POINTS_X - 1) - px; // Zig zag + if (py & 1) px = (GRID_MAX_POINTS_X) - 1 - px; // Zig zag } - static void set_zigzag_z(const int8_t index, const float &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); } - static int8_t cell_index_x(const float &x) { + 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 x) { int8_t cx = (x - (MESH_MIN_X)) * RECIPROCAL(MESH_X_DIST); - return constrain(cx, 0, (GRID_MAX_POINTS_X) - 2); + return constrain(cx, 0, GRID_MAX_CELLS_X - 1); } - static int8_t cell_index_y(const float &y) { + static uint8_t cell_index_y(const float y) { int8_t cy = (y - (MESH_MIN_Y)) * RECIPROCAL(MESH_Y_DIST); - return constrain(cy, 0, (GRID_MAX_POINTS_Y) - 2); + return constrain(cy, 0, GRID_MAX_CELLS_Y - 1); } - static inline xy_int8_t cell_indexes(const float &x, const float &y) { + static xy_uint8_t cell_indexes(const float x, const float y) { return { cell_index_x(x), cell_index_y(y) }; } - static inline xy_int8_t cell_indexes(const xy_pos_t &xy) { return cell_indexes(xy.x, xy.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 &x) { + static int8_t probe_index_x(const float x) { 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; + return WITHIN(px, 0, (GRID_MAX_POINTS_X) - 1) ? px : -1; } - static int8_t probe_index_y(const float &y) { + static int8_t probe_index_y(const float y) { 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; + return WITHIN(py, 0, (GRID_MAX_POINTS_Y) - 1) ? py : -1; } - static inline xy_int8_t probe_indexes(const float &x, const float &y) { + static xy_int8_t probe_indexes(const float x, const float y) { return { probe_index_x(x), probe_index_y(y) }; } - static inline xy_int8_t probe_indexes(const xy_pos_t &xy) { return probe_indexes(xy.x, xy.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 &a0, const float &a1, const float &z1, const float &a2, const float &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; } - static float get_z(const xy_pos_t &pos - #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) - , const float &factor=1.0f - #endif - ) { - #if DISABLED(ENABLE_LEVELING_FADE_HEIGHT) - constexpr float factor = 1.0f; - #endif - const xy_int8_t ind = cell_indexes(pos); - const float x1 = index_to_xpos[ind.x], x2 = index_to_xpos[ind.x+1], - y1 = index_to_xpos[ind.y], y2 = index_to_xpos[ind.y+1], - z1 = calc_z0(pos.x, x1, z_values[ind.x][ind.y ], x2, z_values[ind.x+1][ind.y ]), - z2 = calc_z0(pos.x, x1, z_values[ind.x][ind.y+1], x2, z_values[ind.x+1][ind.y+1]); + static float get_z_offset() { return z_offset; } - return z_offset + calc_z0(pos.y, y1, z1, y2, z2) * factor; + static float get_z_correction(const xy_pos_t &pos) { + const xy_uint8_t ind = cell_indexes(pos); + const float x1 = index_to_xpos[ind.x], x2 = index_to_xpos[ind.x+1], + y1 = index_to_ypos[ind.y], y2 = index_to_ypos[ind.y+1], + z1 = calc_z0(pos.x, x1, z_values[ind.x][ind.y ], x2, z_values[ind.x+1][ind.y ]), + z2 = calc_z0(pos.x, x1, z_values[ind.x][ind.y+1], x2, z_values[ind.x+1][ind.y+1]), + zf = calc_z0(pos.y, y1, z1, y2, z2); + + return zf; } #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 }; -extern mesh_bed_leveling mbl; +extern mesh_bed_leveling bedlevel; diff --git a/Marlin/src/feature/bedlevel/ubl/ubl.cpp b/Marlin/src/feature/bedlevel/ubl/ubl.cpp index 087fdf42b2..2357437633 100644 --- a/Marlin/src/feature/bedlevel/ubl/ubl.cpp +++ b/Marlin/src/feature/bedlevel/ubl/ubl.cpp @@ -24,213 +24,275 @@ #if ENABLED(AUTO_BED_LEVELING_UBL) - #include "../bedlevel.h" +#include "../bedlevel.h" - unified_bed_leveling ubl; +unified_bed_leveling bedlevel; - #include "../../../MarlinCore.h" - #include "../../../gcode/gcode.h" +#include "../../../gcode/gcode.h" - #include "../../../module/settings.h" - #include "../../../module/planner.h" - #include "../../../module/motion.h" - #include "../../../module/probe.h" +#include "../../../module/settings.h" +#include "../../../module/planner.h" +#include "../../../module/motion.h" +#include "../../../module/probe.h" +#include "../../../module/temperature.h" +#if ENABLED(EXTENSIBLE_UI) + #include "../../../lcd/extui/ui_api.h" +#endif + +#include "math.h" + +void unified_bed_leveling::echo_name() { SERIAL_ECHOPGM("Unified Bed Leveling"); } + +void unified_bed_leveling::report_current_mesh() { + if (!leveling_is_valid()) return; + SERIAL_ECHO_MSG(" G29 I999"); + GRID_LOOP(x, y) + if (!isnan(z_values[x][y])) { + SERIAL_ECHO_START(); + SERIAL_ECHOLN(F(" M421 I"), x, F(" J"), y, FPSTR(SP_Z_STR), p_float_t(z_values[x][y], 4)); + serial_delay(75); // Prevent Printrun from exploding + } +} + +void unified_bed_leveling::report_state() { + echo_name(); + serial_ternary(F(" System v" UBL_VERSION " "), planner.leveling_active, nullptr, F("in"), F("active\n")); + 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)) + +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) +); + +volatile int16_t unified_bed_leveling::encoder_diff; + +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; + ZERO(z_values); #if ENABLED(EXTENSIBLE_UI) - #include "../../../lcd/extui/ui_api.h" + GRID_LOOP(x, y) ExtUI::onMeshUpdate(x, y, 0); #endif + if (was_enabled) report_current_position(); +} - #include "math.h" +void unified_bed_leveling::invalidate() { + set_bed_leveling_enabled(false); + set_all_mesh_points_to_value(NAN); +} - void unified_bed_leveling::echo_name() { - SERIAL_ECHOPGM("Unified Bed Leveling"); +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)); + } +} + +#if ENABLED(OPTIMIZED_MESH_STORAGE) + + constexpr float mesh_store_scaling = 1000; + 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 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)) + return Z_STEPS_NAN; // If Z is out of range, return our custom 'NaN' + return int16_t(z_scaled); + }; + GRID_LOOP(x, y) stored_values[x][y] = z_to_store(in_values[x][y]); } - void unified_bed_leveling::report_current_mesh() { - if (!leveling_is_valid()) return; - SERIAL_ECHO_MSG(" G29 I999"); - GRID_LOOP(x, y) - if (!isnan(z_values[x][y])) { - SERIAL_ECHO_START(); - SERIAL_ECHOPAIR(" M421 I", int(x), " J", int(y)); - SERIAL_ECHOLNPAIR_F_P(SP_Z_STR, z_values[x][y], 4); - serial_delay(75); // Prevent Printrun from exploding - } + void unified_bed_leveling::set_mesh_from_store(const mesh_store_t &stored_values, bed_mesh_t &out_values) { + auto store_to_z = [](const int16_t z_scaled) { + return z_scaled == Z_STEPS_NAN ? NAN : z_scaled / mesh_store_scaling; + }; + GRID_LOOP(x, y) out_values[x][y] = store_to_z(stored_values[x][y]); } - void unified_bed_leveling::report_state() { - echo_name(); - SERIAL_ECHO_TERNARY(planner.leveling_active, " System v" UBL_VERSION " ", "", "in", "active\n"); - serial_delay(50); - } +#endif // OPTIMIZED_MESH_STORAGE - int8_t unified_bed_leveling::storage_slot; +static void serial_echo_xy(const uint8_t sp, const int16_t x, const int16_t y) { + SERIAL_ECHO_SP(sp); + SERIAL_CHAR('('); + if (x < 100) { SERIAL_CHAR(' '); if (x < 10) SERIAL_CHAR(' '); } + SERIAL_ECHO(x); + SERIAL_CHAR(','); + if (y < 100) { SERIAL_CHAR(' '); if (y < 10) SERIAL_CHAR(' '); } + SERIAL_ECHO(y); + SERIAL_CHAR(')'); + serial_delay(5); +} - 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)) - - 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) - ); - - volatile int16_t unified_bed_leveling::encoder_diff; - - 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; - ZERO(z_values); - #if ENABLED(EXTENSIBLE_UI) - GRID_LOOP(x, y) ExtUI::onMeshUpdate(x, y, 0); - #endif - if (was_enabled) report_current_position(); - } - - void unified_bed_leveling::invalidate() { - set_bed_leveling_enabled(false); - set_all_mesh_points_to_value(NAN); - } - - 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)); - } - } - - static void serial_echo_xy(const uint8_t sp, const int16_t x, const int16_t y) { +static void serial_echo_column_labels(const uint8_t sp) { + SERIAL_ECHO_SP(7); + for (uint8_t i = 0; i < GRID_MAX_POINTS_X; ++i) { + if (i < 10) SERIAL_CHAR(' '); + SERIAL_ECHO(i); SERIAL_ECHO_SP(sp); - SERIAL_CHAR('('); - if (x < 100) { SERIAL_CHAR(' '); if (x < 10) SERIAL_CHAR(' '); } - SERIAL_ECHO(x); - SERIAL_CHAR(','); - if (y < 100) { SERIAL_CHAR(' '); if (y < 10) SERIAL_CHAR(' '); } - SERIAL_ECHO(y); - SERIAL_CHAR(')'); - serial_delay(5); + } + serial_delay(10); +} + +/** + * Produce one of these mesh maps: + * 0: Human-readable + * 1: CSV format for spreadsheet import + * 2: TODO: Display on Graphical LCD + * 4: Compact Human-Readable + */ +void unified_bed_leveling::display_map(const uint8_t map_type) { + const bool was = gcode.set_autoreport_paused(true); + + constexpr uint8_t eachsp = 1 + 6 + 1, // [-3.567] + twixt = eachsp * (GRID_MAX_POINTS_X) - 9 * 2; // Leading 4sp, Coordinates 9sp each + + const bool human = !(map_type & 0x3), csv = map_type == 1, lcd = map_type == 2, comp = map_type & 0x4; + + 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_EOL(); + serial_echo_column_labels(eachsp - 2); + } + else + SERIAL_ECHOPGM(" for ", csv ? F("CSV:\n") : F("LCD:\n")); + + // 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); + + if (!lcd) SERIAL_EOL(); + for (int8_t j = (GRID_MAX_POINTS_Y) - 1; j >= 0; j--) { + + // Row Label (J index) + if (human) { + if (j < 10) SERIAL_CHAR(' '); + SERIAL_ECHO(j); + SERIAL_ECHOPGM(" |"); + } + + // Row Values (I indexes) + for (uint8_t i = 0; i < GRID_MAX_POINTS_X; ++i) { + + // Opening Brace or Space + const bool is_current = i == curr.x && j == curr.y; + if (human) SERIAL_CHAR(is_current ? '[' : ' '); + + // Z Value at current I, J + const float f = z_values[i][j]; + if (lcd) { + // TODO: Display on Graphical LCD + } + else if (isnan(f)) + SERIAL_ECHO(human ? F(" . ") : F("NAN")); + else if (human || csv) { + if (human && f >= 0) SERIAL_CHAR(f > 0 ? '+' : ' '); // Display sign also for positive numbers (' ' for 0) + SERIAL_ECHO(p_float_t(f, 3)); // Positive: 5 digits, Negative: 6 digits + } + if (csv && i < (GRID_MAX_POINTS_X) - 1) SERIAL_CHAR('\t'); + + // Closing Brace or Space + if (human) SERIAL_CHAR(is_current ? ']' : ' '); + + SERIAL_FLUSHTX(); + marlin.idle_no_sleep(); + } + if (!lcd) SERIAL_EOL(); + + // A blank line between rows (unless compact) + if (j && human && !comp) SERIAL_ECHOLNPGM(" |"); } - static void serial_echo_column_labels(const uint8_t sp) { - SERIAL_ECHO_SP(7); - for (int8_t i = 0; i < GRID_MAX_POINTS_X; i++) { - if (i < 10) SERIAL_CHAR(' '); - SERIAL_ECHO(i); - SERIAL_ECHO_SP(sp); - } - serial_delay(10); + 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_EOL(); + SERIAL_EOL(); } + gcode.set_autoreport_paused(was); +} + +bool unified_bed_leveling::sanity_check() { + uint8_t error_flag = 0; + + if (settings.calc_num_meshes() < 1) { + SERIAL_ECHOLNPGM("?Mesh too big for EEPROM."); + error_flag++; + } + + return !!error_flag; +} + +#if ENABLED(UBL_MESH_WIZARD) + /** - * Produce one of these mesh maps: - * 0: Human-readable - * 1: CSV format for spreadsheet import - * 2: TODO: Display on Graphical LCD - * 4: Compact Human-Readable + * M1004: UBL Mesh Wizard - One-click mesh creation with or without a probe */ - void unified_bed_leveling::display_map(const int map_type) { - const bool was = gcode.set_autoreport_paused(true); + void GcodeSuite::M1004() { - constexpr uint8_t eachsp = 1 + 6 + 1, // [-3.567] - twixt = eachsp * (GRID_MAX_POINTS_X) - 9 * 2; // Leading 4sp, Coordinates 9sp each + #define ALIGN_GCODE TERN(Z_STEPPER_AUTO_ALIGN, "G34\n", "") + #define PROBE_GCODE TERN(HAS_BED_PROBE, "G29P1\nG29P3", "G29P4R") - const bool human = !(map_type & 0x3), csv = map_type == 1, lcd = map_type == 2, comp = map_type & 0x4; - - 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_EOL(); - serial_echo_column_labels(eachsp - 2); - } - else { - SERIAL_ECHOPGM(" for "); - serialprintPGM(csv ? PSTR("CSV:\n") : PSTR("LCD:\n")); - } - - // 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); - - if (!lcd) SERIAL_EOL(); - for (int8_t j = GRID_MAX_POINTS_Y - 1; j >= 0; j--) { - - // Row Label (J index) - if (human) { - if (j < 10) SERIAL_CHAR(' '); - SERIAL_ECHO(j); - SERIAL_ECHOPGM(" |"); + #if HAS_HOTEND + if (parser.seenval('H')) { // Handle H# parameter to set Hotend temp + const celsius_t hotend_temp = parser.value_int(); // Marlin never sends itself F or K, always C + thermalManager.setTargetHotend(hotend_temp, 0); + thermalManager.wait_for_hotend(false); } + #endif - // Row Values (I indexes) - LOOP_L_N(i, GRID_MAX_POINTS_X) { - - // Opening Brace or Space - const bool is_current = i == curr.x && j == curr.y; - if (human) SERIAL_CHAR(is_current ? '[' : ' '); - - // Z Value at current I, J - const float f = z_values[i][j]; - if (lcd) { - // TODO: Display on Graphical LCD - } - else if (isnan(f)) - serialprintPGM(human ? PSTR(" . ") : PSTR("NAN")); - else if (human || csv) { - if (human && f >= 0.0) SERIAL_CHAR(f > 0 ? '+' : ' '); // Space for positive ('-' for negative) - SERIAL_ECHO_F(f, 3); // Positive: 5 digits, Negative: 6 digits - } - if (csv && i < GRID_MAX_POINTS_X - 1) SERIAL_CHAR('\t'); - - // Closing Brace or Space - if (human) SERIAL_CHAR(is_current ? ']' : ' '); - - SERIAL_FLUSHTX(); - idle_no_sleep(); + #if HAS_HEATED_BED + if (parser.seenval('B')) { // Handle B# parameter to set Bed temp + const celsius_t bed_temp = parser.value_int(); // Marlin never sends itself F or K, always C + thermalManager.setTargetBed(bed_temp); + thermalManager.wait_for_bed(false); } - if (!lcd) SERIAL_EOL(); + #endif - // A blank line between rows (unless compact) - if (j && human && !comp) SERIAL_ECHOLNPGM(" |"); + process_subcommands_now(FPSTR(G28_STR)); // Home + process_subcommands_now(F(ALIGN_GCODE // Align multi z axis if available + 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 (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_EOL(); - SERIAL_EOL(); - } - - gcode.set_autoreport_paused(was); + process_subcommands_now(F("G29A\nG29F10\n" // Set UBL Active & Fade 10 + "M140S0\nM104S0\n" // Turn off heaters + "M500")); // Store settings } - bool unified_bed_leveling::sanity_check() { - uint8_t error_flag = 0; - - if (settings.calc_num_meshes() < 1) { - SERIAL_ECHOLNPGM("?Mesh too big for EEPROM."); - error_flag++; - } - - return !!error_flag; - } +#endif // UBL_MESH_WIZARD #endif // AUTO_BED_LEVELING_UBL diff --git a/Marlin/src/feature/bedlevel/ubl/ubl.h b/Marlin/src/feature/bedlevel/ubl/ubl.h index 9ac9de1806..f6e9ba0cd9 100644 --- a/Marlin/src/feature/bedlevel/ubl/ubl.h +++ b/Marlin/src/feature/bedlevel/ubl/ubl.h @@ -32,286 +32,282 @@ #define UBL_OK false #define UBL_ERR true -enum MeshPointType : char { INVALID, REAL, SET_IN_BITMAP }; +enum MeshPointType : char { INVALID, REAL, SET_IN_BITMAP, CLOSEST }; // External references struct mesh_index_pair; -#define MESH_X_DIST (float(MESH_MAX_X - (MESH_MIN_X)) / float(GRID_MAX_POINTS_X - 1)) -#define MESH_Y_DIST (float(MESH_MAX_Y - (MESH_MIN_Y)) / float(GRID_MAX_POINTS_Y - 1)) +#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; + grid_count_t R_repetition; + uint8_t V_verbosity, + P_phase, + T_map_type; + float B_shim_thickness, + C_constant; + xy_pos_t XY_pos; + xy_bool_t XY_seen; + #if HAS_BED_PROBE + uint8_t J_grid_size; + #endif +} G29_parameters_t; class unified_bed_leveling { - private: +private: - static int g29_verbose_level, - g29_phase_value, - g29_repetition_cnt, - g29_storage_slot, - g29_map_type; - static bool g29_c_flag; - static float g29_card_thickness, - g29_constant; - static xy_pos_t g29_pos; - static xy_bool_t xy_seen; + static G29_parameters_t param; - #if HAS_BED_PROBE - static int g29_grid_size; - #endif + #if IS_NEWPANEL + 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, const float, const bool) __O0; + static void fine_tune_mesh(const xy_pos_t &pos, const bool do_ubl_mesh_map) __O0; + #endif - #if ENABLED(NEWPANEL) - static void move_z_with_encoder(const float &multiplier); - static float measure_point_with_encoder(); - static float measure_business_card_thickness(float in_height); - 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(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); + static bool smart_fill_one(const xy_uint8_t &pos, const xy_uint8_t &dir) { + return smart_fill_one(pos.x, pos.y, dir.x, dir.y); + } - static bool g29_parameter_parsing() _O0; - static void shift_mesh_height(); - 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_3pts(const float &z1, const float &z2, const float &z3); - 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); - static inline bool smart_fill_one(const xy_uint8_t &pos, const xy_uint8_t &dir) { - return smart_fill_one(pos.x, pos.y, dir.x, dir.y); + #if ENABLED(UBL_DEVEL_DEBUGGING) + static void g29_what_command(); + static void g29_eeprom_dump(); + static void g29_compare_current_mesh_to_stored_mesh(); + #endif + +public: + + static void echo_name(); + static void report_current_mesh(); + static void report_state(); + static void save_ubl_active_state_and_disable(); + 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 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 ) __O2; // O2 gives smaller code than Os on A2560 + + static int8_t storage_slot; + + 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_MARLINUI_MENU + static bool lcd_map_control; + static void steppers_were_disabled(); + #else + static void steppers_were_disabled() {} + #endif + + static volatile int16_t encoder_diff; // Volatile because buttons may change it at interrupt time + + unified_bed_leveling(); + + 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 x) { + return FLOOR((x - (MESH_MIN_X)) * RECIPROCAL(MESH_X_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 x) { + return WITHIN(cell_index_x_raw(x), 0, GRID_MAX_CELLS_X - 1); + } + + 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 x) { + return constrain(cell_index_x_raw(x), 0, GRID_MAX_CELLS_X - 1); + } + + 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 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 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 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) { + return { closest_x_index(xy.x), closest_y_index(xy.y) }; + } + + /** + * z2 --| + * z0 | | + * | | + (z2-z1) + * z1 | | | + * ---+-------------+--------+-- --| + * a1 a0 a2 + * |<---delta_a---------->| + * + * calc_z0 is the basis for all the Mesh Based correction. It is used to + * find the expected Z Height at a position between two known Z-Height locations. + * + * It is fairly expensive with its 4 floating point additions and 2 floating point + * multiplications. + */ + 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); + } + + #ifdef UBL_Z_RAISE_WHEN_OFF_MESH + #define _UBL_OUTER_Z_RAISE UBL_Z_RAISE_WHEN_OFF_MESH + #else + #define _UBL_OUTER_Z_RAISE NAN + #endif + + /** + * 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 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)) { + if (WITHIN(x1_i, 0, (GRID_MAX_POINTS_X) - 1)) DEBUG_ECHOPGM("yi"); else DEBUG_ECHOPGM("x1_i"); + DEBUG_ECHOLNPGM(" out of bounds in z_correction_for_x_on_horizontal_mesh_line(rx0=", rx0, ",x1_i=", x1_i, ",yi=", yi, ")"); + } + + // The requested location is off the mesh. Return UBL_Z_RAISE_WHEN_OFF_MESH or NAN. + return _UBL_OUTER_Z_RAISE; } - static void smart_fill_mesh(); - #if ENABLED(UBL_DEVEL_DEBUGGING) - static void g29_what_command(); - static void g29_eeprom_dump(); - static void g29_compare_current_mesh_to_stored_mesh(); - #endif + const float xratio = (rx0 - get_mesh_x(x1_i)) * RECIPROCAL(MESH_X_DIST), + z1 = z_values[x1_i][yi]; - public: + return z1 + xratio * (z_values[_MIN(x1_i, (GRID_MAX_POINTS_X) - 2) + 1][yi] - z1); // Don't allow x1_i+1 to be past the end of the array + // If it is, it is clamped to the last element of the + // z_values[][] array and no correction is applied. + } - static void echo_name(); - 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 display_map(const int) _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 value); - static void adjust_mesh_to_mean(const bool cflag, const float value); - static bool sanity_check(); + // + // See comments above for z_correction_for_x_on_horizontal_mesh_line + // + 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)) { - static void G29() _O0; // O0 for no optimization - static void smart_fill_wlsf(const float &) _O2; // O2 gives smaller code than Os on A2560 + if (DEBUGGING(LEVELING)) { + if (WITHIN(xi, 0, (GRID_MAX_POINTS_X) - 1)) DEBUG_ECHOPGM("y1_i"); else DEBUG_ECHOPGM("xi"); + DEBUG_ECHOLNPGM(" out of bounds in z_correction_for_y_on_vertical_mesh_line(ry0=", ry0, ", xi=", xi, ", y1_i=", y1_i, ")"); + } - static int8_t storage_slot; - - static bed_mesh_t z_values; - static const float _mesh_index_to_xpos[GRID_MAX_POINTS_X], - _mesh_index_to_ypos[GRID_MAX_POINTS_Y]; - - #if HAS_LCD_MENU - static bool lcd_map_control; - static void steppers_were_disabled(); - #else - static inline void steppers_were_disabled() {} - #endif - - static volatile int16_t encoder_diff; // Volatile because buttons may changed it at interrupt time - - unified_bed_leveling(); - - 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(const float &x) { - const int8_t cx = (x - (MESH_MIN_X)) * RECIPROCAL(MESH_X_DIST); - return constrain(cx, 0, (GRID_MAX_POINTS_X) - 1); // -1 is appropriate if we want all movement to the X_MAX - } // position. But with this defined this way, it is possible - // to extrapolate off of this point even further out. Probably - // that is OK because something else should be keeping that from - // happening and should not be worried about at this level. - static int8_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_POINTS_Y) - 1); // -1 is appropriate if we want all movement to the Y_MAX - } // position. But with this defined this way, it is possible - // to extrapolate off of this point even further out. Probably - // that is OK because something else should be keeping that from - // happening and should not be worried about at this level. - - static inline xy_int8_t cell_indexes(const float &x, const float &y) { - return { cell_index_x(x), cell_index_y(y) }; + // The requested location is off the mesh. Return UBL_Z_RAISE_WHEN_OFF_MESH or NAN. + return _UBL_OUTER_Z_RAISE; } - static inline xy_int8_t cell_indexes(const xy_pos_t &xy) { return cell_indexes(xy.x, xy.y); } - 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 &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 inline xy_int8_t closest_indexes(const xy_pos_t &xy) { - return { closest_x_index(xy.x), closest_y_index(xy.y) }; - } + const float yratio = (ry0 - get_mesh_y(y1_i)) * RECIPROCAL(MESH_Y_DIST), + z1 = z_values[xi][y1_i]; + + return z1 + yratio * (z_values[xi][_MIN(y1_i, (GRID_MAX_POINTS_Y) - 2) + 1] - z1); // Don't allow y1_i+1 to be past the end of the array + // If it is, it is clamped to the last element of the + // z_values[][] array and no correction is applied. + } + + /** + * This is the generic Z-Correction. It works anywhere within a Mesh Cell. It first + * does a linear interpolation along both of the bounding X-Mesh-Lines to find the + * 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 rx0, const float ry0) { + const int8_t cx = cell_index_x(rx0), cy = cell_index_y(ry0); // return values are clamped /** - * z2 --| - * z0 | | - * | | + (z2-z1) - * z1 | | | - * ---+-------------+--------+-- --| - * a1 a0 a2 - * |<---delta_a---------->| - * - * calc_z0 is the basis for all the Mesh Based correction. It is used to - * find the expected Z Height at a position between two known Z-Height locations. - * - * It is fairly expensive with its 4 floating point additions and 2 floating point - * multiplications. + * Check if the requested location is off the mesh. If so, and + * UBL_Z_RAISE_WHEN_OFF_MESH is specified, that value is returned. */ - 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); - } - - /** - * 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 inline 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)) { - if (WITHIN(x1_i, 0, GRID_MAX_POINTS_X - 1)) DEBUG_ECHOPGM("yi"); else DEBUG_ECHOPGM("x1_i"); - DEBUG_ECHOLNPAIR(" out of bounds in z_correction_for_x_on_horizontal_mesh_line(rx0=", rx0, ",x1_i=", x1_i, ",yi=", yi, ")"); - } - - // The requested location is off the mesh. Return UBL_Z_RAISE_WHEN_OFF_MESH or NAN. - return ( - #ifdef UBL_Z_RAISE_WHEN_OFF_MESH - UBL_Z_RAISE_WHEN_OFF_MESH - #else - NAN - #endif - ); - } - - const float xratio = (rx0 - mesh_index_to_xpos(x1_i)) * RECIPROCAL(MESH_X_DIST), - z1 = z_values[x1_i][yi]; - - return z1 + xratio * (z_values[_MIN(x1_i, GRID_MAX_POINTS_X - 2) + 1][yi] - z1); // Don't allow x1_i+1 to be past the end of the array - // If it is, it is clamped to the last element of the - // z_values[][] array and no correction is applied. - } - - // - // See comments above for z_correction_for_x_on_horizontal_mesh_line - // - static inline 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)) { - if (WITHIN(xi, 0, GRID_MAX_POINTS_X - 1)) DEBUG_ECHOPGM("y1_i"); else DEBUG_ECHOPGM("xi"); - DEBUG_ECHOLNPAIR(" out of bounds in z_correction_for_y_on_vertical_mesh_line(ry0=", ry0, ", xi=", xi, ", y1_i=", y1_i, ")"); - } - - // The requested location is off the mesh. Return UBL_Z_RAISE_WHEN_OFF_MESH or NAN. - return ( - #ifdef UBL_Z_RAISE_WHEN_OFF_MESH - UBL_Z_RAISE_WHEN_OFF_MESH - #else - NAN - #endif - ); - } - - const float yratio = (ry0 - mesh_index_to_ypos(y1_i)) * RECIPROCAL(MESH_Y_DIST), - z1 = z_values[xi][y1_i]; - - return z1 + yratio * (z_values[xi][_MIN(y1_i, GRID_MAX_POINTS_Y - 2) + 1] - z1); // Don't allow y1_i+1 to be past the end of the array - // If it is, it is clamped to the last element of the - // z_values[][] array and no correction is applied. - } - - /** - * This is the generic Z-Correction. It works anywhere within a Mesh Cell. It first - * does a linear interpolation along both of the bounding X-Mesh-Lines to find the - * 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 &rx0, const float &ry0) { - const int8_t cx = cell_index_x(rx0), cy = cell_index_y(ry0); // return values are clamped - - /** - * Check if the requested location is off the mesh. If so, and - * 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)) - return UBL_Z_RAISE_WHEN_OFF_MESH; - #endif - - const float z1 = calc_z0(rx0, - mesh_index_to_xpos(cx), z_values[cx][cy], - mesh_index_to_xpos(cx + 1), z_values[_MIN(cx, GRID_MAX_POINTS_X - 2) + 1][cy]); - - const float z2 = calc_z0(rx0, - mesh_index_to_xpos(cx), z_values[cx][_MIN(cy, GRID_MAX_POINTS_Y - 2) + 1], - mesh_index_to_xpos(cx + 1), z_values[_MIN(cx, GRID_MAX_POINTS_X - 2) + 1][_MIN(cy, GRID_MAX_POINTS_Y - 2) + 1]); - - float z0 = calc_z0(ry0, - mesh_index_to_ypos(cy), z1, - mesh_index_to_ypos(cy + 1), z2); - - if (DEBUGGING(MESH_ADJUST)) { - DEBUG_ECHOPAIR(" raw get_z_correction(", rx0); - DEBUG_CHAR(','); DEBUG_ECHO(ry0); - DEBUG_ECHOPAIR_F(") = ", z0, 6); - DEBUG_ECHOLNPAIR_F(" >>>---> ", z0, 6); - } - - if (isnan(z0)) { // if part of the Mesh is undefined, it will show up as NAN - z0 = 0.0; // in ubl.z_values[][] and propagate through the - // calculations. If our correction is NAN, we throw it out - // because part of the Mesh is undefined and we don't have the - // information we need to complete the height correction. - - if (DEBUGGING(MESH_ADJUST)) { - DEBUG_ECHOPAIR("??? Yikes! NAN in get_z_correction(", rx0); - DEBUG_CHAR(','); - DEBUG_ECHO(ry0); - DEBUG_CHAR(')'); - DEBUG_EOL(); - } - } - return z0; - } - static inline float get_z_correction(const xy_pos_t &pos) { return get_z_correction(pos.x, pos.y); } - - static inline float mesh_index_to_xpos(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 inline float mesh_index_to_ypos(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); - } - - #if UBL_SEGMENTED - 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); + #ifdef UBL_Z_RAISE_WHEN_OFF_MESH + 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 - static inline bool mesh_is_valid() { - GRID_LOOP(x, y) if (isnan(z_values[x][y])) return false; - return true; + const uint8_t mx = _MIN(cx, (GRID_MAX_POINTS_X) - 2) + 1, my = _MIN(cy, (GRID_MAX_POINTS_Y) - 2) + 1; + const float x0 = get_mesh_x(cx), x1 = get_mesh_x(cx + 1), + z1 = calc_z0(rx0, x0, z_values[cx][cy], x1, z_values[mx][cy]), + z2 = calc_z0(rx0, x0, z_values[cx][my], x1, z_values[mx][my]); + float z0 = calc_z0(ry0, get_mesh_y(cy), z1, get_mesh_y(cy + 1), z2); + + if (isnan(z0)) { // If part of the Mesh is undefined, it will show up as NAN + z0 = 0.0; // in z_values[][] and propagate through the calculations. + // If our correction is NAN, we throw it out because part of + // the Mesh is undefined and we don't have the information + // needed to complete the height correction. + + if (DEBUGGING(MESH_ADJUST)) DEBUG_ECHOLNPGM("??? Yikes! NAN in "); } + if (DEBUGGING(MESH_ADJUST)) + DEBUG_ECHOLN(F("get_z_correction("), rx0, F(", "), ry0, F(") => "), p_float_t(z0, 6)); + + return z0; + } + static float get_z_correction(const xy_pos_t &pos) { return get_z_correction(pos.x, pos.y); } + + 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); + } + + #if UBL_SEGMENTED + 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); + #endif + + static bool mesh_is_valid() { + GRID_LOOP(x, y) if (isnan(z_values[x][y])) return false; + return true; + } + }; // class unified_bed_leveling -extern unified_bed_leveling ubl; - -#define _GET_MESH_X(I) ubl.mesh_index_to_xpos(I) -#define _GET_MESH_Y(J) ubl.mesh_index_to_ypos(J) -#define Z_VALUES_ARR ubl.z_values +extern unified_bed_leveling bedlevel; // Prevent debugging propagating to other files #include "../../../core/debug_out.h" diff --git a/Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp b/Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp index e41af9f3cd..6341933bfb 100644 --- a/Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp +++ b/Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp @@ -24,1227 +24,1364 @@ #if ENABLED(AUTO_BED_LEVELING_UBL) - #include "../bedlevel.h" +#include "../bedlevel.h" - #include "../../../MarlinCore.h" - #include "../../../HAL/shared/eeprom_api.h" - #include "../../../libs/hex_print.h" - #include "../../../module/settings.h" - #include "../../../lcd/ultralcd.h" - #include "../../../module/stepper.h" - #include "../../../module/planner.h" - #include "../../../module/motion.h" - #include "../../../module/probe.h" - #include "../../../gcode/gcode.h" - #include "../../../libs/least_squares_fit.h" +#include "../../../HAL/shared/eeprom_api.h" +#include "../../../libs/hex_print.h" +#include "../../../module/settings.h" +#include "../../../lcd/marlinui.h" +#include "../../../module/planner.h" +#include "../../../module/motion.h" +#include "../../../module/probe.h" +#include "../../../gcode/gcode.h" +#include "../../../libs/least_squares_fit.h" - #if HAS_MULTI_HOTEND - #include "../../../module/tool_change.h" - #endif +#define DEBUG_OUT ENABLED(DEBUG_LEVELING_FEATURE) +#include "../../../core/debug_out.h" - #define DEBUG_OUT ENABLED(DEBUG_LEVELING_FEATURE) - #include "../../../core/debug_out.h" +#if ENABLED(EXTENSIBLE_UI) + #include "../../../lcd/extui/ui_api.h" +#endif - #if ENABLED(EXTENSIBLE_UI) - #include "../../../lcd/extui/ui_api.h" - #endif +#if ENABLED(UBL_HILBERT_CURVE) + #include "../hilbert_curve.h" +#endif - #include +#if ENABLED(FT_MOTION) + #include "../../../module/ft_motion.h" +#endif - #define UBL_G29_P31 +#include - #if HAS_LCD_MENU +#define UBL_G29_P31 - bool unified_bed_leveling::lcd_map_control = false; +#if HAS_MARLINUI_MENU - void unified_bed_leveling::steppers_were_disabled() { - if (lcd_map_control) { - lcd_map_control = false; - ui.defer_status_screen(false); - } + bool unified_bed_leveling::lcd_map_control = false; + + void unified_bed_leveling::steppers_were_disabled() { + if (lcd_map_control) { + lcd_map_control = false; + ui.defer_status_screen(false); } - - void ubl_map_screen(); - - #endif - - #define SIZE_OF_LITTLE_RAISE 1 - #define BIG_RAISE_NOT_NEEDED 0 - - int unified_bed_leveling::g29_verbose_level, - unified_bed_leveling::g29_phase_value, - unified_bed_leveling::g29_repetition_cnt, - unified_bed_leveling::g29_storage_slot = 0, - unified_bed_leveling::g29_map_type; - bool unified_bed_leveling::g29_c_flag; - float unified_bed_leveling::g29_card_thickness = 0, - unified_bed_leveling::g29_constant = 0; - xy_bool_t unified_bed_leveling::xy_seen; - xy_pos_t unified_bed_leveling::g29_pos; - - #if HAS_BED_PROBE - int unified_bed_leveling::g29_grid_size; - #endif - - /** - * G29: Unified Bed Leveling by Roxy - * - * Parameters understood by this leveling system: - * - * A Activate Activate the Unified Bed Leveling system. - * - * B # Business Use the 'Business Card' mode of the Manual Probe subsystem with P2. - * Note: A non-compressible Spark Gap feeler gauge is recommended over a business card. - * In this mode of G29 P2, a business or index card is used as a shim that the nozzle can - * grab onto as it is lowered. In principle, the nozzle-bed distance is the same when the - * same resistance is felt in the shim. You can omit the numerical value on first invocation - * of G29 P2 B to measure shim thickness. Subsequent use of 'B' will apply the previously- - * measured thickness by default. - * - * C Continue G29 P1 C continues the generation of a partially-constructed Mesh without invalidating - * previous measurements. - * - * C G29 P2 C tells the Manual Probe subsystem to not use the current nozzle - * location in its search for the closest unmeasured Mesh Point. Instead, attempt to - * start at one end of the uprobed points and Continue sequentially. - * - * G29 P3 C specifies the Constant for the fill. Otherwise, uses a "reasonable" value. - * - * C Current G29 Z C uses the Current location (instead of bed center or nearest edge). - * - * D Disable Disable the Unified Bed Leveling system. - * - * E Stow_probe Stow the probe after each sampled point. - * - * F # Fade Fade the amount of Mesh Based Compensation over a specified height. At the - * specified height, no correction is applied and natural printer kenimatics take over. If no - * number is specified for the command, 10mm is assumed to be reasonable. - * - * H # Height With P2, 'H' specifies the Height to raise the nozzle after each manual probe of the bed. - * If omitted, the nozzle will raise by Z_CLEARANCE_BETWEEN_PROBES. - * - * H # Offset With P4, 'H' specifies the Offset above the mesh height to place the nozzle. - * If omitted, Z_CLEARANCE_BETWEEN_PROBES will be used. - * - * I # Invalidate Invalidate the specified number of Mesh Points near the given 'X' 'Y'. If X or Y are omitted, - * the nozzle location is used. If no 'I' value is given, only the point nearest to the location - * is invalidated. Use 'T' to produce a map afterward. This command is useful to invalidate a - * portion of the Mesh so it can be adjusted using other UBL tools. When attempting to invalidate - * an isolated bad mesh point, the 'T' option shows the nozzle position in the Mesh with (#). You - * can move the nozzle around and use this feature to select the center of the area (or cell) to - * invalidate. - * - * J # Grid Perform a Grid Based Leveling of the current Mesh using a grid with n points on a side. - * Not specifying a grid size will invoke the 3-Point leveling function. - * - * L Load Load Mesh from the previously activated location in the EEPROM. - * - * L # Load Load Mesh from the specified location in the EEPROM. Set this location as activated - * for subsequent Load and Store operations. - * - * The P or Phase commands are used for the bulk of the work to setup a Mesh. In general, your Mesh will - * start off being initialized with a G29 P0 or a G29 P1. Further refinement of the Mesh happens with - * each additional Phase that processes it. - * - * P0 Phase 0 Zero Mesh Data and turn off the Mesh Compensation System. This reverts the - * 3D Printer to the same state it was in before the Unified Bed Leveling Compensation - * was turned on. Setting the entire Mesh to Zero is a special case that allows - * a subsequent G or T leveling operation for backward compatibility. - * - * P1 Phase 1 Invalidate entire Mesh and continue with automatic generation of the Mesh data using - * the Z-Probe. Usually the probe can't reach all areas that the nozzle can reach. For delta - * printers only the areas where the probe and nozzle can both reach will be automatically probed. - * - * Unreachable points will be handled in Phase 2 and Phase 3. - * - * Use 'C' to leave the previous mesh intact and automatically probe needed points. This allows you - * to invalidate parts of the Mesh but still use Automatic Probing. - * - * The 'X' and 'Y' parameters prioritize where to try and measure points. If omitted, the current - * probe position is used. - * - * Use 'T' (Topology) to generate a report of mesh generation. - * - * P1 will suspend Mesh generation if the controller button is held down. Note that you may need - * to press and hold the switch for several seconds if moves are underway. - * - * P2 Phase 2 Probe unreachable points. - * - * Use 'H' to set the height between Mesh points. If omitted, Z_CLEARANCE_BETWEEN_PROBES is used. - * Smaller values will be quicker. Move the nozzle down till it barely touches the bed. Make sure the - * nozzle is clean and unobstructed. Use caution and move slowly. This can damage your printer! - * (Uses SIZE_OF_LITTLE_RAISE mm if the nozzle is moving less than BIG_RAISE_NOT_NEEDED mm.) - * - * The 'H' value can be negative if the Mesh dips in a large area. Press and hold the - * controller button to terminate the current Phase 2 command. You can then re-issue "G29 P 2" - * with an 'H' parameter more suitable for the area you're manually probing. Note that the command - * tries to start in a corner of the bed where movement will be predictable. Override the distance - * calculation location with the X and Y parameters. You can print a Mesh Map (G29 T) to see where - * the mesh is invalidated and where the nozzle needs to move to complete the command. Use 'C' to - * indicate that the search should be based on the current position. - * - * The 'B' parameter for this command is described above. It places the manual probe subsystem into - * Business Card mode where the thickness of a business card is measured and then used to accurately - * set the nozzle height in all manual probing for the duration of the command. A Business card can - * be used, but you'll get better results with a flexible Shim that doesn't compress. This makes it - * easier to produce similar amounts of force and get more accurate measurements. Google if you're - * not sure how to use a shim. - * - * The 'T' (Map) parameter helps track Mesh building progress. - * - * NOTE: P2 requires an LCD controller! - * - * P3 Phase 3 Fill the unpopulated regions of the Mesh with a fixed value. There are two different paths to - * go down: - * - * - If a 'C' constant is specified, the closest invalid mesh points to the nozzle will be filled, - * and a repeat count can then also be specified with 'R'. - * - * - Leaving out 'C' invokes Smart Fill, which scans the mesh from the edges inward looking for - * invalid mesh points. Adjacent points are used to determine the bed slope. If the bed is sloped - * upward from the invalid point, it takes the value of the nearest point. If sloped downward, it's - * replaced by a value that puts all three points in a line. This version of G29 P3 is a quick, easy - * and (usually) safe way to populate unprobed mesh regions before continuing to G26 Mesh Validation - * Pattern. Note that this populates the mesh with unverified values. Pay attention and use caution. - * - * P4 Phase 4 Fine tune the Mesh. The Delta Mesh Compensation System assumes the existence of - * an LCD Panel. It is possible to fine tune the mesh without an LCD Panel using - * G42 and M421. See the UBL documentation for further details. - * - * Phase 4 is meant to be used with G26 Mesh Validation to fine tune the mesh by direct editing - * of Mesh Points. Raise and lower points to fine tune the mesh until it gives consistently reliable - * adhesion. - * - * P4 moves to the closest Mesh Point (and/or the given X Y), raises the nozzle above the mesh height - * by the given 'H' offset (or default 0), and waits while the controller is used to adjust the nozzle - * height. On click the displayed height is saved in the mesh. - * - * Start Phase 4 at a specific location with X and Y. Adjust a specific number of Mesh Points with - * the 'R' (Repeat) parameter. (If 'R' is left out, the whole matrix is assumed.) This command can be - * terminated early (e.g., after editing the area of interest) by pressing and holding the encoder button. - * - * The general form is G29 P4 [R points] [X position] [Y position] - * - * The H [offset] parameter is useful if a shim is used to fine-tune the mesh. For a 0.4mm shim the - * command would be G29 P4 H0.4. The nozzle is moved to the shim height, you adjust height to the shim, - * and on click the height minus the shim thickness will be saved in the mesh. - * - * !!Use with caution, as a very poor mesh could cause the nozzle to crash into the bed!! - * - * NOTE: P4 is not available unless you have LCD support enabled! - * - * P5 Phase 5 Find Mean Mesh Height and Standard Deviation. Typically, it is easier to use and - * work with the Mesh if it is Mean Adjusted. You can specify a C parameter to - * Correct the Mesh to a 0.00 Mean Height. Adding a C parameter will automatically - * execute a G29 P6 C . - * - * P6 Phase 6 Shift Mesh height. The entire Mesh's height is adjusted by the height specified - * with the C parameter. Being able to adjust the height of a Mesh is useful tool. It - * can be used to compensate for poorly calibrated Z-Probes and other errors. Ideally, - * you should have the Mesh adjusted for a Mean Height of 0.00 and the Z-Probe measuring - * 0.000 at the Z Home location. - * - * Q Test Load specified Test Pattern to assist in checking correct operation of system. This - * command is not anticipated to be of much value to the typical user. It is intended - * for developers to help them verify correct operation of the Unified Bed Leveling System. - * - * R # Repeat Repeat this command the specified number of times. If no number is specified the - * command will be repeated GRID_MAX_POINTS_X * GRID_MAX_POINTS_Y times. - * - * S Store Store the current Mesh in the Activated area of the EEPROM. It will also store the - * 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. - * - * S -1 Store Print the current Mesh as G-code that can be used to restore the mesh anytime. - * - * T Topology Display the Mesh Map Topology. - * 'T' can be used alone (e.g., G29 T) or in combination with most of the other commands. - * This option works with all Phase commands (e.g., G29 P4 R 5 T X 50 Y100 C -.1 O) - * This parameter can also specify a Map Type. T0 (the default) is user-readable. T1 - * is suitable to paste into a spreadsheet for a 3D graph of the mesh. - * - * U Unlevel Perform a probe of the outer perimeter to assist in physically leveling unlevel beds. - * Only used for G29 P1 T U. This speeds up the probing of the edge of the bed. Useful - * when the entire bed doesn't need to be probed because it will be adjusted. - * - * V # Verbosity Set the verbosity level (0-4) for extra details. (Default 0) - * - * X # X Location for this command - * - * Y # Y Location for this command - * - * With UBL_DEVEL_DEBUGGING: - * - * K # Kompare Kompare current Mesh with stored Mesh #, replacing current Mesh with the result. - * This command literally performs a diff between two Meshes. - * - * Q-1 Dump EEPROM Dump the UBL contents stored in EEPROM as HEX format. Useful for developers to help - * verify correct operation of the UBL. - * - * W What? Display valuable UBL data. - * - * - * Release Notes: - * You MUST do M502, M500 to initialize the storage. Failure to do this will cause all - * kinds of problems. Enabling EEPROM Storage is required. - * - * When you do a G28 and G29 P1 to automatically build your first mesh, you are going to notice that - * UBL probes points increasingly further from the starting location. (The starting location defaults - * to the center of the bed.) In contrast, ABL and MBL follow a zigzag pattern. The spiral pattern is - * especially better for Delta printers, since it populates the center of the mesh first, allowing for - * a quicker test print to verify settings. You don't need to populate the entire mesh to use it. - * After all, you don't want to spend a lot of time generating a mesh only to realize the resolution - * or probe offsets are incorrect. Mesh-generation gathers points starting closest to the nozzle unless - * an (X,Y) coordinate pair is given. - * - * Unified Bed Leveling uses a lot of EEPROM storage to hold its data, and it takes some effort to get - * the mesh just right. To prevent this valuable data from being destroyed as the EEPROM structure - * evolves, UBL stores all mesh data at the end of EEPROM. - * - * UBL is founded on Edward Patel's Mesh Bed Leveling code. A big 'Thanks!' to him and the creators of - * 3-Point and Grid Based leveling. Combining their contributions we now have the functionality and - * features of all three systems combined. - */ - - void unified_bed_leveling::G29() { - - bool probe_deployed = false; - if (g29_parameter_parsing()) return; // Abort on parameter error - - const int8_t p_val = parser.intval('P', -1); - const bool may_move = p_val == 1 || p_val == 2 || p_val == 4 || parser.seen('J'); - TERN_(HAS_MULTI_HOTEND, const uint8_t old_tool_index = active_extruder); - - // Check for commands that require the printer to be homed - if (may_move) { - planner.synchronize(); - if (axes_should_home()) gcode.home_all_axes(); - TERN_(HAS_MULTI_HOTEND, if (active_extruder) tool_change(0)); - } - - // Invalidate Mesh Points. This command is a little bit asymmetrical because - // it directly specifies the repetition count and does not use the 'R' parameter. - if (parser.seen('I')) { - uint8_t cnt = 0; - g29_repetition_cnt = parser.has_value() ? parser.value_int() : 1; - if (g29_repetition_cnt >= GRID_MAX_POINTS) { - set_all_mesh_points_to_value(NAN); - } - else { - while (g29_repetition_cnt--) { - if (cnt > 20) { cnt = 0; idle(); } - const mesh_index_pair closest = find_closest_mesh_point_of_type(REAL, g29_pos); - const xy_int8_t &cpos = closest.pos; - if (cpos.x < 0) { - // No more REAL mesh points to invalidate, so we ASSUME the user - // meant to invalidate the ENTIRE mesh, which cannot be done with - // find_closest_mesh_point loop which only returns REAL points. - set_all_mesh_points_to_value(NAN); - SERIAL_ECHOLNPGM("Entire Mesh invalidated.\n"); - break; // No more invalid Mesh Points to populate - } - z_values[cpos.x][cpos.y] = NAN; - TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(cpos, 0.0f)); - cnt++; - } - } - SERIAL_ECHOLNPGM("Locations invalidated.\n"); - } - - if (parser.seen('Q')) { - const int test_pattern = parser.has_value() ? parser.value_int() : -99; - if (!WITHIN(test_pattern, -1, 2)) { - SERIAL_ECHOLNPGM("Invalid test_pattern value. (-1 to 2)\n"); - return; - } - SERIAL_ECHOLNPGM("Loading test_pattern values.\n"); - switch (test_pattern) { - - #if ENABLED(UBL_DEVEL_DEBUGGING) - case -1: - g29_eeprom_dump(); - break; - #endif - - 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: - LOOP_L_N(x, GRID_MAX_POINTS_X) { // Create a diagonal line several Mesh cells thick that is raised - z_values[x][x] += 9.999f; - z_values[x][x + (x < (GRID_MAX_POINTS_Y) - 1) ? 1 : -1] += 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, (x + (x < (GRID_MAX_POINTS_Y) - 1) ? 1 : -1), z_values[x][x + (x < (GRID_MAX_POINTS_Y) - 1) ? 1 : -1]); - #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('C') ? g29_constant : 9.99f; - TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(x, y, z_values[x][y])); - } - break; - } - } - - #if HAS_BED_PROBE - - if (parser.seen('J')) { - save_ubl_active_state_and_disable(); - tilt_mesh_based_on_probed_grid(g29_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('P')) { - if (WITHIN(g29_phase_value, 0, 1) && storage_slot == -1) { - storage_slot = 0; - SERIAL_ECHOLNPGM("Default storage slot 0 selected."); - } - - switch (g29_phase_value) { - 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('C')) { - invalidate(); - SERIAL_ECHOLNPGM("Mesh invalidated. Probing mesh."); - } - if (g29_verbose_level > 1) { - SERIAL_ECHOPAIR("Probing around (", g29_pos.x); - SERIAL_CHAR(','); - SERIAL_DECIMAL(g29_pos.y); - SERIAL_ECHOLNPGM(").\n"); - } - const xy_pos_t near_probe_xy = g29_pos + probe.offset_xy; - probe_entire_mesh(near_probe_xy, parser.seen('T'), parser.seen('E'), parser.seen('U')); - - report_current_position(); - probe_deployed = true; - } break; - - #endif // HAS_BED_PROBE - - case 2: { - #if HAS_LCD_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('C') && !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. - */ - g29_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')) { - g29_card_thickness = parser.has_value() ? parser.value_float() : measure_business_card_thickness(float(Z_CLEARANCE_BETWEEN_PROBES)); - if (ABS(g29_card_thickness) > 1.5f) { - SERIAL_ECHOLNPGM("?Error in Business Card measurement."); - return; - } - probe_deployed = true; - } - - if (!position_is_reachable(g29_pos)) { - SERIAL_ECHOLNPGM("XY outside printable radius."); - return; - } - - const float height = parser.floatval('H', Z_CLEARANCE_BETWEEN_PROBES); - manually_probe_remaining_mesh(g29_pos, height, g29_card_thickness, parser.seen('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 (g29_c_flag) { - if (g29_repetition_cnt >= GRID_MAX_POINTS) { - set_all_mesh_points_to_value(g29_constant); - } - else { - while (g29_repetition_cnt--) { // this only populates reachable mesh points near - const mesh_index_pair closest = find_closest_mesh_point_of_type(INVALID, g29_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] = g29_constant; - break; // No more invalid Mesh Points to populate - } - else { - z_values[cpos.x][cpos.y] = g29_constant; - TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(cpos, g29_constant)); - } - } - } - } - else { - const float cvf = parser.value_float(); - switch ((int)truncf(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_LCD_MENU - fine_tune_mesh(g29_pos, parser.seen('T')); - #else - SERIAL_ECHOLNPGM("?P4 is only available when an LCD is present."); - return; - #endif - break; - - case 5: adjust_mesh_to_mean(g29_c_flag, g29_constant); break; - - case 6: shift_mesh_height(); break; - } - } - - #if ENABLED(UBL_DEVEL_DEBUGGING) - - // - // Much of the 'What?' command can be eliminated. But until we are fully debugged, it is - // good to have the extra information. Soon... we prune this to just a few items - // - if (parser.seen('W')) g29_what_command(); - - // - // 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. - // - - if (parser.seen('K')) // Kompare Current Mesh Data to Specified Stored Mesh - g29_compare_current_mesh_to_stored_mesh(); - - #endif // UBL_DEVEL_DEBUGGING - - - // - // Load a Mesh from the EEPROM - // - - if (parser.seen('L')) { // Load Current Mesh Data - g29_storage_slot = parser.has_value() ? parser.value_int() : storage_slot; - - int16_t a = settings.calc_num_meshes(); - - if (!a) { - SERIAL_ECHOLNPGM("?EEPROM storage not available."); - return; - } - - if (!WITHIN(g29_storage_slot, 0, a - 1)) { - SERIAL_ECHOLNPAIR("?Invalid storage slot.\n?Use 0 to ", a - 1); - return; - } - - settings.load_mesh(g29_storage_slot); - storage_slot = g29_storage_slot; - - SERIAL_ECHOLNPGM("Done."); - } - - // - // Store a Mesh in the EEPROM - // - - if (parser.seen('S')) { // Store (or Save) Current Mesh Data - g29_storage_slot = parser.has_value() ? parser.value_int() : storage_slot; - - if (g29_storage_slot == -1) // Special case, the user wants to 'Export' the mesh to the - return report_current_mesh(); // host program to be saved on the user's computer - - int16_t a = settings.calc_num_meshes(); - - if (!a) { - SERIAL_ECHOLNPGM("?EEPROM storage not available."); - goto LEAVE; - } - - if (!WITHIN(g29_storage_slot, 0, a - 1)) { - SERIAL_ECHOLNPAIR("?Invalid storage slot.\n?Use 0 to ", a - 1); - goto LEAVE; - } - - settings.store_mesh(g29_storage_slot); - storage_slot = g29_storage_slot; - - SERIAL_ECHOLNPGM("Done."); - } - - if (parser.seen('T')) - display_map(g29_map_type); - - LEAVE: - - #if HAS_LCD_MENU - ui.reset_alert_level(); - ui.quick_feedback(); - ui.reset_status(); - ui.release(); - #endif - - #ifdef Z_PROBE_END_SCRIPT - if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPAIR("Z Probe End Script: ", Z_PROBE_END_SCRIPT); - if (probe_deployed) { - planner.synchronize(); - gcode.process_subcommands_now_P(PSTR(Z_PROBE_END_SCRIPT)); - } - #else - UNUSED(probe_deployed); - #endif - - TERN_(HAS_MULTI_HOTEND, tool_change(old_tool_index)); - return; } - void unified_bed_leveling::adjust_mesh_to_mean(const bool cflag, const float value) { - float sum = 0; - int n = 0; - GRID_LOOP(x, y) - if (!isnan(z_values[x][y])) { - sum += z_values[x][y]; - n++; + void ubl_map_screen(); + +#endif + +#define SIZE_OF_LITTLE_RAISE 1 +#define BIG_RAISE_NOT_NEEDED 0 + +/** + * G29: Unified Bed Leveling by Roxy + * + * Parameters understood by this leveling system: + * + * A Activate Activate the Unified Bed Leveling system. + * + * B # Business Use the 'Business Card' mode of the Manual Probe subsystem with P2. + * Note: A non-compressible Spark Gap feeler gauge is recommended over a business card. + * In this mode of G29 P2, a business or index card is used as a shim that the nozzle can + * grab onto as it is lowered. In principle, the nozzle-bed distance is the same when the + * same resistance is felt in the shim. You can omit the numerical value on first invocation + * of G29 P2 B to measure shim thickness. Subsequent use of 'B' will apply the previously- + * measured thickness by default. + * + * C Continue G29 P1 C continues the generation of a partially-constructed Mesh without invalidating + * previous measurements. + * + * C G29 P2 C tells the Manual Probe subsystem to not use the current nozzle + * location in its search for the closest unmeasured Mesh Point. Instead, attempt to + * start at one end of the uprobed points and Continue sequentially. + * + * G29 P3 C specifies the Constant for the fill. Otherwise, uses a "reasonable" value. + * + * C Current G29 Z C uses the Current location (instead of bed center or nearest edge). + * + * D Disable Disable the Unified Bed Leveling system. + * + * E Stow_probe Stow the probe after each sampled point. + * + * F # Fade Fade the amount of Mesh Based Compensation over a specified height. At the + * specified height, no correction is applied and natural printer kenimatics take over. If no + * number is specified for the command, 10mm is assumed to be reasonable. + * + * H # Height With P2, 'H' specifies the Height to raise the nozzle after each manual probe of the bed. + * If omitted, the nozzle will raise by Z_CLEARANCE_BETWEEN_PROBES. + * + * H # Offset With P4, 'H' specifies the Offset above the mesh height to place the nozzle. + * If omitted, Z_TWEEN_SAFE_CLEARANCE will be used. + * + * I # Invalidate Invalidate the specified number of Mesh Points near the given 'X' 'Y'. If X or Y are omitted, + * the nozzle location is used. If no 'I' value is given, only the point nearest to the location + * is invalidated. Use 'T' to produce a map afterward. This command is useful to invalidate a + * portion of the Mesh so it can be adjusted using other UBL tools. When attempting to invalidate + * an isolated bad mesh point, the 'T' option shows the nozzle position in the Mesh with (#). You + * can move the nozzle around and use this feature to select the center of the area (or cell) to + * invalidate. + * + * J # Grid Perform a Grid Based Leveling of the current Mesh using a grid with n points on a side. + * Not specifying a grid size will invoke the 3-Point leveling function. + * + * L Load Load Mesh from the previously activated location in the EEPROM. + * + * L # Load Load Mesh from the specified location in the EEPROM. Set this location as activated + * for subsequent Load and Store operations. + * + * The P or Phase commands are used for the bulk of the work to setup a Mesh. In general, your Mesh will + * start off being initialized with a G29 P0 or a G29 P1. Further refinement of the Mesh happens with + * each additional Phase that processes it. + * + * P0 Phase 0 Zero Mesh Data and turn off the Mesh Compensation System. This reverts the + * 3D Printer to the same state it was in before the Unified Bed Leveling Compensation + * was turned on. Setting the entire Mesh to Zero is a special case that allows + * a subsequent G or T leveling operation for backward compatibility. + * + * P1 Phase 1 Invalidate entire Mesh and continue with automatic generation of the Mesh data using + * the Z-Probe. Usually the probe can't reach all areas that the nozzle can reach. For delta + * printers only the areas where the probe and nozzle can both reach will be automatically probed. + * + * Unreachable points will be handled in Phase 2 and Phase 3. + * + * Use 'C' to leave the previous mesh intact and automatically probe needed points. This allows you + * to invalidate parts of the Mesh but still use Automatic Probing. + * + * The 'X' and 'Y' parameters prioritize where to try and measure points. If omitted, the current + * probe position is used. + * + * Use 'T' (Topology) to generate a report of mesh generation. + * + * P1 will suspend Mesh generation if the controller button is held down. Note that you may need + * to press and hold the switch for several seconds if moves are underway. + * + * P2 Phase 2 Probe unreachable points. + * + * Use 'H' to set the height between Mesh points. If omitted, Z_CLEARANCE_BETWEEN_PROBES is used. + * Smaller values will be quicker. Move the nozzle down till it barely touches the bed. Make sure the + * nozzle is clean and unobstructed. Use caution and move slowly. This can damage your printer! + * (Uses SIZE_OF_LITTLE_RAISE mm if the nozzle is moving less than BIG_RAISE_NOT_NEEDED mm.) + * + * The 'H' value can be negative if the Mesh dips in a large area. Press and hold the + * controller button to terminate the current Phase 2 command. You can then re-issue "G29 P 2" + * with an 'H' parameter more suitable for the area you're manually probing. Note that the command + * tries to start in a corner of the bed where movement will be predictable. Override the distance + * calculation location with the X and Y parameters. You can print a Mesh Map (G29 T) to see where + * the mesh is invalidated and where the nozzle needs to move to complete the command. Use 'C' to + * indicate that the search should be based on the current position. + * + * The 'B' parameter for this command is described above. It places the manual probe subsystem into + * Business Card mode where the thickness of a business card is measured and then used to accurately + * set the nozzle height in all manual probing for the duration of the command. A Business card can + * be used, but you'll get better results with a flexible Shim that doesn't compress. This makes it + * easier to produce similar amounts of force and get more accurate measurements. Google if you're + * not sure how to use a shim. + * + * The 'T' (Map) parameter helps track Mesh building progress. + * + * NOTE: P2 requires an LCD controller! + * + * P3 Phase 3 Fill the unpopulated regions of the Mesh with a fixed value. There are two different paths to + * go down: + * + * - If a 'C' constant is specified, the closest invalid mesh points to the nozzle will be filled, + * and a repeat count can then also be specified with 'R'. + * + * - Leaving out 'C' invokes Smart Fill, which scans the mesh from the edges inward looking for + * invalid mesh points. Adjacent points are used to determine the bed slope. If the bed is sloped + * upward from the invalid point, it takes the value of the nearest point. If sloped downward, it's + * replaced by a value that puts all three points in a line. This version of G29 P3 is a quick, easy + * and (usually) safe way to populate unprobed mesh regions before continuing to G26 Mesh Validation + * Pattern. Note that this populates the mesh with unverified values. Pay attention and use caution. + * + * P4 Phase 4 Fine tune the Mesh. The Delta Mesh Compensation System assumes the existence of + * an LCD Panel. It is possible to fine tune the mesh without an LCD Panel using + * G42 and M421. See the UBL documentation for further details. + * + * Phase 4 is meant to be used with G26 Mesh Validation to fine tune the mesh by direct editing + * of Mesh Points. Raise and lower points to fine tune the mesh until it gives consistently reliable + * adhesion. + * + * P4 moves to the closest Mesh Point (and/or the given X Y), raises the nozzle above the mesh height + * by the given 'H' offset (or default 0), and waits while the controller is used to adjust the nozzle + * height. On click the displayed height is saved in the mesh. + * + * Start Phase 4 at a specific location with X and Y. Adjust a specific number of Mesh Points with + * the 'R' (Repeat) parameter. (If 'R' is left out, the whole matrix is assumed.) This command can be + * terminated early (e.g., after editing the area of interest) by pressing and holding the encoder button. + * + * The general form is G29 P4 [R points] [X position] [Y position] + * + * The H [offset] parameter is useful if a shim is used to fine-tune the mesh. For a 0.4mm shim the + * command would be G29 P4 H0.4. The nozzle is moved to the shim height, you adjust height to the shim, + * and on click the height minus the shim thickness will be saved in the mesh. + * + * !!Use with caution, as a very poor mesh could cause the nozzle to crash into the bed!! + * + * NOTE: P4 is not available unless you have LCD support enabled! + * + * P5 Phase 5 Find Mean Mesh Height and Standard Deviation. Typically, it is easier to use and + * work with the Mesh if it is Mean Adjusted. You can specify a C parameter to + * Correct the Mesh to a 0.00 Mean Height. Adding a C parameter will automatically + * execute a G29 P6 C . + * + * P6 Phase 6 Shift Mesh height. The entire Mesh's height is adjusted by the height specified + * with the C parameter. Being able to adjust the height of a Mesh is useful tool. It + * can be used to compensate for poorly calibrated Z-Probes and other errors. Ideally, + * you should have the Mesh adjusted for a Mean Height of 0.00 and the Z-Probe measuring + * 0.000 at the Z Home location. + * + * Q Test Load specified Test Pattern to assist in checking correct operation of system. This + * command is not anticipated to be of much value to the typical user. It is intended + * for developers to help them verify correct operation of the Unified Bed Leveling System. + * + * R # Repeat Repeat this command the specified number of times. If no number is specified the + * command will be repeated GRID_MAX_POINTS_X * GRID_MAX_POINTS_Y times. + * + * S Store Store the current Mesh in the Activated area of the EEPROM. It will also store the + * 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. + * + * S -1 Store Print the current Mesh as G-code that can be used to restore the mesh anytime. + * + * T Topology Display the Mesh Map Topology. + * 'T' can be used alone (e.g., G29 T) or in combination with most of the other commands. + * This option works with all Phase commands (e.g., G29 P4 R 5 T X 50 Y100 C -.1 O) + * This parameter can also specify a Map Type. T0 (the default) is user-readable. T1 + * is suitable to paste into a spreadsheet for a 3D graph of the mesh. + * + * U Unlevel Perform a probe of the outer perimeter to assist in physically leveling unlevel beds. + * Only used for G29 P1 T U. This speeds up the probing of the edge of the bed. Useful + * when the entire bed doesn't need to be probed because it will be adjusted. + * + * V # Verbosity Set the verbosity level (0-4) for extra details. (Default 0) + * + * X # X Location for this command + * + * Y # Y Location for this command + * + * With UBL_DEVEL_DEBUGGING: + * + * K # Kompare Kompare current Mesh with stored Mesh #, replacing current Mesh with the result. + * This command literally performs a diff between two Meshes. + * + * Q-1 Dump EEPROM Dump the UBL contents stored in EEPROM as HEX format. Useful for developers to help + * verify correct operation of the UBL. + * + * W What? Display valuable UBL data. + * + * + * Release Notes: + * You MUST do M502, M500 to initialize the storage. Failure to do this will cause all + * kinds of problems. Enabling EEPROM Storage is required. + * + * When you do a G28 and G29 P1 to automatically build your first mesh, you are going to notice that + * UBL probes points increasingly further from the starting location. (The starting location defaults + * to the center of the bed.) In contrast, ABL and MBL follow a zigzag pattern. The spiral pattern is + * especially better for Delta printers, since it populates the center of the mesh first, allowing for + * a quicker test print to verify settings. You don't need to populate the entire mesh to use it. + * After all, you don't want to spend a lot of time generating a mesh only to realize the resolution + * or probe offsets are incorrect. Mesh-generation gathers points starting closest to the nozzle unless + * an (X,Y) coordinate pair is given. + * + * Unified Bed Leveling uses a lot of EEPROM storage to hold its data, and it takes some effort to get + * the mesh just right. To prevent this valuable data from being destroyed as the EEPROM structure + * evolves, UBL stores all mesh data at the end of EEPROM. + * + * UBL is founded on Edward Patel's Mesh Bed Leveling code. A big 'Thanks!' to him and the creators of + * 3-Point and Grid Based leveling. Combining their contributions we now have the functionality and + * features of all three systems combined. + */ + +G29_parameters_t unified_bed_leveling::param; + +void unified_bed_leveling::G29() { + + #ifdef EVENT_GCODE_AFTER_G29 + bool probe_deployed = false; + #define SET_PROBE_DEPLOYED(N) probe_deployed = N + #else + #define SET_PROBE_DEPLOYED(N) + #endif + + 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'); + + // Potentially disable Fixed-Time Motion for probing + TERN_(FT_MOTION, FTM_DISABLE_IN_SCOPE()); + + // 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(false); // ...without telling ExtUI "done" + #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(); + + #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 = 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 + + do_blocking_move_to(safe_position); + #endif // HAS_SAFE_BED_LEVELING + } + + // 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) 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"); + } - const float mean = sum / 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_ECHOLN(F("?Invalid "), F("(Q) test pattern. (" TERN(UBL_DEVEL_DEBUGGING, "-1", "0") " to 2)\n")); + return; + } + SERIAL_ECHOLNPGM("Applying test pattern.\n"); + switch (test_pattern) { - // - // Sum the squares of difference from mean - // - float sum_of_diff_squared = 0; - GRID_LOOP(x, y) - if (!isnan(z_values[x][y])) - sum_of_diff_squared += sq(z_values[x][y] - mean); + default: + case -1: TERN_(UBL_DEVEL_DEBUGGING, g29_eeprom_dump()); break; - SERIAL_ECHOLNPAIR("# of samples: ", n); - SERIAL_ECHOLNPAIR_F("Mean Mesh Height: ", mean, 6); - - const float sigma = SQRT(sum_of_diff_squared / (n + 1)); - SERIAL_ECHOLNPAIR_F("Standard Deviation: ", sigma, 6); - - if (cflag) - GRID_LOOP(x, y) - if (!isnan(z_values[x][y])) { - z_values[x][y] -= mean + value; + 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; - void unified_bed_leveling::shift_mesh_height() { - GRID_LOOP(x, y) - if (!isnan(z_values[x][y])) { - z_values[x][y] += g29_constant; - TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(x, y, z_values[x][y])); - } + 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; + } } #if HAS_BED_PROBE - /** - * Probe all invalidated locations of the mesh that can be reached by the probe. - * This attempts to fill in locations closest to the nozzle's start location first. - */ - void unified_bed_leveling::probe_entire_mesh(const xy_pos_t &near, const bool do_ubl_mesh_map, const bool stow_probe, const bool do_furthest) { - probe.deploy(); // Deploy before ui.capture() to allow for PAUSE_BEFORE_DEPLOY_STOW - TERN_(HAS_LCD_MENU, ui.capture()); - - save_ubl_active_state_and_disable(); // No bed level correction so only raw data is obtained - uint8_t count = GRID_MAX_POINTS; - - mesh_index_pair best; - TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(best.pos, ExtUI::MESH_START)); - do { - if (do_ubl_mesh_map) display_map(g29_map_type); - - const int point_num = (GRID_MAX_POINTS) - count + 1; - SERIAL_ECHOLNPAIR("\nProbing mesh point ", point_num, "/", int(GRID_MAX_POINTS), ".\n"); - TERN_(HAS_DISPLAY, ui.status_printf_P(0, PSTR(S_FMT " %i/%i"), GET_TEXT(MSG_PROBING_MESH), point_num, int(GRID_MAX_POINTS))); - - #if HAS_LCD_MENU - if (ui.button_pressed()) { - ui.quick_feedback(false); // Preserve button state for click-and-hold - SERIAL_ECHOLNPGM("\nMesh only partially populated.\n"); - ui.wait_for_release(); - ui.quick_feedback(); - ui.release(); - probe.stow(); // Release UI before stow to allow for PAUSE_BEFORE_DEPLOY_STOW - return restore_ubl_active_state_and_leave(); - } - #endif - - best = do_furthest - ? find_furthest_invalid_mesh_point() - : find_closest_mesh_point_of_type(INVALID, near, true); - - if (best.pos.x >= 0) { // mesh point found and is reachable by probe - TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(best.pos, ExtUI::PROBE_START)); - const float measured_z = probe.probe_at_point( - best.meshpos(), - stow_probe ? PROBE_PT_STOW : PROBE_PT_RAISE, g29_verbose_level - ); - z_values[best.pos.x][best.pos.y] = measured_z; - #if ENABLED(EXTENSIBLE_UI) - ExtUI::onMeshUpdate(best.pos, ExtUI::PROBE_FINISH); - ExtUI::onMeshUpdate(best.pos, measured_z); - #endif - } - SERIAL_FLUSH(); // Prevent host M105 buffer overrun. - - } while (best.pos.x >= 0 && --count); - - TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(best.pos, ExtUI::MESH_FINISH)); - - // Release UI during stow to allow for PAUSE_BEFORE_DEPLOY_STOW - TERN_(HAS_LCD_MENU, ui.release()); - probe.stow(); - TERN_(HAS_LCD_MENU, ui.capture()); - - probe.move_z_after_probing(); - - restore_ubl_active_state_and_leave(); - - do_blocking_move_to_xy( - constrain(near.x - probe.offset_xy.x, MESH_MIN_X, MESH_MAX_X), - constrain(near.y - probe.offset_xy.y, MESH_MIN_Y, MESH_MAX_Y) - ); + 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(); + #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(); + SET_PROBE_DEPLOYED(true); } #endif // HAS_BED_PROBE - #if HAS_LCD_MENU - - typedef void (*clickFunc_t)(); - - bool click_and_hold(const clickFunc_t func=nullptr) { - if (ui.button_pressed()) { - 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 - if (ELAPSED(millis(), nxt)) { // After 1.5 seconds - ui.quick_feedback(); - if (func) (*func)(); - ui.wait_for_release(); - return true; - } - } - } - serial_delay(15); - return false; + 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."); } - void unified_bed_leveling::move_z_with_encoder(const float &multiplier) { - ui.wait_for_release(); - while (!ui.button_pressed()) { - idle(); - gcode.reset_stepper_timeout(); // Keep steppers powered - if (encoder_diff) { - do_blocking_move_to_z(current_position.z + float(encoder_diff) * multiplier); - encoder_diff = 0; - } - } - } + switch (param.P_phase) { + case 0: + // + // Zero Mesh Data + // + reset(); + SERIAL_ECHOLNPGM("Mesh zeroed."); + break; - float unified_bed_leveling::measure_point_with_encoder() { - KEEPALIVE_STATE(PAUSED_FOR_USER); - move_z_with_encoder(0.01f); - return current_position.z; - } - - static void echo_and_take_a_measurement() { SERIAL_ECHOLNPGM(" and take a measurement."); } - - float unified_bed_leveling::measure_business_card_thickness(float in_height) { - ui.capture(); - save_ubl_active_state_and_disable(); // Disable bed level correction for probing - - do_blocking_move_to(0.5f * (MESH_MAX_X - (MESH_MIN_X)), 0.5f * (MESH_MAX_Y - (MESH_MIN_Y)), in_height); - //, _MIN(planner.settings.max_feedrate_mm_s[X_AXIS], planner.settings.max_feedrate_mm_s[Y_AXIS]) * 0.5f); - planner.synchronize(); - - SERIAL_ECHOPGM("Place shim under nozzle"); - LCD_MESSAGEPGM(MSG_UBL_BC_INSERT); - ui.return_to_status(); - echo_and_take_a_measurement(); - - const float z1 = measure_point_with_encoder(); - do_blocking_move_to_z(current_position.z + SIZE_OF_LITTLE_RAISE); - planner.synchronize(); - - SERIAL_ECHOPGM("Remove shim"); - LCD_MESSAGEPGM(MSG_UBL_BC_REMOVE); - echo_and_take_a_measurement(); - - const float z2 = measure_point_with_encoder(); - do_blocking_move_to_z(current_position.z + Z_CLEARANCE_BETWEEN_PROBES); - - const float thickness = ABS(z1 - z2); - - if (g29_verbose_level > 1) { - SERIAL_ECHOPAIR_F("Business Card is ", thickness, 4); - SERIAL_ECHOLNPGM("mm thick."); - } - - restore_ubl_active_state_and_leave(); - - return thickness; - } - - 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(); - - 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); - - ui.return_to_status(); - - mesh_index_pair location; - const xy_int8_t &lpos = location.pos; - do { - location = find_closest_mesh_point_of_type(INVALID, pos); - // It doesn't matter if the probe can't reach the NAN location. This is a manual probe. - if (!location.valid()) continue; - - const xyz_pos_t ppos = { - mesh_index_to_xpos(lpos.x), - mesh_index_to_ypos(lpos.y), - Z_CLEARANCE_BETWEEN_PROBES - }; - - if (!position_is_reachable(ppos)) break; // SHOULD NOT OCCUR (find_closest_mesh_point only returns reachable points) - - LCD_MESSAGEPGM(MSG_UBL_MOVING_TO_NEXT); - - do_blocking_move_to(ppos); - do_z_clearance(z_clearance); - - KEEPALIVE_STATE(PAUSED_FOR_USER); - ui.capture(); - - if (do_ubl_mesh_map) display_map(g29_map_type); // show user where we're probing - - serialprintPGM(parser.seen('B') ? GET_TEXT(MSG_UBL_BC_INSERT) : GET_TEXT(MSG_UBL_BC_INSERT2)); - - const float z_step = 0.01f; // existing behavior: 0.01mm per click, occasionally step - //const float z_step = planner.steps_to_mm[Z_AXIS]; // approx one step each click - - move_z_with_encoder(z_step); - - if (click_and_hold()) { - SERIAL_ECHOLNPGM("\nMesh only partially populated."); - do_z_clearance(Z_CLEARANCE_DEPLOY_PROBE); - return restore_ubl_active_state_and_leave(); - } - - z_values[lpos.x][lpos.y] = current_position.z - thick; - TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(location, z_values[lpos.x][lpos.y])); - - if (g29_verbose_level > 2) - SERIAL_ECHOLNPAIR_F("Mesh Point Measured at: ", z_values[lpos.x][lpos.y], 6); - SERIAL_FLUSH(); // Prevent host M105 buffer overrun. - } while (location.valid()); - - if (do_ubl_mesh_map) display_map(g29_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); - } - - inline void set_message_with_feedback(PGM_P const msg_P) { - ui.set_status_P(msg_P); - ui.quick_feedback(); - } - - void abort_fine_tune() { - ui.return_to_status(); - do_z_clearance(Z_CLEARANCE_BETWEEN_PROBES); - set_message_with_feedback(GET_TEXT(MSG_EDITING_STOPPED)); - } - - void unified_bed_leveling::fine_tune_mesh(const xy_pos_t &pos, const bool do_ubl_mesh_map) { - if (!parser.seen('R')) // fine_tune_mesh() is special. If no repetition count flag is specified - g29_repetition_cnt = 1; // do exactly one mesh location. Otherwise use what the parser decided. - - #if ENABLED(UBL_MESH_EDIT_MOVES_Z) - const float h_offset = parser.seenval('H') ? parser.value_linear_units() : 0; - if (!WITHIN(h_offset, 0, 10)) { - SERIAL_ECHOLNPGM("Offset out of bounds. (0 to 10mm)\n"); - return; - } - #endif - - mesh_index_pair location; - - if (!position_is_reachable(pos)) { - SERIAL_ECHOLNPGM("(X,Y) outside printable radius."); - return; - } - - save_ubl_active_state_and_disable(); - - LCD_MESSAGEPGM(MSG_UBL_FINE_TUNE_MESH); - ui.capture(); // Take over control of the LCD encoder - - do_blocking_move_to_xy_z(pos, Z_CLEARANCE_BETWEEN_PROBES); // Move to the given XY with probe clearance - - TERN_(UBL_MESH_EDIT_MOVES_Z, do_blocking_move_to_z(h_offset)); // Move Z to the given 'H' offset - - MeshFlags done_flags{0}; - const xy_int8_t &lpos = location.pos; - do { - location = find_closest_mesh_point_of_type(SET_IN_BITMAP, pos, false, &done_flags); - - if (lpos.x < 0) break; // Stop when there are no more reachable points - - done_flags.mark(lpos); // Mark this location as 'adjusted' so a new - // location is used on the next loop - const xyz_pos_t raw = { - mesh_index_to_xpos(lpos.x), - mesh_index_to_ypos(lpos.y), - Z_CLEARANCE_BETWEEN_PROBES - }; - - if (!position_is_reachable(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 - - TERN_(UBL_MESH_EDIT_MOVES_Z, do_blocking_move_to_z(h_offset)); // Move Z to the given 'H' offset before editing - - KEEPALIVE_STATE(PAUSED_FOR_USER); - - if (do_ubl_mesh_map) display_map(g29_map_type); // Display the current point - - #if IS_TFTGLCD_PANEL - ui.ubl_plot(lpos.x, lpos.y); // update plot screen - #endif - - ui.refresh(); - - float new_z = z_values[lpos.x][lpos.y]; - if (isnan(new_z)) new_z = 0; // Invalid points begin at 0 - new_z = FLOOR(new_z * 1000) * 0.001f; // Chop off digits after the 1000ths place - - lcd_mesh_edit_setup(new_z); - - SET_SOFT_ENDSTOP_LOOSE(true); - - do { - idle(); - new_z = lcd_mesh_edit(); - TERN_(UBL_MESH_EDIT_MOVES_Z, do_blocking_move_to_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); - - if (!lcd_map_control) ui.return_to_status(); // Just editing a single point? Return to status - - if (click_and_hold(abort_fine_tune)) break; // Button held down? Abort editing - - z_values[lpos.x][lpos.y] = new_z; // Save the updated Z value - TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(location, new_z)); - - serial_delay(20); // No switch noise - ui.refresh(); - - } while (lpos.x >= 0 && --g29_repetition_cnt > 0); - - if (do_ubl_mesh_map) display_map(g29_map_type); - restore_ubl_active_state_and_leave(); - - do_blocking_move_to_xy_z(pos, Z_CLEARANCE_BETWEEN_PROBES); - - LCD_MESSAGEPGM(MSG_UBL_DONE_EDITING_MESH); - SERIAL_ECHOLNPGM("Done Editing Mesh"); - - if (lcd_map_control) - ui.goto_screen(ubl_map_screen); - else - ui.return_to_status(); - } - - #endif // HAS_LCD_MENU - - bool unified_bed_leveling::g29_parameter_parsing() { - bool err_flag = false; - - TERN_(HAS_LCD_MENU, set_message_with_feedback(GET_TEXT(MSG_UBL_DOING_G29))); - - g29_constant = 0; - g29_repetition_cnt = 0; - - if (parser.seen('R')) { - g29_repetition_cnt = parser.has_value() ? parser.value_int() : GRID_MAX_POINTS; - NOMORE(g29_repetition_cnt, GRID_MAX_POINTS); - if (g29_repetition_cnt < 1) { - SERIAL_ECHOLNPGM("?(R)epetition count invalid (1+).\n"); - return UBL_ERR; - } - } - - g29_verbose_level = parser.seen('V') ? parser.value_int() : 0; - if (!WITHIN(g29_verbose_level, 0, 4)) { - SERIAL_ECHOLNPGM("?(V)erbose level implausible (0-4).\n"); - err_flag = true; - } - - if (parser.seen('P')) { - const int pv = parser.value_int(); - #if !HAS_BED_PROBE - if (pv == 1) { - SERIAL_ECHOLNPGM("G29 P1 requires a probe.\n"); - err_flag = true; - } - else - #endif - { - g29_phase_value = pv; - if (!WITHIN(g29_phase_value, 0, 6)) { - SERIAL_ECHOLNPGM("?(P)hase value invalid (0-6).\n"); - err_flag = true; - } - } - } - - if (parser.seen('J')) { #if HAS_BED_PROBE - g29_grid_size = parser.has_value() ? parser.value_int() : 0; - if (g29_grid_size && !WITHIN(g29_grid_size, 2, 9)) { - SERIAL_ECHOLNPGM("?Invalid grid size (J) specified (2-9).\n"); - err_flag = true; + + 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(); + SET_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; + } + SET_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 - SERIAL_ECHOLNPGM("G29 J action requires a probe.\n"); - err_flag = true; - #endif - } + else { + const float cvf = parser.value_float(); + switch ((int)TRUNC(cvf * 10.0f) - 30) { // 3.1 -> 1 + #if ENABLED(UBL_G29_P31) + case 1: { - xy_seen.x = parser.seenval('X'); - float sx = xy_seen.x ? parser.value_float() : current_position.x; - xy_seen.y = parser.seenval('Y'); - float sy = xy_seen.y ? parser.value_float() : current_position.y; + // 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 - if (xy_seen.x != xy_seen.y) { - SERIAL_ECHOLNPGM("Both X & Y locations must be specified.\n"); - err_flag = true; - } - - // If X or Y are not valid, use center of the bed values - if (!WITHIN(sx, X_MIN_BED, X_MAX_BED)) sx = X_CENTER; - if (!WITHIN(sy, Y_MIN_BED, Y_MAX_BED)) sy = Y_CENTER; - - if (err_flag) return UBL_ERR; - - g29_pos.set(sx, sy); - - /** - * Activate or deactivate UBL - * Note: UBL's G29 restores the state set here when done. - * Leveling is being enabled here with old data, possibly - * none. Error handling should disable for safety... - */ - if (parser.seen('A')) { - if (parser.seen('D')) { - SERIAL_ECHOLNPGM("?Can't activate and deactivate at the same time.\n"); - return UBL_ERR; - } - set_bed_leveling_enabled(true); - report_state(); - } - else if (parser.seen('D')) { - set_bed_leveling_enabled(false); - report_state(); - } - - // Set global 'C' flag and its value - if ((g29_c_flag = parser.seen('C'))) - g29_constant = parser.value_float(); - - #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) - if (parser.seenval('F')) { - const float fh = parser.value_float(); - if (!WITHIN(fh, 0, 100)) { - SERIAL_ECHOLNPGM("?(F)ade height for Bed Level Correction not plausible.\n"); - return UBL_ERR; + 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; + } } - set_z_fade_height(fh); + break; } - #endif - g29_map_type = parser.intval('T'); - if (!WITHIN(g29_map_type, 0, 2)) { - SERIAL_ECHOLNPGM("Invalid map type.\n"); - return UBL_ERR; + 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; + + case 6: shift_mesh_height(param.C_constant); break; } - return UBL_OK; } - static uint8_t ubl_state_at_invocation = 0; - #if ENABLED(UBL_DEVEL_DEBUGGING) - static uint8_t ubl_state_recursion_chk = 0; + + // + // Much of the 'What?' command can be eliminated. But until we are fully debugged, it is + // good to have the extra information. Soon... we prune this to just a few items + // + if (parser.seen_test('W')) g29_what_command(); + + // + // 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. + // + + if (parser.seen('K')) // Kompare Current Mesh Data to Specified Stored Mesh + g29_compare_current_mesh_to_stored_mesh(); + + #endif // UBL_DEVEL_DEBUGGING + + // + // Load a Mesh from the EEPROM + // + + 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 (!a) { + SERIAL_ECHOLNPGM("?EEPROM storage not available."); + return; + } + + if (!WITHIN(param.KLS_storage_slot, 0, a - 1)) { + SERIAL_ECHOLN(F("?Invalid "), F("storage slot.\n?Use 0 to "), a - 1); + return; + } + + settings.load_mesh(param.KLS_storage_slot); + storage_slot = param.KLS_storage_slot; + + SERIAL_ECHOLNPGM(STR_DONE); + } + + // + // 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; + + 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. + + 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.\n?Use 0 to "), a - 1); + goto LEAVE; + } + + settings.store_mesh(param.KLS_storage_slot); + storage_slot = param.KLS_storage_slot; + + SERIAL_ECHOLNPGM(STR_DONE); + } + + if (parser.seen_test('T')) + display_map(param.T_map_type); + + LEAVE: + + #if HAS_MARLINUI_MENU + ui.reset_alert_level(); + ui.quick_feedback(); + ui.reset_status(); + ui.release(); #endif - void unified_bed_leveling::save_ubl_active_state_and_disable() { - #if ENABLED(UBL_DEVEL_DEBUGGING) - ubl_state_recursion_chk++; - if (ubl_state_recursion_chk != 1) { - SERIAL_ECHOLNPGM("save_ubl_active_state_and_disabled() called multiple times in a row."); - TERN_(HAS_LCD_MENU, set_message_with_feedback(GET_TEXT(MSG_UBL_SAVE_ERROR))); - return; + #ifdef EVENT_GCODE_AFTER_G29 + if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("After G29 G-code: ", EVENT_GCODE_AFTER_G29); + if (probe_deployed) { + planner.synchronize(); + gcode.process_subcommands_now(F(EVENT_GCODE_AFTER_G29)); + } + #endif + + probe.use_probing_tool(false); + return; +} + +/** + * M420 C + * 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 offset) { + float sum = 0; + uint8_t n = 0; + GRID_LOOP(x, y) + if (!isnan(z_values[x][y])) { + sum += z_values[x][y]; + n++; + } + + const float mean = sum / n; + + // + // Sum the squares of difference from mean + // + float sum_of_diff_squared = 0; + GRID_LOOP(x, y) + if (!isnan(z_values[x][y])) + sum_of_diff_squared += sq(z_values[x][y] - mean); + + SERIAL_ECHOLNPGM("# of samples: ", n); + SERIAL_ECHOLNPGM("Mean Mesh Height: ", p_float_t(mean, 6)); + + const float sigma = SQRT(sum_of_diff_squared / (n + 1)); + SERIAL_ECHOLNPGM("Standard Deviation: ", p_float_t(sigma, 6)); + + if (cflag) + GRID_LOOP(x, y) + if (!isnan(z_values[x][y])) { + z_values[x][y] -= mean + offset; + TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(x, y, z_values[x][y])); } - #endif - ubl_state_at_invocation = planner.leveling_active; - set_bed_leveling_enabled(false); +} + +/** + * G29 P6 C : Shift Mesh Height by a uniform constant. + */ +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])); + } +} + +#if HAS_BED_PROBE + /** + * G29 P1 T V : Probe Entire Mesh + * Probe all invalidated locations of the mesh that can be reached by the probe. + * This attempts to fill in locations closest to the nozzle's start location first. + */ + void unified_bed_leveling::probe_entire_mesh(const xy_pos_t &nearby, const bool do_ubl_mesh_map, const bool stow_probe, const bool do_furthest) { + probe.deploy(); // Deploy before ui.capture() to allow for PAUSE_BEFORE_DEPLOY_STOW + + TERN_(HAS_MARLINUI_MENU, ui.capture()); + TERN_(EXTENSIBLE_UI, ExtUI::onLevelingStart()); + + save_ubl_active_state_and_disable(); // No bed level correction so only raw data is obtained + grid_count_t count = GRID_MAX_POINTS; + + mesh_index_pair best; + TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(best.pos, ExtUI::G29_START)); + do { + if (do_ubl_mesh_map) display_map(param.T_map_type); + + const grid_count_t point_num = (GRID_MAX_POINTS - count) + 1; + 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()); + + #if HAS_MARLINUI_MENU + if (ui.button_pressed()) { + ui.quick_feedback(false); // Preserve button state for click-and-hold + SERIAL_ECHOLNPGM("\nMesh only partially populated.\n"); + ui.wait_for_release(); + ui.quick_feedback(); + ui.release(); + probe.stow(); // Release UI before stow to allow for PAUSE_BEFORE_DEPLOY_STOW + return restore_ubl_active_state(); + } + #endif + + #ifndef HUGE_VALF + #define HUGE_VALF __FLT_MAX__ + #endif + + best = do_furthest // Points with valid data or HUGE_VALF are skipped + ? find_furthest_invalid_mesh_point() + : find_closest_mesh_point_of_type(INVALID, nearby, true); + + if (best.pos.x >= 0) { // mesh point found and is reachable by probe + TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(best.pos, ExtUI::G29_POINT_START)); + const float measured_z = probe.probe_at_point(best.meshpos(), stow_probe ? PROBE_PT_STOW : PROBE_PT_RAISE, param.V_verbosity); + z_values[best.pos.x][best.pos.y] = isnan(measured_z) ? HUGE_VALF : measured_z; // Mark invalid point already probed with HUGE_VALF to omit it in the next loop + #if ENABLED(EXTENSIBLE_UI) + ExtUI::onMeshUpdate(best.pos, ExtUI::G29_POINT_FINISH); + ExtUI::onMeshUpdate(best.pos, measured_z); + #endif + } + SERIAL_FLUSH(); // Prevent host M105 buffer overrun. + + } while (best.pos.x >= 0 && --count); + + 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)); + + // Release UI during stow to allow for PAUSE_BEFORE_DEPLOY_STOW + TERN_(HAS_MARLINUI_MENU, ui.release()); + probe.stow(); + TERN_(HAS_MARLINUI_MENU, ui.capture()); + + probe.move_z_after_probing(); + + 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) + ); + + restore_ubl_active_state(); } - void unified_bed_leveling::restore_ubl_active_state_and_leave() { - TERN_(HAS_LCD_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."); - TERN_(HAS_LCD_MENU, set_message_with_feedback(GET_TEXT(MSG_UBL_RESTORE_ERROR))); - return; - } - #endif - set_bed_leveling_enabled(ubl_state_at_invocation); - } +#endif // HAS_BED_PROBE - mesh_index_pair unified_bed_leveling::find_furthest_invalid_mesh_point() { +void set_message_with_feedback(FSTR_P const fstr) { + #if HAS_MARLINUI_MENU + ui.set_status(fstr); + ui.quick_feedback(); + #else + UNUSED(fstr); + #endif +} - bool found_a_NAN = false, found_a_real = false; +#if HAS_MARLINUI_MENU - mesh_index_pair farthest { -1, -1, -99999.99 }; + typedef void (*clickFunc_t)(); - GRID_LOOP(i, j) { - if (!isnan(z_values[i][j])) continue; // Skip valid mesh points - - // Skip unreachable points - if (!probe.can_reach(mesh_index_to_xpos(i), mesh_index_to_ypos(j))) - continue; - - found_a_NAN = true; - - xy_int8_t near { -1, -1 }; - float d1, d2 = 99999.9f; - GRID_LOOP(k, l) { - if (isnan(z_values[k][l])) continue; - - found_a_real = true; - - // Add in a random weighting factor that scrambles the probing of the - // last half of the mesh (when every unprobed mesh point is one index - // from a probed location). - - d1 = HYPOT(i - k, j - l) + (1.0f / ((millis() % 47) + 13)); - - if (d1 < d2) { // Invalid mesh point (i,j) is closer to the defined point (k,l) - d2 = d1; - near.set(i, j); + bool _click_and_hold(const clickFunc_t func=nullptr) { + if (ui.button_pressed()) { + 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! + marlin.idle(); // idle, of course + if (ELAPSED(millis(), nxt)) { // After 1.5 seconds + ui.quick_feedback(); + if (func) (*func)(); + ui.wait_for_release(); + return true; } } - - // - // At this point d2 should have the near defined mesh point to invalid mesh point (i,j) - // - - if (found_a_real && near.x >= 0 && d2 > farthest.distance) { - farthest.pos = near; // Found an invalid location farther from the defined mesh point - farthest.distance = d2; - } - } // GRID_LOOP - - if (!found_a_real && found_a_NAN) { // if the mesh is totally unpopulated, start the probing - farthest.pos.set((GRID_MAX_POINTS_X) / 2, (GRID_MAX_POINTS_Y) / 2); - farthest.distance = 1; } - return farthest; + serial_delay(15); + return false; } - mesh_index_pair unified_bed_leveling::find_closest_mesh_point_of_type(const MeshPointType type, const xy_pos_t &pos, const bool probe_relative/*=false*/, MeshFlags *done_flags/*=nullptr*/) { + void unified_bed_leveling::move_z_with_encoder(const float multiplier) { + ui.wait_for_release(); + while (!ui.button_pressed()) { + marlin.idle(); + gcode.reset_stepper_timeout(); // Keep steppers powered + if (encoder_diff) { + do_blocking_move_to_z(current_position.z + float(encoder_diff) * multiplier); + encoder_diff = 0; + } + } + } + + float unified_bed_leveling::measure_point_with_encoder() { + KEEPALIVE_STATE(PAUSED_FOR_USER); + const float z_step = 0.01f; + move_z_with_encoder(z_step); + return current_position.z; + } + + static void echo_and_take_a_measurement() { SERIAL_ECHOLNPGM(" and take a measurement."); } + + float unified_bed_leveling::measure_business_card_thickness() { + ui.capture(); + save_ubl_active_state_and_disable(); // Disable bed level correction for probing + + do_blocking_move_to( + xyz_pos_t({ + 0.5f * ((MESH_MAX_X) - (MESH_MIN_X)), + 0.5f * ((MESH_MAX_Y) - (MESH_MIN_Y)), + MANUAL_PROBE_START_Z + #ifdef SAFE_BED_LEVELING_START_I + , SAFE_BED_LEVELING_START_I + #endif + #ifdef SAFE_BED_LEVELING_START_J + , SAFE_BED_LEVELING_START_J + #endif + #ifdef SAFE_BED_LEVELING_START_K + , SAFE_BED_LEVELING_START_K + #endif + #ifdef SAFE_BED_LEVELING_START_U + , SAFE_BED_LEVELING_START_U + #endif + #ifdef SAFE_BED_LEVELING_START_V + , SAFE_BED_LEVELING_START_V + #endif + #ifdef SAFE_BED_LEVELING_START_W + , SAFE_BED_LEVELING_START_W + #endif + }) + //, _MIN(planner.settings.max_feedrate_mm_s[X_AXIS], planner.settings.max_feedrate_mm_s[Y_AXIS]) * 0.5f + ); + planner.synchronize(); + + SERIAL_ECHOPGM("Place shim under nozzle"); + LCD_MESSAGE(MSG_UBL_BC_INSERT); + ui.return_to_status(); + echo_and_take_a_measurement(); + + const float z1 = measure_point_with_encoder(); + 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); + + 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(); + + return thickness; + } + + /** + * G29 P2 : Manually Probe Remaining Mesh Points. + * 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 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); + + ui.return_to_status(); + + mesh_index_pair location; + const xy_int8_t &lpos = location.pos; + do { + location = find_closest_mesh_point_of_type(INVALID, pos); + // It doesn't matter if the probe can't reach the NAN location. This is a manual probe. + if (!location.valid()) continue; + + 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) + + LCD_MESSAGE(MSG_UBL_MOVING_TO_NEXT); + + do_blocking_move_to(ppos); + do_z_clearance(z_clearance); + + KEEPALIVE_STATE(PAUSED_FOR_USER); + ui.capture(); + + if (do_ubl_mesh_map) display_map(param.T_map_type); // Show user where we're probing + + if (parser.seen_test('B')) { + SERIAL_ECHOPGM("Place Shim & Measure"); + LCD_MESSAGE(MSG_UBL_BC_INSERT); + } + else { + SERIAL_ECHOPGM("Measure"); + LCD_MESSAGE(MSG_UBL_BC_INSERT2); + } + + const float z_step = 0.01f; // 0.01mm per encoder tick, occasionally step + move_z_with_encoder(z_step); + + if (_click_and_hold([]{ + SERIAL_ECHOLNPGM("\nMesh only partially populated."); + 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; + + // Tell the external UI to update + TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(location, z_values[lpos.x][lpos.y])); + + if (param.V_verbosity > 2) + SERIAL_ECHOLNPGM("Mesh Point Measured at: ", p_float_t(z_values[lpos.x][lpos.y], 6)); + SERIAL_FLUSH(); // Prevent host M105 buffer overrun. + } while (location.valid()); + + if (do_ubl_mesh_map) display_map(param.T_map_type); // show user where we're probing + + restore_ubl_active_state(); + do_blocking_move_to_xy_z(pos, Z_CLEARANCE_DEPLOY_PROBE); + } + + /** + * G29 P4 : Mesh Fine-Tuning. Go to point(s) and adjust values with the LCD. + * NOTE: Blocks the G-code queue and captures Marlin UI during use. + */ + void unified_bed_leveling::fine_tune_mesh(const xy_pos_t &pos, const bool do_ubl_mesh_map) { + if (!parser.seen_test('R')) // fine_tune_mesh() is special. If no repetition count flag is specified + param.R_repetition = 1; // do exactly one mesh location. Otherwise use what the parser decided. + + #if ENABLED(UBL_MESH_EDIT_MOVES_Z) + const float h_offset = parser.seenval('H') ? parser.value_linear_units() : MANUAL_PROBE_START_Z; + if (!WITHIN(h_offset, 0, 10)) { + SERIAL_ECHOLNPGM("Offset out of bounds. (0 to 10mm)\n"); + return; + } + #endif + + mesh_index_pair location; + + if (!position_is_reachable(pos)) { + SERIAL_ECHOLNPGM("(X,Y) outside printable radius."); + return; + } + + save_ubl_active_state_and_disable(); + + 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 + + MeshFlags done_flags{0}; + const xy_int8_t &lpos = location.pos; + + #if IS_TFTGLCD_PANEL + ui.ubl_mesh_edit_start(0); // Change current screen before calling ui.ubl_plot + safe_delay(50); + #endif + + do { + location = find_closest_mesh_point_of_type(SET_IN_BITMAP, pos, false, &done_flags); + + if (lpos.x < 0) break; // Stop when there are no more reachable points + + done_flags.mark(lpos); // Mark this location as 'adjusted' so a new + // 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) + + do_blocking_move_to(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 + + 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 + #endif + + ui.refresh(); + + float new_z = z_values[lpos.x][lpos.y]; + if (isnan(new_z)) new_z = 0; // Invalid points begin at 0 + new_z = FLOOR(new_z * 1000) * 0.001f; // Chop off digits after the 1000ths place + + ui.ubl_mesh_edit_start(new_z); + + SET_SOFT_ENDSTOP_LOOSE(true); + + do { + 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 + SERIAL_FLUSH(); // Prevent host M105 buffer overrun. + } while (!ui.button_pressed()); + + 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); + set_message_with_feedback(GET_TEXT_F(MSG_EDITING_STOPPED)); + })) break; + + // TODO: Disable leveling here so the Z value becomes the 'native' Z value. + + z_values[lpos.x][lpos.y] = new_z; // Save the updated Z value + + // TODO: Re-enable leveling here so Z is correctly based on the updated mesh. + + TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(location, new_z)); + + serial_delay(20); // No switch noise + ui.refresh(); + + } while (lpos.x >= 0 && --param.R_repetition > 0); + + if (do_ubl_mesh_map) display_map(param.T_map_type); + restore_ubl_active_state(); + + do_blocking_move_to_xy_z(pos, Z_TWEEN_SAFE_CLEARANCE); + + LCD_MESSAGE(MSG_UBL_DONE_EDITING_MESH); + SERIAL_ECHOLNPGM("Done Editing Mesh"); + + if (lcd_map_control) + ui.goto_screen(ubl_map_screen); + else + ui.return_to_status(); + } + +#endif // HAS_MARLINUI_MENU + +/** + * Parse and validate most G29 parameters, store for use by G29 functions. + */ +bool unified_bed_leveling::G29_parse_parameters() { + bool err_flag = false; + + set_message_with_feedback(GET_TEXT_F(MSG_UBL_DOING_G29)); + + param.C_constant = 0; + param.R_repetition = 0; + + if (parser.seen('R')) { + param.R_repetition = parser.has_value() ? parser.value_ushort() : GRID_MAX_POINTS; + NOMORE(param.R_repetition, GRID_MAX_POINTS); + if (param.R_repetition < 1) { + SERIAL_ECHOLNPGM("?(R)epetition count invalid (1+).\n"); + return UBL_ERR; + } + } + + param.V_verbosity = parser.byteval('V'); + if (!WITHIN(param.V_verbosity, 0, 4)) { + SERIAL_ECHOLNPGM("?(V)erbose level implausible (0-4).\n"); + err_flag = true; + } + + if (parser.seen('P')) { + const uint8_t pval = parser.value_byte(); + #if !HAS_BED_PROBE + if (pval == 1) { + SERIAL_ECHOLNPGM("G29 P1 requires a probe.\n"); + err_flag = true; + } + else + #endif + { + param.P_phase = pval; + if (!WITHIN(param.P_phase, 0, 6)) { + SERIAL_ECHOLNPGM("?(P)hase value invalid (0-6).\n"); + err_flag = true; + } + } + } + + if (parser.seen('J')) { + #if HAS_BED_PROBE + param.J_grid_size = parser.value_byte(); + if (param.J_grid_size && !WITHIN(param.J_grid_size, 2, 9)) { + SERIAL_ECHOLN(F("?Invalid "), F("grid size (J) specified (2-9).\n")); + err_flag = true; + } + #else + SERIAL_ECHOLNPGM("G29 J action requires a probe.\n"); + err_flag = true; + #endif + } + + param.XY_seen.x = parser.seenval('X'); + float sx = param.XY_seen.x ? parser.value_float() : current_position.x; + param.XY_seen.y = parser.seenval('Y'); + float sy = param.XY_seen.y ? parser.value_float() : current_position.y; + + if (param.XY_seen.x != param.XY_seen.y) { + SERIAL_ECHOLNPGM("Both X & Y locations must be specified.\n"); + err_flag = true; + } + + // 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 (err_flag) return UBL_ERR; + + param.XY_pos.set(sx, sy); + + /** + * Activate or deactivate UBL + * Note: UBL's G29 restores the state set here when done. + * Leveling is being enabled here with old data, possibly + * none. Error handling should disable for safety... + */ + if (parser.seen_test('A')) { + if (parser.seen_test('D')) { + SERIAL_ECHOLNPGM("?Can't activate and deactivate at the same time.\n"); + return UBL_ERR; + } + set_bed_leveling_enabled(true); + report_state(); + } + else if (parser.seen_test('D')) { + set_bed_leveling_enabled(false); + report_state(); + } + + // Set global 'C' flag and its value + if ((param.C_seen = parser.seen('C'))) + param.C_constant = parser.value_float(); + + #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) + if (parser.seenval('F')) { + const float fh = parser.value_float(); + if (!WITHIN(fh, 0, 100)) { + SERIAL_ECHOLNPGM("?(F)ade height for Bed Level Correction not plausible.\n"); + return UBL_ERR; + } + set_z_fade_height(fh); + } + #endif + + param.T_map_type = parser.byteval('T'); + if (!WITHIN(param.T_map_type, 0, 2)) { + SERIAL_ECHOLNPGM("Invalid map type.\n"); + return UBL_ERR; + } + return UBL_OK; +} + +static uint8_t ubl_state_at_invocation = 0; + +#if ENABLED(UBL_DEVEL_DEBUGGING) + static uint8_t ubl_state_recursion_chk = 0; +#endif + +void unified_bed_leveling::save_ubl_active_state_and_disable() { + #if ENABLED(UBL_DEVEL_DEBUGGING) + ubl_state_recursion_chk++; + if (ubl_state_recursion_chk != 1) { + SERIAL_ECHOLNPGM("save_ubl_active_state_and_disabled() called multiple times in a row."); + set_message_with_feedback(GET_TEXT_F(MSG_UBL_SAVE_ERROR)); + return; + } + #endif + ubl_state_at_invocation = planner.leveling_active; + set_bed_leveling_enabled(false); +} + +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() 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); + + if (is_done) { + TERN_(EXTENSIBLE_UI, ExtUI::onLevelingDone()); + } +} + +mesh_index_pair unified_bed_leveling::find_furthest_invalid_mesh_point() { + + bool found_a_NAN = false, found_a_real = false; + + mesh_index_pair farthest { -1, -1, -99999.99 }; + + GRID_LOOP(i, j) { + if (!isnan(z_values[i][j])) continue; // Skip valid mesh points + + // Skip unreachable points + if (!probe.can_reach(get_mesh_x(i), get_mesh_y(j))) + continue; + + found_a_NAN = true; + + xy_int8_t nearby { -1, -1 }; + float d1, d2 = 99999.9f; + GRID_LOOP(k, l) { + if (isnan(z_values[k][l])) continue; + + found_a_real = true; + + // Add in a random weighting factor that scrambles the probing of the + // last half of the mesh (when every unprobed mesh point is one index + // from a probed location). + + d1 = HYPOT(i - k, j - l) + (1.0f / ((millis() % 47) + 13)); + + if (d1 < d2) { // Invalid mesh point (i,j) is closer to the defined point (k,l) + d2 = d1; + nearby.set(i, j); + } + } + + // + // At this point d2 should have the near defined mesh point to invalid mesh point (i,j) + // + + if (found_a_real && nearby.x >= 0 && d2 > farthest.distance) { + farthest.pos = nearby; // Found an invalid location farther from the defined mesh point + farthest.distance = d2; + } + } // GRID_LOOP + + if (!found_a_real && found_a_NAN) { // if the mesh is totally unpopulated, start the probing + farthest.pos.set((GRID_MAX_POINTS_X) / 2, (GRID_MAX_POINTS_Y) / 2); + farthest.distance = 1; + } + return farthest; +} + +#if ENABLED(UBL_HILBERT_CURVE) + + typedef struct { + MeshPointType type; + MeshFlags *done_flags; + bool probe_relative; + mesh_index_pair closest; + } find_closest_t; + + static bool test_func(uint8_t i, uint8_t j, void *data) { + find_closest_t *d = (find_closest_t*)data; + if ( d->type == CLOSEST || d->type == (isnan(bedlevel.z_values[i][j]) ? INVALID : REAL) + || (d->type == SET_IN_BITMAP && !d->done_flags->marked(i, j)) + ) { + // Found a Mesh Point of the specified type! + const xy_pos_t mpos = { bedlevel.get_mesh_x(i), bedlevel.get_mesh_y(j) }; + + // If using the probe as the reference there are some unreachable locations. + // 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))) + return false; + d->closest.pos.set(i, j); + return true; + } + return false; + } + +#endif + +mesh_index_pair unified_bed_leveling::find_closest_mesh_point_of_type(const MeshPointType type, const xy_pos_t &pos, const bool probe_relative/*=false*/, MeshFlags *done_flags/*=nullptr*/) { + + #if ENABLED(UBL_HILBERT_CURVE) + + find_closest_t d; + d.type = type; + d.done_flags = done_flags; + d.probe_relative = probe_relative; + d.closest.invalidate(); + hilbert_curve::search_from_closest(pos, test_func, &d); + return d.closest; + + #else + mesh_index_pair closest; closest.invalidate(); closest.distance = -99999.9f; @@ -1255,11 +1392,11 @@ float best_so_far = 99999.99f; GRID_LOOP(i, j) { - if ( (type == (isnan(z_values[i][j]) ? INVALID : REAL)) + if ( type == CLOSEST || type == (isnan(z_values[i][j]) ? INVALID : REAL) || (type == SET_IN_BITMAP && !done_flags->marked(i, j)) ) { // Found a Mesh Point of the specified type! - const xy_pos_t mpos = { mesh_index_to_xpos(i), mesh_index_to_ypos(j) }; + const xy_pos_t mpos = { get_mesh_x(i), get_mesh_y(j) }; // If using the probe as the reference there are some unreachable locations. // Also for round beds, there are grid points outside the bed the nozzle can't reach. @@ -1284,495 +1421,452 @@ } // GRID_LOOP return closest; - } - /** - * 'Smart Fill': Scan from the outward edges of the mesh towards the center. - * If an invalid location is found, use the next two points (if valid) to - * calculate a 'reasonable' value for the unprobed mesh point. - */ + #endif +} - bool unified_bed_leveling::smart_fill_one(const uint8_t x, const uint8_t y, const int8_t xdir, const int8_t ydir) { - const float v = z_values[x][y]; - if (isnan(v)) { // A NAN... - const int8_t dx = x + xdir, dy = y + ydir; - const float v1 = z_values[dx][dy]; - if (!isnan(v1)) { // ...next to a pair of real values? - const float v2 = z_values[dx + xdir][dy + ydir]; - if (!isnan(v2)) { - z_values[x][y] = v1 < v2 ? v1 : v1 + v1 - v2; - TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(x, y, z_values[x][y])); - return true; - } - } - } - return false; - } +/** + * 'Smart Fill': Scan from the outward edges of the mesh towards the center. + * If an invalid location is found, use the next two points (if valid) to + * calculate a 'reasonable' value for the unprobed mesh point. + */ - typedef struct { uint8_t sx, ex, sy, ey; bool yfirst; } smart_fill_info; - - void unified_bed_leveling::smart_fill_mesh() { - static const smart_fill_info - info0 PROGMEM = { 0, GRID_MAX_POINTS_X, 0, GRID_MAX_POINTS_Y - 2, false }, // Bottom of the mesh looking up - info1 PROGMEM = { 0, GRID_MAX_POINTS_X, GRID_MAX_POINTS_Y - 1, 0, false }, // Top of the mesh looking down - info2 PROGMEM = { 0, GRID_MAX_POINTS_X - 2, 0, GRID_MAX_POINTS_Y, true }, // Left side of the mesh looking right - info3 PROGMEM = { GRID_MAX_POINTS_X - 1, 0, 0, GRID_MAX_POINTS_Y, true }; // Right side of the mesh looking left - static const smart_fill_info * const info[] PROGMEM = { &info0, &info1, &info2, &info3 }; - - LOOP_L_N(i, COUNT(info)) { - const smart_fill_info *f = (smart_fill_info*)pgm_read_ptr(&info[i]); - const int8_t sx = pgm_read_byte(&f->sx), sy = pgm_read_byte(&f->sy), - ex = pgm_read_byte(&f->ex), ey = pgm_read_byte(&f->ey); - if (pgm_read_byte(&f->yfirst)) { - const int8_t dir = ex > sx ? 1 : -1; - for (uint8_t y = sy; y != ey; ++y) - for (uint8_t x = sx; x != ex; x += dir) - if (smart_fill_one(x, y, dir, 0)) break; - } - else { - const int8_t dir = ey > sy ? 1 : -1; - for (uint8_t x = sx; x != ex; ++x) - for (uint8_t y = sy; y != ey; y += dir) - if (smart_fill_one(x, y, 0, dir)) break; +bool unified_bed_leveling::smart_fill_one(const uint8_t x, const uint8_t y, const int8_t xdir, const int8_t ydir) { + const float v = z_values[x][y]; + if (isnan(v)) { // A NAN... + const int8_t dx = x + xdir, dy = y + ydir; + const float v1 = z_values[dx][dy]; + if (!isnan(v1)) { // ...next to a pair of real values? + const float v2 = z_values[dx + xdir][dy + ydir]; + if (!isnan(v2)) { + z_values[x][y] = v1 < v2 ? v1 : v1 + v1 - v2; + TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(x, y, z_values[x][y])); + return true; } } } + return false; +} - #if HAS_BED_PROBE +typedef struct { uint8_t sx, ex, sy, ey; bool yfirst; } smart_fill_info; - //#define VALIDATE_MESH_TILT +void unified_bed_leveling::smart_fill_mesh() { + static const smart_fill_info + info0 PROGMEM = { 0, GRID_MAX_POINTS_X, 0, (GRID_MAX_POINTS_Y) - 2, false }, // Bottom of the mesh looking up + info1 PROGMEM = { 0, GRID_MAX_POINTS_X, (GRID_MAX_POINTS_Y) - 1, 0, false }, // Top of the mesh looking down + info2 PROGMEM = { 0, (GRID_MAX_POINTS_X) - 2, 0, GRID_MAX_POINTS_Y, true }, // Left side of the mesh looking right + info3 PROGMEM = { (GRID_MAX_POINTS_X) - 1, 0, 0, GRID_MAX_POINTS_Y, true }; // Right side of the mesh looking left + static const smart_fill_info * const info[] PROGMEM = { &info0, &info1, &info2, &info3 }; - #include "../../../libs/vector_3.h" + for (uint8_t i = 0; i < COUNT(info); ++i) { + const smart_fill_info *f = (smart_fill_info*)pgm_read_ptr(&info[i]); + const int8_t sx = pgm_read_byte(&f->sx), sy = pgm_read_byte(&f->sy), + ex = pgm_read_byte(&f->ex), ey = pgm_read_byte(&f->ey); + if (pgm_read_byte(&f->yfirst)) { + const int8_t dir = ex > sx ? 1 : -1; + for (uint8_t y = sy; y != ey; ++y) + for (uint8_t x = sx; x != ex; x += dir) + if (smart_fill_one(x, y, dir, 0)) break; + } + else { + const int8_t dir = ey > sy ? 1 : -1; + for (uint8_t x = sx; x != ex; ++x) + for (uint8_t y = sy; y != ey; y += dir) + if (smart_fill_one(x, y, 0, dir)) break; + } + } +} - void unified_bed_leveling::tilt_mesh_based_on_probed_grid(const bool do_3_pt_leveling) { - const float x_min = probe.min_x(), x_max = probe.max_x(), - y_min = probe.min_y(), y_max = probe.max_y(), - dx = (x_max - x_min) / (g29_grid_size - 1), - dy = (y_max - y_min) / (g29_grid_size - 1); +#if HAS_BED_PROBE + //#define VALIDATE_MESH_TILT + + #include "../../../libs/vector_3.h" + + void unified_bed_leveling::tilt_mesh_based_on_probed_grid(const bool do_3_pt_leveling) { + + float measured_z; + bool abort_flag = false; + + struct linear_fit_data lsf_results; + incremental_LSF_reset(&lsf_results); + + if (do_3_pt_leveling) { xy_float_t points[3]; probe.get_three_points(points); - float measured_z; - bool abort_flag = false; - - #ifdef VALIDATE_MESH_TILT - float z1, z2, z3; // Needed for algorithm validation below + #if ENABLED(UBL_TILT_ON_MESH_POINTS_3POINT) + mesh_index_pair cpos[3]; + for (uint8_t ix = 0; ix < 3; ++ix) { // Convert points to coordinates of mesh points + cpos[ix] = find_closest_mesh_point_of_type(REAL, points[ix], true); + points[ix] = cpos[ix].meshpos(); + } #endif - struct linear_fit_data lsf_results; - incremental_LSF_reset(&lsf_results); + #if ENABLED(VALIDATE_MESH_TILT) + float gotz[3]; // Used for algorithm validation below + #endif - if (do_3_pt_leveling) { - SERIAL_ECHOLNPGM("Tilting mesh (1/3)"); - TERN_(HAS_DISPLAY, ui.status_printf_P(0, PSTR(S_FMT " 1/3"), GET_TEXT(MSG_LCD_TILTING_MESH))); + for (uint8_t i = 0; i < 3; ++i) { + SERIAL_ECHOLNPGM("Tilting mesh (", i + 1, "/3)"); + TERN_(HAS_STATUS_MESSAGE, ui.status_printf(0, F(S_FMT " %i/3"), GET_TEXT(MSG_LCD_TILTING_MESH), i + 1)); - measured_z = probe.probe_at_point(points[0], PROBE_PT_RAISE, g29_verbose_level); - if (isnan(measured_z)) - abort_flag = true; - else { - measured_z -= get_z_correction(points[0]); - #ifdef VALIDATE_MESH_TILT - z1 = measured_z; - #endif - if (g29_verbose_level > 3) { - serial_spaces(16); - SERIAL_ECHOLNPAIR("Corrected_Z=", measured_z); - } - incremental_LSF(&lsf_results, points[0], measured_z); - } + measured_z = probe.probe_at_point(points[i], i < 2 ? PROBE_PT_RAISE : PROBE_PT_LAST_STOW, param.V_verbosity); + if ((abort_flag = isnan(measured_z))) break; - if (!abort_flag) { - SERIAL_ECHOLNPGM("Tilting mesh (2/3)"); - TERN_(HAS_DISPLAY, ui.status_printf_P(0, PSTR(S_FMT " 2/3"), GET_TEXT(MSG_LCD_TILTING_MESH))); + measured_z -= TERN(UBL_TILT_ON_MESH_POINTS_3POINT, z_values[cpos[i].pos.x][cpos[i].pos.y], get_z_correction(points[i])); + TERN_(VALIDATE_MESH_TILT, gotz[i] = measured_z); - measured_z = probe.probe_at_point(points[1], PROBE_PT_RAISE, g29_verbose_level); - #ifdef VALIDATE_MESH_TILT - z2 = measured_z; - #endif - if (isnan(measured_z)) - abort_flag = true; - else { - measured_z -= get_z_correction(points[1]); - if (g29_verbose_level > 3) { - serial_spaces(16); - SERIAL_ECHOLNPAIR("Corrected_Z=", measured_z); - } - incremental_LSF(&lsf_results, points[1], measured_z); - } - } + if (param.V_verbosity > 3) { SERIAL_ECHO_SP(16); SERIAL_ECHOLNPGM("Corrected_Z=", measured_z); } - if (!abort_flag) { - SERIAL_ECHOLNPGM("Tilting mesh (3/3)"); - TERN_(HAS_DISPLAY, ui.status_printf_P(0, PSTR(S_FMT " 3/3"), GET_TEXT(MSG_LCD_TILTING_MESH))); - - measured_z = probe.probe_at_point(points[2], PROBE_PT_STOW, g29_verbose_level); - #ifdef VALIDATE_MESH_TILT - z3 = measured_z; - #endif - if (isnan(measured_z)) - abort_flag = true; - else { - measured_z -= get_z_correction(points[2]); - if (g29_verbose_level > 3) { - serial_spaces(16); - SERIAL_ECHOLNPAIR("Corrected_Z=", measured_z); - } - incremental_LSF(&lsf_results, points[2], measured_z); - } - } - - probe.stow(); - probe.move_z_after_probing(); - - if (abort_flag) { - SERIAL_ECHOLNPGM("?Error probing point. Aborting operation."); - return; - } + incremental_LSF(&lsf_results, points[i], measured_z); } - else { // !do_3_pt_leveling - bool zig_zag = false; - - const uint16_t total_points = sq(g29_grid_size); - uint16_t point_num = 1; - - xy_pos_t rpos; - LOOP_L_N(ix, g29_grid_size) { - rpos.x = x_min + ix * dx; - LOOP_L_N(iy, g29_grid_size) { - rpos.y = y_min + dy * (zig_zag ? g29_grid_size - 1 - iy : iy); - - if (!abort_flag) { - SERIAL_ECHOLNPAIR("Tilting mesh point ", point_num, "/", total_points, "\n"); - TERN_(HAS_DISPLAY, ui.status_printf_P(0, PSTR(S_FMT " %i/%i"), GET_TEXT(MSG_LCD_TILTING_MESH), point_num, total_points)); - - measured_z = probe.probe_at_point(rpos, parser.seen('E') ? PROBE_PT_STOW : PROBE_PT_RAISE, g29_verbose_level); // TODO: Needs error handling - - abort_flag = isnan(measured_z); - - #if ENABLED(DEBUG_LEVELING_FEATURE) - if (DEBUGGING(LEVELING)) { - const xy_pos_t lpos = rpos.asLogical(); - DEBUG_CHAR('('); - DEBUG_ECHO_F(rpos.x, 7); - DEBUG_CHAR(','); - DEBUG_ECHO_F(rpos.y, 7); - DEBUG_ECHOPAIR_F(") logical: (", lpos.x, 7); - DEBUG_CHAR(','); - DEBUG_ECHO_F(lpos.y, 7); - DEBUG_ECHOPAIR_F(") measured: ", measured_z, 7); - DEBUG_ECHOPAIR_F(" correction: ", get_z_correction(rpos), 7); - } - #endif - - measured_z -= get_z_correction(rpos) /* + probe.offset.z */ ; - - if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPAIR_F(" final >>>---> ", measured_z, 7); - - if (g29_verbose_level > 3) { - serial_spaces(16); - SERIAL_ECHOLNPAIR("Corrected_Z=", measured_z); - } - incremental_LSF(&lsf_results, rpos, measured_z); - } - - point_num++; - } - - zig_zag ^= true; - } - } probe.stow(); probe.move_z_after_probing(); - if (abort_flag || finish_incremental_LSF(&lsf_results)) { - SERIAL_ECHOPGM("Could not complete LSF!"); + if (abort_flag) { + SERIAL_ECHOLNPGM("?Error probing point. Aborting operation."); return; } + } + else { // !do_3_pt_leveling - vector_3 normal = vector_3(lsf_results.A, lsf_results.B, 1).get_normal(); + #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()), + dx = (x_max - x_min) / (param.J_grid_size - 1), + dy = (y_max - y_min) / (param.J_grid_size - 1); - if (g29_verbose_level > 2) { - SERIAL_ECHOPAIR_F("bed plane normal = [", normal.x, 7); - SERIAL_CHAR(','); - SERIAL_ECHO_F(normal.y, 7); - SERIAL_CHAR(','); - SERIAL_ECHO_F(normal.z, 7); - SERIAL_ECHOLNPGM("]"); - } + bool zig_zag = false; - matrix_3x3 rotation = matrix_3x3::create_look_at(vector_3(lsf_results.A, lsf_results.B, 1)); + const uint16_t total_points = sq(param.J_grid_size); + uint16_t point_num = 1; - GRID_LOOP(i, j) { - float mx = mesh_index_to_xpos(i), - my = mesh_index_to_ypos(j), - mz = z_values[i][j]; + for (uint8_t ix = 0; ix < param.J_grid_size; ++ix) { + xy_pos_t rpos; + rpos.x = x_min + ix * dx; + for (uint8_t iy = 0; iy < param.J_grid_size; ++iy) { + rpos.y = y_min + dy * (zig_zag ? param.J_grid_size - 1 - iy : iy); - if (DEBUGGING(LEVELING)) { - DEBUG_ECHOPAIR_F("before rotation = [", mx, 7); - DEBUG_CHAR(','); - DEBUG_ECHO_F(my, 7); - DEBUG_CHAR(','); - DEBUG_ECHO_F(mz, 7); - DEBUG_ECHOPGM("] ---> "); - DEBUG_DELAY(20); + #if ENABLED(UBL_TILT_ON_MESH_POINTS) + #if ENABLED(DEBUG_LEVELING_FEATURE) + xy_pos_t oldRpos; + if (DEBUGGING(LEVELING)) oldRpos = rpos; + #endif + mesh_index_pair cpos; + rpos -= probe.offset; + cpos = find_closest_mesh_point_of_type(REAL, rpos, true); + rpos = cpos.meshpos(); + #endif + + SERIAL_ECHOLNPGM("Tilting mesh point ", point_num, "/", total_points, "\n"); + TERN_(HAS_STATUS_MESSAGE, ui.status_printf(0, F(S_FMT " %i/%i"), GET_TEXT(MSG_LCD_TILTING_MESH), point_num, total_points)); + + measured_z = probe.probe_at_point(rpos, parser.seen_test('E') ? PROBE_PT_STOW : PROBE_PT_RAISE, param.V_verbosity); // TODO: Needs error handling + + if ((abort_flag = isnan(measured_z))) break; + + const float zcorr = TERN(UBL_TILT_ON_MESH_POINTS, z_values[cpos.pos.x][cpos.pos.y], get_z_correction(rpos)); + + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) { + #if ENABLED(UBL_TILT_ON_MESH_POINTS) + const xy_pos_t oldLpos = oldRpos.asLogical(); + DEBUG_ECHO(F("Calculated point: ("), p_float_t(oldRpos.x, 7), C(','), p_float_t(oldRpos.y, 7), + F(") logical: ("), p_float_t(oldLpos.x, 7), C(','), p_float_t(oldLpos.y, 7), + F(")\nSelected mesh point: ") + ); + #endif + const xy_pos_t lpos = rpos.asLogical(); + DEBUG_ECHO( C('('), p_float_t(rpos.x, 7), C(','), p_float_t(rpos.y, 7), + F(") logical: ("), p_float_t(lpos.x, 7), C(','), p_float_t(lpos.y, 7), + F(") measured: "), p_float_t(measured_z, 7), + F(" correction: "), p_float_t(zcorr, 7) + ); + } + #endif + + measured_z -= zcorr; + + if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM(" final >>>---> ", p_float_t(measured_z, 7)); + + if (param.V_verbosity > 3) { + SERIAL_ECHO_SP(16); + SERIAL_ECHOLNPGM("Corrected_Z=", measured_z); + } + incremental_LSF(&lsf_results, rpos, measured_z); + + point_num++; } - apply_rotation_xyz(rotation, mx, my, mz); - - if (DEBUGGING(LEVELING)) { - DEBUG_ECHOPAIR_F("after rotation = [", mx, 7); - DEBUG_CHAR(','); - DEBUG_ECHO_F(my, 7); - DEBUG_CHAR(','); - DEBUG_ECHO_F(mz, 7); - DEBUG_ECHOLNPGM("]"); - DEBUG_DELAY(20); - } - - z_values[i][j] = mz - lsf_results.D; - TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(i, j, z_values[i][j])); + if (abort_flag) break; + FLIP(zig_zag); } + } + probe.stow(); + probe.move_z_after_probing(); + + if (abort_flag || finish_incremental_LSF(&lsf_results)) { + SERIAL_ECHOLNPGM("Could not complete LSF!"); + return; + } + + vector_3 normal = vector_3(lsf_results.A, lsf_results.B, 1).get_normal(); + + if (param.V_verbosity > 2) + SERIAL_ECHOLN(F("bed plane normal = ["), p_float_t(normal.x, 7), C(','), p_float_t(normal.y, 7), C(','), p_float_t(normal.z, 7), C(']')); + + matrix_3x3 rotation = matrix_3x3::create_look_at(vector_3(lsf_results.A, lsf_results.B, 1)); + + GRID_LOOP(i, j) { + float mx = get_mesh_x(i), my = get_mesh_y(j), mz = z_values[i][j]; if (DEBUGGING(LEVELING)) { - rotation.debug(PSTR("rotation matrix:\n")); - DEBUG_ECHOPAIR_F("LSF Results A=", lsf_results.A, 7); - DEBUG_ECHOPAIR_F(" B=", lsf_results.B, 7); - DEBUG_ECHOLNPAIR_F(" D=", lsf_results.D, 7); - DEBUG_DELAY(55); + DEBUG_ECHOLN(F("before rotation = ["), p_float_t(mx, 7), C(','), p_float_t(my, 7), C(','), p_float_t(mz, 7), F("] ---> ")); + DEBUG_DELAY(20); + } - DEBUG_ECHOPAIR_F("bed plane normal = [", normal.x, 7); - DEBUG_CHAR(','); - DEBUG_ECHO_F(normal.y, 7); - DEBUG_CHAR(','); - DEBUG_ECHO_F(normal.z, 7); - DEBUG_ECHOLNPGM("]"); - DEBUG_EOL(); + rotation.apply_rotation_xyz(mx, my, mz); - /** - * Use the code below to check the validity of the mesh tilting algorithm. - * 3-Point Mesh Tilt uses the same algorithm as grid-based tilting, but only - * three points are used in the calculation. This guarantees that each probed point - * has an exact match when get_z_correction() for that location is calculated. - * The Z error between the probed point locations and the get_z_correction() - * numbers for those locations should be 0. - */ - #ifdef VALIDATE_MESH_TILT - auto d_from = []{ DEBUG_ECHOPGM("D from "); }; - auto normed = [&](const xy_pos_t &pos, const float &zadd) { - return normal.x * pos.x + normal.y * pos.y + zadd; - }; - auto debug_pt = [](PGM_P const pre, const xy_pos_t &pos, const float &zadd) { - d_from(); serialprintPGM(pre); - DEBUG_ECHO_F(normed(pos, zadd), 6); - DEBUG_ECHOLNPAIR_F(" Z error = ", zadd - get_z_correction(pos), 6); - }; - debug_pt(PSTR("1st point: "), probe_pt[0], normal.z * z1); - debug_pt(PSTR("2nd point: "), probe_pt[1], normal.z * z2); - debug_pt(PSTR("3rd point: "), probe_pt[2], normal.z * z3); - d_from(); DEBUG_ECHOPGM("safe home with Z="); - DEBUG_ECHOLNPAIR_F("0 : ", normed(safe_homing_xy, 0), 6); - d_from(); DEBUG_ECHOPGM("safe home with Z="); - DEBUG_ECHOLNPAIR_F("mesh value ", normed(safe_homing_xy, get_z_correction(safe_homing_xy)), 6); - DEBUG_ECHOPAIR(" Z error = (", Z_SAFE_HOMING_X_POINT, ",", Z_SAFE_HOMING_Y_POINT); - DEBUG_ECHOLNPAIR_F(") = ", get_z_correction(safe_homing_xy), 6); - #endif - } // DEBUGGING(LEVELING) + if (DEBUGGING(LEVELING)) { + DEBUG_ECHOLN(F("after rotation = ["), p_float_t(mx, 7), C(','), p_float_t(my, 7), C(','), p_float_t(mz, 7), F("] ---> ")); + DEBUG_DELAY(20); + } + z_values[i][j] = mz - lsf_results.D; + TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(i, j, z_values[i][j])); } - #endif // HAS_BED_PROBE + if (DEBUGGING(LEVELING)) { + rotation.debug(F("rotation matrix:\n")); + DEBUG_ECHOLN(F("LSF Results A="), p_float_t(lsf_results.A, 7), F(" B="), p_float_t(lsf_results.B, 7), F(" D="), p_float_t(lsf_results.D, 7)); + DEBUG_DELAY(55); + DEBUG_ECHOLN(F("bed plane normal = ["), p_float_t(normal.x, 7), C(','), p_float_t(normal.y, 7), C(','), p_float_t(normal.z, 7), C(']')); + DEBUG_EOL(); - #if ENABLED(UBL_G29_P31) - void unified_bed_leveling::smart_fill_wlsf(const float &weight_factor) { + /** + * Use the code below to check the validity of the mesh tilting algorithm. + * 3-Point Mesh Tilt uses the same algorithm as grid-based tilting, but only + * three points are used in the calculation. This guarantees that each probed point + * has an exact match when get_z_correction() for that location is calculated. + * The Z error between the probed point locations and the get_z_correction() + * numbers for those locations should be 0. + */ + #if ENABLED(VALIDATE_MESH_TILT) + auto d_from = []{ DEBUG_ECHOPGM("D from "); }; + 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 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)); + }; + debug_pt(1, probe_pt[0], normal.z * gotz[0]); + debug_pt(2, probe_pt[1], normal.z * gotz[1]); + debug_pt(3, probe_pt[2], normal.z * gotz[2]); + #if ENABLED(Z_SAFE_HOMING) + constexpr xy_float_t safe_xy = { Z_SAFE_HOMING_X_POINT, Z_SAFE_HOMING_Y_POINT }; + d_from(); DEBUG_ECHOLN(F("safe home with Z="), F("0 : "), p_float_t(normed(safe_xy, 0), 6)); + d_from(); DEBUG_ECHOLN(F("safe home with Z="), F("mesh value "), p_float_t(normed(safe_xy, get_z_correction(safe_xy)), 6)); + DEBUG_ECHO(F(" Z error = ("), Z_SAFE_HOMING_X_POINT, C(','), Z_SAFE_HOMING_Y_POINT, F(") = "), p_float_t(get_z_correction(safe_xy), 6)); + #endif + #endif + } // DEBUGGING(LEVELING) - // For each undefined mesh point, compute a distance-weighted least squares fit - // from all the originally populated mesh points, weighted toward the point - // being extrapolated so that nearby points will have greater influence on - // the point being extrapolated. Then extrapolate the mesh point from WLSF. + } - static_assert((GRID_MAX_POINTS_Y) <= 16, "GRID_MAX_POINTS_Y too big"); - uint16_t bitmap[GRID_MAX_POINTS_X] = { 0 }; - struct linear_fit_data lsf_results; +#endif // HAS_BED_PROBE - SERIAL_ECHOPGM("Extrapolating mesh..."); +#if ENABLED(UBL_G29_P31) + void unified_bed_leveling::smart_fill_wlsf(const float weight_factor) { - const float weight_scaled = weight_factor * _MAX(MESH_X_DIST, MESH_Y_DIST); + // For each undefined mesh point, compute a distance-weighted least squares fit + // from all the originally populated mesh points, weighted toward the point + // being extrapolated so that nearby points will have greater influence on + // the point being extrapolated. Then extrapolate the mesh point from WLSF. - GRID_LOOP(jx, jy) if (!isnan(z_values[jx][jy])) SBI(bitmap[jx], jy); + static_assert((GRID_MAX_POINTS_Y) <= 16, "GRID_MAX_POINTS_Y too big"); + uint16_t bitmap[GRID_MAX_POINTS_X] = { 0 }; + struct linear_fit_data lsf_results; - xy_pos_t ppos; - LOOP_L_N(ix, GRID_MAX_POINTS_X) { - ppos.x = mesh_index_to_xpos(ix); - LOOP_L_N(iy, GRID_MAX_POINTS_Y) { - ppos.y = mesh_index_to_ypos(iy); - if (isnan(z_values[ix][iy])) { - // undefined mesh point at (ppos.x,ppos.y), compute weighted LSF from original valid mesh points. - incremental_LSF_reset(&lsf_results); - xy_pos_t rpos; - LOOP_L_N(jx, GRID_MAX_POINTS_X) { - rpos.x = mesh_index_to_xpos(jx); - LOOP_L_N(jy, GRID_MAX_POINTS_Y) { - if (TEST(bitmap[jx], jy)) { - rpos.y = mesh_index_to_ypos(jy); - const float rz = z_values[jx][jy], - w = 1.0f + weight_scaled / (rpos - ppos).magnitude(); - incremental_WLSF(&lsf_results, rpos, rz, w); - } + SERIAL_ECHOPGM("Extrapolating mesh..."); + + const float weight_scaled = weight_factor * _MAX(MESH_X_DIST, MESH_Y_DIST); + + GRID_LOOP(jx, jy) if (!isnan(z_values[jx][jy])) SBI(bitmap[jx], jy); + + xy_pos_t ppos; + for (uint8_t ix = 0; ix < GRID_MAX_POINTS_X; ++ix) { + ppos.x = get_mesh_x(ix); + for (uint8_t iy = 0; iy < GRID_MAX_POINTS_Y; ++iy) { + ppos.y = get_mesh_y(iy); + if (isnan(z_values[ix][iy])) { + // undefined mesh point at (ppos.x,ppos.y), compute weighted LSF from original valid mesh points. + incremental_LSF_reset(&lsf_results); + xy_pos_t rpos; + for (uint8_t jx = 0; jx < GRID_MAX_POINTS_X; ++jx) { + rpos.x = get_mesh_x(jx); + for (uint8_t jy = 0; jy < GRID_MAX_POINTS_Y; ++jy) { + if (TEST(bitmap[jx], jy)) { + rpos.y = get_mesh_y(jy); + const float rz = z_values[jx][jy], + w = 1.0f + weight_scaled / (rpos - ppos).magnitude(); + incremental_WLSF(&lsf_results, rpos, rz, w); } } - if (finish_incremental_LSF(&lsf_results)) { - SERIAL_ECHOLNPGM("Insufficient data"); - return; - } - 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 } + if (finish_incremental_LSF(&lsf_results)) { + SERIAL_ECHOLNPGM(" Insufficient data"); + return; + } + 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])); + marlin.idle(); // housekeeping } } - - SERIAL_ECHOLNPGM("done"); - } - #endif // UBL_G29_P31 - - #if ENABLED(UBL_DEVEL_DEBUGGING) - /** - * Much of the 'What?' command can be eliminated. But until we are fully debugged, it is - * good to have the extra information. Soon... we prune this to just a few items - */ - void unified_bed_leveling::g29_what_command() { - report_state(); - - if (storage_slot == -1) - SERIAL_ECHOPGM("No Mesh Loaded."); - else - SERIAL_ECHOPAIR("Mesh ", storage_slot, " Loaded."); - SERIAL_EOL(); - serial_delay(50); - - #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) - SERIAL_ECHOLNPAIR_F("Fade Height M420 Z", planner.z_fade_height, 4); - #endif - - adjust_mesh_to_mean(g29_c_flag, g29_constant); - - #if HAS_BED_PROBE - SERIAL_ECHOLNPAIR_F("Probe Offset M851 Z", probe.offset.z, 7); - #endif - - SERIAL_ECHOLNPAIR("MESH_MIN_X " STRINGIFY(MESH_MIN_X) "=", MESH_MIN_X); serial_delay(50); - SERIAL_ECHOLNPAIR("MESH_MIN_Y " STRINGIFY(MESH_MIN_Y) "=", MESH_MIN_Y); serial_delay(50); - SERIAL_ECHOLNPAIR("MESH_MAX_X " STRINGIFY(MESH_MAX_X) "=", MESH_MAX_X); serial_delay(50); - SERIAL_ECHOLNPAIR("MESH_MAX_Y " STRINGIFY(MESH_MAX_Y) "=", MESH_MAX_Y); serial_delay(50); - SERIAL_ECHOLNPAIR("GRID_MAX_POINTS_X ", GRID_MAX_POINTS_X); serial_delay(50); - SERIAL_ECHOLNPAIR("GRID_MAX_POINTS_Y ", GRID_MAX_POINTS_Y); serial_delay(50); - SERIAL_ECHOLNPAIR("MESH_X_DIST ", MESH_X_DIST); - SERIAL_ECHOLNPAIR("MESH_Y_DIST ", MESH_Y_DIST); serial_delay(50); - - SERIAL_ECHOPGM("X-Axis Mesh Points at: "); - LOOP_L_N(i, GRID_MAX_POINTS_X) { - SERIAL_ECHO_F(LOGICAL_X_POSITION(mesh_index_to_xpos(i)), 3); - SERIAL_ECHOPGM(" "); - serial_delay(25); - } - SERIAL_EOL(); - - SERIAL_ECHOPGM("Y-Axis Mesh Points at: "); - LOOP_L_N(i, GRID_MAX_POINTS_Y) { - SERIAL_ECHO_F(LOGICAL_Y_POSITION(mesh_index_to_ypos(i)), 3); - SERIAL_ECHOPGM(" "); - serial_delay(25); - } - SERIAL_EOL(); - - #if HAS_KILL - SERIAL_ECHOLNPAIR("Kill pin on :", int(KILL_PIN), " state:", int(kill_state())); - #endif - - SERIAL_EOL(); - serial_delay(50); - - #if ENABLED(UBL_DEVEL_DEBUGGING) - SERIAL_ECHOLNPAIR("ubl_state_at_invocation :", ubl_state_at_invocation, "\nubl_state_recursion_chk :", ubl_state_recursion_chk); - serial_delay(50); - - SERIAL_ECHOLNPAIR("Meshes go from ", hex_address((void*)settings.meshes_start_index()), " to ", hex_address((void*)settings.meshes_end_index())); - serial_delay(50); - - SERIAL_ECHOLNPAIR("sizeof(ubl) : ", (int)sizeof(ubl)); SERIAL_EOL(); - SERIAL_ECHOLNPAIR("z_value[][] size: ", (int)sizeof(z_values)); SERIAL_EOL(); - serial_delay(25); - - SERIAL_ECHOLNPAIR("EEPROM free for UBL: ", hex_address((void*)(settings.meshes_end_index() - settings.meshes_start_index()))); - serial_delay(50); - - SERIAL_ECHOLNPAIR("EEPROM can hold ", settings.calc_num_meshes(), " meshes.\n"); - serial_delay(25); - #endif // UBL_DEVEL_DEBUGGING - - if (!sanity_check()) { - echo_name(); - SERIAL_ECHOLNPGM(" sanity checks passed."); - } } - /** - * 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_ECHOLNPGM(" done."); + } +#endif // UBL_G29_P31 - 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++) { - persistentStore.read_data(i + j, &cccc, sizeof(uint8_t)); - print_hex_byte(cccc); - SERIAL_CHAR(' '); - } - SERIAL_EOL(); +#if ENABLED(UBL_DEVEL_DEBUGGING) + /** + * Much of the 'What?' command can be eliminated. But until we are fully debugged, it is + * good to have the extra information. Soon... we prune this to just a few items + */ + 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 ENABLED(ENABLE_LEVELING_FADE_HEIGHT) + SERIAL_ECHOLN(F("Fade Height M420 Z"), p_float_t(planner.z_fade_height, 4)); + #endif + + adjust_mesh_to_mean(param.C_seen, param.C_constant); + + #if HAS_BED_PROBE + 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_X_DIST ", MESH_X_DIST); + 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_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_delay(25); + } + SERIAL_EOL(); + + #if HAS_KILL + SERIAL_ECHOLNPGM("Kill pin on :", KILL_PIN, " state:", marlin.kill_state()); + #endif + + SERIAL_EOL(); + serial_delay(50); + + 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_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_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); + + if (!sanity_check()) { + echo_name(); + SERIAL_ECHOLNPGM(" sanity checks passed."); + } + } + + /** + * 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(); - 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(); + + if (!a) { + SERIAL_ECHOLNPGM("?EEPROM storage not available."); + return; } - /** - * 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 (!parser.has_value() || !WITHIN(g29_storage_slot, 0, a - 1)) { - SERIAL_ECHOLNPAIR("?Invalid storage slot.\n?Use 0 to ", a - 1); - return; - } - - g29_storage_slot = parser.value_int(); - - float tmp_z_values[GRID_MAX_POINTS_X][GRID_MAX_POINTS_Y]; - settings.load_mesh(g29_storage_slot, &tmp_z_values); - - SERIAL_ECHOLNPAIR("Subtracting mesh in slot ", g29_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])); - } + if (!parser.has_value() || !WITHIN(parser.value_int(), 0, a - 1)) { + SERIAL_ECHOLN(F("?Invalid "), F("storage slot.\n?Use 0 to "), a - 1); + return; } - #endif // UBL_DEVEL_DEBUGGING + 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); + + 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])); + } + } + +#endif // UBL_DEVEL_DEBUGGING #endif // AUTO_BED_LEVELING_UBL diff --git a/Marlin/src/feature/bedlevel/ubl/ubl_motion.cpp b/Marlin/src/feature/bedlevel/ubl/ubl_motion.cpp index 010b5951be..b6b9ec4368 100644 --- a/Marlin/src/feature/bedlevel/ubl/ubl_motion.cpp +++ b/Marlin/src/feature/bedlevel/ubl/ubl_motion.cpp @@ -19,25 +19,34 @@ * along with this program. If not, see . * */ + #include "../../../inc/MarlinConfig.h" #if ENABLED(AUTO_BED_LEVELING_UBL) #include "../bedlevel.h" #include "../../../module/planner.h" -#include "../../../module/stepper.h" #include "../../../module/motion.h" #if ENABLED(DELTA) #include "../../../module/delta.h" #endif -#include "../../../MarlinCore.h" #include +//#define DEBUG_UBL_MOTION +#define DEBUG_OUT ENABLED(DEBUG_UBL_MOTION) +#include "../../../core/debug_out.h" + #if !UBL_SEGMENTED - void unified_bed_leveling::line_to_destination_cartesian(const feedRate_t &scaled_fr_mm_s, const uint8_t extruder) { + // TODO: The first and last parts of a move might result in very short segment(s) + // after getting split on the cell boundary, so moves like that should not + // get split. This will be most common for moves that start/end near the + // 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) { /** * 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 @@ -51,44 +60,37 @@ const xyze_pos_t &start = current_position, &end = destination; #endif - const xy_int8_t istart = cell_indexes(start), iend = cell_indexes(end); + const xy_uint8_t istart = cell_indexes(start), iend = cell_indexes(end); // A move within the same cell needs no splitting if (istart == iend) { - // For a move off the bed, use a constant Z raise - if (!WITHIN(iend.x, 0, GRID_MAX_POINTS_X - 1) || !WITHIN(iend.y, 0, GRID_MAX_POINTS_Y - 1)) { - - // Note: There is no Z Correction in this case. We are off the grid and don't know what - // a reasonable correction would be. If the user has specified a UBL_Z_RAISE_WHEN_OFF_MESH - // value, that will be used instead of a calculated (Bi-Linear interpolation) correction. - - #ifdef UBL_Z_RAISE_WHEN_OFF_MESH - end.z += UBL_Z_RAISE_WHEN_OFF_MESH; - #endif - planner.buffer_segment(end, scaled_fr_mm_s, extruder); - current_position = destination; - return; - } - FINAL_MOVE: - // The distance is always MESH_X_DIST so multiply by the constant reciprocal. - const float xratio = (end.x - mesh_index_to_xpos(iend.x)) * RECIPROCAL(MESH_X_DIST); + // When UBL_Z_RAISE_WHEN_OFF_MESH is disabled Z correction is extrapolated from the edge of the mesh + #ifdef UBL_Z_RAISE_WHEN_OFF_MESH + // For a move off the UBL mesh, use a constant Z raise + if (!cell_index_x_valid(end.x) || !cell_index_y_valid(end.y)) { - float z1, z2; - if (iend.x >= GRID_MAX_POINTS_X - 1) - z1 = z2 = 0.0; - else { - z1 = z_values[iend.x ][iend.y ] + xratio * - (z_values[iend.x + 1][iend.y ] - z_values[iend.x][iend.y ]), - z2 = z_values[iend.x ][iend.y + 1] + xratio * - (z_values[iend.x + 1][iend.y + 1] - z_values[iend.x][iend.y + 1]); - } + // Note: There is no Z Correction in this case. We are off the mesh and don't know what + // a reasonable correction would be, UBL_Z_RAISE_WHEN_OFF_MESH will be used instead of + // a calculated (Bi-Linear interpolation) correction. + + end.z += UBL_Z_RAISE_WHEN_OFF_MESH; + planner.buffer_segment(end, scaled_fr_mm_s, extruder); + current_position = destination; + return; + } + #endif + + // The distance is always MESH_X_DIST so multiply by the constant reciprocal. + const float xratio = (end.x - get_mesh_x(iend.x)) * RECIPROCAL(MESH_X_DIST), + yratio = (end.y - get_mesh_y(iend.y)) * RECIPROCAL(MESH_Y_DIST), + z1 = z_values[iend.x][iend.y ] + xratio * (z_values[iend.x + 1][iend.y ] - z_values[iend.x][iend.y ]), + z2 = z_values[iend.x][iend.y + 1] + xratio * (z_values[iend.x + 1][iend.y + 1] - z_values[iend.x][iend.y + 1]); // X cell-fraction done. Interpolate the two Z offsets with the Y fraction for the final Z offset. - const float yratio = (end.y - mesh_index_to_ypos(iend.y)) * RECIPROCAL(MESH_Y_DIST), - z0 = iend.y < GRID_MAX_POINTS_Y - 1 ? (z1 + (z2 - z1) * yratio) * planner.fade_scaling_factor_for_z(end.z) : 0.0; + const float z0 = (z1 + (z2 - z1) * yratio) * planner.fade_scaling_factor_for_z(end.z); // Undefined parts of the Mesh in z_values[][] are NAN. // Replace NAN corrections with 0.0 to prevent NAN propagation. @@ -105,7 +107,7 @@ const xy_float_t dist = end - start; const xy_bool_t neg { dist.x < 0, dist.y < 0 }; - const xy_int8_t ineg { int8_t(neg.x), int8_t(neg.y) }; + const xy_uint8_t ineg { uint8_t(neg.x), uint8_t(neg.y) }; const xy_float_t sign { neg.x ? -1.0f : 1.0f, neg.y ? -1.0f : 1.0f }; const xy_int8_t iadd { int8_t(iend.x == istart.x ? 0 : sign.x), int8_t(iend.y == istart.y ? 0 : sign.y) }; @@ -120,20 +122,22 @@ const xy_float_t ad = sign * dist; const bool use_x_dist = ad.x > ad.y; - float on_axis_distance = use_x_dist ? dist.x : dist.y, - e_position = end.e - start.e, - z_position = end.z - start.z; + float on_axis_distance = use_x_dist ? dist.x : dist.y; - const float e_normalized_dist = e_position / on_axis_distance, // Allow divide by zero - z_normalized_dist = z_position / on_axis_distance; + const float z_normalized_dist = (end.z - start.z) / on_axis_distance; // Allow divide by zero + #if HAS_EXTRUDERS + const float e_normalized_dist = (end.e - start.e) / on_axis_distance; + const bool inf_normalized_flag = isinf(e_normalized_dist); + #endif - xy_int8_t icell = istart; + xy_uint8_t icell = istart; const float ratio = dist.y / dist.x, // Allow divide by zero c = start.y - ratio * start.x; - const bool inf_normalized_flag = isinf(e_normalized_dist), - inf_ratio_flag = isinf(ratio); + const bool inf_ratio_flag = isinf(ratio); + + xyze_pos_t dest; // Stores XYZE for segmented moves /** * Handle vertical lines that stay within one column. @@ -143,42 +147,46 @@ icell.y += ineg.y; // Line going down? Just go to the bottom. while (icell.y != iend.y + ineg.y) { icell.y += iadd.y; - const float next_mesh_line_y = mesh_index_to_ypos(icell.y); + const float next_mesh_line_y = get_mesh_y(icell.y); /** * Skip the calculations for an infinite slope. * For others the next X is the same so this can continue. * Calculate X at the next Y mesh line. */ - const float rx = inf_ratio_flag ? start.x : (next_mesh_line_y - c) / ratio; + dest.x = inf_ratio_flag ? start.x : (next_mesh_line_y - c) / ratio; - float z0 = z_correction_for_x_on_horizontal_mesh_line(rx, icell.x, icell.y) + float z0 = z_correction_for_x_on_horizontal_mesh_line(dest.x, icell.x, icell.y) * planner.fade_scaling_factor_for_z(end.z); // Undefined parts of the Mesh in z_values[][] are NAN. // Replace NAN corrections with 0.0 to prevent NAN propagation. if (isnan(z0)) z0 = 0.0; - const float ry = mesh_index_to_ypos(icell.y); + dest.y = get_mesh_y(icell.y); /** * Without this check, it's possible to generate a zero length move, as in the case where * the line is heading down, starting exactly on a mesh line boundary. Since this is rare * it might be fine to remove this check and let planner.buffer_segment() filter it out. */ - if (ry != start.y) { + if (dest.y != start.y) { if (!inf_normalized_flag) { // fall-through faster than branch - on_axis_distance = use_x_dist ? rx - start.x : ry - start.y; - e_position = start.e + on_axis_distance * e_normalized_dist; - z_position = start.z + on_axis_distance * z_normalized_dist; + on_axis_distance = use_x_dist ? dest.x - start.x : dest.y - start.y; + TERN_(HAS_EXTRUDERS, dest.e = start.e + on_axis_distance * e_normalized_dist); + dest.z = start.z + on_axis_distance * z_normalized_dist; } else { - e_position = end.e; - z_position = end.z; + TERN_(HAS_EXTRUDERS, dest.e = end.e); + dest.z = end.z; } - planner.buffer_segment(rx, ry, z_position + z0, e_position, scaled_fr_mm_s, extruder); - } //else printf("FIRST MOVE PRUNED "); + dest.z += z0; + planner.buffer_segment(dest, scaled_fr_mm_s, extruder); + + } + else + DEBUG_ECHOLNPGM("[ubl] skip Y segment"); } // At the final destination? Usually not, but when on a Y Mesh Line it's completed. @@ -195,12 +203,13 @@ */ if (iadd.y == 0) { // Horizontal line? icell.x += ineg.x; // Heading left? Just go to the left edge of the cell for the first move. + while (icell.x != iend.x + ineg.x) { icell.x += iadd.x; - const float rx = mesh_index_to_xpos(icell.x); - const float ry = ratio * rx + c; // Calculate Y at the next X mesh line + dest.x = get_mesh_x(icell.x); + dest.y = ratio * dest.x + c; // Calculate Y at the next X mesh line - float z0 = z_correction_for_y_on_vertical_mesh_line(ry, icell.x, icell.y) + float z0 = z_correction_for_y_on_vertical_mesh_line(dest.y, icell.x, icell.y) * planner.fade_scaling_factor_for_z(end.z); // Undefined parts of the Mesh in z_values[][] are NAN. @@ -212,20 +221,23 @@ * the line is heading left, starting exactly on a mesh line boundary. Since this is rare * it might be fine to remove this check and let planner.buffer_segment() filter it out. */ - if (rx != start.x) { + if (dest.x != start.x) { if (!inf_normalized_flag) { - on_axis_distance = use_x_dist ? rx - start.x : ry - start.y; - e_position = start.e + on_axis_distance * e_normalized_dist; // is based on X or Y because this is a horizontal move - z_position = start.z + on_axis_distance * z_normalized_dist; + on_axis_distance = use_x_dist ? dest.x - start.x : dest.y - start.y; + TERN_(HAS_EXTRUDERS, dest.e = start.e + on_axis_distance * e_normalized_dist); // Based on X or Y because the move is horizontal + dest.z = start.z + on_axis_distance * z_normalized_dist; } else { - e_position = end.e; - z_position = end.z; + TERN_(HAS_EXTRUDERS, dest.e = end.e); + dest.z = end.z; } - if (!planner.buffer_segment(rx, ry, z_position + z0, e_position, scaled_fr_mm_s, extruder)) - break; - } //else printf("FIRST MOVE PRUNED "); + dest.z += z0; + if (!planner.buffer_segment(dest, scaled_fr_mm_s, extruder)) break; + + } + else + DEBUG_ECHOLNPGM("[ubl] skip Y segment"); } if (xy_pos_t(current_position) != xy_pos_t(end)) @@ -239,64 +251,72 @@ * Generic case of a line crossing both X and Y Mesh lines. */ - xy_int8_t cnt = (istart - iend).ABS(); + xy_uint8_t cnt = istart.diff(iend); icell += ineg; while (cnt) { - const float next_mesh_line_x = mesh_index_to_xpos(icell.x + iadd.x), - next_mesh_line_y = mesh_index_to_ypos(icell.y + iadd.y), - ry = ratio * next_mesh_line_x + c, // Calculate Y at the next X mesh line - rx = (next_mesh_line_y - c) / ratio; // Calculate X at the next Y mesh line - // (No need to worry about ratio == 0. - // In that case, it was already detected - // as a vertical line move above.) + const float next_mesh_line_x = get_mesh_x(icell.x + iadd.x), + next_mesh_line_y = get_mesh_y(icell.y + iadd.y); - if (neg.x == (rx > next_mesh_line_x)) { // Check if we hit the Y line first + dest.y = ratio * next_mesh_line_x + c; // Calculate Y at the next X mesh line + dest.x = (next_mesh_line_y - c) / ratio; // Calculate X at the next Y mesh line + // (No need to worry about ratio == 0. + // In that case, it was already detected + // as a vertical line move above.) + + if (neg.x == (dest.x > next_mesh_line_x)) { // Check if we hit the Y line first // Yes! Crossing a Y Mesh Line next - float z0 = z_correction_for_x_on_horizontal_mesh_line(rx, icell.x - ineg.x, icell.y + iadd.y) + float z0 = z_correction_for_x_on_horizontal_mesh_line(dest.x, icell.x - ineg.x, icell.y + iadd.y) * planner.fade_scaling_factor_for_z(end.z); // Undefined parts of the Mesh in z_values[][] are NAN. // Replace NAN corrections with 0.0 to prevent NAN propagation. if (isnan(z0)) z0 = 0.0; + dest.y = next_mesh_line_y; + if (!inf_normalized_flag) { - on_axis_distance = use_x_dist ? rx - start.x : next_mesh_line_y - start.y; - e_position = start.e + on_axis_distance * e_normalized_dist; - z_position = start.z + on_axis_distance * z_normalized_dist; + on_axis_distance = use_x_dist ? dest.x - start.x : dest.y - start.y; + TERN_(HAS_EXTRUDERS, dest.e = start.e + on_axis_distance * e_normalized_dist); + dest.z = start.z + on_axis_distance * z_normalized_dist; } else { - e_position = end.e; - z_position = end.z; + TERN_(HAS_EXTRUDERS, dest.e = end.e); + dest.z = end.z; } - if (!planner.buffer_segment(rx, next_mesh_line_y, z_position + z0, e_position, scaled_fr_mm_s, extruder)) - break; + + dest.z += z0; + if (!planner.buffer_segment(dest, scaled_fr_mm_s, extruder)) break; + icell.y += iadd.y; cnt.y--; } else { // Yes! Crossing a X Mesh Line next - float z0 = z_correction_for_y_on_vertical_mesh_line(ry, icell.x + iadd.x, icell.y - ineg.y) + float z0 = z_correction_for_y_on_vertical_mesh_line(dest.y, icell.x + iadd.x, icell.y - ineg.y) * planner.fade_scaling_factor_for_z(end.z); // Undefined parts of the Mesh in z_values[][] are NAN. // Replace NAN corrections with 0.0 to prevent NAN propagation. if (isnan(z0)) z0 = 0.0; + dest.x = next_mesh_line_x; + if (!inf_normalized_flag) { - on_axis_distance = use_x_dist ? next_mesh_line_x - start.x : ry - start.y; - e_position = start.e + on_axis_distance * e_normalized_dist; - z_position = start.z + on_axis_distance * z_normalized_dist; + on_axis_distance = use_x_dist ? dest.x - start.x : dest.y - start.y; + TERN_(HAS_EXTRUDERS, dest.e = start.e + on_axis_distance * e_normalized_dist); + dest.z = start.z + on_axis_distance * z_normalized_dist; } else { - e_position = end.e; - z_position = end.z; + TERN_(HAS_EXTRUDERS, dest.e = end.e); + dest.z = end.z; } - if (!planner.buffer_segment(next_mesh_line_x, ry, z_position + z0, e_position, scaled_fr_mm_s, extruder)) - break; + dest.z += z0; + if (!planner.buffer_segment(dest, scaled_fr_mm_s, extruder)) break; + icell.x += iadd.x; cnt.x--; } @@ -313,14 +333,14 @@ #else // UBL_SEGMENTED #if IS_SCARA - #define DELTA_SEGMENT_MIN_LENGTH 0.25 // SCARA minimum segment size is 0.25mm - #elif ENABLED(DELTA) - #define DELTA_SEGMENT_MIN_LENGTH 0.10 // mm (still subject to DELTA_SEGMENTS_PER_SECOND) + #define SEGMENT_MIN_LENGTH 0.25 // SCARA minimum segment size is 0.25mm + #elif IS_KINEMATIC + #define SEGMENT_MIN_LENGTH 0.10 // (mm) Still subject to DEFAULT_SEGMENTS_PER_SECOND #else // CARTESIAN #ifdef LEVELED_SEGMENT_LENGTH - #define DELTA_SEGMENT_MIN_LENGTH LEVELED_SEGMENT_LENGTH + #define SEGMENT_MIN_LENGTH LEVELED_SEGMENT_LENGTH #else - #define DELTA_SEGMENT_MIN_LENGTH 1.00 // mm (similar to G2/G3 arc segmentation) + #define SEGMENT_MIN_LENGTH 1.00 // (mm) Similar to G2/G3 arc segmentation #endif #endif @@ -330,7 +350,7 @@ * Returns true if did NOT move, false if moved (requires current_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 @@ -338,23 +358,24 @@ const xyze_pos_t total = destination - current_position; const float cart_xy_mm_2 = HYPOT2(total.x, total.y), - cart_xy_mm = SQRT(cart_xy_mm_2); // Total XY distance + cart_xy_mm = SQRT(cart_xy_mm_2); // Total XY distance #if IS_KINEMATIC - const float seconds = cart_xy_mm / scaled_fr_mm_s; // Duration of XY move at requested rate - uint16_t segments = LROUND(delta_segments_per_second * seconds), // Preferred number of segments for distance @ feedrate - seglimit = LROUND(cart_xy_mm * RECIPROCAL(DELTA_SEGMENT_MIN_LENGTH)); // Number of segments at minimum segment length - NOMORE(segments, seglimit); // Limit to minimum segment length (fewer segments) + const float seconds = cart_xy_mm / scaled_fr_mm_s; // Duration of XY move at requested rate + uint16_t segments = LROUND(segments_per_second * seconds), // Preferred number of segments for distance @ feedrate + seglimit = LROUND(cart_xy_mm * RECIPROCAL(SEGMENT_MIN_LENGTH)); // Number of segments at minimum segment length + NOMORE(segments, seglimit); // Limit to minimum segment length (fewer segments) #else - uint16_t segments = LROUND(cart_xy_mm * RECIPROCAL(DELTA_SEGMENT_MIN_LENGTH)); // Cartesian fixed segment length + uint16_t segments = LROUND(cart_xy_mm * RECIPROCAL(SEGMENT_MIN_LENGTH)); // Cartesian fixed segment length #endif - NOLESS(segments, 1U); // Must have at least one segment - const float inv_segments = 1.0f / segments, // Reciprocal to save calculation - segment_xyz_mm = SQRT(cart_xy_mm_2 + sq(total.z)) * inv_segments; // Length of each segment + NOLESS(segments, 1U); // Must have at least one segment + const float inv_segments = 1.0f / segments; // Reciprocal to save calculation - #if ENABLED(SCARA_FEEDRATE_SCALING) - const float inv_duration = scaled_fr_mm_s / segment_xyz_mm; + // Add hints to help optimize the move + PlannerHints hints(SQRT(cart_xy_mm_2 + sq(total.z)) * inv_segments); // Length of each segment + #if ENABLED(FEEDRATE_SCALING) + hints.inv_duration = scaled_fr_mm_s / hints.millimeters; #endif xyze_float_t diff = total * inv_segments; @@ -368,17 +389,9 @@ if (!planner.leveling_active || !planner.leveling_active_at_z(destination.z)) { while (--segments) { raw += diff; - planner.buffer_line(raw, scaled_fr_mm_s, active_extruder, segment_xyz_mm - #if ENABLED(SCARA_FEEDRATE_SCALING) - , inv_duration - #endif - ); + planner.buffer_line(raw, scaled_fr_mm_s, active_extruder, hints); } - planner.buffer_line(destination, scaled_fr_mm_s, active_extruder, segment_xyz_mm - #if ENABLED(SCARA_FEEDRATE_SCALING) - , inv_duration - #endif - ); + planner.buffer_line(destination, scaled_fr_mm_s, active_extruder, hints); return false; // Did not set current from destination } @@ -404,20 +417,22 @@ 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_POINTS_X) - 1); - LIMIT(icell.y, 0, (GRID_MAX_POINTS_Y) - 1); + LIMIT(icell.x, 0, GRID_MAX_CELLS_X); + LIMIT(icell.y, 0, GRID_MAX_CELLS_Y); - float z_x0y0 = z_values[icell.x ][icell.y ], // z at lower left corner - z_x1y0 = z_values[icell.x+1][icell.y ], // z at upper left corner - z_x0y1 = z_values[icell.x ][icell.y+1], // z at lower right corner - z_x1y1 = z_values[icell.x+1][icell.y+1]; // z at upper right corner + const int8_t ncellx = _MIN(icell.x+1, GRID_MAX_CELLS_X), + ncelly = _MIN(icell.y+1, GRID_MAX_CELLS_Y); + float z_x0y0 = z_values[icell.x][icell.y], // z at lower left corner + z_x1y0 = z_values[ncellx ][icell.y], // z at upper left corner + z_x0y1 = z_values[icell.x][ncelly ], // z at lower right corner + z_x1y1 = z_values[ncellx ][ncelly ]; // z at upper right corner if (isnan(z_x0y0)) z_x0y0 = 0; // ideally activating planner.leveling_active (G29 A) if (isnan(z_x1y0)) z_x1y0 = 0; // should refuse if any invalid mesh points if (isnan(z_x0y1)) z_x0y1 = 0; // in order to avoid isnan tests per cell, if (isnan(z_x1y1)) z_x1y1 = 0; // thus guessing zero for undefined points - const xy_pos_t pos = { mesh_index_to_xpos(icell.x), mesh_index_to_ypos(icell.y) }; + const xy_pos_t pos = { get_mesh_x(icell.x), get_mesh_y(icell.y) }; xy_pos_t cell = raw - pos; const float z_xmy0 = (z_x1y0 - z_x0y0) * RECIPROCAL(MESH_X_DIST), // z slope per x along y0 (lower left to lower right) @@ -444,16 +459,11 @@ if (--segments == 0) raw = 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 - #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) - * fade_scaling_factor // apply fade factor to interpolated mesh height - #endif - ; + TERN_(ENABLE_LEVELING_FADE_HEIGHT, * fade_scaling_factor); // apply fade factor to interpolated height - planner.buffer_line(raw.x, raw.y, raw.z + z_cxcy, raw.e, scaled_fr_mm_s, active_extruder, segment_xyz_mm - #if ENABLED(SCARA_FEEDRATE_SCALING) - , inv_duration - #endif - ); + const float oldz = raw.z; raw.z += z_cxcy; + planner.buffer_line(raw, scaled_fr_mm_s, active_extruder, hints); + raw.z = oldz; if (segments == 0) // done with last segment return false; // didn't set current from destination diff --git a/Marlin/src/feature/binary_stream.h b/Marlin/src/feature/binary_stream.h index 32ebcce2f6..304fdae300 100644 --- a/Marlin/src/feature/binary_stream.h +++ b/Marlin/src/feature/binary_stream.h @@ -24,44 +24,29 @@ #include "../inc/MarlinConfig.h" #define BINARY_STREAM_COMPRESSION - #if ENABLED(BINARY_STREAM_COMPRESSION) #include "../libs/heatshrink/heatshrink_decoder.h" -#endif - -inline bool bs_serial_data_available(const uint8_t index) { - switch (index) { - case 0: return MYSERIAL0.available(); - #if HAS_MULTI_SERIAL - case 1: return MYSERIAL1.available(); - #endif - } - return false; -} - -inline int bs_read_serial(const uint8_t index) { - switch (index) { - case 0: return MYSERIAL0.read(); - #if HAS_MULTI_SERIAL - case 1: return MYSERIAL1.read(); - #endif - } - return -1; -} - -#if ENABLED(BINARY_STREAM_COMPRESSION) + // STM32 (and others?) require a word-aligned buffer for SD card transfers via DMA + static __attribute__((aligned(sizeof(size_t)))) uint8_t decode_buffer[512] = {}; static heatshrink_decoder hsd; - static uint8_t decode_buffer[512] = {}; #endif +inline bool bs_serial_data_available(const serial_index_t index) { + return SERIAL_IMPL.available(index); +} + +inline int bs_read_serial(const serial_index_t index) { + return SERIAL_IMPL.read(index); +} + class SDFileTransferProtocol { private: struct Packet { struct [[gnu::packed]] Open { - static bool validate(char* buffer, size_t length) { + static bool validate(char *buffer, size_t length) { return (length > sizeof(Open) && buffer[length - 1] == '\0'); } - static Open& decode(char* buffer) { + static Open& decode(char *buffer) { data = &buffer[2]; return *reinterpret_cast(buffer); } @@ -74,7 +59,7 @@ private: }; }; - static bool file_open(char* filename) { + static bool file_open(char *filename) { if (!dummy_transfer) { card.mount(); card.openFileWrite(filename); @@ -86,7 +71,7 @@ private: return true; } - static bool file_write(char* buffer, const size_t length) { + static bool file_write(char *buffer, const size_t length) { #if ENABLED(BINARY_STREAM_COMPRESSION) if (compression) { size_t total_processed = 0, processed_count = 0; @@ -149,23 +134,23 @@ private: 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; + static void process(uint8_t packet_type, char *buffer, const uint16_t length) { + transfer_timeout = millis() + timeout; switch (static_cast(packet_type)) { case FileTransfer::QUERY: - SERIAL_ECHOPAIR("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_ECHOLNPAIR(":compresion: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(":compresion:none"); + SERIAL_ECHOLNPGM(":compression:none"); #endif break; case FileTransfer::OPEN: @@ -209,7 +194,7 @@ public: } } - 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 { @@ -224,7 +209,7 @@ public: 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 @@ -260,7 +245,7 @@ public: bytes_received = 0; checksum = 0; header_checksum = 0; - timeout = millis() + PACKET_MAX_WAIT; + timeout = millis() + packet_max_wait; buffer = nullptr; } } packet{}; @@ -287,17 +272,17 @@ public: } 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 ENABLED(SDSUPPORT) - PORT_REDIRECT(card.transfer_port_index); + #if HAS_MEDIA + PORT_REDIRECT(SERIAL_PORTMASK(card.transfer_port_index)); #endif #pragma GCC diagnostic push @@ -314,7 +299,7 @@ public: 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; } @@ -337,7 +322,7 @@ public: if (packet.header.checksum == packet.header_checksum) { // The SYNC control packet is a special case in that it doesn't require the stream sync to be correct if (static_cast(packet.header.protocol()) == Protocol::CONTROL && static_cast(packet.header.type()) == ProtocolControl::SYNC) { - SERIAL_ECHOLNPAIR("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; } @@ -352,7 +337,7 @@ public: stream_state = StreamState::PACKET_PROCESS; } else if (packet.header.sync == sync - 1) { // ok response must have been lost - SERIAL_ECHOLNPAIR("ok", packet.header.sync); // transmit valid packet received and drop the payload + SERIAL_ECHOLNPGM("ok", packet.header.sync); // transmit valid packet received and drop the payload stream_state = StreamState::PACKET_RESET; } else if (packet_retries) { @@ -364,8 +349,7 @@ public: } } else { - SERIAL_ECHO_START(); - SERIAL_ECHOLNPAIR("Packet header(", packet.header.sync, "?) corrupt"); + SERIAL_ECHO_MSG("Packet header(", packet.header.sync, "?) corrupt"); stream_state = StreamState::PACKET_RESEND; } } @@ -399,8 +383,7 @@ public: stream_state = StreamState::PACKET_PROCESS; } else { - SERIAL_ECHO_START(); - SERIAL_ECHOLNPAIR("Packet(", packet.header.sync, ") payload corrupt"); + SERIAL_ECHO_MSG("Packet(", packet.header.sync, ") payload corrupt"); stream_state = StreamState::PACKET_RESEND; } } @@ -410,17 +393,16 @@ public: packet_retries = 0; bytes_received += packet.header.size; - SERIAL_ECHOLNPAIR("ok", packet.header.sync); // transmit valid packet received + SERIAL_ECHOLNPGM("ok", packet.header.sync); // transmit valid packet received dispatch(); 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_START(); - SERIAL_ECHOLNPAIR("Resend request ", int(packet_retries)); - SERIAL_ECHOLNPAIR("rs", sync); + SERIAL_ECHO_MSG("Resend request ", packet_retries); + SERIAL_ECHOLNPGM("rs", sync); } else stream_state = StreamState::PACKET_ERROR; @@ -430,7 +412,7 @@ public: stream_state = StreamState::PACKET_RESEND; break; case StreamState::PACKET_ERROR: - SERIAL_ECHOLNPAIR("fe", packet.header.sync); + SERIAL_ECHOLNPGM("fe", packet.header.sync); reset(); // reset everything, resync required stream_state = StreamState::PACKET_RESET; break; @@ -464,7 +446,7 @@ public: 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 d6b1f99c16..85b69d66c4 100644 --- a/Marlin/src/feature/bltouch.cpp +++ b/Marlin/src/feature/bltouch.cpp @@ -28,19 +28,28 @@ BLTouch bltouch; -bool BLTouch::last_written_mode; // Initialized by settings.load, 0 = Open Drain; 1 = 5V Drain +bool BLTouch::od_5v_mode; // Initialized by settings.load, 0 = Open Drain; 1 = 5V Drain +#if HAS_BLTOUCH_HS_MODE + bool BLTouch::high_speed_mode; // Initialized by settings.load, 0 = Low Speed; 1 = High Speed +#else + constexpr bool BLTouch::high_speed_mode; +#endif #include "../module/servo.h" - -void stop(); +#include "../module/probe.h" #define DEBUG_OUT ENABLED(DEBUG_LEVELING_FEATURE) #include "../core/debug_out.h" bool BLTouch::command(const BLTCommand cmd, const millis_t &ms) { - if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPAIR("BLTouch Command :", cmd); - MOVE_SERVO(Z_PROBE_SERVO_NR, cmd); - safe_delay(_MAX(ms, (uint32_t)BLTOUCH_DELAY)); // BLTOUCH_DELAY is also the *minimum* delay + const BLTCommand current = servo[Z_PROBE_SERVO_NR].read(); + if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPGM("BLTouch from ", current, " to ", cmd); + // If the new command is the same, skip it (and the delay). + // 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 + } return triggered(); } @@ -62,18 +71,11 @@ void BLTouch::init(const bool set_voltage/*=false*/) { #else - if (DEBUGGING(LEVELING)) { - DEBUG_ECHOLNPAIR("last_written_mode - ", (int)last_written_mode); - DEBUG_ECHOLNPGM("config mode - " - #if ENABLED(BLTOUCH_SET_5V_MODE) - "BLTOUCH_SET_5V_MODE" - #else - "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 = last_written_mode != ENABLED(BLTOUCH_SET_5V_MODE); + const bool should_set = od_5v_mode != ENABLED(BLTOUCH_SET_5V_MODE); #endif @@ -90,15 +92,7 @@ void BLTouch::clear() { _stow(); // STOW to be ready for meaningful work. Could fail, don't care } -bool BLTouch::triggered() { - return ( - #if ENABLED(Z_MIN_PROBE_USES_Z_MIN_ENDSTOP_PIN) - READ(Z_MIN_PIN) != Z_MIN_ENDSTOP_INVERTING - #else - READ(Z_MIN_PROBE_PIN) != Z_MIN_PROBE_ENDSTOP_INVERTING - #endif - ); -} +bool BLTouch::triggered() { return PROBE_TRIGGERED(); } bool BLTouch::deploy_proc() { // Do a DEPLOY @@ -114,11 +108,8 @@ bool BLTouch::deploy_proc() { // Last attempt to DEPLOY if (_deploy_query_alarm()) { // The deploy might have failed or the probe is actually triggered (nozzle too low?) again - if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("BLTouch Recovery Failed"); - - SERIAL_ERROR_MSG(STR_STOP_BLTOUCH); // Tell the user something is wrong, needs action - stop(); // but it's not too bad, no need to kill, allow restart - + if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("BLTouch Deploy Failed"); + probe.probe_error_stop(); // Something is wrong, needs action, but not too bad, allow restart return true; // Tell our caller we goofed in case he cares to know } } @@ -156,12 +147,8 @@ bool BLTouch::stow_proc() { // But one more STOW will catch that // Last attempt to STOW if (_stow_query_alarm()) { // so if there is now STILL an ALARM condition: - - if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("BLTouch Recovery Failed"); - - SERIAL_ERROR_MSG(STR_STOP_BLTOUCH); // Tell the user something is wrong, needs action - stop(); // but it's not too bad, no need to kill, allow restart - + if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("BLTouch Stow Failed"); + probe.probe_error_stop(); // Something is wrong, needs action, but not too bad, allow restart return true; // Tell our caller we goofed in case he cares to know } } @@ -182,7 +169,7 @@ bool BLTouch::status_proc() { _set_SW_mode(); // Incidentally, _set_SW_mode() will also RESET any active alarm const bool tr = triggered(); // If triggered in SW mode, the pin is up, it is STOWED - if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPAIR("BLTouch is ", (int)tr); + if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("BLTouch is ", tr); if (tr) _stow(); else _deploy(); // Turn off SW mode, reset any trigger, honor pin state return !tr; @@ -194,13 +181,13 @@ void BLTouch::mode_conv_proc(const bool M5V) { * BLTOUCH V3.0: This will set the mode (twice) and sadly, a STOW is needed at the end, because of the deploy * BLTOUCH V3.1: This will set the mode and store it in the eeprom. The STOW is not needed but does not hurt */ - if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPAIR("BLTouch Set Mode - ", (int)M5V); + if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("BLTouch Set Mode - ", M5V); _deploy(); if (M5V) _set_5V_mode(); else _set_OD_mode(); _mode_store(); if (M5V) _set_5V_mode(); else _set_OD_mode(); _stow(); - last_written_mode = M5V; + od_5v_mode = M5V; } #endif // BLTOUCH diff --git a/Marlin/src/feature/bltouch.h b/Marlin/src/feature/bltouch.h index 40685af1b3..1371ec9f46 100644 --- a/Marlin/src/feature/bltouch.h +++ b/Marlin/src/feature/bltouch.h @@ -26,16 +26,10 @@ // BLTouch commands are sent as servo angles typedef unsigned char BLTCommand; -#if ENABLED(CREALITY_TOUCH) - #define STOW_ALARM false - #define BLTOUCH_DEPLOY 170 - #define BLTOUCH_STOW 20 -#else - #define STOW_ALARM true - #define BLTOUCH_DEPLOY 10 - #define BLTOUCH_STOW 90 -#endif - +#define DEPLOY_ALARM true +#define STOW_ALARM true +#define BLTOUCH_DEPLOY 10 +#define BLTOUCH_STOW 90 #define BLTOUCH_SW_MODE 60 #define BLTOUCH_SELFTEST 120 #define BLTOUCH_MODE_STORE 130 @@ -73,37 +67,46 @@ typedef unsigned char BLTCommand; class BLTouch { public: + static void init(const bool set_voltage=false); - static bool last_written_mode; // Initialized by settings.load, 0 = Open Drain; 1 = 5V Drain + static bool od_5v_mode; // Initialized by settings.load, 0 = Open Drain; 1 = 5V Drain + + #if HAS_BLTOUCH_HS_MODE + static bool high_speed_mode; // Initialized by settings.load, 0 = Low Speed; 1 = High Speed + #else + static constexpr bool high_speed_mode = false; + #endif + + static float z_extra_clearance() { return TERN0(HAS_BLTOUCH_HS_MODE, high_speed_mode ? BLTOUCH_HS_EXTRA_CLEARANCE : 0); } // DEPLOY and STOW are wrapped for error handling - these are used by homing and by probing - FORCE_INLINE static bool deploy() { return deploy_proc(); } - FORCE_INLINE static bool stow() { return stow_proc(); } - FORCE_INLINE static bool status() { return status_proc(); } + static bool deploy() { return deploy_proc(); } + static bool stow() { return stow_proc(); } + static bool status() { return status_proc(); } // Native BLTouch commands ("Underscore"...), used in lcd menus and internally - FORCE_INLINE static void _reset() { command(BLTOUCH_RESET, BLTOUCH_RESET_DELAY); } + static void _reset() { command(BLTOUCH_RESET, BLTOUCH_RESET_DELAY); } - FORCE_INLINE static void _selftest() { command(BLTOUCH_SELFTEST, BLTOUCH_DELAY); } + static void _selftest() { command(BLTOUCH_SELFTEST, BLTOUCH_DELAY); } - FORCE_INLINE static void _set_SW_mode() { command(BLTOUCH_SW_MODE, BLTOUCH_DELAY); } - FORCE_INLINE static void _reset_SW_mode() { if (triggered()) _stow(); else _deploy(); } + static void _set_SW_mode() { command(BLTOUCH_SW_MODE, BLTOUCH_DELAY); } + static void _reset_SW_mode() { if (triggered()) _stow(); else _deploy(); } - FORCE_INLINE static void _set_5V_mode() { command(BLTOUCH_5V_MODE, BLTOUCH_SET5V_DELAY); } - FORCE_INLINE static void _set_OD_mode() { command(BLTOUCH_OD_MODE, BLTOUCH_SETOD_DELAY); } - FORCE_INLINE static void _mode_store() { command(BLTOUCH_MODE_STORE, BLTOUCH_MODE_STORE_DELAY); } + static void _set_5V_mode() { command(BLTOUCH_5V_MODE, BLTOUCH_SET5V_DELAY); } + static void _set_OD_mode() { command(BLTOUCH_OD_MODE, BLTOUCH_SETOD_DELAY); } + static void _mode_store() { command(BLTOUCH_MODE_STORE, BLTOUCH_MODE_STORE_DELAY); } - FORCE_INLINE static void _deploy() { command(BLTOUCH_DEPLOY, BLTOUCH_DEPLOY_DELAY); } - FORCE_INLINE static void _stow() { command(BLTOUCH_STOW, BLTOUCH_STOW_DELAY); } + static void _deploy() { command(BLTOUCH_DEPLOY, BLTOUCH_DEPLOY_DELAY); } + static void _stow() { command(BLTOUCH_STOW, BLTOUCH_STOW_DELAY); } - FORCE_INLINE static void mode_conv_5V() { mode_conv_proc(true); } - FORCE_INLINE static void mode_conv_OD() { mode_conv_proc(false); } + static void mode_conv_5V() { mode_conv_proc(true); } + static void mode_conv_OD() { mode_conv_proc(false); } static bool triggered(); private: - FORCE_INLINE static bool _deploy_query_alarm() { return command(BLTOUCH_DEPLOY, BLTOUCH_DEPLOY_DELAY); } - FORCE_INLINE static bool _stow_query_alarm() { return command(BLTOUCH_STOW, BLTOUCH_STOW_DELAY) == STOW_ALARM; } + 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(); static bool command(const BLTCommand cmd, const millis_t &ms); diff --git a/Marlin/src/feature/cancel_object.cpp b/Marlin/src/feature/cancel_object.cpp index 16f876f136..c17c9988e4 100644 --- a/Marlin/src/feature/cancel_object.cpp +++ b/Marlin/src/feature/cancel_object.cpp @@ -19,33 +19,31 @@ * along with this program. If not, see . * */ + #include "../inc/MarlinConfig.h" #if ENABLED(CANCEL_OBJECTS) #include "cancel_object.h" #include "../gcode/gcode.h" -#include "../lcd/ultralcd.h" +#include "../lcd/marlinui.h" 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 HAS_DISPLAY - if (active_object >= 0) - ui.status_printf_P(0, PSTR(S_FMT " %i"), GET_TEXT(MSG_PRINTING_OBJECT), int(active_object)); + #if ALL(HAS_STATUS_MESSAGE, CANCEL_OBJECTS_REPORTING) + if (state.active_object >= 0) + ui.set_status(MString<30>(GET_TEXT_F(MSG_PRINTING_OBJECT), ' ', state.active_object)); else ui.reset_status(); #endif @@ -53,31 +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_START(); - SERIAL_ECHOLNPAIR("Active Object: ", int(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 1d2d77f203..7f04612d13 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 inline bool is_canceled(const int8_t obj) { return TEST(canceled, obj); } - static inline void clear_active_object() { set_active_object(-1); } - static inline void cancel_active_object() { cancel_object(active_object); } - static inline void reset() { canceled = 0x0000; object_count = 0; clear_active_object(); } + 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(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 8a128bb12d..95221111b2 100644 --- a/Marlin/src/feature/caselight.cpp +++ b/Marlin/src/feature/caselight.cpp @@ -28,54 +28,58 @@ CaseLight caselight; -uint8_t CaseLight::brightness = CASE_LIGHT_DEFAULT_BRIGHTNESS; -bool CaseLight::on = CASE_LIGHT_DEFAULT_ON; - -#if ENABLED(CASE_LIGHT_USE_NEOPIXEL) - LEDColor CaseLight::color = - #ifdef CASE_LIGHT_NEOPIXEL_COLOR - CASE_LIGHT_NEOPIXEL_COLOR - #else - { 255, 255, 255, 255 } - #endif - ; +#if CASELIGHT_USES_BRIGHTNESS && !defined(CASE_LIGHT_DEFAULT_BRIGHTNESS) + #define CASE_LIGHT_DEFAULT_BRIGHTNESS 0 // For use on PWM pin as non-PWM just sets a default #endif -#ifndef INVERT_CASE_LIGHT - #define INVERT_CASE_LIGHT false +#if CASELIGHT_USES_BRIGHTNESS + uint8_t CaseLight::brightness = CASE_LIGHT_DEFAULT_BRIGHTNESS; +#endif + +bool CaseLight::on = CASE_LIGHT_DEFAULT_ON; + +#if CASE_LIGHT_IS_COLOR_LED + constexpr uint8_t init_case_light[] = CASE_LIGHT_DEFAULT_COLOR; + 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) { - /** - * The brightness_sav (and sflag) is needed because ARM chips ignore - * a "WRITE(CASE_LIGHT_PIN,x)" command to the pins that are directly - * controlled by the PWM module. In order to turn them off the brightness - * level needs to be set to OFF. Since we can't use the PWM register to - * save the last brightness level we need a variable to save it. - */ - static uint8_t brightness_sav; // Save brightness info for restore on "M355 S1" + #if CASELIGHT_USES_BRIGHTNESS + /** + * The brightness_sav (and sflag) is needed because ARM chips ignore + * a "WRITE(CASE_LIGHT_PIN,x)" command to the pins that are directly + * controlled by the PWM module. In order to turn them off the brightness + * level needs to be set to OFF. Since we can't use the PWM register to + * save the last brightness level we need a variable to save it. + */ + static uint8_t brightness_sav; // Save brightness info for restore on "M355 S1" - if (on || !sflag) - brightness_sav = brightness; // Save brightness except for M355 S0 - if (sflag && on) - brightness = brightness_sav; // Restore last brightness for M355 S1 + if (on || !sflag) + brightness_sav = brightness; // Save brightness except for M355 S0 + if (sflag && on) + brightness = brightness_sav; // Restore last brightness for M355 S1 - #if ENABLED(CASE_LIGHT_USE_NEOPIXEL) || DISABLED(CASE_LIGHT_NO_BRIGHTNESS) - const uint8_t i = on ? brightness : 0, n10ct = INVERT_CASE_LIGHT ? 255 - i : i; + const uint8_t i = on ? brightness : 0, n10ct = ENABLED(INVERT_CASE_LIGHT) ? 255 - i : i; + UNUSED(n10ct); #endif - #if ENABLED(CASE_LIGHT_USE_NEOPIXEL) + #if CASE_LIGHT_IS_COLOR_LED + #if ENABLED(CASE_LIGHT_USE_NEOPIXEL) + if (on) + // Use current color of (NeoPixel) leds and new brightness level + 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(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 - leds.set_color( - MakeLEDColor(color.r, color.g, color.b, color.w, n10ct), - false - ); - - #else // !CASE_LIGHT_USE_NEOPIXEL - - #if DISABLED(CASE_LIGHT_NO_BRIGHTNESS) - if (PWM_PIN(CASE_LIGHT_PIN)) - analogWrite(pin_t(CASE_LIGHT_PIN), ( + #if CASELIGHT_USES_BRIGHTNESS + if (pin_is_pwm()) + hal.set_pwm_duty(pin_t(CASE_LIGHT_PIN), ( #if CASE_LIGHT_MAX_PWM == 255 n10ct #else @@ -85,11 +89,15 @@ void CaseLight::update(const bool sflag) { else #endif { - const bool s = on ? !INVERT_CASE_LIGHT : INVERT_CASE_LIGHT; + const bool s = on ? TERN(INVERT_CASE_LIGHT, LOW, HIGH) : TERN(INVERT_CASE_LIGHT, HIGH, LOW); WRITE(CASE_LIGHT_PIN, s ? HIGH : LOW); } - #endif // !CASE_LIGHT_USE_NEOPIXEL + #endif // !CASE_LIGHT_IS_COLOR_LED + + #if ENABLED(CASE_LIGHT_USE_RGB_LED) + if (leds.lights_on) leds.update(); else leds.set_off(); + #endif } #endif // CASE_LIGHT_ENABLE diff --git a/Marlin/src/feature/caselight.h b/Marlin/src/feature/caselight.h index 9d1e76da26..28466cecaa 100644 --- a/Marlin/src/feature/caselight.h +++ b/Marlin/src/feature/caselight.h @@ -21,24 +21,36 @@ */ #pragma once -#include "../inc/MarlinConfigPre.h" +#include "../inc/MarlinConfig.h" -#if ENABLED(CASE_LIGHT_USE_NEOPIXEL) - #include "leds/leds.h" +#if CASE_LIGHT_IS_COLOR_LED + #include "leds/leds.h" // for LED1Color_t #endif class CaseLight { public: - static uint8_t brightness; static bool on; + #if CASELIGHT_USES_BRIGHTNESS + static uint8_t brightness; + #endif + + static bool pin_is_pwm() { return TERN0(NEED_CASE_LIGHT_PIN, PWM_PIN(CASE_LIGHT_PIN)); } + static bool has_brightness() { return TERN0(CASELIGHT_USES_BRIGHTNESS, TERN(CASE_LIGHT_USE_NEOPIXEL, true, pin_is_pwm())); } + + static void init() { + #if NEED_CASE_LIGHT_PIN + if (pin_is_pwm()) SET_PWM(CASE_LIGHT_PIN); else SET_OUTPUT(CASE_LIGHT_PIN); + #endif + update_brightness(); + } static void update(const bool sflag); - static inline void update_brightness() { update(false); } - static inline void update_enabled() { update(true); } + static void update_brightness() { update(false); } + static void update_enabled() { update(true); } -private: - #if ENABLED(CASE_LIGHT_USE_NEOPIXEL) - static LEDColor color; + #if ENABLED(CASE_LIGHT_IS_COLOR_LED) + private: + static LED1Color_t color; #endif }; diff --git a/Marlin/src/feature/closedloop.cpp b/Marlin/src/feature/closedloop.cpp index 8a97f0c0cd..1b9f711a6b 100644 --- a/Marlin/src/feature/closedloop.cpp +++ b/Marlin/src/feature/closedloop.cpp @@ -19,6 +19,7 @@ * along with this program. If not, see . * */ + #include "../inc/MarlinConfig.h" #if ENABLED(EXTERNAL_CLOSED_LOOP_CONTROLLER) diff --git a/Marlin/src/feature/controllerfan.cpp b/Marlin/src/feature/controllerfan.cpp index fa5a86b019..0636402136 100644 --- a/Marlin/src/feature/controllerfan.cpp +++ b/Marlin/src/feature/controllerfan.cpp @@ -25,7 +25,7 @@ #if ENABLED(USE_CONTROLLER_FAN) #include "controllerfan.h" -#include "../module/stepper/indirection.h" +#include "../module/stepper.h" #include "../module/temperature.h" ControllerFan controllerFan; @@ -34,10 +34,40 @@ uint8_t ControllerFan::speed; #if ENABLED(CONTROLLER_FAN_EDITABLE) controllerFan_settings_t ControllerFan::settings; // {0} + #else + const controllerFan_settings_t &ControllerFan::settings = controllerFan_defaults; +#endif + +#if ENABLED(FAN_SOFT_PWM) + uint8_t ControllerFan::soft_pwm_speed; #endif void ControllerFan::setup() { SET_OUTPUT(CONTROLLER_FAN_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(); } @@ -46,49 +76,92 @@ void ControllerFan::set_fan_speed(const uint8_t s) { } void ControllerFan::update() { - static millis_t lastMotorOn = 0, // Last time a motor was turned on - nextMotorCheck = 0; // Last time the state was checked + static millis_t lastComponentOn = 0, // Last time a stepper, heater, etc. was turned on + nextFanCheck = 0; // Last time the state was checked const millis_t ms = millis(); - if (ELAPSED(ms, nextMotorCheck)) { - nextMotorCheck = ms + 2500UL; // Not a time critical function, so only check every 2.5s + if (ELAPSED(ms, nextFanCheck)) { + nextFanCheck = ms + 2500UL; // Not a time critical function, so only check every 2.5s - #define MOTOR_IS_ON(A,B) (A##_ENABLE_READ() == bool(B##_ENABLE_ON)) - #define _OR_ENABLED_E(N) || MOTOR_IS_ON(E##N,E) + /** + * If any triggers for the controller fan are true... + * - At least one stepper driver is enabled + * - The heated bed (MOSFET) is enabled + * - TEMP_SENSOR_BOARD is reporting >= CONTROLLER_FAN_MIN_BOARD_TEMP + * - TEMP_SENSOR_SOC is reporting >= CONTROLLER_FAN_MIN_SOC_TEMP + */ + 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) + #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 + #ifdef CONTROLLER_FAN_MIN_SOC_TEMP + || thermalManager.wholeDegSoc() >= CONTROLLER_FAN_MIN_SOC_TEMP + #endif + ) lastComponentOn = ms; //... set time to NOW so the fan will turn on - const bool motor_on = ( - ( DISABLED(CONTROLLER_FAN_IGNORE_Z) && - ( MOTOR_IS_ON(Z,Z) - || TERN0(HAS_Z2_ENABLE, MOTOR_IS_ON(Z2,Z)) - || TERN0(HAS_Z3_ENABLE, MOTOR_IS_ON(Z3,Z)) - || TERN0(HAS_Z4_ENABLE, MOTOR_IS_ON(Z4,Z)) - ) - ) || ( - DISABLED(CONTROLLER_FAN_USE_Z_ONLY) && - ( MOTOR_IS_ON(X,X) || MOTOR_IS_ON(Y,Y) - || TERN0(HAS_X2_ENABLE, MOTOR_IS_ON(X2,X)) - || TERN0(HAS_Y2_ENABLE, MOTOR_IS_ON(Y2,Y)) - #if E_STEPPERS - REPEAT(E_STEPPERS, _OR_ENABLED_E) - #endif - ) - ) - ); - - // If any of the drivers or the heated bed are enabled... - if (motor_on || TERN0(HAS_HEATED_BED, thermalManager.temp_bed.soft_pwm_amount > 0)) - lastMotorOn = ms; //... set time to NOW so the fan will turn on - - // Fan Settings. Set fan > 0: - // - If AutoMode is on and steppers have been enabled for CONTROLLERFAN_IDLE_TIME seconds. - // - If System is on idle and idle fan speed settings is activated. + /** + * Fan Settings. Set fan > 0: + * - If AutoMode is on and hot components have been powered for CONTROLLERFAN_IDLE_TIME seconds. + * - If System is on idle and idle fan speed settings is activated. + */ set_fan_speed( - settings.auto_mode && lastMotorOn && PENDING(ms, lastMotorOn + SEC_TO_MS(settings.duration)) + settings.auto_mode && lastComponentOn && PENDING(ms, lastComponentOn, SEC_TO_MS(settings.duration)) ? settings.active_speed : settings.idle_speed ); - // Allow digital or PWM fan output (see M42 handling) - WRITE(CONTROLLER_FAN_PIN, speed); - analogWrite(pin_t(CONTROLLER_FAN_PIN), speed); + speed = CALC_FAN_SPEED(speed); + + #if FAN_KICKSTART_TIME + static millis_t fan_kick_end = 0; + if (speed > FAN_OFF_PWM) { + if (!fan_kick_end) { + fan_kick_end = ms + FAN_KICKSTART_TIME; // May be longer based on slow update interval for controller fn check. Sets minimum + speed = FAN_KICKSTART_POWER; + } + else if (PENDING(ms, fan_kick_end)) + speed = FAN_KICKSTART_POWER; + } + else + 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 >> 1; // Controller Fan Soft PWM uses 0-127 as 0-100% so cut the 0-255 range in half. + #else + 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/controllerfan.h b/Marlin/src/feature/controllerfan.h index d1d39f21f3..68502afa66 100644 --- a/Marlin/src/feature/controllerfan.h +++ b/Marlin/src/feature/controllerfan.h @@ -58,11 +58,14 @@ class ControllerFan { #if ENABLED(CONTROLLER_FAN_EDITABLE) static controllerFan_settings_t settings; #else - static const controllerFan_settings_t constexpr &settings = controllerFan_defaults; + static const controllerFan_settings_t &settings; #endif - static inline bool state() { return speed > 0; } - static inline void init() { reset(); } - static inline void reset() { TERN_(CONTROLLER_FAN_EDITABLE, settings = controllerFan_defaults); } + #if ENABLED(FAN_SOFT_PWM) + static uint8_t soft_pwm_speed; + #endif + static bool state() { return speed > 0; } + static void init() { reset(); } + static void reset() { TERN_(CONTROLLER_FAN_EDITABLE, settings = controllerFan_defaults); } static void setup(); static void update(); }; diff --git a/Marlin/src/feature/cooler.cpp b/Marlin/src/feature/cooler.cpp new file mode 100644 index 0000000000..6c45e99226 --- /dev/null +++ b/Marlin/src/feature/cooler.cpp @@ -0,0 +1,48 @@ +/** + * 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 . + * + */ + +#include "../inc/MarlinConfig.h" + +#if ANY(HAS_COOLER, LASER_COOLANT_FLOW_METER) + +#include "cooler.h" +Cooler cooler; + +#if HAS_COOLER + uint8_t Cooler::mode = 0; + uint16_t Cooler::capacity; + uint16_t Cooler::load; + bool Cooler::enabled = false; +#endif + +#if ENABLED(LASER_COOLANT_FLOW_METER) + bool Cooler::flowmeter = false; + millis_t Cooler::flowmeter_next_ms; // = 0 + volatile uint16_t Cooler::flowpulses; + float Cooler::flowrate; + #if ENABLED(FLOWMETER_SAFETY) + bool Cooler::flowsafety_enabled = true; + bool Cooler::flowfault = false; + #endif +#endif + +#endif // HAS_COOLER || LASER_COOLANT_FLOW_METER diff --git a/Marlin/src/feature/cooler.h b/Marlin/src/feature/cooler.h new file mode 100644 index 0000000000..ef3590c593 --- /dev/null +++ b/Marlin/src/feature/cooler.h @@ -0,0 +1,109 @@ +/** + * 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 + +#include "../inc/MarlinConfigPre.h" + +#ifndef FLOWMETER_PPL + #define FLOWMETER_PPL 5880 // Pulses per liter +#endif +#ifndef FLOWMETER_INTERVAL + #define FLOWMETER_INTERVAL 1000 // milliseconds +#endif + +// Cooling device + +class Cooler { +public: + static uint16_t capacity; // Cooling capacity in watts + static uint16_t load; // Cooling load in watts + + static bool enabled; + static void enable() { enabled = true; } + static void disable() { enabled = false; } + 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; } + + #if ENABLED(LASER_COOLANT_FLOW_METER) + static float flowrate; // Flow meter reading in liters-per-minute. + static bool flowmeter; // Flag to monitor the flow + static volatile uint16_t flowpulses; // Flowmeter IRQ pulse count + static millis_t flowmeter_next_ms; // Next time at which to calculate flow + + static void set_flowmeter(const bool sflag) { + if (flowmeter != sflag) { + flowmeter = sflag; + if (sflag) { + flowpulses = 0; + flowmeter_next_ms = millis() + FLOWMETER_INTERVAL; + } + } + } + + // To calculate flow we only need to count pulses + static void flowmeter_ISR() { flowpulses++; } + + // Enable / Disable the flow meter interrupt + static void flowmeter_interrupt_enable() { + attachInterrupt(digitalPinToInterrupt(FLOWMETER_PIN), flowmeter_ISR, RISING); + } + static void flowmeter_interrupt_disable() { + detachInterrupt(digitalPinToInterrupt(FLOWMETER_PIN)); + } + + // Enable / Disable the flow meter interrupt + static void flowmeter_enable() { set_flowmeter(true); flowpulses = 0; flowmeter_interrupt_enable(); } + static void flowmeter_disable() { set_flowmeter(false); flowmeter_interrupt_disable(); flowpulses = 0; } + + // Get the total flow (in liters per minute) since the last reading + static void calc_flowrate() { + // flowrate = (litres) * (seconds) = litres per minute + flowrate = (flowpulses / (float)FLOWMETER_PPL) * ((1000.0f / (float)FLOWMETER_INTERVAL) * 60.0f); + flowpulses = 0; + } + + // Userland task to update the flow meter + static void flowmeter_task(const millis_t ms=millis()) { + if (!flowmeter) // !! The flow meter must always be on !! + flowmeter_enable(); // Init and prime + if (ELAPSED(ms, flowmeter_next_ms)) { + calc_flowrate(); + flowmeter_next_ms = ms + FLOWMETER_INTERVAL; + } + } + + #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() { FLIP(flowsafety_enabled); } + static bool check_flow_too_low() { + const bool too_low = flowsafety_enabled && flowrate < (FLOWMETER_MIN_LITERS_PER_MINUTE); + flowfault = too_low; + return too_low; + } + #endif + #endif +}; + +extern Cooler cooler; diff --git a/Marlin/src/feature/dac/dac_dac084s085.cpp b/Marlin/src/feature/dac/dac_dac084s085.cpp index 82d17fa28f..1338e5979d 100644 --- a/Marlin/src/feature/dac/dac_dac084s085.cpp +++ b/Marlin/src/feature/dac/dac_dac084s085.cpp @@ -10,8 +10,6 @@ #include "dac_dac084s085.h" -#include "../../MarlinCore.h" -#include "../../module/stepper.h" #include "../../HAL/shared/Delay.h" dac084s085::dac084s085() { } @@ -20,35 +18,35 @@ void dac084s085::begin() { uint8_t externalDac_buf[] = { 0x20, 0x00 }; // all off // All SPI chip-select HIGH - SET_OUTPUT(DAC0_SYNC); + SET_OUTPUT(DAC0_SYNC_PIN); #if HAS_MULTI_EXTRUDER - SET_OUTPUT(DAC1_SYNC); + SET_OUTPUT(DAC1_SYNC_PIN); #endif cshigh(); spiBegin(); //init onboard DAC DELAY_US(2); - WRITE(DAC0_SYNC, LOW); + WRITE(DAC0_SYNC_PIN, LOW); DELAY_US(2); - WRITE(DAC0_SYNC, HIGH); + WRITE(DAC0_SYNC_PIN, HIGH); DELAY_US(2); - WRITE(DAC0_SYNC, LOW); + WRITE(DAC0_SYNC_PIN, LOW); spiSend(SPI_CHAN_DAC, externalDac_buf, COUNT(externalDac_buf)); - WRITE(DAC0_SYNC, HIGH); + WRITE(DAC0_SYNC_PIN, HIGH); #if HAS_MULTI_EXTRUDER //init Piggy DAC DELAY_US(2); - WRITE(DAC1_SYNC, LOW); + WRITE(DAC1_SYNC_PIN, LOW); DELAY_US(2); - WRITE(DAC1_SYNC, HIGH); + WRITE(DAC1_SYNC_PIN, HIGH); DELAY_US(2); - WRITE(DAC1_SYNC, LOW); + WRITE(DAC1_SYNC_PIN, LOW); spiSend(SPI_CHAN_DAC, externalDac_buf, COUNT(externalDac_buf)); - WRITE(DAC1_SYNC, HIGH); + WRITE(DAC1_SYNC_PIN, HIGH); #endif return; @@ -66,18 +64,18 @@ void dac084s085::setValue(const uint8_t channel, const uint8_t value) { cshigh(); if (channel > 3) { // DAC Piggy E1,E2,E3 - WRITE(DAC1_SYNC, LOW); + WRITE(DAC1_SYNC_PIN, LOW); DELAY_US(2); - WRITE(DAC1_SYNC, HIGH); + WRITE(DAC1_SYNC_PIN, HIGH); DELAY_US(2); - WRITE(DAC1_SYNC, LOW); + WRITE(DAC1_SYNC_PIN, LOW); } else { // DAC onboard X,Y,Z,E0 - WRITE(DAC0_SYNC, LOW); + WRITE(DAC0_SYNC_PIN, LOW); DELAY_US(2); - WRITE(DAC0_SYNC, HIGH); + WRITE(DAC0_SYNC_PIN, HIGH); DELAY_US(2); - WRITE(DAC0_SYNC, LOW); + WRITE(DAC0_SYNC_PIN, LOW); } DELAY_US(2); @@ -85,14 +83,14 @@ void dac084s085::setValue(const uint8_t channel, const uint8_t value) { } void dac084s085::cshigh() { - WRITE(DAC0_SYNC, HIGH); + WRITE(DAC0_SYNC_PIN, HIGH); #if HAS_MULTI_EXTRUDER - WRITE(DAC1_SYNC, HIGH); + WRITE(DAC1_SYNC_PIN, HIGH); #endif - WRITE(SPI_EEPROM1_CS, HIGH); - WRITE(SPI_EEPROM2_CS, HIGH); - WRITE(SPI_FLASH_CS, HIGH); - WRITE(SS_PIN, HIGH); + WRITE(SPI_EEPROM1_CS_PIN, HIGH); + WRITE(SPI_EEPROM2_CS_PIN, HIGH); + WRITE(SPI_FLASH_CS_PIN, HIGH); + WRITE(SD_SS_PIN, HIGH); } #endif // MB(ALLIGATOR) diff --git a/Marlin/src/feature/dac/dac_mcp4728.cpp b/Marlin/src/feature/dac/dac_mcp4728.cpp index 81c465cf29..6f5a9ee691 100644 --- a/Marlin/src/feature/dac/dac_mcp4728.cpp +++ b/Marlin/src/feature/dac/dac_mcp4728.cpp @@ -32,7 +32,7 @@ #include "../../inc/MarlinConfig.h" -#if ENABLED(HAS_MOTOR_CURRENT_DAC) +#if HAS_MOTOR_CURRENT_DAC #include "dac_mcp4728.h" @@ -66,14 +66,14 @@ uint8_t MCP4728::analogWrite(const uint8_t channel, const uint16_t value) { } /** - * Write all input resistor values to EEPROM using SequencialWrite method. + * Write all input resistor values to EEPROM using SequentialWrite method. * This will update both input register and EEPROM value * This will also write current Vref, PowerDown, Gain settings to EEPROM */ uint8_t MCP4728::eepromWrite() { Wire.beginTransmission(I2C_ADDRESS(DAC_DEV_ADDRESS)); Wire.write(SEQWRITE); - LOOP_XYZE(i) { + LOOP_LOGICAL_AXES(i) { Wire.write(DAC_STEPPER_VREF << 7 | DAC_STEPPER_GAIN << 4 | highByte(dac_values[i])); Wire.write(lowByte(dac_values[i])); } @@ -81,7 +81,7 @@ uint8_t MCP4728::eepromWrite() { } /** - * Write Voltage reference setting to all input regiters + * Write Voltage reference setting to all input registers */ uint8_t MCP4728::setVref_all(const uint8_t value) { Wire.beginTransmission(I2C_ADDRESS(DAC_DEV_ADDRESS)); @@ -89,7 +89,7 @@ uint8_t MCP4728::setVref_all(const uint8_t value) { return Wire.endTransmission(); } /** - * Write Gain setting to all input regiters + * Write Gain setting to all input registers */ uint8_t MCP4728::setGain_all(const uint8_t value) { Wire.beginTransmission(I2C_ADDRESS(DAC_DEV_ADDRESS)); @@ -123,19 +123,19 @@ uint8_t MCP4728::getDrvPct(const uint8_t channel) { return uint8_t(100.0 * dac_v * Receives all Drive strengths as 0-100 percent values, updates * DAC Values array and calls fastwrite to update the DAC. */ -void MCP4728::setDrvPct(xyze_uint8_t &pct) { - dac_values *= 0.01 * pct * (DAC_STEPPER_MAX); +void MCP4728::setDrvPct(xyze_uint_t &pct) { + dac_values = pct * (DAC_STEPPER_MAX) * 0.01f; fastWrite(); } /** - * FastWrite input register values - All DAC ouput update. refer to DATASHEET 5.6.1 + * FastWrite input register values - All DAC output update. refer to DATASHEET 5.6.1 * DAC Input and PowerDown bits update. * No EEPROM update */ uint8_t MCP4728::fastWrite() { Wire.beginTransmission(I2C_ADDRESS(DAC_DEV_ADDRESS)); - LOOP_XYZE(i) { + LOOP_LOGICAL_AXES(i) { Wire.write(highByte(dac_values[i])); Wire.write(lowByte(dac_values[i])); } diff --git a/Marlin/src/feature/dac/dac_mcp4728.h b/Marlin/src/feature/dac/dac_mcp4728.h index 571716d483..3a7d5f10f6 100644 --- a/Marlin/src/feature/dac/dac_mcp4728.h +++ b/Marlin/src/feature/dac/dac_mcp4728.h @@ -76,7 +76,7 @@ public: static uint8_t fastWrite(); static uint8_t simpleCommand(const byte simpleCommand); static uint8_t getDrvPct(const uint8_t channel); - static void setDrvPct(xyze_uint8_t &pct); + static void setDrvPct(xyze_uint_t &pct); }; extern MCP4728 mcp4728; diff --git a/Marlin/src/feature/dac/stepper_dac.cpp b/Marlin/src/feature/dac/stepper_dac.cpp index 5f10b4ccfb..65423d3189 100644 --- a/Marlin/src/feature/dac/stepper_dac.cpp +++ b/Marlin/src/feature/dac/stepper_dac.cpp @@ -26,14 +26,13 @@ #include "../../inc/MarlinConfig.h" -#if ENABLED(HAS_MOTOR_CURRENT_DAC) +#if HAS_MOTOR_CURRENT_DAC #include "stepper_dac.h" -#include "../../MarlinCore.h" // for SP_X_LBL... bool dac_present = false; constexpr xyze_uint8_t dac_order = DAC_STEPPER_ORDER; -xyze_uint8_t dac_channel_pct = DAC_MOTOR_CURRENT_DEFAULT; +xyze_uint_t dac_channel_pct = DAC_MOTOR_CURRENT_DEFAULT; StepperDAC stepper_dac; @@ -51,7 +50,7 @@ int StepperDAC::init() { mcp4728.setVref_all(DAC_STEPPER_VREF); mcp4728.setGain_all(DAC_STEPPER_GAIN); - if (mcp4728.getDrvPct(0) < 1 || mcp4728.getDrvPct(1) < 1 || mcp4728.getDrvPct(2) < 1 || mcp4728.getDrvPct(3) < 1 ) { + if (mcp4728.getDrvPct(0) < 1 || mcp4728.getDrvPct(1) < 1 || mcp4728.getDrvPct(2) < 1 || mcp4728.getDrvPct(3) < 1) { mcp4728.setDrvPct(dac_channel_pct); mcp4728.eepromWrite(); } @@ -60,7 +59,7 @@ int StepperDAC::init() { } void StepperDAC::set_current_value(const uint8_t channel, uint16_t val) { - if (!dac_present) return; + if (!(dac_present && channel < LOGICAL_AXES)) return; NOMORE(val, uint16_t(DAC_STEPPER_MAX)); @@ -69,15 +68,15 @@ 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 100.0 * mcp4728.getValue(dac_order[n]) * RECIPROCAL(DAC_STEPPER_MAX); } -static float dac_amps(int8_t n) { return mcp4728.getDrvPct(dac_order[n]) * (DAC_STEPPER_MAX) * 0.125 * RECIPROCAL(DAC_STEPPER_SENSE); } +static float dac_perc(int8_t n) { return mcp4728.getDrvPct(dac_order[n]); } +static float dac_amps(int8_t n) { return mcp4728.getValue(dac_order[n]) * 0.125 * RECIPROCAL(DAC_STEPPER_SENSE * 1000); } uint8_t StepperDAC::get_current_percent(const AxisEnum axis) { return mcp4728.getDrvPct(dac_order[axis]); } void StepperDAC::set_current_percents(xyze_uint8_t &pct) { - LOOP_XYZE(i) dac_channel_pct[i] = pct[dac_order[i]]; + LOOP_LOGICAL_AXES(i) dac_channel_pct[i] = pct[dac_order[i]]; mcp4728.setDrvPct(dac_channel_pct); } @@ -85,10 +84,14 @@ void StepperDAC::print_values() { if (!dac_present) return; SERIAL_ECHO_MSG("Stepper current values in % (Amps):"); SERIAL_ECHO_START(); - SERIAL_ECHOPAIR_P( SP_X_LBL, dac_perc(X_AXIS), PSTR(" ("), dac_amps(X_AXIS), PSTR(")")); - SERIAL_ECHOPAIR_P( SP_Y_LBL, dac_perc(Y_AXIS), PSTR(" ("), dac_amps(Y_AXIS), PSTR(")")); - SERIAL_ECHOPAIR_P( SP_Z_LBL, dac_perc(Z_AXIS), PSTR(" ("), dac_amps(Z_AXIS), PSTR(")")); - SERIAL_ECHOLNPAIR_P(SP_E_LBL, dac_perc(E_AXIS), PSTR(" ("), dac_amps(E_AXIS), PSTR(")")); + LOOP_LOGICAL_AXES(a) { + SERIAL_CHAR(' ', IAXIS_CHAR(a), ':'); + SERIAL_ECHO(dac_perc(a)); + 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(")")); + #endif } void StepperDAC::commit_eeprom() { diff --git a/Marlin/src/feature/dac/stepper_dac.h b/Marlin/src/feature/dac/stepper_dac.h index 6836335e98..26a0f2f95c 100644 --- a/Marlin/src/feature/dac/stepper_dac.h +++ b/Marlin/src/feature/dac/stepper_dac.h @@ -34,7 +34,7 @@ public: static void set_current_value(const uint8_t channel, uint16_t val); static void print_values(); static void commit_eeprom(); - static uint8_t get_current_percent(AxisEnum axis); + static uint8_t get_current_percent(const AxisEnum axis); static void set_current_percents(xyze_uint8_t &pct); }; diff --git a/Marlin/src/feature/digipot/digipot.h b/Marlin/src/feature/digipot/digipot.h index c53f8093dd..3fbd1f3662 100644 --- a/Marlin/src/feature/digipot/digipot.h +++ b/Marlin/src/feature/digipot/digipot.h @@ -30,4 +30,4 @@ public: static void set_current(const uint8_t channel, const float current); }; -DigipotI2C digipot_i2c; +extern DigipotI2C digipot_i2c; diff --git a/Marlin/src/feature/digipot/digipot_mcp4018.cpp b/Marlin/src/feature/digipot/digipot_mcp4018.cpp index 6260185fc3..48d7ff492c 100644 --- a/Marlin/src/feature/digipot/digipot_mcp4018.cpp +++ b/Marlin/src/feature/digipot/digipot_mcp4018.cpp @@ -27,12 +27,16 @@ #include "digipot.h" #include -#include // https://github.com/stawel/SlowSoftI2CMaster +#include // https://github.com/felias-fogg/SlowSoftI2CMaster // Settings for the I2C based DIGIPOT (MCP4018) based on WT150 -#define DIGIPOT_A4988_Rsx 0.250 -#define DIGIPOT_A4988_Vrefmax 1.666 +#ifndef DIGIPOT_A4988_Rsx + #define DIGIPOT_A4988_Rsx 0.250 +#endif +#ifndef DIGIPOT_A4988_Vrefmax + #define DIGIPOT_A4988_Vrefmax 1.666 +#endif #define DIGIPOT_MCP4018_MAX_VALUE 127 #define DIGIPOT_A4988_Itripmax(Vref) ((Vref) / (8.0 * DIGIPOT_A4988_Rsx)) @@ -46,21 +50,21 @@ static byte current_to_wiper(const float current) { } static SlowSoftI2CMaster pots[DIGIPOT_I2C_NUM_CHANNELS] = { - SlowSoftI2CMaster(DIGIPOTS_I2C_SDA_X, DIGIPOTS_I2C_SCL) + SlowSoftI2CMaster(DIGIPOTS_I2C_SDA_X, DIGIPOTS_I2C_SCL, ENABLED(DIGIPOT_ENABLE_I2C_PULLUPS)) #if DIGIPOT_I2C_NUM_CHANNELS > 1 - , SlowSoftI2CMaster(DIGIPOTS_I2C_SDA_Y, DIGIPOTS_I2C_SCL) + , SlowSoftI2CMaster(DIGIPOTS_I2C_SDA_Y, DIGIPOTS_I2C_SCL, ENABLED(DIGIPOT_ENABLE_I2C_PULLUPS)) #if DIGIPOT_I2C_NUM_CHANNELS > 2 - , SlowSoftI2CMaster(DIGIPOTS_I2C_SDA_Z, DIGIPOTS_I2C_SCL) + , SlowSoftI2CMaster(DIGIPOTS_I2C_SDA_Z, DIGIPOTS_I2C_SCL, ENABLED(DIGIPOT_ENABLE_I2C_PULLUPS)) #if DIGIPOT_I2C_NUM_CHANNELS > 3 - , SlowSoftI2CMaster(DIGIPOTS_I2C_SDA_E0, DIGIPOTS_I2C_SCL) + , SlowSoftI2CMaster(DIGIPOTS_I2C_SDA_E0, DIGIPOTS_I2C_SCL, ENABLED(DIGIPOT_ENABLE_I2C_PULLUPS)) #if DIGIPOT_I2C_NUM_CHANNELS > 4 - , SlowSoftI2CMaster(DIGIPOTS_I2C_SDA_E1, DIGIPOTS_I2C_SCL) + , SlowSoftI2CMaster(DIGIPOTS_I2C_SDA_E1, DIGIPOTS_I2C_SCL, ENABLED(DIGIPOT_ENABLE_I2C_PULLUPS)) #if DIGIPOT_I2C_NUM_CHANNELS > 5 - , SlowSoftI2CMaster(DIGIPOTS_I2C_SDA_E2, DIGIPOTS_I2C_SCL) + , SlowSoftI2CMaster(DIGIPOTS_I2C_SDA_E2, DIGIPOTS_I2C_SCL, ENABLED(DIGIPOT_ENABLE_I2C_PULLUPS)) #if DIGIPOT_I2C_NUM_CHANNELS > 6 - , SlowSoftI2CMaster(DIGIPOTS_I2C_SDA_E3, DIGIPOTS_I2C_SCL) + , SlowSoftI2CMaster(DIGIPOTS_I2C_SDA_E3, DIGIPOTS_I2C_SCL, ENABLED(DIGIPOT_ENABLE_I2C_PULLUPS)) #if DIGIPOT_I2C_NUM_CHANNELS > 7 - , SlowSoftI2CMaster(DIGIPOTS_I2C_SDA_E4, DIGIPOTS_I2C_SCL) + , SlowSoftI2CMaster(DIGIPOTS_I2C_SDA_E4, DIGIPOTS_I2C_SCL, ENABLED(DIGIPOT_ENABLE_I2C_PULLUPS)) #endif #endif #endif @@ -85,7 +89,7 @@ void DigipotI2C::set_current(const uint8_t channel, const float current) { } void DigipotI2C::init() { - LOOP_L_N(i, DIGIPOT_I2C_NUM_CHANNELS) pots[i].i2c_init(); + for (uint8_t i = 0; i < DIGIPOT_I2C_NUM_CHANNELS; ++i) pots[i].i2c_init(); // Init currents according to Configuration_adv.h static const float digipot_motor_current[] PROGMEM = @@ -95,8 +99,10 @@ void DigipotI2C::init() { DIGIPOT_I2C_MOTOR_CURRENTS #endif ; - LOOP_L_N(i, COUNT(digipot_motor_current)) + for (uint8_t i = 0; i < COUNT(digipot_motor_current); ++i) set_current(i, pgm_read_float(&digipot_motor_current[i])); } +DigipotI2C digipot_i2c; + #endif // DIGIPOT_MCP4018 diff --git a/Marlin/src/feature/digipot/digipot_mcp4451.cpp b/Marlin/src/feature/digipot/digipot_mcp4451.cpp index 7e6ace49a0..e35b42a28b 100644 --- a/Marlin/src/feature/digipot/digipot_mcp4451.cpp +++ b/Marlin/src/feature/digipot/digipot_mcp4451.cpp @@ -35,11 +35,14 @@ // Settings for the I2C based DIGIPOT (MCP4451) on Azteeg X3 Pro #if MB(5DPRINT) - #define DIGIPOT_I2C_FACTOR 117.96f - #define DIGIPOT_I2C_MAX_CURRENT 1.736f + #define DIGIPOT_I2C_FACTOR 117.96f + #define DIGIPOT_I2C_MAX_CURRENT 1.736f #elif MB(AZTEEG_X5_MINI, AZTEEG_X5_MINI_WIFI) #define DIGIPOT_I2C_FACTOR 113.5f #define DIGIPOT_I2C_MAX_CURRENT 2.0f +#elif MB(AZTEEG_X5_GT) + #define DIGIPOT_I2C_FACTOR 51.0f + #define DIGIPOT_I2C_MAX_CURRENT 3.0f #else #define DIGIPOT_I2C_FACTOR 106.7f #define DIGIPOT_I2C_MAX_CURRENT 2.5f @@ -91,8 +94,10 @@ void DigipotI2C::init() { DIGIPOT_I2C_MOTOR_CURRENTS #endif ; - LOOP_L_N(i, COUNT(digipot_motor_current)) + for (uint8_t i = 0; i < COUNT(digipot_motor_current); ++i) set_current(i, pgm_read_float(&digipot_motor_current[i])); } +DigipotI2C digipot_i2c; + #endif // DIGIPOT_MCP4451 diff --git a/Marlin/src/feature/direct_stepping.cpp b/Marlin/src/feature/direct_stepping.cpp index 9766d14640..08cda561b1 100644 --- a/Marlin/src/feature/direct_stepping.cpp +++ b/Marlin/src/feature/direct_stepping.cpp @@ -19,6 +19,7 @@ * along with this program. If not, see . * */ + #include "../inc/MarlinConfigPre.h" #if ENABLED(DIRECT_STEPPING) @@ -51,13 +52,13 @@ namespace DirectStepping { volatile bool SerialPageManager::fatal_error; template - volatile PageState SerialPageManager::page_states[Cfg::NUM_PAGES]; + volatile PageState SerialPageManager::page_states[Cfg::PAGE_COUNT]; template volatile bool SerialPageManager::page_states_dirty; template - uint8_t SerialPageManager::pages[Cfg::NUM_PAGES][Cfg::PAGE_SIZE]; + uint8_t SerialPageManager::pages[Cfg::PAGE_COUNT][Cfg::PAGE_SIZE]; template uint8_t SerialPageManager::checksum; @@ -73,7 +74,7 @@ namespace DirectStepping { template void SerialPageManager::init() { - for (int i = 0 ; i < Cfg::NUM_PAGES ; i++) + for (int i = 0 ; i < Cfg::PAGE_COUNT ; i++) page_states[i] = PageState::FREE; fatal_error = false; @@ -142,14 +143,16 @@ namespace DirectStepping { // special case for 8-bit, check if rolled back to 0 if (Cfg::DIRECTIONAL || !write_page_size) { // full 256 bytes if (write_byte_idx) return true; - } else { - if (write_byte_idx < write_page_size) return true; } - } else if (Cfg::DIRECTIONAL) { - if (write_byte_idx != Cfg::PAGE_SIZE) return true; - } else { - if (write_byte_idx < write_page_size) return true; + else if (write_byte_idx < write_page_size) + return true; } + else if (Cfg::DIRECTIONAL) { + if (write_byte_idx != Cfg::PAGE_SIZE) + return true; + } + else if (write_byte_idx < write_page_size) + return true; state = State::CHECKSUM; return true; @@ -160,11 +163,10 @@ namespace DirectStepping { return true; } case State::UNFAIL: - if (c == 0) { + if (c == 0) set_page_state(write_page_idx, PageState::FREE); - } else { + else fatal_error = true; - } state = State::MONITOR; return true; } @@ -173,29 +175,29 @@ namespace DirectStepping { template void SerialPageManager::write_responses() { if (fatal_error) { - kill(GET_TEXT(MSG_BAD_PAGE)); + marlin.kill(GET_TEXT_F(MSG_BAD_PAGE)); return; } if (!page_states_dirty) return; page_states_dirty = false; - SERIAL_ECHO(Cfg::CONTROL_CHAR); + SERIAL_CHAR(Cfg::CONTROL_CHAR); constexpr int state_bits = 2; - constexpr int n_bytes = Cfg::NUM_PAGES >> state_bits; + constexpr int n_bytes = Cfg::PAGE_COUNT >> state_bits; volatile uint8_t bits_b[n_bytes] = { 0 }; - for (page_idx_t i = 0 ; i < Cfg::NUM_PAGES ; i++) { + for (page_idx_t i = 0 ; i < Cfg::PAGE_COUNT ; i++) { bits_b[i >> state_bits] |= page_states[i] << ((i * state_bits) & 0x7); } uint8_t crc = 0; for (uint8_t i = 0 ; i < n_bytes ; i++) { crc ^= bits_b[i]; - SERIAL_ECHO(bits_b[i]); + SERIAL_CHAR(bits_b[i]); } - SERIAL_ECHO(crc); + SERIAL_CHAR(crc); SERIAL_EOL(); } diff --git a/Marlin/src/feature/direct_stepping.h b/Marlin/src/feature/direct_stepping.h index cde9d1a0b4..b8a803f811 100644 --- a/Marlin/src/feature/direct_stepping.h +++ b/Marlin/src/feature/direct_stepping.h @@ -68,10 +68,10 @@ namespace DirectStepping { static State state; static volatile bool fatal_error; - static volatile PageState page_states[Cfg::NUM_PAGES]; + static volatile PageState page_states[Cfg::PAGE_COUNT]; static volatile bool page_states_dirty; - static uint8_t pages[Cfg::NUM_PAGES][Cfg::PAGE_SIZE]; + static uint8_t pages[Cfg::PAGE_COUNT][Cfg::PAGE_SIZE]; static uint8_t checksum; static write_byte_idx_t write_byte_idx; static page_idx_t write_page_idx; @@ -80,26 +80,23 @@ namespace DirectStepping { static void set_page_state(const page_idx_t page_idx, const PageState page_state); }; - template struct TypeSelector { typedef T type;} ; - template struct TypeSelector { typedef F type; }; - template struct config_t { static constexpr char CONTROL_CHAR = '!'; - static constexpr int NUM_PAGES = num_pages; - static constexpr int NUM_AXES = num_axes; + static constexpr int PAGE_COUNT = num_pages; + static constexpr int AXIS_COUNT = num_axes; static constexpr int BITS_SEGMENT = bits_segment; static constexpr int DIRECTIONAL = dir ? 1 : 0; static constexpr int SEGMENTS = segments; - static constexpr int NUM_SEGMENTS = 1 << BITS_SEGMENT; - static constexpr int SEGMENT_STEPS = (1 << (BITS_SEGMENT - DIRECTIONAL)) - 1; + static constexpr int NUM_SEGMENTS = _BV(BITS_SEGMENT); + static constexpr int SEGMENT_STEPS = _BV(BITS_SEGMENT - DIRECTIONAL) - 1; static constexpr int TOTAL_STEPS = SEGMENT_STEPS * SEGMENTS; - static constexpr int PAGE_SIZE = (NUM_AXES * BITS_SEGMENT * SEGMENTS) / 8; + static constexpr int PAGE_SIZE = (AXIS_COUNT * BITS_SEGMENT * SEGMENTS) / 8; - typedef typename TypeSelector<(PAGE_SIZE>256), uint16_t, uint8_t>::type write_byte_idx_t; - typedef typename TypeSelector<(NUM_PAGES>256), uint16_t, uint8_t>::type page_idx_t; + typedef uvalue_t(PAGE_SIZE - 1) write_byte_idx_t; + typedef uvalue_t(PAGE_COUNT - 1) page_idx_t; }; template diff --git a/Marlin/src/feature/e_parser.cpp b/Marlin/src/feature/e_parser.cpp index a4c2d0dac9..28a903719c 100644 --- a/Marlin/src/feature/e_parser.cpp +++ b/Marlin/src/feature/e_parser.cpp @@ -24,7 +24,7 @@ * e_parser.cpp - Intercept special commands directly in the serial stream */ -#include "../inc/MarlinConfigPre.h" +#include "../inc/MarlinConfig.h" #if ENABLED(EMERGENCY_PARSER) @@ -32,13 +32,208 @@ // Static data members bool EmergencyParser::killed_by_M112, // = false + EmergencyParser::quickstop_by_M410, + #if ENABLED(FTM_RESONANCE_TEST) + EmergencyParser::rt_stop_by_M496, // = false + #endif + #if HAS_MEDIA + EmergencyParser::sd_abort_by_M524, + #endif EmergencyParser::enabled; #if ENABLED(HOST_PROMPT_SUPPORT) + #include "host_actions.h" uint8_t EmergencyParser::M876_reason; // = 0 #endif // Global instance EmergencyParser emergency_parser; +#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(); +#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 (uppercase(c)) { + case ' ': case '\n': case '\r': break; + case 'N': state = EP_N; break; + case 'M': state = EP_M; break; + #if ENABLED(REALTIME_REPORTING_COMMANDS) + case 'S': state = EP_S; break; + case 'P': state = EP_P; break; + case 'R': state = EP_R; break; + #endif + #if ENABLED(SOFT_RESET_VIA_SERIAL) + case '^': state = EP_ctrl; break; + case 'K': state = EP_K; break; + #endif + default: state = EP_IGNORE; + } + break; + + case EP_N: + switch (uppercase(c)) { + case '0' ... '9': + case '-': case ' ': break; + case 'M': state = EP_M; break; + #if ENABLED(REALTIME_REPORTING_COMMANDS) + case 'S': state = EP_S; break; + case 'P': state = EP_P; break; + case 'R': state = EP_R; break; + #endif + default: state = EP_IGNORE; + } + break; + + #if ENABLED(REALTIME_REPORTING_COMMANDS) + case EP_S: state = (c == '0') ? EP_S0 : EP_IGNORE; break; + case EP_S0: state = (c == '0') ? EP_S00 : EP_IGNORE; break; + case EP_S00: state = (c == '0') ? EP_GRBL_STATUS : EP_IGNORE; break; + + case EP_R: state = (c == '0') ? EP_R0 : EP_IGNORE; break; + case EP_R0: state = (c == '0') ? EP_R00 : EP_IGNORE; break; + case EP_R00: state = (c == '0') ? EP_GRBL_RESUME : EP_IGNORE; break; + + case EP_P: state = (c == '0') ? EP_P0 : EP_IGNORE; break; + case EP_P0: state = (c == '0') ? EP_P00 : EP_IGNORE; break; + case EP_P00: state = (c == '0') ? EP_GRBL_PAUSE : EP_IGNORE; break; + #endif + + #if ENABLED(SOFT_RESET_VIA_SERIAL) + case EP_ctrl: state = (c == 'X') ? EP_KILL : EP_IGNORE; break; + case EP_K: state = (c == 'I') ? EP_KI : EP_IGNORE; break; + case EP_KI: state = (c == 'L') ? EP_KIL : EP_IGNORE; break; + case EP_KIL: state = (c == 'L') ? EP_KILL : EP_IGNORE; break; + #endif + + case EP_M: + switch (c) { + case ' ': break; + case '1': state = EP_M1; break; + #if ENABLED(EP_BABYSTEPPING) + case '2': state = EP_M2; break; + #endif + case '4': state = EP_M4; break; + #if HAS_MEDIA + case '5': state = EP_M5; break; + #endif + #if ENABLED(HOST_PROMPT_SUPPORT) + case '8': state = EP_M8; break; + #endif + default: state = EP_IGNORE; + } + break; + + case EP_M1: + switch (c) { + case '0': state = EP_M10; break; + case '1': state = EP_M11; break; + default: state = EP_IGNORE; + } + break; + + 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: + switch (c) { + case '1' :state = EP_M41; break; + #if ENABLED(FTM_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(FTM_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: 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) + + case EP_M8: state = (c == '7') ? EP_M87 : EP_IGNORE; break; + case EP_M87: state = (c == '6') ? EP_M876 : EP_IGNORE; break; + + case EP_M876: + switch (uppercase(c)) { + case ' ': break; + case 'S': state = EP_M876S; break; + default: state = EP_IGNORE; break; + } + break; + + case EP_M876S: + switch (c) { + case ' ': break; + case '0' ... '9': + state = EP_M876SN; + M876_reason = uint8_t(c - '0'); + break; + } + break; + + #endif + + case EP_IGNORE: + if (ISEOL(c)) state = EP_RESET; + break; + + default: + if (ISEOL(c)) { + if (enabled) switch (state) { + 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(FTM_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; + #endif + #if HAS_MEDIA + case EP_M524: sd_abort_by_M524 = true; break; + #endif + #if ENABLED(HOST_PROMPT_SUPPORT) + 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; + #endif + #if ENABLED(SOFT_RESET_VIA_SERIAL) + case EP_KILL: hal.reboot(); break; + #endif + default: break; + } + state = EP_RESET; + } + } +} + #endif // EMERGENCY_PARSER diff --git a/Marlin/src/feature/e_parser.h b/Marlin/src/feature/e_parser.h index 085cbd4eab..9f74a38116 100644 --- a/Marlin/src/feature/e_parser.h +++ b/Marlin/src/feature/e_parser.h @@ -27,42 +27,53 @@ #include "../inc/MarlinConfigPre.h" -#if ENABLED(HOST_PROMPT_SUPPORT) - #include "host_actions.h" -#endif - -// External references -extern bool wait_for_user, wait_for_heatup; -void quickstop_stepper(); - class EmergencyParser { public: - // Currently looking for: M108, M112, M410, M876 - enum State : char { + // Currently looking for: M108, M112, M410, M524, M876 S[0-9], S000, P000, R000 + enum State : uint8_t { EP_RESET, EP_N, EP_M, EP_M1, - EP_M10, - EP_M108, - EP_M11, - EP_M112, - EP_M4, - EP_M41, - EP_M410, + EP_M10, EP_M108, + EP_M11, EP_M112, + EP_M4, EP_M41, EP_M410, + #if HAS_MEDIA + EP_M5, EP_M52, EP_M524, + #endif + #if ENABLED(FTM_RESONANCE_TEST) + EP_M49, EP_M496, + #endif + #if ENABLED(EP_BABYSTEPPING) + EP_M2, EP_M29, EP_M293, EP_M294, + #endif #if ENABLED(HOST_PROMPT_SUPPORT) - EP_M8, - EP_M87, - EP_M876, - EP_M876S, - EP_M876SN, + EP_M8, EP_M87, EP_M876, EP_M876S, EP_M876SN, + #endif + #if ENABLED(REALTIME_REPORTING_COMMANDS) + EP_S, EP_S0, EP_S00, EP_GRBL_STATUS, + EP_R, EP_R0, EP_R00, EP_GRBL_RESUME, + EP_P, EP_P0, EP_P00, EP_GRBL_PAUSE, + #endif + #if ENABLED(SOFT_RESET_VIA_SERIAL) + EP_ctrl, + EP_K, EP_KI, EP_KIL, EP_KILL, #endif EP_IGNORE // to '\n' }; static bool killed_by_M112; + static bool quickstop_by_M410; + + #if ENABLED(FTM_RESONANCE_TEST) + static bool rt_stop_by_M496; + #endif + + #if HAS_MEDIA + static bool sd_abort_by_M524; + #endif #if ENABLED(HOST_PROMPT_SUPPORT) static uint8_t M876_reason; @@ -71,113 +82,11 @@ public: EmergencyParser() { enable(); } FORCE_INLINE static void enable() { enabled = true; } - FORCE_INLINE static void disable() { enabled = false; } - FORCE_INLINE static void update(State &state, const uint8_t c) { - #define ISEOL(C) ((C) == '\n' || (C) == '\r') - switch (state) { - case EP_RESET: - switch (c) { - case ' ': case '\n': case '\r': break; - case 'N': state = EP_N; break; - case 'M': state = EP_M; break; - default: state = EP_IGNORE; - } - break; + static void update(State &state, const uint8_t c); - case EP_N: - switch (c) { - case '0' ... '9': - case '-': case ' ': break; - case 'M': state = EP_M; break; - default: state = EP_IGNORE; - } - break; - - case EP_M: - switch (c) { - case ' ': break; - case '1': state = EP_M1; break; - case '4': state = EP_M4; break; - #if ENABLED(HOST_PROMPT_SUPPORT) - case '8': state = EP_M8; break; - #endif - default: state = EP_IGNORE; - } - break; - - case EP_M1: - switch (c) { - case '0': state = EP_M10; break; - case '1': state = EP_M11; break; - default: state = EP_IGNORE; - } - break; - - 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_M41: - state = (c == '0') ? EP_M410 : EP_IGNORE; - break; - - #if ENABLED(HOST_PROMPT_SUPPORT) - case EP_M8: - state = (c == '7') ? EP_M87 : EP_IGNORE; - break; - - case EP_M87: - state = (c == '6') ? EP_M876 : EP_IGNORE; - break; - - case EP_M876: - switch (c) { - case ' ': break; - case 'S': state = EP_M876S; break; - default: state = EP_IGNORE; break; - } - break; - - case EP_M876S: - switch (c) { - case ' ': break; - case '0' ... '9': - state = EP_M876SN; - M876_reason = (uint8_t)(c - '0'); - break; - } - break; - #endif - - case EP_IGNORE: - if (ISEOL(c)) state = EP_RESET; - break; - - default: - if (ISEOL(c)) { - if (enabled) switch (state) { - case EP_M108: wait_for_user = wait_for_heatup = false; break; - case EP_M112: killed_by_M112 = true; break; - case EP_M410: quickstop_stepper(); break; - #if ENABLED(HOST_PROMPT_SUPPORT) - case EP_M876SN: host_response_handler(M876_reason); break; - #endif - default: break; - } - state = EP_RESET; - } - } - } + static bool isEnabled() { return enabled; } private: static bool enabled; diff --git a/Marlin/src/feature/easythreed_ui.cpp b/Marlin/src/feature/easythreed_ui.cpp new file mode 100644 index 0000000000..7180c4dbcd --- /dev/null +++ b/Marlin/src/feature/easythreed_ui.cpp @@ -0,0 +1,233 @@ +/** + * 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 . + * + */ + +#include "../inc/MarlinConfigPre.h" + +#if ENABLED(EASYTHREED_UI) + +#include "easythreed_ui.h" +#include "pause.h" +#include "../module/temperature.h" +#include "../module/printcounter.h" +#include "../sd/cardreader.h" +#include "../gcode/queue.h" +#include "../module/motion.h" +#include "../module/planner.h" +#include "../MarlinCore.h" + +EasythreedUI easythreed_ui; + +#define BTN_DEBOUNCE_MS 20 + +void EasythreedUI::init() { + SET_INPUT_PULLUP(BTN_HOME); SET_OUTPUT(BTN_HOME_GND); + SET_INPUT_PULLUP(BTN_FEED); SET_OUTPUT(BTN_FEED_GND); + SET_INPUT_PULLUP(BTN_RETRACT); SET_OUTPUT(BTN_RETRACT_GND); + SET_INPUT_PULLUP(BTN_PRINT); + SET_OUTPUT(EASYTHREED_LED_PIN); +} + +void EasythreedUI::run() { + blinkLED(); + loadButton(); + printButton(); +} + +enum LEDInterval : uint16_t { + LED_OFF = 0, + LED_ON = 4000, + LED_BLINK_0 = 2500, + LED_BLINK_1 = 1500, + LED_BLINK_2 = 1000, + LED_BLINK_3 = 800, + LED_BLINK_4 = 500, + LED_BLINK_5 = 300, + LED_BLINK_6 = 150, + LED_BLINK_7 = 50 +}; + +uint16_t blink_interval_ms = LED_ON; // Status LED on Start button + +void EasythreedUI::blinkLED() { + static millis_t prev_blink_interval_ms = 0, blink_start_ms = 0; + + if (blink_interval_ms == LED_OFF) { WRITE(EASYTHREED_LED_PIN, HIGH); return; } // OFF + if (blink_interval_ms >= LED_ON) { WRITE(EASYTHREED_LED_PIN, LOW); return; } // ON + + const millis_t ms = millis(); + if (prev_blink_interval_ms != blink_interval_ms) { + prev_blink_interval_ms = blink_interval_ms; + blink_start_ms = 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)) + WRITE(EASYTHREED_LED_PIN, HIGH); + else + blink_start_ms = ms; +} + +// +// Filament Load/Unload Button +// Load/Unload buttons are a 3 position switch with a common center ground. +// +void EasythreedUI::loadButton() { + if (marlin.printingIsActive()) return; + + enum FilamentStatus : uint8_t { FS_IDLE, FS_PRESS, FS_CHECK, FS_PROCEED }; + static uint8_t filament_status = FS_IDLE; + static millis_t filament_time = 0; + + switch (filament_status) { + + case FS_IDLE: + if (!READ(BTN_RETRACT) || !READ(BTN_FEED)) { // If feed/retract switch is toggled... + filament_status++; // ...proceed to next test. + filament_time = millis(); + } + break; + + case FS_PRESS: + 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 + filament_status++; + } + else + filament_status = FS_IDLE; // Switch not toggled long enough + } + break; + + case FS_CHECK: + if (READ(BTN_RETRACT) && READ(BTN_FEED)) { // Switch in center position (stop) + blink_interval_ms = LED_ON; // LED on steady + filament_status = FS_IDLE; + thermalManager.disable_all_heaters(); + } + else if (thermalManager.hotEnoughToExtrude(0)) { // Is the hotend hot enough to move material? + filament_status++; // Proceed to feed / retract. + blink_interval_ms = LED_BLINK_5; // Blink ~3 times per second + } + break; + + case FS_PROCEED: { + // 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! + thermalManager.disable_all_heaters(); // And disable all the heaters + blink_interval_ms = LED_ON; + } + else if (!flag) { + flag = true; + queue.inject(!READ(BTN_RETRACT) ? F("G91\nG0 E10 F180\nG0 E-120 F180\nM104 S0") : F("G91\nG0 E100 F120\nM104 S0")); + } + } break; + } + +} + +#if HAS_STEPPER_RESET + void disableStepperDrivers(); +#endif + +// +// Print Start/Pause/Resume Button +// +void EasythreedUI::printButton() { + enum KeyStatus : uint8_t { KS_IDLE, KS_PRESS, KS_PROCEED }; + static uint8_t key_status = KS_IDLE; + static millis_t key_time = 0; + + enum PrintFlag : uint8_t { PF_START, PF_PAUSE, PF_RESUME }; + static PrintFlag print_key_flag = PF_START; + + const millis_t ms = millis(); + + switch (key_status) { + case KS_IDLE: + if (!READ(BTN_PRINT)) { // Print/Pause/Resume button pressed? + key_time = ms; // Save start time + key_status++; // Go to debounce test + } + break; + + case KS_PRESS: + 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 + switch (print_key_flag) { + case PF_START: { // The "Print" button starts an SD card print + 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? + blink_interval_ms = LED_OFF; // Turn off LED + print_key_flag = PF_START; + return; // Bail out + } + card.ls(); // List all files to serial output + const int16_t filecnt = card.get_num_items(); // Count printable files in cwd + if (filecnt == 0) return; // None are printable? + card.selectFileByIndex(filecnt); // Select the last file according to current sort options + card.openAndPrintFile(card.filename); // Start printing it + } break; + case PF_PAUSE: { // Pause printing (not currently firing) + 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 (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 + } break; + } + } + else { // Register a longer press + 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 + } + else { // While printing, cancel print + card.abortFilePrintSoon(); // There is a delay while the current steps play out + blink_interval_ms = LED_OFF; // Turn off LED + } + planner.synchronize(); // Wait for commands already in the planner to finish + TERN_(HAS_STEPPER_RESET, disableStepperDrivers()); // Disable all steppers - now! + print_key_flag = PF_START; // The "Print" button now starts a new print + } + break; + } +} +#endif // EASYTHREED_UI diff --git a/Marlin/src/feature/easythreed_ui.h b/Marlin/src/feature/easythreed_ui.h new file mode 100644 index 0000000000..af9ad2d090 --- /dev/null +++ b/Marlin/src/feature/easythreed_ui.h @@ -0,0 +1,35 @@ +/** + * 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 + +class EasythreedUI { + public: + static void init(); + static void run(); + + private: + static void blinkLED(); + static void loadButton(); + static void printButton(); +}; + +extern EasythreedUI easythreed_ui; diff --git a/Marlin/src/feature/encoder_i2c.cpp b/Marlin/src/feature/encoder_i2c.cpp index 8a3e959e07..7c83053b89 100644 --- a/Marlin/src/feature/encoder_i2c.cpp +++ b/Marlin/src/feature/encoder_i2c.cpp @@ -27,28 +27,28 @@ //todo: try faster I2C speed; tweak TWI_FREQ (400000L, or faster?); or just TWBR = ((CPU_FREQ / 400000L) - 16) / 2; //todo: consider Marlin-optimized Wire library; i.e. MarlinWire, like MarlinSerial - #include "../inc/MarlinConfig.h" #if ENABLED(I2C_POSITION_ENCODERS) #include "encoder_i2c.h" -#include "../module/temperature.h" #include "../module/stepper.h" #include "../gcode/parser.h" -#include "../feature/babystep.h" +#include "babystep.h" #include +I2CPositionEncodersMgr I2CPEM; + void I2CPositionEncoder::init(const uint8_t address, const AxisEnum axis) { encoderAxis = axis; i2cAddress = address; - initialized++; + initialized = true; - SERIAL_ECHOLNPAIR("Setting up encoder on ", axis_codes[encoderAxis], " axis, addr = ", address); + SERIAL_ECHOLNPGM("Setting up encoder on ", C(AXIS_CHAR(encoderAxis)), " axis, addr = ", address); position = get_position(); } @@ -66,7 +66,7 @@ void I2CPositionEncoder::update() { /* if (trusted) { //commented out as part of the note below trusted = false; - SERIAL_ECHOLMPAIR("Fault detected on ", axis_codes[encoderAxis], " axis encoder. Disengaging error correction until module is trusted again."); + SERIAL_ECHOLNPGM("Fault detected on ", C(AXIS_CHAR(encoderAxis)), " axis encoder. Disengaging error correction until module is trusted again."); } */ return; @@ -85,15 +85,15 @@ void I2CPositionEncoder::update() { * the encoder would be re-enabled. */ - /* + #if 0 // If the magnetic strength has been good for a certain time, start trusting the module again if (millis() - lastErrorTime > I2CPE_TIME_TRUSTED) { trusted = true; - SERIAL_ECHOLNPAIR("Untrusted encoder module on ", axis_codes[encoderAxis], " axis has been fault-free for set duration, reinstating error correction."); + SERIAL_ECHOLNPGM("Untrusted encoder module on ", C(AXIS_CHAR(encoderAxis)), " axis has been fault-free for set duration, reinstating error correction."); - //the encoder likely lost its place when the error occured, so we'll reset and use the printer's + //the encoder likely lost its place when the error occurred, so we'll reset and use the printer's //idea of where it the axis is to re-initialize const float pos = planner.get_axis_position_mm(encoderAxis); int32_t positionInTicks = pos * get_ticks_unit(); @@ -102,16 +102,13 @@ void I2CPositionEncoder::update() { zeroOffset -= (positionInTicks - get_position()); #ifdef I2CPE_DEBUG - SERIAL_ECHOLNPAIR("Current position is ", pos); - SERIAL_ECHOLNPAIR("Position in encoder ticks is ", positionInTicks); - SERIAL_ECHOLNPAIR("New zero-offset of ", zeroOffset); - SERIAL_ECHOPAIR("New position reads as ", get_position()); - SERIAL_CHAR('('); - SERIAL_DECIMAL(mm_from_count(get_position())); - SERIAL_ECHOLNPGM(")"); + SERIAL_ECHOLNPGM("Current position is ", pos); + SERIAL_ECHOLNPGM("Position in encoder ticks is ", positionInTicks); + SERIAL_ECHOLNPGM("New zero-offset of ", zeroOffset); + SERIAL_ECHOLN(F("New position reads as "), get_position(), C('('), mm_from_count(get_position()), C(')')); #endif } - */ + #endif return; } @@ -137,7 +134,7 @@ void I2CPositionEncoder::update() { errIdx = (errIdx >= I2CPE_ERR_ARRAY_SIZE - 1) ? 0 : errIdx + 1; err[errIdx] = get_axis_error_steps(false); - LOOP_L_N(i, I2CPE_ERR_ARRAY_SIZE) { + for (uint8_t i = 0; i < I2CPE_ERR_ARRAY_SIZE; ++i) { sum += err[i]; if (i) diffSum += ABS(err[i-1] - err[i]); } @@ -148,12 +145,12 @@ void I2CPositionEncoder::update() { const int32_t error = get_axis_error_steps(false); #endif - //SERIAL_ECHOLNPAIR("Axis error steps: ", error); + //SERIAL_ECHOLNPGM("Axis error steps: ", error); #ifdef I2CPE_ERR_THRESH_ABORT if (ABS(error) > I2CPE_ERR_THRESH_ABORT * planner.settings.axis_steps_per_mm[encoderAxis]) { - //kill(PSTR("Significant Error")); - SERIAL_ECHOLNPAIR("Axis error over threshold, aborting!", error); + //marlin.kill(F("Significant Error")); + SERIAL_ECHOLNPGM("Axis error over threshold, aborting!", error); safe_delay(5000); } #endif @@ -169,10 +166,9 @@ void I2CPositionEncoder::update() { errPrst[errPrstIdx++] = error; // Error must persist for I2CPE_ERR_PRST_ARRAY_SIZE error cycles. This also serves to improve the average accuracy if (errPrstIdx >= I2CPE_ERR_PRST_ARRAY_SIZE) { float sumP = 0; - LOOP_L_N(i, I2CPE_ERR_PRST_ARRAY_SIZE) sumP += errPrst[i]; + 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_ECHO(axis_codes[encoderAxis]); - SERIAL_ECHOLNPAIR(" : CORRECT ERR ", errorP * planner.steps_to_mm[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; } @@ -191,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_ECHO(axis_codes[encoderAxis]); - SERIAL_ECHOLNPAIR(" : LARGE ERR ", int(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,12 +203,10 @@ void I2CPositionEncoder::set_homed() { delay(10); zeroOffset = get_raw_count(); - homed++; - trusted++; + homed = trusted = true; #ifdef I2CPE_DEBUG - SERIAL_ECHO(axis_codes[encoderAxis]); - SERIAL_ECHOLNPAIR(" axis encoder homed, offset of ", zeroOffset, " ticks."); + SERIAL_ECHO(C(AXIS_CHAR(encoderAxis)), F(" axis encoder homed, offset of "), zeroOffset, F(" ticks.\n")); #endif } } @@ -223,16 +216,15 @@ void I2CPositionEncoder::set_unhomed() { homed = trusted = false; #ifdef I2CPE_DEBUG - SERIAL_ECHO(axis_codes[encoderAxis]); - SERIAL_ECHOLNPGM(" axis encoder unhomed."); + SERIAL_ECHO(C(AXIS_CHAR(encoderAxis)), F(" axis encoder unhomed.\n")); #endif } bool I2CPositionEncoder::passes_test(const bool report) { if (report) { if (H != I2CPE_MAG_SIG_GOOD) SERIAL_ECHOPGM("Warning. "); - SERIAL_ECHO(axis_codes[encoderAxis]); - serial_ternary(H == I2CPE_MAG_SIG_BAD, PSTR(" axis "), PSTR("magnetic strip "), PSTR("encoder ")); + SERIAL_CHAR(AXIS_CHAR(encoderAxis)); + serial_ternary(F(" axis "), H == I2CPE_MAG_SIG_BAD, F("magnetic strip "), F("encoder ")); switch (H) { case I2CPE_MAG_SIG_GOOD: case I2CPE_MAG_SIG_MID: @@ -251,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_ECHO(axis_codes[encoderAxis]); - SERIAL_ECHOLNPAIR(" 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; } @@ -262,7 +252,7 @@ float I2CPositionEncoder::get_axis_error_mm(const bool report) { int32_t I2CPositionEncoder::get_axis_error_steps(const bool report) { if (!active) { if (report) { - SERIAL_ECHO(axis_codes[encoderAxis]); + SERIAL_CHAR(AXIS_CHAR(encoderAxis)); SERIAL_ECHOLNPGM(" axis encoder not active!"); } return 0; @@ -286,10 +276,8 @@ int32_t I2CPositionEncoder::get_axis_error_steps(const bool report) { errorPrev = error; - if (report) { - SERIAL_ECHO(axis_codes[encoderAxis]); - SERIAL_ECHOLNPAIR(" 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."); @@ -327,17 +315,17 @@ int32_t I2CPositionEncoder::get_raw_count() { } bool I2CPositionEncoder::test_axis() { - //only works on XYZ cartesian machines for the time being + // 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(MMM_TO_MMS((encoderAxis == Z_AXIS) ? HOMING_FEEDRATE_Z : HOMING_FEEDRATE_XY)); + const feedRate_t fr_mm_s = FLOOR(homing_feedrate(encoderAxis)); ec = false; xyze_pos_t startCoord, endCoord; - LOOP_XYZ(a) { + LOOP_NUM_AXES(a) { startCoord[a] = planner.get_axis_position_mm((AxisEnum)a); endCoord[a] = planner.get_axis_position_mm((AxisEnum)a); } @@ -345,9 +333,12 @@ bool I2CPositionEncoder::test_axis() { endCoord[encoderAxis] = endPosition; planner.synchronize(); - startCoord.e = planner.get_axis_position_mm(E_AXIS); - planner.buffer_line(startCoord, fr_mm_s, 0); - planner.synchronize(); + + #if HAS_EXTRUDERS + startCoord.e = planner.get_axis_position_mm(E_AXIS); + planner.buffer_line(startCoord, fr_mm_s, 0); + planner.synchronize(); + #endif // if the module isn't currently trusted, wait until it is (or until it should be if things are working) if (!trusted) { @@ -357,7 +348,7 @@ bool I2CPositionEncoder::test_axis() { } if (trusted) { // if trusted, commence test - endCoord.e = planner.get_axis_position_mm(E_AXIS); + TERN_(HAS_EXTRUDERS, endCoord.e = planner.get_axis_position_mm(E_AXIS)); planner.buffer_line(endCoord, fr_mm_s, 0); planner.synchronize(); } @@ -382,7 +373,7 @@ void I2CPositionEncoder::calibrate_steps_mm(const uint8_t iter) { int32_t startCount, stopCount; - const feedRate_t fr_mm_s = MMM_TO_MMS((encoderAxis == Z_AXIS) ? HOMING_FEEDRATE_Z : HOMING_FEEDRATE_XY); + const feedRate_t fr_mm_s = homing_feedrate(encoderAxis); bool oldec = ec; ec = false; @@ -392,7 +383,7 @@ void I2CPositionEncoder::calibrate_steps_mm(const uint8_t iter) { travelDistance = endDistance - startDistance; xyze_pos_t startCoord, endCoord; - LOOP_XYZ(a) { + LOOP_NUM_AXES(a) { startCoord[a] = planner.get_axis_position_mm((AxisEnum)a); endCoord[a] = planner.get_axis_position_mm((AxisEnum)a); } @@ -401,8 +392,8 @@ void I2CPositionEncoder::calibrate_steps_mm(const uint8_t iter) { planner.synchronize(); - LOOP_L_N(i, iter) { - startCoord.e = planner.get_axis_position_mm(E_AXIS); + for (uint8_t i = 0; i < iter; ++i) { + TERN_(HAS_EXTRUDERS, startCoord.e = planner.get_axis_position_mm(E_AXIS)); planner.buffer_line(startCoord, fr_mm_s, 0); planner.synchronize(); @@ -411,7 +402,7 @@ void I2CPositionEncoder::calibrate_steps_mm(const uint8_t iter) { //do_blocking_move_to(endCoord); - endCoord.e = planner.get_axis_position_mm(E_AXIS); + TERN_(HAS_EXTRUDERS, endCoord.e = planner.get_axis_position_mm(E_AXIS)); planner.buffer_line(endCoord, fr_mm_s, 0); planner.synchronize(); @@ -421,23 +412,23 @@ void I2CPositionEncoder::calibrate_steps_mm(const uint8_t iter) { travelledDistance = mm_from_count(ABS(stopCount - startCount)); - SERIAL_ECHOLNPAIR("Attempted travel: ", travelDistance, "mm"); - SERIAL_ECHOLNPAIR(" Actual travel: ", travelledDistance, "mm"); + SERIAL_ECHOLNPGM("Attempted travel: ", travelDistance, "mm"); + SERIAL_ECHOLNPGM(" Actual travel: ", travelledDistance, "mm"); - //Calculate new axis steps per unit + // Calculate new axis steps per unit old_steps_mm = planner.settings.axis_steps_per_mm[encoderAxis]; new_steps_mm = (old_steps_mm * travelDistance) / travelledDistance; - SERIAL_ECHOLNPAIR("Old steps/mm: ", old_steps_mm); - SERIAL_ECHOLNPAIR("New steps/mm: ", new_steps_mm); + SERIAL_ECHOLNPGM("Old steps/mm: ", old_steps_mm); + SERIAL_ECHOLNPGM("New steps/mm: ", new_steps_mm); - //Save new value + // Save new value planner.settings.axis_steps_per_mm[encoderAxis] = new_steps_mm; if (iter > 1) { total += new_steps_mm; - // swap start and end points so next loop runs from current position + // Swap start and end points so next loop runs from current position const float tempCoord = startCoord[encoderAxis]; startCoord[encoderAxis] = endCoord[encoderAxis]; endCoord[encoderAxis] = tempCoord; @@ -446,7 +437,7 @@ void I2CPositionEncoder::calibrate_steps_mm(const uint8_t iter) { if (iter > 1) { total /= (float)iter; - SERIAL_ECHOLNPAIR("Average steps/mm: ", total); + SERIAL_ECHOLNPGM("Average steps/mm: ", total); } ec = oldec; @@ -462,7 +453,6 @@ void I2CPositionEncoder::reset() { TERN_(I2CPE_ERR_ROLLING_AVERAGE, ZERO(err)); } - bool I2CPositionEncodersMgr::I2CPE_anyaxis; uint8_t I2CPositionEncodersMgr::I2CPE_addr, I2CPositionEncodersMgr::I2CPE_idx; @@ -486,7 +476,7 @@ void I2CPositionEncodersMgr::init() { encoders[i].set_stepper_ticks(I2CPE_ENC_1_TICKS_REV); #endif #ifdef I2CPE_ENC_1_INVERT - encoders[i].set_inverted(I2CPE_ENC_1_INVERT); + encoders[i].set_inverted(ENABLED(I2CPE_ENC_1_INVERT)); #endif #ifdef I2CPE_ENC_1_EC_METHOD encoders[i].set_ec_method(I2CPE_ENC_1_EC_METHOD); @@ -497,9 +487,7 @@ void I2CPositionEncodersMgr::init() { encoders[i].set_active(encoders[i].passes_test(true)); - #if I2CPE_ENC_1_AXIS == E_AXIS - encoders[i].set_homed(); - #endif + TERN_(HAS_EXTRUDERS, if (I2CPE_ENC_1_AXIS == E_AXIS) encoders[i].set_homed()); #endif #if I2CPE_ENCODER_CNT > 1 @@ -517,7 +505,7 @@ void I2CPositionEncodersMgr::init() { encoders[i].set_stepper_ticks(I2CPE_ENC_2_TICKS_REV); #endif #ifdef I2CPE_ENC_2_INVERT - encoders[i].set_inverted(I2CPE_ENC_2_INVERT); + encoders[i].set_inverted(ENABLED(I2CPE_ENC_2_INVERT)); #endif #ifdef I2CPE_ENC_2_EC_METHOD encoders[i].set_ec_method(I2CPE_ENC_2_EC_METHOD); @@ -528,9 +516,7 @@ void I2CPositionEncodersMgr::init() { encoders[i].set_active(encoders[i].passes_test(true)); - #if I2CPE_ENC_2_AXIS == E_AXIS - encoders[i].set_homed(); - #endif + TERN_(HAS_EXTRUDERS, if (I2CPE_ENC_2_AXIS == E_AXIS) encoders[i].set_homed()); #endif #if I2CPE_ENCODER_CNT > 2 @@ -548,7 +534,7 @@ void I2CPositionEncodersMgr::init() { encoders[i].set_stepper_ticks(I2CPE_ENC_3_TICKS_REV); #endif #ifdef I2CPE_ENC_3_INVERT - encoders[i].set_inverted(I2CPE_ENC_3_INVERT); + encoders[i].set_inverted(ENABLED(I2CPE_ENC_3_INVERT)); #endif #ifdef I2CPE_ENC_3_EC_METHOD encoders[i].set_ec_method(I2CPE_ENC_3_EC_METHOD); @@ -557,11 +543,9 @@ void I2CPositionEncodersMgr::init() { encoders[i].set_ec_threshold(I2CPE_ENC_3_EC_THRESH); #endif - encoders[i].set_active(encoders[i].passes_test(true)); + encoders[i].set_active(encoders[i].passes_test(true)); - #if I2CPE_ENC_3_AXIS == E_AXIS - encoders[i].set_homed(); - #endif + TERN_(HAS_EXTRUDERS, if (I2CPE_ENC_3_AXIS == E_AXIS) encoders[i].set_homed()); #endif #if I2CPE_ENCODER_CNT > 3 @@ -579,7 +563,7 @@ void I2CPositionEncodersMgr::init() { encoders[i].set_stepper_ticks(I2CPE_ENC_4_TICKS_REV); #endif #ifdef I2CPE_ENC_4_INVERT - encoders[i].set_inverted(I2CPE_ENC_4_INVERT); + encoders[i].set_inverted(ENABLED(I2CPE_ENC_4_INVERT)); #endif #ifdef I2CPE_ENC_4_EC_METHOD encoders[i].set_ec_method(I2CPE_ENC_4_EC_METHOD); @@ -590,9 +574,7 @@ void I2CPositionEncodersMgr::init() { encoders[i].set_active(encoders[i].passes_test(true)); - #if I2CPE_ENC_4_AXIS == E_AXIS - encoders[i].set_homed(); - #endif + TERN_(HAS_EXTRUDERS, if (I2CPE_ENC_4_AXIS == E_AXIS) encoders[i].set_homed()); #endif #if I2CPE_ENCODER_CNT > 4 @@ -610,7 +592,7 @@ void I2CPositionEncodersMgr::init() { encoders[i].set_stepper_ticks(I2CPE_ENC_5_TICKS_REV); #endif #ifdef I2CPE_ENC_5_INVERT - encoders[i].set_inverted(I2CPE_ENC_5_INVERT); + encoders[i].set_inverted(ENABLED(I2CPE_ENC_5_INVERT)); #endif #ifdef I2CPE_ENC_5_EC_METHOD encoders[i].set_ec_method(I2CPE_ENC_5_EC_METHOD); @@ -621,9 +603,7 @@ void I2CPositionEncodersMgr::init() { encoders[i].set_active(encoders[i].passes_test(true)); - #if I2CPE_ENC_5_AXIS == E_AXIS - encoders[i].set_homed(); - #endif + TERN_(HAS_EXTRUDERS, if (I2CPE_ENC_5_AXIS == E_AXIS) encoders[i].set_homed()); #endif #if I2CPE_ENCODER_CNT > 5 @@ -641,7 +621,7 @@ void I2CPositionEncodersMgr::init() { encoders[i].set_stepper_ticks(I2CPE_ENC_6_TICKS_REV); #endif #ifdef I2CPE_ENC_6_INVERT - encoders[i].set_inverted(I2CPE_ENC_6_INVERT); + encoders[i].set_inverted(ENABLED(I2CPE_ENC_6_INVERT)); #endif #ifdef I2CPE_ENC_6_EC_METHOD encoders[i].set_ec_method(I2CPE_ENC_6_EC_METHOD); @@ -652,51 +632,47 @@ void I2CPositionEncodersMgr::init() { encoders[i].set_active(encoders[i].passes_test(true)); - #if I2CPE_ENC_6_AXIS == E_AXIS - encoders[i].set_homed(); - #endif + TERN_(HAS_EXTRUDERS, if (I2CPE_ENC_6_AXIS == E_AXIS) encoders[i].set_homed()); #endif } 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_ECHO(axis_codes[encoders[idx].get_axis()]); - SERIAL_CHAR(' '); - - for (uint8_t j = 31; j > 0; j--) - SERIAL_ECHO((bool)(0x00000001 & (raw_count >> j))); - - SERIAL_ECHO((bool)(0x00000001 & raw_count)); - SERIAL_CHAR(' '); - SERIAL_ECHOLN(raw_count); - } - else - SERIAL_ECHOLN(encoders[idx].get_position()); + return; } + + if (noOffset) { + const int32_t raw_count = encoders[idx].get_raw_count(); + SERIAL_CHAR(AXIS_CHAR(encoders[idx].get_axis()), ' '); + + for (uint8_t j = 31; j >= 0; 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) { // First check 'new' address is not in use Wire.beginTransmission(I2C_ADDRESS(newaddr)); if (!Wire.endTransmission()) { - SERIAL_ECHOLNPAIR("?There is already a device with that address on the I2C bus! (", newaddr, ")"); + SERIAL_ECHOLNPGM("?There is already a device with that address on the I2C bus! (", newaddr, ")"); return; } // Now check that we can find the module on the oldaddr address Wire.beginTransmission(I2C_ADDRESS(oldaddr)); if (Wire.endTransmission()) { - SERIAL_ECHOLNPAIR("?No module detected at this address! (", oldaddr, ")"); + SERIAL_ECHOLNPGM("?No module detected at this address! (", oldaddr, ")"); return; } - SERIAL_ECHOLNPAIR("Module found at ", oldaddr, ", changing address to ", newaddr); + SERIAL_ECHOLNPGM("Module found at ", oldaddr, ", changing address to ", newaddr); // Change the modules address Wire.beginTransmission(I2C_ADDRESS(oldaddr)); @@ -722,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_ECHO(axis_codes[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)); } @@ -732,11 +708,11 @@ void I2CPositionEncodersMgr::report_module_firmware(const uint8_t address) { // First check there is a module Wire.beginTransmission(I2C_ADDRESS(address)); if (Wire.endTransmission()) { - SERIAL_ECHOLNPAIR("?No module detected at this address! (", address, ")"); + SERIAL_ECHOLNPGM("?No module detected at this address! (", address, ")"); return; } - SERIAL_ECHOLNPAIR("Requesting version info from module at address ", address, ":"); + SERIAL_ECHOLNPGM("Requesting version info from module at address ", address, ":"); Wire.beginTransmission(I2C_ADDRESS(address)); Wire.write(I2CPE_SET_REPORT_MODE); @@ -747,7 +723,7 @@ void I2CPositionEncodersMgr::report_module_firmware(const uint8_t address) { if (Wire.requestFrom(I2C_ADDRESS(address), uint8_t(32))) { char c; while (Wire.available() > 0 && (c = (char)Wire.read()) > 0) - SERIAL_ECHO(c); + SERIAL_CHAR(c); SERIAL_EOL(); } @@ -766,7 +742,7 @@ int8_t I2CPositionEncodersMgr::parse() { if (!parser.has_value()) { SERIAL_ECHOLNPGM("?A seen, but no address specified! [30-200]"); return I2CPE_PARSE_ERR; - }; + } I2CPE_addr = parser.value_byte(); if (!WITHIN(I2CPE_addr, 30, 200)) { // reserve the first 30 and last 55 @@ -783,13 +759,13 @@ int8_t I2CPositionEncodersMgr::parse() { else if (parser.seenval('I')) { if (!parser.has_value()) { - SERIAL_ECHOLNPAIR("?I seen, but no index specified! [0-", I2CPE_ENCODER_CNT - 1, "]"); + SERIAL_ECHOLNPGM("?I seen, but no index specified! [0-", I2CPE_ENCODER_CNT - 1, "]"); return I2CPE_PARSE_ERR; - }; + } I2CPE_idx = parser.value_byte(); if (I2CPE_idx >= I2CPE_ENCODER_CNT) { - SERIAL_ECHOLNPAIR("?Index out of range. [0-", I2CPE_ENCODER_CNT - 1, "]"); + SERIAL_ECHOLNPGM("?Index out of range. [0-", I2CPE_ENCODER_CNT - 1, "]"); return I2CPE_PARSE_ERR; } @@ -801,7 +777,7 @@ int8_t I2CPositionEncodersMgr::parse() { I2CPE_anyaxis = parser.seen_axis(); return I2CPE_PARSE_OK; -}; +} /** * M860: Report the position(s) of position encoder module(s). @@ -820,12 +796,12 @@ int8_t I2CPositionEncodersMgr::parse() { void I2CPositionEncodersMgr::M860() { if (parse()) return; - const bool hasU = parser.seen('U'), hasO = parser.seen('O'); + const bool hasU = parser.seen_test('U'), hasO = parser.seen_test('O'); if (I2CPE_idx == 0xFF) { - LOOP_XYZE(i) { - if (!I2CPE_anyaxis || parser.seen(axis_codes[i])) { - const uint8_t idx = idx_from_axis(AxisEnum(i)); + LOOP_LOGICAL_AXES(i) { + if (!I2CPE_anyaxis || parser.seen_test(AXIS_CHAR(i))) { + const uint8_t idx = idx_from_axis((AxisEnum)i); if ((int8_t)idx >= 0) report_position(idx, hasU, hasO); } } @@ -850,9 +826,9 @@ void I2CPositionEncodersMgr::M861() { if (parse()) return; if (I2CPE_idx == 0xFF) { - LOOP_XYZE(i) { - if (!I2CPE_anyaxis || parser.seen(axis_codes[i])) { - const uint8_t idx = idx_from_axis(AxisEnum(i)); + LOOP_LOGICAL_AXES(i) { + if (!I2CPE_anyaxis || parser.seen(AXIS_CHAR(i))) { + const uint8_t idx = idx_from_axis((AxisEnum)i); if ((int8_t)idx >= 0) report_status(idx); } } @@ -878,9 +854,9 @@ void I2CPositionEncodersMgr::M862() { if (parse()) return; if (I2CPE_idx == 0xFF) { - LOOP_XYZE(i) { - if (!I2CPE_anyaxis || parser.seen(axis_codes[i])) { - const uint8_t idx = idx_from_axis(AxisEnum(i)); + LOOP_LOGICAL_AXES(i) { + if (!I2CPE_anyaxis || parser.seen(AXIS_CHAR(i))) { + const uint8_t idx = idx_from_axis((AxisEnum)i); if ((int8_t)idx >= 0) test_axis(idx); } } @@ -909,9 +885,9 @@ void I2CPositionEncodersMgr::M863() { const uint8_t iterations = constrain(parser.byteval('P', 1), 1, 10); if (I2CPE_idx == 0xFF) { - LOOP_XYZE(i) { - if (!I2CPE_anyaxis || parser.seen(axis_codes[i])) { - const uint8_t idx = idx_from_axis(AxisEnum(i)); + LOOP_LOGICAL_AXES(i) { + if (!I2CPE_anyaxis || parser.seen(AXIS_CHAR(i))) { + const uint8_t idx = idx_from_axis((AxisEnum)i); if ((int8_t)idx >= 0) calibrate_steps_mm(idx, iterations); } } @@ -944,7 +920,7 @@ void I2CPositionEncodersMgr::M864() { if (!parser.has_value()) { SERIAL_ECHOLNPGM("?S seen, but no address specified! [30-200]"); return; - }; + } newAddress = parser.value_byte(); if (!WITHIN(newAddress, 30, 200)) { @@ -957,14 +933,14 @@ void I2CPositionEncodersMgr::M864() { return; } else { - if (parser.seen('X')) newAddress = I2CPE_PRESET_ADDR_X; - else if (parser.seen('Y')) newAddress = I2CPE_PRESET_ADDR_Y; - else if (parser.seen('Z')) newAddress = I2CPE_PRESET_ADDR_Z; - else if (parser.seen('E')) newAddress = I2CPE_PRESET_ADDR_E; + if (parser.seen_test('X')) newAddress = I2CPE_PRESET_ADDR_X; + else if (parser.seen_test('Y')) newAddress = I2CPE_PRESET_ADDR_Y; + else if (parser.seen_test('Z')) newAddress = I2CPE_PRESET_ADDR_Z; + else if (parser.seen_test('E')) newAddress = I2CPE_PRESET_ADDR_E; else return; } - SERIAL_ECHOLNPAIR("Changing module at address ", I2CPE_addr, " to address ", newAddress); + SERIAL_ECHOLNPGM("Changing module at address ", I2CPE_addr, " to address ", newAddress); change_module_address(I2CPE_addr, newAddress); } @@ -985,9 +961,9 @@ void I2CPositionEncodersMgr::M865() { if (parse()) return; if (!I2CPE_addr) { - LOOP_XYZE(i) { - if (!I2CPE_anyaxis || parser.seen(axis_codes[i])) { - const uint8_t idx = idx_from_axis(AxisEnum(i)); + LOOP_LOGICAL_AXES(i) { + if (!I2CPE_anyaxis || parser.seen(AXIS_CHAR(i))) { + const uint8_t idx = idx_from_axis((AxisEnum)i); if ((int8_t)idx >= 0) report_module_firmware(encoders[idx].get_address()); } } @@ -1013,17 +989,17 @@ void I2CPositionEncodersMgr::M865() { void I2CPositionEncodersMgr::M866() { if (parse()) return; - const bool hasR = parser.seen('R'); + const bool hasR = parser.seen_test('R'); if (I2CPE_idx == 0xFF) { - LOOP_XYZE(i) { - if (!I2CPE_anyaxis || parser.seen(axis_codes[i])) { - const uint8_t idx = idx_from_axis(AxisEnum(i)); + LOOP_LOGICAL_AXES(i) { + if (!I2CPE_anyaxis || parser.seen(AXIS_CHAR(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); } } } @@ -1054,12 +1030,12 @@ void I2CPositionEncodersMgr::M867() { const int8_t onoff = parser.seenval('S') ? parser.value_int() : -1; if (I2CPE_idx == 0xFF) { - LOOP_XYZE(i) { - if (!I2CPE_anyaxis || parser.seen(axis_codes[i])) { - const uint8_t idx = idx_from_axis(AxisEnum(i)); + LOOP_LOGICAL_AXES(i) { + if (!I2CPE_anyaxis || parser.seen(AXIS_CHAR(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); } } } @@ -1090,9 +1066,9 @@ void I2CPositionEncodersMgr::M868() { const float newThreshold = parser.seenval('T') ? parser.value_float() : -9999; if (I2CPE_idx == 0xFF) { - LOOP_XYZE(i) { - if (!I2CPE_anyaxis || parser.seen(axis_codes[i])) { - const uint8_t idx = idx_from_axis(AxisEnum(i)); + LOOP_LOGICAL_AXES(i) { + if (!I2CPE_anyaxis || parser.seen(AXIS_CHAR(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()); @@ -1124,9 +1100,9 @@ void I2CPositionEncodersMgr::M869() { if (parse()) return; if (I2CPE_idx == 0xFF) { - LOOP_XYZE(i) { - if (!I2CPE_anyaxis || parser.seen(axis_codes[i])) { - const uint8_t idx = idx_from_axis(AxisEnum(i)); + LOOP_LOGICAL_AXES(i) { + if (!I2CPE_anyaxis || parser.seen(AXIS_CHAR(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 511e560ba0..e8485a6b75 100644 --- a/Marlin/src/feature/encoder_i2c.h +++ b/Marlin/src/feature/encoder_i2c.h @@ -90,7 +90,7 @@ #define I2CPE_PARSE_ERR 1 #define I2CPE_PARSE_OK 0 -#define LOOP_PE(VAR) LOOP_L_N(VAR, I2CPE_ENCODER_CNT) +#define LOOP_PE(VAR) for (uint8_t VAR = 0; VAR < I2CPE_ENCODER_CNT; ++VAR) #define CHECK_IDX() do{ if (!WITHIN(idx, 0, I2CPE_ENCODER_CNT - 1)) return; }while(0) typedef union { @@ -236,7 +236,7 @@ class I2CPositionEncodersMgr { static void report_status(const int8_t idx) { CHECK_IDX(); - SERIAL_ECHOLNPAIR("Encoder ", idx, ": "); + SERIAL_ECHOLNPGM("Encoder ", idx, ": "); encoders[idx].get_raw_count(); encoders[idx].passes_test(true); } @@ -261,32 +261,32 @@ class I2CPositionEncodersMgr { static void report_error_count(const int8_t idx, const AxisEnum axis) { CHECK_IDX(); - SERIAL_ECHOLNPAIR("Error count on ", axis_codes[axis], " axis is ", encoders[idx].get_error_count()); + SERIAL_ECHOLNPGM("Error count on ", C(AXIS_CHAR(axis)), " axis is ", encoders[idx].get_error_count()); } static void reset_error_count(const int8_t idx, const AxisEnum axis) { CHECK_IDX(); encoders[idx].set_error_count(0); - SERIAL_ECHOLNPAIR("Error count on ", axis_codes[axis], " axis has been reset."); + SERIAL_ECHOLNPGM("Error count on ", C(AXIS_CHAR(axis)), " axis has been reset."); } static void enable_ec(const int8_t idx, const bool enabled, const AxisEnum axis) { CHECK_IDX(); encoders[idx].set_ec_enabled(enabled); - SERIAL_ECHOPAIR("Error correction on ", axis_codes[axis]); + SERIAL_ECHOPGM("Error correction on ", C(AXIS_CHAR(axis))); SERIAL_ECHO_TERNARY(encoders[idx].get_ec_enabled(), " axis is ", "en", "dis", "abled.\n"); } static void set_ec_threshold(const int8_t idx, const float newThreshold, const AxisEnum axis) { CHECK_IDX(); encoders[idx].set_ec_threshold(newThreshold); - SERIAL_ECHOLNPAIR("Error correct threshold for ", axis_codes[axis], " axis set to ", newThreshold, "mm."); + SERIAL_ECHOLNPGM("Error correct threshold for ", C(AXIS_CHAR(axis)), " axis set to ", newThreshold, "mm."); } static void get_ec_threshold(const int8_t idx, const AxisEnum axis) { CHECK_IDX(); const float threshold = encoders[idx].get_ec_threshold(); - SERIAL_ECHOLNPAIR("Error correct threshold for ", axis_codes[axis], " axis is ", threshold, "mm."); + SERIAL_ECHOLNPGM("Error correct threshold for ", C(AXIS_CHAR(axis)), " axis is ", threshold, "mm."); } static int8_t idx_from_axis(const AxisEnum axis) { diff --git a/Marlin/src/feature/ethernet.cpp b/Marlin/src/feature/ethernet.cpp new file mode 100644 index 0000000000..cdf176f832 --- /dev/null +++ b/Marlin/src/feature/ethernet.cpp @@ -0,0 +1,217 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2020 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 HAS_ETHERNET + +#include "ethernet.h" +#include "../core/serial.h" + +#define DEBUG_OUT ENABLED(DEBUG_ETHERNET) +#include "../core/debug_out.h" + +bool MarlinEthernet::hardware_enabled, // = false + MarlinEthernet::have_telnet_client; // = false + +IPAddress MarlinEthernet::ip, + MarlinEthernet::myDns, + MarlinEthernet::gateway, + MarlinEthernet::subnet; + +EthernetClient MarlinEthernet::telnetClient; // connected client + +MarlinEthernet ethernet; + +EthernetServer server(23); // telnet server + +enum linkStates { UNLINKED, LINKING, LINKED, CONNECTING, CONNECTED, NO_HARDWARE } linkState; + +#ifdef __IMXRT1062__ + + static void teensyMAC(uint8_t * const mac) { + const uint32_t m1 = HW_OCOTP_MAC1, m2 = HW_OCOTP_MAC0; + mac[0] = m1 >> 8; + mac[1] = m1 >> 0; + mac[2] = m2 >> 24; + mac[3] = m2 >> 16; + mac[4] = m2 >> 8; + mac[5] = m2 >> 0; + } + +#else + + byte mac[] = MAC_ADDRESS; + +#endif + +void ethernet_cable_error() { SERIAL_ERROR_MSG("Ethernet cable is not connected."); } + +void MarlinEthernet::init() { + if (!hardware_enabled) return; + + SERIAL_ECHO_MSG("Starting network..."); + + // Init the Ethernet device + #ifdef __IMXRT1062__ + uint8_t mac[6]; + teensyMAC(mac); + #endif + + if (!ip) { + Ethernet.begin(mac); // use DHCP + } + else { + if (!gateway) { + gateway = ip; + gateway[3] = 1; + myDns = gateway; + subnet = IPAddress(255,255,255,0); + } + if (!myDns) myDns = gateway; + if (!subnet) subnet = IPAddress(255,255,255,0); + Ethernet.begin(mac, ip, myDns, gateway, subnet); + } + + // Check for Ethernet hardware present + if (Ethernet.hardwareStatus() == EthernetNoHardware) { + SERIAL_ERROR_MSG("No Ethernet hardware found."); + linkState = NO_HARDWARE; + return; + } + + linkState = UNLINKED; + + if (Ethernet.linkStatus() == LinkOFF) + ethernet_cable_error(); +} + +void MarlinEthernet::check() { + if (!hardware_enabled) return; + + switch (linkState) { + case NO_HARDWARE: + break; + + case UNLINKED: + if (Ethernet.linkStatus() == LinkOFF) break; + + SERIAL_ECHOLNPGM("Ethernet cable connected"); + server.begin(); + linkState = LINKING; + break; + + case LINKING: + if (!Ethernet.localIP()) break; + + SERIAL_ECHOPGM("Successfully started telnet server with IP "); + MYSERIAL1.println(Ethernet.localIP()); + + linkState = LINKED; + break; + + case LINKED: + if (Ethernet.linkStatus() == LinkOFF) { + ethernet_cable_error(); + linkState = UNLINKED; + break; + } + telnetClient = server.accept(); + if (telnetClient) linkState = CONNECTING; + break; + + case CONNECTING: + telnetClient.println("Marlin " SHORT_BUILD_VERSION); + #ifdef STRING_DISTRIBUTION_DATE + telnetClient.println( + " Last Updated: " STRING_DISTRIBUTION_DATE + " | Author: " STRING_CONFIG_H_AUTHOR + ); + #endif + telnetClient.println(" Compiled: " __DATE__); + + SERIAL_ECHOLNPGM("Client connected"); + have_telnet_client = true; + linkState = CONNECTED; + break; + + case CONNECTED: + if (telnetClient && !telnetClient.connected()) { + SERIAL_ECHOLNPGM("Client disconnected"); + telnetClient.stop(); + have_telnet_client = false; + linkState = LINKED; + } + if (Ethernet.linkStatus() == LinkOFF) { + ethernet_cable_error(); + if (telnetClient) telnetClient.stop(); + linkState = UNLINKED; + } + break; + + default: break; + } +} + +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/HAL/STM32_F4_F7/Servo.h b/Marlin/src/feature/ethernet.h similarity index 59% rename from Marlin/src/HAL/STM32_F4_F7/Servo.h rename to Marlin/src/feature/ethernet.h index e42cc60840..7aa364fc3c 100644 --- a/Marlin/src/HAL/STM32_F4_F7/Servo.h +++ b/Marlin/src/feature/ethernet.h @@ -4,7 +4,6 @@ * * Based on Sprinter and grbl. * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm - * Copyright (c) 2017 Victor Perez * * 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 @@ -22,20 +21,25 @@ */ #pragma once -//#ifdef STM32F7 -// #include <../../libraries/Servo/src/Servo.h> -//#else - #include -//#endif +#ifdef __IMXRT1062__ + #include +#endif -// Inherit and expand on the official library -class libServo : public Servo { +#include "../HAL/shared/Marduino.h" + +// Teensy 4.1 uses internal MAC Address + +class MarlinEthernet { public: - int8_t attach(const int pin); - int8_t attach(const int pin, const int min, const int max); - void move(const int value); - private: - typedef Servo super; - uint16_t min_ticks, max_ticks; - uint8_t servoIndex; // index into the channel data for this servo + static bool hardware_enabled, have_telnet_client; + static IPAddress ip, myDns, gateway, subnet; + 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 new file mode 100644 index 0000000000..d0582ed97a --- /dev/null +++ b/Marlin/src/feature/fancheck.cpp @@ -0,0 +1,151 @@ +/** + * 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 . + * + */ + +/** + * fancheck.cpp - fan tachometer check + */ + +#include "../inc/MarlinConfig.h" + +#if HAS_FANCHECK + +#include "fancheck.h" +#include "../module/temperature.h" + +#if HAS_AUTO_FAN && EXTRUDER_AUTO_FAN_SPEED != 255 && DISABLED(FOURWIRES_FANS) + bool FanCheck::measuring = false; +#endif +Flags FanCheck::tacho_state; +uint16_t FanCheck::edge_counter[TACHO_COUNT]; +uint8_t FanCheck::rps[TACHO_COUNT]; +FanCheck::TachoError FanCheck::error = FanCheck::TachoError::NONE; +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) + #define _EN_TACHINIT(N) TERF(HAS_E##N##_FAN_TACHO, _TACHINIT)(N); + REPEAT(8, _EN_TACHINIT); +} + +void FanCheck::update_tachometers() { + bool status; + + #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) { + REPEAT(8, _TACHO_GET_STATUS) + default: continue; + } + + if (status != tacho_state[f]) { + if (measuring) ++edge_counter[f]; + tacho_state.set(f, status); + } + } +} + +void FanCheck::compute_speed(uint16_t elapsedTime) { + static uint8_t errors_count[TACHO_COUNT]; + static uint8_t fan_reported_errors_msk = 0; + + uint8_t fan_error_msk = 0; + for (uint8_t f = 0; f < TACHO_COUNT; ++f) { + switch (f) { + #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; + + // Check fan speed + constexpr int8_t max_extruder_fan_errors = TERN(HAS_PWMFANCHECK, 10000, 5000) / Temperature::fan_update_interval_ms; + + if (rps[f] >= 20 || TERN0(HAS_AUTO_FAN, thermalManager.autofan_speed[f] == 0)) + errors_count[f] = 0; + else if (errors_count[f] < max_extruder_fan_errors) + ++errors_count[f]; + else if (enabled) + SBI(fan_error_msk, f); + break; + } + } + + // Drop the error when all fans are ok + if (!fan_error_msk && error == TachoError::REPORTED) error = TachoError::FIXED; + + 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(); + } + + if (fan_error_msk & ~fan_reported_errors_msk) { + // Handle new faults only + for (uint8_t f = 0; f < TACHO_COUNT; ++f) if (TEST(fan_error_msk, f)) report_speed_error(f); + } + fan_reported_errors_msk = fan_error_msk; +} + +void FanCheck::report_speed_error(uint8_t fan) { + if (marlin.printJobOngoing()) { + if (error == TachoError::NONE) { + if (thermalManager.degTargetHotend(fan) != 0) { + marlin.kill(GET_TEXT_F(MSG_FAN_SPEED_FAULT)); + error = TachoError::REPORTED; + } + else + error = TachoError::DETECTED; // Plans error for next processed command + } + } + else if (!marlin.printingIsPaused()) { + thermalManager.setTargetHotend(0, fan); // Always disable heating + if (error == TachoError::NONE) error = TachoError::REPORTED; + } + + SERIAL_ERROR_MSG(STR_ERR_FANSPEED, fan); + LCD_ALERTMESSAGE(MSG_FAN_SPEED_FAULT); +} + +void FanCheck::print_fan_states() { + for (uint8_t s = 0; s < 2; ++s) { + for (uint8_t f = 0; f < TACHO_COUNT; ++f) { + switch (f) { + #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 "); + else + SERIAL_ECHOPGM("@:", TERN(HAS_AUTO_FAN, thermalManager.autofan_speed[f], 255), " "); + break; + } + } + } + SERIAL_EOL(); +} + +#if ENABLED(AUTO_REPORT_FANS) + AutoReporter FanCheck::auto_reporter; + void FanCheck::AutoReportFan::report() { print_fan_states(); } +#endif + +#endif // HAS_FANCHECK diff --git a/Marlin/src/feature/fancheck.h b/Marlin/src/feature/fancheck.h new file mode 100644 index 0000000000..fed6a798e1 --- /dev/null +++ b/Marlin/src/feature/fancheck.h @@ -0,0 +1,92 @@ +/** + * 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 + +#include "../inc/MarlinConfig.h" + +#if HAS_FANCHECK + +#include "../lcd/marlinui.h" + +#if ENABLED(AUTO_REPORT_FANS) + #include "../libs/autoreport.h" +#endif + +#if ENABLED(PARK_HEAD_ON_PAUSE) + #include "../gcode/queue.h" +#endif + +/** + * fancheck.h + */ +#define TACHO_COUNT TERN(HAS_E7_FAN_TACHO, 8, TERN(HAS_E6_FAN_TACHO, 7, TERN(HAS_E5_FAN_TACHO, 6, TERN(HAS_E4_FAN_TACHO, 5, TERN(HAS_E3_FAN_TACHO, 4, TERN(HAS_E2_FAN_TACHO, 3, TERN(HAS_E1_FAN_TACHO, 2, 1))))))) + +class FanCheck { + private: + + enum class TachoError : uint8_t { NONE, DETECTED, REPORTED, FIXED }; + + #if HAS_PWMFANCHECK + static bool measuring; // For future use (3 wires PWM controlled fans) + #else + static constexpr bool measuring = true; + #endif + static Flags tacho_state; + static uint16_t edge_counter[TACHO_COUNT]; + static uint8_t rps[TACHO_COUNT]; + static TachoError error; + + static void report_speed_error(uint8_t fan); + + public: + + static bool enabled; + + static void init(); + static void update_tachometers(); + static void compute_speed(uint16_t elapsedTime); + static void print_fan_states(); + #if HAS_PWMFANCHECK + 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; + #if ENABLED(PARK_HEAD_ON_PAUSE) + queue.inject(F("M125")); + #else + marlin.kill(GET_TEXT_F(MSG_FAN_SPEED_FAULT)); + #endif + } + } + + #if ENABLED(AUTO_REPORT_FANS) + struct AutoReportFan { static void report(); }; + static AutoReporter auto_reporter; + #endif +}; + +extern FanCheck fan_check; + +#endif // HAS_FANCHECK diff --git a/Marlin/src/feature/fanmux.h b/Marlin/src/feature/fanmux.h index b1b0c67a55..efb92cf198 100644 --- a/Marlin/src/feature/fanmux.h +++ b/Marlin/src/feature/fanmux.h @@ -25,5 +25,5 @@ * feature/fanmux.h - Cooling Fan Multiplexer support functions */ -extern void fanmux_switch(const uint8_t e); -extern void fanmux_init(); +void fanmux_switch(const uint8_t e); +void fanmux_init(); diff --git a/Marlin/src/feature/filwidth.cpp b/Marlin/src/feature/filwidth.cpp index 2bd9c78980..1142423cf7 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 @@ -42,7 +42,7 @@ int8_t FilamentWidthSensor::ratios[MAX_MEASUREMENT_DELAY + 1], // Ring void FilamentWidthSensor::init() { const int8_t ratio = sample_to_size_ratio(); - LOOP_L_N(i, COUNT(ratios)) ratios[i] = ratio; + for (uint8_t i = 0; i < COUNT(ratios); ++i) ratios[i] = ratio; index_r = index_w = 0; } diff --git a/Marlin/src/feature/filwidth.h b/Marlin/src/feature/filwidth.h index ef3859df71..a16240936a 100644 --- a/Marlin/src/feature/filwidth.h +++ b/Marlin/src/feature/filwidth.h @@ -41,9 +41,9 @@ public: FilamentWidthSensor() { init(); } static void init(); - static inline void enable(const bool ena) { enabled = ena; } + static void enable(const bool ena) { enabled = ena; } - static inline void set_delay_cm(const uint8_t cm) { + static void set_delay_cm(const uint8_t cm) { meas_delay_cm = _MIN(cm, MAX_MEASUREMENT_DELAY); } @@ -67,18 +67,18 @@ public: } // Convert raw measurement to mm - static inline float raw_to_mm(const uint16_t v) { return v * 5.0f * RECIPROCAL(float(MAX_RAW_THERMISTOR_VALUE)); } - static inline float raw_to_mm() { return raw_to_mm(raw); } + 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 // Divide to get to 0-16384 range since we used 1/128 IIR filter approach - static inline void reading_ready() { raw = accum >> 10; } + static void reading_ready() { raw = accum >> 10; } // Update mm from the raw measurement - static inline void update_measured_mm() { measured_mm = raw_to_mm(); } + static void update_measured_mm() { measured_mm = raw_to_mm(); } // Update ring buffer used to delay filament measurements - static inline void advance_e(const float &e_move) { + static void advance_e(const float e_move) { // Increment counters with the E distance e_count += e_move; @@ -106,7 +106,7 @@ public: } // Dynamically set the volumetric multiplier based on the delayed width measurement. - static inline void update_volumetric() { + static void update_volumetric() { if (enabled) { int8_t read_index = index_r - meas_delay_cm; if (read_index < 0) read_index += MMD_CM; // Loop around buffer if needed diff --git a/Marlin/src/feature/fwretract.cpp b/Marlin/src/feature/fwretract.cpp index e5c52562a9..5ea20401ea 100644 --- a/Marlin/src/feature/fwretract.cpp +++ b/Marlin/src/feature/fwretract.cpp @@ -34,7 +34,8 @@ FWRetract fwretract; // Single instance - this calls the constructor #include "../module/motion.h" #include "../module/planner.h" -#include "../module/stepper.h" + +#include "../gcode/gcode.h" #if ENABLED(RETRACT_SYNC_MIXING) #include "mixing.h" @@ -43,7 +44,7 @@ FWRetract fwretract; // Single instance - this calls the constructor // private: #if HAS_MULTI_EXTRUDER - bool FWRetract::retracted_swap[EXTRUDERS]; // Which extruders are swap-retracted + Flags FWRetract::retracted_swap; // Which extruders are swap-retracted #endif // public: @@ -54,7 +55,7 @@ fwretract_settings_t FWRetract::settings; // M207 S F Z W, M208 S F bool FWRetract::autoretract_enabled; // M209 S - Autoretract switch #endif -bool FWRetract::retracted[EXTRUDERS]; // Which extruders are currently retracted +Flags FWRetract::retracted; // Which extruders are currently retracted float FWRetract::current_retract[EXTRUDERS], // Retract value used by planner FWRetract::current_hop; @@ -71,10 +72,10 @@ void FWRetract::reset() { settings.swap_retract_recover_feedrate_mm_s = RETRACT_RECOVER_FEEDRATE_SWAP; current_hop = 0.0; - LOOP_L_N(i, EXTRUDERS) { - retracted[i] = false; - TERN_(HAS_MULTI_EXTRUDER, retracted_swap[i] = false); - current_retract[i] = 0.0; + retracted.reset(); + EXTRUDER_LOOP() { + E_TERN_(retracted_swap.clear(e)); + current_retract[e] = 0.0; } } @@ -89,11 +90,7 @@ void FWRetract::reset() { * Note: Auto-retract will apply the set Z hop in addition to any Z hop * included in the G-code. Use M207 Z0 to to prevent double hop. */ -void FWRetract::retract(const bool retracting - #if HAS_MULTI_EXTRUDER - , bool swapping/*=false*/ - #endif -) { +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; @@ -108,20 +105,20 @@ void FWRetract::retract(const bool retracting #endif /* // debugging - SERIAL_ECHOLNPAIR( - "retracting ", retracting, + SERIAL_ECHOLNPGM( + "retracting ", AS_DIGIT(retracting), " swapping ", swapping, " active extruder ", active_extruder ); - LOOP_L_N(i, EXTRUDERS) { - SERIAL_ECHOLNPAIR("retracted[", i, "] ", retracted[i]); + EXTRUDER_LOOP() { + SERIAL_ECHOLNPGM("retracted[", e, "] ", AS_DIGIT(retracted[e])); #if HAS_MULTI_EXTRUDER - SERIAL_ECHOLNPAIR("retracted_swap[", i, "] ", retracted_swap[i]); + SERIAL_ECHOLNPGM("retracted_swap[", e, "] ", AS_DIGIT(retracted_swap[e])); #endif } - SERIAL_ECHOLNPAIR("current_position.z ", current_position.z); - SERIAL_ECHOLNPAIR("current_position.e ", current_position.e); - SERIAL_ECHOLNPAIR("current_hop ", current_hop); + SERIAL_ECHOLNPGM("current_position.z ", current_position.z); + SERIAL_ECHOLNPGM("current_position.e ", current_position.e); + SERIAL_ECHOLNPGM("current_hop ", current_hop); //*/ const float base_retract = TERN1(RETRACT_SYNC_MIXING, (MIXING_STEPPERS)) @@ -139,8 +136,8 @@ void FWRetract::retract(const bool retracting 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 to destination - settings.retract_feedrate_mm_s * TERN1(RETRACT_SYNC_MIXING, (MIXING_STEPPERS)) + 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? @@ -167,35 +164,108 @@ void FWRetract::retract(const bool retracting current_retract[active_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; + 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[active_extruder] = retracting; // Active extruder now retracted / recovered + retracted.set(active_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[active_extruder] = retracting; + if (swapping) retracted_swap.set(active_extruder, retracting); #endif /* // debugging - SERIAL_ECHOLNPAIR("retracting ", retracting); - SERIAL_ECHOLNPAIR("swapping ", swapping); - SERIAL_ECHOLNPAIR("active_extruder ", active_extruder); - LOOP_L_N(i, EXTRUDERS) { - SERIAL_ECHOLNPAIR("retracted[", i, "] ", retracted[i]); + SERIAL_ECHOLNPGM("retracting ", AS_DIGIT(retracting)); + SERIAL_ECHOLNPGM("swapping ", AS_DIGIT(swapping)); + SERIAL_ECHOLNPGM("active_extruder ", active_extruder); + EXTRUDER_LOOP() { + SERIAL_ECHOLNPGM("retracted[", e, "] ", AS_DIGIT(retracted[e])); #if HAS_MULTI_EXTRUDER - SERIAL_ECHOLNPAIR("retracted_swap[", i, "] ", retracted_swap[i]); + SERIAL_ECHOLNPGM("retracted_swap[", e, "] ", AS_DIGIT(retracted_swap[e])); #endif } - SERIAL_ECHOLNPAIR("current_position.z ", current_position.z); - SERIAL_ECHOLNPAIR("current_position.e ", current_position.e); - SERIAL_ECHOLNPAIR("current_hop ", current_hop); + SERIAL_ECHOLNPGM("current_position.z ", current_position.z); + SERIAL_ECHOLNPGM("current_position.e ", current_position.e); + SERIAL_ECHOLNPGM("current_hop ", current_hop); //*/ } +/** + * M207: Set firmware retraction values + * + * S[+units] retract_length + * W[+units] swap_retract_length (multi-extruder) + * F[units/min] retract_feedrate_mm_s + * Z[units] retract_zraise + */ +void FWRetract::M207() { + if (!parser.seen("FSWZ")) return M207_report(); + if (parser.seenval('S')) settings.retract_length = parser.value_axis_units(E_AXIS); + if (parser.seenval('F')) settings.retract_feedrate_mm_s = MMM_TO_MMS(parser.value_axis_units(E_AXIS)); + if (parser.seenval('Z')) settings.retract_zraise = parser.value_linear_units(); + if (parser.seenval('W')) settings.swap_retract_length = parser.value_axis_units(E_AXIS); +} + +void FWRetract::M207_report() { + TERN_(MARLIN_SMALL_BUILD, return); + + SERIAL_ECHOLNPGM_P( + PSTR(" M207 S"), LINEAR_UNIT(settings.retract_length) + , PSTR(" W"), LINEAR_UNIT(settings.swap_retract_length) + , PSTR(" F"), LINEAR_UNIT(MMS_TO_MMM(settings.retract_feedrate_mm_s)) + , SP_Z_STR, LINEAR_UNIT(settings.retract_zraise) + ); +} + +/** + * M208: Set firmware un-retraction values + * + * S[+units] retract_recover_extra (in addition to M207 S*) + * W[+units] swap_retract_recover_extra (multi-extruder) + * F[units/min] retract_recover_feedrate_mm_s + * R[units/min] swap_retract_recover_feedrate_mm_s + */ +void FWRetract::M208() { + if (!parser.seen("FSRW")) return M208_report(); + if (parser.seen('S')) settings.retract_recover_extra = parser.value_axis_units(E_AXIS); + if (parser.seen('F')) settings.retract_recover_feedrate_mm_s = MMM_TO_MMS(parser.value_axis_units(E_AXIS)); + if (parser.seen('R')) settings.swap_retract_recover_feedrate_mm_s = MMM_TO_MMS(parser.value_axis_units(E_AXIS)); + if (parser.seen('W')) settings.swap_retract_recover_extra = parser.value_axis_units(E_AXIS); +} + +void FWRetract::M208_report() { + TERN_(MARLIN_SMALL_BUILD, return); + + SERIAL_ECHOLNPGM( + " 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)) + ); +} + +#if ENABLED(FWRETRACT_AUTORETRACT) + + /** + * M209: Enable automatic retract (M209 S1) + * For slicers that don't support G10/11, reversed extrude-only + * moves will be classified as retraction. + */ + void FWRetract::M209() { + if (!parser.seen('S')) return M209_report(); + if (MIN_AUTORETRACT <= MAX_AUTORETRACT) + enable_autoretract(parser.value_bool()); + } + + void FWRetract::M209_report() { + TERN_(MARLIN_SMALL_BUILD, return); + + SERIAL_ECHOLNPGM(" M209 S", AS_DIGIT(autoretract_enabled)); + } + +#endif // FWRETRACT_AUTORETRACT + #endif // FWRETRACT diff --git a/Marlin/src/feature/fwretract.h b/Marlin/src/feature/fwretract.h index 134851965d..db2a62c8d4 100644 --- a/Marlin/src/feature/fwretract.h +++ b/Marlin/src/feature/fwretract.h @@ -43,7 +43,7 @@ typedef struct { class FWRetract { private: #if HAS_MULTI_EXTRUDER - static bool retracted_swap[EXTRUDERS]; // Which extruders are swap-retracted + static Flags retracted_swap; // Which extruders are swap-retracted #endif public: @@ -55,7 +55,7 @@ public: static constexpr bool autoretract_enabled = false; #endif - static bool retracted[EXTRUDERS]; // Which extruders are currently retracted + static Flags retracted; // Which extruders are currently retracted static float current_retract[EXTRUDERS], // Retract value used by planner current_hop; // Hop value used by planner @@ -63,9 +63,7 @@ public: static void reset(); - static void refresh_autoretract() { - LOOP_L_N(i, EXTRUDERS) retracted[i] = false; - } + static void refresh_autoretract() { retracted.reset(); } static void enable_autoretract(const bool enable) { #if ENABLED(FWRETRACT_AUTORETRACT) @@ -74,11 +72,16 @@ public: #endif } - static void retract(const bool retracting - #if HAS_MULTI_EXTRUDER - , bool swapping = false - #endif - ); + static void retract(const bool retracting E_OPTARG(bool swapping=false)); + + static void M207_report(); + static void M207(); + static void M208_report(); + static void M208(); + #if ENABLED(FWRETRACT_AUTORETRACT) + static void M209_report(); + static void M209(); + #endif }; extern FWRetract fwretract; diff --git a/Marlin/src/feature/host_actions.cpp b/Marlin/src/feature/host_actions.cpp index 3012639220..94bc4db011 100644 --- a/Marlin/src/feature/host_actions.cpp +++ b/Marlin/src/feature/host_actions.cpp @@ -24,10 +24,10 @@ #if ENABLED(HOST_ACTION_COMMANDS) -#include "host_actions.h" - //#define DEBUG_HOST_ACTIONS +#include "host_actions.h" + #if ENABLED(ADVANCED_PAUSE_FEATURE) #include "pause.h" #include "../gcode/queue.h" @@ -37,96 +37,136 @@ #include "runout.h" #endif -void host_action(PGM_P const pstr, const bool eol) { - PORT_REDIRECT(SERIAL_BOTH); - SERIAL_ECHOPGM("//action:"); - serialprintPGM(pstr); +HostUI hostui; + +void HostUI::action(FSTR_P const fstr, const bool eol) { + PORT_REDIRECT(SerialMask::All); + SERIAL_ECHOPGM("//action:", fstr); if (eol) SERIAL_EOL(); } #ifdef ACTION_ON_KILL - void host_action_kill() { host_action(PSTR(ACTION_ON_KILL)); } + void HostUI::kill() { action(F(ACTION_ON_KILL)); } #endif #ifdef ACTION_ON_PAUSE - void host_action_pause(const bool eol/*=true*/) { host_action(PSTR(ACTION_ON_PAUSE), eol); } + void HostUI::pause(const bool eol/*=true*/) { action(F(ACTION_ON_PAUSE), eol); } #endif #ifdef ACTION_ON_PAUSED - void host_action_paused(const bool eol/*=true*/) { host_action(PSTR(ACTION_ON_PAUSED), eol); } + void HostUI::paused(const bool eol/*=true*/) { action(F(ACTION_ON_PAUSED), eol); } #endif #ifdef ACTION_ON_RESUME - void host_action_resume() { host_action(PSTR(ACTION_ON_RESUME)); } + void HostUI::resume() { action(F(ACTION_ON_RESUME)); } #endif #ifdef ACTION_ON_RESUMED - void host_action_resumed() { host_action(PSTR(ACTION_ON_RESUMED)); } + void HostUI::resumed() { action(F(ACTION_ON_RESUMED)); } #endif #ifdef ACTION_ON_CANCEL - void host_action_cancel() { host_action(PSTR(ACTION_ON_CANCEL)); } + void HostUI::cancel() { action(F(ACTION_ON_CANCEL)); } #endif #ifdef ACTION_ON_START - void host_action_start() { host_action(PSTR(ACTION_ON_START)); } + void HostUI::start() { action(F(ACTION_ON_START)); } +#endif + +#if ENABLED(G29_RETRY_AND_RECOVER) + #ifdef ACTION_ON_G29_RECOVER + void HostUI::g29_recover() { action(F(ACTION_ON_G29_RECOVER)); } + #endif + #ifdef ACTION_ON_G29_FAILURE + void HostUI::g29_failure() { action(F(ACTION_ON_G29_FAILURE)); } + #endif +#endif + +#ifdef SHUTDOWN_ACTION + void HostUI::shutdown() { action(F(SHUTDOWN_ACTION)); } #endif #if ENABLED(HOST_PROMPT_SUPPORT) + PromptReason HostUI::host_prompt_reason = PROMPT_NOT_DEFINED; + PGMSTR(CONTINUE_STR, "Continue"); PGMSTR(DISMISS_STR, "Dismiss"); - #if HAS_RESUME_CONTINUE - extern bool wait_for_user; - #endif - - PromptReason host_prompt_reason = PROMPT_NOT_DEFINED; - - void host_action_notify(const char * const message) { - PORT_REDIRECT(SERIAL_BOTH); - host_action(PSTR("notification "), false); - SERIAL_ECHOLN(message); + void HostUI::notify(const char * const cstr) { + PORT_REDIRECT(SerialMask::All); + action(F("notification "), false); + SERIAL_ECHOLN(cstr); } - void host_action_notify_P(PGM_P const message) { - PORT_REDIRECT(SERIAL_BOTH); - host_action(PSTR("notification "), false); - serialprintPGM(message); - SERIAL_EOL(); + void HostUI::notify_P(PGM_P const pstr) { + PORT_REDIRECT(SerialMask::All); + action(F("notification "), false); + SERIAL_ECHOLNPGM_P(pstr); } - void host_action_prompt(PGM_P const ptype, const bool eol=true) { - PORT_REDIRECT(SERIAL_BOTH); - host_action(PSTR("prompt_"), false); - serialprintPGM(ptype); + void HostUI::prompt(FSTR_P const ptype, const bool eol/*=true*/) { + PORT_REDIRECT(SerialMask::All); + action(F("prompt_"), false); + SERIAL_ECHO(ptype); if (eol) SERIAL_EOL(); } - void host_action_prompt_plus(PGM_P const ptype, PGM_P const pstr, const char extra_char='\0') { - host_action_prompt(ptype, false); - PORT_REDIRECT(SERIAL_BOTH); + void HostUI::prompt_plus(const bool pgm, FSTR_P const ptype, const char * const str, const char extra_char/*='\0'*/) { + prompt(ptype, false); + PORT_REDIRECT(SerialMask::All); SERIAL_CHAR(' '); - serialprintPGM(pstr); + if (pgm) + SERIAL_ECHOPGM_P(str); + else + SERIAL_ECHO(str); if (extra_char != '\0') SERIAL_CHAR(extra_char); SERIAL_EOL(); } - void host_action_prompt_begin(const PromptReason reason, PGM_P const pstr, const char extra_char/*='\0'*/) { - host_action_prompt_end(); + + void HostUI::prompt_begin(const PromptReason reason, FSTR_P const fstr, const char extra_char/*='\0'*/) { + prompt_end(); host_prompt_reason = reason; - host_action_prompt_plus(PSTR("begin"), pstr, extra_char); + prompt_plus(F("begin"), fstr, extra_char); } - void host_action_prompt_button(PGM_P const pstr) { host_action_prompt_plus(PSTR("button"), pstr); } - void host_action_prompt_end() { host_action_prompt(PSTR("end")); } - void host_action_prompt_show() { host_action_prompt(PSTR("show")); } - void host_prompt_do(const PromptReason reason, PGM_P const pstr, PGM_P const btn1/*=nullptr*/, PGM_P const btn2/*=nullptr*/) { - host_action_prompt_begin(reason, pstr); - if (btn1) host_action_prompt_button(btn1); - if (btn2) host_action_prompt_button(btn2); - host_action_prompt_show(); + void HostUI::prompt_begin(const PromptReason reason, const char * const cstr, const char extra_char/*='\0'*/) { + prompt_end(); + host_prompt_reason = reason; + prompt_plus(F("begin"), cstr, extra_char); } - void filament_load_host_prompt() { - const bool disable_to_continue = TERN0(HAS_FILAMENT_SENSOR, runout.filament_ran_out); - host_prompt_do(PROMPT_FILAMENT_RUNOUT, PSTR("Paused"), PSTR("PurgeMore"), - disable_to_continue ? PSTR("DisableRunout") : CONTINUE_STR - ); + void HostUI::prompt_end() { prompt(F("end")); } + void HostUI::prompt_show() { prompt(F("show")); } + + void HostUI::_prompt_show(FSTR_P const btn1, FSTR_P const btn2) { + if (btn1) prompt_button(btn1); + if (btn2) prompt_button(btn2); + prompt_show(); } + void HostUI::prompt_button(FSTR_P const fstr) { prompt_plus(F("button"), fstr); } + void HostUI::prompt_button(const char * const cstr) { prompt_plus(F("button"), cstr); } + + void HostUI::prompt_do(const PromptReason reason, FSTR_P const fstr, FSTR_P const btn1/*=nullptr*/, FSTR_P const btn2/*=nullptr*/) { + prompt_begin(reason, fstr); + _prompt_show(btn1, btn2); + } + void HostUI::prompt_do(const PromptReason reason, const char * const cstr, FSTR_P const btn1/*=nullptr*/, FSTR_P const btn2/*=nullptr*/) { + prompt_begin(reason, cstr); + _prompt_show(btn1, btn2); + } + void HostUI::prompt_do(const PromptReason reason, FSTR_P const fstr, const char extra_char, FSTR_P const btn1/*=nullptr*/, FSTR_P const btn2/*=nullptr*/) { + prompt_begin(reason, fstr, extra_char); + _prompt_show(btn1, btn2); + } + void HostUI::prompt_do(const PromptReason reason, const char * const cstr, const char extra_char, FSTR_P const btn1/*=nullptr*/, FSTR_P const btn2/*=nullptr*/) { + prompt_begin(reason, cstr, extra_char); + _prompt_show(btn1, btn2); + } + + #if ENABLED(ADVANCED_PAUSE_FEATURE) + void HostUI::filament_load_prompt() { + const bool disable_to_continue = TERN0(HAS_FILAMENT_SENSOR, runout.filament_ran_out); + prompt_do(PROMPT_FILAMENT_RUNOUT, F("Paused"), F("PurgeMore"), + disable_to_continue ? F("DisableRunout") : FPSTR(CONTINUE_STR) + ); + } + #endif + // // Handle responses from the host, such as: // - Filament runout responses: Purge More, Continue @@ -134,29 +174,21 @@ void host_action(PGM_P const pstr, const bool eol) { // - Resume Print response // - Dismissal of info // - void host_response_handler(const uint8_t response) { - #ifdef DEBUG_HOST_ACTIONS - static PGMSTR(m876_prefix, "M876 Handle Re"); - serialprintPGM(m876_prefix); SERIAL_ECHOLNPAIR("ason: ", host_prompt_reason); - serialprintPGM(m876_prefix); SERIAL_ECHOLNPAIR("sponse: ", response); - #endif - PGM_P msg = PSTR("UNKNOWN STATE"); + void HostUI::handle_response(const uint8_t response) { const PromptReason hpr = host_prompt_reason; host_prompt_reason = PROMPT_NOT_DEFINED; // Reset now ahead of logic switch (hpr) { case PROMPT_FILAMENT_RUNOUT: - msg = PSTR("FILAMENT_RUNOUT"); switch (response) { case 0: // "Purge More" button - #if BOTH(HAS_LCD_MENU, 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 - filament_load_host_prompt(); // Initiate another host prompt. (NOTE: The loop in load_filament may also do this!) break; case 1: // "Continue" / "Disable Runout" button - #if BOTH(HAS_LCD_MENU, ADVANCED_PAUSE_FEATURE) + #if ENABLED(M600_PURGE_MORE_RESUMABLE) pause_menu_response = PAUSE_RESPONSE_RESUME_PRINT; // Simulate menu selection #endif #if HAS_FILAMENT_SENSOR @@ -169,24 +201,18 @@ void host_action(PGM_P const pstr, const bool eol) { } break; case PROMPT_USER_CONTINUE: - TERN_(HAS_RESUME_CONTINUE, wait_for_user = false); - msg = PSTR("FILAMENT_RUNOUT_CONTINUE"); + TERN_(HAS_RESUME_CONTINUE, marlin.user_resume()); break; case PROMPT_PAUSE_RESUME: - msg = PSTR("LCD_PAUSE_RESUME"); - #if ENABLED(ADVANCED_PAUSE_FEATURE) + #if ALL(ADVANCED_PAUSE_FEATURE, HAS_MEDIA) extern const char M24_STR[]; queue.inject_P(M24_STR); #endif break; case PROMPT_INFO: - msg = PSTR("GCODE_INFO"); break; default: break; } - SERIAL_ECHOPGM("M876 Responding PROMPT_"); - serialprintPGM(msg); - SERIAL_EOL(); } #endif // HOST_PROMPT_SUPPORT diff --git a/Marlin/src/feature/host_actions.h b/Marlin/src/feature/host_actions.h index 09eeed23e2..c030ebad01 100644 --- a/Marlin/src/feature/host_actions.h +++ b/Marlin/src/feature/host_actions.h @@ -24,34 +24,8 @@ #include "../inc/MarlinConfigPre.h" #include "../HAL/shared/Marduino.h" -void host_action(PGM_P const pstr, const bool eol=true); - -#ifdef ACTION_ON_KILL - void host_action_kill(); -#endif -#ifdef ACTION_ON_PAUSE - void host_action_pause(const bool eol=true); -#endif -#ifdef ACTION_ON_PAUSED - void host_action_paused(const bool eol=true); -#endif -#ifdef ACTION_ON_RESUME - void host_action_resume(); -#endif -#ifdef ACTION_ON_RESUMED - void host_action_resumed(); -#endif -#ifdef ACTION_ON_CANCEL - void host_action_cancel(); -#endif -#ifdef ACTION_ON_START - void host_action_start(); -#endif - #if ENABLED(HOST_PROMPT_SUPPORT) - extern const char CONTINUE_STR[], DISMISS_STR[]; - enum PromptReason : uint8_t { PROMPT_NOT_DEFINED, PROMPT_FILAMENT_RUNOUT, @@ -61,20 +35,97 @@ void host_action(PGM_P const pstr, const bool eol=true); PROMPT_INFO }; - extern PromptReason host_prompt_reason; - - void host_response_handler(const uint8_t response); - void host_action_notify(const char * const message); - void host_action_notify_P(PGM_P const message); - void host_action_prompt_begin(const PromptReason reason, PGM_P const pstr, const char extra_char='\0'); - void host_action_prompt_button(PGM_P const pstr); - void host_action_prompt_end(); - void host_action_prompt_show(); - void host_prompt_do(const PromptReason reason, PGM_P const pstr, PGM_P const btn1=nullptr, PGM_P const btn2=nullptr); - inline void host_prompt_open(const PromptReason reason, PGM_P const pstr, PGM_P const btn1=nullptr, PGM_P const btn2=nullptr) { - if (host_prompt_reason == PROMPT_NOT_DEFINED) host_prompt_do(reason, pstr, btn1, btn2); - } - - void filament_load_host_prompt(); + extern const char CONTINUE_STR[], DISMISS_STR[]; #endif + +class HostUI { + public: + + static void action(FSTR_P const fstr, const bool eol=true); + + #ifdef ACTION_ON_KILL + static void kill(); + #endif + #ifdef ACTION_ON_PAUSE + static void pause(const bool eol=true); + #endif + #ifdef ACTION_ON_PAUSED + static void paused(const bool eol=true); + #endif + #ifdef ACTION_ON_RESUME + static void resume(); + #endif + #ifdef ACTION_ON_RESUMED + static void resumed(); + #endif + #ifdef ACTION_ON_CANCEL + static void cancel(); + #endif + #ifdef ACTION_ON_START + static void start(); + #endif + #ifdef SHUTDOWN_ACTION + static void shutdown(); + #endif + + #if ENABLED(G29_RETRY_AND_RECOVER) + #ifdef ACTION_ON_G29_RECOVER + static void g29_recover(); + #endif + #ifdef ACTION_ON_G29_FAILURE + static void g29_failure(); + #endif + #endif + + #if ENABLED(HOST_PROMPT_SUPPORT) + private: + static void prompt(FSTR_P const ptype, const bool eol=true); + static void prompt_plus(const bool pgm, FSTR_P const ptype, const char * const str, const char extra_char='\0'); + static void prompt_plus(FSTR_P const ptype, FSTR_P const fstr, const char extra_char='\0') { + prompt_plus(true, ptype, FTOP(fstr), extra_char); + } + static void prompt_plus(FSTR_P const ptype, const char * const cstr, const char extra_char='\0') { + prompt_plus(false, ptype, cstr, extra_char); + } + + static void prompt_show(); + static void _prompt_show(FSTR_P const btn1, FSTR_P const btn2); + + public: + static PromptReason host_prompt_reason; + + static void handle_response(const uint8_t response); + + static void notify_P(PGM_P const message); + static void notify(FSTR_P const fmsg) { notify_P(FTOP(fmsg)); } + static void notify(const char * const message); + + static void prompt_begin(const PromptReason reason, FSTR_P const fstr, const char extra_char='\0'); + static void prompt_begin(const PromptReason reason, const char * const cstr, const char extra_char='\0'); + static void prompt_end(); + + static void prompt_button(FSTR_P const fstr); + static void prompt_button(const char * const cstr); + + static void prompt_do(const PromptReason reason, FSTR_P const pstr, FSTR_P const btn1=nullptr, FSTR_P const btn2=nullptr); + static void prompt_do(const PromptReason reason, const char * const cstr, FSTR_P const btn1=nullptr, FSTR_P const btn2=nullptr); + static void prompt_do(const PromptReason reason, FSTR_P const pstr, const char extra_char, FSTR_P const btn1=nullptr, FSTR_P const btn2=nullptr); + static void prompt_do(const PromptReason reason, const char * const cstr, const char extra_char, FSTR_P const btn1=nullptr, FSTR_P const btn2=nullptr); + + static void continue_prompt(FSTR_P const fstr) { prompt_do(PROMPT_USER_CONTINUE, fstr, FPSTR(CONTINUE_STR)); } + static void continue_prompt(const char * const cstr) { prompt_do(PROMPT_USER_CONTINUE, cstr, FPSTR(CONTINUE_STR)); } + + static void prompt_open(const PromptReason reason, FSTR_P const pstr, FSTR_P const btn1=nullptr, FSTR_P const btn2=nullptr) { + if (host_prompt_reason == PROMPT_NOT_DEFINED) prompt_do(reason, pstr, btn1, btn2); + } + + #if ENABLED(ADVANCED_PAUSE_FEATURE) + static void filament_load_prompt(); + #endif + + #endif + +}; + +extern HostUI hostui; diff --git a/Marlin/src/feature/hotend_idle.cpp b/Marlin/src/feature/hotend_idle.cpp index 9d5594c2f1..a779efeaff 100644 --- a/Marlin/src/feature/hotend_idle.cpp +++ b/Marlin/src/feature/hotend_idle.cpp @@ -34,29 +34,36 @@ #include "../module/temperature.h" #include "../module/motion.h" -#include "../lcd/ultralcd.h" +#include "../module/planner.h" +#include "../lcd/marlinui.h" -extern HotendIdleProtection hotend_idle; +HotendIdleProtection hotend_idle; millis_t HotendIdleProtection::next_protect_ms = 0; +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, marlin.wait_for_user) || planner.has_blocks_queued()); bool do_prot = false; - HOTEND_LOOP() { - if (thermalManager.degHotend(e) >= HOTEND_IDLE_MIN_TRIGGER) { - do_prot = true; break; + if (!busy && cfg.timeout != 0) { + HOTEND_LOOP() { + if (thermalManager.degHotend(e) >= cfg.trigger) { + do_prot = true; break; + } } } - if (bool(next_protect_ms) != do_prot) - next_protect_ms = do_prot ? ms + hp_interval : 0; + if (!do_prot) + next_protect_ms = 0; // No hotends are hot so cancel timeout + else if (!next_protect_ms) // Timeout is possible? + next_protect_ms = ms + 1000UL * cfg.timeout; // Start timeout if not already set } 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 (next_protect_ms) // If some heater is on then... - next_protect_ms = ms + hp_interval; // ...delay the timeout till later + old_e_position = current_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 } } @@ -75,14 +82,14 @@ void HotendIdleProtection::check() { void HotendIdleProtection::timed_out() { next_protect_ms = 0; SERIAL_ECHOLNPGM("Hotend Idle Timeout"); - LCD_MESSAGEPGM(MSG_HOTEND_IDLE_TIMEOUT); + LCD_MESSAGE(MSG_HOTEND_IDLE_TIMEOUT); HOTEND_LOOP() { - if ((HOTEND_IDLE_NOZZLE_TARGET) < thermalManager.degTargetHotend(e)) - thermalManager.setTargetHotend(HOTEND_IDLE_NOZZLE_TARGET, e); + if (cfg.nozzle_target < thermalManager.degTargetHotend(e)) + thermalManager.setTargetHotend(cfg.nozzle_target, e); } #if HAS_HEATED_BED - if ((HOTEND_IDLE_BED_TARGET) < thermalManager.degTargetBed()) - thermalManager.setTargetBed(HOTEND_IDLE_BED_TARGET); + if (cfg.bed_target < thermalManager.degTargetBed()) + thermalManager.setTargetBed(cfg.bed_target); #endif } diff --git a/Marlin/src/feature/hotend_idle.h b/Marlin/src/feature/hotend_idle.h index 40f557d5ed..d215e27bc5 100644 --- a/Marlin/src/feature/hotend_idle.h +++ b/Marlin/src/feature/hotend_idle.h @@ -21,13 +21,28 @@ */ #pragma once -#include "../core/millis_t.h" +#include "../inc/MarlinConfig.h" + +typedef struct { + int16_t timeout, trigger, nozzle_target; + #if HAS_HEATED_BED + int16_t bed_target; + #endif + void set_defaults() { + timeout = HOTEND_IDLE_TIMEOUT_SEC; + trigger = HOTEND_IDLE_MIN_TRIGGER; + nozzle_target = HOTEND_IDLE_NOZZLE_TARGET; + #if HAS_HEATED_BED + bed_target = HOTEND_IDLE_BED_TARGET; + #endif + } +} hotend_idle_settings_t; class HotendIdleProtection { public: static void check(); + static hotend_idle_settings_t cfg; private: - static constexpr millis_t hp_interval = SEC_TO_MS(HOTEND_IDLE_TIMEOUT_SEC); static millis_t next_protect_ms; static void check_hotends(const millis_t &ms); static void check_e_motion(const millis_t &ms); diff --git a/Marlin/src/feature/joystick.cpp b/Marlin/src/feature/joystick.cpp index d9c5ae7c1b..cbfab1fed8 100644 --- a/Marlin/src/feature/joystick.cpp +++ b/Marlin/src/feature/joystick.cpp @@ -32,7 +32,6 @@ #include "../inc/MarlinConfig.h" // for pins #include "../module/planner.h" -#include "../module/temperature.h" Joystick joystick; @@ -68,18 +67,10 @@ Joystick joystick; #if ENABLED(JOYSTICK_DEBUG) void Joystick::report() { SERIAL_ECHOPGM("Joystick"); - #if HAS_JOY_ADC_X - SERIAL_ECHOPAIR_P(SP_X_STR, JOY_X(x.raw)); - #endif - #if HAS_JOY_ADC_Y - SERIAL_ECHOPAIR_P(SP_Y_STR, JOY_Y(y.raw)); - #endif - #if HAS_JOY_ADC_Z - SERIAL_ECHOPAIR_P(SP_Z_STR, JOY_Z(z.raw)); - #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 @@ -92,29 +83,29 @@ Joystick joystick; if (READ(JOY_EN_PIN)) return; #endif - auto _normalize_joy = [](float &axis_jog, const int16_t raw, const int16_t (&joy_limits)[4]) { + auto _normalize_joy = [](float &axis_jog, const raw_adc_t raw, const raw_adc_t (&joy_limits)[4]) { if (WITHIN(raw, joy_limits[0], joy_limits[3])) { // within limits, check deadzone if (raw > joy_limits[2]) axis_jog = (raw - joy_limits[2]) / float(joy_limits[3] - joy_limits[2]); else if (raw < joy_limits[1]) - axis_jog = (raw - joy_limits[1]) / float(joy_limits[1] - joy_limits[0]); // negative value + axis_jog = int16_t(raw - joy_limits[1]) / float(joy_limits[1] - joy_limits[0]); // negative value // Map normal to jog value via quadratic relationship axis_jog = SIGN(axis_jog) * sq(axis_jog); } }; #if HAS_JOY_ADC_X - static constexpr int16_t joy_x_limits[4] = JOY_X_LIMITS; - _normalize_joy(norm_jog.x, JOY_X(x.raw), joy_x_limits); + static constexpr raw_adc_t joy_x_limits[4] = JOY_X_LIMITS; + _normalize_joy(norm_jog.x, JOY_X(x.getraw()), joy_x_limits); #endif #if HAS_JOY_ADC_Y - static constexpr int16_t joy_y_limits[4] = JOY_Y_LIMITS; - _normalize_joy(norm_jog.y, JOY_Y(y.raw), joy_y_limits); + static constexpr raw_adc_t joy_y_limits[4] = JOY_Y_LIMITS; + _normalize_joy(norm_jog.y, JOY_Y(y.getraw()), joy_y_limits); #endif #if HAS_JOY_ADC_Z - static constexpr int16_t joy_z_limits[4] = JOY_Z_LIMITS; - _normalize_joy(norm_jog.z, JOY_Z(z.raw), joy_z_limits); + static constexpr raw_adc_t joy_z_limits[4] = JOY_Z_LIMITS; + _normalize_joy(norm_jog.z, JOY_Z(z.getraw()), joy_z_limits); #endif } @@ -124,9 +115,14 @@ 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))) + return; + #endif + static constexpr int QUEUE_DEPTH = 5; // Insert up to this many movements static constexpr float target_lag = 0.25f, // Aim for 1/4 second lag seg_time = target_lag / QUEUE_DEPTH; // 0.05 seconds, short segments inserted every 1/20th of a second @@ -159,13 +155,8 @@ Joystick joystick; // norm_jog values of [-1 .. 1] maps linearly to [-feedrate .. feedrate] xyz_float_t move_dist{0}; float hypot2 = 0; - LOOP_XYZ(i) if (norm_jog[i]) { - move_dist[i] = seg_time * norm_jog[i] * - #if ENABLED(EXTENSIBLE_UI) - manual_feedrate_mm_s[i]; - #else - planner.settings.max_feedrate_mm_s[i]; - #endif + LOOP_NUM_AXES(i) if (norm_jog[i]) { + move_dist[i] = seg_time * norm_jog[i] * TERN(EXTENSIBLE_UI, manual_feedrate_mm_s, planner.settings.max_feedrate_mm_s)[i]; hypot2 += sq(move_dist[i]); } @@ -173,8 +164,9 @@ Joystick joystick; current_position += move_dist; apply_motion_limits(current_position); const float length = sqrt(hypot2); + PlannerHints hints(length); injecting_now = true; - planner.buffer_line(current_position, length / seg_time, active_extruder, length); + planner.buffer_line(current_position, length / seg_time, active_extruder, hints); injecting_now = false; } } diff --git a/Marlin/src/feature/joystick.h b/Marlin/src/feature/joystick.h index 1d25a30cc2..91bf6bdc00 100644 --- a/Marlin/src/feature/joystick.h +++ b/Marlin/src/feature/joystick.h @@ -27,17 +27,24 @@ #include "../inc/MarlinConfigPre.h" #include "../core/types.h" -#include "../core/macros.h" #include "../module/temperature.h" class Joystick { friend class Temperature; private: - TERN_(HAS_JOY_ADC_X, static temp_info_t x); - TERN_(HAS_JOY_ADC_Y, static temp_info_t y); - TERN_(HAS_JOY_ADC_Z, static temp_info_t z); + #if HAS_JOY_ADC_X + static temp_info_t x; + #endif + #if HAS_JOY_ADC_Y + static temp_info_t y; + #endif + #if HAS_JOY_ADC_Z + static temp_info_t z; + #endif public: - TERN_(JOYSTICK_DEBUG, static void report()); + #if ENABLED(JOYSTICK_DEBUG) + static void report(); + #endif static void calculate(xyz_float_t &norm_jog); static void inject_jog_moves(); }; diff --git a/Marlin/src/feature/leds/blinkm.cpp b/Marlin/src/feature/leds/blinkm.cpp index 868eb4b3d9..b040c8e76f 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 29a9e78412..d3c528acbf 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 ef9099fb20..55a10fcbbd 100644 --- a/Marlin/src/feature/leds/leds.cpp +++ b/Marlin/src/feature/leds/leds.cpp @@ -30,75 +30,171 @@ #include "leds.h" -#if ENABLED(BLINKM) - #include "blinkm.h" -#endif - -#if ENABLED(PCA9632) - #include "pca9632.h" -#endif - -#if ENABLED(PCA9533) - #include "pca9533.h" +#if ANY(CASE_LIGHT_USE_RGB_LED, CASE_LIGHT_USE_NEOPIXEL) + #include "../caselight.h" #endif #if ENABLED(LED_COLOR_PRESETS) - const LEDColor LEDLights::defaultLEDColor = MakeLEDColor( - LED_USER_PRESET_RED, LED_USER_PRESET_GREEN, LED_USER_PRESET_BLUE, - LED_USER_PRESET_WHITE, LED_USER_PRESET_BRIGHTNESS - ); + 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 EITHER(LED_CONTROL_MENU, PRINTER_EVENT_LEDS) - LEDColor LEDLights::color; +#if ANY(LED_CONTROL_MENU, PRINTER_EVENT_LEDS, CASE_LIGHT_IS_COLOR_LED) + LED1Color_t LEDLights::color; bool LEDLights::lights_on; #endif LEDLights leds; void LEDLights::setup() { - #if EITHER(RGB_LED, RGBW_LED) + #if ANY(RGB_LED, RGBW_LED) if (PWM_PIN(RGB_LED_R_PIN)) SET_PWM(RGB_LED_R_PIN); else SET_OUTPUT(RGB_LED_R_PIN); if (PWM_PIN(RGB_LED_G_PIN)) SET_PWM(RGB_LED_G_PIN); else SET_OUTPUT(RGB_LED_G_PIN); if (PWM_PIN(RGB_LED_B_PIN)) SET_PWM(RGB_LED_B_PIN); else SET_OUTPUT(RGB_LED_B_PIN); #if ENABLED(RGBW_LED) if (PWM_PIN(RGB_LED_W_PIN)) SET_PWM(RGB_LED_W_PIN); else SET_OUTPUT(RGB_LED_W_PIN); #endif - #endif + + #if ENABLED(RGB_STARTUP_TEST) + int8_t led_pin_count = 0; + if (PWM_PIN(RGB_LED_R_PIN) && PWM_PIN(RGB_LED_G_PIN) && PWM_PIN(RGB_LED_B_PIN)) led_pin_count = 3; + #if ENABLED(RGBW_LED) + if (PWM_PIN(RGB_LED_W_PIN) && led_pin_count) led_pin_count++; + #endif + // Startup animation + if (led_pin_count) { + // blackout + if (PWM_PIN(RGB_LED_R_PIN)) hal.set_pwm_duty(pin_t(RGB_LED_R_PIN), 0); else WRITE(RGB_LED_R_PIN, LOW); + if (PWM_PIN(RGB_LED_G_PIN)) hal.set_pwm_duty(pin_t(RGB_LED_G_PIN), 0); else WRITE(RGB_LED_G_PIN, LOW); + if (PWM_PIN(RGB_LED_B_PIN)) hal.set_pwm_duty(pin_t(RGB_LED_B_PIN), 0); else WRITE(RGB_LED_B_PIN, LOW); + #if ENABLED(RGBW_LED) + if (PWM_PIN(RGB_LED_W_PIN)) hal.set_pwm_duty(pin_t(RGB_LED_W_PIN), 0); + else WRITE(RGB_LED_W_PIN, LOW); + #endif + delay(200); + + for (uint8_t i = 0; i < led_pin_count; ++i) { + for (uint8_t b = 0; b <= 200; ++b) { + const uint16_t led_pwm = b <= 100 ? b : 200 - b; + if (i == 0 && PWM_PIN(RGB_LED_R_PIN)) hal.set_pwm_duty(pin_t(RGB_LED_R_PIN), led_pwm); else WRITE(RGB_LED_R_PIN, b < 100 ? HIGH : LOW); + 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 (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 + } + #endif + delay(RGB_STARTUP_TEST_INNER_MS); + } + } + delay(500); + } + #endif // RGB_STARTUP_TEST + + #elif ALL(PCA9632, RGB_STARTUP_TEST) // PCA9632 RGB_STARTUP_TEST + + constexpr int8_t led_pin_count = TERN(HAS_WHITE_LED, 4, 3); + + // Startup animation + LED1Color_t curColor = LEDColorOff(); + PCA9632_set_led_color(curColor); // blackout + delay(200); + + /** + * LED Pin Counter steps -> events + * | 0-100 | 100-200 | 200-300 | 300-400 | + * fade in steady | fade out + * start next pin fade in + */ + + uint16_t led_pin_counters[led_pin_count] = { 1, 0, 0 }; + + bool canEnd = false; + while (led_pin_counters[0] != 99 || !canEnd) { + if (led_pin_counters[0] == 99) // End loop next time pin0 counter is 99 + canEnd = true; + for (uint8_t i = 0; i < led_pin_count; ++i) { + if (led_pin_counters[i] > 0) { + if (++led_pin_counters[i] == 400) // turn off current pin counter in led_pin_counters + led_pin_counters[i] = 0; + else if (led_pin_counters[i] == 201) { // start next pin pwm + led_pin_counters[i + 1 == led_pin_count ? 0 : i + 1] = 1; + i++; // skip next pin in this loop so it doesn't increment twice + } + } + } + uint16_t r, g, b; + r = led_pin_counters[0]; curColor.r = r <= 100 ? r : r <= 300 ? 100 : 400 - r; + g = led_pin_counters[1]; curColor.g = g <= 100 ? g : g <= 300 ? 100 : 400 - g; + b = led_pin_counters[2]; curColor.b = b <= 100 ? b : b <= 300 ? 100 : 400 - b; + #if HAS_WHITE_LED + const uint16_t w = led_pin_counters[3]; curColor.w = w <= 100 ? w : w <= 300 ? 100 : 400 - w; + #endif + PCA9632_set_led_color(curColor); + delay(RGB_STARTUP_TEST_INNER_MS); + } + + // Fade to white + for (uint8_t led_pwm = 0; led_pwm <= 100; ++led_pwm) { + NOLESS(curColor.r, led_pwm); + NOLESS(curColor.g, led_pwm); + NOLESS(curColor.b, led_pwm); + TERN_(HAS_WHITE_LED, NOLESS(curColor.w, led_pwm)); + PCA9632_set_led_color(curColor); + delay(RGB_STARTUP_TEST_INNER_MS); + } + + #endif // PCA9632 && RGB_STARTUP_TEST + TERN_(NEOPIXEL_LED, neo.init()); TERN_(PCA9533, PCA9533_init()); TERN_(LED_USER_PRESET_STARTUP, set_default()); } -void LEDLights::set_color(const LEDColor &incol - #if ENABLED(NEOPIXEL_LED) - , bool isSequence/*=false*/ - #endif +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, incol.w); - static uint16_t nextLed = 0; + ? neo.White() + : neo.Color(incol.r, incol.g, incol.b OPTARG(HAS_WHITE_NEOPIXEL_1, incol.w)); - #ifdef NEOPIXEL_BKGD_LED_INDEX - if (NEOPIXEL_BKGD_LED_INDEX == nextLed) { + #if ENABLED(NEOPIXEL_IS_SEQUENTIAL) + static uint16_t nextLed = 0; + #ifdef NEOPIXEL_BKGD_INDEX_FIRST + while (WITHIN(nextLed, NEOPIXEL_BKGD_INDEX_FIRST, NEOPIXEL_BKGD_INDEX_LAST)) { + neo.reset_background_color(); + if (++nextLed >= neo.pixels()) { nextLed = 0; return; } + } + #endif + #endif + + #if ALL(CASE_LIGHT_MENU, CASE_LIGHT_USE_NEOPIXEL) + // Update brightness only if caselight is ON or switching leds off + if (caselight.on || incol.is_off()) + #endif + neo.set_brightness(incol.i); + + #if ENABLED(NEOPIXEL_IS_SEQUENTIAL) + if (isSequence) { + neo.set_pixel_color(nextLed, neocolor); + neo.show(); if (++nextLed >= neo.pixels()) nextLed = 0; return; } #endif - neo.set_brightness(incol.i); - - if (isSequence) { - neo.set_pixel_color(nextLed, neocolor); - neo.show(); - if (++nextLed >= neo.pixels()) nextLed = 0; - return; - } - + #if ALL(CASE_LIGHT_MENU, CASE_LIGHT_USE_NEOPIXEL) + // Update color only if caselight is ON or switching leds off + if (caselight.on || incol.is_off()) + #endif neo.set_color(neocolor); #endif @@ -110,16 +206,17 @@ void LEDLights::set_color(const LEDColor &incol #endif - #if EITHER(RGB_LED, RGBW_LED) + #if ANY(RGB_LED, RGBW_LED) // This variant uses 3-4 separate pins for the RGB(W) components. // If the pins can do PWM then their intensity will be set. - #define UPDATE_RGBW(C,c) do { \ - if (PWM_PIN(RGB_LED_##C##_PIN)) \ - analogWrite(pin_t(RGB_LED_##C##_PIN), incol.c); \ - else \ - WRITE(RGB_LED_##C##_PIN, incol.c ? HIGH : LOW); \ + #define _UPDATE_RGBW(C,c) do { \ + if (PWM_PIN(RGB_LED_##C##_PIN)) \ + hal.set_pwm_duty(pin_t(RGB_LED_##C##_PIN), c); \ + else \ + WRITE(RGB_LED_##C##_PIN, c ? HIGH : LOW); \ }while(0) + #define UPDATE_RGBW(C,c) _UPDATE_RGBW(C, TERN1(CASE_LIGHT_USE_RGB_LED, caselight.on) ? incol.c : 0) UPDATE_RGBW(R,r); UPDATE_RGBW(G,g); UPDATE_RGBW(B,b); #if ENABLED(RGBW_LED) UPDATE_RGBW(W,w); @@ -131,7 +228,7 @@ void LEDLights::set_color(const LEDColor &incol TERN_(PCA9632, PCA9632_set_led_color(incol)); TERN_(PCA9533, PCA9533_set_rgb(incol.r, incol.g, incol.b)); - #if EITHER(LED_CONTROL_MENU, PRINTER_EVENT_LEDS) + #if ANY(LED_CONTROL_MENU, PRINTER_EVENT_LEDS) // Don't update the color when OFF lights_on = !incol.is_off(); if (lights_on) color = incol; @@ -142,16 +239,18 @@ void LEDLights::set_color(const LEDColor &incol void LEDLights::toggle() { if (lights_on) set_off(); else update(); } #endif -#ifdef LED_BACKLIGHT_TIMEOUT +#if HAS_LED_POWEROFF_TIMEOUT millis_t LEDLights::led_off_time; // = 0 void LEDLights::update_timeout(const bool power_on) { - const millis_t ms = millis(); - if (power_on) - reset_timeout(ms); - else if (ELAPSED(ms, led_off_time)) - set_off(); + if (lights_on) { + const millis_t ms = millis(); + if (power_on) + reset_timeout(ms); + else if (ELAPSED(ms, led_off_time)) + set_off(); + } } #endif @@ -159,14 +258,15 @@ void LEDLights::set_color(const LEDColor &incol #if ENABLED(NEOPIXEL2_SEPARATE) #if ENABLED(NEO2_COLOR_PRESETS) - const LEDColor LEDLights2::defaultLEDColor = MakeLEDColor( - NEO2_USER_PRESET_RED, NEO2_USER_PRESET_GREEN, NEO2_USER_PRESET_BLUE, - NEO2_USER_PRESET_WHITE, NEO2_USER_PRESET_BRIGHTNESS + 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) ); #endif #if ENABLED(LED_CONTROL_MENU) - LEDColor LEDLights2::color; + LED2Color_t LEDLights2::color; bool LEDLights2::lights_on; #endif @@ -177,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, 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 055ea0df37..3ba3a31cdb 100644 --- a/Marlin/src/feature/leds/leds.h +++ b/Marlin/src/feature/leds/leds.h @@ -30,180 +30,213 @@ #include #if ENABLED(NEOPIXEL_LED) + #define _NEOPIXEL_INCLUDE_ #include "neopixel.h" + #undef _NEOPIXEL_INCLUDE_ #endif -// A white component can be passed -#if EITHER(RGBW_LED, NEOPIXEL_LED) +#if ENABLED(BLINKM) + #include "blinkm.h" +#endif + +#if ENABLED(PCA9533) + #include "pca9533.h" +#endif + +#if ENABLED(PCA9632) + #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 - #if HAS_WHITE_LED - , w - #if ENABLED(NEOPIXEL_LED) - , i - #endif - #endif - ; +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) - #if HAS_WHITE_LED - , w(255) - #if ENABLED(NEOPIXEL_LED) - , i(NEOPIXEL_BRIGHTNESS) - #endif - #endif - {} + // Copy constructor + LED1Color_t(const LED1Color_t&) = default; - LEDColor(uint8_t r, uint8_t g, uint8_t b - #if HAS_WHITE_LED - , uint8_t w=0 - #if ENABLED(NEOPIXEL_LED) - , uint8_t i=NEOPIXEL_BRIGHTNESS - #endif - #endif - ) : r(r), g(g), b(b) - #if HAS_WHITE_LED - , w(w) - #if ENABLED(NEOPIXEL_LED) - , i(i) - #endif - #endif - {} + // 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]) - #if HAS_WHITE_LED - , w(rgbw[3]) - #if ENABLED(NEOPIXEL_LED) - , i(NEOPIXEL_BRIGHTNESS) - #endif - #endif - {} + // 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; } - LEDColor& operator=(const LEDColor &right) { - if (this != &right) memcpy(this, &right, sizeof(LEDColor)); - return *this; + // Comparison operators + bool operator==(const LED1Color_t &right) { + return (this == &right) || (0 == memcmp(this, &right, sizeof(LED1Color_t))); } - bool operator==(const LEDColor &right) { - if (this == &right) return true; - return 0 == memcmp(this, &right, sizeof(LEDColor)); + bool operator!=(const LED1Color_t &right) { + return !operator==(right); } - bool operator!=(const LEDColor &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 helpers and presets + * Color presets */ -#if HAS_WHITE_LED - #if ENABLED(NEOPIXEL_LED) - #define MakeLEDColor(R,G,B,W,I) LEDColor(R, G, B, W, I) - #else - #define MakeLEDColor(R,G,B,W,I) LEDColor(R, G, B, W) - #endif + +#define LEDColorOff() LED1Color_t( 0, 0, 0) +#define LEDColorRed() LED1Color_t(255, 0, 0) +#if ENABLED(LED_COLORS_REDUCE_GREEN) + #define LEDColorOrange() LED1Color_t(255, 25, 0) + #define LEDColorYellow() LED1Color_t(255, 75, 0) #else - #define MakeLEDColor(R,G,B,W,I) LEDColor(R, G, B) + #define LEDColorOrange() LED1Color_t(255, 80, 0) + #define LEDColorYellow() LED1Color_t(255, 255, 0) +#endif +#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() LED1Color_t( 0, 0, 0, 255) +#else + #define LEDColorWhite() LED1Color_t(255, 255, 255) #endif -#define LEDColorOff() LEDColor( 0, 0, 0) -#define LEDColorRed() LEDColor(255, 0, 0) +#define LEDColorOff2() LED2Color_t( 0, 0, 0) +#define LEDColorRed2() LED2Color_t(255, 0, 0) #if ENABLED(LED_COLORS_REDUCE_GREEN) - #define LEDColorOrange() LEDColor(255, 25, 0) - #define LEDColorYellow() LEDColor(255, 75, 0) + #define LEDColorOrange2() LED2Color_t(255, 25, 0) + #define LEDColorYellow2() LED2Color_t(255, 75, 0) #else - #define LEDColorOrange() LEDColor(255, 80, 0) - #define LEDColorYellow() LEDColor(255, 255, 0) + #define LEDColorOrange2() LED2Color_t(255, 80, 0) + #define LEDColorYellow2() LED2Color_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) -#if HAS_WHITE_LED && DISABLED(RGB_LED) - #define LEDColorWhite() LEDColor( 0, 0, 0, 255) +#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 LEDColorWhite() LEDColor(255, 255, 255) + #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 LED1Color_t color; // last non-off color + static bool lights_on; // the last set color was "on" + #else + static constexpr bool lights_on = true; + #endif + LEDLights() {} // ctor static void setup(); // init() - static void set_color(const LEDColor &color - #if ENABLED(NEOPIXEL_LED) - , bool isSequence=false - #endif + static void set_color(const LED1Color_t &color + OPTARG(NEOPIXEL_IS_SEQUENTIAL, bool isSequence=false) ); - static inline void set_color(uint8_t r, uint8_t g, uint8_t b - #if HAS_WHITE_LED - , uint8_t w=0 - #endif - #if ENABLED(NEOPIXEL_LED) - , uint8_t i=NEOPIXEL_BRIGHTNESS - , bool isSequence=false - #endif + static void set_color(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) + OPTARG(NEOPIXEL_IS_SEQUENTIAL, bool isSequence=false) ) { - set_color(MakeLEDColor(r, g, b, w, i) - #if ENABLED(NEOPIXEL_LED) - , isSequence - #endif - ); + set_color(LED1Color_t(r, g, b OPTARG(HAS_WHITE_LED, w) OPTARG(NEOPIXEL_LED, i)) OPTARG(NEOPIXEL_IS_SEQUENTIAL, isSequence)); } - static inline void set_off() { set_color(LEDColorOff()); } - static inline void set_green() { set_color(LEDColorGreen()); } - static inline void set_white() { set_color(LEDColorWhite()); } + static void set_off() { set_color(LEDColorOff()); } + static void set_green() { set_color(LEDColorGreen()); } + static void set_white() { set_color(LEDColorWhite()); } #if ENABLED(LED_COLOR_PRESETS) - static const LEDColor defaultLEDColor; - static inline void set_default() { set_color(defaultLEDColor); } - static inline void set_red() { set_color(LEDColorRed()); } - static inline void set_orange() { set_color(LEDColorOrange()); } - static inline void set_yellow() { set_color(LEDColorYellow()); } - static inline void set_blue() { set_color(LEDColorBlue()); } - static inline void set_indigo() { set_color(LEDColorIndigo()); } - static inline void set_violet() { set_color(LEDColorViolet()); } + 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()); } + 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()); } #endif #if ENABLED(PRINTER_EVENT_LEDS) - static inline LEDColor get_color() { return lights_on ? color : LEDColorOff(); } - #endif - - #if EITHER(LED_CONTROL_MENU, PRINTER_EVENT_LEDS) - static LEDColor color; // last non-off color - static bool lights_on; // the last set color was "on" + static LED1Color_t get_color() { return lights_on ? color : LEDColorOff(); } #endif #if ENABLED(LED_CONTROL_MENU) static void toggle(); // swap "off" with color - static inline void update() { set_color(color); } + #endif + #if ANY(LED_CONTROL_MENU, CASE_LIGHT_USE_RGB_LED, HAS_LED_POWEROFF_TIMEOUT) + static void update() { set_color(color); } #endif - #ifdef LED_BACKLIGHT_TIMEOUT + #if HAS_LED_POWEROFF_TIMEOUT private: static millis_t led_off_time; public: - static inline void reset_timeout(const millis_t &ms) { - led_off_time = ms + LED_BACKLIGHT_TIMEOUT; - if (!lights_on) set_default(); + static void reset_timeout(const millis_t &ms) { + led_off_time = ms + LED_POWEROFF_TIMEOUT; + if (!lights_on) update(); } static void update_timeout(const bool power_on); #endif @@ -219,32 +252,38 @@ extern LEDLights leds; static void setup(); // init() - static void set_color(const LEDColor &color); + static void set_color(const LED2Color_t &color); - inline void set_color(uint8_t r, uint8_t g, uint8_t b, uint8_t w=0, uint8_t i=NEOPIXEL2_BRIGHTNESS) { - set_color(MakeLEDColor(r, g, b, w, i)); + static void set_color(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) + ) { + set_color(LED2Color_t(r, g, b + OPTARG(HAS_WHITE_LED2, w) + OPTARG(NEOPIXEL_LED, i) + )); } - static inline void set_off() { set_color(LEDColorOff()); } - static inline void set_green() { set_color(LEDColorGreen()); } - static inline 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 inline void set_default() { set_color(defaultLEDColor); } - static inline void set_red() { set_color(LEDColorRed()); } - static inline void set_orange() { set_color(LEDColorOrange()); } - static inline void set_yellow() { set_color(LEDColorYellow()); } - static inline void set_blue() { set_color(LEDColorBlue()); } - static inline void set_indigo() { set_color(LEDColorIndigo()); } - static inline 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 inline void update() { set_color(color); } + static void update() { set_color(color); } #endif }; diff --git a/Marlin/src/feature/leds/neopixel.cpp b/Marlin/src/feature/leds/neopixel.cpp index 27bbeb348c..8165c7715c 100644 --- a/Marlin/src/feature/leds/neopixel.cpp +++ b/Marlin/src/feature/leds/neopixel.cpp @@ -28,29 +28,41 @@ #if ENABLED(NEOPIXEL_LED) -#include "neopixel.h" +#include "leds.h" -#if EITHER(NEOPIXEL_STARTUP_TEST, NEOPIXEL2_STARTUP_TEST) +#if ANY(NEOPIXEL_STARTUP_TEST, NEOPIXEL2_STARTUP_TEST) #include "../../core/utility.h" #endif Marlin_NeoPixel neo; -int8_t Marlin_NeoPixel::neoindex; +pixel_index_t Marlin_NeoPixel::neoindex; -Adafruit_NeoPixel Marlin_NeoPixel::adaneo1(NEOPIXEL_PIXELS, NEOPIXEL_PIN, NEOPIXEL_TYPE + NEO_KHZ800) - #if CONJOINED_NEOPIXEL - , Marlin_NeoPixel::adaneo2(NEOPIXEL_PIXELS, NEOPIXEL2_PIN, NEOPIXEL2_TYPE + NEO_KHZ800) - #endif -; +Adafruit_NeoPixel Marlin_NeoPixel::adaneo1(NEOPIXEL_PIXELS, NEOPIXEL_PIN, NEOPIXEL_TYPE + NEO_KHZ800); +#if CONJOINED_NEOPIXEL + Adafruit_NeoPixel Marlin_NeoPixel::adaneo2(NEOPIXEL_PIXELS, NEOPIXEL2_PIN, NEOPIXEL2_TYPE + NEO_KHZ800); +#endif -#ifdef NEOPIXEL_BKGD_LED_INDEX +#ifdef NEOPIXEL_BKGD_INDEX_FIRST - void Marlin_NeoPixel::set_color_background() { - uint8_t background_color[4] = NEOPIXEL_BKGD_COLOR; - set_pixel_color(NEOPIXEL_BKGD_LED_INDEX, adaneo1.Color(background_color[0], background_color[1], background_color[2], background_color[3])); + void Marlin_NeoPixel::set_background_color(const uint8_t r, const uint8_t g, const uint8_t b, const uint8_t w) { + for (int background_led = NEOPIXEL_BKGD_INDEX_FIRST; background_led <= NEOPIXEL_BKGD_INDEX_LAST; background_led++) + set_pixel_color(background_led, adaneo1.Color(r, g, b, w)); } -#endif + void Marlin_NeoPixel::reset_background_color() { + constexpr uint8_t background_color[4] = NEOPIXEL_BKGD_COLOR; + set_background_color(background_color); + } + + void Marlin_NeoPixel::set_background_off() { + #ifndef NEOPIXEL_BKGD_TIMEOUT_COLOR + #define NEOPIXEL_BKGD_TIMEOUT_COLOR { 0, 0, 0, 0 } + #endif + constexpr uint8_t background_color_off[4] = NEOPIXEL_BKGD_TIMEOUT_COLOR; + set_background_color(background_color_off); + } + +#endif // NEOPIXEL_BKGD_INDEX_FIRST void Marlin_NeoPixel::set_color(const uint32_t color) { if (neoindex >= 0) { @@ -59,9 +71,10 @@ void Marlin_NeoPixel::set_color(const uint32_t color) { } else { for (uint16_t i = 0; i < pixels(); ++i) { - #ifdef NEOPIXEL_BKGD_LED_INDEX - if (i == NEOPIXEL_BKGD_LED_INDEX && color != 0x000000) { - set_color_background(); + #ifdef NEOPIXEL_BKGD_INDEX_FIRST + if (i == NEOPIXEL_BKGD_INDEX_FIRST && TERN(NEOPIXEL_BKGD_ALWAYS_ON, true, color != 0x000000)) { + reset_background_color(); + i += NEOPIXEL_BKGD_INDEX_LAST - (NEOPIXEL_BKGD_INDEX_FIRST); continue; } #endif @@ -90,41 +103,28 @@ void Marlin_NeoPixel::init() { safe_delay(500); set_color_startup(adaneo1.Color(0, 0, 255, 0)); // blue safe_delay(500); + #if HAS_WHITE_NEOPIXEL_1 + set_color_startup(adaneo1.Color(0, 0, 0, 255)); // white + safe_delay(500); + #endif #endif - #ifdef NEOPIXEL_BKGD_LED_INDEX - set_color_background(); + #ifdef NEOPIXEL_BKGD_INDEX_FIRST + reset_background_color(); #endif - #if ENABLED(LED_USER_PRESET_STARTUP) - set_color(adaneo1.Color(LED_USER_PRESET_RED, LED_USER_PRESET_GREEN, LED_USER_PRESET_BLUE, LED_USER_PRESET_WHITE)); - #else - set_color(adaneo1.Color(0, 0, 0, 0)); - #endif + set_color(adaneo1.Color + TERN(LED_USER_PRESET_STARTUP, + (LED_USER_PRESET_RED, LED_USER_PRESET_GREEN, LED_USER_PRESET_BLUE, LED_USER_PRESET_WHITE), + (0, 0, 0, 0)) + ); } -#if 0 -bool Marlin_NeoPixel::set_led_color(const uint8_t r, const uint8_t g, const uint8_t b, const uint8_t w, const uint8_t p) { - const uint32_t color = adaneo1.Color(r, g, b, w); - set_brightness(p); - #if DISABLED(NEOPIXEL_IS_SEQUENTIAL) - set_color(color); - return false; - #else - static uint16_t nextLed = 0; - set_pixel_color(nextLed, color); - show(); - if (++nextLed >= pixels()) nextLed = 0; - return true; - #endif -} -#endif - #if ENABLED(NEOPIXEL2_SEPARATE) Marlin_NeoPixel2 neo2; - int8_t Marlin_NeoPixel2::neoindex; + pixel_index_t Marlin_NeoPixel2::neoindex; Adafruit_NeoPixel Marlin_NeoPixel2::adaneo(NEOPIXEL2_PIXELS, NEOPIXEL2_PIN, NEOPIXEL2_TYPE); void Marlin_NeoPixel2::set_color(const uint32_t color) { @@ -158,13 +158,17 @@ bool Marlin_NeoPixel::set_led_color(const uint8_t r, const uint8_t g, const uint safe_delay(500); set_color_startup(adaneo.Color(0, 0, 255, 0)); // blue safe_delay(500); + #if HAS_WHITE_NEOPIXEL_2 + set_color_startup(adaneo.Color(0, 0, 0, 255)); // white + safe_delay(500); + #endif #endif - #if ENABLED(NEO2_USER_PRESET_STARTUP) - set_color(adaneo.Color(NEO2_USER_PRESET_RED, NEO2_USER_PRESET_GREEN, NEO2_USER_PRESET_BLUE, NEO2_USER_PRESET_WHITE)); - #else - set_color(adaneo.Color(0, 0, 0, 0)); - #endif + set_color(adaneo.Color + TERN(NEO2_USER_PRESET_STARTUP, + (NEO2_USER_PRESET_RED, NEO2_USER_PRESET_GREEN, NEO2_USER_PRESET_BLUE, NEO2_USER_PRESET_WHITE), + (0, 0, 0, 0)) + ); } #endif // NEOPIXEL2_SEPARATE diff --git a/Marlin/src/feature/leds/neopixel.h b/Marlin/src/feature/leds/neopixel.h index 42046fa563..f420a25b83 100644 --- a/Marlin/src/feature/leds/neopixel.h +++ b/Marlin/src/feature/leds/neopixel.h @@ -25,6 +25,10 @@ * NeoPixel support */ +#ifndef _NEOPIXEL_INCLUDE_ + #error "Always include 'leds.h' and not 'neopixel.h' directly." +#endif + // ------------------------ // Includes // ------------------------ @@ -38,94 +42,116 @@ // Defines // ------------------------ -#if defined(NEOPIXEL2_TYPE) && NEOPIXEL2_TYPE != NEOPIXEL_TYPE && DISABLED(NEOPIXEL2_SEPARATE) +#define _NEO_IS_RGBW(N) ((N) & 0x30) != (((N) >> 2) & 0x30) + +#if _NEO_IS_RGBW(NEOPIXEL_TYPE) + #define HAS_WHITE_NEOPIXEL_1 1 +#endif + +#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 -#if EITHER(MULTIPLE_NEOPIXEL_TYPES, NEOPIXEL2_INSERIES) +#if ANY(MULTIPLE_NEOPIXEL_TYPES, NEOPIXEL2_INSERIES) #define CONJOINED_NEOPIXEL 1 #endif -#if NEOPIXEL_TYPE == NEO_RGB || NEOPIXEL_TYPE == NEO_RBG || NEOPIXEL_TYPE == NEO_GRB || NEOPIXEL_TYPE == NEO_GBR || NEOPIXEL_TYPE == NEO_BRG || NEOPIXEL_TYPE == NEO_BGR - #define NEOPIXEL_IS_RGB 1 -#else - #define NEOPIXEL_IS_RGBW 1 -#endif - -#if NEOPIXEL_IS_RGB - #define NEO_WHITE 255, 255, 255, 0 -#else - #define NEO_WHITE 0, 0, 0, 255 -#endif +#undef _NEO_IS_RGBW // ------------------------ -// Function prototypes +// Types +// ------------------------ + +typedef value_t(TERN0(NEOPIXEL_LED, NEOPIXEL_PIXELS)) pixel_index_t; + +// ------------------------ +// Classes // ------------------------ class Marlin_NeoPixel { private: - static Adafruit_NeoPixel adaneo1 - #if CONJOINED_NEOPIXEL - , adaneo2 - #endif - ; + static Adafruit_NeoPixel adaneo1; + #if CONJOINED_NEOPIXEL + static Adafruit_NeoPixel adaneo2; + #endif public: - static int8_t neoindex; + static pixel_index_t neoindex; static void init(); static void set_color_startup(const uint32_t c); static void set_color(const uint32_t c); - #ifdef NEOPIXEL_BKGD_LED_INDEX - static void set_color_background(); + #ifdef NEOPIXEL_BKGD_INDEX_FIRST + static void set_background_color(const uint8_t r, const uint8_t g, const uint8_t b, const uint8_t w); + static void set_background_color(const uint8_t (&rgbw)[4]) { set_background_color(rgbw[0], rgbw[1], rgbw[2], rgbw[3]); } + static void reset_background_color(); + static void set_background_off(); #endif - static inline void begin() { + static void begin() { adaneo1.begin(); TERN_(CONJOINED_NEOPIXEL, adaneo2.begin()); } - static inline void set_pixel_color(const uint16_t n, const uint32_t c) { + static void set_pixel_color(const uint16_t n, const uint32_t c) { #if ENABLED(NEOPIXEL2_INSERIES) if (n >= NEOPIXEL_PIXELS) adaneo2.setPixelColor(n - (NEOPIXEL_PIXELS), c); else adaneo1.setPixelColor(n, c); #else adaneo1.setPixelColor(n, c); - #if MULTIPLE_NEOPIXEL_TYPES - adaneo2.setPixelColor(n, c); - #endif + TERN_(MULTIPLE_NEOPIXEL_TYPES, adaneo2.setPixelColor(n, c)); #endif } - static inline void set_brightness(const uint8_t b) { + static void set_brightness(const uint8_t b) { adaneo1.setBrightness(b); TERN_(CONJOINED_NEOPIXEL, adaneo2.setBrightness(b)); } - static inline void show() { + static void show() { + // Some platforms cannot maintain PWM output when NeoPixel disables interrupts for long durations. + TERN_(HAS_PAUSE_SERVO_OUTPUT, PAUSE_SERVO_OUTPUT()); adaneo1.show(); #if PIN_EXISTS(NEOPIXEL2) #if CONJOINED_NEOPIXEL adaneo2.show(); #else - TERN(NEOPIXEL2_SEPARATE,,adaneo1.setPin(NEOPIXEL2_PIN)); adaneo1.show(); adaneo1.setPin(NEOPIXEL_PIN); #endif #endif + TERN_(HAS_PAUSE_SERVO_OUTPUT, RESUME_SERVO_OUTPUT()); } - #if 0 - bool set_led_color(const uint8_t r, const uint8_t g, const uint8_t b, const uint8_t w, const uint8_t p); - #endif - // Accessors - static inline uint16_t pixels() { TERN(NEOPIXEL2_INSERIES, return adaneo1.numPixels() * 2, return adaneo1.numPixels()); } - static inline uint8_t brightness() { return adaneo1.getBrightness(); } - static inline uint32_t Color(uint8_t r, uint8_t g, uint8_t b, uint8_t w) { - return adaneo1.Color(r, g, b, w); + 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) + if (n >= NEOPIXEL_PIXELS) return adaneo2.getPixelColor(n - (NEOPIXEL_PIXELS)); + #endif + return adaneo1.getPixelColor(n); + } + + static uint8_t brightness() { return adaneo1.getBrightness(); } + + 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 + ); } }; @@ -134,43 +160,41 @@ extern Marlin_NeoPixel neo; // Neo pixel channel 2 #if ENABLED(NEOPIXEL2_SEPARATE) - #if NEOPIXEL2_TYPE == NEO_RGB || NEOPIXEL2_TYPE == NEO_RBG || NEOPIXEL2_TYPE == NEO_GRB || NEOPIXEL2_TYPE == NEO_GBR || NEOPIXEL2_TYPE == NEO_BRG || NEOPIXEL2_TYPE == NEO_BGR - #define NEOPIXEL2_IS_RGB 1 - #else - #define NEOPIXEL2_IS_RGBW 1 - #endif - - #if NEOPIXEL2_IS_RGB - #define NEO2_WHITE 255, 255, 255, 0 - #else - #define NEO2_WHITE 0, 0, 0, 255 - #endif - class Marlin_NeoPixel2 { private: static Adafruit_NeoPixel adaneo; public: - static int8_t neoindex; + static pixel_index_t neoindex; static void init(); static void set_color_startup(const uint32_t c); static void set_color(const uint32_t c); - static inline void begin() { adaneo.begin(); } - static inline void set_pixel_color(const uint16_t n, const uint32_t c) { adaneo.setPixelColor(n, c); } - static inline void set_brightness(const uint8_t b) { adaneo.setBrightness(b); } - static inline void show() { + static void begin() { adaneo.begin(); } + static void set_pixel_color(const uint16_t n, const uint32_t c) { adaneo.setPixelColor(n, c); } + static void set_brightness(const uint8_t b) { adaneo.setBrightness(b); } + static void show() { adaneo.show(); adaneo.setPin(NEOPIXEL2_PIN); } // Accessors - static inline uint16_t pixels() { return adaneo.numPixels();} - static inline uint8_t brightness() { return adaneo.getBrightness(); } - static inline uint32_t Color(uint8_t r, uint8_t g, uint8_t b, uint8_t w) { - return adaneo.Color(r, g, b, w); + 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_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 + ); } }; diff --git a/Marlin/src/feature/leds/pca9533.cpp b/Marlin/src/feature/leds/pca9533.cpp index 0fd4d66757..d71760e2cb 100644 --- a/Marlin/src/feature/leds/pca9533.cpp +++ b/Marlin/src/feature/leds/pca9533.cpp @@ -1,4 +1,4 @@ -/* +/** * Marlin 3D Printer Firmware * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] * @@ -36,7 +36,7 @@ void PCA9533_init() { PCA9533_reset(); } -static void PCA9533_writeAllRegisters(uint8_t psc0, uint8_t pwm0, uint8_t psc1, uint8_t pwm1, uint8_t ls0){ +static void PCA9533_writeAllRegisters(uint8_t psc0, uint8_t pwm0, uint8_t psc1, uint8_t pwm1, uint8_t ls0) { uint8_t data[6] = { PCA9533_REG_PSC0 | PCA9533_REGM_AI, psc0, pwm0, psc1, pwm1, ls0 }; Wire.beginTransmission(PCA9533_Addr >> 1); Wire.write(data, 6); @@ -44,7 +44,7 @@ static void PCA9533_writeAllRegisters(uint8_t psc0, uint8_t pwm0, uint8_t psc1, delayMicroseconds(1); } -static void PCA9533_writeRegister(uint8_t reg, uint8_t val){ +static void PCA9533_writeRegister(uint8_t reg, uint8_t val) { uint8_t data[2] = { reg, val }; Wire.beginTransmission(PCA9533_Addr >> 1); Wire.write(data, 2); diff --git a/Marlin/src/feature/leds/pca9533.h b/Marlin/src/feature/leds/pca9533.h index 431058c491..3a18e96b24 100644 --- a/Marlin/src/feature/leds/pca9533.h +++ b/Marlin/src/feature/leds/pca9533.h @@ -1,4 +1,4 @@ -/* +/** * Marlin 3D Printer Firmware * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] * @@ -21,7 +21,7 @@ */ #pragma once -/* +/** * Driver for the PCA9533 LED controller found on the MightyBoard * used by FlashForge Creator Pro, MakerBot, etc. * Written 2020 APR 01 by grauerfuchs diff --git a/Marlin/src/feature/leds/pca9632.cpp b/Marlin/src/feature/leds/pca9632.cpp index d8af31cb6c..d8fba380a4 100644 --- a/Marlin/src/feature/leds/pca9632.cpp +++ b/Marlin/src/feature/leds/pca9632.cpp @@ -58,7 +58,7 @@ #define PCA9632_AUTOGLO 0xC0 #define PCA9632_AUTOGI 0xE0 -// Red=LED0 Green=LED1 Blue=LED2 +// Red=LED0 Green=LED1 Blue=LED2 White=LED3 #ifndef PCA9632_RED #define PCA9632_RED 0x00 #endif @@ -68,9 +68,12 @@ #ifndef PCA9632_BLU #define PCA9632_BLU 0x04 #endif +#if ENABLED(PCA9632_RGBW) && !defined(PCA9632_WHT) + #define PCA9632_WHT 0x06 +#endif // If any of the color indexes are greater than 0x04 they can't use auto increment -#if !defined(PCA9632_NO_AUTO_INC) && (PCA9632_RED > 0x04 || PCA9632_GRN > 0x04 || PCA9632_BLU > 0x04) +#if !defined(PCA9632_NO_AUTO_INC) && (PCA9632_RED > 0x04 || PCA9632_GRN > 0x04 || PCA9632_BLU > 0x04 || PCA9632_WHT > 0x04) #define PCA9632_NO_AUTO_INC #endif @@ -89,25 +92,26 @@ static void PCA9632_WriteRegister(const byte addr, const byte regadd, const byte Wire.endTransmission(); } -static void PCA9632_WriteAllRegisters(const byte addr, const byte regadd, const byte vr, const byte vg, const byte vb) { +static void PCA9632_WriteAllRegisters(const byte addr, const byte regadd, const byte vr, const byte vg, const byte vb + OPTARG(PCA9632_RGBW, const byte vw) +) { #if DISABLED(PCA9632_NO_AUTO_INC) - uint8_t data[4], len = 4; + uint8_t data[4]; data[0] = PCA9632_AUTO_IND | regadd; data[1 + (PCA9632_RED >> 1)] = vr; data[1 + (PCA9632_GRN >> 1)] = vg; data[1 + (PCA9632_BLU >> 1)] = vb; + Wire.beginTransmission(I2C_ADDRESS(addr)); + Wire.write(data, sizeof(data)); + Wire.endTransmission(); #else - uint8_t data[6], len = 6; - data[0] = regadd + (PCA9632_RED >> 1); - data[1] = vr; - data[2] = regadd + (PCA9632_GRN >> 1); - data[3] = vg; - data[4] = regadd + (PCA9632_BLU >> 1); - data[5] = vb; + PCA9632_WriteRegister(addr, regadd + (PCA9632_RED >> 1), vr); + PCA9632_WriteRegister(addr, regadd + (PCA9632_GRN >> 1), vg); + PCA9632_WriteRegister(addr, regadd + (PCA9632_BLU >> 1), vb); + #if ENABLED(PCA9632_RGBW) + PCA9632_WriteRegister(addr, regadd + (PCA9632_WHT >> 1), vw); + #endif #endif - Wire.beginTransmission(I2C_ADDRESS(addr)); - Wire.write(data, len); - Wire.endTransmission(); } #if 0 @@ -120,7 +124,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; @@ -130,15 +134,18 @@ void PCA9632_set_led_color(const LEDColor &color) { const byte LEDOUT = (color.r ? LED_PWM << PCA9632_RED : 0) | (color.g ? LED_PWM << PCA9632_GRN : 0) - | (color.b ? LED_PWM << PCA9632_BLU : 0); + | (color.b ? LED_PWM << PCA9632_BLU : 0) + | (TERN0(PCA9632_RGBW, color.w ? LED_PWM << PCA9632_WHT : 0)); - PCA9632_WriteAllRegisters(PCA9632_ADDRESS,PCA9632_PWM0, color.r, color.g, color.b); + PCA9632_WriteAllRegisters(PCA9632_ADDRESS,PCA9632_PWM0, color.r, color.g, 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) { + 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 fb59a8c184..442166c245 100644 --- a/Marlin/src/feature/leds/pca9632.h +++ b/Marlin/src/feature/leds/pca9632.h @@ -26,12 +26,11 @@ * 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 - void PCA9632_buzz(const long, const uint16_t); + void PCA9632_buzz(const long, const uint16_t=0); #endif diff --git a/Marlin/src/feature/leds/printer_event_leds.cpp b/Marlin/src/feature/leds/printer_event_leds.cpp index 31c628c281..3cbce3da8c 100644 --- a/Marlin/src/feature/leds/printer_event_leds.cpp +++ b/Marlin/src/feature/leds/printer_event_leds.cpp @@ -40,16 +40,15 @@ PrinterEventLEDs printerEventLEDs; uint8_t PrinterEventLEDs::old_intensity = 0; - inline uint8_t pel_intensity(const float &start, const float ¤t, const float &target) { - return (uint8_t)map(constrain(current, start, target), start, target, 0.f, 255.f); + inline uint8_t pel_intensity(const celsius_t start, const celsius_t current, const celsius_t target) { + if (start == target) return 255; + return (uint8_t)map(constrain(current, start, target), start, target, 0, 255); } - inline void pel_set_rgb(const uint8_t r, const uint8_t g, const uint8_t b) { + 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( - MakeLEDColor(r, g, b, 0, neo.brightness()) - #if ENABLED(NEOPIXEL_IS_SEQUENTIAL) - , true - #endif + LED1Color_t(r, g, b OPTARG(HAS_WHITE_LED, w) OPTARG(NEOPIXEL_LED, neo.brightness())) + OPTARG(NEOPIXEL_IS_SEQUENTIAL, true) ); } @@ -57,7 +56,7 @@ PrinterEventLEDs printerEventLEDs; #if HAS_TEMP_HOTEND - void PrinterEventLEDs::onHotendHeating(const float &start, const float ¤t, const float &target) { + void PrinterEventLEDs::onHotendHeating(const celsius_t start, const celsius_t current, const celsius_t target) { const uint8_t blue = pel_intensity(start, current, target); if (blue != old_intensity) { old_intensity = blue; @@ -69,13 +68,26 @@ PrinterEventLEDs printerEventLEDs; #if HAS_HEATED_BED - void PrinterEventLEDs::onBedHeating(const float &start, const float ¤t, const float &target) { + void PrinterEventLEDs::onBedHeating(const celsius_t start, const celsius_t current, const celsius_t target) { const uint8_t red = pel_intensity(start, current, target); if (red != old_intensity) { old_intensity = red; pel_set_rgb(red, 0, 255); } } + +#endif + +#if HAS_HEATED_CHAMBER + + void PrinterEventLEDs::onChamberHeating(const celsius_t start, const celsius_t current, const celsius_t target) { + const uint8_t green = pel_intensity(start, current, target); + if (green != old_intensity) { + old_intensity = green; + pel_set_rgb(255, green, 255); + } + } + #endif #endif // PRINTER_EVENT_LEDS diff --git a/Marlin/src/feature/leds/printer_event_leds.h b/Marlin/src/feature/leds/printer_event_leds.h index 86ec292aa3..5fb08a547e 100644 --- a/Marlin/src/feature/leds/printer_event_leds.h +++ b/Marlin/src/feature/leds/printer_event_leds.h @@ -36,33 +36,32 @@ private: static bool leds_off_after_print; #endif - static inline void set_done() { - #if ENABLED(LED_COLOR_PRESETS) - leds.set_default(); - #else - leds.set_off(); - #endif - } + static void set_done() { TERN(LED_COLOR_PRESETS, leds.set_default(), leds.set_off()); } public: #if HAS_TEMP_HOTEND - static inline LEDColor onHotendHeatingStart() { old_intensity = 0; return leds.get_color(); } - static void onHotendHeating(const float &start, const float ¤t, const float &target); + 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 inline LEDColor onBedHeatingStart() { old_intensity = 127; return leds.get_color(); } - static void onBedHeating(const float &start, const float ¤t, const float &target); + 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_TEMP_HOTEND || HAS_HEATED_BED - static inline void onHeatingDone() { leds.set_white(); } - static inline void onPidTuningDone(LEDColor c) { leds.set_color(c); } + #if HAS_HEATED_CHAMBER + 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 ENABLED(SDSUPPORT) + #if HAS_TEMP_HOTEND || HAS_HEATED_BED || HAS_HEATED_CHAMBER + static void onHeatingDone() { leds.set_white(); } + static void onPIDTuningDone(LED1Color_t c) { leds.set_color(c); } + #endif - static inline void onPrintCompleted() { + #if HAS_MEDIA + + static void onPrintCompleted() { leds.set_green(); #if HAS_LEDS_OFF_FLAG leds_off_after_print = true; @@ -72,7 +71,7 @@ public: #endif } - static inline void onResumeAfterWait() { + static void onResumeAfterWait() { #if HAS_LEDS_OFF_FLAG if (leds_off_after_print) { set_done(); @@ -81,7 +80,7 @@ public: #endif } - #endif // SDSUPPORT + #endif // HAS_MEDIA }; extern PrinterEventLEDs printerEventLEDs; diff --git a/Marlin/src/feature/leds/tempstat.cpp b/Marlin/src/feature/leds/tempstat.cpp index 880258f852..967b9f4d81 100644 --- a/Marlin/src/feature/leds/tempstat.cpp +++ b/Marlin/src/feature/leds/tempstat.cpp @@ -36,10 +36,10 @@ void handle_status_leds() { static millis_t next_status_led_update_ms = 0; if (ELAPSED(millis(), next_status_led_update_ms)) { next_status_led_update_ms += 500; // Update every 0.5s - float max_temp = TERN0(HAS_HEATED_BED, _MAX(thermalManager.degTargetBed(), thermalManager.degBed())); + celsius_t max_temp = TERN0(HAS_HEATED_BED, _MAX(thermalManager.degTargetBed(), thermalManager.wholeDegBed())); HOTEND_LOOP() - max_temp = _MAX(max_temp, thermalManager.degHotend(e), thermalManager.degTargetHotend(e)); - const int8_t new_red = (max_temp > 55.0) ? HIGH : (max_temp < 54.0 || old_red < 0) ? LOW : old_red; + max_temp = _MAX(max_temp, thermalManager.wholeDegHotend(e), thermalManager.degTargetHotend(e)); + const int8_t new_red = (max_temp > 55) ? HIGH : (max_temp < 54 || old_red < 0) ? LOW : old_red; if (new_red != old_red) { old_red = new_red; #if PIN_EXISTS(STAT_LED_RED) diff --git a/Marlin/src/feature/max7219.cpp b/Marlin/src/feature/max7219.cpp index ebcb56490d..81f588008d 100644 --- a/Marlin/src/feature/max7219.cpp +++ b/Marlin/src/feature/max7219.cpp @@ -39,7 +39,7 @@ #if ENABLED(MAX7219_DEBUG) -#define MAX7219_ERRORS // Disable to save 406 bytes of Program Memory +#define MAX7219_ERRORS // Requires ~400 bytes of flash #include "max7219.h" @@ -52,6 +52,7 @@ #define HAS_SIDE_BY_SIDE 1 #endif +#define _ROT ((MAX7219_ROTATE + 360) % 360) #if _ROT == 0 || _ROT == 180 #define MAX7219_X_LEDS TERN(HAS_SIDE_BY_SIDE, 8, MAX7219_LINES) #define MAX7219_Y_LEDS TERN(HAS_SIDE_BY_SIDE, MAX7219_LINES, 8) @@ -62,6 +63,35 @@ #error "MAX7219_ROTATE must be a multiple of +/- 90Β°." #endif +#ifdef MAX7219_DEBUG_PROFILE + CodeProfiler::Mode CodeProfiler::mode = ACCUMULATE_AVERAGE; + uint8_t CodeProfiler::instance_count = 0; + uint32_t CodeProfiler::last_calc_time = micros(); + uint8_t CodeProfiler::time_fraction = 0; + uint32_t CodeProfiler::total_time = 0; + 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 }; @@ -69,7 +99,7 @@ uint8_t Max7219::suspended; // = 0; #define LINE_REG(Q) (max7219_reg_digit0 + ((Q) & 0x7)) -#if _ROT == 0 || _ROT == 270 +#if (_ROT == 0 || _ROT == 270) == DISABLED(MAX7219_REVERSE_EACH) #define _LED_BIT(Q) (7 - ((Q) & 0x7)) #else #define _LED_BIT(Q) ((Q) & 0x7) @@ -124,13 +154,10 @@ uint8_t Max7219::suspended; // = 0; #define SIG_DELAY() DELAY_NS(250) #endif -void Max7219::error(const char * const func, const int32_t v1, const int32_t v2/*=-1*/) { +void Max7219::error(FSTR_P const func, const int32_t v1, const int32_t v2/*=-1*/) { #if ENABLED(MAX7219_ERRORS) - SERIAL_ECHOPGM("??? Max7219::"); - serialprintPGM(func); - SERIAL_CHAR('('); - SERIAL_ECHO(v1); - if (v2 > 0) SERIAL_ECHOPAIR(", ", v2); + SERIAL_ECHO(F("??? Max7219::"), func, C('('), v1); + if (v2 > 0) SERIAL_ECHOPGM(", ", v2); SERIAL_CHAR(')'); SERIAL_EOL(); #else @@ -147,7 +174,7 @@ void Max7219::error(const char * const func, const int32_t v1, const int32_t v2/ */ inline uint32_t flipped(const uint32_t bits, const uint8_t n_bytes) { uint32_t mask = 1, outbits = 0; - LOOP_L_N(b, n_bytes * 8) { + for (uint8_t b = 0; b < n_bytes * 8; ++b) { outbits <<= 1; if (bits & mask) outbits |= 1; mask <<= 1; @@ -267,26 +294,27 @@ void Max7219::set(const uint8_t line, const uint8_t bits) { #endif // MAX7219_NUMERIC // Modify a single LED bit and send the changed line -void Max7219::led_set(const uint8_t x, const uint8_t y, const bool on) { - if (x >= MAX7219_X_LEDS || y >= MAX7219_Y_LEDS) return error(PSTR("led_set"), x, y); +void Max7219::led_set(const uint8_t x, const uint8_t y, const bool on, uint8_t * const rcm/*=nullptr*/) { + if (x >= MAX7219_X_LEDS || y >= MAX7219_Y_LEDS) return error(F("led_set"), x, y); if (BIT_7219(x, y) == on) return; XOR_7219(x, y); refresh_unit_line(LED_IND(x, y)); + if (rcm != nullptr) *rcm |= _BV(LED_IND(x, y) & 0x07); } -void Max7219::led_on(const uint8_t x, const uint8_t y) { - if (x >= MAX7219_X_LEDS || y >= MAX7219_Y_LEDS) return error(PSTR("led_on"), x, y); - led_set(x, y, true); +void Max7219::led_on(const uint8_t x, const uint8_t y, uint8_t * const rcm/*=nullptr*/) { + if (x >= MAX7219_X_LEDS || y >= MAX7219_Y_LEDS) return error(F("led_on"), x, y); + led_set(x, y, true, rcm); } -void Max7219::led_off(const uint8_t x, const uint8_t y) { - if (x >= MAX7219_X_LEDS || y >= MAX7219_Y_LEDS) return error(PSTR("led_off"), x, y); - led_set(x, y, false); +void Max7219::led_off(const uint8_t x, const uint8_t y, uint8_t * const rcm/*=nullptr*/) { + if (x >= MAX7219_X_LEDS || y >= MAX7219_Y_LEDS) return error(F("led_off"), x, y); + led_set(x, y, false, rcm); } -void Max7219::led_toggle(const uint8_t x, const uint8_t y) { - if (x >= MAX7219_X_LEDS || y >= MAX7219_Y_LEDS) return error(PSTR("led_toggle"), x, y); - led_set(x, y, !BIT_7219(x, y)); +void Max7219::led_toggle(const uint8_t x, const uint8_t y, uint8_t * const rcm/*=nullptr*/) { + if (x >= MAX7219_X_LEDS || y >= MAX7219_Y_LEDS) return error(F("led_toggle"), x, y); + led_set(x, y, !BIT_7219(x, y), rcm); } void Max7219::send_row(const uint8_t row) { @@ -328,14 +356,14 @@ void Max7219::fill() { } void Max7219::clear_row(const uint8_t row) { - if (row >= MAX7219_Y_LEDS) return error(PSTR("clear_row"), row); - LOOP_L_N(x, MAX7219_X_LEDS) CLR_7219(x, row); + if (row >= MAX7219_Y_LEDS) return error(F("clear_row"), row); + for (uint8_t x = 0; x < MAX7219_X_LEDS; ++x) CLR_7219(x, row); send_row(row); } void Max7219::clear_column(const uint8_t col) { - if (col >= MAX7219_X_LEDS) return error(PSTR("set_column"), col); - LOOP_L_N(y, MAX7219_Y_LEDS) CLR_7219(col, y); + if (col >= MAX7219_X_LEDS) return error(F("set_column"), col); + for (uint8_t y = 0; y < MAX7219_Y_LEDS; ++y) CLR_7219(col, y); send_column(col); } @@ -345,9 +373,9 @@ void Max7219::clear_column(const uint8_t col) { * once with a single call to the function (if rotated 90Β° or 270Β°). */ void Max7219::set_row(const uint8_t row, const uint32_t val) { - if (row >= MAX7219_Y_LEDS) return error(PSTR("set_row"), row); + if (row >= MAX7219_Y_LEDS) return error(F("set_row"), row); uint32_t mask = _BV32(MAX7219_X_LEDS - 1); - LOOP_L_N(x, MAX7219_X_LEDS) { + for (uint8_t x = 0; x < MAX7219_X_LEDS; ++x) { if (val & mask) SET_7219(x, row); else CLR_7219(x, row); mask >>= 1; } @@ -360,9 +388,9 @@ void Max7219::set_row(const uint8_t row, const uint32_t val) { * once with a single call to the function (if rotated 0Β° or 180Β°). */ void Max7219::set_column(const uint8_t col, const uint32_t val) { - if (col >= MAX7219_X_LEDS) return error(PSTR("set_column"), col); + if (col >= MAX7219_X_LEDS) return error(F("set_column"), col); uint32_t mask = _BV32(MAX7219_Y_LEDS - 1); - LOOP_L_N(y, MAX7219_Y_LEDS) { + for (uint8_t y = 0; y < MAX7219_Y_LEDS; ++y) { if (val & mask) SET_7219(col, y); else CLR_7219(col, y); mask >>= 1; } @@ -371,113 +399,111 @@ void Max7219::set_column(const uint8_t col, const uint32_t val) { void Max7219::set_rows_16bits(const uint8_t y, uint32_t val) { #if MAX7219_X_LEDS == 8 - if (y > MAX7219_Y_LEDS - 2) return error(PSTR("set_rows_16bits"), y, val); + if (y > MAX7219_Y_LEDS - 2) return error(F("set_rows_16bits"), y, val); set_row(y + 1, val); val >>= 8; set_row(y + 0, val); #else // at least 16 bits on each row - if (y > MAX7219_Y_LEDS - 1) return error(PSTR("set_rows_16bits"), y, val); + if (y > MAX7219_Y_LEDS - 1) return error(F("set_rows_16bits"), y, val); set_row(y, val); #endif } void Max7219::set_rows_32bits(const uint8_t y, uint32_t val) { #if MAX7219_X_LEDS == 8 - if (y > MAX7219_Y_LEDS - 4) return error(PSTR("set_rows_32bits"), y, val); + if (y > MAX7219_Y_LEDS - 4) return error(F("set_rows_32bits"), y, val); set_row(y + 3, val); val >>= 8; set_row(y + 2, val); val >>= 8; set_row(y + 1, val); val >>= 8; set_row(y + 0, val); #elif MAX7219_X_LEDS == 16 - if (y > MAX7219_Y_LEDS - 2) return error(PSTR("set_rows_32bits"), y, val); + if (y > MAX7219_Y_LEDS - 2) return error(F("set_rows_32bits"), y, val); set_row(y + 1, val); val >>= 16; set_row(y + 0, val); #else // at least 24 bits on each row. In the 3 matrix case, just display the low 24 bits - if (y > MAX7219_Y_LEDS - 1) return error(PSTR("set_rows_32bits"), y, val); + if (y > MAX7219_Y_LEDS - 1) return error(F("set_rows_32bits"), y, val); set_row(y, val); #endif } void Max7219::set_columns_16bits(const uint8_t x, uint32_t val) { #if MAX7219_Y_LEDS == 8 - if (x > MAX7219_X_LEDS - 2) return error(PSTR("set_columns_16bits"), x, val); + if (x > MAX7219_X_LEDS - 2) return error(F("set_columns_16bits"), x, val); set_column(x + 0, val); val >>= 8; set_column(x + 1, val); #else // at least 16 bits in each column - if (x > MAX7219_X_LEDS - 1) return error(PSTR("set_columns_16bits"), x, val); + if (x > MAX7219_X_LEDS - 1) return error(F("set_columns_16bits"), x, val); set_column(x, val); #endif } void Max7219::set_columns_32bits(const uint8_t x, uint32_t val) { #if MAX7219_Y_LEDS == 8 - if (x > MAX7219_X_LEDS - 4) return error(PSTR("set_rows_32bits"), x, val); + if (x > MAX7219_X_LEDS - 4) return error(F("set_rows_32bits"), x, val); set_column(x + 3, val); val >>= 8; set_column(x + 2, val); val >>= 8; set_column(x + 1, val); val >>= 8; set_column(x + 0, val); #elif MAX7219_Y_LEDS == 16 - if (x > MAX7219_X_LEDS - 2) return error(PSTR("set_rows_32bits"), x, val); + if (x > MAX7219_X_LEDS - 2) return error(F("set_rows_32bits"), x, val); set_column(x + 1, val); val >>= 16; set_column(x + 0, val); #else // at least 24 bits on each row. In the 3 matrix case, just display the low 24 bits - if (x > MAX7219_X_LEDS - 1) return error(PSTR("set_rows_32bits"), x, val); + if (x > MAX7219_X_LEDS - 1) return error(F("set_rows_32bits"), x, val); set_column(x, val); #endif } // Initialize the Max7219 void Max7219::register_setup() { - LOOP_L_N(i, MAX7219_NUMBER_UNITS) + for (uint8_t i = 0; i < MAX7219_NUMBER_UNITS; ++i) send(max7219_reg_scanLimit, 0x07); pulse_load(); // Tell the chips to load the clocked out data - LOOP_L_N(i, MAX7219_NUMBER_UNITS) + for (uint8_t i = 0; i < MAX7219_NUMBER_UNITS; ++i) send(max7219_reg_decodeMode, 0x00); // Using an led matrix (not digits) pulse_load(); // Tell the chips to load the clocked out data - LOOP_L_N(i, MAX7219_NUMBER_UNITS) + for (uint8_t i = 0; i < MAX7219_NUMBER_UNITS; ++i) send(max7219_reg_shutdown, 0x01); // Not in shutdown mode pulse_load(); // Tell the chips to load the clocked out data - LOOP_L_N(i, MAX7219_NUMBER_UNITS) + for (uint8_t i = 0; i < MAX7219_NUMBER_UNITS; ++i) send(max7219_reg_displayTest, 0x00); // No display test pulse_load(); // Tell the chips to load the clocked out data - LOOP_L_N(i, MAX7219_NUMBER_UNITS) + for (uint8_t i = 0; i < MAX7219_NUMBER_UNITS; ++i) send(max7219_reg_intensity, 0x01 & 0x0F); // The first 0x0F is the value you can set // Range: 0x00 to 0x0F pulse_load(); // Tell the chips to load the clocked out data } -#ifdef MAX7219_INIT_TEST +#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; + xy_int8_t spiral; + int8_t spiral_dir; + uvalue_t(MAX7219_LEDS) spiral_count; - int8_t spiralx, spiraly, spiral_dir; - IF<(MAX7219_LEDS > 255), uint16_t, uint8_t>::type 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; } } @@ -488,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)) { @@ -498,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; @@ -537,15 +561,34 @@ void Max7219::init() { register_setup(); - LOOP_LE_N(i, 7) { // Empty registers to turn all LEDs off - led_line[i] = 0x00; - send(max7219_reg_digit0 + i, 0); - pulse_load(); // Tell the chips to load the clocked out data - } + clear(); - #ifdef MAX7219_INIT_TEST + #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 } /** @@ -555,41 +598,55 @@ void Max7219::init() { */ // Apply changes to update a marker -void Max7219::mark16(const uint8_t pos, const uint8_t v1, const uint8_t v2) { +void Max7219::mark16(const uint8_t pos, const uint8_t v1, const uint8_t v2, uint8_t * const rcm/*=nullptr*/) { #if MAX7219_X_LEDS > 8 // At least 16 LEDs on the X-Axis. Use single line. - led_off(v1 & 0xF, pos); - led_on(v2 & 0xF, pos); + led_off(v1 & 0xF, pos, rcm); + led_on(v2 & 0xF, pos, rcm); #elif MAX7219_Y_LEDS > 8 // At least 16 LEDs on the Y-Axis. Use a single column. - led_off(pos, v1 & 0xF); - led_on(pos, v2 & 0xF); + led_off(pos, v1 & 0xF, rcm); + led_on(pos, v2 & 0xF, rcm); #else // Single 8x8 LED matrix. Use two lines to get 16 LEDs. - led_off(v1 & 0x7, pos + (v1 >= 8)); - led_on(v2 & 0x7, pos + (v2 >= 8)); + led_off(v1 & 0x7, pos + (v1 >= 8), rcm); + led_on(v2 & 0x7, pos + (v2 >= 8), rcm); #endif } // Apply changes to update a tail-to-head range -void Max7219::range16(const uint8_t y, const uint8_t ot, const uint8_t nt, const uint8_t oh, const uint8_t nh) { +void Max7219::range16(const uint8_t y, const uint8_t ot, const uint8_t nt, const uint8_t oh, + const uint8_t nh, uint8_t * const rcm/*=nullptr*/) { #if MAX7219_X_LEDS > 8 // At least 16 LEDs on the X-Axis. Use single line. if (ot != nt) for (uint8_t n = ot & 0xF; n != (nt & 0xF) && n != (nh & 0xF); n = (n + 1) & 0xF) - led_off(n & 0xF, y); + led_off(n & 0xF, y, rcm); if (oh != nh) for (uint8_t n = (oh + 1) & 0xF; n != ((nh + 1) & 0xF); n = (n + 1) & 0xF) - led_on(n & 0xF, y); + led_on(n & 0xF, y, rcm); #elif MAX7219_Y_LEDS > 8 // At least 16 LEDs on the Y-Axis. Use a single column. if (ot != nt) for (uint8_t n = ot & 0xF; n != (nt & 0xF) && n != (nh & 0xF); n = (n + 1) & 0xF) - led_off(y, n & 0xF); + led_off(y, n & 0xF, rcm); if (oh != nh) for (uint8_t n = (oh + 1) & 0xF; n != ((nh + 1) & 0xF); n = (n + 1) & 0xF) - led_on(y, n & 0xF); + led_on(y, n & 0xF, rcm); #else // Single 8x8 LED matrix. Use two lines to get 16 LEDs. if (ot != nt) for (uint8_t n = ot & 0xF; n != (nt & 0xF) && n != (nh & 0xF); n = (n + 1) & 0xF) - led_off(n & 0x7, y + (n >= 8)); + led_off(n & 0x7, y + (n >= 8), rcm); if (oh != nh) for (uint8_t n = (oh + 1) & 0xF; n != ((nh + 1) & 0xF); n = (n + 1) & 0xF) - led_on(n & 0x7, y + (n >= 8)); + led_on(n & 0x7, y + (n >= 8), rcm); #endif } // Apply changes to update a quantity -void Max7219::quantity16(const uint8_t pos, const uint8_t ov, const uint8_t nv) { +void Max7219::quantity(const uint8_t pos, const uint8_t ov, const uint8_t nv, uint8_t * const rcm/*=nullptr*/) { + for (uint8_t i = _MIN(nv, ov); i < _MAX(nv, ov); i++) + led_set( + #if MAX7219_X_LEDS >= MAX7219_Y_LEDS + i, pos // Single matrix or multiple matrices in Landscape + #else + pos, i // Multiple matrices in Portrait + #endif + , nv >= ov + , rcm + ); +} + +void Max7219::quantity16(const uint8_t pos, const uint8_t ov, const uint8_t nv, uint8_t * const rcm/*=nullptr*/) { for (uint8_t i = _MIN(nv, ov); i < _MAX(nv, ov); i++) led_set( #if MAX7219_X_LEDS > 8 // At least 16 LEDs on the X-Axis. Use single line. @@ -600,6 +657,7 @@ void Max7219::quantity16(const uint8_t pos, const uint8_t ov, const uint8_t nv) i >> 1, pos + (i & 1) #endif , nv >= ov + , rcm ); } @@ -637,26 +695,28 @@ void Max7219::idle_tasks() { register_setup(); } - #ifdef MAX7219_INIT_TEST + #if MAX7219_INIT_TEST if (test_mode) { run_test_pattern(); return; } #endif + // suspend updates and record which lines have changed for batching later + suspended++; + uint8_t row_change_mask = 0x00; + #if ENABLED(MAX7219_DEBUG_PRINTER_ALIVE) if (do_blink) { - led_toggle(MAX7219_X_LEDS - 1, MAX7219_Y_LEDS - 1); + led_toggle(MAX7219_X_LEDS - 1, MAX7219_Y_LEDS - 1, &row_change_mask); next_blink = ms + 1000; } #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; - if (last_head_cnt != head || last_tail_cnt != tail) { - range16(MAX7219_DEBUG_PLANNER_HEAD, last_tail_cnt, tail, last_head_cnt, head); + range16(MAX7219_DEBUG_PLANNER_HEAD, last_tail_cnt, tail, last_head_cnt, head, &row_change_mask); last_head_cnt = head; last_tail_cnt = tail; } @@ -664,17 +724,15 @@ 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); + mark16(MAX7219_DEBUG_PLANNER_HEAD, last_head_cnt, head, &row_change_mask); last_head_cnt = head; } #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); + mark16(MAX7219_DEBUG_PLANNER_TAIL, last_tail_cnt, tail, &row_change_mask); last_tail_cnt = tail; } #endif @@ -682,14 +740,49 @@ void Max7219::idle_tasks() { #endif #ifdef MAX7219_DEBUG_PLANNER_QUEUE - static int16_t last_depth = 0; - const int16_t current_depth = (head - tail + BLOCK_BUFFER_SIZE) & (BLOCK_BUFFER_SIZE - 1) & 0xF; + 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); + quantity16(MAX7219_DEBUG_PLANNER_QUEUE, last_depth, current_depth, &row_change_mask); last_depth = current_depth; } #endif + #ifdef MAX7219_DEBUG_PROFILE + 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); + last_time_fraction = current_time_fraction; + } + #endif + + #ifdef MAX7219_DEBUG_MULTISTEPPING + static uint8_t last_multistepping = 0; + const uint8_t multistepping = stepper.steps_per_isr; + if (multistepping != last_multistepping) { + static uint8_t log2_old = 0; + uint8_t log2_new = 0; + for (uint8_t val = multistepping; val > 1; val >>= 1) log2_new++; + mark16(MAX7219_DEBUG_MULTISTEPPING, log2_old, log2_new, &row_change_mask); + last_multistepping = multistepping; + log2_old = log2_new; + } + #endif + + #ifdef MAX7219_DEBUG_SLOWDOWN + static uint8_t last_slowdown_count = 0; + 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; + } + #endif + + // batch line updates + suspended--; + if (!suspended) + for (uint8_t i = 0; i < 8; ++i) if (row_change_mask & _BV(i)) + refresh_line(i); + // After resume() automatically do a refresh() if (suspended == 0x80) { suspended = 0; diff --git a/Marlin/src/feature/max7219.h b/Marlin/src/feature/max7219.h index 8e98c9456c..9c39541a7c 100644 --- a/Marlin/src/feature/max7219.h +++ b/Marlin/src/feature/max7219.h @@ -42,10 +42,11 @@ * a Max7219_Set_Row(). The opposite is true for rotations of 0 or 180 degrees. */ +#include "../inc/MarlinConfig.h" + #ifndef MAX7219_ROTATE #define MAX7219_ROTATE 0 #endif -#define _ROT ((MAX7219_ROTATE + 360) % 360) #ifndef MAX7219_NUMBER_UNITS #define MAX7219_NUMBER_UNITS 1 @@ -71,6 +72,66 @@ #define max7219_reg_shutdown 0x0C #define max7219_reg_displayTest 0x0F +#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 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 }; + + private: + static Mode mode; + static uint8_t instance_count; + static uint32_t last_calc_time; + static uint32_t total_time; + static uint8_t time_fraction; + static uint16_t call_count; + + uint32_t start_time; + + public: + CodeProfiler() : start_time(micros()) { instance_count++; } + ~CodeProfiler() { + instance_count--; + if (mode == FREEZE) return; + + call_count++; + + const uint32_t now = micros(); + total_time += now - start_time; + + if (mode == ACCUMULATE_TOTAL) return; + + // update time_fraction every hundred milliseconds + 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; + } + } + + static void set_mode(Mode _mode) { mode = _mode; } + static void reset() { + time_fraction = 0; + last_calc_time = micros(); + total_time = 0; + call_count = 0; + } + // returns fraction of total time which was measured, scaled from 0 to 128 + static uint8_t get_time_fraction() { return time_fraction; } + // returns total time in microseconds + static uint32_t get_total_time() { return total_time; } + + static uint16_t get_call_count() { return call_count; } + }; +#endif + class Max7219 { public: static uint8_t led_line[MAX7219_LINES]; @@ -86,13 +147,13 @@ public: static void send(const uint8_t reg, const uint8_t data); // Refresh all units - static inline void refresh() { for (uint8_t i = 0; i < 8; i++) refresh_line(i); } + static void refresh() { for (uint8_t i = 0; i < 8; i++) refresh_line(i); } // Suspend / resume updates to the LED unit // Use these methods to speed up multiple changes // or to apply updates from interrupt context. - static inline void suspend() { suspended++; } - static inline void resume() { suspended--; suspended |= 0x80; } + static void suspend() { suspended++; } + static void resume() { suspended--; suspended |= 0x80; } // Update a single native line on all units static void refresh_line(const uint8_t line); @@ -100,11 +161,18 @@ public: // Update a single native line on just one unit static void refresh_unit_line(const uint8_t line); + #if ENABLED(MAX7219_NUMERIC) + // 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 value, const uint8_t pre_size, const uint8_t post_size, const bool leadzero=false); + #endif + // Set a single LED by XY coordinate - static void led_set(const uint8_t x, const uint8_t y, const bool on); - static void led_on(const uint8_t x, const uint8_t y); - static void led_off(const uint8_t x, const uint8_t y); - static void led_toggle(const uint8_t x, const uint8_t y); + static void led_set(const uint8_t x, const uint8_t y, const bool on, uint8_t * const rcm=nullptr); + static void led_on(const uint8_t x, const uint8_t y, uint8_t * const rcm=nullptr); + static void led_off(const uint8_t x, const uint8_t y, uint8_t * const rcm=nullptr); + static void led_toggle(const uint8_t x, const uint8_t y, uint8_t * const rcm=nullptr); // Set all LEDs in a single column static void set_column(const uint8_t col, const uint32_t val); @@ -133,16 +201,17 @@ public: private: static uint8_t suspended; - static void error(const char * const func, const int32_t v1, const int32_t v2=-1); + static void error(FSTR_P const func, const int32_t v1, const int32_t v2=-1); static void noop(); static void set(const uint8_t line, const uint8_t bits); static void send_row(const uint8_t row); static void send_column(const uint8_t col); - static void mark16(const uint8_t y, const uint8_t v1, const uint8_t v2); - static void range16(const uint8_t y, const uint8_t ot, const uint8_t nt, const uint8_t oh, const uint8_t nh); - static void quantity16(const uint8_t y, const uint8_t ov, const uint8_t nv); + static void mark16(const uint8_t y, const uint8_t v1, const uint8_t v2, uint8_t * const rcm=nullptr); + static void range16(const uint8_t y, const uint8_t ot, const uint8_t nt, const uint8_t oh, const uint8_t nh, uint8_t * const rcm=nullptr); + static void quantity(const uint8_t y, const uint8_t ov, const uint8_t nv, uint8_t * const rcm=nullptr); + static void quantity16(const uint8_t y, const uint8_t ov, const uint8_t nv, uint8_t * const rcm=nullptr); - #ifdef MAX7219_INIT_TEST + #if MAX7219_INIT_TEST static void test_pattern(); static void run_test_pattern(); static void start_test_pattern(); diff --git a/Marlin/src/feature/meatpack.cpp b/Marlin/src/feature/meatpack.cpp new file mode 100644 index 0000000000..3b762d4ded --- /dev/null +++ b/Marlin/src/feature/meatpack.cpp @@ -0,0 +1,220 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2020 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 . + * + */ + +/** + * MeatPack G-code Compression + * + * Algorithm & Implementation: Scott Mudge - mail@scottmudge.com + * Date: Dec. 2020 + * + * Character Frequencies from ~30 MB of comment-stripped G-code: + * '1' -> 4451136 '4' -> 1353273 '\n' -> 1087683 '-' -> 90242 + * '0' -> 4253577 '9' -> 1352147 'G' -> 1075806 'Z' -> 34109 + * ' ' -> 3053297 '3' -> 1262929 'X' -> 975742 'M' -> 11879 + * '.' -> 3035310 '5' -> 1189871 'E' -> 965275 'S' -> 9910 + * '2' -> 1523296 '6' -> 1127900 'Y' -> 965274 + * '8' -> 1366812 '7' -> 1112908 'F' -> 99416 + * + * When space is omitted the letter 'E' is used in its place + */ + +#include "../inc/MarlinConfig.h" + +#if HAS_MEATPACK + +#include "meatpack.h" + +#define MeatPack_ProtocolVersion "PV01" +//#define MP_DEBUG + +#define DEBUG_OUT ENABLED(MP_DEBUG) +#include "../core/debug_out.h" + +// The 15 most-common characters used in G-code, ~90-95% of all G-code uses these characters +// Stored in SRAM for performance. +uint8_t meatPackLookupTable[16] = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '.', ' ', '\n', 'G', 'X', + '\0' // Unused. 0b1111 indicates a literal character +}; + +#if ENABLED(MP_DEBUG) + uint8_t chars_decoded = 0; // Log the first 64 bytes after each reset +#endif + +void MeatPack::reset_state() { + state = 0; + cmd_is_next = false; + second_char = 0; + cmd_count = full_char_count = char_out_count = 0; + TERN_(MP_DEBUG, chars_decoded = 0); +} + +/** + * Unpack one or two characters from a packed byte into a buffer. + * Return flags indicating whether any literal bytes follow. + */ +uint8_t MeatPack::unpack_chars(const uint8_t pk, uint8_t* __restrict const chars_out) { + uint8_t out = 0; + + // If lower nybble is 1111, the higher nybble is unused, and next char is full. + if ((pk & kFirstNotPacked) == kFirstNotPacked) + out = kFirstCharIsLiteral; + else { + const uint8_t chr = pk & 0x0F; + chars_out[0] = meatPackLookupTable[chr]; // Set the first char + } + + // Check if upper nybble is 1111... if so, we don't need the second char. + if ((pk & kSecondNotPacked) == kSecondNotPacked) + out |= kSecondCharIsLiteral; + else { + const uint8_t chr = (pk >> 4) & 0x0F; + chars_out[1] = meatPackLookupTable[chr]; // Set the second char + } + + return out; +} + +/** + * Interpret a single (non-command) character + * according to the current MeatPack state. + */ +void MeatPack::handle_rx_char_inner(const uint8_t c) { + if (TEST(state, MPConfig_Bit_Active)) { // Is MeatPack active? + if (!full_char_count) { // No literal characters to fetch? + uint8_t buf[2] = { 0, 0 }; + const uint8_t res = unpack_chars(c, buf); // Decode the byte into one or two characters. + if (res & kFirstCharIsLiteral) { // The 1st character couldn't be packed. + ++full_char_count; // So the next stream byte is a full character. + if (res & kSecondCharIsLiteral) ++full_char_count; // The 2nd character couldn't be packed. Another stream byte is a full character. + else second_char = buf[1]; // Retain the unpacked second character. + } + else { + handle_output_char(buf[0]); // Send the unpacked first character out. + if (buf[0] != '\n') { // After a newline the next char won't be set + if (res & kSecondCharIsLiteral) ++full_char_count; // The 2nd character couldn't be packed. The next stream byte is a full character. + else handle_output_char(buf[1]); // Send the unpacked second character out. + } + } + } + else { + handle_output_char(c); // Pass through the character that couldn't be packed... + if (second_char) { + handle_output_char(second_char); // ...and send an unpacked 2nd character, if set. + second_char = 0; + } + --full_char_count; // One literal character was consumed + } + } + else // Packing not enabled, just copy character to output + handle_output_char(c); +} + +/** + * Buffer a single output character which will be picked up in + * GCodeQueue::get_serial_commands via calls to get_result_char + */ +void MeatPack::handle_output_char(const uint8_t c) { + char_out_buf[char_out_count++] = c; + + #if ENABLED(MP_DEBUG) + if (chars_decoded < 1024) { + ++chars_decoded; + DEBUG_ECHOLNPGM("RB: ", C(c)); + } + #endif +} + +/** + * Process a MeatPack command byte to update the state. + * Report the new state to serial. + */ +void MeatPack::handle_command(const MeatPack_Command c) { + switch (c) { + case MPCommand_QueryConfig: break; + case MPCommand_EnablePacking: SBI(state, MPConfig_Bit_Active); DEBUG_ECHOLNPGM("[MPDBG] ENA REC"); break; + case MPCommand_DisablePacking: CBI(state, MPConfig_Bit_Active); DEBUG_ECHOLNPGM("[MPDBG] DIS REC"); break; + case MPCommand_ResetAll: reset_state(); DEBUG_ECHOLNPGM("[MPDBG] RESET REC"); break; + case MPCommand_EnableNoSpaces: + SBI(state, MPConfig_Bit_NoSpaces); + meatPackLookupTable[kSpaceCharIdx] = kSpaceCharReplace; DEBUG_ECHOLNPGM("[MPDBG] ENA NSP"); break; + case MPCommand_DisableNoSpaces: + CBI(state, MPConfig_Bit_NoSpaces); + meatPackLookupTable[kSpaceCharIdx] = ' '; DEBUG_ECHOLNPGM("[MPDBG] DIS NSP"); break; + default: DEBUG_ECHOLNPGM("[MPDBG] UNK CMD REC"); + } + report_state(); +} + +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_ECHO( + F("[MP] " MeatPack_ProtocolVersion " "), + ON_OFF(TEST(state, MPConfig_Bit_Active)), + TEST(state, MPConfig_Bit_NoSpaces) ? F(" NSP\n") : F(" ESP\n") + ); +} + +/** + * Interpret a single character received from serial + * according to the current meatpack state. + */ +void MeatPack::handle_rx_char(const uint8_t c, const serial_index_t serial_ind) { + if (c == kCommandByte) { // A command (0xFF) byte? + if (cmd_count) { // In fact, two in a row? + cmd_is_next = true; // Then a MeatPack command follows + cmd_count = 0; + } + else + ++cmd_count; // cmd_count = 1 // One command byte received so far... + return; + } + + if (cmd_is_next) { // Were two command bytes received? + PORT_REDIRECT(SERIAL_PORTMASK(serial_ind)); + handle_command((MeatPack_Command)c); // Then the byte is a MeatPack command + cmd_is_next = false; + return; + } + + if (cmd_count) { // Only a single 0xFF was received + handle_rx_char_inner(kCommandByte); // A single 0xFF is passed on literally so it can be interpreted as kFirstNotPacked|kSecondNotPacked + cmd_count = 0; + } + + handle_rx_char_inner(c); // Other characters are passed on for MeatPack decoding +} + +uint8_t MeatPack::get_result_char(char * const __restrict out) { + uint8_t res = 0; + if (char_out_count) { + res = char_out_count; + char_out_count = 0; + for (uint8_t i = 0; i < res; ++i) + out[i] = (char)char_out_buf[i]; + } + return res; +} + +#endif // HAS_MEATPACK diff --git a/Marlin/src/feature/meatpack.h b/Marlin/src/feature/meatpack.h new file mode 100644 index 0000000000..0de1f792c1 --- /dev/null +++ b/Marlin/src/feature/meatpack.h @@ -0,0 +1,174 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2020 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 . + * + */ + +/** + * MeatPack G-code Compression + * + * Algorithm & Implementation: Scott Mudge - mail@scottmudge.com + * Date: Dec. 2020 + * + * Specifically optimized for 3D printing G-Code, this is a zero-cost data compression method + * which packs ~180-190% more data into the same amount of bytes going to the CNC controller. + * As a majority of G-Code can be represented by a restricted alphabet, I performed histogram + * analysis on a wide variety of 3D printing G-code samples, and found ~93% of all G-code could + * be represented by the same 15-character alphabet. + * + * This allowed me to design a system of packing 2 8-bit characters into a single byte, assuming + * they fall within this limited 15-character alphabet. Using a 4-bit lookup table, these 8-bit + * characters can be represented by a 4-bit index. + * + * Combined with some logic to allow commingling of full-width characters outside of this 15- + * character alphabet (at the cost of an extra 8-bits per full-width character), and by stripping + * out unnecessary comments, the end result is G-code which is roughly half the original size. + * + * Why did I do this? I noticed micro-stuttering and other data-bottleneck issues while printing + * objects with high curvature, especially at high speeds. There is also the issue of the limited + * baud rate provided by Prusa's Atmega2560-based boards, over the USB serial connection. So soft- + * ware like OctoPrint would also suffer this same micro-stuttering and poor print quality issue. + * + */ +#pragma once + +#include +#include "../core/serial_hook.h" + +/** + * Commands sent to MeatPack to control its behavior. + * They are sent by first sending 2x MeatPack_CommandByte (0xFF) in sequence, + * followed by one of the command bytes below. + * Provided that 0xFF is an exceedingly rare character that is virtually never + * present in G-code naturally, it is safe to assume 2 in sequence should never + * happen naturally, and so it is used as a signal here. + * + * 0xFF *IS* used in "packed" G-code (used to denote that the next 2 characters are + * full-width), however 2 in a row will never occur, as the next 2 bytes will always + * some non-0xFF character. + */ +enum MeatPack_Command : uint8_t { + MPCommand_None = 0, + MPCommand_EnablePacking = 0xFB, + MPCommand_DisablePacking = 0xFA, + MPCommand_ResetAll = 0xF9, + MPCommand_QueryConfig = 0xF8, + MPCommand_EnableNoSpaces = 0xF7, + MPCommand_DisableNoSpaces = 0xF6 +}; + +enum MeatPack_ConfigStateBits : uint8_t { + MPConfig_Bit_Active = 0, + MPConfig_Bit_NoSpaces = 1 +}; + +class MeatPack { + + // Utility definitions + static const uint8_t kCommandByte = 0b11111111, + kFirstNotPacked = 0b00001111, + kSecondNotPacked = 0b11110000, + kFirstCharIsLiteral = 0b00000001, + kSecondCharIsLiteral = 0b00000010; + + static const uint8_t kSpaceCharIdx = 11; + static const char kSpaceCharReplace = 'E'; + + bool cmd_is_next; // A command is pending + uint8_t state; // Configuration state + uint8_t second_char; // Buffers a character if dealing with out-of-sequence pairs + uint8_t cmd_count, // Counter of command bytes received (need 2) + full_char_count, // Counter for full-width characters to be received + char_out_count; // Stores number of characters to be read out. + uint8_t char_out_buf[2]; // Output buffer for caching up to 2 characters + +public: + // Pass in a character rx'd by SD card or serial. Automatically parses command/ctrl sequences, + // and will control state internally. + void handle_rx_char(const uint8_t c, const serial_index_t serial_ind); + + /** + * After passing in rx'd char using above method, call this to get characters out. + * Can return from 0 to 2 characters at once. + * @param out [in] Output pointer for unpacked/processed data. + * @return Number of characters returned. Range from 0 to 2. + */ + uint8_t get_result_char(char * const __restrict out); + + void reset_state(); + void report_state(); + uint8_t unpack_chars(const uint8_t pk, uint8_t* __restrict const chars_out); + void handle_command(const MeatPack_Command c); + void handle_output_char(const uint8_t c); + void handle_rx_char_inner(const uint8_t c); + + MeatPack() : cmd_is_next(false), state(0), second_char(0), cmd_count(0), full_char_count(0), char_out_count(0) {} +}; + +// Implement the MeatPack serial class so it's transparent to rest of the code +template +struct MeatpackSerial : public SerialBase > { + typedef SerialBase< MeatpackSerial > BaseClassT; + + SerialT & out; + MeatPack meatpack; + + char serialBuffer[2]; + uint8_t charCount; + uint8_t readIndex; + + NO_INLINE void write(uint8_t c) { out.write(c); } + void flush() { out.flush(); } + void begin(long br) { out.begin(br); readIndex = 0; } + void end() { out.end(); } + + void msgDone() { out.msgDone(); } + // Existing instances implement Arduino's operator bool, so use that if it's available + bool connected() { return Private::HasMember_connected::value ? CALL_IF_EXISTS(bool, &out, connected) : (bool)out; } + void flushTX() { CALL_IF_EXISTS(void, &out, flushTX); } + SerialFeature features(serial_index_t index) const { return SerialFeature::MeatPack | CALL_IF_EXISTS(SerialFeature, &out, features, index); } + + int available(serial_index_t index) { + if (charCount) return charCount; // The buffer still has data + if (out.available(index) <= 0) return 0; // No data to read + + // Don't read in read method, instead do it here, so we can make progress in the read method + const int r = out.read(index); + if (r == -1) return 0; // This is an error from the underlying serial code + meatpack.handle_rx_char((uint8_t)r, index); + charCount = meatpack.get_result_char(serialBuffer); + readIndex = 0; + + return charCount; + } + + int readImpl(const serial_index_t index) { + // Not enough char to make progress? + if (charCount == 0 && available(index) == 0) return -1; + + charCount--; + return serialBuffer[readIndex++]; + } + + int read(serial_index_t index) { return readImpl(index); } + int available() { return available(0); } + int read() { return readImpl(0); } + + MeatpackSerial(const bool e, SerialT & out) : BaseClassT(e), out(out) {} +}; diff --git a/Marlin/src/feature/mixing.cpp b/Marlin/src/feature/mixing.cpp index b002e9808a..bc98bf3b26 100644 --- a/Marlin/src/feature/mixing.cpp +++ b/Marlin/src/feature/mixing.cpp @@ -24,16 +24,10 @@ #if ENABLED(MIXING_EXTRUDER) -//#define MIXER_NORMALIZER_DEBUG - #include "mixing.h" 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. @@ -44,7 +38,7 @@ int_fast8_t Mixer::runner = 0; mixer_comp_t Mixer::s_color[MIXING_STEPPERS]; mixer_accu_t Mixer::accu[MIXING_STEPPERS] = { 0 }; -#if EITHER(HAS_DUAL_MIXING, GRADIENT_MIX) +#if ANY(HAS_DUAL_MIXING, GRADIENT_MIX) mixer_perc_t Mixer::mix[MIXING_STEPPERS]; #endif @@ -62,10 +56,7 @@ void Mixer::normalize(const uint8_t tool_index) { } #ifdef MIXER_NORMALIZER_DEBUG SERIAL_ECHOPGM("Mixer: Old relation : [ "); - MIXER_STEPPER_LOOP(i) { - SERIAL_ECHO_F(collector[i] / csum, 3); - SERIAL_CHAR(' '); - } + MIXER_STEPPER_LOOP(i) SERIAL_ECHO(collector[i] / csum, C(' ')); SERIAL_ECHOLNPGM("]"); #endif @@ -77,16 +68,12 @@ void Mixer::normalize(const uint8_t tool_index) { csum = 0; SERIAL_ECHOPGM("Mixer: Normalize to : [ "); MIXER_STEPPER_LOOP(i) { - SERIAL_ECHO(uint16_t(color[tool_index][i])); - SERIAL_CHAR(' '); + SERIAL_ECHO(uint16_t(color[tool_index][i]), C(' ')); csum += color[tool_index][i]; } SERIAL_ECHOLNPGM("]"); SERIAL_ECHOPGM("Mixer: New relation : [ "); - MIXER_STEPPER_LOOP(i) { - SERIAL_ECHO_F(uint16_t(color[tool_index][i]) / csum, 3); - SERIAL_CHAR(' '); - } + MIXER_STEPPER_LOOP(i) SERIAL_ECHO(p_float_t(uint16_t(color[tool_index][i]) / csum, 3), C(' ')); SERIAL_ECHOLNPGM("]"); #endif @@ -96,21 +83,42 @@ void Mixer::normalize(const uint8_t tool_index) { void Mixer::reset_vtools() { // Virtual Tools 0, 1, 2, 3 = Filament 1, 2, 3, 4, etc. // Every virtual tool gets a pure filament - LOOP_L_N(t, _MIN(MIXING_VIRTUAL_TOOLS, MIXING_STEPPERS)) + for (uint8_t t = 0; t < _MIN(MIXING_VIRTUAL_TOOLS, MIXING_STEPPERS); ++t) MIXER_STEPPER_LOOP(i) color[t][i] = (t == i) ? COLOR_A_MASK : 0; // Remaining virtual tools are 100% filament 1 #if MIXING_VIRTUAL_TOOLS > MIXING_STEPPERS - LOOP_S_L_N(t, MIXING_STEPPERS, MIXING_VIRTUAL_TOOLS) + for (uint8_t t = MIXING_STEPPERS; t < MIXING_VIRTUAL_TOOLS; ++t) MIXER_STEPPER_LOOP(i) color[t][i] = (i == 0) ? COLOR_A_MASK : 0; #endif + + // MIXING_PRESETS: Set a variety of obvious mixes as presets + #if ENABLED(MIXING_PRESETS) && WITHIN(MIXING_STEPPERS, 2, 3) + #if MIXING_STEPPERS == 2 + if (MIXING_VIRTUAL_TOOLS > 2) { collector[0] = 1; collector[1] = 1; mixer.normalize(2); } // 1:1 + if (MIXING_VIRTUAL_TOOLS > 3) { collector[0] = 3; mixer.normalize(3); } // 3:1 + if (MIXING_VIRTUAL_TOOLS > 4) { collector[0] = 1; collector[1] = 3; mixer.normalize(4); } // 1:3 + if (MIXING_VIRTUAL_TOOLS > 5) { collector[1] = 2; mixer.normalize(5); } // 1:2 + if (MIXING_VIRTUAL_TOOLS > 6) { collector[0] = 2; collector[1] = 1; mixer.normalize(6); } // 2:1 + if (MIXING_VIRTUAL_TOOLS > 7) { collector[0] = 3; collector[1] = 2; mixer.normalize(7); } // 3:2 + #else + if (MIXING_VIRTUAL_TOOLS > 3) { collector[0] = 1; collector[1] = 1; collector[2] = 1; mixer.normalize(3); } // 1:1:1 + if (MIXING_VIRTUAL_TOOLS > 4) { collector[1] = 3; collector[2] = 0; mixer.normalize(4); } // 1:3:0 + if (MIXING_VIRTUAL_TOOLS > 5) { collector[0] = 0; collector[2] = 1; mixer.normalize(5); } // 0:3:1 + if (MIXING_VIRTUAL_TOOLS > 6) { collector[1] = 1; mixer.normalize(6); } // 0:1:1 + if (MIXING_VIRTUAL_TOOLS > 7) { collector[0] = 1; collector[2] = 0; mixer.normalize(7); } // 1:1:0 + #endif + ZERO(collector); + #endif } // called at boot void Mixer::init() { + ZERO(collector); + reset_vtools(); #if HAS_MIXER_SYNC_CHANNEL @@ -119,9 +127,7 @@ void Mixer::init() { color[MIXER_AUTORETRACT_TOOL][i] = COLOR_A_MASK; #endif - ZERO(collector); - - #if EITHER(HAS_DUAL_MIXING, GRADIENT_MIX) + #if ANY(HAS_DUAL_MIXING, GRADIENT_MIX) update_mix_from_vtool(); #endif @@ -135,11 +141,11 @@ void Mixer::refresh_collector(const float proportion/*=1.0*/, const uint8_t t/*= cmax = _MAX(cmax, v); csum += v; } - //SERIAL_ECHOPAIR("Mixer::refresh_collector(", proportion, ", ", int(t), ") cmax=", cmax, " csum=", csum, " color"); + //SERIAL_ECHOPGM("Mixer::refresh_collector(", proportion, ", ", t, ") cmax=", cmax, " csum=", csum, " color"); const float inv_prop = proportion / csum; MIXER_STEPPER_LOOP(i) { c[i] = color[t][i] * inv_prop; - //SERIAL_ECHOPAIR(" [", int(t), "][", int(i), "] = ", int(color[t][i]), " (", c[i], ") "); + //SERIAL_ECHOPGM(" [", t, "][", i, "] = ", color[t][i], " (", c[i], ") "); } //SERIAL_EOL(); } @@ -150,14 +156,12 @@ void Mixer::refresh_collector(const float proportion/*=1.0*/, const uint8_t t/*= #include "../module/planner.h" gradient_t Mixer::gradient = { - false, // enabled - {0}, // color (array) - 0, 0, // start_z, end_z - 0, 1, // start_vtool, end_vtool - {0}, {0} // start_mix[], end_mix[] - #if ENABLED(GRADIENT_VTOOL) - , -1 // vtool_index - #endif + false, // enabled + {0}, // color (array) + 0, 0, // start_z, end_z + 0, 1, // start_vtool, end_vtool + {0}, {0} // start_mix[], end_mix[] + OPTARG(GRADIENT_VTOOL, -1) // vtool_index }; float Mixer::prev_z; // = 0 diff --git a/Marlin/src/feature/mixing.h b/Marlin/src/feature/mixing.h index 7fe7062a7a..bd7ffb560e 100644 --- a/Marlin/src/feature/mixing.h +++ b/Marlin/src/feature/mixing.h @@ -43,10 +43,6 @@ typedef int8_t mixer_perc_t; -#ifndef MIXING_VIRTUAL_TOOLS - #define MIXING_VIRTUAL_TOOLS 1 -#endif - enum MixTool { FIRST_USER_VIRTUAL_TOOL = 0 , LAST_USER_VIRTUAL_TOOL = MIXING_VIRTUAL_TOOLS - 1 @@ -61,9 +57,6 @@ enum MixTool { #define MAX_VTOOLS TERN(HAS_MIXER_SYNC_CHANNEL, 254, 255) static_assert(NR_MIXING_VIRTUAL_TOOLS <= MAX_VTOOLS, "MIXING_VIRTUAL_TOOLS must be <= " STRINGIFY(MAX_VTOOLS) "!"); -#define MIXER_BLOCK_FIELD mixer_comp_t b_color[MIXING_STEPPERS] -#define MIXER_POPULATE_BLOCK() mixer.populate_block(block->b_color) -#define MIXER_STEPPER_SETUP() mixer.stepper_setup(current_block->b_color) #define MIXER_STEPPER_LOOP(VAR) for (uint_fast8_t VAR = 0; VAR < MIXING_STEPPERS; VAR++) #if ENABLED(GRADIENT_MIX) @@ -73,9 +66,11 @@ static_assert(NR_MIXING_VIRTUAL_TOOLS <= MAX_VTOOLS, "MIXING_VIRTUAL_TOOLS must mixer_comp_t color[MIXING_STEPPERS]; // The current gradient color float start_z, end_z; // Region for gradient int8_t start_vtool, end_vtool; // Start and end virtual tools - mixer_perc_t start_mix[MIXING_STEPPERS], // Start and end mixes from those tools + mixer_perc_t start_mix[MIXING_STEPPERS], // Start and end mixes from those tools end_mix[MIXING_STEPPERS]; - TERN_(GRADIENT_VTOOL, int8_t vtool_index); // Use this virtual tool number as index + #if ENABLED(GRADIENT_VTOOL) + int8_t vtool_index; // Use this virtual tool number as index + #endif } gradient_t; #endif @@ -109,7 +104,7 @@ class Mixer { } // Used when dealing with blocks - FORCE_INLINE static void populate_block(mixer_comp_t b_color[MIXING_STEPPERS]) { + FORCE_INLINE static void populate_block(mixer_comp_t (&b_color)[MIXING_STEPPERS]) { #if ENABLED(GRADIENT_MIX) if (gradient.enabled) { MIXER_STEPPER_LOOP(i) b_color[i] = gradient.color[i]; @@ -119,15 +114,15 @@ class Mixer { MIXER_STEPPER_LOOP(i) b_color[i] = color[selected_vtool][i]; } - FORCE_INLINE static void stepper_setup(mixer_comp_t b_color[MIXING_STEPPERS]) { + FORCE_INLINE static void stepper_setup(mixer_comp_t (&b_color)[MIXING_STEPPERS]) { MIXER_STEPPER_LOOP(i) s_color[i] = b_color[i]; } - #if EITHER(HAS_DUAL_MIXING, GRADIENT_MIX) + #if ANY(HAS_DUAL_MIXING, GRADIENT_MIX) static mixer_perc_t mix[MIXING_STEPPERS]; // Scratch array for the Mix in proportion to 100 - static inline void copy_mix_to_color(mixer_comp_t (&tcolor)[MIXING_STEPPERS]) { + static void copy_mix_to_color(mixer_comp_t (&tcolor)[MIXING_STEPPERS]) { // Scale each component to the largest one in terms of COLOR_A_MASK // So the largest component will be COLOR_A_MASK and the other will be in proportion to it const float scale = (COLOR_A_MASK) * RECIPROCAL(_MAX( @@ -138,26 +133,24 @@ class Mixer { MIXER_STEPPER_LOOP(i) tcolor[i] = mix[i] * scale; #ifdef MIXER_NORMALIZER_DEBUG - SERIAL_ECHOPGM("Mix [ "); - SERIAL_ECHOLIST_N(MIXING_STEPPERS, int(mix[0]), int(mix[1]), int(mix[2]), int(mix[3]), int(mix[4]), int(mix[5])); - SERIAL_ECHOPGM(" ] to Color [ "); - SERIAL_ECHOLIST_N(MIXING_STEPPERS, int(tcolor[0]), int(tcolor[1]), int(tcolor[2]), int(tcolor[3]), int(tcolor[4]), int(tcolor[5])); - SERIAL_ECHOLNPGM(" ]"); + SERIAL_ECHOLN( + F("Mix [ "), LIST_N(MIXING_STEPPERS, mix[0], mix[1], mix[2], mix[3], mix[4], mix[5]), + F(" ] to Color [ "), LIST_N(MIXING_STEPPERS, tcolor[0], tcolor[1], tcolor[2], tcolor[3], tcolor[4], tcolor[5]), + F(" ]") + ); #endif } - static inline void update_mix_from_vtool(const uint8_t j=selected_vtool) { + static void update_mix_from_vtool(const uint8_t j=selected_vtool) { float ctot = 0; MIXER_STEPPER_LOOP(i) ctot += color[j][i]; - //MIXER_STEPPER_LOOP(i) mix[i] = 100.0f * color[j][i] / ctot; - MIXER_STEPPER_LOOP(i) mix[i] = mixer_perc_t(100.0f * color[j][i] / ctot); + MIXER_STEPPER_LOOP(i) mix[i] = mixer_perc_t(100.0f * color[j][i] / ctot + 0.5f); #ifdef MIXER_NORMALIZER_DEBUG - SERIAL_ECHOPAIR("V-tool ", int(j), " [ "); - SERIAL_ECHOLIST_N(MIXING_STEPPERS, int(color[j][0]), int(color[j][1]), int(color[j][2]), int(color[j][3]), int(color[j][4]), int(color[j][5])); - SERIAL_ECHOPGM(" ] to Mix [ "); - SERIAL_ECHOLIST_N(MIXING_STEPPERS, int(mix[0]), int(mix[1]), int(mix[2]), int(mix[3]), int(mix[4]), int(mix[5])); - SERIAL_ECHOLNPGM(" ]"); + SERIAL_ECHOLN(F("V-tool "), j, + F(" [ "), LIST_N(MIXING_STEPPERS, color[j][0], color[j][1], color[j][2], color[j][3], color[j][4], color[j][5]), + F(" ] to Mix [ "), LIST_N(MIXING_STEPPERS, mix[0], mix[1], mix[2], mix[3], mix[4], mix[5]), F(" ]") + ); #endif } @@ -166,7 +159,7 @@ class Mixer { #if HAS_DUAL_MIXING // Update the virtual tool from an edited mix - static inline void update_vtool_from_mix() { + static void update_vtool_from_mix() { copy_mix_to_color(color[selected_vtool]); TERN_(GRADIENT_MIX, refresh_gradient()); // MIXER_STEPPER_LOOP(i) collector[i] = mix[i]; @@ -183,7 +176,7 @@ class Mixer { // Update the current mix from the gradient for a given Z static void update_gradient_for_z(const float z); static void update_gradient_for_planner_z(); - static inline void gradient_control(const float z) { + static void gradient_control(const float z) { if (gradient.enabled) { if (z >= gradient.end_z) T(gradient.end_vtool); @@ -192,17 +185,16 @@ class Mixer { } } - static inline void update_mix_from_gradient() { + static void update_mix_from_gradient() { float ctot = 0; MIXER_STEPPER_LOOP(i) ctot += gradient.color[i]; MIXER_STEPPER_LOOP(i) mix[i] = (mixer_perc_t)CEIL(100.0f * gradient.color[i] / ctot); #ifdef MIXER_NORMALIZER_DEBUG - SERIAL_ECHOPGM("Gradient [ "); - SERIAL_ECHOLIST_N(MIXING_STEPPERS, int(gradient.color[0]), int(gradient.color[1]), int(gradient.color[2]), int(gradient.color[3]), int(gradient.color[4]), int(gradient.color[5])); - SERIAL_ECHOPGM(" ] to Mix [ "); - SERIAL_ECHOLIST_N(MIXING_STEPPERS, int(mix[0]), int(mix[1]), int(mix[2]), int(mix[3]), int(mix[4]), int(mix[5])); - SERIAL_ECHOLNPGM(" ]"); + SERIAL_ECHOLN( + F("Gradient [ "), LIST_N(MIXING_STEPPERS, gradient.color[0], gradient.color[1], gradient.color[2], gradient.color[3], gradient.color[4], gradient.color[5]), + F(" ] to Mix [ "), LIST_N(MIXING_STEPPERS, mix[0], mix[1], mix[2], mix[3], mix[4], mix[5]), F(" ]") + ); #endif } @@ -235,13 +227,7 @@ class Mixer { for (;;) { if (--runner < 0) runner = MIXING_STEPPERS - 1; accu[runner] += s_color[runner]; - if ( - #ifdef MIXER_ACCU_SIGNED - accu[runner] < 0 - #else - accu[runner] & COLOR_A_MASK - #endif - ) { + if (TERN(MIXER_ACCU_SIGNED, accu[runner] < 0, accu[runner] & COLOR_A_MASK)) { accu[runner] &= COLOR_MASK; return runner; } diff --git a/Marlin/src/feature/snmm.cpp b/Marlin/src/feature/mmu/mmu.cpp similarity index 79% rename from Marlin/src/feature/snmm.cpp rename to Marlin/src/feature/mmu/mmu.cpp index 25723f7b38..b1e66cc425 100644 --- a/Marlin/src/feature/snmm.cpp +++ b/Marlin/src/feature/mmu/mmu.cpp @@ -20,19 +20,26 @@ * */ -#include "../inc/MarlinConfig.h" +#include "../../inc/MarlinConfig.h" -#if ENABLED(MK2_MULTIPLEXER) +#if HAS_PRUSA_MMU1 -#include "../module/stepper.h" +#include "../../module/planner.h" +#include "../../module/stepper.h" + +void mmu_init() { + SET_OUTPUT(E_MUX0_PIN); + SET_OUTPUT(E_MUX1_PIN); + SET_OUTPUT(E_MUX2_PIN); +} void select_multiplexed_stepper(const uint8_t e) { planner.synchronize(); - disable_e_steppers(); + stepper.disable_e_steppers(); WRITE(E_MUX0_PIN, TEST(e, 0) ? HIGH : LOW); WRITE(E_MUX1_PIN, TEST(e, 1) ? HIGH : LOW); WRITE(E_MUX2_PIN, TEST(e, 2) ? HIGH : LOW); safe_delay(100); } -#endif // MK2_MULTIPLEXER +#endif // HAS_PRUSA_MMU1 diff --git a/Marlin/src/feature/snmm.h b/Marlin/src/feature/mmu/mmu.h similarity index 98% rename from Marlin/src/feature/snmm.h rename to Marlin/src/feature/mmu/mmu.h index 10805c8e26..23742d00c6 100644 --- a/Marlin/src/feature/snmm.h +++ b/Marlin/src/feature/mmu/mmu.h @@ -21,4 +21,5 @@ */ #pragma once +void mmu_init(); void select_multiplexed_stepper(const uint8_t e); diff --git a/Marlin/src/feature/mmu/mmu2-serial-protocol.md b/Marlin/src/feature/mmu/mmu2-serial-protocol.md new file mode 100644 index 0000000000..80480d15c7 --- /dev/null +++ b/Marlin/src/feature/mmu/mmu2-serial-protocol.md @@ -0,0 +1,78 @@ +# Startup sequence + +When initialized, MMU sends + +- MMU => 'start\n' + +We follow with + +- MMU <= 'S1\n' +- MMU => 'ok<_Firmware version_>\n' +- MMU <= 'S2\n' +- MMU => 'ok<_Build number_>\n' + +#if (12V_mode) + +- MMU <= 'M1\n' +- MMU => 'ok\n' + +#endif + +- MMU <= 'P0\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. + +<_Build number_> is checked against the required value, if it does not match, printer is halted. + +# Toolchange + +- MMU <= 'T<_Filament index_>\n' + +MMU sends + +- MMU => 'ok\n' + +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.\ +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\ +be one or more extruder moves to feed the filament into the hotend. + +# FINDA status + +- MMU <= 'P0\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. + +# Load filament + +- MMU <= 'L<_Filament index_>\n' + +MMU will feed filament down to the extruder, when done: + +- MMU => 'ok\n' + +# Unload filament + +- MMU <= 'U0\n' + +MMU will retract current filament from the extruder, when done: + +- MMU => 'ok\n' + +# Eject filament + +- MMU <= 'E<_Filament index_>\n' +- MMU => 'ok\n' diff --git a/Marlin/src/feature/mmu/mmu2.cpp b/Marlin/src/feature/mmu/mmu2.cpp new file mode 100644 index 0000000000..76e2b8505f --- /dev/null +++ b/Marlin/src/feature/mmu/mmu2.cpp @@ -0,0 +1,1064 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2020 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_PRUSA_MMU2 + +/** + * mmu2.cpp - Support for PrΕ―Ε‘a MMU2 and MMU2S + */ + +#include "mmu2.h" +#include "../../lcd/menu/menu_mmu2.h" + +MMU2 mmu2; + +#include "../../gcode/gcode.h" +#include "../../lcd/marlinui.h" +#include "../../libs/buzzer.h" +#include "../../libs/nozzle.h" +#include "../../module/temperature.h" +#include "../../module/planner.h" +#include "../../module/stepper.h" + +#if ENABLED(HOST_PROMPT_SUPPORT) + #include "../host_actions.h" +#endif + +#if ENABLED(EXTENSIBLE_UI) + #include "../../lcd/extui/ui_api.h" +#endif + +#define DEBUG_OUT ENABLED(MMU_DEBUG) +#include "../../core/debug_out.h" + +#define MMU_TODELAY 100 +#define MMU_TIMEOUT 10 +#define MMU_CMD_TIMEOUT 45000UL // 45s timeout for mmu commands (except P0) +#define MMU_P0_TIMEOUT 3000UL // Timeout for P0 command: 3seconds + +#define MMU2_SEND(S) tx_str(F(S "\n")) +#define MMU2_RECV(S) rx_str(F(S "\n")) + +#if ENABLED(MMU2_EXTRUDER_SENSOR) + uint8_t mmu_idl_sens = 0; + static bool mmu_loading_flag = false; +#endif + +#define MMU_CMD_NONE 0 +#define MMU_CMD_T0 0x10 // up to supported filaments +#define MMU_CMD_L0 0x20 // up to supported filaments +#define MMU_CMD_C0 0x30 +#define MMU_CMD_U0 0x40 +#define MMU_CMD_E0 0x50 // up to supported filaments +#define MMU_CMD_R0 0x60 +#define MMU_CMD_F0 0x70 // up to supported filaments + +#define MMU_REQUIRED_FW_BUILDNR TERN(MMU2_MODE_12V, 132, 126) + +#define MMU2_NO_TOOL 99 +#define MMU_BAUD 115200 + +bool MMU2::_enabled, MMU2::ready; +#if HAS_PRUSA_MMU2S + bool MMU2::mmu2s_triggered; +#endif +uint8_t MMU2::cmd, MMU2::cmd_arg, MMU2::last_cmd, MMU2::extruder; +int8_t MMU2::state = 0; +volatile int8_t MMU2::finda = 1; +volatile bool MMU2::finda_runout_valid; +millis_t MMU2::prev_request, MMU2::prev_P0_request; +char MMU2::rx_buffer[MMU_RX_SIZE], MMU2::tx_buffer[MMU_TX_SIZE]; + +struct E_Step { + float extrude; //!< extrude distance in mm + 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); + if (sync) planner.synchronize(); +} + +MMU2::MMU2() { + rx_buffer[0] = '\0'; +} + +void MMU2::init() { + + set_runout_valid(false); + + #if PIN_EXISTS(MMU_RST) + WRITE(MMU_RST_PIN, HIGH); + SET_OUTPUT(MMU_RST_PIN); + #endif + + MMU_SERIAL.begin(MMU_BAUD); + extruder = MMU2_NO_TOOL; + + safe_delay(10); + reset(); + rx_buffer[0] = '\0'; + state = -1; +} + +void MMU2::reset() { + DEBUG_ECHOLNPGM("MMU <= reset"); + + #if PIN_EXISTS(MMU_RST) + WRITE(MMU_RST_PIN, LOW); + safe_delay(20); + WRITE(MMU_RST_PIN, HIGH); + #else + MMU2_SEND("X0"); // Send soft reset + #endif +} + +int8_t MMU2::get_current_tool() { return extruder == MMU2_NO_TOOL ? -1 : extruder; } + +#if ANY(HAS_PRUSA_MMU2S, MMU2_EXTRUDER_SENSOR) + #define FILAMENT_PRESENT() (READ(FIL_RUNOUT1_PIN) != FIL_RUNOUT1_STATE) +#else + #define FILAMENT_PRESENT() true +#endif + +void mmu2_attn_buzz(const bool two=false) { + BUZZ(200, 404); + if (two) { BUZZ(10, 0); BUZZ(200, 404); } +} + +// Avoiding sscanf significantly reduces build size +void MMU2::mmu_loop() { + + switch (state) { + + case 0: break; + + case -1: + if (rx_start()) { + prev_P0_request = millis(); // Initialize finda sensor timeout + DEBUG_ECHOLNPGM("MMU => 'start'"); + DEBUG_ECHOLNPGM("MMU <= 'S1'"); + MMU2_SEND("S1"); // Read Version + state = -2; + } + else if (ELAPSED(millis(), prev_request, 30000)) { // 30sec after reset disable MMU + SERIAL_ECHOLNPGM("MMU not responding - DISABLED"); + state = 0; + } + break; + + case -2: + if (rx_ok()) { + const uint16_t version = uint16_t(strtoul(rx_buffer, nullptr, 10)); + DEBUG_ECHOLNPGM("MMU => ", version, "\nMMU <= 'S2'"); + MMU2_SEND("S2"); // Read Build Number + state = -3; + } + break; + + case -3: + if (rx_ok()) { + const uint16_t buildnr = uint16_t(strtoul(rx_buffer, nullptr, 10)); + DEBUG_ECHOLNPGM("MMU => ", buildnr); + + check_version(buildnr); + + #if ENABLED(MMU2_MODE_12V) + DEBUG_ECHOLNPGM("MMU <= 'M1'"); + MMU2_SEND("M1"); // Stealth Mode + state = -5; + #else + DEBUG_ECHOLNPGM("MMU <= 'P0'"); + MMU2_SEND("P0"); // Read FINDA + state = -4; + #endif + } + break; + + #if ENABLED(MMU2_MODE_12V) + case -5: + // response to M1 + if (rx_ok()) { + DEBUG_ECHOLNPGM("MMU => ok"); + DEBUG_ECHOLNPGM("MMU <= 'P0'"); + MMU2_SEND("P0"); // Read FINDA + state = -4; + } + break; + #endif + + case -4: + if (rx_ok()) { + const uint8_t findex = uint8_t(rx_buffer[0] - '0'); + if (findex <= 1) finda = findex; + + DEBUG_ECHOLNPGM("MMU => ", finda, "\nMMU - ENABLED"); + + _enabled = true; + state = 1; + TERN_(HAS_PRUSA_MMU2S, mmu2s_triggered = false); + } + break; + + case 1: + if (cmd) { + if (WITHIN(cmd, MMU_CMD_T0, MMU_CMD_T0 + EXTRUDERS - 1)) { + // tool change + const int filament = cmd - MMU_CMD_T0; + DEBUG_ECHOLNPGM("MMU <= T", filament); + tx_printf(F("T%d\n"), filament); + 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)) { + // load + const int filament = cmd - MMU_CMD_L0; + DEBUG_ECHOLNPGM("MMU <= L", filament); + tx_printf(F("L%d\n"), filament); + state = 3; // wait for response + } + else if (cmd == MMU_CMD_C0) { + // continue loading + DEBUG_ECHOLNPGM("MMU <= 'C0'"); + MMU2_SEND("C0"); + state = 3; // wait for response + } + else if (cmd == MMU_CMD_U0) { + // unload current + DEBUG_ECHOLNPGM("MMU <= 'U0'"); + MMU2_SEND("U0"); + state = 3; // wait for response + } + else if (WITHIN(cmd, MMU_CMD_E0, MMU_CMD_E0 + EXTRUDERS - 1)) { + // eject filament + const int filament = cmd - MMU_CMD_E0; + DEBUG_ECHOLNPGM("MMU <= E", filament); + tx_printf(F("E%d\n"), filament); + state = 3; // wait for response + } + else if (cmd == MMU_CMD_R0) { + // recover after eject + DEBUG_ECHOLNPGM("MMU <= 'R0'"); + MMU2_SEND("R0"); + state = 3; // wait for response + } + else if (WITHIN(cmd, MMU_CMD_F0, MMU_CMD_F0 + EXTRUDERS - 1)) { + // filament type + const int filament = cmd - MMU_CMD_F0; + DEBUG_ECHOLNPGM("MMU <= F", filament, " ", cmd_arg); + tx_printf(F("F%d %d\n"), filament, cmd_arg); + state = 3; // wait for response + } + + last_cmd = cmd; + cmd = MMU_CMD_NONE; + } + else if (ELAPSED(millis(), prev_P0_request, 300)) { + MMU2_SEND("P0"); // Read FINDA + state = 2; // wait for response + } + + TERN_(HAS_PRUSA_MMU2S, check_filament()); + break; + + case 2: // response to command P0 + if (rx_ok()) { + const uint8_t findex = uint8_t(rx_buffer[0] - '0'); + if (findex <= 1) finda = findex; + + // This is super annoying. Only activate if necessary + //if (finda_runout_valid) DEBUG_ECHOLNPGM("MMU <= 'P0'\nMMU => ", p_float_t(finda, 6)); + + if (!finda && finda_runout_valid) filament_runout(); + if (cmd == MMU_CMD_NONE) ready = true; + state = 1; + } + 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(MMU2_EXTRUDER_SENSOR) + if (mmu_idl_sens) { + if (FILAMENT_PRESENT() && mmu_loading_flag) { + DEBUG_ECHOLNPGM("MMU <= 'A'"); + MMU2_SEND("A"); // send 'abort' request + mmu_idl_sens = 0; + DEBUG_ECHOLNPGM("MMU IDLER_SENSOR = 0 - ABORT"); + } + } + #endif + + if (rx_ok()) { + #if HAS_PRUSA_MMU2S + // Respond to C0 MMU command in MMU2S model + const bool keep_trying = !mmu2s_triggered && last_cmd == MMU_CMD_C0; + if (keep_trying) { + // MMU ok received but filament sensor not triggered, retrying... + DEBUG_ECHOLNPGM("MMU => 'ok' (no filament in gears)"); + DEBUG_ECHOLNPGM("MMU <= 'C0' (keep trying)"); + MMU2_SEND("C0"); + } + #else + constexpr bool keep_trying = false; + #endif + + if (!keep_trying) { + DEBUG_ECHOLNPGM("MMU => 'ok'"); + ready = true; + state = 1; + last_cmd = MMU_CMD_NONE; + } + } + else if (ELAPSED(millis(), prev_request, MMU_CMD_TIMEOUT)) { + // resend request after timeout + if (last_cmd) { + DEBUG_ECHOLNPGM("MMU retry"); + cmd = last_cmd; + last_cmd = MMU_CMD_NONE; + } + state = 1; + } + TERN_(HAS_PRUSA_MMU2S, check_filament()); + break; + } +} + +/** + * Check if MMU was started + */ +bool MMU2::rx_start() { + // check for start message + return MMU2_RECV("start"); +} + +/** + * Check if the data received ends with the given string. + */ +bool MMU2::rx_str(FSTR_P fstr) { + PGM_P pstr = FTOP(fstr); + + uint8_t i = strlen(rx_buffer); + + while (MMU_SERIAL.available()) { + rx_buffer[i++] = MMU_SERIAL.read(); + + if (i == sizeof(rx_buffer) - 1) { + DEBUG_ECHOLNPGM("rx buffer overrun"); + break; + } + } + rx_buffer[i] = '\0'; + + uint8_t len = strlen_P(pstr); + + if (i < len) return false; + + pstr += len; + + while (len--) { + char c0 = pgm_read_byte(pstr--), c1 = rx_buffer[i--]; + if (c0 == c1) continue; + if (c0 == '\r' && c1 == '\n') continue; // match cr as lf + if (c0 == '\n' && c1 == '\r') continue; // match lf as cr + return false; + } + return true; +} + +/** + * Transfer data to MMU, no argument + */ +void MMU2::tx_str(FSTR_P fstr) { + clear_rx_buffer(); + PGM_P pstr = FTOP(fstr); + while (const char c = pgm_read_byte(pstr)) { MMU_SERIAL.write(c); pstr++; } + prev_request = millis(); +} + +/** + * Transfer data to MMU, single argument + */ +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) MMU_SERIAL.write(tx_buffer[i]); + prev_request = millis(); +} + +/** + * Transfer data to MMU, two arguments + */ +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) MMU_SERIAL.write(tx_buffer[i]); + prev_request = millis(); +} + +/** + * Empty the rx buffer + */ +void MMU2::clear_rx_buffer() { + while (MMU_SERIAL.available()) MMU_SERIAL.read(); + rx_buffer[0] = '\0'; +} + +/** + * Check if we received 'ok' from MMU + */ +bool MMU2::rx_ok() { + if (MMU2_RECV("ok")) { + prev_P0_request = millis(); + return true; + } + return false; +} + +/** + * Check if MMU has compatible firmware + */ +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."); + marlin.kill(GET_TEXT_F(MSG_KILL_MMU2_FIRMWARE)); + } +} + +static void mmu2_not_responding() { + LCD_MESSAGE(MSG_MMU2_NOT_RESPONDING); + BUZZ(100, 659); + BUZZ(200, 698); + BUZZ(100, 659); + BUZZ(300, 440); + BUZZ(100, 659); +} + +inline void beep_bad_cmd() { BUZZ(400, 40); } + +#if HAS_PRUSA_MMU2S + + /** + * Load filament until the sensor at the gears is triggered + * and give up after a number of attempts set with MMU2_C0_RETRY. + * Each try has a timeout before returning a fail state. + */ + bool MMU2::load_to_gears() { + command(MMU_CMD_C0); + manage_response(true, true); + for (uint8_t i = 0; i < MMU2_C0_RETRY; ++i) { // Keep loading until filament reaches gears + if (mmu2s_triggered) break; + command(MMU_CMD_C0); + manage_response(true, true); + check_filament(); + } + const bool success = mmu2s_triggered && can_load(); + if (!success) mmu2_not_responding(); + return success; + } + + /** + * Handle tool change + */ + void MMU2::tool_change(const uint8_t index) { + + if (!_enabled) return; + + set_runout_valid(false); + + if (index != extruder) { + if (ENABLED(MMU_IR_UNLOAD_MOVE) && FILAMENT_PRESENT()) { + DEBUG_ECHOLNPGM("Unloading\n"); + while (FILAMENT_PRESENT()) // Filament present? Keep unloading. + unscaled_mmu2_e_move(-0.25, MMM_TO_MMS(120)); // 0.25mm is a guessed value. Adjust to preference. + } + + stepper.disable_extruder(); + ui.status_printf(0, GET_TEXT_F(MSG_MMU2_LOADING_FILAMENT), int(index + 1)); + + command(MMU_CMD_T0 + index); + manage_response(true, true); + + if (load_to_gears()) { + extruder = index; // filament change is finished + active_extruder = 0; + stepper.enable_extruder(); + SERIAL_ECHO_MSG(STR_ACTIVE_EXTRUDER, extruder); + } + ui.reset_status(); + } + + set_runout_valid(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. + */ + void MMU2::tool_change(const char *special) { + if (!_enabled) return; + + set_runout_valid(false); + + switch (*special) { + case '?': { + #if ENABLED(MMU_MENUS) + const uint8_t index = mmu2_choose_filament(); + while (!thermalManager.wait_for_hotend(active_extruder, false)) safe_delay(100); + load_to_nozzle(index); + #else + beep_bad_cmd(); + #endif + } break; + + case 'x': { + #if ENABLED(MMU_MENUS) + planner.synchronize(); + const uint8_t index = mmu2_choose_filament(); + stepper.disable_extruder(); + command(MMU_CMD_T0 + index); + manage_response(true, true); + + if (load_to_gears()) { + mmu_loop(); + stepper.enable_extruder(); + extruder = index; + active_extruder = 0; + } + #else + beep_bad_cmd(); + #endif + } break; + + case 'c': { + while (!thermalManager.wait_for_hotend(active_extruder, false)) safe_delay(100); + load_to_nozzle_sequence(); + } break; + } + + set_runout_valid(true); + } + +#elif ENABLED(MMU2_EXTRUDER_SENSOR) + + /** + * Handle tool change + */ + void MMU2::tool_change(const uint8_t index) { + if (!_enabled) return; + + set_runout_valid(false); + + if (index != extruder) { + stepper.disable_extruder(); + if (FILAMENT_PRESENT()) { + DEBUG_ECHOLNPGM("Unloading\n"); + mmu_loading_flag = false; + command(MMU_CMD_U0); + manage_response(true, true); + } + ui.status_printf(0, GET_TEXT_F(MSG_MMU2_LOADING_FILAMENT), int(index + 1)); + mmu_loading_flag = true; + command(MMU_CMD_T0 + index); + manage_response(true, true); + mmu_continue_loading(); + //command(MMU_CMD_C0); + extruder = index; + active_extruder = 0; + + stepper.enable_extruder(); + SERIAL_ECHO_MSG(STR_ACTIVE_EXTRUDER, extruder); + + ui.reset_status(); + } + + set_runout_valid(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. + */ + void MMU2::tool_change(const char *special) { + if (!_enabled) return; + + set_runout_valid(false); + + switch (*special) { + case '?': { + DEBUG_ECHOLNPGM("case ?\n"); + #if ENABLED(MMU_MENUS) + uint8_t index = mmu2_choose_filament(); + while (!thermalManager.wait_for_hotend(active_extruder, false)) safe_delay(100); + load_to_nozzle(index); + #else + beep_bad_cmd(); + #endif + } break; + + case 'x': { + DEBUG_ECHOLNPGM("case x\n"); + #if ENABLED(MMU_MENUS) + planner.synchronize(); + uint8_t index = mmu2_choose_filament(); + stepper.disable_extruder(); + command(MMU_CMD_T0 + index); + manage_response(true, true); + mmu_continue_loading(); + command(MMU_CMD_C0); + mmu_loop(); + + stepper.enable_extruder(); + extruder = index; + active_extruder = 0; + #else + beep_bad_cmd(); + #endif + } break; + + case 'c': { + DEBUG_ECHOLNPGM("case c\n"); + while (!thermalManager.wait_for_hotend(active_extruder, false)) safe_delay(100); + load_to_nozzle_sequence(); + } break; + } + + set_runout_valid(true); + } + + 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 < MMU2_LOADING_ATTEMPTS_NR; i++) { + DEBUG_ECHOLNPGM("Load attempt #", i + 1); + + // Done as soon as filament is present + fil_present = FILAMENT_PRESENT(); + if (fil_present) break; + + // Attempt to load the filament, 1mm at a time, for 3s + command(MMU_CMD_C0); + stepper.enable_extruder(); + const millis_t expire_ms = millis() + 3000; + do { + current_position.e += 1; + line_to_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. + // Send (C0) to load more and move E_AXIS a little to release pressure. + if ((fil_present = FILAMENT_PRESENT())) MMU2_SEND("A"); + } while (!fil_present && PENDING(millis(), expire_ms)); + stepper.disable_extruder(); + manage_response(true, true); + } + + // Was the filament still missing in the last check? + if (!fil_present) { + DEBUG_ECHOLNPGM("Filament never reached sensor, runout"); + filament_runout(); + } + mmu_idl_sens = 0; + } + +#else // !HAS_PRUSA_MMU2S && !MMU2_EXTRUDER_SENSOR + + /** + * Handle tool change + */ + void MMU2::tool_change(const uint8_t index) { + if (!_enabled) return; + + set_runout_valid(false); + + if (index != extruder) { + stepper.disable_extruder(); + ui.status_printf(0, GET_TEXT_F(MSG_MMU2_LOADING_FILAMENT), int(index + 1)); + command(MMU_CMD_T0 + index); + manage_response(true, true); + command(MMU_CMD_C0); + extruder = index; // Filament change is finished + active_extruder = 0; + stepper.enable_extruder(); + SERIAL_ECHO_MSG(STR_ACTIVE_EXTRUDER, extruder); + ui.reset_status(); + } + + set_runout_valid(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. + */ + void MMU2::tool_change(const char *special) { + if (!_enabled) return; + + set_runout_valid(false); + + switch (*special) { + case '?': { + DEBUG_ECHOLNPGM("case ?\n"); + #if ENABLED(MMU_MENUS) + uint8_t index = mmu2_choose_filament(); + while (!thermalManager.wait_for_hotend(active_extruder, false)) safe_delay(100); + load_to_nozzle(index); + #else + beep_bad_cmd(); + #endif + } break; + + case 'x': { + DEBUG_ECHOLNPGM("case x\n"); + #if ENABLED(MMU_MENUS) + planner.synchronize(); + uint8_t index = mmu2_choose_filament(); + stepper.disable_extruder(); + command(MMU_CMD_T0 + index); + manage_response(true, true); + command(MMU_CMD_C0); + mmu_loop(); + + stepper.enable_extruder(); + extruder = index; + active_extruder = 0; + #else + beep_bad_cmd(); + #endif + } break; + + case 'c': { + DEBUG_ECHOLNPGM("case c\n"); + while (!thermalManager.wait_for_hotend(active_extruder, false)) safe_delay(100); + load_to_nozzle_sequence(); + } break; + } + + set_runout_valid(true); + } + +#endif // HAS_PRUSA_MMU2S + +/** + * Set next command + */ +void MMU2::command(const uint8_t mmu_cmd) { + if (!_enabled) return; + cmd = mmu_cmd; + ready = false; +} + +/** + * Wait for response from MMU + */ +bool MMU2::get_response() { + while (cmd != MMU_CMD_NONE) marlin.idle(); + + while (!ready) { + marlin.idle(); + if (state != 3) break; + } + + const bool ret = ready; + ready = false; + + return ret; +} + +/** + * Wait for response and deal with timeout if necessary + */ +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); + + KEEPALIVE_STATE(PAUSED_FOR_USER); + + while (!response) { + + response = get_response(); // wait for "ok" from mmu + + if (!response) { // No "ok" was received in reserved time frame, user will fix the issue on mmu unit + if (!mmu_print_saved) { // First occurrence. Save current position, park print head, disable nozzle heater. + + planner.synchronize(); + + mmu_print_saved = true; + + SERIAL_ECHOLNPGM("MMU not responding"); + + resume_hotend_temp = thermalManager.degTargetHotend(active_extruder); + resume_position = current_position; + + if (move_axes && all_axes_homed()) nozzle.park(0, park_point); + + if (turn_off_nozzle) thermalManager.setTargetHotend(0, active_extruder); + + mmu2_not_responding(); + } + } + else if (mmu_print_saved) { + SERIAL_ECHOLNPGM("\nMMU starts responding"); + + if (turn_off_nozzle && resume_hotend_temp) { + thermalManager.setTargetHotend(resume_hotend_temp, active_extruder); + LCD_MESSAGE(MSG_HEATING); + ERR_BUZZ(); + while (!thermalManager.wait_for_hotend(active_extruder, false)) safe_delay(1000); + } + + LCD_MESSAGE(MSG_MMU2_RESUMING); + mmu2_attn_buzz(true); + + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" + + if (move_axes && all_axes_homed()) { + // Move XY to starting position, then Z + do_blocking_move_to_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)); + } + + #pragma GCC diagnostic pop + } + } +} + +void MMU2::set_filament_type(const uint8_t index, const uint8_t filamentType) { + if (!_enabled) return; + + cmd_arg = filamentType; + command(MMU_CMD_F0 + index); + + manage_response(true, true); +} + +void MMU2::filament_runout() { + queue.inject(F(MMU2_FILAMENT_RUNOUT_SCRIPT)); + planner.synchronize(); +} + +#if HAS_PRUSA_MMU2S + + void MMU2::check_filament() { + const bool present = FILAMENT_PRESENT(); + if (cmd == MMU_CMD_NONE && last_cmd == MMU_CMD_C0) { + if (present && !mmu2s_triggered) { + DEBUG_ECHOLNPGM("MMU <= 'A'"); + MMU2_SEND("A"); + } + // Slowly spin the extruder during C0 + else { + while (planner.movesplanned() < 3) + unscaled_mmu2_e_move(0.25, MMM_TO_MMS(120), false); + } + } + mmu2s_triggered = present; + } + + bool MMU2::can_load() { + 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)); + + int filament_detected_count = 0; + const int steps = (MMU2_CAN_LOAD_RETRACT) / (MMU2_CAN_LOAD_INCREMENT); + DEBUG_ECHOLNPGM("MMU can_load:"); + for (uint8_t i = 0; i < steps; ++i) { + execute_extruder_sequence(can_load_increment_sequence, COUNT(can_load_increment_sequence)); + check_filament(); // Don't trust the idle function + DEBUG_CHAR(mmu2s_triggered ? 'O' : 'o'); + if (mmu2s_triggered) ++filament_detected_count; + } + + if (filament_detected_count <= steps - (MMU2_CAN_LOAD_DEVIATION) / (MMU2_CAN_LOAD_INCREMENT)) { + DEBUG_ECHOLNPGM(" failed."); + return false; + } + + DEBUG_ECHOLNPGM(" succeeded."); + return true; + } + +#endif + +// Load filament into MMU2 +void MMU2::load_to_feeder(const uint8_t index) { + if (!_enabled) return; + + command(MMU_CMD_L0 + index); + manage_response(false, false); + mmu2_attn_buzz(); +} + +/** + * Switch material and load to nozzle + */ +bool MMU2::load_to_nozzle(const uint8_t index) { + if (!_enabled) return false; + + if (thermalManager.tooColdToExtrude(active_extruder)) { + mmu2_attn_buzz(); + LCD_ALERTMESSAGE(MSG_HOTEND_TOO_COLD); + return false; + } + + if (TERN0(MMU_IR_UNLOAD_MOVE, index != extruder) && FILAMENT_PRESENT()) { + DEBUG_ECHOLNPGM("Unloading\n"); + ramming_sequence(); // Unloading instructions from printer side when operating LCD + while (FILAMENT_PRESENT()) // Filament present? Keep unloading. + unscaled_mmu2_e_move(-0.25, MMM_TO_MMS(120)); // 0.25mm is a guessed value. Adjust to preference. + } + + stepper.disable_extruder(); + command(MMU_CMD_T0 + index); + manage_response(true, true); + + const bool success = load_to_gears(); + if (success) { + mmu_loop(); + extruder = index; + active_extruder = 0; + load_to_nozzle_sequence(); + mmu2_attn_buzz(); + } + return success; +} + +bool MMU2::eject_filament(const uint8_t index, const bool recover) { + + if (!_enabled) return false; + + if (thermalManager.tooColdToExtrude(active_extruder)) { + mmu2_attn_buzz(); + LCD_ALERTMESSAGE(MSG_HOTEND_TOO_COLD); + return false; + } + + LCD_MESSAGE(MSG_MMU2_EJECTING_FILAMENT); + + unscaled_mmu2_e_move(-(MMU2_FILAMENTCHANGE_EJECT_FEED), MMM_TO_MMS(2500)); + command(MMU_CMD_E0 + index); + manage_response(false, false); + + if (recover) { + LCD_MESSAGE(MSG_MMU2_REMOVE_AND_CLICK); + 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, marlin.wait_for_user_response()); + mmu2_attn_buzz(); + + command(MMU_CMD_R0); + manage_response(false, false); + } + + ui.reset_status(); + + // no active tool + extruder = MMU2_NO_TOOL; + + set_runout_valid(false); + + mmu2_attn_buzz(); + + stepper.disable_extruder(); + + return true; +} + +/** + * Unload from hotend and retract to MMU + */ +bool MMU2::unload() { + + if (!_enabled) return false; + + if (thermalManager.tooColdToExtrude(active_extruder)) { + mmu2_attn_buzz(); + LCD_ALERTMESSAGE(MSG_HOTEND_TOO_COLD); + return false; + } + + // Unload sequence to optimize shape of the tip of the unloaded filament + ramming_sequence(); + + command(MMU_CMD_U0); + manage_response(false, true); + + mmu2_attn_buzz(); + + // no active tool + extruder = MMU2_NO_TOOL; + + set_runout_valid(false); + + return true; +} + +void MMU2::ramming_sequence() { + static const E_Step sequence[] PROGMEM = { MMU2_RAMMING_SEQUENCE }; + execute_extruder_sequence(sequence, COUNT(sequence)); +} + +void MMU2::load_to_nozzle_sequence() { + static const E_Step sequence[] PROGMEM = { MMU2_LOAD_TO_NOZZLE_SEQUENCE }; + execute_extruder_sequence(sequence, COUNT(sequence)); +} + +void MMU2::execute_extruder_sequence(const E_Step * const sequence, const uint8_t steps) { + + planner.synchronize(); + + const E_Step *step = sequence; + + for (uint8_t i = 0; i < steps; ++i) { + const float es = pgm_read_float(&(step->extrude)); + const feedRate_t fr_mm_m = pgm_read_float(&(step->feedRate)); + DEBUG_ECHO_MSG("E step ", es, "/", fr_mm_m); + unscaled_mmu2_e_move(es, MMM_TO_MMS(fr_mm_m)); + step++; + } + + stepper.disable_extruder(); +} + +#endif // HAS_PRUSA_MMU2 diff --git a/Marlin/src/feature/mmu2/mmu2.h b/Marlin/src/feature/mmu/mmu2.h similarity index 68% rename from Marlin/src/feature/mmu2/mmu2.h rename to Marlin/src/feature/mmu/mmu2.h index 678f65d072..117b3a6046 100644 --- a/Marlin/src/feature/mmu2/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 @@ -43,44 +47,40 @@ public: static void init(); static void reset(); + static bool enabled() { return _enabled; } static void mmu_loop(); static void tool_change(const uint8_t index); - static void tool_change(const char* special); - static uint8_t get_current_tool(); + static void tool_change(const char *special); + static int8_t get_current_tool(); static void set_filament_type(const uint8_t index, const uint8_t type); - #if BOTH(HAS_LCD_MENU, MMU2_MENUS) - static bool unload(); - static void load_filament(uint8_t); - static void load_all(); - static bool load_filament_to_nozzle(const uint8_t index); - static bool eject_filament(const uint8_t index, const bool recover); - #endif + static bool unload(); + static void load_to_feeder(const uint8_t index); + static bool load_to_nozzle(const uint8_t index); + static bool eject_filament(const uint8_t index, const bool recover); private: - static bool rx_str_P(const char* str); - static void tx_str_P(const char* str); - static void tx_printf_P(const char* format, const int argument); - static void tx_printf_P(const char* format, const int argument1, const int argument2); + static bool rx_str(FSTR_P fstr); + static void tx_str(FSTR_P fstr); + static void tx_printf(FSTR_P ffmt, const int argument); + static void tx_printf(FSTR_P ffmt, const int argument1, const int argument2); static void clear_rx_buffer(); static bool rx_ok(); static bool rx_start(); - static void check_version(); + static void check_version(const uint16_t buildnr); static void command(const uint8_t cmd); static bool get_response(); static void manage_response(const bool move_axes, const bool turn_off_nozzle); - #if BOTH(HAS_LCD_MENU, MMU2_MENUS) - static void load_to_nozzle(); - static void filament_ramming(); - static void execute_extruder_sequence(const E_Step * sequence, int steps); - #endif + static void execute_extruder_sequence(const E_Step * const sequence, const uint8_t steps); + static void ramming_sequence(); + static void load_to_nozzle_sequence(); static void filament_runout(); - #if ENABLED(PRUSA_MMU2_S_MODE) + #if HAS_PRUSA_MMU2S static bool mmu2s_triggered; static void check_filament(); static bool can_load(); @@ -89,21 +89,21 @@ private: 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 - static bool enabled, ready, mmu_print_saved; + static bool _enabled, ready; static uint8_t cmd, cmd_arg, last_cmd, extruder; static int8_t state; static volatile int8_t finda; static volatile bool finda_runout_valid; - static int16_t version, buildnr; static millis_t prev_request, prev_P0_request; static char rx_buffer[MMU_RX_SIZE], tx_buffer[MMU_TX_SIZE]; - static inline void set_runout_valid(const bool valid) { + static void set_runout_valid(const bool valid) { finda_runout_valid = valid; #if HAS_FILAMENT_SENSOR if (valid) runout.reset(); diff --git a/Marlin/src/feature/mmu2/mmu2.cpp b/Marlin/src/feature/mmu2/mmu2.cpp deleted file mode 100644 index 3d635369e4..0000000000 --- a/Marlin/src/feature/mmu2/mmu2.cpp +++ /dev/null @@ -1,1068 +0,0 @@ -/** - * Marlin 3D Printer Firmware - * Copyright (c) 2020 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(PRUSA_MMU2) - -#include "mmu2.h" -#include "../../lcd/menu/menu_mmu2.h" - -MMU2 mmu2; - -#include "../../gcode/gcode.h" -#include "../../lcd/ultralcd.h" -#include "../../libs/buzzer.h" -#include "../../libs/nozzle.h" -#include "../../module/temperature.h" -#include "../../module/planner.h" -#include "../../module/stepper/indirection.h" -#include "../../MarlinCore.h" - -#if ENABLED(HOST_PROMPT_SUPPORT) - #include "../../feature/host_actions.h" -#endif - -#if ENABLED(EXTENSIBLE_UI) - #include "../../lcd/extui/ui_api.h" -#endif - -#define DEBUG_OUT ENABLED(MMU2_DEBUG) -#include "../../core/debug_out.h" - -#define MMU_TODELAY 100 -#define MMU_TIMEOUT 10 -#define MMU_CMD_TIMEOUT 45000UL // 45s timeout for mmu commands (except P0) -#define MMU_P0_TIMEOUT 3000UL // Timeout for P0 command: 3seconds - -#define MMU2_COMMAND(S) tx_str_P(PSTR(S "\n")) - -#if ENABLED(MMU_EXTRUDER_SENSOR) - uint8_t mmu_idl_sens = 0; - static bool mmu_loading_flag = false; -#endif - -#define MMU_CMD_NONE 0 -#define MMU_CMD_T0 0x10 -#define MMU_CMD_T1 0x11 -#define MMU_CMD_T2 0x12 -#define MMU_CMD_T3 0x13 -#define MMU_CMD_T4 0x14 -#define MMU_CMD_L0 0x20 -#define MMU_CMD_L1 0x21 -#define MMU_CMD_L2 0x22 -#define MMU_CMD_L3 0x23 -#define MMU_CMD_L4 0x24 -#define MMU_CMD_C0 0x30 -#define MMU_CMD_U0 0x40 -#define MMU_CMD_E0 0x50 -#define MMU_CMD_E1 0x51 -#define MMU_CMD_E2 0x52 -#define MMU_CMD_E3 0x53 -#define MMU_CMD_E4 0x54 -#define MMU_CMD_R0 0x60 -#define MMU_CMD_F0 0x70 -#define MMU_CMD_F1 0x71 -#define MMU_CMD_F2 0x72 -#define MMU_CMD_F3 0x73 -#define MMU_CMD_F4 0x74 - -#define MMU_REQUIRED_FW_BUILDNR TERN(MMU2_MODE_12V, 132, 126) - -#define MMU2_NO_TOOL 99 -#define MMU_BAUD 115200 - -#define mmuSerial MMU2_SERIAL - -bool MMU2::enabled, MMU2::ready, MMU2::mmu_print_saved; -#if ENABLED(PRUSA_MMU2_S_MODE) - bool MMU2::mmu2s_triggered; -#endif -uint8_t MMU2::cmd, MMU2::cmd_arg, MMU2::last_cmd, MMU2::extruder; -int8_t MMU2::state = 0; -volatile int8_t MMU2::finda = 1; -volatile bool MMU2::finda_runout_valid; -int16_t MMU2::version = -1, MMU2::buildnr = -1; -millis_t MMU2::prev_request, MMU2::prev_P0_request; -char MMU2::rx_buffer[MMU_RX_SIZE], MMU2::tx_buffer[MMU_TX_SIZE]; - -#if BOTH(HAS_LCD_MENU, MMU2_MENUS) - - struct E_Step { - float extrude; //!< extrude distance in mm - feedRate_t feedRate; //!< feed rate in mm/s - }; - - static constexpr E_Step - ramming_sequence[] PROGMEM = { MMU2_RAMMING_SEQUENCE } - , load_to_nozzle_sequence[] PROGMEM = { MMU2_LOAD_TO_NOZZLE_SEQUENCE } - #if ENABLED(PRUSA_MMU2_S_MODE) - , can_load_sequence[] PROGMEM = { MMU2_CAN_LOAD_SEQUENCE } - , can_load_increment_sequence[] PROGMEM = { MMU2_CAN_LOAD_INCREMENT_SEQUENCE } - #endif - ; - -#endif // MMU2_MENUS - -MMU2::MMU2() { - rx_buffer[0] = '\0'; -} - -void MMU2::init() { - - set_runout_valid(false); - - #if PIN_EXISTS(MMU2_RST) - // TODO use macros for this - WRITE(MMU2_RST_PIN, HIGH); - SET_OUTPUT(MMU2_RST_PIN); - #endif - - mmuSerial.begin(MMU_BAUD); - extruder = MMU2_NO_TOOL; - - safe_delay(10); - reset(); - rx_buffer[0] = '\0'; - state = -1; -} - -void MMU2::reset() { - DEBUG_ECHOLNPGM("MMU <= reset"); - - #if PIN_EXISTS(MMU2_RST) - WRITE(MMU2_RST_PIN, LOW); - safe_delay(20); - WRITE(MMU2_RST_PIN, HIGH); - #else - MMU2_COMMAND("X0"); // Send soft reset - #endif -} - -uint8_t MMU2::get_current_tool() { - return extruder == MMU2_NO_TOOL ? -1 : extruder; -} - -#if EITHER(PRUSA_MMU2_S_MODE, MMU_EXTRUDER_SENSOR) - #define FILAMENT_PRESENT() (READ(FIL_RUNOUT_PIN) != FIL_RUNOUT_STATE) -#endif - -void MMU2::mmu_loop() { - - switch (state) { - - case 0: break; - - case -1: - if (rx_start()) { - DEBUG_ECHOLNPGM("MMU => 'start'"); - DEBUG_ECHOLNPGM("MMU <= 'S1'"); - - MMU2_COMMAND("S1"); // Read Version - state = -2; - } - else if (millis() > 3000000) { - SERIAL_ECHOLNPGM("MMU not responding - DISABLED"); - state = 0; - } - break; - - case -2: - if (rx_ok()) { - sscanf(rx_buffer, "%uok\n", &version); - - DEBUG_ECHOLNPAIR("MMU => ", version, "\nMMU <= 'S2'"); - - MMU2_COMMAND("S2"); // Read Build Number - state = -3; - } - break; - - case -3: - if (rx_ok()) { - sscanf(rx_buffer, "%uok\n", &buildnr); - - DEBUG_ECHOLNPAIR("MMU => ", buildnr); - - check_version(); - - #if ENABLED(MMU2_MODE_12V) - DEBUG_ECHOLNPGM("MMU <= 'M1'"); - - MMU2_COMMAND("M1"); // Stealth Mode - state = -5; - - #else - DEBUG_ECHOLNPGM("MMU <= 'P0'"); - - MMU2_COMMAND("P0"); // Read FINDA - state = -4; - #endif - } - break; - - #if ENABLED(MMU2_MODE_12V) - case -5: - // response to M1 - if (rx_ok()) { - DEBUG_ECHOLNPGM("MMU => ok"); - - DEBUG_ECHOLNPGM("MMU <= 'P0'"); - - MMU2_COMMAND("P0"); // Read FINDA - state = -4; - } - break; - #endif - - case -4: - if (rx_ok()) { - sscanf(rx_buffer, "%hhuok\n", &finda); - - DEBUG_ECHOLNPAIR("MMU => ", finda, "\nMMU - ENABLED"); - - enabled = true; - state = 1; - TERN_(PRUSA_MMU2_S_MODE, mmu2s_triggered = false); - } - break; - - case 1: - if (cmd) { - if (WITHIN(cmd, MMU_CMD_T0, MMU_CMD_T4)) { - // tool change - int filament = cmd - MMU_CMD_T0; - DEBUG_ECHOLNPAIR("MMU <= T", filament); - tx_printf_P(PSTR("T%d\n"), filament); - TERN_(MMU_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_L4)) { - // load - int filament = cmd - MMU_CMD_L0; - DEBUG_ECHOLNPAIR("MMU <= L", filament); - tx_printf_P(PSTR("L%d\n"), filament); - state = 3; // wait for response - } - else if (cmd == MMU_CMD_C0) { - // continue loading - DEBUG_ECHOLNPGM("MMU <= 'C0'"); - MMU2_COMMAND("C0"); - state = 3; // wait for response - } - else if (cmd == MMU_CMD_U0) { - // unload current - DEBUG_ECHOLNPGM("MMU <= 'U0'"); - - MMU2_COMMAND("U0"); - state = 3; // wait for response - } - else if (WITHIN(cmd, MMU_CMD_E0, MMU_CMD_E4)) { - // eject filament - int filament = cmd - MMU_CMD_E0; - DEBUG_ECHOLNPAIR("MMU <= E", filament); - tx_printf_P(PSTR("E%d\n"), filament); - state = 3; // wait for response - } - else if (cmd == MMU_CMD_R0) { - // recover after eject - DEBUG_ECHOLNPGM("MMU <= 'R0'"); - MMU2_COMMAND("R0"); - state = 3; // wait for response - } - else if (WITHIN(cmd, MMU_CMD_F0, MMU_CMD_F4)) { - // filament type - int filament = cmd - MMU_CMD_F0; - DEBUG_ECHOPAIR("MMU <= F", filament, " "); - DEBUG_ECHO_F(cmd_arg, DEC); - DEBUG_EOL(); - tx_printf_P(PSTR("F%d %d\n"), filament, cmd_arg); - state = 3; // wait for response - } - - last_cmd = cmd; - cmd = MMU_CMD_NONE; - } - else if (ELAPSED(millis(), prev_P0_request + 300)) { - MMU2_COMMAND("P0"); // Read FINDA - state = 2; // wait for response - } - - TERN_(PRUSA_MMU2_S_MODE, check_filament()); - break; - - case 2: // response to command P0 - if (rx_ok()) { - sscanf(rx_buffer, "%hhuok\n", &finda); - - // This is super annoying. Only activate if necessary - // if (finda_runout_valid) DEBUG_ECHOLNPAIR_F("MMU <= 'P0'\nMMU => ", finda, 6); - - if (!finda && finda_runout_valid) filament_runout(); - if (cmd == 0) ready = true; - state = 1; - } - else if (ELAPSED(millis(), prev_request + MMU_P0_TIMEOUT)) // Resend request after timeout (3s) - state = 1; - - TERN_(PRUSA_MMU2_S_MODE, check_filament()); - break; - - case 3: // response to mmu commands - #if ENABLED(MMU_EXTRUDER_SENSOR) - if (mmu_idl_sens) { - if (FILAMENT_PRESENT() && mmu_loading_flag) { - DEBUG_ECHOLNPGM("MMU <= 'A'"); - MMU2_COMMAND("A"); // send 'abort' request - mmu_idl_sens = 0; - DEBUG_ECHOLNPGM("MMU IDLER_SENSOR = 0 - ABORT"); - } - } - #endif - - if (rx_ok()) { - // Response to C0 mmu command in PRUSA_MMU2_S_MODE - bool can_reset = true; - #if ENABLED(PRUSA_MMU2_S_MODE) - if (!mmu2s_triggered && last_cmd == MMU_CMD_C0) { - can_reset = false; - // MMU ok received but filament sensor not triggered, retrying... - DEBUG_ECHOLNPGM("MMU => 'ok' (filament not present in gears)"); - DEBUG_ECHOLNPGM("MMU <= 'C0' (keep trying)"); - MMU2_COMMAND("C0"); - } - #endif - if (can_reset) { - DEBUG_ECHOLNPGM("MMU => 'ok'"); - ready = true; - state = 1; - last_cmd = MMU_CMD_NONE; - } - } - else if (ELAPSED(millis(), prev_request + MMU_CMD_TIMEOUT)) { - // resend request after timeout - if (last_cmd) { - DEBUG_ECHOLNPGM("MMU retry"); - cmd = last_cmd; - last_cmd = MMU_CMD_NONE; - } - state = 1; - } - TERN_(PRUSA_MMU2_S_MODE, check_filament()); - break; - } -} - -/** - * Check if MMU was started - */ -bool MMU2::rx_start() { - // check for start message - if (rx_str_P(PSTR("start\n"))) { - prev_P0_request = millis(); - return true; - } - return false; -} - -/** - * Check if the data received ends with the given string. - */ -bool MMU2::rx_str_P(const char* str) { - uint8_t i = strlen(rx_buffer); - - while (mmuSerial.available()) { - rx_buffer[i++] = mmuSerial.read(); - rx_buffer[i] = '\0'; - - if (i == sizeof(rx_buffer) - 1) { - DEBUG_ECHOLNPGM("rx buffer overrun"); - break; - } - } - - uint8_t len = strlen_P(str); - - if (i < len) return false; - - str += len; - - while (len--) { - char c0 = pgm_read_byte(str--), c1 = rx_buffer[i--]; - if (c0 == c1) continue; - if (c0 == '\r' && c1 == '\n') continue; // match cr as lf - if (c0 == '\n' && c1 == '\r') continue; // match lf as cr - return false; - } - return true; -} - -/** - * Transfer data to MMU, no argument - */ -void MMU2::tx_str_P(const char* str) { - clear_rx_buffer(); - uint8_t len = strlen_P(str); - LOOP_L_N(i, len) mmuSerial.write(pgm_read_byte(str++)); - rx_buffer[0] = '\0'; - prev_request = millis(); -} - -/** - * Transfer data to MMU, single argument - */ -void MMU2::tx_printf_P(const char* format, int argument = -1) { - clear_rx_buffer(); - uint8_t len = sprintf_P(tx_buffer, format, argument); - LOOP_L_N(i, len) mmuSerial.write(tx_buffer[i]); - rx_buffer[0] = '\0'; - prev_request = millis(); -} - -/** - * Transfer data to MMU, two arguments - */ -void MMU2::tx_printf_P(const char* format, int argument1, int argument2) { - clear_rx_buffer(); - uint8_t len = sprintf_P(tx_buffer, format, argument1, argument2); - LOOP_L_N(i, len) mmuSerial.write(tx_buffer[i]); - rx_buffer[0] = '\0'; - prev_request = millis(); -} - -/** - * Empty the rx buffer - */ -void MMU2::clear_rx_buffer() { - while (mmuSerial.available()) mmuSerial.read(); - rx_buffer[0] = '\0'; -} - -/** - * Check if we received 'ok' from MMU - */ -bool MMU2::rx_ok() { - if (rx_str_P(PSTR("ok\n"))) { - prev_P0_request = millis(); - return true; - } - return false; -} - -/** - * Check if MMU has compatible firmware - */ -void MMU2::check_version() { - if (buildnr < MMU_REQUIRED_FW_BUILDNR) { - SERIAL_ERROR_MSG("Invalid MMU2 firmware. Version >= " STRINGIFY(MMU_REQUIRED_FW_BUILDNR) " required."); - kill(GET_TEXT(MSG_KILL_MMU2_FIRMWARE)); - } -} - -static void mmu2_not_responding() { - LCD_MESSAGEPGM(MSG_MMU2_NOT_RESPONDING); - BUZZ(100, 659); - BUZZ(200, 698); - BUZZ(100, 659); - BUZZ(300, 440); - BUZZ(100, 659); -} - -#if ENABLED(PRUSA_MMU2_S_MODE) - - bool MMU2::load_to_gears() { - command(MMU_CMD_C0); - manage_response(true, true); - LOOP_L_N(i, MMU2_C0_RETRY) { // Keep loading until filament reaches gears - if (mmu2s_triggered) break; - command(MMU_CMD_C0); - manage_response(true, true); - check_filament(); - } - const bool success = mmu2s_triggered && can_load(); - if (!success) mmu2_not_responding(); - return success; - } - - /** - * Handle tool change - */ - void MMU2::tool_change(const uint8_t index) { - - if (!enabled) return; - - set_runout_valid(false); - - if (index != extruder) { - - DISABLE_AXIS_E0(); - ui.status_printf_P(0, GET_TEXT(MSG_MMU2_LOADING_FILAMENT), int(index + 1)); - - command(MMU_CMD_T0 + index); - manage_response(true, true); - - if (load_to_gears()) { - extruder = index; // filament change is finished - active_extruder = 0; - ENABLE_AXIS_E0(); - SERIAL_ECHO_START(); - SERIAL_ECHOLNPAIR(STR_ACTIVE_EXTRUDER, int(extruder)); - } - ui.reset_status(); - } - - set_runout_valid(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. - */ - void MMU2::tool_change(const char* special) { - - if (!enabled) return; - - #if ENABLED(MMU2_MENUS) - - set_runout_valid(false); - - switch (*special) { - case '?': { - uint8_t index = mmu2_choose_filament(); - while (!thermalManager.wait_for_hotend(active_extruder, false)) safe_delay(100); - load_filament_to_nozzle(index); - } break; - - case 'x': { - planner.synchronize(); - uint8_t index = mmu2_choose_filament(); - DISABLE_AXIS_E0(); - command(MMU_CMD_T0 + index); - manage_response(true, true); - - if (load_to_gears()) { - mmu_loop(); - ENABLE_AXIS_E0(); - extruder = index; - active_extruder = 0; - } - } break; - - case 'c': { - while (!thermalManager.wait_for_hotend(active_extruder, false)) safe_delay(100); - execute_extruder_sequence((const E_Step *)load_to_nozzle_sequence, COUNT(load_to_nozzle_sequence)); - } break; - } - - set_runout_valid(true); - - #endif // MMU2_MENUS - } - -#elif ENABLED(MMU_EXTRUDER_SENSOR) - - /** - * Handle tool change - */ - void MMU2::tool_change(const uint8_t index) { - if (!enabled) return; - - set_runout_valid(false); - - if (index != extruder) { - DISABLE_AXIS_E0(); - if (FILAMENT_PRESENT()) { - DEBUG_ECHOLNPGM("Unloading\n"); - mmu_loading_flag = false; - command(MMU_CMD_U0); - manage_response(true, true); - } - ui.status_printf_P(0, GET_TEXT(MSG_MMU2_LOADING_FILAMENT), int(index + 1)); - mmu_loading_flag = true; - command(MMU_CMD_T0 + index); - manage_response(true, true); - mmu_continue_loading(); - command(MMU_CMD_C0); - extruder = index; - active_extruder = 0; - - ENABLE_AXIS_E0(); - SERIAL_ECHO_START(); - SERIAL_ECHOLNPAIR(STR_ACTIVE_EXTRUDER, int(extruder)); - - ui.reset_status(); - } - - set_runout_valid(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. - */ - void MMU2::tool_change(const char* special) { - if (!enabled) return; - - #if ENABLED(MMU2_MENUS) - - set_runout_valid(false); - - switch (*special) { - case '?': { - DEBUG_ECHOLNPGM("case ?\n"); - uint8_t index = mmu2_choose_filament(); - while (!thermalManager.wait_for_hotend(active_extruder, false)) safe_delay(100); - load_filament_to_nozzle(index); - } break; - - case 'x': { - DEBUG_ECHOLNPGM("case x\n"); - planner.synchronize(); - uint8_t index = mmu2_choose_filament(); - DISABLE_AXIS_E0(); - command(MMU_CMD_T0 + index); - manage_response(true, true); - mmu_continue_loading(); - command(MMU_CMD_C0); - mmu_loop(); - - ENABLE_AXIS_E0(); - extruder = index; - active_extruder = 0; - } break; - - case 'c': { - DEBUG_ECHOLNPGM("case c\n"); - while (!thermalManager.wait_for_hotend(active_extruder, false)) safe_delay(100); - execute_extruder_sequence((const E_Step *)load_to_nozzle_sequence, COUNT(load_to_nozzle_sequence)); - } break; - } - - set_runout_valid(true); - - #endif // MMU2_MENUS - } - - void MMU2::mmu_continue_loading() { - for (uint8_t i = 0; i < MMU_LOADING_ATTEMPTS_NR; i++) { - DEBUG_ECHOLNPAIR("Additional load attempt #", i); - if (FILAMENT_PRESENT()) break; - command(MMU_CMD_C0); - manage_response(true, true); - } - if (!FILAMENT_PRESENT()) { - DEBUG_ECHOLNPGM("Filament never reached sensor, runout"); - filament_runout(); - } - mmu_idl_sens = 0; - } - -#elif DISABLED(MMU_EXTRUDER_SENSOR) && DISABLED(PRUSA_MMU2_S_MODE) - -/** - * Handle tool change - */ -void MMU2::tool_change(const uint8_t index) { - if (!enabled) return; - - set_runout_valid(false); - - if (index != extruder) { - DISABLE_AXIS_E0(); - ui.status_printf_P(0, GET_TEXT(MSG_MMU2_LOADING_FILAMENT), int(index + 1)); - command(MMU_CMD_T0 + index); - manage_response(true, true); - command(MMU_CMD_C0); - extruder = index; //filament change is finished - active_extruder = 0; - ENABLE_AXIS_E0(); - SERIAL_ECHO_START(); - SERIAL_ECHOLNPAIR(STR_ACTIVE_EXTRUDER, int(extruder)); - ui.reset_status(); - } - - set_runout_valid(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. - */ -void MMU2::tool_change(const char* special) { - if (!enabled) return; - - #if ENABLED(MMU2_MENUS) - - set_runout_valid(false); - - switch (*special) { - case '?': { - DEBUG_ECHOLNPGM("case ?\n"); - uint8_t index = mmu2_choose_filament(); - while (!thermalManager.wait_for_hotend(active_extruder, false)) safe_delay(100); - load_filament_to_nozzle(index); - } break; - - case 'x': { - DEBUG_ECHOLNPGM("case x\n"); - planner.synchronize(); - uint8_t index = mmu2_choose_filament(); - DISABLE_AXIS_E0(); - command(MMU_CMD_T0 + index); - manage_response(true, true); - command(MMU_CMD_C0); - mmu_loop(); - - ENABLE_AXIS_E0(); - extruder = index; - active_extruder = 0; - } break; - - case 'c': { - DEBUG_ECHOLNPGM("case c\n"); - while (!thermalManager.wait_for_hotend(active_extruder, false)) safe_delay(100); - execute_extruder_sequence((const E_Step *)load_to_nozzle_sequence, COUNT(load_to_nozzle_sequence)); - } break; - } - - set_runout_valid(true); - - #endif - } - -#endif // MMU_EXTRUDER_SENSOR - -/** - * Set next command - */ -void MMU2::command(const uint8_t mmu_cmd) { - if (!enabled) return; - cmd = mmu_cmd; - ready = false; -} - -/** - * Wait for response from MMU - */ -bool MMU2::get_response() { - while (cmd != MMU_CMD_NONE) idle(); - - while (!ready) { - idle(); - if (state != 3) break; - } - - const bool ret = ready; - ready = false; - - return ret; -} - -/** - * Wait for response and deal with timeout if nexcessary - */ -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; - int16_t resume_hotend_temp = thermalManager.degTargetHotend(active_extruder); - - KEEPALIVE_STATE(PAUSED_FOR_USER); - - while (!response) { - - response = get_response(); // wait for "ok" from mmu - - if (!response) { // No "ok" was received in reserved time frame, user will fix the issue on mmu unit - if (!mmu_print_saved) { // First occurrence. Save current position, park print head, disable nozzle heater. - - planner.synchronize(); - - mmu_print_saved = true; - - SERIAL_ECHOLNPGM("MMU not responding"); - - resume_hotend_temp = thermalManager.degTargetHotend(active_extruder); - resume_position = current_position; - - if (move_axes && all_axes_homed()) - nozzle.park(0, park_point /*= NOZZLE_PARK_POINT*/); - - if (turn_off_nozzle) thermalManager.setTargetHotend(0, active_extruder); - - mmu2_not_responding(); - } - } - else if (mmu_print_saved) { - SERIAL_ECHOLNPGM("MMU starts responding\n"); - - if (turn_off_nozzle && resume_hotend_temp) { - thermalManager.setTargetHotend(resume_hotend_temp, active_extruder); - LCD_MESSAGEPGM(MSG_HEATING); - BUZZ(200, 40); - - while (!thermalManager.wait_for_hotend(active_extruder, false)) safe_delay(1000); - } - - if (move_axes && all_axes_homed()) { - LCD_MESSAGEPGM(MSG_MMU2_RESUMING); - BUZZ(198, 404); BUZZ(4, 0); BUZZ(198, 404); - - // Move XY to starting position, then Z - do_blocking_move_to_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)); - } - else { - BUZZ(198, 404); BUZZ(4, 0); BUZZ(198, 404); - LCD_MESSAGEPGM(MSG_MMU2_RESUMING); - } - } - } -} - -void MMU2::set_filament_type(const uint8_t index, const uint8_t filamentType) { - if (!enabled) return; - - cmd_arg = filamentType; - command(MMU_CMD_F0 + index); - - manage_response(true, true); -} - -void MMU2::filament_runout() { - queue.inject_P(PSTR(MMU2_FILAMENT_RUNOUT_SCRIPT)); - planner.synchronize(); -} - -#if ENABLED(PRUSA_MMU2_S_MODE) - - void MMU2::check_filament() { - const bool present = FILAMENT_PRESENT(); - if (cmd == MMU_CMD_NONE && last_cmd == MMU_CMD_C0) { - if (present && !mmu2s_triggered) { - DEBUG_ECHOLNPGM("MMU <= 'A'"); - tx_str_P(PSTR("A\n")); - } - // Slowly spin the extruder during C0 - else { - while (planner.movesplanned() < 3) { - current_position.e += 0.25; - line_to_current_position(MMM_TO_MMS(120)); - } - } - } - mmu2s_triggered = present; - } - - bool MMU2::can_load() { - execute_extruder_sequence((const E_Step *)can_load_sequence, COUNT(can_load_sequence)); - - int filament_detected_count = 0; - const int steps = (MMU2_CAN_LOAD_RETRACT) / (MMU2_CAN_LOAD_INCREMENT); - DEBUG_ECHOLNPGM("MMU can_load:"); - LOOP_L_N(i, steps) { - execute_extruder_sequence((const E_Step *)can_load_increment_sequence, COUNT(can_load_increment_sequence)); - check_filament(); // Don't trust the idle function - DEBUG_CHAR(mmu2s_triggered ? 'O' : 'o'); - if (mmu2s_triggered) ++filament_detected_count; - } - - if (filament_detected_count <= steps - (MMU2_CAN_LOAD_DEVIATION) / (MMU2_CAN_LOAD_INCREMENT)) { - DEBUG_ECHOLNPGM(" failed."); - return false; - } - - DEBUG_ECHOLNPGM(" succeeded."); - return true; - } -#endif - -#if BOTH(HAS_LCD_MENU, MMU2_MENUS) - - // Load filament into MMU2 - void MMU2::load_filament(const uint8_t index) { - if (!enabled) return; - command(MMU_CMD_L0 + index); - manage_response(false, false); - BUZZ(200, 404); - } - - /** - * Switch material and load to nozzle - */ - bool MMU2::load_filament_to_nozzle(const uint8_t index) { - - if (!enabled) return false; - - if (thermalManager.tooColdToExtrude(active_extruder)) { - BUZZ(200, 404); - LCD_ALERTMESSAGEPGM(MSG_HOTEND_TOO_COLD); - return false; - } - - command(MMU_CMD_T0 + index); - manage_response(true, true); - - const bool success = load_to_gears(); - if (success) { - mmu_loop(); - extruder = index; - active_extruder = 0; - load_to_nozzle(); - BUZZ(200, 404); - } - return success; - } - - /** - * Load filament to nozzle of multimaterial printer - * - * This function is used only only after T? (user select filament) and M600 (change filament). - * It is not used after T0 .. T4 command (select filament), in such case, gcode is responsible for loading - * filament to nozzle. - */ - void MMU2::load_to_nozzle() { - if (!enabled) return; - execute_extruder_sequence((const E_Step *)load_to_nozzle_sequence, COUNT(load_to_nozzle_sequence)); - } - - bool MMU2::eject_filament(const uint8_t index, const bool recover) { - - if (!enabled) return false; - - if (thermalManager.tooColdToExtrude(active_extruder)) { - BUZZ(200, 404); - LCD_ALERTMESSAGEPGM(MSG_HOTEND_TOO_COLD); - return false; - } - - LCD_MESSAGEPGM(MSG_MMU2_EJECTING_FILAMENT); - - ENABLE_AXIS_E0(); - current_position.e -= MMU2_FILAMENTCHANGE_EJECT_FEED; - line_to_current_position(2500 / 60); - planner.synchronize(); - command(MMU_CMD_E0 + index); - manage_response(false, false); - - if (recover) { - LCD_MESSAGEPGM(MSG_MMU2_EJECT_RECOVER); - BUZZ(200, 404); - TERN_(HOST_PROMPT_SUPPORT, host_prompt_do(PROMPT_USER_CONTINUE, PSTR("MMU2 Eject Recover"), CONTINUE_STR)); - TERN_(EXTENSIBLE_UI, ExtUI::onUserConfirmRequired_P(PSTR("MMU2 Eject Recover"))); - wait_for_user_response(); - BUZZ(200, 404); - BUZZ(200, 404); - - command(MMU_CMD_R0); - manage_response(false, false); - } - - ui.reset_status(); - - // no active tool - extruder = MMU2_NO_TOOL; - - set_runout_valid(false); - - BUZZ(200, 404); - - DISABLE_AXIS_E0(); - - return true; - } - - /** - * Unload from hotend and retract to MMU - */ - bool MMU2::unload() { - - if (!enabled) return false; - - if (thermalManager.tooColdToExtrude(active_extruder)) { - BUZZ(200, 404); - LCD_ALERTMESSAGEPGM(MSG_HOTEND_TOO_COLD); - return false; - } - - filament_ramming(); - - command(MMU_CMD_U0); - manage_response(false, true); - - BUZZ(200, 404); - - // no active tool - extruder = MMU2_NO_TOOL; - - set_runout_valid(false); - - return true; - } - - /** - * Unload sequence to optimize shape of the tip of the unloaded filament - */ - void MMU2::filament_ramming() { - execute_extruder_sequence((const E_Step *)ramming_sequence, sizeof(ramming_sequence) / sizeof(E_Step)); - } - - void MMU2::execute_extruder_sequence(const E_Step * sequence, int steps) { - - planner.synchronize(); - ENABLE_AXIS_E0(); - - const E_Step* step = sequence; - - LOOP_L_N(i, steps) { - const float es = pgm_read_float(&(step->extrude)); - const feedRate_t fr_mm_m = pgm_read_float(&(step->feedRate)); - - DEBUG_ECHO_START(); - DEBUG_ECHOLNPAIR("E step ", es, "/", fr_mm_m); - - current_position.e += es; - line_to_current_position(MMM_TO_MMS(fr_mm_m)); - planner.synchronize(); - - step++; - } - - DISABLE_AXIS_E0(); - } - -#endif // HAS_LCD_MENU && MMU2_MENUS - -#endif // PRUSA_MMU2 diff --git a/Marlin/src/feature/mmu2/serial-protocol.md b/Marlin/src/feature/mmu2/serial-protocol.md deleted file mode 100644 index 936830e1dc..0000000000 --- a/Marlin/src/feature/mmu2/serial-protocol.md +++ /dev/null @@ -1,94 +0,0 @@ -Startup sequence -================ - -When initialized, MMU sends - -- MMU => 'start\n' - -We follow with - -- MMU <= 'S1\n' -- MMU => 'ok*Firmware version*\n' -- MMU <= 'S2\n' -- MMU => 'ok*Build number*\n' - -#if (12V_mode) - -- MMU <= 'M1\n' -- MMU => 'ok\n' - -#endif - -- MMU <= 'P0\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 is filament loaded to the extruder, 0 otherwise - - -*Build number* is checked against the required value, if it does not match, printer is halted. - - - -Toolchange -========== - -- MMU <= 'T*Filament index*\n' - -MMU sends - -- MMU => 'ok\n' - -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. -When done, the MMU sends - -- MMU => 'ok\n' - -We don't wait for a response here but immediately continue with the next gcode which should -be one or more extruder moves to feed the filament into the hotend. - - -FINDA status -============ - -- MMU <= 'P0\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. - - - -Load filament -============= - -- MMU <= 'L*Filament index*\n' - -MMU will feed filament down to the extruder, when done - -- MMU => 'ok\n' - - -Unload filament -============= - -- MMU <= 'U0\n' - -MMU will retract current filament from the extruder, when done - -- MMU => 'ok\n' - - - -Eject filament -============== - -- MMU <= 'E*Filament index*\n' -- MMU => 'ok\n' - diff --git a/Marlin/src/feature/mmu3/SpoolJoin.cpp b/Marlin/src/feature/mmu3/SpoolJoin.cpp new file mode 100644 index 0000000000..f27d2bc7e9 --- /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 0000000000..b205d26ef5 --- /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 0000000000..0d0f7ac6e0 --- /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 0000000000..dd06126724 --- /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 + current_position + * machine β”‚ / \ + * position β”‚ / \◄────────── stepper_position_mm + current_position + * (mm) β”‚ / \ + * β”‚ / \ + * β”‚/ \◄───────current_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 0000000000..968f76a912 --- /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 0000000000..2e752c4873 --- /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 0000000000..f7221b38f5 --- /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 0000000000..7ed53c0229 --- /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 0000000000..93a4d5e455 --- /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 0000000000..32d2fac3f6 --- /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 0000000000..0ef4864e9f --- /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 0000000000..af2c92ec50 --- /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 0000000000..e4dbffeee2 --- /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 0000000000..499af0f285 --- /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 0000000000..f12d83772a --- /dev/null +++ b/Marlin/src/feature/mmu3/mmu3_marlin1.cpp @@ -0,0 +1,173 @@ +/** + * 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) { + line_to_current_position(feedRate_mm_s); + } + + static void planner_line_to_current_position_sync(float feedRate_mm_s) { + planner_line_to_current_position(feedRate_mm_s); + planner_synchronize(); + } + + void extruder_move(const float delta, const float feedRate_mm_s, const bool sync/*=true*/) { + current_position.e += delta / planner.e_factor[active_extruder]; + planner_line_to_current_position(feedRate_mm_s); + if (sync) planner.synchronize(); + } + + float move_raise_z(const float delta) { + //return raise_z(delta); + xyze_pos_t current_position_before = current_position; + do_z_clearance_by(delta); + return (current_position - current_position_before).z; + } + + void planner_abort_queued_moves() { + //planner_abort_hard(); + quickstop_stepper(); + + // Unblock the planner. This should be safe in the + // toolchange context. Currently we are mainly aborting + // excess E-moves after detecting filament during toolchange. + // If a MMU error is reported, the planner must be unblocked + // as well so the extruder can be parked safely. + //planner_aborted = false; + // eoyilmaz: we don't need this part, the print is not aborted + } + + void planner_synchronize() { + planner.synchronize(); + } + + bool planner_any_moves() { + return planner.has_blocks_queued(); + } + + float planner_get_machine_position_E_mm() { + return current_position.e; + } + + float stepper_get_machine_position_E_mm() { + return planner.get_axis_position_mm(E_AXIS); + } + + float planner_get_current_position_E() { + return current_position.e; + } + + void planner_set_current_position_E(float e) { + current_position.e = e; + } + + xyz_pos_t planner_current_position() { + return xyz_pos_t(current_position); + } + + void motion_blocking_move_xy(float rx, float ry, float feedRate_mm_s) { + current_position.set(rx, ry); + planner_line_to_current_position_sync(feedRate_mm_s); + } + + void motion_blocking_move_z(float z, float feedRate_mm_s) { + current_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 axis_is_trusted(X_AXIS) && 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 0000000000..b277ce9805 --- /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 0000000000..1782cb49fc --- /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/feature/mmu3/mmu3_power.h b/Marlin/src/feature/mmu3/mmu3_power.h new file mode 100644 index 0000000000..77259073a1 --- /dev/null +++ b/Marlin/src/feature/mmu3/mmu3_power.h @@ -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 . + * + */ +#pragma once + +/** + * mmu2_power.h + */ + +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 0000000000..8933f2d5fd --- /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 0000000000..e7b1d86821 --- /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 0000000000..739599f142 --- /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 0000000000..f9553b58c8 --- /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 0000000000..4323925b6b --- /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 0000000000..30f9c5d0d5 --- /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 0000000000..c9b9dbe554 --- /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 0000000000..d3e8db9c5e --- /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 0000000000..036d3ae255 --- /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 0000000000..fe21c79d5a --- /dev/null +++ b/Marlin/src/feature/mmu3/mmu3_supported_version.h @@ -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 . + * + */ +#pragma once + +/** + * mmu2_supported_version.h + */ + +#include + +#define mmuVersionMajor 3 +#define mmuVersionMinor 0 +#define mmuVersionPatch 2 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 0000000000..2b35d6339c --- /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 0000000000..197bc6dcc5 --- /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 0000000000..b0bc36e206 --- /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 0000000000..9cfdba2283 --- /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 0000000000..16e63a8564 --- /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 0000000000..3e423c479d --- /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 0000000000..3e423c479d --- /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 0000000000..63edd2dd5e --- /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 0000000000..f72e6ecafe --- /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 0000000000..d3cd3d371e --- /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 0000000000..e7f06dfb1f --- /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 90bb647118..d36b3edd09 100644 --- a/Marlin/src/feature/password/password.cpp +++ b/Marlin/src/feature/password/password.cpp @@ -31,28 +31,31 @@ Password password; // public: -bool Password::is_set, Password::is_locked; +bool Password::is_set, Password::is_locked, Password::did_first_run; // = false 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; - TERN_(HAS_LCD_MENU, authenticate_user(ui.status_screen, screen_password_entry)); + TERN_(HAS_MARLINUI_MENU, authenticate_user(ui.status_screen, screen_password_entry)); } // // Authentication check // void Password::authentication_check() { - if (value_entry == value) + if (value_entry == value) { is_locked = false; - else + did_first_run = true; + } + else { + is_locked = true; SERIAL_ECHOLNPGM(STR_WRONG_PASSWORD); - - TERN_(HAS_LCD_MENU, authentication_done()); + } + TERN_(HAS_MARLINUI_MENU, authentication_done()); } #endif // PASSWORD_FEATURE diff --git a/Marlin/src/feature/password/password.h b/Marlin/src/feature/password/password.h index 3ed584b29d..0f8bc28bd8 100644 --- a/Marlin/src/feature/password/password.h +++ b/Marlin/src/feature/password/password.h @@ -21,22 +21,24 @@ */ #pragma once -#include "../../lcd/ultralcd.h" +#include "../../lcd/marlinui.h" class Password { public: - static bool is_set, is_locked; + static bool is_set, is_locked, did_first_run; static uint32_t value, value_entry; - Password() { is_locked = false; } + Password() {} static void lock_machine(); + static void authentication_check(); - #if HAS_LCD_MENU + #if HAS_MARLINUI_MENU static void access_menu_password(); - static void authentication_check(); 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); @@ -47,7 +49,7 @@ public: static void start_over(); static void digit_entered(); - static void set_password_done(); + static void set_password_done(const bool with_set=true); static void menu_password_report(); static void remove_password(); diff --git a/Marlin/src/feature/pause.cpp b/Marlin/src/feature/pause.cpp index 0ab6594021..025bcb8383 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" @@ -35,10 +37,17 @@ #include "../gcode/gcode.h" #include "../module/motion.h" #include "../module/planner.h" -#include "../module/stepper.h" #include "../module/printcounter.h" #include "../module/temperature.h" +#if HAS_EXTRUDERS + #include "../module/stepper.h" +#endif + +#if ENABLED(AUTO_BED_LEVELING_UBL) + #include "bedlevel/bedlevel.h" +#endif + #if ENABLED(FWRETRACT) #include "fwretract.h" #endif @@ -53,11 +62,13 @@ #if ENABLED(EXTENSIBLE_UI) #include "../lcd/extui/ui_api.h" +#elif ENABLED(SOVOL_SV06_RTS) + #include "../lcd/sovol_rts/sovol_rts.h" #endif -#include "../lcd/ultralcd.h" +#include "../lcd/marlinui.h" -#if HAS_BUZZER +#if HAS_SOUND #include "../libs/buzzer.h" #endif @@ -75,14 +86,16 @@ static xyze_pos_t resume_position; -#if HAS_LCD_MENU +#if M600_PURGE_MORE_RESUMABLE PauseMenuResponse pause_menu_response; PauseMode pause_mode = PAUSE_MODE_PAUSE_PRINT; #endif -fil_change_settings_t fc_settings[EXTRUDERS]; +#if ENABLED(CONFIGURE_FILAMENT_CHANGE) + fil_change_settings_t fc_settings[EXTRUDERS]; +#endif -#if ENABLED(SDSUPPORT) +#if HAS_MEDIA #include "../sd/cardreader.h" #endif @@ -92,10 +105,10 @@ fil_change_settings_t fc_settings[EXTRUDERS]; #define _PMSG(L) L##_LCD #endif -#if HAS_BUZZER +#if HAS_SOUND static void impatient_beep(const int8_t max_beep_count, const bool restart=false) { - if (TERN0(HAS_LCD_MENU, pause_mode == PAUSE_MODE_PAUSE_PRINT)) return; + if (TERN0(HAS_MARLINUI_MENU, pause_mode == PAUSE_MODE_PAUSE_PRINT)) return; static millis_t next_buzz = 0; static int8_t runout_beep = 0; @@ -130,23 +143,27 @@ fil_change_settings_t fc_settings[EXTRUDERS]; */ static bool ensure_safe_temperature(const bool wait=true, const PauseMode mode=PAUSE_MODE_SAME) { DEBUG_SECTION(est, "ensure_safe_temperature", true); - DEBUG_ECHOLNPAIR("... wait:", int(wait), " mode:", int(mode)); + DEBUG_ECHOLNPGM("... wait:", wait, " mode:", mode); - if (!DEBUGGING(DRYRUN) && thermalManager.targetTooColdToExtrude(active_extruder)) - thermalManager.setTargetHotend(thermalManager.extrude_min_temp, active_extruder); - - #if HAS_LCD_MENU - lcd_pause_show_message(PAUSE_MESSAGE_HEATING, mode); + #if ENABLED(PREVENT_COLD_EXTRUSION) + if (!DEBUGGING(DRYRUN) && thermalManager.targetTooColdToExtrude(active_extruder)) + thermalManager.setTargetHotend(thermalManager.extrude_min_temp, active_extruder); #endif - UNUSED(mode); - if (wait) - return thermalManager.wait_for_hotend(active_extruder); + ui.pause_show_message(PAUSE_MESSAGE_HEATING, mode); - wait_for_heatup = true; // Allow interruption by Emergency Parser M108 - while (wait_for_heatup && ABS(thermalManager.degHotend(active_extruder) - thermalManager.degTargetHotend(active_extruder)) > TEMP_WINDOW) - idle(); - wait_for_heatup = false; + #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); + + // Allow interruption by Emergency Parser M108 + marlin.wait_for_heatup = TERN1(PREVENT_COLD_EXTRUSION, !thermalManager.allow_cold_extrude); + while (marlin.is_heating() && ABS(thermalManager.wholeDegHotend(active_extruder) - thermalManager.degTargetHotend(active_extruder)) > (TEMP_WINDOW)) + marlin.idle(); + marlin.heatup_done(); #if ENABLED(PREVENT_COLD_EXTRUSION) // A user can cancel wait-for-heating with M108 @@ -171,60 +188,61 @@ 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 &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 +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_ECHOLNPAIR("... slowlen:", slow_load_length, " fastlen:", fast_load_length, " purgelen:", purge_length, " maxbeep:", int(max_beep_count), " showlcd:", int(show_lcd), " pauseforuser:", int(pause_for_user), " pausemode:", int(mode) DXC_SAY); - - UNUSED(show_lcd); + 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); if (!ensure_safe_temperature(false, mode)) { - #if HAS_LCD_MENU - if (show_lcd) lcd_pause_show_message(PAUSE_MESSAGE_STATUS, mode); - #endif + if (show_lcd) ui.pause_show_message(PAUSE_MESSAGE_STATUS, mode); return false; } if (pause_for_user) { - #if HAS_LCD_MENU - if (show_lcd) lcd_pause_show_message(PAUSE_MESSAGE_INSERT, mode); - #endif + if (show_lcd) ui.pause_show_message(PAUSE_MESSAGE_INSERT, mode); SERIAL_ECHO_MSG(_PMSG(STR_FILAMENT_CHANGE_INSERT)); first_impatient_beep(max_beep_count); KEEPALIVE_STATE(PAUSED_FOR_USER); + 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' - #if NUM_RUNOUT_SENSORS > 1 - + active_extruder - #endif - ; - host_action_prompt_begin(PROMPT_USER_CONTINUE, PSTR("Load Filament T"), tool); - host_action_prompt_button(CONTINUE_STR); - host_action_prompt_show(); + const char tool = '0' + TERN0(MULTI_FILAMENT_SENSOR, active_extruder); + hostui.prompt_do(PROMPT_USER_CONTINUE, F("Load Filament T"), tool, FPSTR(CONTINUE_STR)); #endif - TERN_(EXTENSIBLE_UI, ExtUI::onUserConfirmRequired_P(PSTR("Load Filament"))); - while (wait_for_user) { + + while (marlin.wait_for_user) { impatient_beep(max_beep_count); - idle_no_sleep(); + #if ALL(HAS_FILAMENT_SENSOR, FILAMENT_CHANGE_RESUME_ON_INSERT) + #if MULTI_FILAMENT_SENSOR + #define _CASE_INSERTED(N) case N-1: if (!FILAMENT_IS_OUT(N)) marlin.user_resume(); break; + switch (active_extruder) { + REPEAT_1(NUM_RUNOUT_SENSORS, _CASE_INSERTED) + } + #else + if (!FILAMENT_IS_OUT()) marlin.user_resume(); + #endif + #endif + marlin.idle_no_sleep(); } } - #if HAS_LCD_MENU - if (show_lcd) lcd_pause_show_message(PAUSE_MESSAGE_LOAD, mode); - #endif + 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; - active_extruder = DXC_ext; - extruder_duplication_enabled = false; + set_duplication_enabled(false, DXC_ext); #endif + TERN_(BELTPRINTER, do_blocking_move_to_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); @@ -243,58 +261,77 @@ bool load_filament(const float &slow_load_length/*=0*/, const float &fast_load_l } #if ENABLED(DUAL_X_CARRIAGE) // Tie the two extruders movement back together. - active_extruder = saved_ext; - extruder_duplication_enabled = saved_ext_dup_mode; - stepper.set_directions(); + set_duplication_enabled(saved_ext_dup_mode, saved_ext); #endif #if ENABLED(ADVANCED_PAUSE_CONTINUOUS_PURGE) - #if HAS_LCD_MENU - if (show_lcd) lcd_pause_show_message(PAUSE_MESSAGE_PURGE); - #endif + if (show_lcd) ui.pause_show_message(PAUSE_MESSAGE_PURGE); - TERN_(HOST_PROMPT_SUPPORT, host_prompt_do(PROMPT_USER_CONTINUE, PSTR("Filament Purging..."), CONTINUE_STR)); - TERN_(EXTENSIBLE_UI, ExtUI::onUserConfirmRequired_P(PSTR("Filament Purging..."))); - 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) + 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))); + 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) unscaled_e_move(1, ADVANCED_PAUSE_PURGE_FEEDRATE); - wait_for_user = false; + marlin.user_resume(); #else do { if (purge_length > 0) { // "Wait for filament purge" - #if HAS_LCD_MENU - if (show_lcd) lcd_pause_show_message(PAUSE_MESSAGE_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); } - TERN_(HOST_PROMPT_SUPPORT, filament_load_host_prompt()); // Initiate another host prompt. (NOTE: host_response_handler may also do this!) + TERN_(HOST_PROMPT_SUPPORT, hostui.filament_load_prompt()); // Initiate another host prompt. - #if HAS_LCD_MENU + #if M600_PURGE_MORE_RESUMABLE if (show_lcd) { // Show "Purge More" / "Resume" menu and wait for reply KEEPALIVE_STATE(PAUSED_FOR_USER); - wait_for_user = false; - lcd_pause_show_message(PAUSE_MESSAGE_OPTION); - while (pause_menu_response == PAUSE_RESPONSE_WAIT_FOR) idle_no_sleep(); + 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 + TERN_(SOVOL_SV06_RTS, rts.gotoPage(ID_PurgeMore_L, ID_PurgeMore_D)); + #endif + while (pause_menu_response == PAUSE_RESPONSE_WAIT_FOR) marlin.idle_no_sleep(); } #endif // Keep looping if "Purge More" was selected - } while (TERN0(HAS_LCD_MENU, show_lcd && pause_menu_response == PAUSE_RESPONSE_EXTRUDE_MORE)); + } while (TERN0(M600_PURGE_MORE_RESUMABLE, pause_menu_response == PAUSE_RESPONSE_EXTRUDE_MORE)); #endif - TERN_(HOST_PROMPT_SUPPORT, host_action_prompt_end()); + + TERN_(MPCTEMP, MPC::e_paused = false); + + TERN_(HOST_PROMPT_SUPPORT, hostui.prompt_end()); return true; } +/** + * Disabling E steppers for manual filament change should be fine + * as long as users don't spin the E motor ridiculously fast and + * send current back to their board, potentially frying it. + */ +inline void disable_active_extruder() { + #if HAS_EXTRUDERS + stepper.DISABLE_EXTRUDER(active_extruder); + safe_delay(100); + #endif +} + /** * Unload filament from the hotend * @@ -305,34 +342,33 @@ bool load_filament(const float &slow_load_length/*=0*/, const float &fast_load_l * * Returns 'true' if unload was completed, 'false' for abort */ -bool unload_filament(const float &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 BOTH(FILAMENT_UNLOAD_ALL_EXTRUDERS, MIXING_EXTRUDER) - , const float &mix_multiplier/*=1.0*/ + #if ALL(FILAMENT_UNLOAD_ALL_EXTRUDERS, MIXING_EXTRUDER) + , const float mix_multiplier/*=1.0*/ #endif ) { DEBUG_SECTION(uf, "unload_filament", true); - DEBUG_ECHOLNPAIR("... unloadlen:", unload_length, " showlcd:", int(show_lcd), " mode:", int(mode) - #if BOTH(FILAMENT_UNLOAD_ALL_EXTRUDERS, MIXING_EXTRUDER) + DEBUG_ECHOLNPGM("... unloadlen:", unload_length, " showlcd:", show_lcd, " mode:", mode + #if ALL(FILAMENT_UNLOAD_ALL_EXTRUDERS, MIXING_EXTRUDER) , " mixmult:", mix_multiplier #endif ); - UNUSED(show_lcd); - - #if !BOTH(FILAMENT_UNLOAD_ALL_EXTRUDERS, MIXING_EXTRUDER) - constexpr float mix_multiplier = 1.0; + #if !ALL(FILAMENT_UNLOAD_ALL_EXTRUDERS, MIXING_EXTRUDER) + constexpr float mix_multiplier = 1.0f; #endif if (!ensure_safe_temperature(false, mode)) { - #if HAS_LCD_MENU - if (show_lcd) lcd_pause_show_message(PAUSE_MESSAGE_STATUS); - #endif + if (show_lcd) ui.pause_show_message(PAUSE_MESSAGE_STATUS); return false; } - #if HAS_LCD_MENU - if (show_lcd) lcd_pause_show_message(PAUSE_MESSAGE_UNLOAD, mode); + 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 @@ -357,11 +393,8 @@ bool unload_filament(const float &unload_length, const bool show_lcd/*=false*/, planner.settings.retract_acceleration = saved_acceleration; #endif - // Disable E steppers for manual change - #if HAS_E_STEPPER_ENABLE - disable_e_stepper(active_extruder); - safe_delay(100); - #endif + // Disable the Extruder for manual change + disable_active_extruder(); return true; } @@ -383,30 +416,29 @@ bool unload_filament(const float &unload_length, const bool show_lcd/*=false*/, */ uint8_t did_pause_print = 0; -bool pause_print(const float &retract, const xyz_pos_t &park_point, const float &unload_length/*=0*/, const bool show_lcd/*=false*/ 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_ECHOLNPAIR("... park.x:", park_point.x, " y:", park_point.y, " z:", park_point.z, " unloadlen:", unload_length, " showlcd:", int(show_lcd) DXC_SAY); - - UNUSED(show_lcd); + DEBUG_ECHOLNPGM("... park.x:", park_point.x, " y:", park_point.y, " z:", park_point.z, " unloadlen:", unload_length, " showlcd:", show_lcd DXC_SAY); if (did_pause_print) return false; // already paused #if ENABLED(HOST_ACTION_COMMANDS) #ifdef ACTION_ON_PAUSED - host_action_paused(); + hostui.paused(); #elif defined(ACTION_ON_PAUSE) - host_action_pause(); + hostui.pause(); #endif #endif - TERN_(HOST_PROMPT_SUPPORT, host_prompt_open(PROMPT_INFO, PSTR("Pause"), DISMISS_STR)); + TERN_(HOST_PROMPT_SUPPORT, hostui.prompt_open(PROMPT_INFO, F("Pause"), FPSTR(DISMISS_STR))); // Indicate that the printer is paused ++did_pause_print; // Pause the print job and timer - #if ENABLED(SDSUPPORT) - if (IS_SD_PRINTING()) { + #if HAS_MEDIA + const bool was_sd_printing = card.isStillPrinting(); + if (was_sd_printing) { card.pauseSDPrint(); ++did_pause_print; // Indicate SD pause also } @@ -417,38 +449,54 @@ bool pause_print(const float &retract, const xyz_pos_t &park_point, const float // Save current position resume_position = current_position; + // Will the nozzle be parking? + const bool do_park = !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; + if (was_sd_printing && recovery.enabled) recovery.save(true, park_raise, do_park); + #endif + // Wait for buffered blocks to complete planner.synchronize(); - #if ENABLED(ADVANCED_PAUSE_FANS_PAUSE) && HAS_FAN + #if ALL(ADVANCED_PAUSE_FANS_PAUSE, HAS_FAN) thermalManager.set_fans_paused(true); #endif // Initial retract before move to filament change position if (retract && thermalManager.hotEnoughToExtrude(active_extruder)) { - DEBUG_ECHOLNPAIR("... retract:", retract); + DEBUG_ECHOLNPGM("... retract:", retract); + + #if ENABLED(AUTO_BED_LEVELING_UBL) + const bool leveling_was_enabled = planner.leveling_active; // save leveling state + set_bed_leveling_enabled(false); // turn off leveling + #endif + unscaled_e_move(retract, PAUSE_PARK_RETRACT_FEEDRATE); + + TERN_(AUTO_BED_LEVELING_UBL, set_bed_leveling_enabled(leveling_was_enabled)); // restore leveling } - // Park the nozzle by moving up by z_lift and then moving to (x_pos, y_pos) - if (!axes_should_home()) - nozzle.park(0, park_point); + // If axes don't need to home then the nozzle can park + if (do_park) nozzle.park(0, park_point); // Park the nozzle by doing a Minimum Z Raise followed by an XY Move + 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; - active_extruder = DXC_ext; - extruder_duplication_enabled = false; + set_duplication_enabled(false, DXC_ext); #endif - if (unload_length) // Unload the filament + // Unload the filament, if specified + if (unload_length) unload_filament(unload_length, show_lcd, PAUSE_MODE_CHANGE_FILAMENT); - #if ENABLED(DUAL_X_CARRIAGE) - active_extruder = saved_ext; - extruder_duplication_enabled = saved_ext_dup_mode; - stepper.set_directions(); - #endif + TERN_(DUAL_X_CARRIAGE, set_duplication_enabled(saved_ext_dup_mode, saved_ext)); + + // Disable the Extruder for manual change + disable_active_extruder(); return true; } @@ -468,16 +516,21 @@ bool pause_print(const float &retract, const xyz_pos_t &park_point, const float void show_continue_prompt(const bool is_reload) { DEBUG_SECTION(scp, "pause_print", true); - DEBUG_ECHOLNPAIR("... is_reload:", int(is_reload)); + DEBUG_ECHOLNPGM("... is_reload:", is_reload); - TERN_(HAS_LCD_MENU, lcd_pause_show_message(is_reload ? PAUSE_MESSAGE_INSERT : PAUSE_MESSAGE_WAITING)); + 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(); - serialprintPGM(is_reload ? PSTR(_PMSG(STR_FILAMENT_CHANGE_INSERT) "\n") : PSTR(_PMSG(STR_FILAMENT_CHANGE_WAIT) "\n")); + SERIAL_ECHO(is_reload ? F(_PMSG(STR_FILAMENT_CHANGE_INSERT) "\n") : F(_PMSG(STR_FILAMENT_CHANGE_WAIT) "\n")); } void wait_for_confirmation(const bool is_reload/*=false*/, const int8_t max_beep_count/*=0*/ DXC_ARGS) { DEBUG_SECTION(wfc, "wait_for_confirmation", true); - DEBUG_ECHOLNPAIR("... is_reload:", is_reload, " maxbeep:", int(max_beep_count) DXC_SAY); + DEBUG_ECHOLNPGM("... is_reload:", is_reload, " maxbeep:", max_beep_count DXC_SAY); bool nozzle_timed_out = false; @@ -493,16 +546,15 @@ void wait_for_confirmation(const bool is_reload/*=false*/, const int8_t max_beep #if ENABLED(DUAL_X_CARRIAGE) const int8_t saved_ext = active_extruder; const bool saved_ext_dup_mode = extruder_duplication_enabled; - active_extruder = DXC_ext; - extruder_duplication_enabled = false; + set_duplication_enabled(false, DXC_ext); #endif // Wait for filament insert by user and press button KEEPALIVE_STATE(PAUSED_FOR_USER); - TERN_(HOST_PROMPT_SUPPORT, host_prompt_do(PROMPT_USER_CONTINUE, GET_TEXT(MSG_NOZZLE_PARKED), CONTINUE_STR)); - TERN_(EXTENSIBLE_UI, ExtUI::onUserConfirmRequired_P(GET_TEXT(MSG_NOZZLE_PARKED))); - wait_for_user = true; // LCD click or M108 will clear this - while (wait_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))); + 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... @@ -512,18 +564,26 @@ void wait_for_confirmation(const bool is_reload/*=false*/, const int8_t max_beep // Wait for the user to press the button to re-heat the nozzle, then // re-heat the nozzle, re-show the continue prompt, restart idle timers, start over if (nozzle_timed_out) { - TERN_(HAS_LCD_MENU, lcd_pause_show_message(PAUSE_MESSAGE_HEAT)); + 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, host_prompt_do(PROMPT_USER_CONTINUE, GET_TEXT(MSG_HEATER_TIMEOUT), GET_TEXT(MSG_REHEAT))); + TERN_(HOST_PROMPT_SUPPORT, hostui.prompt_do(PROMPT_USER_CONTINUE, GET_TEXT_F(MSG_HEATER_TIMEOUT), GET_TEXT_F(MSG_REHEAT))); - TERN_(EXTENSIBLE_UI, ExtUI::onUserConfirmRequired_P(GET_TEXT(MSG_HEATER_TIMEOUT))); + #if ENABLED(TOUCH_UI_FTDI_EVE) + ExtUI::onUserConfirmRequired(GET_TEXT_F(MSG_FTDI_HEATER_TIMEOUT)); + #elif ENABLED(EXTENSIBLE_UI) + ExtUI::onUserConfirmRequired(GET_TEXT_F(MSG_HEATER_TIMEOUT)); + #endif - 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, host_prompt_do(PROMPT_INFO, GET_TEXT(MSG_REHEATING))); + TERN_(HOST_PROMPT_SUPPORT, hostui.prompt_do(PROMPT_INFO, GET_TEXT_F(MSG_REHEATING))); - TERN_(EXTENSIBLE_UI, ExtUI::onStatusChanged_P(GET_TEXT(MSG_REHEATING))); + LCD_MESSAGE(MSG_REHEATING); // Re-enable the heaters if they timed out HOTEND_LOOP() thermalManager.reset_hotend_idle_timer(e); @@ -538,20 +598,22 @@ void wait_for_confirmation(const bool is_reload/*=false*/, const int8_t max_beep const millis_t nozzle_timeout = SEC_TO_MS(PAUSE_PARK_NOZZLE_TIMEOUT); HOTEND_LOOP() thermalManager.heater_idle[e].start(nozzle_timeout); - TERN_(HOST_PROMPT_SUPPORT, host_prompt_do(PROMPT_USER_CONTINUE, PSTR("Reheat Done"), CONTINUE_STR)); - TERN_(EXTENSIBLE_UI, ExtUI::onUserConfirmRequired_P(PSTR("Reheat finished."))); - wait_for_user = true; - nozzle_timed_out = false; + TERN_(HOST_PROMPT_SUPPORT, hostui.continue_prompt(GET_TEXT_F(MSG_REHEATDONE))); + #if ENABLED(EXTENSIBLE_UI) + ExtUI::onUserConfirmRequired(GET_TEXT_F(MSG_REHEATDONE)); + #else + LCD_MESSAGE(MSG_REHEATDONE); + #endif + + 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(); } - #if ENABLED(DUAL_X_CARRIAGE) - active_extruder = saved_ext; - extruder_duplication_enabled = saved_ext_dup_mode; - stepper.set_directions(); - #endif + TERN_(DUAL_X_CARRIAGE, set_duplication_enabled(saved_ext_dup_mode, saved_ext)); } /** @@ -574,12 +636,30 @@ 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 &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*/, int16_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_ECHOLNPAIR("... slowlen:", slow_load_length, " fastlen:", fast_load_length, " purgelen:", purge_length, " maxbeep:", int(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_ECHOLNPAIR( + SERIAL_ECHOLNPGM( "start of resume_print()\ndual_x_carriage_mode:", dual_x_carriage_mode, "\nextruder_duplication_enabled:", extruder_duplication_enabled, "\nactive_extruder:", active_extruder, @@ -590,43 +670,51 @@ void resume_print(const float &slow_load_length/*=0*/, const float &fast_load_le 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)) { + if (targetTemp > thermalManager.degTargetHotend(active_extruder)) thermalManager.setTargetHotend(targetTemp, active_extruder); - } // Load the new filament - load_filament(slow_load_length, fast_load_length, purge_length, max_beep_count, true, nozzle_timed_out, PAUSE_MODE_SAME DXC_PASS); + load_filament(slow_load_length, fast_load_length, purge_length, max_beep_count, show_lcd, nozzle_timed_out, PAUSE_MODE_SAME DXC_PASS); if (targetTemp > 0) { thermalManager.setTargetHotend(targetTemp, active_extruder); thermalManager.wait_for_hotend(active_extruder, false); } - TERN_(HAS_LCD_MENU, lcd_pause_show_message(PAUSE_MESSAGE_RESUME)); + ui.pause_show_message(PAUSE_MESSAGE_RESUME); // Check Temperature before moving hotend - ensure_safe_temperature(); + ensure_safe_temperature(DISABLED(BELTPRINTER)); // Retract to prevent oozing unscaled_e_move(-(PAUSE_PARK_RETRACT_LENGTH), feedRate_t(PAUSE_PARK_RETRACT_FEEDRATE)); if (!axes_should_home()) { - // Move XY to starting position, then Z - do_blocking_move_to_xy(resume_position, feedRate_t(NOZZLE_PARK_XY_FEEDRATE)); + // 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); - // Move Z_AXIS to saved position - do_blocking_move_to_z(resume_position.z, feedRate_t(NOZZLE_PARK_Z_FEEDRATE)); + // Move Z back to saved position + destination.z = resume_position.z; + prepare_internal_move_to_destination(NOZZLE_PARK_Z_FEEDRATE); } + #if ENABLED(AUTO_BED_LEVELING_UBL) + const bool leveling_was_enabled = planner.leveling_active; // save leveling state + set_bed_leveling_enabled(false); // turn off leveling + #endif + // Unretract 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 @@ -636,31 +724,43 @@ void resume_print(const float &slow_load_length/*=0*/, const float &fast_load_le // If resume_position is negative if (resume_position.e < 0) unscaled_e_move(resume_position.e, feedRate_t(PAUSE_PARK_RETRACT_FEEDRATE)); - #if ADVANCED_PAUSE_RESUME_PRIME != 0 - unscaled_e_move(ADVANCED_PAUSE_RESUME_PRIME, feedRate_t(ADVANCED_PAUSE_PURGE_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)); #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)); - // Write PLR now to update the z axis value - TERN_(POWER_LOSS_RECOVERY, if (recovery.enabled) recovery.save(true)); - - TERN_(HAS_LCD_MENU, lcd_pause_show_message(PAUSE_MESSAGE_STATUS)); + 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 - host_action_resumed(); + hostui.resumed(); #elif defined(ACTION_ON_RESUME) - host_action_resume(); + hostui.resume(); #endif --did_pause_print; - TERN_(HOST_PROMPT_SUPPORT, host_prompt_open(PROMPT_INFO, PSTR("Resuming"), DISMISS_STR)); + TERN_(HOST_PROMPT_SUPPORT, hostui.prompt_open(PROMPT_INFO, F("Resuming"), FPSTR(DISMISS_STR))); - #if ENABLED(SDSUPPORT) - if (did_pause_print) { card.startFileprint(); --did_pause_print; } + // Resume the print job timer if it was running + if (print_job_timer.isPaused()) print_job_timer.start(); + + #if HAS_MEDIA + if (did_pause_print) { + --did_pause_print; + card.startOrResumeFilePrinting(); + // Write PLR now to update the z axis value + TERN_(POWER_LOSS_RECOVERY, if (recovery.enabled) recovery.save(true)); + } #endif #if ENABLED(ADVANCED_PAUSE_FANS_PAUSE) && HAS_FAN @@ -669,11 +769,8 @@ void resume_print(const float &slow_load_length/*=0*/, const float &fast_load_le TERN_(HAS_FILAMENT_SENSOR, runout.reset()); - // Resume the print job timer if it was running - if (print_job_timer.isPaused()) print_job_timer.start(); - - TERN_(HAS_DISPLAY, ui.reset_status()); - TERN_(HAS_LCD_MENU, ui.return_to_status()); + ui.reset_status(); + ui.return_to_status(); } #endif // ADVANCED_PAUSE_FEATURE diff --git a/Marlin/src/feature/pause.h b/Marlin/src/feature/pause.h index 016b0ce3f7..4fbb108180 100644 --- a/Marlin/src/feature/pause.h +++ b/Marlin/src/feature/pause.h @@ -26,10 +26,6 @@ * This may be combined with related G-codes if features are consolidated. */ -typedef struct { - float unload_length, load_length; -} fil_change_settings_t; - #include "../inc/MarlinConfigPre.h" #if ENABLED(ADVANCED_PAUSE_FEATURE) @@ -48,18 +44,20 @@ enum PauseMessage : char { PAUSE_MESSAGE_PARKING, PAUSE_MESSAGE_CHANGING, PAUSE_MESSAGE_WAITING, - PAUSE_MESSAGE_UNLOAD, PAUSE_MESSAGE_INSERT, PAUSE_MESSAGE_LOAD, + PAUSE_MESSAGE_UNLOAD, PAUSE_MESSAGE_PURGE, PAUSE_MESSAGE_OPTION, PAUSE_MESSAGE_RESUME, - PAUSE_MESSAGE_STATUS, PAUSE_MESSAGE_HEAT, - PAUSE_MESSAGE_HEATING + PAUSE_MESSAGE_HEATING, + PAUSE_MESSAGE_STATUS, + PAUSE_MESSAGE_COUNT }; -#if HAS_LCD_MENU +#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, @@ -69,35 +67,76 @@ enum PauseMessage : char { extern PauseMode pause_mode; #endif -extern fil_change_settings_t fc_settings[EXTRUDERS]; +typedef struct FilamentChangeSettings { + #if ENABLED(CONFIGURE_FILAMENT_CHANGE) + float load_length, unload_length; + #else + static constexpr float load_length = FILAMENT_CHANGE_FAST_LOAD_LENGTH, + unload_length = FILAMENT_CHANGE_UNLOAD_LENGTH; + #endif +} fil_change_settings_t; + +#if ENABLED(CONFIGURE_FILAMENT_CHANGE) + extern fil_change_settings_t fc_settings[EXTRUDERS]; +#else + constexpr fil_change_settings_t fc_settings[EXTRUDERS]; +#endif extern uint8_t did_pause_print; -#if ENABLED(DUAL_X_CARRIAGE) - #define DXC_PARAMS , const int8_t DXC_ext=-1 - #define DXC_ARGS , const int8_t DXC_ext - #define DXC_PASS , DXC_ext - #define DXC_SAY , " dxc:", int(DXC_ext) -#else - #define DXC_PARAMS - #define DXC_ARGS - #define DXC_PASS - #define DXC_SAY -#endif +#define DXC_PARAMS OPTARG(DUAL_X_CARRIAGE, const int8_t DXC_ext=-1) +#define DXC_ARGS OPTARG(DUAL_X_CARRIAGE, const int8_t DXC_ext) +#define DXC_PASS OPTARG(DUAL_X_CARRIAGE, DXC_ext) +#define DXC_SAY OPTARG(DUAL_X_CARRIAGE, " dxc:", int(DXC_ext)) -bool pause_print(const float &retract, const xyz_pos_t &park_point, const float &unload_length=0, const bool show_lcd=false DXC_PARAMS); +// Pause the print. If unload_length is set, do a Filament Unload +bool pause_print( + 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 unload_length=0 // (mm) Filament Change Unload Length - 0 to skip + DXC_PARAMS // Dual-X-Carriage extruder index +); -void wait_for_confirmation(const bool is_reload=false, const int8_t max_beep_count=0 DXC_PARAMS); +void wait_for_confirmation( + const bool is_reload=false, // Reload Filament? (otherwise Resume Print) + const int8_t max_beep_count=0 // Beep alert for attention + DXC_PARAMS // Dual-X-Carriage extruder index +); -void resume_print(const float &slow_load_length=0, const float &fast_load_length=0, const float &extrude_length=ADVANCED_PAUSE_PURGE_LENGTH, const int8_t max_beep_count=0, int16_t targetTemp=0 DXC_PARAMS); +void resume_print( + 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 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 &slow_load_length=0, const float &fast_load_length=0, const float &extrude_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_PARAMS); +bool load_filament( + 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? + const PauseMode mode=PAUSE_MODE_PAUSE_PRINT // Pause Mode to apply + DXC_PARAMS // Dual-X-Carriage extruder index +); -bool unload_filament(const float &unload_length, const bool show_lcd=false, const PauseMode mode=PAUSE_MODE_PAUSE_PRINT - #if BOTH(FILAMENT_UNLOAD_ALL_EXTRUDERS, MIXING_EXTRUDER) - , const float &mix_multiplier=1.0 +bool unload_filament( + 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 mix_multiplier=1.0f // Extrusion multiplier (for a Mixing Extruder) #endif ); -#endif // ADVANCED_PAUSE_FEATURE +#else // !ADVANCED_PAUSE_FEATURE + + constexpr uint8_t did_pause_print = 0; + +#endif // !ADVANCED_PAUSE_FEATURE diff --git a/Marlin/src/feature/power.cpp b/Marlin/src/feature/power.cpp index 5be554e5e4..2068558fe9 100644 --- a/Marlin/src/feature/power.cpp +++ b/Marlin/src/feature/power.cpp @@ -24,94 +24,259 @@ * power.cpp - power control */ -#include "../inc/MarlinConfig.h" +#include "../inc/MarlinConfigPre.h" -#if ENABLED(AUTO_POWER_CONTROL) +#if ANY(PSU_CONTROL, AUTO_POWER_CONTROL) #include "power.h" +#include "../module/planner.h" +#include "../module/stepper/indirection.h" // for restore_stepper_drivers #include "../module/temperature.h" -#include "../module/stepper/indirection.h" #include "../MarlinCore.h" -#if BOTH(USE_CONTROLLER_FAN, AUTO_POWER_CONTROLLERFAN) - #include "controllerfan.h" +#if ENABLED(MAX7219_REINIT_ON_POWERUP) + #include "max7219.h" +#endif + +#if ENABLED(PS_OFF_SOUND) + #include "../libs/buzzer.h" +#endif + +#if defined(PSU_POWERUP_GCODE) || defined(PSU_POWEROFF_GCODE) + #include "../gcode/gcode.h" #endif Power powerManager; +bool Power::psu_on; -millis_t Power::lastPowerOn; +#if ENABLED(AUTO_POWER_CONTROL) + #include "../module/stepper.h" + #include "../module/temperature.h" -bool Power::is_power_needed() { - #if ENABLED(AUTO_POWER_FANS) - FANS_LOOP(i) if (thermalManager.fan_speed[i]) return true; + #if ALL(USE_CONTROLLER_FAN, AUTO_POWER_CONTROLLERFAN) + #include "controllerfan.h" #endif - #if ENABLED(AUTO_POWER_E_FANS) - HOTEND_LOOP() if (thermalManager.autofan_speed[e]) return true; + #if ANY(LASER_FEATURE, SPINDLE_FEATURE) + #include "spindle_laser.h" #endif - #if BOTH(USE_CONTROLLER_FAN, AUTO_POWER_CONTROLLERFAN) - if (controllerFan.state()) return true; - #endif + millis_t Power::lastPowerOn; +#endif - if (TERN0(AUTO_POWER_CHAMBER_FAN, thermalManager.chamberfan_speed)) - return true; +#if PSU_TRACK_STATE_MS + millis_t Power::last_state_change_ms = 0; +#endif - // If any of the drivers or the bed are enabled... - if (X_ENABLE_READ() == X_ENABLE_ON || Y_ENABLE_READ() == Y_ENABLE_ON || Z_ENABLE_READ() == Z_ENABLE_ON - #if HAS_X2_ENABLE - || X2_ENABLE_READ() == X_ENABLE_ON - #endif - #if HAS_Y2_ENABLE - || Y2_ENABLE_READ() == Y_ENABLE_ON - #endif - #if HAS_Z2_ENABLE - || Z2_ENABLE_READ() == Z_ENABLE_ON - #endif - #if E_STEPPERS - #define _OR_ENABLED_E(N) || E##N##_ENABLE_READ() == E_ENABLE_ON - REPEAT(E_STEPPERS, _OR_ENABLED_E) - #endif - ) return true; - - HOTEND_LOOP() if (thermalManager.degTargetHotend(e) > 0 || thermalManager.temp_hotend[e].soft_pwm_amount > 0) return true; - if (TERN0(HAS_HEATED_BED, thermalManager.degTargetBed() > 0 || thermalManager.temp_bed.soft_pwm_amount > 0)) return true; - - #if HAS_HOTEND && AUTO_POWER_E_TEMP - HOTEND_LOOP() if (thermalManager.degHotend(e) >= AUTO_POWER_E_TEMP) return true; - #endif - - #if HAS_HEATED_CHAMBER && AUTO_POWER_CHAMBER_TEMP - if (thermalManager.degChamber() >= AUTO_POWER_CHAMBER_TEMP) return true; - #endif - - return false; -} - -void Power::check() { - static millis_t nextPowerCheck = 0; - millis_t ms = millis(); - if (ELAPSED(ms, nextPowerCheck)) { - nextPowerCheck = ms + 2500UL; - if (is_power_needed()) - power_on(); - else if (!lastPowerOn || ELAPSED(ms, lastPowerOn + SEC_TO_MS(POWER_TIMEOUT))) - power_off(); - } +/** + * Initialize pins & state for the power manager. + * + */ +void Power::init() { + psu_on = ENABLED(PSU_DEFAULT_OFF); // Set opposite state to get full power_off/on + TERN(PSU_DEFAULT_OFF, power_off(), power_on()); } +/** + * Power on if the power is currently off. + * Restores stepper drivers and processes any PSU_POWERUP_GCODE. + * + */ void Power::power_on() { - lastPowerOn = millis(); - if (!powersupply_on) { - PSU_PIN_ON(); - safe_delay(PSU_POWERUP_DELAY); - restore_stepper_drivers(); - TERN_(HAS_TRINAMIC_CONFIG, safe_delay(PSU_POWERUP_DELAY)); - } + #if ENABLED(AUTO_POWER_CONTROL) + const millis_t now = millis(); + lastPowerOn = now + !now; + #endif + + if (psu_on) return; + + #if ANY(POWER_OFF_TIMER, POWER_OFF_WAIT_FOR_COOLDOWN) + cancelAutoPowerOff(); + #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 + gcode.process_subcommands_now(F(PSU_POWERUP_GCODE)); + #endif } +/** + * Power off if the power is currently on. + * Processes any PSU_POWEROFF_GCODE and makes a PS_OFF_SOUND if enabled. + */ void Power::power_off() { - if (powersupply_on) PSU_PIN_OFF(); + TERN_(HAS_SUICIDE, marlin.suicide()); + + if (!psu_on) return; + + SERIAL_ECHOLNPGM(STR_POWEROFF); + + #ifdef PSU_POWEROFF_GCODE + gcode.process_subcommands_now(F(PSU_POWEROFF_GCODE)); + #endif + + #if ENABLED(PS_OFF_SOUND) + BUZZ(1000, 659); + #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) + cancelAutoPowerOff(); + #endif } +#if ANY(AUTO_POWER_CONTROL, POWER_OFF_WAIT_FOR_COOLDOWN) + + bool Power::is_cooling_needed() { + #if HAS_HOTEND && AUTO_POWER_E_TEMP + HOTEND_LOOP() if (thermalManager.degHotend(e) >= (AUTO_POWER_E_TEMP)) return true; + #endif + + #if HAS_HEATED_CHAMBER && AUTO_POWER_CHAMBER_TEMP + if (thermalManager.degChamber() >= (AUTO_POWER_CHAMBER_TEMP)) return true; + #endif + + #if HAS_COOLER && AUTO_POWER_COOLER_TEMP + if (thermalManager.degCooler() >= (AUTO_POWER_COOLER_TEMP)) return true; + #endif + + return false; + } + +#endif + +#if ANY(POWER_OFF_TIMER, POWER_OFF_WAIT_FOR_COOLDOWN) + + #if ENABLED(POWER_OFF_TIMER) + millis_t Power::power_off_time = 0; + void Power::setPowerOffTimer(const millis_t delay_ms) { power_off_time = millis() + delay_ms; } + #endif + + #if ENABLED(POWER_OFF_WAIT_FOR_COOLDOWN) + bool Power::power_off_on_cooldown = false; + void Power::setPowerOffOnCooldown(const bool ena) { power_off_on_cooldown = ena; } + #endif + + void Power::cancelAutoPowerOff() { + TERN_(POWER_OFF_TIMER, power_off_time = 0); + TERN_(POWER_OFF_WAIT_FOR_COOLDOWN, power_off_on_cooldown = false); + } + + void Power::checkAutoPowerOff() { + if (TERN1(POWER_OFF_TIMER, !power_off_time) && TERN1(POWER_OFF_WAIT_FOR_COOLDOWN, !power_off_on_cooldown)) return; + if (TERN0(POWER_OFF_WAIT_FOR_COOLDOWN, power_off_on_cooldown && is_cooling_needed())) return; + if (TERN0(POWER_OFF_TIMER, power_off_time && PENDING(millis(), power_off_time))) return; + power_off(); + } + +#endif // POWER_OFF_TIMER || POWER_OFF_WAIT_FOR_COOLDOWN + +#if ENABLED(AUTO_POWER_CONTROL) + + #ifndef POWER_TIMEOUT + #define POWER_TIMEOUT 0 + #endif + + /** + * Check all conditions that would signal power needing to be on. + * + * @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 (marlin.printJobOngoing() || marlin.printingIsPaused()) return true; + + #if ENABLED(AUTO_POWER_FANS) + FANS_LOOP(i) if (thermalManager.fan_speed[i]) return true; + #endif + + #if ENABLED(AUTO_POWER_E_FANS) + HOTEND_LOOP() if (thermalManager.autofan_speed[e]) return true; + #endif + + #if ALL(USE_CONTROLLER_FAN, AUTO_POWER_CONTROLLERFAN) + if (controllerFan.state()) return true; + #endif + + #if ANY(LASER_FEATURE, SPINDLE_FEATURE) + if (TERN0(AUTO_POWER_SPINDLE_LASER, cutter.enabled())) return true; + #endif + + if (TERN0(AUTO_POWER_CHAMBER_FAN, thermalManager.chamberfan_speed)) + return true; + + if (TERN0(AUTO_POWER_COOLER_FAN, thermalManager.coolerfan_speed)) + return true; + + #if HAS_HOTEND + HOTEND_LOOP() if (thermalManager.degTargetHotend(e) > 0 || thermalManager.temp_hotend[e].soft_pwm_amount > 0) return true; + #endif + + if (TERN0(HAS_HEATED_BED, thermalManager.degTargetBed() > 0 || thermalManager.temp_bed.soft_pwm_amount > 0)) return true; + + return is_cooling_needed(); + } + + /** + * Check if we should power off automatically (POWER_TIMEOUT elapsed, !is_power_needed). + * + * @param pause pause the 'timer' + */ + void Power::check(const bool pause) { + static millis_t nextPowerCheck = 0; + const millis_t now = millis(); + #if POWER_TIMEOUT > 0 + static bool _pause = false; + if (pause != _pause) { + lastPowerOn = now + !now; + _pause = pause; + } + if (pause) return; + #endif + if (ELAPSED(now, nextPowerCheck)) { + nextPowerCheck = now + 2500UL; + if (is_power_needed()) + power_on(); + else if (!lastPowerOn || (POWER_TIMEOUT > 0 && ELAPSED(now, lastPowerOn, SEC_TO_MS(POWER_TIMEOUT)))) + power_off(); + } + } + + #if POWER_OFF_DELAY > 0 + + /** + * Power off with a delay. Power off is triggered by check() after the delay. + */ + void Power::power_off_soon() { + lastPowerOn = millis() - SEC_TO_MS(POWER_TIMEOUT) + SEC_TO_MS(POWER_OFF_DELAY); + } + + #endif + #endif // AUTO_POWER_CONTROL + +#endif // PSU_CONTROL || AUTO_POWER_CONTROL diff --git a/Marlin/src/feature/power.h b/Marlin/src/feature/power.h index 8b988907e6..16f9dbcef5 100644 --- a/Marlin/src/feature/power.h +++ b/Marlin/src/feature/power.h @@ -24,17 +24,56 @@ /** * 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 -#include "../core/millis_t.h" +#if ANY(AUTO_POWER_CONTROL, POWER_OFF_TIMER, PSU_TRACK_STATE_MS) + #include "../core/millis_t.h" +#endif class Power { public: - static void check(); + static bool psu_on; + + static void init(); static void power_on(); static void power_off(); - private: - static millis_t lastPowerOn; - static bool is_power_needed(); + + #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; + static void setPowerOffTimer(const millis_t delay_ms); + #endif + #if ENABLED(POWER_OFF_WAIT_FOR_COOLDOWN) + static bool power_off_on_cooldown; + static void setPowerOffOnCooldown(const bool ena); + #endif + static void cancelAutoPowerOff(); + static void checkAutoPowerOff(); + #endif + + #if ENABLED(AUTO_POWER_CONTROL) && POWER_OFF_DELAY > 0 + static void power_off_soon(); + #else + static void power_off_soon() { power_off(); } + #endif + + #if ENABLED(AUTO_POWER_CONTROL) + static void check(const bool pause); + + private: + static millis_t lastPowerOn; + static bool is_power_needed(); + static bool is_cooling_needed(); + #elif ENABLED(POWER_OFF_WAIT_FOR_COOLDOWN) + private: + static bool is_cooling_needed(); + #endif }; extern Power powerManager; diff --git a/Marlin/src/feature/power_monitor.cpp b/Marlin/src/feature/power_monitor.cpp index af31d156fc..e3c3e58fc4 100644 --- a/Marlin/src/feature/power_monitor.cpp +++ b/Marlin/src/feature/power_monitor.cpp @@ -1,9 +1,9 @@ /** * Marlin 3D Printer Firmware - * Copyright (C) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] + * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] * * Based on Sprinter and grbl. - * Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm + * 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 @@ -26,8 +26,11 @@ #include "power_monitor.h" -#include "../lcd/ultralcd.h" -#include "../lcd/lcdprint.h" +#if HAS_MARLINUI_MENU + #include "../lcd/marlinui.h" + #include "../lcd/lcdprint.h" +#endif + #include "../libs/numtostr.h" uint8_t PowerMonitor::flags; // = 0 @@ -50,15 +53,15 @@ PowerMonitor power_monitor; // Single instance - this calls the constructor void PowerMonitor::draw_current() { const float amps = getAmps(); lcd_put_u8str(amps < 100 ? ftostr31ns(amps) : ui16tostr4rj((uint16_t)amps)); - lcd_put_wchar('A'); + lcd_put_u8str(F("A")); } #endif - #if HAS_POWER_MONITOR_VREF + #if ENABLED(POWER_MONITOR_VOLTAGE) void PowerMonitor::draw_voltage() { const float volts = getVolts(); lcd_put_u8str(volts < 100 ? ftostr31ns(volts) : ui16tostr4rj((uint16_t)volts)); - lcd_put_wchar('V'); + lcd_put_u8str(F("V")); } #endif @@ -66,7 +69,7 @@ PowerMonitor power_monitor; // Single instance - this calls the constructor void PowerMonitor::draw_power() { const float power = getPower(); lcd_put_u8str(power < 100 ? ftostr31ns(power) : ui16tostr4rj((uint16_t)power)); - lcd_put_wchar('W'); + lcd_put_u8str(F("W")); } #endif diff --git a/Marlin/src/feature/power_monitor.h b/Marlin/src/feature/power_monitor.h index a0eaf353f4..d57ef6fa67 100644 --- a/Marlin/src/feature/power_monitor.h +++ b/Marlin/src/feature/power_monitor.h @@ -1,9 +1,9 @@ /** * Marlin 3D Printer Firmware - * Copyright (C) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] + * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] * * Based on Sprinter and grbl. - * Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm + * 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 @@ -23,7 +23,7 @@ #include "../inc/MarlinConfig.h" -#define PM_SAMPLE_RANGE 1024 +#define PM_SAMPLE_RANGE HAL_ADC_RANGE #define PM_K_VALUE 6 #define PM_K_SCALE 6 @@ -32,10 +32,10 @@ struct pm_lpf_t { uint32_t filter_buf; float value; void add_sample(const uint16_t sample) { - filter_buf = filter_buf - (filter_buf >> K_VALUE) + (uint32_t(sample) << K_SCALE); + filter_buf += (uint32_t(sample) << K_SCALE) - (filter_buf >> K_VALUE); } void capture() { - value = filter_buf * (SCALE * (1.0f / (1UL << (PM_K_VALUE + PM_K_SCALE)))) + (POWER_MONITOR_CURRENT_OFFSET); + value = filter_buf * (SCALE * (1.0f / (1UL << (PM_K_VALUE + PM_K_SCALE)))); } void reset(uint16_t reset_value = 0) { filter_buf = uint32_t(reset_value) << (K_VALUE + K_SCALE); @@ -46,11 +46,11 @@ struct pm_lpf_t { class PowerMonitor { private: #if ENABLED(POWER_MONITOR_CURRENT) - static constexpr float amps_adc_scale = float(ADC_VREF) / (POWER_MONITOR_VOLTS_PER_AMP * PM_SAMPLE_RANGE); + static constexpr float amps_adc_scale = (float(ADC_VREF_MV) / 1000.0f) / (POWER_MONITOR_VOLTS_PER_AMP * PM_SAMPLE_RANGE); static pm_lpf_t amps; #endif #if ENABLED(POWER_MONITOR_VOLTAGE) - static constexpr float volts_adc_scale = float(ADC_VREF) / (POWER_MONITOR_VOLTS_PER_VOLT * PM_SAMPLE_RANGE); + static constexpr float volts_adc_scale = (float(ADC_VREF_MV) / 1000.0f) / (POWER_MONITOR_VOLTS_PER_VOLT * PM_SAMPLE_RANGE); static pm_lpf_t volts; #endif @@ -69,19 +69,15 @@ public: }; #if ENABLED(POWER_MONITOR_CURRENT) - FORCE_INLINE static float getAmps() { return amps.value; } + FORCE_INLINE static float getAmps() { return amps.value + (POWER_MONITOR_CURRENT_OFFSET); } void add_current_sample(const uint16_t value) { amps.add_sample(value); } #endif - #if HAS_POWER_MONITOR_VREF - #if ENABLED(POWER_MONITOR_VOLTAGE) - FORCE_INLINE static float getVolts() { return volts.value; } - #else - FORCE_INLINE static float getVolts() { return POWER_MONITOR_FIXED_VOLTAGE; } // using a specified fixed valtage as the voltage measurement - #endif - #if ENABLED(POWER_MONITOR_VOLTAGE) - void add_voltage_sample(const uint16_t value) { volts.add_sample(value); } - #endif + #if ENABLED(POWER_MONITOR_VOLTAGE) + FORCE_INLINE static float getVolts() { return volts.value + (POWER_MONITOR_VOLTAGE_OFFSET); } + void add_voltage_sample(const uint16_t value) { volts.add_sample(value); } + #else + FORCE_INLINE static float getVolts() { return POWER_MONITOR_FIXED_VOLTAGE; } #endif #if HAS_POWER_MONITOR_WATTS @@ -98,7 +94,7 @@ public: FORCE_INLINE static void set_current_display(const bool b) { SET_BIT_TO(flags, PM_DISP_BIT_I, b); } FORCE_INLINE static void toggle_current_display() { TBI(flags, PM_DISP_BIT_I); } #endif - #if HAS_POWER_MONITOR_VREF + #if ENABLED(POWER_MONITOR_VOLTAGE) static void draw_voltage(); FORCE_INLINE static bool voltage_display_enabled() { return TEST(flags, PM_DISP_BIT_V); } FORCE_INLINE static void set_voltage_display(const bool b) { SET_BIT_TO(flags, PM_DISP_BIT_V, b); } @@ -123,7 +119,7 @@ public: volts.reset(); #endif - #if ENABLED(SDSUPPORT) + #if HAS_MEDIA display_item_ms = 0; display_item = 0; #endif diff --git a/Marlin/src/feature/powerloss.cpp b/Marlin/src/feature/powerloss.cpp index e4bc605bb5..0dd19808bc 100644 --- a/Marlin/src/feature/powerloss.cpp +++ b/Marlin/src/feature/powerloss.cpp @@ -28,31 +28,47 @@ #if ENABLED(POWER_LOSS_RECOVERY) +#include "../inc/MarlinConfig.h" + #include "powerloss.h" -#include "../core/macros.h" -bool PrintJobRecovery::enabled; // Initialized by settings.load() +#if ENABLED(EXTENSIBLE_UI) + #include "../lcd/extui/ui_api.h" +#endif -SdFile PrintJobRecovery::file; +bool PrintJobRecovery::enabled; // Initialized by settings.load + +#if HAS_PLR_BED_THRESHOLD + celsius_t PrintJobRecovery::bed_temp_threshold; // Initialized by settings.load +#endif + +MediaFile PrintJobRecovery::file; job_recovery_info_t PrintJobRecovery::info; const char PrintJobRecovery::filename[5] = "/PLR"; uint8_t PrintJobRecovery::queue_index_r; uint32_t PrintJobRecovery::cmd_sdpos, // = 0 PrintJobRecovery::sdpos[BUFSIZE]; -#if ENABLED(DWIN_CREALITY_LCD) - bool PrintJobRecovery::dwin_flag; // = false +#if HAS_PLR_UI_FLAG + bool PrintJobRecovery::ui_flag_resume; // = false #endif #include "../sd/cardreader.h" -#include "../lcd/ultralcd.h" +#include "../lcd/marlinui.h" #include "../gcode/queue.h" #include "../gcode/gcode.h" #include "../module/motion.h" #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" @@ -63,24 +79,27 @@ uint32_t PrintJobRecovery::cmd_sdpos, // = 0 PrintJobRecovery recovery; -#ifndef POWER_LOSS_PURGE_LEN - #define POWER_LOSS_PURGE_LEN 0 -#endif -#ifndef POWER_LOSS_ZRAISE - #define POWER_LOSS_ZRAISE 2 // Move on loss with backup power, or on resume without it -#endif - #if DISABLED(BACKUP_POWER_SUPPLY) #undef POWER_LOSS_RETRACT_LEN // No retract at outage without backup power #endif #ifndef POWER_LOSS_RETRACT_LEN #define POWER_LOSS_RETRACT_LEN 0 #endif +#ifndef POWER_LOSS_PURGE_LEN + #define POWER_LOSS_PURGE_LEN 0 +#endif + +// Allow power-loss recovery to be aborted +#define PLR_CAN_ABORT +#define PROCESS_SUBCOMMANDS_NOW(cmd) do{ \ + if (TERN0(PLR_CAN_ABORT, card.flag.abort_sd_printing)) return; \ + gcode.process_subcommands_now(cmd); \ + }while(0) /** * Clear the recovery info */ -void PrintJobRecovery::init() { memset(&info, 0, sizeof(info)); } +void PrintJobRecovery::init() { info = { 0 }; } /** * Enable or disable then call changed() @@ -98,8 +117,9 @@ 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)); } /** @@ -107,13 +127,26 @@ void PrintJobRecovery::changed() { * * If a saved state exists send 'M1000 S' to initiate job recovery. */ -void PrintJobRecovery::check() { +bool PrintJobRecovery::check() { //if (!card.isMounted()) card.mount(); + bool success = false; if (card.isMounted()) { load(); - if (!valid()) return cancel(); - queue.inject_P(PSTR("M1000S")); + success = valid(); + if (!success) + cancel(); + else + queue.inject(F("M1000S")); } + return success; +} + +/** + * Cancel recovery + */ +void PrintJobRecovery::cancel() { + TERN_(PLR_HEAT_BED_ON_REBOOT, set_bed_temp(false)); + purge(); } /** @@ -133,21 +166,23 @@ void PrintJobRecovery::load() { (void)file.read(&info, sizeof(info)); close(); } - debug(PSTR("Load")); + debug(F("Load")); } /** * Set info fields that won't change */ void PrintJobRecovery::prepare() { - card.getAbsFilename(info.sd_filename); // SD filename + card.getAbsFilenameInCWD(info.sd_filename); // SD filename cmd_sdpos = 0; } /** * Save the current machine state to the power-loss recovery file */ -void PrintJobRecovery::save(const bool force/*=false*/, const float zraise/*=0*/) { +void PrintJobRecovery::save(const bool force/*=false*/, const float zraise/*=POWER_LOSS_ZRAISE*/, const bool raised/*=false*/) { + + // 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 @@ -175,41 +210,46 @@ void PrintJobRecovery::save(const bool force/*=false*/, const float zraise/*=0*/ // 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.current_position = current_position; + // 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_percentage = 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_POSITION_SHIFT, info.position_shift = position_shift); - info.feedrate = uint16_t(feedrate_mm_s * 60.0f); + TERN_(HAS_WORKSPACE_OFFSET, info.workspace_offset = workspace_offset); + E_TERN_(info.active_extruder = active_extruder); - #if HAS_MULTI_EXTRUDER - info.active_extruder = active_extruder; - #endif - - #if DISABLED(NO_VOLUMETRICS) - info.volumetric_enabled = parser.volumetric_enabled; + #if HAS_VOLUMETRIC_EXTRUSION + info.flag.volumetric_enabled = parser.volumetric_enabled; #if HAS_MULTI_EXTRUDER - for (int8_t e = 0; e < EXTRUDERS; e++) 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]; #endif #endif - #if EXTRUDERS - HOTEND_LOOP() info.target_temperature[e] = thermalManager.temp_hotend[e].target; + #if HAS_HOTEND + HOTEND_LOOP() info.target_temperature[e] = thermalManager.degTargetHotend(e); #endif - TERN_(HAS_HEATED_BED, info.target_temperature_bed = thermalManager.temp_bed.target); + TERN_(HAS_HEATED_BED, info.target_temperature_bed = thermalManager.degTargetBed()); - #if HAS_FAN - COPY(info.fan_speed, thermalManager.fan_speed); - #endif + TERN_(HAS_HEATED_CHAMBER, info.target_temperature_chamber = thermalManager.degTargetChamber()); + + TERN_(HAS_FAN, COPY(info.fan_speed, thermalManager.fan_speed)); #if HAS_LEVELING - info.leveling = planner.leveling_active; + info.flag.leveling = planner.leveling_active; info.fade = TERN0(ENABLE_LEVELING_FADE_HEIGHT, planner.z_fade_height); #endif @@ -220,12 +260,12 @@ void PrintJobRecovery::save(const bool force/*=false*/, const float zraise/*=0*/ info.retract_hop = fwretract.current_hop; #endif - // Relative axis modes - info.axis_relative = gcode.axis_relative; - // Elapsed print job time info.print_job_elapsed = print_job_timer.duration(); + // Relative axis modes + info.axis_relative = gcode.axis_relative; + // Misc. Marlin flags info.flag.dryrun = !!(marlin_debug_flags & MARLIN_DEBUG_DRYRUN); info.flag.allow_cold_extrusion = TERN0(PREVENT_COLD_EXTRUSION, thermalManager.allow_cold_extrude); @@ -238,23 +278,23 @@ void PrintJobRecovery::save(const bool force/*=false*/, const float zraise/*=0*/ #if ENABLED(BACKUP_POWER_SUPPLY) - void PrintJobRecovery::retract_and_lift(const float &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_P(PSTR("G1 F3000 E-" STRINGIFY(POWER_LOSS_RETRACT_LEN))); + const uint16_t old_flow = planner.flow_percentage[active_extruder]; + planner.set_flow(active_extruder, 100); + gcode.process_subcommands_now(F("G1F3000E-" STRINGIFY(POWER_LOSS_RETRACT_LEN))); + planner.set_flow(active_extruder, old_flow); #endif #if POWER_LOSS_ZRAISE // Raise the Z axis now - if (zraise) { - char cmd[20], str_1[16]; - sprintf_P(cmd, PSTR("G0 Z%s"), dtostrf(zraise, 1, 3, str_1)); - gcode.process_subcommands_now(cmd); - } + if (zraise) + gcode.process_subcommands_now(TS(F("G0Z"), p_float_t(zraise, 3))); #else UNUSED(zraise); #endif @@ -266,6 +306,10 @@ void PrintJobRecovery::save(const bool force/*=false*/, const float zraise/*=0*/ #endif +#endif // POWER_LOSS_PIN + +#if PIN_EXISTS(POWER_LOSS) || ENABLED(DEBUG_POWER_LOSS_RECOVERY) + /** * An outage was detected by a sensor pin. * - If not SD printing, let the machine turn off on its own with no "KILL" screen @@ -274,10 +318,10 @@ void PrintJobRecovery::save(const bool force/*=false*/, const float zraise/*=0*/ * - If backup power is available Retract E and Raise Z * - Go to the KILL screen */ - void PrintJobRecovery::_outage() { + void PrintJobRecovery::_outage(TERN_(DEBUG_POWER_LOSS_RECOVERY, const bool simulated/*=false*/)) { #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 @@ -288,8 +332,12 @@ void PrintJobRecovery::save(const bool force/*=false*/, const float zraise/*=0*/ constexpr float zraise = 0; #endif - // Save, including the limited Z raise - if (IS_SD_PRINTING()) save(true, zraise); + // Save the current position, distance that Z was (or should be) raised, + // and a flag whether the raise was already done here. + 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()); // Disable all heaters to reduce power loss thermalManager.disable_all_heaters(); @@ -301,17 +349,23 @@ void PrintJobRecovery::save(const bool force/*=false*/, const float zraise/*=0*/ retract_and_lift(zraise); #endif - kill(GET_TEXT(MSG_OUTAGE_RECOVERY)); + if (TERN0(DEBUG_POWER_LOSS_RECOVERY, simulated)) { + card.fileHasFinished(); + current_position.reset(); + sync_plan_position(); + } + else + marlin.kill(GET_TEXT_F(MSG_OUTAGE_RECOVERY)); } -#endif +#endif // POWER_LOSS_PIN || DEBUG_POWER_LOSS_RECOVERY /** * Save the recovery info the recovery file */ void PrintJobRecovery::write() { - debug(PSTR("Write")); + debug(F("Write")); open(false); file.seekSet(0); @@ -320,257 +374,310 @@ 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 */ void PrintJobRecovery::resume() { - - char cmd[MAX_CMD_SIZE+16], str_1[16], str_2[16]; - - const uint32_t resume_sdpos = info.sdpos; // Get here before the stepper ISR overwrites it + // Get these fields before any moves because stepper.cpp overwrites them + const xyze_pos_t resume_pos = info.current_position; + const uint32_t resume_sdpos = info.sdpos; // Apply the dry-run flag if enabled if (info.flag.dryrun) marlin_debug_flags |= MARLIN_DEBUG_DRYRUN; + #if ENABLED(DEBUG_POWER_LOSS_RECOVERY) + struct OnExit { + uint8_t old_flags; + OnExit() { + old_flags = marlin_debug_flags; + marlin_debug_flags |= MARLIN_DEBUG_ECHO; + } + ~OnExit() { marlin_debug_flags = old_flags; } + } on_exit; + #endif + // Restore cold extrusion permission TERN_(PREVENT_COLD_EXTRUSION, thermalManager.allow_cold_extrude = info.flag.allow_cold_extrusion); #if HAS_LEVELING // Make sure leveling is off before any G92 and G28 - gcode.process_subcommands_now_P(PSTR("M420 S0 Z0")); + PROCESS_SUBCOMMANDS_NOW(F("M420S0")); #endif - // Reset E, raise Z, home XY... - #if Z_HOME_DIR > 0 - - // If Z homing goes to max, just reset E and home all - gcode.process_subcommands_now_P(PSTR( - "G92.9 E0\n" - "G28R0" - )); - - #else // "G92.9 E0 ..." - - // Set Z to 0, raise Z by info.zraise, and Home (XY only for Cartesian) - // with no raise. (Only do simulated homing in Marlin Dev Mode.) - - sprintf_P(cmd, PSTR("G92.9 E0 " - #if ENABLED(BACKUP_POWER_SUPPLY) - "Z%s" // Z was already raised at outage - #else - "Z0\nG1Z%s" // Set Z=0 and Raise Z now - #endif - ), - dtostrf(info.zraise, 1, 3, str_1) - ); - gcode.process_subcommands_now(cmd); - - gcode.process_subcommands_now_P(PSTR( - "G28R0" // No raise during G28 - #if IS_CARTESIAN && DISABLED(POWER_LOSS_RECOVER_ZHOME) - "XY" // Don't home Z on Cartesian unless overridden - #endif - )); - - #endif - - // Pretend that all axes are homed - set_all_homed(); - - #if ENABLED(POWER_LOSS_RECOVER_ZHOME) - // Z has been homed so restore Z to ZsavedPos + POWER_LOSS_ZRAISE - sprintf_P(cmd, PSTR("G1 F500 Z%s"), dtostrf(info.current_position.z + POWER_LOSS_ZRAISE, 1, 3, str_1)); - gcode.process_subcommands_now(cmd); - #endif - - // Recover volumetric extrusion state - #if DISABLED(NO_VOLUMETRICS) - #if HAS_MULTI_EXTRUDER - for (int8_t e = 0; e < EXTRUDERS; e++) { - sprintf_P(cmd, PSTR("M200 T%i D%s"), e, dtostrf(info.filament_size[e], 1, 3, str_1)); - gcode.process_subcommands_now(cmd); - } - if (!info.volumetric_enabled) { - sprintf_P(cmd, PSTR("M200 T%i D0"), info.active_extruder); - gcode.process_subcommands_now(cmd); - } - #else - if (info.volumetric_enabled) { - sprintf_P(cmd, PSTR("M200 D%s"), dtostrf(info.filament_size[0], 1, 3, str_1)); - gcode.process_subcommands_now(cmd); - } - #endif + #if HAS_HEATED_CHAMBER + // Restore the chamber temperature + const celsius_t ct = info.target_temperature_chamber; + if (ct) PROCESS_SUBCOMMANDS_NOW(TS(F("M191S"), ct)); #endif #if HAS_HEATED_BED - const int16_t bt = info.target_temperature_bed; - if (bt) { - // Restore the bed temperature - sprintf_P(cmd, PSTR("M190 S%i"), bt); - gcode.process_subcommands_now(cmd); + // Restore the bed temperature + const celsius_t bt = info.target_temperature_bed; + if (bt) PROCESS_SUBCOMMANDS_NOW(TS(F("M190S"), bt)); + #endif + + // Heat hotend enough to soften material + #if HAS_HOTEND + HOTEND_LOOP() { + const celsius_t et = _MAX(info.target_temperature[e], 180); + if (et) { + TERN_(HAS_MULTI_HOTEND, PROCESS_SUBCOMMANDS_NOW(TS('T', e, 'S'))); + PROCESS_SUBCOMMANDS_NOW(TS(F("M109S"), et)); + } } #endif + // Interpret the saved Z according to flags + 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 + // establish the current position as best we can. + // + + PROCESS_SUBCOMMANDS_NOW(F("G92.9E0")); // Reset E to 0 + + #if Z_HOME_TO_MAX + + float z_now = z_raised; + + // If Z homing goes to max then just move back to the "raised" position + PROCESS_SUBCOMMANDS_NOW(TS( + F( "G28R0\n" // Home all axes (no raise) + "G1F1200Z") // Move Z down to (raised) height + , p_float_t(z_now, 3) + )); + + #elif DISABLED(BELTPRINTER) + + #if ENABLED(POWER_LOSS_RECOVER_ZHOME) && defined(POWER_LOSS_ZHOME_POS) + #define HOMING_Z_DOWN 1 + #endif + + float z_now = info.flag.raised ? z_raised : z_print; + + #if !HOMING_Z_DOWN + // Set Z to the real position + PROCESS_SUBCOMMANDS_NOW(TS(F("G92.9Z"), p_float_t(z_now, 3))); + #endif + + // Does Z need to be raised now? It should be raised before homing XY. + if (z_raised > z_now) { + z_now = z_raised; + PROCESS_SUBCOMMANDS_NOW(TS(F("G1F600Z"), p_float_t(z_now, 3))); + } + + // Home XY with no Z raise + PROCESS_SUBCOMMANDS_NOW(F("G28R0XY")); // No raise during G28 + + #endif + + #if HOMING_Z_DOWN + // Move to a safe XY position and home Z while avoiding the print. + const xy_pos_t p = xy_pos_t(POWER_LOSS_ZHOME_POS) TERN_(HOMING_Z_WITH_PROBE, - probe.offset_xy); + 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(); + + #if HAS_LEVELING + // Restore Z fade and possibly re-enable bed leveling compensation. + // Leveling may already be enabled due to the ENABLE_LEVELING_AFTER_G28 option. + // TODO: Add a G28 parameter to leave leveling disabled. + PROCESS_SUBCOMMANDS_NOW(TS(F("M420S"), '0' + (char)info.flag.leveling, 'Z', p_float_t(info.fade, 1))); + + #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, 3))); + #endif + #endif + + #if ENABLED(POWER_LOSS_RECOVER_ZHOME) + // Z was homed down to the bed, so move up to the raised height. + z_now = z_raised; + PROCESS_SUBCOMMANDS_NOW(TS(F("G1F600Z"), p_float_t(z_now, 3))); + #endif + + // Recover volumetric extrusion state + #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)); + #else + if (info.flag.volumetric_enabled) + PROCESS_SUBCOMMANDS_NOW(TS(F("M200D"), p_float_t(info.filament_size[0], 3))); + #endif + #endif + // Restore all hotend temperatures #if HAS_HOTEND HOTEND_LOOP() { - const int16_t et = info.target_temperature[e]; + const celsius_t et = info.target_temperature[e]; if (et) { - #if HAS_MULTI_HOTEND - sprintf_P(cmd, PSTR("T%i S"), e); - gcode.process_subcommands_now(cmd); - #endif - sprintf_P(cmd, PSTR("M109 S%i"), et); - gcode.process_subcommands_now(cmd); + TERN_(HAS_MULTI_HOTEND, PROCESS_SUBCOMMANDS_NOW(TS('T', e, 'S'))); + PROCESS_SUBCOMMANDS_NOW(TS(F("M109S"), et)); } } #endif - // Select the previously active tool (with no_move) - #if HAS_MULTI_EXTRUDER - sprintf_P(cmd, PSTR("T%i S"), info.active_extruder); - gcode.process_subcommands_now(cmd); + // Restore the previously active tool (with no_move) + #if HAS_MULTI_EXTRUDER || HAS_MULTI_HOTEND + PROCESS_SUBCOMMANDS_NOW(TS('T', info.active_extruder, 'S')); #endif // Restore print cooling fan speeds - FANS_LOOP(i) { - uint8_t f = info.fan_speed[i]; - if (f) { - sprintf_P(cmd, PSTR("M106 P%i S%i"), i, f); - gcode.process_subcommands_now(cmd); + #if HAS_FAN + FANS_LOOP(i) { + const int f = info.fan_speed[i]; + if (f) PROCESS_SUBCOMMANDS_NOW(TS(F("M106P"), i, 'S', f)); } - } + #endif - // Restore retract and hop state + // Restore retract and hop state from an active 'G10' command #if ENABLED(FWRETRACT) - LOOP_L_N(e, EXTRUDERS) { + EXTRUDER_LOOP() { if (info.retract[e] != 0.0) { fwretract.current_retract[e] = info.retract[e]; - fwretract.retracted[e] = true; + fwretract.retracted.set(e); } } fwretract.current_hop = info.retract_hop; #endif - #if HAS_LEVELING - // Restore leveling state before 'G92 Z' to ensure - // the Z stepper count corresponds to the native Z. - if (info.fade || info.leveling) { - sprintf_P(cmd, PSTR("M420 S%i Z%s"), int(info.leveling), dtostrf(info.fade, 1, 1, str_1)); - gcode.process_subcommands_now(cmd); - } - #endif - #if ENABLED(GRADIENT_MIX) memcpy(&mixer.gradient, &info.gradient, sizeof(info.gradient)); #endif // Un-retract if there was a retract at outage - #if POWER_LOSS_RETRACT_LEN - gcode.process_subcommands_now_P(PSTR("G1 E" STRINGIFY(POWER_LOSS_RETRACT_LEN) " F3000")); + #if ENABLED(BACKUP_POWER_SUPPLY) && POWER_LOSS_RETRACT_LEN > 0 + PROCESS_SUBCOMMANDS_NOW(F("G1F3000E" STRINGIFY(POWER_LOSS_RETRACT_LEN))); #endif - // Additional purge if configured + // Additional purge on resume if configured #if POWER_LOSS_PURGE_LEN - sprintf_P(cmd, PSTR("G1 E%d F200"), (POWER_LOSS_PURGE_LEN) + (POWER_LOSS_RETRACT_LEN)); - gcode.process_subcommands_now(cmd); + PROCESS_SUBCOMMANDS_NOW(TS(F("G1F3000E"), (POWER_LOSS_PURGE_LEN) + (POWER_LOSS_RETRACT_LEN))); #endif #if ENABLED(NOZZLE_CLEAN_FEATURE) - gcode.process_subcommands_now_P(PSTR("G12")); + PROCESS_SUBCOMMANDS_NOW(F("G12")); #endif - // Move back to the saved XY - sprintf_P(cmd, PSTR("G1 X%s Y%s F3000"), - dtostrf(info.current_position.x, 1, 3, str_1), - dtostrf(info.current_position.y, 1, 3, str_2) - ); - gcode.process_subcommands_now(cmd); + // Move back over to the saved XY + PROCESS_SUBCOMMANDS_NOW(TS( + F("G1F3000X"), p_float_t(resume_pos.x, 3), 'Y', p_float_t(resume_pos.y, 3) + )); - // Move back to the saved Z - dtostrf(info.current_position.z, 1, 3, str_1); - #if Z_HOME_DIR > 0 || ENABLED(POWER_LOSS_RECOVER_ZHOME) - sprintf_P(cmd, PSTR("G1 Z%s F200"), str_1); - #else - gcode.process_subcommands_now_P(PSTR("G1 Z0 F200")); - sprintf_P(cmd, PSTR("G92.9 Z%s"), str_1); - #endif - gcode.process_subcommands_now(cmd); + // Move back down to the saved Z for printing + PROCESS_SUBCOMMANDS_NOW(TS(F("G1F600Z"), p_float_t(z_print, 3))); - // Restore the feedrate - sprintf_P(cmd, PSTR("G1 F%d"), info.feedrate); - gcode.process_subcommands_now(cmd); + // Restore the feedrate and percentage + PROCESS_SUBCOMMANDS_NOW(TS(F("G1F"), info.feedrate)); + feedrate_percentage = info.feedrate_percentage; + + // Flowrate percentage + EXTRUDER_LOOP() planner.set_flow(e, info.flow_percentage[e]); // Restore E position with G92.9 - sprintf_P(cmd, PSTR("G92.9 E%s"), dtostrf(info.current_position.e, 1, 3, str_1)); - gcode.process_subcommands_now(cmd); + 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); // Relative axis modes gcode.axis_relative = info.axis_relative; - TERN_(HAS_HOME_OFFSET, home_offset = info.home_offset); - TERN_(HAS_POSITION_SHIFT, position_shift = info.position_shift); - #if HAS_HOME_OFFSET || HAS_POSITION_SHIFT - LOOP_XYZ(i) update_workspace_offset((AxisEnum)i); - #endif - - #if ENABLED(DEBUG_POWER_LOSS_RECOVERY) - const uint8_t old_flags = marlin_debug_flags; - marlin_debug_flags |= MARLIN_DEBUG_ECHO; - #endif - // Continue to apply PLR when a file is resumed! enable(true); // Resume the SD file from the last position - char *fn = info.sd_filename; - extern const char M23_STR[]; - sprintf_P(cmd, M23_STR, fn); - gcode.process_subcommands_now(cmd); - sprintf_P(cmd, PSTR("M24 S%ld T%ld"), resume_sdpos, info.print_job_elapsed); - gcode.process_subcommands_now(cmd); + PROCESS_SUBCOMMANDS_NOW(MString(F("M23 "), info.sd_filename)); + PROCESS_SUBCOMMANDS_NOW(TS(F("M24S"), resume_sdpos, 'T', info.print_job_elapsed)); - TERN_(DEBUG_POWER_LOSS_RECOVERY, marlin_debug_flags = old_flags); + #if ENABLED(SOVOL_SV06_RTS) + if (rts.print_state) rts.refreshTime(); + rts.start_print_flag = false; + #endif } #if ENABLED(DEBUG_POWER_LOSS_RECOVERY) - void PrintJobRecovery::debug(PGM_P const prefix) { - DEBUG_PRINT_P(prefix); - DEBUG_ECHOLNPAIR(" Job Recovery Info...\nvalid_head:", int(info.valid_head), " valid_foot:", int(info.valid_foot)); + void PrintJobRecovery::debug(FSTR_P const prefix) { + 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: "); - LOOP_XYZE(i) { + LOOP_LOGICAL_AXES(i) { if (i) DEBUG_CHAR(','); - DEBUG_DECIMAL(info.current_position[i]); + DEBUG_ECHO(info.current_position[i]); } DEBUG_EOL(); - DEBUG_ECHOLNPAIR("zraise: ", info.zraise); + 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); + for (uint8_t i = ind; i--;) + DEBUG_ECHOLNPGM("...", i, " sdpos: ", info.stored_repeat.get_marker_sdpos(i), " count: ", info.stored_repeat.get_marker_counter(i)); + #endif #if HAS_HOME_OFFSET DEBUG_ECHOPGM("home_offset: "); - LOOP_XYZ(i) { + LOOP_NUM_AXES(i) { if (i) DEBUG_CHAR(','); - DEBUG_DECIMAL(info.home_offset[i]); + DEBUG_ECHO(info.home_offset[i]); } DEBUG_EOL(); #endif - #if HAS_POSITION_SHIFT - DEBUG_ECHOPGM("position_shift: "); - LOOP_XYZ(i) { + #if HAS_WORKSPACE_OFFSET + DEBUG_ECHOPGM("workspace_offset: "); + LOOP_NUM_AXES(i) { if (i) DEBUG_CHAR(','); - DEBUG_DECIMAL(info.position_shift[i]); + DEBUG_ECHO(info.workspace_offset[i]); } DEBUG_EOL(); #endif - DEBUG_ECHOLNPAIR("feedrate: ", info.feedrate); - #if HAS_MULTI_EXTRUDER - DEBUG_ECHOLNPAIR("active_extruder: ", int(info.active_extruder)); + DEBUG_ECHOLNPGM("active_extruder: ", info.active_extruder); + #endif + + #if HAS_VOLUMETRIC_EXTRUSION + DEBUG_ECHOPGM("filament_size:"); + EXTRUDER_LOOP() DEBUG_ECHOLNPGM(" ", info.filament_size[e]); + DEBUG_EOL(); #endif #if HAS_HOTEND @@ -583,35 +690,59 @@ void PrintJobRecovery::resume() { #endif #if HAS_HEATED_BED - DEBUG_ECHOLNPAIR("target_temperature_bed: ", info.target_temperature_bed); + DEBUG_ECHOLNPGM("target_temperature_bed: ", info.target_temperature_bed); + #endif + + #if HAS_HEATED_CHAMBER + DEBUG_ECHOLNPGM("target_temperature_chamber: ", info.target_temperature_chamber); #endif #if HAS_FAN DEBUG_ECHOPGM("fan_speed: "); FANS_LOOP(i) { - DEBUG_ECHO(int(info.fan_speed[i])); + DEBUG_ECHO(info.fan_speed[i]); if (i < FAN_COUNT - 1) DEBUG_CHAR(','); } DEBUG_EOL(); #endif #if HAS_LEVELING - DEBUG_ECHOLNPAIR("leveling: ", int(info.leveling), " fade: ", info.fade); + DEBUG_ECHOLNPGM("leveling: ", info.flag.leveling ? "ON" : "OFF", " fade: ", info.fade); #endif + #if ENABLED(FWRETRACT) DEBUG_ECHOPGM("retract: "); - for (int8_t e = 0; e < EXTRUDERS; e++) { + EXTRUDER_LOOP() { DEBUG_ECHO(info.retract[e]); if (e < EXTRUDERS - 1) DEBUG_CHAR(','); } DEBUG_EOL(); - DEBUG_ECHOLNPAIR("retract_hop: ", info.retract_hop); + DEBUG_ECHOLNPGM("retract_hop: ", info.retract_hop); + #endif + + // Mixing extruder and gradient + #if ALL(MIXING_EXTRUDER, GRADIENT_MIX) + DEBUG_ECHOLNPGM("gradient: ", info.gradient.enabled ? "ON" : "OFF"); + #endif + + DEBUG_ECHOLNPGM("sd_filename: ", info.sd_filename); + DEBUG_ECHOLNPGM("sdpos: ", info.sdpos); + DEBUG_ECHOLNPGM("print_job_elapsed: ", info.print_job_elapsed); + + DEBUG_ECHOPGM("axis_relative:"); + if (TEST(info.axis_relative, REL_X)) DEBUG_ECHOPGM(" REL_X"); + if (TEST(info.axis_relative, REL_Y)) DEBUG_ECHOPGM(" REL_Y"); + if (TEST(info.axis_relative, REL_Z)) DEBUG_ECHOPGM(" REL_Z"); + if (TEST(info.axis_relative, REL_E)) DEBUG_ECHOPGM(" REL_E"); + if (TEST(info.axis_relative, E_MODE_ABS)) DEBUG_ECHOPGM(" E_MODE_ABS"); + if (TEST(info.axis_relative, E_MODE_REL)) DEBUG_ECHOPGM(" E_MODE_REL"); + DEBUG_EOL(); + + DEBUG_ECHOLNPGM("flag.dryrun: ", AS_DIGIT(info.flag.dryrun)); + DEBUG_ECHOLNPGM("flag.allow_cold_extrusion: ", AS_DIGIT(info.flag.allow_cold_extrusion)); + #if HAS_VOLUMETRIC_EXTRUSION + DEBUG_ECHOLNPGM("flag.volumetric_enabled: ", AS_DIGIT(info.flag.volumetric_enabled)); #endif - DEBUG_ECHOLNPAIR("sd_filename: ", info.sd_filename); - DEBUG_ECHOLNPAIR("sdpos: ", info.sdpos); - DEBUG_ECHOLNPAIR("print_job_elapsed: ", info.print_job_elapsed); - DEBUG_ECHOLNPAIR("dryrun: ", int(info.flag.dryrun)); - DEBUG_ECHOLNPAIR("allow_cold_extrusion: ", int(info.flag.allow_cold_extrusion)); } else DEBUG_ECHOLNPGM("INVALID DATA"); diff --git a/Marlin/src/feature/powerloss.h b/Marlin/src/feature/powerloss.h index e31b2ec915..bfa5b4717f 100644 --- a/Marlin/src/feature/powerloss.h +++ b/Marlin/src/feature/powerloss.h @@ -30,14 +30,29 @@ #include "../inc/MarlinConfig.h" +#if ENABLED(CANCEL_OBJECTS) + #include "cancel_object.h" +#endif + +#if ENABLED(GCODE_REPEAT_MARKERS) + #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) #define POWER_LOSS_STATE HIGH #endif +#if DISABLED(BACKUP_POWER_SUPPLY) + #undef POWER_LOSS_ZRAISE // No Z raise at outage without backup power +#endif +#ifndef POWER_LOSS_ZRAISE + #define POWER_LOSS_ZRAISE 2 // Default Z-raise on outage or resume +#endif + //#define DEBUG_POWER_LOSS_RECOVERY //#define SAVE_EACH_CMD_MODE //#define SAVE_INFO_INTERVAL_MS 0 @@ -47,40 +62,50 @@ 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; + #endif + #if HAS_HOME_OFFSET xyz_pos_t home_offset; #endif - #if HAS_POSITION_SHIFT - xyz_pos_t position_shift; + #if HAS_WORKSPACE_OFFSET + xyz_pos_t workspace_offset; #endif - - uint16_t feedrate; - #if HAS_MULTI_EXTRUDER uint8_t active_extruder; #endif - #if DISABLED(NO_VOLUMETRICS) - bool volumetric_enabled; + #if HAS_VOLUMETRIC_EXTRUSION float filament_size[EXTRUDERS]; #endif #if HAS_HOTEND - int16_t target_temperature[HOTENDS]; + celsius_t target_temperature[HOTENDS]; #endif - #if HAS_HEATED_BED - int16_t target_temperature_bed; + celsius_t target_temperature_bed; + #endif + #if HAS_HEATED_CHAMBER + celsius_t target_temperature_chamber; #endif - #if HAS_FAN uint8_t fan_speed[FAN_COUNT]; #endif #if HAS_LEVELING - bool leveling; float fade; #endif @@ -97,9 +122,6 @@ typedef struct { #endif #endif - // Relative axis modes - uint8_t axis_relative; - // SD Filename and position char sd_filename[MAXPATHNAMELENGTH]; volatile uint32_t sdpos; @@ -107,10 +129,20 @@ typedef struct { // Job elapsed time millis_t print_job_elapsed; + // Relative axis modes + relative_t axis_relative; + // Misc. Marlin flags struct { + bool raised:1; // Raised before saved bool dryrun:1; // M111 S8 bool allow_cold_extrusion:1; // M302 P1 + #if HAS_LEVELING + bool leveling:1; // M420 S + #endif + #if HAS_VOLUMETRIC_EXTRUSION + bool volumetric_enabled:1; // M200 S D + #endif } flag; uint8_t valid_foot; @@ -123,28 +155,29 @@ class PrintJobRecovery { public: static const char filename[5]; - static SdFile file; + static MediaFile file; static job_recovery_info_t info; static uint8_t queue_index_r; //!< Queue index of the active command static uint32_t cmd_sdpos, //!< SD position of the next command sdpos[BUFSIZE]; //!< SD positions of queued commands - #if ENABLED(DWIN_CREALITY_LCD) - 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(); static void prepare(); - static inline void setup() { + static void setup() { + #if PIN_EXISTS(OUTAGECON) + OUT_WRITE(OUTAGECON_PIN, HIGH); + #endif #if PIN_EXISTS(POWER_LOSS) - #if ENABLED(POWER_LOSS_PULL) - #if POWER_LOSS_STATE == LOW - SET_INPUT_PULLUP(POWER_LOSS_PIN); - #else - SET_INPUT_PULLDOWN(POWER_LOSS_PIN); - #endif + #if ENABLED(POWER_LOSS_PULLUP) + SET_INPUT_PULLUP(POWER_LOSS_PIN); + #elif ENABLED(POWER_LOSS_PULLDOWN) + SET_INPUT_PULLDOWN(POWER_LOSS_PIN); #else SET_INPUT(POWER_LOSS_PIN); #endif @@ -152,54 +185,69 @@ class PrintJobRecovery { } // Track each command's file offsets - static inline uint32_t command_sdpos() { return sdpos[queue_index_r]; } - static inline void commit_sdpos(const uint8_t index_w) { sdpos[index_w] = cmd_sdpos; } + static uint32_t command_sdpos() { return sdpos[queue_index_r]; } + static void commit_sdpos(const uint8_t index_w) { sdpos[index_w] = cmd_sdpos; } static bool enabled; static void enable(const bool onoff); static void changed(); - static inline bool exists() { return card.jobRecoverFileExists(); } - static inline void open(const bool read) { card.openJobRecoveryFile(read); } - static inline void close() { file.close(); } + #if HAS_PLR_BED_THRESHOLD + static celsius_t bed_temp_threshold; + #endif + + static bool exists() { return card.jobRecoverFileExists(); } + static void open(const bool read) { card.openJobRecoveryFile(read); } + 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 check(); static void resume(); static void purge(); - static inline void cancel() { purge(); card.autostart_index = 0; } + static void cancel(); static void load(); - static void save(const bool force=ENABLED(SAVE_EACH_CMD_MODE), const float zraise=0); + static void save(const bool force=ENABLED(SAVE_EACH_CMD_MODE), const float zraise=POWER_LOSS_ZRAISE, const bool raised=false); #if PIN_EXISTS(POWER_LOSS) - static inline void outage() { - if (enabled && READ(POWER_LOSS_PIN) == POWER_LOSS_STATE) - _outage(); + static void outage() { + static constexpr uint8_t OUTAGE_THRESHOLD = 3; + static uint8_t outage_counter = 0; + if (enabled && READ(POWER_LOSS_PIN) == POWER_LOSS_STATE) { + outage_counter++; + if (outage_counter >= OUTAGE_THRESHOLD) _outage(); + } + else + outage_counter = 0; } #endif // The referenced file exists - static inline bool interrupted_file_exists() { return card.fileExists(info.sd_filename); } + static bool interrupted_file_exists() { return card.fileExists(info.sd_filename); } - static inline bool valid() { return info.valid() && interrupted_file_exists(); } + static bool valid() { return info.valid() && interrupted_file_exists(); } #if ENABLED(DEBUG_POWER_LOSS_RECOVERY) - static void debug(PGM_P const prefix); + static void debug(FSTR_P const prefix); #else - static inline void debug(PGM_P const) {} + static void debug(FSTR_P const) {} #endif private: static void write(); #if ENABLED(BACKUP_POWER_SUPPLY) - static void retract_and_lift(const float &zraise); + static void retract_and_lift(const float zraise); #endif - #if PIN_EXISTS(POWER_LOSS) + #if PIN_EXISTS(POWER_LOSS) || ENABLED(DEBUG_POWER_LOSS_RECOVERY) friend class GcodeSuite; - static void _outage(); + static void _outage(TERN_(DEBUG_POWER_LOSS_RECOVERY, const bool simulated=false)); #endif }; diff --git a/Marlin/src/feature/probe_temp_comp.cpp b/Marlin/src/feature/probe_temp_comp.cpp index af8039d8b1..7dfa28f4b9 100644 --- a/Marlin/src/feature/probe_temp_comp.cpp +++ b/Marlin/src/feature/probe_temp_comp.cpp @@ -22,43 +22,57 @@ #include "../inc/MarlinConfigPre.h" -#if ENABLED(PROBE_TEMP_COMPENSATION) +#if HAS_PTC + +//#define DEBUG_PTC // Print extra debug output with 'M871' #include "probe_temp_comp.h" #include +#include "../module/temperature.h" -ProbeTempComp temp_comp; +ProbeTempComp ptc; -int16_t ProbeTempComp::z_offsets_probe[cali_info_init[TSI_PROBE].measurements], // = {0} - ProbeTempComp::z_offsets_bed[cali_info_init[TSI_BED].measurements]; // = {0} +#if ENABLED(PTC_PROBE) + constexpr int16_t z_offsets_probe_default[PTC_PROBE_COUNT] = PTC_PROBE_ZOFFS; + int16_t ProbeTempComp::z_offsets_probe[PTC_PROBE_COUNT] = PTC_PROBE_ZOFFS; +#endif -#if ENABLED(USE_TEMP_EXT_COMPENSATION) - int16_t ProbeTempComp::z_offsets_ext[cali_info_init[TSI_EXT].measurements]; // = {0} +#if ENABLED(PTC_BED) + constexpr int16_t z_offsets_bed_default[PTC_BED_COUNT] = PTC_BED_ZOFFS; + int16_t ProbeTempComp::z_offsets_bed[PTC_BED_COUNT] = PTC_BED_ZOFFS; +#endif + +#if ENABLED(PTC_HOTEND) + constexpr int16_t z_offsets_hotend_default[PTC_HOTEND_COUNT] = PTC_HOTEND_ZOFFS; + int16_t ProbeTempComp::z_offsets_hotend[PTC_HOTEND_COUNT] = PTC_HOTEND_ZOFFS; #endif int16_t *ProbeTempComp::sensor_z_offsets[TSI_COUNT] = { - ProbeTempComp::z_offsets_probe, ProbeTempComp::z_offsets_bed - #if ENABLED(USE_TEMP_EXT_COMPENSATION) - , ProbeTempComp::z_offsets_ext + #if ENABLED(PTC_PROBE) + ProbeTempComp::z_offsets_probe, + #endif + #if ENABLED(PTC_BED) + ProbeTempComp::z_offsets_bed, + #endif + #if ENABLED(PTC_HOTEND) + ProbeTempComp::z_offsets_hotend, #endif }; -const temp_calib_t ProbeTempComp::cali_info[TSI_COUNT] = { - cali_info_init[TSI_PROBE], cali_info_init[TSI_BED] - #if ENABLED(USE_TEMP_EXT_COMPENSATION) - , cali_info_init[TSI_EXT] - #endif -}; - -constexpr xyz_pos_t ProbeTempComp::park_point; -constexpr xy_pos_t ProbeTempComp::measure_point; -constexpr int ProbeTempComp::probe_calib_bed_temp; +constexpr temp_calib_t ProbeTempComp::cali_info[TSI_COUNT]; uint8_t ProbeTempComp::calib_idx; // = 0 float ProbeTempComp::init_measurement; // = 0.0 +bool ProbeTempComp::enabled = true; + +void ProbeTempComp::reset() { + TERN_(PTC_PROBE, for (uint8_t i = 0; i < PTC_PROBE_COUNT; ++i) z_offsets_probe[i] = z_offsets_probe_default[i]); + TERN_(PTC_BED, for (uint8_t i = 0; i < PTC_BED_COUNT; ++i) z_offsets_bed[i] = z_offsets_bed_default[i]); + TERN_(PTC_HOTEND, for (uint8_t i = 0; i < PTC_HOTEND_COUNT; ++i) z_offsets_hotend[i] = z_offsets_hotend_default[i]); +} void ProbeTempComp::clear_offsets(const TempSensorID tsi) { - LOOP_L_N(i, cali_info[tsi].measurements) + for (uint8_t i = 0; i < cali_info[tsi].measurements; ++i) sensor_z_offsets[tsi][i] = 0; calib_idx = 0; } @@ -70,70 +84,63 @@ bool ProbeTempComp::set_offset(const TempSensorID tsi, const uint8_t idx, const } void ProbeTempComp::print_offsets() { - LOOP_L_N(s, TSI_COUNT) { - float temp = cali_info[s].start_temp; + for (uint8_t s = 0; s < TSI_COUNT; ++s) { + celsius_t temp = cali_info[s].start_temp; for (int16_t i = -1; i < cali_info[s].measurements; ++i) { - serialprintPGM(s == TSI_BED ? PSTR("Bed") : - #if ENABLED(USE_TEMP_EXT_COMPENSATION) - s == TSI_EXT ? PSTR("Extruder") : - #endif - PSTR("Probe") + SERIAL_ECHOLN( + TERN_(PTC_BED, s == TSI_BED ? F("Bed") :) TERN_(PTC_HOTEND, s == TSI_EXT ? F("Extruder") :) F("Probe"), + F(" temp: "), temp, F("C; Offset: "), i < 0 ? 0.0f : sensor_z_offsets[s][i], F(" um") ); - SERIAL_ECHOLNPAIR( - " temp: ", temp, - "C; Offset: ", i < 0 ? 0.0f : sensor_z_offsets[s][i], " um" - ); - temp += cali_info[s].temp_res; + temp += cali_info[s].temp_resolution; } } + #if ENABLED(DEBUG_PTC) + float meas[4] = { 0, 0, 0, 0 }; + compensate_measurement(TSI_PROBE, 27.5, meas[0]); + compensate_measurement(TSI_PROBE, 32.5, meas[1]); + compensate_measurement(TSI_PROBE, 77.5, meas[2]); + compensate_measurement(TSI_PROBE, 82.5, meas[3]); + SERIAL_ECHOLNPGM("DEBUG_PTC 27.5:", meas[0], " 32.5:", meas[1], " 77.5:", meas[2], " 82.5:", meas[3]); + #endif } -void ProbeTempComp::prepare_new_calibration(const float &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 &meas_z) { - switch (tsi) { - case TSI_PROBE: - case TSI_BED: - //case TSI_EXT: - if (calib_idx >= cali_info[tsi].measurements) return; - sensor_z_offsets[tsi][calib_idx++] = static_cast(meas_z * 1000.0f - init_measurement * 1000.0f); - default: break; - } +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); } bool ProbeTempComp::finish_calibration(const TempSensorID tsi) { - if (tsi != TSI_PROBE && tsi != TSI_BED) return false; - - if (calib_idx < 3) { - SERIAL_ECHOLNPGM("!Insufficient measurements (min. 3)."); + if (!calib_idx) { + SERIAL_ECHOLNPGM("!No measurements."); clear_offsets(tsi); return false; } const uint8_t measurements = cali_info[tsi].measurements; - const float start_temp = cali_info[tsi].start_temp, - res_temp = cali_info[tsi].temp_res; + const celsius_t start_temp = cali_info[tsi].start_temp, + res_temp = cali_info[tsi].temp_resolution; int16_t * const data = sensor_z_offsets[tsi]; // Extrapolate float k, d; if (calib_idx < measurements) { - SERIAL_ECHOLNPAIR("Got ", calib_idx, " measurements. "); + SERIAL_ECHOLNPGM("Got ", calib_idx, " measurements. "); if (linear_regression(tsi, k, d)) { SERIAL_ECHOPGM("Applying linear extrapolation"); - calib_idx--; for (; calib_idx < measurements; ++calib_idx) { - const float temp = start_temp + float(calib_idx) * res_temp; + const celsius_float_t temp = start_temp + float(calib_idx + 1) * res_temp; data[calib_idx] = static_cast(k * temp + d); } } else { // Simply use the last measured value for higher temperatures SERIAL_ECHOPGM("Failed to extrapolate"); - const int16_t last_val = data[calib_idx]; + const int16_t last_val = data[calib_idx-1]; for (; calib_idx < measurements; ++calib_idx) data[calib_idx] = last_val; } @@ -143,15 +150,15 @@ bool ProbeTempComp::finish_calibration(const TempSensorID tsi) { // Sanity check for (calib_idx = 0; calib_idx < measurements; ++calib_idx) { // Restrict the max. offset - if (abs(data[calib_idx]) > 2000) { + if (ABS(data[calib_idx]) > 2000) { SERIAL_ECHOLNPGM("!Invalid Z-offset detected (0-2)."); clear_offsets(tsi); return false; } // Restrict the max. offset difference between two probings - if (calib_idx > 0 && abs(data[calib_idx - 1] - data[calib_idx]) > 800) { + if (calib_idx > 0 && ABS(data[calib_idx - 1] - data[calib_idx]) > 800) { SERIAL_ECHOLNPGM("!Invalid Z-offset between two probings detected (0-0.8)."); - clear_offsets(TSI_PROBE); + clear_offsets(tsi); return false; } } @@ -159,65 +166,70 @@ bool ProbeTempComp::finish_calibration(const TempSensorID tsi) { return true; } -void ProbeTempComp::compensate_measurement(const TempSensorID tsi, const float &temp, float &meas_z) { - if (WITHIN(temp, cali_info[tsi].start_temp, cali_info[tsi].end_temp)) - meas_z -= get_offset_for_temperature(tsi, temp); +void ProbeTempComp::apply_compensation(float &meas_z) { + if (!enabled) return; + TERN_(PTC_BED, compensate_measurement(TSI_BED, thermalManager.degBed(), meas_z)); + TERN_(PTC_PROBE, compensate_measurement(TSI_PROBE, thermalManager.degProbe(), meas_z)); + TERN_(PTC_HOTEND, compensate_measurement(TSI_EXT, thermalManager.degHotend(0), meas_z)); } -float ProbeTempComp::get_offset_for_temperature(const TempSensorID tsi, const float &temp) { +void ProbeTempComp::compensate_measurement(const TempSensorID tsi, const celsius_t temp, float &meas_z) { const uint8_t measurements = cali_info[tsi].measurements; - const float start_temp = cali_info[tsi].start_temp, - res_temp = cali_info[tsi].temp_res; + const celsius_t start_temp = cali_info[tsi].start_temp, + res_temp = cali_info[tsi].temp_resolution, + end_temp = start_temp + measurements * res_temp; const int16_t * const data = sensor_z_offsets[tsi]; - auto point = [&](uint8_t i) { - return xy_float_t({start_temp + i*res_temp, static_cast(data[i])}); + // Given a data index, return { celsius, zoffset } in the form { x, y } + auto tpoint = [&](uint8_t i) -> xy_float_t { + return xy_float_t({ static_cast(start_temp) + i * res_temp, i ? static_cast(data[i - 1]) : 0.0f }); }; - auto linear_interp = [](float x, xy_float_t p1, xy_float_t p2) { - return (p2.y - p1.y) / (p2.x - p2.y) * (x - p1.x) + p1.y; + // Interpolate Z based on a temperature being within a given range + 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); }; - // Linear interpolation - uint8_t idx = static_cast((temp - start_temp) / res_temp); - - // offset in um + // offset in Β΅m float offset = 0.0f; - #if !defined(PTC_LINEAR_EXTRAPOLATION) || PTC_LINEAR_EXTRAPOLATION <= 0 - if (idx < 0) - offset = 0.0f; - else if (idx > measurements - 2) - offset = static_cast(data[measurements - 1]); + #if PTC_LINEAR_EXTRAPOLATION + if (temp < start_temp) + offset = linear_interp(temp, tpoint(0), tpoint(PTC_LINEAR_EXTRAPOLATION)); + else if (temp >= end_temp) + offset = linear_interp(temp, tpoint(measurements - PTC_LINEAR_EXTRAPOLATION), tpoint(measurements)); #else - if (idx < 0) - offset = linear_interp(temp, point(0), point(PTC_LINEAR_EXTRAPOLATION)); - else if (idx > measurements - 2) - offset = linear_interp(temp, point(measurements - PTC_LINEAR_EXTRAPOLATION - 1), point(measurements - 1)); + if (temp < start_temp) + offset = 0.0f; + else if (temp >= end_temp) + offset = static_cast(data[measurements - 1]); #endif - else - offset = linear_interp(temp, point(idx), point(idx + 1)); + else { + // Linear interpolation + const int8_t idx = static_cast((temp - start_temp) / res_temp); + offset = linear_interp(temp, tpoint(idx), tpoint(idx + 1)); + } - // return offset in mm - return offset / 1000.0f; + // convert offset to mm and apply it + meas_z -= offset * 0.001f; } bool ProbeTempComp::linear_regression(const TempSensorID tsi, float &k, float &d) { - if (tsi != TSI_PROBE && tsi != TSI_BED) return false; + if (!WITHIN(calib_idx, 1, cali_info[tsi].measurements)) return false; - if (!WITHIN(calib_idx, 2, cali_info[tsi].measurements)) return false; - - const float start_temp = cali_info[tsi].start_temp, - res_temp = cali_info[tsi].temp_res; + const celsius_t start_temp = cali_info[tsi].start_temp, + res_temp = cali_info[tsi].temp_resolution; const int16_t * const data = sensor_z_offsets[tsi]; float sum_x = start_temp, sum_x2 = sq(start_temp), sum_xy = 0, sum_y = 0; - LOOP_L_N(i, calib_idx) { - const float xi = start_temp + (i + 1) * res_temp, - yi = static_cast(data[i]); + float xi = static_cast(start_temp); + for (uint8_t i = 0; i < calib_idx; ++i) { + const float yi = static_cast(data[i]); + xi += res_temp; sum_x += xi; sum_x2 += sq(xi); sum_xy += xi * yi; @@ -237,4 +249,4 @@ bool ProbeTempComp::linear_regression(const TempSensorID tsi, float &k, float &d return true; } -#endif // PROBE_TEMP_COMPENSATION +#endif // HAS_PTC diff --git a/Marlin/src/feature/probe_temp_comp.h b/Marlin/src/feature/probe_temp_comp.h index 626dd87f94..e6509a20cf 100644 --- a/Marlin/src/feature/probe_temp_comp.h +++ b/Marlin/src/feature/probe_temp_comp.h @@ -24,19 +24,22 @@ #include "../inc/MarlinConfig.h" enum TempSensorID : uint8_t { - TSI_PROBE, - TSI_BED, - #if ENABLED(USE_TEMP_EXT_COMPENSATION) + #if ENABLED(PTC_PROBE) + TSI_PROBE, + #endif + #if ENABLED(PTC_BED) + TSI_BED, + #endif + #if ENABLED(PTC_HOTEND) TSI_EXT, #endif TSI_COUNT }; typedef struct { - uint8_t measurements; // Max. number of measurements to be stored (35 - 80Β°C) - float temp_res, // Resolution in Β°C between measurements - start_temp, // Base measurement; z-offset == 0 - end_temp; + uint8_t measurements; // Max. number of measurements to be stored (35 - 80Β°C) + celsius_t temp_resolution, // Resolution in Β°C between measurements + start_temp; // Base measurement; z-offset == 0 } temp_calib_t; /** @@ -45,89 +48,55 @@ typedef struct { * measurement errors/shifts due to changed temperature. */ -// Probe temperature calibration constants -#ifndef PTC_SAMPLE_COUNT - #define PTC_SAMPLE_COUNT 10U -#endif -#ifndef PTC_SAMPLE_RES - #define PTC_SAMPLE_RES 5.0f -#endif -#ifndef PTC_SAMPLE_START - #define PTC_SAMPLE_START 30.0f -#endif -#define PTC_SAMPLE_END ((PTC_SAMPLE_START) + (PTC_SAMPLE_COUNT) * (PTC_SAMPLE_RES)) - -// Bed temperature calibration constants -#ifndef BTC_PROBE_TEMP - #define BTC_PROBE_TEMP 30.0f -#endif -#ifndef BTC_SAMPLE_COUNT - #define BTC_SAMPLE_COUNT 10U -#endif -#ifndef BTC_SAMPLE_STEP - #define BTC_SAMPLE_RES 5.0f -#endif -#ifndef BTC_SAMPLE_START - #define BTC_SAMPLE_START 60.0f -#endif -#define BTC_SAMPLE_END ((BTC_SAMPLE_START) + (BTC_SAMPLE_COUNT) * (BTC_SAMPLE_RES)) - -#ifndef PTC_PROBE_HEATING_OFFSET - #define PTC_PROBE_HEATING_OFFSET 0.5f -#endif - -#ifndef PTC_PROBE_RAISE - #define PTC_PROBE_RAISE 10.0f -#endif - -static constexpr temp_calib_t cali_info_init[TSI_COUNT] = { - { PTC_SAMPLE_COUNT, PTC_SAMPLE_RES, PTC_SAMPLE_START, PTC_SAMPLE_END }, // Probe - { BTC_SAMPLE_COUNT, BTC_SAMPLE_RES, BTC_SAMPLE_START, BTC_SAMPLE_END }, // Bed - #if ENABLED(USE_TEMP_EXT_COMPENSATION) - { 20, 5, 180, 180 + 5 * 20 } // Extruder - #endif -}; - class ProbeTempComp { public: - static const temp_calib_t cali_info[TSI_COUNT]; + static constexpr temp_calib_t cali_info[TSI_COUNT] = { + #if ENABLED(PTC_PROBE) + { PTC_PROBE_COUNT, PTC_PROBE_RES, PTC_PROBE_START }, // Probe + #endif + #if ENABLED(PTC_BED) + { PTC_BED_COUNT, PTC_BED_RES, PTC_BED_START }, // Bed + #endif + #if ENABLED(PTC_HOTEND) + { PTC_HOTEND_COUNT, PTC_HOTEND_RES, PTC_HOTEND_START }, // Extruder + #endif + }; - // Where to park nozzle to wait for probe cooldown - static constexpr xyz_pos_t park_point = PTC_PARK_POS; - - // XY coordinates of nozzle for probing the bed - static constexpr xy_pos_t measure_point = PTC_PROBE_POS; // Coordinates to probe - //measure_point = { 12.0f, 7.3f }; // Coordinates for the MK52 magnetic heatbed - - static constexpr int probe_calib_bed_temp = BED_MAX_TARGET, // Bed temperature while calibrating probe - bed_calib_probe_temp = BTC_PROBE_TEMP; // Probe temperature while calibrating bed - - static int16_t *sensor_z_offsets[TSI_COUNT], - z_offsets_probe[cali_info_init[TSI_PROBE].measurements], // (Β΅m) - z_offsets_bed[cali_info_init[TSI_BED].measurements]; // (Β΅m) - - #if ENABLED(USE_TEMP_EXT_COMPENSATION) - static int16_t z_offsets_ext[cali_info_init[TSI_EXT].measurements]; // (Β΅m) + static int16_t *sensor_z_offsets[TSI_COUNT]; + #if ENABLED(PTC_PROBE) + static int16_t z_offsets_probe[PTC_PROBE_COUNT]; // (Β΅m) + #endif + #if ENABLED(PTC_BED) + static int16_t z_offsets_bed[PTC_BED_COUNT]; // (Β΅m) + #endif + #if ENABLED(PTC_HOTEND) + static int16_t z_offsets_hotend[PTC_HOTEND_COUNT]; // (Β΅m) #endif - static inline void reset_index() { calib_idx = 0; }; - static inline uint8_t get_index() { return calib_idx; } - static void clear_offsets(const TempSensorID tsi); - static inline void clear_all_offsets() { - clear_offsets(TSI_BED); - clear_offsets(TSI_PROBE); - TERN_(USE_TEMP_EXT_COMPENSATION, clear_offsets(TSI_EXT)); + static void reset_index() { calib_idx = 0; }; + static uint8_t get_index() { return calib_idx; } + static void reset(); + static void clear_all_offsets() { + TERN_(PTC_PROBE, clear_offsets(TSI_PROBE)); + TERN_(PTC_BED, clear_offsets(TSI_BED)); + TERN_(PTC_HOTEND, clear_offsets(TSI_EXT)); } 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 &init_meas_z); - static void push_back_new_measurement(const TempSensorID tsi, const float &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 compensate_measurement(const TempSensorID tsi, const float &temp, float &meas_z); + static void set_enabled(const bool ena) { enabled = ena; } + + // Apply all temperature compensation adjustments + static void apply_compensation(float &meas_z); private: static uint8_t calib_idx; + static bool enabled; + + static void clear_offsets(const TempSensorID tsi); /** * Base value. Temperature compensation values will be deltas @@ -135,13 +104,13 @@ class ProbeTempComp { */ static float init_measurement; - static float get_offset_for_temperature(const TempSensorID tsi, const float &temp); - /** * Fit a linear function in measured temperature offsets * to allow generating values of higher temperatures. */ static bool linear_regression(const TempSensorID tsi, float &k, float &d); + + static void compensate_measurement(const TempSensorID tsi, const celsius_t temp, float &meas_z); }; -extern ProbeTempComp temp_comp; +extern ProbeTempComp ptc; diff --git a/Marlin/src/feature/repeat.cpp b/Marlin/src/feature/repeat.cpp new file mode 100644 index 0000000000..4484dab95b --- /dev/null +++ b/Marlin/src/feature/repeat.cpp @@ -0,0 +1,82 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2020 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(GCODE_REPEAT_MARKERS) + +//#define DEBUG_GCODE_REPEAT_MARKERS + +#include "repeat.h" + +#include "../gcode/gcode.h" +#include "../sd/cardreader.h" + +#define DEBUG_OUT ENABLED(DEBUG_GCODE_REPEAT_MARKERS) +#include "../core/debug_out.h" + +repeat_marker_t Repeat::marker[MAX_REPEAT_NESTING]; +uint8_t Repeat::index; + +void Repeat::add_marker(const uint32_t sdpos, const uint16_t count) { + if (index >= MAX_REPEAT_NESTING) + SERIAL_ECHO_MSG("!Too many markers."); + else { + marker[index].sdpos = sdpos; + marker[index].counter = count ? count - 1 : -1; + index++; + DEBUG_ECHOLNPGM("Add Marker ", index, " at ", sdpos, " (", count, ")"); + } +} + +void Repeat::loop() { + if (!index) // No marker? + SERIAL_ECHO_MSG("!No marker set."); // Inform the user. + else { + const uint8_t ind = index - 1; // Active marker's index + if (!marker[ind].counter) { // Did its counter run out? + DEBUG_ECHOLNPGM("Pass Marker ", index); + index--; // Carry on. Previous marker on the next 'M808'. + } + else { + card.setIndex(marker[ind].sdpos); // Loop back to the marker. + if (marker[ind].counter > 0) // Ignore a negative (or zero) counter. + --marker[ind].counter; // Decrement the counter. If zero this 'M808' will be skipped next time. + DEBUG_ECHOLNPGM("Goto Marker ", index, " at ", marker[ind].sdpos, " (", marker[ind].counter, ")"); + } + } +} + +void Repeat::cancel() { for (uint8_t i = 0; i < index; ++i) marker[i].counter = 0; } + +void Repeat::early_parse_M808(char * const cmd) { + if (is_command_M808(cmd)) { + DEBUG_ECHOLNPGM("Parsing \"", cmd, "\""); + parser.parse(cmd); + if (parser.seen('L')) + add_marker(card.getIndex(), parser.value_ushort()); + else + Repeat::loop(); + } +} + +#endif // GCODE_REPEAT_MARKERS diff --git a/Marlin/src/feature/repeat.h b/Marlin/src/feature/repeat.h new file mode 100644 index 0000000000..ce309f6470 --- /dev/null +++ b/Marlin/src/feature/repeat.h @@ -0,0 +1,56 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2020 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 "../gcode/parser.h" + +#include + +#define MAX_REPEAT_NESTING 10 + +typedef struct { + uint32_t sdpos; // The repeat file position + int16_t counter; // The counter for looping +} repeat_marker_t; + +class Repeat { +private: + static repeat_marker_t marker[MAX_REPEAT_NESTING]; + static uint8_t index; +public: + static void reset() { index = 0; } + static bool is_active() { + for (uint8_t i = 0; i < index; ++i) if (marker[i].counter) return true; + return false; + } + static bool is_command_M808(char * const cmd) { return cmd[0] == 'M' && cmd[1] == '8' && cmd[2] == '0' && cmd[3] == '8' && !NUMERIC(cmd[4]); } + static void early_parse_M808(char * const cmd); + static void add_marker(const uint32_t sdpos, const uint16_t count); + static void loop(); + static void cancel(); + static uint8_t count() { return index; } + static int16_t get_marker_counter(const uint8_t i) { return marker[i].counter; } + static uint32_t get_marker_sdpos(const uint8_t i) { return marker[i].sdpos; } +}; + +extern Repeat repeat; diff --git a/Marlin/src/feature/rs485.cpp b/Marlin/src/feature/rs485.cpp new file mode 100644 index 0000000000..8009185450 --- /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 0000000000..3327626a3c --- /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 71f31f2145..f0b94d0de8 100644 --- a/Marlin/src/feature/runout.cpp +++ b/Marlin/src/feature/runout.cpp @@ -33,31 +33,37 @@ 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 #endif #if ENABLED(TOOLCHANGE_MIGRATION_FEATURE) - //#define DEBUG_TOOLCHANGE_MIGRATION_FEATURE #include "../module/tool_change.h" + #define DEBUG_OUT ENABLED(DEBUG_TOOLCHANGE_MIGRATION_FEATURE) + #include "../core/debug_out.h" #endif #if HAS_FILAMENT_RUNOUT_DISTANCE float RunoutResponseDelayed::runout_distance_mm = FILAMENT_RUNOUT_DISTANCE_MM; - volatile float RunoutResponseDelayed::runout_mm_countdown[EXTRUDERS]; + countdown_t RunoutResponseDelayed::mm_countdown; #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; // = 0 + int8_t RunoutResponseDebounced::runout_count[NUM_RUNOUT_SENSORS]; // = 0 #endif // // Filament Runout event handler // #include "../MarlinCore.h" +#include "pause.h" #include "../gcode/queue.h" #if ENABLED(HOST_ACTION_COMMANDS) @@ -68,72 +74,84 @@ bool FilamentMonitorBase::enabled = true, #include "../lcd/extui/ui_api.h" #endif -void event_filament_runout() { +void event_filament_runout(const uint8_t extruder) { - if (TERN0(ADVANCED_PAUSE_FEATURE, did_pause_print)) return; // Action already in progress. Purge triggered repeated runout. + 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) if (migration.in_progress) { - #if ENABLED(DEBUG_TOOLCHANGE_MIGRATION_FEATURE) - SERIAL_ECHOLN("Migration Already In Progress"); - #endif + DEBUG_ECHOLNPGM("Migration Already In Progress"); return; // Action already in progress. Purge triggered repeated runout. } if (migration.automode) { - #if ENABLED(DEBUG_TOOLCHANGE_MIGRATION_FEATURE) - SERIAL_ECHOLN("Migration Starting"); - #endif + DEBUG_ECHOLNPGM("Migration Starting"); if (extruder_migration()) return; } #endif - TERN_(EXTENSIBLE_UI, ExtUI::onFilamentRunout(ExtUI::getActiveTool())); + TERN_(EXTENSIBLE_UI, ExtUI::onFilamentRunout(ExtUI::getTool(extruder))); - #if EITHER(HOST_PROMPT_SUPPORT, HOST_ACTION_COMMANDS) - const char tool = '0' - #if NUM_RUNOUT_SENSORS > 1 - + active_extruder - #endif - ; + #if ANY(HOST_PROMPT_SUPPORT, HOST_ACTION_COMMANDS, MULTI_FILAMENT_SENSOR) + const char tool = '0' + TERN0(MULTI_FILAMENT_SENSOR, extruder); #endif //action:out_of_filament #if ENABLED(HOST_PROMPT_SUPPORT) - host_action_prompt_begin(PROMPT_FILAMENT_RUNOUT, PSTR("FilamentRunout T"), tool); - host_action_prompt_show(); + hostui.prompt_do(PROMPT_FILAMENT_RUNOUT, F("FilamentRunout T"), tool); //action:out_of_filament #endif const bool run_runout_script = !runout.host_handling; #if ENABLED(HOST_ACTION_COMMANDS) - if (run_runout_script - && ( strstr(FILAMENT_RUNOUT_SCRIPT, "M600") + + const bool park_or_pause = (false + #ifdef FILAMENT_RUNOUT_SCRIPT + || strstr(FILAMENT_RUNOUT_SCRIPT, "M600") || strstr(FILAMENT_RUNOUT_SCRIPT, "M125") - #if ENABLED(ADVANCED_PAUSE_FEATURE) - || strstr(FILAMENT_RUNOUT_SCRIPT, "M25") - #endif - ) - ) { - host_action_paused(false); + || TERN0(ADVANCED_PAUSE_FEATURE, strstr(FILAMENT_RUNOUT_SCRIPT, "M25")) + #endif + ); + + if (run_runout_script && park_or_pause) { + hostui.paused(false); } else { // Legacy Repetier command for use until newer version supports standard dialog // To be removed later when pause command also triggers dialog #ifdef ACTION_ON_FILAMENT_RUNOUT - host_action(PSTR(ACTION_ON_FILAMENT_RUNOUT " T"), false); + hostui.action(F(ACTION_ON_FILAMENT_RUNOUT " T"), false); SERIAL_CHAR(tool); SERIAL_EOL(); #endif - host_action_pause(false); + hostui.pause(false); } SERIAL_ECHOPGM(" " ACTION_REASON_ON_FILAMENT_RUNOUT " "); SERIAL_CHAR(tool); SERIAL_EOL(); + #endif // HOST_ACTION_COMMANDS - if (run_runout_script) - queue.inject_P(PSTR(FILAMENT_RUNOUT_SCRIPT)); + #ifdef FILAMENT_RUNOUT_SCRIPT + if (run_runout_script) { + #if MULTI_FILAMENT_SENSOR + MString script; + script.setf(F(FILAMENT_RUNOUT_SCRIPT), C(tool)); + #if ENABLED(FILAMENT_RUNOUT_SENSOR_DEBUG) + SERIAL_ECHOLNPGM("Runout Command: ", &script); + #endif + queue.inject(&script); + #else + #if ENABLED(FILAMENT_RUNOUT_SENSOR_DEBUG) + SERIAL_ECHOPGM("Runout Command: "); + SERIAL_ECHOLNPGM(FILAMENT_RUNOUT_SCRIPT); + #endif + queue.inject(F(FILAMENT_RUNOUT_SCRIPT)); + #endif + } + #endif } #endif // HAS_FILAMENT_SENSOR diff --git a/Marlin/src/feature/runout.h b/Marlin/src/feature/runout.h index a7f8366849..7881af4e38 100644 --- a/Marlin/src/feature/runout.h +++ b/Marlin/src/feature/runout.h @@ -30,6 +30,7 @@ #include "../module/planner.h" #include "../module/stepper.h" // for block_t #include "../gcode/queue.h" +#include "pause.h" // for did_pause_print #include "../inc/MarlinConfig.h" @@ -37,21 +38,36 @@ #include "../lcd/extui/ui_api.h" #endif -#if ENABLED(ADVANCED_PAUSE_FEATURE) - #include "pause.h" -#endif - //#define FILAMENT_RUNOUT_SENSOR_DEBUG #ifndef FILAMENT_RUNOUT_THRESHOLD #define FILAMENT_RUNOUT_THRESHOLD 5 #endif -void event_filament_runout(); +#if ENABLED(FILAMENT_MOTION_SENSOR) + #define HAS_FILAMENT_MOTION 1 + #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 + #else + NUM_RUNOUT_SENSORS + #endif + > runout_flags_t; + +void event_filament_runout(const uint8_t extruder); +inline bool should_monitor_runout() { return did_pause_print || marlin.printingIsActive(); } template class TFilamentMonitor; -class FilamentSensorEncoder; -class FilamentSensorSwitch; +class FilamentSensor; class RunoutResponseDelayed; class RunoutResponseDebounced; @@ -59,7 +75,7 @@ class RunoutResponseDebounced; typedef TFilamentMonitor< TERN(HAS_FILAMENT_RUNOUT_DISTANCE, RunoutResponseDelayed, RunoutResponseDebounced), - TERN(FILAMENT_MOTION_SENSOR, FilamentSensorEncoder, FilamentSensorSwitch) + FilamentSensor > FilamentMonitor; extern FilamentMonitor runout; @@ -86,30 +102,38 @@ class TFilamentMonitor : public FilamentMonitorBase { static sensor_t sensor; public: - static inline void setup() { + static void setup() { sensor.setup(); reset(); } - static inline void reset() { + static void reset() { filament_ran_out = false; response.reset(); } // Call this method when filament is present, // so the response can reset its counter. - static inline void filament_present(const uint8_t extruder) { + static void filament_present(const uint8_t extruder) { response.filament_present(extruder); } + #if ENABLED(FILAMENT_SWITCH_AND_MOTION) + 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; } + #endif #if HAS_FILAMENT_RUNOUT_DISTANCE - static inline float& runout_distance() { return response.runout_distance_mm; } - static inline void set_runout_distance(const float &mm) { response.runout_distance_mm = mm; } + static float& runout_distance() { return response.runout_distance_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. - static inline void block_completed(const block_t* const b) { + // Called from ISR context! + static void block_completed(const block_t * const b) { if (enabled) { response.block_completed(b); sensor.block_completed(b); @@ -117,22 +141,45 @@ class TFilamentMonitor : public FilamentMonitorBase { } // Give the response a chance to update its counter. - static inline void run() { - if ( enabled && !filament_ran_out - && (printingIsActive() || TERN0(ADVANCED_PAUSE_FEATURE, did_pause_print)) - ) { - TERN_(HAS_FILAMENT_RUNOUT_DISTANCE, cli()); // Prevent RunoutResponseDelayed::block_completed from accumulating here - response.run(); - sensor.run(); - const bool ran_out = response.has_run_out(); - TERN_(HAS_FILAMENT_RUNOUT_DISTANCE, sei()); - if (ran_out) { - filament_ran_out = true; - event_filament_runout(); - planner.synchronize(); - } - } + static void run() { + 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 = runout_flags[active_extruder]; // suppress non active extruders + uint8_t extruder = active_extruder; + #endif + #else + const bool ran_out = bool(runout_flags); + uint8_t extruder = active_extruder; + #endif + + if (!ran_out) return; + + #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 ***************************/ @@ -143,44 +190,62 @@ class FilamentSensorBase { * Called by FilamentSensorSwitch::run when filament is detected. * Called by FilamentSensorEncoder::block_completed when motion is detected. */ - static inline void filament_present(const uint8_t extruder) { + static void filament_present(const uint8_t extruder) { runout.filament_present(extruder); // ...which calls response.filament_present(extruder) } + #if ENABLED(FILAMENT_SWITCH_AND_MOTION) + static void filament_motion_present(const uint8_t extruder) { + runout.filament_motion_present(extruder); // ...which calls response.filament_motion_present(extruder) + } + #endif public: - static inline void setup() { - #if ENABLED(FIL_RUNOUT_PULLUP) - #define INIT_RUNOUT_PIN(P) SET_INPUT_PULLUP(P) - #elif ENABLED(FIL_RUNOUT_PULLDOWN) - #define INIT_RUNOUT_PIN(P) SET_INPUT_PULLDOWN(P) - #else - #define INIT_RUNOUT_PIN(P) SET_INPUT(P) - #endif - - #define _INIT_RUNOUT(N) INIT_RUNOUT_PIN(FIL_RUNOUT##N##_PIN); - REPEAT_S(1, INCREMENT(NUM_RUNOUT_SENSORS), _INIT_RUNOUT) - #undef _INIT_RUNOUT + static void setup() { + #define _INIT_RUNOUT_PIN(P,S,U,D) do{ if (ENABLED(U)) SET_INPUT_PULLUP(P); else if (ENABLED(D)) SET_INPUT_PULLDOWN(P); else SET_INPUT(P); }while(0); + #define INIT_RUNOUT_PIN(N) _INIT_RUNOUT_PIN(FIL_RUNOUT##N##_PIN, FIL_RUNOUT##N##_STATE, FIL_RUNOUT##N##_PULLUP, FIL_RUNOUT##N##_PULLDOWN); + REPEAT_1(NUM_RUNOUT_SENSORS, INIT_RUNOUT_PIN) #undef INIT_RUNOUT_PIN + + #if ENABLED(FILAMENT_SWITCH_AND_MOTION) + #define INIT_MOTION_PIN(N) _INIT_RUNOUT_PIN(FIL_MOTION##N##_PIN, FIL_MOTION##N##_STATE, FIL_MOTION##N##_PULLUP, FIL_MOTION##N##_PULLDOWN); + REPEAT_1(NUM_MOTION_SENSORS, INIT_MOTION_PIN) + #undef INIT_MOTION_PIN + #endif + #undef _INIT_RUNOUT_PIN } // Return a bitmask of runout pin states - static inline uint8_t poll_runout_pins() { + static uint8_t poll_runout_pins() { #define _OR_RUNOUT(N) | (READ(FIL_RUNOUT##N##_PIN) ? _BV((N) - 1) : 0) - return (0 REPEAT_S(1, INCREMENT(NUM_RUNOUT_SENSORS), _OR_RUNOUT)); + return (0 REPEAT_1(NUM_RUNOUT_SENSORS, _OR_RUNOUT)); #undef _OR_RUNOUT } // Return a bitmask of runout flag states (1 bits always indicates runout) - static inline uint8_t poll_runout_states() { - return poll_runout_pins() - #if FIL_RUNOUT_STATE == LOW - ^ uint8_t(_BV(NUM_RUNOUT_SENSORS) - 1) - #endif - ; + static uint8_t poll_runout_states() { + #define _INVERT_BIT(N) | (FIL_RUNOUT##N##_STATE ? 0 : _BV(N - 1)) + return poll_runout_pins() ^ uint8_t(0 REPEAT_1(NUM_RUNOUT_SENSORS, _INVERT_BIT)); + #undef _INVERT_BIT } + + #if ENABLED(FILAMENT_SWITCH_AND_MOTION) + // Return a bitmask of motion pin states + static uint8_t poll_motion_pins() { + #define _OR_MOTION(N) | (READ(FIL_MOTION##N##_PIN) ? _BV((N) - 1) : 0) + return (0 REPEAT_1(NUM_MOTION_SENSORS, _OR_MOTION)); + #undef _OR_MOTION + } + + // Return a bitmask of motion flag states (1 bits always indicates runout) + static uint8_t poll_motion_states() { + #define _OR_MOTION(N) | (FIL_MOTION##N##_STATE ? 0 : _BV(N - 1)) + return poll_motion_pins() ^ uint8_t(0 REPEAT_1(NUM_MOTION_SENSORS, _OR_MOTION)); + #undef _OR_MOTION + } + #endif }; -#if ENABLED(FILAMENT_MOTION_SENSOR) +#if HAS_FILAMENT_MOTION /** * This sensor uses a magnetic encoder disc and a Hall effect @@ -192,16 +257,16 @@ class FilamentSensorBase { private: static uint8_t motion_detected; - static inline void poll_motion_sensor() { + static void poll_motion_sensor() { static uint8_t old_state; - const uint8_t new_state = poll_runout_pins(), + const uint8_t new_state = TERN(FILAMENT_SWITCH_AND_MOTION, poll_motion_pins, poll_runout_pins)(), change = old_state ^ new_state; old_state = new_state; - #ifdef FILAMENT_RUNOUT_SENSOR_DEBUG + #if ENABLED(FILAMENT_RUNOUT_SENSOR_DEBUG) if (change) { SERIAL_ECHOPGM("Motion detected:"); - LOOP_L_N(e, NUM_RUNOUT_SENSORS) + for (uint8_t e = 0; e < TERN(FILAMENT_SWITCH_AND_MOTION, NUM_MOTION_SENSORS, NUM_RUNOUT_SENSORS); ++e) if (TEST(change, e)) SERIAL_CHAR(' ', '0' + e); SERIAL_EOL(); } @@ -211,20 +276,23 @@ class FilamentSensorBase { } public: - static inline void block_completed(const block_t* const b) { + // 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. if (TEST(motion_detected, b->extruder)) - filament_present(b->extruder); + TERN(FILAMENT_SWITCH_AND_MOTION, filament_motion_present, filament_present)(b->extruder); // Clear motion triggers for next block motion_detected = 0; } - static inline void run() { poll_motion_sensor(); } + static void run() { poll_motion_sensor(); } }; -#else +#endif // HAS_FILAMENT_MOTION + +#if HAS_FILAMENT_SWITCH /** * This is a simple endstop switch in the path of the filament. @@ -232,88 +300,208 @@ class FilamentSensorBase { */ class FilamentSensorSwitch : public FilamentSensorBase { private: - static inline bool poll_runout_state(const uint8_t extruder) { + static bool poll_runout_state(const uint8_t extruder) { const uint8_t runout_states = poll_runout_states(); - #if NUM_RUNOUT_SENSORS == 1 - UNUSED(extruder); - #else - if ( !TERN0(DUAL_X_CARRIAGE, dxc_is_duplicating()) + #if MULTI_FILAMENT_SENSOR + if ( !TERN0(DUAL_X_CARRIAGE, idex_is_duplicating()) && !TERN0(MULTI_NOZZLE_DUPLICATION, extruder_duplication_enabled) ) return TEST(runout_states, extruder); // A specific extruder ran out + #else + UNUSED(extruder); #endif return !!runout_states; // Any extruder ran out } public: - static inline void block_completed(const block_t* const) {} + // Called from ISR context to indicate a block was completed + static void block_completed(const block_t * const) {} - static inline void run() { - const bool out = poll_runout_state(active_extruder); - if (!out) filament_present(active_extruder); - #ifdef FILAMENT_RUNOUT_SENSOR_DEBUG - static bool was_out = false; - if (out != was_out) { - was_out = out; - SERIAL_ECHOPGM("Filament "); - serialprintPGM(out ? PSTR("OUT\n") : PSTR("IN\n")); - } - #endif + 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); + #if ENABLED(FILAMENT_RUNOUT_SENSOR_DEBUG) + static uint8_t was_out; // = 0 + if (out != TEST(was_out, s)) { + TBI(was_out, s); + SERIAL_ECHOLN(F("Filament Sensor "), AS_DIGIT(s), out ? F(" OUT") : F(" IN")); + } + #endif + } } }; + #endif // HAS_FILAMENT_SWITCH -#endif // !FILAMENT_MOTION_SENSOR + /** + * This is a simple endstop switch in the path of the filament. + * It can detect filament runout, but not stripouts or jams. + */ + class FilamentSensor : public FilamentSensorBase { + private: + TERN_(HAS_FILAMENT_MOTION, static FilamentSensorEncoder encoder_sensor); + 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)); + } + + static void run() { + TERN_(HAS_FILAMENT_MOTION, encoder_sensor.run()); + TERN_(HAS_FILAMENT_SWITCH, switch_sensor.run()); + } + }; /********************************* RESPONSE TYPE *********************************/ #if HAS_FILAMENT_RUNOUT_DISTANCE + typedef struct { + float runout[NUM_RUNOUT_SENSORS]; + Flags runout_reset; // Reset runout later + #if ENABLED(FILAMENT_SWITCH_AND_MOTION) + float motion[NUM_MOTION_SENSORS]; + Flags motion_reset; // Reset motion later + #endif + } countdown_t; + // RunoutResponseDelayed triggers a runout event only if the length // of filament specified by FILAMENT_RUNOUT_DISTANCE_MM has been fed // during a runout condition. class RunoutResponseDelayed { private: - static volatile float runout_mm_countdown[EXTRUDERS]; + 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; - static inline void reset() { - LOOP_L_N(i, EXTRUDERS) filament_present(i); + #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 inline void run() { - #ifdef FILAMENT_RUNOUT_SENSOR_DEBUG + 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; - LOOP_L_N(i, EXTRUDERS) { - serialprintPGM(i ? PSTR(", ") : PSTR("Remaining mm: ")); - SERIAL_ECHO(runout_mm_countdown[i]); + if (PENDING(ms, t)) return; + t = ms + 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(); + #endif + } + + // Get runout status for all presence sensors and motion sensors + static runout_flags_t has_run_out() { + runout_flags_t runout_flags{0}; + + #if ENABLED(FILAMENT_SWITCH_AND_MOTION) + // 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! + } } - SERIAL_EOL(); + } + #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; + } + + static void filament_present(const uint8_t extruder) { + if (mm_countdown.runout[extruder] < runout_distance_mm || did_pause_print) { + // Reset runout only if it is smaller than runout_distance or printing is paused. + // On Bowden systems retract may be larger than runout_distance_mm, so if retract + // was added leave it in place, or the following unretract will cause runout event. + mm_countdown.runout[extruder] = runout_distance_mm; + mm_countdown.runout_reset.clear(extruder); + } + else { + // If runout is larger than runout distance, we cannot reset right now, as Bowden and retract + // distance larger than runout_distance_mm leads to negative runout right after unretract. + // But we cannot ignore filament_present event. After unretract, runout will become smaller + // than runout_distance_mm and should be reset after that. So activate delayed reset. + mm_countdown.runout_reset.set(extruder); + } + } + + #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] < motion_distance_mm || did_pause_print) { + mm_countdown.motion[extruder] = motion_distance_mm; + mm_countdown.motion_reset.clear(extruder); + } + else + mm_countdown.motion_reset.set(extruder); + } + #endif + + // Called from ISR context to indicate a block was completed + static void block_completed(const block_t * const b) { + // 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) + // 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 } - static inline bool has_run_out() { - return runout_mm_countdown[active_extruder] < 0; - } - - static inline void filament_present(const uint8_t extruder) { - runout_mm_countdown[extruder] = runout_distance_mm; - } - - static inline void block_completed(const block_t* const b) { - if (b->steps.x || b->steps.y || b->steps.z - || TERN0(ADVANCED_PAUSE_FEATURE, did_pause_print) // Allow pause purge move to re-trigger runout state - ) { - // Only trigger on extrusion with XYZ movement to allow filament change and retract/recover. - const uint8_t e = b->extruder; - const int32_t steps = b->steps.e; - runout_mm_countdown[e] -= (TEST(b->direction_bits, E_AXIS) ? -steps : steps) * planner.steps_to_mm[E_AXIS_N(e)]; - } + // 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 } }; @@ -325,13 +513,32 @@ class FilamentSensorBase { class RunoutResponseDebounced { private: static constexpr int8_t runout_threshold = FILAMENT_RUNOUT_THRESHOLD; - static int8_t runout_count; + static int8_t runout_count[NUM_RUNOUT_SENSORS]; + public: - static inline void reset() { runout_count = runout_threshold; } - static inline void run() { if (runout_count >= 0) runout_count--; } - static inline bool has_run_out() { return runout_count < 0; } - static inline void block_completed(const block_t* const) { } - static inline void filament_present(const uint8_t) { runout_count = runout_threshold; } + static void reset() { + for (uint8_t i = 0; i < NUM_RUNOUT_SENSORS; ++i) filament_present(i); + } + + static void run() { + for (uint8_t i = 0; i < NUM_RUNOUT_SENSORS; ++i) if (runout_count[i] >= 0) runout_count[i]--; + } + + static runout_flags_t has_run_out() { + runout_flags_t runout_flags{0}; + for (uint8_t i = 0; i < NUM_RUNOUT_SENSORS; ++i) if (runout_count[i] < 0) runout_flags.set(i); + 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 8646ec872f..cc4522e30a 100644 --- a/Marlin/src/feature/solenoid.cpp +++ b/Marlin/src/feature/solenoid.cpp @@ -22,77 +22,34 @@ #include "../inc/MarlinConfig.h" -#if EITHER(EXT_SOLENOID, MANUAL_SOLENOID_CONTROL) +#if ANY(EXT_SOLENOID, MANUAL_SOLENOID_CONTROL) #include "solenoid.h" #include "../module/motion.h" // for active_extruder - -#if ENABLED(MANUAL_SOLENOID_CONTROL) - #define HAS_SOLENOID(N) HAS_SOLENOID_##N -#else - #define HAS_SOLENOID(N) (HAS_SOLENOID_##N && EXTRUDERS > N) -#endif +#include "../module/tool_change.h" // for parking_extruder_set_parked // Used primarily with MANUAL_SOLENOID_CONTROL -static void set_solenoid(const uint8_t num, const bool active) { - const uint8_t value = active ? HIGH : LOW; +static void set_solenoid(const uint8_t num, const uint8_t state) { + #define _SOL_CASE(N) case N: TERN_(HAS_SOLENOID_##N, OUT_WRITE(SOL##N##_PIN, state)); break; switch (num) { - case 0: - OUT_WRITE(SOL0_PIN, value); - break; - #if HAS_SOLENOID(1) - case 1: - OUT_WRITE(SOL1_PIN, value); - break; - #endif - #if HAS_SOLENOID(2) - case 2: - OUT_WRITE(SOL2_PIN, value); - break; - #endif - #if HAS_SOLENOID(3) - case 3: - OUT_WRITE(SOL3_PIN, value); - break; - #endif - #if HAS_SOLENOID(4) - case 4: - OUT_WRITE(SOL4_PIN, value); - break; - #endif - #if HAS_SOLENOID(5) - case 5: - OUT_WRITE(SOL5_PIN, value); - break; - #endif - default: - SERIAL_ECHO_MSG(STR_INVALID_SOLENOID); - break; + REPEAT(8, _SOL_CASE) + default: SERIAL_ECHO_MSG(STR_INVALID_SOLENOID); break; } + + #if ENABLED(PARKING_EXTRUDER) + if (state == LOW && active_extruder == num) // If active extruder's solenoid is disabled, carriage is considered parked + parking_extruder_set_parked(true); + #endif } -void enable_solenoid(const uint8_t num) { set_solenoid(num, true); } -void disable_solenoid(const uint8_t num) { set_solenoid(num, false); } -void enable_solenoid_on_active_extruder() { enable_solenoid(active_extruder); } +// PARKING_EXTRUDER options alter the default behavior of solenoids to ensure compliance of M380-381 +void enable_solenoid(const uint8_t num) { set_solenoid(num, TERN1(PARKING_EXTRUDER, PE_MAGNET_ON_STATE)); } +void disable_solenoid(const uint8_t num) { set_solenoid(num, TERN0(PARKING_EXTRUDER, !PE_MAGNET_ON_STATE)); } void disable_all_solenoids() { - disable_solenoid(0); - #if HAS_SOLENOID(1) - disable_solenoid(1); - #endif - #if HAS_SOLENOID(2) - disable_solenoid(2); - #endif - #if HAS_SOLENOID(3) - disable_solenoid(3); - #endif - #if HAS_SOLENOID(4) - disable_solenoid(4); - #endif - #if HAS_SOLENOID(5) - disable_solenoid(5); - #endif + #define _SOL_DISABLE(N) TERN_(HAS_SOLENOID_##N, disable_solenoid(N)); + REPEAT(8, _SOL_DISABLE) } #endif // EXT_SOLENOID || MANUAL_SOLENOID_CONTROL diff --git a/Marlin/src/feature/solenoid.h b/Marlin/src/feature/solenoid.h index 2ba4983fb0..3131aeb868 100644 --- a/Marlin/src/feature/solenoid.h +++ b/Marlin/src/feature/solenoid.h @@ -21,7 +21,6 @@ */ #pragma once -void enable_solenoid_on_active_extruder(); void disable_all_solenoids(); void enable_solenoid(const uint8_t num); void disable_solenoid(const uint8_t num); diff --git a/Marlin/src/feature/spindle_laser.cpp b/Marlin/src/feature/spindle_laser.cpp index bc387a9334..dd8d859bde 100644 --- a/Marlin/src/feature/spindle_laser.cpp +++ b/Marlin/src/feature/spindle_laser.cpp @@ -30,89 +30,175 @@ #include "spindle_laser.h" -SpindleLaser cutter; -uint8_t SpindleLaser::power; -bool SpindleLaser::isReady; // Ready to apply power setting from the UI to OCR -cutter_power_t SpindleLaser::menuPower, // Power set via LCD menu in PWM, PERCENT, or RPM - SpindleLaser::unitPower; // LCD status power in PWM, PERCENT, or RPM - -#if ENABLED(MARLIN_DEV_MODE) - cutter_frequency_t SpindleLaser::frequency; // PWM frequency setting; range: 2K - 50K +#if ENABLED(SPINDLE_SERVO) + #include "../module/servo.h" #endif -#define SPINDLE_LASER_PWM_OFF ((SPINDLE_LASER_PWM_INVERT) ? 255 : 0) -// -// Init the cutter to a safe OFF state -// +#if ENABLED(I2C_AMMETER) + #include "ammeter.h" +#endif + +SpindleLaser cutter; +bool SpindleLaser::enable_state; // Virtual enable state, controls enable pin if present and or apply power if > 0 +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 + feedRate_t SpindleLaser::feedrate_mm_m = 1500, + SpindleLaser::last_feedrate_mm_m; // = 0 // (mm/min) Track feedrate changes for dynamic power +#endif + +bool SpindleLaser::isReadyForUI = false; // Ready to apply power setting from the UI to OCR +CutterMode SpindleLaser::cutter_mode = CUTTER_MODE_STANDARD; // Default is standard mode + +constexpr cutter_cpower_t SpindleLaser::power_floor; +cutter_power_t SpindleLaser::menuPower = 0, // Power value via LCD menu in PWM, PERCENT, or RPM based on configured format set by CUTTER_POWER_UNIT. + SpindleLaser::unitPower = 0; // Unit power is in PWM, PERCENT, or RPM based on CUTTER_POWER_UNIT. + +cutter_frequency_t SpindleLaser::frequency; // PWM frequency setting; range: 2K - 50K + +#define SPINDLE_LASER_PWM_OFF TERN(SPINDLE_LASER_PWM_INVERT, 255, 0) + +/** + * Init the cutter to a safe OFF state + */ void SpindleLaser::init() { - OUT_WRITE(SPINDLE_LASER_ENA_PIN, !SPINDLE_LASER_ACTIVE_STATE); // Init spindle to off + #if ENABLED(SPINDLE_SERVO) + servo[SPINDLE_SERVO_NR].move(SPINDLE_SERVO_MIN); + #elif PIN_EXISTS(SPINDLE_LASER_ENA) + OUT_WRITE(SPINDLE_LASER_ENA_PIN, !SPINDLE_LASER_ACTIVE_STATE); // Init spindle to off + #endif #if ENABLED(SPINDLE_CHANGE_DIR) - OUT_WRITE(SPINDLE_DIR_PIN, SPINDLE_INVERT_DIR ? 255 : 0); // Init rotation to clockwise (M3) + OUT_WRITE(SPINDLE_DIR_PIN, SPINDLE_INVERT_DIR); // Init rotation to clockwise (M3) #endif - #if ENABLED(SPINDLE_LASER_PWM) + #if ENABLED(HAL_CAN_SET_PWM_FREQ) && SPINDLE_LASER_FREQUENCY + frequency = SPINDLE_LASER_FREQUENCY; + hal.set_pwm_frequency(pin_t(SPINDLE_LASER_PWM_PIN), SPINDLE_LASER_FREQUENCY); + #endif + #if ENABLED(SPINDLE_LASER_USE_PWM) SET_PWM(SPINDLE_LASER_PWM_PIN); - analogWrite(pin_t(SPINDLE_LASER_PWM_PIN), SPINDLE_LASER_PWM_OFF); // Set to lowest speed + hal.set_pwm_duty(pin_t(SPINDLE_LASER_PWM_PIN), SPINDLE_LASER_PWM_OFF); // Set to lowest speed #endif - #if ENABLED(HAL_CAN_SET_PWM_FREQ) && defined(SPINDLE_LASER_FREQUENCY) - set_pwm_frequency(pin_t(SPINDLE_LASER_PWM_PIN), SPINDLE_LASER_FREQUENCY); - TERN_(MARLIN_DEV_MODE, frequency = SPINDLE_LASER_FREQUENCY); + #if ENABLED(AIR_EVACUATION) + OUT_WRITE(AIR_EVACUATION_PIN, !AIR_EVACUATION_ACTIVE); // Init Vacuum/Blower OFF #endif + #if ENABLED(AIR_ASSIST) + OUT_WRITE(AIR_ASSIST_PIN, !AIR_ASSIST_ACTIVE); // Init Air Assist OFF + #endif + TERN_(I2C_AMMETER, ammeter.init()); // Init I2C Ammeter } -#if ENABLED(SPINDLE_LASER_PWM) +#if ENABLED(SPINDLE_LASER_USE_PWM) /** * Set the cutter PWM directly to the given ocr value + * + * @param ocr Power value */ - void SpindleLaser::set_ocr(const uint8_t ocr) { - WRITE(SPINDLE_LASER_ENA_PIN, SPINDLE_LASER_ACTIVE_STATE); // Turn spindle on - analogWrite(pin_t(SPINDLE_LASER_PWM_PIN), ocr ^ SPINDLE_LASER_PWM_OFF); - #if NEEDS_HARDWARE_PWM && SPINDLE_LASER_FREQUENCY - set_pwm_duty(pin_t(SPINDLE_LASER_PWM_PIN), ocr ^ SPINDLE_LASER_PWM_OFF); + void SpindleLaser::_set_ocr(const uint8_t ocr) { + #if ENABLED(HAL_CAN_SET_PWM_FREQ) && SPINDLE_LASER_FREQUENCY + hal.set_pwm_frequency(pin_t(SPINDLE_LASER_PWM_PIN), frequency); + #endif + #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::ocr_off() { - WRITE(SPINDLE_LASER_ENA_PIN, !SPINDLE_LASER_ACTIVE_STATE); // Turn spindle off - analogWrite(pin_t(SPINDLE_LASER_PWM_PIN), SPINDLE_LASER_PWM_OFF); // Only write low byte - } -#endif -// -// Set cutter ON/OFF state (and PWM) to the given cutter power value -// + void SpindleLaser::set_ocr(const uint8_t ocr) { + #if PIN_EXISTS(SPINDLE_LASER_ENA) + WRITE(SPINDLE_LASER_ENA_PIN, SPINDLE_LASER_ACTIVE_STATE); // Cutter ON + #endif + _set_ocr(ocr); + } + + 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 + } +#endif // SPINDLE_LASER_USE_PWM + +/** + * Apply power for Laser or Spindle + * + * Apply cutter power value for PWM, Servo, and on/off pin. + * + * @param opwr Power value. Range 0 to MAX. + */ void SpindleLaser::apply_power(const uint8_t opwr) { - static uint8_t last_power_applied = 0; - if (opwr == last_power_applied) return; - last_power_applied = opwr; - power = opwr; - #if ENABLED(SPINDLE_LASER_PWM) - if (cutter.unitPower == 0 && CUTTER_UNIT_IS(RPM)) { - ocr_off(); - isReady = false; - } - else if (enabled() || ENABLED(CUTTER_POWER_RELATIVE)) { - set_ocr(power); - isReady = true; - } - else { - ocr_off(); - isReady = false; - } - #else - WRITE(SPINDLE_LASER_ENA_PIN, enabled() ? SPINDLE_LASER_ACTIVE_STATE : !SPINDLE_LASER_ACTIVE_STATE); - isReady = true; - #endif + if (enabled() || opwr == 0) { // 0 check allows us to disable where no ENA pin exists + // Test the last power used to improve performance + if (opwr == last_power_applied) return; + // Handle PWM driven or just simple on/off + #if ENABLED(SPINDLE_LASER_USE_PWM) + if (CUTTER_UNIT_IS(RPM) && unitPower == 0) + ocr_off(); + else if (ENABLED(CUTTER_POWER_RELATIVE) || enabled() || opwr == 0) { + set_ocr(opwr); + isReadyForUI = true; + } + else + ocr_off(); + #elif ENABLED(SPINDLE_SERVO) + servo[SPINDLE_SERVO_NR].move(opwr); + #else + 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) + WRITE(SPINDLE_LASER_ENA_PIN, !SPINDLE_LASER_ACTIVE_STATE); + #endif + isReadyForUI = false; // Only used for UI display updates. + TERN_(SPINDLE_LASER_USE_PWM, ocr_off()); + } } #if ENABLED(SPINDLE_CHANGE_DIR) - // - // Set the spindle direction and apply immediately - // Stop on direction change if SPINDLE_STOP_ON_DIR_CHANGE is enabled - // - void SpindleLaser::set_direction(const bool reverse) { + /** + * Set the spindle direction and apply immediately + * Stop on direction change if SPINDLE_STOP_ON_DIR_CHANGE is enabled + */ + void SpindleLaser::set_reverse(const bool reverse) { const bool dir_state = (reverse == SPINDLE_INVERT_DIR); // Forward (M3) HIGH when not inverted if (TERN0(SPINDLE_STOP_ON_DIR_CHANGE, enabled()) && READ(SPINDLE_DIR_PIN) != dir_state) disable(); WRITE(SPINDLE_DIR_PIN, dir_state); } #endif +#if ENABLED(AIR_EVACUATION) + // Enable / disable Cutter Vacuum or Laser Blower motor + void SpindleLaser::air_evac_enable() { WRITE(AIR_EVACUATION_PIN, AIR_EVACUATION_ACTIVE); } // Turn ON + void SpindleLaser::air_evac_disable() { WRITE(AIR_EVACUATION_PIN, !AIR_EVACUATION_ACTIVE); } // Turn OFF + void SpindleLaser::air_evac_toggle() { TOGGLE(AIR_EVACUATION_PIN); } // Toggle state +#endif + +#if ENABLED(AIR_ASSIST) + // Enable / disable air assist + void SpindleLaser::air_assist_enable() { WRITE(AIR_ASSIST_PIN, AIR_ASSIST_ACTIVE); } // Turn ON + void SpindleLaser::air_assist_disable() { WRITE(AIR_ASSIST_PIN, !AIR_ASSIST_ACTIVE); } // Turn OFF + void SpindleLaser::air_assist_toggle() { TOGGLE(AIR_ASSIST_PIN); } // Toggle state +#endif + #endif // HAS_CUTTER diff --git a/Marlin/src/feature/spindle_laser.h b/Marlin/src/feature/spindle_laser.h index 15ff661c73..8305c1fec4 100644 --- a/Marlin/src/feature/spindle_laser.h +++ b/Marlin/src/feature/spindle_laser.h @@ -30,253 +30,304 @@ #include "spindle_laser_types.h" -#if ENABLED(LASER_POWER_INLINE) - #include "../module/planner.h" -#endif +#include "../libs/buzzer.h" +// 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)) -#ifndef SPEED_POWER_INTERCEPT - #define SPEED_POWER_INTERCEPT 0 -#endif -#define SPEED_POWER_FLOOR TERN(CUTTER_POWER_RELATIVE, SPEED_POWER_MIN, 0) - -// #define _MAP(N,S1,S2,D1,D2) ((N)*_MAX((D2)-(D1),0)/_MAX((S2)-(S1),1)+(D1)) +// Laser/Cutter operation mode +enum CutterMode : int8_t { + CUTTER_MODE_ERROR = -1, + CUTTER_MODE_STANDARD, // M3 power is applied directly and waits for planner moves to sync. + CUTTER_MODE_CONTINUOUS, // M3 or G1/2/3 move power is controlled within planner blocks, set with 'M3 I', cleared with 'M5 I'. + CUTTER_MODE_DYNAMIC // M4 laser power is proportional to the feed rate, set with 'M4 I', cleared with 'M5 I'. +}; class SpindleLaser { public: - static constexpr float - min_pct = round(TERN(CUTTER_POWER_RELATIVE, 0, (100 * float(SPEED_POWER_MIN) / TERN(SPINDLE_FEATURE, float(SPEED_POWER_MAX), 100)))), - max_pct = round(TERN(SPINDLE_FEATURE, 100, float(SPEED_POWER_MAX))); + static CutterMode cutter_mode; - static const inline uint8_t pct_to_ocr(const float 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 (ie SPEED_POWER_MAX) - static const inline uint8_t cpwr_to_pct(const cutter_cpower_t cpwr) { // configured value to pct - return unitPower ? round(100 * (cpwr - SPEED_POWER_FLOOR) / (SPEED_POWER_MAX - SPEED_POWER_FLOOR)) : 0; + // 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 ? LROUND(100.0f * (cpwr - power_floor) / (SPEED_POWER_MAX - power_floor)) : 0; } - // Convert a configured value (cpower)(ie SPEED_POWER_STARTUP) to unit power (upwr, upower), - // which can be PWM, Percent, or RPM (rel/abs). - static const inline cutter_power_t cpwr_to_upwr(const cutter_cpower_t cpwr) { // STARTUP power to Unit power - const cutter_power_t upwr = ( + // Convert config defines from RPM to %, angle or PWM when in Spindle mode + // and convert from PERCENT to PWM when in Laser mode + static constexpr cutter_power_t cpwr_to_upwr(const cutter_cpower_t cpwr) { // STARTUP power to Unit power + return ( #if ENABLED(SPINDLE_FEATURE) - // Spindle configured values are in RPM + // Spindle configured define values are in RPM #if CUTTER_UNIT_IS(RPM) - cpwr // to RPM - #elif CUTTER_UNIT_IS(PERCENT) // to PCT - cpwr_to_pct(cpwr) - #else // to PWM - PCT_TO_PWM(cpwr_to_pct(cpwr)) + cpwr // to same + #elif CUTTER_UNIT_IS(PERCENT) + cpwr_to_pct(cpwr) // to Percent + #elif CUTTER_UNIT_IS(SERVO) + PCT_TO_SERVO(cpwr_to_pct(cpwr)) // to SERVO angle + #else + PCT_TO_PWM(cpwr_to_pct(cpwr)) // to PWM #endif #else - // Laser configured values are in PCT + // Laser configured define values are in Percent #if CUTTER_UNIT_IS(PWM255) - PCT_TO_PWM(cpwr) + PCT_TO_PWM(cpwr) // to PWM #else - cpwr // to RPM/PCT + cpwr // to same #endif #endif ); - return upwr; } - static const cutter_power_t mpower_min() { return cpwr_to_upwr(SPEED_POWER_MIN); } - static const cutter_power_t mpower_max() { return cpwr_to_upwr(SPEED_POWER_MAX); } + static constexpr cutter_power_t mpower_min() { return cpwr_to_upwr(SPEED_POWER_MIN); } + static constexpr cutter_power_t mpower_max() { return cpwr_to_upwr(SPEED_POWER_MAX); } - static bool isReady; // Ready to apply power setting from the UI to OCR - static uint8_t power; + #if ENABLED(LASER_FEATURE) + static cutter_test_pulse_t testPulse; // (ms) Test fire pulse duration + static uint8_t last_block_power; // Track power changes for dynamic power - #if ENABLED(MARLIN_DEV_MODE) - static cutter_frequency_t frequency; // Set PWM frequency; range: 2K-50K + static feedRate_t feedrate_mm_m, last_feedrate_mm_m; // (mm/min) Track feedrate changes for dynamic power + static bool laser_feedrate_changed() { + const bool changed = last_feedrate_mm_m != feedrate_mm_m; + if (changed) last_feedrate_mm_m = feedrate_mm_m; + return changed; + } #endif - 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 bool isReadyForUI; // Ready to apply power setting from the UI to OCR + static bool enable_state; + static uint8_t power, + last_power_applied; // Basic power state tracking + 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 + + #if HAS_SPINDLE_ACCELERATION + static uint32_t acceleration_spindle_deg_per_s2; // (Β°/s/s) Spindle acceleration + #endif static void init(); - #if ENABLED(MARLIN_DEV_MODE) - static inline void refresh_frequency() { set_pwm_frequency(pin_t(SPINDLE_LASER_PWM_PIN), frequency); } + #if ENABLED(HAL_CAN_SET_PWM_FREQ) && SPINDLE_LASER_FREQUENCY + static void refresh_frequency() { hal.set_pwm_frequency(pin_t(SPINDLE_LASER_PWM_PIN), frequency); } #endif // Modifying this function should update everywhere - static inline bool enabled(const cutter_power_t opwr) { return opwr > 0; } - static inline bool enabled() { return enabled(power); } + static bool enabled(const cutter_power_t opwr) { return opwr > 0; } + static bool enabled() { return enable_state; } static void apply_power(const uint8_t inpow); FORCE_INLINE static void refresh() { apply_power(power); } - FORCE_INLINE static void set_power(const uint8_t upwr) { power = upwr; refresh(); } - #if ENABLED(SPINDLE_LASER_PWM) + #if ENABLED(SPINDLE_LASER_USE_PWM) + + private: + + static void _set_ocr(const uint8_t ocr); + + public: static void set_ocr(const uint8_t ocr); - static inline void set_ocr_power(const uint8_t ocr) { power = ocr; set_ocr(ocr); } static void ocr_off(); - // Used to update output for power->OCR translation - static inline uint8_t upower_to_ocr(const cutter_power_t upwr) { - return ( + + /** + * Update output for power->OCR translation + */ + static uint8_t upower_to_ocr(const cutter_power_t upwr) { + return uint8_t( #if CUTTER_UNIT_IS(PWM255) - uint8_t(upwr) + upwr #elif CUTTER_UNIT_IS(PERCENT) pct_to_ocr(upwr) #else - uint8_t(pct_to_ocr(cpwr_to_pct(upwr))) + pct_to_ocr(cpwr_to_pct(upwr)) #endif ); } - // Correct power to configured range - static inline cutter_power_t power_to_range(const cutter_power_t pwr) { - return power_to_range(pwr, ( - #if CUTTER_UNIT_IS(PWM255) - 0 - #elif CUTTER_UNIT_IS(PERCENT) - 1 - #elif CUTTER_UNIT_IS(RPM) - 2 - #else - #error "???" - #endif - )); - } - static inline cutter_power_t power_to_range(const cutter_power_t pwr, const uint8_t pwrUnit) { - if (pwr <= 0) return 0; - cutter_power_t upwr; - switch (pwrUnit) { - case 0: // PWM - upwr = cutter_power_t( - (pwr < pct_to_ocr(min_pct)) ? pct_to_ocr(min_pct) // Use minimum if set below - : (pwr > pct_to_ocr(max_pct)) ? pct_to_ocr(max_pct) // Use maximum if set above - : pwr - ); - break; - case 1: // PERCENT - upwr = cutter_power_t( - (pwr < min_pct) ? min_pct // Use minimum if set below - : (pwr > max_pct) ? max_pct // Use maximum if set above - : pwr // PCT - ); - break; - case 2: // RPM - upwr = cutter_power_t( - (pwr < SPEED_POWER_MIN) ? SPEED_POWER_MIN // Use minimum if set below - : (pwr > SPEED_POWER_MAX) ? SPEED_POWER_MAX // Use maximum if set above - : pwr // Calculate OCR value - ); - break; - default: break; - } - return upwr; - } + #endif // SPINDLE_LASER_USE_PWM - #endif // SPINDLE_LASER_PWM - - static inline void set_enabled(const bool enable) { - set_power(enable ? TERN(SPINDLE_LASER_PWM, (power ?: (unitPower ? upower_to_ocr(cpwr_to_upwr(SPEED_POWER_STARTUP)) : 0)), 255) : 0); + /** + * Correct power to configured range + */ + 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, 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; + switch (pwrUnit) { + case _CUTTER_POWER_PWM255: { // PWM + const uint8_t pmin = pct_to_ocr(min_pct), pmax = pct_to_ocr(max_pct); + upwr = cutter_power_t(constrain(pwr, pmin, pmax)); + } break; + case _CUTTER_POWER_PERCENT: // Percent + upwr = cutter_power_t(constrain(pwr, min_pct, max_pct)); + break; + case _CUTTER_POWER_RPM: // Calculate OCR value + upwr = cutter_power_t(constrain(pwr, SPEED_POWER_MIN, SPEED_POWER_MAX)); + break; + default: break; + } + return upwr; } - // Wait for spindle to spin up or spin down - static inline void power_delay(const bool on) { - #if DISABLED(LASER_POWER_INLINE) - safe_delay(on ? SPINDLE_LASER_POWERUP_DELAY : SPINDLE_LASER_POWERDOWN_DELAY); + /** + * Enable Laser or Spindle output. + * It's important to prevent changing the power output value during inline cutter operation. + * Inline power is adjusted in the planner to support LASER_TRAP_POWER and CUTTER_MODE_DYNAMIC mode. + * + * This method accepts one of the following control states: + * + * - For CUTTER_MODE_STANDARD the cutter power is either full on/off or ocr-based and it will apply + * SPEED_POWER_STARTUP if no value is assigned. + * + * - For CUTTER_MODE_CONTINUOUS inline and power remains where last set and the cutter output enable flag is set. + * + * - CUTTER_MODE_DYNAMIC is also inline-based and it just sets the enable output flag. + * + * - For CUTTER_MODE_ERROR set the output enable_state flag directly and set power to 0 for any mode. + * This mode allows a global power shutdown action to occur. + */ + static void set_enabled(bool enable) { + switch (cutter_mode) { + case CUTTER_MODE_STANDARD: + apply_power(enable ? TERN(SPINDLE_LASER_USE_PWM, (power ?: (unitPower ? upower_to_ocr(cpwr_to_upwr(SPEED_POWER_STARTUP)) : 0)), 255) : 0); + break; + case CUTTER_MODE_CONTINUOUS: + case CUTTER_MODE_DYNAMIC: + TERN_(LASER_FEATURE, set_inline_enabled(enable)); + break; + case CUTTER_MODE_ERROR: // Error mode, no enable and kill power. + enable = false; + apply_power(0); + } + #if PIN_EXISTS(SPINDLE_LASER_ENA) + WRITE(SPINDLE_LASER_ENA_PIN, enable ? SPINDLE_LASER_ACTIVE_STATE : !SPINDLE_LASER_ACTIVE_STATE); #endif + enable_state = enable; + } + + static void disable() { isReadyForUI = false; set_enabled(false); } + + // Wait for spindle/laser to startup or shutdown + static void power_delay(const bool on) { + safe_delay(on ? SPINDLE_LASER_POWERUP_DELAY : SPINDLE_LASER_POWERDOWN_DELAY); } #if ENABLED(SPINDLE_CHANGE_DIR) - static void set_direction(const bool reverse); + static void set_reverse(const bool reverse); + static bool is_reverse() { return READ(SPINDLE_DIR_PIN) == SPINDLE_INVERT_DIR; } #else - static inline void set_direction(const bool) {} + static void set_reverse(const bool) {} + static bool is_reverse() { return false; } #endif - static inline void disable() { isReady = false; set_enabled(false); } - - #if HAS_LCD_MENU - - static inline void enable_with_dir(const bool reverse) { - isReady = true; - const uint8_t ocr = TERN(SPINDLE_LASER_PWM, upower_to_ocr(menuPower), 255); - if (menuPower) - power = ocr; - else - menuPower = cpwr_to_upwr(SPEED_POWER_STARTUP); - unitPower = menuPower; - set_direction(reverse); - set_enabled(true); + #if ENABLED(AIR_EVACUATION) + static void air_evac_enable(); // Turn On Cutter Vacuum or Laser Blower motor + static void air_evac_disable(); // Turn Off Cutter Vacuum or Laser Blower motor + static void air_evac_toggle(); // Toggle Cutter Vacuum or Laser Blower motor + static bool air_evac_state() { // Get current state + return (READ(AIR_EVACUATION_PIN) == AIR_EVACUATION_ACTIVE); } - FORCE_INLINE static void enable_forward() { enable_with_dir(false); } - FORCE_INLINE static void enable_reverse() { enable_with_dir(true); } + #endif - #if ENABLED(SPINDLE_LASER_PWM) - static inline void update_from_mpower() { - if (isReady) power = upower_to_ocr(menuPower); + #if ENABLED(AIR_ASSIST) + static void air_assist_enable(); // Turn on air assist + static void air_assist_disable(); // Turn off air assist + static void air_assist_toggle(); // Toggle air assist + static bool air_assist_state() { // Get current state + return (READ(AIR_ASSIST_PIN) == AIR_ASSIST_ACTIVE); + } + #endif + + #if HAS_MARLINUI_MENU + + #if ENABLED(SPINDLE_FEATURE) + static void enable_with_dir(const bool reverse) { + isReadyForUI = true; + const uint8_t ocr = TERN(SPINDLE_LASER_USE_PWM, upower_to_ocr(menuPower), 255); + if (menuPower) + power = ocr; + else + menuPower = cpwr_to_upwr(SPEED_POWER_STARTUP); + unitPower = menuPower; + set_reverse(reverse); + set_enabled(true); + } + FORCE_INLINE static void enable_forward() { enable_with_dir(false); } + FORCE_INLINE static void enable_reverse() { enable_with_dir(true); } + FORCE_INLINE static void enable_same_dir() { enable_with_dir(is_reverse()); } + #endif // SPINDLE_FEATURE + + #if ENABLED(SPINDLE_LASER_USE_PWM) + static void update_from_mpower() { + if (isReadyForUI) power = upower_to_ocr(menuPower); unitPower = menuPower; } #endif - #endif + #if ENABLED(LASER_FEATURE) + // Toggle the laser on/off with menuPower. Apply SPEED_POWER_STARTUP if it was 0 on entry. + static void menu_set_enabled(const bool state) { + set_enabled(state); + if (state) { + if (!menuPower) menuPower = cpwr_to_upwr(SPEED_POWER_STARTUP); + power = TERN(SPINDLE_LASER_USE_PWM, upower_to_ocr(menuPower), 255); + apply_power(power); + } else + apply_power(0); + } - #if ENABLED(LASER_POWER_INLINE) - /** - * Inline power adds extra fields to the planner block - * to handle laser power and scale to movement speed. - */ + /** + * Test fire the laser using the testPulse ms duration + * Also fires with any PWM power that was previous set + * If not set defaults to 80% power + */ + static void test_fire_pulse() { + BUZZ(30, 3000); + cutter_mode = CUTTER_MODE_STANDARD; // Menu needs standard mode. + menu_set_enabled(true); // Laser On + delay(testPulse); // Delay for time set by user in pulse ms menu screen. + menu_set_enabled(false); // Laser Off + } + #endif // LASER_FEATURE - // Force disengage planner power control - static inline void inline_disable() { - isReady = false; - unitPower = 0; - planner.laser_inline.status.isPlanned = false; - planner.laser_inline.status.isEnabled = false; - planner.laser_inline.power = 0; + #endif // HAS_MARLINUI_MENU + + #if ENABLED(LASER_FEATURE) + + // Dynamic mode rate calculation + static uint8_t calc_dynamic_power() { + if (feedrate_mm_m > 65535) return 255; // Too fast, go always on + uint16_t rate = uint16_t(feedrate_mm_m); // 16 bits from the G-code parser float input + rate >>= 8; // Take the G-code input e.g. F40000 and shift off the lower bits to get an OCR value from 1-255 + return uint8_t(rate); } // Inline modes of all other functions; all enable planner inline power control - static inline void set_inline_enabled(const bool enable) { - if (enable) - inline_power(cpwr_to_upwr(SPEED_POWER_STARTUP)); - else { - isReady = false; - unitPower = menuPower = 0; - planner.laser_inline.status.isPlanned = false; - TERN(SPINDLE_LASER_PWM, inline_ocr_power, inline_power)(0); - } - } + static void set_inline_enabled(const bool enable) { planner.laser_inline.status.isEnabled = enable; } // Set the power for subsequent movement blocks - static void inline_power(const cutter_power_t upwr) { - unitPower = menuPower = upwr; - #if ENABLED(SPINDLE_LASER_PWM) - #if ENABLED(SPEED_POWER_RELATIVE) && !CUTTER_UNIT_IS(RPM) // relative mode does not turn laser off at 0, except for RPM - planner.laser_inline.status.isEnabled = true; - planner.laser_inline.power = upower_to_ocr(upwr); - isReady = true; - #else - inline_ocr_power(upower_to_ocr(upwr)); - #endif - #else - planner.laser_inline.status.isEnabled = enabled(upwr); - planner.laser_inline.power = upwr; - isReady = enabled(upwr); - #endif + static void inline_power(const cutter_power_t cpwr) { + TERN(SPINDLE_LASER_USE_PWM, power = planner.laser_inline.power = cpwr, planner.laser_inline.power = cpwr > 0 ? 255 : 0); } - static inline void inline_direction(const bool) { /* never */ } + #endif // LASER_FEATURE - #if ENABLED(SPINDLE_LASER_PWM) - static inline void inline_ocr_power(const uint8_t ocrpwr) { - isReady = ocrpwr > 0; - planner.laser_inline.status.isEnabled = ocrpwr > 0; - planner.laser_inline.power = ocrpwr; - } - #endif - #endif // LASER_POWER_INLINE - - static inline void kill() { - TERN_(LASER_POWER_INLINE, inline_disable()); - disable(); - } + static void kill() { disable(); } }; extern SpindleLaser cutter; diff --git a/Marlin/src/feature/spindle_laser_types.h b/Marlin/src/feature/spindle_laser_types.h index 181c4d73ac..4e5e4d06f6 100644 --- a/Marlin/src/feature/spindle_laser_types.h +++ b/Marlin/src/feature/spindle_laser_types.h @@ -28,26 +28,56 @@ #include "../inc/MarlinConfigPre.h" -#if ENABLED(SPINDLE_FEATURE) - #define _MSG_CUTTER(M) MSG_SPINDLE_##M -#else - #define _MSG_CUTTER(M) MSG_LASER_##M -#endif #define MSG_CUTTER(M) _MSG_CUTTER(M) -typedef IF<(SPEED_POWER_MAX > 255), uint16_t, uint8_t>::type cutter_cpower_t; +#ifndef SPEED_POWER_INTERCEPT + #define SPEED_POWER_INTERCEPT 0 +#endif +#if ENABLED(SPINDLE_FEATURE) + #define _MSG_CUTTER(M) MSG_SPINDLE_##M + #ifndef SPEED_POWER_MIN + #define SPEED_POWER_MIN 5000 + #endif + #ifndef SPEED_POWER_MAX + #define SPEED_POWER_MAX 30000 + #endif + #ifndef SPEED_POWER_STARTUP + #define SPEED_POWER_STARTUP 25000 + #endif +#else + #define _MSG_CUTTER(M) MSG_LASER_##M + #ifndef SPEED_POWER_MIN + #define SPEED_POWER_MIN 0 + #endif + #ifndef SPEED_POWER_MAX + #define SPEED_POWER_MAX 255 + #endif + #ifndef SPEED_POWER_STARTUP + #define SPEED_POWER_STARTUP 255 + #endif +#endif + +typedef uvalue_t(SPEED_POWER_MAX) cutter_cpower_t; #if CUTTER_UNIT_IS(RPM) && SPEED_POWER_MAX > 255 typedef uint16_t cutter_power_t; - #define CUTTER_MENU_POWER_TYPE uint16_5 - #define cutter_power2str ui16tostr5rj + #define CUTTER_MENU_POWER_TYPE uint16_5 + #define cutter_power2str ui16tostr5rj #else typedef uint8_t cutter_power_t; - #define CUTTER_MENU_POWER_TYPE uint8 - #define cutter_power2str ui8tostr3rj + #if CUTTER_UNIT_IS(PERCENT) + #define CUTTER_MENU_POWER_TYPE percent_3 + #define cutter_power2str pcttostrpctrj + #else + #define CUTTER_MENU_POWER_TYPE uint8 + #define cutter_power2str ui8tostr3rj + #endif #endif -#if ENABLED(MARLIN_DEV_MODE) - typedef uint16_t cutter_frequency_t; +typedef uint16_t cutter_frequency_t; + +#if ENABLED(LASER_FEATURE) + typedef uint16_t cutter_test_pulse_t; + #define CUTTER_MENU_PULSE_TYPE uint16_3 #define CUTTER_MENU_FREQUENCY_TYPE uint16_5 #endif diff --git a/Marlin/src/feature/stepper_driver_safety.cpp b/Marlin/src/feature/stepper_driver_safety.cpp new file mode 100644 index 0000000000..acdd695909 --- /dev/null +++ b/Marlin/src/feature/stepper_driver_safety.cpp @@ -0,0 +1,122 @@ +/** + * 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 . + * + */ +#include "../inc/MarlinConfig.h" +#include "../lcd/marlinui.h" + +#if HAS_DRIVER_SAFE_POWER_PROTECT + +#include "stepper_driver_safety.h" + +static uint32_t axis_plug_backward = 0; + +void stepper_driver_backward_error(FSTR_P const fstr) { + SERIAL_ERROR_START(); + SERIAL_ECHOLN(fstr, F(" driver is backward!")); + ui.status_printf(2, F(S_FMT S_FMT), FTOP(fstr), GET_TEXT(MSG_DRIVER_BACKWARD)); +} + +void stepper_driver_backward_check() { + + OUT_WRITE(SAFE_POWER_PIN, LOW); + + #define _TEST_BACKWARD(AXIS, BIT) do { \ + SET_INPUT(AXIS##_ENABLE_PIN); \ + OUT_WRITE(AXIS##_STEP_PIN, false); \ + delay(20); \ + if (READ(AXIS##_ENABLE_PIN) == LOW) { \ + SBI(axis_plug_backward, BIT); \ + stepper_driver_backward_error(F(STRINGIFY(AXIS))); \ + } \ + }while(0) + + #define TEST_BACKWARD(AXIS, BIT) TERN_(HAS_##AXIS##_ENABLE, _TEST_BACKWARD(AXIS, BIT)) + + TEST_BACKWARD(X, 0); + TEST_BACKWARD(X2, 1); + + TEST_BACKWARD(Y, 2); + TEST_BACKWARD(Y2, 3); + + TEST_BACKWARD(Z, 4); + TEST_BACKWARD(Z2, 5); + TEST_BACKWARD(Z3, 6); + TEST_BACKWARD(Z4, 7); + + TEST_BACKWARD(I, 8); + TEST_BACKWARD(J, 9); + TEST_BACKWARD(K, 10); + TEST_BACKWARD(U, 11); + TEST_BACKWARD(V, 12); + TEST_BACKWARD(W, 13); + + TEST_BACKWARD(E0, 14); + TEST_BACKWARD(E1, 15); + TEST_BACKWARD(E2, 16); + TEST_BACKWARD(E3, 17); + TEST_BACKWARD(E4, 18); + TEST_BACKWARD(E5, 19); + TEST_BACKWARD(E6, 20); + TEST_BACKWARD(E7, 21); + + if (!axis_plug_backward) + WRITE(SAFE_POWER_PIN, HIGH); +} + +void stepper_driver_backward_report() { + if (!axis_plug_backward) return; + + auto _report_if_backward = [](FSTR_P const axis, uint8_t bit) { + if (TEST(axis_plug_backward, bit)) + stepper_driver_backward_error(axis); + }; + + #define REPORT_BACKWARD(axis, bit) TERN_(HAS_##axis##_ENABLE, _report_if_backward(F(STRINGIFY(axis)), bit)) + + REPORT_BACKWARD(X, 0); + REPORT_BACKWARD(X2, 1); + + REPORT_BACKWARD(Y, 2); + REPORT_BACKWARD(Y2, 3); + + REPORT_BACKWARD(Z, 4); + REPORT_BACKWARD(Z2, 5); + REPORT_BACKWARD(Z3, 6); + REPORT_BACKWARD(Z4, 7); + + REPORT_BACKWARD(I, 8); + REPORT_BACKWARD(J, 9); + REPORT_BACKWARD(K, 10); + REPORT_BACKWARD(U, 11); + REPORT_BACKWARD(V, 12); + REPORT_BACKWARD(W, 13); + + REPORT_BACKWARD(E0, 14); + REPORT_BACKWARD(E1, 15); + REPORT_BACKWARD(E2, 16); + REPORT_BACKWARD(E3, 17); + REPORT_BACKWARD(E4, 18); + REPORT_BACKWARD(E5, 19); + REPORT_BACKWARD(E6, 20); + REPORT_BACKWARD(E7, 21); +} + +#endif // HAS_DRIVER_SAFE_POWER_PROTECT diff --git a/Marlin/src/feature/stepper_driver_safety.h b/Marlin/src/feature/stepper_driver_safety.h new file mode 100644 index 0000000000..ac3d8b64f9 --- /dev/null +++ b/Marlin/src/feature/stepper_driver_safety.h @@ -0,0 +1,27 @@ +/** + * 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 + +#include "../inc/MarlinConfigPre.h" + +void stepper_driver_backward_check(); +void stepper_driver_backward_report(); diff --git a/Marlin/src/feature/tmc_util.cpp b/Marlin/src/feature/tmc_util.cpp index 781253079e..a91d435355 100644 --- a/Marlin/src/feature/tmc_util.cpp +++ b/Marlin/src/feature/tmc_util.cpp @@ -24,24 +24,32 @@ #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 "../module/planner.h" #include "../libs/hex_print.h" #if ENABLED(MONITOR_DRIVER_STATUS) static uint16_t report_tmc_status_interval; // = 0 #endif #endif -#if HAS_LCD_MENU - #include "../module/stepper.h" +#if ENABLED(EDITABLE_HOMING_CURRENT) + homing_current_t homing_current_mA; #endif /** @@ -69,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 @@ -139,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) @@ -147,7 +216,7 @@ static TMC_driver_data get_driver_data(TMC2208Stepper &st) { constexpr uint8_t OTPW_bp = 0, OT_bp = 1; - constexpr uint8_t S2G_bm = 0b11110; // 2..5 + constexpr uint8_t S2G_bm = 0b111100; // 2..5 TMC_driver_data data; const auto ds = data.drv_status = st.DRV_STATUS(); data.is_otpw = TEST(ds, OTPW_bp); @@ -208,58 +277,58 @@ #if ENABLED(STOP_ON_ERROR) void report_driver_error(const TMC_driver_data &data) { SERIAL_ECHOPGM(" driver error detected: 0x"); - SERIAL_PRINTLN(data.drv_status, HEX); + SERIAL_PRINTLN(data.drv_status, PrintBase::Hex); if (data.is_ot) SERIAL_ECHOLNPGM("overtemperature"); if (data.is_s2g) SERIAL_ECHOLNPGM("coil short circuit"); - TERN_(TMC_DEBUG, tmc_report_all(true, true, true, true)); - kill(PSTR("Driver error")); + TERN_(TMC_DEBUG, tmc_report_all()); + TERN_(SOVOL_SV06_RTS, rts.gotoPage(ID_DriverError_L, ID_DriverError_D)); + marlin.kill(F("Driver error")); } #endif template void report_driver_otpw(TMC &st) { - char timestamp[14]; + MString<13> timestamp; duration_t elapsed = print_job_timer.duration(); const bool has_days = (elapsed.value > 60*60*24L); - (void)elapsed.toDigital(timestamp, has_days); - SERIAL_EOL(); - SERIAL_ECHO(timestamp); - SERIAL_ECHOPGM(": "); + (void)elapsed.toDigital(×tamp, has_days); + TSS('\n', timestamp, F(": ")).echo(); st.printLabel(); - SERIAL_ECHOLNPAIR(" driver overtemperature warning! (", st.getMilliamps(), "mA)"); + 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(); - SERIAL_CHAR(':'); SERIAL_PRINT(pwm_scale, DEC); - #if ENABLED(TMC_DEBUG) - #if HAS_TMCX1X0 || HAS_TMC220x - SERIAL_CHAR('/'); SERIAL_PRINT(data.cs_actual, DEC); + #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 - SERIAL_CHAR('/'); + report += '/'; if (data.sg_result_reasonable) - SERIAL_ECHO(data.sg_result); + report += data.sg_result; else - SERIAL_CHAR('-'); + report += '-'; #endif - #endif - SERIAL_CHAR('|'); - if (st.error_count) SERIAL_CHAR('E'); // Error - if (data.is_ot) SERIAL_CHAR('O'); // Over-temperature - if (data.is_otpw) SERIAL_CHAR('W'); // over-temperature pre-Warning - #if ENABLED(TMC_DEBUG) - if (data.is_stall) SERIAL_CHAR('G'); // stallGuard - if (data.is_stealth) SERIAL_CHAR('T'); // stealthChop - if (data.is_standstill) SERIAL_CHAR('I'); // standstIll - #endif - if (st.flag_otpw) SERIAL_CHAR('F'); // otpw Flag - SERIAL_CHAR('|'); - if (st.otpw_count > 0) SERIAL_PRINT(st.otpw_count, DEC); - SERIAL_CHAR('\t'); - } + 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 + 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 @@ -271,7 +340,7 @@ st.rms_current(I_rms); #if ENABLED(REPORT_CURRENT_CHANGE) st.printLabel(); - SERIAL_ECHOLNPAIR(" current decreased to ", I_rms); + SERIAL_ECHOLNPGM(" current decreased to ", I_rms); #endif } } @@ -291,7 +360,7 @@ bool should_step_down = false; if (need_update_error_counters) { - if (data.is_ot /* | data.s2ga | data.s2gb*/) st.error_count++; + if (data.is_ot | data.is_s2g) st.error_count++; else if (st.error_count > 0) st.error_count--; #if ENABLED(STOP_ON_ERROR) @@ -319,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; } @@ -345,103 +414,71 @@ 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(E0) - (void)monitor_tmc_driver(stepperE0, need_update_error_counters, need_debug_reporting); + #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(E1) - (void)monitor_tmc_driver(stepperE1, need_update_error_counters, need_debug_reporting); + #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(E2) - (void)monitor_tmc_driver(stepperE2, need_update_error_counters, need_debug_reporting); + #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(E3) - (void)monitor_tmc_driver(stepperE3, need_update_error_counters, need_debug_reporting); + #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(E4) - (void)monitor_tmc_driver(stepperE4, need_update_error_counters, need_debug_reporting); + #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(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); + #if W_IS_TRINAMIC + if (monitor_tmc_driver(stepperW, need_update_error_counters, need_debug_reporting)) + step_current_down(stepperW); #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(); } } @@ -457,12 +494,8 @@ void tmc_set_report_interval(const uint16_t update_interval) { if ((report_tmc_status_interval = update_interval)) SERIAL_ECHOLNPGM("axis:pwm_scale" - #if HAS_STEALTHCHOP - "/curr_scale" - #endif - #if HAS_STALLGUARD - "/mech_load" - #endif + TERN_(HAS_STEALTHCHOP, "/curr_scale") + TERN_(HAS_STALLGUARD, "/mech_load") "|flags|warncount" ); } @@ -490,14 +523,20 @@ TMC_TSTEP, TMC_TPWMTHRS, TMC_TPWMTHRS_MMS, - TMC_OTPW, + TMC_DEBUG_OTPW, TMC_OTPW_TRIGGERED, TMC_TOFF, TMC_TBL, TMC_HEND, TMC_HSTRT, TMC_SGT, - TMC_MSCNT + TMC_MSCNT, + TMC_INTERPOLATE, + TMC_VAIN, + TMC_VSUPPLY, + TMC_TEMP, + TMC_OVERTEMP, + TMC_OVERVOLT_THD }; enum TMC_drv_status_enum : char { TMC_DRV_CODES, @@ -541,138 +580,218 @@ 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) { serialprintPGM(st.vsense() ? PSTR("1=.18") : PSTR("0=.325")); } + 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_PRINT(st.PWM_SCALE(), DEC); break; - case TMC_SGT: SERIAL_PRINT(st.sgt(), DEC); break; - case TMC_STEALTHCHOP: serialprint_truefalse(st.en_pwm_mode()); break; + case TMC_PWM_SCALE: SERIAL_ECHO(st.PWM_SCALE()); break; + case TMC_SGT: SERIAL_ECHO(st.sgt()); 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; - case TMC_SG_RESULT: SERIAL_PRINT(st.sg_result(), DEC); break; + case TMC_SG_RESULT: SERIAL_ECHO(st.sg_result()); break; case TMC_FSACTIVE: if (st.fsactive()) SERIAL_CHAR('*'); break; - case TMC_DRV_CS_ACTUAL: SERIAL_PRINT(st.cs_actual(), DEC); break; + case TMC_DRV_CS_ACTUAL: SERIAL_ECHO(st.cs_actual()); break; default: break; } } #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_PRINT(st.PWM_SCALE(), DEC); break; - case TMC_SGT: SERIAL_PRINT(st.sgt(), DEC); break; - case TMC_STEALTHCHOP: serialprint_truefalse(st.en_pwm_mode()); break; - case TMC_GLOBAL_SCALER: - { - uint16_t value = st.GLOBAL_SCALER(); - SERIAL_PRINT(value ?: 256, DEC); - SERIAL_ECHOPGM("/256"); - } - break; + case TMC_PWM_SCALE: SERIAL_ECHO(st.PWM_SCALE()); break; + case TMC_SGT: SERIAL_ECHO(st.sgt()); 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) { - case TMC_PWM_SCALE_SUM: SERIAL_PRINT(st.pwm_scale_sum(), DEC); break; - case TMC_PWM_SCALE_AUTO: SERIAL_PRINT(st.pwm_scale_auto(), DEC); break; - case TMC_PWM_OFS_AUTO: SERIAL_PRINT(st.pwm_ofs_auto(), DEC); break; - case TMC_PWM_GRAD_AUTO: SERIAL_PRINT(st.pwm_grad_auto(), DEC); break; - case TMC_STEALTHCHOP: serialprint_truefalse(st.stealth()); break; - case TMC_S2VSA: if (st.s2vsa()) SERIAL_CHAR('*'); break; - case TMC_S2VSB: if (st.s2vsb()) SERIAL_CHAR('*'); break; + // 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; 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_PRINT(st.SGTHRS(), DEC); break; - case TMC_UART_ADDR: SERIAL_PRINT(st.get_address(), DEC); break; + 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; case TMC_T150: if (st.t150()) SERIAL_CHAR('*'); break; case TMC_T143: if (st.t143()) SERIAL_CHAR('*'); break; case TMC_T120: if (st.t120()) SERIAL_CHAR('*'); break; - case TMC_DRV_CS_ACTUAL: SERIAL_PRINT(st.cs_actual(), DEC); break; + case TMC_S2VSA: if (st.s2vsa()) SERIAL_CHAR('*'); break; + case TMC_S2VSB: if (st.s2vsb()) SERIAL_CHAR('*'); break; + case TMC_DRV_CS_ACTUAL: SERIAL_ECHO(st.cs_actual()); break; default: break; } } #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_PRINT(st.SG_RESULT(), DEC); break; - default: _tmc_parse_drv_status(static_cast(st), i); break; + case TMC_SG_RESULT: SERIAL_ECHO(st.SG_RESULT()); 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: 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_PRINT((float)st.rms_current() * 1.41, 0); break; - case TMC_IRUN: - SERIAL_PRINT(st.irun(), DEC); - SERIAL_ECHOPGM("/31"); - break; - case TMC_IHOLD: - SERIAL_PRINT(st.ihold(), DEC); - SERIAL_ECHOPGM("/31"); - break; - case TMC_CS_ACTUAL: - SERIAL_PRINT(st.cs_actual(), DEC); - SERIAL_ECHOPGM("/31"); - 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: 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: { @@ -680,15 +799,15 @@ 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_PRINT(st.toff(), DEC); break; - case TMC_TBL: SERIAL_PRINT(st.blank_time(), DEC); break; - case TMC_HEND: SERIAL_PRINT(st.hysteresis_end(), DEC); break; - case TMC_HSTRT: SERIAL_PRINT(st.hysteresis_start(), DEC); break; - case TMC_MSCNT: SERIAL_PRINT(st.get_microstep_counter(), DEC); break; + case TMC_TOFF: SERIAL_ECHO(st.toff()); 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; default: _tmc_status(st, i); break; } } @@ -699,177 +818,121 @@ 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_PRINT((float)st.rms_current() * 1.41, 0); break; - case TMC_IRUN: - SERIAL_PRINT(st.cs(), DEC); - SERIAL_ECHOPGM("/31"); - break; - case TMC_VSENSE: serialprintPGM(st.vsense() ? PSTR("1=.165") : PSTR("0=.310")); 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_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_SGT: SERIAL_PRINT(st.sgt(), DEC); break; - case TMC_TOFF: SERIAL_PRINT(st.toff(), DEC); break; - case TMC_TBL: SERIAL_PRINT(st.blank_time(), DEC); break; - case TMC_HEND: SERIAL_PRINT(st.hysteresis_end(), DEC); break; - case TMC_HSTRT: SERIAL_PRINT(st.hysteresis_start(), DEC); break; - default: 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: print_blank_time(st); break; + case TMC_HEND: SERIAL_ECHO(st.hysteresis_end()); break; + case TMC_HSTRT: SERIAL_ECHO(st.hysteresis_start()); 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, ':'); + 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 i, const bool print_x, const bool print_y, const bool print_z, const bool print_e) { - if (print_x) { - #if AXIS_IS_TMC(X) - tmc_status(stepperX, i); - #endif - #if AXIS_IS_TMC(X2) - tmc_status(stepperX2, i); - #endif + static void tmc_debug_loop(const TMC_debug_enum n OPTARGS_LOGICAL(const bool)) { + if (TERN0(HAS_X_AXIS, x)) { + TERN_(X_IS_TRINAMIC, tmc_status(stepperX, n)); + TERN_(X2_IS_TRINAMIC, tmc_status(stepperX2, n)); } - if (print_y) { - #if AXIS_IS_TMC(Y) - tmc_status(stepperY, i); - #endif - #if AXIS_IS_TMC(Y2) - tmc_status(stepperY2, i); - #endif + if (TERN0(HAS_Y_AXIS, y)) { + TERN_(Y_IS_TRINAMIC, tmc_status(stepperY, n)); + TERN_(Y2_IS_TRINAMIC, tmc_status(stepperY2, n)); } - if (print_z) { - #if AXIS_IS_TMC(Z) - tmc_status(stepperZ, i); - #endif - #if AXIS_IS_TMC(Z2) - tmc_status(stepperZ2, i); - #endif - #if AXIS_IS_TMC(Z3) - tmc_status(stepperZ3, i); - #endif - #if AXIS_IS_TMC(Z4) - tmc_status(stepperZ4, i); - #endif + if (TERN0(HAS_Z_AXIS, z)) { + 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 (print_e) { - #if AXIS_IS_TMC(E0) - tmc_status(stepperE0, i); - #endif - #if AXIS_IS_TMC(E1) - tmc_status(stepperE1, i); - #endif - #if AXIS_IS_TMC(E2) - tmc_status(stepperE2, i); - #endif - #if AXIS_IS_TMC(E3) - tmc_status(stepperE3, i); - #endif - #if AXIS_IS_TMC(E4) - tmc_status(stepperE4, i); - #endif - #if AXIS_IS_TMC(E5) - tmc_status(stepperE5, i); - #endif - #if AXIS_IS_TMC(E6) - tmc_status(stepperE6, i); - #endif - #if AXIS_IS_TMC(E7) - tmc_status(stepperE7, i); - #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)) { + 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(); } - static void drv_status_loop(const TMC_drv_status_enum i, const bool print_x, const bool print_y, const bool print_z, const bool print_e) { - if (print_x) { - #if AXIS_IS_TMC(X) - tmc_parse_drv_status(stepperX, i); - #endif - #if AXIS_IS_TMC(X2) - tmc_parse_drv_status(stepperX2, i); - #endif + static void drv_status_loop(const TMC_drv_status_enum n OPTARGS_LOGICAL(const bool)) { + if (TERN0(HAS_X_AXIS, x)) { + TERN_(X_IS_TRINAMIC, tmc_parse_drv_status(stepperX, n)); + TERN_(X2_IS_TRINAMIC, tmc_parse_drv_status(stepperX2, n)); } - if (print_y) { - #if AXIS_IS_TMC(Y) - tmc_parse_drv_status(stepperY, i); - #endif - #if AXIS_IS_TMC(Y2) - tmc_parse_drv_status(stepperY2, i); - #endif + if (TERN0(HAS_Y_AXIS, y)) { + TERN_(Y_IS_TRINAMIC, tmc_parse_drv_status(stepperY, n)); + TERN_(Y2_IS_TRINAMIC, tmc_parse_drv_status(stepperY2, n)); } - if (print_z) { - #if AXIS_IS_TMC(Z) - tmc_parse_drv_status(stepperZ, i); - #endif - #if AXIS_IS_TMC(Z2) - tmc_parse_drv_status(stepperZ2, i); - #endif - #if AXIS_IS_TMC(Z3) - tmc_parse_drv_status(stepperZ3, i); - #endif - #if AXIS_IS_TMC(Z4) - tmc_parse_drv_status(stepperZ4, i); - #endif + if (TERN0(HAS_Z_AXIS, z)) { + 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 (print_e) { - #if AXIS_IS_TMC(E0) - tmc_parse_drv_status(stepperE0, i); - #endif - #if AXIS_IS_TMC(E1) - tmc_parse_drv_status(stepperE1, i); - #endif - #if AXIS_IS_TMC(E2) - tmc_parse_drv_status(stepperE2, i); - #endif - #if AXIS_IS_TMC(E3) - tmc_parse_drv_status(stepperE3, i); - #endif - #if AXIS_IS_TMC(E4) - tmc_parse_drv_status(stepperE4, i); - #endif - #if AXIS_IS_TMC(E5) - tmc_parse_drv_status(stepperE5, i); - #endif - #if AXIS_IS_TMC(E6) - tmc_parse_drv_status(stepperE6, i); - #endif - #if AXIS_IS_TMC(E7) - tmc_parse_drv_status(stepperE7, i); - #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)) { + 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(); @@ -879,9 +942,10 @@ * M122 report functions */ - void tmc_report_all(bool print_x, const bool print_y, const bool print_z, const bool print_e) { - #define TMC_REPORT(LABEL, ITEM) do{ SERIAL_ECHOPGM(LABEL); tmc_debug_loop(ITEM, print_x, print_y, print_z, print_e); }while(0) - #define DRV_REPORT(LABEL, ITEM) do{ SERIAL_ECHOPGM(LABEL); drv_status_loop(ITEM, print_x, print_y, print_z, print_e); }while(0) + 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) + TMC_REPORT("\t", TMC_CODES); #if HAS_DRIVER(TMC2209) TMC_REPORT("Address\t", TMC_UART_ADDR); @@ -902,19 +966,20 @@ #endif TMC_REPORT("stealthChop", TMC_STEALTHCHOP); TMC_REPORT("msteps\t", TMC_MICROSTEPS); + TMC_REPORT("interp\t", TMC_INTERPOLATE); 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); @@ -923,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 @@ -943,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; - } - } - #endif - #if HAS_TMC220x - static void tmc_get_ic_registers(TMC2208Stepper, const TMC_get_registers_enum) { SERIAL_CHAR('\t'); } - #endif - #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 + template static void tmc_get_registers(TMC &st, const TMC_get_registers_enum i) { switch (i) { @@ -986,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) { @@ -1004,72 +1082,47 @@ } #endif - static void tmc_get_registers(TMC_get_registers_enum i, const bool print_x, const bool print_y, const bool print_z, const bool print_e) { - if (print_x) { - #if AXIS_IS_TMC(X) - tmc_get_registers(stepperX, i); - #endif - #if AXIS_IS_TMC(X2) - tmc_get_registers(stepperX2, i); - #endif + static void tmc_get_registers(TMC_get_registers_enum n OPTARGS_LOGICAL(const bool)) { + if (TERN0(HAS_X_AXIS, x)) { + TERN_(X_IS_TRINAMIC, tmc_get_registers(stepperX, n)); + TERN_(X2_IS_TRINAMIC, tmc_get_registers(stepperX2, n)); } - if (print_y) { - #if AXIS_IS_TMC(Y) - tmc_get_registers(stepperY, i); - #endif - #if AXIS_IS_TMC(Y2) - tmc_get_registers(stepperY2, i); - #endif + if (TERN0(HAS_Y_AXIS, y)) { + TERN_(Y_IS_TRINAMIC, tmc_get_registers(stepperY, n)); + TERN_(Y2_IS_TRINAMIC, tmc_get_registers(stepperY2, n)); } - if (print_z) { - #if AXIS_IS_TMC(Z) - tmc_get_registers(stepperZ, i); - #endif - #if AXIS_IS_TMC(Z2) - tmc_get_registers(stepperZ2, i); - #endif - #if AXIS_IS_TMC(Z3) - tmc_get_registers(stepperZ3, i); - #endif - #if AXIS_IS_TMC(Z4) - tmc_get_registers(stepperZ4, i); - #endif + if (TERN0(HAS_Z_AXIS, z)) { + 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 (print_e) { - #if AXIS_IS_TMC(E0) - tmc_get_registers(stepperE0, i); - #endif - #if AXIS_IS_TMC(E1) - tmc_get_registers(stepperE1, i); - #endif - #if AXIS_IS_TMC(E2) - tmc_get_registers(stepperE2, i); - #endif - #if AXIS_IS_TMC(E3) - tmc_get_registers(stepperE3, i); - #endif - #if AXIS_IS_TMC(E4) - tmc_get_registers(stepperE4, i); - #endif - #if AXIS_IS_TMC(E5) - tmc_get_registers(stepperE5, i); - #endif - #if AXIS_IS_TMC(E6) - tmc_get_registers(stepperE6, i); - #endif - #if AXIS_IS_TMC(E7) - tmc_get_registers(stepperE7, i); - #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)) { + 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(bool print_x, bool print_y, bool print_z, bool print_e) { - #define _TMC_GET_REG(LABEL, ITEM) do{ SERIAL_ECHOPGM(LABEL); tmc_get_registers(ITEM, print_x, print_y, print_z, print_e); }while(0) + 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); TMC_GET_REG(GCONF, "\t\t"); @@ -1119,68 +1172,32 @@ 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 -#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(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 - template static bool test_connection(TMC &st) { SERIAL_ECHOPGM("Testing "); @@ -1190,83 +1207,57 @@ static bool test_connection(TMC &st) { if (test_result > 0) SERIAL_ECHOPGM("Error: All "); - const char *stat; + FSTR_P stat; switch (test_result) { default: - case 0: stat = PSTR("OK"); break; - case 1: stat = PSTR("HIGH"); break; - case 2: stat = PSTR("LOW"); break; + case 0: stat = F("OK"); break; + case 1: stat = F("HIGH"); break; + case 2: stat = F("LOW"); break; } - serialprintPGM(stat); - SERIAL_EOL(); + SERIAL_ECHOLN(stat); return test_result; } -void test_tmc_connection(const bool test_x, const bool test_y, const bool test_z, const bool test_e) { +void test_tmc_connection(LOGICAL_AXIS_ARGS_LC(const bool)) { uint8_t axis_connection = 0; - if (test_x) { - #if AXIS_IS_TMC(X) - axis_connection += test_connection(stepperX); - #endif - #if AXIS_IS_TMC(X2) - axis_connection += test_connection(stepperX2); - #endif + if (TERN0(HAS_X_AXIS, x)) { + TERN_(X_IS_TRINAMIC, axis_connection += test_connection(stepperX)); + TERN_(X2_IS_TRINAMIC, axis_connection += test_connection(stepperX2)); } - if (test_y) { - #if AXIS_IS_TMC(Y) - axis_connection += test_connection(stepperY); - #endif - #if AXIS_IS_TMC(Y2) - axis_connection += test_connection(stepperY2); - #endif + if (TERN0(HAS_Y_AXIS, y)) { + TERN_(Y_IS_TRINAMIC, axis_connection += test_connection(stepperY)); + TERN_(Y2_IS_TRINAMIC, axis_connection += test_connection(stepperY2)); } - if (test_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 + if (TERN0(HAS_Z_AXIS, z)) { + 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 (test_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_(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)) { + 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_MESSAGEPGM(MSG_ERROR_TMC); + if (axis_connection) LCD_MESSAGE(MSG_ERROR_TMC); } #endif // HAS_TRINAMIC_CONFIG diff --git a/Marlin/src/feature/tmc_util.h b/Marlin/src/feature/tmc_util.h index b65a1254c6..556035f08c 100644 --- a/Marlin/src/feature/tmc_util.h +++ b/Marlin/src/feature/tmc_util.h @@ -22,14 +22,14 @@ #pragma once #include "../inc/MarlinConfig.h" -#include "../lcd/ultralcd.h" +#include "../lcd/marlinui.h" #if HAS_TRINAMIC_CONFIG #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 } @@ -45,6 +45,12 @@ constexpr uint16_t _tmc_thrs(const uint16_t msteps, const uint32_t thrs, const u return 12650000UL * msteps / (256 * thrs * spmm); } +typedef struct { + uint8_t toff; + int8_t hend; + uint8_t hstrt; +} chopper_timing_t; + template class TMCStorage { protected: @@ -58,21 +64,21 @@ class TMCStorage { uint8_t otpw_count = 0, error_count = 0; bool flag_otpw = false; - inline bool getOTPW() { return flag_otpw; } - inline void clear_otpw() { flag_otpw = 0; } + bool getOTPW() { return flag_otpw; } + void clear_otpw() { flag_otpw = 0; } #endif - inline uint16_t getMilliamps() { return val_mA; } + uint16_t getMilliamps() { return val_mA; } - inline void printLabel() { + void printLabel() { SERIAL_CHAR(AXIS_LETTER); if (DRIVER_ID > '0') SERIAL_CHAR(DRIVER_ID); } struct { - TERN_(HAS_STEALTHCHOP, bool stealthChop_enabled = false); - TERN_(HYBRID_THRESHOLD, uint8_t hybrid_thrs = 0); - TERN_(USE_SENSORLESS, int16_t homing_thrs = 0); + OPTCODE(HAS_STEALTHCHOP, bool stealthChop_enabled = false) + OPTCODE(HYBRID_THRESHOLD, uint16_t hybrid_thrs = 0) + OPTCODE(USE_SENSORLESS, int16_t homing_thrs = 0) } stored; }; @@ -89,56 +95,62 @@ 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) {} - inline uint16_t rms_current() { return TMC::rms_current(); } - inline void rms_current(uint16_t mA) { + uint16_t rms_current() { return TMC::rms_current(); } + void rms_current(uint16_t mA) { this->val_mA = mA; TMC::rms_current(mA); } - inline void rms_current(const uint16_t mA, const float mult) { + void rms_current(const uint16_t mA, const float mult) { this->val_mA = mA; TMC::rms_current(mA, mult); } - inline uint16_t get_microstep_counter() { return TMC::MSCNT(); } + uint16_t get_microstep_counter() { return TMC::MSCNT(); } #if HAS_STEALTHCHOP - inline void refresh_stepping_mode() { this->en_pwm_mode(this->stored.stealthChop_enabled); } - inline bool get_stealthChop_status() { return this->en_pwm_mode(); } - inline bool get_stored_stealthChop_status() { return this->stored.stealthChop_enabled; } + 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) { + TMC::toff(ct.toff); + TMC::hysteresis_end(ct.hend); + TMC::hysteresis_start(ct.hstrt); + } + #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])); - TERN_(HAS_LCD_MENU, this->stored.hybrid_thrs = thrs); + TERN_(HAS_MARLINUI_MENU, this->stored.hybrid_thrs = thrs); } #endif #if USE_SENSORLESS - inline int16_t homing_threshold() { return TMC::sgt(); } + int16_t homing_threshold() { return TMC::sgt(); } void homing_threshold(int16_t sgt_val) { sgt_val = (int16_t)constrain(sgt_val, sgt_min, sgt_max); TMC::sgt(sgt_val); - TERN_(HAS_LCD_MENU, this->stored.homing_thrs = sgt_val); + TERN_(HAS_MARLINUI_MENU, this->stored.homing_thrs = sgt_val); } #if ENABLED(SPI_ENDSTOPS) bool test_stall_status(); #endif #endif - #if HAS_LCD_MENU - inline void refresh_stepper_current() { rms_current(this->val_mA); } + void refresh_stepper_current() { rms_current(this->val_mA); } - #if ENABLED(HYBRID_THRESHOLD) - inline void refresh_hybrid_thrs() { set_pwm_thrs(this->stored.hybrid_thrs); } - #endif - #if USE_SENSORLESS - inline 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, @@ -159,38 +171,44 @@ class TMCMarlin : public TMC220 {} uint16_t rms_current() { return TMC2208Stepper::rms_current(); } - inline void rms_current(const uint16_t mA) { + void rms_current(const uint16_t mA) { this->val_mA = mA; TMC2208Stepper::rms_current(mA); } - inline void rms_current(const uint16_t mA, const float mult) { + void rms_current(const uint16_t mA, const float mult) { this->val_mA = mA; TMC2208Stepper::rms_current(mA, mult); } - inline uint16_t get_microstep_counter() { return TMC2208Stepper::MSCNT(); } + uint16_t get_microstep_counter() { return TMC2208Stepper::MSCNT(); } #if HAS_STEALTHCHOP - inline void refresh_stepping_mode() { en_spreadCycle(!this->stored.stealthChop_enabled); } - inline bool get_stealthChop_status() { return !this->en_spreadCycle(); } - inline bool get_stored_stealthChop_status() { return this->stored.stealthChop_enabled; } + bool get_stealthChop() { return !this->en_spreadCycle(); } + bool get_stored_stealthChop() { return this->stored.stealthChop_enabled; } + void refresh_stepping_mode() { this->en_spreadCycle(!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) { + TMC2208Stepper::toff(ct.toff); + TMC2208Stepper::hysteresis_end(ct.hend); + TMC2208Stepper::hysteresis_start(ct.hstrt); + } + #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])); - TERN_(HAS_LCD_MENU, this->stored.hybrid_thrs = thrs); + TERN_(HAS_MARLINUI_MENU, this->stored.hybrid_thrs = thrs); } #endif - #if HAS_LCD_MENU - inline void refresh_stepper_current() { rms_current(this->val_mA); } + void refresh_stepper_current() { rms_current(this->val_mA); } - #if ENABLED(HYBRID_THRESHOLD) - inline 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 }; @@ -205,55 +223,130 @@ class TMCMarlin : public TMC220 {} uint8_t get_address() { return slave_address; } uint16_t rms_current() { return TMC2209Stepper::rms_current(); } - inline void rms_current(const uint16_t mA) { + void rms_current(const uint16_t mA) { this->val_mA = mA; TMC2209Stepper::rms_current(mA); } - inline void rms_current(const uint16_t mA, const float mult) { + void rms_current(const uint16_t mA, const float mult) { this->val_mA = mA; TMC2209Stepper::rms_current(mA, mult); } - inline uint16_t get_microstep_counter() { return TMC2209Stepper::MSCNT(); } + uint16_t get_microstep_counter() { return TMC2209Stepper::MSCNT(); } #if HAS_STEALTHCHOP - inline void refresh_stepping_mode() { en_spreadCycle(!this->stored.stealthChop_enabled); } - inline bool get_stealthChop_status() { return !this->en_spreadCycle(); } - inline bool get_stored_stealthChop_status() { return this->stored.stealthChop_enabled; } + bool get_stealthChop() { return !this->en_spreadCycle(); } + bool get_stored_stealthChop() { return this->stored.stealthChop_enabled; } + void refresh_stepping_mode() { this->en_spreadCycle(!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) { + TMC2209Stepper::toff(ct.toff); + TMC2209Stepper::hysteresis_end(ct.hend); + TMC2209Stepper::hysteresis_start(ct.hstrt); + } + #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_LCD_MENU, this->stored.hybrid_thrs = thrs); + TERN_(HAS_MARLINUI_MENU, this->stored.hybrid_thrs = thrs); } #endif + #if USE_SENSORLESS - inline int16_t homing_threshold() { return TMC2209Stepper::SGTHRS(); } + int16_t homing_threshold() { return TMC2209Stepper::SGTHRS(); } void homing_threshold(int16_t sgt_val) { sgt_val = (int16_t)constrain(sgt_val, sgt_min, sgt_max); TMC2209Stepper::SGTHRS(sgt_val); - TERN_(HAS_LCD_MENU, this->stored.homing_thrs = sgt_val); + TERN_(HAS_MARLINUI_MENU, this->stored.homing_thrs = sgt_val); } #endif - #if HAS_LCD_MENU - inline void refresh_stepper_current() { rms_current(this->val_mA); } + void refresh_stepper_current() { rms_current(this->val_mA); } - #if ENABLED(HYBRID_THRESHOLD) - inline void refresh_hybrid_thrs() { set_pwm_thrs(this->stored.hybrid_thrs); } - #endif - #if USE_SENSORLESS - inline 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: @@ -263,80 +356,47 @@ class TMCMarlin : public TMC266 TMCMarlin(const uint16_t CS, const float RS, const uint16_t pinMOSI, const uint16_t pinMISO, const uint16_t pinSCK, const uint8_t) : TMC2660Stepper(CS, RS, pinMOSI, pinMISO, pinSCK) {} - inline uint16_t rms_current() { return TMC2660Stepper::rms_current(); } - inline void rms_current(const uint16_t mA) { + uint16_t rms_current() { return TMC2660Stepper::rms_current(); } + void rms_current(const uint16_t mA) { this->val_mA = mA; TMC2660Stepper::rms_current(mA); } - inline uint16_t get_microstep_counter() { return TMC2660Stepper::mstep(); } + uint16_t get_microstep_counter() { return TMC2660Stepper::mstep(); } + + void set_chopper_times(const chopper_timing_t &ct) { + TMC2660Stepper::toff(ct.toff); + TMC2660Stepper::hysteresis_end(ct.hend); + TMC2660Stepper::hysteresis_start(ct.hstrt); + } #if USE_SENSORLESS - inline int16_t homing_threshold() { return TMC2660Stepper::sgt(); } + int16_t homing_threshold() { return TMC2660Stepper::sgt(); } void homing_threshold(int16_t sgt_val) { sgt_val = (int16_t)constrain(sgt_val, sgt_min, sgt_max); TMC2660Stepper::sgt(sgt_val); - TERN_(HAS_LCD_MENU, this->stored.homing_thrs = sgt_val); + TERN_(HAS_MARLINUI_MENU, this->stored.homing_thrs = sgt_val); } #endif - #if HAS_LCD_MENU - inline void refresh_stepper_current() { rms_current(this->val_mA); } + void refresh_stepper_current() { rms_current(this->val_mA); } - #if USE_SENSORLESS - inline 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, sgt_max = 63; }; -template -void tmc_print_current(TMC &st) { - st.printLabel(); - SERIAL_ECHOLNPAIR(" driver current: ", st.getMilliamps()); -} - -#if ENABLED(MONITOR_DRIVER_STATUS) - template - void tmc_report_otpw(TMC &st) { - st.printLabel(); - SERIAL_ECHOPGM(" temperature prewarn triggered: "); - serialprint_truefalse(st.getOTPW()); - SERIAL_EOL(); - } - template - void tmc_clear_otpw(TMC &st) { - st.clear_otpw(); - st.printLabel(); - SERIAL_ECHOLNPGM(" prewarn flag cleared"); - } -#endif -#if ENABLED(HYBRID_THRESHOLD) - template - void tmc_print_pwmthrs(TMC &st) { - st.printLabel(); - SERIAL_ECHOLNPAIR(" stealthChop max speed: ", st.get_pwm_thrs()); - } -#endif -#if USE_SENSORLESS - template - void tmc_print_sgt(TMC &st) { - st.printLabel(); - SERIAL_ECHOPGM(" homing sensitivity: "); - SERIAL_PRINTLN(st.homing_threshold(), DEC); - } -#endif - void monitor_tmc_drivers(); -void test_tmc_connection(const bool test_x, const bool test_y, const bool test_z, const bool test_e); +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(const bool print_x, const bool print_y, const bool print_z, const bool print_e); - void tmc_get_registers(const bool print_x, const bool print_y, const bool print_z, const bool print_e); + void tmc_report_all(LOGICAL_AXIS_DECL_LC(const bool, true)); + void tmc_get_registers(LOGICAL_AXIS_ARGS_LC(const bool)); #endif /** @@ -349,16 +409,11 @@ void test_tmc_connection(const bool test_x, const bool test_y, const bool test_z #if USE_SENSORLESS // Track enabled status of stealthChop and only re-enable where applicable - struct sensorless_t { bool x, y, z, x2, y2, z2, z3, z4; }; + struct sensorless_t { bool NUM_AXIS_ARGS_() x2, y2, z2, z3, z4; }; #if ENABLED(IMPROVE_HOMING_RELIABILITY) extern millis_t sg_guard_period; constexpr uint16_t default_sg_guard_duration = 400; - - struct slow_homing_t { - xy_ulong_t acceleration; - TERN_(HAS_CLASSIC_JERK, xy_float_t jerk_xy); - }; #endif bool tmc_enable_stallguard(TMC2130Stepper &st); @@ -367,6 +422,9 @@ void test_tmc_connection(const bool test_x, const bool test_y, const bool test_z 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); @@ -376,7 +434,7 @@ void test_tmc_connection(const bool test_x, const bool test_y, const bool test_z 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(); @@ -384,12 +442,35 @@ void test_tmc_connection(const bool test_x, const bool test_y, const bool test_z return drv_status.stallGuard; } + #endif // SPI_ENDSTOPS #endif // USE_SENSORLESS -#if HAS_TMC_SPI - void tmc_init_cs_pins(); -#endif +#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 + +#endif // HAS_HOMING_CURRENT #endif // HAS_TRINAMIC_CONFIG diff --git a/Marlin/src/feature/tramming.cpp b/Marlin/src/feature/tramming.cpp new file mode 100644 index 0000000000..3721c5eb81 --- /dev/null +++ b/Marlin/src/feature/tramming.cpp @@ -0,0 +1,49 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2020 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(ASSISTED_TRAMMING) + +#include "tramming.h" + +#define DEBUG_OUT ENABLED(DEBUG_LEVELING_FEATURE) +#include "../core/debug_out.h" + +#define _TRAM_NAME_DEF(N) PGMSTR(point_name_##N, TRAMMING_POINT_NAME_##N); +#define _TRAM_NAME_ITEM(N) point_name_##N +REPEAT_1(_NR_TRAM_NAMES, _TRAM_NAME_DEF) + +PGM_P const tramming_point_name[] PROGMEM = { REPLIST_1(_NR_TRAM_NAMES, _TRAM_NAME_ITEM) }; + +#ifdef ASSISTED_TRAMMING_WAIT_POSITION + + // Move to the defined wait position + 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); + } + +#endif + +#endif // ASSISTED_TRAMMING diff --git a/Marlin/src/feature/tramming.h b/Marlin/src/feature/tramming.h new file mode 100644 index 0000000000..de4c6c020c --- /dev/null +++ b/Marlin/src/feature/tramming.h @@ -0,0 +1,76 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2020 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 "../module/probe.h" + +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; + +#define G35_PROBE_COUNT COUNT(tramming_points) +static_assert(WITHIN(G35_PROBE_COUNT, 3, 9), "TRAMMING_POINT_XY requires between 3 and 9 XY positions."); + +#ifdef TRAMMING_POINT_NAME_9 + #define _NR_TRAM_NAMES 9 +#elif defined(TRAMMING_POINT_NAME_8) + #define _NR_TRAM_NAMES 8 +#elif defined(TRAMMING_POINT_NAME_7) + #define _NR_TRAM_NAMES 7 +#elif defined(TRAMMING_POINT_NAME_6) + #define _NR_TRAM_NAMES 6 +#elif defined(TRAMMING_POINT_NAME_5) + #define _NR_TRAM_NAMES 5 +#elif defined(TRAMMING_POINT_NAME_4) + #define _NR_TRAM_NAMES 4 +#elif defined(TRAMMING_POINT_NAME_3) + #define _NR_TRAM_NAMES 3 +#else + #define _NR_TRAM_NAMES 0 +#endif + +static_assert(_NR_TRAM_NAMES >= G35_PROBE_COUNT, "Define enough TRAMMING_POINT_NAME_s for all TRAMMING_POINT_XY entries."); + +#define _TRAM_NAME_PTR(N) point_name_##N[] +extern const char REPLIST_1(_NR_TRAM_NAMES, _TRAM_NAME_PTR); + +#define _CHECK_TRAM_POINT(N) static_assert(Probe::build_time::can_reach(tramming_points[N]), "TRAMMING_POINT_XY point " STRINGIFY(N) " is not reachable with the default NOZZLE_TO_PROBE offset and PROBING_MARGIN."); +REPEAT(_NR_TRAM_NAMES, _CHECK_TRAM_POINT) +#undef _CHECK_TRAM_POINT + +extern PGM_P const tramming_point_name[]; + +#ifdef ASSISTED_TRAMMING_WAIT_POSITION + void move_to_tramming_wait_pos(); +#else + inline void move_to_tramming_wait_pos() {} +#endif diff --git a/Marlin/src/feature/twibus.cpp b/Marlin/src/feature/twibus.cpp index 3cc20579ac..5cfe9f9421 100644 --- a/Marlin/src/feature/twibus.cpp +++ b/Marlin/src/feature/twibus.cpp @@ -28,11 +28,24 @@ #include +#include "../libs/hex_print.h" + +TWIBus i2c; + TWIBus::TWIBus() { #if I2C_SLAVE_ADDRESS == 0 - Wire.begin(); // No address joins the BUS as the master + + #if PINS_EXIST(I2C_SCL, I2C_SDA) && DISABLED(SOFT_I2C_EEPROM) + Wire.setSDA(pin_t(I2C_SDA_PIN)); + Wire.setSCL(pin_t(I2C_SCL_PIN)); + #endif + + Wire.begin(); // No address joins the BUS as the master + #else - Wire.begin(I2C_SLAVE_ADDRESS); // Join the bus as a slave + + Wire.begin(I2C_SLAVE_ADDRESS); // Join the bus as a slave + #endif reset(); } @@ -43,33 +56,32 @@ void TWIBus::reset() { } void TWIBus::address(const uint8_t adr) { - if (!WITHIN(adr, 8, 127)) { + if (!WITHIN(adr, 8, 127)) SERIAL_ECHO_MSG("Bad I2C address (8-127)"); - } addr = adr; - debug(PSTR("address"), adr); + debug(F("address"), adr); } void TWIBus::addbyte(const char c) { if (buffer_s >= COUNT(buffer)) return; buffer[buffer_s++] = c; - debug(PSTR("addbyte"), c); + debug(F("addbyte"), c); } void TWIBus::addbytes(char src[], uint8_t bytes) { - debug(PSTR("addbytes"), bytes); + debug(F("addbytes"), bytes); while (bytes--) addbyte(*src++); } void TWIBus::addstring(char str[]) { - debug(PSTR("addstring"), str); + debug(F("addstring"), str); while (char c = *str++) addbyte(c); } void TWIBus::send() { - debug(PSTR("send"), addr); + debug(F("send"), addr); Wire.beginTransmission(I2C_ADDRESS(addr)); Wire.write(buffer, buffer_s); @@ -79,44 +91,82 @@ void TWIBus::send() { } // static -void TWIBus::echoprefix(uint8_t bytes, const char pref[], uint8_t adr) { +void TWIBus::echoprefix(uint8_t bytes, FSTR_P const pref, uint8_t adr) { SERIAL_ECHO_START(); - serialprintPGM(pref); - SERIAL_ECHOPAIR(": from:", adr, " bytes:", bytes, " data:"); + SERIAL_ECHO(pref, F(": from:"), adr, F(" bytes:"), bytes, F(" data:")); } // static -void TWIBus::echodata(uint8_t bytes, const char pref[], uint8_t adr) { +void TWIBus::echodata(uint8_t bytes, FSTR_P const pref, uint8_t adr, const uint8_t style/*=0*/) { + union TwoBytesToInt16 { uint8_t bytes[2]; int16_t integervalue; }; + TwoBytesToInt16 ConversionUnion; + echoprefix(bytes, pref, adr); - while (bytes-- && Wire.available()) SERIAL_CHAR(Wire.read()); + + while (bytes-- && Wire.available()) { + int value = Wire.read(); + switch (style) { + + // Style 1, HEX DUMP + case 1: + SERIAL_CHAR(hex_nybble((value & 0xF0) >> 4)); + SERIAL_CHAR(hex_nybble(value & 0x0F)); + if (bytes) SERIAL_CHAR(' '); + break; + + // Style 2, signed two byte integer (int16) + case 2: + if (bytes == 1) + ConversionUnion.bytes[1] = (uint8_t)value; + else if (bytes == 0) { + ConversionUnion.bytes[0] = (uint8_t)value; + // Output value in base 10 (standard decimal) + SERIAL_ECHO(ConversionUnion.integervalue); + } + break; + + // Style 3, unsigned byte, base 10 (uint8) + case 3: + SERIAL_ECHO(value); + if (bytes) SERIAL_CHAR(' '); + break; + + // Default style (zero), raw serial output + default: + // This can cause issues with some serial consoles, Pronterface is an example where things go wrong + SERIAL_CHAR(value); + break; + } + } + SERIAL_EOL(); } -void TWIBus::echobuffer(const char pref[], uint8_t adr) { - echoprefix(buffer_s, pref, adr); - LOOP_L_N(i, buffer_s) SERIAL_CHAR(buffer[i]); +void TWIBus::echobuffer(FSTR_P const prefix, uint8_t adr) { + echoprefix(buffer_s, prefix, adr); + for (uint8_t i = 0; i < buffer_s; ++i) SERIAL_CHAR(buffer[i]); SERIAL_EOL(); } bool TWIBus::request(const uint8_t bytes) { if (!addr) return false; - debug(PSTR("request"), bytes); + debug(F("request"), bytes); // requestFrom() is a blocking function if (Wire.requestFrom(I2C_ADDRESS(addr), bytes) == 0) { - debug("request fail", I2C_ADDRESS(addr)); + debug(F("request fail"), I2C_ADDRESS(addr)); return false; } return true; } -void TWIBus::relay(const uint8_t bytes) { - debug(PSTR("relay"), bytes); +void TWIBus::relay(const uint8_t bytes, const uint8_t style/*=0*/) { + debug(F("relay"), bytes); if (request(bytes)) - echodata(bytes, PSTR("i2c-reply"), addr); + echodata(bytes, F("i2c-reply"), addr, style); } uint8_t TWIBus::capture(char *dst, const uint8_t bytes) { @@ -125,7 +175,7 @@ uint8_t TWIBus::capture(char *dst, const uint8_t bytes) { while (count < bytes && Wire.available()) dst[count++] = Wire.read(); - debug(PSTR("capture"), count); + debug(F("capture"), count); return count; } @@ -138,12 +188,12 @@ void TWIBus::flush() { #if I2C_SLAVE_ADDRESS > 0 void TWIBus::receive(uint8_t bytes) { - debug(PSTR("receive"), bytes); - echodata(bytes, PSTR("i2c-receive"), 0); + debug(F("receive"), bytes); + echodata(bytes, F("i2c-receive"), 0); } void TWIBus::reply(char str[]/*=nullptr*/) { - debug(PSTR("reply"), str); + debug(F("reply"), str); if (str) { reset(); @@ -155,23 +205,29 @@ void TWIBus::flush() { reset(); } + void i2c_on_receive(int bytes) { // just echo all bytes received to serial + i2c.receive(bytes); + } + + void i2c_on_request() { // just send dummy data for now + i2c.reply("Hello World!\n"); + } + #endif #if ENABLED(DEBUG_TWIBUS) // static - void TWIBus::prefix(const char func[]) { - SERIAL_ECHOPGM("TWIBus::"); - serialprintPGM(func); - SERIAL_ECHOPGM(": "); + void TWIBus::prefix(FSTR_P const func) { + SERIAL_ECHOPGM("TWIBus::", func, ": "); } - void TWIBus::debug(const char func[], uint32_t adr) { + void TWIBus::debug(FSTR_P const func, uint32_t adr) { if (DEBUGGING(INFO)) { prefix(func); SERIAL_ECHOLN(adr); } } - void TWIBus::debug(const char func[], char c) { + void TWIBus::debug(FSTR_P const func, char c) { if (DEBUGGING(INFO)) { prefix(func); SERIAL_ECHOLN(c); } } - void TWIBus::debug(const char func[], char str[]) { + void TWIBus::debug(FSTR_P const func, char str[]) { if (DEBUGGING(INFO)) { prefix(func); SERIAL_ECHOLN(str); } } diff --git a/Marlin/src/feature/twibus.h b/Marlin/src/feature/twibus.h index 82aa9aa16a..b438d10a33 100644 --- a/Marlin/src/feature/twibus.h +++ b/Marlin/src/feature/twibus.h @@ -31,6 +31,17 @@ typedef void (*twiReceiveFunc_t)(int bytes); typedef void (*twiRequestFunc_t)(); +/** + * For a light i2c protocol that runs on two boards running Marlin see: + * See https://github.com/MarlinFirmware/Marlin/issues/4776#issuecomment-246262879 + */ +#if I2C_SLAVE_ADDRESS > 0 + + void i2c_on_receive(int bytes); // Demo i2c onReceive handler + void i2c_on_request(); // Demo i2c onRequest handler + +#endif + #define TWIBUS_BUFFER_SIZE 32 /** @@ -53,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; @@ -63,11 +74,10 @@ class TWIBus { */ uint8_t buffer[TWIBUS_BUFFER_SIZE]; - 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; @@ -131,7 +141,7 @@ class TWIBus { * * @param bytes the number of bytes to request */ - static void echoprefix(uint8_t bytes, const char prefix[], uint8_t adr); + static void echoprefix(uint8_t bytes, FSTR_P const prefix, uint8_t adr); /** * @brief Echo data on the bus to serial @@ -139,8 +149,9 @@ class TWIBus { * to serial in a parser-friendly format. * * @param bytes the number of bytes to request + * @param style Output format for the bytes, 0 = Raw byte [default], 1 = Hex characters, 2 = uint16_t */ - static void echodata(uint8_t bytes, const char prefix[], uint8_t adr); + static void echodata(uint8_t bytes, FSTR_P const prefix, uint8_t adr, const uint8_t style=0); /** * @brief Echo data in the buffer to serial @@ -149,7 +160,7 @@ class TWIBus { * * @param bytes the number of bytes to request */ - void echobuffer(const char prefix[], uint8_t adr); + void echobuffer(FSTR_P const prefix, uint8_t adr); /** * @brief Request data from the slave device and wait. @@ -181,10 +192,11 @@ class TWIBus { * @brief Request data from the slave device, echo to serial. * @details Request a number of bytes from a slave device and output * the returned data to serial in a parser-friendly format. + * @style Output format for the bytes, 0 = raw byte [default], 1 = Hex characters, 2 = uint16_t * * @param bytes the number of bytes to request */ - void relay(const uint8_t bytes); + void relay(const uint8_t bytes, const uint8_t style=0); #if I2C_SLAVE_ADDRESS > 0 @@ -226,15 +238,16 @@ class TWIBus { * @brief Prints a debug message * @details Prints a simple debug message "TWIBus::function: value" */ - static void prefix(const char func[]); - static void debug(const char func[], uint32_t adr); - static void debug(const char func[], char c); - static void debug(const char func[], char adr[]); - static inline void debug(const char func[], uint8_t v) { debug(func, (uint32_t)v); } + static void prefix(FSTR_P const func); + static void debug(FSTR_P const func, uint32_t adr); + static void debug(FSTR_P const func, char c); + static void debug(FSTR_P const func, char adr[]); #else - static inline void debug(const char[], uint32_t) {} - static inline void debug(const char[], char) {} - static inline void debug(const char[], char[]) {} - static inline void debug(const char[], uint8_t) {} + static void debug(FSTR_P const, uint32_t) {} + static void debug(FSTR_P const, char) {} + static void debug(FSTR_P const, char[]) {} #endif + static void debug(FSTR_P const func, uint8_t v) { debug(func, (uint32_t)v); } }; + +extern TWIBus i2c; diff --git a/Marlin/src/feature/x_twist.cpp b/Marlin/src/feature/x_twist.cpp new file mode 100644 index 0000000000..6b155c49ab --- /dev/null +++ b/Marlin/src/feature/x_twist.cpp @@ -0,0 +1,67 @@ +/** + * 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 . + * + */ +#include "../inc/MarlinConfig.h" + +#if ENABLED(X_AXIS_TWIST_COMPENSATION) + +#include "x_twist.h" +#include "../module/probe.h" + +XATC xatc; + +bool XATC::enabled; +float XATC::spacing, XATC::start; +xatc_array_t XATC::z_offset; // Initialized by settings.load + +void XATC::reset() { + constexpr float xzo[] = XATC_Z_OFFSETS; + static_assert(COUNT(xzo) == XATC_MAX_POINTS, "XATC_Z_OFFSETS is the wrong size."); + COPY(z_offset, xzo); + start = probe.min_x(); + spacing = (probe.max_x() - start) / (XATC_MAX_POINTS - 1); + enabled = true; +} + +void XATC::print_points() { + SERIAL_ECHOLNPGM(" X-Twist Correction:"); + for (uint8_t x = 0; x < XATC_MAX_POINTS; ++x) { + SERIAL_CHAR(' '); + if (!isnan(z_offset[x])) + serial_offset(z_offset[x]); + else + for (uint8_t i = 0; i < 6; ++i) SERIAL_CHAR(i ? '=' : ' '); + } + SERIAL_EOL(); +} + +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; + if (NEAR_ZERO(spacing)) return 0; + float t = (raw.x - start) / spacing; + const int i = constrain(FLOOR(t), 0, XATC_MAX_POINTS - 2); + t -= i; + return lerp(t, z_offset[i], z_offset[i + 1]); +} + +#endif // X_AXIS_TWIST_COMPENSATION diff --git a/Marlin/src/feature/x_twist.h b/Marlin/src/feature/x_twist.h new file mode 100644 index 0000000000..6a2ff27901 --- /dev/null +++ b/Marlin/src/feature/x_twist.h @@ -0,0 +1,40 @@ +/** + * 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 + +#include "../inc/MarlinConfigPre.h" + +typedef float xatc_array_t[XATC_MAX_POINTS]; + +class XATC { + static bool enabled; +public: + static float spacing, start; + static xatc_array_t z_offset; + + static void reset(); + static void set_enabled(const bool ena) { enabled = ena; } + static float compensation(const xy_pos_t &raw); + static void print_points(); +}; + +extern XATC xatc; diff --git a/Marlin/src/feature/z_stepper_align.cpp b/Marlin/src/feature/z_stepper_align.cpp index 87b1f6f251..9dba21d821 100644 --- a/Marlin/src/feature/z_stepper_align.cpp +++ b/Marlin/src/feature/z_stepper_align.cpp @@ -33,51 +33,35 @@ ZStepperAlign z_stepper_align; -xy_pos_t ZStepperAlign::xy[NUM_Z_STEPPER_DRIVERS]; +xy_pos_t ZStepperAlign::xy[NUM_Z_STEPPERS]; -#if ENABLED(Z_STEPPER_ALIGN_KNOWN_STEPPER_POSITIONS) - xy_pos_t ZStepperAlign::stepper_xy[NUM_Z_STEPPER_DRIVERS]; +#if HAS_Z_STEPPER_ALIGN_STEPPER_XY + xy_pos_t ZStepperAlign::stepper_xy[NUM_Z_STEPPERS]; #endif void ZStepperAlign::reset_to_default() { #ifdef Z_STEPPER_ALIGN_XY constexpr xy_pos_t xy_init[] = Z_STEPPER_ALIGN_XY; - static_assert(COUNT(xy_init) == NUM_Z_STEPPER_DRIVERS, + static_assert(COUNT(xy_init) == NUM_Z_STEPPERS, "Z_STEPPER_ALIGN_XY requires " - #if NUM_Z_STEPPER_DRIVERS == 4 + #if NUM_Z_STEPPERS == 4 "four {X,Y} entries (Z, Z2, Z3, and Z4)." - #elif NUM_Z_STEPPER_DRIVERS == 3 + #elif NUM_Z_STEPPERS == 3 "three {X,Y} entries (Z, Z2, and Z3)." #else "two {X,Y} entries (Z and Z2)." #endif ); - constexpr xyz_pos_t dpo = NOZZLE_TO_PROBE_OFFSET; + #define VALIDATE_ALIGN_POINT(N) static_assert(N >= NUM_Z_STEPPERS || Probe::build_time::can_reach(xy_init[N]), \ + "Z_STEPPER_ALIGN_XY point " STRINGIFY(N) " is not reachable with the default NOZZLE_TO_PROBE offset and PROBING_MARGIN.") + VALIDATE_ALIGN_POINT(0); VALIDATE_ALIGN_POINT(1); VALIDATE_ALIGN_POINT(2); VALIDATE_ALIGN_POINT(3); - #define LTEST(N) (xy_init[N].x >= _MAX(X_MIN_BED + PROBING_MARGIN_LEFT, X_MIN_POS + dpo.x) - 0.00001f) - #define RTEST(N) (xy_init[N].x <= _MIN(X_MAX_BED - PROBING_MARGIN_RIGHT, X_MAX_POS + dpo.x) + 0.00001f) - #define FTEST(N) (xy_init[N].y >= _MAX(Y_MIN_BED + PROBING_MARGIN_FRONT, Y_MIN_POS + dpo.y) - 0.00001f) - #define BTEST(N) (xy_init[N].y <= _MIN(Y_MAX_BED - PROBING_MARGIN_BACK, Y_MAX_POS + dpo.y) + 0.00001f) - - static_assert(LTEST(0) && RTEST(0), "The 1st Z_STEPPER_ALIGN_XY X is unreachable with the default probe X offset."); - static_assert(FTEST(0) && BTEST(0), "The 1st Z_STEPPER_ALIGN_XY Y is unreachable with the default probe Y offset."); - static_assert(LTEST(1) && RTEST(1), "The 2nd Z_STEPPER_ALIGN_XY X is unreachable with the default probe X offset."); - static_assert(FTEST(1) && BTEST(1), "The 2nd Z_STEPPER_ALIGN_XY Y is unreachable with the default probe Y offset."); - #if NUM_Z_STEPPER_DRIVERS >= 3 - static_assert(LTEST(2) && RTEST(2), "The 3rd Z_STEPPER_ALIGN_XY X is unreachable with the default probe X offset."); - static_assert(FTEST(2) && BTEST(2), "The 3rd Z_STEPPER_ALIGN_XY Y is unreachable with the default probe Y offset."); - #if NUM_Z_STEPPER_DRIVERS >= 4 - static_assert(LTEST(3) && RTEST(3), "The 4th Z_STEPPER_ALIGN_XY X is unreachable with the default probe X offset."); - static_assert(FTEST(3) && BTEST(3), "The 4th Z_STEPPER_ALIGN_XY Y is unreachable with the default probe Y offset."); - #endif - #endif - - #else // !defined(Z_STEPPER_ALIGN_XY) + #else // !Z_STEPPER_ALIGN_XY const xy_pos_t xy_init[] = { - #if NUM_Z_STEPPER_DRIVERS >= 3 // First probe point... + #if NUM_Z_STEPPERS >= 3 // First probe point... #if !Z_STEPPERS_ORIENTATION { probe.min_x(), probe.min_y() }, // SW #elif Z_STEPPERS_ORIENTATION == 1 @@ -89,7 +73,7 @@ void ZStepperAlign::reset_to_default() { #else #error "Z_STEPPERS_ORIENTATION must be from 0 to 3 (first point SW, NW, NE, SE)." #endif - #if NUM_Z_STEPPER_DRIVERS == 4 // 3 more points... + #if NUM_Z_STEPPERS == 4 // 3 more points... #if !Z_STEPPERS_ORIENTATION { probe.min_x(), probe.max_y() }, { probe.max_x(), probe.max_y() }, { probe.max_x(), probe.min_y() } // SW #elif Z_STEPPERS_ORIENTATION == 1 @@ -115,18 +99,18 @@ void ZStepperAlign::reset_to_default() { #endif }; - #endif // !defined(Z_STEPPER_ALIGN_XY) + #endif // !Z_STEPPER_ALIGN_XY COPY(xy, xy_init); - #if ENABLED(Z_STEPPER_ALIGN_KNOWN_STEPPER_POSITIONS) + #if HAS_Z_STEPPER_ALIGN_STEPPER_XY constexpr xy_pos_t stepper_xy_init[] = Z_STEPPER_ALIGN_STEPPER_XY; static_assert( - COUNT(stepper_xy_init) == NUM_Z_STEPPER_DRIVERS, + COUNT(stepper_xy_init) == NUM_Z_STEPPERS, "Z_STEPPER_ALIGN_STEPPER_XY requires " - #if NUM_Z_STEPPER_DRIVERS == 4 + #if NUM_Z_STEPPERS == 4 "four {X,Y} entries (Z, Z2, Z3, and Z4)." - #elif NUM_Z_STEPPER_DRIVERS == 3 + #elif NUM_Z_STEPPERS == 3 "three {X,Y} entries (Z, Z2, and Z3)." #endif ); diff --git a/Marlin/src/feature/z_stepper_align.h b/Marlin/src/feature/z_stepper_align.h index e1b235b52c..f3f9abb845 100644 --- a/Marlin/src/feature/z_stepper_align.h +++ b/Marlin/src/feature/z_stepper_align.h @@ -29,10 +29,10 @@ class ZStepperAlign { public: - static xy_pos_t xy[NUM_Z_STEPPER_DRIVERS]; + static xy_pos_t xy[NUM_Z_STEPPERS]; - #if ENABLED(Z_STEPPER_ALIGN_KNOWN_STEPPER_POSITIONS) - static xy_pos_t stepper_xy[NUM_Z_STEPPER_DRIVERS]; + #if HAS_Z_STEPPER_ALIGN_STEPPER_XY + static xy_pos_t stepper_xy[NUM_Z_STEPPERS]; #endif static void reset_to_default(); diff --git a/Marlin/src/gcode/bedlevel/G26.cpp b/Marlin/src/gcode/bedlevel/G26.cpp index 661128b04e..899498faae 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 @@ -71,8 +71,8 @@ * pliers while holding the LCD Click wheel in a depressed state. If you do not have * an LCD, you must specify a value if you use P. * - * Q # Multiplier Retraction Multiplier. Normally not needed. Retraction defaults to 1.0mm and - * un-retraction is at 1.2mm These numbers will be scaled by the specified amount + * Q # Multiplier Retraction Multiplier. (Normally not needed.) During G26 retraction will use the length + * specified by this parameter (1mm by default). Recover will be 1.2x the retract distance. * * R # Repeat Prints the number of patterns given as a parameter, starting at the current location. * If a parameter isn't given, every point will be printed unless G26 is interrupted. @@ -102,16 +102,22 @@ #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/stepper.h" #include "../../module/motion.h" #include "../../module/tool_change.h" #include "../../module/temperature.h" -#include "../../lcd/ultralcd.h" +#include "../../lcd/marlinui.h" + +#if ENABLED(EXTENSIBLE_UI) + #include "../../lcd/extui/ui_api.h" +#endif + +#if ENABLED(UBL_HILBERT_CURVE) + #include "../../feature/bedlevel/hilbert_curve.h" +#endif #define EXTRUSION_MULTIPLIER 1.0 #define PRIME_LENGTH 10.0 @@ -125,7 +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_MM_S / 1.5) #endif #if CROSSHAIRS_SIZE >= INTERSECTION_CIRCLE_RADIUS @@ -136,329 +146,329 @@ #define G26_ERR true #if ENABLED(ARC_SUPPORT) - void plan_arc(const xyze_pos_t &cart, const ab_float_t &offset, const uint8_t clockwise); + void plan_arc(const xyze_pos_t&, const ab_float_t&, const bool, const uint8_t); #endif constexpr float g26_e_axis_feedrate = 0.025; -static MeshFlags circle_flags, horizontal_mesh_line_flags, vertical_mesh_line_flags; +static MeshFlags circle_flags; float g26_random_deviation = 0.0; -static bool g26_retracted = false; // Track the retracted state of the nozzle so mismatched - // retracts/recovers won't result in a bad state. - -float g26_extrusion_multiplier, - g26_retraction_multiplier, - g26_layer_height, - g26_prime_length; - -xy_pos_t g26_xy_pos; // = { 0, 0 } - -int16_t g26_bed_temp, - g26_hotend_temp; - -int8_t g26_prime_flag; - -#if HAS_LCD_MENU +#if HAS_MARLINUI_MENU /** * If the LCD is clicked, cancel, wait for release, return true */ bool user_canceled() { if (!ui.button_pressed()) return false; // Return if the button isn't pressed - ui.set_status_P(GET_TEXT(MSG_G26_CANCELED), 99); - TERN_(HAS_LCD_MENU, ui.quick_feedback()); + LCD_MESSAGE_MAX(MSG_G26_CANCELED); + ui.quick_feedback(); ui.wait_for_release(); return true; } #endif -mesh_index_pair find_closest_circle_to_print(const xy_pos_t &pos) { - float closest = 99999.99; - mesh_index_pair out_point; - - out_point.pos = -1; - - GRID_LOOP(i, j) { - if (!circle_flags.marked(i, j)) { - // We found a circle that needs to be printed - const xy_pos_t m = { _GET_MESH_X(i), _GET_MESH_Y(j) }; - - // Get the distance to this intersection - float f = (pos - m).magnitude(); - - // It is possible that we are being called with the values - // to let us find the closest circle to the start position. - // But if this is not the case, add a small weighting to the - // distance calculation to help it choose a better place to continue. - f += (g26_xy_pos - m).magnitude() / 15.0f; - - // Add the specified amount of Random Noise to our search - if (g26_random_deviation > 1.0) f += random(0.0, g26_random_deviation); - - if (f < closest) { - closest = f; // Found a closer un-printed location - out_point.pos.set(i, j); // Save its data - out_point.distance = closest; - } - } - } - circle_flags.mark(out_point); // Mark this location as done. - return out_point; -} - -void move_to(const float &rx, const float &ry, const float &z, const float &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. - - destination = current_position; + const bool has_xy_component = dest != current_position, // Check if X or Y is involved in the movement. + has_e_component = e_delta != 0.0; if (z != last_z) { - last_z = destination.z = z; - const feedRate_t feed_value = planner.settings.max_feedrate_mm_s[Z_AXIS] * 0.5f; // Use half of the Z_AXIS max feed rate - prepare_internal_move_to_destination(feed_value); - destination = current_position; + last_z = z; + destination.set(current_position.x, current_position.y, z, current_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); } - // If X or Y is involved do a 'normal' move. Otherwise retract/recover/hop. + // 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; - const feedRate_t feed_value = has_xy_component ? feedRate_t(G26_XY_FEEDRATE) : planner.settings.max_feedrate_mm_s[E_AXIS] * 0.666f; - prepare_internal_move_to_destination(feed_value); - destination = current_position; + 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); } -FORCE_INLINE void move_to(const xyz_pos_t &where, const float &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); } -void retract_filament(const xyz_pos_t &where) { - if (!g26_retracted) { // Only retract if we are not already retracted! - g26_retracted = true; - move_to(where, -1.0f * g26_retraction_multiplier); - } -} +typedef struct { + float extrusion_multiplier = EXTRUSION_MULTIPLIER, + retraction_multiplier = G26_RETRACT_MULTIPLIER, + layer_height = MESH_TEST_LAYER_HEIGHT, + prime_length = PRIME_LENGTH; -// 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.0); // Z lift to minimize scraping - move_to(s.x, s.y, s.z + 0.5f, 0.0); // Get to the starting point with no extrusion while lifted -} + celsius_t bed_temp = MESH_TEST_BED_TEMP, + hotend_temp = MESH_TEST_HOTEND_TEMP; -void recover_filament(const xyz_pos_t &where) { - if (g26_retracted) { // Only un-retract if we are retracted. - move_to(where, 1.2f * g26_retraction_multiplier); - g26_retracted = false; - } -} + float nozzle = MESH_TEST_NOZZLE_SIZE, + filament_diameter = DEFAULT_NOMINAL_FILAMENT_DIA, + ooze_amount; // 'O' ... OOZE_AMOUNT -/** - * print_line_from_here_to_there() takes two cartesian coordinates and draws a line from one - * to the other. But there are really three sets of coordinates involved. The first coordinate - * is the present location of the nozzle. We don't necessarily want to print from this location. - * We first need to move the nozzle to the start of line segment where we want to print. Once - * there, we can use the two coordinates supplied to draw the line. - * - * Note: Although we assume the first set of coordinates is the start of the line and the second - * set of coordinates is the end of the line, it does not always work out that way. This function - * optimizes the movement to minimize the travel distance before it can start printing. This saves - * a lot of time and eliminates a lot of nonsensical movement of the nozzle. However, it does - * cause a lot of very little short retracement of th nozzle when it draws the very first line - * segment of a 'circle'. The time this requires is very short and is easily saved by the other - * cases where the optimization comes into play. - */ -void print_line_from_here_to_there(const xyz_pos_t &s, const xyz_pos_t &e) { + bool continue_with_closest, // 'C' + keep_heaters_on; // 'K' - // Distances to the start / end of the line - xy_float_t svec = current_position - s, evec = current_position - e; + xy_pos_t xy_pos; // = { 0, 0 } - const float dist_start = HYPOT2(svec.x, svec.y), - dist_end = HYPOT2(evec.x, evec.y), - line_length = HYPOT(e.x - s.x, e.y - s.y); + int8_t prime_flag = 0; - // 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); + bool g26_retracted = false; // Track the retracted state during G26 so mismatched + // retracts/recovers don't result in a bad state. - // Decide whether to retract & lift - if (dist_start > 2.0) retract_lift_move(s); - - move_to(s, 0.0); // Get to the starting point with no extrusion / un-Z lift - - const float e_pos_delta = line_length * g26_e_axis_feedrate * g26_extrusion_multiplier; - - recover_filament(destination); - move_to(e, e_pos_delta); // Get to the ending point with an appropriate amount of extrusion -} - -inline bool look_for_lines_to_connect() { - xyz_pos_t s, e; - s.z = e.z = g26_layer_height; - - GRID_LOOP(i, j) { - - if (TERN0(HAS_LCD_MENU, user_canceled())) return true; - - if (i < (GRID_MAX_POINTS_X)) { // Can't connect to anything farther to the right than GRID_MAX_POINTS_X. - // Already a half circle at the edge of the bed. - - if (circle_flags.marked(i, j) && circle_flags.marked(i + 1, j)) { // Test whether a leftward line can be done - if (!horizontal_mesh_line_flags.marked(i, j)) { - // Two circles need a horizontal line to connect them - s.x = _GET_MESH_X( i ) + (INTERSECTION_CIRCLE_RADIUS - (CROSSHAIRS_SIZE)); // right edge - e.x = _GET_MESH_X(i + 1) - (INTERSECTION_CIRCLE_RADIUS - (CROSSHAIRS_SIZE)); // left edge - - LIMIT(s.x, X_MIN_POS + 1, X_MAX_POS - 1); - s.y = e.y = constrain(_GET_MESH_Y(j), Y_MIN_POS + 1, Y_MAX_POS - 1); - LIMIT(e.x, X_MIN_POS + 1, X_MAX_POS - 1); - - if (position_is_reachable(s.x, s.y) && position_is_reachable(e.x, e.y)) - print_line_from_here_to_there(s, e); - - horizontal_mesh_line_flags.mark(i, j); // Mark done, even if skipped - } - } - - if (j < (GRID_MAX_POINTS_Y)) { // Can't connect to anything further back than GRID_MAX_POINTS_Y. - // Already a half circle at the edge of the bed. - - if (circle_flags.marked(i, j) && circle_flags.marked(i, j + 1)) { // Test whether a downward line can be done - if (!vertical_mesh_line_flags.marked(i, j)) { - // Two circles that need a vertical line to connect them - s.y = _GET_MESH_Y( j ) + (INTERSECTION_CIRCLE_RADIUS - (CROSSHAIRS_SIZE)); // top edge - e.y = _GET_MESH_Y(j + 1) - (INTERSECTION_CIRCLE_RADIUS - (CROSSHAIRS_SIZE)); // bottom edge - - s.x = e.x = constrain(_GET_MESH_X(i), X_MIN_POS + 1, X_MAX_POS - 1); - LIMIT(s.y, Y_MIN_POS + 1, Y_MAX_POS - 1); - LIMIT(e.y, Y_MIN_POS + 1, Y_MAX_POS - 1); - - if (position_is_reachable(s.x, s.y) && position_is_reachable(e.x, e.y)) - print_line_from_here_to_there(s, e); - - vertical_mesh_line_flags.mark(i, j); // Mark done, even if skipped - } - } - } + void retract_filament(const xyz_pos_t &where) { + if (!g26_retracted) { // Only retract if we are not already retracted! + g26_retracted = true; + move_to(where, -1.0f * retraction_multiplier); } } - return false; -} -/** - * Turn on the bed and nozzle heat and - * wait for them to get up to temperature. - */ -inline bool turn_on_heaters() { - - SERIAL_ECHOLNPGM("Waiting for heatup."); - - #if HAS_HEATED_BED - - if (g26_bed_temp > 25) { - #if HAS_WIRED_LCD - ui.set_status_P(GET_TEXT(MSG_G26_HEATING_BED), 99); - ui.quick_feedback(); - TERN_(HAS_LCD_MENU, ui.capture()); - #endif - thermalManager.setTargetBed(g26_bed_temp); - - // Wait for the temperature to stabilize - if (!thermalManager.wait_for_bed(true - #if G26_CLICK_CAN_CANCEL - , true - #endif - ) - ) return G26_ERR; - } - - #endif // HAS_HEATED_BED - - // Start heating the active nozzle - #if HAS_WIRED_LCD - ui.set_status_P(GET_TEXT(MSG_G26_HEATING_NOZZLE), 99); - ui.quick_feedback(); - #endif - thermalManager.setTargetHotend(g26_hotend_temp, active_extruder); - - // Wait for the temperature to stabilize - if (!thermalManager.wait_for_hotend(active_extruder, true - #if G26_CLICK_CAN_CANCEL - , true - #endif - )) return G26_ERR; - - #if HAS_WIRED_LCD - ui.reset_status(); - ui.quick_feedback(); - #endif - - return G26_OK; -} - -/** - * Prime the nozzle if needed. Return true on error. - */ -inline bool prime_nozzle() { - - const feedRate_t fr_slow_e = planner.settings.max_feedrate_mm_s[E_AXIS] / 15.0f; - #if HAS_LCD_MENU && !HAS_TOUCH_XPT2046 // ui.button_pressed issue with touchscreen - #if ENABLED(PREVENT_LENGTHY_EXTRUDE) - float Total_Prime = 0.0; - #endif - - if (g26_prime_flag == -1) { // The user wants to control how much filament gets purged - ui.capture(); - ui.set_status_P(GET_TEXT(MSG_G26_MANUAL_PRIME), 99); - ui.chirp(); - - destination = current_position; - - recover_filament(destination); // Make sure G26 doesn't think the filament is retracted(). - - while (!ui.button_pressed()) { - ui.chirp(); - destination.e += 0.25; - #if ENABLED(PREVENT_LENGTHY_EXTRUDE) - Total_Prime += 0.25; - if (Total_Prime >= EXTRUDE_MAXLENGTH) { - ui.release(); - return G26_ERR; - } - #endif - prepare_internal_move_to_destination(fr_slow_e); - destination = current_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 - // action to give the user a more responsive 'Stop'. - } - - ui.wait_for_release(); - - ui.set_status_P(GET_TEXT(MSG_G26_PRIME_DONE), 99); - ui.quick_feedback(); - ui.release(); - } - else - #endif - { - #if HAS_WIRED_LCD - ui.set_status_P(GET_TEXT(MSG_G26_FIXED_LENGTH), 99); - ui.quick_feedback(); - #endif - destination = current_position; - destination.e += g26_prime_length; - prepare_internal_move_to_destination(fr_slow_e); - destination.e -= g26_prime_length; + // 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 + move_to(s.x, s.y, s.z + 0.5f, 0.0f); // Get to the starting point with no extrusion while lifted } - return G26_OK; -} + void recover_filament(const xyz_pos_t &where) { + if (g26_retracted) { // Only un-retract if we are retracted. + move_to(where, 1.2f * retraction_multiplier); + g26_retracted = false; + } + } + + /** + * print_line_from_here_to_there() takes two cartesian coordinates and draws a line from one + * to the other. But there are really three sets of coordinates involved. The first coordinate + * is the present location of the nozzle. We don't necessarily want to print from this location. + * We first need to move the nozzle to the start of line segment where we want to print. Once + * there, we can use the two coordinates supplied to draw the line. + * + * Note: Although we assume the first set of coordinates is the start of the line and the second + * set of coordinates is the end of the line, it does not always work out that way. This function + * optimizes the movement to minimize the travel distance before it can start printing. This saves + * a lot of time and eliminates a lot of nonsensical movement of the nozzle. However, it does + * cause a lot of very little short retracement of th nozzle when it draws the very first line + * segment of a 'circle'. The time this requires is very short and is easily saved by the other + * cases where the optimization comes into play. + */ + 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; + + const float dist_start = HYPOT2(svec.x, svec.y), + dist_end = HYPOT2(evec.x, evec.y), + line_length = HYPOT(e.x - s.x, e.y - s.y); + + // 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)) { + print_line_from_here_to_there(e, s); + return; + } + + // Decide whether to retract & lift + if (dist_start > 2.0) retract_lift_move(s); + + move_to(s, 0.0); // Get to the starting point with no extrusion / un-Z lift + + const float e_pos_delta = line_length * g26_e_axis_feedrate * extrusion_multiplier; + + recover_filament(destination); + move_to(e, e_pos_delta); // Get to the ending point with an appropriate amount of extrusion + } + + void connect_neighbor_with_line(const xy_int8_t &p1, int8_t dx, int8_t dy) { + xy_int8_t p2; + p2.x = p1.x + dx; + p2.y = p1.y + dy; + + if (p2.x < 0 || p2.x >= (GRID_MAX_POINTS_X)) return; + if (p2.y < 0 || p2.y >= (GRID_MAX_POINTS_Y)) return; + + if (circle_flags.marked(p1.x, p1.y) && circle_flags.marked(p2.x, p2.y)) { + xyz_pos_t s, e; + s.x = bedlevel.get_mesh_x(p1.x) + (INTERSECTION_CIRCLE_RADIUS - (CROSSHAIRS_SIZE)) * dx; + e.x = bedlevel.get_mesh_x(p2.x) - (INTERSECTION_CIRCLE_RADIUS - (CROSSHAIRS_SIZE)) * dx; + s.y = bedlevel.get_mesh_y(p1.y) + (INTERSECTION_CIRCLE_RADIUS - (CROSSHAIRS_SIZE)) * dy; + e.y = bedlevel.get_mesh_y(p2.y) - (INTERSECTION_CIRCLE_RADIUS - (CROSSHAIRS_SIZE)) * dy; + s.z = e.z = layer_height; + + #if HAS_ENDSTOPS + LIMIT(s.y, Y_MIN_POS + 1, Y_MAX_POS - 1); + LIMIT(e.y, Y_MIN_POS + 1, Y_MAX_POS - 1); + LIMIT(s.x, X_MIN_POS + 1, X_MAX_POS - 1); + LIMIT(e.x, X_MIN_POS + 1, X_MAX_POS - 1); + #endif + + if (position_is_reachable(s) && position_is_reachable(e)) + print_line_from_here_to_there(s, e); + } + } + + /** + * Turn on the bed and nozzle heat and + * wait for them to get up to temperature. + */ + bool turn_on_heaters() { + + SERIAL_ECHOLNPGM("Waiting for heatup."); + + #if HAS_HEATED_BED + + if (bed_temp > 25) { + LCD_MESSAGE_MAX(MSG_G26_HEATING_BED); + ui.quick_feedback(); + TERN_(HAS_MARLINUI_MENU, ui.capture()); + thermalManager.setTargetBed(bed_temp); + + // Wait for the temperature to stabilize + if (!thermalManager.wait_for_bed(true OPTARG(G26_CLICK_CAN_CANCEL, true))) + return G26_ERR; + } + + #else + + UNUSED(bed_temp); + + #endif // HAS_HEATED_BED + + // Start heating the active nozzle + LCD_MESSAGE_MAX(MSG_G26_HEATING_NOZZLE); + ui.quick_feedback(); + thermalManager.setTargetHotend(hotend_temp, active_extruder); + + // Wait for the temperature to stabilize + if (!thermalManager.wait_for_hotend(active_extruder, true OPTARG(G26_CLICK_CAN_CANCEL, true))) + return G26_ERR; + + ui.reset_status(); + ui.completion_feedback(); + + return G26_OK; + } + + /** + * Prime the nozzle if needed. Return true on error. + */ + bool prime_nozzle() { + + const feedRate_t fr_slow_e = planner.settings.max_feedrate_mm_s[E_AXIS] / 15.0f; + #if HAS_MARLINUI_MENU && !HAS_TOUCH_BUTTONS // ui.button_pressed issue with touchscreen + #if ENABLED(PREVENT_LENGTHY_EXTRUDE) + float Total_Prime = 0.0; + #endif + + if (prime_flag == -1) { // The user wants to control how much filament gets purged + ui.capture(); + LCD_MESSAGE_MAX(MSG_G26_MANUAL_PRIME); + ui.chirp(); + + destination = current_position; + + recover_filament(destination); // Make sure G26 doesn't think the filament is retracted(). + + while (!ui.button_pressed()) { + ui.chirp(); + destination.e += 0.25; + #if ENABLED(PREVENT_LENGTHY_EXTRUDE) + Total_Prime += 0.25; + if (Total_Prime >= EXTRUDE_MAXLENGTH) { + ui.release(); + return G26_ERR; + } + #endif + prepare_internal_move_to_destination(fr_slow_e); + destination = current_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 + // action to give the user a more responsive 'Stop'. + } + + ui.wait_for_release(); + + LCD_MESSAGE_MAX(MSG_G26_PRIME_DONE); + ui.quick_feedback(); + ui.release(); + } + else + #endif + { + 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); + } + + return G26_OK; + } + + /** + * Find the nearest point at which to print a circle + */ + mesh_index_pair find_closest_circle_to_print(const xy_pos_t &pos) { + + mesh_index_pair out_point; + out_point.pos = -1; + + #if ENABLED(UBL_HILBERT_CURVE) + + auto test_func = [](uint8_t i, uint8_t j, void *data) -> bool { + if (!circle_flags.marked(i, j)) { + mesh_index_pair *out_point = (mesh_index_pair*)data; + out_point->pos.set(i, j); // Save its data + return true; + } + return false; + }; + + hilbert_curve::search_from_closest(pos, test_func, &out_point); + + #else + + float closest = 99999.99; + + GRID_LOOP(i, j) { + if (!circle_flags.marked(i, j)) { + // We found a circle that needs to be printed + const xy_pos_t m = { bedlevel.get_mesh_x(i), bedlevel.get_mesh_y(j) }; + + // Get the distance to this intersection + float f = (pos - m).magnitude(); + + // It is possible that we are being called with the values + // to let us find the closest circle to the start position. + // But if this is not the case, add a small weighting to the + // distance calculation to help it choose a better place to continue. + f += (xy_pos - m).magnitude() / 15.0f; + + // Add the specified amount of Random Noise to our search + if (g26_random_deviation > 1.0) f += random(0.0, g26_random_deviation); + + if (f < closest) { + closest = f; // Found a closer un-printed location + out_point.pos.set(i, j); // Save its data + out_point.distance = closest; + } + } + } + + #endif + + circle_flags.mark(out_point); // Mark this location as done. + return out_point; + } + +} g26_helper_t; /** * G26: Mesh Validation Pattern generation. @@ -492,36 +502,29 @@ void GcodeSuite::G26() { // or if the parameter parsing did not go OK, abort if (homing_needed_error()) return; - // Change the tool first, if specified - if (parser.seenval('T')) tool_change(parser.value_int()); + #if HAS_TOOLCHANGE + // Change the tool first, if specified + if (parser.seenval('T')) tool_change(parser.value_int()); + #endif - g26_extrusion_multiplier = EXTRUSION_MULTIPLIER; - g26_retraction_multiplier = G26_RETRACT_MULTIPLIER; - g26_layer_height = MESH_TEST_LAYER_HEIGHT; - g26_prime_length = PRIME_LENGTH; - g26_bed_temp = MESH_TEST_BED_TEMP; - g26_hotend_temp = MESH_TEST_HOTEND_TEMP; - g26_prime_flag = 0; + g26_helper_t g26; - float g26_nozzle = MESH_TEST_NOZZLE_SIZE, - g26_filament_diameter = DEFAULT_NOMINAL_FILAMENT_DIA, - g26_ooze_amount = parser.linearval('O', OOZE_AMOUNT); - - bool g26_continue_with_closest = parser.boolval('C'), - g26_keep_heaters_on = parser.boolval('K'); + g26.ooze_amount = parser.linearval('O', OOZE_AMOUNT); + g26.continue_with_closest = parser.boolval('C'); + g26.keep_heaters_on = parser.boolval('K'); // Accept 'I' if temperature presets are defined - #if PREHEAT_COUNT + #if HAS_PREHEAT const uint8_t preset_index = parser.seenval('I') ? _MIN(parser.value_byte(), PREHEAT_COUNT - 1) + 1 : 0; #endif #if HAS_HEATED_BED // Get a temperature from 'I' or 'B' - int16_t bedtemp = 0; + celsius_t bedtemp = 0; // Use the 'I' index if temperature presets are defined - #if PREHEAT_COUNT + #if HAS_PREHEAT if (preset_index) bedtemp = ui.material_preset[preset_index - 1].bed_temp; #endif @@ -530,81 +533,81 @@ void GcodeSuite::G26() { if (bedtemp) { if (!WITHIN(bedtemp, 40, BED_MAX_TARGET)) { - SERIAL_ECHOLNPAIR("?Specified bed temperature not plausible (40-", int(BED_MAX_TARGET), "C)."); + SERIAL_ECHOLNPGM(GCODE_ERR_MSG("Specified bed temperature not plausible (40-", BED_MAX_TARGET, "C).")); return; } - g26_bed_temp = bedtemp; + g26.bed_temp = bedtemp; } #endif // HAS_HEATED_BED if (parser.seenval('L')) { - g26_layer_height = parser.value_linear_units(); - if (!WITHIN(g26_layer_height, 0.0, 2.0)) { - SERIAL_ECHOLNPGM("?Specified layer height not plausible."); + g26.layer_height = parser.value_linear_units(); + if (!WITHIN(g26.layer_height, 0.0, 2.0)) { + SERIAL_ECHOLNPGM(GCODE_ERR_MSG("Specified layer height not plausible.")); return; } } if (parser.seen('Q')) { if (parser.has_value()) { - g26_retraction_multiplier = parser.value_float(); - if (!WITHIN(g26_retraction_multiplier, 0.05, 15.0)) { - SERIAL_ECHOLNPGM("?Specified Retraction Multiplier not plausible."); + g26.retraction_multiplier = parser.value_float(); + if (!WITHIN(g26.retraction_multiplier, 0.05, 15.0)) { + SERIAL_ECHOLNPGM(GCODE_ERR_MSG("Specified Retraction Multiplier not plausible.")); return; } } else { - SERIAL_ECHOLNPGM("?Retraction Multiplier must be specified."); + SERIAL_ECHOLNPGM(GCODE_ERR_MSG("Retraction Multiplier must be specified.")); return; } } if (parser.seenval('S')) { - g26_nozzle = parser.value_float(); - if (!WITHIN(g26_nozzle, 0.1, 2.0)) { - SERIAL_ECHOLNPGM("?Specified nozzle size not plausible."); + g26.nozzle = parser.value_float(); + if (!WITHIN(g26.nozzle, 0.1, 2.0)) { + SERIAL_ECHOLNPGM(GCODE_ERR_MSG("Specified nozzle size not plausible.")); return; } } if (parser.seen('P')) { if (!parser.has_value()) { - #if HAS_LCD_MENU - g26_prime_flag = -1; + #if HAS_MARLINUI_MENU + g26.prime_flag = -1; #else - SERIAL_ECHOLNPGM("?Prime length must be specified when not using an LCD."); + SERIAL_ECHOLNPGM(GCODE_ERR_MSG("Prime length must be specified when not using an LCD.")); return; #endif } else { - g26_prime_flag++; - g26_prime_length = parser.value_linear_units(); - if (!WITHIN(g26_prime_length, 0.0, 25.0)) { - SERIAL_ECHOLNPGM("?Specified prime length not plausible."); + g26.prime_flag++; + g26.prime_length = parser.value_linear_units(); + if (!WITHIN(g26.prime_length, 0.0, 25.0)) { + SERIAL_ECHOLNPGM(GCODE_ERR_MSG("Specified prime length not plausible.")); return; } } } if (parser.seenval('F')) { - g26_filament_diameter = parser.value_linear_units(); - if (!WITHIN(g26_filament_diameter, 1.0, 4.0)) { - SERIAL_ECHOLNPGM("?Specified filament size not plausible."); + g26.filament_diameter = parser.value_linear_units(); + if (!WITHIN(g26.filament_diameter, 1.0, 4.0)) { + SERIAL_ECHOLNPGM(GCODE_ERR_MSG("Specified filament size not plausible.")); return; } } - g26_extrusion_multiplier *= sq(1.75) / sq(g26_filament_diameter); // If we aren't using 1.75mm filament, we need to + g26.extrusion_multiplier *= sq(1.75) / sq(g26.filament_diameter); // If we aren't using 1.75mm filament, we need to // scale up or down the length needed to get the // same volume of filament - g26_extrusion_multiplier *= g26_filament_diameter * sq(g26_nozzle) / sq(0.3); // Scale up by nozzle size + g26.extrusion_multiplier *= g26.filament_diameter * sq(g26.nozzle) / sq(0.3); // Scale up by nozzle size // Get a temperature from 'I' or 'H' - int16_t noztemp = 0; + celsius_t noztemp = 0; // Accept 'I' if temperature presets are defined - #if PREHEAT_COUNT + #if HAS_PREHEAT if (preset_index) noztemp = ui.material_preset[preset_index - 1].hotend_temp; #endif @@ -613,11 +616,11 @@ void GcodeSuite::G26() { // If any preset or temperature was specified if (noztemp) { - if (!WITHIN(noztemp, 165, (HEATER_0_MAXTEMP) - (HOTEND_OVERSHOOT))) { - SERIAL_ECHOLNPGM("?Specified nozzle temperature not plausible."); + if (!WITHIN(noztemp, 165, thermalManager.hotend_max_target(active_extruder))) { + SERIAL_ECHOLNPGM(GCODE_ERR_MSG("Specified nozzle temperature not plausible.")); return; } - g26_hotend_temp = noztemp; + g26.hotend_temp = noztemp; } // 'U' to Randomize and optionally set circle deviation @@ -628,49 +631,49 @@ void GcodeSuite::G26() { } // Get repeat from 'R', otherwise do one full circuit - int16_t g26_repeats; - #if HAS_LCD_MENU + grid_count_t g26_repeats; + #if HAS_MARLINUI_MENU g26_repeats = parser.intval('R', GRID_MAX_POINTS + 1); #else - if (!parser.seen('R')) { - SERIAL_ECHOLNPGM("?(R)epeat must be specified when not using an LCD."); + if (parser.seen('R')) + g26_repeats = parser.has_value() ? parser.value_int() : GRID_MAX_POINTS + 1; + else { + SERIAL_ECHOLNPGM(GCODE_ERR_MSG("(R)epeat must be specified when not using an LCD.")); return; } - else - g26_repeats = parser.has_value() ? parser.value_int() : GRID_MAX_POINTS + 1; #endif if (g26_repeats < 1) { - SERIAL_ECHOLNPGM("?(R)epeat value not plausible; must be at least 1."); + SERIAL_ECHOLNPGM(GCODE_ERR_MSG("(R)epeat value not plausible; must be at least 1.")); 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, + 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)) { - SERIAL_ECHOLNPGM("?Specified X,Y coordinate out of bounds."); + if (!position_is_reachable(g26.xy_pos)) { + SERIAL_ECHOLNPGM(GCODE_ERR_MSG("Specified X,Y coordinate out of bounds.")); return; } /** * Wait until all parameters are verified before altering the state! */ - set_bed_leveling_enabled(!parser.seen('D')); + set_bed_leveling_enabled(!parser.seen_test('D')); 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(); #endif - if (turn_on_heaters() != G26_OK) goto LEAVE; + if (g26.turn_on_heaters() != G26_OK) goto LEAVE; current_position.e = 0.0; sync_plan_position_e(); - if (g26_prime_flag && prime_nozzle() != G26_OK) goto LEAVE; + if (g26.prime_flag && g26.prime_nozzle() != G26_OK) goto LEAVE; /** * Bed is preheated @@ -683,16 +686,14 @@ void GcodeSuite::G26() { */ circle_flags.reset(); - horizontal_mesh_line_flags.reset(); - vertical_mesh_line_flags.reset(); // Move nozzle to the specified height for the first layer destination = current_position; - destination.z = g26_layer_height; + destination.z = g26.layer_height; move_to(destination, 0.0); - move_to(destination, g26_ooze_amount); + move_to(destination, g26.ooze_amount); - TERN_(HAS_LCD_MENU, ui.capture()); + TERN_(HAS_MARLINUI_MENU, ui.capture()); #if DISABLED(ARC_SUPPORT) @@ -709,18 +710,20 @@ void GcodeSuite::G26() { #error "A_CNT must be a positive value. Please change A_INT." #endif float trig_table[A_CNT]; - LOOP_L_N(i, A_CNT) + for (uint8_t i = 0; i < A_CNT; ++i) trig_table[i] = INTERSECTION_CIRCLE_RADIUS * cos(RADIANS(i * A_INT)); #endif // !ARC_SUPPORT mesh_index_pair location; + TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(location.pos, ExtUI::G26_START)); do { // Find the nearest confluence - location = 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(current_position) : g26.xy_pos); if (location.valid()) { - const xy_pos_t circle = _GET_MESH_POS(location.pos); + 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; @@ -729,8 +732,8 @@ void GcodeSuite::G26() { // which is always drawn counter-clockwise. const xy_int8_t st = location; const bool f = st.y == 0, - r = st.x >= GRID_MAX_POINTS_X - 1, - b = st.y >= GRID_MAX_POINTS_Y - 1; + r = st.x >= (GRID_MAX_POINTS_X) - 1, + b = st.y >= (GRID_MAX_POINTS_Y) - 1; #if ENABLED(ARC_SUPPORT) @@ -747,7 +750,7 @@ void GcodeSuite::G26() { if (!b) { e.x = circle.x; e.y += INTERSECTION_CIRCLE_RADIUS; } arc_length = (f || b) ? ARC_LENGTH(1) : ARC_LENGTH(2); } - else if (r) { // right edge + else if (r) { // right edge if (b) s.set(circle.x - (INTERSECTION_CIRCLE_RADIUS), circle.y); else s.set(circle.x, circle.y + INTERSECTION_CIRCLE_RADIUS); if (f) e.set(circle.x - (INTERSECTION_CIRCLE_RADIUS), circle.y); @@ -767,27 +770,26 @@ void GcodeSuite::G26() { const xy_float_t dist = current_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) + e.x, e.y, g26.layer_height, + current_position.e + (arc_length * g26_e_axis_feedrate * g26.extrusion_multiplier) }; if (dist_start > 2.0) { - s.z = g26_layer_height + 0.5f; - retract_lift_move(s); + s.z = g26.layer_height + 0.5f; + g26.retract_lift_move(s); } - s.z = g26_layer_height; + s.z = g26.layer_height; move_to(s, 0.0); // Get to the starting point with no extrusion / un-Z lift - recover_filament(destination); + g26.recover_filament(destination); - const feedRate_t old_feedrate = feedrate_mm_s; - feedrate_mm_s = PLANNER_XY_FEEDRATE() * 0.1f; - plan_arc(endpoint, arc_offset, false); // Draw a counter-clockwise arc - feedrate_mm_s = old_feedrate; - destination = current_position; + { REMEMBER(fr, 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; + } - if (TERN0(HAS_LCD_MENU, user_canceled())) goto LEAVE; // Check if the user wants to stop the Mesh Validation + if (TERN0(HAS_MARLINUI_MENU, user_canceled())) goto LEAVE; // Check if the user wants to stop the Mesh Validation #else // !ARC_SUPPORT @@ -811,28 +813,34 @@ void GcodeSuite::G26() { for (int8_t ind = start_ind; ind <= end_ind; ind++) { - if (TERN0(HAS_LCD_MENU, user_canceled())) goto LEAVE; // Check if the user wants to stop the Mesh Validation + if (TERN0(HAS_MARLINUI_MENU, user_canceled())) goto LEAVE; // Check if the user wants to stop the Mesh Validation - xyz_float_t p = { circle.x + _COS(ind ), circle.y + _SIN(ind ), g26_layer_height }, - q = { circle.x + _COS(ind + 1), circle.y + _SIN(ind + 1), g26_layer_height }; + xyz_float_t p = { circle.x + _COS(ind ), circle.y + _SIN(ind ), g26.layer_height }, + q = { circle.x + _COS(ind + 1), circle.y + _SIN(ind + 1), g26.layer_height }; #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; - #else + #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); LIMIT(q.x, X_MIN_POS + 1, X_MAX_POS - 1); LIMIT(q.y, Y_MIN_POS + 1, Y_MAX_POS - 1); #endif - print_line_from_here_to_there(p, q); + g26.print_line_from_here_to_there(p, q); SERIAL_FLUSH(); // Prevent host M105 buffer overrun. } #endif // !ARC_SUPPORT - if (look_for_lines_to_connect()) goto LEAVE; + g26.connect_neighbor_with_line(location.pos, -1, 0); + g26.connect_neighbor_with_line(location.pos, 1, 0); + g26.connect_neighbor_with_line(location.pos, 0, -1); + g26.connect_neighbor_with_line(location.pos, 0, 1); + planner.synchronize(); + TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(location.pos, ExtUI::G26_POINT_FINISH)); + if (TERN0(HAS_MARLINUI_MENU, user_canceled())) goto LEAVE; } SERIAL_FLUSH(); // Prevent host M105 buffer overrun. @@ -840,23 +848,21 @@ void GcodeSuite::G26() { } while (--g26_repeats && location.valid()); LEAVE: - ui.set_status_P(GET_TEXT(MSG_G26_LEAVING), -1); + LCD_MESSAGE_MIN(MSG_G26_LEAVING); + TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(location, ExtUI::G26_FINISH)); - retract_filament(destination); + g26.retract_filament(destination); destination.z = Z_CLEARANCE_BETWEEN_PROBES; - move_to(destination, 0); // Raise the nozzle + move_to(destination, 0); // Raise the nozzle - destination = g26_xy_pos; // Move back to the starting XY position - move_to(destination, 0); // Move back to the starting position - - #if DISABLED(NO_VOLUMETRICS) + #if HAS_VOLUMETRIC_EXTRUSION parser.volumetric_enabled = volumetric_was_enabled; planner.calculate_volumetric_multipliers(); #endif - TERN_(HAS_LCD_MENU, ui.release()); // Give back control of the LCD + TERN_(HAS_MARLINUI_MENU, ui.release()); // Give back control of the LCD - if (!g26_keep_heaters_on) { + if (!g26.keep_heaters_on) { TERN_(HAS_HEATED_BED, thermalManager.setTargetBed(0)); thermalManager.setTargetHotend(active_extruder, 0); } diff --git a/Marlin/src/gcode/bedlevel/G35.cpp b/Marlin/src/gcode/bedlevel/G35.cpp old mode 100755 new mode 100644 index 0ede4e79c6..b3c56b49b3 --- a/Marlin/src/gcode/bedlevel/G35.cpp +++ b/Marlin/src/gcode/bedlevel/G35.cpp @@ -29,42 +29,18 @@ #include "../../module/probe.h" #include "../../feature/bedlevel/bedlevel.h" -#if HAS_MULTI_HOTEND - #include "../../module/tool_change.h" +#if ENABLED(BLTOUCH) + #include "../../feature/bltouch.h" #endif #define DEBUG_OUT ENABLED(DEBUG_LEVELING_FEATURE) #include "../../core/debug_out.h" -constexpr xy_pos_t screws_tilt_adjust_pos[] = TRAMMING_POINT_XY; +// +// Define tramming point names. +// -static PGMSTR(point_name_1, TRAMMING_POINT_NAME_1); -static PGMSTR(point_name_2, TRAMMING_POINT_NAME_2); -static PGMSTR(point_name_3, TRAMMING_POINT_NAME_3); -#ifdef TRAMMING_POINT_NAME_4 - static PGMSTR(point_name_4, TRAMMING_POINT_NAME_4); - #ifdef TRAMMING_POINT_NAME_5 - static PGMSTR(point_name_5, TRAMMING_POINT_NAME_5); - #endif -#endif - -static PGM_P const tramming_point_name[] PROGMEM = { - point_name_1, point_name_2, point_name_3 - #ifdef TRAMMING_POINT_NAME_4 - , point_name_4 - #ifdef TRAMMING_POINT_NAME_5 - , point_name_5 - #endif - #endif -}; - -#define G35_PROBE_COUNT COUNT(screws_tilt_adjust_pos) - -#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 - -static_assert(G35_PROBE_COUNT > 2, "TRAMMING_POINT_XY requires at least 3 XY positions."); +#include "../../feature/tramming.h" /** * G35: Read bed corners to help adjust bed screws @@ -77,8 +53,9 @@ static_assert(G35_PROBE_COUNT > 2, "TRAMMING_POINT_XY requires at least 3 XY pos * 41 - Counter-Clockwise M4 * 50 - Clockwise M5 * 51 - Counter-Clockwise M5 - **/ + */ void GcodeSuite::G35() { + DEBUG_SECTION(log_G35, "G35", DEBUGGING(LEVELING)); if (DEBUGGING(LEVELING)) log_machine_info(); @@ -87,7 +64,7 @@ void GcodeSuite::G35() { const uint8_t screw_thread = parser.byteval('S', TRAMMING_SCREW_THREAD); if (!WITHIN(screw_thread, 30, 51) || screw_thread % 10 > 1) { - SERIAL_ECHOLNPGM("?(S)crew thread must be 30, 31, 40, 41, 50, or 51."); + SERIAL_ECHOLNPGM(GCODE_ERR_MSG("(S)crew thread must be 30, 31, 40, 41, 50, or 51.")); return; } @@ -96,49 +73,44 @@ void GcodeSuite::G35() { // Disable the leveling matrix before auto-aligning #if HAS_LEVELING - TERN_(RESTORE_LEVELING_AFTER_G35, const bool leveling_was_active = planner.leveling_active); + #if ENABLED(RESTORE_LEVELING_AFTER_G35) + const bool leveling_was_active = planner.leveling_active; + #endif set_bed_leveling_enabled(false); #endif - #if ENABLED(CNC_WORKSPACE_PLANES) - workspace_plane = PLANE_XY; - #endif + TERN_(CNC_WORKSPACE_PLANES, workspace_plane = PLANE_XY); - // Always home with tool 0 active - #if HAS_MULTI_HOTEND - const uint8_t old_tool_index = active_extruder; - tool_change(0, true); - #endif + probe.use_probing_tool(); - #if HAS_DUPLICATION_MODE - extruder_duplication_enabled = false; - #endif + // Disable duplication mode on homing + TERN_(HAS_DUPLICATION_MODE, set_duplication_enabled(false)); - // Home all before this procedure - home_all_axes(); + // 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")); bool err_break = false; // Probe all positions - LOOP_L_N(i, G35_PROBE_COUNT) { - - // In BLTOUCH HS mode, the probe travels in a deployed state. - // Users of G35 might have a badly misaligned bed, so raise Z by the - // length of the deployed pin (BLTOUCH stroke < 7mm) - current_position.z = (Z_CLEARANCE_BETWEEN_PROBES) + (7 * ENABLED(BLTOUCH_HS_MODE)); - - const float z_probed_height = probe.probe_at_point(screws_tilt_adjust_pos[i], PROBE_PT_RAISE, 0, true); - + 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_ECHOPAIR("G35 failed at point ", int(i), " (", tramming_point_name[i], ")"); - SERIAL_ECHOLNPAIR_P(SP_X_STR, screws_tilt_adjust_pos[i].x, SP_Y_STR, screws_tilt_adjust_pos[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; } if (DEBUGGING(LEVELING)) { - DEBUG_ECHOPAIR("Probing point ", int(i), " (", tramming_point_name[i], ")"); - SERIAL_ECHOLNPAIR_P(SP_X_STR, screws_tilt_adjust_pos[i].x, SP_Y_STR, screws_tilt_adjust_pos[i].y, SP_Z_STR, z_probed_height); + DEBUG_ECHOLN( + F("Probing 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, + FPSTR(SP_Z_STR), z_probed_height + ); } z_measured[i] = z_probed_height; @@ -148,19 +120,19 @@ void GcodeSuite::G35() { const float threads_factor[] = { 0.5, 0.7, 0.8 }; // Calculate adjusts - LOOP_S_L_N(i, 1, G35_PROBE_COUNT) { + for (uint8_t i = 1; i < G35_PROBE_COUNT; ++i) { const float diff = z_measured[0] - z_measured[i], - adjust = abs(diff) < 0.001f ? 0 : diff / threads_factor[(screw_thread - 30) / 10]; + adjust = ABS(diff) < 0.001f ? 0 : diff / threads_factor[(screw_thread - 30) / 10]; const int full_turns = trunc(adjust); const float decimal_part = adjust - float(full_turns); const int minutes = trunc(decimal_part * 60.0f); - SERIAL_ECHOPAIR("Turn ", tramming_point_name[i], - " ", (screw_thread & 1) == (adjust > 0) ? "CCW" : "CW", - " by ", abs(full_turns), " turns"); - if (minutes) SERIAL_ECHOPAIR(" and ", abs(minutes), " minutes"); - if (ENABLED(REPORT_TRAMMING_MM)) SERIAL_ECHOPAIR(" (", -diff, "mm)"); + SERIAL_ECHOPGM("Turn "); + SERIAL_ECHOPGM_P((char *)pgm_read_ptr(&tramming_point_name[i])); + SERIAL_ECHOPGM(" ", (screw_thread & 1) == (adjust > 0) ? "CCW" : "CW", " by ", ABS(full_turns), " turns"); + if (minutes) SERIAL_ECHOPGM(" and ", ABS(minutes), " minutes"); + if (ENABLED(REPORT_TRAMMING_MM)) SERIAL_ECHOPGM(" (", -diff, "mm)"); SERIAL_EOL(); } } @@ -168,11 +140,9 @@ void GcodeSuite::G35() { SERIAL_ECHOLNPGM("G35 aborted."); // Restore the active tool after homing - #if HAS_MULTI_HOTEND - tool_change(old_tool_index, DISABLED(PARKING_EXTRUDER)); // Fetch previous toolhead if not PARKING_EXTRUDER - #endif + probe.use_probing_tool(false); - #if BOTH(HAS_LEVELING, RESTORE_LEVELING_AFTER_G35) + #if ALL(HAS_LEVELING, RESTORE_LEVELING_AFTER_G35) set_bed_leveling_enabled(leveling_was_active); #endif @@ -180,11 +150,10 @@ void GcodeSuite::G35() { // the probe deployed if it was successful. probe.stow(); + move_to_tramming_wait_pos(); + // After this operation the Z position needs correction set_axis_never_homed(Z_AXIS); - - // Home Z after the alignment procedure - process_subcommands_now_P(PSTR("G28Z")); } #endif // ASSISTED_TRAMMING diff --git a/Marlin/src/gcode/bedlevel/G42.cpp b/Marlin/src/gcode/bedlevel/G42.cpp index a2896ed6c7..4b9b028419 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_CONDITIONS) 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) destination.x = _GET_MESH_X(ix); - if (hasJ) destination.y = _GET_MESH_Y(iy); - - #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 - - 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 - prepare_internal_fast_move_to_destination(fr_mm_s); - #else - prepare_internal_move_to_destination(fr_mm_s); - #endif + 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; } + + // Move to current_position, as modified by I, J, P parameters + destination = current_position; + + if (hasI) destination.x = bedlevel.get_mesh_x(ix); + if (hasJ) destination.y = bedlevel.get_mesh_y(iy); + + #if HAS_PROBE_XY_OFFSET + if (parser.seen_test('P')) { + if (hasI) destination.x -= probe.offset_xy.x; + if (hasJ) destination.y -= probe.offset_xy.y; + } + #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 + prepare_internal_fast_move_to_destination(fr_mm_s); + #else + 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 96122c18f8..05fa98e459 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,14 +45,14 @@ /** * 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 * * With mesh-based leveling only: * @@ -67,18 +70,21 @@ void GcodeSuite::M420() { const float x_min = probe.min_x(), x_max = probe.max_x(), y_min = probe.min_y(), y_max = probe.max_y(); #if ENABLED(AUTO_BED_LEVELING_BILINEAR) - bilinear_start.set(x_min, y_min); - bilinear_grid_spacing.set((x_max - x_min) / (GRID_MAX_POINTS_X - 1), - (y_max - y_min) / (GRID_MAX_POINTS_Y - 1)); + xy_pos_t start, spacing; + start.set(x_min, y_min); + spacing.set((x_max - x_min) / (GRID_MAX_CELLS_X), + (y_max - y_min) / (GRID_MAX_CELLS_Y)); + bedlevel.set_grid(spacing, start); #endif GRID_LOOP(x, y) { - Z_VALUES(x, y) = 0.001 * random(-200, 200); - TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(x, y, Z_VALUES(x, y))); + bedlevel.z_values[x][y] = 0.001 * random(-200, 200); + TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(x, y, bedlevel.z_values[x][y])); } + 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_ECHOPAIR(" (", x_min); + SERIAL_ECHOPGM(" (", x_min); SERIAL_CHAR(','); SERIAL_ECHO(y_min); - SERIAL_ECHOPAIR(")-(", x_max); + SERIAL_ECHOPGM(")-(", x_max); SERIAL_CHAR(','); SERIAL_ECHO(y_max); SERIAL_ECHOLNPGM(")"); } @@ -98,26 +104,25 @@ void GcodeSuite::M420() { set_bed_leveling_enabled(false); #if ENABLED(EEPROM_SETTINGS) - const int8_t storage_slot = parser.has_value() ? parser.value_int() : ubl.storage_slot; + const int8_t storage_slot = parser.has_value() ? parser.value_int() : bedlevel.storage_slot; const int16_t a = settings.calc_num_meshes(); if (!a) { - SERIAL_ECHOLNPGM("?EEPROM storage not available."); + SERIAL_ECHOLNPGM(GCODE_ERR_MSG("EEPROM storage not available.")); return; } if (!WITHIN(storage_slot, 0, a - 1)) { - SERIAL_ECHOLNPGM("?Invalid storage slot."); - SERIAL_ECHOLNPAIR("?Use 0 to ", a - 1); + SERIAL_ECHOLNPGM(GCODE_ERR_MSG("Invalid storage slot. Use 0 to ", a - 1)); return; } settings.load_mesh(storage_slot); - ubl.storage_slot = storage_slot; + bedlevel.storage_slot = storage_slot; #else - SERIAL_ECHOLNPGM("?EEPROM storage not available."); + SERIAL_ECHOLNPGM(GCODE_ERR_MSG("EEPROM storage not available.")); return; #endif @@ -125,15 +130,15 @@ void GcodeSuite::M420() { // L or V display the map info if (parser.seen("LV")) { - ubl.display_map(parser.byteval('T')); + bedlevel.display_map(parser.byteval('T')); SERIAL_ECHOPGM("Mesh is "); - if (!ubl.mesh_is_valid()) SERIAL_ECHOPGM("in"); - SERIAL_ECHOLNPAIR("valid\nStorage slot: ", ubl.storage_slot); + if (!bedlevel.mesh_is_valid()) SERIAL_ECHOPGM("in"); + SERIAL_ECHOLNPGM("valid\nStorage slot: ", bedlevel.storage_slot); } #endif // AUTO_BED_LEVELING_UBL - const bool seenV = parser.seen('V'); + const bool seenV = parser.seen_test('V'); #if HAS_MESH @@ -145,7 +150,7 @@ void GcodeSuite::M420() { #if ENABLED(AUTO_BED_LEVELING_UBL) set_bed_leveling_enabled(false); - ubl.adjust_mesh_to_mean(true, cval); + bedlevel.adjust_mesh_to_mean(true, cval); #else @@ -153,19 +158,19 @@ void GcodeSuite::M420() { // Get the sum and average of all mesh values float mesh_sum = 0; - GRID_LOOP(x, y) mesh_sum += Z_VALUES(x, y); + GRID_LOOP(x, y) mesh_sum += bedlevel.z_values[x][y]; const float zmean = mesh_sum / float(GRID_MAX_POINTS); - #else + #else // midrange - // Find the low and high mesh values + // Find the low and high mesh values. float lo_val = 100, hi_val = -100; GRID_LOOP(x, y) { - const float z = Z_VALUES(x, y); + const float z = bedlevel.z_values[x][y]; NOMORE(lo_val, z); NOLESS(hi_val, z); } - // Take the mean of the lowest and highest + // Get the midrange plus C value. (The median may be better.) const float zmean = (lo_val + hi_val) / 2.0 + cval; #endif @@ -175,10 +180,10 @@ void GcodeSuite::M420() { set_bed_leveling_enabled(false); // Subtract the mean from all values GRID_LOOP(x, y) { - Z_VALUES(x, y) -= zmean; - TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(x, y, Z_VALUES(x, y))); + bedlevel.z_values[x][y] -= zmean; + TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(x, y, bedlevel.z_values[x][y])); } - TERN_(ABL_BILINEAR_SUBDIVISION, bed_level_virt_interpolate()); + TERN_(AUTO_BED_LEVELING_BILINEAR, bedlevel.refresh_bed_level()); } #endif @@ -195,15 +200,14 @@ void GcodeSuite::M420() { // V to print the matrix or mesh if (seenV) { #if ABL_PLANAR - planner.bed_level_matrix.debug(PSTR("Bed Level Correction Matrix:")); + planner.bed_level_matrix.debug(F("Bed Level Correction Matrix:")); #else if (leveling_is_valid()) { #if ENABLED(AUTO_BED_LEVELING_BILINEAR) - print_bilinear_leveling_grid(); - TERN_(ABL_BILINEAR_SUBDIVISION, print_bilinear_leveling_grid_virt()); + bedlevel.print_leveling_grid(); #elif ENABLED(MESH_BED_LEVELING) SERIAL_ECHOLNPGM("Mesh Bed Level data:"); - mbl.report_mesh(); + bedlevel.report_mesh(); #endif } #endif @@ -224,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(); @@ -242,4 +244,19 @@ void GcodeSuite::M420() { report_current_position(); } +void GcodeSuite::M420_report(const bool forReplay/*=true*/) { + TERN_(MARLIN_SMALL_BUILD, return); + + report_heading_etc(forReplay, F( + TERN(MESH_BED_LEVELING, "Mesh Bed Leveling", TERN(AUTO_BED_LEVELING_UBL, "Unified Bed Leveling", "Auto Bed Leveling")) + )); + 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 "), ON_OFF(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 f7de6c8cee..800d51dac6 100644 --- a/Marlin/src/gcode/bedlevel/abl/G29.cpp +++ b/Marlin/src/gcode/bedlevel/abl/G29.cpp @@ -32,19 +32,10 @@ #include "../../../feature/bedlevel/bedlevel.h" #include "../../../module/motion.h" #include "../../../module/planner.h" -#include "../../../module/stepper.h" #include "../../../module/probe.h" +#include "../../../module/temperature.h" #include "../../queue.h" -#if ENABLED(PROBE_TEMP_COMPENSATION) - #include "../../../feature/probe_temp_comp.h" - #include "../../../module/temperature.h" -#endif - -#if HAS_DISPLAY - #include "../../../lcd/ultralcd.h" -#endif - #if ENABLED(AUTO_BED_LEVELING_LINEAR) #include "../../../libs/least_squares_fit.h" #endif @@ -52,204 +43,241 @@ #if ABL_PLANAR #include "../../../libs/vector_3.h" #endif +#if ENABLED(BD_SENSOR_PROBE_NO_STOP) + #include "../../../feature/bedlevel/bdl/bdl.h" +#endif + +#include "../../../lcd/marlinui.h" +#if ENABLED(EXTENSIBLE_UI) + #include "../../../lcd/extui/ui_api.h" +#elif ENABLED(DWIN_CREALITY_LCD) + #include "../../../lcd/dwin/creality/dwin.h" +#elif ENABLED(SOVOL_SV06_RTS) + #include "../../../lcd/sovol_rts/sovol_rts.h" +#endif #define DEBUG_OUT ENABLED(DEBUG_LEVELING_FEATURE) #include "../../../core/debug_out.h" -#if ENABLED(EXTENSIBLE_UI) - #include "../../../lcd/extui/ui_api.h" +#if DISABLED(PROBE_MANUALLY) && ENABLED(FT_MOTION) + #include "../../../module/ft_motion.h" #endif -#if ENABLED(DWIN_CREALITY_LCD) - #include "../../../lcd/dwin/e3v2/dwin.h" -#endif - -#if HAS_MULTI_HOTEND - #include "../../../module/tool_change.h" -#endif - -#if ABL_GRID +#if ABL_USES_GRID #if ENABLED(PROBE_Y_FIRST) - #define PR_OUTER_VAR meshCount.x - #define PR_OUTER_END abl_grid_points.x - #define PR_INNER_VAR meshCount.y - #define PR_INNER_END abl_grid_points.y + #define PR_OUTER_VAR abl.meshCount.x + #define PR_OUTER_SIZE abl.grid_points.x + #define PR_INNER_VAR abl.meshCount.y + #define PR_INNER_SIZE abl.grid_points.y #else - #define PR_OUTER_VAR meshCount.y - #define PR_OUTER_END abl_grid_points.y - #define PR_INNER_VAR meshCount.x - #define PR_INNER_END abl_grid_points.x + #define PR_OUTER_VAR abl.meshCount.y + #define PR_OUTER_SIZE abl.grid_points.y + #define PR_INNER_VAR abl.meshCount.x + #define PR_INNER_SIZE abl.grid_points.x #endif #endif -#define G29_RETURN(b) return TERN_(G29_RETRY_AND_RECOVER, b) - /** - * 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: - * - * 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 - * - * Parameters with DEBUG_LEVELING_FEATURE only: - * - * C Make a totally fake grid with no actual probing. - * For use in testing when no probing is possible. - * - * Parameters with BILINEAR leveling only: - * - * Z Supply an additional Z probe offset - * - * Extra parameters 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. - * - * Q Query leveling and G29 state - * - * A Abort current leveling procedure - * - * Extra parameters with BILINEAR only: - * - * 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. - * - * 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. + * @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. */ -G29_TYPE GcodeSuite::G29() { - - reset_stepper_timeout(); - - const bool seenQ = EITHER(DEBUG_LEVELING_FEATURE, PROBE_MANUALLY) && parser.seen('Q'); - - // G29 Q is also available if debugging - #if ENABLED(DEBUG_LEVELING_FEATURE) - const uint8_t old_debug_flags = marlin_debug_flags; - if (seenQ) marlin_debug_flags |= MARLIN_DEBUG_LEVELING; - DEBUG_SECTION(log_G29, "G29", DEBUGGING(LEVELING)); - if (DEBUGGING(LEVELING)) log_machine_info(); - marlin_debug_flags = old_debug_flags; - if (DISABLED(PROBE_MANUALLY) && seenQ) G29_RETURN(false); - #endif - - const bool seenA = TERN0(PROBE_MANUALLY, parser.seen('A')), - no_action = seenA || seenQ, - faux = ENABLED(DEBUG_LEVELING_FEATURE) && DISABLED(PROBE_MANUALLY) ? parser.boolval('C') : no_action; - - // Don't allow auto-leveling without homing first - if (homing_needed_error()) G29_RETURN(false); - - if (!no_action && planner.leveling_active && parser.boolval('O')) { // Auto-level only if needed - if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("> Auto-level not needed, skip"); - G29_RETURN(false); +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 DISABLED(G29_RETRY_AND_RECOVER) + if (!retry || did) { + TERN_(DWIN_CREALITY_LCD, dwinLevelingDone()); + TERN_(EXTENSIBLE_UI, ExtUI::onLevelingDone()); + } + #endif +} - // Define local vars 'static' for manual probing, 'auto' otherwise - #define ABL_VAR TERN_(PROBE_MANUALLY, static) +#define G29_RETURN(retry, did) do{ \ + pre_g29_return(TERN0(G29_RETRY_AND_RECOVER, retry), did); \ + return TERN_(G29_RETRY_AND_RECOVER, retry); \ +}while(0) - ABL_VAR int verbose_level; - ABL_VAR xy_pos_t probePos; - ABL_VAR float measured_z; - ABL_VAR bool dryrun, abl_should_enable; +// For manual probing values persist over multiple G29 +class G29_State { +public: + int verbose_level; + xy_pos_t probePos; + float measured_z; + bool dryrun, + reenable; - #if EITHER(PROBE_MANUALLY, AUTO_BED_LEVELING_LINEAR) - ABL_VAR int abl_probe_index; + #if ANY(PROBE_MANUALLY, AUTO_BED_LEVELING_LINEAR) + int abl_probe_index; #endif - #if ABL_GRID + #if ENABLED(AUTO_BED_LEVELING_LINEAR) + grid_count_t abl_points; + #elif ENABLED(AUTO_BED_LEVELING_3POINT) + static constexpr grid_count_t abl_points = 3; + #elif ABL_USES_GRID + static constexpr grid_count_t abl_points = GRID_MAX_POINTS; + #endif - #if ENABLED(PROBE_MANUALLY) - ABL_VAR xy_int8_t meshCount; - #endif + #if ABL_USES_GRID - ABL_VAR xy_pos_t probe_position_lf, probe_position_rb; - ABL_VAR xy_float_t gridSpacing = { 0, 0 }; + xy_int8_t meshCount; + + xy_pos_t probe_position_lf, + probe_position_rb; + + xy_float_t gridSpacing; // = { 0.0f, 0.0f } #if ENABLED(AUTO_BED_LEVELING_LINEAR) - ABL_VAR bool do_topography_map; - ABL_VAR xy_uint8_t abl_grid_points; + bool topography_map; + xy_uint8_t grid_points; #else // Bilinear - constexpr xy_uint8_t abl_grid_points = { GRID_MAX_POINTS_X, GRID_MAX_POINTS_Y }; - #endif - - #if ENABLED(AUTO_BED_LEVELING_LINEAR) - ABL_VAR int abl_points; - #else - int constexpr abl_points = GRID_MAX_POINTS; + static constexpr xy_uint8_t grid_points = { GRID_MAX_POINTS_X, GRID_MAX_POINTS_Y }; #endif #if ENABLED(AUTO_BED_LEVELING_BILINEAR) - - ABL_VAR float zoffset; - - #elif ENABLED(AUTO_BED_LEVELING_LINEAR) - - ABL_VAR int indexIntoAB[GRID_MAX_POINTS_X][GRID_MAX_POINTS_Y]; - - ABL_VAR float eqnAMatrix[(GRID_MAX_POINTS) * 3], // "A" matrix of the linear system of equations - eqnBVector[GRID_MAX_POINTS], // "B" vector of Z points - mean; + float Z_offset; + bed_mesh_t z_values; #endif - #elif ENABLED(AUTO_BED_LEVELING_3POINT) - - #if ENABLED(PROBE_MANUALLY) - int constexpr abl_points = 3; // used to show total points + #if ENABLED(AUTO_BED_LEVELING_LINEAR) + int indexIntoAB[GRID_MAX_POINTS_X][GRID_MAX_POINTS_Y]; + float eqnAMatrix[GRID_MAX_POINTS * 3], // "A" matrix of the linear system of equations + eqnBVector[GRID_MAX_POINTS], // "B" vector of Z points + mean; #endif + #endif +}; +#if ABL_USES_GRID && ANY(AUTO_BED_LEVELING_3POINT, AUTO_BED_LEVELING_BILINEAR) + constexpr xy_uint8_t G29_State::grid_points; + constexpr grid_count_t G29_State::abl_points; +#endif + +/** + * G29: Bed Leveling + * + * 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. + * + * Parameters: + * O Auto-level only if needed (Optional) + * + * 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<0-4> Set the verbose level (0-4) + * Example: G29 V3 + * + * With AUTO_BED_LEVELING_LINEAR: + * 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. + * + * 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 + * + * 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 + * + * With DEBUG_LEVELING_FEATURE: + * C Make a totally fake grid with no actual probing. + * For use in testing when no probing is possible. + * + * 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. + * + * 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. + */ +G29_TYPE GcodeSuite::G29() { + + DEBUG_SECTION(log_G29, "G29", DEBUGGING(LEVELING)); + + // Leveling state is persistent when done manually with multiple G29 commands + TERN_(PROBE_MANUALLY, static) G29_State abl; + + // Keep powered steppers from timing out + reset_stepper_timeout(); + + // Q = Query leveling and G29 state + const bool seenQ = ANY(DEBUG_LEVELING_FEATURE, PROBE_MANUALLY) && parser.seen_test('Q'); + + // G29 Q is also available if debugging + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (seenQ || DEBUGGING(LEVELING)) log_machine_info(); + if (DISABLED(PROBE_MANUALLY) && seenQ) G29_RETURN(false, false); + #endif + + // A = Abort manual probing + // C = Generate fake probe points (DEBUG_LEVELING_FEATURE) + const bool seenA = TERN0(PROBE_MANUALLY, parser.seen_test('A')), + no_action = seenA || seenQ, + faux = ENABLED(DEBUG_LEVELING_FEATURE) && DISABLED(PROBE_MANUALLY) ? parser.boolval('C') : no_action; + + // O = Don't level if leveling is already active + if (!no_action && planner.leveling_active && parser.boolval('O')) { + if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("> Auto-level not needed, skip"); + G29_RETURN(false, false); + } + + // 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))); + + // Don't allow auto-leveling without homing first + if (homing_needed_error()) G29_RETURN(false, false); + + // 3-point leveling gets points from the probe class + #if ENABLED(AUTO_BED_LEVELING_3POINT) vector_3 points[3]; probe.get_three_points(points); + #endif - #endif // AUTO_BED_LEVELING_3POINT - + // Storage for ABL Linear results #if ENABLED(AUTO_BED_LEVELING_LINEAR) struct linear_fit_data lsf_results; - incremental_LSF_reset(&lsf_results); + #endif + + // Set and report "probing" state to host + TERN_(FULL_REPORT_TO_HOST_FEATURE, 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 /** @@ -257,50 +285,63 @@ G29_TYPE GcodeSuite::G29() { */ if (!g29_in_progress) { - TERN_(HAS_MULTI_HOTEND, if (active_extruder) tool_change(0)); + probe.use_probing_tool(); - #if EITHER(PROBE_MANUALLY, AUTO_BED_LEVELING_LINEAR) - abl_probe_index = -1; + #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 - abl_should_enable = planner.leveling_active; + #if ANY(PROBE_MANUALLY, AUTO_BED_LEVELING_LINEAR) + abl.abl_probe_index = -1; + #endif + + abl.reenable = planner.leveling_active; #if ENABLED(AUTO_BED_LEVELING_BILINEAR) - const bool seen_w = parser.seen('W'); + const bool seen_w = parser.seen_test('W'); if (seen_w) { if (!leveling_is_valid()) { SERIAL_ERROR_MSG("No bilinear grid"); - G29_RETURN(false); + G29_RETURN(false, false); } const float rz = parser.seenval('Z') ? RAW_Z_POSITION(parser.value_linear_units()) : current_position.z; if (!WITHIN(rz, -10, 10)) { SERIAL_ERROR_MSG("Bad Z value"); - G29_RETURN(false); + G29_RETURN(false, false); } const float rx = RAW_X_POSITION(parser.linearval('X', NAN)), ry = RAW_Y_POSITION(parser.linearval('Y', NAN)); int8_t i = parser.byteval('I', -1), j = parser.byteval('J', -1); + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" + if (!isnan(rx) && !isnan(ry)) { // Get nearest i / j from rx / ry - i = (rx - bilinear_start.x + 0.5 * gridSpacing.x) / gridSpacing.x; - j = (ry - bilinear_start.y + 0.5 * gridSpacing.y) / gridSpacing.y; - LIMIT(i, 0, GRID_MAX_POINTS_X - 1); - LIMIT(j, 0, GRID_MAX_POINTS_Y - 1); + i = (rx - bedlevel.grid_start.x) / bedlevel.grid_spacing.x + 0.5f; + j = (ry - bedlevel.grid_start.y) / bedlevel.grid_spacing.y + 0.5f; + LIMIT(i, 0, (GRID_MAX_POINTS_X) - 1); + LIMIT(j, 0, (GRID_MAX_POINTS_Y) - 1); } - if (WITHIN(i, 0, GRID_MAX_POINTS_X - 1) && WITHIN(j, 0, GRID_MAX_POINTS_Y)) { + + #pragma GCC diagnostic pop + + if (WITHIN(i, 0, (GRID_MAX_POINTS_X) - 1) && WITHIN(j, 0, (GRID_MAX_POINTS_Y) - 1)) { set_bed_leveling_enabled(false); - z_values[i][j] = rz; - TERN_(ABL_BILINEAR_SUBDIVISION, bed_level_virt_interpolate()); + bedlevel.z_values[i][j] = rz; + bedlevel.refresh_bed_level(); TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(i, j, rz)); - set_bed_leveling_enabled(abl_should_enable); - if (abl_should_enable) report_current_position(); + if (abl.reenable) { + set_bed_leveling_enabled(true); + report_current_position(); + } } - G29_RETURN(false); - } // parser.seen('W') + G29_RETURN(false, false); + } // parser.seen_test('W') #else @@ -309,139 +350,175 @@ G29_TYPE GcodeSuite::G29() { #endif // Jettison bed leveling data - if (!seen_w && parser.seen('J')) { + if (!seen_w && parser.seen_test('J')) { reset_bed_level(); - G29_RETURN(false); + G29_RETURN(false, false); } - verbose_level = parser.intval('V'); - if (!WITHIN(verbose_level, 0, 4)) { - SERIAL_ECHOLNPGM("?(V)erbose level implausible (0-4)."); - G29_RETURN(false); + abl.verbose_level = parser.intval('V'); + if (!WITHIN(abl.verbose_level, 0, 4)) { + SERIAL_ECHOLNPGM(GCODE_ERR_MSG("(V)erbose level implausible (0-4).")); + G29_RETURN(false, false); } - dryrun = parser.boolval('D') || TERN0(PROBE_MANUALLY, no_action); + abl.dryrun = parser.boolval('D') || TERN0(PROBE_MANUALLY, no_action); #if ENABLED(AUTO_BED_LEVELING_LINEAR) - do_topography_map = verbose_level > 2 || parser.boolval('T'); + incremental_LSF_reset(&lsf_results); + + abl.topography_map = abl.verbose_level > 2 || parser.boolval('T'); // X and Y specify points in each direction, overriding the default // These values may be saved with the completed mesh - abl_grid_points.set( + abl.grid_points.set( parser.byteval('X', GRID_MAX_POINTS_X), parser.byteval('Y', GRID_MAX_POINTS_Y) ); - if (parser.seenval('P')) abl_grid_points.x = abl_grid_points.y = parser.value_int(); + if (parser.seenval('P')) abl.grid_points.x = abl.grid_points.y = parser.value_int(); - if (!WITHIN(abl_grid_points.x, 2, GRID_MAX_POINTS_X)) { - SERIAL_ECHOLNPGM("?Probe points (X) implausible (2-" STRINGIFY(GRID_MAX_POINTS_X) ")."); - G29_RETURN(false); + if (!WITHIN(abl.grid_points.x, 2, GRID_MAX_POINTS_X)) { + SERIAL_ECHOLNPGM(GCODE_ERR_MSG("Probe points (X) implausible (2-" STRINGIFY(GRID_MAX_POINTS_X) ").")); + G29_RETURN(false, false); } - if (!WITHIN(abl_grid_points.y, 2, GRID_MAX_POINTS_Y)) { - SERIAL_ECHOLNPGM("?Probe points (Y) implausible (2-" STRINGIFY(GRID_MAX_POINTS_Y) ")."); - G29_RETURN(false); + if (!WITHIN(abl.grid_points.y, 2, GRID_MAX_POINTS_Y)) { + SERIAL_ECHOLNPGM(GCODE_ERR_MSG("Probe points (Y) implausible (2-" STRINGIFY(GRID_MAX_POINTS_Y) ").")); + G29_RETURN(false, false); } - abl_points = abl_grid_points.x * abl_grid_points.y; - mean = 0; + abl.abl_points = abl.grid_points.x * abl.grid_points.y; + abl.mean = 0; #elif ENABLED(AUTO_BED_LEVELING_BILINEAR) - zoffset = parser.linearval('Z'); + abl.Z_offset = parser.linearval('Z'); #endif - #if ABL_GRID + #if ABL_USES_GRID - xy_probe_feedrate_mm_s = MMM_TO_MMS(parser.linearval('S', XY_PROBE_SPEED)); + constexpr feedRate_t min_probe_feedrate_mm_s = XY_PROBE_FEEDRATE_MIN; + xy_probe_feedrate_mm_s = MMM_TO_MMS(parser.linearval('S', XY_PROBE_FEEDRATE)); + if (xy_probe_feedrate_mm_s < min_probe_feedrate_mm_s) { + xy_probe_feedrate_mm_s = min_probe_feedrate_mm_s; + SERIAL_ECHOLNPGM(GCODE_ERR_MSG("Feedrate (S) too low. (Using ", min_probe_feedrate_mm_s, ")")); + } const float x_min = probe.min_x(), x_max = probe.max_x(), y_min = probe.min_y(), y_max = probe.max_y(); if (parser.seen('H')) { const int16_t size = (int16_t)parser.value_linear_units(); - probe_position_lf.set( - _MAX(X_CENTER - size / 2, x_min), - _MAX(Y_CENTER - size / 2, y_min) - ); - probe_position_rb.set( - _MIN(probe_position_lf.x + size, x_max), - _MIN(probe_position_lf.y + size, y_max) - ); + abl.probe_position_lf.set(_MAX((X_CENTER) - size / 2, x_min), _MAX((Y_CENTER) - size / 2, y_min)); + abl.probe_position_rb.set(_MIN(abl.probe_position_lf.x + size, x_max), _MIN(abl.probe_position_lf.y + size, y_max)); } else { - probe_position_lf.set( - parser.seenval('L') ? RAW_X_POSITION(parser.value_linear_units()) : x_min, - parser.seenval('F') ? RAW_Y_POSITION(parser.value_linear_units()) : y_min - ); - probe_position_rb.set( - parser.seenval('R') ? RAW_X_POSITION(parser.value_linear_units()) : x_max, - parser.seenval('B') ? RAW_Y_POSITION(parser.value_linear_units()) : y_max - ); + abl.probe_position_lf.set(parser.linearval('L', x_min), parser.linearval('F', y_min)); + abl.probe_position_rb.set(parser.linearval('R', x_max), parser.linearval('B', y_max)); } - if (!probe.good_bounds(probe_position_lf, probe_position_rb)) { - SERIAL_ECHOLNPGM("? (L,R,F,B) out of bounds."); - G29_RETURN(false); + if (!probe.good_bounds(abl.probe_position_lf, abl.probe_position_rb)) { + if (DEBUGGING(LEVELING)) { + DEBUG_ECHOLNPGM("G29 L", abl.probe_position_lf.x, " R", abl.probe_position_rb.x, + " F", abl.probe_position_lf.y, " B", abl.probe_position_rb.y); + } + SERIAL_ECHOLNPGM(GCODE_ERR_MSG(" (L,R,F,B) out of bounds.")); + G29_RETURN(false, false); } - // probe at the points of a lattice grid - gridSpacing.set((probe_position_rb.x - probe_position_lf.x) / (abl_grid_points.x - 1), - (probe_position_rb.y - probe_position_lf.y) / (abl_grid_points.y - 1)); + // Probe at the points of a lattice grid + abl.gridSpacing.set((abl.probe_position_rb.x - abl.probe_position_lf.x) / (abl.grid_points.x - 1), + (abl.probe_position_rb.y - abl.probe_position_lf.y) / (abl.grid_points.y - 1)); - #endif // ABL_GRID + #endif // ABL_USES_GRID - if (verbose_level > 0) { + if (abl.verbose_level > 0) { SERIAL_ECHOPGM("G29 Auto Bed Leveling"); - if (dryrun) SERIAL_ECHOPGM(" (DRYRUN)"); + if (abl.dryrun) SERIAL_ECHOPGM(" (DRYRUN)"); SERIAL_EOL(); } planner.synchronize(); - if (!faux) remember_feedrate_scaling_off(); + #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 + #endif + + TERN_(EXTENSIBLE_UI, ExtUI::onLevelingStart()); + + if (!faux) { + 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, + 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; + #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 + + do_blocking_move_to(safe_position); + #endif // HAS_SAFE_BED_LEVELING // Disable auto bed leveling during G29. // Be formal so G29 can be done successively without G28. if (!no_action) set_bed_leveling_enabled(false); // Deploy certain probes before starting probing - #if HAS_BED_PROBE - if (ENABLED(BLTOUCH)) - do_z_clearance(Z_CLEARANCE_DEPLOY_PROBE); - else if (probe.deploy()) { - set_bed_leveling_enabled(abl_should_enable); - G29_RETURN(false); + #if ENABLED(BLTOUCH) || ALL(HAS_Z_SERVO_PROBE, Z_SERVO_INTERMEDIATE_STOW) + 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); + G29_RETURN(false, true); } #endif #if ENABLED(AUTO_BED_LEVELING_BILINEAR) - - if (TERN1(PROBE_MANUALLY, !no_action) - && (gridSpacing != bilinear_grid_spacing || probe_position_lf != bilinear_start) - ) { - // Reset grid to 0.0 or "not probed". (Also disables ABL) - reset_bed_level(); - - // Initialize a grid with the given dimensions - bilinear_grid_spacing = gridSpacing; - bilinear_start = probe_position_lf; - - // Can't re-enable (on error) until the new grid is written - abl_should_enable = false; + if (!abl.dryrun && (abl.gridSpacing != bedlevel.grid_spacing || abl.probe_position_lf != bedlevel.grid_start)) { + reset_bed_level(); // Reset grid to 0.0 or "not probed". (Also disables ABL) + abl.reenable = false; // Can't re-enable (on error) until the new grid is written } - - #endif // AUTO_BED_LEVELING_BILINEAR - - #if ENABLED(AUTO_BED_LEVELING_3POINT) - - if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("> 3-point Leveling"); - - // Probe at 3 arbitrary points - points[0].z = points[1].z = points[2].z = 0; - - #endif // AUTO_BED_LEVELING_3POINT + // Pre-populate local Z values from the stored mesh + TERN_(IS_KINEMATIC, COPY(abl.z_values, bedlevel.z_values)); + #endif } // !g29_in_progress @@ -450,7 +527,7 @@ G29_TYPE GcodeSuite::G29() { // For manual probing, get the next index to probe now. // On the first probe this will be incremented to 0. if (!no_action) { - ++abl_probe_index; + ++abl.abl_probe_index; g29_in_progress = true; } @@ -458,25 +535,24 @@ G29_TYPE GcodeSuite::G29() { if (seenA && g29_in_progress) { SERIAL_ECHOLNPGM("Manual G29 aborted"); SET_SOFT_ENDSTOP_LOOSE(false); - set_bed_leveling_enabled(abl_should_enable); + set_bed_leveling_enabled(abl.reenable); g29_in_progress = false; TERN_(LCD_BED_LEVELING, ui.wait_for_move = false); } // Query G29 status - if (verbose_level || seenQ) { + if (abl.verbose_level || seenQ) { SERIAL_ECHOPGM("Manual G29 "); - if (g29_in_progress) { - SERIAL_ECHOPAIR("point ", _MIN(abl_probe_index + 1, abl_points)); - SERIAL_ECHOLNPAIR(" of ", abl_points); - } + if (g29_in_progress) + SERIAL_ECHOLNPGM("point ", _MIN(abl.abl_probe_index + 1, abl.abl_points), " of ", abl.abl_points); else SERIAL_ECHOLNPGM("idle"); } - if (no_action) G29_RETURN(false); + // For 'A' or 'Q' exit with success state + if (no_action) G29_RETURN(false, true); - if (abl_probe_index == 0) { + if (abl.abl_probe_index == 0) { // For the initial G29 S2 save software endstop state SET_SOFT_ENDSTOP_LOOSE(true); // Move close to the bed before the first point @@ -484,35 +560,35 @@ G29_TYPE GcodeSuite::G29() { } else { - #if EITHER(AUTO_BED_LEVELING_LINEAR, AUTO_BED_LEVELING_3POINT) - const uint16_t index = abl_probe_index - 1; + #if ANY(AUTO_BED_LEVELING_LINEAR, AUTO_BED_LEVELING_3POINT) + const uint16_t index = abl.abl_probe_index - 1; #endif // For G29 after adjusting Z. // Save the previous Z before going to the next point - measured_z = current_position.z; + abl.measured_z = current_position.z; #if ENABLED(AUTO_BED_LEVELING_LINEAR) - mean += measured_z; - eqnBVector[index] = measured_z; - eqnAMatrix[index + 0 * abl_points] = probePos.x; - eqnAMatrix[index + 1 * abl_points] = probePos.y; - eqnAMatrix[index + 2 * abl_points] = 1; + abl.mean += abl.measured_z; + abl.eqnBVector[index] = abl.measured_z; + abl.eqnAMatrix[index + 0 * abl.abl_points] = abl.probePos.x; + abl.eqnAMatrix[index + 1 * abl.abl_points] = abl.probePos.y; + abl.eqnAMatrix[index + 2 * abl.abl_points] = 1; - incremental_LSF(&lsf_results, probePos, measured_z); + incremental_LSF(&lsf_results, abl.probePos, abl.measured_z); #elif ENABLED(AUTO_BED_LEVELING_3POINT) - points[index].z = measured_z; + points[index].z = abl.measured_z; #elif ENABLED(AUTO_BED_LEVELING_BILINEAR) - const float newz = measured_z + zoffset; - z_values[meshCount.x][meshCount.y] = newz; - TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(meshCount, newz)); + const float newz = abl.measured_z + abl.Z_offset; + abl.z_values[abl.meshCount.x][abl.meshCount.y] = newz; + TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(abl.meshCount, newz)); - if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPAIR_P(PSTR("Save X"), meshCount.x, SP_Y_STR, meshCount.y, SP_Z_STR, measured_z + zoffset); + if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM_P(PSTR("Save X"), abl.meshCount.x, SP_Y_STR, abl.meshCount.y, SP_Z_STR, abl.measured_z + abl.Z_offset); #endif } @@ -521,35 +597,35 @@ G29_TYPE GcodeSuite::G29() { // If there's another point to sample, move there with optional lift. // - #if ABL_GRID + #if ABL_USES_GRID // Skip any unreachable points - while (abl_probe_index < abl_points) { + while (abl.abl_probe_index < abl.abl_points) { - // Set meshCount.x, meshCount.y based on abl_probe_index, with zig-zag - PR_OUTER_VAR = abl_probe_index / PR_INNER_END; - PR_INNER_VAR = abl_probe_index - (PR_OUTER_VAR * PR_INNER_END); + // Set abl.meshCount.x, abl.meshCount.y based on abl.abl_probe_index, with zig-zag + PR_OUTER_VAR = abl.abl_probe_index / PR_INNER_SIZE; + PR_INNER_VAR = abl.abl_probe_index - (PR_OUTER_VAR * PR_INNER_SIZE); // Probe in reverse order for every other row/column - const bool zig = (PR_OUTER_VAR & 1); // != ((PR_OUTER_END) & 1); - if (zig) PR_INNER_VAR = (PR_INNER_END - 1) - PR_INNER_VAR; + const bool zig = (PR_OUTER_VAR & 1); // != ((PR_OUTER_SIZE) & 1); + if (zig) PR_INNER_VAR = (PR_INNER_SIZE - 1) - PR_INNER_VAR; - probePos = probe_position_lf + gridSpacing * meshCount.asFloat(); + abl.probePos = abl.probe_position_lf + abl.gridSpacing * abl.meshCount.asFloat(); - TERN_(AUTO_BED_LEVELING_LINEAR, indexIntoAB[meshCount.x][meshCount.y] = abl_probe_index); + 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(probePos)) break; - ++abl_probe_index; + if (position_is_reachable(abl.probePos)) break; + ++abl.abl_probe_index; } // Is there a next point to move to? - if (abl_probe_index < abl_points) { - _manual_goto_xy(probePos); // Can be used here too! + if (abl.abl_probe_index < abl.abl_points) { + _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); - G29_RETURN(false); + G29_RETURN(false, true); } else { // Leveling done! Fall through to G29 finishing code below @@ -561,13 +637,13 @@ G29_TYPE GcodeSuite::G29() { #elif ENABLED(AUTO_BED_LEVELING_3POINT) // Probe at 3 arbitrary points - if (abl_probe_index < abl_points) { - probePos = points[abl_probe_index]; - _manual_goto_xy(probePos); + if (abl.abl_probe_index < abl.abl_points) { + abl.probePos = xy_pos_t(points[abl.abl_probe_index]); + _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); - G29_RETURN(false); + G29_RETURN(false, true); } else { @@ -576,13 +652,13 @@ G29_TYPE GcodeSuite::G29() { // Re-enable software endstops, if needed SET_SOFT_ENDSTOP_LOOSE(false); - if (!dryrun) { + if (!abl.dryrun) { vector_3 planeNormal = vector_3::cross(points[0] - points[1], points[2] - points[1]).get_normal(); if (planeNormal.z < 0) planeNormal *= -1; planner.bed_level_matrix = matrix_3x3::create_look_at(planeNormal); // Can't re-enable (on error) until the new grid is written - abl_should_enable = false; + abl.reenable = false; } } @@ -593,84 +669,141 @@ G29_TYPE GcodeSuite::G29() { { const ProbePtRaise raise_after = parser.boolval('E') ? PROBE_PT_STOW : PROBE_PT_RAISE; - measured_z = 0; + abl.measured_z = 0; - #if ABL_GRID + #if ABL_USES_GRID - bool zig = PR_OUTER_END & 1; // Always end at RIGHT and BACK_PROBE_BED_POSITION - - measured_z = 0; - - xy_int8_t meshCount; + bool zig = PR_OUTER_SIZE & 1; // Always end at RIGHT and BACK_PROBE_BED_POSITION // Outer loop is X with PROBE_Y_FIRST enabled // Outer loop is Y with PROBE_Y_FIRST disabled - for (PR_OUTER_VAR = 0; PR_OUTER_VAR < PR_OUTER_END && !isnan(measured_z); PR_OUTER_VAR++) { + for (PR_OUTER_VAR = 0; PR_OUTER_VAR < PR_OUTER_SIZE && !isnan(abl.measured_z); PR_OUTER_VAR++) { int8_t inStart, inStop, inInc; - if (zig) { // Zig away from origin - inStart = 0; // Left or front - inStop = PR_INNER_END; // Right or back - inInc = 1; // Zig right + if (zig) { // Zig away from origin + inStart = 0; // Left or front + inStop = PR_INNER_SIZE; // Right or back + inInc = 1; // Zig right } - else { // Zag towards origin - inStart = PR_INNER_END - 1; // Right or back - inStop = -1; // Left or front - inInc = -1; // Zag left + else { // Zag towards origin + inStart = PR_INNER_SIZE - 1; // Right or back + inStop = -1; // Left or front + inInc = -1; // Zag left } - zig ^= true; // zag + FLIP(zig); // zag // An index to print current state - uint8_t pt_index = (PR_OUTER_VAR) * (PR_INNER_END) + 1; + grid_count_t pt_index = (PR_OUTER_VAR) * (PR_INNER_SIZE) + 1; // Inner loop is Y with PROBE_Y_FIRST enabled // Inner loop is X with PROBE_Y_FIRST disabled for (PR_INNER_VAR = inStart; PR_INNER_VAR != inStop; pt_index++, PR_INNER_VAR += inInc) { - probePos = probe_position_lf + gridSpacing * meshCount.asFloat(); + abl.probePos = abl.probe_position_lf + abl.gridSpacing * abl.meshCount.asFloat(); - TERN_(AUTO_BED_LEVELING_LINEAR, indexIntoAB[meshCount.x][meshCount.y] = ++abl_probe_index); // 0... + TERN_(AUTO_BED_LEVELING_LINEAR, abl.indexIntoAB[abl.meshCount.x][abl.meshCount.y] = ++abl.abl_probe_index); // 0... // Avoid probing outside the round or hexagonal area - if (TERN0(IS_KINEMATIC, !probe.can_reach(probePos))) continue; + if (TERN0(IS_KINEMATIC, !probe.can_reach(abl.probePos))) continue; - if (verbose_level) SERIAL_ECHOLNPAIR("Probing mesh point ", int(pt_index), "/", abl_points, "."); - TERN_(HAS_DISPLAY, ui.status_printf_P(0, PSTR(S_FMT " %i/%i"), GET_TEXT(MSG_PROBING_MESH), int(pt_index), int(abl_points))); + if (abl.verbose_level) SERIAL_ECHOLNPGM("Probing mesh point ", pt_index, "/", abl.abl_points, "."); + TERN_(HAS_STATUS_MESSAGE, ui.status_printf(0, F(S_FMT " %i/%i"), GET_TEXT(MSG_PROBING_POINT), int(pt_index), int(abl.abl_points))); - measured_z = faux ? 0.001f * random(-100, 101) : probe.probe_at_point(probePos, raise_after, verbose_level); + #if ENABLED(BD_SENSOR_PROBE_NO_STOP) + if (PR_INNER_VAR == inStart) { + char tmp_1[32]; - if (isnan(measured_z)) { - set_bed_leveling_enabled(abl_should_enable); + // 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); + // Go to the end of the row/column ... and back up by one + // TODO: Why not just use... PR_INNER_VAR = inStop - inInc + for (PR_INNER_VAR = inStart; PR_INNER_VAR != inStop; PR_INNER_VAR += inInc); + PR_INNER_VAR -= inInc; + + // Get the coordinate of the resulting grid point + 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; + + // 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(); + // } + 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(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 + // TODO: Use NEAR() because float is imprecise + constexpr AxisEnum axis = TERN(PROBE_Y_FIRST, Y_AXIS, X_AXIS); + const float cmp = abl.probePos[axis] - probe.offset_xy[axis]; + float pos; + for (;;) { + pos = planner.get_axis_position_mm(axis); + if (inInc > 0 ? (pos >= cmp) : (pos <= cmp)) break; + 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(); + if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPGM("x_cur ", planner.get_axis_position_mm(X_AXIS), " z ", abl.measured_z); + + #else // !BD_SENSOR_PROBE_NO_STOP + + abl.measured_z = faux ? 0.001f * random(-100, 101) : probe.probe_at_point(abl.probePos, raise_after, abl.verbose_level); + + #endif + + if (isnan(abl.measured_z)) { + set_bed_leveling_enabled(abl.reenable); break; // Breaks out of both loops } - #if ENABLED(PROBE_TEMP_COMPENSATION) - temp_comp.compensate_measurement(TSI_BED, thermalManager.degBed(), measured_z); - temp_comp.compensate_measurement(TSI_PROBE, thermalManager.degProbe(), measured_z); - TERN_(USE_TEMP_EXT_COMPENSATION, temp_comp.compensate_measurement(TSI_EXT, thermalManager.degHotend(), measured_z)); - #endif - #if ENABLED(AUTO_BED_LEVELING_LINEAR) - mean += measured_z; - eqnBVector[abl_probe_index] = measured_z; - eqnAMatrix[abl_probe_index + 0 * abl_points] = probePos.x; - eqnAMatrix[abl_probe_index + 1 * abl_points] = probePos.y; - eqnAMatrix[abl_probe_index + 2 * abl_points] = 1; + abl.mean += abl.measured_z; + abl.eqnBVector[abl.abl_probe_index] = abl.measured_z; + abl.eqnAMatrix[abl.abl_probe_index + 0 * abl.abl_points] = abl.probePos.x; + abl.eqnAMatrix[abl.abl_probe_index + 1 * abl.abl_points] = abl.probePos.y; + abl.eqnAMatrix[abl.abl_probe_index + 2 * abl.abl_points] = 1; - incremental_LSF(&lsf_results, probePos, measured_z); + incremental_LSF(&lsf_results, abl.probePos, abl.measured_z); #elif ENABLED(AUTO_BED_LEVELING_BILINEAR) - z_values[meshCount.x][meshCount.y] = measured_z + zoffset; - TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(meshCount, z_values[meshCount.x][meshCount.y])); + const float z = abl.measured_z + abl.Z_offset; + 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_should_enable = false; - idle_no_sleep(); + abl.reenable = false; // Don't re-enable after modifying the mesh + marlin.idle_no_sleep(); } // inner } // outer @@ -679,50 +812,50 @@ G29_TYPE GcodeSuite::G29() { // Probe at 3 arbitrary points - LOOP_L_N(i, 3) { - if (verbose_level) SERIAL_ECHOLNPAIR("Probing point ", int(i + 1), "/3."); - TERN_(HAS_DISPLAY, ui.status_printf_P(0, PSTR(S_FMT " %i/3"), GET_TEXT(MSG_PROBING_MESH), int(i + 1))); + for (uint8_t i = 0; i < 3; ++i) { + if (abl.verbose_level) SERIAL_ECHOLNPGM("Probing point ", i + 1, "/3."); + TERN_(HAS_STATUS_MESSAGE, ui.status_printf(0, F(S_FMT " %i/3"), GET_TEXT(MSG_PROBING_POINT), int(i + 1))); // Retain the last probe position - probePos = points[i]; - measured_z = faux ? 0.001 * random(-100, 101) : probe.probe_at_point(probePos, raise_after, verbose_level); - if (isnan(measured_z)) { - set_bed_leveling_enabled(abl_should_enable); + abl.probePos = xy_pos_t(points[i]); + abl.measured_z = faux ? 0.001 * random(-100, 101) : probe.probe_at_point(abl.probePos, raise_after, abl.verbose_level); + if (isnan(abl.measured_z)) { + set_bed_leveling_enabled(abl.reenable); break; } - points[i].z = measured_z; + points[i].z = abl.measured_z; } - if (!dryrun && !isnan(measured_z)) { + if (!abl.dryrun && !isnan(abl.measured_z)) { vector_3 planeNormal = vector_3::cross(points[0] - points[1], points[2] - points[1]).get_normal(); if (planeNormal.z < 0) planeNormal *= -1; planner.bed_level_matrix = matrix_3x3::create_look_at(planeNormal); // Can't re-enable (on error) until the new grid is written - abl_should_enable = false; + abl.reenable = false; } #endif // AUTO_BED_LEVELING_3POINT - TERN_(HAS_DISPLAY, ui.reset_status()); + ui.reset_status(); // Stow the probe. No raise for FIX_MOUNTED_PROBE. if (probe.stow()) { - set_bed_leveling_enabled(abl_should_enable); - measured_z = NAN; + set_bed_leveling_enabled(abl.reenable); + abl.measured_z = NAN; } } #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); @@ -732,27 +865,31 @@ G29_TYPE GcodeSuite::G29() { #endif // Calculate leveling, print reports, correct the position - if (!isnan(measured_z)) { + if (!isnan(abl.measured_z)) { #if ENABLED(AUTO_BED_LEVELING_BILINEAR) - if (!dryrun) extrapolate_unprobed_bed_level(); - print_bilinear_leveling_grid(); + if (abl.dryrun) + bedlevel.print_leveling_grid(&abl.z_values); + else { + bedlevel.set_grid(abl.gridSpacing, abl.probe_position_lf); + COPY(bedlevel.z_values, abl.z_values); + TERN_(IS_KINEMATIC, bedlevel.extrapolate_unprobed_bed_level()); + bedlevel.refresh_bed_level(); - refresh_bed_level(); - - TERN_(ABL_BILINEAR_SUBDIVISION, print_bilinear_leveling_grid_virt()); + bedlevel.print_leveling_grid(); + } #elif ENABLED(AUTO_BED_LEVELING_LINEAR) // 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; @@ -761,62 +898,62 @@ G29_TYPE GcodeSuite::G29() { plane_equation_coefficients.b = -lsf_results.B; // but that is not yet tested. plane_equation_coefficients.d = -lsf_results.D; - mean /= abl_points; + abl.mean /= abl.abl_points; - if (verbose_level) { - SERIAL_ECHOPAIR_F("Eqn coefficients: a: ", plane_equation_coefficients.a, 8); - SERIAL_ECHOPAIR_F(" b: ", plane_equation_coefficients.b, 8); - SERIAL_ECHOPAIR_F(" d: ", plane_equation_coefficients.d, 8); - if (verbose_level > 2) - SERIAL_ECHOPAIR_F("\nMean of sampled points: ", mean, 8); + if (abl.verbose_level) { + SERIAL_ECHOPGM("Eqn coefficients: a: ", p_float_t(plane_equation_coefficients.a, 8), + " b: ", p_float_t(plane_equation_coefficients.b, 8), + " d: ", p_float_t(plane_equation_coefficients.d, 8)); + if (abl.verbose_level > 2) + SERIAL_ECHOPGM("\nMean of sampled points: ", p_float_t(abl.mean, 8)); SERIAL_EOL(); } // Create the matrix but don't correct the position yet - if (!dryrun) + if (!abl.dryrun) planner.bed_level_matrix = matrix_3x3::create_look_at( vector_3(-plane_equation_coefficients.a, -plane_equation_coefficients.b, 1) // We can eliminate the '-' here and up above ); // Show the Topography map if enabled - if (do_topography_map) { + if (abl.topography_map) { float min_diff = 999; - auto print_topo_map = [&](PGM_P const title, const bool get_min) { - serialprintPGM(title); - for (int8_t yy = abl_grid_points.y - 1; yy >= 0; yy--) { - LOOP_L_N(xx, abl_grid_points.x) { - const int ind = indexIntoAB[xx][yy]; - xyz_float_t tmp = { eqnAMatrix[ind + 0 * abl_points], - eqnAMatrix[ind + 1 * abl_points], 0 }; - apply_rotation_xyz(planner.bed_level_matrix, tmp); - if (get_min) NOMORE(min_diff, eqnBVector[ind] - tmp.z); - const float subval = get_min ? mean : tmp.z + min_diff, - diff = eqnBVector[ind] - subval; + auto print_topo_map = [&](FSTR_P const title, const bool get_min) { + SERIAL_ECHO(title); + for (int8_t yy = abl.grid_points.y - 1; yy >= 0; yy--) { + for (uint8_t xx = 0; xx < abl.grid_points.x; ++xx) { + const int ind = abl.indexIntoAB[xx][yy]; + xyz_float_t tmp = { abl.eqnAMatrix[ind + 0 * abl.abl_points], + abl.eqnAMatrix[ind + 1 * abl.abl_points], 0 }; + planner.bed_level_matrix.apply_rotation_xyz(tmp.x, tmp.y, tmp.z); + if (get_min) NOMORE(min_diff, abl.eqnBVector[ind] - tmp.z); + const float subval = get_min ? abl.mean : tmp.z + min_diff, + diff = abl.eqnBVector[ind] - subval; SERIAL_CHAR(' '); if (diff >= 0.0) SERIAL_CHAR('+'); // Include + for column alignment - SERIAL_ECHO_F(diff, 5); + SERIAL_ECHO(p_float_t(diff, 5)); } // xx SERIAL_EOL(); } // yy SERIAL_EOL(); }; - print_topo_map(PSTR("\nBed Height Topography:\n" - " +--- BACK --+\n" - " | |\n" - " L | (+) | R\n" - " E | | I\n" - " F | (-) N (+) | G\n" - " T | | H\n" - " | (-) | T\n" - " | |\n" - " O-- FRONT --+\n" - " (0,0)\n"), true); - if (verbose_level > 3) - print_topo_map(PSTR("\nCorrected Bed Height vs. Bed Topology:\n"), false); + print_topo_map(F("\nBed Height Topography:\n" + " +--- BACK --+\n" + " | |\n" + " L | (+) | R\n" + " E | | I\n" + " F | (-) N (+) | G\n" + " T | | H\n" + " | (-) | T\n" + " | |\n" + " O-- FRONT --+\n" + " (0,0)\n"), true); + if (abl.verbose_level > 3) + print_topo_map(F("\nCorrected Bed Height vs. Bed Topology:\n"), false); - } //do_topography_map + } // abl.topography_map #endif // AUTO_BED_LEVELING_LINEAR @@ -824,10 +961,10 @@ G29_TYPE GcodeSuite::G29() { // For LINEAR and 3POINT leveling correct the current position - if (verbose_level > 0) - planner.bed_level_matrix.debug(PSTR("\n\nBed Level Correction Matrix:")); + if (abl.verbose_level > 0) + planner.bed_level_matrix.debug(F("\n\nBed Level Correction Matrix:")); - if (!dryrun) { + if (!abl.dryrun) { // // Correct the current XYZ position based on the tilted plane. // @@ -838,11 +975,11 @@ G29_TYPE GcodeSuite::G29() { planner.force_unapply_leveling(converted); // use conversion machinery // Use the last measured distance to the bed, if possible - if ( NEAR(current_position.x, probePos.x - probe.offset_xy.x) - && NEAR(current_position.y, probePos.y - probe.offset_xy.y) + if ( NEAR(current_position.x, abl.probePos.x - probe.offset_xy.x) + && NEAR(current_position.y, abl.probePos.y - probe.offset_xy.y) ) { - const float simple_z = current_position.z - measured_z; - if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPAIR("Probed Z", simple_z, " Matrix Z", converted.z, " Discrepancy ", simple_z - converted.z); + const float simple_z = current_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; } @@ -850,50 +987,43 @@ G29_TYPE GcodeSuite::G29() { current_position = converted; if (DEBUGGING(LEVELING)) DEBUG_POS("G29 corrected XYZ", current_position); + + abl.reenable = true; + } + + // Auto Bed Leveling is complete! Enable if possible. + if (abl.reenable) { + planner.leveling_active = true; + sync_plan_position(); } #elif ENABLED(AUTO_BED_LEVELING_BILINEAR) - if (!dryrun) { - if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPAIR("G29 uncorrected Z:", current_position.z); + // Auto Bed Leveling is complete! Enable if possible. + if (!abl.dryrun || abl.reenable) set_bed_leveling_enabled(true); - // Unapply the offset because it is going to be immediately applied - // and cause compensation movement in Z - const float fade_scaling_factor = TERN(ENABLE_LEVELING_FADE_HEIGHT, planner.fade_scaling_factor_for_z(current_position.z), 1); - current_position.z -= fade_scaling_factor * bilinear_z_offset(current_position); + #endif - if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPAIR(" corrected Z:", current_position.z); - } - - #endif // ABL_PLANAR - - // Auto Bed Leveling is complete! Enable if possible. - planner.leveling_active = dryrun ? abl_should_enable : true; - } // !isnan(measured_z) + } // !isnan(abl.measured_z) // Restore state after probing if (!faux) restore_feedrate_and_scaling(); - // Sync the planner from the current_position - if (planner.leveling_active) sync_plan_position(); + TERN_(HAS_BED_PROBE, probe.move_z_after_probing()); - #if HAS_BED_PROBE - probe.move_z_after_probing(); - #endif - - #ifdef Z_PROBE_END_SCRIPT - if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPAIR("Z Probe End Script: ", Z_PROBE_END_SCRIPT); + #ifdef EVENT_GCODE_AFTER_G29 + if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("After G29 G-code: ", EVENT_GCODE_AFTER_G29); planner.synchronize(); - process_subcommands_now_P(PSTR(Z_PROBE_END_SCRIPT)); + process_subcommands_now(F(EVENT_GCODE_AFTER_G29)); #endif - #if ENABLED(DWIN_CREALITY_LCD) - DWIN_CompletedLeveling(); - #endif + TERN_(SOVOL_SV06_RTS, RTS_AutoBedLevelPage()); + + probe.use_probing_tool(false); report_current_position(); - G29_RETURN(isnan(measured_z)); + G29_RETURN(isnan(abl.measured_z), true); } #endif // HAS_ABL_NOT_UBL diff --git a/Marlin/src/gcode/bedlevel/abl/M421.cpp b/Marlin/src/gcode/bedlevel/abl/M421.cpp index 182dc32515..f66d023190 100644 --- a/Marlin/src/gcode/bedlevel/abl/M421.cpp +++ b/Marlin/src/gcode/bedlevel/abl/M421.cpp @@ -56,13 +56,13 @@ void GcodeSuite::M421() { const float zval = parser.value_linear_units(); uint8_t sx = ix >= 0 ? ix : 0, ex = ix >= 0 ? ix : GRID_MAX_POINTS_X - 1, sy = iy >= 0 ? iy : 0, ey = iy >= 0 ? iy : GRID_MAX_POINTS_Y - 1; - LOOP_S_LE_N(x, sx, ex) { - LOOP_S_LE_N(y, sy, ey) { - z_values[x][y] = zval + (hasQ ? z_values[x][y] : 0); - TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(x, y, z_values[x][y])); + for (uint8_t x = sx; x <= ex; ++x) { + for (uint8_t y = sy; y <= ey; ++y) { + bedlevel.z_values[x][y] = zval + (hasQ ? bedlevel.z_values[x][y] : 0); + TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(x, y, bedlevel.z_values[x][y])); } } - TERN_(ABL_BILINEAR_SUBDIVISION, bed_level_virt_interpolate()); + bedlevel.refresh_bed_level(); } else SERIAL_ERROR_MSG(STR_ERR_MESH_XY); diff --git a/Marlin/src/gcode/bedlevel/mbl/G29.cpp b/Marlin/src/gcode/bedlevel/mbl/G29.cpp index d94c16052b..a08ecc327d 100644 --- a/Marlin/src/gcode/bedlevel/mbl/G29.cpp +++ b/Marlin/src/gcode/bedlevel/mbl/G29.cpp @@ -34,14 +34,21 @@ #include "../../queue.h" #include "../../../libs/buzzer.h" -#include "../../../lcd/ultralcd.h" +#include "../../../lcd/marlinui.h" #include "../../../module/motion.h" -#include "../../../module/stepper.h" +#include "../../../module/planner.h" #if ENABLED(EXTENSIBLE_UI) #include "../../../lcd/extui/ui_api.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."); } @@ -60,6 +67,20 @@ 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 + #if ENABLED(DEBUG_LEVELING_FEATURE) + const bool seenQ = parser.seen_test('Q'); + if (seenQ || DEBUGGING(LEVELING)) { + log_machine_info(); + if (seenQ) return; + } + #endif + static int mbl_probe_index = -1; MeshLevelingState state = (MeshLevelingState)parser.byteval('S', (int8_t)MeshReport); @@ -68,24 +89,67 @@ void GcodeSuite::G29() { return; } + TERN_(FULL_REPORT_TO_HOST_FEATURE, set_and_report_grblstate(M_PROBE)); + int8_t ix, iy; + ix = iy = 0; switch (state) { case MeshReport: SERIAL_ECHOPGM("Mesh Bed Leveling "); if (leveling_is_valid()) { - serialprintln_onoff(planner.leveling_active); - mbl.report_mesh(); + SERIAL_ECHOLN(ON_OFF(planner.leveling_active)); + bedlevel.report_mesh(); } else SERIAL_ECHOLNPGM("has no data."); break; case MeshStart: - mbl.reset(); + bedlevel.reset(); mbl_probe_index = 0; if (!ui.wait_for_move) { - queue.inject_P(PSTR("G28\nG29 S2")); + if (parser.seen_test('N')) + queue.inject(F("G28" TERN_(CAN_SET_LEVELING_AFTER_G28, "L0"))); + + // 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 + + do_blocking_move_to(safe_position); + #endif // HAS_SAFE_BED_LEVELING + + queue.inject(F("G29S2")); + + TERN_(EXTENSIBLE_UI, ExtUI::onLevelingStart()); + return; } state = MeshNext; @@ -98,11 +162,18 @@ 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(0); + do_blocking_move_to_z( + #ifdef MANUAL_PROBE_START_Z + MANUAL_PROBE_START_Z + #else + 0.4f + #endif + ); } else { // Save Z for the previous mesh position - mbl.set_zigzag_z(mbl_probe_index - 1, current_position.z); + bedlevel.set_zigzag_z(mbl_probe_index - 1, current_position.z); + TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(ix, iy, current_position.z)); SET_SOFT_ENDSTOP_LOOSE(false); } // If there's another point to sample, move there with optional lift. @@ -110,20 +181,26 @@ void GcodeSuite::G29() { // 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); - mbl.zigzag(mbl_probe_index++, ix, iy); - _manual_goto_xy({ mbl.index_to_xpos[ix], mbl.index_to_ypos[iy] }); + bedlevel.zigzag(mbl_probe_index++, ix, iy); + _manual_goto_xy({ bedlevel.index_to_xpos[ix], bedlevel.index_to_ypos[iy] }); } else { - // One last "return to the bed" (as originally coded) at completion - current_position.z = MANUAL_PROBE_HEIGHT; + // Move to the after probing position + current_position.z = ( + #ifdef Z_AFTER_PROBING + Z_AFTER_PROBING + #else + Z_CLEARANCE_BETWEEN_MANUAL_PROBES + #endif + ); line_to_current_position(); planner.synchronize(); // After recording the last point, activate home and activate mbl_probe_index = -1; SERIAL_ECHOLNPGM("Mesh probing done."); - BUZZ(100, 659); - BUZZ(100, 698); + TERN_(HAS_STATUS_MESSAGE, LCD_MESSAGE(MSG_MESH_DONE)); + OKAY_BUZZ(); home_all_axes(); set_bed_leveling_enabled(true); @@ -135,26 +212,25 @@ void GcodeSuite::G29() { #endif TERN_(LCD_BED_LEVELING, ui.wait_for_move = false); + TERN_(EXTENSIBLE_UI, ExtUI::onLevelingDone()); } break; case MeshSet: if (parser.seenval('I')) { ix = parser.value_int(); - if (!WITHIN(ix, 0, GRID_MAX_POINTS_X - 1)) { - SERIAL_ECHOPAIR("I out of range (0-", int(GRID_MAX_POINTS_X - 1)); - SERIAL_ECHOLNPGM(")"); + if (!WITHIN(ix, 0, (GRID_MAX_POINTS_X) - 1)) { + SERIAL_ECHOLNPGM("I out of range (0-", (GRID_MAX_POINTS_X) - 1, ")"); return; } } else - return echo_not_entered('J'); + return echo_not_entered('I'); if (parser.seenval('J')) { iy = parser.value_int(); - if (!WITHIN(iy, 0, GRID_MAX_POINTS_Y - 1)) { - SERIAL_ECHOPAIR("J out of range (0-", int(GRID_MAX_POINTS_Y - 1)); - SERIAL_ECHOLNPGM(")"); + if (!WITHIN(iy, 0, (GRID_MAX_POINTS_Y) - 1)) { + SERIAL_ECHOLNPGM("J out of range (0-", (GRID_MAX_POINTS_Y) - 1, ")"); return; } } @@ -162,8 +238,8 @@ void GcodeSuite::G29() { return echo_not_entered('J'); if (parser.seenval('Z')) { - mbl.z_values[ix][iy] = parser.value_linear_units(); - TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(ix, iy, mbl.z_values[ix][iy])); + bedlevel.z_values[ix][iy] = parser.value_linear_units(); + TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(ix, iy, bedlevel.z_values[ix][iy])); } else return echo_not_entered('Z'); @@ -171,7 +247,7 @@ void GcodeSuite::G29() { case MeshSetZOffset: if (parser.seenval('Z')) - mbl.z_offset = parser.value_linear_units(); + bedlevel.z_offset = parser.value_linear_units(); else return echo_not_entered('Z'); break; @@ -183,11 +259,13 @@ void GcodeSuite::G29() { } // switch(state) if (state == MeshNext) { - SERIAL_ECHOPAIR("MBL G29 point ", _MIN(mbl_probe_index, GRID_MAX_POINTS)); - SERIAL_ECHOLNPAIR(" of ", int(GRID_MAX_POINTS)); + SERIAL_ECHOLNPGM("MBL G29 point ", _MIN(mbl_probe_index, GRID_MAX_POINTS), " of ", GRID_MAX_POINTS); + 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(); + + TERN_(FULL_REPORT_TO_HOST_FEATURE, 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 1368ab0bef..e23683d55f 100644 --- a/Marlin/src/gcode/bedlevel/mbl/M421.cpp +++ b/Marlin/src/gcode/bedlevel/mbl/M421.cpp @@ -43,9 +43,9 @@ */ void GcodeSuite::M421() { const bool hasX = parser.seen('X'), hasI = parser.seen('I'); - const int8_t ix = hasI ? parser.value_int() : hasX ? mbl.probe_index_x(RAW_X_POSITION(parser.value_linear_units())) : -1; + const int8_t ix = hasI ? parser.value_int() : hasX ? bedlevel.probe_index_x(RAW_X_POSITION(parser.value_linear_units())) : -1; const bool hasY = parser.seen('Y'), hasJ = parser.seen('J'); - const int8_t iy = hasJ ? parser.value_int() : hasY ? mbl.probe_index_y(RAW_Y_POSITION(parser.value_linear_units())) : -1; + const int8_t iy = hasJ ? parser.value_int() : hasY ? bedlevel.probe_index_y(RAW_Y_POSITION(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)) @@ -53,7 +53,7 @@ void GcodeSuite::M421() { else if (ix < 0 || iy < 0) SERIAL_ERROR_MSG(STR_ERR_MESH_XY); else - mbl.set_z(ix, iy, parser.value_linear_units() + (hasQ ? mbl.z_values[ix][iy] : 0)); + bedlevel.set_z(ix, iy, parser.value_linear_units() + (hasQ ? bedlevel.z_values[ix][iy] : 0)); } #endif // MESH_BED_LEVELING diff --git a/Marlin/src/gcode/bedlevel/ubl/G29.cpp b/Marlin/src/gcode/bedlevel/ubl/G29.cpp index 2ef3ab4cec..90deab3d2e 100644 --- a/Marlin/src/gcode/bedlevel/ubl/G29.cpp +++ b/Marlin/src/gcode/bedlevel/ubl/G29.cpp @@ -31,6 +31,17 @@ #include "../../gcode.h" #include "../../../feature/bedlevel/bedlevel.h" -void GcodeSuite::G29() { ubl.G29(); } +#if ENABLED(FULL_REPORT_TO_HOST_FEATURE) + #include "../../../module/motion.h" +#endif + +void GcodeSuite::G29() { + + TERN_(FULL_REPORT_TO_HOST_FEATURE, set_and_report_grblstate(M_PROBE)); + + bedlevel.G29(); + + TERN_(FULL_REPORT_TO_HOST_FEATURE, 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 600c1fc8ba..99ba3ce19b 100644 --- a/Marlin/src/gcode/bedlevel/ubl/M421.cpp +++ b/Marlin/src/gcode/bedlevel/ubl/M421.cpp @@ -21,7 +21,7 @@ */ /** - * unified.cpp - Unified Bed Leveling + * M421.cpp - Unified Bed Leveling */ #include "../../../inc/MarlinConfig.h" @@ -39,31 +39,34 @@ * M421: Set a single Mesh Bed Leveling Z coordinate * * Usage: - * M421 I J Z - * M421 I J Q - * M421 I J N - * M421 C Z - * M421 C Q + * M421 I J Z : Set the Mesh Point IJ to the Z value + * M421 I J Q : Add the Q value to the Mesh Point IJ + * M421 I J N : Set the Mesh Point IJ to NAN (not set) + * M421 C Z : Set the closest Mesh Point to the Z value + * M421 C Q : Add the Q value to the closest Mesh Point */ void GcodeSuite::M421() { xy_int8_t ij = { int8_t(parser.intval('I', -1)), int8_t(parser.intval('J', -1)) }; const bool hasI = ij.x >= 0, hasJ = ij.y >= 0, - hasC = parser.seen('C'), - hasN = parser.seen('N'), + hasC = parser.seen_test('C'), + hasN = parser.seen_test('N'), hasZ = parser.seen('Z'), hasQ = !hasZ && parser.seen('Q'); - if (hasC) ij = ubl.find_closest_mesh_point_of_type(REAL, current_position); + if (hasC) ij = bedlevel.find_closest_mesh_point_of_type(CLOSEST, current_position); + // Test for bad parameter combinations if (int(hasC) + int(hasI && hasJ) != 1 || !(hasZ || hasQ || hasN)) SERIAL_ERROR_MSG(STR_ERR_M421_PARAMETERS); + + // Test for I J out of range else if (!WITHIN(ij.x, 0, GRID_MAX_POINTS_X - 1) || !WITHIN(ij.y, 0, GRID_MAX_POINTS_Y - 1)) SERIAL_ERROR_MSG(STR_ERR_MESH_XY); else { - float &zval = ubl.z_values[ij.x][ij.y]; - zval = hasN ? NAN : parser.value_linear_units() + (hasQ ? zval : 0); - TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(ij.x, ij.y, zval)); + 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 } } diff --git a/Marlin/src/gcode/calibrate/G28.cpp b/Marlin/src/gcode/calibrate/G28.cpp index 3420956803..4a0ecffc4b 100644 --- a/Marlin/src/gcode/calibrate/G28.cpp +++ b/Marlin/src/gcode/calibrate/G28.cpp @@ -24,8 +24,13 @@ #include "../gcode.h" -#include "../../module/stepper.h" #include "../../module/endstops.h" +#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" @@ -39,22 +44,29 @@ #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 -#include "../../lcd/ultralcd.h" -#if ENABLED(DWIN_CREALITY_LCD) - #include "../../lcd/dwin/e3v2/dwin.h" +#if ENABLED(FT_MOTION) + #include "../../module/ft_motion.h" #endif -#if HAS_L64XX // set L6470 absolute position registers to counts - #include "../../libs/L64XX/L64XX_Marlin.h" +#include "../../lcd/marlinui.h" + +#if ENABLED(EXTENSIBLE_UI) + #include "../../lcd/extui/ui_api.h" +#elif ENABLED(DWIN_CREALITY_LCD) + #include "../../lcd/dwin/creality/dwin.h" +#elif ENABLED(SOVOL_SV06_RTS) + #include "../../lcd/sovol_rts/sovol_rts.h" #endif -#if ENABLED(LASER_MOVE_G28_OFF) +#if ENABLED(LASER_FEATURE) #include "../../feature/spindle_laser.h" #endif @@ -69,44 +81,46 @@ current_position.set(0.0, 0.0); sync_plan_position(); - const int x_axis_home_dir = x_home_dir(active_extruder); + const int x_axis_home_dir = TOOL_X_HOME_DIR(active_extruder); - const float mlx = max_length(X_AXIS), - mly = max_length(Y_AXIS), - mlratio = mlx > mly ? mly / mlx : mlx / mly, - fr_mm_s = _MIN(homing_feedrate(X_AXIS), homing_feedrate(Y_AXIS)) * SQRT(sq(mlratio) + 1.0); + // Use a higher diagonal feedrate so axes move at homing speed + const float minfr = _MIN(homing_feedrate(X_AXIS), 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, set_homing_current(X_AXIS)); + #if Y_HAS_HOME_CURRENT && NONE(CORE_IS_XY, MARKFORGED_XY, MARKFORGED_YX) + set_homing_current(Y_AXIS); + #endif #if ENABLED(SENSORLESS_HOMING) sensorless_t stealth_states { - tmc_enable_stallguard(stepperX) - , tmc_enable_stallguard(stepperY) - , false - , false - #if AXIS_HAS_STALLGUARD(X2) - || tmc_enable_stallguard(stepperX2) - #endif - , false - #if AXIS_HAS_STALLGUARD(Y2) - || tmc_enable_stallguard(stepperY2) - #endif + NUM_AXIS_LIST( + TERN0(X_SENSORLESS, tmc_enable_stallguard(stepperX)), + TERN0(Y_SENSORLESS, tmc_enable_stallguard(stepperY)), + false, false, false, false, false, false, false + ) + , TERN0(X2_SENSORLESS, tmc_enable_stallguard(stepperX2)) + , TERN0(Y2_SENSORLESS, tmc_enable_stallguard(stepperY2)) }; #endif - do_blocking_move_to_xy(1.5 * mlx * x_axis_home_dir, 1.5 * mly * home_dir(Y_AXIS), fr_mm_s); + 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); endstops.validate_homing_move(); current_position.set(0.0, 0.0); - #if ENABLED(SENSORLESS_HOMING) - tmc_disable_stallguard(stepperX, stealth_states.x); - tmc_disable_stallguard(stepperY, stealth_states.y); - #if AXIS_HAS_STALLGUARD(X2) - tmc_disable_stallguard(stepperX2, stealth_states.x2); - #endif - #if AXIS_HAS_STALLGUARD(Y2) - tmc_disable_stallguard(stepperY2, stealth_states.y2); - #endif + TERN_(X_HAS_HOME_CURRENT, restore_homing_current(X_AXIS)); + #if Y_HAS_HOME_CURRENT && NONE(CORE_IS_XY, MARKFORGED_XY, MARKFORGED_YX) + 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)); + TERN_(X2_SENSORLESS, tmc_disable_stallguard(stepperX2, stealth_states.x2)); + TERN_(Y_SENSORLESS, tmc_disable_stallguard(stepperY, stealth_states.y)); + TERN_(Y2_SENSORLESS, tmc_disable_stallguard(stepperY2, stealth_states.y2)); #endif } @@ -120,12 +134,16 @@ // Disallow Z homing if X or Y homing is needed if (homing_needed_error(_BV(X_AXIS) | _BV(Y_AXIS))) return; + // Potentially disable Fixed-Time Motion for homing + TERN_(FT_MOTION, FTM_DISABLE_IN_SCOPE()); + 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); TERN_(HOMING_Z_WITH_PROBE, destination -= probe.offset_xy); @@ -134,8 +152,8 @@ if (DEBUGGING(LEVELING)) DEBUG_POS("home_z_safely", destination); - // This causes the carriage on Dual X to unpark - TERN_(DUAL_X_CARRIAGE, active_extruder_parked = false); + // Free the active extruder for movement + TERN_(DUAL_X_CARRIAGE, idex_set_parked(false)); TERN_(SENSORLESS_HOMING, safe_delay(500)); // Short delay needed to settle @@ -143,7 +161,7 @@ homeaxis(Z_AXIS); } else { - LCD_MESSAGEPGM(MSG_ZPROBE_OUT); + LCD_MESSAGE(MSG_ZPROBE_OUT); SERIAL_ECHO_MSG(STR_ZPROBE_OUT_SER); } } @@ -152,42 +170,52 @@ #if ENABLED(IMPROVE_HOMING_RELIABILITY) - slow_homing_t begin_slow_homing() { - slow_homing_t slow_homing{0}; - slow_homing.acceleration.set(planner.settings.max_acceleration_mm_per_s2[X_AXIS], - planner.settings.max_acceleration_mm_per_s2[Y_AXIS]); + motion_state_t begin_slow_homing() { + motion_state_t motion_state{0}; + motion_state.acceleration.set(planner.settings.max_acceleration_mm_per_s2[X_AXIS], + planner.settings.max_acceleration_mm_per_s2[Y_AXIS] + OPTARG(DELTA, planner.settings.max_acceleration_mm_per_s2[Z_AXIS]) + ); planner.settings.max_acceleration_mm_per_s2[X_AXIS] = 100; planner.settings.max_acceleration_mm_per_s2[Y_AXIS] = 100; - #if HAS_CLASSIC_JERK - slow_homing.jerk_xy = planner.max_jerk; - planner.max_jerk.set(0, 0); + TERN_(DELTA, planner.settings.max_acceleration_mm_per_s2[Z_AXIS] = 100); + #if ENABLED(CLASSIC_JERK) + motion_state.jerk_state = planner.max_jerk; + planner.max_jerk.set(0, 0 OPTARG(DELTA, 0)); #endif - planner.reset_acceleration_rates(); - return slow_homing; + planner.refresh_acceleration_rates(); + return motion_state; } - void end_slow_homing(const slow_homing_t &slow_homing) { - planner.settings.max_acceleration_mm_per_s2[X_AXIS] = slow_homing.acceleration.x; - planner.settings.max_acceleration_mm_per_s2[Y_AXIS] = slow_homing.acceleration.y; - TERN_(HAS_CLASSIC_JERK, planner.max_jerk = slow_homing.jerk_xy); - planner.reset_acceleration_rates(); + void end_slow_homing(const motion_state_t &motion_state) { + planner.settings.max_acceleration_mm_per_s2[X_AXIS] = motion_state.acceleration.x; + planner.settings.max_acceleration_mm_per_s2[Y_AXIS] = motion_state.acceleration.y; + TERN_(DELTA, planner.settings.max_acceleration_mm_per_s2[Z_AXIS] = motion_state.acceleration.z); + TERN_(CLASSIC_JERK, planner.max_jerk = motion_state.jerk_state); + planner.refresh_acceleration_rates(); } #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. * - * O Home only if position is unknown + * 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. * - * Rn Raise by n mm/inches before homing - * - * Cartesian/SCARA parameters + * Cartesian/SCARA parameters: * * X Home to the X endstop * Y Home to the Y endstop @@ -197,18 +225,9 @@ void GcodeSuite::G28() { DEBUG_SECTION(log_G28, "G28", DEBUGGING(LEVELING)); if (DEBUGGING(LEVELING)) log_machine_info(); - TERN_(LASER_MOVE_G28_OFF, cutter.set_inline_enabled(false)); // turn off laser - - TERN_(DWIN_CREALITY_LCD, HMI_flag.home_flag = true); - - #if ENABLED(DUAL_X_CARRIAGE) - bool IDEX_saved_duplication_state = extruder_duplication_enabled; - DualXMode IDEX_saved_mode = dual_x_carriage_mode; - #endif - #if ENABLED(MARLIN_DEV_MODE) - if (parser.seen('S')) { - LOOP_XYZ(a) set_axis_is_at_home((AxisEnum)a); + if (parser.seen_test('S')) { + LOOP_NUM_AXES(a) set_axis_is_at_home((AxisEnum)a); sync_plan_position(); SERIAL_ECHOLNPGM("Simulated Homing"); report_current_position(); @@ -216,266 +235,355 @@ void GcodeSuite::G28() { } #endif + /** + * Set the laser power to false to stop the planner from processing the current power setting. + */ + #if ENABLED(LASER_FEATURE) + planner.laser_inline.status.isPowered = false; + #endif + // Home (O)nly if position is unknown - if (!homing_needed() && parser.boolval('O')) { + if (!axes_should_home() && parser.seen_test('O')) { if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("> homing not needed, skip"); return; } - planner.synchronize(); // Wait for planner moves to finish! - - SET_SOFT_ENDSTOP_LOOSE(false); // Reset a leftover 'loose' motion state - - // Disable the leveling matrix before homing - #if HAS_LEVELING - - // Cancel the active G29 session - TERN_(PROBE_MANUALLY, g29_in_progress = false); - - TERN_(RESTORE_LEVELING_AFTER_G28, const bool leveling_was_active = planner.leveling_active); - set_bed_leveling_enabled(false); + #if ENABLED(FULL_REPORT_TO_HOST_FEATURE) + const M_StateEnum old_grblstate = M_State_grbl; + set_and_report_grblstate(M_HOMING); #endif - TERN_(CNC_WORKSPACE_PLANES, workspace_plane = PLANE_XY); + TERN_(DWIN_CREALITY_LCD, dwinHomingStart()); + TERN_(EXTENSIBLE_UI, ExtUI::onHomingStart()); + + planner.synchronize(); // Wait for planner moves to finish! // Count this command as movement / activity reset_stepper_timeout(); - #define HAS_CURRENT_HOME(N) (defined(N##_CURRENT_HOME) && N##_CURRENT_HOME != N##_CURRENT) - #if HAS_CURRENT_HOME(X) || HAS_CURRENT_HOME(X2) || HAS_CURRENT_HOME(Y) || HAS_CURRENT_HOME(Y2) - #define HAS_HOMING_CURRENT 1 - #endif + #if NUM_AXES - #if HAS_HOMING_CURRENT - auto debug_current = [](PGM_P const s, const int16_t a, const int16_t b){ - serialprintPGM(s); DEBUG_ECHOLNPAIR(" current: ", a, " -> ", b); - }; - #if HAS_CURRENT_HOME(X) - const int16_t tmc_save_current_X = stepperX.getMilliamps(); - stepperX.rms_current(X_CURRENT_HOME); - if (DEBUGGING(LEVELING)) debug_current(PSTR("X"), tmc_save_current_X, X_CURRENT_HOME); + #if ENABLED(DUAL_X_CARRIAGE) + bool IDEX_saved_duplication_state = extruder_duplication_enabled; + DualXMode IDEX_saved_mode = dual_x_carriage_mode; #endif - #if HAS_CURRENT_HOME(X2) - const int16_t tmc_save_current_X2 = stepperX2.getMilliamps(); - stepperX2.rms_current(X2_CURRENT_HOME); - if (DEBUGGING(LEVELING)) debug_current(PSTR("X2"), tmc_save_current_X2, X2_CURRENT_HOME); + + SET_SOFT_ENDSTOP_LOOSE(false); // Reset a leftover 'loose' motion state + + // Disable the leveling matrix before homing + #if CAN_SET_LEVELING_AFTER_G28 + const bool leveling_restore_state = parser.boolval('L', TERN1(RESTORE_LEVELING_AFTER_G28, planner.leveling_active)); #endif - #if HAS_CURRENT_HOME(Y) - const int16_t tmc_save_current_Y = stepperY.getMilliamps(); - stepperY.rms_current(Y_CURRENT_HOME); - if (DEBUGGING(LEVELING)) debug_current(PSTR("Y"), tmc_save_current_Y, Y_CURRENT_HOME); + + // Cancel any prior G29 session + TERN_(PROBE_MANUALLY, g29_in_progress = false); + + // Disable leveling before homing + TERN_(HAS_LEVELING, set_bed_leveling_enabled(false)); + + // Reset to the XY plane + TERN_(CNC_WORKSPACE_PLANES, workspace_plane = PLANE_XY); + + #if ENABLED(IMPROVE_HOMING_RELIABILITY) + motion_state_t saved_motion_state = begin_slow_homing(); #endif - #if HAS_CURRENT_HOME(Y2) - const int16_t tmc_save_current_Y2 = stepperY2.getMilliamps(); - stepperY2.rms_current(Y2_CURRENT_HOME); - if (DEBUGGING(LEVELING)) debug_current(PSTR("Y2"), tmc_save_current_Y2, Y2_CURRENT_HOME); + + // Potentially disable Fixed-Time Motion for homing + TERN_(FT_MOTION, FTM_DISABLE_IN_SCOPE()); + + // 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; + #endif + // PARKING_EXTRUDER homing requires different handling of movement / solenoid activation, depending on the side of homing + #if ENABLED(PARKING_EXTRUDER) + 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 - #endif - TERN_(IMPROVE_HOMING_RELIABILITY, slow_homing_t slow_homing = begin_slow_homing()); + TERN_(HAS_DUPLICATION_MODE, set_duplication_enabled(false)); - // 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; + remember_feedrate_scaling_off(); + + endstops.enable(true); // Enable endstops for next homing move + + #if HAS_Z_AXIS + bool finalRaiseZ = false; #endif - tool_change(0, true); - #endif - TERN_(HAS_DUPLICATION_MODE, extruder_duplication_enabled = false); + #if ENABLED(DELTA) - remember_feedrate_scaling_off(); + constexpr bool doZ = true; // for NANODLP_Z_SYNC if your DLP is on a DELTA - endstops.enable(true); // Enable endstops for next homing move + home_delta(); - #if ENABLED(DELTA) + TERN_(IMPROVE_HOMING_RELIABILITY, end_slow_homing(saved_motion_state)); - constexpr bool doZ = true; // for NANODLP_Z_SYNC if your DLP is on a DELTA + #elif ENABLED(AXEL_TPARA) - home_delta(); + constexpr bool doZ = true; // for NANODLP_Z_SYNC if your DLP is on a TPARA - TERN_(IMPROVE_HOMING_RELIABILITY, end_slow_homing(slow_homing)); + home_TPARA(); - #else // NOT DELTA + #else // !DELTA && !AXEL_TPARA - const bool homeZ = parser.seen('Z'), - needX = homeZ && TERN0(Z_SAFE_HOMING, axes_should_home(_BV(X_AXIS))), - needY = homeZ && TERN0(Z_SAFE_HOMING, axes_should_home(_BV(Y_AXIS))), - homeX = needX || parser.seen('X'), homeY = needY || parser.seen('Y'), - home_all = homeX == homeY && homeX == homeZ, // All or None - doX = home_all || homeX, doY = home_all || homeY, doZ = home_all || homeZ; + #define _UNSAFE(A) TERN0(Z_SAFE_HOMING, homeZZ && axis_should_home(_AXIS(A))) - #if Z_HOME_DIR > 0 // If homing away from BED do Z first + 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), + needU = _UNSAFE(U), needV = _UNSAFE(V), needW = _UNSAFE(W) + ) + NUM_AXIS_LIST_( // Home each axis if needed or flagged + homeX = needX || parser.seen_test('X'), + homeY = needY || parser.seen_test('Y'), + 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) + ) + home_all = NUM_AXIS_GANG_( // Home-all if all or none are flagged + homeX == homeX, && homeY == homeX, && homeZ == homeX, + && homeI == homeX, && homeJ == homeX, && homeK == homeX, + && homeU == homeX, && homeV == homeX, && homeW == homeX + ) + NUM_AXIS_LIST( + doX = home_all || homeX, doY = home_all || homeY, doZ = home_all || homeZ, + doI = home_all || homeI, doJ = home_all || homeJ, doK = home_all || homeK, + doU = home_all || homeU, doV = home_all || homeV, doW = home_all || homeW + ); - if (doZ) homeaxis(Z_AXIS); + #if !HAS_Y_AXIS + constexpr bool doY = false; + #endif + + #if HAS_Z_AXIS + + 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); + + // '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. + const bool seenR = parser.seenval('R'); + + // Use raise given by 'R' or Z_CLEARANCE_FOR_HOMING (above the probe trigger point) + 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); + + if (seenR && z_homing_height == 0) { + if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("R0 = No Z raise"); + } + else { + 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 || !(z_min_trusted || axis_should_home(Z_AXIS))) { + z_homing_height += current_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); + } + } + + // Init BLTouch ahead of any lateral motion, even if not homing with the probe + TERN_(BLTOUCH, if (may_skate) bltouch.init()); + + #endif // HAS_Z_AXIS + + // Diagonal move first if both are homing + TERN_(QUICK_HOME, if (doX && doY) quick_home_xy()); + + #if HAS_Y_AXIS + // Home Y (before X) + if (ENABLED(HOME_Y_BEFORE_X) && (doY || TERN0(CODEPENDENT_XY_HOMING, doX))) + 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(); + + #else + + homeaxis(X_AXIS); + + #endif + } + #endif // HAS_X_AXIS + + #if ALL(FOAMCUTTER_XYUV, HAS_I_AXIS) + // Home I (after X) + if (doI) homeaxis(I_AXIS); + #endif + + #if HAS_Y_AXIS + // Home Y (after X) + if (DISABLED(HOME_Y_BEFORE_X) && doY) homeaxis(Y_AXIS); + #endif + + #if ALL(FOAMCUTTER_XYUV, HAS_J_AXIS) + // Home J (after Y) + if (doJ) homeaxis(J_AXIS); + #endif + + TERN_(IMPROVE_HOMING_RELIABILITY, end_slow_homing(saved_motion_state)); + + #if ENABLED(FOAMCUTTER_XYUV) + + // Skip homing of unused Z axis for foamcutters + if (doZ) set_axis_is_at_home(Z_AXIS); + + #elif HAS_Z_AXIS + + // Home Z last if homing towards the bed + #if DISABLED(HOME_Z_FIRST) + if (doZ) { + #if ANY(Z_MULTI_ENDSTOPS, Z_STEPPER_AUTO_ALIGN) + stepper.set_all_z_lock(false); + stepper.set_separate_multi_axis(false); + #endif + + #if ENABLED(Z_SAFE_HOMING) + // 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 + homeaxis(Z_AXIS); + #else + homeaxis(Z_AXIS); + #endif + + #if ANY(Z_HOME_TO_MIN, ALLOW_Z_AFTER_HOMING) + finalRaiseZ = true; + #endif + } + #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) + ); + + #endif // HAS_Z_AXIS + + sync_plan_position(); #endif - const float z_homing_height = - ENABLED(UNKNOWN_Z_NO_RAISE) && !TEST(axis_known_position, Z_AXIS) - ? 0 - : (parser.seenval('R') ? parser.value_linear_units() : Z_HOMING_HEIGHT); + /** + * Preserve DXC mode across a G28 for IDEX printers in DXC_DUPLICATION_MODE. + * This is important because it lets a user use the LCD Panel to set an IDEX Duplication mode, and + * then print a standard G-Code file that contains a single print that does a G28 and has no other + * IDEX specific commands in it. + */ + #if ENABLED(DUAL_X_CARRIAGE) - if (z_homing_height && (doX || doY || (ENABLED(Z_SAFE_HOMING) && doZ))) { - // Raise Z before homing any other axes and z is not already high enough (never lower z) - if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPAIR("Raise Z (before homing) by ", z_homing_height); - do_z_clearance(z_homing_height, true, DISABLED(UNKNOWN_Z_NO_RAISE)); - } + if (idex_is_duplicating()) { - #if ENABLED(QUICK_HOME) - - if (doX && doY) quick_home_xy(); - - #endif - - // Home Y (before X) - if (ENABLED(HOME_Y_BEFORE_X) && (doY || (ENABLED(CODEPENDENT_XY_HOMING) && doX))) - homeaxis(Y_AXIS); - - // Home X - if (doX || (doY && ENABLED(CODEPENDENT_XY_HOMING) && DISABLED(HOME_Y_BEFORE_X))) { - - #if ENABLED(DUAL_X_CARRIAGE) + 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_pos = current_position.x; + inactive_extruder_x = current_position.x; // Home the 1st (left) extruder active_extruder = 0; homeaxis(X_AXIS); // Consider the active extruder to be parked - raised_parked_position = current_position; - delayed_move_time = 0; - active_extruder_parked = true; + idex_set_parked(); - #else + dual_x_carriage_mode = IDEX_saved_mode; + set_duplication_enabled(IDEX_saved_duplication_state); - homeaxis(X_AXIS); + TERN_(IMPROVE_HOMING_RELIABILITY, end_slow_homing(saved_motion_state)); + } - #endif - } + #endif // DUAL_X_CARRIAGE - // Home Y (after X) - if (DISABLED(HOME_Y_BEFORE_X) && doY) - homeaxis(Y_AXIS); + endstops.not_homing(); - TERN_(IMPROVE_HOMING_RELIABILITY, end_slow_homing(slow_homing)); + // Clear endstop state for polled stallGuard endstops + TERN_(SPI_ENDSTOPS, endstops.clear_endstop_state()); - // Home Z last if homing towards the bed - #if Z_HOME_DIR < 0 + // 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)); - if (doZ) { - TERN_(BLTOUCH, bltouch.init()); - - TERN(Z_SAFE_HOMING, home_z_safely(), homeaxis(Z_AXIS)); - - probe.move_z_after_homing(); - - } // doZ - - #endif // Z_HOME_DIR < 0 - - sync_plan_position(); - - #endif // !DELTA (G28) - - /** - * Preserve DXC mode across a G28 for IDEX printers in DXC_DUPLICATION_MODE. - * This is important because it lets a user use the LCD Panel to set an IDEX Duplication mode, and - * then print a standard GCode file that contains a single print that does a G28 and has no other - * IDEX specific commands in it. - */ - #if ENABLED(DUAL_X_CARRIAGE) - - if (dxc_is_duplicating()) { - - TERN_(IMPROVE_HOMING_RELIABILITY, slow_homing = 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_pos = current_position.x; - - // Home the 1st (left) extruder - active_extruder = 0; - homeaxis(X_AXIS); - - // Consider the active extruder to be parked - raised_parked_position = current_position; - delayed_move_time = 0; - active_extruder_parked = true; - extruder_duplication_enabled = IDEX_saved_duplication_state; - dual_x_carriage_mode = IDEX_saved_mode; - stepper.set_directions(); - - TERN_(IMPROVE_HOMING_RELIABILITY, end_slow_homing(slow_homing)); - } - - #endif // DUAL_X_CARRIAGE - - endstops.not_homing(); - - // Clear endstop state for polled stallGuard endstops - TERN_(SPI_ENDSTOPS, endstops.clear_endstop_state()); - - #if BOTH(DELTA, DELTA_HOME_TO_SAFE_ZONE) - // move to a height where we can use the full xy-area - do_blocking_move_to_z(delta_clip_start_height); - #endif - - TERN_(RESTORE_LEVELING_AFTER_G28, set_bed_leveling_enabled(leveling_was_active)); - - restore_feedrate_and_scaling(); - - // Restore the active tool after homing - #if HAS_MULTI_HOTEND && (DISABLED(DELTA) || ENABLED(DELTA_HOME_TO_SAFE_ZONE)) - tool_change(old_tool_index, NONE(PARKING_EXTRUDER, DUAL_X_CARRIAGE)); // Do move if one of these - #endif - - #if HAS_HOMING_CURRENT - if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("Restore driver current..."); - #if HAS_CURRENT_HOME(X) - stepperX.rms_current(tmc_save_current_X); + #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(); #endif - #if HAS_CURRENT_HOME(X2) - stepperX2.rms_current(tmc_save_current_X2); + + TERN_(CAN_SET_LEVELING_AFTER_G28, if (leveling_restore_state) set_bed_leveling_enabled()); + + // Restore the active tool after homing + #if HAS_MULTI_HOTEND && (DISABLED(DELTA) || ENABLED(DELTA_HOME_TO_SAFE_ZONE)) + tool_change(old_tool_index, TERN(PARKING_EXTRUDER, !pe_final_change_must_unpark, DISABLED(DUAL_X_CARRIAGE))); // Do move if one of these #endif - #if HAS_CURRENT_HOME(Y) - stepperY.rms_current(tmc_save_current_Y); + + #ifdef XY_AFTER_HOMING + if (!axes_should_home(_BV(X_AXIS) | _BV(Y_AXIS))) + do_blocking_move_to(xy_pos_t(XY_AFTER_HOMING)); #endif - #if HAS_CURRENT_HOME(Y2) - stepperY2.rms_current(tmc_save_current_Y2); - #endif - #endif + + restore_feedrate_and_scaling(); + + if (ENABLED(NANODLP_Z_SYNC) && (ENABLED(NANODLP_ALL_AXIS) || TERN0(HAS_Z_AXIS, doZ))) + SERIAL_ECHOLNPGM(STR_Z_MOVE_COMP); + + #endif // NUM_AXES ui.refresh(); - TERN_(DWIN_CREALITY_LCD, DWIN_CompletedHoming()); + TERN_(SOVOL_SV06_RTS, RTS_MoveAxisHoming()); + TERN_(DWIN_CREALITY_LCD, dwinHomingDone()); + TERN_(EXTENSIBLE_UI, ExtUI::onHomingDone()); report_current_position(); - if (ENABLED(NANODLP_Z_SYNC) && (doZ || ENABLED(NANODLP_ALL_AXIS))) - SERIAL_ECHOLNPGM(STR_Z_MOVE_COMP); + TERN_(FULL_REPORT_TO_HOST_FEATURE, set_and_report_grblstate(old_grblstate)); - #if HAS_L64XX - // Set L6470 absolute position registers to counts - // constexpr *might* move this to PROGMEM. - // If not, this will need a PROGMEM directive and an accessor. - static constexpr AxisEnum L64XX_axis_xref[MAX_L64XX] = { - X_AXIS, Y_AXIS, Z_AXIS, - X_AXIS, Y_AXIS, Z_AXIS, Z_AXIS, - E_AXIS, E_AXIS, E_AXIS, E_AXIS, E_AXIS, E_AXIS - }; - for (uint8_t j = 1; j <= L64XX::chain[0]; j++) { - const uint8_t cv = L64XX::chain[j]; - L64xxManager.set_param((L64XX_axis_t)cv, L6470_ABS_POS, stepper.position(L64XX_axis_xref[cv])); - } + #ifdef EVENT_GCODE_AFTER_HOMING + gcode.process_subcommands_now(F(EVENT_GCODE_AFTER_HOMING)); #endif + } diff --git a/Marlin/src/gcode/calibrate/G33.cpp b/Marlin/src/gcode/calibrate/G33.cpp index 53af04d528..0a8dd459ca 100644 --- a/Marlin/src/gcode/calibrate/G33.cpp +++ b/Marlin/src/gcode/calibrate/G33.cpp @@ -27,26 +27,22 @@ #include "../gcode.h" #include "../../module/delta.h" #include "../../module/motion.h" -#include "../../module/stepper.h" +#include "../../module/planner.h" #include "../../module/endstops.h" -#include "../../lcd/ultralcd.h" +#include "../../lcd/marlinui.h" #if HAS_BED_PROBE #include "../../module/probe.h" #endif -#if HAS_MULTI_HOTEND - #include "../../module/tool_change.h" -#endif - #if HAS_LEVELING #include "../../feature/bedlevel/bedlevel.h" #endif 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, @@ -63,18 +59,18 @@ enum CalEnum : char { // the 7 main calibration points - #define LOOP_CAL_RAD(VAR) LOOP_CAL_PT(VAR, __A, _7P_STEP) #define LOOP_CAL_ACT(VAR, _4P, _OP) LOOP_CAL_PT(VAR, _OP ? _AB : __A, _4P ? _4P_STEP : _7P_STEP) -TERN_(HAS_MULTI_HOTEND, const uint8_t old_tool_index = active_extruder); - float lcd_probe_pt(const xy_pos_t &xy); void ac_home() { endstops.enable(true); + TERN_(IMPROVE_HOMING_RELIABILITY, planner.enable_stall_prevention(true)); home_delta(); + TERN_(IMPROVE_HOMING_RELIABILITY, planner.enable_stall_prevention(false)); endstops.not_homing(); } void ac_setup(const bool reset_bed) { - TERN_(HAS_MULTI_HOTEND, tool_change(0, true)); + TERN_(HAS_BED_PROBE, probe.use_probing_tool()); planner.synchronize(); remember_feedrate_scaling_off(); @@ -84,58 +80,54 @@ void ac_setup(const bool reset_bed) { #endif } -void ac_cleanup(TERN_(HAS_MULTI_HOTEND, const uint8_t old_tool_index)) { +void ac_cleanup() { TERN_(DELTA_HOME_TO_SAFE_ZONE, do_blocking_move_to_z(delta_clip_start_height)); TERN_(HAS_BED_PROBE, probe.stow()); restore_feedrate_and_scaling(); - TERN_(HAS_MULTI_HOTEND, tool_change(old_tool_index, true)); + TERN_(HAS_BED_PROBE, probe.use_probing_tool(false)); } -void print_signed_float(PGM_P const prefix, const float &f) { - SERIAL_ECHOPGM(" "); - serialprintPGM(prefix); - SERIAL_CHAR(':'); - if (f >= 0) SERIAL_CHAR('+'); - SERIAL_ECHO_F(f, 2); +void print_signed_float(FSTR_P const prefix, const float f) { + SERIAL_ECHO(F(" "), prefix, C(':')); + serial_offset(f); } /** - * - Print the delta settings + * - Print the delta settings */ static void print_calibration_settings(const bool end_stops, const bool tower_angles) { - SERIAL_ECHOPAIR(".Height:", delta_height); + SERIAL_ECHOPGM(".Height:", delta_height); if (end_stops) { - print_signed_float(PSTR("Ex"), delta_endstop_adj.a); - print_signed_float(PSTR("Ey"), delta_endstop_adj.b); - print_signed_float(PSTR("Ez"), delta_endstop_adj.c); + print_signed_float(F("Ex"), delta_endstop_adj.a); + print_signed_float(F("Ey"), delta_endstop_adj.b); + print_signed_float(F("Ez"), delta_endstop_adj.c); } if (end_stops && tower_angles) { - SERIAL_ECHOPAIR(" Radius:", delta_radius); - SERIAL_EOL(); + SERIAL_ECHOLNPGM(" Radius:", delta_radius); SERIAL_CHAR('.'); SERIAL_ECHO_SP(13); } if (tower_angles) { - print_signed_float(PSTR("Tx"), delta_tower_angle_trim.a); - print_signed_float(PSTR("Ty"), delta_tower_angle_trim.b); - print_signed_float(PSTR("Tz"), delta_tower_angle_trim.c); - } - if ((!end_stops && tower_angles) || (end_stops && !tower_angles)) { // XOR - SERIAL_ECHOPAIR(" Radius:", delta_radius); + print_signed_float(F("Tx"), delta_tower_angle_trim.a); + print_signed_float(F("Ty"), delta_tower_angle_trim.b); + print_signed_float(F("Tz"), delta_tower_angle_trim.c); } + if (end_stops != tower_angles) + SERIAL_ECHOPGM(" Radius:", delta_radius); + SERIAL_EOL(); } /** - * - Print the probe results + * - Print the probe results */ static void print_calibration_results(const float z_pt[NPP + 1], const bool tower_points, const bool opposite_points) { SERIAL_ECHOPGM(". "); - print_signed_float(PSTR("c"), z_pt[CEN]); + print_signed_float(F("c"), z_pt[CEN]); if (tower_points) { - print_signed_float(PSTR(" x"), z_pt[__A]); - print_signed_float(PSTR(" y"), z_pt[__B]); - print_signed_float(PSTR(" z"), z_pt[__C]); + print_signed_float(F(" x"), z_pt[__A]); + print_signed_float(F(" y"), z_pt[__B]); + print_signed_float(F(" z"), z_pt[__C]); } if (tower_points && opposite_points) { SERIAL_EOL(); @@ -143,15 +135,15 @@ static void print_calibration_results(const float z_pt[NPP + 1], const bool towe SERIAL_ECHO_SP(13); } if (opposite_points) { - print_signed_float(PSTR("yz"), z_pt[_BC]); - print_signed_float(PSTR("zx"), z_pt[_CA]); - print_signed_float(PSTR("xy"), z_pt[_AB]); + print_signed_float(F("yz"), z_pt[_BC]); + print_signed_float(F("zx"), z_pt[_CA]); + print_signed_float(F("xy"), z_pt[_AB]); } SERIAL_EOL(); } /** - * - Calculate the standard deviation from the zero plane + * - Calculate the standard deviation from the zero plane */ static float std_dev_points(float z_pt[NPP + 1], const bool _0p_cal, const bool _1p_cal, const bool _4p_cal, const bool _4p_opp) { if (!_0p_cal) { @@ -162,18 +154,18 @@ 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; } /** - * - Probe a point + * - Probe a point */ -static float calibration_probe(const xy_pos_t &xy, const bool stow) { +static float calibration_probe(const xy_pos_t &xy, const bool stow, const bool probe_at_offset) { #if HAS_BED_PROBE - return probe.probe_at_point(xy, stow ? PROBE_PT_STOW : PROBE_PT_RAISE, 0, true, false); + return probe.probe_at_point(xy, stow ? PROBE_PT_STOW : PROBE_PT_RAISE, 0, probe_at_offset, false, Z_PROBE_LOW_POINT, Z_TWEEN_SAFE_CLEARANCE, true); #else UNUSED(stow); return lcd_probe_pt(xy); @@ -181,9 +173,9 @@ static float calibration_probe(const xy_pos_t &xy, const bool stow) { } /** - * - Probe a grid + * - Probe a grid */ -static bool probe_calibration_points(float z_pt[NPP + 1], const int8_t probe_points, const bool towers_set, const bool stow_after_each) { +static bool probe_calibration_points(float z_pt[NPP + 1], const int8_t probe_points, const float dcr, const bool towers_set, const bool stow_after_each, const bool probe_at_offset) { const bool _0p_calibration = probe_points == 0, _1p_calibration = probe_points == 1 || probe_points == -1, _4p_calibration = probe_points == 2, @@ -205,28 +197,26 @@ static bool probe_calibration_points(float z_pt[NPP + 1], const int8_t probe_poi if (!_0p_calibration) { - const float dcr = delta_calibration_radius(); - - 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); + 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) { const float a = RADIANS(210 + (360 / NPP) * (rad - 1)), r = dcr * 0.1; const xy_pos_t vec = { cos(a), sin(a) }; - z_pt[CEN] += calibration_probe(vec * r, stow_after_each); + z_pt[CEN] += calibration_probe(vec * r, stow_after_each, probe_at_offset); if (isnan(z_pt[CEN])) return false; } 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 @@ -245,13 +235,13 @@ static bool probe_calibration_points(float z_pt[NPP + 1], const int8_t probe_poi r = dcr * (1 - 0.1 * (zig_zag ? offset - circle : circle)), interpol = FMOD(rad, 1); const xy_pos_t vec = { cos(a), sin(a) }; - const float z_temp = calibration_probe(vec * r, stow_after_each); + const float z_temp = calibration_probe(vec * r, stow_after_each, probe_at_offset); if (isnan(z_temp)) return false; // split probe point to neighbouring calibration points 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) @@ -264,15 +254,15 @@ static bool probe_calibration_points(float z_pt[NPP + 1], const int8_t probe_poi } /** - * 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]) { +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}; - const float dcr = delta_calibration_radius(); LOOP_CAL_ALL(rad) { const float a = RADIANS(210 + (360 / NPP) * (rad - 1)), r = (rad == CEN ? 0.0f : dcr); @@ -282,8 +272,8 @@ static void reverse_kinematics_probe_points(float z_pt[NPP + 1], abc_float_t mm_ } } -static void forward_kinematics_probe_points(abc_float_t mm_at_pt_axis[NPP + 1], float z_pt[NPP + 1]) { - const float r_quot = delta_calibration_radius() / delta_radius; +static void forward_kinematics_probe_points(abc_float_t mm_at_pt_axis[NPP + 1], float z_pt[NPP + 1], const float dcr) { + const float r_quot = dcr / delta_radius; #define ZPP(N,I,A) (((1.0f + r_quot * (N)) / 3.0f) * mm_at_pt_axis[I].A) #define Z00(I, A) ZPP( 0, I, A) @@ -301,19 +291,19 @@ static void forward_kinematics_probe_points(abc_float_t mm_at_pt_axis[NPP + 1], z_pt[_AB] = Zp1(_AB, a) + Zp1(_AB, b) + Zm2(_AB, c); } -static void calc_kinematics_diff_probe_points(float z_pt[NPP + 1], abc_float_t delta_e, const float delta_r, abc_float_t delta_t) { +static void calc_kinematics_diff_probe_points(float z_pt[NPP + 1], const float dcr, abc_float_t delta_e, const float delta_r, abc_float_t delta_t) { const float z_center = z_pt[CEN]; abc_float_t diff_mm_at_pt_axis[NPP + 1], new_mm_at_pt_axis[NPP + 1]; - reverse_kinematics_probe_points(z_pt, diff_mm_at_pt_axis); + reverse_kinematics_probe_points(z_pt, diff_mm_at_pt_axis, dcr); delta_radius += delta_r; delta_tower_angle_trim += delta_t; recalc_delta_settings(); - reverse_kinematics_probe_points(z_pt, new_mm_at_pt_axis); + reverse_kinematics_probe_points(z_pt, new_mm_at_pt_axis, dcr); LOOP_CAL_ALL(rad) diff_mm_at_pt_axis[rad] -= new_mm_at_pt_axis[rad] + delta_e; - forward_kinematics_probe_points(diff_mm_at_pt_axis, z_pt); + forward_kinematics_probe_points(diff_mm_at_pt_axis, z_pt, dcr); LOOP_CAL_RAD(rad) z_pt[rad] -= z_pt[CEN] - z_center; z_pt[CEN] = z_center; @@ -323,31 +313,31 @@ static void calc_kinematics_diff_probe_points(float z_pt[NPP + 1], abc_float_t d recalc_delta_settings(); } -static float auto_tune_h() { - const float r_quot = delta_calibration_radius() / delta_radius; - return RECIPROCAL(r_quot / (2.0f / 3.0f)); // (2/3)/CR +static float auto_tune_h(const float dcr) { + const float r_quot = dcr / delta_radius; + return RECIPROCAL(r_quot * (3.0f / 2.0f)); // (2/3)/CR } -static float auto_tune_r() { +static float auto_tune_r(const float dcr) { constexpr float diff = 0.01f, delta_r = diff; float r_fac = 0.0f, z_pt[NPP + 1] = { 0.0f }; abc_float_t delta_e = { 0.0f }, delta_t = { 0.0f }; - calc_kinematics_diff_probe_points(z_pt, delta_e, delta_r, delta_t); + calc_kinematics_diff_probe_points(z_pt, dcr, delta_e, delta_r, delta_t); r_fac = -(z_pt[__A] + z_pt[__B] + z_pt[__C] + z_pt[_BC] + z_pt[_CA] + z_pt[_AB]) / 6.0f; r_fac = diff / r_fac / 3.0f; // 1/(3*delta_Z) return r_fac; } -static float auto_tune_a() { +static float auto_tune_a(const float dcr) { constexpr float diff = 0.01f, delta_r = 0.0f; float a_fac = 0.0f, z_pt[NPP + 1] = { 0.0f }; abc_float_t delta_e = { 0.0f }, delta_t = { 0.0f }; delta_t.reset(); - LOOP_XYZ(axis) { + LOOP_NUM_AXES(axis) { delta_t[axis] = diff; - calc_kinematics_diff_probe_points(z_pt, delta_e, delta_r, delta_t); + calc_kinematics_diff_probe_points(z_pt, dcr, delta_e, delta_r, delta_t); delta_t[axis] = 0; a_fac += z_pt[uint8_t((axis * _4P_STEP) - _7P_STEP + NPP) % NPP + 1] / 6.0f; a_fac -= z_pt[uint8_t((axis * _4P_STEP) + 1 + _7P_STEP)] / 6.0f; @@ -357,61 +347,91 @@ static float auto_tune_a() { } /** - * G33 - Delta '1-4-7-point' Auto-Calibration - * Calibrate height, z_offset, endstops, delta radius, and tower angles. + * G33: Delta Auto Calibration + * + * Calibrate height, z_offset, endstops, delta radius, and tower angles. * * 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 * - * 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. + * R Temporarily reduce the size of the probe grid by the specified amount * - * T Don't calibrate tower angle corrections + * T Disable tower angle corrections calibration (P3-P7) * - * Cn.nn Calibration precision; when omitted calibrates to maximum precision + * C Calibration precision; if omitted iterations stop at best achievable precision * - * Fn Force to run at least n iterations and take the best result + * F<1-30> Run (β€œforce”) this number of iterations and take the best result * - * 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 + * 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 * - * E Engage the probe for each point + * E Engage the probe for each point + * + * O Probe at probe-offset-relative positions instead of the required kinematic points + * + * 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)); + const int8_t probe_points = parser.intval('P', DELTA_CALIBRATION_DEFAULT_POINTS); if (!WITHIN(probe_points, 0, 10)) { - SERIAL_ECHOLNPGM("?(P)oints implausible (0-10)."); + SERIAL_ECHOLNPGM(GCODE_ERR_MSG("(P)oints implausible (0-10).")); return; } - const bool towers_set = !parser.seen('T'); + const bool probe_at_offset = TERN0(HAS_PROBE_XY_OFFSET, parser.seen_test('O')), + towers_set = !parser.seen_test('T'); + + // The calibration radius is set to a calculated value + float dcr = probe_at_offset ? PRINTABLE_RADIUS : PRINTABLE_RADIUS - PROBING_MARGIN; + #if HAS_PROBE_XY_OFFSET + const float total_offset = HYPOT(probe.offset_xy.x, probe.offset_xy.y); + dcr -= probe_at_offset ? _MAX(total_offset, PROBING_MARGIN) : total_offset; + #endif + NOMORE(dcr, PRINTABLE_RADIUS); + if (parser.seenval('R')) dcr -= _MAX(parser.value_float(), 0.0f); + TERN_(HAS_DELTA_SENSORLESS_PROBING, dcr *= sensorless_radius_factor); const float calibration_precision = parser.floatval('C', 0.0f); if (calibration_precision < 0) { - SERIAL_ECHOLNPGM("?(C)alibration precision implausible (>=0)."); + SERIAL_ECHOLNPGM(GCODE_ERR_MSG("(C)alibration precision implausible (>=0).")); return; } const int8_t force_iterations = parser.intval('F', 0); if (!WITHIN(force_iterations, 0, 30)) { - SERIAL_ECHOLNPGM("?(F)orce iteration implausible (0-30)."); + SERIAL_ECHOLNPGM(GCODE_ERR_MSG("(F)orce iteration implausible (0-30).")); return; } const int8_t verbose_level = parser.byteval('V', 1); if (!WITHIN(verbose_level, 0, 3)) { - SERIAL_ECHOLNPGM("?(V)erbose level implausible (0-3)."); + SERIAL_ECHOLNPGM(GCODE_ERR_MSG("(V)erbose level implausible (0-3).")); return; } - const bool stow_after_each = parser.seen('E'); + const bool stow_after_each = parser.seen_test('E'); + + #if HAS_DELTA_SENSORLESS_PROBING + probe.test_sensitivity = { !parser.seen_test('X'), !parser.seen_test('Y'), !parser.seen_test('Z') }; + const bool do_save_offset_adj = parser.seen_test('S'); + #endif const bool _0p_calibration = probe_points == 0, _1p_calibration = probe_points == 1 || probe_points == -1, @@ -435,24 +455,12 @@ void GcodeSuite::G33() { SERIAL_ECHOLNPGM("G33 Auto Calibrate"); - const float dcr = delta_calibration_radius(); - - if (!_1p_calibration && !_0p_calibration) { // test if the outer radius is reachable - LOOP_CAL_RAD(axis) { - const float a = RADIANS(210 + (360 / NPP) * (axis - 1)); - if (!position_is_reachable(cos(a) * dcr, sin(a) * dcr)) { - SERIAL_ECHOLNPGM("?Bed calibration radius implausible."); - return; - } - } - } - // Report settings - PGM_P const checkingac = PSTR("Checking... AC"); - serialprintPGM(checkingac); + FSTR_P const checkingac = F("Checking... AC"); + SERIAL_ECHO(checkingac, F(" at radius:"), dcr); if (verbose_level == 0) SERIAL_ECHOPGM(" (DRY-RUN)"); SERIAL_EOL(); - ui.set_status_P(checkingac); + ui.set_status(checkingac); print_calibration_settings(_endstop_results, _angle_results); @@ -460,18 +468,36 @@ void GcodeSuite::G33() { if (!_0p_calibration) ac_home(); - do { // start iterations + #if HAS_DELTA_SENSORLESS_PROBING + if (verbose_level > 0 && do_save_offset_adj) { + offset_sensorless_adj.reset(); + auto caltower = [&](Probe::sense_bool_t s) { + float z_at_pt[NPP + 1]; + LOOP_CAL_ALL(rad) z_at_pt[rad] = 0.0f; + probe.test_sensitivity = s; + if (probe_calibration_points(z_at_pt, 1, dcr, false, false, probe_at_offset)) + probe.set_offset_sensorless_adj(z_at_pt[CEN]); + }; + caltower({ true, false, false }); // A + caltower({ false, true, false }); // B + caltower({ false, false, true }); // C + + probe.test_sensitivity = { true, true, true }; // Reset to all + } + #endif + + 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 zero_std_dev_old = zero_std_dev; - if (!probe_calibration_points(z_at_pt, probe_points, towers_set, stow_after_each)) { + if (!probe_calibration_points(z_at_pt, probe_points, dcr, towers_set, stow_after_each, probe_at_offset)) { SERIAL_ECHOLNPGM("Correct delta settings with M665 and M666"); - return ac_cleanup(TERN_(HAS_MULTI_HOTEND, old_tool_index)); + return ac_cleanup(); } zero_std_dev = std_dev_points(z_at_pt, _0p_calibration, _1p_calibration, _4p_calibration, _4p_opposite_points); @@ -480,11 +506,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; @@ -495,43 +521,44 @@ 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 - if (_7p_9_center) calibration_radius_factor = 0.9f; - h_factor = auto_tune_h(); - r_factor = auto_tune_r(); - a_factor = auto_tune_a(); - calibration_radius_factor = 1.0f; + // Calculate factors + if (_7p_9_center) dcr *= 0.9f; + h_factor = auto_tune_h(dcr); + r_factor = auto_tune_r(dcr); + a_factor = auto_tune_a(dcr); + if (_7p_9_center) dcr /= 0.9f; 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 - LOOP_XYZ(axis) e_delta[axis] = +Z4(CEN); + 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)); @@ -539,13 +566,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); @@ -557,36 +584,45 @@ 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) { float a_sum = 0.0f; - LOOP_XYZ(axis) a_sum += delta_tower_angle_trim[axis]; - LOOP_XYZ(axis) delta_tower_angle_trim[axis] -= a_sum / 3.0f; + LOOP_NUM_AXES(axis) a_sum += delta_tower_angle_trim[axis]; + 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_XYZ(axis) delta_endstop_adj[axis] -= z_temp; + LOOP_NUM_AXES(axis) delta_endstop_adj[axis] -= z_temp; } recalc_delta_settings(); NOMORE(zero_std_dev_min, zero_std_dev); - // print report + // Print report - if (verbose_level == 3) + if (verbose_level == 3 || verbose_level == 0) { print_calibration_results(z_at_pt, _tower_results, _opposite_results); + #if HAS_DELTA_SENSORLESS_PROBING + if (verbose_level == 0 && probe_points == 1) { + if (do_save_offset_adj) + probe.set_offset_sensorless_adj(z_at_pt[CEN]); + else + probe.refresh_largest_sensorless_adj(); + } + #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); @@ -596,53 +632,55 @@ void GcodeSuite::G33() { else #endif { - SERIAL_ECHOPAIR_F("std dev:", zero_std_dev_min, 3); + SERIAL_ECHOPGM("std dev:", p_float_t(zero_std_dev_min, 3)); } SERIAL_EOL(); - char mess[21]; - strcpy_P(mess, PSTR("Calibration sd:")); + + MString<21> msg(F("Calibration sd:")); if (zero_std_dev_min < 1) - sprintf_P(&mess[15], PSTR("0.%03i"), (int)LROUND(zero_std_dev_min * 1000.0f)); + msg.appendf(F("0.%03i"), (int)LROUND(zero_std_dev_min * 1000.0f)); else - sprintf_P(&mess[15], PSTR("%03i.x"), (int)LROUND(zero_std_dev_min)); - ui.set_status(mess); + msg.appendf(F("%03i.x"), (int)LROUND(zero_std_dev_min)); + ui.set_status(msg); print_calibration_settings(_endstop_results, _angle_results); SERIAL_ECHOLNPGM("Save with M500 and/or copy to Configuration.h"); } else { // !end iterations - char mess[15]; + SString<15> msg; if (iterations < 31) - sprintf_P(mess, PSTR("Iteration : %02i"), (unsigned int)iterations); + msg.setf(F("Iteration : %02i"), (unsigned int)iterations); else - strcpy_P(mess, PSTR("No convergence")); - SERIAL_ECHO(mess); + msg.set(F("No convergence")); + msg.echo(); SERIAL_ECHO_SP(32); - SERIAL_ECHOLNPAIR_F("std dev:", zero_std_dev, 3); - ui.set_status(mess); + SERIAL_ECHOLNPGM("std dev:", p_float_t(zero_std_dev, 3)); + ui.set_status(msg); if (verbose_level > 1) print_calibration_settings(_endstop_results, _angle_results); } } - else { // dry run - PGM_P const enddryrun = PSTR("End DRY-RUN"); - serialprintPGM(enddryrun); + else { // Dry-run + FSTR_P const enddryrun = F("End DRY-RUN"); + SERIAL_ECHO(enddryrun); SERIAL_ECHO_SP(35); - SERIAL_ECHOLNPAIR_F("std dev:", zero_std_dev, 3); - - char mess[21]; - strcpy_P(mess, enddryrun); - strcpy_P(&mess[11], PSTR(" sd:")); + SERIAL_ECHOLNPGM("std dev:", p_float_t(zero_std_dev, 3)); + MString<30> msg(enddryrun, F(" sd:")); if (zero_std_dev < 1) - sprintf_P(&mess[15], PSTR("0.%03i"), (int)LROUND(zero_std_dev * 1000.0f)); + msg.appendf(F("0.%03i"), (int)LROUND(zero_std_dev * 1000.0f)); else - sprintf_P(&mess[15], PSTR("%03i.x"), (int)LROUND(zero_std_dev)); - ui.set_status(mess); + msg.appendf(F("%03i.x"), (int)LROUND(zero_std_dev)); + ui.set_status(msg); } ac_home(); } while (((zero_std_dev < test_precision && iterations < 31) || iterations <= force_iterations) && zero_std_dev > calibration_precision); - ac_cleanup(TERN_(HAS_MULTI_HOTEND, old_tool_index)); + ac_cleanup(); + + TERN_(FULL_REPORT_TO_HOST_FEATURE, set_and_report_grblstate(M_IDLE)); + #if HAS_DELTA_SENSORLESS_PROBING + probe.test_sensitivity = { true, true, true }; + #endif } #endif // DELTA_AUTO_CALIBRATION diff --git a/Marlin/src/gcode/calibrate/G34.cpp b/Marlin/src/gcode/calibrate/G34.cpp index 0ca4490eb6..504dcd1c6f 100644 --- a/Marlin/src/gcode/calibrate/G34.cpp +++ b/Marlin/src/gcode/calibrate/G34.cpp @@ -26,9 +26,12 @@ #include "../gcode.h" #include "../../module/motion.h" -#include "../../module/stepper.h" #include "../../module/endstops.h" +#if ANY(HAS_MOTOR_CURRENT_SPI, HAS_MOTOR_CURRENT_PWM, HAS_TRINAMIC_CONFIG) + #include "../../module/stepper.h" +#endif + #if HAS_LEVELING #include "../../feature/bedlevel/bedlevel.h" #endif @@ -36,17 +39,37 @@ #define DEBUG_OUT ENABLED(DEBUG_LEVELING_FEATURE) #include "../../core/debug_out.h" +/** + * 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' + * (or GANTRY_CALIBRATION_CURRENT) to prevent damage to steppers and other parts. + * The reduced current should be just high enough to move the Z axis when not blocked. + * - The Z axis is jogged past the Z limit, only as far as the specified Z distance + * (or GANTRY_CALIBRATION_EXTRA_HEIGHT) at the GANTRY_CALIBRATION_FEEDRATE. + * - The Z axis is moved back to the working area (also at GANTRY_CALIBRATION_FEEDRATE). + * - Stepper current is restored back to normal. + * - 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) + */ void GcodeSuite::G34() { // Home before the alignment procedure - if (!all_axes_known()) home_all_axes(); + home_if_needed(); + + TERN_(HAS_LEVELING, TEMPORARY_BED_LEVELING_STATE(false)); SET_SOFT_ENDSTOP_LOOSE(true); - TEMPORARY_BED_LEVELING_STATE(false); TemporaryGlobalEndstopsState unlock_z(false); #ifdef GANTRY_CALIBRATION_COMMANDS_PRE - gcode.process_subcommands_now_P(PSTR(GANTRY_CALIBRATION_COMMANDS_PRE)); + process_subcommands_now(F(GANTRY_CALIBRATION_COMMANDS_PRE)); if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("Sub Commands Processed"); #endif @@ -54,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(safe_pos, MMM_TO_MMS(GANTRY_CALIBRATION_XY_PARK_FEEDRATE)); + do_blocking_move_to_xy(safe_pos, MMM_TO_MMS(GANTRY_CALIBRATION_XY_PARK_FEEDRATE)); #endif const float move_distance = parser.intval('Z', GANTRY_CALIBRATION_EXTRA_HEIGHT), @@ -63,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, MMM_TO_MMS(HOMING_FEEDRATE_Z)); + do_blocking_move_to_z(zpounce, homing_feedrate(Z_AXIS)); // Store current motor settings, then apply reduced value @@ -74,37 +97,36 @@ 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[Z_AXIS]; + 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 ENABLED(HAS_MOTOR_CURRENT_I2C) + #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_STEPPER_DRIVERS]; - #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 @@ -113,41 +135,33 @@ void GcodeSuite::G34() { if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("Final Z Move"); do_blocking_move_to_z(zgrind, MMM_TO_MMS(GANTRY_CALIBRATION_FEEDRATE)); - // 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)); - #if _REDUCE_CURRENT // Reset current to original values if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("Restore Current"); #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); - #elif ENABLED(HAS_MOTOR_CURRENT_I2C) - digipot_i2c.set_current(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_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)); + #ifdef GANTRY_CALIBRATION_COMMANDS_POST if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("Running Post Commands"); - gcode.process_subcommands_now_P(PSTR(GANTRY_CALIBRATION_COMMANDS_POST)); + process_subcommands_now(F(GANTRY_CALIBRATION_COMMANDS_POST)); #endif SET_SOFT_ENDSTOP_LOOSE(false); diff --git a/Marlin/src/gcode/calibrate/G34_M422.cpp b/Marlin/src/gcode/calibrate/G34_M422.cpp index 24292477f9..6c367ad882 100644 --- a/Marlin/src/gcode/calibrate/G34_M422.cpp +++ b/Marlin/src/gcode/calibrate/G34_M422.cpp @@ -22,7 +22,7 @@ #include "../../inc/MarlinConfigPre.h" -#if ENABLED(Z_STEPPER_AUTO_ALIGN) +#if ANY(Z_MULTI_ENDSTOPS, Z_STEPPER_AUTO_ALIGN) #include "../../feature/z_stepper_align.h" @@ -31,391 +31,449 @@ #include "../../module/stepper.h" #include "../../module/planner.h" #include "../../module/probe.h" -#include "../../lcd/ultralcd.h" // for LCD_MESSAGEPGM +#include "../../lcd/marlinui.h" // for LCD_MESSAGE #if HAS_LEVELING #include "../../feature/bedlevel/bedlevel.h" #endif -#if HAS_MULTI_HOTEND - #include "../../module/tool_change.h" +#if HAS_Z_STEPPER_ALIGN_STEPPER_XY + #include "../../libs/least_squares_fit.h" #endif -#if ENABLED(Z_STEPPER_ALIGN_KNOWN_STEPPER_POSITIONS) - #include "../../libs/least_squares_fit.h" +#if ENABLED(BLTOUCH) + #include "../../feature/bltouch.h" #endif #define DEBUG_OUT ENABLED(DEBUG_LEVELING_FEATURE) #include "../../core/debug_out.h" +#if NUM_Z_STEPPERS >= 3 + #define TRIPLE_Z 1 + #if NUM_Z_STEPPERS >= 4 + #define QUAD_Z 1 + #endif +#endif + /** - * G34: Z-Stepper automatic alignment + * G34: Z Steppers Auto-Alignment * - * I - * T - * A - * R points based on current probe offsets + * 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 + * + * 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 + * + * Example: + * G34 Z1 ; Lock Z1 + * G34 L Z2 ; Unlock all, then lock Z2 + * G34 Z2 S0 ; Unlock Z2 */ void GcodeSuite::G34() { + DEBUG_SECTION(log_G34, "G34", DEBUGGING(LEVELING)); if (DEBUGGING(LEVELING)) log_machine_info(); - do { // break out on error + planner.synchronize(); // Prevent damage - #if NUM_Z_STEPPER_DRIVERS == 4 - SERIAL_ECHOLNPGM("Alignment for 4 steppers is Experimental!"); - #elif NUM_Z_STEPPER_DRIVERS > 4 - SERIAL_ECHOLNPGM("Alignment not supported for over 4 steppers"); - break; - #endif + const bool seenL = parser.seen('L'); + if (seenL) stepper.set_all_z_lock(false); - const int8_t z_auto_align_iterations = parser.intval('I', Z_STEPPER_ALIGN_ITERATIONS); - if (!WITHIN(z_auto_align_iterations, 1, 30)) { - SERIAL_ECHOLNPGM("?(I)teration out of bounds (1-30)."); - break; + const bool seenZ = parser.seenval('Z'); + if (seenZ) { + const bool state = parser.boolval('S', true); + switch (parser.intval('Z')) { + case 1: stepper.set_z1_lock(state); break; + case 2: stepper.set_z2_lock(state); break; + #if TRIPLE_Z + case 3: stepper.set_z3_lock(state); break; + #if QUAD_Z + case 4: stepper.set_z4_lock(state); break; + #endif + #endif } + } - 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("?(T)arget accuracy out of bounds (0.01-1.0)."); - break; - } + if (seenL || seenZ) { + stepper.set_separate_multi_axis(seenZ); + return; + } - const float z_auto_align_amplification = - #if ENABLED(Z_STEPPER_ALIGN_KNOWN_STEPPER_POSITIONS) - Z_STEPPER_ALIGN_AMP; - #else - parser.floatval('A', Z_STEPPER_ALIGN_AMP); - if (!WITHIN(ABS(z_auto_align_amplification), 0.5f, 2.0f)) { - SERIAL_ECHOLNPGM("?(A)mplification out of bounds (0.5-2.0)."); - break; - } - #endif + #if ENABLED(Z_STEPPER_AUTO_ALIGN) - if (parser.seen('R')) z_stepper_align.reset_to_default(); + do { // break out on error - const ProbePtRaise raise_after = parser.boolval('E') ? PROBE_PT_STOW : PROBE_PT_RAISE; - - // Wait for planner moves to finish! - planner.synchronize(); - - // Disable the leveling matrix before auto-aligning - #if HAS_LEVELING - TERN_(RESTORE_LEVELING_AFTER_G34, const bool leveling_was_active = planner.leveling_active); - set_bed_leveling_enabled(false); - #endif - - TERN_(CNC_WORKSPACE_PLANES, workspace_plane = PLANE_XY); - - // Always home with tool 0 active - #if HAS_MULTI_HOTEND - const uint8_t old_tool_index = active_extruder; - tool_change(0, true); - #endif - - TERN_(HAS_DUPLICATION_MODE, extruder_duplication_enabled = false); - - // In BLTOUCH HS mode, the probe travels in a deployed state. - // Users of G34 might have a badly misaligned bed, so raise Z by the - // length of the deployed pin (BLTOUCH stroke < 7mm) - #define Z_BASIC_CLEARANCE (Z_CLEARANCE_BETWEEN_PROBES + 7.0f * BOTH(BLTOUCH, BLTOUCH_HS_MODE)) - - // Compute a worst-case clearance height to probe from. After the first - // iteration this will be re-calculated based on the actual bed position - auto magnitude2 = [&](const uint8_t i, const uint8_t j) { - const xy_pos_t diff = z_stepper_align.xy[i] - z_stepper_align.xy[j]; - return HYPOT2(diff.x, diff.y); - }; - float z_probe = Z_BASIC_CLEARANCE + (G34_MAX_GRADE) * 0.01f * SQRT( - #if NUM_Z_STEPPER_DRIVERS == 3 - _MAX(magnitude2(0, 1), magnitude2(1, 2), magnitude2(2, 0)) - #elif NUM_Z_STEPPER_DRIVERS == 4 - _MAX(magnitude2(0, 1), magnitude2(1, 2), magnitude2(2, 3), - magnitude2(3, 0), magnitude2(0, 2), magnitude2(1, 3)) - #else - magnitude2(0, 1) - #endif - ); - - // Home before the alignment procedure - if (!all_axes_known()) home_all_axes(); - - // Move the Z coordinate realm towards the positive - dirty trick - current_position.z += z_probe * 0.5f; - sync_plan_position(); - // Now, the Z origin lies below the build plate. That allows to probe deeper, before run_z_probe throws an error. - // This hack is un-done at the end of G34 - either by re-homing, or by using the probed heights of the last iteration. - - #if DISABLED(Z_STEPPER_ALIGN_KNOWN_STEPPER_POSITIONS) - float last_z_align_move[NUM_Z_STEPPER_DRIVERS] = ARRAY_N(NUM_Z_STEPPER_DRIVERS, 10000.0f, 10000.0f, 10000.0f, 10000.0f); - #else - float last_z_align_level_indicator = 10000.0f; - #endif - float z_measured[NUM_Z_STEPPER_DRIVERS] = { 0 }, - z_maxdiff = 0.0f, - amplification = z_auto_align_amplification; - - #if DISABLED(Z_STEPPER_ALIGN_KNOWN_STEPPER_POSITIONS) - bool adjustment_reverse = false; - #endif - - #if HAS_DISPLAY - PGM_P const msg_iteration = GET_TEXT(MSG_ITERATION); - const uint8_t iter_str_len = strlen_P(msg_iteration); - #endif - - // Final z and iteration values will be used after breaking the loop - float z_measured_min; - uint8_t iteration = 0; - bool err_break = false; // To break out of nested loops - while (iteration < z_auto_align_iterations) { - if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("> probing all positions."); - - const int iter = iteration + 1; - SERIAL_ECHOLNPAIR("\nG34 Iteration: ", iter); - #if HAS_DISPLAY - char str[iter_str_len + 2 + 1]; - sprintf_P(str, msg_iteration, iter); - ui.set_status(str); - #endif - - // Initialize minimum value - z_measured_min = 100000.0f; - float z_measured_max = -100000.0f; - - // Probe all positions (one per Z-Stepper) - LOOP_L_N(i, NUM_Z_STEPPER_DRIVERS) { - // iteration odd/even --> downward / upward stepper sequence - const uint8_t iprobe = (iteration & 1) ? NUM_Z_STEPPER_DRIVERS - 1 - i : i; - - // Safe clearance even on an incline - if ((iteration == 0 || i > 0) && z_probe > current_position.z) do_blocking_move_to_z(z_probe); - - if (DEBUGGING(LEVELING)) - DEBUG_ECHOLNPAIR_P(PSTR("Probing X"), z_stepper_align.xy[iprobe].x, SP_Y_STR, z_stepper_align.xy[iprobe].y); - - // 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(z_stepper_align.xy[iprobe], raise_after, 0, true, false); - if (isnan(z_probed_height)) { - SERIAL_ECHOLNPGM("Probing failed"); - LCD_MESSAGEPGM(MSG_LCD_PROBING_FAILED); - err_break = true; - break; - } - - // Add height to each value, to provide a more useful target height for - // the next iteration of probing. This allows adjustments to be made away from the bed. - z_measured[iprobe] = z_probed_height + Z_CLEARANCE_BETWEEN_PROBES; - - if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPAIR("> Z", int(iprobe + 1), " measured position is ", z_measured[iprobe]); - - // Remember the minimum measurement to calculate the correction later on - z_measured_min = _MIN(z_measured_min, z_measured[iprobe]); - z_measured_max = _MAX(z_measured_max, z_measured[iprobe]); - } // for (i) - - if (err_break) break; - - // 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_BASIC_CLEARANCE + z_measured_max + z_maxdiff; - - #if ENABLED(Z_STEPPER_ALIGN_KNOWN_STEPPER_POSITIONS) - // Replace the initial values in z_measured with calculated heights at - // each stepper position. This allows the adjustment algorithm to be - // shared between both possible probing mechanisms. - - // This must be done after the next z_probe height is calculated, so that - // the height is calculated from actual print area positions, and not - // extrapolated motor movements. - - // Compute the least-squares fit for all probed points. - // Calculate the Z position of each stepper and store it in z_measured. - // This allows the actual adjustment logic to be shared by both algorithms. - linear_fit_data lfd; - incremental_LSF_reset(&lfd); - LOOP_L_N(i, NUM_Z_STEPPER_DRIVERS) { - SERIAL_ECHOLNPAIR("PROBEPT_", int(i), ": ", z_measured[i]); - incremental_LSF(&lfd, z_stepper_align.xy[i], z_measured[i]); - } - finish_incremental_LSF(&lfd); - - z_measured_min = 100000.0f; - LOOP_L_N(i, NUM_Z_STEPPER_DRIVERS) { - z_measured[i] = -(lfd.A * z_stepper_align.stepper_xy[i].x + lfd.B * z_stepper_align.stepper_xy[i].y + lfd.D); - z_measured_min = _MIN(z_measured_min, z_measured[i]); - } - - SERIAL_ECHOLNPAIR("CALCULATED STEPPER POSITIONS: Z1=", z_measured[0], " Z2=", z_measured[1], " Z3=", z_measured[2]); - #endif - - SERIAL_ECHOLNPAIR("\n" - "DIFFERENCE Z1-Z2=", ABS(z_measured[0] - z_measured[1]) - #if NUM_Z_STEPPER_DRIVERS == 3 - , " Z2-Z3=", ABS(z_measured[1] - z_measured[2]) - , " Z3-Z1=", ABS(z_measured[2] - z_measured[0]) - #endif - ); - #if HAS_DISPLAY - char fstr1[10]; - #if NUM_Z_STEPPER_DRIVERS == 2 - char msg[6 + (6 + 5) * 1 + 1]; - #else - char msg[6 + (6 + 5) * 3 + 1], fstr2[10], fstr3[10]; - #endif - sprintf_P(msg, - PSTR("Diffs Z1-Z2=%s" - #if NUM_Z_STEPPER_DRIVERS == 3 - " Z2-Z3=%s" - " Z3-Z1=%s" - #endif - ), dtostrf(ABS(z_measured[0] - z_measured[1]), 1, 3, fstr1) - #if NUM_Z_STEPPER_DRIVERS == 3 - , dtostrf(ABS(z_measured[1] - z_measured[2]), 1, 3, fstr2) - , dtostrf(ABS(z_measured[2] - z_measured[0]), 1, 3, fstr3) - #endif - ); - ui.set_status(msg); - #endif - - auto decreasing_accuracy = [](const float &v1, const float &v2){ - if (v1 < v2 * 0.7f) { - SERIAL_ECHOLNPGM("Decreasing Accuracy Detected."); - LCD_MESSAGEPGM(MSG_DECREASING_ACCURACY); - return true; - } - return false; - }; - - #if ENABLED(Z_STEPPER_ALIGN_KNOWN_STEPPER_POSITIONS) - - // Check if the applied corrections go in the correct direction. - // Calculate the sum of the absolute deviations from the mean of the probe measurements. - // Compare to the last iteration to ensure it's getting better. - - // Calculate mean value as a reference - float z_measured_mean = 0.0f; - LOOP_L_N(zstepper, NUM_Z_STEPPER_DRIVERS) z_measured_mean += z_measured[zstepper]; - z_measured_mean /= NUM_Z_STEPPER_DRIVERS; - - // Calculate the sum of the absolute deviations from the mean value - float z_align_level_indicator = 0.0f; - LOOP_L_N(zstepper, NUM_Z_STEPPER_DRIVERS) - z_align_level_indicator += ABS(z_measured[zstepper] - z_measured_mean); - - // If it's getting worse, stop and throw an error - err_break = decreasing_accuracy(last_z_align_level_indicator, z_align_level_indicator); - if (err_break) break; - - last_z_align_level_indicator = z_align_level_indicator; - #endif - - // The following correction actions are to be enabled for select Z-steppers only - stepper.set_separate_multi_axis(true); - - bool success_break = true; - // Correct the individual stepper offsets - LOOP_L_N(zstepper, NUM_Z_STEPPER_DRIVERS) { - // Calculate current stepper move - float z_align_move = z_measured[zstepper] - z_measured_min; - const float z_align_abs = ABS(z_align_move); - - #if DISABLED(Z_STEPPER_ALIGN_KNOWN_STEPPER_POSITIONS) - // Optimize one iteration's correction based on the first measurements - if (z_align_abs) amplification = (iteration == 1) ? _MIN(last_z_align_move[zstepper] / z_align_abs, 2.0f) : z_auto_align_amplification; - - // Check for less accuracy compared to last move - if (decreasing_accuracy(last_z_align_move[zstepper], z_align_abs)) { - if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPAIR("> Z", int(zstepper + 1), " last_z_align_move = ", last_z_align_move[zstepper]); - if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPAIR("> Z", int(zstepper + 1), " z_align_abs = ", z_align_abs); - adjustment_reverse = !adjustment_reverse; - } - - // Remember the alignment for the next iteration, but only if steppers move, - // otherwise it would be just zero (in case this stepper was at z_measured_min already) - if (z_align_abs > 0) last_z_align_move[zstepper] = z_align_abs; - #endif - - // Stop early if all measured points achieve accuracy target - if (z_align_abs > z_auto_align_accuracy) success_break = false; - - if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPAIR("> Z", int(zstepper + 1), " corrected by ", z_align_move); - - // Lock all steppers except one - stepper.set_all_z_lock(true, zstepper); - - #if DISABLED(Z_STEPPER_ALIGN_KNOWN_STEPPER_POSITIONS) - // 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; - if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPAIR("> Z", int(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); - } // for (zstepper) - - // Back to normal stepper operations - stepper.set_all_z_lock(false); - stepper.set_separate_multi_axis(false); - - if (err_break) break; - - if (success_break) { - SERIAL_ECHOLNPGM("Target accuracy achieved."); - LCD_MESSAGEPGM(MSG_ACCURACY_ACHIEVED); + const int8_t z_auto_align_iterations = parser.intval('I', Z_STEPPER_ALIGN_ITERATIONS); + if (!WITHIN(z_auto_align_iterations, 1, 30)) { + SERIAL_ECHOLNPGM(GCODE_ERR_MSG("(I)teration out of bounds (1-30).")); break; } - iteration++; - } // while (iteration < z_auto_align_iterations) + const float z_auto_align_accuracy = parser.floatval('T', Z_STEPPER_ALIGN_ACC); + 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; + } - if (err_break) - SERIAL_ECHOLNPGM("G34 aborted."); - else { - SERIAL_ECHOLNPAIR("Did ", int(iteration + (iteration != z_auto_align_iterations)), " of ", int(z_auto_align_iterations)); - SERIAL_ECHOLNPAIR_F("Accuracy: ", z_maxdiff); - } + const float z_auto_align_amplification = TERN(HAS_Z_STEPPER_ALIGN_STEPPER_XY, Z_STEPPER_ALIGN_AMP, parser.floatval('A', Z_STEPPER_ALIGN_AMP)); + if (!WITHIN(ABS(z_auto_align_amplification), 0.5f, 2.0f)) { + SERIAL_ECHOLNPGM(GCODE_ERR_MSG("(A)mplification out of bounds (0.5-2.0).")); + break; + } - // Stow the probe, as the last call to probe.probe_at_point(...) left - // the probe deployed if it was successful. - probe.stow(); + if (parser.seen('R')) z_stepper_align.reset_to_default(); - #if ENABLED(HOME_AFTER_G34) - // After this operation the z position needs correction - set_axis_never_homed(Z_AXIS); - // Home Z after the alignment procedure - process_subcommands_now_P(PSTR("G28Z")); - #else - // 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 - (float)Z_CLEARANCE_BETWEEN_PROBES; - sync_plan_position(); - #endif + const ProbePtRaise raise_after = parser.boolval('E') ? PROBE_PT_STOW : PROBE_PT_RAISE; - // Restore the active tool after homing - TERN_(HAS_MULTI_HOTEND, tool_change(old_tool_index, DISABLED(PARKING_EXTRUDER))); // Fetch previous tool for parking extruder + // Disable the leveling matrix before auto-aligning + #if HAS_LEVELING + #if ENABLED(RESTORE_LEVELING_AFTER_G34) + const bool leveling_was_active = planner.leveling_active; + #endif + set_bed_leveling_enabled(false); + #endif - #if BOTH(HAS_LEVELING, RESTORE_LEVELING_AFTER_G34) - set_bed_leveling_enabled(leveling_was_active); - #endif + TERN_(CNC_WORKSPACE_PLANES, workspace_plane = PLANE_XY); - }while(0); + probe.use_probing_tool(); + + #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, set_duplication_enabled(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 + auto magnitude2 = [&](const uint8_t i, const uint8_t j) { + const xy_pos_t diff = z_stepper_align.xy[i] - z_stepper_align.xy[j]; + return HYPOT2(diff.x, diff.y); + }; + const float zoffs = (probe.offset.z < 0) ? -probe.offset.z : 0.0f; + float z_probe = (Z_TWEEN_SAFE_CLEARANCE + zoffs) + (G34_MAX_GRADE) * 0.01f * SQRT(_MAX(0, magnitude2(0, 1) + #if TRIPLE_Z + , magnitude2(2, 1), magnitude2(2, 0) + #if QUAD_Z + , magnitude2(3, 2), magnitude2(3, 1), magnitude2(3, 0) + #endif + #endif + )); + + // Home before the alignment procedure + 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); + #else + float last_z_align_level_indicator = 10000.0f; + #endif + float z_measured[NUM_Z_STEPPERS] = { 0 }, + z_maxdiff = 0.0f, + amplification = z_auto_align_amplification; + + #if !HAS_Z_STEPPER_ALIGN_STEPPER_XY + bool adjustment_reverse = false; + #endif + + #if HAS_STATUS_MESSAGE + PGM_P const msg_iteration = GET_TEXT(MSG_ITERATION); + const uint8_t iter_str_len = strlen_P(msg_iteration); + #endif + + // Final z and iteration values will be used after breaking the loop + float z_measured_min; + uint8_t iteration = 0; + bool err_break = false; // To break out of nested loops + while (iteration < z_auto_align_iterations) { + if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("> probing all positions."); + + const int iter = iteration + 1; + SERIAL_ECHOLNPGM("\nG34 Iteration: ", iter); + #if HAS_STATUS_MESSAGE + char str[iter_str_len + 2 + 1]; + sprintf_P(str, msg_iteration, iter); + ui.set_status(str); + #endif + + // Initialize minimum value + z_measured_min = 100000.0f; + float z_measured_max = -100000.0f; + + // Probe all positions (one per Z-Stepper) + for (uint8_t i = 0; i < NUM_Z_STEPPERS; ++i) { + // iteration odd/even --> downward / upward stepper sequence + const uint8_t iprobe = (iteration & 1) ? NUM_Z_STEPPERS - 1 - i : i; + + xy_pos_t &ppos = z_stepper_align.xy[iprobe]; + + if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM_P(PSTR("Probing X"), ppos.x, SP_Y_STR, ppos.y); + + // 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 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(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); + err_break = true; + break; + } + + // Add height to each value, to provide a more useful target height for + // the next iteration of probing. This allows adjustments to be made away from the bed. + z_measured[iprobe] = z_probed_height + (Z_TWEEN_SAFE_CLEARANCE + zoffs); //do we need to add the clearance to this? + + if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("> Z", iprobe + 1, " measured position is ", z_measured[iprobe]); + + // Remember the minimum measurement to calculate the correction later on + z_measured_min = _MIN(z_measured_min, z_measured[iprobe]); + z_measured_max = _MAX(z_measured_max, z_measured[iprobe]); + } // for (i) + + if (err_break) break; + + // 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; + + // 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 + // each stepper position. This allows the adjustment algorithm to be + // shared between both possible probing mechanisms. + + // This must be done after the next z_probe height is calculated, so that + // the height is calculated from actual print area positions, and not + // extrapolated motor movements. + + // Compute the least-squares fit for all probed points. + // Calculate the Z position of each stepper and store it in z_measured. + // This allows the actual adjustment logic to be shared by both algorithms. + linear_fit_data lfd; + incremental_LSF_reset(&lfd); + for (uint8_t i = 0; i < NUM_Z_STEPPERS; ++i) { + SERIAL_ECHOLNPGM("PROBEPT_", i, ": ", z_measured[i]); + incremental_LSF(&lfd, z_stepper_align.xy[i], z_measured[i]); + } + finish_incremental_LSF(&lfd); + + z_measured_min = 100000.0f; + for (uint8_t i = 0; i < NUM_Z_STEPPERS; ++i) { + z_measured[i] = -(lfd.A * z_stepper_align.stepper_xy[i].x + lfd.B * z_stepper_align.stepper_xy[i].y + lfd.D); + z_measured_min = _MIN(z_measured_min, z_measured[i]); + } + + SERIAL_ECHOLNPGM( + LIST_N(DOUBLE(NUM_Z_STEPPERS), + "Calculated Z1=", z_measured[0], + " Z2=", z_measured[1], + " Z3=", z_measured[2], + " Z4=", z_measured[3] + ) + ); + #endif + + SERIAL_EOL(); + + 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)); + #endif + #if QUAD_Z + msg.append(F(" 4-3="), p_float_t(ABS(z_measured[3] - z_measured[2]), 3)) + .append(F(" 4-2="), p_float_t(ABS(z_measured[3] - z_measured[1]), 3)) + .append(F(" 4-1="), p_float_t(ABS(z_measured[3] - z_measured[0]), 3)); + #endif + + msg.echoln(); + ui.set_status(msg); + + auto decreasing_accuracy = [](const float v1, const float v2) { + if (v1 < v2 * 0.7f) { + SERIAL_ECHOLNPGM("Decreasing Accuracy Detected."); + LCD_MESSAGE(MSG_DECREASING_ACCURACY); + return true; + } + return false; + }; + + #if HAS_Z_STEPPER_ALIGN_STEPPER_XY + // Check if the applied corrections go in the correct direction. + // Calculate the sum of the absolute deviations from the mean of the probe measurements. + // Compare to the last iteration to ensure it's getting better. + + // Calculate mean value as a reference + float z_measured_mean = 0.0f; + for (uint8_t zstepper = 0; zstepper < NUM_Z_STEPPERS; ++zstepper) z_measured_mean += z_measured[zstepper]; + z_measured_mean /= NUM_Z_STEPPERS; + + // Calculate the sum of the absolute deviations from the mean value + float z_align_level_indicator = 0.0f; + for (uint8_t zstepper = 0; zstepper < NUM_Z_STEPPERS; ++zstepper) + z_align_level_indicator += ABS(z_measured[zstepper] - z_measured_mean); + + // If it's getting worse, stop and throw an error + err_break = decreasing_accuracy(last_z_align_level_indicator, z_align_level_indicator); + if (err_break) break; + + last_z_align_level_indicator = z_align_level_indicator; + #endif + + // The following correction actions are to be enabled for select Z-steppers only + stepper.set_separate_multi_axis(true); + + bool success_break = true; + // Correct the individual stepper offsets + for (uint8_t zstepper = 0; zstepper < NUM_Z_STEPPERS; ++zstepper) { + // Calculate current stepper move + float z_align_move = z_measured[zstepper] - z_measured_min; + const float z_align_abs = ABS(z_align_move); + + #if !HAS_Z_STEPPER_ALIGN_STEPPER_XY + // Optimize one iteration's correction based on the first measurements + if (z_align_abs) amplification = (iteration == 1) ? _MIN(last_z_align_move[zstepper] / z_align_abs, 2.0f) : z_auto_align_amplification; + + // Check for less accuracy compared to last move + 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); + FLIP(adjustment_reverse); + } + + // Remember the alignment for the next iteration, but only if steppers move, + // otherwise it would be just zero (in case this stepper was at z_measured_min already) + if (z_align_abs > 0) last_z_align_move[zstepper] = z_align_abs; + #endif + + // Stop early if all measured points achieve accuracy target + if (z_align_abs > z_auto_align_accuracy) success_break = false; + + if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("> Z", zstepper + 1, " corrected by ", z_align_move); + + // Lock all steppers except one + stepper.set_all_z_lock(true, zstepper); + + #if !HAS_Z_STEPPER_ALIGN_STEPPER_XY + // 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 *= -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); + } // for (zstepper) + + // Back to normal stepper operations + stepper.set_all_z_lock(false); + stepper.set_separate_multi_axis(false); + + if (err_break) break; + + if (success_break) { + SERIAL_ECHOLNPGM("Target accuracy achieved."); + LCD_MESSAGE(MSG_ACCURACY_ACHIEVED); + break; + } + + iteration++; + } // while (iteration < z_auto_align_iterations) + + if (err_break) + 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, 3)); + } + + // Stow the probe because the last call to probe.probe_at_point(...) + // leaves the probe deployed when it's successful. + IF_DISABLED(TOUCH_MI_PROBE, probe.stow()); + + #if ENABLED(HOME_AFTER_G34) + // Home Z after the alignment procedure + process_subcommands_now(F("G28Z")); + #else + // 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. + 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) + 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(); + #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); + + #if ALL(HAS_LEVELING, RESTORE_LEVELING_AFTER_G34) + set_bed_leveling_enabled(leveling_was_active); + #endif + + }while(0); + + probe.use_probing_tool(false); + + #endif // Z_STEPPER_AUTO_ALIGN } +#endif // Z_MULTI_ENDSTOPS || Z_STEPPER_AUTO_ALIGN + +#if ENABLED(Z_STEPPER_AUTO_ALIGN) + /** * M422: Set a Z-Stepper automatic alignment XY point. * Use repeatedly to set multiple points. * * S : Index of the probe point to set * - * With Z_STEPPER_ALIGN_KNOWN_STEPPER_POSITIONS: + * With Z_STEPPER_ALIGN_STEPPER_XY: * W : Index of the Z stepper position to set * The W and S parameters may not be combined. * @@ -427,71 +485,49 @@ void GcodeSuite::G34() { */ void GcodeSuite::M422() { + if (!parser.seen_any()) return M422_report(); + if (parser.seen('R')) { z_stepper_align.reset_to_default(); return; } - if (!parser.seen_any()) { - LOOP_L_N(i, NUM_Z_STEPPER_DRIVERS) - SERIAL_ECHOLNPAIR_P(PSTR("M422 S"), int(i + 1), SP_X_STR, z_stepper_align.xy[i].x, SP_Y_STR, z_stepper_align.xy[i].y); - #if ENABLED(Z_STEPPER_ALIGN_KNOWN_STEPPER_POSITIONS) - LOOP_L_N(i, NUM_Z_STEPPER_DRIVERS) - SERIAL_ECHOLNPAIR_P(PSTR("M422 W"), int(i + 1), SP_X_STR, z_stepper_align.stepper_xy[i].x, SP_Y_STR, z_stepper_align.stepper_xy[i].y); - #endif + const bool is_probe_point = parser.seen_test('S'); + + if (TERN0(HAS_Z_STEPPER_ALIGN_STEPPER_XY, is_probe_point && parser.seen_test('W'))) { + SERIAL_ECHOLNPGM(GCODE_ERR_MSG("(S) and (W) may not be combined.")); return; } - const bool is_probe_point = parser.seen('S'); - - #if ENABLED(Z_STEPPER_ALIGN_KNOWN_STEPPER_POSITIONS) - if (is_probe_point && parser.seen('W')) { - SERIAL_ECHOLNPGM("?(S) and (W) may not be combined."); - return; - } - #endif - - xy_pos_t *pos_dest = ( - #if ENABLED(Z_STEPPER_ALIGN_KNOWN_STEPPER_POSITIONS) - !is_probe_point ? z_stepper_align.stepper_xy : - #endif + xy_pos_t * const pos_dest = ( + TERN_(HAS_Z_STEPPER_ALIGN_STEPPER_XY, !is_probe_point ? z_stepper_align.stepper_xy :) z_stepper_align.xy ); - if (!is_probe_point - #if ENABLED(Z_STEPPER_ALIGN_KNOWN_STEPPER_POSITIONS) - && !parser.seen('W') - #endif - ) { - SERIAL_ECHOLNPGM( - #if ENABLED(Z_STEPPER_ALIGN_KNOWN_STEPPER_POSITIONS) - "?(S) or (W) is required." - #else - "?(S) is required." - #endif - ); + if (!is_probe_point && TERN1(HAS_Z_STEPPER_ALIGN_STEPPER_XY, !parser.seen_test('W'))) { + SERIAL_ECHOLNPGM(GCODE_ERR_MSG("(S)" TERN_(HAS_Z_STEPPER_ALIGN_STEPPER_XY, " or (W)") " is required.")); return; } // Get the Probe Position Index or Z Stepper Index - int8_t position_index; - if (is_probe_point) { - position_index = parser.intval('S') - 1; - if (!WITHIN(position_index, 0, int8_t(NUM_Z_STEPPER_DRIVERS) - 1)) { - SERIAL_ECHOLNPGM("?(S) Z-ProbePosition index invalid."); - return; - } - } + int8_t position_index = 1; + FSTR_P err_string = F("?(S) Probe-position"); + if (is_probe_point) + position_index = parser.intval('S'); else { - #if ENABLED(Z_STEPPER_ALIGN_KNOWN_STEPPER_POSITIONS) - position_index = parser.intval('W') - 1; - if (!WITHIN(position_index, 0, NUM_Z_STEPPER_DRIVERS - 1)) { - SERIAL_ECHOLNPGM("?(W) Z-Stepper index invalid."); - return; - } + #if HAS_Z_STEPPER_ALIGN_STEPPER_XY + err_string = F("?(W) Z-stepper"); + position_index = parser.intval('W'); #endif } + if (!WITHIN(position_index, 1, NUM_Z_STEPPERS)) { + SERIAL_ECHOLN(err_string, F(" index invalid (1.." STRINGIFY(NUM_Z_STEPPERS) ").")); + return; + } + + --position_index; + const xy_pos_t pos = { parser.floatval('X', pos_dest[position_index].x), parser.floatval('Y', pos_dest[position_index].y) @@ -499,11 +535,11 @@ void GcodeSuite::M422() { if (is_probe_point) { if (!probe.can_reach(pos.x, Y_CENTER)) { - SERIAL_ECHOLNPGM("?(X) out of bounds."); + SERIAL_ECHOLNPGM(GCODE_ERR_MSG("(X) out of bounds.")); return; } if (!probe.can_reach(pos)) { - SERIAL_ECHOLNPGM("?(Y) out of bounds."); + SERIAL_ECHOLNPGM(GCODE_ERR_MSG("(Y) out of bounds.")); return; } } @@ -511,4 +547,28 @@ void GcodeSuite::M422() { pos_dest[position_index] = pos; } +void GcodeSuite::M422_report(const bool forReplay/*=true*/) { + TERN_(MARLIN_SMALL_BUILD, return); + + report_heading(forReplay, F(STR_Z_AUTO_ALIGN)); + for (uint8_t i = 0; i < NUM_Z_STEPPERS; ++i) { + report_echo_start(forReplay); + SERIAL_ECHOLNPGM_P( + PSTR(" M422 S"), i + 1, + SP_X_STR, z_stepper_align.xy[i].x, + SP_Y_STR, z_stepper_align.xy[i].y + ); + } + #if HAS_Z_STEPPER_ALIGN_STEPPER_XY + for (uint8_t i = 0; i < NUM_Z_STEPPERS; ++i) { + report_echo_start(forReplay); + SERIAL_ECHOLNPGM_P( + PSTR(" M422 W"), i + 1, + SP_X_STR, z_stepper_align.stepper_xy[i].x, + SP_Y_STR, z_stepper_align.stepper_xy[i].y + ); + } + #endif +} + #endif // Z_STEPPER_AUTO_ALIGN diff --git a/Marlin/src/gcode/calibrate/G425.cpp b/Marlin/src/gcode/calibrate/G425.cpp index 8968f78999..883d7b4b6f 100644 --- a/Marlin/src/gcode/calibrate/G425.cpp +++ b/Marlin/src/gcode/calibrate/G425.cpp @@ -30,13 +30,16 @@ #include "../../feature/backlash.h" #endif -#include "../../lcd/ultralcd.h" +#include "../../lcd/marlinui.h" #include "../../module/motion.h" #include "../../module/planner.h" -#include "../../module/tool_change.h" #include "../../module/endstops.h" #include "../../feature/bedlevel/bedlevel.h" +#if HAS_MULTI_HOTEND + #include "../../module/tool_change.h" +#endif + #if !AXIS_CAN_CALIBRATE(X) #undef CALIBRATION_MEASURE_LEFT #undef CALIBRATION_MEASURE_RIGHT @@ -70,14 +73,37 @@ #define CALIBRATION_MEASUREMENT_CERTAIN 0.5 // mm #endif -#if BOTH(CALIBRATION_MEASURE_LEFT, CALIBRATION_MEASURE_RIGHT) +#if ALL(HAS_X_AXIS, CALIBRATION_MEASURE_LEFT, CALIBRATION_MEASURE_RIGHT) #define HAS_X_CENTER 1 #endif -#if BOTH(CALIBRATION_MEASURE_FRONT, CALIBRATION_MEASURE_BACK) +#if ALL(HAS_Y_AXIS, CALIBRATION_MEASURE_FRONT, CALIBRATION_MEASURE_BACK) #define HAS_Y_CENTER 1 #endif +#if ALL(HAS_I_AXIS, CALIBRATION_MEASURE_IMIN, CALIBRATION_MEASURE_IMAX) + #define HAS_I_CENTER 1 +#endif +#if ALL(HAS_J_AXIS, CALIBRATION_MEASURE_JMIN, CALIBRATION_MEASURE_JMAX) + #define HAS_J_CENTER 1 +#endif +#if ALL(HAS_K_AXIS, CALIBRATION_MEASURE_KMIN, CALIBRATION_MEASURE_KMAX) + #define HAS_K_CENTER 1 +#endif +#if ALL(HAS_U_AXIS, CALIBRATION_MEASURE_UMIN, CALIBRATION_MEASURE_UMAX) + #define HAS_U_CENTER 1 +#endif +#if ALL(HAS_V_AXIS, CALIBRATION_MEASURE_VMIN, CALIBRATION_MEASURE_VMAX) + #define HAS_V_CENTER 1 +#endif +#if ALL(HAS_W_AXIS, CALIBRATION_MEASURE_WMIN, CALIBRATION_MEASURE_WMAX) + #define HAS_W_CENTER 1 +#endif -enum side_t : uint8_t { TOP, RIGHT, FRONT, LEFT, BACK, NUM_SIDES }; +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) +}; static constexpr xyz_pos_t true_center CALIBRATION_OBJECT_CENTER; static constexpr xyz_float_t dimensions CALIBRATION_OBJECT_DIMENSIONS; @@ -93,19 +119,33 @@ struct measurements_t { }; #if ENABLED(BACKLASH_GCODE) - #define TEMPORARY_BACKLASH_CORRECTION(value) REMEMBER(tbst, backlash.correction, value) + class restorer_correction { + const uint8_t val_; + public: + restorer_correction(const uint8_t temp_val) : val_(backlash.get_correction_uint8()) { backlash.set_correction_uint8(temp_val); } + ~restorer_correction() { backlash.set_correction_uint8(val_); } + }; + + #define TEMPORARY_BACKLASH_CORRECTION(value) restorer_correction restorer_tbst(value) #else #define TEMPORARY_BACKLASH_CORRECTION(value) #endif #if ENABLED(BACKLASH_GCODE) && defined(BACKLASH_SMOOTHING_MM) - #define TEMPORARY_BACKLASH_SMOOTHING(value) REMEMBER(tbsm, backlash.smoothing_mm, value) + class restorer_smoothing { + const float val_; + public: + restorer_smoothing(const float temp_val) : val_(backlash.get_smoothing_mm()) { backlash.set_smoothing_mm(temp_val); } + ~restorer_smoothing() { backlash.set_smoothing_mm(val_); } + }; + + #define TEMPORARY_BACKLASH_SMOOTHING(value) restorer_smoothing restorer_tbsm(value) #else #define TEMPORARY_BACKLASH_SMOOTHING(value) #endif inline void calibration_move() { - do_blocking_move_to(current_position, MMM_TO_MMS(CALIBRATION_FEEDRATE_TRAVEL)); + do_blocking_move_to((xyz_pos_t)current_position, MMM_TO_MMS(CALIBRATION_FEEDRATE_TRAVEL)); } /** @@ -136,25 +176,13 @@ inline void park_above_object(measurements_t &m, const float uncertainty) { #if HAS_HOTEND_OFFSET inline void normalize_hotend_offsets() { - LOOP_S_L_N(e, 1, HOTENDS) + for (uint8_t e = 1; e < HOTENDS; ++e) hotend_offset[e] -= hotend_offset[0]; hotend_offset[0].reset(); } #endif -inline bool read_calibration_pin() { - return ( - #if PIN_EXISTS(CALIBRATION) - READ(CALIBRATION_PIN) != CALIBRATION_PIN_INVERTING - #elif HAS_CUSTOM_PROBE_PIN - READ(Z_MIN_PROBE_PIN) != Z_MIN_PROBE_ENDSTOP_INVERTING - #else - READ(Z_MIN_PIN) != Z_MIN_ENDSTOP_INVERTING - #endif - ); -} - /** * Move along axis in the specified dir until the probe value becomes stop_state, * then return the axis value. @@ -165,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(destination, mms); - planner.synchronize(); - if (read_calibration_pin() == stop_state) break; - } - return destination[axis]; + destination[axis] += dir * limit; + endstops.enable_calibration_probe(true, stop_state); + do_blocking_move_to((xyz_pos_t)destination, mms); + endstops.enable_calibration_probe(false); + endstops.hit_on_purpose(); + set_current_from_steppers_for_axis(axis); + sync_plan_position(); + return current_position[axis]; } /** @@ -192,18 +220,22 @@ float measuring_movement(const AxisEnum axis, const int dir, const bool stop_sta inline float measure(const AxisEnum axis, const int dir, const bool stop_state, float * const backlash_ptr, const float uncertainty) { const bool fast = uncertainty == CALIBRATION_MEASUREMENT_UNKNOWN; - // Save position - destination = current_position; - const float start_pos = destination[axis]; + // Save the current position of the specified axis + const float start_pos = current_position[axis]; + + // Take a measurement. Only the specified axis will be affected. const float measured_pos = measuring_movement(axis, dir, stop_state, fast); + // Measure backlash if (backlash_ptr && !fast) { const float release_pos = measuring_movement(axis, -dir, !stop_state, fast); *backlash_ptr = ABS(release_pos - measured_pos); } - // Return to starting position + + // Move back to the starting position + destination = current_position; destination[axis] = start_pos; - do_blocking_move_to(destination, MMM_TO_MMS(CALIBRATION_FEEDRATE_TRAVEL)); + do_blocking_move_to((xyz_pos_t)destination, MMM_TO_MMS(CALIBRATION_FEEDRATE_TRAVEL)); return measured_pos; } @@ -223,7 +255,16 @@ inline void probe_side(measurements_t &m, const float uncertainty, const side_t park_above_object(m, uncertainty); + #define _ACASE(N,A,B) case A: dir = -1; case B: axis = N##_AXIS; break + #define _PCASE(N) _ACASE(N, N##MINIMUM, N##MAXIMUM) + switch (side) { + #if AXIS_CAN_CALIBRATE(X) + _ACASE(X, RIGHT, LEFT); + #endif + #if AXIS_CAN_CALIBRATE(Y) + _ACASE(Y, BACK, FRONT); + #endif #if AXIS_CAN_CALIBRATE(Z) case TOP: { const float measurement = measure(Z_AXIS, -1, true, &m.backlash[TOP], uncertainty); @@ -232,13 +273,23 @@ inline void probe_side(measurements_t &m, const float uncertainty, const side_t return; } #endif - #if AXIS_CAN_CALIBRATE(X) - case LEFT: axis = X_AXIS; break; - case RIGHT: axis = X_AXIS; dir = -1; break; + #if AXIS_CAN_CALIBRATE(I) + _PCASE(I); #endif - #if AXIS_CAN_CALIBRATE(Y) - case FRONT: axis = Y_AXIS; break; - case BACK: axis = Y_AXIS; dir = -1; break; + #if AXIS_CAN_CALIBRATE(J) + _PCASE(J); + #endif + #if AXIS_CAN_CALIBRATE(K) + _PCASE(K); + #endif + #if AXIS_CAN_CALIBRATE(U) + _PCASE(U); + #endif + #if AXIS_CAN_CALIBRATE(V) + _PCASE(V); + #endif + #if AXIS_CAN_CALIBRATE(W) + _PCASE(W); #endif default: return; } @@ -283,121 +334,232 @@ 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)); - 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)); + /** + * 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_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)); + TERN_(CALIBRATION_MEASURE_JMAX, probe_side(m, uncertainty, JMAXIMUM, probe_top_at_edge)); + TERN_(CALIBRATION_MEASURE_KMIN, probe_side(m, uncertainty, KMINIMUM, probe_top_at_edge)); + TERN_(CALIBRATION_MEASURE_KMAX, probe_side(m, uncertainty, KMAXIMUM, probe_top_at_edge)); + TERN_(CALIBRATION_MEASURE_UMIN, probe_side(m, uncertainty, UMINIMUM, probe_top_at_edge)); + TERN_(CALIBRATION_MEASURE_UMAX, probe_side(m, uncertainty, UMAXIMUM, probe_top_at_edge)); + TERN_(CALIBRATION_MEASURE_VMIN, probe_side(m, uncertainty, VMINIMUM, probe_top_at_edge)); + TERN_(CALIBRATION_MEASURE_VMAX, probe_side(m, uncertainty, VMAXIMUM, probe_top_at_edge)); + TERN_(CALIBRATION_MEASURE_WMIN, probe_side(m, uncertainty, WMINIMUM, probe_top_at_edge)); + TERN_(CALIBRATION_MEASURE_WMAX, probe_side(m, uncertainty, WMAXIMUM, probe_top_at_edge)); // 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_X_CENTER, m.obj_center.x = (m.obj_side[LEFT] + m.obj_side[RIGHT]) / 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); + TERN_(HAS_U_CENTER, m.obj_center.u = (m.obj_side[UMINIMUM] + m.obj_side[UMAXIMUM]) / 2); + TERN_(HAS_V_CENTER, m.obj_center.v = (m.obj_side[VMINIMUM] + m.obj_side[VMAXIMUM]) / 2); + TERN_(HAS_W_CENTER, m.obj_center.w = (m.obj_side[WMINIMUM] + m.obj_side[WMAXIMUM]) / 2); // 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); // The difference between the known and the measured location // of the calibration object is the positional error - m.pos_error.x = (0 - #if HAS_X_CENTER - + true_center.x - m.obj_center.x - #endif + NUM_AXIS_CODE( + m.pos_error.x = TERN0(HAS_X_CENTER, true_center.x - m.obj_center.x), + m.pos_error.y = TERN0(HAS_Y_CENTER, true_center.y - m.obj_center.y), + m.pos_error.z = true_center.z - m.obj_center.z, + m.pos_error.i = TERN0(HAS_I_CENTER, true_center.i - m.obj_center.i), + m.pos_error.j = TERN0(HAS_J_CENTER, true_center.j - m.obj_center.j), + m.pos_error.k = TERN0(HAS_K_CENTER, true_center.k - m.obj_center.k), + m.pos_error.u = TERN0(HAS_U_CENTER, true_center.u - m.obj_center.u), + m.pos_error.v = TERN0(HAS_V_CENTER, true_center.v - m.obj_center.v), + m.pos_error.w = TERN0(HAS_W_CENTER, true_center.w - m.obj_center.w) ); - m.pos_error.y = (0 - #if HAS_Y_CENTER - + true_center.y - m.obj_center.y - #endif - ); - m.pos_error.z = true_center.z - m.obj_center.z; } #if ENABLED(CALIBRATION_REPORTING) inline void report_measured_faces(const measurements_t &m) { SERIAL_ECHOLNPGM("Sides:"); #if AXIS_CAN_CALIBRATE(Z) - SERIAL_ECHOLNPAIR(" Top: ", m.obj_side[TOP]); + SERIAL_ECHOLNPGM(" Top: ", m.obj_side[TOP]); #endif - #if ENABLED(CALIBRATION_MEASURE_LEFT) - SERIAL_ECHOLNPAIR(" Left: ", m.obj_side[LEFT]); + #if HAS_X_AXIS + #if ENABLED(CALIBRATION_MEASURE_LEFT) + SERIAL_ECHOLNPGM(" Left: ", m.obj_side[LEFT]); + #endif + #if ENABLED(CALIBRATION_MEASURE_RIGHT) + SERIAL_ECHOLNPGM(" Right: ", m.obj_side[RIGHT]); + #endif #endif - #if ENABLED(CALIBRATION_MEASURE_RIGHT) - SERIAL_ECHOLNPAIR(" Right: ", m.obj_side[RIGHT]); + #if HAS_Y_AXIS + #if ENABLED(CALIBRATION_MEASURE_FRONT) + SERIAL_ECHOLNPGM(" Front: ", m.obj_side[FRONT]); + #endif + #if ENABLED(CALIBRATION_MEASURE_BACK) + SERIAL_ECHOLNPGM(" Back: ", m.obj_side[BACK]); + #endif #endif - #if ENABLED(CALIBRATION_MEASURE_FRONT) - SERIAL_ECHOLNPAIR(" Front: ", m.obj_side[FRONT]); + #if HAS_I_AXIS + #if ENABLED(CALIBRATION_MEASURE_IMIN) + SERIAL_ECHOLNPGM(" " STR_I_MIN ": ", m.obj_side[IMINIMUM]); + #endif + #if ENABLED(CALIBRATION_MEASURE_IMAX) + SERIAL_ECHOLNPGM(" " STR_I_MAX ": ", m.obj_side[IMAXIMUM]); + #endif #endif - #if ENABLED(CALIBRATION_MEASURE_BACK) - SERIAL_ECHOLNPAIR(" Back: ", m.obj_side[BACK]); + #if HAS_J_AXIS + #if ENABLED(CALIBRATION_MEASURE_JMIN) + SERIAL_ECHOLNPGM(" " STR_J_MIN ": ", m.obj_side[JMINIMUM]); + #endif + #if ENABLED(CALIBRATION_MEASURE_JMAX) + SERIAL_ECHOLNPGM(" " STR_J_MAX ": ", m.obj_side[JMAXIMUM]); + #endif + #endif + #if HAS_K_AXIS + #if ENABLED(CALIBRATION_MEASURE_KMIN) + SERIAL_ECHOLNPGM(" " STR_K_MIN ": ", m.obj_side[KMINIMUM]); + #endif + #if ENABLED(CALIBRATION_MEASURE_KMAX) + SERIAL_ECHOLNPGM(" " STR_K_MAX ": ", m.obj_side[KMAXIMUM]); + #endif + #endif + #if HAS_U_AXIS + #if ENABLED(CALIBRATION_MEASURE_UMIN) + SERIAL_ECHOLNPGM(" " STR_U_MIN ": ", m.obj_side[UMINIMUM]); + #endif + #if ENABLED(CALIBRATION_MEASURE_UMAX) + SERIAL_ECHOLNPGM(" " STR_U_MAX ": ", m.obj_side[UMAXIMUM]); + #endif + #endif + #if HAS_V_AXIS + #if ENABLED(CALIBRATION_MEASURE_VMIN) + SERIAL_ECHOLNPGM(" " STR_V_MIN ": ", m.obj_side[VMINIMUM]); + #endif + #if ENABLED(CALIBRATION_MEASURE_VMAX) + SERIAL_ECHOLNPGM(" " STR_V_MAX ": ", m.obj_side[VMAXIMUM]); + #endif + #endif + #if HAS_W_AXIS + #if ENABLED(CALIBRATION_MEASURE_WMIN) + SERIAL_ECHOLNPGM(" " STR_W_MIN ": ", m.obj_side[WMINIMUM]); + #endif + #if ENABLED(CALIBRATION_MEASURE_WMAX) + SERIAL_ECHOLNPGM(" " STR_W_MAX ": ", m.obj_side[WMAXIMUM]); + #endif #endif SERIAL_EOL(); } inline void report_measured_center(const measurements_t &m) { SERIAL_ECHOLNPGM("Center:"); - #if HAS_X_CENTER - SERIAL_ECHOLNPAIR_P(SP_X_STR, m.obj_center.x); - #endif - #if HAS_Y_CENTER - SERIAL_ECHOLNPAIR_P(SP_Y_STR, m.obj_center.y); - #endif - SERIAL_ECHOLNPAIR_P(SP_Z_STR, m.obj_center.z); + 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); + 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_ECHOLNPAIR(" Left: ", m.backlash[LEFT]); - #endif - #if ENABLED(CALIBRATION_MEASURE_RIGHT) - SERIAL_ECHOLNPAIR(" 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_ECHOLNPAIR(" Front: ", m.backlash[FRONT]); - #endif - #if ENABLED(CALIBRATION_MEASURE_BACK) - SERIAL_ECHOLNPAIR(" 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_ECHOLNPAIR(" Top: ", m.backlash[TOP]); + SERIAL_ECHOLNPGM(" Top: ", m.backlash[TOP]); + #endif + #if AXIS_CAN_CALIBRATE(I) + 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) + 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) + 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) + 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) + 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) + 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(int(active_extruder)); + SERIAL_ECHO(active_extruder); SERIAL_ECHOLNPGM(" Positional Error:"); - #if HAS_X_CENTER - SERIAL_ECHOLNPAIR_P(SP_X_STR, m.pos_error.x); + #if HAS_X_CENTER && AXIS_CAN_CALIBRATE(X) + SERIAL_ECHOLNPGM_P(SP_X_STR, m.pos_error.x); #endif - #if HAS_Y_CENTER - SERIAL_ECHOLNPAIR_P(SP_Y_STR, m.pos_error.y); + #if HAS_Y_CENTER && AXIS_CAN_CALIBRATE(Y) + SERIAL_ECHOLNPGM_P(SP_Y_STR, m.pos_error.y); + #endif + #if AXIS_CAN_CALIBRATE(Z) + SERIAL_ECHOLNPGM_P(SP_Z_STR, m.pos_error.z); + #endif + #if HAS_I_CENTER && AXIS_CAN_CALIBRATE(I) + SERIAL_ECHOLNPGM_P(SP_I_STR, m.pos_error.i); + #endif + #if HAS_J_CENTER && AXIS_CAN_CALIBRATE(J) + SERIAL_ECHOLNPGM_P(SP_J_STR, m.pos_error.j); + #endif + #if HAS_K_CENTER && AXIS_CAN_CALIBRATE(K) + SERIAL_ECHOLNPGM_P(SP_K_STR, m.pos_error.k); + #endif + #if HAS_U_CENTER && AXIS_CAN_CALIBRATE(U) + SERIAL_ECHOLNPGM_P(SP_U_STR, m.pos_error.u); + #endif + #if HAS_V_CENTER && AXIS_CAN_CALIBRATE(V) + SERIAL_ECHOLNPGM_P(SP_V_STR, m.pos_error.v); + #endif + #if HAS_W_CENTER && AXIS_CAN_CALIBRATE(W) + SERIAL_ECHOLNPGM_P(SP_W_STR, m.pos_error.w); #endif - if (AXIS_CAN_CALIBRATE(Z)) SERIAL_ECHOLNPAIR_P(SP_Z_STR, m.pos_error.z); SERIAL_EOL(); } inline void report_measured_nozzle_dimensions(const measurements_t &m) { SERIAL_ECHOLNPGM("Nozzle Tip Outer Dimensions:"); - #if HAS_X_CENTER || HAS_Y_CENTER - #if HAS_X_CENTER - SERIAL_ECHOLNPAIR_P(SP_X_STR, m.nozzle_outer_dimension.x); - #endif - #if HAS_Y_CENTER - SERIAL_ECHOLNPAIR_P(SP_Y_STR, m.nozzle_outer_dimension.y); - #endif - #else - UNUSED(m); - #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); } #if HAS_HOTEND_OFFSET @@ -405,8 +567,8 @@ inline void probe_sides(measurements_t &m, const float uncertainty) { // This function requires normalize_hotend_offsets() to be called // inline void report_hotend_offsets() { - LOOP_S_L_N(e, 1, HOTENDS) - SERIAL_ECHOLNPAIR_P(PSTR("T"), int(e), PSTR(" Hotend Offset X"), hotend_offset[e].x, SP_Y_STR, hotend_offset[e].y, SP_Z_STR, hotend_offset[e].z); + 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); } #endif @@ -423,7 +585,7 @@ inline void calibrate_backlash(measurements_t &m, const float uncertainty) { { // New scope for TEMPORARY_BACKLASH_CORRECTION - TEMPORARY_BACKLASH_CORRECTION(all_off); + TEMPORARY_BACKLASH_CORRECTION(backlash.all_off); TEMPORARY_BACKLASH_SMOOTHING(0.0f); probe_sides(m, uncertainty); @@ -431,23 +593,72 @@ inline void calibrate_backlash(measurements_t &m, const float uncertainty) { #if ENABLED(BACKLASH_GCODE) #if HAS_X_CENTER - backlash.distance_mm.x = (m.backlash[LEFT] + m.backlash[RIGHT]) / 2; + backlash.set_distance_mm(X_AXIS, (m.backlash[LEFT] + m.backlash[RIGHT]) / 2); #elif ENABLED(CALIBRATION_MEASURE_LEFT) - backlash.distance_mm.x = m.backlash[LEFT]; + backlash.set_distance_mm(X_AXIS, m.backlash[LEFT]); #elif ENABLED(CALIBRATION_MEASURE_RIGHT) - backlash.distance_mm.x = m.backlash[RIGHT]; + backlash.set_distance_mm(X_AXIS, m.backlash[RIGHT]); #endif #if HAS_Y_CENTER - backlash.distance_mm.y = (m.backlash[FRONT] + m.backlash[BACK]) / 2; + backlash.set_distance_mm(Y_AXIS, (m.backlash[FRONT] + m.backlash[BACK]) / 2); #elif ENABLED(CALIBRATION_MEASURE_FRONT) - backlash.distance_mm.y = m.backlash[FRONT]; + backlash.set_distance_mm(Y_AXIS, m.backlash[FRONT]); #elif ENABLED(CALIBRATION_MEASURE_BACK) - backlash.distance_mm.y = m.backlash[BACK]; + backlash.set_distance_mm(Y_AXIS, m.backlash[BACK]); #endif - if (AXIS_CAN_CALIBRATE(Z)) backlash.distance_mm.z = m.backlash[TOP]; - #endif + TERN_(HAS_Z_AXIS, if (AXIS_CAN_CALIBRATE(Z)) backlash.set_distance_mm(Z_AXIS, m.backlash[TOP])); + + #if HAS_I_CENTER + backlash.set_distance_mm(I_AXIS, (m.backlash[IMINIMUM] + m.backlash[IMAXIMUM]) / 2); + #elif ENABLED(CALIBRATION_MEASURE_IMIN) + backlash.set_distance_mm(I_AXIS, m.backlash[IMINIMUM]); + #elif ENABLED(CALIBRATION_MEASURE_IMAX) + backlash.set_distance_mm(I_AXIS, m.backlash[IMAXIMUM]); + #endif + + #if HAS_J_CENTER + backlash.set_distance_mm(J_AXIS, (m.backlash[JMINIMUM] + m.backlash[JMAXIMUM]) / 2); + #elif ENABLED(CALIBRATION_MEASURE_JMIN) + backlash.set_distance_mm(J_AXIS, m.backlash[JMINIMUM]); + #elif ENABLED(CALIBRATION_MEASURE_JMAX) + backlash.set_distance_mm(J_AXIS, m.backlash[JMAXIMUM]); + #endif + + #if HAS_K_CENTER + backlash.set_distance_mm(K_AXIS, (m.backlash[KMINIMUM] + m.backlash[KMAXIMUM]) / 2); + #elif ENABLED(CALIBRATION_MEASURE_KMIN) + backlash.set_distance_mm(K_AXIS, m.backlash[KMINIMUM]); + #elif ENABLED(CALIBRATION_MEASURE_KMAX) + backlash.set_distance_mm(K_AXIS, m.backlash[KMAXIMUM]); + #endif + + #if HAS_U_CENTER + backlash.distance_mm.u = (m.backlash[UMINIMUM] + m.backlash[UMAXIMUM]) / 2; + #elif ENABLED(CALIBRATION_MEASURE_UMIN) + backlash.distance_mm.u = m.backlash[UMINIMUM]; + #elif ENABLED(CALIBRATION_MEASURE_UMAX) + backlash.distance_mm.u = m.backlash[UMAXIMUM]; + #endif + + #if HAS_V_CENTER + backlash.distance_mm.v = (m.backlash[VMINIMUM] + m.backlash[VMAXIMUM]) / 2; + #elif ENABLED(CALIBRATION_MEASURE_VMIN) + backlash.distance_mm.v = m.backlash[VMINIMUM]; + #elif ENABLED(CALIBRATION_MEASURE_UMAX) + backlash.distance_mm.v = m.backlash[VMAXIMUM]; + #endif + + #if HAS_W_CENTER + backlash.distance_mm.w = (m.backlash[WMINIMUM] + m.backlash[WMAXIMUM]) / 2; + #elif ENABLED(CALIBRATION_MEASURE_WMIN) + backlash.distance_mm.w = m.backlash[WMINIMUM]; + #elif ENABLED(CALIBRATION_MEASURE_WMAX) + backlash.distance_mm.w = m.backlash[WMAXIMUM]; + #endif + + #endif // BACKLASH_GCODE } #if ENABLED(BACKLASH_GCODE) @@ -455,9 +666,13 @@ inline void calibrate_backlash(measurements_t &m, const float uncertainty) { // allowed directions to take up any backlash { // New scope for TEMPORARY_BACKLASH_CORRECTION - TEMPORARY_BACKLASH_CORRECTION(all_on); + TEMPORARY_BACKLASH_CORRECTION(backlash.all_on); TEMPORARY_BACKLASH_SMOOTHING(0.0f); - const xyz_float_t move = { AXIS_CAN_CALIBRATE(X) * 3, AXIS_CAN_CALIBRATE(Y) * 3, AXIS_CAN_CALIBRATE(Z) * 3 }; + const xyz_float_t move = NUM_AXIS_ARRAY( + AXIS_CAN_CALIBRATE(X) * 3, AXIS_CAN_CALIBRATE(Y) * 3, AXIS_CAN_CALIBRATE(Z) * 3, + 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(); } @@ -482,14 +697,10 @@ inline void update_measurements(measurements_t &m, const AxisEnum axis) { * - Call calibrate_backlash() beforehand for best accuracy */ inline void calibrate_toolhead(measurements_t &m, const float uncertainty, const uint8_t extruder) { - TEMPORARY_BACKLASH_CORRECTION(all_on); + TEMPORARY_BACKLASH_CORRECTION(backlash.all_on); TEMPORARY_BACKLASH_SMOOTHING(0.0f); - #if HAS_MULTI_HOTEND - set_nozzle(m, extruder); - #else - UNUSED(extruder); - #endif + TERN(HAS_MULTI_HOTEND, set_nozzle(m, extruder), UNUSED(extruder)); probe_sides(m, uncertainty); @@ -508,6 +719,13 @@ inline void calibrate_toolhead(measurements_t &m, const float uncertainty, const if (ENABLED(HAS_Y_CENTER) && AXIS_CAN_CALIBRATE(Y)) update_measurements(m, Y_AXIS); if (AXIS_CAN_CALIBRATE(Z)) update_measurements(m, Z_AXIS); + TERN_(HAS_I_CENTER, update_measurements(m, I_AXIS)); + TERN_(HAS_J_CENTER, update_measurements(m, J_AXIS)); + TERN_(HAS_K_CENTER, update_measurements(m, K_AXIS)); + TERN_(HAS_U_CENTER, update_measurements(m, U_AXIS)); + TERN_(HAS_V_CENTER, update_measurements(m, V_AXIS)); + TERN_(HAS_W_CENTER, update_measurements(m, W_AXIS)); + sync_plan_position(); } @@ -519,7 +737,7 @@ inline void calibrate_toolhead(measurements_t &m, const float uncertainty, const * uncertainty in - How far away from the object to begin probing */ inline void calibrate_all_toolheads(measurements_t &m, const float uncertainty) { - TEMPORARY_BACKLASH_CORRECTION(all_on); + TEMPORARY_BACKLASH_CORRECTION(backlash.all_on); TEMPORARY_BACKLASH_SMOOTHING(0.0f); HOTEND_LOOP() calibrate_toolhead(m, uncertainty, e); @@ -535,7 +753,7 @@ inline void calibrate_all_toolheads(measurements_t &m, const float uncertainty) * 1) For each nozzle, touch top and sides of object to determine object position and * nozzle offsets. Do a fast but rough search over a wider area. * 2) With the first nozzle, touch top and sides of object to determine backlash values - * for all axis (if BACKLASH_GCODE is enabled) + * for all axes (if BACKLASH_GCODE is enabled) * 3) For each nozzle, touch top and sides of object slowly to determine precise * position of object. Adjust coordinate system and nozzle offsets so probed object * location corresponds to known object location with a high degree of precision. @@ -545,7 +763,7 @@ inline void calibrate_all() { TERN_(HAS_HOTEND_OFFSET, reset_hotend_offsets()); - TEMPORARY_BACKLASH_CORRECTION(all_on); + TEMPORARY_BACKLASH_CORRECTION(backlash.all_on); TEMPORARY_BACKLASH_SMOOTHING(0.0f); // Do a fast and rough calibration of the toolheads @@ -578,7 +796,7 @@ inline void calibrate_all() { void GcodeSuite::G425() { #ifdef CALIBRATION_SCRIPT_PRE - GcodeSuite::process_subcommands_now_P(PSTR(CALIBRATION_SCRIPT_PRE)); + process_subcommands_now(F(CALIBRATION_SCRIPT_PRE)); #endif if (homing_needed_error()) return; @@ -587,12 +805,12 @@ void GcodeSuite::G425() { SET_SOFT_ENDSTOP_LOOSE(true); measurements_t m; - float uncertainty = parser.seenval('U') ? parser.value_float() : CALIBRATION_MEASUREMENT_UNCERTAIN; + const float uncertainty = parser.floatval('U', CALIBRATION_MEASUREMENT_UNCERTAIN); - if (parser.seen('B')) + if (parser.seen_test('B')) calibrate_backlash(m, uncertainty); - else if (parser.seen('T')) - calibrate_toolhead(m, uncertainty, parser.has_value() ? parser.value_int() : active_extruder); + else if (parser.seen_test('T')) + calibrate_toolhead(m, uncertainty, parser.intval('T', active_extruder)); #if ENABLED(CALIBRATION_REPORTING) else if (parser.seen('V')) { probe_sides(m, uncertainty); @@ -614,7 +832,7 @@ void GcodeSuite::G425() { SET_SOFT_ENDSTOP_LOOSE(false); #ifdef CALIBRATION_SCRIPT_POST - GcodeSuite::process_subcommands_now_P(PSTR(CALIBRATION_SCRIPT_POST)); + process_subcommands_now(F(CALIBRATION_SCRIPT_POST)); #endif } diff --git a/Marlin/src/gcode/calibrate/G76_M192_M871.cpp b/Marlin/src/gcode/calibrate/G76_M192_M871.cpp deleted file mode 100644 index 89393b4582..0000000000 --- a/Marlin/src/gcode/calibrate/G76_M192_M871.cpp +++ /dev/null @@ -1,357 +0,0 @@ -/** - * Marlin 3D Printer Firmware - * Copyright (c) 2020 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 . - * - */ - -/** - * G76_M871.cpp - Temperature calibration/compensation for z-probing - */ - -#include "../../inc/MarlinConfig.h" - -#if ENABLED(PROBE_TEMP_COMPENSATION) - -#include "../gcode.h" -#include "../../module/motion.h" -#include "../../module/planner.h" -#include "../../module/probe.h" -#include "../../feature/bedlevel/bedlevel.h" -#include "../../module/temperature.h" -#include "../../module/probe.h" -#include "../../feature/probe_temp_comp.h" - -#include "../../lcd/ultralcd.h" -#include "../../MarlinCore.h" // for wait_for_heatup and idle() - -#if ENABLED(PRINTJOB_TIMER_AUTOSTART) - #include "../../module/printcounter.h" -#endif - -#if ENABLED(PRINTER_EVENTS_LEDS) - #include "../../feature/leds/leds.h" -#endif - -/** - * 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. Alternativly you could just - * make sure the fan is not running while running the calibration process. - * - * 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. - * - * 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. - * - * G76 [B | P] - * - no flag - Both calibration procedures will be run. - * - `B` - Run bed temperature calibration. - * - `P` - Run probe temperature calibration. - */ -void GcodeSuite::G76() { - // Check if heated bed is available and z-homing is done with probe - #if TEMP_SENSOR_BED == 0 || !(HOMING_Z_WITH_PROBE) - return; - #endif - - auto report_temps = [](millis_t &ntr, millis_t timeout=0) { - idle_no_sleep(); - const millis_t ms = millis(); - if (ELAPSED(ms, ntr)) { - ntr = ms + 1000; - thermalManager.print_heater_states(active_extruder); - } - return (timeout && ELAPSED(ms, timeout)); - }; - - auto wait_for_temps = [&](const float tb, const float tp, millis_t &ntr, const millis_t timeout=0) { - SERIAL_ECHOLNPGM("Waiting for bed and probe temperature."); - while (fabs(thermalManager.degBed() - tb) > 0.1f || thermalManager.degProbe() > tp) - if (report_temps(ntr, timeout)) return true; - return false; - }; - - auto g76_probe = [](const TempSensorID sid, uint16_t &targ, const xy_pos_t &nozpos) { - do_z_clearance(5.0); // Raise nozzle before probing - const float measured_z = probe.probe_at_point(nozpos, PROBE_PT_STOW, 0, false); // verbose=0, probe_relative=false - if (isnan(measured_z)) - SERIAL_ECHOLNPGM("!Received NAN. Aborting."); - else { - SERIAL_ECHOLNPAIR_F("Measured: ", measured_z); - if (targ == cali_info_init[sid].start_temp) - temp_comp.prepare_new_calibration(measured_z); - else - temp_comp.push_back_new_measurement(sid, measured_z); - targ += cali_info_init[sid].temp_res; - } - return measured_z; - }; - - #if ENABLED(BLTOUCH) - // Make sure any BLTouch error condition is cleared - bltouch_command(BLTOUCH_RESET, BLTOUCH_RESET_DELAY); - set_bltouch_deployed(false); - #endif - - bool do_bed_cal = parser.boolval('B'), do_probe_cal = parser.boolval('P'); - if (!do_bed_cal && !do_probe_cal) do_bed_cal = do_probe_cal = true; - - // Synchronize with planner - planner.synchronize(); - - const xyz_pos_t parkpos = temp_comp.park_point, - probe_pos_xyz = xyz_pos_t(temp_comp.measure_point) + xyz_pos_t({ 0.0f, 0.0f, PTC_PROBE_HEATING_OFFSET }), - noz_pos_xyz = probe_pos_xyz - xy_pos_t(probe.offset_xy); // Nozzle position based on probe position - - 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); - if (!reachable) - SERIAL_ECHOLNPGM("!Park"); - else { - // Ensure probe position is reachable - reachable = probe.can_reach(probe_pos_xyz); - if (!reachable) SERIAL_ECHOLNPGM("!Probe"); - } - - if (!reachable) { - SERIAL_ECHOLNPGM(" position unreachable - aborting."); - return; - } - - process_subcommands_now_P(PSTR("G28")); - } - - remember_feedrate_scaling_off(); - - - /****************************************** - * Calibrate bed temperature offsets - ******************************************/ - - // Report temperatures every second and handle heating timeouts - millis_t next_temp_report = millis() + 1000; - - auto report_targets = [&](const uint16_t tb, const uint16_t tp) { - SERIAL_ECHOLNPAIR("Target Bed:", tb, " Probe:", tp); - }; - - if (do_bed_cal) { - - uint16_t target_bed = cali_info_init[TSI_BED].start_temp, - target_probe = temp_comp.bed_calib_probe_temp; - - SERIAL_ECHOLNPGM("Waiting for cooling."); - while (thermalManager.degBed() > target_bed || thermalManager.degProbe() > target_probe) - report_temps(next_temp_report); - - // Disable leveling so it won't mess with us - TERN_(HAS_LEVELING, set_bed_leveling_enabled(false)); - - for (;;) { - thermalManager.setTargetBed(target_bed); - - report_targets(target_bed, target_probe); - - // Park nozzle - do_blocking_move_to(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))) { - SERIAL_ECHOLNPGM("!Bed heating timeout."); - break; - } - - // Move the nozzle to the probing point and wait for the probe to reach target temp - do_blocking_move_to(noz_pos_xyz); - SERIAL_ECHOLNPGM("Waiting for probe heating."); - while (thermalManager.degProbe() < target_probe) - report_temps(next_temp_report); - - const float measured_z = g76_probe(TSI_BED, target_bed, noz_pos_xyz); - if (isnan(measured_z) || target_bed > BED_MAX_TARGET) break; - } - - SERIAL_ECHOLNPAIR("Retrieved measurements: ", temp_comp.get_index()); - if (temp_comp.finish_calibration(TSI_BED)) - SERIAL_ECHOLNPGM("Successfully calibrated bed."); - else - SERIAL_ECHOLNPGM("!Failed to calibrate bed. Values reset."); - - // Cleanup - thermalManager.setTargetBed(0); - TERN_(HAS_LEVELING, set_bed_leveling_enabled(true)); - } // do_bed_cal - - /******************************************** - * Calibrate probe temperature offsets - ********************************************/ - - if (do_probe_cal) { - - // Park nozzle - do_blocking_move_to(parkpos); - - // Initialize temperatures - const uint16_t target_bed = temp_comp.probe_calib_bed_temp; - thermalManager.setTargetBed(target_bed); - - uint16_t target_probe = cali_info_init[TSI_PROBE].start_temp; - - report_targets(target_bed, target_probe); - - // Wait for heatbed to reach target temp and probe to cool below target temp - wait_for_temps(target_bed, target_probe, next_temp_report); - - // Disable leveling so it won't mess with us - TERN_(HAS_LEVELING, set_bed_leveling_enabled(false)); - - bool timeout = false; - for (;;) { - // Move probe to probing point and wait for it to reach target temperature - do_blocking_move_to(noz_pos_xyz); - - SERIAL_ECHOLNPAIR("Waiting for probe heating. Bed:", target_bed, " Probe:", target_probe); - const millis_t probe_timeout_ms = millis() + 900UL * 1000UL; - while (thermalManager.degProbe() < target_probe) { - if (report_temps(next_temp_report, probe_timeout_ms)) { - SERIAL_ECHOLNPGM("!Probe heating timed out."); - timeout = true; - break; - } - } - if (timeout) break; - - const float measured_z = g76_probe(TSI_PROBE, target_probe, noz_pos_xyz); - if (isnan(measured_z) || target_probe > cali_info_init[TSI_PROBE].end_temp) break; - } - - SERIAL_ECHOLNPAIR("Retrieved measurements: ", temp_comp.get_index()); - if (temp_comp.finish_calibration(TSI_PROBE)) - SERIAL_ECHOPGM("Successfully calibrated"); - else - SERIAL_ECHOPGM("!Failed to calibrate"); - SERIAL_ECHOLNPGM(" probe."); - - // Cleanup - thermalManager.setTargetBed(0); - TERN_(HAS_LEVELING, set_bed_leveling_enabled(true)); - - SERIAL_ECHOLNPGM("Final compensation values:"); - temp_comp.print_offsets(); - } // do_probe_cal - - restore_feedrate_and_scaling(); -} - -/** - * M871: Report / reset temperature compensation offsets. - * Note: This does not affect values in EEPROM until M500. - * - * M871 [ R | B | P | E ] - * - * No Parameters - 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 - * - * With B, P, or E: - * I[index] - Index in the array - * V[value] - Adjustment in Β΅m - */ -void GcodeSuite::M871() { - - if (parser.seen('R')) { - // Reset z-probe offsets to factory defaults - temp_comp.clear_all_offsets(); - SERIAL_ECHOLNPGM("Offsets reset to default."); - } - else if (parser.seen("BPE")) { - if (!parser.seenval('V')) return; - const int16_t offset_val = parser.value_int(); - if (!parser.seenval('I')) return; - const int16_t idx = parser.value_int(); - const TempSensorID mod = (parser.seen('B') ? TSI_BED : - #if ENABLED(USE_TEMP_EXT_COMPENSATION) - parser.seen('E') ? TSI_EXT : - #endif - TSI_PROBE - ); - if (idx > 0 && temp_comp.set_offset(mod, idx - 1, offset_val)) - SERIAL_ECHOLNPAIR("Set value: ", offset_val); - else - SERIAL_ECHOLNPGM("!Invalid index. Failed to set value (note: value at index 0 is constant)."); - - } - else // Print current Z-probe adjustments. Note: Values in EEPROM might differ. - temp_comp.print_offsets(); -} - -/** - * M192: Wait for probe temperature sensor to reach a target - * - * Select only one of these flags: - * R - Wait for heating or cooling - * S - Wait only for heating - */ -void GcodeSuite::M192() { - if (DEBUGGING(DRYRUN)) return; - - const bool no_wait_for_cooling = parser.seenval('S'); - if (!no_wait_for_cooling && ! parser.seenval('R')) { - SERIAL_ERROR_MSG("No target temperature set."); - return; - } - - const float target_temp = parser.value_celsius(); - ui.set_status_P(thermalManager.isProbeBelowTemp(target_temp) ? GET_TEXT(MSG_PROBE_HEATING) : GET_TEXT(MSG_PROBE_COOLING)); - thermalManager.wait_for_probe(target_temp, no_wait_for_cooling); -} - -#endif // PROBE_TEMP_COMPENSATION diff --git a/Marlin/src/gcode/calibrate/G76_M871.cpp b/Marlin/src/gcode/calibrate/G76_M871.cpp new file mode 100644 index 0000000000..35f7ac3174 --- /dev/null +++ b/Marlin/src/gcode/calibrate/G76_M871.cpp @@ -0,0 +1,347 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2020 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 . + * + */ + +/** + * G76_M871.cpp - Temperature calibration/compensation for z-probing + */ + +#include "../../inc/MarlinConfig.h" + +#if HAS_PTC + +#include "../gcode.h" +#include "../../module/motion.h" +#include "../../module/planner.h" +#include "../../module/probe.h" +#include "../../feature/bedlevel/bedlevel.h" +#include "../../module/temperature.h" +#include "../../feature/probe_temp_comp.h" +#include "../../lcd/marlinui.h" + +/** + * G76: Probe Temperature Calibration + * + * Calibrate probe and/or bed temperature offsets. + * + * 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. + * + * 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) + + static void say_waiting_for() { SERIAL_ECHOPGM("Waiting for "); } + static void say_waiting_for_probe_heating() { say_waiting_for(); SERIAL_ECHOLNPGM("probe heating."); } + static void say_successfully_calibrated() { SERIAL_ECHOPGM("Successfully calibrated"); } + static void say_failed_to_calibrate() { SERIAL_ECHOPGM("!Failed to calibrate"); } + + void GcodeSuite::G76() { + auto report_temps = [](millis_t &ntr, millis_t timeout=0) { + marlin.idle_no_sleep(); + const millis_t ms = millis(); + if (ELAPSED(ms, ntr)) { + ntr = ms + 1000; + thermalManager.print_heater_states(active_extruder); + } + return (timeout && ELAPSED(ms, timeout)); + }; + + auto wait_for_temps = [&](const celsius_t tb, const celsius_t tp, millis_t &ntr, const millis_t timeout=0) { + say_waiting_for(); SERIAL_ECHOLNPGM("bed and probe temperature."); + while (thermalManager.wholeDegBed() != tb || thermalManager.wholeDegProbe() > tp) + if (report_temps(ntr, timeout)) return true; + return false; + }; + + auto g76_probe = [](const TempSensorID sid, celsius_t &targ, const xy_pos_t &nozpos) { + ptc.set_enabled(false); + const float measured_z = probe.probe_at_point(nozpos, PROBE_PT_STOW, 0, false); // verbose=0, probe_relative=false + ptc.set_enabled(true); + if (isnan(measured_z)) + SERIAL_ECHOLNPGM("!Received NAN. Aborting."); + else { + SERIAL_ECHOLNPGM("Measured: ", p_float_t(measured_z, 2)); + if (targ == ProbeTempComp::cali_info[sid].start_temp) + ptc.prepare_new_calibration(measured_z); + else + ptc.push_back_new_measurement(sid, measured_z); + targ += ProbeTempComp::cali_info[sid].temp_resolution; + } + return measured_z; + }; + + #if ENABLED(BLTOUCH) + // Make sure any BLTouch error condition is cleared + bltouch_command(BLTOUCH_RESET, BLTOUCH_RESET_DELAY); + set_bltouch_deployed(false); + #endif + + bool do_bed_cal = parser.boolval('B'), do_probe_cal = parser.boolval('P'); + if (!do_bed_cal && !do_probe_cal) do_bed_cal = do_probe_cal = true; + + // Synchronize with planner + planner.synchronize(); + + #ifndef PTC_PROBE_HEATING_OFFSET + #define PTC_PROBE_HEATING_OFFSET 0 + #endif + const xyz_pos_t parkpos = PTC_PARK_POS, + probe_pos_xyz = xyz_pos_t(PTC_PROBE_POS) + xyz_pos_t({ 0.0f, 0.0f, PTC_PROBE_HEATING_OFFSET }), + noz_pos_xyz = probe_pos_xyz - probe.offset_xy; // Nozzle position based on probe position + + 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); + if (!reachable) + SERIAL_ECHOLNPGM("!Park"); + else { + // Ensure probe position is reachable + reachable = probe.can_reach(probe_pos_xyz); + if (!reachable) SERIAL_ECHOLNPGM("!Probe"); + } + + if (!reachable) { + SERIAL_ECHOLNPGM(" position unreachable - aborting."); + return; + } + + process_subcommands_now(FPSTR(G28_STR)); + } + + remember_feedrate_scaling_off(); + + /****************************************** + * Calibrate bed temperature offsets + ******************************************/ + + // Report temperatures every second and handle heating timeouts + millis_t next_temp_report = millis() + 1000; + + auto report_targets = [&](const celsius_t tb, const celsius_t tp) { + SERIAL_ECHOLNPGM("Target Bed:", tb, " Probe:", tp); + }; + + if (do_bed_cal) { + + celsius_t target_bed = PTC_BED_START, + target_probe = PTC_PROBE_TEMP; + + say_waiting_for(); SERIAL_ECHOLNPGM(" cooling."); + while (thermalManager.wholeDegBed() > target_bed || thermalManager.wholeDegProbe() > target_probe) + report_temps(next_temp_report); + + // Disable leveling so it won't mess with us + TERN_(HAS_LEVELING, set_bed_leveling_enabled(false)); + + for (uint8_t idx = 0; idx <= PTC_BED_COUNT; idx++) { + thermalManager.setTargetBed(target_bed); + + report_targets(target_bed, target_probe); + + // Park nozzle + do_blocking_move_to(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))) { + SERIAL_ECHOLNPGM("!Bed heating timeout."); + break; + } + + // Move the nozzle to the probing point and wait for the probe to reach target temp + do_blocking_move_to(noz_pos_xyz); + say_waiting_for_probe_heating(); + SERIAL_EOL(); + while (thermalManager.wholeDegProbe() < target_probe) + report_temps(next_temp_report); + + const float measured_z = g76_probe(TSI_BED, target_bed, noz_pos_xyz); + if (isnan(measured_z) || target_bed > (BED_MAX_TARGET)) break; + } + + SERIAL_ECHOLNPGM("Retrieved measurements: ", ptc.get_index()); + if (ptc.finish_calibration(TSI_BED)) { + say_successfully_calibrated(); + SERIAL_ECHOLNPGM(" bed."); + } + else { + say_failed_to_calibrate(); + SERIAL_ECHOLNPGM(" bed. Values reset."); + } + + // Cleanup + thermalManager.setTargetBed(0); + TERN_(HAS_LEVELING, set_bed_leveling_enabled(true)); + } // do_bed_cal + + /******************************************** + * Calibrate probe temperature offsets + ********************************************/ + + if (do_probe_cal) { + + // Park nozzle + do_blocking_move_to(parkpos); + + // Initialize temperatures + const celsius_t target_bed = BED_MAX_TARGET; + thermalManager.setTargetBed(target_bed); + + celsius_t target_probe = PTC_PROBE_START; + + report_targets(target_bed, target_probe); + + // Wait for heatbed to reach target temp and probe to cool below target temp + wait_for_temps(target_bed, target_probe, next_temp_report); + + // Disable leveling so it won't mess with us + TERN_(HAS_LEVELING, set_bed_leveling_enabled(false)); + + 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); + + say_waiting_for_probe_heating(); + SERIAL_ECHOLNPGM(" Bed:", target_bed, " Probe:", target_probe); + const millis_t probe_timeout_ms = millis() + MIN_TO_MS(15); + while (thermalManager.degProbe() < target_probe) { + if (report_temps(next_temp_report, probe_timeout_ms)) { + SERIAL_ECHOLNPGM("!Probe heating timed out."); + timeout = true; + break; + } + } + if (timeout) break; + + const float measured_z = g76_probe(TSI_PROBE, target_probe, noz_pos_xyz); + if (isnan(measured_z)) break; + } + + SERIAL_ECHOLNPGM("Retrieved measurements: ", ptc.get_index()); + if (ptc.finish_calibration(TSI_PROBE)) + say_successfully_calibrated(); + else + say_failed_to_calibrate(); + SERIAL_ECHOLNPGM(" probe."); + + // Cleanup + thermalManager.setTargetBed(0); + TERN_(HAS_LEVELING, set_bed_leveling_enabled(true)); + + SERIAL_ECHOLNPGM("Final compensation values:"); + ptc.print_offsets(); + } // do_probe_cal + + restore_feedrate_and_scaling(); + } + +#endif // PTC_PROBE && PTC_BED + +/** + * 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 ] + * + * 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 + * + * With B, P, or E: + * I Index in the array + * V Adjustment in Β΅m + */ +void GcodeSuite::M871() { + + if (parser.seen_test('R')) { + // Reset z-probe offsets to factory defaults + ptc.clear_all_offsets(); + SERIAL_ECHOLNPGM("Offsets reset to default."); + } + else if (parser.seen("BPE")) { + if (!parser.seenval('V')) return; + const int16_t offset_val = parser.value_int(); + if (!parser.seenval('I')) return; + const int16_t idx = parser.value_int(); + const TempSensorID mod = TERN_(PTC_BED, parser.seen_test('B') ? TSI_BED :) + TERN_(PTC_HOTEND, parser.seen_test('E') ? TSI_EXT :) + TERN_(PTC_PROBE, parser.seen_test('P') ? TSI_PROBE :) TSI_COUNT; + if (mod == TSI_COUNT) + SERIAL_ECHOLNPGM("!Invalid sensor."); + else if (idx > 0 && ptc.set_offset(mod, idx - 1, offset_val)) + SERIAL_ECHOLNPGM("Set value: ", offset_val); + else + SERIAL_ECHOLNPGM("!Invalid index. Failed to set value (note: value at index 0 is constant)."); + } + else // Print current Z-probe adjustments. Note: Values in EEPROM might differ. + ptc.print_offsets(); +} + +#endif // HAS_PTC diff --git a/Marlin/src/gcode/calibrate/M100.cpp b/Marlin/src/gcode/calibrate/M100.cpp index 9ac2380e79..822ce969ec 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(PGM_P const title, const char * const start, const char * const end); + * 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 EITHER(__AVR__, IS_32BIT_TEENSY) +#if ANY(__AVR__, IS_32BIT_TEENSY) && !IS_TEENSY_40_41 extern char __bss_end; char *end_bss = &__bss_end, @@ -151,7 +149,7 @@ inline int32_t count_test_bytes(const char * const start_free_memory) { * the block. If so, it may indicate memory corruption due to a bad pointer. * Unexpected bytes are flagged in the right column. */ - inline void dump_free_memory(char *start_free_memory, char *end_free_memory) { + void dump_free_memory(char *start_free_memory, char *end_free_memory) { // // Start and end the dump on a nice 16 byte boundary // (even though the values are not 16-byte aligned). @@ -163,14 +161,14 @@ inline int32_t count_test_bytes(const char * const start_free_memory) { while (start_free_memory < end_free_memory) { print_hex_address(start_free_memory); // Print the address SERIAL_CHAR(':'); - LOOP_L_N(i, 16) { // and 16 data bytes + for (uint8_t i = 0; i < 16; ++i) { // and 16 data bytes if (i == 8) SERIAL_CHAR('-'); print_hex_byte(start_free_memory[i]); SERIAL_CHAR(' '); } serial_delay(25); SERIAL_CHAR('|'); // Point out non test bytes - LOOP_L_N(i, 16) { + for (uint8_t i = 0; i < 16; ++i) { char ccc = (char)start_free_memory[i]; // cast to char before automatically casting to char on assignment, in case the compiler is broken ccc = (ccc == TEST_BYTE) ? ' ' : '?'; SERIAL_CHAR(ccc); @@ -178,16 +176,16 @@ inline int32_t count_test_bytes(const char * const start_free_memory) { SERIAL_EOL(); start_free_memory += 16; serial_delay(25); - idle(); + marlin.idle(); } } - void M100_dump_routine(PGM_P const title, const char * const start, const char * const end) { - serialprintPGM(title); - SERIAL_EOL(); + void M100_dump_routine(FSTR_P const title, const char * const start, const uintptr_t size) { + SERIAL_ECHOLN(title); // // Round the start and end locations to produce full lines of output // + const char * const end = start + size - 1; dump_free_memory( (char*)(uintptr_t(uint32_t(start) & ~0xFUL)), // Align to 16-byte boundary (char*)(uintptr_t(uint32_t(end) | 0xFUL)) // Align end_free_memory to the 15th byte (at or above end_free_memory) @@ -196,28 +194,28 @@ inline int32_t count_test_bytes(const char * const start_free_memory) { #endif // M100_FREE_MEMORY_DUMPER -inline int check_for_free_memory_corruption(PGM_P const title) { - serialprintPGM(title); +inline int check_for_free_memory_corruption(FSTR_P const title) { + SERIAL_ECHO(title); char *start_free_memory = free_memory_start, *end_free_memory = free_memory_end; int n = end_free_memory - start_free_memory; - SERIAL_ECHOPAIR("\nfmc() n=", n); - SERIAL_ECHOPAIR("\nfree_memory_start=", hex_address(free_memory_start)); - SERIAL_ECHOLNPAIR(" end_free_memory=", hex_address(end_free_memory)); + SERIAL_ECHOLNPGM("\nfmc() n=", n, + "\nfree_memory_start=", hex_address(free_memory_start), + " end=", hex_address(end_free_memory)); 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. - // while ( !READ(63)) - // idle(); + //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 + // marlin.idle(); // being on pin-63 which is unassigend and available on most controller + //safe_delay(20); // boards. + //while ( !READ(63)) + // marlin.idle(); serial_delay(20); #if ENABLED(M100_FREE_MEMORY_DUMPER) - M100_dump_routine(PSTR(" Memory corruption detected with end_free_memory 8) { - // SERIAL_ECHOPAIR("Found ", j); - // SERIAL_ECHOLNPAIR(" bytes free at ", hex_address(start_free_memory + i)); + //SERIAL_ECHOPGM("Found ", j); + //SERIAL_ECHOLNPGM(" bytes free at ", hex_address(start_free_memory + i)); i += j; block_cnt++; - SERIAL_ECHOPAIR(" (", block_cnt); - SERIAL_ECHOPAIR(") found=", j); - SERIAL_ECHOLNPGM(" "); + SERIAL_ECHOLNPGM(" (", block_cnt, ") found=", j); } } } - SERIAL_ECHOPAIR(" block_found=", block_cnt); + SERIAL_ECHOPGM(" block_found=", block_cnt); if (block_cnt != 1) SERIAL_ECHOLNPGM("\nMemory Corruption detected in free memory area."); @@ -269,8 +265,7 @@ inline void free_memory_pool_report(char * const start_free_memory, const int32_ if (*addr == TEST_BYTE) { const int32_t j = count_test_bytes(addr); if (j > 8) { - SERIAL_ECHOPAIR("Found ", j); - SERIAL_ECHOLNPAIR(" bytes free at ", hex_address(addr)); + SERIAL_ECHOLNPGM("Found ", j, " bytes free at ", hex_address(addr)); if (j > max_cnt) { max_cnt = j; max_addr = addr; @@ -280,12 +275,11 @@ inline void free_memory_pool_report(char * const start_free_memory, const int32_ } } } - if (block_cnt > 1) { - SERIAL_ECHOLNPGM("\nMemory Corruption detected in free memory area."); - SERIAL_ECHOPAIR("\nLargest free block is ", max_cnt); - SERIAL_ECHOLNPAIR(" bytes at ", hex_address(max_addr)); - } - SERIAL_ECHOLNPAIR("check_for_free_memory_corruption() = ", check_for_free_memory_corruption(PSTR("M100 F "))); + if (block_cnt > 1) SERIAL_ECHOLNPGM( + "\nMemory Corruption detected in free memory area." + "\nLargest free block is ", max_cnt, " bytes at ", hex_address(max_addr) + ); + SERIAL_ECHOLNPGM("check_for_free_memory_corruption() = ", check_for_free_memory_corruption(F("M100 F "))); } #if ENABLED(M100_FREE_MEMORY_CORRUPTOR) @@ -294,16 +288,16 @@ inline void free_memory_pool_report(char * const start_free_memory, const int32_ * Corrupt locations in the free memory pool and report the corrupt addresses. * This is useful to check the correctness of the M100 D and the M100 F commands. */ - inline void corrupt_free_memory(char *start_free_memory, const uint32_t size) { + inline void corrupt_free_memory(char *start_free_memory, const uintptr_t size) { start_free_memory += 8; const uint32_t near_top = top_of_stack() - start_free_memory - 250, // -250 to avoid interrupt activity that's altered the stack. j = near_top / (size + 1); - SERIAL_ECHOLNPGM("Corrupting free memory block.\n"); + SERIAL_ECHOLNPGM("Corrupting free memory block."); for (uint32_t i = 1; i <= size; i++) { char * const addr = start_free_memory + i * j; *addr = i; - SERIAL_ECHOPAIR("\nCorrupting address: ", hex_address(addr)); + SERIAL_ECHOPGM("\nCorrupting address: ", hex_address(addr)); } SERIAL_EOL(); } @@ -322,8 +316,8 @@ inline void init_free_memory(char *start_free_memory, int32_t size) { return; } - start_free_memory += 8; // move a few bytes away from the heap just because we don't want - // to be altering memory that close to it. + start_free_memory += 8; // move a few bytes away from the heap just because we + // don't want to be altering memory that close to it. memset(start_free_memory, TEST_BYTE, size); SERIAL_ECHO(size); @@ -331,8 +325,8 @@ inline void init_free_memory(char *start_free_memory, int32_t size) { for (int32_t i = 0; i < size; i++) { if (start_free_memory[i] != TEST_BYTE) { - SERIAL_ECHOPAIR("? address : ", hex_address(start_free_memory + i)); - SERIAL_ECHOLNPAIR("=", hex_byte(start_free_memory[i])); + SERIAL_ECHOPGM("? address : ", hex_address(start_free_memory + i)); + SERIAL_ECHOLNPGM("=", hex_byte(start_free_memory[i])); SERIAL_EOL(); } } @@ -342,16 +336,16 @@ inline void init_free_memory(char *start_free_memory, int32_t size) { * M100: Free Memory Check */ void GcodeSuite::M100() { - char *sp = top_of_stack(); if (!free_memory_end) free_memory_end = sp - MEMORY_END_CORRECTION; - SERIAL_ECHOPAIR("\nbss_end : ", hex_address(end_bss)); - if (heaplimit) SERIAL_ECHOPAIR("\n__heaplimit : ", hex_address(heaplimit)); - SERIAL_ECHOPAIR("\nfree_memory_start : ", hex_address(free_memory_start)); - if (stacklimit) SERIAL_ECHOPAIR("\n__stacklimit : ", hex_address(stacklimit)); - SERIAL_ECHOPAIR("\nfree_memory_end : ", hex_address(free_memory_end)); - if (MEMORY_END_CORRECTION) SERIAL_ECHOPAIR("\nMEMORY_END_CORRECTION: ", MEMORY_END_CORRECTION); - SERIAL_ECHOLNPAIR("\nStack Pointer : ", hex_address(sp)); + SERIAL_ECHOPGM("\nbss_end : ", hex_address(end_bss)); + if (heaplimit) SERIAL_ECHOPGM("\n__heaplimit : ", hex_address(heaplimit)); + SERIAL_ECHOPGM("\nfree_memory_start : ", hex_address(free_memory_start)); + if (stacklimit) SERIAL_ECHOPGM("\n__stacklimit : ", hex_address(stacklimit)); + SERIAL_ECHOPGM("\nfree_memory_end : ", hex_address(free_memory_end)); + if (MEMORY_END_CORRECTION) + SERIAL_ECHOPGM("\nMEMORY_END_CORRECTION : ", MEMORY_END_CORRECTION); + SERIAL_ECHOLNPGM("\nStack Pointer : ", hex_address(sp)); // Always init on the first invocation of M100 static bool m100_not_initialized = true; @@ -369,10 +363,8 @@ void GcodeSuite::M100() { return free_memory_pool_report(free_memory_start, free_memory_end - free_memory_start); #if ENABLED(M100_FREE_MEMORY_CORRUPTOR) - if (parser.seen('C')) return corrupt_free_memory(free_memory_start, parser.value_int()); - #endif } diff --git a/Marlin/src/gcode/calibrate/M12.cpp b/Marlin/src/gcode/calibrate/M12.cpp index da24454375..191ff22d7d 100644 --- a/Marlin/src/gcode/calibrate/M12.cpp +++ b/Marlin/src/gcode/calibrate/M12.cpp @@ -19,6 +19,7 @@ * along with this program. If not, see . * */ + #include "../../inc/MarlinConfigPre.h" #if ENABLED(EXTERNAL_CLOSED_LOOP_CONTROLLER) diff --git a/Marlin/src/gcode/calibrate/M425.cpp b/Marlin/src/gcode/calibrate/M425.cpp index 40441ac08d..5dab92a307 100644 --- a/Marlin/src/gcode/calibrate/M425.cpp +++ b/Marlin/src/gcode/calibrate/M425.cpp @@ -46,19 +46,19 @@ void GcodeSuite::M425() { bool noArgs = true; - auto axis_can_calibrate = [](const uint8_t a) { + auto axis_can_calibrate = [](const uint8_t a) -> bool { + #define _CAN_CASE(N) case N##_AXIS: return bool(AXIS_CAN_CALIBRATE(N)); switch (a) { - default: - case X_AXIS: return AXIS_CAN_CALIBRATE(X); - case Y_AXIS: return AXIS_CAN_CALIBRATE(Y); - case Z_AXIS: return AXIS_CAN_CALIBRATE(Z); + MAIN_AXIS_MAP(_CAN_CASE) + default: break; } + return false; }; - LOOP_XYZ(a) { - if (axis_can_calibrate(a) && parser.seen(XYZ_CHAR(a))) { + LOOP_NUM_AXES(a) { + if (axis_can_calibrate(a) && parser.seen(AXIS_CHAR(a))) { planner.synchronize(); - backlash.distance_mm[a] = parser.has_value() ? parser.value_linear_units() : backlash.get_measurement(AxisEnum(a)); + backlash.set_distance_mm((AxisEnum)a, parser.has_value() ? parser.value_axis_units((AxisEnum)a) : backlash.get_measurement((AxisEnum)a)); noArgs = false; } } @@ -72,33 +72,30 @@ void GcodeSuite::M425() { #ifdef BACKLASH_SMOOTHING_MM if (parser.seen('S')) { planner.synchronize(); - backlash.smoothing_mm = parser.value_linear_units(); + backlash.set_smoothing_mm(parser.value_linear_units()); noArgs = false; } #endif if (noArgs) { SERIAL_ECHOPGM("Backlash Correction "); - if (!backlash.correction) SERIAL_ECHOPGM("in"); + if (!backlash.get_correction_uint8()) SERIAL_ECHOPGM("in"); SERIAL_ECHOLNPGM("active:"); - SERIAL_ECHOLNPAIR(" Correction Amount/Fade-out: F", backlash.get_correction(), " (F1.0 = full, F0.0 = none)"); + SERIAL_ECHOLNPGM(" Correction Amount/Fade-out: F", backlash.get_correction(), " (F1.0 = full, F0.0 = none)"); SERIAL_ECHOPGM(" Backlash Distance (mm): "); - LOOP_XYZ(a) if (axis_can_calibrate(a)) { - SERIAL_CHAR(' ', XYZ_CHAR(a)); - SERIAL_ECHO(backlash.distance_mm[a]); - SERIAL_EOL(); + LOOP_NUM_AXES(a) if (axis_can_calibrate(a)) { + SERIAL_ECHOLNPGM_P((PGM_P)pgm_read_ptr(&SP_AXIS_STR[a]), backlash.get_distance_mm((AxisEnum)a)); } #ifdef BACKLASH_SMOOTHING_MM - SERIAL_ECHOLNPAIR(" Smoothing (mm): S", backlash.smoothing_mm); + SERIAL_ECHOLNPGM(" Smoothing (mm): S", backlash.get_smoothing_mm()); #endif #if ENABLED(MEASURE_BACKLASH_WHEN_PROBING) SERIAL_ECHOPGM(" Average measured backlash (mm):"); if (backlash.has_any_measurement()) { - LOOP_XYZ(a) if (axis_can_calibrate(a) && backlash.has_measurement(AxisEnum(a))) { - SERIAL_CHAR(' ', XYZ_CHAR(a)); - SERIAL_ECHO(backlash.get_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)); } } else @@ -108,4 +105,30 @@ void GcodeSuite::M425() { } } +void GcodeSuite::M425_report(const bool forReplay/*=true*/) { + TERN_(MARLIN_SMALL_BUILD, return); + + report_heading_etc(forReplay, F(STR_BACKLASH_COMPENSATION)); + 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 + 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 8640dfa391..0c4355f5b1 100644 --- a/Marlin/src/gcode/calibrate/M48.cpp +++ b/Marlin/src/gcode/calibrate/M48.cpp @@ -27,7 +27,7 @@ #include "../gcode.h" #include "../../module/motion.h" #include "../../module/probe.h" -#include "../../lcd/ultralcd.h" +#include "../../lcd/marlinui.h" #include "../../feature/bedlevel/bedlevel.h" @@ -35,11 +35,15 @@ #include "../../module/planner.h" #endif +#if HAS_PTC + #include "../../feature/probe_temp_comp.h" +#endif + /** * M48: Z probe repeatability measurement function. * * Usage: - * M48 + * M48 * P = Number of sampled points (4-50, default 10) * X = Sample X position * Y = Sample Y position @@ -47,28 +51,24 @@ * E = Engage Z probe for each reading * L = Number of legs of movement before probe * S = Schizoid (Or Star if you prefer) + * C = Enable probe temperature compensation (0 or 1, default 1) * * This function requires the machine to be homed before invocation. */ -extern const char SP_Y_STR[]; - void GcodeSuite::M48() { if (homing_needed_error()) return; const int8_t verbose_level = parser.byteval('V', 1); if (!WITHIN(verbose_level, 0, 4)) { - SERIAL_ECHOLNPGM("?(V)erbose level implausible (0-4)."); + SERIAL_ECHOLNPGM(GCODE_ERR_MSG("(V)erbose level implausible (0-4).")); return; } - if (verbose_level > 0) - SERIAL_ECHOLNPGM("M48 Z-Probe Repeatability Test"); - const int8_t n_samples = parser.byteval('P', 10); if (!WITHIN(n_samples, 4, 50)) { - SERIAL_ECHOLNPGM("?Sample size not plausible (4-50)."); + SERIAL_ECHOLNPGM(GCODE_ERR_MSG("Sample size not plausible (4-50).")); return; } @@ -81,8 +81,8 @@ void GcodeSuite::M48() { }; if (!probe.can_reach(test_position)) { - ui.set_status_P(GET_TEXT(MSG_M48_OUT_OF_BOUNDS), 99); - SERIAL_ECHOLNPGM("? (X,Y) out of bounds."); + LCD_MESSAGE_MAX(MSG_M48_OUT_OF_BOUNDS); + SERIAL_ECHOLNPGM(GCODE_ERR_MSG(" (X,Y) out of bounds.")); return; } @@ -90,7 +90,7 @@ void GcodeSuite::M48() { bool seen_L = parser.seen('L'); uint8_t n_legs = seen_L ? parser.value_byte() : 0; if (n_legs > 15) { - SERIAL_ECHOLNPGM("?Legs of movement implausible (0-15)."); + SERIAL_ECHOLNPGM(GCODE_ERR_MSG("Legs of movement implausible (0-15).")); return; } if (n_legs == 1) n_legs = 2; @@ -99,6 +99,9 @@ void GcodeSuite::M48() { const bool schizoid_flag = parser.boolval('S'); if (schizoid_flag && !seen_L) n_legs = 7; + if (verbose_level > 0) + SERIAL_ECHOLNPGM("M48 Z-Probe Repeatability Test"); + if (verbose_level > 2) SERIAL_ECHOLNPGM("Positioning the probe..."); @@ -109,6 +112,8 @@ void GcodeSuite::M48() { set_bed_leveling_enabled(false); #endif + TERN_(HAS_PTC, ptc.set_enabled(parser.boolval('C', true))); + // Work with reasonable feedrates remember_feedrate_scaling_off(); @@ -119,17 +124,15 @@ 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 &mean, const float &sigma, const float &min, const float &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_ECHOPAIR_F("Mean: ", mean, 6); - if (!final) SERIAL_ECHOPAIR_F(" Sigma: ", sigma, 6); - SERIAL_ECHOPAIR_F(" Min: ", min, 3); - SERIAL_ECHOPAIR_F(" Max: ", max, 3); - SERIAL_ECHOPAIR_F(" Range: ", max-min, 3); + SERIAL_ECHOPGM("Mean: ", p_float_t(mean, 6)); + if (!final) SERIAL_ECHOPGM(" Sigma: ", p_float_t(sigma, 6)); + SERIAL_ECHOPGM(" Min: ", p_float_t(min, 3), " Max: ", p_float_t(max, 3), " Range: ", p_float_t(max-min, 3)); if (final) SERIAL_EOL(); } if (final) { - SERIAL_ECHOLNPAIR_F("Standard Deviation: ", sigma, 6); + SERIAL_ECHOLNPGM("Standard Deviation: ", p_float_t(sigma, 6)); SERIAL_EOL(); } }; @@ -143,10 +146,10 @@ void GcodeSuite::M48() { float sample_sum = 0.0; - LOOP_L_N(n, n_samples) { - #if HAS_WIRED_LCD + for (uint8_t n = 0; n < n_samples; ++n) { + #if HAS_STATUS_MESSAGE // Display M48 progress in the status bar - ui.status_printf_P(0, PSTR(S_FMT ": %d/%d"), GET_TEXT(MSG_M48_POINT), int(n + 1), int(n_samples)); + ui.status_printf(0, F(S_FMT ": %d/%d"), GET_TEXT(MSG_M48_POINT), int(n + 1), int(n_samples)); #endif // When there are "legs" of movement move around the point before probing @@ -157,20 +160,20 @@ void GcodeSuite::M48() { float angle = random(0, 360); const float radius = random( #if ENABLED(DELTA) - int(0.1250000000 * (DELTA_PRINTABLE_RADIUS)), - int(0.3333333333 * (DELTA_PRINTABLE_RADIUS)) + int(0.1250000000 * (PRINTABLE_RADIUS)), + int(0.3333333333 * (PRINTABLE_RADIUS)) #else int(5), int(0.125 * _MIN(X_BED_SIZE, Y_BED_SIZE)) #endif ); if (verbose_level > 3) { - SERIAL_ECHOPAIR("Start radius:", radius, " angle:", angle, " dir:"); + SERIAL_ECHOPGM("Start radius:", radius, " angle:", angle, " dir:"); if (dir > 0) SERIAL_CHAR('C'); SERIAL_ECHOLNPGM("CW"); } // Move from leg to leg in rapid succession - LOOP_L_N(l, n_legs - 1) { + for (uint8_t l = 0; l < n_legs - 1; ++l) { // Move some distance around the perimeter float delta_angle; @@ -202,23 +205,23 @@ void GcodeSuite::M48() { while (!probe.can_reach(next_pos)) { next_pos *= 0.8f; if (verbose_level > 3) - SERIAL_ECHOLNPAIR_P(PSTR("Moving inward: X"), next_pos.x, SP_Y_STR, next_pos.y); + SERIAL_ECHOLN(F("Moving inward: X"), next_pos.x, FPSTR(SP_Y_STR), next_pos.y); } - #else + #elif HAS_ENDSTOPS // For a rectangular bed just keep the probe in bounds LIMIT(next_pos.x, X_MIN_POS, X_MAX_POS); LIMIT(next_pos.y, Y_MIN_POS, Y_MAX_POS); #endif if (verbose_level > 3) - SERIAL_ECHOLNPAIR_P(PSTR("Going to: X"), next_pos.x, SP_Y_STR, next_pos.y); + SERIAL_ECHOLN(F("Going to: X"), next_pos.x, FPSTR(SP_Y_STR), next_pos.y); do_blocking_move_to_xy(next_pos); } // n_legs loop } // n_legs // Probe a single point - const float pz = probe.probe_at_point(test_position, raise_after, 0); + const float pz = probe.probe_at_point(test_position, raise_after); // Break the loop if the probe fails probing_good = !isnan(pz); @@ -238,13 +241,11 @@ void GcodeSuite::M48() { // Calculate the standard deviation so far. // The value after the last sample will be the final output. float dev_sum = 0.0; - LOOP_LE_N(j, n) dev_sum += sq(sample_set[j] - mean); + for (uint8_t j = 0; j <= n; ++j) dev_sum += sq(sample_set[j] - mean); sigma = SQRT(dev_sum / (n + 1)); if (verbose_level > 1) { - SERIAL_ECHO(n + 1); - SERIAL_ECHOPAIR(" of ", int(n_samples)); - SERIAL_ECHOPAIR_F(": z: ", pz, 3); + SERIAL_ECHO(n + 1, F(" of "), n_samples, F(": z: "), p_float_t(pz, 3), C(' ')); dev_report(verbose_level > 2, mean, sigma, min, max); SERIAL_EOL(); } @@ -258,10 +259,21 @@ void GcodeSuite::M48() { SERIAL_ECHOLNPGM("Finished!"); dev_report(verbose_level > 0, mean, sigma, min, max, true); - #if HAS_WIRED_LCD + #if HAS_STATUS_MESSAGE // Display M48 results in the status bar - char sigma_str[8]; - ui.status_printf_P(0, PSTR(S_FMT ": %s"), GET_TEXT(MSG_M48_DEVIATION), dtostrf(sigma, 2, 6, sigma_str)); + 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 } @@ -270,6 +282,9 @@ void GcodeSuite::M48() { // Re-enable bed level correction if it had been on TERN_(HAS_LEVELING, set_bed_leveling_enabled(was_enabled)); + // Re-enable probe temperature correction + TERN_(HAS_PTC, ptc.set_enabled(true)); + report_current_position(); } diff --git a/Marlin/src/gcode/calibrate/M665.cpp b/Marlin/src/gcode/calibrate/M665.cpp index 557204cc11..5409ff4232 100644 --- a/Marlin/src/gcode/calibrate/M665.cpp +++ b/Marlin/src/gcode/calibrate/M665.cpp @@ -30,6 +30,7 @@ #if ENABLED(DELTA) #include "../../module/delta.h" + /** * M665: Set delta configurations * @@ -40,24 +41,44 @@ * X = Alpha (Tower 1) angle trim * Y = Beta (Tower 2) angle trim * Z = Gamma (Tower 3) angle trim - * A = Alpha (Tower 1) digonal rod trim - * B = Beta (Tower 2) digonal rod trim - * C = Gamma (Tower 3) digonal rod trim + * A = Alpha (Tower 1) diagonal rod trim + * B = Beta (Tower 2) diagonal rod trim + * C = Gamma (Tower 3) diagonal rod trim */ void GcodeSuite::M665() { - if (parser.seen('H')) delta_height = parser.value_linear_units(); - if (parser.seen('L')) delta_diagonal_rod = parser.value_linear_units(); - if (parser.seen('R')) delta_radius = parser.value_linear_units(); - if (parser.seen('S')) delta_segments_per_second = parser.value_float(); - if (parser.seen('X')) delta_tower_angle_trim.a = parser.value_float(); - if (parser.seen('Y')) delta_tower_angle_trim.b = parser.value_float(); - if (parser.seen('Z')) delta_tower_angle_trim.c = parser.value_float(); - if (parser.seen('A')) delta_diagonal_rod_trim.a = parser.value_float(); - if (parser.seen('B')) delta_diagonal_rod_trim.b = parser.value_float(); - if (parser.seen('C')) delta_diagonal_rod_trim.c = parser.value_float(); + if (!parser.seen_any()) return M665_report(); + + if (parser.seenval('H')) delta_height = parser.value_linear_units(); + if (parser.seenval('L')) delta_diagonal_rod = parser.value_linear_units(); + if (parser.seenval('R')) delta_radius = parser.value_linear_units(); + if (parser.seenval('S')) segments_per_second = parser.value_float(); + if (parser.seenval('X')) delta_tower_angle_trim.a = parser.value_float(); + if (parser.seenval('Y')) delta_tower_angle_trim.b = parser.value_float(); + if (parser.seenval('Z')) delta_tower_angle_trim.c = parser.value_float(); + if (parser.seenval('A')) delta_diagonal_rod_trim.a = parser.value_float(); + if (parser.seenval('B')) delta_diagonal_rod_trim.b = parser.value_float(); + if (parser.seenval('C')) delta_diagonal_rod_trim.c = parser.value_float(); recalc_delta_settings(); } + void GcodeSuite::M665_report(const bool forReplay/*=true*/) { + TERN_(MARLIN_SMALL_BUILD, return); + + report_heading_etc(forReplay, F(STR_DELTA_SETTINGS)); + SERIAL_ECHOLNPGM_P( + PSTR(" M665 L"), LINEAR_UNIT(delta_diagonal_rod) + , PSTR(" R"), LINEAR_UNIT(delta_radius) + , PSTR(" H"), LINEAR_UNIT(delta_height) + , PSTR(" S"), segments_per_second + , SP_X_STR, LINEAR_UNIT(delta_tower_angle_trim.a) + , SP_Y_STR, LINEAR_UNIT(delta_tower_angle_trim.b) + , SP_Z_STR, LINEAR_UNIT(delta_tower_angle_trim.c) + , PSTR(" A"), LINEAR_UNIT(delta_diagonal_rod_trim.a) + , PSTR(" B"), LINEAR_UNIT(delta_diagonal_rod_trim.b) + , PSTR(" C"), LINEAR_UNIT(delta_diagonal_rod_trim.c) + ); + } + #elif IS_SCARA #include "../../module/scara.h" @@ -67,16 +88,21 @@ * * Parameters: * - * S[segments-per-second] - Segments-per-second - * P[theta-psi-offset] - Theta-Psi offset, added to the shoulder (A/X) angle - * T[theta-offset] - Theta offset, added to the elbow (B/Y) angle - * Z[z-offset] - Z offset, added to Z + * S[segments] - Segments-per-second + * + * Without NO_WORKSPACE_OFFSETS: + * + * P[theta-psi-offset] - Theta-Psi offset, added to the shoulder (A/X) angle + * T[theta-offset] - Theta offset, added to the elbow (B/Y) angle + * Z[z-offset] - Z offset, added to Z * * A, P, and X are all aliases for the shoulder angle * B, T, and Y are all aliases for the elbow angle */ void GcodeSuite::M665() { - if (parser.seenval('S')) delta_segments_per_second = parser.value_float(); + if (!parser.seen_any()) return M665_report(); + + if (parser.seenval('S')) segments_per_second = parser.value_float(); #if HAS_SCARA_OFFSET @@ -107,6 +133,80 @@ #endif // HAS_SCARA_OFFSET } -#endif + void GcodeSuite::M665_report(const bool forReplay/*=true*/) { + TERN_(MARLIN_SMALL_BUILD, return); + + report_heading_etc(forReplay, F(STR_SCARA_SETTINGS " (" STR_S_SEG_PER_SEC TERN_(HAS_SCARA_OFFSET, " " STR_SCARA_P_T_Z) ")")); + 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) + #endif + ); + } + +#elif ENABLED(POLARGRAPH) + + #include "../../module/polargraph.h" + + /** + * M665: Set POLARGRAPH settings + * + * Parameters: + * + * S[segments] - Segments-per-second + * L[left] - Work area minimum X + * R[right] - Work area maximum X + * T[top] - Work area maximum Y + * B[bottom] - Work area minimum Y + * H[length] - Maximum belt length + */ + void GcodeSuite::M665() { + if (!parser.seen_any()) return M665_report(); + if (parser.seenval('S')) segments_per_second = parser.value_float(); + if (parser.seenval('L')) draw_area_min.x = parser.value_linear_units(); + if (parser.seenval('R')) draw_area_max.x = parser.value_linear_units(); + if (parser.seenval('T')) draw_area_max.y = parser.value_linear_units(); + if (parser.seenval('B')) draw_area_min.y = parser.value_linear_units(); + if (parser.seenval('H')) polargraph_max_belt_len = parser.value_linear_units(); + } + + void GcodeSuite::M665_report(const bool forReplay/*=true*/) { + TERN_(MARLIN_SMALL_BUILD, return); + + report_heading_etc(forReplay, F(STR_POLARGRAPH_SETTINGS)); + SERIAL_ECHOLNPGM_P( + PSTR(" M665 S"), LINEAR_UNIT(segments_per_second), + PSTR(" L"), LINEAR_UNIT(draw_area_min.x), + PSTR(" R"), LINEAR_UNIT(draw_area_max.x), + SP_T_STR, LINEAR_UNIT(draw_area_max.y), + SP_B_STR, LINEAR_UNIT(draw_area_min.y), + PSTR(" H"), LINEAR_UNIT(polargraph_max_belt_len) + ); + } + +#elif ENABLED(POLAR) + + #include "../../module/polar.h" + + /** + * M665: Set POLAR settings + * Parameters: + * S[segments] - Segments-per-second + */ + void GcodeSuite::M665() { + if (!parser.seen_any()) return M665_report(); + if (parser.seenval('S')) segments_per_second = parser.value_float(); + } + + void GcodeSuite::M665_report(const bool forReplay/*=true*/) { + TERN_(MARLIN_SMALL_BUILD, return); + report_heading_etc(forReplay, F(STR_POLAR_SETTINGS)); + SERIAL_ECHOLNPGM_P(PSTR(" M665 S"), segments_per_second); + } + +#endif // POLAR #endif // IS_KINEMATIC diff --git a/Marlin/src/gcode/calibrate/M666.cpp b/Marlin/src/gcode/calibrate/M666.cpp index e915aa8ff7..2584782cf3 100644 --- a/Marlin/src/gcode/calibrate/M666.cpp +++ b/Marlin/src/gcode/calibrate/M666.cpp @@ -22,47 +22,86 @@ #include "../../inc/MarlinConfig.h" -#if ENABLED(DELTA) || HAS_EXTRA_ENDSTOPS +#if ANY(DELTA, HAS_EXTRA_ENDSTOPS) #include "../gcode.h" #if ENABLED(DELTA) - #include "../../module/delta.h" #include "../../module/motion.h" +#else + #include "../../module/endstops.h" +#endif - #define DEBUG_OUT ENABLED(DEBUG_LEVELING_FEATURE) - #include "../../core/debug_out.h" +#define DEBUG_OUT ENABLED(DEBUG_LEVELING_FEATURE) +#include "../../core/debug_out.h" + +#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)); - LOOP_XYZ(i) { - if (parser.seen(XYZ_CHAR(i))) { + bool is_err = false, is_set = false; + LOOP_NUM_AXES(i) { + if (parser.seenval(AXIS_CHAR(i))) { + is_set = true; const float v = parser.value_linear_units(); - if (v * Z_HOME_DIR <= 0) delta_endstop_adj[i] = v; - if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPAIR("delta_endstop_adj[", XYZ_CHAR(i), "] = ", delta_endstop_adj[i]); + if (v > 0) + is_err = true; + else { + delta_endstop_adj[i] = v; + if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("delta_endstop_adj[", C(AXIS_CHAR(i)), "] = ", v); + } } } + if (is_err) SERIAL_ECHOLNPGM(GCODE_ERR_MSG("M666 offsets must be <= 0")); + if (!is_set) M666_report(); } -#elif HAS_EXTRA_ENDSTOPS + void GcodeSuite::M666_report(const bool forReplay/*=true*/) { + TERN_(MARLIN_SMALL_BUILD, return); - #include "../../module/endstops.h" + report_heading_etc(forReplay, F(STR_ENDSTOP_ADJUSTMENT)); + SERIAL_ECHOLNPGM_P( + PSTR(" M666 X"), LINEAR_UNIT(delta_endstop_adj.a) + , SP_Y_STR, LINEAR_UNIT(delta_endstop_adj.b) + , SP_Z_STR, LINEAR_UNIT(delta_endstop_adj.c) + ); + } + +#else /** - * M666: Set Dual Endstops offsets for X, Y, and/or Z. - * With no parameters report current offsets. + * M666: Set Dual Endstop Offsets * - * 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 + * 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 + * + * 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(); + #if ENABLED(X_DUAL_ENDSTOPS) if (parser.seenval('X')) endstops.x2_endstop_adj = parser.value_linear_units(); #endif @@ -71,33 +110,42 @@ #endif #if ENABLED(Z_MULTI_ENDSTOPS) if (parser.seenval('Z')) { - #if NUM_Z_STEPPER_DRIVERS >= 3 - const float z_adj = parser.value_linear_units(); - const int ind = parser.intval('S'); - if (!ind || ind == 2) endstops.z2_endstop_adj = z_adj; - if (!ind || ind == 3) endstops.z3_endstop_adj = z_adj; - #if NUM_Z_STEPPER_DRIVERS >= 4 - if (!ind || ind == 4) endstops.z4_endstop_adj = z_adj; - #endif + const float z_adj = parser.value_linear_units(); + #if NUM_Z_STEPPERS == 2 + endstops.z2_endstop_adj = z_adj; #else - endstops.z2_endstop_adj = parser.value_linear_units(); + const int ind = parser.intval('S'); + #define _SET_ZADJ(N) if (!ind || ind == N) endstops.z##N##_endstop_adj = z_adj; + REPEAT_S(2, INCREMENT(NUM_Z_STEPPERS), _SET_ZADJ) #endif } #endif - if (!parser.seen("XYZ")) { - SERIAL_ECHOPGM("Dual Endstop Adjustment (mm): "); - #if ENABLED(X_DUAL_ENDSTOPS) - SERIAL_ECHOPAIR(" X2:", endstops.x2_endstop_adj); + } + + void GcodeSuite::M666_report(const bool forReplay/*=true*/) { + TERN_(MARLIN_SMALL_BUILD, return); + + report_heading_etc(forReplay, F(STR_ENDSTOP_ADJUSTMENT)); + SERIAL_ECHOPGM(" M666"); + #if ENABLED(X_DUAL_ENDSTOPS) + SERIAL_ECHOLNPGM_P(SP_X_STR, LINEAR_UNIT(endstops.x2_endstop_adj)); + #endif + #if ENABLED(Y_DUAL_ENDSTOPS) + SERIAL_ECHOLNPGM_P(SP_Y_STR, LINEAR_UNIT(endstops.y2_endstop_adj)); + #endif + #if ENABLED(Z_MULTI_ENDSTOPS) + #if NUM_Z_STEPPERS >= 3 + SERIAL_ECHOPGM(" S2 Z", LINEAR_UNIT(endstops.z3_endstop_adj)); + report_echo_start(forReplay); + SERIAL_ECHOPGM(" M666 S3 Z", LINEAR_UNIT(endstops.z3_endstop_adj)); + #if NUM_Z_STEPPERS >= 4 + report_echo_start(forReplay); + SERIAL_ECHOPGM(" M666 S4 Z", LINEAR_UNIT(endstops.z4_endstop_adj)); + #endif + #else + SERIAL_ECHOLNPGM_P(SP_Z_STR, LINEAR_UNIT(endstops.z2_endstop_adj)); #endif - #if ENABLED(Y_DUAL_ENDSTOPS) - SERIAL_ECHOPAIR(" Y2:", endstops.y2_endstop_adj); - #endif - #if ENABLED(Z_MULTI_ENDSTOPS) - #define _ECHO_ZADJ(N) SERIAL_ECHOPAIR(" Z" STRINGIFY(N) ":", endstops.z##N##_endstop_adj); - REPEAT_S(2, INCREMENT(NUM_Z_STEPPER_DRIVERS), _ECHO_ZADJ) - #endif - SERIAL_EOL(); - } + #endif } #endif // HAS_EXTRA_ENDSTOPS diff --git a/Marlin/src/gcode/calibrate/M852.cpp b/Marlin/src/gcode/calibrate/M852.cpp index b60f41748f..7cd9aaf718 100644 --- a/Marlin/src/gcode/calibrate/M852.cpp +++ b/Marlin/src/gcode/calibrate/M852.cpp @@ -28,18 +28,23 @@ #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() { - uint8_t ijk = 0, badval = 0, setval = 0; + if (!parser.seen("SIJK")) return M852_report(); - if (parser.seen('I') || parser.seen('S')) { - ++ijk; + uint8_t badval = 0, setval = 0; + + if (parser.seenval('I') || parser.seenval('S')) { const float value = parser.value_linear_units(); if (WITHIN(value, SKEW_FACTOR_MIN, SKEW_FACTOR_MAX)) { if (planner.skew_factor.xy != value) { @@ -53,8 +58,7 @@ void GcodeSuite::M852() { #if ENABLED(SKEW_CORRECTION_FOR_Z) - if (parser.seen('J')) { - ++ijk; + if (parser.seenval('J')) { const float value = parser.value_linear_units(); if (WITHIN(value, SKEW_FACTOR_MIN, SKEW_FACTOR_MAX)) { if (planner.skew_factor.xz != value) { @@ -66,8 +70,7 @@ void GcodeSuite::M852() { ++badval; } - if (parser.seen('K')) { - ++ijk; + if (parser.seenval('K')) { const float value = parser.value_linear_units(); if (WITHIN(value, SKEW_FACTOR_MIN, SKEW_FACTOR_MAX)) { if (planner.skew_factor.yz != value) { @@ -86,21 +89,22 @@ void GcodeSuite::M852() { // When skew is changed the current position changes if (setval) { - set_current_from_steppers_for_axis(ALL_AXES); + set_current_from_steppers_for_axis(ALL_AXES_ENUM); sync_plan_position(); report_current_position(); } +} - if (!ijk) { - SERIAL_ECHO_START(); - serialprintPGM(GET_TEXT(MSG_SKEW_FACTOR)); - SERIAL_ECHOPAIR_F(" XY: ", planner.skew_factor.xy, 6); - #if ENABLED(SKEW_CORRECTION_FOR_Z) - SERIAL_ECHOPAIR_F(" XZ: ", planner.skew_factor.xz, 6); - SERIAL_ECHOPAIR_F(" YZ: ", planner.skew_factor.yz, 6); - #endif - SERIAL_EOL(); - } +void GcodeSuite::M852_report(const bool forReplay/*=true*/) { + TERN_(MARLIN_SMALL_BUILD, return); + + report_heading_etc(forReplay, F(STR_SKEW_FACTOR)); + SERIAL_ECHOPGM(" M852 I", p_float_t(planner.skew_factor.xy, 6)); + #if ENABLED(SKEW_CORRECTION_FOR_Z) + SERIAL_ECHOLNPGM(" J", p_float_t(planner.skew_factor.xz, 6), " K", p_float_t(planner.skew_factor.yz, 6), " ; XY, XZ, YZ"); + #else + SERIAL_ECHOLNPGM(" ; XY"); + #endif } #endif // SKEW_CORRECTION_GCODE diff --git a/Marlin/src/gcode/config/M200-M205.cpp b/Marlin/src/gcode/config/M200-M205.cpp index cb17fc45a6..53d0b4b4a8 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 @@ -32,9 +32,14 @@ * T - Optional extruder number. Current extruder if omitted. * D - Set filament diameter and enable. D0 disables volumetric. * S - Turn volumetric ON or OFF. + * + * With VOLUMETRIC_EXTRUDER_LIMIT: + * * L - Volumetric extruder limit (in mm^3/sec). L0 disables the limit. */ void GcodeSuite::M200() { + if (!parser.seen("DST" TERN_(VOLUMETRIC_EXTRUDER_LIMIT, "L"))) + return M200_report(); const int8_t target_extruder = get_target_extruder_from_command(); if (target_extruder < 0) return; @@ -59,71 +64,205 @@ if (parser.seenval('L')) { // Set volumetric limit (in mm^3/sec) const float lval = parser.value_float(); - if (WITHIN(lval, 0, 20)) + if (WITHIN(lval, 0, VOLUMETRIC_EXTRUDER_LIMIT_MAX)) planner.set_volumetric_extruder_limit(target_extruder, lval); else - SERIAL_ECHOLNPGM("?L value out of range (0-20)."); + SERIAL_ECHOLNPGM(GCODE_ERR_MSG("L value out of range (0-" STRINGIFY(VOLUMETRIC_EXTRUDER_LIMIT_MAX) ").")); } #endif planner.calculate_volumetric_multipliers(); } -#endif // !NO_VOLUMETRICS + void GcodeSuite::M200_report(const bool forReplay/*=true*/) { + TERN_(MARLIN_SMALL_BUILD, return); + + if (!forReplay) { + report_heading(false, F(STR_FILAMENT_SETTINGS), false); + if (!parser.volumetric_enabled) SERIAL_ECHOPGM(" (Disabled):"); + SERIAL_EOL(); + report_echo_start(false); + } + + #if EXTRUDERS == 1 + { + SERIAL_ECHOLNPGM( + " M200 S", parser.volumetric_enabled, " D", LINEAR_UNIT(planner.filament_size[0]) + #if ENABLED(VOLUMETRIC_EXTRUDER_LIMIT) + , " L", LINEAR_UNIT(planner.volumetric_extruder_limit[0]) + #endif + ); + } + #else + SERIAL_ECHOLNPGM(" M200 S", parser.volumetric_enabled); + EXTRUDER_LOOP() { + report_echo_start(forReplay); + SERIAL_ECHOLNPGM( + " M200 T", e, " D", LINEAR_UNIT(planner.filament_size[e]) + #if ENABLED(VOLUMETRIC_EXTRUDER_LIMIT) + , " L", LINEAR_UNIT(planner.volumetric_extruder_limit[e]) + #endif + ); + } + #endif + } + +#endif // HAS_VOLUMETRIC_EXTRUSION /** - * M201: Set max acceleration in units/s^2 for print moves (M201 X1000 Y1000) + * M201: Set max acceleration in units/s^2 for print moves. * - * With multiple extruders use T to specify which one. + * X : Max Acceleration for X + * Y : Max Acceleration for Y + * Z : Max Acceleration for Z + * ... : etc + * E : Max Acceleration for Extruder + * T : Extruder index to set + * + * With XY_FREQUENCY_LIMIT: + * F : Frequency limit for XY...IJKUVW + * S : Speed factor percentage. */ void GcodeSuite::M201() { + 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; #ifdef XY_FREQUENCY_LIMIT if (parser.seenval('F')) planner.set_frequency_limit(parser.value_byte()); - if (parser.seenval('G')) planner.xy_freq_min_speed_factor = constrain(parser.value_float(), 1, 100) / 100; + if (parser.seenval('S')) planner.xy_freq_min_speed_factor = constrain(parser.value_float(), 1, 100) / 100; #endif - LOOP_XYZE(i) { - if (parser.seen(axis_codes[i])) { - const uint8_t a = (i == E_AXIS ? uint8_t(E_AXIS_N(target_extruder)) : i); - planner.set_max_acceleration(a, parser.value_axis_units((AxisEnum)a)); + LOOP_LOGICAL_AXES(i) { + if (parser.seenval(AXIS_CHAR(i))) { + const AxisEnum a = TERN(HAS_EXTRUDERS, (i == E_AXIS ? E_AXIS_N(target_extruder) : (AxisEnum)i), (AxisEnum)i); + planner.set_max_acceleration(a, parser.value_axis_units(a)); } } } +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 + 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 + + #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); + SERIAL_ECHOLNPGM_P( + PSTR(" M201 T"), i + , SP_E_STR, VOLUMETRIC_UNIT(planner.settings.max_acceleration_mm_per_s2[E_AXIS_N(i)]) + ); + } + #endif +} + /** * M203: Set maximum feedrate that your machine can sustain (M203 X200 Y200 Z300 E10000) in units/sec * * With multiple extruders use T to specify which one. */ void GcodeSuite::M203() { + if (!parser.seen("T" STR_AXES_LOGICAL)) + return M203_report(); const int8_t target_extruder = get_target_extruder_from_command(); if (target_extruder < 0) return; - LOOP_XYZE(i) - if (parser.seen(axis_codes[i])) { - const uint8_t a = (i == E_AXIS ? uint8_t(E_AXIS_N(target_extruder)) : i); - planner.set_max_feedrate(a, parser.value_axis_units((AxisEnum)a)); + LOOP_LOGICAL_AXES(i) + if (parser.seenval(AXIS_CHAR(i))) { + const AxisEnum a = TERN(HAS_EXTRUDERS, (i == E_AXIS ? E_AXIS_N(target_extruder) : (AxisEnum)i), (AxisEnum)i); + planner.set_max_feedrate(a, parser.value_axis_units(a)); } } +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 + 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 (eol) SERIAL_EOL(); + + #if ENABLED(DISTINCT_E_FACTORS) + for (uint8_t i = 0; i < E_STEPPERS; ++i) { + 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)]) + ); + } + #endif +} + /** * M204: Set Accelerations in units/sec^2 (M204 P1200 R3000 T3000) * - * P = Printing moves - * R = Retract only (no X, Y, Z) moves - * T = Travel (non printing) moves + * P Printing moves + * R Retract only (no X, Y, Z) moves + * T Travel (non printing) moves */ void GcodeSuite::M204() { - if (!parser.seen("PRST")) { - SERIAL_ECHOPAIR("Acceleration: P", planner.settings.acceleration); - SERIAL_ECHOPAIR(" R", planner.settings.retract_acceleration); - SERIAL_ECHOLNPAIR_P(SP_T_STR, planner.settings.travel_acceleration); - } + if (!parser.seen("PRST")) + return M204_report(); else { //planner.synchronize(); // 'S' for legacy compatibility. Should NOT BE USED for new development @@ -134,58 +273,120 @@ void GcodeSuite::M204() { } } +void GcodeSuite::M204_report(const bool forReplay/*=true*/) { + TERN_(MARLIN_SMALL_BUILD, return); + + report_heading_etc(forReplay, F(STR_ACCELERATION_P_R_T)); + SERIAL_ECHOLNPGM_P( + PSTR(" M204 P"), LINEAR_UNIT(planner.settings.acceleration) + , PSTR(" R"), LINEAR_UNIT(planner.settings.retract_acceleration) + , SP_T_STR, LINEAR_UNIT(planner.settings.travel_acceleration) + ); +} + +#if AXIS_COLLISION('B') + #define M205_MIN_SEG_TIME_PARAM 'D' + #define M205_MIN_SEG_TIME_STR "D" + #warning "Use 'M205 D' for Minimum Segment Time." +#else + #define M205_MIN_SEG_TIME_PARAM 'B' + #define M205_MIN_SEG_TIME_STR "B" +#endif + /** * M205: Set Advanced Settings * - * B = Min Segment Time (Β΅s) - * S = Min Feed Rate (units/s) - * T = Min Travel Feed Rate (units/s) - * X = Max X Jerk (units/sec^2) - * Y = Max Y Jerk (units/sec^2) - * Z = Max Z Jerk (units/sec^2) - * E = Max E Jerk (units/sec^2) - * J = Junction Deviation (mm) (If not using CLASSIC_JERK) + * B<Β΅s> : Min Segment Time + * S : Min Feed Rate + * T : Min Travel Feed Rate + * + * With CLASSIC_JERK: + * X : Max X Jerk + * Y : Max Y Jerk + * Z : Max Z Jerk + * ... : etc + * E : Max E Jerk + * + * Without CLASSIC_JERK: + * J(mm) : Junction Deviation */ void GcodeSuite::M205() { - #if HAS_JUNCTION_DEVIATION - #define J_PARAM "J" - #else - #define J_PARAM - #endif - #if HAS_CLASSIC_JERK - #define XYZE_PARAM "XYZE" - #else - #define XYZE_PARAM - #endif - if (!parser.seen("BST" J_PARAM XYZE_PARAM)) return; + if (!parser.seen_any()) return M205_report(); //planner.synchronize(); - if (parser.seen('B')) planner.settings.min_segment_time_us = parser.value_ulong(); - if (parser.seen('S')) planner.settings.min_feedrate_mm_s = parser.value_linear_units(); - if (parser.seen('T')) planner.settings.min_travel_feedrate_mm_s = parser.value_linear_units(); + if (parser.seenval(M205_MIN_SEG_TIME_PARAM)) planner.settings.min_segment_time_us = parser.value_ulong(); + if (parser.seenval('S')) planner.settings.min_feedrate_mm_s = parser.value_linear_units(); + if (parser.seenval('T')) planner.settings.min_travel_feedrate_mm_s = parser.value_linear_units(); #if HAS_JUNCTION_DEVIATION - if (parser.seen('J')) { + if (parser.seenval('J')) { const float junc_dev = parser.value_linear_units(); if (WITHIN(junc_dev, 0.01f, 0.3f)) { planner.junction_deviation_mm = junc_dev; - TERN_(LIN_ADVANCE, planner.recalculate_max_e_jerk()); + TERN_(HAS_LINEAR_E_JERK, planner.recalculate_max_e_jerk()); } else SERIAL_ERROR_MSG("?J out of range (0.01 to 0.3)"); } #endif - #if HAS_CLASSIC_JERK - if (parser.seen('X')) planner.set_max_jerk(X_AXIS, parser.value_linear_units()); - if (parser.seen('Y')) planner.set_max_jerk(Y_AXIS, parser.value_linear_units()); - if (parser.seen('Z')) { - planner.set_max_jerk(Z_AXIS, parser.value_linear_units()); - #if HAS_MESH && DISABLED(LIMITED_JERK_EDITING) - if (planner.max_jerk.z <= 0.1f) - SERIAL_ECHOLNPGM("WARNING! Low Z Jerk may lead to unwanted pauses."); - #endif - } - #if HAS_CLASSIC_E_JERK - if (parser.seen('E')) planner.set_max_jerk(E_AXIS, parser.value_linear_units()); + #if ENABLED(CLASSIC_JERK) + bool seenZ = false; + LOGICAL_AXIS_CODE( + if (parser.seenval('E')) planner.set_max_jerk(E_AXIS, parser.value_linear_units()), + if (parser.seenval('X')) planner.set_max_jerk(X_AXIS, parser.value_linear_units()), + if (parser.seenval('Y')) planner.set_max_jerk(Y_AXIS, parser.value_linear_units()), + if ((seenZ = parser.seenval('Z'))) planner.set_max_jerk(Z_AXIS, parser.value_linear_units()), + if (parser.seenval(AXIS4_NAME)) planner.set_max_jerk(I_AXIS, parser.TERN(AXIS4_ROTATES, value_float, value_linear_units)()), + if (parser.seenval(AXIS5_NAME)) planner.set_max_jerk(J_AXIS, parser.TERN(AXIS5_ROTATES, value_float, value_linear_units)()), + if (parser.seenval(AXIS6_NAME)) planner.set_max_jerk(K_AXIS, parser.TERN(AXIS6_ROTATES, value_float, value_linear_units)()), + if (parser.seenval(AXIS7_NAME)) planner.set_max_jerk(U_AXIS, parser.TERN(AXIS7_ROTATES, value_float, value_linear_units)()), + if (parser.seenval(AXIS8_NAME)) planner.set_max_jerk(V_AXIS, parser.TERN(AXIS8_ROTATES, value_float, value_linear_units)()), + if (parser.seenval(AXIS9_NAME)) planner.set_max_jerk(W_AXIS, parser.TERN(AXIS9_ROTATES, value_float, value_linear_units)()) + ); + #if HAS_MESH && DISABLED(LIMITED_JERK_EDITING) + if (seenZ && planner.max_jerk.z <= 0.1f) + SERIAL_ECHOLNPGM("WARNING! Low Z Jerk may lead to unwanted pauses."); #endif - #endif + #endif // CLASSIC_JERK +} + +void GcodeSuite::M205_report(const bool forReplay/*=true*/) { + TERN_(MARLIN_SMALL_BUILD, return); + + report_heading_etc(forReplay, F( + "Advanced (" M205_MIN_SEG_TIME_STR " S T" + TERN_(HAS_JUNCTION_DEVIATION, " J") + #if ENABLED(CLASSIC_JERK) + NUM_AXIS_GANG( + " X", " Y", " Z", + " " STR_I "", " " STR_J "", " " STR_K "", + " " STR_U "", " " STR_V "", " " STR_W "" + ) + #endif + TERN_(HAS_CLASSIC_E_JERK, " E") + ")" + )); + SERIAL_ECHOLNPGM_P( + PSTR(" M205 " M205_MIN_SEG_TIME_STR), LINEAR_UNIT(planner.settings.min_segment_time_us) + , PSTR(" S"), LINEAR_UNIT(planner.settings.min_feedrate_mm_s) + , SP_T_STR, LINEAR_UNIT(planner.settings.min_travel_feedrate_mm_s) + #if HAS_JUNCTION_DEVIATION + , PSTR(" J"), LINEAR_UNIT(planner.junction_deviation_mm) + #endif + #if ENABLED(CLASSIC_JERK) && 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), + SP_I_STR, I_AXIS_UNIT(planner.max_jerk.i), + SP_J_STR, J_AXIS_UNIT(planner.max_jerk.j), + SP_K_STR, K_AXIS_UNIT(planner.max_jerk.k), + SP_U_STR, U_AXIS_UNIT(planner.max_jerk.u), + SP_V_STR, V_AXIS_UNIT(planner.max_jerk.v), + SP_W_STR, W_AXIS_UNIT(planner.max_jerk.w) + ) + #if HAS_CLASSIC_E_JERK + , SP_E_STR, LINEAR_UNIT(planner.max_jerk.e) + #endif + #endif + ); } diff --git a/Marlin/src/gcode/config/M210.cpp b/Marlin/src/gcode/config/M210.cpp new file mode 100644 index 0000000000..41dbd73db4 --- /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) homing_feedrate_mm_m.x = parser.value_axis_units(X_AXIS), + if (parser.floatval(AXIS2_PARAM) > 0) homing_feedrate_mm_m.y = parser.value_axis_units(Y_AXIS), + if (parser.floatval(AXIS3_PARAM) > 0) homing_feedrate_mm_m.z = parser.value_axis_units(Z_AXIS), + if (parser.floatval(AXIS4_PARAM) > 0) homing_feedrate_mm_m.i = parser.value_axis_units(I_AXIS), + if (parser.floatval(AXIS5_PARAM) > 0) homing_feedrate_mm_m.j = parser.value_axis_units(J_AXIS), + if (parser.floatval(AXIS6_PARAM) > 0) homing_feedrate_mm_m.k = parser.value_axis_units(K_AXIS), + if (parser.floatval(AXIS7_PARAM) > 0) homing_feedrate_mm_m.u = parser.value_axis_units(U_AXIS), + if (parser.floatval(AXIS8_PARAM) > 0) homing_feedrate_mm_m.v = parser.value_axis_units(V_AXIS), + if (parser.floatval(AXIS9_PARAM) > 0) 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(homing_feedrate_mm_m.x), + SP_Y_STR, Y_AXIS_UNIT(homing_feedrate_mm_m.y), + SP_Z_STR, Z_AXIS_UNIT(homing_feedrate_mm_m.z), + SP_I_STR, I_AXIS_UNIT(homing_feedrate_mm_m.i), + SP_J_STR, J_AXIS_UNIT(homing_feedrate_mm_m.j), + SP_K_STR, K_AXIS_UNIT(homing_feedrate_mm_m.k), + SP_U_STR, U_AXIS_UNIT(homing_feedrate_mm_m.u), + SP_V_STR, V_AXIS_UNIT(homing_feedrate_mm_m.v), + SP_W_STR, W_AXIS_UNIT(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 b57dec31f3..df275c2d31 100644 --- a/Marlin/src/gcode/config/M217.cpp +++ b/Marlin/src/gcode/config/M217.cpp @@ -25,74 +25,42 @@ #if HAS_MULTI_EXTRUDER #include "../gcode.h" -#include "../../module/tool_change.h" -#if ENABLED(TOOLCHANGE_MIGRATION_FEATURE) - #include "../../module/motion.h" +#if HAS_TOOLCHANGE + #include "../../module/tool_change.h" #endif -#include "../../MarlinCore.h" // for SP_X_STR, etc. - -extern const char SP_X_STR[], SP_Y_STR[], SP_Z_STR[]; - -void M217_report(const bool eeprom=false) { - - #if ENABLED(TOOLCHANGE_FILAMENT_SWAP) - serialprintPGM(eeprom ? PSTR(" M217") : PSTR("Toolchange:")); - SERIAL_ECHOPAIR(" S", LINEAR_UNIT(toolchange_settings.swap_length)); - SERIAL_ECHOPAIR_P(SP_B_STR, LINEAR_UNIT(toolchange_settings.extra_resume), - SP_E_STR, LINEAR_UNIT(toolchange_settings.extra_prime), - SP_P_STR, LINEAR_UNIT(toolchange_settings.prime_speed)); - SERIAL_ECHOPAIR(" R", LINEAR_UNIT(toolchange_settings.retract_speed), - " U", LINEAR_UNIT(toolchange_settings.unretract_speed), - " F", toolchange_settings.fan_speed, - " G", toolchange_settings.fan_time); - - #if ENABLED(TOOLCHANGE_MIGRATION_FEATURE) - SERIAL_ECHOPAIR(" A", int(migration.automode)); - SERIAL_ECHOPAIR(" L", LINEAR_UNIT(migration.last)); - #endif - - #if ENABLED(TOOLCHANGE_PARK) - SERIAL_ECHOPAIR(" W", LINEAR_UNIT(toolchange_settings.enable_park)); - SERIAL_ECHOPAIR_P(SP_X_STR, LINEAR_UNIT(toolchange_settings.change_point.x)); - SERIAL_ECHOPAIR_P(SP_Y_STR, LINEAR_UNIT(toolchange_settings.change_point.y)); - #endif - - #if ENABLED(TOOLCHANGE_FS_PRIME_FIRST_USED) - SERIAL_ECHOPAIR(" V", LINEAR_UNIT(enable_first_prime)); - #endif - - #else - - UNUSED(eeprom); - - #endif - - SERIAL_ECHOPAIR_P(SP_Z_STR, LINEAR_UNIT(toolchange_settings.z_raise)); - SERIAL_EOL(); -} +#if ENABLED(TOOLCHANGE_MIGRATION_FEATURE) + #include "../../module/motion.h" // for active_extruder +#endif /** - * M217 - Set SINGLENOZZLE toolchange parameters + * M217 - Set toolchange parameters * * // Tool change command * Q Prime active tool and exit * * // Tool change settings - * S[linear] Swap length - * B[linear] Extra Swap length - * E[linear] Prime length - * P[linear/m] Prime speed - * R[linear/m] Retract speed - * U[linear/m] UnRetract speed - * V[linear] 0/1 Enable auto prime first extruder used - * W[linear] 0/1 Enable park & Z Raise - * X[linear] Park X (Requires TOOLCHANGE_PARK) - * Y[linear] Park Y (Requires TOOLCHANGE_PARK) - * Z[linear] Z Raise - * F[linear] Fan Speed 0-255 - * G[linear/s] Fan time + * S[linear] Swap length + * B[linear] Extra Swap resume length + * E[linear] Extra Prime length (as used by M217 Q) + * G[linear] Cutting wipe retract length (<=100mm) + * R[linear/min] Retract speed + * U[linear/min] UnRetract speed + * P[linear/min] Prime speed + * V[linear] 0/1 Enable auto prime first extruder used + * W[linear] 0/1 Enable park & Z Raise + * X[linear] Park X (Requires TOOLCHANGE_PARK) + * Y[linear] Park Y (Requires TOOLCHANGE_PARK and NUM_AXES >= 2) + * I[linear] Park I (Requires TOOLCHANGE_PARK and NUM_AXES >= 4) + * J[linear] Park J (Requires TOOLCHANGE_PARK and NUM_AXES >= 5) + * K[linear] Park K (Requires TOOLCHANGE_PARK and NUM_AXES >= 6) + * C[linear] Park U (Requires TOOLCHANGE_PARK and NUM_AXES >= 7) + * H[linear] Park V (Requires TOOLCHANGE_PARK and NUM_AXES >= 8) + * O[linear] Park W (Requires TOOLCHANGE_PARK and NUM_AXES >= 9) + * Z[linear] Z Raise + * F[speed] Fan Speed 0-255 + * D[seconds] Fan time * * Tool migration settings * A[0|1] Enable auto-migration on runout @@ -113,11 +81,12 @@ void GcodeSuite::M217() { if (parser.seenval('B')) { const float v = parser.value_linear_units(); toolchange_settings.extra_resume = constrain(v, -10, 10); } if (parser.seenval('E')) { const float v = parser.value_linear_units(); toolchange_settings.extra_prime = constrain(v, 0, max_extrude); } if (parser.seenval('P')) { const int16_t v = parser.value_linear_units(); toolchange_settings.prime_speed = constrain(v, 10, 5400); } + if (parser.seenval('G')) { const int16_t v = parser.value_linear_units(); toolchange_settings.wipe_retract = constrain(v, 0, 100); } if (parser.seenval('R')) { const int16_t v = parser.value_linear_units(); toolchange_settings.retract_speed = constrain(v, 10, 5400); } if (parser.seenval('U')) { const int16_t v = parser.value_linear_units(); toolchange_settings.unretract_speed = constrain(v, 10, 5400); } #if TOOLCHANGE_FS_FAN >= 0 && HAS_FAN - if (parser.seenval('F')) { const int16_t v = parser.value_linear_units(); toolchange_settings.fan_speed = constrain(v, 0, 255); } - if (parser.seenval('G')) { const int16_t v = parser.value_linear_units(); toolchange_settings.fan_time = constrain(v, 1, 30); } + if (parser.seenval('F')) { const uint16_t v = parser.value_ushort(); toolchange_settings.fan_speed = constrain(v, 0, 255); } + if (parser.seenval('D')) { const uint16_t v = parser.value_ushort(); toolchange_settings.fan_time = constrain(v, 1, 30); } #endif #endif @@ -127,11 +96,35 @@ void GcodeSuite::M217() { #if ENABLED(TOOLCHANGE_PARK) if (parser.seenval('W')) { toolchange_settings.enable_park = parser.value_linear_units(); } - if (parser.seenval('X')) { const int16_t v = parser.value_linear_units(); toolchange_settings.change_point.x = constrain(v, X_MIN_POS, X_MAX_POS); } - if (parser.seenval('Y')) { const int16_t v = parser.value_linear_units(); toolchange_settings.change_point.y = constrain(v, Y_MIN_POS, Y_MAX_POS); } + #if HAS_X_AXIS + if (parser.seenval('X')) { const int16_t v = parser.value_linear_units(); toolchange_settings.change_point.x = constrain(v, X_MIN_POS, X_MAX_POS); } + #endif + #if HAS_Y_AXIS + if (parser.seenval('Y')) { const int16_t v = parser.value_linear_units(); toolchange_settings.change_point.y = constrain(v, Y_MIN_POS, Y_MAX_POS); } + #endif + #if HAS_I_AXIS + if (parser.seenval('I')) { const int16_t v = parser.TERN(AXIS4_ROTATES, value_int, value_linear_units)(); toolchange_settings.change_point.i = constrain(v, I_MIN_POS, I_MAX_POS); } + #endif + #if HAS_J_AXIS + if (parser.seenval('J')) { const int16_t v = parser.TERN(AXIS5_ROTATES, value_int, value_linear_units)(); toolchange_settings.change_point.j = constrain(v, J_MIN_POS, J_MAX_POS); } + #endif + #if HAS_K_AXIS + if (parser.seenval('K')) { const int16_t v = parser.TERN(AXIS6_ROTATES, value_int, value_linear_units)(); toolchange_settings.change_point.k = constrain(v, K_MIN_POS, K_MAX_POS); } + #endif + #if HAS_U_AXIS + if (parser.seenval('C')) { const int16_t v = parser.TERN(AXIS7_ROTATES, value_int, value_linear_units)(); toolchange_settings.change_point.u = constrain(v, U_MIN_POS, U_MAX_POS); } + #endif + #if HAS_V_AXIS + if (parser.seenval('H')) { const int16_t v = parser.TERN(AXIS8_ROTATES, value_int, value_linear_units)(); toolchange_settings.change_point.v = constrain(v, V_MIN_POS, V_MAX_POS); } + #endif + #if HAS_W_AXIS + if (parser.seenval('O')) { const int16_t v = parser.TERN(AXIS9_ROTATES, value_int, value_linear_units)(); toolchange_settings.change_point.w = constrain(v, W_MIN_POS, W_MAX_POS); } + #endif #endif - if (parser.seenval('Z')) { toolchange_settings.z_raise = parser.value_linear_units(); } + #if HAS_Z_AXIS && HAS_TOOLCHANGE + if (parser.seenval('Z')) { toolchange_settings.z_raise = parser.value_linear_units(); } + #endif #if ENABLED(TOOLCHANGE_MIGRATION_FEATURE) migration.target = 0; // 0 = disabled @@ -170,4 +163,61 @@ void GcodeSuite::M217() { M217_report(); } +void GcodeSuite::M217_report(const bool forReplay/*=true*/) { + TERN_(MARLIN_SMALL_BUILD, return); + + report_heading_etc(forReplay, F(STR_TOOL_CHANGING)); + + SERIAL_ECHOPGM(" M217"); + + #if ENABLED(TOOLCHANGE_FILAMENT_SWAP) + SERIAL_ECHOPGM_P( + PSTR(" S"), LINEAR_UNIT(toolchange_settings.swap_length), + SP_B_STR, LINEAR_UNIT(toolchange_settings.extra_resume), + SP_E_STR, LINEAR_UNIT(toolchange_settings.extra_prime), + SP_P_STR, LINEAR_UNIT(toolchange_settings.prime_speed), + PSTR(" G"), LINEAR_UNIT(toolchange_settings.wipe_retract), + PSTR(" R"), LINEAR_UNIT(toolchange_settings.retract_speed), + PSTR(" U"), LINEAR_UNIT(toolchange_settings.unretract_speed), + PSTR(" F"), toolchange_settings.fan_speed, + PSTR(" D"), toolchange_settings.fan_time + ); + + #if ENABLED(TOOLCHANGE_MIGRATION_FEATURE) + SERIAL_ECHOPGM(" A", migration.automode, " L", LINEAR_UNIT(migration.last)); + #endif + + #if ENABLED(TOOLCHANGE_PARK) + SERIAL_ECHOPGM(" W", LINEAR_UNIT(toolchange_settings.enable_park)); + #if NUM_AXES + { + SERIAL_ECHOPGM_P( + SP_X_STR, LINEAR_UNIT(toolchange_settings.change_point.x) + #if HAS_Y_AXIS + , SP_Y_STR, LINEAR_UNIT(toolchange_settings.change_point.y) + #endif + #if SECONDARY_AXES >= 1 + , LIST_N(DOUBLE(SECONDARY_AXES) + , SP_I_STR, I_AXIS_UNIT(toolchange_settings.change_point.i) + , SP_J_STR, J_AXIS_UNIT(toolchange_settings.change_point.j) + , SP_K_STR, K_AXIS_UNIT(toolchange_settings.change_point.k) + , SP_C_STR, U_AXIS_UNIT(toolchange_settings.change_point.u) + , PSTR(" H"), V_AXIS_UNIT(toolchange_settings.change_point.v) + , PSTR(" O"), W_AXIS_UNIT(toolchange_settings.change_point.w) + ) + #endif + ); + } + #endif + #endif + + #if ENABLED(TOOLCHANGE_FS_PRIME_FIRST_USED) + SERIAL_ECHOPGM(" V", LINEAR_UNIT(enable_first_prime)); + #endif + + #endif + + SERIAL_ECHOLNPGM_P(SP_Z_STR, LINEAR_UNIT(toolchange_settings.z_raise)); +} + #endif // HAS_MULTI_EXTRUDER diff --git a/Marlin/src/gcode/config/M218.cpp b/Marlin/src/gcode/config/M218.cpp index 7701320e9e..7d167e502d 100644 --- a/Marlin/src/gcode/config/M218.cpp +++ b/Marlin/src/gcode/config/M218.cpp @@ -41,26 +41,20 @@ */ void GcodeSuite::M218() { + if (!parser.seen_any()) return M218_report(); + const int8_t target_extruder = get_target_extruder_from_command(); if (target_extruder < 0) return; - if (parser.seenval('X')) hotend_offset[target_extruder].x = parser.value_linear_units(); - if (parser.seenval('Y')) hotend_offset[target_extruder].y = parser.value_linear_units(); - if (parser.seenval('Z')) hotend_offset[target_extruder].z = parser.value_linear_units(); - - if (!parser.seen("XYZ")) { - SERIAL_ECHO_START(); - SERIAL_ECHOPGM(STR_HOTEND_OFFSET); - HOTEND_LOOP() { - SERIAL_CHAR(' '); - SERIAL_ECHO(hotend_offset[e].x); - SERIAL_CHAR(','); - SERIAL_ECHO(hotend_offset[e].y); - SERIAL_CHAR(','); - SERIAL_ECHO_F(hotend_offset[e].z, 3); - } - SERIAL_EOL(); - } + #if HAS_X_AXIS + if (parser.seenval('X')) 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(); + #endif + #if HAS_Z_AXIS + if (parser.seenval('Z')) hotend_offset[target_extruder].z = parser.value_linear_units(); + #endif #if ENABLED(DELTA) if (target_extruder == active_extruder) @@ -68,4 +62,19 @@ void GcodeSuite::M218() { #endif } +void GcodeSuite::M218_report(const bool forReplay/*=true*/) { + TERN_(MARLIN_SMALL_BUILD, return); + + 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) + ); + } +} + #endif // HAS_HOTEND_OFFSET diff --git a/Marlin/src/gcode/config/M220.cpp b/Marlin/src/gcode/config/M220.cpp index 1bec6a7782..0d1e204800 100644 --- a/Marlin/src/gcode/config/M220.cpp +++ b/Marlin/src/gcode/config/M220.cpp @@ -24,30 +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 - * - * With PRUSA_MMU2... - * 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, "%"); + return; + } - #if ENABLED(PRUSA_MMU2) - static int16_t backup_feedrate_percentage = 100; - if (parser.seen('B')) backup_feedrate_percentage = feedrate_percentage; - if (parser.seen('R')) feedrate_percentage = backup_feedrate_percentage; - #endif - + 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; + if (parser.seen_test('B')) backup_feedrate_percentage = now_feedrate_perc; if (parser.seenval('S')) feedrate_percentage = parser.value_int(); - if (!parser.seen_any()) { - SERIAL_ECHOPAIR("FR:", feedrate_percentage); - SERIAL_CHAR('%'); - SERIAL_EOL(); - } } diff --git a/Marlin/src/gcode/config/M221.cpp b/Marlin/src/gcode/config/M221.cpp index 7ba22d1901..f653aded7c 100644 --- a/Marlin/src/gcode/config/M221.cpp +++ b/Marlin/src/gcode/config/M221.cpp @@ -23,7 +23,7 @@ #include "../gcode.h" #include "../../module/planner.h" -#if EXTRUDERS +#if HAS_EXTRUDERS /** * M221: Set extrusion percentage (M221 T0 S95) @@ -38,7 +38,7 @@ void GcodeSuite::M221() { else { SERIAL_ECHO_START(); SERIAL_CHAR('E', '0' + target_extruder); - SERIAL_ECHOPAIR(" Flow: ", planner.flow_percentage[target_extruder]); + SERIAL_ECHOPGM(" Flow: ", planner.flow_percentage[target_extruder]); SERIAL_CHAR('%'); SERIAL_EOL(); } diff --git a/Marlin/src/gcode/config/M281.cpp b/Marlin/src/gcode/config/M281.cpp index 018ca1c092..24a179e54e 100644 --- a/Marlin/src/gcode/config/M281.cpp +++ b/Marlin/src/gcode/config/M281.cpp @@ -19,6 +19,7 @@ * along with this program. If not, see . * */ + #include "../../inc/MarlinConfig.h" #if ENABLED(EDITABLE_SERVO_ANGLES) @@ -34,7 +35,10 @@ * U - Stowed Angle */ void GcodeSuite::M281() { + if (!parser.seen_any()) return M281_report(); + if (!parser.seenval('P')) return; + const int servo_index = parser.value_int(); if (WITHIN(servo_index, 0, NUM_SERVOS - 1)) { #if ENABLED(BLTOUCH) @@ -43,25 +47,36 @@ void GcodeSuite::M281() { return; } #endif - bool angle_change = false; - if (parser.seen('L')) { - servo_angles[servo_index][0] = parser.value_int(); - angle_change = true; - } - if (parser.seen('U')) { - servo_angles[servo_index][1] = parser.value_int(); - angle_change = true; - } - if (!angle_change) { - SERIAL_ECHO_START(); - SERIAL_ECHOLNPAIR(" Servo ", servo_index, - " L", servo_angles[servo_index][0], - " U", servo_angles[servo_index][1]); - } + if (parser.seenval('L')) servo_angles[servo_index][0] = parser.value_int(); + if (parser.seenval('U')) servo_angles[servo_index][1] = parser.value_int(); } - else { - SERIAL_ERROR_START(); - SERIAL_ECHOLNPAIR("Servo ", servo_index, " out of range"); + else + SERIAL_ERROR_MSG("Servo ", servo_index, " out of range"); +} + +void GcodeSuite::M281_report(const bool forReplay/*=true*/) { + TERN_(MARLIN_SMALL_BUILD, return); + + report_heading_etc(forReplay, F(STR_SERVO_ANGLES)); + for (uint8_t i = 0; i < NUM_SERVOS; ++i) { + switch (i) { + default: break; + #if ENABLED(SWITCHING_EXTRUDER) + case SWITCHING_EXTRUDER_SERVO_NR: + #if EXTRUDERS > 3 + case SWITCHING_EXTRUDER_E23_SERVO_NR: + #endif + #elif ENABLED(SWITCHING_NOZZLE) + case SWITCHING_NOZZLE_SERVO_NR: + #if ENABLED(SWITCHING_NOZZLE_TWO_SERVOS) + case SWITCHING_NOZZLE_E1_SERVO_NR: + #endif + #elif ENABLED(BLTOUCH) || (HAS_Z_SERVO_PROBE && defined(Z_SERVO_ANGLES)) + case Z_PROBE_SERVO_NR: + #endif + report_echo_start(forReplay); + SERIAL_ECHOLNPGM(" M281 P", i, " L", servo_angles[i][0], " U", servo_angles[i][1]); + } } } diff --git a/Marlin/src/gcode/config/M301.cpp b/Marlin/src/gcode/config/M301.cpp index 7b3f57608b..a47c03a8d4 100644 --- a/Marlin/src/gcode/config/M301.cpp +++ b/Marlin/src/gcode/config/M301.cpp @@ -28,64 +28,82 @@ #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 // default behavior (omitting E parameter) is to update for extruder 0 only - const uint8_t e = parser.byteval('E'); // extruder being updated + int8_t e = E_TERN0(parser.byteval('E', -1)); // extruder being updated + + if (!parser.seen("PID" TERN_(PID_EXTRUSION_SCALING, "CL") TERN_(PID_FAN_SCALING, "F"))) + return M301_report(true E_OPTARG(e)); + + if (e == -1) e = 0; if (e < HOTENDS) { // catch bad input value - if (parser.seen('P')) PID_PARAM(Kp, e) = parser.value_float(); - if (parser.seen('I')) PID_PARAM(Ki, e) = scalePID_i(parser.value_float()); - if (parser.seen('D')) PID_PARAM(Kd, e) = scalePID_d(parser.value_float()); + + if (parser.seenval('P')) SET_HOTEND_PID(Kp, e, parser.value_float()); + if (parser.seenval('I')) SET_HOTEND_PID(Ki, e, parser.value_float()); + if (parser.seenval('D')) SET_HOTEND_PID(Kd, e, parser.value_float()); + #if ENABLED(PID_EXTRUSION_SCALING) - if (parser.seen('C')) PID_PARAM(Kc, e) = parser.value_float(); + if (parser.seenval('C')) SET_HOTEND_PID(Kc, e, parser.value_float()); if (parser.seenval('L')) thermalManager.lpq_len = parser.value_int(); - NOMORE(thermalManager.lpq_len, LPQ_MAX_LEN); - NOLESS(thermalManager.lpq_len, 0); + LIMIT(thermalManager.lpq_len, 0, LPQ_MAX_LEN); #endif #if ENABLED(PID_FAN_SCALING) - if (parser.seen('F')) PID_PARAM(Kf, e) = parser.value_float(); + if (parser.seenval('F')) SET_HOTEND_PID(Kf, e, parser.value_float()); #endif thermalManager.updatePID(); - - SERIAL_ECHO_START(); - #if ENABLED(PID_PARAMS_PER_HOTEND) - SERIAL_ECHOPAIR(" e:", e); // specify extruder in serial output - #endif - SERIAL_ECHOPAIR(" p:", PID_PARAM(Kp, e), - " i:", unscalePID_i(PID_PARAM(Ki, e)), - " d:", unscalePID_d(PID_PARAM(Kd, e))); - #if ENABLED(PID_EXTRUSION_SCALING) - SERIAL_ECHOPAIR(" c:", PID_PARAM(Kc, e)); - #endif - #if ENABLED(PID_FAN_SCALING) - SERIAL_ECHOPAIR(" f:", PID_PARAM(Kf, e)); - #endif - - SERIAL_EOL(); } else SERIAL_ERROR_MSG(STR_INVALID_EXTRUDER); } +void GcodeSuite::M301_report(const bool forReplay/*=true*/ E_OPTARG(const int8_t eindex/*=-1*/)) { + TERN_(MARLIN_SMALL_BUILD, return); + + report_heading(forReplay, F(STR_HOTEND_PID)); + IF_DISABLED(HAS_MULTI_EXTRUDER, constexpr int8_t eindex = -1); + HOTEND_LOOP() { + if (e == eindex || eindex == -1) { + const hotend_pid_t &pid = thermalManager.temp_hotend[e].pid; + report_echo_start(forReplay); + SERIAL_ECHOPGM_P( + #if ENABLED(PID_PARAMS_PER_HOTEND) + PSTR(" M301 E"), e, SP_P_STR + #else + PSTR(" M301 P") + #endif + , pid.p(), PSTR(" I"), pid.i(), PSTR(" D"), pid.d() + ); + #if ENABLED(PID_EXTRUSION_SCALING) + SERIAL_ECHOPGM_P(SP_C_STR, pid.c()); + if (e == 0) SERIAL_ECHOPGM(" L", thermalManager.lpq_len); + #endif + #if ENABLED(PID_FAN_SCALING) + SERIAL_ECHOPGM(" F", pid.f()); + #endif + SERIAL_EOL(); + } + } +} + #endif // PIDTEMP diff --git a/Marlin/src/gcode/config/M302.cpp b/Marlin/src/gcode/config/M302.cpp index afdc6c9e85..0648f3edca 100644 --- a/Marlin/src/gcode/config/M302.cpp +++ b/Marlin/src/gcode/config/M302.cpp @@ -25,6 +25,11 @@ #if ENABLED(PREVENT_COLD_EXTRUSION) #include "../gcode.h" + +#if ENABLED(EXTENSIBLE_UI) + #include "../../lcd/extui/ui_api.h" +#endif + #include "../../module/temperature.h" /** @@ -46,17 +51,17 @@ 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_(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_ECHOPGM("Cold extrudes are "); - serialprintPGM(thermalManager.allow_cold_extrude ? PSTR("en") : PSTR("dis")); - SERIAL_ECHOLNPAIR("abled (min temp ", thermalManager.extrude_min_temp, "C)"); + 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/M304.cpp b/Marlin/src/gcode/config/M304.cpp index 10739be3f8..8201730afd 100644 --- a/Marlin/src/gcode/config/M304.cpp +++ b/Marlin/src/gcode/config/M304.cpp @@ -35,14 +35,21 @@ * D - Set the D value */ void GcodeSuite::M304() { - if (parser.seen('P')) thermalManager.temp_bed.pid.Kp = parser.value_float(); - if (parser.seen('I')) thermalManager.temp_bed.pid.Ki = scalePID_i(parser.value_float()); - if (parser.seen('D')) thermalManager.temp_bed.pid.Kd = scalePID_d(parser.value_float()); + if (!parser.seen("PID")) return M304_report(); + if (parser.seenval('P')) thermalManager.temp_bed.pid.set_Kp(parser.value_float()); + if (parser.seenval('I')) thermalManager.temp_bed.pid.set_Ki(parser.value_float()); + if (parser.seenval('D')) thermalManager.temp_bed.pid.set_Kd(parser.value_float()); +} - SERIAL_ECHO_START(); - SERIAL_ECHOLNPAIR(" p:", thermalManager.temp_bed.pid.Kp, - " i:", unscalePID_i(thermalManager.temp_bed.pid.Ki), - " d:", unscalePID_d(thermalManager.temp_bed.pid.Kd)); +void GcodeSuite::M304_report(const bool forReplay/*=true*/) { + TERN_(MARLIN_SMALL_BUILD, return); + + report_heading_etc(forReplay, F(STR_BED_PID)); + SERIAL_ECHOLNPGM(" M304" + " P", thermalManager.temp_bed.pid.p() + , " I", thermalManager.temp_bed.pid.i() + , " D", thermalManager.temp_bed.pid.d() + ); } #endif // PIDTEMPBED diff --git a/Marlin/src/gcode/config/M305.cpp b/Marlin/src/gcode/config/M305.cpp index 3e7206aee4..48d7cf1882 100644 --- a/Marlin/src/gcode/config/M305.cpp +++ b/Marlin/src/gcode/config/M305.cpp @@ -49,33 +49,31 @@ void GcodeSuite::M305() { const bool do_set = parser.seen("BCRT"); // A valid P index is required - if (t_index >= (USER_THERMISTORS) || (do_set && t_index < 0)) { - SERIAL_ECHO_START(); - SERIAL_ECHOLNPAIR("!Invalid index. (0 <= P <= ", int(USER_THERMISTORS - 1), ")"); - } + if (t_index >= (USER_THERMISTORS) || (do_set && t_index < 0)) + SERIAL_ECHO_MSG("!Invalid index. (0 <= P <= ", USER_THERMISTORS - 1, ")"); else if (do_set) { - if (parser.seen('R')) // Pullup resistor value + if (parser.seenval('R')) // Pullup resistor value if (!thermalManager.set_pull_up_res(t_index, parser.value_float())) SERIAL_ECHO_MSG("!Invalid series resistance. (0 < R < 1000000)"); - if (parser.seen('T')) // Resistance at 25C + if (parser.seenval('T')) // Resistance at 25C if (!thermalManager.set_res25(t_index, parser.value_float())) SERIAL_ECHO_MSG("!Invalid 25C resistance. (0 < T < 10000000)"); - if (parser.seen('B')) // Beta value + if (parser.seenval('B')) // Beta value if (!thermalManager.set_beta(t_index, parser.value_float())) SERIAL_ECHO_MSG("!Invalid beta. (0 < B < 1000000)"); - if (parser.seen('C')) // Steinhart-Hart C coefficient + if (parser.seenval('C')) // Steinhart-Hart C coefficient if (!thermalManager.set_sh_coeff(t_index, parser.value_float())) SERIAL_ECHO_MSG("!Invalid Steinhart-Hart C coeff. (-0.01 < C < +0.01)"); } // If not setting then report parameters else if (t_index < 0) { // ...all user thermistors - LOOP_L_N(i, USER_THERMISTORS) - thermalManager.log_user_thermistor(i); + for (uint8_t i = 0; i < USER_THERMISTORS; ++i) + thermalManager.M305_report(i); } else // ...one user thermistor - thermalManager.log_user_thermistor(t_index); + thermalManager.M305_report(t_index); } #endif // HAS_USER_THERMISTORS diff --git a/Marlin/src/gcode/config/M309.cpp b/Marlin/src/gcode/config/M309.cpp new file mode 100644 index 0000000000..033f5731ed --- /dev/null +++ b/Marlin/src/gcode/config/M309.cpp @@ -0,0 +1,55 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2020 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(PIDTEMPCHAMBER) + +#include "../gcode.h" +#include "../../module/temperature.h" + +/** + * M309 - Set and/or Report the current Chamber PID values + * + * P - Set the P value + * I - Set the I value + * D - Set the D value + */ +void GcodeSuite::M309() { + if (!parser.seen("PID")) return M309_report(); + if (parser.seenval('P')) thermalManager.temp_chamber.pid.set_Kp(parser.value_float()); + if (parser.seenval('I')) thermalManager.temp_chamber.pid.set_Ki(parser.value_float()); + if (parser.seenval('D')) thermalManager.temp_chamber.pid.set_Kd(parser.value_float()); +} + +void GcodeSuite::M309_report(const bool forReplay/*=true*/) { + TERN_(MARLIN_SMALL_BUILD, return); + + report_heading_etc(forReplay, F(STR_CHAMBER_PID)); + SERIAL_ECHOLNPGM(" M309" + " P", thermalManager.temp_chamber.pid.p() + , " I", thermalManager.temp_chamber.pid.i() + , " D", thermalManager.temp_chamber.pid.d() + ); +} + +#endif // PIDTEMPCHAMBER diff --git a/Marlin/src/gcode/config/M43.cpp b/Marlin/src/gcode/config/M43.cpp index 1e780ca05a..9410fea136 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 #include "../../pins/pinsDebug.h" #include "../../module/endstops.h" @@ -47,7 +46,7 @@ #endif #if HAS_RESUME_CONTINUE - #include "../../lcd/ultralcd.h" + #include "../../lcd/marlinui.h" #endif #ifndef GET_PIN_MAP_PIN_M43 @@ -61,36 +60,38 @@ inline void toggle_pins() { end = PARSED_PIN_INDEX('L', NUM_DIGITAL_PINS - 1), wait = parser.intval('W', 500); - LOOP_S_LE_N(i, start, end) { + 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, PSTR("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 { - watchdog_refresh(); - report_pin_state_extended(pin, ignore_protection, true, PSTR("Pulsing ")); - #ifdef __STM32F1__ - const auto prior_mode = _GET_MODE(i); - #else - const bool prior_mode = GET_PINMODE(pin); - #endif + hal.watchdog_refresh(); + 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 @@ -98,10 +99,10 @@ inline void toggle_pins() { { pinMode(pin, OUTPUT); for (int16_t j = 0; j < repeat; j++) { - watchdog_refresh(); extDigitalWrite(pin, 0); safe_delay(wait); - watchdog_refresh(); extDigitalWrite(pin, 1); safe_delay(wait); - watchdog_refresh(); extDigitalWrite(pin, 0); safe_delay(wait); - watchdog_refresh(); + hal.watchdog_refresh(); extDigitalWrite(pin, 0); safe_delay(wait); + hal.watchdog_refresh(); extDigitalWrite(pin, 1); safe_delay(wait); + hal.watchdog_refresh(); extDigitalWrite(pin, 0); safe_delay(wait); + hal.watchdog_refresh(); } } #ifdef __STM32F1__ @@ -112,13 +113,13 @@ inline void toggle_pins() { } SERIAL_EOL(); } - SERIAL_ECHOLNPGM("Done."); + SERIAL_ECHOLNPGM(STR_DONE); } // toggle_pins inline void servo_probe_test() { - #if !(NUM_SERVOS > 0 && HAS_SERVO_0) + #if !HAS_SERVO_0 SERIAL_ERROR_MSG("SERVO not set up."); @@ -130,8 +131,8 @@ inline void servo_probe_test() { const uint8_t probe_index = parser.byteval('P', Z_PROBE_SERVO_NR); - SERIAL_ECHOLNPAIR("Servo probe test\n" - ". using index: ", int(probe_index), + SERIAL_ECHOLNPGM("Servo probe test\n" + ". using index: ", probe_index, ", deploy angle: ", servo_angles[probe_index][0], ", stow angle: ", servo_angles[probe_index][1] ); @@ -139,94 +140,83 @@ inline void servo_probe_test() { bool deploy_state = false, stow_state; #if ENABLED(Z_MIN_PROBE_USES_Z_MIN_ENDSTOP_PIN) - #define PROBE_TEST_PIN Z_MIN_PIN - constexpr bool probe_inverting = Z_MIN_ENDSTOP_INVERTING; - - SERIAL_ECHOLNPAIR(". Probe Z_MIN_PIN: ", int(PROBE_TEST_PIN)); - SERIAL_ECHOPGM(". Z_MIN_ENDSTOP_INVERTING: "); - + #define _PROBE_PREF "Z_MIN" #else - #define PROBE_TEST_PIN Z_MIN_PROBE_PIN - constexpr bool probe_inverting = Z_MIN_PROBE_ENDSTOP_INVERTING; - - SERIAL_ECHOLNPAIR(". Probe Z_MIN_PROBE_PIN: ", int(PROBE_TEST_PIN)); - SERIAL_ECHOPGM( ". Z_MIN_PROBE_ENDSTOP_INVERTING: "); - + #define _PROBE_PREF "Z_MIN_PROBE" #endif - serialprint_truefalse(probe_inverting); + SERIAL_ECHOLNPGM(". Probe " _PROBE_PREF "_PIN: ", PROBE_TEST_PIN); + serial_ternary(F(". " _PROBE_PREF "_ENDSTOP_HIT_STATE: "), PROBE_HIT_STATE, F("HIGH"), F("LOW")); SERIAL_EOL(); 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_inverting == READ(PROBE_TEST_PIN)) { - bltouch._set_SW_mode(); - if (probe_inverting != READ(PROBE_TEST_PIN)) { - bltouch._deploy(); - if (probe_inverting == READ(PROBE_TEST_PIN)) { - 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 - uint8_t i = 0; SERIAL_ECHOLNPGM(". Deploy & stow 4 times"); - do { - MOVE_SERVO(probe_index, servo_angles[Z_PROBE_SERVO_NR][0]); // Deploy + for (uint8_t i = 0; i < 4; ++i) { + servo[probe_index].move(servo_angles[Z_PROBE_SERVO_NR][0]); // Deploy safe_delay(500); deploy_state = READ(PROBE_TEST_PIN); - MOVE_SERVO(probe_index, servo_angles[Z_PROBE_SERVO_NR][1]); // Stow + servo[probe_index].move(servo_angles[Z_PROBE_SERVO_NR][1]); // Stow safe_delay(500); stow_state = READ(PROBE_TEST_PIN); - } while (++i < 4); + } - if (probe_inverting != deploy_state) SERIAL_ECHOLNPGM("WARNING: INVERTING setting probably backwards."); + if (PROBE_HIT_STATE == deploy_state) SERIAL_ECHOLNPGM("WARNING: " _PROBE_PREF "_ENDSTOP_HIT_STATE is probably wrong."); if (deploy_state != stow_state) { SERIAL_ECHOLNPGM("= Mechanical Switch detected"); if (deploy_state) { - SERIAL_ECHOLNPAIR(" DEPLOYED state: HIGH (logic 1)", - " STOWED (triggered) state: LOW (logic 0)"); + SERIAL_ECHOLNPGM(". DEPLOYED state: HIGH (logic 1)\n" + ". STOWED (triggered) state: LOW (logic 0)"); } else { - SERIAL_ECHOLNPAIR(" DEPLOYED state: LOW (logic 0)", - " STOWED (triggered) state: HIGH (logic 1)"); + SERIAL_ECHOLNPGM(". DEPLOYED state: LOW (logic 0)\n" + ". STOWED (triggered) state: HIGH (logic 1)"); } #if ENABLED(BLTOUCH) - SERIAL_ECHOLNPGM("FAIL: BLTOUCH enabled - Set up this device as a Servo Probe with INVERTING set to 'true'."); + SERIAL_ECHOLNPGM("FAIL: Can't enable BLTOUCH. Check your settings."); #endif return; } } // Ask the user for a trigger event and measure the pulse width. - MOVE_SERVO(probe_index, servo_angles[Z_PROBE_SERVO_NR][0]); // Deploy + servo[probe_index].move(servo_angles[Z_PROBE_SERVO_NR][0]); // Deploy safe_delay(500); SERIAL_ECHOLNPGM("** Please trigger probe within 30 sec **"); uint16_t probe_counter = 0; @@ -244,7 +234,7 @@ inline void servo_probe_test() { if (probe_counter == 15) SERIAL_ECHOLNPGM(": 30ms or more"); else - SERIAL_ECHOLNPAIR(" (+/- 4ms): ", probe_counter * 2); + SERIAL_ECHOLNPGM(" (+/- 4ms): ", probe_counter * 2); if (probe_counter >= 4) { if (probe_counter == 15) { @@ -256,7 +246,7 @@ inline void servo_probe_test() { } else SERIAL_ECHOLNPGM("FAIL: Noise detected - please re-run test"); - MOVE_SERVO(probe_index, servo_angles[Z_PROBE_SERVO_NR][1]); // Stow + servo[probe_index].move(servo_angles[Z_PROBE_SERVO_NR][1]); // Stow return; } } @@ -288,8 +278,8 @@ inline void servo_probe_test() { * S - Start Pin number. If not given, will default to 0 * L - End Pin number. If not given, will default to last pin defined for this board * I - Flag to ignore Marlin's pin protection. Use with caution!!!! - * R - Repeat pulses on each pin this number of times before continueing to next pin - * W - Wait time (in miliseconds) between pulses. If not given will default to 500 + * R - Repeat pulses on each pin this number of times before continuing to next pin + * W - Wait time (in milliseconds) between pulses. If not given will default to 500 * * M43 S - Servo probe test * P - Probe index (optional - defaults to 0 @@ -302,9 +292,7 @@ void GcodeSuite::M43() { // 'E' Enable or disable endstop monitoring and return if (parser.seen('E')) { endstops.monitor_flag = parser.value_bool(); - SERIAL_ECHOPGM("endstop monitor "); - serialprintPGM(endstops.monitor_flag ? PSTR("en") : PSTR("dis")); - SERIAL_ECHOLNPGM("abled"); + SERIAL_ECHOLN(F("endstop monitor "), endstops.monitor_flag ? F("en") : F("dis"), F("abled")); return; } @@ -313,72 +301,104 @@ void GcodeSuite::M43() { // 'P' Get the range of pins to test or watch uint8_t first_pin = PARSED_PIN_INDEX('P', 0), - last_pin = parser.seenval('P') ? first_pin : NUMBER_PINS_TOTAL - 1; + last_pin = parser.seenval('L') ? PARSED_PIN_INDEX('L', 0) : (parser.seenval('P') ? first_pin : (NUMBER_PINS_TOTAL) - 1); - if (first_pin > last_pin) return; + NOMORE(first_pin, (NUMBER_PINS_TOTAL) - 1); + NOMORE(last_pin, (NUMBER_PINS_TOTAL) - 1); + + if (first_pin > last_pin) { + const uint8_t f = first_pin; + first_pin = last_pin; + last_pin = f; + } // 'I' to ignore protected pins const bool ignore_protection = parser.boolval('I'); // 'W' Watch until click, M108, or reset if (parser.boolval('W')) { - SERIAL_ECHOLNPGM("Watching pins"); #ifdef ARDUINO_ARCH_SAM NOLESS(first_pin, 2); // Don't hijack the UART pins #endif - uint8_t pin_state[last_pin - first_pin + 1]; - LOOP_S_LE_N(i, first_pin, last_pin) { + + const uint8_t pin_count = last_pin - first_pin + 1; + uint8_t pin_state[pin_count]; + 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[...] - else - //*/ - pin_state[i - first_pin] = extDigitalRead(pin); + /* + if (isAnalogPin(pin)) + pin_state[pin - first_pin] = analogRead(digitalPinToAnalogIndex(pin)); // int16_t pin_state[...] + else + //*/ + pin_state[i - first_pin] = extDigitalRead(pin); } + const bool multipin = (pin_count > 1); + + if (!can_watch) { + SERIAL_ECHOPGM("Specified pin"); + SERIAL_ECHOPGM_P(multipin ? PSTR("s are") : PSTR(" is")); + SERIAL_ECHOLNPGM(" protected. Use 'I' to override."); + return; + } + + // "Watching pin(s) # - #" + SERIAL_ECHOPGM("Watching pin"); + if (multipin) SERIAL_CHAR('s'); + SERIAL_CHAR(' '); SERIAL_ECHO(first_pin); + if (multipin) SERIAL_ECHOPGM(" - ", last_pin); + SERIAL_EOL(); + #if HAS_RESUME_CONTINUE KEEPALIVE_STATE(PAUSED_FOR_USER); - wait_for_user = true; - TERN_(HOST_PROMPT_SUPPORT, host_prompt_do(PROMPT_USER_CONTINUE, PSTR("M43 Wait Called"), CONTINUE_STR)); - TERN_(EXTENSIBLE_UI, ExtUI::onUserConfirmRequired_P(PSTR("M43 Wait Called"))); + marlin.wait_start(); + TERN_(HOST_PROMPT_SUPPORT, hostui.continue_prompt(F("M43 Waiting..."))); + #if ENABLED(EXTENSIBLE_UI) + ExtUI::onUserConfirmRequired(F("M43 Waiting...")); + #else + LCD_MESSAGE(MSG_USERWAIT); + #endif #endif for (;;) { - LOOP_S_LE_N(i, first_pin, last_pin) { - 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; + for (uint8_t i = first_pin; i <= last_pin; ++i) { + const pin_t pin = GET_PIN_MAP_PIN_M43(i); + 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, false); + 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); } + + TERN_(HAS_RESUME_CONTINUE, ui.reset_status()); } else { // Report current state of selected pin(s) - LOOP_S_LE_N(i, first_pin, last_pin) { - pin_t pin = GET_PIN_MAP_PIN_M43(i); - if (VALID_PIN(pin)) report_pin_state_extended(pin, ignore_protection, true); + for (uint8_t i = first_pin; i <= last_pin; ++i) { + const pin_t pin = GET_PIN_MAP_PIN_M43(i); + if (isValidPin(pin)) printPinStateExt(pin, ignore_protection, true); } } } diff --git a/Marlin/src/gcode/config/M540.cpp b/Marlin/src/gcode/config/M540.cpp index 54d52f3a31..e751248dd6 100644 --- a/Marlin/src/gcode/config/M540.cpp +++ b/Marlin/src/gcode/config/M540.cpp @@ -25,7 +25,7 @@ #if ENABLED(SD_ABORT_ON_ENDSTOP_HIT) #include "../gcode.h" -#include "../../module/stepper.h" +#include "../../module/planner.h" /** * M540: Set whether SD card print should abort on endstop hit (M540 S<0|1>) diff --git a/Marlin/src/gcode/config/M550.cpp b/Marlin/src/gcode/config/M550.cpp new file mode 100644 index 0000000000..ed7dc49159 --- /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 44723b7f2f..dd8f8addb5 100644 --- a/Marlin/src/gcode/config/M575.cpp +++ b/Marlin/src/gcode/config/M575.cpp @@ -51,20 +51,26 @@ void GcodeSuite::M575() { switch (baud) { case 2400: case 9600: case 19200: case 38400: case 57600: case 115200: case 250000: case 500000: case 1000000: { - const int8_t port = parser.intval('P', -99); - const bool set0 = (port == -99 || port == 0); - if (set0) SERIAL_ECHO_MSG(" Serial ", '0', " baud rate set to ", baud); - #if HAS_MULTI_SERIAL - const bool set1 = (port == -99 || port == 1); - if (set1) SERIAL_ECHO_MSG(" Serial ", '1', " baud rate set to ", baud); - #endif - SERIAL_FLUSH(); - if (set0) { MYSERIAL0.end(); MYSERIAL0.begin(baud); } - + const int8_t port = parser.intval('P', -99); + const bool set1 = (port == -99 || port == 0); + if (set1) { MYSERIAL1.end(); MYSERIAL1.begin(baud); } #if HAS_MULTI_SERIAL - if (set1) { MYSERIAL1.end(); MYSERIAL1.begin(baud); } + const bool set2 = (port == -99 || port == 1); + if (set2) { MYSERIAL2.end(); MYSERIAL2.begin(baud); } + #ifdef SERIAL_PORT_3 + const bool set3 = (port == -99 || port == 2); + if (set3) { MYSERIAL3.end(); MYSERIAL3.begin(baud); } + #endif + #endif + + if (set1) SERIAL_ECHO_MSG(" Serial ", AS_DIGIT(0), " baud rate set to ", baud); + #if HAS_MULTI_SERIAL + if (set2) SERIAL_ECHO_MSG(" Serial ", AS_DIGIT(1), " baud rate set to ", baud); + #ifdef SERIAL_PORT_3 + if (set3) SERIAL_ECHO_MSG(" Serial ", AS_DIGIT(2), " baud rate set to ", baud); + #endif #endif } break; diff --git a/Marlin/src/gcode/config/M672.cpp b/Marlin/src/gcode/config/M672.cpp index af74230516..064d05d0b6 100644 --- a/Marlin/src/gcode/config/M672.cpp +++ b/Marlin/src/gcode/config/M672.cpp @@ -53,8 +53,8 @@ // b7 b6 b5 b4 ~b4 ... hi bits, NOT last bit // b3 b2 b1 b0 ~b0 ... lo bits, NOT last bit // -void M672_send(uint8_t b) { // bit rate requirement: 1KHz +/- 30% - LOOP_L_N(bits, 14) { +void M672_send(uint8_t b) { // bit rate requirement: 1kHz +/- 30% + for (uint8_t bits = 0; bits < 14; ++bits) { switch (bits) { default: { OUT_WRITE(SMART_EFFECTOR_MOD_PIN, !!(b & 0x80)); b <<= 1; break; } // send bit, shift next into place case 7: diff --git a/Marlin/src/gcode/config/M92.cpp b/Marlin/src/gcode/config/M92.cpp index 0a7d52b633..48c3c5f2f0 100644 --- a/Marlin/src/gcode/config/M92.cpp +++ b/Marlin/src/gcode/config/M92.cpp @@ -20,72 +20,54 @@ * */ +#include "../../inc/MarlinConfigPre.h" + +#if ENABLED(EDITABLE_STEPS_PER_UNIT) + #include "../gcode.h" #include "../../module/planner.h" -void report_M92(const bool echo=true, const int8_t e=-1) { - if (echo) SERIAL_ECHO_START(); else SERIAL_CHAR(' '); - SERIAL_ECHOPAIR_P(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])); - #if DISABLED(DISTINCT_E_FACTORS) - SERIAL_ECHOPAIR_P(SP_E_STR, VOLUMETRIC_UNIT(planner.settings.axis_steps_per_mm[E_AXIS])); - #endif - SERIAL_EOL(); - - #if ENABLED(DISTINCT_E_FACTORS) - LOOP_L_N(i, E_STEPPERS) { - if (e >= 0 && i != e) continue; - if (echo) SERIAL_ECHO_START(); else SERIAL_CHAR(' '); - SERIAL_ECHOLNPAIR_P(PSTR(" M92 T"), (int)i, - SP_E_STR, VOLUMETRIC_UNIT(planner.settings.axis_steps_per_mm[E_AXIS_N(i)])); - } - #endif - - UNUSED_E(e); -} - /** - * M92: Set axis steps-per-unit for one or more axes, X, Y, Z, and E. + * M92: Set axis steps-per-unit for one or more axes, X, Y, Z, [I, [J, [K, [U, [V, [W,]]]]]] and E. * (Follows the same syntax as G92) * * With multiple extruders use T to specify which one. * * If no argument is given print the current values. * - * With MAGIC_NUMBERS_GCODE: - * Use 'H' and/or 'L' to get ideal layer-height information. - * 'H' specifies micro-steps to use. We guess if it's not supplied. - * 'L' specifies a desired layer height. Nearest good heights are shown. + * With MAGIC_NUMBERS_GCODE: + * + * Use 'H' and/or 'L' to get ideal layer-height information. + * H - Specify micro-steps to use. Best guess if not supplied. + * L - Desired layer height in current units. Nearest good heights are shown. */ + void GcodeSuite::M92() { const int8_t target_extruder = get_target_extruder_from_command(); if (target_extruder < 0) return; // No arguments? Show M92 report. - if (!parser.seen("XYZE" - #if ENABLED(MAGIC_NUMBERS_GCODE) - "HL" - #endif - )) return report_M92(true, target_extruder); + if (!parser.seen(STR_AXES_LOGICAL TERN_(MAGIC_NUMBERS_GCODE, "HL"))) + return M92_report(true, target_extruder); - LOOP_XYZE(i) { - if (parser.seenval(axis_codes[i])) { - if (i == E_AXIS) { - 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 HAS_CLASSIC_JERK && HAS_CLASSIC_E_JERK - planner.max_jerk.e *= factor; - #endif - planner.settings.max_feedrate_mm_s[E_AXIS_N(target_extruder)] *= factor; - planner.max_acceleration_steps_per_s2[E_AXIS_N(target_extruder)] *= factor; - } - planner.settings.axis_steps_per_mm[E_AXIS_N(target_extruder)] = value; - } - else { + LOOP_LOGICAL_AXES(i) { + if (parser.seenval(AXIS_CHAR(i))) { + if (TERN1(HAS_EXTRUDERS, i != E_AXIS)) 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))); + 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) + planner.max_jerk.e *= factor; + #endif + planner.settings.max_feedrate_mm_s[E_AXIS_N(target_extruder)] *= factor; + planner.max_acceleration_steps_per_s2[E_AXIS_N(target_extruder)] *= factor; + } + planner.settings.axis_steps_per_mm[E_AXIS_N(target_extruder)] = value; + #endif } } } @@ -95,20 +77,62 @@ void GcodeSuite::M92() { #ifndef Z_MICROSTEPS #define Z_MICROSTEPS 16 #endif - const float wanted = parser.floatval('L'); + const float wanted = parser.linearval('L'); if (parser.seen('H') || wanted) { const uint16_t argH = parser.ushortval('H'), micro_steps = argH ?: Z_MICROSTEPS; - const float z_full_step_mm = micro_steps * planner.steps_to_mm[Z_AXIS]; + const float z_full_step_mm = micro_steps * planner.mm_per_step[Z_AXIS]; SERIAL_ECHO_START(); - SERIAL_ECHOPAIR("{ micro_steps:", micro_steps, ", z_full_step_mm:", z_full_step_mm); + SERIAL_ECHOPGM("{ micro_steps:", micro_steps, ", z_full_step_mm:", z_full_step_mm); if (wanted) { const float best = uint16_t(wanted / z_full_step_mm) * z_full_step_mm; - SERIAL_ECHOPAIR(", best:[", best); - if (best != wanted) { SERIAL_CHAR(','); SERIAL_DECIMAL(best + z_full_step_mm); } + SERIAL_ECHOPGM(", best:[", best); + if (best != wanted) { SERIAL_ECHO(C(','), best + z_full_step_mm); } SERIAL_CHAR(']'); } SERIAL_ECHOLNPGM(" }"); } #endif } + +void GcodeSuite::M92_report(const bool forReplay/*=true*/, const int8_t e/*=-1*/) { + TERN_(MARLIN_SMALL_BUILD, return); + + report_heading_etc(forReplay, F(STR_STEPS_PER_UNIT)); + #if NUM_AXES + #define PRINT_EOL + 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]), + SP_I_STR, I_AXIS_UNIT(planner.settings.axis_steps_per_mm[I_AXIS]), + SP_J_STR, J_AXIS_UNIT(planner.settings.axis_steps_per_mm[J_AXIS]), + SP_K_STR, K_AXIS_UNIT(planner.settings.axis_steps_per_mm[K_AXIS]), + SP_U_STR, U_AXIS_UNIT(planner.settings.axis_steps_per_mm[U_AXIS]), + SP_V_STR, V_AXIS_UNIT(planner.settings.axis_steps_per_mm[V_AXIS]), + SP_W_STR, W_AXIS_UNIT(planner.settings.axis_steps_per_mm[W_AXIS]) + )); + #endif + + #if HAS_EXTRUDERS && DISABLED(DISTINCT_E_FACTORS) + #define PRINT_EOL + SERIAL_ECHOPGM_P(SP_E_STR, VOLUMETRIC_UNIT(planner.settings.axis_steps_per_mm[E_AXIS])); + #endif + + if (ENABLED(PRINT_EOL)) SERIAL_EOL(); + + #if ENABLED(DISTINCT_E_FACTORS) + for (uint8_t i = 0; i < E_STEPPERS; ++i) { + if (e >= 0 && i != e) continue; + report_echo_start(forReplay); + SERIAL_ECHOLNPGM_P( + PSTR(" M92 T"), i, + SP_E_STR, VOLUMETRIC_UNIT(planner.settings.axis_steps_per_mm[E_AXIS_N(i)]) + ); + } + #else + UNUSED(e); + #endif +} + +#endif // EDITABLE_STEPS_PER_UNIT diff --git a/Marlin/src/gcode/control/M108_M112_M410.cpp b/Marlin/src/gcode/control/M108_M112_M410.cpp index df145d5d11..e2ca9b2247 100644 --- a/Marlin/src/gcode/control/M108_M112_M410.cpp +++ b/Marlin/src/gcode/control/M108_M112_M410.cpp @@ -21,25 +21,21 @@ */ #include "../../inc/MarlinConfig.h" - -#if DISABLED(EMERGENCY_PARSER) - #include "../gcode.h" -#include "../../MarlinCore.h" // for wait_for_heatup, kill, quickstop_stepper +#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(M112_KILL_STR, nullptr, true); + marlin.kill(FPSTR(M112_KILL_STR), nullptr, true); } /** @@ -51,5 +47,3 @@ void GcodeSuite::M112() { void GcodeSuite::M410() { quickstop_stepper(); } - -#endif // !EMERGENCY_PARSER diff --git a/Marlin/src/gcode/control/M10_M11.cpp b/Marlin/src/gcode/control/M10_M11.cpp new file mode 100644 index 0000000000..8fd299c546 --- /dev/null +++ b/Marlin/src/gcode/control/M10_M11.cpp @@ -0,0 +1,49 @@ +/** + * 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 . + * + */ + +/** + * gcode/control/M10_M11.cpp + * Air Evacuation + */ + +#include "../../inc/MarlinConfig.h" + +#if ENABLED(AIR_EVACUATION) + +#include "../gcode.h" +#include "../../feature/spindle_laser.h" + +/** + * M10: Vacuum or Blower On + */ +void GcodeSuite::M10() { + cutter.air_evac_enable(); // Turn on Vacuum or Blower motor +} + +/** + * M11: Vacuum or Blower OFF + */ +void GcodeSuite::M11() { + cutter.air_evac_disable(); // Turn off Vacuum or Blower motor +} + +#endif // AIR_EVACUATION diff --git a/Marlin/src/gcode/control/M111.cpp b/Marlin/src/gcode/control/M111.cpp index cc871bf38b..a8e549b69d 100644 --- a/Marlin/src/gcode/control/M111.cpp +++ b/Marlin/src/gcode/control/M111.cpp @@ -20,58 +20,64 @@ * */ +#include "../../inc/MarlinConfig.h" #include "../gcode.h" /** * M111: Set the debug level */ void GcodeSuite::M111() { - if (parser.seen('S')) marlin_debug_flags = parser.byteval('S'); - - static PGMSTR(str_debug_1, STR_DEBUG_ECHO); - static PGMSTR(str_debug_2, STR_DEBUG_INFO); - static PGMSTR(str_debug_4, STR_DEBUG_ERRORS); + if (parser.seenval('S')) marlin_debug_flags = parser.value_byte(); + #if ENABLED(DEBUG_FLAGS_GCODE) + static PGMSTR(str_debug_1, STR_DEBUG_ECHO); + static PGMSTR(str_debug_2, STR_DEBUG_INFO); + static PGMSTR(str_debug_4, STR_DEBUG_ERRORS); + #endif static PGMSTR(str_debug_8, STR_DEBUG_DRYRUN); - static PGMSTR(str_debug_16, STR_DEBUG_COMMUNICATION); + #if ENABLED(DEBUG_FLAGS_GCODE) + static PGMSTR(str_debug_16, STR_DEBUG_COMMUNICATION); + #endif #if ENABLED(DEBUG_LEVELING_FEATURE) - static PGMSTR(str_debug_lvl, STR_DEBUG_LEVELING); + static PGMSTR(str_debug_detail, STR_DEBUG_DETAIL); #endif static PGM_P const debug_strings[] PROGMEM = { - str_debug_1, str_debug_2, str_debug_4, str_debug_8, str_debug_16, - TERN_(DEBUG_LEVELING_FEATURE, str_debug_lvl) + TERN(DEBUG_FLAGS_GCODE, str_debug_1, nullptr), + TERN(DEBUG_FLAGS_GCODE, str_debug_2, nullptr), + TERN(DEBUG_FLAGS_GCODE, str_debug_4, nullptr), + str_debug_8, + TERN(DEBUG_FLAGS_GCODE, str_debug_16, nullptr), + TERN_(DEBUG_LEVELING_FEATURE, str_debug_detail) }; SERIAL_ECHO_START(); SERIAL_ECHOPGM(STR_DEBUG_PREFIX); if (marlin_debug_flags) { uint8_t comma = 0; - LOOP_L_N(i, COUNT(debug_strings)) { - if (TEST(marlin_debug_flags, i)) { + for (uint8_t i = 0; i < COUNT(debug_strings); ++i) { + PGM_P const pstr = (PGM_P)pgm_read_ptr(&debug_strings[i]); + if (pstr && TEST(marlin_debug_flags, i)) { if (comma++) SERIAL_CHAR(','); - serialprintPGM((char*)pgm_read_ptr(&debug_strings[i])); + SERIAL_ECHOPGM_P(pstr); } } } else { SERIAL_ECHOPGM(STR_DEBUG_OFF); - #if !IS_AT90USB + #if !(defined(__AVR__) && defined(USBCON)) #if ENABLED(SERIAL_STATS_RX_BUFFER_OVERRUNS) - SERIAL_ECHOPAIR("\nBuffer Overruns: ", MYSERIAL0.buffer_overruns()); + SERIAL_ECHOPGM("\nBuffer Overruns: ", MYSERIAL1.buffer_overruns()); #endif - #if ENABLED(SERIAL_STATS_RX_FRAMING_ERRORS) - SERIAL_ECHOPAIR("\nFraming Errors: ", MYSERIAL0.framing_errors()); + SERIAL_ECHOPGM("\nFraming Errors: ", MYSERIAL1.framing_errors()); #endif - #if ENABLED(SERIAL_STATS_DROPPED_RX) - SERIAL_ECHOPAIR("\nDropped bytes: ", MYSERIAL0.dropped()); + SERIAL_ECHOPGM("\nDropped bytes: ", MYSERIAL1.dropped()); #endif - #if ENABLED(SERIAL_STATS_MAX_RX_QUEUED) - SERIAL_ECHOPAIR("\nMax RX Queue Size: ", MYSERIAL0.rxMaxEnqueued()); + SERIAL_ECHOPGM("\nMax RX Queue Size: ", MYSERIAL1.rxMaxEnqueued()); #endif - #endif // !IS_AT90USB + #endif // !(__AVR__ && USBCON) } SERIAL_EOL(); } diff --git a/Marlin/src/gcode/control/M17_M18_M84.cpp b/Marlin/src/gcode/control/M17_M18_M84.cpp index 499feef715..90563889f3 100644 --- a/Marlin/src/gcode/control/M17_M18_M84.cpp +++ b/Marlin/src/gcode/control/M17_M18_M84.cpp @@ -22,48 +22,230 @@ #include "../gcode.h" #include "../../MarlinCore.h" // for stepper_inactive_time, disable_e_steppers -#include "../../lcd/ultralcd.h" +#include "../../lcd/marlinui.h" +#include "../../module/motion.h" // for e_axis_mask +#include "../../module/planner.h" #include "../../module/stepper.h" #if ENABLED(AUTO_BED_LEVELING_UBL) #include "../../feature/bedlevel/bedlevel.h" #endif -/** - * M17: Enable stepper motors - */ -void GcodeSuite::M17() { - if (parser.seen("XYZE")) { - if (parser.seen('X')) ENABLE_AXIS_X(); - if (parser.seen('Y')) ENABLE_AXIS_Y(); - if (parser.seen('Z')) ENABLE_AXIS_Z(); - if (TERN0(HAS_E_STEPPER_ENABLE, parser.seen('E'))) enable_e_steppers(); +#define DEBUG_OUT ENABLED(MARLIN_DEV_MODE) +#include "../../core/debug_out.h" +#include "../../libs/hex_print.h" + +inline stepper_flags_t selected_axis_bits() { + stepper_flags_t selected{0}; + #if HAS_EXTRUDERS + if (parser.seen('E')) { + if (E_TERN0(parser.has_value())) { + const uint8_t e = parser.value_int(); + if (e < EXTRUDERS) + selected.bits = _BV(INDEX_OF_AXIS(E_AXIS, e)); + } + else + selected.bits = e_axis_mask; + } + #endif + #if NUM_AXES + selected.bits |= NUM_AXIS_GANG( + (parser.seen_test('X') << X_AXIS), + | (parser.seen_test('Y') << Y_AXIS), + | (parser.seen_test('Z') << Z_AXIS), + | (parser.seen_test(AXIS4_NAME) << I_AXIS), + | (parser.seen_test(AXIS5_NAME) << J_AXIS), + | (parser.seen_test(AXIS6_NAME) << K_AXIS), + | (parser.seen_test(AXIS7_NAME) << U_AXIS), + | (parser.seen_test(AXIS8_NAME) << V_AXIS), + | (parser.seen_test(AXIS9_NAME) << W_AXIS) + ); + #endif + return selected; +} + +// Enable specified axes and warn about other affected axes +void do_enable(const stepper_flags_t to_enable) { + const ena_mask_t was_enabled = stepper.axis_enabled.bits, + shall_enable = to_enable.bits & ~was_enabled; + + DEBUG_ECHOLNPGM("Now Enabled: ", hex_word(stepper.axis_enabled.bits), " Enabling: ", hex_word(to_enable.bits), " | ", shall_enable); + + if (!shall_enable) return; // All specified axes already enabled? + + ena_mask_t also_enabled = 0; // Track steppers enabled due to overlap + + // Enable all flagged axes + LOOP_NUM_AXES(a) { + if (TEST(shall_enable, a)) { + 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]; + } } - else { - LCD_MESSAGEPGM(MSG_NO_MOVE); - enable_all_steppers(); + #if HAS_EXTRUDERS + EXTRUDER_LOOP() { + const uint8_t a = INDEX_OF_AXIS(E_AXIS, e); + if (TEST(shall_enable, a)) { + stepper.ENABLE_EXTRUDER(e); + DEBUG_ECHOLNPGM("Enabled E", AS_DIGIT(e), " (", a, ") with overlap ", hex_word(enable_overlap[a]), " ... ", hex_word(stepper.axis_enabled.bits)); + also_enabled |= enable_overlap[a]; + } + } + #endif + + if ((also_enabled &= ~(shall_enable | was_enabled))) { + SERIAL_CHAR('('); + LOOP_NUM_AXES(a) if (TEST(also_enabled, a)) SERIAL_CHAR(AXIS_CHAR(a), ' '); + #if HAS_EXTRUDERS + #define _EN_ALSO(N) if (TEST(also_enabled, INDEX_OF_AXIS(E_AXIS, N))) SERIAL_CHAR('E', '0' + N, ' '); + REPEAT(EXTRUDERS, _EN_ALSO) + #endif + SERIAL_ECHOLNPGM("also enabled)"); } + + DEBUG_ECHOLNPGM("Enabled Now: ", hex_word(stepper.axis_enabled.bits)); } /** - * M18, M84: Disable stepper motors + * M17: Enable stepper motor power for one or more axes. + * Print warnings for axes that share an ENABLE_PIN. + * + * Examples: + * + * M17 XZ ; Enable X and Z axes + * M17 E ; Enable all E steppers + * M17 E1 ; Enable just the E1 stepper + */ +void GcodeSuite::M17() { + if (parser.seen_axis()) { + if (any_enable_overlap()) + do_enable(selected_axis_bits()); + else { + #if HAS_EXTRUDERS + if (parser.seen('E')) { + if (parser.has_value()) { + const uint8_t e = parser.value_int(); + if (e < EXTRUDERS) stepper.ENABLE_EXTRUDER(e); + } + else + stepper.enable_e_steppers(); + } + #endif + LOOP_NUM_AXES(a) + if (parser.seen_test(AXIS_CHAR(a))) stepper.enable_axis((AxisEnum)a); + } + } + else { + LCD_MESSAGE(MSG_NO_MOVE); + stepper.enable_all_steppers(); + } +} + +void try_to_disable(const stepper_flags_t to_disable) { + ena_mask_t still_enabled = to_disable.bits & stepper.axis_enabled.bits; + + DEBUG_ECHOLNPGM("Enabled: ", hex_word(stepper.axis_enabled.bits), " To Disable: ", hex_word(to_disable.bits), " | ", hex_word(still_enabled)); + + if (!still_enabled) return; + + // Attempt to disable all flagged axes + 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 + DEBUG_ECHOPGM("OK"); + still_enabled &= ~(_BV(a) | enable_overlap[a]); // If actually disabled, clear one or more tracked bits + } + else + DEBUG_ECHOPGM("OVERLAP"); + DEBUG_ECHOLNPGM(" ... still_enabled=", hex_word(still_enabled)); + } + #if HAS_EXTRUDERS + EXTRUDER_LOOP() { + const uint8_t a = INDEX_OF_AXIS(E_AXIS, e); + if (TEST(to_disable.bits, a)) { + DEBUG_ECHOPGM("Try to disable E", AS_DIGIT(e), " (", a, ") with overlap ", hex_word(enable_overlap[a]), " ... "); + if (stepper.DISABLE_EXTRUDER(e)) { + DEBUG_ECHOPGM("OK"); + still_enabled &= ~(_BV(a) | enable_overlap[a]); + } + else + DEBUG_ECHOPGM("OVERLAP"); + DEBUG_ECHOLNPGM(" ... still_enabled=", hex_word(still_enabled)); + } + } + #endif + + auto overlap_warning = [](const ena_mask_t axis_bits) { + SERIAL_ECHOPGM(" not disabled. Shared with"); + LOOP_NUM_AXES(a) if (TEST(axis_bits, a)) SERIAL_ECHOPGM_P((PGM_P)pgm_read_ptr(&SP_AXIS_STR[a])); + #if HAS_EXTRUDERS + #define _EN_STILLON(N) if (TEST(axis_bits, INDEX_OF_AXIS(E_AXIS, N))) SERIAL_CHAR(' ', 'E', '0' + N); + REPEAT(EXTRUDERS, _EN_STILLON) + #endif + SERIAL_ECHOLNPGM("."); + }; + + // If any of the requested axes are still enabled, give a warning + LOOP_NUM_AXES(a) { + if (TEST(still_enabled, a)) { + SERIAL_CHAR(AXIS_CHAR(a)); + overlap_warning(stepper.axis_enabled.bits & enable_overlap[a]); + } + } + #if HAS_EXTRUDERS + EXTRUDER_LOOP() { + const uint8_t a = INDEX_OF_AXIS(E_AXIS, e); + if (TEST(still_enabled, a)) { + SERIAL_CHAR('E', '0' + e); + overlap_warning(stepper.axis_enabled.bits & enable_overlap[a]); + } + } + #endif + + DEBUG_ECHOLNPGM("Enabled Now: ", hex_word(stepper.axis_enabled.bits)); +} + +/** + * M18, M84: Disable stepper motor power for one or more axes. + * Print warnings for axes that share an ENABLE_PIN. */ void GcodeSuite::M18_M84() { if (parser.seenval('S')) { reset_stepper_timeout(); - stepper_inactive_time = parser.value_millis_from_seconds(); + #if HAS_DISABLE_IDLE_AXES + const millis_t ms = parser.value_millis_from_seconds(); + #if LASER_SAFETY_TIMEOUT_MS > 0 + if (ms && ms <= LASER_SAFETY_TIMEOUT_MS) { + SERIAL_ECHO_MSG("M18 timeout must be > ", MS_TO_SEC(LASER_SAFETY_TIMEOUT_MS + 999), " s for laser safety."); + return; + } + #endif + stepper_inactive_time = ms; + #endif } else { - if (parser.seen("XYZE")) { + if (parser.seen_axis()) { planner.synchronize(); - if (parser.seen('X')) DISABLE_AXIS_X(); - if (parser.seen('Y')) DISABLE_AXIS_Y(); - if (parser.seen('Z')) DISABLE_AXIS_Z(); - if (TERN0(HAS_E_STEPPER_ENABLE, parser.seen('E'))) disable_e_steppers(); + if (any_enable_overlap()) + try_to_disable(selected_axis_bits()); + else { + #if HAS_EXTRUDERS + if (parser.seen('E')) { + if (E_TERN0(parser.has_value())) + stepper.DISABLE_EXTRUDER(parser.value_int()); + else + stepper.disable_e_steppers(); + } + #endif + LOOP_NUM_AXES(a) + if (parser.seen_test(AXIS_CHAR(a))) stepper.disable_axis((AxisEnum)a); + } } else planner.finish_and_disable(); - TERN_(AUTO_BED_LEVELING_UBL, ubl.steppers_were_disabled()); + TERN_(AUTO_BED_LEVELING_UBL, bedlevel.steppers_were_disabled()); } } diff --git a/Marlin/src/gcode/control/M211.cpp b/Marlin/src/gcode/control/M211.cpp index 2049f1eb69..e51a9d5297 100644 --- a/Marlin/src/gcode/control/M211.cpp +++ b/Marlin/src/gcode/control/M211.cpp @@ -33,14 +33,23 @@ * Usage: M211 S1 to enable, M211 S0 to disable, M211 alone for report */ void GcodeSuite::M211() { - const xyz_pos_t l_soft_min = soft_endstop.min.asLogical(), - l_soft_max = soft_endstop.max.asLogical(); - SERIAL_ECHO_START(); - SERIAL_ECHOPGM(STR_SOFT_ENDSTOPS); - if (parser.seen('S')) soft_endstop._enabled = parser.value_bool(); - serialprint_onoff(soft_endstop._enabled); - print_xyz(l_soft_min, PSTR(STR_SOFT_MIN), PSTR(" ")); - print_xyz(l_soft_max, PSTR(STR_SOFT_MAX)); + if (parser.seen('S')) + soft_endstop._enabled = parser.value_bool(); + else + M211_report(); } -#endif +void GcodeSuite::M211_report(const bool forReplay/*=true*/) { + TERN_(MARLIN_SMALL_BUILD, return); + + report_heading_etc(forReplay, F(STR_SOFT_ENDSTOPS)); + SERIAL_ECHOLNPGM(" M211 S", AS_DIGIT(soft_endstop._enabled), " ; ", ON_OFF(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(); + print_xyz(l_soft_min, F(STR_SOFT_MIN), F(" ")); + print_xyz(l_soft_max, F(STR_SOFT_MAX)); +} + +#endif // HAS_SOFTWARE_ENDSTOPS diff --git a/Marlin/src/gcode/control/M226.cpp b/Marlin/src/gcode/control/M226.cpp index ad717e614d..921ce0d4c2 100644 --- a/Marlin/src/gcode/control/M226.cpp +++ b/Marlin/src/gcode/control/M226.cpp @@ -25,8 +25,9 @@ #if ENABLED(DIRECT_PIN_CONTROL) #include "../gcode.h" -#include "../../MarlinCore.h" // for pin_is_protected and idle() -#include "../../module/stepper.h" +#include "../../module/planner.h" + +void protected_pin_err(); /** * M226: Wait until the specified pin reaches the state required (M226 P S) @@ -38,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; @@ -49,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/M280.cpp b/Marlin/src/gcode/control/M280.cpp index 6ccbb7becc..82981e44bc 100644 --- a/Marlin/src/gcode/control/M280.cpp +++ b/Marlin/src/gcode/control/M280.cpp @@ -26,30 +26,51 @@ #include "../gcode.h" #include "../../module/servo.h" +#include "../../module/planner.h" /** - * M280: Get or set servo position. P [S] + * M280: Get or set servo position. + * P - Servo index + * S - Angle to set, omit to read current angle, or use -1 to detach + * + * With POLARGRAPH: + * T - Duration of servo move */ void GcodeSuite::M280() { - if (!parser.seen('P')) return; + + if (!parser.seenval('P')) return; + + TERN_(POLARGRAPH, planner.synchronize()); + const int servo_index = parser.value_int(); if (WITHIN(servo_index, 0, NUM_SERVOS - 1)) { - if (parser.seen('S')) { - const int a = parser.value_int(); - if (a == -1) - servo[servo_index].detach(); + if (parser.seenval('S')) { + const int anew = parser.value_int(); + if (anew >= 0) { + #if ENABLED(POLARGRAPH) + if (parser.seenval('T')) { // (ms) Total duration of servo move + const int16_t t = constrain(parser.value_int(), 0, 10000); + const int aold = servo[servo_index].read(); + millis_t now = millis(); + const millis_t start = now, end = start + t; + while (PENDING(now, end)) { + safe_delay(50); + now = _MIN(millis(), end); + servo[servo_index].move(LROUND(aold + (anew - aold) * (float(now - start) / t))); + } + } + #endif // POLARGRAPH + servo[servo_index].move(anew); + } else - MOVE_SERVO(servo_index, a); - } - else { - SERIAL_ECHO_START(); - SERIAL_ECHOLNPAIR(" Servo ", servo_index, ": ", servo[servo_index].read()); + servo[servo_index].detach(); } + else + SERIAL_ECHO_MSG(" Servo ", servo_index, ": ", servo[servo_index].read()); } - else { - SERIAL_ERROR_START(); - SERIAL_ECHOLNPAIR("Servo ", servo_index, " out of range"); - } + else + SERIAL_ERROR_MSG("Servo ", servo_index, " out of range"); + } #endif // HAS_SERVOS diff --git a/Marlin/src/gcode/control/M282.cpp b/Marlin/src/gcode/control/M282.cpp new file mode 100644 index 0000000000..3ac5ac9f5b --- /dev/null +++ b/Marlin/src/gcode/control/M282.cpp @@ -0,0 +1,45 @@ +/** + * 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 . + * + */ + +#include "../../inc/MarlinConfig.h" + +#if ENABLED(SERVO_DETACH_GCODE) + +#include "../gcode.h" +#include "../../module/servo.h" + +/** + * M282: Detach Servo. P + */ +void GcodeSuite::M282() { + + if (!parser.seenval('P')) return; + + const int servo_index = parser.value_int(); + if (WITHIN(servo_index, 0, NUM_SERVOS - 1)) + servo[servo_index].detach(); + else + SERIAL_ECHO_MSG("Servo ", servo_index, " out of range"); + +} + +#endif // SERVO_DETACH_GCODE diff --git a/Marlin/src/gcode/control/M3-M5.cpp b/Marlin/src/gcode/control/M3-M5.cpp index 1326c30669..9b6a2fb2f7 100644 --- a/Marlin/src/gcode/control/M3-M5.cpp +++ b/Marlin/src/gcode/control/M3-M5.cpp @@ -26,24 +26,32 @@ #include "../gcode.h" #include "../../feature/spindle_laser.h" -#include "../../module/stepper.h" +#include "../../module/planner.h" /** * Laser: * M3 - Laser ON/Power (Ramped power) - * M4 - Laser ON/Power (Continuous power) + * M4 - Laser ON/Power (Ramped power) + * + * M3I - Enable continuous inline power to be processed by the planner, with power + * calculated and set in the planner blocks, processed inline during stepping. + * In inline mode M3 S-Values will set the power for the next moves. + * (e.g., G1 X10 Y10 powers on with the last S-Value.) + * M3I must be set before using planner-synced M3 inline S-Values (LASER_POWER_SYNC). + * + * M4I - Set dynamic mode which calculates laser power OCR based on the current feedrate. * * Spindle: * M3 - Spindle ON (Clockwise) * M4 - Spindle ON (Counter-clockwise) * * Parameters: - * S - Set power. S0 will turn the spindle/laser off, except in relative mode. - * O - Set power and OCR (oscillator count register) + * 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. + * If no PWM pin is defined then M3/M4 just turns it on or off. * - * At least 12.8KHz (50Hz * 256) is needed for Spindle PWM. + * At least 12.8kHz (50Hz * 256) is needed for Spindle PWM. * Hardware PWM is required on AVR. ISRs are too slow. * * NOTE: WGM for timers 3, 4, and 5 must be either Mode 1 or Mode 5. @@ -66,69 +74,88 @@ * PWM duty cycle goes from 0 (off) to 255 (always on). */ void GcodeSuite::M3_M4(const bool is_M4) { + #if LASER_SAFETY_TIMEOUT_MS > 0 + reset_stepper_timeout(); // Reset timeout to allow subsequent G-code to power the laser (imm.) + #endif + + if (cutter.cutter_mode == CUTTER_MODE_STANDARD) + planner.synchronize(); // Wait for previous movement commands (G0/G1/G2/G3) to complete before changing power + + #if ENABLED(LASER_FEATURE) + if (parser.seen_test('I')) { + cutter.cutter_mode = is_M4 ? CUTTER_MODE_DYNAMIC : CUTTER_MODE_CONTINUOUS; + cutter.inline_power(0); + cutter.set_enabled(true); + } + #endif + auto get_s_power = [] { if (parser.seenval('S')) { - const float spwr = parser.value_float(); - cutter.unitPower = TERN(SPINDLE_LASER_PWM, - cutter.power_to_range(cutter_power_t(round(spwr))), - spwr > 0 ? 255 : 0); + const float v = parser.value_float(); + cutter.menuPower = cutter.unitPower = TERN(LASER_POWER_TRAP, constrain( v, 0, CUTTER_POWER_MAX), cutter.power_to_range(v)); } - else - cutter.unitPower = cutter.cpwr_to_upwr(SPEED_POWER_STARTUP); - return cutter.unitPower; + 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); + + // PWM not implied, power converted to OCR from unit definition and on/off if not PWM. + cutter.power = TERN(SPINDLE_LASER_USE_PWM, cutter.upower_to_ocr(cutter.unitPower), cutter.unitPower > 0 ? 255 : 0); }; - #if ENABLED(LASER_POWER_INLINE) - if (parser.seen('I') == DISABLED(LASER_POWER_INLINE_INVERT)) { - // Laser power in inline mode - cutter.inline_direction(is_M4); // Should always be unused - #if ENABLED(SPINDLE_LASER_PWM) - if (parser.seen('O')) { - cutter.unitPower = cutter.power_to_range(parser.value_byte(), 0); - cutter.inline_ocr_power(cutter.unitPower); // The OCR is a value from 0 to 255 (uint8_t) - } - else - cutter.inline_power(cutter.upower_to_ocr(get_s_power())); + if (cutter.cutter_mode == CUTTER_MODE_CONTINUOUS || cutter.cutter_mode == CUTTER_MODE_DYNAMIC) { // Laser power in inline mode + #if ENABLED(LASER_FEATURE) + planner.laser_inline.status.isPowered = true; // M3 or M4 is powered either way + get_s_power(); // Update cutter.power if seen + #if ENABLED(LASER_POWER_SYNC) + // With power sync we only set power so it does not effect queued inline power sets + planner.buffer_sync_block(BLOCK_BIT_LASER_PWR); // Send the flag, queueing inline power #else - cutter.set_inline_enabled(true); + planner.synchronize(); + cutter.inline_power(cutter.power); #endif - return; - } - // Non-inline, standard case - cutter.inline_disable(); // Prevent future blocks re-setting the power - #endif - - planner.synchronize(); // Wait for previous movement commands (G0/G0/G2/G3) to complete before changing power - cutter.set_direction(is_M4); - - #if ENABLED(SPINDLE_LASER_PWM) - if (parser.seenval('O')) { - cutter.unitPower = cutter.power_to_range(parser.value_byte(), 0); - cutter.set_ocr_power(cutter.unitPower); // The OCR is a value from 0 to 255 (uint8_t) - } - else - cutter.set_power(cutter.upower_to_ocr(get_s_power())); - #else + #endif + } + else { cutter.set_enabled(true); - #endif - cutter.menuPower = cutter.unitPower; + get_s_power(); + cutter.apply_power( + #if ENABLED(SPINDLE_SERVO) + cutter.unitPower + #elif ENABLED(SPINDLE_LASER_USE_PWM) + cutter.upower_to_ocr(cutter.unitPower) + #else + cutter.unitPower > 0 ? 255 : 0 + #endif + ); + TERN_(SPINDLE_CHANGE_DIR, cutter.set_reverse(is_M4)); + } } /** * M5 - Cutter OFF (when moves are complete) + * + * Laser: + * M5 - Set power output to 0 (leaving inline mode unchanged). + * M5I - Clear inline mode and set power to 0. + * + * Spindle: + * M5 - Spindle OFF */ void GcodeSuite::M5() { - #if ENABLED(LASER_POWER_INLINE) - if (parser.seen('I') == DISABLED(LASER_POWER_INLINE_INVERT)) { - cutter.set_inline_enabled(false); // Laser power in inline mode - return; - } - // Non-inline, standard case - cutter.inline_disable(); // Prevent future blocks re-setting the power - #endif planner.synchronize(); - cutter.set_enabled(false); - cutter.menuPower = cutter.unitPower; + cutter.power = 0; + cutter.apply_power(0); // M5 just kills power, leaving inline mode unchanged + if (cutter.cutter_mode != CUTTER_MODE_STANDARD) { + if (parser.seen_test('I')) { + TERN_(LASER_FEATURE, cutter.inline_power(cutter.power)); + cutter.set_enabled(false); // Needs to happen while we are in inline mode to clear inline power. + cutter.cutter_mode = CUTTER_MODE_STANDARD; // Switch from inline to standard mode. + } + } + cutter.set_enabled(false); // Disable enable output setting } #endif // HAS_CUTTER diff --git a/Marlin/src/gcode/control/M350_M351.cpp b/Marlin/src/gcode/control/M350_M351.cpp index 463bd2ad58..76753b2ea9 100644 --- a/Marlin/src/gcode/control/M350_M351.cpp +++ b/Marlin/src/gcode/control/M350_M351.cpp @@ -27,35 +27,42 @@ #include "../gcode.h" #include "../../module/stepper.h" +#if NUM_AXES == 3 && EXTRUDERS >= 1 + #define HAS_M350_B_PARAM 1 // "5th axis" (after E0) for an original XYZEB setup. +#endif + /** * M350: Set axis microstepping modes. S sets mode for all drivers. * * Warning: Steps-per-unit remains unchanged. */ void GcodeSuite::M350() { - if (parser.seen('S')) LOOP_LE_N(i, 4) stepper.microstep_mode(i, parser.value_byte()); - LOOP_XYZE(i) if (parser.seen(axis_codes[i])) stepper.microstep_mode(i, parser.value_byte()); - if (parser.seen('B')) stepper.microstep_mode(4, parser.value_byte()); + if (parser.seen('S')) LOOP_DISTINCT_AXES(i) stepper.microstep_mode(i, parser.value_byte()); + LOOP_LOGICAL_AXES(i) if (parser.seenval(AXIS_CHAR(i))) stepper.microstep_mode(i, parser.value_byte()); + TERN_(HAS_M350_B_PARAM, if (parser.seenval('B')) stepper.microstep_mode(E_AXIS + 1, parser.value_byte())); stepper.microstep_readings(); } /** - * M351: Toggle MS1 MS2 pins directly with axis codes X Y Z E B + * M351: Toggle MS1 MS2 pins directly with axis codes X Y Z . . . E [B] * S# determines MS1, MS2 or MS3, X# sets the pin high/low. + * + * Parameter 'B' sets "5th axis" (after E0) only for an original XYZEB setup. */ void GcodeSuite::M351() { + const int8_t bval = TERN(HAS_M350_B_PARAM, parser.byteval('B', -1), -1); UNUSED(bval); if (parser.seenval('S')) switch (parser.value_byte()) { case 1: - LOOP_XYZE(i) if (parser.seenval(axis_codes[i])) stepper.microstep_ms(i, parser.value_byte(), -1, -1); - if (parser.seenval('B')) stepper.microstep_ms(4, parser.value_byte(), -1, -1); + LOOP_LOGICAL_AXES(i) if (parser.seenval(AXIS_CHAR(i))) stepper.microstep_ms(i, parser.value_byte(), -1, -1); + TERN_(HAS_M350_B_PARAM, if (bval >= 0) stepper.microstep_ms(E_AXIS + 1, bval != 0, -1, -1)); break; case 2: - LOOP_XYZE(i) if (parser.seenval(axis_codes[i])) stepper.microstep_ms(i, -1, parser.value_byte(), -1); - if (parser.seenval('B')) stepper.microstep_ms(4, -1, parser.value_byte(), -1); + LOOP_LOGICAL_AXES(i) if (parser.seenval(AXIS_CHAR(i))) stepper.microstep_ms(i, -1, parser.value_byte(), -1); + TERN_(HAS_M350_B_PARAM, if (bval >= 0) stepper.microstep_ms(E_AXIS + 1, -1, bval != 0, -1)); break; case 3: - LOOP_XYZE(i) if (parser.seenval(axis_codes[i])) stepper.microstep_ms(i, -1, -1, parser.value_byte()); - if (parser.seenval('B')) stepper.microstep_ms(4, -1, -1, parser.value_byte()); + LOOP_LOGICAL_AXES(i) if (parser.seenval(AXIS_CHAR(i))) stepper.microstep_ms(i, -1, -1, parser.value_byte()); + TERN_(HAS_M350_B_PARAM, if (bval >= 0) stepper.microstep_ms(E_AXIS + 1, -1, -1, bval != 0)); break; } stepper.microstep_readings(); diff --git a/Marlin/src/gcode/control/M380_M381.cpp b/Marlin/src/gcode/control/M380_M381.cpp index 3f5b252465..20d24484ed 100644 --- a/Marlin/src/gcode/control/M380_M381.cpp +++ b/Marlin/src/gcode/control/M380_M381.cpp @@ -22,7 +22,7 @@ #include "../../inc/MarlinConfig.h" -#if EITHER(EXT_SOLENOID, MANUAL_SOLENOID_CONTROL) +#if ANY(EXT_SOLENOID, MANUAL_SOLENOID_CONTROL) #include "../gcode.h" #include "../../feature/solenoid.h" @@ -37,7 +37,7 @@ void GcodeSuite::M380() { #if ENABLED(MANUAL_SOLENOID_CONTROL) enable_solenoid(parser.intval('S', active_extruder)); #else - enable_solenoid_on_active_extruder(); + enable_solenoid(active_extruder); #endif } diff --git a/Marlin/src/gcode/control/M42.cpp b/Marlin/src/gcode/control/M42.cpp index c635c06ec6..717b0695a4 100644 --- a/Marlin/src/gcode/control/M42.cpp +++ b/Marlin/src/gcode/control/M42.cpp @@ -25,23 +25,35 @@ #if ENABLED(DIRECT_PIN_CONTROL) #include "../gcode.h" -#include "../../MarlinCore.h" // for pin_is_protected #if HAS_FAN #include "../../module/temperature.h" #endif +#ifdef MAPLE_STM32F1 + // these are enums on the F1... + #define INPUT_PULLDOWN INPUT_PULLDOWN + #define INPUT_ANALOG INPUT_ANALOG + #define OUTPUT_OPEN_DRAIN OUTPUT_OPEN_DRAIN +#endif + +void protected_pin_err() { + SERIAL_ERROR_MSG(STR_ERR_PROTECTED_PIN); +} + /** - * M42: Change pin status via GCode + * 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 * - * M Pin mode: 0=INPUT 1=OUTPUT 2=INPUT_PULLUP 3=INPUT_PULLDOWN + * 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)); @@ -49,17 +61,24 @@ 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(); - if (parser.seenval('M')) { + bool avoidWrite = false; + if (parser.seenval('T')) { switch (parser.value_byte()) { - case 0: pinMode(pin, INPUT); break; + case 0: pinMode(pin, INPUT); avoidWrite = true; break; case 1: pinMode(pin, OUTPUT); break; - case 2: pinMode(pin, INPUT_PULLUP); break; + case 2: pinMode(pin, INPUT_PULLUP); avoidWrite = true; break; #ifdef INPUT_PULLDOWN - case 3: pinMode(pin, INPUT_PULLDOWN); break; + case 3: pinMode(pin, INPUT_PULLDOWN); avoidWrite = true; break; #endif - default: SERIAL_ECHOLNPGM("Invalid Pin Mode"); return; + #ifdef INPUT_ANALOG + case 4: pinMode(pin, INPUT_ANALOG); avoidWrite = true; break; + #endif + #ifdef OUTPUT_OPEN_DRAIN + case 5: pinMode(pin, OUTPUT_OPEN_DRAIN); break; + #endif + default: SERIAL_ECHOLNPGM(GCODE_ERR_MSG("Invalid Pin Mode")); return; } } @@ -68,36 +87,28 @@ void GcodeSuite::M42() { #if HAS_FAN switch (pin) { - #if HAS_FAN0 - case FAN0_PIN: thermalManager.fan_speed[0] = pin_status; return; - #endif - #if HAS_FAN1 - case FAN1_PIN: thermalManager.fan_speed[1] = pin_status; return; - #endif - #if HAS_FAN2 - case FAN2_PIN: thermalManager.fan_speed[2] = pin_status; return; - #endif - #if HAS_FAN3 - case FAN3_PIN: thermalManager.fan_speed[3] = pin_status; return; - #endif - #if HAS_FAN4 - case FAN4_PIN: thermalManager.fan_speed[4] = pin_status; return; - #endif - #if HAS_FAN5 - case FAN5_PIN: thermalManager.fan_speed[5] = pin_status; return; - #endif - #if HAS_FAN6 - case FAN6_PIN: thermalManager.fan_speed[6] = pin_status; return; - #endif - #if HAS_FAN7 - case FAN7_PIN: thermalManager.fan_speed[7] = pin_status; return; - #endif + #define _CASE(N) case FAN##N##_PIN: thermalManager.fan_speed[N] = pin_status; return; + REPEAT(FAN_COUNT, _CASE) } #endif - pinMode(pin, OUTPUT); + if (avoidWrite) { + SERIAL_ECHOLNPGM(GCODE_ERR_MSG("Cannot write to INPUT")); + return; + } + + // An OUTPUT_OPEN_DRAIN should not be changed to normal OUTPUT (STM32) + // Use M42 Px T1/5 S0/1 to set the output type and then set value + #ifndef OUTPUT_OPEN_DRAIN + pinMode(pin, OUTPUT); + #endif extDigitalWrite(pin, pin_status); - analogWrite(pin, pin_status); + + #ifdef ARDUINO_ARCH_STM32 + // A simple I/O will be set to 0 by hal.set_pwm_duty() + if (pin_status <= 1 && !PWM_PIN(pin)) return; + #endif + hal.set_pwm_duty(pin, pin_status); } #endif // DIRECT_PIN_CONTROL diff --git a/Marlin/src/gcode/control/M605.cpp b/Marlin/src/gcode/control/M605.cpp index 5dc36428b5..bf5549262d 100644 --- a/Marlin/src/gcode/control/M605.cpp +++ b/Marlin/src/gcode/control/M605.cpp @@ -28,7 +28,6 @@ #include "../gcode.h" #include "../../module/motion.h" -#include "../../module/stepper.h" #include "../../module/tool_change.h" #include "../../module/planner.h" @@ -64,53 +63,59 @@ void GcodeSuite::M605() { planner.synchronize(); - if (parser.seen('S')) { + if (parser.seenval('S')) { const DualXMode previous_mode = dual_x_carriage_mode; dual_x_carriage_mode = (DualXMode)parser.value_byte(); - mirrored_duplication_mode = false; - - if (dual_x_carriage_mode == 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; - return; - } - mirrored_duplication_mode = true; - stepper.set_directions(); - float x_jog = current_position.x - .1; - for (uint8_t i = 2; --i;) { - planner.buffer_line(x_jog, current_position.y, current_position.z, current_position.e, feedrate_mm_s, 0); - x_jog += .1; - } - return; - } + idex_set_mirrored_mode(false); switch (dual_x_carriage_mode) { + case DXC_FULL_CONTROL_MODE: case DXC_AUTO_PARK_MODE: break; + case DXC_DUPLICATION_MODE: // Set the X offset, but no less than the safety gap - if (parser.seen('X')) duplicate_extruder_x_offset = _MAX(parser.value_linear_units(), (X2_MIN_POS) - (X1_MIN_POS)); - if (parser.seen('R')) duplicate_extruder_temp_offset = parser.value_celsius_diff(); + 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(); // Always switch back to tool 0 if (active_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; + return; + } + idex_set_mirrored_mode(true); + + // Do a small 'jog' motion in the X axis + xyze_pos_t dest = current_position; dest.x -= 0.1f; + for (uint8_t i = 2; --i;) { + planner.buffer_line(dest, feedrate_mm_s, 0); + dest.x += 0.1f; + } + } return; + default: dual_x_carriage_mode = DEFAULT_DUAL_X_CARRIAGE_MODE; break; } - active_extruder_parked = false; - extruder_duplication_enabled = false; - stepper.set_directions(); - delayed_move_time = 0; + + idex_set_parked(false); + set_duplication_enabled(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; - #ifdef DEBUG_DXC_MODE + #if ENABLED(DEBUG_DXC_MODE) if (parser.seen('W')) { DEBUG_ECHO_START(); @@ -121,26 +126,25 @@ case DXC_DUPLICATION_MODE: DEBUG_ECHOPGM("DUPLICATION"); break; case DXC_MIRRORED_MODE: DEBUG_ECHOPGM("MIRRORED"); break; } - DEBUG_ECHOPAIR("\nActive Ext: ", int(active_extruder)); - if (!active_extruder_parked) DEBUG_ECHOPGM(" NOT "); - DEBUG_ECHOPGM(" parked."); - DEBUG_ECHOPAIR("\nactive_extruder_x_pos: ", current_position.x); - DEBUG_ECHOPAIR("\ninactive_extruder_x_pos: ", inactive_extruder_x_pos); - DEBUG_ECHOPAIR("\nextruder_duplication_enabled: ", int(extruder_duplication_enabled)); - DEBUG_ECHOPAIR("\nduplicate_extruder_x_offset: ", duplicate_extruder_x_offset); - DEBUG_ECHOPAIR("\nduplicate_extruder_temp_offset: ", duplicate_extruder_temp_offset); - DEBUG_ECHOPAIR("\ndelayed_move_time: ", delayed_move_time); - DEBUG_ECHOPAIR("\nX1 Home X: ", x_home_pos(0), "\nX1_MIN_POS=", int(X1_MIN_POS), "\nX1_MAX_POS=", int(X1_MAX_POS)); - DEBUG_ECHOPAIR("\nX2 Home X: ", x_home_pos(1), "\nX2_MIN_POS=", int(X2_MIN_POS), "\nX2_MAX_POS=", int(X2_MAX_POS)); - DEBUG_ECHOPAIR("\nX2_HOME_DIR=", int(X2_HOME_DIR), "\nX2_HOME_POS=", int(X2_HOME_POS)); - DEBUG_ECHOPAIR("\nDEFAULT_DUAL_X_CARRIAGE_MODE=", STRINGIFY(DEFAULT_DUAL_X_CARRIAGE_MODE)); - DEBUG_ECHOPAIR("\toolchange_settings.z_raise=", toolchange_settings.z_raise); - DEBUG_ECHOPAIR("\nDEFAULT_DUPLICATION_X_OFFSET=", int(DEFAULT_DUPLICATION_X_OFFSET)); - DEBUG_EOL(); + DEBUG_ECHOPGM("\nActive Ext: ", active_extruder); + if (!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, + "\nDEFAULT_DUAL_X_CARRIAGE_MODE=", STRINGIFY(DEFAULT_DUAL_X_CARRIAGE_MODE), + "\toolchange_settings.z_raise=", toolchange_settings.z_raise, + "\nDEFAULT_DUPLICATION_X_OFFSET=", DEFAULT_DUPLICATION_X_OFFSET + ); HOTEND_LOOP() { - DEBUG_ECHOPAIR_P(SP_T_STR, int(e)); - LOOP_XYZ(a) DEBUG_ECHOPAIR(" hotend_offset[", int(e), "].", XYZ_CHAR(a) | 0x20, "=", hotend_offset[e][a]); + 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]); DEBUG_EOL(); } DEBUG_EOL(); @@ -151,26 +155,28 @@ #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 = pow(2, parser.value_int() + 1) - 1; // Set the mask by E index + 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)); - extruder_duplication_enabled = ena && (duplication_e_mask >= 3); + set_duplication_enabled(ena && (duplication_e_mask >= 3)); } SERIAL_ECHO_START(); - SERIAL_ECHOPGM(STR_DUPLICATION_MODE); - serialprint_onoff(extruder_duplication_enabled); + SERIAL_ECHOPGM(STR_DUPLICATION_MODE, ON_OFF(extruder_duplication_enabled)); if (ena) { SERIAL_ECHOPGM(" ( "); HOTEND_LOOP() if (TEST(duplication_e_mask, e)) { SERIAL_ECHO(e); SERIAL_CHAR(' '); } diff --git a/Marlin/src/gcode/control/M7-M9.cpp b/Marlin/src/gcode/control/M7-M9.cpp index a33e43288b..837bb114b2 100644 --- a/Marlin/src/gcode/control/M7-M9.cpp +++ b/Marlin/src/gcode/control/M7-M9.cpp @@ -20,9 +20,9 @@ * */ -#include "../../inc/MarlinConfig.h" +#include "../../inc/MarlinConfigPre.h" -#if ENABLED(COOLANT_CONTROL) +#if ANY(COOLANT_MIST, COOLANT_FLOOD, AIR_ASSIST) #include "../gcode.h" #include "../../module/planner.h" @@ -37,27 +37,41 @@ } #endif -#if ENABLED(COOLANT_FLOOD) +#if ANY(COOLANT_FLOOD, AIR_ASSIST) + + #if ENABLED(AIR_ASSIST) + #include "../../feature/spindle_laser.h" + #endif + /** - * M8: Flood Coolant On + * M8: Flood Coolant / Air Assist ON */ void GcodeSuite::M8() { - planner.synchronize(); // Wait for move to arrive - WRITE(COOLANT_FLOOD_PIN, !(COOLANT_FLOOD_INVERT)); // Turn on Flood coolant + planner.synchronize(); // Wait for move to arrive + #if ENABLED(COOLANT_FLOOD) + WRITE(COOLANT_FLOOD_PIN, !(COOLANT_FLOOD_INVERT)); // Turn on Flood coolant + #endif + #if ENABLED(AIR_ASSIST) + cutter.air_assist_enable(); // Turn on Air Assist + #endif } + #endif /** - * M9: Coolant OFF + * M9: Coolant / Air Assist OFF */ void GcodeSuite::M9() { - planner.synchronize(); // Wait for move to arrive + planner.synchronize(); // Wait for move to arrive #if ENABLED(COOLANT_MIST) - WRITE(COOLANT_MIST_PIN, COOLANT_MIST_INVERT); // Turn off Mist coolant + WRITE(COOLANT_MIST_PIN, COOLANT_MIST_INVERT); // Turn off Mist coolant #endif #if ENABLED(COOLANT_FLOOD) - WRITE(COOLANT_FLOOD_PIN, COOLANT_FLOOD_INVERT); // Turn off Flood coolant + WRITE(COOLANT_FLOOD_PIN, COOLANT_FLOOD_INVERT); // Turn off Flood coolant + #endif + #if ENABLED(AIR_ASSIST) + cutter.air_assist_disable(); // Turn off Air Assist #endif } -#endif // COOLANT_CONTROL +#endif // COOLANT_MIST | COOLANT_FLOOD | AIR_ASSIST diff --git a/Marlin/src/gcode/control/M80_M81.cpp b/Marlin/src/gcode/control/M80_M81.cpp index 6302bb5c67..a7653a4037 100644 --- a/Marlin/src/gcode/control/M80_M81.cpp +++ b/Marlin/src/gcode/control/M80_M81.cpp @@ -25,29 +25,25 @@ #include "../../module/temperature.h" #include "../../module/planner.h" // for planner.finish_and_disable #include "../../module/printcounter.h" // for print_job_timer.stop -#include "../../lcd/ultralcd.h" // for LCD_MESSAGEPGM_P +#include "../../lcd/marlinui.h" // for LCD_MESSAGE_F #include "../../inc/MarlinConfig.h" -#if HAS_SUICIDE +#if ENABLED(PSU_CONTROL) + #include "../queue.h" + #include "../../feature/power.h" +#endif + +#if ENABLED(POWER_LOSS_RECOVERY) + #include "../../feature/powerloss.h" +#endif + +#if ANY(HAS_SUICIDE, CONFIGURABLE_MACHINE_NAME) #include "../../MarlinCore.h" #endif #if ENABLED(PSU_CONTROL) - #if ENABLED(AUTO_POWER_CONTROL) - #include "../../feature/power.h" - #else - void restore_stepper_drivers(); - #endif - - // Could be moved to a feature, but this is all the data - bool powersupply_on; - - #if HAS_TRINAMIC_CONFIG - #include "../../feature/tmc_util.h" - #endif - /** * M80 : Turn on the Power Supply * M80 S : Report the current state and exit @@ -56,11 +52,11 @@ // S: Report the current power supply state and exit if (parser.seen('S')) { - serialprintPGM(powersupply_on ? PSTR("PS:1\n") : PSTR("PS:0\n")); + SERIAL_ECHO(powerManager.psu_on ? F("PS:1\n") : F("PS:0\n")); return; } - PSU_ON(); + powerManager.power_on(); /** * If you have a switch on suicide pin, this is useful @@ -68,16 +64,10 @@ * a print without suicide... */ #if HAS_SUICIDE - OUT_WRITE(SUICIDE_PIN, !SUICIDE_PIN_INVERTING); + OUT_WRITE(SUICIDE_PIN, !SUICIDE_PIN_STATE); #endif - #if DISABLED(AUTO_POWER_CONTROL) - safe_delay(PSU_POWERUP_DELAY); - restore_stepper_drivers(); - TERN_(HAS_TRINAMIC_CONFIG, safe_delay(PSU_POWERUP_DELAY)); - #endif - - TERN_(HAS_LCD_MENU, ui.reset_status()); + TERN_(HAS_MARLINUI_MENU, ui.reset_status()); } #endif // PSU_CONTROL @@ -88,25 +78,53 @@ * This code should ALWAYS be available for FULL SHUTDOWN! */ void GcodeSuite::M81() { - thermalManager.disable_all_heaters(); - print_job_timer.stop(); planner.finish_and_disable(); + thermalManager.cooldown(); - #if HAS_FAN - thermalManager.zero_fan_speeds(); - #if ENABLED(PROBING_FANS_OFF) - thermalManager.fans_paused = false; - ZERO(thermalManager.saved_fan_speed); - #endif + print_job_timer.stop(); + + #if ALL(HAS_FAN, PROBING_FANS_OFF) + thermalManager.fans_paused = false; + 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 - #if HAS_SUICIDE - suicide(); - #elif ENABLED(PSU_CONTROL) - PSU_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 - LCD_MESSAGEPGM_P(PSTR(MACHINE_NAME " " STR_OFF ".")); + bool delayed_power_off = false; + + #if ENABLED(POWER_OFF_TIMER) + if (parser.seenval('D')) { + uint16_t delay = parser.value_ushort(); + if (delay > 1) { // skip already observed 1s delay + delayed_power_off = true; + powerManager.setPowerOffTimer(SEC_TO_MS(delay - 1)); + } + } + #endif + + #if ENABLED(POWER_OFF_WAIT_FOR_COOLDOWN) + if (parser.boolval('S')) { + delayed_power_off = true; + powerManager.setPowerOffOnCooldown(true); + } + #endif + + if (delayed_power_off) { + SERIAL_ECHOLNPGM(STR_DELAYED_POWEROFF); + return; + } + + #if ENABLED(PSU_CONTROL) + powerManager.power_off_soon(); + #elif HAS_SUICIDE + marlin.suicide(); + #endif } diff --git a/Marlin/src/gcode/control/M85.cpp b/Marlin/src/gcode/control/M85.cpp index 9c8c02c59a..7846315413 100644 --- a/Marlin/src/gcode/control/M85.cpp +++ b/Marlin/src/gcode/control/M85.cpp @@ -29,7 +29,19 @@ void GcodeSuite::M85() { if (parser.seen('S')) { reset_stepper_timeout(); - max_inactive_time = parser.value_millis_from_seconds(); + const millis_t ms = parser.value_millis_from_seconds(); + #if LASER_SAFETY_TIMEOUT_MS > 0 + if (ms && ms <= LASER_SAFETY_TIMEOUT_MS) { + SERIAL_ECHO_MSG(GCODE_ERR_MSG("M85 timeout must be > ", MS_TO_SEC(LASER_SAFETY_TIMEOUT_MS + 999), " s for laser safety.")); + return; + } + #endif + max_inactive_time = ms; + } + else { + #if DISABLED(MARLIN_SMALL_BUILD) + SERIAL_ECHOLNPGM("Inactivity timeout ", MS_TO_SEC(max_inactive_time), " s."); + #endif } } diff --git a/Marlin/src/gcode/control/M993_M994.cpp b/Marlin/src/gcode/control/M993_M994.cpp index ff9ff85bf5..392c37234e 100644 --- a/Marlin/src/gcode/control/M993_M994.cpp +++ b/Marlin/src/gcode/control/M993_M994.cpp @@ -22,7 +22,7 @@ #include "../../inc/MarlinConfig.h" -#if ALL(HAS_SPI_FLASH, SDSUPPORT, MARLIN_DEV_MODE) +#if SPI_FLASH_BACKUP #include "../gcode.h" #include "../../sd/cardreader.h" @@ -37,7 +37,7 @@ void GcodeSuite::M993() { char fname[] = "spiflash.bin"; card.openFileWrite(fname); if (!card.isFileOpen()) { - SERIAL_ECHOLNPAIR("Failed to open ", fname, " to write."); + SERIAL_ECHOLNPGM("Failed to open ", fname, " to write."); return; } @@ -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"); @@ -65,7 +66,7 @@ void GcodeSuite::M994() { char fname[] = "spiflash.bin"; card.openFileRead(fname); if (!card.isFileOpen()) { - SERIAL_ECHOLNPAIR("Failed to open ", fname, " to read."); + SERIAL_ECHOLNPGM("Failed to open ", fname, " to read."); return; } @@ -78,11 +79,12 @@ 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"); card.closefile(); } -#endif // HAS_SPI_FLASH && SDSUPPORT && MARLIN_DEV_MODE +#endif // SPI_FLASH_BACKUP diff --git a/Marlin/src/gcode/control/M997.cpp b/Marlin/src/gcode/control/M997.cpp index cdff96f1ac..c651961902 100644 --- a/Marlin/src/gcode/control/M997.cpp +++ b/Marlin/src/gcode/control/M997.cpp @@ -24,11 +24,17 @@ #if ENABLED(PLATFORM_M997_SUPPORT) +#if ENABLED(EXTENSIBLE_UI) + #include "../../lcd/extui/ui_api.h" +#endif + /** * M997: Perform in-application firmware update */ void GcodeSuite::M997() { + 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 3bd908cad6..863ab8eb90 100644 --- a/Marlin/src/gcode/control/M999.cpp +++ b/Marlin/src/gcode/control/M999.cpp @@ -22,8 +22,8 @@ #include "../gcode.h" -#include "../../lcd/ultralcd.h" // for lcd_reset_alert_level -#include "../../MarlinCore.h" // for marlin_state +#include "../../lcd/marlinui.h" // for ui.reset_alert_level +#include "../../MarlinCore.h" // for setState #include "../queue.h" // for flush_and_request_resend /** @@ -36,10 +36,10 @@ * existing command buffer. */ void GcodeSuite::M999() { - marlin_state = MF_RUNNING; + marlin.setState(MF_RUNNING); ui.reset_alert_level(); if (parser.boolval('S')) return; - queue.flush_and_request_resend(); + queue.flush_and_request_resend(queue.ring_buffer.command_port()); } diff --git a/Marlin/src/gcode/control/T.cpp b/Marlin/src/gcode/control/T.cpp index d95e60ff8d..d5affa37e2 100644 --- a/Marlin/src/gcode/control/T.cpp +++ b/Marlin/src/gcode/control/T.cpp @@ -20,15 +20,21 @@ * */ +#include "../../inc/MarlinConfigPre.h" + +#if HAS_TOOLCHANGE + #include "../gcode.h" #include "../../module/tool_change.h" -#if EITHER(HAS_MULTI_EXTRUDER, DEBUG_LEVELING_FEATURE) +#if ANY(HAS_MULTI_EXTRUDER, DEBUG_LEVELING_FEATURE) #include "../../module/motion.h" #endif -#if ENABLED(PRUSA_MMU2) - #include "../../feature/mmu2/mmu2.h" +#if HAS_PRUSA_MMU3 + #include "../../feature/mmu3/mmu3.h" +#elif HAS_PRUSA_MMU2 + #include "../../feature/mmu/mmu2.h" #endif #define DEBUG_OUT ENABLED(DEBUG_LEVELING_FEATURE) @@ -37,40 +43,50 @@ /** * 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: - * T[n] Gcode to extrude at least 38.10 mm at feedrate 19.02 mm/s must follow immediately to load to extruder wheels. - * T? Gcode 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); + return; + } + #endif + DEBUG_SECTION(log_T, "T", DEBUGGING(LEVELING)); - if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPAIR("...(", tool_index, ")"); + if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("...(", tool_index, ")"); // Count this command as movement / activity reset_stepper_timeout(); - #if ENABLED(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; } #endif - #if EXTRUDERS < 2 - - tool_change(tool_index); - - #else - - tool_change( - tool_index, - (tool_index == active_extruder) || parser.boolval('S') - ); - - #endif + 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() + #endif + ); } + +#endif // HAS_TOOLCHANGE diff --git a/Marlin/src/gcode/eeprom/M500-M504.cpp b/Marlin/src/gcode/eeprom/M500-M504.cpp index 26c50a6129..ffa6d5c686 100644 --- a/Marlin/src/gcode/eeprom/M500-M504.cpp +++ b/Marlin/src/gcode/eeprom/M500-M504.cpp @@ -22,9 +22,13 @@ #include "../gcode.h" #include "../../module/settings.h" -#include "../../core/serial.h" #include "../../inc/MarlinConfig.h" +#if ENABLED(CONFIGURATION_EMBEDDING) + #include "../../sd/cardreader.h" + #include "../../mczip.h" +#endif + /** * M500: Store settings in EEPROM */ @@ -50,9 +54,32 @@ void GcodeSuite::M502() { /** * M503: print settings currently in memory + * + * S : Include / exclude header comments in the output. (Default: S1) + * + * With CONFIGURATION_EMBEDDING: + * C : Save the full Marlin configuration to SD Card as "mc.zip" */ void GcodeSuite::M503() { (void)settings.report(!parser.boolval('S', true)); + + #if ENABLED(CONFIGURATION_EMBEDDING) + if (parser.seen_test('C')) { + MediaFile file; + // Need to create the config size on the SD card + MediaFile root = card.getroot(); + if (file.open(&root, "mc.zip", O_WRITE|O_CREAT)) { + bool success = true; + for (uint16_t i = 0; success && i < sizeof(mc_zip); ++i) { + const uint8_t c = pgm_read_byte(&mc_zip[i]); + success = (file.write(c) == 1); + } + success = file.close() && success; + + if (success) SERIAL_ECHO_MSG("Configuration saved as 'mc.zip'"); + } + } + #endif } #endif // !DISABLE_M503 @@ -75,14 +102,14 @@ void GcodeSuite::M502() { if (dowrite) { val = parser.byteval('V'); persistentStore.write_data(addr, &val); - SERIAL_ECHOLNPAIR("Wrote address ", addr, " with ", int(val)); + SERIAL_ECHOLNPGM("Wrote address ", addr, " with ", val); } else { if (parser.seenval('T')) { const int endaddr = parser.value_ushort(); while (addr <= endaddr) { persistentStore.read_data(addr, &val); - SERIAL_ECHOLNPAIR("0x", hex_word(addr), ":", hex_byte(val)); + SERIAL_ECHOLNPGM("0x", hex_word(addr), ":", hex_byte(val)); addr++; safe_delay(10); } @@ -90,7 +117,7 @@ void GcodeSuite::M502() { } else { persistentStore.read_data(addr, &val); - SERIAL_ECHOLNPAIR("Read address ", addr, " and got ", int(val)); + SERIAL_ECHOLNPGM("Read address ", addr, " and got ", val); } } return; diff --git a/Marlin/src/gcode/feature/L6470/M122.cpp b/Marlin/src/gcode/feature/L6470/M122.cpp deleted file mode 100644 index d2b7f73997..0000000000 --- a/Marlin/src/gcode/feature/L6470/M122.cpp +++ /dev/null @@ -1,151 +0,0 @@ -/** - * Marlin 3D Printer Firmware - * Copyright (c) 2020 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_L64XX - -#include "../../gcode.h" -#include "../../../libs/L64XX/L64XX_Marlin.h" -#include "../../../module/stepper/indirection.h" - -void echo_yes_no(const bool yes); - -inline void L6470_say_status(const L64XX_axis_t axis) { - if (L64xxManager.spi_abort) return; - const L64XX_Marlin::L64XX_shadow_t &sh = L64xxManager.shadow; - L64xxManager.get_status(axis); - L64xxManager.say_axis(axis); - #if ENABLED(L6470_CHITCHAT) - char temp_buf[20]; - sprintf_P(temp_buf, PSTR(" status: %4x "), sh.STATUS_AXIS_RAW); - SERIAL_ECHO(temp_buf); - print_bin(sh.STATUS_AXIS_RAW); - switch (sh.STATUS_AXIS_LAYOUT) { - case L6470_STATUS_LAYOUT: serialprintPGM(PSTR(" L6470")); break; - case L6474_STATUS_LAYOUT: serialprintPGM(PSTR(" L6474")); break; - case L6480_STATUS_LAYOUT: serialprintPGM(PSTR(" L6480/powerSTEP01")); break; - } - #endif - SERIAL_ECHOPGM("\n...OUTPUT: "); - serialprintPGM(sh.STATUS_AXIS & STATUS_HIZ ? PSTR("OFF") : PSTR("ON ")); - SERIAL_ECHOPGM(" BUSY: "); echo_yes_no((sh.STATUS_AXIS & STATUS_BUSY) == 0); - SERIAL_ECHOPGM(" DIR: "); - serialprintPGM((((sh.STATUS_AXIS & STATUS_DIR) >> 4) ^ L64xxManager.index_to_dir[axis]) ? PSTR("FORWARD") : PSTR("REVERSE")); - if (sh.STATUS_AXIS_LAYOUT == L6480_STATUS_LAYOUT) { - SERIAL_ECHOPGM(" Last Command: "); - if (sh.STATUS_AXIS & sh.STATUS_AXIS_WRONG_CMD) SERIAL_ECHOPGM("VALID"); - else SERIAL_ECHOPGM("ERROR"); - SERIAL_ECHOPGM("\n...THERMAL: "); - switch ((sh.STATUS_AXIS & (sh.STATUS_AXIS_TH_SD | sh.STATUS_AXIS_TH_WRN)) >> 11) { - case 0: SERIAL_ECHOPGM("DEVICE SHUTDOWN"); break; - case 1: SERIAL_ECHOPGM("BRIDGE SHUTDOWN"); break; - case 2: SERIAL_ECHOPGM("WARNING "); break; - case 3: SERIAL_ECHOPGM("OK "); break; - } - } - else { - SERIAL_ECHOPGM(" Last Command: "); - if (!(sh.STATUS_AXIS & sh.STATUS_AXIS_WRONG_CMD)) SERIAL_ECHOPGM("IN"); - SERIAL_ECHOPGM("VALID "); - serialprintPGM(sh.STATUS_AXIS & sh.STATUS_AXIS_NOTPERF_CMD ? PSTR("COMPLETED ") : PSTR("Not PERFORMED")); - SERIAL_ECHOPAIR("\n...THERMAL: ", !(sh.STATUS_AXIS & sh.STATUS_AXIS_TH_SD) ? "SHUTDOWN " : !(sh.STATUS_AXIS & sh.STATUS_AXIS_TH_WRN) ? "WARNING " : "OK "); - } - SERIAL_ECHOPGM(" OVERCURRENT:"); echo_yes_no((sh.STATUS_AXIS & sh.STATUS_AXIS_OCD) == 0); - if (sh.STATUS_AXIS_LAYOUT != L6474_STATUS_LAYOUT) { - SERIAL_ECHOPGM(" STALL:"); echo_yes_no((sh.STATUS_AXIS & sh.STATUS_AXIS_STEP_LOSS_A) == 0 || (sh.STATUS_AXIS & sh.STATUS_AXIS_STEP_LOSS_B) == 0); - SERIAL_ECHOPGM(" STEP-CLOCK MODE:"); echo_yes_no((sh.STATUS_AXIS & sh.STATUS_AXIS_SCK_MOD) != 0); - } - else { - SERIAL_ECHOPGM(" STALL: NA " - " STEP-CLOCK MODE: NA" - " UNDER VOLTAGE LOCKOUT: "); echo_yes_no((sh.STATUS_AXIS & sh.STATUS_AXIS_UVLO) == 0); - } - SERIAL_EOL(); -} - -/** - * M122: Debug L6470 drivers - */ -void GcodeSuite::M122() { - L64xxManager.pause_monitor(true); // Keep monitor_driver() from stealing status - L64xxManager.spi_active = true; // Tell set_directions() a series of SPI transfers is underway - - //if (parser.seen('S')) - // tmc_set_report_interval(parser.value_bool()); - //else - - #if AXIS_IS_L64XX(X) - L6470_say_status(X); - #endif - #if AXIS_IS_L64XX(X2) - L6470_say_status(X2); - #endif - #if AXIS_IS_L64XX(Y) - L6470_say_status(Y); - #endif - #if AXIS_IS_L64XX(Y2) - L6470_say_status(Y2); - #endif - #if AXIS_IS_L64XX(Z) - L6470_say_status(Z); - #endif - #if AXIS_IS_L64XX(Z2) - L6470_say_status(Z2); - #endif - #if AXIS_IS_L64XX(Z3) - L6470_say_status(Z3); - #endif - #if AXIS_IS_L64XX(Z4) - L6470_say_status(Z4); - #endif - #if AXIS_IS_L64XX(E0) - L6470_say_status(E0); - #endif - #if AXIS_IS_L64XX(E1) - L6470_say_status(E1); - #endif - #if AXIS_IS_L64XX(E2) - L6470_say_status(E2); - #endif - #if AXIS_IS_L64XX(E3) - L6470_say_status(E3); - #endif - #if AXIS_IS_L64XX(E4) - L6470_say_status(E4); - #endif - #if AXIS_IS_L64XX(E5) - L6470_say_status(E5); - #endif - #if AXIS_IS_L64XX(E6) - L6470_say_status(E6); - #endif - #if AXIS_IS_L64XX(E7) - L6470_say_status(E7); - #endif - - L64xxManager.spi_active = false; // done with all SPI transfers - clear handshake flags - L64xxManager.spi_abort = false; - L64xxManager.pause_monitor(false); -} - -#endif // HAS_L64XX diff --git a/Marlin/src/gcode/feature/L6470/M906.cpp b/Marlin/src/gcode/feature/L6470/M906.cpp deleted file mode 100644 index 7bd446a1ab..0000000000 --- a/Marlin/src/gcode/feature/L6470/M906.cpp +++ /dev/null @@ -1,370 +0,0 @@ -/** - * Marlin 3D Printer Firmware - * Copyright (c) 2020 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_L64XX - -#include "../../gcode.h" -#include "../../../libs/L64XX/L64XX_Marlin.h" -#include "../../../module/stepper/indirection.h" -#include "../../../module/planner.h" - -#define DEBUG_OUT ENABLED(L6470_CHITCHAT) -#include "../../../core/debug_out.h" - -/** - * M906: report or set KVAL_HOLD which sets the maximum effective voltage provided by the - * PWMs to the steppers - * - * On L6474 this sets the TVAL register (same address). - * - * I - select which driver(s) to change on multi-driver axis - * 0 - (default) all drivers on the axis or E0 - * 1 - monitor only X, Y, Z or E1 - * 2 - monitor only X2, Y2, Z2 or E2 - * 3 - monitor only Z3 or E3 - * 4 - monitor only Z4 or E4 - * 5 - monitor only E5 - * Xxxx, Yxxx, Zxxx, Exxx - axis to change (optional) - * L6474 - current in mA (4A max) - * All others - 0-255 - */ - -/** - * Sets KVAL_HOLD wich affects the current being driven through the stepper. - * - * L6470 is used in the STEP-CLOCK mode. KVAL_HOLD is the only KVAL_xxx - * that affects the effective voltage seen by the stepper. - */ - -/** - * MACRO to fetch information on the items associated with current limiting - * and maximum voltage output. - * - * L6470 can be setup to shutdown if either current threshold is exceeded. - * - * L6470 output current can not be set directly. It is set indirectly by - * setting the maximum effective output voltage. - * - * Effective output voltage is set by PWM duty cycle. - * - * Maximum effective output voltage is affected by MANY variables. The main ones are: - * KVAL_HOLD - * KVAL_RUN - * KVAL_ACC - * KVAL_DEC - * Vs compensation (if enabled) - */ -void L64XX_report_current(L64XX &motor, const L64XX_axis_t axis) { - - if (L64xxManager.spi_abort) return; // don't do anything if set_directions() has occurred - - const L64XX_Marlin::L64XX_shadow_t &sh = L64xxManager.shadow; - const uint16_t status = L64xxManager.get_status(axis); //also populates shadow structure - const uint8_t OverCurrent_Threshold = uint8_t(motor.GetParam(L6470_OCD_TH)); - - auto say_axis_status = [](const L64XX_axis_t axis, const uint16_t status) { - L64xxManager.say_axis(axis); - #if ENABLED(L6470_CHITCHAT) - char tmp[10]; - sprintf_P(tmp, PSTR("%4x "), status); - DEBUG_ECHOPAIR(" status: ", tmp); - print_bin(status); - #else - UNUSED(status); - #endif - SERIAL_EOL(); - }; - - char temp_buf[10]; - - switch (sh.STATUS_AXIS_LAYOUT) { - case L6470_STATUS_LAYOUT: // L6470 - case L6480_STATUS_LAYOUT: { // L6480 & powerstep01 - const uint16_t Stall_Threshold = (uint8_t)motor.GetParam(L6470_STALL_TH), - motor_status = (status & (STATUS_MOT_STATUS)) >> 5, - L6470_ADC_out = motor.GetParam(L6470_ADC_OUT), - L6470_ADC_out_limited = constrain(L6470_ADC_out, 8, 24); - const float comp_coef = 1600.0f / L6470_ADC_out_limited; - const uint16_t MicroSteps = _BV(motor.GetParam(L6470_STEP_MODE) & 0x07); - - say_axis_status(axis, sh.STATUS_AXIS_RAW); - - SERIAL_ECHOPGM("...OverCurrent Threshold: "); - sprintf_P(temp_buf, PSTR("%2d ("), OverCurrent_Threshold); - SERIAL_ECHO(temp_buf); - SERIAL_ECHO((OverCurrent_Threshold + 1) * motor.OCD_CURRENT_CONSTANT_INV); - SERIAL_ECHOPGM(" mA)"); - SERIAL_ECHOPGM(" Stall Threshold: "); - sprintf_P(temp_buf, PSTR("%2d ("), Stall_Threshold); - SERIAL_ECHO(temp_buf); - SERIAL_ECHO((Stall_Threshold + 1) * motor.STALL_CURRENT_CONSTANT_INV); - SERIAL_ECHOPGM(" mA)"); - SERIAL_ECHOPGM(" Motor Status: "); - switch (motor_status) { - case 0: SERIAL_ECHOPGM("stopped"); break; - case 1: SERIAL_ECHOPGM("accelerating"); break; - case 2: SERIAL_ECHOPGM("decelerating"); break; - case 3: SERIAL_ECHOPGM("at constant speed"); break; - } - SERIAL_EOL(); - - SERIAL_ECHOPAIR("...MicroSteps: ", MicroSteps, - " ADC_OUT: ", L6470_ADC_out); - SERIAL_ECHOPGM(" Vs_compensation: "); - serialprintPGM((motor.GetParam(sh.L6470_AXIS_CONFIG) & CONFIG_EN_VSCOMP) ? PSTR("ENABLED ") : PSTR("DISABLED")); - SERIAL_ECHOLNPAIR(" Compensation coefficient: ~", comp_coef * 0.01f); - - SERIAL_ECHOPAIR("...KVAL_HOLD: ", motor.GetParam(L6470_KVAL_HOLD), - " KVAL_RUN : ", motor.GetParam(L6470_KVAL_RUN), - " KVAL_ACC: ", motor.GetParam(L6470_KVAL_ACC), - " KVAL_DEC: ", motor.GetParam(L6470_KVAL_DEC), - " V motor max = "); - switch (motor_status) { - case 0: SERIAL_ECHO(motor.GetParam(L6470_KVAL_HOLD) * 100 / 256); SERIAL_ECHOPGM("% (KVAL_HOLD)"); break; - case 1: SERIAL_ECHO(motor.GetParam(L6470_KVAL_RUN) * 100 / 256); SERIAL_ECHOPGM("% (KVAL_RUN)"); break; - case 2: SERIAL_ECHO(motor.GetParam(L6470_KVAL_ACC) * 100 / 256); SERIAL_ECHOPGM("% (KVAL_ACC)"); break; - case 3: SERIAL_ECHO(motor.GetParam(L6470_KVAL_DEC) * 100 / 256); SERIAL_ECHOPGM("% (KVAL_HOLD)"); break; - } - SERIAL_EOL(); - - #if ENABLED(L6470_CHITCHAT) - DEBUG_ECHOPGM("...SLEW RATE: "); - switch (sh.STATUS_AXIS_LAYOUT) { - case L6470_STATUS_LAYOUT: { - switch ((motor.GetParam(sh.L6470_AXIS_CONFIG) & CONFIG_POW_SR) >> CONFIG_POW_SR_BIT) { - case 0: { DEBUG_ECHOLNPGM("320V/uS") ; break; } - case 1: { DEBUG_ECHOLNPGM("75V/uS") ; break; } - case 2: { DEBUG_ECHOLNPGM("110V/uS") ; break; } - case 3: { DEBUG_ECHOLNPGM("260V/uS") ; break; } - } - break; - } - case L6480_STATUS_LAYOUT: { - switch (motor.GetParam(L6470_GATECFG1) & CONFIG1_SR ) { - case CONFIG1_SR_220V_us: { DEBUG_ECHOLNPGM("220V/uS") ; break; } - case CONFIG1_SR_400V_us: { DEBUG_ECHOLNPGM("400V/uS") ; break; } - case CONFIG1_SR_520V_us: { DEBUG_ECHOLNPGM("520V/uS") ; break; } - case CONFIG1_SR_980V_us: { DEBUG_ECHOLNPGM("980V/uS") ; break; } - default: { DEBUG_ECHOLNPGM("unknown") ; break; } - } - } - } - #endif - SERIAL_EOL(); - break; - } - - case L6474_STATUS_LAYOUT: { // L6474 - const uint16_t L6470_ADC_out = motor.GetParam(L6470_ADC_OUT) & 0x1F, - L6474_TVAL_val = motor.GetParam(L6474_TVAL) & 0x7F; - - say_axis_status(axis, sh.STATUS_AXIS_RAW); - - SERIAL_ECHOPGM("...OverCurrent Threshold: "); - sprintf_P(temp_buf, PSTR("%2d ("), OverCurrent_Threshold); - SERIAL_ECHO(temp_buf); - SERIAL_ECHO((OverCurrent_Threshold + 1) * motor.OCD_CURRENT_CONSTANT_INV); - SERIAL_ECHOPGM(" mA)"); - SERIAL_ECHOPGM(" TVAL: "); - sprintf_P(temp_buf, PSTR("%2d ("), L6474_TVAL_val); - SERIAL_ECHO(temp_buf); - SERIAL_ECHO((L6474_TVAL_val + 1) * motor.STALL_CURRENT_CONSTANT_INV); - SERIAL_ECHOLNPGM(" mA) Motor Status: NA"); - - const uint16_t MicroSteps = _BV(motor.GetParam(L6470_STEP_MODE) & 0x07); //NOMORE(MicroSteps, 16); - SERIAL_ECHOPAIR("...MicroSteps: ", MicroSteps, - " ADC_OUT: ", L6470_ADC_out); - - SERIAL_ECHOLNPGM(" Vs_compensation: NA\n"); - SERIAL_ECHOLNPGM("...KVAL_HOLD: NA" - " KVAL_RUN : NA" - " KVAL_ACC: NA" - " KVAL_DEC: NA" - " V motor max = NA"); - - #if ENABLED(L6470_CHITCHAT) - DEBUG_ECHOPGM("...SLEW RATE: "); - switch ((motor.GetParam(sh.L6470_AXIS_CONFIG) & CONFIG_POW_SR) >> CONFIG_POW_SR_BIT) { - case 0: DEBUG_ECHOLNPGM("320V/uS") ; break; - case 1: DEBUG_ECHOLNPGM("75V/uS") ; break; - case 2: DEBUG_ECHOLNPGM("110V/uS") ; break; - case 3: DEBUG_ECHOLNPGM("260V/uS") ; break; - default: DEBUG_ECHOLNPAIR("slew rate: ", (motor.GetParam(sh.L6470_AXIS_CONFIG) & CONFIG_POW_SR) >> CONFIG_POW_SR_BIT); break; - } - #endif - SERIAL_EOL(); - SERIAL_EOL(); - break; - } - } -} - -void GcodeSuite::M906() { - - L64xxManager.pause_monitor(true); // Keep monitor_driver() from stealing status - - #define L6470_SET_KVAL_HOLD(Q) (AXIS_IS_L64XX(Q) ? stepper##Q.setTVALCurrent(value) : stepper##Q.SetParam(L6470_KVAL_HOLD, uint8_t(value))) - - DEBUG_ECHOLNPGM("M906"); - - uint8_t report_current = true; - - #if HAS_L64XX - const uint8_t index = parser.byteval('I'); - #endif - - LOOP_XYZE(i) if (uint16_t value = parser.intval(axis_codes[i])) { - - report_current = false; - - if (planner.has_blocks_queued() || planner.cleaning_buffer_counter) { - SERIAL_ECHOLNPGM("Test aborted. Can't set KVAL_HOLD while steppers are moving."); - return; - } - - switch (i) { - case X_AXIS: - #if AXIS_IS_L64XX(X) - if (index == 0) L6470_SET_KVAL_HOLD(X); - #endif - #if AXIS_IS_L64XX(X2) - if (index == 1) L6470_SET_KVAL_HOLD(X2); - #endif - break; - case Y_AXIS: - #if AXIS_IS_L64XX(Y) - if (index == 0) L6470_SET_KVAL_HOLD(Y); - #endif - #if AXIS_IS_L64XX(Y2) - if (index == 1) L6470_SET_KVAL_HOLD(Y2); - #endif - break; - case Z_AXIS: - #if AXIS_IS_L64XX(Z) - if (index == 0) L6470_SET_KVAL_HOLD(Z); - #endif - #if AXIS_IS_L64XX(Z2) - if (index == 1) L6470_SET_KVAL_HOLD(Z2); - #endif - #if AXIS_IS_L64XX(Z3) - if (index == 2) L6470_SET_KVAL_HOLD(Z3); - #endif - #if AXIS_DRIVER_TYPE_Z4(L6470) - if (index == 3) L6470_SET_KVAL_HOLD(Z4); - #endif - break; - case E_AXIS: { - const int8_t target_extruder = get_target_extruder_from_command(); - if (target_extruder < 0) return; - switch (target_extruder) { - #if AXIS_IS_L64XX(E0) - case 0: L6470_SET_KVAL_HOLD(E0); break; - #endif - #if AXIS_IS_L64XX(E1) - case 1: L6470_SET_KVAL_HOLD(E1); break; - #endif - #if AXIS_IS_L64XX(E2) - case 2: L6470_SET_KVAL_HOLD(E2); break; - #endif - #if AXIS_IS_L64XX(E3) - case 3: L6470_SET_KVAL_HOLD(E3); break; - #endif - #if AXIS_IS_L64XX(E4) - case 4: L6470_SET_KVAL_HOLD(E4); break; - #endif - #if AXIS_IS_L64XX(E5) - case 5: L6470_SET_KVAL_HOLD(E5); break; - #endif - #if AXIS_IS_L64XX(E6) - case 6: L6470_SET_KVAL_HOLD(E6); break; - #endif - #if AXIS_IS_L64XX(E7) - case 7: L6470_SET_KVAL_HOLD(E7); break; - #endif - } - } break; - } - } - - if (report_current) { - #define L64XX_REPORT_CURRENT(Q) L64XX_report_current(stepper##Q, Q) - - L64xxManager.spi_active = true; // Tell set_directions() a series of SPI transfers is underway - - #if AXIS_IS_L64XX(X) - L64XX_REPORT_CURRENT(X); - #endif - #if AXIS_IS_L64XX(X2) - L64XX_REPORT_CURRENT(X2); - #endif - #if AXIS_IS_L64XX(Y) - L64XX_REPORT_CURRENT(Y); - #endif - #if AXIS_IS_L64XX(Y2) - L64XX_REPORT_CURRENT(Y2); - #endif - #if AXIS_IS_L64XX(Z) - L64XX_REPORT_CURRENT(Z); - #endif - #if AXIS_IS_L64XX(Z2) - L64XX_REPORT_CURRENT(Z2); - #endif - #if AXIS_IS_L64XX(Z3) - L64XX_REPORT_CURRENT(Z3); - #endif - #if AXIS_IS_L64XX(Z4) - L64XX_REPORT_CURRENT(Z4); - #endif - #if AXIS_IS_L64XX(E0) - L64XX_REPORT_CURRENT(E0); - #endif - #if AXIS_IS_L64XX(E1) - L64XX_REPORT_CURRENT(E1); - #endif - #if AXIS_IS_L64XX(E2) - L64XX_REPORT_CURRENT(E2); - #endif - #if AXIS_IS_L64XX(E3) - L64XX_REPORT_CURRENT(E3); - #endif - #if AXIS_IS_L64XX(E4) - L64XX_REPORT_CURRENT(E4); - #endif - #if AXIS_IS_L64XX(E5) - L64XX_REPORT_CURRENT(E5); - #endif - #if AXIS_IS_L64XX(E6) - L64XX_REPORT_CURRENT(E6); - #endif - #if AXIS_IS_L64XX(E7) - L64XX_REPORT_CURRENT(E7); - #endif - - L64xxManager.spi_active = false; // done with all SPI transfers - clear handshake flags - L64xxManager.spi_abort = false; - L64xxManager.pause_monitor(false); - } -} - -#endif // HAS_L64XX diff --git a/Marlin/src/gcode/feature/L6470/M916-918.cpp b/Marlin/src/gcode/feature/L6470/M916-918.cpp deleted file mode 100644 index 8165b71e44..0000000000 --- a/Marlin/src/gcode/feature/L6470/M916-918.cpp +++ /dev/null @@ -1,651 +0,0 @@ -/** - * Marlin 3D Printer Firmware - * Copyright (c) 2020 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 . - * - */ - -// -// NOTE: All tests assume each axis uses matching driver chips. -// - -#include "../../../inc/MarlinConfig.h" - -#if HAS_L64XX - -#include "../../gcode.h" -#include "../../../module/stepper/indirection.h" -#include "../../../module/planner.h" -#include "../../../libs/L64XX/L64XX_Marlin.h" - -#define DEBUG_OUT ENABLED(L6470_CHITCHAT) -#include "../../../core/debug_out.h" - -/** - * M916: increase KVAL_HOLD until get thermal warning - * NOTE - on L6474 it is TVAL that is used - * - * J - select which driver(s) to monitor on multi-driver axis - * 0 - (default) monitor all drivers on the axis or E0 - * 1 - monitor only X, Y, Z, E1 - * 2 - monitor only X2, Y2, Z2, E2 - * 3 - monitor only Z3, E3 - * 4 - monitor only Z4, E4 - * - * Xxxx, Yxxx, Zxxx, Exxx - axis to be monitored with displacement - * xxx (1-255) is distance moved on either side of current position - * - * F - feedrate - * optional - will use default max feedrate from configuration.h if not specified - * - * T - current (mA) setting for TVAL (0 - 4A in 31.25mA increments, rounds down) - L6474 only - * optional - will report current value from driver if not specified - * - * K - value for KVAL_HOLD (0 - 255) (ignored for L6474) - * optional - will report current value from driver if not specified - * - * D - time (in seconds) to run each setting of KVAL_HOLD/TVAL - * optional - defaults to zero (runs each setting once) - */ - -/** - * This routine is also useful for determining the approximate KVAL_HOLD - * where the stepper stops losing steps. The sound will get noticeably quieter - * as it stops losing steps. - */ - -void GcodeSuite::M916() { - - DEBUG_ECHOLNPGM("M916"); - - L64xxManager.pause_monitor(true); // Keep monitor_driver() from stealing status - - // Variables used by L64xxManager.get_user_input function - some may not be used - char axis_mon[3][3] = { {" "}, {" "}, {" "} }; // list of Axes to be monitored - L64XX_axis_t axis_index[3]; - uint16_t axis_status[3]; - uint8_t driver_count = 1; - float position_max; - float position_min; - float final_feedrate; - uint8_t kval_hold; - uint8_t OCD_TH_val = 0; - uint8_t STALL_TH_val = 0; - uint16_t over_current_threshold; - constexpr uint8_t over_current_flag = false; // M916 doesn't play with the overcurrent thresholds - - #define DRIVER_TYPE_L6474(Q) AXIS_DRIVER_TYPE_##Q(L6474) - - uint8_t j; // general purpose counter - - if (L64xxManager.get_user_input(driver_count, axis_index, axis_mon, position_max, position_min, final_feedrate, kval_hold, over_current_flag, OCD_TH_val, STALL_TH_val, over_current_threshold)) - return; // quit if invalid user input - - DEBUG_ECHOLNPAIR("feedrate = ", final_feedrate); - - planner.synchronize(); // wait for all current movement commands to complete - - const L64XX_Marlin::L64XX_shadow_t &sh = L64xxManager.shadow; - for (j = 0; j < driver_count; j++) - L64xxManager.get_status(axis_index[j]); // clear out any pre-existing error flags - - char temp_axis_string[] = " "; - temp_axis_string[0] = axis_mon[0][0]; // need to have a string for use within sprintf format section - char gcode_string[80]; - uint16_t status_composite = 0; - - uint16_t M91x_counter = kval_hold; - uint16_t M91x_counter_max; - if (sh.STATUS_AXIS_LAYOUT == L6474_STATUS_LAYOUT) { - M91x_counter_max = 128; // TVAL is 7 bits - LIMIT(M91x_counter, 0U, 127U); - } - else - M91x_counter_max = 256; // KVAL_HOLD is 8 bits - - uint8_t M91x_delay_s = parser.byteval('D'); // get delay in seconds - millis_t M91x_delay_ms = M91x_delay_s * 60 * 1000; - millis_t M91x_delay_end; - - DEBUG_ECHOLNPGM(".\n."); - - do { - - if (sh.STATUS_AXIS_LAYOUT == L6474_STATUS_LAYOUT) - DEBUG_ECHOLNPAIR("TVAL current (mA) = ", (M91x_counter + 1) * sh.AXIS_STALL_CURRENT_CONSTANT_INV); // report TVAL current for this run - else - DEBUG_ECHOLNPAIR("kval_hold = ", M91x_counter); // report KVAL_HOLD for this run - - for (j = 0; j < driver_count; j++) - L64xxManager.set_param(axis_index[j], L6470_KVAL_HOLD, M91x_counter); //set KVAL_HOLD or TVAL (same register address) - - M91x_delay_end = millis() + M91x_delay_ms; - do { - // turn the motor(s) both directions - sprintf_P(gcode_string, PSTR("G0 %s%03d F%03d"), temp_axis_string, uint16_t(position_min), uint16_t(final_feedrate)); - gcode.process_subcommands_now_P(gcode_string); - - sprintf_P(gcode_string, PSTR("G0 %s%03d F%03d"), temp_axis_string, uint16_t(position_max), uint16_t(final_feedrate)); - gcode.process_subcommands_now_P(gcode_string); - - // get the status after the motors have stopped - planner.synchronize(); - - status_composite = 0; // clear out the old bits - - for (j = 0; j < driver_count; j++) { - axis_status[j] = (~L64xxManager.get_status(axis_index[j])) & sh.L6470_ERROR_MASK; // bits of interest are all active low - status_composite |= axis_status[j] ; - } - - if (status_composite) break; - } while (millis() < M91x_delay_end); - - if (status_composite) break; - - M91x_counter++; - - } while (!(status_composite & (sh.STATUS_AXIS_TH_WRN | sh.STATUS_AXIS_TH_SD)) && (M91x_counter < M91x_counter_max)); - - DEBUG_ECHOLNPGM("."); - - #if ENABLED(L6470_CHITCHAT) - if (status_composite) { - L64xxManager.error_status_decode(status_composite, axis_index[0], - sh.STATUS_AXIS_TH_SD, sh.STATUS_AXIS_TH_WRN, - sh.STATUS_AXIS_STEP_LOSS_A, sh.STATUS_AXIS_STEP_LOSS_B, - sh.STATUS_AXIS_OCD, sh.STATUS_AXIS_LAYOUT); - DEBUG_ECHOLNPGM("."); - } - #endif - - if ((status_composite & (sh.STATUS_AXIS_TH_WRN | sh.STATUS_AXIS_TH_SD))) - DEBUG_ECHOLNPGM(".\n.\nTest completed normally - Thermal warning/shutdown has occurred"); - else if (status_composite) - DEBUG_ECHOLNPGM(".\n.\nTest completed abnormally - non-thermal error has occured"); - else - DEBUG_ECHOLNPGM(".\n.\nTest completed normally - Unable to get to thermal warning/shutdown"); - - L64xxManager.pause_monitor(false); -} - -/** - * M917: Find minimum current thresholds - * - * Decrease OCD current until overcurrent error - * Increase OCD until overcurrent error goes away - * Decrease stall threshold until stall (not done on L6474) - * Increase stall until stall error goes away (not done on L6474) - * - * J - select which driver(s) to monitor on multi-driver axis - * 0 - (default) monitor all drivers on the axis or E0 - * 1 - monitor only X, Y, Z, E1 - * 2 - monitor only X2, Y2, Z2, E2 - * Xxxx, Yxxx, Zxxx, Exxx - axis to be monitored with displacement - * xxx (1-255) is distance moved on either side of current position - * - * F - feedrate - * optional - will use default max feedrate from Configuration.h if not specified - * - * I - starting over-current threshold - * optional - will report current value from driver if not specified - * if there are multiple drivers on the axis then all will be set the same - * - * T - current (mA) setting for TVAL (0 - 4A in 31.25mA increments, rounds down) - L6474 only - * optional - will report current value from driver if not specified - * - * K - value for KVAL_HOLD (0 - 255) (ignored for L6474) - * optional - will report current value from driver if not specified - */ -void GcodeSuite::M917() { - - DEBUG_ECHOLNPGM("M917"); - - L64xxManager.pause_monitor(true); // Keep monitor_driver() from stealing status - - char axis_mon[3][3] = { {" "}, {" "}, {" "} }; // list of Axes to be monitored - L64XX_axis_t axis_index[3]; - uint16_t axis_status[3]; - uint8_t driver_count = 1; - float position_max; - float position_min; - float final_feedrate; - uint8_t kval_hold; - uint8_t OCD_TH_val = 0; - uint8_t STALL_TH_val = 0; - uint16_t over_current_threshold; - constexpr uint8_t over_current_flag = true; - - uint8_t j; // general purpose counter - - if (L64xxManager.get_user_input(driver_count, axis_index, axis_mon, position_max, position_min, final_feedrate, kval_hold, over_current_flag, OCD_TH_val, STALL_TH_val, over_current_threshold)) - return; // quit if invalid user input - - DEBUG_ECHOLNPAIR("feedrate = ", final_feedrate); - - planner.synchronize(); // wait for all current movement commands to complete - - const L64XX_Marlin::L64XX_shadow_t &sh = L64xxManager.shadow; - for (j = 0; j < driver_count; j++) - L64xxManager.get_status(axis_index[j]); // clear error flags - char temp_axis_string[] = " "; - temp_axis_string[0] = axis_mon[0][0]; // need a sprintf format string - char gcode_string[80]; - uint16_t status_composite = 0; - uint8_t test_phase = 0; // 0 - decreasing OCD - exit when OCD warning occurs (ignore STALL) - // 1 - increasing OCD - exit when OCD warning stops (ignore STALL) - // 2 - OCD finalized - decreasing STALL - exit when STALL warning happens - // 3 - OCD finalized - increasing STALL - exit when STALL warning stop - // 4 - all testing completed - DEBUG_ECHOPAIR(".\n.\n.\nover_current threshold : ", (OCD_TH_val + 1) * 375); // first status display - DEBUG_ECHOPAIR(" (OCD_TH: : ", OCD_TH_val); - if (sh.STATUS_AXIS_LAYOUT != L6474_STATUS_LAYOUT) { - DEBUG_ECHOPAIR(") Stall threshold: ", (STALL_TH_val + 1) * 31.25); - DEBUG_ECHOPAIR(" (STALL_TH: ", STALL_TH_val); - } - DEBUG_ECHOLNPGM(")"); - - do { - - if (sh.STATUS_AXIS_LAYOUT != L6474_STATUS_LAYOUT) DEBUG_ECHOPAIR("STALL threshold : ", (STALL_TH_val + 1) * 31.25); - DEBUG_ECHOLNPAIR(" OCD threshold : ", (OCD_TH_val + 1) * 375); - - sprintf_P(gcode_string, PSTR("G0 %s%03d F%03d"), temp_axis_string, uint16_t(position_min), uint16_t(final_feedrate)); - gcode.process_subcommands_now_P(gcode_string); - - sprintf_P(gcode_string, PSTR("G0 %s%03d F%03d"), temp_axis_string, uint16_t(position_max), uint16_t(final_feedrate)); - gcode.process_subcommands_now_P(gcode_string); - - planner.synchronize(); - - status_composite = 0; // clear out the old bits - - for (j = 0; j < driver_count; j++) { - axis_status[j] = (~L64xxManager.get_status(axis_index[j])) & sh.L6470_ERROR_MASK; // bits of interest are all active low - status_composite |= axis_status[j]; - } - - if (status_composite && (status_composite & sh.STATUS_AXIS_UVLO)) { - DEBUG_ECHOLNPGM("Test aborted (Undervoltage lockout active)"); - #if ENABLED(L6470_CHITCHAT) - for (j = 0; j < driver_count; j++) { - if (j) DEBUG_ECHOPGM("..."); - L64xxManager.error_status_decode(axis_status[j], axis_index[j], - sh.STATUS_AXIS_TH_SD, sh.STATUS_AXIS_TH_WRN, - sh.STATUS_AXIS_STEP_LOSS_A, sh.STATUS_AXIS_STEP_LOSS_B, - sh.STATUS_AXIS_OCD, sh.STATUS_AXIS_LAYOUT); - } - #endif - return; - } - - if (status_composite & (sh.STATUS_AXIS_TH_WRN | sh.STATUS_AXIS_TH_SD)) { - DEBUG_ECHOLNPGM("thermal problem - waiting for chip(s) to cool down "); - uint16_t status_composite_temp = 0; - uint8_t k = 0; - do { - k++; - if (!(k % 4)) { - kval_hold *= 0.95; - DEBUG_EOL(); - DEBUG_ECHOLNPAIR("Lowering KVAL_HOLD by about 5% to ", kval_hold); - for (j = 0; j < driver_count; j++) - L64xxManager.set_param(axis_index[j], L6470_KVAL_HOLD, kval_hold); - } - DEBUG_ECHOLNPGM("."); - gcode.reset_stepper_timeout(); // reset_stepper_timeout to keep steppers powered - watchdog_refresh();; // beat the dog - safe_delay(5000); - status_composite_temp = 0; - for (j = 0; j < driver_count; j++) { - axis_status[j] = (~L64xxManager.get_status(axis_index[j])) & sh.L6470_ERROR_MASK; // bits of interest are all active low - status_composite_temp |= axis_status[j]; - } - } - while (status_composite_temp & (sh.STATUS_AXIS_TH_WRN | sh.STATUS_AXIS_TH_SD)); - DEBUG_EOL(); - } - if (status_composite & (sh.STATUS_AXIS_STEP_LOSS_A | sh.STATUS_AXIS_STEP_LOSS_B | sh.STATUS_AXIS_OCD)) { - switch (test_phase) { - - case 0: { - if (status_composite & sh.STATUS_AXIS_OCD) { - // phase 0 with OCD warning - time to go to next phase - if (OCD_TH_val >= sh.AXIS_OCD_TH_MAX) { - OCD_TH_val = sh.AXIS_OCD_TH_MAX; // limit to max - test_phase = 2; // at highest value so skip phase 1 - //DEBUG_ECHOLNPGM("LOGIC E0A OCD at highest - skip to 2"); - DEBUG_ECHOLNPGM("OCD at highest - OCD finalized"); - } - else { - OCD_TH_val++; // normal exit to next phase - test_phase = 1; // setup for first pass of phase 1 - //DEBUG_ECHOLNPGM("LOGIC E0B - inc OCD & go to 1"); - DEBUG_ECHOLNPGM("inc OCD"); - } - } - else { // phase 0 without OCD warning - keep on decrementing if can - if (OCD_TH_val) { - OCD_TH_val--; // try lower value - //DEBUG_ECHOLNPGM("LOGIC E0C - dec OCD"); - DEBUG_ECHOLNPGM("dec OCD"); - } - else { - test_phase = 2; // at lowest value without warning so skip phase 1 - //DEBUG_ECHOLNPGM("LOGIC E0D - OCD at latest - go to 2"); - DEBUG_ECHOLNPGM("OCD finalized"); - } - } - } break; - - case 1: { - if (status_composite & sh.STATUS_AXIS_OCD) { - // phase 1 with OCD warning - increment if can - if (OCD_TH_val >= sh.AXIS_OCD_TH_MAX) { - OCD_TH_val = sh.AXIS_OCD_TH_MAX; // limit to max - test_phase = 2; // at highest value so go to next phase - //DEBUG_ECHOLNPGM("LOGIC E1A - OCD at max - go to 2"); - DEBUG_ECHOLNPGM("OCD finalized"); - } - else { - OCD_TH_val++; // try a higher value - //DEBUG_ECHOLNPGM("LOGIC E1B - inc OCD"); - DEBUG_ECHOLNPGM("inc OCD"); - } - } - else { // phase 1 without OCD warning - normal exit to phase 2 - test_phase = 2; - //DEBUG_ECHOLNPGM("LOGIC E1C - no OCD warning - go to 1"); - DEBUG_ECHOLNPGM("OCD finalized"); - } - } break; - - case 2: { - if (sh.STATUS_AXIS_LAYOUT == L6474_STATUS_LAYOUT) { // skip all STALL_TH steps if L6474 - test_phase = 4; - break; - } - if (status_composite & (sh.STATUS_AXIS_STEP_LOSS_A | sh.STATUS_AXIS_STEP_LOSS_B)) { - // phase 2 with stall warning - time to go to next phase - if (STALL_TH_val >= 127) { - STALL_TH_val = 127; // limit to max - //DEBUG_ECHOLNPGM("LOGIC E2A - STALL warning, STALL at max, quit"); - DEBUG_ECHOLNPGM("finished - STALL at maximum value but still have stall warning"); - test_phase = 4; - } - else { - test_phase = 3; // normal exit to next phase (found failing value of STALL) - STALL_TH_val++; // setup for first pass of phase 3 - //DEBUG_ECHOLNPGM("LOGIC E2B - INC - STALL warning, inc Stall, go to 3"); - DEBUG_ECHOLNPGM("inc Stall"); - } - } - else { // phase 2 without stall warning - decrement if can - if (STALL_TH_val) { - STALL_TH_val--; // try a lower value - //DEBUG_ECHOLNPGM("LOGIC E2C - no STALL, dec STALL"); - DEBUG_ECHOLNPGM("dec STALL"); - } - else { - DEBUG_ECHOLNPGM("finished - STALL at lowest value but still do NOT have stall warning"); - test_phase = 4; - //DEBUG_ECHOLNPGM("LOGIC E2D - no STALL, at lowest so quit"); - } - } - } break; - - case 3: { - if (sh.STATUS_AXIS_LAYOUT == L6474_STATUS_LAYOUT) { // skip all STALL_TH steps if L6474 - test_phase = 4; - break; - } - if (status_composite & (sh.STATUS_AXIS_STEP_LOSS_A | sh.STATUS_AXIS_STEP_LOSS_B)) { - // phase 3 with stall warning - increment if can - if (STALL_TH_val >= 127) { - STALL_TH_val = 127; // limit to max - DEBUG_ECHOLNPGM("finished - STALL at maximum value but still have stall warning"); - test_phase = 4; - //DEBUG_ECHOLNPGM("LOGIC E3A - STALL, at max so quit"); - } - else { - STALL_TH_val++; // still looking for passing value - //DEBUG_ECHOLNPGM("LOGIC E3B - STALL, inc stall"); - DEBUG_ECHOLNPGM("inc stall"); - } - } - else { //phase 3 without stall warning but have OCD warning - DEBUG_ECHOLNPGM("Hardware problem - OCD warning without STALL warning"); - test_phase = 4; - //DEBUG_ECHOLNPGM("LOGIC E3C - not STALLED, hardware problem (quit)"); - } - } break; - - } - - } - else { - switch (test_phase) { - case 0: { // phase 0 without OCD warning - keep on decrementing if can - if (OCD_TH_val) { - OCD_TH_val--; // try lower value - //DEBUG_ECHOLNPGM("LOGIC N0A - DEC OCD"); - DEBUG_ECHOLNPGM("DEC OCD"); - } - else { - test_phase = 2; // at lowest value without warning so skip phase 1 - //DEBUG_ECHOLNPGM("LOGIC N0B - OCD at lowest (go to phase 2)"); - DEBUG_ECHOLNPGM("OCD finalized"); - } - } break; - - case 1: //DEBUG_ECHOLNPGM("LOGIC N1 (go directly to 2)"); // phase 1 without OCD warning - drop directly to phase 2 - DEBUG_ECHOLNPGM("OCD finalized"); - - case 2: { // phase 2 without stall warning - keep on decrementing if can - if (sh.STATUS_AXIS_LAYOUT == L6474_STATUS_LAYOUT) { // skip all STALL_TH steps if L6474 - test_phase = 4; - break; - } - if (STALL_TH_val) { - STALL_TH_val--; // try a lower value (stay in phase 2) - //DEBUG_ECHOLNPGM("LOGIC N2B - dec STALL"); - DEBUG_ECHOLNPGM("dec STALL"); - } - else { - DEBUG_ECHOLNPGM("finished - STALL at lowest value but still no stall warning"); - test_phase = 4; - //DEBUG_ECHOLNPGM("LOGIC N2C - STALL at lowest (quit)"); - } - } break; - - case 3: { - if (sh.STATUS_AXIS_LAYOUT == L6474_STATUS_LAYOUT) { // skip all STALL_TH steps if L6474 - test_phase = 4; - break; - } - test_phase = 4; - //DEBUG_ECHOLNPGM("LOGIC N3 - finished!"); - DEBUG_ECHOLNPGM("finished!"); - } break; // phase 3 without any warnings - desired exit - } // - } // end of status checks - - if (test_phase != 4) { - for (j = 0; j < driver_count; j++) { // update threshold(s) - L64xxManager.set_param(axis_index[j], L6470_OCD_TH, OCD_TH_val); - if (sh.STATUS_AXIS_LAYOUT != L6474_STATUS_LAYOUT) L64xxManager.set_param(axis_index[j], L6470_STALL_TH, STALL_TH_val); - if (L64xxManager.get_param(axis_index[j], L6470_OCD_TH) != OCD_TH_val) DEBUG_ECHOLNPGM("OCD mismatch"); - if ((L64xxManager.get_param(axis_index[j], L6470_STALL_TH) != STALL_TH_val) && (sh.STATUS_AXIS_LAYOUT != L6474_STATUS_LAYOUT)) DEBUG_ECHOLNPGM("STALL mismatch"); - } - } - - } while (test_phase != 4); - - DEBUG_ECHOLNPGM("."); - if (status_composite) { - #if ENABLED(L6470_CHITCHAT) - for (j = 0; j < driver_count; j++) { - if (j) DEBUG_ECHOPGM("..."); - L64xxManager.error_status_decode(axis_status[j], axis_index[j], - sh.STATUS_AXIS_TH_SD, sh.STATUS_AXIS_TH_WRN, - sh.STATUS_AXIS_STEP_LOSS_A, sh.STATUS_AXIS_STEP_LOSS_B, - sh.STATUS_AXIS_OCD, sh.STATUS_AXIS_LAYOUT); - } - DEBUG_ECHOLNPGM("."); - #endif - DEBUG_ECHOLNPGM("Completed with errors"); - } - else - DEBUG_ECHOLNPGM("Completed with no errors"); - DEBUG_ECHOLNPGM("."); - - L64xxManager.pause_monitor(false); -} - -/** - * M918: increase speed until error or max feedrate achieved (as shown in configuration.h)) - * - * J - select which driver(s) to monitor on multi-driver axis - * 0 - (default) monitor all drivers on the axis or E0 - * 1 - monitor only X, Y, Z, E1 - * 2 - monitor only X2, Y2, Z2, E2 - * Xxxx, Yxxx, Zxxx, Exxx - axis to be monitored with displacement - * xxx (1-255) is distance moved on either side of current position - * - * I - over current threshold - * optional - will report current value from driver if not specified - * - * T - current (mA) setting for TVAL (0 - 4A in 31.25mA increments, rounds down) - L6474 only - * optional - will report current value from driver if not specified - * - * K - value for KVAL_HOLD (0 - 255) (ignored for L6474) - * optional - will report current value from driver if not specified - * - * M - value for microsteps (1 - 128) (optional) - * optional - will report current value from driver if not specified - */ -void GcodeSuite::M918() { - - DEBUG_ECHOLNPGM("M918"); - - L64xxManager.pause_monitor(true); // Keep monitor_driver() from stealing status - - char axis_mon[3][3] = { {" "}, {" "}, {" "} }; // list of Axes to be monitored - L64XX_axis_t axis_index[3]; - uint16_t axis_status[3]; - uint8_t driver_count = 1; - float position_max, position_min; - float final_feedrate; - uint8_t kval_hold; - uint8_t OCD_TH_val = 0; - uint8_t STALL_TH_val = 0; - uint16_t over_current_threshold; - constexpr uint8_t over_current_flag = true; - - const L64XX_Marlin::L64XX_shadow_t &sh = L64xxManager.shadow; - - uint8_t j; // general purpose counter - - if (L64xxManager.get_user_input(driver_count, axis_index, axis_mon, position_max, position_min, final_feedrate, kval_hold, over_current_flag, OCD_TH_val, STALL_TH_val, over_current_threshold)) - return; // quit if invalid user input - - L64xxManager.get_status(axis_index[0]); // populate shadow array - - uint8_t m_steps = parser.byteval('M'); - - if (m_steps != 0) { - LIMIT(m_steps, 1, sh.STATUS_AXIS_LAYOUT == L6474_STATUS_LAYOUT ? 16 : 128); // L6474 - - uint8_t stepVal; - for (stepVal = 0; stepVal < 8; stepVal++) { // convert to L64xx register value - if (m_steps == 1) break; - m_steps >>= 1; - } - - if (sh.STATUS_AXIS_LAYOUT == L6474_STATUS_LAYOUT) - stepVal |= 0x98; // NO SYNC - else - stepVal |= (!SYNC_EN) | SYNC_SEL_1 | stepVal; - - for (j = 0; j < driver_count; j++) { - L64xxManager.set_param(axis_index[j], dSPIN_HARD_HIZ, 0); // can't write STEP register if stepper being powered - // results in an extra NOOP being sent (data 00) - L64xxManager.set_param(axis_index[j], L6470_STEP_MODE, stepVal); // set microsteps - } - } - m_steps = L64xxManager.get_param(axis_index[0], L6470_STEP_MODE) & 0x07; // get microsteps - - DEBUG_ECHOLNPAIR("Microsteps = ", _BV(m_steps)); - DEBUG_ECHOLNPAIR("target (maximum) feedrate = ", final_feedrate); - - const float feedrate_inc = final_feedrate / 10, // Start at 1/10 of max & go up by 1/10 per step - fr_limit = final_feedrate * 0.99f; // Rounding-safe comparison value - float current_feedrate = 0; - - planner.synchronize(); // Wait for moves to complete - - for (j = 0; j < driver_count; j++) - L64xxManager.get_status(axis_index[j]); // Clear error flags - - char temp_axis_string[2] = " "; - temp_axis_string[0] = axis_mon[0][0]; // Need a sprintf format string - //temp_axis_string[1] = '\n'; - - char gcode_string[80]; - uint16_t status_composite = 0; - DEBUG_ECHOLNPGM(".\n.\n."); // Make feedrate outputs easier to read - - do { - current_feedrate += feedrate_inc; - DEBUG_ECHOLNPAIR("...feedrate = ", current_feedrate); - - sprintf_P(gcode_string, PSTR("G0 %s%03d F%03d"), temp_axis_string, uint16_t(position_min), uint16_t(current_feedrate)); - gcode.process_subcommands_now_P(gcode_string); - - sprintf_P(gcode_string, PSTR("G0 %s%03d F%03d"), temp_axis_string, uint16_t(position_max), uint16_t(current_feedrate)); - gcode.process_subcommands_now_P(gcode_string); - - planner.synchronize(); - - for (j = 0; j < driver_count; j++) { - axis_status[j] = (~L64xxManager.get_status(axis_index[j])) & 0x0800; // Bits of interest are all active LOW - status_composite |= axis_status[j]; - } - if (status_composite) break; // Break on any error - } while (current_feedrate < fr_limit); - - DEBUG_ECHOPGM("Completed with "); - if (status_composite) { - DEBUG_ECHOLNPGM("errors"); - #if ENABLED(L6470_CHITCHAT) - for (j = 0; j < driver_count; j++) { - if (j) DEBUG_ECHOPGM("..."); - L64xxManager.error_status_decode(axis_status[j], axis_index[j], - sh.STATUS_AXIS_TH_SD, sh.STATUS_AXIS_TH_WRN, - sh.STATUS_AXIS_STEP_LOSS_A, sh.STATUS_AXIS_STEP_LOSS_B, - sh.STATUS_AXIS_OCD, sh.STATUS_AXIS_LAYOUT); - } - #endif - } - else - DEBUG_ECHOLNPGM("no errors"); - - L64xxManager.pause_monitor(false); -} - -#endif // HAS_L64XX diff --git a/Marlin/src/gcode/feature/adc/M3426.cpp b/Marlin/src/gcode/feature/adc/M3426.cpp new file mode 100644 index 0000000000..2820c8b880 --- /dev/null +++ b/Marlin/src/gcode/feature/adc/M3426.cpp @@ -0,0 +1,68 @@ +/** + * 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 . + * + */ + +#include "../../../inc/MarlinConfig.h" + +#if ENABLED(HAS_MCP3426_ADC) + +#include "../../gcode.h" + +#include "../../../feature/adc/adc_mcp3426.h" + +#define MCP3426_BASE_ADDR (0b1101 << 3) + +/** + * M3426: Read 16 bit (signed) value from I2C MCP3426 ADC device + * + * M3426 C channel 1 or 2 + * M3426 G gain 1, 2, 4 or 8 + * M3426 I 0 or 1, invert reply + */ +void GcodeSuite::M3426() { + uint8_t channel = parser.byteval('C', 1), // Channel 1 or 2 + gain = parser.byteval('G', 1), // Gain 1, 2, 4, or 8 + address = parser.byteval('A', 3); // Address 0-7 (or 104-111) + const bool inverted = parser.boolval('I'); + + if (address <= 7) address += MCP3426_BASE_ADDR; + + if (WITHIN(channel, 1, 2) && (gain == 1 || gain == 2 || gain == 4 || gain == 8) && WITHIN(address, MCP3426_BASE_ADDR, MCP3426_BASE_ADDR + 7)) { + int16_t result = mcp3426.ReadValue(channel, gain, address); + + if (mcp3426.Error == false) { + if (inverted) { + // Should we invert the reading (32767 - ADC value) ? + // Caters to end devices that expect values to increase when in reality they decrease. + // e.g., A pressure sensor in a vacuum when the end device expects a positive pressure. + result = INT16_MAX - result; + } + //SERIAL_ECHOPGM(STR_OK); + SERIAL_ECHOLNPGM("V:", result, " C:", channel, " G:", gain, " I:", inverted ? 1 : 0); + } + else + SERIAL_ERROR_MSG("MCP342X i2c error"); + } + else + SERIAL_ERROR_MSG("MCP342X Bad request"); +} + +#endif // HAS_MCP3426_ADC diff --git a/Marlin/src/gcode/feature/advance/M900.cpp b/Marlin/src/gcode/feature/advance/M900.cpp index 5c7155d7c9..7631cd0e8c 100644 --- a/Marlin/src/gcode/feature/advance/M900.cpp +++ b/Marlin/src/gcode/feature/advance/M900.cpp @@ -22,13 +22,13 @@ #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(EXTRA_LIN_ADVANCE_K) +#if ENABLED(ADVANCE_K_EXTRA) float other_extruder_advance_K[EXTRUDERS]; uint8_t lin_adv_slot = 0; #endif @@ -36,14 +36,19 @@ /** * 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 EXTRA_LIN_ADVANCE_K. - * S<0/1> Activate slot 0 or 1. Requires EXTRA_LIN_ADVANCE_K. + * 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() { auto echo_value_oor = [](const char ltr, const bool ten=true) { - SERIAL_CHAR('?'); SERIAL_CHAR(ltr); + SERIAL_CHAR('?', ltr); SERIAL_ECHOPGM(" value out of range"); if (ten) SERIAL_ECHOPGM(" (0-10)"); SERIAL_ECHOLNPGM("."); @@ -51,6 +56,7 @@ void GcodeSuite::M900() { #if EXTRUDERS < 2 constexpr uint8_t tool_index = 0; + UNUSED(tool_index); #else const uint8_t tool_index = parser.intval('T', active_extruder); if (tool_index >= EXTRUDERS) { @@ -59,38 +65,43 @@ void GcodeSuite::M900() { } #endif - float &kref = planner.extruder_advance_K[tool_index], newK = kref; - const float oldK = newK; + const float oldK = planner.get_advance_k(tool_index); + float newK = oldK; - #if ENABLED(EXTRA_LIN_ADVANCE_K) + #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[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,43 +116,82 @@ 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(EXTRA_LIN_ADVANCE_K) + #if ENABLED(ADVANCE_K_EXTRA) - #if EXTRUDERS < 2 - SERIAL_ECHOLNPAIR("Advance S", int(new_slot), " K", kref, "(S", int(!new_slot), " K", lref, ")"); + #if DISABLED(DISTINCT_E_FACTORS) + SERIAL_ECHOLNPGM("Advance S", new_slot, " K", newK, "(S", !new_slot, " K", lref, ")"); #else - LOOP_L_N(i, EXTRUDERS) { - const bool slot = TEST(lin_adv_slot, i); - SERIAL_ECHOLNPAIR("Advance T", int(i), " S", int(slot), " K", planner.extruder_advance_K[i], - "(S", int(!slot), " K", other_extruder_advance_K[i], ")"); - SERIAL_EOL(); + EXTRUDER_LOOP() { + const bool slot = TEST(lin_adv_slot, 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 EXTRUDERS < 2 - SERIAL_ECHOLNPAIR("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"); - LOOP_L_N(i, EXTRUDERS) { - SERIAL_CHAR(' ', '0' + i, ':'); - SERIAL_DECIMAL(planner.extruder_advance_K[i]); - } + 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 } } -#endif // LIN_ADVANCE +void GcodeSuite::M900_report(const bool forReplay/*=true*/) { + TERN_(MARLIN_SMALL_BUILD, return); + + report_heading(forReplay, F(STR_LINEAR_ADVANCE)); + DISTINCT_E_LOOP() { + report_echo_start(forReplay); + 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 // HAS_LIN_ADVANCE_K diff --git a/Marlin/src/gcode/feature/camera/M240.cpp b/Marlin/src/gcode/feature/camera/M240.cpp index fc350d8a55..191a827ff4 100644 --- a/Marlin/src/gcode/feature/camera/M240.cpp +++ b/Marlin/src/gcode/feature/camera/M240.cpp @@ -31,10 +31,6 @@ 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,7 +43,7 @@ #endif #ifdef PHOTO_RETRACT_MM - inline void e_move_m240(const float length, const feedRate_t &fr_mm_s) { + 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); } @@ -84,7 +80,7 @@ inline void spin_photo_pin() { static constexpr uint32_t sequence[] = PHOTO_PULSES_US; - LOOP_L_N(i, COUNT(sequence)) + for (uint8_t i = 0; i < COUNT(sequence); ++i) pulse_photo_pin(sequence[i], !(i & 1)); } @@ -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. * @@ -111,7 +107,7 @@ * B - Y offset to the return position * F - Override the XY movement feedrate * R - Retract/recover length (current units) - * S - Retract/recover feedrate (mm/m) + * S - Retract/recover feedrate (mm/min) * X - Move to X before triggering the shutter * Y - Move to Y before triggering the shutter * Z - Raise Z by a distance before triggering the shutter @@ -128,28 +124,21 @@ void GcodeSuite::M240() { if (homing_needed_error()) return; - const xyz_pos_t old_pos = { + 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.z, + current_position.i, current_position.j, current_position.k, + current_position.u, current_position.v, current_position.w + ); #ifdef PHOTO_RETRACT_MM - const float rval = parser.seenval('R') ? parser.value_linear_units() : _PHOTO_RETRACT_MM; - feedRate_t sval = ( - #if ENABLED(ADVANCED_PAUSE_FEATURE) - PAUSE_PARK_RETRACT_FEEDRATE - #elif ENABLED(FWRETRACT) - RETRACT_FEEDRATE - #else - 45 - #endif - ); - if (parser.seenval('S')) sval = parser.value_feedrate(); + const float rval = parser.linearval('R', _PHOTO_RETRACT_MM); + const feedRate_t sval = parser.feedrateval('S', TERN(ADVANCED_PAUSE_FEATURE, PAUSE_PARK_RETRACT_FEEDRATE, TERN(FWRETRACT, RETRACT_FEEDRATE, 45))); e_move_m240(-rval, sval); #endif - feedRate_t fr_mm_s = MMM_TO_MMS(parser.linearval('F')); + feedRate_t fr_mm_s = parser.feedrateval('F'); if (fr_mm_s) NOLESS(fr_mm_s, 10.0f); constexpr xyz_pos_t photo_position = PHOTO_POSITION; @@ -192,7 +181,7 @@ 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); #ifdef PHOTO_RETRACT_MM diff --git a/Marlin/src/gcode/feature/cancel/M486.cpp b/Marlin/src/gcode/feature/cancel/M486.cpp index 1f14ae0fd2..237654e433 100644 --- a/Marlin/src/gcode/feature/cancel/M486.cpp +++ b/Marlin/src/gcode/feature/cancel/M486.cpp @@ -41,17 +41,17 @@ 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.seen('S')) + 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.seen('P')) cancelable.cancel_object(parser.value_int()); + if (parser.seenval('P')) cancelable.cancel_object(parser.value_int()); - if (parser.seen('U')) cancelable.uncancel_object(parser.value_int()); + if (parser.seenval('U')) cancelable.uncancel_object(parser.value_int()); } #endif // CANCEL_OBJECTS diff --git a/Marlin/src/gcode/feature/caselight/M355.cpp b/Marlin/src/gcode/feature/caselight/M355.cpp index bb6b57f666..b3b863f02e 100644 --- a/Marlin/src/gcode/feature/caselight/M355.cpp +++ b/Marlin/src/gcode/feature/caselight/M355.cpp @@ -41,10 +41,12 @@ */ void GcodeSuite::M355() { bool didset = false; - if (parser.seenval('P')) { - didset = true; - caselight.brightness = parser.value_byte(); - } + #if CASELIGHT_USES_BRIGHTNESS + if (parser.seenval('P')) { + didset = true; + caselight.brightness = parser.value_byte(); + } + #endif const bool sflag = parser.seenval('S'); if (sflag) { didset = true; @@ -58,8 +60,13 @@ void GcodeSuite::M355() { if (!caselight.on) SERIAL_ECHOLNPGM(STR_OFF); else { - if (!PWM_PIN(CASE_LIGHT_PIN)) SERIAL_ECHOLNPGM(STR_ON); - else SERIAL_ECHOLN(int(caselight.brightness)); + #if CASELIGHT_USES_BRIGHTNESS + if (TERN(CASE_LIGHT_USE_NEOPIXEL, true, TERN0(NEED_CASE_LIGHT_PIN, PWM_PIN(CASE_LIGHT_PIN)))) { + SERIAL_ECHOLN(int(caselight.brightness)); + return; + } + #endif + SERIAL_ECHOLNPGM(STR_ON); } } diff --git a/Marlin/src/gcode/feature/clean/G12.cpp b/Marlin/src/gcode/feature/clean/G12.cpp index 216db5bae3..a5e312f8fd 100644 --- a/Marlin/src/gcode/feature/clean/G12.cpp +++ b/Marlin/src/gcode/feature/clean/G12.cpp @@ -42,22 +42,31 @@ * P0 S : Stroke cleaning with S strokes * P1 Sn T : Zigzag cleaning with S repeats and T zigzags * P2 Sn R : Circle cleaning with S repeats and R radius + * X, Y, Z : Specify axes to move during cleaning. Default: ALL. */ void GcodeSuite::G12() { + // Don't allow nozzle cleaning without homing first - if (homing_needed_error()) return; + 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; #ifdef WIPE_SEQUENCE_COMMANDS if (!parser.seen_any()) { - gcode.process_subcommands_now_P(PSTR(WIPE_SEQUENCE_COMMANDS)); + process_subcommands_now(F(WIPE_SEQUENCE_COMMANDS)); return; } #endif - const uint8_t pattern = parser.ushortval('P', 0), - strokes = parser.ushortval('S', NOZZLE_CLEAN_STROKES), - objects = parser.ushortval('T', NOZZLE_CLEAN_TRIANGLES); - const float radius = parser.linearval('R', NOZZLE_CLEAN_CIRCLE_RADIUS); + const uint8_t pattern = ( + #if COUNT_ENABLED(NOZZLE_CLEAN_PATTERN_LINE, NOZZLE_CLEAN_PATTERN_ZIGZAG, NOZZLE_CLEAN_PATTERN_CIRCLE) > 1 + parser.ushortval('P', NOZZLE_CLEAN_DEFAULT_PATTERN) + #else + NOZZLE_CLEAN_DEFAULT_PATTERN + #endif + ); + const uint8_t strokes = parser.ushortval('S', NOZZLE_CLEAN_STROKES), + objects = TERN0(NOZZLE_CLEAN_PATTERN_ZIGZAG, parser.ushortval('T', NOZZLE_CLEAN_TRIANGLES)); + const float radius = TERN0(NOZZLE_CLEAN_PATTERN_CIRCLE, parser.linearval('R', NOZZLE_CLEAN_CIRCLE_RADIUS)); const bool seenxyz = parser.seen("XYZ"); const uint8_t cleans = (!seenxyz || parser.boolval('X') ? _BV(X_AXIS) : 0) diff --git a/Marlin/src/gcode/feature/controllerfan/M710.cpp b/Marlin/src/gcode/feature/controllerfan/M710.cpp index 67d4ad8abf..6bdfcd9b32 100644 --- a/Marlin/src/gcode/feature/controllerfan/M710.cpp +++ b/Marlin/src/gcode/feature/controllerfan/M710.cpp @@ -1,9 +1,9 @@ /** * Marlin 3D Printer Firmware - * Copyright (C) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] + * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] * * Based on Sprinter and grbl. - * Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm + * 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 @@ -27,18 +27,6 @@ #include "../../gcode.h" #include "../../../feature/controllerfan.h" -void M710_report(const bool forReplay) { - if (!forReplay) { SERIAL_ECHOLNPGM("; Controller Fan"); SERIAL_ECHO_START(); } - SERIAL_ECHOLNPAIR(" M710" - " S", int(controllerFan.settings.active_speed), - " I", int(controllerFan.settings.idle_speed), - " A", int(controllerFan.settings.auto_mode), - " D", controllerFan.settings.duration, - " ; (", (int(controllerFan.settings.active_speed) * 100) / 255, "%" - " ", (int(controllerFan.settings.idle_speed) * 100) / 255, "%)" - ); -} - /** * M710: Set controller fan settings * @@ -58,24 +46,36 @@ void M710_report(const bool forReplay) { * 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 (parser.seenval('D')) + controllerFan.settings.duration = parser.value_ushort(); +} - if (!(seenR || seenS || seenI || seenA || seenD)) - M710_report(false); +void GcodeSuite::M710_report(const bool forReplay/*=true*/) { + TERN_(MARLIN_SMALL_BUILD, return); + + report_heading_etc(forReplay, F(STR_CONTROLLER_FAN)); + SERIAL_ECHOLNPGM(" M710" + " S", int(controllerFan.settings.active_speed), + " I", int(controllerFan.settings.idle_speed), + " A", int(controllerFan.settings.auto_mode), + " D", controllerFan.settings.duration, + " ; (", (int(controllerFan.settings.active_speed) * 100) / 255, "%" + " ", (int(controllerFan.settings.idle_speed) * 100) / 255, "%)" + ); } #endif // CONTROLLER_FAN_EDITABLE diff --git a/Marlin/src/gcode/feature/digipot/M907-M910.cpp b/Marlin/src/gcode/feature/digipot/M907-M910.cpp index e463666207..425d27a72d 100644 --- a/Marlin/src/gcode/feature/digipot/M907-M910.cpp +++ b/Marlin/src/gcode/feature/digipot/M907-M910.cpp @@ -22,7 +22,7 @@ #include "../../../inc/MarlinConfig.h" -#if ANY(HAS_MOTOR_CURRENT_SPI, HAS_MOTOR_CURRENT_PWM, HAS_MOTOR_CURRENT_I2C, HAS_MOTOR_CURRENT_DAC) +#if HAS_MOTOR_CURRENT_SPI || HAS_MOTOR_CURRENT_PWM || HAS_MOTOR_CURRENT_I2C || HAS_MOTOR_CURRENT_DAC #include "../../gcode.h" @@ -34,53 +34,178 @@ #include "../../../feature/digipot/digipot.h" #endif -#if ENABLED(HAS_MOTOR_CURRENT_DAC) +#if HAS_MOTOR_CURRENT_DAC #include "../../../feature/dac/stepper_dac.h" #endif /** - * M907: Set digital trimpot motor current using axis codes X, Y, Z, E, B, S + * M907: Set digital trimpot motor current using axis codes X [Y] [Z] [I] [J] [K] [U] [V] [W] [E] + * B - Special case for E1 (Requires DIGIPOTSS_PIN or DIGIPOT_MCP4018 or DIGIPOT_MCP4451) + * C - Special case for E2 (Requires DIGIPOTSS_PIN or DIGIPOT_MCP4018 or DIGIPOT_MCP4451) + * S - Set current in mA for all axes (Requires DIGIPOTSS_PIN or DIGIPOT_MCP4018 or DIGIPOT_MCP4451), or + * Set percentage of max current for all axes (Requires HAS_DIGIPOT_DAC) */ void GcodeSuite::M907() { + #if HAS_MOTOR_CURRENT_SPI - LOOP_XYZE(i) if (parser.seenval(axis_codes[i])) stepper.set_digipot_current(i, parser.value_int()); - if (parser.seenval('B')) stepper.set_digipot_current(4, parser.value_int()); - if (parser.seenval('S')) LOOP_LE_N(i, 4) stepper.set_digipot_current(i, parser.value_int()); + if (!parser.seen("BS" STR_AXES_LOGICAL)) + return M907_report(); + + // 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 + if (parser.seenval('C')) stepper.set_digipot_current(E_AXIS + 2, parser.value_int()); + #endif + #endif #elif HAS_MOTOR_CURRENT_PWM - #if ANY_PIN(MOTOR_CURRENT_PWM_X, MOTOR_CURRENT_PWM_Y, MOTOR_CURRENT_PWM_XY) - if (parser.seenval('X') || parser.seenval('Y')) 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()); + #if ANY_PIN(MOTOR_CURRENT_PWM_X, MOTOR_CURRENT_PWM_Y, MOTOR_CURRENT_PWM_XY, MOTOR_CURRENT_PWM_I, MOTOR_CURRENT_PWM_J, MOTOR_CURRENT_PWM_K, MOTOR_CURRENT_PWM_U, MOTOR_CURRENT_PWM_V, MOTOR_CURRENT_PWM_W) + #define HAS_X_Y_XY_I_J_K_U_V_W 1 #endif - #endif + #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 + 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(); + + // 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); + } + + // 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 - LOOP_XYZE(i) if (parser.seenval(axis_codes[i])) digipot_i2c.set_current(i, parser.value_float()); - // Additional extruders use B,C,D for channels 4,5,6. - // TODO: Change these parameters because 'E' is used. B? - for (uint8_t i = E_AXIS + 1; i < DIGIPOT_I2C_NUM_CHANNELS; i++) - if (parser.seenval('B' + i - (E_AXIS + 1))) digipot_i2c.set_current(i, parser.value_float()); - #endif + // This current driver takes actual Amps in floating point + // rather than milli-amps or some scalar unit. - #if ENABLED(HAS_MOTOR_CURRENT_DAC) + // 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: 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()); + #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_LE_N(i, 4) stepper_dac.set_current_percent(i, dac_percent); + LOOP_LOGICAL_AXES(i) + stepper_dac.set_current_percent(i, dac_percent); } - LOOP_XYZE(i) if (parser.seenval(axis_codes[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 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 } -#if EITHER(HAS_MOTOR_CURRENT_SPI, HAS_MOTOR_CURRENT_DAC) +#if HAS_MOTOR_CURRENT_SPI || HAS_MOTOR_CURRENT_PWM + + void GcodeSuite::M907_report(const bool forReplay/*=true*/) { + TERN_(MARLIN_SMALL_BUILD, return); + + 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) + #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: + LOOP_LOGICAL_AXES(q) { // X Y Z (I J K U V W) E (map to X Y Z (I J K U V W) E0 by default) + SERIAL_CHAR(' ', IAXIS_CHAR(q)); + SERIAL_ECHO(stepper.motor_current_setting[q]); + } + #if E_STEPPERS >= 2 + SERIAL_ECHOPGM_P(PSTR(" B"), stepper.motor_current_setting[E_AXIS + 1] // B (maps to E1 with NUM_AXES 3 according to DIGIPOT_CHANNELS) + #if E_STEPPERS >= 3 + , PSTR(" C"), stepper.motor_current_setting[E_AXIS + 2] // C (mapping to E2 must be defined by DIGIPOT_CHANNELS) + #endif + ); + #endif + SERIAL_EOL(); + #endif + } + +#endif // HAS_MOTOR_CURRENT_SPI || HAS_MOTOR_CURRENT_PWM + +#if HAS_MOTOR_CURRENT_SPI || HAS_MOTOR_CURRENT_DAC /** * M908: Control digital trimpot directly (M908 P S) @@ -90,7 +215,7 @@ void GcodeSuite::M907() { TERN_(HAS_MOTOR_CURRENT_DAC, stepper_dac.set_current_value(parser.byteval('P', -1), parser.ushortval('S', 0))); } - #if ENABLED(HAS_MOTOR_CURRENT_DAC) + #if HAS_MOTOR_CURRENT_DAC void GcodeSuite::M909() { stepper_dac.print_values(); } void GcodeSuite::M910() { stepper_dac.commit_eeprom(); } diff --git a/Marlin/src/gcode/feature/filwidth/M404-M407.cpp b/Marlin/src/gcode/feature/filwidth/M404-M407.cpp index 516289fe27..5b906760ed 100644 --- a/Marlin/src/gcode/feature/filwidth/M404-M407.cpp +++ b/Marlin/src/gcode/feature/filwidth/M404-M407.cpp @@ -26,8 +26,6 @@ #include "../../../feature/filwidth.h" #include "../../../module/planner.h" -#include "../../../module/temperature.h" -#include "../../../MarlinCore.h" #include "../../gcode.h" /** @@ -39,7 +37,7 @@ void GcodeSuite::M404() { planner.volumetric_area_nominal = CIRCLE_AREA(filwidth.nominal_mm * 0.5); } else - SERIAL_ECHOLNPAIR("Filament dia (nominal mm):", filwidth.nominal_mm); + SERIAL_ECHOLNPGM("Filament dia (nominal mm):", filwidth.nominal_mm); } /** @@ -66,7 +64,7 @@ void GcodeSuite::M406() { * M407: Get measured filament diameter on serial output */ void GcodeSuite::M407() { - SERIAL_ECHOLNPAIR("Filament dia (measured mm):", filwidth.measured_mm); + SERIAL_ECHOLNPGM("Filament dia (measured mm):", filwidth.measured_mm); } #endif // FILAMENT_WIDTH_SENSOR diff --git a/Marlin/src/gcode/feature/ft_motion/M493.cpp b/Marlin/src/gcode/feature/ft_motion/M493.cpp new file mode 100644 index 0000000000..272c3d7ab3 --- /dev/null +++ b/Marlin/src/gcode/feature/ft_motion/M493.cpp @@ -0,0 +1,503 @@ +/** + * 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 + * (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(FT_MOTION) + +#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; + 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() { + const ft_config_t &c = ftMotion.cfg; + + // FT Enabled + SERIAL_ECHO_TERNARY(c.active, "Fixed-Time Motion ", "en", "dis", "abled"); + + // FT Shaping + 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) if (AXIS_IS_SHAPING(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, 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 (is_shaping) { + #if HAS_DYNAMIC_FREQ + SERIAL_ECHOPGM("Dynamic Frequency Mode "); + switch (c.dynFreqMode) { + default: + case dynFreqMode_DISABLED: SERIAL_ECHOPGM("disabled"); break; + #if HAS_DYNAMIC_FREQ_MM + case dynFreqMode_Z_BASED: SERIAL_ECHOPGM("Z-based"); break; + #endif + #if HAS_DYNAMIC_FREQ_G + case dynFreqMode_MASS_BASED: SERIAL_ECHOPGM("Mass-based"); break; + #endif + } + SERIAL_ECHOLNPGM("."); + #endif + + #if HAS_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_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 ENABLED(FTM_SHAPER_Z) + SERIAL_CHAR(STEPPER_C_NAME); + SERIAL_ECHO_TERNARY(dynamic, " ", "base dynamic", "static", " shaper frequency: "); + SERIAL_ECHO(p_float_t(c.baseFreq.z, 2), F(" Hz")); + #if HAS_DYNAMIC_FREQ + if (dynamic) SERIAL_ECHO(F(" scaling: "), p_float_t(c.dynFreqK.z, 2), F("Hz/"), z_based ? F("mm") : F("g")); + #endif + SERIAL_EOL(); + #endif + + #if ENABLED(FTM_SHAPER_E) + SERIAL_CHAR('E'); + SERIAL_ECHO_TERNARY(dynamic, " ", "base dynamic", "static", " shaper frequency: "); + SERIAL_ECHO(p_float_t(c.baseFreq.e, 2), F(" Hz")); + #if HAS_DYNAMIC_FREQ + if (dynamic) SERIAL_ECHO(F(" scaling: "), p_float_t(c.dynFreqK.e, 2), F("Hz/"), z_based ? F("mm") : F("g")); + #endif + SERIAL_EOL(); + #endif + } +} + +void GcodeSuite::M493_report(const bool forReplay/*=true*/) { + TERN_(MARLIN_SMALL_BUILD, return); + + report_heading_etc(forReplay, F(STR_FT_MOTION)); + const ft_config_t &c = ftMotion.cfg; + + SERIAL_ECHOLNPGM( + " M493 S", c.active + #if HAS_DYNAMIC_FREQ + , " D", c.dynFreqMode + #endif + // Axis Synchronization + , " H", c.axis_sync_enabled + ); + + #if HAS_DYNAMIC_FREQ + #define F_REPORT(A) , F(" F"), c.dynFreqK.A + #else + #define F_REPORT(A) + #endif + #if HAS_FTM_EI_SHAPING + #define Q_REPORT(A) , F(" Q"), c.vtol.A + #else + #define Q_REPORT(A) + #endif + #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 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) + * + * H Enable (1) or Disable (0) Axis Synchronization. + * + * Linear / Pressure Advance: + * + * P Enable (1) or Disable (0) Linear Advance pressure control + * + * 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. + * + * 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 + * 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) + * + * F Set frequency scaling for the specified axes + * + */ +void GcodeSuite::M493() { + // 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 = 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 NUM_AXES_SHAPED > 0 + + 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; + }; + if (seenC) { + #define _SET_SHAPER(A) set_shaper(_AXIS(A), shaperVal); + SHAPED_MAP(_SET_SHAPER); + } + + #endif // NUM_AXES_SHAPED > 0 + + // 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 (AXIS_IS_SHAPING(X) || AXIS_IS_SHAPING(Y) || AXIS_IS_SHAPING(Z) || AXIS_IS_SHAPING(E)) { + 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("?Shaper required for (D)ynamic Frequency Mode ", c.dynFreqMode, "."); + } + + 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 + + if (apply_xy || parser.seen_test('X')) { + + // 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 // Mode doesn't use frequency. + SERIAL_ECHOLNPGM("?Wrong mode for ", C(STEPPER_A_NAME), " (A) frequency."); + } + + #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("?Wrong mode for ", C(STEPPER_A_NAME), " (I) zeta parameter."); + } + + #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."); + } + #endif + } + + #endif // HAS_X_AXIS + + #if HAS_Y_AXIS + + if (apply_xy || parser.seen_test('Y')) { + + // Parse Y frequency parameter + if (seenA) { + if (AXIS_IS_SHAPING(Y)) { + if (goodBaseFreq && c.setBaseFreq(Y_AXIS, baseFreqVal)) + flag.update = flag.report = true; + } + else // Mode doesn't use frequency. + SERIAL_ECHOLNPGM("?Wrong mode for ", C(STEPPER_B_NAME), " (A) frequency."); + } + + #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(STEPPER_B_NAME), " (I) zeta parameter."); + } + + #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."); + } + #endif + } + + #endif // HAS_Y_AXIS + + #if ENABLED(FTM_SHAPER_Z) + + if (parser.seen_test('Z')) { + + // 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 && c.setDynFreqK(Z_AXIS, baseDynFreqVal)) + flag.report = true; + #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("?Wrong mode for ", C(STEPPER_C_NAME), " (I) zeta 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 // FTM_SHAPER_Z + + #if ENABLED(FTM_SHAPER_E) + + if (parser.seen_test('E')) { + + // 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 && c.setDynFreqK(E_AXIS, baseDynFreqVal)) + flag.report = true; + #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) 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 0000000000..d6161a9574 --- /dev/null +++ b/Marlin/src/gcode/feature/ft_motion/M494.cpp @@ -0,0 +1,139 @@ +/** + * 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) + const ft_config_t &c = ftMotion.cfg; + #endif + + #if ENABLED(FTM_POLYS) + SERIAL_ECHOLN(F(" Trajectory: "), ftMotion.getTrajectoryName(), C('('), (uint8_t)ftMotion.getTrajectoryType(), C(')')); + if (ftMotion.getTrajectoryType() == TrajectoryType::POLY6) + SERIAL_ECHOLNPGM(" Poly6 Overshoot: ", p_float_t(c.poly6_acceleration_overshoot, 3)); + #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) + 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 + + SERIAL_EOL(); +} + +/** + * M494: Set Fixed-time Motion Control parameters + * + * Parameters: + * T Set trajectory generator type (0=TRAPEZOIDAL, 1=POLY5, 2=POLY6) + * O Set acceleration overshoot for POLY6 (1.25-1.875) + * X