Duet3D Logo Duet3D
    • Tags
    • Documentation
    • Order
    • Register
    • Login
    1. Home
    2. Schmart
    3. Best
    • Profile
    • Following 0
    • Followers 1
    • Topics 6
    • Posts 75
    • Best 7
    • Controversial 0
    • Groups 0

    Best posts made by Schmart

    • Pressure advance tuning with conditional G-code

      I've created a conditional G-code macro that prints a model to help tune pressure advance. It's based on the model used in this post, which I happen to like very much. It's a fast and native solution, starts from your printer directly and requires no slicer or other software.

      The macro is a work-in-progress, but offers a lot of configurability already. I will probably make a number of improvements as I use it myself, but I wanted to share this with you at this stage for feedback and educational purposes (i.e. my education).

      I can't guarantee that this code will work properly on your machine. I have a simple cartesian model with XYZE axes. Also, I've tested this on my printer with a self-compiled post-beta 5 firmware 3.4.0, since I ran into some rounding issues in the output of earlier RRF firmwares, though this has been fixed by @dc42 and I've seen mentioned that this fix is part of the beta 6 release.

      I've kept the variable names as descriptive as possible. However, let me know if you need explanation on how to use this:

      ; This macro generates a test print to determine the best pressure advance factor (M572 S-parameter)
      ; It uses the test print described in this forum post:
      ; https://forum.duet3d.com/topic/6698/pressure-advance-calibration
      
      ; Author: Schmart
      
      ; WARNING: all dimensions, size units and speeds are in mm and mm/s
      
      ; Reference for conditional G-code:
      ; - https://duet3d.dozuki.com/Wiki/GCode_Meta_Commands
      
      ; Wishes for conditional G-code:
      ; - Canceling or stopping running conditional G-code
      ; - Simulation; logging what commands would be executed/generated without actually executing them
      ; - Dumping the variables (names and values) in a macro, perhaps also treating variable in a meta-way, e.g. {#var} for count, {var[0].name} and {var['layer_height'].value}
      ; - Assigning an object or array to a variable. E.g. var axis = move.axes[0]; echo {var.axis.letter}
      ; - Custom subroutine/function definition, e.g. def print_line(var a, var b)
      ; - ceiling(), pow() and sq() functions
      
      ; TODO:
      ; - Many optimizations still remaining
      ; - Perhaps the most important variables (bed and print temperature, filament diameter, extrusion multiplier, PA stepping)
      ;   can be made into macro parameters, e.g. M98 P"0:/macros/4 - Print Tuning/Pressure Advance" B40 P204 D1.75 X0.95 S0.02 P0.002
      
      ; Starting value for pressure advance
      var pa_start = 0.0
      ; Pressure advance increment for each (whole) millimeter print height
      ; e.g. if var.height = 20, var.pa_start = 0.1 and var.pa_stepping = 0.004, then
      ; the PA test range is from 0.1 to (0.1 + 20 * 0.004) => 0.1 to 0.18.
      var pa_stepping = 0.01
      ; The extruder to apply the pressure advance factor to
      var pa_extruder = 0
      ; These values specify the center of the print bed
      ; The default bed center is at (0,0)
      var x_center = 0
      var y_center = 0
      ; Print temperatures
      var print_temperature = 205
      var standby_temperature = 120
      var bed_temperature = 40
      ; Height of the test print
      var height = 20
      var layer_height = 0.20
      ; Number of fast segments
      var fast_segments = 3
      ; Slow segments are at the beginning and the end, and in between fast segments, e.g. SLOW FAST SLOW FAST SLOW
      var slow_segment_length = 10
      var fast_segment_length = 20
      ; Definition of speeds
      var first_layer_speed = 20
      var travel_speed = 80
      var fast_segment_speed = 60
      var slow_segment_speed = 10
      ; Number of perimeters around the object as a stable base
      var skirt_loops = 6
      ; Height of the skirt in layers
      var skirt_layers = 2
      ; The tool number to print with
      var tool_number = 0
      ; Extrusion width is calculated here, but can also be set with a literal value
      ; Note that 1.05 and 1.125 are common factors that result in 0.42mm or 0.45mm width respectively
      var nozzle_bore_diameter = 0.40
      var extrusion_width = {var.nozzle_bore_diameter * 1.125}
      var filament_diameter = 1.78
      var extrusion_multiplier = 0.93
      ; Firmware retraction settings
      var retract_length = 0.5
      var retract_restart_length = 0
      var retract_speed = 40
      var deretract_speed = 40
      var retract_z_lift = 0
      
      ; Flow math
      var filament_flow = {pi * var.filament_diameter * var.filament_diameter / 4}
      var regular_flow = {(var.extrusion_width - var.layer_height) * var.layer_height + pi * var.layer_height * var.layer_height / 4}
      var bridge_flow = {pi * var.nozzle_bore_diameter * var.nozzle_bore_diameter / 4}
      var line_spacing = {var.extrusion_width - var.layer_height * (1 - pi / 4)}
      var regular_flow_ratio = { var.extrusion_multiplier * var.regular_flow / var.filament_flow}
      var purge_line_flow_ratio = { 2.0 * var.regular_flow_ratio }
      
      echo "extrusion_width: " ^ var.extrusion_width
      echo "layer_height: " ^ var.layer_height
      echo "filament_flow: " ^ var.filament_flow
      echo "bridge_flow: " ^ var.bridge_flow
      echo "regular_flow: " ^ var.regular_flow
      echo "line_spacing: " ^ var.line_spacing
      echo "regular_flow_ratio: " ^ var.regular_flow_ratio
      
      ;M37 S1 ; Enter simulation mode
      
      ; Set firmware retraction
      M207 S{var.retract_length} R{var.retract_restart_length} F{60 * var.retract_speed} T{60 * var.deretract_speed} Z{var.retract_z_lift}
      
      T{var.tool_number} ; Select tool
      M106 S0 ; Turn off part cooling fan
      M568 P{var.tool_number} S{var.print_temperature} R{var.standby_temperature} A1 ; Set tool to standby temperature
      M190 S{var.bed_temperature} ; Wait for bed temperature to reach setpoint
      M116 P{var.tool_number} ; Wait for temperatures associated with the selected tool to be reached
      
      ; Make an inventory of axes that have not yet been homed
      var axes = ""
      echo "Total number of axes: " ^ {#move.axes}
      while {iterations < #move.axes}
        if {!move.axes[iterations].homed}
          set var.axes = {var.axes ^ move.axes[iterations].letter}
      
      ; Home applicable axes
      echo "Axes to be homed: " ^ var.axes
      G28 {var.axes}
      
      ;G28 XYZ ; Home the X, Y and Z axes
      ;G28 XY ; Home the X and Y axes
      ;G28 Z ; Home the Z axis
      
      G21 ; Set units to millimeters
      M83 ; Use relative distances for extrusion
      
      ; Calculate object width
      var width = {var.fast_segments * var.fast_segment_length + (1 + var.fast_segments) * var.slow_segment_length}
      ; Calculate starting coordinates and other constant(s)
      var x_start = {var.x_center - 0.5 * var.width + var.skirt_loops * var.line_spacing}
      var y_start = {var.y_center - 0.5 * var.line_spacing + var.skirt_loops * var.line_spacing}
      var travel_feedrate = {60 * var.travel_speed}
      var first_layer_feedrate = {60 * var.first_layer_speed}
      
      ; Absolute position for purge line in X and Y space, 50 mm behind model
      G90 ; Use absolute coordinates
      G1 X{var.x_start} Y{var.y_start + 50} F{var.travel_feedrate}
      
      ; Set heater to final temperature and wait
      M568 A2
      M116 P{var.tool_number}
      
      ; Absolute position of nozzle at first layer height
      G1 Z{var.layer_height} F{var.travel_feedrate}
      
      ; Relatively print two fat purge lines
      G91 ; Switch to relative coordinates
      G1 X{var.width} E{var.width * var.purge_line_flow_ratio} F{var.first_layer_feedrate}
      G1 Y{-(2 * var.line_spacing)} F{var.travel_feedrate}
      G1 X{-var.width} E{var.width * var.purge_line_flow_ratio} F{var.first_layer_feedrate}
      
      G10 ; Retract to prevent oozing
      
      ; Move to the start of the model in X, Y and Z space
      ; The skirt code also moves to the start, but the skirt can be disabled.
      ; Also, the skirt code does not set Z, and there may be no purge line for which Z is set. Safety first.
      G90 ; Use absolute coordinates
      G1 X{var.x_start} Y{var.y_start} Z{var.layer_height} F{var.travel_feedrate}
      G91 ; Switch to relative coordinates
      
      G11 ; Advance/unretract/deretract in preparation to print
      
      ; Routine for printing the test object
      var layers = {floor(var.height / var.layer_height)}
      echo "Total number of layers: " ^ var.layers
      
      while {iterations < var.layers}
        ; Track current layer
        var layer = {iterations + 1}
        ; Current height in mm
        var z = {var.layer * var.layer_height}
        ; Calculate pressure advance factor
        var pa = {var.pa_start + floor(var.z) * var.pa_stepping}
        ; Set pressure advance
        M572 D{var.pa_extruder} S{var.pa}
      
        ; Output some statistics while printing
        echo "Layer " ^ iterations ^ " (" ^ {iterations + 1} ^ " of " ^ {var.layers} ^ " at " ^ {var.z} ^ "mm)"
        echo "Pressure advance: " ^ {var.pa}
      
        ; Pre-calculate feedrates for first layer and other layers
        var slow_segment_feedrate = {60 * (var.layer == 1 ? var.first_layer_speed : var.slow_segment_speed)}
        var fast_segment_feedrate = {60 * (var.layer == 1 ? var.first_layer_speed : var.fast_segment_speed)}
      
        ; Print skirt
        if {iterations < var.skirt_layers}
          G90 ; Use absolute coordinates
          ; Move to absolute XY start coordinates
          G1 X{var.x_start} Y{var.y_start} F{var.travel_feedrate}
          G91 ; Switch to relative coordinates
      
          ; Print all loops of the skirt
          while {iterations < var.skirt_loops}
            var skirt_loop = {var.skirt_loops - iterations}
            var x = {var.width + 2 * var.skirt_loop * var.line_spacing}
            var y = {var.line_spacing + 2 * var.skirt_loop * var.line_spacing}
            ; Print one full skirt loop
            while iterations < 2
              var direction = {iterations == 0 ? 1 : -1}
              G1 X{var.direction * var.x} E{var.x * var.regular_flow_ratio} F{var.fast_segment_feedrate}
              G1 Y{var.direction * var.y} E{var.y * var.regular_flow_ratio} F{var.fast_segment_feedrate}
            ; Travel to the start of the next skirt loop
            G1 X{var.line_spacing} Y{var.line_spacing} F{var.travel_feedrate}
      
        ; Print two perimeters back and forth of alternating slow and fast segments
        while iterations < 2
          var direction = {iterations == 0 ? 1 : -1}
          ; Slow starting segment (X)
          G1 X{var.direction * var.slow_segment_length} E{var.slow_segment_length * var.regular_flow_ratio} F{var.slow_segment_feedrate}
          ; Remaining fast and slow segments (X)
          while iterations < var.fast_segments
            G1 X{var.direction * var.fast_segment_length} E{var.fast_segment_length * var.regular_flow_ratio} F{var.fast_segment_feedrate}
            G1 X{var.direction * var.slow_segment_length} E{var.slow_segment_length * var.regular_flow_ratio} F{var.slow_segment_feedrate}
          ; Print the side perimeter (Y)
          G1 Y{var.direction * var.line_spacing} E{var.line_spacing * var.regular_flow_ratio} F{var.slow_segment_feedrate}
      
        ; Move one layer up
        G1 Z{var.layer_height} F{var.travel_feedrate}
      
      G10 ; Retract
      
      G91 ; Relative positioning
      G1 F3000 Z20 ; Move gantry up 20mm
      G90 ; Absolute positioning
      G28 X ; Home X axis
      ;M104 S0 ; Turn off nozzle heat block
      M568 P{var.tool_number} S0 R0 A2 ; Set required heater temperature off
      M140 S0 ; Turn off bed
      M106 S0 ; Turn off part cooling fan
      M18 ; Disable stepper motors
      
      ;M37 S0 ; Leave simulation mode
      

      Photo of one print in progress and one done:

      IMG_9105.jpg

      Lastly, I'm so happy with the concept and speed, that I'm now toying with the idea to make macros for:

      • Tuning bridge parameters (with flow, temperature and part cooling as variables)
      • Layer adhesion (with temperature and part cooling are variable)
      • Tuning the extrusion multiplier

      EDIT: The code didn't look right when it contains words like "doesn't" and "haven't" in comments. This results in random "{0}" inserted and illegal G-code.

      posted in Gcode meta commands
      Schmartundefined
      Schmart
    • RE: RepRapFirmware 3.01-RC4 and DSF 1.2.5 now available

      Hi @arhi, documentation is not yet fully updated with the added behavior, but M220 essentially scales the print speed, as if you set higher or lower print speeds in your slicer. M220 now does not apply to retract/unretract and move commands in macros anymore.

      M221 scales the extrusion, which is independent of M220. So if you want 50% higher speed and 5% more extrusion, that would be M220 S150 and M221 S105.

      posted in Beta Firmware
      Schmartundefined
      Schmart
    • RE: Duet 3 Mini 5+ : Initial announcement

      That is the intention, however it's one of the few hardware features of the board that I haven't yet written the firmware support for.

      @dc42 do you mean support for the Neopixel backlight specifically or the MiniPanel itself? Because you're welcome to use the code in my fork of RRF3 for the MiniPanel support. I've been using this build successfully for many weeks now. I suppose that even if the Mini 5+ hardware side changed a bit, my work could still save you some time.

      BTW, I'm also interested in beta testing the new board. I have this 'gift' of being detail oriented and noticing issues. Also curious to find out if the TMC2209 drivers will better cooperate with my steppers compared to the TMC2224 drivers on my Maestro.

      posted in General Discussion
      Schmartundefined
      Schmart
    • RE: Pressure advance tuning with conditional G-code

      @t3p3tony Apologies, I was indeed referring to the markdown representation of G-code in the forum. Fortunately, the firmware itself handles comments with single quotes without any issue. @o_lampe I wasn't aware of the 'perl' option, I will try this when I'm back home!

      posted in Gcode meta commands
      Schmartundefined
      Schmart
    • RE: Genuine BLTouch v3.0 weirdness with RFF >= 3.3

      @dc42 I hooked up my logic analyser to the zprobe.mod and VCC pins of the BLTouch, and tested a couple of scenarios with both RRF 3.2.2 and RRF 3.4.0 beta 4, including the startup sequence, reset and deploy.

      I'm not seeing differences in the PWM generated by the M280 commands between the two firmwares.

      However, I do see a significant difference between the firmwares in the time it takes from VCC fully powering the BLTouch and the zprobe.mod pin going LOW:

      • RRF 3.2.2 takes about 20.6ms
      • RRF 3.4.0 takes about 40.6ms

      Here are my measurements. I performed these multiple times to verify the timings were consistent. Channel 0 is zprobe.mod and Channel 1 is VCC to the BLTouch. T0 is when I power on the printer (small power spike) and after about 1.2-1.3s it seems that the board starts initialisation.

      RRF 3.2.2:

      RRF 3.2.2 Startup (zprobe.mod and VCC).png

      RRF 3.4.0 beta 4:

      RRF 3.4.0 beta 4 Startup (zprobe.mod and VCC).png

      My theory is that on startup of the Duet 2 Maestro with RRF 3.4.0, the BLTouch interrupts its self-test because zprobe.mod happens to turn LOW in the middle of that process. Once it has entered that error state, it remains there until it can successfully complete its self-test.

      This effect is also described on the Antclabs site:

      BLTouch Flashing 80% Duty Cycle.png

      I suspect that the reason that this doesn't happen on RRF 3.2.2, is that it turns the zprobe.mod pin LOW before the BLTouch starts its self-test.

      I looked at the Platform.cpp code, is there a safe way to turn zprobe.mod low earlier (UrgentInit() perhaps?) or delay the InitZProbe() by the 100ms 'non-PWM Time' that Antclabs recommends?

      Signal timing

      posted in Beta Firmware
      Schmartundefined
      Schmart
    • RE: Pressure advance tuning with conditional G-code

      @t3p3tony said in Pressure advance tuning with conditional G-code:

      @cncmodeller, @Schmart you would need to use a global variable, set in the sub macro, to pass information back to the main calling macro.

      Hi Tony, I had already done some testing with moving subroutine code in separate macros. On the surface, it seems that, between the Neanderthal approach of copying and pasting code, and re-using logic through parameterized macros, the balance tips towards the latter. However, I came across some conceptual and technical downsides that made me decide otherwise:

      • Abstraction. I consider the macro as a vehicle for an end-user, not a developer; a cohesive unit of code that an end user would want to run. The macro is the 'public interface' to the functionality provided. A 'submacro' should not be able to have a public interface since it doesn't provide a useful experience by itself.
      • Coupling and reliability. Subroutines exist to improve the maintainability of code and are not intended for an end-user to touch or run. If a unit of code is spread across multiple macros, it's easy for the end user to break functionality (e.g. remove a macro) or never get the functionality to run (e.g. not naming macros correctly). The absolute locations and names of macros are suddenly important and tightly coupled to the whole solution.
      • Deployment and versioning. Depending on the complexity of the test print I want to generate, there could be many submacros involved, for things like circles, infill, fonts, etc. Splitting a solution in (potentially) many different macros complicates the setup, handling and publishing of code and updates. This becomes even more challenging when the same submacro is used by multiple main macros and the submacro needs an update that maintains backward compatibility.
      • Testability. When using submacros, I have even less troubleshooting options and I can't isolate problems in my code properly. The only surefire way to test a multi-macro solution is to actually execute it with M98 and send it to the printer. The developer experience is therefore not optimal:
        1. If I call the main macro with M37 P"parent", the M98 P"child" within the main macro (with the purpose of calling the submacro) is not executed. Submacros are only called when I execute the main macro with M98.
        2. If I send an M37 S1 command in the DWC console, this causes any echo statement to be suppressed, leaving me fully in the dark. Using M37 S1 in the main macro is an option, but then submacros are still not called.
        3. Isolated testing of a 'submacro' is not possible since M37 doesn't accept parameters. Wrapping that submacro with another macro is of no use because of point #1.
      • Scope. Traditionally, variables in a parent's scope are available to a subroutine, function, a C++ preprocessor directive or a code fragment that's simply #included from another file. However, variables in a parent macro cannot be shared with a child macro, and that makes sense in terms of separation of concerns. Ways to solve this:
        • Making all or only the necessary variables global is an option, though, to avoid naming clashes, these should be namespaced properly. However, I consider this a workaround. I believe the concept of global variables is more suited to defining a small number of shared and relatively static or read-only printer settings. Global variables don't really seem intended for inter-scope communication where there are a lot of global variables in use and where they continually change.
        • Using parameterized macros is also an option. However, because of parameter naming requirements, the number of variables that can be passed to a macro is limited. You can use single letters only, and three or four letters are already reserved (G, M, N and T). Additionally, this requires me to convert back and forth between highly recognizable local variable names and obscure macro parameter names, and keep the mapping between these logical.
      • Performance. I haven't yet determined if the speed with which macros can be called would be an issue, so whether e.g. caching of called macros is implemented. For sections of a print that need to be executed quickly and frequently, this would be a requirement to prevent stuttering in the movement and blobs in the print. Accuracy and timing is pretty important in test prints, so that's something I need to avoid.

      All in all, I've settled on putting the code as one big chunk in a single macro for now, and my hopes for the future are that some of the remarks above can be addressed!


      I've included the test code. I had to slightly clean up the code to include it in this post.

      Hopefully that provides some additional clarification to the above epistle (TLDR anyone? 😉 ).

      _Parent code

      Run this with M37 P"0:/macros/_Parent". Note how messages from the child macro are not included. Running this with M98 P"0:/macros/_Parent" does call the child macro, but âš  also actually tries to print this. Wrapping the parent code between M37 S1 and M37 S0 commands and running it with M98 also ignores the call to the child macro. At least on my RepRapFirmware 3.4.0 beta 6.

      echo "Parent started"
      
      while {iterations < 10}
        echo "Calling child macro, iteration #" ^ {iterations}
        M98 P"0:/macros/_Child" X50 Y50 F2400 R6000
      
      echo "Parent ended"
      

      _Child code

      Note that a standalone simulation with M37 P"0:/macros/_Child" X100 Y100 F2400 R6000 does not work because M37 does not pass parameters, and causes unknown parameter errors.

      var size_x = {param.X}
      var size_y = {param.Y}
      var feedrate = {param.F}
      var travel_feedrate = {param.R}
      
      echo "Child started"
      
      ; Print single-perimeter rectangle
      G90 ; Use absolute coordinates
      G1 X{-var.size_x / 2} Y{-var.size_y / 2} F{var.travel_feedrate}
      G91 ; Use relative coordinates
      G1 X{var.size_x} F{var.feedrate}
      G1 Y{var.size_y} F{var.feedrate}
      G1 X{-var.size_x} F{var.feedrate}
      G1 Y{-var.size_y} F{var.feedrate}
      
      echo "Child ended"
      
      posted in Gcode meta commands
      Schmartundefined
      Schmart
    • RE: Genuine BLTouch v3.0 weirdness with RFF >= 3.3

      @dc42 Apologies, I neglected to mention that I had tried this, both the delay/dwell and moving around commands in the config.g. I'm confident that zprobe.mod pin going LOW happens before my config.g even gets loaded. The G4 S10 I used at the start of config.g really made this clear.

      That's why I mentioned there's maybe something that can be done relatively quickly after board startup, e.g. in UrgentInit() or Platform::Init(), or perhaps even prevent the pin going HIGH in the first place (I haven't yet checked if the SAM4S8C initializes pins HIGH by default, or that this is done by a specific piece of RRF code).

      As a proof of concept, I've built the latest GitHub branch 3.4-dev and modified Init() in Platform.cpp slightly with just one statement added at the beginning:

      // Initialise the Platform. Note: this is the first module to be initialised, so don't call other modules from here!
      void Platform::Init() noexcept
      {
      	// Prevent BLTouch from receiving a delayed transition to LOW on its zprobe.mod pin.
      	// This confuses the self-test process and throws the BLTouch in a 80% duty cycle blinking mode.
      	// DiagPin is identical to Z_PROBE_MOD_PIN on Duet 2 Maestro.
      	// This statement was also present in earlier firmwares, e.g. <= 3.2.2
      	pinMode(DiagPin, OUTPUT_LOW);
      

      This has drastically reduced the time that it takes for zprobe.mod to become LOW. It now only takes 8.6ms (!) measured from the point that VCC is stable:

      RRF 3.4.0 beta 4 Startup (Modified Init()).png

      I can happily report that this quick fix has proven that I found the culprit and that this has solved the issue. The BLTouch is now performing its self-test completely and correctly: the pin deploys and retracts twice when powering on the printer and the red LED is on continuously now.

      P.S. I had mentioned UrgentInit() and InitZProbe() before, but I was looking at an older dev branch, and things have changed considerably since then with the whole state machine in Platform::Tick(), so please forget I mentioned that.

      posted in Beta Firmware
      Schmartundefined
      Schmart