Compare commits
91 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
48d4d920be | |
|
|
b2dd038814 | |
|
|
3aed994b9c | |
|
|
e089be4ff8 | |
|
|
9e3b300b2e | |
|
|
9c23658445 | |
|
|
3d32c300a3 | |
|
|
f4388b8d20 | |
|
|
5d8fa524fb | |
|
|
42cb40eeb9 | |
|
|
06f41bf1e6 | |
|
|
b6bdd55def | |
|
|
c1aa190d05 | |
|
|
88a241c741 | |
|
|
7a250c24db | |
|
|
39b24f8b43 | |
|
|
22a8f1df57 | |
|
|
ceda1cce08 | |
|
|
5e50516fe7 | |
|
|
454619bfd4 | |
|
|
f240bb24d1 | |
|
|
b00ac883c4 | |
|
|
9e44c06f1e | |
|
|
e9889ec066 | |
|
|
4e4e2df739 | |
|
|
b8ed9abc7f | |
|
|
e28ab28824 | |
|
|
255dc8726a | |
|
|
b23d6827a7 | |
|
|
dc68b70cff | |
|
|
0eeef4cafd | |
|
|
4ab07d627a | |
|
|
0a205e41ff | |
|
|
3c8663d900 | |
|
|
195aad9cf0 | |
|
|
e7abc6a7d8 | |
|
|
32d5f3b4be | |
|
|
ca1d09ad15 | |
|
|
89d3c6fc47 | |
|
|
75570a82ff | |
|
|
3cd341f95f | |
|
|
5d0f772270 | |
|
|
3ee1e22e9a | |
|
|
ef23490a49 | |
|
|
08171415ca | |
|
|
5ed48aee0b | |
|
|
00fd54f7a4 | |
|
|
e14175d9cd | |
|
|
2933737b3b | |
|
|
878c763878 | |
|
|
6ca0d650da | |
|
|
df97d70d8e | |
|
|
8b04f7f9c7 | |
|
|
3a28bfe887 | |
|
|
1c0c732291 | |
|
|
cc5c425538 | |
|
|
67e3a3c06c | |
|
|
d510e1e482 | |
|
|
58577b832d | |
|
|
19a0cd3ebb | |
|
|
ce0df08b89 | |
|
|
d68ccd6552 | |
|
|
f019de539b | |
|
|
4845d0b335 | |
|
|
6b96f983c0 | |
|
|
8d6e4d1be5 | |
|
|
a5ca4612d2 | |
|
|
fb65f4e563 | |
|
|
09bacabb05 | |
|
|
a7286de016 | |
|
|
b0eac8f523 | |
|
|
dda3899156 | |
|
|
03b0d6f776 | |
|
|
18a040c278 | |
|
|
8073ce66fb | |
|
|
515b93e9e7 | |
|
|
c60d562c8c | |
|
|
6340421f51 | |
|
|
1d1d2f23ec | |
|
|
fe354a1f51 | |
|
|
4e9ad80384 | |
|
|
fbb00f38d3 | |
|
|
1f66a8b908 | |
|
|
e2f396e2a2 | |
|
|
2e02eccd91 | |
|
|
f6a943812f | |
|
|
d68efb994e | |
|
|
90c1601159 | |
|
|
063130726a | |
|
|
3f4658dd56 | |
|
|
3924cb3d4f |
|
|
@ -0,0 +1,11 @@
|
|||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: Questions and Discussions
|
||||
url: https://github.com/prusa3d/Prusa-Firmware-MMU/discussions
|
||||
about: Please use Discussions for questions, chat, and open-ended ideas. Be sure to search before creating a new topic, your question may already have been answered!
|
||||
- name: Issues with an MMU3 on a Prusa MK3S/MK3S+
|
||||
url: https://github.com/prusa3d/Prusa-Firmware/issues
|
||||
about: If you have an issue or bug pertaining to the MMU3 on an 8-bit Prusa Printer, create it here.
|
||||
- name: Issues with an MMU3 on a Prusa MK3.9/MK4
|
||||
url: https://github.com/prusa3d/Prusa-Firmware-Buddy/issues
|
||||
about: If you have an issue or bug pertaining to the MMU3 on a 32-bit Prusa Printer, create it here.
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
name: ci-build
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- '*'
|
||||
push:
|
||||
branches: [ main, MMU_* ]
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
|
||||
# setup base required dependencies
|
||||
- name: Setup dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install cmake ninja-build python3-pyelftools python3-regex python3-polib
|
||||
|
||||
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
|
||||
- name: Checkout ${{ github.event.pull_request.head.ref }}
|
||||
uses: actions/checkout@v4
|
||||
if: ${{ github.event.pull_request }}
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
submodules: true
|
||||
|
||||
- name: Checkout ${{ github.event.ref }}
|
||||
uses: actions/checkout@v4
|
||||
if: ${{ !github.event.pull_request }}
|
||||
with:
|
||||
ref: ${{ github.event.ref }}
|
||||
submodules: true
|
||||
|
||||
- name: Cache Dependencies
|
||||
uses: actions/cache@v4
|
||||
id: cache-pkgs
|
||||
with:
|
||||
path: ".dependencies"
|
||||
key: "build-deps-1_0_0-linux"
|
||||
|
||||
- name: Setup build dependencies
|
||||
run: |
|
||||
./utils/bootstrap.py
|
||||
|
||||
- name: Cache permissions
|
||||
run: sudo chmod -R 744 .dependencies
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DCMAKE_TOOLCHAIN_FILE="../cmake/AvrGcc.cmake" -DCMAKE_BUILD_TYPE=Release -G Ninja
|
||||
ninja
|
||||
|
||||
- name: Upload artifacts
|
||||
if: ${{ !github.event.pull_request }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Firmware
|
||||
path: build/*.hex
|
||||
|
||||
- name: RELEASE THE KRAKEN
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
uses: "marvinpinto/action-automatic-releases@latest"
|
||||
with:
|
||||
repo_token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
draft: true
|
||||
files: |
|
||||
build/autopublish/*.hex
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
#!/bin/sh
|
||||
MESSAGE=$1
|
||||
BASE_DIR=$2
|
||||
PR_DIR=$3
|
||||
shift 3
|
||||
|
||||
# this assumes we're running from the repository root
|
||||
AVR_SIZE=$(echo .dependencies/avr-gcc-*/bin/avr-size)
|
||||
test -x "$AVR_SIZE" || exit 2
|
||||
|
||||
avr_size()
|
||||
{
|
||||
"$AVR_SIZE" --mcu=atmel32u4 -C "$@"
|
||||
}
|
||||
|
||||
avr_flash()
|
||||
{
|
||||
avr_size "$@" | sed -ne 's/^Program: *\([0-9]\+\).*/\1/p'
|
||||
}
|
||||
|
||||
avr_ram()
|
||||
{
|
||||
avr_size "$@" | sed -ne 's/^Data: *\([0-9]\+\).*/\1/p'
|
||||
}
|
||||
|
||||
cat <<EOF > "$MESSAGE"
|
||||
All values in bytes. Δ Delta to base
|
||||
|
||||
| ΔFlash | ΔSRAM | Used Flash | Used SRAM | Free Flash | Free SRAM |
|
||||
| ------ | ----- | -----------| --------- | ---------- | --------- |
|
||||
EOF
|
||||
|
||||
atmel32u4_max_upload_size=$(grep "prusa_mm_control.upload.maximum_size=" .dependencies/prusa3dboards-*/boards.txt | cut -d "=" -f2)
|
||||
atmel32u4_max_upload_data_size=$(grep "prusa_mm_control.upload.maximum_data_size=" .dependencies/prusa3dboards-*/boards.txt | cut -d "=" -f2)
|
||||
|
||||
base_bin=$(echo ${BASE_DIR}/firmware)
|
||||
base_flash=$(avr_flash "$base_bin")
|
||||
base_ram=$(avr_ram "$base_bin")
|
||||
|
||||
pr_bin=$(echo ${PR_DIR}/firmware)
|
||||
pr_flash=$(avr_flash "$pr_bin")
|
||||
pr_ram=$(avr_ram "$pr_bin")
|
||||
|
||||
flash_d=$(($pr_flash - $base_flash))
|
||||
ram_d=$(($pr_ram - $base_ram))
|
||||
|
||||
flash_free=$(($atmel32u4_max_upload_size - $pr_flash))
|
||||
ram_free=$(($atmel32u4_max_upload_data_size - $pr_ram))
|
||||
|
||||
echo "| $flash_d | $ram_d | $pr_flash | $pr_ram | $flash_free | $ram_free |" >> "$MESSAGE"
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
name: pr-size
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
branches: [ main, MMU_* ]
|
||||
|
||||
env:
|
||||
TARGETS: "firmware"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-22.04
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
|
||||
# setup base required dependencies
|
||||
- name: Setup dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install cmake ninja-build python3-pyelftools python3-regex python3-polib
|
||||
|
||||
# build the base branch
|
||||
- name: Checkout base
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Cache Dependencies
|
||||
uses: actions/cache@v4
|
||||
id: cache-pkgs
|
||||
with:
|
||||
path: ".dependencies"
|
||||
key: "build-deps-1_0_0-linux"
|
||||
|
||||
- name: Setup build dependencies
|
||||
run: |
|
||||
./utils/bootstrap.py
|
||||
|
||||
- name: Cache permissions
|
||||
run: sudo chmod -R 744 .dependencies
|
||||
|
||||
- name: Build base
|
||||
run: |
|
||||
rm -rf build-base
|
||||
mkdir build-base
|
||||
cd build-base
|
||||
cmake .. -DCMAKE_TOOLCHAIN_FILE="../cmake/AvrGcc.cmake" -DCMAKE_BUILD_TYPE=Release -G Ninja
|
||||
ninja
|
||||
|
||||
# save pr-size for later use
|
||||
- name: Save base data
|
||||
run: |
|
||||
cp -f ./.github/workflows/pr-size.sh build-base
|
||||
|
||||
# build the PR branch
|
||||
- name: Checkout PR
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
clean: false
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- name: Build PR
|
||||
run: |
|
||||
rm -rf build-pr
|
||||
mkdir build-pr
|
||||
cd build-pr
|
||||
cmake .. -DCMAKE_TOOLCHAIN_FILE="../cmake/AvrGcc.cmake" -DCMAKE_BUILD_TYPE=Release -G Ninja
|
||||
ninja
|
||||
|
||||
# extract/show build differences
|
||||
- name: Calculate binary changes
|
||||
run: |
|
||||
rm -rf build-changes
|
||||
./build-base/pr-size.sh build-changes build-base build-pr
|
||||
|
||||
- name: Add PR Comment
|
||||
uses: mshick/add-pr-comment@v2.8.2
|
||||
with:
|
||||
message-path: build-changes
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
name: Mark stale issues
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# 1:30 AM on MON/THU
|
||||
- cron: "30 1 * * 1,2,3,4"
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
# Don't ever mark PRs as stale.
|
||||
days-before-pr-stale: -1
|
||||
stale-issue-message: 'This issue has been flagged as stale because it has been open for 60 days with no activity. The issue will be closed in 7 days unless someone removes the "stale" label or adds a comment.'
|
||||
close-issue-message: 'This issue has been closed due to lack of recent activity.'
|
||||
# Don't act on things assigned to a milestone or assigned to someone.
|
||||
exempt-all-milestones: true
|
||||
exempt-all-assignees: true
|
||||
enable-statistics: true
|
||||
# Disable this and change the operations per run back to 30 when this goes live.
|
||||
debug-only: false
|
||||
operations-per-run: 30
|
||||
stale-issue-label: 'stale-issue'
|
||||
stale-pr-label: 'stale-pr'
|
||||
ascending: true
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
name: tests
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
branches: [ main, MMU_* ]
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
runs-on: ubuntu-22.04
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
|
||||
# setup base required dependencies
|
||||
- name: Setup dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install gcc-11 g++11 lcov cmake ninja-build python3-pyelftools python3-regex python3-polib
|
||||
|
||||
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
|
||||
- name: Checkout ${{ github.event.pull_request.head.ref }}
|
||||
uses: actions/checkout@v4
|
||||
if: ${{ github.event.pull_request }}
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
submodules: true
|
||||
|
||||
- name: Checkout ${{ github.event.ref }}
|
||||
uses: actions/checkout@v4
|
||||
if: ${{ !github.event.pull_request }}
|
||||
with:
|
||||
ref: ${{ github.event.ref }}
|
||||
submodules: true
|
||||
|
||||
- name: Cache Dependencies
|
||||
uses: actions/cache@v4
|
||||
id: cache-pkgs
|
||||
with:
|
||||
path: ".dependencies"
|
||||
key: "build-deps-1_0_0-linux"
|
||||
|
||||
- name: Setup build dependencies
|
||||
run: |
|
||||
./utils/bootstrap.py
|
||||
|
||||
- name: Cache permissions
|
||||
run: sudo chmod -R 744 .dependencies
|
||||
|
||||
- name: Build
|
||||
id: tests_run
|
||||
continue-on-error: true
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -G Ninja
|
||||
ninja test_coverage_report
|
||||
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Coverage
|
||||
path: build/Coverage
|
||||
|
||||
- name: Add PR Comment
|
||||
if: ${{ github.event.pull_request }}
|
||||
uses: mshick/add-pr-comment@v2.8.2
|
||||
with:
|
||||
message-path: build/Summary.txt
|
||||
message-id: "coverage"
|
||||
|
||||
- name: Report failure
|
||||
if: steps.tests_run.outcome == 'failure'
|
||||
run: echo ${{ steps.tests_run.outcome }} && test -n ""
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
"name": "avr-gcc",
|
||||
"toolchainFile": "${workspaceFolder}/cmake/AvrGcc.cmake",
|
||||
"cmakeSettings": {
|
||||
"CMAKE_MAKE_PROGRAM": "${workspaceFolder}/.dependencies/ninja-1.10.2/ninja",
|
||||
"CMAKE_MAKE_PROGRAM": "${workspaceFolder}/.dependencies/ninja-1.12.1/ninja",
|
||||
"CMAKE_BUILD_TYPE": "Release"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -159,12 +159,10 @@ set_target_properties(firmware PROPERTIES CXX_STANDARD 17)
|
|||
set_target_properties(firmware PROPERTIES INTERPROCEDURAL_OPTIMIZATION True)
|
||||
|
||||
if(CMAKE_CROSSCOMPILING)
|
||||
#[[
|
||||
# configure linker script
|
||||
set(LINKER_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/src/avr5.xn)
|
||||
target_link_options(firmware PUBLIC -Wl,-T,${LINKER_SCRIPT})
|
||||
add_link_dependency(firmware ${LINKER_SCRIPT})
|
||||
#]]
|
||||
|
||||
# limit the text section to 28K (32K - 4k reserved for the bootloader)
|
||||
target_link_options(firmware PUBLIC -Wl,--defsym=__TEXT_REGION_LENGTH__=28K)
|
||||
|
|
@ -183,7 +181,11 @@ if(CMAKE_CROSSCOMPILING)
|
|||
COMMAND ${CMAKE_OBJCOPY} firmware -O binary --gap-fill 0xFF --pad-to 0x00007000 firmware.bin
|
||||
COMMAND ${CMAKE_COMMAND} -E cat firmware.bin bootloader.bin > fw_bootloader.bin
|
||||
COMMAND ${CMAKE_OBJCOPY} -I binary -O ihex fw_bootloader.bin
|
||||
"MMU3_${PROJECT_VERSION}${PROJECT_VERSION_SUFFIX_SHORT}_bootloader.hex"
|
||||
"MMU2S_MMU3_BOOTLOADER_${PROJECT_VERSION}${PROJECT_VERSION_SUFFIX_SHORT}.hex"
|
||||
COMMAND
|
||||
${CMAKE_COMMAND} -E copy
|
||||
"MMU2S_MMU3_BOOTLOADER_${PROJECT_VERSION}${PROJECT_VERSION_SUFFIX_SHORT}.hex"
|
||||
"MMU2S_MMU3_BOOTLOADER_${PROJECT_VERSION}.hex"
|
||||
BYPRODUCTS bootloader.bin firmware.bin fw_bootloader.bin
|
||||
)
|
||||
|
||||
|
|
@ -212,8 +214,14 @@ if(CMAKE_CROSSCOMPILING)
|
|||
POST_BUILD
|
||||
COMMAND
|
||||
${CMAKE_COMMAND} -D WORK_DIR=${CMAKE_BINARY_DIR} -D
|
||||
HEX_NAME="MMU3_${PROJECT_VERSION}${PROJECT_VERSION_SUFFIX_SHORT}.hex" -P
|
||||
HEX_NAME="MMU2S_MMU3_FW${PROJECT_VERSION}${PROJECT_VERSION_SUFFIX_SHORT}.hex" -P
|
||||
${CMAKE_SOURCE_DIR}/cmake/HexConcat.cmake DEPENDS firmware.hex
|
||||
COMMAND
|
||||
${CMAKE_COMMAND} -E copy "MMU2S_MMU3_FW${PROJECT_VERSION}${PROJECT_VERSION_SUFFIX_SHORT}.hex"
|
||||
"MMU2S_MMU3_FW${PROJECT_VERSION}.hex"
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/autopublish
|
||||
COMMAND ${CMAKE_COMMAND} -E copy
|
||||
"MMU2S_MMU3_FW${PROJECT_VERSION}${PROJECT_VERSION_SUFFIX_SHORT}.hex" "autopublish/"
|
||||
)
|
||||
|
||||
endif()
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ This firmware for the Original Prusa MMU3 and Original Prusa MMU2/S is distribut
|
|||
- The firmware source code is licensed under the GNU General Public License v3.0. Meaning you and your company can use the code both for the commercial and non-commercial activities, but you have to fulfil the requirements of the license. Read more about the permissions and limitations below.
|
||||
- The graphics and design are licensed under Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0). Meaning you and your company are strictly prohibited from using these elements in a commercial product. You have to create your own graphics and design. Read more about the permissions and limitations below. Violating these conditions will lead to a legal dispute.
|
||||
|
||||
Original Prusa MMU3® and Original Prusa MMU2S® are registered trademarks of the Prusa Research s.r.o.
|
||||
Original Prusa® MMU3 and Original Prusa® MMU2S are registered trademarks of Prusa Research a.s.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# Prusa-Firmware-MMU-Private
|
||||
This repository includes source code and firmware releases for the Original Prusa Multi Material Upgrade based on 8-bit ATMEL microcontroller.
|
||||
# Prusa-Firmware-MMU
|
||||
This repository includes source code and firmware releases for the Original Prusa Multi Material Unit based on 8-bit ATMEL microcontroller.
|
||||
|
||||
The currently supported models are:
|
||||
- Original Prusa MMU3
|
||||
|
|
@ -41,7 +41,7 @@ Run `./utils/bootstrap.py`
|
|||
`bootstrap.py` will now download all the "missing" dependencies into the `.dependencies` folder:
|
||||
- clang-format-9.0.0-noext
|
||||
- cmake-3.22.5
|
||||
- ninja-1.10.2
|
||||
- ninja-1.12.1
|
||||
- avr-gcc-7.3.0
|
||||
|
||||
### How to build the preliminary project so far:
|
||||
|
|
|
|||
|
|
@ -60,6 +60,11 @@ function(resolve_version_variables)
|
|||
# PROJECT_VERSION_TIMESTAMP
|
||||
if(NOT PROJECT_VERSION_TIMESTAMP)
|
||||
git_head_commit_timestamp(timestamp)
|
||||
set(ERRORS "GIT-NOTFOUND" "HEAD-FORMAT-NOTFOUND" "HEAD-HASH-NOTFOUND")
|
||||
if(timestamp IN_LIST ERRORS)
|
||||
# git not available, set fallback values
|
||||
set(timestamp 0)
|
||||
endif()
|
||||
set(PROJECT_VERSION_TIMESTAMP
|
||||
"${timestamp}"
|
||||
PARENT_SCOPE
|
||||
|
|
|
|||
|
|
@ -42,14 +42,14 @@
|
|||
#include "lufa/LUFA/Drivers/USB/USB.h"
|
||||
|
||||
/* Macros: */
|
||||
/** Endpoint address of the CDC device-to-host notification IN endpoint. */
|
||||
#define CDC_NOTIFICATION_EPADDR (ENDPOINT_DIR_IN | 2)
|
||||
|
||||
/** Endpoint address of the CDC device-to-host data IN endpoint. */
|
||||
#define CDC_TX_EPADDR (ENDPOINT_DIR_IN | 3)
|
||||
#define CDC_TX_EPADDR (ENDPOINT_DIR_IN | 1)
|
||||
|
||||
/** Endpoint address of the CDC host-to-device data OUT endpoint. */
|
||||
#define CDC_RX_EPADDR (ENDPOINT_DIR_OUT | 4)
|
||||
#define CDC_RX_EPADDR (ENDPOINT_DIR_OUT | 2)
|
||||
|
||||
/** Endpoint address of the CDC device-to-host notification IN endpoint. */
|
||||
#define CDC_NOTIFICATION_EPADDR (ENDPOINT_DIR_IN | 3)
|
||||
|
||||
/** Size in bytes of the CDC device-to-host notification IN endpoint. */
|
||||
#define CDC_NOTIFICATION_EPSIZE 8
|
||||
|
|
|
|||
|
|
@ -2,16 +2,16 @@
|
|||
|
||||
#define USB_DEVICE_ONLY
|
||||
#define DEVICE_STATE_AS_GPIOR 0
|
||||
// #define ORDERED_EP_CONFIG
|
||||
#define ORDERED_EP_CONFIG
|
||||
#define FIXED_CONTROL_ENDPOINT_SIZE 8
|
||||
#define FIXED_NUM_CONFIGURATIONS 1
|
||||
#define INTERRUPT_CONTROL_ENDPOINT
|
||||
#define USE_FLASH_DESCRIPTORS
|
||||
#define USE_STATIC_OPTIONS (USB_DEVICE_OPT_FULLSPEED | USB_OPT_REG_ENABLED | USB_OPT_AUTO_PLL)
|
||||
#define USE_STATIC_OPTIONS (USB_DEVICE_OPT_FULLSPEED | USB_OPT_REG_ENABLED | USB_OPT_MANUAL_PLL)
|
||||
#define NO_INTERNAL_SERIAL
|
||||
#define NO_DEVICE_SELF_POWER
|
||||
#define NO_DEVICE_REMOTE_WAKEUP
|
||||
// #define NO_SOF_EVENTS
|
||||
#define NO_SOF_EVENTS
|
||||
#define F_USB F_CPU
|
||||
#define DEVICE_VID 0x2C99
|
||||
#define DEVICE_PID 0x0004
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
target_sources(firmware PRIVATE application.cpp debug.cpp main.cpp registers.cpp version.hpp)
|
||||
target_sources(firmware PRIVATE application.cpp debug.cpp main.cpp registers.cpp version.cpp)
|
||||
|
||||
target_link_libraries(firmware LUFA)
|
||||
|
||||
|
|
|
|||
|
|
@ -255,6 +255,7 @@ void Application::ProcessRequestMsg(const mp::RequestMsg &rq) {
|
|||
case mp::RequestMsgCodes::Eject:
|
||||
case mp::RequestMsgCodes::Home:
|
||||
case mp::RequestMsgCodes::Load:
|
||||
case mp::RequestMsgCodes::Mode:
|
||||
case mp::RequestMsgCodes::Tool:
|
||||
case mp::RequestMsgCodes::Unload:
|
||||
PlanCommand(rq);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,271 @@
|
|||
/* Script for -n: mix text and data on same page */
|
||||
/* Copyright (C) 2014-2015 Free Software Foundation, Inc.
|
||||
Copying and distribution of this script, with or without modification,
|
||||
are permitted in any medium without royalty provided the copyright
|
||||
notice and this notice are preserved. */
|
||||
OUTPUT_FORMAT("elf32-avr","elf32-avr","elf32-avr")
|
||||
OUTPUT_ARCH(avr:5)
|
||||
__TEXT_REGION_ORIGIN__ = DEFINED(__TEXT_REGION_ORIGIN__) ? __TEXT_REGION_ORIGIN__ : 0;
|
||||
__DATA_REGION_ORIGIN__ = DEFINED(__DATA_REGION_ORIGIN__) ? __DATA_REGION_ORIGIN__ : 0x800060;
|
||||
__TEXT_REGION_LENGTH__ = DEFINED(__TEXT_REGION_LENGTH__) ? __TEXT_REGION_LENGTH__ : 128K;
|
||||
__DATA_REGION_LENGTH__ = DEFINED(__DATA_REGION_LENGTH__) ? __DATA_REGION_LENGTH__ : 0xffa0;
|
||||
__EEPROM_REGION_LENGTH__ = DEFINED(__EEPROM_REGION_LENGTH__) ? __EEPROM_REGION_LENGTH__ : 64K;
|
||||
__FUSE_REGION_LENGTH__ = DEFINED(__FUSE_REGION_LENGTH__) ? __FUSE_REGION_LENGTH__ : 1K;
|
||||
__LOCK_REGION_LENGTH__ = DEFINED(__LOCK_REGION_LENGTH__) ? __LOCK_REGION_LENGTH__ : 1K;
|
||||
__SIGNATURE_REGION_LENGTH__ = DEFINED(__SIGNATURE_REGION_LENGTH__) ? __SIGNATURE_REGION_LENGTH__ : 1K;
|
||||
__USER_SIGNATURE_REGION_LENGTH__ = DEFINED(__USER_SIGNATURE_REGION_LENGTH__) ? __USER_SIGNATURE_REGION_LENGTH__ : 1K;
|
||||
MEMORY
|
||||
{
|
||||
text (rx) : ORIGIN = __TEXT_REGION_ORIGIN__, LENGTH = __TEXT_REGION_LENGTH__
|
||||
data (rw!x) : ORIGIN = __DATA_REGION_ORIGIN__, LENGTH = __DATA_REGION_LENGTH__
|
||||
eeprom (rw!x) : ORIGIN = 0x810000, LENGTH = __EEPROM_REGION_LENGTH__
|
||||
fuse (rw!x) : ORIGIN = 0x820000, LENGTH = __FUSE_REGION_LENGTH__
|
||||
lock (rw!x) : ORIGIN = 0x830000, LENGTH = __LOCK_REGION_LENGTH__
|
||||
signature (rw!x) : ORIGIN = 0x840000, LENGTH = __SIGNATURE_REGION_LENGTH__
|
||||
user_signatures (rw!x) : ORIGIN = 0x850000, LENGTH = __USER_SIGNATURE_REGION_LENGTH__
|
||||
}
|
||||
SECTIONS
|
||||
{
|
||||
/* Read-only sections, merged into text segment: */
|
||||
.hash : { *(.hash) }
|
||||
.dynsym : { *(.dynsym) }
|
||||
.dynstr : { *(.dynstr) }
|
||||
.gnu.version : { *(.gnu.version) }
|
||||
.gnu.version_d : { *(.gnu.version_d) }
|
||||
.gnu.version_r : { *(.gnu.version_r) }
|
||||
.rel.init : { *(.rel.init) }
|
||||
.rela.init : { *(.rela.init) }
|
||||
.rel.text :
|
||||
{
|
||||
*(.rel.text)
|
||||
*(.rel.text.*)
|
||||
*(.rel.gnu.linkonce.t*)
|
||||
}
|
||||
.rela.text :
|
||||
{
|
||||
*(.rela.text)
|
||||
*(.rela.text.*)
|
||||
*(.rela.gnu.linkonce.t*)
|
||||
}
|
||||
.rel.fini : { *(.rel.fini) }
|
||||
.rela.fini : { *(.rela.fini) }
|
||||
.rel.rodata :
|
||||
{
|
||||
*(.rel.rodata)
|
||||
*(.rel.rodata.*)
|
||||
*(.rel.gnu.linkonce.r*)
|
||||
}
|
||||
.rela.rodata :
|
||||
{
|
||||
*(.rela.rodata)
|
||||
*(.rela.rodata.*)
|
||||
*(.rela.gnu.linkonce.r*)
|
||||
}
|
||||
.rel.data :
|
||||
{
|
||||
*(.rel.data)
|
||||
*(.rel.data.*)
|
||||
*(.rel.gnu.linkonce.d*)
|
||||
}
|
||||
.rela.data :
|
||||
{
|
||||
*(.rela.data)
|
||||
*(.rela.data.*)
|
||||
*(.rela.gnu.linkonce.d*)
|
||||
}
|
||||
.rel.ctors : { *(.rel.ctors) }
|
||||
.rela.ctors : { *(.rela.ctors) }
|
||||
.rel.dtors : { *(.rel.dtors) }
|
||||
.rela.dtors : { *(.rela.dtors) }
|
||||
.rel.got : { *(.rel.got) }
|
||||
.rela.got : { *(.rela.got) }
|
||||
.rel.bss : { *(.rel.bss) }
|
||||
.rela.bss : { *(.rela.bss) }
|
||||
.rel.plt : { *(.rel.plt) }
|
||||
.rela.plt : { *(.rela.plt) }
|
||||
/* Internal text space or external memory. */
|
||||
.text :
|
||||
{
|
||||
*(.vectors)
|
||||
KEEP(*(.vectors))
|
||||
|
||||
/* Version information kept in flash at a fixed address after the vectors */
|
||||
. = ALIGN(2);
|
||||
KEEP(*(.version))
|
||||
. = ALIGN(2);
|
||||
|
||||
/* For data that needs to reside in the lower 64k of progmem. */
|
||||
*(.progmem.gcc*)
|
||||
/* PR 13812: Placing the trampolines here gives a better chance
|
||||
that they will be in range of the code that uses them. */
|
||||
. = ALIGN(2);
|
||||
__trampolines_start = . ;
|
||||
/* The jump trampolines for the 16-bit limited relocs will reside here. */
|
||||
*(.trampolines)
|
||||
*(.trampolines*)
|
||||
__trampolines_end = . ;
|
||||
/* avr-libc expects these data to reside in lower 64K. */
|
||||
*libprintf_flt.a:*(.progmem.data)
|
||||
*libc.a:*(.progmem.data)
|
||||
*(.progmem*)
|
||||
. = ALIGN(2);
|
||||
/* For future tablejump instruction arrays for 3 byte pc devices.
|
||||
We don't relax jump/call instructions within these sections. */
|
||||
*(.jumptables)
|
||||
*(.jumptables*)
|
||||
/* For code that needs to reside in the lower 128k progmem. */
|
||||
*(.lowtext)
|
||||
*(.lowtext*)
|
||||
__ctors_start = . ;
|
||||
*(.ctors)
|
||||
__ctors_end = . ;
|
||||
__dtors_start = . ;
|
||||
*(.dtors)
|
||||
__dtors_end = . ;
|
||||
KEEP(SORT(*)(.ctors))
|
||||
KEEP(SORT(*)(.dtors))
|
||||
/* From this point on, we don't bother about wether the insns are
|
||||
below or above the 16 bits boundary. */
|
||||
*(.init0) /* Start here after reset. */
|
||||
KEEP (*(.init0))
|
||||
*(.init1)
|
||||
KEEP (*(.init1))
|
||||
*(.init2) /* Clear __zero_reg__, set up stack pointer. */
|
||||
KEEP (*(.init2))
|
||||
*(.init3)
|
||||
KEEP (*(.init3))
|
||||
*(.init4) /* Initialize data and BSS. */
|
||||
KEEP (*(.init4))
|
||||
*(.init5)
|
||||
KEEP (*(.init5))
|
||||
*(.init6) /* C++ constructors. */
|
||||
KEEP (*(.init6))
|
||||
*(.init7)
|
||||
KEEP (*(.init7))
|
||||
*(.init8)
|
||||
KEEP (*(.init8))
|
||||
*(.init9) /* Call main(). */
|
||||
KEEP (*(.init9))
|
||||
*(.text)
|
||||
. = ALIGN(2);
|
||||
*(.text.*)
|
||||
. = ALIGN(2);
|
||||
*(.fini9) /* _exit() starts here. */
|
||||
KEEP (*(.fini9))
|
||||
*(.fini8)
|
||||
KEEP (*(.fini8))
|
||||
*(.fini7)
|
||||
KEEP (*(.fini7))
|
||||
*(.fini6) /* C++ destructors. */
|
||||
KEEP (*(.fini6))
|
||||
*(.fini5)
|
||||
KEEP (*(.fini5))
|
||||
*(.fini4)
|
||||
KEEP (*(.fini4))
|
||||
*(.fini3)
|
||||
KEEP (*(.fini3))
|
||||
*(.fini2)
|
||||
KEEP (*(.fini2))
|
||||
*(.fini1)
|
||||
KEEP (*(.fini1))
|
||||
*(.fini0) /* Infinite loop after program termination. */
|
||||
KEEP (*(.fini0))
|
||||
_etext = . ;
|
||||
} > text
|
||||
.data :
|
||||
{
|
||||
PROVIDE (__data_start = .) ;
|
||||
*(.data)
|
||||
*(.data*)
|
||||
*(.gnu.linkonce.d*)
|
||||
*(.rodata) /* We need to include .rodata here if gcc is used */
|
||||
*(.rodata*) /* with -fdata-sections. */
|
||||
*(.gnu.linkonce.r*)
|
||||
. = ALIGN(2);
|
||||
_edata = . ;
|
||||
PROVIDE (__data_end = .) ;
|
||||
} > data AT> text
|
||||
.bss ADDR(.data) + SIZEOF (.data) : AT (ADDR (.bss))
|
||||
{
|
||||
PROVIDE (__bss_start = .) ;
|
||||
*(.bss)
|
||||
*(.bss*)
|
||||
*(COMMON)
|
||||
PROVIDE (__bss_end = .) ;
|
||||
} > data
|
||||
__data_load_start = LOADADDR(.data);
|
||||
__data_load_end = __data_load_start + SIZEOF(.data);
|
||||
/* Global data not cleared after reset. */
|
||||
.noinit ADDR(.bss) + SIZEOF (.bss) : AT (ADDR (.noinit))
|
||||
{
|
||||
PROVIDE (__noinit_start = .) ;
|
||||
*(.noinit*)
|
||||
PROVIDE (__noinit_end = .) ;
|
||||
_end = . ;
|
||||
PROVIDE (__heap_start = .) ;
|
||||
} > data
|
||||
.eeprom :
|
||||
{
|
||||
/* See .data above... */
|
||||
KEEP(*(.eeprom*))
|
||||
__eeprom_end = . ;
|
||||
} > eeprom
|
||||
.fuse :
|
||||
{
|
||||
KEEP(*(.fuse))
|
||||
KEEP(*(.lfuse))
|
||||
KEEP(*(.hfuse))
|
||||
KEEP(*(.efuse))
|
||||
} > fuse
|
||||
.lock :
|
||||
{
|
||||
KEEP(*(.lock*))
|
||||
} > lock
|
||||
.signature :
|
||||
{
|
||||
KEEP(*(.signature*))
|
||||
} > signature
|
||||
.user_signatures :
|
||||
{
|
||||
KEEP(*(.user_signatures*))
|
||||
} > user_signatures
|
||||
/* Stabs debugging sections. */
|
||||
.stab 0 : { *(.stab) }
|
||||
.stabstr 0 : { *(.stabstr) }
|
||||
.stab.excl 0 : { *(.stab.excl) }
|
||||
.stab.exclstr 0 : { *(.stab.exclstr) }
|
||||
.stab.index 0 : { *(.stab.index) }
|
||||
.stab.indexstr 0 : { *(.stab.indexstr) }
|
||||
.comment 0 : { *(.comment) }
|
||||
.note.gnu.build-id : { *(.note.gnu.build-id) }
|
||||
/* DWARF debug sections.
|
||||
Symbols in the DWARF debugging sections are relative to the beginning
|
||||
of the section so we begin them at 0. */
|
||||
/* DWARF 1 */
|
||||
.debug 0 : { *(.debug) }
|
||||
.line 0 : { *(.line) }
|
||||
/* GNU DWARF 1 extensions */
|
||||
.debug_srcinfo 0 : { *(.debug_srcinfo) }
|
||||
.debug_sfnames 0 : { *(.debug_sfnames) }
|
||||
/* DWARF 1.1 and DWARF 2 */
|
||||
.debug_aranges 0 : { *(.debug_aranges) }
|
||||
.debug_pubnames 0 : { *(.debug_pubnames) }
|
||||
/* DWARF 2 */
|
||||
.debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) }
|
||||
.debug_abbrev 0 : { *(.debug_abbrev) }
|
||||
.debug_line 0 : { *(.debug_line .debug_line.* .debug_line_end ) }
|
||||
.debug_frame 0 : { *(.debug_frame) }
|
||||
.debug_str 0 : { *(.debug_str) }
|
||||
.debug_loc 0 : { *(.debug_loc) }
|
||||
.debug_macinfo 0 : { *(.debug_macinfo) }
|
||||
/* SGI/MIPS DWARF 2 extensions */
|
||||
.debug_weaknames 0 : { *(.debug_weaknames) }
|
||||
.debug_funcnames 0 : { *(.debug_funcnames) }
|
||||
.debug_typenames 0 : { *(.debug_typenames) }
|
||||
.debug_varnames 0 : { *(.debug_varnames) }
|
||||
/* DWARF 3 */
|
||||
.debug_pubtypes 0 : { *(.debug_pubtypes) }
|
||||
.debug_ranges 0 : { *(.debug_ranges) }
|
||||
/* DWARF Extension. */
|
||||
.debug_macro 0 : { *(.debug_macro) }
|
||||
}
|
||||
|
|
@ -28,6 +28,7 @@ struct AxisConfig {
|
|||
uint8_t iHold; ///< holding current
|
||||
bool stealth; ///< Default to Stealth mode
|
||||
long double stepsPerUnit; ///< steps per unit
|
||||
long double stepsPerUnitReciprocal; ///< reciprocal of step per unit (used to avoid divisions)
|
||||
int8_t sg_thrs; /// @todo 7bit two's complement for the sg_thrs
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -104,6 +104,7 @@ static constexpr U_mm pulleyHelperMove = 10.0_mm; ///< Helper move for Load/Unlo
|
|||
static constexpr U_mm cutLength = 8.0_mm;
|
||||
static constexpr U_mm fsensorToNozzle = 30.0_mm; ///< ~20mm from MK4's filament sensor through extruder gears into nozzle
|
||||
static constexpr U_mm fsensorToNozzleAvoidGrind = 5.0_mm;
|
||||
static constexpr U_mm fsensorToNozzleAvoidGrindUnload = 20.0_mm;
|
||||
/// Check the state of FSensor after this amount of filament got (hopefully) pulled out while unloading.
|
||||
static constexpr U_mm fsensorUnloadCheckDistance = 40.0_mm;
|
||||
|
||||
|
|
@ -115,6 +116,7 @@ static constexpr AxisConfig pulley = {
|
|||
.iHold = 0, /// 17mA in SpreadCycle, freewheel in StealthChop
|
||||
.stealth = false,
|
||||
.stepsPerUnit = (200 * 8 / 19.147274),
|
||||
.stepsPerUnitReciprocal = 1 / ((200 * 8 / 19.147274)),
|
||||
.sg_thrs = 8,
|
||||
};
|
||||
|
||||
|
|
@ -140,6 +142,7 @@ static constexpr AxisConfig selector = {
|
|||
.iHold = 0, /// 17mA in SpreadCycle, freewheel in StealthChop
|
||||
.stealth = false,
|
||||
.stepsPerUnit = (200 * 8 / 8.),
|
||||
.stepsPerUnitReciprocal = 1 / ((200 * 8 / 8.)),
|
||||
.sg_thrs = 3,
|
||||
};
|
||||
|
||||
|
|
@ -190,6 +193,7 @@ static constexpr AxisConfig idler = {
|
|||
.iHold = 5, /// 99mA - parked current
|
||||
.stealth = false,
|
||||
.stepsPerUnit = (200 * 16 / 360.),
|
||||
.stepsPerUnitReciprocal = 1 / ((200 * 16 / 360.)),
|
||||
.sg_thrs = 7,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -52,31 +52,31 @@ struct GPIO_pin {
|
|||
// No constructor here in order to allow brace-initialization in old
|
||||
// gcc versions/standards
|
||||
GPIO_TypeDef *const port;
|
||||
const uint8_t pin;
|
||||
const uint8_t pin_mask;
|
||||
};
|
||||
|
||||
__attribute__((always_inline)) inline void WritePin(const GPIO_pin portPin, Level level) {
|
||||
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
|
||||
if (level == Level::high)
|
||||
portPin.port->PORTx |= (1 << portPin.pin);
|
||||
portPin.port->PORTx |= portPin.pin_mask;
|
||||
else
|
||||
portPin.port->PORTx &= ~(1 << portPin.pin);
|
||||
portPin.port->PORTx &= ~portPin.pin_mask;
|
||||
}
|
||||
}
|
||||
|
||||
__attribute__((always_inline)) inline Level ReadPin(const GPIO_pin portPin) {
|
||||
#ifdef __AVR__
|
||||
return (Level)((portPin.port->PINx & (1 << portPin.pin)) != 0);
|
||||
return (Level)((portPin.port->PINx & portPin.pin_mask) != 0);
|
||||
#else
|
||||
// Return the value modified by WritePin
|
||||
return (Level)((portPin.port->PORTx & (1 << portPin.pin)) != 0);
|
||||
return (Level)((portPin.port->PORTx & portPin.pin_mask) != 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
__attribute__((always_inline)) inline void TogglePin(const GPIO_pin portPin) {
|
||||
#ifdef __AVR__
|
||||
// Optimized path for AVR, resulting in a pin toggle
|
||||
portPin.port->PINx = (1 << portPin.pin);
|
||||
portPin.port->PINx = portPin.pin_mask;
|
||||
#else
|
||||
WritePin(portPin, (Level)(ReadPin(portPin) != Level::high));
|
||||
#endif
|
||||
|
|
@ -85,9 +85,9 @@ __attribute__((always_inline)) inline void TogglePin(const GPIO_pin portPin) {
|
|||
__attribute__((always_inline)) inline void Init(const GPIO_pin portPin, GPIO_InitTypeDef GPIO_Init) {
|
||||
if (GPIO_Init.mode == Mode::output) {
|
||||
WritePin(portPin, GPIO_Init.level);
|
||||
portPin.port->DDRx |= (1 << portPin.pin);
|
||||
portPin.port->DDRx |= portPin.pin_mask;
|
||||
} else {
|
||||
portPin.port->DDRx &= ~(1 << portPin.pin);
|
||||
portPin.port->DDRx &= ~portPin.pin_mask;
|
||||
WritePin(portPin, (Level)GPIO_Init.pull);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -212,7 +212,7 @@ void CommandBase::ErrDisengagingIdler() {
|
|||
void CommandBase::GoToErrDisengagingIdler(ErrorCode deferredEC) {
|
||||
state = ProgressCode::ERRDisengagingIdler;
|
||||
deferredErrorCode = deferredEC;
|
||||
ml::leds.SetPairButOffOthers(mg::globals.ActiveSlot(), ml::off, ml::blink0);
|
||||
ml::leds.ActiveSlotError();
|
||||
mi::idler.Disengage();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ bool CutFilament::StepInner() {
|
|||
// move selector aside - prepare the blade into active position
|
||||
state = ProgressCode::PreparingBlade;
|
||||
mg::globals.SetFilamentLoaded(cutSlot, mg::FilamentLoadState::AtPulley);
|
||||
ml::leds.SetPairButOffOthers(mg::globals.ActiveSlot(), ml::blink0, ml::off);
|
||||
ml::leds.ActiveSlotProcessing();
|
||||
MoveSelector(cutSlot + 1);
|
||||
}
|
||||
}
|
||||
|
|
@ -94,7 +94,7 @@ bool CutFilament::StepInner() {
|
|||
case ProgressCode::PreparingBlade:
|
||||
if (ms::selector.Slot() == cutSlot + 1) {
|
||||
state = ProgressCode::PushingFilament;
|
||||
mpu::pulley.PlanMove(config::cutLength + config::cuttingEdgeRetract, config::pulleySlowFeedrate);
|
||||
mpu::pulley.PlanMove(mg::globals.CutLength() + config::cuttingEdgeRetract, mg::globals.PulleySlowFeedrate_mm_s());
|
||||
}
|
||||
break;
|
||||
case ProgressCode::PushingFilament:
|
||||
|
|
@ -123,7 +123,7 @@ bool CutFilament::StepInner() {
|
|||
// revert move speed
|
||||
mg::globals.SetSelectorFeedrate_mm_s(savedSelectorFeedRate_mm_s);
|
||||
ms::selector.InvalidateHoming();
|
||||
mpu::pulley.PlanMove(-config::cuttingEdgeRetract, config::pulleySlowFeedrate);
|
||||
mpu::pulley.PlanMove(-config::cuttingEdgeRetract, mg::globals.PulleySlowFeedrate_mm_s());
|
||||
}
|
||||
break;
|
||||
case ProgressCode::Homing:
|
||||
|
|
@ -134,7 +134,7 @@ bool CutFilament::StepInner() {
|
|||
case ProgressCode::ReturningSelector:
|
||||
if (ms::selector.State() == ms::selector.Ready) {
|
||||
FinishedOK();
|
||||
ml::leds.SetPairButOffOthers(mg::globals.ActiveSlot(), ml::on, ml::off);
|
||||
ml::leds.ActiveSlotDonePrimed();
|
||||
}
|
||||
break;
|
||||
case ProgressCode::OK:
|
||||
|
|
|
|||
|
|
@ -28,8 +28,6 @@ public:
|
|||
ErrorCode Error() const override;
|
||||
|
||||
private:
|
||||
constexpr static const uint16_t cutStepsPre = 700;
|
||||
constexpr static const uint16_t cutStepsPost = 150;
|
||||
UnloadFilament unl; ///< a high-level command/operation may be used as a building block of other operations as well
|
||||
FeedToFinda feed;
|
||||
RetractFromFinda retract;
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ bool EjectFilament::StepInner() {
|
|||
if (mi::idler.Engaged()) {
|
||||
state = ProgressCode::EjectingFilament;
|
||||
mpu::pulley.InitAxis();
|
||||
mpu::pulley.PlanMove(config::ejectFromCuttingEdge, config::pulleySlowFeedrate);
|
||||
mpu::pulley.PlanMove(config::ejectFromCuttingEdge, mg::globals.PulleySlowFeedrate_mm_s());
|
||||
}
|
||||
break;
|
||||
case ProgressCode::EjectingFilament:
|
||||
|
|
|
|||
|
|
@ -7,10 +7,6 @@
|
|||
|
||||
namespace logic {
|
||||
|
||||
// These cannot be class memebers without definition until c++17
|
||||
static constexpr modules::motion::P_pos_t ejectLength = 50.0_P_mm; //@@TODO
|
||||
static constexpr modules::motion::P_speed_t ejectSpeed = 1000.0_P_mm_s; //@@TODO
|
||||
|
||||
/// @brief A high-level command state machine - handles the complex logic of ejecting filament
|
||||
///
|
||||
/// The eject operation consists of:
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ void FeedToBondtech::Reset(uint8_t maxRetries) {
|
|||
dbg_logic_P(PSTR("\nFeed to Bondtech\n\n"));
|
||||
state = EngagingIdler;
|
||||
this->maxRetries = maxRetries;
|
||||
ml::leds.SetMode(mg::globals.ActiveSlot(), ml::green, ml::blink0);
|
||||
ml::leds.ActiveSlotProcessing();
|
||||
mi::idler.Engage(mg::globals.ActiveSlot());
|
||||
}
|
||||
|
||||
|
|
@ -78,7 +78,7 @@ bool FeedToBondtech::Step() {
|
|||
mg::globals.SetFilamentLoaded(mg::globals.ActiveSlot(), mg::FilamentLoadState::InNozzle);
|
||||
mi::idler.PartiallyDisengage(mg::globals.ActiveSlot());
|
||||
// while disengaging the idler, keep on moving with the pulley to avoid grinding while the printer is trying to grab the filament itself
|
||||
mpu::pulley.PlanMove(config::fsensorToNozzleAvoidGrind, config::pulleySlowFeedrate);
|
||||
mpu::pulley.PlanMove(config::fsensorToNozzleAvoidGrind, mg::globals.PulleySlowFeedrate_mm_s());
|
||||
state = PartiallyDisengagingIdler;
|
||||
}
|
||||
return false;
|
||||
|
|
@ -95,7 +95,7 @@ bool FeedToBondtech::Step() {
|
|||
dbg_logic_P(PSTR("Feed to Bondtech --> Idler disengaged"));
|
||||
dbg_logic_fP(PSTR("Pulley end steps %u"), mpu::pulley.CurrentPosition_mm());
|
||||
state = OK;
|
||||
ml::leds.SetMode(mg::globals.ActiveSlot(), ml::green, ml::on);
|
||||
ml::leds.ActiveSlotDonePrimed();
|
||||
}
|
||||
return false;
|
||||
case OK:
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ namespace logic {
|
|||
/// To prevent constant EEPROM updates only significant changes are recorded.
|
||||
struct FeedToBondtech {
|
||||
/// internal states of the state machine
|
||||
enum {
|
||||
enum : uint8_t {
|
||||
EngagingIdler,
|
||||
PushingFilamentFast,
|
||||
PushingFilamentToFSensor,
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ bool FeedToFinda::Reset(bool feedPhaseLimited, bool haltAtEnd) {
|
|||
state = EngagingIdler;
|
||||
this->feedPhaseLimited = feedPhaseLimited;
|
||||
this->haltAtEnd = haltAtEnd;
|
||||
ml::leds.SetPairButOffOthers(mg::globals.ActiveSlot(), ml::blink0, ml::off);
|
||||
ml::leds.ActiveSlotProcessing();
|
||||
if (ms::selector.MoveToSlot(mg::globals.ActiveSlot()) != ms::Selector::OperationResult::Accepted) {
|
||||
// We can't get any FINDA readings if the selector is at the wrong spot - move it accordingly if necessary
|
||||
// And prevent issuing any commands to the idler in such an error state
|
||||
|
|
@ -44,14 +44,14 @@ bool FeedToFinda::Step() {
|
|||
// mpu::pulley.PlanMove(config::filamentMinLoadedToMMU, config::pulleySlowFeedrate);
|
||||
// }
|
||||
|
||||
mpu::pulley.PlanMove(config::maximumFeedToFinda, config::pulleySlowFeedrate);
|
||||
mpu::pulley.PlanMove(config::maximumFeedToFinda, mg::globals.PulleySlowFeedrate_mm_s());
|
||||
if (feedPhaseLimited) {
|
||||
state = PushingFilament;
|
||||
} else {
|
||||
state = PushingFilamentUnlimited;
|
||||
// in unlimited move we plan 2 moves at once to make the move "seamless"
|
||||
// one move has already been planned above, this is the second one
|
||||
mpu::pulley.PlanMove(config::maximumFeedToFinda, config::pulleySlowFeedrate);
|
||||
mpu::pulley.PlanMove(config::maximumFeedToFinda, mg::globals.PulleySlowFeedrate_mm_s());
|
||||
}
|
||||
mg::globals.SetFilamentLoaded(mg::globals.ActiveSlot(), mg::FilamentLoadState::InSelector);
|
||||
mui::userInput.Clear(); // remove all buffered events if any just before we wait for some input
|
||||
|
|
@ -68,7 +68,7 @@ bool FeedToFinda::Step() {
|
|||
return true; // return immediately to allow for a seamless planning of another move (like feeding to bondtech)
|
||||
} else if (mm::motion.QueueEmpty()) { // all moves have been finished and FINDA didn't switch on
|
||||
state = Failed;
|
||||
ml::leds.SetPairButOffOthers(mg::globals.ActiveSlot(), ml::off, ml::blink0);
|
||||
ml::leds.ActiveSlotError();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
|
@ -85,7 +85,7 @@ bool FeedToFinda::Step() {
|
|||
return true; // return immediately to allow for a seamless planning of another move (like feeding to bondtech)
|
||||
} else if (mm::motion.PlannedMoves(mm::Pulley) < 2) {
|
||||
// plan another move to make the illusion of unlimited moves
|
||||
mpu::pulley.PlanMove(config::maximumFeedToFinda, config::pulleySlowFeedrate);
|
||||
mpu::pulley.PlanMove(config::maximumFeedToFinda, mg::globals.PulleySlowFeedrate_mm_s());
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ namespace logic {
|
|||
/// Leaves the Pulley axis enabled for chaining potential next operations
|
||||
struct FeedToFinda {
|
||||
/// internal states of the state machine
|
||||
enum {
|
||||
enum : uint8_t {
|
||||
EngagingIdler,
|
||||
PushingFilament,
|
||||
PushingFilamentUnlimited,
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ bool HWSanity::Reset(uint8_t param) {
|
|||
return true;
|
||||
}
|
||||
|
||||
enum pin_bits {
|
||||
enum pin_bits : uint8_t {
|
||||
BIT_STEP = 0b001,
|
||||
BIT_DIR = 0b010,
|
||||
BIT_ENA = 0b100,
|
||||
|
|
|
|||
|
|
@ -50,8 +50,6 @@ void logic::LoadFilament::Reset2(bool feedPhaseLimited) {
|
|||
if (!feed.Reset(feedPhaseLimited, true)) {
|
||||
// selector refused to move
|
||||
GoToErrDisengagingIdler(ErrorCode::FINDA_FLICKERS);
|
||||
} else {
|
||||
ml::leds.SetPairButOffOthers(mg::globals.ActiveSlot(), ml::blink0, ml::off);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,16 +2,19 @@
|
|||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
/// Base class for ProgressCode and its extensions
|
||||
using RawProgressCode = uint_fast8_t;
|
||||
|
||||
/// A complete set of progress codes which may be reported while running a high-level command/operation
|
||||
/// This header file shall be included in the printer's firmware as well as a reference,
|
||||
/// therefore the progress codes have been extracted to one place
|
||||
enum class ProgressCode : uint_fast8_t {
|
||||
enum class ProgressCode : RawProgressCode {
|
||||
OK = 0, ///< finished ok
|
||||
|
||||
EngagingIdler, // P1
|
||||
DisengagingIdler, // P2
|
||||
UnloadingToFinda, // P3
|
||||
UnloadingToPulley, //P4
|
||||
UnloadingToPulley, // P4
|
||||
FeedingToFinda, // P5
|
||||
FeedingToBondtech, // P6
|
||||
FeedingToNozzle, // P7
|
||||
|
|
@ -37,7 +40,7 @@ enum class ProgressCode : uint_fast8_t {
|
|||
RetractingFromFinda, // P25
|
||||
|
||||
Homing, // P26
|
||||
MovingSelector, // P27
|
||||
MovingSelector, // P2
|
||||
|
||||
FeedingToFSensor, // P28
|
||||
|
||||
|
|
@ -50,5 +53,9 @@ enum class ProgressCode : uint_fast8_t {
|
|||
HWTestDisplay, // P35
|
||||
ErrHwTestFailed, // P36
|
||||
|
||||
/// Keep as the last item (except for Empty)
|
||||
/// Used for extending the progress codes on the printer side
|
||||
_cnt,
|
||||
|
||||
Empty = 0xff // dummy empty state
|
||||
};
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
/// therefore the error codes have been extracted to one place.
|
||||
///
|
||||
/// Please note that currently only LoadFilament can return something else than "OK"
|
||||
enum class ResultCode : uint_fast16_t {
|
||||
enum class ResultCode : uint_fast8_t {
|
||||
OK = 0,
|
||||
Cancelled = 1
|
||||
};
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ namespace logic {
|
|||
void RetractFromFinda::Reset() {
|
||||
dbg_logic_P(PSTR("\nRetract from FINDA\n\n"));
|
||||
state = EngagingIdler;
|
||||
ml::leds.SetPairButOffOthers(mg::globals.ActiveSlot(), ml::blink0, ml::off);
|
||||
ml::leds.ActiveSlotProcessing();
|
||||
mi::idler.Engage(mg::globals.ActiveSlot());
|
||||
}
|
||||
|
||||
|
|
@ -33,10 +33,10 @@ bool RetractFromFinda::Step() {
|
|||
state = OK;
|
||||
mg::globals.SetFilamentLoaded(mg::globals.ActiveSlot(), mg::FilamentLoadState::AtPulley);
|
||||
dbg_logic_fP(PSTR("Pulley end steps %u"), mpu::pulley.CurrentPosition_mm());
|
||||
ml::leds.SetMode(mg::globals.ActiveSlot(), ml::green, ml::off);
|
||||
ml::leds.ActiveSlotDoneEmpty();
|
||||
} else { // FINDA didn't switch off
|
||||
state = Failed;
|
||||
ml::leds.SetPairButOffOthers(mg::globals.ActiveSlot(), ml::off, ml::blink0);
|
||||
ml::leds.ActiveSlotError();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ namespace logic {
|
|||
/// - leaves idler engaged for chaining operations
|
||||
struct RetractFromFinda {
|
||||
/// internal states of the state machine
|
||||
enum {
|
||||
enum : uint8_t {
|
||||
EngagingIdler,
|
||||
UnloadBackToPTFE,
|
||||
OK,
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ bool ToolChange::Reset(uint8_t param) {
|
|||
if (mg::globals.FilamentLoaded() >= mg::FilamentLoadState::InSelector) {
|
||||
dbg_logic_P(PSTR("Filament is loaded --> unload"));
|
||||
state = ProgressCode::UnloadingFilament;
|
||||
unl.Reset(mg::globals.ActiveSlot());
|
||||
unl.Reset2(mg::globals.ActiveSlot());
|
||||
} else {
|
||||
mg::globals.SetFilamentLoaded(plannedSlot, mg::FilamentLoadState::InSelector); // activate the correct slot, feed uses that
|
||||
if (feed.Reset(true, false)) {
|
||||
|
|
@ -54,20 +54,19 @@ bool ToolChange::Reset(uint8_t param) {
|
|||
return true;
|
||||
}
|
||||
|
||||
void logic::ToolChange::GoToFeedingToBondtech() {
|
||||
ml::leds.SetPairButOffOthers(mg::globals.ActiveSlot(), ml::blink0, ml::off);
|
||||
void ToolChange::GoToFeedingToBondtech() {
|
||||
james.Reset(3);
|
||||
state = ProgressCode::FeedingToBondtech;
|
||||
error = ErrorCode::RUNNING;
|
||||
}
|
||||
|
||||
void logic::ToolChange::ToolChangeFinishedCorrectly() {
|
||||
ml::leds.SetPairButOffOthers(mg::globals.ActiveSlot(), ml::on, ml::off);
|
||||
void ToolChange::ToolChangeFinishedCorrectly() {
|
||||
ml::leds.ActiveSlotDonePrimed();
|
||||
mui::userInput.SetPrinterInCharge(false);
|
||||
FinishedOK();
|
||||
}
|
||||
|
||||
void logic::ToolChange::GoToFeedingToFinda() {
|
||||
void ToolChange::GoToFeedingToFinda() {
|
||||
state = ProgressCode::FeedingToFinda;
|
||||
error = ErrorCode::RUNNING;
|
||||
mg::globals.SetFilamentLoaded(plannedSlot, mg::FilamentLoadState::AtPulley);
|
||||
|
|
|
|||
|
|
@ -28,11 +28,17 @@ bool UnloadFilament::Reset(uint8_t /*param*/) {
|
|||
mpu::pulley.InitAxis();
|
||||
state = ProgressCode::UnloadingToFinda;
|
||||
error = ErrorCode::RUNNING;
|
||||
skipDisengagingIdler = false;
|
||||
unl.Reset(maxRetries);
|
||||
ml::leds.SetAllOff();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UnloadFilament::Reset2(uint8_t param) {
|
||||
bool rv = Reset(param);
|
||||
skipDisengagingIdler = true;
|
||||
return rv;
|
||||
}
|
||||
|
||||
void UnloadFilament::UnloadFinishedCorrectly() {
|
||||
FinishedOK();
|
||||
mpu::pulley.Disable();
|
||||
|
|
@ -77,7 +83,11 @@ bool UnloadFilament::StepInner() {
|
|||
GoToErrDisengagingIdler(ErrorCode::FINDA_DIDNT_SWITCH_OFF); // signal unloading error
|
||||
} else {
|
||||
state = ProgressCode::DisengagingIdler;
|
||||
mi::idler.Disengage();
|
||||
if (skipDisengagingIdler && ms::selector.State() == ms::Selector::Ready) {
|
||||
UnloadFinishedCorrectly(); // skip disengaging the Idler - to be used inside ToolChange to speed up
|
||||
} else {
|
||||
mi::idler.Disengage();
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -12,12 +12,15 @@ namespace logic {
|
|||
class UnloadFilament : public CommandBase {
|
||||
public:
|
||||
inline constexpr UnloadFilament()
|
||||
: CommandBase() {}
|
||||
: CommandBase()
|
||||
, skipDisengagingIdler(false) {}
|
||||
|
||||
/// Restart the automaton
|
||||
/// @param param is not used, always unloads from the active slot
|
||||
bool Reset(uint8_t param) override;
|
||||
|
||||
bool Reset2(uint8_t param);
|
||||
|
||||
/// @returns true if the state machine finished its job, false otherwise
|
||||
bool StepInner() override;
|
||||
|
||||
|
|
@ -32,6 +35,7 @@ private:
|
|||
UnloadToFinda unl;
|
||||
FeedToFinda feed;
|
||||
RetractFromFinda retract;
|
||||
bool skipDisengagingIdler;
|
||||
};
|
||||
|
||||
/// The one and only instance of UnloadFilament state machine in the FW
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
#include "../modules/motion.h"
|
||||
#include "../modules/permanent_storage.h"
|
||||
#include "../modules/pulley.h"
|
||||
#include "../modules/timebase.h"
|
||||
|
||||
namespace logic {
|
||||
|
||||
|
|
@ -19,19 +20,40 @@ void UnloadToFinda::Reset(uint8_t maxTries) {
|
|||
} else {
|
||||
// FINDA is sensing the filament, plan moves to unload it
|
||||
state = EngagingIdler;
|
||||
mi::idler.Engage(mg::globals.ActiveSlot());
|
||||
mi::idler.PartiallyDisengage(mg::globals.ActiveSlot()); // basically prepare before the active slot - saves ~1s
|
||||
started_ms = mt::timebase.Millis();
|
||||
ml::leds.ActiveSlotProcessing();
|
||||
}
|
||||
}
|
||||
|
||||
bool UnloadToFinda::Step() {
|
||||
switch (state) {
|
||||
// start by engaging the idler into intermediate position
|
||||
// Then, wait for !fsensor.Pressed: that's to speed-up the pull process - unload operation will be started during the purging moves
|
||||
// and as soon as the fsensor turns off, the MMU engages the idler fully and starts pulling.
|
||||
// It will not wait for the extruder to finish the relieve move.
|
||||
// However, such an approach breaks running the MMU on a non-reworked MK4/C1, which hasn't been officially supported, but possible (with some level of uncertainity).
|
||||
case EngagingIdler:
|
||||
if (mg::globals.FilamentLoaded() >= mg::FilamentLoadState::InSelector) {
|
||||
state = UnloadingToFinda;
|
||||
mpu::pulley.InitAxis();
|
||||
ml::leds.SetMode(mg::globals.ActiveSlot(), ml::green, ml::blink0);
|
||||
if (!mi::idler.PartiallyDisengaged()) { // just waiting for Idler to get into the target intermediate position
|
||||
return false;
|
||||
}
|
||||
if (mfs::fsensor.Pressed()) { // still pressed, printer didn't free the filament yet
|
||||
if (mt::timebase.Elapsed(started_ms, 4000)) {
|
||||
state = FailedFSensor; // fsensor didn't turn off within 4 seconds, something is seriously wrong
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
state = FailedFINDA;
|
||||
// fsensor is OFF and Idler is partially engaged, engage the Idler fully and pull
|
||||
if (mg::globals.FilamentLoaded() >= mg::FilamentLoadState::InSelector) {
|
||||
state = UnloadingToFinda;
|
||||
mpu::pulley.InitAxis();
|
||||
mi::idler.Engage(mg::globals.ActiveSlot());
|
||||
|
||||
// slow move for the first few millimeters - help the printer relieve the filament while engaging the Idler fully
|
||||
mpu::pulley.PlanMove(-config::fsensorToNozzleAvoidGrindUnload, mg::globals.PulleySlowFeedrate_mm_s(), mg::globals.PulleySlowFeedrate_mm_s());
|
||||
} else {
|
||||
state = FailedFINDA;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
case UnloadingToFinda:
|
||||
|
|
@ -55,17 +77,20 @@ bool UnloadToFinda::Step() {
|
|||
// This scenario should not be tried again - repeating it may cause more damage to filament + potentially more collateral damage
|
||||
state = FailedFSensor;
|
||||
mm::motion.AbortPlannedMoves(); // stop rotating the pulley
|
||||
ml::leds.SetMode(mg::globals.ActiveSlot(), ml::green, ml::off);
|
||||
ml::leds.ActiveSlotDoneEmpty();
|
||||
} else if (!mf::finda.Pressed()) {
|
||||
// detected end of filament
|
||||
state = OK;
|
||||
mm::motion.AbortPlannedMoves(); // stop rotating the pulley
|
||||
ml::leds.SetMode(mg::globals.ActiveSlot(), ml::green, ml::off);
|
||||
ml::leds.ActiveSlotDoneEmpty();
|
||||
} else if (/*tmc2130_read_gstat() &&*/ mm::motion.QueueEmpty()) {
|
||||
// we reached the end of move queue, but the FINDA didn't switch off
|
||||
// two possible causes - grinded filament or malfunctioning FINDA
|
||||
if (--maxTries) {
|
||||
Reset(maxTries); // try again
|
||||
// Ideally, the Idler shall rehome and then try again.
|
||||
// That would auto-resolve errors caused by slipped or misaligned Idler
|
||||
mi::idler.InvalidateHoming();
|
||||
Reset(maxTries);
|
||||
} else {
|
||||
state = FailedFINDA;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ namespace logic {
|
|||
/// - load/unload to finda
|
||||
struct UnloadToFinda {
|
||||
/// internal states of the state machine
|
||||
enum {
|
||||
enum : uint8_t {
|
||||
EngagingIdler,
|
||||
UnloadingToFinda,
|
||||
WaitingForFINDA,
|
||||
|
|
@ -24,7 +24,8 @@ struct UnloadToFinda {
|
|||
inline constexpr UnloadToFinda()
|
||||
: state(OK)
|
||||
, maxTries(3)
|
||||
, unloadStart_mm(0) {}
|
||||
, unloadStart_mm(0)
|
||||
, started_ms(0) {}
|
||||
|
||||
/// Restart the automaton
|
||||
/// @param maxTries maximum number of retried attempts before reporting a fail
|
||||
|
|
@ -40,6 +41,7 @@ private:
|
|||
uint8_t state;
|
||||
uint8_t maxTries;
|
||||
int32_t unloadStart_mm; // intentionally trying to avoid using U_mm because it is a float (reps. long double)
|
||||
uint16_t started_ms; // timeout on fsensor turn off
|
||||
};
|
||||
|
||||
} // namespace logic
|
||||
|
|
|
|||
|
|
@ -95,10 +95,7 @@ static void setup2() {
|
|||
mb::buttons.Step();
|
||||
|
||||
// Turn off all leds
|
||||
for (uint8_t i = 0; i < config::toolCount; i++) {
|
||||
ml::leds.SetMode(i, ml::green, ml::off);
|
||||
ml::leds.SetMode(i, ml::red, ml::off);
|
||||
}
|
||||
ml::leds.SetAllOff();
|
||||
ml::leds.Step();
|
||||
mb::buttons.Step();
|
||||
|
||||
|
|
@ -128,7 +125,7 @@ static void setup2() {
|
|||
|
||||
// activate the correct LED if filament is present
|
||||
if (mg::globals.FilamentLoaded() > mg::FilamentLoadState::AtPulley) {
|
||||
ml::leds.SetMode(mg::globals.ActiveSlot(), ml::green, ml::on);
|
||||
ml::leds.ActiveSlotDonePrimed();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -94,12 +94,13 @@ constexpr AxisUnit<T, A, U> operator*(const long double f, const AxisUnit<T, A,
|
|||
struct AxisScale {
|
||||
unit::UnitBase base;
|
||||
long double stepsPerUnit;
|
||||
long double stepsPerUnitReciprocal;
|
||||
};
|
||||
|
||||
static constexpr AxisScale axisScale[config::NUM_AXIS] = {
|
||||
{ config::pulleyLimits.base, config::pulley.stepsPerUnit },
|
||||
{ config::selectorLimits.base, config::selector.stepsPerUnit },
|
||||
{ config::idlerLimits.base, config::idler.stepsPerUnit },
|
||||
{ config::pulleyLimits.base, config::pulley.stepsPerUnit, config::pulley.stepsPerUnitReciprocal },
|
||||
{ config::selectorLimits.base, config::selector.stepsPerUnit, config::selector.stepsPerUnitReciprocal },
|
||||
{ config::idlerLimits.base, config::idler.stepsPerUnit, config::idler.stepsPerUnitReciprocal },
|
||||
};
|
||||
|
||||
/// Convert a unit::Unit to AxisUnit.
|
||||
|
|
@ -126,7 +127,7 @@ template <typename U, typename AU, typename T = int32_t>
|
|||
static constexpr T axisUnitToTruncatedUnit(AU v, long double mul = 1.) {
|
||||
static_assert(AU::unit == U::unit, "incorrect unit type conversion");
|
||||
static_assert(U::base == axisScale[AU::axis].base, "incorrect unit base conversion");
|
||||
return { ((T)v.v / (T)(axisScale[AU::axis].stepsPerUnit / mul)) };
|
||||
return { ((T)(v.v * (axisScale[AU::axis].stepsPerUnitReciprocal / mul))) };
|
||||
}
|
||||
|
||||
/// Truncate an Unit type to an integer (normally int32_t)
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ private:
|
|||
};
|
||||
|
||||
/// Enum of buttons - used also as indices in an array of buttons to keep the code size tight.
|
||||
enum {
|
||||
enum : uint8_t {
|
||||
Right = 0,
|
||||
Middle,
|
||||
Left
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ private:
|
|||
/// Intentionally not modeled as an enum class
|
||||
/// as it would impose additional casts which do not play well with the struct Flags
|
||||
/// and would make the code less readable
|
||||
enum State {
|
||||
enum State : uint8_t {
|
||||
Waiting = 0,
|
||||
Detected,
|
||||
WaitForRelease,
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ void Globals::Init() {
|
|||
ResetIdlerHomingFeedrate();
|
||||
|
||||
ResetCutIRunCurrent();
|
||||
ResetCutLength();
|
||||
}
|
||||
|
||||
uint8_t Globals::ActiveSlot() const {
|
||||
|
|
|
|||
|
|
@ -109,6 +109,10 @@ public:
|
|||
void ResetCutIRunCurrent() { cutIRunCurrent = config::selectorCutIRun; }
|
||||
void SetCutIRunCurrent(uint8_t v) { cutIRunCurrent = v; }
|
||||
|
||||
config::U_mm CutLength() const { return config::U_mm({ (long double)cutLength_mm }); }
|
||||
void ResetCutLength() { cutLength_mm = config::cutLength.v; }
|
||||
void SetCutLength(uint8_t v) { cutLength_mm = v; }
|
||||
|
||||
private:
|
||||
/// Sets the active slot, usually after some command/operation.
|
||||
/// Also updates the EEPROM records accordingly
|
||||
|
|
@ -132,6 +136,7 @@ private:
|
|||
uint16_t idlerHomingFeedrate_deg_s;
|
||||
|
||||
uint8_t cutIRunCurrent;
|
||||
uint8_t cutLength_mm;
|
||||
};
|
||||
|
||||
/// The one and only instance of global state variables
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ void Idler::PlanHomingMoveBack() {
|
|||
bool Idler::FinishHomingAndPlanMoveToParkPos() {
|
||||
// check the axis' length
|
||||
if (AxisDistance(mm::axisUnitToTruncatedUnit<config::U_deg>(mm::motion.CurPosition<mm::Idler>()))
|
||||
< (config::idlerLimits.lenght.v - 10)) { //@@TODO is 10 degrees ok?
|
||||
< uint16_t(config::idlerLimits.lenght.v - 10)) { //@@TODO is 10 degrees ok?
|
||||
return false; // we couldn't home correctly, we cannot set the Idler's position
|
||||
}
|
||||
|
||||
|
|
@ -64,32 +64,12 @@ void Idler::FinishMove() {
|
|||
}
|
||||
|
||||
bool Idler::StallGuardAllowed(bool forward) const {
|
||||
const uint8_t checkDistance = forward ? 220 : 200;
|
||||
const uint8_t checkDistance = forward ? 200 : 180;
|
||||
return AxisDistance(mm::axisUnitToTruncatedUnit<config::U_deg>(mm::motion.CurPosition<mm::Idler>())) > checkDistance;
|
||||
}
|
||||
|
||||
Idler::OperationResult Idler::Disengage() {
|
||||
if (state == Moving || IsOnHold()) {
|
||||
dbg_logic_P(PSTR("Moving --> Disengage refused"));
|
||||
return OperationResult::Refused;
|
||||
}
|
||||
plannedSlot = IdleSlotIndex();
|
||||
plannedMove = Operation::disengage;
|
||||
|
||||
// coordinates invalid, first home, then disengage
|
||||
if (!homingValid) {
|
||||
PlanHome();
|
||||
return OperationResult::Accepted;
|
||||
}
|
||||
|
||||
// already disengaged
|
||||
if (Disengaged()) {
|
||||
dbg_logic_P(PSTR("Idler Disengaged"));
|
||||
return OperationResult::Accepted;
|
||||
}
|
||||
|
||||
// disengaging
|
||||
return InitMovementNoReinitAxis();
|
||||
return PlanMoveInner(IdleSlotIndex(), Operation::disengage);
|
||||
}
|
||||
|
||||
Idler::OperationResult Idler::PartiallyDisengage(uint8_t slot) {
|
||||
|
|
@ -114,7 +94,7 @@ Idler::OperationResult Idler::PlanMoveInner(uint8_t slot, Operation plannedOp) {
|
|||
return OperationResult::Accepted;
|
||||
}
|
||||
|
||||
// coordinates invalid, first home, then engage
|
||||
// coordinates invalid, first home, then engage or disengage
|
||||
// The MMU FW only decides to engage the Idler when it is supposed to do something and not while it is idle
|
||||
// so rebooting the MMU while the printer is printing (and thus holding the filament by the moving Idler)
|
||||
// should not be an issue
|
||||
|
|
@ -123,8 +103,8 @@ Idler::OperationResult Idler::PlanMoveInner(uint8_t slot, Operation plannedOp) {
|
|||
return OperationResult::Accepted;
|
||||
}
|
||||
|
||||
// already engaged
|
||||
if (currentlyEngaged == plannedMove) {
|
||||
// already engaged or disengaged
|
||||
if (currentlyEngaged == plannedMove && currentSlot == plannedSlot) {
|
||||
return OperationResult::Accepted;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
#include "leds.h"
|
||||
#include "../hal/shr16.h"
|
||||
#include "timebase.h"
|
||||
#include "globals.h"
|
||||
|
||||
namespace modules {
|
||||
namespace leds {
|
||||
|
|
@ -68,5 +69,21 @@ void LEDs::SetAllOff() {
|
|||
}
|
||||
}
|
||||
|
||||
void LEDs::ActiveSlotProcessing() {
|
||||
SetPairButOffOthers(mg::globals.ActiveSlot(), ml::blink0, ml::off);
|
||||
}
|
||||
|
||||
void LEDs::ActiveSlotError() {
|
||||
SetPairButOffOthers(mg::globals.ActiveSlot(), ml::off, ml::blink0);
|
||||
}
|
||||
|
||||
void LEDs::ActiveSlotDoneEmpty() {
|
||||
SetPairButOffOthers(mg::globals.ActiveSlot(), ml::off, ml::off);
|
||||
}
|
||||
|
||||
void LEDs::ActiveSlotDonePrimed() {
|
||||
SetPairButOffOthers(mg::globals.ActiveSlot(), ml::on, ml::off);
|
||||
}
|
||||
|
||||
} // namespace leds
|
||||
} // namespace modules
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ namespace leds {
|
|||
|
||||
/// Enum of LED modes
|
||||
/// blink0 and blink1 allow for interlaced blinking of LEDs (one is on and the other off)
|
||||
enum Mode {
|
||||
enum Mode : uint8_t {
|
||||
off,
|
||||
on,
|
||||
blink0, ///< start blinking at even periods
|
||||
|
|
@ -28,7 +28,7 @@ enum Mode {
|
|||
};
|
||||
|
||||
/// Enum of LEDs color - green or red
|
||||
enum Color {
|
||||
enum Color : uint8_t {
|
||||
red = 0,
|
||||
green = 1
|
||||
};
|
||||
|
|
@ -122,6 +122,14 @@ public:
|
|||
/// Turn off all LEDs
|
||||
void SetAllOff();
|
||||
|
||||
/// Convenience functions - provide uniform implementation of LED behaviour through all the logic commands.
|
||||
/// Intentionally not inlined to save quite some space (140B)
|
||||
/// It's not a clean solution, LEDs should not know about mg::globals.ActiveSlot(), but the savings are important
|
||||
void ActiveSlotProcessing();
|
||||
void ActiveSlotError();
|
||||
void ActiveSlotDoneEmpty();
|
||||
void ActiveSlotDonePrimed();
|
||||
|
||||
private:
|
||||
constexpr static const uint8_t ledPairs = config::toolCount;
|
||||
/// pairs of LEDs:
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ namespace motion {
|
|||
class MovableBase {
|
||||
public:
|
||||
/// Internal states of the state machine
|
||||
enum {
|
||||
enum : uint8_t {
|
||||
Ready = 0, // intentionally set as zero in order to allow zeroing the Idler structure upon startup -> avoid explicit initialization code
|
||||
Moving = 1,
|
||||
PlannedHome = 2,
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ public:
|
|||
static bool set(uint8_t filament);
|
||||
|
||||
private:
|
||||
enum Key {
|
||||
enum Key : uint8_t {
|
||||
KeyFront1,
|
||||
KeyReverse1,
|
||||
KeyFront2,
|
||||
|
|
|
|||
|
|
@ -11,11 +11,6 @@ namespace pulley {
|
|||
|
||||
Pulley pulley;
|
||||
|
||||
bool __attribute__((noinline)) Pulley::FinishHomingAndPlanMoveToParkPos() {
|
||||
mm::motion.SetPosition(mm::Pulley, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Pulley::Step() {
|
||||
if (IsOnHold()) {
|
||||
return true; // just wait, do nothing!
|
||||
|
|
@ -28,10 +23,6 @@ bool Pulley::Step() {
|
|||
case Moving:
|
||||
PerformMove();
|
||||
return false;
|
||||
case HomeBack:
|
||||
homingValid = true;
|
||||
FinishHomingAndPlanMoveToParkPos();
|
||||
return true;
|
||||
case Ready:
|
||||
return true;
|
||||
case TMCFailed:
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ protected:
|
|||
virtual void PrepareMoveToPlannedSlot() override {}
|
||||
virtual void PlanHomingMoveForward() override {}
|
||||
virtual void PlanHomingMoveBack() override {}
|
||||
virtual bool FinishHomingAndPlanMoveToParkPos() override;
|
||||
virtual bool FinishHomingAndPlanMoveToParkPos() override { return true; }
|
||||
virtual void FinishMove() override {}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ void Selector::PlanHomingMoveBack() {
|
|||
|
||||
bool Selector::FinishHomingAndPlanMoveToParkPos() {
|
||||
// check the axis' length
|
||||
if (AxisDistance(mm::axisUnitToTruncatedUnit<config::U_mm>(mm::motion.CurPosition<mm::Selector>())) < (config::selectorLimits.lenght.v - 3)) { //@@TODO is 3mm ok?
|
||||
if (AxisDistance(mm::axisUnitToTruncatedUnit<config::U_mm>(mm::motion.CurPosition<mm::Selector>())) < uint16_t(config::selectorLimits.lenght.v - 3)) { //@@TODO is 3mm ok?
|
||||
return false; // we couldn't home correctly, we cannot set the Selector's position
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,13 +25,13 @@ USB_ClassInfo_CDC_Device_t VirtualSerial_CDC_Interface = {
|
|||
.Address = CDC_TX_EPADDR,
|
||||
.Size = CDC_TXRX_EPSIZE,
|
||||
.Type = EP_TYPE_BULK,
|
||||
.Banks = 2,
|
||||
.Banks = 1,
|
||||
},
|
||||
.DataOUTEndpoint = {
|
||||
.Address = CDC_RX_EPADDR,
|
||||
.Size = CDC_TXRX_EPSIZE,
|
||||
.Type = EP_TYPE_BULK,
|
||||
.Banks = 2,
|
||||
.Banks = 1,
|
||||
},
|
||||
.NotificationEndpoint = {
|
||||
.Address = CDC_NOTIFICATION_EPADDR,
|
||||
|
|
@ -90,6 +90,14 @@ namespace usb {
|
|||
CDC cdc;
|
||||
|
||||
void CDC::Init() {
|
||||
#if defined(USE_STATIC_OPTIONS) && (USE_STATIC_OPTIONS & USB_OPT_MANUAL_PLL)
|
||||
#if defined(USB_SERIES_4_AVR)
|
||||
PLLFRQ = ((1 << PLLUSB) | (1 << PDIV3) | (1 << PDIV1));
|
||||
#endif
|
||||
USB_PLL_On();
|
||||
while (!(USB_PLL_IsReady()));
|
||||
#endif
|
||||
|
||||
USB_Init();
|
||||
|
||||
/* Create a regular character stream for the interface so that it can be used with the stdio.h functions */
|
||||
|
|
|
|||
38
src/pins.h
38
src/pins.h
|
|
@ -3,28 +3,28 @@
|
|||
#include "hal/gpio.h"
|
||||
|
||||
/// pin definitions
|
||||
static constexpr hal::gpio::GPIO_pin TMC2130_SPI_MISO_PIN = { GPIOB, 3 };
|
||||
static constexpr hal::gpio::GPIO_pin TMC2130_SPI_MOSI_PIN = { GPIOB, 2 };
|
||||
static constexpr hal::gpio::GPIO_pin TMC2130_SPI_SCK_PIN = { GPIOB, 1 };
|
||||
static constexpr hal::gpio::GPIO_pin TMC2130_SPI_SS_PIN = { GPIOB, 0 };
|
||||
static constexpr hal::gpio::GPIO_pin TMC2130_SPI_MISO_PIN = { GPIOB, (1 << 3) };
|
||||
static constexpr hal::gpio::GPIO_pin TMC2130_SPI_MOSI_PIN = { GPIOB, (1 << 2) };
|
||||
static constexpr hal::gpio::GPIO_pin TMC2130_SPI_SCK_PIN = { GPIOB, (1 << 1) };
|
||||
static constexpr hal::gpio::GPIO_pin TMC2130_SPI_SS_PIN = { GPIOB, (1 << 0) };
|
||||
|
||||
static constexpr hal::gpio::GPIO_pin SHR16_DATA = { GPIOB, 5 }; ///DS
|
||||
static constexpr hal::gpio::GPIO_pin SHR16_LATCH = { GPIOB, 6 }; ///STCP
|
||||
static constexpr hal::gpio::GPIO_pin SHR16_CLOCK = { GPIOC, 7 }; ///SHCP
|
||||
static constexpr hal::gpio::GPIO_pin SHR16_DATA = { GPIOB, (1 << 5) }; ///DS
|
||||
static constexpr hal::gpio::GPIO_pin SHR16_LATCH = { GPIOB, (1 << 6) }; ///STCP
|
||||
static constexpr hal::gpio::GPIO_pin SHR16_CLOCK = { GPIOC, (1 << 7) }; ///SHCP
|
||||
|
||||
static constexpr hal::gpio::GPIO_pin USART_RX = { GPIOD, 2 };
|
||||
static constexpr hal::gpio::GPIO_pin USART_TX = { GPIOD, 3 };
|
||||
static constexpr hal::gpio::GPIO_pin USART_RX = { GPIOD, (1 << 2) };
|
||||
static constexpr hal::gpio::GPIO_pin USART_TX = { GPIOD, (1 << 3) };
|
||||
|
||||
static constexpr hal::gpio::GPIO_pin PULLEY_CS_PIN = { GPIOC, 6 };
|
||||
static constexpr hal::gpio::GPIO_pin PULLEY_SG_PIN = { GPIOF, 4 };
|
||||
static constexpr hal::gpio::GPIO_pin PULLEY_STEP_PIN = { GPIOB, 4 };
|
||||
static constexpr hal::gpio::GPIO_pin PULLEY_CS_PIN = { GPIOC, (1 << 6) };
|
||||
static constexpr hal::gpio::GPIO_pin PULLEY_SG_PIN = { GPIOF, (1 << 4) };
|
||||
static constexpr hal::gpio::GPIO_pin PULLEY_STEP_PIN = { GPIOB, (1 << 4) };
|
||||
|
||||
static constexpr hal::gpio::GPIO_pin SELECTOR_CS_PIN = { GPIOD, 7 };
|
||||
static constexpr hal::gpio::GPIO_pin SELECTOR_SG_PIN = { GPIOF, 1 };
|
||||
static constexpr hal::gpio::GPIO_pin SELECTOR_STEP_PIN = { GPIOD, 4 };
|
||||
static constexpr hal::gpio::GPIO_pin SELECTOR_CS_PIN = { GPIOD, (1 << 7) };
|
||||
static constexpr hal::gpio::GPIO_pin SELECTOR_SG_PIN = { GPIOF, (1 << 1) };
|
||||
static constexpr hal::gpio::GPIO_pin SELECTOR_STEP_PIN = { GPIOD, (1 << 4) };
|
||||
|
||||
static constexpr hal::gpio::GPIO_pin IDLER_CS_PIN = { GPIOB, 7 };
|
||||
static constexpr hal::gpio::GPIO_pin IDLER_SG_PIN = { GPIOF, 0 };
|
||||
static constexpr hal::gpio::GPIO_pin IDLER_STEP_PIN = { GPIOD, 6 };
|
||||
static constexpr hal::gpio::GPIO_pin IDLER_CS_PIN = { GPIOB, (1 << 7) };
|
||||
static constexpr hal::gpio::GPIO_pin IDLER_SG_PIN = { GPIOF, (1 << 0) };
|
||||
static constexpr hal::gpio::GPIO_pin IDLER_STEP_PIN = { GPIOD, (1 << 6) };
|
||||
|
||||
static constexpr hal::gpio::GPIO_pin FINDA_PIN = { GPIOF, 6 }; /// PF6 A1 ADC6/TDI
|
||||
static constexpr hal::gpio::GPIO_pin FINDA_PIN = { GPIOF, (1 << 6) }; /// PF6 A1 ADC6/TDI
|
||||
|
|
|
|||
|
|
@ -145,9 +145,9 @@
|
|||
| 0x0bh 11 | uint8 | extra_load_distance | 00h 0 | 1eh 30 | unit mm | Read / Write | M707 A0x0b | M708 A0x0b Xnn
|
||||
| 0x0ch 12 | uint8 | FSensor_unload_check_dist. | 00h 0 | 28h 30 | unit mm | Read / Write | M707 A0x0c | M708 A0x0c Xnn
|
||||
| 0x0dh 13 | uint16 | Pulley_unload_feedrate | 0000h 0 | 005fh 95 | unit mm/s | Read / Write | M707 A0x0d | M708 A0x0d Xnnnn
|
||||
| 0x0eh 14 | uint16 | Pulley_acceleration | 0000h 0 | 320h 800.0 | unit mm/s² | Read (Write) | M707 A0x0e | (M708 A0x0e Xnnnn)
|
||||
| 0x0fh 15 | uint16 | Selector_acceleration | 0000h 0 | 00c8h 200.0 | unit mm/s² | Read (Write) | M707 A0x0f | (M708 A0x0f Xnnnn)
|
||||
| 0x10h 16 | uint16 | Idler_acceleration | 0000h 0 | 01f4h 500.0 | unit deg/s² | Read (Write) | M707 A0x10 | (M708 A0x10 Xnnnn)
|
||||
| 0x0eh 14 | uint16 | Pulley_acceleration | 0000h 0 | 320h 800.0 | unit mm/s² | Read / Write | M707 A0x0e | M708 A0x0e Xnnnn
|
||||
| 0x0fh 15 | uint16 | Selector_acceleration | 0000h 0 | 00c8h 200.0 | unit mm/s² | Read / Write | M707 A0x0f | M708 A0x0f Xnnnn
|
||||
| 0x10h 16 | uint16 | Idler_acceleration | 0000h 0 | 01f4h 500.0 | unit deg/s² | Read / Write | M707 A0x10 | M708 A0x10 Xnnnn
|
||||
| 0x11h 17 | uint16 | Pulley_load_feedrate | 0000h 0 | 005fh 95 | unit mm/s | Read / Write | M707 A0x11 | M708 A0x11 Xnnnn
|
||||
| 0x12h 18 | uint16 | Selector_nominal_feedrate | 0000h 0 | 002dh 45 | unit mm/s | Read / Write | M707 A0x12 | M708 A0x12 Xnnnn
|
||||
| 0x13h 19 | uint16 | Idler_nominal_feedrate | 0000h 0 | 012ch 300 | unit deg/s | Read / Write | M707 A0x13 | M708 A0x13 Xnnnn
|
||||
|
|
@ -166,46 +166,9 @@
|
|||
| 0x20h 32 | uint16 | Set/Get Idler iRun current | 0-31 | 1fh 31 | 31->530mA: see TMC2130 current conversion| Read / Write | M707 A0x20 | M708 A0x20 Xn
|
||||
| 0x21h 33 | uint16 | Reserved for internal use | 225 | | N/A | N/A | N/A | N/A
|
||||
| 0x22h 34 | uint16 | Bowden length | 341-1000 | 168h 360 | unit mm | Read / Write Persistent | M707 A0x22 | M708 A0x22 Xn
|
||||
| 0x23h 35 | uint8 | Cut length | 0-255 | 8 | unit mm | Read / Write | M707 A0x23 | M708 A0x23 Xn
|
||||
*/
|
||||
|
||||
struct __attribute__((packed)) RegisterFlags {
|
||||
struct __attribute__((packed)) A {
|
||||
uint8_t size : 2; // 0: 1 bit, 1: 1 byte, 2: 2 bytes - keeping size as the lowest 2 bits avoids costly shifts when accessing them
|
||||
uint8_t writable : 1;
|
||||
uint8_t rwfuncs : 1; // 1: register needs special read and write functions
|
||||
constexpr A(uint8_t size, bool writable)
|
||||
: size(size)
|
||||
, writable(writable)
|
||||
, rwfuncs(0) {}
|
||||
constexpr A(uint8_t size, bool writable, bool rwfuncs)
|
||||
: size(size)
|
||||
, writable(writable)
|
||||
, rwfuncs(rwfuncs) {}
|
||||
};
|
||||
union __attribute__((packed)) U {
|
||||
A bits;
|
||||
uint8_t b;
|
||||
constexpr U(uint8_t size, bool writable)
|
||||
: bits(size, writable) {}
|
||||
constexpr U(uint8_t size, bool writable, bool rwfuncs)
|
||||
: bits(size, writable, rwfuncs) {}
|
||||
constexpr U(uint8_t b)
|
||||
: b(b) {}
|
||||
} u;
|
||||
constexpr RegisterFlags(uint8_t size, bool writable)
|
||||
: u(size, writable) {}
|
||||
constexpr RegisterFlags(uint8_t size, bool writable, bool rwfuncs)
|
||||
: u(size, writable, rwfuncs) {}
|
||||
explicit constexpr RegisterFlags(uint8_t b)
|
||||
: u(b) {}
|
||||
|
||||
constexpr bool Writable() const { return u.bits.writable; }
|
||||
constexpr bool RWFuncs() const { return u.bits.rwfuncs; }
|
||||
constexpr uint8_t Size() const { return u.bits.size; }
|
||||
};
|
||||
|
||||
static_assert(sizeof(RegisterFlags) == 1);
|
||||
|
||||
using TReadFunc = uint16_t (*)();
|
||||
using TWriteFunc = void (*)(uint16_t);
|
||||
|
||||
|
|
@ -213,7 +176,6 @@ using TWriteFunc = void (*)(uint16_t);
|
|||
static constexpr uint16_t dummyZero = 0;
|
||||
|
||||
struct __attribute__((packed)) RegisterRec {
|
||||
RegisterFlags flags;
|
||||
union __attribute__((packed)) U1 {
|
||||
void *addr;
|
||||
TReadFunc readFunc;
|
||||
|
|
@ -232,29 +194,22 @@ struct __attribute__((packed)) RegisterRec {
|
|||
: addr(a) {}
|
||||
} A2;
|
||||
|
||||
template <typename T>
|
||||
constexpr RegisterRec(bool writable, T *address)
|
||||
: flags(RegisterFlags(sizeof(T), writable))
|
||||
, A1((void *)address)
|
||||
, A2((void *)nullptr) {}
|
||||
constexpr RegisterRec(const TReadFunc &readFunc, uint8_t bytes)
|
||||
: flags(RegisterFlags(bytes, false, true))
|
||||
, A1(readFunc)
|
||||
constexpr RegisterRec(const TReadFunc &readFunc)
|
||||
: A1(readFunc)
|
||||
, A2((void *)nullptr) {}
|
||||
|
||||
constexpr RegisterRec(const TReadFunc &readFunc, const TWriteFunc &writeFunc, uint8_t bytes)
|
||||
: flags(RegisterFlags(bytes, true, true))
|
||||
, A1(readFunc)
|
||||
constexpr RegisterRec(const TReadFunc &readFunc, const TWriteFunc &writeFunc)
|
||||
: A1(readFunc)
|
||||
, A2(writeFunc) {}
|
||||
|
||||
constexpr RegisterRec()
|
||||
: flags(RegisterFlags(1, false, false))
|
||||
, A1((void *)&dummyZero)
|
||||
: A1((void *)&dummyZero)
|
||||
, A2((void *)nullptr) {}
|
||||
};
|
||||
|
||||
// Make sure the structure is tightly packed - necessary for unit tests.
|
||||
static_assert(sizeof(RegisterRec) == sizeof(uint8_t) + sizeof(void *) + sizeof(void *));
|
||||
static_assert(sizeof(RegisterRec) == /*sizeof(uint8_t) +*/ sizeof(void *) + sizeof(void *));
|
||||
|
||||
// Beware: the size is expected to be 17B on an x86_64 and it requires the platform to be able to do unaligned reads.
|
||||
// That might be a problem when running unit tests on non-x86 platforms.
|
||||
// So far, no countermeasures have been taken to tackle this potential issue.
|
||||
|
|
@ -273,174 +228,161 @@ static_assert(sizeof(RegisterRec) == sizeof(uint8_t) + sizeof(void *) + sizeof(v
|
|||
// sts <modules::globals::globals+0x4>, r24
|
||||
// ret
|
||||
//
|
||||
// @@TODO at the moment we are having problems compiling this array statically into PROGMEM.
|
||||
// In this project that's really not an issue since we have half of the RAM empty:
|
||||
// Data: 1531 bytes (59.8% Full)
|
||||
// But it would be nice to fix that in the future - might be hard to push the compiler to such a construct
|
||||
// @@TODO
|
||||
// The "ret" instruction actually is a serious overhead since it is present in every lambda function.
|
||||
// The only way around is a giant C-style switch which screws up the beauty of this array of registers.
|
||||
// But it will save a few bytes.
|
||||
static const RegisterRec registers[] PROGMEM = {
|
||||
// 0x00
|
||||
RegisterRec(false, &project_major),
|
||||
RegisterRec([]() -> uint16_t { return project_major; }),
|
||||
// 0x01
|
||||
RegisterRec(false, &project_minor),
|
||||
RegisterRec([]() -> uint16_t { return project_minor; }),
|
||||
// 0x02
|
||||
RegisterRec(false, &project_revision),
|
||||
RegisterRec([]() -> uint16_t { return project_revision; }),
|
||||
// 0x03
|
||||
RegisterRec(false, &project_build_number),
|
||||
RegisterRec([]() -> uint16_t { return project_build_number; }),
|
||||
// 0x04
|
||||
RegisterRec( // MMU errors
|
||||
[]() -> uint16_t { return mg::globals.DriveErrors(); }, // compiles to: <{lambda()#1}::_FUN()>: jmp <modules::permanent_storage::DriveError::get()>
|
||||
[]() -> uint16_t { return mg::globals.DriveErrors(); } // compiles to: <{lambda()#1}::_FUN()>: jmp <modules::permanent_storage::DriveError::get()>
|
||||
// [](uint16_t) {}, // @@TODO think about setting/clearing the error counter from the outside
|
||||
2),
|
||||
),
|
||||
// 0x05
|
||||
RegisterRec([]() -> uint16_t { return application.CurrentProgressCode(); }, 1),
|
||||
RegisterRec([]() -> uint16_t { return application.CurrentProgressCode(); }),
|
||||
// 0x06
|
||||
RegisterRec([]() -> uint16_t { return application.CurrentErrorCode(); }, 2),
|
||||
RegisterRec([]() -> uint16_t { return application.CurrentErrorCode(); }),
|
||||
// 0x07 filamentState
|
||||
RegisterRec(
|
||||
[]() -> uint16_t { return mg::globals.FilamentLoaded(); },
|
||||
[](uint16_t v) { return mg::globals.SetFilamentLoaded(mg::globals.ActiveSlot(), static_cast<mg::FilamentLoadState>(v)); },
|
||||
1),
|
||||
[](uint16_t v) { return mg::globals.SetFilamentLoaded(mg::globals.ActiveSlot(), static_cast<mg::FilamentLoadState>(v)); }),
|
||||
// 0x08 FINDA
|
||||
RegisterRec(
|
||||
[]() -> uint16_t { return static_cast<uint16_t>(mf::finda.Pressed()); },
|
||||
1),
|
||||
[]() -> uint16_t { return static_cast<uint16_t>(mf::finda.Pressed()); }),
|
||||
// 09 fsensor
|
||||
RegisterRec(
|
||||
[]() -> uint16_t { return static_cast<uint16_t>(mfs::fsensor.Pressed()); },
|
||||
[](uint16_t v) { return mfs::fsensor.ProcessMessage(v != 0); },
|
||||
1),
|
||||
[](uint16_t v) { return mfs::fsensor.ProcessMessage(v != 0); }),
|
||||
// 0xa motor mode (stealth = 1/normal = 0)
|
||||
RegisterRec([]() -> uint16_t { return static_cast<uint16_t>(mg::globals.MotorsStealth()); }, 1),
|
||||
RegisterRec([]() -> uint16_t { return static_cast<uint16_t>(mg::globals.MotorsStealth()); }),
|
||||
// 0xb extra load distance after fsensor triggered (30mm default) [mm] RW
|
||||
RegisterRec(
|
||||
[]() -> uint16_t { return mg::globals.FSensorToNozzle_mm().v; },
|
||||
[](uint16_t d) { mg::globals.SetFSensorToNozzle_mm(d); },
|
||||
1),
|
||||
[](uint16_t d) { mg::globals.SetFSensorToNozzle_mm(d); }),
|
||||
// 0x0c fsensor unload check distance (40mm default) [mm] RW
|
||||
RegisterRec(
|
||||
[]() -> uint16_t { return mg::globals.FSensorUnloadCheck_mm().v; },
|
||||
[](uint16_t d) { mg::globals.SetFSensorUnloadCheck_mm(d); },
|
||||
1),
|
||||
[](uint16_t d) { mg::globals.SetFSensorUnloadCheck_mm(d); }),
|
||||
|
||||
// 0xd 2 Pulley unload feedrate [mm/s] RW
|
||||
RegisterRec(
|
||||
[]() -> uint16_t { return mg::globals.PulleyUnloadFeedrate_mm_s().v; },
|
||||
[](uint16_t d) { mg::globals.SetPulleyUnloadFeedrate_mm_s(d); },
|
||||
2),
|
||||
[](uint16_t d) { mg::globals.SetPulleyUnloadFeedrate_mm_s(d); }),
|
||||
|
||||
// 0xe Pulley acceleration [mm/s2] RW
|
||||
RegisterRec(
|
||||
[]() -> uint16_t { return config::pulleyLimits.accel.v; },
|
||||
//@@TODO please update documentation as well
|
||||
2),
|
||||
[]() -> uint16_t {
|
||||
mm::steps_t val = mm::motion.Acceleration(config::Pulley);
|
||||
return mm::axisUnitToTruncatedUnit<config::U_mm_s2>(mm::P_accel_t({ val }));
|
||||
},
|
||||
[](uint16_t d) { mm::motion.SetAcceleration(config::Pulley, mm::unitToSteps<mm::P_accel_t>(config::U_mm_s2({ (long double)d }))); }),
|
||||
// 0xf Selector acceleration [mm/s2] RW
|
||||
RegisterRec(
|
||||
[]() -> uint16_t { return config::selectorLimits.accel.v; },
|
||||
//@@TODO please update documentation as well
|
||||
2),
|
||||
[]() -> uint16_t {
|
||||
mm::steps_t val = mm::motion.Acceleration(config::Selector);
|
||||
return mm::axisUnitToTruncatedUnit<config::U_mm_s2>(mm::S_accel_t({ val }));
|
||||
},
|
||||
[](uint16_t d) { (mm::motion.SetAcceleration(config::Selector, mm::unitToSteps<mm::S_accel_t>(config::U_mm_s2({ (long double)d })))); }),
|
||||
// 0x10 Idler acceleration [deg/s2] RW
|
||||
RegisterRec(
|
||||
[]() -> uint16_t { return config::idlerLimits.accel.v; },
|
||||
//@@TODO please update documentation as well
|
||||
2),
|
||||
[]() -> uint16_t {
|
||||
mm::steps_t val = mm::motion.Acceleration(config::Idler);
|
||||
return mm::axisUnitToTruncatedUnit<config::U_deg_s2>(mm::I_accel_t({ val }));
|
||||
},
|
||||
[](uint16_t d) { mm::motion.SetAcceleration(config::Idler, mm::unitToSteps<mm::I_accel_t>(config::U_deg_s2({ (long double)d }))); }),
|
||||
|
||||
// 0x11 Pulley load feedrate [mm/s] RW
|
||||
RegisterRec(
|
||||
[]() -> uint16_t { return mg::globals.PulleyLoadFeedrate_mm_s().v; },
|
||||
[](uint16_t d) { mg::globals.SetPulleyLoadFeedrate_mm_s(d); },
|
||||
2),
|
||||
[](uint16_t d) { mg::globals.SetPulleyLoadFeedrate_mm_s(d); }),
|
||||
// 0x12 Selector nominal feedrate [mm/s] RW
|
||||
RegisterRec(
|
||||
[]() -> uint16_t { return mg::globals.SelectorFeedrate_mm_s().v; },
|
||||
[](uint16_t d) { mg::globals.SetSelectorFeedrate_mm_s(d); },
|
||||
2),
|
||||
[](uint16_t d) { mg::globals.SetSelectorFeedrate_mm_s(d); }),
|
||||
// 0x13 Idler nominal feedrate [deg/s] RW
|
||||
RegisterRec(
|
||||
[]() -> uint16_t { return mg::globals.IdlerFeedrate_deg_s().v; },
|
||||
[](uint16_t d) { mg::globals.SetIdlerFeedrate_deg_s(d); },
|
||||
2),
|
||||
[](uint16_t d) { mg::globals.SetIdlerFeedrate_deg_s(d); }),
|
||||
|
||||
// 0x14 Pulley slow load to fsensor feedrate [mm/s] RW
|
||||
RegisterRec(
|
||||
[]() -> uint16_t { return mg::globals.PulleySlowFeedrate_mm_s().v; },
|
||||
[](uint16_t d) { mg::globals.SetPulleySlowFeedrate_mm_s(d); },
|
||||
2),
|
||||
[](uint16_t d) { mg::globals.SetPulleySlowFeedrate_mm_s(d); }),
|
||||
// 0x15 Selector homing feedrate [mm/s] RW
|
||||
RegisterRec(
|
||||
[]() -> uint16_t { return mg::globals.SelectorHomingFeedrate_mm_s().v; },
|
||||
[](uint16_t d) { mg::globals.SetSelectorHomingFeedrate_mm_s(d); },
|
||||
2),
|
||||
[](uint16_t d) { mg::globals.SetSelectorHomingFeedrate_mm_s(d); }),
|
||||
// 0x16 Idler homing feedrate [deg/s] RW
|
||||
RegisterRec(
|
||||
[]() -> uint16_t { return mg::globals.IdlerHomingFeedrate_deg_s().v; },
|
||||
[](uint16_t d) { mg::globals.SetIdlerHomingFeedrate_deg_s(d); },
|
||||
2),
|
||||
[](uint16_t d) { mg::globals.SetIdlerHomingFeedrate_deg_s(d); }),
|
||||
|
||||
// 0x17 Pulley sg_thrs threshold RW
|
||||
RegisterRec(
|
||||
[]() -> uint16_t { return mg::globals.StallGuardThreshold(config::Pulley); },
|
||||
[](uint16_t d) { mg::globals.SetStallGuardThreshold(config::Pulley, d); },
|
||||
1),
|
||||
[](uint16_t d) { mg::globals.SetStallGuardThreshold(config::Pulley, d); }),
|
||||
// 0x18 Selector sg_thrs RW
|
||||
RegisterRec(
|
||||
[]() -> uint16_t { return mg::globals.StallGuardThreshold(mm::Axis::Selector); },
|
||||
[](uint16_t d) { mg::globals.SetStallGuardThreshold(mm::Axis::Selector, d); },
|
||||
1),
|
||||
[](uint16_t d) { mg::globals.SetStallGuardThreshold(mm::Axis::Selector, d); }),
|
||||
// 0x19 Idler sg_thrs RW
|
||||
RegisterRec(
|
||||
[]() -> uint16_t { return mg::globals.StallGuardThreshold(mm::Axis::Idler); },
|
||||
[](uint16_t d) { mg::globals.SetStallGuardThreshold(mm::Axis::Idler, d); },
|
||||
1),
|
||||
[](uint16_t d) { mg::globals.SetStallGuardThreshold(mm::Axis::Idler, d); }),
|
||||
|
||||
// 0x1a Get Pulley position [mm] R
|
||||
RegisterRec(
|
||||
[]() -> uint16_t { return mpu::pulley.CurrentPosition_mm(); },
|
||||
2),
|
||||
[]() -> uint16_t { return mpu::pulley.CurrentPosition_mm(); }),
|
||||
// 0x1b Set/Get Selector slot RW
|
||||
RegisterRec(
|
||||
[]() -> uint16_t { return ms::selector.Slot(); },
|
||||
[](uint16_t d) { ms::selector.MoveToSlot(d); },
|
||||
1),
|
||||
[](uint16_t d) { ms::selector.MoveToSlot(d); }),
|
||||
// 0x1c Set/Get Idler slot RW
|
||||
RegisterRec(
|
||||
[]() -> uint16_t { return mi::idler.Slot(); },
|
||||
[](uint16_t d) { d >= config::toolCount ? mi::idler.Disengage() : mi::idler.Engage(d); },
|
||||
1),
|
||||
[](uint16_t d) { d >= config::toolCount ? mi::idler.Disengage() : mi::idler.Engage(d); }),
|
||||
// 0x1d Set/Get Selector cut iRun current level RW
|
||||
RegisterRec(
|
||||
[]() -> uint16_t { return mg::globals.CutIRunCurrent(); },
|
||||
[](uint16_t d) { mg::globals.SetCutIRunCurrent(d); },
|
||||
1),
|
||||
[](uint16_t d) { mg::globals.SetCutIRunCurrent(d); }),
|
||||
|
||||
// 0x1e Get/Set Pulley iRun current RW
|
||||
RegisterRec(
|
||||
[]() -> uint16_t { return mm::motion.CurrentsForAxis(config::Pulley).iRun; },
|
||||
[](uint16_t d) { mm::motion.SetIRunForAxis(config::Pulley, d); },
|
||||
1),
|
||||
[](uint16_t d) { mm::motion.SetIRunForAxis(config::Pulley, d); }),
|
||||
// 0x1f Set/Get Selector iRun current RW
|
||||
RegisterRec(
|
||||
[]() -> uint16_t { return mm::motion.CurrentsForAxis(config::Selector).iRun; },
|
||||
[](uint16_t d) { mm::motion.SetIRunForAxis(config::Selector, d); },
|
||||
1),
|
||||
[](uint16_t d) { mm::motion.SetIRunForAxis(config::Selector, d); }),
|
||||
// 0x20 Set/Get Idler iRun current RW
|
||||
RegisterRec(
|
||||
[]() -> uint16_t { return mm::motion.CurrentsForAxis(config::Idler).iRun; },
|
||||
[](uint16_t d) { mm::motion.SetIRunForAxis(config::Idler, d); },
|
||||
1),
|
||||
[](uint16_t d) { mm::motion.SetIRunForAxis(config::Idler, d); }),
|
||||
// 0x21 Current VCC voltage level R
|
||||
RegisterRec(
|
||||
[]() -> uint16_t { return 225; /*mv::vcc.CurrentBandgapVoltage();*/ },
|
||||
2),
|
||||
[]() -> uint16_t { return 225; /*mv::vcc.CurrentBandgapVoltage();*/ }),
|
||||
|
||||
// 0x22 Detected bowden length R
|
||||
RegisterRec(
|
||||
[]() -> uint16_t { return mps::BowdenLength::Get(); },
|
||||
[](uint16_t d) { mps::BowdenLength::Set(d); },
|
||||
2),
|
||||
[](uint16_t d) { mps::BowdenLength::Set(d); }),
|
||||
|
||||
// 0x23 Cut length
|
||||
RegisterRec(
|
||||
[]() -> uint16_t { return mg::globals.CutLength().v; },
|
||||
[](uint16_t d) { mg::globals.SetCutLength(d); }),
|
||||
};
|
||||
|
||||
static constexpr uint8_t registersSize = sizeof(registers) / sizeof(RegisterRec);
|
||||
static_assert(registersSize == 35);
|
||||
static_assert(registersSize == 36);
|
||||
|
||||
bool ReadRegister(uint8_t address, uint16_t &value) {
|
||||
if (address >= registersSize) {
|
||||
|
|
@ -450,59 +392,27 @@ bool ReadRegister(uint8_t address, uint16_t &value) {
|
|||
|
||||
// Get pointer to register at address
|
||||
const uint8_t *addr = reinterpret_cast<const uint8_t *>(registers + address);
|
||||
const RegisterFlags rf(hal::progmem::read_byte(addr));
|
||||
|
||||
// beware - abusing the knowledge of RegisterRec memory layout to do lpm_reads
|
||||
const void *varAddr = addr + sizeof(RegisterFlags);
|
||||
if (!rf.RWFuncs()) {
|
||||
switch (rf.Size()) {
|
||||
case 0:
|
||||
case 1:
|
||||
value = *hal::progmem::read_ptr<const uint8_t *>(varAddr);
|
||||
break;
|
||||
case 2:
|
||||
value = *hal::progmem::read_ptr<const uint16_t *>(varAddr);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
auto readFunc = hal::progmem::read_ptr<const TReadFunc>(varAddr);
|
||||
value = readFunc();
|
||||
return true;
|
||||
}
|
||||
const void *varAddr = addr;
|
||||
auto readFunc = hal::progmem::read_ptr<const TReadFunc>(varAddr);
|
||||
value = readFunc();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WriteRegister(uint8_t address, uint16_t value) {
|
||||
if (address >= registersSize) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint8_t *addr = reinterpret_cast<const uint8_t *>(registers + address);
|
||||
const RegisterFlags rf(hal::progmem::read_byte(addr));
|
||||
|
||||
if (!rf.Writable()) {
|
||||
// beware - abusing the knowledge of RegisterRec memory layout to do lpm_reads
|
||||
// addr offset should be 2 on AVR, but 8 on x86_64, therefore "sizeof(void*)"
|
||||
const void *varAddr = addr + sizeof(RegisterRec::A1);
|
||||
auto writeFunc = hal::progmem::read_ptr<const TWriteFunc>(varAddr);
|
||||
if (writeFunc == nullptr) {
|
||||
return false;
|
||||
}
|
||||
// beware - abusing the knowledge of RegisterRec memory layout to do lpm_reads
|
||||
// addr offset should be 3 on AVR, but 9 on x86_64, therefore "1 + sizeof(void*)"
|
||||
const void *varAddr = addr + sizeof(RegisterFlags) + sizeof(RegisterRec::A1);
|
||||
if (!rf.RWFuncs()) {
|
||||
switch (rf.Size()) {
|
||||
case 0:
|
||||
case 1:
|
||||
*hal::progmem::read_ptr<uint8_t *>(varAddr) = value;
|
||||
break;
|
||||
case 2:
|
||||
*hal::progmem::read_ptr<uint16_t *>(varAddr) = value;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
auto writeFunc = hal::progmem::read_ptr<const TWriteFunc>(varAddr);
|
||||
writeFunc(value);
|
||||
return true;
|
||||
}
|
||||
writeFunc(value);
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
#include <version.hpp>
|
||||
|
||||
|
||||
// Define structure at a fixed address in flash which contains information about the version of the firmware
|
||||
// This structure is read using the bootloader.
|
||||
struct Signatures {
|
||||
uint8_t project_major;
|
||||
uint8_t project_minor;
|
||||
uint16_t project_revision;
|
||||
uint16_t project_build_number;
|
||||
} static constexpr signatures __attribute__((section(".version"), used)) = {
|
||||
PROJECT_VERSION_MAJOR,
|
||||
PROJECT_VERSION_MINOR,
|
||||
PROJECT_VERSION_REV,
|
||||
PROJECT_BUILD_NUMBER,
|
||||
};
|
||||
|
|
@ -15,7 +15,7 @@ if(GCOV_ENABLE)
|
|||
COMMAND ${CMAKE_COMMAND} -E remove ${PROJECT_BINARY_DIR}/.ctest-finished
|
||||
)
|
||||
|
||||
set(ctest_test_args --timeout 30 --output-on-failure)
|
||||
set(ctest_test_args --timeout 180 --output-on-failure)
|
||||
|
||||
include(ProcessorCount)
|
||||
ProcessorCount(N)
|
||||
|
|
@ -55,7 +55,8 @@ if(GCOV_ENABLE)
|
|||
COMMAND tar -zcvf Coverage.tar.gz Coverage
|
||||
# Cheat and compare a file to itself to check for existence. File-Not-Found is a failure
|
||||
# code.
|
||||
COMMAND ../../utils/gcovr.py -r . -e '../../tests' -e '../../lib/Catch2' | tee Summary.txt
|
||||
COMMAND ${PROJECT_SOURCE_DIR}/utils/gcovr.py -r ${CMAKE_SOURCE_DIR} -e 'tests' -e 'lib/Catch2' |
|
||||
tee ${CMAKE_BINARY_DIR}/Summary.txt
|
||||
COMMAND ${CMAKE_COMMAND} -E compare_files ${PROJECT_BINARY_DIR}/.ctest-finished
|
||||
${PROJECT_BINARY_DIR}/.ctest-finished
|
||||
BYPRODUCTS ${PROJECT_BINARY_DIR}/Summary.txt ${PROJECT_BINARY_DIR}/Coverage.tar.gz
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ void FailingMovableUnload(hal::tmc2130::ErrorFlags ef, ErrorCode ec, config::Axi
|
|||
// UnloadFilament starts by engaging the idler (through the UnloadToFinda state machine)
|
||||
uf.Reset(0);
|
||||
|
||||
REQUIRE(VerifyState(uf, mg::FilamentLoadState::InNozzle, mi::Idler::IdleSlotIndex(), 0, true, true, ml::off, ml::off, ErrorCode::RUNNING, ProgressCode::UnloadingToFinda));
|
||||
REQUIRE(VerifyState(uf, mg::FilamentLoadState::InNozzle, mi::Idler::IdleSlotIndex(), 0, true, true, ml::blink0, ml::off, ErrorCode::RUNNING, ProgressCode::UnloadingToFinda));
|
||||
|
||||
REQUIRE(WhileCondition(
|
||||
uf,
|
||||
|
|
|
|||
|
|
@ -84,6 +84,13 @@ void LoadFilamentSuccessful(uint8_t slot, logic::LoadFilament &lf) {
|
|||
void LoadFilamentSuccessfulWithRehomeSelector(uint8_t slot, logic::LoadFilament &lf) {
|
||||
// Stage 2 - feeding to finda
|
||||
// make FINDA switch on
|
||||
// engaging idler
|
||||
|
||||
REQUIRE(WhileCondition(
|
||||
lf,
|
||||
[&](uint32_t) { return !mi::idler.Engaged(); },
|
||||
5000));
|
||||
|
||||
REQUIRE(WhileCondition(lf, std::bind(SimulateFeedToFINDA, _1, 100), 5000));
|
||||
REQUIRE(VerifyState(lf, mg::FilamentLoadState::InSelector, slot, slot, true, true, ml::blink0, ml::off, ErrorCode::RUNNING, ProgressCode::RetractingFromFinda));
|
||||
|
||||
|
|
@ -176,8 +183,6 @@ void FailedLoadToFindaResolveTryAgain(uint8_t slot, logic::LoadFilament &lf) {
|
|||
REQUIRE(VerifyState(lf, mg::FilamentLoadState::InSelector, config::toolCount, slot, false, false, ml::blink0, ml::off, ErrorCode::RUNNING, ProgressCode::FeedingToFinda));
|
||||
ClearButtons(lf);
|
||||
|
||||
SimulateIdlerHoming(lf);
|
||||
|
||||
LoadFilamentSuccessfulWithRehomeSelector(slot, lf);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@
|
|||
|
||||
#include "../stubs/stub_motion.h"
|
||||
|
||||
#include "catch2/catch_test_macros.hpp"
|
||||
|
||||
void SimulateIdlerAndSelectorHoming(logic::CommandBase &cb) {
|
||||
#if 0
|
||||
// do 5 steps until we trigger the simulated StallGuard
|
||||
|
|
@ -58,6 +60,18 @@ void SimulateIdlerAndSelectorHoming(logic::CommandBase &cb) {
|
|||
|
||||
void SimulateIdlerHoming(logic::CommandBase &cb) {
|
||||
uint32_t idlerStepsFwd = mm::unitToSteps<mm::I_pos_t>(config::idlerLimits.lenght - 5.0_deg);
|
||||
|
||||
// Sometimes the initial idler state is Ready. Let's wait for the firmware to start
|
||||
// homing.
|
||||
REQUIRE(WhileCondition(
|
||||
cb,
|
||||
[&](uint32_t) { return mi::idler.State() == mm::MovableBase::Ready; },
|
||||
5000));
|
||||
|
||||
// At this point the idler should always be homing forward.
|
||||
REQUIRE((int)mi::idler.State() == (int)mm::MovableBase::HomeForward);
|
||||
|
||||
// Simulate the idler steps in one direction (forward)
|
||||
for (uint32_t i = 0; i < idlerStepsFwd; ++i) {
|
||||
main_loop();
|
||||
cb.Step();
|
||||
|
|
@ -68,6 +82,8 @@ void SimulateIdlerHoming(logic::CommandBase &cb) {
|
|||
cb.Step();
|
||||
mm::motion.StallGuardReset(mm::Idler);
|
||||
|
||||
REQUIRE((int)mi::idler.State() == (int)mm::MovableBase::HomeBack);
|
||||
|
||||
// now do a correct amount of steps of each axis towards the other end
|
||||
uint32_t idlerSteps = mm::unitToSteps<mm::I_pos_t>(config::idlerLimits.lenght);
|
||||
uint32_t maxSteps = idlerSteps + 1;
|
||||
|
|
@ -82,6 +98,9 @@ void SimulateIdlerHoming(logic::CommandBase &cb) {
|
|||
mm::motion.StallGuardReset(mm::Idler);
|
||||
}
|
||||
}
|
||||
|
||||
// If the homing has failed, the axis length was too short.
|
||||
REQUIRE(!((mi::idler.State() & mm::MovableBase::HomingFailed) == mm::MovableBase::HomingFailed));
|
||||
}
|
||||
|
||||
void SimulateIdlerWaitForHomingValid(logic::CommandBase &cb) {
|
||||
|
|
@ -185,33 +204,40 @@ bool SimulateFailedHomeSelectorPostfix(logic::CommandBase &cb) {
|
|||
}
|
||||
|
||||
bool SimulateFailedHomeFirstTime(logic::CommandBase &cb) {
|
||||
if (mi::idler.HomingValid())
|
||||
return false;
|
||||
if (ms::selector.HomingValid())
|
||||
return false;
|
||||
REQUIRE(!mi::idler.HomingValid());
|
||||
REQUIRE(!ms::selector.HomingValid());
|
||||
|
||||
// Idler homing is successful
|
||||
SimulateIdlerHoming(cb);
|
||||
SimulateIdlerWaitForHomingValid(cb);
|
||||
|
||||
// Selector homes once the idler homing is valid.
|
||||
REQUIRE(mi::idler.HomingValid());
|
||||
REQUIRE(!ms::selector.HomingValid());
|
||||
|
||||
// The selector will only rehome once the idler homing is valid. At that moment
|
||||
// the state will change to HomeForward.
|
||||
REQUIRE(WhileCondition(
|
||||
cb,
|
||||
[&](uint32_t) { return ms::selector.State() != mm::MovableBase::HomeForward; },
|
||||
5000));
|
||||
|
||||
constexpr uint32_t selectorSteps = mm::unitToSteps<mm::S_pos_t>(config::selectorLimits.lenght) + 1;
|
||||
{
|
||||
// do 5 steps until we trigger the simulated StallGuard
|
||||
constexpr uint32_t idlerStepsFwd = mm::unitToSteps<mm::I_pos_t>(config::idlerLimits.lenght - 5.0_deg);
|
||||
static_assert(idlerStepsFwd < selectorSteps); // beware, we expect that the Idler homes faster than Selector (less steps)
|
||||
for (uint32_t i = 0; i < idlerStepsFwd; ++i) {
|
||||
for (uint32_t i = 0; i < selectorSteps; ++i) {
|
||||
main_loop();
|
||||
cb.Step();
|
||||
}
|
||||
|
||||
mm::TriggerStallGuard(mm::Selector);
|
||||
mm::TriggerStallGuard(mm::Idler);
|
||||
main_loop();
|
||||
cb.Step();
|
||||
mm::motion.StallGuardReset(mm::Selector);
|
||||
mm::motion.StallGuardReset(mm::Idler);
|
||||
}
|
||||
// now do a correct amount of steps of each axis towards the other end
|
||||
constexpr uint32_t idlerSteps = mm::unitToSteps<mm::I_pos_t>(config::idlerLimits.lenght);
|
||||
// now do LESS steps than expected to simulate something is blocking the selector
|
||||
|
||||
constexpr uint32_t selectorTriggerShort = std::min(idlerSteps, selectorSteps) / 2;
|
||||
// now do LESS steps than expected to simulate something is blocking the selector
|
||||
constexpr uint32_t selectorTriggerShort = selectorSteps / 2;
|
||||
constexpr uint32_t maxSteps = selectorTriggerShort + 1;
|
||||
{
|
||||
for (uint32_t i = 0; i < maxSteps; ++i) {
|
||||
|
|
@ -225,17 +251,6 @@ bool SimulateFailedHomeFirstTime(logic::CommandBase &cb) {
|
|||
}
|
||||
}
|
||||
|
||||
// make sure the Idler finishes its homing procedure (makes further checks much easier)
|
||||
for (uint32_t i = maxSteps; i < idlerSteps + 1; ++i) {
|
||||
main_loop();
|
||||
cb.Step();
|
||||
if (i == idlerSteps) {
|
||||
mm::TriggerStallGuard(mm::Idler);
|
||||
} else {
|
||||
mm::motion.StallGuardReset(mm::Idler);
|
||||
}
|
||||
}
|
||||
|
||||
while (!(ms::selector.State() & mm::MovableBase::OnHold)) {
|
||||
main_loop();
|
||||
cb.Step();
|
||||
|
|
|
|||
|
|
@ -79,19 +79,27 @@ void ForceReinitAllAutomata() {
|
|||
mg::globals.Init();
|
||||
mg::globals.SetFilamentLoaded(mg::globals.ActiveSlot(), mg::FilamentLoadState::AtPulley);
|
||||
}
|
||||
void HomeIdler() {
|
||||
logic::NoCommand nc; // just a dummy instance which has an empty Step()
|
||||
SimulateIdlerHoming(nc);
|
||||
SimulateIdlerWaitForHomingValid(nc);
|
||||
SimulateIdlerMoveToParkingPosition(nc);
|
||||
}
|
||||
|
||||
void HomeSelector() {
|
||||
logic::NoCommand nc; // just a dummy instance which has an empty Step()
|
||||
SimulateSelectorHoming(nc);
|
||||
SimulateSelectorWaitForHomingValid(nc);
|
||||
SimulateSelectorWaitForReadyState(nc);
|
||||
}
|
||||
|
||||
void HomeIdlerAndSelector() {
|
||||
mi::idler.InvalidateHoming();
|
||||
ms::selector.InvalidateHoming();
|
||||
logic::NoCommand nc; // just a dummy instance which has an empty Step()
|
||||
|
||||
SimulateIdlerHoming(nc);
|
||||
SimulateIdlerWaitForHomingValid(nc);
|
||||
SimulateIdlerMoveToParkingPosition(nc);
|
||||
HomeIdler();
|
||||
|
||||
SimulateSelectorHoming(nc);
|
||||
SimulateSelectorWaitForHomingValid(nc);
|
||||
SimulateSelectorWaitForReadyState(nc);
|
||||
HomeSelector();
|
||||
}
|
||||
|
||||
bool EnsureActiveSlotIndex(uint8_t slot, mg::FilamentLoadState loadState) {
|
||||
|
|
@ -181,10 +189,10 @@ void SimulateErrDisengagingIdler(logic::CommandBase &cb, ErrorCode deferredEC) {
|
|||
REQUIRE(WhileCondition(
|
||||
cb, [&](uint32_t) {
|
||||
if (cb.TopLevelState() == ProgressCode::ERRDisengagingIdler) {
|
||||
REQUIRE(cb.Error() == ErrorCode::RUNNING); // ensure the error gets never set while disengaging the idler
|
||||
REQUIRE((cb.Error() == ErrorCode::RUNNING)); // ensure the error gets never set while disengaging the idler
|
||||
return true;
|
||||
} else {
|
||||
REQUIRE(cb.Error() == deferredEC);
|
||||
REQUIRE((cb.Error() == deferredEC));
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
#include "../../../../src/logic/command_base.h"
|
||||
#include "../../../../src/modules/globals.h"
|
||||
#include "../../../../src/modules/idler.h"
|
||||
|
||||
extern void main_loop();
|
||||
extern void ForceReinitAllAutomata();
|
||||
|
|
@ -44,3 +45,21 @@ static constexpr uint32_t selectorMoveMaxSteps = 40000UL;
|
|||
void HomeIdlerAndSelector();
|
||||
|
||||
void SimulateErrDisengagingIdler(logic::CommandBase &cb, ErrorCode deferredEC);
|
||||
|
||||
template <typename T>
|
||||
bool SimulateEngageIdlerFully(T &cb) {
|
||||
return WhileCondition(
|
||||
cb,
|
||||
[&](uint32_t) { return !mi::idler.Engaged(); },
|
||||
5000);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool SimulateEngageIdlerPartially(T &cb) {
|
||||
return WhileCondition(
|
||||
cb,
|
||||
[&](uint32_t) { return !mi::idler.PartiallyDisengaged(); },
|
||||
5000);
|
||||
}
|
||||
|
||||
void HomeIdler();
|
||||
|
|
|
|||
|
|
@ -328,13 +328,43 @@ void ToolChangeFailFSensor(logic::ToolChange &tc, uint8_t fromSlot, uint8_t toSl
|
|||
// restart the automaton
|
||||
tc.Reset(toSlot);
|
||||
|
||||
REQUIRE(VerifyState(tc, mg::FilamentLoadState::InNozzle, mi::idler.IdleSlotIndex(), fromSlot, true, true, ml::off, ml::off, ErrorCode::RUNNING, ProgressCode::UnloadingFilament));
|
||||
// simulate unload to finda but fail the fsensor test
|
||||
REQUIRE(WhileCondition(tc, std::bind(SimulateUnloadToFINDA, _1, 500'000, 10'000), 200'000));
|
||||
REQUIRE(VerifyState(tc, mg::FilamentLoadState::InSelector, mi::idler.IdleSlotIndex(), fromSlot, false, false, ml::off, ml::blink0, ErrorCode::FSENSOR_DIDNT_SWITCH_OFF, ProgressCode::UnloadingFilament));
|
||||
REQUIRE(VerifyState(tc, mg::FilamentLoadState::InNozzle, mi::idler.IdleSlotIndex(), fromSlot, true, true, ml::blink0, ml::off, ErrorCode::RUNNING, ProgressCode::UnloadingFilament));
|
||||
|
||||
// now simulate unload to finda but fail the fsensor test - that means basically waiting for 4 seconds
|
||||
for (uint32_t ms = 0; ms < 4000; ++ms) {
|
||||
main_loop(); // increments 1ms each run
|
||||
tc.Step();
|
||||
}
|
||||
|
||||
// UnloadFilament.unl finds out, that fsensor didn't turn off in time
|
||||
main_loop();
|
||||
tc.Step();
|
||||
|
||||
// UnloadFilament starts error handling
|
||||
main_loop();
|
||||
tc.Step();
|
||||
|
||||
// wait for the Idler to be disengaged in error state
|
||||
REQUIRE(WhileCondition(
|
||||
tc, [&](uint32_t) {
|
||||
return tc.State() == ProgressCode::ERRDisengagingIdler;
|
||||
},
|
||||
idlerEngageDisengageMaxSteps));
|
||||
|
||||
REQUIRE(VerifyState(tc, mg::FilamentLoadState::InNozzle, mi::idler.IdleSlotIndex(), fromSlot, true, false, ml::off, ml::blink0, ErrorCode::FSENSOR_DIDNT_SWITCH_OFF, ProgressCode::UnloadingFilament));
|
||||
REQUIRE(tc.unl.State() == ProgressCode::ERRWaitingForUser);
|
||||
}
|
||||
|
||||
bool SimulateUnloadFilamentUntilSelectorRehoming(uint32_t step, const logic::ToolChange *tc, uint32_t unloadLengthSteps) {
|
||||
if (step == 20) { // on 20th step make FSensor switch off
|
||||
mfs::fsensor.ProcessMessage(false);
|
||||
} else if (step == unloadLengthSteps) {
|
||||
FINDAOnOff(false);
|
||||
}
|
||||
// end simulation at the DisengagingIdler stage, selector re-homing will take place later
|
||||
return tc->unl.State() != ProgressCode::DisengagingIdler;
|
||||
}
|
||||
|
||||
void ToolChangeFailFSensorMiddleBtn(logic::ToolChange &tc, uint8_t fromSlot, uint8_t toSlot) {
|
||||
using namespace std::placeholders;
|
||||
|
||||
|
|
@ -343,43 +373,31 @@ void ToolChangeFailFSensorMiddleBtn(logic::ToolChange &tc, uint8_t fromSlot, uin
|
|||
REQUIRE_FALSE(mui::userInput.AnyEvent());
|
||||
PressButtonAndDebounce(tc, mb::Middle, true);
|
||||
|
||||
REQUIRE(VerifyState(tc, mg::FilamentLoadState::InSelector, mi::idler.IdleSlotIndex(), fromSlot, false, false, ml::blink0, ml::off, ErrorCode::RUNNING, ProgressCode::UnloadingFilament));
|
||||
REQUIRE(tc.unl.State() == ProgressCode::FeedingToFinda); // MMU must find out where the filament is FS is OFF, FINDA is OFF
|
||||
REQUIRE(VerifyState(tc, mg::FilamentLoadState::InNozzle, mi::idler.IdleSlotIndex(), fromSlot, true, true, ml::blink0, ml::off, ErrorCode::RUNNING, ProgressCode::UnloadingFilament));
|
||||
|
||||
// both movables should have their homing flag invalidated
|
||||
REQUIRE_FALSE(mi::idler.HomingValid());
|
||||
REQUIRE_FALSE(ms::selector.HomingValid());
|
||||
|
||||
// make FINDA trigger - Idler will rehome in this step, Selector must remain at its place
|
||||
SimulateIdlerHoming(tc);
|
||||
|
||||
REQUIRE_FALSE(mi::idler.HomingValid());
|
||||
REQUIRE_FALSE(ms::selector.HomingValid());
|
||||
|
||||
SimulateIdlerWaitForHomingValid(tc);
|
||||
|
||||
REQUIRE(mi::idler.HomingValid());
|
||||
REQUIRE_FALSE(ms::selector.HomingValid());
|
||||
|
||||
SimulateIdlerMoveToParkingPosition(tc);
|
||||
|
||||
// now trigger the FINDA
|
||||
REQUIRE(WhileCondition(tc, std::bind(SimulateFeedToFINDA, _1, 100), 5000));
|
||||
REQUIRE(VerifyState(tc, mg::FilamentLoadState::InSelector, fromSlot, fromSlot, true, true, ml::blink0, ml::off, ErrorCode::RUNNING, ProgressCode::UnloadingFilament));
|
||||
REQUIRE(tc.unl.State() == ProgressCode::RetractingFromFinda);
|
||||
|
||||
// make FINDA switch off
|
||||
REQUIRE(WhileCondition(tc, std::bind(SimulateRetractFromFINDA, _1, 100), 5000));
|
||||
// perform a successful unload
|
||||
// During the last stage (DisengagingIdler), the Selector will start re-homing - needs special handling
|
||||
REQUIRE(WhileCondition(
|
||||
tc, [&](uint32_t) { return tc.unl.State() == ProgressCode::RetractingFromFinda; }, 50000));
|
||||
tc,
|
||||
std::bind(SimulateUnloadFilamentUntilSelectorRehoming, _1, &tc, mm::unitToSteps<mm::P_pos_t>(config::minimumBowdenLength)),
|
||||
200'000UL));
|
||||
|
||||
// Selector will start rehoming at this stage - that was the error this test was to find
|
||||
REQUIRE(VerifyState(tc, mg::FilamentLoadState::AtPulley, fromSlot, config::toolCount, false, true, ml::blink0, ml::off, ErrorCode::RUNNING, ProgressCode::UnloadingFilament));
|
||||
REQUIRE(tc.unl.State() == ProgressCode::DisengagingIdler);
|
||||
SimulateSelectorHoming(tc);
|
||||
SimulateSelectorWaitForHomingValid(tc);
|
||||
|
||||
// Selector should be moving to the target slot to accomplish the unload phase
|
||||
REQUIRE(WhileTopState(tc, ProgressCode::UnloadingFilament, 50'000));
|
||||
|
||||
// Idler has probably engaged meanwhile, ignore its position check
|
||||
REQUIRE(WhileTopState(tc, ProgressCode::UnloadingFilament, 50000));
|
||||
REQUIRE(VerifyState2(tc, mg::FilamentLoadState::AtPulley, config::toolCount, fromSlot, false, false, toSlot, ml::blink0, ml::off, ErrorCode::RUNNING, ProgressCode::FeedingToFinda));
|
||||
|
||||
// after that, perform a normal load
|
||||
|
|
@ -528,7 +546,7 @@ void ToolChangeFSENSOR_TOO_EARLY(logic::ToolChange &tc, uint8_t slot) {
|
|||
|
||||
// make AutoRetry
|
||||
PressButtonAndDebounce(tc, mb::Middle, true);
|
||||
REQUIRE(VerifyState(tc, mg::FilamentLoadState::InSelector, mi::idler.IdleSlotIndex(), slot, true, true, ml::off, ml::off, ErrorCode::RUNNING, ProgressCode::UnloadingFilament));
|
||||
REQUIRE(VerifyState(tc, mg::FilamentLoadState::InSelector, mi::idler.IdleSlotIndex(), slot, true, true, ml::blink0, ml::off, ErrorCode::RUNNING, ProgressCode::UnloadingFilament));
|
||||
|
||||
SimulateIdlerHoming(tc);
|
||||
|
||||
|
|
|
|||
|
|
@ -53,16 +53,10 @@ void RegularUnloadFromSlot04(uint8_t slot, logic::UnloadFilament &uf, uint8_t en
|
|||
REQUIRE(VerifyState(uf, (mg::FilamentLoadState)(mg::FilamentLoadState::InNozzle | mg::FilamentLoadState::InSelector),
|
||||
entryIdlerSlotIndex, slot, true, true, entryGreenLED, ml::off, ErrorCode::RUNNING, ProgressCode::UnloadingToFinda));
|
||||
|
||||
// run the automaton
|
||||
// Stage 1 - unloading to FINDA
|
||||
REQUIRE(WhileCondition(
|
||||
uf,
|
||||
[&](uint32_t step) -> bool {
|
||||
if(step == 100){ // on 100th step make FINDA trigger
|
||||
hal::gpio::WritePin(FINDA_PIN, hal::gpio::Level::low);
|
||||
}
|
||||
return uf.TopLevelState() == ProgressCode::UnloadingToFinda; },
|
||||
50000));
|
||||
REQUIRE(WhileCondition(uf, std::bind(SimulateUnloadToFINDA, _1, 100, 2'000), 200'000));
|
||||
|
||||
main_loop();
|
||||
uf.Step();
|
||||
|
||||
// we still think we have filament loaded at this stage
|
||||
// idler should have been activated by the underlying automaton
|
||||
|
|
@ -100,7 +94,7 @@ TEST_CASE("unload_filament::regular_unload_from_slot_0-4", "[unload_filament]")
|
|||
for (uint8_t slot = 0; slot < config::toolCount; ++slot) {
|
||||
logic::UnloadFilament uf;
|
||||
RegularUnloadFromSlot04Init(slot, uf);
|
||||
RegularUnloadFromSlot04(slot, uf, mi::Idler::IdleSlotIndex(), false, ml::off);
|
||||
RegularUnloadFromSlot04(slot, uf, mi::Idler::IdleSlotIndex(), false, ml::blink0);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -130,7 +124,7 @@ void FindaDidntTriggerCommonSetup(uint8_t slot, logic::UnloadFilament &uf) {
|
|||
// FINDA triggered off
|
||||
// green LED should be off
|
||||
// no error so far
|
||||
REQUIRE(VerifyState(uf, mg::FilamentLoadState::InNozzle, mi::Idler::IdleSlotIndex(), slot, true, true, ml::off, ml::off, ErrorCode::RUNNING, ProgressCode::UnloadingToFinda));
|
||||
REQUIRE(VerifyState(uf, mg::FilamentLoadState::InNozzle, mi::Idler::IdleSlotIndex(), slot, true, true, ml::blink0, ml::off, ErrorCode::RUNNING, ProgressCode::UnloadingToFinda));
|
||||
|
||||
// run the automaton
|
||||
// Stage 1 - unloading to FINDA - do NOT let it trigger - keep it pressed, the automaton should finish all moves with the pulley
|
||||
|
|
@ -177,7 +171,7 @@ void FindaDidntTriggerResolveTryAgain(uint8_t slot, logic::UnloadFilament &uf) {
|
|||
// no change in selector's position
|
||||
// FINDA still on
|
||||
// red LED should blink, green LED should be off
|
||||
REQUIRE(VerifyState(uf, mg::FilamentLoadState::InSelector, mi::Idler::IdleSlotIndex(), slot, true, true, ml::off, ml::off, ErrorCode::RUNNING, ProgressCode::UnloadingToFinda));
|
||||
REQUIRE(VerifyState(uf, mg::FilamentLoadState::InSelector, mi::Idler::IdleSlotIndex(), slot, true, true, ml::blink0, ml::off, ErrorCode::RUNNING, ProgressCode::UnloadingToFinda));
|
||||
|
||||
ClearButtons(uf);
|
||||
|
||||
|
|
|
|||
|
|
@ -12,10 +12,12 @@
|
|||
#include "../../../../src/modules/motion.h"
|
||||
#include "../../../../src/modules/permanent_storage.h"
|
||||
#include "../../../../src/modules/selector.h"
|
||||
#include "../../../../src/modules/timebase.h"
|
||||
|
||||
#include "../../../../src/logic/unload_to_finda.h"
|
||||
|
||||
#include "../../modules/stubs/stub_adc.h"
|
||||
#include "../../modules/stubs/stub_timebase.h"
|
||||
|
||||
#include "../stubs/main_loop_stub.h"
|
||||
#include "../stubs/stub_motion.h"
|
||||
|
|
@ -24,7 +26,7 @@ using namespace std::placeholders;
|
|||
|
||||
namespace ha = hal::adc;
|
||||
|
||||
TEST_CASE("unload_to_finda::regular_unload", "[unload_to_finda]") {
|
||||
void UnloadToFindaCommonSetup(logic::UnloadToFinda &ff, uint8_t retryAttempts) {
|
||||
ForceReinitAllAutomata();
|
||||
REQUIRE(EnsureActiveSlotIndex(0, mg::FilamentLoadState::AtPulley));
|
||||
|
||||
|
|
@ -35,27 +37,41 @@ TEST_CASE("unload_to_finda::regular_unload", "[unload_to_finda]") {
|
|||
// and MMU "thinks" it has the filament loaded
|
||||
mg::globals.SetFilamentLoaded(mg::globals.ActiveSlot(), mg::FilamentLoadState::InNozzle);
|
||||
|
||||
logic::UnloadToFinda ff;
|
||||
|
||||
// restart the automaton - just 1 attempt
|
||||
ff.Reset(1);
|
||||
ff.Reset(retryAttempts);
|
||||
|
||||
REQUIRE(ff.State() == logic::UnloadToFinda::EngagingIdler);
|
||||
|
||||
// it should have instructed the selector and idler to move to slot 1
|
||||
// check if the idler and selector have the right command
|
||||
CHECK(mm::AxisNearestTargetPos(mm::Idler) == mi::Idler::SlotPosition(0).v);
|
||||
CHECK(mm::AxisNearestTargetPos(mm::Idler) == mi::Idler::IntermediateSlotPosition(0).v);
|
||||
CHECK(mm::AxisNearestTargetPos(mm::Selector) == ms::Selector::SlotPosition(0).v);
|
||||
|
||||
// engaging idler
|
||||
REQUIRE(WhileCondition(
|
||||
ff,
|
||||
[&](uint32_t) { return !mi::idler.Engaged(); },
|
||||
5000));
|
||||
REQUIRE(SimulateEngageIdlerPartially(ff));
|
||||
}
|
||||
|
||||
void UnloadToFindaCommonTurnOffFSensor(logic::UnloadToFinda &ff) {
|
||||
// turn off fsensor - the printer freed the filament from the gears
|
||||
SetFSensorStateAndDebounce(false);
|
||||
|
||||
// make sure we step ff to handle turned-off fsensor
|
||||
ff.Step();
|
||||
|
||||
REQUIRE(ff.State() == logic::UnloadToFinda::UnloadingToFinda);
|
||||
CHECK(mm::axes[mm::Pulley].enabled == true);
|
||||
CHECK(mm::AxisNearestTargetPos(mm::Idler) == mi::Idler::SlotPosition(0).v);
|
||||
|
||||
REQUIRE(SimulateEngageIdlerFully(ff));
|
||||
|
||||
// now pulling the filament until finda triggers
|
||||
REQUIRE(ff.State() == logic::UnloadToFinda::WaitingForFINDA);
|
||||
}
|
||||
|
||||
TEST_CASE("unload_to_finda::regular_unload", "[unload_to_finda]") {
|
||||
logic::UnloadToFinda ff;
|
||||
UnloadToFindaCommonSetup(ff, 1);
|
||||
UnloadToFindaCommonTurnOffFSensor(ff);
|
||||
|
||||
REQUIRE(WhileCondition(ff, std::bind(SimulateUnloadToFINDA, _1, 10, 1000), 1100));
|
||||
|
||||
REQUIRE(ff.State() == logic::UnloadToFinda::OK);
|
||||
|
|
@ -77,37 +93,9 @@ TEST_CASE("unload_to_finda::no_sense_FINDA_upon_start", "[unload_to_finda]") {
|
|||
}
|
||||
|
||||
TEST_CASE("unload_to_finda::unload_without_FINDA_trigger", "[unload_to_finda]") {
|
||||
ForceReinitAllAutomata();
|
||||
REQUIRE(EnsureActiveSlotIndex(0, mg::FilamentLoadState::AtPulley));
|
||||
|
||||
// we need finda ON
|
||||
SetFINDAStateAndDebounce(true);
|
||||
// fsensor should be ON
|
||||
SetFSensorStateAndDebounce(true);
|
||||
// and MMU "thinks" it has the filament loaded
|
||||
mg::globals.SetFilamentLoaded(mg::globals.ActiveSlot(), mg::FilamentLoadState::InNozzle);
|
||||
|
||||
logic::UnloadToFinda ff;
|
||||
|
||||
// restart the automaton - just 1 attempt
|
||||
ff.Reset(1);
|
||||
|
||||
REQUIRE(ff.State() == logic::UnloadToFinda::EngagingIdler);
|
||||
|
||||
// it should have instructed the selector and idler to move to slot 1
|
||||
// check if the idler and selector have the right command
|
||||
CHECK(mm::AxisNearestTargetPos(mm::Idler) == mi::Idler::SlotPosition(0).v);
|
||||
CHECK(mm::AxisNearestTargetPos(mm::Selector) == ms::Selector::SlotPosition(0).v);
|
||||
CHECK(mm::axes[mm::Idler].enabled == true);
|
||||
|
||||
// engaging idler
|
||||
REQUIRE(WhileCondition(
|
||||
ff,
|
||||
[&](uint32_t) { return !mi::idler.Engaged(); },
|
||||
5000));
|
||||
|
||||
// now pulling the filament until finda triggers
|
||||
REQUIRE(ff.State() == logic::UnloadToFinda::WaitingForFINDA);
|
||||
UnloadToFindaCommonSetup(ff, 1);
|
||||
UnloadToFindaCommonTurnOffFSensor(ff);
|
||||
|
||||
// no changes to FINDA during unload - we'll pretend it never triggers
|
||||
// but set FSensor correctly
|
||||
|
|
@ -119,96 +107,67 @@ TEST_CASE("unload_to_finda::unload_without_FINDA_trigger", "[unload_to_finda]")
|
|||
}
|
||||
|
||||
TEST_CASE("unload_to_finda::unload_without_FSensor_trigger", "[unload_to_finda]") {
|
||||
ForceReinitAllAutomata();
|
||||
REQUIRE(EnsureActiveSlotIndex(0, mg::FilamentLoadState::AtPulley));
|
||||
|
||||
// we need finda ON
|
||||
SetFINDAStateAndDebounce(true);
|
||||
// fsensor should be ON
|
||||
SetFSensorStateAndDebounce(true);
|
||||
// and MMU "thinks" it has the filament loaded
|
||||
mg::globals.SetFilamentLoaded(mg::globals.ActiveSlot(), mg::FilamentLoadState::InNozzle);
|
||||
|
||||
logic::UnloadToFinda ff;
|
||||
|
||||
// restart the automaton - just 1 attempt
|
||||
ff.Reset(1);
|
||||
|
||||
REQUIRE(ff.State() == logic::UnloadToFinda::EngagingIdler);
|
||||
|
||||
// it should have instructed the selector and idler to move to slot 1
|
||||
// check if the idler and selector have the right command
|
||||
CHECK(mm::AxisNearestTargetPos(mm::Idler) == mi::Idler::SlotPosition(0).v);
|
||||
CHECK(mm::AxisNearestTargetPos(mm::Selector) == ms::Selector::SlotPosition(0).v);
|
||||
CHECK(mm::axes[mm::Idler].enabled == true);
|
||||
|
||||
// engaging idler
|
||||
REQUIRE(WhileCondition(
|
||||
ff,
|
||||
[&](uint32_t) { return !mi::idler.Engaged(); },
|
||||
5000));
|
||||
|
||||
// now pulling the filament until finda triggers
|
||||
REQUIRE(ff.State() == logic::UnloadToFinda::WaitingForFINDA);
|
||||
UnloadToFindaCommonSetup(ff, 1);
|
||||
|
||||
// no changes to FSensor during unload - we'll pretend it never triggers
|
||||
// but set FINDA correctly
|
||||
REQUIRE(WhileCondition(ff, std::bind(SimulateUnloadToFINDA, _1, 150000, 10000), 50000));
|
||||
// time-out in 4 seconds
|
||||
mt::IncMillis(4000);
|
||||
|
||||
main_loop();
|
||||
ff.Step();
|
||||
|
||||
// no pulling actually starts, because the fsensor didn't turn off and the time-out elapsed
|
||||
REQUIRE(ff.State() == logic::UnloadToFinda::FailedFSensor);
|
||||
REQUIRE(mg::globals.FilamentLoaded() == mg::FilamentLoadState::InSelector);
|
||||
REQUIRE(mg::globals.FilamentLoaded() == mg::FilamentLoadState::InNozzle);
|
||||
}
|
||||
|
||||
TEST_CASE("unload_to_finda::unload_repeated", "[unload_to_finda]") {
|
||||
ForceReinitAllAutomata();
|
||||
REQUIRE(EnsureActiveSlotIndex(0, mg::FilamentLoadState::AtPulley));
|
||||
|
||||
// we need finda ON
|
||||
SetFINDAStateAndDebounce(true);
|
||||
// fsensor should be ON
|
||||
SetFSensorStateAndDebounce(true);
|
||||
// and MMU "thinks" it has the filament loaded
|
||||
mg::globals.SetFilamentLoaded(mg::globals.ActiveSlot(), mg::FilamentLoadState::InNozzle);
|
||||
|
||||
logic::UnloadToFinda ff;
|
||||
UnloadToFindaCommonSetup(ff, 2);
|
||||
|
||||
// restart the automaton - 2 attempts
|
||||
ff.Reset(2);
|
||||
UnloadToFindaCommonTurnOffFSensor(ff);
|
||||
|
||||
REQUIRE(ff.State() == logic::UnloadToFinda::EngagingIdler);
|
||||
|
||||
// it should have instructed the selector and idler to move to slot 1
|
||||
// check if the idler and selector have the right command
|
||||
CHECK(mm::AxisNearestTargetPos(mm::Idler) == mi::Idler::SlotPosition(0).v);
|
||||
CHECK(mm::AxisNearestTargetPos(mm::Selector) == ms::Selector::SlotPosition(0).v);
|
||||
CHECK(mm::axes[mm::Idler].enabled == true);
|
||||
|
||||
// engaging idler
|
||||
REQUIRE(WhileCondition(
|
||||
ff,
|
||||
[&](uint32_t) { return !mi::idler.Engaged(); },
|
||||
5000));
|
||||
|
||||
// now pulling the filament until finda triggers
|
||||
REQUIRE(ff.State() == logic::UnloadToFinda::WaitingForFINDA);
|
||||
// remember raw Pulley pos for tweaking the steps below
|
||||
// because a 20mm (config::fsensorToNozzleAvoidGrindUnload)
|
||||
// move is being executed while the Idler is fully engaging
|
||||
// It is roughly -90 steps
|
||||
// int32_t pulleySteppedAlready = mm::axes[config::Pulley].pos;
|
||||
|
||||
// no changes to FINDA during unload - we'll pretend it never triggers
|
||||
// but set FSensor correctly
|
||||
// In this case it is vital to correctly compute the amount of steps
|
||||
// to make the unload state machine restart after the 1st attempt
|
||||
uint32_t unlSteps = 1 + mm::unitToSteps<mm::P_pos_t>(config::maximumBowdenLength + config::feedToFinda + config::filamentMinLoadedToMMU);
|
||||
// The number of steps must be more than what the state machine expects for FINDA to trigger.
|
||||
uint32_t unlSteps = 1 + mm::unitToSteps<mm::P_pos_t>(
|
||||
// standard fast move distance
|
||||
config::maximumBowdenLength + config::feedToFinda + config::filamentMinLoadedToMMU
|
||||
// slow start move distance
|
||||
+ config::fsensorToNozzleAvoidGrindUnload);
|
||||
// compensation
|
||||
// + pulleySteppedAlready;
|
||||
REQUIRE_FALSE(WhileCondition(ff, std::bind(SimulateUnloadToFINDA, _1, 10, 150000), unlSteps));
|
||||
|
||||
main_loop();
|
||||
ff.Step();
|
||||
|
||||
REQUIRE_FALSE(mi::idler.HomingValid());
|
||||
REQUIRE(ff.State() == logic::UnloadToFinda::EngagingIdler);
|
||||
REQUIRE(mg::globals.FilamentLoaded() == mg::FilamentLoadState::InSelector);
|
||||
|
||||
HomeIdler();
|
||||
|
||||
main_loop();
|
||||
ff.Step();
|
||||
REQUIRE(ff.State() == logic::UnloadToFinda::UnloadingToFinda);
|
||||
|
||||
SimulateEngageIdlerPartially(ff);
|
||||
|
||||
main_loop();
|
||||
ff.Step();
|
||||
|
||||
REQUIRE(ff.State() == logic::UnloadToFinda::UnloadingToFinda);
|
||||
SimulateEngageIdlerFully(ff);
|
||||
|
||||
REQUIRE(mg::globals.FilamentLoaded() == mg::FilamentLoadState::InSelector);
|
||||
|
||||
// make arbitrary amount of steps
|
||||
|
|
|
|||
|
|
@ -1,7 +1,13 @@
|
|||
# define the test executable
|
||||
add_executable(
|
||||
leds_tests ${CMAKE_SOURCE_DIR}/src/modules/leds.cpp ${MODULES_STUBS_DIR}/stub_shr16.cpp
|
||||
${MODULES_STUBS_DIR}/stub_timebase.cpp test_leds.cpp
|
||||
leds_tests
|
||||
${CMAKE_SOURCE_DIR}/src/modules/leds.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/modules/globals.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/modules/permanent_storage.cpp
|
||||
${MODULES_STUBS_DIR}/stub_shr16.cpp
|
||||
${MODULES_STUBS_DIR}/stub_timebase.cpp
|
||||
${MODULES_STUBS_DIR}/stub_eeprom.cpp
|
||||
test_leds.cpp
|
||||
)
|
||||
|
||||
# define required search paths
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import tarfile
|
|||
import zipfile
|
||||
from argparse import ArgumentParser
|
||||
from pathlib import Path
|
||||
from tarfile import TarFile
|
||||
from urllib.request import urlretrieve
|
||||
project_root_dir = Path(__file__).resolve().parent.parent
|
||||
dependencies_dir = project_root_dir / '.dependencies'
|
||||
|
|
@ -28,11 +29,11 @@ dependencies_dir = project_root_dir / '.dependencies'
|
|||
# yapf: disable
|
||||
dependencies = {
|
||||
'ninja': {
|
||||
'version': '1.10.2',
|
||||
'version': '1.12.1',
|
||||
'url': {
|
||||
'Linux': 'https://github.com/ninja-build/ninja/releases/download/v1.10.2/ninja-linux.zip',
|
||||
'Windows': 'https://github.com/ninja-build/ninja/releases/download/v1.10.2/ninja-win.zip',
|
||||
'Darwin': 'https://github.com/ninja-build/ninja/releases/download/v1.10.2/ninja-mac.zip',
|
||||
'Linux': 'https://github.com/ninja-build/ninja/releases/download/v1.12.1/ninja-linux.zip',
|
||||
'Windows': 'https://github.com/ninja-build/ninja/releases/download/v1.12.1/ninja-win.zip',
|
||||
'Darwin': 'https://github.com/ninja-build/ninja/releases/download/v1.12.1/ninja-mac.zip',
|
||||
},
|
||||
},
|
||||
'cmake': {
|
||||
|
|
@ -99,7 +100,11 @@ def download_and_unzip(url: str, directory: Path):
|
|||
obj = tarfile.open(f)
|
||||
else:
|
||||
obj = zipfile.ZipFile(f, 'r')
|
||||
obj.extractall(path=str(extract_dir))
|
||||
|
||||
if isinstance(obj, TarFile):
|
||||
obj.extractall(path=str(extract_dir), filter='data')
|
||||
else: # Zip file
|
||||
obj.extractall(path=str(extract_dir))
|
||||
|
||||
subdir = find_single_subdir(extract_dir)
|
||||
shutil.move(str(subdir), str(directory))
|
||||
|
|
|
|||
|
|
@ -619,6 +619,10 @@ def process_gcov_data(data_fname, covdata, source_fname, options):
|
|||
is_code_statement = False
|
||||
if tmp[0] == '-' or (excluding and tmp[0] in "#=0123456789"):
|
||||
is_code_statement = True
|
||||
if len(segments) < 3:
|
||||
noncode.add(lineno)
|
||||
continue
|
||||
|
||||
code = segments[2].strip()
|
||||
# remember certain non-executed lines
|
||||
if excluding or is_non_code(segments[2]):
|
||||
|
|
@ -634,7 +638,7 @@ def process_gcov_data(data_fname, covdata, source_fname, options):
|
|||
uncovered_exceptional.add(lineno)
|
||||
elif tmp[0] in "0123456789":
|
||||
is_code_statement = True
|
||||
covered[lineno] = int(segments[0].strip())
|
||||
covered[lineno] = int(segments[0].strip().rstrip('*'))
|
||||
elif tmp.startswith('branch'):
|
||||
exclude_branch = False
|
||||
if options.exclude_unreachable_branches and \
|
||||
|
|
|
|||
|
|
@ -93,11 +93,11 @@ pipeline {
|
|||
steps {
|
||||
sh """
|
||||
python3 utils/bootstrap.py
|
||||
export PATH=\$PWD/.dependencies/cmake-3.22.5/bin:\$PWD/.dependencies/ninja-1.10.2:\$PATH
|
||||
export PATH=\$PWD/.dependencies/cmake-3.22.5/bin:\$PWD/.dependencies/ninja-1.12.1:\$PATH
|
||||
export CTEST_OUTPUT_ON_FAILURE=1
|
||||
mkdir -p build-test
|
||||
LD_LIBRARY_PATH=/usr/local/lib32 \$PWD/.dependencies/cmake-3.22.5/bin/ctest --build-and-test . build-test \
|
||||
-DCMAKE_MAKE_PROGRAM=\$PWD/.dependencies/ninja-1.10.2/ninja \
|
||||
-DCMAKE_MAKE_PROGRAM=\$PWD/.dependencies/ninja-1.12.1/ninja \
|
||||
--build-generator Ninja \
|
||||
--build-target tests \
|
||||
--test-command ctest
|
||||
|
|
|
|||
|
|
@ -10,6 +10,6 @@ set(PROJECT_VERSION_MINOR
|
|||
CACHE STRING "Project minor version" FORCE
|
||||
)
|
||||
set(PROJECT_VERSION_REV
|
||||
1
|
||||
4
|
||||
CACHE STRING "Project revision" FORCE
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in New Issue