/** Delay in microseconds \param delay time to wait in microseconds Calibrated in SimulAVR. Accuracy on 20 MHz CPU clock: -1/+3 clock cycles over the whole range(!). Accuracy on 16 MHz CPU clock: delay is about 0.8% too short. Exceptions are delays of 0..2 on 20 MHz, which are all 0.75 us and delays of 0..3 on 16 MHz, which are all 0.93us. */ void delay_us(uint16_t delay) { wd_reset(); // Compensate call overhead, as close as possible. #define OVERHEAD_CALL_CLOCKS 39 // clock cycles #define OVERHEAD_CALL_DIV ((OVERHEAD_CALL_CLOCKS / (F_CPU / 1000000)) + 1) #define OVERHEAD_CALL_REM ((OVERHEAD_CALL_DIV * (F_CPU / 1000000)) - \ OVERHEAD_CALL_CLOCKS) if (delay > OVERHEAD_CALL_DIV) { delay -= OVERHEAD_CALL_DIV; if (OVERHEAD_CALL_REM >= 2) _delay_loop_2((OVERHEAD_CALL_REM + 2) / 4); } else { return; } while (delay > (65536L / (F_CPU / 4000000L))) { #define OVERHEAD_LOOP_CLOCKS 13 _delay_loop_2(65536 - (OVERHEAD_LOOP_CLOCKS + 2) / 4); delay -= (65536L / (F_CPU / 4000000L)); wd_reset(); } if (delay) _delay_loop_2(delay * (F_CPU / 4000000L)); wd_reset(); }
/** Delay in milliseconds. \param delay Time to wait in milliseconds. Accuracy on AVR, 20 MHz: delay < 0.04% too long over the whole range. Accuracy on AVR, 16 MHz: delay < 0.8% too short over the whole range. Accuracy on ARM, 48 MHz: delay < 0.1% too long over the whole range. */ void delay_ms(uint32_t delay) { wd_reset(); while (delay > 65) { delay_us(64999); delay -= 65; wd_reset(); } delay_us(delay * 1000 - 2); wd_reset(); }
/// delay microseconds /// \param delay time to wait in microseconds void _delay(uint32_t delay) { #ifdef TRASH wd_reset(); while (delay > (65536L / (F_CPU / 4000000L))) { _delay_loop_2(65534); // we use 65534 here to compensate for the time that the surrounding loop takes. TODO: exact figure needs tuning delay -= (65536L / (F_CPU / 4000000L)); wd_reset(); } _delay_loop_2(delay * (F_CPU / 4000000L)); wd_reset(); #endif //#ifdef TRASH }
/// delay milliseconds /// \param delay time to wait in milliseconds void _delay_ms(uint32_t delay) { #ifdef TRASH wd_reset(); while (delay > 65) { delay_us(64999); delay -= 65; wd_reset(); } delay_us(delay * 1000); wd_reset(); #endif //#ifdef TRASH }
void init(void) { // set up watchdog wd_init(); // set up serial serial_init(); // set up inputs and outputs io_init(); // set up timers timer_init(); // read PID settings from EEPROM heater_init(); // set up default feedrate current_position.F = startpoint.F = next_target.target.F = SEARCH_FEEDRATE_Z; // start up analog read interrupt loop, if anything uses analog as determined by ANALOG_MASK in your config.h analog_init(); // set up temperature inputs temp_init(); // enable interrupts sei(); // reset watchdog wd_reset(); // say hi to host serial_writestr_P(PSTR("Start\nok\n")); }
void emergency_stop(void) { power_off(); timer_stop(); queue_flush(); cli(); for (;;) wd_reset(); }
/*! do stuff every 10 milliseconds called from clock(), do not call directly */ static void clock_10ms(void) { // reset watchdog wd_reset(); temp_sensor_tick(); ifclock(clock_flag_250ms) { clock_250ms(); } }
void clock_10ms() { // reset watchdog wd_reset(); temp_tick(); ifclock(CLOCK_FLAG_250MS) { clock_250ms(); } }
/*! do stuff every 10 milliseconds call from ifclock(CLOCK_FLAG_10MS) in busy loops */ void clock_10ms() { // reset watchdog wd_reset(); temp_tick(); ifclock(clock_flag_250ms) { clock_250ms(); } update_position(); }
/// Startup code, run when we come out of reset void init(void) { halInit(); chSysInit(); #if defined(PORT_LED1) && defined(PIN_LED1) palSetPadMode(PORT_LED1, PIN_LED1, PAL_MODE_OUTPUT_PUSHPULL); #endif #if defined(PORT_LED2) && defined(PIN_LED2) palSetPadMode(PORT_LED2, PIN_LED2, PAL_MODE_OUTPUT_PUSHPULL); #endif // set up watchdog wd_init(); // set up serial serial_init(); // set up G-code parsing gcode_init(); // set up inputs and outputs io_init(); // set up timers timer_init(); // read PID settings from EEPROM heater_init(); // set up dda dda_init(); // start up analog read interrupt loop, // if any of the temp sensors in your config.h use analog interface analog_init(); // set up temperature inputs temp_init(); // enable interrupts enable_irq(); // reset watchdog wd_reset(); // say hi to host serial_writestr_P(PSTR("start\nok\n")); }
/*! do stuff every 10 milliseconds call from ifclock(CLOCK_FLAG_10MS) in busy loops */ void clock_10ms() { // reset watchdog wd_reset(); temp_tick(); #ifdef MOTOR_OVER_INTERCOM send_motor_if_new(); #endif ifclock(clock_flag_250ms) { clock_250ms(); } update_position(); }
/// Startup code, run when we come out of reset void init(void) { // set up watchdog wd_init(); // set up serial serial_init(); // set up G-code parsing gcode_init(); // set up inputs and outputs io_init(); // set up timers timer_init(); // read PID settings from EEPROM heater_init(); // set up dda dda_init(); // start up analog read interrupt loop, // if any of the temp sensors in your config.h use analog interface analog_init(); // set up temperature inputs temp_init(); // enable interrupts sei(); // reset watchdog wd_reset(); // prepare the power supply power_init(); // say hi to host serial_writestr_P(PSTR("start\nok\n")); }
/** * Configures the selected watchdog timer. * The watchdog is configured to reset after the first time out. * The function also starts the selected watchdog. * * @note Once this function successfully configures one watchdog, * it cannot be called (and configure other watchdogs) * anymore. In this case, nothing will be done and * pdFAIL will be returned immediately. * * @param wd - desired watchdog timer (between 0 and 1) * @param timeoutMs -watchdog's time out period in milliseconds * * @return pdPASS on success, pdFAIL if 'wd' is invalid */ int16_t watchdogInit( uint8_t wd, uint32_t timeoutMs ) { int16_t retVal = pdFAIL; /* * Immediately calculate the required load for the selected * watchdog, using relevant clocks' know frequencies. * Note: the frequency must be divided by 1000 first, otherwise * the multiplication of 'timeoutMs' and a frequency may * exceed the uint32_t range! */ const uint32_t timeoutTicks = ( 1==wd ? timeoutMs * (configPIOSC_CLOCK_HZ / 1000) : timeoutMs * (configCPU_CLOCK_HZ /1000) ); /* The function only succeeds if no watchdog has been configured yet */ if ( __wdNr==WD_UNSPECIFIED && wd<BSP_NR_WATCHDOGS ) { __wdNr = wd; /* reset the watchdog */ wd_reset( __wdNr ); /* configure its timeout period */ wd_config( __wdNr, timeoutTicks , WDEX_IRQ_RESET ); /* register its ISR handler */ wd_registerIntHandler( __wdNr, &__wdHandler ); /* and start the watchdog */ wd_start( __wdNr ); /* finally assign it a high interrupt priority */ wd_setIntPriority(0); retVal = pdPASS; } return retVal; }
/** * Reloads the selected watchdog's counter to the * load value, specified by wd_config(). * * Nothing is done if 'wd' is greater than 1. * * @param wd - watchdog timer (between 0 and 1) */ void wd_reload(uint8_t wd) { /* * For more details about the WDTLOAD register, * see page 779 of the Data Sheet */ if ( wd >= BSP_NR_WATCHDOGS ) { return; } if ( 1==wd && (6==sysctl_mcu_revision || 7==sysctl_mcu_revision) ) { /* * According to page 64 of the Silicon Errata, * reloading the WDTLOAD of the watchdog 1 will not * restart the counter. As a workaround it is * suggested to reset, reconfigure and restart * the watchdog. */ wd_reset(wd); wd_config(wd, __wdSettings[wd].loadVal, __wdSettings[wd].exType); /* Set the STALL bit if necessary */ HWREG_SET_BITS( pReg[wd]->WD_TEST, TST_STALL ); __waitWd1(wd); wd_start(wd); } else { /* * At other watchdogs, no workaround is necessary. * Reloading the WDTLOAD register will also * restart the counter. * Note: __waitWd1() is still preserved in the code. */ /* * There is a hardware bug in both watchdog timers. * If the STALL bit of the WDTTEST register is set, * the WTDLOAD register cannot be changed. * See page 68 of the Silicon Errata for more details. * * For that reason the STALL bit will be cleared first * and set again later. */ HWREG_CLEAR_BITS( pReg[wd]->WD_TEST, TST_STALL ); __waitWd1(wd); pReg[wd]->WD_LOAD = __wdSettings[wd].loadVal; __waitWd1(wd); /* Set the STALL bit again. */ HWREG_SET_BITS( pReg[wd]->WD_TEST, TST_STALL ); __waitWd1(wd); } }
void process_gcode_command() { uint32_t backup_f; // convert relative to absolute if (next_target.option_all_relative) { next_target.target.axis[X] += startpoint.axis[X]; next_target.target.axis[Y] += startpoint.axis[Y]; next_target.target.axis[Z] += startpoint.axis[Z]; } // E relative movement. // Matches Sprinter's behaviour as of March 2012. if (next_target.option_e_relative) next_target.target.e_relative = 1; else next_target.target.e_relative = 0; if (next_target.option_all_relative && !next_target.option_e_relative) next_target.target.axis[E] += startpoint.axis[E]; // implement axis limits #ifdef X_MIN if (next_target.target.axis[X] < (int32_t)(X_MIN * 1000.)) next_target.target.axis[X] = (int32_t)(X_MIN * 1000.); #endif #ifdef X_MAX if (next_target.target.axis[X] > (int32_t)(X_MAX * 1000.)) next_target.target.axis[X] = (int32_t)(X_MAX * 1000.); #endif #ifdef Y_MIN if (next_target.target.axis[Y] < (int32_t)(Y_MIN * 1000.)) next_target.target.axis[Y] = (int32_t)(Y_MIN * 1000.); #endif #ifdef Y_MAX if (next_target.target.axis[Y] > (int32_t)(Y_MAX * 1000.)) next_target.target.axis[Y] = (int32_t)(Y_MAX * 1000.); #endif #ifdef Z_MIN if (next_target.target.axis[Z] < (int32_t)(Z_MIN * 1000.)) next_target.target.axis[Z] = (int32_t)(Z_MIN * 1000.); #endif #ifdef Z_MAX if (next_target.target.axis[Z] > (int32_t)(Z_MAX * 1000.)) next_target.target.axis[Z] = (int32_t)(Z_MAX * 1000.); #endif // The GCode documentation was taken from http://reprap.org/wiki/Gcode . if (next_target.seen_T) { //? --- T: Select Tool --- //? //? Example: T1 //? //? Select extruder number 1 to build with. Extruder numbering starts at 0. next_tool = next_target.T; } if (next_target.seen_G) { uint8_t axisSelected = 0; switch (next_target.G) { case 0: //? G0: Rapid Linear Motion //? //? Example: G0 X12 //? //? In this case move rapidly to X = 12 mm. In fact, the RepRap firmware uses exactly the same code for rapid as it uses for controlled moves (see G1 below), as - for the RepRap machine - this is just as efficient as not doing so. (The distinction comes from some old machine tools that used to move faster if the axes were not driven in a straight line. For them G0 allowed any movement in space to get to the destination as fast as possible.) //? temp_wait(); backup_f = next_target.target.F; next_target.target.F = MAXIMUM_FEEDRATE_X * 2L; enqueue(&next_target.target); next_target.target.F = backup_f; break; case 1: //? --- G1: Linear Motion at Feed Rate --- //? //? Example: G1 X90.6 Y13.8 E22.4 //? //? Go in a straight line from the current (X, Y) point to the point (90.6, 13.8), extruding material as the move happens from the current extruded length to a length of 22.4 mm. //? temp_wait(); enqueue(&next_target.target); break; // G2 - Arc Clockwise // unimplemented // G3 - Arc Counter-clockwise // unimplemented case 4: //? --- G4: Dwell --- //? //? Example: G4 P200 //? //? In this case sit still doing nothing for 200 milliseconds. During delays the state of the machine (for example the temperatures of its extruders) will still be preserved and controlled. //? queue_wait(); // delay if (next_target.seen_P) { for (;next_target.P > 0;next_target.P--) { clock(); delay_ms(1); } } break; case 20: //? --- G20: Set Units to Inches --- //? //? Example: G20 //? //? Units from now on are in inches. //? next_target.option_inches = 1; break; case 21: //? --- G21: Set Units to Millimeters --- //? //? Example: G21 //? //? Units from now on are in millimeters. (This is the RepRap default.) //? next_target.option_inches = 0; break; case 28: //? --- G28: Home --- //? //? Example: G28 //? //? This causes the RepRap machine to search for its X, Y and Z //? endstops. It does so at high speed, so as to get there fast. When //? it arrives it backs off slowly until the endstop is released again. //? Backing off slowly ensures more accurate positioning. //? //? If you add axis characters, then just the axes specified will be //? seached. Thus //? //? G28 X Y72.3 //? //? will zero the X and Y axes, but not Z. Coordinate values are //? ignored. //? queue_wait(); if (next_target.seen_X) { #if defined X_MIN_PIN home_x_negative(); #elif defined X_MAX_PIN home_x_positive(); #endif axisSelected = 1; } if (next_target.seen_Y) { #if defined Y_MIN_PIN home_y_negative(); #elif defined Y_MAX_PIN home_y_positive(); #endif axisSelected = 1; } if (next_target.seen_Z) { #if defined Z_MIN_PIN home_z_negative(); #elif defined Z_MAX_PIN home_z_positive(); #endif axisSelected = 1; } // there's no point in moving E, as E has no endstops if (!axisSelected) { home(); } break; case 90: //? --- G90: Set to Absolute Positioning --- //? //? Example: G90 //? //? All coordinates from now on are absolute relative to the origin //? of the machine. This is the RepRap default. //? //? If you ever want to switch back and forth between relative and //? absolute movement keep in mind, X, Y and Z follow the machine's //? coordinate system while E doesn't change it's position in the //? coordinate system on relative movements. //? // No wait_queue() needed. next_target.option_all_relative = 0; break; case 91: //? --- G91: Set to Relative Positioning --- //? //? Example: G91 //? //? All coordinates from now on are relative to the last position. //? // No wait_queue() needed. next_target.option_all_relative = 1; break; case 92: //? --- G92: Set Position --- //? //? Example: G92 X10 E90 //? //? Allows programming of absolute zero point, by reseting the current position to the values specified. This would set the machine's X coordinate to 10, and the extrude coordinate to 90. No physical motion will occur. //? queue_wait(); if (next_target.seen_X) { startpoint.axis[X] = next_target.target.axis[X]; axisSelected = 1; } if (next_target.seen_Y) { startpoint.axis[Y] = next_target.target.axis[Y]; axisSelected = 1; } if (next_target.seen_Z) { startpoint.axis[Z] = next_target.target.axis[Z]; axisSelected = 1; } if (next_target.seen_E) { startpoint.axis[E] = next_target.target.axis[E]; axisSelected = 1; } if (axisSelected == 0) { startpoint.axis[X] = next_target.target.axis[X] = startpoint.axis[Y] = next_target.target.axis[Y] = startpoint.axis[Z] = next_target.target.axis[Z] = startpoint.axis[E] = next_target.target.axis[E] = 0; } dda_new_startpoint(); break; case 161: //? --- G161: Home negative --- //? //? Find the minimum limit of the specified axes by searching for the limit switch. //? #if defined X_MIN_PIN if (next_target.seen_X) home_x_negative(); #endif #if defined Y_MIN_PIN if (next_target.seen_Y) home_y_negative(); #endif #if defined Z_MIN_PIN if (next_target.seen_Z) home_z_negative(); #endif break; case 162: //? --- G162: Home positive --- //? //? Find the maximum limit of the specified axes by searching for the limit switch. //? #if defined X_MAX_PIN if (next_target.seen_X) home_x_positive(); #endif #if defined Y_MAX_PIN if (next_target.seen_Y) home_y_positive(); #endif #if defined Z_MAX_PIN if (next_target.seen_Z) home_z_positive(); #endif break; // unknown gcode: spit an error default: sersendf_P(PSTR("E: Bad G-code %d\n"), next_target.G); return; } } else if (next_target.seen_M) { uint8_t i; switch (next_target.M) { case 0: //? --- M0: machine stop --- //? //? Example: M0 //? //? http://linuxcnc.org/handbook/RS274NGC_3/RS274NGC_33a.html#1002379 //? Unimplemented, especially the restart after the stop. Fall trough to M2. //? case 2: case 84: // For compatibility with slic3rs default end G-code. //? --- M2: program end --- //? //? Example: M2 //? //? http://linuxcnc.org/handbook/RS274NGC_3/RS274NGC_33a.html#1002379 //? queue_wait(); for (i = 0; i < NUM_HEATERS; i++) temp_set(i, 0); power_off(); serial_writestr_P(PSTR("\nstop\n")); break; case 6: //? --- M6: tool change --- //? //? Undocumented. tool = next_tool; break; #ifdef SD case 20: //? --- M20: list SD card. --- sd_list("/"); break; case 21: //? --- M21: initialise SD card. --- //? //? Has to be done before doing any other operation, including M20. sd_mount(); break; case 22: //? --- M22: release SD card. --- //? //? Not mandatory. Just removing the card is fine, but results in //? odd behaviour when trying to read from the card anyways. M22 //? makes also sure SD card printing is disabled, even with the card //? inserted. sd_unmount(); break; case 23: //? --- M23: select file. --- //? //? This opens a file for reading. This file is valid up to M22 or up //? to the next M23. sd_open(gcode_str_buf); break; case 24: //? --- M24: start/resume SD print. --- //? //? This makes the SD card available as a G-code source. File is the //? one selected with M23. gcode_sources |= GCODE_SOURCE_SD; break; case 25: //? --- M25: pause SD print. --- //? //? This removes the SD card from the bitfield of available G-code //? sources. The file is kept open. The position inside the file //? is kept as well, to allow resuming. gcode_sources &= ! GCODE_SOURCE_SD; break; #endif /* SD */ case 82: //? --- M82 - Set E codes absolute --- //? //? This is the default and overrides G90/G91. //? M82/M83 is not documented in the RepRap wiki, behaviour //? was taken from Sprinter as of March 2012. //? //? While E does relative movements, it doesn't change its //? position in the coordinate system. See also comment on G90. //? // No wait_queue() needed. next_target.option_e_relative = 0; break; case 83: //? --- M83 - Set E codes relative --- //? //? Counterpart to M82. //? // No wait_queue() needed. next_target.option_e_relative = 1; break; // M3/M101- extruder on case 3: case 101: //? --- M101: extruder on --- //? //? Undocumented. temp_wait(); #ifdef DC_EXTRUDER heater_set(DC_EXTRUDER, DC_EXTRUDER_PWM); #endif break; // M5/M103- extruder off case 5: case 103: //? --- M103: extruder off --- //? //? Undocumented. #ifdef DC_EXTRUDER heater_set(DC_EXTRUDER, 0); #endif break; case 104: //? --- M104: Set Extruder Temperature (Fast) --- //? //? Example: M104 S190 //? //? Set the temperature of the current extruder to 190<sup>o</sup>C //? and return control to the host immediately (''i.e.'' before that //? temperature has been reached by the extruder). For waiting, see M116. //? //? Teacup supports an optional P parameter as a zero-based temperature //? sensor index to address (e.g. M104 P1 S100 will set the temperature //? of the heater connected to the second temperature sensor rather //? than the extruder temperature). //? if ( ! next_target.seen_S) break; if ( ! next_target.seen_P) #ifdef HEATER_EXTRUDER next_target.P = HEATER_EXTRUDER; #else next_target.P = 0; #endif temp_set(next_target.P, next_target.S); break; case 105: //? --- M105: Get Temperature(s) --- //? //? Example: M105 //? //? Request the temperature of the current extruder and the build base //? in degrees Celsius. For example, the line sent to the host in //? response to this command looks like //? //? <tt>ok T:201 B:117</tt> //? //? Teacup supports an optional P parameter as a zero-based temperature //? sensor index to address. //? #ifdef ENFORCE_ORDER queue_wait(); #endif if ( ! next_target.seen_P) next_target.P = TEMP_SENSOR_none; temp_print(next_target.P); break; case 7: case 106: //? --- M106: Set Fan Speed / Set Device Power --- //? //? Example: M106 S120 //? //? Control the cooling fan (if any). //? //? Teacup supports an optional P parameter as a zero-based heater //? index to address. The heater index can differ from the temperature //? sensor index, see config.h. #ifdef ENFORCE_ORDER // wait for all moves to complete queue_wait(); #endif if ( ! next_target.seen_P) #ifdef HEATER_FAN next_target.P = HEATER_FAN; #else next_target.P = 0; #endif if ( ! next_target.seen_S) break; heater_set(next_target.P, next_target.S); break; case 110: //? --- M110: Set Current Line Number --- //? //? Example: N123 M110 //? //? Set the current line number to 123. Thus the expected next line after this command will be 124. //? This is a no-op in Teacup. //? break; #ifdef DEBUG case 111: //? --- M111: Set Debug Level --- //? //? Example: M111 S6 //? //? Set the level of debugging information transmitted back to the host to level 6. The level is the OR of three bits: //? //? <Pre> //? #define DEBUG_PID 1 //? #define DEBUG_DDA 2 //? #define DEBUG_POSITION 4 //? </pre> //? //? This command is only available in DEBUG builds of Teacup. if ( ! next_target.seen_S) break; debug_flags = next_target.S; break; #endif /* DEBUG */ case 112: //? --- M112: Emergency Stop --- //? //? Example: M112 //? //? Any moves in progress are immediately terminated, then the printer //? shuts down. All motors and heaters are turned off. Only way to //? restart is to press the reset button on the master microcontroller. //? See also M0. //? timer_stop(); queue_flush(); power_off(); cli(); for (;;) wd_reset(); break; case 114: //? --- M114: Get Current Position --- //? //? Example: M114 //? //? This causes the RepRap machine to report its current X, Y, Z and E coordinates to the host. //? //? For example, the machine returns a string such as: //? //? <tt>ok C: X:0.00 Y:0.00 Z:0.00 E:0.00</tt> //? #ifdef ENFORCE_ORDER // wait for all moves to complete queue_wait(); #endif update_current_position(); sersendf_P(PSTR("X:%lq,Y:%lq,Z:%lq,E:%lq,F:%lu\n"), current_position.axis[X], current_position.axis[Y], current_position.axis[Z], current_position.axis[E], current_position.F); if (mb_tail_dda != NULL) { if (DEBUG_POSITION && (debug_flags & DEBUG_POSITION)) { sersendf_P(PSTR("Endpoint: X:%ld,Y:%ld,Z:%ld,E:%ld,F:%lu,c:%lu}\n"), mb_tail_dda->endpoint.axis[X], mb_tail_dda->endpoint.axis[Y], mb_tail_dda->endpoint.axis[Z], mb_tail_dda->endpoint.axis[E], mb_tail_dda->endpoint.F, #ifdef ACCELERATION_REPRAP mb_tail_dda->end_c #else mb_tail_dda->c #endif ); } print_queue(); } break; case 115: //? --- M115: Get Firmware Version and Capabilities --- //? //? Example: M115 //? //? Request the Firmware Version and Capabilities of the current microcontroller //? The details are returned to the host computer as key:value pairs separated by spaces and terminated with a linefeed. //? //? sample data from firmware: //? FIRMWARE_NAME:Teacup FIRMWARE_URL:http://github.com/traumflug/Teacup_Firmware/ PROTOCOL_VERSION:1.0 MACHINE_TYPE:Mendel EXTRUDER_COUNT:1 TEMP_SENSOR_COUNT:1 HEATER_COUNT:1 //? sersendf_P(PSTR("FIRMWARE_NAME:Teacup FIRMWARE_URL:http://github.com/traumflug/Teacup_Firmware/ PROTOCOL_VERSION:1.0 MACHINE_TYPE:Mendel EXTRUDER_COUNT:%d TEMP_SENSOR_COUNT:%d HEATER_COUNT:%d\n"), 1, NUM_TEMP_SENSORS, NUM_HEATERS); break; case 116: //? --- M116: Wait --- //? //? Example: M116 //? //? Wait for temperatures and other slowly-changing variables to arrive at their set values. temp_set_wait(); break; case 119: //? --- M119: report endstop status --- //? Report the current status of the endstops configured in the //? firmware to the host. power_on(); endstops_on(); delay_ms(10); // allow the signal to stabilize { #if ! (defined(X_MIN_PIN) || defined(X_MAX_PIN) || \ defined(Y_MIN_PIN) || defined(Y_MAX_PIN) || \ defined(Z_MIN_PIN) || defined(Z_MAX_PIN)) serial_writestr_P(PSTR("No endstops defined.")); #else const char* const open = PSTR("open "); const char* const triggered = PSTR("triggered "); #endif #if defined(X_MIN_PIN) serial_writestr_P(PSTR("x_min:")); x_min() ? serial_writestr_P(triggered) : serial_writestr_P(open); #endif #if defined(X_MAX_PIN) serial_writestr_P(PSTR("x_max:")); x_max() ? serial_writestr_P(triggered) : serial_writestr_P(open); #endif #if defined(Y_MIN_PIN) serial_writestr_P(PSTR("y_min:")); y_min() ? serial_writestr_P(triggered) : serial_writestr_P(open); #endif #if defined(Y_MAX_PIN) serial_writestr_P(PSTR("y_max:")); y_max() ? serial_writestr_P(triggered) : serial_writestr_P(open); #endif #if defined(Z_MIN_PIN) serial_writestr_P(PSTR("z_min:")); z_min() ? serial_writestr_P(triggered) : serial_writestr_P(open); #endif #if defined(Z_MAX_PIN) serial_writestr_P(PSTR("z_max:")); z_max() ? serial_writestr_P(triggered) : serial_writestr_P(open); #endif } endstops_off(); serial_writechar('\n'); break; #ifdef EECONFIG case 130: //? --- M130: heater P factor --- //? Undocumented. // P factor in counts per degreeC of error if ( ! next_target.seen_P) #ifdef HEATER_EXTRUDER next_target.P = HEATER_EXTRUDER; #else next_target.P = 0; #endif if (next_target.seen_S) pid_set_p(next_target.P, next_target.S); break; case 131: //? --- M131: heater I factor --- //? Undocumented. // I factor in counts per C*s of integrated error if ( ! next_target.seen_P) #ifdef HEATER_EXTRUDER next_target.P = HEATER_EXTRUDER; #else next_target.P = 0; #endif if (next_target.seen_S) pid_set_i(next_target.P, next_target.S); break; case 132: //? --- M132: heater D factor --- //? Undocumented. // D factor in counts per degreesC/second if ( ! next_target.seen_P) #ifdef HEATER_EXTRUDER next_target.P = HEATER_EXTRUDER; #else next_target.P = 0; #endif if (next_target.seen_S) pid_set_d(next_target.P, next_target.S); break; case 133: //? --- M133: heater I limit --- //? Undocumented. if ( ! next_target.seen_P) #ifdef HEATER_EXTRUDER next_target.P = HEATER_EXTRUDER; #else next_target.P = 0; #endif if (next_target.seen_S) pid_set_i_limit(next_target.P, next_target.S); break; case 134: //? --- M134: save PID settings to eeprom --- //? Undocumented. heater_save_settings(); break; #endif /* EECONFIG */ #ifdef DEBUG case 136: //? --- M136: PRINT PID settings to host --- //? Undocumented. //? This comand is only available in DEBUG builds. if ( ! next_target.seen_P) #ifdef HEATER_EXTRUDER next_target.P = HEATER_EXTRUDER; #else next_target.P = 0; #endif heater_print(next_target.P); break; #endif /* DEBUG */ case 140: //? --- M140: Set heated bed temperature --- //? Undocumented. #ifdef HEATER_BED if ( ! next_target.seen_S) break; temp_set(HEATER_BED, next_target.S); #endif break; case 220: //? --- M220: Set speed factor override percentage --- if ( ! next_target.seen_S) break; // Scale 100% = 256 next_target.target.f_multiplier = (next_target.S * 64 + 12) / 25; break; case 221: //? --- M221: Control the extruders flow --- if ( ! next_target.seen_S) break; // Scale 100% = 256 next_target.target.e_multiplier = (next_target.S * 64 + 12) / 25; break; #ifdef DEBUG case 240: //? --- M240: echo off --- //? Disable echo. //? This command is only available in DEBUG builds. debug_flags &= ~DEBUG_ECHO; serial_writestr_P(PSTR("Echo off\n")); break; case 241: //? --- M241: echo on --- //? Enable echo. //? This command is only available in DEBUG builds. debug_flags |= DEBUG_ECHO; serial_writestr_P(PSTR("Echo on\n")); break; #endif /* DEBUG */ // unknown mcode: spit an error default: sersendf_P(PSTR("E: Bad M-code %d\n"), next_target.M); } // switch (next_target.M) } // else if (next_target.seen_M) } // process_gcode_command()
void process_gcode_command() { uint32_t backup_f; // convert relative to absolute if (next_target.option_all_relative) { next_target.target.X += startpoint.X; next_target.target.Y += startpoint.Y; next_target.target.Z += startpoint.Z; } // E relative movement. // Matches Sprinter's behaviour as of March 2012. if (next_target.option_all_relative || next_target.option_e_relative) next_target.target.e_relative = 1; else next_target.target.e_relative = 0; // implement axis limits #ifdef X_MIN if (next_target.target.X < X_MIN * 1000.) next_target.target.X = X_MIN * 1000.; #endif #ifdef X_MAX if (next_target.target.X > X_MAX * 1000.) next_target.target.X = X_MAX * 1000.; #endif #ifdef Y_MIN if (next_target.target.Y < Y_MIN * 1000.) next_target.target.Y = Y_MIN * 1000.; #endif #ifdef Y_MAX if (next_target.target.Y > Y_MAX * 1000.) next_target.target.Y = Y_MAX * 1000.; #endif #ifdef Z_MIN if (next_target.target.Z < Z_MIN * 1000.) next_target.target.Z = Z_MIN * 1000.; #endif #ifdef Z_MAX if (next_target.target.Z > Z_MAX * 1000.) next_target.target.Z = Z_MAX * 1000.; #endif // The GCode documentation was taken from http://reprap.org/wiki/Gcode . if (next_target.seen_T) { //? --- T: Select Tool --- //? //? Example: T1 //? //? Select extruder number 1 to build with. Extruder numbering starts at 0. next_tool = next_target.T; } if (next_target.seen_G) { uint8_t axisSelected = 0; switch (next_target.G) { case 0: //? G0: Rapid Linear Motion //? //? Example: G0 X12 //? //? In this case move rapidly to X = 12 mm. In fact, the RepRap firmware uses exactly the same code for rapid as it uses for controlled moves (see G1 below), as - for the RepRap machine - this is just as efficient as not doing so. (The distinction comes from some old machine tools that used to move faster if the axes were not driven in a straight line. For them G0 allowed any movement in space to get to the destination as fast as possible.) //? backup_f = next_target.target.F; next_target.target.F = MAXIMUM_FEEDRATE_X * 2L; enqueue(&next_target.target); next_target.target.F = backup_f; break; case 1: //? --- G1: Linear Motion at Feed Rate --- //? //? Example: G1 X90.6 Y13.8 E22.4 //? //? Go in a straight line from the current (X, Y) point to the point (90.6, 13.8), extruding material as the move happens from the current extruded length to a length of 22.4 mm. //? enqueue(&next_target.target); break; // G2 - Arc Clockwise // unimplemented // G3 - Arc Counter-clockwise // unimplemented case 4: //? --- G4: Dwell --- //? //? Example: G4 P200 //? //? In this case sit still doing nothing for 200 milliseconds. During delays the state of the machine (for example the temperatures of its extruders) will still be preserved and controlled. //? queue_wait(); // delay if (next_target.seen_P) { for (;next_target.P > 0;next_target.P--) { clock(); delay_ms(1); } } break; case 20: //? --- G20: Set Units to Inches --- //? //? Example: G20 //? //? Units from now on are in inches. //? next_target.option_inches = 1; break; case 21: //? --- G21: Set Units to Millimeters --- //? //? Example: G21 //? //? Units from now on are in millimeters. (This is the RepRap default.) //? next_target.option_inches = 0; break; case 30: //? --- G30: Go home via point --- //? //? Undocumented. enqueue(&next_target.target); // no break here, G30 is move and then go home case 28: //? --- G28: Home --- //? //? Example: G28 //? //? This causes the RepRap machine to move back to its X, Y and Z zero endstops. It does so accelerating, so as to get there fast. But when it arrives it backs off by 1 mm in each direction slowly, then moves back slowly to the stop. This ensures more accurate positioning. //? //? If you add coordinates, then just the axes with coordinates specified will be zeroed. Thus //? //? G28 X0 Y72.3 //? //? will zero the X and Y axes, but not Z. The actual coordinate values are ignored. //? queue_wait(); if (next_target.seen_X) { #if defined X_MIN_PIN home_x_negative(); #elif defined X_MAX_PIN home_x_positive(); #endif axisSelected = 1; } if (next_target.seen_Y) { #if defined Y_MIN_PIN home_y_negative(); #elif defined Y_MAX_PIN home_y_positive(); #endif axisSelected = 1; } if (next_target.seen_Z) { #if defined Z_MAX_PIN home_z_positive(); #elif defined Z_MIN_PIN home_z_negative(); #endif axisSelected = 1; } // there's no point in moving E, as E has no endstops if (!axisSelected) { home(); } break; case 90: //? --- G90: Set to Absolute Positioning --- //? //? Example: G90 //? //? All coordinates from now on are absolute relative to the origin //? of the machine. This is the RepRap default. //? //? If you ever want to switch back and forth between relative and //? absolute movement keep in mind, X, Y and Z follow the machine's //? coordinate system while E doesn't change it's position in the //? coordinate system on relative movements. //? // No wait_queue() needed. next_target.option_all_relative = 0; break; case 91: //? --- G91: Set to Relative Positioning --- //? //? Example: G91 //? //? All coordinates from now on are relative to the last position. //? // No wait_queue() needed. next_target.option_all_relative = 1; break; case 92: //? --- G92: Set Position --- //? //? Example: G92 X10 E90 //? //? Allows programming of absolute zero point, by reseting the current position to the values specified. This would set the machine's X coordinate to 10, and the extrude coordinate to 90. No physical motion will occur. //? queue_wait(); if (next_target.seen_X) { startpoint.X = next_target.target.X; axisSelected = 1; } if (next_target.seen_Y) { startpoint.Y = next_target.target.Y; axisSelected = 1; } if (next_target.seen_Z) { startpoint.Z = next_target.target.Z; axisSelected = 1; } if (next_target.seen_E) { startpoint.E = next_target.target.E; axisSelected = 1; } if (axisSelected == 0) { startpoint.X = next_target.target.X = startpoint.Y = next_target.target.Y = startpoint.Z = next_target.target.Z = startpoint.E = next_target.target.E = 0; } dda_new_startpoint(); break; case 161: //? --- G161: Home negative --- //? //? Find the minimum limit of the specified axes by searching for the limit switch. //? if (next_target.seen_X) home_x_negative(); if (next_target.seen_Y) home_y_negative(); if (next_target.seen_Z) home_z_negative(); break; case 162: //? --- G162: Home positive --- //? //? Find the maximum limit of the specified axes by searching for the limit switch. //? if (next_target.seen_X) home_x_positive(); if (next_target.seen_Y) home_y_positive(); if (next_target.seen_Z) home_z_positive(); break; // unknown gcode: spit an error default: sersendf_P(PSTR("E: Bad G-code %d"), next_target.G); // newline is sent from gcode_parse after we return return; } } else if (next_target.seen_M) { uint8_t i; switch (next_target.M) { case 0: //? --- M0: machine stop --- //? //? Example: M0 //? //? http://linuxcnc.org/handbook/RS274NGC_3/RS274NGC_33a.html#1002379 //? Unimplemented, especially the restart after the stop. Fall trough to M2. //? case 2: case 84: // For compatibility with slic3rs default end G-code. //? --- M2: program end --- //? //? Example: M2 //? //? http://linuxcnc.org/handbook/RS274NGC_3/RS274NGC_33a.html#1002379 //? queue_wait(); for (i = 0; i < NUM_HEATERS; i++) temp_set(i, 0); power_off(); break; case 112: //? --- M112: Emergency Stop --- //? //? Example: M112 //? //? Any moves in progress are immediately terminated, then RepRap shuts down. All motors and heaters are turned off. //? It can be started again by pressing the reset button on the master microcontroller. See also M0. //? timer_stop(); queue_flush(); power_off(); cli(); for (;;) wd_reset(); break; case 6: //? --- M6: tool change --- //? //? Undocumented. tool = next_tool; break; case 82: //? --- M82 - Set E codes absolute --- //? //? This is the default and overrides G90/G91. //? M82/M83 is not documented in the RepRap wiki, behaviour //? was taken from Sprinter as of March 2012. //? //? While E does relative movements, it doesn't change its //? position in the coordinate system. See also comment on G90. //? // No wait_queue() needed. next_target.option_e_relative = 0; break; case 83: //? --- M83 - Set E codes relative --- //? //? Counterpart to M82. //? // No wait_queue() needed. next_target.option_e_relative = 1; break; // M3/M101- extruder on case 3: case 101: //? --- M101: extruder on --- //? //? Undocumented. if (temp_achieved() == 0) { enqueue(NULL); } #ifdef DC_EXTRUDER heater_set(DC_EXTRUDER, DC_EXTRUDER_PWM); #elif E_STARTSTOP_STEPS > 0 do { // backup feedrate, move E very quickly then restore feedrate backup_f = startpoint.F; startpoint.F = MAXIMUM_FEEDRATE_E; SpecialMoveE(E_STARTSTOP_STEPS, MAXIMUM_FEEDRATE_E); startpoint.F = backup_f; } while (0); #endif break; // M102- extruder reverse // M5/M103- extruder off case 5: case 103: //? --- M103: extruder off --- //? //? Undocumented. #ifdef DC_EXTRUDER heater_set(DC_EXTRUDER, 0); #elif E_STARTSTOP_STEPS > 0 do { // backup feedrate, move E very quickly then restore feedrate backup_f = startpoint.F; startpoint.F = MAXIMUM_FEEDRATE_E; SpecialMoveE(-E_STARTSTOP_STEPS, MAXIMUM_FEEDRATE_E); startpoint.F = backup_f; } while (0); #endif break; case 104: //? --- M104: Set Extruder Temperature (Fast) --- //? //? Example: M104 S190 //? //? Set the temperature of the current extruder to 190<sup>o</sup>C //? and return control to the host immediately (''i.e.'' before that //? temperature has been reached by the extruder). For waiting, see M116. //? //? Teacup supports an optional P parameter as a zero-based temperature //? sensor index to address (e.g. M104 P1 S100 will set the temperature //? of the heater connected to the second temperature sensor rather //? than the extruder temperature). //? if ( ! next_target.seen_S) break; #ifdef HEATER_EXTRUDER if ( ! next_target.seen_P) next_target.P = HEATER_EXTRUDER; // else use the first available device #endif temp_set(next_target.P, next_target.S); break; case 105: //? --- M105: Get Temperature(s) --- //? //? Example: M105 //? //? Request the temperature of the current extruder and the build base //? in degrees Celsius. For example, the line sent to the host in //? response to this command looks like //? //? <tt>ok T:201 B:117</tt> //? //? Teacup supports an optional P parameter as a zero-based temperature //? sensor index to address. //? #ifdef ENFORCE_ORDER queue_wait(); #endif if ( ! next_target.seen_P) next_target.P = TEMP_SENSOR_none; temp_print(next_target.P); break; case 7: case 106: //? --- M106: Set Fan Speed / Set Device Power --- //? //? Example: M106 S120 //? //? Control the cooling fan (if any). //? //? Teacup supports an optional P parameter as a zero-based heater //? index to address. The heater index can differ from the temperature //? sensor index, see config.h. #ifdef ENFORCE_ORDER // wait for all moves to complete queue_wait(); #endif #ifdef HEATER_FAN if ( ! next_target.seen_P) next_target.P = HEATER_FAN; // else use the first available device #endif if ( ! next_target.seen_S) break; heater_set(next_target.P, next_target.S); break; case 110: //? --- M110: Set Current Line Number --- //? //? Example: N123 M110 //? //? Set the current line number to 123. Thus the expected next line after this command will be 124. //? This is a no-op in Teacup. //? break; #ifdef DEBUG case 111: //? --- M111: Set Debug Level --- //? //? Example: M111 S6 //? //? Set the level of debugging information transmitted back to the host to level 6. The level is the OR of three bits: //? //? <Pre> //? #define DEBUG_PID 1 //? #define DEBUG_DDA 2 //? #define DEBUG_POSITION 4 //? </pre> //? //? This command is only available in DEBUG builds of Teacup. if ( ! next_target.seen_S) break; debug_flags = next_target.S; break; #endif case 114: //? --- M114: Get Current Position --- //? //? Example: M114 //? //? This causes the RepRap machine to report its current X, Y, Z and E coordinates to the host. //? //? For example, the machine returns a string such as: //? //? <tt>ok C: X:0.00 Y:0.00 Z:0.00 E:0.00</tt> //? #ifdef ENFORCE_ORDER // wait for all moves to complete queue_wait(); #endif update_current_position(); sersendf_P(PSTR("X:%lq,Y:%lq,Z:%lq,E:%lq,F:%lu"), current_position.X, current_position.Y, current_position.Z, current_position.E, current_position.F); #ifdef DEBUG if (DEBUG_POSITION && (debug_flags & DEBUG_POSITION)) { sersendf_P(PSTR(",c:%lu}\nEndpoint: X:%ld,Y:%ld,Z:%ld,E:%ld,F:%lu,c:%lu}"), movebuffer[mb_tail].c, movebuffer[mb_tail].endpoint.X, movebuffer[mb_tail].endpoint.Y, movebuffer[mb_tail].endpoint.Z, movebuffer[mb_tail].endpoint.E, movebuffer[mb_tail].endpoint.F, #ifdef ACCELERATION_REPRAP movebuffer[mb_tail].end_c #else movebuffer[mb_tail].c #endif ); print_queue(); } #endif /* DEBUG */ // newline is sent from gcode_parse after we return break; case 115: //? --- M115: Get Firmware Version and Capabilities --- //? //? Example: M115 //? //? Request the Firmware Version and Capabilities of the current microcontroller //? The details are returned to the host computer as key:value pairs separated by spaces and terminated with a linefeed. //? //? sample data from firmware: //? FIRMWARE_NAME:Teacup FIRMWARE_URL:http://github.com/triffid/Teacup_Firmware/ PROTOCOL_VERSION:1.0 MACHINE_TYPE:Mendel EXTRUDER_COUNT:1 TEMP_SENSOR_COUNT:1 HEATER_COUNT:1 //? sersendf_P(PSTR("FIRMWARE_NAME:Teacup FIRMWARE_URL:http://github.com/triffid/Teacup_Firmware/ PROTOCOL_VERSION:1.0 MACHINE_TYPE:Mendel EXTRUDER_COUNT:%d TEMP_SENSOR_COUNT:%d HEATER_COUNT:%d"), 1, NUM_TEMP_SENSORS, NUM_HEATERS); // newline is sent from gcode_parse after we return break; case 116: //? --- M116: Wait --- //? //? Example: M116 //? //? Wait for temperatures and other slowly-changing variables to arrive at their set values. enqueue(NULL); break; case 119: //? --- M119: report endstop status --- //? Report the current status of the endstops configured in the //? firmware to the host. power_on(); endstops_on(); delay_ms(10); // allow the signal to stabilize #if defined(X_MIN_PIN) sersendf_P(PSTR("x_min:%d "), x_min()); #endif #if defined(X_MAX_PIN) sersendf_P(PSTR("x_max:%d "), x_max()); #endif #if defined(Y_MIN_PIN) sersendf_P(PSTR("y_min:%d "), y_min()); #endif #if defined(Y_MAX_PIN) sersendf_P(PSTR("y_max:%d "), y_max()); #endif #if defined(Z_MIN_PIN) sersendf_P(PSTR("z_min:%d "), z_min()); #endif #if defined(Z_MAX_PIN) sersendf_P(PSTR("z_max:%d "), z_max()); #endif #if ! (defined(X_MIN_PIN) || defined(X_MAX_PIN) || \ defined(Y_MIN_PIN) || defined(Y_MAX_PIN) || \ defined(Z_MIN_PIN) || defined(Z_MAX_PIN)) sersendf_P(PSTR("no endstops defined")); #endif endstops_off(); break; #ifdef EECONFIG case 130: //? --- M130: heater P factor --- //? Undocumented. #ifdef HEATER_EXTRUDER if ( ! next_target.seen_P) next_target.P = HEATER_EXTRUDER; // else use the first available device #endif if (next_target.seen_S) pid_set_p(next_target.P, next_target.S); break; case 131: //? --- M131: heater I factor --- //? Undocumented. #ifdef HEATER_EXTRUDER if ( ! next_target.seen_P) next_target.P = HEATER_EXTRUDER; #endif if (next_target.seen_S) pid_set_i(next_target.P, next_target.S); break; case 132: //? --- M132: heater D factor --- //? Undocumented. #ifdef HEATER_EXTRUDER if ( ! next_target.seen_P) next_target.P = HEATER_EXTRUDER; #endif if (next_target.seen_S) pid_set_d(next_target.P, next_target.S); break; case 133: //? --- M133: heater I limit --- //? Undocumented. #ifdef HEATER_EXTRUDER if ( ! next_target.seen_P) next_target.P = HEATER_EXTRUDER; #endif if (next_target.seen_S) pid_set_i_limit(next_target.P, next_target.S); break; case 134: //? --- M134: save PID settings to eeprom --- //? Undocumented. heater_save_settings(); break; #endif /* EECONFIG */ #ifdef DEBUG case 136: //? --- M136: PRINT PID settings to host --- //? Undocumented. //? This comand is only available in DEBUG builds. if ( ! next_target.seen_P) next_target.P = HEATER_EXTRUDER; heater_print(next_target.P); break; #endif case 140: //? --- M140: Set heated bed temperature --- //? Undocumented. #ifdef HEATER_BED if ( ! next_target.seen_S) break; temp_set(HEATER_BED, next_target.S); #endif break; #ifdef DEBUG case 240: //? --- M240: echo off --- //? Disable echo. //? This command is only available in DEBUG builds. debug_flags &= ~DEBUG_ECHO; serial_writestr_P(PSTR("Echo off")); // newline is sent from gcode_parse after we return break; case 241: //? --- M241: echo on --- //? Enable echo. //? This command is only available in DEBUG builds. debug_flags |= DEBUG_ECHO; serial_writestr_P(PSTR("Echo on")); // newline is sent from gcode_parse after we return break; #endif /* DEBUG */ // unknown mcode: spit an error default: sersendf_P(PSTR("E: Bad M-code %d"), next_target.M); // newline is sent from gcode_parse after we return } // switch (next_target.M) } // else if (next_target.seen_M) } // process_gcode_command()
void manage_heater() { #ifdef USE_WATCHDOG wd_reset(); #endif float pid_input; float pid_output; if(temp_meas_ready != true) //better readability return; CRITICAL_SECTION_START; temp_meas_ready = false; CRITICAL_SECTION_END; for(int e = 0; e < EXTRUDERS; e++) { #ifdef PIDTEMP pid_input = analog2temp(current_raw[e], e); #ifndef PID_OPENLOOP pid_error[e] = pid_setpoint[e] - pid_input; if(pid_error[e] > 10) { pid_output = PID_MAX; pid_reset[e] = true; } else if(pid_error[e] < -10) { pid_output = 0; pid_reset[e] = true; } else { if(pid_reset[e] == true) { temp_iState[e] = 0.0; pid_reset[e] = false; } pTerm[e] = Kp * pid_error[e]; temp_iState[e] += pid_error[e]; temp_iState[e] = constrain(temp_iState[e], temp_iState_min[e], temp_iState_max[e]); iTerm[e] = Ki * temp_iState[e]; //K1 defined in Configuration.h in the PID settings #define K2 (1.0-K1) dTerm[e] = (Kd * (pid_input - temp_dState[e]))*K2 + (K1 * dTerm[e]); temp_dState[e] = pid_input; pid_output = constrain(pTerm[e] + iTerm[e] - dTerm[e], 0, PID_MAX); } #endif //PID_OPENLOOP #ifdef PID_DEBUG SERIAL_ECHOLN(" PIDDEBUG "<<e<<": Input "<<pid_input<<" Output "<<pid_output" pTerm "<<pTerm[e]<<" iTerm "<<iTerm[e]<<" dTerm "<<dTerm[e]); #endif //PID_DEBUG #else /* PID off */ pid_output = 0; if(current_raw[e] < target_raw[e]) { pid_output = PID_MAX; } #endif // Check if temperature is within the correct range if((current_raw[e] > minttemp[e]) && (current_raw[e] < maxttemp[e])) { //analogWrite(heater_pin_map[e], pid_output); soft_pwm[e] = (int)pid_output >> 1; } else {