diff --git a/Marlin/Configuration_adv.h b/Marlin/Configuration_adv.h index cd8a055ffb206..bd827f3347c1c 100644 --- a/Marlin/Configuration_adv.h +++ b/Marlin/Configuration_adv.h @@ -1173,6 +1173,8 @@ //#define SDCARD_READONLY // Read-only SD card (to save over 2K of flash) + //#define GCODE_REPEAT_MARKERS // Enable G-code M41 to set repeat markers and do looping + #define SD_PROCEDURE_DEPTH 1 // Increase if you need more nested M32 calls #define SD_FINISHED_STEPPERRELEASE true // Disable steppers when SD Print is finished diff --git a/Marlin/src/feature/repeat.cpp b/Marlin/src/feature/repeat.cpp new file mode 100644 index 0000000000000..b83c66b404b34 --- /dev/null +++ b/Marlin/src/feature/repeat.cpp @@ -0,0 +1,68 @@ +/** + * 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(GCODE_REPEAT_MARKERS) + +#include "repeat.h" + +#include "../gcode/gcode.h" +#include "../sd/cardreader.h" + +repeat_marker_t Repeat::marker[MAX_REPEAT_NESTING]; +int8_t Repeat::index; + +bool Repeat::early_parse_M41(char * const cmd, const uint32_t &sdpos) { + parser.parse(cmd); + if (parser.is_command('M', 41)) { + if (sdpos) { + gcode.process_parsed_command(); + marker[index].sdpos = sdpos; + } + return false; + } + return true; +} + +void Repeat::add_marker(const uint16_t count) { + if (index < MAX_REPEAT_NESTING) { + marker[index].counter = count ? count : -1; + index++; + } +} + +void Repeat::loop() { + if (!index) // No marker has been set? + card.setIndex(0); // Go back to the top + else { + const uint16_t ind = index - 1; // The relevant marker index + if (!marker[ind].counter) // Did the counter run out? + --index; // Carry on and loop elsewhere on the next 'M41' + else { + card.setIndex(marker[ind].sdpos); // Loop back to the marker. + if (marker[ind].counter > 0) // Don't decrement a negative (or zero) counter. + --marker[ind].counter; // Decrement the counter. If zero this 'M41' will be skipped next time. + } + } +} + +#endif // GCODE_REPEAT_MARKERS diff --git a/Marlin/src/feature/repeat.h b/Marlin/src/feature/repeat.h new file mode 100644 index 0000000000000..412d9310816be --- /dev/null +++ b/Marlin/src/feature/repeat.h @@ -0,0 +1,44 @@ +/** + * 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/MarlinConfigPre.h" + +#include + +#define MAX_REPEAT_NESTING 10 + +typedef struct { + uint32_t sdpos; // The repeat file position + int16_t counter; // The counter for looping +} repeat_marker_t; + +class Repeat { +public: + static repeat_marker_t marker[MAX_REPEAT_NESTING]; + static int8_t index; + static bool early_parse_M41(char * const cmd, const uint32_t &sdpos); + static void add_marker(const uint16_t count); + static void loop(); +}; + +extern Repeat repeat; diff --git a/Marlin/src/gcode/gcode.cpp b/Marlin/src/gcode/gcode.cpp index 1d6bd94231754..86ee2e3fa9b0d 100644 --- a/Marlin/src/gcode/gcode.cpp +++ b/Marlin/src/gcode/gcode.cpp @@ -250,7 +250,7 @@ void GcodeSuite::process_parsed_command(const bool no_ok/*=false*/) { * Will still block Gcodes if M511 is disabled, in which case the printer should be unlocked via LCD Menu */ #if ENABLED(PASSWORD_FEATURE) - if (password.is_locked && !(parser.command_letter == 'M' && parser.codenum == 511)) { + if (password.is_locked && !parser.is_command('M', 511)) { SERIAL_ECHO_MSG(STR_PRINTER_LOCKED); return; } @@ -449,6 +449,10 @@ void GcodeSuite::process_parsed_command(const bool no_ok/*=false*/) { case 31: M31(); break; // M31: Report time since the start of SD print or last M109 + #if ENABLED(GCODE_REPEAT_MARKERS) + case 41: M41(); break; // M41: Set / Goto repeat markers + #endif + #if ENABLED(DIRECT_PIN_CONTROL) case 42: M42(); break; // M42: Change pin state #endif diff --git a/Marlin/src/gcode/gcode.h b/Marlin/src/gcode/gcode.h index 5db8b08e08d45..939bbd0cbb885 100644 --- a/Marlin/src/gcode/gcode.h +++ b/Marlin/src/gcode/gcode.h @@ -548,6 +548,8 @@ class GcodeSuite { #endif #endif + TERN_(GCODE_REPEAT_MARKERS, static void M41()); + TERN_(DIRECT_PIN_CONTROL, static void M42()); TERN_(PINS_DEBUGGING, static void M43()); diff --git a/Marlin/src/gcode/host/M115.cpp b/Marlin/src/gcode/host/M115.cpp index 529b1dd6d0127..e38481523db21 100644 --- a/Marlin/src/gcode/host/M115.cpp +++ b/Marlin/src/gcode/host/M115.cpp @@ -117,6 +117,9 @@ void GcodeSuite::M115() { // SDCARD (M20, M23, M24, etc.) cap_line(PSTR("SDCARD"), ENABLED(SDSUPPORT)); + // REPEAT (M41) + cap_line(PSTR("REPEAT"), ENABLED(GCODE_REPEAT_MARKERS)); + // AUTOREPORT_SD_STATUS (M27 extension) cap_line(PSTR("AUTOREPORT_SD_STATUS"), ENABLED(AUTO_REPORT_SD_STATUS)); diff --git a/Marlin/src/gcode/parser.cpp b/Marlin/src/gcode/parser.cpp index bba64dbbc4051..4bff045e304d0 100644 --- a/Marlin/src/gcode/parser.cpp +++ b/Marlin/src/gcode/parser.cpp @@ -45,7 +45,7 @@ char *GCodeParser::command_ptr, *GCodeParser::string_arg, *GCodeParser::value_ptr; char GCodeParser::command_letter; -int GCodeParser::codenum; +uint16_t GCodeParser::codenum; #if ENABLED(USE_GCODE_SUBCODES) uint8_t GCodeParser::subcode; @@ -270,7 +270,7 @@ void GCodeParser::parse(char *p) { // Special handling for M32 [P] !/path/to/file.g# // The path must be the last parameter - if (param == '!' && letter == 'M' && codenum == 32) { + if (param == '!' && is_command('M', 32)) { string_arg = p; // Name starts after '!' char * const lb = strchr(p, '#'); // Already seen '#' as SD char (to pause buffering) if (lb) *lb = '\0'; // Safe to mark the end of the filename diff --git a/Marlin/src/gcode/parser.h b/Marlin/src/gcode/parser.h index 17fb084388902..69bbdaf02d398 100644 --- a/Marlin/src/gcode/parser.h +++ b/Marlin/src/gcode/parser.h @@ -84,7 +84,7 @@ class GCodeParser { static char *command_ptr, // The command, so it can be echoed *string_arg, // string of command line command_letter; // G, M, or T - static int codenum; // 123 + static uint16_t codenum; // 123 #if ENABLED(USE_GCODE_SUBCODES) static uint8_t subcode; // .1 #endif @@ -244,6 +244,9 @@ class GCodeParser { static bool chain(); #endif + // Test whether the parsed command matches the input + static inline bool is_command(const char ltr, const uint16_t num) { return command_letter == ltr && codenum == num; } + // The code value pointer was set FORCE_INLINE static bool has_value() { return !!value_ptr; } diff --git a/Marlin/src/gcode/queue.cpp b/Marlin/src/gcode/queue.cpp index f481052cbf4f4..3d9b552a6c587 100644 --- a/Marlin/src/gcode/queue.cpp +++ b/Marlin/src/gcode/queue.cpp @@ -51,6 +51,10 @@ GCodeQueue queue; #include "../feature/powerloss.h" #endif +#if ENABLED(GCODE_REPEAT_MARKERS) + #include "../feature/repeat.h" +#endif + /** * GCode line number handling. Hosts may opt to include line numbers when * sending commands to Marlin, and lines will be checked for sequentiality. @@ -416,11 +420,14 @@ inline void process_stream_char(const char c, uint8_t &sis, char (&buff)[MAX_CMD * keep sensor readings going and watchdog alive. */ inline bool process_line_done(uint8_t &sis, char (&buff)[MAX_CMD_SIZE], int &ind) { - sis = PS_NORMAL; - buff[ind] = 0; - if (ind) { ind = 0; return false; } - thermalManager.manage_heater(); - return true; + sis = PS_NORMAL; // "Normal" Serial Input State + buff[ind] = '\0'; // Of course, I'm a Terminator. + const bool is_empty = (ind == 0); // An empty line? + if (is_empty) + thermalManager.manage_heater(); // Keep sensors satisfied + else + ind = 0; // Start a new line + return is_empty; // Inform the caller } /** @@ -547,12 +554,14 @@ void GCodeQueue::get_serial_commands() { last_command_time = ms; #endif - // Add the command to the queue - _enqueue(serial_line_buffer[i], true - #if HAS_MULTI_SERIAL - , i - #endif - ); + if (TERN1(GCODE_REPEAT_MARKERS, repeat.early_parse_M41(serial_line_buffer[i], 0))) { + // Add the command to the queue + _enqueue(serial_line_buffer[i], true + #if HAS_MULTI_SERIAL + , i + #endif + ); + } } else process_stream_char(serial_char, serial_input_state[i], serial_line_buffer[i], serial_count[i]); @@ -588,10 +597,9 @@ void GCodeQueue::get_serial_commands() { // Reset stream state, terminate the buffer, and commit a non-empty command if (!is_eol && sd_count) ++sd_count; // End of file with no newline if (!process_line_done(sd_input_state, command_buffer[index_w], sd_count)) { - _commit_command(false); - #if ENABLED(POWER_LOSS_RECOVERY) - recovery.cmd_sdpos = card.getIndex(); // Prime for the NEXT _commit_command - #endif + if (TERN1(GCODE_REPEAT_MARKERS, repeat.early_parse_M41(command_buffer[index_w], card.getIndex()))) + _commit_command(false); + TERN_(POWER_LOSS_RECOVERY, recovery.cmd_sdpos = card.getIndex()); // Prime for the NEXT _commit_command } if (card_eof) card.fileHasFinished(); // Handle end of file reached diff --git a/Marlin/src/gcode/sd/M41.cpp b/Marlin/src/gcode/sd/M41.cpp new file mode 100644 index 0000000000000..baaef70f08059 --- /dev/null +++ b/Marlin/src/gcode/sd/M41.cpp @@ -0,0 +1,53 @@ +/** + * 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(GCODE_REPEAT_MARKERS) + +#include "../gcode.h" +#include "../../feature/repeat.h" +#include "../../sd/cardreader.h" + +/** + * M41: Set / Goto a repeat marker + * + * S - Set a repeat marker with 'count' repetitions. If omitted, infinity. + * + * Examples: + * + * M41 S ; Set a loop marker with a count of infinity + * M41 S2 ; Set a loop marker with a count of 2 + * M41 ; Decrement and loop if not zero. + */ +void GcodeSuite::M41() { + + if (!IS_SD_PRINTING()) return; + + if (parser.seen('S')) // If 'S' was seen... + repeat.add_marker(parser.value_ushort()); // Add a marker with a count. 'S' or 'S0' = forever. + else + repeat.loop(); + +} + +#endif // GCODE_REPEAT_MARKERS diff --git a/Marlin/src/lcd/marlinui.h b/Marlin/src/lcd/marlinui.h index d0b66aee457cc..ad32fd1feaa71 100644 --- a/Marlin/src/lcd/marlinui.h +++ b/Marlin/src/lcd/marlinui.h @@ -84,10 +84,6 @@ inline void wrap_string_P(uint8_t &col, uint8_t &row, PGM_P const pstr, const bool wordwrap=false) { _wrap_string(col, row, pstr, read_byte_rom, wordwrap); } inline void wrap_string(uint8_t &col, uint8_t &row, const char * const string, const bool wordwrap=false) { _wrap_string(col, row, string, read_byte_ram, wordwrap); } - #if ENABLED(SDSUPPORT) - #include "../sd/cardreader.h" - #endif - typedef void (*screenFunc_t)(); typedef void (*menuAction_t)(); diff --git a/buildroot/tests/mega2560-tests b/buildroot/tests/mega2560-tests index a6902b9d144b5..f8f7acae72b69 100755 --- a/buildroot/tests/mega2560-tests +++ b/buildroot/tests/mega2560-tests @@ -82,7 +82,7 @@ restore_configs opt_set MOTHERBOARD BOARD_MEGACONTROLLER opt_set LCD_LANGUAGE de opt_enable EEPROM_SETTINGS EEPROM_CHITCHAT \ - MINIPANEL SDSUPPORT PCA9632 LCD_INFO_MENU SOUND_MENU_ITEM \ + MINIPANEL SDSUPPORT PCA9632 LCD_INFO_MENU SOUND_MENU_ITEM GCODE_REPEAT_MARKERS \ AUTO_BED_LEVELING_BILINEAR PROBE_MANUALLY LCD_BED_LEVELING G26_MESH_VALIDATION MESH_EDIT_MENU \ LIN_ADVANCE EXTRA_LIN_ADVANCE_K \ INCH_MODE_SUPPORT TEMPERATURE_UNITS_SUPPORT EXPERIMENTAL_I2CBUS M100_FREE_MEMORY_WATCHER \ diff --git a/platformio.ini b/platformio.ini index 15b8d4c23a644..4e4c278574b99 100644 --- a/platformio.ini +++ b/platformio.ini @@ -179,8 +179,7 @@ default_src_filter = + - - + - - - - - - - + - - - - - - @@ -372,6 +371,7 @@ G38_PROBE_TARGET = src_filter=+ MAGNETIC_PARKING_EXTRUDER = src_filter=+ SDSUPPORT = src_filter=+ HAS_MEDIA_SUBCALLS = src_filter=+ +GCODE_REPEAT_MARKERS = src_filter=+ HAS_EXTRUDERS = src_filter=+ + AUTO_REPORT_TEMPERATURES = src_filter=+ INCH_MODE_SUPPORT = src_filter=+