From 8a22ef0c83a94f742be39005f259226e005ded2d Mon Sep 17 00:00:00 2001 From: Colin Godsey Date: Mon, 11 May 2020 18:22:41 -0600 Subject: [PATCH] G6 Direct Stepping (#17853) --- Marlin/Configuration_adv.h | 24 ++- Marlin/src/HAL/AVR/MarlinSerial.cpp | 24 ++- Marlin/src/MarlinCore.cpp | 11 + Marlin/src/feature/direct_stepping.cpp | 273 +++++++++++++++++++++++++ Marlin/src/feature/direct_stepping.h | 137 +++++++++++++ Marlin/src/gcode/gcode.cpp | 4 + Marlin/src/gcode/gcode.h | 2 + Marlin/src/gcode/geometry/G92.cpp | 4 +- Marlin/src/gcode/motion/G6.cpp | 61 ++++++ Marlin/src/inc/Conditionals_adv.h | 12 ++ Marlin/src/inc/SanityCheck.h | 9 + Marlin/src/lcd/language/language_en.h | 3 + Marlin/src/module/planner.cpp | 86 +++++++- Marlin/src/module/planner.h | 40 +++- Marlin/src/module/stepper.cpp | 213 ++++++++++++++++--- Marlin/src/module/stepper.h | 15 ++ buildroot/share/tests/mega2560-tests | 5 +- 17 files changed, 859 insertions(+), 64 deletions(-) create mode 100644 Marlin/src/feature/direct_stepping.cpp create mode 100644 Marlin/src/feature/direct_stepping.h create mode 100644 Marlin/src/gcode/motion/G6.cpp diff --git a/Marlin/Configuration_adv.h b/Marlin/Configuration_adv.h index 8123582b80..0acb279fb6 100644 --- a/Marlin/Configuration_adv.h +++ b/Marlin/Configuration_adv.h @@ -1663,6 +1663,16 @@ // Support for G5 with XYZE destination and IJPQ offsets. Requires ~2666 bytes. //#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 * @@ -1731,14 +1741,16 @@ //================================= Buffers ================================= //=========================================================================== -// @section hidden +// @section motion -// The number of linear motions that can be in the plan at any give time. -// 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. -#if ENABLED(SDSUPPORT) - #define BLOCK_BUFFER_SIZE 16 // SD,LCD,Buttons take more memory, block buffer needs to be smaller +// The number of lineear moves that can be in the planner at once. +// The value of BLOCK_BUFFER_SIZE must be a power of 2 (e.g. 8, 16, 32) +#if BOTH(SDSUPPORT, DIRECT_STEPPING) + #define BLOCK_BUFFER_SIZE 8 +#elif ENABLED(SDSUPPORT) + #define BLOCK_BUFFER_SIZE 16 #else - #define BLOCK_BUFFER_SIZE 16 // maximize block buffer + #define BLOCK_BUFFER_SIZE 16 #endif // @section serial diff --git a/Marlin/src/HAL/AVR/MarlinSerial.cpp b/Marlin/src/HAL/AVR/MarlinSerial.cpp index 350d0f302d..c544b905f3 100644 --- a/Marlin/src/HAL/AVR/MarlinSerial.cpp +++ b/Marlin/src/HAL/AVR/MarlinSerial.cpp @@ -43,6 +43,10 @@ #include "MarlinSerial.h" #include "../../MarlinCore.h" + #if ENABLED(DIRECT_STEPPING) + #include "../../feature/direct_stepping.h" + #endif + template typename MarlinSerial::ring_buffer_r MarlinSerial::rx_buffer = { 0, 0, { 0 } }; template typename MarlinSerial::ring_buffer_t MarlinSerial::tx_buffer = { 0 }; template bool MarlinSerial::_written = false; @@ -131,6 +135,18 @@ 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 // 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. @@ -142,14 +158,6 @@ // Get the next element 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 the character is to be stored at the index just before the tail diff --git a/Marlin/src/MarlinCore.cpp b/Marlin/src/MarlinCore.cpp index 73f936bc05..2434df0ad4 100644 --- a/Marlin/src/MarlinCore.cpp +++ b/Marlin/src/MarlinCore.cpp @@ -59,6 +59,10 @@ #include "gcode/parser.h" #include "gcode/queue.h" +#if ENABLED(DIRECT_STEPPING) + #include "feature/direct_stepping.h" +#endif + #if ENABLED(TOUCH_BUTTONS) #include "feature/touch/xpt2046.h" #endif @@ -713,6 +717,9 @@ void idle(TERN_(ADVANCED_PAUSE_FEATURE, bool no_stepper_sleep/*=false*/)) { // Handle Joystick jogging 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()); #endif + #if ENABLED(DIRECT_STEPPING) + SETUP_RUN(page_manager.init()); + #endif + marlin_state = MF_RUNNING; SETUP_LOG("setup() completed."); diff --git a/Marlin/src/feature/direct_stepping.cpp b/Marlin/src/feature/direct_stepping.cpp new file mode 100644 index 0000000000..7bed075b87 --- /dev/null +++ b/Marlin/src/feature/direct_stepping.cpp @@ -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 . + * + */ +#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 + State SerialPageManager::state; + + template + volatile bool SerialPageManager::fatal_error; + + template + volatile PageState SerialPageManager::page_states[Cfg::NUM_PAGES]; + + template + volatile bool SerialPageManager::page_states_dirty; + + template + millis_t SerialPageManager::next_response; + + template + uint8_t SerialPageManager::pages[Cfg::NUM_PAGES][Cfg::PAGE_SIZE]; + + template + uint8_t SerialPageManager::checksum; + + template + typename Cfg::write_byte_idx_t SerialPageManager::write_byte_idx; + + template + typename Cfg::page_idx_t SerialPageManager::write_page_idx; + + template + typename Cfg::write_byte_idx_t SerialPageManager::write_page_size; + + template + void SerialPageManager::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 + FORCE_INLINE bool SerialPageManager::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 + void SerialPageManager::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 + FORCE_INLINE void SerialPageManager::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 diff --git a/Marlin/src/feature/direct_stepping.h b/Marlin/src/feature/direct_stepping.h new file mode 100644 index 0000000000..4d12f83db7 --- /dev/null +++ b/Marlin/src/feature/direct_stepping.h @@ -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 . + * + */ +#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 + 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 struct TypeSelector { typedef T type;} ; + template struct TypeSelector { typedef F type; }; + + template + 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 + using SP_4x4D_128 = config_t; + + template + using SP_4x2_256 = config_t; + + template + using SP_4x1_512 = config_t; + + // configured types + typedef STEPPER_PAGE_FORMAT Config; + + template class PAGE_MANAGER; + typedef PAGE_MANAGER 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; diff --git a/Marlin/src/gcode/gcode.cpp b/Marlin/src/gcode/gcode.cpp index ac5a60ed93..14f2eea7d9 100644 --- a/Marlin/src/gcode/gcode.cpp +++ b/Marlin/src/gcode/gcode.cpp @@ -261,6 +261,10 @@ void GcodeSuite::process_parsed_command(const bool no_ok/*=false*/) { case 5: G5(); break; // G5: Cubic B_spline #endif + #if ENABLED(DIRECT_STEPPING) + case 6: G6(); break; // G6: Direct Stepper Move + #endif + #if ENABLED(FWRETRACT) case 10: G10(); break; // G10: Retract / Swap Retract case 11: G11(); break; // G11: Recover / Swap Recover diff --git a/Marlin/src/gcode/gcode.h b/Marlin/src/gcode/gcode.h index 92292f609b..573ef0f625 100644 --- a/Marlin/src/gcode/gcode.h +++ b/Marlin/src/gcode/gcode.h @@ -402,6 +402,8 @@ private: TERN_(BEZIER_CURVE_SUPPORT, static void G5()); + TERN_(DIRECT_STEPPING, static void G6()); + #if ENABLED(FWRETRACT) static void G10(); static void G11(); diff --git a/Marlin/src/gcode/geometry/G92.cpp b/Marlin/src/gcode/geometry/G92.cpp index 91a746dd76..16717de14d 100644 --- a/Marlin/src/gcode/geometry/G92.cpp +++ b/Marlin/src/gcode/geometry/G92.cpp @@ -99,5 +99,7 @@ void GcodeSuite::G92() { if (sync_XYZ) sync_plan_position(); else if (sync_E) sync_plan_position_e(); - report_current_position(); + #if DISABLED(DIRECT_STEPPING) + report_current_position(); + #endif } diff --git a/Marlin/src/gcode/motion/G6.cpp b/Marlin/src/gcode/motion/G6.cpp new file mode 100644 index 0000000000..4405ff6b9c --- /dev/null +++ b/Marlin/src/gcode/motion/G6.cpp @@ -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 . + * + */ +#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 diff --git a/Marlin/src/inc/Conditionals_adv.h b/Marlin/src/inc/Conditionals_adv.h index 2009e7c965..70a1333038 100644 --- a/Marlin/src/inc/Conditionals_adv.h +++ b/Marlin/src/inc/Conditionals_adv.h @@ -323,6 +323,18 @@ #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 // Defined here so pins and sanity checks can use them diff --git a/Marlin/src/inc/SanityCheck.h b/Marlin/src/inc/SanityCheck.h index 11cc7060d4..c8f8907e88 100644 --- a/Marlin/src/inc/SanityCheck.h +++ b/Marlin/src/inc/SanityCheck.h @@ -2923,3 +2923,12 @@ static_assert( _ARR_TEST(3,0) && _ARR_TEST(3,1) && _ARR_TEST(3,2) #if SAVED_POSITIONS > 256 #error "SAVED_POSITIONS must be an integer from 0 to 256." #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 diff --git a/Marlin/src/lcd/language/language_en.h b/Marlin/src/lcd/language/language_en.h index df27de57a5..3ee5bea6e4 100644 --- a/Marlin/src/lcd/language/language_en.h +++ b/Marlin/src/lcd/language/language_en.h @@ -577,6 +577,9 @@ namespace Language_en { PROGMEM Language_Str MSG_SNAKE = _UxGT("Sn4k3"); 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 // ...or up to 2 lines on a 3-line display diff --git a/Marlin/src/module/planner.cpp b/Marlin/src/module/planner.cpp index 7d23789df1..dea51ac67f 100644 --- a/Marlin/src/module/planner.cpp +++ b/Marlin/src/module/planner.cpp @@ -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 #endif +#if ENABLED(DIRECT_STEPPING) + uint32_t Planner::last_page_step_rate = 0; + xyze_bool_t Planner::last_page_dir{0}; +#endif + #if EXTRUDERS 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 @@ -235,6 +240,10 @@ void Planner::init() { TERN_(ABL_PLANAR, bed_level_matrix.set_to_identity()); clear_block_buffer(); delay_before_delivering = 0; + #if ENABLED(DIRECT_STEPPING) + last_page_step_rate = 0; + last_page_dir.reset(); + #endif } #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 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 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 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 block_t *current = &block_buffer[block_index]; - // Only consider non sync blocks - if (!TEST(current->flag, BLOCK_BIT_SYNC_POSITION)) { + // Only consider non sync and page blocks + if (!TEST(current->flag, BLOCK_BIT_SYNC_POSITION) && !IS_PAGE(current)) { reverse_pass_kernel(current, next); next = current; } @@ -1089,8 +1098,8 @@ void Planner::forward_pass() { // Perform the forward pass block = &block_buffer[block_index]; - // Skip SYNC blocks - if (!TEST(block->flag, BLOCK_BIT_SYNC_POSITION)) { + // Skip SYNC and page blocks + if (!TEST(block->flag, BLOCK_BIT_SYNC_POSITION) && !IS_PAGE(block)) { // If there's no previous block or the previous block is not // BUSY (thus, modifiable) run the forward_pass_kernel. Otherwise, // the previous block became BUSY, so assume the current block's @@ -1139,8 +1148,8 @@ void Planner::recalculate_trapezoids() { next = &block_buffer[block_index]; - // Skip sync blocks - if (!TEST(next->flag, BLOCK_BIT_SYNC_POSITION)) { + // Skip sync and page blocks + if (!TEST(next->flag, BLOCK_BIT_SYNC_POSITION) && !IS_PAGE(next)) { next_entry_speed = SQRT(next->entry_speed_sqr); if (block) { @@ -2717,6 +2726,69 @@ bool Planner::buffer_line(const float &rx, const float &ry, const float &rz, con #endif } // 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) * converting mm (or angles for SCARA) into steps. diff --git a/Marlin/src/module/planner.h b/Marlin/src/module/planner.h index 60dbb612c7..0314758a1d 100644 --- a/Marlin/src/module/planner.h +++ b/Marlin/src/module/planner.h @@ -66,6 +66,13 @@ #include "../feature/spindle_laser_types.h" #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 #ifdef MANUAL_FEEDRATE constexpr xyze_feedrate_t _mf = MANUAL_FEEDRATE, @@ -90,13 +97,21 @@ enum BlockFlagBit : char { // Sync the stepper counts from the block BLOCK_BIT_SYNC_POSITION + + // Direct stepping page + #if ENABLED(DIRECT_STEPPING) + , BLOCK_BIT_IS_PAGE + #endif }; enum BlockFlag : char { - BLOCK_FLAG_RECALCULATE = _BV(BLOCK_BIT_RECALCULATE), - BLOCK_FLAG_NOMINAL_LENGTH = _BV(BLOCK_BIT_NOMINAL_LENGTH), - BLOCK_FLAG_CONTINUED = _BV(BLOCK_BIT_CONTINUED), - BLOCK_FLAG_SYNC_POSITION = _BV(BLOCK_BIT_SYNC_POSITION) + BLOCK_FLAG_RECALCULATE = _BV(BLOCK_BIT_RECALCULATE) + , BLOCK_FLAG_NOMINAL_LENGTH = _BV(BLOCK_BIT_NOMINAL_LENGTH) + , BLOCK_FLAG_CONTINUED = _BV(BLOCK_BIT_CONTINUED) + , 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) @@ -180,6 +195,10 @@ typedef struct block_t { final_rate, // The minimal rate at exit 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 cutter_power_t cutter_power; // Power level for Spindle, Laser, etc. #endif @@ -296,6 +315,11 @@ class Planner { static uint8_t last_extruder; // Respond to extruder change #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 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 @@ -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. * Used by G92, G28, G29, and other procedures. @@ -811,10 +839,10 @@ class Planner { 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. */ - FORCE_INLINE static void discard_current_block() { + FORCE_INLINE static void release_current_block() { if (has_blocks_queued()) block_buffer_tail = next_block_index(block_buffer_tail); } diff --git a/Marlin/src/module/stepper.cpp b/Marlin/src/module/stepper.cpp index 92ee753392..68e75e36e3 100644 --- a/Marlin/src/module/stepper.cpp +++ b/Marlin/src/module/stepper.cpp @@ -230,6 +230,10 @@ uint32_t Stepper::advance_divisor = 0, uint32_t Stepper::nextBabystepISR = BABYSTEP_NEVER; #endif +#if ENABLED(DIRECT_STEPPING) + page_step_state_t Stepper::page_step_state; +#endif + int32_t Stepper::ticks_nominal = -1; #if DISABLED(S_CURVE_ACCELERATION) 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 (abort_current_block) { abort_current_block = false; - if (current_block) { - axis_did_move = 0; - current_block = nullptr; - planner.discard_current_block(); - } + if (current_block) discard_current_block(); } // If there is no current block, do nothing @@ -1558,46 +1558,160 @@ void Stepper::pulse_phase_isr() { } \ }while(0) - // Start an active pulse, if Bresenham says so, and update position + // Start an active pulse if needed #define PULSE_START(AXIS) do{ \ if (step_needed[_AXIS(AXIS)]) { \ _APPLY_STEP(AXIS, !_INVERT_STEP_PIN(AXIS), 0); \ } \ }while(0) - // Stop an active pulse, if any, and adjust error term + // Stop an active pulse if needed #define PULSE_STOP(AXIS) do { \ if (step_needed[_AXIS(AXIS)]) { \ _APPLY_STEP(AXIS, _INVERT_STEP_PIN(AXIS), 0); \ } \ }while(0) - // 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 + // Direct Stepping page? + const bool is_page = IS_PAGE(current_block); + + #if ENABLED(DIRECT_STEPPING) + + if (is_page) { + + #if STEPPER_PAGE_FORMAT == SP_4x4D_128 + + #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 - step_needed.e = true; + #error "Unknown direct stepping page format!" #endif } - #elif HAS_E0_STEP - PULSE_PREP(E); - #endif + + #endif // DIRECT_STEPPING + + 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 (firstStep) @@ -1676,14 +1790,28 @@ uint32_t Stepper::block_phase_isr() { // If there is a 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 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 runout.block_completed(current_block); #endif - axis_did_move = 0; - current_block = nullptr; - planner.discard_current_block(); + discard_current_block(); } else { // Step events not completed yet... @@ -1867,7 +1995,7 @@ uint32_t Stepper::block_phase_isr() { // Sync block? Sync the stepper counts and return while (TEST(current_block->flag, BLOCK_BIT_SYNC_POSITION)) { _set_position(current_block->position); - planner.discard_current_block(); + discard_current_block(); // Try to get a new 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); + #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 #if IS_CORE diff --git a/Marlin/src/module/stepper.h b/Marlin/src/module/stepper.h index 793dc8745b..a89d36e98f 100644 --- a/Marlin/src/module/stepper.h +++ b/Marlin/src/module/stepper.h @@ -334,6 +334,10 @@ class Stepper { static uint32_t nextBabystepISR; #endif + #if ENABLED(DIRECT_STEPPING) + static page_step_state_t page_step_state; + #endif + static int32_t ticks_nominal; #if DISABLED(S_CURVE_ACCELERATION) 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_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 FORCE_INLINE static void quick_stop() { abort_current_block = true; } diff --git a/buildroot/share/tests/mega2560-tests b/buildroot/share/tests/mega2560-tests index b17d60ba89..ed5713eff8 100755 --- a/buildroot/share/tests/mega2560-tests +++ b/buildroot/share/tests/mega2560-tests @@ -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 \ 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 \ - PRUSA_MMU2 MMU2_MENUS PRUSA_MMU2_S_MODE FILAMENT_RUNOUT_SENSOR NOZZLE_PARK_FEATURE ADVANCED_PAUSE_FEATURE Z_SAFE_HOMING -exec_test $1 $2 "RAMPS | ZONESTAR_LCD | MMU2 | Servo Probe | ABL 3-Pt | Debug Leveling | EEPROM | G38 ..." + PRUSA_MMU2 MMU2_MENUS PRUSA_MMU2_S_MODE DIRECT_STEPPING \ + 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