/// Character Received - add it to our command /// \param c the next character to process void gcode_parse_char(uint8_t c) { uint8_t checksum_char = c; // uppercase if (c >= 'a' && c <= 'z') c &= ~32; // process previous field if (last_field) { // check if we're seeing a new field or end of line // any character will start a new field, even invalid/unknown ones if ((c >= 'A' && c <= 'Z') || c == '*' || (c == 10) || (c == 13)) { switch (last_field) { case 'G': next_target.G = read_digit.mantissa; if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO)) serwrite_uint8(next_target.G); break; case 'M': next_target.M = read_digit.mantissa; if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO)) serwrite_uint8(next_target.M); break; case 'X': if (next_target.option_inches) next_target.target.X = decfloat_to_int(&read_digit, 25400); else next_target.target.X = decfloat_to_int(&read_digit, 1000); if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO)) serwrite_int32(next_target.target.X); break; case 'Y': if (next_target.option_inches) next_target.target.Y = decfloat_to_int(&read_digit, 25400); else next_target.target.Y = decfloat_to_int(&read_digit, 1000); if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO)) serwrite_int32(next_target.target.Y); break; case 'Z': if (next_target.option_inches) next_target.target.Z = decfloat_to_int(&read_digit, 25400); else next_target.target.Z = decfloat_to_int(&read_digit, 1000); if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO)) serwrite_int32(next_target.target.Z); break; case 'E': if (next_target.option_inches) next_target.target.E = decfloat_to_int(&read_digit, 25400); else next_target.target.E = decfloat_to_int(&read_digit, 1000); if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO)) serwrite_uint32(next_target.target.E); break; case 'F': // just use raw integer, we need move distance and n_steps to convert it to a useful value, so wait until we have those to convert it if (next_target.option_inches) next_target.target.F = decfloat_to_int(&read_digit, 25400); else next_target.target.F = decfloat_to_int(&read_digit, 1); if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO)) serwrite_uint32(next_target.target.F); break; case 'S': // if this is temperature, multiply by 4 to convert to quarter-degree units // cosmetically this should be done in the temperature section, // but it takes less code, less memory and loses no precision if we do it here instead if ((next_target.M == 104) || (next_target.M == 109) || (next_target.M == 140)) next_target.S = decfloat_to_int(&read_digit, 4); // if this is heater PID stuff, multiply by PID_SCALE because we divide by PID_SCALE later on else if ((next_target.M >= 130) && (next_target.M <= 132)) next_target.S = decfloat_to_int(&read_digit, PID_SCALE); else next_target.S = decfloat_to_int(&read_digit, 1); if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO)) serwrite_uint16(next_target.S); break; case 'P': next_target.P = decfloat_to_int(&read_digit, 1); if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO)) serwrite_uint16(next_target.P); break; case 'T': next_target.T = read_digit.mantissa; if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO)) serwrite_uint8(next_target.T); break; case 'N': next_target.N = decfloat_to_int(&read_digit, 1); if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO)) serwrite_uint32(next_target.N); break; case '*': next_target.checksum_read = decfloat_to_int(&read_digit, 1); if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO)) serwrite_uint8(next_target.checksum_read); break; } // reset for next field last_field = 0; read_digit.sign = read_digit.mantissa = read_digit.exponent = 0; } } // skip comments if (next_target.seen_semi_comment == 0 && next_target.seen_parens_comment == 0) { // new field? if ((c >= 'A' && c <= 'Z') || c == '*') { last_field = c; if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO)) serial_writechar(c); } // process character switch (c) { // each currently known command is either G or M, so preserve previous G/M unless a new one has appeared // FIXME: same for T command case 'G': next_target.seen_G = 1; next_target.seen_M = 0; next_target.M = 0; break; case 'M': next_target.seen_M = 1; next_target.seen_G = 0; next_target.G = 0; break; case 'X': next_target.seen_X = 1; break; case 'Y': next_target.seen_Y = 1; break; case 'Z': next_target.seen_Z = 1; break; case 'E': next_target.seen_E = 1; break; case 'F': next_target.seen_F = 1; break; case 'S': next_target.seen_S = 1; break; case 'P': next_target.seen_P = 1; break; case 'T': next_target.seen_T = 1; break; case 'N': next_target.seen_N = 1; break; case '*': next_target.seen_checksum = 1; break; // comments case ';': next_target.seen_semi_comment = 1; break; case '(': next_target.seen_parens_comment = 1; break; // now for some numeracy case '-': read_digit.sign = 1; // force sign to be at start of number, so 1-2 = -2 instead of -12 read_digit.exponent = 0; read_digit.mantissa = 0; break; case '.': if (read_digit.exponent == 0) read_digit.exponent = 1; break; #ifdef DEBUG case ' ': case '\t': case 10: case 13: // ignore break; #endif default: // can't do ranges in switch..case, so process actual digits here. if (c >= '0' && c <= '9') { if (read_digit.exponent < DECFLOAT_EXP_MAX + 1 && ((next_target.option_inches == 0 && read_digit.mantissa < DECFLOAT_MANT_MM_MAX) || (next_target.option_inches && read_digit.mantissa < DECFLOAT_MANT_IN_MAX))) { // this is simply mantissa = (mantissa * 10) + atoi(c) in different clothes read_digit.mantissa = (read_digit.mantissa << 3) + (read_digit.mantissa << 1) + (c - '0'); if (read_digit.exponent) read_digit.exponent++; } } #ifdef DEBUG else { // invalid serial_writechar('?'); serial_writechar(c); serial_writechar('?'); } #endif } } else if ( next_target.seen_parens_comment == 1 && c == ')') next_target.seen_parens_comment = 0; // recognize stuff after a (comment) if (next_target.seen_checksum == 0) next_target.checksum_calculated = crc(next_target.checksum_calculated, checksum_char); // end of line if ((c == 10) || (c == 13)) { if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO)) serial_writechar(c); if ( #ifdef REQUIRE_LINENUMBER ((next_target.N >= next_target.N_expected) && (next_target.seen_N == 1)) || (next_target.seen_M && (next_target.M == 110)) #else 1 #endif ) { if ( #ifdef REQUIRE_CHECKSUM ((next_target.checksum_calculated == next_target.checksum_read) && (next_target.seen_checksum == 1)) #else ((next_target.checksum_calculated == next_target.checksum_read) || (next_target.seen_checksum == 0)) #endif ) { // process serial_writestr_P(PSTR("ok ")); process_gcode_command(); serial_writechar('\n'); // expect next line number if (next_target.seen_N == 1) next_target.N_expected = next_target.N + 1; } else { sersendf_P(PSTR("rs N%ld Expected checksum %d\n"), next_target.N_expected, next_target.checksum_calculated); // request_resend(); } } else { sersendf_P(PSTR("rs N%ld Expected line number %ld\n"), next_target.N_expected, next_target.N_expected); // request_resend(); } // reset variables next_target.seen_X = next_target.seen_Y = next_target.seen_Z = \ next_target.seen_E = next_target.seen_F = next_target.seen_S = \ next_target.seen_P = next_target.seen_T = next_target.seen_N = \ next_target.seen_M = next_target.seen_checksum = next_target.seen_semi_comment = \ next_target.seen_parens_comment = next_target.checksum_read = \ next_target.checksum_calculated = 0; // last_field and read_digit are reset above already // assume a G1 by default next_target.seen_G = 1; next_target.G = 1; if (next_target.option_all_relative) { next_target.target.X = next_target.target.Y = next_target.target.Z = 0; } if (next_target.option_all_relative || next_target.option_e_relative) { next_target.target.E = 0; } } }
void heater_tick(heater_t h, temp_sensor_t t, uint16_t current_temp, uint16_t target_temp) { uint8_t pid_output; #ifndef BANG_BANG int16_t heater_p; int16_t heater_d; int16_t t_error = target_temp - current_temp; #endif /* BANG_BANG */ if (h >= NUM_HEATERS || t >= NUM_TEMP_SENSORS) return; #ifndef BANG_BANG heaters_runtime[h].temp_history[heaters_runtime[h].temp_history_pointer++] = current_temp; heaters_runtime[h].temp_history_pointer &= (TH_COUNT - 1); // PID stuff // proportional heater_p = t_error; // integral heaters_runtime[h].heater_i += t_error; // prevent integrator wind-up if (heaters_runtime[h].heater_i > heaters_pid[h].i_limit) heaters_runtime[h].heater_i = heaters_pid[h].i_limit; else if (heaters_runtime[h].heater_i < -heaters_pid[h].i_limit) heaters_runtime[h].heater_i = -heaters_pid[h].i_limit; // derivative // note: D follows temp rather than error so there's no large derivative when the target changes heater_d = heaters_runtime[h].temp_history[heaters_runtime[h].temp_history_pointer] - current_temp; // combine factors int32_t pid_output_intermed = ( ( (((int32_t) heater_p) * heaters_pid[h].p_factor) + (((int32_t) heaters_runtime[h].heater_i) * heaters_pid[h].i_factor) + (((int32_t) heater_d) * heaters_pid[h].d_factor) ) / PID_SCALE ); // rebase and limit factors if (pid_output_intermed > 255) pid_output = 255; else if (pid_output_intermed < 0) pid_output = 0; else pid_output = pid_output_intermed & 0xFF; #ifdef DEBUG if (debug_flags & DEBUG_PID) sersendf_P(PSTR("T{E:%d, P:%d * %ld = %ld / I:%d * %ld = %ld / D:%d * %ld = %ld # O: %ld = %u}\n"), t_error, heater_p, heaters_pid[h].p_factor, (int32_t) heater_p * heaters_pid[h].p_factor / PID_SCALE, heaters_runtime[h].heater_i, heaters_pid[h].i_factor, (int32_t) heaters_runtime[h].heater_i * heaters_pid[h].i_factor / PID_SCALE, heater_d, heaters_pid[h].d_factor, (int32_t) heater_d * heaters_pid[h].d_factor / PID_SCALE, pid_output_intermed, pid_output); #endif #else if (current_temp >= target_temp) pid_output = BANG_BANG_ON; else pid_output = BANG_BANG_OFF; #endif #ifdef HEATER_SANITY_CHECK // check heater sanity // implementation is a moving window with some slow-down to compensate for thermal mass if (target_temp > (current_temp + TEMP_HYSTERESIS)) { // heating if (current_temp > heaters_runtime[h].sane_temperature) // hotter than sane- good since we're heating unless too hot heaters_runtime[h].sane_temperature = current_temp; else { if (heaters_runtime[h].sanity_counter < 40) heaters_runtime[h].sanity_counter++; else { heaters_runtime[h].sanity_counter = 0; // ratchet up expected temp heaters_runtime[h].sane_temperature++; } } // limit to target, so if we overshoot by too much for too long an error is flagged if (heaters_runtime[h].sane_temperature > target_temp) heaters_runtime[h].sane_temperature = target_temp; } else if (target_temp < (current_temp - TEMP_HYSTERESIS)) { // cooling if (current_temp < heaters_runtime[h].sane_temperature) // cooler than sane- good since we're cooling heaters_runtime[h].sane_temperature = current_temp; else { if (heaters_runtime[h].sanity_counter < 125) heaters_runtime[h].sanity_counter++; else { heaters_runtime[h].sanity_counter = 0; // ratchet down expected temp heaters_runtime[h].sane_temperature--; } } // if we're at or below 60 celsius, don't freak out if we can't drop any more. if (current_temp <= 240) heaters_runtime[h].sane_temperature = current_temp; // limit to target, so if we don't cool down for too long an error is flagged else if (heaters_runtime[h].sane_temperature < target_temp) heaters_runtime[h].sane_temperature = target_temp; } // we're within HYSTERESIS of our target else { heaters_runtime[h].sane_temperature = current_temp; heaters_runtime[h].sanity_counter = 0; } // compare where we're at to where we should be if (labs(current_temp - heaters_runtime[h].sane_temperature) > TEMP_HYSTERESIS) { // no change, or change in wrong direction for a long time- heater is broken! pid_output = 0; sersendf_P(PSTR("!! heater %d or temp sensor %d broken- temp is %d.%dC, target is %d.%dC, didn't reach %d.%dC in %d0 milliseconds\n"), h, t, current_temp >> 2, (current_temp & 3) * 25, target_temp >> 2, (target_temp & 3) * 25, heaters_runtime[h].sane_temperature >> 2, (heaters_runtime[h].sane_temperature & 3) * 25, heaters_runtime[h].sanity_counter); }
/// DEBUG - print queue. /// Qt/hs format, t is tail, h is head, s is F/full, E/empty or neither void print_queue() { sersendf_P(PSTR("Q%d/%d%c"), mb_tail, mb_head, (queue_full()?'F':(queue_empty()?'E':' '))); }
/// called every 10ms from clock.c - check all temp sensors that are ready for checking void temp_sensor_tick() { temp_sensor_t i = 0; for (; i < NUM_TEMP_SENSORS; i++) { if (temp_sensors_runtime[i].next_read_time) { temp_sensors_runtime[i].next_read_time--; } else { uint16_t temp = 0; //time to deal with this temp sensor switch(temp_sensors[i].temp_type) { #ifdef TEMP_MAX6675 case TT_MAX6675: #ifdef PRR PRR &= ~MASK(PRSPI); #elif defined PRR0 PRR0 &= ~MASK(PRSPI); #endif SPCR = MASK(MSTR) | MASK(SPE) | MASK(SPR0); // enable TT_MAX6675 WRITE(SS, 0); // No delay required, see // https://github.com/triffid/Teacup_Firmware/issues/22 // read MSB SPDR = 0; for (;(SPSR & MASK(SPIF)) == 0;); temp = SPDR; temp <<= 8; // read LSB SPDR = 0; for (;(SPSR & MASK(SPIF)) == 0;); temp |= SPDR; // disable TT_MAX6675 WRITE(SS, 1); temp_sensors_runtime[i].temp_flags = 0; if ((temp & 0x8002) == 0) { // got "device id" temp_sensors_runtime[i].temp_flags |= PRESENT; if (temp & 4) { // thermocouple open temp_sensors_runtime[i].temp_flags |= TCOPEN; } else { temp = temp >> 3; } } // this number depends on how frequently temp_sensor_tick is called. the MAX6675 can give a reading every 0.22s, so set this to about 250ms temp_sensors_runtime[i].next_read_time = 25; break; #endif /* TEMP_MAX6675 */ #ifdef TEMP_THERMISTOR case TT_THERMISTOR: do { uint8_t j, table_num; //Read current temperature temp = analog_read(i); // for thermistors the thermistor table number is in the additional field table_num = temp_sensors[i].additional; //Calculate real temperature based on lookup table for (j = 1; j < NUMTEMPS; j++) { if (pgm_read_word(&(temptable[table_num][j][0])) > temp) { // Thermistor table is already in 14.2 fixed point #ifndef EXTRUDER if (DEBUG_PID && (debug_flags & DEBUG_PID)) sersendf_P(PSTR("pin:%d Raw ADC:%d table entry: %d"),temp_sensors[i].temp_pin,temp,j); #endif // Linear interpolating temperature value // y = ((x - x₀)y₁ + (x₁-x)y₀ ) / (x₁ - x₀) // y = temp // x = ADC reading // x₀= temptable[j-1][0] // x₁= temptable[j][0] // y₀= temptable[j-1][1] // y₁= temptable[j][1] // y = // Wikipedia's example linear interpolation formula. temp = ( // ((x - x₀)y₁ ((uint32_t)temp - pgm_read_word(&(temptable[table_num][j-1][0]))) * pgm_read_word(&(temptable[table_num][j][1])) // + + // (x₁-x) (pgm_read_word(&(temptable[table_num][j][0])) - (uint32_t)temp) // y₀ ) * pgm_read_word(&(temptable[table_num][j-1][1]))) // / / // (x₁ - x₀) (pgm_read_word(&(temptable[table_num][j][0])) - pgm_read_word(&(temptable[table_num][j-1][0]))); #ifndef EXTRUDER if (DEBUG_PID && (debug_flags & DEBUG_PID)) sersendf_P(PSTR(" temp:%d.%d"),temp/4,(temp%4)*25); #endif break; } } #ifndef EXTRUDER if (DEBUG_PID && (debug_flags & DEBUG_PID)) sersendf_P(PSTR(" Sensor:%d\n"),i); #endif //Clamp for overflows if (j == NUMTEMPS) temp = temptable[table_num][NUMTEMPS-1][1]; temp_sensors_runtime[i].next_read_time = 0; } while (0); break; #endif /* TEMP_THERMISTOR */ #ifdef TEMP_AD595 case TT_AD595: temp = analog_read(i); // convert // >>8 instead of >>10 because internal temp is stored as 14.2 fixed point temp = (temp * 500L) >> 8; temp_sensors_runtime[i].next_read_time = 0; break; #endif /* TEMP_AD595 */ #ifdef TEMP_PT100 case TT_PT100: #warning TODO: PT100 code break #endif /* TEMP_PT100 */ #ifdef TEMP_INTERCOM case TT_INTERCOM: temp = read_temperature(temp_sensors[i].temp_pin); temp_sensors_runtime[i].next_read_time = 25; break; #endif /* TEMP_INTERCOM */ #ifdef TEMP_DUMMY case TT_DUMMY: temp = temp_sensors_runtime[i].last_read_temp; if (temp_sensors_runtime[i].target_temp > temp) temp++; else if (temp_sensors_runtime[i].target_temp < temp) temp--; temp_sensors_runtime[i].next_read_time = 0; break; #endif /* TEMP_DUMMY */ default: /* prevent compiler warning */ break; } /* Exponentially Weighted Moving Average alpha constant for smoothing noisy sensors. Instrument Engineer's Handbook, 4th ed, Vol 2 p126 says values of 0.05 to 0.1 for TEMP_EWMA are typical. */ #ifndef TEMP_EWMA #define TEMP_EWMA 1.0 #endif #define EWMA_SCALE 1024L #define EWMA_ALPHA ((long) (TEMP_EWMA * EWMA_SCALE)) temp_sensors_runtime[i].last_read_temp = (uint16_t) ((EWMA_ALPHA * temp + (EWMA_SCALE-EWMA_ALPHA) * temp_sensors_runtime[i].last_read_temp ) / EWMA_SCALE); } if (labs((int16_t)(temp_sensors_runtime[i].last_read_temp - temp_sensors_runtime[i].target_temp)) < (TEMP_HYSTERESIS*4)) { if (temp_sensors_runtime[i].temp_residency < (TEMP_RESIDENCY_TIME*120)) temp_sensors_runtime[i].temp_residency++; } else { // Deal with flakey sensors which occasionally report a wrong value // by setting residency back, but not entirely to zero. if (temp_sensors_runtime[i].temp_residency > 10) temp_sensors_runtime[i].temp_residency -= 10; else temp_sensors_runtime[i].temp_residency = 0; } if (temp_sensors[i].heater < NUM_HEATERS) { heater_tick(temp_sensors[i].heater, temp_sensors[i].temp_type, temp_sensors_runtime[i].last_read_temp, temp_sensors_runtime[i].target_temp); } if (DEBUG_PID && (debug_flags & DEBUG_PID)) sersendf_P(PSTR("DU temp: {%d %d %d.%d}"), i, temp_sensors_runtime[i].last_read_temp, temp_sensors_runtime[i].last_read_temp / 4, (temp_sensors_runtime[i].last_read_temp & 0x03) * 25); }
/** * Join 2 moves by removing the full stop between them, where possible. * To join the moves, the expected jerk - or force - of the change in direction is calculated. * The jerk is used to scale the common feed rate between both moves to obtain an acceptable speed * to transition between 'prev' and 'current'. * * Premise: we currently join the last move in the queue and the one before it (if any). * This means the feed rate at the end of the 'current' move is 0. * * Premise: the 'current' move is not dispatched in the queue: it should remain constant while this * function is running. * * Note: the planner always makes sure the movement can be stopped within the * last move (= 'current'); as a result a lot of small moves will still limit the speed. */ void dda_join_moves(DDA *prev, DDA *current) { // Calculating the look-ahead settings can take a while; before modifying // the previous move, we need to locally store any values and write them // when we are done (and the previous move is not already active). uint32_t prev_F, prev_F_start, prev_F_end, prev_end; uint32_t prev_rampup, prev_rampdown, prev_total_steps; uint8_t prev_id; // Similarly, we only want to modify the current move if we have the results of the calculations; // until then, we do not want to touch the current move settings. // Note: we assume 'current' will not be dispatched while this function runs, so we do not to // back up the move settings: they will remain constant. uint32_t this_F_start, this_start, this_rampup, this_rampdown; int32_t jerk, jerk_e; // Expresses the forces if we would change directions at full speed static uint32_t la_cnt = 0; // Counter: how many moves did we join? #ifdef LOOKAHEAD_DEBUG static uint32_t moveno = 0; // Debug counter to number the moves - helps while debugging moveno++; #endif // Bail out if there's nothing to join (e.g. G1 F1500). if ( ! prev || prev->nullmove) return; serprintf(PSTR("Current Delta: %ld,%ld,%ld E:%ld Live:%d\r\n"), current->delta_um.X, current->delta_um.Y, current->delta_um.Z, current->delta_um.E, current->live); serprintf(PSTR("Prev Delta: %ld,%ld,%ld E:%ld Live:%d\r\n"), prev->delta_um.X, prev->delta_um.Y, prev->delta_um.Z, prev->delta_um.E, prev->live); // Look-ahead: attempt to join moves into smooth movements // Note: moves are only modified after the calculations are complete. // Only prepare for look-ahead if we have 2 available moves to // join and the Z axis is unused (for now, Z axis moves are NOT joined). if (prev->live == 0 && prev->delta_um.Z == current->delta_um.Z) { // Calculate the jerk if the previous move and this move would be joined // together at full speed. jerk = dda_jerk_size_2d(prev->delta_um.X, prev->delta_um.Y, prev->endpoint.F, current->delta_um.X, current->delta_um.Y, current->endpoint.F); serprintf(PSTR("Jerk: %lu\r\n"), jerk); jerk_e = dda_jerk_size_1d(prev->delta_um.E, prev->endpoint.F, current->delta_um.E, current->endpoint.F); serprintf(PSTR("Jerk_e: %lu\r\n"), jerk_e); } else { // Move already executing or Z moved: abort the join return; } // Make sure we have 2 moves and the previous move is not already active if (prev->live == 0) { // Perform an atomic copy to preserve volatile parameters during the calculations ATOMIC_START prev_id = prev->id; prev_F = prev->endpoint.F; prev_F_start = prev->F_start; prev_F_end = prev->F_end; prev_rampup = prev->rampup_steps; prev_rampdown = prev->rampdown_steps; prev_total_steps = prev->total_steps; ATOMIC_END // The initial crossing speed is the minimum between both target speeds // Note: this is a given: the start speed and end speed can NEVER be // higher than the target speed in a move! // Note 2: this provides an upper limit, if needed, the speed is lowered. uint32_t crossF = prev_F; if(crossF > current->endpoint.F) crossF = current->endpoint.F; //sersendf_P(PSTR("j:%lu - XF:%lu"), jerk, crossF); // If the XY jerk is too big, scale the proposed cross speed if(jerk > LOOKAHEAD_MAX_JERK_XY) { serprintf(PSTR("Jerk too big: scale cross speed between moves\r\n")); // Get the highest speed between both moves if(crossF < prev_F) crossF = prev_F; // Perform an exponential scaling uint32_t ujerk = (uint32_t)jerk; // Use unsigned to double the range before overflowing crossF = (crossF*LOOKAHEAD_MAX_JERK_XY*LOOKAHEAD_MAX_JERK_XY)/(ujerk*ujerk); // Optimize: if the crossing speed is zero, there is no join possible between these // two (fast) moves. Stop calculating and leave the full stop that is currently between // them. if(crossF == 0) return; // Safety: make sure we never exceed the maximum speed of a move if(crossF > current->endpoint.F) crossF = current->endpoint.F; if(crossF > prev_F) crossF = prev_F; sersendf_P(PSTR("=>F:%lu"), crossF); } // Same to the extruder jerk: make sure we do not yank it if(jerk_e > LOOKAHEAD_MAX_JERK_E) { sersendf_P(PSTR("Jerk_e too big: scale cross speed between moves\r\n")); uint32_t crossF2 = MAX(current->endpoint.F, prev_F); // Perform an exponential scaling uint32_t ujerk = (uint32_t)jerk_e; // Use unsigned to double the range before overflowing crossF2 = (crossF2*LOOKAHEAD_MAX_JERK_E*LOOKAHEAD_MAX_JERK_E)/(ujerk*ujerk); // Only continue with joining if there is a feasible crossing speed if(crossF2 == 0) return; // Safety: make sure the proposed speed is not higher than the target speeds of each move crossF2 = MIN(crossF2, current->endpoint.F); crossF2 = MIN(crossF2, prev_F); if(crossF2 > crossF) { sersendf_P(PSTR("Jerk_e: %lu => crossF: %lu (original: %lu)\r\n"), jerk_e, crossF2, crossF); } // Pick the crossing speed for these 2 move to be within the jerk limits crossF = MIN(crossF, crossF2); } // Show the proposed crossing speed - this might get adjusted below serprintf(PSTR("Initial crossing speed: %lu\r\n"), crossF); // Forward check: test if we can actually reach the target speed in the previous move // If not: we need to determine the obtainable speed and adjust crossF accordingly. // Note: these ramps can be longer than the move: if so we can not reach top speed. uint32_t up = ACCELERATE_RAMP_LEN(prev_F) - ACCELERATE_RAMP_LEN(prev_F_start); uint32_t down = ACCELERATE_RAMP_LEN(prev_F) - ACCELERATE_RAMP_LEN(crossF); // Test if both the ramp up and ramp down fit within the move if(up+down > prev_total_steps) { // Test if we can reach the crossF rate: if the difference between both ramps is larger // than the move itself, there is no ramp up or down from F_start to crossF... uint32_t diff = (up>down) ? up-down : down-up; if(diff > prev_total_steps) { // Cannot reach crossF from F_start, lower crossF and adjust both ramp-up and down down = 0; // Before we can determine how fast we can go in this move, we need the number of // steps needed to reach the entry speed. uint32_t prestep = ACCELERATE_RAMP_LEN(prev_F_start); // Calculate what feed rate we can reach during this move crossF = dda_steps_to_velocity(prestep+prev_total_steps); // Make sure we do not exceed the target speeds if(crossF > prev_F) crossF = prev_F; if(crossF > current->endpoint.F) crossF = current->endpoint.F; // The problem with the 'dda_steps_to_velocity' is that it will produce a // rounded result. Use it to obtain an exact amount of steps needed to reach // that speed and set that as the ramp up; we might stop accelerating for a // couple of steps but that is better than introducing errors in the moves. up = ACCELERATE_RAMP_LEN(crossF) - prestep; #ifdef LOOKAHEAD_DEBUG // Sanity check: the ramp up should never exceed the move length if(up > prev_total_steps) { sersendf_P(PSTR("FATAL ERROR during prev ramp scale, ramp is too long: up:%lu ; len:%lu ; target speed: %lu\r\n"), up, prev_total_steps, crossF); sersendf_P(PSTR("F_start:%lu ; F:%lu ; crossF:%lu\r\n"), prev_F_start, prev_F, crossF); dda_emergency_shutdown(PSTR("LA prev ramp scale, ramp is too long")); } #endif // Show the result on the speed on the clipping of the ramp serprintf(PSTR("Prev speed & crossing speed truncated to: %lu\r\n"), crossF); } else { // Can reach crossF; determine the apex between ramp up and ramp down // In other words: calculate how long we can accelerate before decelerating to exit at crossF // Note: while the number of steps is exponentially proportional to the velocity, // the acceleration is linear: we can simply remove the same number of steps of both ramps. uint32_t diff = (up + down - prev_total_steps) / 2; up -= diff; down -= diff; } #ifdef LOOKAHEAD_DEBUG // Sanity check: make sure the speed limits are maintained if(prev_F_start > prev_F || crossF > prev_F) { serprintf(PSTR("Prev target speed exceeded!: prev_F_start:%lu ; prev_F:%lu ; prev_F_end:%lu\r\n"), prev_F_start, prev_F, crossF); dda_emergency_shutdown(PSTR("Prev target speed exceeded")); } #endif } // Save the results prev_rampup = up; prev_rampdown = prev_total_steps - down; prev_F_end = crossF; prev_end = ACCELERATE_RAMP_LEN(prev_F_end); #ifdef LOOKAHEAD_DEBUG // Sanity check: make sure the speed limits are maintained if(crossF > current->endpoint.F) { serprintf(PSTR("This target speed exceeded!: F_start:%lu ; F:%lu ; prev_F_end:%lu\r\n"), crossF, current->endpoint.F); dda_emergency_shutdown(PSTR("This target speed exceeded")); } #endif // Forward check 2: test if we can actually reach the target speed in this move. // If not: determine obtainable speed and adjust crossF accordingly. If that // happens, a third (reverse) pass is needed to lower the speeds in the previous move... //ramp_scaler = ACCELERATE_SCALER(current->lead); // Use scaler for current leading axis up = ACCELERATE_RAMP_LEN(current->endpoint.F) - ACCELERATE_RAMP_LEN(crossF); down = ACCELERATE_RAMP_LEN(current->endpoint.F); // Test if both the ramp up and ramp down fit within the move if(up+down > current->total_steps) { // Test if we can reach the crossF rate // Note: this is the inverse of the previous move: we need to exit at 0 speed as // this is the last move in the queue. Implies that down >= up if(down-up > current->total_steps) { serprintf(PSTR("This move can not reach crossF - lower it\r\n")); // Cannot reach crossF, lower it and adjust ramps // Note: after this, the previous move needs to be modified to match crossF. up = 0; // Calculate what crossing rate we can reach: total/down * F crossF = dda_steps_to_velocity(current->total_steps); // Speed limit: never exceed the target rate if(crossF > current->endpoint.F) crossF = current->endpoint.F; // crossF will be conservative: calculate the actual ramp down length down = ACCELERATE_RAMP_LEN(crossF); #ifdef LOOKAHEAD_DEBUG // Make sure we can break to a full stop before the move ends if(down > current->total_steps) { sersendf_P(PSTR("FATAL ERROR during ramp scale, ramp is too long: down:%lu ; len:%lu ; target speed: %lu\r\n"), down, current->total_steps, crossF); dda_emergency_shutdown(PSTR("LA current ramp scale, ramp is too long")); } #endif } else { serprintf(PSTR("This: crossF is usable but we will not reach Fmax\r\n")); // Can reach crossF; determine the apex between ramp up and ramp down // In other words: calculate how long we can accelerate before decelerating to start at crossF // and end at F = 0 uint32_t diff = (down + up - current->total_steps) / 2; up -= diff; down -= diff; serprintf(PSTR("Apex: %lu - new up: %lu - new down: %lu\r\n"), diff, up, down); // sanity stuff: calculate the speeds for these ramps serprintf(PSTR("Ramp up speed: %lu mm/s\r\n"), dda_steps_to_velocity(up+prev->rampup_steps)); serprintf(PSTR("Ramp down speed: %lu mm/s\r\n"), dda_steps_to_velocity(down)); } } // Save the results this_rampup = up; this_rampdown = current->total_steps - down; this_F_start = crossF; this_start = ACCELERATE_RAMP_LEN(this_F_start); serprintf(PSTR("Actual crossing speed: %lu\r\n"), crossF); // Potential reverse processing: // Make sure the crossing speed is the same, if its not, we need to slow the previous move to // the current crossing speed (note: the crossing speed could only be lowered). // This can happen when this move is a short move and the previous move was a larger or faster move: // since we need to be able to stop if this is the last move, we lowered the crossing speed // between this move and the previous move... if(prev_F_end != crossF) { // Third reverse pass: slow the previous move to end at the target crossing speed. //ramp_scaler = ACCELERATE_SCALER(current->lead); //todo: prev_lead // Use scaler for previous leading axis (again) // Note: use signed values so we can check if results go below zero // Note 2: when up2 and/or down2 are below zero from the start, you found a bug in the logic above. int32_t up2 = ACCELERATE_RAMP_LEN(prev_F) - ACCELERATE_RAMP_LEN(prev_F_start); int32_t down2 = ACCELERATE_RAMP_LEN(prev_F) - ACCELERATE_RAMP_LEN(crossF); // Test if both the ramp up and ramp down fit within the move if(up2+down2 > prev_total_steps) { int32_t diff = (up2 + down2 - (int32_t)prev_total_steps) / 2; up2 -= diff; down2 -= diff; #ifdef LOOKAHEAD_DEBUG if(up2 < 0 || down2 < 0) { // Cannot reach crossF from prev_F_start - this should not happen! sersendf_P(PSTR("FATAL ERROR during reverse pass ramp scale, ramps are too long: up:%ld ; down:%ld; len:%lu ; F_start: %lu ; crossF: %lu\r\n"), up2, down2, prev_total_steps, prev_F_start, crossF); sersendf_P(PSTR("Original up: %ld - down %ld (diff=%ld)\r\n"),up2+diff,down2+diff,diff); dda_emergency_shutdown(PSTR("reverse pass ramp scale, can not reach F_end from F_start")); } #endif } // Assign the results prev_rampup = up2; prev_rampdown = prev_total_steps - down2; prev_F_end = crossF; prev_end = ACCELERATE_RAMP_LEN(prev_F_end); } #ifdef LOOKAHEAD_DEBUG if(crossF > current->endpoint.F || crossF > prev_F) dda_emergency_shutdown(PSTR("Lookahead exceeded speed limits in crossing!")); // When debugging, print the 2 moves we joined // Legenda: Fs=F_start, len=# of steps, up/down=# steps in ramping, Fe=F_end serprintf(PSTR("LA: (%lu) Fs=%lu, len=%lu, up=%lu, down=%lu, Fe=%lu <=> (%lu) Fs=%lu, len=%lu, up=%lu, down=%lu, Fe=0\r\n\r\n"), moveno-1, prev->F_start, prev->total_steps, prev->rampup_steps, prev->total_steps-prev->rampdown_steps, prev->F_end, moveno, current->F_start, current->total_steps, current->rampup_steps, current->total_steps - this_rampdown); #endif uint8_t timeout = 0; ATOMIC_START // Evaluation: determine how we did... lookahead_joined++; // Determine if we are fast enough - if not, just leave the moves // Note: to test if the previous move was already executed and replaced by a new // move, we compare the DDA id. if(prev->live == 0 && prev->id == prev_id) { prev->F_end = prev_F_end; prev->end_steps = prev_end; prev->rampup_steps = prev_rampup; prev->rampdown_steps = prev_rampdown; current->rampup_steps = this_rampup; current->rampdown_steps = this_rampdown; current->F_end = 0; current->end_steps = 0; current->F_start = this_F_start; current->start_steps = this_start; la_cnt++; } else timeout = 1; ATOMIC_END // If we were not fast enough, any feedback will happen outside the atomic block: if(timeout) { sersendf_P(PSTR("Error: look ahead not fast enough\r\n")); lookahead_timeout++; } }
/// Character Received - add it to our command /// \param c the next character to process void gcode_parse_char(uint8_t c) { // uppercase if (c >= 'a' && c <= 'z') c &= ~32; // process previous field if (last_field) { // check if we're seeing a new field or end of line // any character will start a new field, even invalid/unknown ones if ((c >= 'A' && c <= 'Z') || c == '*' || (c == 10) || (c == 13)) { switch (last_field) { case 'G': next_target.G = parse_number.AsInt( false ); //if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO)) // serwrite_uint8(next_target.G); break; case 'M': next_target.M = parse_number.AsInt( false ); //if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO)) // serwrite_uint8(next_target.M); break; case 'X': next_target.target.X = parse_number.AsPosition( next_target.option_inches ); //if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO)) // serwrite_int32(next_target.target.X); break; case 'Y': next_target.target.Y = parse_number.AsPosition( next_target.option_inches ); //if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO)) // serwrite_int32(next_target.target.Y); break; case 'Z': next_target.target.Z = parse_number.AsPosition( next_target.option_inches ); //if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO)) // serwrite_int32(next_target.target.Z); break; case 'E': next_target.target.E = parse_number.AsPosition( next_target.option_inches ); //if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO)) // serwrite_uint32(next_target.target.E); break; case 'F': // just use raw integer, we need move distance and n_steps to convert it to a useful value, so wait until we have those to convert it next_target.target.F = parse_number.AsInt( next_target.option_inches ); //if (next_target.option_inches) // next_target.target.F = decfloat_to_int(&read_digit, 25400, 1); //else // next_target.target.F = decfloat_to_int(&read_digit, 1, 0); //if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO)) // serwrite_uint32(next_target.target.F); break; case 'S': // if this is temperature, multiply by 4 to convert to quarter-degree units // cosmetically this should be done in the temperature section, // but it takes less code, less memory and loses no precision if we do it here instead if ((next_target.M == 104) || (next_target.M == 109) || (next_target.M == 140)) next_target.S = parse_number.AsScaledInt( 4 ); //decfloat_to_int(&read_digit, 4, 0); // if this is heater PID stuff, multiply by PID_SCALE because we divide by PID_SCALE later on else if ((next_target.M >= 130) && (next_target.M <= 132)) next_target.S = parse_number.AsScaledInt( PID_SCALE );// decfloat_to_int(&read_digit, PID_SCALE, 0); else next_target.S = parse_number.AsInt( false ); //decfloat_to_int(&read_digit, 1, 0); //if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO)) // serwrite_uint16(next_target.S); break; case 'P': next_target.P = parse_number.AsInt( false ); //decfloat_to_int(&read_digit, 1, 0); //if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO)) // serwrite_uint16(next_target.P); break; case 'T': next_target.T = parse_number.AsInt( false ); //read_digit.mantissa; //if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO)) // serwrite_uint8(next_target.T); break; case 'N': next_target.N = parse_number.AsInt( false ); //decfloat_to_int(&read_digit, 1, 0); //if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO)) // serwrite_uint32(next_target.N); break; case '*': next_target.checksum_read = parse_number.AsInt( false ); //decfloat_to_int(&read_digit, 1, 0); //if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO)) // serwrite_uint8(next_target.checksum_read); break; } // reset for next field last_field = 0; parse_number.Clear(); } } // skip comments if (next_target.seen_semi_comment == 0 && next_target.seen_parens_comment == 0) { // new field? if ((c >= 'A' && c <= 'Z') || c == '*') { last_field = c; //if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO)) // serial_writechar(c); } // process character switch (c) { // each currently known command is either G or M, so preserve previous G/M unless a new one has appeared // FIXME: same for T command case 'G': next_target.seen_G = 1; next_target.seen_M = 0; next_target.M = 0; break; case 'M': next_target.seen_M = 1; next_target.seen_G = 0; next_target.G = 0; break; case 'X': next_target.seen_X = 1; break; case 'Y': next_target.seen_Y = 1; break; case 'Z': next_target.seen_Z = 1; break; case 'E': next_target.seen_E = 1; break; case 'F': next_target.seen_F = 1; break; case 'S': next_target.seen_S = 1; break; case 'P': next_target.seen_P = 1; break; case 'T': next_target.seen_T = 1; break; case 'N': next_target.seen_N = 1; break; case '*': next_target.seen_checksum = 1; break; // comments case ';': next_target.seen_semi_comment = 1; break; case '(': next_target.seen_parens_comment = 1; break; // now for some numeracy case '-': parse_number.AddChar(c); break; case '.': parse_number.AddChar(c); break; #ifdef DEBUG case ' ': case '\t': case 10: case 13: // ignore break; #endif default: parse_number.AddChar(c); } } else if ( next_target.seen_parens_comment == 1 && c == ')') next_target.seen_parens_comment = 0; // recognize stuff after a (comment) if (next_target.seen_checksum == 0) next_target.checksum_calculated = crc(next_target.checksum_calculated, c); // end of line if ((c == 10) || (c == 13)) { //if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO)) // serial_writechar(c); if ( #ifdef REQUIRE_LINENUMBER ((next_target.N >= next_target.N_expected) && (next_target.seen_N == 1)) || (next_target.seen_M && (next_target.M == 110)) #else 1 #endif ) { if ( #ifdef REQUIRE_CHECKSUM ((next_target.checksum_calculated == next_target.checksum_read) && (next_target.seen_checksum == 1)) #else ((next_target.checksum_calculated == next_target.checksum_read) || (next_target.seen_checksum == 0)) #endif ) { // process // teacup waits until after the processing of the gcode to send the eol... // FIXME - what to do here... serial_writestr_P(PSTR("ok ")); process_gcode_command(); serial_writechar('\n'); // expect next line number if (next_target.seen_N == 1) next_target.N_expected = next_target.N + 1; } else { sersendf_P(PSTR("rs N%ld Expected checksum %d\n"), next_target.N_expected, next_target.checksum_calculated); // request_resend(); } } else { sersendf_P(PSTR("rs N%ld Expected line number %ld\n"), next_target.N_expected, next_target.N_expected); // request_resend(); } // reset variables next_target.seen_X = next_target.seen_Y = next_target.seen_Z = \ next_target.seen_E = next_target.seen_F = next_target.seen_S = \ next_target.seen_P = next_target.seen_T = next_target.seen_N = \ next_target.seen_M = next_target.seen_checksum = next_target.seen_semi_comment = \ next_target.seen_parens_comment = next_target.checksum_read = \ next_target.checksum_calculated = 0; // last_field and read_digit are reset above already // assume a G1 by default next_target.seen_G = 1; next_target.G = 1; if (next_target.option_relative) { next_target.target.X = next_target.target.Y = next_target.target.Z = 0; #ifdef E_ABSOLUTE next_target.target.E = 0; #endif } #ifndef E_ABSOLUTE // E always relative next_target.target.E = 0; #endif } }
/** Character received - add it to our command. \param c The next character to process. \return Whether end of line was reached. This parser operates character by character, so there's no need for a buffer holding the entire line of G-code. */ uint8_t gcode_parse_char(uint8_t c) { uint8_t checksum_char = c; // uppercase if (c >= 'a' && c <= 'z') c &= ~32; #ifdef SIMULATOR sim_gcode_ch(c); #endif // An asterisk is a quasi-EOL and always ends all fields. if (c == '*') { next_target.seen_semi_comment = next_target.seen_parens_comment = next_target.read_string = 0; } // Skip comments and strings. if (next_target.seen_semi_comment == 0 && next_target.seen_parens_comment == 0 && next_target.read_string == 0 ) { // Check if the field has ended. Either by a new field, space or EOL. if (last_field && (c < '0' || c > '9') && c != '.') { switch (last_field) { case 'G': next_target.G = read_digit.mantissa; if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO)) serwrite_uint8(next_target.G); break; case 'M': next_target.M = read_digit.mantissa; #ifdef SD if (next_target.M == 23) { // SD card command with a filename. next_target.read_string = 1; // Reset by string handler or EOL. str_buf_ptr = 0; last_field = 0; } #endif if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO)) serwrite_uint8(next_target.M); break; case 'X': if (next_target.option_inches) next_target.target.axis[X] = decfloat_to_int(&read_digit, 25400); else next_target.target.axis[X] = decfloat_to_int(&read_digit, 1000); if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO)) serwrite_int32(next_target.target.axis[X]); break; case 'Y': if (next_target.option_inches) next_target.target.axis[Y] = decfloat_to_int(&read_digit, 25400); else next_target.target.axis[Y] = decfloat_to_int(&read_digit, 1000); if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO)) serwrite_int32(next_target.target.axis[Y]); break; case 'Z': if (next_target.option_inches) next_target.target.axis[Z] = decfloat_to_int(&read_digit, 25400); else next_target.target.axis[Z] = decfloat_to_int(&read_digit, 1000); if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO)) serwrite_int32(next_target.target.axis[Z]); break; case 'E': if (next_target.option_inches) next_target.target.axis[E] = decfloat_to_int(&read_digit, 25400); else next_target.target.axis[E] = decfloat_to_int(&read_digit, 1000); if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO)) serwrite_int32(next_target.target.axis[E]); break; case 'F': // just use raw integer, we need move distance and n_steps to convert it to a useful value, so wait until we have those to convert it if (next_target.option_inches) next_target.target.F = decfloat_to_int(&read_digit, 25400); else next_target.target.F = decfloat_to_int(&read_digit, 1); if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO)) serwrite_uint32(next_target.target.F); break; case 'S': // if this is temperature, multiply by 4 to convert to quarter-degree units // cosmetically this should be done in the temperature section, // but it takes less code, less memory and loses no precision if we do it here instead if ((next_target.M == 104) || (next_target.M == 109) || (next_target.M == 140)) next_target.S = decfloat_to_int(&read_digit, 4); // if this is heater PID stuff, multiply by PID_SCALE because we divide by PID_SCALE later on else if ((next_target.M >= 130) && (next_target.M <= 132)) next_target.S = decfloat_to_int(&read_digit, PID_SCALE); else next_target.S = decfloat_to_int(&read_digit, 1); if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO)) serwrite_int32(next_target.S); break; case 'P': next_target.P = decfloat_to_int(&read_digit, 1); if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO)) serwrite_uint16(next_target.P); break; case 'T': next_target.T = read_digit.mantissa; if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO)) serwrite_uint8(next_target.T); break; case 'N': next_target.N = decfloat_to_int(&read_digit, 1); if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO)) serwrite_uint32(next_target.N); break; case '*': next_target.checksum_read = decfloat_to_int(&read_digit, 1); if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO)) serwrite_uint8(next_target.checksum_read); break; } } // new field? if ((c >= 'A' && c <= 'Z') || c == '*') { last_field = c; read_digit.sign = read_digit.mantissa = read_digit.exponent = 0; if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO)) serial_writechar(c); } // process character // Can't do ranges in switch..case, so process actual digits here. // Do it early, as there are many more digits than characters expected. if (c >= '0' && c <= '9') { if (read_digit.exponent < DECFLOAT_EXP_MAX + 1 && ((next_target.option_inches == 0 && read_digit.mantissa < DECFLOAT_MANT_MM_MAX) || (next_target.option_inches && read_digit.mantissa < DECFLOAT_MANT_IN_MAX))) { // this is simply mantissa = (mantissa * 10) + atoi(c) in different clothes read_digit.mantissa = (read_digit.mantissa << 3) + (read_digit.mantissa << 1) + (c - '0'); if (read_digit.exponent) read_digit.exponent++; } } else { switch (c) { // Each currently known command is either G or M, so preserve // previous G/M unless a new one has appeared. // FIXME: same for T command case 'G': next_target.seen_G = 1; next_target.seen_M = 0; next_target.M = 0; break; case 'M': next_target.seen_M = 1; next_target.seen_G = 0; next_target.G = 0; break; case 'X': next_target.seen_X = 1; break; case 'Y': next_target.seen_Y = 1; break; case 'Z': next_target.seen_Z = 1; break; case 'E': next_target.seen_E = 1; break; case 'F': next_target.seen_F = 1; break; case 'S': next_target.seen_S = 1; break; case 'P': next_target.seen_P = 1; break; case 'T': next_target.seen_T = 1; break; case 'N': next_target.seen_N = 1; break; case '*': next_target.seen_checksum = 1; break; // comments case ';': next_target.seen_semi_comment = 1; // Reset by EOL. break; case '(': next_target.seen_parens_comment = 1; // Reset by ')' or EOL. break; // now for some numeracy case '-': read_digit.sign = 1; // force sign to be at start of number, so 1-2 = -2 instead of -12 read_digit.exponent = 0; read_digit.mantissa = 0; break; case '.': if (read_digit.exponent == 0) read_digit.exponent = 1; break; #ifdef DEBUG case ' ': case '\t': case 10: case 13: // ignore break; #endif default: #ifdef DEBUG // invalid serial_writechar('?'); serial_writechar(c); serial_writechar('?'); #endif break; } } } else if ( next_target.seen_parens_comment == 1 && c == ')') next_target.seen_parens_comment = 0; // recognize stuff after a (comment) if (next_target.seen_checksum == 0) next_target.checksum_calculated = crc(next_target.checksum_calculated, checksum_char); // end of line if ((c == 10) || (c == 13)) { if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO)) serial_writechar(c); // Assume G1 for unspecified movements. if ( ! next_target.seen_G && ! next_target.seen_M && ! next_target.seen_T && (next_target.seen_X || next_target.seen_Y || next_target.seen_Z || next_target.seen_E || next_target.seen_F)) { next_target.seen_G = 1; next_target.G = 1; } if ( #ifdef REQUIRE_LINENUMBER ((next_target.N >= next_target.N_expected) && (next_target.seen_N == 1)) || (next_target.seen_M && (next_target.M == 110)) #else 1 #endif ) { if ( #ifdef REQUIRE_CHECKSUM ((next_target.checksum_calculated == next_target.checksum_read) && (next_target.seen_checksum == 1)) #else ((next_target.checksum_calculated == next_target.checksum_read) || (next_target.seen_checksum == 0)) #endif ) { // process process_gcode_command(); // Acknowledgement ("ok") is sent in the main loop, in mendel.c. // expect next line number if (next_target.seen_N == 1) next_target.N_expected = next_target.N + 1; } else { sersendf_P(PSTR("rs N%ld Expected checksum %d\n"), next_target.N_expected, next_target.checksum_calculated); // request_resend(); } } else { sersendf_P(PSTR("rs N%ld Expected line number %ld\n"), next_target.N_expected, next_target.N_expected); // request_resend(); } // reset variables next_target.seen_X = next_target.seen_Y = next_target.seen_Z = \ next_target.seen_E = next_target.seen_F = next_target.seen_S = \ next_target.seen_P = next_target.seen_T = next_target.seen_N = \ next_target.seen_G = next_target.seen_M = next_target.seen_checksum = \ next_target.seen_semi_comment = next_target.seen_parens_comment = \ next_target.read_string = next_target.checksum_read = \ next_target.checksum_calculated = 0; last_field = 0; read_digit.sign = read_digit.mantissa = read_digit.exponent = 0; if (next_target.option_all_relative) { next_target.target.axis[X] = next_target.target.axis[Y] = next_target.target.axis[Z] = 0; } if (next_target.option_all_relative || next_target.option_e_relative) { next_target.target.axis[E] = 0; } return 1; } #ifdef SD // Handle string reading. After checking for EOL. if (next_target.read_string) { if (c == ' ') { if (str_buf_ptr) next_target.read_string = 0; } else if (str_buf_ptr < STR_BUF_LEN) { gcode_str_buf[str_buf_ptr] = c; str_buf_ptr++; gcode_str_buf[str_buf_ptr] = '\0'; } } #endif /* SD */ return 0; }
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: //? --- 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; // M84- stop idle hold case 84: stepper_disable(); x_disable(); y_disable(); z_disable(); e_disable(); 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). See also M116. //? Teacup supports an optional P parameter as a sensor index to address (eg M104 P1 S100 will set the bed temperature rather than the extruder temperature). //? if ( ! next_target.seen_S) break; if ( ! next_target.seen_P) next_target.P = HEATER_EXTRUDER; temp_set(next_target.P, next_target.S); if (next_target.S) power_on(); break; case 105: //? --- M105: Get Extruder Temperature --- //? //? Example: M105 //? //? Request the temperature of the current extruder and the build base in degrees Celsius. The temperatures are returned to the host computer. 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 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 --- //? //? Example: M106 S120 //? //? Control the cooling fan (if any). //? #ifdef ENFORCE_ORDER // wait for all moves to complete queue_wait(); #endif #ifdef HEATER_FAN if ( ! next_target.seen_S) break; temp_set(HEATER_FAN, next_target.S); if (next_target.S) power_on(); #endif 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 // M113- extruder PWM 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 130: //? --- M130: heater P factor --- //? Undocumented. if ( ! next_target.seen_P) next_target.P = HEATER_EXTRUDER; if (next_target.seen_S) pid_set_p(next_target.P, next_target.S); break; case 131: //? --- M131: heater I factor --- //? Undocumented. if ( ! next_target.seen_P) next_target.P = HEATER_EXTRUDER; if (next_target.seen_S) pid_set_i(next_target.P, next_target.S); break; case 132: //? --- M132: heater D factor --- //? Undocumented. if ( ! next_target.seen_P) next_target.P = HEATER_EXTRUDER; 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) next_target.P = HEATER_EXTRUDER; 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; case 135: //? --- M135: set heater output --- //? Undocumented. if ( ! next_target.seen_P) next_target.P = HEATER_EXTRUDER; if (next_target.seen_S) { heater_set(next_target.P, next_target.S); power_on(); } break; #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); if (next_target.S) power_on(); #endif break; case 190: //? --- M190: Power On --- //? Undocumented. //? This one is pointless in Teacup. Implemented to calm the RepRap gurus. //? power_on(); stepper_enable(); x_enable(); y_enable(); z_enable(); e_enable(); break; case 191: //? --- M191: Power Off --- //? Undocumented. //? Same as M2. RepRap obviously prefers to invent new numbers instead of looking into standards. #ifdef ENFORCE_ORDER // wait for all moves to complete queue_wait(); #endif power_off(); break; case 200: //? --- M200: report endstop status --- //? Report the current status of the endstops configured in the firmware to the host. power_on(); #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 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()
/** Open a file for reading. \param filename Name of the file to open and to read G-code from. Before too long this will cause the printer to read G-code from this file until done or until stopped by G-code coming in over the serial line. */ void sd_open(const char* filename) { result = pf_open(filename); if (result != FR_OK) { sersendf_P(PSTR("E: failed to open file. (%su)\n"), result); } }
/** Mount the SD card. */ void sd_mount(void) { result = pf_mount(&sdfile); if (result != FR_OK) sersendf_P(PSTR("E: SD init failed. (%su)\n"), result); }