first commit

This commit is contained in:
ZennDev1337 2024-01-08 09:23:06 +01:00
commit 8c39952965
1956 changed files with 1460685 additions and 0 deletions

269
docs/Bresenham.md Normal file
View 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;
}
};
```

Binary file not shown.

Binary file not shown.

BIN
docs/LCD_RTS.xmind Normal file

Binary file not shown.

59
docs/Queue.md Normal file
View 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 | +---+ +------------------+ | | |
| +------------+ | | | | | | | GCode |
| | | | | | | 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!)

74
docs/Serial.md Normal file
View file

@ -0,0 +1,74 @@
# 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_ECHOPAIR` | String / Value pairs | Print a series of string literals and values alternately | `SERIAL_ECHOPAIR("Bob", 34);` | `Bob34` |
| `SERIAL_ECHOLNPAIR` | Same as `SERIAL_ECHOPAIR` | Do `SERIAL_ECHOPAIR`, adding a newline | `SERIAL_ECHOPAIR("Alice", 56);` | `alice56` |
| `SERIAL_ECHOPAIR_P` | Like `SERIAL_ECHOPAIR` but takes PGM strings | Print a series of PGM strings and values alternately | `SERIAL_ECHOPAIR_P(GET_TEXT(MSG_HELLO), 123);` | `Hello123` |
| `SERIAL_ECHOLNPAIR_P` | Same as `SERIAL_ECHOPAIR_P` | Do `SERIAL_ECHOPAIR_P`, adding a newline | `SERIAL_ECHOLNPAIR_P(PSTR("Alice"), 78);` | `alice78\n` |
| `SERIAL_ECHOLIST` | String literal, values | Print a string literal and a list of values | `SERIAL_ECHOLIST("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_ECHOLN_PAIR` | 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_ECHOLN_PAIR` | 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` |
| `SERIAL_OUT` | `SERIAL_OUT(myMethod)` | Call a custom serial method | `SERIAL_OUT(msgDone);` | ... |
*This document was written by [X-Ryl669](https://blog.cyril.by) and is under [CC-SA license](https://creativecommons.org/licenses/by-sa)*