G6 Direct Stepping (#17853)

This commit is contained in:
Colin Godsey 2020-05-11 18:22:41 -06:00 committed by GitHub
parent 174e41c17d
commit 8a22ef0c83
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 859 additions and 64 deletions

View file

@ -1663,6 +1663,16 @@
// Support for G5 with XYZE destination and IJPQ offsets. Requires ~2666 bytes. // Support for G5 with XYZE destination and IJPQ offsets. Requires ~2666 bytes.
//#define BEZIER_CURVE_SUPPORT //#define BEZIER_CURVE_SUPPORT
/**
* Direct Stepping
*
* Comparable to the method used by Klipper, G6 direct stepping significantly
* reduces motion calculations, increases top printing speeds, and results in
* less step aliasing by calculating all motions in advance.
* Preparing your G-code: https://github.com/colinrgodsey/step-daemon
*/
//#define DIRECT_STEPPING
/** /**
* G38 Probe Target * G38 Probe Target
* *
@ -1731,14 +1741,16 @@
//================================= Buffers ================================= //================================= Buffers =================================
//=========================================================================== //===========================================================================
// @section hidden // @section motion
// The number of linear motions that can be in the plan at any give time. // The number of lineear moves that can be in the planner at once.
// THE BLOCK_BUFFER_SIZE NEEDS TO BE A POWER OF 2 (e.g. 8, 16, 32) because shifts and ors are used to do the ring-buffering. // The value of BLOCK_BUFFER_SIZE must be a power of 2 (e.g. 8, 16, 32)
#if ENABLED(SDSUPPORT) #if BOTH(SDSUPPORT, DIRECT_STEPPING)
#define BLOCK_BUFFER_SIZE 16 // SD,LCD,Buttons take more memory, block buffer needs to be smaller #define BLOCK_BUFFER_SIZE 8
#elif ENABLED(SDSUPPORT)
#define BLOCK_BUFFER_SIZE 16
#else #else
#define BLOCK_BUFFER_SIZE 16 // maximize block buffer #define BLOCK_BUFFER_SIZE 16
#endif #endif
// @section serial // @section serial

View file

@ -43,6 +43,10 @@
#include "MarlinSerial.h" #include "MarlinSerial.h"
#include "../../MarlinCore.h" #include "../../MarlinCore.h"
#if ENABLED(DIRECT_STEPPING)
#include "../../feature/direct_stepping.h"
#endif
template<typename Cfg> typename MarlinSerial<Cfg>::ring_buffer_r MarlinSerial<Cfg>::rx_buffer = { 0, 0, { 0 } }; template<typename Cfg> typename MarlinSerial<Cfg>::ring_buffer_r MarlinSerial<Cfg>::rx_buffer = { 0, 0, { 0 } };
template<typename Cfg> typename MarlinSerial<Cfg>::ring_buffer_t MarlinSerial<Cfg>::tx_buffer = { 0 }; template<typename Cfg> typename MarlinSerial<Cfg>::ring_buffer_t MarlinSerial<Cfg>::tx_buffer = { 0 };
template<typename Cfg> bool MarlinSerial<Cfg>::_written = false; template<typename Cfg> bool MarlinSerial<Cfg>::_written = false;
@ -131,6 +135,18 @@
static EmergencyParser::State emergency_state; // = EP_RESET static EmergencyParser::State emergency_state; // = EP_RESET
// This must read the R_UCSRA register before reading the received byte to detect error causes
if (Cfg::DROPPED_RX && B_DOR && !++rx_dropped_bytes) --rx_dropped_bytes;
if (Cfg::RX_OVERRUNS && B_DOR && !++rx_buffer_overruns) --rx_buffer_overruns;
if (Cfg::RX_FRAMING_ERRORS && B_FE && !++rx_framing_errors) --rx_framing_errors;
// Read the character from the USART
uint8_t c = R_UDR;
#if ENABLED(DIRECT_STEPPING)
if (page_manager.maybe_store_rxd_char(c)) return;
#endif
// Get the tail - Nothing can alter its value while this ISR is executing, but there's // Get the tail - Nothing can alter its value while this ISR is executing, but there's
// a chance that this ISR interrupted the main process while it was updating the index. // a chance that this ISR interrupted the main process while it was updating the index.
// The backup mechanism ensures the correct value is always returned. // The backup mechanism ensures the correct value is always returned.
@ -142,14 +158,6 @@
// Get the next element // Get the next element
ring_buffer_pos_t i = (ring_buffer_pos_t)(h + 1) & (ring_buffer_pos_t)(Cfg::RX_SIZE - 1); ring_buffer_pos_t i = (ring_buffer_pos_t)(h + 1) & (ring_buffer_pos_t)(Cfg::RX_SIZE - 1);
// This must read the R_UCSRA register before reading the received byte to detect error causes
if (Cfg::DROPPED_RX && B_DOR && !++rx_dropped_bytes) --rx_dropped_bytes;
if (Cfg::RX_OVERRUNS && B_DOR && !++rx_buffer_overruns) --rx_buffer_overruns;
if (Cfg::RX_FRAMING_ERRORS && B_FE && !++rx_framing_errors) --rx_framing_errors;
// Read the character from the USART
uint8_t c = R_UDR;
if (Cfg::EMERGENCYPARSER) emergency_parser.update(emergency_state, c); if (Cfg::EMERGENCYPARSER) emergency_parser.update(emergency_state, c);
// If the character is to be stored at the index just before the tail // If the character is to be stored at the index just before the tail

View file

@ -59,6 +59,10 @@
#include "gcode/parser.h" #include "gcode/parser.h"
#include "gcode/queue.h" #include "gcode/queue.h"
#if ENABLED(DIRECT_STEPPING)
#include "feature/direct_stepping.h"
#endif
#if ENABLED(TOUCH_BUTTONS) #if ENABLED(TOUCH_BUTTONS)
#include "feature/touch/xpt2046.h" #include "feature/touch/xpt2046.h"
#endif #endif
@ -713,6 +717,9 @@ void idle(TERN_(ADVANCED_PAUSE_FEATURE, bool no_stepper_sleep/*=false*/)) {
// Handle Joystick jogging // Handle Joystick jogging
TERN_(POLL_JOG, joystick.inject_jog_moves()); TERN_(POLL_JOG, joystick.inject_jog_moves());
// Direct Stepping
TERN_(DIRECT_STEPPING, page_manager.write_responses());
} }
/** /**
@ -1124,6 +1131,10 @@ void setup() {
SETUP_RUN(max7219.init()); SETUP_RUN(max7219.init());
#endif #endif
#if ENABLED(DIRECT_STEPPING)
SETUP_RUN(page_manager.init());
#endif
marlin_state = MF_RUNNING; marlin_state = MF_RUNNING;
SETUP_LOG("setup() completed."); SETUP_LOG("setup() completed.");

View file

@ -0,0 +1,273 @@
/**
* Marlin 3D Printer Firmware
* Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
*
* Based on Sprinter and grbl.
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "../inc/MarlinConfigPre.h"
#if ENABLED(DIRECT_STEPPING)
#include "direct_stepping.h"
#include "../MarlinCore.h"
#define CHECK_PAGE(I, R) do{ \
if (I >= sizeof(page_states) / sizeof(page_states[0])) { \
fatal_error = true; \
return R; \
} \
}while(0)
#define CHECK_PAGE_STATE(I, R, S) do { \
CHECK_PAGE(I, R); \
if (page_states[I] != S) { \
fatal_error = true; \
return R; \
} \
}while(0)
namespace DirectStepping {
template<typename Cfg>
State SerialPageManager<Cfg>::state;
template<typename Cfg>
volatile bool SerialPageManager<Cfg>::fatal_error;
template<typename Cfg>
volatile PageState SerialPageManager<Cfg>::page_states[Cfg::NUM_PAGES];
template<typename Cfg>
volatile bool SerialPageManager<Cfg>::page_states_dirty;
template<typename Cfg>
millis_t SerialPageManager<Cfg>::next_response;
template<typename Cfg>
uint8_t SerialPageManager<Cfg>::pages[Cfg::NUM_PAGES][Cfg::PAGE_SIZE];
template<typename Cfg>
uint8_t SerialPageManager<Cfg>::checksum;
template<typename Cfg>
typename Cfg::write_byte_idx_t SerialPageManager<Cfg>::write_byte_idx;
template<typename Cfg>
typename Cfg::page_idx_t SerialPageManager<Cfg>::write_page_idx;
template<typename Cfg>
typename Cfg::write_byte_idx_t SerialPageManager<Cfg>::write_page_size;
template <typename Cfg>
void SerialPageManager<Cfg>::init() {
for (int i = 0 ; i < Cfg::NUM_PAGES ; i++)
page_states[i] = PageState::FREE;
fatal_error = false;
next_response = 0;
state = State::NEWLINE;
page_states_dirty = false;
SERIAL_ECHOLNPGM("pages_ready");
}
template<typename Cfg>
FORCE_INLINE bool SerialPageManager<Cfg>::maybe_store_rxd_char(uint8_t c) {
switch (state) {
default:
case State::MONITOR:
switch (c) {
case '\n':
case '\r':
state = State::NEWLINE;
default:
return false;
}
case State::NEWLINE:
switch (c) {
case Cfg::CONTROL_CHAR:
state = State::ADDRESS;
return true;
case '\n':
case '\r':
state = State::NEWLINE;
return false;
default:
state = State::MONITOR;
return false;
}
case State::ADDRESS:
//TODO: 16 bit address, State::ADDRESS2
write_page_idx = c;
write_byte_idx = 0;
checksum = 0;
CHECK_PAGE(write_page_idx, true);
if (page_states[write_page_idx] == PageState::FAIL) {
// Special case for fail
state = State::UNFAIL;
return true;
}
set_page_state(write_page_idx, PageState::WRITING);
state = Cfg::DIRECTIONAL ? State::COLLECT : State::SIZE;
return true;
case State::SIZE:
// Zero means full page size
write_page_size = c;
state = State::COLLECT;
return true;
case State::COLLECT:
pages[write_page_idx][write_byte_idx++] = c;
checksum ^= c;
// check if still collecting
if (Cfg::PAGE_SIZE == 256) {
// special case for 8-bit, check if rolled back to 0
if (Cfg::DIRECTIONAL || !write_page_size) { // full 256 bytes
if (write_byte_idx) return true;
} else {
if (write_byte_idx < write_page_size) return true;
}
} else if (Cfg::DIRECTIONAL) {
if (write_byte_idx != Cfg::PAGE_SIZE) return true;
} else {
if (write_byte_idx < write_page_size) return true;
}
state = State::CHECKSUM;
return true;
case State::CHECKSUM: {
const PageState page_state = (checksum == c) ? PageState::OK : PageState::FAIL;
set_page_state(write_page_idx, page_state);
state = State::MONITOR;
return true;
}
case State::UNFAIL:
if (c == 0) {
set_page_state(write_page_idx, PageState::FREE);
} else {
fatal_error = true;
}
state = State::MONITOR;
return true;
}
}
template <typename Cfg>
void SerialPageManager<Cfg>::write_responses() {
if (fatal_error) {
kill(GET_TEXT(MSG_BAD_PAGE));
return;
}
// Runs on a set interval also, as responses may get lost.
if (next_response && next_response < millis()) {
page_states_dirty = true;
}
if (!page_states_dirty) return;
page_states_dirty = false;
next_response = millis() + Cfg::RESPONSE_INTERVAL_MS;
SERIAL_ECHO(Cfg::CONTROL_CHAR);
constexpr int state_bits = 2;
constexpr int n_bytes = Cfg::NUM_PAGES >> state_bits;
volatile uint8_t bits_b[n_bytes] = { 0 };
for (page_idx_t i = 0 ; i < Cfg::NUM_PAGES ; i++) {
bits_b[i >> state_bits] |= page_states[i] << ((i * state_bits) & 0x7);
}
uint8_t crc = 0;
for (uint8_t i = 0 ; i < n_bytes ; i++) {
crc ^= bits_b[i];
SERIAL_ECHO(bits_b[i]);
}
SERIAL_ECHO(crc);
SERIAL_EOL();
}
template <typename Cfg>
FORCE_INLINE void SerialPageManager<Cfg>::set_page_state(const page_idx_t page_idx, const PageState page_state) {
CHECK_PAGE(page_idx,);
page_states[page_idx] = page_state;
page_states_dirty = true;
}
template <>
FORCE_INLINE uint8_t *PageManager::get_page(const page_idx_t page_idx) {
CHECK_PAGE(page_idx, nullptr);
return pages[page_idx];
}
template <>
FORCE_INLINE void PageManager::free_page(const page_idx_t page_idx) {
set_page_state(page_idx, PageState::FREE);
}
};
DirectStepping::PageManager page_manager;
const uint8_t segment_table[DirectStepping::Config::NUM_SEGMENTS][DirectStepping::Config::SEGMENT_STEPS] PROGMEM = {
#if STEPPER_PAGE_FORMAT == SP_4x4D_128
{ 1, 1, 1, 1, 1, 1, 1, 0 }, // 0 = -7
{ 1, 1, 1, 0, 1, 1, 1, 0 }, // 1 = -6
{ 0, 1, 1, 0, 1, 0, 1, 1 }, // 2 = -5
{ 0, 1, 0, 1, 0, 1, 0, 1 }, // 3 = -4
{ 0, 1, 0, 0, 1, 0, 0, 1 }, // 4 = -3
{ 0, 0, 1, 0, 0, 0, 1, 0 }, // 5 = -2
{ 0, 0, 0, 0, 1, 0, 0, 0 }, // 6 = -1
{ 0, 0, 0, 0, 0, 0, 0, 0 }, // 7 = 0
{ 0, 0, 0, 0, 1, 0, 0, 0 }, // 8 = 1
{ 0, 0, 1, 0, 0, 0, 1, 0 }, // 9 = 2
{ 0, 1, 0, 0, 1, 0, 0, 1 }, // 10 = 3
{ 0, 1, 0, 1, 0, 1, 0, 1 }, // 11 = 4
{ 0, 1, 1, 0, 1, 0, 1, 1 }, // 12 = 5
{ 1, 1, 1, 0, 1, 1, 1, 0 }, // 13 = 6
{ 1, 1, 1, 1, 1, 1, 1, 0 }, // 14 = 7
{ 0 }
#elif STEPPER_PAGE_FORMAT == SP_4x2_256
{ 0, 0, 0, 0 }, // 0
{ 0, 1, 0, 0 }, // 1
{ 1, 0, 1, 0 }, // 2
{ 1, 1, 1, 0 }, // 3
#elif STEPPER_PAGE_FORMAT == SP_4x1_512
{0} // Uncompressed format, table not used
#endif
};
#endif // DIRECT_STEPPING

View file

@ -0,0 +1,137 @@
/**
* Marlin 3D Printer Firmware
* Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
*
* Based on Sprinter and grbl.
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#pragma once
#include "../inc/MarlinConfig.h"
namespace DirectStepping {
enum State : char {
MONITOR, NEWLINE, ADDRESS, SIZE, COLLECT, CHECKSUM, UNFAIL
};
enum PageState : uint8_t {
FREE, WRITING, OK, FAIL
};
// Static state used for stepping through direct stepping pages
struct page_step_state_t {
// Current page
uint8_t *page;
// Current segment
uint16_t segment_idx;
// Current steps within segment
uint8_t segment_steps;
// Segment delta
xyze_uint8_t sd;
// Block delta
xyze_int_t bd;
};
template<typename Cfg>
class SerialPageManager {
public:
typedef typename Cfg::page_idx_t page_idx_t;
static bool maybe_store_rxd_char(uint8_t c);
static void write_responses();
// common methods for page managers
static void init();
static uint8_t *get_page(const page_idx_t page_idx);
static void free_page(const page_idx_t page_idx);
protected:
typedef typename Cfg::write_byte_idx_t write_byte_idx_t;
static State state;
static volatile bool fatal_error;
static volatile PageState page_states[Cfg::NUM_PAGES];
static volatile bool page_states_dirty;
static millis_t next_response;
static uint8_t pages[Cfg::NUM_PAGES][Cfg::PAGE_SIZE];
static uint8_t checksum;
static write_byte_idx_t write_byte_idx;
static page_idx_t write_page_idx;
static write_byte_idx_t write_page_size;
static void set_page_state(const page_idx_t page_idx, const PageState page_state);
};
template<bool b, typename T, typename F> struct TypeSelector { typedef T type;} ;
template<typename T, typename F> struct TypeSelector<false, T, F> { typedef F type; };
template <int num_pages, int num_axes, int bits_segment, bool dir, int segments>
struct config_t {
static constexpr char CONTROL_CHAR = '!';
static constexpr int NUM_PAGES = num_pages;
static constexpr int NUM_AXES = num_axes;
static constexpr int BITS_SEGMENT = bits_segment;
static constexpr int DIRECTIONAL = dir ? 1 : 0;
static constexpr int SEGMENTS = segments;
static constexpr int RAW = (BITS_SEGMENT == 1) ? 1 : 0;
static constexpr int NUM_SEGMENTS = 1 << BITS_SEGMENT;
static constexpr int SEGMENT_STEPS = 1 << (BITS_SEGMENT - DIRECTIONAL - RAW);
static constexpr int TOTAL_STEPS = SEGMENT_STEPS * SEGMENTS;
static constexpr int PAGE_SIZE = (NUM_AXES * BITS_SEGMENT * SEGMENTS) / 8;
static constexpr millis_t RESPONSE_INTERVAL_MS = 50;
typedef typename TypeSelector<(PAGE_SIZE>256), uint16_t, uint8_t>::type write_byte_idx_t;
typedef typename TypeSelector<(NUM_PAGES>256), uint16_t, uint8_t>::type page_idx_t;
};
template <uint8_t num_pages>
using SP_4x4D_128 = config_t<num_pages, 4, 4, true, 128>;
template <uint8_t num_pages>
using SP_4x2_256 = config_t<num_pages, 4, 2, false, 256>;
template <uint8_t num_pages>
using SP_4x1_512 = config_t<num_pages, 4, 1, false, 512>;
// configured types
typedef STEPPER_PAGE_FORMAT<STEPPER_PAGES> Config;
template class PAGE_MANAGER<Config>;
typedef PAGE_MANAGER<Config> PageManager;
};
#define SP_4x4D_128 1
//#define SP_4x4_128 2
//#define SP_4x2D_256 3
#define SP_4x2_256 4
#define SP_4x1_512 5
typedef typename DirectStepping::Config::page_idx_t page_idx_t;
// TODO: use config
typedef DirectStepping::page_step_state_t page_step_state_t;
extern const uint8_t segment_table[DirectStepping::Config::NUM_SEGMENTS][DirectStepping::Config::SEGMENT_STEPS];
extern DirectStepping::PageManager page_manager;

View file

@ -261,6 +261,10 @@ void GcodeSuite::process_parsed_command(const bool no_ok/*=false*/) {
case 5: G5(); break; // G5: Cubic B_spline case 5: G5(); break; // G5: Cubic B_spline
#endif #endif
#if ENABLED(DIRECT_STEPPING)
case 6: G6(); break; // G6: Direct Stepper Move
#endif
#if ENABLED(FWRETRACT) #if ENABLED(FWRETRACT)
case 10: G10(); break; // G10: Retract / Swap Retract case 10: G10(); break; // G10: Retract / Swap Retract
case 11: G11(); break; // G11: Recover / Swap Recover case 11: G11(); break; // G11: Recover / Swap Recover

View file

@ -402,6 +402,8 @@ private:
TERN_(BEZIER_CURVE_SUPPORT, static void G5()); TERN_(BEZIER_CURVE_SUPPORT, static void G5());
TERN_(DIRECT_STEPPING, static void G6());
#if ENABLED(FWRETRACT) #if ENABLED(FWRETRACT)
static void G10(); static void G10();
static void G11(); static void G11();

View file

@ -99,5 +99,7 @@ void GcodeSuite::G92() {
if (sync_XYZ) sync_plan_position(); if (sync_XYZ) sync_plan_position();
else if (sync_E) sync_plan_position_e(); else if (sync_E) sync_plan_position_e();
report_current_position(); #if DISABLED(DIRECT_STEPPING)
report_current_position();
#endif
} }

View file

@ -0,0 +1,61 @@
/**
* Marlin 3D Printer Firmware
* Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
*
* Based on Sprinter and grbl.
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "../../inc/MarlinConfig.h"
#if ENABLED(DIRECT_STEPPING)
#include "../../feature/direct_stepping.h"
#include "../gcode.h"
#include "../../module/planner.h"
/**
* G6: Direct Stepper Move
*/
void GcodeSuite::G6() {
// TODO: feedrate support?
if (parser.seen('R'))
planner.last_page_step_rate = parser.value_ulong();
if (!DirectStepping::Config::DIRECTIONAL) {
if (parser.seen('X')) planner.last_page_dir.x = !!parser.value_byte();
if (parser.seen('Y')) planner.last_page_dir.y = !!parser.value_byte();
if (parser.seen('Z')) planner.last_page_dir.z = !!parser.value_byte();
if (parser.seen('E')) planner.last_page_dir.e = !!parser.value_byte();
}
// No index means we just set the state
if (!parser.seen('I')) return;
// No speed is set, can't schedule the move
if (!planner.last_page_step_rate) return;
const page_idx_t page_idx = (page_idx_t) parser.value_ulong();
uint16_t num_steps = DirectStepping::Config::TOTAL_STEPS;
if (parser.seen('S')) num_steps = parser.value_ushort();
planner.buffer_page(page_idx, 0, num_steps);
reset_stepper_timeout();
}
#endif // DIRECT_STEPPING

View file

@ -323,6 +323,18 @@
#endif #endif
#endif #endif
#if ENABLED(DIRECT_STEPPING)
#ifndef STEPPER_PAGES
#define STEPPER_PAGES 16
#endif
#ifndef STEPPER_PAGE_FORMAT
#define STEPPER_PAGE_FORMAT SP_4x2_256
#endif
#ifndef PAGE_MANAGER
#define PAGE_MANAGER SerialPageManager
#endif
#endif
// //
// SD Card connection methods // SD Card connection methods
// Defined here so pins and sanity checks can use them // Defined here so pins and sanity checks can use them

View file

@ -2923,3 +2923,12 @@ static_assert( _ARR_TEST(3,0) && _ARR_TEST(3,1) && _ARR_TEST(3,2)
#if SAVED_POSITIONS > 256 #if SAVED_POSITIONS > 256
#error "SAVED_POSITIONS must be an integer from 0 to 256." #error "SAVED_POSITIONS must be an integer from 0 to 256."
#endif #endif
/**
* Sanity checks for stepper chunk support
*/
#if ENABLED(DIRECT_STEPPING)
#if ENABLED(LIN_ADVANCE)
#error "DIRECT_STEPPING is incompatible with LIN_ADVANCE. Enable in external planner if possible."
#endif
#endif

View file

@ -577,6 +577,9 @@ namespace Language_en {
PROGMEM Language_Str MSG_SNAKE = _UxGT("Sn4k3"); PROGMEM Language_Str MSG_SNAKE = _UxGT("Sn4k3");
PROGMEM Language_Str MSG_MAZE = _UxGT("Maze"); PROGMEM Language_Str MSG_MAZE = _UxGT("Maze");
PROGMEM Language_Str MSG_BAD_PAGE = _UxGT("Bad page index");
PROGMEM Language_Str MSG_BAD_PAGE_SPEED = _UxGT("Bad page speed");
// //
// Filament Change screens show up to 3 lines on a 4-line display // Filament Change screens show up to 3 lines on a 4-line display
// ...or up to 2 lines on a 3-line display // ...or up to 2 lines on a 3-line display

View file

@ -151,6 +151,11 @@ float Planner::steps_to_mm[XYZE_N]; // (mm) Millimeters per step
uint8_t Planner::last_extruder = 0; // Respond to extruder change uint8_t Planner::last_extruder = 0; // Respond to extruder change
#endif #endif
#if ENABLED(DIRECT_STEPPING)
uint32_t Planner::last_page_step_rate = 0;
xyze_bool_t Planner::last_page_dir{0};
#endif
#if EXTRUDERS #if EXTRUDERS
int16_t Planner::flow_percentage[EXTRUDERS] = ARRAY_BY_EXTRUDERS1(100); // Extrusion factor for each extruder int16_t Planner::flow_percentage[EXTRUDERS] = ARRAY_BY_EXTRUDERS1(100); // Extrusion factor for each extruder
float Planner::e_factor[EXTRUDERS] = ARRAY_BY_EXTRUDERS1(1.0f); // The flow percentage and volumetric multiplier combine to scale E movement float Planner::e_factor[EXTRUDERS] = ARRAY_BY_EXTRUDERS1(1.0f); // The flow percentage and volumetric multiplier combine to scale E movement
@ -235,6 +240,10 @@ void Planner::init() {
TERN_(ABL_PLANAR, bed_level_matrix.set_to_identity()); TERN_(ABL_PLANAR, bed_level_matrix.set_to_identity());
clear_block_buffer(); clear_block_buffer();
delay_before_delivering = 0; delay_before_delivering = 0;
#if ENABLED(DIRECT_STEPPING)
last_page_step_rate = 0;
last_page_dir.reset();
#endif
} }
#if ENABLED(S_CURVE_ACCELERATION) #if ENABLED(S_CURVE_ACCELERATION)
@ -906,7 +915,7 @@ void Planner::calculate_trapezoid_for_block(block_t* const block, const float &e
streaming operating conditions. Use for planning optimizations by avoiding recomputing parts of the streaming operating conditions. Use for planning optimizations by avoiding recomputing parts of the
planner buffer that don't change with the addition of a new block, as describe above. In addition, planner buffer that don't change with the addition of a new block, as describe above. In addition,
this block can never be less than block_buffer_tail and will always be pushed forward and maintain this block can never be less than block_buffer_tail and will always be pushed forward and maintain
this requirement when encountered by the Planner::discard_current_block() routine during a cycle. this requirement when encountered by the Planner::release_current_block() routine during a cycle.
NOTE: Since the planner only computes on what's in the planner buffer, some motions with lots of short NOTE: Since the planner only computes on what's in the planner buffer, some motions with lots of short
line segments, like G2/3 arcs or complex curves, may seem to move slow. This is because there simply isn't line segments, like G2/3 arcs or complex curves, may seem to move slow. This is because there simply isn't
@ -994,8 +1003,8 @@ void Planner::reverse_pass() {
// Perform the reverse pass // Perform the reverse pass
block_t *current = &block_buffer[block_index]; block_t *current = &block_buffer[block_index];
// Only consider non sync blocks // Only consider non sync and page blocks
if (!TEST(current->flag, BLOCK_BIT_SYNC_POSITION)) { if (!TEST(current->flag, BLOCK_BIT_SYNC_POSITION) && !IS_PAGE(current)) {
reverse_pass_kernel(current, next); reverse_pass_kernel(current, next);
next = current; next = current;
} }
@ -1089,8 +1098,8 @@ void Planner::forward_pass() {
// Perform the forward pass // Perform the forward pass
block = &block_buffer[block_index]; block = &block_buffer[block_index];
// Skip SYNC blocks // Skip SYNC and page blocks
if (!TEST(block->flag, BLOCK_BIT_SYNC_POSITION)) { if (!TEST(block->flag, BLOCK_BIT_SYNC_POSITION) && !IS_PAGE(block)) {
// If there's no previous block or the previous block is not // If there's no previous block or the previous block is not
// BUSY (thus, modifiable) run the forward_pass_kernel. Otherwise, // BUSY (thus, modifiable) run the forward_pass_kernel. Otherwise,
// the previous block became BUSY, so assume the current block's // the previous block became BUSY, so assume the current block's
@ -1139,8 +1148,8 @@ void Planner::recalculate_trapezoids() {
next = &block_buffer[block_index]; next = &block_buffer[block_index];
// Skip sync blocks // Skip sync and page blocks
if (!TEST(next->flag, BLOCK_BIT_SYNC_POSITION)) { if (!TEST(next->flag, BLOCK_BIT_SYNC_POSITION) && !IS_PAGE(next)) {
next_entry_speed = SQRT(next->entry_speed_sqr); next_entry_speed = SQRT(next->entry_speed_sqr);
if (block) { if (block) {
@ -2717,6 +2726,69 @@ bool Planner::buffer_line(const float &rx, const float &ry, const float &rz, con
#endif #endif
} // buffer_line() } // buffer_line()
#if ENABLED(DIRECT_STEPPING)
void Planner::buffer_page(const page_idx_t page_idx, const uint8_t extruder, const uint16_t num_steps) {
if (!last_page_step_rate) {
kill(GET_TEXT(MSG_BAD_PAGE_SPEED));
return;
}
uint8_t next_buffer_head;
block_t * const block = get_next_free_block(next_buffer_head);
block->flag = BLOCK_FLAG_IS_PAGE;
#if FAN_COUNT > 0
FANS_LOOP(i) block->fan_speed[i] = thermalManager.fan_speed[i];
#endif
#if EXTRUDERS > 1
block->extruder = extruder;
#endif
block->page_idx = page_idx;
block->step_event_count = num_steps;
block->initial_rate =
block->final_rate =
block->nominal_rate = last_page_step_rate; // steps/s
block->accelerate_until = 0;
block->decelerate_after = block->step_event_count;
// Will be set to last direction later if directional format.
block->direction_bits = 0;
#define PAGE_UPDATE_DIR(AXIS) \
if (!last_page_dir[_AXIS(AXIS)]) SBI(block->direction_bits, _AXIS(AXIS));
if (!DirectStepping::Config::DIRECTIONAL) {
PAGE_UPDATE_DIR(X);
PAGE_UPDATE_DIR(Y);
PAGE_UPDATE_DIR(Z);
PAGE_UPDATE_DIR(E);
}
// If this is the first added movement, reload the delay, otherwise, cancel it.
if (block_buffer_head == block_buffer_tail) {
// If it was the first queued block, restart the 1st block delivery delay, to
// give the planner an opportunity to queue more movements and plan them
// As there are no queued movements, the Stepper ISR will not touch this
// variable, so there is no risk setting this here (but it MUST be done
// before the following line!!)
delay_before_delivering = BLOCK_DELAY_FOR_1ST_MOVE;
}
// Move buffer head
block_buffer_head = next_buffer_head;
enable_all_steppers();
stepper.wake_up();
}
#endif // DIRECT_STEPPING
/** /**
* Directly set the planner ABC position (and stepper positions) * Directly set the planner ABC position (and stepper positions)
* converting mm (or angles for SCARA) into steps. * converting mm (or angles for SCARA) into steps.

View file

@ -66,6 +66,13 @@
#include "../feature/spindle_laser_types.h" #include "../feature/spindle_laser_types.h"
#endif #endif
#if ENABLED(DIRECT_STEPPING)
#include "../feature/direct_stepping.h"
#define IS_PAGE(B) TEST(B->flag, BLOCK_BIT_IS_PAGE)
#else
#define IS_PAGE(B) false
#endif
// Feedrate for manual moves // Feedrate for manual moves
#ifdef MANUAL_FEEDRATE #ifdef MANUAL_FEEDRATE
constexpr xyze_feedrate_t _mf = MANUAL_FEEDRATE, constexpr xyze_feedrate_t _mf = MANUAL_FEEDRATE,
@ -90,13 +97,21 @@ enum BlockFlagBit : char {
// Sync the stepper counts from the block // Sync the stepper counts from the block
BLOCK_BIT_SYNC_POSITION BLOCK_BIT_SYNC_POSITION
// Direct stepping page
#if ENABLED(DIRECT_STEPPING)
, BLOCK_BIT_IS_PAGE
#endif
}; };
enum BlockFlag : char { enum BlockFlag : char {
BLOCK_FLAG_RECALCULATE = _BV(BLOCK_BIT_RECALCULATE), BLOCK_FLAG_RECALCULATE = _BV(BLOCK_BIT_RECALCULATE)
BLOCK_FLAG_NOMINAL_LENGTH = _BV(BLOCK_BIT_NOMINAL_LENGTH), , BLOCK_FLAG_NOMINAL_LENGTH = _BV(BLOCK_BIT_NOMINAL_LENGTH)
BLOCK_FLAG_CONTINUED = _BV(BLOCK_BIT_CONTINUED), , BLOCK_FLAG_CONTINUED = _BV(BLOCK_BIT_CONTINUED)
BLOCK_FLAG_SYNC_POSITION = _BV(BLOCK_BIT_SYNC_POSITION) , BLOCK_FLAG_SYNC_POSITION = _BV(BLOCK_BIT_SYNC_POSITION)
#if ENABLED(DIRECT_STEPPING)
, BLOCK_FLAG_IS_PAGE = _BV(BLOCK_BIT_IS_PAGE)
#endif
}; };
#if ENABLED(LASER_POWER_INLINE) #if ENABLED(LASER_POWER_INLINE)
@ -180,6 +195,10 @@ typedef struct block_t {
final_rate, // The minimal rate at exit final_rate, // The minimal rate at exit
acceleration_steps_per_s2; // acceleration steps/sec^2 acceleration_steps_per_s2; // acceleration steps/sec^2
#if ENABLED(DIRECT_STEPPING)
page_idx_t page_idx; // Page index used for direct stepping
#endif
#if HAS_CUTTER #if HAS_CUTTER
cutter_power_t cutter_power; // Power level for Spindle, Laser, etc. cutter_power_t cutter_power; // Power level for Spindle, Laser, etc.
#endif #endif
@ -296,6 +315,11 @@ class Planner {
static uint8_t last_extruder; // Respond to extruder change static uint8_t last_extruder; // Respond to extruder change
#endif #endif
#if ENABLED(DIRECT_STEPPING)
static uint32_t last_page_step_rate; // Last page step rate given
static xyze_bool_t last_page_dir; // Last page direction given
#endif
#if EXTRUDERS #if EXTRUDERS
static int16_t flow_percentage[EXTRUDERS]; // Extrusion factor for each extruder static int16_t flow_percentage[EXTRUDERS]; // Extrusion factor for each extruder
static float e_factor[EXTRUDERS]; // The flow percentage and volumetric multiplier combine to scale E movement static float e_factor[EXTRUDERS]; // The flow percentage and volumetric multiplier combine to scale E movement
@ -726,6 +750,10 @@ class Planner {
); );
} }
#if ENABLED(DIRECT_STEPPING)
static void buffer_page(const page_idx_t page_idx, const uint8_t extruder, const uint16_t num_steps);
#endif
/** /**
* Set the planner.position and individual stepper positions. * Set the planner.position and individual stepper positions.
* Used by G92, G28, G29, and other procedures. * Used by G92, G28, G29, and other procedures.
@ -811,10 +839,10 @@ class Planner {
static block_t* get_current_block(); static block_t* get_current_block();
/** /**
* "Discard" the block and "release" the memory. * "Release" the current block so its slot can be reused.
* Called when the current block is no longer needed. * Called when the current block is no longer needed.
*/ */
FORCE_INLINE static void discard_current_block() { FORCE_INLINE static void release_current_block() {
if (has_blocks_queued()) if (has_blocks_queued())
block_buffer_tail = next_block_index(block_buffer_tail); block_buffer_tail = next_block_index(block_buffer_tail);
} }

View file

@ -230,6 +230,10 @@ uint32_t Stepper::advance_divisor = 0,
uint32_t Stepper::nextBabystepISR = BABYSTEP_NEVER; uint32_t Stepper::nextBabystepISR = BABYSTEP_NEVER;
#endif #endif
#if ENABLED(DIRECT_STEPPING)
page_step_state_t Stepper::page_step_state;
#endif
int32_t Stepper::ticks_nominal = -1; int32_t Stepper::ticks_nominal = -1;
#if DISABLED(S_CURVE_ACCELERATION) #if DISABLED(S_CURVE_ACCELERATION)
uint32_t Stepper::acc_step_rate; // needed for deceleration start point uint32_t Stepper::acc_step_rate; // needed for deceleration start point
@ -1520,11 +1524,7 @@ void Stepper::pulse_phase_isr() {
// If we must abort the current block, do so! // If we must abort the current block, do so!
if (abort_current_block) { if (abort_current_block) {
abort_current_block = false; abort_current_block = false;
if (current_block) { if (current_block) discard_current_block();
axis_did_move = 0;
current_block = nullptr;
planner.discard_current_block();
}
} }
// If there is no current block, do nothing // If there is no current block, do nothing
@ -1558,46 +1558,160 @@ void Stepper::pulse_phase_isr() {
} \ } \
}while(0) }while(0)
// Start an active pulse, if Bresenham says so, and update position // Start an active pulse if needed
#define PULSE_START(AXIS) do{ \ #define PULSE_START(AXIS) do{ \
if (step_needed[_AXIS(AXIS)]) { \ if (step_needed[_AXIS(AXIS)]) { \
_APPLY_STEP(AXIS, !_INVERT_STEP_PIN(AXIS), 0); \ _APPLY_STEP(AXIS, !_INVERT_STEP_PIN(AXIS), 0); \
} \ } \
}while(0) }while(0)
// Stop an active pulse, if any, and adjust error term // Stop an active pulse if needed
#define PULSE_STOP(AXIS) do { \ #define PULSE_STOP(AXIS) do { \
if (step_needed[_AXIS(AXIS)]) { \ if (step_needed[_AXIS(AXIS)]) { \
_APPLY_STEP(AXIS, _INVERT_STEP_PIN(AXIS), 0); \ _APPLY_STEP(AXIS, _INVERT_STEP_PIN(AXIS), 0); \
} \ } \
}while(0) }while(0)
// Determine if pulses are needed // Direct Stepping page?
#if HAS_X_STEP const bool is_page = IS_PAGE(current_block);
PULSE_PREP(X);
#endif #if ENABLED(DIRECT_STEPPING)
#if HAS_Y_STEP
PULSE_PREP(Y); if (is_page) {
#endif
#if HAS_Z_STEP #if STEPPER_PAGE_FORMAT == SP_4x4D_128
PULSE_PREP(Z);
#endif #define PAGE_SEGMENT_UPDATE(AXIS, VALUE, MID) do{ \
if ((VALUE) == MID) {} \
else if ((VALUE) < MID) SBI(dm, _AXIS(AXIS)); \
else CBI(dm, _AXIS(AXIS)); \
page_step_state.sd[_AXIS(AXIS)] = VALUE; \
page_step_state.bd[_AXIS(AXIS)] += VALUE; \
}while(0)
#define PAGE_PULSE_PREP(AXIS) do{ \
step_needed[_AXIS(AXIS)] = \
pgm_read_byte(&segment_table[page_step_state.sd[_AXIS(AXIS)]][page_step_state.segment_steps & 0x7]); \
}while(0)
switch (page_step_state.segment_steps) {
case 8:
page_step_state.segment_idx += 2;
page_step_state.segment_steps = 0;
// fallthru
case 0: {
const uint8_t low = page_step_state.page[page_step_state.segment_idx],
high = page_step_state.page[page_step_state.segment_idx + 1];
uint8_t dm = last_direction_bits;
PAGE_SEGMENT_UPDATE(X, low >> 4, 7);
PAGE_SEGMENT_UPDATE(Y, low & 0xF, 7);
PAGE_SEGMENT_UPDATE(Z, high >> 4, 7);
PAGE_SEGMENT_UPDATE(E, high & 0xF, 7);
if (dm != last_direction_bits) {
last_direction_bits = dm;
set_directions();
}
} break;
default: break;
}
PAGE_PULSE_PREP(X),
PAGE_PULSE_PREP(Y),
PAGE_PULSE_PREP(Z),
PAGE_PULSE_PREP(E);
page_step_state.segment_steps++;
#elif STEPPER_PAGE_FORMAT == SP_4x2_256
#define PAGE_SEGMENT_UPDATE(AXIS, VALUE) \
page_step_state.sd[_AXIS(AXIS)] = VALUE; \
page_step_state.bd[_AXIS(AXIS)] += VALUE;
#define PAGE_PULSE_PREP(AXIS) do{ \
step_needed[_AXIS(AXIS)] = \
pgm_read_byte(&segment_table[page_step_state.sd[_AXIS(AXIS)]][page_step_state.segment_steps & 0x3]); \
}while(0)
switch (page_step_state.segment_steps) {
case 4:
page_step_state.segment_idx++;
page_step_state.segment_steps = 0;
// fallthru
case 0: {
const uint8_t b = page_step_state.page[page_step_state.segment_idx];
PAGE_SEGMENT_UPDATE(X, (b >> 6) & 0x3);
PAGE_SEGMENT_UPDATE(Y, (b >> 4) & 0x3);
PAGE_SEGMENT_UPDATE(Z, (b >> 2) & 0x3);
PAGE_SEGMENT_UPDATE(E, (b >> 0) & 0x3);
} break;
default: break;
}
PAGE_PULSE_PREP(X);
PAGE_PULSE_PREP(Y);
PAGE_PULSE_PREP(Z);
PAGE_PULSE_PREP(E);
page_step_state.segment_steps++;
#elif STEPPER_PAGE_FORMAT == SP_4x1_512
#define PAGE_PULSE_PREP(AXIS, BITS) do{ \
step_needed[_AXIS(AXIS)] = (steps >> BITS) & 0x1; \
if (step_needed[_AXIS(AXIS)]) \
page_step_state.bd[_AXIS(AXIS)]++; \
}while(0)
uint8_t steps = page_step_state.page[page_step_state.segment_idx >> 1];
if (page_step_state.segment_idx & 0x1) steps >>= 4;
PAGE_PULSE_PREP(X, 3);
PAGE_PULSE_PREP(Y, 2);
PAGE_PULSE_PREP(Z, 1);
PAGE_PULSE_PREP(E, 0);
page_step_state.segment_idx++;
#if EITHER(LIN_ADVANCE, MIXING_EXTRUDER)
delta_error.e += advance_dividend.e;
if (delta_error.e >= 0) {
count_position.e += count_direction.e;
#if ENABLED(LIN_ADVANCE)
delta_error.e -= advance_divisor;
// Don't step E here - But remember the number of steps to perform
motor_direction(E_AXIS) ? --LA_steps : ++LA_steps;
#else #else
step_needed.e = true; #error "Unknown direct stepping page format!"
#endif #endif
} }
#elif HAS_E0_STEP
PULSE_PREP(E); #endif // DIRECT_STEPPING
#endif
if (!is_page) {
// Determine if pulses are needed
#if HAS_X_STEP
PULSE_PREP(X);
#endif
#if HAS_Y_STEP
PULSE_PREP(Y);
#endif
#if HAS_Z_STEP
PULSE_PREP(Z);
#endif
#if EITHER(LIN_ADVANCE, MIXING_EXTRUDER)
delta_error.e += advance_dividend.e;
if (delta_error.e >= 0) {
count_position.e += count_direction.e;
#if ENABLED(LIN_ADVANCE)
delta_error.e -= advance_divisor;
// Don't step E here - But remember the number of steps to perform
motor_direction(E_AXIS) ? --LA_steps : ++LA_steps;
#else
step_needed.e = true;
#endif
}
#elif HAS_E0_STEP
PULSE_PREP(E);
#endif
}
#if ISR_MULTI_STEPS #if ISR_MULTI_STEPS
if (firstStep) if (firstStep)
@ -1676,14 +1790,28 @@ uint32_t Stepper::block_phase_isr() {
// If there is a current block // If there is a current block
if (current_block) { if (current_block) {
// If current block is finished, reset pointer // If current block is finished, reset pointer and finalize state
if (step_events_completed >= step_event_count) { if (step_events_completed >= step_event_count) {
#if ENABLED(DIRECT_STEPPING)
#if STEPPER_PAGE_FORMAT == SP_4x4D_128
#define PAGE_SEGMENT_UPDATE_POS(AXIS) \
count_position[_AXIS(AXIS)] += page_step_state.bd[_AXIS(AXIS)] - 128 * 7;
#elif STEPPER_PAGE_FORMAT == SP_4x1_512 || STEPPER_PAGE_FORMAT == SP_4x2_256
#define PAGE_SEGMENT_UPDATE_POS(AXIS) \
count_position[_AXIS(AXIS)] += page_step_state.bd[_AXIS(AXIS)] * count_direction[_AXIS(AXIS)];
#endif
if (IS_PAGE(current_block)) {
PAGE_SEGMENT_UPDATE_POS(X);
PAGE_SEGMENT_UPDATE_POS(Y);
PAGE_SEGMENT_UPDATE_POS(Z);
PAGE_SEGMENT_UPDATE_POS(E);
}
#endif
#ifdef FILAMENT_RUNOUT_DISTANCE_MM #ifdef FILAMENT_RUNOUT_DISTANCE_MM
runout.block_completed(current_block); runout.block_completed(current_block);
#endif #endif
axis_did_move = 0; discard_current_block();
current_block = nullptr;
planner.discard_current_block();
} }
else { else {
// Step events not completed yet... // Step events not completed yet...
@ -1867,7 +1995,7 @@ uint32_t Stepper::block_phase_isr() {
// Sync block? Sync the stepper counts and return // Sync block? Sync the stepper counts and return
while (TEST(current_block->flag, BLOCK_BIT_SYNC_POSITION)) { while (TEST(current_block->flag, BLOCK_BIT_SYNC_POSITION)) {
_set_position(current_block->position); _set_position(current_block->position);
planner.discard_current_block(); discard_current_block();
// Try to get a new block // Try to get a new block
if (!(current_block = planner.get_current_block())) if (!(current_block = planner.get_current_block()))
@ -1878,6 +2006,23 @@ uint32_t Stepper::block_phase_isr() {
TERN_(POWER_LOSS_RECOVERY, recovery.info.sdpos = current_block->sdpos); TERN_(POWER_LOSS_RECOVERY, recovery.info.sdpos = current_block->sdpos);
#if ENABLED(DIRECT_STEPPING)
if (IS_PAGE(current_block)) {
page_step_state.segment_steps = 0;
page_step_state.segment_idx = 0;
page_step_state.page = page_manager.get_page(current_block->page_idx);
page_step_state.bd.reset();
if (DirectStepping::Config::DIRECTIONAL)
current_block->direction_bits = last_direction_bits;
if (!page_step_state.page) {
discard_current_block();
return interval;
}
}
#endif
// Flag all moving axes for proper endstop handling // Flag all moving axes for proper endstop handling
#if IS_CORE #if IS_CORE

View file

@ -334,6 +334,10 @@ class Stepper {
static uint32_t nextBabystepISR; static uint32_t nextBabystepISR;
#endif #endif
#if ENABLED(DIRECT_STEPPING)
static page_step_state_t page_step_state;
#endif
static int32_t ticks_nominal; static int32_t ticks_nominal;
#if DISABLED(S_CURVE_ACCELERATION) #if DISABLED(S_CURVE_ACCELERATION)
static uint32_t acc_step_rate; // needed for deceleration start point static uint32_t acc_step_rate; // needed for deceleration start point
@ -426,6 +430,17 @@ class Stepper {
static void report_a_position(const xyz_long_t &pos); static void report_a_position(const xyz_long_t &pos);
static void report_positions(); static void report_positions();
// Discard current block and free any resources
FORCE_INLINE static void discard_current_block() {
#if ENABLED(DIRECT_STEPPING)
if (IS_PAGE(current_block))
page_manager.free_page(current_block->page_idx);
#endif
current_block = nullptr;
axis_did_move = 0;
planner.release_current_block();
}
// Quickly stop all steppers // Quickly stop all steppers
FORCE_INLINE static void quick_stop() { abort_current_block = true; } FORCE_INLINE static void quick_stop() { abort_current_block = true; }

View file

@ -71,8 +71,9 @@ opt_set NUM_SERVOS 1
opt_enable ZONESTAR_LCD Z_PROBE_SERVO_NR Z_SERVO_ANGLES DEACTIVATE_SERVOS_AFTER_MOVE BOOT_MARLIN_LOGO_ANIMATED \ opt_enable ZONESTAR_LCD Z_PROBE_SERVO_NR Z_SERVO_ANGLES DEACTIVATE_SERVOS_AFTER_MOVE BOOT_MARLIN_LOGO_ANIMATED \
AUTO_BED_LEVELING_3POINT DEBUG_LEVELING_FEATURE EEPROM_SETTINGS EEPROM_CHITCHAT M114_DETAIL \ AUTO_BED_LEVELING_3POINT DEBUG_LEVELING_FEATURE EEPROM_SETTINGS EEPROM_CHITCHAT M114_DETAIL \
NO_VOLUMETRICS EXTENDED_CAPABILITIES_REPORT AUTO_REPORT_TEMPERATURES AUTOTEMP G38_PROBE_TARGET JOYSTICK \ NO_VOLUMETRICS EXTENDED_CAPABILITIES_REPORT AUTO_REPORT_TEMPERATURES AUTOTEMP G38_PROBE_TARGET JOYSTICK \
PRUSA_MMU2 MMU2_MENUS PRUSA_MMU2_S_MODE FILAMENT_RUNOUT_SENSOR NOZZLE_PARK_FEATURE ADVANCED_PAUSE_FEATURE Z_SAFE_HOMING PRUSA_MMU2 MMU2_MENUS PRUSA_MMU2_S_MODE DIRECT_STEPPING \
exec_test $1 $2 "RAMPS | ZONESTAR_LCD | MMU2 | Servo Probe | ABL 3-Pt | Debug Leveling | EEPROM | G38 ..." FILAMENT_RUNOUT_SENSOR NOZZLE_PARK_FEATURE ADVANCED_PAUSE_FEATURE Z_SAFE_HOMING
exec_test $1 $2 "RAMPS | ZONESTAR + Chinese | MMU2 | Servo | 3-Point + Debug | G38 ..."
# #
# Test MINIRAMBO with PWM_MOTOR_CURRENT and many features # Test MINIRAMBO with PWM_MOTOR_CURRENT and many features