MarlinFirmware / Marlin

Marlin is an optimized firmware for RepRap 3D printers based on the Arduino platform. Many commercial 3D printers come with Marlin installed. Check with your vendor if you need source code for your specific machine.
https://marlinfw.org
GNU General Public License v3.0
16.35k stars 19.26k forks source link

UBL "G29 M" behave unexpectingly under repetier host #4794

Closed lrpirlet closed 8 years ago

lrpirlet commented 8 years ago

I am using repetier host version 1.6.2 to test the UBL branch...

@Roxy-3D, I think the use of M as a parameter to G29 is not going to work smoothly with Repetier... and maybe other host software... see further

@repetier I do issue the command Gnn M... Repetier changes this and send to the printer M0Gnn... It issues a wait code... Is that an intended behavior???

20:34:39.084 : ok
20:34:46.960 : N45 I will Issue G29 M*96
20:34:46.960 : echo:N45 I will Issue G29 M*96
20:34:46.960 : echo:Unknown command: "I will Issue G29 M"
20:34:46.960 : ok

Here I issue G29 M in the repetier "Manual Control" box labeled G-Code: Repetier send M0G29 as you can see

20:34:54.445 : N49 M0G29*82
20:34:54.445 : echo:N49 M0G29*82
20:35:34.512 : Communication timeout - reset send buffer block

Here, everythings seems blocked... G29 shows in the display... Repetier shows "1 command waiting" I press the button to proceed, the display clears... Sometimes Repetier comes back with Idle sometimes NOT... In this case Repetiercomes back "Idle" and allow me to issue another G-code...

20:35:38.153 : TEST12ok
20:35:38.184 : ok

I did try to replace G29 with any G command.. I get same behavior with G55 (that does NOT exist in Marlin

20:52:53.385 : N89 M111 S255*82
20:52:53.385 : echo:DEBUG:ECHO,INFO,ERRORS,DRYRUN,COMMUNICATION,LEVELING,SEGMENTS,ADJUSTMENTS
20:52:53.385 : ok
20:52:57.542 : N91 I will Issue G55 M*98
20:52:57.542 : echo:N91 I will Issue G55 M*98
20:52:57.542 : echo:Unknown command: "I will Issue G55 M"
20:52:57.542 : ok
20:53:06.574 : N95 M0G55*88
20:53:06.574 : echo:N95 M0G55*88
20:53:13.153 : TEST12ok

Note TEST12 is output by M0 in debug mode...

The "funny" thing is that when I insert V4 or W between the command and the M, the command behave as I expect it....

20:56:35.478 : N148 I will Issue G29 W M*43
20:56:35.478 : echo:N148 I will Issue G29 W M*43
20:56:35.478 : echo:Unknown command: "I will Issue G29 W M"
20:56:35.478 : ok
20:56:38.290 : N150 G29 W M*12
20:56:38.290 : echo:N150 G29 W M*12
20:56:38.290 : Unified Bed Leveling System Inactive.
20:56:38.290 : No Mesh Loaded.
20:56:38.306 : G29_Correction_Fade_Height : 10.00
20:56:38.306 : z_offset: 0.000000
20:56:38.306 : UBL_state_at_invokation :0
20:56:38.306 : UBL_state_recursion_chk :0
20:56:38.322 : Free EEPROM space starts at: 0x0120
20:56:38.337 : end of EEPROM              : 0FFF
20:56:38.337 : z_value[][] size: 354  220
20:56:38.353 : EEPROM free for UBL: 0x0EDF
20:56:38.368 : EEPROM can hold 0x0011 meshes.
20:56:38.384 : sizeof(stat)     :0043
20:56:38.384 : MESH_NUM_X_POINTS  11
20:56:38.384 : MESH_NUM_Y_POINTS  5
20:56:38.384 : MESH_MIN_X         0
20:56:38.384 : MESH_MIN_Y         0
20:56:38.384 : MESH_MAX_X         190
20:56:38.384 : MESH_MAX_Y         190
20:56:38.400 : MESH_X_DIST        19.000000
20:56:38.400 : MESH_Y_DIST        47.500000
20:56:38.478 : Unified Bed Leveling sanity checks passed.
20:56:38.478 : Bed Topography Report:
20:56:38.493 : (0,4)                                                                                                                                                                              (10,4)
20:56:38.509 : (0,190)                                                                                                                                                                          (190,190)
20:56:38.509 : 0.00000     0.00000     0.00000     0.00000     0.00000     0.00000     0.00000     0.00000     0.00000     0.00000     0.00000
20:56:38.509 : 
20:56:38.525 : 0.00000     0.00000     0.00000     0.00000     0.00000     0.00000     0.00000     0.00000     0.00000     0.00000     0.00000
20:56:38.525 : 
20:56:38.540 : 0.00000     0.00000     0.00000     0.00000     0.00000     0.00000     0.00000     0.00000     0.00000     0.00000     0.00000
20:56:38.540 : 
20:56:38.556 : 0.00000     0.00000     0.00000     0.00000     0.00000     0.00000     0.00000     0.00000     0.00000     0.00000     0.00000
20:56:38.556 : 
20:56:38.572 : [ 0.00000]   0.00000     0.00000     0.00000     0.00000     0.00000     0.00000     0.00000     0.00000     0.00000     0.00000
20:56:38.587 : (0,0)                                                                                                                                                                              (190,0)
20:56:38.603 : (0,0)                                                                                                                                                                                 (10,0)
20:56:38.603 : 
20:56:38.603 : ok
Roxy-3D commented 8 years ago

I am using repetier host version 1.6.2 to test the UBL branch...

@lrpirlet I have only tested using PronterFace. The M option works flawlessly with PronterFace. Even the weird cases where you do something like G29 P1 R M and it generates map after map. They keep just scrolling up the screen showing you what has been discovered about the bed's topology.

I think the use of M as a parameter to G29 is not going to work smoothly with Repetier... and maybe other host software... see further

As stated, it works flawlessly with PronterFace. But Repetier Host is very important. We will have to figure out what is keeping it from working there and get it fixed.

@repetier Is there anything you need from me to get this working better? If so, just let me know. It is possible the problem is on the Marlin side of things. But if so, I'll need some guidance on what I should change to make it so Marlin is not getting Repetier Host angry with us. Please let me know!!!

thinkyhead commented 8 years ago

We can't use "M" as the letter for a parameter. Verboten. Nor "G" or "N". "T" seems to be excepted.

Roxy-3D commented 8 years ago

We can't use "M" as the letter for a parameter.

@thinkyhead Can you give more detail? I've been using that since the start of UBL and it works fine with PronterFace. M is easy to remember for 'Map'. So if possible, I would like to keep using it for that purpose.

thinkyhead commented 8 years ago

M, G, and T are GCode reserved letters, along with N and F. Repetier is well within rights to interpret these letters as having standard meanings. The more we diverge from standard GCode, the more grief we get from CNC world.

Roxy-3D commented 8 years ago

The more we diverge from standard GCode, the more grief we get from CNC world.

Maybe. But I think if we put it up to a vote from the people already using UBL, they are going to say they want the 'M' option to stay there. And $#!%, take a look at the parameter (or option) list for UBL's G29. You can do that here: https://github.com/MarlinFirmware/Marlin/blob/devel-ubl/Marlin/G29_Unified_Bed_Leveling.cpp#L47

I really do need option letters that "Make Sense". @lrpirlet Can you try using a lower case 'm' and see if that blows up? I'm checking out an updated version of the code that is going to get uploaded in the next day or two. If a lower case 'm' doesn't cause any sickness, I'll just make that option take either version of the letter. And in a day or two, everything will be working for you.

thinkyhead commented 8 years ago

UBL is hardly finalized, and "map" isn't the only word in the dictionary that could be applied. You'll find not a single commit from me where I chose to use N, M, G, etc. as parameters because I've been assiduously avoiding their use. Specifically, because I researched GCode and examined several GCode parsers to get a sense of why it is the way it is.

In essence, each of these letters indicates a "mode change" in the character stream, and in fact standard GCode doesn't even require line-breaks. I realize that RepRap G-code represents a divergence from the standard, but I think in this case we ought to defer.

take a look at the parameter (or option) list

That's actually a really over-the-top list of options. I'm sure this will have to be seriously reformed before it will be acceptable. I don't have time to get into alternatives, but clearly it needs a re-design. Just as a for-example, "G29 A" to activate? It really should be a whole different GCode. G29 is supposed to simply initiate a leveling procedure. We've already indulged enough with MBL's several S parameter values. We shouldn't take that slip in our discipline as an invitation to further overload G29.

repetier commented 8 years ago

Parses check for either M or G in commandline and match it. You can not mix. Also N is line number for every command so it also forbidden for all codes! T is an exception so you can use it as extruder for M104, also Tx is normally a command to switch tools.

I guess host/server may drop M or G or at least parse it wrong so you are provoking problems mixing M and G. Also, host will expect a number after M. Also it would now replace all missing values by 0, I think.

lrpirlet commented 8 years ago

@Roxy-3D Well, I issued "g29 m" in the G-Code box... the result is M0G29... Just for the fun I did input "hello there"... Repetier did output 20:53:49.341 : echo:Unknown command: "Hello There"...

So, it looks like, maybe, the first letter of each word is changed to uppercase.

That said, I can still use the G29 M by writing G29 V4 M or G29 W M... This seems to work, FOR ME and probably up to when I install a next version of Repetier... So, I will never trust myself and I will chose a different approach.

If you ask my advice, I would say that I feel this command to be much too complex for a G-Code... This looks like a UNIX command with lots and lots of modifier... I have a fairly good feeling for UNIX commands, but I will NEVER be fluent enough with this G29 G-Code to use it fully... Especially given the fact that I have no -h modifier

The only thing I ask to g29 is to transform a bed, 3 axes and one tip into a coherent system where, for chosen Z, the tip moves over the bed at a constant height relative to the bed... A set of 4 to 6 modifiers is about what I can and will remember... ( I do NOT use m48 to the full... I learned that m48 P12 V4 will give me the mean, a significant Sigma .. I know that I can move the tip first, I know that I can make the servo go up and down, I know that I can move the tip between probing... Without doc, I will NEVER trust my memory... and to be honest, when I have some time it is NOT to play with modifiers, but to print the part I want...)

Please understand that the above is MY opinion.... This is NO complaint against your choices... I will use what you produce or I will write it myself (If I can :-) )

thinkyhead commented 8 years ago

Don't get me wrong, either. The geeks writing host software seem to be making some "brittle" choices in their parsers and don't seem to appreciate the power of regular expressions, for example.

As developers of the firmware, we should probably be providing code to the host developers to help them along —so-called "reference implementations"— to demonstrate how GCode and the serial output of various commands may be parsed to remain both backward and forward-compatible. Of course, this extra work takes time and effort, and I know we're all pretty constrained lately.

Roxy-3D commented 8 years ago

Well, I issued "g29 m" in the G-Code box... the result is M0G29... Just for the fun I did input "hello there"... Repetier did output 20:53:49.341 : echo:Unknown command: "Hello There"...

So, it looks like, maybe, the first letter of each word is changed to uppercase.

OK, in the new code the Map display is also enabled with an 'O' command. I'll let either one of them display the map.

If you ask my advice, I would say that I feel this command to be much too complex for a G-Code... This looks like a UNIX command with lots and lots of modifier... I

Yes, in a way this is true. But remember, I'm trying to provide all the features of the different existing Auto Bed Leveling systems, and do it for every machine type. Guess what? There are going to be a lot of options.

I'll make it so big chunks of the code can be turned off later once everything is working. The user should have the ability to just enable the parts they want. But for right now, there isn't much harm having everything turned on.

thinkyhead commented 8 years ago

do it for every machine type

But it will only be installed on one machine at a time. So it's not necessary to provide every option on every machine. Likewise, I also presume that not every leveling method will be enabled at once, but just one. So again, we should keep it tight. Ideally just a bare G29 that will probe the bed according to previous configuration, and perhaps preserving the L R F B options we already have, to specify a smaller region (although I'm disinclined to keep that because we still have to extrapolate across the entire print region), do a dry run, and V to do verbose logging.

For other options, including enable/disable, we should add a couple other GCodes.

thinkyhead commented 8 years ago

I note that G32 is now tending towards being preferred for bed probing… Obviously we're bound to stick with G29 since default Start GCode in various slicers include it.

Also have a look at G33 which is basically Mesh Leveling. @repetier is adding support for various GCodes, so we will have to watch the RepRap Wiki and write a bot to watch their Github to keep abreast of these changes.

thinkyhead commented 8 years ago

Repetier codes:

M320 : Activate Autolevel M321 : Deactivate Autolevel M322 : Reset Autolevel Matrix M323 : "Distortion correction" on/off

Smoothie codes:

M371 : Move to next calibration point M372 : Record calibration value, move to next point M373 : End bed level calibration mode M374 : Save calibration grid M375 : Display / Load Matrix M565 : Set Z Probe offsets

Roxy-3D commented 8 years ago

M320 : Activate Autolevel M321 : Deactivate Autolevel M322 : Reset Autolevel Matrix M323 : "Distortion correction" on/off

At least for me... It is much more intuitive to know G29 is used for the Auto Bed Leveling. It is easy for me to remember a G29 A activates it. And it is easy for me to remember that G29 D deactivates it. I don't see any harm in 'over loading' G29 that way.

thinkyhead commented 8 years ago

Customarily in G-Code "A" represents a value for the "A Axis."

Roxy-3D commented 8 years ago

Well, GCode is fine for what it is intended to do. But it is a very limited and constrained syntax. And... right now, every thing is in Cartesian coordinates, even if you are on a Scara or Delta. I'll address 'A-Axis' issues later if they come up.

Please load up the UBL System and use it. I think you will find it has a nice feel to it and it makes sense.

thinkyhead commented 8 years ago

Please load up the UBL System and use it

Can't. I don't have a 3D printer at my disposal.

Actually, I've been making some allowances for raw moves on SCARA, so you can directly set the A and B angles. For my client's firmware it's G6 with A B C. A and B are angles in degrees. C is the Z stepper. It also allows uninterpolated moves with G0, something which is fine on SCARA but which we can't allow on Delta because it produces dips in the Z position.

Roxy-3D commented 8 years ago

which we can't allow on Delta because it produces dips in the Z position.

If it is desirable to allow it on Delta's, there are a couple of ways to do it and not have the dip. I'm really not trying to be argumentative. But here is the way I see it: When the term 'uninterpolated' is used, it is talking about the mapping of the coordinate system for the start and end points. It really isn't saying you can't do an intelligent trajectory to get from one to the other. The problem with Delta's is the simple case is an arc that dips. So... You still don't map (interpolate) the start and end point. But you can do a 'smart' path that is physically a straight line. One simple technique to do that is just break the line segment up into many smaller line segments (with very small dips).

PS. These are the discussions that I really find interesting. If they annoy you or make you think I'm just being difficult, please speak up. That isn't what is happening on my side. I really find these discussion to be fascinating and I learn a lot from them!

thinkyhead commented 8 years ago

there are a couple of ways to do it and not have the dip

The only technique available is to split up lines into small moves. There isn't another option, because in any given straight cartesian movement, the tower carriages always move parabolically, not linearly. Even when the effector moves from the middle straight towards or away from a tower, the tower movement is parabolic.

One simple technique to do that is just break the line segment up into many smaller line segments

Yeppers!

I really find these discussion to be fascinating and I learn a lot from them!

Sorry if I'm being terse; I will try to be patient. I've just been getting a lot of "D'oh!" issues lately, and I'm trying to slog through a pile of changes that are a tangled web of interdependencies.

thinkyhead commented 8 years ago

…break the line segment up…

Hmm, I'm going to continue to ruminate on this in a general way. Take note that for any given straight line in Cartesian space, an individual tower carriage might move up a bit, then down a bit, (as the effector moves past the tower). But it isn't always the case, so we could possibly do some heuristics ahead of time and plan the tower moves directly, instead of always working from the cartesian coordinates.

As mentioned, the tower carriages move non-linearly, gaining speed when the effector is closer to the tower, and reducing speed when the effector is farther away. So, we just might be able to build on the Bresenham technique to achieve the necessary parabolic movement. We just need to know the ratios of effector-to-tower for the start, middle, and end of any given linear movement (for all three towers).

Meanwhile, check it out. I added a hidden setting that you should try on a Delta at the next opportunity. If you enable DELTA_FAST_SQRT it will use the Q_rsqrt fast inverse square root algorithm (as used in Quake 3 Arena) instead of the float library's sqrt function. It's supposed to be significantly faster than sqrt — but since we need the reciprocal, there's one additional float division per tower.

The accuracy of Q_rsqrt is supposed to be pretty good. For the moment, it completely supplants the sqrt function in inverse_kinematics, but we could periodically use sqrt to make a correction, if it turns out to be too far off. Since we only need to be within a couple of stepper steps, it's probably accurate enough.

Roxy-3D commented 8 years ago

As mentioned, the tower carriages move non-linearly, gaining speed when the effector is closer to the tower, and reducing speed when the effector is farther away. So, we just might be able to build on the Bresenham technique to achieve the necessary parabolic movement. We just need to know the ratios of effector-to-tower for the start, middle, and end of any given linear movement (for all three towers).

I guess this is obvious but I didn't realize that was the cause of the dip until you pointed it out. With that realization, here is a question: Is the dip truly parabolic? Because, if it is, that type of curve can be approximated very accurately with just three data points. We already have the start and end point. I wonder if we could have a table (coming straight out from each tower) to get the third data point. Depending upon how close the effector is passing by the tower, we grab the third number out of a table and can exactly (and quickly) predict the expected dip????

Meanwhile, check it out. I added a hidden setting that you should try on a Delta at the next opportunity. If you enable DELTA_FAST_SQRT it will use the Q_rsqrt fast inverse square root algorithm (as used in Quake 3 Arena) instead of the float library's sqrt function. It's supposed to be significantly faster than sqrt — but since we need the reciprocal, there's one additional float division per tower

I'll definitely check out the faster square root algorithm. But I've got my head too full of stuff right now with a bug I'm trying to chase down. When I get to the point where I start trying to bring up the UBL code on Delta's I will be in a position to both look at and use the new square root code.

ghost commented 8 years ago

DELTA_FAST_SQRT

It's interesting.

test program: ([reference](http://forum.arduino.cc/index.php?topic=74656.0)) ``` cpp float Q_rsqrt(float number) { long i; float x2, y; const float threehalfs = 1.5F; x2 = number * 0.5F; y = number; i = *(long *)&y; // evil floating point bit level hacking i = 0x5f3759df - (i >> 1); // what the fuck? y = *(float *)&i; y = y * (threehalfs - (x2 * y * y)); // iteration return y; } float Q_rsqrt2(float number) { long i; float x2, y; const float threehalfs = 1.5F; x2 = number * 0.5F; y = number; i = *(long *)&y; // evil floating point bit level hacking i = 0x5f3759df - (i >> 1); // what the fuck? y = *(float *)&i; y = y * (threehalfs - (x2 * y * y)); // 1st iteration y = y * (threehalfs - (x2 * y * y)); // 2nd iteration, this can be removed return y; } float Q_rsqrt3(float number) { long i; float x2, y; const float threehalfs = 1.5F; x2 = number * 0.5F; y = number; i = *(long *)&y; // evil floating point bit level hacking i = 0x5f3759df - (i >> 1); // what the fuck? y = *(float *)&i; y = y * (threehalfs - (x2 * y * y)); // 1st iteration y = y * (threehalfs - (x2 * y * y)); // 2nd iteration, this can be removed y = y * (threehalfs - (x2 * y * y)); // 3nd iteration, for test return y; } void setup() { Serial.begin(250000); } void loop() { float dummy = 0; uint32_t t; double err_tmp = 0; double max_err = 0; double ave_err = 0; uint32_t count = 0; Serial.println("Starting long calculation:"); Serial.print("\n"); t = micros(); for (uint16_t a = 0; a < 6000; a++) { dummy += sqrt(a); } t = micros() - t; Serial.print("calculation time of sqrt(): "); Serial.println(t); Serial.print("\n"); t = micros(); for (uint16_t a = 0; a < 6000; a++) { dummy += sqrtf(a); } t = micros() - t; Serial.print("calculation time of sqrtf(): "); Serial.println(t); Serial.print("\n"); t = micros(); for (uint16_t a = 0; a < 6000; a++) { dummy += Q_rsqrt(a); } t = micros() - t; for (float a = 0; a < 1000; a += 0.01) { count++; err_tmp = (float)(sqrt(a)) - 1.0 / Q_rsqrt(a); ave_err += err_tmp; if (abs(err_tmp) > max_err) { max_err = abs(err_tmp); } } Serial.print("calculation time of Q_rsqrt(): "); Serial.println(t); Serial.print("maximum error with sqrt(): "); if (max_err != 0) Serial.println(max_err, 7); else Serial.println("nodiff"); Serial.print("average error with sqrt(): "); Serial.println(ave_err / count, 7); Serial.print("\n"); t = micros(); for (uint16_t a = 0; a < 6000; a++) { dummy += Q_rsqrt2(a); } t = micros() - t; max_err = 0; ave_err = 0; for (float a = 0; a < 1000; a += 0.01) { err_tmp = (float)(sqrt(a)) - 1.0 / Q_rsqrt2(a); ave_err += err_tmp; if (abs(err_tmp) > max_err) { max_err = abs(err_tmp); } } Serial.print("calculation time of Q_rsqrt2(): "); Serial.println(t); Serial.print("maximum error with sqrt(): "); if (max_err != 0) Serial.println(max_err, 7); else Serial.println("nodiff"); Serial.print("average error with sqrt(): "); Serial.println(ave_err / count, 7); Serial.print("\n"); t = micros(); for (uint16_t a = 0; a < 6000; a++) { dummy += Q_rsqrt3(a); } t = micros() - t; max_err = 0; ave_err = 0; for (float a = 0; a < 1000; a += 0.01) { err_tmp = (float)(sqrt(a)) - 1.0 / Q_rsqrt3(a); ave_err += err_tmp; if (abs(err_tmp) > max_err) { max_err = abs(err_tmp); } } Serial.print("calculation time of Q_rsqrt3(): "); Serial.println(t); Serial.print("maximum error with sqrt(): "); if (max_err != 0) Serial.println(max_err, 7); else Serial.println("nodiff"); Serial.print("average error with sqrt(): "); Serial.println(ave_err / count, 7); Serial.print("\n"); Serial.print("dummy value: "); Serial.println(dummy); Serial.println("\n"); delay(1000); } ```

Result of Arduino MEGA:

calculation time of sqrt(): 260108

calculation time of sqrtf(): 266968

calculation time of Q_rsqrt(): 314992
maximum error with sqrt(): 0.0542393
average error with sqrt(): -0.0204092

calculation time of Q_rsqrt2(): 535780
maximum error with sqrt(): 0.0001450
average error with sqrt(): -0.0000408

calculation time of Q_rsqrt3(): 741392
maximum error with sqrt(): 0.0000038
average error with sqrt(): -0.0000000

Result of Arduino Due:

calculation time of sqrt(): 158269

calculation time of sqrtf(): 55673

calculation time of Q_rsqrt(): 35595
maximum error with sqrt(): 0.0542397
average error with sqrt(): -0.0204093

calculation time of Q_rsqrt2(): 58142
maximum error with sqrt(): 0.0001452
average error with sqrt(): -0.0000408

calculation time of Q_rsqrt3(): 79701
maximum error with sqrt(): 0.0000047
average error with sqrt(): -0.0000000

In case of MEGA, It looks like that Q_rsqrt is slower than default sqrt() and sqrtf() for some reason. In case of Due, Q_rsqrt is x2 ~ x4 faster than default sqrt(), but Q_rsqrt2() and Q_rsqrt3() are slower than sqrtf(). IDE is 1.6.12 HourlyBuild 2016/09/08 10:33.

Roxy-3D commented 8 years ago

@esenapaj Do you know if the Due is doing 4 byte or 8 byte floating point math? If I remember right, the Due has a floating point co-processor built into the chip. If that is true, this all makes sense. The Q_rsqrt() would be a software algorithm that is having to loop and do a bunch of work to get its number calculated.

Roxy-3D commented 8 years ago

Also have a look at G33 which is basically Mesh Leveling. @repetier is adding support for various GCodes, so we will have to watch the RepRap Wiki and write a bot to watch their Github to keep abreast of these changes.

We already have something very similar with M421. We can warm that over and change it to be G33 so it is exactly compliant with that description. But really... I haven't been using M421. The Mesh Editor built into the UBL G29 code is much nicer to use.

thinkyhead commented 8 years ago

@esenapaj That is interesting, alright. It sure would have been nice if Q_rsqrt was faster. We really need a new trick to make Delta better. My guess it that Q_rsqrt was faster for desktop platforms running Quake III Arena, but obviously it's not too hot on AVR (circa 1997) processors.

Blue-Marlin commented 8 years ago

@Roxy-3D No. The Due has no FPU but much wider then 8bit registers and a integer division in hardware.

AnHardt commented 8 years ago

Floats are 32bit. Doubles are 32bit on the 8 bit processors and 64bit at the DUE. https://www.arduino.cc/en/Reference/Double

ghost commented 8 years ago

I've altered the above test program for more information and experiment.

altered test program: ``` cpp float Q_rsqrt(float number, uint_fast8_t n) { float x2, y; int32_t i; char* num_ptr; const float threehalfs = 1.5f; x2 = number * 0.5f; num_ptr = (char*)&number; // suppress warning for 32bit architecture i = 0x5f3759df - (*(int32_t*)num_ptr >> 1); // evil floating point bit level hacking, what the fuck? num_ptr = (char*)&i; // suppress warning for 32bit architecture y = *(float*)num_ptr; for (uint_fast8_t i = 0; i < n; i++) y *= (threehalfs - (x2 * y * y)); // iteration return y; } void setup() { Serial.begin(250000); } void loop() { uint_fast32_t t, count = 0; float dummy[8] = { 0.0f }; double err_tmp = 0.0, max_err = 0.0, ave_err = 0.0; Serial.println("Starting long calculation:"); Serial.print("\n"); t = micros(); for (uint_fast16_t a = 0; a < 6000; a++) dummy[0] += sqrt(a); t = micros() - t; Serial.print("calculation time of sqrt(): "); Serial.println(t); Serial.print("\n"); t = micros(); for (uint_fast16_t a = 0; a < 6000; a++) dummy[1] += sqrtf(a); t = micros() - t; Serial.print("calculation time of sqrtf(): "); Serial.println(t); for (float a = 0.0f; a < 1000.0f; a += 0.01f) { count++; err_tmp = sqrt(a) - sqrtf(a); ave_err += err_tmp; if (abs(err_tmp) > max_err) max_err = abs(err_tmp); } Serial.print("maximum error with sqrt(): "); if (max_err != 0) Serial.println((float)max_err, 7); else Serial.println("nodiff"); Serial.print("average error with sqrt(): "); Serial.println((float)(ave_err / count), 7); Serial.print("\n"); for (uint_fast8_t i = 1; i <= 3; i++) { max_err = 0.0; ave_err = 0.0; Serial.print("Q_rsqrt() ("); Serial.print(i); Serial.println(" time iteration): "); t = micros(); for (uint16_t a = 0; a < 6000; a++) dummy[i * 2] += Q_rsqrt(a, i); t = micros() - t; Serial.print("calculation time of Q_rsqrt(): "); Serial.println(t); t = micros(); for (uint16_t a = 0; a < 6000; a++) dummy[i * 2 + 1] += 1.0f / Q_rsqrt(a, i); t = micros() - t; Serial.print("calculation time of 1.0f / Q_rsqrt(): "); Serial.println(t); for (float a = 0.0f; a < 1000.0f; a += 0.01f) { err_tmp = sqrt(a) - 1.0f / Q_rsqrt(a, i); ave_err += err_tmp; if (abs(err_tmp) > max_err) max_err = abs(err_tmp); } Serial.print("maximum error with sqrt(): "); if (max_err != 0) Serial.println((float)max_err, 7); else Serial.println("nodiff"); Serial.print("average error with sqrt(): "); Serial.println((float)(ave_err / count), 7); Serial.print("\n"); } Serial.println("dummy value for preventing excess optimization by compiler: "); for (uint_fast8_t i = 0; i < 8; i++) Serial.println(dummy[i]); Serial.println("\n"); delay(1000); } ```

Result of Arduino MEGA:

calculation time of sqrt(): 255580

calculation time of sqrtf(): 255568
maximum error with sqrt(): nodiff
average error with sqrt(): 0.0000000

Q_rsqrt() (1 time iteration): 
calculation time of Q_rsqrt(): 337148
calculation time of 1.0f / Q_rsqrt(): 536100
maximum error with sqrt(): 0.0542393
average error with sqrt(): -0.0204092

Q_rsqrt() (2 time iteration): 
calculation time of Q_rsqrt(): 541444
calculation time of 1.0f / Q_rsqrt(): 740512
maximum error with sqrt(): 0.0001450
average error with sqrt(): -0.0000408

Q_rsqrt() (3 time iteration): 
calculation time of Q_rsqrt(): 745168
calculation time of 1.0f / Q_rsqrt(): 944372
maximum error with sqrt(): 0.0000038
average error with sqrt(): -0.0000000

Result of Arduino Due:

calculation time of sqrt(): 150328

calculation time of sqrtf(): 61020
maximum error with sqrt(): 0.0000010
average error with sqrt(): 0.0000000

Q_rsqrt() (1 time iteration): 
calculation time of Q_rsqrt(): 40406
calculation time of 1.0f / Q_rsqrt(): 61214
maximum error with sqrt(): 0.0542400
average error with sqrt(): -0.0204093

Q_rsqrt() (2 time iteration): 
calculation time of Q_rsqrt(): 64150
calculation time of 1.0f / Q_rsqrt(): 84956
maximum error with sqrt(): 0.0001452
average error with sqrt(): -0.0000408

Q_rsqrt() (3 time iteration): 
calculation time of Q_rsqrt(): 87700
calculation time of 1.0f / Q_rsqrt(): 108507
maximum error with sqrt(): 0.0000046
average error with sqrt(): -0.0000000

In case of MEGA, It looks like that default sqrt() or sqrtf() is best. In case of Due, It looks like that default sqrtf() is best.

thinkyhead commented 8 years ago

@esenapaj I'm looking at the possibility of using 1/2 as many sqrt calls and interpolating between them, rather than doing sqrt for every single segment. Do you think this will produce too much error? The general idea I'm thinking of for prepare_kinematic_move_to is this…

float prev_delta[ABC];
if (segments >= 2) inverse_kinematics(logical); // 2 is enough
for (uint16_t s = segments + 1; --s;) { // (ex: 5+1=6, but --s so: 5,3,1)
  if (s > 1) { // (5,3)
    --s; // (4,2)
    prev_delta[A_AXIS] = delta[A_AXIS];
    prev_delta[B_AXIS] = delta[B_AXIS];
    prev_delta[C_AXIS] = delta[C_AXIS];
    LOOP_XYZE(i) logical[i] += segment_distance[i] + segment_distance[i];
    inverse_kinematics(logical);
    planner.buffer_line(
      (prev_delta[A_AXIS] + delta[A_AXIS]) * 0.5,
      (prev_delta[B_AXIS] + delta[B_AXIS]) * 0.5,
      (prev_delta[C_AXIS] + delta[C_AXIS]) * 0.5,
      logical[E_AXIS], _feedrate_mm_s, active_extruder
    );
  }
  else { // (1)
    LOOP_XYZE(i) logical[i] += segment_distance[i];
    inverse_kinematics(logical);
  }
  planner.buffer_line(delta[A_AXIS], delta[B_AXIS], delta[C_AXIS], logical[E_AXIS], _feedrate_mm_s, active_extruder);
}
thinkyhead commented 8 years ago

Also, there are 6 float subtract operations that can easily be eliminated for every Delta segment, by converting Delta moves to "raw" coordinates in advance of segmentation.

Currently we have this:

void inverse_kinematics(const float logical[XYZ]) {

  const float cartesian[XYZ] = {
    RAW_X_POSITION(logical[X_AXIS]),
    RAW_Y_POSITION(logical[Y_AXIS]),
    RAW_Z_POSITION(logical[Z_AXIS])
  };

  // Macro to obtain the Z position of an individual tower
  #define DELTA_Z(T) cartesian[Z_AXIS] + _SQRT( \
    delta_diagonal_rod_2_tower_##T - HYPOT2(    \
        delta_tower##T##_x - cartesian[X_AXIS], \
        delta_tower##T##_y - cartesian[Y_AXIS]  \
      )                                         \
    )

  delta[A_AXIS] = DELTA_Z(1);
  delta[B_AXIS] = DELTA_Z(2);
  delta[C_AXIS] = DELTA_Z(3);
}

By pre-subtracting the coordinate space offsets in prepare_kinematic_move_to, this can be replaced by…

void inverse_kinematics(const float raw[XYZ]) {

  // Macro to obtain the Z position of an individual tower
  #define DELTA_Z(T) raw[Z_AXIS] + _SQRT(    \
    delta_diagonal_rod_2_tower_##T - HYPOT2( \
        delta_tower##T##_x - raw[X_AXIS],    \
        delta_tower##T##_y - raw[Y_AXIS]     \
      )                                      \
    )

  delta[A_AXIS] = DELTA_Z(1);
  delta[B_AXIS] = DELTA_Z(2);
  delta[C_AXIS] = DELTA_Z(3);
}

This will have the greatest effect on long straight lines, but probably not too much effect on lines under ~5mm. It will depend on delta speed.

Some other questions: Are small (and slow) moves too aggressively segmented by prepare_kinematic_move_to? Does it make sense to split up lines into segments smaller than 0.2mm? Would vertical deviation at that scale make any noticeable difference in the print quality?

ghost commented 8 years ago

Do you think this will produce too much error?

I'm not familiar with it because it's a advanced content for me... Is this OK?

test program: ``` cpp enum AxisEnum { NO_AXIS = -1, X_AXIS = 0, A_AXIS = 0, Y_AXIS = 1, B_AXIS = 1, Z_AXIS = 2, C_AXIS = 2, E_AXIS = 3, X_HEAD = 4, Y_HEAD = 5, Z_HEAD = 6, ALL_AXES = 100 }; #define LOOP_XYZE(VAR) for (uint8_t VAR=X_AXIS; VAR<=E_AXIS; VAR++) #define NUM_AXIS 4 #define XYZE 4 #define ABC 3 #define XYZ 3 volatile float position_shift[XYZ] = { 0 }, home_offset[XYZ] = { 0 }; #define RAW_POSITION(POS, AXIS) (POS - home_offset[AXIS] - position_shift[AXIS]) #define RAW_X_POSITION(POS) RAW_POSITION(POS, X_AXIS) #define RAW_Y_POSITION(POS) RAW_POSITION(POS, Y_AXIS) #define RAW_Z_POSITION(POS) RAW_POSITION(POS, Z_AXIS) #define RAW_CURRENT_POSITION(AXIS) RAW_POSITION(current_position[AXIS], AXIS) #define UNEAR_ZERO(x) ((x) < 0.000001) volatile int feedrate_percentage = 100; #define MMM_TO_MMS(MM_M) ((MM_M)/60.0) #define MMS_SCALED(MM_S) ((MM_S)*feedrate_percentage*0.01) volatile static float feedrate_mm_s = MMM_TO_MMS(1500.0); #define ARG_X float lx #define ARG_Y float ly #define ARG_Z float lz #define HYPOT2(x,y) (sq(x)+sq(y)) #define HYPOT(x,y) sqrt(HYPOT2(x,y)) #define NOLESS(v,n) do{ if (v < n) v = n; }while(0) volatile static float destination[NUM_AXIS] = { 0.0 }; volatile float offset[NUM_AXIS] = { 0.0 }, target[NUM_AXIS], // Destination position current_position[NUM_AXIS] = { 0.0 }, radius = HYPOT(offset[X_AXIS], offset[Y_AXIS]), center_X = current_position[X_AXIS] + offset[X_AXIS], center_Y = current_position[Y_AXIS] + offset[Y_AXIS], linear_travel = target[Z_AXIS] - current_position[Z_AXIS], r_X = -offset[X_AXIS], // Radius vector from center to current location r_Y = -offset[Y_AXIS], rt_X = target[X_AXIS] - center_X, rt_Y = target[Y_AXIS] - center_Y, angular_travel = atan2(r_X * rt_Y - r_Y * rt_X, r_X * rt_X + r_Y * rt_Y), mm_of_travel = HYPOT(angular_travel * radius, fabs(linear_travel)), segment_distance[NUM_AXIS] = { 0.0 }, delta[ABC] = { 0.0 }; #define MM_PER_ARC_SEGMENT 1 volatile uint16_t segments = floor(mm_of_travel / (MM_PER_ARC_SEGMENT)); volatile uint8_t active_extruder = 0; #define DELTA_DIAGONAL_ROD 250.0 // mm #define DELTA_RADIUS_TRIM_TOWER_1 0.0 #define DELTA_RADIUS_TRIM_TOWER_2 0.0 #define DELTA_RADIUS_TRIM_TOWER_3 0.0 #define DELTA_DIAGONAL_ROD_TRIM_TOWER_1 0.0 #define DELTA_DIAGONAL_ROD_TRIM_TOWER_2 0.0 #define DELTA_DIAGONAL_ROD_TRIM_TOWER_3 0.0 #define DELTA_SEGMENTS_PER_SECOND 200 #define DELTA_SMOOTH_ROD_OFFSET 175.0 // mm #define DELTA_EFFECTOR_OFFSET 33.0 // mm #define DELTA_CARRIAGE_OFFSET 18.0 // mm #define DELTA_RADIUS (DELTA_SMOOTH_ROD_OFFSET-(DELTA_EFFECTOR_OFFSET)-(DELTA_CARRIAGE_OFFSET)) #define SIN_60 0.8660254037844386 #define COS_60 0.5 float delta_radius = DELTA_RADIUS, delta_tower1_x = -SIN_60 * (delta_radius + DELTA_RADIUS_TRIM_TOWER_1), // front left tower delta_tower1_y = -COS_60 * (delta_radius + DELTA_RADIUS_TRIM_TOWER_1), delta_tower2_x = SIN_60 * (delta_radius + DELTA_RADIUS_TRIM_TOWER_2), // front right tower delta_tower2_y = -COS_60 * (delta_radius + DELTA_RADIUS_TRIM_TOWER_2), delta_tower3_x = 0, // back middle tower delta_tower3_y = (delta_radius + DELTA_RADIUS_TRIM_TOWER_3), delta_diagonal_rod = DELTA_DIAGONAL_ROD, delta_diagonal_rod_trim_tower_1 = DELTA_DIAGONAL_ROD_TRIM_TOWER_1, delta_diagonal_rod_trim_tower_2 = DELTA_DIAGONAL_ROD_TRIM_TOWER_2, delta_diagonal_rod_trim_tower_3 = DELTA_DIAGONAL_ROD_TRIM_TOWER_3, delta_diagonal_rod_2_tower_1 = sq(delta_diagonal_rod + delta_diagonal_rod_trim_tower_1), delta_diagonal_rod_2_tower_2 = sq(delta_diagonal_rod + delta_diagonal_rod_trim_tower_2), delta_diagonal_rod_2_tower_3 = sq(delta_diagonal_rod + delta_diagonal_rod_trim_tower_3), delta_segments_per_second = DELTA_SEGMENTS_PER_SECOND; #define _SQRT(n) sqrt(n) volatile double dummy = 0.0; double err_tmp_tmp[ABC] = { 0.0 }; #define DELTA_Z(T) raw[Z_AXIS] + _SQRT( \ delta_diagonal_rod_2_tower_##T - HYPOT2( \ delta_tower##T##_x - raw[X_AXIS], \ delta_tower##T##_y - raw[Y_AXIS] \ ) \ ) #define DELTA_LOGICAL_IK() do { \ const float raw[XYZ] = { \ RAW_X_POSITION(logical[X_AXIS]), \ RAW_Y_POSITION(logical[Y_AXIS]), \ RAW_Z_POSITION(logical[Z_AXIS]) \ }; \ delta[A_AXIS] = DELTA_Z(1); \ delta[B_AXIS] = DELTA_Z(2); \ delta[C_AXIS] = DELTA_Z(3); \ } while(0) void buffer_line(ARG_X, ARG_Y, ARG_Z, volatile const float &e, float fr_mm_s, const uint8_t extruder, bool calc_err) { if (calc_err) { err_tmp_tmp[0] = lx; err_tmp_tmp[1] = ly; err_tmp_tmp[2] = lz; } else { dummy += lx + ly + lz; } dummy += e + fr_mm_s + extruder; } void inverse_kinematics(volatile const float logical[XYZ]) { DELTA_LOGICAL_IK(); } inline bool prepare_kinematic_move_to(volatile float logical[NUM_AXIS], bool use_raw_kinematics, bool use_delta_ik_interpolation, bool calc_err) { // Get the top feedrate of the move in the XY plane float _feedrate_mm_s = MMS_SCALED(feedrate_mm_s); // If the move is only in Z don't split up the move. // This shortcut cannot be used if planar bed leveling // is in use, but is fine with mesh-based bed leveling if (logical[X_AXIS] == current_position[X_AXIS] && logical[Y_AXIS] == current_position[Y_AXIS]) { inverse_kinematics(logical); buffer_line(delta[A_AXIS], delta[B_AXIS], delta[C_AXIS], logical[E_AXIS], _feedrate_mm_s, active_extruder, calc_err); return true; } // Get the distance moved in XYZ float difference[NUM_AXIS]; LOOP_XYZE(i) difference[i] = logical[i] - current_position[i]; float cartesian_mm = sqrt(sq(difference[X_AXIS]) + sq(difference[Y_AXIS]) + sq(difference[Z_AXIS])); if (UNEAR_ZERO(cartesian_mm)) cartesian_mm = abs(difference[E_AXIS]); if (UNEAR_ZERO(cartesian_mm)) return false; // Minimum number of seconds to move the given distance float seconds = cartesian_mm / _feedrate_mm_s; // The number of segments-per-second times the duration // gives the number of segments we should produce uint16_t segments = delta_segments_per_second * seconds; //#if IS_SCARA // NOMORE(segments, cartesian_mm * 2); //#endif NOLESS(segments, 1); // Each segment produces this much of the move float inv_segments = 1.0 / segments, segment_distance[XYZE] = { difference[X_AXIS] * inv_segments, difference[Y_AXIS] * inv_segments, difference[Z_AXIS] * inv_segments, difference[E_AXIS] * inv_segments }; // SERIAL_ECHOPAIR("mm=", cartesian_mm); // SERIAL_ECHOPAIR(" seconds=", seconds); // SERIAL_ECHOLNPAIR(" segments=", segments); // Send all the segments to the planner float raw[XYZE] = { 0.0f }; if (use_raw_kinematics) { //#define DELTA_E raw[E_AXIS] //#define DELTA_NEXT(ADDEND) LOOP_XYZE(i) raw[i] += ADDEND; //#define DELTA_IK() do { \ // delta[A_AXIS] = DELTA_Z(1); \ // delta[B_AXIS] = DELTA_Z(2); \ // delta[C_AXIS] = DELTA_Z(3); \ //} while(0) // Get the raw current position as starting point raw[0] = RAW_CURRENT_POSITION(X_AXIS); raw[1] = RAW_CURRENT_POSITION(Y_AXIS); raw[2] = RAW_CURRENT_POSITION(Z_AXIS); raw[3] = RAW_CURRENT_POSITION(E_AXIS); } else { //#define DELTA_E logical[E_AXIS] //#define DELTA_NEXT(ADDEND) LOOP_XYZE(i) logical[i] += ADDEND; //#define DELTA_IK() DELTA_LOGICAL_IK() // Get the logical current position as starting point LOOP_XYZE(i) logical[i] = current_position[i]; } if (use_delta_ik_interpolation) { // Get the starting delta for interpolation if (segments >= 2) inverse_kinematics(logical); for (uint16_t s = segments + 1; --s;) { if (s > 1) { // Save the previous delta for interpolation float prev_delta[ABC] = { delta[A_AXIS], delta[B_AXIS], delta[C_AXIS] }; // Get the delta 2 segments ahead (rather than the next) if (use_raw_kinematics) { //DELTA_NEXT(segment_distance[i] + segment_distance[i]); //DELTA_IK(); LOOP_XYZE(i) raw[i] += segment_distance[i] + segment_distance[i]; do { delta[A_AXIS] = DELTA_Z(1); delta[B_AXIS] = DELTA_Z(2); delta[C_AXIS] = DELTA_Z(3); } while(0); } else { //DELTA_NEXT(segment_distance[i] + segment_distance[i]); //DELTA_IK(); LOOP_XYZE(i) logical[i] += segment_distance[i] + segment_distance[i]; DELTA_LOGICAL_IK(); } // Move to the interpolated delta position first buffer_line( (prev_delta[A_AXIS] + delta[A_AXIS]) * 0.5, (prev_delta[B_AXIS] + delta[B_AXIS]) * 0.5, (prev_delta[C_AXIS] + delta[C_AXIS]) * 0.5, logical[E_AXIS], _feedrate_mm_s, active_extruder, calc_err ); // Do an extra decrement of the loop --s; } else { // Get the last segment delta (only when segments is odd) if (use_raw_kinematics) { //DELTA_NEXT(segment_distance[i]) //DELTA_IK(); LOOP_XYZE(i) raw[i] += segment_distance[i]; do { delta[A_AXIS] = DELTA_Z(1); delta[B_AXIS] = DELTA_Z(2); delta[C_AXIS] = DELTA_Z(3); } while(0); } else { //DELTA_NEXT(segment_distance[i]) //DELTA_IK(); LOOP_XYZE(i) logical[i] += segment_distance[i]; DELTA_LOGICAL_IK(); } } // Move to the non-interpolated position if (use_raw_kinematics) { buffer_line(delta[A_AXIS], delta[B_AXIS], delta[C_AXIS], raw[E_AXIS], _feedrate_mm_s, active_extruder, calc_err); } else { buffer_line(delta[A_AXIS], delta[B_AXIS], delta[C_AXIS], logical[E_AXIS], _feedrate_mm_s, active_extruder, calc_err); } } } else { // For non-interpolated delta calculate every segment for (uint16_t s = segments + 1; --s;) { if (use_raw_kinematics) { //DELTA_NEXT(segment_distance[i]) //DELTA_IK(); LOOP_XYZE(i) raw[i] += segment_distance[i]; do { delta[A_AXIS] = DELTA_Z(1); delta[B_AXIS] = DELTA_Z(2); delta[C_AXIS] = DELTA_Z(3); } while(0); } else { //DELTA_NEXT(segment_distance[i]) //DELTA_IK(); LOOP_XYZE(i) logical[i] += segment_distance[i]; DELTA_LOGICAL_IK(); } buffer_line(delta[A_AXIS], delta[B_AXIS], delta[C_AXIS], logical[E_AXIS], _feedrate_mm_s, active_extruder, calc_err); } } return true; } void bench(bool use_raw_kinematics, bool use_delta_ik_interpolation) { uint_fast32_t t, count = 0; double err_tmp[ABC] = { 0.0 }, max_err[ABC] = { 0.0 }, ave_err[ABC] = { 0.0 }; const char* str[3] = {"delta[A_AXIS] ", "delta[B_AXIS] ", "delta[C_AXIS] " }; t = micros(); for (uint_fast16_t a = 0; a < 100; a++) { for (uint8_t b = 0; b < NUM_AXIS; b++) destination[b] = a; prepare_kinematic_move_to(destination, use_raw_kinematics, use_delta_ik_interpolation, false); } t = micros() - t; Serial.print("calculation time of prepare_kinematic_move_to() ("); if (use_raw_kinematics) Serial.print("USE_RAW_KINEMATICS "); if (use_delta_ik_interpolation) Serial.print("USE_DELTA_IK_INTERPOLATION "); if (use_raw_kinematics || use_delta_ik_interpolation) { Serial.print("enabled"); } else { Serial.print("not optimized"); } Serial.print("): "); Serial.println(t); if (use_raw_kinematics || use_delta_ik_interpolation) { for (float a = 0.0f; a < 10.0f; a += 0.01f) { count++; for (uint8_t b = 0; b < NUM_AXIS; b++) destination[b] = a; prepare_kinematic_move_to(destination, false, false, true); for (uint8_t i = 0; i < ABC; i++) err_tmp[i] = err_tmp_tmp[i]; prepare_kinematic_move_to(destination, use_raw_kinematics, use_delta_ik_interpolation, true); for (uint8_t i = 0; i < ABC; i++) { err_tmp[i] = err_tmp[i] - err_tmp_tmp[i]; ave_err[i] += err_tmp[i]; if (abs(err_tmp[i]) > max_err[i]) max_err[i] = abs(err_tmp[i]); } } Serial.println("maximum error with prepare_kinematic_move_to() (not optimized):"); for (uint8_t i = 0; i < ABC; i++) { Serial.print(str[i]); if (max_err[i] != 0) Serial.println((float)max_err[i], 7); else Serial.println("nodiff"); } Serial.println("average error with prepare_kinematic_move_to() (not optimized):"); for (uint8_t i = 0; i < ABC; i++) { Serial.print(str[i]); Serial.println((float)(ave_err[i] / count), 7); } } Serial.print("\n"); } void setup() { Serial.begin(250000); } void loop() { Serial.println("Starting long calculation:"); Serial.print("\n"); bench(false, false); bench(true, false); bench(false, true); bench(true, true); Serial.print("\n"); delay(1000); } ```

Result:

calculation time of prepare_kinematic_move_to() (not optimized): 26387400

calculation time of prepare_kinematic_move_to() (USE_RAW_KINEMATICS enabled): 23953812
maximum error with prepare_kinematic_move_to() (not optimized):
delta[A_AXIS] 0.0000153
delta[B_AXIS] 0.0000305
delta[C_AXIS] 0.0000458
average error with prepare_kinematic_move_to() (not optimized):
delta[A_AXIS] -0.0000002
delta[B_AXIS] -0.0000001
delta[C_AXIS] 0.0000001

calculation time of prepare_kinematic_move_to() (USE_DELTA_IK_INTERPOLATION enabled): 17734760
maximum error with prepare_kinematic_move_to() (not optimized):
delta[A_AXIS] 0.0000153
delta[B_AXIS] 0.0000305
delta[C_AXIS] 0.0000305
average error with prepare_kinematic_move_to() (not optimized):
delta[A_AXIS] -0.0000003
delta[B_AXIS] -0.0000002
delta[C_AXIS] 0.0000002

calculation time of prepare_kinematic_move_to() (USE_RAW_KINEMATICS USE_DELTA_IK_INTERPOLATION enabled): 16438640
maximum error with prepare_kinematic_move_to() (not optimized):
delta[A_AXIS] 0.0000153
delta[B_AXIS] 0.0000305
delta[C_AXIS] 0.0000305
average error with prepare_kinematic_move_to() (not optimized):
delta[A_AXIS] -0.0000003
delta[B_AXIS] -0.0000002
delta[C_AXIS] 0.0000002
ghost commented 8 years ago

Are small (and slow) moves too aggressively segmented by prepare_kinematic_move_to? Does it make sense to split up lines into segments smaller than 0.2mm? Would vertical deviation at that scale make any noticeable difference in the print quality?

Sorry, I haven't knowledge for answering about those at this time...

thinkyhead commented 8 years ago

From 23,953,812µs down to 16,438,640µs saves 7,515,172µs, or 7.5 whole seconds. Or, about 32% faster. That's not too bad.

The one place I can see trouble is if you have something like: 12.5 … 12.7 … 12.4 where the middle move would be completely missed because we're at the top (or bottom) of the movement. This would end up interpolated as 12.5 … 12.45 … 12.4.

lrpirlet commented 8 years ago

@thinkyhead @Roxy-3D @esenapaj This thread "slipped" away from the original subject... This is fine, as I did learn some , but I will let anyone of you close it, because I am not too sure that the subject has been exhausted...

Roxy-3D commented 8 years ago

Yeah... That does seem to happen! :) I think we have resolution on what caused the problem with the 'M' (Map) function, and I already have the system changed to accept either a lower case 'm' or an upper case 'O'. So... The option can be used on Repetier Host now.

repetier commented 8 years ago

" I already have the system changed to accept either a lower case 'm' or an upper case 'O'"

O is fine, but comment on lower case m: users are lazy and often write lower letters meaning someone will fix it and since older firmwares did not fix, host uppercases gcode letters normally if nor forced to send original command. As I understand GCode specification all letters were supposed uppercase anyway so relying on upper/lower case is a dangerous path.

thinkyhead commented 8 years ago

I concur. No lower-case, please. That's just not good. I shouldn't need to explain why.

Roxy-3D commented 8 years ago

Alright.. I agree... Let's not cause any problems needlessly!

github-actions[bot] commented 2 years ago

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.