diff --git a/Grbl_Esp32/Custom/CoreXY.cpp b/Grbl_Esp32/Custom/CoreXY.cpp index b1c6fb87..63e0eaa5 100644 --- a/Grbl_Esp32/Custom/CoreXY.cpp +++ b/Grbl_Esp32/Custom/CoreXY.cpp @@ -59,6 +59,19 @@ void machine_init() { #endif } +// Converts Cartesian to motors with no motion control +static void cartesian_to_motors(float* position) { + float motors[MAX_N_AXIS]; + + motors[X_AXIS] = geometry_factor * position[X_AXIS] + position[Y_AXIS]; + motors[Y_AXIS] = geometry_factor * position[X_AXIS] - position[Y_AXIS]; + + position[X_AXIS] = motors[X_AXIS]; + position[Y_AXIS] = motors[Y_AXIS]; + + // Z and higher just pass through unchanged +} + // Cycle mask is 0 unless the user sends a single axis command like $HZ // This will always return true to prevent the normal Grbl homing cycle bool user_defined_homing(uint8_t cycle_mask) { @@ -135,7 +148,7 @@ bool user_defined_homing(uint8_t cycle_mask) { } // convert back to motor steps - inverse_kinematics(target); + cartesian_to_motors(target); pl_data->feed_rate = homing_rate; // feed or seek rates plan_buffer_line(target, pl_data); // Bypass mc_line(). Directly plan homing motion. @@ -226,7 +239,7 @@ bool user_defined_homing(uint8_t cycle_mask) { } // convert to motors - inverse_kinematics(target); + cartesian_to_motors(target); // convert to steps for (axis = X_AXIS; axis <= Y_AXIS; axis++) { sys_position[axis] = target[axis] * axis_settings[axis]->steps_per_mm->get(); @@ -242,24 +255,10 @@ bool user_defined_homing(uint8_t cycle_mask) { return true; } -// This function is used by Grbl convert Cartesian to motors -// this does not do any motion control -void inverse_kinematics(float* position) { - float motors[MAX_N_AXIS]; - - motors[X_AXIS] = geometry_factor * position[X_AXIS] + position[Y_AXIS]; - motors[Y_AXIS] = geometry_factor * position[X_AXIS] - position[Y_AXIS]; - - position[X_AXIS] = motors[X_AXIS]; - position[Y_AXIS] = motors[Y_AXIS]; - - // Z and higher just pass through unchanged -} - // Inverse Kinematics calculates motor positions from real world cartesian positions // position is the current position // Breaking into segments is not needed with CoreXY, because it is a linear system. -void inverse_kinematics(float* target, plan_line_data_t* pl_data, float* position) //The target and position are provided in MPos +bool inverse_kinematics(float* target, plan_line_data_t* pl_data, float* position) //The target and position are provided in MPos { float dx, dy, dz; // distances in each cartesian axis float motors[MAX_N_AXIS]; @@ -288,7 +287,7 @@ void inverse_kinematics(float* target, plan_line_data_t* pl_data, float* positio memcpy(last_motors, motors, sizeof(motors)); - mc_line(motors, pl_data); + return mc_line(motors, pl_data); } // motors -> cartesian @@ -314,7 +313,7 @@ void forward_kinematics(float* position) { // apply the forward kinemetics to the machine coordinates // https://corexy.com/theory.html //calc_fwd[X_AXIS] = 0.5 / geometry_factor * (position[X_AXIS] + position[Y_AXIS]); - calc_fwd[X_AXIS] = ((0.5 * (print_position[X_AXIS] + print_position[Y_AXIS]) * geometry_factor) - wco[X_AXIS]); + calc_fwd[X_AXIS] = ((0.5 * (print_position[X_AXIS] + print_position[Y_AXIS]) / geometry_factor) - wco[X_AXIS]); calc_fwd[Y_AXIS] = ((0.5 * (print_position[X_AXIS] - print_position[Y_AXIS])) - wco[Y_AXIS]); for (int axis = 0; axis < n_axis; axis++) { @@ -336,9 +335,9 @@ void kinematics_post_homing() { } } -// this is used used by Grbl soft limits to see if the range of the machine is exceeded. -uint8_t kinematic_limits_check(float* target) { - return true; +// this is used used by Limits.cpp to see if the range of the machine is exceeded. +bool limitsCheckTravel(float* target) { + return false; } void user_m30() {} diff --git a/Grbl_Esp32/Custom/custom_code_template.cpp b/Grbl_Esp32/Custom/custom_code_template.cpp index bd468f6d..6725e59d 100644 --- a/Grbl_Esp32/Custom/custom_code_template.cpp +++ b/Grbl_Esp32/Custom/custom_code_template.cpp @@ -54,7 +54,6 @@ special things your machine needs at startup. void machine_init() {} #endif -#ifdef USE_CUSTOM_HOMING /* user_defined_homing(uint8_t cycle_mask) is called at the begining of the normal Grbl_ESP32 homing sequence. If user_defined_homing(uint8_t cycle_mask) returns false, the rest of normal Grbl_ESP32 @@ -66,9 +65,7 @@ bool user_defined_homing(uint8_t cycle_mask) { // True = done with homing, false = continue with normal Grbl_ESP32 homing return true; } -#endif -#ifdef USE_KINEMATICS /* Inverse Kinematics converts X,Y,Z cartesian coordinate to the steps on your "joint" motors. It requires the following three functions: @@ -86,9 +83,9 @@ bool user_defined_homing(uint8_t cycle_mask) { pl_data = planner data (see the definition of this type to see what it is) position = an N_AXIS array of where the machine is starting from for this move */ -void inverse_kinematics(float* target, plan_line_data_t* pl_data, float* position) { +bool inverse_kinematics(float* target, plan_line_data_t* pl_data, float* position) { // this simply moves to the target. Replace with your kinematics. - mc_line(target, pl_data); + return mc_line(target, pl_data); } /* @@ -97,8 +94,7 @@ void inverse_kinematics(float* target, plan_line_data_t* pl_data, float* positio cycle_mask is a bit mask of the axes being homed this time. */ -bool kinematics_pre_homing(uint8_t cycle_mask)) -{ +bool kinematics_pre_homing(uint8_t cycle_mask) { return false; // finish normal homing cycle } @@ -106,9 +102,15 @@ bool kinematics_pre_homing(uint8_t cycle_mask)) kinematics_post_homing() is called at the end of normal homing */ void kinematics_post_homing() {} -#endif -#ifdef USE_FWD_KINEMATICS +/* + limitsCheckTravel() is called to check soft limits + It returns true if the motion is outside the limit values +*/ +bool limitsCheckTravel() { + return false; +} + /* The status command uses forward_kinematics() to convert your motor positions to cartesian X,Y,Z... coordinates. @@ -119,7 +121,6 @@ void forward_kinematics(float* position) { // position[X_AXIS] = // position[Y_AXIS] = } -#endif #ifdef USE_TOOL_CHANGE /* diff --git a/Grbl_Esp32/Custom/oled_basic.cpp b/Grbl_Esp32/Custom/oled_basic.cpp new file mode 100644 index 00000000..fedc543e --- /dev/null +++ b/Grbl_Esp32/Custom/oled_basic.cpp @@ -0,0 +1,280 @@ +/* + oled_basic.cpp + Part of Grbl_ESP32 + + copyright (c) 2018 - Bart Dring This file was modified for use on the ESP32 + CPU. Do not use this with Grbl for atMega328P + + Grbl is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Grbl is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Grbl. If not, see . + + -------------------------------------------------------------- + + This is a minimal implentation of a display as a test project. + It is designed to be used with a machine that has no easily accessible serial connection + It shows basic status and connection information. + + When in alarm mode it will show the current Wifi/BT paramaters and status + Most machines will start in alarm mode (needs homing) + If the machine is running a job from SD it will show the progress + In other modes it will show state and 3 axis DROs + Thats All! + + Library Infor: + https://github.com/ThingPulse/esp8266-oled-ssd1306 + + Install to PlatformIO with this typed at the terminal + platformio lib install 562 + + Add this to your machine definition file + #define DISPLAY_CODE_FILENAME "Custom/oled_basic.cpp" +*/ + +// Include the correct display library + +#include "SSD1306Wire.h" +#include "../src/WebUI/WebSettings.h" + +#ifndef OLED_ADDRESS +# define OLED_ADDRESS 0x3c +#endif + +#ifndef OLED_SDA +# define OLED_SDA GPIO_NUM_14 +#endif + +#ifndef OLED_SCL +# define OLED_SCL GPIO_NUM_13 +#endif + +#ifndef OLED_GEOMETRY +# define OLED_GEOMETRY GEOMETRY_128_64 +#endif + +SSD1306Wire display(OLED_ADDRESS, OLED_SDA, OLED_SCL, OLED_GEOMETRY); + +static TaskHandle_t displayUpdateTaskHandle = 0; + +// This displays the status of the ESP32 Radios...BT, WiFi, etc +void displayRadioInfo() { + String radio_addr = ""; + String radio_name = ""; + String radio_status = ""; + +#ifdef ENABLE_BLUETOOTH + if (WebUI::wifi_radio_mode->get() == ESP_BT) { + radio_name = String("BT: ") + WebUI::bt_name->get(); + } +#endif +#ifdef ENABLE_WIFI + if ((WiFi.getMode() == WIFI_MODE_STA) || (WiFi.getMode() == WIFI_MODE_APSTA)) { + radio_name = "STA: " + WiFi.SSID(); + radio_addr = WiFi.localIP().toString(); + } else if ((WiFi.getMode() == WIFI_MODE_AP) || (WiFi.getMode() == WIFI_MODE_APSTA)) { + radio_name = String("AP:") + WebUI::wifi_ap_ssid->get(); + radio_addr = WiFi.softAPIP().toString(); + } +#endif + +#ifdef WIFI_OR_BLUETOOTH + if (WebUI::wifi_radio_mode->get() == ESP_RADIO_OFF) { + radio_name = "Radio Mode: None"; + } +#else + radio_name = "Radio Mode:Disabled"; +#endif + + display.setTextAlignment(TEXT_ALIGN_LEFT); + display.setFont(ArialMT_Plain_10); + + if (sys.state == State::Alarm) { // print below Alarm: + display.drawString(0, 18, radio_name); + display.drawString(0, 30, radio_addr); + + } else { // print next to status + if (WebUI::wifi_radio_mode->get() == ESP_BT) { + display.drawString(55, 2, radio_name); + } else { + display.drawString(55, 2, radio_addr); + } + } +} +// Here changes begin Here changes begin Here changes begin Here changes begin Here changes begin + +void draw_checkbox(int16_t x, int16_t y, int16_t width, int16_t height, bool checked) { + if (checked) + display.fillRect(x, y, width, height); // If log.0 + else + display.drawRect(x, y, width, height); // If log.1 +} + +void displayDRO() { + uint8_t oled_y_pos; + float print_position[MAX_N_AXIS]; + //float wco[MAX_N_AXIS]; + + display.setTextAlignment(TEXT_ALIGN_LEFT); + display.setFont(ArialMT_Plain_10); + + char axisVal[20]; + + display.drawString(80, 14, "L"); // Limit switch + + auto n_axis = number_axis->get(); + AxisMask lim_pin_state = limits_get_state(); + ControlPins ctrl_pin_state = system_control_get_state(); + bool prb_pin_state = probe_get_state(); + + if (bit_istrue(status_mask->get(), RtStatus::Position)) { + calc_mpos(print_position); + } else { + calc_wpos(print_position); + } + + for (uint8_t axis = X_AXIS; axis < n_axis; axis++) { + oled_y_pos = 24 + (axis * 10); + + String axis_letter = String(report_get_axis_letter(axis)); + axis_letter += ":"; + display.setTextAlignment(TEXT_ALIGN_LEFT); + display.drawString(0, oled_y_pos, axis_letter); // String('X') + ":"); + + display.setTextAlignment(TEXT_ALIGN_RIGHT); + snprintf(axisVal, 20 - 1, "%.3f", print_position[axis]); + display.drawString(60, oled_y_pos, axisVal); + + if (limitsSwitchDefined(axis, 0)) { // olny draw the box if a switch has been defined + draw_checkbox(80, 27 + (axis * 10), 7, 7, bit_istrue(lim_pin_state, bit(axis))); + } + } + + oled_y_pos = 14; + + if (PROBE_PIN != UNDEFINED_PIN) { + display.drawString(110, oled_y_pos, "P"); + draw_checkbox(120, oled_y_pos + 3, 7, 7, prb_pin_state); + oled_y_pos += 10; + } + +#ifdef CONTROL_FEED_HOLD_PIN + display.drawString(110, oled_y_pos, "H"); + draw_checkbox(120, oled_y_pos + 3, 7, 7, ctrl_pin_state.bit.feedHold); + oled_y_pos += 10; +#endif + +#ifdef CONTROL_CYCLE_START_PIN + display.drawString(110, oled_y_pos, "S"); + draw_checkbox(120, oled_y_pos + 3, 7, 7, ctrl_pin_state.bit.cycleStart); + oled_y_pos += 10; +#endif + +#ifdef CONTROL_RESET_PIN + display.drawString(110, oled_y_pos, "R"); + draw_checkbox(120, oled_y_pos + 3, 7, 7, ctrl_pin_state.bit.reset); + oled_y_pos += 10; +#endif + +#ifdef CONTROL_SAFETY_DOOR_PIN + display.drawString(110, oled_y_pos, "D"); + draw_checkbox(120, oled_y_pos + 3, 7, 7, ctrl_pin_state.bit.safetyDoor); +#endif +} + +void displayUpdate(void* pvParameters) { + TickType_t xLastWakeTime; + const TickType_t xDisplayFrequency = 100; // in ticks (typically ms) + xLastWakeTime = xTaskGetTickCount(); // Initialise the xLastWakeTime variable with the current time. + + vTaskDelay(2500); + uint16_t sd_file_ticker = 0; + + display.init(); + display.flipScreenVertically(); + + while (true) { + display.clear(); + + String state_string = ""; + + display.setTextAlignment(TEXT_ALIGN_LEFT); + display.setFont(ArialMT_Plain_16); + display.drawString(0, 0, report_state_text()); + + if (get_sd_state(false) == SDState::BusyPrinting) { + display.clear(); + display.setTextAlignment(TEXT_ALIGN_CENTER); + display.setFont(ArialMT_Plain_10); + state_string = "SD File"; + for (int i = 0; i < sd_file_ticker % 10; i++) { + state_string += "."; + } + sd_file_ticker++; + display.drawString(63, 0, state_string); + + char path[50]; + sd_get_current_filename(path); + display.drawString(63, 12, path); + + int progress = sd_report_perc_complete(); + // draw the progress bar + display.drawProgressBar(0, 45, 120, 10, progress); + + // draw the percentage as String + display.setFont(ArialMT_Plain_10); + display.setTextAlignment(TEXT_ALIGN_CENTER); + display.drawString(64, 25, String(progress) + "%"); + + } else if (sys.state == State::Alarm) { + displayRadioInfo(); + } else { + displayDRO(); + displayRadioInfo(); + } + + display.display(); + + vTaskDelayUntil(&xLastWakeTime, xDisplayFrequency); + } +} + +void display_init() { + // Initialising the UI will init the display too. + grbl_msg_sendf(CLIENT_SERIAL, MsgLevel::Info, "Init Basic OLED SDA:%s SCL:%s", pinName(OLED_SDA), pinName(OLED_SCL)); + display.init(); + + display.flipScreenVertically(); + + display.clear(); + + display.setTextAlignment(TEXT_ALIGN_CENTER); + display.setFont(ArialMT_Plain_10); + + String mach_name = MACHINE_NAME; + // remove characters from the end until the string fits + while (display.getStringWidth(mach_name) > 128) { + mach_name = mach_name.substring(0, mach_name.length() - 1); + } + display.drawString(63, 0, mach_name); + + display.display(); + + xTaskCreatePinnedToCore(displayUpdate, // task + "displayUpdateTask", // name for task + 4096, // size of task stack + NULL, // parameters + 1, // priority + &displayUpdateTaskHandle, + CONFIG_ARDUINO_RUNNING_CORE // must run the task on same core + // core + ); +} diff --git a/Grbl_Esp32/Custom/parallel_delta.cpp b/Grbl_Esp32/Custom/parallel_delta.cpp index a82de59b..ea3f578d 100644 --- a/Grbl_Esp32/Custom/parallel_delta.cpp +++ b/Grbl_Esp32/Custom/parallel_delta.cpp @@ -135,23 +135,12 @@ bool user_defined_homing(uint8_t cycle_mask) { // true = do not continue with n } // This function is used by Grbl -void inverse_kinematics(float* position) { - float motor_angles[3]; - - read_settings(); - delta_calcInverse(position, motor_angles); - position[0] = motor_angles[0]; - position[1] = motor_angles[1]; - position[2] = motor_angles[2]; -} - -// This function is used by Grbl -void inverse_kinematics(float* target, plan_line_data_t* pl_data, float* position) //The target and position are provided in MPos +bool inverse_kinematics(float* target, plan_line_data_t* pl_data, float* position) //The target and position are provided in MPos { float dx, dy, dz; // distances in each cartesian axis float motor_angles[3]; - float seg_target[3]; // The target of the current segment + float seg_target[3]; // The target of the current segment float feed_rate = pl_data->feed_rate; // save original feed rate bool show_error = true; // shows error once @@ -210,7 +199,7 @@ void inverse_kinematics(float* target, plan_line_data_t* pl_data, float* positio pl_data->feed_rate = (feed_rate * delta_distance / segment_dist); } - mc_line(motor_angles, pl_data); + return mc_line(motor_angles, pl_data); } else { if (show_error) { @@ -222,36 +211,35 @@ void inverse_kinematics(float* target, plan_line_data_t* pl_data, float* positio // motor_angles[1], // motor_angles[2]); show_error = false; + return false; } } } } // this is used used by Grbl soft limits to see if the range of the machine is exceeded. -uint8_t kinematic_limits_check(float* target) { +bool limitsCheckTravel(float* target) { float motor_angles[3]; read_settings(); grbl_msg_sendf(CLIENT_SERIAL, MsgLevel::Info, "Kin Soft Check %3.3f, %3.3f, %3.3f", target[0], target[1], target[2]); - KinematicError status = delta_calcInverse(target, motor_angles); - - switch (status) { + switch (delta_calcInverse(target, motor_angles)) { case KinematicError::OUT_OF_RANGE: grbl_msg_sendf(CLIENT_SERIAL, MsgLevel::Info, "Kin target out of range"); - break; + return true; case KinematicError::ANGLE_TOO_NEGATIVE: grbl_msg_sendf(CLIENT_SERIAL, MsgLevel::Info, "Kin target max negative"); - break; + return true; case KinematicError::ANGLE_TOO_POSITIVE: grbl_msg_sendf(CLIENT_SERIAL, MsgLevel::Info, "Kin target max positive"); - break; + return true; case KinematicError::NONE: - break; + return false; } - return (status == KinematicError::NONE); + return false; } // inverse kinematics: cartesian -> angles diff --git a/Grbl_Esp32/Custom/polar_coaster.cpp b/Grbl_Esp32/Custom/polar_coaster.cpp index 85cf3f8a..966f100e 100644 --- a/Grbl_Esp32/Custom/polar_coaster.cpp +++ b/Grbl_Esp32/Custom/polar_coaster.cpp @@ -85,7 +85,7 @@ void kinematics_post_homing() { */ -void inverse_kinematics(float* target, plan_line_data_t* pl_data, float* position) { +bool inverse_kinematics(float* target, plan_line_data_t* pl_data, float* position) { //static float last_angle = 0; //static float last_radius = 0; float dx, dy, dz; // distances in each cartesian axis @@ -111,6 +111,7 @@ void inverse_kinematics(float* target, plan_line_data_t* pl_data, float* positio segment_count = ceil(dist / SEGMENT_LENGTH); // determine the number of segments we need ... round up so there is at least 1 } dist /= segment_count; // segment distance + bool added = false; for (uint32_t segment = 1; segment <= segment_count; segment++) { // determine this segment's target seg_target[X_AXIS] = position[X_AXIS] + (dx / float(segment_count) * segment) - x_offset; @@ -141,9 +142,10 @@ void inverse_kinematics(float* target, plan_line_data_t* pl_data, float* positio polar[Z_AXIS] += z_offset; last_radius = polar[RADIUS_AXIS]; last_angle = polar[POLAR_AXIS]; - mc_line(polar, pl_data); + added = mc_line(polar, pl_data); } // TO DO don't need a feedrate for rapids + return added; } /* diff --git a/Grbl_Esp32/src/data/favicon.ico b/Grbl_Esp32/data/favicon.ico similarity index 100% rename from Grbl_Esp32/src/data/favicon.ico rename to Grbl_Esp32/data/favicon.ico diff --git a/Grbl_Esp32/src/data/index.html.gz b/Grbl_Esp32/data/index.html.gz similarity index 100% rename from Grbl_Esp32/src/data/index.html.gz rename to Grbl_Esp32/data/index.html.gz diff --git a/Grbl_Esp32/src/CustomCode.cpp b/Grbl_Esp32/src/CustomCode.cpp index 7ee3f88a..f0e0e626 100644 --- a/Grbl_Esp32/src/CustomCode.cpp +++ b/Grbl_Esp32/src/CustomCode.cpp @@ -6,3 +6,7 @@ #ifdef CUSTOM_CODE_FILENAME # include CUSTOM_CODE_FILENAME #endif + +#ifdef DISPLAY_CODE_FILENAME +# include DISPLAY_CODE_FILENAME +#endif \ No newline at end of file diff --git a/Grbl_Esp32/src/GCode.cpp b/Grbl_Esp32/src/GCode.cpp index bd1bf2b9..8254d80a 100644 --- a/Grbl_Esp32/src/GCode.cpp +++ b/Grbl_Esp32/src/GCode.cpp @@ -1287,11 +1287,12 @@ Error gc_execute_line(char* line, uint8_t client) { FAIL(Error::InvalidJogCommand); } // Initialize planner data to current spindle and coolant modal state. - pl_data->spindle_speed = gc_state.spindle_speed; - pl_data->spindle = gc_state.modal.spindle; - pl_data->coolant = gc_state.modal.coolant; - Error status = jog_execute(pl_data, &gc_block); - if (status == Error::Ok) { + pl_data->spindle_speed = gc_state.spindle_speed; + pl_data->spindle = gc_state.modal.spindle; + pl_data->coolant = gc_state.modal.coolant; + bool cancelledInflight = false; + Error status = jog_execute(pl_data, &gc_block, &cancelledInflight); + if (status == Error::Ok && !cancelledInflight) { memcpy(gc_state.position, gc_block.values.xyz, sizeof(gc_block.values.xyz)); } return status; @@ -1485,9 +1486,9 @@ Error gc_execute_line(char* line, uint8_t client) { // and absolute and incremental modes. pl_data->motion.rapidMotion = 1; // Set rapid motion flag. if (axis_command != AxisCommand::None) { - mc_line_kins(gc_block.values.xyz, pl_data, gc_state.position); + inverse_kinematics(gc_block.values.xyz, pl_data, gc_state.position); } - mc_line_kins(coord_data, pl_data, gc_state.position); + inverse_kinematics(coord_data, pl_data, gc_state.position); memcpy(gc_state.position, coord_data, sizeof(gc_state.position)); break; case NonModal::SetHome0: @@ -1515,12 +1516,10 @@ Error gc_execute_line(char* line, uint8_t client) { if (axis_command == AxisCommand::MotionMode) { GCUpdatePos gc_update_pos = GCUpdatePos::Target; if (gc_state.modal.motion == Motion::Linear) { - //mc_line(gc_block.values.xyz, pl_data); - mc_line_kins(gc_block.values.xyz, pl_data, gc_state.position); + inverse_kinematics(gc_block.values.xyz, pl_data, gc_state.position); } else if (gc_state.modal.motion == Motion::Seek) { pl_data->motion.rapidMotion = 1; // Set rapid motion flag. - //mc_line(gc_block.values.xyz, pl_data); - mc_line_kins(gc_block.values.xyz, pl_data, gc_state.position); + inverse_kinematics(gc_block.values.xyz, pl_data, gc_state.position); } else if ((gc_state.modal.motion == Motion::CwArc) || (gc_state.modal.motion == Motion::CcwArc)) { mc_arc(gc_block.values.xyz, pl_data, diff --git a/Grbl_Esp32/src/Grbl.cpp b/Grbl_Esp32/src/Grbl.cpp index be3d311d..c4a8926a 100644 --- a/Grbl_Esp32/src/Grbl.cpp +++ b/Grbl_Esp32/src/Grbl.cpp @@ -30,7 +30,8 @@ void grbl_init() { WiFi.enableSTA(false); WiFi.enableAP(false); WiFi.mode(WIFI_OFF); - serial_init(); // Setup serial baud rate and interrupts + client_init(); // Setup serial baud rate and interrupts + display_init(); grbl_msg_sendf(CLIENT_SERIAL, MsgLevel::Info, "Grbl_ESP32 Ver %s Date %s", GRBL_VERSION, GRBL_VERSION_BUILD); // print grbl_esp32 verion info grbl_msg_sendf(CLIENT_SERIAL, MsgLevel::Info, "Compiled with ESP32 SDK:%s", ESP.getSdkVersion()); // print the SDK version // show the map name at startup @@ -39,7 +40,7 @@ void grbl_init() { #endif settings_init(); // Load Grbl settings from non-volatile storage stepper_init(); // Configure stepper pins and interrupt timers - system_ini(); // Configure pinout pins and pin-change interrupt (Renamed due to conflict with esp32 files) + system_ini(); // Configure pinout pins and pin-change interrupt (Renamed due to conflict with esp32 files) init_motors(); memset(sys_position, 0, sizeof(sys_position)); // Clear machine position. machine_init(); // weak definition in Grbl.cpp does nothing @@ -92,8 +93,8 @@ static void reset_variables() { sys_rt_s_override = SpindleSpeedOverride::Default; // Reset Grbl primary systems. - serial_reset_read_buffer(CLIENT_ALL); // Clear serial read buffer - gc_init(); // Set g-code parser to default state + client_reset_read_buffer(CLIENT_ALL); + gc_init(); // Set g-code parser to default state spindle->stop(); coolant_init(); limits_init(); @@ -104,6 +105,10 @@ static void reset_variables() { plan_sync_position(); gc_sync_position(); report_init_message(CLIENT_ALL); + + // used to keep track of a jog command sent to mc_line() so we can cancel it. + // this is needed if a jogCancel comes along after we have already parsed a jog and it is in-flight. + sys_pl_data_inflight = NULL; } void run_once() { @@ -116,6 +121,7 @@ void run_once() { void __attribute__((weak)) machine_init() {} +void __attribute__((weak)) display_init() {} /* setup() and loop() in the Arduino .ino implements this control flow: diff --git a/Grbl_Esp32/src/Grbl.h b/Grbl_Esp32/src/Grbl.h index e17bbfff..f23f1f0a 100644 --- a/Grbl_Esp32/src/Grbl.h +++ b/Grbl_Esp32/src/Grbl.h @@ -22,7 +22,7 @@ // Grbl versioning system const char* const GRBL_VERSION = "1.3a"; -const char* const GRBL_VERSION_BUILD = "20210311"; +const char* const GRBL_VERSION_BUILD = "20210420"; //#include #include @@ -51,8 +51,9 @@ const char* const GRBL_VERSION_BUILD = "20210311"; #include "Limits.h" #include "MotionControl.h" #include "Protocol.h" -#include "Report.h" +#include "Uart.h" #include "Serial.h" +#include "Report.h" #include "Pins.h" #include "Spindles/Spindle.h" #include "Motors/Motors.h" @@ -65,6 +66,8 @@ const char* const GRBL_VERSION_BUILD = "20210311"; #include "UserOutput.h" +#include + // Do not guard this because it is needed for local files too #include "SDCard.h" @@ -90,20 +93,18 @@ const char* const GRBL_VERSION_BUILD = "20210311"; void grbl_init(); void run_once(); -// Called if USE_MACHINE_INIT is defined -void machine_init(); +void machine_init(); // weak definition in Grbl.cpp +void display_init(); // weak definition in Grbl.cpp bool user_defined_homing(uint8_t cycle_mask); // weak definition in Limits.cpp -// Called if USE_KINEMATICS is defined +// weak definitions in MotionControl.cpp +bool inverse_kinematics(float* target, plan_line_data_t* pl_data, float* position); +bool kinematics_pre_homing(uint8_t cycle_mask); +void kinematics_post_homing(); -void inverse_kinematics(float* target, plan_line_data_t* pl_data, float* position); -bool kinematics_pre_homing(uint8_t cycle_mask); -void kinematics_post_homing(); -uint8_t kinematic_limits_check(float* target); +bool limitsCheckTravel(float* target); // weak in Limits.cpp; true if out of range -// Called if USE_FWD_KINEMATICS is defined -void inverse_kinematics(float* position); // used to return a converted value void forward_kinematics(float* position); // weak definition in Report.cpp // Called if MACRO_BUTTON_0_PIN or MACRO_BUTTON_1_PIN or MACRO_BUTTON_2_PIN is defined diff --git a/Grbl_Esp32/src/I2SOut.cpp b/Grbl_Esp32/src/I2SOut.cpp index 211cffaf..463c7c65 100644 --- a/Grbl_Esp32/src/I2SOut.cpp +++ b/Grbl_Esp32/src/I2SOut.cpp @@ -48,6 +48,7 @@ #include "WebUI/ESPResponse.h" #include "Probe.h" #include "System.h" +#include "Serial.h" #include "Report.h" #include diff --git a/Grbl_Esp32/src/Jog.cpp b/Grbl_Esp32/src/Jog.cpp index 1c0c3d00..568e9842 100644 --- a/Grbl_Esp32/src/Jog.cpp +++ b/Grbl_Esp32/src/Jog.cpp @@ -24,11 +24,13 @@ #include "Grbl.h" // Sets up valid jog motion received from g-code parser, checks for soft-limits, and executes the jog. -Error jog_execute(plan_line_data_t* pl_data, parser_block_t* gc_block) { +// cancelledInflight will be set to true if was not added to parser due to a cancelJog. +Error jog_execute(plan_line_data_t* pl_data, parser_block_t* gc_block, bool* cancelledInflight) { // Initialize planner data struct for jogging motions. // NOTE: Spindle and coolant are allowed to fully function with overrides during a jog. pl_data->feed_rate = gc_block->values.f; pl_data->motion.noFeedOverride = 1; + pl_data->is_jog = true; #ifdef USE_LINE_NUMBERS pl_data->line_number = gc_block->values.n; #endif @@ -37,12 +39,12 @@ Error jog_execute(plan_line_data_t* pl_data, parser_block_t* gc_block) { return Error::TravelExceeded; } } -// Valid jog command. Plan, set state, and execute. -#ifndef USE_KINEMATICS - mc_line(gc_block->values.xyz, pl_data); -#else // else use kinematics - inverse_kinematics(gc_block->values.xyz, pl_data, gc_state.position); -#endif + // Valid jog command. Plan, set state, and execute. + if (!inverse_kinematics(gc_block->values.xyz, pl_data, gc_state.position)) { + if (cancelledInflight) + *cancelledInflight = true; + return Error::Ok; + } if (sys.state == State::Idle) { if (plan_get_current_block() != NULL) { // Check if there is a block to execute. diff --git a/Grbl_Esp32/src/Jog.h b/Grbl_Esp32/src/Jog.h index 59307834..1867871e 100644 --- a/Grbl_Esp32/src/Jog.h +++ b/Grbl_Esp32/src/Jog.h @@ -28,4 +28,5 @@ const int JOG_LINE_NUMBER = 0; // Sets up valid jog motion received from g-code parser, checks for soft-limits, and executes the jog. -Error jog_execute(plan_line_data_t* pl_data, parser_block_t* gc_block); +// cancelledInflight will be set to true if was not added to parser due to a cancelJog. +Error jog_execute(plan_line_data_t* pl_data, parser_block_t* gc_block, bool* cancelledInflight); diff --git a/Grbl_Esp32/src/Limits.cpp b/Grbl_Esp32/src/Limits.cpp index 41596e9d..ab167daa 100644 --- a/Grbl_Esp32/src/Limits.cpp +++ b/Grbl_Esp32/src/Limits.cpp @@ -55,10 +55,12 @@ void IRAM_ATTR isr_limit_switches() { # ifdef HARD_LIMIT_FORCE_STATE_CHECK // Check limit pin state. if (limits_get_state()) { + grbl_msg_sendf(CLIENT_ALL, MsgLevel::Debug, "Hard limits"); mc_reset(); // Initiate system kill. sys_rt_exec_alarm = ExecAlarm::HardLimit; // Indicate hard limit critical event } # else + grbl_msg_sendf(CLIENT_ALL, MsgLevel::Debug, "Hard limits"); mc_reset(); // Initiate system kill. sys_rt_exec_alarm = ExecAlarm::HardLimit; // Indicate hard limit critical event # endif @@ -195,7 +197,8 @@ void limits_go_home(uint8_t cycle_mask) { if (sys_rt_exec_alarm != ExecAlarm::None) { motors_set_homing_mode(cycle_mask, false); // tell motors homing is done...failed - mc_reset(); // Stop motors, if they are running. + grbl_msg_sendf(CLIENT_ALL, MsgLevel::Debug, "Homing fail"); + mc_reset(); // Stop motors, if they are running. protocol_execute_realtime(); return; } else { @@ -351,6 +354,7 @@ void limits_soft_check(float* target) { } } while (sys.state != State::Idle); } + grbl_msg_sendf(CLIENT_ALL, MsgLevel::Debug, "Soft limits"); mc_reset(); // Issue system reset and ensure spindle and coolant are shutdown. sys_rt_exec_alarm = ExecAlarm::SoftLimit; // Indicate soft limit critical event protocol_execute_realtime(); // Execute to enter critical event loop and system abort @@ -367,7 +371,7 @@ void limitCheckTask(void* pvParameters) { AxisMask switch_state; switch_state = limits_get_state(); if (switch_state) { - //grbl_msg_sendf(CLIENT_SERIAL, MsgLevel::Info, "Limit Switch State %08d", switch_state); + grbl_msg_sendf(CLIENT_SERIAL, MsgLevel::Debug, "Limit Switch State %08d", switch_state); mc_reset(); // Initiate system kill. sys_rt_exec_alarm = ExecAlarm::HardLimit; // Indicate hard limit critical event } @@ -392,19 +396,24 @@ float limitsMinPosition(uint8_t axis) { // Checks and reports if target array exceeds machine travel limits. // Return true if exceeding limits -bool limitsCheckTravel(float* target) { +// Set $/MaxTravel=0 to selectively remove an axis from soft limit checks +bool __attribute__((weak)) limitsCheckTravel(float* target) { uint8_t idx; auto n_axis = number_axis->get(); for (idx = 0; idx < n_axis; idx++) { float max_mpos, min_mpos; - if (target[idx] < limitsMinPosition(idx) || target[idx] > limitsMaxPosition(idx)) { + if ((target[idx] < limitsMinPosition(idx) || target[idx] > limitsMaxPosition(idx)) && axis_settings[idx]->max_travel->get() > 0) { return true; } } return false; } +bool limitsSwitchDefined(uint8_t axis, uint8_t gang_index) { + return (limit_pins[axis][gang_index] != UNDEFINED_PIN); +} + bool __attribute__((weak)) user_defined_homing(uint8_t cycle_mask) { return false; } diff --git a/Grbl_Esp32/src/Limits.h b/Grbl_Esp32/src/Limits.h index 3cc2fccd..3697f8e0 100644 --- a/Grbl_Esp32/src/Limits.h +++ b/Grbl_Esp32/src/Limits.h @@ -54,3 +54,6 @@ float limitsMinPosition(uint8_t axis); // Internal factor used by limits_soft_check bool limitsCheckTravel(float* target); + +// check if a switch has been defined +bool limitsSwitchDefined(uint8_t axis, uint8_t gang_index); diff --git a/Grbl_Esp32/src/Machines/midtbot.h b/Grbl_Esp32/src/Machines/midtbot.h index 41e35c2a..861295bb 100644 --- a/Grbl_Esp32/src/Machines/midtbot.h +++ b/Grbl_Esp32/src/Machines/midtbot.h @@ -30,8 +30,6 @@ #define CUSTOM_CODE_FILENAME "../Custom/CoreXY.cpp" #define MIDTBOT // applies the geometry correction to the kinematics -#define USE_KINEMATICS // there are kinematic equations for this machine -#define USE_FWD_KINEMATICS // report in cartesian #define USE_MACHINE_INIT // There is some custom initialization for this machine #define USE_CUSTOM_HOMING diff --git a/Grbl_Esp32/src/Machines/polar_coaster.h b/Grbl_Esp32/src/Machines/polar_coaster.h index b0e45e7c..b0ff598b 100644 --- a/Grbl_Esp32/src/Machines/polar_coaster.h +++ b/Grbl_Esp32/src/Machines/polar_coaster.h @@ -37,8 +37,6 @@ #define POLAR_AXIS 1 #define SEGMENT_LENGTH 0.5 // segment length in mm -#define USE_KINEMATICS -#define USE_FWD_KINEMATICS // report in cartesian #define USE_M30 #define X_STEP_PIN GPIO_NUM_15 diff --git a/Grbl_Esp32/src/Machines/tapster_3.h b/Grbl_Esp32/src/Machines/tapster_3.h index b4de2b1c..e537ca06 100644 --- a/Grbl_Esp32/src/Machines/tapster_3.h +++ b/Grbl_Esp32/src/Machines/tapster_3.h @@ -25,8 +25,6 @@ // ================= Firmware Customization =================== -#define USE_KINEMATICS // there are kinematic equations for this machine -#define USE_FWD_KINEMATICS // report in cartesian #define USE_MACHINE_INIT // There is some custom initialization for this machine // ================== Delta Geometry =========================== diff --git a/Grbl_Esp32/src/Machines/tapster_pro_6P_trinamic.h b/Grbl_Esp32/src/Machines/tapster_pro_6P_trinamic.h index 6f88a816..4f633511 100644 --- a/Grbl_Esp32/src/Machines/tapster_pro_6P_trinamic.h +++ b/Grbl_Esp32/src/Machines/tapster_pro_6P_trinamic.h @@ -41,8 +41,6 @@ #define N_AXIS 3 -#define USE_KINEMATICS // there are kinematic equations for this machine -#define USE_FWD_KINEMATICS // report in cartesian #define USE_MACHINE_INIT // There is some custom initialization for this machine // ================== Delta Geometry =========================== diff --git a/Grbl_Esp32/src/MotionControl.cpp b/Grbl_Esp32/src/MotionControl.cpp index 1b8cda8f..3428206c 100644 --- a/Grbl_Esp32/src/MotionControl.cpp +++ b/Grbl_Esp32/src/MotionControl.cpp @@ -33,15 +33,6 @@ SquaringMode ganged_mode = SquaringMode::Dual; -// this allows kinematics to be used. -void mc_line_kins(float* target, plan_line_data_t* pl_data, float* position) { -#ifndef USE_KINEMATICS - mc_line(target, pl_data); -#else // else use kinematics - inverse_kinematics(target, pl_data, position); -#endif -} - // Execute linear motion in absolute millimeter coordinates. Feed rate given in millimeters/second // unless invert_feed_rate is true. Then the feed_rate means that the motion should be completed in // (1 minute)/feed_rate time. @@ -49,7 +40,11 @@ void mc_line_kins(float* target, plan_line_data_t* pl_data, float* position) { // segments, must pass through this routine before being passed to the planner. The seperation of // mc_line and plan_buffer_line is done primarily to place non-planner-type functions from being // in the planner and to let backlash compensation or canned cycle integration simple and direct. -void mc_line(float* target, plan_line_data_t* pl_data) { +// returns true if line was submitted to planner, or false if intentionally dropped. +bool mc_line(float* target, plan_line_data_t* pl_data) { + bool submitted_result = false; + // store the plan data so it can be cancelled by the protocol system if needed + sys_pl_data_inflight = pl_data; // If enabled, check for soft limit violations. Placed here all line motions are picked up // from everywhere in Grbl. if (soft_limits->get()) { @@ -60,7 +55,8 @@ void mc_line(float* target, plan_line_data_t* pl_data) { } // If in check gcode mode, prevent motion by blocking planner. Soft limits still work. if (sys.state == State::CheckMode) { - return; + sys_pl_data_inflight = NULL; + return submitted_result; } // NOTE: Backlash compensation may be installed here. It will need direction info to track when // to insert a backlash line motion(s) before the intended line motion and will require its own @@ -80,7 +76,8 @@ void mc_line(float* target, plan_line_data_t* pl_data) { do { protocol_execute_realtime(); // Check for any run-time commands if (sys.abort) { - return; // Bail, if system abort. + sys_pl_data_inflight = NULL; + return submitted_result; // Bail, if system abort. } if (plan_check_full_buffer()) { protocol_auto_cycle_start(); // Auto-cycle start when buffer is full. @@ -90,9 +87,25 @@ void mc_line(float* target, plan_line_data_t* pl_data) { } while (1); // Plan and queue motion into planner buffer // uint8_t plan_status; // Not used in normal operation. - plan_buffer_line(target, pl_data); + if (sys_pl_data_inflight == pl_data) { + plan_buffer_line(target, pl_data); + submitted_result = true; + } + sys_pl_data_inflight = NULL; + return submitted_result; } +bool __attribute__((weak)) inverse_kinematics(float* target, plan_line_data_t* pl_data, float* position) { + return mc_line(target, pl_data); +} + +bool __attribute__((weak)) kinematics_pre_homing(uint8_t cycle_mask) { + return false; // finish normal homing cycle +} + +void __attribute__((weak)) kinematics_post_homing() {} + +void __attribute__((weak)) forward_kinematics(float* position) {} // Execute an arc in offset mode format. position == current xyz, target == target xyz, // offset == offset from current xyz, axis_X defines circle plane in tool space, axis_linear is // the direction of helical travel, radius == circle radius, isclockwise boolean. Used @@ -117,14 +130,12 @@ void mc_arc(float* target, float rt_axis1 = target[axis_1] - center_axis1; float previous_position[MAX_N_AXIS] = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }; -#ifdef USE_KINEMATICS uint16_t n; auto n_axis = number_axis->get(); for (n = 0; n < n_axis; n++) { previous_position[n] = position[n]; } -#endif // CCW angle between position and target from circle center. Only one atan2() trig computation required. float angular_travel = atan2(r_axis0 * rt_axis1 - r_axis1 * rt_axis0, r_axis0 * rt_axis0 + r_axis1 * rt_axis1); if (is_clockwise_arc) { // Correct atan2 output per direction @@ -206,15 +217,11 @@ void mc_arc(float* target, position[axis_0] = center_axis0 + r_axis0; position[axis_1] = center_axis1 + r_axis1; position[axis_linear] += linear_per_segment; -#ifdef USE_KINEMATICS pl_data->feed_rate = original_feedrate; // This restores the feedrate kinematics may have altered - mc_line_kins(position, pl_data, previous_position); + inverse_kinematics(position, pl_data, previous_position); previous_position[axis_0] = position[axis_0]; previous_position[axis_1] = position[axis_1]; previous_position[axis_linear] = position[axis_linear]; -#else - mc_line(position, pl_data); -#endif // Bail mid-circle on system abort. Runtime command check already performed by mc_line. if (sys.abort) { return; @@ -222,7 +229,7 @@ void mc_arc(float* target, } } // Ensure last segment arrives at target location. - mc_line_kins(target, pl_data, previous_position); + inverse_kinematics(target, pl_data, previous_position); } // Execute dwell in seconds. @@ -292,11 +299,9 @@ void mc_homing_cycle(uint8_t cycle_mask) { // This give kinematics a chance to do something before normal homing // if it returns true, the homing is canceled. -#ifdef USE_KINEMATICS if (kinematics_pre_homing(cycle_mask)) { return; } -#endif // Check and abort homing cycle, if hard limits are already enabled. Helps prevent problems // with machines with limits wired on both ends of travel to one limit pin. // TODO: Move the pin-specific LIMIT_PIN call to Limits.cpp as a function. @@ -366,10 +371,8 @@ void mc_homing_cycle(uint8_t cycle_mask) { // Sync gcode parser and planner positions to homed position. gc_sync_position(); plan_sync_position(); -#ifdef USE_KINEMATICS // This give kinematics a chance to do something after normal homing kinematics_post_homing(); -#endif // If hard limits feature enabled, re-enable hard limits pin change register after homing cycle. limits_init(); } @@ -412,7 +415,7 @@ GCUpdatePos mc_probe_cycle(float* target, plan_line_data_t* pl_data, uint8_t par } // Setup and queue probing motion. Auto cycle-start should not start the cycle. grbl_msg_sendf(CLIENT_SERIAL, MsgLevel::Info, "Found"); - mc_line_kins(target, pl_data, gc_state.position); + inverse_kinematics(target, pl_data, gc_state.position); // Activate the probing state monitor in the stepper module. sys_probe_state = Probe::Active; // Perform probing cycle. Wait here until probe is triggered or motion completes. @@ -499,6 +502,7 @@ void mc_override_ctrl_update(uint8_t override_state) { // lost, since there was an abrupt uncontrolled deceleration. Called at an interrupt level by // realtime abort command and hard limits. So, keep to a minimum. void mc_reset() { + grbl_msg_sendf(CLIENT_SERIAL, MsgLevel::Debug, "mc_reset()"); // Only this function can set the system reset. Helps prevent multiple kill calls. if (!sys_rt_exec_state.bit.reset) { sys_rt_exec_state.bit.reset = true; diff --git a/Grbl_Esp32/src/MotionControl.h b/Grbl_Esp32/src/MotionControl.h index c7adebc5..70d72cf0 100644 --- a/Grbl_Esp32/src/MotionControl.h +++ b/Grbl_Esp32/src/MotionControl.h @@ -35,8 +35,8 @@ const int PARKING_MOTION_LINE_NUMBER = 0; // Execute linear motion in absolute millimeter coordinates. Feed rate given in millimeters/second // unless invert_feed_rate is true. Then the feed_rate means that the motion should be completed in // (1 minute)/feed_rate time. -void mc_line_kins(float* target, plan_line_data_t* pl_data, float* position); -void mc_line(float* target, plan_line_data_t* pl_data); +bool inverse_kinematics(float* target, plan_line_data_t* pl_data, float* position); +bool mc_line(float* target, plan_line_data_t* pl_data); // returns true if line was submitted to planner // Execute an arc in offset mode format. position == current xyz, target == target xyz, // offset == offset from current xyz, axis_XXX defines circle plane in tool space, axis_linear is diff --git a/Grbl_Esp32/src/Motors/TrinamicUartDriver.h b/Grbl_Esp32/src/Motors/TrinamicUartDriver.h index ef489ae1..440231a8 100644 --- a/Grbl_Esp32/src/Motors/TrinamicUartDriver.h +++ b/Grbl_Esp32/src/Motors/TrinamicUartDriver.h @@ -21,6 +21,7 @@ #include "Motor.h" #include "StandardStepper.h" +#include "../Uart.h" #include // https://github.com/teemuatlut/TMCStepper @@ -63,7 +64,7 @@ const double TRINAMIC_UART_FCLK = 12700000.0; // Internal clock Approx (Hz) use # define TMC_UART_TX UNDEFINED_PIN #endif -extern HardwareSerial tmc_serial; +extern Uart tmc_serial; namespace Motors { @@ -75,6 +76,9 @@ namespace Motors { }; class TrinamicUartDriver : public StandardStepper { + private: + static bool _uart_started; + public: TrinamicUartDriver(uint8_t axis_index, uint8_t step_pin, @@ -128,4 +132,4 @@ namespace Motors { // void config_message() override; }; -} \ No newline at end of file +} diff --git a/Grbl_Esp32/src/Motors/TrinamicUartDriverClass.cpp b/Grbl_Esp32/src/Motors/TrinamicUartDriverClass.cpp index f2498505..6409f1e6 100644 --- a/Grbl_Esp32/src/Motors/TrinamicUartDriverClass.cpp +++ b/Grbl_Esp32/src/Motors/TrinamicUartDriverClass.cpp @@ -26,10 +26,12 @@ #include -HardwareSerial tmc_serial(TMC_UART); +Uart tmc_serial(TMC_UART); namespace Motors { + bool TrinamicUartDriver::_uart_started = false; + TrinamicUartDriver* TrinamicUartDriver::List = NULL; // a static ist of all drivers for stallguard reporting /* HW Serial Constructor. */ @@ -41,9 +43,11 @@ namespace Motors { _r_sense = r_sense; this->addr = addr; - uart_set_pin(TMC_UART, TMC_UART_TX, TMC_UART_RX, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE); - tmc_serial.begin(115200, SERIAL_8N1, TMC_UART_RX, TMC_UART_TX); - tmc_serial.setRxBufferSize(128); + if (!_uart_started) { + tmc_serial.setPins(TMC_UART_TX, TMC_UART_RX); + tmc_serial.begin(115200, Uart::Data::Bits8, Uart::Stop::Bits1, Uart::Parity::None); + _uart_started = true; + } hw_serial_init(); link = List; @@ -231,7 +235,7 @@ namespace Motors { tmcstepper->en_spreadCycle(false); tmcstepper->pwm_autoscale(false); tmcstepper->TCOOLTHRS(calc_tstep(homing_feed_rate->get(), 150.0)); - tmcstepper->SGTHRS(constrain(axis_settings[_axis_index]->stallguard->get(),0,255)); + tmcstepper->SGTHRS(constrain(axis_settings[_axis_index]->stallguard->get(), 0, 255)); break; default: grbl_msg_sendf(CLIENT_SERIAL, MsgLevel::Info, "Unknown Trinamic mode:d", _mode); @@ -394,4 +398,4 @@ namespace Motors { #endif } } -} \ No newline at end of file +} diff --git a/Grbl_Esp32/src/Planner.h b/Grbl_Esp32/src/Planner.h index 1f5f3eb4..812229c9 100644 --- a/Grbl_Esp32/src/Planner.h +++ b/Grbl_Esp32/src/Planner.h @@ -91,6 +91,7 @@ typedef struct { #ifdef USE_LINE_NUMBERS int32_t line_number; // Desired line number to report when executing. #endif + bool is_jog; // true if this was generated due to a jog command } plan_line_data_t; // Initialize and reset the motion plan subsystem diff --git a/Grbl_Esp32/src/ProcessSettings.cpp b/Grbl_Esp32/src/ProcessSettings.cpp index 05b765ec..25041262 100644 --- a/Grbl_Esp32/src/ProcessSettings.cpp +++ b/Grbl_Esp32/src/ProcessSettings.cpp @@ -192,6 +192,7 @@ Error toggle_check_mode(const char* value, WebUI::AuthenticationLevel auth_level // is idle and ready, regardless of alarm locks. This is mainly to keep things // simple and consistent. if (sys.state == State::CheckMode) { + grbl_msg_sendf(CLIENT_ALL, MsgLevel::Debug, "Check mode"); mc_reset(); report_feedback_message(Message::Disabled); } else { diff --git a/Grbl_Esp32/src/Protocol.cpp b/Grbl_Esp32/src/Protocol.cpp index 0a331983..3c669128 100644 --- a/Grbl_Esp32/src/Protocol.cpp +++ b/Grbl_Esp32/src/Protocol.cpp @@ -103,7 +103,7 @@ bool can_park() { GRBL PRIMARY LOOP: */ void protocol_main_loop() { - serial_reset_read_buffer(CLIENT_ALL); + client_reset_read_buffer(CLIENT_ALL); empty_lines(); //uint8_t client = CLIENT_SERIAL; // default client // Perform some machine checks to make sure everything is good to go. @@ -135,7 +135,7 @@ void protocol_main_loop() { // Primary loop! Upon a system abort, this exits back to main() to reset the system. // This is also where Grbl idles while waiting for something to do. // --------------------------------------------------------------------------------- - uint8_t c; + int c; for (;;) { #ifdef ENABLE_SD_CARD if (SD_ready_next) { @@ -157,7 +157,7 @@ void protocol_main_loop() { uint8_t client = CLIENT_SERIAL; char* line; for (client = 0; client < CLIENT_COUNT; client++) { - while ((c = serial_read(client)) != SERIAL_NO_DATA) { + while ((c = client_read(client)) != -1) { Error res = add_char_to_line(c, client); switch (res) { case Error::Ok: @@ -567,6 +567,12 @@ static void protocol_exec_rt_suspend() { if (sys.abort) { return; } + // if a jogCancel comes in and we have a jog "in-flight" (parsed and handed over to mc_line()), + // then we need to cancel it before it reaches the planner. otherwise we may try to move way out of + // normal bounds, especially with senders that issue a series of jog commands before sending a cancel. + if (sys.suspend.bit.jogCancel && sys_pl_data_inflight != NULL && ((plan_line_data_t*)sys_pl_data_inflight)->is_jog) { + sys_pl_data_inflight = NULL; + } // Block until initial hold is complete and the machine has stopped motion. if (sys.suspend.bit.holdComplete) { // Parking manager. Handles de/re-energizing, switch state checks, and parking motions for diff --git a/Grbl_Esp32/src/Report.cpp b/Grbl_Esp32/src/Report.cpp index b4728154..3339a23c 100644 --- a/Grbl_Esp32/src/Report.cpp +++ b/Grbl_Esp32/src/Report.cpp @@ -54,30 +54,8 @@ EspClass esp; #endif const int DEFAULTBUFFERSIZE = 64; -// this is a generic send function that everything should use, so interfaces could be added (Bluetooth, etc) void grbl_send(uint8_t client, const char* text) { - if (client == CLIENT_INPUT) { - return; - } -#ifdef ENABLE_BLUETOOTH - if (WebUI::SerialBT.hasClient() && (client == CLIENT_BT || client == CLIENT_ALL)) { - WebUI::SerialBT.print(text); - //delay(10); // possible fix for dropped characters - } -#endif -#if defined(ENABLE_WIFI) && defined(ENABLE_HTTP) && defined(ENABLE_SERIAL2SOCKET_OUT) - if (client == CLIENT_WEBUI || client == CLIENT_ALL) { - WebUI::Serial2Socket.write((const uint8_t*)text, strlen(text)); - } -#endif -#if defined(ENABLE_WIFI) && defined(ENABLE_TELNET) - if (client == CLIENT_TELNET || client == CLIENT_ALL) { - WebUI::telnet_server.write((const uint8_t*)text, strlen(text)); - } -#endif - if (client == CLIENT_SERIAL || client == CLIENT_ALL) { - Serial.print(text); - } + client_write(client, text); } // This is a formating version of the grbl_send(CLIENT_ALL,...) function that work like printf @@ -291,13 +269,13 @@ std::map MessageText = { // NOTE: For interfaces, messages are always placed within brackets. And if silent mode // is installed, the message number codes are less than zero. void report_feedback_message(Message message) { // ok to send to all clients -#if defined (ENABLE_SD_CARD) +#if defined(ENABLE_SD_CARD) if (message == Message::SdFileQuit) { grbl_notifyf("SD print canceled", "Reset during SD file at line: %d", sd_get_current_line_number()); grbl_msg_sendf(CLIENT_SERIAL, MsgLevel::Info, "Reset during SD file at line: %d", sd_get_current_line_number()); - } else -#endif //ENABLE_SD_CARD + } else +#endif //ENABLE_SD_CARD { auto it = MessageText.find(message); if (it != MessageText.end()) { @@ -592,82 +570,52 @@ void report_echo_line_received(char* line, uint8_t client) { grbl_sendf(client, "[echo: %s]\r\n", line); } +// Calculate the position for status reports. +// float print_position = returned position +// float wco = returns the work coordinate offset +// bool wpos = true for work position compensation +void report_calc_status_position(float* print_position, float* wco, bool wpos) { + int32_t current_position[MAX_N_AXIS]; // Copy current state of the system position variable + memcpy(current_position, sys_position, sizeof(sys_position)); + system_convert_array_steps_to_mpos(print_position, current_position); + + //float wco[MAX_N_AXIS]; + if (wpos || (sys.report_wco_counter == 0)) { + auto n_axis = number_axis->get(); + for (uint8_t idx = 0; idx < n_axis; idx++) { + // Apply work coordinate offsets and tool length offset to current position. + wco[idx] = gc_state.coord_system[idx] + gc_state.coord_offset[idx]; + if (idx == TOOL_LENGTH_OFFSET_AXIS) { + wco[idx] += gc_state.tool_length_offset; + } + if (wpos) { + print_position[idx] -= wco[idx]; + } + } + } + + forward_kinematics(print_position); // a weak definition does nothing. Users can provide strong version +} + // Prints real-time data. This function grabs a real-time snapshot of the stepper subprogram // and the actual location of the CNC machine. Users may change the following function to their // specific needs, but the desired real-time data report must be as short as possible. This is // requires as it minimizes the computational overhead and allows grbl to keep running smoothly, // especially during g-code programs with fast, short line segments and high frequency reports (5-20Hz). void report_realtime_status(uint8_t client) { - uint8_t idx; - int32_t current_position[MAX_N_AXIS]; // Copy current state of the system position variable - memcpy(current_position, sys_position, sizeof(sys_position)); float print_position[MAX_N_AXIS]; char status[200]; char temp[MAX_N_AXIS * 20]; - system_convert_array_steps_to_mpos(print_position, current_position); - // Report current machine state and sub-states + strcpy(status, "<"); - switch (sys.state) { - case State::Idle: - strcat(status, "Idle"); - break; - case State::Cycle: - strcat(status, "Run"); - break; - case State::Hold: - if (!(sys.suspend.bit.jogCancel)) { - strcat(status, "Hold:"); - strcat(status, sys.suspend.bit.holdComplete ? "0" : "1"); // Ready to resume - break; - } // Continues to print jog state during jog cancel. - case State::Jog: - strcat(status, "Jog"); - break; - case State::Homing: - strcat(status, "Home"); - break; - case State::Alarm: - strcat(status, "Alarm"); - break; - case State::CheckMode: - strcat(status, "Check"); - break; - case State::SafetyDoor: - strcat(status, "Door:"); - if (sys.suspend.bit.initiateRestore) { - strcat(status, "3"); // Restoring - } else { - if (sys.suspend.bit.retractComplete) { - strcat(status, sys.suspend.bit.safetyDoorAjar ? "1" : "0"); // Door ajar - // Door closed and ready to resume - } else { - strcat(status, "2"); // Retracting - } - } - break; - case State::Sleep: - strcat(status, "Sleep"); - break; - } - float wco[MAX_N_AXIS]; - if (bit_isfalse(status_mask->get(), RtStatus::Position) || (sys.report_wco_counter == 0)) { - auto n_axis = number_axis->get(); - for (idx = 0; idx < n_axis; idx++) { - // Apply work coordinate offsets and tool length offset to current position. - wco[idx] = gc_state.coord_system[idx] + gc_state.coord_offset[idx]; - if (idx == TOOL_LENGTH_OFFSET_AXIS) { - wco[idx] += gc_state.tool_length_offset; - } - if (bit_isfalse(status_mask->get(), RtStatus::Position)) { - print_position[idx] -= wco[idx]; - } - } - } - forward_kinematics(print_position); // a weak definition does nothing. Users can provide strong version - // Report machine position + strcat(status, report_state_text()); + + // Report position if (bit_istrue(status_mask->get(), RtStatus::Position)) { + calc_mpos(print_position); strcat(status, "|MPos:"); } else { + calc_wpos(print_position); strcat(status, "|WPos:"); } report_util_axis_values(print_position, temp); @@ -688,7 +636,7 @@ void report_realtime_status(uint8_t client) { } # endif //ENABLE_BLUETOOTH if (client == CLIENT_SERIAL) { - bufsize = serial_get_rx_buffer_available(CLIENT_SERIAL); + bufsize = client_get_rx_buffer_available(CLIENT_SERIAL); } sprintf(temp, "|Bf:%d,%d", plan_get_block_buffer_available(), bufsize); strcat(status, temp); @@ -793,7 +741,7 @@ void report_realtime_status(uint8_t client) { sys.report_ovr_counter = 1; // Set override on next report. } strcat(status, "|WCO:"); - report_util_axis_values(wco, temp); + report_util_axis_values(get_wco(), temp); strcat(status, temp); } #endif @@ -913,6 +861,54 @@ void report_hex_msg(uint8_t* buf, const char* prefix, int len) { grbl_msg_sendf(CLIENT_SERIAL, MsgLevel::Info, "%s", report); } +char* report_state_text() { + static char state[10]; + + switch (sys.state) { + case State::Idle: + strcpy(state, "Idle"); + break; + case State::Cycle: + strcpy(state, "Run"); + break; + case State::Hold: + if (!(sys.suspend.bit.jogCancel)) { + sys.suspend.bit.holdComplete ? strcpy(state, "Hold:0") : strcpy(state, "Hold:1"); + break; + } // Continues to print jog state during jog cancel. + case State::Jog: + strcpy(state, "Jog"); + break; + case State::Homing: + strcpy(state, "Home"); + break; + case State::Alarm: + strcpy(state, "Alarm"); + break; + case State::CheckMode: + strcpy(state, "Check"); + break; + case State::SafetyDoor: + strcpy(state, "Door:"); + if (sys.suspend.bit.initiateRestore) { + strcat(state, "3"); // Restoring + } else { + if (sys.suspend.bit.retractComplete) { + sys.suspend.bit.safetyDoorAjar ? strcat(state, "1") : strcat(state, "0"); + ; // Door ajar + // Door closed and ready to resume + } else { + strcat(state, "2"); // Retracting + } + } + break; + case State::Sleep: + strcpy(state, "Sleep"); + break; + } + return state; +} + char report_get_axis_letter(uint8_t axis) { switch (axis) { case X_AXIS: @@ -960,4 +956,37 @@ void reportTaskStackSize(UBaseType_t& saved) { #endif } +void calc_mpos(float* print_position) { + int32_t current_position[MAX_N_AXIS]; // Copy current state of the system position variable + memcpy(current_position, sys_position, sizeof(sys_position)); + system_convert_array_steps_to_mpos(print_position, current_position); + forward_kinematics(print_position); // a weak definition does nothing. Users can provide strong version +} + +void calc_wpos(float* print_position) { + int32_t current_position[MAX_N_AXIS]; // Copy current state of the system position variable + memcpy(current_position, sys_position, sizeof(sys_position)); + system_convert_array_steps_to_mpos(print_position, current_position); + + float* wco = get_wco(); + auto n_axis = number_axis->get(); + for (int idx = 0; idx < n_axis; idx++) { + print_position[idx] -= wco[idx]; + } + + forward_kinematics(print_position); // a weak definition does nothing. Users can provide strong version +} +float* get_wco() { + static float wco[MAX_N_AXIS]; + auto n_axis = number_axis->get(); + for (int idx = 0; idx < n_axis; idx++) { + // Apply work coordinate offsets and tool length offset to current position. + wco[idx] = gc_state.coord_system[idx] + gc_state.coord_offset[idx]; + if (idx == TOOL_LENGTH_OFFSET_AXIS) { + wco[idx] += gc_state.tool_length_offset; + } + } + return wco; +} + void __attribute__((weak)) forward_kinematics(float* position) {} // This version does nothing. Make your own to do something with it diff --git a/Grbl_Esp32/src/Report.h b/Grbl_Esp32/src/Report.h index 2ffcecee..366e8d9d 100644 --- a/Grbl_Esp32/src/Report.h +++ b/Grbl_Esp32/src/Report.h @@ -92,6 +92,9 @@ void report_grbl_settings(uint8_t client, uint8_t show_extended); // Prints an echo of the pre-parsed line received right before execution. void report_echo_line_received(char* line, uint8_t client); +// calculate the postion for status reports +void report_calc_status_position(float* print_position, float* wco, bool wpos); + // Prints realtime status report void report_realtime_status(uint8_t client); @@ -122,10 +125,14 @@ void report_machine_type(uint8_t client); void report_hex_msg(char* buf, const char* prefix, int len); void report_hex_msg(uint8_t* buf, const char* prefix, int len); -char report_get_axis_letter(uint8_t axis); - +char report_get_axis_letter(uint8_t axis); char* reportAxisLimitsMsg(uint8_t axis); char* reportAxisNameMsg(uint8_t axis); char* reportAxisNameMsg(uint8_t axis, uint8_t dual_axis); void reportTaskStackSize(UBaseType_t& saved); + +char* report_state_text(); +float* get_wco(); +void calc_mpos(float* print_position); +void calc_wpos(float* print_position); diff --git a/Grbl_Esp32/src/Serial.cpp b/Grbl_Esp32/src/Serial.cpp index c5d0220b..2489920f 100644 --- a/Grbl_Esp32/src/Serial.cpp +++ b/Grbl_Esp32/src/Serial.cpp @@ -57,15 +57,26 @@ #include "Grbl.h" +// Define this to use the Arduino serial (UART) driver instead +// of the one in Uart.cpp, which uses the ESP-IDF UART driver. +// This is for regression testing, and can be removed after +// testing is complete. +// #define REVERT_TO_ARDUINO_SERIAL + portMUX_TYPE myMutex = portMUX_INITIALIZER_UNLOCKED; -static TaskHandle_t serialCheckTaskHandle = 0; +static TaskHandle_t clientCheckTaskHandle = 0; WebUI::InputBuffer client_buffer[CLIENT_COUNT]; // create a buffer for each client // Returns the number of bytes available in a client buffer. -uint8_t serial_get_rx_buffer_available(uint8_t client) { - return client_buffer[client].availableforwrite(); +uint8_t client_get_rx_buffer_available(uint8_t client) { +#ifdef REVERT_TO_ARDUINO_SERIAL + return 128 - Serial.available(); +#else + return 128 - Uart0.available(); +#endif + // return client_buffer[client].availableforwrite(); } void heapCheckTask(void* pvParameters) { @@ -85,75 +96,84 @@ void heapCheckTask(void* pvParameters) { } } -void serial_init() { +void client_init() { #ifdef DEBUG_REPORT_HEAP_SIZE // For a 2000-word stack, uxTaskGetStackHighWaterMark reports 288 words available xTaskCreatePinnedToCore(heapCheckTask, "heapTask", 2000, NULL, 1, NULL, 1); #endif - Serial.begin(BAUD_RATE); - Serial.setRxBufferSize(256); - // reset all buffers - serial_reset_read_buffer(CLIENT_ALL); - grbl_send(CLIENT_SERIAL, "\r\n"); // create some white space after ESP32 boot info - serialCheckTaskHandle = 0; +#ifdef REVERT_TO_ARDUINO_SERIAL + Serial.begin(BAUD_RATE, SERIAL_8N1, 3, 1, false); + client_reset_read_buffer(CLIENT_ALL); + Serial.write("\r\n"); // create some white space after ESP32 boot info +#else + Uart0.setPins(1, 3); // Tx 1, Rx 3 - standard hardware pins + Uart0.begin(BAUD_RATE, Uart::Data::Bits8, Uart::Stop::Bits1, Uart::Parity::None); + + client_reset_read_buffer(CLIENT_ALL); + Uart0.write("\r\n"); // create some white space after ESP32 boot info +#endif + clientCheckTaskHandle = 0; // create a task to check for incoming data // For a 4096-word stack, uxTaskGetStackHighWaterMark reports 244 words available // after WebUI attaches. - xTaskCreatePinnedToCore(serialCheckTask, // task - "serialCheckTask", // name for task + xTaskCreatePinnedToCore(clientCheckTask, // task + "clientCheckTask", // name for task 4096, // size of task stack NULL, // parameters 1, // priority - &serialCheckTaskHandle, + &clientCheckTaskHandle, SUPPORT_TASK_CORE // must run the task on same core // core ); } -// this task runs and checks for data on all interfaces -// REaltime stuff is acted upon, then characters are added to the appropriate buffer -void serialCheckTask(void* pvParameters) { - uint8_t data = 0; - uint8_t client = CLIENT_ALL; // who sent the data - static UBaseType_t uxHighWaterMark = 0; - while (true) { // run continuously - while (any_client_has_data()) { - if (Serial.available()) { - client = CLIENT_SERIAL; - data = Serial.read(); - } else if (WebUI::inputBuffer.available()) { - client = CLIENT_INPUT; - data = WebUI::inputBuffer.read(); - } else { - //currently is wifi or BT but better to prepare both can be live +static uint8_t getClientChar(uint8_t* data) { + int res; +#ifdef REVERT_TO_ARDUINO_SERIAL + if (client_buffer[CLIENT_SERIAL].availableforwrite() && (res = Serial.read()) != -1) { +#else + if (client_buffer[CLIENT_SERIAL].availableforwrite() && (res = Uart0.read()) != -1) { +#endif + *data = res; + return CLIENT_SERIAL; + } + if (WebUI::inputBuffer.available()) { + *data = WebUI::inputBuffer.read(); + return CLIENT_INPUT; + } + //currently is wifi or BT but better to prepare both can be live #ifdef ENABLE_BLUETOOTH - if (WebUI::SerialBT.hasClient() && WebUI::SerialBT.available()) { - client = CLIENT_BT; - data = WebUI::SerialBT.read(); - - // Serial.write(data); // echo all data to serial. - } else { + if (WebUI::SerialBT.hasClient()) { + if ((res = WebUI::SerialBT.read()) != -1) { + *data = res; + return CLIENT_BT; + } + } #endif #if defined(ENABLE_WIFI) && defined(ENABLE_HTTP) && defined(ENABLE_SERIAL2SOCKET_IN) - if (WebUI::Serial2Socket.available()) { - client = CLIENT_WEBUI; - data = WebUI::Serial2Socket.read(); - } else { + if (WebUI::Serial2Socket.available()) { + *data = WebUI::Serial2Socket.read(); + return CLIENT_WEBUI; + } #endif #if defined(ENABLE_WIFI) && defined(ENABLE_TELNET) - if (WebUI::telnet_server.available()) { - client = CLIENT_TELNET; - data = WebUI::telnet_server.read(); - } + if (WebUI::telnet_server.available()) { + *data = WebUI::telnet_server.read(); + return CLIENT_TELNET; + } #endif -#if defined(ENABLE_WIFI) && defined(ENABLE_HTTP) && defined(ENABLE_SERIAL2SOCKET_IN) - } -#endif -#ifdef ENABLE_BLUETOOTH - } -#endif - } + return CLIENT_ALL; +} + +// this task runs and checks for data on all interfaces +// REaltime stuff is acted upon, then characters are added to the appropriate buffer +void clientCheckTask(void* pvParameters) { + uint8_t data = 0; + uint8_t client; // who sent the data + static UBaseType_t uxHighWaterMark = 0; + while (true) { // run continuously + while ((client = getClientChar(&data)) != CLIENT_ALL) { // Pick off realtime command characters directly from the serial stream. These characters are // not passed into the main buffer, but these set system state flag bits for realtime execution. if (is_realtime_command(data)) { @@ -161,7 +181,7 @@ void serialCheckTask(void* pvParameters) { } else { #if defined(ENABLE_SD_CARD) if (get_sd_state(false) < SDState::Busy) { -#endif //ENABLE_SD_CARD +#endif //ENABLE_SD_CARD vTaskEnterCritical(&myMutex); client_buffer[client].write(data); vTaskExitCritical(&myMutex); @@ -172,7 +192,7 @@ void serialCheckTask(void* pvParameters) { grbl_msg_sendf(client, MsgLevel::Info, "SD card job running"); } } -#endif //ENABLE_SD_CARD +#endif //ENABLE_SD_CARD } } // if something available WebUI::COMMANDS::handle(); @@ -194,7 +214,7 @@ void serialCheckTask(void* pvParameters) { } } -void serial_reset_read_buffer(uint8_t client) { +void client_reset_read_buffer(uint8_t client) { for (uint8_t client_num = 0; client_num < CLIENT_COUNT; client_num++) { if (client == client_num || client == CLIENT_ALL) { client_buffer[client_num].begin(); @@ -202,38 +222,12 @@ void serial_reset_read_buffer(uint8_t client) { } } -// Writes one byte to the TX serial buffer. Called by main program. -void serial_write(uint8_t data) { - Serial.write((char)data); -} - -// Fetches the first byte in the serial read buffer. Called by protocol loop. -uint8_t serial_read(uint8_t client) { - uint8_t data; +// Fetches the first byte in the client read buffer. Called by protocol loop. +int client_read(uint8_t client) { vTaskEnterCritical(&myMutex); - if (client_buffer[client].available()) { - data = client_buffer[client].read(); - vTaskExitCritical(&myMutex); - //Serial.write((char)data); - return data; - } else { - vTaskExitCritical(&myMutex); - return SERIAL_NO_DATA; - } -} - -bool any_client_has_data() { - return (Serial.available() || WebUI::inputBuffer.available() -#ifdef ENABLE_BLUETOOTH - || (WebUI::SerialBT.hasClient() && WebUI::SerialBT.available()) -#endif -#if defined(ENABLE_WIFI) && defined(ENABLE_HTTP) && defined(ENABLE_SERIAL2SOCKET_IN) - || WebUI::Serial2Socket.available() -#endif -#if defined(ENABLE_WIFI) && defined(ENABLE_TELNET) - || WebUI::telnet_server.available() -#endif - ); + int data = client_buffer[client].read(); + vTaskExitCritical(&myMutex); + return data; } // checks to see if a character is a realtime character @@ -249,6 +243,7 @@ bool is_realtime_command(uint8_t data) { void execute_realtime_command(Cmd command, uint8_t client) { switch (command) { case Cmd::Reset: + grbl_msg_sendf(CLIENT_ALL, MsgLevel::Debug, "Cmd::Reset"); mc_reset(); // Call motion control reset routine. break; case Cmd::StatusReport: @@ -350,3 +345,32 @@ void execute_realtime_command(Cmd command, uint8_t client) { break; } } + +void client_write(uint8_t client, const char* text) { + if (client == CLIENT_INPUT) { + return; + } +#ifdef ENABLE_BLUETOOTH + if (WebUI::SerialBT.hasClient() && (client == CLIENT_BT || client == CLIENT_ALL)) { + WebUI::SerialBT.print(text); + //delay(10); // possible fix for dropped characters + } +#endif +#if defined(ENABLE_WIFI) && defined(ENABLE_HTTP) && defined(ENABLE_SERIAL2SOCKET_OUT) + if (client == CLIENT_WEBUI || client == CLIENT_ALL) { + WebUI::Serial2Socket.write((const uint8_t*)text, strlen(text)); + } +#endif +#if defined(ENABLE_WIFI) && defined(ENABLE_TELNET) + if (client == CLIENT_TELNET || client == CLIENT_ALL) { + WebUI::telnet_server.write((const uint8_t*)text, strlen(text)); + } +#endif + if (client == CLIENT_SERIAL || client == CLIENT_ALL) { +#ifdef REVERT_TO_ARDUINO_SERIAL + Serial.write(text); +#else + Uart0.write(text); +#endif + } +} diff --git a/Grbl_Esp32/src/Serial.h b/Grbl_Esp32/src/Serial.h index 7cca0d94..ec290050 100644 --- a/Grbl_Esp32/src/Serial.h +++ b/Grbl_Esp32/src/Serial.h @@ -20,7 +20,7 @@ along with Grbl. If not, see . */ -#include "Grbl.h" +#include "stdint.h" #ifndef RX_BUFFER_SIZE # define RX_BUFFER_SIZE 256 @@ -33,24 +33,22 @@ # endif #endif -const float SERIAL_NO_DATA = 0xff; - // a task to read for incoming data from serial port -void serialCheckTask(void* pvParameters); +void clientCheckTask(void* pvParameters); + +void client_write(uint8_t client, const char* text); -void serial_write(uint8_t data); // Fetches the first byte in the serial read buffer. Called by main program. -uint8_t serial_read(uint8_t client); +int client_read(uint8_t client); // See if the character is an action command like feedhold or jogging. If so, do the action and return true uint8_t check_action_command(uint8_t data); -void serial_init(); -void serial_reset_read_buffer(uint8_t client); +void client_init(); +void client_reset_read_buffer(uint8_t client); // Returns the number of bytes available in the RX serial buffer. -uint8_t serial_get_rx_buffer_available(uint8_t client); +uint8_t client_get_rx_buffer_available(uint8_t client); void execute_realtime_command(Cmd command, uint8_t client); -bool any_client_has_data(); bool is_realtime_command(uint8_t data); diff --git a/Grbl_Esp32/src/SettingsDefinitions.cpp b/Grbl_Esp32/src/SettingsDefinitions.cpp index cb83afcc..b98da4e3 100644 --- a/Grbl_Esp32/src/SettingsDefinitions.cpp +++ b/Grbl_Esp32/src/SettingsDefinitions.cpp @@ -301,7 +301,7 @@ void make_settings() { } for (axis = MAX_N_AXIS - 1; axis >= 0; axis--) { def = &axis_defaults[axis]; - auto setting = new FloatSetting(GRBL, WG, makeGrblName(axis, 130), makename(def->name, "MaxTravel"), def->max_travel, 1.0, 100000.0); + auto setting = new FloatSetting(GRBL, WG, makeGrblName(axis, 130), makename(def->name, "MaxTravel"), def->max_travel, 0, 100000.0); setting->setAxis(axis); axis_settings[axis]->max_travel = setting; } diff --git a/Grbl_Esp32/src/Spindles/H2ASpindle.cpp b/Grbl_Esp32/src/Spindles/H2ASpindle.cpp index a62112fd..70c0db8f 100644 --- a/Grbl_Esp32/src/Spindles/H2ASpindle.cpp +++ b/Grbl_Esp32/src/Spindles/H2ASpindle.cpp @@ -28,17 +28,10 @@ managed to piece together. */ -#include - namespace Spindles { - void H2A::default_modbus_settings(uart_config_t& uart) { - // sets the uart to 19200 8E1 - VFD::default_modbus_settings(uart); - - uart.baud_rate = 19200; - uart.data_bits = UART_DATA_8_BITS; - uart.parity = UART_PARITY_EVEN; - uart.stop_bits = UART_STOP_BITS_1; + H2A::H2A() : VFD() { + _baudrate = 19200; + _parity = Uart::Parity::Even; } void H2A::direction_command(SpindleState mode, ModbusCommand& data) { diff --git a/Grbl_Esp32/src/Spindles/H2ASpindle.h b/Grbl_Esp32/src/Spindles/H2ASpindle.h index 8243867a..59dac1dc 100644 --- a/Grbl_Esp32/src/Spindles/H2ASpindle.h +++ b/Grbl_Esp32/src/Spindles/H2ASpindle.h @@ -24,8 +24,6 @@ namespace Spindles { class H2A : public VFD { protected: - void default_modbus_settings(uart_config_t& uart) override; - void direction_command(SpindleState mode, ModbusCommand& data) override; void set_speed_command(uint32_t rpm, ModbusCommand& data) override; @@ -36,5 +34,8 @@ namespace Spindles { bool supports_actual_rpm() const override { return true; } bool safety_polling() const override { return false; } + + public: + H2A(); }; } diff --git a/Grbl_Esp32/src/Spindles/HuanyangSpindle.cpp b/Grbl_Esp32/src/Spindles/HuanyangSpindle.cpp index 22fddb9d..cf2e6b80 100644 --- a/Grbl_Esp32/src/Spindles/HuanyangSpindle.cpp +++ b/Grbl_Esp32/src/Spindles/HuanyangSpindle.cpp @@ -149,15 +149,10 @@ If the frequency is -say- 25 Hz, Huanyang wants us to send 2500 (eg. 25.00 Hz). */ -#include - namespace Spindles { - void Huanyang::default_modbus_settings(uart_config_t& uart) { - // sets the uart to 9600 8N1 - VFD::default_modbus_settings(uart); - - // uart.baud_rate = 9600; - // Baud rate is set in the PD164 setting. + Huanyang::Huanyang() : VFD() { + // Baud rate is set in the PD164 setting. If it is not 9600, add, for example, + // _baudrate = 19200; } void Huanyang::direction_command(SpindleState mode, ModbusCommand& data) { diff --git a/Grbl_Esp32/src/Spindles/HuanyangSpindle.h b/Grbl_Esp32/src/Spindles/HuanyangSpindle.h index 9c6ee875..e3074366 100644 --- a/Grbl_Esp32/src/Spindles/HuanyangSpindle.h +++ b/Grbl_Esp32/src/Spindles/HuanyangSpindle.h @@ -35,8 +35,6 @@ namespace Spindles { void updateRPM(); - void default_modbus_settings(uart_config_t& uart) override; - void direction_command(SpindleState mode, ModbusCommand& data) override; void set_speed_command(uint32_t rpm, ModbusCommand& data) override; @@ -45,5 +43,8 @@ namespace Spindles { response_parser get_current_rpm(ModbusCommand& data) override; bool supports_actual_rpm() const override { return true; } + + public: + Huanyang(); }; } diff --git a/Grbl_Esp32/src/Spindles/Spindle.cpp b/Grbl_Esp32/src/Spindles/Spindle.cpp index b9cfed56..7dfd8f6b 100644 --- a/Grbl_Esp32/src/Spindles/Spindle.cpp +++ b/Grbl_Esp32/src/Spindles/Spindle.cpp @@ -38,6 +38,7 @@ #include "H2ASpindle.h" #include "BESCSpindle.h" #include "10vSpindle.h" +#include "YL620Spindle.h" namespace Spindles { // An instance of each type of spindle is created here. @@ -51,6 +52,7 @@ namespace Spindles { H2A h2a; BESC besc; _10v _10v; + YL620 yl620; void Spindle::select() { switch (static_cast(spindle_type->get())) { @@ -78,6 +80,9 @@ namespace Spindles { case SpindleType::H2A: spindle = &h2a; break; + case SpindleType::YL620: + spindle = &yl620; + break; case SpindleType::NONE: default: spindle = &null; diff --git a/Grbl_Esp32/src/Spindles/Spindle.h b/Grbl_Esp32/src/Spindles/Spindle.h index 2f507767..200e99cd 100644 --- a/Grbl_Esp32/src/Spindles/Spindle.h +++ b/Grbl_Esp32/src/Spindles/Spindle.h @@ -38,6 +38,7 @@ enum class SpindleType : int8_t { BESC, _10V, H2A, + YL620, }; #include "../Grbl.h" diff --git a/Grbl_Esp32/src/Spindles/VFDSpindle.cpp b/Grbl_Esp32/src/Spindles/VFDSpindle.cpp index 956f3c6a..463a0933 100644 --- a/Grbl_Esp32/src/Spindles/VFDSpindle.cpp +++ b/Grbl_Esp32/src/Spindles/VFDSpindle.cpp @@ -42,11 +42,12 @@ // be plenty: assuming 9600 8N1, that's roughly 250 chars. A message of 2x16 chars with 4x4 // chars buffering is just 40 chars. -const uart_port_t VFD_RS485_UART_PORT = UART_NUM_2; // hard coded for this port right now -const int VFD_RS485_BUF_SIZE = 127; -const int VFD_RS485_QUEUE_SIZE = 10; // numv\ber of commands that can be queued up. -const int RESPONSE_WAIT_MILLIS = 1000; // how long to wait for a response in milliseconds -const int VFD_RS485_POLL_RATE = 250; // in milliseconds between commands +const int VFD_RS485_UART_PORT = 2; // hard coded for this port right now +const int VFD_RS485_BUF_SIZE = 127; +const int VFD_RS485_QUEUE_SIZE = 10; // numv\ber of commands that can be queued up. +const int RESPONSE_WAIT_MILLIS = 1000; // how long to wait for a response in milliseconds +const int VFD_RS485_POLL_RATE = 250; // in milliseconds between commands +const TickType_t response_ticks = RESPONSE_WAIT_MILLIS / portTICK_PERIOD_MS; // in milliseconds between commands // OK to change these // #define them in your machine definition file if you want different values @@ -55,9 +56,48 @@ const int VFD_RS485_POLL_RATE = 250; // in milliseconds between comma #endif namespace Spindles { + Uart _uart(VFD_RS485_UART_PORT); QueueHandle_t VFD::vfd_cmd_queue = nullptr; TaskHandle_t VFD::vfd_cmdTaskHandle = nullptr; + VFD::VFD() : + _txd_pin( +#ifdef VFD_RS485_TXD_PIN + VFD_RS485_TXD_PIN +#else + -1 +#endif + ), + _rxd_pin( +#ifdef VFD_RS485_RXD_PIN + VFD_RS485_RXD_PIN +#else + -1 +#endif + ), + _rts_pin( +#ifdef VFD_RS485_RTS_PIN + VFD_RS485_RTS_PIN +#else + -1 +#endif + ), + _baudrate( +#ifdef VFD_RS485_BAUD_RATE + VFD_RS485_BAUD_RATE +#else + 9600 +#endif + ), + _dataBits(Uart::Data::Bits8), _stopBits(Uart::Stop::Bits1), _parity( +#ifdef VFD_RS485_PARITY + VFD_RS485_PARITY +#else + Uart::Parity::None +#endif + ) { + } + // The communications task void VFD::vfd_cmd_task(void* pvParameters) { static bool unresponsive = false; // to pop off a message once each time it becomes unresponsive @@ -145,16 +185,15 @@ namespace Spindles { int retry_count = 0; for (; retry_count < MAX_RETRIES; ++retry_count) { // Flush the UART: - uart_flush(VFD_RS485_UART_PORT); + _uart.flush(); // Write the data: - uart_write_bytes(VFD_RS485_UART_PORT, reinterpret_cast(next_cmd.msg), next_cmd.tx_length); - uart_wait_tx_done(VFD_RS485_UART_PORT, RESPONSE_WAIT_MILLIS / portTICK_PERIOD_MS); + _uart.write(reinterpret_cast(next_cmd.msg), next_cmd.tx_length); + _uart.flushTxTimed(response_ticks); // Read the response - uint16_t read_length = 0; - uint16_t current_read = - uart_read_bytes(VFD_RS485_UART_PORT, rx_message, next_cmd.rx_length, RESPONSE_WAIT_MILLIS / portTICK_PERIOD_MS); + uint16_t read_length = 0; + uint16_t current_read = _uart.readBytes(rx_message, next_cmd.rx_length, response_ticks); read_length += current_read; // Apparently some Huanyang report modbus errors in the correct way, and the rest not. Sigh. @@ -165,10 +204,7 @@ namespace Spindles { while (read_length < next_cmd.rx_length && current_read > 0) { // Try to read more; we're not there yet... - current_read = uart_read_bytes(VFD_RS485_UART_PORT, - rx_message + read_length, - next_cmd.rx_length - read_length, - RESPONSE_WAIT_MILLIS / portTICK_PERIOD_MS); + current_read = _uart.readBytes(rx_message + read_length, next_cmd.rx_length - read_length, response_ticks); read_length += current_read; } if (current_read < 0) { @@ -257,13 +293,6 @@ namespace Spindles { } // ================== Class methods ================================== - void VFD::default_modbus_settings(uart_config_t& uart) { - // Default is 9600 8N1, which is sane for most VFD's: - uart.baud_rate = 9600; - uart.data_bits = UART_DATA_8_BITS; - uart.parity = UART_PARITY_DISABLE; - uart.stop_bits = UART_STOP_BITS_1; - } void VFD::init() { vfd_ok = false; // initialize @@ -281,38 +310,16 @@ namespace Spindles { // this allows us to init() again later. // If you change certain settings, init() gets called agian - uart_driver_delete(VFD_RS485_UART_PORT); + // uart_driver_delete(VFD_RS485_UART_PORT); - uart_config_t uart_config; - default_modbus_settings(uart_config); - - // Overwrite with user defined defines: -#ifdef VFD_RS485_BAUD_RATE - uart_config.baud_rate = VFD_RS485_BAUD_RATE; -#endif -#ifdef VFD_RS485_PARITY - uart_config.parity = VFD_RS485_PARITY; -#endif - - uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE; - uart_config.rx_flow_ctrl_thresh = 122; - - if (uart_param_config(VFD_RS485_UART_PORT, &uart_config) != ESP_OK) { - grbl_msg_sendf(CLIENT_SERIAL, MsgLevel::Info, "RS485 VFD uart parameters failed"); - return; - } - - if (uart_set_pin(VFD_RS485_UART_PORT, _txd_pin, _rxd_pin, _rts_pin, UART_PIN_NO_CHANGE) != ESP_OK) { + if (_uart.setPins(_txd_pin, _rxd_pin, _rts_pin)) { grbl_msg_sendf(CLIENT_SERIAL, MsgLevel::Info, "RS485 VFD uart pin config failed"); return; } - if (uart_driver_install(VFD_RS485_UART_PORT, VFD_RS485_BUF_SIZE * 2, 0, 0, NULL, 0) != ESP_OK) { - grbl_msg_sendf(CLIENT_SERIAL, MsgLevel::Info, "RS485 VFD uart driver install failed"); - return; - } + _uart.begin(_baudrate, _dataBits, _stopBits, _parity); - if (uart_set_mode(VFD_RS485_UART_PORT, UART_MODE_RS485_HALF_DUPLEX) != ESP_OK) { + if (_uart.setHalfDuplex()) { grbl_msg_sendf(CLIENT_SERIAL, MsgLevel::Info, "RS485 VFD uart set half duplex failed"); return; } @@ -349,26 +356,20 @@ namespace Spindles { bool VFD::get_pins_and_settings() { bool pins_settings_ok = true; -#ifdef VFD_RS485_TXD_PIN - _txd_pin = VFD_RS485_TXD_PIN; -#else - grbl_msg_sendf(CLIENT_SERIAL, MsgLevel::Info, "Undefined VFD_RS485_TXD_PIN"); - pins_settings_ok = false; -#endif + if (_txd_pin == -1) { + grbl_msg_sendf(CLIENT_SERIAL, MsgLevel::Info, "Undefined VFD_RS485_TXD_PIN"); + pins_settings_ok = false; + } -#ifdef VFD_RS485_RXD_PIN - _rxd_pin = VFD_RS485_RXD_PIN; -#else - grbl_msg_sendf(CLIENT_SERIAL, MsgLevel::Info, "Undefined VFD_RS485_RXD_PIN"); - pins_settings_ok = false; -#endif + if (_rxd_pin == -1) { + grbl_msg_sendf(CLIENT_SERIAL, MsgLevel::Info, "Undefined VFD_RS485_RXD_PIN"); + pins_settings_ok = false; + } -#ifdef VFD_RS485_RTS_PIN - _rts_pin = VFD_RS485_RTS_PIN; -#else - grbl_msg_sendf(CLIENT_SERIAL, MsgLevel::Info, "Undefined VFD_RS485_RTS_PIN"); - pins_settings_ok = false; -#endif + if (_rts_pin == -1) { + grbl_msg_sendf(CLIENT_SERIAL, MsgLevel::Info, "Undefined VFD_RS485_RTS_PIN"); + pins_settings_ok = false; + } if (laser_mode->get()) { grbl_msg_sendf(CLIENT_SERIAL, MsgLevel::Info, "VFD spindle disabled in laser mode. Set $GCode/LaserMode=Off and restart"); @@ -564,9 +565,12 @@ namespace Spindles { ModbusCommand rpm_cmd; rpm_cmd.msg[0] = VFD_RS485_ADDR; - set_speed_command(rpm, rpm_cmd); + // Sometimes sync_rpm is retained between different set_speed_command's. We don't want that - we want + // spindle sync to kick in after we set the speed. This forces that. + _sync_rpm = UINT32_MAX; + rpm_cmd.critical = (rpm == 0); if (xQueueSend(vfd_cmd_queue, &rpm_cmd, 0) != pdTRUE) { @@ -576,7 +580,12 @@ namespace Spindles { return rpm; } - void VFD::stop() { set_mode(SpindleState::Disable, true); } + void VFD::stop() { +#ifdef VFD_DEBUG_MODE + grbl_msg_sendf(CLIENT_SERIAL, MsgLevel::Debug, "VFD::stop()"); +#endif + set_mode(SpindleState::Disable, true); + } // state is cached rather than read right now to prevent delays SpindleState VFD::get_state() { return _current_state; } diff --git a/Grbl_Esp32/src/Spindles/VFDSpindle.h b/Grbl_Esp32/src/Spindles/VFDSpindle.h index 2661d246..89035a79 100644 --- a/Grbl_Esp32/src/Spindles/VFDSpindle.h +++ b/Grbl_Esp32/src/Spindles/VFDSpindle.h @@ -20,11 +20,12 @@ */ #include "Spindle.h" -#include +#include "../Uart.h" // #define VFD_DEBUG_MODE namespace Spindles { + extern Uart _uart; class VFD : public Spindle { private: @@ -34,9 +35,9 @@ namespace Spindles { bool set_mode(SpindleState mode, bool critical); bool get_pins_and_settings(); - uint8_t _txd_pin; - uint8_t _rxd_pin; - uint8_t _rts_pin; + int _txd_pin; + int _rxd_pin; + int _rts_pin; uint32_t _current_rpm = 0; bool _task_running = false; @@ -57,8 +58,6 @@ namespace Spindles { uint8_t msg[VFD_RS485_MAX_MSG_SIZE]; }; - virtual void default_modbus_settings(uart_config_t& uart); - // Commands: virtual void direction_command(SpindleState mode, ModbusCommand& data) = 0; virtual void set_speed_command(uint32_t rpm, ModbusCommand& data) = 0; @@ -73,8 +72,14 @@ namespace Spindles { virtual bool supports_actual_rpm() const { return false; } virtual bool safety_polling() const { return true; } + // The constructor sets these + int _baudrate; + Uart::Data _dataBits; + Uart::Stop _stopBits; + Uart::Parity _parity; + public: - VFD() = default; + VFD(); VFD(const VFD&) = delete; VFD(VFD&&) = delete; VFD& operator=(const VFD&) = delete; diff --git a/Grbl_Esp32/src/Spindles/YL620Spindle.cpp b/Grbl_Esp32/src/Spindles/YL620Spindle.cpp new file mode 100644 index 00000000..68383cad --- /dev/null +++ b/Grbl_Esp32/src/Spindles/YL620Spindle.cpp @@ -0,0 +1,232 @@ +#include "YL620Spindle.h" + +/* + YL620Spindle.cpp + + This is for a Yalang YL620/YL620-A VFD based spindle to be controlled via RS485 Modbus RTU. + + Part of Grbl_ESP32 + 2021 - Marco Wagner + + Grbl is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + Grbl is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with Grbl. If not, see . + + WARNING!!!! + VFDs are very dangerous. They have high voltages and are very powerful + Remove power before changing bits. + + ============================================================================================================= + + Configuration required for the YL620 + + Parameter number Description Value + ------------------------------------------------------------------------------- + P00.00 Main frequency 400.00Hz (match to your spindle) + P00.01 Command source 3 + + P03.00 RS485 Baud rate 3 (9600) + P03.01 RS485 address 1 + P03.02 RS485 protocol 2 + P03.08 Frequency given lower limit 100.0Hz (match to your spindle cooling-type) + + =============================================================================================================== + + RS485 communication is standard Modbus RTU + + Therefore, the following operation codes are relevant: + 0x03: read single holding register + 0x06: write single holding register + + Holding register address Description + --------------------------------------------------------------------------- + 0x0000 main frequency + 0x0308 frequency given lower limit + + 0x2000 command register (further information below) + 0x2001 Modbus485 frequency command (x0.1Hz => 2500 = 250.0Hz) + + 0x200A Target frequency + 0x200B Output frequency + 0x200C Output current + + + Command register at holding address 0x2000 + -------------------------------------------------------------------------- + bit 1:0 b00: No function + b01: shutdown command + b10: start command + b11: Jog command + bit 3:2 reserved + bit 5:4 b00: No function + b01: Forward command + b10: Reverse command + b11: change direction + bit 7:6 b00: No function + b01: reset an error flag + b10: reset all error flags + b11: reserved +*/ + +namespace Spindles { + YL620::YL620() : VFD() {} + + void YL620::direction_command(SpindleState mode, ModbusCommand& data) { + // NOTE: data length is excluding the CRC16 checksum. + data.tx_length = 6; + data.rx_length = 6; + + // data.msg[0] is omitted (modbus address is filled in later) + data.msg[1] = 0x06; // 06: write output register + data.msg[2] = 0x20; // 0x2000: command register address + data.msg[3] = 0x00; + + data.msg[4] = 0x00; // High-Byte of command always 0x00 + switch (mode) { + case SpindleState::Cw: + data.msg[5] = 0x12; // Start in forward direction + break; + case SpindleState::Ccw: + data.msg[5] = 0x22; // Start in reverse direction + break; + default: // SpindleState::Disable + data.msg[5] = 0x01; // Disable spindle + break; + } + } + + void YL620::set_speed_command(uint32_t rpm, ModbusCommand& data) { + // NOTE: data length is excluding the CRC16 checksum. + data.tx_length = 6; + data.rx_length = 6; + + // We have to know the max RPM before we can set the current RPM: + auto max_rpm = this->_max_rpm; + auto max_frequency = this->_maxFrequency; + + uint16_t freqFromRPM = (uint16_t(rpm) * uint16_t(max_frequency)) / uint16_t(max_rpm); + +#ifdef VFD_DEBUG_MODE + grbl_msg_sendf(CLIENT_SERIAL, MsgLevel::Info, "For %d RPM the output frequency is set to %d Hz*10", int(rpm), int(freqFromRPM)); +#endif + + data.msg[1] = 0x06; + data.msg[2] = 0x20; + data.msg[3] = 0x01; + data.msg[4] = uint8_t(freqFromRPM >> 8); + data.msg[5] = uint8_t(freqFromRPM & 0xFF); + } + + VFD::response_parser YL620::initialization_sequence(int index, ModbusCommand& data) { + if (index == -1) { + // NOTE: data length is excluding the CRC16 checksum. + data.tx_length = 6; + data.rx_length = 5; + + data.msg[1] = 0x03; + data.msg[2] = 0x03; + data.msg[3] = 0x08; + data.msg[4] = 0x00; + data.msg[5] = 0x01; + + // Recv: 01 03 02 03 E8 xx xx + // -- -- = 1000 + return [](const uint8_t* response, Spindles::VFD* vfd) -> bool { + auto yl620 = static_cast(vfd); + yl620->_minFrequency = (uint16_t(response[3]) << 8) | uint16_t(response[4]); + +#ifdef VFD_DEBUG_MODE + grbl_msg_sendf(CLIENT_SERIAL, MsgLevel::Info, "YL620 allows minimum frequency of %d Hz", int(yl620->_minFrequency)); +#endif + + return true; + }; + } else if (index == -2) { + // NOTE: data length is excluding the CRC16 checksum. + data.tx_length = 6; + data.rx_length = 5; + + data.msg[1] = 0x03; + data.msg[2] = 0x00; + data.msg[3] = 0x00; + data.msg[4] = 0x00; + data.msg[5] = 0x01; + + // Recv: 01 03 02 0F A0 xx xx + // -- -- = 4000 + return [](const uint8_t* response, Spindles::VFD* vfd) -> bool { + auto yl620 = static_cast(vfd); + yl620->_maxFrequency = (uint16_t(response[3]) << 8) | uint16_t(response[4]); + + vfd->_min_rpm = uint32_t(yl620->_minFrequency) * uint32_t(vfd->_max_rpm) / + uint32_t(yl620->_maxFrequency); // 1000 * 24000 / 4000 = 6000 RPM. + +#ifdef VFD_DEBUG_MODE + grbl_msg_sendf(CLIENT_SERIAL, MsgLevel::Info, "YL620 allows maximum frequency of %d Hz", int(yl620->_maxFrequency)); + grbl_msg_sendf(CLIENT_SERIAL, + MsgLevel::Info, + "Configured maxRPM of %d RPM results in minRPM of %d RPM", + int(vfd->_max_rpm), + int(vfd->_min_rpm)); +#endif + + return true; + }; + } else { + return nullptr; + } + } + + VFD::response_parser YL620::get_current_rpm(ModbusCommand& data) { + // NOTE: data length is excluding the CRC16 checksum. + data.tx_length = 6; + data.rx_length = 5; + + // Send: 01 03 200B 0001 + data.msg[1] = 0x03; + data.msg[2] = 0x20; + data.msg[3] = 0x0B; + data.msg[4] = 0x00; + data.msg[5] = 0x01; + + // Recv: 01 03 02 05 DC xx xx + // ---- = 1500 + return [](const uint8_t* response, Spindles::VFD* vfd) -> bool { + uint16_t freq = (uint16_t(response[3]) << 8) | uint16_t(response[4]); + + auto yl620 = static_cast(vfd); + + uint16_t rpm = freq * uint16_t(vfd->_max_rpm) / uint16_t(yl620->_maxFrequency); + + // Set current RPM value? Somewhere? + vfd->_sync_rpm = rpm; + return true; + }; + } + + VFD::response_parser YL620::get_current_direction(ModbusCommand& data) { + // NOTE: data length is excluding the CRC16 checksum. + data.tx_length = 6; + data.rx_length = 5; + + // Send: 01 03 20 00 00 01 + data.msg[1] = 0x03; + data.msg[2] = 0x20; + data.msg[3] = 0x00; + data.msg[4] = 0x00; + data.msg[5] = 0x01; + + // Receive: 01 03 02 00 0A xx xx + // ----- status is in 00 0A bit 5:4 + + // TODO: What are we going to do with this? Update sys.spindle_speed? Update vfd state? + return [](const uint8_t* response, Spindles::VFD* vfd) -> bool { return true; }; + } +} diff --git a/Grbl_Esp32/src/Spindles/YL620Spindle.h b/Grbl_Esp32/src/Spindles/YL620Spindle.h new file mode 100644 index 00000000..d2d2e10b --- /dev/null +++ b/Grbl_Esp32/src/Spindles/YL620Spindle.h @@ -0,0 +1,44 @@ +#pragma once + +#include "VFDSpindle.h" + +/* + YL620Spindle.h + + Part of Grbl_ESP32 + 2021 - Marco Wagner + + Grbl is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + Grbl is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with Grbl. If not, see . + +*/ + +namespace Spindles { + class YL620 : public VFD { + protected: + uint16_t _minFrequency = 0; // frequency lower limit. Factor 10 of actual frequency + uint16_t _maxFrequency = 4000; // max frequency the VFD will allow. Normally 400.0. Factor 10 of actual frequency + + void direction_command(SpindleState mode, ModbusCommand& data) override; + void set_speed_command(uint32_t rpm, ModbusCommand& data) override; + + response_parser initialization_sequence(int index, ModbusCommand& data) override; + response_parser get_current_rpm(ModbusCommand& data) override; + response_parser get_current_direction(ModbusCommand& data) override; + response_parser get_status_ok(ModbusCommand& data) override { return nullptr; } + + bool supports_actual_rpm() const override { return true; } + bool safety_polling() const override { return false; } + + public: + YL620(); + }; +} diff --git a/Grbl_Esp32/src/System.cpp b/Grbl_Esp32/src/System.cpp index 60a399b2..35e8837e 100644 --- a/Grbl_Esp32/src/System.cpp +++ b/Grbl_Esp32/src/System.cpp @@ -30,6 +30,7 @@ volatile ExecState sys_rt_exec_state; // Global realtime executor bitflag v volatile ExecAlarm sys_rt_exec_alarm; // Global realtime executor bitflag variable for setting various alarms. volatile ExecAccessory sys_rt_exec_accessory_override; // Global realtime executor bitflag variable for spindle/coolant overrides. volatile bool cycle_stop; // For state transitions, instead of bitflag +volatile void* sys_pl_data_inflight; // holds a plan_line_data_t while inverse_kinematics has taken ownership of a line motion #ifdef DEBUG volatile bool sys_rt_exec_debug; #endif diff --git a/Grbl_Esp32/src/System.h b/Grbl_Esp32/src/System.h index d955f01e..3fb78809 100644 --- a/Grbl_Esp32/src/System.h +++ b/Grbl_Esp32/src/System.h @@ -138,6 +138,7 @@ extern volatile Percent sys_rt_f_override; // Feed override extern volatile Percent sys_rt_r_override; // Rapid feed override value in percent extern volatile Percent sys_rt_s_override; // Spindle override value in percent extern volatile bool cycle_stop; +extern volatile void* sys_pl_data_inflight; // holds a plan_line_data_t while inverse_kinematics has taken ownership of a line motion #ifdef DEBUG extern volatile bool sys_rt_exec_debug; #endif diff --git a/Grbl_Esp32/src/Uart.cpp b/Grbl_Esp32/src/Uart.cpp new file mode 100644 index 00000000..7bb08382 --- /dev/null +++ b/Grbl_Esp32/src/Uart.cpp @@ -0,0 +1,94 @@ +/* + * UART driver that accesses the ESP32 hardware FIFOs directly. + */ + +#include "Grbl.h" + +#include "esp_system.h" +#include "soc/uart_reg.h" +#include "soc/io_mux_reg.h" +#include "soc/gpio_sig_map.h" +#include "soc/dport_reg.h" +#include "soc/rtc.h" + +Uart::Uart(int uart_num) : _uart_num(uart_port_t(uart_num)), _pushback(-1) {} + +void Uart::begin(unsigned long baudrate, Data dataBits, Stop stopBits, Parity parity) { + // uart_driver_delete(_uart_num); + uart_config_t conf; + conf.baud_rate = baudrate; + conf.data_bits = uart_word_length_t(dataBits); + conf.parity = uart_parity_t(parity); + conf.stop_bits = uart_stop_bits_t(stopBits); + conf.flow_ctrl = UART_HW_FLOWCTRL_DISABLE; + conf.rx_flow_ctrl_thresh = 0; + conf.use_ref_tick = false; + if (uart_param_config(_uart_num, &conf) != ESP_OK) { + return; + }; + uart_driver_install(_uart_num, 256, 0, 0, NULL, 0); +} + +int Uart::available() { + size_t size = 0; + uart_get_buffered_data_len(_uart_num, &size); + return size + (_pushback >= 0); +} + +int Uart::peek() { + _pushback = read(); + return _pushback; +} + +int Uart::read(TickType_t timeout) { + if (_pushback >= 0) { + int ret = _pushback; + _pushback = -1; + return ret; + } + uint8_t c; + int res = uart_read_bytes(_uart_num, &c, 1, timeout); + return res != 1 ? -1 : c; +} +int Uart::read() { + return read(0); +} + +size_t Uart::readBytes(char* buffer, size_t length, TickType_t timeout) { + bool pushback = _pushback >= 0; + if (pushback && length) { + *buffer++ = _pushback; + _pushback = -1; + --length; + } + int res = uart_read_bytes(_uart_num, (uint8_t*)buffer, length, timeout); + // The Stream class version of readBytes never returns -1, + // so if uart_read_bytes returns -1, we change that to 0 + return pushback + (res >= 0 ? res : 0); +} +size_t Uart::readBytes(char* buffer, size_t length) { + return readBytes(buffer, length, (TickType_t)0); +} +size_t Uart::write(uint8_t c) { + return uart_write_bytes(_uart_num, (char*)&c, 1); +} + +size_t Uart::write(const uint8_t* buffer, size_t length) { + return uart_write_bytes(_uart_num, (const char*)buffer, length); +} + +size_t Uart::write(const char* text) { + return uart_write_bytes(_uart_num, text, strlen(text)); +} + +bool Uart::setHalfDuplex() { + return uart_set_mode(_uart_num, UART_MODE_RS485_HALF_DUPLEX) != ESP_OK; +} +bool Uart::setPins(int tx_pin, int rx_pin, int rts_pin, int cts_pin) { + return uart_set_pin(_uart_num, tx_pin, rx_pin, rts_pin, cts_pin) != ESP_OK; +} +bool Uart::flushTxTimed(TickType_t ticks) { + return uart_wait_tx_done(_uart_num, ticks) != ESP_OK; +} + +Uart Uart0(0); diff --git a/Grbl_Esp32/src/Uart.h b/Grbl_Esp32/src/Uart.h new file mode 100644 index 00000000..e942b161 --- /dev/null +++ b/Grbl_Esp32/src/Uart.h @@ -0,0 +1,49 @@ +#pragma once + +#include + +class Uart : public Stream { +private: + uart_port_t _uart_num; + int _pushback; + +public: + enum class Data : int { + Bits5 = UART_DATA_5_BITS, + Bits6 = UART_DATA_6_BITS, + Bits7 = UART_DATA_7_BITS, + Bits8 = UART_DATA_8_BITS, + }; + + enum class Stop : int { + Bits1 = UART_STOP_BITS_1, + Bits1_5 = UART_STOP_BITS_1_5, + Bits2 = UART_STOP_BITS_2, + }; + + enum class Parity : int { + None = UART_PARITY_DISABLE, + Even = UART_PARITY_EVEN, + Odd = UART_PARITY_ODD, + }; + + Uart(int uart_num); + bool setHalfDuplex(); + bool setPins(int tx_pin, int rx_pin, int rts_pin = -1, int cts_pin = -1); + void begin(unsigned long baud, Data dataBits, Stop stopBits, Parity parity); + int available(void) override; + int read(void) override; + int read(TickType_t timeout); + size_t readBytes(char* buffer, size_t length, TickType_t timeout); + size_t readBytes(uint8_t* buffer, size_t length, TickType_t timeout) { return readBytes((char*)buffer, length, timeout); } + size_t readBytes(char* buffer, size_t length) override; + int peek(void) override; + size_t write(uint8_t data); + size_t write(const uint8_t* buffer, size_t length); + inline size_t write(const char* buffer, size_t size) { return write((uint8_t*)buffer, size); } + size_t write(const char* text); + void flush() { uart_flush(_uart_num); } + bool flushTxTimed(TickType_t ticks); +}; + +extern Uart Uart0; diff --git a/platformio.ini b/platformio.ini index cdd2e8a0..ddf3a854 100644 --- a/platformio.ini +++ b/platformio.ini @@ -1,12 +1,22 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + [platformio] -src_dir=Grbl_Esp32 -lib_dir=libraries -data_dir=Grbl_Esp32/data -default_envs=release +src_dir = Grbl_Esp32 +lib_dir = libraries +data_dir = Grbl_Esp32/data +default_envs = release ;extra_configs=debug.ini [common_env_data] -lib_deps_builtin = +lib_deps_builtin = ArduinoOTA BluetoothSerial DNSServer @@ -23,37 +33,40 @@ lib_deps_builtin = WiFiClientSecure [common] -build_flags = - ;-DMACHINE_FILENAME=test_drive.h ;Remove ";" from the beginning of this line and specify the machine file +build_flags = + ;-DMACHINE_FILENAME=test_drive.h ;Remove ";" from the beginning of this line and specify the machine file -DCORE_DEBUG_LEVEL=0 -Wno-unused-variable -Wno-unused-function - ;-DDEBUG_REPORT_HEAP_SIZE - ;-DDEBUG_REPORT_STACK_FREE [env] -lib_deps = - TMCStepper@>=0.7.0,<1.0.0 -platform = espressif32 +;lib_deps = +; TMCStepper@>=0.7.0,<1.0.0 +platform = espressif32@3.0.0 ; temporary fix for lost uart rx characters board = esp32dev framework = arduino upload_speed = 921600 board_build.partitions = min_spiffs.csv monitor_speed = 115200 -monitor_flags = +monitor_flags = --eol=CRLF --echo --filter=esp32_exception_decoder board_build.f_cpu = 240000000L -; set frequency to 80MHz board_build.f_flash = 80000000L board_build.flash_mode = qio build_flags = ${common.build_flags} -src_filter = - +<*.h> +<*.s> +<*.S> +<*.cpp> +<*.c> +<*.ino> + - -<.git/> - - - +src_filter = + +<*.h> +<*.s> +<*.S> +<*.cpp> +<*.c> +<*.ino> + + -<.git/> - - - [env:release] +lib_deps = + TMCStepper@>=0.7.0,<1.0.0 + ESP8266 and ESP32 OLED driver for SSD1306 displays@^4.2.0 [env:debug] build_type = debug +lib_deps = + TMCStepper@>=0.7.0,<1.0.0 + ESP8266 and ESP32 OLED driver for SSD1306 displays@^4.2.0