Compare commits
419 Commits
v0.12.0
...
work-close
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad0e9da00e | ||
|
|
0114d72a6c | ||
|
|
8c1037ef1b | ||
|
|
ed796fcfaa | ||
|
|
6ab253366c | ||
|
|
4aa550837f | ||
|
|
c72d73ec45 | ||
|
|
5fe333934d | ||
|
|
9a06d2b7e8 | ||
|
|
cf3b0475da | ||
|
|
aae29ba48b | ||
|
|
485c8f2ef0 | ||
|
|
7083879700 | ||
|
|
9ca71d8608 | ||
|
|
8a3d2afd79 | ||
|
|
80d185c94c | ||
|
|
cb13ee76ff | ||
|
|
a2a91654a9 | ||
|
|
383b83d788 | ||
|
|
2b9e041a86 | ||
|
|
90c1b82baa | ||
|
|
896343d943 | ||
|
|
1499bfa489 | ||
|
|
b7233d1197 | ||
|
|
16b4b6b302 | ||
|
|
7f89668d6c | ||
|
|
aecb29d2b0 | ||
|
|
9ce631e8d1 | ||
|
|
2165c90011 | ||
|
|
a6df541104 | ||
|
|
62325d4a35 | ||
|
|
03068b48fe | ||
|
|
d45b9c92d8 | ||
|
|
49205f92ff | ||
|
|
5493c60373 | ||
|
|
847331260c | ||
|
|
8291788f40 | ||
|
|
a18c74be05 | ||
|
|
42d8b9b847 | ||
|
|
2cfef4d94d | ||
|
|
f2e69a3703 | ||
|
|
d6494ffed5 | ||
|
|
9bd0d47576 | ||
|
|
a46dba08e2 | ||
|
|
f6718291b7 | ||
|
|
8a203cf2cb | ||
|
|
58541a799e | ||
|
|
848124ac4d | ||
|
|
3cdb1793d4 | ||
|
|
64ba37c02e | ||
|
|
06bb49f135 | ||
|
|
61f81bdb26 | ||
|
|
c28ed06e98 | ||
|
|
405935f918 | ||
|
|
906431bb00 | ||
|
|
4ef21a1e9b | ||
|
|
c5c79c936f | ||
|
|
f00919070e | ||
|
|
2ad0b1afc2 | ||
|
|
c75eb53c0c | ||
|
|
9f328cab95 | ||
|
|
c88ee84bed | ||
|
|
38bf6f2693 | ||
|
|
4f3a7fd227 | ||
|
|
f119e96e8f | ||
|
|
6d1256ddcc | ||
|
|
2af8d3f1d0 | ||
|
|
6631275ab6 | ||
|
|
9d36f31615 | ||
|
|
0f7887fffe | ||
|
|
a34034494e | ||
|
|
eeb2678ec2 | ||
|
|
a91d8a66f3 | ||
|
|
9d4ab862b9 | ||
|
|
85ebafd3f6 | ||
|
|
9adb313ee8 | ||
|
|
faa89be816 | ||
|
|
89d94dd33b | ||
|
|
a796ca5e72 | ||
|
|
94da4d10d7 | ||
|
|
31fe50ffa3 | ||
|
|
b381f509d1 | ||
|
|
ea546c789b | ||
|
|
f0a7797712 | ||
|
|
08102a0bf9 | ||
|
|
1c3b30b815 | ||
|
|
1563a68144 | ||
|
|
48590a35e4 | ||
|
|
335a0e20c2 | ||
|
|
8a1c3cd668 | ||
|
|
2c246c7d33 | ||
|
|
a4aa2a9002 | ||
|
|
08a85ba869 | ||
|
|
39f08aeda1 | ||
|
|
fe89c19ac0 | ||
|
|
0c806d84f7 | ||
|
|
55339998e5 | ||
|
|
0855994e59 | ||
|
|
8e1cdb199a | ||
|
|
34e9ea55df | ||
|
|
52af688245 | ||
|
|
8a530cbcce | ||
|
|
b89d552387 | ||
|
|
96cceed23e | ||
|
|
8f361a15b2 | ||
|
|
f4143af4fa | ||
|
|
1c0adb9af8 | ||
|
|
8a7a39530e | ||
|
|
3358295de8 | ||
|
|
ef75346861 | ||
|
|
064eee6859 | ||
|
|
8b7cc43952 | ||
|
|
9426485bb6 | ||
|
|
b4aca122a1 | ||
|
|
d9236f1c20 | ||
|
|
8a5801a204 | ||
|
|
71433b8224 | ||
|
|
87ac69363a | ||
|
|
5731d964b6 | ||
|
|
f323a4fcc7 | ||
|
|
69e0d866c0 | ||
|
|
0532a41c75 | ||
|
|
900bf2be55 | ||
|
|
cc4ad6670f | ||
|
|
28995a8bce | ||
|
|
7940a6a728 | ||
|
|
6ade82ed7e | ||
|
|
3a57f71f33 | ||
|
|
293858c51f | ||
|
|
14a83103c3 | ||
|
|
08a1c9f127 | ||
|
|
40d6a06f8f | ||
|
|
f71d2c7cfc | ||
|
|
81de9a8615 | ||
|
|
f9d7a71195 | ||
|
|
ff3eed2ad8 | ||
|
|
9e45ec222e | ||
|
|
3e55008323 | ||
|
|
d81eb557d7 | ||
|
|
d5e5a6da2d | ||
|
|
c0edfbc4ea | ||
|
|
3f2ef88eb9 | ||
|
|
503e7e368b | ||
|
|
ca815f52c8 | ||
|
|
025ae2349d | ||
|
|
d7d9092a92 | ||
|
|
ba2a149e9a | ||
|
|
13c75ea876 | ||
|
|
cb15d0fec6 | ||
|
|
055f07c638 | ||
|
|
c0095812ff | ||
|
|
0844388d70 | ||
|
|
12cd1d9e81 | ||
|
|
6848843224 | ||
|
|
bd1dbc8af3 | ||
|
|
7603953ef7 | ||
|
|
0087f04cc3 | ||
|
|
c84d78f3f1 | ||
|
|
248d3dbf8b | ||
|
|
00cb683def | ||
|
|
34732f857a | ||
|
|
9318901f19 | ||
|
|
4d21ffc1d6 | ||
|
|
6d70050261 | ||
|
|
2d73211190 | ||
|
|
d89722056b | ||
|
|
9fa0fb1a0e | ||
|
|
7149bb1b6d | ||
|
|
ba529996ea | ||
|
|
4ac283cc0e | ||
|
|
a19d64febd | ||
|
|
a19af08894 | ||
|
|
2c7e09cfa6 | ||
|
|
fc0f17b920 | ||
|
|
c7b7c11cc3 | ||
|
|
f2df011c68 | ||
|
|
0a14e33150 | ||
|
|
11f04ba1ba | ||
|
|
863a463cb2 | ||
|
|
ae227d485c | ||
|
|
433fcb6f24 | ||
|
|
beba2c2d33 | ||
|
|
fcf064ba68 | ||
|
|
aa0dbf6ee6 | ||
|
|
49f511e679 | ||
|
|
429aa2b2a6 | ||
|
|
bf1bc1ee0f | ||
|
|
1591a51f76 | ||
|
|
8de7153952 | ||
|
|
0d87bec159 | ||
|
|
589bd64ce0 | ||
|
|
36b8831c7e | ||
|
|
17c645f000 | ||
|
|
d4bae4dffe | ||
|
|
931d1ce8f4 | ||
|
|
93245b3678 | ||
|
|
068d2a9f5a | ||
|
|
f72f94e299 | ||
|
|
58753e58a2 | ||
|
|
abfe3675d6 | ||
|
|
e780049a74 | ||
|
|
f4adb29999 | ||
|
|
982a50c70a | ||
|
|
de9798fb5b | ||
|
|
f9a2920cee | ||
|
|
292512f813 | ||
|
|
6ea5b94d1e | ||
|
|
6f6122a576 | ||
|
|
8fc11b4a2e | ||
|
|
bec47e0492 | ||
|
|
12f92c55f1 | ||
|
|
49c0ad6369 | ||
|
|
6cd174208b | ||
|
|
3078912f1d | ||
|
|
b6a0063235 | ||
|
|
4a92727eab | ||
|
|
37482178b5 | ||
|
|
4709f1fad5 | ||
|
|
04c562941c | ||
|
|
cb6cce3934 | ||
|
|
3dc7c9ab29 | ||
|
|
6fac654352 | ||
|
|
29bfbd02f9 | ||
|
|
236d780a0a | ||
|
|
5d52b32e64 | ||
|
|
2efde0111e | ||
|
|
faee2c0e52 | ||
|
|
b7f7b8a346 | ||
|
|
694d38c791 | ||
|
|
dae8b8cacf | ||
|
|
e0cbd7b5fc | ||
|
|
ed8dca8df0 | ||
|
|
5249d955bb | ||
|
|
0f3f29101c | ||
|
|
1ca1054957 | ||
|
|
f01c8853ca | ||
|
|
472d8e5b66 | ||
|
|
80b55d3528 | ||
|
|
8f510da12b | ||
|
|
79930ed99a | ||
|
|
434770eaf9 | ||
|
|
7e8c7f46a9 | ||
|
|
af149b4781 | ||
|
|
d8d072b375 | ||
|
|
7b490f3ec1 | ||
|
|
b1eec53ff4 | ||
|
|
0b329c5d28 | ||
|
|
c3ec4af6cc | ||
|
|
2f6e94c94c | ||
|
|
713b509698 | ||
|
|
28faf81414 | ||
|
|
819599362c | ||
|
|
abb7910316 | ||
|
|
9ceaae3847 | ||
|
|
56829b07d2 | ||
|
|
f73e6dcd12 | ||
|
|
144af05270 | ||
|
|
95fdb68587 | ||
|
|
c106955850 | ||
|
|
12e9b633d8 | ||
|
|
2425a74638 | ||
|
|
36f9b26ef9 | ||
|
|
75d7c17656 | ||
|
|
c37329e9e2 | ||
|
|
4cfa266e00 | ||
|
|
a8b493a1ae | ||
|
|
b09897245e | ||
|
|
30e0fddbbf | ||
|
|
28281c595b | ||
|
|
13b2926e0c | ||
|
|
b0d90fd013 | ||
|
|
d84fc431a1 | ||
|
|
da2b258441 | ||
|
|
b8f1df3a96 | ||
|
|
acdf8bb108 | ||
|
|
fa5fa74761 | ||
|
|
01c7befacb | ||
|
|
b029d04668 | ||
|
|
6f16e11197 | ||
|
|
24c884e9f3 | ||
|
|
75a40e817d | ||
|
|
5e280680c5 | ||
|
|
67c152745e | ||
|
|
9e1cbdcee3 | ||
|
|
0aacbc3973 | ||
|
|
bedec55154 | ||
|
|
239f8e59e0 | ||
|
|
e37b007f67 | ||
|
|
d9043345b6 | ||
|
|
40728e9231 | ||
|
|
de1cf216ac | ||
|
|
e6df93fcf8 | ||
|
|
235b75be3c | ||
|
|
78a15b6d81 | ||
|
|
bfb71bc2dc | ||
|
|
09a78c31bb | ||
|
|
0291a1554c | ||
|
|
d99d1a8463 | ||
|
|
bb512ef5d7 | ||
|
|
bddefdde36 | ||
|
|
0105aa330f | ||
|
|
18de421c4a | ||
|
|
71604b712a | ||
|
|
31de734d19 | ||
|
|
b98375b360 | ||
|
|
a77d07907f | ||
|
|
28f06a104b | ||
|
|
72b301a285 | ||
|
|
4f00f21991 | ||
|
|
c92732e4f1 | ||
|
|
2f7b234189 | ||
|
|
b2ac0f1ce3 | ||
|
|
9a940ffccb | ||
|
|
0aaabf1904 | ||
|
|
0cd16e956d | ||
|
|
1b24f6a2ad | ||
|
|
6ce6fbbce0 | ||
|
|
9f41f53c5e | ||
|
|
16a7b50ce9 | ||
|
|
600e89ae8c | ||
|
|
5e3daa6f21 | ||
|
|
5e433fff06 | ||
|
|
f1982edcd5 | ||
|
|
44e79e0c37 | ||
|
|
23c5b20f5b | ||
|
|
55e46aa625 | ||
|
|
43a9685c58 | ||
|
|
fd2feff67d | ||
|
|
1baa45913f | ||
|
|
7abafb575b | ||
|
|
4115ea128a | ||
|
|
2e8b54ae5f | ||
|
|
f653db9c88 | ||
|
|
daf875e6e4 | ||
|
|
d785b396a7 | ||
|
|
d853c19811 | ||
|
|
6f0e91f69f | ||
|
|
2dc4cfc5df | ||
|
|
266e96621c | ||
|
|
dc6182f3b3 | ||
|
|
95e1a290f1 | ||
|
|
5ff555a705 | ||
|
|
3275614b89 | ||
|
|
83d0d2f19b | ||
|
|
94719fe327 | ||
|
|
1d92be71da | ||
|
|
6cc409f6fb | ||
|
|
d633ef2cfc | ||
|
|
43d0dba4b4 | ||
|
|
7a74888b43 | ||
|
|
3d3b87f97f | ||
|
|
447a88eb08 | ||
|
|
1ea9f3aa35 | ||
|
|
dab39c02cd | ||
|
|
96ab906946 | ||
|
|
84aa3caa45 | ||
|
|
49315b3cc4 | ||
|
|
1a1568c38b | ||
|
|
b50d6669a8 | ||
|
|
0665dc8976 | ||
|
|
92fe8f15b8 | ||
|
|
25bc649cd2 | ||
|
|
9847b44901 | ||
|
|
d7f6348ae6 | ||
|
|
b502558052 | ||
|
|
6f686ddee3 | ||
|
|
c716edafe2 | ||
|
|
3370134593 | ||
|
|
95c753292d | ||
|
|
acde3720a4 | ||
|
|
ffd44c0219 | ||
|
|
f4c8f0bf88 | ||
|
|
3f84501955 | ||
|
|
43ce7c0b9a | ||
|
|
d6a4669ce0 | ||
|
|
e67cbbe5c1 | ||
|
|
978c294741 | ||
|
|
644f7e0872 | ||
|
|
fe56bf36c9 | ||
|
|
77619e912c | ||
|
|
147492b253 | ||
|
|
71ab6240f2 | ||
|
|
547bfbf818 | ||
|
|
3417940fd8 | ||
|
|
2defd7374a | ||
|
|
1e5f688b53 | ||
|
|
19862bc3b7 | ||
|
|
f0753bd338 | ||
|
|
6676c1df86 | ||
|
|
2c2bb720fa | ||
|
|
1a83845c9f | ||
|
|
fe7082e4a8 | ||
|
|
c5bd813d8b | ||
|
|
62bf52bfcf | ||
|
|
99d7af87fd | ||
|
|
c491ea669f | ||
|
|
b7b13588c7 | ||
|
|
9e574c3497 | ||
|
|
93cd8834f3 | ||
|
|
bafb126abd | ||
|
|
5bd32e2984 | ||
|
|
d929be487b | ||
|
|
7ed3c679dc | ||
|
|
0ccf5f8e47 | ||
|
|
fc102edc24 | ||
|
|
05d5451347 | ||
|
|
795ce490a0 | ||
|
|
38221df83a | ||
|
|
03f69cd81a | ||
|
|
ea2f6bc0f5 | ||
|
|
bb4711c5d3 | ||
|
|
187cc2f1b8 | ||
|
|
3c8f202dfa | ||
|
|
83df4a8627 | ||
|
|
29b7550ce5 | ||
|
|
48a05eaa54 | ||
|
|
4688c21c54 | ||
|
|
3f8f30d612 | ||
|
|
74473322e5 |
2
.github/workflows/build-test.yaml
vendored
2
.github/workflows/build-test.yaml
vendored
@@ -21,7 +21,7 @@ jobs:
|
|||||||
run: ./scripts/ci-build.sh 2>&1
|
run: ./scripts/ci-build.sh 2>&1
|
||||||
|
|
||||||
- name: Upload micro-controller data dictionaries
|
- name: Upload micro-controller data dictionaries
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: data-dict
|
name: data-dict
|
||||||
path: ci_build/dict
|
path: ci_build/dict
|
||||||
|
|||||||
116
.github/workflows/stale-issue-bot.yaml
vendored
116
.github/workflows/stale-issue-bot.yaml
vendored
@@ -11,16 +11,14 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v8
|
- uses: actions/stale@v8
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
stale-pr-message: |
|
||||||
stale-issue-message: |
|
|
||||||
Hello,
|
Hello,
|
||||||
|
|
||||||
It looks like there hasn't been any recent updates on this
|
It looks like there hasn't been any recent updates on this
|
||||||
Klipper github issue. If you created this issue and no
|
github ticket. We prefer to only list tickets as "open" if
|
||||||
longer consider it open, then please login to github and
|
they are actively being worked on. Feel free to provide an
|
||||||
close the issue. Otherwise, if there is no further activity
|
update on this ticket. Otherwise the ticket will be
|
||||||
on this thread then it will be automatically closed in a few
|
automatically closed in a few days.
|
||||||
days.
|
|
||||||
|
|
||||||
Best regards,
|
Best regards,
|
||||||
|
|
||||||
@@ -29,10 +27,10 @@ jobs:
|
|||||||
PS: I'm just an automated script, not a human being.
|
PS: I'm just an automated script, not a human being.
|
||||||
|
|
||||||
exempt-issue-labels: 'enhancement,bug'
|
exempt-issue-labels: 'enhancement,bug'
|
||||||
days-before-stale: 35
|
days-before-stale: 60
|
||||||
days-before-close: 7
|
days-before-close: 7
|
||||||
days-before-pr-stale: -1
|
days-before-issue-stale: -1
|
||||||
days-before-pr-close: -1
|
days-before-issue-close: -1
|
||||||
# Close tickets marked with "not on github" label
|
# Close tickets marked with "not on github" label
|
||||||
close_not_on_github:
|
close_not_on_github:
|
||||||
if: github.repository == 'Klipper3d/klipper'
|
if: github.repository == 'Klipper3d/klipper'
|
||||||
@@ -62,54 +60,54 @@ jobs:
|
|||||||
state: 'closed'
|
state: 'closed'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
# Close tickets marked with "reviewer needed" label for 2+ weeks
|
# # Close tickets marked with "reviewer needed" label for 2+ weeks
|
||||||
close_reviewer_needed:
|
# close_reviewer_needed:
|
||||||
if: github.repository == 'Klipper3d/klipper'
|
# if: github.repository == 'Klipper3d/klipper'
|
||||||
runs-on: ubuntu-latest
|
# runs-on: ubuntu-latest
|
||||||
steps:
|
# steps:
|
||||||
- uses: actions/github-script@v6
|
# - uses: actions/github-script@v6
|
||||||
with:
|
# with:
|
||||||
script: |
|
# script: |
|
||||||
const issues = await github.rest.issues.listForRepo({
|
# const issues = await github.rest.issues.listForRepo({
|
||||||
owner: context.repo.owner,
|
# owner: context.repo.owner,
|
||||||
repo: context.repo.repo,
|
# repo: context.repo.repo,
|
||||||
state: 'open',
|
# state: 'open',
|
||||||
labels: 'reviewer needed',
|
# labels: 'reviewer needed',
|
||||||
assignee: 'none',
|
# assignee: 'none',
|
||||||
per_page: 100,
|
# per_page: 100,
|
||||||
page: 1
|
# page: 1
|
||||||
});
|
# });
|
||||||
msg = "Unfortunately a reviewer has not assigned themselves to"
|
# msg = "Unfortunately a reviewer has not assigned themselves to"
|
||||||
+ " this GitHub Pull Request and it is therefore being"
|
# + " this GitHub Pull Request and it is therefore being"
|
||||||
+ " closed. It is a good idea to move"
|
# + " closed. It is a good idea to move"
|
||||||
+ " further discussion to the [Klipper Discourse]"
|
# + " further discussion to the [Klipper Discourse]"
|
||||||
+ "(https://www.klipper3d.org/Contact.html#discourse-forum)"
|
# + "(https://www.klipper3d.org/Contact.html#discourse-forum)"
|
||||||
+ " server. Reviewers can reach out on that forum to let you"
|
# + " server. Reviewers can reach out on that forum to let you"
|
||||||
+ " know if they are interested and when they are available."
|
# + " know if they are interested and when they are available."
|
||||||
+ "\n\n"
|
# + "\n\n"
|
||||||
+ "Best regards,\n"
|
# + "Best regards,\n"
|
||||||
+ "~ Your friendly GitIssueBot"
|
# + "~ Your friendly GitIssueBot"
|
||||||
+ "\n\n"
|
# + "\n\n"
|
||||||
+ "PS: I'm just an automated script, not a human being.";
|
# + "PS: I'm just an automated script, not a human being.";
|
||||||
const expireMillis = 1000 * 60 * 60 * 24 * 14;
|
# const expireMillis = 1000 * 60 * 60 * 24 * 14;
|
||||||
const curtime = new Date().getTime();
|
# const curtime = new Date().getTime();
|
||||||
for (const issue of issues.data.values()) {
|
# for (const issue of issues.data.values()) {
|
||||||
const updatetime = new Date(issue.updated_at).getTime();
|
# const updatetime = new Date(issue.updated_at).getTime();
|
||||||
if (curtime < updatetime + expireMillis)
|
# if (curtime < updatetime + expireMillis)
|
||||||
continue;
|
# continue;
|
||||||
await github.rest.issues.createComment({
|
# await github.rest.issues.createComment({
|
||||||
owner: context.repo.owner,
|
# owner: context.repo.owner,
|
||||||
repo: context.repo.repo,
|
# repo: context.repo.repo,
|
||||||
issue_number: issue.number,
|
# issue_number: issue.number,
|
||||||
body: msg
|
# body: msg
|
||||||
});
|
# });
|
||||||
await github.rest.issues.update({
|
# await github.rest.issues.update({
|
||||||
owner: context.repo.owner,
|
# owner: context.repo.owner,
|
||||||
repo: context.repo.repo,
|
# repo: context.repo.repo,
|
||||||
issue_number: issue.number,
|
# issue_number: issue.number,
|
||||||
state: 'closed'
|
# state: 'closed'
|
||||||
});
|
# });
|
||||||
}
|
# }
|
||||||
# Mark unassigned PRs that are idle for 2 weeks
|
# Mark unassigned PRs that are idle for 2 weeks
|
||||||
mark_reviewer_needed:
|
mark_reviewer_needed:
|
||||||
if: github.repository == 'Klipper3d/klipper'
|
if: github.repository == 'Klipper3d/klipper'
|
||||||
@@ -330,7 +328,7 @@ jobs:
|
|||||||
}
|
}
|
||||||
# Lock closed issues after 6 months of inactivity and PRs after 1 year.
|
# Lock closed issues after 6 months of inactivity and PRs after 1 year.
|
||||||
lock:
|
lock:
|
||||||
name: Lock Closed Issues
|
name: Lock Closed Tickets
|
||||||
if: github.repository == 'Klipper3d/klipper'
|
if: github.repository == 'Klipper3d/klipper'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
7
Makefile
7
Makefile
@@ -29,10 +29,11 @@ dirs-y = src
|
|||||||
cc-option=$(shell if test -z "`$(1) $(2) -S -o /dev/null -xc /dev/null 2>&1`" \
|
cc-option=$(shell if test -z "`$(1) $(2) -S -o /dev/null -xc /dev/null 2>&1`" \
|
||||||
; then echo "$(2)"; else echo "$(3)"; fi ;)
|
; then echo "$(2)"; else echo "$(3)"; fi ;)
|
||||||
|
|
||||||
CFLAGS := -I$(OUT) -Isrc -I$(OUT)board-generic/ -std=gnu11 -O2 -MD \
|
CFLAGS := -iquote $(OUT) -iquote src -iquote $(OUT)board-generic/ \
|
||||||
-Wall -Wold-style-definition $(call cc-option,$(CC),-Wtype-limits,) \
|
-std=gnu11 -O2 -MD -Wall \
|
||||||
|
-Wold-style-definition $(call cc-option,$(CC),-Wtype-limits,) \
|
||||||
-ffunction-sections -fdata-sections -fno-delete-null-pointer-checks
|
-ffunction-sections -fdata-sections -fno-delete-null-pointer-checks
|
||||||
CFLAGS += -flto -fwhole-program -fno-use-linker-plugin -ggdb3
|
CFLAGS += -flto=auto -fwhole-program -fno-use-linker-plugin -ggdb3
|
||||||
|
|
||||||
OBJS_klipper.elf = $(patsubst %.c, $(OUT)src/%.o,$(src-y))
|
OBJS_klipper.elf = $(patsubst %.c, $(OUT)src/%.o,$(src-y))
|
||||||
OBJS_klipper.elf += $(OUT)compile_time_request.o
|
OBJS_klipper.elf += $(OUT)compile_time_request.o
|
||||||
|
|||||||
15
README.md
15
README.md
@@ -4,15 +4,14 @@ Welcome to the Klipper project!
|
|||||||
|
|
||||||
https://www.klipper3d.org/
|
https://www.klipper3d.org/
|
||||||
|
|
||||||
Klipper is a 3d-Printer firmware. It combines the power of a general
|
The Klipper firmware controls 3d-Printers. It combines the power of a
|
||||||
purpose computer with one or more micro-controllers. See the
|
general purpose computer with one or more micro-controllers. See the
|
||||||
[features document](https://www.klipper3d.org/Features.html) for more
|
[features document](https://www.klipper3d.org/Features.html) for more
|
||||||
information on why you should use Klipper.
|
information on why you should use the Klipper software.
|
||||||
|
|
||||||
To begin using Klipper start by
|
Start by [installing Klipper software](https://www.klipper3d.org/Installation.html).
|
||||||
[installing](https://www.klipper3d.org/Installation.html) it.
|
|
||||||
|
|
||||||
Klipper is Free Software. See the [license](COPYING) or read the
|
Klipper software is Free Software. See the [license](COPYING) or read
|
||||||
[documentation](https://www.klipper3d.org/Overview.html). We depend on
|
the [documentation](https://www.klipper3d.org/Overview.html). We
|
||||||
the generous support from our
|
depend on the generous support from our
|
||||||
[sponsors](https://www.klipper3d.org/Sponsors.html).
|
[sponsors](https://www.klipper3d.org/Sponsors.html).
|
||||||
|
|||||||
223
config/generic-I3DBEEZ9.cfg
Normal file
223
config/generic-I3DBEEZ9.cfg
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
# This file contains common pin mappings for the I3DBEEZ9 V1.0.
|
||||||
|
# To use this config, the firmware should be compiled for the
|
||||||
|
# STM32F407 with a "32KiB bootloader".
|
||||||
|
|
||||||
|
# The "make flash" command does not work on the I3DBEEZ9. Instead,
|
||||||
|
# after running "make", copy the generated "out/klipper.bin" file to a
|
||||||
|
# file named "firmware.bin" on an SD card and then restart the I3DBEEZ9
|
||||||
|
# with that SD card.
|
||||||
|
|
||||||
|
# See docs/Config_Reference.md for a description of parameters.
|
||||||
|
|
||||||
|
[stepper_x]
|
||||||
|
step_pin: PE9
|
||||||
|
dir_pin: PF1
|
||||||
|
enable_pin: !PF2
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 40
|
||||||
|
endstop_pin: PB10
|
||||||
|
position_endstop: 0
|
||||||
|
position_max: 200
|
||||||
|
homing_speed: 50
|
||||||
|
|
||||||
|
[stepper_y]
|
||||||
|
step_pin: PE11
|
||||||
|
dir_pin: PE1
|
||||||
|
enable_pin: !PD7
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 40
|
||||||
|
endstop_pin: PE12
|
||||||
|
position_endstop: 0
|
||||||
|
position_max: 200
|
||||||
|
homing_speed: 50
|
||||||
|
|
||||||
|
[stepper_z]
|
||||||
|
step_pin: PE13
|
||||||
|
dir_pin: PC2
|
||||||
|
enable_pin: !PC0
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 8
|
||||||
|
endstop_pin: PG8
|
||||||
|
position_endstop: 0
|
||||||
|
position_max: 200
|
||||||
|
|
||||||
|
[extruder]
|
||||||
|
step_pin: PE14
|
||||||
|
dir_pin: PA0
|
||||||
|
enable_pin: !PC3
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 33.500
|
||||||
|
nozzle_diameter: 0.400
|
||||||
|
filament_diameter: 1.750
|
||||||
|
heater_pin: PB1 # Heat0
|
||||||
|
sensor_pin: PF4 # T1 Header
|
||||||
|
sensor_type: EPCOS 100K B57560G104F
|
||||||
|
control: pid
|
||||||
|
pid_Kp: 22.2
|
||||||
|
pid_Ki: 1.08
|
||||||
|
pid_Kd: 114
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 250
|
||||||
|
|
||||||
|
#[extruder1]
|
||||||
|
#step_pin: PD15
|
||||||
|
#dir_pin: PE7
|
||||||
|
#enable_pin: !PA3
|
||||||
|
#heater_pin: PD14 # Heat1
|
||||||
|
#sensor_pin: PF5 # T2
|
||||||
|
#...
|
||||||
|
|
||||||
|
#[extruder2]
|
||||||
|
#step_pin: PD13
|
||||||
|
#dir_pin: PG9
|
||||||
|
#enable_pin: !PF0
|
||||||
|
#heater_pin: PB0 # Heat2
|
||||||
|
#sensor_pin: PF6 # T3
|
||||||
|
#...
|
||||||
|
|
||||||
|
#[stepper_z1]
|
||||||
|
#step_pin: PE4
|
||||||
|
#dir_pin: PE3
|
||||||
|
#enable_pin: !PC13
|
||||||
|
#microsteps: 16
|
||||||
|
#rotation_distance: 8
|
||||||
|
#endstop_pin: PD0
|
||||||
|
#position_endstop: 0.5
|
||||||
|
#position_max: 200
|
||||||
|
|
||||||
|
[heater_bed]
|
||||||
|
heater_pin: PD12
|
||||||
|
sensor_pin: PF3 # T0
|
||||||
|
sensor_type: ATC Semitec 104GT-2
|
||||||
|
control: watermark
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 130
|
||||||
|
|
||||||
|
[fan]
|
||||||
|
pin: PC8
|
||||||
|
|
||||||
|
[heater_fan fan1]
|
||||||
|
pin: PE5
|
||||||
|
|
||||||
|
#[heater_fan fan2]
|
||||||
|
#pin: PE6
|
||||||
|
|
||||||
|
[mcu]
|
||||||
|
serial: /dev/serial/by-id/usb-Klipper_Klipper_firmware_12345-if00
|
||||||
|
|
||||||
|
[printer]
|
||||||
|
kinematics: cartesian
|
||||||
|
max_velocity: 300
|
||||||
|
max_accel: 3000
|
||||||
|
max_z_velocity: 5
|
||||||
|
max_z_accel: 100
|
||||||
|
|
||||||
|
|
||||||
|
########################################
|
||||||
|
# TMC2208 configuration
|
||||||
|
########################################
|
||||||
|
|
||||||
|
#[tmc2208 stepper_x]
|
||||||
|
#uart_pin: PA15
|
||||||
|
#run_current: 0.800
|
||||||
|
#stealthchop_threshold: 999999
|
||||||
|
|
||||||
|
#[tmc2208 stepper_y]
|
||||||
|
#uart_pin: PB8
|
||||||
|
#run_current: 0.800
|
||||||
|
#stealthchop_threshold: 999999
|
||||||
|
|
||||||
|
#[tmc2208 stepper_z]
|
||||||
|
#uart_pin: PB9
|
||||||
|
#run_current: 0.650
|
||||||
|
#stealthchop_threshold: 999999
|
||||||
|
|
||||||
|
#[tmc2208 extruder]
|
||||||
|
#uart_pin: PB3
|
||||||
|
#run_current: 0.800
|
||||||
|
#stealthchop_threshold: 999999
|
||||||
|
|
||||||
|
#[tmc2208 extruder1]
|
||||||
|
#uart_pin: PG15
|
||||||
|
#run_current: 0.800
|
||||||
|
#stealthchop_threshold: 999999
|
||||||
|
|
||||||
|
#[tmc2208 extruder2]
|
||||||
|
#uart_pin: PG12
|
||||||
|
#run_current: 0.800
|
||||||
|
#stealthchop_threshold: 999999
|
||||||
|
|
||||||
|
#[tmc2208 stepper_z1]
|
||||||
|
#uart_pin: PE2
|
||||||
|
#run_current: 0.650
|
||||||
|
#stealthchop_threshold: 999999
|
||||||
|
|
||||||
|
########################################
|
||||||
|
# TMC2130 configuration
|
||||||
|
########################################
|
||||||
|
|
||||||
|
#[tmc2130 stepper_x]
|
||||||
|
#cs_pin: PA15
|
||||||
|
#spi_bus: spi3a
|
||||||
|
##diag1_pin: PB10
|
||||||
|
#run_current: 0.800
|
||||||
|
#stealthchop_threshold: 999999
|
||||||
|
|
||||||
|
#[tmc2130 stepper_y]
|
||||||
|
#cs_pin: PB8
|
||||||
|
#spi_bus: spi3a
|
||||||
|
##diag1_pin: PE12
|
||||||
|
#run_current: 0.800
|
||||||
|
#stealthchop_threshold: 999999
|
||||||
|
|
||||||
|
#[tmc2130 stepper_z]
|
||||||
|
#cs_pin: PB9
|
||||||
|
#spi_bus: spi3a
|
||||||
|
##diag1_pin: PG8
|
||||||
|
#run_current: 0.650
|
||||||
|
#stealthchop_threshold: 999999
|
||||||
|
|
||||||
|
#[tmc2130 extruder]
|
||||||
|
#cs_pin: PB3
|
||||||
|
#spi_bus: spi3a
|
||||||
|
##diag1_pin: PE15
|
||||||
|
#run_current: 0.800
|
||||||
|
#stealthchop_threshold: 999999
|
||||||
|
|
||||||
|
#[tmc2130 extruder1]
|
||||||
|
#cs_pin: PG15
|
||||||
|
#spi_bus: spi3a
|
||||||
|
##diag1_pin: PE10
|
||||||
|
#run_current: 0.800
|
||||||
|
#stealthchop_threshold: 999999
|
||||||
|
|
||||||
|
#[tmc2130 extruder2]
|
||||||
|
#cs_pin: PG12
|
||||||
|
#spi_bus: spi3a
|
||||||
|
##diag1_pin: PG5
|
||||||
|
#run_current: 0.800
|
||||||
|
#stealthchop_threshold: 999999
|
||||||
|
|
||||||
|
#[tmc2130 stepper_z1]
|
||||||
|
#cs_pin: PE2
|
||||||
|
#spi_bus: spi3a
|
||||||
|
##diag1_pin: PD0
|
||||||
|
#run_current: 0.650
|
||||||
|
#stealthchop_threshold: 999999
|
||||||
|
|
||||||
|
|
||||||
|
########################################
|
||||||
|
# EXP1 / EXP2 (display) pins
|
||||||
|
########################################
|
||||||
|
|
||||||
|
[board_pins]
|
||||||
|
aliases:
|
||||||
|
# EXP1 header
|
||||||
|
EXP1_1=PG4, EXP1_3=PD11, EXP1_5=PG2, EXP1_7=PG6, EXP1_9=<GND>,
|
||||||
|
EXP1_2=PA8, EXP1_4=PD10, EXP1_6=PG3, EXP1_8=PG7, EXP1_10=<5V>,
|
||||||
|
# EXP2 header
|
||||||
|
EXP2_1=PB14, EXP2_3=PG10, EXP2_5=PF11, EXP2_7=PF12, EXP2_9=<GND>,
|
||||||
|
EXP2_2=PB13, EXP2_4=PB12, EXP2_6=PB15, EXP2_8=<RST>, EXP2_10=PF13
|
||||||
|
# Pins EXP2_1, EXP2_6, EXP2_2 are also MISO, MOSI, SCK of bus "spi2"
|
||||||
|
|
||||||
|
# See the sample-lcd.cfg file for definitions of common LCD displays.
|
||||||
@@ -85,11 +85,10 @@ uart_pin: PC11
|
|||||||
tx_pin: PC10
|
tx_pin: PC10
|
||||||
uart_address: 3
|
uart_address: 3
|
||||||
run_current: 0.650
|
run_current: 0.650
|
||||||
stealthchop_threshold: 999999
|
|
||||||
|
|
||||||
[heater_bed]
|
[heater_bed]
|
||||||
heater_pin: PC9
|
heater_pin: PC9
|
||||||
sensor_type: ATC Semitec 104GT-2
|
sensor_type: EPCOS 100K B57560G104F
|
||||||
sensor_pin: PC4
|
sensor_pin: PC4
|
||||||
control: pid
|
control: pid
|
||||||
pid_Kp: 54.027
|
pid_Kp: 54.027
|
||||||
|
|||||||
@@ -95,4 +95,4 @@ max_z_accel: 100
|
|||||||
aliases:
|
aliases:
|
||||||
EXP1_1=PC6,EXP1_3=PB10,EXP1_5=PB14,EXP1_7=PB12,EXP1_9=<GND>,
|
EXP1_1=PC6,EXP1_3=PB10,EXP1_5=PB14,EXP1_7=PB12,EXP1_9=<GND>,
|
||||||
EXP1_2=PB2,EXP1_4=PB11,EXP1_6=PB13,EXP1_8=PB15,EXP1_10=<5V>,
|
EXP1_2=PB2,EXP1_4=PB11,EXP1_6=PB13,EXP1_8=PB15,EXP1_10=<5V>,
|
||||||
PROBE_IN=PB0,PROBE_OUT=PB1,FIL_RUNOUT=PC6
|
PROBE_IN=PB0,PROBE_OUT=PB1,FIL_RUNOUT=PA4
|
||||||
|
|||||||
241
config/generic-ldo-leviathan-v1.2.cfg
Normal file
241
config/generic-ldo-leviathan-v1.2.cfg
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
# This file contains common pin mappings for the LDO Leviathan v1.2.
|
||||||
|
|
||||||
|
# To use this config, during "make menuconfig", select "Enable
|
||||||
|
# low-level configuration options", select the STM32F446 micro-controller,
|
||||||
|
# select a "32KiB bootloader", and select a "12Mhz crystal".
|
||||||
|
|
||||||
|
# See docs/Config_Reference.md for a description of parameters.
|
||||||
|
|
||||||
|
# HV-STEPPER-0
|
||||||
|
[stepper_x]
|
||||||
|
step_pin: PB10
|
||||||
|
dir_pin: PB11
|
||||||
|
enable_pin: !PG0
|
||||||
|
microsteps: 32
|
||||||
|
rotation_distance: 40
|
||||||
|
endstop_pin: PC1 # X-ENDSTOP
|
||||||
|
position_endstop: 0
|
||||||
|
position_max: 200
|
||||||
|
homing_speed: 50
|
||||||
|
|
||||||
|
[tmc5160 stepper_x]
|
||||||
|
spi_bus: spi4
|
||||||
|
cs_pin: PE15
|
||||||
|
#diag0_pin: PG1
|
||||||
|
interpolate: False
|
||||||
|
sense_resistor: 0.075
|
||||||
|
run_current: 0.8
|
||||||
|
stealthchop_threshold: 0
|
||||||
|
|
||||||
|
# HV-STEPPER-1
|
||||||
|
[stepper_y]
|
||||||
|
step_pin: PF15
|
||||||
|
dir_pin: PF14
|
||||||
|
enable_pin: !PE9
|
||||||
|
microsteps: 32
|
||||||
|
rotation_distance: 40
|
||||||
|
endstop_pin: PC2 # Y-ENDSTOP
|
||||||
|
position_endstop: 0
|
||||||
|
position_max: 200
|
||||||
|
homing_speed: 50
|
||||||
|
|
||||||
|
[tmc5160 stepper_y]
|
||||||
|
spi_bus: spi4
|
||||||
|
cs_pin: PE11
|
||||||
|
#diag0_pin: PE10
|
||||||
|
interpolate: False
|
||||||
|
sense_resistor: 0.075
|
||||||
|
run_current: 0.8
|
||||||
|
stealthchop_threshold: 0
|
||||||
|
|
||||||
|
# STEPPER-0
|
||||||
|
[stepper_z]
|
||||||
|
step_pin: PD4
|
||||||
|
dir_pin: PD3
|
||||||
|
enable_pin: !PD7
|
||||||
|
microsteps: 32
|
||||||
|
rotation_distance: 8
|
||||||
|
endstop_pin: PC3 # Z-ENDSTOP
|
||||||
|
position_endstop: 0
|
||||||
|
position_max: 200
|
||||||
|
|
||||||
|
[tmc2209 stepper_z]
|
||||||
|
uart_pin: PD5
|
||||||
|
#diag_pin: PD6
|
||||||
|
interpolate: False
|
||||||
|
run_current: 0.6
|
||||||
|
stealthchop_threshold: 999999
|
||||||
|
|
||||||
|
# The Leviathan was developed for Voron printers. It therefore has several
|
||||||
|
# steppers for the z-axes, but only one heater for one extruder.
|
||||||
|
|
||||||
|
# STEPPER-1
|
||||||
|
#[stepper_z1]
|
||||||
|
#step_pin: PC12
|
||||||
|
#dir_pin: PC11
|
||||||
|
#enable_pin: !PD2
|
||||||
|
#microsteps: 32
|
||||||
|
#rotation_distance: 8
|
||||||
|
#
|
||||||
|
#[tmc2209 stepper_z1]
|
||||||
|
#uart_pin: PD5
|
||||||
|
##diag_pin: PD6
|
||||||
|
#interpolate: False
|
||||||
|
#run_current: 0.6
|
||||||
|
#stealthchop_threshold: 999999
|
||||||
|
|
||||||
|
# STEPPER-2
|
||||||
|
#[stepper_z2]
|
||||||
|
#step_pin: PC9
|
||||||
|
#dir_pin: PC8
|
||||||
|
#enable_pin: !PC10
|
||||||
|
#microsteps: 32
|
||||||
|
#rotation_distance: 8
|
||||||
|
#
|
||||||
|
#[tmc2209 stepper_z2]
|
||||||
|
#uart_pin: PA8
|
||||||
|
##diag_pin: PA15
|
||||||
|
#interpolate: False
|
||||||
|
#run_current: 0.6
|
||||||
|
#stealthchop_threshold: 999999
|
||||||
|
|
||||||
|
# STEPPER-3
|
||||||
|
#[stepper_z3]
|
||||||
|
#step_pin: PG7
|
||||||
|
#dir_pin: PG6
|
||||||
|
#enable_pin: !PC7
|
||||||
|
#microsteps: 32
|
||||||
|
#rotation_distance: 8
|
||||||
|
#
|
||||||
|
#[tmc2209 stepper_z2]
|
||||||
|
#uart_pin: PG8
|
||||||
|
##diag_pin: PC6
|
||||||
|
#interpolate: False
|
||||||
|
#run_current: 0.6
|
||||||
|
#stealthchop_threshold: 999999
|
||||||
|
|
||||||
|
# STEPPER-4
|
||||||
|
[extruder]
|
||||||
|
step_pin: PD10
|
||||||
|
dir_pin: PD9
|
||||||
|
enable_pin: !PD13
|
||||||
|
microsteps: 32
|
||||||
|
rotation_distance: 22.67
|
||||||
|
nozzle_diameter: 0.400
|
||||||
|
filament_diameter: 1.750
|
||||||
|
heater_pin: PG10 # HEATER
|
||||||
|
sensor_pin: PA2 # TH1
|
||||||
|
sensor_type: ATC Semitec 104NT-4-R025H42G
|
||||||
|
pullup_resistor: 2200
|
||||||
|
control: pid
|
||||||
|
pid_Kp: 36.787
|
||||||
|
pid_Ki: 4.716
|
||||||
|
pid_Kd: 71.735
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 250
|
||||||
|
|
||||||
|
[tmc2209 stepper_z]
|
||||||
|
uart_pin: PD11
|
||||||
|
#diag_pin: PD12
|
||||||
|
interpolate: False
|
||||||
|
run_current: 0.5
|
||||||
|
stealthchop_threshold: 0
|
||||||
|
|
||||||
|
#[filament_switch_sensor material_0]
|
||||||
|
#switch_pin: PC0 # FILAMENT-SENSOR
|
||||||
|
|
||||||
|
[heater_bed]
|
||||||
|
heater_pin: PG11 # HEATBED
|
||||||
|
sensor_pin: PA1 # TH0
|
||||||
|
sensor_type: ATC Semitec 104GT-2
|
||||||
|
pullup_resistor: 2200
|
||||||
|
control: pid
|
||||||
|
pid_kp: 56.723
|
||||||
|
pid_ki: 5.561
|
||||||
|
pid_kd: 144.642
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 130
|
||||||
|
|
||||||
|
[fan]
|
||||||
|
pin: PB7 # FAN0
|
||||||
|
#tachometer_pin: PB0
|
||||||
|
|
||||||
|
#[heater_fan fan1]
|
||||||
|
#pin: PB3
|
||||||
|
#tachometer_pin: PB4
|
||||||
|
|
||||||
|
#[heater_fan fan2]
|
||||||
|
#pin: PF7
|
||||||
|
#tachometer_pin: PF6
|
||||||
|
|
||||||
|
#[controller_fan fan3]
|
||||||
|
#pin: PF9
|
||||||
|
#tachometer_pin: PF8
|
||||||
|
|
||||||
|
[mcu]
|
||||||
|
serial: /dev/serial/by-id/usb-Klipper_Klipper_firmware_12345-if00
|
||||||
|
# CAN bus is also available on this board
|
||||||
|
|
||||||
|
[printer]
|
||||||
|
kinematics: cartesian
|
||||||
|
max_velocity: 300
|
||||||
|
max_accel: 3000
|
||||||
|
max_z_velocity: 5
|
||||||
|
max_z_accel: 100
|
||||||
|
|
||||||
|
[board_pins]
|
||||||
|
aliases:
|
||||||
|
# EXP1 header
|
||||||
|
EXP1_1=PG9, EXP1_2=PG12,
|
||||||
|
EXP1_3=PG13, EXP1_4=PG14,
|
||||||
|
EXP1_5=PC13, EXP1_6=PC14,
|
||||||
|
EXP1_7=PC15, EXP1_8=PF0,
|
||||||
|
EXP1_9=<GND>, EXP1_10=<5V>,
|
||||||
|
|
||||||
|
# EXP2 header
|
||||||
|
EXP2_1=PA6, EXP2_2=PA5,
|
||||||
|
EXP2_3=PE2, EXP2_4=PE4,
|
||||||
|
EXP2_5=PE3, EXP2_6=PA7,
|
||||||
|
EXP2_7=PE5, EXP2_8=<RST>,
|
||||||
|
EXP2_9=<GND>, EXP2_10=PE4,
|
||||||
|
|
||||||
|
# See the sample-lcd.cfg file for definitions of common LCD displays.
|
||||||
|
|
||||||
|
# EXTENSION PORT
|
||||||
|
EXP3_1=<5V>, EXP3_2=<5V>, # max. 0.5A
|
||||||
|
EXP3_3=<GND>, EXP3_4=<GND>,
|
||||||
|
EXP3_5=<3.3V>, EXP3_6=<3.3V>, # max. 0.5A
|
||||||
|
EXP3_7=PF5, EXP3_8=PF4,
|
||||||
|
EXP3_9=PF3, EXP3_10=PF2,
|
||||||
|
EXP3_11=PC4, EXP3_12=PC5, # EXP3_11 and EXP3_12 are ADC inputs
|
||||||
|
EXP3_13=PB0, EXP3_14=PB1, # EXP3_13 and EXP3_14 are ADC inputs
|
||||||
|
EXP3_15=PE8, EXP3_16=PE7, # EXP3_15 is UART5_TX, EXP3_16 is UART5_RX
|
||||||
|
EXP3_17=PG5, EXP3_18=PG4,
|
||||||
|
EXP3_19=PG3, EXP3_20=PG2,
|
||||||
|
EXP3_21=PD15, EXP3_22=PD14,
|
||||||
|
EXP3_23=PB15, EXP3_24=PB14, # EXP3_23 is SPI2_MOSI
|
||||||
|
# EXP3_24 is SPI2_MISO
|
||||||
|
EXP3_25=PB13, EXP3_26=PB12, # EXP3_25 is SPI2_SCK + CAN2_TX
|
||||||
|
# EXP3_26 is SPI2_CS + CAN2_RX
|
||||||
|
EXP3_27=<GND>, EXP3_28=<GND>,
|
||||||
|
EXP3_29=<24V>, EXP3_30=<24V>, # max. 0.5A
|
||||||
|
|
||||||
|
#[probe]
|
||||||
|
#sensor_pin: PF1 # Z-PROBE
|
||||||
|
#z_offset: 0
|
||||||
|
|
||||||
|
#[led my_led]
|
||||||
|
#white_pin: PE6 # LED-Strip
|
||||||
|
|
||||||
|
#[neopixel my_neopixel]
|
||||||
|
#pin: PF10 # NEOPIXEL
|
||||||
|
|
||||||
|
#[temperature_sensor TH2]
|
||||||
|
#sensor_type: ATC Semitec 104GT-2
|
||||||
|
#sensor_pin: PA0 # TH2
|
||||||
|
#pullup_resistor: 2200
|
||||||
|
|
||||||
|
#[temperature_sensor TH3]
|
||||||
|
#sensor_type: ATC Semitec 104GT-2
|
||||||
|
#sensor_pin: PA3 # TH3
|
||||||
|
#pullup_resistor: 2200
|
||||||
232
config/generic-mellow-fly-e3-v2.cfg
Normal file
232
config/generic-mellow-fly-e3-v2.cfg
Normal file
@@ -0,0 +1,232 @@
|
|||||||
|
# This file contains common pin mappings for the Mellow Fly-E3-v2.
|
||||||
|
# To use this config, the firmware should be compiled for the
|
||||||
|
# STM32F407 with a "32KiB bootloader".
|
||||||
|
|
||||||
|
# The "make flash" command does not work on the Fly-E3-v2. Instead,
|
||||||
|
# after running "make", copy the generated "out/klipper.bin" file to a
|
||||||
|
# file named "firmware.bin" or "klipper.bin" on an SD card and then restart the Fly-E3-v2
|
||||||
|
# with that SD card.
|
||||||
|
|
||||||
|
# See docs/Config_Reference.md for a description of parameters.
|
||||||
|
|
||||||
|
[mcu]
|
||||||
|
serial: /dev/serial/by-id/usb-Klipper_stm32f407xx_27004A001851323333353137-if00
|
||||||
|
|
||||||
|
[stepper_x]
|
||||||
|
step_pin: PE5
|
||||||
|
dir_pin: PC0
|
||||||
|
enable_pin: !PC1
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 30
|
||||||
|
full_steps_per_rotation: 200
|
||||||
|
endstop_pin: PE7 #X-STOP
|
||||||
|
position_endstop: 0
|
||||||
|
position_max: 200
|
||||||
|
homing_speed: 50
|
||||||
|
second_homing_speed: 10
|
||||||
|
homing_retract_dist: 5.0
|
||||||
|
homing_positive_dir: false
|
||||||
|
step_pulse_duration: 0.000004
|
||||||
|
|
||||||
|
[stepper_y]
|
||||||
|
step_pin: PE4
|
||||||
|
dir_pin: !PC13
|
||||||
|
enable_pin: !PC14
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 30
|
||||||
|
full_steps_per_rotation: 200
|
||||||
|
endstop_pin: PE8 #Y-STOP
|
||||||
|
position_endstop: 0
|
||||||
|
position_max: 200
|
||||||
|
homing_speed: 50
|
||||||
|
second_homing_speed: 10
|
||||||
|
homing_retract_dist: 5.0
|
||||||
|
homing_positive_dir: false
|
||||||
|
step_pulse_duration: 0.000004
|
||||||
|
|
||||||
|
[stepper_z]
|
||||||
|
step_pin: PE1
|
||||||
|
dir_pin: !PB7
|
||||||
|
enable_pin: !PE3
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 30
|
||||||
|
full_steps_per_rotation: 200
|
||||||
|
endstop_pin: PE9 #Z-STOP
|
||||||
|
position_min: 0
|
||||||
|
position_endstop: 0
|
||||||
|
position_max: 200
|
||||||
|
homing_speed: 5
|
||||||
|
second_homing_speed: 3
|
||||||
|
homing_retract_dist: 5.0
|
||||||
|
homing_positive_dir: false
|
||||||
|
step_pulse_duration: 0.000004
|
||||||
|
|
||||||
|
[extruder]
|
||||||
|
step_pin: PE2
|
||||||
|
dir_pin: PD5
|
||||||
|
enable_pin: !PD6
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 33.500
|
||||||
|
nozzle_diameter: 0.400
|
||||||
|
filament_diameter: 1.750
|
||||||
|
heater_pin: PC6 #E0
|
||||||
|
|
||||||
|
########################################
|
||||||
|
# Extruder 100K thermistor configuration
|
||||||
|
########################################
|
||||||
|
sensor_type: ATC Semitec 104GT-2
|
||||||
|
sensor_pin: PC4 #T0 TEMP
|
||||||
|
control: pid
|
||||||
|
pid_Kp: 22.2
|
||||||
|
pid_Ki: 1.08
|
||||||
|
pid_Kd: 114
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 275
|
||||||
|
########################################
|
||||||
|
# Extruder MAX31865 PT100 2 wire config
|
||||||
|
########################################
|
||||||
|
# sensor_type: MAX31865
|
||||||
|
# sensor_pin: PD15 #PT-100
|
||||||
|
# spi_speed: 4000000
|
||||||
|
# spi_software_sclk_pin: PD12
|
||||||
|
# spi_software_mosi_pin: PD11
|
||||||
|
# spi_software_miso_pin: PD13
|
||||||
|
# rtd_nominal_r: 100
|
||||||
|
# rtd_reference_r: 430
|
||||||
|
# rtd_num_of_wires: 2
|
||||||
|
# rtd_use_50Hz_filter: True
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 300
|
||||||
|
|
||||||
|
#[extruder1]
|
||||||
|
#step_pin: PE0
|
||||||
|
#dir_pin: PD1
|
||||||
|
#enable_pin: !PD3
|
||||||
|
#microsteps: 16
|
||||||
|
#heater_pin: PC7 #E1
|
||||||
|
#sensor_pin: PC5 #T1 TEMP
|
||||||
|
|
||||||
|
########################################
|
||||||
|
# TMC2209 configuration
|
||||||
|
########################################
|
||||||
|
|
||||||
|
[tmc2209 stepper_x]
|
||||||
|
uart_pin: PC15
|
||||||
|
interpolate: False
|
||||||
|
run_current: 0.3
|
||||||
|
sense_resistor: 0.110
|
||||||
|
stealthchop_threshold: 999999
|
||||||
|
|
||||||
|
[tmc2209 stepper_y]
|
||||||
|
uart_pin: PB6
|
||||||
|
interpolate: False
|
||||||
|
run_current: 0.3
|
||||||
|
sense_resistor: 0.110
|
||||||
|
stealthchop_threshold: 999999
|
||||||
|
|
||||||
|
[tmc2209 stepper_z]
|
||||||
|
uart_pin: PD7
|
||||||
|
interpolate: False
|
||||||
|
run_current: 0.4
|
||||||
|
sense_resistor: 0.110
|
||||||
|
stealthchop_threshold: 999999
|
||||||
|
|
||||||
|
[tmc2209 extruder]
|
||||||
|
uart_pin: PD4
|
||||||
|
interpolate: False
|
||||||
|
run_current: 0.27
|
||||||
|
sense_resistor: 0.075
|
||||||
|
stealthchop_threshold: 999999
|
||||||
|
|
||||||
|
#[tmc2209 extruder1]
|
||||||
|
#uart_pin: PD0
|
||||||
|
#interpolate: False
|
||||||
|
#run_current: 0.27
|
||||||
|
#sense_resistor: 0.075
|
||||||
|
#stealthchop_threshold: 999999
|
||||||
|
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Heated Bed
|
||||||
|
#######################################
|
||||||
|
|
||||||
|
[heater_bed]
|
||||||
|
heater_pin: PB0 #BED
|
||||||
|
sensor_type: Generic 3950
|
||||||
|
sensor_pin: PB1 #B-TEMP
|
||||||
|
max_power: 1.0
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 120
|
||||||
|
control: pid
|
||||||
|
pid_kp: 58.437
|
||||||
|
pid_ki: 2.347
|
||||||
|
pid_kd: 363.769
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# LIGHTING
|
||||||
|
#######################################
|
||||||
|
|
||||||
|
#[led Toolhead]
|
||||||
|
#white_pin: PA2 #FAN2
|
||||||
|
#cycle_time: 0.010
|
||||||
|
#initial_white: 0
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# COOLING
|
||||||
|
#######################################
|
||||||
|
|
||||||
|
[heater_fan hotend_fan]
|
||||||
|
pin: PA1 #FAN1
|
||||||
|
max_power: 1.0
|
||||||
|
kick_start_time: 0.5
|
||||||
|
heater: extruder
|
||||||
|
heater_temp: 50
|
||||||
|
fan_speed: 1.0
|
||||||
|
|
||||||
|
[controller_fan controller_fan]
|
||||||
|
pin: PA0 #FAN0
|
||||||
|
max_power: 1.0
|
||||||
|
kick_start_time: 0.5
|
||||||
|
heater: extruder
|
||||||
|
stepper: stepper_x, stepper_y, stepper_z
|
||||||
|
fan_speed: 1.0
|
||||||
|
idle_timeout: 60
|
||||||
|
|
||||||
|
[fan]
|
||||||
|
pin: PA3 #FAN3
|
||||||
|
max_power: 1.0
|
||||||
|
off_below: 0.2
|
||||||
|
|
||||||
|
[temperature_sensor Mellow_Fly_E3_V2]
|
||||||
|
sensor_type: temperature_mcu
|
||||||
|
min_temp: 5
|
||||||
|
max_temp: 80
|
||||||
|
|
||||||
|
[printer]
|
||||||
|
kinematics: cartesian
|
||||||
|
max_velocity: 300
|
||||||
|
max_accel: 3000
|
||||||
|
max_z_velocity: 50
|
||||||
|
max_z_accel: 100
|
||||||
|
|
||||||
|
########################################
|
||||||
|
# EXP1 / EXP2 (display) pins
|
||||||
|
########################################
|
||||||
|
[board_pins]
|
||||||
|
aliases:
|
||||||
|
EXP1_1=PD10, EXP1_3=PA8, EXP1_5=PE15, EXP1_7=PA14, EXP1_9=<GND>,
|
||||||
|
EXP1_2=PA9, EXP1_4=PA10, EXP1_6=PE14, EXP1_8=PA13, EXP1_10=<5V>,
|
||||||
|
# EXP2 header
|
||||||
|
EXP2_1=PA6, EXP2_3=PB11, EXP2_5=PB10, EXP2_7=PE13, EXP2_9=<GND>,
|
||||||
|
EXP2_2=PA5, EXP2_4=PA4, EXP2_6=PA7, EXP2_8=<RST>, EXP2_10=<NC>,
|
||||||
|
|
||||||
|
# See the sample-lcd.cfg file for definitions of common LCD displays.
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# BL-Touch
|
||||||
|
#######################################
|
||||||
|
|
||||||
|
#[bltouch]
|
||||||
|
#sensor_pin: PC2
|
||||||
|
#control_pin: PE6
|
||||||
|
#z_offset: 0
|
||||||
@@ -84,7 +84,7 @@ pwm: True
|
|||||||
scale: 2.0
|
scale: 2.0
|
||||||
cycle_time: .000030
|
cycle_time: .000030
|
||||||
hardware_pwm: True
|
hardware_pwm: True
|
||||||
static_value: 1.3
|
value: 1.3
|
||||||
|
|
||||||
[output_pin stepper_z_current]
|
[output_pin stepper_z_current]
|
||||||
pin: PL4
|
pin: PL4
|
||||||
@@ -92,7 +92,7 @@ pwm: True
|
|||||||
scale: 2.0
|
scale: 2.0
|
||||||
cycle_time: .000030
|
cycle_time: .000030
|
||||||
hardware_pwm: True
|
hardware_pwm: True
|
||||||
static_value: 1.3
|
value: 1.3
|
||||||
|
|
||||||
[output_pin stepper_e_current]
|
[output_pin stepper_e_current]
|
||||||
pin: PL5
|
pin: PL5
|
||||||
@@ -100,7 +100,7 @@ pwm: True
|
|||||||
scale: 2.0
|
scale: 2.0
|
||||||
cycle_time: .000030
|
cycle_time: .000030
|
||||||
hardware_pwm: True
|
hardware_pwm: True
|
||||||
static_value: 1.25
|
value: 1.25
|
||||||
|
|
||||||
[static_digital_output stepper_config]
|
[static_digital_output stepper_config]
|
||||||
pins:
|
pins:
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ max_z_accel: 30
|
|||||||
|
|
||||||
[output_pin case_light]
|
[output_pin case_light]
|
||||||
pin: PH5
|
pin: PH5
|
||||||
static_value: 1.0
|
value: 1.0
|
||||||
|
|
||||||
# Motor current settings.
|
# Motor current settings.
|
||||||
[output_pin stepper_xy_current]
|
[output_pin stepper_xy_current]
|
||||||
@@ -107,7 +107,7 @@ scale: 2.000
|
|||||||
# Max power setting.
|
# Max power setting.
|
||||||
cycle_time: .000030
|
cycle_time: .000030
|
||||||
hardware_pwm: True
|
hardware_pwm: True
|
||||||
static_value: 1.200
|
value: 1.200
|
||||||
# Power adjustment setting.
|
# Power adjustment setting.
|
||||||
|
|
||||||
[output_pin stepper_z_current]
|
[output_pin stepper_z_current]
|
||||||
@@ -116,7 +116,7 @@ pwm: True
|
|||||||
scale: 2.000
|
scale: 2.000
|
||||||
cycle_time: .000030
|
cycle_time: .000030
|
||||||
hardware_pwm: True
|
hardware_pwm: True
|
||||||
static_value: 1.200
|
value: 1.200
|
||||||
|
|
||||||
[output_pin stepper_e_current]
|
[output_pin stepper_e_current]
|
||||||
pin: PL3
|
pin: PL3
|
||||||
@@ -124,4 +124,4 @@ pwm: True
|
|||||||
scale: 2.000
|
scale: 2.000
|
||||||
cycle_time: .000030
|
cycle_time: .000030
|
||||||
hardware_pwm: True
|
hardware_pwm: True
|
||||||
static_value: 1.250
|
value: 1.250
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ pwm: True
|
|||||||
scale: 2.0
|
scale: 2.0
|
||||||
cycle_time: .000030
|
cycle_time: .000030
|
||||||
hardware_pwm: True
|
hardware_pwm: True
|
||||||
static_value: 1.3
|
value: 1.3
|
||||||
|
|
||||||
[output_pin stepper_z_current]
|
[output_pin stepper_z_current]
|
||||||
pin: PL4
|
pin: PL4
|
||||||
@@ -97,7 +97,7 @@ pwm: True
|
|||||||
scale: 2.0
|
scale: 2.0
|
||||||
cycle_time: .000030
|
cycle_time: .000030
|
||||||
hardware_pwm: True
|
hardware_pwm: True
|
||||||
static_value: 1.3
|
value: 1.3
|
||||||
|
|
||||||
[output_pin stepper_e_current]
|
[output_pin stepper_e_current]
|
||||||
pin: PL3
|
pin: PL3
|
||||||
@@ -105,7 +105,7 @@ pwm: True
|
|||||||
scale: 2.0
|
scale: 2.0
|
||||||
cycle_time: .000030
|
cycle_time: .000030
|
||||||
hardware_pwm: True
|
hardware_pwm: True
|
||||||
static_value: 1.25
|
value: 1.25
|
||||||
|
|
||||||
[display]
|
[display]
|
||||||
lcd_type: st7920
|
lcd_type: st7920
|
||||||
|
|||||||
@@ -126,7 +126,6 @@ restart_method: arduino
|
|||||||
kinematics: cartesian
|
kinematics: cartesian
|
||||||
max_velocity: 150
|
max_velocity: 150
|
||||||
max_accel: 3000
|
max_accel: 3000
|
||||||
max_accel_to_decel: 1500
|
|
||||||
max_z_velocity: 7
|
max_z_velocity: 7
|
||||||
max_z_accel: 50
|
max_z_accel: 50
|
||||||
square_corner_velocity: 5
|
square_corner_velocity: 5
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ cycle_time: 0.00005 #20kHz
|
|||||||
|
|
||||||
[output_pin enable_pin]
|
[output_pin enable_pin]
|
||||||
pin: PB6
|
pin: PB6
|
||||||
static_value: 1
|
value: 1
|
||||||
#This pin enables the bed, hotend, extruder fan, part fan.
|
#This pin enables the bed, hotend, extruder fan, part fan.
|
||||||
|
|
||||||
[mcu]
|
[mcu]
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
# To build the firmware, use the following configuration:
|
# To build the firmware, use the following configuration:
|
||||||
# - Micro-controller: Huada Semiconductor HC32F460
|
# - Micro-controller: Huada Semiconductor HC32F460
|
||||||
# - Communication interface: Serial (PA3 & PA2) - Anycube
|
# - Communication interface: Serial (PA3 & PA2) - Anycube
|
||||||
|
# - Clock Speed: 200 MHz
|
||||||
#
|
#
|
||||||
# Installation:
|
# Installation:
|
||||||
# 1. Rename the klipper bin to `firmware.bin` and copy it to an SD Card.
|
# 1. Rename the klipper bin to `firmware.bin` and copy it to an SD Card.
|
||||||
@@ -144,10 +145,9 @@ max_temp: 120
|
|||||||
pause_on_runout: True
|
pause_on_runout: True
|
||||||
switch_pin: !PC13
|
switch_pin: !PC13
|
||||||
|
|
||||||
[heater_fan controller_fan]
|
[controller_fan controller_fan]
|
||||||
pin: PA14
|
pin: PA14
|
||||||
heater: heater_bed
|
heater: heater_bed
|
||||||
heater_temp: 45.0
|
|
||||||
|
|
||||||
[heater_fan hotend_fan]
|
[heater_fan hotend_fan]
|
||||||
pin: PA13
|
pin: PA13
|
||||||
|
|||||||
126
config/printer-artillery-genius-pro-2022.cfg
Normal file
126
config/printer-artillery-genius-pro-2022.cfg
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
# This file contains pin mappings for the Artillery Genius Pro (2022)
|
||||||
|
# with a Artillery_Ruby-v1.2 board. To use this config, during "make menuconfig"
|
||||||
|
# select the STM32F401 with "No bootloader" and USB (on PA11/PA12)
|
||||||
|
# communication.
|
||||||
|
|
||||||
|
# To flash this firmware, set the physical bridge between +3.3V and Boot0 PIN
|
||||||
|
# on Artillery_Ruby mainboard. Then run the command:
|
||||||
|
# make flash FLASH_DEVICE=/dev/serial/by-id/usb-Klipper_stm32f401xc_*-if00
|
||||||
|
|
||||||
|
# See docs/Config_Reference.md for a description of parameters.
|
||||||
|
|
||||||
|
[extruder]
|
||||||
|
max_extrude_only_distance: 700.0
|
||||||
|
step_pin: PA7
|
||||||
|
dir_pin: PA6
|
||||||
|
enable_pin: !PC4
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 7.1910
|
||||||
|
nozzle_diameter: 0.400
|
||||||
|
filament_diameter: 1.750
|
||||||
|
heater_pin: PC9
|
||||||
|
sensor_type: EPCOS 100K B57560G104F
|
||||||
|
sensor_pin: PC0
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 250
|
||||||
|
control: pid
|
||||||
|
pid_Kp: 23.223
|
||||||
|
pid_Ki: 1.518
|
||||||
|
pid_Kd: 88.826
|
||||||
|
|
||||||
|
[stepper_x]
|
||||||
|
step_pin: !PB14
|
||||||
|
dir_pin: PB13
|
||||||
|
enable_pin: !PB15
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 40
|
||||||
|
endstop_pin: !PA2
|
||||||
|
position_endstop: 0
|
||||||
|
position_max: 220
|
||||||
|
homing_speed: 60
|
||||||
|
|
||||||
|
[stepper_y]
|
||||||
|
step_pin: PB10
|
||||||
|
dir_pin: PB2
|
||||||
|
enable_pin: !PB12
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 40
|
||||||
|
endstop_pin: !PA1
|
||||||
|
position_endstop: 0
|
||||||
|
position_max: 220
|
||||||
|
homing_speed: 60
|
||||||
|
|
||||||
|
[stepper_z]
|
||||||
|
step_pin: PB0
|
||||||
|
dir_pin: !PC5
|
||||||
|
enable_pin: !PB1
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 8
|
||||||
|
endstop_pin: probe:z_virtual_endstop
|
||||||
|
position_max: 250
|
||||||
|
position_min: -5
|
||||||
|
|
||||||
|
[heater_bed]
|
||||||
|
heater_pin: PA8
|
||||||
|
sensor_type: EPCOS 100K B57560G104F
|
||||||
|
sensor_pin: PC1
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 130
|
||||||
|
control: pid
|
||||||
|
pid_Kp: 23.223
|
||||||
|
pid_Ki: 1.518
|
||||||
|
pid_Kd: 88.826
|
||||||
|
|
||||||
|
[bed_screws]
|
||||||
|
screw1: 38,45
|
||||||
|
screw2: 180,45
|
||||||
|
screw3: 180,180
|
||||||
|
screw4: 38,180
|
||||||
|
|
||||||
|
[fan]
|
||||||
|
pin: PC8
|
||||||
|
off_below: 0.1
|
||||||
|
|
||||||
|
[heater_fan hotend_fan]
|
||||||
|
pin: PC7
|
||||||
|
heater: extruder
|
||||||
|
heater_temp: 50.0
|
||||||
|
|
||||||
|
[controller_fan stepper_fan]
|
||||||
|
pin: PC6
|
||||||
|
idle_timeout: 300
|
||||||
|
|
||||||
|
[mcu]
|
||||||
|
serial: /dev/serial/by-id/usb-Klipper_stm32f401xc_
|
||||||
|
|
||||||
|
[printer]
|
||||||
|
kinematics: cartesian
|
||||||
|
max_velocity: 500
|
||||||
|
max_accel: 4000
|
||||||
|
max_z_velocity: 50
|
||||||
|
square_corner_velocity: 5.0
|
||||||
|
max_z_accel: 100
|
||||||
|
|
||||||
|
[bltouch]
|
||||||
|
sensor_pin: PC2
|
||||||
|
control_pin: PC3
|
||||||
|
x_offset:27.25
|
||||||
|
y_offset:-12.8
|
||||||
|
z_offset: 0.25
|
||||||
|
speed:10
|
||||||
|
samples:1
|
||||||
|
samples_result:average
|
||||||
|
|
||||||
|
[bed_mesh]
|
||||||
|
speed: 800
|
||||||
|
mesh_min: 30, 20
|
||||||
|
mesh_max: 210, 200
|
||||||
|
probe_count: 5,5
|
||||||
|
algorithm: bicubic
|
||||||
|
move_check_distance: 3.0
|
||||||
|
|
||||||
|
[safe_z_home]
|
||||||
|
home_xy_position: 110,110
|
||||||
|
speed: 100
|
||||||
|
z_hop: 10
|
||||||
|
z_hop_speed: 5
|
||||||
188
config/printer-artillery-sidewinder-x3-plus-2024.cfg
Normal file
188
config/printer-artillery-sidewinder-x3-plus-2024.cfg
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
# For the Artillery Sidewinder X3 Pro/Plus that came factory installed with V1.29 firmware, follow these steps.
|
||||||
|
# - Compile with the processor model STM32F401.
|
||||||
|
# - Select the 48KiB bootloader,
|
||||||
|
# - Select USB PA11/PA12 for USB communication interface.
|
||||||
|
# - Select USART2 PA3/PA2 for UART communication via the Wi-Fi Tx/Rx pins
|
||||||
|
# To set 48KiB bootloader, you need to make a change to make menuconfig Kconfig file
|
||||||
|
# Here is a link to a how-to video: https://youtu.be/dpc76zN7Dh0
|
||||||
|
# Rename klipper.bin to yuntu.bin
|
||||||
|
# Copy the file out/yuntu.bin to an SD card and then restart the printer with that SD card
|
||||||
|
#
|
||||||
|
# For models that did not come with V1.29 installed
|
||||||
|
# - Compile with the processor model STM32F401.
|
||||||
|
# - Select the NO BOOTLOADER
|
||||||
|
# - Select USB PA11/PA12 for USB communication interface.
|
||||||
|
# - Select USART2 PA3/PA2 for UART communication via the Wi-Fi Tx/Rx pins
|
||||||
|
# - quit, save, make
|
||||||
|
# - Connect your printer to a computer running Pronterface, Octoprint, Repetier, BedLeveler5000 (anything with Console capability)
|
||||||
|
# - Power on the machine and send M997 through console into Marlin, this will put the board into "DFU" mode
|
||||||
|
# - DO NOT TURN OFF THE PRINTER
|
||||||
|
# - Connect your Linux/Klipper device to the USB port
|
||||||
|
# - Run lsusb and verify that the STM32 DFU device is visible (Bus 001 Device 006: ID 0483:df11 STMicroelectronics STM Device in DFU Mode)
|
||||||
|
# - Run sudo make flash 0483:df11
|
||||||
|
# - Run lsusb again and there should be two devices:
|
||||||
|
# Bus 001 Device 007: ID 1d50:614e OpenMoko, Inc. stm32f401xc
|
||||||
|
# Bus 001 Device 003: ID 0cf3:e010 Qualcomm Atheros Communications stm32f401xc
|
||||||
|
# See docs/Config_Reference.md for a description of parameters.
|
||||||
|
|
||||||
|
[mcu]
|
||||||
|
serial: /dev/ttyACM0
|
||||||
|
restart_method: command
|
||||||
|
|
||||||
|
[printer]
|
||||||
|
kinematics: cartesian
|
||||||
|
max_velocity: 300
|
||||||
|
max_accel: 3000
|
||||||
|
max_z_velocity: 15
|
||||||
|
max_z_accel: 100
|
||||||
|
square_corner_velocity: 5
|
||||||
|
|
||||||
|
[led LED_Light]
|
||||||
|
white_pin: PC2
|
||||||
|
initial_white: 1.0
|
||||||
|
|
||||||
|
[neopixel hotend_neopixel]
|
||||||
|
pin: PD2
|
||||||
|
color_order: GRB
|
||||||
|
initial_RED: 1.0
|
||||||
|
initial_GREEN: 1.0
|
||||||
|
initial_BLUE: 1.0
|
||||||
|
|
||||||
|
[stepper_x]
|
||||||
|
step_pin: PA8
|
||||||
|
dir_pin: PC9
|
||||||
|
enable_pin: !PA15
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 40
|
||||||
|
endstop_pin: !PB9
|
||||||
|
position_min: 0
|
||||||
|
position_endstop: 0
|
||||||
|
position_max: 315
|
||||||
|
homing_speed: 50
|
||||||
|
|
||||||
|
[stepper_y]
|
||||||
|
step_pin: PC7
|
||||||
|
dir_pin: !PC6
|
||||||
|
enable_pin: !PC8
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 40
|
||||||
|
endstop_pin: !PB8
|
||||||
|
position_endstop: 0
|
||||||
|
position_max: 315
|
||||||
|
homing_speed: 50
|
||||||
|
|
||||||
|
[stepper_z]
|
||||||
|
step_pin: PB10
|
||||||
|
dir_pin: !PA4
|
||||||
|
enable_pin: !PC4
|
||||||
|
rotation_distance: 8
|
||||||
|
microsteps: 16
|
||||||
|
position_min: -1
|
||||||
|
position_max: 400
|
||||||
|
endstop_pin: probe:z_virtual_endstop # Use Z- as endstop
|
||||||
|
#homing_speed: 10.0
|
||||||
|
|
||||||
|
[extruder]
|
||||||
|
max_extrude_only_distance: 100.0
|
||||||
|
step_pin: PC11
|
||||||
|
dir_pin: !PC10
|
||||||
|
enable_pin: !PC12
|
||||||
|
microsteps: 64
|
||||||
|
nozzle_diameter: 0.400
|
||||||
|
filament_diameter: 1.750
|
||||||
|
heater_pin: PA6
|
||||||
|
sensor_type: EPCOS 100K B57560G104F #Generic 3950
|
||||||
|
sensor_pin: PC5
|
||||||
|
min_extrude_temp: 170
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 300
|
||||||
|
# Calibrate E-Steps https://www.klipper3d.org/Rotation_Distance.html#calibrating-rotation_distance-on-extruders
|
||||||
|
rotation_distance: 17.75
|
||||||
|
# Calibrate PID: https://www.klipper3d.org/Config_checks.html#calibrate-pid-settings
|
||||||
|
# - Example: PID_CALIBRATE HEATER=extruder TARGET=200
|
||||||
|
control: pid
|
||||||
|
pid_kp: 30.356
|
||||||
|
pid_ki: 1.857
|
||||||
|
pid_kd: 124.081
|
||||||
|
# Calibrate PA: https://www.klipper3d.org/Pressure_Advance.html
|
||||||
|
|
||||||
|
[heater_bed]
|
||||||
|
heater_pin: PA7
|
||||||
|
sensor_type: EPCOS 100K B57560G104F
|
||||||
|
sensor_pin: PC0
|
||||||
|
max_temp: 100
|
||||||
|
min_temp: 0
|
||||||
|
# Calibrate PID: https://www.klipper3d.org/Config_checks.html#calibrate-pid-settings
|
||||||
|
# - Example: PID_CALIBRATE HEATER=heater_bed TARGET=60
|
||||||
|
control: pid
|
||||||
|
pid_kp: 64.230
|
||||||
|
pid_ki: 0.723
|
||||||
|
pid_kd: 1425.905
|
||||||
|
|
||||||
|
[heater_fan hotend_fan]
|
||||||
|
pin: PB1
|
||||||
|
heater: extruder
|
||||||
|
heater_temp: 50.0
|
||||||
|
|
||||||
|
[fan]
|
||||||
|
pin: PB0
|
||||||
|
|
||||||
|
[temperature_fan Artillery_MCU]
|
||||||
|
sensor_type: temperature_mcu
|
||||||
|
pin: PA5
|
||||||
|
max_temp: 60.0
|
||||||
|
target_temp: 40.0
|
||||||
|
min_temp: 0
|
||||||
|
shutdown_speed: 0.0
|
||||||
|
kick_start_time: 0.5
|
||||||
|
off_below: 0.19
|
||||||
|
max_speed: 1.0
|
||||||
|
min_speed: 0.0
|
||||||
|
control: watermark
|
||||||
|
|
||||||
|
[filament_switch_sensor filament_sensor]
|
||||||
|
pause_on_runout: true
|
||||||
|
switch_pin: PC1
|
||||||
|
|
||||||
|
[probe]
|
||||||
|
pin: PC14
|
||||||
|
x_offset:45.2
|
||||||
|
y_offset:11.6
|
||||||
|
speed:5
|
||||||
|
lift_speed:15
|
||||||
|
z_offset: 2.350
|
||||||
|
|
||||||
|
[safe_z_home]
|
||||||
|
home_xy_position: 110, 145 # X, Y coordinate (e.g. 100, 100) where the Z homing should be
|
||||||
|
speed: 300.0
|
||||||
|
z_hop: 10
|
||||||
|
z_hop_speed: 15.0
|
||||||
|
|
||||||
|
[bed_mesh]
|
||||||
|
speed: 300
|
||||||
|
horizontal_move_z: 6
|
||||||
|
mesh_min: 46,15
|
||||||
|
mesh_max: 300,300
|
||||||
|
probe_count: 10, 10
|
||||||
|
fade_start: 1.0
|
||||||
|
fade_end: 0.0
|
||||||
|
algorithm: bicubic
|
||||||
|
|
||||||
|
[screws_tilt_adjust]
|
||||||
|
screw1: 120, 153
|
||||||
|
screw1_name: center reference
|
||||||
|
screw2: 7, 45
|
||||||
|
screw2_name: front left
|
||||||
|
screw3: 210, 45
|
||||||
|
screw3_name: front right
|
||||||
|
screw4: 227, 145
|
||||||
|
screw4_name: right center
|
||||||
|
screw5: 210, 245
|
||||||
|
screw5_name: rear right
|
||||||
|
screw6: 7, 245
|
||||||
|
screw6_name: rear left
|
||||||
|
screw7: 7, 145
|
||||||
|
screw7_name: left center
|
||||||
|
horizontal_move_z: 8
|
||||||
|
speed: 300
|
||||||
|
screw_thread: CW-M4
|
||||||
@@ -98,7 +98,6 @@ max_temp: 100
|
|||||||
|
|
||||||
[output_pin led]
|
[output_pin led]
|
||||||
pin: PC14
|
pin: PC14
|
||||||
static_value: 0
|
|
||||||
|
|
||||||
# Neopixel LED support
|
# Neopixel LED support
|
||||||
# [neopixel led_neopixel]
|
# [neopixel led_neopixel]
|
||||||
|
|||||||
@@ -98,6 +98,10 @@ z_offset: 0.0
|
|||||||
speed: 2.0
|
speed: 2.0
|
||||||
samples: 5
|
samples: 5
|
||||||
|
|
||||||
|
[safe_z_home]
|
||||||
|
home_xy_position: 117, 117
|
||||||
|
z_hop: 10
|
||||||
|
|
||||||
[filament_switch_sensor filament_sensor]
|
[filament_switch_sensor filament_sensor]
|
||||||
pause_on_runout: true
|
pause_on_runout: true
|
||||||
switch_pin: ^!PA7
|
switch_pin: ^!PA7
|
||||||
|
|||||||
@@ -98,6 +98,10 @@ z_offset: 0.0
|
|||||||
speed: 2.0
|
speed: 2.0
|
||||||
samples: 5
|
samples: 5
|
||||||
|
|
||||||
|
[safe_z_home]
|
||||||
|
home_xy_position: 117, 117
|
||||||
|
z_hop: 10
|
||||||
|
|
||||||
[filament_switch_sensor filament_sensor]
|
[filament_switch_sensor filament_sensor]
|
||||||
pause_on_runout: true
|
pause_on_runout: true
|
||||||
switch_pin: ^!PA7
|
switch_pin: ^!PA7
|
||||||
|
|||||||
170
config/printer-creality-ender5-s1-2023.cfg
Normal file
170
config/printer-creality-ender5-s1-2023.cfg
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
# Creality Ender 5 S1 (HW version: CR4NS200141C13)
|
||||||
|
#
|
||||||
|
# printer_size: 220x220x280
|
||||||
|
# To use this config, during "make menuconfig" select the STM32F401
|
||||||
|
# with a "64KiB bootloader" and serial (on USART1 PA10/PA9)
|
||||||
|
# communication.
|
||||||
|
#
|
||||||
|
# Flash this firmware by creating a directory named "STM32F4_UPDATE"
|
||||||
|
# on an SD card, copying the "out/klipper.bin" to it and then turn
|
||||||
|
# on the printer with the card inserted. The firmware filename must
|
||||||
|
# end in ".bin" and must not match the last filename that was flashed.
|
||||||
|
#
|
||||||
|
# See docs/Config_Reference.md for a description of parameters.
|
||||||
|
|
||||||
|
[stepper_x]
|
||||||
|
step_pin: PC2
|
||||||
|
dir_pin: !PB9
|
||||||
|
enable_pin: !PC3
|
||||||
|
rotation_distance: 40
|
||||||
|
microsteps: 16
|
||||||
|
endstop_pin: !PA5
|
||||||
|
position_endstop: 220
|
||||||
|
position_max: 222
|
||||||
|
homing_speed: 80
|
||||||
|
|
||||||
|
[stepper_y]
|
||||||
|
step_pin: PB8
|
||||||
|
dir_pin: !PB7
|
||||||
|
enable_pin: !PC3
|
||||||
|
rotation_distance: 40
|
||||||
|
microsteps: 16
|
||||||
|
endstop_pin: !PA6
|
||||||
|
position_endstop: 220
|
||||||
|
position_max: 220
|
||||||
|
homing_speed: 80
|
||||||
|
|
||||||
|
[stepper_z]
|
||||||
|
step_pin: PB6
|
||||||
|
dir_pin: PB5
|
||||||
|
enable_pin: !PC3
|
||||||
|
rotation_distance: 8
|
||||||
|
microsteps: 16
|
||||||
|
endstop_pin: probe:z_virtual_endstop
|
||||||
|
position_max: 280
|
||||||
|
homing_speed: 20
|
||||||
|
second_homing_speed: 1
|
||||||
|
homing_retract_dist: 2.0
|
||||||
|
|
||||||
|
[extruder]
|
||||||
|
step_pin: PB4
|
||||||
|
dir_pin: PB3
|
||||||
|
enable_pin: !PC3
|
||||||
|
rotation_distance: 7.5
|
||||||
|
microsteps: 16
|
||||||
|
nozzle_diameter: 0.400
|
||||||
|
filament_diameter: 1.750
|
||||||
|
heater_pin: PA1
|
||||||
|
sensor_type: EPCOS 100K B57560G104F
|
||||||
|
sensor_pin: PC5
|
||||||
|
control: pid # tuned for stock hardware with 210 degree Celsius target
|
||||||
|
pid_kp: 20.749
|
||||||
|
pid_ki: 1.064
|
||||||
|
pid_kd: 101.153
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 305
|
||||||
|
|
||||||
|
[heater_bed]
|
||||||
|
heater_pin: PA7
|
||||||
|
sensor_type: EPCOS 100K B57560G104F
|
||||||
|
sensor_pin: PC4
|
||||||
|
control: pid # tuned for stock hardware with 60 degree Celsius target
|
||||||
|
pid_kp: 66.566
|
||||||
|
pid_ki: 0.958
|
||||||
|
pid_kd: 1155.761
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 110
|
||||||
|
|
||||||
|
# Part cooling fan
|
||||||
|
[fan]
|
||||||
|
pin: PA0
|
||||||
|
kick_start_time: 0.5
|
||||||
|
|
||||||
|
# Hotend fan
|
||||||
|
# set fan runnig when extruder temperature is over 60
|
||||||
|
[heater_fan heatbreak_fan]
|
||||||
|
pin: PC0
|
||||||
|
heater:extruder
|
||||||
|
heater_temp: 60
|
||||||
|
fan_speed: 0.8
|
||||||
|
|
||||||
|
[filament_switch_sensor filament_sensor]
|
||||||
|
pause_on_runout: true
|
||||||
|
switch_pin: ^!PC15
|
||||||
|
|
||||||
|
# Stock CR Touch bed sensor
|
||||||
|
[bltouch]
|
||||||
|
sensor_pin: ^PC14
|
||||||
|
control_pin: PC13
|
||||||
|
x_offset: -13
|
||||||
|
y_offset: 27
|
||||||
|
z_offset: 2.0
|
||||||
|
speed: 10
|
||||||
|
stow_on_each_sample: true # Occasional bed crashes when false
|
||||||
|
samples: 4
|
||||||
|
sample_retract_dist: 2
|
||||||
|
samples_result: average
|
||||||
|
probe_with_touch_mode: true
|
||||||
|
|
||||||
|
[bed_mesh]
|
||||||
|
speed: 150
|
||||||
|
mesh_min: 3,28 # need to handle head distance with bl_touch
|
||||||
|
mesh_max: 205,218
|
||||||
|
mesh_pps: 3
|
||||||
|
probe_count: 4,4
|
||||||
|
fade_start: 1
|
||||||
|
fade_end: 10
|
||||||
|
fade_target: 0
|
||||||
|
|
||||||
|
[mcu]
|
||||||
|
serial: /dev/serial/by-id/usb-1a86_USB_Serial-if00-port0
|
||||||
|
restart_method: command
|
||||||
|
|
||||||
|
[safe_z_home]
|
||||||
|
home_xy_position: 123,83
|
||||||
|
speed: 200
|
||||||
|
z_hop: 10
|
||||||
|
z_hop_speed: 10
|
||||||
|
|
||||||
|
# Many Ender 5 S1 printers appear to suffer from a slight twist
|
||||||
|
# in the X axis. This twist can be measured, and compensated for
|
||||||
|
# using the AXIS_TWIST_COMPENSATION_CALIBRATE G-Code command. See
|
||||||
|
# https://www.klipper3d.org/Axis_Twist_Compensation.html for more
|
||||||
|
# information. This section provides the setup for this optional
|
||||||
|
# calibration step.
|
||||||
|
[axis_twist_compensation]
|
||||||
|
calibrate_start_x: 3
|
||||||
|
calibrate_end_x: 207
|
||||||
|
calibrate_y: 110
|
||||||
|
|
||||||
|
# Probe locations for assisted bed screw adjustment.
|
||||||
|
[screws_tilt_adjust]
|
||||||
|
screw1: 38,6
|
||||||
|
screw1_name: Front Left Screw
|
||||||
|
screw2: 215,6
|
||||||
|
screw2_name: Front Right Screw
|
||||||
|
screw3: 215,175
|
||||||
|
screw3_name: Rear Right Screw
|
||||||
|
screw4: 38,175
|
||||||
|
screw4_name: Rear Left Screw
|
||||||
|
horizontal_move_z: 5
|
||||||
|
speed: 100
|
||||||
|
screw_thread: CW-M4
|
||||||
|
|
||||||
|
[bed_screws]
|
||||||
|
screw1: 25,25
|
||||||
|
screw1_name: Front Left Screw
|
||||||
|
screw2: 195,25
|
||||||
|
screw2_name: Front Right Screw
|
||||||
|
screw3: 195,195
|
||||||
|
screw3_name: Rear Right Screw
|
||||||
|
screw4: 25,195
|
||||||
|
screw4_name: Rear Left Screw
|
||||||
|
|
||||||
|
[printer]
|
||||||
|
kinematics: cartesian
|
||||||
|
max_velocity: 300
|
||||||
|
max_accel: 5000
|
||||||
|
max_z_velocity: 5
|
||||||
|
max_z_accel: 100
|
||||||
|
square_corner_velocity: 5.0
|
||||||
@@ -71,25 +71,21 @@ pid_Kp: 39
|
|||||||
pid_Ki: 2
|
pid_Ki: 2
|
||||||
pid_Kd: 210
|
pid_Kd: 210
|
||||||
|
|
||||||
[extruder1]
|
[extruder_stepper e1]
|
||||||
|
extruder:
|
||||||
step_pin: PA0
|
step_pin: PA0
|
||||||
dir_pin: !PB6
|
dir_pin: !PB6
|
||||||
enable_pin: !PA1
|
enable_pin: !PA1
|
||||||
microsteps: 16
|
microsteps: 16
|
||||||
rotation_distance: 32
|
rotation_distance: 32
|
||||||
nozzle_diameter: 0.4
|
|
||||||
filament_diameter: 1.75
|
|
||||||
shared_heater: extruder
|
|
||||||
|
|
||||||
[extruder2]
|
[extruder_stepper e2]
|
||||||
|
extruder:
|
||||||
step_pin: PB2
|
step_pin: PB2
|
||||||
dir_pin: !PB11
|
dir_pin: !PB11
|
||||||
enable_pin: !PC4
|
enable_pin: !PC4
|
||||||
microsteps: 16
|
microsteps: 16
|
||||||
rotation_distance: 32
|
rotation_distance: 32
|
||||||
nozzle_diameter: 0.4
|
|
||||||
filament_diameter: 1.75
|
|
||||||
shared_heater: extruder
|
|
||||||
|
|
||||||
[heater_bed]
|
[heater_bed]
|
||||||
heater_pin: PB1
|
heater_pin: PB1
|
||||||
|
|||||||
256
config/printer-geeetech-A10T-A20T-2021.cfg
Normal file
256
config/printer-geeetech-A10T-A20T-2021.cfg
Normal file
@@ -0,0 +1,256 @@
|
|||||||
|
# This file contains common pin mappings for the Geeetech GT2560 v4.0 and v4.1b
|
||||||
|
# boards. These boards use a firmware compiled for the AVR atmega2560.
|
||||||
|
# For default Geeetech A10/A20 (1 extruder),
|
||||||
|
# A10M/A20M (mixing 2 in 1 out),
|
||||||
|
# A10T/A20T (mixing 3 in 1 out) printers
|
||||||
|
# Installation: https://www.klipper3d.org/Installation.html
|
||||||
|
# Always read for first start: https://www.klipper3d.org/Config_checks.html
|
||||||
|
|
||||||
|
[mcu]
|
||||||
|
# Might need to be changed: https://www.klipper3d.org/Installation.html
|
||||||
|
serial: /dev/serial/by-id/usb-1a86_USB_Serial-if00-port0
|
||||||
|
|
||||||
|
[printer]
|
||||||
|
kinematics: cartesian
|
||||||
|
max_velocity: 200
|
||||||
|
max_accel: 1500
|
||||||
|
max_z_velocity: 20
|
||||||
|
max_z_accel: 500
|
||||||
|
|
||||||
|
# # uncomment for BLTouch/3DTouch
|
||||||
|
# [bltouch]
|
||||||
|
# sensor_pin: PC7 # there is an external pull up so no need in ^
|
||||||
|
# control_pin: PB5
|
||||||
|
# speed: 3.0
|
||||||
|
# samples: 2
|
||||||
|
# x_offset: -42.0
|
||||||
|
# y_offset: -1.0
|
||||||
|
# z_offset: 1.0 # during calibration this line is commented out and new record added at the end of file
|
||||||
|
|
||||||
|
[safe_z_home]
|
||||||
|
home_xy_position: 100, 100 # Change coordinates to the center of your print bed
|
||||||
|
speed: 50
|
||||||
|
z_hop: 10 # Move up 10mm
|
||||||
|
z_hop_speed: 5
|
||||||
|
|
||||||
|
[stepper_x]
|
||||||
|
enable_pin: !PC2
|
||||||
|
dir_pin: !PG2
|
||||||
|
step_pin: PC0
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 40
|
||||||
|
endstop_pin: !PA2 # there are external pull ups
|
||||||
|
position_endstop: 0
|
||||||
|
position_max: 220 # for A10/M/T / change to 250 for A20/M/T
|
||||||
|
homing_speed: 40
|
||||||
|
|
||||||
|
[stepper_y]
|
||||||
|
enable_pin: !PA7
|
||||||
|
dir_pin: !PC4
|
||||||
|
step_pin: PC6
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 40
|
||||||
|
endstop_pin: !PA6 # there are external pull ups
|
||||||
|
position_endstop: 0
|
||||||
|
position_max: 220 # for A10/M/T / change to 250 for A20/M/T
|
||||||
|
homing_speed: 40
|
||||||
|
|
||||||
|
[stepper_z]
|
||||||
|
enable_pin: !PA5
|
||||||
|
dir_pin: PA1
|
||||||
|
step_pin: PA3
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 8
|
||||||
|
#endstop_pin: probe:z_virtual_endstop # uncomment for BLTouch/3DTouch
|
||||||
|
endstop_pin: !PC7 # comment for BLTouch/3DTouch
|
||||||
|
position_endstop: 0 # comment for BLTouch/3DTouch
|
||||||
|
position_max: 230 # for A10/M/T / change to 250 for A20/M/T
|
||||||
|
position_min: -5
|
||||||
|
homing_speed: 20
|
||||||
|
|
||||||
|
[extruder]
|
||||||
|
enable_pin: !PB6
|
||||||
|
dir_pin: PL5
|
||||||
|
step_pin: PL3
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 8 # Needs to be optimized: https://www.klipper3d.org/Rotation_Distance.html#calibrating-rotation_distance-on-extruders
|
||||||
|
nozzle_diameter: 0.4
|
||||||
|
filament_diameter: 1.750
|
||||||
|
heater_pin: PB4
|
||||||
|
sensor_type: EPCOS 100K B57560G104F
|
||||||
|
sensor_pin: PK3
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 250
|
||||||
|
max_extrude_only_distance: 200.0
|
||||||
|
# Parameters for stock hotend on A10M
|
||||||
|
# Please recalibrate according to https://www.klipper3d.org/Config_checks.html#calibrate-pid-settings
|
||||||
|
control: pid
|
||||||
|
pid_kp: 54.722
|
||||||
|
pid_ki: 4.800
|
||||||
|
pid_kd: 155.958
|
||||||
|
|
||||||
|
[extruder_stepper extruder_1]
|
||||||
|
extruder:
|
||||||
|
enable_pin: !PL1
|
||||||
|
dir_pin: PL2
|
||||||
|
step_pin: PL0
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 8 # Needs to be optimized: https://www.klipper3d.org/Rotation_Distance.html#calibrating-rotation_distance-on-extruders
|
||||||
|
|
||||||
|
[extruder_stepper extruder_2]
|
||||||
|
extruder:
|
||||||
|
enable_pin: !PG0
|
||||||
|
dir_pin: PL4
|
||||||
|
step_pin: PL6
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 8 # Needs to be optimized: https://www.klipper3d.org/Rotation_Distance.html#calibrating-rotation_distance-on-extruders
|
||||||
|
|
||||||
|
[heater_bed]
|
||||||
|
heater_pin: PG5
|
||||||
|
sensor_type: EPCOS 100K B57560G104F
|
||||||
|
sensor_pin: PK2
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 120
|
||||||
|
# Parameters for `SuperPlate` on A10M
|
||||||
|
# Please recalibrate according to https://www.klipper3d.org/Config_checks.html#calibrate-pid-settings
|
||||||
|
control: pid
|
||||||
|
pid_kp: 70.936
|
||||||
|
pid_ki: 1.785
|
||||||
|
pid_kd: 704.924
|
||||||
|
|
||||||
|
[fan]
|
||||||
|
pin: PH6
|
||||||
|
cycle_time: 0.150
|
||||||
|
kick_start_time: 0.300
|
||||||
|
|
||||||
|
# # for GT2560V4.0 with 20pin flat cable toward the display
|
||||||
|
# [display]
|
||||||
|
# lcd_type: hd44780
|
||||||
|
# hd44780_protocol_init: True
|
||||||
|
# rs_pin: PD1
|
||||||
|
# e_pin: PH0
|
||||||
|
# d4_pin: PH1
|
||||||
|
# d5_pin: PD0
|
||||||
|
# d6_pin: PE3
|
||||||
|
# d7_pin: PC1
|
||||||
|
# encoder_pins: ^PG1, ^PL7
|
||||||
|
# click_pin: ^!PD2
|
||||||
|
|
||||||
|
|
||||||
|
# for GT2560V4.1B with 12pin flat cable toward the display YHCB2004-06 ver3.0
|
||||||
|
# the aip31068_spi driver was added to Klipper on 2024-12-02, commit aecb29d2
|
||||||
|
[display]
|
||||||
|
lcd_type: aip31068_spi
|
||||||
|
latch_pin: PE3
|
||||||
|
spi_software_sclk_pin: PD0
|
||||||
|
spi_software_mosi_pin: PC1
|
||||||
|
spi_software_miso_pin: PH7 # any unused pin
|
||||||
|
encoder_pins: ^PH0, ^PH1
|
||||||
|
click_pin: ^!PD2
|
||||||
|
|
||||||
|
|
||||||
|
[filament_switch_sensor sensor_e0]
|
||||||
|
switch_pin: !PK4
|
||||||
|
|
||||||
|
[filament_switch_sensor sensor_e1]
|
||||||
|
switch_pin: !PK5
|
||||||
|
|
||||||
|
[filament_switch_sensor sensor_e2]
|
||||||
|
# switch_pin: !PE2 # for GT2560V4.0
|
||||||
|
switch_pin: !PF0 # for GT2560V4.1B
|
||||||
|
|
||||||
|
# to enable M118 echo command
|
||||||
|
[respond]
|
||||||
|
|
||||||
|
# Specific macros for mixing colors.
|
||||||
|
# Add in slicer new filament color and in filament start G-Code add desired mixing factor:
|
||||||
|
# M163 S0 P50 ; set extruder 0 to 50%
|
||||||
|
# M163 S1 P40 ; set extruder 1 to 40%
|
||||||
|
# M163 S2 P10 ; set extruder 2 to 10%
|
||||||
|
# M164 ; commit the mix factors
|
||||||
|
[gcode_macro M163]
|
||||||
|
description: M163 [P<factor>] [S<index>] Set a single mix factor (in proportion to the sum total of all mix factors). The mix must be committed to a virtual tool by M164 before it takes effect.
|
||||||
|
gcode:
|
||||||
|
{% if 'P' in params %}
|
||||||
|
{% set s = params.S|default(0)| int %}
|
||||||
|
{% if s == 0 %}
|
||||||
|
SET_GCODE_VARIABLE MACRO=M164 VARIABLE=e0_parts VALUE={params.P|default(0)|float}
|
||||||
|
M118 Set Mixing factor for extruder 0 to {params.P|default(0)|float}
|
||||||
|
{% elif s == 1 %}
|
||||||
|
SET_GCODE_VARIABLE MACRO=M164 VARIABLE=e1_parts VALUE={params.P|default(0)|float}
|
||||||
|
M118 Set Mixing factor for extruder 1 to {params.P|default(0)|float}
|
||||||
|
{% elif s == 2 %}
|
||||||
|
SET_GCODE_VARIABLE MACRO=M164 VARIABLE=e2_parts VALUE={params.P|default(0)|float}
|
||||||
|
M118 Set Mixing factor for extruder 2 to {params.P|default(0)|float}
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
M118 No Mixing factor set, missing value for P
|
||||||
|
{% endif %}
|
||||||
|
M118 {e0_parts} {e1_parts} {e2_parts}
|
||||||
|
|
||||||
|
|
||||||
|
[gcode_macro M164]
|
||||||
|
description: Applies the set mixing factors to the extruders
|
||||||
|
# default values:
|
||||||
|
variable_e0_parts : 100
|
||||||
|
variable_e1_parts : 0
|
||||||
|
variable_e2_parts : 0
|
||||||
|
gcode:
|
||||||
|
# normalize the parts to sum of 1
|
||||||
|
{% set e0 = e0_parts / (e0_parts + e1_parts + e2_parts) | float %}
|
||||||
|
{% set e1 = e1_parts / (e0_parts + e1_parts + e2_parts) | float %}
|
||||||
|
{% set e2 = e2_parts / (e0_parts + e1_parts + e2_parts) | float %}
|
||||||
|
M118 scaled rot-dist_e0 { printer.configfile.settings.extruder.rotation_distance / (e0 + 0.000001) | float }
|
||||||
|
M118 scaled rot-dist_e1 { printer.configfile.settings['extruder_stepper extruder_1'].rotation_distance / (e1 + 0.000001) | float }
|
||||||
|
M118 scaled rot-dist_e2 { printer.configfile.settings['extruder_stepper extruder_2'].rotation_distance / (e2 + 0.000001) |float }
|
||||||
|
# activate stepper percentages
|
||||||
|
SYNC_EXTRUDER_MOTION EXTRUDER=extruder MOTION_QUEUE=extruder
|
||||||
|
SYNC_EXTRUDER_MOTION EXTRUDER=extruder_1 MOTION_QUEUE=extruder
|
||||||
|
SYNC_EXTRUDER_MOTION EXTRUDER=extruder_2 MOTION_QUEUE=extruder
|
||||||
|
SET_EXTRUDER_ROTATION_DISTANCE EXTRUDER=extruder DISTANCE={ printer.configfile.settings.extruder.rotation_distance / (e0+0.000001)|float }
|
||||||
|
SET_EXTRUDER_ROTATION_DISTANCE EXTRUDER=extruder_1 DISTANCE={ printer.configfile.settings['extruder_stepper extruder_1'].rotation_distance / (e1+0.000001)|float }
|
||||||
|
SET_EXTRUDER_ROTATION_DISTANCE EXTRUDER=extruder_2 DISTANCE={ printer.configfile.settings['extruder_stepper extruder_2'].rotation_distance / (e2+0.000001)|float }
|
||||||
|
M118 Mixing factors {e0} {e1} {e2} are activated
|
||||||
|
|
||||||
|
# In PrusaSlicer:
|
||||||
|
# - you can add as many extruders as mixing ratios you want
|
||||||
|
# - in Printer Settings -> Custom G-code -> Tool change G-code add:
|
||||||
|
# TOOL_CHANGE EXTRUDER={next_extruder}
|
||||||
|
# - in this config file add:
|
||||||
|
# [gcode_macro TOOL_CHANGE]
|
||||||
|
# description: Tool change macro with mix ratio setup for 11 extruders
|
||||||
|
# variable_extruder: 0
|
||||||
|
# gcode:
|
||||||
|
# {% set extruder = params.EXTRUDER|default(0)| int %}
|
||||||
|
# {% if extruder == 0 %}
|
||||||
|
# M163 S0 P100
|
||||||
|
# M163 S1 P0
|
||||||
|
# M163 S2 P0
|
||||||
|
# M164
|
||||||
|
# M118 Switching to Extruder 0
|
||||||
|
# {% elif extruder == 1 %}
|
||||||
|
# M163 S0 P90
|
||||||
|
# M163 S1 P10
|
||||||
|
# M163 S2P0
|
||||||
|
# M164
|
||||||
|
# M118 Switching to Extruder 1
|
||||||
|
# {% elif extruder == 2 %}
|
||||||
|
# # and so on ...
|
||||||
|
# {% else %}
|
||||||
|
# M118 Unknown extruder number: {extruder}
|
||||||
|
# {% endif %}
|
||||||
|
|
||||||
|
# In OrcaSlicer:
|
||||||
|
# you can add as many filaments as mixing ratios you want
|
||||||
|
# in Material settings -> Advanced -> Filament start G-code add desired mixing ratio:
|
||||||
|
# ; filament start gcode
|
||||||
|
# M163 S0 P100 ; set extruder 0
|
||||||
|
# M163 S1 P0 ; set extruder 1
|
||||||
|
# M163 S2 P0 ; set extruder 2
|
||||||
|
# M164 ; commit the mix factors
|
||||||
|
|
||||||
|
# For gradient over Z axis:
|
||||||
|
# In `Printer -> Custom G-code -> After layer change G-code` add:
|
||||||
|
# M163 S0 P{ layer_num * 100 / total_layer_count } ; Gradient 0-100
|
||||||
|
# M163 S1 P{(total_layer_count-layer_num) * 100 / total_layer_count} ; Gradient 100-0
|
||||||
|
# M164 ; commit the mix factors
|
||||||
@@ -125,7 +125,7 @@ pwm: True
|
|||||||
scale: 2.0
|
scale: 2.0
|
||||||
cycle_time: .000030
|
cycle_time: .000030
|
||||||
hardware_pwm: True
|
hardware_pwm: True
|
||||||
static_value: 1.300
|
value: 1.300
|
||||||
|
|
||||||
[output_pin stepper_z_current]
|
[output_pin stepper_z_current]
|
||||||
pin: PL4
|
pin: PL4
|
||||||
@@ -133,7 +133,7 @@ pwm: True
|
|||||||
scale: 2.0
|
scale: 2.0
|
||||||
cycle_time: .000030
|
cycle_time: .000030
|
||||||
hardware_pwm: True
|
hardware_pwm: True
|
||||||
static_value: 1.630
|
value: 1.630
|
||||||
|
|
||||||
[output_pin stepper_e_current]
|
[output_pin stepper_e_current]
|
||||||
pin: PL5
|
pin: PL5
|
||||||
@@ -141,7 +141,7 @@ pwm: True
|
|||||||
scale: 2.0
|
scale: 2.0
|
||||||
cycle_time: .000030
|
cycle_time: .000030
|
||||||
hardware_pwm: True
|
hardware_pwm: True
|
||||||
static_value: 1.250
|
value: 1.250
|
||||||
|
|
||||||
[static_digital_output stepper_config]
|
[static_digital_output stepper_config]
|
||||||
# Microstepping pins
|
# Microstepping pins
|
||||||
|
|||||||
@@ -199,7 +199,6 @@ algorithm: bicubic
|
|||||||
bicubic_tension: 0.15
|
bicubic_tension: 0.15
|
||||||
fade_start: 0.5
|
fade_start: 0.5
|
||||||
fade_end: 2.5
|
fade_end: 2.5
|
||||||
relative_reference_index: 60
|
|
||||||
|
|
||||||
[bed_screws]
|
[bed_screws]
|
||||||
screw1: 0,0
|
screw1: 0,0
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ restart_method: command
|
|||||||
kinematics: cartesian
|
kinematics: cartesian
|
||||||
max_velocity: 300
|
max_velocity: 300
|
||||||
max_accel: 1000
|
max_accel: 1000
|
||||||
max_accel_to_decel: 1000
|
minimum_cruise_ratio: 0.0
|
||||||
max_z_velocity: 5
|
max_z_velocity: 5
|
||||||
max_z_accel: 100
|
max_z_accel: 100
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ microsteps: 16
|
|||||||
rotation_distance: 40
|
rotation_distance: 40
|
||||||
endstop_pin: tmc2209_stepper_x:virtual_endstop
|
endstop_pin: tmc2209_stepper_x:virtual_endstop
|
||||||
position_endstop: 0
|
position_endstop: 0
|
||||||
position_max: 225
|
position_max: 220
|
||||||
homing_speed: 40
|
homing_speed: 40
|
||||||
homing_retract_dist: 0
|
homing_retract_dist: 0
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ microsteps: 16
|
|||||||
rotation_distance: 40
|
rotation_distance: 40
|
||||||
endstop_pin: tmc2209_stepper_y:virtual_endstop
|
endstop_pin: tmc2209_stepper_y:virtual_endstop
|
||||||
position_endstop: 0
|
position_endstop: 0
|
||||||
position_max: 225
|
position_max: 220
|
||||||
homing_speed: 40
|
homing_speed: 40
|
||||||
homing_retract_dist: 0
|
homing_retract_dist: 0
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ microsteps: 16
|
|||||||
rotation_distance: 4
|
rotation_distance: 4
|
||||||
endstop_pin: probe:z_virtual_endstop
|
endstop_pin: probe:z_virtual_endstop
|
||||||
position_min: -4
|
position_min: -4
|
||||||
position_max: 261
|
position_max: 250
|
||||||
homing_speed: 4
|
homing_speed: 4
|
||||||
|
|
||||||
[tmc2209 stepper_z]
|
[tmc2209 stepper_z]
|
||||||
@@ -127,7 +127,7 @@ pin: PA0
|
|||||||
|
|
||||||
[probe]
|
[probe]
|
||||||
pin: PB1
|
pin: PB1
|
||||||
x_offset: 28
|
x_offset: 27
|
||||||
y_offset: -20
|
y_offset: -20
|
||||||
z_offset: 0
|
z_offset: 0
|
||||||
samples: 2
|
samples: 2
|
||||||
|
|||||||
147
config/printer-sovol-sv06-plus-2023.cfg
Normal file
147
config/printer-sovol-sv06-plus-2023.cfg
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
# This file contains pin mappings for the stock Sovol SV06 Plus
|
||||||
|
# To use this config, during "make menuconfig" select the
|
||||||
|
# STM32F103 with a "28KiB bootloader" and serial (on USART1 PA10/PA9) communication.
|
||||||
|
# Also, since it is using the GD32F103, please select Disable SWD at startup
|
||||||
|
#
|
||||||
|
# Flash this firmware by copying "out/klipper.bin" to a SD card and
|
||||||
|
# turning on the printer with the card inserted. The firmware
|
||||||
|
# filename must end in ".bin" and must not match the last filename
|
||||||
|
# that was flashed.
|
||||||
|
#
|
||||||
|
# Note: The stock LCD display does not currently work with Klipper
|
||||||
|
#
|
||||||
|
# See docs/Config_Reference.md for a description of parameters.
|
||||||
|
[mcu]
|
||||||
|
serial: /dev/serial/by-id/usb-1a86_USB2.0-Serial-if00-port0
|
||||||
|
restart_method: command
|
||||||
|
|
||||||
|
[printer]
|
||||||
|
kinematics: cartesian
|
||||||
|
max_velocity: 500
|
||||||
|
max_accel: 2000
|
||||||
|
max_z_velocity: 10
|
||||||
|
max_z_accel: 100
|
||||||
|
|
||||||
|
[stepper_x]
|
||||||
|
step_pin: PC2
|
||||||
|
dir_pin: !PB9
|
||||||
|
enable_pin: !PC3
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 40
|
||||||
|
endstop_pin: tmc2209_stepper_x:virtual_endstop
|
||||||
|
position_endstop: 0
|
||||||
|
position_max: 305
|
||||||
|
homing_speed: 40
|
||||||
|
homing_retract_dist: 0
|
||||||
|
|
||||||
|
[tmc2209 stepper_x]
|
||||||
|
uart_pin: PC1
|
||||||
|
run_current: 0.860
|
||||||
|
sense_resistor: 0.150
|
||||||
|
uart_address: 3
|
||||||
|
driver_SGTHRS: 86
|
||||||
|
diag_pin: PA5
|
||||||
|
|
||||||
|
[stepper_y]
|
||||||
|
step_pin: PB8
|
||||||
|
dir_pin: PB7
|
||||||
|
enable_pin: !PC3
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 40
|
||||||
|
endstop_pin: tmc2209_stepper_y:virtual_endstop
|
||||||
|
position_endstop: 0
|
||||||
|
position_max: 305
|
||||||
|
homing_speed: 40
|
||||||
|
homing_retract_dist: 0
|
||||||
|
|
||||||
|
[tmc2209 stepper_y]
|
||||||
|
uart_pin: PC0
|
||||||
|
run_current: 0.900
|
||||||
|
sense_resistor: 0.150
|
||||||
|
uart_address: 3
|
||||||
|
driver_SGTHRS: 110
|
||||||
|
diag_pin: PA6
|
||||||
|
|
||||||
|
[stepper_z]
|
||||||
|
step_pin: PB6
|
||||||
|
dir_pin: !PB5
|
||||||
|
enable_pin: !PC3
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 4
|
||||||
|
endstop_pin: probe:z_virtual_endstop
|
||||||
|
position_min: -4
|
||||||
|
position_max: 350
|
||||||
|
homing_speed: 4
|
||||||
|
|
||||||
|
[tmc2209 stepper_z]
|
||||||
|
uart_pin: PA15
|
||||||
|
run_current: 1.000
|
||||||
|
interpolate: False
|
||||||
|
sense_resistor: 0.150
|
||||||
|
uart_address: 3
|
||||||
|
diag_pin: PA7
|
||||||
|
|
||||||
|
[extruder]
|
||||||
|
max_extrude_only_distance: 100.0
|
||||||
|
step_pin: PB4
|
||||||
|
dir_pin: !PB3
|
||||||
|
enable_pin: !PC3
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 4.56
|
||||||
|
nozzle_diameter: 0.400
|
||||||
|
filament_diameter: 1.750
|
||||||
|
heater_pin: PA1
|
||||||
|
sensor_type: EPCOS 100K B57560G104F
|
||||||
|
sensor_pin: PC5
|
||||||
|
control: pid
|
||||||
|
pid_kd: 41.96
|
||||||
|
pid_kp: 15.66
|
||||||
|
pid_ki: 1.49
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 300
|
||||||
|
|
||||||
|
[tmc2209 extruder]
|
||||||
|
uart_pin: PC14
|
||||||
|
run_current: 0.550
|
||||||
|
stealthchop_threshold: 0
|
||||||
|
interpolate: False
|
||||||
|
sense_resistor: 0.150
|
||||||
|
uart_address: 3
|
||||||
|
|
||||||
|
[heater_bed]
|
||||||
|
heater_pin: PA2
|
||||||
|
sensor_type: EPCOS 100K B57560G104F
|
||||||
|
sensor_pin: PC4
|
||||||
|
control: pid
|
||||||
|
pid_kp: 186.38
|
||||||
|
pid_ki: 36.12
|
||||||
|
pid_kd: 637.30
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 130
|
||||||
|
|
||||||
|
[fan]
|
||||||
|
pin: PA0
|
||||||
|
|
||||||
|
[probe]
|
||||||
|
pin: PB1
|
||||||
|
x_offset: 28
|
||||||
|
y_offset: -20
|
||||||
|
z_offset: 0
|
||||||
|
|
||||||
|
[safe_z_home]
|
||||||
|
home_xy_position: 123,170
|
||||||
|
z_hop: 10
|
||||||
|
z_hop_speed: 5
|
||||||
|
|
||||||
|
[bed_mesh]
|
||||||
|
speed: 120
|
||||||
|
mesh_min: 28, 20
|
||||||
|
mesh_max: 270, 270
|
||||||
|
probe_count: 5
|
||||||
|
algorithm: bicubic
|
||||||
|
fade_end: 10
|
||||||
|
fade_target: 0
|
||||||
|
|
||||||
|
[filament_switch_sensor filament_runout_sensor]
|
||||||
|
switch_pin: PA4
|
||||||
|
pause_on_runout: True
|
||||||
138
config/printer-tronxy-crux1-2022.cfg
Normal file
138
config/printer-tronxy-crux1-2022.cfg
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
# Klipper configuration for the TronXY Crux1 printer
|
||||||
|
# CXY-V10.1-220921 mainboard, GD32F4XX or STM32F446 MCU
|
||||||
|
#
|
||||||
|
# =======================
|
||||||
|
# BUILD AND FLASH OPTIONS
|
||||||
|
# =======================
|
||||||
|
#
|
||||||
|
# MCU-architecture: STMicroelectronics
|
||||||
|
# Processor model: STM32F446
|
||||||
|
# Bootloader offset: 64KiB
|
||||||
|
# Comms interface: Serial on USART1 PA10/PA9
|
||||||
|
#
|
||||||
|
# Build the firmware with these options
|
||||||
|
# Rename the resulting klipper.bin into fmw_tronxy.bin
|
||||||
|
# Put the file into a directory called "update" on a FAT32 formatted SD card.
|
||||||
|
# Turn off the printer, plug in the SD card and turn the printer back on
|
||||||
|
# Flashing will start automatically and progress will be indicated on the LCD
|
||||||
|
# Once the flashing is completed the display will get stuck on the white Tronxy logo bootscreen
|
||||||
|
# The LCD display will NOT work anymore after flashing Klipper onto this printer
|
||||||
|
|
||||||
|
[mcu]
|
||||||
|
serial: /dev/serial/by-id/usb-1a86_USB_Serial-if00-port0
|
||||||
|
restart_method: command
|
||||||
|
|
||||||
|
[printer]
|
||||||
|
kinematics: cartesian
|
||||||
|
max_velocity: 250
|
||||||
|
max_accel: 1500
|
||||||
|
square_corner_velocity: 5
|
||||||
|
max_z_velocity: 15
|
||||||
|
max_z_accel: 100
|
||||||
|
|
||||||
|
[controller_fan drivers_fan]
|
||||||
|
pin: PD7
|
||||||
|
|
||||||
|
[pwm_cycle_time BEEPER_pin]
|
||||||
|
pin: PA8
|
||||||
|
value: 0
|
||||||
|
shutdown_value: 0
|
||||||
|
cycle_time: 0.001
|
||||||
|
|
||||||
|
[safe_z_home]
|
||||||
|
home_xy_position: 0, 0
|
||||||
|
speed: 100
|
||||||
|
z_hop: 10
|
||||||
|
z_hop_speed: 5
|
||||||
|
|
||||||
|
[stepper_x]
|
||||||
|
step_pin: PE5
|
||||||
|
dir_pin: PF1
|
||||||
|
enable_pin: !PF0
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 20
|
||||||
|
endstop_pin: ^!PC15
|
||||||
|
position_endstop: -1
|
||||||
|
position_min: -1
|
||||||
|
position_max: 180
|
||||||
|
homing_speed: 100
|
||||||
|
homing_retract_dist: 10
|
||||||
|
second_homing_speed: 25
|
||||||
|
|
||||||
|
[stepper_y]
|
||||||
|
step_pin: PF9
|
||||||
|
dir_pin: !PF3
|
||||||
|
enable_pin: !PF5
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 20
|
||||||
|
endstop_pin: ^!PC14
|
||||||
|
position_endstop: -3
|
||||||
|
position_min: -3
|
||||||
|
position_max: 180
|
||||||
|
homing_retract_dist: 10
|
||||||
|
homing_speed: 100
|
||||||
|
second_homing_speed: 25
|
||||||
|
|
||||||
|
[stepper_z]
|
||||||
|
step_pin: PA6
|
||||||
|
dir_pin: !PF15
|
||||||
|
enable_pin: !PA5
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 4
|
||||||
|
endstop_pin: ^!PC13
|
||||||
|
position_endstop: 0
|
||||||
|
position_max: 180
|
||||||
|
position_min: 0
|
||||||
|
|
||||||
|
[extruder]
|
||||||
|
step_pin: PB1
|
||||||
|
dir_pin: PF13
|
||||||
|
enable_pin: !PF14
|
||||||
|
microsteps: 16
|
||||||
|
rotation_distance: 16.75
|
||||||
|
nozzle_diameter: 0.400
|
||||||
|
filament_diameter: 1.750
|
||||||
|
heater_pin: PG7
|
||||||
|
sensor_type: EPCOS 100K B57560G104F
|
||||||
|
sensor_pin: PC3
|
||||||
|
control: pid
|
||||||
|
pid_kp: 22.2
|
||||||
|
pid_ki: 1.08
|
||||||
|
pid_kd: 114.00
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 250
|
||||||
|
min_extrude_temp: 170
|
||||||
|
max_extrude_only_distance: 450
|
||||||
|
|
||||||
|
[heater_fan hotend_fan]
|
||||||
|
heater: extruder
|
||||||
|
heater_temp: 50.0
|
||||||
|
pin: PG9
|
||||||
|
|
||||||
|
[fan]
|
||||||
|
pin: PG0
|
||||||
|
|
||||||
|
[filament_switch_sensor filament_sensor]
|
||||||
|
pause_on_runout: True
|
||||||
|
switch_pin: ^!PE6
|
||||||
|
|
||||||
|
[heater_bed]
|
||||||
|
heater_pin: PE2
|
||||||
|
sensor_type: EPCOS 100K B57560G104F
|
||||||
|
sensor_pin: PC2
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 130
|
||||||
|
control: pid
|
||||||
|
pid_kp: 10.00
|
||||||
|
pid_ki: 0.023
|
||||||
|
pid_kd: 305.4
|
||||||
|
|
||||||
|
[bed_screws]
|
||||||
|
screw1: 17.5, 11
|
||||||
|
screw1_name: front_left
|
||||||
|
screw2: 162.5, 11
|
||||||
|
screw2_name: front_right
|
||||||
|
screw3: 162.5, 162.5
|
||||||
|
screw3_name: back_right
|
||||||
|
screw4: 17.5, 162.5
|
||||||
|
screw4_name: back_left
|
||||||
@@ -86,7 +86,7 @@ pwm: True
|
|||||||
scale: 2.782
|
scale: 2.782
|
||||||
cycle_time: .000030
|
cycle_time: .000030
|
||||||
hardware_pwm: True
|
hardware_pwm: True
|
||||||
static_value: 1.2
|
value: 1.2
|
||||||
|
|
||||||
[output_pin stepper_z_current]
|
[output_pin stepper_z_current]
|
||||||
pin: PL4
|
pin: PL4
|
||||||
@@ -94,7 +94,7 @@ pwm: True
|
|||||||
scale: 2.782
|
scale: 2.782
|
||||||
cycle_time: .000030
|
cycle_time: .000030
|
||||||
hardware_pwm: True
|
hardware_pwm: True
|
||||||
static_value: 1.2
|
value: 1.2
|
||||||
|
|
||||||
[output_pin stepper_e_current]
|
[output_pin stepper_e_current]
|
||||||
pin: PL3
|
pin: PL3
|
||||||
@@ -102,7 +102,7 @@ pwm: True
|
|||||||
scale: 2.782
|
scale: 2.782
|
||||||
cycle_time: .000030
|
cycle_time: .000030
|
||||||
hardware_pwm: True
|
hardware_pwm: True
|
||||||
static_value: 1.0
|
value: 1.0
|
||||||
|
|
||||||
[display]
|
[display]
|
||||||
lcd_type: ssd1306
|
lcd_type: ssd1306
|
||||||
|
|||||||
@@ -77,5 +77,14 @@ heater_temp: 50.0
|
|||||||
pin: toolboard:PA9
|
pin: toolboard:PA9
|
||||||
z_offset: 20
|
z_offset: 20
|
||||||
|
|
||||||
|
[samd_sercom sercom_i2c]
|
||||||
|
sercom: sercom1
|
||||||
|
tx_pin: toolboard:PA16
|
||||||
|
clk_pin: toolboard:PA17
|
||||||
|
|
||||||
|
[lis3dh]
|
||||||
|
i2c_mcu: toolboard
|
||||||
|
i2c_bus: sercom1
|
||||||
|
|
||||||
[mcu toolboard]
|
[mcu toolboard]
|
||||||
canbus_uuid: 4b194673554e
|
canbus_uuid: 4b194673554e
|
||||||
|
|||||||
@@ -61,12 +61,10 @@ gcode:
|
|||||||
# P is the tone duration, S the tone frequency.
|
# P is the tone duration, S the tone frequency.
|
||||||
# The frequency won't be pitch perfect.
|
# The frequency won't be pitch perfect.
|
||||||
|
|
||||||
[output_pin BEEPER_pin]
|
[pwm_cycle_time BEEPER_pin]
|
||||||
pin: ar37
|
pin: ar37
|
||||||
# Beeper pin. This parameter must be provided.
|
# Beeper pin. This parameter must be provided.
|
||||||
# ar37 is the default RAMPS/MKS pin.
|
# ar37 is the default RAMPS/MKS pin.
|
||||||
pwm: True
|
|
||||||
# A piezo beeper needs a PWM signal, a DC buzzer doesn't.
|
|
||||||
value: 0
|
value: 0
|
||||||
# Silent at power on, set to 1 if active low.
|
# Silent at power on, set to 1 if active low.
|
||||||
shutdown_value: 0
|
shutdown_value: 0
|
||||||
|
|||||||
@@ -2,9 +2,8 @@
|
|||||||
# such as a laser or spindle.
|
# such as a laser or spindle.
|
||||||
# See docs/Using_PWM_Tools.md for a more detailed description.
|
# See docs/Using_PWM_Tools.md for a more detailed description.
|
||||||
|
|
||||||
[output_pin TOOL]
|
[pwm_tool TOOL]
|
||||||
pin: !ar9 # use your fan's pin number
|
pin: !ar9 # use your fan's pin number
|
||||||
pwm: True
|
|
||||||
hardware_pwm: True
|
hardware_pwm: True
|
||||||
cycle_time: 0.001
|
cycle_time: 0.001
|
||||||
shutdown_value: 0
|
shutdown_value: 0
|
||||||
@@ -36,9 +35,9 @@ gcode:
|
|||||||
|
|
||||||
[menu __main __control __toolonoff]
|
[menu __main __control __toolonoff]
|
||||||
type: input
|
type: input
|
||||||
enable: {'output_pin TOOL' in printer}
|
enable: {'pwm_tool TOOL' in printer}
|
||||||
name: Fan: {'ON ' if menu.input else 'OFF'}
|
name: Fan: {'ON ' if menu.input else 'OFF'}
|
||||||
input: {printer['output_pin TOOL'].value}
|
input: {printer['pwm_tool TOOL'].value}
|
||||||
input_min: 0
|
input_min: 0
|
||||||
input_max: 1
|
input_max: 1
|
||||||
input_step: 1
|
input_step: 1
|
||||||
@@ -47,9 +46,9 @@ gcode:
|
|||||||
|
|
||||||
[menu __main __control __toolspeed]
|
[menu __main __control __toolspeed]
|
||||||
type: input
|
type: input
|
||||||
enable: {'output_pin TOOL' in printer}
|
enable: {'pwm_tool TOOL' in printer}
|
||||||
name: Tool speed: {'%3d' % (menu.input*100)}%
|
name: Tool speed: {'%3d' % (menu.input*100)}%
|
||||||
input: {printer['output_pin TOOL'].value}
|
input: {printer['pwm_tool TOOL'].value}
|
||||||
input_min: 0
|
input_min: 0
|
||||||
input_max: 1
|
input_max: 1
|
||||||
input_step: 0.01
|
input_step: 0.01
|
||||||
|
|||||||
@@ -364,6 +364,38 @@ and might later produce asynchronous messages such as:
|
|||||||
The "header" field in the initial query response is used to describe
|
The "header" field in the initial query response is used to describe
|
||||||
the fields found in later "data" responses.
|
the fields found in later "data" responses.
|
||||||
|
|
||||||
|
### hx71x/dump_hx71x
|
||||||
|
|
||||||
|
This endpoint is used to subscribe to raw HX711 and HX717 ADC data.
|
||||||
|
Obtaining these low-level ADC updates may be useful for diagnostic
|
||||||
|
and debugging purposes. Using this endpoint may increase Klipper's
|
||||||
|
system load.
|
||||||
|
|
||||||
|
A request may look like:
|
||||||
|
`{"id": 123, "method":"hx71x/dump_hx71x",
|
||||||
|
"params": {"sensor": "load_cell", "response_template": {}}}`
|
||||||
|
and might return:
|
||||||
|
`{"id": 123,"result":{"header":["time","counts","value"]}}`
|
||||||
|
and might later produce asynchronous messages such as:
|
||||||
|
`{"params":{"data":[[3292.432935, 562534, 0.067059278],
|
||||||
|
[3292.4394937, 5625322, 0.670590639]]}}`
|
||||||
|
|
||||||
|
### ads1220/dump_ads1220
|
||||||
|
|
||||||
|
This endpoint is used to subscribe to raw ADS1220 ADC data.
|
||||||
|
Obtaining these low-level ADC updates may be useful for diagnostic
|
||||||
|
and debugging purposes. Using this endpoint may increase Klipper's
|
||||||
|
system load.
|
||||||
|
|
||||||
|
A request may look like:
|
||||||
|
`{"id": 123, "method":"ads1220/dump_ads1220",
|
||||||
|
"params": {"sensor": "load_cell", "response_template": {}}}`
|
||||||
|
and might return:
|
||||||
|
`{"id": 123,"result":{"header":["time","counts","value"]}}`
|
||||||
|
and might later produce asynchronous messages such as:
|
||||||
|
`{"params":{"data":[[3292.432935, 562534, 0.067059278],
|
||||||
|
[3292.4394937, 5625322, 0.670590639]]}}`
|
||||||
|
|
||||||
### pause_resume/cancel
|
### pause_resume/cancel
|
||||||
|
|
||||||
This endpoint is similar to running the "PRINT_CANCEL" G-Code command.
|
This endpoint is similar to running the "PRINT_CANCEL" G-Code command.
|
||||||
@@ -401,3 +433,130 @@ might return:
|
|||||||
|
|
||||||
As with the "gcode/script" endpoint, this endpoint only completes
|
As with the "gcode/script" endpoint, this endpoint only completes
|
||||||
after any pending G-Code commands complete.
|
after any pending G-Code commands complete.
|
||||||
|
|
||||||
|
### bed_mesh/dump_mesh
|
||||||
|
|
||||||
|
Dumps the configuration and state for the current mesh and all
|
||||||
|
saved profiles.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
`{"id": 123, "method": "bed_mesh/dump_mesh"}`
|
||||||
|
|
||||||
|
might return:
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"current_mesh": {
|
||||||
|
"name": "eddy-scan-test",
|
||||||
|
"probed_matrix": [...],
|
||||||
|
"mesh_matrix": [...],
|
||||||
|
"mesh_params": {
|
||||||
|
"x_count": 9,
|
||||||
|
"y_count": 9,
|
||||||
|
"mesh_x_pps": 2,
|
||||||
|
"mesh_y_pps": 2,
|
||||||
|
"algo": "bicubic",
|
||||||
|
"tension": 0.5,
|
||||||
|
"min_x": 20,
|
||||||
|
"max_x": 330,
|
||||||
|
"min_y": 30,
|
||||||
|
"max_y": 320
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"profiles": {
|
||||||
|
"default": {
|
||||||
|
"points": [...],
|
||||||
|
"mesh_params": {
|
||||||
|
"min_x": 20,
|
||||||
|
"max_x": 330,
|
||||||
|
"min_y": 30,
|
||||||
|
"max_y": 320,
|
||||||
|
"x_count": 9,
|
||||||
|
"y_count": 9,
|
||||||
|
"mesh_x_pps": 2,
|
||||||
|
"mesh_y_pps": 2,
|
||||||
|
"algo": "bicubic",
|
||||||
|
"tension": 0.5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"eddy-scan-test": {
|
||||||
|
"points": [...],
|
||||||
|
"mesh_params": {
|
||||||
|
"x_count": 9,
|
||||||
|
"y_count": 9,
|
||||||
|
"mesh_x_pps": 2,
|
||||||
|
"mesh_y_pps": 2,
|
||||||
|
"algo": "bicubic",
|
||||||
|
"tension": 0.5,
|
||||||
|
"min_x": 20,
|
||||||
|
"max_x": 330,
|
||||||
|
"min_y": 30,
|
||||||
|
"max_y": 320
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"eddy-rapid-test": {
|
||||||
|
"points": [...],
|
||||||
|
"mesh_params": {
|
||||||
|
"x_count": 9,
|
||||||
|
"y_count": 9,
|
||||||
|
"mesh_x_pps": 2,
|
||||||
|
"mesh_y_pps": 2,
|
||||||
|
"algo": "bicubic",
|
||||||
|
"tension": 0.5,
|
||||||
|
"min_x": 20,
|
||||||
|
"max_x": 330,
|
||||||
|
"min_y": 30,
|
||||||
|
"max_y": 320
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"calibration": {
|
||||||
|
"points": [...],
|
||||||
|
"config": {
|
||||||
|
"x_count": 9,
|
||||||
|
"y_count": 9,
|
||||||
|
"mesh_x_pps": 2,
|
||||||
|
"mesh_y_pps": 2,
|
||||||
|
"algo": "bicubic",
|
||||||
|
"tension": 0.5,
|
||||||
|
"mesh_min": [
|
||||||
|
20,
|
||||||
|
30
|
||||||
|
],
|
||||||
|
"mesh_max": [
|
||||||
|
330,
|
||||||
|
320
|
||||||
|
],
|
||||||
|
"origin": null,
|
||||||
|
"radius": null
|
||||||
|
},
|
||||||
|
"probe_path": [...],
|
||||||
|
"rapid_path": [...]
|
||||||
|
},
|
||||||
|
"probe_offsets": [
|
||||||
|
0,
|
||||||
|
25,
|
||||||
|
0.5
|
||||||
|
],
|
||||||
|
"axis_minimum": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
-5,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"axis_maximum": [
|
||||||
|
351,
|
||||||
|
358,
|
||||||
|
330,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `dump_mesh` endpoint takes one optional parameter, `mesh_args`.
|
||||||
|
This parameter must be an object, where the keys and values are
|
||||||
|
parameters available to [BED_MESH_CALIBRATE](#bed_mesh_calibrate).
|
||||||
|
This will update the mesh configuration and probe points using the
|
||||||
|
supplied parameters prior to returning the result. It is recommended
|
||||||
|
to omit mesh parameters unless it is desired to visualize the probe points
|
||||||
|
and/or travel path before performing `BED_MESH_CALIBRATE`.
|
||||||
|
|||||||
@@ -24,19 +24,51 @@ try to probe the bed without attaching the probe if you use it.
|
|||||||
> **Tip:** Make sure the [probe X and Y offsets](Config_Reference.md#probe) are
|
> **Tip:** Make sure the [probe X and Y offsets](Config_Reference.md#probe) are
|
||||||
> correctly set as they greatly influence calibration.
|
> correctly set as they greatly influence calibration.
|
||||||
|
|
||||||
1. After setting up the [axis_twist_compensation] module,
|
### Basic Usage: X-Axis Calibration
|
||||||
perform `AXIS_TWIST_COMPENSATION_CALIBRATE`
|
1. After setting up the ```[axis_twist_compensation]``` module, run:
|
||||||
* The calibration wizard will prompt you to measure the probe Z offset at a few
|
```
|
||||||
points along the bed
|
AXIS_TWIST_COMPENSATION_CALIBRATE
|
||||||
* The calibration defaults to 3 points but you can use the option
|
```
|
||||||
`SAMPLE_COUNT=` to use a different number.
|
This command will calibrate the X-axis by default.
|
||||||
2. [Adjust your Z offset](Probe_Calibrate.md#calibrating-probe-z-offset)
|
- The calibration wizard will prompt you to measure the probe Z offset at
|
||||||
3. Perform automatic/probe-based bed tramming operations, such as
|
several points along the bed.
|
||||||
[Screws Tilt Adjust](G-Codes.md#screws_tilt_adjust),
|
- By default, the calibration uses 3 points, but you can specify a different
|
||||||
[Z Tilt Adjust](G-Codes.md#z_tilt_adjust) etc
|
number with the option:
|
||||||
4. Home all axis, then perform a [Bed Mesh](Bed_Mesh.md) if required
|
``
|
||||||
5. Perform a test print, followed by any
|
SAMPLE_COUNT=<value>
|
||||||
[fine-tuning](Axis_Twist_Compensation.md#fine-tuning) as desired
|
``
|
||||||
|
|
||||||
|
2. **Adjust Your Z Offset:**
|
||||||
|
After completing the calibration, be sure to [adjust your Z offset]
|
||||||
|
(Probe_Calibrate.md#calibrating-probe-z-offset).
|
||||||
|
|
||||||
|
3. **Perform Bed Leveling Operations:**
|
||||||
|
Use probe-based operations as needed, such as:
|
||||||
|
- [Screws Tilt Adjust](G-Codes.md#screws_tilt_adjust)
|
||||||
|
- [Z Tilt Adjust](G-Codes.md#z_tilt_adjust)
|
||||||
|
|
||||||
|
4. **Finalize the Setup:**
|
||||||
|
- Home all axes, and perform a [Bed Mesh](Bed_Mesh.md) if necessary.
|
||||||
|
- Run a test print, followed by any
|
||||||
|
[fine-tuning](Axis_Twist_Compensation.md#fine-tuning)
|
||||||
|
if needed.
|
||||||
|
|
||||||
|
### For Y-Axis Calibration
|
||||||
|
The calibration process for the Y-axis is similar to the X-axis. To calibrate
|
||||||
|
the Y-axis, use:
|
||||||
|
```
|
||||||
|
AXIS_TWIST_COMPENSATION_CALIBRATE AXIS=Y
|
||||||
|
```
|
||||||
|
This will guide you through the same measuring process as for the X-axis.
|
||||||
|
|
||||||
|
### Automatic Calibration for Both Axes
|
||||||
|
To perform automatic calibration for both the X and Y axes without manual
|
||||||
|
intervention, use:
|
||||||
|
```
|
||||||
|
AXIS_TWIST_COMPENSATION_CALIBRATE AUTO=True
|
||||||
|
```
|
||||||
|
In this mode, the calibration process will run for both axes automatically.
|
||||||
|
|
||||||
|
|
||||||
> **Tip:** Bed temperature and nozzle temperature and size do not seem to have
|
> **Tip:** Bed temperature and nozzle temperature and size do not seem to have
|
||||||
> an influence to the calibration process.
|
> an influence to the calibration process.
|
||||||
|
|||||||
@@ -6,23 +6,64 @@ PRU.
|
|||||||
## Building an OS image
|
## Building an OS image
|
||||||
|
|
||||||
Start by installing the
|
Start by installing the
|
||||||
[Debian 9.9 2019-08-03 4GB SD IoT](https://beagleboard.org/latest-images)
|
[Debian 11.7 2023-09-02 4GB microSD IoT](https://beagleboard.org/latest-images)
|
||||||
image. One may run the image from either a micro-SD card or from
|
image. One may run the image from either a micro-SD card or from
|
||||||
builtin eMMC. If using the eMMC, install it to eMMC now by following
|
builtin eMMC. If using the eMMC, install it to eMMC now by following
|
||||||
the instructions from the above link.
|
the instructions from the above link.
|
||||||
|
|
||||||
Then ssh into the Beaglebone machine (`ssh debian@beaglebone` --
|
Then ssh into the Beaglebone machine (`ssh debian@beaglebone` --
|
||||||
password is `temppwd`) and install Klipper by running the following
|
password is `temppwd`).
|
||||||
|
|
||||||
|
Before start installing Klipper you need to free-up additional space.
|
||||||
|
there are 3 options to do that:
|
||||||
|
1. remove some BeagleBone "Demo" resources
|
||||||
|
2. if you did boot from SD-Card, and it's bigger than 4Gb - you can expand
|
||||||
|
current filesystem to take whole card space
|
||||||
|
3. do option #1 and #2 together.
|
||||||
|
|
||||||
|
To remove some BeagleBone "Demo" resources execute these commands
|
||||||
|
```
|
||||||
|
sudo apt remove bb-node-red-installer
|
||||||
|
sudo apt remove bb-code-server
|
||||||
|
```
|
||||||
|
|
||||||
|
To expand filesystem to full size of your SD-Card execute this command, reboot is not required.
|
||||||
|
```
|
||||||
|
sudo growpart /dev/mmcblk0 1
|
||||||
|
sudo resize2fs /dev/mmcblk0p1
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Install Klipper by running the following
|
||||||
commands:
|
commands:
|
||||||
|
|
||||||
```
|
```
|
||||||
git clone https://github.com/Klipper3d/klipper
|
git clone https://github.com/Klipper3d/klipper.git
|
||||||
./klipper/scripts/install-beaglebone.sh
|
./klipper/scripts/install-beaglebone.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
## Install Octoprint
|
After installing Klipper you need to decide what kind of deployment do you need,
|
||||||
|
but take a note that BeagleBone is 3.3v based hardware and in most cases you can't
|
||||||
|
directly connect pins to 5v or 12v based hardware without conversion boards.
|
||||||
|
|
||||||
One may then install Octoprint:
|
As Klipper have multimodule architecture on BeagleBone you can achieve many different use cases,
|
||||||
|
but general ones are following:
|
||||||
|
|
||||||
|
Use case 1: Use BeagleBone only as a host system to run Klipper and additional software
|
||||||
|
like OctoPrint/Fluidd + Moonraker/... and this configuration will be driving
|
||||||
|
external micro-controllers via serial/usb/canbus connections.
|
||||||
|
|
||||||
|
Use case 2: Use BeagleBone with extension board (cape) like CRAMPS board.
|
||||||
|
in this configuration BeagleBone will host Klipper + additional software, and
|
||||||
|
it will drive extension board with BeagleBone PRU cores (2 additional cores 200Mh, 32Bit).
|
||||||
|
|
||||||
|
Use case 3: It's same as "Use case 1" but additionally you want to drive
|
||||||
|
BeagleBone GPIOs with high speed by utilizing PRU cores to offload main CPU.
|
||||||
|
|
||||||
|
|
||||||
|
## Installing Octoprint
|
||||||
|
|
||||||
|
One may then install Octoprint or fully skip this section if desired other software:
|
||||||
```
|
```
|
||||||
git clone https://github.com/foosel/OctoPrint.git
|
git clone https://github.com/foosel/OctoPrint.git
|
||||||
cd OctoPrint/
|
cd OctoPrint/
|
||||||
@@ -51,25 +92,89 @@ Then start the Octoprint service:
|
|||||||
```
|
```
|
||||||
sudo systemctl start octoprint
|
sudo systemctl start octoprint
|
||||||
```
|
```
|
||||||
|
Wait 1-2 minutes and make sure the OctoPrint web server is accessible - it should be at:
|
||||||
Make sure the OctoPrint web server is accessible - it should be at:
|
|
||||||
[http://beaglebone:5000/](http://beaglebone:5000/)
|
[http://beaglebone:5000/](http://beaglebone:5000/)
|
||||||
|
|
||||||
## Building the micro-controller code
|
|
||||||
|
|
||||||
To compile the Klipper micro-controller code, start by configuring it
|
## Building the BeagleBone PRU micro-controller code (PRU firmware)
|
||||||
for the "Beaglebone PRU":
|
This section is required for "Use case 2" and "Use case 3" mentioned above,
|
||||||
|
you should skip it for "Use case 1".
|
||||||
|
|
||||||
|
Check that required devices are present
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo beagle-version
|
||||||
|
```
|
||||||
|
You should check that output contains successful "remoteproc" drivers loading and presence of PRU cores,
|
||||||
|
in Kernel 5.10 they should be "remoteproc1" and "remoteproc2" (4a334000.pru, 4a338000.pru)
|
||||||
|
Also check that many GPIOs are loaded they will look like "Allocated GPIO id=0 name='P8_03'"
|
||||||
|
Usually everything is fine and no hardware configuration is required.
|
||||||
|
If something is missing - try to play with "uboot overlays" options or with cape-overlays
|
||||||
|
Just for reference some output of working BeagleBone Black configuration with CRAMPS board:
|
||||||
|
```
|
||||||
|
model:[TI_AM335x_BeagleBone_Black]
|
||||||
|
UBOOT: Booted Device-Tree:[am335x-boneblack-uboot-univ.dts]
|
||||||
|
UBOOT: Loaded Overlay:[BB-ADC-00A0.bb.org-overlays]
|
||||||
|
UBOOT: Loaded Overlay:[BB-BONE-eMMC1-01-00A0.bb.org-overlays]
|
||||||
|
kernel:[5.10.168-ti-r71]
|
||||||
|
/boot/uEnv.txt Settings:
|
||||||
|
uboot_overlay_options:[enable_uboot_overlays=1]
|
||||||
|
uboot_overlay_options:[disable_uboot_overlay_video=0]
|
||||||
|
uboot_overlay_options:[disable_uboot_overlay_audio=1]
|
||||||
|
uboot_overlay_options:[disable_uboot_overlay_wireless=1]
|
||||||
|
uboot_overlay_options:[enable_uboot_cape_universal=1]
|
||||||
|
pkg:[bb-cape-overlays]:[4.14.20210821.0-0~bullseye+20210821]
|
||||||
|
pkg:[bb-customizations]:[1.20230720.1-0~bullseye+20230720]
|
||||||
|
pkg:[bb-usb-gadgets]:[1.20230414.0-0~bullseye+20230414]
|
||||||
|
pkg:[bb-wl18xx-firmware]:[1.20230414.0-0~bullseye+20230414]
|
||||||
|
.............
|
||||||
|
.............
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
To compile the Klipper micro-controller code, start by configuring it for the "Beaglebone PRU",
|
||||||
|
for "BeagleBone Black" additionally disable options "Support GPIO Bit-banging devices" and disable "Support LCD devices"
|
||||||
|
inside the "Optional features" because they will not fit in 8Kb PRU firmware memory,
|
||||||
|
then exit and save config:
|
||||||
```
|
```
|
||||||
cd ~/klipper/
|
cd ~/klipper/
|
||||||
make menuconfig
|
make menuconfig
|
||||||
```
|
```
|
||||||
|
|
||||||
To build and install the new micro-controller code, run:
|
To build and install the new PRU micro-controller code, run:
|
||||||
```
|
```
|
||||||
sudo service klipper stop
|
sudo service klipper stop
|
||||||
make flash
|
make flash
|
||||||
sudo service klipper start
|
sudo service klipper start
|
||||||
```
|
```
|
||||||
|
After previous commands was executed your PRU firmware should be ready and started
|
||||||
|
to check if everything was fine you can execute following command
|
||||||
|
```
|
||||||
|
dmesg
|
||||||
|
```
|
||||||
|
and compare last messages with sample one which indicate that everything started properly:
|
||||||
|
```
|
||||||
|
[ 71.105499] remoteproc remoteproc1: 4a334000.pru is available
|
||||||
|
[ 71.157155] remoteproc remoteproc2: 4a338000.pru is available
|
||||||
|
[ 73.256287] remoteproc remoteproc1: powering up 4a334000.pru
|
||||||
|
[ 73.279246] remoteproc remoteproc1: Booting fw image am335x-pru0-fw, size 97112
|
||||||
|
[ 73.285807] remoteproc1#vdev0buffer: registered virtio0 (type 7)
|
||||||
|
[ 73.285836] remoteproc remoteproc1: remote processor 4a334000.pru is now up
|
||||||
|
[ 73.286322] remoteproc remoteproc2: powering up 4a338000.pru
|
||||||
|
[ 73.313717] remoteproc remoteproc2: Booting fw image am335x-pru1-fw, size 188560
|
||||||
|
[ 73.313753] remoteproc remoteproc2: header-less resource table
|
||||||
|
[ 73.329964] remoteproc remoteproc2: header-less resource table
|
||||||
|
[ 73.348321] remoteproc remoteproc2: remote processor 4a338000.pru is now up
|
||||||
|
[ 73.443355] virtio_rpmsg_bus virtio0: creating channel rpmsg-pru addr 0x1e
|
||||||
|
[ 73.443727] virtio_rpmsg_bus virtio0: msg received with no recipient
|
||||||
|
[ 73.444352] virtio_rpmsg_bus virtio0: rpmsg host is online
|
||||||
|
[ 73.540993] rpmsg_pru virtio0.rpmsg-pru.-1.30: new rpmsg_pru device: /dev/rpmsg_pru30
|
||||||
|
```
|
||||||
|
take a note about "/dev/rpmsg_pru30" - it's your future serial device for main mcu configuration
|
||||||
|
this device is required to be present, if it's absent - your PRU cores did not start properly.
|
||||||
|
|
||||||
|
## Building and installing Linux host micro-controller code
|
||||||
|
This section is required for "Use case 2" and optional for "Use case 3" mentioned above
|
||||||
|
|
||||||
It is also necessary to compile and install the micro-controller code
|
It is also necessary to compile and install the micro-controller code
|
||||||
for a Linux host process. Configure it a second time for a "Linux process":
|
for a Linux host process. Configure it a second time for a "Linux process":
|
||||||
@@ -83,12 +188,24 @@ sudo service klipper stop
|
|||||||
make flash
|
make flash
|
||||||
sudo service klipper start
|
sudo service klipper start
|
||||||
```
|
```
|
||||||
|
take a note about "/tmp/klipper_host_mcu" - it will be your future serial device for "mcu host"
|
||||||
|
if that file don't exist - refer to "scripts/klipper-mcu.service" file, it was installed by
|
||||||
|
previous commands, and it's responsible for it.
|
||||||
|
|
||||||
|
|
||||||
|
Take a note for "Use case 2" about following: when you will define printer configuration you should always
|
||||||
|
use temperature sensors from "mcu host" because ADCs not present in default "mcu" (PRU cores).
|
||||||
|
Sample configuration of "sensor_pin" for extruder and heated bed are available in "generic-cramps.cfg"
|
||||||
|
You can use any other GPIO directly from "mcu host" by referencing them this way "host:gpiochip1/gpio17"
|
||||||
|
but that should be avoided because it will be creating additional load on main CPU and most probably
|
||||||
|
you can't use them for stepper control.
|
||||||
|
|
||||||
|
|
||||||
## Remaining configuration
|
## Remaining configuration
|
||||||
|
|
||||||
Complete the installation by configuring Klipper and Octoprint
|
Complete the installation by configuring Klipper
|
||||||
following the instructions in
|
following the instructions in
|
||||||
the main [Installation](Installation.md#configuring-klipper) document.
|
the main [Installation](Installation.md#configuring-octoprint-to-use-klipper) document.
|
||||||
|
|
||||||
## Printing on the Beaglebone
|
## Printing on the Beaglebone
|
||||||
|
|
||||||
@@ -97,4 +214,111 @@ OctoPrint well. Print stalls have been known to occur on complex
|
|||||||
prints (the printer may move faster than OctoPrint can send movement
|
prints (the printer may move faster than OctoPrint can send movement
|
||||||
commands). If this occurs, consider using the "virtual_sdcard" feature
|
commands). If this occurs, consider using the "virtual_sdcard" feature
|
||||||
(see [Config Reference](Config_Reference.md#virtual_sdcard) for
|
(see [Config Reference](Config_Reference.md#virtual_sdcard) for
|
||||||
details) to print directly from Klipper.
|
details) to print directly from Klipper
|
||||||
|
and disable any DEBUG or VERBOSE logging options if you did enable them.
|
||||||
|
|
||||||
|
|
||||||
|
## AVR micro-controller code build
|
||||||
|
This environment have everything to build necessary micro-controller code except AVR,
|
||||||
|
AVR packages was removed because of conflict with PRU packages.
|
||||||
|
if you still want to build AVR micro-controller code in this environment you need to remove
|
||||||
|
PRU packages and install AVR packages by executing following commands
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo apt-get remove gcc-pru
|
||||||
|
sudo apt-get install avrdude gcc-avr binutils-avr avr-libc
|
||||||
|
```
|
||||||
|
if you need to restore PRU packages - then remove ARV packages before that
|
||||||
|
```
|
||||||
|
sudo apt-get remove avrdude gcc-avr binutils-avr avr-libc
|
||||||
|
sudo apt-get install gcc-pru
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Hardware Pin designation
|
||||||
|
BeagleBone is very flexible in terms of pin designation, same pin can be configured for different function
|
||||||
|
but always single function for single pin, same function can be present on different pins.
|
||||||
|
So you can't have multiple functions on single pin or have same function on multiple pins.
|
||||||
|
Example:
|
||||||
|
P9_20 - i2c2_sda/can0_tx/spi1_cs0/gpio0_12/uart1_ctsn
|
||||||
|
P9_19 - i2c2_scl/can0_rx/spi1_cs1/gpio0_13/uart1_rtsn
|
||||||
|
P9_24 - i2c1_scl/can1_rx/gpio0_15/uart1_tx
|
||||||
|
P9_26 - i2c1_sda/can1_tx/gpio0_14/uart1_rx
|
||||||
|
|
||||||
|
Pin designation is defined by using special "overlays" which will be loaded during linux boot
|
||||||
|
they are configured by editing file /boot/uEnv.txt with elevated permissions
|
||||||
|
```
|
||||||
|
sudo editor /boot/uEnv.txt
|
||||||
|
```
|
||||||
|
and defining which functionality to load, for example to enable CAN1 you need to define overlay for it
|
||||||
|
```
|
||||||
|
uboot_overlay_addr4=/lib/firmware/BB-CAN1-00A0.dtbo
|
||||||
|
```
|
||||||
|
This overlay BB-CAN1-00A0.dtbo will reconfigure all required pins for CAN1 and create CAN device in Linux.
|
||||||
|
Any change in overlays will require system reboot to be applied.
|
||||||
|
If you need to understand which pins are involved in some overlay - you can analyze source files in
|
||||||
|
this location: /opt/sources/bb.org-overlays/src/arm/
|
||||||
|
or search info in BeagleBone forums.
|
||||||
|
|
||||||
|
|
||||||
|
## Enabling hardware SPI
|
||||||
|
BeagleBone usually have multiple hardware SPI buses, for example BeagleBone Black can have 2 of them,
|
||||||
|
they can work up to 48Mhz, but usually they are limited to 16Mhz by Kernel Device-tree.
|
||||||
|
By default, in BeagleBone Black some of SPI1 pins are configured for HDMI-Audio output,
|
||||||
|
to fully enable 4-wire SPI1 you need to disable HDMI Audio and enable SPI1
|
||||||
|
To do that edit file /boot/uEnv.txt with elevated permissions
|
||||||
|
```
|
||||||
|
sudo editor /boot/uEnv.txt
|
||||||
|
```
|
||||||
|
uncomment variable
|
||||||
|
```
|
||||||
|
disable_uboot_overlay_audio=1
|
||||||
|
```
|
||||||
|
|
||||||
|
next uncomment variable and define it this way
|
||||||
|
```
|
||||||
|
uboot_overlay_addr4=/lib/firmware/BB-SPIDEV1-00A0.dtbo
|
||||||
|
```
|
||||||
|
Save changes in /boot/uEnv.txt and reboot the board.
|
||||||
|
Now you have SPI1 Enabled, to verify its presence execute command
|
||||||
|
```
|
||||||
|
ls /dev/spidev1.*
|
||||||
|
```
|
||||||
|
Take a note that BeagleBone usually is 3.3v based hardware and to use 5V SPI devices
|
||||||
|
you need to add Level-Shifting chip, for example SN74CBTD3861, SN74LVC1G34 or similar.
|
||||||
|
If you are using CRAMPS board - it already contains Level-Shifting chip and SPI1 pins
|
||||||
|
will become available on P503 port, and they can accept 5v hardware,
|
||||||
|
check CRAMPS board Schematics for pin references.
|
||||||
|
|
||||||
|
## Enabling hardware I2C
|
||||||
|
BeagleBone usually have multiple hardware I2C buses, for example BeagleBone Black can have 3 of them,
|
||||||
|
they support speed up-to 400Kbit Fast mode.
|
||||||
|
By default, in BeagleBone Black there are two of them (i2c-1 and i2c-2) usually both are already configured and
|
||||||
|
present on P9, third ic2-0 usually reserved for internal use.
|
||||||
|
If you are using CRAMPS board then i2c-2 is present on P303 port with 3.3v level,
|
||||||
|
If you want to obtain I2c-1 in CRAMPS board - you can get them on Extruder1.Step, Extruder1.Dir pins,
|
||||||
|
they also are 3.3v based, check CRAMPS board Schematics for pin references.
|
||||||
|
Related overlays, for [Hardware Pin designation](#hardware-pin-designation):
|
||||||
|
I2C1(100Kbit): BB-I2C1-00A0.dtbo
|
||||||
|
I2C1(400Kbit): BB-I2C1-FAST-00A0.dtbo
|
||||||
|
I2C2(100Kbit): BB-I2C2-00A0.dtbo
|
||||||
|
I2C2(400Kbit): BB-I2C2-FAST-00A0.dtbo
|
||||||
|
|
||||||
|
## Enabling hardware UART(Serial)/CAN
|
||||||
|
BeagleBone have up to 6 hardware UART(Serial) buses (up to 3Mbit)
|
||||||
|
and up to 2 hardware CAN(1Mbit) buses.
|
||||||
|
UART1(RX,TX) and CAN1(TX,RX) and I2C2(SDA,SCL) are using same pins - so you need to chose what to use
|
||||||
|
UART1(CTSN,RTSN) and CAN0(TX,RX) and I2C1(SDA,SCL) are using same pins - so you need to chose what to use
|
||||||
|
All UART/CAN related pins are 3.3v based, so you will need to use Transceiver chips/boards like SN74LVC2G241DCUR (for UART),
|
||||||
|
SN65HVD230 (for CAN), TTL-RS485 (for RS-485) or something similar which can convert 3.3v signals to appropriate levels.
|
||||||
|
|
||||||
|
Related overlays, for [Hardware Pin designation](#hardware-pin-designation)
|
||||||
|
CAN0: BB-CAN0-00A0.dtbo
|
||||||
|
CAN1: BB-CAN1-00A0.dtbo
|
||||||
|
UART0: - used for Console
|
||||||
|
UART1(RX,TX): BB-UART1-00A0.dtbo
|
||||||
|
UART1(RTS,CTS): BB-UART1-RTSCTS-00A0.dtbo
|
||||||
|
UART2(RX,TX): BB-UART2-00A0.dtbo
|
||||||
|
UART3(RX,TX): BB-UART3-00A0.dtbo
|
||||||
|
UART4(RS-485): BB-UART4-RS485-00A0.dtbo
|
||||||
|
UART5(RX,TX): BB-UART5-00A0.dtbo
|
||||||
|
|||||||
353
docs/Bed_Mesh.md
353
docs/Bed_Mesh.md
@@ -44,10 +44,9 @@ probe_count: 5, 3
|
|||||||
|
|
||||||
- `mesh_max: 240, 198`\
|
- `mesh_max: 240, 198`\
|
||||||
_Required_\
|
_Required_\
|
||||||
The probed coordinate farthest farthest from the origin. This is not
|
The probed coordinate farthest from the origin. This is not necessarily
|
||||||
necessarily the last point probed, as the probing process occurs in a
|
the last point probed, as the probing process occurs in a zig-zag fashion.
|
||||||
zig-zag fashion. As with `mesh_min`, this coordinate is relative to
|
As with `mesh_min`, this coordinate is relative to the probe's location.
|
||||||
the probe's location.
|
|
||||||
|
|
||||||
- `probe_count: 5, 3`\
|
- `probe_count: 5, 3`\
|
||||||
_Default Value: 3, 3_\
|
_Default Value: 3, 3_\
|
||||||
@@ -142,7 +141,7 @@ bicubic_tension: 0.2
|
|||||||
integer pair, and also may be specified a single integer that is applied
|
integer pair, and also may be specified a single integer that is applied
|
||||||
to both axes. In this example there are 4 segments along the X axis
|
to both axes. In this example there are 4 segments along the X axis
|
||||||
and 2 segments along the Y axis. This evaluates to 8 interpolated
|
and 2 segments along the Y axis. This evaluates to 8 interpolated
|
||||||
points along X, 6 interpolated points along Y, which results in a 13x8
|
points along X, 6 interpolated points along Y, which results in a 13x9
|
||||||
mesh. Note that if mesh_pps is set to 0 then mesh interpolation is
|
mesh. Note that if mesh_pps is set to 0 then mesh interpolation is
|
||||||
disabled and the probed matrix will be sampled directly.
|
disabled and the probed matrix will be sampled directly.
|
||||||
|
|
||||||
@@ -270,7 +269,7 @@ printers use an endstop for homing the Z axis and a probe for calibrating the
|
|||||||
mesh. In this configuration it is possible offset the mesh so that the (X, Y)
|
mesh. In this configuration it is possible offset the mesh so that the (X, Y)
|
||||||
`reference position` applies zero adjustment. The `reference postion` should
|
`reference position` applies zero adjustment. The `reference postion` should
|
||||||
be the location on the bed where a
|
be the location on the bed where a
|
||||||
[Z_ENDSTOP_CALIBRATE](./Manual_Level#calibrating-a-z-endstop)
|
[Z_ENDSTOP_CALIBRATE](./Manual_Level.md#calibrating-a-z-endstop)
|
||||||
paper test is performed. The bed_mesh module provides the
|
paper test is performed. The bed_mesh module provides the
|
||||||
`zero_reference_position` option for specifying this coordinate:
|
`zero_reference_position` option for specifying this coordinate:
|
||||||
|
|
||||||
@@ -370,21 +369,146 @@ are identified in green.
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
### Adaptive Meshes
|
||||||
|
|
||||||
|
Adaptive bed meshing is a way to speed up the bed mesh generation by only probing
|
||||||
|
the area of the bed used by the objects being printed. When used, the method will
|
||||||
|
automatically adjust the mesh parameters based on the area occupied by the defined
|
||||||
|
print objects.
|
||||||
|
|
||||||
|
The adapted mesh area will be computed from the area defined by the boundaries of all
|
||||||
|
the defined print objects so it covers every object, including any margins defined in
|
||||||
|
the configuration. After the area is computed, the number of probe points will be
|
||||||
|
scaled down based on the ratio of the default mesh area and the adapted mesh area. To
|
||||||
|
illustrate this consider the following example:
|
||||||
|
|
||||||
|
For a 150mmx150mm bed with `mesh_min` set to `25,25` and `mesh_max` set to `125,125`,
|
||||||
|
the default mesh area is a 100mmx100mm square. An adapted mesh area of `50,50`
|
||||||
|
means a ratio of `0.5x0.5` between the adapted area and default mesh area.
|
||||||
|
|
||||||
|
If the `bed_mesh` configuration specified `probe_count` as `7x7`, the adapted bed
|
||||||
|
mesh will use 4x4 probe points (7 * 0.5 rounded up).
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```
|
||||||
|
[bed_mesh]
|
||||||
|
speed: 120
|
||||||
|
horizontal_move_z: 5
|
||||||
|
mesh_min: 35, 6
|
||||||
|
mesh_max: 240, 198
|
||||||
|
probe_count: 5, 3
|
||||||
|
adaptive_margin: 5
|
||||||
|
```
|
||||||
|
|
||||||
|
- `adaptive_margin` \
|
||||||
|
_Default Value: 0_ \
|
||||||
|
Margin (in mm) to add around the area of the bed used by the defined objects. The diagram
|
||||||
|
below shows the adapted bed mesh area with an `adaptive_margin` of 5mm. The adapted mesh
|
||||||
|
area (area in green) is computed as the used bed area (area in blue) plus the defined margin.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
By nature, adaptive bed meshes use the objects defined by the Gcode file being printed.
|
||||||
|
Therefore, it is expected that each Gcode file will generate a mesh that probes a different
|
||||||
|
area of the print bed. Therefore, adapted bed meshes should not be re-used. The expectation
|
||||||
|
is that a new mesh will be generated for each print if adaptive meshing is used.
|
||||||
|
|
||||||
|
It is also important to consider that adaptive bed meshing is best used on machines that can
|
||||||
|
normally probe the entire bed and achieve a maximum variance less than or equal to 1 layer
|
||||||
|
height. Machines with mechanical issues that a full bed mesh normally compensates for may
|
||||||
|
have undesirable results when attempting print moves **outside** of the probed area. If a
|
||||||
|
full bed mesh has a variance greater than 1 layer height, caution must be taken when using
|
||||||
|
adaptive bed meshes and attempting print moves outside of the meshed area.
|
||||||
|
|
||||||
|
## Surface Scans
|
||||||
|
|
||||||
|
Some probes, such as the [Eddy Current Probe](./Eddy_Probe.md), are capable of
|
||||||
|
"scanning" the surface of the bed. That is, these probes can sample a mesh
|
||||||
|
without lifting the tool between samples. To activate scanning mode, the
|
||||||
|
`METHOD=scan` or `METHOD=rapid_scan` probe parameter should be passed in the
|
||||||
|
`BED_MESH_CALIBRATE` gcode command.
|
||||||
|
|
||||||
|
### Scan Height
|
||||||
|
|
||||||
|
The scan height is set by the `horizontal_move_z` option in `[bed_mesh]`. In
|
||||||
|
addition it can be supplied with the `BED_MESH_CALIBRATE` gcode command via the
|
||||||
|
`HORIZONTAL_MOVE_Z` parameter.
|
||||||
|
|
||||||
|
The scan height must be sufficiently low to avoid scanning errors. Typically
|
||||||
|
a height of 2mm (ie: `HORIZONTAL_MOVE_Z=2`) should work well, presuming that the
|
||||||
|
probe is mounted correctly.
|
||||||
|
|
||||||
|
It should be noted that if the probe is more than 4mm above the surface then the
|
||||||
|
results will be invalid. Thus, scanning is not possible on beds with severe
|
||||||
|
surface deviation or beds with extreme tilt that hasn't been corrected.
|
||||||
|
|
||||||
|
### Rapid (Continuous) Scanning
|
||||||
|
|
||||||
|
When performing a `rapid_scan` one should keep in mind that the results will
|
||||||
|
have some amount of error. This error should be low enough to be useful on
|
||||||
|
large print areas with reasonably thick layer heights. Some probes may be
|
||||||
|
more prone to error than others.
|
||||||
|
|
||||||
|
It is not recommended that rapid mode be used to scan a "dense" mesh. Some of
|
||||||
|
the error introduced during a rapid scan may be gaussian noise from the sensor,
|
||||||
|
and a dense mesh will reflect this noise (ie: there will be peaks and valleys).
|
||||||
|
|
||||||
|
Bed Mesh will attempt to optimize the travel path to provide the best possible
|
||||||
|
result based on the configuration. This includes avoiding faulty regions
|
||||||
|
when collecting samples and "overshooting" the mesh when changing direction.
|
||||||
|
This overshoot improves sampling at the edges of a mesh, however it requires
|
||||||
|
that the mesh be configured in a way that allows the tool to travel outside
|
||||||
|
of the mesh.
|
||||||
|
|
||||||
|
```
|
||||||
|
[bed_mesh]
|
||||||
|
speed: 120
|
||||||
|
horizontal_move_z: 5
|
||||||
|
mesh_min: 35, 6
|
||||||
|
mesh_max: 240, 198
|
||||||
|
probe_count: 5
|
||||||
|
scan_overshoot: 8
|
||||||
|
```
|
||||||
|
|
||||||
|
- `scan_overshoot`
|
||||||
|
_Default Value: 0 (disabled)_\
|
||||||
|
The maximum amount of travel (in mm) available outside of the mesh.
|
||||||
|
For rectangular beds this applies to travel on the X axis, and for round beds
|
||||||
|
it applies to the entire radius. The tool must be able to travel the amount
|
||||||
|
specified outside of the mesh. This value is used to optimize the travel
|
||||||
|
path when performing a "rapid scan". The minimum value that may be specified
|
||||||
|
is 1. The default is no overshoot.
|
||||||
|
|
||||||
|
If no scan overshoot is configured then travel path optimization will not
|
||||||
|
be applied to changes in direction.
|
||||||
|
|
||||||
## Bed Mesh Gcodes
|
## Bed Mesh Gcodes
|
||||||
|
|
||||||
### Calibration
|
### Calibration
|
||||||
|
|
||||||
`BED_MESH_CALIBRATE PROFILE=<name> METHOD=[manual | automatic] [<probe_parameter>=<value>]
|
`BED_MESH_CALIBRATE PROFILE=<name> METHOD=[manual | automatic | scan | rapid_scan] \
|
||||||
[<mesh_parameter>=<value>]`\
|
[<probe_parameter>=<value>] [<mesh_parameter>=<value>] [ADAPTIVE=[0|1] \
|
||||||
|
[ADAPTIVE_MARGIN=<value>]`\
|
||||||
_Default Profile: default_\
|
_Default Profile: default_\
|
||||||
_Default Method: automatic if a probe is detected, otherwise manual_
|
_Default Method: automatic if a probe is detected, otherwise manual_ \
|
||||||
|
_Default Adaptive: 0_ \
|
||||||
|
_Default Adaptive Margin: 0_
|
||||||
|
|
||||||
Initiates the probing procedure for Bed Mesh Calibration.
|
Initiates the probing procedure for Bed Mesh Calibration.
|
||||||
|
|
||||||
The mesh will be saved into a profile specified by the `PROFILE` parameter,
|
The mesh will be saved into a profile specified by the `PROFILE` parameter,
|
||||||
or `default` if unspecified. If `METHOD=manual` is selected then manual probing
|
or `default` if unspecified. The `METHOD` parameter takes one of the following
|
||||||
will occur. When switching between automatic and manual probing the generated
|
values:
|
||||||
mesh points will automatically be adjusted.
|
|
||||||
|
- `METHOD=manual`: enables manual probing using the nozzle and the paper test
|
||||||
|
- `METHOD=automatic`: Automatic (standard) probing. This is the default.
|
||||||
|
- `METHOD=scan`: Enables surface scanning. The tool will pause over each position
|
||||||
|
to collect a sample.
|
||||||
|
- `METHOD=rapid_scan`: Enables continuous surface scanning.
|
||||||
|
|
||||||
|
XY positions are automatically adjusted to include the X and/or Y offsets
|
||||||
|
when a probing method other than `manual` is selected.
|
||||||
|
|
||||||
It is possible to specify mesh parameters to modify the probed area. The
|
It is possible to specify mesh parameters to modify the probed area. The
|
||||||
following parameters are available:
|
following parameters are available:
|
||||||
@@ -398,7 +522,10 @@ following parameters are available:
|
|||||||
- `MESH_ORIGIN`
|
- `MESH_ORIGIN`
|
||||||
- `ROUND_PROBE_COUNT`
|
- `ROUND_PROBE_COUNT`
|
||||||
- All beds:
|
- All beds:
|
||||||
|
- `MESH_PPS`
|
||||||
- `ALGORITHM`
|
- `ALGORITHM`
|
||||||
|
- `ADAPTIVE`
|
||||||
|
- `ADAPTIVE_MARGIN`
|
||||||
|
|
||||||
See the configuration documentation above for details on how each parameter
|
See the configuration documentation above for details on how each parameter
|
||||||
applies to the mesh.
|
applies to the mesh.
|
||||||
@@ -486,11 +613,207 @@ This gcode may be used to clear the internal mesh state.
|
|||||||
|
|
||||||
### Apply X/Y offsets
|
### Apply X/Y offsets
|
||||||
|
|
||||||
`BED_MESH_OFFSET [X=<value>] [Y=<value>]`
|
`BED_MESH_OFFSET [X=<value>] [Y=<value>] [ZFADE=<value>]`
|
||||||
|
|
||||||
This is useful for printers with multiple independent extruders, as an offset
|
This is useful for printers with multiple independent extruders, as an offset
|
||||||
is necessary to produce correct Z adjustment after a tool change. Offsets
|
is necessary to produce correct Z adjustment after a tool change. Offsets
|
||||||
should be specified relative to the primary extruder. That is, a positive
|
should be specified relative to the primary extruder. That is, a positive
|
||||||
X offset should be specified if the secondary extruder is mounted to the
|
X offset should be specified if the secondary extruder is mounted to the
|
||||||
right of the primary extruder, and a positive Y offset should be specified
|
right of the primary extruder, a positive Y offset should be specified
|
||||||
if the secondary extruder is mounted "behind" the primary extruder.
|
if the secondary extruder is mounted "behind" the primary extruder, and
|
||||||
|
a positive ZFADE offset should be specified if the secondary extruder's
|
||||||
|
nozzle is above the primary extruder's.
|
||||||
|
|
||||||
|
Note that a ZFADE offset does *NOT* directly apply additional adjustment. It
|
||||||
|
is intended to compensate for a `gcode offset` when [mesh fade](#mesh-fade)
|
||||||
|
is enabled. For example, if a secondary extruder is higher than the primary
|
||||||
|
and needs a negative gcode offset, ie: `SET_GCODE_OFFSET Z=-.2`, it can be
|
||||||
|
accounted for in `bed_mesh` with `BED_MESH_OFFSET ZFADE=.2`.
|
||||||
|
|
||||||
|
## Bed Mesh Webhooks APIs
|
||||||
|
|
||||||
|
### Dumping mesh data
|
||||||
|
|
||||||
|
`{"id": 123, "method": "bed_mesh/dump_mesh"}`
|
||||||
|
|
||||||
|
Dumps the configuration and state for the current mesh and all
|
||||||
|
saved profiles.
|
||||||
|
|
||||||
|
The `dump_mesh` endpoint takes one optional parameter, `mesh_args`.
|
||||||
|
This parameter must be an object, where the keys and values are
|
||||||
|
parameters available to [BED_MESH_CALIBRATE](#bed_mesh_calibrate).
|
||||||
|
This will update the mesh configuration and probe points using the
|
||||||
|
supplied parameters prior to returning the result. It is recommended
|
||||||
|
to omit mesh parameters unless it is desired to visualize the probe points
|
||||||
|
and/or travel path before performing `BED_MESH_CALIBRATE`.
|
||||||
|
|
||||||
|
## Visualization and analysis
|
||||||
|
|
||||||
|
Most users will likely find that the visualizers included with
|
||||||
|
applications such as Mainsail, Fluidd, and Octoprint are sufficient
|
||||||
|
for basic analysis. However, Klipper's `scripts` folder contains the
|
||||||
|
`graph_mesh.py` script that may be used to perform additional
|
||||||
|
visualizations and more detailed analysis, particularly useful
|
||||||
|
for debugging hardware or the results produced by `bed_mesh`:
|
||||||
|
|
||||||
|
```
|
||||||
|
usage: graph_mesh.py [-h] {list,plot,analyze,dump} ...
|
||||||
|
|
||||||
|
Graph Bed Mesh Data
|
||||||
|
|
||||||
|
positional arguments:
|
||||||
|
{list,plot,analyze,dump}
|
||||||
|
list List available plot types
|
||||||
|
plot Plot a specified type
|
||||||
|
analyze Perform analysis on mesh data
|
||||||
|
dump Dump API response to json file
|
||||||
|
|
||||||
|
options:
|
||||||
|
-h, --help show this help message and exit
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pre-requisites
|
||||||
|
|
||||||
|
Like most graphing tools provided by Klipper, `graph_mesh.py` requires
|
||||||
|
the `matplotlib` and `numpy` python dependencies. In addition, connecting
|
||||||
|
to Klipper via Moonraker's websocket requires the `websockets` python
|
||||||
|
dependency. While all visualizations can be output to an `svg` file, most of
|
||||||
|
the visualizations offered by `graph_mesh.py` are better viewed in live
|
||||||
|
preview mode on a desktop class PC. For example, the 3D visualizations may be
|
||||||
|
rotated and zoomed in preview mode, and the path visualizations can optionally
|
||||||
|
be animated in preview mode.
|
||||||
|
|
||||||
|
### Plotting Mesh data
|
||||||
|
|
||||||
|
The `graph_mesh.py` tool can plot several types of visualizations.
|
||||||
|
Available types can be shown by running `graph_mesh.py list`:
|
||||||
|
|
||||||
|
```
|
||||||
|
graph_mesh.py list
|
||||||
|
points Plot original generated points
|
||||||
|
path Plot probe travel path
|
||||||
|
rapid Plot rapid scan travel path
|
||||||
|
probedz Plot probed Z values
|
||||||
|
meshz Plot mesh Z values
|
||||||
|
overlay Plots the current probed mesh overlaid with a profile
|
||||||
|
delta Plots the delta between current probed mesh and a profile
|
||||||
|
```
|
||||||
|
|
||||||
|
Several options are available when plotting visualizations:
|
||||||
|
|
||||||
|
```
|
||||||
|
usage: graph_mesh.py plot [-h] [-a] [-s] [-p PROFILE_NAME] [-o OUTPUT] <plot type> <input>
|
||||||
|
|
||||||
|
positional arguments:
|
||||||
|
<plot type> Type of data to graph
|
||||||
|
<input> Path/url to Klipper Socket or path to json file
|
||||||
|
|
||||||
|
options:
|
||||||
|
-h, --help show this help message and exit
|
||||||
|
-a, --animate Animate paths in live preview
|
||||||
|
-s, --scale-plot Use axis limits reported by Klipper to scale plot X/Y
|
||||||
|
-p PROFILE_NAME, --profile-name PROFILE_NAME
|
||||||
|
Optional name of a profile to plot for 'probedz'
|
||||||
|
-o OUTPUT, --output OUTPUT
|
||||||
|
Output file path
|
||||||
|
```
|
||||||
|
|
||||||
|
Below is a description of each argument:
|
||||||
|
|
||||||
|
- `plot type`: A required positional argument designating the type of
|
||||||
|
visualization to generate. Must be one of the types output by the
|
||||||
|
`graph_mesh.py list` command.
|
||||||
|
- `input`: A required positional argument containing a path or url
|
||||||
|
to the input source. This must be one of the following:
|
||||||
|
- A path to Klipper's Unix Domain Socket
|
||||||
|
- A url to an instance of Moonraker
|
||||||
|
- A path to a json file produced by `graph_mesh.py dump <input>`
|
||||||
|
- `-a`: Optional animation for the `path` and `rapid` visualization types.
|
||||||
|
Animations only apply to a live preview.
|
||||||
|
- `-s`: Optionally scales a plot using the `axis_minimum` and `axis_maximum`
|
||||||
|
values reported by Klipper's `toolhead` object when the dump file was
|
||||||
|
generated.
|
||||||
|
- `-p`: A profile name that may be specified when generating the
|
||||||
|
`probedz` 3D mesh visualization. When generating an `overlay` or
|
||||||
|
`delta` visualization this argument must be provided.
|
||||||
|
- `-o`: An optional file path indicating that the script should save the
|
||||||
|
visualization to this location rather than run in preview mode. Images
|
||||||
|
are saved in `svg` format.
|
||||||
|
|
||||||
|
For example, to plot an animated rapid path, connecting via Klipper's unix
|
||||||
|
socket:
|
||||||
|
|
||||||
|
```
|
||||||
|
graph_mesh.py plot -a rapid ~/printer_data/comms/klippy.sock
|
||||||
|
```
|
||||||
|
|
||||||
|
Or to plot a 3d visualization of the mesh, connecting via Moonraker:
|
||||||
|
|
||||||
|
```
|
||||||
|
graph_mesh.py plot meshz http://my-printer.local
|
||||||
|
```
|
||||||
|
|
||||||
|
### Bed Mesh Analysis
|
||||||
|
|
||||||
|
The `graph_mesh.py` tool may also be used to perform an analysis on the
|
||||||
|
data provided by the [bed_mesh/dump_mesh](#dumping-mesh-data) API:
|
||||||
|
|
||||||
|
```
|
||||||
|
graph_mesh.py analyze <input>
|
||||||
|
```
|
||||||
|
|
||||||
|
As with the `plot` command, the `<input>` must be a path to Klipper's
|
||||||
|
unix socket, a URL to an instance of Moonraker, or a path to a json file
|
||||||
|
generated by the dump command.
|
||||||
|
|
||||||
|
To begin, the analysis will perform various checks on the points and
|
||||||
|
probe paths generated by `bed_mesh` at the time of the dump. This
|
||||||
|
includes the following:
|
||||||
|
|
||||||
|
- The number of probe points generated, without any additions
|
||||||
|
- The number of probe points generated including any points generated
|
||||||
|
as the result faulty regions and/or a configured zero reference position.
|
||||||
|
- The number of probe points generated when performing a rapid scan.
|
||||||
|
- The total number of moves generated for a rapid scan.
|
||||||
|
- A validation that the probe points generated for a rapid scan are
|
||||||
|
identical to the probe points generated for a standard probing procedure.
|
||||||
|
- A "backtracking" check for both the standard probe path and a rapid scan
|
||||||
|
path. Backtracking can be defined as moving to the same position more than
|
||||||
|
once during the probing procedure. Backtracking should never occur during a
|
||||||
|
standard probe. Faulty regions *can* result in backtracking during a rapid
|
||||||
|
scan in an attempt to avoid entering a faulty region when approaching or
|
||||||
|
leaving a probe location, however should never occur otherwise.
|
||||||
|
|
||||||
|
Next each probed mesh present in the dump will by analyzed, beginning with
|
||||||
|
the mesh loaded at the time of the dump (if present) and followed by any
|
||||||
|
saved profiles. The following data is extracted:
|
||||||
|
|
||||||
|
- Mesh shape (Min X,Y, Max X,Y Probe Count)
|
||||||
|
- Mesh Z range, (Minimum Z, Maximum Z)
|
||||||
|
- Mean Z value in the mesh
|
||||||
|
- Standard Deviation of the Z values in the Mesh
|
||||||
|
|
||||||
|
In addition to the above, a delta analysis is performed between meshes
|
||||||
|
with the same shape, reporting the following:
|
||||||
|
- The range of the delta between to meshes (Minimum and Maximum)
|
||||||
|
- The mean delta
|
||||||
|
- Standard Deviation of the delta
|
||||||
|
- The absolute maximum difference
|
||||||
|
- The absolute mean
|
||||||
|
|
||||||
|
### Save mesh data to a file
|
||||||
|
|
||||||
|
The `dump` command may be used to save the response to a file which
|
||||||
|
can be shared for analysis when troubleshooting:
|
||||||
|
|
||||||
|
```
|
||||||
|
graph_mesh.py dump -o <output file name> <input>
|
||||||
|
```
|
||||||
|
|
||||||
|
The `<input>` should be a path to Klipper's unix socket or
|
||||||
|
a URL to an instance of Moonraker. The `-o` option may be used to
|
||||||
|
specify the path to the output file. If omitted, the file will be
|
||||||
|
saved in the working directory, with a file name in the following
|
||||||
|
format:
|
||||||
|
|
||||||
|
`klipper-bedmesh-{year}{month}{day}{hour}{minute}{second}.json`
|
||||||
|
|||||||
@@ -354,6 +354,26 @@ micro-controller.
|
|||||||
| 1 stepper (200Mhz) | 39 |
|
| 1 stepper (200Mhz) | 39 |
|
||||||
| 3 stepper (200Mhz) | 181 |
|
| 3 stepper (200Mhz) | 181 |
|
||||||
|
|
||||||
|
### SAME70 step rate benchmark
|
||||||
|
|
||||||
|
The following configuration sequence is used on the SAME70:
|
||||||
|
```
|
||||||
|
allocate_oids count=3
|
||||||
|
config_stepper oid=0 step_pin=PC18 dir_pin=PB5 invert_step=-1 step_pulse_ticks=0
|
||||||
|
config_stepper oid=1 step_pin=PC16 dir_pin=PD10 invert_step=-1 step_pulse_ticks=0
|
||||||
|
config_stepper oid=2 step_pin=PC28 dir_pin=PA4 invert_step=-1 step_pulse_ticks=0
|
||||||
|
finalize_config crc=0
|
||||||
|
```
|
||||||
|
|
||||||
|
The test was last run on commit `34e9ea55` with gcc version
|
||||||
|
`arm-none-eabi-gcc (NixOS 10.3-2021.10) 10.3.1` on a SAME70Q20B
|
||||||
|
micro-controller.
|
||||||
|
|
||||||
|
| same70 | ticks |
|
||||||
|
| -------------------- | ----- |
|
||||||
|
| 1 stepper | 45 |
|
||||||
|
| 3 stepper | 190 |
|
||||||
|
|
||||||
### AR100 step rate benchmark ###
|
### AR100 step rate benchmark ###
|
||||||
|
|
||||||
The following configuration sequence is used on AR100 CPU (Allwinner A64):
|
The following configuration sequence is used on AR100 CPU (Allwinner A64):
|
||||||
@@ -366,7 +386,7 @@ finalize_config crc=0
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The test was last run on commit `08d037c6` with gcc version
|
The test was last run on commit `b7978d37` with gcc version
|
||||||
`or1k-linux-musl-gcc (GCC) 9.2.0` on an Allwinner A64-H
|
`or1k-linux-musl-gcc (GCC) 9.2.0` on an Allwinner A64-H
|
||||||
micro-controller.
|
micro-controller.
|
||||||
|
|
||||||
@@ -375,9 +395,9 @@ micro-controller.
|
|||||||
| 1 stepper | 85 |
|
| 1 stepper | 85 |
|
||||||
| 3 stepper | 359 |
|
| 3 stepper | 359 |
|
||||||
|
|
||||||
### RP2040 step rate benchmark
|
### RPxxxx step rate benchmark
|
||||||
|
|
||||||
The following configuration sequence is used on the RP2040:
|
The following configuration sequence is used on the RP2040 and RP2350:
|
||||||
|
|
||||||
```
|
```
|
||||||
allocate_oids count=3
|
allocate_oids count=3
|
||||||
@@ -387,15 +407,26 @@ config_stepper oid=2 step_pin=gpio27 dir_pin=gpio5 invert_step=-1 step_pulse_tic
|
|||||||
finalize_config crc=0
|
finalize_config crc=0
|
||||||
```
|
```
|
||||||
|
|
||||||
The test was last run on commit `59314d99` with gcc version
|
The test was last run on commit `f6718291` with gcc version
|
||||||
`arm-none-eabi-gcc (Fedora 10.2.0-4.fc34) 10.2.0` on a Raspberry Pi
|
`arm-none-eabi-gcc (Fedora 14.1.0-1.fc40) 14.1.0` on Raspberry Pi
|
||||||
Pico board.
|
Pico and Pico 2 boards.
|
||||||
|
|
||||||
| rp2040 | ticks |
|
| rp2040 (*) | ticks |
|
||||||
| -------------------- | ----- |
|
| -------------------- | ----- |
|
||||||
| 1 stepper | 5 |
|
| 1 stepper | 5 |
|
||||||
| 3 stepper | 22 |
|
| 3 stepper | 22 |
|
||||||
|
|
||||||
|
| rp2350 | ticks |
|
||||||
|
| -------------------- | ----- |
|
||||||
|
| 1 stepper | 36 |
|
||||||
|
| 3 stepper | 169 |
|
||||||
|
|
||||||
|
(*) Note that the reported rp2040 ticks are relative to a 12Mhz
|
||||||
|
scheduling timer and do not correspond to its 125Mhz internal ARM
|
||||||
|
processing rate. It is expected that 5 scheduling ticks corresponds to
|
||||||
|
~47 ARM core cycles and 22 scheduling ticks corresponds to ~224 ARM
|
||||||
|
core cycles.
|
||||||
|
|
||||||
### Linux MCU step rate benchmark
|
### Linux MCU step rate benchmark
|
||||||
|
|
||||||
The following configuration sequence is used on a Raspberry Pi:
|
The following configuration sequence is used on a Raspberry Pi:
|
||||||
@@ -456,7 +487,8 @@ hub.
|
|||||||
| sam4s8c (USB) | 650K | 8d4a5c16 | arm-none-eabi-gcc (Fedora 7.4.0-1.fc30) 7.4.0 |
|
| sam4s8c (USB) | 650K | 8d4a5c16 | arm-none-eabi-gcc (Fedora 7.4.0-1.fc30) 7.4.0 |
|
||||||
| samd51 (USB) | 864K | 01d2183f | arm-none-eabi-gcc (Fedora 7.4.0-1.fc30) 7.4.0 |
|
| samd51 (USB) | 864K | 01d2183f | arm-none-eabi-gcc (Fedora 7.4.0-1.fc30) 7.4.0 |
|
||||||
| stm32f446 (USB) | 870K | 01d2183f | arm-none-eabi-gcc (Fedora 7.4.0-1.fc30) 7.4.0 |
|
| stm32f446 (USB) | 870K | 01d2183f | arm-none-eabi-gcc (Fedora 7.4.0-1.fc30) 7.4.0 |
|
||||||
| rp2040 (USB) | 873K | c5667193 | arm-none-eabi-gcc (Fedora 10.2.0-4.fc34) 10.2.0 |
|
| rp2040 (USB) | 885K | f6718291 | arm-none-eabi-gcc (Fedora 14.1.0-1.fc40) 14.1.0 |
|
||||||
|
| rp2350 (USB) | 885K | f6718291 | arm-none-eabi-gcc (Fedora 14.1.0-1.fc40) 14.1.0 |
|
||||||
|
|
||||||
## Host Benchmarks
|
## Host Benchmarks
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ adapter. This is typically done by creating a new file named
|
|||||||
allow-hotplug can0
|
allow-hotplug can0
|
||||||
iface can0 can static
|
iface can0 can static
|
||||||
bitrate 1000000
|
bitrate 1000000
|
||||||
up ifconfig $IFACE txqueuelen 128
|
up ip link set $IFACE txqueuelen 128
|
||||||
```
|
```
|
||||||
|
|
||||||
## Terminating Resistors
|
## Terminating Resistors
|
||||||
@@ -113,7 +113,7 @@ Some important notes when using this mode:
|
|||||||
allow-hotplug can0
|
allow-hotplug can0
|
||||||
iface can0 can static
|
iface can0 can static
|
||||||
bitrate 1000000
|
bitrate 1000000
|
||||||
up ifconfig $IFACE txqueuelen 128
|
up ip link set $IFACE txqueuelen 128
|
||||||
```
|
```
|
||||||
|
|
||||||
* The "bridge mcu" is not actually on the CAN bus. Messages to and
|
* The "bridge mcu" is not actually on the CAN bus. Messages to and
|
||||||
|
|||||||
@@ -52,6 +52,56 @@ Reordered messages is a severe problem that must be fixed. It will
|
|||||||
result in unstable behavior and can lead to confusing errors at any
|
result in unstable behavior and can lead to confusing errors at any
|
||||||
part of a print.
|
part of a print.
|
||||||
|
|
||||||
|
## Use an appropriate txqueuelen setting
|
||||||
|
|
||||||
|
The Klipper code uses the Linux kernel to manage CAN bus traffic. By
|
||||||
|
default, the kernel will only queue 10 CAN transmit packets. It is
|
||||||
|
recommended to [configure the can0 device](CANBUS.md#host-hardware)
|
||||||
|
with a `txqueuelen 128` to increase that size.
|
||||||
|
|
||||||
|
If Klipper transmits a packet and Linux has filled all of its transmit
|
||||||
|
queue space then Linux will drop that packet and messages like the
|
||||||
|
following will appear in the Klipper log:
|
||||||
|
```
|
||||||
|
Got error -1 in can write: (105)No buffer space available
|
||||||
|
```
|
||||||
|
Klipper will automatically retransmit the lost messages as part of its
|
||||||
|
normal application level message retransmit system. Thus, this log
|
||||||
|
message is a warning and it does not indicate an unrecoverable error.
|
||||||
|
|
||||||
|
If a complete CAN bus failure occurs (such as a CAN wire break) then
|
||||||
|
Linux will not be able to transmit any messages on the CAN bus and it
|
||||||
|
is common to find the above message in the Klipper log. In this case,
|
||||||
|
the log message is a symptom of a larger problem (the inability to
|
||||||
|
transmit any messages) and is not directly related to Linux
|
||||||
|
`txqueuelen`.
|
||||||
|
|
||||||
|
One may check the current queue size by running the Linux command `ip
|
||||||
|
link show can0`. It should report a bunch of text including the
|
||||||
|
snippet `qlen 128`. If one sees something like `qlen 10` then it
|
||||||
|
indicates the CAN device has not been properly configured.
|
||||||
|
|
||||||
|
It is not recommended to use a `txqueuelen` significantly larger
|
||||||
|
than 128. A CAN bus running at a frequency of 1000000 will typically
|
||||||
|
take around 120us to transmit a CAN packet. Thus a queue of 128
|
||||||
|
packets is likely to take around 15-20ms to drain. A substantially
|
||||||
|
larger queue could cause excessive spikes in message round-trip-time
|
||||||
|
which could lead to unrecoverable errors. Said another way, Klipper's
|
||||||
|
application retransmit system is more robust if it does not have to
|
||||||
|
wait for Linux to drain an excessively large queue of possibly stale
|
||||||
|
data. This is analogous to the problem of
|
||||||
|
[bufferbloat](https://en.wikipedia.org/wiki/Bufferbloat) on internet
|
||||||
|
routers.
|
||||||
|
|
||||||
|
Under normal circumstances Klipper may utilize ~25 queue slots per
|
||||||
|
MCU - typically only utilizing more slots during retransmits.
|
||||||
|
(Specifically, the Klipper host may transmit up to 192 bytes to each
|
||||||
|
Klipper MCU before receiving an acknowledgment from that MCU.) If a
|
||||||
|
single CAN bus has 5 or more Klipper MCUs on it, then it might be
|
||||||
|
necessary to increase the `txqueuelen` above the recommended value
|
||||||
|
of 128. However, as above, care should be taken when selecting a new
|
||||||
|
value to avoid excessive round-trip-time latency.
|
||||||
|
|
||||||
## Obtaining candump logs
|
## Obtaining candump logs
|
||||||
|
|
||||||
The CAN bus messages sent to and from the micro-controller are handled
|
The CAN bus messages sent to and from the micro-controller are handled
|
||||||
|
|||||||
@@ -136,8 +136,9 @@ provides further information on the mechanics of moves.
|
|||||||
|
|
||||||
* The ToolHead class (in toolhead.py) handles "look-ahead" and tracks
|
* The ToolHead class (in toolhead.py) handles "look-ahead" and tracks
|
||||||
the timing of printing actions. The main codepath for a move is:
|
the timing of printing actions. The main codepath for a move is:
|
||||||
`ToolHead.move() -> MoveQueue.add_move() -> MoveQueue.flush() ->
|
`ToolHead.move() -> LookAheadQueue.add_move() ->
|
||||||
Move.set_junction() -> ToolHead._process_moves()`.
|
LookAheadQueue.flush() -> Move.set_junction() ->
|
||||||
|
ToolHead._process_moves()`.
|
||||||
* ToolHead.move() creates a Move() object with the parameters of the
|
* ToolHead.move() creates a Move() object with the parameters of the
|
||||||
move (in cartesian space and in units of seconds and millimeters).
|
move (in cartesian space and in units of seconds and millimeters).
|
||||||
* The kinematics class is given the opportunity to audit each move
|
* The kinematics class is given the opportunity to audit each move
|
||||||
@@ -146,10 +147,10 @@ provides further information on the mechanics of moves.
|
|||||||
may raise an error if the move is not valid. If check_move()
|
may raise an error if the move is not valid. If check_move()
|
||||||
completes successfully then the underlying kinematics must be able
|
completes successfully then the underlying kinematics must be able
|
||||||
to handle the move.
|
to handle the move.
|
||||||
* MoveQueue.add_move() places the move object on the "look-ahead"
|
* LookAheadQueue.add_move() places the move object on the
|
||||||
queue.
|
"look-ahead" queue.
|
||||||
* MoveQueue.flush() determines the start and end velocities of each
|
* LookAheadQueue.flush() determines the start and end velocities of
|
||||||
move.
|
each move.
|
||||||
* Move.set_junction() implements the "trapezoid generator" on a
|
* Move.set_junction() implements the "trapezoid generator" on a
|
||||||
move. The "trapezoid generator" breaks every move into three parts:
|
move. The "trapezoid generator" breaks every move into three parts:
|
||||||
a constant acceleration phase, followed by a constant velocity
|
a constant acceleration phase, followed by a constant velocity
|
||||||
@@ -170,17 +171,18 @@ provides further information on the mechanics of moves.
|
|||||||
placed on a "trapezoid motion queue": `ToolHead._process_moves() ->
|
placed on a "trapezoid motion queue": `ToolHead._process_moves() ->
|
||||||
trapq_append()` (in klippy/chelper/trapq.c). The step times are then
|
trapq_append()` (in klippy/chelper/trapq.c). The step times are then
|
||||||
generated: `ToolHead._process_moves() ->
|
generated: `ToolHead._process_moves() ->
|
||||||
ToolHead._update_move_time() -> MCU_Stepper.generate_steps() ->
|
ToolHead._advance_move_time() -> ToolHead._advance_flush_time() ->
|
||||||
itersolve_generate_steps() -> itersolve_gen_steps_range()` (in
|
MCU_Stepper.generate_steps() -> itersolve_generate_steps() ->
|
||||||
klippy/chelper/itersolve.c). The goal of the iterative solver is to
|
itersolve_gen_steps_range()` (in klippy/chelper/itersolve.c). The
|
||||||
find step times given a function that calculates a stepper position
|
goal of the iterative solver is to find step times given a function
|
||||||
from a time. This is done by repeatedly "guessing" various times
|
that calculates a stepper position from a time. This is done by
|
||||||
until the stepper position formula returns the desired position of
|
repeatedly "guessing" various times until the stepper position
|
||||||
the next step on the stepper. The feedback produced from each guess
|
formula returns the desired position of the next step on the
|
||||||
is used to improve future guesses so that the process rapidly
|
stepper. The feedback produced from each guess is used to improve
|
||||||
converges to the desired time. The kinematic stepper position
|
future guesses so that the process rapidly converges to the desired
|
||||||
formulas are located in the klippy/chelper/ directory (eg,
|
time. The kinematic stepper position formulas are located in the
|
||||||
kin_cart.c, kin_corexy.c, kin_delta.c, kin_extruder.c).
|
klippy/chelper/ directory (eg, kin_cart.c, kin_corexy.c,
|
||||||
|
kin_delta.c, kin_extruder.c).
|
||||||
|
|
||||||
* Note that the extruder is handled in its own kinematic class:
|
* Note that the extruder is handled in its own kinematic class:
|
||||||
`ToolHead._process_moves() -> PrinterExtruder.move()`. Since
|
`ToolHead._process_moves() -> PrinterExtruder.move()`. Since
|
||||||
@@ -357,10 +359,10 @@ Useful steps:
|
|||||||
be efficient as it is typically only called during homing and
|
be efficient as it is typically only called during homing and
|
||||||
probing operations.
|
probing operations.
|
||||||
5. Other methods. Implement the `check_move()`, `get_status()`,
|
5. Other methods. Implement the `check_move()`, `get_status()`,
|
||||||
`get_steppers()`, `home()`, and `set_position()` methods. These
|
`get_steppers()`, `home()`, `clear_homing_state()`, and `set_position()`
|
||||||
functions are typically used to provide kinematic specific checks.
|
methods. These functions are typically used to provide kinematic
|
||||||
However, at the start of development one can use boiler-plate code
|
specific checks. However, at the start of development one can use
|
||||||
here.
|
boiler-plate code here.
|
||||||
6. Implement test cases. Create a g-code file with a series of moves
|
6. Implement test cases. Create a g-code file with a series of moves
|
||||||
that can test important cases for the given kinematics. Follow the
|
that can test important cases for the given kinematics. Follow the
|
||||||
[debugging documentation](Debugging.md) to convert this g-code file
|
[debugging documentation](Debugging.md) to convert this g-code file
|
||||||
|
|||||||
@@ -8,6 +8,84 @@ All dates in this document are approximate.
|
|||||||
|
|
||||||
## Changes
|
## Changes
|
||||||
|
|
||||||
|
20241203: The resonance test has been changed to include slow sweeping
|
||||||
|
moves. This change requires that testing point(s) have some clearance
|
||||||
|
in X/Y plane (+/- 30 mm from the test point should suffice when using
|
||||||
|
the default settings). The new test should generally produce more
|
||||||
|
accurate and reliable test results. However, if required, the previous
|
||||||
|
test behavior can be restored by adding options `sweeping_period: 0` and
|
||||||
|
`accel_per_hz: 75` to the `[resonance_tester]` config section.
|
||||||
|
|
||||||
|
20241201: In some cases Klipper may have ignored leading characters or
|
||||||
|
spaces in a traditional G-Code command. For example, "99M123" may have
|
||||||
|
been interpreted as "M123" and "M 321" may have been interpreted as
|
||||||
|
"M321". Klipper will now report these cases with an "Unknown command"
|
||||||
|
warning.
|
||||||
|
|
||||||
|
20241112: Option `CHIPS=<chip_name>` in `TEST_RESONANCES` and
|
||||||
|
`SHAPER_CALIBRATE` requires specifying the full name(s) of the accel
|
||||||
|
chip(s). For example, `adxl345 rpi` instead of short name - `rpi`.
|
||||||
|
|
||||||
|
20240912: `SET_PIN`, `SET_SERVO`, `SET_FAN_SPEED`, `M106`, and `M107`
|
||||||
|
commands are now collated. Previously, if many updates to the same
|
||||||
|
object were issued faster than the minimum scheduling time (typically
|
||||||
|
100ms) then actual updates could be queued far into the future. Now if
|
||||||
|
many updates are issued in rapid succession then it is possible that
|
||||||
|
only the latest request will be applied. If the previous behavior is
|
||||||
|
requried then consider adding explicit `G4` delay commands between
|
||||||
|
updates.
|
||||||
|
|
||||||
|
20240912: Support for `maximum_mcu_duration` and `static_value`
|
||||||
|
parameters in `[output_pin]` config sections have been removed. These
|
||||||
|
options have been deprecated since 20240123.
|
||||||
|
|
||||||
|
20240415: The `on_error_gcode` parameter in the `[virtual_sdcard]`
|
||||||
|
config section now has a default. If this parameter is not specified
|
||||||
|
it now defaults to `TURN_OFF_HEATERS`. If the previous behavior is
|
||||||
|
desired (take no default action on an error during a virtual_sdcard
|
||||||
|
print) then define `on_error_gcode` with an empty value.
|
||||||
|
|
||||||
|
20240313: The `max_accel_to_decel` parameter in the `[printer]` config
|
||||||
|
section has been deprecated. The `ACCEL_TO_DECEL` parameter of the
|
||||||
|
`SET_VELOCITY_LIMIT` command has been deprecated. The
|
||||||
|
`printer.toolhead.max_accel_to_decel` status has been removed. Use the
|
||||||
|
[minimum_cruise_ratio parameter](./Config_Reference.md#printer)
|
||||||
|
instead. The deprecated features will be removed in the near future,
|
||||||
|
and using them in the interim may result in subtly different behavior.
|
||||||
|
|
||||||
|
20240215: Several deprecated features have been removed. Using "NTC
|
||||||
|
100K beta 3950" as a thermistor name has been removed (deprecated on
|
||||||
|
20211110). The `SYNC_STEPPER_TO_EXTRUDER` and
|
||||||
|
`SET_EXTRUDER_STEP_DISTANCE` commands have been removed, and the
|
||||||
|
extruder `shared_heater` config option has been removed (deprecated on
|
||||||
|
20220210). The bed_mesh `relative_reference_index` option has been
|
||||||
|
removed (deprecated on 20230619).
|
||||||
|
|
||||||
|
20240123: The output_pin SET_PIN CYCLE_TIME parameter has been
|
||||||
|
removed. Use the new
|
||||||
|
[pwm_cycle_time](Config_Reference.md#pwm_cycle_time) module if it is
|
||||||
|
necessary to dynamically change a pwm pin's cycle time.
|
||||||
|
|
||||||
|
20240123: The output_pin `maximum_mcu_duration` parameter is
|
||||||
|
deprecated. Use a [pwm_tool config section](Config_Reference.md#pwm_tool)
|
||||||
|
instead. The option will be removed in the near future.
|
||||||
|
|
||||||
|
20240123: The output_pin `static_value` parameter is deprecated.
|
||||||
|
Replace with `value` and `shutdown_value` parameters. The option will
|
||||||
|
be removed in the near future.
|
||||||
|
|
||||||
|
20231216: The `[hall_filament_width_sensor]` is changed to trigger filament runout
|
||||||
|
when the thickness of the filament exceeds `max_diameter`. The maximum diameter
|
||||||
|
defaults to `default_nominal_filament_diameter + max_difference`. See
|
||||||
|
[[hall_filament_width_sensor] configuration
|
||||||
|
reference](./Config_Reference.md#hall_filament_width_sensor) for more details.
|
||||||
|
|
||||||
|
20231207: Several undocumented config parameters in the `[printer]`
|
||||||
|
config section have been removed (the buffer_time_low,
|
||||||
|
buffer_time_high, buffer_time_start, and move_flush_time parameters).
|
||||||
|
|
||||||
|
20231110: Klipper v0.12.0 released.
|
||||||
|
|
||||||
20230826: If `safe_distance` is set or calculated to be 0 in `[dual_carriage]`,
|
20230826: If `safe_distance` is set or calculated to be 0 in `[dual_carriage]`,
|
||||||
the carriages proximity checks will be disabled as per documentation. A user
|
the carriages proximity checks will be disabled as per documentation. A user
|
||||||
may wish to configure `safe_distance` explicitly to prevent accidental crashes
|
may wish to configure `safe_distance` explicitly to prevent accidental crashes
|
||||||
|
|||||||
@@ -88,16 +88,31 @@ kinematics:
|
|||||||
# deltesian, polar, winch, or none. This parameter must be specified.
|
# deltesian, polar, winch, or none. This parameter must be specified.
|
||||||
max_velocity:
|
max_velocity:
|
||||||
# Maximum velocity (in mm/s) of the toolhead (relative to the
|
# Maximum velocity (in mm/s) of the toolhead (relative to the
|
||||||
# print). This parameter must be specified.
|
# print). This value may be changed at runtime using the
|
||||||
|
# SET_VELOCITY_LIMIT command. This parameter must be specified.
|
||||||
max_accel:
|
max_accel:
|
||||||
# Maximum acceleration (in mm/s^2) of the toolhead (relative to the
|
# Maximum acceleration (in mm/s^2) of the toolhead (relative to the
|
||||||
# print). This parameter must be specified.
|
# print). Although this parameter is described as a "maximum"
|
||||||
#max_accel_to_decel:
|
# acceleration, in practice most moves that accelerate or decelerate
|
||||||
# A pseudo acceleration (in mm/s^2) controlling how fast the
|
# will do so at the rate specified here. The value specified here
|
||||||
# toolhead may go from acceleration to deceleration. It is used to
|
# may be changed at runtime using the SET_VELOCITY_LIMIT command.
|
||||||
# reduce the top speed of short zig-zag moves (and thus reduce
|
# This parameter must be specified.
|
||||||
# printer vibration from these moves). The default is half of
|
#minimum_cruise_ratio: 0.5
|
||||||
# max_accel.
|
# Most moves will accelerate to a cruising speed, travel at that
|
||||||
|
# cruising speed, and then decelerate. However, some moves that
|
||||||
|
# travel a short distance could nominally accelerate and then
|
||||||
|
# immediately decelerate. This option reduces the top speed of these
|
||||||
|
# moves to ensure there is always a minimum distance traveled at a
|
||||||
|
# cruising speed. That is, it enforces a minimum distance traveled
|
||||||
|
# at cruising speed relative to the total distance traveled. It is
|
||||||
|
# intended to reduce the top speed of short zigzag moves (and thus
|
||||||
|
# reduce printer vibration from these moves). For example, a
|
||||||
|
# minimum_cruise_ratio of 0.5 would ensure that a standalone 1.5mm
|
||||||
|
# move would have a minimum cruising distance of 0.75mm. Specify a
|
||||||
|
# ratio of 0.0 to disable this feature (there would be no minimum
|
||||||
|
# cruising distance enforced between acceleration and deceleration).
|
||||||
|
# The value specified here may be changed at runtime using the
|
||||||
|
# SET_VELOCITY_LIMIT command. The default is 0.5.
|
||||||
#square_corner_velocity: 5.0
|
#square_corner_velocity: 5.0
|
||||||
# The maximum velocity (in mm/s) that the toolhead may travel a 90
|
# The maximum velocity (in mm/s) that the toolhead may travel a 90
|
||||||
# degree corner at. A non-zero value can reduce changes in extruder
|
# degree corner at. A non-zero value can reduce changes in extruder
|
||||||
@@ -107,7 +122,11 @@ max_accel:
|
|||||||
# larger than 90 degrees will have a higher cornering velocity while
|
# larger than 90 degrees will have a higher cornering velocity while
|
||||||
# corners with angles less than 90 degrees will have a lower
|
# corners with angles less than 90 degrees will have a lower
|
||||||
# cornering velocity. If this is set to zero then the toolhead will
|
# cornering velocity. If this is set to zero then the toolhead will
|
||||||
# decelerate to zero at each corner. The default is 5mm/s.
|
# decelerate to zero at each corner. The value specified here may be
|
||||||
|
# changed at runtime using the SET_VELOCITY_LIMIT command. The
|
||||||
|
# default is 5mm/s.
|
||||||
|
#max_accel_to_decel:
|
||||||
|
# This parameter is deprecated and should no longer be used.
|
||||||
```
|
```
|
||||||
|
|
||||||
### [stepper]
|
### [stepper]
|
||||||
@@ -971,18 +990,21 @@ Visual Examples:
|
|||||||
# where Z = 0. When this option is specified the mesh will be offset
|
# where Z = 0. When this option is specified the mesh will be offset
|
||||||
# so that zero Z adjustment occurs at this location. The default is
|
# so that zero Z adjustment occurs at this location. The default is
|
||||||
# no zero reference.
|
# no zero reference.
|
||||||
#relative_reference_index:
|
|
||||||
# **DEPRECATED, use the "zero_reference_position" option**
|
|
||||||
# The legacy option superceded by the "zero reference position".
|
|
||||||
# Rather than a coordinate this option takes an integer "index" that
|
|
||||||
# refers to the location of one of the generated points. It is recommended
|
|
||||||
# to use the "zero_reference_position" instead of this option for new
|
|
||||||
# configurations. The default is no relative reference index.
|
|
||||||
#faulty_region_1_min:
|
#faulty_region_1_min:
|
||||||
#faulty_region_1_max:
|
#faulty_region_1_max:
|
||||||
# Optional points that define a faulty region. See docs/Bed_Mesh.md
|
# Optional points that define a faulty region. See docs/Bed_Mesh.md
|
||||||
# for details on faulty regions. Up to 99 faulty regions may be added.
|
# for details on faulty regions. Up to 99 faulty regions may be added.
|
||||||
# By default no faulty regions are set.
|
# By default no faulty regions are set.
|
||||||
|
#adaptive_margin:
|
||||||
|
# An optional margin (in mm) to be added around the bed area used by
|
||||||
|
# the defined print objects when generating an adaptive mesh.
|
||||||
|
#scan_overshoot:
|
||||||
|
# The maximum amount of travel (in mm) available outside of the mesh.
|
||||||
|
# For rectangular beds this applies to travel on the X axis, and for round beds
|
||||||
|
# it applies to the entire radius. The tool must be able to travel the amount
|
||||||
|
# specified outside of the mesh. This value is used to optimize the travel
|
||||||
|
# path when performing a "rapid scan". The minimum value that may be specified
|
||||||
|
# is 1. The default is no overshoot.
|
||||||
```
|
```
|
||||||
|
|
||||||
### [bed_tilt]
|
### [bed_tilt]
|
||||||
@@ -1457,7 +1479,8 @@ path:
|
|||||||
# be provided.
|
# be provided.
|
||||||
#on_error_gcode:
|
#on_error_gcode:
|
||||||
# A list of G-Code commands to execute when an error is reported.
|
# A list of G-Code commands to execute when an error is reported.
|
||||||
|
# See docs/Command_Templates.md for G-Code format. The default is to
|
||||||
|
# run TURN_OFF_HEATERS.
|
||||||
```
|
```
|
||||||
|
|
||||||
### [sdcard_loop]
|
### [sdcard_loop]
|
||||||
@@ -1652,8 +1675,9 @@ Support for LIS2DW accelerometers.
|
|||||||
|
|
||||||
```
|
```
|
||||||
[lis2dw]
|
[lis2dw]
|
||||||
cs_pin:
|
#cs_pin:
|
||||||
# The SPI enable pin for the sensor. This parameter must be provided.
|
# The SPI enable pin for the sensor. This parameter must be provided
|
||||||
|
# if using SPI.
|
||||||
#spi_speed: 5000000
|
#spi_speed: 5000000
|
||||||
# The SPI speed (in hz) to use when communicating with the chip.
|
# The SPI speed (in hz) to use when communicating with the chip.
|
||||||
# The default is 5000000.
|
# The default is 5000000.
|
||||||
@@ -1663,6 +1687,46 @@ cs_pin:
|
|||||||
#spi_software_miso_pin:
|
#spi_software_miso_pin:
|
||||||
# See the "common SPI settings" section for a description of the
|
# See the "common SPI settings" section for a description of the
|
||||||
# above parameters.
|
# above parameters.
|
||||||
|
#i2c_address:
|
||||||
|
# Default is 25 (0x19). If SA0 is high, it would be 24 (0x18) instead.
|
||||||
|
#i2c_mcu:
|
||||||
|
#i2c_bus:
|
||||||
|
#i2c_software_scl_pin:
|
||||||
|
#i2c_software_sda_pin:
|
||||||
|
#i2c_speed: 400000
|
||||||
|
# See the "common I2C settings" section for a description of the
|
||||||
|
# above parameters. The default "i2c_speed" is 400000.
|
||||||
|
#axes_map: x, y, z
|
||||||
|
# See the "adxl345" section for information on this parameter.
|
||||||
|
```
|
||||||
|
|
||||||
|
### [lis3dh]
|
||||||
|
|
||||||
|
Support for LIS3DH accelerometers.
|
||||||
|
|
||||||
|
```
|
||||||
|
[lis3dh]
|
||||||
|
#cs_pin:
|
||||||
|
# The SPI enable pin for the sensor. This parameter must be provided
|
||||||
|
# if using SPI.
|
||||||
|
#spi_speed: 5000000
|
||||||
|
# The SPI speed (in hz) to use when communicating with the chip.
|
||||||
|
# The default is 5000000.
|
||||||
|
#spi_bus:
|
||||||
|
#spi_software_sclk_pin:
|
||||||
|
#spi_software_mosi_pin:
|
||||||
|
#spi_software_miso_pin:
|
||||||
|
# See the "common SPI settings" section for a description of the
|
||||||
|
# above parameters.
|
||||||
|
#i2c_address:
|
||||||
|
# Default is 25 (0x19). If SA0 is high, it would be 24 (0x18) instead.
|
||||||
|
#i2c_mcu:
|
||||||
|
#i2c_bus:
|
||||||
|
#i2c_software_scl_pin:
|
||||||
|
#i2c_software_sda_pin:
|
||||||
|
#i2c_speed: 400000
|
||||||
|
# See the "common I2C settings" section for a description of the
|
||||||
|
# above parameters. The default "i2c_speed" is 400000.
|
||||||
#axes_map: x, y, z
|
#axes_map: x, y, z
|
||||||
# See the "adxl345" section for information on this parameter.
|
# See the "adxl345" section for information on this parameter.
|
||||||
```
|
```
|
||||||
@@ -1726,11 +1790,14 @@ section of the measuring resonances guide for more information on
|
|||||||
# auto-calibration (with 'SHAPER_CALIBRATE' command). By default no
|
# auto-calibration (with 'SHAPER_CALIBRATE' command). By default no
|
||||||
# maximum smoothing is specified. Refer to Measuring_Resonances guide
|
# maximum smoothing is specified. Refer to Measuring_Resonances guide
|
||||||
# for more details on using this feature.
|
# for more details on using this feature.
|
||||||
|
#move_speed: 50
|
||||||
|
# The speed (in mm/s) to move the toolhead to and between test points
|
||||||
|
# during the calibration. The default is 50.
|
||||||
#min_freq: 5
|
#min_freq: 5
|
||||||
# Minimum frequency to test for resonances. The default is 5 Hz.
|
# Minimum frequency to test for resonances. The default is 5 Hz.
|
||||||
#max_freq: 133.33
|
#max_freq: 133.33
|
||||||
# Maximum frequency to test for resonances. The default is 133.33 Hz.
|
# Maximum frequency to test for resonances. The default is 133.33 Hz.
|
||||||
#accel_per_hz: 75
|
#accel_per_hz: 60
|
||||||
# This parameter is used to determine which acceleration to use to
|
# This parameter is used to determine which acceleration to use to
|
||||||
# test a specific frequency: accel = accel_per_hz * freq. Higher the
|
# test a specific frequency: accel = accel_per_hz * freq. Higher the
|
||||||
# value, the higher is the energy of the oscillations. Can be set to
|
# value, the higher is the energy of the oscillations. Can be set to
|
||||||
@@ -1744,6 +1811,13 @@ section of the measuring resonances guide for more information on
|
|||||||
# hz_per_sec. Small values make the test slow, and the large values
|
# hz_per_sec. Small values make the test slow, and the large values
|
||||||
# will decrease the precision of the test. The default value is 1.0
|
# will decrease the precision of the test. The default value is 1.0
|
||||||
# (Hz/sec == sec^-2).
|
# (Hz/sec == sec^-2).
|
||||||
|
#sweeping_accel: 400
|
||||||
|
# An acceleration of slow sweeping moves. The default is 400 mm/sec^2.
|
||||||
|
#sweeping_period: 1.2
|
||||||
|
# A period of slow sweeping moves. Setting this parameter to 0
|
||||||
|
# disables slow sweeping moves. Avoid setting it to a too small
|
||||||
|
# non-zero value in order to not poison the measurements.
|
||||||
|
# The default is 1.2 sec which is a good all-round choice.
|
||||||
```
|
```
|
||||||
|
|
||||||
## Config file helpers
|
## Config file helpers
|
||||||
@@ -1980,11 +2054,48 @@ z_offset:
|
|||||||
# See the "probe" section for more information on the parameters above.
|
# See the "probe" section for more information on the parameters above.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### [probe_eddy_current]
|
||||||
|
|
||||||
|
Support for eddy current inductive probes. One may define this section
|
||||||
|
(instead of a probe section) to enable this probe. See the
|
||||||
|
[command reference](G-Codes.md#probe_eddy_current) for further information.
|
||||||
|
|
||||||
|
```
|
||||||
|
[probe_eddy_current my_eddy_probe]
|
||||||
|
sensor_type: ldc1612
|
||||||
|
# The sensor chip used to perform eddy current measurements. This
|
||||||
|
# parameter must be provided and must be set to ldc1612.
|
||||||
|
#intb_pin:
|
||||||
|
# MCU gpio pin connected to the ldc1612 sensor's INTB pin (if
|
||||||
|
# available). The default is to not use the INTB pin.
|
||||||
|
#z_offset:
|
||||||
|
# The nominal distance (in mm) between the nozzle and bed that a
|
||||||
|
# probing attempt should stop at. This parameter must be provided.
|
||||||
|
#i2c_address:
|
||||||
|
#i2c_mcu:
|
||||||
|
#i2c_bus:
|
||||||
|
#i2c_software_scl_pin:
|
||||||
|
#i2c_software_sda_pin:
|
||||||
|
#i2c_speed:
|
||||||
|
# The i2c settings for the sensor chip. See the "common I2C
|
||||||
|
# settings" section for a description of the above parameters.
|
||||||
|
#x_offset:
|
||||||
|
#y_offset:
|
||||||
|
#speed:
|
||||||
|
#lift_speed:
|
||||||
|
#samples:
|
||||||
|
#sample_retract_dist:
|
||||||
|
#samples_result:
|
||||||
|
#samples_tolerance:
|
||||||
|
#samples_tolerance_retries:
|
||||||
|
# See the "probe" section for information on these parameters.
|
||||||
|
```
|
||||||
|
|
||||||
### [axis_twist_compensation]
|
### [axis_twist_compensation]
|
||||||
|
|
||||||
A tool to compensate for inaccurate probe readings due to twist in X gantry. See
|
A tool to compensate for inaccurate probe readings due to twist in X or Y
|
||||||
the [Axis Twist Compensation Guide](Axis_Twist_Compensation.md) for more
|
gantry. See the [Axis Twist Compensation Guide](Axis_Twist_Compensation.md)
|
||||||
detailed information regarding symptoms, configuration and setup.
|
for more detailed information regarding symptoms, configuration and setup.
|
||||||
|
|
||||||
```
|
```
|
||||||
[axis_twist_compensation]
|
[axis_twist_compensation]
|
||||||
@@ -1997,16 +2108,33 @@ detailed information regarding symptoms, configuration and setup.
|
|||||||
calibrate_start_x: 20
|
calibrate_start_x: 20
|
||||||
# Defines the minimum X coordinate of the calibration
|
# Defines the minimum X coordinate of the calibration
|
||||||
# This should be the X coordinate that positions the nozzle at the starting
|
# This should be the X coordinate that positions the nozzle at the starting
|
||||||
# calibration position. This parameter must be provided.
|
# calibration position.
|
||||||
calibrate_end_x: 200
|
calibrate_end_x: 200
|
||||||
# Defines the maximum X coordinate of the calibration
|
# Defines the maximum X coordinate of the calibration
|
||||||
# This should be the X coordinate that positions the nozzle at the ending
|
# This should be the X coordinate that positions the nozzle at the ending
|
||||||
# calibration position. This parameter must be provided.
|
# calibration position.
|
||||||
calibrate_y: 112.5
|
calibrate_y: 112.5
|
||||||
# Defines the Y coordinate of the calibration
|
# Defines the Y coordinate of the calibration
|
||||||
# This should be the Y coordinate that positions the nozzle during the
|
# This should be the Y coordinate that positions the nozzle during the
|
||||||
# calibration process. This parameter must be provided and is recommended to
|
# calibration process. This parameter is recommended to
|
||||||
# be near the center of the bed
|
# be near the center of the bed
|
||||||
|
|
||||||
|
# For Y-axis twist compensation, specify the following parameters:
|
||||||
|
calibrate_start_y: ...
|
||||||
|
# Defines the minimum Y coordinate of the calibration
|
||||||
|
# This should be the Y coordinate that positions the nozzle at the starting
|
||||||
|
# calibration position for the Y axis. This parameter must be provided if
|
||||||
|
# compensating for Y axis twist.
|
||||||
|
calibrate_end_y: ...
|
||||||
|
# Defines the maximum Y coordinate of the calibration
|
||||||
|
# This should be the Y coordinate that positions the nozzle at the ending
|
||||||
|
# calibration position for the Y axis. This parameter must be provided if
|
||||||
|
# compensating for Y axis twist.
|
||||||
|
calibrate_x: ...
|
||||||
|
# Defines the X coordinate of the calibration for Y axis twist compensation
|
||||||
|
# This should be the X coordinate that positions the nozzle during the
|
||||||
|
# calibration process for Y axis twist compensation. This parameter must be
|
||||||
|
# provided and is recommended to be near the center of the bed.
|
||||||
```
|
```
|
||||||
|
|
||||||
## Additional stepper motors and extruders
|
## Additional stepper motors and extruders
|
||||||
@@ -2341,6 +2469,69 @@ temperature sensors that are reported via the M105 command.
|
|||||||
# parameter.
|
# parameter.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### [temperature_probe]
|
||||||
|
|
||||||
|
Reports probe coil temperature. Includes optional thermal drift
|
||||||
|
calibration for eddy current based probes. A `[temperature_probe]`
|
||||||
|
section may be linked to a `[probe_eddy_current]` by using the same
|
||||||
|
postfix for both sections.
|
||||||
|
|
||||||
|
```
|
||||||
|
[temperature_probe my_probe]
|
||||||
|
#sensor_type:
|
||||||
|
#sensor_pin:
|
||||||
|
#min_temp:
|
||||||
|
#max_temp:
|
||||||
|
# Temperature sensor configuration.
|
||||||
|
# See the "extruder" section for the definition of the above
|
||||||
|
# parameters.
|
||||||
|
#smooth_time:
|
||||||
|
# A time value (in seconds) over which temperature measurements will
|
||||||
|
# be smoothed to reduce the impact of measurement noise. The default
|
||||||
|
# is 2.0 seconds.
|
||||||
|
#gcode_id:
|
||||||
|
# See the "heater_generic" section for the definition of this
|
||||||
|
# parameter.
|
||||||
|
#speed:
|
||||||
|
# The travel speed [mm/s] for xy moves during calibration. Default
|
||||||
|
# is the speed defined by the probe.
|
||||||
|
#horizontal_move_z:
|
||||||
|
# The z distance [mm] from the bed at which xy moves will occur
|
||||||
|
# during calibration. Default is 2mm.
|
||||||
|
#resting_z:
|
||||||
|
# The z distance [mm] from the bed at which the tool will rest
|
||||||
|
# to heat the probe coil during calibration. Default is .4mm
|
||||||
|
#calibration_position:
|
||||||
|
# The X, Y, Z position where the tool should be moved when
|
||||||
|
# probe drift calibration initializes. This is the location
|
||||||
|
# where the first manual probe will occur. If omitted, the
|
||||||
|
# default behavior is not to move the tool prior to the first
|
||||||
|
# manual probe.
|
||||||
|
#calibration_bed_temp:
|
||||||
|
# The maximum safe bed temperature (in C) used to heat the probe
|
||||||
|
# during probe drift calibration. When set, the calibration
|
||||||
|
# procedure will turn on the bed after the first sample is
|
||||||
|
# taken. When the calibration procedure is complete the bed
|
||||||
|
# temperature will be set to zero. When omitted the default
|
||||||
|
# behavior is not to set the bed temperature.
|
||||||
|
#calibration_extruder_temp:
|
||||||
|
# The extruder temperature (in C) set probe during drift calibration.
|
||||||
|
# When this option is supplied the procedure will wait for until the
|
||||||
|
# specified temperature is reached before requesting the first manual
|
||||||
|
# probe. When the calibration procedure is complete the extruder
|
||||||
|
# temperature will be set to 0. When omitted the default behavior is
|
||||||
|
# not to set the extruder temperature.
|
||||||
|
#extruder_heating_z: 50.
|
||||||
|
# The Z location where extruder heating will occur if the
|
||||||
|
# "calibration_extruder_temp" option is set. Its recommended to heat
|
||||||
|
# the extruder some distance from the bed to minimize its impact on
|
||||||
|
# the probe coil temperature. The default is 50.
|
||||||
|
#max_validation_temp: 60.
|
||||||
|
# The maximum temperature used to validate the calibration. It is
|
||||||
|
# recommended to set this to a value between 100 and 120 for enclosed
|
||||||
|
# printers. The default is 60.
|
||||||
|
```
|
||||||
|
|
||||||
## Temperature sensors
|
## Temperature sensors
|
||||||
|
|
||||||
Klipper includes definitions for many types of temperature sensors.
|
Klipper includes definitions for many types of temperature sensors.
|
||||||
@@ -2440,9 +2631,9 @@ sensor_pin:
|
|||||||
# name in the above list.
|
# name in the above list.
|
||||||
```
|
```
|
||||||
|
|
||||||
### BMP280/BME280/BME680 temperature sensor
|
### BMP180/BMP280/BME280/BMP388/BME680 temperature sensor
|
||||||
|
|
||||||
BMP280/BME280/BME680 two wire interface (I2C) environmental sensors.
|
BMP180/BMP280/BME280/BMP388/BME680 two wire interface (I2C) environmental sensors.
|
||||||
Note that these sensors are not intended for use with extruders and
|
Note that these sensors are not intended for use with extruders and
|
||||||
heater beds, but rather for monitoring ambient temperature (C),
|
heater beds, but rather for monitoring ambient temperature (C),
|
||||||
pressure (hPa), relative humidity and in case of the BME680 gas level.
|
pressure (hPa), relative humidity and in case of the BME680 gas level.
|
||||||
@@ -2453,8 +2644,8 @@ temperature.
|
|||||||
```
|
```
|
||||||
sensor_type: BME280
|
sensor_type: BME280
|
||||||
#i2c_address:
|
#i2c_address:
|
||||||
# Default is 118 (0x76). Some BME280 sensors have an address of 119
|
# Default is 118 (0x76). The BMP180, BMP388 and some BME280 sensors
|
||||||
# (0x77).
|
# have an address of 119 (0x77).
|
||||||
#i2c_mcu:
|
#i2c_mcu:
|
||||||
#i2c_bus:
|
#i2c_bus:
|
||||||
#i2c_software_scl_pin:
|
#i2c_software_scl_pin:
|
||||||
@@ -2525,6 +2716,25 @@ sensor_type:
|
|||||||
# Interval in seconds between readings. Default is 30
|
# Interval in seconds between readings. Default is 30
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### SHT3X sensor
|
||||||
|
|
||||||
|
SHT3X family two wire interface (I2C) environmental sensor. These sensors
|
||||||
|
have a range of -55~125 C, so are usable for e.g. chamber temperature
|
||||||
|
monitoring. They can also function as simple fan/heater controllers.
|
||||||
|
|
||||||
|
```
|
||||||
|
sensor_type: SHT3X
|
||||||
|
#i2c_address:
|
||||||
|
# Default is 68 (0x44).
|
||||||
|
#i2c_mcu:
|
||||||
|
#i2c_bus:
|
||||||
|
#i2c_software_scl_pin:
|
||||||
|
#i2c_software_sda_pin:
|
||||||
|
#i2c_speed:
|
||||||
|
# See the "common I2C settings" section for a description of the
|
||||||
|
# above parameters.
|
||||||
|
```
|
||||||
|
|
||||||
### LM75 temperature sensor
|
### LM75 temperature sensor
|
||||||
|
|
||||||
LM75/LM75A two wire (I2C) connected temperature sensors. These sensors
|
LM75/LM75A two wire (I2C) connected temperature sensors. These sensors
|
||||||
@@ -2551,7 +2761,7 @@ sensor_type: LM75
|
|||||||
|
|
||||||
### Builtin micro-controller temperature sensor
|
### Builtin micro-controller temperature sensor
|
||||||
|
|
||||||
The atsam, atsamd, and stm32 micro-controllers contain an internal
|
The atsam, atsamd, stm32 and rp2040 micro-controllers contain an internal
|
||||||
temperature sensor. One can use the "temperature_mcu" sensor to
|
temperature sensor. One can use the "temperature_mcu" sensor to
|
||||||
monitor these temperatures.
|
monitor these temperatures.
|
||||||
|
|
||||||
@@ -3088,24 +3298,12 @@ pin:
|
|||||||
# If this is true, the value fields should be between 0 and 1; if it
|
# If this is true, the value fields should be between 0 and 1; if it
|
||||||
# is false the value fields should be either 0 or 1. The default is
|
# is false the value fields should be either 0 or 1. The default is
|
||||||
# False.
|
# False.
|
||||||
#static_value:
|
|
||||||
# If this is set, then the pin is assigned to this value at startup
|
|
||||||
# and the pin can not be changed during runtime. A static pin uses
|
|
||||||
# slightly less ram in the micro-controller. The default is to use
|
|
||||||
# runtime configuration of pins.
|
|
||||||
#value:
|
#value:
|
||||||
# The value to initially set the pin to during MCU configuration.
|
# The value to initially set the pin to during MCU configuration.
|
||||||
# The default is 0 (for low voltage).
|
# The default is 0 (for low voltage).
|
||||||
#shutdown_value:
|
#shutdown_value:
|
||||||
# The value to set the pin to on an MCU shutdown event. The default
|
# The value to set the pin to on an MCU shutdown event. The default
|
||||||
# is 0 (for low voltage).
|
# is 0 (for low voltage).
|
||||||
#maximum_mcu_duration:
|
|
||||||
# The maximum duration a non-shutdown value may be driven by the MCU
|
|
||||||
# without an acknowledge from the host.
|
|
||||||
# If host can not keep up with an update, the MCU will shutdown
|
|
||||||
# and set all pins to their respective shutdown values.
|
|
||||||
# Default: 0 (disabled)
|
|
||||||
# Usual values are around 5 seconds.
|
|
||||||
#cycle_time: 0.100
|
#cycle_time: 0.100
|
||||||
# The amount of time (in seconds) per PWM cycle. It is recommended
|
# The amount of time (in seconds) per PWM cycle. It is recommended
|
||||||
# this be 10 milliseconds or greater when using software based PWM.
|
# this be 10 milliseconds or greater when using software based PWM.
|
||||||
@@ -3125,6 +3323,54 @@ pin:
|
|||||||
# then the 'value' parameter can be specified using the desired
|
# then the 'value' parameter can be specified using the desired
|
||||||
# amperage for the stepper. The default is to not scale the 'value'
|
# amperage for the stepper. The default is to not scale the 'value'
|
||||||
# parameter.
|
# parameter.
|
||||||
|
#maximum_mcu_duration:
|
||||||
|
#static_value:
|
||||||
|
# These options are deprecated and should no longer be specified.
|
||||||
|
```
|
||||||
|
|
||||||
|
### [pwm_tool]
|
||||||
|
|
||||||
|
Pulse width modulation digital output pins capable of high speed
|
||||||
|
updates (one may define any number of sections with an "output_pin"
|
||||||
|
prefix). Pins configured here will be setup as output pins and one may
|
||||||
|
modify them at run-time using "SET_PIN PIN=my_pin VALUE=.1" type
|
||||||
|
extended [g-code commands](G-Codes.md#output_pin).
|
||||||
|
|
||||||
|
```
|
||||||
|
[pwm_tool my_tool]
|
||||||
|
pin:
|
||||||
|
# The pin to configure as an output. This parameter must be provided.
|
||||||
|
#maximum_mcu_duration:
|
||||||
|
# The maximum duration a non-shutdown value may be driven by the MCU
|
||||||
|
# without an acknowledge from the host.
|
||||||
|
# If host can not keep up with an update, the MCU will shutdown
|
||||||
|
# and set all pins to their respective shutdown values.
|
||||||
|
# Default: 0 (disabled)
|
||||||
|
# Usual values are around 5 seconds.
|
||||||
|
#value:
|
||||||
|
#shutdown_value:
|
||||||
|
#cycle_time: 0.100
|
||||||
|
#hardware_pwm: False
|
||||||
|
#scale:
|
||||||
|
# See the "output_pin" section for the definition of these parameters.
|
||||||
|
```
|
||||||
|
|
||||||
|
### [pwm_cycle_time]
|
||||||
|
|
||||||
|
Run-time configurable output pins with dynamic pwm cycle timing (one
|
||||||
|
may define any number of sections with an "pwm_cycle_time" prefix).
|
||||||
|
Pins configured here will be setup as output pins and one may modify
|
||||||
|
them at run-time using "SET_PIN PIN=my_pin VALUE=.1 CYCLE_TIME=0.100"
|
||||||
|
type extended [g-code commands](G-Codes.md#pwm_cycle_time).
|
||||||
|
|
||||||
|
```
|
||||||
|
[pwm_cycle_time my_pin]
|
||||||
|
pin:
|
||||||
|
#value:
|
||||||
|
#shutdown_value:
|
||||||
|
#cycle_time: 0.100
|
||||||
|
#scale:
|
||||||
|
# See the "output_pin" section for information on these parameters.
|
||||||
```
|
```
|
||||||
|
|
||||||
### [static_digital_output]
|
### [static_digital_output]
|
||||||
@@ -3212,6 +3458,18 @@ run_current:
|
|||||||
# set, "stealthChop" mode will be enabled if the stepper motor
|
# set, "stealthChop" mode will be enabled if the stepper motor
|
||||||
# velocity is below this value. The default is 0, which disables
|
# velocity is below this value. The default is 0, which disables
|
||||||
# "stealthChop" mode.
|
# "stealthChop" mode.
|
||||||
|
#coolstep_threshold:
|
||||||
|
# The velocity (in mm/s) to set the TMC driver internal "CoolStep"
|
||||||
|
# threshold to. If set, the coolstep feature will be enabled when
|
||||||
|
# the stepper motor velocity is near or above this value. Important
|
||||||
|
# - if coolstep_threshold is set and "sensorless homing" is used,
|
||||||
|
# then one must ensure that the homing speed is above the coolstep
|
||||||
|
# threshold! The default is to not enable the coolstep feature.
|
||||||
|
#high_velocity_threshold:
|
||||||
|
# The velocity (in mm/s) to set the TMC driver internal "high
|
||||||
|
# velocity" threshold (THIGH) to. This is typically used to disable
|
||||||
|
# the "CoolStep" feature at high speeds. The default is to not set a
|
||||||
|
# TMC "high velocity" threshold.
|
||||||
#driver_MSLUT0: 2863314260
|
#driver_MSLUT0: 2863314260
|
||||||
#driver_MSLUT1: 1251300522
|
#driver_MSLUT1: 1251300522
|
||||||
#driver_MSLUT2: 608774441
|
#driver_MSLUT2: 608774441
|
||||||
@@ -3242,11 +3500,19 @@ run_current:
|
|||||||
#driver_TOFF: 4
|
#driver_TOFF: 4
|
||||||
#driver_HEND: 7
|
#driver_HEND: 7
|
||||||
#driver_HSTRT: 0
|
#driver_HSTRT: 0
|
||||||
|
#driver_VHIGHFS: 0
|
||||||
|
#driver_VHIGHCHM: 0
|
||||||
#driver_PWM_AUTOSCALE: True
|
#driver_PWM_AUTOSCALE: True
|
||||||
#driver_PWM_FREQ: 1
|
#driver_PWM_FREQ: 1
|
||||||
#driver_PWM_GRAD: 4
|
#driver_PWM_GRAD: 4
|
||||||
#driver_PWM_AMPL: 128
|
#driver_PWM_AMPL: 128
|
||||||
#driver_SGT: 0
|
#driver_SGT: 0
|
||||||
|
#driver_SEMIN: 0
|
||||||
|
#driver_SEUP: 0
|
||||||
|
#driver_SEMAX: 0
|
||||||
|
#driver_SEDN: 0
|
||||||
|
#driver_SEIMIN: 0
|
||||||
|
#driver_SFILT: 0
|
||||||
# Set the given register during the configuration of the TMC2130
|
# Set the given register during the configuration of the TMC2130
|
||||||
# chip. This may be used to set custom motor parameters. The
|
# chip. This may be used to set custom motor parameters. The
|
||||||
# defaults for each parameter are next to the parameter name in the
|
# defaults for each parameter are next to the parameter name in the
|
||||||
@@ -3343,6 +3609,13 @@ run_current:
|
|||||||
#sense_resistor: 0.110
|
#sense_resistor: 0.110
|
||||||
#stealthchop_threshold: 0
|
#stealthchop_threshold: 0
|
||||||
# See the "tmc2208" section for the definition of these parameters.
|
# See the "tmc2208" section for the definition of these parameters.
|
||||||
|
#coolstep_threshold:
|
||||||
|
# The velocity (in mm/s) to set the TMC driver internal "CoolStep"
|
||||||
|
# threshold to. If set, the coolstep feature will be enabled when
|
||||||
|
# the stepper motor velocity is near or above this value. Important
|
||||||
|
# - if coolstep_threshold is set and "sensorless homing" is used,
|
||||||
|
# then one must ensure that the homing speed is above the coolstep
|
||||||
|
# threshold! The default is to not enable the coolstep feature.
|
||||||
#uart_address:
|
#uart_address:
|
||||||
# The address of the TMC2209 chip for UART messages (an integer
|
# The address of the TMC2209 chip for UART messages (an integer
|
||||||
# between 0 and 3). This is typically used when multiple TMC2209
|
# between 0 and 3). This is typically used when multiple TMC2209
|
||||||
@@ -3362,6 +3635,11 @@ run_current:
|
|||||||
#driver_PWM_GRAD: 14
|
#driver_PWM_GRAD: 14
|
||||||
#driver_PWM_OFS: 36
|
#driver_PWM_OFS: 36
|
||||||
#driver_SGTHRS: 0
|
#driver_SGTHRS: 0
|
||||||
|
#driver_SEMIN: 0
|
||||||
|
#driver_SEUP: 0
|
||||||
|
#driver_SEMAX: 0
|
||||||
|
#driver_SEDN: 0
|
||||||
|
#driver_SEIMIN: 0
|
||||||
# Set the given register during the configuration of the TMC2209
|
# Set the given register during the configuration of the TMC2209
|
||||||
# chip. This may be used to set custom motor parameters. The
|
# chip. This may be used to set custom motor parameters. The
|
||||||
# defaults for each parameter are next to the parameter name in the
|
# defaults for each parameter are next to the parameter name in the
|
||||||
@@ -3496,6 +3774,18 @@ run_current:
|
|||||||
# set, "stealthChop" mode will be enabled if the stepper motor
|
# set, "stealthChop" mode will be enabled if the stepper motor
|
||||||
# velocity is below this value. The default is 0, which disables
|
# velocity is below this value. The default is 0, which disables
|
||||||
# "stealthChop" mode.
|
# "stealthChop" mode.
|
||||||
|
#coolstep_threshold:
|
||||||
|
# The velocity (in mm/s) to set the TMC driver internal "CoolStep"
|
||||||
|
# threshold to. If set, the coolstep feature will be enabled when
|
||||||
|
# the stepper motor velocity is near or above this value. Important
|
||||||
|
# - if coolstep_threshold is set and "sensorless homing" is used,
|
||||||
|
# then one must ensure that the homing speed is above the coolstep
|
||||||
|
# threshold! The default is to not enable the coolstep feature.
|
||||||
|
#high_velocity_threshold:
|
||||||
|
# The velocity (in mm/s) to set the TMC driver internal "high
|
||||||
|
# velocity" threshold (THIGH) to. This is typically used to disable
|
||||||
|
# the "CoolStep" feature at high speeds. The default is to not set a
|
||||||
|
# TMC "high velocity" threshold.
|
||||||
#driver_MSLUT0: 2863314260
|
#driver_MSLUT0: 2863314260
|
||||||
#driver_MSLUT1: 1251300522
|
#driver_MSLUT1: 1251300522
|
||||||
#driver_MSLUT2: 608774441
|
#driver_MSLUT2: 608774441
|
||||||
@@ -3556,6 +3846,7 @@ run_current:
|
|||||||
#driver_SEIMIN: 0
|
#driver_SEIMIN: 0
|
||||||
#driver_SFILT: 0
|
#driver_SFILT: 0
|
||||||
#driver_SG4_ANGLE_OFFSET: 1
|
#driver_SG4_ANGLE_OFFSET: 1
|
||||||
|
#driver_SLOPE_CONTROL: 0
|
||||||
# Set the given register during the configuration of the TMC2240
|
# Set the given register during the configuration of the TMC2240
|
||||||
# chip. This may be used to set custom motor parameters. The
|
# chip. This may be used to set custom motor parameters. The
|
||||||
# defaults for each parameter are next to the parameter name in the
|
# defaults for each parameter are next to the parameter name in the
|
||||||
@@ -3617,6 +3908,18 @@ run_current:
|
|||||||
# set, "stealthChop" mode will be enabled if the stepper motor
|
# set, "stealthChop" mode will be enabled if the stepper motor
|
||||||
# velocity is below this value. The default is 0, which disables
|
# velocity is below this value. The default is 0, which disables
|
||||||
# "stealthChop" mode.
|
# "stealthChop" mode.
|
||||||
|
#coolstep_threshold:
|
||||||
|
# The velocity (in mm/s) to set the TMC driver internal "CoolStep"
|
||||||
|
# threshold to. If set, the coolstep feature will be enabled when
|
||||||
|
# the stepper motor velocity is near or above this value. Important
|
||||||
|
# - if coolstep_threshold is set and "sensorless homing" is used,
|
||||||
|
# then one must ensure that the homing speed is above the coolstep
|
||||||
|
# threshold! The default is to not enable the coolstep feature.
|
||||||
|
#high_velocity_threshold:
|
||||||
|
# The velocity (in mm/s) to set the TMC driver internal "high
|
||||||
|
# velocity" threshold (THIGH) to. This is typically used to disable
|
||||||
|
# the "CoolStep" feature at high speeds. The default is to not set a
|
||||||
|
# TMC "high velocity" threshold.
|
||||||
#driver_MSLUT0: 2863314260
|
#driver_MSLUT0: 2863314260
|
||||||
#driver_MSLUT1: 1251300522
|
#driver_MSLUT1: 1251300522
|
||||||
#driver_MSLUT2: 608774441
|
#driver_MSLUT2: 608774441
|
||||||
@@ -3847,15 +4150,16 @@ Support for a display attached to the micro-controller.
|
|||||||
[display]
|
[display]
|
||||||
lcd_type:
|
lcd_type:
|
||||||
# The type of LCD chip in use. This may be "hd44780", "hd44780_spi",
|
# The type of LCD chip in use. This may be "hd44780", "hd44780_spi",
|
||||||
# "st7920", "emulated_st7920", "uc1701", "ssd1306", or "sh1106".
|
# "aip31068_spi", "st7920", "emulated_st7920", "uc1701", "ssd1306", or
|
||||||
|
# "sh1106".
|
||||||
# See the display sections below for information on each type and
|
# See the display sections below for information on each type and
|
||||||
# additional parameters they provide. This parameter must be
|
# additional parameters they provide. This parameter must be
|
||||||
# provided.
|
# provided.
|
||||||
#display_group:
|
#display_group:
|
||||||
# The name of the display_data group to show on the display. This
|
# The name of the display_data group to show on the display. This
|
||||||
# controls the content of the screen (see the "display_data" section
|
# controls the content of the screen (see the "display_data" section
|
||||||
# for more information). The default is _default_20x4 for hd44780
|
# for more information). The default is _default_20x4 for hd44780 or
|
||||||
# displays and _default_16x4 for other displays.
|
# aip31068_spi displays and _default_16x4 for other displays.
|
||||||
#menu_timeout:
|
#menu_timeout:
|
||||||
# Timeout for menu. Being inactive this amount of seconds will
|
# Timeout for menu. Being inactive this amount of seconds will
|
||||||
# trigger menu exit or return to root menu when having autorun
|
# trigger menu exit or return to root menu when having autorun
|
||||||
@@ -3981,6 +4285,31 @@ spi_software_miso_pin:
|
|||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### aip31068_spi display
|
||||||
|
|
||||||
|
Information on configuring an aip31068_spi display - a very similar to hd44780_spi
|
||||||
|
a 20x04 (20 symbols by 4 lines) display with slightly different internal
|
||||||
|
protocol.
|
||||||
|
|
||||||
|
```
|
||||||
|
[display]
|
||||||
|
lcd_type: aip31068_spi
|
||||||
|
latch_pin:
|
||||||
|
spi_software_sclk_pin:
|
||||||
|
spi_software_mosi_pin:
|
||||||
|
spi_software_miso_pin:
|
||||||
|
# The pins connected to the shift register controlling the display.
|
||||||
|
# The spi_software_miso_pin needs to be set to an unused pin of the
|
||||||
|
# printer mainboard as the shift register does not have a MISO pin,
|
||||||
|
# but the software spi implementation requires this pin to be
|
||||||
|
# configured.
|
||||||
|
#line_length:
|
||||||
|
# Set the number of characters per line for an hd44780 type lcd.
|
||||||
|
# Possible values are 20 (default) and 16. The number of lines is
|
||||||
|
# fixed to 4.
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
#### st7920 display
|
#### st7920 display
|
||||||
|
|
||||||
Information on configuring st7920 displays (which is used in
|
Information on configuring st7920 displays (which is used in
|
||||||
@@ -4400,6 +4729,9 @@ adc2:
|
|||||||
# command.
|
# command.
|
||||||
#min_diameter: 1.0
|
#min_diameter: 1.0
|
||||||
# Minimal diameter for trigger virtual filament_switch_sensor.
|
# Minimal diameter for trigger virtual filament_switch_sensor.
|
||||||
|
#max_diameter:
|
||||||
|
# Maximum diameter for triggering virtual filament_switch_sensor.
|
||||||
|
# The default is default_nominal_filament_diameter + max_difference.
|
||||||
#use_current_dia_while_delay: False
|
#use_current_dia_while_delay: False
|
||||||
# Use the current diameter instead of the nominal diameter while
|
# Use the current diameter instead of the nominal diameter while
|
||||||
# the measurement delay has not run through.
|
# the measurement delay has not run through.
|
||||||
@@ -4412,6 +4744,112 @@ adc2:
|
|||||||
# above parameters.
|
# above parameters.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Load Cells
|
||||||
|
|
||||||
|
### [load_cell]
|
||||||
|
Load Cell. Uses an ADC sensor attached to a load cell to create a digital
|
||||||
|
scale.
|
||||||
|
|
||||||
|
```
|
||||||
|
[load_cell]
|
||||||
|
sensor_type:
|
||||||
|
# This must be one of the supported sensor types, see below.
|
||||||
|
```
|
||||||
|
|
||||||
|
#### HX711
|
||||||
|
This is a 24 bit low sample rate chip using "bit-bang" communications. It is
|
||||||
|
suitable for filament scales.
|
||||||
|
```
|
||||||
|
[load_cell]
|
||||||
|
sensor_type: hx711
|
||||||
|
sclk_pin:
|
||||||
|
# The pin connected to the HX711 clock line. This parameter must be provided.
|
||||||
|
dout_pin:
|
||||||
|
# The pin connected to the HX711 data output line. This parameter must be
|
||||||
|
# provided.
|
||||||
|
#gain: A-128
|
||||||
|
# Valid values for gain are: A-128, A-64, B-32. The default is A-128.
|
||||||
|
# 'A' denotes the input channel and the number denotes the gain. Only the 3
|
||||||
|
# listed combinations are supported by the chip. Note that changing the gain
|
||||||
|
# setting also selects the channel being read.
|
||||||
|
#sample_rate: 80
|
||||||
|
# Valid values for sample_rate are 80 or 10. The default value is 80.
|
||||||
|
# This must match the wiring of the chip. The sample rate cannot be changed
|
||||||
|
# in software.
|
||||||
|
```
|
||||||
|
|
||||||
|
#### HX717
|
||||||
|
This is the 4x higher sample rate version of the HX711, suitable for probing.
|
||||||
|
```
|
||||||
|
[load_cell]
|
||||||
|
sensor_type: hx717
|
||||||
|
sclk_pin:
|
||||||
|
# The pin connected to the HX717 clock line. This parameter must be provided.
|
||||||
|
dout_pin:
|
||||||
|
# The pin connected to the HX717 data output line. This parameter must be
|
||||||
|
# provided.
|
||||||
|
#gain: A-128
|
||||||
|
# Valid values for gain are A-128, B-64, A-64, B-8.
|
||||||
|
# 'A' denotes the input channel and the number denotes the gain setting.
|
||||||
|
# Only the 4 listed combinations are supported by the chip. Note that
|
||||||
|
# changing the gain setting also selects the channel being read.
|
||||||
|
#sample_rate: 320
|
||||||
|
# Valid values for sample_rate are: 10, 20, 80, 320. The default is 320.
|
||||||
|
# This must match the wiring of the chip. The sample rate cannot be changed
|
||||||
|
# in software.
|
||||||
|
```
|
||||||
|
|
||||||
|
#### ADS1220
|
||||||
|
The ADS1220 is a 24 bit ADC supporting up to a 2Khz sample rate configurable in
|
||||||
|
software.
|
||||||
|
```
|
||||||
|
[load_cell]
|
||||||
|
sensor_type: ads1220
|
||||||
|
cs_pin:
|
||||||
|
# The pin connected to the ADS1220 chip select line. This parameter must
|
||||||
|
# be provided.
|
||||||
|
#spi_speed: 512000
|
||||||
|
# This chip supports 2 speeds: 256000 or 512000. The faster speed is only
|
||||||
|
# enabled when one of the Turbo sample rates is used. The correct spi_speed
|
||||||
|
# is selected based on the sample rate.
|
||||||
|
#spi_bus:
|
||||||
|
#spi_software_sclk_pin:
|
||||||
|
#spi_software_mosi_pin:
|
||||||
|
#spi_software_miso_pin:
|
||||||
|
# See the "common SPI settings" section for a description of the
|
||||||
|
# above parameters.
|
||||||
|
data_ready_pin:
|
||||||
|
# Pin connected to the ADS1220 data ready line. This parameter must be
|
||||||
|
# provided.
|
||||||
|
#gain: 128
|
||||||
|
# Valid gain values are 128, 64, 32, 16, 8, 4, 2, 1
|
||||||
|
# The default is 128
|
||||||
|
#pga_bypass: False
|
||||||
|
# Disable the internal Programmable Gain Amplifier. If
|
||||||
|
# True the PGA will be disabled for gains 1, 2, and 4. The PGA is always
|
||||||
|
# enabled for gain settings 8 to 128, regardless of the pga_bypass setting.
|
||||||
|
# If AVSS is used as an input pga_bypass is forced to True.
|
||||||
|
# The default is False.
|
||||||
|
#sample_rate: 660
|
||||||
|
# This chip supports two ranges of sample rates, Normal and Turbo. In turbo
|
||||||
|
# mode the chip's internal clock runs twice as fast and the SPI communication
|
||||||
|
# speed is also doubled.
|
||||||
|
# Normal sample rates: 20, 45, 90, 175, 330, 600, 1000
|
||||||
|
# Turbo sample rates: 40, 90, 180, 350, 660, 1200, 2000
|
||||||
|
# The default is 660
|
||||||
|
#input_mux:
|
||||||
|
# Input multiplexer configuration, select a pair of pins to use. The first pin
|
||||||
|
# is the positive, AINP, and the second pin is the negative, AINN. Valid
|
||||||
|
# values are: 'AIN0_AIN1', 'AIN0_AIN2', 'AIN0_AIN3', 'AIN1_AIN2', 'AIN1_AIN3',
|
||||||
|
# 'AIN2_AIN3', 'AIN1_AIN0', 'AIN3_AIN2', 'AIN0_AVSS', 'AIN1_AVSS', 'AIN2_AVSS'
|
||||||
|
# and 'AIN3_AVSS'. If AVSS is used the PGA is bypassed and the pga_bypass
|
||||||
|
# setting will be forced to True.
|
||||||
|
# The default is AIN0_AIN1.
|
||||||
|
#vref:
|
||||||
|
# The selected voltage reference. Valid values are: 'internal', 'REF0', 'REF1'
|
||||||
|
# and 'analog_supply'. Default is 'internal'.
|
||||||
|
```
|
||||||
|
|
||||||
## Board specific hardware support
|
## Board specific hardware support
|
||||||
|
|
||||||
### [sx1509]
|
### [sx1509]
|
||||||
@@ -4498,6 +4936,50 @@ vssa_pin:
|
|||||||
# noise. The default is 2 seconds.
|
# noise. The default is 2 seconds.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### [ads1x1x]
|
||||||
|
|
||||||
|
ADS1013, ADS1014, ADS1015, ADS1113, ADS1114 and ADS1115 are I2C based Analog to
|
||||||
|
Digital Converters that can be used for temperature sensors. They provide 4
|
||||||
|
analog input pins either as single line or as differential input.
|
||||||
|
|
||||||
|
Note: Use caution if using this sensor to control heaters. The heater min_temp
|
||||||
|
and max_temp are only verified in the host and only if the host is running and
|
||||||
|
operating normally. (ADC inputs directly connected to the micro-controller
|
||||||
|
verify min_temp and max_temp within the micro-controller and do not require a
|
||||||
|
working connection to the host.)
|
||||||
|
|
||||||
|
```
|
||||||
|
[ads1x1x my_ads1x1x]
|
||||||
|
chip: ADS1115
|
||||||
|
#pga: 4.096V
|
||||||
|
# Default value is 4.096V. The maximum voltage range used for the input. This
|
||||||
|
# scales all values read from the ADC. Options are: 6.144V, 4.096V, 2.048V,
|
||||||
|
# 1.024V, 0.512V, 0.256V
|
||||||
|
#adc_voltage: 3.3
|
||||||
|
# The suppy voltage for the device. This allows additional software scaling
|
||||||
|
# for all values read from the ADC.
|
||||||
|
i2c_mcu: host
|
||||||
|
i2c_bus: i2c.1
|
||||||
|
#address_pin: GND
|
||||||
|
# Default value is GND. There can be up to four addressed devices depending
|
||||||
|
# upon wiring of the device. Check the datasheet for details. The i2c_address
|
||||||
|
# can be specified directly instead of using the address_pin.
|
||||||
|
```
|
||||||
|
|
||||||
|
The chip provides pins that can be used on other sensors.
|
||||||
|
|
||||||
|
```
|
||||||
|
sensor_type: ...
|
||||||
|
# Can be any thermistor or adc_temperature.
|
||||||
|
sensor_pin: my_ads1x1x:AIN0
|
||||||
|
# A combination of the name of the ads1x1x chip and the pin. Possible
|
||||||
|
# pin values are AIN0, AIN1, AIN2 and AIN3 for single ended lines and
|
||||||
|
# DIFF01, DIFF03, DIFF13 and DIFF23 for differential between their
|
||||||
|
# correspoding lines. For example
|
||||||
|
# DIFF03 measures the differential between line 0 and 3. Only specific
|
||||||
|
# combinations for the differentials are allowed.
|
||||||
|
```
|
||||||
|
|
||||||
### [replicape]
|
### [replicape]
|
||||||
|
|
||||||
Replicape support - see the [beaglebone guide](Beaglebone.md) and the
|
Replicape support - see the [beaglebone guide](Beaglebone.md) and the
|
||||||
@@ -4603,8 +5085,9 @@ serial:
|
|||||||
### [angle]
|
### [angle]
|
||||||
|
|
||||||
Magnetic hall angle sensor support for reading stepper motor angle
|
Magnetic hall angle sensor support for reading stepper motor angle
|
||||||
shaft measurements using a1333, as5047d, or tle5012b SPI chips. The
|
shaft measurements using a1333, as5047d, mt6816, mt6826s,
|
||||||
measurements are available via the [API Server](API_Server.md) and
|
or tle5012b SPI chips.
|
||||||
|
The measurements are available via the [API Server](API_Server.md) and
|
||||||
[motion analysis tool](Debugging.md#motion-analysis-and-data-logging).
|
[motion analysis tool](Debugging.md#motion-analysis-and-data-logging).
|
||||||
See the [G-Code reference](G-Codes.md#angle) for available commands.
|
See the [G-Code reference](G-Codes.md#angle) for available commands.
|
||||||
|
|
||||||
@@ -4612,7 +5095,7 @@ See the [G-Code reference](G-Codes.md#angle) for available commands.
|
|||||||
[angle my_angle_sensor]
|
[angle my_angle_sensor]
|
||||||
sensor_type:
|
sensor_type:
|
||||||
# The type of the magnetic hall sensor chip. Available choices are
|
# The type of the magnetic hall sensor chip. Available choices are
|
||||||
# "a1333", "as5047d", and "tle5012b". This parameter must be
|
# "a1333", "as5047d", "mt6816", "mt6826s", and "tle5012b". This parameter must be
|
||||||
# specified.
|
# specified.
|
||||||
#sample_period: 0.000400
|
#sample_period: 0.000400
|
||||||
# The query period (in seconds) to use during measurements. The
|
# The query period (in seconds) to use during measurements. The
|
||||||
@@ -4675,8 +5158,9 @@ Most Klipper micro-controller implementations only support an
|
|||||||
micro-controller supports a 400000 speed (*fast mode*, 400kbit/s), but it must be
|
micro-controller supports a 400000 speed (*fast mode*, 400kbit/s), but it must be
|
||||||
[set in the operating system](RPi_microcontroller.md#optional-enabling-i2c)
|
[set in the operating system](RPi_microcontroller.md#optional-enabling-i2c)
|
||||||
and the `i2c_speed` parameter is otherwise ignored. The Klipper
|
and the `i2c_speed` parameter is otherwise ignored. The Klipper
|
||||||
"RP2040" micro-controller and ATmega AVR family support a rate of 400000
|
"RP2040" micro-controller and ATmega AVR family and some STM32
|
||||||
via the `i2c_speed` parameter. All other Klipper micro-controllers use a
|
(F0, G0, G4, L4, F7, H7) support a rate of 400000 via the `i2c_speed` parameter.
|
||||||
|
All other Klipper micro-controllers use a
|
||||||
100000 rate and ignore the `i2c_speed` parameter.
|
100000 rate and ignore the `i2c_speed` parameter.
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -132,3 +132,10 @@ There are several
|
|||||||
you have questions on the code then you can also ask in the
|
you have questions on the code then you can also ask in the
|
||||||
[Klipper Discourse Forum](#discourse-forum) or on the
|
[Klipper Discourse Forum](#discourse-forum) or on the
|
||||||
[Klipper Discord Chat](#discord-chat).
|
[Klipper Discord Chat](#discord-chat).
|
||||||
|
|
||||||
|
## Professional Services
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Custom software development, software support, and solutions:
|
||||||
|
[https://ko-fi.com/koconnor](https://ko-fi.com/koconnor)
|
||||||
|
|||||||
146
docs/Eddy_Probe.md
Normal file
146
docs/Eddy_Probe.md
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
# Eddy Current Inductive probe
|
||||||
|
|
||||||
|
This document describes how to use an
|
||||||
|
[eddy current](https://en.wikipedia.org/wiki/Eddy_current) inductive
|
||||||
|
probe in Klipper.
|
||||||
|
|
||||||
|
Currently, an eddy current probe can not be used for Z homing. The
|
||||||
|
sensor can only be used for Z probing.
|
||||||
|
|
||||||
|
Start by declaring a
|
||||||
|
[probe_eddy_current config section](Config_Reference.md#probe_eddy_current)
|
||||||
|
in the printer.cfg file. It is recommended to set the `z_offset` to
|
||||||
|
0.5mm. It is typical for the sensor to require an `x_offset` and
|
||||||
|
`y_offset`. If these values are not known, one should estimate the
|
||||||
|
values during initial calibration.
|
||||||
|
|
||||||
|
The first step in calibration is to determine the appropriate
|
||||||
|
DRIVE_CURRENT for the sensor. Home the printer and navigate the
|
||||||
|
toolhead so that the sensor is near the center of the bed and is about
|
||||||
|
20mm above the bed. Then issue an `LDC_CALIBRATE_DRIVE_CURRENT
|
||||||
|
CHIP=<config_name>` command. For example, if the config section was
|
||||||
|
named `[probe_eddy_current my_eddy_probe]` then one would run
|
||||||
|
`LDC_CALIBRATE_DRIVE_CURRENT CHIP=my_eddy_probe`. This command should
|
||||||
|
complete in a few seconds. After it completes, issue a `SAVE_CONFIG`
|
||||||
|
command to save the results to the printer.cfg and restart.
|
||||||
|
|
||||||
|
The second step in calibration is to correlate the sensor readings to
|
||||||
|
the corresponding Z heights. Home the printer and navigate the
|
||||||
|
toolhead so that the nozzle is near the center of the bed. Then run an
|
||||||
|
`PROBE_EDDY_CURRENT_CALIBRATE CHIP=my_eddy_probe` command. Once the
|
||||||
|
tool starts, follow the steps described at
|
||||||
|
["the paper test"](Bed_Level.md#the-paper-test) to determine the
|
||||||
|
actual distance between the nozzle and bed at the given location. Once
|
||||||
|
those steps are complete one can `ACCEPT` the position. The tool will
|
||||||
|
then move the the toolhead so that the sensor is above the point where
|
||||||
|
the nozzle used to be and run a series of movements to correlate the
|
||||||
|
sensor to Z positions. This will take a couple of minutes. After the
|
||||||
|
tool completes, issue a `SAVE_CONFIG` command to save the results to
|
||||||
|
the printer.cfg and restart.
|
||||||
|
|
||||||
|
After initial calibration it is a good idea to verify that the
|
||||||
|
`x_offset` and `y_offset` are accurate. Follow the steps to
|
||||||
|
[calibrate probe x and y offsets](Probe_Calibrate.md#calibrating-probe-x-and-y-offsets).
|
||||||
|
If either the `x_offset` or `y_offset` is modified then be sure to run
|
||||||
|
the `PROBE_EDDY_CURRENT_CALIBRATE` command (as described above) after
|
||||||
|
making the change.
|
||||||
|
|
||||||
|
Once calibration is complete, one may use all the standard Klipper
|
||||||
|
tools that use a Z probe.
|
||||||
|
|
||||||
|
Note that eddy current sensors (and inductive probes in general) are
|
||||||
|
susceptible to "thermal drift". That is, changes in temperature can
|
||||||
|
result in changes in reported Z height. Changes in either the bed
|
||||||
|
surface temperature or sensor hardware temperature can skew the
|
||||||
|
results. It is important that calibration and probing is only done
|
||||||
|
when the printer is at a stable temperature.
|
||||||
|
|
||||||
|
## Thermal Drift Calibration
|
||||||
|
|
||||||
|
As with all inductive probes, eddy current probes are subject to
|
||||||
|
significant thermal drift. If the eddy probe has a temperature
|
||||||
|
sensor on the coil it is possible to configure a `[temperature_probe]`
|
||||||
|
to report coil temperature and enable software drift compensation. To
|
||||||
|
link a temperature probe to an eddy current probe the
|
||||||
|
`[temperature_probe]` section must share a name with the
|
||||||
|
`[probe_eddy_current]` section. For example:
|
||||||
|
|
||||||
|
```
|
||||||
|
[probe_eddy_current my_probe]
|
||||||
|
# eddy probe configuration...
|
||||||
|
|
||||||
|
[temperature_probe my_probe]
|
||||||
|
# temperature probe configuration...
|
||||||
|
```
|
||||||
|
|
||||||
|
See the [configuration reference](Config_Reference.md#temperature_probe)
|
||||||
|
for further details on how to configure a `temperature_probe`. It is
|
||||||
|
advised to configure the `calibration_position`,
|
||||||
|
`calibration_extruder_temp`, `extruder_heating_z`, and
|
||||||
|
`calibration_bed_temp` options, as doing so will automate some of the
|
||||||
|
steps outlined below. If the printer to be calibrated is enclosed, it
|
||||||
|
is strongly recommended to set the `max_validation_temp` option to a value
|
||||||
|
between 100 and 120.
|
||||||
|
|
||||||
|
Eddy probe manufacturers may offer a stock drift calibration that can be
|
||||||
|
manually added to `drift_calibration` option of the `[probe_eddy_current]`
|
||||||
|
section. If they do not, or if the stock calibration does not perform well on
|
||||||
|
your system, the `temperature_probe` module offers a manual calibration
|
||||||
|
procedure via the `TEMPERATURE_PROBE_CALIBRATE` gcode command.
|
||||||
|
|
||||||
|
Prior to performing calibration the user should have an idea of what the
|
||||||
|
maximum attainable temperature probe coil temperature is. This temperature
|
||||||
|
should be used to set the `TARGET` parameter of the
|
||||||
|
`TEMPERATURE_PROBE_CALIBRATE` command. The goal is to calibrate across the
|
||||||
|
widest temperature range possible, thus its desirable to start with the printer
|
||||||
|
cold and finish with the coil at the maximum temperature it can reach.
|
||||||
|
|
||||||
|
Once a `[temperature_probe]` is configured, the following steps may be taken
|
||||||
|
to perform thermal drift calibration:
|
||||||
|
|
||||||
|
- The probe must be calibrated using `PROBE_EDDY_CURRENT_CALIBRATE`
|
||||||
|
when a `[temperature_probe]` is configured and linked. This captures
|
||||||
|
the temperature during calibration which is necessary to perform
|
||||||
|
thermal drift compensation.
|
||||||
|
- Make sure the nozzle is free of debris and filament.
|
||||||
|
- The bed, nozzle, and probe coil should be cold prior to calibration.
|
||||||
|
- The following steps are required if the `calibration_position`,
|
||||||
|
`calibration_extruder_temp`, and `extruder_heating_z` options in
|
||||||
|
`[temperature_probe]` are **NOT** configured:
|
||||||
|
- Move the tool to the center of the bed. Z should be 30mm+ above the bed.
|
||||||
|
- Heat the extruder to a temperature above the maximum safe bed temperature.
|
||||||
|
150-170C should be sufficient for most configurations. The purpose of
|
||||||
|
heating the extruder is to avoid nozzle expansion during calibration.
|
||||||
|
- When the extruder temperature has settled, move the Z axis down to about 1mm
|
||||||
|
above the bed.
|
||||||
|
- Start drift calibration. If the probe's name is `my_probe` and the maximum
|
||||||
|
probe temperature we can achieve is 80C, the appropriate gcode command is
|
||||||
|
`TEMPERATURE_PROBE_CALIBRATE PROBE=my_probe TARGET=80`. If configured, the
|
||||||
|
tool will move to the X,Y coordinate specified by the `calibration_position`
|
||||||
|
and the Z value specified by `extruder_heating_z`. After heating the extruder
|
||||||
|
to the specified temperature the tool will move to the Z value specified
|
||||||
|
by the`calibration_position`.
|
||||||
|
- The procedure will request a manual probe. Perform the manual probe with
|
||||||
|
the paper test and `ACCEPT`. The calibration procedure will take the first
|
||||||
|
set of samples with the probe then park the probe in the heating position.
|
||||||
|
- If the `calibration_bed_temp` is **NOT** configured turn on the bed heat
|
||||||
|
to the maximum safe temperature. Otherwise this step will be performed
|
||||||
|
automatically.
|
||||||
|
- By default the calibration procedure will request a manual probe every
|
||||||
|
2C between samples until the `TARGET` is reached. The temperature delta
|
||||||
|
between samples can be customized by setting the `STEP` parameter in
|
||||||
|
`TEMPERATURE_PROBE_CALIBRATE`. Care should be taken when setting a custom
|
||||||
|
`STEP` value, a value too high may request too few samples resulting in
|
||||||
|
a poor calibration.
|
||||||
|
- The following additional gcode commands are available during drift
|
||||||
|
calibration:
|
||||||
|
- `TEMPERATURE_PROBE_NEXT` may be used to force a new sample before the step
|
||||||
|
delta has been reached.
|
||||||
|
- `TEMPERATURE_PROBE_COMPLETE` may be used to complete calibration before the
|
||||||
|
`TARGET` has been reached.
|
||||||
|
- `ABORT` may be used to end calibration and discard results.
|
||||||
|
- When calibration is finished use `SAVE_CONFIG` to store the drift
|
||||||
|
calibration.
|
||||||
|
|
||||||
|
As one may conclude, the calibration process outlined above is more challenging
|
||||||
|
and time consuming than most other procedures. It may require practice and several attempts to achieve an optimal calibration.
|
||||||
@@ -190,6 +190,8 @@ represent total number of steps per second on the micro-controller.
|
|||||||
| AR100 | 3529K | 2507K |
|
| AR100 | 3529K | 2507K |
|
||||||
| STM32F407 | 3652K | 2459K |
|
| STM32F407 | 3652K | 2459K |
|
||||||
| STM32F446 | 3913K | 2634K |
|
| STM32F446 | 3913K | 2634K |
|
||||||
|
| RP2350 | 4167K | 2663K |
|
||||||
|
| SAME70 | 6667K | 4737K |
|
||||||
| STM32H743 | 9091K | 6061K |
|
| STM32H743 | 9091K | 6061K |
|
||||||
|
|
||||||
If unsure of the micro-controller on a particular board, find the
|
If unsure of the micro-controller on a particular board, find the
|
||||||
|
|||||||
262
docs/G-Codes.md
262
docs/G-Codes.md
@@ -127,6 +127,14 @@ use this tool the Python "numpy" package must be installed (see the
|
|||||||
[measuring resonance document](Measuring_Resonances.md#software-installation)
|
[measuring resonance document](Measuring_Resonances.md#software-installation)
|
||||||
for more information).
|
for more information).
|
||||||
|
|
||||||
|
#### ANGLE_CHIP_CALIBRATE
|
||||||
|
`ANGLE_CHIP_CALIBRATE CHIP=<chip_name>`: Perform internal sensor calibration,
|
||||||
|
if implemented (MT6826S/MT6835).
|
||||||
|
|
||||||
|
- **MT68XX**: The motor should be disconnected
|
||||||
|
from any printer carriage before performing calibration.
|
||||||
|
After calibration, the sensor should be reset by disconnecting the power.
|
||||||
|
|
||||||
#### ANGLE_DEBUG_READ
|
#### ANGLE_DEBUG_READ
|
||||||
`ANGLE_DEBUG_READ CHIP=<config_name> REG=<register>`: Queries sensor
|
`ANGLE_DEBUG_READ CHIP=<config_name> REG=<register>`: Queries sensor
|
||||||
register "register" (e.g. 44 or 0x2C). Can be useful for debugging
|
register "register" (e.g. 44 or 0x2C). Can be useful for debugging
|
||||||
@@ -139,6 +147,27 @@ Writes raw "value" into register "register". Both "value" and
|
|||||||
and refer to sensor data sheet for the reference. This is only
|
and refer to sensor data sheet for the reference. This is only
|
||||||
available for tle5012b chips.
|
available for tle5012b chips.
|
||||||
|
|
||||||
|
### [axis_twist_compensation]
|
||||||
|
|
||||||
|
The following commands are available when the
|
||||||
|
[axis_twist_compensation config
|
||||||
|
section](Config_Reference.md#axis_twist_compensation) is enabled.
|
||||||
|
|
||||||
|
#### AXIS_TWIST_COMPENSATION_CALIBRATE
|
||||||
|
`AXIS_TWIST_COMPENSATION_CALIBRATE [AXIS=<X|Y>] [AUTO=<True|False>]
|
||||||
|
[SAMPLE_COUNT=<value>]`
|
||||||
|
|
||||||
|
Calibrates axis twist compensation by specifying the target axis or
|
||||||
|
enabling automatic calibration.
|
||||||
|
|
||||||
|
- **AXIS:** Define the axis (`X` or `Y`) for which the twist compensation
|
||||||
|
will be calibrated. If not specified, the axis defaults to `'X'`.
|
||||||
|
|
||||||
|
- **AUTO:** Enables automatic calibration mode. When `AUTO=True`, the
|
||||||
|
calibration will run for both the X and Y axes. In this mode, `AXIS`
|
||||||
|
cannot be specified. If both `AXIS` and `AUTO` are provided, an error
|
||||||
|
will be raised.
|
||||||
|
|
||||||
### [bed_mesh]
|
### [bed_mesh]
|
||||||
|
|
||||||
The following commands are available when the
|
The following commands are available when the
|
||||||
@@ -146,15 +175,21 @@ The following commands are available when the
|
|||||||
(also see the [bed mesh guide](Bed_Mesh.md)).
|
(also see the [bed mesh guide](Bed_Mesh.md)).
|
||||||
|
|
||||||
#### BED_MESH_CALIBRATE
|
#### BED_MESH_CALIBRATE
|
||||||
`BED_MESH_CALIBRATE [METHOD=manual] [HORIZONTAL_MOVE_Z=<value>]
|
`BED_MESH_CALIBRATE [PROFILE=<name>] [METHOD=manual] [HORIZONTAL_MOVE_Z=<value>]
|
||||||
[<probe_parameter>=<value>] [<mesh_parameter>=<value>]`: This command probes
|
[<probe_parameter>=<value>] [<mesh_parameter>=<value>] [ADAPTIVE=1]
|
||||||
the bed using generated points specified by the parameters in the config. After
|
[ADAPTIVE_MARGIN=<value>]`: This command probes the bed using generated points
|
||||||
probing, a mesh is generated and z-movement is adjusted according to the mesh.
|
specified by the parameters in the config. After probing, a mesh is generated
|
||||||
|
and z-movement is adjusted according to the mesh.
|
||||||
|
The mesh will be saved into a profile specified by the `PROFILE` parameter,
|
||||||
|
or `default` if unspecified.
|
||||||
See the PROBE command for details on the optional probe parameters. If
|
See the PROBE command for details on the optional probe parameters. If
|
||||||
METHOD=manual is specified then the manual probing tool is activated - see the
|
METHOD=manual is specified then the manual probing tool is activated - see the
|
||||||
MANUAL_PROBE command above for details on the additional commands available
|
MANUAL_PROBE command above for details on the additional commands available
|
||||||
while this tool is active. The optional `HORIZONTAL_MOVE_Z` value overrides the
|
while this tool is active. The optional `HORIZONTAL_MOVE_Z` value overrides the
|
||||||
`horizontal_move_z` option specified in the config file.
|
`horizontal_move_z` option specified in the config file. If ADAPTIVE=1 is
|
||||||
|
specified then the objects defined by the Gcode file being printed will be used
|
||||||
|
to define the probed area. The optional `ADAPTIVE_MARGIN` value overrides the
|
||||||
|
`adaptive_margin` option specified in the config file.
|
||||||
|
|
||||||
#### BED_MESH_OUTPUT
|
#### BED_MESH_OUTPUT
|
||||||
`BED_MESH_OUTPUT PGP=[<0:1>]`: This command outputs the current probed
|
`BED_MESH_OUTPUT PGP=[<0:1>]`: This command outputs the current probed
|
||||||
@@ -184,10 +219,12 @@ SAVE_CONFIG gcode must be run to make the changes to persistent memory
|
|||||||
permanent.
|
permanent.
|
||||||
|
|
||||||
#### BED_MESH_OFFSET
|
#### BED_MESH_OFFSET
|
||||||
`BED_MESH_OFFSET [X=<value>] [Y=<value>]`: Applies X and/or Y offsets
|
`BED_MESH_OFFSET [X=<value>] [Y=<value>] [ZFADE=<value]`: Applies X, Y,
|
||||||
to the mesh lookup. This is useful for printers with independent
|
and/or ZFADE offsets to the mesh lookup. This is useful for printers with
|
||||||
extruders, as an offset is necessary to produce correct Z adjustment
|
independent extruders, as an offset is necessary to produce correct Z
|
||||||
after a tool change.
|
adjustment after a tool change. Note that a ZFADE offset does not apply
|
||||||
|
additional z-adjustment directly, it is used to correct the `fade`
|
||||||
|
calculation when a `gcode offset` has been applied to the Z axis.
|
||||||
|
|
||||||
### [bed_screws]
|
### [bed_screws]
|
||||||
|
|
||||||
@@ -447,12 +484,6 @@ MOTION_QUEUE (as defined in an [extruder](Config_Reference.md#extruder)
|
|||||||
config section). If MOTION_QUEUE is an empty string then the stepper
|
config section). If MOTION_QUEUE is an empty string then the stepper
|
||||||
will be desynchronized from all extruder movement.
|
will be desynchronized from all extruder movement.
|
||||||
|
|
||||||
#### SET_EXTRUDER_STEP_DISTANCE
|
|
||||||
This command is deprecated and will be removed in the near future.
|
|
||||||
|
|
||||||
#### SYNC_STEPPER_TO_EXTRUDER
|
|
||||||
This command is deprecated and will be removed in the near future.
|
|
||||||
|
|
||||||
### [fan_generic]
|
### [fan_generic]
|
||||||
|
|
||||||
The following command is available when a
|
The following command is available when a
|
||||||
@@ -463,6 +494,20 @@ enabled.
|
|||||||
`SET_FAN_SPEED FAN=config_name SPEED=<speed>` This command sets the
|
`SET_FAN_SPEED FAN=config_name SPEED=<speed>` This command sets the
|
||||||
speed of a fan. "speed" must be between 0.0 and 1.0.
|
speed of a fan. "speed" must be between 0.0 and 1.0.
|
||||||
|
|
||||||
|
`SET_FAN_SPEED PIN=config_name TEMPLATE=<template_name>
|
||||||
|
[<param_x>=<literal>]`: If `TEMPLATE` is specified then it assigns a
|
||||||
|
[display_template](Config_Reference.md#display_template) to the given
|
||||||
|
fan. For example, if one defined a `[display_template
|
||||||
|
my_fan_template]` config section then one could assign
|
||||||
|
`TEMPLATE=my_fan_template` here. The display_template should produce a
|
||||||
|
string containing a floating point number with the desired value. The
|
||||||
|
template will be continuously evaluated and the fan will be
|
||||||
|
automatically set to the resulting speed. One may set display_template
|
||||||
|
parameters to use during template evaluation (parameters will be
|
||||||
|
parsed as Python literals). If TEMPLATE is an empty string then this
|
||||||
|
command will clear any previous template assigned to the pin (one can
|
||||||
|
then use `SET_FAN_SPEED` commands to manage the values directly).
|
||||||
|
|
||||||
### [filament_switch_sensor]
|
### [filament_switch_sensor]
|
||||||
|
|
||||||
The following command is available when a
|
The following command is available when a
|
||||||
@@ -540,15 +585,18 @@ state; issue a G28 afterwards to reset the kinematics. This command is
|
|||||||
intended for low-level diagnostics and debugging.
|
intended for low-level diagnostics and debugging.
|
||||||
|
|
||||||
#### SET_KINEMATIC_POSITION
|
#### SET_KINEMATIC_POSITION
|
||||||
`SET_KINEMATIC_POSITION [X=<value>] [Y=<value>] [Z=<value>]`: Force
|
`SET_KINEMATIC_POSITION [X=<value>] [Y=<value>] [Z=<value>]
|
||||||
the low-level kinematic code to believe the toolhead is at the given
|
[CLEAR=<[X][Y][Z]>]`: Force the low-level kinematic code to believe the
|
||||||
cartesian position. This is a diagnostic and debugging command; use
|
toolhead is at the given cartesian position. This is a diagnostic and
|
||||||
SET_GCODE_OFFSET and/or G92 for regular axis transformations. If an
|
debugging command; use SET_GCODE_OFFSET and/or G92 for regular axis
|
||||||
axis is not specified then it will default to the position that the
|
transformations. If an axis is not specified then it will default to the
|
||||||
head was last commanded to. Setting an incorrect or invalid position
|
position that the head was last commanded to. Setting an incorrect or
|
||||||
may lead to internal software errors. This command may invalidate
|
invalid position may lead to internal software errors. Use the CLEAR
|
||||||
future boundary checks; issue a G28 afterwards to reset the
|
parameter to forget the homing state for the given axes. Note that CLEAR
|
||||||
kinematics.
|
will not override the previous functionality; if an axis is not specified
|
||||||
|
to CLEAR it will have its kinematic position set as per above. This
|
||||||
|
command may invalidate future boundary checks; issue a G28 afterwards to
|
||||||
|
reset the kinematics.
|
||||||
|
|
||||||
### [gcode]
|
### [gcode]
|
||||||
|
|
||||||
@@ -834,21 +882,29 @@ commands to manage the LED's color settings).
|
|||||||
### [output_pin]
|
### [output_pin]
|
||||||
|
|
||||||
The following command is available when an
|
The following command is available when an
|
||||||
[output_pin config section](Config_Reference.md#output_pin) is
|
[output_pin config section](Config_Reference.md#output_pin) or
|
||||||
|
[pwm_tool config section](Config_Reference.md#pwm_tool) is
|
||||||
enabled.
|
enabled.
|
||||||
|
|
||||||
#### SET_PIN
|
#### SET_PIN
|
||||||
`SET_PIN PIN=config_name VALUE=<value> [CYCLE_TIME=<cycle_time>]`: Set
|
`SET_PIN PIN=config_name VALUE=<value>`: Set the pin to the given
|
||||||
the pin to the given output `VALUE`. VALUE should be 0 or 1 for
|
output `VALUE`. VALUE should be 0 or 1 for "digital" output pins. For
|
||||||
"digital" output pins. For PWM pins, set to a value between 0.0 and
|
PWM pins, set to a value between 0.0 and 1.0, or between 0.0 and
|
||||||
1.0, or between 0.0 and `scale` if a scale is configured in the
|
`scale` if a scale is configured in the output_pin config section.
|
||||||
output_pin config section.
|
|
||||||
|
|
||||||
Some pins (currently only "soft PWM" pins) support setting an explicit
|
`SET_PIN PIN=config_name TEMPLATE=<template_name> [<param_x>=<literal>]`:
|
||||||
cycle time using the CYCLE_TIME parameter (specified in seconds). Note
|
If `TEMPLATE` is specified then it assigns a
|
||||||
that the CYCLE_TIME parameter is not stored between SET_PIN commands
|
[display_template](Config_Reference.md#display_template) to the given
|
||||||
(any SET_PIN command without an explicit CYCLE_TIME parameter will use
|
pin. For example, if one defined a `[display_template
|
||||||
the `cycle_time` specified in the output_pin config section).
|
my_pin_template]` config section then one could assign
|
||||||
|
`TEMPLATE=my_pin_template` here. The display_template should produce a
|
||||||
|
string containing a floating point number with the desired value. The
|
||||||
|
template will be continuously evaluated and the pin will be
|
||||||
|
automatically set to the resulting value. One may set display_template
|
||||||
|
parameters to use during template evaluation (parameters will be
|
||||||
|
parsed as Python literals). If TEMPLATE is an empty string then this
|
||||||
|
command will clear any previous template assigned to the pin (one can
|
||||||
|
then use `SET_PIN` commands to manage the values directly).
|
||||||
|
|
||||||
### [palette2]
|
### [palette2]
|
||||||
|
|
||||||
@@ -977,6 +1033,58 @@ babystepping), and subtract if from the probe's z_offset. This acts
|
|||||||
to take a frequently used babystepping value, and "make it permanent".
|
to take a frequently used babystepping value, and "make it permanent".
|
||||||
Requires a `SAVE_CONFIG` to take effect.
|
Requires a `SAVE_CONFIG` to take effect.
|
||||||
|
|
||||||
|
### [probe_eddy_current]
|
||||||
|
|
||||||
|
The following commands are available when a
|
||||||
|
[probe_eddy_current config section](Config_Reference.md#probe_eddy_current)
|
||||||
|
is enabled.
|
||||||
|
|
||||||
|
#### PROBE_EDDY_CURRENT_CALIBRATE
|
||||||
|
`PROBE_EDDY_CURRENT_CALIBRATE CHIP=<config_name>`: This starts a tool
|
||||||
|
that calibrates the sensor resonance frequencies to corresponding Z
|
||||||
|
heights. The tool will take a couple of minutes to complete. After
|
||||||
|
completion, use the SAVE_CONFIG command to store the results in the
|
||||||
|
printer.cfg file.
|
||||||
|
|
||||||
|
#### LDC_CALIBRATE_DRIVE_CURRENT
|
||||||
|
`LDC_CALIBRATE_DRIVE_CURRENT CHIP=<config_name>` This tool will
|
||||||
|
calibrate the ldc1612 DRIVE_CURRENT0 register. Prior to using this
|
||||||
|
tool, move the sensor so that it is near the center of the bed and
|
||||||
|
about 20mm above the bed surface. Run this command to determine an
|
||||||
|
appropriate DRIVE_CURRENT for the sensor. After running this command
|
||||||
|
use the SAVE_CONFIG command to store that new setting in the
|
||||||
|
printer.cfg config file.
|
||||||
|
|
||||||
|
### [pwm_cycle_time]
|
||||||
|
|
||||||
|
The following command is available when a
|
||||||
|
[pwm_cycle_time config section](Config_Reference.md#pwm_cycle_time)
|
||||||
|
is enabled.
|
||||||
|
|
||||||
|
#### SET_PIN
|
||||||
|
`SET_PIN PIN=config_name VALUE=<value> [CYCLE_TIME=<cycle_time>]`:
|
||||||
|
This command works similarly to [output_pin](#output_pin) SET_PIN
|
||||||
|
commands. The command here supports setting an explicit cycle time
|
||||||
|
using the CYCLE_TIME parameter (specified in seconds). Note that the
|
||||||
|
CYCLE_TIME parameter is not stored between SET_PIN commands (any
|
||||||
|
SET_PIN command without an explicit CYCLE_TIME parameter will use the
|
||||||
|
`cycle_time` specified in the pwm_cycle_time config section).
|
||||||
|
|
||||||
|
### [quad_gantry_level]
|
||||||
|
|
||||||
|
The following commands are available when the
|
||||||
|
[quad_gantry_level config section](Config_Reference.md#quad_gantry_level)
|
||||||
|
is enabled.
|
||||||
|
|
||||||
|
#### QUAD_GANTRY_LEVEL
|
||||||
|
`QUAD_GANTRY_LEVEL [RETRIES=<value>] [RETRY_TOLERANCE=<value>]
|
||||||
|
[HORIZONTAL_MOVE_Z=<value>] [<probe_parameter>=<value>]`: This command
|
||||||
|
will probe the points specified in the config and then make
|
||||||
|
independent adjustments to each Z stepper to compensate for tilt. See
|
||||||
|
the PROBE command for details on the optional probe parameters. The
|
||||||
|
optional `RETRIES`, `RETRY_TOLERANCE`, and `HORIZONTAL_MOVE_Z` values
|
||||||
|
override those options specified in the config file.
|
||||||
|
|
||||||
### [query_adc]
|
### [query_adc]
|
||||||
|
|
||||||
The query_adc module is automatically loaded.
|
The query_adc module is automatically loaded.
|
||||||
@@ -1012,20 +1120,19 @@ is enabled (also see the
|
|||||||
all enabled accelerometer chips.
|
all enabled accelerometer chips.
|
||||||
|
|
||||||
#### TEST_RESONANCES
|
#### TEST_RESONANCES
|
||||||
`TEST_RESONANCES AXIS=<axis> OUTPUT=<resonances,raw_data>
|
`TEST_RESONANCES AXIS=<axis> [OUTPUT=<resonances,raw_data>]
|
||||||
[NAME=<name>] [FREQ_START=<min_freq>] [FREQ_END=<max_freq>]
|
[NAME=<name>] [FREQ_START=<min_freq>] [FREQ_END=<max_freq>]
|
||||||
[HZ_PER_SEC=<hz_per_sec>] [CHIPS=<adxl345_chip_name>]
|
[ACCEL_PER_HZ=<accel_per_hz>] [HZ_PER_SEC=<hz_per_sec>] [CHIPS=<chip_name>]
|
||||||
[POINT=x,y,z] [INPUT_SHAPING=[<0:1>]]`: Runs the resonance
|
[POINT=x,y,z] [INPUT_SHAPING=<0:1>]`: Runs the resonance
|
||||||
test in all configured probe points for the requested "axis" and
|
test in all configured probe points for the requested "axis" and
|
||||||
measures the acceleration using the accelerometer chips configured for
|
measures the acceleration using the accelerometer chips configured for
|
||||||
the respective axis. "axis" can either be X or Y, or specify an
|
the respective axis. "axis" can either be X or Y, or specify an
|
||||||
arbitrary direction as `AXIS=dx,dy`, where dx and dy are floating
|
arbitrary direction as `AXIS=dx,dy`, where dx and dy are floating
|
||||||
point numbers defining a direction vector (e.g. `AXIS=X`, `AXIS=Y`, or
|
point numbers defining a direction vector (e.g. `AXIS=X`, `AXIS=Y`, or
|
||||||
`AXIS=1,-1` to define a diagonal direction). Note that `AXIS=dx,dy`
|
`AXIS=1,-1` to define a diagonal direction). Note that `AXIS=dx,dy`
|
||||||
and `AXIS=-dx,-dy` is equivalent. `adxl345_chip_name` can be one or
|
and `AXIS=-dx,-dy` is equivalent. `chip_name` can be one or
|
||||||
more configured adxl345 chip,delimited with comma, for example
|
more configured accel chips, delimited with comma, for example
|
||||||
`CHIPS="adxl345, adxl345 rpi"`. Note that `adxl345` can be omitted from
|
`CHIPS="adxl345, adxl345 rpi"`. If POINT is specified it will override the point(s)
|
||||||
named adxl345 chips. If POINT is specified it will override the point(s)
|
|
||||||
configured in `[resonance_tester]`. If `INPUT_SHAPING=0` or not set(default),
|
configured in `[resonance_tester]`. If `INPUT_SHAPING=0` or not set(default),
|
||||||
disables input shaping for the resonance testing, because
|
disables input shaping for the resonance testing, because
|
||||||
it is not valid to run the resonance testing with the input shaper
|
it is not valid to run the resonance testing with the input shaper
|
||||||
@@ -1042,8 +1149,9 @@ frequency response is calculated (across all probe points) and written into
|
|||||||
|
|
||||||
#### SHAPER_CALIBRATE
|
#### SHAPER_CALIBRATE
|
||||||
`SHAPER_CALIBRATE [AXIS=<axis>] [NAME=<name>] [FREQ_START=<min_freq>]
|
`SHAPER_CALIBRATE [AXIS=<axis>] [NAME=<name>] [FREQ_START=<min_freq>]
|
||||||
[FREQ_END=<max_freq>] [HZ_PER_SEC=<hz_per_sec>] [CHIPS=<adxl345_chip_name>]
|
[FREQ_END=<max_freq>] [ACCEL_PER_HZ=<accel_per_hz>][HZ_PER_SEC=<hz_per_sec>]
|
||||||
[MAX_SMOOTHING=<max_smoothing>]`: Similarly to `TEST_RESONANCES`, runs
|
[CHIPS=<chip_name>] [MAX_SMOOTHING=<max_smoothing>] [INPUT_SHAPING=<0:1>]`:
|
||||||
|
Similarly to `TEST_RESONANCES`, runs
|
||||||
the resonance test as configured, and tries to find the optimal
|
the resonance test as configured, and tries to find the optimal
|
||||||
parameters for the input shaper for the requested axis (or both X and
|
parameters for the input shaper for the requested axis (or both X and
|
||||||
Y axes if `AXIS` parameter is unset). If `MAX_SMOOTHING` is unset, its
|
Y axes if `AXIS` parameter is unset). If `MAX_SMOOTHING` is unset, its
|
||||||
@@ -1280,8 +1388,11 @@ The toolhead module is automatically loaded.
|
|||||||
|
|
||||||
#### SET_VELOCITY_LIMIT
|
#### SET_VELOCITY_LIMIT
|
||||||
`SET_VELOCITY_LIMIT [VELOCITY=<value>] [ACCEL=<value>]
|
`SET_VELOCITY_LIMIT [VELOCITY=<value>] [ACCEL=<value>]
|
||||||
[ACCEL_TO_DECEL=<value>] [SQUARE_CORNER_VELOCITY=<value>]`: Modify the
|
[MINIMUM_CRUISE_RATIO=<value>] [SQUARE_CORNER_VELOCITY=<value>]`: This
|
||||||
printer's velocity limits.
|
command can alter the velocity limits that were specified in the
|
||||||
|
printer config file. See the
|
||||||
|
[printer config section](Config_Reference.md#printer) for a
|
||||||
|
description of each parameter.
|
||||||
|
|
||||||
### [tuning_tower]
|
### [tuning_tower]
|
||||||
|
|
||||||
@@ -1339,17 +1450,6 @@ print.
|
|||||||
#### SDCARD_RESET_FILE
|
#### SDCARD_RESET_FILE
|
||||||
`SDCARD_RESET_FILE`: Unload file and clear SD state.
|
`SDCARD_RESET_FILE`: Unload file and clear SD state.
|
||||||
|
|
||||||
### [axis_twist_compensation]
|
|
||||||
|
|
||||||
The following commands are available when the
|
|
||||||
[axis_twist_compensation config
|
|
||||||
section](Config_Reference.md#axis_twist_compensation) is enabled.
|
|
||||||
|
|
||||||
#### AXIS_TWIST_COMPENSATION_CALIBRATE
|
|
||||||
`AXIS_TWIST_COMPENSATION_CALIBRATE [SAMPLE_COUNT=<value>]`: Initiates the X
|
|
||||||
twist calibration wizard. `SAMPLE_COUNT` specifies the number of points along
|
|
||||||
the X axis to calibrate at and defaults to 3.
|
|
||||||
|
|
||||||
### [z_thermal_adjust]
|
### [z_thermal_adjust]
|
||||||
|
|
||||||
The following commands are available when the
|
The following commands are available when the
|
||||||
@@ -1374,8 +1474,46 @@ The following commands are available when the
|
|||||||
[z_tilt config section](Config_Reference.md#z_tilt) is enabled.
|
[z_tilt config section](Config_Reference.md#z_tilt) is enabled.
|
||||||
|
|
||||||
#### Z_TILT_ADJUST
|
#### Z_TILT_ADJUST
|
||||||
`Z_TILT_ADJUST [HORIZONTAL_MOVE_Z=<value>] [<probe_parameter>=<value>]`: This
|
`Z_TILT_ADJUST [RETRIES=<value>] [RETRY_TOLERANCE=<value>]
|
||||||
command will probe the points specified in the config and then make independent
|
[HORIZONTAL_MOVE_Z=<value>] [<probe_parameter>=<value>]`: This command
|
||||||
adjustments to each Z stepper to compensate for tilt. See the PROBE command for
|
will probe the points specified in the config and then make
|
||||||
details on the optional probe parameters. The optional `HORIZONTAL_MOVE_Z`
|
independent adjustments to each Z stepper to compensate for tilt. See
|
||||||
value overrides the `horizontal_move_z` option specified in the config file.
|
the PROBE command for details on the optional probe parameters. The
|
||||||
|
optional `RETRIES`, `RETRY_TOLERANCE`, and `HORIZONTAL_MOVE_Z` values
|
||||||
|
override those options specified in the config file.
|
||||||
|
|
||||||
|
### [temperature_probe]
|
||||||
|
|
||||||
|
The following commands are available when a
|
||||||
|
[temperature_probe config section](Config_Reference.md#temperature_probe)
|
||||||
|
is enabled.
|
||||||
|
|
||||||
|
#### TEMPERATURE_PROBE_CALIBRATE
|
||||||
|
`TEMPERATURE_PROBE_CALIBRATE [PROBE=<probe name>] [TARGET=<value>] [STEP=<value>]`:
|
||||||
|
Initiates probe drift calibration for eddy current based probes. The `TARGET`
|
||||||
|
is a target temperature for the last sample. When the temperature recorded
|
||||||
|
during a sample exceeds the `TARGET` calibration will complete. The `STEP`
|
||||||
|
parameter sets temperature delta (in C) between samples. After a sample has
|
||||||
|
been taken, this delta is used to schedule a call to `TEMPERATURE_PROBE_NEXT`.
|
||||||
|
The default `STEP` is 2.
|
||||||
|
|
||||||
|
#### TEMPERATURE_PROBE_NEXT
|
||||||
|
`TEMPERATURE_PROBE_NEXT`: After calibration has started this command is run to
|
||||||
|
take the next sample. It is automatically scheduled to run when the delta
|
||||||
|
specified by `STEP` has been reached, however its also possible to manually run
|
||||||
|
this command to force a new sample. This command is only available during
|
||||||
|
calibration.
|
||||||
|
|
||||||
|
#### TEMPERATURE_PROBE_COMPLETE:
|
||||||
|
`TEMPERATURE_PROBE_COMPLETE`: Can be used to end calibration and save the
|
||||||
|
current result before the `TARGET` temperature is reached. This command
|
||||||
|
is only available during calibration.
|
||||||
|
|
||||||
|
#### ABORT
|
||||||
|
`ABORT`: Aborts the calibration process, discarding the current results.
|
||||||
|
This command is only available during drift calibration.
|
||||||
|
|
||||||
|
### TEMPERATURE_PROBE_ENABLE
|
||||||
|
`TEMPERATURE_PROBE_ENABLE ENABLE=[0|1]`: Sets temperature drift
|
||||||
|
compensation on or off. If ENABLE is set to 0, drift compensation
|
||||||
|
will be disabled, if set to 1 it is enabled.
|
||||||
|
|||||||
@@ -1,15 +1,20 @@
|
|||||||
# Installation
|
# Installation
|
||||||
|
|
||||||
These instructions assume the software will run on a Raspberry Pi
|
These instructions assume the software will run on a linux based host
|
||||||
computer in conjunction with OctoPrint. It is recommended that a
|
running a Klipper compatible front end. It is recommended that a
|
||||||
Raspberry Pi 2, 3, or 4 computer be used as the host machine (see the
|
SBC(Small Board Computer) such as a Raspberry Pi or Debian based Linux
|
||||||
|
device be used as the host machine (see the
|
||||||
[FAQ](FAQ.md#can-i-run-klipper-on-something-other-than-a-raspberry-pi-3)
|
[FAQ](FAQ.md#can-i-run-klipper-on-something-other-than-a-raspberry-pi-3)
|
||||||
for other machines).
|
for other options).
|
||||||
|
|
||||||
|
For the purposes of these instructions host relates to the Linux device and
|
||||||
|
mcu relates to the printboard. SBC relates to the term Small Board Computer
|
||||||
|
such as the Raspberry Pi.
|
||||||
|
|
||||||
## Obtain a Klipper Configuration File
|
## Obtain a Klipper Configuration File
|
||||||
|
|
||||||
Most Klipper settings are determined by a "printer configuration file"
|
Most Klipper settings are determined by a "printer configuration file"
|
||||||
that will be stored on the Raspberry Pi. An appropriate configuration
|
printer.cfg, that will be stored on the host. An appropriate configuration
|
||||||
file can often be found by looking in the Klipper
|
file can often be found by looking in the Klipper
|
||||||
[config directory](../config/) for a file starting with a "printer-"
|
[config directory](../config/) for a file starting with a "printer-"
|
||||||
prefix that corresponds to the target printer. The Klipper
|
prefix that corresponds to the target printer. The Klipper
|
||||||
@@ -35,38 +40,51 @@ printer configuration file, then start with the closest example
|
|||||||
[config file](../config/) and use the Klipper
|
[config file](../config/) and use the Klipper
|
||||||
[config reference](Config_Reference.md) for further information.
|
[config reference](Config_Reference.md) for further information.
|
||||||
|
|
||||||
## Prepping an OS image
|
## Interacting with Klipper
|
||||||
|
|
||||||
Start by installing [OctoPi](https://github.com/guysoft/OctoPi) on the
|
Klipper is a 3d printer firmware, so it needs some way for the user to
|
||||||
Raspberry Pi computer. Use OctoPi v0.17.0 or later - see the
|
interact with it.
|
||||||
[OctoPi releases](https://github.com/guysoft/OctoPi/releases) for
|
|
||||||
release information. One should verify that OctoPi boots and that the
|
|
||||||
OctoPrint web server works. After connecting to the OctoPrint web
|
|
||||||
page, follow the prompt to upgrade OctoPrint to v1.4.2 or later.
|
|
||||||
|
|
||||||
After installing OctoPi and upgrading OctoPrint, it will be necessary
|
Currently the best choices are front ends that retrieve information through
|
||||||
to ssh into the target machine to run a handful of system commands. If
|
the [Moonraker web API](https://moonraker.readthedocs.io/) and there is also
|
||||||
using a Linux or MacOS desktop, then the "ssh" software should already
|
the option to use [Octoprint](https://octoprint.org/) to control Klipper.
|
||||||
be installed on the desktop. There are free ssh clients available for
|
|
||||||
other desktops (eg,
|
|
||||||
[PuTTY](https://www.chiark.greenend.org.uk/~sgtatham/putty/)). Use the
|
|
||||||
ssh utility to connect to the Raspberry Pi (ssh pi@octopi -- password
|
|
||||||
is "raspberry") and run the following commands:
|
|
||||||
|
|
||||||
```
|
The choice is up to the user on what to use, but the underlying Klipper is the
|
||||||
git clone https://github.com/Klipper3d/klipper
|
same in all cases. We encourage users to research the options available and
|
||||||
./klipper/scripts/install-octopi.sh
|
make an informed decision.
|
||||||
```
|
|
||||||
|
|
||||||
The above will download Klipper, install some system dependencies,
|
## Obtaining an OS image for SBC's
|
||||||
setup Klipper to run at system startup, and start the Klipper host
|
|
||||||
software. It will require an internet connection and it may take a few
|
There are many ways to obtain an OS image for Klipper for SBC use, most depend on
|
||||||
minutes to complete.
|
what front end you wish to use. Some manafactures of these SBC boards also provide
|
||||||
|
their own Klipper-centric images.
|
||||||
|
|
||||||
|
The two main Moonraker based front ends are [Fluidd](https://docs.fluidd.xyz/)
|
||||||
|
and [Mainsail](https://docs.mainsail.xyz/), the latter of which has a premade install
|
||||||
|
image ["MainsailOS"](http://docs.mainsailOS.xyz), this has the option for Raspberry Pi
|
||||||
|
and some OrangePi varianta.
|
||||||
|
|
||||||
|
Fluidd can be installed via KIAUH(Klipper Install And Update Helper), which
|
||||||
|
is explained below and is a 3rd party installer for all things Klipper.
|
||||||
|
|
||||||
|
OctoPrint can be installed via the popular OctoPi image or via KIAUH, this
|
||||||
|
process is explained in [OctoPrint.md](OctoPrint.md)
|
||||||
|
|
||||||
|
## Installing via KIAUH
|
||||||
|
|
||||||
|
Normally you would start with a base image for your SBC, RPiOS Lite for example,
|
||||||
|
or in the case of a x86 Linux device, Ubuntu Server. Please note that Desktop
|
||||||
|
variants are not recommended due to certain helper programs that can stop some
|
||||||
|
Klipper functions working and even mask access to some print boards.
|
||||||
|
|
||||||
|
KIAUH can be used to install Klipper and its associated programs on a variety
|
||||||
|
of Linux based systems that run a form of Debian. More information can be found
|
||||||
|
at https://github.com/dw-0/kiauh
|
||||||
|
|
||||||
## Building and flashing the micro-controller
|
## Building and flashing the micro-controller
|
||||||
|
|
||||||
To compile the micro-controller code, start by running these commands
|
To compile the micro-controller code, start by running these commands
|
||||||
on the Raspberry Pi:
|
on your host device:
|
||||||
|
|
||||||
```
|
```
|
||||||
cd ~/klipper/
|
cd ~/klipper/
|
||||||
@@ -108,10 +126,21 @@ It should report something similar to the following:
|
|||||||
It's common for each printer to have its own unique serial port name.
|
It's common for each printer to have its own unique serial port name.
|
||||||
This unique name will be used when flashing the micro-controller. It's
|
This unique name will be used when flashing the micro-controller. It's
|
||||||
possible there may be multiple lines in the above output - if so,
|
possible there may be multiple lines in the above output - if so,
|
||||||
choose the line corresponding to the micro-controller (see the
|
choose the line corresponding to the micro-controller. If many
|
||||||
|
items are listed and the choice is ambiguous, unplug the board and
|
||||||
|
run the command again, the missing item will be your print board(see the
|
||||||
[FAQ](FAQ.md#wheres-my-serial-port) for more information).
|
[FAQ](FAQ.md#wheres-my-serial-port) for more information).
|
||||||
|
|
||||||
For common micro-controllers, the code can be flashed with something
|
For common micro-controllers with STM32 or clone chips, LPC chips and
|
||||||
|
others it is usual that these need an initial Klipper flash via SD card.
|
||||||
|
|
||||||
|
When flashing with this method, it is important to make sure that the
|
||||||
|
print board is not connected with USB to the host, due to some boards
|
||||||
|
being able to feed power back to the board and stopping a flash from
|
||||||
|
occuring.
|
||||||
|
|
||||||
|
For common micro-controllers using Atmega chips, for example the 2560,
|
||||||
|
the code can be flashed with something
|
||||||
similar to:
|
similar to:
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -123,53 +152,38 @@ sudo service klipper start
|
|||||||
Be sure to update the FLASH_DEVICE with the printer's unique serial
|
Be sure to update the FLASH_DEVICE with the printer's unique serial
|
||||||
port name.
|
port name.
|
||||||
|
|
||||||
When flashing for the first time, make sure that OctoPrint is not
|
For common micro-controllers using RP2040 chips, the code can be flashed
|
||||||
connected directly to the printer (from the OctoPrint web page, under
|
with something similar to:
|
||||||
the "Connection" section, click "Disconnect").
|
|
||||||
|
|
||||||
## Configuring OctoPrint to use Klipper
|
```
|
||||||
|
sudo service klipper stop
|
||||||
|
make flash FLASH_DEVICE=first
|
||||||
|
sudo service klipper start
|
||||||
|
```
|
||||||
|
|
||||||
The OctoPrint web server needs to be configured to communicate with
|
It is important to note that RP2040 chips may need to be put into Boot mode
|
||||||
the Klipper host software. Using a web browser, login to the OctoPrint
|
before this operation.
|
||||||
web page and then configure the following items:
|
|
||||||
|
|
||||||
Navigate to the Settings tab (the wrench icon at the top of the
|
|
||||||
page). Under "Serial Connection" in "Additional serial ports" add
|
|
||||||
"/tmp/printer". Then click "Save".
|
|
||||||
|
|
||||||
Enter the Settings tab again and under "Serial Connection" change the
|
|
||||||
"Serial Port" setting to "/tmp/printer".
|
|
||||||
|
|
||||||
In the Settings tab, navigate to the "Behavior" sub-tab and select the
|
|
||||||
"Cancel any ongoing prints but stay connected to the printer"
|
|
||||||
option. Click "Save".
|
|
||||||
|
|
||||||
From the main page, under the "Connection" section (at the top left of
|
|
||||||
the page) make sure the "Serial Port" is set to "/tmp/printer" and
|
|
||||||
click "Connect". (If "/tmp/printer" is not an available selection then
|
|
||||||
try reloading the page.)
|
|
||||||
|
|
||||||
Once connected, navigate to the "Terminal" tab and type "status"
|
|
||||||
(without the quotes) into the command entry box and click "Send". The
|
|
||||||
terminal window will likely report there is an error opening the
|
|
||||||
config file - that means OctoPrint is successfully communicating with
|
|
||||||
Klipper. Proceed to the next section.
|
|
||||||
|
|
||||||
## Configuring Klipper
|
## Configuring Klipper
|
||||||
|
|
||||||
The next step is to copy the
|
The next step is to copy the
|
||||||
[printer configuration file](#obtain-a-klipper-configuration-file) to
|
[printer configuration file](#obtain-a-klipper-configuration-file) to
|
||||||
the Raspberry Pi.
|
the host.
|
||||||
|
|
||||||
Arguably the easiest way to set the Klipper configuration file is to
|
Arguably the easiest way to set the Klipper configuration file is using the
|
||||||
use a desktop editor that supports editing files over the "scp" and/or
|
built in editors in Mainsail or Fluidd. These will allow the user to open
|
||||||
"sftp" protocols. There are freely available tools that support this
|
the configuration examples and save them to be printer.cfg.
|
||||||
(eg, Notepad++, WinSCP, and Cyberduck). Load the printer config file
|
|
||||||
in the editor and then save it as a file named "printer.cfg" in the
|
Another option is to use a desktop editor that supports editing files
|
||||||
home directory of the pi user (ie, /home/pi/printer.cfg).
|
over the "scp" and/or "sftp" protocols. There are freely available tools
|
||||||
|
that support this (eg, Notepad++, WinSCP, and Cyberduck).
|
||||||
|
Load the printer config file in the editor and then save it as a file
|
||||||
|
named "printer.cfg" in the home directory of the pi user
|
||||||
|
(ie, /home/pi/printer.cfg).
|
||||||
|
|
||||||
Alternatively, one can also copy and edit the file directly on the
|
Alternatively, one can also copy and edit the file directly on the
|
||||||
Raspberry Pi via ssh. That may look something like the following (be
|
host via ssh. That may look something like the following (be
|
||||||
sure to update the command to use the appropriate printer config
|
sure to update the command to use the appropriate printer config
|
||||||
filename):
|
filename):
|
||||||
|
|
||||||
@@ -201,7 +215,7 @@ serial: /dev/serial/by-id/usb-1a86_USB2.0-Serial-if00-port0
|
|||||||
```
|
```
|
||||||
|
|
||||||
After creating and editing the file it will be necessary to issue a
|
After creating and editing the file it will be necessary to issue a
|
||||||
"restart" command in the OctoPrint web terminal to load the config. A
|
"restart" command in the command console to load the config. A
|
||||||
"status" command will report the printer is ready if the Klipper
|
"status" command will report the printer is ready if the Klipper
|
||||||
config file is successfully read and the micro-controller is
|
config file is successfully read and the micro-controller is
|
||||||
successfully found and configured.
|
successfully found and configured.
|
||||||
@@ -211,10 +225,10 @@ Klipper to report a configuration error. If an error occurs, make any
|
|||||||
necessary corrections to the printer config file and issue "restart"
|
necessary corrections to the printer config file and issue "restart"
|
||||||
until "status" reports the printer is ready.
|
until "status" reports the printer is ready.
|
||||||
|
|
||||||
Klipper reports error messages via the OctoPrint terminal tab. The
|
Klipper reports error messages via the command console and via pop up in
|
||||||
"status" command can be used to re-report error messages. The default
|
Fluidd and Mainsail. The "status" command can be used to re-report error
|
||||||
Klipper startup script also places a log in **/tmp/klippy.log** which
|
messages. A log is available and usually located in ~/printer_data/logs
|
||||||
provides more detailed information.
|
this is named klippy.log
|
||||||
|
|
||||||
After Klipper reports that the printer is ready, proceed to the
|
After Klipper reports that the printer is ready, proceed to the
|
||||||
[config check document](Config_checks.md) to perform some basic checks
|
[config check document](Config_checks.md) to perform some basic checks
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ Key formula for look-ahead:
|
|||||||
end_velocity^2 = start_velocity^2 + 2*accel*move_distance
|
end_velocity^2 = start_velocity^2 + 2*accel*move_distance
|
||||||
```
|
```
|
||||||
|
|
||||||
### Smoothed look-ahead
|
### Minimum cruise ratio
|
||||||
|
|
||||||
Klipper also implements a mechanism for smoothing out the motions of
|
Klipper also implements a mechanism for smoothing out the motions of
|
||||||
short "zigzag" moves. Consider the following moves:
|
short "zigzag" moves. Consider the following moves:
|
||||||
@@ -105,21 +105,27 @@ short "zigzag" moves. Consider the following moves:
|
|||||||
|
|
||||||
In the above, the frequent changes from acceleration to deceleration
|
In the above, the frequent changes from acceleration to deceleration
|
||||||
can cause the machine to vibrate which causes stress on the machine
|
can cause the machine to vibrate which causes stress on the machine
|
||||||
and increases the noise. To reduce this, Klipper tracks both regular
|
and increases the noise. Klipper implements a mechanism to ensure
|
||||||
move acceleration as well as a virtual "acceleration to deceleration"
|
there is always some movement at a cruising speed between acceleration
|
||||||
rate. Using this system, the top speed of these short "zigzag" moves
|
and deceleration. This is done by reducing the top speed of some moves
|
||||||
are limited to smooth out the printer motion:
|
(or sequence of moves) to ensure there is a minimum distance traveled
|
||||||
|
at cruising speed relative to the distance traveled during
|
||||||
|
acceleration and deceleration.
|
||||||
|
|
||||||
|
Klipper implements this feature by tracking both a regular move
|
||||||
|
acceleration as well as a virtual "acceleration to deceleration" rate:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Specifically, the code calculates what the velocity of each move would
|
Specifically, the code calculates what the velocity of each move would
|
||||||
be if it were limited to this virtual "acceleration to deceleration"
|
be if it were limited to this virtual "acceleration to deceleration"
|
||||||
rate (half the normal acceleration rate by default). In the above
|
rate. In the above picture the dashed gray lines represent this
|
||||||
picture the dashed gray lines represent this virtual acceleration rate
|
virtual acceleration rate for the first move. If a move can not reach
|
||||||
for the first move. If a move can not reach its full cruising speed
|
its full cruising speed using this virtual acceleration rate then its
|
||||||
using this virtual acceleration rate then its top speed is reduced to
|
top speed is reduced to the maximum speed it could obtain at this
|
||||||
the maximum speed it could obtain at this virtual acceleration
|
virtual acceleration rate.
|
||||||
rate. For most moves the limit will be at or above the move's existing
|
|
||||||
|
For most moves the limit will be at or above the move's existing
|
||||||
limits and no change in behavior is induced. For short zigzag moves,
|
limits and no change in behavior is induced. For short zigzag moves,
|
||||||
however, this limit reduces the top speed. Note that it does not
|
however, this limit reduces the top speed. Note that it does not
|
||||||
change the actual acceleration within the move - the move continues to
|
change the actual acceleration within the move - the move continues to
|
||||||
|
|||||||
@@ -1,24 +1,26 @@
|
|||||||
# Measuring Resonances
|
# Measuring Resonances
|
||||||
|
|
||||||
Klipper has built-in support for the ADXL345, MPU-9250 and LIS2DW compatible
|
Klipper has built-in support for the ADXL345, MPU-9250, LIS2DW and LIS3DH compatible
|
||||||
accelerometers which can be used to measure resonance frequencies of the printer
|
accelerometers which can be used to measure resonance frequencies of the printer
|
||||||
for different axes, and auto-tune [input shapers](Resonance_Compensation.md) to
|
for different axes, and auto-tune [input shapers](Resonance_Compensation.md) to
|
||||||
compensate for resonances. Note that using accelerometers requires some
|
compensate for resonances. Note that using accelerometers requires some
|
||||||
soldering and crimping. The ADXL345/LIS2DW can be connected to the SPI interface
|
soldering and crimping. The ADXL345 can be connected to the SPI interface
|
||||||
of a Raspberry Pi or MCU board (it needs to be reasonably fast). The MPU family can
|
of a Raspberry Pi or MCU board (it needs to be reasonably fast). The MPU family can
|
||||||
be connected to the I2C interface of a Raspberry Pi directly, or to an I2C
|
be connected to the I2C interface of a Raspberry Pi directly, or to an I2C
|
||||||
interface of an MCU board that supports 400kbit/s *fast mode* in Klipper.
|
interface of an MCU board that supports 400kbit/s *fast mode* in Klipper. The
|
||||||
|
LIS2DW and LIS3DH can be connected to either SPI or I2C with the same considerations
|
||||||
|
as above.
|
||||||
|
|
||||||
When sourcing accelerometers, be aware that there are a variety of different PCB
|
When sourcing accelerometers, be aware that there are a variety of different PCB
|
||||||
board designs and different clones of them. If it is going to be connected to a
|
board designs and different clones of them. If it is going to be connected to a
|
||||||
5V printer MCU ensure it has a voltage regulator and level shifters.
|
5V printer MCU ensure it has a voltage regulator and level shifters.
|
||||||
|
|
||||||
For ADXL345s/LIS2DWs, make sure that the board supports SPI mode (a small number of
|
For ADXL345s, make sure that the board supports SPI mode (a small number of
|
||||||
boards appear to be hard-configured for I2C by pulling SDO to GND).
|
boards appear to be hard-configured for I2C by pulling SDO to GND).
|
||||||
|
|
||||||
For MPU-9250/MPU-9255/MPU-6515/MPU-6050/MPU-6500s there are also a variety of
|
For MPU-9250/MPU-9255/MPU-6515/MPU-6050/MPU-6500s and LIS2DW/LIS3DH there are also
|
||||||
board designs and clones with different I2C pull-up resistors which will need
|
a variety of board designs and clones with different I2C pull-up resistors which
|
||||||
supplementing.
|
will need supplementing.
|
||||||
|
|
||||||
## MCUs with Klipper I2C *fast-mode* Support
|
## MCUs with Klipper I2C *fast-mode* Support
|
||||||
|
|
||||||
@@ -27,6 +29,7 @@ supplementing.
|
|||||||
| Raspberry Pi | 3B+, Pico | 3A, 3A+, 3B, 4 |
|
| Raspberry Pi | 3B+, Pico | 3A, 3A+, 3B, 4 |
|
||||||
| AVR ATmega | ATmega328p | ATmega32u4, ATmega128, ATmega168, ATmega328, ATmega644p, ATmega1280, ATmega1284, ATmega2560 |
|
| AVR ATmega | ATmega328p | ATmega32u4, ATmega128, ATmega168, ATmega328, ATmega644p, ATmega1280, ATmega1284, ATmega2560 |
|
||||||
| AVR AT90 | - | AT90usb646, AT90usb1286 |
|
| AVR AT90 | - | AT90usb646, AT90usb1286 |
|
||||||
|
| SAMD | SAMC21G18 | SAMC21G18, SAMD21G18, SAMD21E18, SAMD21J18, SAMD21E15, SAMD51G19, SAMD51J19, SAMD51N19, SAMD51P20, SAME51J19, SAME51N19, SAME54P20 |
|
||||||
|
|
||||||
## Installation instructions
|
## Installation instructions
|
||||||
|
|
||||||
@@ -207,17 +210,25 @@ software dependencies not installed by default. First, run on your Raspberry Pi
|
|||||||
the following commands:
|
the following commands:
|
||||||
```
|
```
|
||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt install python3-numpy python3-matplotlib libatlas-base-dev
|
sudo apt install python3-numpy python3-matplotlib libatlas-base-dev libopenblas-dev
|
||||||
```
|
```
|
||||||
|
|
||||||
Next, in order to install NumPy in the Klipper environment, run the command:
|
Next, in order to install NumPy in the Klipper environment, run the command:
|
||||||
```
|
```
|
||||||
~/klippy-env/bin/pip install -v numpy
|
~/klippy-env/bin/pip install -v "numpy<1.26"
|
||||||
```
|
```
|
||||||
Note that, depending on the performance of the CPU, it may take *a lot*
|
Note that, depending on the performance of the CPU, it may take *a lot*
|
||||||
of time, up to 10-20 minutes. Be patient and wait for the completion of
|
of time, up to 10-20 minutes. Be patient and wait for the completion of
|
||||||
the installation. On some occasions, if the board has too little RAM
|
the installation. On some occasions, if the board has too little RAM
|
||||||
the installation may fail and you will need to enable swap.
|
the installation may fail and you will need to enable swap. Also note
|
||||||
|
the forced version, due to newer versions of NumPY having requirements
|
||||||
|
that may not be satisfied in some klipper python environments.
|
||||||
|
|
||||||
|
Once installed please check that no errors show from the command:
|
||||||
|
```
|
||||||
|
~/klippy-env/bin/python -c 'import numpy;'
|
||||||
|
```
|
||||||
|
The correct output should simply be a new line.
|
||||||
|
|
||||||
#### Configure ADXL345 With RPi
|
#### Configure ADXL345 With RPi
|
||||||
|
|
||||||
@@ -305,7 +316,7 @@ you'll also want to modify your `printer.cfg` file to include this:
|
|||||||
|
|
||||||
Restart Klipper via the `RESTART` command.
|
Restart Klipper via the `RESTART` command.
|
||||||
|
|
||||||
#### Configure LIS2DW series
|
#### Configure LIS2DW series over SPI
|
||||||
|
|
||||||
```
|
```
|
||||||
[mcu lis]
|
[mcu lis]
|
||||||
@@ -450,7 +461,11 @@ TEST_RESONANCES AXIS=Y
|
|||||||
```
|
```
|
||||||
This will generate 2 CSV files (`/tmp/resonances_x_*.csv` and
|
This will generate 2 CSV files (`/tmp/resonances_x_*.csv` and
|
||||||
`/tmp/resonances_y_*.csv`). These files can be processed with the stand-alone
|
`/tmp/resonances_y_*.csv`). These files can be processed with the stand-alone
|
||||||
script on a Raspberry Pi. To do that, run the following commands:
|
script on a Raspberry Pi. This script is intended to be run with a single CSV
|
||||||
|
file for each axis measured, although it can be used with multiple CSV files
|
||||||
|
if you desire to average the results. Averaging results can be useful, for
|
||||||
|
example, if resonance tests were done at multiple test points. Delete the extra
|
||||||
|
CSV files if you do not desire to average them.
|
||||||
```
|
```
|
||||||
~/klipper/scripts/calibrate_shaper.py /tmp/resonances_x_*.csv -o /tmp/shaper_calibrate_x.png
|
~/klipper/scripts/calibrate_shaper.py /tmp/resonances_x_*.csv -o /tmp/shaper_calibrate_x.png
|
||||||
~/klipper/scripts/calibrate_shaper.py /tmp/resonances_y_*.csv -o /tmp/shaper_calibrate_y.png
|
~/klipper/scripts/calibrate_shaper.py /tmp/resonances_y_*.csv -o /tmp/shaper_calibrate_y.png
|
||||||
@@ -662,10 +677,41 @@ The same notice applies to the input shaper
|
|||||||
`max_accel` value after the auto-calibration, and the suggested acceleration
|
`max_accel` value after the auto-calibration, and the suggested acceleration
|
||||||
limits will not be applied automatically.
|
limits will not be applied automatically.
|
||||||
|
|
||||||
|
Keep in mind that the maximum acceleration without too much smoothing depends
|
||||||
|
on the `square_corner_velocity`. The general recommendation is not to change
|
||||||
|
it from its default value 5.0, and this is the value used by default by the
|
||||||
|
`calibrate_shaper.py` script. If you did change it though, you should inform
|
||||||
|
the script about it by passing `--square_corner_velocity=...` parameter, e.g.
|
||||||
|
```
|
||||||
|
~/klipper/scripts/calibrate_shaper.py /tmp/resonances_x_*.csv -o /tmp/shaper_calibrate_x.png --square_corner_velocity=10.0
|
||||||
|
```
|
||||||
|
so that it can calculate the maximum acceleration recommendations correctly.
|
||||||
|
Note that the `SHAPER_CALIBRATE` command already takes the configured
|
||||||
|
`square_corner_velocity` parameter into account, and there is no need
|
||||||
|
to specify it explicitly.
|
||||||
|
|
||||||
If you are doing a shaper re-calibration and the reported smoothing for the
|
If you are doing a shaper re-calibration and the reported smoothing for the
|
||||||
suggested shaper configuration is almost the same as what you got during the
|
suggested shaper configuration is almost the same as what you got during the
|
||||||
previous calibration, this step can be skipped.
|
previous calibration, this step can be skipped.
|
||||||
|
|
||||||
|
### Unreliable measurements of resonance frequencies
|
||||||
|
|
||||||
|
Sometimes the resonance measurements can produce bogus results, leading to
|
||||||
|
the incorrect suggestions for the input shapers. This can be caused by a
|
||||||
|
variety of reasons, including running fans on the toolhead, incorrect
|
||||||
|
position or non-rigid mounting of the accelerometer, or mechanical problems
|
||||||
|
such as loose belts or binding or bumpy axis. Keep in mind that all fans
|
||||||
|
should be disabled for resonance testing, especially the noisy ones, and
|
||||||
|
that the accelerometer should be rigidly mounted on the corresponding
|
||||||
|
moving part (e.g. on the bed itself for the bed slinger, or on the extruder
|
||||||
|
of the printer itself and not the carriage, and some people get better
|
||||||
|
results by mounting the accelerometer on the nozzle itself). As for
|
||||||
|
mechanical problems, the user should inspect if there is any fault that
|
||||||
|
can be fixed with a moving axis (e.g. linear guide rails cleaned up and
|
||||||
|
lubricated and V-slot wheels tension adjusted correctly). If none of that
|
||||||
|
helps, a user may try the other shapers from the produced list besides the
|
||||||
|
one recommended by default.
|
||||||
|
|
||||||
### Testing custom axes
|
### Testing custom axes
|
||||||
|
|
||||||
`TEST_RESONANCES` command supports custom axes. While this is not really
|
`TEST_RESONANCES` command supports custom axes. While this is not really
|
||||||
|
|||||||
@@ -31,9 +31,15 @@ overshoot and account for it in its calculations. However, it is
|
|||||||
important that the hardware design is capable of handling overshoot
|
important that the hardware design is capable of handling overshoot
|
||||||
without causing damage to the machine.
|
without causing damage to the machine.
|
||||||
|
|
||||||
Should Klipper detect a communication issue between micro-controllers
|
In order to use this "multi-mcu homing" capability the hardware must
|
||||||
during multi-mcu homing then it will raise a "Communication timeout
|
have predictably low latency between the host computer and all of the
|
||||||
during homing" error.
|
micro-controllers. Typically the round-trip time must be consistently
|
||||||
|
less than 10ms. High latency (even for short periods) is likely to
|
||||||
|
result in homing failures.
|
||||||
|
|
||||||
|
Should high latency result in a failure (or if some other
|
||||||
|
communication issue is detected) then Klipper will raise a
|
||||||
|
"Communication timeout during homing" error.
|
||||||
|
|
||||||
Note that an axis with multiple steppers (eg, `stepper_z` and
|
Note that an axis with multiple steppers (eg, `stepper_z` and
|
||||||
`stepper_z1`) need to be on the same micro-controller in order to use
|
`stepper_z1`) need to be on the same micro-controller in order to use
|
||||||
|
|||||||
79
docs/OctoPrint.md
Normal file
79
docs/OctoPrint.md
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
# OctoPrint for Klipper
|
||||||
|
|
||||||
|
Klipper has a few options for its front ends, Octoprint was the first
|
||||||
|
and original front end for Klipper. This document will give
|
||||||
|
a brief overview of installing with this option.
|
||||||
|
|
||||||
|
## Install with OctoPi
|
||||||
|
|
||||||
|
Start by installing [OctoPi](https://github.com/guysoft/OctoPi) on the
|
||||||
|
Raspberry Pi computer. Use OctoPi v0.17.0 or later - see the
|
||||||
|
[OctoPi releases](https://github.com/guysoft/OctoPi/releases) for
|
||||||
|
release information.
|
||||||
|
|
||||||
|
One should verify that OctoPi boots and that the
|
||||||
|
OctoPrint web server works. After connecting to the OctoPrint web
|
||||||
|
page, follow the prompt to upgrade OctoPrint if needed.
|
||||||
|
|
||||||
|
After installing OctoPi and upgrading OctoPrint, it will be necessary
|
||||||
|
to ssh into the target machine to run a handful of system commands.
|
||||||
|
|
||||||
|
Start by running these commands on your host device:
|
||||||
|
|
||||||
|
__If you do not have git installed, please do so with:__
|
||||||
|
```
|
||||||
|
sudo apt install git
|
||||||
|
```
|
||||||
|
then proceed:
|
||||||
|
```
|
||||||
|
cd ~
|
||||||
|
git clone https://github.com/Klipper3d/klipper
|
||||||
|
./klipper/scripts/install-octopi.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
The above will download Klipper, install the needed system dependencies,
|
||||||
|
setup Klipper to run at system startup, and start the Klipper host
|
||||||
|
software. It will require an internet connection and it may take a few
|
||||||
|
minutes to complete.
|
||||||
|
|
||||||
|
## Installing with KIAUH
|
||||||
|
|
||||||
|
KIAUH can be used to install OctoPrint on a variety of Linux based systems
|
||||||
|
that run a form of Debian. More information can be found
|
||||||
|
at https://github.com/dw-0/kiauh
|
||||||
|
|
||||||
|
## Configuring OctoPrint to use Klipper
|
||||||
|
|
||||||
|
The OctoPrint web server needs to be configured to communicate with the Klipper
|
||||||
|
host software. Using a web browser, login to the OctoPrint web page and then
|
||||||
|
configure the following items:
|
||||||
|
|
||||||
|
Navigate to the Settings tab (the wrench icon at the top of the page).
|
||||||
|
Under "Serial Connection" in "Additional serial ports" add:
|
||||||
|
|
||||||
|
```
|
||||||
|
~/printer_data/comms/klippy.serial
|
||||||
|
```
|
||||||
|
Then click "Save".
|
||||||
|
|
||||||
|
_In some older setups this address may be `/tmp/printer`_
|
||||||
|
|
||||||
|
|
||||||
|
Enter the Settings tab again and under "Serial Connection" change the "Serial Port"
|
||||||
|
setting to the one added above.
|
||||||
|
|
||||||
|
In the Settings tab, navigate to the "Behavior" sub-tab and select the
|
||||||
|
"Cancel any ongoing prints but stay connected to the printer" option. Click "Save".
|
||||||
|
|
||||||
|
From the main page, under the "Connection" section (at the top left of the page)
|
||||||
|
make sure the "Serial Port" is set to the new additional one added
|
||||||
|
and click "Connect". (If it is not in the available selection then
|
||||||
|
try reloading the page.)
|
||||||
|
|
||||||
|
Once connected, navigate to the "Terminal" tab and type "status" (without the quotes)
|
||||||
|
into the command entry box and click "Send". The terminal window will likely report
|
||||||
|
there is an error opening the config file - that means OctoPrint is successfully
|
||||||
|
communicating with Klipper.
|
||||||
|
|
||||||
|
Please proceed to [Installation.md](Installation.md) and the
|
||||||
|
_Building and flashing the micro-controller_ section
|
||||||
@@ -17,6 +17,7 @@ communication with the Klipper developers.
|
|||||||
## Installation and Configuration
|
## Installation and Configuration
|
||||||
|
|
||||||
- [Installation](Installation.md): Guide to installing Klipper.
|
- [Installation](Installation.md): Guide to installing Klipper.
|
||||||
|
- [Octoprint](OctoPrint.md): Guide to installing Octoprint with Klipper.
|
||||||
- [Config Reference](Config_Reference.md): Description of config
|
- [Config Reference](Config_Reference.md): Description of config
|
||||||
parameters.
|
parameters.
|
||||||
- [Rotation Distance](Rotation_Distance.md): Calculating the
|
- [Rotation Distance](Rotation_Distance.md): Calculating the
|
||||||
@@ -99,3 +100,4 @@ communication with the Klipper developers.
|
|||||||
troubleshooting CAN bus.
|
troubleshooting CAN bus.
|
||||||
- [TSL1401CL filament width sensor](TSL1401CL_Filament_Width_Sensor.md)
|
- [TSL1401CL filament width sensor](TSL1401CL_Filament_Width_Sensor.md)
|
||||||
- [Hall filament width sensor](Hall_Filament_Width_Sensor.md)
|
- [Hall filament width sensor](Hall_Filament_Width_Sensor.md)
|
||||||
|
- [Eddy Current Inductive probe](Eddy_Probe.md)
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ automatic probe point, then `ABORT` the manual probe tool and perform
|
|||||||
the XY probe offset calibration described above.
|
the XY probe offset calibration described above.
|
||||||
|
|
||||||
Once the manual probe tool starts, follow the steps described at
|
Once the manual probe tool starts, follow the steps described at
|
||||||
["the paper test"](Bed_Level.md#the-paper-test)) to determine the
|
["the paper test"](Bed_Level.md#the-paper-test) to determine the
|
||||||
actual distance between the nozzle and bed at the given location. Once
|
actual distance between the nozzle and bed at the given location. Once
|
||||||
those steps are complete one can `ACCEPT` the position and save the
|
those steps are complete one can `ACCEPT` the position and save the
|
||||||
results to the config file with:
|
results to the config file with:
|
||||||
|
|||||||
@@ -48,8 +48,8 @@ First, measure the **ringing frequency**.
|
|||||||
to 5.0. It is not advised to increase it when using input shaper
|
to 5.0. It is not advised to increase it when using input shaper
|
||||||
because it can cause more smoothing in parts - it is better to use
|
because it can cause more smoothing in parts - it is better to use
|
||||||
higher acceleration value instead.
|
higher acceleration value instead.
|
||||||
2. Increase `max_accel_to_decel` by issuing the following command:
|
2. Disable the `minimum_cruise_ratio` feature by issuing the following
|
||||||
`SET_VELOCITY_LIMIT ACCEL_TO_DECEL=7000`
|
command: `SET_VELOCITY_LIMIT MINIMUM_CRUISE_RATIO=0`
|
||||||
3. Disable Pressure Advance: `SET_PRESSURE_ADVANCE ADVANCE=0`
|
3. Disable Pressure Advance: `SET_PRESSURE_ADVANCE ADVANCE=0`
|
||||||
4. If you have already added `[input_shaper]` section to the printer.cfg,
|
4. If you have already added `[input_shaper]` section to the printer.cfg,
|
||||||
execute `SET_INPUT_SHAPER SHAPER_FREQ_X=0 SHAPER_FREQ_Y=0` command. If you
|
execute `SET_INPUT_SHAPER SHAPER_FREQ_X=0 SHAPER_FREQ_Y=0` command. If you
|
||||||
@@ -149,7 +149,7 @@ a few other related parameters.
|
|||||||
Print the ringing test model as follows:
|
Print the ringing test model as follows:
|
||||||
|
|
||||||
1. Restart the firmware: `RESTART`
|
1. Restart the firmware: `RESTART`
|
||||||
2. Prepare for test: `SET_VELOCITY_LIMIT ACCEL_TO_DECEL=7000`
|
2. Prepare for test: `SET_VELOCITY_LIMIT MINIMUM_CRUISE_RATIO=0`
|
||||||
3. Disable Pressure Advance: `SET_PRESSURE_ADVANCE ADVANCE=0`
|
3. Disable Pressure Advance: `SET_PRESSURE_ADVANCE ADVANCE=0`
|
||||||
4. Execute: `SET_INPUT_SHAPER SHAPER_TYPE=MZV`
|
4. Execute: `SET_INPUT_SHAPER SHAPER_TYPE=MZV`
|
||||||
5. Execute the command:
|
5. Execute the command:
|
||||||
@@ -270,7 +270,7 @@ frequencies after enabling [input_shaper], this section will not help with that.
|
|||||||
Assuming that you have sliced the ringing model with suggested
|
Assuming that you have sliced the ringing model with suggested
|
||||||
parameters, complete the following steps for each of the axes X and Y:
|
parameters, complete the following steps for each of the axes X and Y:
|
||||||
|
|
||||||
1. Prepare for test: `SET_VELOCITY_LIMIT ACCEL_TO_DECEL=7000`
|
1. Prepare for test: `SET_VELOCITY_LIMIT MINIMUM_CRUISE_RATIO=0`
|
||||||
2. Make sure Pressure Advance is disabled: `SET_PRESSURE_ADVANCE ADVANCE=0`
|
2. Make sure Pressure Advance is disabled: `SET_PRESSURE_ADVANCE ADVANCE=0`
|
||||||
3. Execute: `SET_INPUT_SHAPER SHAPER_TYPE=ZV`
|
3. Execute: `SET_INPUT_SHAPER SHAPER_TYPE=ZV`
|
||||||
4. From the existing ringing test model with your chosen input shaper select
|
4. From the existing ringing test model with your chosen input shaper select
|
||||||
@@ -331,7 +331,7 @@ with suggested parameters, print the test model 3 times as
|
|||||||
follows. First time, prior to printing, run
|
follows. First time, prior to printing, run
|
||||||
|
|
||||||
1. `RESTART`
|
1. `RESTART`
|
||||||
2. `SET_VELOCITY_LIMIT ACCEL_TO_DECEL=7000`
|
2. `SET_VELOCITY_LIMIT MINIMUM_CRUISE_RATIO=0`
|
||||||
3. `SET_PRESSURE_ADVANCE ADVANCE=0`
|
3. `SET_PRESSURE_ADVANCE ADVANCE=0`
|
||||||
4. `SET_INPUT_SHAPER SHAPER_TYPE=2HUMP_EI SHAPER_FREQ_X=60 SHAPER_FREQ_Y=60`
|
4. `SET_INPUT_SHAPER SHAPER_TYPE=2HUMP_EI SHAPER_FREQ_X=60 SHAPER_FREQ_Y=60`
|
||||||
5. `TUNING_TOWER COMMAND=SET_VELOCITY_LIMIT PARAMETER=ACCEL START=1500 STEP_DELTA=500 STEP_HEIGHT=5`
|
5. `TUNING_TOWER COMMAND=SET_VELOCITY_LIMIT PARAMETER=ACCEL START=1500 STEP_DELTA=500 STEP_HEIGHT=5`
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ or by issuing a `SET_SKEW CLEAR=1` gcode.
|
|||||||
|
|
||||||
## Take your measurements
|
## Take your measurements
|
||||||
|
|
||||||
The `[skew_correcton]` module requires 3 measurements for each plane you want
|
The `[skew_correction]` module requires 3 measurements for each plane you want
|
||||||
to correct; the length from Corner A to Corner C, the length from Corner B
|
to correct; the length from Corner A to Corner C, the length from Corner B
|
||||||
to Corner D, and the length from Corner A to Corner D. When measuring length
|
to Corner D, and the length from Corner A to Corner D. When measuring length
|
||||||
AD do not include the flats on the corners that some test objects provide.
|
AD do not include the flats on the corners that some test objects provide.
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ serve the 3D printing community better. Follow them on
|
|||||||
## Sponsors
|
## Sponsors
|
||||||
|
|
||||||
[<img src="./img/sponsors/obico-light-horizontal.png" width="200" style="margin:25px" />](https://obico.io/klipper.html?source=klipper_sponsor)
|
[<img src="./img/sponsors/obico-light-horizontal.png" width="200" style="margin:25px" />](https://obico.io/klipper.html?source=klipper_sponsor)
|
||||||
[<img src="./img/sponsors/peopoly-logo.png" width="200" style="margin:25px" />](https://peopoly.net)
|
|
||||||
|
|
||||||
## Klipper Developers
|
## Klipper Developers
|
||||||
|
|
||||||
|
|||||||
@@ -168,6 +168,12 @@ The following information is available in the
|
|||||||
module. These settings may differ from the config file if a
|
module. These settings may differ from the config file if a
|
||||||
`SET_RETRACTION` command alters them.
|
`SET_RETRACTION` command alters them.
|
||||||
|
|
||||||
|
## gcode
|
||||||
|
|
||||||
|
The following information is available in the `gcode` object:
|
||||||
|
- `commands`: Returns a list of all currently available commands. For each
|
||||||
|
command, if a help string is defined it will also be provided.
|
||||||
|
|
||||||
## gcode_button
|
## gcode_button
|
||||||
|
|
||||||
The following information is available in
|
The following information is available in
|
||||||
@@ -318,7 +324,8 @@ is defined):
|
|||||||
## output_pin
|
## output_pin
|
||||||
|
|
||||||
The following information is available in
|
The following information is available in
|
||||||
[output_pin some_name](Config_Reference.md#output_pin) objects:
|
[output_pin some_name](Config_Reference.md#output_pin) and
|
||||||
|
[pwm_tool some_name](Config_Reference.md#pwm_tool) objects:
|
||||||
- `value`: The "value" of the pin, as set by a `SET_PIN` command.
|
- `value`: The "value" of the pin, as set by a `SET_PIN` command.
|
||||||
|
|
||||||
## palette2
|
## palette2
|
||||||
@@ -367,6 +374,13 @@ is defined):
|
|||||||
template expansion, the PROBE (or similar) command must be run prior
|
template expansion, the PROBE (or similar) command must be run prior
|
||||||
to the macro containing this reference.
|
to the macro containing this reference.
|
||||||
|
|
||||||
|
## pwm_cycle_time
|
||||||
|
|
||||||
|
The following information is available in
|
||||||
|
[pwm_cycle_time some_name](Config_Reference.md#pwm_cycle_time)
|
||||||
|
objects:
|
||||||
|
- `value`: The "value" of the pin, as set by a `SET_PIN` command.
|
||||||
|
|
||||||
## quad_gantry_level
|
## quad_gantry_level
|
||||||
|
|
||||||
The following information is available in the `quad_gantry_level` object
|
The following information is available in the `quad_gantry_level` object
|
||||||
@@ -431,6 +445,7 @@ The following information is available in
|
|||||||
|
|
||||||
[bme280 config_section_name](Config_Reference.md#bmp280bme280bme680-temperature-sensor),
|
[bme280 config_section_name](Config_Reference.md#bmp280bme280bme680-temperature-sensor),
|
||||||
[htu21d config_section_name](Config_Reference.md#htu21d-sensor),
|
[htu21d config_section_name](Config_Reference.md#htu21d-sensor),
|
||||||
|
[sht3x config_section_name](Config_Reference.md#sht31-sensor),
|
||||||
[lm75 config_section_name](Config_Reference.md#lm75-temperature-sensor),
|
[lm75 config_section_name](Config_Reference.md#lm75-temperature-sensor),
|
||||||
[temperature_host config_section_name](Config_Reference.md#host-temperature-sensor)
|
[temperature_host config_section_name](Config_Reference.md#host-temperature-sensor)
|
||||||
and
|
and
|
||||||
@@ -438,7 +453,7 @@ and
|
|||||||
objects:
|
objects:
|
||||||
- `temperature`: The last read temperature from the sensor.
|
- `temperature`: The last read temperature from the sensor.
|
||||||
- `humidity`, `pressure`, `gas`: The last read values from the sensor
|
- `humidity`, `pressure`, `gas`: The last read values from the sensor
|
||||||
(only on bme280, htu21d, and lm75 sensors).
|
(only on bme280, htu21d, sht3x and lm75 sensors).
|
||||||
|
|
||||||
## temperature_fan
|
## temperature_fan
|
||||||
|
|
||||||
@@ -497,7 +512,7 @@ The following information is available in the `toolhead` object
|
|||||||
limit value (eg, `axis_minimum.x`, `axis_maximum.z`).
|
limit value (eg, `axis_minimum.x`, `axis_maximum.z`).
|
||||||
- For Delta printers the `cone_start_z` is the max z height at
|
- For Delta printers the `cone_start_z` is the max z height at
|
||||||
maximum radius (`printer.toolhead.cone_start_z`).
|
maximum radius (`printer.toolhead.cone_start_z`).
|
||||||
- `max_velocity`, `max_accel`, `max_accel_to_decel`,
|
- `max_velocity`, `max_accel`, `minimum_cruise_ratio`,
|
||||||
`square_corner_velocity`: The current printing limits that are in
|
`square_corner_velocity`: The current printing limits that are in
|
||||||
effect. This may differ from the config file settings if a
|
effect. This may differ from the config file settings if a
|
||||||
`SET_VELOCITY_LIMIT` (or `M204`) command alters them at run-time.
|
`SET_VELOCITY_LIMIT` (or `M204`) command alters them at run-time.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Using PWM tools
|
# Using PWM tools
|
||||||
|
|
||||||
This document describes how to setup a PWM-controlled laser or spindle
|
This document describes how to setup a PWM-controlled laser or spindle
|
||||||
using `output_pin` and some macros.
|
using `pwm_tool` and some macros.
|
||||||
|
|
||||||
## How does it work?
|
## How does it work?
|
||||||
|
|
||||||
@@ -26,14 +26,6 @@ so that when your host or MCU encounters an error, the tool will stop.
|
|||||||
|
|
||||||
For an example configuration, see [config/sample-pwm-tool.cfg](/config/sample-pwm-tool.cfg).
|
For an example configuration, see [config/sample-pwm-tool.cfg](/config/sample-pwm-tool.cfg).
|
||||||
|
|
||||||
## Current Limitations
|
|
||||||
|
|
||||||
There is a limitation of how frequent PWM updates may occur.
|
|
||||||
While being very precise, a PWM update may only occur every 0.1 seconds,
|
|
||||||
rendering it almost useless for raster engraving.
|
|
||||||
However, there exists an [experimental branch](https://github.com/Cirromulus/klipper/tree/laser_tool) with its own tradeoffs.
|
|
||||||
In long term, it is planned to add this functionality to main-line klipper.
|
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
`M3/M4 S<value>` : Set PWM duty-cycle. Values between 0 and 255.
|
`M3/M4 S<value>` : Set PWM duty-cycle. Values between 0 and 255.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Python virtualenv module requirements for mkdocs
|
# Python virtualenv module requirements for mkdocs
|
||||||
jinja2==3.0.3
|
jinja2==3.1.4
|
||||||
mkdocs==1.2.3
|
mkdocs==1.2.4
|
||||||
mkdocs-material==8.1.3
|
mkdocs-material==8.1.3
|
||||||
mkdocs-simple-hooks==0.1.3
|
mkdocs-simple-hooks==0.1.3
|
||||||
mkdocs-exclude==1.0.2
|
mkdocs-exclude==1.0.2
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ extra:
|
|||||||
# https://squidfunk.github.io/mkdocs-material/setup/setting-up-site-analytics/#site-search-tracking
|
# https://squidfunk.github.io/mkdocs-material/setup/setting-up-site-analytics/#site-search-tracking
|
||||||
analytics:
|
analytics:
|
||||||
provider: google
|
provider: google
|
||||||
property: UA-138371409-1
|
property: G-VEN1PGNQL4
|
||||||
# Language Selection
|
# Language Selection
|
||||||
alternate:
|
alternate:
|
||||||
- name: English
|
- name: English
|
||||||
@@ -88,7 +88,9 @@ nav:
|
|||||||
- Config_Changes.md
|
- Config_Changes.md
|
||||||
- Contact.md
|
- Contact.md
|
||||||
- Installation and Configuration:
|
- Installation and Configuration:
|
||||||
|
- Installation:
|
||||||
- Installation.md
|
- Installation.md
|
||||||
|
- OctoPrint.md
|
||||||
- Configuration Reference:
|
- Configuration Reference:
|
||||||
- Config_Reference.md
|
- Config_Reference.md
|
||||||
- Rotation_Distance.md
|
- Rotation_Distance.md
|
||||||
@@ -138,4 +140,5 @@ nav:
|
|||||||
- CANBUS_Troubleshooting.md
|
- CANBUS_Troubleshooting.md
|
||||||
- TSL1401CL_Filament_Width_Sensor.md
|
- TSL1401CL_Filament_Width_Sensor.md
|
||||||
- Hall_Filament_Width_Sensor.md
|
- Hall_Filament_Width_Sensor.md
|
||||||
|
- Eddy_Probe.md
|
||||||
- Sponsors.md
|
- Sponsors.md
|
||||||
|
|||||||
4
docs/img/adaptive_bed_mesh.svg
Normal file
4
docs/img/adaptive_bed_mesh.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 36 KiB |
4
docs/img/adaptive_bed_mesh_margin.svg
Normal file
4
docs/img/adaptive_bed_mesh_margin.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 20 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 20 KiB |
@@ -6,13 +6,16 @@ title: Welcome
|
|||||||
|
|
||||||
{ .center-image }
|
{ .center-image }
|
||||||
|
|
||||||
Klipper is a 3d-Printer firmware. It combines the power of a general
|
The Klipper firmware controls 3d-Printers. It combines the power of a
|
||||||
purpose computer with one or more micro-controllers. See the
|
general purpose computer with one or more micro-controllers. See the
|
||||||
[features](Features.md) document for more information on why you
|
[features document](https://www.klipper3d.org/Features.html) for more
|
||||||
should use Klipper.
|
information on why you should use the Klipper software.
|
||||||
|
|
||||||
To begin using Klipper start by [installing](Installation.md) it.
|
Start by [installing Klipper software](https://www.klipper3d.org/Installation.html).
|
||||||
|
|
||||||
Klipper is Free Software. Read the [documentation](Overview.md) or
|
Klipper software is Free Software. Read the
|
||||||
view [the Klipper code on github](https://github.com/Klipper3d/klipper).
|
[documentation](https://www.klipper3d.org/Overview.html), see the
|
||||||
We depend on the generous support from our [sponsors](Sponsors.md).
|
[license](COPYING), or
|
||||||
|
[download](https://github.com/Klipper3d/Klipper) the software. We
|
||||||
|
depend on the generous support from our
|
||||||
|
[sponsors](https://www.klipper3d.org/Sponsors.html).
|
||||||
|
|||||||
@@ -49,6 +49,8 @@ defs_stepcompress = """
|
|||||||
, uint64_t clock);
|
, uint64_t clock);
|
||||||
int stepcompress_queue_msg(struct stepcompress *sc
|
int stepcompress_queue_msg(struct stepcompress *sc
|
||||||
, uint32_t *data, int len);
|
, uint32_t *data, int len);
|
||||||
|
int stepcompress_queue_mq_msg(struct stepcompress *sc, uint64_t req_clock
|
||||||
|
, uint32_t *data, int len);
|
||||||
int stepcompress_extract_old(struct stepcompress *sc
|
int stepcompress_extract_old(struct stepcompress *sc
|
||||||
, struct pull_history_steps *p, int max
|
, struct pull_history_steps *p, int max
|
||||||
, uint64_t start_clock, uint64_t end_clock);
|
, uint64_t start_clock, uint64_t end_clock);
|
||||||
@@ -58,7 +60,8 @@ defs_stepcompress = """
|
|||||||
void steppersync_free(struct steppersync *ss);
|
void steppersync_free(struct steppersync *ss);
|
||||||
void steppersync_set_time(struct steppersync *ss
|
void steppersync_set_time(struct steppersync *ss
|
||||||
, double time_offset, double mcu_freq);
|
, double time_offset, double mcu_freq);
|
||||||
int steppersync_flush(struct steppersync *ss, uint64_t move_clock);
|
int steppersync_flush(struct steppersync *ss, uint64_t move_clock
|
||||||
|
, uint64_t clear_history_clock);
|
||||||
"""
|
"""
|
||||||
|
|
||||||
defs_itersolve = """
|
defs_itersolve = """
|
||||||
@@ -92,7 +95,8 @@ defs_trapq = """
|
|||||||
, double start_pos_x, double start_pos_y, double start_pos_z
|
, double start_pos_x, double start_pos_y, double start_pos_z
|
||||||
, double axes_r_x, double axes_r_y, double axes_r_z
|
, double axes_r_x, double axes_r_y, double axes_r_z
|
||||||
, double start_v, double cruise_v, double accel);
|
, double start_v, double cruise_v, double accel);
|
||||||
void trapq_finalize_moves(struct trapq *tq, double print_time);
|
void trapq_finalize_moves(struct trapq *tq, double print_time
|
||||||
|
, double clear_history_time);
|
||||||
void trapq_set_position(struct trapq *tq, double print_time
|
void trapq_set_position(struct trapq *tq, double print_time
|
||||||
, double pos_x, double pos_y, double pos_z);
|
, double pos_x, double pos_y, double pos_z);
|
||||||
int trapq_extract_old(struct trapq *tq, struct pull_move *p, int max
|
int trapq_extract_old(struct trapq *tq, struct pull_move *p, int max
|
||||||
@@ -138,8 +142,9 @@ defs_kin_winch = """
|
|||||||
|
|
||||||
defs_kin_extruder = """
|
defs_kin_extruder = """
|
||||||
struct stepper_kinematics *extruder_stepper_alloc(void);
|
struct stepper_kinematics *extruder_stepper_alloc(void);
|
||||||
|
void extruder_stepper_free(struct stepper_kinematics *sk);
|
||||||
void extruder_set_pressure_advance(struct stepper_kinematics *sk
|
void extruder_set_pressure_advance(struct stepper_kinematics *sk
|
||||||
, double pressure_advance, double smooth_time);
|
, double print_time, double pressure_advance, double smooth_time);
|
||||||
"""
|
"""
|
||||||
|
|
||||||
defs_kin_shaper = """
|
defs_kin_shaper = """
|
||||||
|
|||||||
@@ -9,9 +9,15 @@
|
|||||||
#include <string.h> // memset
|
#include <string.h> // memset
|
||||||
#include "compiler.h" // __visible
|
#include "compiler.h" // __visible
|
||||||
#include "itersolve.h" // struct stepper_kinematics
|
#include "itersolve.h" // struct stepper_kinematics
|
||||||
|
#include "list.h" // list_node
|
||||||
#include "pyhelper.h" // errorf
|
#include "pyhelper.h" // errorf
|
||||||
#include "trapq.h" // move_get_distance
|
#include "trapq.h" // move_get_distance
|
||||||
|
|
||||||
|
struct pa_params {
|
||||||
|
double pressure_advance, active_print_time;
|
||||||
|
struct list_node node;
|
||||||
|
};
|
||||||
|
|
||||||
// Without pressure advance, the extruder stepper position is:
|
// Without pressure advance, the extruder stepper position is:
|
||||||
// extruder_position(t) = nominal_position(t)
|
// extruder_position(t) = nominal_position(t)
|
||||||
// When pressure advance is enabled, additional filament is pushed
|
// When pressure advance is enabled, additional filament is pushed
|
||||||
@@ -52,17 +58,25 @@ extruder_integrate_time(double base, double start_v, double half_accel
|
|||||||
|
|
||||||
// Calculate the definitive integral of extruder for a given move
|
// Calculate the definitive integral of extruder for a given move
|
||||||
static double
|
static double
|
||||||
pa_move_integrate(struct move *m, double pressure_advance
|
pa_move_integrate(struct move *m, struct list_head *pa_list
|
||||||
, double base, double start, double end, double time_offset)
|
, double base, double start, double end, double time_offset)
|
||||||
{
|
{
|
||||||
if (start < 0.)
|
if (start < 0.)
|
||||||
start = 0.;
|
start = 0.;
|
||||||
if (end > m->move_t)
|
if (end > m->move_t)
|
||||||
end = m->move_t;
|
end = m->move_t;
|
||||||
// Calculate base position and velocity with pressure advance
|
// Determine pressure_advance value
|
||||||
int can_pressure_advance = m->axes_r.y != 0.;
|
int can_pressure_advance = m->axes_r.y != 0.;
|
||||||
if (!can_pressure_advance)
|
double pressure_advance = 0.;
|
||||||
pressure_advance = 0.;
|
if (can_pressure_advance) {
|
||||||
|
struct pa_params *pa = list_last_entry(pa_list, struct pa_params, node);
|
||||||
|
while (unlikely(pa->active_print_time > m->print_time) &&
|
||||||
|
!list_is_first(&pa->node, pa_list)) {
|
||||||
|
pa = list_prev_entry(pa, node);
|
||||||
|
}
|
||||||
|
pressure_advance = pa->pressure_advance;
|
||||||
|
}
|
||||||
|
// Calculate base position and velocity with pressure advance
|
||||||
base += pressure_advance * m->start_v;
|
base += pressure_advance * m->start_v;
|
||||||
double start_v = m->start_v + pressure_advance * 2. * m->half_accel;
|
double start_v = m->start_v + pressure_advance * 2. * m->half_accel;
|
||||||
// Calculate definitive integral
|
// Calculate definitive integral
|
||||||
@@ -75,20 +89,20 @@ pa_move_integrate(struct move *m, double pressure_advance
|
|||||||
// Calculate the definitive integral of the extruder over a range of moves
|
// Calculate the definitive integral of the extruder over a range of moves
|
||||||
static double
|
static double
|
||||||
pa_range_integrate(struct move *m, double move_time
|
pa_range_integrate(struct move *m, double move_time
|
||||||
, double pressure_advance, double hst)
|
, struct list_head *pa_list, double hst)
|
||||||
{
|
{
|
||||||
// Calculate integral for the current move
|
// Calculate integral for the current move
|
||||||
double res = 0., start = move_time - hst, end = move_time + hst;
|
double res = 0., start = move_time - hst, end = move_time + hst;
|
||||||
double start_base = m->start_pos.x;
|
double start_base = m->start_pos.x;
|
||||||
res += pa_move_integrate(m, pressure_advance, 0., start, move_time, start);
|
res += pa_move_integrate(m, pa_list, 0., start, move_time, start);
|
||||||
res -= pa_move_integrate(m, pressure_advance, 0., move_time, end, end);
|
res -= pa_move_integrate(m, pa_list, 0., move_time, end, end);
|
||||||
// Integrate over previous moves
|
// Integrate over previous moves
|
||||||
struct move *prev = m;
|
struct move *prev = m;
|
||||||
while (unlikely(start < 0.)) {
|
while (unlikely(start < 0.)) {
|
||||||
prev = list_prev_entry(prev, node);
|
prev = list_prev_entry(prev, node);
|
||||||
start += prev->move_t;
|
start += prev->move_t;
|
||||||
double base = prev->start_pos.x - start_base;
|
double base = prev->start_pos.x - start_base;
|
||||||
res += pa_move_integrate(prev, pressure_advance, base, start
|
res += pa_move_integrate(prev, pa_list, base, start
|
||||||
, prev->move_t, start);
|
, prev->move_t, start);
|
||||||
}
|
}
|
||||||
// Integrate over future moves
|
// Integrate over future moves
|
||||||
@@ -96,14 +110,15 @@ pa_range_integrate(struct move *m, double move_time
|
|||||||
end -= m->move_t;
|
end -= m->move_t;
|
||||||
m = list_next_entry(m, node);
|
m = list_next_entry(m, node);
|
||||||
double base = m->start_pos.x - start_base;
|
double base = m->start_pos.x - start_base;
|
||||||
res -= pa_move_integrate(m, pressure_advance, base, 0., end, end);
|
res -= pa_move_integrate(m, pa_list, base, 0., end, end);
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct extruder_stepper {
|
struct extruder_stepper {
|
||||||
struct stepper_kinematics sk;
|
struct stepper_kinematics sk;
|
||||||
double pressure_advance, half_smooth_time, inv_half_smooth_time2;
|
struct list_head pa_list;
|
||||||
|
double half_smooth_time, inv_half_smooth_time2;
|
||||||
};
|
};
|
||||||
|
|
||||||
static double
|
static double
|
||||||
@@ -116,22 +131,45 @@ extruder_calc_position(struct stepper_kinematics *sk, struct move *m
|
|||||||
// Pressure advance not enabled
|
// Pressure advance not enabled
|
||||||
return m->start_pos.x + move_get_distance(m, move_time);
|
return m->start_pos.x + move_get_distance(m, move_time);
|
||||||
// Apply pressure advance and average over smooth_time
|
// Apply pressure advance and average over smooth_time
|
||||||
double area = pa_range_integrate(m, move_time, es->pressure_advance, hst);
|
double area = pa_range_integrate(m, move_time, &es->pa_list, hst);
|
||||||
return m->start_pos.x + area * es->inv_half_smooth_time2;
|
return m->start_pos.x + area * es->inv_half_smooth_time2;
|
||||||
}
|
}
|
||||||
|
|
||||||
void __visible
|
void __visible
|
||||||
extruder_set_pressure_advance(struct stepper_kinematics *sk
|
extruder_set_pressure_advance(struct stepper_kinematics *sk, double print_time
|
||||||
, double pressure_advance, double smooth_time)
|
, double pressure_advance, double smooth_time)
|
||||||
{
|
{
|
||||||
struct extruder_stepper *es = container_of(sk, struct extruder_stepper, sk);
|
struct extruder_stepper *es = container_of(sk, struct extruder_stepper, sk);
|
||||||
double hst = smooth_time * .5;
|
double hst = smooth_time * .5, old_hst = es->half_smooth_time;
|
||||||
es->half_smooth_time = hst;
|
es->half_smooth_time = hst;
|
||||||
es->sk.gen_steps_pre_active = es->sk.gen_steps_post_active = hst;
|
es->sk.gen_steps_pre_active = es->sk.gen_steps_post_active = hst;
|
||||||
|
|
||||||
|
// Cleanup old pressure advance parameters
|
||||||
|
double cleanup_time = sk->last_flush_time - (old_hst > hst ? old_hst : hst);
|
||||||
|
struct pa_params *first_pa = list_first_entry(
|
||||||
|
&es->pa_list, struct pa_params, node);
|
||||||
|
while (!list_is_last(&first_pa->node, &es->pa_list)) {
|
||||||
|
struct pa_params *next_pa = list_next_entry(first_pa, node);
|
||||||
|
if (next_pa->active_print_time >= cleanup_time) break;
|
||||||
|
list_del(&first_pa->node);
|
||||||
|
first_pa = next_pa;
|
||||||
|
}
|
||||||
|
|
||||||
if (! hst)
|
if (! hst)
|
||||||
return;
|
return;
|
||||||
es->inv_half_smooth_time2 = 1. / (hst * hst);
|
es->inv_half_smooth_time2 = 1. / (hst * hst);
|
||||||
es->pressure_advance = pressure_advance;
|
|
||||||
|
if (list_last_entry(&es->pa_list, struct pa_params, node)->pressure_advance
|
||||||
|
== pressure_advance) {
|
||||||
|
// Retain old pa_params
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Add new pressure advance parameters
|
||||||
|
struct pa_params *pa = malloc(sizeof(*pa));
|
||||||
|
memset(pa, 0, sizeof(*pa));
|
||||||
|
pa->pressure_advance = pressure_advance;
|
||||||
|
pa->active_print_time = print_time;
|
||||||
|
list_add_tail(&pa->node, &es->pa_list);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct stepper_kinematics * __visible
|
struct stepper_kinematics * __visible
|
||||||
@@ -141,5 +179,22 @@ extruder_stepper_alloc(void)
|
|||||||
memset(es, 0, sizeof(*es));
|
memset(es, 0, sizeof(*es));
|
||||||
es->sk.calc_position_cb = extruder_calc_position;
|
es->sk.calc_position_cb = extruder_calc_position;
|
||||||
es->sk.active_flags = AF_X;
|
es->sk.active_flags = AF_X;
|
||||||
|
list_init(&es->pa_list);
|
||||||
|
struct pa_params *pa = malloc(sizeof(*pa));
|
||||||
|
memset(pa, 0, sizeof(*pa));
|
||||||
|
list_add_tail(&pa->node, &es->pa_list);
|
||||||
return &es->sk;
|
return &es->sk;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void __visible
|
||||||
|
extruder_stepper_free(struct stepper_kinematics *sk)
|
||||||
|
{
|
||||||
|
struct extruder_stepper *es = container_of(sk, struct extruder_stepper, sk);
|
||||||
|
while (!list_empty(&es->pa_list)) {
|
||||||
|
struct pa_params *pa = list_first_entry(
|
||||||
|
&es->pa_list, struct pa_params, node);
|
||||||
|
list_del(&pa->node);
|
||||||
|
free(pa);
|
||||||
|
}
|
||||||
|
free(sk);
|
||||||
|
}
|
||||||
|
|||||||
@@ -222,12 +222,11 @@ handle_message(struct serialqueue *sq, double eventtime, int len)
|
|||||||
pthread_mutex_lock(&sq->lock);
|
pthread_mutex_lock(&sq->lock);
|
||||||
|
|
||||||
// Calculate receive sequence number
|
// Calculate receive sequence number
|
||||||
uint64_t rseq = ((sq->receive_seq & ~MESSAGE_SEQ_MASK)
|
uint32_t rseq_delta = ((sq->input_buf[MESSAGE_POS_SEQ] - sq->receive_seq)
|
||||||
| (sq->input_buf[MESSAGE_POS_SEQ] & MESSAGE_SEQ_MASK));
|
& MESSAGE_SEQ_MASK);
|
||||||
|
uint64_t rseq = sq->receive_seq + rseq_delta;
|
||||||
if (rseq != sq->receive_seq) {
|
if (rseq != sq->receive_seq) {
|
||||||
// New sequence number
|
// New sequence number
|
||||||
if (rseq < sq->receive_seq)
|
|
||||||
rseq += MESSAGE_SEQ_MASK+1;
|
|
||||||
if (rseq > sq->send_seq && sq->receive_seq != 1) {
|
if (rseq > sq->send_seq && sq->receive_seq != 1) {
|
||||||
// An ack for a message not sent? Out of order message?
|
// An ack for a message not sent? Out of order message?
|
||||||
sq->bytes_invalid += len;
|
sq->bytes_invalid += len;
|
||||||
|
|||||||
@@ -54,8 +54,6 @@ struct step_move {
|
|||||||
int16_t add;
|
int16_t add;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define HISTORY_EXPIRE (30.0)
|
|
||||||
|
|
||||||
struct history_steps {
|
struct history_steps {
|
||||||
struct list_node node;
|
struct list_node node;
|
||||||
uint64_t first_clock, last_clock;
|
uint64_t first_clock, last_clock;
|
||||||
@@ -292,6 +290,13 @@ free_history(struct stepcompress *sc, uint64_t end_clock)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Expire the stepcompress history older than the given clock
|
||||||
|
static void
|
||||||
|
stepcompress_history_expire(struct stepcompress *sc, uint64_t end_clock)
|
||||||
|
{
|
||||||
|
free_history(sc, end_clock);
|
||||||
|
}
|
||||||
|
|
||||||
// Free memory associated with a 'stepcompress' object
|
// Free memory associated with a 'stepcompress' object
|
||||||
void __visible
|
void __visible
|
||||||
stepcompress_free(struct stepcompress *sc)
|
stepcompress_free(struct stepcompress *sc)
|
||||||
@@ -322,9 +327,6 @@ calc_last_step_print_time(struct stepcompress *sc)
|
|||||||
{
|
{
|
||||||
double lsc = sc->last_step_clock;
|
double lsc = sc->last_step_clock;
|
||||||
sc->last_step_print_time = sc->mcu_time_offset + (lsc - .5) / sc->mcu_freq;
|
sc->last_step_print_time = sc->mcu_time_offset + (lsc - .5) / sc->mcu_freq;
|
||||||
|
|
||||||
if (lsc > sc->mcu_freq * HISTORY_EXPIRE)
|
|
||||||
free_history(sc, lsc - sc->mcu_freq * HISTORY_EXPIRE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the conversion rate of 'print_time' to mcu clock
|
// Set the conversion rate of 'print_time' to mcu clock
|
||||||
@@ -623,6 +625,21 @@ stepcompress_queue_msg(struct stepcompress *sc, uint32_t *data, int len)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Queue an mcu command that will consume space in the mcu move queue
|
||||||
|
int __visible
|
||||||
|
stepcompress_queue_mq_msg(struct stepcompress *sc, uint64_t req_clock
|
||||||
|
, uint32_t *data, int len)
|
||||||
|
{
|
||||||
|
int ret = stepcompress_flush(sc, UINT64_MAX);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
struct queue_message *qm = message_alloc_and_encode(data, len);
|
||||||
|
qm->min_clock = qm->req_clock = req_clock;
|
||||||
|
list_add_tail(&qm->node, &sc->msg_queue);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Return history of queue_step commands
|
// Return history of queue_step commands
|
||||||
int __visible
|
int __visible
|
||||||
stepcompress_extract_old(struct stepcompress *sc, struct pull_history_steps *p
|
stepcompress_extract_old(struct stepcompress *sc, struct pull_history_steps *p
|
||||||
@@ -716,6 +733,18 @@ steppersync_set_time(struct steppersync *ss, double time_offset
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Expire the stepcompress history before the given clock time
|
||||||
|
static void
|
||||||
|
steppersync_history_expire(struct steppersync *ss, uint64_t end_clock)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < ss->sc_num; i++)
|
||||||
|
{
|
||||||
|
struct stepcompress *sc = ss->sc_list[i];
|
||||||
|
stepcompress_history_expire(sc, end_clock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Implement a binary heap algorithm to track when the next available
|
// Implement a binary heap algorithm to track when the next available
|
||||||
// 'struct move' in the mcu will be available
|
// 'struct move' in the mcu will be available
|
||||||
static void
|
static void
|
||||||
@@ -743,7 +772,8 @@ heap_replace(struct steppersync *ss, uint64_t req_clock)
|
|||||||
|
|
||||||
// Find and transmit any scheduled steps prior to the given 'move_clock'
|
// Find and transmit any scheduled steps prior to the given 'move_clock'
|
||||||
int __visible
|
int __visible
|
||||||
steppersync_flush(struct steppersync *ss, uint64_t move_clock)
|
steppersync_flush(struct steppersync *ss, uint64_t move_clock
|
||||||
|
, uint64_t clear_history_clock)
|
||||||
{
|
{
|
||||||
// Flush each stepcompress to the specified move_clock
|
// Flush each stepcompress to the specified move_clock
|
||||||
int i;
|
int i;
|
||||||
@@ -791,5 +821,7 @@ steppersync_flush(struct steppersync *ss, uint64_t move_clock)
|
|||||||
// Transmit commands
|
// Transmit commands
|
||||||
if (!list_empty(&msgs))
|
if (!list_empty(&msgs))
|
||||||
serialqueue_send_batch(ss->sq, ss->cq, &msgs);
|
serialqueue_send_batch(ss->sq, ss->cq, &msgs);
|
||||||
|
|
||||||
|
steppersync_history_expire(ss, clear_history_clock);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ int stepcompress_set_last_position(struct stepcompress *sc, uint64_t clock
|
|||||||
int64_t stepcompress_find_past_position(struct stepcompress *sc
|
int64_t stepcompress_find_past_position(struct stepcompress *sc
|
||||||
, uint64_t clock);
|
, uint64_t clock);
|
||||||
int stepcompress_queue_msg(struct stepcompress *sc, uint32_t *data, int len);
|
int stepcompress_queue_msg(struct stepcompress *sc, uint32_t *data, int len);
|
||||||
|
int stepcompress_queue_mq_msg(struct stepcompress *sc, uint64_t req_clock
|
||||||
|
, uint32_t *data, int len);
|
||||||
int stepcompress_extract_old(struct stepcompress *sc
|
int stepcompress_extract_old(struct stepcompress *sc
|
||||||
, struct pull_history_steps *p, int max
|
, struct pull_history_steps *p, int max
|
||||||
, uint64_t start_clock, uint64_t end_clock);
|
, uint64_t start_clock, uint64_t end_clock);
|
||||||
@@ -40,6 +42,7 @@ struct steppersync *steppersync_alloc(
|
|||||||
void steppersync_free(struct steppersync *ss);
|
void steppersync_free(struct steppersync *ss);
|
||||||
void steppersync_set_time(struct steppersync *ss, double time_offset
|
void steppersync_set_time(struct steppersync *ss, double time_offset
|
||||||
, double mcu_freq);
|
, double mcu_freq);
|
||||||
int steppersync_flush(struct steppersync *ss, uint64_t move_clock);
|
int steppersync_flush(struct steppersync *ss, uint64_t move_clock
|
||||||
|
, uint64_t clear_history_clock);
|
||||||
|
|
||||||
#endif // stepcompress.h
|
#endif // stepcompress.h
|
||||||
|
|||||||
@@ -163,11 +163,10 @@ trapq_append(struct trapq *tq, double print_time
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#define HISTORY_EXPIRE (30.0)
|
|
||||||
|
|
||||||
// Expire any moves older than `print_time` from the trapezoid velocity queue
|
// Expire any moves older than `print_time` from the trapezoid velocity queue
|
||||||
void __visible
|
void __visible
|
||||||
trapq_finalize_moves(struct trapq *tq, double print_time)
|
trapq_finalize_moves(struct trapq *tq, double print_time
|
||||||
|
, double clear_history_time)
|
||||||
{
|
{
|
||||||
struct move *head_sentinel = list_first_entry(&tq->moves, struct move,node);
|
struct move *head_sentinel = list_first_entry(&tq->moves, struct move,node);
|
||||||
struct move *tail_sentinel = list_last_entry(&tq->moves, struct move, node);
|
struct move *tail_sentinel = list_last_entry(&tq->moves, struct move, node);
|
||||||
@@ -190,10 +189,9 @@ trapq_finalize_moves(struct trapq *tq, double print_time)
|
|||||||
if (list_empty(&tq->history))
|
if (list_empty(&tq->history))
|
||||||
return;
|
return;
|
||||||
struct move *latest = list_first_entry(&tq->history, struct move, node);
|
struct move *latest = list_first_entry(&tq->history, struct move, node);
|
||||||
double expire_time = latest->print_time + latest->move_t - HISTORY_EXPIRE;
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
struct move *m = list_last_entry(&tq->history, struct move, node);
|
struct move *m = list_last_entry(&tq->history, struct move, node);
|
||||||
if (m == latest || m->print_time + m->move_t > expire_time)
|
if (m == latest || m->print_time + m->move_t > clear_history_time)
|
||||||
break;
|
break;
|
||||||
list_del(&m->node);
|
list_del(&m->node);
|
||||||
free(m);
|
free(m);
|
||||||
@@ -206,7 +204,7 @@ trapq_set_position(struct trapq *tq, double print_time
|
|||||||
, double pos_x, double pos_y, double pos_z)
|
, double pos_x, double pos_y, double pos_z)
|
||||||
{
|
{
|
||||||
// Flush all moves from trapq
|
// Flush all moves from trapq
|
||||||
trapq_finalize_moves(tq, NEVER_TIME);
|
trapq_finalize_moves(tq, NEVER_TIME, 0);
|
||||||
|
|
||||||
// Prune any moves in the trapq history that were interrupted
|
// Prune any moves in the trapq history that were interrupted
|
||||||
while (!list_empty(&tq->history)) {
|
while (!list_empty(&tq->history)) {
|
||||||
|
|||||||
@@ -43,7 +43,8 @@ void trapq_append(struct trapq *tq, double print_time
|
|||||||
, double start_pos_x, double start_pos_y, double start_pos_z
|
, double start_pos_x, double start_pos_y, double start_pos_z
|
||||||
, double axes_r_x, double axes_r_y, double axes_r_z
|
, double axes_r_x, double axes_r_y, double axes_r_z
|
||||||
, double start_v, double cruise_v, double accel);
|
, double start_v, double cruise_v, double accel);
|
||||||
void trapq_finalize_moves(struct trapq *tq, double print_time);
|
void trapq_finalize_moves(struct trapq *tq, double print_time
|
||||||
|
, double clear_history_time);
|
||||||
void trapq_set_position(struct trapq *tq, double print_time
|
void trapq_set_position(struct trapq *tq, double print_time
|
||||||
, double pos_x, double pos_y, double pos_z);
|
, double pos_x, double pos_y, double pos_z);
|
||||||
int trapq_extract_old(struct trapq *tq, struct pull_move *p, int max
|
int trapq_extract_old(struct trapq *tq, struct pull_move *p, int max
|
||||||
|
|||||||
@@ -66,10 +66,8 @@ class ClockSync:
|
|||||||
self.queries_pending = 0
|
self.queries_pending = 0
|
||||||
# Extend clock to 64bit
|
# Extend clock to 64bit
|
||||||
last_clock = self.last_clock
|
last_clock = self.last_clock
|
||||||
clock = (last_clock & ~0xffffffff) | params['clock']
|
clock_delta = (params['clock'] - last_clock) & 0xffffffff
|
||||||
if clock < last_clock:
|
self.last_clock = clock = last_clock + clock_delta
|
||||||
clock += 0x100000000
|
|
||||||
self.last_clock = clock
|
|
||||||
# Check if this is the best round-trip-time seen so far
|
# Check if this is the best round-trip-time seen so far
|
||||||
sent_time = params['#sent_time']
|
sent_time = params['#sent_time']
|
||||||
if not sent_time:
|
if not sent_time:
|
||||||
@@ -138,10 +136,9 @@ class ClockSync:
|
|||||||
# misc commands
|
# misc commands
|
||||||
def clock32_to_clock64(self, clock32):
|
def clock32_to_clock64(self, clock32):
|
||||||
last_clock = self.last_clock
|
last_clock = self.last_clock
|
||||||
clock_diff = (last_clock - clock32) & 0xffffffff
|
clock_diff = (clock32 - last_clock) & 0xffffffff
|
||||||
if clock_diff & 0x80000000:
|
clock_diff -= (clock_diff & 0x80000000) << 1
|
||||||
return last_clock + 0x100000000 - clock_diff
|
return last_clock + clock_diff
|
||||||
return last_clock - clock_diff
|
|
||||||
def is_active(self):
|
def is_active(self):
|
||||||
return self.queries_pending <= 4
|
return self.queries_pending <= 4
|
||||||
def dump_debug(self):
|
def dump_debug(self):
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
# Code for reading and writing the Klipper config file
|
# Code for reading and writing the Klipper config file
|
||||||
#
|
#
|
||||||
# Copyright (C) 2016-2021 Kevin O'Connor <kevin@koconnor.net>
|
# Copyright (C) 2016-2024 Kevin O'Connor <kevin@koconnor.net>
|
||||||
#
|
#
|
||||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
import sys, os, glob, re, time, logging, configparser, io
|
import sys, os, glob, re, time, logging, configparser, io
|
||||||
|
|
||||||
error = configparser.Error
|
error = configparser.Error
|
||||||
|
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
# Config section parsing helper
|
||||||
|
######################################################################
|
||||||
|
|
||||||
class sentinel:
|
class sentinel:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -69,6 +74,8 @@ class ConfigWrapper:
|
|||||||
return self._get_wrapper(self.fileconfig.getboolean, option, default,
|
return self._get_wrapper(self.fileconfig.getboolean, option, default,
|
||||||
note_valid=note_valid)
|
note_valid=note_valid)
|
||||||
def getchoice(self, option, choices, default=sentinel, note_valid=True):
|
def getchoice(self, option, choices, default=sentinel, note_valid=True):
|
||||||
|
if type(choices) == type([]):
|
||||||
|
choices = {i: i for i in choices}
|
||||||
if choices and type(list(choices.keys())[0]) == int:
|
if choices and type(list(choices.keys())[0]) == int:
|
||||||
c = self.getint(option, default, note_valid=note_valid)
|
c = self.getint(option, default, note_valid=note_valid)
|
||||||
else:
|
else:
|
||||||
@@ -132,28 +139,13 @@ class ConfigWrapper:
|
|||||||
pconfig = self.printer.lookup_object("configfile")
|
pconfig = self.printer.lookup_object("configfile")
|
||||||
pconfig.deprecate(self.section, option, value, msg)
|
pconfig.deprecate(self.section, option, value, msg)
|
||||||
|
|
||||||
AUTOSAVE_HEADER = """
|
|
||||||
#*# <---------------------- SAVE_CONFIG ---------------------->
|
|
||||||
#*# DO NOT EDIT THIS BLOCK OR BELOW. The contents are auto-generated.
|
|
||||||
#*#
|
|
||||||
"""
|
|
||||||
|
|
||||||
class PrinterConfig:
|
######################################################################
|
||||||
def __init__(self, printer):
|
# Config file parsing (with include file support)
|
||||||
self.printer = printer
|
######################################################################
|
||||||
self.autosave = None
|
|
||||||
self.deprecated = {}
|
class ConfigFileReader:
|
||||||
self.status_raw_config = {}
|
def read_config_file(self, filename):
|
||||||
self.status_save_pending = {}
|
|
||||||
self.status_settings = {}
|
|
||||||
self.status_warnings = []
|
|
||||||
self.save_config_pending = False
|
|
||||||
gcode = self.printer.lookup_object('gcode')
|
|
||||||
gcode.register_command("SAVE_CONFIG", self.cmd_SAVE_CONFIG,
|
|
||||||
desc=self.cmd_SAVE_CONFIG_help)
|
|
||||||
def get_printer(self):
|
|
||||||
return self.printer
|
|
||||||
def _read_config_file(self, filename):
|
|
||||||
try:
|
try:
|
||||||
f = open(filename, 'r')
|
f = open(filename, 'r')
|
||||||
data = f.read()
|
data = f.read()
|
||||||
@@ -163,6 +155,102 @@ class PrinterConfig:
|
|||||||
logging.exception(msg)
|
logging.exception(msg)
|
||||||
raise error(msg)
|
raise error(msg)
|
||||||
return data.replace('\r\n', '\n')
|
return data.replace('\r\n', '\n')
|
||||||
|
def build_config_string(self, fileconfig):
|
||||||
|
sfile = io.StringIO()
|
||||||
|
fileconfig.write(sfile)
|
||||||
|
return sfile.getvalue().strip()
|
||||||
|
def append_fileconfig(self, fileconfig, data, filename):
|
||||||
|
if not data:
|
||||||
|
return
|
||||||
|
# Strip trailing comments
|
||||||
|
lines = data.split('\n')
|
||||||
|
for i, line in enumerate(lines):
|
||||||
|
pos = line.find('#')
|
||||||
|
if pos >= 0:
|
||||||
|
lines[i] = line[:pos]
|
||||||
|
sbuffer = io.StringIO('\n'.join(lines))
|
||||||
|
if sys.version_info.major >= 3:
|
||||||
|
fileconfig.read_file(sbuffer, filename)
|
||||||
|
else:
|
||||||
|
fileconfig.readfp(sbuffer, filename)
|
||||||
|
def _create_fileconfig(self):
|
||||||
|
if sys.version_info.major >= 3:
|
||||||
|
fileconfig = configparser.RawConfigParser(
|
||||||
|
strict=False, inline_comment_prefixes=(';', '#'))
|
||||||
|
else:
|
||||||
|
fileconfig = configparser.RawConfigParser()
|
||||||
|
return fileconfig
|
||||||
|
def build_fileconfig(self, data, filename):
|
||||||
|
fileconfig = self._create_fileconfig()
|
||||||
|
self.append_fileconfig(fileconfig, data, filename)
|
||||||
|
return fileconfig
|
||||||
|
def _resolve_include(self, source_filename, include_spec, fileconfig,
|
||||||
|
visited):
|
||||||
|
dirname = os.path.dirname(source_filename)
|
||||||
|
include_spec = include_spec.strip()
|
||||||
|
include_glob = os.path.join(dirname, include_spec)
|
||||||
|
include_filenames = glob.glob(include_glob)
|
||||||
|
if not include_filenames and not glob.has_magic(include_glob):
|
||||||
|
# Empty set is OK if wildcard but not for direct file reference
|
||||||
|
raise error("Include file '%s' does not exist" % (include_glob,))
|
||||||
|
include_filenames.sort()
|
||||||
|
for include_filename in include_filenames:
|
||||||
|
include_data = self.read_config_file(include_filename)
|
||||||
|
self._parse_config(include_data, include_filename, fileconfig,
|
||||||
|
visited)
|
||||||
|
return include_filenames
|
||||||
|
def _parse_config(self, data, filename, fileconfig, visited):
|
||||||
|
path = os.path.abspath(filename)
|
||||||
|
if path in visited:
|
||||||
|
raise error("Recursive include of config file '%s'" % (filename))
|
||||||
|
visited.add(path)
|
||||||
|
lines = data.split('\n')
|
||||||
|
# Buffer lines between includes and parse as a unit so that overrides
|
||||||
|
# in includes apply linearly as they do within a single file
|
||||||
|
buf = []
|
||||||
|
for line in lines:
|
||||||
|
# Strip trailing comment
|
||||||
|
pos = line.find('#')
|
||||||
|
if pos >= 0:
|
||||||
|
line = line[:pos]
|
||||||
|
# Process include or buffer line
|
||||||
|
mo = configparser.RawConfigParser.SECTCRE.match(line)
|
||||||
|
header = mo and mo.group('header')
|
||||||
|
if header and header.startswith('include '):
|
||||||
|
self.append_fileconfig(fileconfig, '\n'.join(buf), filename)
|
||||||
|
del buf[:]
|
||||||
|
include_spec = header[8:].strip()
|
||||||
|
self._resolve_include(filename, include_spec, fileconfig,
|
||||||
|
visited)
|
||||||
|
else:
|
||||||
|
buf.append(line)
|
||||||
|
self.append_fileconfig(fileconfig, '\n'.join(buf), filename)
|
||||||
|
visited.remove(path)
|
||||||
|
def build_fileconfig_with_includes(self, data, filename):
|
||||||
|
fileconfig = self._create_fileconfig()
|
||||||
|
self._parse_config(data, filename, fileconfig, set())
|
||||||
|
return fileconfig
|
||||||
|
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
# Config auto save helper
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
AUTOSAVE_HEADER = """
|
||||||
|
#*# <---------------------- SAVE_CONFIG ---------------------->
|
||||||
|
#*# DO NOT EDIT THIS BLOCK OR BELOW. The contents are auto-generated.
|
||||||
|
#*#
|
||||||
|
"""
|
||||||
|
|
||||||
|
class ConfigAutoSave:
|
||||||
|
def __init__(self, printer):
|
||||||
|
self.printer = printer
|
||||||
|
self.fileconfig = None
|
||||||
|
self.status_save_pending = {}
|
||||||
|
self.save_config_pending = False
|
||||||
|
gcode = self.printer.lookup_object('gcode')
|
||||||
|
gcode.register_command("SAVE_CONFIG", self.cmd_SAVE_CONFIG,
|
||||||
|
desc=self.cmd_SAVE_CONFIG_help)
|
||||||
def _find_autosave_data(self, data):
|
def _find_autosave_data(self, data):
|
||||||
regular_data = data
|
regular_data = data
|
||||||
autosave_data = ""
|
autosave_data = ""
|
||||||
@@ -171,8 +259,8 @@ class PrinterConfig:
|
|||||||
regular_data = data[:pos]
|
regular_data = data[:pos]
|
||||||
autosave_data = data[pos + len(AUTOSAVE_HEADER):].strip()
|
autosave_data = data[pos + len(AUTOSAVE_HEADER):].strip()
|
||||||
# Check for errors and strip line prefixes
|
# Check for errors and strip line prefixes
|
||||||
if "\n#*# " in regular_data:
|
if "\n#*# " in regular_data or autosave_data.find(AUTOSAVE_HEADER) >= 0:
|
||||||
logging.warn("Can't read autosave from config file"
|
logging.warning("Can't read autosave from config file"
|
||||||
" - autosave state corrupted")
|
" - autosave state corrupted")
|
||||||
return data, ""
|
return data, ""
|
||||||
out = [""]
|
out = [""]
|
||||||
@@ -180,7 +268,7 @@ class PrinterConfig:
|
|||||||
if ((not line.startswith("#*#")
|
if ((not line.startswith("#*#")
|
||||||
or (len(line) >= 4 and not line.startswith("#*# ")))
|
or (len(line) >= 4 and not line.startswith("#*# ")))
|
||||||
and autosave_data):
|
and autosave_data):
|
||||||
logging.warn("Can't read autosave from config file"
|
logging.warning("Can't read autosave from config file"
|
||||||
" - modifications after header")
|
" - modifications after header")
|
||||||
return data, ""
|
return data, ""
|
||||||
out.append(line[4:])
|
out.append(line[4:])
|
||||||
@@ -188,8 +276,7 @@ class PrinterConfig:
|
|||||||
return regular_data, "\n".join(out)
|
return regular_data, "\n".join(out)
|
||||||
comment_r = re.compile('[#;].*$')
|
comment_r = re.compile('[#;].*$')
|
||||||
value_r = re.compile('[^A-Za-z0-9_].*$')
|
value_r = re.compile('[^A-Za-z0-9_].*$')
|
||||||
def _strip_duplicates(self, data, config):
|
def _strip_duplicates(self, data, fileconfig):
|
||||||
fileconfig = config.fileconfig
|
|
||||||
# Comment out fields in 'data' that are defined in 'config'
|
# Comment out fields in 'data' that are defined in 'config'
|
||||||
lines = data.split('\n')
|
lines = data.split('\n')
|
||||||
section = None
|
section = None
|
||||||
@@ -207,143 +294,31 @@ class PrinterConfig:
|
|||||||
section = pruned_line[1:-1].strip()
|
section = pruned_line[1:-1].strip()
|
||||||
continue
|
continue
|
||||||
field = self.value_r.sub('', pruned_line)
|
field = self.value_r.sub('', pruned_line)
|
||||||
if config.fileconfig.has_option(section, field):
|
if fileconfig.has_option(section, field):
|
||||||
is_dup_field = True
|
is_dup_field = True
|
||||||
lines[lineno] = '#' + lines[lineno]
|
lines[lineno] = '#' + lines[lineno]
|
||||||
return "\n".join(lines)
|
return "\n".join(lines)
|
||||||
def _parse_config_buffer(self, buffer, filename, fileconfig):
|
def load_main_config(self):
|
||||||
if not buffer:
|
|
||||||
return
|
|
||||||
data = '\n'.join(buffer)
|
|
||||||
del buffer[:]
|
|
||||||
sbuffer = io.StringIO(data)
|
|
||||||
fileconfig.readfp(sbuffer, filename)
|
|
||||||
def _resolve_include(self, source_filename, include_spec, fileconfig,
|
|
||||||
visited):
|
|
||||||
dirname = os.path.dirname(source_filename)
|
|
||||||
include_spec = include_spec.strip()
|
|
||||||
include_glob = os.path.join(dirname, include_spec)
|
|
||||||
include_filenames = glob.glob(include_glob)
|
|
||||||
if not include_filenames and not glob.has_magic(include_glob):
|
|
||||||
# Empty set is OK if wildcard but not for direct file reference
|
|
||||||
raise error("Include file '%s' does not exist" % (include_glob,))
|
|
||||||
include_filenames.sort()
|
|
||||||
for include_filename in include_filenames:
|
|
||||||
include_data = self._read_config_file(include_filename)
|
|
||||||
self._parse_config(include_data, include_filename, fileconfig,
|
|
||||||
visited)
|
|
||||||
return include_filenames
|
|
||||||
def _parse_config(self, data, filename, fileconfig, visited):
|
|
||||||
path = os.path.abspath(filename)
|
|
||||||
if path in visited:
|
|
||||||
raise error("Recursive include of config file '%s'" % (filename))
|
|
||||||
visited.add(path)
|
|
||||||
lines = data.split('\n')
|
|
||||||
# Buffer lines between includes and parse as a unit so that overrides
|
|
||||||
# in includes apply linearly as they do within a single file
|
|
||||||
buffer = []
|
|
||||||
for line in lines:
|
|
||||||
# Strip trailing comment
|
|
||||||
pos = line.find('#')
|
|
||||||
if pos >= 0:
|
|
||||||
line = line[:pos]
|
|
||||||
# Process include or buffer line
|
|
||||||
mo = configparser.RawConfigParser.SECTCRE.match(line)
|
|
||||||
header = mo and mo.group('header')
|
|
||||||
if header and header.startswith('include '):
|
|
||||||
self._parse_config_buffer(buffer, filename, fileconfig)
|
|
||||||
include_spec = header[8:].strip()
|
|
||||||
self._resolve_include(filename, include_spec, fileconfig,
|
|
||||||
visited)
|
|
||||||
else:
|
|
||||||
buffer.append(line)
|
|
||||||
self._parse_config_buffer(buffer, filename, fileconfig)
|
|
||||||
visited.remove(path)
|
|
||||||
def _build_config_wrapper(self, data, filename):
|
|
||||||
if sys.version_info.major >= 3:
|
|
||||||
fileconfig = configparser.RawConfigParser(
|
|
||||||
strict=False, inline_comment_prefixes=(';', '#'))
|
|
||||||
else:
|
|
||||||
fileconfig = configparser.RawConfigParser()
|
|
||||||
self._parse_config(data, filename, fileconfig, set())
|
|
||||||
return ConfigWrapper(self.printer, fileconfig, {}, 'printer')
|
|
||||||
def _build_config_string(self, config):
|
|
||||||
sfile = io.StringIO()
|
|
||||||
config.fileconfig.write(sfile)
|
|
||||||
return sfile.getvalue().strip()
|
|
||||||
def read_config(self, filename):
|
|
||||||
return self._build_config_wrapper(self._read_config_file(filename),
|
|
||||||
filename)
|
|
||||||
def read_main_config(self):
|
|
||||||
filename = self.printer.get_start_args()['config_file']
|
filename = self.printer.get_start_args()['config_file']
|
||||||
data = self._read_config_file(filename)
|
cfgrdr = ConfigFileReader()
|
||||||
|
data = cfgrdr.read_config_file(filename)
|
||||||
regular_data, autosave_data = self._find_autosave_data(data)
|
regular_data, autosave_data = self._find_autosave_data(data)
|
||||||
regular_config = self._build_config_wrapper(regular_data, filename)
|
regular_fileconfig = cfgrdr.build_fileconfig_with_includes(
|
||||||
autosave_data = self._strip_duplicates(autosave_data, regular_config)
|
regular_data, filename)
|
||||||
self.autosave = self._build_config_wrapper(autosave_data, filename)
|
autosave_data = self._strip_duplicates(autosave_data,
|
||||||
cfg = self._build_config_wrapper(regular_data + autosave_data, filename)
|
regular_fileconfig)
|
||||||
return cfg
|
self.fileconfig = cfgrdr.build_fileconfig(autosave_data, filename)
|
||||||
def check_unused_options(self, config):
|
cfgrdr.append_fileconfig(regular_fileconfig,
|
||||||
fileconfig = config.fileconfig
|
autosave_data, '*AUTOSAVE*')
|
||||||
objects = dict(self.printer.lookup_objects())
|
return regular_fileconfig, self.fileconfig
|
||||||
# Determine all the fields that have been accessed
|
|
||||||
access_tracking = dict(config.access_tracking)
|
|
||||||
for section in self.autosave.fileconfig.sections():
|
|
||||||
for option in self.autosave.fileconfig.options(section):
|
|
||||||
access_tracking[(section.lower(), option.lower())] = 1
|
|
||||||
# Validate that there are no undefined parameters in the config file
|
|
||||||
valid_sections = { s: 1 for s, o in access_tracking }
|
|
||||||
for section_name in fileconfig.sections():
|
|
||||||
section = section_name.lower()
|
|
||||||
if section not in valid_sections and section not in objects:
|
|
||||||
raise error("Section '%s' is not a valid config section"
|
|
||||||
% (section,))
|
|
||||||
for option in fileconfig.options(section_name):
|
|
||||||
option = option.lower()
|
|
||||||
if (section, option) not in access_tracking:
|
|
||||||
raise error("Option '%s' is not valid in section '%s'"
|
|
||||||
% (option, section))
|
|
||||||
# Setup get_status()
|
|
||||||
self._build_status(config)
|
|
||||||
def log_config(self, config):
|
|
||||||
lines = ["===== Config file =====",
|
|
||||||
self._build_config_string(config),
|
|
||||||
"======================="]
|
|
||||||
self.printer.set_rollover_info("config", "\n".join(lines))
|
|
||||||
# Status reporting
|
|
||||||
def deprecate(self, section, option, value=None, msg=None):
|
|
||||||
self.deprecated[(section, option, value)] = msg
|
|
||||||
def _build_status(self, config):
|
|
||||||
self.status_raw_config.clear()
|
|
||||||
for section in config.get_prefix_sections(''):
|
|
||||||
self.status_raw_config[section.get_name()] = section_status = {}
|
|
||||||
for option in section.get_prefix_options(''):
|
|
||||||
section_status[option] = section.get(option, note_valid=False)
|
|
||||||
self.status_settings = {}
|
|
||||||
for (section, option), value in config.access_tracking.items():
|
|
||||||
self.status_settings.setdefault(section, {})[option] = value
|
|
||||||
self.status_warnings = []
|
|
||||||
for (section, option, value), msg in self.deprecated.items():
|
|
||||||
if value is None:
|
|
||||||
res = {'type': 'deprecated_option'}
|
|
||||||
else:
|
|
||||||
res = {'type': 'deprecated_value', 'value': value}
|
|
||||||
res['message'] = msg
|
|
||||||
res['section'] = section
|
|
||||||
res['option'] = option
|
|
||||||
self.status_warnings.append(res)
|
|
||||||
def get_status(self, eventtime):
|
def get_status(self, eventtime):
|
||||||
return {'config': self.status_raw_config,
|
return {'save_config_pending': self.save_config_pending,
|
||||||
'settings': self.status_settings,
|
|
||||||
'warnings': self.status_warnings,
|
|
||||||
'save_config_pending': self.save_config_pending,
|
|
||||||
'save_config_pending_items': self.status_save_pending}
|
'save_config_pending_items': self.status_save_pending}
|
||||||
# Autosave functions
|
|
||||||
def set(self, section, option, value):
|
def set(self, section, option, value):
|
||||||
if not self.autosave.fileconfig.has_section(section):
|
if not self.fileconfig.has_section(section):
|
||||||
self.autosave.fileconfig.add_section(section)
|
self.fileconfig.add_section(section)
|
||||||
svalue = str(value)
|
svalue = str(value)
|
||||||
self.autosave.fileconfig.set(section, option, svalue)
|
self.fileconfig.set(section, option, svalue)
|
||||||
pending = dict(self.status_save_pending)
|
pending = dict(self.status_save_pending)
|
||||||
if not section in pending or pending[section] is None:
|
if not section in pending or pending[section] is None:
|
||||||
pending[section] = {}
|
pending[section] = {}
|
||||||
@@ -354,8 +329,8 @@ class PrinterConfig:
|
|||||||
self.save_config_pending = True
|
self.save_config_pending = True
|
||||||
logging.info("save_config: set [%s] %s = %s", section, option, svalue)
|
logging.info("save_config: set [%s] %s = %s", section, option, svalue)
|
||||||
def remove_section(self, section):
|
def remove_section(self, section):
|
||||||
if self.autosave.fileconfig.has_section(section):
|
if self.fileconfig.has_section(section):
|
||||||
self.autosave.fileconfig.remove_section(section)
|
self.fileconfig.remove_section(section)
|
||||||
pending = dict(self.status_save_pending)
|
pending = dict(self.status_save_pending)
|
||||||
pending[section] = None
|
pending[section] = None
|
||||||
self.status_save_pending = pending
|
self.status_save_pending = pending
|
||||||
@@ -366,21 +341,20 @@ class PrinterConfig:
|
|||||||
del pending[section]
|
del pending[section]
|
||||||
self.status_save_pending = pending
|
self.status_save_pending = pending
|
||||||
self.save_config_pending = True
|
self.save_config_pending = True
|
||||||
def _disallow_include_conflicts(self, regular_data, cfgname, gcode):
|
def _disallow_include_conflicts(self, regular_fileconfig):
|
||||||
config = self._build_config_wrapper(regular_data, cfgname)
|
for section in self.fileconfig.sections():
|
||||||
for section in self.autosave.fileconfig.sections():
|
for option in self.fileconfig.options(section):
|
||||||
for option in self.autosave.fileconfig.options(section):
|
if regular_fileconfig.has_option(section, option):
|
||||||
if config.fileconfig.has_option(section, option):
|
|
||||||
msg = ("SAVE_CONFIG section '%s' option '%s' conflicts "
|
msg = ("SAVE_CONFIG section '%s' option '%s' conflicts "
|
||||||
"with included value" % (section, option))
|
"with included value" % (section, option))
|
||||||
raise gcode.error(msg)
|
raise self.printer.command_error(msg)
|
||||||
cmd_SAVE_CONFIG_help = "Overwrite config file and restart"
|
cmd_SAVE_CONFIG_help = "Overwrite config file and restart"
|
||||||
def cmd_SAVE_CONFIG(self, gcmd):
|
def cmd_SAVE_CONFIG(self, gcmd):
|
||||||
if not self.autosave.fileconfig.sections():
|
if not self.fileconfig.sections():
|
||||||
return
|
return
|
||||||
gcode = self.printer.lookup_object('gcode')
|
|
||||||
# Create string containing autosave data
|
# Create string containing autosave data
|
||||||
autosave_data = self._build_config_string(self.autosave)
|
cfgrdr = ConfigFileReader()
|
||||||
|
autosave_data = cfgrdr.build_config_string(self.fileconfig)
|
||||||
lines = [('#*# ' + l).strip()
|
lines = [('#*# ' + l).strip()
|
||||||
for l in autosave_data.split('\n')]
|
for l in autosave_data.split('\n')]
|
||||||
lines.insert(0, "\n" + AUTOSAVE_HEADER.rstrip())
|
lines.insert(0, "\n" + AUTOSAVE_HEADER.rstrip())
|
||||||
@@ -389,16 +363,27 @@ class PrinterConfig:
|
|||||||
# Read in and validate current config file
|
# Read in and validate current config file
|
||||||
cfgname = self.printer.get_start_args()['config_file']
|
cfgname = self.printer.get_start_args()['config_file']
|
||||||
try:
|
try:
|
||||||
data = self._read_config_file(cfgname)
|
data = cfgrdr.read_config_file(cfgname)
|
||||||
|
except error as e:
|
||||||
|
msg = "Unable to read existing config on SAVE_CONFIG"
|
||||||
|
logging.exception(msg)
|
||||||
|
raise gcmd.error(msg)
|
||||||
regular_data, old_autosave_data = self._find_autosave_data(data)
|
regular_data, old_autosave_data = self._find_autosave_data(data)
|
||||||
config = self._build_config_wrapper(regular_data, cfgname)
|
regular_data = self._strip_duplicates(regular_data, self.fileconfig)
|
||||||
|
data = regular_data.rstrip() + autosave_data
|
||||||
|
new_regular_data, new_autosave_data = self._find_autosave_data(data)
|
||||||
|
if not new_autosave_data:
|
||||||
|
raise gcmd.error(
|
||||||
|
"Existing config autosave is corrupted."
|
||||||
|
" Can't complete SAVE_CONFIG")
|
||||||
|
try:
|
||||||
|
regular_fileconfig = cfgrdr.build_fileconfig_with_includes(
|
||||||
|
new_regular_data, cfgname)
|
||||||
except error as e:
|
except error as e:
|
||||||
msg = "Unable to parse existing config on SAVE_CONFIG"
|
msg = "Unable to parse existing config on SAVE_CONFIG"
|
||||||
logging.exception(msg)
|
logging.exception(msg)
|
||||||
raise gcode.error(msg)
|
raise gcmd.error(msg)
|
||||||
regular_data = self._strip_duplicates(regular_data, self.autosave)
|
self._disallow_include_conflicts(regular_fileconfig)
|
||||||
self._disallow_include_conflicts(regular_data, cfgname, gcode)
|
|
||||||
data = regular_data.rstrip() + autosave_data
|
|
||||||
# Determine filenames
|
# Determine filenames
|
||||||
datestr = time.strftime("-%Y%m%d_%H%M%S")
|
datestr = time.strftime("-%Y%m%d_%H%M%S")
|
||||||
backup_name = cfgname + datestr
|
backup_name = cfgname + datestr
|
||||||
@@ -418,6 +403,135 @@ class PrinterConfig:
|
|||||||
except:
|
except:
|
||||||
msg = "Unable to write config file during SAVE_CONFIG"
|
msg = "Unable to write config file during SAVE_CONFIG"
|
||||||
logging.exception(msg)
|
logging.exception(msg)
|
||||||
raise gcode.error(msg)
|
raise gcmd.error(msg)
|
||||||
# Request a restart
|
# Request a restart
|
||||||
|
gcode = self.printer.lookup_object('gcode')
|
||||||
gcode.request_restart('restart')
|
gcode.request_restart('restart')
|
||||||
|
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
# Config validation (check for undefined options)
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
class ConfigValidate:
|
||||||
|
def __init__(self, printer):
|
||||||
|
self.printer = printer
|
||||||
|
self.status_settings = {}
|
||||||
|
self.access_tracking = {}
|
||||||
|
self.autosave_options = {}
|
||||||
|
def start_access_tracking(self, autosave_fileconfig):
|
||||||
|
# Note autosave options for use during undefined options check
|
||||||
|
self.autosave_options = {}
|
||||||
|
for section in autosave_fileconfig.sections():
|
||||||
|
for option in autosave_fileconfig.options(section):
|
||||||
|
self.autosave_options[(section.lower(), option.lower())] = 1
|
||||||
|
self.access_tracking = {}
|
||||||
|
return self.access_tracking
|
||||||
|
def check_unused(self, fileconfig):
|
||||||
|
# Don't warn on fields set in autosave segment
|
||||||
|
access_tracking = dict(self.access_tracking)
|
||||||
|
access_tracking.update(self.autosave_options)
|
||||||
|
# Note locally used sections
|
||||||
|
valid_sections = { s: 1 for s, o in self.printer.lookup_objects() }
|
||||||
|
valid_sections.update({ s: 1 for s, o in access_tracking })
|
||||||
|
# Validate that there are no undefined parameters in the config file
|
||||||
|
for section_name in fileconfig.sections():
|
||||||
|
section = section_name.lower()
|
||||||
|
if section not in valid_sections:
|
||||||
|
raise error("Section '%s' is not a valid config section"
|
||||||
|
% (section,))
|
||||||
|
for option in fileconfig.options(section_name):
|
||||||
|
option = option.lower()
|
||||||
|
if (section, option) not in access_tracking:
|
||||||
|
raise error("Option '%s' is not valid in section '%s'"
|
||||||
|
% (option, section))
|
||||||
|
# Setup get_status()
|
||||||
|
self._build_status_settings()
|
||||||
|
# Clear tracking state
|
||||||
|
self.access_tracking.clear()
|
||||||
|
self.autosave_options.clear()
|
||||||
|
def _build_status_settings(self):
|
||||||
|
self.status_settings = {}
|
||||||
|
for (section, option), value in self.access_tracking.items():
|
||||||
|
self.status_settings.setdefault(section, {})[option] = value
|
||||||
|
def get_status(self, eventtime):
|
||||||
|
return {'settings': self.status_settings}
|
||||||
|
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
# Main printer config tracking
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
class PrinterConfig:
|
||||||
|
def __init__(self, printer):
|
||||||
|
self.printer = printer
|
||||||
|
self.autosave = ConfigAutoSave(printer)
|
||||||
|
self.validate = ConfigValidate(printer)
|
||||||
|
self.deprecated = {}
|
||||||
|
self.runtime_warnings = []
|
||||||
|
self.deprecate_warnings = []
|
||||||
|
self.status_raw_config = {}
|
||||||
|
self.status_warnings = []
|
||||||
|
def get_printer(self):
|
||||||
|
return self.printer
|
||||||
|
def read_config(self, filename):
|
||||||
|
cfgrdr = ConfigFileReader()
|
||||||
|
data = cfgrdr.read_config_file(filename)
|
||||||
|
fileconfig = cfgrdr.build_fileconfig(data, filename)
|
||||||
|
return ConfigWrapper(self.printer, fileconfig, {}, 'printer')
|
||||||
|
def read_main_config(self):
|
||||||
|
fileconfig, autosave_fileconfig = self.autosave.load_main_config()
|
||||||
|
access_tracking = self.validate.start_access_tracking(
|
||||||
|
autosave_fileconfig)
|
||||||
|
config = ConfigWrapper(self.printer, fileconfig,
|
||||||
|
access_tracking, 'printer')
|
||||||
|
self._build_status_config(config)
|
||||||
|
return config
|
||||||
|
def log_config(self, config):
|
||||||
|
cfgrdr = ConfigFileReader()
|
||||||
|
lines = ["===== Config file =====",
|
||||||
|
cfgrdr.build_config_string(config.fileconfig),
|
||||||
|
"======================="]
|
||||||
|
self.printer.set_rollover_info("config", "\n".join(lines))
|
||||||
|
def check_unused_options(self, config):
|
||||||
|
self.validate.check_unused(config.fileconfig)
|
||||||
|
# Deprecation warnings
|
||||||
|
def runtime_warning(self, msg):
|
||||||
|
logging.warning(msg)
|
||||||
|
res = {'type': 'runtime_warning', 'message': msg}
|
||||||
|
self.runtime_warnings.append(res)
|
||||||
|
self.status_warnings = self.runtime_warnings + self.deprecate_warnings
|
||||||
|
def deprecate(self, section, option, value=None, msg=None):
|
||||||
|
key = (section, option, value)
|
||||||
|
if key in self.deprecated and self.deprecated[key] == msg:
|
||||||
|
return
|
||||||
|
self.deprecated[key] = msg
|
||||||
|
self.deprecate_warnings = []
|
||||||
|
for (section, option, value), msg in self.deprecated.items():
|
||||||
|
if value is None:
|
||||||
|
res = {'type': 'deprecated_option'}
|
||||||
|
else:
|
||||||
|
res = {'type': 'deprecated_value', 'value': value}
|
||||||
|
res['message'] = msg
|
||||||
|
res['section'] = section
|
||||||
|
res['option'] = option
|
||||||
|
self.deprecate_warnings.append(res)
|
||||||
|
self.status_warnings = self.runtime_warnings + self.deprecate_warnings
|
||||||
|
# Status reporting
|
||||||
|
def _build_status_config(self, config):
|
||||||
|
self.status_raw_config = {}
|
||||||
|
for section in config.get_prefix_sections(''):
|
||||||
|
self.status_raw_config[section.get_name()] = section_status = {}
|
||||||
|
for option in section.get_prefix_options(''):
|
||||||
|
section_status[option] = section.get(option, note_valid=False)
|
||||||
|
def get_status(self, eventtime):
|
||||||
|
status = {'config': self.status_raw_config,
|
||||||
|
'warnings': self.status_warnings}
|
||||||
|
status.update(self.autosave.get_status(eventtime))
|
||||||
|
status.update(self.validate.get_status(eventtime))
|
||||||
|
return status
|
||||||
|
# Autosave functions
|
||||||
|
def set(self, section, option, value):
|
||||||
|
self.autosave.set(section, option, value)
|
||||||
|
def remove_section(self, section):
|
||||||
|
self.autosave.remove_section(section)
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
SAMPLE_TIME = 0.001
|
SAMPLE_TIME = 0.001
|
||||||
SAMPLE_COUNT = 8
|
SAMPLE_COUNT = 8
|
||||||
REPORT_TIME = 0.300
|
REPORT_TIME = 0.300
|
||||||
RANGE_CHECK_COUNT = 4
|
|
||||||
|
|
||||||
class MCU_scaled_adc:
|
class MCU_scaled_adc:
|
||||||
def __init__(self, main, pin_params):
|
def __init__(self, main, pin_params):
|
||||||
@@ -18,7 +17,7 @@ class MCU_scaled_adc:
|
|||||||
qname = main.name + ":" + pin_params['pin']
|
qname = main.name + ":" + pin_params['pin']
|
||||||
query_adc.register_adc(qname, self._mcu_adc)
|
query_adc.register_adc(qname, self._mcu_adc)
|
||||||
self._callback = None
|
self._callback = None
|
||||||
self.setup_minmax = self._mcu_adc.setup_minmax
|
self.setup_adc_sample = self._mcu_adc.setup_adc_sample
|
||||||
self.get_mcu = self._mcu_adc.get_mcu
|
self.get_mcu = self._mcu_adc.get_mcu
|
||||||
def _handle_callback(self, read_time, read_value):
|
def _handle_callback(self, read_time, read_value):
|
||||||
max_adc = self._main.last_vref[1]
|
max_adc = self._main.last_vref[1]
|
||||||
@@ -54,8 +53,7 @@ class PrinterADCScaled:
|
|||||||
ppins = self.printer.lookup_object('pins')
|
ppins = self.printer.lookup_object('pins')
|
||||||
mcu_adc = ppins.setup_pin('adc', pin_name)
|
mcu_adc = ppins.setup_pin('adc', pin_name)
|
||||||
mcu_adc.setup_adc_callback(REPORT_TIME, callback)
|
mcu_adc.setup_adc_callback(REPORT_TIME, callback)
|
||||||
mcu_adc.setup_minmax(SAMPLE_TIME, SAMPLE_COUNT, minval=0., maxval=1.,
|
mcu_adc.setup_adc_sample(SAMPLE_TIME, SAMPLE_COUNT)
|
||||||
range_check_count=RANGE_CHECK_COUNT)
|
|
||||||
query_adc = config.get_printer().load_object(config, 'query_adc')
|
query_adc = config.get_printer().load_object(config, 'query_adc')
|
||||||
query_adc.register_adc(self.name + ":" + name, mcu_adc)
|
query_adc.register_adc(self.name + ":" + name, mcu_adc)
|
||||||
return mcu_adc
|
return mcu_adc
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Obtain temperature using linear interpolation of ADC values
|
# Obtain temperature using linear interpolation of ADC values
|
||||||
#
|
#
|
||||||
# Copyright (C) 2016-2018 Kevin O'Connor <kevin@koconnor.net>
|
# Copyright (C) 2016-2024 Kevin O'Connor <kevin@koconnor.net>
|
||||||
#
|
#
|
||||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
import logging, bisect
|
import logging, bisect
|
||||||
@@ -22,8 +22,8 @@ class PrinterADCtoTemperature:
|
|||||||
ppins = config.get_printer().lookup_object('pins')
|
ppins = config.get_printer().lookup_object('pins')
|
||||||
self.mcu_adc = ppins.setup_pin('adc', config.get('sensor_pin'))
|
self.mcu_adc = ppins.setup_pin('adc', config.get('sensor_pin'))
|
||||||
self.mcu_adc.setup_adc_callback(REPORT_TIME, self.adc_callback)
|
self.mcu_adc.setup_adc_callback(REPORT_TIME, self.adc_callback)
|
||||||
query_adc = config.get_printer().load_object(config, 'query_adc')
|
self.diag_helper = HelperTemperatureDiagnostics(
|
||||||
query_adc.register_adc(config.get_name(), self.mcu_adc)
|
config, self.mcu_adc, adc_convert.calc_temp)
|
||||||
def setup_callback(self, temperature_callback):
|
def setup_callback(self, temperature_callback):
|
||||||
self.temperature_callback = temperature_callback
|
self.temperature_callback = temperature_callback
|
||||||
def get_report_time_delta(self):
|
def get_report_time_delta(self):
|
||||||
@@ -32,10 +32,44 @@ class PrinterADCtoTemperature:
|
|||||||
temp = self.adc_convert.calc_temp(read_value)
|
temp = self.adc_convert.calc_temp(read_value)
|
||||||
self.temperature_callback(read_time + SAMPLE_COUNT * SAMPLE_TIME, temp)
|
self.temperature_callback(read_time + SAMPLE_COUNT * SAMPLE_TIME, temp)
|
||||||
def setup_minmax(self, min_temp, max_temp):
|
def setup_minmax(self, min_temp, max_temp):
|
||||||
adc_range = [self.adc_convert.calc_adc(t) for t in [min_temp, max_temp]]
|
arange = [self.adc_convert.calc_adc(t) for t in [min_temp, max_temp]]
|
||||||
self.mcu_adc.setup_minmax(SAMPLE_TIME, SAMPLE_COUNT,
|
min_adc, max_adc = sorted(arange)
|
||||||
minval=min(adc_range), maxval=max(adc_range),
|
self.mcu_adc.setup_adc_sample(SAMPLE_TIME, SAMPLE_COUNT,
|
||||||
|
minval=min_adc, maxval=max_adc,
|
||||||
range_check_count=RANGE_CHECK_COUNT)
|
range_check_count=RANGE_CHECK_COUNT)
|
||||||
|
self.diag_helper.setup_diag_minmax(min_temp, max_temp, min_adc, max_adc)
|
||||||
|
|
||||||
|
# Tool to register with query_adc and report extra info on ADC range errors
|
||||||
|
class HelperTemperatureDiagnostics:
|
||||||
|
def __init__(self, config, mcu_adc, calc_temp_cb):
|
||||||
|
self.printer = config.get_printer()
|
||||||
|
self.name = config.get_name()
|
||||||
|
self.mcu_adc = mcu_adc
|
||||||
|
self.calc_temp_cb = calc_temp_cb
|
||||||
|
self.min_temp = self.max_temp = self.min_adc = self.max_adc = None
|
||||||
|
query_adc = self.printer.load_object(config, 'query_adc')
|
||||||
|
query_adc.register_adc(self.name, self.mcu_adc)
|
||||||
|
error_mcu = self.printer.load_object(config, 'error_mcu')
|
||||||
|
error_mcu.add_clarify("ADC out of range", self._clarify_adc_range)
|
||||||
|
def setup_diag_minmax(self, min_temp, max_temp, min_adc, max_adc):
|
||||||
|
self.min_temp, self.max_temp = min_temp, max_temp
|
||||||
|
self.min_adc, self.max_adc = min_adc, max_adc
|
||||||
|
def _clarify_adc_range(self, msg, details):
|
||||||
|
if self.min_temp is None:
|
||||||
|
return None
|
||||||
|
last_value, last_read_time = self.mcu_adc.get_last_value()
|
||||||
|
if not last_read_time:
|
||||||
|
return None
|
||||||
|
if last_value >= self.min_adc and last_value <= self.max_adc:
|
||||||
|
return None
|
||||||
|
tempstr = "?"
|
||||||
|
try:
|
||||||
|
last_temp = self.calc_temp_cb(last_value)
|
||||||
|
tempstr = "%.3f" % (last_temp,)
|
||||||
|
except e:
|
||||||
|
logging.exception("Error in calc_temp callback")
|
||||||
|
return ("Sensor '%s' temperature %s not in range %.3f:%.3f"
|
||||||
|
% (self.name, tempstr, self.min_temp, self.max_temp))
|
||||||
|
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
@@ -95,7 +129,7 @@ class LinearVoltage:
|
|||||||
for temp, volt in params:
|
for temp, volt in params:
|
||||||
adc = (volt - voltage_offset) / adc_voltage
|
adc = (volt - voltage_offset) / adc_voltage
|
||||||
if adc < 0. or adc > 1.:
|
if adc < 0. or adc > 1.:
|
||||||
logging.warn("Ignoring adc sample %.3f/%.3f in heater %s",
|
logging.warning("Ignoring adc sample %.3f/%.3f in heater %s",
|
||||||
temp, volt, config.get_name())
|
temp, volt, config.get_name())
|
||||||
continue
|
continue
|
||||||
samples.append((adc, temp))
|
samples.append((adc, temp))
|
||||||
|
|||||||
216
klippy/extras/ads1220.py
Normal file
216
klippy/extras/ads1220.py
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
# ADS1220 Support
|
||||||
|
#
|
||||||
|
# Copyright (C) 2024 Gareth Farrington <gareth@waves.ky>
|
||||||
|
#
|
||||||
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
|
import logging
|
||||||
|
from . import bulk_sensor, bus
|
||||||
|
|
||||||
|
#
|
||||||
|
# Constants
|
||||||
|
#
|
||||||
|
BYTES_PER_SAMPLE = 4 # samples are 4 byte wide unsigned integers
|
||||||
|
MAX_SAMPLES_PER_MESSAGE = bulk_sensor.MAX_BULK_MSG_SIZE // BYTES_PER_SAMPLE
|
||||||
|
UPDATE_INTERVAL = 0.10
|
||||||
|
RESET_CMD = 0x06
|
||||||
|
START_SYNC_CMD = 0x08
|
||||||
|
RREG_CMD = 0x20
|
||||||
|
WREG_CMD = 0x40
|
||||||
|
NOOP_CMD = 0x0
|
||||||
|
RESET_STATE = bytearray([0x0, 0x0, 0x0, 0x0])
|
||||||
|
|
||||||
|
# turn bytearrays into pretty hex strings: [0xff, 0x1]
|
||||||
|
def hexify(byte_array):
|
||||||
|
return "[%s]" % (", ".join([hex(b) for b in byte_array]))
|
||||||
|
|
||||||
|
|
||||||
|
class ADS1220:
|
||||||
|
def __init__(self, config):
|
||||||
|
self.printer = printer = config.get_printer()
|
||||||
|
self.name = config.get_name().split()[-1]
|
||||||
|
self.last_error_count = 0
|
||||||
|
self.consecutive_fails = 0
|
||||||
|
# Chip options
|
||||||
|
# Gain
|
||||||
|
self.gain_options = {'1': 0x0, '2': 0x1, '4': 0x2, '8': 0x3, '16': 0x4,
|
||||||
|
'32': 0x5, '64': 0x6, '128': 0x7}
|
||||||
|
self.gain = config.getchoice('gain', self.gain_options, default='128')
|
||||||
|
# Sample rate
|
||||||
|
self.sps_normal = {'20': 20, '45': 45, '90': 90, '175': 175,
|
||||||
|
'330': 330, '600': 600, '1000': 1000}
|
||||||
|
self.sps_turbo = {'40': 40, '90': 90, '180': 180, '350': 350,
|
||||||
|
'660': 660, '1200': 1200, '2000': 2000}
|
||||||
|
self.sps_options = self.sps_normal.copy()
|
||||||
|
self.sps_options.update(self.sps_turbo)
|
||||||
|
self.sps = config.getchoice('sample_rate', self.sps_options,
|
||||||
|
default='660')
|
||||||
|
self.is_turbo = str(self.sps) in self.sps_turbo
|
||||||
|
# Input multiplexer: AINP and AINN
|
||||||
|
mux_options = {'AIN0_AIN1': 0b0000, 'AIN0_AIN2': 0b0001,
|
||||||
|
'AIN0_AIN3': 0b0010, 'AIN1_AIN2': 0b0011,
|
||||||
|
'AIN1_AIN3': 0b0100, 'AIN2_AIN3': 0b0101,
|
||||||
|
'AIN1_AIN0': 0b0110, 'AIN3_AIN2': 0b0111,
|
||||||
|
'AIN0_AVSS': 0b1000, 'AIN1_AVSS': 0b1001,
|
||||||
|
'AIN2_AVSS': 0b1010, 'AIN3_AVSS': 0b1011}
|
||||||
|
self.mux = config.getchoice('input_mux', mux_options,
|
||||||
|
default='AIN0_AIN1')
|
||||||
|
# PGA Bypass
|
||||||
|
self.pga_bypass = config.getboolean('pga_bypass', default=False)
|
||||||
|
# bypass PGA when AVSS is the negative input
|
||||||
|
force_pga_bypass = self.mux >= 0b1000
|
||||||
|
self.pga_bypass = force_pga_bypass or self.pga_bypass
|
||||||
|
# Voltage Reference
|
||||||
|
self.vref_options = {'internal': 0b0, 'REF0': 0b01, 'REF1': 0b10,
|
||||||
|
'analog_supply': 0b11}
|
||||||
|
self.vref = config.getchoice('vref', self.vref_options,
|
||||||
|
default='internal')
|
||||||
|
# check for conflict between REF1 and AIN0/AIN3
|
||||||
|
mux_conflict = [0b0000, 0b0001, 0b0010, 0b0100, 0b0101, 0b0110, 0b0111,
|
||||||
|
0b1000, 0b1011]
|
||||||
|
if self.vref == 0b10 and self.mux in mux_conflict:
|
||||||
|
raise config.error("ADS1220 config error: AIN0/REFP1 and AIN3/REFN1"
|
||||||
|
" cant be used as a voltage reference and"
|
||||||
|
" an input at the same time")
|
||||||
|
# SPI Setup
|
||||||
|
spi_speed = 512000 if self.is_turbo else 256000
|
||||||
|
self.spi = bus.MCU_SPI_from_config(config, 1, default_speed=spi_speed)
|
||||||
|
self.mcu = mcu = self.spi.get_mcu()
|
||||||
|
self.oid = mcu.create_oid()
|
||||||
|
# Data Ready (DRDY) Pin
|
||||||
|
drdy_pin = config.get('data_ready_pin')
|
||||||
|
ppins = printer.lookup_object('pins')
|
||||||
|
drdy_ppin = ppins.lookup_pin(drdy_pin)
|
||||||
|
self.data_ready_pin = drdy_ppin['pin']
|
||||||
|
drdy_pin_mcu = drdy_ppin['chip']
|
||||||
|
if drdy_pin_mcu != self.mcu:
|
||||||
|
raise config.error("ADS1220 config error: SPI communication and"
|
||||||
|
" data_ready_pin must be on the same MCU")
|
||||||
|
# Bulk Sensor Setup
|
||||||
|
self.bulk_queue = bulk_sensor.BulkDataQueue(self.mcu, oid=self.oid)
|
||||||
|
# Clock tracking
|
||||||
|
chip_smooth = self.sps * UPDATE_INTERVAL * 2
|
||||||
|
# Measurement conversion
|
||||||
|
self.ffreader = bulk_sensor.FixedFreqReader(mcu, chip_smooth, "<i")
|
||||||
|
# Process messages in batches
|
||||||
|
self.batch_bulk = bulk_sensor.BatchBulkHelper(
|
||||||
|
self.printer, self._process_batch, self._start_measurements,
|
||||||
|
self._finish_measurements, UPDATE_INTERVAL)
|
||||||
|
# publish raw samples to the socket
|
||||||
|
hdr = {'header': ('time', 'counts', 'value')}
|
||||||
|
self.batch_bulk.add_mux_endpoint("ads1220/dump_ads1220", "sensor",
|
||||||
|
self.name, hdr)
|
||||||
|
# Command Configuration
|
||||||
|
mcu.add_config_cmd(
|
||||||
|
"config_ads1220 oid=%d spi_oid=%d data_ready_pin=%s"
|
||||||
|
% (self.oid, self.spi.get_oid(), self.data_ready_pin))
|
||||||
|
mcu.add_config_cmd("query_ads1220 oid=%d rest_ticks=0"
|
||||||
|
% (self.oid,), on_restart=True)
|
||||||
|
mcu.register_config_callback(self._build_config)
|
||||||
|
self.query_ads1220_cmd = None
|
||||||
|
|
||||||
|
def _build_config(self):
|
||||||
|
cmdqueue = self.spi.get_command_queue()
|
||||||
|
self.query_ads1220_cmd = self.mcu.lookup_command(
|
||||||
|
"query_ads1220 oid=%c rest_ticks=%u", cq=cmdqueue)
|
||||||
|
self.ffreader.setup_query_command("query_ads1220_status oid=%c",
|
||||||
|
oid=self.oid, cq=cmdqueue)
|
||||||
|
|
||||||
|
def get_mcu(self):
|
||||||
|
return self.mcu
|
||||||
|
|
||||||
|
def get_samples_per_second(self):
|
||||||
|
return self.sps
|
||||||
|
|
||||||
|
# returns a tuple of the minimum and maximum value of the sensor, used to
|
||||||
|
# detect if a data value is saturated
|
||||||
|
def get_range(self):
|
||||||
|
return -0x800000, 0x7FFFFF
|
||||||
|
|
||||||
|
# add_client interface, direct pass through to bulk_sensor API
|
||||||
|
def add_client(self, callback):
|
||||||
|
self.batch_bulk.add_client(callback)
|
||||||
|
|
||||||
|
# Measurement decoding
|
||||||
|
def _convert_samples(self, samples):
|
||||||
|
adc_factor = 1. / (1 << 23)
|
||||||
|
count = 0
|
||||||
|
for ptime, val in samples:
|
||||||
|
samples[count] = (round(ptime, 6), val, round(val * adc_factor, 9))
|
||||||
|
count += 1
|
||||||
|
del samples[count:]
|
||||||
|
|
||||||
|
# Start, stop, and process message batches
|
||||||
|
def _start_measurements(self):
|
||||||
|
self.last_error_count = 0
|
||||||
|
self.consecutive_fails = 0
|
||||||
|
# Start bulk reading
|
||||||
|
self.reset_chip()
|
||||||
|
self.setup_chip()
|
||||||
|
rest_ticks = self.mcu.seconds_to_clock(1. / (10. * self.sps))
|
||||||
|
self.query_ads1220_cmd.send([self.oid, rest_ticks])
|
||||||
|
logging.info("ADS1220 starting '%s' measurements", self.name)
|
||||||
|
# Initialize clock tracking
|
||||||
|
self.ffreader.note_start()
|
||||||
|
|
||||||
|
def _finish_measurements(self):
|
||||||
|
# don't use serial connection after shutdown
|
||||||
|
if self.printer.is_shutdown():
|
||||||
|
return
|
||||||
|
# Halt bulk reading
|
||||||
|
self.query_ads1220_cmd.send_wait_ack([self.oid, 0])
|
||||||
|
self.ffreader.note_end()
|
||||||
|
logging.info("ADS1220 finished '%s' measurements", self.name)
|
||||||
|
|
||||||
|
def _process_batch(self, eventtime):
|
||||||
|
samples = self.ffreader.pull_samples()
|
||||||
|
self._convert_samples(samples)
|
||||||
|
return {'data': samples, 'errors': self.last_error_count,
|
||||||
|
'overflows': self.ffreader.get_last_overflows()}
|
||||||
|
|
||||||
|
def reset_chip(self):
|
||||||
|
# the reset command takes 50us to complete
|
||||||
|
self.send_command(RESET_CMD)
|
||||||
|
# read startup register state and validate
|
||||||
|
val = self.read_reg(0x0, 4)
|
||||||
|
if val != RESET_STATE:
|
||||||
|
raise self.printer.command_error(
|
||||||
|
"Invalid ads1220 reset state (got %s vs %s).\n"
|
||||||
|
"This is generally indicative of connection problems\n"
|
||||||
|
"(e.g. faulty wiring) or a faulty ADS1220 chip."
|
||||||
|
% (hexify(val), hexify(RESET_STATE)))
|
||||||
|
|
||||||
|
def setup_chip(self):
|
||||||
|
continuous = 0x1 # enable continuous conversions
|
||||||
|
mode = 0x2 if self.is_turbo else 0x0 # turbo mode
|
||||||
|
sps_list = self.sps_turbo if self.is_turbo else self.sps_normal
|
||||||
|
data_rate = list(sps_list.keys()).index(str(self.sps))
|
||||||
|
reg_values = [(self.mux << 4) | (self.gain << 1) | int(self.pga_bypass),
|
||||||
|
(data_rate << 5) | (mode << 3) | (continuous << 2),
|
||||||
|
(self.vref << 6),
|
||||||
|
0x0]
|
||||||
|
self.write_reg(0x0, reg_values)
|
||||||
|
# start measurements immediately
|
||||||
|
self.send_command(START_SYNC_CMD)
|
||||||
|
|
||||||
|
def read_reg(self, reg, byte_count):
|
||||||
|
read_command = [RREG_CMD | (reg << 2) | (byte_count - 1)]
|
||||||
|
read_command += [NOOP_CMD] * byte_count
|
||||||
|
params = self.spi.spi_transfer(read_command)
|
||||||
|
return bytearray(params['response'][1:])
|
||||||
|
|
||||||
|
def send_command(self, cmd):
|
||||||
|
self.spi.spi_send([cmd])
|
||||||
|
|
||||||
|
def write_reg(self, reg, register_bytes):
|
||||||
|
write_command = [WREG_CMD | (reg << 2) | (len(register_bytes) - 1)]
|
||||||
|
write_command.extend(register_bytes)
|
||||||
|
self.spi.spi_send(write_command)
|
||||||
|
stored_val = self.read_reg(reg, len(register_bytes))
|
||||||
|
if bytearray(register_bytes) != stored_val:
|
||||||
|
raise self.printer.command_error(
|
||||||
|
"Failed to set ADS1220 register [0x%x] to %s: got %s. "
|
||||||
|
"This may be a connection problem (e.g. faulty wiring)" % (
|
||||||
|
reg, hexify(register_bytes), hexify(stored_val)))
|
||||||
|
|
||||||
|
|
||||||
|
ADS1220_SENSOR_TYPE = {"ads1220": ADS1220}
|
||||||
393
klippy/extras/ads1x1x.py
Normal file
393
klippy/extras/ads1x1x.py
Normal file
@@ -0,0 +1,393 @@
|
|||||||
|
# Support for I2C based ADS1013, ADS1014, ADS1015, ADS1113, ADS1114 and ADS1115
|
||||||
|
#
|
||||||
|
# Copyright (C) 2024 Konstantin Koch <korsarnek@gmail.com>
|
||||||
|
#
|
||||||
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
|
import logging
|
||||||
|
import pins
|
||||||
|
from . import bus
|
||||||
|
|
||||||
|
# Supported chip types
|
||||||
|
ADS1X1X_CHIP_TYPE = {
|
||||||
|
'ADS1013': 3,
|
||||||
|
'ADS1014': 4,
|
||||||
|
'ADS1015': 5,
|
||||||
|
'ADS1113': 13,
|
||||||
|
'ADS1114': 14,
|
||||||
|
'ADS1115': 15
|
||||||
|
}
|
||||||
|
|
||||||
|
def isADS101X(chip):
|
||||||
|
return (chip == ADS1X1X_CHIP_TYPE['ADS1013'] \
|
||||||
|
or chip == ADS1X1X_CHIP_TYPE['ADS1014'] \
|
||||||
|
or chip == ADS1X1X_CHIP_TYPE['ADS1015'])
|
||||||
|
|
||||||
|
def isADS111X(chip):
|
||||||
|
return (chip == ADS1X1X_CHIP_TYPE['ADS1113'] \
|
||||||
|
or chip == ADS1X1X_CHIP_TYPE['ADS1114'] \
|
||||||
|
or chip == ADS1X1X_CHIP_TYPE['ADS1115'])
|
||||||
|
|
||||||
|
# Address is defined by how the address pin is wired
|
||||||
|
ADS1X1X_CHIP_ADDR = {
|
||||||
|
'GND': 0x48,
|
||||||
|
'VCC': 0x49,
|
||||||
|
'SDA': 0x4a,
|
||||||
|
'SCL': 0x4b
|
||||||
|
}
|
||||||
|
|
||||||
|
# Chip "pointer" registers
|
||||||
|
ADS1X1X_REG_POINTER_MASK = 0x03
|
||||||
|
ADS1X1X_REG_POINTER = {
|
||||||
|
'CONVERSION': 0x00,
|
||||||
|
'CONFIG': 0x01,
|
||||||
|
'LO_THRESH': 0x02,
|
||||||
|
'HI_THRESH': 0x03
|
||||||
|
}
|
||||||
|
|
||||||
|
# Config register masks
|
||||||
|
ADS1X1X_REG_CONFIG = {
|
||||||
|
'OS_MASK': 0x8000,
|
||||||
|
'MULTIPLEXER_MASK': 0x7000,
|
||||||
|
'PGA_MASK': 0x0E00,
|
||||||
|
'MODE_MASK': 0x0100,
|
||||||
|
'DATA_RATE_MASK': 0x00E0,
|
||||||
|
'COMPARATOR_MODE_MASK': 0x0010,
|
||||||
|
'COMPARATOR_POLARITY_MASK': 0x0008,
|
||||||
|
# Determines if ALERT/RDY pin latches once asserted
|
||||||
|
'COMPARATOR_LATCHING_MASK': 0x0004,
|
||||||
|
'COMPARATOR_QUEUE_MASK': 0x0003
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# The following enums are to be used with the configuration functions.
|
||||||
|
#
|
||||||
|
ADS1X1X_OS = {
|
||||||
|
'OS_IDLE': 0x8000, # Device is not performing a conversion
|
||||||
|
'OS_SINGLE': 0x8000 # Single-conversion
|
||||||
|
}
|
||||||
|
|
||||||
|
ADS1X1X_MUX = {
|
||||||
|
'DIFF01': 0x0000, # Differential P = AIN0, N = AIN1 0
|
||||||
|
'DIFF03': 0x1000, # Differential P = AIN0, N = AIN3 4096
|
||||||
|
'DIFF13': 0x2000, # Differential P = AIN1, N = AIN3 8192
|
||||||
|
'DIFF23': 0x3000, # Differential P = AIN2, N = AIN3 12288
|
||||||
|
'AIN0': 0x4000, # Single-ended (ADS1015: AIN0 16384)
|
||||||
|
'AIN1': 0x5000, # Single-ended (ADS1015: AIN1 20480)
|
||||||
|
'AIN2': 0x6000, # Single-ended (ADS1015: AIN2 24576)
|
||||||
|
'AIN3': 0x7000 # Single-ended (ADS1015: AIN3 28672)
|
||||||
|
}
|
||||||
|
|
||||||
|
ADS1X1X_PGA = {
|
||||||
|
'6.144V': 0x0000, # +/-6.144V range = Gain 2/3
|
||||||
|
'4.096V': 0x0200, # +/-4.096V range = Gain 1
|
||||||
|
'2.048V': 0x0400, # +/-2.048V range = Gain 2
|
||||||
|
'1.024V': 0x0600, # +/-1.024V range = Gain 4
|
||||||
|
'0.512V': 0x0800, # +/-0.512V range = Gain 8
|
||||||
|
'0.256V': 0x0A00 # +/-0.256V range = Gain 16
|
||||||
|
}
|
||||||
|
ADS1X1X_PGA_VALUE = {
|
||||||
|
0x0000: 6.144,
|
||||||
|
0x0200: 4.096,
|
||||||
|
0x0400: 2.048,
|
||||||
|
0x0600: 1.024,
|
||||||
|
0x0800: 0.512,
|
||||||
|
0x0A00: 0.256,
|
||||||
|
}
|
||||||
|
ADS111X_RESOLUTION = 32767.0
|
||||||
|
ADS111X_PGA_SCALAR = {
|
||||||
|
0x0000: 6.144 / ADS111X_RESOLUTION, # +/-6.144V range = Gain 2/3
|
||||||
|
0x0200: 4.096 / ADS111X_RESOLUTION, # +/-4.096V range = Gain 1
|
||||||
|
0x0400: 2.048 / ADS111X_RESOLUTION, # +/-2.048V range = Gain 2
|
||||||
|
0x0600: 1.024 / ADS111X_RESOLUTION, # +/-1.024V range = Gain 4
|
||||||
|
0x0800: 0.512 / ADS111X_RESOLUTION, # +/-0.512V range = Gain 8
|
||||||
|
0x0A00: 0.256 / ADS111X_RESOLUTION # +/-0.256V range = Gain 16
|
||||||
|
}
|
||||||
|
ADS101X_RESOLUTION = 2047.0
|
||||||
|
ADS101X_PGA_SCALAR = {
|
||||||
|
0x0000: 6.144 / ADS101X_RESOLUTION, # +/-6.144V range = Gain 2/3
|
||||||
|
0x0200: 4.096 / ADS101X_RESOLUTION, # +/-4.096V range = Gain 1
|
||||||
|
0x0400: 2.048 / ADS101X_RESOLUTION, # +/-2.048V range = Gain 2
|
||||||
|
0x0600: 1.024 / ADS101X_RESOLUTION, # +/-1.024V range = Gain 4
|
||||||
|
0x0800: 0.512 / ADS101X_RESOLUTION, # +/-0.512V range = Gain 8
|
||||||
|
0x0A00: 0.256 / ADS101X_RESOLUTION # +/-0.256V range = Gain 16
|
||||||
|
}
|
||||||
|
ADS1X1X_MODE = {
|
||||||
|
'continuous': 0x0000, # Continuous conversion mode
|
||||||
|
'single': 0x0100 # Power-down single-shot mode
|
||||||
|
}
|
||||||
|
|
||||||
|
# Lesser samples per second means it takes and averages more samples before
|
||||||
|
# returning a result.
|
||||||
|
ADS101X_SAMPLES_PER_SECOND = {
|
||||||
|
'128': 0x0000, # 128 samples per second
|
||||||
|
'250': 0x0020, # 250 samples per second
|
||||||
|
'490': 0x0040, # 490 samples per second
|
||||||
|
'920': 0x0060, # 920 samples per second
|
||||||
|
'1600': 0x0080, # 1600 samples per second
|
||||||
|
'2400': 0x00a0, # 2400 samples per second
|
||||||
|
'3300': 0x00c0, # 3300 samples per second
|
||||||
|
}
|
||||||
|
|
||||||
|
ADS111X_SAMPLES_PER_SECOND = {
|
||||||
|
'8': 0x0000, # 8 samples per second
|
||||||
|
'16': 0x0020, # 16 samples per second
|
||||||
|
'32': 0x0040, # 32 samples per second
|
||||||
|
'64': 0x0060, # 64 samples per second
|
||||||
|
'128': 0x0080, # 128 samples per second
|
||||||
|
'250': 0x00a0, # 250 samples per second
|
||||||
|
'475': 0x00c0, # 475 samples per second
|
||||||
|
'860': 0x00e0 # 860 samples per second
|
||||||
|
}
|
||||||
|
|
||||||
|
ADS1X1X_COMPARATOR_MODE = {
|
||||||
|
'TRADITIONAL': 0x0000, # Traditional comparator with hysteresis
|
||||||
|
'WINDOW': 0x0010 # Window comparator
|
||||||
|
}
|
||||||
|
|
||||||
|
ADS1X1X_COMPARATOR_POLARITY = {
|
||||||
|
'ACTIVE_LO': 0x0000, # ALERT/RDY pin is low when active
|
||||||
|
'ACTIVE_HI': 0x0008 # ALERT/RDY pin is high when active
|
||||||
|
}
|
||||||
|
|
||||||
|
ADS1X1X_COMPARATOR_LATCHING = {
|
||||||
|
'NON_LATCHING': 0x0000, # Non-latching comparator
|
||||||
|
'LATCHING': 0x0004 # Latching comparator
|
||||||
|
}
|
||||||
|
|
||||||
|
ADS1X1X_COMPARATOR_QUEUE = {
|
||||||
|
'QUEUE_1': 0x0000, # Assert ALERT/RDY after one conversions
|
||||||
|
'QUEUE_2': 0x0001, # Assert ALERT/RDY after two conversions
|
||||||
|
'QUEUE_4': 0x0002, # Assert ALERT/RDY after four conversions
|
||||||
|
'QUEUE_NONE': 0x0003 # Disable the comparator and put ALERT/RDY
|
||||||
|
# in high state
|
||||||
|
}
|
||||||
|
|
||||||
|
ADS1X1_OPERATIONS = {
|
||||||
|
'SET_MUX': 0,
|
||||||
|
'READ_CONVERSION': 1
|
||||||
|
}
|
||||||
|
|
||||||
|
class ADS1X1X_chip:
|
||||||
|
|
||||||
|
def __init__(self, config):
|
||||||
|
self._printer = config.get_printer()
|
||||||
|
self._reactor = self._printer.get_reactor()
|
||||||
|
|
||||||
|
self.name = config.get_name().split()[-1]
|
||||||
|
self.chip = config.getchoice('chip', ADS1X1X_CHIP_TYPE)
|
||||||
|
address = ADS1X1X_CHIP_ADDR['GND']
|
||||||
|
# If none is specified, i2c_address can be used for a specific address
|
||||||
|
if config.get('address_pin', None) is not None:
|
||||||
|
address = config.getchoice('address_pin', ADS1X1X_CHIP_ADDR)
|
||||||
|
|
||||||
|
self._ppins = self._printer.lookup_object("pins")
|
||||||
|
self._ppins.register_chip(self.name, self)
|
||||||
|
|
||||||
|
self.pga = config.getchoice('pga', ADS1X1X_PGA, '4.096V')
|
||||||
|
self.adc_voltage = config.getfloat('adc_voltage', above=0., default=3.3)
|
||||||
|
# Comparators are not implemented, they would only be useful if the
|
||||||
|
# alert pin is used, which we haven't made configurable.
|
||||||
|
# But that wouldn't be useful for a normal temperature sensor anyway.
|
||||||
|
self.comp_mode = ADS1X1X_COMPARATOR_MODE['TRADITIONAL']
|
||||||
|
self.comp_polarity = ADS1X1X_COMPARATOR_POLARITY['ACTIVE_LO']
|
||||||
|
self.comp_latching = ADS1X1X_COMPARATOR_LATCHING['NON_LATCHING']
|
||||||
|
self.comp_queue = ADS1X1X_COMPARATOR_QUEUE['QUEUE_NONE']
|
||||||
|
self._i2c = bus.MCU_I2C_from_config(config, address)
|
||||||
|
|
||||||
|
self.mcu = self._i2c.get_mcu()
|
||||||
|
|
||||||
|
self._printer.add_object("ads1x1x " + self.name, self)
|
||||||
|
self._printer.register_event_handler("klippy:connect", \
|
||||||
|
self._handle_connect)
|
||||||
|
|
||||||
|
self._pins = {}
|
||||||
|
self._mutex = self._reactor.mutex()
|
||||||
|
|
||||||
|
def setup_pin(self, pin_type, pin_params):
|
||||||
|
pin = pin_params['pin']
|
||||||
|
if pin_type == 'adc':
|
||||||
|
if (pin not in ADS1X1X_MUX):
|
||||||
|
raise pins.error('ADS1x1x pin %s is not valid' % \
|
||||||
|
pin_params['pin'])
|
||||||
|
|
||||||
|
config = 0
|
||||||
|
config |= (ADS1X1X_OS['OS_SINGLE'] & \
|
||||||
|
ADS1X1X_REG_CONFIG['OS_MASK'])
|
||||||
|
config |= (ADS1X1X_MUX[pin_params['pin']] & \
|
||||||
|
ADS1X1X_REG_CONFIG['MULTIPLEXER_MASK'])
|
||||||
|
config |= (self.pga & ADS1X1X_REG_CONFIG['PGA_MASK'])
|
||||||
|
# Have to use single mode, because in continuous, it never reaches
|
||||||
|
# idle state, which we use to determine if the sampling is done.
|
||||||
|
config |= (ADS1X1X_MODE['single'] & \
|
||||||
|
ADS1X1X_REG_CONFIG['MODE_MASK'])
|
||||||
|
# lowest sample rate per default, until report time has been set in
|
||||||
|
# setup_adc_sample
|
||||||
|
config |= (self.comp_mode \
|
||||||
|
& ADS1X1X_REG_CONFIG['COMPARATOR_MODE_MASK'])
|
||||||
|
config |= (self.comp_polarity \
|
||||||
|
& ADS1X1X_REG_CONFIG['COMPARATOR_POLARITY_MASK'])
|
||||||
|
config |= (self.comp_latching \
|
||||||
|
& ADS1X1X_REG_CONFIG['COMPARATOR_LATCHING_MASK'])
|
||||||
|
config |= (self.comp_queue \
|
||||||
|
& ADS1X1X_REG_CONFIG['COMPARATOR_QUEUE_MASK'])
|
||||||
|
|
||||||
|
pin_obj = ADS1X1X_pin(self, config)
|
||||||
|
if pin in self._pins:
|
||||||
|
raise pins.error(
|
||||||
|
'pin %s for chip %s is used multiple times' \
|
||||||
|
% (pin, self.name))
|
||||||
|
self._pins[pin] = pin_obj
|
||||||
|
|
||||||
|
return pin_obj
|
||||||
|
raise pins.error('Wrong pin or incompatible type: %s with type %s! ' % (
|
||||||
|
pin, pin_type))
|
||||||
|
|
||||||
|
def _handle_connect(self):
|
||||||
|
try:
|
||||||
|
# Init all devices on bus for this kind of device
|
||||||
|
self._i2c.i2c_write([0x06, 0x00, 0x00])
|
||||||
|
except Exception:
|
||||||
|
logging.exception("ADS1X1X: error while resetting device")
|
||||||
|
|
||||||
|
def is_ready(self):
|
||||||
|
config = self._read_register(ADS1X1X_REG_POINTER['CONFIG'])
|
||||||
|
return bool((config & ADS1X1X_REG_CONFIG['OS_MASK']) == \
|
||||||
|
ADS1X1X_OS['OS_IDLE'])
|
||||||
|
|
||||||
|
def calculate_sample_rate(self):
|
||||||
|
pin_count = len(self._pins)
|
||||||
|
lowest_report_time = 1
|
||||||
|
for pin in self._pins.values():
|
||||||
|
lowest_report_time = min(lowest_report_time, pin.report_time)
|
||||||
|
|
||||||
|
sample_rate = 1 / lowest_report_time * pin_count
|
||||||
|
samples_per_second = ADS111X_SAMPLES_PER_SECOND
|
||||||
|
if isADS101X(self.chip):
|
||||||
|
samples_per_second = ADS101X_SAMPLES_PER_SECOND
|
||||||
|
|
||||||
|
# make sure the samples list is sorted correctly by number.
|
||||||
|
samples_per_second = sorted(samples_per_second.items(), \
|
||||||
|
key=lambda t: int(t[0]))
|
||||||
|
for rate, bits in samples_per_second:
|
||||||
|
rate_number = int(rate)
|
||||||
|
if sample_rate <= rate_number:
|
||||||
|
return (rate_number, bits)
|
||||||
|
logging.warning(
|
||||||
|
"ADS1X1X: requested sample rate %s is higher than supported by %s."\
|
||||||
|
% (sample_rate, self.name))
|
||||||
|
return (rate_number, bits)
|
||||||
|
|
||||||
|
def handle_report_time_update(self):
|
||||||
|
(sample_rate, sample_rate_bits) = self.calculate_sample_rate()
|
||||||
|
|
||||||
|
for pin in self._pins.values():
|
||||||
|
pin.config = (pin.config & ~ADS1X1X_REG_CONFIG['DATA_RATE_MASK']) \
|
||||||
|
| (sample_rate_bits & ADS1X1X_REG_CONFIG['DATA_RATE_MASK'])
|
||||||
|
|
||||||
|
self.delay = 1 / float(sample_rate)
|
||||||
|
|
||||||
|
def sample(self, pin):
|
||||||
|
with self._mutex:
|
||||||
|
try:
|
||||||
|
self._write_register(ADS1X1X_REG_POINTER['CONFIG'], pin.config)
|
||||||
|
self._reactor.pause(self._reactor.monotonic() + self.delay)
|
||||||
|
start_time = self._reactor.monotonic()
|
||||||
|
while not self.is_ready():
|
||||||
|
self._reactor.pause(self._reactor.monotonic() + 0.001)
|
||||||
|
# if we waited twice the expected time, mark this an error
|
||||||
|
if start_time + self.delay < self._reactor.monotonic():
|
||||||
|
logging.warning("ADS1X1X: timeout during sampling")
|
||||||
|
return None
|
||||||
|
return self._read_register(ADS1X1X_REG_POINTER['CONVERSION'])
|
||||||
|
except Exception as e:
|
||||||
|
logging.exception("ADS1X1X: error while sampling: %s" % str(e))
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _read_register(self, reg):
|
||||||
|
# read a single register
|
||||||
|
params = self._i2c.i2c_read([reg], 2)
|
||||||
|
buff = bytearray(params['response'])
|
||||||
|
return (buff[0]<<8 | buff[1])
|
||||||
|
|
||||||
|
def _write_register(self, reg, data):
|
||||||
|
data = [
|
||||||
|
(reg & 0xFF), # Control register
|
||||||
|
((data>>8) & 0xFF), # High byte
|
||||||
|
(data & 0xFF), # Lo byte
|
||||||
|
]
|
||||||
|
self._i2c.i2c_write(data)
|
||||||
|
|
||||||
|
class ADS1X1X_pin:
|
||||||
|
def __init__(self, chip, config):
|
||||||
|
self.mcu = chip.mcu
|
||||||
|
self.chip = chip
|
||||||
|
self.config = config
|
||||||
|
|
||||||
|
self.invalid_count = 0
|
||||||
|
|
||||||
|
self.chip._printer.register_event_handler("klippy:connect", \
|
||||||
|
self._handle_connect)
|
||||||
|
|
||||||
|
def _handle_connect(self):
|
||||||
|
self._reactor = self.chip._printer.get_reactor()
|
||||||
|
self._sample_timer = \
|
||||||
|
self._reactor.register_timer(self._process_sample, \
|
||||||
|
self._reactor.NOW)
|
||||||
|
|
||||||
|
def _process_sample(self, eventtime):
|
||||||
|
sample = self.chip.sample(self)
|
||||||
|
if sample is not None:
|
||||||
|
# The sample is encoded in the top 12 or full 16 bits
|
||||||
|
# Value's meaning is defined by ADS1X1X_REG_CONFIG['PGA_MASK']
|
||||||
|
if isADS101X(self.chip.chip):
|
||||||
|
sample >>= 4
|
||||||
|
target_value = sample / ADS101X_RESOLUTION
|
||||||
|
else:
|
||||||
|
target_value = sample / ADS111X_RESOLUTION
|
||||||
|
|
||||||
|
# Thermistors expect a value between 0 and 1 to work. If we use a
|
||||||
|
# PGA with 4.096V but supply only 3.3V, the reference voltage for
|
||||||
|
# voltage divider is only 3.3V, not 4.096V. So we remap the range
|
||||||
|
# from what the PGA allows as range to end up between 0 and 1 for
|
||||||
|
# the thermistor logic to work as expected.
|
||||||
|
target_value = target_value * (ADS1X1X_PGA_VALUE[self.chip.pga] / \
|
||||||
|
self.chip.adc_voltage)
|
||||||
|
|
||||||
|
if target_value > self.maxval or target_value < self.minval:
|
||||||
|
self.invalid_count = self.invalid_count + 1
|
||||||
|
logging.warning("ADS1X1X: temperature outside range")
|
||||||
|
self.check_invalid()
|
||||||
|
else:
|
||||||
|
self.invalid_count = 0
|
||||||
|
|
||||||
|
# Publish result
|
||||||
|
measured_time = self._reactor.monotonic()
|
||||||
|
self.callback(self.chip.mcu.estimated_print_time(measured_time),
|
||||||
|
target_value)
|
||||||
|
else:
|
||||||
|
self.invalid_count = self.invalid_count + 1
|
||||||
|
self.check_invalid()
|
||||||
|
|
||||||
|
return eventtime + self.report_time
|
||||||
|
|
||||||
|
def check_invalid(self):
|
||||||
|
if self.invalid_count > self.range_check_count:
|
||||||
|
self.chip._printer.invoke_shutdown(
|
||||||
|
"ADS1X1X temperature check failed")
|
||||||
|
|
||||||
|
def get_mcu(self):
|
||||||
|
return self.mcu
|
||||||
|
|
||||||
|
def setup_adc_callback(self, report_time, callback):
|
||||||
|
self.report_time = report_time
|
||||||
|
self.callback = callback
|
||||||
|
self.chip.handle_report_time_update()
|
||||||
|
|
||||||
|
def setup_adc_sample(self, sample_time, sample_count,
|
||||||
|
minval=0., maxval=1., range_check_count=0):
|
||||||
|
self.minval = minval
|
||||||
|
self.maxval = maxval
|
||||||
|
self.range_check_count = range_check_count
|
||||||
|
|
||||||
|
def load_config_prefix(config):
|
||||||
|
return ADS1X1X_chip(config)
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
# Support for reading acceleration data from an adxl345 chip
|
# Support for reading acceleration data from an adxl345 chip
|
||||||
#
|
#
|
||||||
# Copyright (C) 2020-2021 Kevin O'Connor <kevin@koconnor.net>
|
# Copyright (C) 2020-2023 Kevin O'Connor <kevin@koconnor.net>
|
||||||
#
|
#
|
||||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
import logging, time, collections, threading, multiprocessing, os
|
import logging, time, collections, multiprocessing, os
|
||||||
from . import bus, motion_report
|
from . import bus, bulk_sensor
|
||||||
|
|
||||||
# ADXL345 registers
|
# ADXL345 registers
|
||||||
REG_DEVID = 0x00
|
REG_DEVID = 0x00
|
||||||
@@ -32,26 +32,29 @@ Accel_Measurement = collections.namedtuple(
|
|||||||
|
|
||||||
# Helper class to obtain measurements
|
# Helper class to obtain measurements
|
||||||
class AccelQueryHelper:
|
class AccelQueryHelper:
|
||||||
def __init__(self, printer, cconn):
|
def __init__(self, printer):
|
||||||
self.printer = printer
|
self.printer = printer
|
||||||
self.cconn = cconn
|
self.is_finished = False
|
||||||
print_time = printer.lookup_object('toolhead').get_last_move_time()
|
print_time = printer.lookup_object('toolhead').get_last_move_time()
|
||||||
self.request_start_time = self.request_end_time = print_time
|
self.request_start_time = self.request_end_time = print_time
|
||||||
self.samples = self.raw_samples = []
|
self.msgs = []
|
||||||
|
self.samples = []
|
||||||
def finish_measurements(self):
|
def finish_measurements(self):
|
||||||
toolhead = self.printer.lookup_object('toolhead')
|
toolhead = self.printer.lookup_object('toolhead')
|
||||||
self.request_end_time = toolhead.get_last_move_time()
|
self.request_end_time = toolhead.get_last_move_time()
|
||||||
toolhead.wait_moves()
|
toolhead.wait_moves()
|
||||||
self.cconn.finalize()
|
self.is_finished = True
|
||||||
def _get_raw_samples(self):
|
def handle_batch(self, msg):
|
||||||
raw_samples = self.cconn.get_messages()
|
if self.is_finished:
|
||||||
if raw_samples:
|
return False
|
||||||
self.raw_samples = raw_samples
|
if len(self.msgs) >= 10000:
|
||||||
return self.raw_samples
|
# Avoid filling up memory with too many samples
|
||||||
|
return False
|
||||||
|
self.msgs.append(msg)
|
||||||
|
return True
|
||||||
def has_valid_samples(self):
|
def has_valid_samples(self):
|
||||||
raw_samples = self._get_raw_samples()
|
for msg in self.msgs:
|
||||||
for msg in raw_samples:
|
data = msg['data']
|
||||||
data = msg['params']['data']
|
|
||||||
first_sample_time = data[0][0]
|
first_sample_time = data[0][0]
|
||||||
last_sample_time = data[-1][0]
|
last_sample_time = data[-1][0]
|
||||||
if (first_sample_time > self.request_end_time
|
if (first_sample_time > self.request_end_time
|
||||||
@@ -60,21 +63,20 @@ class AccelQueryHelper:
|
|||||||
# The time intervals [first_sample_time, last_sample_time]
|
# The time intervals [first_sample_time, last_sample_time]
|
||||||
# and [request_start_time, request_end_time] have non-zero
|
# and [request_start_time, request_end_time] have non-zero
|
||||||
# intersection. It is still theoretically possible that none
|
# intersection. It is still theoretically possible that none
|
||||||
# of the samples from raw_samples fall into the time interval
|
# of the samples from msgs fall into the time interval
|
||||||
# [request_start_time, request_end_time] if it is too narrow
|
# [request_start_time, request_end_time] if it is too narrow
|
||||||
# or on very heavy data losses. In practice, that interval
|
# or on very heavy data losses. In practice, that interval
|
||||||
# is at least 1 second, so this possibility is negligible.
|
# is at least 1 second, so this possibility is negligible.
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
def get_samples(self):
|
def get_samples(self):
|
||||||
raw_samples = self._get_raw_samples()
|
if not self.msgs:
|
||||||
if not raw_samples:
|
|
||||||
return self.samples
|
return self.samples
|
||||||
total = sum([len(m['params']['data']) for m in raw_samples])
|
total = sum([len(m['data']) for m in self.msgs])
|
||||||
count = 0
|
count = 0
|
||||||
self.samples = samples = [None] * total
|
self.samples = samples = [None] * total
|
||||||
for msg in raw_samples:
|
for msg in self.msgs:
|
||||||
for samp_time, x, y, z in msg['params']['data']:
|
for samp_time, x, y, z in msg['data']:
|
||||||
if samp_time < self.request_start_time:
|
if samp_time < self.request_start_time:
|
||||||
continue
|
continue
|
||||||
if samp_time > self.request_end_time:
|
if samp_time > self.request_end_time:
|
||||||
@@ -173,112 +175,54 @@ class AccelCommandHelper:
|
|||||||
val = gcmd.get("VAL", minval=0, maxval=255, parser=lambda x: int(x, 0))
|
val = gcmd.get("VAL", minval=0, maxval=255, parser=lambda x: int(x, 0))
|
||||||
self.chip.set_reg(reg, val)
|
self.chip.set_reg(reg, val)
|
||||||
|
|
||||||
# Helper class for chip clock synchronization via linear regression
|
# Helper to read the axes_map parameter from the config
|
||||||
class ClockSyncRegression:
|
def read_axes_map(config, scale_x, scale_y, scale_z):
|
||||||
def __init__(self, mcu, chip_clock_smooth, decay = 1. / 20.):
|
am = {'x': (0, scale_x), 'y': (1, scale_y), 'z': (2, scale_z),
|
||||||
self.mcu = mcu
|
'-x': (0, -scale_x), '-y': (1, -scale_y), '-z': (2, -scale_z)}
|
||||||
self.chip_clock_smooth = chip_clock_smooth
|
axes_map = config.getlist('axes_map', ('x','y','z'), count=3)
|
||||||
self.decay = decay
|
if any([a not in am for a in axes_map]):
|
||||||
self.last_chip_clock = self.last_exp_mcu_clock = 0.
|
raise config.error("Invalid axes_map parameter")
|
||||||
self.mcu_clock_avg = self.mcu_clock_variance = 0.
|
return [am[a.strip()] for a in axes_map]
|
||||||
self.chip_clock_avg = self.chip_clock_covariance = 0.
|
|
||||||
def reset(self, mcu_clock, chip_clock):
|
|
||||||
self.mcu_clock_avg = self.last_mcu_clock = mcu_clock
|
|
||||||
self.chip_clock_avg = chip_clock
|
|
||||||
self.mcu_clock_variance = self.chip_clock_covariance = 0.
|
|
||||||
self.last_chip_clock = self.last_exp_mcu_clock = 0.
|
|
||||||
def update(self, mcu_clock, chip_clock):
|
|
||||||
# Update linear regression
|
|
||||||
decay = self.decay
|
|
||||||
diff_mcu_clock = mcu_clock - self.mcu_clock_avg
|
|
||||||
self.mcu_clock_avg += decay * diff_mcu_clock
|
|
||||||
self.mcu_clock_variance = (1. - decay) * (
|
|
||||||
self.mcu_clock_variance + diff_mcu_clock**2 * decay)
|
|
||||||
diff_chip_clock = chip_clock - self.chip_clock_avg
|
|
||||||
self.chip_clock_avg += decay * diff_chip_clock
|
|
||||||
self.chip_clock_covariance = (1. - decay) * (
|
|
||||||
self.chip_clock_covariance + diff_mcu_clock*diff_chip_clock*decay)
|
|
||||||
def set_last_chip_clock(self, chip_clock):
|
|
||||||
base_mcu, base_chip, inv_cfreq = self.get_clock_translation()
|
|
||||||
self.last_chip_clock = chip_clock
|
|
||||||
self.last_exp_mcu_clock = base_mcu + (chip_clock-base_chip) * inv_cfreq
|
|
||||||
def get_clock_translation(self):
|
|
||||||
inv_chip_freq = self.mcu_clock_variance / self.chip_clock_covariance
|
|
||||||
if not self.last_chip_clock:
|
|
||||||
return self.mcu_clock_avg, self.chip_clock_avg, inv_chip_freq
|
|
||||||
# Find mcu clock associated with future chip_clock
|
|
||||||
s_chip_clock = self.last_chip_clock + self.chip_clock_smooth
|
|
||||||
scdiff = s_chip_clock - self.chip_clock_avg
|
|
||||||
s_mcu_clock = self.mcu_clock_avg + scdiff * inv_chip_freq
|
|
||||||
# Calculate frequency to converge at future point
|
|
||||||
mdiff = s_mcu_clock - self.last_exp_mcu_clock
|
|
||||||
s_inv_chip_freq = mdiff / self.chip_clock_smooth
|
|
||||||
return self.last_exp_mcu_clock, self.last_chip_clock, s_inv_chip_freq
|
|
||||||
def get_time_translation(self):
|
|
||||||
base_mcu, base_chip, inv_cfreq = self.get_clock_translation()
|
|
||||||
clock_to_print_time = self.mcu.clock_to_print_time
|
|
||||||
base_time = clock_to_print_time(base_mcu)
|
|
||||||
inv_freq = clock_to_print_time(base_mcu + inv_cfreq) - base_time
|
|
||||||
return base_time, base_chip, inv_freq
|
|
||||||
|
|
||||||
MIN_MSG_TIME = 0.100
|
BATCH_UPDATES = 0.100
|
||||||
|
|
||||||
BYTES_PER_SAMPLE = 5
|
|
||||||
SAMPLES_PER_BLOCK = 10
|
|
||||||
|
|
||||||
# Printer class that controls ADXL345 chip
|
# Printer class that controls ADXL345 chip
|
||||||
class ADXL345:
|
class ADXL345:
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
self.printer = config.get_printer()
|
self.printer = config.get_printer()
|
||||||
AccelCommandHelper(config, self)
|
AccelCommandHelper(config, self)
|
||||||
self.query_rate = 0
|
self.axes_map = read_axes_map(config, SCALE_XY, SCALE_XY, SCALE_Z)
|
||||||
am = {'x': (0, SCALE_XY), 'y': (1, SCALE_XY), 'z': (2, SCALE_Z),
|
|
||||||
'-x': (0, -SCALE_XY), '-y': (1, -SCALE_XY), '-z': (2, -SCALE_Z)}
|
|
||||||
axes_map = config.getlist('axes_map', ('x','y','z'), count=3)
|
|
||||||
if any([a not in am for a in axes_map]):
|
|
||||||
raise config.error("Invalid adxl345 axes_map parameter")
|
|
||||||
self.axes_map = [am[a.strip()] for a in axes_map]
|
|
||||||
self.data_rate = config.getint('rate', 3200)
|
self.data_rate = config.getint('rate', 3200)
|
||||||
if self.data_rate not in QUERY_RATES:
|
if self.data_rate not in QUERY_RATES:
|
||||||
raise config.error("Invalid rate parameter: %d" % (self.data_rate,))
|
raise config.error("Invalid rate parameter: %d" % (self.data_rate,))
|
||||||
# Measurement storage (accessed from background thread)
|
|
||||||
self.lock = threading.Lock()
|
|
||||||
self.raw_samples = []
|
|
||||||
# Setup mcu sensor_adxl345 bulk query code
|
# Setup mcu sensor_adxl345 bulk query code
|
||||||
self.spi = bus.MCU_SPI_from_config(config, 3, default_speed=5000000)
|
self.spi = bus.MCU_SPI_from_config(config, 3, default_speed=5000000)
|
||||||
self.mcu = mcu = self.spi.get_mcu()
|
self.mcu = mcu = self.spi.get_mcu()
|
||||||
self.oid = oid = mcu.create_oid()
|
self.oid = oid = mcu.create_oid()
|
||||||
self.query_adxl345_cmd = self.query_adxl345_end_cmd = None
|
self.query_adxl345_cmd = None
|
||||||
self.query_adxl345_status_cmd = None
|
|
||||||
mcu.add_config_cmd("config_adxl345 oid=%d spi_oid=%d"
|
mcu.add_config_cmd("config_adxl345 oid=%d spi_oid=%d"
|
||||||
% (oid, self.spi.get_oid()))
|
% (oid, self.spi.get_oid()))
|
||||||
mcu.add_config_cmd("query_adxl345 oid=%d clock=0 rest_ticks=0"
|
mcu.add_config_cmd("query_adxl345 oid=%d rest_ticks=0"
|
||||||
% (oid,), on_restart=True)
|
% (oid,), on_restart=True)
|
||||||
mcu.register_config_callback(self._build_config)
|
mcu.register_config_callback(self._build_config)
|
||||||
mcu.register_response(self._handle_adxl345_data, "adxl345_data", oid)
|
# Bulk sample message reading
|
||||||
# Clock tracking
|
chip_smooth = self.data_rate * BATCH_UPDATES * 2
|
||||||
self.last_sequence = self.max_query_duration = 0
|
self.ffreader = bulk_sensor.FixedFreqReader(mcu, chip_smooth, "BBBBB")
|
||||||
self.last_limit_count = self.last_error_count = 0
|
self.last_error_count = 0
|
||||||
self.clock_sync = ClockSyncRegression(self.mcu, 640)
|
# Process messages in batches
|
||||||
# API server endpoints
|
self.batch_bulk = bulk_sensor.BatchBulkHelper(
|
||||||
self.api_dump = motion_report.APIDumpHelper(
|
self.printer, self._process_batch,
|
||||||
self.printer, self._api_update, self._api_startstop, 0.100)
|
self._start_measurements, self._finish_measurements, BATCH_UPDATES)
|
||||||
self.name = config.get_name().split()[-1]
|
self.name = config.get_name().split()[-1]
|
||||||
wh = self.printer.lookup_object('webhooks')
|
hdr = ('time', 'x_acceleration', 'y_acceleration', 'z_acceleration')
|
||||||
wh.register_mux_endpoint("adxl345/dump_adxl345", "sensor", self.name,
|
self.batch_bulk.add_mux_endpoint("adxl345/dump_adxl345", "sensor",
|
||||||
self._handle_dump_adxl345)
|
self.name, {'header': hdr})
|
||||||
def _build_config(self):
|
def _build_config(self):
|
||||||
cmdqueue = self.spi.get_command_queue()
|
cmdqueue = self.spi.get_command_queue()
|
||||||
self.query_adxl345_cmd = self.mcu.lookup_command(
|
self.query_adxl345_cmd = self.mcu.lookup_command(
|
||||||
"query_adxl345 oid=%c clock=%u rest_ticks=%u", cq=cmdqueue)
|
"query_adxl345 oid=%c rest_ticks=%u", cq=cmdqueue)
|
||||||
self.query_adxl345_end_cmd = self.mcu.lookup_query_command(
|
self.ffreader.setup_query_command("query_adxl345_status oid=%c",
|
||||||
"query_adxl345 oid=%c clock=%u rest_ticks=%u",
|
oid=self.oid, cq=cmdqueue)
|
||||||
"adxl345_status oid=%c clock=%u query_ticks=%u next_sequence=%hu"
|
|
||||||
" buffered=%c fifo=%c limit_count=%hu", oid=self.oid, cq=cmdqueue)
|
|
||||||
self.query_adxl345_status_cmd = self.mcu.lookup_query_command(
|
|
||||||
"query_adxl345_status oid=%c",
|
|
||||||
"adxl345_status oid=%c clock=%u query_ticks=%u next_sequence=%hu"
|
|
||||||
" buffered=%c fifo=%c limit_count=%hu", oid=self.oid, cq=cmdqueue)
|
|
||||||
def read_reg(self, reg):
|
def read_reg(self, reg):
|
||||||
params = self.spi.spi_transfer([reg | REG_MOD_READ, 0x00])
|
params = self.spi.spi_transfer([reg | REG_MOD_READ, 0x00])
|
||||||
response = bytearray(params['response'])
|
response = bytearray(params['response'])
|
||||||
@@ -292,29 +236,15 @@ class ADXL345:
|
|||||||
"This is generally indicative of connection problems "
|
"This is generally indicative of connection problems "
|
||||||
"(e.g. faulty wiring) or a faulty adxl345 chip." % (
|
"(e.g. faulty wiring) or a faulty adxl345 chip." % (
|
||||||
reg, val, stored_val))
|
reg, val, stored_val))
|
||||||
# Measurement collection
|
def start_internal_client(self):
|
||||||
def is_measuring(self):
|
aqh = AccelQueryHelper(self.printer)
|
||||||
return self.query_rate > 0
|
self.batch_bulk.add_client(aqh.handle_batch)
|
||||||
def _handle_adxl345_data(self, params):
|
return aqh
|
||||||
with self.lock:
|
# Measurement decoding
|
||||||
self.raw_samples.append(params)
|
def _convert_samples(self, samples):
|
||||||
def _extract_samples(self, raw_samples):
|
|
||||||
# Load variables to optimize inner loop below
|
|
||||||
(x_pos, x_scale), (y_pos, y_scale), (z_pos, z_scale) = self.axes_map
|
(x_pos, x_scale), (y_pos, y_scale), (z_pos, z_scale) = self.axes_map
|
||||||
last_sequence = self.last_sequence
|
count = 0
|
||||||
time_base, chip_base, inv_freq = self.clock_sync.get_time_translation()
|
for ptime, xlow, ylow, zlow, xzhigh, yzhigh in samples:
|
||||||
# Process every message in raw_samples
|
|
||||||
count = seq = 0
|
|
||||||
samples = [None] * (len(raw_samples) * SAMPLES_PER_BLOCK)
|
|
||||||
for params in raw_samples:
|
|
||||||
seq_diff = (last_sequence - params['sequence']) & 0xffff
|
|
||||||
seq_diff -= (seq_diff & 0x8000) << 1
|
|
||||||
seq = last_sequence - seq_diff
|
|
||||||
d = bytearray(params['data'])
|
|
||||||
msg_cdiff = seq * SAMPLES_PER_BLOCK - chip_base
|
|
||||||
for i in range(len(d) // BYTES_PER_SAMPLE):
|
|
||||||
d_xyz = d[i*BYTES_PER_SAMPLE:(i+1)*BYTES_PER_SAMPLE]
|
|
||||||
xlow, ylow, zlow, xzhigh, yzhigh = d_xyz
|
|
||||||
if yzhigh & 0x80:
|
if yzhigh & 0x80:
|
||||||
self.last_error_count += 1
|
self.last_error_count += 1
|
||||||
continue
|
continue
|
||||||
@@ -326,49 +256,11 @@ class ADXL345:
|
|||||||
x = round(raw_xyz[x_pos] * x_scale, 6)
|
x = round(raw_xyz[x_pos] * x_scale, 6)
|
||||||
y = round(raw_xyz[y_pos] * y_scale, 6)
|
y = round(raw_xyz[y_pos] * y_scale, 6)
|
||||||
z = round(raw_xyz[z_pos] * z_scale, 6)
|
z = round(raw_xyz[z_pos] * z_scale, 6)
|
||||||
ptime = round(time_base + (msg_cdiff + i) * inv_freq, 6)
|
samples[count] = (round(ptime, 6), x, y, z)
|
||||||
samples[count] = (ptime, x, y, z)
|
|
||||||
count += 1
|
count += 1
|
||||||
self.clock_sync.set_last_chip_clock(seq * SAMPLES_PER_BLOCK + i)
|
|
||||||
del samples[count:]
|
del samples[count:]
|
||||||
return samples
|
# Start, stop, and process message batches
|
||||||
def _update_clock(self, minclock=0):
|
|
||||||
# Query current state
|
|
||||||
for retry in range(5):
|
|
||||||
params = self.query_adxl345_status_cmd.send([self.oid],
|
|
||||||
minclock=minclock)
|
|
||||||
fifo = params['fifo'] & 0x7f
|
|
||||||
if fifo <= 32:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
raise self.printer.command_error("Unable to query adxl345 fifo")
|
|
||||||
mcu_clock = self.mcu.clock32_to_clock64(params['clock'])
|
|
||||||
sequence = (self.last_sequence & ~0xffff) | params['next_sequence']
|
|
||||||
if sequence < self.last_sequence:
|
|
||||||
sequence += 0x10000
|
|
||||||
self.last_sequence = sequence
|
|
||||||
buffered = params['buffered']
|
|
||||||
limit_count = (self.last_limit_count & ~0xffff) | params['limit_count']
|
|
||||||
if limit_count < self.last_limit_count:
|
|
||||||
limit_count += 0x10000
|
|
||||||
self.last_limit_count = limit_count
|
|
||||||
duration = params['query_ticks']
|
|
||||||
if duration > self.max_query_duration:
|
|
||||||
# Skip measurement as a high query time could skew clock tracking
|
|
||||||
self.max_query_duration = max(2 * self.max_query_duration,
|
|
||||||
self.mcu.seconds_to_clock(.000005))
|
|
||||||
return
|
|
||||||
self.max_query_duration = 2 * duration
|
|
||||||
msg_count = (sequence * SAMPLES_PER_BLOCK
|
|
||||||
+ buffered // BYTES_PER_SAMPLE + fifo)
|
|
||||||
# The "chip clock" is the message counter plus .5 for average
|
|
||||||
# inaccuracy of query responses and plus .5 for assumed offset
|
|
||||||
# of adxl345 hw processing time.
|
|
||||||
chip_clock = msg_count + 1
|
|
||||||
self.clock_sync.update(mcu_clock + duration // 2, chip_clock)
|
|
||||||
def _start_measurements(self):
|
def _start_measurements(self):
|
||||||
if self.is_measuring():
|
|
||||||
return
|
|
||||||
# In case of miswiring, testing ADXL345 device ID prevents treating
|
# In case of miswiring, testing ADXL345 device ID prevents treating
|
||||||
# noise or wrong signal as a correctly initialized device
|
# noise or wrong signal as a correctly initialized device
|
||||||
dev_id = self.read_reg(REG_DEVID)
|
dev_id = self.read_reg(REG_DEVID)
|
||||||
@@ -384,59 +276,27 @@ class ADXL345:
|
|||||||
self.set_reg(REG_FIFO_CTL, 0x00)
|
self.set_reg(REG_FIFO_CTL, 0x00)
|
||||||
self.set_reg(REG_BW_RATE, QUERY_RATES[self.data_rate])
|
self.set_reg(REG_BW_RATE, QUERY_RATES[self.data_rate])
|
||||||
self.set_reg(REG_FIFO_CTL, SET_FIFO_CTL)
|
self.set_reg(REG_FIFO_CTL, SET_FIFO_CTL)
|
||||||
# Setup samples
|
|
||||||
with self.lock:
|
|
||||||
self.raw_samples = []
|
|
||||||
# Start bulk reading
|
# Start bulk reading
|
||||||
systime = self.printer.get_reactor().monotonic()
|
|
||||||
print_time = self.mcu.estimated_print_time(systime) + MIN_MSG_TIME
|
|
||||||
reqclock = self.mcu.print_time_to_clock(print_time)
|
|
||||||
rest_ticks = self.mcu.seconds_to_clock(4. / self.data_rate)
|
rest_ticks = self.mcu.seconds_to_clock(4. / self.data_rate)
|
||||||
self.query_rate = self.data_rate
|
self.query_adxl345_cmd.send([self.oid, rest_ticks])
|
||||||
self.query_adxl345_cmd.send([self.oid, reqclock, rest_ticks],
|
self.set_reg(REG_POWER_CTL, 0x08)
|
||||||
reqclock=reqclock)
|
|
||||||
logging.info("ADXL345 starting '%s' measurements", self.name)
|
logging.info("ADXL345 starting '%s' measurements", self.name)
|
||||||
# Initialize clock tracking
|
# Initialize clock tracking
|
||||||
self.last_sequence = 0
|
self.ffreader.note_start()
|
||||||
self.last_limit_count = self.last_error_count = 0
|
self.last_error_count = 0
|
||||||
self.clock_sync.reset(reqclock, 0)
|
|
||||||
self.max_query_duration = 1 << 31
|
|
||||||
self._update_clock(minclock=reqclock)
|
|
||||||
self.max_query_duration = 1 << 31
|
|
||||||
def _finish_measurements(self):
|
def _finish_measurements(self):
|
||||||
if not self.is_measuring():
|
|
||||||
return
|
|
||||||
# Halt bulk reading
|
# Halt bulk reading
|
||||||
params = self.query_adxl345_end_cmd.send([self.oid, 0, 0])
|
self.set_reg(REG_POWER_CTL, 0x00)
|
||||||
self.query_rate = 0
|
self.query_adxl345_cmd.send_wait_ack([self.oid, 0])
|
||||||
with self.lock:
|
self.ffreader.note_end()
|
||||||
self.raw_samples = []
|
|
||||||
logging.info("ADXL345 finished '%s' measurements", self.name)
|
logging.info("ADXL345 finished '%s' measurements", self.name)
|
||||||
# API interface
|
def _process_batch(self, eventtime):
|
||||||
def _api_update(self, eventtime):
|
samples = self.ffreader.pull_samples()
|
||||||
self._update_clock()
|
self._convert_samples(samples)
|
||||||
with self.lock:
|
|
||||||
raw_samples = self.raw_samples
|
|
||||||
self.raw_samples = []
|
|
||||||
if not raw_samples:
|
|
||||||
return {}
|
|
||||||
samples = self._extract_samples(raw_samples)
|
|
||||||
if not samples:
|
if not samples:
|
||||||
return {}
|
return {}
|
||||||
return {'data': samples, 'errors': self.last_error_count,
|
return {'data': samples, 'errors': self.last_error_count,
|
||||||
'overflows': self.last_limit_count}
|
'overflows': self.ffreader.get_last_overflows()}
|
||||||
def _api_startstop(self, is_start):
|
|
||||||
if is_start:
|
|
||||||
self._start_measurements()
|
|
||||||
else:
|
|
||||||
self._finish_measurements()
|
|
||||||
def _handle_dump_adxl345(self, web_request):
|
|
||||||
self.api_dump.add_client(web_request)
|
|
||||||
hdr = ('time', 'x_acceleration', 'y_acceleration', 'z_acceleration')
|
|
||||||
web_request.send({'header': hdr})
|
|
||||||
def start_internal_client(self):
|
|
||||||
cconn = self.api_dump.add_internal_client()
|
|
||||||
return AccelQueryHelper(self.printer, cconn)
|
|
||||||
|
|
||||||
def load_config(config):
|
def load_config(config):
|
||||||
return ADXL345(config)
|
return ADXL345(config)
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
# Copyright (C) 2021,2022 Kevin O'Connor <kevin@koconnor.net>
|
# Copyright (C) 2021,2022 Kevin O'Connor <kevin@koconnor.net>
|
||||||
#
|
#
|
||||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
import logging, math, threading
|
import logging, math
|
||||||
from . import bus, motion_report
|
from . import bus, bulk_sensor
|
||||||
|
|
||||||
MIN_MSG_TIME = 0.100
|
MIN_MSG_TIME = 0.100
|
||||||
TCODE_ERROR = 0xff
|
TCODE_ERROR = 0xff
|
||||||
@@ -85,9 +85,9 @@ class AngleCalibration:
|
|||||||
cal2 = calibration[bucket + 1]
|
cal2 = calibration[bucket + 1]
|
||||||
adj = (angle & interp_mask) * (cal2 - cal1)
|
adj = (angle & interp_mask) * (cal2 - cal1)
|
||||||
adj = cal1 + ((adj + interp_round) >> interp_bits)
|
adj = cal1 + ((adj + interp_round) >> interp_bits)
|
||||||
angle_diff = (angle - adj) & 0xffff
|
angle_diff = (adj - angle) & 0xffff
|
||||||
angle_diff -= (angle_diff & 0x8000) << 1
|
angle_diff -= (angle_diff & 0x8000) << 1
|
||||||
new_angle = angle - angle_diff
|
new_angle = angle + angle_diff
|
||||||
if calibration_reversed:
|
if calibration_reversed:
|
||||||
new_angle = -new_angle
|
new_angle = -new_angle
|
||||||
samples[i] = (samp_time, new_angle)
|
samples[i] = (samp_time, new_angle)
|
||||||
@@ -157,8 +157,14 @@ class AngleCalibration:
|
|||||||
def do_calibration_moves(self):
|
def do_calibration_moves(self):
|
||||||
move = self.printer.lookup_object('force_move').manual_move
|
move = self.printer.lookup_object('force_move').manual_move
|
||||||
# Start data collection
|
# Start data collection
|
||||||
angle_sensor = self.printer.lookup_object(self.name)
|
msgs = []
|
||||||
cconn = angle_sensor.start_internal_client()
|
is_finished = False
|
||||||
|
def handle_batch(msg):
|
||||||
|
if is_finished:
|
||||||
|
return False
|
||||||
|
msgs.append(msg)
|
||||||
|
return True
|
||||||
|
self.printer.lookup_object(self.name).add_client(handle_batch)
|
||||||
# Move stepper several turns (to allow internal sensor calibration)
|
# Move stepper several turns (to allow internal sensor calibration)
|
||||||
microsteps, full_steps = self.get_microsteps()
|
microsteps, full_steps = self.get_microsteps()
|
||||||
mcu_stepper = self.mcu_stepper
|
mcu_stepper = self.mcu_stepper
|
||||||
@@ -190,13 +196,12 @@ class AngleCalibration:
|
|||||||
move(mcu_stepper, .5*rotation_dist + align_dist, move_speed)
|
move(mcu_stepper, .5*rotation_dist + align_dist, move_speed)
|
||||||
toolhead.wait_moves()
|
toolhead.wait_moves()
|
||||||
# Finish data collection
|
# Finish data collection
|
||||||
cconn.finalize()
|
is_finished = True
|
||||||
msgs = cconn.get_messages()
|
|
||||||
# Correlate query responses
|
# Correlate query responses
|
||||||
cal = {}
|
cal = {}
|
||||||
step = 0
|
step = 0
|
||||||
for msg in msgs:
|
for msg in msgs:
|
||||||
for query_time, pos in msg['params']['data']:
|
for query_time, pos in msg['data']:
|
||||||
# Add to step tracking
|
# Add to step tracking
|
||||||
while step < len(times) and query_time > times[step][1]:
|
while step < len(times) and query_time > times[step][1]:
|
||||||
step += 1
|
step += 1
|
||||||
@@ -375,9 +380,9 @@ class HelperTLE5012B:
|
|||||||
mcu_clock, chip_clock = self._query_clock()
|
mcu_clock, chip_clock = self._query_clock()
|
||||||
mdiff = mcu_clock - self.last_chip_mcu_clock
|
mdiff = mcu_clock - self.last_chip_mcu_clock
|
||||||
chip_mclock = self.last_chip_clock + int(mdiff * self.chip_freq + .5)
|
chip_mclock = self.last_chip_clock + int(mdiff * self.chip_freq + .5)
|
||||||
cdiff = (chip_mclock - chip_clock) & 0xffff
|
cdiff = (chip_clock - chip_mclock) & 0xffff
|
||||||
cdiff -= (cdiff & 0x8000) << 1
|
cdiff -= (cdiff & 0x8000) << 1
|
||||||
new_chip_clock = chip_mclock - cdiff
|
new_chip_clock = chip_mclock + cdiff
|
||||||
self.chip_freq = float(new_chip_clock - self.last_chip_clock) / mdiff
|
self.chip_freq = float(new_chip_clock - self.last_chip_clock) / mdiff
|
||||||
self.last_chip_clock = new_chip_clock
|
self.last_chip_clock = new_chip_clock
|
||||||
self.last_chip_mcu_clock = mcu_clock
|
self.last_chip_mcu_clock = mcu_clock
|
||||||
@@ -406,7 +411,201 @@ class HelperTLE5012B:
|
|||||||
parser=lambda x: int(x, 0))
|
parser=lambda x: int(x, 0))
|
||||||
self._write_reg(reg, val)
|
self._write_reg(reg, val)
|
||||||
|
|
||||||
|
class HelperMT6816:
|
||||||
|
SPI_MODE = 3
|
||||||
|
SPI_SPEED = 10000000
|
||||||
|
def __init__(self, config, spi, oid):
|
||||||
|
self.printer = config.get_printer()
|
||||||
|
self.spi = spi
|
||||||
|
self.oid = oid
|
||||||
|
self.mcu = spi.get_mcu()
|
||||||
|
self.mcu.register_config_callback(self._build_config)
|
||||||
|
self.spi_angle_transfer_cmd = None
|
||||||
|
self.is_tcode_absolute = False
|
||||||
|
self.last_temperature = None
|
||||||
|
name = config.get_name().split()[-1]
|
||||||
|
gcode = self.printer.lookup_object("gcode")
|
||||||
|
gcode.register_mux_command("ANGLE_DEBUG_READ", "CHIP", name,
|
||||||
|
self.cmd_ANGLE_DEBUG_READ,
|
||||||
|
desc=self.cmd_ANGLE_DEBUG_READ_help)
|
||||||
|
def _build_config(self):
|
||||||
|
cmdqueue = self.spi.get_command_queue()
|
||||||
|
self.spi_angle_transfer_cmd = self.mcu.lookup_query_command(
|
||||||
|
"spi_angle_transfer oid=%c data=%*s",
|
||||||
|
"spi_angle_transfer_response oid=%c clock=%u response=%*s",
|
||||||
|
oid=self.oid, cq=cmdqueue)
|
||||||
|
def _send_spi(self, msg):
|
||||||
|
return self.spi.spi_transfer(msg)
|
||||||
|
def get_static_delay(self):
|
||||||
|
return .000001
|
||||||
|
def _read_reg(self, reg):
|
||||||
|
msg = [reg, 0, 0]
|
||||||
|
params = self._send_spi(msg)
|
||||||
|
resp = bytearray(params['response'])
|
||||||
|
val = (resp[1] << 8) | resp[2]
|
||||||
|
return val
|
||||||
|
def start(self):
|
||||||
|
pass
|
||||||
|
cmd_ANGLE_DEBUG_READ_help = "Query low-level angle sensor register"
|
||||||
|
def cmd_ANGLE_DEBUG_READ(self, gcmd):
|
||||||
|
reg = 0x83
|
||||||
|
val = self._read_reg(reg)
|
||||||
|
gcmd.respond_info("ANGLE REG[0x%02x] = 0x%04x" % (reg, val))
|
||||||
|
angle = val >> 2
|
||||||
|
parity = bin(val >> 1).count("1") % 2
|
||||||
|
gcmd.respond_info("Angle %i ~ %.2f" % (angle, angle * 360 / (1 << 14)))
|
||||||
|
gcmd.respond_info("No Mag: %i" % (val >> 1 & 0x1))
|
||||||
|
gcmd.respond_info("Parity: %i == %i" % (parity, val & 0x1))
|
||||||
|
|
||||||
|
class HelperMT6826S:
|
||||||
|
SPI_MODE = 3
|
||||||
|
SPI_SPEED = 10000000
|
||||||
|
def __init__(self, config, spi, oid):
|
||||||
|
self.printer = config.get_printer()
|
||||||
|
self.stepper_name = config.get('stepper', None)
|
||||||
|
self.spi = spi
|
||||||
|
self.oid = oid
|
||||||
|
self.mcu = spi.get_mcu()
|
||||||
|
self.mcu.register_config_callback(self._build_config)
|
||||||
|
self.spi_angle_transfer_cmd = None
|
||||||
|
self.is_tcode_absolute = False
|
||||||
|
self.last_temperature = None
|
||||||
|
name = config.get_name().split()[-1]
|
||||||
|
gcode = self.printer.lookup_object("gcode")
|
||||||
|
gcode.register_mux_command("ANGLE_DEBUG_READ", "CHIP", name,
|
||||||
|
self.cmd_ANGLE_DEBUG_READ,
|
||||||
|
desc=self.cmd_ANGLE_DEBUG_READ_help)
|
||||||
|
gcode.register_mux_command("ANGLE_CHIP_CALIBRATE", "CHIP", name,
|
||||||
|
self.cmd_ANGLE_CHIP_CALIBRATE,
|
||||||
|
desc=self.cmd_ANGLE_CHIP_CALIBRATE_help)
|
||||||
|
self.status_map = {
|
||||||
|
0: "No Calibration",
|
||||||
|
1: "Running Calibration",
|
||||||
|
2: "Calibration Failed",
|
||||||
|
3: "Calibration Successful"
|
||||||
|
}
|
||||||
|
def _build_config(self):
|
||||||
|
cmdqueue = self.spi.get_command_queue()
|
||||||
|
self.spi_angle_transfer_cmd = self.mcu.lookup_query_command(
|
||||||
|
"spi_angle_transfer oid=%c data=%*s",
|
||||||
|
"spi_angle_transfer_response oid=%c clock=%u response=%*s",
|
||||||
|
oid=self.oid, cq=cmdqueue)
|
||||||
|
def _send_spi(self, msg):
|
||||||
|
params = self.spi.spi_transfer(msg)
|
||||||
|
return params
|
||||||
|
def get_static_delay(self):
|
||||||
|
return .00001
|
||||||
|
def _read_reg(self, reg):
|
||||||
|
reg = 0x3000 | reg
|
||||||
|
msg = [reg >> 8, reg & 0xff, 0]
|
||||||
|
params = self._send_spi(msg)
|
||||||
|
resp = bytearray(params['response'])
|
||||||
|
return resp[2]
|
||||||
|
def _write_reg(self, reg, data):
|
||||||
|
reg = 0x6000 | reg
|
||||||
|
msg = [reg >> 8, reg & 0xff, data]
|
||||||
|
self._send_spi(msg)
|
||||||
|
def crc8(self, data):
|
||||||
|
polynomial = 0x07
|
||||||
|
crc = 0x00
|
||||||
|
for byte in data:
|
||||||
|
crc ^= byte
|
||||||
|
for _ in range(8):
|
||||||
|
if crc & 0x80:
|
||||||
|
crc = (crc << 1) ^ polynomial
|
||||||
|
else:
|
||||||
|
crc <<= 1
|
||||||
|
crc &= 0xFF
|
||||||
|
return crc
|
||||||
|
def _read_angle(self, reg):
|
||||||
|
reg = 0x3000 | reg
|
||||||
|
msg = [reg >> 8, reg & 0xff, 0, 0, 0, 0]
|
||||||
|
params = self._send_spi(msg)
|
||||||
|
resp = bytearray(params['response'])
|
||||||
|
angle = (resp[2] << 7) | (resp[3] >> 1)
|
||||||
|
status = resp[4]
|
||||||
|
crc_computed = self.crc8([resp[2], resp[3], resp[4]])
|
||||||
|
crc = resp[5]
|
||||||
|
return angle, status, crc, crc_computed
|
||||||
|
def start(self):
|
||||||
|
val = self._read_reg(0x00d)
|
||||||
|
# Set histeresis to 0.003 degree
|
||||||
|
self._write_reg(0x00d, (val & 0xf8) | 0x5)
|
||||||
|
def get_microsteps(self):
|
||||||
|
configfile = self.printer.lookup_object('configfile')
|
||||||
|
sconfig = configfile.get_status(None)['settings']
|
||||||
|
stconfig = sconfig.get(self.stepper_name, {})
|
||||||
|
microsteps = stconfig['microsteps']
|
||||||
|
full_steps = stconfig['full_steps_per_rotation']
|
||||||
|
return microsteps, full_steps
|
||||||
|
cmd_ANGLE_CHIP_CALIBRATE_help = "Run MT6826s calibration sequence"
|
||||||
|
def cmd_ANGLE_CHIP_CALIBRATE(self, gcmd):
|
||||||
|
fmove = self.printer.lookup_object('force_move')
|
||||||
|
mcu_stepper = fmove.lookup_stepper(self.stepper_name)
|
||||||
|
if self.stepper_name is None:
|
||||||
|
gcmd.respond_info("stepper not defined")
|
||||||
|
return
|
||||||
|
|
||||||
|
gcmd.respond_info("MT6826S Run calibration sequence")
|
||||||
|
gcmd.respond_info("Motor will do 18+ rotations -" +
|
||||||
|
" ensure pulley is disconnected")
|
||||||
|
req_freq = self._read_reg(0x00e) >> 4 & 0x7
|
||||||
|
# Minimal calibration speed
|
||||||
|
rpm = (3200 >> req_freq) + 1
|
||||||
|
rps = rpm / 60
|
||||||
|
move = fmove.manual_move
|
||||||
|
# Move stepper several turns (to allow internal sensor calibration)
|
||||||
|
microsteps, full_steps = self.get_microsteps()
|
||||||
|
step_dist = mcu_stepper.get_step_dist()
|
||||||
|
full_step_dist = step_dist * microsteps
|
||||||
|
rotation_dist = full_steps * full_step_dist
|
||||||
|
move(mcu_stepper, 2 * rotation_dist, rps * rotation_dist)
|
||||||
|
self._write_reg(0x155, 0x5e)
|
||||||
|
move(mcu_stepper, 20 * rotation_dist, rps * rotation_dist)
|
||||||
|
val = self._read_reg(0x113)
|
||||||
|
code = val >> 6
|
||||||
|
gcmd.respond_info("Status: %s" % (self.status_map[code]))
|
||||||
|
while code == 1:
|
||||||
|
move(mcu_stepper, 5 * rotation_dist, rps * rotation_dist)
|
||||||
|
val = self._read_reg(0x113)
|
||||||
|
code = val >> 6
|
||||||
|
gcmd.respond_info("Status: %s" % (self.status_map[code]))
|
||||||
|
if code == 2:
|
||||||
|
gcmd.respond_info("Calibration failed")
|
||||||
|
if code == 3:
|
||||||
|
gcmd.respond_info("Calibration success, please poweroff sensor")
|
||||||
|
cmd_ANGLE_DEBUG_READ_help = "Query low-level angle sensor register"
|
||||||
|
def cmd_ANGLE_DEBUG_READ(self, gcmd):
|
||||||
|
reg = gcmd.get("REG", minval=0, maxval=0x155,
|
||||||
|
parser=lambda x: int(x, 0))
|
||||||
|
if reg == 0x003:
|
||||||
|
angle, status, crc1, crc2 = self._read_angle(reg)
|
||||||
|
gcmd.respond_info("ANGLE REG[0x003] = 0x%02x" %
|
||||||
|
(angle >> 7))
|
||||||
|
gcmd.respond_info("ANGLE REG[0x004] = 0x%02x" %
|
||||||
|
((angle << 1) & 0xff))
|
||||||
|
gcmd.respond_info("Angle %i ~ %.2f" % (angle,
|
||||||
|
angle * 360 / (1 << 15)))
|
||||||
|
gcmd.respond_info("Weak Mag: %i" % (status >> 1 & 0x1))
|
||||||
|
gcmd.respond_info("Under Voltage: %i" % (status >> 2 & 0x1))
|
||||||
|
gcmd.respond_info("CRC: 0x%02x == 0x%02x" % (crc1, crc2))
|
||||||
|
elif reg == 0x00e:
|
||||||
|
val = self._read_reg(reg)
|
||||||
|
gcmd.respond_info("GPIO_DS = %i" % (val >> 7))
|
||||||
|
gcmd.respond_info("AUTOCAL_FREQ = %i" % (val >> 4 & 0x7))
|
||||||
|
elif reg == 0x113:
|
||||||
|
val = self._read_reg(reg)
|
||||||
|
gcmd.respond_info("Status: %s" % (self.cal_status[val >> 6]))
|
||||||
|
else:
|
||||||
|
val = self._read_reg(reg)
|
||||||
|
gcmd.respond_info("REG[0x%04x] = 0x%02x" % (reg, val))
|
||||||
|
|
||||||
|
|
||||||
|
BYTES_PER_SAMPLE = 3
|
||||||
|
SAMPLES_PER_BLOCK = bulk_sensor.MAX_BULK_MSG_SIZE // BYTES_PER_SAMPLE
|
||||||
|
|
||||||
SAMPLE_PERIOD = 0.000400
|
SAMPLE_PERIOD = 0.000400
|
||||||
|
BATCH_UPDATES = 0.100
|
||||||
|
|
||||||
class Angle:
|
class Angle:
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
@@ -417,12 +616,12 @@ class Angle:
|
|||||||
# Measurement conversion
|
# Measurement conversion
|
||||||
self.start_clock = self.time_shift = self.sample_ticks = 0
|
self.start_clock = self.time_shift = self.sample_ticks = 0
|
||||||
self.last_sequence = self.last_angle = 0
|
self.last_sequence = self.last_angle = 0
|
||||||
# Measurement storage (accessed from background thread)
|
|
||||||
self.lock = threading.Lock()
|
|
||||||
self.raw_samples = []
|
|
||||||
# Sensor type
|
# Sensor type
|
||||||
sensors = { "a1333": HelperA1333, "as5047d": HelperAS5047D,
|
sensors = { "a1333": HelperA1333,
|
||||||
"tle5012b": HelperTLE5012B }
|
"as5047d": HelperAS5047D,
|
||||||
|
"tle5012b": HelperTLE5012B,
|
||||||
|
"mt6816": HelperMT6816,
|
||||||
|
"mt6826s": HelperMT6826S }
|
||||||
sensor_type = config.getchoice('sensor_type', {s: s for s in sensors})
|
sensor_type = config.getchoice('sensor_type', {s: s for s in sensors})
|
||||||
sensor_class = sensors[sensor_type]
|
sensor_class = sensors[sensor_type]
|
||||||
self.spi = bus.MCU_SPI_from_config(config, sensor_class.SPI_MODE,
|
self.spi = bus.MCU_SPI_from_config(config, sensor_class.SPI_MODE,
|
||||||
@@ -431,7 +630,7 @@ class Angle:
|
|||||||
self.oid = oid = mcu.create_oid()
|
self.oid = oid = mcu.create_oid()
|
||||||
self.sensor_helper = sensor_class(config, self.spi, oid)
|
self.sensor_helper = sensor_class(config, self.spi, oid)
|
||||||
# Setup mcu sensor_spi_angle bulk query code
|
# Setup mcu sensor_spi_angle bulk query code
|
||||||
self.query_spi_angle_cmd = self.query_spi_angle_end_cmd = None
|
self.query_spi_angle_cmd = None
|
||||||
mcu.add_config_cmd(
|
mcu.add_config_cmd(
|
||||||
"config_spi_angle oid=%d spi_oid=%d spi_angle_type=%s"
|
"config_spi_angle oid=%d spi_oid=%d spi_angle_type=%s"
|
||||||
% (oid, self.spi.get_oid(), sensor_type))
|
% (oid, self.spi.get_oid(), sensor_type))
|
||||||
@@ -439,15 +638,15 @@ class Angle:
|
|||||||
"query_spi_angle oid=%d clock=0 rest_ticks=0 time_shift=0"
|
"query_spi_angle oid=%d clock=0 rest_ticks=0 time_shift=0"
|
||||||
% (oid,), on_restart=True)
|
% (oid,), on_restart=True)
|
||||||
mcu.register_config_callback(self._build_config)
|
mcu.register_config_callback(self._build_config)
|
||||||
mcu.register_response(self._handle_spi_angle_data,
|
self.bulk_queue = bulk_sensor.BulkDataQueue(mcu, oid=oid)
|
||||||
"spi_angle_data", oid)
|
# Process messages in batches
|
||||||
# API server endpoints
|
self.batch_bulk = bulk_sensor.BatchBulkHelper(
|
||||||
self.api_dump = motion_report.APIDumpHelper(
|
self.printer, self._process_batch,
|
||||||
self.printer, self._api_update, self._api_startstop, 0.100)
|
self._start_measurements, self._finish_measurements, BATCH_UPDATES)
|
||||||
self.name = config.get_name().split()[1]
|
self.name = config.get_name().split()[1]
|
||||||
wh = self.printer.lookup_object('webhooks')
|
api_resp = {'header': ('time', 'angle')}
|
||||||
wh.register_mux_endpoint("angle/dump_angle", "sensor", self.name,
|
self.batch_bulk.add_mux_endpoint("angle/dump_angle",
|
||||||
self._handle_dump_angle)
|
"sensor", self.name, api_resp)
|
||||||
def _build_config(self):
|
def _build_config(self):
|
||||||
freq = self.mcu.seconds_to_clock(1.)
|
freq = self.mcu.seconds_to_clock(1.)
|
||||||
while float(TCODE_ERROR << self.time_shift) / freq < 0.002:
|
while float(TCODE_ERROR << self.time_shift) / freq < 0.002:
|
||||||
@@ -456,17 +655,11 @@ class Angle:
|
|||||||
self.query_spi_angle_cmd = self.mcu.lookup_command(
|
self.query_spi_angle_cmd = self.mcu.lookup_command(
|
||||||
"query_spi_angle oid=%c clock=%u rest_ticks=%u time_shift=%c",
|
"query_spi_angle oid=%c clock=%u rest_ticks=%u time_shift=%c",
|
||||||
cq=cmdqueue)
|
cq=cmdqueue)
|
||||||
self.query_spi_angle_end_cmd = self.mcu.lookup_query_command(
|
|
||||||
"query_spi_angle oid=%c clock=%u rest_ticks=%u time_shift=%c",
|
|
||||||
"spi_angle_end oid=%c sequence=%hu", oid=self.oid, cq=cmdqueue)
|
|
||||||
def get_status(self, eventtime=None):
|
def get_status(self, eventtime=None):
|
||||||
return {'temperature': self.sensor_helper.last_temperature}
|
return {'temperature': self.sensor_helper.last_temperature}
|
||||||
# Measurement collection
|
def add_client(self, client_cb):
|
||||||
def is_measuring(self):
|
self.batch_bulk.add_client(client_cb)
|
||||||
return self.start_clock != 0
|
# Measurement decoding
|
||||||
def _handle_spi_angle_data(self, params):
|
|
||||||
with self.lock:
|
|
||||||
self.raw_samples.append(params)
|
|
||||||
def _extract_samples(self, raw_samples):
|
def _extract_samples(self, raw_samples):
|
||||||
# Load variables to optimize inner loop below
|
# Load variables to optimize inner loop below
|
||||||
sample_ticks = self.sample_ticks
|
sample_ticks = self.sample_ticks
|
||||||
@@ -487,23 +680,23 @@ class Angle:
|
|||||||
static_delay = self.sensor_helper.get_static_delay()
|
static_delay = self.sensor_helper.get_static_delay()
|
||||||
# Process every message in raw_samples
|
# Process every message in raw_samples
|
||||||
count = error_count = 0
|
count = error_count = 0
|
||||||
samples = [None] * (len(raw_samples) * 16)
|
samples = [None] * (len(raw_samples) * SAMPLES_PER_BLOCK)
|
||||||
for params in raw_samples:
|
for params in raw_samples:
|
||||||
seq = (last_sequence & ~0xffff) | params['sequence']
|
seq_diff = (params['sequence'] - last_sequence) & 0xffff
|
||||||
if seq < last_sequence:
|
last_sequence += seq_diff
|
||||||
seq += 0x10000
|
samp_count = last_sequence * SAMPLES_PER_BLOCK
|
||||||
last_sequence = seq
|
msg_mclock = start_clock + samp_count*sample_ticks
|
||||||
d = bytearray(params['data'])
|
d = bytearray(params['data'])
|
||||||
msg_mclock = start_clock + seq*16*sample_ticks
|
for i in range(len(d) // BYTES_PER_SAMPLE):
|
||||||
for i in range(len(d) // 3):
|
d_ta = d[i*BYTES_PER_SAMPLE:(i+1)*BYTES_PER_SAMPLE]
|
||||||
tcode = d[i*3]
|
tcode = d_ta[0]
|
||||||
if tcode == TCODE_ERROR:
|
if tcode == TCODE_ERROR:
|
||||||
error_count += 1
|
error_count += 1
|
||||||
continue
|
continue
|
||||||
raw_angle = d[i*3 + 1] | (d[i*3 + 2] << 8)
|
raw_angle = d_ta[1] | (d_ta[2] << 8)
|
||||||
angle_diff = (last_angle - raw_angle) & 0xffff
|
angle_diff = (raw_angle - last_angle) & 0xffff
|
||||||
angle_diff -= (angle_diff & 0x8000) << 1
|
angle_diff -= (angle_diff & 0x8000) << 1
|
||||||
last_angle -= angle_diff
|
last_angle += angle_diff
|
||||||
mclock = msg_mclock + i*sample_ticks
|
mclock = msg_mclock + i*sample_ticks
|
||||||
if is_tcode_absolute:
|
if is_tcode_absolute:
|
||||||
# tcode is tle5012b frame counter
|
# tcode is tle5012b frame counter
|
||||||
@@ -522,29 +715,14 @@ class Angle:
|
|||||||
self.last_angle = last_angle
|
self.last_angle = last_angle
|
||||||
del samples[count:]
|
del samples[count:]
|
||||||
return samples, error_count
|
return samples, error_count
|
||||||
# API interface
|
# Start, stop, and process message batches
|
||||||
def _api_update(self, eventtime):
|
def _is_measuring(self):
|
||||||
if self.sensor_helper.is_tcode_absolute:
|
return self.start_clock != 0
|
||||||
self.sensor_helper.update_clock()
|
|
||||||
with self.lock:
|
|
||||||
raw_samples = self.raw_samples
|
|
||||||
self.raw_samples = []
|
|
||||||
if not raw_samples:
|
|
||||||
return {}
|
|
||||||
samples, error_count = self._extract_samples(raw_samples)
|
|
||||||
if not samples:
|
|
||||||
return {}
|
|
||||||
offset = self.calibration.apply_calibration(samples)
|
|
||||||
return {'data': samples, 'errors': error_count,
|
|
||||||
'position_offset': offset}
|
|
||||||
def _start_measurements(self):
|
def _start_measurements(self):
|
||||||
if self.is_measuring():
|
|
||||||
return
|
|
||||||
logging.info("Starting angle '%s' measurements", self.name)
|
logging.info("Starting angle '%s' measurements", self.name)
|
||||||
self.sensor_helper.start()
|
self.sensor_helper.start()
|
||||||
# Start bulk reading
|
# Start bulk reading
|
||||||
with self.lock:
|
self.bulk_queue.clear_queue()
|
||||||
self.raw_samples = []
|
|
||||||
self.last_sequence = 0
|
self.last_sequence = 0
|
||||||
systime = self.printer.get_reactor().monotonic()
|
systime = self.printer.get_reactor().monotonic()
|
||||||
print_time = self.mcu.estimated_print_time(systime) + MIN_MSG_TIME
|
print_time = self.mcu.estimated_print_time(systime) + MIN_MSG_TIME
|
||||||
@@ -554,26 +732,23 @@ class Angle:
|
|||||||
self.query_spi_angle_cmd.send([self.oid, reqclock, rest_ticks,
|
self.query_spi_angle_cmd.send([self.oid, reqclock, rest_ticks,
|
||||||
self.time_shift], reqclock=reqclock)
|
self.time_shift], reqclock=reqclock)
|
||||||
def _finish_measurements(self):
|
def _finish_measurements(self):
|
||||||
if not self.is_measuring():
|
|
||||||
return
|
|
||||||
# Halt bulk reading
|
# Halt bulk reading
|
||||||
params = self.query_spi_angle_end_cmd.send([self.oid, 0, 0, 0])
|
self.query_spi_angle_cmd.send_wait_ack([self.oid, 0, 0, 0])
|
||||||
self.start_clock = 0
|
self.bulk_queue.clear_queue()
|
||||||
with self.lock:
|
|
||||||
self.raw_samples = []
|
|
||||||
self.sensor_helper.last_temperature = None
|
self.sensor_helper.last_temperature = None
|
||||||
logging.info("Stopped angle '%s' measurements", self.name)
|
logging.info("Stopped angle '%s' measurements", self.name)
|
||||||
def _api_startstop(self, is_start):
|
def _process_batch(self, eventtime):
|
||||||
if is_start:
|
if self.sensor_helper.is_tcode_absolute:
|
||||||
self._start_measurements()
|
self.sensor_helper.update_clock()
|
||||||
else:
|
raw_samples = self.bulk_queue.pull_queue()
|
||||||
self._finish_measurements()
|
if not raw_samples:
|
||||||
def _handle_dump_angle(self, web_request):
|
return {}
|
||||||
self.api_dump.add_client(web_request)
|
samples, error_count = self._extract_samples(raw_samples)
|
||||||
hdr = ('time', 'angle')
|
if not samples:
|
||||||
web_request.send({'header': hdr})
|
return {}
|
||||||
def start_internal_client(self):
|
offset = self.calibration.apply_calibration(samples)
|
||||||
return self.api_dump.add_internal_client()
|
return {'data': samples, 'errors': error_count,
|
||||||
|
'position_offset': offset}
|
||||||
|
|
||||||
def load_config_prefix(config):
|
def load_config_prefix(config):
|
||||||
return Angle(config)
|
return Angle(config)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
|
|
||||||
import math
|
import math
|
||||||
from . import manual_probe as ManualProbe, bed_mesh as BedMesh
|
from . import manual_probe, bed_mesh, probe
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_SAMPLE_COUNT = 3
|
DEFAULT_SAMPLE_COUNT = 3
|
||||||
@@ -23,45 +23,75 @@ class AxisTwistCompensation:
|
|||||||
self.horizontal_move_z = config.getfloat('horizontal_move_z',
|
self.horizontal_move_z = config.getfloat('horizontal_move_z',
|
||||||
DEFAULT_HORIZONTAL_MOVE_Z)
|
DEFAULT_HORIZONTAL_MOVE_Z)
|
||||||
self.speed = config.getfloat('speed', DEFAULT_SPEED)
|
self.speed = config.getfloat('speed', DEFAULT_SPEED)
|
||||||
self.calibrate_start_x = config.getfloat('calibrate_start_x')
|
self.calibrate_start_x = config.getfloat('calibrate_start_x',
|
||||||
self.calibrate_end_x = config.getfloat('calibrate_end_x')
|
default=None)
|
||||||
self.calibrate_y = config.getfloat('calibrate_y')
|
self.calibrate_end_x = config.getfloat('calibrate_end_x', default=None)
|
||||||
|
self.calibrate_y = config.getfloat('calibrate_y', default=None)
|
||||||
self.z_compensations = config.getlists('z_compensations',
|
self.z_compensations = config.getlists('z_compensations',
|
||||||
default=[], parser=float)
|
default=[], parser=float)
|
||||||
self.compensation_start_x = config.getfloat('compensation_start_x',
|
self.compensation_start_x = config.getfloat('compensation_start_x',
|
||||||
default=None)
|
default=None)
|
||||||
self.compensation_end_x = config.getfloat('compensation_start_y',
|
self.compensation_end_x = config.getfloat('compensation_end_x',
|
||||||
default=None)
|
default=None)
|
||||||
|
|
||||||
self.m = None
|
self.calibrate_start_y = config.getfloat('calibrate_start_y',
|
||||||
self.b = None
|
default=None)
|
||||||
|
self.calibrate_end_y = config.getfloat('calibrate_end_y', default=None)
|
||||||
|
self.calibrate_x = config.getfloat('calibrate_x', default=None)
|
||||||
|
self.compensation_start_y = config.getfloat('compensation_start_y',
|
||||||
|
default=None)
|
||||||
|
self.compensation_end_y = config.getfloat('compensation_end_y',
|
||||||
|
default=None)
|
||||||
|
self.zy_compensations = config.getlists('zy_compensations',
|
||||||
|
default=[], parser=float)
|
||||||
|
|
||||||
# setup calibrater
|
# setup calibrater
|
||||||
self.calibrater = Calibrater(self, config)
|
self.calibrater = Calibrater(self, config)
|
||||||
|
# register events
|
||||||
|
self.printer.register_event_handler("probe:update_results",
|
||||||
|
self._update_z_compensation_value)
|
||||||
|
|
||||||
def get_z_compensation_value(self, pos):
|
def _update_z_compensation_value(self, pos):
|
||||||
if not self.z_compensations:
|
if self.z_compensations:
|
||||||
return 0
|
pos[2] += self._get_interpolated_z_compensation(
|
||||||
|
pos[0], self.z_compensations,
|
||||||
|
self.compensation_start_x,
|
||||||
|
self.compensation_end_x
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.zy_compensations:
|
||||||
|
pos[2] += self._get_interpolated_z_compensation(
|
||||||
|
pos[1], self.zy_compensations,
|
||||||
|
self.compensation_start_y,
|
||||||
|
self.compensation_end_y
|
||||||
|
)
|
||||||
|
|
||||||
|
def _get_interpolated_z_compensation(
|
||||||
|
self, coord, z_compensations,
|
||||||
|
comp_start,
|
||||||
|
comp_end
|
||||||
|
):
|
||||||
|
|
||||||
x_coord = pos[0]
|
|
||||||
z_compensations = self.z_compensations
|
|
||||||
sample_count = len(z_compensations)
|
sample_count = len(z_compensations)
|
||||||
spacing = ((self.calibrate_end_x - self.calibrate_start_x)
|
spacing = ((comp_end - comp_start)
|
||||||
/ (sample_count - 1))
|
/ (sample_count - 1))
|
||||||
interpolate_t = (x_coord - self.calibrate_start_x) / spacing
|
interpolate_t = (coord - comp_start) / spacing
|
||||||
interpolate_i = int(math.floor(interpolate_t))
|
interpolate_i = int(math.floor(interpolate_t))
|
||||||
interpolate_i = BedMesh.constrain(interpolate_i, 0, sample_count - 2)
|
interpolate_i = bed_mesh.constrain(interpolate_i, 0, sample_count - 2)
|
||||||
interpolate_t -= interpolate_i
|
interpolate_t -= interpolate_i
|
||||||
interpolated_z_compensation = BedMesh.lerp(
|
interpolated_z_compensation = bed_mesh.lerp(
|
||||||
interpolate_t, z_compensations[interpolate_i],
|
interpolate_t, z_compensations[interpolate_i],
|
||||||
z_compensations[interpolate_i + 1])
|
z_compensations[interpolate_i + 1])
|
||||||
return interpolated_z_compensation
|
return interpolated_z_compensation
|
||||||
|
|
||||||
def clear_compensations(self):
|
def clear_compensations(self, axis=None):
|
||||||
|
if axis is None:
|
||||||
self.z_compensations = []
|
self.z_compensations = []
|
||||||
self.m = None
|
self.zy_compensations = []
|
||||||
self.b = None
|
elif axis == 'X':
|
||||||
|
self.z_compensations = []
|
||||||
|
elif axis == 'Y':
|
||||||
|
self.zy_compensations = []
|
||||||
|
|
||||||
class Calibrater:
|
class Calibrater:
|
||||||
def __init__(self, compensation, config):
|
def __init__(self, compensation, config):
|
||||||
@@ -77,10 +107,14 @@ class Calibrater:
|
|||||||
self._handle_connect)
|
self._handle_connect)
|
||||||
self.speed = compensation.speed
|
self.speed = compensation.speed
|
||||||
self.horizontal_move_z = compensation.horizontal_move_z
|
self.horizontal_move_z = compensation.horizontal_move_z
|
||||||
self.start_point = (compensation.calibrate_start_x,
|
self.x_start_point = (compensation.calibrate_start_x,
|
||||||
compensation.calibrate_y)
|
compensation.calibrate_y)
|
||||||
self.end_point = (compensation.calibrate_end_x,
|
self.x_end_point = (compensation.calibrate_end_x,
|
||||||
compensation.calibrate_y)
|
compensation.calibrate_y)
|
||||||
|
self.y_start_point = (compensation.calibrate_x,
|
||||||
|
compensation.calibrate_start_y)
|
||||||
|
self.y_end_point = (compensation.calibrate_x,
|
||||||
|
compensation.calibrate_end_y)
|
||||||
self.results = None
|
self.results = None
|
||||||
self.current_point_index = None
|
self.current_point_index = None
|
||||||
self.gcmd = None
|
self.gcmd = None
|
||||||
@@ -95,7 +129,7 @@ class Calibrater:
|
|||||||
config = self.printer.lookup_object('configfile')
|
config = self.printer.lookup_object('configfile')
|
||||||
raise config.error(
|
raise config.error(
|
||||||
"AXIS_TWIST_COMPENSATION requires [probe] to be defined")
|
"AXIS_TWIST_COMPENSATION requires [probe] to be defined")
|
||||||
self.lift_speed = self.probe.get_lift_speed()
|
self.lift_speed = self.probe.get_probe_params()['lift_speed']
|
||||||
self.probe_x_offset, self.probe_y_offset, _ = \
|
self.probe_x_offset, self.probe_y_offset, _ = \
|
||||||
self.probe.get_offsets()
|
self.probe.get_offsets()
|
||||||
|
|
||||||
@@ -116,39 +150,246 @@ class Calibrater:
|
|||||||
def cmd_AXIS_TWIST_COMPENSATION_CALIBRATE(self, gcmd):
|
def cmd_AXIS_TWIST_COMPENSATION_CALIBRATE(self, gcmd):
|
||||||
self.gcmd = gcmd
|
self.gcmd = gcmd
|
||||||
sample_count = gcmd.get_int('SAMPLE_COUNT', DEFAULT_SAMPLE_COUNT)
|
sample_count = gcmd.get_int('SAMPLE_COUNT', DEFAULT_SAMPLE_COUNT)
|
||||||
|
axis = gcmd.get('AXIS', None)
|
||||||
|
auto = gcmd.get('AUTO', False)
|
||||||
|
|
||||||
|
if axis is not None and auto:
|
||||||
|
raise self.gcmd.error(
|
||||||
|
"Cannot use both 'AXIS' and 'AUTO' at the same time."
|
||||||
|
)
|
||||||
|
|
||||||
|
if auto:
|
||||||
|
self._start_autocalibration(sample_count)
|
||||||
|
return
|
||||||
|
|
||||||
|
if axis is None and not auto:
|
||||||
|
axis = 'X'
|
||||||
|
|
||||||
|
# check for valid sample_count
|
||||||
|
if sample_count < 2:
|
||||||
|
raise self.gcmd.error(
|
||||||
|
"SAMPLE_COUNT to probe must be at least 2")
|
||||||
|
|
||||||
|
# calculate the points to put the probe at, returned as a list of tuples
|
||||||
|
nozzle_points = []
|
||||||
|
|
||||||
|
if axis == 'X':
|
||||||
|
|
||||||
|
self.compensation.clear_compensations('X')
|
||||||
|
|
||||||
|
if not all([
|
||||||
|
self.x_start_point[0],
|
||||||
|
self.x_end_point[0],
|
||||||
|
self.x_start_point[1]
|
||||||
|
]):
|
||||||
|
raise self.gcmd.error(
|
||||||
|
"""AXIS_TWIST_COMPENSATION for X axis requires
|
||||||
|
calibrate_start_x, calibrate_end_x and calibrate_y
|
||||||
|
to be defined
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
start_point = self.x_start_point
|
||||||
|
end_point = self.x_end_point
|
||||||
|
|
||||||
|
x_axis_range = end_point[0] - start_point[0]
|
||||||
|
interval_dist = x_axis_range / (sample_count - 1)
|
||||||
|
|
||||||
|
for i in range(sample_count):
|
||||||
|
x = start_point[0] + i * interval_dist
|
||||||
|
y = start_point[1]
|
||||||
|
nozzle_points.append((x, y))
|
||||||
|
|
||||||
|
elif axis == 'Y':
|
||||||
|
|
||||||
|
self.compensation.clear_compensations('Y')
|
||||||
|
|
||||||
|
if not all([
|
||||||
|
self.y_start_point[0],
|
||||||
|
self.y_end_point[0],
|
||||||
|
self.y_start_point[1]
|
||||||
|
]):
|
||||||
|
raise self.gcmd.error(
|
||||||
|
"""AXIS_TWIST_COMPENSATION for Y axis requires
|
||||||
|
calibrate_start_y, calibrate_end_y and calibrate_x
|
||||||
|
to be defined
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
start_point = self.y_start_point
|
||||||
|
end_point = self.y_end_point
|
||||||
|
|
||||||
|
y_axis_range = end_point[1] - start_point[1]
|
||||||
|
interval_dist = y_axis_range / (sample_count - 1)
|
||||||
|
|
||||||
|
for i in range(sample_count):
|
||||||
|
x = start_point[0]
|
||||||
|
y = start_point[1] + i * interval_dist
|
||||||
|
nozzle_points.append((x, y))
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise self.gcmd.error(
|
||||||
|
"AXIS_TWIST_COMPENSATION_CALIBRATE: "
|
||||||
|
"Invalid axis.")
|
||||||
|
|
||||||
|
probe_points = self._calculate_probe_points(
|
||||||
|
nozzle_points, self.probe_x_offset, self.probe_y_offset)
|
||||||
|
|
||||||
|
# verify no other manual probe is in progress
|
||||||
|
manual_probe.verify_no_manual_probe(self.printer)
|
||||||
|
|
||||||
|
# begin calibration
|
||||||
|
self.current_point_index = 0
|
||||||
|
self.results = []
|
||||||
|
self.current_axis = axis
|
||||||
|
self._calibration(probe_points, nozzle_points, interval_dist)
|
||||||
|
|
||||||
|
def _calculate_corrections(self, coordinates):
|
||||||
|
# Extracting x, y, and z values from coordinates
|
||||||
|
x_coords = [coord[0] for coord in coordinates]
|
||||||
|
y_coords = [coord[1] for coord in coordinates]
|
||||||
|
z_coords = [coord[2] for coord in coordinates]
|
||||||
|
|
||||||
|
# Calculate the desired point (average of all corner points in z)
|
||||||
|
# For a general case, we should extract the unique
|
||||||
|
# combinations of corner points
|
||||||
|
z_corners = [z_coords[i] for i, coord in enumerate(coordinates)
|
||||||
|
if (coord[0] in [x_coords[0], x_coords[-1]])
|
||||||
|
and (coord[1] in [y_coords[0], y_coords[-1]])]
|
||||||
|
z_desired = sum(z_corners) / len(z_corners)
|
||||||
|
|
||||||
|
|
||||||
|
# Calculate average deformation per axis
|
||||||
|
unique_x_coords = sorted(set(x_coords))
|
||||||
|
unique_y_coords = sorted(set(y_coords))
|
||||||
|
|
||||||
|
avg_z_x = []
|
||||||
|
for x in unique_x_coords:
|
||||||
|
indices = [i for i, coord in enumerate(coordinates)
|
||||||
|
if coord[0] == x]
|
||||||
|
avg_z = sum(z_coords[i] for i in indices) / len(indices)
|
||||||
|
avg_z_x.append(avg_z)
|
||||||
|
|
||||||
|
avg_z_y = []
|
||||||
|
for y in unique_y_coords:
|
||||||
|
indices = [i for i, coord in enumerate(coordinates)
|
||||||
|
if coord[1] == y]
|
||||||
|
avg_z = sum(z_coords[i] for i in indices) / len(indices)
|
||||||
|
avg_z_y.append(avg_z)
|
||||||
|
|
||||||
|
# Calculate corrections to reach the desired point
|
||||||
|
x_corrections = [z_desired - avg for avg in avg_z_x]
|
||||||
|
y_corrections = [z_desired - avg for avg in avg_z_y]
|
||||||
|
|
||||||
|
return x_corrections, y_corrections
|
||||||
|
|
||||||
|
def _start_autocalibration(self, sample_count):
|
||||||
|
|
||||||
|
if not all([
|
||||||
|
self.x_start_point[0],
|
||||||
|
self.x_end_point[0],
|
||||||
|
self.y_start_point[0],
|
||||||
|
self.y_end_point[0]
|
||||||
|
]):
|
||||||
|
raise self.gcmd.error(
|
||||||
|
"""AXIS_TWIST_COMPENSATION_AUTOCALIBRATE requires
|
||||||
|
calibrate_start_x, calibrate_end_x, calibrate_start_y
|
||||||
|
and calibrate_end_y to be defined
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
# check for valid sample_count
|
# check for valid sample_count
|
||||||
if sample_count is None or sample_count < 2:
|
if sample_count is None or sample_count < 2:
|
||||||
raise self.gcmd.error(
|
raise self.gcmd.error(
|
||||||
"SAMPLE_COUNT to probe must be at least 2")
|
"SAMPLE_COUNT to probe must be at least 2")
|
||||||
|
|
||||||
|
# verify no other manual probe is in progress
|
||||||
|
manual_probe.verify_no_manual_probe(self.printer)
|
||||||
|
|
||||||
# clear the current config
|
# clear the current config
|
||||||
self.compensation.clear_compensations()
|
self.compensation.clear_compensations()
|
||||||
|
|
||||||
# calculate some values
|
min_x = self.x_start_point[0]
|
||||||
x_range = self.end_point[0] - self.start_point[0]
|
max_x = self.x_end_point[0]
|
||||||
interval_dist = x_range / (sample_count - 1)
|
min_y = self.y_start_point[1]
|
||||||
nozzle_points = self._calculate_nozzle_points(sample_count,
|
max_y = self.y_end_point[1]
|
||||||
interval_dist)
|
|
||||||
probe_points = self._calculate_probe_points(
|
|
||||||
nozzle_points, self.probe_x_offset, self.probe_y_offset)
|
|
||||||
|
|
||||||
# verify no other manual probe is in progress
|
# calculate x positions
|
||||||
ManualProbe.verify_no_manual_probe(self.printer)
|
interval_x = (max_x - min_x) / (sample_count - 1)
|
||||||
|
xps = [min_x + interval_x * i for i in range(sample_count)]
|
||||||
|
|
||||||
# begin calibration
|
# Calculate points array
|
||||||
self.current_point_index = 0
|
interval_y = (max_y - min_y) / (sample_count - 1)
|
||||||
self.results = []
|
flip = False
|
||||||
self._calibration(probe_points, nozzle_points, interval_dist)
|
|
||||||
|
|
||||||
def _calculate_nozzle_points(self, sample_count, interval_dist):
|
points = []
|
||||||
# calculate the points to put the probe at, returned as a list of tuples
|
|
||||||
nozzle_points = []
|
|
||||||
for i in range(sample_count):
|
for i in range(sample_count):
|
||||||
x = self.start_point[0] + i * interval_dist
|
for j in range(sample_count):
|
||||||
y = self.start_point[1]
|
if(not flip):
|
||||||
nozzle_points.append((x, y))
|
idx = j
|
||||||
return nozzle_points
|
else:
|
||||||
|
idx = sample_count -1 - j
|
||||||
|
points.append([xps[i], min_y + interval_y * idx ])
|
||||||
|
flip = not flip
|
||||||
|
|
||||||
|
|
||||||
|
# calculate the points to put the nozzle at, and probe
|
||||||
|
probe_points = []
|
||||||
|
|
||||||
|
for i in range(len(points)):
|
||||||
|
x = points[i][0] - self.probe_x_offset
|
||||||
|
y = points[i][1] - self.probe_y_offset
|
||||||
|
probe_points.append([x, y, self._auto_calibration((x,y))[2]])
|
||||||
|
|
||||||
|
# calculate corrections
|
||||||
|
x_corr, y_corr = self._calculate_corrections(probe_points)
|
||||||
|
|
||||||
|
x_corr_str = ', '.join(["{:.6f}".format(x)
|
||||||
|
for x in x_corr])
|
||||||
|
|
||||||
|
y_corr_str = ', '.join(["{:.6f}".format(x)
|
||||||
|
for x in y_corr])
|
||||||
|
|
||||||
|
# finalize
|
||||||
|
configfile = self.printer.lookup_object('configfile')
|
||||||
|
configfile.set(self.configname, 'z_compensations', x_corr_str)
|
||||||
|
configfile.set(self.configname, 'compensation_start_x',
|
||||||
|
self.x_start_point[0])
|
||||||
|
configfile.set(self.configname, 'compensation_end_x',
|
||||||
|
self.x_end_point[0])
|
||||||
|
|
||||||
|
|
||||||
|
configfile.set(self.configname, 'zy_compensations', y_corr_str)
|
||||||
|
configfile.set(self.configname, 'compensation_start_y',
|
||||||
|
self.y_start_point[1])
|
||||||
|
configfile.set(self.configname, 'compensation_end_y',
|
||||||
|
self.y_end_point[1])
|
||||||
|
|
||||||
|
self.gcode.respond_info(
|
||||||
|
"AXIS_TWIST_COMPENSATION state has been saved "
|
||||||
|
"for the current session. The SAVE_CONFIG command will "
|
||||||
|
"update the printer config file and restart the printer.")
|
||||||
|
# output result
|
||||||
|
self.gcmd.respond_info(
|
||||||
|
"AXIS_TWIST_COMPENSATION_AUTOCALIBRATE: Calibration complete: ")
|
||||||
|
self.gcmd.respond_info("\n".join(map(str, [x_corr, y_corr])), log=False)
|
||||||
|
|
||||||
|
def _auto_calibration(self, probe_point):
|
||||||
|
|
||||||
|
# horizontal_move_z (to prevent probe trigger or hitting bed)
|
||||||
|
self._move_helper((None, None, self.horizontal_move_z))
|
||||||
|
|
||||||
|
# move to point to probe
|
||||||
|
self._move_helper((probe_point[0],
|
||||||
|
probe_point[1], None))
|
||||||
|
|
||||||
|
# probe the point
|
||||||
|
pos = probe.run_single_probe(self.probe, self.gcmd)
|
||||||
|
|
||||||
|
# horizontal_move_z (to prevent probe trigger or hitting bed)
|
||||||
|
self._move_helper((None, None, self.horizontal_move_z))
|
||||||
|
|
||||||
|
return pos
|
||||||
|
|
||||||
def _calculate_probe_points(self, nozzle_points,
|
def _calculate_probe_points(self, nozzle_points,
|
||||||
probe_x_offset, probe_y_offset):
|
probe_x_offset, probe_y_offset):
|
||||||
@@ -186,7 +427,8 @@ class Calibrater:
|
|||||||
probe_points[self.current_point_index][1], None))
|
probe_points[self.current_point_index][1], None))
|
||||||
|
|
||||||
# probe the point
|
# probe the point
|
||||||
self.current_measured_z = self.probe.run_probe(self.gcmd)[2]
|
pos = probe.run_single_probe(self.probe, self.gcmd)
|
||||||
|
self.current_measured_z = pos[2]
|
||||||
|
|
||||||
# horizontal_move_z (to prevent probe trigger or hitting bed)
|
# horizontal_move_z (to prevent probe trigger or hitting bed)
|
||||||
self._move_helper((None, None, self.horizontal_move_z))
|
self._move_helper((None, None, self.horizontal_move_z))
|
||||||
@@ -195,7 +437,7 @@ class Calibrater:
|
|||||||
self._move_helper((nozzle_points[self.current_point_index]))
|
self._move_helper((nozzle_points[self.current_point_index]))
|
||||||
|
|
||||||
# start the manual (nozzle) probe
|
# start the manual (nozzle) probe
|
||||||
ManualProbe.ManualProbeHelper(
|
manual_probe.ManualProbeHelper(
|
||||||
self.printer, self.gcmd,
|
self.printer, self.gcmd,
|
||||||
self._manual_probe_callback_factory(
|
self._manual_probe_callback_factory(
|
||||||
probe_points, nozzle_points, interval))
|
probe_points, nozzle_points, interval))
|
||||||
@@ -234,14 +476,31 @@ class Calibrater:
|
|||||||
configfile = self.printer.lookup_object('configfile')
|
configfile = self.printer.lookup_object('configfile')
|
||||||
values_as_str = ', '.join(["{:.6f}".format(x)
|
values_as_str = ', '.join(["{:.6f}".format(x)
|
||||||
for x in self.results])
|
for x in self.results])
|
||||||
|
|
||||||
|
if(self.current_axis == 'X'):
|
||||||
|
|
||||||
configfile.set(self.configname, 'z_compensations', values_as_str)
|
configfile.set(self.configname, 'z_compensations', values_as_str)
|
||||||
configfile.set(self.configname, 'compensation_start_x',
|
configfile.set(self.configname, 'compensation_start_x',
|
||||||
self.start_point[0])
|
self.x_start_point[0])
|
||||||
configfile.set(self.configname, 'compensation_end_x',
|
configfile.set(self.configname, 'compensation_end_x',
|
||||||
self.end_point[0])
|
self.x_end_point[0])
|
||||||
|
|
||||||
self.compensation.z_compensations = self.results
|
self.compensation.z_compensations = self.results
|
||||||
self.compensation.compensation_start_x = self.start_point[0]
|
self.compensation.compensation_start_x = self.x_start_point[0]
|
||||||
self.compensation.compensation_end_x = self.end_point[0]
|
self.compensation.compensation_end_x = self.x_end_point[0]
|
||||||
|
|
||||||
|
elif(self.current_axis == 'Y'):
|
||||||
|
|
||||||
|
configfile.set(self.configname, 'zy_compensations', values_as_str)
|
||||||
|
configfile.set(self.configname, 'compensation_start_y',
|
||||||
|
self.y_start_point[1])
|
||||||
|
configfile.set(self.configname, 'compensation_end_y',
|
||||||
|
self.y_end_point[1])
|
||||||
|
|
||||||
|
self.compensation.zy_compensations = self.results
|
||||||
|
self.compensation.compensation_start_y = self.y_start_point[1]
|
||||||
|
self.compensation.compensation_end_y = self.y_end_point[1]
|
||||||
|
|
||||||
self.gcode.respond_info(
|
self.gcode.respond_info(
|
||||||
"AXIS_TWIST_COMPENSATION state has been saved "
|
"AXIS_TWIST_COMPENSATION state has been saved "
|
||||||
"for the current session. The SAVE_CONFIG command will "
|
"for the current session. The SAVE_CONFIG command will "
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
# BLTouch support
|
# BLTouch support
|
||||||
#
|
#
|
||||||
# Copyright (C) 2018-2021 Kevin O'Connor <kevin@koconnor.net>
|
# Copyright (C) 2018-2024 Kevin O'Connor <kevin@koconnor.net>
|
||||||
#
|
#
|
||||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
import logging
|
import logging
|
||||||
@@ -23,13 +23,9 @@ Commands = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# BLTouch "endstop" wrapper
|
# BLTouch "endstop" wrapper
|
||||||
class BLTouchEndstopWrapper:
|
class BLTouchProbe:
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
self.printer = config.get_printer()
|
self.printer = config.get_printer()
|
||||||
self.printer.register_event_handler("klippy:connect",
|
|
||||||
self.handle_connect)
|
|
||||||
self.printer.register_event_handler('klippy:mcu_identify',
|
|
||||||
self.handle_mcu_identify)
|
|
||||||
self.position_endstop = config.getfloat('z_offset', minval=0.)
|
self.position_endstop = config.getfloat('z_offset', minval=0.)
|
||||||
self.stow_on_each_sample = config.getboolean('stow_on_each_sample',
|
self.stow_on_each_sample = config.getboolean('stow_on_each_sample',
|
||||||
True)
|
True)
|
||||||
@@ -44,12 +40,9 @@ class BLTouchEndstopWrapper:
|
|||||||
self.next_cmd_time = self.action_end_time = 0.
|
self.next_cmd_time = self.action_end_time = 0.
|
||||||
self.finish_home_complete = self.wait_trigger_complete = None
|
self.finish_home_complete = self.wait_trigger_complete = None
|
||||||
# Create an "endstop" object to handle the sensor pin
|
# Create an "endstop" object to handle the sensor pin
|
||||||
pin = config.get('sensor_pin')
|
self.mcu_endstop = ppins.setup_pin('endstop', config.get('sensor_pin'))
|
||||||
pin_params = ppins.lookup_pin(pin, can_invert=True, can_pullup=True)
|
|
||||||
mcu = pin_params['chip']
|
|
||||||
self.mcu_endstop = mcu.setup_pin('endstop', pin_params)
|
|
||||||
# output mode
|
# output mode
|
||||||
omodes = {'5V': '5V', 'OD': 'OD', None: None}
|
omodes = ['5V', 'OD', None]
|
||||||
self.output_mode = config.getchoice('set_output_mode', omodes, None)
|
self.output_mode = config.getchoice('set_output_mode', omodes, None)
|
||||||
# Setup for sensor test
|
# Setup for sensor test
|
||||||
self.next_test_time = 0.
|
self.next_test_time = 0.
|
||||||
@@ -65,19 +58,30 @@ class BLTouchEndstopWrapper:
|
|||||||
self.get_steppers = self.mcu_endstop.get_steppers
|
self.get_steppers = self.mcu_endstop.get_steppers
|
||||||
self.home_wait = self.mcu_endstop.home_wait
|
self.home_wait = self.mcu_endstop.home_wait
|
||||||
self.query_endstop = self.mcu_endstop.query_endstop
|
self.query_endstop = self.mcu_endstop.query_endstop
|
||||||
|
# multi probes state
|
||||||
|
self.multi = 'OFF'
|
||||||
|
# Common probe implementation helpers
|
||||||
|
self.cmd_helper = probe.ProbeCommandHelper(
|
||||||
|
config, self, self.mcu_endstop.query_endstop)
|
||||||
|
self.probe_offsets = probe.ProbeOffsetsHelper(config)
|
||||||
|
self.probe_session = probe.ProbeSessionHelper(config, self)
|
||||||
# Register BLTOUCH_DEBUG command
|
# Register BLTOUCH_DEBUG command
|
||||||
self.gcode = self.printer.lookup_object('gcode')
|
self.gcode = self.printer.lookup_object('gcode')
|
||||||
self.gcode.register_command("BLTOUCH_DEBUG", self.cmd_BLTOUCH_DEBUG,
|
self.gcode.register_command("BLTOUCH_DEBUG", self.cmd_BLTOUCH_DEBUG,
|
||||||
desc=self.cmd_BLTOUCH_DEBUG_help)
|
desc=self.cmd_BLTOUCH_DEBUG_help)
|
||||||
self.gcode.register_command("BLTOUCH_STORE", self.cmd_BLTOUCH_STORE,
|
self.gcode.register_command("BLTOUCH_STORE", self.cmd_BLTOUCH_STORE,
|
||||||
desc=self.cmd_BLTOUCH_STORE_help)
|
desc=self.cmd_BLTOUCH_STORE_help)
|
||||||
# multi probes state
|
# Register events
|
||||||
self.multi = 'OFF'
|
self.printer.register_event_handler("klippy:connect",
|
||||||
def handle_mcu_identify(self):
|
self.handle_connect)
|
||||||
kin = self.printer.lookup_object('toolhead').get_kinematics()
|
def get_probe_params(self, gcmd=None):
|
||||||
for stepper in kin.get_steppers():
|
return self.probe_session.get_probe_params(gcmd)
|
||||||
if stepper.is_active_axis('z'):
|
def get_offsets(self):
|
||||||
self.add_stepper(stepper)
|
return self.probe_offsets.get_offsets()
|
||||||
|
def get_status(self, eventtime):
|
||||||
|
return self.cmd_helper.get_status(eventtime)
|
||||||
|
def start_probe_session(self, gcmd):
|
||||||
|
return self.probe_session.start_probe_session(gcmd)
|
||||||
def handle_connect(self):
|
def handle_connect(self):
|
||||||
self.sync_mcu_print_time()
|
self.sync_mcu_print_time()
|
||||||
self.next_cmd_time += 0.200
|
self.next_cmd_time += 0.200
|
||||||
@@ -116,7 +120,11 @@ class BLTouchEndstopWrapper:
|
|||||||
self.mcu_endstop.home_start(self.action_end_time, ENDSTOP_SAMPLE_TIME,
|
self.mcu_endstop.home_start(self.action_end_time, ENDSTOP_SAMPLE_TIME,
|
||||||
ENDSTOP_SAMPLE_COUNT, ENDSTOP_REST_TIME,
|
ENDSTOP_SAMPLE_COUNT, ENDSTOP_REST_TIME,
|
||||||
triggered=triggered)
|
triggered=triggered)
|
||||||
trigger_time = self.mcu_endstop.home_wait(self.action_end_time + 0.100)
|
try:
|
||||||
|
trigger_time = self.mcu_endstop.home_wait(
|
||||||
|
self.action_end_time + 0.100)
|
||||||
|
except self.printer.command_error as e:
|
||||||
|
return False
|
||||||
return trigger_time > 0.
|
return trigger_time > 0.
|
||||||
def raise_probe(self):
|
def raise_probe(self):
|
||||||
self.sync_mcu_print_time()
|
self.sync_mcu_print_time()
|
||||||
@@ -183,6 +191,9 @@ class BLTouchEndstopWrapper:
|
|||||||
self.verify_raise_probe()
|
self.verify_raise_probe()
|
||||||
self.sync_print_time()
|
self.sync_print_time()
|
||||||
self.multi = 'OFF'
|
self.multi = 'OFF'
|
||||||
|
def probing_move(self, pos, speed):
|
||||||
|
phoming = self.printer.lookup_object('homing')
|
||||||
|
return phoming.probing_move(self, pos, speed)
|
||||||
def probe_prepare(self, hmove):
|
def probe_prepare(self, hmove):
|
||||||
if self.multi == 'OFF' or self.multi == 'FIRST':
|
if self.multi == 'OFF' or self.multi == 'FIRST':
|
||||||
self.lower_probe()
|
self.lower_probe()
|
||||||
@@ -271,6 +282,6 @@ class BLTouchEndstopWrapper:
|
|||||||
self.sync_print_time()
|
self.sync_print_time()
|
||||||
|
|
||||||
def load_config(config):
|
def load_config(config):
|
||||||
blt = BLTouchEndstopWrapper(config)
|
blt = BLTouchProbe(config)
|
||||||
config.get_printer().add_object('probe', probe.PrinterProbe(config, blt))
|
config.get_printer().add_object('probe', blt)
|
||||||
return blt
|
return blt
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from . import bus
|
|||||||
|
|
||||||
REPORT_TIME = .8
|
REPORT_TIME = .8
|
||||||
BME280_CHIP_ADDR = 0x76
|
BME280_CHIP_ADDR = 0x76
|
||||||
|
|
||||||
BME280_REGS = {
|
BME280_REGS = {
|
||||||
'RESET': 0xE0, 'CTRL_HUM': 0xF2,
|
'RESET': 0xE0, 'CTRL_HUM': 0xF2,
|
||||||
'STATUS': 0xF3, 'CTRL_MEAS': 0xF4, 'CONFIG': 0xF5,
|
'STATUS': 0xF3, 'CTRL_MEAS': 0xF4, 'CONFIG': 0xF5,
|
||||||
@@ -16,6 +17,29 @@ BME280_REGS = {
|
|||||||
'HUM_MSB': 0xFD, 'HUM_LSB': 0xFE, 'CAL_1': 0x88, 'CAL_2': 0xE1
|
'HUM_MSB': 0xFD, 'HUM_LSB': 0xFE, 'CAL_1': 0x88, 'CAL_2': 0xE1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BMP388_REGS = {
|
||||||
|
"CMD": 0x7E,
|
||||||
|
"STATUS": 0x03,
|
||||||
|
"PWR_CTRL": 0x1B,
|
||||||
|
"OSR": 0x1C,
|
||||||
|
"ORD": 0x1D,
|
||||||
|
"INT_CTRL": 0x19,
|
||||||
|
"CAL_1": 0x31,
|
||||||
|
"TEMP_MSB": 0x09,
|
||||||
|
"TEMP_LSB": 0x08,
|
||||||
|
"TEMP_XLSB": 0x07,
|
||||||
|
"PRESS_MSB": 0x06,
|
||||||
|
"PRESS_LSB": 0x05,
|
||||||
|
"PRESS_XLSB": 0x04,
|
||||||
|
}
|
||||||
|
BMP388_REG_VAL_PRESS_EN = 0x01
|
||||||
|
BMP388_REG_VAL_TEMP_EN = 0x02
|
||||||
|
BMP388_REG_VAL_PRESS_OS_NO = 0b000
|
||||||
|
BMP388_REG_VAL_TEMP_OS_NO = 0b000000
|
||||||
|
BMP388_REG_VAL_ODR_50_HZ = 0x02
|
||||||
|
BMP388_REG_VAL_DRDY_EN = 0b100000
|
||||||
|
BMP388_REG_VAL_NORMAL_MODE = 0x30
|
||||||
|
|
||||||
BME680_REGS = {
|
BME680_REGS = {
|
||||||
'RESET': 0xE0, 'CTRL_HUM': 0x72, 'CTRL_GAS_1': 0x71, 'CTRL_GAS_0': 0x70,
|
'RESET': 0xE0, 'CTRL_HUM': 0x72, 'CTRL_GAS_1': 0x71, 'CTRL_GAS_0': 0x70,
|
||||||
'GAS_WAIT_0': 0x64, 'RES_HEAT_0': 0x5A, 'IDAC_HEAT_0': 0x50,
|
'GAS_WAIT_0': 0x64, 'RES_HEAT_0': 0x5A, 'IDAC_HEAT_0': 0x50,
|
||||||
@@ -46,9 +70,20 @@ BME680_GAS_CONSTANTS = {
|
|||||||
15: (1., 244.140625)
|
15: (1., 244.140625)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BMP180_REGS = {
|
||||||
|
'RESET': 0xE0,
|
||||||
|
'CAL_1': 0xAA,
|
||||||
|
'CTRL_MEAS': 0xF4,
|
||||||
|
'REG_MSB': 0xF6,
|
||||||
|
'REG_LSB': 0xF7,
|
||||||
|
'CRV_TEMP': 0x2E,
|
||||||
|
'CRV_PRES': 0x34
|
||||||
|
}
|
||||||
|
|
||||||
STATUS_MEASURING = 1 << 3
|
STATUS_MEASURING = 1 << 3
|
||||||
STATUS_IM_UPDATE = 1
|
STATUS_IM_UPDATE = 1
|
||||||
MODE = 1
|
MODE = 1
|
||||||
|
MODE_PERIODIC = 3
|
||||||
RUN_GAS = 1 << 4
|
RUN_GAS = 1 << 4
|
||||||
NB_CONV_0 = 0
|
NB_CONV_0 = 0
|
||||||
EAS_NEW_DATA = 1 << 7
|
EAS_NEW_DATA = 1 << 7
|
||||||
@@ -57,9 +92,11 @@ MEASURE_DONE = 1 << 5
|
|||||||
RESET_CHIP_VALUE = 0xB6
|
RESET_CHIP_VALUE = 0xB6
|
||||||
|
|
||||||
BME_CHIPS = {
|
BME_CHIPS = {
|
||||||
0x58: 'BMP280', 0x60: 'BME280', 0x61: 'BME680'
|
0x58: 'BMP280', 0x60: 'BME280', 0x61: 'BME680', 0x55: 'BMP180',
|
||||||
|
0x50: 'BMP388'
|
||||||
}
|
}
|
||||||
BME_CHIP_ID_REG = 0xD0
|
BME_CHIP_ID_REG = 0xD0
|
||||||
|
BMP3_CHIP_ID_REG = 0x00
|
||||||
|
|
||||||
|
|
||||||
def get_twos_complement(val, bit_size):
|
def get_twos_complement(val, bit_size):
|
||||||
@@ -81,6 +118,14 @@ def get_signed_byte(bits):
|
|||||||
return get_twos_complement(bits, 8)
|
return get_twos_complement(bits, 8)
|
||||||
|
|
||||||
|
|
||||||
|
def get_unsigned_short_msb(bits):
|
||||||
|
return bits[0] << 8 | bits[1]
|
||||||
|
|
||||||
|
|
||||||
|
def get_signed_short_msb(bits):
|
||||||
|
val = get_unsigned_short_msb(bits)
|
||||||
|
return get_twos_complement(val, 16)
|
||||||
|
|
||||||
class BME280:
|
class BME280:
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
self.printer = config.get_printer()
|
self.printer = config.get_printer()
|
||||||
@@ -99,6 +144,7 @@ class BME280:
|
|||||||
pow(2, self.os_temp - 1), pow(2, self.os_hum - 1),
|
pow(2, self.os_temp - 1), pow(2, self.os_hum - 1),
|
||||||
pow(2, self.os_pres - 1)))
|
pow(2, self.os_pres - 1)))
|
||||||
logging.info("BMxx80: IIR: %dx" % (pow(2, self.iir_filter) - 1))
|
logging.info("BMxx80: IIR: %dx" % (pow(2, self.iir_filter) - 1))
|
||||||
|
self.iir_filter = self.iir_filter & 0x07
|
||||||
|
|
||||||
self.temp = self.pressure = self.humidity = self.gas = self.t_fine = 0.
|
self.temp = self.pressure = self.humidity = self.gas = self.t_fine = 0.
|
||||||
self.min_temp = self.max_temp = self.range_switching_error = 0.
|
self.min_temp = self.max_temp = self.range_switching_error = 0.
|
||||||
@@ -111,6 +157,7 @@ class BME280:
|
|||||||
return
|
return
|
||||||
self.printer.register_event_handler("klippy:connect",
|
self.printer.register_event_handler("klippy:connect",
|
||||||
self.handle_connect)
|
self.handle_connect)
|
||||||
|
self.last_gas_time = 0
|
||||||
|
|
||||||
def handle_connect(self):
|
def handle_connect(self):
|
||||||
self._init_bmxx80()
|
self._init_bmxx80()
|
||||||
@@ -144,6 +191,29 @@ class BME280:
|
|||||||
dig['P9'] = get_signed_short(calib_data_1[22:24])
|
dig['P9'] = get_signed_short(calib_data_1[22:24])
|
||||||
return dig
|
return dig
|
||||||
|
|
||||||
|
def read_calibration_data_bmp388(calib_data_1):
|
||||||
|
dig = {}
|
||||||
|
dig["T1"] = get_unsigned_short(calib_data_1[0:2]) / 0.00390625
|
||||||
|
dig["T2"] = get_unsigned_short(calib_data_1[2:4]) / 1073741824.0
|
||||||
|
dig["T3"] = get_signed_byte(calib_data_1[4]) / 281474976710656.0
|
||||||
|
|
||||||
|
dig["P1"] = get_signed_short(calib_data_1[5:7]) - 16384
|
||||||
|
dig["P1"] /= 1048576.0
|
||||||
|
dig["P2"] = get_signed_short(calib_data_1[7:9]) - 16384
|
||||||
|
dig["P2"] /= 536870912.0
|
||||||
|
dig["P3"] = get_signed_byte(calib_data_1[9]) / 4294967296.0
|
||||||
|
dig["P4"] = get_signed_byte(calib_data_1[10]) / 137438953472.0
|
||||||
|
dig["P5"] = get_unsigned_short(calib_data_1[11:13]) / 0.125
|
||||||
|
dig["P6"] = get_unsigned_short(calib_data_1[13:15]) / 64.0
|
||||||
|
dig["P7"] = get_signed_byte(calib_data_1[15]) / 256.0
|
||||||
|
dig["P8"] = get_signed_byte(calib_data_1[16]) / 32768.0
|
||||||
|
dig["P9"] = get_signed_short(calib_data_1[17:19])
|
||||||
|
dig["P9"] /= 281474976710656.0
|
||||||
|
dig["P10"] = get_signed_byte(calib_data_1[19]) / 281474976710656.0
|
||||||
|
dig["P11"] = get_signed_byte(calib_data_1[20])
|
||||||
|
dig["P11"] /= 36893488147419103232.0
|
||||||
|
return dig
|
||||||
|
|
||||||
def read_calibration_data_bme280(calib_data_1, calib_data_2):
|
def read_calibration_data_bme280(calib_data_1, calib_data_2):
|
||||||
dig = read_calibration_data_bmp280(calib_data_1)
|
dig = read_calibration_data_bmp280(calib_data_1)
|
||||||
dig['H1'] = calib_data_1[25] & 0xFF
|
dig['H1'] = calib_data_1[25] & 0xFF
|
||||||
@@ -188,7 +258,24 @@ class BME280:
|
|||||||
dig['G3'] = get_signed_byte(calib_data_2[13])
|
dig['G3'] = get_signed_byte(calib_data_2[13])
|
||||||
return dig
|
return dig
|
||||||
|
|
||||||
chip_id = self.read_id()
|
def read_calibration_data_bmp180(calib_data_1):
|
||||||
|
dig = {}
|
||||||
|
dig['AC1'] = get_signed_short_msb(calib_data_1[0:2])
|
||||||
|
dig['AC2'] = get_signed_short_msb(calib_data_1[2:4])
|
||||||
|
dig['AC3'] = get_signed_short_msb(calib_data_1[4:6])
|
||||||
|
dig['AC4'] = get_unsigned_short_msb(calib_data_1[6:8])
|
||||||
|
dig['AC5'] = get_unsigned_short_msb(calib_data_1[8:10])
|
||||||
|
dig['AC6'] = get_unsigned_short_msb(calib_data_1[10:12])
|
||||||
|
|
||||||
|
dig['B1'] = get_signed_short_msb(calib_data_1[12:14])
|
||||||
|
dig['B2'] = get_signed_short_msb(calib_data_1[14:16])
|
||||||
|
|
||||||
|
dig['MB'] = get_signed_short_msb(calib_data_1[16:18])
|
||||||
|
dig['MC'] = get_signed_short_msb(calib_data_1[18:20])
|
||||||
|
dig['MD'] = get_signed_short_msb(calib_data_1[20:22])
|
||||||
|
return dig
|
||||||
|
|
||||||
|
chip_id = self.read_id() or self.read_bmp3_id()
|
||||||
if chip_id not in BME_CHIPS.keys():
|
if chip_id not in BME_CHIPS.keys():
|
||||||
logging.info("bme280: Unknown Chip ID received %#x" % chip_id)
|
logging.info("bme280: Unknown Chip ID received %#x" % chip_id)
|
||||||
else:
|
else:
|
||||||
@@ -197,30 +284,62 @@ class BME280:
|
|||||||
self.chip_type, self.i2c.i2c_address))
|
self.chip_type, self.i2c.i2c_address))
|
||||||
|
|
||||||
# Reset chip
|
# Reset chip
|
||||||
self.write_register('RESET', [RESET_CHIP_VALUE])
|
self.write_register('RESET', [RESET_CHIP_VALUE], wait=True)
|
||||||
self.reactor.pause(self.reactor.monotonic() + .5)
|
self.reactor.pause(self.reactor.monotonic() + .5)
|
||||||
|
|
||||||
# Make sure non-volatile memory has been copied to registers
|
# Make sure non-volatile memory has been copied to registers
|
||||||
|
if self.chip_type != 'BMP180':
|
||||||
|
# BMP180 has no status register available
|
||||||
status = self.read_register('STATUS', 1)[0]
|
status = self.read_register('STATUS', 1)[0]
|
||||||
while status & STATUS_IM_UPDATE:
|
while status & STATUS_IM_UPDATE:
|
||||||
self.reactor.pause(self.reactor.monotonic() + .01)
|
self.reactor.pause(self.reactor.monotonic() + .01)
|
||||||
status = self.read_register('STATUS', 1)[0]
|
status = self.read_register('STATUS', 1)[0]
|
||||||
|
|
||||||
if self.chip_type == 'BME680':
|
if self.chip_type == 'BME680':
|
||||||
self.max_sample_time = 0.5
|
self.max_sample_time = \
|
||||||
|
(1.25 + (2.3 * self.os_temp) + ((2.3 * self.os_pres) + .575)
|
||||||
|
+ ((2.3 * self.os_hum) + .575)) / 1000
|
||||||
self.sample_timer = self.reactor.register_timer(self._sample_bme680)
|
self.sample_timer = self.reactor.register_timer(self._sample_bme680)
|
||||||
self.chip_registers = BME680_REGS
|
self.chip_registers = BME680_REGS
|
||||||
else:
|
elif self.chip_type == 'BMP180':
|
||||||
|
self.sample_timer = self.reactor.register_timer(self._sample_bmp180)
|
||||||
|
self.chip_registers = BMP180_REGS
|
||||||
|
elif self.chip_type == 'BMP388':
|
||||||
|
self.chip_registers = BMP388_REGS
|
||||||
|
self.write_register(
|
||||||
|
"PWR_CTRL",
|
||||||
|
[
|
||||||
|
BMP388_REG_VAL_PRESS_EN
|
||||||
|
| BMP388_REG_VAL_TEMP_EN
|
||||||
|
| BMP388_REG_VAL_NORMAL_MODE
|
||||||
|
],
|
||||||
|
)
|
||||||
|
self.write_register(
|
||||||
|
"OSR", [BMP388_REG_VAL_PRESS_OS_NO | BMP388_REG_VAL_TEMP_OS_NO]
|
||||||
|
)
|
||||||
|
self.write_register("ORD", [BMP388_REG_VAL_ODR_50_HZ])
|
||||||
|
self.write_register("INT_CTRL", [BMP388_REG_VAL_DRDY_EN])
|
||||||
|
|
||||||
|
self.sample_timer = self.reactor.register_timer(self._sample_bmp388)
|
||||||
|
elif self.chip_type == 'BME280':
|
||||||
self.max_sample_time = \
|
self.max_sample_time = \
|
||||||
(1.25 + (2.3 * self.os_temp) + ((2.3 * self.os_pres) + .575)
|
(1.25 + (2.3 * self.os_temp) + ((2.3 * self.os_pres) + .575)
|
||||||
+ ((2.3 * self.os_hum) + .575)) / 1000
|
+ ((2.3 * self.os_hum) + .575)) / 1000
|
||||||
self.sample_timer = self.reactor.register_timer(self._sample_bme280)
|
self.sample_timer = self.reactor.register_timer(self._sample_bme280)
|
||||||
self.chip_registers = BME280_REGS
|
self.chip_registers = BME280_REGS
|
||||||
|
else:
|
||||||
if self.chip_type in ('BME680', 'BME280'):
|
self.max_sample_time = \
|
||||||
self.write_register('CONFIG', (self.iir_filter & 0x07) << 2)
|
(1.25 + (2.3 * self.os_temp)
|
||||||
|
+ ((2.3 * self.os_pres) + .575)) / 1000
|
||||||
|
self.sample_timer = self.reactor.register_timer(self._sample_bme280)
|
||||||
|
self.chip_registers = BME280_REGS
|
||||||
|
|
||||||
# Read out and calculate the trimming parameters
|
# Read out and calculate the trimming parameters
|
||||||
|
if self.chip_type == 'BMP180':
|
||||||
|
cal_1 = self.read_register('CAL_1', 22)
|
||||||
|
elif self.chip_type == 'BMP388':
|
||||||
|
cal_1 = self.read_register('CAL_1', 21)
|
||||||
|
else:
|
||||||
cal_1 = self.read_register('CAL_1', 26)
|
cal_1 = self.read_register('CAL_1', 26)
|
||||||
cal_2 = self.read_register('CAL_2', 16)
|
cal_2 = self.read_register('CAL_2', 16)
|
||||||
if self.chip_type == 'BME280':
|
if self.chip_type == 'BME280':
|
||||||
@@ -229,22 +348,69 @@ class BME280:
|
|||||||
self.dig = read_calibration_data_bmp280(cal_1)
|
self.dig = read_calibration_data_bmp280(cal_1)
|
||||||
elif self.chip_type == 'BME680':
|
elif self.chip_type == 'BME680':
|
||||||
self.dig = read_calibration_data_bme680(cal_1, cal_2)
|
self.dig = read_calibration_data_bme680(cal_1, cal_2)
|
||||||
|
elif self.chip_type == 'BMP180':
|
||||||
|
self.dig = read_calibration_data_bmp180(cal_1)
|
||||||
|
elif self.chip_type == 'BMP388':
|
||||||
|
self.dig = read_calibration_data_bmp388(cal_1)
|
||||||
|
|
||||||
def _sample_bme280(self, eventtime):
|
if self.chip_type in ('BME280', 'BMP280'):
|
||||||
# Enter forced mode
|
max_standby_time = REPORT_TIME - self.max_sample_time
|
||||||
|
# 0.5 ms
|
||||||
|
t_sb = 0
|
||||||
|
if self.chip_type == 'BME280':
|
||||||
|
if max_standby_time > 1:
|
||||||
|
t_sb = 5
|
||||||
|
elif max_standby_time > 0.5:
|
||||||
|
t_sb = 4
|
||||||
|
elif max_standby_time > 0.25:
|
||||||
|
t_sb = 3
|
||||||
|
elif max_standby_time > 0.125:
|
||||||
|
t_sb = 2
|
||||||
|
elif max_standby_time > 0.0625:
|
||||||
|
t_sb = 1
|
||||||
|
elif max_standby_time > 0.020:
|
||||||
|
t_sb = 7
|
||||||
|
elif max_standby_time > 0.010:
|
||||||
|
t_sb = 6
|
||||||
|
else:
|
||||||
|
if max_standby_time > 4:
|
||||||
|
t_sb = 7
|
||||||
|
elif max_standby_time > 2:
|
||||||
|
t_sb = 6
|
||||||
|
elif max_standby_time > 1:
|
||||||
|
t_sb = 5
|
||||||
|
elif max_standby_time > 0.5:
|
||||||
|
t_sb = 4
|
||||||
|
elif max_standby_time > 0.25:
|
||||||
|
t_sb = 3
|
||||||
|
elif max_standby_time > 0.125:
|
||||||
|
t_sb = 2
|
||||||
|
elif max_standby_time > 0.0625:
|
||||||
|
t_sb = 1
|
||||||
|
|
||||||
|
cfg = t_sb << 5 | self.iir_filter << 2
|
||||||
|
self.write_register('CONFIG', cfg)
|
||||||
if self.chip_type == 'BME280':
|
if self.chip_type == 'BME280':
|
||||||
self.write_register('CTRL_HUM', self.os_hum)
|
self.write_register('CTRL_HUM', self.os_hum)
|
||||||
meas = self.os_temp << 5 | self.os_pres << 2 | MODE
|
# Enter normal (periodic) mode
|
||||||
self.write_register('CTRL_MEAS', meas)
|
meas = self.os_temp << 5 | self.os_pres << 2 | MODE_PERIODIC
|
||||||
|
self.write_register('CTRL_MEAS', meas, wait=True)
|
||||||
|
|
||||||
|
if self.chip_type == 'BME680':
|
||||||
|
self.write_register('CONFIG', self.iir_filter << 2)
|
||||||
|
# Should be set once and reused on every mode register write
|
||||||
|
self.write_register('CTRL_HUM', self.os_hum & 0x07)
|
||||||
|
gas_wait_0 = self._calc_gas_heater_duration(self.gas_heat_duration)
|
||||||
|
self.write_register('GAS_WAIT_0', [gas_wait_0])
|
||||||
|
res_heat_0 = self._calc_gas_heater_resistance(self.gas_heat_temp)
|
||||||
|
self.write_register('RES_HEAT_0', [res_heat_0])
|
||||||
|
# Set initial heater current to reach Gas heater target on start
|
||||||
|
self.write_register('IDAC_HEAT_0', 96)
|
||||||
|
|
||||||
|
def _sample_bme280(self, eventtime):
|
||||||
|
# In normal mode data shadowing is performed
|
||||||
|
# So reading can be done while measurements are in process
|
||||||
try:
|
try:
|
||||||
# wait until results are ready
|
|
||||||
status = self.read_register('STATUS', 1)[0]
|
|
||||||
while status & STATUS_MEASURING:
|
|
||||||
self.reactor.pause(
|
|
||||||
self.reactor.monotonic() + self.max_sample_time)
|
|
||||||
status = self.read_register('STATUS', 1)[0]
|
|
||||||
|
|
||||||
if self.chip_type == 'BME280':
|
if self.chip_type == 'BME280':
|
||||||
data = self.read_register('PRESSURE_MSB', 8)
|
data = self.read_register('PRESSURE_MSB', 8)
|
||||||
elif self.chip_type == 'BMP280':
|
elif self.chip_type == 'BMP280':
|
||||||
@@ -271,36 +437,113 @@ class BME280:
|
|||||||
self._callback(self.mcu.estimated_print_time(measured_time), self.temp)
|
self._callback(self.mcu.estimated_print_time(measured_time), self.temp)
|
||||||
return measured_time + REPORT_TIME
|
return measured_time + REPORT_TIME
|
||||||
|
|
||||||
|
def _sample_bmp388(self, eventtime):
|
||||||
|
status = self.read_register("STATUS", 1)
|
||||||
|
if status[0] & 0b100000:
|
||||||
|
self.temp = self._sample_bmp388_temp()
|
||||||
|
if self.temp < self.min_temp or self.temp > self.max_temp:
|
||||||
|
self.printer.invoke_shutdown(
|
||||||
|
"BME280 temperature %0.1f outside range of %0.1f:%.01f"
|
||||||
|
% (self.temp, self.min_temp, self.max_temp)
|
||||||
|
)
|
||||||
|
|
||||||
|
if status[0] & 0b010000:
|
||||||
|
self.pressure = self._sample_bmp388_press() / 100.0
|
||||||
|
|
||||||
|
measured_time = self.reactor.monotonic()
|
||||||
|
self._callback(self.mcu.estimated_print_time(measured_time), self.temp)
|
||||||
|
return measured_time + REPORT_TIME
|
||||||
|
|
||||||
|
def _sample_bmp388_temp(self):
|
||||||
|
xlsb = self.read_register("TEMP_XLSB", 1)
|
||||||
|
lsb = self.read_register("TEMP_LSB", 1)
|
||||||
|
msb = self.read_register("TEMP_MSB", 1)
|
||||||
|
adc_T = (msb[0] << 16) + (lsb[0] << 8) + (xlsb[0])
|
||||||
|
|
||||||
|
partial_data1 = adc_T - self.dig["T1"]
|
||||||
|
partial_data2 = self.dig["T2"] * partial_data1
|
||||||
|
|
||||||
|
self.t_fine = partial_data2
|
||||||
|
self.t_fine += (partial_data1 * partial_data1) * self.dig["T3"]
|
||||||
|
|
||||||
|
if self.t_fine < -40.0:
|
||||||
|
self.t_fine = -40.0
|
||||||
|
|
||||||
|
if self.t_fine > 85.0:
|
||||||
|
self.t_fine = 85.0
|
||||||
|
|
||||||
|
return self.t_fine
|
||||||
|
|
||||||
|
def _sample_bmp388_press(self):
|
||||||
|
xlsb = self.read_register("PRESS_XLSB", 1)
|
||||||
|
lsb = self.read_register("PRESS_LSB", 1)
|
||||||
|
msb = self.read_register("PRESS_MSB", 1)
|
||||||
|
adc_P = (msb[0] << 16) + (lsb[0] << 8) + (xlsb[0])
|
||||||
|
|
||||||
|
partial_data1 = self.dig["P6"] * self.t_fine
|
||||||
|
partial_data2 = self.dig["P7"] * (self.t_fine * self.t_fine)
|
||||||
|
partial_data3 = self.dig["P8"]
|
||||||
|
partial_data3 *= self.t_fine * self.t_fine * self.t_fine
|
||||||
|
partial_out1 = self.dig["P5"]
|
||||||
|
partial_out1 += partial_data1 + partial_data2 + partial_data3
|
||||||
|
|
||||||
|
partial_data1 = self.dig["P2"] * self.t_fine
|
||||||
|
partial_data2 = self.dig["P3"] * (self.t_fine * self.t_fine)
|
||||||
|
partial_data3 = self.dig["P4"]
|
||||||
|
partial_data3 *= (self.t_fine * self.t_fine * self.t_fine)
|
||||||
|
partial_out2 = adc_P * (
|
||||||
|
self.dig["P1"] + partial_data1 + partial_data2 + partial_data3
|
||||||
|
)
|
||||||
|
|
||||||
|
partial_data1 = adc_P * adc_P
|
||||||
|
partial_data2 = self.dig["P9"] + (self.dig["P10"] * self.t_fine)
|
||||||
|
partial_data3 = partial_data1 * partial_data2
|
||||||
|
partial_data4 = partial_data3 + adc_P * adc_P * adc_P * self.dig["P11"]
|
||||||
|
|
||||||
|
comp_press = partial_out1 + partial_out2 + partial_data4
|
||||||
|
|
||||||
|
if comp_press < 30000:
|
||||||
|
comp_press = 30000
|
||||||
|
|
||||||
|
if comp_press > 125000:
|
||||||
|
comp_press = 125000
|
||||||
|
|
||||||
|
return comp_press
|
||||||
|
|
||||||
def _sample_bme680(self, eventtime):
|
def _sample_bme680(self, eventtime):
|
||||||
self.write_register('CTRL_HUM', self.os_hum & 0x07)
|
def data_ready(stat, run_gas):
|
||||||
meas = self.os_temp << 5 | self.os_pres << 2
|
|
||||||
self.write_register('CTRL_MEAS', [meas])
|
|
||||||
|
|
||||||
gas_wait_0 = self._calculate_gas_heater_duration(self.gas_heat_duration)
|
|
||||||
self.write_register('GAS_WAIT_0', [gas_wait_0])
|
|
||||||
res_heat_0 = self._calculate_gas_heater_resistance(self.gas_heat_temp)
|
|
||||||
self.write_register('RES_HEAT_0', [res_heat_0])
|
|
||||||
gas_config = RUN_GAS | NB_CONV_0
|
|
||||||
self.write_register('CTRL_GAS_1', [gas_config])
|
|
||||||
|
|
||||||
def data_ready(stat):
|
|
||||||
new_data = (stat & EAS_NEW_DATA)
|
new_data = (stat & EAS_NEW_DATA)
|
||||||
gas_done = not (stat & GAS_DONE)
|
gas_done = not (stat & GAS_DONE)
|
||||||
meas_done = not (stat & MEASURE_DONE)
|
meas_done = not (stat & MEASURE_DONE)
|
||||||
|
if not run_gas:
|
||||||
|
gas_done = True
|
||||||
return new_data and gas_done and meas_done
|
return new_data and gas_done and meas_done
|
||||||
|
|
||||||
|
run_gas = False
|
||||||
|
# Check VOC once a while
|
||||||
|
if self.reactor.monotonic() - self.last_gas_time > 3:
|
||||||
|
gas_config = RUN_GAS | NB_CONV_0
|
||||||
|
self.write_register('CTRL_GAS_1', [gas_config])
|
||||||
|
run_gas = True
|
||||||
|
|
||||||
# Enter forced mode
|
# Enter forced mode
|
||||||
meas = meas | MODE
|
meas = self.os_temp << 5 | self.os_pres << 2 | MODE
|
||||||
self.write_register('CTRL_MEAS', meas)
|
self.write_register('CTRL_MEAS', meas, wait=True)
|
||||||
|
max_sample_time = self.max_sample_time
|
||||||
|
if run_gas:
|
||||||
|
max_sample_time += self.gas_heat_duration / 1000
|
||||||
|
self.reactor.pause(self.reactor.monotonic() + max_sample_time)
|
||||||
try:
|
try:
|
||||||
# wait until results are ready
|
# wait until results are ready
|
||||||
status = self.read_register('EAS_STATUS_0', 1)[0]
|
status = self.read_register('EAS_STATUS_0', 1)[0]
|
||||||
while not data_ready(status):
|
while not data_ready(status, run_gas):
|
||||||
self.reactor.pause(
|
self.reactor.pause(
|
||||||
self.reactor.monotonic() + self.max_sample_time)
|
self.reactor.monotonic() + self.max_sample_time)
|
||||||
status = self.read_register('EAS_STATUS_0', 1)[0]
|
status = self.read_register('EAS_STATUS_0', 1)[0]
|
||||||
|
|
||||||
data = self.read_register('PRESSURE_MSB', 8)
|
data = self.read_register('PRESSURE_MSB', 8)
|
||||||
|
gas_data = [0, 0]
|
||||||
|
if run_gas:
|
||||||
gas_data = self.read_register('GAS_R_MSB', 2)
|
gas_data = self.read_register('GAS_R_MSB', 2)
|
||||||
except Exception:
|
except Exception:
|
||||||
logging.exception("BME680: Error reading data")
|
logging.exception("BME680: Error reading data")
|
||||||
@@ -325,6 +568,10 @@ class BME280:
|
|||||||
gas_raw = (gas_data[0] << 2) | ((gas_data[1] & 0xC0) >> 6)
|
gas_raw = (gas_data[0] << 2) | ((gas_data[1] & 0xC0) >> 6)
|
||||||
gas_range = (gas_data[1] & 0x0F)
|
gas_range = (gas_data[1] & 0x0F)
|
||||||
self.gas = self._compensate_gas(gas_raw, gas_range)
|
self.gas = self._compensate_gas(gas_raw, gas_range)
|
||||||
|
# Disable gas measurement on success
|
||||||
|
gas_config = NB_CONV_0
|
||||||
|
self.write_register('CTRL_GAS_1', [gas_config])
|
||||||
|
self.last_gas_time = self.reactor.monotonic()
|
||||||
|
|
||||||
if self.temp < self.min_temp or self.temp > self.max_temp:
|
if self.temp < self.min_temp or self.temp > self.max_temp:
|
||||||
self.printer.invoke_shutdown(
|
self.printer.invoke_shutdown(
|
||||||
@@ -334,6 +581,43 @@ class BME280:
|
|||||||
self._callback(self.mcu.estimated_print_time(measured_time), self.temp)
|
self._callback(self.mcu.estimated_print_time(measured_time), self.temp)
|
||||||
return measured_time + REPORT_TIME
|
return measured_time + REPORT_TIME
|
||||||
|
|
||||||
|
def _sample_bmp180(self, eventtime):
|
||||||
|
meas = self.chip_registers['CRV_TEMP']
|
||||||
|
self.write_register('CTRL_MEAS', meas)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.reactor.pause(self.reactor.monotonic() + .01)
|
||||||
|
data = self.read_register('REG_MSB', 2)
|
||||||
|
temp_raw = (data[0] << 8) | data[1]
|
||||||
|
except Exception:
|
||||||
|
logging.exception("BMP180: Error reading temperature")
|
||||||
|
self.temp = self.pressure = .0
|
||||||
|
return self.reactor.NEVER
|
||||||
|
|
||||||
|
meas = self.chip_registers['CRV_PRES'] | (self.os_pres << 6)
|
||||||
|
self.write_register('CTRL_MEAS', meas)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.reactor.pause(self.reactor.monotonic() + .01)
|
||||||
|
data = self.read_register('REG_MSB', 3)
|
||||||
|
pressure_raw = \
|
||||||
|
((data[0] << 16)|(data[1] << 8)|data[2]) >> (8 - self.os_pres)
|
||||||
|
except Exception:
|
||||||
|
logging.exception("BMP180: Error reading pressure")
|
||||||
|
self.temp = self.pressure = .0
|
||||||
|
return self.reactor.NEVER
|
||||||
|
|
||||||
|
self.temp = self._compensate_temp_bmp180(temp_raw)
|
||||||
|
self.pressure = self._compensate_pressure_bmp180(pressure_raw) / 100.
|
||||||
|
if self.temp < self.min_temp or self.temp > self.max_temp:
|
||||||
|
self.printer.invoke_shutdown(
|
||||||
|
"BMP180 temperature %0.1f outside range of %0.1f:%.01f"
|
||||||
|
% (self.temp, self.min_temp, self.max_temp))
|
||||||
|
measured_time = self.reactor.monotonic()
|
||||||
|
self._callback(self.mcu.estimated_print_time(measured_time), self.temp)
|
||||||
|
return measured_time + REPORT_TIME
|
||||||
|
|
||||||
|
|
||||||
def _compensate_temp(self, raw_temp):
|
def _compensate_temp(self, raw_temp):
|
||||||
dig = self.dig
|
dig = self.dig
|
||||||
var1 = ((raw_temp / 16384. - (dig['T1'] / 1024.)) * dig['T2'])
|
var1 = ((raw_temp / 16384. - (dig['T1'] / 1024.)) * dig['T2'])
|
||||||
@@ -416,7 +700,7 @@ class BME280:
|
|||||||
gas_raw - 512. + var1)
|
gas_raw - 512. + var1)
|
||||||
return gas
|
return gas
|
||||||
|
|
||||||
def _calculate_gas_heater_resistance(self, target_temp):
|
def _calc_gas_heater_resistance(self, target_temp):
|
||||||
amb_temp = self.temp
|
amb_temp = self.temp
|
||||||
heater_data = self.read_register('RES_HEAT_VAL', 3)
|
heater_data = self.read_register('RES_HEAT_VAL', 3)
|
||||||
res_heat_val = get_signed_byte(heater_data[0])
|
res_heat_val = get_signed_byte(heater_data[0])
|
||||||
@@ -431,7 +715,7 @@ class BME280:
|
|||||||
* (1. / (1. + (res_heat_val * 0.002)))) - 25))
|
* (1. / (1. + (res_heat_val * 0.002)))) - 25))
|
||||||
return int(res_heat)
|
return int(res_heat)
|
||||||
|
|
||||||
def _calculate_gas_heater_duration(self, duration_ms):
|
def _calc_gas_heater_duration(self, duration_ms):
|
||||||
if duration_ms >= 4032:
|
if duration_ms >= 4032:
|
||||||
duration_reg = 0xff
|
duration_reg = 0xff
|
||||||
else:
|
else:
|
||||||
@@ -443,24 +727,64 @@ class BME280:
|
|||||||
|
|
||||||
return duration_reg
|
return duration_reg
|
||||||
|
|
||||||
|
def _compensate_temp_bmp180(self, raw_temp):
|
||||||
|
dig = self.dig
|
||||||
|
x1 = (raw_temp - dig['AC6']) * dig['AC5'] / 32768.
|
||||||
|
x2 = dig['MC'] * 2048 / (x1 + dig['MD'])
|
||||||
|
b5 = x1 + x2
|
||||||
|
self.t_fine = b5
|
||||||
|
return (b5 + 8)/16./10.
|
||||||
|
|
||||||
|
def _compensate_pressure_bmp180(self, raw_pressure):
|
||||||
|
dig = self.dig
|
||||||
|
b5 = self.t_fine
|
||||||
|
b6 = b5 - 4000
|
||||||
|
x1 = (dig['B2'] * (b6 * b6 / 4096)) / 2048
|
||||||
|
x2 = dig['AC2'] * b6 / 2048
|
||||||
|
x3 = x1 + x2
|
||||||
|
b3 = ((int(dig['AC1'] * 4 + x3) << self.os_pres) + 2) / 4
|
||||||
|
x1 = dig['AC3'] * b6 / 8192
|
||||||
|
x2 = (dig['B1'] * (b6 * b6 / 4096)) / 65536
|
||||||
|
x3 = ((x1 + x2) + 2) / 4
|
||||||
|
b4 = dig['AC4'] * (x3 + 32768) / 32768
|
||||||
|
b7 = (raw_pressure - b3) * (50000 >> self.os_pres)
|
||||||
|
if (b7 < 0x80000000):
|
||||||
|
p = (b7 * 2) / b4
|
||||||
|
else:
|
||||||
|
p = (b7 / b4) * 2
|
||||||
|
x1 = (p / 256) * (p / 256)
|
||||||
|
x1 = (x1 * 3038) / 65536
|
||||||
|
x2 = (-7357 * p) / 65536
|
||||||
|
p = p + (x1 + x2 + 3791) / 16.
|
||||||
|
return p
|
||||||
|
|
||||||
def read_id(self):
|
def read_id(self):
|
||||||
# read chip id register
|
# read chip id register
|
||||||
regs = [BME_CHIP_ID_REG]
|
regs = [BME_CHIP_ID_REG]
|
||||||
params = self.i2c.i2c_read(regs, 1)
|
params = self.i2c.i2c_read(regs, 1)
|
||||||
return bytearray(params['response'])[0]
|
return bytearray(params['response'])[0]
|
||||||
|
|
||||||
|
def read_bmp3_id(self):
|
||||||
|
# read chip id register
|
||||||
|
regs = [BMP3_CHIP_ID_REG]
|
||||||
|
params = self.i2c.i2c_read(regs, 1)
|
||||||
|
return bytearray(params['response'])[0]
|
||||||
|
|
||||||
def read_register(self, reg_name, read_len):
|
def read_register(self, reg_name, read_len):
|
||||||
# read a single register
|
# read a single register
|
||||||
regs = [self.chip_registers[reg_name]]
|
regs = [self.chip_registers[reg_name]]
|
||||||
params = self.i2c.i2c_read(regs, read_len)
|
params = self.i2c.i2c_read(regs, read_len)
|
||||||
return bytearray(params['response'])
|
return bytearray(params['response'])
|
||||||
|
|
||||||
def write_register(self, reg_name, data):
|
def write_register(self, reg_name, data, wait = False):
|
||||||
if type(data) is not list:
|
if type(data) is not list:
|
||||||
data = [data]
|
data = [data]
|
||||||
reg = self.chip_registers[reg_name]
|
reg = self.chip_registers[reg_name]
|
||||||
data.insert(0, reg)
|
data.insert(0, reg)
|
||||||
|
if not wait:
|
||||||
self.i2c.i2c_write(data)
|
self.i2c.i2c_write(data)
|
||||||
|
else:
|
||||||
|
self.i2c.i2c_write_wait_ack(data)
|
||||||
|
|
||||||
def get_status(self, eventtime):
|
def get_status(self, eventtime):
|
||||||
data = {
|
data = {
|
||||||
|
|||||||
297
klippy/extras/bulk_sensor.py
Normal file
297
klippy/extras/bulk_sensor.py
Normal file
@@ -0,0 +1,297 @@
|
|||||||
|
# Tools for reading bulk sensor data from the mcu
|
||||||
|
#
|
||||||
|
# Copyright (C) 2020-2023 Kevin O'Connor <kevin@koconnor.net>
|
||||||
|
#
|
||||||
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
|
import logging, threading, struct
|
||||||
|
|
||||||
|
# This "bulk sensor" module facilitates the processing of sensor chip
|
||||||
|
# measurements that do not require the host to respond with low
|
||||||
|
# latency. This module helps collect these measurements into batches
|
||||||
|
# that are then processed periodically by the host code (as specified
|
||||||
|
# by BatchBulkHelper.batch_interval). It supports the collection of
|
||||||
|
# thousands of sensor measurements per second.
|
||||||
|
#
|
||||||
|
# Processing measurements in batches reduces load on the mcu, reduces
|
||||||
|
# bandwidth to/from the mcu, and reduces load on the host. It also
|
||||||
|
# makes it easier to export the raw measurements via the webhooks
|
||||||
|
# system (aka API Server).
|
||||||
|
|
||||||
|
BATCH_INTERVAL = 0.500
|
||||||
|
|
||||||
|
# Helper to process accumulated messages in periodic batches
|
||||||
|
class BatchBulkHelper:
|
||||||
|
def __init__(self, printer, batch_cb, start_cb=None, stop_cb=None,
|
||||||
|
batch_interval=BATCH_INTERVAL):
|
||||||
|
self.printer = printer
|
||||||
|
self.batch_cb = batch_cb
|
||||||
|
if start_cb is None:
|
||||||
|
start_cb = (lambda: None)
|
||||||
|
self.start_cb = start_cb
|
||||||
|
if stop_cb is None:
|
||||||
|
stop_cb = (lambda: None)
|
||||||
|
self.stop_cb = stop_cb
|
||||||
|
self.is_started = False
|
||||||
|
self.batch_interval = batch_interval
|
||||||
|
self.batch_timer = None
|
||||||
|
self.client_cbs = []
|
||||||
|
self.webhooks_start_resp = {}
|
||||||
|
# Periodic batch processing
|
||||||
|
def _start(self):
|
||||||
|
if self.is_started:
|
||||||
|
return
|
||||||
|
self.is_started = True
|
||||||
|
try:
|
||||||
|
self.start_cb()
|
||||||
|
except self.printer.command_error as e:
|
||||||
|
logging.exception("BatchBulkHelper start callback error")
|
||||||
|
self.is_started = False
|
||||||
|
del self.client_cbs[:]
|
||||||
|
raise
|
||||||
|
reactor = self.printer.get_reactor()
|
||||||
|
systime = reactor.monotonic()
|
||||||
|
waketime = systime + self.batch_interval
|
||||||
|
self.batch_timer = reactor.register_timer(self._proc_batch, waketime)
|
||||||
|
def _stop(self):
|
||||||
|
del self.client_cbs[:]
|
||||||
|
self.printer.get_reactor().unregister_timer(self.batch_timer)
|
||||||
|
self.batch_timer = None
|
||||||
|
if not self.is_started:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
self.stop_cb()
|
||||||
|
except self.printer.command_error as e:
|
||||||
|
logging.exception("BatchBulkHelper stop callback error")
|
||||||
|
del self.client_cbs[:]
|
||||||
|
self.is_started = False
|
||||||
|
if self.client_cbs:
|
||||||
|
# New client started while in process of stopping
|
||||||
|
self._start()
|
||||||
|
def _proc_batch(self, eventtime):
|
||||||
|
try:
|
||||||
|
msg = self.batch_cb(eventtime)
|
||||||
|
except self.printer.command_error as e:
|
||||||
|
logging.exception("BatchBulkHelper batch callback error")
|
||||||
|
self._stop()
|
||||||
|
return self.printer.get_reactor().NEVER
|
||||||
|
if not msg:
|
||||||
|
return eventtime + self.batch_interval
|
||||||
|
for client_cb in list(self.client_cbs):
|
||||||
|
res = client_cb(msg)
|
||||||
|
if not res:
|
||||||
|
# This client no longer needs updates - unregister it
|
||||||
|
self.client_cbs.remove(client_cb)
|
||||||
|
if not self.client_cbs:
|
||||||
|
self._stop()
|
||||||
|
return self.printer.get_reactor().NEVER
|
||||||
|
return eventtime + self.batch_interval
|
||||||
|
# Client registration
|
||||||
|
def add_client(self, client_cb):
|
||||||
|
self.client_cbs.append(client_cb)
|
||||||
|
self._start()
|
||||||
|
# Webhooks registration
|
||||||
|
def _add_api_client(self, web_request):
|
||||||
|
whbatch = BatchWebhooksClient(web_request)
|
||||||
|
self.add_client(whbatch.handle_batch)
|
||||||
|
web_request.send(self.webhooks_start_resp)
|
||||||
|
def add_mux_endpoint(self, path, key, value, webhooks_start_resp):
|
||||||
|
self.webhooks_start_resp = webhooks_start_resp
|
||||||
|
wh = self.printer.lookup_object('webhooks')
|
||||||
|
wh.register_mux_endpoint(path, key, value, self._add_api_client)
|
||||||
|
|
||||||
|
# A webhooks wrapper for use by BatchBulkHelper
|
||||||
|
class BatchWebhooksClient:
|
||||||
|
def __init__(self, web_request):
|
||||||
|
self.cconn = web_request.get_client_connection()
|
||||||
|
self.template = web_request.get_dict('response_template', {})
|
||||||
|
def handle_batch(self, msg):
|
||||||
|
if self.cconn.is_closed():
|
||||||
|
return False
|
||||||
|
tmp = dict(self.template)
|
||||||
|
tmp['params'] = msg
|
||||||
|
self.cconn.send(tmp)
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Helper class to store incoming messages in a queue
|
||||||
|
class BulkDataQueue:
|
||||||
|
def __init__(self, mcu, msg_name="sensor_bulk_data", oid=None):
|
||||||
|
# Measurement storage (accessed from background thread)
|
||||||
|
self.lock = threading.Lock()
|
||||||
|
self.raw_samples = []
|
||||||
|
# Register callback with mcu
|
||||||
|
mcu.register_response(self._handle_data, msg_name, oid)
|
||||||
|
def _handle_data(self, params):
|
||||||
|
with self.lock:
|
||||||
|
self.raw_samples.append(params)
|
||||||
|
def pull_queue(self):
|
||||||
|
with self.lock:
|
||||||
|
raw_samples = self.raw_samples
|
||||||
|
self.raw_samples = []
|
||||||
|
return raw_samples
|
||||||
|
def clear_queue(self):
|
||||||
|
self.pull_queue()
|
||||||
|
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
# Clock synchronization
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
# It is common for sensors to produce measurements at a fixed
|
||||||
|
# frequency. If the mcu can reliably obtain all of these
|
||||||
|
# measurements, then the code here can calculate a precision timestamp
|
||||||
|
# for them. That is, it can determine the actual sensor measurement
|
||||||
|
# frequency, the time of the first measurement, and thus a precise
|
||||||
|
# time for all measurements.
|
||||||
|
#
|
||||||
|
# This system works by having the mcu periodically report a precision
|
||||||
|
# timestamp along with the total number of measurements the sensor has
|
||||||
|
# taken as of that time. In brief, knowing the total number of
|
||||||
|
# measurements taken over an extended period provides an accurate
|
||||||
|
# estimate of measurement frequency, which can then also be utilized
|
||||||
|
# to determine the time of the first measurement.
|
||||||
|
|
||||||
|
# Helper class for chip clock synchronization via linear regression
|
||||||
|
class ClockSyncRegression:
|
||||||
|
def __init__(self, mcu, chip_clock_smooth, decay = 1. / 20.):
|
||||||
|
self.mcu = mcu
|
||||||
|
self.chip_clock_smooth = chip_clock_smooth
|
||||||
|
self.decay = decay
|
||||||
|
self.last_chip_clock = self.last_exp_mcu_clock = 0.
|
||||||
|
self.mcu_clock_avg = self.mcu_clock_variance = 0.
|
||||||
|
self.chip_clock_avg = self.chip_clock_covariance = 0.
|
||||||
|
def reset(self, mcu_clock, chip_clock):
|
||||||
|
self.mcu_clock_avg = self.last_mcu_clock = mcu_clock
|
||||||
|
self.chip_clock_avg = chip_clock
|
||||||
|
self.mcu_clock_variance = self.chip_clock_covariance = 0.
|
||||||
|
self.last_chip_clock = self.last_exp_mcu_clock = 0.
|
||||||
|
def update(self, mcu_clock, chip_clock):
|
||||||
|
# Update linear regression
|
||||||
|
decay = self.decay
|
||||||
|
diff_mcu_clock = mcu_clock - self.mcu_clock_avg
|
||||||
|
self.mcu_clock_avg += decay * diff_mcu_clock
|
||||||
|
self.mcu_clock_variance = (1. - decay) * (
|
||||||
|
self.mcu_clock_variance + diff_mcu_clock**2 * decay)
|
||||||
|
diff_chip_clock = chip_clock - self.chip_clock_avg
|
||||||
|
self.chip_clock_avg += decay * diff_chip_clock
|
||||||
|
self.chip_clock_covariance = (1. - decay) * (
|
||||||
|
self.chip_clock_covariance + diff_mcu_clock*diff_chip_clock*decay)
|
||||||
|
def set_last_chip_clock(self, chip_clock):
|
||||||
|
base_mcu, base_chip, inv_cfreq = self.get_clock_translation()
|
||||||
|
self.last_chip_clock = chip_clock
|
||||||
|
self.last_exp_mcu_clock = base_mcu + (chip_clock-base_chip) * inv_cfreq
|
||||||
|
def get_clock_translation(self):
|
||||||
|
inv_chip_freq = self.mcu_clock_variance / self.chip_clock_covariance
|
||||||
|
if not self.last_chip_clock:
|
||||||
|
return self.mcu_clock_avg, self.chip_clock_avg, inv_chip_freq
|
||||||
|
# Find mcu clock associated with future chip_clock
|
||||||
|
s_chip_clock = self.last_chip_clock + self.chip_clock_smooth
|
||||||
|
scdiff = s_chip_clock - self.chip_clock_avg
|
||||||
|
s_mcu_clock = self.mcu_clock_avg + scdiff * inv_chip_freq
|
||||||
|
# Calculate frequency to converge at future point
|
||||||
|
mdiff = s_mcu_clock - self.last_exp_mcu_clock
|
||||||
|
s_inv_chip_freq = mdiff / self.chip_clock_smooth
|
||||||
|
return self.last_exp_mcu_clock, self.last_chip_clock, s_inv_chip_freq
|
||||||
|
def get_time_translation(self):
|
||||||
|
base_mcu, base_chip, inv_cfreq = self.get_clock_translation()
|
||||||
|
clock_to_print_time = self.mcu.clock_to_print_time
|
||||||
|
base_time = clock_to_print_time(base_mcu)
|
||||||
|
inv_freq = clock_to_print_time(base_mcu + inv_cfreq) - base_time
|
||||||
|
return base_time, base_chip, inv_freq
|
||||||
|
|
||||||
|
MAX_BULK_MSG_SIZE = 51
|
||||||
|
|
||||||
|
# Read sensor_bulk_data and calculate timestamps for devices that take
|
||||||
|
# samples at a fixed frequency (and produce fixed data size samples).
|
||||||
|
class FixedFreqReader:
|
||||||
|
def __init__(self, mcu, chip_clock_smooth, unpack_fmt):
|
||||||
|
self.mcu = mcu
|
||||||
|
self.clock_sync = ClockSyncRegression(mcu, chip_clock_smooth)
|
||||||
|
unpack = struct.Struct(unpack_fmt)
|
||||||
|
self.unpack_from = unpack.unpack_from
|
||||||
|
self.bytes_per_sample = unpack.size
|
||||||
|
self.samples_per_block = MAX_BULK_MSG_SIZE // self.bytes_per_sample
|
||||||
|
self.last_sequence = self.max_query_duration = 0
|
||||||
|
self.last_overflows = 0
|
||||||
|
self.bulk_queue = self.oid = self.query_status_cmd = None
|
||||||
|
def setup_query_command(self, msgformat, oid, cq):
|
||||||
|
# Lookup sensor query command (that responds with sensor_bulk_status)
|
||||||
|
self.oid = oid
|
||||||
|
self.query_status_cmd = self.mcu.lookup_query_command(
|
||||||
|
msgformat, "sensor_bulk_status oid=%c clock=%u query_ticks=%u"
|
||||||
|
" next_sequence=%hu buffered=%u possible_overflows=%hu",
|
||||||
|
oid=oid, cq=cq)
|
||||||
|
# Read sensor_bulk_data messages and store in a queue
|
||||||
|
self.bulk_queue = BulkDataQueue(self.mcu, oid=oid)
|
||||||
|
def get_last_overflows(self):
|
||||||
|
return self.last_overflows
|
||||||
|
def _clear_duration_filter(self):
|
||||||
|
self.max_query_duration = 1 << 31
|
||||||
|
def note_start(self):
|
||||||
|
self.last_sequence = 0
|
||||||
|
self.last_overflows = 0
|
||||||
|
# Clear local queue (clear any stale samples from previous session)
|
||||||
|
self.bulk_queue.clear_queue()
|
||||||
|
# Set initial clock
|
||||||
|
self._clear_duration_filter()
|
||||||
|
self._update_clock(is_reset=True)
|
||||||
|
self._clear_duration_filter()
|
||||||
|
def note_end(self):
|
||||||
|
# Clear local queue (free no longer needed memory)
|
||||||
|
self.bulk_queue.clear_queue()
|
||||||
|
def _update_clock(self, is_reset=False):
|
||||||
|
params = self.query_status_cmd.send([self.oid])
|
||||||
|
mcu_clock = self.mcu.clock32_to_clock64(params['clock'])
|
||||||
|
seq_diff = (params['next_sequence'] - self.last_sequence) & 0xffff
|
||||||
|
self.last_sequence += seq_diff
|
||||||
|
buffered = params['buffered']
|
||||||
|
po_diff = (params['possible_overflows'] - self.last_overflows) & 0xffff
|
||||||
|
self.last_overflows += po_diff
|
||||||
|
duration = params['query_ticks']
|
||||||
|
if duration > self.max_query_duration:
|
||||||
|
# Skip measurement as a high query time could skew clock tracking
|
||||||
|
self.max_query_duration = max(2 * self.max_query_duration,
|
||||||
|
self.mcu.seconds_to_clock(.000005))
|
||||||
|
return
|
||||||
|
self.max_query_duration = 2 * duration
|
||||||
|
msg_count = (self.last_sequence * self.samples_per_block
|
||||||
|
+ buffered // self.bytes_per_sample)
|
||||||
|
# The "chip clock" is the message counter plus .5 for average
|
||||||
|
# inaccuracy of query responses and plus .5 for assumed offset
|
||||||
|
# of hardware processing time.
|
||||||
|
chip_clock = msg_count + 1
|
||||||
|
avg_mcu_clock = mcu_clock + duration // 2
|
||||||
|
if is_reset:
|
||||||
|
self.clock_sync.reset(avg_mcu_clock, chip_clock)
|
||||||
|
else:
|
||||||
|
self.clock_sync.update(avg_mcu_clock, chip_clock)
|
||||||
|
# Convert sensor_bulk_data responses into list of samples
|
||||||
|
def pull_samples(self):
|
||||||
|
# Query MCU for sample timing and update clock synchronization
|
||||||
|
self._update_clock()
|
||||||
|
# Pull sensor_bulk_data messages from local queue
|
||||||
|
raw_samples = self.bulk_queue.pull_queue()
|
||||||
|
if not raw_samples:
|
||||||
|
return []
|
||||||
|
# Load variables to optimize inner loop below
|
||||||
|
last_sequence = self.last_sequence
|
||||||
|
time_base, chip_base, inv_freq = self.clock_sync.get_time_translation()
|
||||||
|
unpack_from = self.unpack_from
|
||||||
|
bytes_per_sample = self.bytes_per_sample
|
||||||
|
samples_per_block = self.samples_per_block
|
||||||
|
# Process every message in raw_samples
|
||||||
|
count = seq = 0
|
||||||
|
samples = [None] * (len(raw_samples) * samples_per_block)
|
||||||
|
for params in raw_samples:
|
||||||
|
seq_diff = (params['sequence'] - last_sequence) & 0xffff
|
||||||
|
seq_diff -= (seq_diff & 0x8000) << 1
|
||||||
|
seq = last_sequence + seq_diff
|
||||||
|
msg_cdiff = seq * samples_per_block - chip_base
|
||||||
|
data = params['data']
|
||||||
|
for i in range(len(data) // bytes_per_sample):
|
||||||
|
ptime = time_base + (msg_cdiff + i) * inv_freq
|
||||||
|
udata = unpack_from(data, i * bytes_per_sample)
|
||||||
|
samples[count] = (ptime,) + udata
|
||||||
|
count += 1
|
||||||
|
self.clock_sync.set_last_chip_clock(seq * samples_per_block + i)
|
||||||
|
del samples[count:]
|
||||||
|
return samples
|
||||||
@@ -160,7 +160,7 @@ class MCU_I2C:
|
|||||||
% (self.oid, speed, addr))
|
% (self.oid, speed, addr))
|
||||||
self.cmd_queue = self.mcu.alloc_command_queue()
|
self.cmd_queue = self.mcu.alloc_command_queue()
|
||||||
self.mcu.register_config_callback(self.build_config)
|
self.mcu.register_config_callback(self.build_config)
|
||||||
self.i2c_write_cmd = self.i2c_read_cmd = self.i2c_modify_bits_cmd = None
|
self.i2c_write_cmd = self.i2c_read_cmd = None
|
||||||
def get_oid(self):
|
def get_oid(self):
|
||||||
return self.oid
|
return self.oid
|
||||||
def get_mcu(self):
|
def get_mcu(self):
|
||||||
@@ -180,9 +180,6 @@ class MCU_I2C:
|
|||||||
"i2c_read oid=%c reg=%*s read_len=%u",
|
"i2c_read oid=%c reg=%*s read_len=%u",
|
||||||
"i2c_read_response oid=%c response=%*s", oid=self.oid,
|
"i2c_read_response oid=%c response=%*s", oid=self.oid,
|
||||||
cq=self.cmd_queue)
|
cq=self.cmd_queue)
|
||||||
self.i2c_modify_bits_cmd = self.mcu.lookup_command(
|
|
||||||
"i2c_modify_bits oid=%c reg=%*s clear_set_bits=%*s",
|
|
||||||
cq=self.cmd_queue)
|
|
||||||
def i2c_write(self, data, minclock=0, reqclock=0):
|
def i2c_write(self, data, minclock=0, reqclock=0):
|
||||||
if self.i2c_write_cmd is None:
|
if self.i2c_write_cmd is None:
|
||||||
# Send setup message via mcu initialization
|
# Send setup message via mcu initialization
|
||||||
@@ -192,21 +189,11 @@ class MCU_I2C:
|
|||||||
return
|
return
|
||||||
self.i2c_write_cmd.send([self.oid, data],
|
self.i2c_write_cmd.send([self.oid, data],
|
||||||
minclock=minclock, reqclock=reqclock)
|
minclock=minclock, reqclock=reqclock)
|
||||||
|
def i2c_write_wait_ack(self, data, minclock=0, reqclock=0):
|
||||||
|
self.i2c_write_cmd.send_wait_ack([self.oid, data],
|
||||||
|
minclock=minclock, reqclock=reqclock)
|
||||||
def i2c_read(self, write, read_len):
|
def i2c_read(self, write, read_len):
|
||||||
return self.i2c_read_cmd.send([self.oid, write, read_len])
|
return self.i2c_read_cmd.send([self.oid, write, read_len])
|
||||||
def i2c_modify_bits(self, reg, clear_bits, set_bits,
|
|
||||||
minclock=0, reqclock=0):
|
|
||||||
clearset = clear_bits + set_bits
|
|
||||||
if self.i2c_modify_bits_cmd is None:
|
|
||||||
# Send setup message via mcu initialization
|
|
||||||
reg_msg = "".join(["%02x" % (x,) for x in reg])
|
|
||||||
clearset_msg = "".join(["%02x" % (x,) for x in clearset])
|
|
||||||
self.mcu.add_config_cmd(
|
|
||||||
"i2c_modify_bits oid=%d reg=%s clear_set_bits=%s" % (
|
|
||||||
self.oid, reg_msg, clearset_msg), is_init=True)
|
|
||||||
return
|
|
||||||
self.i2c_modify_bits_cmd.send([self.oid, reg, clearset],
|
|
||||||
minclock=minclock, reqclock=reqclock)
|
|
||||||
|
|
||||||
def MCU_I2C_from_config(config, default_addr=None, default_speed=100000):
|
def MCU_I2C_from_config(config, default_addr=None, default_speed=100000):
|
||||||
# Load bus parameters
|
# Load bus parameters
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Support for button detection and callbacks
|
# Support for button detection and callbacks
|
||||||
#
|
#
|
||||||
# Copyright (C) 2018 Kevin O'Connor <kevin@koconnor.net>
|
# Copyright (C) 2018-2023 Kevin O'Connor <kevin@koconnor.net>
|
||||||
#
|
#
|
||||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
import logging
|
import logging
|
||||||
@@ -57,10 +57,9 @@ class MCU_buttons:
|
|||||||
def handle_buttons_state(self, params):
|
def handle_buttons_state(self, params):
|
||||||
# Expand the message ack_count from 8-bit
|
# Expand the message ack_count from 8-bit
|
||||||
ack_count = self.ack_count
|
ack_count = self.ack_count
|
||||||
ack_diff = (ack_count - params['ack_count']) & 0xff
|
ack_diff = (params['ack_count'] - ack_count) & 0xff
|
||||||
if ack_diff & 0x80:
|
ack_diff -= (ack_diff & 0x80) << 1
|
||||||
ack_diff -= 0x100
|
msg_ack_count = ack_count + ack_diff
|
||||||
msg_ack_count = ack_count - ack_diff
|
|
||||||
# Determine new buttons
|
# Determine new buttons
|
||||||
buttons = bytearray(params['state'])
|
buttons = bytearray(params['state'])
|
||||||
new_count = msg_ack_count + len(buttons) - self.ack_count
|
new_count = msg_ack_count + len(buttons) - self.ack_count
|
||||||
@@ -70,17 +69,17 @@ class MCU_buttons:
|
|||||||
# Send ack to MCU
|
# Send ack to MCU
|
||||||
self.ack_cmd.send([self.oid, new_count])
|
self.ack_cmd.send([self.oid, new_count])
|
||||||
self.ack_count += new_count
|
self.ack_count += new_count
|
||||||
# Call self.handle_button() with this event in main thread
|
# Invoke callbacks with this event in main thread
|
||||||
for nb in new_buttons:
|
btime = params['#receive_time']
|
||||||
self.reactor.register_async_callback(
|
for button in new_buttons:
|
||||||
(lambda e, s=self, b=nb: s.handle_button(e, b)))
|
|
||||||
def handle_button(self, eventtime, button):
|
|
||||||
button ^= self.invert
|
button ^= self.invert
|
||||||
changed = button ^ self.last_button
|
changed = button ^ self.last_button
|
||||||
|
self.last_button = button
|
||||||
for mask, shift, callback in self.callbacks:
|
for mask, shift, callback in self.callbacks:
|
||||||
if changed & mask:
|
if changed & mask:
|
||||||
callback(eventtime, (button & mask) >> shift)
|
state = (button & mask) >> shift
|
||||||
self.last_button = button
|
self.reactor.register_async_callback(
|
||||||
|
(lambda et, c=callback, bt=btime, s=state: c(bt, s)))
|
||||||
|
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
@@ -105,7 +104,7 @@ class MCU_ADC_buttons:
|
|||||||
self.max_value = 0.
|
self.max_value = 0.
|
||||||
ppins = printer.lookup_object('pins')
|
ppins = printer.lookup_object('pins')
|
||||||
self.mcu_adc = ppins.setup_pin('adc', self.pin)
|
self.mcu_adc = ppins.setup_pin('adc', self.pin)
|
||||||
self.mcu_adc.setup_minmax(ADC_SAMPLE_TIME, ADC_SAMPLE_COUNT)
|
self.mcu_adc.setup_adc_sample(ADC_SAMPLE_TIME, ADC_SAMPLE_COUNT)
|
||||||
self.mcu_adc.setup_adc_callback(ADC_REPORT_TIME, self.adc_callback)
|
self.mcu_adc.setup_adc_callback(ADC_REPORT_TIME, self.adc_callback)
|
||||||
query_adc = printer.lookup_object('query_adc')
|
query_adc = printer.lookup_object('query_adc')
|
||||||
query_adc.register_adc('adc_button:' + pin.strip(), self.mcu_adc)
|
query_adc.register_adc('adc_button:' + pin.strip(), self.mcu_adc)
|
||||||
|
|||||||
@@ -62,9 +62,7 @@ class ControllerFan:
|
|||||||
self.last_on += 1
|
self.last_on += 1
|
||||||
if speed != self.last_speed:
|
if speed != self.last_speed:
|
||||||
self.last_speed = speed
|
self.last_speed = speed
|
||||||
curtime = self.printer.get_reactor().monotonic()
|
self.fan.set_speed(speed)
|
||||||
print_time = self.fan.get_mcu().estimated_print_time(curtime)
|
|
||||||
self.fan.set_speed(print_time + PIN_MIN_TIME, speed)
|
|
||||||
return eventtime + 1.
|
return eventtime + 1.
|
||||||
|
|
||||||
def load_config_prefix(config):
|
def load_config_prefix(config):
|
||||||
|
|||||||
209
klippy/extras/display/aip31068_spi.py
Normal file
209
klippy/extras/display/aip31068_spi.py
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
# Support for YHCB2004 (20x4 text) LCD displays based on AiP31068 controller
|
||||||
|
#
|
||||||
|
# Copyright (C) 2018 Kevin O'Connor <kevin@koconnor.net>
|
||||||
|
# Copyright (C) 2018 Eric Callahan <arksine.code@gmail.com>
|
||||||
|
# Copyright (C) 2021 Marc-Andre Denis <marcadenis@msn.com>
|
||||||
|
# Copyright (C) 2024 Alexander Bazarov <oss@bazarov.dev>
|
||||||
|
#
|
||||||
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
|
|
||||||
|
# This file is a modified version of hd44780_spi.py, introducing slightly
|
||||||
|
# different protocol as implemented in Marlin FW (based on
|
||||||
|
# https://github.com/red-scorp/LiquidCrystal_AIP31068 ).
|
||||||
|
# In addition, a hack is used to send 8 commands, each 9 bits, at once,
|
||||||
|
# allowing the transmission of a full 9 bytes.
|
||||||
|
# This helps avoid modifying the SW_SPI driver to handle non-8-bit data.
|
||||||
|
|
||||||
|
from .. import bus
|
||||||
|
|
||||||
|
LINE_LENGTH_DEFAULT=20
|
||||||
|
LINE_LENGTH_OPTIONS={16:16, 20:20}
|
||||||
|
|
||||||
|
TextGlyphs = { 'right_arrow': b'\x7e' }
|
||||||
|
|
||||||
|
# Each command is 9 bits long:
|
||||||
|
# 1 bit for RS (Register Select) - 0 for command, 1 for data
|
||||||
|
# 8 bits for the command/data
|
||||||
|
# Command is a bitwise OR of CMND(=opcode) and flg_CMND(=parameters) multiplied
|
||||||
|
# by 1 or 0 as En/Dis flag.
|
||||||
|
# cmd = CMND | flg_CMND.param0*0 | flg_CMND.param1*1
|
||||||
|
# or just by OR with enabled flags:
|
||||||
|
# cmd = CMND | flg_CMND.param1
|
||||||
|
class CMND:
|
||||||
|
CLR = 1 # Clear display
|
||||||
|
HOME = 2 # Return home
|
||||||
|
ENTERY_MODE = 2**2 # Entry mode set
|
||||||
|
DISPLAY = 2**3 # Display on/off control
|
||||||
|
SHIFT = 2**4 # Cursor or display shift
|
||||||
|
FUNCTION = 2**5 # Function set
|
||||||
|
CGRAM = 2**6 # Character Generator RAM
|
||||||
|
DDRAM = 2**7 # Display Data RAM
|
||||||
|
WRITE_RAM = 2**8 # Write to RAM
|
||||||
|
|
||||||
|
# Define flags for all commands:
|
||||||
|
class flg_ENTERY_MODE:
|
||||||
|
INC = 2**1 # Increment
|
||||||
|
SHIFT = 2**0 # Shift display
|
||||||
|
|
||||||
|
class flg_DISPLAY:
|
||||||
|
ON = 2**2 # Display ON
|
||||||
|
CURSOR = 2**1 # Cursor ON
|
||||||
|
BLINK = 2**0 # Blink ON
|
||||||
|
|
||||||
|
class flg_SHIFT:
|
||||||
|
WHOLE_DISPLAY = 2**3 # Shift whole display
|
||||||
|
RIGHT = 2**2 # Shift right
|
||||||
|
|
||||||
|
class flg_FUNCTION:
|
||||||
|
TWO_LINES = 2**3 # 2-line display mode
|
||||||
|
FIVE_BY_ELEVEN = 2**2 # 5x11 dot character font
|
||||||
|
|
||||||
|
class flg_CGRAM:
|
||||||
|
MASK = 0b00111111 # CGRAM address mask
|
||||||
|
|
||||||
|
class flg_DDRAM:
|
||||||
|
MASK = 0b01111111 # DDRAM address mask
|
||||||
|
|
||||||
|
class flg_WRITE_RAM:
|
||||||
|
MASK = 0b11111111 # Write RAM mask
|
||||||
|
|
||||||
|
DISPLAY_INIT_CMNDS= [
|
||||||
|
# CMND.CLR - no need as framebuffer will rewrite all
|
||||||
|
CMND.HOME, # move cursor to home (0x00)
|
||||||
|
CMND.ENTERY_MODE | flg_ENTERY_MODE.INC, # increment cursor and no shift
|
||||||
|
CMND.DISPLAY | flg_DISPLAY.ON, # keep cursor and blinking off
|
||||||
|
CMND.SHIFT | flg_SHIFT.RIGHT, # shift right cursor only
|
||||||
|
CMND.FUNCTION | flg_FUNCTION.TWO_LINES, # 2-line display mode, 5x8 dots
|
||||||
|
]
|
||||||
|
|
||||||
|
class aip31068_spi:
|
||||||
|
def __init__(self, config):
|
||||||
|
self.printer = config.get_printer()
|
||||||
|
# spi config
|
||||||
|
self.spi = bus.MCU_SPI_from_config(
|
||||||
|
config, 0x00, pin_option="latch_pin") # (config, mode, cs_name)
|
||||||
|
self.mcu = self.spi.get_mcu()
|
||||||
|
self.icons = {}
|
||||||
|
self.line_length = config.getchoice('line_length', LINE_LENGTH_OPTIONS,
|
||||||
|
LINE_LENGTH_DEFAULT)
|
||||||
|
# each controller's line is 2 lines on the display and hence twice
|
||||||
|
# line length
|
||||||
|
self.text_framebuffers = [bytearray(b' '*2*self.line_length),
|
||||||
|
bytearray(b' '*2*self.line_length)]
|
||||||
|
self.glyph_framebuffer = bytearray(64)
|
||||||
|
# all_framebuffers - list of tuples per buffer type.
|
||||||
|
# Each tuple contains:
|
||||||
|
# 1. the updated framebuffer
|
||||||
|
# 2. a copy of the old framebuffer == data on the display
|
||||||
|
# 3. the command to send to write to this buffer
|
||||||
|
# Then flush() will compare new data with data on the display
|
||||||
|
# and send only the differences to the display controller
|
||||||
|
# and update the old framebuffer with the new data
|
||||||
|
# (immutable tuple is allowed to store mutable bytearray)
|
||||||
|
self.all_framebuffers = [
|
||||||
|
# Text framebuffers
|
||||||
|
(self.text_framebuffers[0], bytearray(b'~'*2*self.line_length),
|
||||||
|
CMND.DDRAM | (flg_DDRAM.MASK & 0x00) ),
|
||||||
|
(self.text_framebuffers[1], bytearray(b'~'*2*self.line_length),
|
||||||
|
CMND.DDRAM | (flg_DDRAM.MASK & 0x40) ),
|
||||||
|
# Glyph framebuffer
|
||||||
|
(self.glyph_framebuffer, bytearray(b'~'*64),
|
||||||
|
CMND.CGRAM | (flg_CGRAM.MASK & 0x00) ) ]
|
||||||
|
@staticmethod
|
||||||
|
def encode(data, width = 9):
|
||||||
|
encoded_bytes = []
|
||||||
|
accumulator = 0 # To accumulate bits
|
||||||
|
acc_bits = 0 # Count of bits in the accumulator
|
||||||
|
for num in data:
|
||||||
|
# check that num will fit in width bits
|
||||||
|
if num >= (1 << width):
|
||||||
|
raise ValueError("Number {} does not fit in {} bits".
|
||||||
|
format(num, width))
|
||||||
|
# Shift the current number into the accumulator from the right
|
||||||
|
accumulator = (accumulator << width) | num
|
||||||
|
acc_bits += width # Update the count of bits in the accumulator
|
||||||
|
# While we have at least 8 bits, form a byte and append it
|
||||||
|
while acc_bits >= 8:
|
||||||
|
acc_bits -= 8 # Decrease bit count by 8
|
||||||
|
# Extract the 8 most significant bits to form a byte
|
||||||
|
byte = (accumulator >> acc_bits) & 0xFF
|
||||||
|
# Remove msb 8 bits from the accumulator
|
||||||
|
accumulator &= (1 << acc_bits) - 1
|
||||||
|
encoded_bytes.append(byte)
|
||||||
|
# Handle any remaining bits by padding them on the right to byte
|
||||||
|
if acc_bits > 0:
|
||||||
|
last_byte = accumulator << (8 - acc_bits)
|
||||||
|
encoded_bytes.append(last_byte)
|
||||||
|
return encoded_bytes
|
||||||
|
def send(self, data, minclock=0):
|
||||||
|
# different commands have different processing time
|
||||||
|
# to avoid timing violation pad with some fast command, e.g. ENTRY_MODE
|
||||||
|
# that has execution time of 39us (for comparison CLR is 1.53ms)
|
||||||
|
pad = CMND.ENTERY_MODE | flg_ENTERY_MODE.INC
|
||||||
|
for i in range(0, len(data), 8):
|
||||||
|
# Take a slice of 8 numbers
|
||||||
|
group = data[i:i+8]
|
||||||
|
# Pad the group if it has fewer than 8 elements
|
||||||
|
if len(group) < 8:
|
||||||
|
group.extend([pad] * (8 - len(group)))
|
||||||
|
self.spi.spi_send(self.encode(group), minclock)
|
||||||
|
def flush(self):
|
||||||
|
# Find all differences in the framebuffers and send them to the chip
|
||||||
|
for new_data, old_data, fb_cmnd in self.all_framebuffers:
|
||||||
|
if new_data == old_data:
|
||||||
|
continue
|
||||||
|
# Find the position of all changed bytes in this framebuffer
|
||||||
|
diffs = [[i, 1] for i, (n, o) in enumerate(zip(new_data, old_data))
|
||||||
|
if n != o]
|
||||||
|
# Batch together changes that are close to each other
|
||||||
|
for i in range(len(diffs)-2, -1, -1):
|
||||||
|
pos, count = diffs[i]
|
||||||
|
nextpos, nextcount = diffs[i+1]
|
||||||
|
if pos + 4 >= nextpos and nextcount < 16:
|
||||||
|
diffs[i][1] = nextcount + (nextpos - pos)
|
||||||
|
del diffs[i+1]
|
||||||
|
# Transmit changes
|
||||||
|
for pos, count in diffs:
|
||||||
|
chip_pos = pos
|
||||||
|
self.send([fb_cmnd + chip_pos])
|
||||||
|
self.send([CMND.WRITE_RAM | byte for byte in
|
||||||
|
new_data[pos:pos+count]])
|
||||||
|
old_data[:] = new_data
|
||||||
|
def init(self):
|
||||||
|
curtime = self.printer.get_reactor().monotonic()
|
||||||
|
print_time = self.mcu.estimated_print_time(curtime)
|
||||||
|
for i, cmds in enumerate(DISPLAY_INIT_CMNDS):
|
||||||
|
minclock = self.mcu.print_time_to_clock(print_time + i * .100)
|
||||||
|
self.send([cmds], minclock=minclock)
|
||||||
|
self.flush()
|
||||||
|
def write_text(self, x, y, data):
|
||||||
|
if x + len(data) > self.line_length:
|
||||||
|
data = data[:self.line_length - min(x, self.line_length)]
|
||||||
|
pos = x + ((y & 0x02) >> 1) * self.line_length
|
||||||
|
self.text_framebuffers[y & 1][pos:pos+len(data)] = data
|
||||||
|
def set_glyphs(self, glyphs):
|
||||||
|
for glyph_name, glyph_data in glyphs.items():
|
||||||
|
data = glyph_data.get('icon5x8')
|
||||||
|
if data is not None:
|
||||||
|
self.icons[glyph_name] = data
|
||||||
|
def write_glyph(self, x, y, glyph_name):
|
||||||
|
data = self.icons.get(glyph_name)
|
||||||
|
if data is not None:
|
||||||
|
slot, bits = data
|
||||||
|
self.write_text(x, y, [slot])
|
||||||
|
self.glyph_framebuffer[slot * 8:(slot + 1) * 8] = bits
|
||||||
|
return 1
|
||||||
|
char = TextGlyphs.get(glyph_name)
|
||||||
|
if char is not None:
|
||||||
|
# Draw character
|
||||||
|
self.write_text(x, y, char)
|
||||||
|
return 1
|
||||||
|
return 0
|
||||||
|
def write_graphics(self, x, y, data):
|
||||||
|
pass # this display supports only hardcoded or 8 user defined glyphs
|
||||||
|
def clear(self):
|
||||||
|
spaces = b' ' * 2*self.line_length
|
||||||
|
self.text_framebuffers[0][:] = spaces
|
||||||
|
self.text_framebuffers[1][:] = spaces
|
||||||
|
def get_dimensions(self):
|
||||||
|
return (self.line_length, 4)
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
#
|
#
|
||||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
import logging, os, ast
|
import logging, os, ast
|
||||||
from . import hd44780, hd44780_spi, st7920, uc1701, menu
|
from . import aip31068_spi, hd44780, hd44780_spi, st7920, uc1701, menu
|
||||||
|
|
||||||
# Normal time between each screen redraw
|
# Normal time between each screen redraw
|
||||||
REDRAW_TIME = 0.500
|
REDRAW_TIME = 0.500
|
||||||
@@ -17,7 +17,8 @@ LCD_chips = {
|
|||||||
'st7920': st7920.ST7920, 'emulated_st7920': st7920.EmulatedST7920,
|
'st7920': st7920.ST7920, 'emulated_st7920': st7920.EmulatedST7920,
|
||||||
'hd44780': hd44780.HD44780, 'uc1701': uc1701.UC1701,
|
'hd44780': hd44780.HD44780, 'uc1701': uc1701.UC1701,
|
||||||
'ssd1306': uc1701.SSD1306, 'sh1106': uc1701.SH1106,
|
'ssd1306': uc1701.SSD1306, 'sh1106': uc1701.SH1106,
|
||||||
'hd44780_spi': hd44780_spi.hd44780_spi
|
'hd44780_spi': hd44780_spi.hd44780_spi,
|
||||||
|
'aip31068_spi':aip31068_spi.aip31068_spi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Storage of [display_template my_template] config sections
|
# Storage of [display_template my_template] config sections
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import logging
|
|||||||
|
|
||||||
BACKGROUND_PRIORITY_CLOCK = 0x7fffffff00000000
|
BACKGROUND_PRIORITY_CLOCK = 0x7fffffff00000000
|
||||||
LINE_LENGTH_DEFAULT=20
|
LINE_LENGTH_DEFAULT=20
|
||||||
LINE_LENGTH_OPTIONS={16:16, 20:20}
|
LINE_LENGTH_OPTIONS=[16, 20]
|
||||||
|
|
||||||
TextGlyphs = { 'right_arrow': b'\x7e' }
|
TextGlyphs = { 'right_arrow': b'\x7e' }
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import logging
|
|||||||
from .. import bus
|
from .. import bus
|
||||||
|
|
||||||
LINE_LENGTH_DEFAULT=20
|
LINE_LENGTH_DEFAULT=20
|
||||||
LINE_LENGTH_OPTIONS={16:16, 20:20}
|
LINE_LENGTH_OPTIONS=[16, 20]
|
||||||
|
|
||||||
TextGlyphs = { 'right_arrow': b'\x7e' }
|
TextGlyphs = { 'right_arrow': b'\x7e' }
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ class MenuKeys:
|
|||||||
# Register rotary encoder
|
# Register rotary encoder
|
||||||
encoder_pins = config.get('encoder_pins', None)
|
encoder_pins = config.get('encoder_pins', None)
|
||||||
encoder_steps_per_detent = config.getchoice('encoder_steps_per_detent',
|
encoder_steps_per_detent = config.getchoice('encoder_steps_per_detent',
|
||||||
{2: 2, 4: 4}, 4)
|
[2, 4], 4)
|
||||||
if encoder_pins is not None:
|
if encoder_pins is not None:
|
||||||
try:
|
try:
|
||||||
pin1, pin2 = encoder_pins.split(',')
|
pin1, pin2 = encoder_pins.split(',')
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
# Support for "dotstar" leds
|
# Support for "dotstar" leds
|
||||||
#
|
#
|
||||||
# Copyright (C) 2019-2022 Kevin O'Connor <kevin@koconnor.net>
|
# Copyright (C) 2019-2024 Kevin O'Connor <kevin@koconnor.net>
|
||||||
#
|
#
|
||||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
from . import bus
|
from . import bus, led
|
||||||
|
|
||||||
BACKGROUND_PRIORITY_CLOCK = 0x7fffffff00000000
|
BACKGROUND_PRIORITY_CLOCK = 0x7fffffff00000000
|
||||||
|
|
||||||
class PrinterDotstar:
|
class PrinterDotstar:
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
self.printer = printer = config.get_printer()
|
self.printer = printer = config.get_printer()
|
||||||
name = config.get_name().split()[1]
|
|
||||||
# Configure a software spi bus
|
# Configure a software spi bus
|
||||||
ppins = printer.lookup_object('pins')
|
ppins = printer.lookup_object('pins')
|
||||||
data_pin_params = ppins.lookup_pin(config.get('data_pin'))
|
data_pin_params = ppins.lookup_pin(config.get('data_pin'))
|
||||||
@@ -23,8 +22,7 @@ class PrinterDotstar:
|
|||||||
self.spi = bus.MCU_SPI(mcu, None, None, 0, 500000, sw_spi_pins)
|
self.spi = bus.MCU_SPI(mcu, None, None, 0, 500000, sw_spi_pins)
|
||||||
# Initialize color data
|
# Initialize color data
|
||||||
self.chain_count = config.getint('chain_count', 1, minval=1)
|
self.chain_count = config.getint('chain_count', 1, minval=1)
|
||||||
pled = printer.load_object(config, "led")
|
self.led_helper = led.LEDHelper(config, self.update_leds,
|
||||||
self.led_helper = pled.setup_helper(config, self.update_leds,
|
|
||||||
self.chain_count)
|
self.chain_count)
|
||||||
self.prev_data = None
|
self.prev_data = None
|
||||||
# Register commands
|
# Register commands
|
||||||
|
|||||||
@@ -191,7 +191,6 @@ class EndstopPhases:
|
|||||||
def generate_stats(self, stepper_name, phase_calc):
|
def generate_stats(self, stepper_name, phase_calc):
|
||||||
phase_history = phase_calc.phase_history
|
phase_history = phase_calc.phase_history
|
||||||
wph = phase_history + phase_history
|
wph = phase_history + phase_history
|
||||||
count = sum(phase_history)
|
|
||||||
phases = len(phase_history)
|
phases = len(phase_history)
|
||||||
half_phases = phases // 2
|
half_phases = phases // 2
|
||||||
res = []
|
res = []
|
||||||
|
|||||||
133
klippy/extras/error_mcu.py
Normal file
133
klippy/extras/error_mcu.py
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
# More verbose information on micro-controller errors
|
||||||
|
#
|
||||||
|
# Copyright (C) 2024 Kevin O'Connor <kevin@koconnor.net>
|
||||||
|
#
|
||||||
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
|
import logging
|
||||||
|
|
||||||
|
message_shutdown = """
|
||||||
|
Once the underlying issue is corrected, use the
|
||||||
|
"FIRMWARE_RESTART" command to reset the firmware, reload the
|
||||||
|
config, and restart the host software.
|
||||||
|
Printer is shutdown
|
||||||
|
"""
|
||||||
|
|
||||||
|
message_protocol_error1 = """
|
||||||
|
This is frequently caused by running an older version of the
|
||||||
|
firmware on the MCU(s). Fix by recompiling and flashing the
|
||||||
|
firmware.
|
||||||
|
"""
|
||||||
|
|
||||||
|
message_protocol_error2 = """
|
||||||
|
Once the underlying issue is corrected, use the "RESTART"
|
||||||
|
command to reload the config and restart the host software.
|
||||||
|
"""
|
||||||
|
|
||||||
|
message_mcu_connect_error = """
|
||||||
|
Once the underlying issue is corrected, use the
|
||||||
|
"FIRMWARE_RESTART" command to reset the firmware, reload the
|
||||||
|
config, and restart the host software.
|
||||||
|
Error configuring printer
|
||||||
|
"""
|
||||||
|
|
||||||
|
Common_MCU_errors = {
|
||||||
|
("Timer too close",): """
|
||||||
|
This often indicates the host computer is overloaded. Check
|
||||||
|
for other processes consuming excessive CPU time, high swap
|
||||||
|
usage, disk errors, overheating, unstable voltage, or
|
||||||
|
similar system problems on the host computer.""",
|
||||||
|
("Missed scheduling of next ",): """
|
||||||
|
This is generally indicative of an intermittent
|
||||||
|
communication failure between micro-controller and host.""",
|
||||||
|
("ADC out of range",): """
|
||||||
|
This generally occurs when a heater temperature exceeds
|
||||||
|
its configured min_temp or max_temp.""",
|
||||||
|
("Rescheduled timer in the past", "Stepper too far in past"): """
|
||||||
|
This generally occurs when the micro-controller has been
|
||||||
|
requested to step at a rate higher than it is capable of
|
||||||
|
obtaining.""",
|
||||||
|
("Command request",): """
|
||||||
|
This generally occurs in response to an M112 G-Code command
|
||||||
|
or in response to an internal error in the host software.""",
|
||||||
|
}
|
||||||
|
|
||||||
|
def error_hint(msg):
|
||||||
|
for prefixes, help_msg in Common_MCU_errors.items():
|
||||||
|
for prefix in prefixes:
|
||||||
|
if msg.startswith(prefix):
|
||||||
|
return help_msg
|
||||||
|
return ""
|
||||||
|
|
||||||
|
class PrinterMCUError:
|
||||||
|
def __init__(self, config):
|
||||||
|
self.printer = config.get_printer()
|
||||||
|
self.clarify_callbacks = {}
|
||||||
|
self.printer.register_event_handler("klippy:notify_mcu_shutdown",
|
||||||
|
self._handle_notify_mcu_shutdown)
|
||||||
|
self.printer.register_event_handler("klippy:notify_mcu_error",
|
||||||
|
self._handle_notify_mcu_error)
|
||||||
|
def add_clarify(self, msg, callback):
|
||||||
|
self.clarify_callbacks.setdefault(msg, []).append(callback)
|
||||||
|
def _check_mcu_shutdown(self, msg, details):
|
||||||
|
mcu_name = details['mcu']
|
||||||
|
mcu_msg = details['reason']
|
||||||
|
event_type = details['event_type']
|
||||||
|
prefix = "MCU '%s' shutdown: " % (mcu_name,)
|
||||||
|
if event_type == 'is_shutdown':
|
||||||
|
prefix = "Previous MCU '%s' shutdown: " % (mcu_name,)
|
||||||
|
# Lookup generic hint
|
||||||
|
hint = error_hint(mcu_msg)
|
||||||
|
# Add per instance help
|
||||||
|
clarify = [cb(msg, details)
|
||||||
|
for cb in self.clarify_callbacks.get(mcu_msg, [])]
|
||||||
|
clarify = [cm for cm in clarify if cm is not None]
|
||||||
|
clarify_msg = ""
|
||||||
|
if clarify:
|
||||||
|
clarify_msg = "\n".join(["", ""] + clarify + [""])
|
||||||
|
# Update error message
|
||||||
|
newmsg = "%s%s%s%s%s" % (prefix, mcu_msg, clarify_msg,
|
||||||
|
hint, message_shutdown)
|
||||||
|
self.printer.update_error_msg(msg, newmsg)
|
||||||
|
def _handle_notify_mcu_shutdown(self, msg, details):
|
||||||
|
if msg == "MCU shutdown":
|
||||||
|
self._check_mcu_shutdown(msg, details)
|
||||||
|
else:
|
||||||
|
self.printer.update_error_msg(msg, "%s%s" % (msg, message_shutdown))
|
||||||
|
def _check_protocol_error(self, msg, details):
|
||||||
|
host_version = self.printer.start_args['software_version']
|
||||||
|
msg_update = []
|
||||||
|
msg_updated = []
|
||||||
|
for mcu_name, mcu in self.printer.lookup_objects('mcu'):
|
||||||
|
try:
|
||||||
|
mcu_version = mcu.get_status()['mcu_version']
|
||||||
|
except:
|
||||||
|
logging.exception("Unable to retrieve mcu_version from mcu")
|
||||||
|
continue
|
||||||
|
if mcu_version != host_version:
|
||||||
|
msg_update.append("%s: Current version %s"
|
||||||
|
% (mcu_name.split()[-1], mcu_version))
|
||||||
|
else:
|
||||||
|
msg_updated.append("%s: Current version %s"
|
||||||
|
% (mcu_name.split()[-1], mcu_version))
|
||||||
|
if not msg_update:
|
||||||
|
msg_update.append("<none>")
|
||||||
|
if not msg_updated:
|
||||||
|
msg_updated.append("<none>")
|
||||||
|
newmsg = ["MCU Protocol error",
|
||||||
|
message_protocol_error1,
|
||||||
|
"Your Klipper version is: %s" % (host_version,),
|
||||||
|
"MCU(s) which should be updated:"]
|
||||||
|
newmsg += msg_update + ["Up-to-date MCU(s):"] + msg_updated
|
||||||
|
newmsg += [message_protocol_error2, details['error']]
|
||||||
|
self.printer.update_error_msg(msg, "\n".join(newmsg))
|
||||||
|
def _check_mcu_connect_error(self, msg, details):
|
||||||
|
newmsg = "%s%s" % (details['error'], message_mcu_connect_error)
|
||||||
|
self.printer.update_error_msg(msg, newmsg)
|
||||||
|
def _handle_notify_mcu_error(self, msg, details):
|
||||||
|
if msg == "Protocol error":
|
||||||
|
self._check_protocol_error(msg, details)
|
||||||
|
elif msg == "MCU error during connect":
|
||||||
|
self._check_mcu_connect_error(msg, details)
|
||||||
|
|
||||||
|
def load_config(config):
|
||||||
|
return PrinterMCUError(config)
|
||||||
@@ -234,7 +234,7 @@ class ExcludeObject:
|
|||||||
|
|
||||||
elif current:
|
elif current:
|
||||||
if not self.current_object:
|
if not self.current_object:
|
||||||
gcmd.respond_error('There is no current object to cancel')
|
raise self.gcode.error('There is no current object to cancel')
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self._exclude_object(self.current_object)
|
self._exclude_object(self.current_object)
|
||||||
|
|||||||
@@ -1,17 +1,14 @@
|
|||||||
# Printer cooling fan
|
# Printer cooling fan
|
||||||
#
|
#
|
||||||
# Copyright (C) 2016-2020 Kevin O'Connor <kevin@koconnor.net>
|
# Copyright (C) 2016-2024 Kevin O'Connor <kevin@koconnor.net>
|
||||||
#
|
#
|
||||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
from . import pulse_counter
|
from . import pulse_counter, output_pin
|
||||||
|
|
||||||
FAN_MIN_TIME = 0.100
|
|
||||||
|
|
||||||
class Fan:
|
class Fan:
|
||||||
def __init__(self, config, default_shutdown_speed=0.):
|
def __init__(self, config, default_shutdown_speed=0.):
|
||||||
self.printer = config.get_printer()
|
self.printer = config.get_printer()
|
||||||
self.last_fan_value = 0.
|
self.last_fan_value = self.last_req_value = 0.
|
||||||
self.last_fan_time = 0.
|
|
||||||
# Read config
|
# Read config
|
||||||
self.max_power = config.getfloat('max_power', 1., above=0., maxval=1.)
|
self.max_power = config.getfloat('max_power', 1., above=0., maxval=1.)
|
||||||
self.kick_start_time = config.getfloat('kick_start_time', 0.1,
|
self.kick_start_time = config.getfloat('kick_start_time', 0.1,
|
||||||
@@ -36,6 +33,10 @@ class Fan:
|
|||||||
self.enable_pin = ppins.setup_pin('digital_out', enable_pin)
|
self.enable_pin = ppins.setup_pin('digital_out', enable_pin)
|
||||||
self.enable_pin.setup_max_duration(0.)
|
self.enable_pin.setup_max_duration(0.)
|
||||||
|
|
||||||
|
# Create gcode request queue
|
||||||
|
self.gcrq = output_pin.GCodeRequestQueue(config, self.mcu_fan.get_mcu(),
|
||||||
|
self._apply_speed)
|
||||||
|
|
||||||
# Setup tachometer
|
# Setup tachometer
|
||||||
self.tachometer = FanTachometer(config)
|
self.tachometer = FanTachometer(config)
|
||||||
|
|
||||||
@@ -45,37 +46,37 @@ class Fan:
|
|||||||
|
|
||||||
def get_mcu(self):
|
def get_mcu(self):
|
||||||
return self.mcu_fan.get_mcu()
|
return self.mcu_fan.get_mcu()
|
||||||
def set_speed(self, print_time, value):
|
def _apply_speed(self, print_time, value):
|
||||||
if value < self.off_below:
|
if value < self.off_below:
|
||||||
value = 0.
|
value = 0.
|
||||||
value = max(0., min(self.max_power, value * self.max_power))
|
value = max(0., min(self.max_power, value * self.max_power))
|
||||||
if value == self.last_fan_value:
|
if value == self.last_fan_value:
|
||||||
return
|
return "discard", 0.
|
||||||
print_time = max(self.last_fan_time + FAN_MIN_TIME, print_time)
|
|
||||||
if self.enable_pin:
|
if self.enable_pin:
|
||||||
if value > 0 and self.last_fan_value == 0:
|
if value > 0 and self.last_fan_value == 0:
|
||||||
self.enable_pin.set_digital(print_time, 1)
|
self.enable_pin.set_digital(print_time, 1)
|
||||||
elif value == 0 and self.last_fan_value > 0:
|
elif value == 0 and self.last_fan_value > 0:
|
||||||
self.enable_pin.set_digital(print_time, 0)
|
self.enable_pin.set_digital(print_time, 0)
|
||||||
if (value and value < self.max_power and self.kick_start_time
|
if (value and self.kick_start_time
|
||||||
and (not self.last_fan_value or value - self.last_fan_value > .5)):
|
and (not self.last_fan_value or value - self.last_fan_value > .5)):
|
||||||
# Run fan at full speed for specified kick_start_time
|
# Run fan at full speed for specified kick_start_time
|
||||||
|
self.last_req_value = value
|
||||||
|
self.last_fan_value = self.max_power
|
||||||
self.mcu_fan.set_pwm(print_time, self.max_power)
|
self.mcu_fan.set_pwm(print_time, self.max_power)
|
||||||
print_time += self.kick_start_time
|
return "delay", self.kick_start_time
|
||||||
|
self.last_fan_value = self.last_req_value = value
|
||||||
self.mcu_fan.set_pwm(print_time, value)
|
self.mcu_fan.set_pwm(print_time, value)
|
||||||
self.last_fan_time = print_time
|
def set_speed(self, value, print_time=None):
|
||||||
self.last_fan_value = value
|
self.gcrq.send_async_request(value, print_time)
|
||||||
def set_speed_from_command(self, value):
|
def set_speed_from_command(self, value):
|
||||||
toolhead = self.printer.lookup_object('toolhead')
|
self.gcrq.queue_gcode_request(value)
|
||||||
toolhead.register_lookahead_callback((lambda pt:
|
|
||||||
self.set_speed(pt, value)))
|
|
||||||
def _handle_request_restart(self, print_time):
|
def _handle_request_restart(self, print_time):
|
||||||
self.set_speed(print_time, 0.)
|
self.set_speed(0., print_time)
|
||||||
|
|
||||||
def get_status(self, eventtime):
|
def get_status(self, eventtime):
|
||||||
tachometer_status = self.tachometer.get_status(eventtime)
|
tachometer_status = self.tachometer.get_status(eventtime)
|
||||||
return {
|
return {
|
||||||
'speed': self.last_fan_value,
|
'speed': self.last_req_value,
|
||||||
'rpm': tachometer_status['rpm'],
|
'rpm': tachometer_status['rpm'],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
# Support fans that are controlled by gcode
|
# Support fans that are controlled by gcode
|
||||||
#
|
#
|
||||||
# Copyright (C) 2016-2020 Kevin O'Connor <kevin@koconnor.net>
|
# Copyright (C) 2016-2024 Kevin O'Connor <kevin@koconnor.net>
|
||||||
#
|
#
|
||||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
from . import fan
|
import logging
|
||||||
|
from . import fan, output_pin
|
||||||
|
|
||||||
class PrinterFanGeneric:
|
class PrinterFanGeneric:
|
||||||
cmd_SET_FAN_SPEED_help = "Sets the speed of a fan"
|
cmd_SET_FAN_SPEED_help = "Sets the speed of a fan"
|
||||||
@@ -12,6 +13,9 @@ class PrinterFanGeneric:
|
|||||||
self.fan = fan.Fan(config, default_shutdown_speed=0.)
|
self.fan = fan.Fan(config, default_shutdown_speed=0.)
|
||||||
self.fan_name = config.get_name().split()[-1]
|
self.fan_name = config.get_name().split()[-1]
|
||||||
|
|
||||||
|
# Template handling
|
||||||
|
self.template_eval = output_pin.lookup_template_eval(config)
|
||||||
|
|
||||||
gcode = self.printer.lookup_object("gcode")
|
gcode = self.printer.lookup_object("gcode")
|
||||||
gcode.register_mux_command("SET_FAN_SPEED", "FAN",
|
gcode.register_mux_command("SET_FAN_SPEED", "FAN",
|
||||||
self.fan_name,
|
self.fan_name,
|
||||||
@@ -20,8 +24,21 @@ class PrinterFanGeneric:
|
|||||||
|
|
||||||
def get_status(self, eventtime):
|
def get_status(self, eventtime):
|
||||||
return self.fan.get_status(eventtime)
|
return self.fan.get_status(eventtime)
|
||||||
|
def _template_update(self, text):
|
||||||
|
try:
|
||||||
|
value = float(text)
|
||||||
|
except ValueError as e:
|
||||||
|
logging.exception("fan_generic template render error")
|
||||||
|
self.fan.set_speed(value)
|
||||||
def cmd_SET_FAN_SPEED(self, gcmd):
|
def cmd_SET_FAN_SPEED(self, gcmd):
|
||||||
speed = gcmd.get_float('SPEED', 0.)
|
speed = gcmd.get_float('SPEED', None, 0.)
|
||||||
|
template = gcmd.get('TEMPLATE', None)
|
||||||
|
if (speed is None) == (template is None):
|
||||||
|
raise gcmd.error("SET_FAN_SPEED must specify SPEED or TEMPLATE")
|
||||||
|
# Check for template setting
|
||||||
|
if template is not None:
|
||||||
|
self.template_eval.set_template(gcmd, self._template_update)
|
||||||
|
return
|
||||||
self.fan.set_speed_from_command(speed)
|
self.fan.set_speed_from_command(speed)
|
||||||
|
|
||||||
def load_config_prefix(config):
|
def load_config_prefix(config):
|
||||||
|
|||||||
@@ -86,11 +86,13 @@ class ForceMove:
|
|||||||
0., 0., 0., axis_r, 0., 0., 0., cruise_v, accel)
|
0., 0., 0., axis_r, 0., 0., 0., cruise_v, accel)
|
||||||
print_time = print_time + accel_t + cruise_t + accel_t
|
print_time = print_time + accel_t + cruise_t + accel_t
|
||||||
stepper.generate_steps(print_time)
|
stepper.generate_steps(print_time)
|
||||||
self.trapq_finalize_moves(self.trapq, print_time + 99999.9)
|
self.trapq_finalize_moves(self.trapq, print_time + 99999.9,
|
||||||
|
print_time + 99999.9)
|
||||||
stepper.set_trapq(prev_trapq)
|
stepper.set_trapq(prev_trapq)
|
||||||
stepper.set_stepper_kinematics(prev_sk)
|
stepper.set_stepper_kinematics(prev_sk)
|
||||||
toolhead.note_kinematic_activity(print_time)
|
toolhead.note_mcu_movequeue_activity(print_time)
|
||||||
toolhead.dwell(accel_t + cruise_t + accel_t)
|
toolhead.dwell(accel_t + cruise_t + accel_t)
|
||||||
|
toolhead.flush_step_generation()
|
||||||
def _lookup_stepper(self, gcmd):
|
def _lookup_stepper(self, gcmd):
|
||||||
name = gcmd.get('STEPPER')
|
name = gcmd.get('STEPPER')
|
||||||
if name not in self.steppers:
|
if name not in self.steppers:
|
||||||
@@ -129,8 +131,12 @@ class ForceMove:
|
|||||||
x = gcmd.get_float('X', curpos[0])
|
x = gcmd.get_float('X', curpos[0])
|
||||||
y = gcmd.get_float('Y', curpos[1])
|
y = gcmd.get_float('Y', curpos[1])
|
||||||
z = gcmd.get_float('Z', curpos[2])
|
z = gcmd.get_float('Z', curpos[2])
|
||||||
logging.info("SET_KINEMATIC_POSITION pos=%.3f,%.3f,%.3f", x, y, z)
|
clear = gcmd.get('CLEAR', '').lower()
|
||||||
toolhead.set_position([x, y, z, curpos[3]], homing_axes=(0, 1, 2))
|
clear_axes = "".join([a for a in "xyz" if a in clear])
|
||||||
|
logging.info("SET_KINEMATIC_POSITION pos=%.3f,%.3f,%.3f clear=%s",
|
||||||
|
x, y, z, clear_axes)
|
||||||
|
toolhead.set_position([x, y, z, curpos[3]], homing_axes="xyz")
|
||||||
|
toolhead.get_kinematics().clear_homing_state(clear_axes)
|
||||||
|
|
||||||
def load_config(config):
|
def load_config(config):
|
||||||
return ForceMove(config)
|
return ForceMove(config)
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user