Initial commit
This commit is contained in:
234
docs/BinaryFileTransferProtocol.md
Normal file
234
docs/BinaryFileTransferProtocol.md
Normal file
@ -0,0 +1,234 @@
|
||||
# Marlin Binary File Transfer (BFT)
|
||||
Marlin is capable of transferring binary data to the internal storage (SD card) via serial when built with `BINARY_FILE_TRANSFER` enabled. The following is a description of the binary protocol that must be used to conduct transfers once the printer is in binary mode after running `M28 B1`.
|
||||
|
||||
## Data Endianness
|
||||
All data structures are **little-endian**! This means that when constructing the packets with multi-byte values, the lower bits are packed first. For example, each packet should start with a 16-bit start token with the value of `0xB5AD`. The data itself should start with a value of `0xAD` followed by `0xB5` etc.
|
||||
|
||||
An example Connection SYNC packet, which is only a header and has no payload:
|
||||
```
|
||||
S S P P P H
|
||||
t y r a a e
|
||||
a n o c y a
|
||||
r c t k l d
|
||||
t o e o e
|
||||
c t a r
|
||||
o d
|
||||
l t C
|
||||
y l S
|
||||
p e
|
||||
e n
|
||||
---- -- - - ---- ----
|
||||
ADB5 00 0 1 0000 0103
|
||||
```
|
||||
|
||||
## Packet Header
|
||||
|
||||
```
|
||||
0 1 2 3
|
||||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
+-------------------------------+---------------+-------+-------+
|
||||
| Start Token (0xB5AD) | Sync Number | Prot- | Pack- |
|
||||
| | | ocol | et |
|
||||
| | | ID | Type |
|
||||
+-------------------------------+---------------+-------+-------+
|
||||
| Payload Length | Header Checksum |
|
||||
+-------------------------------+-------------------------------+
|
||||
```
|
||||
|
||||
| Field | Width | Description |
|
||||
|-----------------|---------|---|
|
||||
| Start Token | 16 bits | Each packet must start with the 16-bit value `0xB5AD`. |
|
||||
| Sync Number | 8 bits | Synchronization value, each packet after sync should increment this value by 1. |
|
||||
| Protocol ID | 4 bits | Protocol ID. `0` for Connection Control, `1` for Transfer. See Below. |
|
||||
| Packet Type | 4 bits | Packet Type ID. Depends on the Protocol ID, see below. |
|
||||
| Payload Length | 16 bits | Length of payload data. If this value is greater than 0, a packet payload will follow the header. |
|
||||
| Header Checksum | 16 bits | 16-bit Fletchers checksum of the header data excluding the Start Token |
|
||||
|
||||
## Packet Payload
|
||||
If the Payload Length field of the header is non-zero, payload data is expected to follow.
|
||||
|
||||
```
|
||||
0 1 2 3
|
||||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
+-------------------------------+-------------------------------+
|
||||
| Payload Data ... |
|
||||
+-------------------------------+-------------------------------+
|
||||
| ... | Packet Checksum |
|
||||
+-------------------------------+-------------------------------+
|
||||
```
|
||||
|
||||
| Field | Width | Description |
|
||||
|-----------------|------------------------|---|
|
||||
| Payload Data | Payload Length bytes | Payload data. This should be no longer than the buffer length reported by the Connection SYNC operation. |
|
||||
| Packet Checksum | 16 bits | 16-bit Fletchers checksum of the header and payload, including the Header Checksum, but excluding the Start Token. |
|
||||
|
||||
## Fletchers Checksum
|
||||
Data packets require a checksum for the header and a checksum for the entire packet if the packet has a payload. In both cases the checksum does not include the first two bytes of the packet, the Start Token.
|
||||
|
||||
A simple example implementation:
|
||||
```c++
|
||||
uint16_t cs = 0;
|
||||
for (size_t i = 2; i<packet.size(); i++) {
|
||||
uint8_t cslow = (((cs & 0xFF) + packet[i]) % 255);
|
||||
cs = ((((cs >> 8) + cslow) % 255) << 8) | cslow;
|
||||
}
|
||||
```
|
||||
|
||||
## General Responses
|
||||
|
||||
### ok
|
||||
All packets **except** the SYNC Packet (see below) are acknowledged by an `ok<SYNC>` message. This acknowledgement only signifies the client has received the packet and that the header was well formed. An `ok` acknowledgement does not signify successful operation in cases where the client also sends detailed response messages (see details on packet types below). Most notably, with the current implementation the client will still respond `ok` when a client sends multiple packets with the same Sync Number, but will not send the proper response or any errors.
|
||||
|
||||
**NOTE**: The `ok` acknowledgement is sent before any packet type specific output. The `SYNC` value should match the Sync Number of the last packet sent, and the next packet sent should use a Sync Number of this value + 1.
|
||||
|
||||
Example:
|
||||
```
|
||||
ok1
|
||||
```
|
||||
|
||||
### rs
|
||||
In the case of a packet being sent out of order, where the Sync Number is not the previous Sync Number + 1, an `rs<SYNC>` message will be sent with the last Sync Number received.
|
||||
|
||||
Example:
|
||||
```
|
||||
rs1
|
||||
```
|
||||
|
||||
## Connection Control (`Protocol ID` 0)
|
||||
`Protocol ID` 0 packets control the binary connection itself. There are only 2 types:
|
||||
|
||||
| Packet Type | Name | Description |
|
||||
|---|---|---|
|
||||
| 1 | SYNC | Synchronize host and client and get connection info. |
|
||||
| 2 | CLOSE | Close the binary connection and switch back to ASCII. |
|
||||
|
||||
### SYNC Packet
|
||||
A SYNC packet should be the first packet sent by a host after enabling binary mode. On success, a sync response will be sent.
|
||||
|
||||
**Note**: This is the only packet that is not acknowledged with an `ok` response.
|
||||
|
||||
Returns a sync response:
|
||||
```
|
||||
ss<SYNC>,<BUFFER_SIZE>,<VERSION_MAJOR>.<VERSION_MINOR>.<VERSION_PATCH>
|
||||
```
|
||||
|
||||
| Value | Description |
|
||||
|---|---|
|
||||
| SYNC | The current Sync Number, this should be used in the next packet sent and incremented by 1 for each packet sent after. |
|
||||
| BUFFER_SIZE | The client buffer size. Packet Payload Length must not exceed this value. |
|
||||
| VERSION_MAJOR | The major version number of the client Marlin BFT protocol, e.g., `0`. |
|
||||
| VERSION_MINOR | The minor version number of the client Marlin BFT protocol, e.g., `1`. |
|
||||
| VERSION_PATCH | The patch version number of the client Marlin BFT protocol, e.g., `0`. |
|
||||
|
||||
Example response:
|
||||
```
|
||||
ss0,96,0.1.0
|
||||
```
|
||||
|
||||
### CLOSE Packet
|
||||
A CLOSE packet should be the last packet sent by a host. On success, the client will switch back to ASCII mode.
|
||||
|
||||
## Transfer Control (`Protocol ID` 1)
|
||||
`Protocol ID` 1 packets control the file transfers performed over the connection:
|
||||
|
||||
| Packet Type | Name | Description |
|
||||
|---|---|---|
|
||||
| 0 | QUERY | Query the client protocol details and compression parameters. |
|
||||
| 1 | OPEN | Open a file for writing and begin accepting data to transfer. |
|
||||
| 2 | CLOSE | Finish writing and close the current file. |
|
||||
| 3 | WRITE | Write data to an open file. |
|
||||
| 4 | ABORT | Abort file transfer. |
|
||||
|
||||
### QUERY Packet
|
||||
A QUERY packet should be the second packet sent by a host, after a SYNC packet. On success a query response will be sent in addition to an `ok<sync>` acknowledgement.
|
||||
|
||||
Returns a query response:
|
||||
```
|
||||
PFT:version:<VERSION_MAJOR>.<VERSION_MINOR>.<VERSION_PATCH>:compression:<COMPRESSION_ALGO>(,<COMPRESSION_PARAMS>)
|
||||
```
|
||||
|
||||
| Value | Description |
|
||||
|---|---|
|
||||
| VERSION_MAJOR | The major version number of the client Marlin BFT protocol, e.g., `0`. |
|
||||
| VERSION_MINOR | The minor version number of the client Marlin BFT protocol, e.g., `1`. |
|
||||
| VERSION_PATCH | The patch version number of the client Marlin BFT protocol, e.g., `0`. |
|
||||
| COMPRESSION_ALGO | Compression algorithm. Currently either `heatshrink` or `none` |
|
||||
| COMPRESSION_PARAMS | Compression parameters, separated by commas. Currently, if `COMPRESSION_AGLO` is heatshrink, this will be the window size and lookahead size. |
|
||||
|
||||
Example response:
|
||||
```
|
||||
PFT:version:0.1.0:compression:heatshrink,8,4
|
||||
```
|
||||
|
||||
### OPEN Packet
|
||||
Opens a file for writing. The filename and other options are specified in the Packet Payload. The filename can be a long filename if the firmware is compiled with support, however the entire Packet Payload must not be longer than the buffer length returned by the SYNC Packet. The filename value must include a null terminator.
|
||||
|
||||
Payload:
|
||||
```
|
||||
0 1 2 3
|
||||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
+---------------+---------------+-------------------------------+
|
||||
| Dummy | Compression | Filename |
|
||||
+---------------------------------------------------------------|
|
||||
| ... |
|
||||
+-------------------------------+-------------------------------+
|
||||
| ... | NULL (0x0) | Packet Checksum |
|
||||
+-------------------------------+-------------------------------+
|
||||
```
|
||||
|
||||
| Field | Width | Description |
|
||||
|-----------------|------------------------|---|
|
||||
| Dummy | 8 bits | A boolean value indicating if this file transfer should be actually carried out or not. If `1`, the client will respond as if the file is opened and accept data transfer, but no data will be written. |
|
||||
| Compression | 8 bits | A boolean value indicating if the data to be transferred will be compressed using the algorithm and parameters returned in the QUERY Packet. |
|
||||
| Filename | ... | A filename including a null terminator byte. |
|
||||
| Packet Checksum | 16 bits | 16-bit Fletchers checksum of the header and payload, including the Header Checksum, but excluding the Start Token. |
|
||||
|
||||
Responses:
|
||||
|
||||
| Response | Description |
|
||||
|---|---|
|
||||
| `PFT:success` | File opened and ready for write. |
|
||||
| `PFT:fail` | The client couldn't open the file. |
|
||||
| `PFT:busy` | The file is already open. |
|
||||
|
||||
### CLOSE Packet
|
||||
Closes the currently open file.
|
||||
|
||||
Responses:
|
||||
|
||||
| Response | Description |
|
||||
|---|---|
|
||||
| `PFT:success` | Buffer flushed and file closed. |
|
||||
| `PFT:ioerror` | Client storage device failure. |
|
||||
| `PFT:invalid` | No file open. |
|
||||
|
||||
### WRITE Packet
|
||||
Writes payload data to the currently open file. If the file was opened with Compression set to 1, the data will be decompressed first. Payload Length must not exceed the buffer size returned by the SYNC Packet.
|
||||
|
||||
Responses:
|
||||
On success, an `ok<SYNC>` response will be sent. On error, an `ok<SYNC>` response will be followed by an error response:
|
||||
|
||||
| Response | Description |
|
||||
|---|---|
|
||||
| `PFT:ioerror` | Client storage device failure. |
|
||||
| `PFT:invalid` | No file open. |
|
||||
|
||||
### ABORT Packet
|
||||
Closes the currently open file and remove it.
|
||||
|
||||
Responses:
|
||||
|
||||
| Response | Description |
|
||||
|---|---|
|
||||
| `PFT:success` | Transfer aborted, file removed. |
|
||||
|
||||
## Typical Usage
|
||||
|
||||
1. Send ASCII command `M28 B1` to initiate Binary Transfer mode.
|
||||
2. Send Connection SYNC Packet, record Sync Number and Buffer Size.
|
||||
3. Send Transfer QUERY Packet, using Sync Number from above. Record compression algorithm and parameters.
|
||||
4. Send Transfer OPEN Packet, using last Sync Number + 1, filename and compression options. If error, send Connection CLOSE Packet and abort.
|
||||
5. Send Transfer Write Packets, using last Sync Number + 1 with the file data. The Payload Length must not exceed the Buffer Size reported in step 2. On error, send a Transfer ABORT Packet, followed by a Connection CLOSE Packet and then abort transfer.
|
||||
6. Send Transfer CLOSE Packet, using last Sync Number + 1.
|
||||
7. Send Connection CLOSE Packet, using last Sync Number + 1.
|
||||
8. Client is now in ASCII mode, transfer complete
|
269
docs/Bresenham.md
Normal file
269
docs/Bresenham.md
Normal file
@ -0,0 +1,269 @@
|
||||
On the Bresenham algorithm as implemented by Marlin:
|
||||
(Taken from (https://www.cs.helsinki.fi/group/goa/mallinnus/lines/bresenh.html)
|
||||
|
||||
The basic Bresenham algorithm:
|
||||
|
||||
Consider drawing a line on a raster grid where we restrict the allowable slopes of the line to the range 0 <= m <= 1
|
||||
|
||||
If we further restrict the line-drawing routine so that it always increments x as it plots, it becomes clear that, having plotted a point at (x,y), the routine has a severely limited range of options as to where it may put the next point on the line:
|
||||
|
||||
- It may plot the point (x+1,y), or:
|
||||
- It may plot the point (x+1,y+1).
|
||||
|
||||
So, working in the first positive octant of the plane, line drawing becomes a matter of deciding between two possibilities at each step.
|
||||
|
||||
We can draw a diagram of the situation which the plotting program finds itself in having plotted (x,y).
|
||||
|
||||
```
|
||||
y+1 +--------------*
|
||||
| /
|
||||
| /
|
||||
| /
|
||||
| /
|
||||
| y+e+m*--------+-
|
||||
| /| ^ |
|
||||
| / | |m |
|
||||
| / | | |
|
||||
| / | v |
|
||||
| y+e*----|----- |m+ε
|
||||
| /| | ^ |
|
||||
| / | | |ε |
|
||||
| / | | | |
|
||||
|/ | | v v
|
||||
y *----+----+----------+--
|
||||
x x+1
|
||||
```
|
||||
|
||||
In plotting (x,y) the line drawing routine will, in general, be making a compromise between what it would like to draw and what the resolution of the stepper motors actually allows it to draw. Usually the plotted point (x,y) will be in error, the actual, mathematical point on the line will not be addressable on the pixel grid. So we associate an error, ε, with each y ordinate, the real value of y should be y+ε . This error will range from -0.5 to just under +0.5.
|
||||
|
||||
In moving from x to x+1 we increase the value of the true (mathematical) y-ordinate by an amount equal to the slope of the line, m. We will choose to plot (x+1,y) if the difference between this new value and y is less than 0.5
|
||||
|
||||
```
|
||||
y + ε + m < y + 0.5
|
||||
```
|
||||
|
||||
Otherwise we will plot (x+1,y+1). It should be clear that by so doing we minimize the total error between the mathematical line segment and what actually gets drawn on the display.
|
||||
|
||||
The error resulting from this new point can now be written back into ε, this will allow us to repeat the whole process for the next point along the line, at x+2.
|
||||
|
||||
The new value of error can adopt one of two possible values, depending on what new point is plotted. If (x+1,y) is chosen, the new value of error is given by:
|
||||
|
||||
```
|
||||
ε[new] = (y + ε + m) - y
|
||||
```
|
||||
|
||||
Otherwise, it is:
|
||||
|
||||
```
|
||||
ε[new] = (y + ε + m) - (y + 1)
|
||||
```
|
||||
|
||||
This gives an algorithm for a DDA which avoids rounding operations, instead using the error variable ε to control plotting:
|
||||
|
||||
```
|
||||
ε = 0, y = y[1]
|
||||
for x = x1 to x2 do
|
||||
Plot point at (x,y)
|
||||
if (ε + m < 0.5)
|
||||
ε = ε + m
|
||||
else
|
||||
y = y + 1, ε = ε + m - 1
|
||||
endif
|
||||
endfor
|
||||
```
|
||||
|
||||
This still employs floating point values. Consider, however, what happens if we multiply across both sides of the plotting test by Δx and then by 2:
|
||||
|
||||
```
|
||||
ε + m < 0.5
|
||||
ε + Δy/Δx < 0.5
|
||||
2.ε.Δx + 2.Δy < Δx
|
||||
```
|
||||
|
||||
All quantities in this inequality are now integral.
|
||||
|
||||
Substitute ε' for ε.Δx . The test becomes:
|
||||
|
||||
```
|
||||
2.(ε' + Δy) < Δx
|
||||
```
|
||||
|
||||
This gives an integer-only test for deciding which point to plot.
|
||||
|
||||
The update rules for the error on each step may also be cast into ε' form. Consider the floating-point versions of the update rules:
|
||||
|
||||
```
|
||||
ε = ε + m
|
||||
ε = ε + m - 1
|
||||
```
|
||||
|
||||
Multiplying through by Δx yields:
|
||||
|
||||
```
|
||||
ε.Δx = ε.Δx + Δy
|
||||
ε.Δx = ε.Δx + Δy - Δx
|
||||
```
|
||||
|
||||
Which is in ε' form:
|
||||
|
||||
```
|
||||
ε' = ε' + Δy
|
||||
ε' = ε' + Δy - Δx
|
||||
```
|
||||
|
||||
Using this new ``error'' value, ε' with the new test and update equations gives Bresenham's integer-only line drawing algorithm:
|
||||
|
||||
```
|
||||
ε' = 0, y = y[1]
|
||||
for x = x1 to x2 do
|
||||
Plot point at (x,y)
|
||||
if (2.(ε' + Δy) < Δx)
|
||||
ε' = ε' + Δy
|
||||
else
|
||||
y = y + 1, ε' = ε' + Δy - Δx
|
||||
endif
|
||||
endfor
|
||||
```
|
||||
|
||||
It is a Integer only algorithm - hence efficient (fast). And the Multiplication by 2 can be implemented by left-shift. 0 <= m <= 1
|
||||
|
||||
### Oversampling Bresenham algorithm:
|
||||
|
||||
Even if Bresenham does NOT lose steps at all, and also does NOT accumulate error, there is a concept i would call "time resolution" - If the quotient between major axis and minor axis (major axis means, in this context, the axis that must create more step pulses compared with the other ones, including the extruder)
|
||||
|
||||
Well, if the quotient result is not an integer, then Bresenham, at some points in the movement of the major axis, must decide that it has to move the minor axis. It is done in such way that after the full major axis movement has executed, it also has executed the full movements of the minor axis. And the minor axis steps were properly distributed evenly along the major axis movement. So good so far.
|
||||
|
||||
But, as said, Bresenham has "discrete" decision points: It can only decide to move (or not to move) minor axis exactly at the moment the major axis moves. And that is not the ideal point (in time) usually.
|
||||
|
||||
With slow movements that are composed of a similar, but not equal number of steps in all axes, the problem worsens, as the decision points are distributed very sparsely, and there are large delays between those decision points.
|
||||
|
||||
It is nearly trivial to extend Bresenham to "oversample" in that situation: Let's do it:
|
||||
|
||||
Assume that we want to use Bresenham to calculate when to step (move in Y direction), but we want to do it, not for integer increments of the X axis, rather than, for fractional increments.
|
||||
|
||||
Let's call 'r' the count of subdivisions we want to split an integer increment of the X axis:
|
||||
|
||||
```
|
||||
m = Δy/Δx = increment of y due to the increment of x1
|
||||
```
|
||||
|
||||
Every time we move `1/r` in the X axis, then the Y axis should move `m.1/r`
|
||||
|
||||
But, as stated previously, due to the resolution of the screen, there are 2 choices:
|
||||
|
||||
- It may plot the point `(x+(1/r),y)`, or:
|
||||
- It may plot the point `(x+(1/r),y+1)`.
|
||||
|
||||
That decision must be made keeping the error as small as possible:
|
||||
|
||||
```
|
||||
-0.5 < ε < 0.5
|
||||
```
|
||||
|
||||
So, the proper condition for that decision is (`m/r` is the increment of y due to the fractional `1/r` increment of `x`):
|
||||
|
||||
```
|
||||
y + ε + m/r < y + 0.5
|
||||
ε + m/r < 0.5 [1]
|
||||
```
|
||||
|
||||
Once we did the decision, then the error update conditions are:
|
||||
|
||||
Decision A:
|
||||
```
|
||||
ε[new] = y + ε + m/r - y
|
||||
ε[new] = ε + m/r [2]
|
||||
```
|
||||
|
||||
Decision B:
|
||||
```
|
||||
ε[new] = y + ε + m/r - (y+1)
|
||||
ε[new] = ε + m/r - 1 [3]
|
||||
```
|
||||
|
||||
We replace m in the decision inequality [1] by its definition:
|
||||
|
||||
```
|
||||
ε + m/r < 0.5
|
||||
ε + ΔY/(ΔX*r) < 0.5
|
||||
```
|
||||
|
||||
Then, we multiply it by `2.Δx.r`:
|
||||
|
||||
```
|
||||
ε + ΔY/(ΔX*r) < 0.5
|
||||
2.ΔX.ε.r + 2.ΔY < ΔX.r
|
||||
```
|
||||
|
||||
If we define `ε' = 2.ε.ΔX.r` then it becomes:
|
||||
|
||||
```
|
||||
ε' + 2.ΔY < ΔX.r [4]
|
||||
```
|
||||
|
||||
Now, for the update rules, we multiply by 2.r.ΔX
|
||||
|
||||
```
|
||||
ε[new] = ε + m/r
|
||||
2.r.ΔX.ε[new] = 2.r.ΔX.ε + 2.r.ΔX.ΔY/ΔX/r
|
||||
2.r.ΔX.ε[new] = 2.r.ΔX.ε + 2.ΔY
|
||||
ε'[new] = ε' + 2.ΔY [6]
|
||||
```
|
||||
|
||||
```
|
||||
ε[new] = ε + m/r - 1
|
||||
2.r.ΔX.ε[new] = 2.r.ΔX.ε + 2.r.ΔX.ΔY/ΔX/r - 1 . 2.r.ΔX
|
||||
2.r.ΔX.ε[new] = 2.r.ΔX.ε + 2.ΔY - 2.ΔX.r
|
||||
ε'[new] = ε' + 2.ΔY - 2.ΔX.r [7]
|
||||
```
|
||||
|
||||
All expressions, the decision inequality [4], and the update equations [5] and [6] are integer valued. There is no need for floating point arithmetic at all.
|
||||
|
||||
Summarizing:
|
||||
|
||||
```
|
||||
Condition equation:
|
||||
|
||||
ε' + 2.ΔY < ΔX.r [4]
|
||||
|
||||
Error update equations:
|
||||
|
||||
ε'[new] = ε' + 2.ΔY [6]
|
||||
|
||||
ε'[new] = ε' + 2.ΔY - 2.ΔX.r [7]
|
||||
```
|
||||
|
||||
This can be implemented in C++ as:
|
||||
|
||||
```cpp
|
||||
class OversampledBresenham {
|
||||
private:
|
||||
long divisor, // stepsX
|
||||
dividend, // stepsY
|
||||
advanceDivisor, // advanceX
|
||||
advanceDividend; // advanceY
|
||||
int errorAccumulator; // Error accumulator
|
||||
|
||||
public:
|
||||
unsigned int ticker;
|
||||
|
||||
OversampledBresenhan(const long& inDividend, const long& inDivisor, int rate) {
|
||||
ticker = 0;
|
||||
divisor = inDivisor;
|
||||
dividend = inDividend;
|
||||
advanceDivisor = divisor * 2 * rate;
|
||||
advanceDividend = dividend * 2;
|
||||
errorAccumulator = -divisor * rate;
|
||||
}
|
||||
|
||||
bool tick() {
|
||||
errorAccumulator += advanceDividend;
|
||||
const bool over = errorAccumulator >= 0;
|
||||
if (over) {
|
||||
ticker++;
|
||||
errorAccumulator -= advanceDivisor;
|
||||
}
|
||||
return over;
|
||||
}
|
||||
};
|
||||
```
|
19
docs/ConfigEmbedding.md
Normal file
19
docs/ConfigEmbedding.md
Normal file
@ -0,0 +1,19 @@
|
||||
# Configuration Embedding
|
||||
|
||||
Starting with version 2.0.9.3, Marlin can automatically extract the configuration used to generate the firmware and store it in the firmware binary. This is enabled by defining `CONFIGURATION_EMBEDDING` in `Configuration_adv.h`.
|
||||
|
||||
## How it's done
|
||||
At the start of the PlatformIO build process, we create an embedded configuration by extracting all active options from the Configuration files and writing them out as JSON to `marlin_config.json`, which also includes specific build information (like the git revision, the build date, and some version information. The JSON file is then compressed in a ZIP archive called `.pio/build/mc.zip` which is converted into a C array and stored in a C++ file called `mc.h` which is included in the build.
|
||||
|
||||
## Extracting configurations from a Marlin binary
|
||||
To get the configuration out of a binary firmware, you'll need a non-write-protected SD card inserted into the printer while running the firmware.
|
||||
Send the command `M503 C` to write the file `mc.zip` to the SD card. Copy the file to your computer, ideally in the same folder as the Marlin repository.
|
||||
|
||||
Run the following commands to extract and apply the configuration:
|
||||
```
|
||||
$ git checkout -f
|
||||
$ unzip mc.zip
|
||||
$ python buildroot/share/PlatformIO/scripts/mc-apply.py
|
||||
```
|
||||
|
||||
This will attempt to update the configuration files to match the settings used for the original build. It will also dump the git reference used to build the code (which may be accessible if the firmware was built from the main repository. As a fallback it also includes the `STRING_DISTRIBUTION_DATE` which is unlikely to be modified in a fork).
|
137
docs/Cutter.md
Normal file
137
docs/Cutter.md
Normal file
@ -0,0 +1,137 @@
|
||||
### Introduction
|
||||
|
||||
With Marlin version 2.0.9.x or higher, Laser improvements were introduced that enhance inline functionality. Previously the inline feature option was not operational without enabling and recompiling the source. Also with inline enabled the base features are not functional. With v2.0.9.x new functionality is added which allows the standard and inline modes to be G-Code selectable and also compatible with each other. Additionally an experimental dynamic mode is also available. Spindle operational features are available with defines and recompiling.
|
||||
|
||||
### Architecture
|
||||
|
||||
Laser selectable feature capability is defined through 4 global mode flags within G-code ,laser/spindle, planner and stepper routines. The default mode maintains the standard laser function. G-Codes are received, processed and parsed to determine what mode to set through M3, M4 and M5 commands. When the inline mode parameter set is detected, laser power processing will be driven through the planner and stepper routines. Handling of the initial power values and settings are performed by G-Code parsing and the laser/spindle routines.
|
||||
|
||||
Inline power feeds from the block->inline_power variable into the planner's laser.power when in continuous power mode. Further power adjustment will be applied if the laser power trap feature is active otherwise laser.power is used as set in the stepper for the entire block. When laser power trap is active the power levels are step incremented during acceleration and step decremented during deceleration.
|
||||
|
||||
Two additional power sets are fed in the planner by features laser power sync and laser fan power sync. Both of these power sets are done with planner sync block bit flags. With laser power sync, when the bit flag is matched the global block laser.power value is updated from laser/spindle standard M3 S-Value power sets. For laser fan sync, power values are updated into the planner block->fan_speed[i] variable from fan G-Code S-Value sets.
|
||||
|
||||
With dynamic inline power mode, F-Value feedrate sets are processed with cutter.calc_dynamic_power() and fed into the planner laser.power value.
|
||||
|
||||
Irrespective of what laser power value source is used, the final laser output pin is always updated using the laser/spindle code. Specifically the apply_power(value) call is used to set the laser or spindle output. This call permits safe power control in the event that a sensor fault occurs.
|
||||
|
||||
Note: Spindle operation is not selectable with G-Codes at this time.
|
||||
|
||||
The following flow charts depict the flow control logic for spindle and laser operations in the code base.
|
||||
|
||||
#### Spindle Mode Logic:
|
||||
|
||||
┌──────────┐ ┌───────────┐ ┌───────────┐
|
||||
│M3 S-Value│ │Dir !same ?│ │Stepper │
|
||||
│Spindle │ │stop & wait│ │processes │
|
||||
┌──┤Clockwise ├──┤ & start ├──┤moves │
|
||||
┌──────┐ │ │ │ │spindle │ │ │
|
||||
│G-Code│ │ └──────────┘ └───────────┘ └───────────┘
|
||||
│Send ├──┤ ┌──────────┐ ┌───────────┐ ┌───────────┐
|
||||
└──────┘ │ │M4 S-Value│ │Dir !same ?│ │Stepper │
|
||||
├──┤Spindle ├──┤stop & wait├──┤processes │
|
||||
│ │Counter │ │& start │ │moves │
|
||||
│ │Clockwise │ │spindle │ │ │
|
||||
│ └──────────┘ └───────────┘ └───────────┘
|
||||
│ ┌──────────┐ ┌────────┐
|
||||
│ │M5 │ │Wait for│
|
||||
│ │Spindle ├──┤move & │
|
||||
└──┤Stop │ │disable │
|
||||
└──────────┘ └────────┘
|
||||
┌──────────┐ ┌──────────┐
|
||||
Sensors─────┤Fault ├──┤Disable │
|
||||
└──────────┘ │power │
|
||||
└──────────┘
|
||||
|
||||
#### Laser Mode Logic:
|
||||
|
||||
┌──────────┐ ┌─────────────┐ ┌───────────┐
|
||||
│M3,M4,M5 I│ │Set power │ │Stepper │
|
||||
┌──┤Standard ├──┤Immediately &├──┤processes │
|
||||
│ │Default │ │wait for move│ │moves │
|
||||
│ │ │ │completion │ │ │
|
||||
│ └──────────┘ └─────────────┘ └───────────┘
|
||||
│ ┌──────────┐ ┌───────────┐ ┌───────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌───────────┐
|
||||
┌──────┐ │ │M3 I │ │G0,G1,G2,G4│ │Planner │ │Planner │ │Planner fan │ │Planner │ │Stepper │
|
||||
│G-Code│ │ │Continuous│ │M3 receive │ │sets block │ │sync power ?│ │sync power ?│ │trap power ?│ │uses block │
|
||||
│Send ├──┼──┤Inline ├──┤power from ├──┤power using├──┤process M3 ├──┤process fan ├──┤adjusts for ├──┤values to │
|
||||
└──────┘ │ │ │ │S-Value │ │Gx S-Value │ │power inline│ │power inline│ │accel/decel │ │apply power│
|
||||
│ └──────────┘ └───────────┘ └───────────┘ └────────────┘ └────────────┘ └────────────┘ └───────────┘
|
||||
│ ┌──────────┐ ┌───────────┐ ┌────────────────┐ ┌───────────┐
|
||||
│ │M4 I │ │Gx F-Value │ │Planner │ │Stepper │
|
||||
│ │Dynamic │ │set power │ │Calc & set block│ │uses block │
|
||||
└──┤Inline ├──┤or use ├──┤block power ├──┤values to │
|
||||
│ │ │default │ │using F-Value │ │apply power│
|
||||
└──────────┘ └───────────┘ └────────────────┘ └───────────┘
|
||||
┌──────────┐ ┌──────────┐
|
||||
Sensors─────┤Fault ├──┤Disable │
|
||||
└──────────┘ │Power │
|
||||
└──────────┘
|
||||
|
||||
<!-- https://asciiflow.com/#/ -->
|
||||
|
||||
### Continuous Inline Trap Power Calculations
|
||||
|
||||
When LASER_FEATURE and LASER_POWER_TRAP are defined, planner calculations are performed and applied to the incoming laser power S-Value. The power will be factored and distributed across trapezoid acceleration and deceleration movements.
|
||||
|
||||
When the laser.power > 0
|
||||
|
||||
We set a minimum power if defined in SPEED_POWER_MIN it's fed into the planner block as laser_power_floor.
|
||||
|
||||
A reduced entry laser power factor is based on the entry step rate to cruise step rate ratio for acceleration.
|
||||
|
||||
block entry laser power = laser power * ( entry step rate / cruise step rate )
|
||||
|
||||
The initial power will be set to no less than the laser_power_floor or the initial power calculation.
|
||||
|
||||
The reduced final power factor is based on the final step rate to cruise step rate ratio for deceleration.
|
||||
|
||||
block exit laser power = laser power * ( exit step rate / cruise step rate )
|
||||
|
||||
Once the entry and exit power values are determined, the values are divided into step increments to be applied in the stepper.
|
||||
|
||||
trap step power incr_decr = ( cruize power - entry_exit ) / accel_decel_steps
|
||||
|
||||
The trap steps are incremented or decremented during each accel or decel step until the block is complete.
|
||||
Step power is either cumulatively added or subtracted during trapeziod ramp progressions.
|
||||
|
||||
#### Planner Code:
|
||||
|
||||
```
|
||||
if (block->laser.power > 0) {
|
||||
NOLESS(block->laser.power, laser_power_floor);
|
||||
block->laser.trap_ramp_active_pwr = (block->laser.power - laser_power_floor) * (initial_rate / float(block->nominal_rate)) + laser_power_floor;
|
||||
block->laser.trap_ramp_entry_incr = (block->laser.power - block->laser.trap_ramp_active_pwr) / accelerate_steps;
|
||||
float laser_pwr = block->laser.power * (final_rate / float(block->nominal_rate));
|
||||
NOLESS(laser_pwr, laser_power_floor);
|
||||
block->laser.trap_ramp_exit_decr = (block->laser.power - laser_pwr) / decelerate_steps;
|
||||
```
|
||||
|
||||
#### Stepper Code:
|
||||
|
||||
```
|
||||
if (current_block->laser.trap_ramp_entry_incr > 0) {
|
||||
cutter.apply_power(current_block->laser.trap_ramp_active_pwr);
|
||||
current_block->laser.trap_ramp_active_pwr += current_block->laser.trap_ramp_entry_incr;
|
||||
```
|
||||
|
||||
```
|
||||
if (current_block->laser.trap_ramp_exit_decr > 0) {
|
||||
current_block->laser.trap_ramp_active_pwr -= current_block->laser.trap_ramp_exit_decr;
|
||||
cutter.apply_power(current_block->laser.trap_ramp_active_pwr);
|
||||
```
|
||||
|
||||
### Dynamic Inline Calculations
|
||||
|
||||
Dynamic mode will calculate laser power based on the F-Value feedrate. The method uses bit shifting to set a power level from 0 to 255. It's simple and fast and we can use a scaler to shift the laser power output to center on a given power level.
|
||||
|
||||
#### Spindle/Laser Code:
|
||||
|
||||
```
|
||||
// Dynamic mode rate calculation
|
||||
static inline uint8_t calc_dynamic_power() {
|
||||
if (feedrate_mm_m > 65535) return 255; // Too fast, go always on
|
||||
uint16_t rate = uint16_t(feedrate_mm_m); // 16 bits from the G-code parser float input
|
||||
rate >>= 8; // Take the G-code input e.g. F40000 and shift off the lower bits to get an OCR value from 1-255
|
||||
return uint8_t(rate);
|
||||
}
|
||||
```
|
59
docs/Queue.md
Normal file
59
docs/Queue.md
Normal file
@ -0,0 +1,59 @@
|
||||
# Marlin's command queue concept
|
||||
|
||||
Marlin Firmware processes G-code commands as they arrive from multiple sources, including the SD card and one or more serial ports such as USB-connected hosts, WiFi, Bluetooth, and so on.
|
||||
|
||||
Marlin is also continuously processing the commands at the front of the queue, converting them into signals for many physical actuators such as motors, heaters, lasers, and RGB LEDs.
|
||||
|
||||
The firmware needs to maintain continuity and timing so the command senders remain unblocked, while still performing physical movements and other actions in real-time, respecting the physical limits of stepper motors and other peripherals.
|
||||
|
||||
To keep things flowing Marlin feeds a single queue of G-code commands from all inputs, inserting them in the order received. Movement commands immediately go into the Planner Buffer, if there is room. The buffering of a move is considered the completion of the command, so if a non-movement command has to occur after a move is done, and not just after a move is buffered, then there has to be an `M400` to wait for the Planner Buffer to finish.
|
||||
|
||||
Whenever the command queue gets full the sender needs to wait for space to open up, and the host may need to re-send the last command again. Marlin does some handshaking to keep the host informed during a print job, described below.
|
||||
|
||||
An opposite problem called "planner starvation" occurs when Marlin receives many short and fast moves in a row so the Planner Buffer gets completed very quickly. In this case the host can't send commands fast enough to prevent the Planner Buffer from emptying out. Planner starvation causes obvious stuttering and is commonly seen on overloaded deltabots during small curves. Marlin has strategies to mitigate this issue, but sometimes a model has to be re-sliced (or the G-code has to be post-processed with Arc Welder) just to stay within the machine's inherent limits.
|
||||
|
||||
Here's a basic flowchart of Marlin command processing:
|
||||
```
|
||||
+------+ Marlin's GCodeQueue
|
||||
| | +--------------------------------------+ +-----------+
|
||||
| Host | | SerialState RingBuffer | | |
|
||||
| | Marlin | NUM_SERIAL BUF_SIZE | | Marlin |
|
||||
+--+---+ R/TX_BUFFER_SIZE | +---+ +------------------+ | | |
|
||||
| +------------+ | | | | | | | G-Code |
|
||||
| | | | | | | MAX_CMD_SIZE +-+-----> processor |
|
||||
| | Platform | | | | On EOL | +--------------+ | r_pos | |
|
||||
+-------------> serial's +-----------> +--------> | G-code | | | +-----------+
|
||||
| buffer | | | | w_pos | | command | | |
|
||||
| | | | | | | line | | |
|
||||
+------------+ | +---+ | +--------------+ | |
|
||||
| Line buffer | x BUF_SIZE | |
|
||||
| | | |
|
||||
| | | |
|
||||
| | | |
|
||||
| | | |
|
||||
| +------------------+ |
|
||||
| |
|
||||
| |
|
||||
| |
|
||||
+--------------------------------------+
|
||||
```
|
||||
|
||||
Marlin is a single-threaded application with a main `loop()` that manages the command queue and an `idle()` routine that manages the hardware. The command queue is handled in two stages:
|
||||
1. The `idle()` routine reads all inputs and attempts to enqueue any completed command lines.
|
||||
2. The main `loop()` gets the command at the front the G-code queue (if any) and runs it. Each G-code command blocks the main loop, preventing the queue from advancing until it returns. To keep essential tasks and the UI running, any commands that run a long process need to call `idle()` frequently.
|
||||
|
||||
## Synchronization
|
||||
|
||||
To maintain synchronization Marlin replies "`ok`" to the host as soon as the command has been enqueued. This lets the host know that it can send another command, and well-behaved hosts will wait for this message. With `ADVANCED_OK` enabled the `ok` message includes extra information (such as the number of slots left in the queue).
|
||||
|
||||
If no data is available on the serial buffer, Marlin can be configured to periodically send a "`wait`" message to the host. This was the only method of "host keepalive" provided in Marlin 1.0, but today the better options are `HOST_KEEPALIVE` and `ADVANCED_OK`.
|
||||
|
||||
## Limitation of the design
|
||||
|
||||
Some limitations to the design are evident:
|
||||
1. Whenever the G-code processor is busy processing a command, the G-code queue cannot advance.
|
||||
2. A long command like `G29` causes commands to pile up and to fill the queue, making the host wait.
|
||||
3. Each serial input requires a buffer large enough for a complete G-code line. This is set by `MAX_CMD_SIZE` with a default value of 96.
|
||||
4. Since serial buffer sizes are likely used as ring buffers themselves, as an optimization their sizes must be a power of 2 (64 or 128 bytes recommended).
|
||||
5. If a host sends too much G-code at once it can saturate the `GCodeQueue`. This doesn't do anything to improve the processing rate of Marlin since only one command can be dispatched per loop iteration.
|
||||
6. With the previous point in mind, it's clear that the longstanding wisdom that you don't need a large `BUF_SIZE` is not just apocryphal. The default value of 4 is typically just fine for a single serial port. (And, if you decide to send a `G25` to pause the machine, the wait will be much shorter!)
|
73
docs/Serial.md
Normal file
73
docs/Serial.md
Normal file
@ -0,0 +1,73 @@
|
||||
# Serial port architecture in Marlin
|
||||
|
||||
Marlin is targeting a plethora of different CPU architectures and platforms. Each of these platforms has its own serial interface.
|
||||
While many provide a Arduino-like Serial class, it's not all of them, and the differences in the existing API create a very complex brain teaser for writing code that works more or less on each platform.
|
||||
|
||||
Moreover, many platform have intrinsic needs about serial port (like forwarding the output on multiple serial port, providing a *serial-like* telnet server, mixing USB-based serial port with SD card emulation) that are difficult to handle cleanly in the other platform serial logic.
|
||||
|
||||
Starting with version 2.0.8, Marlin provides a common interface for its serial needs.
|
||||
|
||||
## Common interface
|
||||
|
||||
This interface is declared in `Marlin/src/core/serial_base.h`
|
||||
Any implementation will need to follow this interface for being used transparently in Marlin's codebase.
|
||||
|
||||
The implementation was written to prioritize performance over abstraction, so the base interface is not using virtual inheritance to avoid the cost of virtual dispatching while calling methods.
|
||||
Instead, the Curiously Recurring Template Pattern (**CRTP**) is used so that, upon compilation, the interface abstraction does not incur a performance cost.
|
||||
|
||||
Because some platform do not follow the same interface, the missing method in the actual low-level implementation are detected via SFINAE and a wrapper is generated when such method are missing. See the `CALL_IF_EXISTS` macro in `Marlin/src/core/macros.h` for documentation of this technique.
|
||||
|
||||
## Composing the desired feature
|
||||
The different specificities for each architecture are provided by composing the serial type based on desired functionality.
|
||||
In the `Marlin/src/core/serial_hook.h` file, the different serial feature are declared and defined in each templated type:
|
||||
1. `BaseSerial` is a simple 1:1 wrapper to the underlying, Arduino compatible, `Serial`'s class. It derives from it. You'll use this if the platform does not do anything specific for the `Serial` object (for example, if an interrupt callback calls directly the serial **instance** in the platform's framework code, this is not the right class to use). This wrapper is completely inlined so that it does not generate any code upon compilation. `BaseSerial` constructor forwards any parameter to the platform's `Serial`'s constructor.
|
||||
2. `ForwardSerial` is a composing wrapper. It references an actual Arduino compatible `Serial` instance. You'll use this if the instance is declared in the platform's framework and is being referred directly in the framework. This is not as efficient as the `BaseSerial` implementation since static dereferencing is done for each method call (it'll still be faster than virtual dispatching)
|
||||
3. `ConditionalSerial` is working a bit like the `ForwardSerial` interface, but it checks a boolean condition before calling the referenced instance. You'll use it when the serial output can be switch off at runtime, for example in a *telnet* like serial output that should not emit any packet if no client is connected.
|
||||
4. `RuntimeSerial` is providing a runtime-modifiable hooking method for its `write` and `msgDone` method. You'll use it if you need to capture the serial output of Marlin, for example to display the G-Code parser's output on a GUI interface. The hooking interface is setup via the `setHook` method.
|
||||
5. `MultiSerial` is a runtime modifiable serial output multiplexer. It can output (*respectively input*) to 2 different interface based on a port *mask*. You'll use this if you need to output the same serial stream to multiple port. You can plug a `MultiSerial` to itself to duplicate to more than 2 ports.
|
||||
|
||||
## Plumbing
|
||||
Since all the types above are using CRTP, it's possible to combine them to get the appropriate functionality.
|
||||
This is easily done via type definition of the feature.
|
||||
|
||||
For example, to create a single serial interface with 2 serial outputs (one enabled at runtime and the other switchable):
|
||||
```cpp
|
||||
typedef MultiSerial< RuntimeSerial<Serial>, ConditionalSerial<TelnetClient> > Serial1Class;
|
||||
```
|
||||
|
||||
To send the same output to 4 serial ports you could nest `MultiSerial` like this:
|
||||
```cpp
|
||||
typedef MultiSerial< MultiSerial< BaseSerial<Serial>, BaseSerial<Serial1> >, MultiSerial< BaseSerial<Serial2>, BaseSerial<Serial3>, 2, 1>, 0, 2> Serial1Class;
|
||||
```
|
||||
The magical numbers here are the step and offset for computing the serial port. Simplifying the above monster a bit:
|
||||
```cpp
|
||||
MS< A = MS<a, b, offset=0, step=1>, B=MS<c, d, offset=2, step=1>, offset=0, step=2>
|
||||
```
|
||||
This means that the underlying multiserial A (with output to `a,b`) is available from offset = 0 to offset + step = 1 (default value).
|
||||
The multiserial B (with output to `c,d`) is available from offset = 2 (the next step from the root multiserial) to offset + step = 3.
|
||||
In practice, the root multiserial will redirect any index/mask `offset` to `offset + step - 1` to its first leaf, and any index/mask `offset + step` to `offset + 2*step - 1` to its second leaf.
|
||||
|
||||
## Emergency parser
|
||||
By default, the serial base interface provide an emergency parser that's only enable for serial classes that support it. Because of this condition, all underlying types take a first `bool emergencyParserEnabled` argument to their constructor. You must take into account this parameter when defining the actual type used.
|
||||
|
||||
## SERIAL macros
|
||||
The following macros are defined (in `serial.h`) to output data to the serial ports:
|
||||
|
||||
| MACRO | Parameters | Usage | Example | Expected output |
|
||||
|-------|------------|-------|---------|-----------------|
|
||||
| `SERIAL_ECHO` | Any basic type is supported (`char`, `uint8_t`, `int16_t`, `int32_t`, `float`, `long`, `const char*`, ...). | For a numeric type it prints the number in decimal. A string is output as a string. | `uint8_t a = 123; SERIAL_ECHO(a); SERIAL_CHAR(' '); SERIAL_ECHO(' '); ` | `123 32` |
|
||||
| `SERIAL_ECHOLN` | Same as `SERIAL_ECHO` | Do `SERIAL_ECHO`, adding a newline | `int a = 456; SERIAL_ECHOLN(a);` | `456\n` |
|
||||
| `SERIAL_ECHO_F` | `float` or `double` | Print a decimal value with a given precision (default 2) | `float a = 3.1415; SERIAL_ECHO_F(a); SERIAL_CHAR(' '); SERIAL_ECHO_F(a, 4);` | `3.14 3.1415`|
|
||||
| `SERIAL_ECHOPGM` | String / Value pairs | Print a series of string literals and values alternately | `SERIAL_ECHOPGM("Bob", 34);` | `Bob34` |
|
||||
| `SERIAL_ECHOLNPGM` | Same as `SERIAL_ECHOPGM` | Do `SERIAL_ECHOPGM`, adding a newline | `SERIAL_ECHOPGM("Alice", 56);` | `alice56` |
|
||||
| `SERIAL_ECHOPGM_P` | Like `SERIAL_ECHOPGM` but takes PGM strings | Print a series of PGM strings and values alternately | `SERIAL_ECHOPGM_P(GET_TEXT(MSG_HELLO), 123);` | `Hello123` |
|
||||
| `SERIAL_ECHOLNPGM_P` | Same as `SERIAL_ECHOPGM_P` | Do `SERIAL_ECHOPGM_P`, adding a newline | `SERIAL_ECHOLNPGM_P(PSTR("Alice"), 78);` | `alice78\n` |
|
||||
| `SERIAL_ECHOLIST` | String literal, values | Print a string literal and a list of values | `SERIAL_ECHOLIST(F("Key "), 1, 2, 3);` | `Key 1, 2, 3` |
|
||||
| `SERIAL_ECHO_START` | None | Prefix an echo line | `SERIAL_ECHO_START();` | `echo:` |
|
||||
| `SERIAL_ECHO_MSG` | Same as `SERIAL_ECHOLNPGM` | Print a full echo line | `SERIAL_ECHO_MSG("Count is ", count);` | `echo:Count is 3` |
|
||||
| `SERIAL_ERROR_START`| None | Prefix an error line | `SERIAL_ERROR_START();` | `Error:` |
|
||||
| `SERIAL_ERROR_MSG` | Same as `SERIAL_ECHOLNPGM` | Print a full error line | `SERIAL_ERROR_MSG("Not found");` | `Error:Not found` |
|
||||
| `SERIAL_ECHO_SP` | Number of spaces | Print one or more spaces | `SERIAL_ECHO_SP(3)` | ` ` |
|
||||
| `SERIAL_EOL` | None | Print an end of line | `SERIAL_EOL();` | `\n` |
|
||||
|
||||
*This document was written by [X-Ryl669](https://blog.cyril.by) and is under [CC-SA license](https://creativecommons.org/licenses/by-sa)*
|
Reference in New Issue
Block a user