G6 Direct Stepping (#17853)
This commit is contained in:
parent
174e41c17d
commit
8a22ef0c83
17 changed files with 859 additions and 64 deletions
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.");
|
||||||
|
|
273
Marlin/src/feature/direct_stepping.cpp
Normal file
273
Marlin/src/feature/direct_stepping.cpp
Normal 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
|
137
Marlin/src/feature/direct_stepping.h
Normal file
137
Marlin/src/feature/direct_stepping.h
Normal 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;
|
|
@ -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
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
61
Marlin/src/gcode/motion/G6.cpp
Normal file
61
Marlin/src/gcode/motion/G6.cpp
Normal 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
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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; }
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
Reference in a new issue