Skip to content
Snippets Groups Projects
Marlin_main.cpp 226 KiB
Newer Older
  • Learn to ignore specific revisions
  • /**
     * Marlin Firmware
     *
     * 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/>.
     *
     * About Marlin
     *
     * This firmware is a mashup between Sprinter and grbl.
     *  - https://github.com/kliment/Sprinter
     *  - https://github.com/simen/grbl/tree
     *
     * It has preliminary support for Matthew Roberts advance algorithm
     *  - http://reprap.org/pipermail/reprap-dev/2011-May/003323.html
    
    Erik vd Zalm's avatar
    Erik vd Zalm committed
    
    
    #if ENABLED(AUTO_BED_LEVELING_FEATURE)
    
      #include "vector_3.h"
    
      #if ENABLED(AUTO_BED_LEVELING_GRID)
    
    #endif // AUTO_BED_LEVELING_FEATURE
    
    #if ENABLED(MESH_BED_LEVELING)
    
      #include "mesh_bed_leveling.h"
    
    #include "ultralcd.h"
    #include "planner.h"
    #include "stepper.h"
    #include "temperature.h"
    #include "cardreader.h"
    #include "watchdog.h"
    
    #include "configuration_store.h"
    
    #include "language.h"
    #include "pins_arduino.h"
    
    #include "math.h"
    
    #include "buzzer.h"
    
      #include "blinkm.h"
    
    Scott Lahteine's avatar
    Scott Lahteine committed
      #include "Wire.h"
    
    #endif
    
    #if HAS_DIGIPOTSS
    
    Scott Lahteine's avatar
    Scott Lahteine committed
      #include <SPI.h>
    
    Erik vd Zalm's avatar
    Erik vd Zalm committed
    #endif
    
    
    /**
     * Look here for descriptions of G-codes:
     *  - http://linuxcnc.org/handbook/gcode/g-code.html
     *  - http://objects.reprap.org/wiki/Mendel_User_Manual:_RepRapGCodes
     *
     * Help us document these G-codes online:
    
     *  - http://www.marlinfirmware.org/index.php/G-Code
    
     *  - http://reprap.org/wiki/G-code
    
     * Implemented Codes
    
     *
     * "G" Codes
     *
     * G0  -> G1
     * G1  - Coordinated Movement X Y Z E
     * G2  - CW ARC
     * G3  - CCW ARC
     * G4  - Dwell S<seconds> or P<milliseconds>
     * G10 - retract filament according to settings of M207
     * G11 - retract recover filament according to settings of M208
     * G28 - Home one or more axes
    
     * G29 - Detailed Z probe, probes the bed at 3 or more points.  Will fail if you haven't homed yet.
     * G30 - Single Z probe, probes bed at current XY location.
    
     * G31 - Dock sled (Z_PROBE_SLED only)
     * G32 - Undock sled (Z_PROBE_SLED only)
     * G90 - Use Absolute Coordinates
     * G91 - Use Relative Coordinates
     * G92 - Set current position to coordinates given
     *
     * "M" Codes
     *
     * M0   - Unconditional stop - Wait for user to press a button on the LCD (Only if ULTRA_LCD is enabled)
     * M1   - Same as M0
     * M17  - Enable/Power all stepper motors
     * M18  - Disable all stepper motors; same as M84
     * M20  - List SD card
     * M21  - Init SD card
     * M22  - Release SD card
     * M23  - Select SD file (M23 filename.g)
     * M24  - Start/resume SD print
     * M25  - Pause SD print
     * M26  - Set SD position in bytes (M26 S12345)
     * M27  - Report SD print status
     * M28  - Start SD write (M28 filename.g)
     * M29  - Stop SD write
     * M30  - Delete file from SD (M30 filename.g)
     * M31  - Output time since last M109 or SD card start to serial
     * M32  - Select file and start SD print (Can be used _while_ printing from SD card files):
     *        syntax "M32 /path/filename#", or "M32 S<startpos bytes> !filename#"
     *        Call gcode file : "M32 P !filename#" and return to caller file after finishing (similar to #include).
     *        The '#' is necessary when calling from within sd files, as it stops buffer prereading
    
     * M33  - Get the longname version of a path
    
     * M42  - Change pin status via gcode Use M42 Px Sy to set pin x to value y, when omitting Px the onboard led will be used.
    
     * M48  - Measure Z_Probe repeatability. M48 [P # of points] [X position] [Y position] [V_erboseness #] [E_ngage Probe] [L # of legs of travel]
    
     * M80  - Turn on Power Supply
     * M81  - Turn off Power Supply
     * M82  - Set E codes absolute (default)
     * M83  - Set E codes relative while in Absolute Coordinates (G90) mode
     * M84  - Disable steppers until next move,
     *        or use S<seconds> to specify an inactivity timeout, after which the steppers will be disabled.  S0 to disable the timeout.
     * M85  - Set inactivity shutdown timer with parameter S<seconds>. To disable set zero (default)
     * M92  - Set axis_steps_per_unit - same syntax as G92
     * M104 - Set extruder target temp
     * M105 - Read current temp
     * M106 - Fan on
     * M107 - Fan off
     * M109 - Sxxx Wait for extruder current temp to reach target temp. Waits only when heating
     *        Rxxx Wait for extruder current temp to reach target temp. Waits when heating and cooling
     *        IF AUTOTEMP is enabled, S<mintemp> B<maxtemp> F<factor>. Exit autotemp by any M109 without F
    
     * M110 - Set the current line number
    
    Scott Lahteine's avatar
    Scott Lahteine committed
     * M111 - Set debug flags with S<mask>. See flag bits defined in Marlin.h.
    
     * M112 - Emergency stop
     * M114 - Output current position to serial port
     * M115 - Capabilities string
    
     * M117 - Display a message on the controller screen
    
     * M119 - Output Endstop status to serial port
     * M120 - Enable endstop detection
     * M121 - Disable endstop detection
     * M126 - Solenoid Air Valve Open (BariCUDA support by jmil)
     * M127 - Solenoid Air Valve Closed (BariCUDA vent to atmospheric pressure by jmil)
     * M128 - EtoP Open (BariCUDA EtoP = electricity to air pressure transducer by jmil)
     * M129 - EtoP Closed (BariCUDA EtoP = electricity to air pressure transducer by jmil)
     * M140 - Set bed target temp
    
     * M145 - Set the heatup state H<hotend> B<bed> F<fan speed> for S<material> (0=PLA, 1=ABS)
    
     * M150 - Set BlinkM Color Output R: Red<0-255> U(!): Green<0-255> B: Blue<0-255> over i2c, G for green does not work.
     * M190 - Sxxx Wait for bed current temp to reach target temp. Waits only when heating
     *        Rxxx Wait for bed current temp to reach target temp. Waits when heating and cooling
    
     * M200 - set filament diameter and set E axis units to cubic millimeters (use S0 to set back to millimeters).:D<millimeters>-
    
     * M201 - Set max acceleration in units/s^2 for print moves (M201 X1000 Y1000)
     * M202 - Set max acceleration in units/s^2 for travel moves (M202 X1000 Y1000) Unused in Marlin!!
     * M203 - Set maximum feedrate that your machine can sustain (M203 X200 Y200 Z300 E10000) in mm/sec
     * M204 - Set default acceleration: P for Printing moves, R for Retract only (no X, Y, Z) moves and T for Travel (non printing) moves (ex. M204 P800 T3000 R9000) in mm/sec^2
     * M205 -  advanced settings:  minimum travel speed S=while printing T=travel only,  B=minimum segment time X= maximum xy jerk, Z=maximum Z jerk, E=maximum E jerk
     * M206 - Set additional homing offset
     * M207 - Set retract length S[positive mm] F[feedrate mm/min] Z[additional zlift/hop], stays in mm regardless of M200 setting
    
     * M208 - Set recover=unretract length S[positive mm surplus to the M207 S*] F[feedrate mm/min]
    
     * M209 - S<1=true/0=false> enable automatic retract detect if the slicer did not support G10/11: every normal extrude-only move will be classified as retract depending on the direction.
     * M218 - Set hotend offset (in mm): T<extruder_number> X<offset_on_X> Y<offset_on_Y>
     * M220 - Set speed factor override percentage: S<factor in percent>
     * M221 - Set extrude factor override percentage: S<factor in percent>
     * M226 - Wait until the specified pin reaches the state required: P<pin number> S<pin state>
     * M240 - Trigger a camera to take a photograph
     * M250 - Set LCD contrast C<contrast value> (value 0..63)
     * M280 - Set servo position absolute. P: servo index, S: angle or microseconds
     * M300 - Play beep sound S<frequency Hz> P<duration ms>
     * M301 - Set PID parameters P I and D
     * M302 - Allow cold extrudes, or set the minimum extrude S<temperature>.
     * M303 - PID relay autotune S<temperature> sets the target temperature. (default target temperature = 150C)
     * M304 - Set bed PID parameters P I and D
     * M380 - Activate solenoid on active extruder
     * M381 - Disable all solenoids
     * M400 - Finish all moves
    
     * M401 - Lower Z probe if present
     * M402 - Raise Z probe if present
    
     * M404 - N<dia in mm> Enter the nominal filament width (3mm, 1.75mm ) or will display nominal filament width without parameters
     * M405 - Turn on Filament Sensor extrusion control.  Optional D<delay in cm> to set delay in centimeters between sensor and extruder
     * M406 - Turn off Filament Sensor extrusion control
     * M407 - Display measured filament diameter
    
    Scott Lahteine's avatar
    Scott Lahteine committed
     * M410 - Quickstop. Abort all the planned moves
    
     * M420 - Enable/Disable Mesh Leveling (with current values) S1=enable S0=disable
    
     * M421 - Set a single Z coordinate in the Mesh Leveling grid. X<mm> Y<mm> Z<mm>
    
     * M428 - Set the home_offset logically based on the current_position
    
     * M500 - Store parameters in EEPROM
     * M501 - Read parameters from EEPROM (if you need reset them after you changed them temporarily).
     * M502 - Revert to the default "factory settings". You still need to store them in EEPROM afterwards if you want to.
     * M503 - Print the current settings (from memory not from EEPROM). Use S0 to leave off headings.
     * M540 - Use S[0|1] to enable or disable the stop SD card print on endstop hit (requires ABORT_ON_ENDSTOP_HIT_FEATURE_ENABLED)
     * M600 - Pause for filament change X[pos] Y[pos] Z[relative lift] E[initial retract] L[later retract distance for removal]
     * M665 - Set delta configurations: L<diagonal rod> R<delta radius> S<segments/s>
     * M666 - Set delta endstop adjustment
     * M605 - Set dual x-carriage movement mode: S<mode> [ X<duplication x-offset> R<duplication temp offset> ]
     * M907 - Set digital trimpot motor current using axis codes.
     * M908 - Control digital trimpot directly.
     * M350 - Set microstepping mode.
     * M351 - Toggle MS1 MS2 pins directly.
     *
     * ************ SCARA Specific - This can change to suit future G-code regulations
     * M360 - SCARA calibration: Move to cal-position ThetaA (0 deg calibration)
     * M361 - SCARA calibration: Move to cal-position ThetaB (90 deg calibration - steps per degree)
     * M362 - SCARA calibration: Move to cal-position PsiA (0 deg calibration)
     * M363 - SCARA calibration: Move to cal-position PsiB (90 deg calibration - steps per degree)
     * M364 - SCARA calibration: Move to cal-position PSIC (90 deg to Theta calibration position)
     * M365 - SCARA calibration: Scaling factor, X, Y, Z axis
     * ************* SCARA End ***************
     *
    
     * ************ Custom codes - This can change to suit future G-code regulations
    
    Richard Wackerbarth's avatar
    Richard Wackerbarth committed
     * M100 - Watch Free Memory (For Debugging Only)
    
     * M851 - Set Z probe's Z offset (mm above extruder -- The value will always be negative)
    
     * M928 - Start SD logging (M928 filename.g) - ended by M29
     * M999 - Restart after being stopped by error
    
     *
     * "T" Codes
     *
     * T0-T3 - Select a tool by index (usually an extruder) [ F<mm/min> ]
     *
    
    #if ENABLED(M100_FREE_MEMORY_WATCHER)
    
    Richard Wackerbarth's avatar
    Richard Wackerbarth committed
      void gcode_M100();
    
    Richard Wackerbarth's avatar
    Richard Wackerbarth committed
    #endif
    
    
    Scott Lahteine's avatar
    Scott Lahteine committed
      CardReader card;
    
    bool Running = true;
    
    
    Scott Lahteine's avatar
    Scott Lahteine committed
    uint8_t marlin_debug_flags = DEBUG_INFO|DEBUG_ERRORS;
    
    
    static float feedrate = 1500.0, saved_feedrate;
    
    float current_position[NUM_AXIS] = { 0.0 };
    
    static float destination[NUM_AXIS] = { 0.0 };
    bool axis_known_position[3] = { false };
    
    static long gcode_N, gcode_LastN, Stopped_gcode_LastN = 0;
    
    static char *current_command, *current_command_args;
    
    static int cmd_queue_index_r = 0;
    static int cmd_queue_index_w = 0;
    static int commands_in_queue = 0;
    static char command_queue[BUFSIZE][MAX_CMD_SIZE];
    
    const float homing_feedrate[] = HOMING_FEEDRATE;
    
    bool axis_relative_modes[] = AXIS_RELATIVE_MODES;
    
    int feedrate_multiplier = 100; //100->1 200->2
    int saved_feedrate_multiplier;
    
    int extruder_multiplier[EXTRUDERS] = ARRAY_BY_EXTRUDERS1(100);
    
    bool volumetric_enabled = false;
    
    float filament_size[EXTRUDERS] = ARRAY_BY_EXTRUDERS1(DEFAULT_NOMINAL_FILAMENT_DIA);
    float volumetric_multiplier[EXTRUDERS] = ARRAY_BY_EXTRUDERS1(1.0);
    
    float home_offset[3] = { 0 };
    
    float min_pos[3] = { X_MIN_POS, Y_MIN_POS, Z_MIN_POS };
    float max_pos[3] = { X_MAX_POS, Y_MAX_POS, Z_MAX_POS };
    
    uint8_t active_extruder = 0;
    int fanSpeed = 0;
    bool cancel_heatup = false;
    
    const char errormagic[] PROGMEM = "Error:";
    const char echomagic[] PROGMEM = "echo:";
    const char axis_codes[NUM_AXIS] = {'X', 'Y', 'Z', 'E'};
    
    static bool relative_mode = false;  //Determines Absolute or Relative Coordinates
    static char serial_char;
    static int serial_count = 0;
    static boolean comment_mode = false;
    
    static char *seen_pointer; ///< A pointer to find chars in the command string (X, Y, Z, E, etc.)
    
    const char* queued_commands_P= NULL; /* pointer to the current line in the active sequence of commands, or NULL when none */
    const int sensitive_pins[] = SENSITIVE_PINS; ///< Sensitive pin list for M42
    // Inactivity shutdown
    
    millis_t previous_cmd_ms = 0;
    static millis_t max_inactive_time = 0;
    static millis_t stepper_inactive_time = DEFAULT_STEPPER_DEACTIVE_TIME * 1000L;
    
    millis_t print_job_start_ms = 0; ///< Print job start time
    millis_t print_job_stop_ms = 0;  ///< Print job stop time
    
    static uint8_t target_extruder;
    
    bool no_wait_for_cooling = true;
    
    bool target_direction;
    
    
    #if ENABLED(AUTO_BED_LEVELING_FEATURE)
    
      int xy_travel_speed = XY_TRAVEL_SPEED;
    
      float zprobe_zoffset = Z_PROBE_OFFSET_FROM_EXTRUDER;
    
    #if ENABLED(Z_DUAL_ENDSTOPS) && DISABLED(DELTA)
    
      float z_endstop_adj = 0;
    #endif
    
    // Extruder offsets
    
    #if EXTRUDERS > 1
    
      #ifndef EXTRUDER_OFFSET_X
    
        #define EXTRUDER_OFFSET_X { 0 }
    
    Scott Lahteine's avatar
    Scott Lahteine committed
      #endif
    
      #ifndef EXTRUDER_OFFSET_Y
    
        #define EXTRUDER_OFFSET_Y { 0 }
    
      float extruder_offset[][EXTRUDERS] = {
        EXTRUDER_OFFSET_X,
        EXTRUDER_OFFSET_Y
    
        #if ENABLED(DUAL_X_CARRIAGE)
    
          , { 0 } // supports offsets in XYZ plane
        #endif
      };
    
    #if HAS_SERVO_ENDSTOPS
    
      const int servo_endstop_id[] = SERVO_ENDSTOP_IDS;
    
      const int servo_endstop_angle[][2] = SERVO_ENDSTOP_ANGLES;
    
    Scott Lahteine's avatar
    Scott Lahteine committed
      int ValvePressure = 0;
      int EtoPPressure = 0;
    
    Scott Lahteine's avatar
    Scott Lahteine committed
    
      bool autoretract_enabled = false;
    
      bool retracted[EXTRUDERS] = { false };
      bool retracted_swap[EXTRUDERS] = { false };
    
      float retract_length = RETRACT_LENGTH;
    
      float retract_length_swap = RETRACT_LENGTH_SWAP;
    
      float retract_feedrate = RETRACT_FEEDRATE;
      float retract_zlift = RETRACT_ZLIFT;
      float retract_recover_length = RETRACT_RECOVER_LENGTH;
    
      float retract_recover_length_swap = RETRACT_RECOVER_LENGTH_SWAP;
    
      float retract_recover_feedrate = RETRACT_RECOVER_FEEDRATE;
    
    MagoKimbra's avatar
    MagoKimbra committed
    #endif // FWRETRACT
    
    #if ENABLED(ULTIPANEL) && HAS_POWER_SWITCH
    
      bool powersupply =
    
        #if ENABLED(PS_DEFAULT_OFF)
    
    Scott Lahteine's avatar
    Scott Lahteine committed
          false
        #else
    
    Scott Lahteine's avatar
    Scott Lahteine committed
        #endif
      ;
    
    
      #define TOWER_1 X_AXIS
      #define TOWER_2 Y_AXIS
      #define TOWER_3 Z_AXIS
    
    
      float delta[3] = { 0 };
      #define SIN_60 0.8660254037844386
      #define COS_60 0.5
      float endstop_adj[3] = { 0 };
      // these are the default values, can be overriden with M665
      float delta_radius = DELTA_RADIUS;
    
      float delta_tower1_x = -SIN_60 * (delta_radius + DELTA_RADIUS_TRIM_TOWER_1); // front left tower
      float delta_tower1_y = -COS_60 * (delta_radius + DELTA_RADIUS_TRIM_TOWER_1);
      float delta_tower2_x =  SIN_60 * (delta_radius + DELTA_RADIUS_TRIM_TOWER_2); // front right tower
      float delta_tower2_y = -COS_60 * (delta_radius + DELTA_RADIUS_TRIM_TOWER_2);
      float delta_tower3_x = 0;                                                    // back middle tower
      float delta_tower3_y = (delta_radius + DELTA_RADIUS_TRIM_TOWER_3);
    
      float delta_diagonal_rod = DELTA_DIAGONAL_ROD;
    
      float delta_diagonal_rod_trim_tower_1 = DELTA_DIAGONAL_ROD_TRIM_TOWER_1;
      float delta_diagonal_rod_trim_tower_2 = DELTA_DIAGONAL_ROD_TRIM_TOWER_2;
      float delta_diagonal_rod_trim_tower_3 = DELTA_DIAGONAL_ROD_TRIM_TOWER_3;
      float delta_diagonal_rod_2_tower_1 = sq(delta_diagonal_rod + delta_diagonal_rod_trim_tower_1);
      float delta_diagonal_rod_2_tower_2 = sq(delta_diagonal_rod + delta_diagonal_rod_trim_tower_2);
      float delta_diagonal_rod_2_tower_3 = sq(delta_diagonal_rod + delta_diagonal_rod_trim_tower_3);
      //float delta_diagonal_rod_2 = sq(delta_diagonal_rod);
    
      float delta_segments_per_second = DELTA_SEGMENTS_PER_SECOND;
    
      #if ENABLED(AUTO_BED_LEVELING_FEATURE)
    
        int delta_grid_spacing[2] = { 0, 0 };
        float bed_level[AUTO_BED_LEVELING_GRID_POINTS][AUTO_BED_LEVELING_GRID_POINTS];
    
    #else
    
      static bool home_all_axis = true;
    #endif
    
      float delta_segments_per_second = SCARA_SEGMENTS_PER_SECOND;
    
      static float delta[3] = { 0 };
      float axis_scaling[3] = { 1, 1, 1 };    // Build size scaling, default to 1
    #endif
    
    
    #if ENABLED(FILAMENT_SENSOR)
    
      //Variables for Filament Sensor input
      float filament_width_nominal = DEFAULT_NOMINAL_FILAMENT_DIA;  //Set nominal filament width, can be changed with M404
      bool filament_sensor = false;  //M405 turns on filament_sensor control, M406 turns it off
      float filament_width_meas = DEFAULT_MEASURED_FILAMENT_DIA; //Stores the measured filament diameter
      signed char measurement_delay[MAX_MEASUREMENT_DELAY+1];  //ring buffer to delay measurement  store extruder factor after subtracting 100
      int delay_index1 = 0;  //index into ring buffer
      int delay_index2 = -1;  //index into ring buffer - set to -1 on startup to indicate ring buffer needs to be initialized
      float delay_dist = 0; //delay distance counter
    
      int meas_delay_cm = MEASUREMENT_DELAY_CM;  //distance delay setting
    #endif
    
    
    #if ENABLED(FILAMENT_RUNOUT_SENSOR)
    
       static bool filrunoutEnqueued = false;
    
      static bool fromsd[BUFSIZE];
    #endif
    
      Servo servo[NUM_SERVOS];
    
    blddk's avatar
    blddk committed
    #ifdef CHDK
    
    Scott Lahteine's avatar
    Scott Lahteine committed
      unsigned long chdkHigh = 0;
      boolean chdkActive = false;
    
    blddk's avatar
    blddk committed
    #endif
    
    
    //===========================================================================
    
    //================================ Functions ================================
    
    //===========================================================================
    
    
    void process_next_command();
    
    
    Scott Lahteine's avatar
    Scott Lahteine committed
    void plan_arc(float target[NUM_AXIS], float *offset, uint8_t clockwise);
    
    
    void serial_echopair_P(const char *s_P, int v)           { serialprintPGM(s_P); SERIAL_ECHO(v); }
    void serial_echopair_P(const char *s_P, long v)          { serialprintPGM(s_P); SERIAL_ECHO(v); }
    
    void serial_echopair_P(const char *s_P, float v)         { serialprintPGM(s_P); SERIAL_ECHO(v); }
    void serial_echopair_P(const char *s_P, double v)        { serialprintPGM(s_P); SERIAL_ECHO(v); }
    void serial_echopair_P(const char *s_P, unsigned long v) { serialprintPGM(s_P); SERIAL_ECHO(v); }
    
    
    #if ENABLED(PREVENT_DANGEROUS_EXTRUDE)
    
      float extrude_min_temp = EXTRUDE_MINTEMP;
    #endif
    
      #include "SdFatUtil.h"
      int freeMemory() { return SdFatUtil::FreeRam(); }
    #else
      extern "C" {
        extern unsigned int __bss_end;
        extern unsigned int __heap_start;
        extern void *__brkval;
    
        int freeMemory() {
          int free_memory;
    
          if ((int)__brkval == 0)
            free_memory = ((int)&free_memory) - ((int)&__bss_end);
          else
            free_memory = ((int)&free_memory) - ((int)__brkval);
    
    /**
     * Inject the next command from the command queue, when possible
     * Return false only if no command was pending
     */
    
    static bool drain_queued_commands_P() {
      if (!queued_commands_P) return false;
    
    
      // Get the next 30 chars from the sequence of gcodes to run
    
      char cmd[30];
      strncpy_P(cmd, queued_commands_P, sizeof(cmd) - 1);
      cmd[sizeof(cmd) - 1] = '\0';
    
    
      // Look for the end of line, or the end of sequence
    
      size_t i = 0;
    
      while((c = cmd[i]) && c != '\n') i++; // find the end of this gcode command
      cmd[i] = '\0';
    
      if (enqueuecommand(cmd)) {      // buffer was not full (else we will retry later)
    
        if (c)
          queued_commands_P += i + 1; // move to next command
    
          queued_commands_P = NULL;   // will have no more commands in the sequence
    
    /**
     * Record one or many commands to run from program memory.
     * Aborts the current queue, if any.
     * Note: drain_queued_commands_P() must be called repeatedly to drain the commands afterwards
     */
    
    void enqueuecommands_P(const char* pgcode) {
    
      queued_commands_P = pgcode;
      drain_queued_commands_P(); // first command executed asap (when possible)
    
    /**
     * Copy a command directly into the main command buffer, from RAM.
     *
     * This is done in a non-safe way and needs a rework someday.
     * Returns false if it doesn't add any command
     */
    bool enqueuecommand(const char *cmd) {
    
      if (*cmd == ';' || commands_in_queue >= BUFSIZE) return false;
    
      // This is dangerous if a mixing of serial and this happens
      char *command = command_queue[cmd_queue_index_w];
      strcpy(command, cmd);
    
      SERIAL_ECHOPGM(MSG_Enqueueing);
      SERIAL_ECHO(command);
    
      cmd_queue_index_w = (cmd_queue_index_w + 1) % BUFSIZE;
      commands_in_queue++;
    
    void setup_killpin() {
    
      #if HAS_KILL
    
        SET_INPUT(KILL_PIN);
    
        WRITE(KILL_PIN, HIGH);
    
    void setup_filrunoutpin() {
    
      #if HAS_FILRUNOUT
        pinMode(FILRUNOUT_PIN, INPUT);
    
        #if ENABLED(ENDSTOPPULLUP_FIL_RUNOUT)
    
          WRITE(FILRUNOUT_PIN, HIGH);
    
        #endif
      #endif
    
    // Set home pin
    
    void setup_homepin(void) {
    
      #if HAS_HOME
        SET_INPUT(HOME_PIN);
        WRITE(HOME_PIN, HIGH);
      #endif
    
    void setup_photpin() {
    
      #if HAS_PHOTOGRAPH
    
        OUT_WRITE(PHOTOGRAPH_PIN, LOW);
    
    void setup_powerhold() {
    
      #if HAS_SUICIDE
    
        OUT_WRITE(SUICIDE_PIN, HIGH);
    
      #if HAS_POWER_SWITCH
    
        #if ENABLED(PS_DEFAULT_OFF)
    
          OUT_WRITE(PS_ON_PIN, PS_ON_ASLEEP);
    
          OUT_WRITE(PS_ON_PIN, PS_ON_AWAKE);
        #endif
    
    void suicide() {
    
      #if HAS_SUICIDE
    
        OUT_WRITE(SUICIDE_PIN, LOW);
    
    void servo_init() {
    
      #if NUM_SERVOS >= 1 && HAS_SERVO_0
    
        servo[0].attach(SERVO0_PIN);
    
    AnHardt's avatar
    AnHardt committed
        servo[0].detach(); // Just set up the pin. We don't have a position yet. Don't move to a random position.
    
      #if NUM_SERVOS >= 2 && HAS_SERVO_1
    
        servo[1].attach(SERVO1_PIN);
    
    AnHardt's avatar
    AnHardt committed
        servo[1].detach();
    
      #if NUM_SERVOS >= 3 && HAS_SERVO_2
    
        servo[2].attach(SERVO2_PIN);
    
    AnHardt's avatar
    AnHardt committed
        servo[2].detach();
    
      #if NUM_SERVOS >= 4 && HAS_SERVO_3
    
        servo[3].attach(SERVO3_PIN);
    
    AnHardt's avatar
    AnHardt committed
        servo[3].detach();
    
    
      // Set position of Servo Endstops that are defined
    
      #if HAS_SERVO_ENDSTOPS
    
        for (int i = 0; i < 3; i++)
    
          if (servo_endstop_id[i] >= 0)
    
            servo[servo_endstop_id[i]].move(servo_endstop_angle[i][1]);
    
    /**
     * Stepper Reset (RigidBoard, et.al.)
     */
    #if HAS_STEPPER_RESET
      void disableStepperDrivers() {
        pinMode(STEPPER_RESET_PIN, OUTPUT);
        digitalWrite(STEPPER_RESET_PIN, LOW);  // drive it down to hold in reset motor driver chips
      }
      void enableStepperDrivers() { pinMode(STEPPER_RESET_PIN, INPUT); }  // set to input, which allows it to be pulled high by pullups
    #endif
    
    
    /**
     * Marlin entry-point: Set up before the program loop
     *  - Set up the kill pin, filament runout, power hold
     *  - Start the serial port
     *  - Print startup messages and diagnostics
     *  - Get EEPROM or default settings
     *  - Initialize managers for:
     *    • temperature
     *    • planner
     *    • watchdog
     *    • stepper
     *    • photo pin
     *    • servos
     *    • LCD controller
     *    • Digipot I2C
     *    • Z probe sled
     *    • status LEDs
     */
    
      setup_killpin();
    
      setup_filrunoutpin();
    
    
      #if HAS_STEPPER_RESET
        disableStepperDrivers();
      #endif
    
    
      MYSERIAL.begin(BAUDRATE);
      SERIAL_PROTOCOLLNPGM("start");
      SERIAL_ECHO_START;
    
      // Check startup - does nothing if bootloader sets MCUSR to 0
      byte mcu = MCUSR;
    
      if (mcu & 1) SERIAL_ECHOLNPGM(MSG_POWERUP);
      if (mcu & 2) SERIAL_ECHOLNPGM(MSG_EXTERNAL_RESET);
      if (mcu & 4) SERIAL_ECHOLNPGM(MSG_BROWNOUT_RESET);
      if (mcu & 8) SERIAL_ECHOLNPGM(MSG_WATCHDOG_RESET);
      if (mcu & 32) SERIAL_ECHOLNPGM(MSG_SOFTWARE_RESET);
      MCUSR = 0;
    
      SERIAL_ECHOLNPGM(" " SHORT_BUILD_VERSION);
    
      #ifdef STRING_DISTRIBUTION_DATE
    
        #ifdef STRING_CONFIG_H_AUTHOR
          SERIAL_ECHO_START;
          SERIAL_ECHOPGM(MSG_CONFIGURATION_VER);
    
          SERIAL_ECHOPGM(STRING_DISTRIBUTION_DATE);
    
          SERIAL_ECHOPGM(MSG_AUTHOR);
          SERIAL_ECHOLNPGM(STRING_CONFIG_H_AUTHOR);
    
          SERIAL_ECHOPGM("Compiled: ");
          SERIAL_ECHOLNPGM(__DATE__);
    
    MagoKimbra's avatar
    MagoKimbra committed
        #endif // STRING_CONFIG_H_AUTHOR
    
      #endif // STRING_DISTRIBUTION_DATE
    
      SERIAL_ECHO_START;
      SERIAL_ECHOPGM(MSG_FREE_MEMORY);
      SERIAL_ECHO(freeMemory());
      SERIAL_ECHOPGM(MSG_PLANNER_BUFFER_BYTES);
      SERIAL_ECHOLN((int)sizeof(block_t)*BLOCK_BUFFER_SIZE);
    
        for (int8_t i = 0; i < BUFSIZE; i++) fromsd[i] = false;
    
      #endif
    
      // loads data from EEPROM if available else uses defaults (and resets step acceleration rate)
    
      Config_RetrieveSettings();
    
    AnHardt's avatar
    AnHardt committed
      lcd_init();
    
    
      tp_init();    // Initialize temperature loop
    
      plan_init();  // Initialize planner;
      watchdog_init();
      st_init();    // Initialize stepper, this enables interrupts!
      setup_photpin();
    
      #if HAS_CONTROLLERFAN
    
        SET_OUTPUT(CONTROLLERFAN_PIN); //Set pin used for driver cooling fan
    
      #if HAS_STEPPER_RESET
        enableStepperDrivers();
      #endif
    
    
        digipot_i2c_init();
      #endif
    
      #if ENABLED(Z_PROBE_SLED)
    
    Christian Bohn's avatar
    Christian Bohn committed
        pinMode(SLED_PIN, OUTPUT);
        digitalWrite(SLED_PIN, LOW); // turn it off
    
      #endif // Z_PROBE_SLED
    
    
      setup_homepin();
    
      #ifdef STAT_LED_RED
        pinMode(STAT_LED_RED, OUTPUT);
        digitalWrite(STAT_LED_RED, LOW); // turn it off
      #endif
    
      #ifdef STAT_LED_BLUE
        pinMode(STAT_LED_BLUE, OUTPUT);
        digitalWrite(STAT_LED_BLUE, LOW); // turn it off
    
    /**
     * The main Marlin program loop
     *
     *  - Save or log commands to SD
     *  - Process available commands (if not saving)
     *  - Call heater manager
     *  - Call inactivity manager
     *  - Call endstop manager
     *  - Call LCD update
     */
    
    void loop() {
    
      if (commands_in_queue < BUFSIZE - 1) get_command();
    
        card.checkautostart(false);
    
      if (commands_in_queue) {
    
    
          if (card.saving) {
    
            char *command = command_queue[cmd_queue_index_r];
            if (strstr_P(command, PSTR("M29"))) {
              // M29 closes the file
              card.closefile();
              SERIAL_PROTOCOLLNPGM(MSG_FILE_SAVED);
            }
            else {
              // Write the string from the read buffer to SD
              card.write_command(command);
    
              if (card.logging)
    
                process_next_command(); // The card is saving because it's logging
    
                SERIAL_PROTOCOLLNPGM(MSG_OK);
    
            process_next_command();
    
          process_next_command();
    
        #endif // SDSUPPORT
    
    
        commands_in_queue--;
        cmd_queue_index_r = (cmd_queue_index_r + 1) % BUFSIZE;
    
    void gcode_line_error(const char *err, bool doFlush=true) {
      SERIAL_ERROR_START;
      serialprintPGM(err);
      SERIAL_ERRORLN(gcode_LastN);
      //Serial.println(gcode_N);
      if (doFlush) FlushSerialRequestResend();
      serial_count = 0;
    }
    
    
    /**
     * Add to the circular command queue the next command from:
     *  - The command-injection queue (queued_commands_P)
     *  - The active serial input (usually USB)
     *  - The SD card file being actively printed
     */
    
    void get_command() {
    
      if (drain_queued_commands_P()) return; // priority is given to non-serial commands
    
        static millis_t last_command_time = 0;
        millis_t ms = millis();
    
    Wurstnase's avatar
    Wurstnase committed
        if (!MYSERIAL.available() && commands_in_queue == 0 && ms - last_command_time > NO_TIMEOUTS) {
    
          SERIAL_ECHOLNPGM(MSG_WAIT);
          last_command_time = ms;
        }
      #endif
    
    Scott Lahteine's avatar
    Scott Lahteine committed
    
      //
      // Loop while serial characters are incoming and the queue is not full
      //
      while (commands_in_queue < BUFSIZE && MYSERIAL.available() > 0) {
    
    
          last_command_time = ms;
        #endif
    
    Scott Lahteine's avatar
    Scott Lahteine committed
        //
        // If the character ends the line, or the line is full...
        //
        if (serial_char == '\n' || serial_char == '\r' || serial_count >= MAX_CMD_SIZE-1) {
    
    
          // end of line == end of comment
          comment_mode = false;
    
    
    Scott Lahteine's avatar
    Scott Lahteine committed
          if (!serial_count) return; // empty lines just exit
    
          char *command = command_queue[cmd_queue_index_w];
          command[serial_count] = 0; // terminate string
    
    Scott Lahteine's avatar
    Scott Lahteine committed
          // this item in the queue is not from sd
    
            fromsd[cmd_queue_index_w] = false;
    
          char *npos = strchr(command, 'N');
          char *apos = strchr(command, '*');
          if (npos) {
    
    
            boolean M110 = strstr_P(command, PSTR("M110")) != NULL;
    
            if (M110) {
              char *n2pos = strchr(command + 4, 'N');
              if (n2pos) npos = n2pos;
            }
    
    
            gcode_N = strtol(npos + 1, NULL, 10);
    
            if (gcode_N != gcode_LastN + 1 && !M110) {
    
              gcode_line_error(PSTR(MSG_ERR_LINE_NO));
    
            if (apos) {
              byte checksum = 0, count = 0;
    
              while (command[count] != '*') checksum ^= command[count++];
    
              if (strtol(apos + 1, NULL, 10) != checksum) {
    
                gcode_line_error(PSTR(MSG_ERR_CHECKSUM_MISMATCH));
    
              // if no errors, continue parsing
    
            else if (npos == command) {
    
              gcode_line_error(PSTR(MSG_ERR_NO_CHECKSUM));
    
            gcode_LastN = gcode_N;
    
            // if no errors, continue parsing
    
          else if (apos) { // No '*' without 'N'
            gcode_line_error(PSTR(MSG_ERR_NO_LINENUMBER_WITH_CHECKSUM), false);
            return;
    
          // Movement commands alert when stopped
          if (IsStopped()) {
            char *gpos = strchr(command, 'G');
            if (gpos) {
              int codenum = strtol(gpos + 1, NULL, 10);
              switch (codenum) {
                case 0:
                case 1:
                case 2:
                case 3:
    
                  SERIAL_ERRORLNPGM(MSG_ERR_STOPPED);
                  LCD_MESSAGEPGM(MSG_STOPPED);
    
          // If command was e-stop process now
    
          if (strcmp(command, "M112") == 0) kill(PSTR(MSG_KILLED));
    
          cmd_queue_index_w = (cmd_queue_index_w + 1) % BUFSIZE;
          commands_in_queue += 1;
    
        else if (serial_char == '\\') {  // Handle escapes
    
    Scott Lahteine's avatar
    Scott Lahteine committed
          if (MYSERIAL.available() > 0 && commands_in_queue < BUFSIZE) {
    
            // if we have one more character, copy it over
            serial_char = MYSERIAL.read();
    
            command_queue[cmd_queue_index_w][serial_count++] = serial_char;
    
          }
          // otherwise do nothing
    
        }
        else { // its not a newline, carriage return or escape char
    
          if (serial_char == ';') comment_mode = true;
    
          if (!comment_mode) command_queue[cmd_queue_index_w][serial_count++] = serial_char;
    
        if (!card.sdprinting || serial_count) return;
    
        // '#' stops reading from SD to the buffer prematurely, so procedural macro calls are possible
        // if it occurs, stop_buffering is triggered and the buffer is ran dry.
        // this character _can_ occur in serial com, due to checksums. however, no checksums are used in SD printing
    
        static bool stop_buffering = false;
    
        if (commands_in_queue == 0) stop_buffering = false;
    
        while (!card.eof() && commands_in_queue < BUFSIZE && !stop_buffering) {
    
          int16_t n = card.get();
          serial_char = (char)n;
          if (serial_char == '\n' || serial_char == '\r' ||
              ((serial_char == '#' || serial_char == ':') && !comment_mode) ||
              serial_count >= (MAX_CMD_SIZE - 1) || n == -1
          ) {
            if (card.eof()) {
              SERIAL_PROTOCOLLNPGM(MSG_FILE_PRINTED);
    
              print_job_stop_ms = millis();
    
              char time[30];
    
              millis_t t = (print_job_stop_ms - print_job_start_ms) / 1000;
    
              int hours = t / 60 / 60, minutes = (t / 60) % 60;
              sprintf_P(time, PSTR("%i " MSG_END_HOUR " %i " MSG_END_MINUTE), hours, minutes);
              SERIAL_ECHO_START;
              SERIAL_ECHOLN(time);
              lcd_setstatus(time, true);
              card.printingHasFinished();
              card.checkautostart(true);
            }
            if (serial_char == '#') stop_buffering = true;
    
            if (!serial_count) {
              comment_mode = false; //for new command
              return; //if empty line
            }
    
            command_queue[cmd_queue_index_w][serial_count] = 0; //terminate string
    
            // if (!comment_mode) {
    
            fromsd[cmd_queue_index_w] = true;
            commands_in_queue += 1;
            cmd_queue_index_w = (cmd_queue_index_w + 1) % BUFSIZE;
    
            // }
            comment_mode = false; //for new command
            serial_count = 0; //clear buffer
          }
          else {
            if (serial_char == ';') comment_mode = true;
    
            if (!comment_mode) command_queue[cmd_queue_index_w][serial_count++] = serial_char;
    
      #endif // SDSUPPORT
    
    bool code_has_value() {
    
      int i = 1;
    
      char c = seen_pointer[i];
      if (c == '-' || c == '+') c = seen_pointer[++i];
      if (c == '.') c = seen_pointer[++i];
    
      return (c >= '0' && c <= '9');
    
    Scott Lahteine's avatar
    Scott Lahteine committed
    }
    
    
    Scott Lahteine's avatar
    Scott Lahteine committed
    float code_value() {
      float ret;
    
      char *e = strchr(seen_pointer, 'E');
    
    Scott Lahteine's avatar
    Scott Lahteine committed
      if (e) {
        *e = 0;
    
        ret = strtod(seen_pointer+1, NULL);
    
    Scott Lahteine's avatar
    Scott Lahteine committed
        *e = 'E';
      }
      else
    
        ret = strtod(seen_pointer+1, NULL);
    
    Scott Lahteine's avatar
    Scott Lahteine committed
      return ret;
    
    long code_value_long() { return strtol(seen_pointer + 1, NULL, 10); }
    
    int16_t code_value_short() { return (int16_t)strtol(seen_pointer + 1, NULL, 10); }
    
    Scott Lahteine's avatar
    Scott Lahteine committed
    bool code_seen(char code) {
    
      seen_pointer = strchr(current_command_args, code);
      return (seen_pointer != NULL); // Return TRUE if the code-letter was found
    
    #define DEFINE_PGM_READ_ANY(type, reader)       \
        static inline type pgm_read_any(const type *p)  \
        { return pgm_read_##reader##_near(p); }
    
    
    DEFINE_PGM_READ_ANY(float,       float);
    DEFINE_PGM_READ_ANY(signed char, byte);