diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..97d41b66 --- /dev/null +++ b/.clang-format @@ -0,0 +1,92 @@ +--- +AccessModifierOffset: '-4' +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: 'true' +AlignConsecutiveDeclarations: 'true' +AlignEscapedNewlines: Right +AlignOperands: 'true' +AlignTrailingComments: 'true' +AllowShortBlocksOnASingleLine: 'true' +AllowShortCaseLabelsOnASingleLine: 'true' +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: 'false' +AllowShortLoopsOnASingleLine: 'false' +AlwaysBreakBeforeMultilineStrings: 'false' +AlwaysBreakTemplateDeclarations: 'true' +BinPackArguments: 'false' +BinPackParameters: 'false' +BreakBeforeBinaryOperators: None +BraceWrapping: + AfterClass: 'true' + AfterControlStatement: 'true' + AfterEnum: 'true' + AfterFunction: 'true' + AfterNamespace: 'true' + AfterObjCDeclaration: 'true' + AfterStruct: 'true' + AfterUnion: 'true' + AfterExternBlock: 'true' + BeforeCatch: 'true' + BeforeElse: 'true' + SplitEmptyFunction: 'false' + SplitEmptyRecord: 'false' + SplitEmptyNamespace: 'false' +BreakBeforeInheritanceComma: 'true' +BreakBeforeTernaryOperators: 'false' +BreakConstructorInitializers: AfterColon +BreakInheritanceList: AfterColon +ColumnLimit: '140' +CommentPragmas: '^ :: ' +CompactNamespaces: 'false' +Cpp11BracedListStyle: 'false' +FixNamespaceComments: 'false' +IncludeCategories: + - Regex: '^".*' + Priority: 1 + - Regex: '^"(.*)/' + Priority: 2 + - Regex: '^<(.*)/' + Priority: 3 + - Regex: '.*' + Priority: 4 +IncludeBlocks: Regroup +IndentCaseLabels: 'true' +IndentPPDirectives: AfterHash +IndentWidth: '4' +IndentWrappedFunctionNames: 'true' +JavaScriptQuotes: Leave +KeepEmptyLinesAtTheStartOfBlocks: 'false' +Language: Cpp +MaxEmptyLinesToKeep: '1' +NamespaceIndentation: All +PenaltyBreakBeforeFirstCallParameter: 7 +PenaltyBreakAssignment: 8 +PenaltyBreakComment: 200 +PenaltyBreakFirstLessLess: 50 +PenaltyBreakString: 100 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 10 +PenaltyReturnTypeOnItsOwnLine: 1000000 +PointerAlignment: Left +ReflowComments: 'false' +SortIncludes: 'false' +SortUsingDeclarations: 'true' +SpaceAfterTemplateKeyword: 'true' +SpaceBeforeAssignmentOperators: 'true' +SpaceBeforeCpp11BracedList: 'true' +SpaceBeforeCtorInitializerColon: 'true' +SpaceBeforeInheritanceColon: 'true' +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: 'true' +SpaceInEmptyParentheses: 'false' +SpacesBeforeTrailingComments: '2' +SpacesInAngles: 'false' +SpacesInCStyleCastParentheses: 'false' +SpacesInContainerLiterals: 'false' +SpacesInParentheses: 'false' +SpacesInSquareBrackets: 'false' +Standard: Cpp11 +TabWidth: '4' +UseTab: Never + +... diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..6553990d --- /dev/null +++ b/.editorconfig @@ -0,0 +1,4 @@ +root = true + +[*] +insert_final_newline = true \ No newline at end of file diff --git a/CodingStyle.md b/CodingStyle.md new file mode 100644 index 00000000..a73dde4a --- /dev/null +++ b/CodingStyle.md @@ -0,0 +1,80 @@ +# Coding style guidelines + +While most software developers have a strong opinion about coding +style and conventions, the general agreement is that having a single +coding style in your project is beneficial for the readability and +maintainability. + +Coding style of this project is enforced through `.clang-format`. +Most IDE's nowadays pick up the clang-format file automatically. If +this is not the case, please run it manually before committing code. + +Note that different IDE's are compiled against different versions of +clang-format. This clang-format file is created in such a way, that +it should work with most IDE's out-of-the-box, and can apply changes +when the file is saved. + +There may be violations of these guidelines in the code, due to +historical reasons or, in rare instances, other considerations. +We intend to fix such violations; patches that correct them +are most welcome - but should be tested carefully across the +supported compilation environments (Arduino and platformio). + +## Guidelines + +A few guidelines need to be taken into consideration while using +clang-format: + +1. Include order and `".."` vs `<...>` matters. Clang-format + automatically re-orders your include files. This configuration + is created in such a way that header file includes always add + the minimum number of implicit dependencies. If this generates + problems, you should be fixing your includes, instead of disabling + clang-format. +2. Preprocessor commands are not formatted nicely in some cases. + This can hurt readibility in rare cases, in which case it's + okay to disable clang-format temporarily with + `// clang-format off` and `// clang-format on`. Most notably, + machine definitions should have clang-format off. +3. Use `#pragma once` in all header files. The reason is that + preprocessor `#ifdef`s are nested automatically, which making + things messy when using the alternative. + +## Classes and namespaces + +Filenames should correspond with clas names, folder names should +correspond with namespace names. This implies that a file should +have a single class. + +## Naming + +- Class names and namespace names are named `CamelCase`. Note that + class and namespace names should only start with an `_` if they are + (A) not in the global namespace and (b) should otherwise start with a digit. + For example `_10V`. +- Class member functions should be `snake_case` +- Class member variables should be `_snake_case` with a leading `_`. + +Namespaces should have classes, and classes should have members. Avoid +using functions that have no class attached to them. + +## Using namespace + +- `using namespace` is not allowed in header files, except when + using it in the body of a function. +- Try to be conservative with `using namespace` in cpp files. + Prefer to use it in a function body whenever possible for clarity + what is used where. + +## Including files + +- It's a good practice to include just what you need. In general, + try to include as much as possible in the cpp file, and as little + as possible in the header file. +- A CPP file should normally include the corresponding header file + first (e.g. `WebSettings.cpp` should include `WebSettings.h`) + and then everything else. +- When including a system or library file, use `<...>` syntax; + when including a local file, use `"..."` syntax. Some IDE's + actually have trouble compiling if not done correctly. +- Never include a cpp file; always header files! diff --git a/Grbl_Esp32/Custom/atari_1020.cpp b/Grbl_Esp32/Custom/atari_1020.cpp index b111a20f..427560d8 100644 --- a/Grbl_Esp32/Custom/atari_1020.cpp +++ b/Grbl_Esp32/Custom/atari_1020.cpp @@ -28,60 +28,60 @@ // in Machines/atari_1020.h, thus causing this file to be included // from ../custom_code.cpp -#define HOMING_PHASE_FULL_APPROACH 0 // move to right end -#define HOMING_PHASE_CHECK 1 // check reed switch -#define HOMING_PHASE_RETRACT 2 // retract -#define HOMING_PHASE_SHORT_APPROACH 3 // retract +#define HOMING_PHASE_FULL_APPROACH 0 // move to right end +#define HOMING_PHASE_CHECK 1 // check reed switch +#define HOMING_PHASE_RETRACT 2 // retract +#define HOMING_PHASE_SHORT_APPROACH 3 // retract static TaskHandle_t solenoidSyncTaskHandle = 0; -static TaskHandle_t atariHomingTaskHandle = 0; -int8_t solenoid_pwm_chan_num; -uint16_t solenoid_pull_count; -bool atari_homing = false; -uint8_t homing_phase = HOMING_PHASE_FULL_APPROACH; -uint8_t current_tool; +static TaskHandle_t atariHomingTaskHandle = 0; +int8_t solenoid_pwm_chan_num; +uint16_t solenoid_pull_count; +bool atari_homing = false; +uint8_t homing_phase = HOMING_PHASE_FULL_APPROACH; +uint8_t current_tool; void machine_init() { - solenoid_pull_count = 0; // initialize + solenoid_pull_count = 0; // initialize grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Atari 1020 Solenoid"); // setup PWM channel solenoid_pwm_chan_num = sys_get_next_PWM_chan_num(); ledcSetup(solenoid_pwm_chan_num, SOLENOID_PWM_FREQ, SOLENOID_PWM_RES_BITS); ledcAttachPin(SOLENOID_PEN_PIN, solenoid_pwm_chan_num); - pinMode(SOLENOID_DIRECTION_PIN, OUTPUT); // this sets the direction of the solenoid current - pinMode(REED_SW_PIN, INPUT_PULLUP); // external pullup required + pinMode(SOLENOID_DIRECTION_PIN, OUTPUT); // this sets the direction of the solenoid current + pinMode(REED_SW_PIN, INPUT_PULLUP); // external pullup required // setup a task that will calculate solenoid position - xTaskCreatePinnedToCore(solenoidSyncTask, // task - "solenoidSyncTask", // name for task - 4096, // size of task stack - NULL, // parameters - 1, // priority + xTaskCreatePinnedToCore(solenoidSyncTask, // task + "solenoidSyncTask", // name for task + 4096, // size of task stack + NULL, // parameters + 1, // priority &solenoidSyncTaskHandle, - 0 // core - ); + 0 // core + ); // setup a task that will do the custom homing sequence - xTaskCreatePinnedToCore(atari_home_task, // task - "atari_home_task", // name for task - 4096, // size of task stack - NULL, // parameters - 1, // priority + xTaskCreatePinnedToCore(atari_home_task, // task + "atari_home_task", // name for task + 4096, // size of task stack + NULL, // parameters + 1, // priority &atariHomingTaskHandle, - 0 // core - ); + 0 // core + ); } // this task tracks the Z position and sets the solenoid void solenoidSyncTask(void* pvParameters) { - int32_t current_position[N_AXIS]; // copy of current location - float m_pos[N_AXIS]; // machine position in mm - TickType_t xLastWakeTime; - const TickType_t xSolenoidFrequency = SOLENOID_TASK_FREQ; // in ticks (typically ms) - xLastWakeTime = xTaskGetTickCount(); // Initialise the xLastWakeTime variable with the current time. + int32_t current_position[N_AXIS]; // copy of current location + float m_pos[N_AXIS]; // machine position in mm + TickType_t xLastWakeTime; + const TickType_t xSolenoidFrequency = SOLENOID_TASK_FREQ; // in ticks (typically ms) + xLastWakeTime = xTaskGetTickCount(); // Initialise the xLastWakeTime variable with the current time. while (true) { // don't ever return from this or the task dies - memcpy(current_position, sys_position, sizeof(sys_position)); // get current position in step - system_convert_array_steps_to_mpos(m_pos, current_position); // convert to millimeters - calc_solenoid(m_pos[Z_AXIS]); // calculate kinematics and move the servos + memcpy(current_position, sys_position, sizeof(sys_position)); // get current position in step + system_convert_array_steps_to_mpos(m_pos, current_position); // convert to millimeters + calc_solenoid(m_pos[Z_AXIS]); // calculate kinematics and move the servos vTaskDelayUntil(&xLastWakeTime, xSolenoidFrequency); } } @@ -94,7 +94,7 @@ bool user_defined_homing() { // create and start a task to do the special homing homing_phase = HOMING_PHASE_FULL_APPROACH; atari_homing = true; - return true; // this does it...skip the rest of mc_homing_cycle(...) + return true; // this does it...skip the rest of mc_homing_cycle(...) } /* @@ -113,54 +113,54 @@ bool user_defined_homing() { */ void atari_home_task(void* pvParameters) { - uint8_t homing_attempt = 0; // how many times have we tried to home - TickType_t xLastWakeTime; - const TickType_t xHomingTaskFrequency = 100; // in ticks (typically ms) .... need to make sure there is enough time to get out of idle - char gcode_line[20]; + uint8_t homing_attempt = 0; // how many times have we tried to home + TickType_t xLastWakeTime; + const TickType_t xHomingTaskFrequency = 100; // in ticks (typically ms) .... need to make sure there is enough time to get out of idle + char gcode_line[20]; while (true) { // this task will only last as long as it is homing if (atari_homing) { // must be in idle or alarm state if (sys.state == STATE_IDLE) { switch (homing_phase) { - case HOMING_PHASE_FULL_APPROACH: // a full width move to insure it hits left end - inputBuffer.push("G90G0Z1\r"); // lift the pen - sprintf(gcode_line, "G91G0X%3.2f\r", -ATARI_PAPER_WIDTH + ATARI_HOME_POS - 3.0); // plus a little extra - inputBuffer.push(gcode_line); - homing_attempt = 1; - homing_phase = HOMING_PHASE_CHECK; - break; - case HOMING_PHASE_CHECK: // check the limits switch - if (digitalRead(REED_SW_PIN) == 0) { - // see if reed switch is grounded - inputBuffer.push("G4P0.1\n"); // dramtic pause - sys_position[X_AXIS] = ATARI_HOME_POS * axis_settings[X_AXIS]->steps_per_mm->get(); - sys_position[Y_AXIS] = 0.0; - sys_position[Z_AXIS] = 1.0 * axis_settings[Y_AXIS]->steps_per_mm->get(); - gc_sync_position(); - plan_sync_position(); - sprintf(gcode_line, "G90G0X%3.2f\r", ATARI_PAPER_WIDTH); // alway return to right side to reduce home travel stalls + case HOMING_PHASE_FULL_APPROACH: // a full width move to insure it hits left end + inputBuffer.push("G90G0Z1\r"); // lift the pen + sprintf(gcode_line, "G91G0X%3.2f\r", -ATARI_PAPER_WIDTH + ATARI_HOME_POS - 3.0); // plus a little extra inputBuffer.push(gcode_line); - current_tool = 1; // local copy for reference...until actual M6 change - gc_state.tool = current_tool; - atari_homing = false; // done with homing sequence - } else { - homing_phase = HOMING_PHASE_RETRACT; - homing_attempt++; - } - break; - case HOMING_PHASE_RETRACT: - sprintf(gcode_line, "G0X%3.2f\r", -ATARI_HOME_POS); - inputBuffer.push(gcode_line); - sprintf(gcode_line, "G0X%3.2f\r", ATARI_HOME_POS); - inputBuffer.push(gcode_line); - homing_phase = HOMING_PHASE_CHECK; - break; - default: - grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Homing phase error %d", homing_phase); - atari_homing = false; - ; // kills task - break; + homing_attempt = 1; + homing_phase = HOMING_PHASE_CHECK; + break; + case HOMING_PHASE_CHECK: // check the limits switch + if (digitalRead(REED_SW_PIN) == 0) { + // see if reed switch is grounded + inputBuffer.push("G4P0.1\n"); // dramtic pause + sys_position[X_AXIS] = ATARI_HOME_POS * axis_settings[X_AXIS]->steps_per_mm->get(); + sys_position[Y_AXIS] = 0.0; + sys_position[Z_AXIS] = 1.0 * axis_settings[Y_AXIS]->steps_per_mm->get(); + gc_sync_position(); + plan_sync_position(); + sprintf(gcode_line, "G90G0X%3.2f\r", ATARI_PAPER_WIDTH); // alway return to right side to reduce home travel stalls + inputBuffer.push(gcode_line); + current_tool = 1; // local copy for reference...until actual M6 change + gc_state.tool = current_tool; + atari_homing = false; // done with homing sequence + } else { + homing_phase = HOMING_PHASE_RETRACT; + homing_attempt++; + } + break; + case HOMING_PHASE_RETRACT: + sprintf(gcode_line, "G0X%3.2f\r", -ATARI_HOME_POS); + inputBuffer.push(gcode_line); + sprintf(gcode_line, "G0X%3.2f\r", ATARI_HOME_POS); + inputBuffer.push(gcode_line); + homing_phase = HOMING_PHASE_CHECK; + break; + default: + grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Homing phase error %d", homing_phase); + atari_homing = false; + ; // kills task + break; } if (homing_attempt > ATARI_HOMING_ATTEMPTS) { // try all positions plus 1 @@ -177,25 +177,25 @@ void atari_home_task(void* pvParameters) { // calculate and set the PWM value for the servo void calc_solenoid(float penZ) { - bool isPenUp; + bool isPenUp; static bool previousPenState = false; - uint32_t solenoid_pen_pulse_len; // duty cycle of solenoid - isPenUp = ((penZ > 0) || (sys.state == STATE_ALARM)); // is pen above Z0 or is there an alarm + uint32_t solenoid_pen_pulse_len; // duty cycle of solenoid + isPenUp = ((penZ > 0) || (sys.state == STATE_ALARM)); // is pen above Z0 or is there an alarm // if the state has not change, we only count down to the pull time if (previousPenState == isPenUp) { // if state is unchanged if (solenoid_pull_count > 0) { solenoid_pull_count--; - solenoid_pen_pulse_len = SOLENOID_PULSE_LEN_PULL; // stay at full power while counting down + solenoid_pen_pulse_len = SOLENOID_PULSE_LEN_PULL; // stay at full power while counting down } else { - solenoid_pen_pulse_len = SOLENOID_PULSE_LEN_HOLD; // pull in delay has expired so lower duty cycle + solenoid_pen_pulse_len = SOLENOID_PULSE_LEN_HOLD; // pull in delay has expired so lower duty cycle } } else { // pen direction has changed - solenoid_pen_pulse_len = SOLENOID_PULSE_LEN_PULL; // go to full power - solenoid_pull_count = SOLENOID_PULL_DURATION; // set the time to count down + solenoid_pen_pulse_len = SOLENOID_PULSE_LEN_PULL; // go to full power + solenoid_pull_count = SOLENOID_PULL_DURATION; // set the time to count down } - previousPenState = isPenUp; // save the prev state + previousPenState = isPenUp; // save the prev state digitalWrite(SOLENOID_DIRECTION_PIN, isPenUp); // skip setting value if it is unchanged if (ledcRead(solenoid_pwm_chan_num) == solenoid_pen_pulse_len) @@ -214,8 +214,8 @@ void calc_solenoid(float penZ) { */ void user_tool_change(uint8_t new_tool) { uint8_t move_count; - char gcode_line[20]; - protocol_buffer_synchronize(); // wait for all previous moves to complete + char gcode_line[20]; + protocol_buffer_synchronize(); // wait for all previous moves to complete if ((new_tool < 1) || (new_tool > MAX_PEN_NUMBER)) { grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Requested Pen#%d is out of 1-4 range", new_tool); return; @@ -226,10 +226,10 @@ void user_tool_change(uint8_t new_tool) { move_count = BUMPS_PER_PEN_CHANGE * (new_tool - current_tool); else move_count = BUMPS_PER_PEN_CHANGE * ((MAX_PEN_NUMBER - current_tool) + new_tool); - sprintf(gcode_line, "G0Z%3.2f\r", ATARI_TOOL_CHANGE_Z); // go to tool change height + sprintf(gcode_line, "G0Z%3.2f\r", ATARI_TOOL_CHANGE_Z); // go to tool change height inputBuffer.push(gcode_line); for (uint8_t i = 0; i < move_count; i++) { - sprintf(gcode_line, "G0X%3.2f\r", ATARI_HOME_POS); // + sprintf(gcode_line, "G0X%3.2f\r", ATARI_HOME_POS); // inputBuffer.push(gcode_line); inputBuffer.push("G0X0\r"); } @@ -251,38 +251,36 @@ void user_defined_macro(uint8_t index) { char gcode_line[20]; switch (index) { #ifdef MACRO_BUTTON_0_PIN - case CONTROL_PIN_INDEX_MACRO_0: - grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Pen switch"); - inputBuffer.push("$H\r"); - break; + case CONTROL_PIN_INDEX_MACRO_0: + grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Pen switch"); + inputBuffer.push("$H\r"); + break; #endif #ifdef MACRO_BUTTON_1_PIN - case CONTROL_PIN_INDEX_MACRO_1: - grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Color switch"); - atari_next_pen(); - sprintf(gcode_line, "G90G0X%3.2f\r", ATARI_PAPER_WIDTH); // alway return to right side to reduce home travel stalls - inputBuffer.push(gcode_line); - break; + case CONTROL_PIN_INDEX_MACRO_1: + grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Color switch"); + atari_next_pen(); + sprintf(gcode_line, "G90G0X%3.2f\r", ATARI_PAPER_WIDTH); // alway return to right side to reduce home travel stalls + inputBuffer.push(gcode_line); + break; #endif #ifdef MACRO_BUTTON_2_PIN - case CONTROL_PIN_INDEX_MACRO_2: - // feed out some paper and reset the Y 0 - grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Paper switch"); - inputBuffer.push("G0Y-25\r"); - inputBuffer.push("G4P0.1\r"); // sync...forces wait for planner to clear - sys_position[Y_AXIS] = 0.0; // reset the Y position - gc_sync_position(); - plan_sync_position(); - break; + case CONTROL_PIN_INDEX_MACRO_2: + // feed out some paper and reset the Y 0 + grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Paper switch"); + inputBuffer.push("G0Y-25\r"); + inputBuffer.push("G4P0.1\r"); // sync...forces wait for planner to clear + sys_position[Y_AXIS] = 0.0; // reset the Y position + gc_sync_position(); + plan_sync_position(); + break; #endif - default: - grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Unknown Switch %d", index); - break; + default: grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Unknown Switch %d", index); break; } } void user_m30() { char gcode_line[20]; - sprintf(gcode_line, "G90G0X%3.2f\r", ATARI_PAPER_WIDTH); // + sprintf(gcode_line, "G90G0X%3.2f\r", ATARI_PAPER_WIDTH); // inputBuffer.push(gcode_line); } diff --git a/Grbl_Esp32/Custom/custom_code_template.cpp b/Grbl_Esp32/Custom/custom_code_template.cpp index 1c68d67b..0e6f0779 100644 --- a/Grbl_Esp32/Custom/custom_code_template.cpp +++ b/Grbl_Esp32/Custom/custom_code_template.cpp @@ -51,9 +51,7 @@ enabled with USE_ defines in Machines/my_machine.h machine_init() is called when Grbl_ESP32 first starts. You can use it to do any special things your machine needs at startup. */ -void machine_init() -{ -} +void machine_init() {} #endif #ifdef USE_CUSTOM_HOMING @@ -64,10 +62,9 @@ void machine_init() example, if you need to manually prep the machine for homing, you could implement user_defined_homing() to wait for some button to be pressed, then return true. */ -bool user_defined_homing() -{ - // True = done with homing, false = continue with normal Grbl_ESP32 homing - return true; +bool user_defined_homing() { + // True = done with homing, false = continue with normal Grbl_ESP32 homing + return true; } #endif @@ -89,10 +86,9 @@ bool user_defined_homing() 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) -{ - // this simply moves to the target. Replace with your kinematics. - mc_line(target, pl_data); +void 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); } /* @@ -103,15 +99,13 @@ void inverse_kinematics(float *target, plan_line_data_t *pl_data, float *positio */ bool kinematics_pre_homing(uint8_t cycle_mask)) { - return false; // finish normal homing cycle + return false; // finish normal homing cycle } /* kinematics_post_homing() is called at the end of normal homing */ -void kinematics_post_homing() -{ -} +void kinematics_post_homing() {} #endif #ifdef USE_FWD_KINEMATIC @@ -121,10 +115,9 @@ void kinematics_post_homing() Convert the N_AXIS array of motor positions to cartesian in your code. */ -void forward_kinematics(float *position) -{ - // position[X_AXIS] = - // position[Y_AXIS] = +void forward_kinematics(float* position) { + // position[X_AXIS] = + // position[Y_AXIS] = } #endif @@ -133,9 +126,7 @@ void forward_kinematics(float *position) user_tool_change() is called when tool change gcode is received, to perform appropriate actions for your machine. */ -void user_tool_change(uint8_t new_tool) -{ -} +void user_tool_change(uint8_t new_tool) {} #endif #if defined(MACRO_BUTTON_0_PIN) || defined(MACRO_BUTTON_1_PIN) || defined(MACRO_BUTTON_2_PIN) @@ -143,18 +134,14 @@ void user_tool_change(uint8_t new_tool) options. user_defined_macro() is called with the button number to perform whatever actions you choose. */ -void user_defined_macro(uint8_t index) -{ -} +void user_defined_macro(uint8_t index) {} #endif #ifdef USE_M30 /* user_m30() is called when an M30 gcode signals the end of a gcode file. */ -void user_m30() -{ -} +void user_m30() {} #endif #ifdef USE_MACHINE_TRINAMIC_INIT @@ -162,9 +149,7 @@ void user_m30() machine_triaminic_setup() replaces the normal setup of trinamic drivers with your own code. For example, you could setup StallGuard */ -void machine_trinamic_setup() -{ -} +void machine_trinamic_setup() {} #endif // If you add any additional functions specific to your machine that diff --git a/Grbl_Esp32/Custom/esp32_printer_controller.cpp b/Grbl_Esp32/Custom/esp32_printer_controller.cpp index c232a068..8fd3ef2f 100644 --- a/Grbl_Esp32/Custom/esp32_printer_controller.cpp +++ b/Grbl_Esp32/Custom/esp32_printer_controller.cpp @@ -1,5 +1,5 @@ /* - custom_code_template.cpp (copy and use your machine name) + esp32_printer_controller.cpp (copy and use your machine name) Part of Grbl_ESP32 copyright (c) 2020 - Bart Dring. This file was intended for use on the ESP32 @@ -51,33 +51,32 @@ enabled with USE_ defines in Machines/my_machine.h machine_init() is called when Grbl_ESP32 first starts. You can use it to do any special things your machine needs at startup. */ -#define STEPPERS_DISABLE_PIN_X 138 -#define STEPPERS_DISABLE_PIN_Y 134 -#define STEPPERS_DISABLE_PIN_Z 131 -#define STEPPERS_DISABLE_PIN_A 139 +# define STEPPERS_DISABLE_PIN_X 138 +# define STEPPERS_DISABLE_PIN_Y 134 +# define STEPPERS_DISABLE_PIN_Z 131 +# define STEPPERS_DISABLE_PIN_A 139 -#define FAN1_PIN 13 -#define FAN2_PIN 142 -#define FAN3_PIN 143 +# define FAN1_PIN 13 +# define FAN2_PIN 142 +# define FAN3_PIN 143 -#define BED_PIN 4 -#define NOZZLE_PIN 2 +# define BED_PIN 4 +# define NOZZLE_PIN 2 -void machine_init() -{ - // Enable steppers - digitalWrite(STEPPERS_DISABLE_PIN_X, LOW); // enable - digitalWrite(STEPPERS_DISABLE_PIN_Y, LOW); // enable - digitalWrite(STEPPERS_DISABLE_PIN_Z, LOW); // enable - digitalWrite(STEPPERS_DISABLE_PIN_A, LOW); // enable +void machine_init() { + // Enable steppers + digitalWrite(STEPPERS_DISABLE_PIN_X, LOW); // enable + digitalWrite(STEPPERS_DISABLE_PIN_Y, LOW); // enable + digitalWrite(STEPPERS_DISABLE_PIN_Z, LOW); // enable + digitalWrite(STEPPERS_DISABLE_PIN_A, LOW); // enable - // digitalWrite(FAN1_PIN, LOW); // comment out for JTAG debugging + // digitalWrite(FAN1_PIN, LOW); // comment out for JTAG debugging - digitalWrite(FAN2_PIN, LOW); // disable - digitalWrite(FAN3_PIN, LOW); // disable + digitalWrite(FAN2_PIN, LOW); // disable + digitalWrite(FAN3_PIN, LOW); // disable - digitalWrite(BED_PIN, LOW); // disable - digitalWrite(NOZZLE_PIN, LOW); // disable + digitalWrite(BED_PIN, LOW); // disable + digitalWrite(NOZZLE_PIN, LOW); // disable } #endif @@ -89,10 +88,9 @@ void machine_init() example, if you need to manually prep the machine for homing, you could implement user_defined_homing() to wait for some button to be pressed, then return true. */ -bool user_defined_homing() -{ - // True = done with homing, false = continue with normal Grbl_ESP32 homing - return true; +bool user_defined_homing() { + // True = done with homing, false = continue with normal Grbl_ESP32 homing + return true; } #endif @@ -114,10 +112,9 @@ bool user_defined_homing() 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) -{ - // this simply moves to the target. Replace with your kinematics. - mc_line(target, pl_data); +void 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); } /* @@ -128,15 +125,13 @@ void inverse_kinematics(float *target, plan_line_data_t *pl_data, float *positio */ bool kinematics_pre_homing(uint8_t cycle_mask)) { - return false; // finish normal homing cycle + return false; // finish normal homing cycle } /* kinematics_post_homing() is called at the end of normal homing */ -void kinematics_post_homing() -{ -} +void kinematics_post_homing() {} #endif #ifdef USE_FWD_KINEMATIC @@ -146,10 +141,9 @@ void kinematics_post_homing() Convert the N_AXIS array of motor positions to cartesian in your code. */ -void forward_kinematics(float *position) -{ - // position[X_AXIS] = - // position[Y_AXIS] = +void forward_kinematics(float* position) { + // position[X_AXIS] = + // position[Y_AXIS] = } #endif @@ -158,9 +152,7 @@ void forward_kinematics(float *position) user_tool_change() is called when tool change gcode is received, to perform appropriate actions for your machine. */ -void user_tool_change(uint8_t new_tool) -{ -} +void user_tool_change(uint8_t new_tool) {} #endif #if defined(MACRO_BUTTON_0_PIN) || defined(MACRO_BUTTON_1_PIN) || defined(MACRO_BUTTON_2_PIN) @@ -168,18 +160,14 @@ void user_tool_change(uint8_t new_tool) options. user_defined_macro() is called with the button number to perform whatever actions you choose. */ -void user_defined_macro(uint8_t index) -{ -} +void user_defined_macro(uint8_t index) {} #endif #ifdef USE_M30 /* user_m30() is called when an M30 gcode signals the end of a gcode file. */ -void user_m30() -{ -} +void user_m30() {} #endif #ifdef USE_MACHINE_TRINAMIC_INIT @@ -187,9 +175,7 @@ void user_m30() machine_triaminic_setup() replaces the normal setup of trinamic drivers with your own code. For example, you could setup StallGuard */ -void machine_trinamic_setup() -{ -} +void machine_trinamic_setup() {} #endif // If you add any additional functions specific to your machine that diff --git a/Grbl_Esp32/Custom/polar_coaster.cpp b/Grbl_Esp32/Custom/polar_coaster.cpp index 036c283c..af07238f 100644 --- a/Grbl_Esp32/Custom/polar_coaster.cpp +++ b/Grbl_Esp32/Custom/polar_coaster.cpp @@ -54,18 +54,17 @@ // in Machines/polar_coaster.h, thus causing this file to be included // from ../custom_code.cpp - -void calc_polar(float *target_xyz, float *polar, float last_angle); +void calc_polar(float* target_xyz, float* polar, float last_angle); float abs_angle(float ang); -static float last_angle = 0; +static float last_angle = 0; static float last_radius = 0; // this get called before homing // return false to complete normal home // return true to exit normal homing bool kinematics_pre_homing(uint8_t cycle_mask) { - return false; // finish normal homing cycle + return false; // finish normal homing cycle } void kinematics_post_homing() { @@ -86,17 +85,17 @@ void kinematics_post_homing() { */ -void inverse_kinematics(float *target, plan_line_data_t *pl_data, float *position) { +void 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 - float p_dx, p_dy, p_dz; // distances in each polar axis - float dist, polar_dist; // the distances in both systems...used to determine feed rate - uint32_t segment_count; // number of segments the move will be broken in to. - float seg_target[N_AXIS]; // The target of the current segment - float polar[N_AXIS]; // target location in polar coordinates - float x_offset = gc_state.coord_system[X_AXIS] + gc_state.coord_offset[X_AXIS]; // offset from machine coordinate system - float z_offset = gc_state.coord_system[Z_AXIS] + gc_state.coord_offset[Z_AXIS]; // offset from machine coordinate system + float dx, dy, dz; // distances in each cartesian axis + float p_dx, p_dy, p_dz; // distances in each polar axis + float dist, polar_dist; // the distances in both systems...used to determine feed rate + uint32_t segment_count; // number of segments the move will be broken in to. + float seg_target[N_AXIS]; // The target of the current segment + float polar[N_AXIS]; // target location in polar coordinates + float x_offset = gc_state.coord_system[X_AXIS] + gc_state.coord_offset[X_AXIS]; // offset from machine coordinate system + float z_offset = gc_state.coord_system[Z_AXIS] + gc_state.coord_offset[Z_AXIS]; // offset from machine coordinate system //grbl_sendf(CLIENT_SERIAL, "Position: %4.2f %4.2f %4.2f \r\n", position[X_AXIS] - x_offset, position[Y_AXIS], position[Z_AXIS]); //grbl_sendf(CLIENT_SERIAL, "Target: %4.2f %4.2f %4.2f \r\n", target[X_AXIS] - x_offset, target[Y_AXIS], target[Z_AXIS]); // calculate cartesian move distance for each axis @@ -120,11 +119,11 @@ void inverse_kinematics(float *target, plan_line_data_t *pl_data, float *positio calc_polar(seg_target, polar, last_angle); // begin determining new feed rate // calculate move distance for each axis - p_dx = polar[RADIUS_AXIS] - last_radius; - p_dy = polar[POLAR_AXIS] - last_angle; - p_dz = dz; - polar_dist = sqrt((p_dx * p_dx) + (p_dy * p_dy) + (p_dz * p_dz)); // calculate the total move distance - float polar_rate_multiply = 1.0; // fail safe rate + p_dx = polar[RADIUS_AXIS] - last_radius; + p_dy = polar[POLAR_AXIS] - last_angle; + p_dz = dz; + polar_dist = sqrt((p_dx * p_dx) + (p_dy * p_dy) + (p_dz * p_dz)); // calculate the total move distance + float polar_rate_multiply = 1.0; // fail safe rate if (polar_dist == 0 || dist == 0) { // prevent 0 feed rate and division by 0 polar_rate_multiply = 1.0; // default to same feed rate @@ -141,7 +140,7 @@ void inverse_kinematics(float *target, plan_line_data_t *pl_data, float *positio polar[RADIUS_AXIS] += x_offset; polar[Z_AXIS] += z_offset; last_radius = polar[RADIUS_AXIS]; - last_angle = polar[POLAR_AXIS]; + last_angle = polar[POLAR_AXIS]; mc_line(polar, pl_data); } // TO DO don't need a feedrate for rapids @@ -160,18 +159,18 @@ position = the current machine position converted = position with forward kinematics applied. */ -void forward_kinematics(float *position) { - float original_position[N_AXIS]; // temporary storage of original - float print_position[N_AXIS]; +void forward_kinematics(float* position) { + float original_position[N_AXIS]; // temporary storage of original + float print_position[N_AXIS]; int32_t current_position[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); original_position[X_AXIS] = print_position[X_AXIS] - gc_state.coord_system[X_AXIS] + gc_state.coord_offset[X_AXIS]; original_position[Y_AXIS] = print_position[Y_AXIS] - gc_state.coord_system[Y_AXIS] + gc_state.coord_offset[Y_AXIS]; original_position[Z_AXIS] = print_position[Z_AXIS] - gc_state.coord_system[Z_AXIS] + gc_state.coord_offset[Z_AXIS]; - position[X_AXIS] = cos(radians(original_position[Y_AXIS])) * original_position[X_AXIS] * -1; - position[Y_AXIS] = sin(radians(original_position[Y_AXIS])) * original_position[X_AXIS]; - position[Z_AXIS] = original_position[Z_AXIS]; // unchanged + position[X_AXIS] = cos(radians(original_position[Y_AXIS])) * original_position[X_AXIS] * -1; + position[Y_AXIS] = sin(radians(original_position[Y_AXIS])) * original_position[X_AXIS]; + position[Z_AXIS] = original_position[Z_AXIS]; // unchanged } // helper functions @@ -189,7 +188,7 @@ void forward_kinematics(float *position) { * a long job. * */ -void calc_polar(float *target_xyz, float *polar, float last_angle) { +void calc_polar(float* target_xyz, float* polar, float last_angle) { float delta_ang; // the difference from the last and next angle polar[RADIUS_AXIS] = hypot_f(target_xyz[X_AXIS], target_xyz[Y_AXIS]); if (polar[RADIUS_AXIS] == 0) { @@ -200,7 +199,7 @@ void calc_polar(float *target_xyz, float *polar, float last_angle) { polar[POLAR_AXIS] = abs_angle(polar[POLAR_AXIS]); } polar[Z_AXIS] = target_xyz[Z_AXIS]; // Z is unchanged - delta_ang = polar[POLAR_AXIS] - abs_angle(last_angle); + delta_ang = polar[POLAR_AXIS] - abs_angle(last_angle); // if the delta is above 180 degrees it means we are crossing the 0 degree line if (fabs(delta_ang) <= 180.0) polar[POLAR_AXIS] = last_angle + delta_ang; @@ -225,26 +224,24 @@ float abs_angle(float ang) { void user_defined_macro(uint8_t index) { switch (index) { #ifdef MACRO_BUTTON_0_PIN - case CONTROL_PIN_INDEX_MACRO_0: - inputBuffer.push("$H\r"); // home machine - break; + case CONTROL_PIN_INDEX_MACRO_0: + inputBuffer.push("$H\r"); // home machine + break; #endif #ifdef MACRO_BUTTON_1_PIN - case CONTROL_PIN_INDEX_MACRO_1: - inputBuffer.push("[ESP220]/1.nc\r"); // run SD card file 1.nc - break; + case CONTROL_PIN_INDEX_MACRO_1: + inputBuffer.push("[ESP220]/1.nc\r"); // run SD card file 1.nc + break; #endif #ifdef MACRO_BUTTON_2_PIN - case CONTROL_PIN_INDEX_MACRO_2: - inputBuffer.push("[ESP220]/2.nc\r"); // run SD card file 2.nc - break; + case CONTROL_PIN_INDEX_MACRO_2: + inputBuffer.push("[ESP220]/2.nc\r"); // run SD card file 2.nc + break; #endif #ifdef MACRO_BUTTON_3_PIN - case CONTROL_PIN_INDEX_MACRO_3: - break; + case CONTROL_PIN_INDEX_MACRO_3: break; #endif - default: - break; + default: break; } } diff --git a/Grbl_Esp32/Grbl_Esp32.ino b/Grbl_Esp32/Grbl_Esp32.ino index 26431157..74918296 100644 --- a/Grbl_Esp32/Grbl_Esp32.ino +++ b/Grbl_Esp32/Grbl_Esp32.ino @@ -18,55 +18,45 @@ along with Grbl. If not, see . */ -#include "grbl.h" -#include "WiFi.h" - -#include "Spindles/SpindleClass.cpp" -#include "Motors/MotorClass.cpp" +#include "src/Grbl.h" +#include // Declare system global variable structure -system_t sys; -int32_t sys_position[N_AXIS]; // Real-time machine (aka home) position vector in steps. -int32_t sys_probe_position[N_AXIS]; // Last probe position in machine coordinates and steps. -volatile uint8_t sys_probe_state; // Probing state value. Used to coordinate the probing cycle with stepper ISR. -volatile uint8_t sys_rt_exec_state; // Global realtime executor bitflag variable for state management. See EXEC bitmasks. -volatile uint8_t sys_rt_exec_alarm; // Global realtime executor bitflag variable for setting various alarms. -volatile uint8_t sys_rt_exec_motion_override; // Global realtime executor bitflag variable for motion-based overrides. -volatile uint8_t sys_rt_exec_accessory_override; // Global realtime executor bitflag variable for spindle/coolant overrides. +system_t sys; +int32_t sys_position[N_AXIS]; // Real-time machine (aka home) position vector in steps. +int32_t sys_probe_position[N_AXIS]; // Last probe position in machine coordinates and steps. +volatile uint8_t sys_probe_state; // Probing state value. Used to coordinate the probing cycle with stepper ISR. +volatile uint8_t sys_rt_exec_state; // Global realtime executor bitflag variable for state management. See EXEC bitmasks. +volatile uint8_t sys_rt_exec_alarm; // Global realtime executor bitflag variable for setting various alarms. +volatile uint8_t sys_rt_exec_motion_override; // Global realtime executor bitflag variable for motion-based overrides. +volatile uint8_t sys_rt_exec_accessory_override; // Global realtime executor bitflag variable for spindle/coolant overrides. #ifdef DEBUG - volatile uint8_t sys_rt_exec_debug; +volatile uint8_t sys_rt_exec_debug; #endif -Spindle *spindle; - - +Spindles::Spindle* spindle; void setup() { #ifdef USE_I2S_OUT - i2s_out_init(); // The I2S out must be initialized before it can access the expanded GPIO port + i2s_out_init(); // The I2S out must be initialized before it can access the expanded GPIO port #endif WiFi.persistent(false); WiFi.disconnect(true); WiFi.enableSTA(false); WiFi.enableAP(false); WiFi.mode(WIFI_OFF); - serial_init(); // Setup serial baud rate and interrupts - grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Grbl_ESP32 Ver %s Date %s", GRBL_VERSION, GRBL_VERSION_BUILD); // print grbl_esp32 verion info - grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Compiled with ESP32 SDK:%s", ESP.getSdkVersion()); // print the SDK version + serial_init(); // Setup serial baud rate and interrupts + grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Grbl_ESP32 Ver %s Date %s", GRBL_VERSION, GRBL_VERSION_BUILD); // print grbl_esp32 verion info + grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Compiled with ESP32 SDK:%s", ESP.getSdkVersion()); // print the SDK version // show the map name at startup #ifdef MACHINE_NAME - #ifdef MACHINE_EXTRA - #define MACHINE_STRING MACHINE_NAME MACHINE_EXTRA - #else - #define MACHINE_STRING MACHINE_NAME - #endif report_machine_type(CLIENT_SERIAL); #endif - settings_init(); // Load Grbl settings from EEPROM - stepper_init(); // Configure stepper pins and interrupt timers + settings_init(); // Load Grbl settings from EEPROM + stepper_init(); // Configure stepper pins and interrupt timers init_motors(); - system_ini(); // Configure pinout pins and pin-change interrupt (Renamed due to conflict with esp32 files) - memset(sys_position, 0, sizeof(sys_position)); // Clear machine position. + system_ini(); // Configure pinout pins and pin-change interrupt (Renamed due to conflict with esp32 files) + memset(sys_position, 0, sizeof(sys_position)); // Clear machine position. #ifdef USE_PEN_SERVO servo_init(); #endif @@ -77,7 +67,7 @@ void setup() { solenoid_init(); #endif #ifdef USE_MACHINE_INIT - machine_init(); // user supplied function for special initialization + machine_init(); // user supplied function for special initialization #endif // Initialize system state. #ifdef FORCE_INITIALIZATION_ALARM @@ -94,9 +84,10 @@ void setup() { // not after disabling the alarm locks. Prevents motion startup blocks from crashing into // things uncontrollably. Very bad. #ifdef HOMING_INIT_LOCK - if (homing_enable->get()) sys.state = STATE_ALARM; + if (homing_enable->get()) + sys.state = STATE_ALARM; #endif - spindle_select(); + Spindles::Spindle::select(); #ifdef ENABLE_WIFI wifi_config.begin(); #endif @@ -109,26 +100,26 @@ void setup() { void loop() { // Reset system variables. uint8_t prior_state = sys.state; - memset(&sys, 0, sizeof(system_t)); // Clear system struct variable. - sys.state = prior_state; - sys.f_override = DEFAULT_FEED_OVERRIDE; // Set to 100% - sys.r_override = DEFAULT_RAPID_OVERRIDE; // Set to 100% - sys.spindle_speed_ovr = DEFAULT_SPINDLE_SPEED_OVERRIDE; // Set to 100% - memset(sys_probe_position, 0, sizeof(sys_probe_position)); // Clear probe position. - sys_probe_state = 0; - sys_rt_exec_state = 0; - sys_rt_exec_alarm = 0; - sys_rt_exec_motion_override = 0; + memset(&sys, 0, sizeof(system_t)); // Clear system struct variable. + sys.state = prior_state; + sys.f_override = DEFAULT_FEED_OVERRIDE; // Set to 100% + sys.r_override = DEFAULT_RAPID_OVERRIDE; // Set to 100% + sys.spindle_speed_ovr = DEFAULT_SPINDLE_SPEED_OVERRIDE; // Set to 100% + memset(sys_probe_position, 0, sizeof(sys_probe_position)); // Clear probe position. + sys_probe_state = 0; + sys_rt_exec_state = 0; + sys_rt_exec_alarm = 0; + sys_rt_exec_motion_override = 0; sys_rt_exec_accessory_override = 0; // Reset Grbl primary systems. - serial_reset_read_buffer(CLIENT_ALL); // Clear serial read buffer - gc_init(); // Set g-code parser to default state + serial_reset_read_buffer(CLIENT_ALL); // Clear serial read buffer + gc_init(); // Set g-code parser to default state spindle->stop(); coolant_init(); limits_init(); probe_init(); - plan_reset(); // Clear block buffer and planner variables - st_reset(); // Clear stepper subsystem variables + plan_reset(); // Clear block buffer and planner variables + st_reset(); // Clear stepper subsystem variables // Sync cleared gcode and planner positions to current system position. plan_sync_position(); gc_sync_position(); diff --git a/Grbl_Esp32/Motors/MotorClass.h b/Grbl_Esp32/Motors/MotorClass.h deleted file mode 100644 index 072ebbe6..00000000 --- a/Grbl_Esp32/Motors/MotorClass.h +++ /dev/null @@ -1,218 +0,0 @@ -/* - MotorClass.h - Header file for Motor Classes - Here is the hierarchy - Motor - Nullmotor - StandardStepper - TrinamicDriver - Unipolar - RC Servo - - These are for motors coordinated by Grbl_ESP32 - See motorClass.cpp for more details - - Part of Grbl_ESP32 - 2020 - Bart Dring - - 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 . -*/ - -#ifndef MOTORCLASS_H -#define MOTORCLASS_H - -#include "../grbl.h" -#include // https://github.com/teemuatlut/TMCStepper -#include "TrinamicDriverClass.h" -#include "RcServoClass.h" -//#include "SolenoidClass.h" - -extern uint8_t rmt_chan_num[MAX_AXES][2]; -extern rmt_item32_t rmtItem[2]; -extern rmt_config_t rmtConfig; - -typedef enum { - MOTOR, - NULL_MOTOR, - STANDARD_MOTOR, - TRINAMIC_SPI_MOTOR, - UNIPOLAR_MOTOR, - RC_SERVO_MOTOR, - SOLENOID -} motor_class_id_t; - -// These are used for setup and to talk to the motors as a group. -void init_motors(); -uint8_t get_next_trinamic_driver_index(); -bool motors_have_type_id(motor_class_id_t id); -void readSgTask(void* pvParameters); -void motors_read_settings(); -void motors_set_homing_mode(uint8_t homing_mask, bool isHoming); -void motors_set_disable(bool disable); -void motors_set_direction_pins(uint8_t onMask); -void motors_step(uint8_t step_mask, uint8_t dir_mask); -void servoUpdateTask(void* pvParameters); - -extern bool motor_class_steps; // true if at least one motor class is handling steps - -// ==================== Motor Classes ==================== - -class Motor { - public: - Motor(); - - virtual void init(); // not in constructor because this also gets called when $$ settings change - virtual void config_message(); - virtual void debug_message(); - virtual void read_settings(); - virtual void set_homing_mode(uint8_t homing_mask, bool isHoming); - virtual void set_disable(bool disable); - virtual void set_direction_pins(uint8_t onMask); - virtual void step(uint8_t step_mask, uint8_t dir_mask); // only used on Unipolar right now - virtual bool test(); - virtual void set_axis_name(); - virtual void update(); - - motor_class_id_t type_id; - uint8_t is_active = false; - - protected: - uint8_t axis_index; // X_AXIS, etc - uint8_t dual_axis_index; // 0 = primary 1=ganged - - bool _showError; - bool _use_mpos = true; - uint8_t _homing_mask; - char _axis_name[10];// this the name to use when reporting like "X" or "X2" -}; - -class Nullmotor : public Motor { - -}; - -class StandardStepper : public Motor { - public: - StandardStepper(); - StandardStepper(uint8_t axis_index, uint8_t step_pin, uint8_t dir_pin, uint8_t disable_pin); - - virtual void config_message(); - virtual void init(); - virtual void set_direction_pins(uint8_t onMask); - void init_step_dir_pins(); - virtual void set_disable(bool disable); - uint8_t step_pin; - - protected: - bool _invert_step_pin; - uint8_t dir_pin; - uint8_t disable_pin; -}; - -class TrinamicDriver : public StandardStepper { - public: - TrinamicDriver(uint8_t axis_index, - uint8_t step_pin, - uint8_t dir_pin, - uint8_t disable_pin, - uint8_t cs_pin, - uint16_t driver_part_number, - float r_sense, - int8_t spi_index); - - void config_message(); - void init(); - void set_mode(bool isHoming); - void read_settings(); - void trinamic_test_response(); - void trinamic_stepper_enable(bool enable); - void debug_message(); - void set_homing_mode(uint8_t homing_mask, bool ishoming); - void set_disable(bool disable); - bool test(); - - private: - uint32_t calc_tstep(float speed, float percent); - - TMC2130Stepper* tmcstepper; // all other driver types are subclasses of this one - uint8_t _homing_mode; - uint8_t cs_pin = UNDEFINED_PIN; // The chip select pin (can be the same for daisy chain) - uint16_t _driver_part_number; // example: use 2130 for TMC2130 - float _r_sense; - int8_t spi_index; - protected: - uint8_t _mode; - uint8_t _lastMode = 255; -}; - - -class UnipolarMotor : public Motor { - public: - UnipolarMotor(); - UnipolarMotor(uint8_t axis_index, uint8_t pin_phase0, uint8_t pin_phase1, uint8_t pin_phase2, uint8_t pin_phase3); - void init(); - void config_message(); - void set_disable(bool disable); - void step(uint8_t step_mask, uint8_t dir_mask); // only used on Unipolar right now - - private: - uint8_t _pin_phase0; - uint8_t _pin_phase1; - uint8_t _pin_phase2; - uint8_t _pin_phase3; - uint8_t _current_phase; - bool _half_step; - bool _enabled; -}; - -class RcServo : public Motor { - public: - RcServo(); - RcServo(uint8_t axis_index, uint8_t pwm_pin, float min, float max); - virtual void config_message(); - virtual void init(); - void _write_pwm(uint32_t duty); - virtual void set_disable(bool disable); - virtual void update(); - void read_settings(); - void set_homing_mode(bool is_homing, bool isHoming); - - protected: - void set_location(); - void _get_calibration(); - - uint8_t _pwm_pin; - uint8_t _channel_num; - uint32_t _current_pwm_duty; - bool _disabled; - - float _position_min; - float _position_max; // position in millimeters - float _homing_position; - - float _pwm_pulse_min; - float _pwm_pulse_max; -}; - -class Solenoid : public RcServo { - public: - Solenoid(); - Solenoid(uint8_t axis_index, gpio_num_t pwm_pin, float transition_poiont); - void config_message(); - void set_location(); - void update(); - void init(); - void set_disable(bool disable); - - float _transition_poiont; -}; - -#endif diff --git a/Grbl_Esp32/Motors/RcServoClass.cpp b/Grbl_Esp32/Motors/RcServoClass.cpp deleted file mode 100644 index 51253138..00000000 --- a/Grbl_Esp32/Motors/RcServoClass.cpp +++ /dev/null @@ -1,190 +0,0 @@ -/* - RcServoServoClass.cpp - - This allows an RcServo to be used like any other motor. Serrvos - do have limitation in travel and speed, so you do need to respect that. - - Part of Grbl_ESP32 - - 2020 - Bart Dring - - Servos have a limited travel, so they map the travel across a range in - the current work coordinatee system. The servo can only travel as far - as the range, but the internal axis value can keep going. - - Range: The range is specified in the machine definition file with... - #define X_SERVO_RANGE_MIN 0.0 - #define X_SERVO_RANGE_MAX 5.0 - - Direction: The direction can be changed using the $3 setting for the axis - - Homing: During homing, the servo will move to one of the endpoints. The - endpoint is determined by the $23 or $HomingDirInvertMask setting for the axis. - Do not define a homing cycle for the axis with the servo. - You do need at least 1 homing cycle. TODO: Fix this - - Calibration. You can tweak the endpoints using the $10n or nStepsPerMm and - $13n or $xMaxTravel setting, where n is the axis. - The value is a percent. If you secify a percent outside the - the range specified by the values below, it will be reset to 100.0 (100% ... no change) - The calibration adjusts in direction of positive momement, so a value above 100% moves - towards the higher axis value. - - #define SERVO_CAL_MIN - #define SERVO_CAL_MAX - - Grbl_ESP32 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 . -*/ - -RcServo :: RcServo() { - -} - -RcServo :: RcServo(uint8_t axis_index, uint8_t pwm_pin, float min, float max) { - type_id = RC_SERVO_MOTOR; - this->axis_index = axis_index % MAX_AXES; - this->dual_axis_index = axis_index < MAX_AXES ? 0 : 1; // 0 = primary 1 = ganged - this->_pwm_pin = pwm_pin; - _position_min = min; - _position_max = max; - init(); -} - -void RcServo :: init() { - read_settings(); - _channel_num = sys_get_next_PWM_chan_num(); - ledcSetup(_channel_num, SERVO_PULSE_FREQ, SERVO_PULSE_RES_BITS); - ledcAttachPin(_pwm_pin, _channel_num); - _current_pwm_duty = 0; - is_active = true; // as opposed to NullMotors, this is a real motor - set_axis_name(); - config_message(); -} - -void RcServo :: config_message() { - grbl_msg_sendf(CLIENT_SERIAL, - MSG_LEVEL_INFO, - "%s Axis RC Servo motor Output:%d Min:%5.3fmm Max:%5.3fmm", - _axis_name, - _pwm_pin, - _position_min, - _position_max); -} - -void RcServo::_write_pwm(uint32_t duty) { - // to prevent excessive calls to ledcWrite, make sure duty hass changed - if (duty == _current_pwm_duty) - return; - - _current_pwm_duty = duty; - - //grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "%s Servo Pwm %d", _axis_name, duty); - ledcWrite(_channel_num, duty); -} - -// sets the PWM to zero. This allows most servos to be manually moved -void RcServo::set_disable(bool disable) { - return; - _disabled = disable; - if (_disabled) - _write_pwm(0); -} - -void RcServo::set_homing_mode(bool is_homing, bool isHoming) { - float home_pos = 0.0; - - if (!is_homing) - return; - - if (bit_istrue(homing_dir_mask->get(), bit(axis_index))) - home_pos = _position_min; - else - home_pos = _position_max; - - //grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Servo set home %d %3.2f", is_homing, home_pos); - sys_position[axis_index] = home_pos * axis_settings[axis_index]->steps_per_mm->get(); // convert to steps - -} - -void RcServo::update() { - set_location(); -} - -void RcServo::set_location() { - uint32_t servo_pulse_len; - float servo_pos, mpos, offset; - // skip location if we are in alarm mode - - //grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "locate"); - _get_calibration(); - - if (sys.state == STATE_ALARM) { - set_disable(true); - return; - } - - mpos = system_convert_axis_steps_to_mpos(sys_position, axis_index); // get the axis machine position in mm - offset = gc_state.coord_system[axis_index] + gc_state.coord_offset[axis_index]; // get the current axis work offset - servo_pos = mpos - offset; // determine the current work position - - // determine the pulse length - servo_pulse_len = (uint32_t)mapConstrain(servo_pos, _position_min, _position_max, _pwm_pulse_min, _pwm_pulse_max); - - _write_pwm(servo_pulse_len); - -} - -void RcServo::read_settings() { - _get_calibration(); -} - -// this should change to use its own settings. -void RcServo::_get_calibration() { - float _cal_min = 1.0; - float _cal_max = 1.0; - - //grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Read settings"); - - // make sure the min is in range - if ((axis_settings[axis_index]->steps_per_mm->get() < SERVO_CAL_MIN) || (axis_settings[axis_index]->steps_per_mm->get() > SERVO_CAL_MAX)) { - grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Servo calibration ($10%d) value error. Reset to 100", axis_index); - char reset_val[] = "100"; - axis_settings[axis_index]->steps_per_mm->setStringValue(reset_val); - } - - // make sure the max is in range - // Note: Max travel is set positive via $$, but stored as a negative number - if ((axis_settings[axis_index]->max_travel->get() < SERVO_CAL_MIN) || (axis_settings[axis_index]->max_travel->get() > SERVO_CAL_MAX)) { - grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Servo calibration ($13%d) value error. %3.2f Reset to 100", axis_index, axis_settings[axis_index]->max_travel->get()); - char reset_val[] = "100"; - axis_settings[axis_index]->max_travel->setStringValue(reset_val); - } - - _pwm_pulse_min = SERVO_MIN_PULSE; - _pwm_pulse_max = SERVO_MAX_PULSE; - - - if (bit_istrue(dir_invert_mask->get(), bit(axis_index))) { // normal direction - _cal_min = 2.0 - (axis_settings[axis_index]->steps_per_mm->get() / 100.0); - _cal_max = 2.0 - (axis_settings[axis_index]->max_travel->get() / 100.0); - swap(_pwm_pulse_min, _pwm_pulse_max); - } else { // inverted direction - _cal_min = (axis_settings[axis_index]->steps_per_mm->get() / 100.0); - _cal_max = (axis_settings[axis_index]->max_travel->get() / 100.0); - } - - _pwm_pulse_min *= _cal_min; - _pwm_pulse_max *= _cal_max; - - //grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Servo calibration min:%1.2f max %1.2f", _pwm_pulse_min, _pwm_pulse_max); - -} \ No newline at end of file diff --git a/Grbl_Esp32/Motors/RcServoClass.h b/Grbl_Esp32/Motors/RcServoClass.h deleted file mode 100644 index 0a23d6ae..00000000 --- a/Grbl_Esp32/Motors/RcServoClass.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - RcServoClass.h - - Part of Grbl_ESP32 - - 2020 - Bart Dring - - 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 . -*/ -#ifndef RCSERVOCLASS_H -#define RCSERVOCLASS_H - -// this is the pulse range of a the servo. Typical servos are 0.001 to 0.002 seconds -// some servos have a wider range. You can adjust this here or in the calibration feature -#define SERVO_MIN_PULSE_SEC 0.001 // min pulse in seconds -#define SERVO_MAX_PULSE_SEC 0.002 // max pulse in seconds - -#define SERVO_POSITION_MIN_DEFAULT 0.0 // mm -#define SERVO_POSITION_MAX_DEFAULT 20.0 // mm - -#define SERVO_PULSE_FREQ 50 // 50Hz ...This is a standard analog servo value. Digital ones can repeat faster - -#define SERVO_PULSE_RES_BITS 16 // bits of resolution of PWM (16 is max) -#define SERVO_PULSE_RES_COUNT 65535 // see above TODO...do the math here 2^SERVO_PULSE_RES_BITS - -#define SERVO_TIME_PER_BIT ((1.0 / (float)SERVO_PULSE_FREQ) / ((float)SERVO_PULSE_RES_COUNT) ) // seconds - -#define SERVO_MIN_PULSE (uint16_t)(SERVO_MIN_PULSE_SEC / SERVO_TIME_PER_BIT) // in timer counts -#define SERVO_MAX_PULSE (uint16_t)(SERVO_MAX_PULSE_SEC / SERVO_TIME_PER_BIT) // in timer counts - -#define SERVO_PULSE_RANGE (SERVO_MAX_PULSE-SERVO_MIN_PULSE) - -#define SERVO_CAL_MIN 20.0 // Percent: the minimum allowable calibration value -#define SERVO_CAL_MAX 180.0 // Percent: the maximum allowable calibration value - -#define SERVO_TIMER_INT_FREQ 50.0 // Hz This is the task frequency - -#endif \ No newline at end of file diff --git a/Grbl_Esp32/Motors/StandardStepperClass.cpp b/Grbl_Esp32/Motors/StandardStepperClass.cpp deleted file mode 100644 index f4f63820..00000000 --- a/Grbl_Esp32/Motors/StandardStepperClass.cpp +++ /dev/null @@ -1,107 +0,0 @@ -/* - StandardStepperClass.cpp - - This is used for a stepper motor that just requires step and direction - pins. - TODO: Add an enable pin - - Part of Grbl_ESP32 - - 2020 - Bart Dring - - 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 . -*/ - -StandardStepper :: StandardStepper() { - -} - -StandardStepper :: StandardStepper(uint8_t axis_index, uint8_t step_pin, uint8_t dir_pin, uint8_t disable_pin) { - type_id = STANDARD_MOTOR; - this->axis_index = axis_index % MAX_AXES; - this->dual_axis_index = axis_index < MAX_AXES ? 0 : 1; // 0 = primary 1 = ganged - this->step_pin = step_pin; - this->dir_pin = dir_pin; - this->disable_pin = disable_pin; - init(); -} - -void StandardStepper :: init() { - _homing_mask = 0; - is_active = true; // as opposed to NullMotors, this is a real motor - set_axis_name(); - init_step_dir_pins(); - config_message(); -} - -void StandardStepper :: init_step_dir_pins() { - // TODO Step pin, but RMT complicates things - _invert_step_pin = bit_istrue(step_invert_mask->get(), bit(axis_index)); - pinMode(dir_pin, OUTPUT); - -#ifdef USE_RMT_STEPS - rmtConfig.rmt_mode = RMT_MODE_TX; - rmtConfig.clk_div = 20; - rmtConfig.mem_block_num = 2; - rmtConfig.tx_config.loop_en = false; - rmtConfig.tx_config.carrier_en = false; - rmtConfig.tx_config.carrier_freq_hz = 0; - rmtConfig.tx_config.carrier_duty_percent = 50; - rmtConfig.tx_config.carrier_level = RMT_CARRIER_LEVEL_LOW; - rmtConfig.tx_config.idle_output_en = true; - - -#ifdef STEP_PULSE_DELAY - rmtItem[0].duration0 = STEP_PULSE_DELAY * 4; -#else - rmtItem[0].duration0 = 1; -#endif - - rmtItem[0].duration1 = 4 * pulse_microseconds->get(); - rmtItem[1].duration0 = 0; - rmtItem[1].duration1 = 0; - - rmt_chan_num[axis_index][dual_axis_index] = sys_get_next_RMT_chan_num(); - rmt_set_source_clk((rmt_channel_t)rmt_chan_num[axis_index][dual_axis_index], RMT_BASECLK_APB); - rmtConfig.channel = (rmt_channel_t)rmt_chan_num[axis_index][dual_axis_index]; - rmtConfig.tx_config.idle_level = _invert_step_pin ? RMT_IDLE_LEVEL_HIGH : RMT_IDLE_LEVEL_LOW; - rmtConfig.gpio_num = gpio_num_t(step_pin); // c is a wacky lang - rmtItem[0].level0 = rmtConfig.tx_config.idle_level; - rmtItem[0].level1 = !rmtConfig.tx_config.idle_level; - rmt_config(&rmtConfig); - rmt_fill_tx_items(rmtConfig.channel, &rmtItem[0], rmtConfig.mem_block_num, 0); - -#else - pinMode(step_pin, OUTPUT); - -#endif // USE_RMT_STEPS - pinMode(disable_pin, OUTPUT); -} - - -void StandardStepper :: config_message() { - grbl_msg_sendf(CLIENT_SERIAL, - MSG_LEVEL_INFO, - "%s Axis standard stepper motor Step:%s Dir:%s Disable:%s", - _axis_name, - pinName(step_pin).c_str(), - pinName(dir_pin).c_str(), - pinName(disable_pin).c_str()); -} - -void StandardStepper :: set_direction_pins(uint8_t onMask) { - digitalWrite(dir_pin, (onMask & bit(axis_index))); -} - -void StandardStepper :: set_disable(bool disable) { - digitalWrite(disable_pin, disable); -} diff --git a/Grbl_Esp32/Motors/TrinamicDriverClass.cpp b/Grbl_Esp32/Motors/TrinamicDriverClass.cpp deleted file mode 100644 index 13628f90..00000000 --- a/Grbl_Esp32/Motors/TrinamicDriverClass.cpp +++ /dev/null @@ -1,242 +0,0 @@ -/* - TrinamicDriverClass.cpp - This is used for Trinamic SPI controlled stepper motor drivers. - - Part of Grbl_ESP32 - 2020 - Bart Dring - - 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 . - -*/ -#include -#include "TrinamicDriverClass.h" - -TrinamicDriver :: TrinamicDriver(uint8_t axis_index, - uint8_t step_pin, - uint8_t dir_pin, - uint8_t disable_pin, - uint8_t cs_pin, - uint16_t driver_part_number, - float r_sense, - int8_t spi_index) { - type_id = TRINAMIC_SPI_MOTOR; - this->axis_index = axis_index % MAX_AXES; - this->dual_axis_index = axis_index < 6 ? 0 : 1; // 0 = primary 1 = ganged - _driver_part_number = driver_part_number; - _r_sense = r_sense; - this->step_pin = step_pin; - this->dir_pin = dir_pin; - this->disable_pin = disable_pin; - this->cs_pin = cs_pin; - this->spi_index = spi_index; - - _homing_mode = TRINAMIC_HOMING_MODE; - _homing_mask = 0; // no axes homing - - if (_driver_part_number == 2130) - tmcstepper = new TMC2130Stepper(cs_pin, _r_sense, spi_index); - else if (_driver_part_number == 5160) - tmcstepper = new TMC5160Stepper(cs_pin, _r_sense, spi_index); - else { - grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Trinamic unsupported p/n:%d", _driver_part_number); - return; - } - - set_axis_name(); - - init_step_dir_pins(); // from StandardStepper - - digitalWrite(cs_pin, HIGH); - pinMode(cs_pin, OUTPUT); - - // use slower speed if I2S - if (cs_pin >= I2S_OUT_PIN_BASE) - tmcstepper->setSPISpeed(TRINAMIC_SPI_FREQ); - - config_message(); - - // init() must be called later, after all TMC drivers have CS pins setup. -} - -void TrinamicDriver :: init() { - - SPI.begin(); // this will get called for each motor, but does not seem to hurt anything - - tmcstepper->begin(); - test(); // Try communicating with motor. Prints an error if there is a problem. - read_settings(); // pull info from settings - set_mode(false); - - _homing_mask = 0; - is_active = true; // as opposed to NullMotors, this is a real motor -} - -/* - This is the startup message showing the basic definition -*/ -void TrinamicDriver :: config_message() { - grbl_msg_sendf(CLIENT_SERIAL, - MSG_LEVEL_INFO, - "%s Axis Trinamic TMC%d Step:%s Dir:%s CS:%s Disable:%s Index:%d", - _axis_name, - _driver_part_number, - pinName(step_pin).c_str(), - pinName(dir_pin).c_str(), - pinName(cs_pin).c_str(), - pinName(disable_pin).c_str(), - spi_index); -} - -bool TrinamicDriver :: test() { - switch (tmcstepper->test_connection()) { - case 1: - grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "%s Trinamic driver test failed. Check connection", _axis_name); - return false; - case 2: - grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "%s Trinamic driver test failed. Check motor power", _axis_name); - return false; - default: - grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "%s Trinamic driver test passed", _axis_name); - return true; - } -} - - -/* - Read setting and send them to the driver. Called at init() and whenever related settings change - both are stored as float Amps, but TMCStepper library expects... - uint16_t run (mA) - float hold (as a percentage of run) -*/ -void TrinamicDriver :: read_settings() { - uint16_t run_i_ma = (uint16_t)(axis_settings[axis_index]->run_current->get() * 1000.0); - float hold_i_percent; - - if (axis_settings[axis_index]->run_current->get() == 0) - hold_i_percent = 0; - else { - hold_i_percent = axis_settings[axis_index]->hold_current->get() / axis_settings[axis_index]->run_current->get(); - if (hold_i_percent > 1.0) - hold_i_percent = 1.0; - } - //grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "%s Current run %d hold %f", _axis_name, run_i_ma, hold_i_percent); - - tmcstepper->microsteps(axis_settings[axis_index]->microsteps->get()); - tmcstepper->rms_current(run_i_ma, hold_i_percent); - -} - -void TrinamicDriver :: set_homing_mode(uint8_t homing_mask, bool isHoming) { - _homing_mask = homing_mask; - set_mode(isHoming); -} - -/* - There are ton of settings. I'll start by grouping then into modes for now. - Many people will want quiet and stallgaurd homing. Stallguard only run in - Coolstep mode, so it will need to switch to Coolstep when homing -*/ -void TrinamicDriver :: set_mode(bool isHoming) { - - if (isHoming) - _mode = TRINAMIC_HOMING_MODE; - else - _mode = TRINAMIC_RUN_MODE; - - if (_lastMode == _mode) - return; - _lastMode = _mode; - - switch (_mode) { - case TRINAMIC_MODE_STEALTHCHOP: - //grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "TRINAMIC_MODE_STEALTHCHOP"); - tmcstepper->en_pwm_mode(true); - tmcstepper->pwm_autoscale(true); - tmcstepper->diag1_stall(false); - break; - case TRINAMIC_MODE_COOLSTEP: - //grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "TRINAMIC_MODE_COOLSTEP"); - tmcstepper->en_pwm_mode(false); - tmcstepper->pwm_autoscale(false); - tmcstepper->TCOOLTHRS(NORMAL_TCOOLTHRS); // when to turn on coolstep - tmcstepper->THIGH(NORMAL_THIGH); - break; - case TRINAMIC_MODE_STALLGUARD: - //grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "TRINAMIC_MODE_STALLGUARD"); - tmcstepper->en_pwm_mode(false); - tmcstepper->pwm_autoscale(false); - tmcstepper->TCOOLTHRS(calc_tstep(homing_feed_rate->get(), 150.0)); - tmcstepper->THIGH(calc_tstep(homing_feed_rate->get(), 60.0)); - tmcstepper->sfilt(1); - tmcstepper->diag1_stall(true); // stallguard i/o is on diag1 - tmcstepper->sgt(axis_settings[axis_index]->stallguard->get()); - break; - default: - grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "TRINAMIC_MODE_UNDEFINED"); - } - -} - -/* - This is the stallguard tuning info. It is call debug, so it could be generic across all classes. -*/ -void TrinamicDriver :: debug_message() { - - uint32_t tstep = tmcstepper->TSTEP(); - - if (tstep == 0xFFFFF || tstep < 1) // if axis is not moving return - return; - float feedrate = st_get_realtime_rate(); //* settings.microsteps[axis_index] / 60.0 ; // convert mm/min to Hz - - grbl_msg_sendf(CLIENT_SERIAL, - MSG_LEVEL_INFO, - "%s Stallguard %d SG_Val: %04d Rate: %05.0f mm/min SG_Setting:%d", - _axis_name, - tmcstepper->stallguard(), - tmcstepper->sg_result(), - feedrate, - axis_settings[axis_index]->stallguard->get()); -} - -// calculate a tstep from a rate -// tstep = TRINAMIC_FCLK / (time between 1/256 steps) -// This is used to set the stallguard window from the homing speed. -// The percent is the offset on the window -uint32_t TrinamicDriver :: calc_tstep(float speed, float percent) { - float tstep = speed / 60.0 * axis_settings[axis_index]->steps_per_mm->get() * (float)(256 / axis_settings[axis_index]->microsteps->get()); - tstep = TRINAMIC_FCLK / tstep * percent / 100.0; - - return (uint32_t)tstep; -} - - -// this can use the enable feature over SPI. The dedicated pin must be in the enable mode, -// but that can be hardwired that way. -void TrinamicDriver :: set_disable(bool disable) { - //grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "%s Axis disable %d", _axis_name, disable); - - digitalWrite(disable_pin, disable); - -#ifdef USE_TRINAMIC_ENABLE - if (disable) - tmcstepper->toff(TRINAMIC_TOFF_DISABLE); - else { - if (_mode == TRINAMIC_MODE_STEALTHCHOP) - tmcstepper->toff(TRINAMIC_TOFF_STEALTHCHOP); - else - tmcstepper->toff(TRINAMIC_TOFF_COOLSTEP); - } -#endif - // the pin based enable could be added here. - // This would be for individual motors, not the single pin for all motors. -} - diff --git a/Grbl_Esp32/Motors/TrinamicDriverClass.h b/Grbl_Esp32/Motors/TrinamicDriverClass.h deleted file mode 100644 index b31e2195..00000000 --- a/Grbl_Esp32/Motors/TrinamicDriverClass.h +++ /dev/null @@ -1,64 +0,0 @@ -/* - TrinamicDriverClass.h - - Part of Grbl_ESP32 - - 2020 - Bart Dring - - 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 . -*/ - -#define TRINAMIC_MODE_STEALTHCHOP 0 // very quiet -#define TRINAMIC_MODE_COOLSTEP 1 // everything runs cooler so higher current possible -#define TRINAMIC_MODE_STALLGUARD 2 // coolstep plus generates stall indication - -#define NORMAL_TCOOLTHRS 0xFFFFF // 20 bit is max -#define NORMAL_THIGH 0 - -#define TMC2130_RSENSE_DEFAULT 0.11f -#define TMC5160_RSENSE_DEFAULT 0.075f - -#define TRINAMIC_SPI_FREQ 100000 - -#define TRINAMIC_FCLK 12700000.0 // Internal clock Approx (Hz) used to calculate TSTEP from homing rate - -// ==== defaults OK to define them in your machine definition ==== -#ifndef TRINAMIC_RUN_MODE - #define TRINAMIC_RUN_MODE TRINAMIC_MODE_COOLSTEP -#endif - -#ifndef TRINAMIC_HOMING_MODE - #define TRINAMIC_HOMING_MODE TRINAMIC_RUN_MODE -#endif - - -#ifndef TRINAMIC_TOFF_DISABLE - #define TRINAMIC_TOFF_DISABLE 0 -#endif - -#ifndef TRINAMIC_TOFF_STEALTHCHOP - #define TRINAMIC_TOFF_STEALTHCHOP 5 -#endif - -#ifndef TRINAMIC_TOFF_COOLSTEP - #define TRINAMIC_TOFF_COOLSTEP 3 -#endif - - - -#ifndef TRINAMICDRIVERCLASS_H -#define TRINAMICDRIVERCLASS_H - -#include "MotorClass.h" -#include // https://github.com/teemuatlut/TMCStepper - -#endif diff --git a/Grbl_Esp32/Motors/UnipolarMotorClass.cpp b/Grbl_Esp32/Motors/UnipolarMotorClass.cpp deleted file mode 100644 index cd00218c..00000000 --- a/Grbl_Esp32/Motors/UnipolarMotorClass.cpp +++ /dev/null @@ -1,146 +0,0 @@ -UnipolarMotor :: UnipolarMotor() { - -} - - -UnipolarMotor :: UnipolarMotor(uint8_t axis_index, uint8_t pin_phase0, uint8_t pin_phase1, uint8_t pin_phase2, uint8_t pin_phase3) { - type_id = UNIPOLAR_MOTOR; - this->axis_index = axis_index % MAX_AXES; - this->dual_axis_index = axis_index < MAX_AXES ? 0 : 1; // 0 = primary 1 = ganged - _pin_phase0 = pin_phase0; - _pin_phase1 = pin_phase1; - _pin_phase2 = pin_phase2; - _pin_phase3 = pin_phase3; - - _half_step = true; // TODO read from settings ... microstep > 1 = half step - - set_axis_name(); - init(); - config_message(); -} - -void UnipolarMotor :: init() { - pinMode(_pin_phase0, OUTPUT); - pinMode(_pin_phase1, OUTPUT); - pinMode(_pin_phase2, OUTPUT); - pinMode(_pin_phase3, OUTPUT); - _current_phase = 0; -} - -void UnipolarMotor :: config_message() { - grbl_msg_sendf(CLIENT_SERIAL, - MSG_LEVEL_INFO, - "%s Axis unipolar stepper motor Ph0:%s Ph1:%s Ph2:%s Ph3:%s", - _axis_name, - pinName(_pin_phase0).c_str(), - pinName(_pin_phase1).c_str(), - pinName(_pin_phase2).c_str(), - pinName(_pin_phase3).c_str()); -} - -void UnipolarMotor :: set_disable(bool disable) { - if (disable) { - digitalWrite(_pin_phase0, 0); - digitalWrite(_pin_phase1, 0); - digitalWrite(_pin_phase2, 0); - digitalWrite(_pin_phase3, 0); - } - _enabled = !disable; -} - -void UnipolarMotor::step(uint8_t step_mask, uint8_t dir_mask) { - uint8_t _phase[8] = {0, 0, 0, 0, 0, 0, 0, 0}; // temporary phase values...all start as off - uint8_t phase_max; - - if (!(step_mask & bit(axis_index))) - return; // a step is not required on this interrupt - - if (!_enabled) - return; // don't do anything, phase is not changed or lost - - if (_half_step) - phase_max = 7; - else - phase_max = 3; - - if (dir_mask & bit(axis_index)) { // count up - if (_current_phase == phase_max) - _current_phase = 0; - else - _current_phase++; - } else { // count down - if (_current_phase == 0) - _current_phase = phase_max; - else - _current_phase--; - } - /* - 8 Step : A – AB – B – BC – C – CD – D – DA - 4 Step : AB – BC – CD – DA - - Step IN4 IN3 IN2 IN1 - A 0 0 0 1 - AB 0 0 1 1 - B 0 0 1 0 - BC 0 1 1 0 - C 0 1 0 0 - CD 1 1 0 0 - D 1 0 0 0 - DA 1 0 0 1 - */ - if (_half_step) { - switch (_current_phase) { - case 0: - _phase[0] = 1; - break; - case 1: - _phase[0] = 1; - _phase[1] = 1; - break; - case 2: - _phase[1] = 1; - break; - case 3: - _phase[1] = 1; - _phase[2] = 1; - break; - case 4: - _phase[2] = 1; - break; - case 5: - _phase[2] = 1; - _phase[3] = 1; - break; - case 6: - _phase[3] = 1; - break; - case 7: - _phase[3] = 1; - _phase[0] = 1; - break; - } - } else { - switch (_current_phase) { - case 0: - _phase[0] = 1; - _phase[1] = 1; - break; - case 1: - _phase[1] = 1; - _phase[2] = 1; - break; - case 2: - _phase[2] = 1; - _phase[3] = 1; - break; - case 3: - _phase[3] = 1; - _phase[0] = 1; - break; - } - } - digitalWrite(_pin_phase0, _phase[0]); - digitalWrite(_pin_phase1, _phase[1]); - digitalWrite(_pin_phase2, _phase[2]); - digitalWrite(_pin_phase3, _phase[3]); -} diff --git a/Grbl_Esp32/SettingsClass.h b/Grbl_Esp32/SettingsClass.h deleted file mode 100644 index a7b2c819..00000000 --- a/Grbl_Esp32/SettingsClass.h +++ /dev/null @@ -1,351 +0,0 @@ -#pragma once -#include "JSONencoder.h" -#include -#include -#include "espresponse.h" - -// Command::List is a linked list of all settings, -// so common code can enumerate them. -class Command; -// extern Command *CommandsList; - -// This abstract class defines the generic interface that -// is used to set and get values for all settings independent -// of their underlying data type. The values are always -// represented as human-readable strings. This generic -// interface is used for managing settings via the user interface. - -// Derived classes implement these generic functions for different -// kinds of data. Code that accesses settings should use only these -// generic functions and should not use derived classes directly. - -enum { - NO_AXIS = 255, -}; -typedef enum : uint8_t { - GRBL = 1, // Classic GRBL settings like $100 - EXTENDED, // Settings added by early versions of Grbl_Esp32 - WEBSET, // Settings for ESP3D_WebUI, stored in NVS - GRBLCMD, // Non-persistent GRBL commands like $H - WEBCMD, // ESP3D_WebUI commands that are not directly settings -} type_t; -typedef enum : uint8_t { - WG, // Readable and writable as guest - WU, // Readable and writable as user and admin - WA, // Readable as user and admin, writable as admin -} permissions_t; -typedef uint8_t axis_t; - -class Word { -protected: - const char* _description; - const char* _grblName; - const char* _fullName; - type_t _type; - permissions_t _permissions; -public: - Word(type_t type, permissions_t permissions, const char *description, const char * grblName, const char* fullName); - type_t getType() { return _type; } - permissions_t getPermissions() { return _permissions; } - const char* getName() { return _fullName; } - const char* getGrblName() { return _grblName; } - const char* getDescription() { return _description; } -}; - -class Command : public Word { -protected: - Command *link; // linked list of setting objects -public: - static Command* List; - Command* next() { return link; } - - ~Command() {} - Command(const char *description, type_t type, permissions_t permissions, const char * grblName, const char* fullName); - - // The default implementation of addWebui() does nothing. - // Derived classes may override it to do something. - virtual void addWebui(JSONencoder *) {}; - - virtual err_t action(char* value, auth_t auth_level, ESPResponseStream* out) =0; -}; - -class Setting : public Word { -private: -protected: - static nvs_handle _handle; - // group_t _group; - axis_t _axis = NO_AXIS; - Setting *link; // linked list of setting objects - - bool (*_checker)(char *); - const char* _keyName; -public: - static void init(); - static Setting* List; - Setting* next() { return link; } - - err_t check(char *s); - - static err_t report_nvs_stats(const char* value, auth_t auth_level, ESPResponseStream* out) { - nvs_stats_t stats; - if (err_t err = nvs_get_stats(NULL, &stats)) - return err; - grbl_sendf(out->client(), "[MSG: NVS Used: %d Free: %d Total: %d]\r\n", - stats.used_entries, stats.free_entries, stats.total_entries); -#if 0 // The SDK we use does not have this yet - nvs_iterator_t it = nvs_entry_find(NULL, NULL, NVS_TYPE_ANY); - while (it != NULL) { - nvs_entry_info_t info; - nvs_entry_info(it, &info); - it = nvs_entry_next(it); - grbl_sendf(out->client(), "namespace %s key '%s', type '%d' \n", info.namespace_name, info.key, info.type); - } -#endif - return STATUS_OK; - } - - static err_t eraseNVS(const char* value, auth_t auth_level, ESPResponseStream* out) { - nvs_erase_all(_handle); - // return STATUS_OK; - return 0; - } - - ~Setting() {} - // Setting(const char *description, group_t group, const char * grblName, const char* fullName, bool (*checker)(char *)); - Setting(const char *description, type_t type, permissions_t permissions, const char * grblName, const char* fullName, bool (*checker)(char *)); - axis_t getAxis() { return _axis; } - void setAxis(axis_t axis) { _axis = axis; } - - // load() reads the backing store to get the current - // value of the setting. This could be slow so it - // should be done infrequently, typically once at startup. - virtual void load() {}; - virtual void setDefault() {}; - - // The default implementation of addWebui() does nothing. - // Derived classes may override it to do something. - virtual void addWebui(JSONencoder *) {}; - - virtual err_t setStringValue(char* value) =0; - err_t setStringValue(String s) { return setStringValue(s.c_str()); } - virtual const char* getStringValue() =0; - virtual const char* getCompatibleValue() { return getStringValue(); } -}; - -class IntSetting : public Setting { -private: - int32_t _defaultValue; - int32_t _currentValue; - int32_t _storedValue; - int32_t _minValue; - int32_t _maxValue; - -public: - IntSetting(const char *description, type_t type, permissions_t permissions, const char* grblName, const char* name, int32_t defVal, int32_t minVal, int32_t maxVal, bool (*checker)(char *)); - - IntSetting(type_t type, permissions_t permissions, const char* grblName, const char* name, int32_t defVal, int32_t minVal, int32_t maxVal, bool (*checker)(char *) = NULL) - : IntSetting(NULL, type, permissions, grblName, name, defVal, minVal, maxVal, checker) - { } - - void load(); - void setDefault(); - void addWebui(JSONencoder *); - err_t setStringValue(char* value); - const char* getStringValue(); - - int32_t get() { return _currentValue; } -}; - -class AxisMaskSetting : public Setting { -private: - int32_t _defaultValue; - int32_t _currentValue; - int32_t _storedValue; - -public: - AxisMaskSetting(const char *description, type_t type, permissions_t permissions, const char* grblName, const char* name, int32_t defVal, bool (*checker)(char *)); - - AxisMaskSetting(type_t type, permissions_t permissions, const char* grblName, const char* name, int32_t defVal, bool (*checker)(char *) = NULL) - : AxisMaskSetting(NULL, type, permissions, grblName, name, defVal, checker) - { } - - void load(); - void setDefault(); - void addWebui(JSONencoder *); - err_t setStringValue(char* value); - const char* getCompatibleValue(); - const char* getStringValue(); - - int32_t get() { return _currentValue; } -}; - -class FloatSetting : public Setting { -private: - float _defaultValue; - float _currentValue; - float _storedValue; - float _minValue; - float _maxValue; -public: - FloatSetting(const char *description, type_t type, permissions_t permissions, const char* grblName, const char* name, float defVal, float minVal, float maxVal, bool (*checker)(char *)); - - FloatSetting(type_t type, permissions_t permissions, const char* grblName, const char* name, float defVal, float minVal, float maxVal, bool (*checker)(char *) = NULL) - : FloatSetting(NULL, type, permissions, grblName, name, defVal, minVal, maxVal, checker) - { } - - void load(); - void setDefault(); - // There are no Float settings in WebUI - void addWebui(JSONencoder *) {} - err_t setStringValue(char* value); - const char* getStringValue(); - - float get() { return _currentValue; } -}; - -#define MAX_SETTING_STRING 256 -class StringSetting : public Setting { -private: - String _defaultValue; - String _currentValue; - String _storedValue; - int _minLength; - int _maxLength; - void _setStoredValue(const char *s); -public: - StringSetting(const char *description, type_t type, permissions_t permissions, const char* grblName, const char* name, const char* defVal, int min, int max, bool (*checker)(char *)); - - StringSetting(type_t type, permissions_t permissions, const char* grblName, const char* name, const char* defVal, bool (*checker)(char *) = NULL) - : StringSetting(NULL, type, permissions, grblName, name, defVal, 0, 0, checker) - { }; - - void load(); - void setDefault(); - void addWebui(JSONencoder *); - err_t setStringValue(char* value); - const char* getStringValue(); - - const char* get() { return _currentValue.c_str(); } -}; -struct cmp_str -{ - bool operator()(char const *a, char const *b) const - { - return strcasecmp(a, b) < 0; - } -}; -typedef std::map enum_opt_t; - -class EnumSetting : public Setting { -private: - int8_t _defaultValue; - int8_t _storedValue; - int8_t _currentValue; - std::map* _options; -public: - EnumSetting(const char *description, type_t type, permissions_t permissions, const char* grblName, const char* name, int8_t defVal, enum_opt_t* opts); - - EnumSetting(type_t type, permissions_t permissions, const char* grblName, const char* name, int8_t defVal, enum_opt_t* opts) : - EnumSetting(NULL, type, permissions, grblName, name, defVal, opts) - { } - - void load(); - void setDefault(); - void addWebui(JSONencoder *); - err_t setStringValue(char* value); - const char* getStringValue(); - - int8_t get() { return _currentValue; } -}; - -class FlagSetting : public Setting { -private: - bool _defaultValue; - int8_t _storedValue; - bool _currentValue; -public: - FlagSetting(const char *description, type_t type, permissions_t permissions, const char* grblName, const char* name, bool defVal, bool (*checker)(char *)); - FlagSetting(type_t type, permissions_t permissions, const char* grblName, const char* name, bool defVal, bool (*checker)(char *) = NULL) - : FlagSetting(NULL, type, permissions, grblName, name, defVal, checker) - { } - - void load(); - void setDefault(); - // There are no Flag settings in WebUI - // The booleans are expressed as Enums - void addWebui(JSONencoder *) {} - err_t setStringValue(char* value); - const char* getCompatibleValue(); - const char* getStringValue(); - - bool get() { return _currentValue; } -}; - -class IPaddrSetting : public Setting { -private: - uint32_t _defaultValue; - uint32_t _currentValue; - uint32_t _storedValue; - -public: - IPaddrSetting(const char *description, type_t type, permissions_t permissions, const char * grblName, const char* name, uint32_t defVal, bool (*checker)(char *)); - IPaddrSetting(const char *description, type_t type, permissions_t permissions, const char * grblName, const char* name, const char *defVal, bool (*checker)(char *)); - - void load(); - void setDefault(); - void addWebui(JSONencoder *); - err_t setStringValue(char* value); - const char* getStringValue(); - - uint32_t get() { return _currentValue; } -}; - -class AxisSettings { -public: - const char* name; - FloatSetting *steps_per_mm; - FloatSetting *max_rate; - FloatSetting *acceleration; - FloatSetting *max_travel; - FloatSetting *run_current; - FloatSetting *hold_current; - IntSetting *microsteps; - IntSetting *stallguard; - - AxisSettings(const char *axisName); -}; -class WebCommand : public Command { - private: - err_t (*_action)(char *, auth_t); - const char* password; - public: - WebCommand(const char* description, type_t type, permissions_t permissions, const char * grblName, const char* name, err_t (*action)(char *, auth_t)) : - Command(description, type, permissions, grblName, name), - _action(action) - {} - err_t action(char* value, auth_t auth_level, ESPResponseStream* response); -}; - -enum : uint8_t { - ANY_STATE = 0, - IDLE_OR_ALARM = 0xff & ~STATE_ALARM, - IDLE_OR_JOG = 0xff & ~STATE_JOG, - NOT_CYCLE_OR_HOLD = STATE_CYCLE | STATE_HOLD, -}; - -class GrblCommand : public Command { - private: - err_t (*_action)(const char *, auth_t, ESPResponseStream*); - uint8_t _disallowedStates; - public: - GrblCommand(const char * grblName, const char* name, err_t (*action)(const char*, auth_t, ESPResponseStream*), uint8_t disallowedStates, permissions_t auth) - : Command(NULL, GRBLCMD, auth, grblName, name) - , _action(action) - , _disallowedStates(disallowedStates) - {} - - GrblCommand(const char * grblName, const char* name, err_t (*action)(const char*, auth_t, ESPResponseStream*), uint8_t disallowedStates) - : GrblCommand(grblName, name, action, disallowedStates, WG) - {} - err_t action(char* value, auth_t auth_level, ESPResponseStream* response); -}; diff --git a/Grbl_Esp32/SettingsDefinitions.cpp b/Grbl_Esp32/SettingsDefinitions.cpp deleted file mode 100644 index 9c10c1a2..00000000 --- a/Grbl_Esp32/SettingsDefinitions.cpp +++ /dev/null @@ -1,320 +0,0 @@ -#include "grbl.h" - -bool motorSettingChanged = false; - -StringSetting* startup_line_0; -StringSetting* startup_line_1; -StringSetting* build_info; - -IntSetting* pulse_microseconds; -IntSetting* stepper_idle_lock_time; - -AxisMaskSetting* step_invert_mask; -AxisMaskSetting* dir_invert_mask; -// TODO Settings - need to call st_generate_step_invert_masks; -AxisMaskSetting* homing_dir_mask; -AxisMaskSetting* stallguard_debug_mask; - -FlagSetting* step_enable_invert; -FlagSetting* limit_invert; -FlagSetting* probe_invert; -FlagSetting* report_inches; -FlagSetting* soft_limits; -// TODO Settings - need to check for HOMING_ENABLE -FlagSetting* hard_limits; -// TODO Settings - need to call limits_init; -FlagSetting* homing_enable; -// TODO Settings - also need to clear, but not set, soft_limits -FlagSetting* laser_mode; -// TODO Settings - also need to call my_spindle->init; - -IntSetting* status_mask; -FloatSetting* junction_deviation; -FloatSetting* arc_tolerance; - -FloatSetting* homing_feed_rate; -FloatSetting* homing_seek_rate; -FloatSetting* homing_debounce; -FloatSetting* homing_pulloff; -FloatSetting* spindle_pwm_freq; -FloatSetting* rpm_max; -FloatSetting* rpm_min; -FloatSetting* spindle_delay_spinup; -FloatSetting* spindle_delay_spindown; - -FloatSetting* spindle_pwm_off_value; -FloatSetting* spindle_pwm_min_value; -FloatSetting* spindle_pwm_max_value; -IntSetting* spindle_pwm_bit_precision; - -EnumSetting* spindle_type; - -enum_opt_t spindleTypes = { - { "NONE", SPINDLE_TYPE_NONE, }, - { "PWM", SPINDLE_TYPE_PWM, }, - { "RELAY", SPINDLE_TYPE_RELAY, }, - { "LASER", SPINDLE_TYPE_LASER, }, - { "DAC", SPINDLE_TYPE_DAC, }, - { "HUANYANG", SPINDLE_TYPE_HUANYANG, }, - { "BESC", SPINDLE_TYPE_BESC, }, - { "10V", SPINDLE_TYPE_10V, }, -}; - -AxisSettings* x_axis_settings; -AxisSettings* y_axis_settings; -AxisSettings* z_axis_settings; -AxisSettings* a_axis_settings; -AxisSettings* b_axis_settings; -AxisSettings* c_axis_settings; - -AxisSettings* axis_settings[MAX_N_AXIS]; - -typedef struct { - const char* name; - float steps_per_mm; - float max_rate; - float acceleration; - float max_travel; - float run_current; - float hold_current; - uint16_t microsteps; - uint16_t stallguard; -} axis_defaults_t; -axis_defaults_t axis_defaults[] = { - { - "X", - DEFAULT_X_STEPS_PER_MM, - DEFAULT_X_MAX_RATE, - DEFAULT_X_ACCELERATION, - DEFAULT_X_MAX_TRAVEL, - DEFAULT_X_CURRENT, - DEFAULT_X_HOLD_CURRENT, - DEFAULT_X_MICROSTEPS, - DEFAULT_X_STALLGUARD - }, - { - "Y", - DEFAULT_Y_STEPS_PER_MM, - DEFAULT_Y_MAX_RATE, - DEFAULT_Y_ACCELERATION, - DEFAULT_Y_MAX_TRAVEL, - DEFAULT_Y_CURRENT, - DEFAULT_Y_HOLD_CURRENT, - DEFAULT_Y_MICROSTEPS, - DEFAULT_Y_STALLGUARD - }, - { - "Z", - DEFAULT_Z_STEPS_PER_MM, - DEFAULT_Z_MAX_RATE, - DEFAULT_Z_ACCELERATION, - DEFAULT_Z_MAX_TRAVEL, - DEFAULT_Z_CURRENT, - DEFAULT_Z_HOLD_CURRENT, - DEFAULT_Z_MICROSTEPS, - DEFAULT_Z_STALLGUARD - }, - { - "A", - DEFAULT_A_STEPS_PER_MM, - DEFAULT_A_MAX_RATE, - DEFAULT_A_ACCELERATION, - DEFAULT_A_MAX_TRAVEL, - DEFAULT_A_CURRENT, - DEFAULT_A_HOLD_CURRENT, - DEFAULT_A_MICROSTEPS, - DEFAULT_A_STALLGUARD - }, - { - "B", - DEFAULT_B_STEPS_PER_MM, - DEFAULT_B_MAX_RATE, - DEFAULT_B_ACCELERATION, - DEFAULT_B_MAX_TRAVEL, - DEFAULT_B_CURRENT, - DEFAULT_B_HOLD_CURRENT, - DEFAULT_B_MICROSTEPS, - DEFAULT_B_STALLGUARD - }, - { - "C", - DEFAULT_C_STEPS_PER_MM, - DEFAULT_C_MAX_RATE, - DEFAULT_C_ACCELERATION, - DEFAULT_C_MAX_TRAVEL, - DEFAULT_C_CURRENT, - DEFAULT_C_HOLD_CURRENT, - DEFAULT_C_MICROSTEPS, - DEFAULT_C_STALLGUARD - } -}; - -// Construct e.g. X_MAX_RATE from axisName "X" and tail "_MAX_RATE" -// in dynamically allocated memory that will not be freed. - -static const char *makename(const char *axisName, const char *tail) { - char* retval = (char *)malloc(strlen(axisName) + strlen(tail) + 2); - - strcpy(retval, axisName); - strcat(retval, "/"); - return strcat(retval, tail); -} - -static bool checkStartupLine(char* value) { - if (sys.state != STATE_IDLE) - return STATUS_IDLE_ERROR; - return gc_execute_line(value, CLIENT_SERIAL) == 0; -} - -static bool checkStallguard(char* value) { - motorSettingChanged = true; - return true; -} - -static bool checkMicrosteps(char* value) { - motorSettingChanged = true; - return true; -} - -static bool checkRunCurrent(char* value) { - motorSettingChanged = true; - return true; -} - -static bool checkHoldcurrent(char* value) { - motorSettingChanged = true; - return true; -} - - -static bool checkStallguardDebugMask(char* val) { - motorSettingChanged = true; - return true; -} - -// Generates a string like "122" from axisNum 2 and base 120 -static const char* makeGrblName(int axisNum, int base) { - // To omit A,B,C axes: - // if (axisNum > 2) return NULL; - char buf[4]; - snprintf(buf, 4, "%d", axisNum + base); - char* retval = (char*)malloc(strlen(buf)); - return strcpy(retval, buf); -} - -void make_settings() { - Setting::init(); - - // Create the axis settings in the order that people are - // accustomed to seeing. - int axis; - axis_defaults_t* def; - for (axis = 0; axis < N_AXIS; axis++) { - def = &axis_defaults[axis]; - axis_settings[axis] = new AxisSettings(def->name); - } - x_axis_settings = axis_settings[X_AXIS]; - y_axis_settings = axis_settings[Y_AXIS]; - z_axis_settings = axis_settings[Z_AXIS]; - a_axis_settings = axis_settings[A_AXIS]; - b_axis_settings = axis_settings[B_AXIS]; - c_axis_settings = axis_settings[C_AXIS]; - for (axis = N_AXIS - 1; axis >= 0; axis--) { - def = &axis_defaults[axis]; - auto setting = new IntSetting(EXTENDED, WG, makeGrblName(axis, 170), makename(def->name, "StallGuard"), def->stallguard, -64, 63, checkStallguard); - setting->setAxis(axis); - axis_settings[axis]->stallguard = setting; - } - for (axis = N_AXIS - 1; axis >= 0; axis--) { - def = &axis_defaults[axis]; - auto setting = new IntSetting(EXTENDED, WG, makeGrblName(axis, 160), makename(def->name, "Microsteps"), def->microsteps, 0, 256, checkMicrosteps); - setting->setAxis(axis); - axis_settings[axis]->microsteps = setting; - } - for (axis = N_AXIS - 1; axis >= 0; axis--) { - def = &axis_defaults[axis]; - auto setting = new FloatSetting(EXTENDED, WG, makeGrblName(axis, 150), makename(def->name, "Current/Hold"), def->hold_current, 0.05, 20.0, checkHoldcurrent); // Amps - setting->setAxis(axis); - axis_settings[axis]->hold_current = setting; - } - for (axis = N_AXIS - 1; axis >= 0; axis--) { - def = &axis_defaults[axis]; - auto setting = new FloatSetting(EXTENDED, WG, makeGrblName(axis, 140), makename(def->name, "Current/Run"), def->run_current, 0.0, 20.0, checkRunCurrent); // Amps - setting->setAxis(axis); - axis_settings[axis]->run_current = setting; - } - for (axis = 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); - setting->setAxis(axis); - axis_settings[axis]->max_travel = setting; - } - for (axis = N_AXIS - 1; axis >= 0; axis--) { - def = &axis_defaults[axis]; - auto setting = new FloatSetting(GRBL, WG, makeGrblName(axis, 120), makename(def->name, "Acceleration"), def->acceleration, 1.0, 100000.0); - setting->setAxis(axis); - axis_settings[axis]->acceleration = setting; - } - for (axis = N_AXIS - 1; axis >= 0; axis--) { - def = &axis_defaults[axis]; - auto setting = new FloatSetting(GRBL, WG, makeGrblName(axis, 110), makename(def->name, "MaxRate"), def->max_rate, 1.0, 100000.0); - setting->setAxis(axis); - axis_settings[axis]->max_rate = setting; - } - for (axis = N_AXIS - 1; axis >= 0; axis--) { - def = &axis_defaults[axis]; - auto setting = new FloatSetting(GRBL, WG, makeGrblName(axis, 100), makename(def->name, "StepsPerMm"), def->steps_per_mm, 1.0, 100000.0); - setting->setAxis(axis); - axis_settings[axis]->steps_per_mm = setting; - } - - // Spindle Settings - spindle_pwm_max_value = new FloatSetting(EXTENDED, WG, "36", "Spindle/PWM/Max", DEFAULT_SPINDLE_MAX_VALUE, 0.0, 100.0); - spindle_pwm_min_value = new FloatSetting(EXTENDED, WG, "35", "Spindle/PWM/Min", DEFAULT_SPINDLE_MIN_VALUE, 0.0, 100.0); - spindle_pwm_off_value = new FloatSetting(EXTENDED, WG, "34", "Spindle/PWM/Off", DEFAULT_SPINDLE_OFF_VALUE, 0.0, 100.0); // these are percentages - // IntSetting spindle_pwm_bit_precision(EXTENDED, WG, "Spindle/PWM/Precision", DEFAULT_SPINDLE_BIT_PRECISION, 1, 16); - spindle_pwm_freq = new FloatSetting(EXTENDED, WG, "33", "Spindle/PWM/Frequency", DEFAULT_SPINDLE_FREQ, 0, 100000); - spindle_delay_spinup = new FloatSetting(EXTENDED, WG, NULL, "Spindle/Delay/SpinUp", DEFAULT_SPINDLE_DELAY_SPINUP, 0, 30); - spindle_delay_spindown = new FloatSetting(EXTENDED, WG, NULL, "Spindle/Delay/SpinDown", DEFAULT_SPINDLE_DELAY_SPINUP, 0, 30); - - // GRBL Non-numbered settings - startup_line_0 = new StringSetting(GRBL, WG, "N0", "GCode/Line0", "", checkStartupLine); - startup_line_1 = new StringSetting(GRBL, WG, "N1", "GCode/Line1", "", checkStartupLine); - - // GRBL Numbered Settings - laser_mode = new FlagSetting(GRBL, WG, "32", "GCode/LaserMode", DEFAULT_LASER_MODE); - // TODO Settings - also need to call my_spindle->init(); - rpm_min = new FloatSetting(GRBL, WG, "31", "GCode/MinS", DEFAULT_SPINDLE_RPM_MIN, 0, 100000); - rpm_max = new FloatSetting(GRBL, WG, "30", "GCode/MaxS", DEFAULT_SPINDLE_RPM_MAX, 0, 100000); - - - homing_pulloff = new FloatSetting(GRBL, WG, "27", "Homing/Pulloff", DEFAULT_HOMING_PULLOFF, 0, 1000); - homing_debounce = new FloatSetting(GRBL, WG, "26", "Homing/Debounce", DEFAULT_HOMING_DEBOUNCE_DELAY, 0, 10000); - homing_seek_rate = new FloatSetting(GRBL, WG, "25", "Homing/Seek", DEFAULT_HOMING_SEEK_RATE, 0, 10000); - homing_feed_rate = new FloatSetting(GRBL, WG, "24", "Homing/Feed", DEFAULT_HOMING_FEED_RATE, 0, 10000); - - // TODO Settings - need to call st_generate_step_invert_masks() - homing_dir_mask = new AxisMaskSetting(GRBL, WG, "23", "Homing/DirInvert", DEFAULT_HOMING_DIR_MASK); - - // TODO Settings - need to call limits_init(); - homing_enable = new FlagSetting(GRBL, WG, "22", "Homing/Enable", DEFAULT_HOMING_ENABLE); - // TODO Settings - need to check for HOMING_ENABLE - hard_limits = new FlagSetting(GRBL, WG, "21", "Limits/Hard", DEFAULT_HARD_LIMIT_ENABLE); - soft_limits = new FlagSetting(GRBL, WG, "20", "Limits/Soft", DEFAULT_SOFT_LIMIT_ENABLE, NULL); - - report_inches = new FlagSetting(GRBL, WG, "13", "Report/Inches", DEFAULT_REPORT_INCHES); - // TODO Settings - also need to clear, but not set, soft_limits - arc_tolerance = new FloatSetting(GRBL, WG, "12", "GCode/ArcTolerance", DEFAULT_ARC_TOLERANCE, 0, 1); - junction_deviation = new FloatSetting(GRBL, WG, "11", "GCode/JunctionDeviation", DEFAULT_JUNCTION_DEVIATION, 0, 10); - status_mask = new IntSetting(GRBL, WG, "10", "Report/Status", DEFAULT_STATUS_REPORT_MASK, 0, 2); - - probe_invert = new FlagSetting(GRBL, WG, "6", "Probe/Invert", DEFAULT_INVERT_PROBE_PIN); - limit_invert = new FlagSetting(GRBL, WG, "5", "Limits/Invert", DEFAULT_INVERT_LIMIT_PINS); - step_enable_invert = new FlagSetting(GRBL, WG, "4", "Stepper/EnableInvert", DEFAULT_INVERT_ST_ENABLE); - dir_invert_mask = new AxisMaskSetting(GRBL, WG, "3", "Stepper/DirInvert", DEFAULT_DIRECTION_INVERT_MASK); - step_invert_mask = new AxisMaskSetting(GRBL, WG, "2", "Stepper/StepInvert", DEFAULT_STEPPING_INVERT_MASK); - stepper_idle_lock_time = new IntSetting(GRBL, WG, "1", "Stepper/IdleTime", DEFAULT_STEPPER_IDLE_LOCK_TIME, 0, 255); - pulse_microseconds = new IntSetting(GRBL, WG, "0", "Stepper/Pulse", DEFAULT_STEP_PULSE_MICROSECONDS, 3, 1000); - spindle_type = new EnumSetting(NULL, EXTENDED, WG, NULL, "Spindle/Type", SPINDLE_TYPE, &spindleTypes); - stallguard_debug_mask = new AxisMaskSetting(EXTENDED, WG, NULL, "Report/StallGuard", 0, checkStallguardDebugMask); -} diff --git a/Grbl_Esp32/Spindles/10vSpindle.cpp b/Grbl_Esp32/Spindles/10vSpindle.cpp deleted file mode 100644 index d210a26a..00000000 --- a/Grbl_Esp32/Spindles/10vSpindle.cpp +++ /dev/null @@ -1,172 +0,0 @@ -/* - 10vSpindle.cpp - - - This is basically a PWM spindle with some changes, so a separate forward and - reverse signal can be sent. - - The direction pins will act as enables for the 2 directions. There is usually - a min RPM with VFDs, that speed will remain even if speed is 0. You - must turn off both direction pins when enable is off. - - - Part of Grbl_ESP32 - 2020 - Bart Dring - - 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 . - - - -*/ -#include "SpindleClass.h" - - - -void _10vSpindle :: init() { - - get_pins_and_settings(); // these gets the standard PWM settings, but many need to be changed for BESC - - // a couple more pins not inherited from PWM Spindle -#ifdef SPINDLE_FORWARD_PIN - _forward_pin = SPINDLE_FORWARD_PIN; -#else - _forward_pin = UNDEFINED_PIN; -#endif - -#ifdef SPINDLE_REVERSE_PIN - _reverse_pin = SPINDLE_REVERSE_PIN; -#else - _reverse_pin = UNDEFINED_PIN; -#endif - - - if (_output_pin == UNDEFINED_PIN) { - grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Warning: BESC output pin not defined"); - return; // We cannot continue without the output pin - } - - ledcSetup(_spindle_pwm_chan_num, (double)_pwm_freq, _pwm_precision); // setup the channel - ledcAttachPin(_output_pin, _spindle_pwm_chan_num); // attach the PWM to the pin - - pinMode(_enable_pin, OUTPUT); - pinMode(_direction_pin, OUTPUT); - pinMode(_forward_pin, OUTPUT); - pinMode(_reverse_pin, OUTPUT); - - set_rpm(0); - - config_message(); - - is_reversable = true; // these VFDs are always reversable - use_delays = true; - -} - -// prints the startup message of the spindle config -void _10vSpindle :: config_message() { - grbl_msg_sendf(CLIENT_SERIAL, - MSG_LEVEL_INFO, - "0-10V spindle Out:%s Enbl:%s, Dir:%s, Fwd:%s, Rev:%s, Freq:%dHz Res:%dbits", - pinName(_output_pin).c_str(), - pinName(_enable_pin).c_str(), - pinName(_direction_pin).c_str(), - pinName(_forward_pin).c_str(), - pinName(_reverse_pin).c_str(), - _pwm_freq, - _pwm_precision); -} - -uint32_t _10vSpindle::set_rpm(uint32_t rpm) { - uint32_t pwm_value; - - if (_output_pin == UNDEFINED_PIN) - return rpm; - - // apply speed overrides - rpm = rpm * sys.spindle_speed_ovr / 100; // Scale by spindle speed override value (percent) - - // apply limits limits - if ((_min_rpm >= _max_rpm) || (rpm >= _max_rpm)) - rpm = _max_rpm; - else if (rpm != 0 && rpm <= _min_rpm) - rpm = _min_rpm; - sys.spindle_speed = rpm; - - // determine the pwm value - if (rpm == 0) - pwm_value = _pwm_off_value; - else - pwm_value = map_uint32_t(rpm, _min_rpm, _max_rpm, _pwm_min_value, _pwm_max_value); - - set_output(pwm_value); - return rpm; -} -/* -void _10vSpindle::set_state(uint8_t state, uint32_t rpm) { - if (sys.abort) - return; // Block during abort. - - if (state == SPINDLE_DISABLE) { // Halt or set spindle direction and rpm. - sys.spindle_speed = 0; - stop(); - } else { - set_spindle_dir_pin(state == SPINDLE_ENABLE_CW); - set_rpm(rpm); - } - - set_enable_pin(state != SPINDLE_DISABLE); - - sys.report_ovr_counter = 0; // Set to report change immediately -} - -*/ - - -uint8_t _10vSpindle::get_state() { - if (_current_pwm_duty == 0 || _output_pin == UNDEFINED_PIN) - return (SPINDLE_STATE_DISABLE); - if (_direction_pin != UNDEFINED_PIN) - return digitalRead(_direction_pin) ? SPINDLE_STATE_CW : SPINDLE_STATE_CCW; - return (SPINDLE_STATE_CW); -} - -void _10vSpindle::stop() { - // inverts are delt with in methods - set_enable_pin(false); - set_output(_pwm_off_value); -} - -void _10vSpindle::set_enable_pin(bool enable) { - //grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "_10vSpindle::set_enable_pin"); - if (_off_with_zero_speed && sys.spindle_speed == 0) - enable = false; - -#ifdef INVERT_SPINDLE_ENABLE_PIN - enable = !enable; -#endif - digitalWrite(_enable_pin, enable); - - // turn off anything that acts like an enable - if (!enable) { - digitalWrite(_direction_pin, enable); - digitalWrite(_forward_pin, enable); - digitalWrite(_reverse_pin, enable); - } -} - -void _10vSpindle::set_spindle_dir_pin(bool Clockwise) { - //grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "_10vSpindle::set_spindle_dir_pin"); - digitalWrite(_direction_pin, Clockwise); - digitalWrite(_forward_pin, Clockwise); - digitalWrite(_reverse_pin, ! Clockwise); -} - diff --git a/Grbl_Esp32/Spindles/BESCSpindle.cpp b/Grbl_Esp32/Spindles/BESCSpindle.cpp deleted file mode 100644 index 14d575d7..00000000 --- a/Grbl_Esp32/Spindles/BESCSpindle.cpp +++ /dev/null @@ -1,122 +0,0 @@ -/* - BESCSpindle.cpp - - This a special type of PWM spindle for RC type Brushless DC Speed - controllers. They use a short pulse for off and a longer pulse for - full on. The pulse is always a small portion of the full cycle. - Some BESCs have a special turn on procedure. This may be a one time - procedure or must be done every time. The user must do that via gcode. - - Part of Grbl_ESP32 - 2020 - Bart Dring - - 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 . - - Important ESC Settings - 50 Hz this is a typical frequency for an ESC - Some ESCs can handle higher frequencies, but there is no advantage to changing it. - - Determine the typical min and max pulse length of your ESC - BESC_MIN_PULSE_SECS is typically 1ms (0.001 sec) or less - BESC_MAX_PULSE_SECS is typically 2ms (0.002 sec) or more - -*/ -#include "SpindleClass.h" - - -// don't change these -#define BESC_PWM_FREQ 50.0f // Hz -#define BESC_PWM_BIT_PRECISION 16 // bits -#define BESC_PULSE_PERIOD (1.0 / BESC_PWM_FREQ) - -// Ok to tweak. These are the pulse lengths in seconds -// #define them in your machine definition file if you want different values -#ifndef BESC_MIN_PULSE_SECS - #define BESC_MIN_PULSE_SECS 0.0009f // in seconds -#endif - -#ifndef BESC_MAX_PULSE_SECS - #define BESC_MAX_PULSE_SECS 0.0022f // in seconds -#endif - -//calculations...don't change -#define BESC_MIN_PULSE_CNT (uint16_t)(BESC_MIN_PULSE_SECS / BESC_PULSE_PERIOD * 65535.0) -#define BESC_MAX_PULSE_CNT (uint16_t)(BESC_MAX_PULSE_SECS / BESC_PULSE_PERIOD * 65535.0) - -void BESCSpindle :: init() { - - get_pins_and_settings(); // these gets the standard PWM settings, but many need to be changed for BESC - - if (_output_pin == UNDEFINED_PIN) { - grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Warning: BESC output pin not defined"); - return; // We cannot continue without the output pin - } - - // override some settings to what is required for a BESC - _pwm_freq = (uint32_t)BESC_PWM_FREQ; - _pwm_precision = 16; - - // override these settings - _pwm_off_value = BESC_MIN_PULSE_CNT; - _pwm_min_value = _pwm_off_value; - _pwm_max_value = BESC_MAX_PULSE_CNT; - - ledcSetup(_spindle_pwm_chan_num, (double)_pwm_freq, _pwm_precision); // setup the channel - ledcAttachPin(_output_pin, _spindle_pwm_chan_num); // attach the PWM to the pin - - pinMode(_enable_pin, OUTPUT); - - set_rpm(0); - - use_delays = true; - - config_message(); -} - -// prints the startup message of the spindle config -void BESCSpindle :: config_message() { - grbl_msg_sendf(CLIENT_SERIAL, - MSG_LEVEL_INFO, - "BESC spindle on Pin:%s Min:%0.2fms Max:%0.2fms Freq:%dHz Res:%dbits", - pinName(_output_pin).c_str(), - BESC_MIN_PULSE_SECS * 1000.0, // convert to milliseconds - BESC_MAX_PULSE_SECS * 1000.0, // convert to milliseconds - _pwm_freq, - _pwm_precision); -} - -uint32_t BESCSpindle::set_rpm(uint32_t rpm) { - uint32_t pwm_value; - - if (_output_pin == UNDEFINED_PIN) - return rpm; - - // apply speed overrides - rpm = rpm * sys.spindle_speed_ovr / 100; // Scale by spindle speed override value (percent) - - // apply limits limits - if ((_min_rpm >= _max_rpm) || (rpm >= _max_rpm)) - rpm = _max_rpm; - else if (rpm != 0 && rpm <= _min_rpm) - rpm = _min_rpm; - sys.spindle_speed = rpm; - - // determine the pwm value - if (rpm == 0) { - pwm_value = _pwm_off_value; - } else { - pwm_value = map_uint32_t(rpm, _min_rpm, _max_rpm, _pwm_min_value, _pwm_max_value); - } - - set_output(pwm_value); - return rpm; -} diff --git a/Grbl_Esp32/Spindles/DacSpindle.cpp b/Grbl_Esp32/Spindles/DacSpindle.cpp deleted file mode 100644 index 1e54d638..00000000 --- a/Grbl_Esp32/Spindles/DacSpindle.cpp +++ /dev/null @@ -1,104 +0,0 @@ -/* - DacSpindle.cpp - - This uses the Analog DAC in the ESP32 to generate a voltage - proportional to the GCode S value desired. Some spindle uses - a 0-5V or 0-10V value to control the spindle. You would use - an Op Amp type circuit to get from the 0.3.3V of the ESP32 to that voltage. - - Part of Grbl_ESP32 - 2020 - Bart Dring - - 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 . - -*/ -#include "SpindleClass.h" - -// ======================================== DacSpindle ====================================== -void DacSpindle :: init() { - get_pins_and_settings(); - - if (_output_pin == UNDEFINED_PIN) - return; - - _min_rpm = rpm_min->get(); - _max_rpm = rpm_max->get(); - _pwm_min_value = 0; // not actually PWM...DAC counts - _pwm_max_value = 255; // not actually PWM...DAC counts - _gpio_ok = true; - - if (_output_pin != GPIO_NUM_25 && _output_pin != GPIO_NUM_26) { // DAC can only be used on these pins - _gpio_ok = false; - grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "DAC spindle pin invalid GPIO_NUM_%d (pin 25 or 26 only)", _output_pin); - return; - } - - pinMode(_enable_pin, OUTPUT); - pinMode(_direction_pin, OUTPUT); - - is_reversable = (_direction_pin != UNDEFINED_PIN); - use_delays = true; - - config_message(); -} - -void DacSpindle :: config_message() { - grbl_msg_sendf(CLIENT_SERIAL, - MSG_LEVEL_INFO, - "DAC spindle Output:%s, Enbl:%s, Dir:%s, Res:8bits", - pinName(_output_pin).c_str(), - pinName(_enable_pin).c_str(), - pinName(_direction_pin).c_str()); -} - -uint32_t DacSpindle::set_rpm(uint32_t rpm) { - if (_output_pin == UNDEFINED_PIN) - return rpm; - - uint32_t pwm_value; - - // apply overrides and limits - rpm = rpm * sys.spindle_speed_ovr / 100; // Scale by spindle speed override value (percent) - - // Calculate PWM register value based on rpm max/min settings and programmed rpm. - if ((_min_rpm >= _max_rpm) || (rpm >= _max_rpm)) { - // No PWM range possible. Set simple on/off spindle control pin state. - sys.spindle_speed = _max_rpm; - pwm_value = _pwm_max_value; - } else if (rpm <= _min_rpm) { - if (rpm == 0) { // S0 disables spindle - sys.spindle_speed = 0; - pwm_value = 0; - } else { // Set minimum PWM output - rpm = _min_rpm; - sys.spindle_speed = rpm; - pwm_value = 0; - grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Spindle RPM less than min RPM:%5.2f %d", rpm, pwm_value); - } - } else { - // Compute intermediate PWM value with linear spindle speed model. - // NOTE: A nonlinear model could be installed here, if required, but keep it VERY light-weight. - sys.spindle_speed = rpm; - - pwm_value = map_uint32_t(rpm, _min_rpm, _max_rpm, _pwm_min_value, _pwm_max_value); - } - - set_output(pwm_value); - - return rpm; -} - -void DacSpindle :: set_output(uint32_t duty) { - if (_gpio_ok) { - dacWrite(_output_pin, (uint8_t)duty); - } -} diff --git a/Grbl_Esp32/Spindles/HuanyangSpindle.cpp b/Grbl_Esp32/Spindles/HuanyangSpindle.cpp deleted file mode 100644 index fdd9e81f..00000000 --- a/Grbl_Esp32/Spindles/HuanyangSpindle.cpp +++ /dev/null @@ -1,483 +0,0 @@ -/* - HuanyangSpindle.cpp - - This is for a Huanyang VFD based spindle via RS485 Modbus. - Sorry for the lengthy comments, but finding the details on this - VFD was a PITA. I am just trying to help the next person. - - Part of Grbl_ESP32 - 2020 - Bart Dring - - 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. - - ============================================================================== - - If a user changes state or RPM level, the command to do that is sent. If - the command is not responded to a message is sent to serial that there was - a timeout. If the Grbl is in a critical state, an alarm will be generated and - the machine stopped. - - If there are no commands to execute, various status items will be polled. If there - is no response, it will behave as described above. It will stop any running jobs with - an alarm. - - =============================================================================== - - Protocol Details - - VFD frequencies are in Hz. Multiply by 60 for RPM - - before using spindle, VFD must be setup for RS485 and match your spindle - PD001 2 RS485 Control of run commands - PD002 2 RS485 Control of operating frequency - PD005 400 Maximum frequency Hz (Typical for spindles) - PD011 120 Min Speed (Recommend Aircooled=120 Water=100) - PD014 10 Acceleration time (Test to optimize) - PD015 10 Deceleration time (Test to optimize) - PD023 1 Reverse run enabled - PD142 3.7 Max current Amps (0.8kw=3.7 1.5kw=7.0, 2.2kw=??) - PD143 2 Poles most are 2 (I think this is only used for RPM calc from Hz) - PD163 1 RS485 Address: 1 (Typical. OK to change...see below) - PD164 1 RS485 Baud rate: 9600 (Typical. OK to change...see below) - PD165 3 RS485 Mode: RTU, 8N1 - - The official documentation of the RS485 is horrible. I had to piece it together from - a lot of different sources - - Manuals: https://github.com/RobertOlechowski/Huanyang_VFD/tree/master/Documentations/pdf - Reference: https://github.com/Smoothieware/Smoothieware/blob/edge/src/modules/tools/spindle/HuanyangSpindleControl.cpp - Refernece: https://gist.github.com/Bouni/803492ed0aab3f944066 - VFD settings: https://www.hobbytronics.co.za/Content/external/1159/Spindle_Settings.pdf - Spindle Talker 2 https://github.com/GilchristT/SpindleTalker2/releases - Python https://github.com/RobertOlechowski/Huanyang_VFD - - ========================================================================= - - Commands - ADDR CMD LEN DATA CRC - 0x01 0x03 0x01 0x01 0x31 0x88 Start spindle clockwise - 0x01 0x03 0x01 0x08 0xF1 0x8E Stop spindle - 0x01 0x03 0x01 0x11 0x30 0x44 Start spindle counter-clockwise - - Return values are - 0 = run - 1 = jog - 2 = r/f - 3 = running - 4 = jogging - 5 = r/f - 6 = Braking - 7 = Track start - - ========================================================================== - - Setting RPM - ADDR CMD LEN DATA CRC - 0x01 0x05 0x02 0x09 0xC4 0xBF 0x0F Write Frequency (0x9C4 = 2500 = 25.00HZ) - - Response is same as data sent - - ========================================================================== - - Status registers - Addr Read Len Reg DataH DataL CRC CRC - 0x01 0x04 0x03 0x00 0x00 0x00 CRC CRC // Set Frequency * 100 (25Hz = 2500) - 0x01 0x04 0x03 0x01 0x00 0x00 CRC CRC // Ouput Frequency * 100 - 0x01 0x04 0x03 0x02 0x00 0x00 CRC CRC // Ouput Amps * 10 - 0x01 0x04 0x03 0x03 0x00 0x00 0xF0 0x4E // Read RPM (example CRC shown) - 0x01 0x04 0x03 0x0 0x00 0x00 CRC CRC // DC voltage - 0x01 0x04 0x03 0x05 0x00 0x00 CRC CRC // AC voltage - 0x01 0x04 0x03 0x06 0x00 0x00 CRC CRC // Cont - 0x01 0x04 0x03 0x07 0x00 0x00 CRC CRC // VFD Temp - Message is returned with requested value = (DataH * 16) + DataL (see decimal offset above) - - TODO: - Move CRC Calc to task to free up main task - - -*/ -#include "SpindleClass.h" - -#include "driver/uart.h" - -#define HUANYANG_UART_PORT UART_NUM_2 // hard coded for this port right now -#define ECHO_TEST_CTS UART_PIN_NO_CHANGE // CTS pin is not used -#define HUANYANG_BUF_SIZE 127 -#define HUANYANG_QUEUE_SIZE 10 // numv\ber of commands that can be queued up. -#define RESPONSE_WAIT_TICKS 50 // how long to wait for a response -#define HUANYANG_MAX_MSG_SIZE 16 // more than enough for a modbus message -#define HUANYANG_POLL_RATE 200 // in milliseconds betwwen commands - -// OK to change these -// #define them in your machine definition file if you want different values -#ifndef HUANYANG_ADDR - #define HUANYANG_ADDR 0x01 -#endif - -#ifndef HUANYANG_BAUD_RATE - #define HUANYANG_BAUD_RATE 9600 // PD164 setting -#endif - -// communication task and queue stuff -typedef struct { - uint8_t tx_length; - uint8_t rx_length; - bool critical; - char msg[HUANYANG_MAX_MSG_SIZE]; -} hy_command_t; - -typedef enum : uint8_t { - READ_SET_FREQ = 0, // The set frequency - READ_OUTPUT_FREQ = 1, // The current operating frequency - READ_OUTPUT_AMPS = 2, // - READ_SET_RPM = 3, // This is the last requested freq even in off mode - READ_DC_VOLTAGE = 4, // - READ_AC_VOLTAGE = 5, // - READ_CONT = 6, // counting value??? - READ_TEMP = 7, // -} read_register_t; - -QueueHandle_t hy_cmd_queue; - -static TaskHandle_t vfd_cmdTaskHandle = 0; - -bool hy_spindle_ok = true; - -// The communications task -void vfd_cmd_task(void* pvParameters) { - static bool unresponsive = false; // to pop off a message once each time it becomes unresponsive - uint8_t reg_item = 0x00; - hy_command_t next_cmd; - uint8_t rx_message[HUANYANG_MAX_MSG_SIZE]; - - while (true) { - if (xQueueReceive(hy_cmd_queue, &next_cmd, 0) == pdTRUE) { - - uart_flush(HUANYANG_UART_PORT); - //report_hex_msg(next_cmd.msg, "Tx: ", next_cmd.tx_length); - uart_write_bytes(HUANYANG_UART_PORT, next_cmd.msg, next_cmd.tx_length); - - uint16_t read_length = uart_read_bytes(HUANYANG_UART_PORT, rx_message, next_cmd.rx_length, RESPONSE_WAIT_TICKS); - - if (read_length < next_cmd.rx_length) { - if (!unresponsive) { - grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Spindle RS485 Unresponsive %d", read_length); - if (next_cmd.critical) { - grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Critical Spindle RS485 Unresponsive"); - system_set_exec_alarm(EXEC_ALARM_SPINDLE_CONTROL); - } - unresponsive = true; - } - } else { - // success - unresponsive = false; - //report_hex_msg(rx_message, "Rx: ", read_length); - uint32_t ret_value = ((uint32_t)rx_message[4] << 8) + rx_message[5]; - //grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Item:%d value:%05d ", rx_message[3], ret_value); - } - - } else { - HuanyangSpindle :: read_value(reg_item); // only this appears to work all the time. Other registers are flakey. - if (reg_item < 0x03) - reg_item++; - else - { - reg_item = 0x00; - } - - } - vTaskDelay(HUANYANG_POLL_RATE); // TODO: What is the best value here? - } -} - -// ================== Class methods ================================== - -void HuanyangSpindle :: init() { - hy_spindle_ok = true; // initialize - - // fail if required items are not defined - if (!get_pins_and_settings()) { - grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Huanyang spindle errors"); - return; - } - - if (! _task_running) { // init can happen many times, we only want to start one task - hy_cmd_queue = xQueueCreate(HUANYANG_QUEUE_SIZE, sizeof(hy_command_t)); - xTaskCreatePinnedToCore(vfd_cmd_task, // task - "vfd_cmdTaskHandle", // name for task - 2048, // size of task stack - NULL, // parameters - 1, // priority - &vfd_cmdTaskHandle, - 0 // core - ); - _task_running = true; - } - - // this allows us to init() again later. - // If you change certain settings, init() gets called agian - uart_driver_delete(HUANYANG_UART_PORT); - - uart_config_t uart_config = { - .baud_rate = HUANYANG_BAUD_RATE, - .data_bits = UART_DATA_8_BITS, - .parity = UART_PARITY_DISABLE, - .stop_bits = UART_STOP_BITS_1, - .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, - .rx_flow_ctrl_thresh = 122, - }; - - uart_param_config(HUANYANG_UART_PORT, &uart_config); - - uart_set_pin(HUANYANG_UART_PORT, - _txd_pin, - _rxd_pin, - _rts_pin, - UART_PIN_NO_CHANGE); - - uart_driver_install(HUANYANG_UART_PORT, - HUANYANG_BUF_SIZE * 2, - 0, - 0, - NULL, - 0); - - uart_set_mode(HUANYANG_UART_PORT, UART_MODE_RS485_HALF_DUPLEX); - - is_reversable = true; // these VFDs are always reversable - use_delays = true; - - // - _current_rpm = 0; - _state = SPINDLE_DISABLE; - - config_message(); -} - -// Checks for all the required pin definitions -// It returns a message for each missing pin -// Returns true if all pins are defined. -bool HuanyangSpindle :: get_pins_and_settings() { - - -#ifdef HUANYANG_TXD_PIN - _txd_pin = HUANYANG_TXD_PIN; -#else - grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Undefined HUANYANG_TXD_PIN"); - hy_spindle_ok = false; -#endif - -#ifdef HUANYANG_RXD_PIN - _rxd_pin = HUANYANG_RXD_PIN; -#else - grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Undefined HUANYANG_RXD_PIN"); - hy_spindle_ok = false; -#endif - -#ifdef HUANYANG_RTS_PIN - _rts_pin = HUANYANG_RTS_PIN; -#else - grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Undefined HUANYANG_RTS_PIN"); - hy_spindle_ok = false; -#endif - - if (laser_mode->get()) { - grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Huanyang spindle disabled in laser mode. Set $GCode/LaserMode=Off and restart"); - hy_spindle_ok = false; - } - - _min_rpm = rpm_min->get(); - _max_rpm = rpm_max->get(); - - return hy_spindle_ok; -} - -void HuanyangSpindle :: config_message() { - grbl_msg_sendf(CLIENT_SERIAL, - MSG_LEVEL_INFO, - "Huanyang Spindle Tx:%s Rx:%s RTS:%s", - pinName(_txd_pin).c_str(), - pinName(_rxd_pin).c_str(), - pinName(_rts_pin).c_str()); -} - - -void HuanyangSpindle :: set_state(uint8_t state, uint32_t rpm) { - if (sys.abort) - return; // Block during abort. - - bool critical = (sys.state == STATE_CYCLE || state != SPINDLE_DISABLE); - - if (_current_state != state) { // already at the desired state. This function gets called a lot. - set_mode(state, critical); // critical if we are in a job - set_rpm(rpm); - if (state == SPINDLE_DISABLE) { - sys.spindle_speed = 0; - if (_current_state != state) - mc_dwell(spindle_delay_spindown->get()); - } else { - if (_current_state != state) - mc_dwell(spindle_delay_spinup->get()); - } - } else { - if (_current_rpm != rpm) - set_rpm(rpm); - } - - _current_state = state; // store locally for faster get_state() - - sys.report_ovr_counter = 0; // Set to report change immediately - - return; -} - -bool HuanyangSpindle :: set_mode(uint8_t mode, bool critical) { - - if (!hy_spindle_ok) return false; - - hy_command_t mode_cmd; - - mode_cmd.tx_length = 6; - mode_cmd.rx_length = 6; - - mode_cmd.msg[0] = HUANYANG_ADDR; - mode_cmd.msg[1] = 0x03; - mode_cmd.msg[2] = 0x01; - - if (mode == SPINDLE_ENABLE_CW) - mode_cmd.msg[3] = 0x01; - else if (mode == SPINDLE_ENABLE_CCW) - mode_cmd.msg[3] = 0x11; - else { //SPINDLE_DISABLE - mode_cmd.msg[3] = 0x08; - - if (! xQueueReset(hy_cmd_queue)) { - grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "VFD spindle off, queue could not be reset"); - } - } - - add_ModRTU_CRC(mode_cmd.msg, mode_cmd.rx_length); - - mode_cmd.critical = critical; - - if (xQueueSend(hy_cmd_queue, &mode_cmd, 0) != pdTRUE) - grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "VFD Queue Full"); - - return true; -} - - -uint32_t HuanyangSpindle :: set_rpm(uint32_t rpm) { - if (!hy_spindle_ok) return 0; - - hy_command_t rpm_cmd; - - // apply override - rpm = rpm * sys.spindle_speed_ovr / 100; // Scale by spindle speed override value (uint8_t percent) - - // apply limits - if ((_min_rpm >= _max_rpm) || (rpm >= _max_rpm)) - rpm = _max_rpm; - else if (rpm != 0 && rpm <= _min_rpm) - rpm = _min_rpm; - - sys.spindle_speed = rpm; - - if (rpm == _current_rpm) // prevent setting same RPM twice - return rpm; - - _current_rpm = rpm; - - // TODO add the speed modifiers override, linearization, etc. - - rpm_cmd.tx_length = 7; - rpm_cmd.rx_length = 6; - - rpm_cmd.msg[0] = HUANYANG_ADDR; - rpm_cmd.msg[1] = 0x05; - rpm_cmd.msg[2] = 0x02; - - uint16_t data = (uint16_t)(rpm * 100 / 60); // send Hz * 10 (Ex:1500 RPM = 25Hz .... Send 2500) - - rpm_cmd.msg[3] = (data & 0xFF00) >> 8; - rpm_cmd.msg[4] = (data & 0xFF); - - add_ModRTU_CRC(rpm_cmd.msg, rpm_cmd.tx_length); - - rpm_cmd.critical = false; - - if (xQueueSend(hy_cmd_queue, &rpm_cmd, 0) != pdTRUE) - grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "VFD Queue Full"); - - return rpm; -} - - -// This appears to read the control register and will return an RPM running or not. -void HuanyangSpindle :: read_value(uint8_t reg) { - uint16_t ret_value = 0; - hy_command_t read_cmd; - uint8_t rx_message[HUANYANG_MAX_MSG_SIZE]; - - read_cmd.tx_length = 8; - read_cmd.rx_length = 8; - - read_cmd.msg[0] = HUANYANG_ADDR; - read_cmd.msg[1] = 0x04; - read_cmd.msg[2] = 0x03; - - read_cmd.msg[3] = reg; - - read_cmd.msg[4] = 0x00; - read_cmd.msg[5] = 0x00; - - read_cmd.critical = (sys.state == STATE_CYCLE); // only critical if running a job TBD.... maybe spindle on? - - add_ModRTU_CRC(read_cmd.msg, read_cmd.tx_length); - - if (xQueueSend(hy_cmd_queue, &read_cmd, 0) != pdTRUE) - grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "VFD Queue Full"); -} - -void HuanyangSpindle ::stop() { - set_mode(SPINDLE_DISABLE, false); -} - -// state is cached rather than read right now to prevent delays -uint8_t HuanyangSpindle :: get_state() { - return _state; -} - -// Calculate the CRC on all of the byte except the last 2 -// It then added the CRC to those last 2 bytes -// full_msg_len This is the length of the message including the 2 crc bytes -// Source: https://ctlsys.com/support/how_to_compute_the_modbus_rtu_message_crc/ -void HuanyangSpindle :: add_ModRTU_CRC(char* buf, int full_msg_len) { - uint16_t crc = 0xFFFF; - for (int pos = 0; pos < full_msg_len - 2; pos++) { - crc ^= (uint16_t)buf[pos]; // XOR byte into least sig. byte of crc - for (int i = 8; i != 0; i--) { // Loop over each bit - if ((crc & 0x0001) != 0) { // If the LSB is set - crc >>= 1; // Shift right and XOR 0xA001 - crc ^= 0xA001; - } else // Else LSB is not set - crc >>= 1; // Just shift right - } - } - // add the calculated Crc to the message - buf[full_msg_len - 1] = (crc & 0xFF00) >> 8; - buf[full_msg_len - 2] = (crc & 0xFF); -} diff --git a/Grbl_Esp32/Spindles/PWMSpindle.cpp b/Grbl_Esp32/Spindles/PWMSpindle.cpp deleted file mode 100644 index 3f28273d..00000000 --- a/Grbl_Esp32/Spindles/PWMSpindle.cpp +++ /dev/null @@ -1,268 +0,0 @@ -/* - PWMSpindle.cpp - - This is a full featured TTL PWM spindle This does not include speed/power - compensation. Use the Laser class for that. - - Part of Grbl_ESP32 - 2020 - Bart Dring - - 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 . - -*/ -#include "SpindleClass.h" - -// ======================= PWMSpindle ============================== -/* - This gets called at startup or whenever a spindle setting changes - If the spindle is running it will stop and need to be restarted with M3Snnnn -*/ - -//#include "grbl.h" - -void PWMSpindle :: init() { - - get_pins_and_settings(); - - if (_output_pin == UNDEFINED_PIN) { - grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Warning: Spindle output pin not defined"); - return; // We cannot continue without the output pin - } - - if (_output_pin >= I2S_OUT_PIN_BASE) { - grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Warning: Spindle output pin %s cannot do PWM", pinName(_output_pin).c_str()); - return; - } - - ledcSetup(_spindle_pwm_chan_num, (double)_pwm_freq, _pwm_precision); // setup the channel - ledcAttachPin(_output_pin, _spindle_pwm_chan_num); // attach the PWM to the pin - - pinMode(_enable_pin, OUTPUT); - pinMode(_direction_pin, OUTPUT); - - use_delays = true; - - config_message(); -} - -// Get the GPIO from the machine definition -void PWMSpindle :: get_pins_and_settings() { - // setup all the pins - -#ifdef SPINDLE_OUTPUT_PIN - _output_pin = SPINDLE_OUTPUT_PIN; -#else - _output_pin = UNDEFINED_PIN; -#endif - -#ifdef INVERT_SPINDLE_OUTPUT_PIN - _invert_pwm = true; -#else - _invert_pwm = false; -#endif - -#ifdef SPINDLE_ENABLE_PIN - _enable_pin = SPINDLE_ENABLE_PIN; -#ifdef SPINDLE_ENABLE_OFF_WITH_ZERO_SPEED - _off_with_zero_speed = true; -#endif -#else - _enable_pin = UNDEFINED_PIN; - _off_with_zero_speed = false; -#endif - - -#ifdef SPINDLE_DIR_PIN - _direction_pin = SPINDLE_DIR_PIN; -#else - _direction_pin = UNDEFINED_PIN; -#endif - - is_reversable = (_direction_pin != UNDEFINED_PIN); - - _pwm_freq = spindle_pwm_freq->get(); - _pwm_precision = calc_pwm_precision(_pwm_freq); // detewrmine the best precision - _pwm_period = (1 << _pwm_precision); - - if (spindle_pwm_min_value->get() > spindle_pwm_min_value->get()) - grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Warning: Spindle min pwm is greater than max. Check $35 and $36"); - - // pre-caculate some PWM count values - _pwm_off_value = (_pwm_period * spindle_pwm_off_value->get() / 100.0); - _pwm_min_value = (_pwm_period * spindle_pwm_min_value->get() / 100.0); - _pwm_max_value = (_pwm_period * spindle_pwm_max_value->get() / 100.0); - -#ifdef ENABLE_PIECEWISE_LINEAR_SPINDLE - _min_rpm = RPM_MIN; - _max_rpm = RPM_MAX; - _piecewide_linear = true; -#else - _min_rpm = rpm_min->get(); - _max_rpm = rpm_max->get(); - _piecewide_linear = false; -#endif - // The pwm_gradient is the pwm duty cycle units per rpm - // _pwm_gradient = (_pwm_max_value - _pwm_min_value) / (_max_rpm - _min_rpm); - - _spindle_pwm_chan_num = 0; // Channel 0 is reserved for spindle use - - -} - -uint32_t PWMSpindle::set_rpm(uint32_t rpm) { - uint32_t pwm_value; - - if (_output_pin == UNDEFINED_PIN) - return rpm; - - //grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "set_rpm(%d)", rpm); - - // apply override - rpm = rpm * sys.spindle_speed_ovr / 100; // Scale by spindle speed override value (uint8_t percent) - - // apply limits - if ((_min_rpm >= _max_rpm) || (rpm >= _max_rpm)) - rpm = _max_rpm; - else if (rpm != 0 && rpm <= _min_rpm) - rpm = _min_rpm; - - sys.spindle_speed = rpm; - - if (_piecewide_linear) { - //pwm_value = piecewise_linear_fit(rpm); TODO - pwm_value = 0; - grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Warning: Linear fit not implemented yet."); - - } else { - if (rpm == 0) - pwm_value = _pwm_off_value; - else - pwm_value = map_uint32_t(rpm, _min_rpm, _max_rpm, _pwm_min_value, _pwm_max_value); - } - - set_output(pwm_value); - - return 0; -} - -void PWMSpindle::set_state(uint8_t state, uint32_t rpm) { - if (sys.abort) - return; // Block during abort. - - if (state == SPINDLE_DISABLE) { // Halt or set spindle direction and rpm. - sys.spindle_speed = 0; - stop(); - if (use_delays && (_current_state != state)) { - //grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "SpinDown Start "); - mc_dwell(spindle_delay_spindown->get()); - //grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "SpinDown Done"); - } - } else { - set_spindle_dir_pin(state == SPINDLE_ENABLE_CW); - set_rpm(rpm); - set_enable_pin(state != SPINDLE_DISABLE); // must be done after setting rpm for enable features to work - if (use_delays && (_current_state != state)) { - //grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "SpinUp Start %d", rpm); - mc_dwell(spindle_delay_spinup->get()); - //grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "SpinUp Done"); - } - } - - _current_state = state; - - sys.report_ovr_counter = 0; // Set to report change immediately -} - -uint8_t PWMSpindle::get_state() { - if (_current_pwm_duty == 0 || _output_pin == UNDEFINED_PIN) - return (SPINDLE_STATE_DISABLE); - if (_direction_pin != UNDEFINED_PIN) - return digitalRead(_direction_pin) ? SPINDLE_STATE_CW : SPINDLE_STATE_CCW; - return (SPINDLE_STATE_CW); -} - -void PWMSpindle::stop() { - // inverts are delt with in methods - set_enable_pin(false); - set_output(_pwm_off_value); - -} - -// prints the startup message of the spindle config -void PWMSpindle :: config_message() { - grbl_msg_sendf(CLIENT_SERIAL, - MSG_LEVEL_INFO, - "PWM spindle Output:%s, Enbl:%s, Dir:%s, Freq:%dHz, Res:%dbits", - pinName(_output_pin).c_str(), - pinName(_enable_pin).c_str(), - pinName(_direction_pin).c_str(), - _pwm_freq, - _pwm_precision); -} - - -void PWMSpindle::set_output(uint32_t duty) { - - - if (_output_pin == UNDEFINED_PIN) - return; - - // to prevent excessive calls to ledcWrite, make sure duty hass changed - if (duty == _current_pwm_duty) - return; - - _current_pwm_duty = duty; - - if (_invert_pwm) - duty = (1 << _pwm_precision) - duty; - - //grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "set_output(%d)", duty); - - ledcWrite(_spindle_pwm_chan_num, duty); -} - -void PWMSpindle::set_enable_pin(bool enable) { - - if (_enable_pin == UNDEFINED_PIN) - return; - - if (_off_with_zero_speed && sys.spindle_speed == 0) - enable = false; - -#ifndef INVERT_SPINDLE_ENABLE_PIN - digitalWrite(_enable_pin, enable); -#else - digitalWrite(_enable_pin, !enable); -#endif - digitalWrite(_enable_pin, enable); -} - -void PWMSpindle::set_spindle_dir_pin(bool Clockwise) { - digitalWrite(_direction_pin, Clockwise); -} - - -/* - Calculate the highest precision of a PWM based on the frequency in bits - - 80,000,000 / freq = period - determine the highest precision where (1 << precision) < period -*/ -uint8_t PWMSpindle :: calc_pwm_precision(uint32_t freq) { - uint8_t precision = 0; - - // increase the precision (bits) until it exceeds allow by frequency the max or is 16 - while ((1 << precision) < (uint32_t)(80000000 / freq) && precision <= 16) - precision++; - - return precision - 1; -} diff --git a/Grbl_Esp32/Spindles/RelaySpindle.cpp b/Grbl_Esp32/Spindles/RelaySpindle.cpp deleted file mode 100644 index f90b9b90..00000000 --- a/Grbl_Esp32/Spindles/RelaySpindle.cpp +++ /dev/null @@ -1,70 +0,0 @@ -/* - RelaySpindle.cpp - - This is used for a basic on/off spindle All S Values above 0 - will turn the spindle on. - - Part of Grbl_ESP32 - 2020 - Bart Dring - - 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 . - -*/ -#include "SpindleClass.h" - -// ========================= RelaySpindle ================================== -/* - This is a sub class of PWMSpindle but is a digital rather than PWM output -*/ -void RelaySpindle::init() { - get_pins_and_settings(); - - if (_output_pin == UNDEFINED_PIN) - return; - - pinMode(_output_pin, OUTPUT); - pinMode(_enable_pin, OUTPUT); - pinMode(_direction_pin, OUTPUT); - - is_reversable = (_direction_pin != UNDEFINED_PIN); - use_delays = true; - - config_message(); -} - -// prints the startup message of the spindle config -void RelaySpindle :: config_message() { - grbl_msg_sendf(CLIENT_SERIAL, - MSG_LEVEL_INFO, - "Relay spindle Output:%s, Enbl:%s, Dir:%s", - pinName(_output_pin).c_str(), - pinName(_enable_pin).c_str(), - pinName(_direction_pin).c_str()); -} - -uint32_t RelaySpindle::set_rpm(uint32_t rpm) { - if (_output_pin == UNDEFINED_PIN) - return rpm; - - sys.spindle_speed = rpm; - set_output(rpm != 0); - - - return rpm; -} - -void RelaySpindle::set_output(uint32_t duty) { -#ifdef INVERT_SPINDLE_PWM - duty = (duty == 0); // flip duty -#endif - digitalWrite(_output_pin, duty > 0); // anything greater -} diff --git a/Grbl_Esp32/Spindles/SpindleClass.cpp b/Grbl_Esp32/Spindles/SpindleClass.cpp deleted file mode 100644 index 75c3b2ce..00000000 --- a/Grbl_Esp32/Spindles/SpindleClass.cpp +++ /dev/null @@ -1,96 +0,0 @@ -/* - SpindleClass.cpp - - A Base class for spindles and spinsle like things such as lasers - - Part of Grbl_ESP32 - - 2020 - Bart Dring - - 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 . - - TODO - Add Spindle spin up/down delays - - Get rid of dependance on machine definition #defines - SPINDLE_OUTPUT_PIN - SPINDLE_ENABLE_PIN - SPINDLE_DIR_PIN - -*/ -#include "NullSpindle.cpp" -#include "PWMSpindle.cpp" -#include "DacSpindle.cpp" -#include "RelaySpindle.cpp" -#include "Laser.cpp" -#include "HuanyangSpindle.cpp" -#include "BESCSpindle.cpp" -#include "10vSpindle.cpp" - - -// An instance of each type of spindle is created here. -// This allows the spindle to be dynamicly switched -NullSpindle null_spindle; -PWMSpindle pwm_spindle; -RelaySpindle relay_spindle; -Laser laser; -DacSpindle dac_spindle; -HuanyangSpindle huanyang_spindle; -BESCSpindle besc_spindle; -_10vSpindle _10v_spindle; - - -void spindle_select() { - - switch (spindle_type->get()) { - case SPINDLE_TYPE_PWM: - spindle = &pwm_spindle; - break; - case SPINDLE_TYPE_RELAY: - spindle = &relay_spindle; - break; - case SPINDLE_TYPE_LASER: - spindle = &laser; - break; - case SPINDLE_TYPE_DAC: - spindle = &dac_spindle; - break; - case SPINDLE_TYPE_HUANYANG: - spindle = &huanyang_spindle; - break; - case SPINDLE_TYPE_BESC: - spindle = &besc_spindle; - break; - case SPINDLE_TYPE_10V: - spindle = &_10v_spindle; - break; - case SPINDLE_TYPE_NONE: - default: - spindle = &null_spindle; - break; - } - - spindle->init(); -} - -// ========================= Spindle ================================== - -bool Spindle::isRateAdjusted() { - return false; // default for basic spindle is false -} - -void Spindle :: spindle_sync(uint8_t state, uint32_t rpm) { - if (sys.state == STATE_CHECK_MODE) - return; - protocol_buffer_synchronize(); // Empty planner buffer to ensure spindle is set when programmed. - set_state(state, rpm); -} diff --git a/Grbl_Esp32/Spindles/SpindleClass.h b/Grbl_Esp32/Spindles/SpindleClass.h deleted file mode 100644 index 28a7fdd9..00000000 --- a/Grbl_Esp32/Spindles/SpindleClass.h +++ /dev/null @@ -1,229 +0,0 @@ -/* - SpindleClass.h - - Header file for a Spindle Class - See SpindleClass.cpp for more details - - Part of Grbl_ESP32 - - 2020 - 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 . - - See SpindleClass.cpp for more info and references - -*/ - -#define SPINDLE_STATE_DISABLE 0 // Must be zero. -#define SPINDLE_STATE_CW bit(0) -#define SPINDLE_STATE_CCW bit(1) - - -#define SPINDLE_TYPE_NONE 0 -#define SPINDLE_TYPE_PWM 1 -#define SPINDLE_TYPE_RELAY 2 -#define SPINDLE_TYPE_LASER 3 -#define SPINDLE_TYPE_DAC 4 -#define SPINDLE_TYPE_HUANYANG 5 -#define SPINDLE_TYPE_BESC 6 -#define SPINDLE_TYPE_10V 7 - -#ifndef SPINDLE_CLASS_H -#define SPINDLE_CLASS_H - -#include "../grbl.h" -#include -#include "driver/uart.h" - - -// =============== No floats! =========================== -// ================ NO FLOATS! ========================== - -// This is the base class. Do not use this as your spindle -class Spindle { - public: - virtual void init(); // not in constructor because this also gets called when $$ settings change - virtual uint32_t set_rpm(uint32_t rpm); - virtual void set_state(uint8_t state, uint32_t rpm); - virtual uint8_t get_state(); - virtual void stop(); - virtual void config_message(); - virtual bool isRateAdjusted(); - virtual void spindle_sync(uint8_t state, uint32_t rpm); - - bool is_reversable; - bool use_delays; // will SpinUp and SpinDown delays be used. - uint8_t _current_state; -}; - -// This is a dummy spindle that has no I/O. -// It is used to ignore spindle commands when no spinde is desired -class NullSpindle : public Spindle { - public: - void init(); - uint32_t set_rpm(uint32_t rpm); - void set_state(uint8_t state, uint32_t rpm); - uint8_t get_state(); - void stop(); - void config_message(); -}; - -// This adds support for PWM -class PWMSpindle : public Spindle { - public: - void init(); - virtual uint32_t set_rpm(uint32_t rpm); - void set_state(uint8_t state, uint32_t rpm); - uint8_t get_state(); - void stop(); - void config_message(); - - private: - void set_spindle_dir_pin(bool Clockwise); - - - - protected: - - int32_t _current_pwm_duty; - uint32_t _min_rpm; - uint32_t _max_rpm; - uint32_t _pwm_off_value; - uint32_t _pwm_min_value; - uint32_t _pwm_max_value; - uint8_t _output_pin; - uint8_t _enable_pin; - uint8_t _direction_pin; - uint8_t _spindle_pwm_chan_num; - uint32_t _pwm_freq; - uint32_t _pwm_period; // how many counts in 1 period - uint8_t _pwm_precision; - bool _piecewide_linear; - bool _off_with_zero_speed; - bool _invert_pwm; - //uint32_t _pwm_gradient; // Precalulated value to speed up rpm to PWM conversions. - - virtual void set_output(uint32_t duty); - void set_enable_pin(bool enable_pin); - void get_pins_and_settings(); - uint8_t calc_pwm_precision(uint32_t freq); -}; - -// This is for an on/off spindle all RPMs above 0 are on -class RelaySpindle : public PWMSpindle { - public: - void init(); - void config_message(); - uint32_t set_rpm(uint32_t rpm); - protected: - void set_output(uint32_t duty); -}; - -// this is the same as a PWM spindle but the M4 compensation is supported. -class Laser : public PWMSpindle { - public: - bool isRateAdjusted(); - void config_message(); -}; - -// This uses one of the (2) DAC pins on ESP32 to output a voltage -class DacSpindle : public PWMSpindle { - public: - void init(); - void config_message(); - uint32_t set_rpm(uint32_t rpm); - private: - bool _gpio_ok; // DAC is on a valid pin - protected: - void set_output(uint32_t duty); // sets DAC instead of PWM -}; - -class HuanyangSpindle : public Spindle { - private: - uint16_t ModRTU_CRC(char* buf, int len); - - bool set_mode(uint8_t mode, bool critical); - - bool get_pins_and_settings(); - - uint32_t _current_rpm; - uint8_t _txd_pin; - uint8_t _rxd_pin; - uint8_t _rts_pin; - uint8_t _state; - bool _task_running; - - public: - HuanyangSpindle() { - _task_running = false; - } - void init(); - void config_message(); - void set_state(uint8_t state, uint32_t rpm); - uint8_t get_state(); - uint32_t set_rpm(uint32_t rpm); - void stop(); - static void read_value(uint8_t reg); - static void add_ModRTU_CRC(char* buf, int full_msg_len); - - protected: - uint32_t _min_rpm; - uint32_t _max_rpm; - - - -}; - -class BESCSpindle : public PWMSpindle { - public: - void init(); - void config_message(); - uint32_t set_rpm(uint32_t rpm); -}; - -class _10vSpindle : public PWMSpindle { - public: - void init(); - void config_message(); - uint32_t set_rpm(uint32_t rpm); - uint8_t _forward_pin; - uint8_t _reverse_pin; - - //void set_state(uint8_t state, uint32_t rpm); - - uint8_t get_state(); - void stop(); - protected: - void set_enable_pin(bool enable_pin); - void set_spindle_dir_pin(bool Clockwise); - -}; - -extern Spindle* spindle; - - -extern NullSpindle null_spindle; -extern PWMSpindle pwm_spindle; -extern RelaySpindle relay_spindle; -extern Laser laser; -extern DacSpindle dac_spindle; -extern HuanyangSpindle huanyang_spindle; -extern BESCSpindle besc_spindle; -extern _10vSpindle _10v_spindle; - -void spindle_select(); - -// in HuanyangSpindle.cpp -void vfd_cmd_task(void* pvParameters); - -#endif diff --git a/Grbl_Esp32/authentication.h b/Grbl_Esp32/authentication.h deleted file mode 100644 index 09ca20eb..00000000 --- a/Grbl_Esp32/authentication.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -//Authentication level -typedef enum { - LEVEL_GUEST = 0, - LEVEL_USER = 1, - LEVEL_ADMIN = 2 -} auth_t; - -#define MIN_LOCAL_PASSWORD_LENGTH 1 -#define MAX_LOCAL_PASSWORD_LENGTH 16 - -void remove_password(char *str, auth_t& auth_level); diff --git a/Grbl_Esp32/defaults.h b/Grbl_Esp32/defaults.h deleted file mode 100644 index 67513c00..00000000 --- a/Grbl_Esp32/defaults.h +++ /dev/null @@ -1,437 +0,0 @@ -/* - defaults.h - defaults settings configuration file - Part of Grbl - - Copyright (c) 2012-2016 Sungeun K. Jeon for Gnea Research LLC - - 2018 - Bart Dring This file was modifed 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 . -*/ - -/* The defaults.h file serves as a central default settings selector for different machine - types, from DIY CNC mills to CNC conversions of off-the-shelf machines. The settings - files listed here are supplied by users, so your results may vary. However, this should - give you a good starting point as you get to know your machine and tweak the settings for - your nefarious needs. - NOTE: Ensure one and only one of these DEFAULTS_XXX values is defined in config.h */ - -#ifndef defaults_h - - /* - All of these settings check to see if they have been defined already - before defining them. This allows to to easily set them eslewhere. - You only need to set ones that are important or unique to your - machine. The rest will be pulled from here. - */ - - // Grbl generic default settings. Should work across different machines. - #ifndef DEFAULT_STEP_PULSE_MICROSECONDS - #define DEFAULT_STEP_PULSE_MICROSECONDS 3 // $0 - #endif - - #ifndef DEFAULT_STEPPER_IDLE_LOCK_TIME - #define DEFAULT_STEPPER_IDLE_LOCK_TIME 250 // $1 msec (0-254, 255 keeps steppers enabled) - #endif - - #ifndef DEFAULT_STEPPING_INVERT_MASK - #define DEFAULT_STEPPING_INVERT_MASK 0 // $2 uint8_t - #endif - - #ifndef DEFAULT_DIRECTION_INVERT_MASK - #define DEFAULT_DIRECTION_INVERT_MASK 0 // $3 uint8_ - #endif - - #ifndef DEFAULT_INVERT_ST_ENABLE - #define DEFAULT_INVERT_ST_ENABLE 0 // $4 boolean - #endif - - #ifndef DEFAULT_INVERT_LIMIT_PINS - #define DEFAULT_INVERT_LIMIT_PINS 1 // $5 boolean - #endif - - #ifndef DEFAULT_INVERT_PROBE_PIN - #define DEFAULT_INVERT_PROBE_PIN 0 // $6 boolean - #endif - - #ifndef DEFAULT_STATUS_REPORT_MASK - #define DEFAULT_STATUS_REPORT_MASK 1 // $10 - #endif - - #ifndef DEFAULT_JUNCTION_DEVIATION - #define DEFAULT_JUNCTION_DEVIATION 0.01 // $11 mm - #endif - - #ifndef DEFAULT_ARC_TOLERANCE - #define DEFAULT_ARC_TOLERANCE 0.002 // $12 mm - #endif - - #ifndef DEFAULT_REPORT_INCHES - #define DEFAULT_REPORT_INCHES 0 // $13 false - #endif - - #ifndef DEFAULT_SOFT_LIMIT_ENABLE - #define DEFAULT_SOFT_LIMIT_ENABLE 0 // $20 false - #endif - - #ifndef DEFAULT_HARD_LIMIT_ENABLE - #define DEFAULT_HARD_LIMIT_ENABLE 0 // $21 false - #endif - - #ifndef DEFAULT_HOMING_ENABLE - #define DEFAULT_HOMING_ENABLE 0 // $22 false - #endif - - #ifndef DEFAULT_HOMING_DIR_MASK - #define DEFAULT_HOMING_DIR_MASK 3 // $23 move positive dir Z, negative X,Y - #endif - - #ifndef DEFAULT_HOMING_FEED_RATE - #define DEFAULT_HOMING_FEED_RATE 200.0 // $24 mm/min - #endif - - #ifndef DEFAULT_HOMING_SEEK_RATE - #define DEFAULT_HOMING_SEEK_RATE 2000.0 // $25 mm/min - #endif - - #ifndef DEFAULT_HOMING_DEBOUNCE_DELAY - #define DEFAULT_HOMING_DEBOUNCE_DELAY 250 // $26 msec (0-65k) - #endif - - #ifndef DEFAULT_HOMING_PULLOFF - #define DEFAULT_HOMING_PULLOFF 1.0 // $27 mm - #endif - - // ======== SPINDLE STUFF ==================== - #ifndef SPINDLE_TYPE - #define SPINDLE_TYPE SPINDLE_TYPE_NONE - #endif - - #ifndef DEFAULT_SPINDLE_RPM_MIN // $31 - #define DEFAULT_SPINDLE_RPM_MIN 0.0 // rpm - #endif - - #ifndef DEFAULT_LASER_MODE // $32 - #define DEFAULT_LASER_MODE 0 // false - #endif - - #ifndef DEFAULT_SPINDLE_RPM_MAX // $30 - #define DEFAULT_SPINDLE_RPM_MAX 1000.0 // rpm - #endif - - #ifndef DEFAULT_SPINDLE_FREQ - #define DEFAULT_SPINDLE_FREQ 5000.0 // $33 Hz (extended set) - #endif - - #ifndef DEFAULT_SPINDLE_OFF_VALUE - #define DEFAULT_SPINDLE_OFF_VALUE 0.0 // $34 Percent of full period(extended set) - #endif - - #ifndef DEFAULT_SPINDLE_MIN_VALUE - #define DEFAULT_SPINDLE_MIN_VALUE 0.0 // $35 Percent of full period (extended set) - #endif - - #ifndef DEFAULT_SPINDLE_MAX_VALUE - #define DEFAULT_SPINDLE_MAX_VALUE 100.0 // $36 Percent of full period (extended set) - #endif - - #ifndef DEFAULT_SPINDLE_DELAY_SPINUP - #define DEFAULT_SPINDLE_DELAY_SPINUP 0 - #endif - - #ifndef DEFAULT_SPINDLE_DELAY_SPINDOWN - #define DEFAULT_SPINDLE_DELAY_SPINDOWN 0 - #endif - - // ================ user settings ===================== - #ifndef DEFAULT_USER_INT_80 - #define DEFAULT_USER_INT_80 0 // $80 User integer setting - #endif - - #ifndef DEFAULT_USER_INT_81 - #define DEFAULT_USER_INT_81 0 // $81 User integer setting - #endif - - #ifndef DEFAULT_USER_INT_82 - #define DEFAULT_USER_INT_82 0 // $82 User integer setting - #endif - - #ifndef DEFAULT_USER_INT_83 - #define DEFAULT_USER_INT_83 0 // $83 User integer setting - #endif - - #ifndef DEFAULT_USER_INT_84 - #define DEFAULT_USER_INT_84 0 // $84 User integer setting - #endif - - #ifndef DEFAULT_USER_FLOAT_90 - #define DEFAULT_USER_FLOAT_90 0.0 // $90 User integer setting - #endif - - #ifndef DEFAULT_USER_FLOAT_91 - #define DEFAULT_USER_FLOAT_91 0.0 // $92 User integer setting - #endif - - #ifndef DEFAULT_USER_FLOAT_92 - #define DEFAULT_USER_FLOAT_92 0.0 // $92 User integer setting - #endif - - #ifndef DEFAULT_USER_FLOAT_93 - #define DEFAULT_USER_FLOAT_93 0.0 // $93 User integer setting - #endif - - #ifndef DEFAULT_USER_FLOAT_94 - #define DEFAULT_USER_FLOAT_94 0.0 // $94 User integer setting - #endif - - - // =========== AXIS RESOLUTION ====== - - #ifndef DEFAULT_X_STEPS_PER_MM - #define DEFAULT_X_STEPS_PER_MM 100.0 - #endif - #ifndef DEFAULT_Y_STEPS_PER_MM - #define DEFAULT_Y_STEPS_PER_MM 100.0 - #endif - #ifndef DEFAULT_Z_STEPS_PER_MM - #define DEFAULT_Z_STEPS_PER_MM 100.0 - #endif - #ifndef DEFAULT_A_STEPS_PER_MM - #define DEFAULT_A_STEPS_PER_MM 100.0 - #endif - #ifndef DEFAULT_B_STEPS_PER_MM - #define DEFAULT_B_STEPS_PER_MM 100.0 - #endif - #ifndef DEFAULT_C_STEPS_PER_MM - #define DEFAULT_C_STEPS_PER_MM 100.0 - #endif - - // ============ AXIS MAX SPPED ========= - - #ifndef DEFAULT_X_MAX_RATE - #define DEFAULT_X_MAX_RATE 1000.0 // mm/min - #endif - #ifndef DEFAULT_Y_MAX_RATE - #define DEFAULT_Y_MAX_RATE 1000.0 // mm/min - #endif - #ifndef DEFAULT_Z_MAX_RATE - #define DEFAULT_Z_MAX_RATE 1000.0 // mm/min - #endif - #ifndef DEFAULT_A_MAX_RATE - #define DEFAULT_A_MAX_RATE 1000.0 // mm/min - #endif - #ifndef DEFAULT_B_MAX_RATE - #define DEFAULT_B_MAX_RATE 1000.0 // mm/min - #endif - #ifndef DEFAULT_C_MAX_RATE - #define DEFAULT_C_MAX_RATE 1000.0 // mm/min - #endif - - // ============== Axis Acceleration ========= - #define SEC_PER_MIN_SQ (60.0*60.0) // Seconds Per Minute Squared, for acceleration conversion - // Default accelerations are expressed in mm/sec^2 - #ifndef DEFAULT_X_ACCELERATION - #define DEFAULT_X_ACCELERATION 200.0 - #endif - #ifndef DEFAULT_Y_ACCELERATION - #define DEFAULT_Y_ACCELERATION 200.0 - #endif - #ifndef DEFAULT_Z_ACCELERATION - #define DEFAULT_Z_ACCELERATION 200.0 - #endif - #ifndef DEFAULT_A_ACCELERATION - #define DEFAULT_A_ACCELERATION 200.0 - #endif - #ifndef DEFAULT_B_ACCELERATION - #define DEFAULT_B_ACCELERATION 200.0 - #endif - #ifndef DEFAULT_C_ACCELERATION - #define DEFAULT_C_ACCELERATION 200.0 - #endif - - // ========= AXIS MAX TRAVEL ============ - - #ifndef DEFAULT_X_MAX_TRAVEL - #define DEFAULT_X_MAX_TRAVEL 300.0 // $130 mm NOTE: Must be a positive value. - #endif - #ifndef DEFAULT_Y_MAX_TRAVEL - #define DEFAULT_Y_MAX_TRAVEL 300.0 // mm NOTE: Must be a positive value. - #endif - #ifndef DEFAULT_Z_MAX_TRAVEL - #define DEFAULT_Z_MAX_TRAVEL 300.0 // mm NOTE: Must be a positive value. - #endif - #ifndef DEFAULT_A_MAX_TRAVEL - #define DEFAULT_A_MAX_TRAVEL 300.0 // mm NOTE: Must be a positive value. - #endif - #ifndef DEFAULT_B_MAX_TRAVEL - #define DEFAULT_B_MAX_TRAVEL 300.0 // mm NOTE: Must be a positive value. - #endif - #ifndef DEFAULT_C_MAX_TRAVEL - #define DEFAULT_C_MAX_TRAVEL 300.0 // mm NOTE: Must be a positive value. - #endif - - // ========== Motor current (SPI Drivers ) ============= - #ifndef DEFAULT_X_CURRENT - #define DEFAULT_X_CURRENT 0.25 // $140 current in amps (extended set) - #endif - #ifndef DEFAULT_Y_CURRENT - #define DEFAULT_Y_CURRENT 0.25 // $141 current in amps (extended set) - #endif - #ifndef DEFAULT_Z_CURRENT - #define DEFAULT_Z_CURRENT 0.25 // $142 current in amps (extended set) - #endif - #ifndef DEFAULT_A_CURRENT - #define DEFAULT_A_CURRENT 0.25 // $143 current in amps (extended set) - #endif - #ifndef DEFAULT_B_CURRENT - #define DEFAULT_B_CURRENT 0.25 // $144 current in amps (extended set) - #endif - #ifndef DEFAULT_C_CURRENT - #define DEFAULT_C_CURRENT 0.25 // $145 current in amps (extended set) - #endif - - // ========== Motor hold current (SPI Drivers ) ============= - - #ifndef DEFAULT_X_HOLD_CURRENT - #define DEFAULT_X_HOLD_CURRENT 0.125 // $150 current in amps (extended set) - #endif - #ifndef DEFAULT_Y_HOLD_CURRENT - #define DEFAULT_Y_HOLD_CURRENT 0.125 // $151 current in amps (extended set) - #endif - #ifndef DEFAULT_Z_HOLD_CURRENT - #define DEFAULT_Z_HOLD_CURRENT 0.125 // $152 current in amps (extended set) - #endif - #ifndef DEFAULT_A_HOLD_CURRENT - #define DEFAULT_A_HOLD_CURRENT 0.125 // $153 current in amps (extended set) - #endif - #ifndef DEFAULT_B_HOLD_CURRENT - #define DEFAULT_B_HOLD_CURRENT 0.125 // $154 current in amps (extended set) - #endif - #ifndef DEFAULT_C_HOLD_CURRENT - #define DEFAULT_C_HOLD_CURRENT 0.125 // $154 current in amps (extended set) - #endif - - // ========== Microsteps (SPI Drivers ) ================ - - #ifndef DEFAULT_X_MICROSTEPS - #define DEFAULT_X_MICROSTEPS 16 // $160 micro steps (extended set) - #endif - #ifndef DEFAULT_Y_MICROSTEPS - #define DEFAULT_Y_MICROSTEPS 16 // $161 micro steps (extended set) - #endif - #ifndef DEFAULT_Z_MICROSTEPS - #define DEFAULT_Z_MICROSTEPS 16 // $162 micro steps (extended set) - #endif - #ifndef DEFAULT_A_MICROSTEPS - #define DEFAULT_A_MICROSTEPS 16 // $163 micro steps (extended set) - #endif - #ifndef DEFAULT_B_MICROSTEPS - #define DEFAULT_B_MICROSTEPS 16 // $164 micro steps (extended set) - #endif - #ifndef DEFAULT_C_MICROSTEPS - #define DEFAULT_C_MICROSTEPS 16 // $165 micro steps (extended set) - #endif - - // ========== Stallguard (SPI Drivers ) ================ - - #ifndef DEFAULT_X_STALLGUARD - #define DEFAULT_X_STALLGUARD 16 // $170 stallguard (extended set) - #endif - #ifndef DEFAULT_Y_STALLGUARD - #define DEFAULT_Y_STALLGUARD 16 // $171 stallguard (extended set) - #endif - #ifndef DEFAULT_Z_STALLGUARD - #define DEFAULT_Z_STALLGUARD 16 // $172 stallguard (extended set) - #endif - #ifndef DEFAULT_A_STALLGUARD - #define DEFAULT_A_STALLGUARD 16 // $173 stallguard (extended set) - #endif - #ifndef DEFAULT_B_STALLGUARD - #define DEFAULT_B_STALLGUARD 16 // $174 stallguard (extended set) - #endif - #ifndef DEFAULT_C_STALLGUARD - #define DEFAULT_C_STALLGUARD 16 // $175 stallguard (extended set) - #endif - - -// ================== pin defaults ======================== - - -// Here is a place to default pins to UNDEFINED_PIN. -// This can eliminate checking to see if the pin is defined because -// the overridden pinMode and digitalWrite functions will deal with it. - -#ifndef STEPPERS_DISABLE_PIN - #define STEPPERS_DISABLE_PIN UNDEFINED_PIN -#endif - -#ifndef X_DISABLE_PIN - #define X_DISABLE_PIN UNDEFINED_PIN -#endif -#ifndef Y_DISABLE_PIN - #define Y_DISABLE_PIN UNDEFINED_PIN -#endif -#ifndef Z_DISABLE_PIN - #define Z_DISABLE_PIN UNDEFINED_PIN -#endif -#ifndef A_DISABLE_PIN - #define A_DISABLE_PIN UNDEFINED_PIN -#endif -#ifndef B_DISABLE_PIN - #define B_DISABLE_PIN UNDEFINED_PIN -#endif -#ifndef C_DISABLE_PIN - #define C_DISABLE_PIN UNDEFINED_PIN -#endif - -#ifndef X2_DISABLE_PIN - #define X2_DISABLE_PIN UNDEFINED_PIN -#endif -#ifndef Y2_DISABLE_PIN - #define Y2_DISABLE_PIN UNDEFINED_PIN -#endif -#ifndef Z2_DISABLE_PIN - #define Z2_DISABLE_PIN UNDEFINED_PIN -#endif -#ifndef A2_DISABLE_PIN - #define A2_DISABLE_PIN UNDEFINED_PIN -#endif -#ifndef B2_DISABLE_PIN - #define B2_DISABLE_PIN UNDEFINED_PIN -#endif -#ifndef C2_DISABLE_PIN - #define C2_DISABLE_PIN UNDEFINED_PIN -#endif - -#ifndef X_LIMIT_PIN - #define X_LIMIT_PIN UNDEFINED_PIN -#endif -#ifndef Y_LIMIT_PIN - #define Y_LIMIT_PIN UNDEFINED_PIN -#endif -#ifndef Z_LIMIT_PIN - #define Z_LIMIT_PIN UNDEFINED_PIN -#endif -#ifndef A_LIMIT_PIN - #define A_LIMIT_PIN UNDEFINED_PIN -#endif -#ifndef B_LIMIT_PIN - #define B_LIMIT_PIN UNDEFINED_PIN -#endif -#ifndef C_LIMIT_PIN - #define C_LIMIT_PIN UNDEFINED_PIN -#endif - -#endif diff --git a/Grbl_Esp32/gcode.cpp b/Grbl_Esp32/gcode.cpp deleted file mode 100644 index 0fd5eb75..00000000 --- a/Grbl_Esp32/gcode.cpp +++ /dev/null @@ -1,1390 +0,0 @@ -/* - gcode.c - rs274/ngc parser. - Part of Grbl - - Copyright (c) 2011-2016 Sungeun K. Jeon for Gnea Research LLC - Copyright (c) 2009-2011 Simen Svale Skogsrud - - 2018 - Bart Dring This file was modifed 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 . -*/ - -#include "grbl.h" - -// NOTE: Max line number is defined by the g-code standard to be 99999. It seems to be an -// arbitrary value, and some GUIs may require more. So we increased it based on a max safe -// value when converting a float (7.2 digit precision)s to an integer. -#define MAX_LINE_NUMBER 10000000 -#define MAX_TOOL_NUMBER 255 // Limited by max unsigned 8-bit value - -#define AXIS_COMMAND_NONE 0 -#define AXIS_COMMAND_NON_MODAL 1 -#define AXIS_COMMAND_MOTION_MODE 2 -#define AXIS_COMMAND_TOOL_LENGTH_OFFSET 3 // *Undefined but required - -// Declare gc extern struct -parser_state_t gc_state; -parser_block_t gc_block; - -#define FAIL(status) return(status); - - -void gc_init() { - memset(&gc_state, 0, sizeof(parser_state_t)); - // Load default G54 coordinate system. - if (!(settings_read_coord_data(gc_state.modal.coord_select, gc_state.coord_system))) - report_status_message(STATUS_SETTING_READ_FAIL, CLIENT_SERIAL); -} - - -// Sets g-code parser position in mm. Input in steps. Called by the system abort and hard -// limit pull-off routines. -void gc_sync_position() { - system_convert_array_steps_to_mpos(gc_state.position, sys_position); -} - - -// Edit GCode line in-place, removing whitespace and comments and -// converting to uppercase -void collapseGCode(char *line) { - // parenPtr, if non-NULL, is the address of the character after ( - char* parenPtr = NULL; - // outPtr is the address where newly-processed characters will be placed. - // outPtr is alway less than or equal to inPtr. - char* outPtr = line; - char c; - for (char* inPtr = line; (c = *inPtr) != '\0'; inPtr++) { - if (isspace(c)) { - continue; - } - switch (c) { - case ')': - if (parenPtr) { - // Terminate comment by replacing ) with NUL - *inPtr = '\0'; - report_gcode_comment(parenPtr); - parenPtr = NULL; - } - // Strip out ) that does not follow a ( - break; - case '(': - // Start the comment at the character after ( - parenPtr = inPtr + 1; - break; - case ';': - // NOTE: ';' comment to EOL is a LinuxCNC definition. Not NIST. -#ifdef REPORT_SEMICOLON_COMMENTS - report_gcode_comment(inPtr + 1); -#endif - *outPtr = '\0'; - return; - case '%': - // TODO: Install '%' feature - // Program start-end percent sign NOT SUPPORTED. - // NOTE: This maybe installed to tell Grbl when a program is running vs manual input, - // where, during a program, the system auto-cycle start will continue to execute - // everything until the next '%' sign. This will help fix resuming issues with certain - // functions that empty the planner buffer to execute its task on-time. - break; - case '\r': - // In case one sneaks in - break; - default: - if (!parenPtr) { - *outPtr++ = toupper(c); // make upper case - } - } - } - // On loop exit, *inPtr is '\0' - if (parenPtr) { - // Handle unterminated ( comments - report_gcode_comment(parenPtr); - } - *outPtr = '\0'; -} - -// Executes one line of NUL-terminated G-Code. -// The line may contain whitespace and comments, which are first removed, -// and lower case characters, which are converted to upper case. -// In this function, all units and positions are converted and -// exported to grbl's internal functions in terms of (mm, mm/min) and absolute machine -// coordinates, respectively. -uint8_t gc_execute_line(char* line, uint8_t client) { - // Step 0 - remove whitespace and comments and convert to upper case - collapseGCode(line); -#ifdef REPORT_ECHO_LINE_RECEIVED - report_echo_line_received(line, client); -#endif - - /* ------------------------------------------------------------------------------------- - STEP 1: Initialize parser block struct and copy current g-code state modes. The parser - updates these modes and commands as the block line is parser and will only be used and - executed after successful error-checking. The parser block struct also contains a block - values struct, word tracking variables, and a non-modal commands tracker for the new - block. This struct contains all of the necessary information to execute the block. */ - memset(&gc_block, 0, sizeof(parser_block_t)); // Initialize the parser block struct. - memcpy(&gc_block.modal, &gc_state.modal, sizeof(gc_modal_t)); // Copy current modes - uint8_t axis_command = AXIS_COMMAND_NONE; - uint8_t axis_0, axis_1, axis_linear; - uint8_t coord_select = 0; // Tracks G10 P coordinate selection for execution - // Initialize bitflag tracking variables for axis indices compatible operations. - uint8_t axis_words = 0; // XYZ tracking - uint8_t ijk_words = 0; // IJK tracking - // Initialize command and value words and parser flags variables. - uint16_t command_words = 0; // Tracks G and M command words. Also used for modal group violations. - uint16_t value_words = 0; // Tracks value words. - uint8_t gc_parser_flags = GC_PARSER_NONE; - // Determine if the line is a jogging motion or a normal g-code block. - if (line[0] == '$') { // NOTE: `$J=` already parsed when passed to this function. - // Set G1 and G94 enforced modes to ensure accurate error checks. - gc_parser_flags |= GC_PARSER_JOG_MOTION; - gc_block.modal.motion = MOTION_MODE_LINEAR; - gc_block.modal.feed_rate = FEED_RATE_MODE_UNITS_PER_MIN; -#ifdef USE_LINE_NUMBERS - gc_block.values.n = JOG_LINE_NUMBER; // Initialize default line number reported during jog. -#endif - } - /* ------------------------------------------------------------------------------------- - STEP 2: Import all g-code words in the block line. A g-code word is a letter followed by - a number, which can either be a 'G'/'M' command or sets/assigns a command value. Also, - perform initial error-checks for command word modal group violations, for any repeated - words, and for negative values set for the value words F, N, P, T, and S. */ - uint8_t word_bit = 0; // Bit-value for assigning tracking variables - uint8_t char_counter; - char letter; - float value; - uint8_t int_value = 0; - uint16_t mantissa = 0; - if (gc_parser_flags & GC_PARSER_JOG_MOTION) { - char_counter = 3; // Start parsing after `$J=` - } else - char_counter = 0; - while (line[char_counter] != 0) { // Loop until no more g-code words in line. - // Import the next g-code word, expecting a letter followed by a value. Otherwise, error out. - letter = line[char_counter]; - if ((letter < 'A') || (letter > 'Z')) { - FAIL(STATUS_EXPECTED_COMMAND_LETTER); // [Expected word letter] - } - char_counter++; - if (!read_float(line, &char_counter, &value)) { - FAIL(STATUS_BAD_NUMBER_FORMAT); // [Expected word value] - } - // Convert values to smaller uint8 significand and mantissa values for parsing this word. - // NOTE: Mantissa is multiplied by 100 to catch non-integer command values. This is more - // accurate than the NIST gcode requirement of x10 when used for commands, but not quite - // accurate enough for value words that require integers to within 0.0001. This should be - // a good enough compromise and catch most all non-integer errors. To make it compliant, - // we would simply need to change the mantissa to int16, but this add compiled flash space. - // Maybe update this later. - int_value = trunc(value); - mantissa = round(100 * (value - int_value)); // Compute mantissa for Gxx.x commands. - // NOTE: Rounding must be used to catch small floating point errors. - // Check if the g-code word is supported or errors due to modal group violations or has - // been repeated in the g-code block. If ok, update the command or record its value. - switch (letter) { - /* 'G' and 'M' Command Words: Parse commands and check for modal group violations. - NOTE: Modal group numbers are defined in Table 4 of NIST RS274-NGC v3, pg.20 */ - case 'G': - // Determine 'G' command and its modal group - switch (int_value) { - case 10: - case 28: - case 30: - case 92: - // Check for G10/28/30/92 being called with G0/1/2/3/38 on same block. - // * G43.1 is also an axis command but is not explicitly defined this way. - if (mantissa == 0) { // Ignore G28.1, G30.1, and G92.1 - if (axis_command) { - FAIL(STATUS_GCODE_AXIS_COMMAND_CONFLICT); // [Axis word/command conflict] - } - axis_command = AXIS_COMMAND_NON_MODAL; - } - // No break. Continues to next line. - case 4: - case 53: - word_bit = MODAL_GROUP_G0; - gc_block.non_modal_command = int_value; - if ((int_value == 28) || (int_value == 30) || (int_value == 92)) { - if (!((mantissa == 0) || (mantissa == 10))) - FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); - gc_block.non_modal_command += mantissa; - mantissa = 0; // Set to zero to indicate valid non-integer G command. - } - break; - case 0: - case 1: - case 2: - case 3: - case 38: -#ifndef PROBE_PIN //only allow G38 "Probe" commands if a probe pin is defined. - if (int_value == 38) { - grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "No probe pin defined"); - FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); // [Unsupported G command] - } -#endif - // Check for G0/1/2/3/38 being called with G10/28/30/92 on same block. - // * G43.1 is also an axis command but is not explicitly defined this way. - if (axis_command) { - FAIL(STATUS_GCODE_AXIS_COMMAND_CONFLICT); // [Axis word/command conflict] - } - axis_command = AXIS_COMMAND_MOTION_MODE; - // No break. Continues to next line. - case 80: - word_bit = MODAL_GROUP_G1; - gc_block.modal.motion = int_value; - if (int_value == 38) { - if (!((mantissa == 20) || (mantissa == 30) || (mantissa == 40) || (mantissa == 50))) { - FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); // [Unsupported G38.x command] - } - gc_block.modal.motion += (mantissa / 10) + 100; - mantissa = 0; // Set to zero to indicate valid non-integer G command. - } - break; - case 17: - case 18: - case 19: - word_bit = MODAL_GROUP_G2; - gc_block.modal.plane_select = int_value - 17; - break; - case 90: - case 91: - if (mantissa == 0) { - word_bit = MODAL_GROUP_G3; - gc_block.modal.distance = int_value - 90; - } else { - word_bit = MODAL_GROUP_G4; - if ((mantissa != 10) || (int_value == 90)) { - FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); // [G90.1 not supported] - } - mantissa = 0; // Set to zero to indicate valid non-integer G command. - // Otherwise, arc IJK incremental mode is default. G91.1 does nothing. - } - break; - case 93: - case 94: - word_bit = MODAL_GROUP_G5; - gc_block.modal.feed_rate = 94 - int_value; - break; - case 20: - case 21: - word_bit = MODAL_GROUP_G6; - gc_block.modal.units = 21 - int_value; - break; - case 40: - word_bit = MODAL_GROUP_G7; - // NOTE: Not required since cutter radius compensation is always disabled. Only here - // to support G40 commands that often appear in g-code program headers to setup defaults. - // gc_block.modal.cutter_comp = CUTTER_COMP_DISABLE; // G40 - break; - case 43: - case 49: - word_bit = MODAL_GROUP_G8; - // NOTE: The NIST g-code standard vaguely states that when a tool length offset is changed, - // there cannot be any axis motion or coordinate offsets updated. Meaning G43, G43.1, and G49 - // all are explicit axis commands, regardless if they require axis words or not. - if (axis_command) - FAIL(STATUS_GCODE_AXIS_COMMAND_CONFLICT); - // [Axis word/command conflict] } - axis_command = AXIS_COMMAND_TOOL_LENGTH_OFFSET; - if (int_value == 49) // G49 - gc_block.modal.tool_length = TOOL_LENGTH_OFFSET_CANCEL; - else if (mantissa == 10) // G43.1 - gc_block.modal.tool_length = TOOL_LENGTH_OFFSET_ENABLE_DYNAMIC; - else { - FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); // [Unsupported G43.x command] - } - mantissa = 0; // Set to zero to indicate valid non-integer G command. - break; - case 54: - case 55: - case 56: - case 57: - case 58: - case 59: - // NOTE: G59.x are not supported. (But their int_values would be 60, 61, and 62.) - word_bit = MODAL_GROUP_G12; - gc_block.modal.coord_select = int_value - 54; // Shift to array indexing. - break; - case 61: - word_bit = MODAL_GROUP_G13; - if (mantissa != 0) { - FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); // [G61.1 not supported] - } - // gc_block.modal.control = CONTROL_MODE_EXACT_PATH; // G61 - break; - default: - FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); // [Unsupported G command] - } - if (mantissa > 0) { - FAIL(STATUS_GCODE_COMMAND_VALUE_NOT_INTEGER); // [Unsupported or invalid Gxx.x command] - } - // Check for more than one command per modal group violations in the current block - // NOTE: Variable 'word_bit' is always assigned, if the command is valid. - if (bit_istrue(command_words, bit(word_bit))) - FAIL(STATUS_GCODE_MODAL_GROUP_VIOLATION); - command_words |= bit(word_bit); - break; - case 'M': - // Determine 'M' command and its modal group - if (mantissa > 0) { - FAIL(STATUS_GCODE_COMMAND_VALUE_NOT_INTEGER); // [No Mxx.x commands] - } - switch (int_value) { - case 0: - case 1: - case 2: - case 30: - word_bit = MODAL_GROUP_M4; - switch (int_value) { - case 0: - gc_block.modal.program_flow = PROGRAM_FLOW_PAUSED; - break; // Program pause - case 1: - break; // Optional stop not supported. Ignore. - default: - gc_block.modal.program_flow = int_value; // Program end and reset - } - break; - case 3: - case 4: - case 5: - word_bit = MODAL_GROUP_M7; - switch (int_value) { - case 3: - gc_block.modal.spindle = SPINDLE_ENABLE_CW; - break; - case 4: // Supported if SPINDLE_DIR_PIN is defined or laser mode is on. - if (spindle->is_reversable || laser_mode->get()) - gc_block.modal.spindle = SPINDLE_ENABLE_CCW; - else - FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); - break; - case 5: - gc_block.modal.spindle = SPINDLE_DISABLE; - break; - } - break; - case 6: // tool change - word_bit = MODAL_GROUP_M6; - gc_block.modal.tool_change = TOOL_CHANGE; -#ifdef USE_TOOL_CHANGE - //user_tool_change(gc_state.tool); -#endif - break; - case 7: - case 8: - case 9: - word_bit = MODAL_GROUP_M8; - switch (int_value) { -#ifdef COOLANT_MIST_PIN - case 7: - gc_block.modal.coolant = COOLANT_MIST_ENABLE; - break; -#endif -#ifdef COOLANT_FLOOD_PIN - case 8: - gc_block.modal.coolant = COOLANT_FLOOD_ENABLE; - break; -#endif - case 9: - gc_block.modal.coolant = COOLANT_DISABLE; - break; - } - break; -#ifdef ENABLE_PARKING_OVERRIDE_CONTROL - case 56: - word_bit = MODAL_GROUP_M9; - gc_block.modal.override = OVERRIDE_PARKING_MOTION; - break; -#endif - case 62: - case 63: - //grbl_sendf(CLIENT_SERIAL,"M%d...\r\n", int_value); - word_bit = MODAL_GROUP_M10; - switch (int_value) { - case 62: - gc_block.modal.io_control = NON_MODAL_IO_ENABLE; - break; - case 63: - gc_block.modal.io_control = NON_MODAL_IO_DISABLE; - break; - default: - break; - } - break; - default: - FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); // [Unsupported M command] - } - // Check for more than one command per modal group violations in the current block - // NOTE: Variable 'word_bit' is always assigned, if the command is valid. - if (bit_istrue(command_words, bit(word_bit))) - FAIL(STATUS_GCODE_MODAL_GROUP_VIOLATION); - command_words |= bit(word_bit); - break; - // NOTE: All remaining letters assign values. - default: - /* Non-Command Words: This initial parsing phase only checks for repeats of the remaining - legal g-code words and stores their value. Error-checking is performed later since some - words (I,J,K,L,P,R) have multiple connotations and/or depend on the issued commands. */ - switch (letter) { -#if (N_AXIS > A_AXIS) - case 'A': - word_bit = WORD_A; - gc_block.values.xyz[A_AXIS] = value; - axis_words |= bit(A_AXIS); - break; -#endif -#if (N_AXIS > B_AXIS) - case 'B': - word_bit = WORD_B; - gc_block.values.xyz[B_AXIS] = value; - axis_words |= bit(B_AXIS); - break; -#endif -#if (N_AXIS > C_AXIS) - case 'C': - word_bit = WORD_C; - gc_block.values.xyz[C_AXIS] = value; - axis_words |= bit(C_AXIS); - break; -#endif - // case 'D': // Not supported - case 'F': - word_bit = WORD_F; - gc_block.values.f = value; - break; - // case 'H': // Not supported - case 'I': - word_bit = WORD_I; - gc_block.values.ijk[X_AXIS] = value; - ijk_words |= bit(X_AXIS); - break; - case 'J': - word_bit = WORD_J; - gc_block.values.ijk[Y_AXIS] = value; - ijk_words |= bit(Y_AXIS); - break; - case 'K': - word_bit = WORD_K; - gc_block.values.ijk[Z_AXIS] = value; - ijk_words |= bit(Z_AXIS); - break; - case 'L': - word_bit = WORD_L; - gc_block.values.l = int_value; - break; - case 'N': - word_bit = WORD_N; - gc_block.values.n = trunc(value); - break; - case 'P': - word_bit = WORD_P; - gc_block.values.p = value; - break; - // NOTE: For certain commands, P value must be an integer, but none of these commands are supported. - // case 'Q': // Not supported - case 'R': - word_bit = WORD_R; - gc_block.values.r = value; - break; - case 'S': - word_bit = WORD_S; - gc_block.values.s = value; - break; - case 'T': - word_bit = WORD_T; - if (value > MAX_TOOL_NUMBER) - FAIL(STATUS_GCODE_MAX_VALUE_EXCEEDED); - grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Tool No: %d", int_value); - gc_state.tool = int_value; - break; - case 'X': - word_bit = WORD_X; - gc_block.values.xyz[X_AXIS] = value; - axis_words |= bit(X_AXIS); - break; - case 'Y': - word_bit = WORD_Y; - gc_block.values.xyz[Y_AXIS] = value; - axis_words |= bit(Y_AXIS); - break; - case 'Z': - word_bit = WORD_Z; - gc_block.values.xyz[Z_AXIS] = value; - axis_words |= bit(Z_AXIS); - break; - default: - FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); - } - // NOTE: Variable 'word_bit' is always assigned, if the non-command letter is valid. - if (bit_istrue(value_words, bit(word_bit))) { - FAIL(STATUS_GCODE_WORD_REPEATED); // [Word repeated] - } - // Check for invalid negative values for words F, N, P, T, and S. - // NOTE: Negative value check is done here simply for code-efficiency. - if (bit(word_bit) & (bit(WORD_F) | bit(WORD_N) | bit(WORD_P) | bit(WORD_T) | bit(WORD_S))) { - if (value < 0.0) { - FAIL(STATUS_NEGATIVE_VALUE); // [Word value cannot be negative] - } - } - value_words |= bit(word_bit); // Flag to indicate parameter assigned. - } - } - // Parsing complete! - /* ------------------------------------------------------------------------------------- - STEP 3: Error-check all commands and values passed in this block. This step ensures all of - the commands are valid for execution and follows the NIST standard as closely as possible. - If an error is found, all commands and values in this block are dumped and will not update - the active system g-code modes. If the block is ok, the active system g-code modes will be - updated based on the commands of this block, and signal for it to be executed. - - Also, we have to pre-convert all of the values passed based on the modes set by the parsed - block. There are a number of error-checks that require target information that can only be - accurately calculated if we convert these values in conjunction with the error-checking. - This relegates the next execution step as only updating the system g-code modes and - performing the programmed actions in order. The execution step should not require any - conversion calculations and would only require minimal checks necessary to execute. - */ - /* NOTE: At this point, the g-code block has been parsed and the block line can be freed. - NOTE: It's also possible, at some future point, to break up STEP 2, to allow piece-wise - parsing of the block on a per-word basis, rather than the entire block. This could remove - the need for maintaining a large string variable for the entire block and free up some memory. - To do this, this would simply need to retain all of the data in STEP 1, such as the new block - data struct, the modal group and value bitflag tracking variables, and axis array indices - compatible variables. This data contains all of the information necessary to error-check the - new g-code block when the EOL character is received. However, this would break Grbl's startup - lines in how it currently works and would require some refactoring to make it compatible. - */ - // [0. Non-specific/common error-checks and miscellaneous setup]: - // Determine implicit axis command conditions. Axis words have been passed, but no explicit axis - // command has been sent. If so, set axis command to current motion mode. - if (axis_words) { - if (!axis_command) { - axis_command = AXIS_COMMAND_MOTION_MODE; // Assign implicit motion-mode - } - } - // Check for valid line number N value. - if (bit_istrue(value_words, bit(WORD_N))) { - // Line number value cannot be less than zero (done) or greater than max line number. - if (gc_block.values.n > MAX_LINE_NUMBER) { - FAIL(STATUS_GCODE_INVALID_LINE_NUMBER); // [Exceeds max line number] - } - } - // bit_false(value_words,bit(WORD_N)); // NOTE: Single-meaning value word. Set at end of error-checking. - // Track for unused words at the end of error-checking. - // NOTE: Single-meaning value words are removed all at once at the end of error-checking, because - // they are always used when present. This was done to save a few bytes of flash. For clarity, the - // single-meaning value words may be removed as they are used. Also, axis words are treated in the - // same way. If there is an explicit/implicit axis command, XYZ words are always used and are - // are removed at the end of error-checking. - // [1. Comments ]: MSG's NOT SUPPORTED. Comment handling performed by protocol. - // [2. Set feed rate mode ]: G93 F word missing with G1,G2/3 active, implicitly or explicitly. Feed rate - // is not defined after switching to G94 from G93. - // NOTE: For jogging, ignore prior feed rate mode. Enforce G94 and check for required F word. - if (gc_parser_flags & GC_PARSER_JOG_MOTION) { - if (bit_isfalse(value_words, bit(WORD_F))) - FAIL(STATUS_GCODE_UNDEFINED_FEED_RATE); - if (gc_block.modal.units == UNITS_MODE_INCHES) - gc_block.values.f *= MM_PER_INCH; - } else { - if (gc_block.modal.feed_rate == FEED_RATE_MODE_INVERSE_TIME) { // = G93 - // NOTE: G38 can also operate in inverse time, but is undefined as an error. Missing F word check added here. - if (axis_command == AXIS_COMMAND_MOTION_MODE) { - if ((gc_block.modal.motion != MOTION_MODE_NONE) || (gc_block.modal.motion != MOTION_MODE_SEEK)) { - if (bit_isfalse(value_words, bit(WORD_F))) { - FAIL(STATUS_GCODE_UNDEFINED_FEED_RATE); // [F word missing] - } - } - } - // NOTE: It seems redundant to check for an F word to be passed after switching from G94 to G93. We would - // accomplish the exact same thing if the feed rate value is always reset to zero and undefined after each - // inverse time block, since the commands that use this value already perform undefined checks. This would - // also allow other commands, following this switch, to execute and not error out needlessly. This code is - // combined with the above feed rate mode and the below set feed rate error-checking. - // [3. Set feed rate ]: F is negative (done.) - // - In inverse time mode: Always implicitly zero the feed rate value before and after block completion. - // NOTE: If in G93 mode or switched into it from G94, just keep F value as initialized zero or passed F word - // value in the block. If no F word is passed with a motion command that requires a feed rate, this will error - // out in the motion modes error-checking. However, if no F word is passed with NO motion command that requires - // a feed rate, we simply move on and the state feed rate value gets updated to zero and remains undefined. - } else { // = G94 - // - In units per mm mode: If F word passed, ensure value is in mm/min, otherwise push last state value. - if (gc_state.modal.feed_rate == FEED_RATE_MODE_UNITS_PER_MIN) { // Last state is also G94 - if (bit_istrue(value_words, bit(WORD_F))) { - if (gc_block.modal.units == UNITS_MODE_INCHES) - gc_block.values.f *= MM_PER_INCH; - } else { - gc_block.values.f = gc_state.feed_rate; // Push last state feed rate - } - } // Else, switching to G94 from G93, so don't push last state feed rate. Its undefined or the passed F word value. - } - } - // bit_false(value_words,bit(WORD_F)); // NOTE: Single-meaning value word. Set at end of error-checking. - // [4. Set spindle speed ]: S is negative (done.) - if (bit_isfalse(value_words, bit(WORD_S))) - gc_block.values.s = gc_state.spindle_speed; - // bit_false(value_words,bit(WORD_S)); // NOTE: Single-meaning value word. Set at end of error-checking. - // [5. Select tool ]: NOT SUPPORTED. Only tracks value. T is negative (done.) Not an integer. Greater than max tool value. - // bit_false(value_words,bit(WORD_T)); // NOTE: Single-meaning value word. Set at end of error-checking. - // [6. Change tool ]: N/A - // [7. Spindle control ]: N/A - // [8. Coolant control ]: N/A - // [9. Enable/disable feed rate or spindle overrides ]: NOT SUPPORTED. -#ifdef ENABLE_PARKING_OVERRIDE_CONTROL - if (bit_istrue(command_words, bit(MODAL_GROUP_M9))) { // Already set as enabled in parser. - if (bit_istrue(value_words, bit(WORD_P))) { - if (gc_block.values.p == 0.0) gc_block.modal.override = OVERRIDE_DISABLED; - bit_false(value_words, bit(WORD_P)); - } - } -#endif - // [10. Dwell ]: P value missing. P is negative (done.) NOTE: See below. - if (gc_block.non_modal_command == NON_MODAL_DWELL) { - if (bit_isfalse(value_words, bit(WORD_P))) { - FAIL(STATUS_GCODE_VALUE_WORD_MISSING); // [P word missing] - } - bit_false(value_words, bit(WORD_P)); - } - if ((gc_block.modal.io_control == NON_MODAL_IO_ENABLE) || (gc_block.modal.io_control == NON_MODAL_IO_DISABLE)) { - if (bit_isfalse(value_words, bit(WORD_P))) { - FAIL(STATUS_GCODE_VALUE_WORD_MISSING); // [P word missing] - } - bit_false(value_words, bit(WORD_P)); - } - // [11. Set active plane ]: N/A - switch (gc_block.modal.plane_select) { - case PLANE_SELECT_XY: - axis_0 = X_AXIS; - axis_1 = Y_AXIS; - axis_linear = Z_AXIS; - break; - case PLANE_SELECT_ZX: - axis_0 = Z_AXIS; - axis_1 = X_AXIS; - axis_linear = Y_AXIS; - break; - default: // case PLANE_SELECT_YZ: - axis_0 = Y_AXIS; - axis_1 = Z_AXIS; - axis_linear = X_AXIS; - } - // [12. Set length units ]: N/A - // Pre-convert XYZ coordinate values to millimeters, if applicable. - uint8_t idx; - if (gc_block.modal.units == UNITS_MODE_INCHES) { - for (idx = 0; idx < N_AXIS; idx++) { // Axes indices are consistent, so loop may be used. - if (bit_istrue(axis_words, bit(idx))) - gc_block.values.xyz[idx] *= MM_PER_INCH; - } - } - // [13. Cutter radius compensation ]: G41/42 NOT SUPPORTED. Error, if enabled while G53 is active. - // [G40 Errors]: G2/3 arc is programmed after a G40. The linear move after disabling is less than tool diameter. - // NOTE: Since cutter radius compensation is never enabled, these G40 errors don't apply. Grbl supports G40 - // only for the purpose to not error when G40 is sent with a g-code program header to setup the default modes. - // [14. Cutter length compensation ]: G43 NOT SUPPORTED, but G43.1 and G49 are. - // [G43.1 Errors]: Motion command in same line. - // NOTE: Although not explicitly stated so, G43.1 should be applied to only one valid - // axis that is configured (in config.h). There should be an error if the configured axis - // is absent or if any of the other axis words are present. - if (axis_command == AXIS_COMMAND_TOOL_LENGTH_OFFSET) { // Indicates called in block. - if (gc_block.modal.tool_length == TOOL_LENGTH_OFFSET_ENABLE_DYNAMIC) { - if (axis_words ^ bit(TOOL_LENGTH_OFFSET_AXIS)) - FAIL(STATUS_GCODE_G43_DYNAMIC_AXIS_ERROR); - } - } - // [15. Coordinate system selection ]: *N/A. Error, if cutter radius comp is active. - // TODO: An EEPROM read of the coordinate data may require a buffer sync when the cycle - // is active. The read pauses the processor temporarily and may cause a rare crash. For - // future versions on processors with enough memory, all coordinate data should be stored - // in memory and written to EEPROM only when there is not a cycle active. - float block_coord_system[N_AXIS]; - memcpy(block_coord_system, gc_state.coord_system, sizeof(gc_state.coord_system)); - if (bit_istrue(command_words, bit(MODAL_GROUP_G12))) { // Check if called in block - if (gc_block.modal.coord_select > N_COORDINATE_SYSTEM) { - FAIL(STATUS_GCODE_UNSUPPORTED_COORD_SYS); // [Greater than N sys] - } - if (gc_state.modal.coord_select != gc_block.modal.coord_select) { - if (!(settings_read_coord_data(gc_block.modal.coord_select, block_coord_system))) - FAIL(STATUS_SETTING_READ_FAIL); - } - } - // [16. Set path control mode ]: N/A. Only G61. G61.1 and G64 NOT SUPPORTED. - // [17. Set distance mode ]: N/A. Only G91.1. G90.1 NOT SUPPORTED. - // [18. Set retract mode ]: NOT SUPPORTED. - // [19. Remaining non-modal actions ]: Check go to predefined position, set G10, or set axis offsets. - // NOTE: We need to separate the non-modal commands that are axis word-using (G10/G28/G30/G92), as these - // commands all treat axis words differently. G10 as absolute offsets or computes current position as - // the axis value, G92 similarly to G10 L20, and G28/30 as an intermediate target position that observes - // all the current coordinate system and G92 offsets. - switch (gc_block.non_modal_command) { - case NON_MODAL_SET_COORDINATE_DATA: - // [G10 Errors]: L missing and is not 2 or 20. P word missing. (Negative P value done.) - // [G10 L2 Errors]: R word NOT SUPPORTED. P value not 0 to nCoordSys(max 9). Axis words missing. - // [G10 L20 Errors]: P must be 0 to nCoordSys(max 9). Axis words missing. - if (!axis_words) { - FAIL(STATUS_GCODE_NO_AXIS_WORDS) - }; // [No axis words] - if (bit_isfalse(value_words, (bit(WORD_P) | bit(WORD_L)))) { - FAIL(STATUS_GCODE_VALUE_WORD_MISSING); // [P/L word missing] - } - coord_select = trunc(gc_block.values.p); // Convert p value to int. - if (coord_select > N_COORDINATE_SYSTEM) { - FAIL(STATUS_GCODE_UNSUPPORTED_COORD_SYS); // [Greater than N sys] - } - if (gc_block.values.l != 20) { - if (gc_block.values.l == 2) { - if (bit_istrue(value_words, bit(WORD_R))) { - FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); // [G10 L2 R not supported] - } - } else { - FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); // [Unsupported L] - } - } - bit_false(value_words, (bit(WORD_L) | bit(WORD_P))); - // Determine coordinate system to change and try to load from EEPROM. - if (coord_select > 0) { - coord_select--; // Adjust P1-P6 index to EEPROM coordinate data indexing. - } else { - coord_select = gc_block.modal.coord_select; // Index P0 as the active coordinate system - } - // NOTE: Store parameter data in IJK values. By rule, they are not in use with this command. - // FIXME: Instead of IJK, we'd better use: float vector[N_AXIS]; // [DG] - if (!settings_read_coord_data(coord_select, gc_block.values.ijk)) { - FAIL(STATUS_SETTING_READ_FAIL); // [EEPROM read fail] - } - // Pre-calculate the coordinate data changes. - for (idx = 0; idx < N_AXIS; idx++) { // Axes indices are consistent, so loop may be used. - // Update axes defined only in block. Always in machine coordinates. Can change non-active system. - if (bit_istrue(axis_words, bit(idx))) { - if (gc_block.values.l == 20) { - // L20: Update coordinate system axis at current position (with modifiers) with programmed value - // WPos = MPos - WCS - G92 - TLO -> WCS = MPos - G92 - TLO - WPos - gc_block.values.ijk[idx] = gc_state.position[idx] - gc_state.coord_offset[idx] - gc_block.values.xyz[idx]; - if (idx == TOOL_LENGTH_OFFSET_AXIS) - gc_block.values.ijk[idx] -= gc_state.tool_length_offset; - } else { - // L2: Update coordinate system axis to programmed value. - gc_block.values.ijk[idx] = gc_block.values.xyz[idx]; - } - } // Else, keep current stored value. - } - break; - case NON_MODAL_SET_COORDINATE_OFFSET: - // [G92 Errors]: No axis words. - if (!axis_words) { - FAIL(STATUS_GCODE_NO_AXIS_WORDS); // [No axis words] - } - // Update axes defined only in block. Offsets current system to defined value. Does not update when - // active coordinate system is selected, but is still active unless G92.1 disables it. - for (idx = 0; idx < N_AXIS; idx++) { // Axes indices are consistent, so loop may be used. - if (bit_istrue(axis_words, bit(idx))) { - // WPos = MPos - WCS - G92 - TLO -> G92 = MPos - WCS - TLO - WPos - gc_block.values.xyz[idx] = gc_state.position[idx] - block_coord_system[idx] - gc_block.values.xyz[idx]; - if (idx == TOOL_LENGTH_OFFSET_AXIS) - gc_block.values.xyz[idx] -= gc_state.tool_length_offset; - } else - gc_block.values.xyz[idx] = gc_state.coord_offset[idx]; - } - break; - default: - // At this point, the rest of the explicit axis commands treat the axis values as the traditional - // target position with the coordinate system offsets, G92 offsets, absolute override, and distance - // modes applied. This includes the motion mode commands. We can now pre-compute the target position. - // NOTE: Tool offsets may be appended to these conversions when/if this feature is added. - if (axis_command != AXIS_COMMAND_TOOL_LENGTH_OFFSET) { // TLO block any axis command. - if (axis_words) { - for (idx = 0; idx < N_AXIS; idx++) { // Axes indices are consistent, so loop may be used to save flash space. - if (bit_isfalse(axis_words, bit(idx))) { - gc_block.values.xyz[idx] = gc_state.position[idx]; // No axis word in block. Keep same axis position. - } else { - // Update specified value according to distance mode or ignore if absolute override is active. - // NOTE: G53 is never active with G28/30 since they are in the same modal group. - if (gc_block.non_modal_command != NON_MODAL_ABSOLUTE_OVERRIDE) { - // Apply coordinate offsets based on distance mode. - if (gc_block.modal.distance == DISTANCE_MODE_ABSOLUTE) { - gc_block.values.xyz[idx] += block_coord_system[idx] + gc_state.coord_offset[idx]; - if (idx == TOOL_LENGTH_OFFSET_AXIS) - gc_block.values.xyz[idx] += gc_state.tool_length_offset; - } else // Incremental mode - gc_block.values.xyz[idx] += gc_state.position[idx]; - } - } - } - } - } - // Check remaining non-modal commands for errors. - switch (gc_block.non_modal_command) { - case NON_MODAL_GO_HOME_0: // G28 - case NON_MODAL_GO_HOME_1: // G30 - // [G28/30 Errors]: Cutter compensation is enabled. - // Retreive G28/30 go-home position data (in machine coordinates) from EEPROM - // NOTE: Store parameter data in IJK values. By rule, they are not in use with this command. - if (gc_block.non_modal_command == NON_MODAL_GO_HOME_0) { - if (!settings_read_coord_data(SETTING_INDEX_G28, gc_block.values.ijk)) - FAIL(STATUS_SETTING_READ_FAIL); - } else { // == NON_MODAL_GO_HOME_1 - if (!settings_read_coord_data(SETTING_INDEX_G30, gc_block.values.ijk)) - FAIL(STATUS_SETTING_READ_FAIL); - } - if (axis_words) { - // Move only the axes specified in secondary move. - for (idx = 0; idx < N_AXIS; idx++) { - if (!(axis_words & bit(idx))) - gc_block.values.ijk[idx] = gc_state.position[idx]; - } - } else { - axis_command = AXIS_COMMAND_NONE; // Set to none if no intermediate motion. - } - break; - case NON_MODAL_SET_HOME_0: // G28.1 - case NON_MODAL_SET_HOME_1: // G30.1 - // [G28.1/30.1 Errors]: Cutter compensation is enabled. - // NOTE: If axis words are passed here, they are interpreted as an implicit motion mode. - break; - case NON_MODAL_RESET_COORDINATE_OFFSET: - // NOTE: If axis words are passed here, they are interpreted as an implicit motion mode. - break; - case NON_MODAL_ABSOLUTE_OVERRIDE: - // [G53 Errors]: G0 and G1 are not active. Cutter compensation is enabled. - // NOTE: All explicit axis word commands are in this modal group. So no implicit check necessary. - if (!(gc_block.modal.motion == MOTION_MODE_SEEK || gc_block.modal.motion == MOTION_MODE_LINEAR)) { - FAIL(STATUS_GCODE_G53_INVALID_MOTION_MODE); // [G53 G0/1 not active] - } - break; - } - } - // [20. Motion modes ]: - if (gc_block.modal.motion == MOTION_MODE_NONE) { - // [G80 Errors]: Axis word are programmed while G80 is active. - // NOTE: Even non-modal commands or TLO that use axis words will throw this strict error. - if (axis_words) { - FAIL(STATUS_GCODE_AXIS_WORDS_EXIST); // [No axis words allowed] - } - // Check remaining motion modes, if axis word are implicit (exist and not used by G10/28/30/92), or - // was explicitly commanded in the g-code block. - } else if (axis_command == AXIS_COMMAND_MOTION_MODE) { - if (gc_block.modal.motion == MOTION_MODE_SEEK) { - // [G0 Errors]: Axis letter not configured or without real value (done.) - // Axis words are optional. If missing, set axis command flag to ignore execution. - if (!axis_words) - axis_command = AXIS_COMMAND_NONE; - // All remaining motion modes (all but G0 and G80), require a valid feed rate value. In units per mm mode, - // the value must be positive. In inverse time mode, a positive value must be passed with each block. - } else { - // Check if feed rate is defined for the motion modes that require it. - if (gc_block.values.f == 0.0) { - FAIL(STATUS_GCODE_UNDEFINED_FEED_RATE); // [Feed rate undefined] - } - switch (gc_block.modal.motion) { - case MOTION_MODE_LINEAR: - // [G1 Errors]: Feed rate undefined. Axis letter not configured or without real value. - // Axis words are optional. If missing, set axis command flag to ignore execution. - if (!axis_words) - axis_command = AXIS_COMMAND_NONE; - break; - case MOTION_MODE_CW_ARC: - gc_parser_flags |= GC_PARSER_ARC_IS_CLOCKWISE; // No break intentional. - case MOTION_MODE_CCW_ARC: - // [G2/3 Errors All-Modes]: Feed rate undefined. - // [G2/3 Radius-Mode Errors]: No axis words in selected plane. Target point is same as current. - // [G2/3 Offset-Mode Errors]: No axis words and/or offsets in selected plane. The radius to the current - // point and the radius to the target point differs more than 0.002mm (EMC def. 0.5mm OR 0.005mm and 0.1% radius). - // [G2/3 Full-Circle-Mode Errors]: NOT SUPPORTED. Axis words exist. No offsets programmed. P must be an integer. - // NOTE: Both radius and offsets are required for arc tracing and are pre-computed with the error-checking. - if (!axis_words) { - FAIL(STATUS_GCODE_NO_AXIS_WORDS); // [No axis words] - } - if (!(axis_words & (bit(axis_0) | bit(axis_1)))) { - FAIL(STATUS_GCODE_NO_AXIS_WORDS_IN_PLANE); // [No axis words in plane] - } - // Calculate the change in position along each selected axis - float x, y; - x = gc_block.values.xyz[axis_0] - gc_state.position[axis_0]; // Delta x between current position and target - y = gc_block.values.xyz[axis_1] - gc_state.position[axis_1]; // Delta y between current position and target - if (value_words & bit(WORD_R)) { // Arc Radius Mode - bit_false(value_words, bit(WORD_R)); - if (isequal_position_vector(gc_state.position, gc_block.values.xyz)) { - FAIL(STATUS_GCODE_INVALID_TARGET); // [Invalid target] - } - // Convert radius value to proper units. - if (gc_block.modal.units == UNITS_MODE_INCHES) - gc_block.values.r *= MM_PER_INCH; - /* We need to calculate the center of the circle that has the designated radius and passes - through both the current position and the target position. This method calculates the following - set of equations where [x,y] is the vector from current to target position, d == magnitude of - that vector, h == hypotenuse of the triangle formed by the radius of the circle, the distance to - the center of the travel vector. A vector perpendicular to the travel vector [-y,x] is scaled to the - length of h [-y/d*h, x/d*h] and added to the center of the travel vector [x/2,y/2] to form the new point - [i,j] at [x/2-y/d*h, y/2+x/d*h] which will be the center of our arc. - - d^2 == x^2 + y^2 - h^2 == r^2 - (d/2)^2 - i == x/2 - y/d*h - j == y/2 + x/d*h - - O <- [i,j] - - | - r - | - - | - - | h - - | - [0,0] -> C -----------------+--------------- T <- [x,y] - | <------ d/2 ---->| - - C - Current position - T - Target position - O - center of circle that pass through both C and T - d - distance from C to T - r - designated radius - h - distance from center of CT to O - - Expanding the equations: - - d -> sqrt(x^2 + y^2) - h -> sqrt(4 * r^2 - x^2 - y^2)/2 - i -> (x - (y * sqrt(4 * r^2 - x^2 - y^2)) / sqrt(x^2 + y^2)) / 2 - j -> (y + (x * sqrt(4 * r^2 - x^2 - y^2)) / sqrt(x^2 + y^2)) / 2 - - Which can be written: - - i -> (x - (y * sqrt(4 * r^2 - x^2 - y^2))/sqrt(x^2 + y^2))/2 - j -> (y + (x * sqrt(4 * r^2 - x^2 - y^2))/sqrt(x^2 + y^2))/2 - - Which we for size and speed reasons optimize to: - - h_x2_div_d = sqrt(4 * r^2 - x^2 - y^2)/sqrt(x^2 + y^2) - i = (x - (y * h_x2_div_d))/2 - j = (y + (x * h_x2_div_d))/2 - */ - // First, use h_x2_div_d to compute 4*h^2 to check if it is negative or r is smaller - // than d. If so, the sqrt of a negative number is complex and error out. - float h_x2_div_d = 4.0 * gc_block.values.r * gc_block.values.r - x * x - y * y; - if (h_x2_div_d < 0) { - FAIL(STATUS_GCODE_ARC_RADIUS_ERROR); // [Arc radius error] - } - // Finish computing h_x2_div_d. - h_x2_div_d = -sqrt(h_x2_div_d) / hypot_f(x, y); // == -(h * 2 / d) - // Invert the sign of h_x2_div_d if the circle is counter clockwise (see sketch below) - if (gc_block.modal.motion == MOTION_MODE_CCW_ARC) - h_x2_div_d = -h_x2_div_d; - /* The counter clockwise circle lies to the left of the target direction. When offset is positive, - the left hand circle will be generated - when it is negative the right hand circle is generated. - - T <-- Target position - - ^ - Clockwise circles with this center | Clockwise circles with this center will have - will have > 180 deg of angular travel | < 180 deg of angular travel, which is a good thing! - \ | / - center of arc when h_x2_div_d is positive -> x <----- | -----> x <- center of arc when h_x2_div_d is negative - | - | - - C <-- Current position - */ - // Negative R is g-code-alese for "I want a circle with more than 180 degrees of travel" (go figure!), - // even though it is advised against ever generating such circles in a single line of g-code. By - // inverting the sign of h_x2_div_d the center of the circles is placed on the opposite side of the line of - // travel and thus we get the unadvisably long arcs as prescribed. - if (gc_block.values.r < 0) { - h_x2_div_d = -h_x2_div_d; - gc_block.values.r = -gc_block.values.r; // Finished with r. Set to positive for mc_arc - } - // Complete the operation by calculating the actual center of the arc - gc_block.values.ijk[axis_0] = 0.5 * (x - (y * h_x2_div_d)); - gc_block.values.ijk[axis_1] = 0.5 * (y + (x * h_x2_div_d)); - } else { // Arc Center Format Offset Mode - if (!(ijk_words & (bit(axis_0) | bit(axis_1)))) { - FAIL(STATUS_GCODE_NO_OFFSETS_IN_PLANE); // [No offsets in plane] - } - bit_false(value_words, (bit(WORD_I) | bit(WORD_J) | bit(WORD_K))); - // Convert IJK values to proper units. - if (gc_block.modal.units == UNITS_MODE_INCHES) { - for (idx = 0; idx < N_AXIS; idx++) { // Axes indices are consistent, so loop may be used to save flash space. - if (ijk_words & bit(idx)) - gc_block.values.ijk[idx] *= MM_PER_INCH; - } - } - // Arc radius from center to target - x -= gc_block.values.ijk[axis_0]; // Delta x between circle center and target - y -= gc_block.values.ijk[axis_1]; // Delta y between circle center and target - float target_r = hypot_f(x, y); - // Compute arc radius for mc_arc. Defined from current location to center. - gc_block.values.r = hypot_f(gc_block.values.ijk[axis_0], gc_block.values.ijk[axis_1]); - // Compute difference between current location and target radii for final error-checks. - float delta_r = fabs(target_r - gc_block.values.r); - if (delta_r > 0.005) { - if (delta_r > 0.5) { - FAIL(STATUS_GCODE_INVALID_TARGET); // [Arc definition error] > 0.5mm - } - if (delta_r > (0.001 * gc_block.values.r)) { - FAIL(STATUS_GCODE_INVALID_TARGET); // [Arc definition error] > 0.005mm AND 0.1% radius - } - } - } - break; - case MOTION_MODE_PROBE_TOWARD_NO_ERROR: - case MOTION_MODE_PROBE_AWAY_NO_ERROR: - gc_parser_flags |= GC_PARSER_PROBE_IS_NO_ERROR; // No break intentional. - case MOTION_MODE_PROBE_TOWARD: - case MOTION_MODE_PROBE_AWAY: - if ((gc_block.modal.motion == MOTION_MODE_PROBE_AWAY) || - (gc_block.modal.motion == MOTION_MODE_PROBE_AWAY_NO_ERROR)) - gc_parser_flags |= GC_PARSER_PROBE_IS_AWAY; - // [G38 Errors]: Target is same current. No axis words. Cutter compensation is enabled. Feed rate - // is undefined. Probe is triggered. NOTE: Probe check moved to probe cycle. Instead of returning - // an error, it issues an alarm to prevent further motion to the probe. It's also done there to - // allow the planner buffer to empty and move off the probe trigger before another probing cycle. - if (!axis_words) { - FAIL(STATUS_GCODE_NO_AXIS_WORDS); // [No axis words] - } - if (isequal_position_vector(gc_state.position, gc_block.values.xyz)) { - FAIL(STATUS_GCODE_INVALID_TARGET); // [Invalid target] - } - break; - } - } - } - // [21. Program flow ]: No error checks required. - // [0. Non-specific error-checks]: Complete unused value words check, i.e. IJK used when in arc - // radius mode, or axis words that aren't used in the block. - if (gc_parser_flags & GC_PARSER_JOG_MOTION) { - // Jogging only uses the F feed rate and XYZ value words. N is valid, but S and T are invalid. - bit_false(value_words, (bit(WORD_N) | bit(WORD_F))); - } else { - bit_false(value_words, (bit(WORD_N) | bit(WORD_F) | bit(WORD_S) | bit(WORD_T))); // Remove single-meaning value words. - } - if (axis_command) { - bit_false(value_words, (bit(WORD_X) | bit(WORD_Y) | bit(WORD_Z) | bit(WORD_A) | bit(WORD_B) | bit(WORD_C))); // Remove axis words. - } - if (value_words) { - FAIL(STATUS_GCODE_UNUSED_WORDS); // [Unused words] - } - /* ------------------------------------------------------------------------------------- - STEP 4: EXECUTE!! - Assumes that all error-checking has been completed and no failure modes exist. We just - need to update the state and execute the block according to the order-of-execution. - */ - // Initialize planner data struct for motion blocks. - plan_line_data_t plan_data; - plan_line_data_t* pl_data = &plan_data; - memset(pl_data, 0, sizeof(plan_line_data_t)); // Zero pl_data struct - // Intercept jog commands and complete error checking for valid jog commands and execute. - // NOTE: G-code parser state is not updated, except the position to ensure sequential jog - // targets are computed correctly. The final parser position after a jog is updated in - // protocol_execute_realtime() when jogging completes or is canceled. - if (gc_parser_flags & GC_PARSER_JOG_MOTION) { - // Only distance and unit modal commands and G53 absolute override command are allowed. - // NOTE: Feed rate word and axis word checks have already been performed in STEP 3. - if (command_words & ~(bit(MODAL_GROUP_G3) | bit(MODAL_GROUP_G6) | bit(MODAL_GROUP_G0))) { - FAIL(STATUS_INVALID_JOG_COMMAND) - }; - if (!(gc_block.non_modal_command == NON_MODAL_ABSOLUTE_OVERRIDE || gc_block.non_modal_command == NON_MODAL_NO_ACTION)) - FAIL(STATUS_INVALID_JOG_COMMAND); - // Initialize planner data to current spindle and coolant modal state. - pl_data->spindle_speed = gc_state.spindle_speed; - plan_data.condition = (gc_state.modal.spindle | gc_state.modal.coolant); - uint8_t status = jog_execute(&plan_data, &gc_block); - if (status == STATUS_OK) - memcpy(gc_state.position, gc_block.values.xyz, sizeof(gc_block.values.xyz)); - return (status); - } - // If in laser mode, setup laser power based on current and past parser conditions. - if (laser_mode->get()) { - if (!((gc_block.modal.motion == MOTION_MODE_LINEAR) || (gc_block.modal.motion == MOTION_MODE_CW_ARC) - || (gc_block.modal.motion == MOTION_MODE_CCW_ARC))) - gc_parser_flags |= GC_PARSER_LASER_DISABLE; - // Any motion mode with axis words is allowed to be passed from a spindle speed update. - // NOTE: G1 and G0 without axis words sets axis_command to none. G28/30 are intentionally omitted. - // TODO: Check sync conditions for M3 enabled motions that don't enter the planner. (zero length). - if (axis_words && (axis_command == AXIS_COMMAND_MOTION_MODE)) - gc_parser_flags |= GC_PARSER_LASER_ISMOTION; - else { - // M3 constant power laser requires planner syncs to update the laser when changing between - // a G1/2/3 motion mode state and vice versa when there is no motion in the line. - if (gc_state.modal.spindle == SPINDLE_ENABLE_CW) { - if ((gc_state.modal.motion == MOTION_MODE_LINEAR) || (gc_state.modal.motion == MOTION_MODE_CW_ARC) - || (gc_state.modal.motion == MOTION_MODE_CCW_ARC)) { - if (bit_istrue(gc_parser_flags, GC_PARSER_LASER_DISABLE)) { - gc_parser_flags |= GC_PARSER_LASER_FORCE_SYNC; // Change from G1/2/3 motion mode. - } - } else { - // When changing to a G1 motion mode without axis words from a non-G1/2/3 motion mode. - if (bit_isfalse(gc_parser_flags, GC_PARSER_LASER_DISABLE)) - gc_parser_flags |= GC_PARSER_LASER_FORCE_SYNC; - } - } - } - } - // [0. Non-specific/common error-checks and miscellaneous setup]: - // NOTE: If no line number is present, the value is zero. - gc_state.line_number = gc_block.values.n; -#ifdef USE_LINE_NUMBERS - pl_data->line_number = gc_state.line_number; // Record data for planner use. -#endif - // [1. Comments feedback ]: NOT SUPPORTED - // [2. Set feed rate mode ]: - gc_state.modal.feed_rate = gc_block.modal.feed_rate; - if (gc_state.modal.feed_rate) { - pl_data->condition |= PL_COND_FLAG_INVERSE_TIME; // Set condition flag for planner use. - } - // [3. Set feed rate ]: - gc_state.feed_rate = gc_block.values.f; // Always copy this value. See feed rate error-checking. - pl_data->feed_rate = gc_state.feed_rate; // Record data for planner use. - // [4. Set spindle speed ]: - if ((gc_state.spindle_speed != gc_block.values.s) || bit_istrue(gc_parser_flags, GC_PARSER_LASER_FORCE_SYNC)) { - if (gc_state.modal.spindle != SPINDLE_DISABLE) { - if (bit_isfalse(gc_parser_flags, GC_PARSER_LASER_ISMOTION)) { - if (bit_istrue(gc_parser_flags, GC_PARSER_LASER_DISABLE)) - spindle->spindle_sync(gc_state.modal.spindle, 0); - else - spindle->spindle_sync(gc_state.modal.spindle, (uint32_t)gc_block.values.s); - } - } - gc_state.spindle_speed = gc_block.values.s; // Update spindle speed state. - } - // NOTE: Pass zero spindle speed for all restricted laser motions. - if (bit_isfalse(gc_parser_flags, GC_PARSER_LASER_DISABLE)) { - pl_data->spindle_speed = gc_state.spindle_speed; // Record data for planner use. - } // else { pl_data->spindle_speed = 0.0; } // Initialized as zero already. - // [5. Select tool ]: NOT SUPPORTED. Only tracks tool value. - // gc_state.tool = gc_block.values.t; - // [6. Change tool ]: NOT SUPPORTED - if (gc_block.modal.tool_change == TOOL_CHANGE) { -#ifdef USE_TOOL_CHANGE - user_tool_change(gc_state.tool); -#endif - } - // [7. Spindle control ]: - if (gc_state.modal.spindle != gc_block.modal.spindle) { - // Update spindle control and apply spindle speed when enabling it in this block. - // NOTE: All spindle state changes are synced, even in laser mode. Also, pl_data, - // rather than gc_state, is used to manage laser state for non-laser motions. - spindle->spindle_sync(gc_block.modal.spindle, (uint32_t)pl_data->spindle_speed); - gc_state.modal.spindle = gc_block.modal.spindle; - } - pl_data->condition |= gc_state.modal.spindle; // Set condition flag for planner use. - // [8. Coolant control ]: - if (gc_state.modal.coolant != gc_block.modal.coolant) { - // NOTE: Coolant M-codes are modal. Only one command per line is allowed. But, multiple states - // can exist at the same time, while coolant disable clears all states. - coolant_sync(gc_block.modal.coolant); - if (gc_block.modal.coolant == COOLANT_DISABLE) - gc_state.modal.coolant = COOLANT_DISABLE; - else - gc_state.modal.coolant |= gc_block.modal.coolant; - } - pl_data->condition |= gc_state.modal.coolant; // Set condition flag for planner use. - // turn on/off an i/o pin - if ((gc_block.modal.io_control == NON_MODAL_IO_ENABLE) || (gc_block.modal.io_control == NON_MODAL_IO_DISABLE)) { - if (gc_block.values.p <= MAX_USER_DIGITAL_PIN) - sys_io_control(bit((int)gc_block.values.p), (gc_block.modal.io_control == NON_MODAL_IO_ENABLE)); - else - FAIL(STATUS_P_PARAM_MAX_EXCEEDED); - } - // [9. Override control ]: NOT SUPPORTED. Always enabled. Except for a Grbl-only parking control. -#ifdef ENABLE_PARKING_OVERRIDE_CONTROL - if (gc_state.modal.override != gc_block.modal.override) { - gc_state.modal.override = gc_block.modal.override; - mc_override_ctrl_update(gc_state.modal.override); - } -#endif - // [10. Dwell ]: - if (gc_block.non_modal_command == NON_MODAL_DWELL) - mc_dwell(gc_block.values.p); - // [11. Set active plane ]: - gc_state.modal.plane_select = gc_block.modal.plane_select; - // [12. Set length units ]: - gc_state.modal.units = gc_block.modal.units; - // [13. Cutter radius compensation ]: G41/42 NOT SUPPORTED - // gc_state.modal.cutter_comp = gc_block.modal.cutter_comp; // NOTE: Not needed since always disabled. - // [14. Cutter length compensation ]: G43.1 and G49 supported. G43 NOT SUPPORTED. - // NOTE: If G43 were supported, its operation wouldn't be any different from G43.1 in terms - // of execution. The error-checking step would simply load the offset value into the correct - // axis of the block XYZ value array. - if (axis_command == AXIS_COMMAND_TOOL_LENGTH_OFFSET) { // Indicates a change. - gc_state.modal.tool_length = gc_block.modal.tool_length; - if (gc_state.modal.tool_length == TOOL_LENGTH_OFFSET_CANCEL) // G49 - gc_block.values.xyz[TOOL_LENGTH_OFFSET_AXIS] = 0.0; - // else G43.1 - if (gc_state.tool_length_offset != gc_block.values.xyz[TOOL_LENGTH_OFFSET_AXIS]) { - gc_state.tool_length_offset = gc_block.values.xyz[TOOL_LENGTH_OFFSET_AXIS]; - system_flag_wco_change(); - } - } - // [15. Coordinate system selection ]: - if (gc_state.modal.coord_select != gc_block.modal.coord_select) { - gc_state.modal.coord_select = gc_block.modal.coord_select; - memcpy(gc_state.coord_system, block_coord_system, N_AXIS * sizeof(float)); - system_flag_wco_change(); - } - // [16. Set path control mode ]: G61.1/G64 NOT SUPPORTED - // gc_state.modal.control = gc_block.modal.control; // NOTE: Always default. - // [17. Set distance mode ]: - gc_state.modal.distance = gc_block.modal.distance; - // [18. Set retract mode ]: NOT SUPPORTED - // [19. Go to predefined position, Set G10, or Set axis offsets ]: - switch (gc_block.non_modal_command) { - case NON_MODAL_SET_COORDINATE_DATA: - settings_write_coord_data(coord_select, gc_block.values.ijk); - // Update system coordinate system if currently active. - if (gc_state.modal.coord_select == coord_select) { - memcpy(gc_state.coord_system, gc_block.values.ijk, N_AXIS * sizeof(float)); - system_flag_wco_change(); - } - break; - case NON_MODAL_GO_HOME_0: - case NON_MODAL_GO_HOME_1: - // Move to intermediate position before going home. Obeys current coordinate system and offsets - // and absolute and incremental modes. - pl_data->condition |= PL_COND_FLAG_RAPID_MOTION; // Set rapid motion condition flag. - if (axis_command) { - mc_line(gc_block.values.xyz, pl_data); // kinematics kinematics not used for homing righ now - } - mc_line(gc_block.values.ijk, pl_data); - memcpy(gc_state.position, gc_block.values.ijk, N_AXIS * sizeof(float)); - break; - case NON_MODAL_SET_HOME_0: - settings_write_coord_data(SETTING_INDEX_G28, gc_state.position); - break; - case NON_MODAL_SET_HOME_1: - settings_write_coord_data(SETTING_INDEX_G30, gc_state.position); - break; - case NON_MODAL_SET_COORDINATE_OFFSET: - memcpy(gc_state.coord_offset, gc_block.values.xyz, sizeof(gc_block.values.xyz)); - system_flag_wco_change(); - break; - case NON_MODAL_RESET_COORDINATE_OFFSET: - clear_vector(gc_state.coord_offset); // Disable G92 offsets by zeroing offset vector. - system_flag_wco_change(); - break; - } - // [20. Motion modes ]: - // NOTE: Commands G10,G28,G30,G92 lock out and prevent axis words from use in motion modes. - // Enter motion modes only if there are axis words or a motion mode command word in the block. - gc_state.modal.motion = gc_block.modal.motion; - if (gc_state.modal.motion != MOTION_MODE_NONE) { - if (axis_command == AXIS_COMMAND_MOTION_MODE) { - uint8_t gc_update_pos = GC_UPDATE_POS_TARGET; - if (gc_state.modal.motion == MOTION_MODE_LINEAR) { - //mc_line(gc_block.values.xyz, pl_data); - mc_line_kins(gc_block.values.xyz, pl_data, gc_state.position); - } else if (gc_state.modal.motion == MOTION_MODE_SEEK) { - pl_data->condition |= PL_COND_FLAG_RAPID_MOTION; // Set rapid motion condition flag. - //mc_line(gc_block.values.xyz, pl_data); - mc_line_kins(gc_block.values.xyz, pl_data, gc_state.position); - } else if ((gc_state.modal.motion == MOTION_MODE_CW_ARC) || (gc_state.modal.motion == MOTION_MODE_CCW_ARC)) { - mc_arc(gc_block.values.xyz, pl_data, gc_state.position, gc_block.values.ijk, gc_block.values.r, - axis_0, axis_1, axis_linear, bit_istrue(gc_parser_flags, GC_PARSER_ARC_IS_CLOCKWISE)); - } else { - // NOTE: gc_block.values.xyz is returned from mc_probe_cycle with the updated position value. So - // upon a successful probing cycle, the machine position and the returned value should be the same. -#ifndef ALLOW_FEED_OVERRIDE_DURING_PROBE_CYCLES - pl_data->condition |= PL_COND_FLAG_NO_FEED_OVERRIDE; -#endif - gc_update_pos = mc_probe_cycle(gc_block.values.xyz, pl_data, gc_parser_flags); - } - // As far as the parser is concerned, the position is now == target. In reality the - // motion control system might still be processing the action and the real tool position - // in any intermediate location. - if (gc_update_pos == GC_UPDATE_POS_TARGET) { - memcpy(gc_state.position, gc_block.values.xyz, sizeof(gc_block.values.xyz)); // gc_state.position[] = gc_block.values.xyz[] - } else if (gc_update_pos == GC_UPDATE_POS_SYSTEM) { - gc_sync_position(); // gc_state.position[] = sys_position - } // == GC_UPDATE_POS_NONE - } - } - // [21. Program flow ]: - // M0,M1,M2,M30: Perform non-running program flow actions. During a program pause, the buffer may - // refill and can only be resumed by the cycle start run-time command. - gc_state.modal.program_flow = gc_block.modal.program_flow; - if (gc_state.modal.program_flow) { - protocol_buffer_synchronize(); // Sync and finish all remaining buffered motions before moving on. - if (gc_state.modal.program_flow == PROGRAM_FLOW_PAUSED) { - if (sys.state != STATE_CHECK_MODE) { - system_set_exec_state_flag(EXEC_FEED_HOLD); // Use feed hold for program pause. - protocol_execute_realtime(); // Execute suspend. - } - } else { // == PROGRAM_FLOW_COMPLETED - // Upon program complete, only a subset of g-codes reset to certain defaults, according to - // LinuxCNC's program end descriptions and testing. Only modal groups [G-code 1,2,3,5,7,12] - // and [M-code 7,8,9] reset to [G1,G17,G90,G94,G40,G54,M5,M9,M48]. The remaining modal groups - // [G-code 4,6,8,10,13,14,15] and [M-code 4,5,6] and the modal words [F,S,T,H] do not reset. - gc_state.modal.motion = MOTION_MODE_LINEAR; - gc_state.modal.plane_select = PLANE_SELECT_XY; - gc_state.modal.distance = DISTANCE_MODE_ABSOLUTE; - gc_state.modal.feed_rate = FEED_RATE_MODE_UNITS_PER_MIN; - // gc_state.modal.cutter_comp = CUTTER_COMP_DISABLE; // Not supported. - gc_state.modal.coord_select = 0; // G54 - gc_state.modal.spindle = SPINDLE_DISABLE; - gc_state.modal.coolant = COOLANT_DISABLE; -#ifdef ENABLE_PARKING_OVERRIDE_CONTROL -#ifdef DEACTIVATE_PARKING_UPON_INIT - gc_state.modal.override = OVERRIDE_DISABLED; -#else - gc_state.modal.override = OVERRIDE_PARKING_MOTION; -#endif -#endif - // gc_state.modal.override = OVERRIDE_DISABLE; // Not supported. -#ifdef RESTORE_OVERRIDES_AFTER_PROGRAM_END - sys.f_override = DEFAULT_FEED_OVERRIDE; - sys.r_override = DEFAULT_RAPID_OVERRIDE; - sys.spindle_speed_ovr = DEFAULT_SPINDLE_SPEED_OVERRIDE; -#endif - // Execute coordinate change and spindle/coolant stop. - if (sys.state != STATE_CHECK_MODE) { - if (!(settings_read_coord_data(gc_state.modal.coord_select, gc_state.coord_system))) - FAIL(STATUS_SETTING_READ_FAIL); - system_flag_wco_change(); // Set to refresh immediately just in case something altered. - spindle->set_state(SPINDLE_DISABLE, 0); - coolant_set_state(COOLANT_DISABLE); - } - report_feedback_message(MESSAGE_PROGRAM_END); -#ifdef USE_M30 - user_m30(); -#endif - } - gc_state.modal.program_flow = PROGRAM_FLOW_RUNNING; // Reset program flow. - } - // TODO: % to denote start of program. - return (STATUS_OK); -} - - -/* - Not supported: - - - Canned cycles - - Tool radius compensation - - A,B,C-axes - - Evaluation of expressions - - Variables - - Override control (TBD) - - Tool changes - - Switches - - (*) Indicates optional parameter, enabled through config.h and re-compile - group 0 = {G92.2, G92.3} (Non modal: Cancel and re-enable G92 offsets) - group 1 = {G81 - G89} (Motion modes: Canned cycles) - group 4 = {M1} (Optional stop, ignored) - group 6 = {M6} (Tool change) - group 7 = {G41, G42} cutter radius compensation (G40 is supported) - group 8 = {G43} tool length offset (G43.1/G49 are supported) - group 8 = {M7*} enable mist coolant (* Compile-option) - group 9 = {M48, M49} enable/disable feed and speed override switches - group 10 = {G98, G99} return mode canned cycles - group 13 = {G61.1, G64} path control mode (G61 is supported) -*/ diff --git a/Grbl_Esp32/gcode.h b/Grbl_Esp32/gcode.h deleted file mode 100644 index 2415155a..00000000 --- a/Grbl_Esp32/gcode.h +++ /dev/null @@ -1,268 +0,0 @@ -/* - gcode.h - rs274/ngc parser. - Part of Grbl - - Copyright (c) 2011-2015 Sungeun K. Jeon - Copyright (c) 2009-2011 Simen Svale Skogsrud - - 2018 - Bart Dring This file was modifed 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 . -*/ -#ifndef gcode_h -#define gcode_h - - -// Define modal group internal numbers for checking multiple command violations and tracking the -// type of command that is called in the block. A modal group is a group of g-code commands that are -// mutually exclusive, or cannot exist on the same line, because they each toggle a state or execute -// a unique motion. These are defined in the NIST RS274-NGC v3 g-code standard, available online, -// and are similar/identical to other g-code interpreters by manufacturers (Haas,Fanuc,Mazak,etc). -// NOTE: Modal group define values must be sequential and starting from zero. -#define MODAL_GROUP_G0 0 // [G4,G10,G28,G28.1,G30,G30.1,G53,G92,G92.1] Non-modal -#define MODAL_GROUP_G1 1 // [G0,G1,G2,G3,G38.2,G38.3,G38.4,G38.5,G80] Motion -#define MODAL_GROUP_G2 2 // [G17,G18,G19] Plane selection -#define MODAL_GROUP_G3 3 // [G90,G91] Distance mode -#define MODAL_GROUP_G4 4 // [G91.1] Arc IJK distance mode -#define MODAL_GROUP_G5 5 // [G93,G94] Feed rate mode -#define MODAL_GROUP_G6 6 // [G20,G21] Units -#define MODAL_GROUP_G7 7 // [G40] Cutter radius compensation mode. G41/42 NOT SUPPORTED. -#define MODAL_GROUP_G8 8 // [G43.1,G49] Tool length offset -#define MODAL_GROUP_G12 9 // [G54,G55,G56,G57,G58,G59] Coordinate system selection -#define MODAL_GROUP_G13 10 // [G61] Control mode - -#define MODAL_GROUP_M4 11 // [M0,M1,M2,M30] Stopping -#define MODAL_GROUP_M6 14 // [M6] Tool change -#define MODAL_GROUP_M7 12 // [M3,M4,M5] Spindle turning -#define MODAL_GROUP_M8 13 // [M7,M8,M9] Coolant control -#define MODAL_GROUP_M9 14 // [M56] Override control -#define MODAL_GROUP_M10 15 // [M62, M63] User Defined http://linuxcnc.org/docs/html/gcode/overview.html#_modal_groups - -// #define OTHER_INPUT_F 14 -// #define OTHER_INPUT_S 15 -// #define OTHER_INPUT_T 16 - -// Define command actions for within execution-type modal groups (motion, stopping, non-modal). Used -// internally by the parser to know which command to execute. -// NOTE: Some macro values are assigned specific values to make g-code state reporting and parsing -// compile a litte smaller. Necessary due to being completely out of flash on the 328p. Although not -// ideal, just be careful with values that state 'do not alter' and check both report.c and gcode.c -// to see how they are used, if you need to alter them. - -// Modal Group G0: Non-modal actions -#define NON_MODAL_NO_ACTION 0 // (Default: Must be zero) -#define NON_MODAL_DWELL 4 // G4 (Do not alter value) -#define NON_MODAL_SET_COORDINATE_DATA 10 // G10 (Do not alter value) -#define NON_MODAL_GO_HOME_0 28 // G28 (Do not alter value) -#define NON_MODAL_SET_HOME_0 38 // G28.1 (Do not alter value) -#define NON_MODAL_GO_HOME_1 30 // G30 (Do not alter value) -#define NON_MODAL_SET_HOME_1 40 // G30.1 (Do not alter value) -#define NON_MODAL_ABSOLUTE_OVERRIDE 53 // G53 (Do not alter value) -#define NON_MODAL_SET_COORDINATE_OFFSET 92 // G92 (Do not alter value) -#define NON_MODAL_RESET_COORDINATE_OFFSET 102 //G92.1 (Do not alter value) - -// Modal Group G1: Motion modes -#define MOTION_MODE_SEEK 0 // G0 (Default: Must be zero) -#define MOTION_MODE_LINEAR 1 // G1 (Do not alter value) -#define MOTION_MODE_CW_ARC 2 // G2 (Do not alter value) -#define MOTION_MODE_CCW_ARC 3 // G3 (Do not alter value) -#define MOTION_MODE_PROBE_TOWARD 140 // G38.2 (Do not alter value) -#define MOTION_MODE_PROBE_TOWARD_NO_ERROR 141 // G38.3 (Do not alter value) -#define MOTION_MODE_PROBE_AWAY 142 // G38.4 (Do not alter value) -#define MOTION_MODE_PROBE_AWAY_NO_ERROR 143 // G38.5 (Do not alter value) -#define MOTION_MODE_NONE 80 // G80 (Do not alter value) - -// Modal Group G2: Plane select -#define PLANE_SELECT_XY 0 // G17 (Default: Must be zero) -#define PLANE_SELECT_ZX 1 // G18 (Do not alter value) -#define PLANE_SELECT_YZ 2 // G19 (Do not alter value) - -// Modal Group G3: Distance mode -#define DISTANCE_MODE_ABSOLUTE 0 // G90 (Default: Must be zero) -#define DISTANCE_MODE_INCREMENTAL 1 // G91 (Do not alter value) - -// Modal Group G4: Arc IJK distance mode -#define DISTANCE_ARC_MODE_INCREMENTAL 0 // G91.1 (Default: Must be zero) - -// Modal Group M4: Program flow -#define PROGRAM_FLOW_RUNNING 0 // (Default: Must be zero) -#define PROGRAM_FLOW_PAUSED 3 // M0 -#define PROGRAM_FLOW_OPTIONAL_STOP 1 // M1 NOTE: Not supported, but valid and ignored. -#define PROGRAM_FLOW_COMPLETED_M2 2 // M2 (Do not alter value) -#define PROGRAM_FLOW_COMPLETED_M30 30 // M30 (Do not alter value) - -// Modal Group G5: Feed rate mode -#define FEED_RATE_MODE_UNITS_PER_MIN 0 // G94 (Default: Must be zero) -#define FEED_RATE_MODE_INVERSE_TIME 1 // G93 (Do not alter value) - -// Modal Group G6: Units mode -#define UNITS_MODE_MM 0 // G21 (Default: Must be zero) -#define UNITS_MODE_INCHES 1 // G20 (Do not alter value) - -// Modal Group G7: Cutter radius compensation mode -#define CUTTER_COMP_DISABLE 0 // G40 (Default: Must be zero) - -// Modal Group G13: Control mode -#define CONTROL_MODE_EXACT_PATH 0 // G61 (Default: Must be zero) - -// Modal Group M7: Spindle control -#define SPINDLE_DISABLE 0 // M5 (Default: Must be zero) -#define SPINDLE_ENABLE_CW PL_COND_FLAG_SPINDLE_CW // M3 (NOTE: Uses planner condition bit flag) -#define SPINDLE_ENABLE_CCW PL_COND_FLAG_SPINDLE_CCW // M4 (NOTE: Uses planner condition bit flag) - -// Modal Group M8: Coolant control -#define COOLANT_DISABLE 0 // M9 (Default: Must be zero) -#define COOLANT_FLOOD_ENABLE PL_COND_FLAG_COOLANT_FLOOD // M8 (NOTE: Uses planner condition bit flag) -#define COOLANT_MIST_ENABLE PL_COND_FLAG_COOLANT_MIST // M7 (NOTE: Uses planner condition bit flag) - -// Modal Group M9: Override control -#ifdef DEACTIVATE_PARKING_UPON_INIT - #define OVERRIDE_DISABLED 0 // (Default: Must be zero) - #define OVERRIDE_PARKING_MOTION 1 // M56 -#else - #define OVERRIDE_PARKING_MOTION 0 // M56 (Default: Must be zero) - #define OVERRIDE_DISABLED 1 // Parking disabled. -#endif - -// modal Group M10: User I/O control -#define NON_MODAL_IO_ENABLE 1 -#define NON_MODAL_IO_DISABLE 2 -#define MAX_USER_DIGITAL_PIN 4 - -// Modal Group G8: Tool length offset -#define TOOL_LENGTH_OFFSET_CANCEL 0 // G49 (Default: Must be zero) -#define TOOL_LENGTH_OFFSET_ENABLE_DYNAMIC 1 // G43.1 - -#define TOOL_CHANGE 1 - -// Modal Group G12: Active work coordinate system -// N/A: Stores coordinate system value (54-59) to change to. - -// Define parameter word mapping. -#define WORD_F 0 -#define WORD_I 1 -#define WORD_J 2 -#define WORD_K 3 -#define WORD_L 4 -#define WORD_N 5 -#define WORD_P 6 -#define WORD_R 7 -#define WORD_S 8 -#define WORD_T 9 -#define WORD_X 10 -#define WORD_Y 11 -#define WORD_Z 12 -#define WORD_A 13 -#define WORD_B 14 -#define WORD_C 15 - -// Define g-code parser position updating flags -#define GC_UPDATE_POS_TARGET 0 // Must be zero -#define GC_UPDATE_POS_SYSTEM 1 -#define GC_UPDATE_POS_NONE 2 - -// Define probe cycle exit states and assign proper position updating. -#define GC_PROBE_FOUND GC_UPDATE_POS_SYSTEM -#define GC_PROBE_ABORT GC_UPDATE_POS_NONE -#define GC_PROBE_FAIL_INIT GC_UPDATE_POS_NONE -#define GC_PROBE_FAIL_END GC_UPDATE_POS_TARGET -#ifdef SET_CHECK_MODE_PROBE_TO_START - #define GC_PROBE_CHECK_MODE GC_UPDATE_POS_NONE -#else - #define GC_PROBE_CHECK_MODE GC_UPDATE_POS_TARGET -#endif - -// Define gcode parser flags for handling special cases. -#define GC_PARSER_NONE 0 // Must be zero. -#define GC_PARSER_JOG_MOTION bit(0) -#define GC_PARSER_CHECK_MANTISSA bit(1) -#define GC_PARSER_ARC_IS_CLOCKWISE bit(2) -#define GC_PARSER_PROBE_IS_AWAY bit(3) -#define GC_PARSER_PROBE_IS_NO_ERROR bit(4) -#define GC_PARSER_LASER_FORCE_SYNC bit(5) -#define GC_PARSER_LASER_DISABLE bit(6) -#define GC_PARSER_LASER_ISMOTION bit(7) - - -// NOTE: When this struct is zeroed, the above defines set the defaults for the system. -typedef struct { - uint8_t motion; // {G0,G1,G2,G3,G38.2,G80} - uint8_t feed_rate; // {G93,G94} - uint8_t units; // {G20,G21} - uint8_t distance; // {G90,G91} - // uint8_t distance_arc; // {G91.1} NOTE: Don't track. Only default supported. - uint8_t plane_select; // {G17,G18,G19} - // uint8_t cutter_comp; // {G40} NOTE: Don't track. Only default supported. - uint8_t tool_length; // {G43.1,G49} - uint8_t coord_select; // {G54,G55,G56,G57,G58,G59} - // uint8_t control; // {G61} NOTE: Don't track. Only default supported. - uint8_t program_flow; // {M0,M1,M2,M30} - uint8_t coolant; // {M7,M8,M9} - uint8_t spindle; // {M3,M4,M5} - uint8_t tool_change; // {M6} - uint8_t io_control; // {M62, M63} - uint8_t override; // {M56} -} gc_modal_t; - -typedef struct { - float f; // Feed - float ijk[N_AXIS]; // I,J,K Axis arc offsets - uint8_t l; // G10 or canned cycles parameters - int32_t n; // Line number - float p; // G10 or dwell parameters - // float q; // G82 peck drilling - float r; // Arc radius - float s; // Spindle speed - uint8_t t; // Tool selection - float xyz[N_AXIS]; // X,Y,Z Translational axes -} gc_values_t; - - -typedef struct { - gc_modal_t modal; - - float spindle_speed; // RPM - float feed_rate; // Millimeters/min - uint8_t tool; // Tracks tool number. NOT USED. - int32_t line_number; // Last line number sent - - float position[N_AXIS]; // Where the interpreter considers the tool to be at this point in the code - - float coord_system[N_AXIS]; // Current work coordinate system (G54+). Stores offset from absolute machine - // position in mm. Loaded from EEPROM when called. - float coord_offset[N_AXIS]; // Retains the G92 coordinate offset (work coordinates) relative to - // machine zero in mm. Non-persistent. Cleared upon reset and boot. - float tool_length_offset; // Tracks tool length offset value when enabled. -} parser_state_t; -extern parser_state_t gc_state; - - -typedef struct { - uint8_t non_modal_command; - gc_modal_t modal; - gc_values_t values; -} parser_block_t; - - -// Initialize the parser -void gc_init(); - -// Execute one block of rs275/ngc/g-code -uint8_t gc_execute_line(char* line, uint8_t client); - -// Set g-code parser position. Input in steps. -void gc_sync_position(); - -#endif diff --git a/Grbl_Esp32/grbl_sd.h b/Grbl_Esp32/grbl_sd.h deleted file mode 100644 index 20affa0d..00000000 --- a/Grbl_Esp32/grbl_sd.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Connect the SD card to the following pins: - * - * SD Card | ESP32 - * D2 - - * D3 SS - * CMD MOSI - * VSS GND - * VDD 3.3V - * CLK SCK - * VSS GND - * D0 MISO - * D1 - - */ - -#ifndef grbl_sd_h -#define grbl_sd_h - - -#include "grbl.h" -#include "FS.h" -#include "SD.h" -#include "SPI.h" - -#define FILE_TYPE_COUNT 5 // number of acceptable gcode file types in array - -#define SDCARD_DET_PIN -1 -#define SDCARD_DET_VAL 0 - -#define SDCARD_IDLE 0 -#define SDCARD_NOT_PRESENT 1 -#define SDCARD_BUSY_PRINTING 2 -#define SDCARD_BUSY_UPLOADING 4 -#define SDCARD_BUSY_PARSING 8 - - - -extern bool SD_ready_next; // Grbl has processed a line and is waiting for another -extern uint8_t SD_client; - -//bool sd_mount(); -uint8_t get_sd_state(bool refresh); -uint8_t set_sd_state(uint8_t flag); -void listDir(fs::FS& fs, const char* dirname, uint8_t levels, uint8_t client); -boolean openFile(fs::FS& fs, const char* path); -boolean closeFile(); -boolean readFileLine(char* line, int len); -void readFile(fs::FS& fs, const char* path); -float sd_report_perc_complete(); -uint32_t sd_get_current_line_number(); -void sd_get_current_filename(char* name); - -#endif diff --git a/Grbl_Esp32/i2s_out.cpp b/Grbl_Esp32/i2s_out.cpp deleted file mode 100644 index 11d88d07..00000000 --- a/Grbl_Esp32/i2s_out.cpp +++ /dev/null @@ -1,907 +0,0 @@ -/* - i2s_out.cpp - Part of Grbl_ESP32 - - Basic GPIO expander using the ESP32 I2S peripheral (output) - - 2020 - Michiyasu Odaki - - Grbl_ESP32 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_ESP32. If not, see . -*/ -/** - * Marlin 3D Printer Firmware - * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * - * Based on Sprinter and grbl. - * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ -#include "config.h" - -#ifdef USE_I2S_OUT - -#include -#include -#include -#include -#include - -#include - -#include "Pins.h" -#include "i2s_out.h" - -// -// Configrations for DMA connected I2S -// -// One DMA buffer transfer takes about 2 ms -// I2S_OUT_DMABUF_LEN / I2S_SAMPLE_SIZE x I2S_OUT_USEC_PER_PULSE -// = 2000 / 4 x 4 -// = 2000us = 2ms -// If I2S_OUT_DMABUF_COUNT is 5, it will take about 10 ms for all the DMA buffer transfers to finish. -// -// Increasing I2S_OUT_DMABUF_COUNT has the effect of preventing buffer underflow, -// but on the other hand, it leads to a delay with pulse and/or non-pulse-generated I/Os. -// The number of I2S_OUT_DMABUF_COUNT should be chosen carefully. -// -// Reference information: -// FreeRTOS task time slice = portTICK_PERIOD_MS = 1 ms (ESP32 FreeRTOS port) -// -#define I2S_SAMPLE_SIZE 4 /* 4 bytes, 32 bits per sample */ -#define DMA_SAMPLE_COUNT I2S_OUT_DMABUF_LEN / I2S_SAMPLE_SIZE /* number of samples per buffer */ -#define SAMPLE_SAFE_COUNT (20/I2S_OUT_USEC_PER_PULSE) /* prevent buffer overrun (GRBL's $0 should be less than or equal 20) */ - -#ifdef USE_I2S_OUT_STREAM -typedef struct { - uint32_t **buffers; - uint32_t *current; - uint32_t rw_pos; - lldesc_t **desc; - xQueueHandle queue; -} i2s_out_dma_t; - -static i2s_out_dma_t o_dma; -static intr_handle_t i2s_out_isr_handle; -#endif - -// output value -static atomic_uint_least32_t i2s_out_port_data = ATOMIC_VAR_INIT(0); - -// inner lock -static portMUX_TYPE i2s_out_spinlock = portMUX_INITIALIZER_UNLOCKED; -#define I2S_OUT_ENTER_CRITICAL() portENTER_CRITICAL(&i2s_out_spinlock) -#define I2S_OUT_EXIT_CRITICAL() portEXIT_CRITICAL(&i2s_out_spinlock) -#define I2S_OUT_ENTER_CRITICAL_ISR() portENTER_CRITICAL_ISR(&i2s_out_spinlock) -#define I2S_OUT_EXIT_CRITICAL_ISR() portEXIT_CRITICAL_ISR(&i2s_out_spinlock) - -static int i2s_out_initialized = 0; - -#ifdef USE_I2S_OUT_STREAM -static volatile uint32_t i2s_out_pulse_period; -static uint32_t i2s_out_remain_time_until_next_pulse; // Time remaining until the next pulse (μsec) -static volatile i2s_out_pulse_func_t i2s_out_pulse_func; -#endif - -static uint8_t i2s_out_ws_pin = 255; -static uint8_t i2s_out_bck_pin = 255; -static uint8_t i2s_out_data_pin = 255; - -enum i2s_out_pulser_status_t { - PASSTHROUGH = 0, // Static I2S mode.The i2s_out_write() reflected with very little delay - STEPPING, // Streaming step data. - WAITING, // Waiting for the step DMA completion -}; -static volatile i2s_out_pulser_status_t i2s_out_pulser_status = PASSTHROUGH; - -// outer lock -static portMUX_TYPE i2s_out_pulser_spinlock = portMUX_INITIALIZER_UNLOCKED; -#define I2S_OUT_PULSER_ENTER_CRITICAL() portENTER_CRITICAL(&i2s_out_pulser_spinlock) -#define I2S_OUT_PULSER_EXIT_CRITICAL() portEXIT_CRITICAL(&i2s_out_pulser_spinlock) -#define I2S_OUT_PULSER_ENTER_CRITICAL_ISR() portENTER_CRITICAL_ISR(&i2s_out_pulser_spinlock) -#define I2S_OUT_PULSER_EXIT_CRITICAL_ISR() portEXIT_CRITICAL_ISR(&i2s_out_pulser_spinlock) - -// -// Internal functions -// -static inline void gpio_matrix_out_check(uint8_t gpio, uint32_t signal_idx, bool out_inv, bool oen_inv) { - //if pin == 255, do not need to configure - if (gpio != 255) { - PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[gpio], PIN_FUNC_GPIO); - gpio_set_direction((gpio_num_t)gpio, (gpio_mode_t)GPIO_MODE_DEF_OUTPUT); - gpio_matrix_out(gpio, signal_idx, out_inv, oen_inv); - } -} - -static inline void i2s_out_single_data() { -#if I2S_OUT_NUM_BITS == 16 - uint32_t port_data = atomic_load(&i2s_out_port_data); - port_data <<= 16; // Shift needed. This specification is not spelled out in the manual. - I2S0.conf_single_data = port_data; // Apply port data in real-time (static I2S) -#else - I2S0.conf_single_data = atomic_load(&i2s_out_port_data); // Apply port data in real-time (static I2S) -#endif -} - -static inline void i2s_out_reset_fifo_without_lock() { - I2S0.conf.rx_fifo_reset = 1; - I2S0.conf.rx_fifo_reset = 0; - I2S0.conf.tx_fifo_reset = 1; - I2S0.conf.tx_fifo_reset = 0; -} - -static void IRAM_ATTR i2s_out_reset_fifo() { - I2S_OUT_ENTER_CRITICAL(); - i2s_out_reset_fifo_without_lock(); - I2S_OUT_EXIT_CRITICAL(); -} - -#ifdef USE_I2S_OUT_STREAM -static int IRAM_ATTR i2s_clear_dma_buffer(lldesc_t *dma_desc, uint32_t port_data) { - - uint32_t *buf = (uint32_t*)dma_desc->buf; - for (int i = 0; i < DMA_SAMPLE_COUNT; i++) { - buf[i] = port_data; - } - // Restore the buffer length. - // The length may have been changed short when the data was filled in to prevent buffer overrun. - dma_desc->length = I2S_OUT_DMABUF_LEN; - return 0; -} - -static int IRAM_ATTR i2s_clear_o_dma_buffers(uint32_t port_data) { - for (int buf_idx = 0; buf_idx < I2S_OUT_DMABUF_COUNT; buf_idx++) { - // Initialize DMA descriptor - o_dma.desc[buf_idx]->owner = 1; - o_dma.desc[buf_idx]->eof = 1; // set to 1 will trigger the interrupt - o_dma.desc[buf_idx]->sosf = 0; - o_dma.desc[buf_idx]->length = I2S_OUT_DMABUF_LEN; - o_dma.desc[buf_idx]->size = I2S_OUT_DMABUF_LEN; - o_dma.desc[buf_idx]->buf = (uint8_t *) o_dma.buffers[buf_idx]; - o_dma.desc[buf_idx]->offset = 0; - o_dma.desc[buf_idx]->qe.stqe_next = (lldesc_t *)((buf_idx < (I2S_OUT_DMABUF_COUNT - 1)) ? (o_dma.desc[buf_idx + 1]) : o_dma.desc[0]); - i2s_clear_dma_buffer(o_dma.desc[buf_idx], port_data); - } - return 0; -} -#endif - -static int IRAM_ATTR i2s_out_gpio_attach(uint8_t ws, uint8_t bck, uint8_t data) { - // Route the i2s pins to the appropriate GPIO - gpio_matrix_out_check(data, I2S0O_DATA_OUT23_IDX, 0, 0); - gpio_matrix_out_check(bck, I2S0O_BCK_OUT_IDX, 0, 0); - gpio_matrix_out_check(ws, I2S0O_WS_OUT_IDX, 0, 0); - return 0; -} - -#define I2S_OUT_DETACH_PORT_IDX 0x100 - -static int IRAM_ATTR i2s_out_gpio_detach(uint8_t ws, uint8_t bck, uint8_t data) { - // Route the i2s pins to the appropriate GPIO - gpio_matrix_out_check(ws, I2S_OUT_DETACH_PORT_IDX, 0, 0); - gpio_matrix_out_check(bck, I2S_OUT_DETACH_PORT_IDX, 0, 0); - gpio_matrix_out_check(data, I2S_OUT_DETACH_PORT_IDX, 0, 0); - return 0; -} - -static int IRAM_ATTR i2s_out_gpio_shiftout(uint32_t port_data) { - __digitalWrite(i2s_out_ws_pin, LOW); - for (int i = 0; i buf; - o_dma.rw_pos = 0; - // It reuses the oldest (just transferred) buffer with the name "current" - // and fills the buffer for later DMA. - I2S_OUT_PULSER_ENTER_CRITICAL(); // Lock pulser status - if (i2s_out_pulser_status == STEPPING) { - // - // Fillout the buffer for pulse - // - // To avoid buffer overflow, all of the maximum pulse width (normaly about 10us) - // is adjusted to be in a single buffer. - // DMA_SAMPLE_SAFE_COUNT is referred to as the margin value. - // Therefore, if a buffer is close to full and it is time to generate a pulse, - // the generation of the buffer is interrupted (the buffer length is shortened slightly) - // and the pulse generation is postponed until the next buffer is filled. - // - o_dma.rw_pos = 0; - while (o_dma.rw_pos < (DMA_SAMPLE_COUNT - SAMPLE_SAFE_COUNT)) { - // no data to read (buffer empty) - if (i2s_out_remain_time_until_next_pulse < I2S_OUT_USEC_PER_PULSE) { - // pulser status may change in pulse phase func, so I need to check it every time. - if (i2s_out_pulser_status == STEPPING) { - // fillout future DMA buffer (tail of the DMA buffer chains) - if (i2s_out_pulse_func != NULL) { - I2S_OUT_PULSER_EXIT_CRITICAL(); // Temporarily unlocked status lock as it may be locked in pulse callback. - (*i2s_out_pulse_func)(); // should be pushed into buffer max DMA_SAMPLE_SAFE_COUNT - I2S_OUT_PULSER_ENTER_CRITICAL(); // Lock again. - i2s_out_remain_time_until_next_pulse = i2s_out_pulse_period; - if (i2s_out_pulser_status == WAITING) { - // i2s_out_set_passthrough() has called from the pulse function. - // It needs to go into pass-through mode. - // This DMA descriptor must be a tail of the chain. - dma_desc->qe.stqe_next = NULL; // Cut the DMA descriptor ring. This allow us to identify the tail of the buffer. - } else if (i2s_out_pulser_status == PASSTHROUGH) { - // i2s_out_reset() has called during the execution of the pulse function. - // I2S has already in static mode, and buffers has cleared to zero. - // To prevent the pulse function from being called back, - // we assume that the buffer is already full. - i2s_out_remain_time_until_next_pulse = 0; // There is no need to fill the current buffer. - o_dma.rw_pos = DMA_SAMPLE_COUNT; // The buffer is full. - break; - } - continue; - } - } - } - // no pulse data in push buffer (pulse off or idle or callback is not defined) - buf[o_dma.rw_pos++] = atomic_load(&i2s_out_port_data); - if (i2s_out_remain_time_until_next_pulse >= I2S_OUT_USEC_PER_PULSE) { - i2s_out_remain_time_until_next_pulse -= I2S_OUT_USEC_PER_PULSE; - } else { - i2s_out_remain_time_until_next_pulse = 0; - } - } - // set filled length to the DMA descriptor - dma_desc->length = o_dma.rw_pos * I2S_SAMPLE_SIZE; - } else if (i2s_out_pulser_status == WAITING) { - i2s_clear_dma_buffer(dma_desc, 0); // Essentially, no clearing is required. I'll make sure I know when I've written something. - o_dma.rw_pos = 0; // If someone calls i2s_out_push_sample, make sure there is no buffer overflow - dma_desc->qe.stqe_next = NULL; // Cut the DMA descriptor ring. This allow us to identify the tail of the buffer. - } else { - // Stepper paused (passthrough state, static I2S control mode) - // In the passthrough mode, there is no need to fill the buffer with port_data. - i2s_clear_dma_buffer(dma_desc, 0); // Essentially, no clearing is required. I'll make sure I know when I've written something. - o_dma.rw_pos = 0; // If someone calls i2s_out_push_sample, make sure there is no buffer overflow - } - I2S_OUT_PULSER_EXIT_CRITICAL(); // Unlock pulser status - - return 0; -} - -// -// I2S out DMA Interrupts handler -// -static void IRAM_ATTR i2s_out_intr_handler(void *arg) { - lldesc_t *finish_desc; - portBASE_TYPE high_priority_task_awoken = pdFALSE; - - if (I2S0.int_st.out_eof || I2S0.int_st.out_total_eof) { - if (I2S0.int_st.out_total_eof) { - // This is tail of the DMA descriptors - I2S_OUT_ENTER_CRITICAL_ISR(); - // Stop FIFO DMA - I2S0.out_link.stop = 1; - // Disconnect DMA from FIFO - I2S0.fifo_conf.dscr_en = 0; //Unset this bit to disable I2S DMA mode. (R/W) - // Stop TX module - I2S0.conf.tx_start = 0; - I2S_OUT_EXIT_CRITICAL_ISR(); - } - // Get the descriptor of the last item in the linkedlist - finish_desc = (lldesc_t*) I2S0.out_eof_des_addr; - - // If the queue is full it's because we have an underflow, - // more than buf_count isr without new data, remove the front buffer - if (xQueueIsQueueFullFromISR(o_dma.queue)) { - lldesc_t *front_desc; - // Remove a descriptor from the DMA complete event queue - xQueueReceiveFromISR(o_dma.queue, &front_desc, &high_priority_task_awoken); - I2S_OUT_PULSER_ENTER_CRITICAL_ISR(); - uint32_t port_data = 0; - if (i2s_out_pulser_status == STEPPING) { - port_data = atomic_load(&i2s_out_port_data); - } - I2S_OUT_PULSER_EXIT_CRITICAL_ISR(); - for (int i = 0; i < DMA_SAMPLE_COUNT; i++) { - front_desc->buf[i] = port_data; - } - front_desc->length = I2S_OUT_DMABUF_LEN; - } - - // Send a DMA complete event to the I2S bitstreamer task with finished buffer - xQueueSendFromISR(o_dma.queue, &finish_desc, &high_priority_task_awoken); - } - - if (high_priority_task_awoken == pdTRUE) portYIELD_FROM_ISR(); - - // clear interrupt - I2S0.int_clr.val = I2S0.int_st.val; //clear pending interrupt -} - -// -// I2S bitstream generator task -// -static void IRAM_ATTR i2sOutTask(void* parameter) { - lldesc_t *dma_desc; - while (1) { - // Wait a DMA complete event from I2S isr - // (Block until a DMA transfer has complete) - xQueueReceive(o_dma.queue, &dma_desc, portMAX_DELAY); - o_dma.current = (uint32_t*)(dma_desc->buf); - // It reuses the oldest (just transferred) buffer with the name "current" - // and fills the buffer for later DMA. - I2S_OUT_PULSER_ENTER_CRITICAL(); // Lock pulser status - if (i2s_out_pulser_status == STEPPING) { - // - // Fillout the buffer for pulse - // - // To avoid buffer overflow, all of the maximum pulse width (normaly about 10us) - // is adjusted to be in a single buffer. - // DMA_SAMPLE_SAFE_COUNT is referred to as the margin value. - // Therefore, if a buffer is close to full and it is time to generate a pulse, - // the generation of the buffer is interrupted (the buffer length is shortened slightly) - // and the pulse generation is postponed until the next buffer is filled. - // - i2s_fillout_dma_buffer(dma_desc); - dma_desc->length = o_dma.rw_pos * I2S_SAMPLE_SIZE; - } else if (i2s_out_pulser_status == WAITING) { - if (dma_desc->qe.stqe_next == NULL) { - // Tail of the DMA descriptor found - // I2S TX module has alrewdy stopped by ISR - i2s_out_stop(); - i2s_clear_o_dma_buffers(0); // 0 for static I2S control mode (right ch. data is always 0) - // You need to set the status before calling i2s_out_start() - // because the process in i2s_out_start() is different depending on the status. - i2s_out_pulser_status = PASSTHROUGH; - i2s_out_start(); - } else { - // Processing a buffer slightly ahead of the tail buffer. - // We don't need to fill up the buffer by port_data any more. - i2s_clear_dma_buffer(dma_desc, 0); // Essentially, no clearing is required. I'll make sure I know when I've written something. - o_dma.rw_pos = 0; // If someone calls i2s_out_push_sample, make sure there is no buffer overflow - dma_desc->qe.stqe_next = NULL; // Cut the DMA descriptor ring. This allow us to identify the tail of the buffer. - } - } else { - // Stepper paused (passthrough state, static I2S control mode) - // In the passthrough mode, there is no need to fill the buffer with port_data. - i2s_clear_dma_buffer(dma_desc, 0); // Essentially, no clearing is required. I'll make sure I know when I've written something. - o_dma.rw_pos = 0; // If someone calls i2s_out_push_sample, make sure there is no buffer overflow - } - I2S_OUT_PULSER_EXIT_CRITICAL(); // Unlock pulser status - } -} -#endif - -// -// External funtions -// -void IRAM_ATTR i2s_out_delay() { -#ifdef USE_I2S_OUT_STREAM - I2S_OUT_PULSER_ENTER_CRITICAL(); - if (i2s_out_pulser_status == PASSTHROUGH) { - // Depending on the timing, it may not be reflected immediately, - // so wait twice as long just in case. - ets_delay_us(I2S_OUT_USEC_PER_PULSE * 2); - } else { - // Just wait until the data now registered in the DMA descripter - // is reflected in the I2S TX module via FIFO. - delay(I2S_OUT_DELAY_MS); - } - I2S_OUT_PULSER_EXIT_CRITICAL(); -#else - ets_delay_us(I2S_OUT_USEC_PER_PULSE * 2); -#endif -} - -void IRAM_ATTR i2s_out_write(uint8_t pin, uint8_t val) { - uint32_t bit = bit(pin); - if (val) { - atomic_fetch_or(&i2s_out_port_data, bit); - } else { - atomic_fetch_and(&i2s_out_port_data, ~bit); - } -#ifdef USE_I2S_OUT_STREAM - // It needs a lock for access, but I've given up because I need speed. - // This is not a problem as long as there is no overlap between the status change and digitalWrite(). - if (i2s_out_pulser_status == PASSTHROUGH) { - i2s_out_single_data(); - } -#else - i2s_out_single_data(); -#endif -} - -uint8_t IRAM_ATTR i2s_out_state(uint8_t pin) { - uint32_t port_data = atomic_load(&i2s_out_port_data); - return (!!(port_data & bit(pin))); -} - -uint32_t IRAM_ATTR i2s_out_push_sample(uint32_t num) { -#ifdef USE_I2S_OUT_STREAM - if (num > SAMPLE_SAFE_COUNT) { - return 0; - } - // push at least one sample (even if num is zero) - uint32_t port_data = atomic_load(&i2s_out_port_data); - uint32_t n = 0; - do { - o_dma.current[o_dma.rw_pos++] = port_data; - n++; - } while(n < num); - return n; -#else - return 0; -#endif -} - -int IRAM_ATTR i2s_out_set_passthrough() { - I2S_OUT_PULSER_ENTER_CRITICAL(); -#ifdef USE_I2S_OUT_STREAM - if (i2s_out_pulser_status == STEPPING) { - i2s_out_pulser_status = WAITING; // Start stopping the pulser - } -#else - i2s_out_pulser_status = PASSTHROUGH; -#endif - I2S_OUT_PULSER_EXIT_CRITICAL(); - return 0; -} - -int IRAM_ATTR i2s_out_set_stepping() { - I2S_OUT_PULSER_ENTER_CRITICAL(); -#ifdef USE_I2S_OUT_STREAM - if (i2s_out_pulser_status == STEPPING) { - // Re-entered (fail safe) - I2S_OUT_PULSER_EXIT_CRITICAL(); - return 0; - } - - if (i2s_out_pulser_status == WAITING) { - // Wait for complete DMAs - for(;;) { - I2S_OUT_PULSER_EXIT_CRITICAL(); - delay(I2S_OUT_DELAY_DMABUF_MS); - I2S_OUT_PULSER_ENTER_CRITICAL(); - if (i2s_out_pulser_status == WAITING) { - continue; - } - if (i2s_out_pulser_status == PASSTHROUGH) { - // DMA completed - break; - } - // Another function change the I2S state to STEPPING - I2S_OUT_PULSER_EXIT_CRITICAL(); - return 0; - } - } - - // Change I2S state from PASSTHROUGH to STEPPING - i2s_out_stop(); - uint32_t port_data = atomic_load(&i2s_out_port_data); - i2s_clear_o_dma_buffers(port_data); - - // You need to set the status before calling i2s_out_start() - // because the process in i2s_out_start() is different depending on the status. - i2s_out_pulser_status = STEPPING; - i2s_out_start(); -#else - i2s_out_pulser_status = STEPPING; -#endif - I2S_OUT_PULSER_EXIT_CRITICAL(); - return 0; -} - -int IRAM_ATTR i2s_out_set_pulse_period(uint32_t period) { -#ifdef USE_I2S_OUT_STREAM - i2s_out_pulse_period = period; -#endif - return 0; -} - -int IRAM_ATTR i2s_out_set_pulse_callback(i2s_out_pulse_func_t func) { -#ifdef USE_I2S_OUT_STREAM - i2s_out_pulse_func = func; -#endif - return 0; -} - -int IRAM_ATTR i2s_out_reset() { - I2S_OUT_PULSER_ENTER_CRITICAL(); - i2s_out_stop(); -#ifdef USE_I2S_OUT_STREAM - if (i2s_out_pulser_status == STEPPING) { - uint32_t port_data = atomic_load(&i2s_out_port_data); - i2s_clear_o_dma_buffers(port_data); - } else if (i2s_out_pulser_status == WAITING) { - i2s_clear_o_dma_buffers(0); - i2s_out_pulser_status = PASSTHROUGH; - } -#endif - // You need to set the status before calling i2s_out_start() - // because the process in i2s_out_start() is different depending on the status. - i2s_out_start(); - I2S_OUT_PULSER_EXIT_CRITICAL(); - return 0; -} - -// -// Initialize funtion (external function) -// -int IRAM_ATTR i2s_out_init(i2s_out_init_t &init_param) { - if (i2s_out_initialized) { - // already initialized - return -1; - } - - atomic_store(&i2s_out_port_data, init_param.init_val); - - // To make sure hardware is enabled before any hardware register operations. - periph_module_reset(PERIPH_I2S0_MODULE); - periph_module_enable(PERIPH_I2S0_MODULE); - - // Route the i2s pins to the appropriate GPIO - i2s_out_gpio_attach(init_param.ws_pin, init_param.bck_pin, init_param.data_pin); - - /** - * Each i2s transfer will take - * fpll = PLL_D2_CLK -- clka_en = 0 - * - * fi2s = fpll / N + b/a -- N + b/a = clkm_div_num - * fi2s = 160MHz / 2 - * fi2s = 80MHz - * - * fbclk = fi2s / M -- M = tx_bck_div_num - * fbclk = 80MHz / 2 - * fbclk = 40MHz - * - * fwclk = fbclk / 32 - * - * for fwclk = 250kHz(16-bit: 4µS pulse time), 125kHz(32-bit: 8μS pulse time) - * N = 10, b/a = 0 - * M = 2 - * for fwclk = 500kHz(16-bit: 2µS pulse time), 250kHz(32-bit: 4μS pulse time) - * N = 5, b/a = 0 - * M = 2 - * for fwclk = 1000kHz(16-bit: 1µS pulse time), 500kHz(32-bit: 2μS pulse time) - * N = 2, b/a = 2/1 (N + b/a = 2.5) - * M = 2 - */ - -#ifdef USE_I2S_OUT_STREAM - // Allocate the array of pointers to the buffers - o_dma.buffers = (uint32_t **)malloc(sizeof(uint32_t*) * I2S_OUT_DMABUF_COUNT); - if (o_dma.buffers == nullptr) return -1; - - // Allocate each buffer that can be used by the DMA controller - for (int buf_idx = 0; buf_idx < I2S_OUT_DMABUF_COUNT; buf_idx++) { - o_dma.buffers[buf_idx] = (uint32_t*) heap_caps_calloc(1, I2S_OUT_DMABUF_LEN, MALLOC_CAP_DMA); - if (o_dma.buffers[buf_idx] == nullptr) return -1; - } - - // Allocate the array of DMA descriptors - o_dma.desc = (lldesc_t**) malloc(sizeof(lldesc_t*) * I2S_OUT_DMABUF_COUNT); - if (o_dma.desc == nullptr) return -1; - - // Allocate each DMA descriptor that will be used by the DMA controller - for (int buf_idx = 0; buf_idx < I2S_OUT_DMABUF_COUNT; buf_idx++) { - o_dma.desc[buf_idx] = (lldesc_t*) heap_caps_malloc(sizeof(lldesc_t), MALLOC_CAP_DMA); - if (o_dma.desc[buf_idx] == nullptr) return -1; - } - - // Initialize - i2s_clear_o_dma_buffers(init_param.init_val); - o_dma.rw_pos = 0; - o_dma.current = NULL; - o_dma.queue = xQueueCreate(I2S_OUT_DMABUF_COUNT, sizeof(uint32_t *)); - - // Set the first DMA descriptor - I2S0.out_link.addr = (uint32_t)o_dma.desc[0]; -#endif - - // stop i2s - I2S0.out_link.stop = 1; - I2S0.conf.tx_start = 0; - - I2S0.int_clr.val = I2S0.int_st.val; //clear pending interrupt - - // - // i2s_param_config - // - - // configure I2S data port interface. - i2s_out_reset_fifo(); - - //reset i2s - I2S0.conf.tx_reset = 1; - I2S0.conf.tx_reset = 0; - I2S0.conf.rx_reset = 1; - I2S0.conf.rx_reset = 0; - - //reset dma - I2S0.lc_conf.in_rst = 1; // Set this bit to reset in DMA FSM. (R/W) - I2S0.lc_conf.in_rst = 0; - I2S0.lc_conf.out_rst = 1; // Set this bit to reset out DMA FSM. (R/W) - I2S0.lc_conf.out_rst = 0; - - //Enable and configure DMA - I2S0.lc_conf.check_owner = 0; - I2S0.lc_conf.out_loop_test = 0; - I2S0.lc_conf.out_auto_wrback = 0; // Disable auto outlink-writeback when all the data has been transmitted - I2S0.lc_conf.out_data_burst_en = 0; - I2S0.lc_conf.outdscr_burst_en = 0; - I2S0.lc_conf.out_no_restart_clr = 0; - I2S0.lc_conf.indscr_burst_en = 0; -#ifdef USE_I2S_OUT_STREAM - I2S0.lc_conf.out_eof_mode = 1; // I2S_OUT_EOF_INT generated when DMA has popped all data from the FIFO; -#endif - I2S0.conf2.lcd_en = 0; - I2S0.conf2.camera_en = 0; - I2S0.pdm_conf.pcm2pdm_conv_en = 0; - I2S0.pdm_conf.pdm2pcm_conv_en = 0; - - I2S0.fifo_conf.dscr_en = 0; - -#ifdef USE_I2S_OUT_STREAM - if (i2s_out_pulser_status == STEPPING) { - // Stream output mode - I2S0.conf_chan.tx_chan_mod = 4; // 3:right+constant 4:left+constant (when tx_msb_right = 1) - I2S0.conf_single_data = 0; - } else { - // Static output mode - I2S0.conf_chan.tx_chan_mod = 3; // 3:right+constant 4:left+constant (when tx_msb_right = 1) - I2S0.conf_single_data = init_param.init_val; - } -#else - // For the static output mode - I2S0.conf_chan.tx_chan_mod = 3; // 3:right+constant 4:left+constant (when tx_msb_right = 1) - I2S0.conf_single_data = init_param.init_val; // initial constant value -#endif -#if I2S_OUT_NUM_BITS == 16 - I2S0.fifo_conf.tx_fifo_mod = 0; // 0: 16-bit dual channel data, 3: 32-bit single channel data - I2S0.fifo_conf.rx_fifo_mod = 0; // 0: 16-bit dual channel data, 3: 32-bit single channel data - I2S0.sample_rate_conf.tx_bits_mod = 16; // default is 16-bits - I2S0.sample_rate_conf.rx_bits_mod = 16; // default is 16-bits -#else - I2S0.fifo_conf.tx_fifo_mod = 3; // 0: 16-bit dual channel data, 3: 32-bit single channel data - I2S0.fifo_conf.rx_fifo_mod = 3; // 0: 16-bit dual channel data, 3: 32-bit single channel data - // Data width is 32-bit. Forgetting this setting will result in a 16-bit transfer. - I2S0.sample_rate_conf.tx_bits_mod = 32; - I2S0.sample_rate_conf.rx_bits_mod = 32; -#endif - I2S0.conf.tx_mono = 0; // Set this bit to enable transmitter’s mono mode in PCM standard mode. - - I2S0.conf_chan.rx_chan_mod = 1; // 1: right+right - I2S0.conf.rx_mono = 0; - -#ifdef USE_I2S_OUT_STREAM - I2S0.fifo_conf.dscr_en = 1; //connect DMA to fifo -#endif - I2S0.conf.tx_start = 0; - I2S0.conf.rx_start = 0; - - I2S0.conf.tx_msb_right = 1; // Set this bit to place right-channel data at the MSB in the transmit FIFO. - I2S0.conf.tx_right_first = 0; // Setting this bit allows the right-channel data to be sent first. - - I2S0.conf.tx_slave_mod = 0; // Master -#ifdef USE_I2S_OUT_STREAM - I2S0.fifo_conf.tx_fifo_mod_force_en = 1; //The bit should always be set to 1. -#endif - I2S0.pdm_conf.rx_pdm_en = 0; // Set this bit to enable receiver’s PDM mode. - I2S0.pdm_conf.tx_pdm_en = 0; // Set this bit to enable transmitter’s PDM mode. - - // I2S_COMM_FORMAT_I2S_LSB - I2S0.conf.tx_short_sync = 0; // Set this bit to enable transmitter in PCM standard mode. - I2S0.conf.rx_short_sync = 0; // Set this bit to enable receiver in PCM standard mode. - I2S0.conf.tx_msb_shift = 0; // Do not use the Philips standard to avoid bit-shifting - I2S0.conf.rx_msb_shift = 0; // Do not use the Philips standard to avoid bit-shifting - - // - // i2s_set_clk - // - - // set clock (fi2s) 160MHz / 5 - I2S0.clkm_conf.clka_en = 0; // Use 160 MHz PLL_D2_CLK as reference - // N + b/a = 0 -#if I2S_OUT_NUM_BITS == 16 - // N = 10 - I2S0.clkm_conf.clkm_div_num = 10; // minimum value of 2, reset value of 4, max 256 (I²S clock divider’s integral value) -#else - // N = 5 - I2S0.clkm_conf.clkm_div_num = 5; // minimum value of 2, reset value of 4, max 256 (I²S clock divider’s integral value) -#endif - // b/a = 0 - I2S0.clkm_conf.clkm_div_b = 0; // 0 at reset - I2S0.clkm_conf.clkm_div_a = 0; // 0 at reset, what about divide by 0? (not an issue) - - // Bit clock configuration bit in transmitter mode. - // fbck = fi2s / tx_bck_div_num = (160 MHz / 5) / 2 = 16 MHz - I2S0.sample_rate_conf.tx_bck_div_num = 2; // minimum value of 2 defaults to 6 - I2S0.sample_rate_conf.rx_bck_div_num = 2; - -#ifdef USE_I2S_OUT_STREAM - // Enable TX interrupts (DMA Interrupts) - I2S0.int_ena.out_eof = 1; // Triggered when rxlink has finished sending a packet. - I2S0.int_ena.out_dscr_err = 0; // Triggered when invalid rxlink descriptors are encountered. - I2S0.int_ena.out_total_eof = 1; // Triggered when all transmitting linked lists are used up. - I2S0.int_ena.out_done = 0; // Triggered when all transmitted and buffered data have been read. - - // default pulse callback period (μsec) - i2s_out_pulse_period = init_param.pulse_period; - i2s_out_pulse_func = init_param.pulse_func; - - // Create the task that will feed the buffer - xTaskCreatePinnedToCore(i2sOutTask, - "I2SOutTask", - 1024 * 10, - NULL, - 1, - nullptr, - CONFIG_ARDUINO_RUNNING_CORE // must run the task on same core - ); - - // Allocate and Enable the I2S interrupt - esp_intr_alloc(ETS_I2S0_INTR_SOURCE, 0, i2s_out_intr_handler, nullptr, &i2s_out_isr_handle); - esp_intr_enable(i2s_out_isr_handle); -#endif - - // Remember GPIO pin numbers - i2s_out_ws_pin = init_param.ws_pin; - i2s_out_bck_pin = init_param.bck_pin; - i2s_out_data_pin = init_param.data_pin; - i2s_out_initialized = 1; - - // Start the I2S peripheral - i2s_out_start(); - - return 0; -} - -#ifndef I2S_OUT_WS -#define I2S_OUT_WS GPIO_NUM_17 -#endif -#ifndef I2S_OUT_BCK -#define I2S_OUT_BCK GPIO_NUM_22 -#endif -#ifndef I2S_OUT_DATA -#define I2S_OUT_DATA GPIO_NUM_21 -#endif -#ifndef I2S_OUT_INIT_VAL -#define I2S_OUT_INIT_VAL 0 -#endif -/* - Initialize I2S out by default parameters. - - return -1 ... already initialized -*/ -int IRAM_ATTR i2s_out_init() { - i2s_out_init_t default_param = { - .ws_pin = I2S_OUT_WS, - .bck_pin = I2S_OUT_BCK, - .data_pin = I2S_OUT_DATA, - .pulse_func = NULL, - .pulse_period = I2S_OUT_USEC_PER_PULSE, - .init_val = I2S_OUT_INIT_VAL, - }; - return i2s_out_init(default_param); -} - -#endif diff --git a/Grbl_Esp32/machine_common.h b/Grbl_Esp32/machine_common.h deleted file mode 100644 index ea3e20fb..00000000 --- a/Grbl_Esp32/machine_common.h +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef _machine_common_h -#define _machine_common_h - -#ifndef SPINDLE_TYPE - #define SPINDLE_TYPE SPINDLE_TYPE_PWM -#endif - -// Grbl setting that are common to all machines -// It should not be necessary to change anything herein - -#ifndef GRBL_SPI_FREQ - // You can override these by defining them in a board file. - // To override, you must set all of them - //-1 means use the default board pin - #define GRBL_SPI_SS -1 - #define GRBL_SPI_MOSI -1 - #define GRBL_SPI_MISO -1 - #define GRBL_SPI_SCK -1 - #define GRBL_SPI_FREQ 4000000 -#endif - -// ESP32 CPU Settings -#define F_TIMERS 80000000 // a reference to the speed of ESP32 timers -#define F_STEPPER_TIMER 20000000 // frequency of step pulse timer -#define STEPPER_OFF_TIMER_PRESCALE 8 // gives a frequency of 10MHz -#define STEPPER_OFF_PERIOD_uSEC 3 // each tick is - -#define STEP_PULSE_MIN 2 // uSeconds -#define STEP_PULSE_MAX 10 // uSeconds - -// =============== Don't change or comment these out ====================== -// They are for legacy purposes and will not affect your I/O - -#define STEP_MASK B111111 - -#define PROBE_MASK 1 - -#endif // _machine_common_h diff --git a/Grbl_Esp32/print.cpp b/Grbl_Esp32/print.cpp deleted file mode 100644 index 8fec2847..00000000 --- a/Grbl_Esp32/print.cpp +++ /dev/null @@ -1,138 +0,0 @@ -/* - print.c - Functions for formatting output strings - Part of Grbl - - Copyright (c) 2011-2016 Sungeun K. Jeon for Gnea Research LLC - Copyright (c) 2009-2011 Simen Svale Skogsrud - - 2018 - Bart Dring This file was modifed 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 . -*/ - -#include "grbl.h" - - - - -// void printIntegerInBase(unsigned long n, unsigned long base) -// { -// unsigned char buf[8 * sizeof(long)]; // Assumes 8-bit chars. -// unsigned long i = 0; -// -// if (n == 0) { -// serial_write('0'); -// return; -// } -// -// while (n > 0) { -// buf[i++] = n % base; -// n /= base; -// } -// -// for (; i > 0; i--) -// serial_write(buf[i - 1] < 10 ? -// '0' + buf[i - 1] : -// 'A' + buf[i - 1] - 10); -// } - - -// Prints an uint8 variable in base 10. -void print_uint8_base10(uint8_t n) { - uint8_t digit_a = 0; - uint8_t digit_b = 0; - if (n >= 100) { // 100-255 - digit_a = '0' + n % 10; - n /= 10; - } - if (n >= 10) { // 10-99 - digit_b = '0' + n % 10; - n /= 10; - } - serial_write('0' + n); - if (digit_b) serial_write(digit_b); - if (digit_a) serial_write(digit_a); -} - - -// Prints an uint8 variable in base 2 with desired number of desired digits. -void print_uint8_base2_ndigit(uint8_t n, uint8_t digits) { - unsigned char buf[digits]; - uint8_t i = 0; - for (; i < digits; i++) { - buf[i] = n % 2 ; - n /= 2; - } - for (; i > 0; i--) - Serial.print('0' + buf[i - 1]); -} - - -void print_uint32_base10(uint32_t n) { - if (n == 0) { - Serial.print('0'); - return; - } - unsigned char buf[10]; - uint8_t i = 0; - while (n > 0) { - buf[i++] = n % 10; - n /= 10; - } - for (; i > 0; i--) - Serial.print('0' + buf[i - 1]); -} - - -void printInteger(long n) { - if (n < 0) { - Serial.print('-'); - print_uint32_base10(-n); - } else - print_uint32_base10(n); -} - - -// Convert float to string by immediately converting to a long integer, which contains -// more digits than a float. Number of decimal places, which are tracked by a counter, -// may be set by the user. The integer is then efficiently converted to a string. -// NOTE: AVR '%' and '/' integer operations are very efficient. Bitshifting speed-up -// techniques are actually just slightly slower. Found this out the hard way. -void printFloat(float n, uint8_t decimal_places) { - Serial.print(n, decimal_places); -} - - -// Floating value printing handlers for special variables types used in Grbl and are defined -// in the config.h. -// - CoordValue: Handles all position or coordinate values in inches or mm reporting. -// - RateValue: Handles feed rate and current velocity in inches or mm reporting. -void printFloat_CoordValue(float n) { - if (report_inches->get()) - printFloat(n * INCH_PER_MM, N_DECIMAL_COORDVALUE_INCH); - else - printFloat(n, N_DECIMAL_COORDVALUE_MM); -} - -// Debug tool to print free memory in bytes at the called point. -// NOTE: Keep commented unless using. Part of this function always gets compiled in. -// void printFreeMemory() -// { -// extern int __heap_start, *__brkval; -// uint16_t free; // Up to 64k values. -// free = (int) &free - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); -// printInteger((int32_t)free); -// printString(" "); -// } diff --git a/Grbl_Esp32/print.h b/Grbl_Esp32/print.h deleted file mode 100644 index 128dcc13..00000000 --- a/Grbl_Esp32/print.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - print.h - Functions for formatting output strings - Part of Grbl - - Copyright (c) 2011-2016 Sungeun K. Jeon for Gnea Research LLC - Copyright (c) 2009-2011 Simen Svale Skogsrud - - 2018 - Bart Dring This file was modifed 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 . -*/ - -#ifndef print_h -#define print_h - - -void printString(const char* s); - -void printPgmString(const char* s); - -void printInteger(long n); - -void print_uint32_base10(uint32_t n); - -// Prints an uint8 variable in base 10. -void print_uint8_base10(uint8_t n); - -// Prints an uint8 variable in base 2 with desired number of desired digits. -void print_uint8_base2_ndigit(uint8_t n, uint8_t digits); - -void printFloat(float n, uint8_t decimal_places); - -// Floating value printing handlers for special variables types used in Grbl. -// - CoordValue: Handles all position or coordinate values in inches or mm reporting. -// - RateValue: Handles feed rate and current velocity in inches or mm reporting. -void printFloat_CoordValue(float n); -void printFloat_RateValue(float n); - -// Debug tool to print free memory in bytes at the called point. Not used otherwise. -void printFreeMemory(); - -#endif diff --git a/Grbl_Esp32/cpu_map.h b/Grbl_Esp32/src/CPUMap.h similarity index 100% rename from Grbl_Esp32/cpu_map.h rename to Grbl_Esp32/src/CPUMap.h diff --git a/Grbl_Esp32/config.h b/Grbl_Esp32/src/Config.h similarity index 84% rename from Grbl_Esp32/config.h rename to Grbl_Esp32/src/Config.h index e50f2103..a594c324 100644 --- a/Grbl_Esp32/config.h +++ b/Grbl_Esp32/src/Config.h @@ -1,5 +1,7 @@ +#pragma once + /* - config.h - compile time configuration + Config.h - compile time configuration Part of Grbl Copyright (c) 2012-2016 Sungeun K. Jeon for Gnea Research LLC @@ -35,8 +37,6 @@ Some features should not be changed. See notes below. */ -#ifndef config_h -#define config_h #include // It is no longer necessary to edit this file to choose @@ -62,7 +62,7 @@ Some features should not be changed. See notes below. // NOTE: Defaults are set for a traditional 3-axis CNC machine. Z-axis first to clear, followed by X & Y. // These homing cycle definitions precede the machine.h file so that the machine // definition can undefine them if necessary. -#define HOMING_CYCLE_0 bit(Z_AXIS) // TYPICALLY REQUIRED: First move Z to clear workspace. +#define HOMING_CYCLE_0 bit(Z_AXIS) // TYPICALLY REQUIRED: First move Z to clear workspace. #define HOMING_CYCLE_1 bit(X_AXIS) #define HOMING_CYCLE_2 bit(Y_AXIS) @@ -74,34 +74,34 @@ Some features should not be changed. See notes below. // normally-closed switches on the specified pins, rather than the default normally-open switches. // The mask order is Cycle Start | Feed Hold | Reset | Safety Door // For example B1101 will invert the function of the Reset pin. -#define INVERT_CONTROL_PIN_MASK B1111 +#define INVERT_CONTROL_PIN_MASK B1111 -#define ENABLE_CONTROL_SW_DEBOUNCE // Default disabled. Uncomment to enable. -#define CONTROL_SW_DEBOUNCE_PERIOD 32 // in milliseconds default 32 microseconds +#define ENABLE_CONTROL_SW_DEBOUNCE // Default disabled. Uncomment to enable. +#define CONTROL_SW_DEBOUNCE_PERIOD 32 // in milliseconds default 32 microseconds #define USE_RMT_STEPS // Include the file that loads the machine-specific config file. // machine.h must be edited to choose the desired file. -#include "machine.h" +#include "Machine.h" // machine_common.h contains settings that do not change -#include "machine_common.h" +#include "MachineCommon.h" #define MAX_N_AXIS 6 // Number of axes defined (steppers, servos, etc) (valid range: 3 to 6) // Even if your machine only uses less than the minimum of 3, you should select 3 #ifndef N_AXIS - #define N_AXIS 3 +# define N_AXIS 3 #endif #ifndef LIMIT_MASK - #define LIMIT_MASK B0 +# define LIMIT_MASK B0 #endif -#define VERBOSE_HELP // Currently this doesn't do anything -#define GRBL_MSG_LEVEL MSG_LEVEL_INFO // what level of [MSG:....] do you want to see 0=all off +#define VERBOSE_HELP // Currently this doesn't do anything +#define GRBL_MSG_LEVEL MSG_LEVEL_INFO // what level of [MSG:....] do you want to see 0=all off // Serial baud rate // OK to change, but the ESP32 boot text is 115200, so you will not see that is your @@ -112,24 +112,23 @@ Some features should not be changed. See notes below. //#define CONNECT_TO_SSID "your SSID" //#define SSID_PASSWORD "your SSID password" //CONFIGURE_EYECATCH_BEGIN (DO NOT MODIFY THIS LINE) -#define ENABLE_BLUETOOTH // enable bluetooth +#define ENABLE_BLUETOOTH // enable bluetooth -#define ENABLE_SD_CARD // enable use of SD Card to run jobs +#define ENABLE_SD_CARD // enable use of SD Card to run jobs -#define ENABLE_WIFI //enable wifi +#define ENABLE_WIFI //enable wifi #if defined(ENABLE_WIFI) || defined(ENABLE_BLUETOOTH) -#define WIFI_OR_BLUETOOTH +# define WIFI_OR_BLUETOOTH #endif - -#define ENABLE_HTTP //enable HTTP and all related services -#define ENABLE_OTA //enable OTA -#define ENABLE_TELNET //enable telnet -#define ENABLE_TELNET_WELCOME_MSG //display welcome string when connect to telnet -#define ENABLE_MDNS //enable mDNS discovery -#define ENABLE_SSDP //enable UPNP discovery -#define ENABLE_NOTIFICATIONS //enable notifications +#define ENABLE_HTTP //enable HTTP and all related services +#define ENABLE_OTA //enable OTA +#define ENABLE_TELNET //enable telnet +#define ENABLE_TELNET_WELCOME_MSG //display welcome string when connect to telnet +#define ENABLE_MDNS //enable mDNS discovery +#define ENABLE_SSDP //enable UPNP discovery +#define ENABLE_NOTIFICATIONS //enable notifications #define ENABLE_SERIAL2SOCKET_IN #define ENABLE_SERIAL2SOCKET_OUT @@ -150,32 +149,32 @@ Some features should not be changed. See notes below. #define NAMESPACE "GRBL" #ifdef ENABLE_AUTHENTICATION - #define DEFAULT_ADMIN_PWD "admin" - #define DEFAULT_USER_PWD "user" - #define DEFAULT_ADMIN_LOGIN "admin" - #define DEFAULT_USER_LOGIN "user" +# define DEFAULT_ADMIN_PWD "admin" +# define DEFAULT_USER_PWD "user" +# define DEFAULT_ADMIN_LOGIN "admin" +# define DEFAULT_USER_LOGIN "user" #endif //Radio Mode #define ESP_RADIO_OFF 0 #define ESP_WIFI_STA 1 -#define ESP_WIFI_AP 2 -#define ESP_BT 3 +#define ESP_WIFI_AP 2 +#define ESP_BT 3 //Default mode #ifdef ENABLE_WIFI - #ifdef CONNECT_TO_SSID - #define DEFAULT_RADIO_MODE ESP_WIFI_STA - #else - #define DEFAULT_RADIO_MODE ESP_WIFI_AP - #endif //CONNECT_TO_SSID +# ifdef CONNECT_TO_SSID +# define DEFAULT_RADIO_MODE ESP_WIFI_STA +# else +# define DEFAULT_RADIO_MODE ESP_WIFI_AP +# endif //CONNECT_TO_SSID #else - #undef ENABLE_NOTIFICATIONS - #ifdef ENABLE_BLUETOOTH - #define DEFAULT_RADIO_MODE ESP_BT - #else - #define DEFAULT_RADIO_MODE ESP_RADIO_OFF - #endif +# undef ENABLE_NOTIFICATIONS +# ifdef ENABLE_BLUETOOTH +# define DEFAULT_RADIO_MODE ESP_BT +# else +# define DEFAULT_RADIO_MODE ESP_RADIO_OFF +# endif #endif // Define realtime command special characters. These characters are 'picked-off' directly from the @@ -185,7 +184,7 @@ Some features should not be changed. See notes below. // g-code programs, maybe selected for interface programs. // NOTE: If changed, manually update help message in report.c. -#define CMD_RESET 0x18 // ctrl-x. +#define CMD_RESET 0x18 // ctrl-x. #define CMD_STATUS_REPORT '?' #define CMD_CYCLE_START '~' #define CMD_FEED_HOLD '!' @@ -199,19 +198,19 @@ Some features should not be changed. See notes below. // #define CMD_CYCLE_START 0x82 // #define CMD_FEED_HOLD 0x83 #define CMD_SAFETY_DOOR 0x84 -#define CMD_JOG_CANCEL 0x85 -#define CMD_DEBUG_REPORT 0x86 // Only when DEBUG enabled, sends debug report in '{}' braces. -#define CMD_FEED_OVR_RESET 0x90 // Restores feed override value to 100%. +#define CMD_JOG_CANCEL 0x85 +#define CMD_DEBUG_REPORT 0x86 // Only when DEBUG enabled, sends debug report in '{}' braces. +#define CMD_FEED_OVR_RESET 0x90 // Restores feed override value to 100%. #define CMD_FEED_OVR_COARSE_PLUS 0x91 #define CMD_FEED_OVR_COARSE_MINUS 0x92 -#define CMD_FEED_OVR_FINE_PLUS 0x93 -#define CMD_FEED_OVR_FINE_MINUS 0x94 -#define CMD_RAPID_OVR_RESET 0x95 // Restores rapid override value to 100%. +#define CMD_FEED_OVR_FINE_PLUS 0x93 +#define CMD_FEED_OVR_FINE_MINUS 0x94 +#define CMD_RAPID_OVR_RESET 0x95 // Restores rapid override value to 100%. #define CMD_RAPID_OVR_MEDIUM 0x96 #define CMD_RAPID_OVR_LOW 0x97 // #define CMD_RAPID_OVR_EXTRA_LOW 0x98 // *NOT SUPPORTED* -#define CMD_SPINDLE_OVR_RESET 0x99 // Restores spindle override value to 100%. -#define CMD_SPINDLE_OVR_COARSE_PLUS 0x9A // 154 +#define CMD_SPINDLE_OVR_RESET 0x99 // Restores spindle override value to 100%. +#define CMD_SPINDLE_OVR_COARSE_PLUS 0x9A // 154 #define CMD_SPINDLE_OVR_COARSE_MINUS 0x9B #define CMD_SPINDLE_OVR_FINE_PLUS 0x9C #define CMD_SPINDLE_OVR_FINE_MINUS 0x9D @@ -222,18 +221,18 @@ Some features should not be changed. See notes below. // If homing is enabled, homing init lock sets Grbl into an alarm state upon power up. This forces // the user to perform the homing cycle (or override the locks) before doing anything else. This is // mainly a safety feature to remind the user to home, since position is unknown to Grbl. -#define HOMING_INIT_LOCK // Comment to disable +#define HOMING_INIT_LOCK // Comment to disable // Number of homing cycles performed after when the machine initially jogs to limit switches. // This help in preventing overshoot and should improve repeatability. This value should be one or // greater. -#define N_HOMING_LOCATE_CYCLE 1 // Integer (1-128) +#define N_HOMING_LOCATE_CYCLE 1 // Integer (1-128) // Enables single axis homing commands. $HX, $HY, and $HZ for X, Y, and Z-axis homing. The full homing // cycle is still invoked by the $H command. This is disabled by default. It's here only to address // users that need to switch between a two-axis and three-axis machine. This is actually very rare. // If you have a two-axis machine, DON'T USE THIS. Instead, just alter the homing cycle for two-axes. -#define HOMING_SINGLE_AXIS_COMMANDS // Default disabled. Uncomment to enable. +#define HOMING_SINGLE_AXIS_COMMANDS // Default disabled. Uncomment to enable. // After homing, Grbl will set by default the entire machine space into negative space, as is typical // for professional CNC machines, regardless of where the limit switches are located. Uncomment this @@ -250,7 +249,7 @@ Some features should not be changed. See notes below. // and addresses are defined in settings.h. With the current settings, up to 2 startup blocks may // be stored and executed in order. These startup blocks would typically be used to set the g-code // parser state depending on user preferences. -#define N_STARTUP_LINE 2 // Integer (1-2) +#define N_STARTUP_LINE 2 // Integer (1-2) // Number of floating decimal points printed by Grbl for certain value types. These settings are // determined by realistic and commonly observed values in CNC machines. For example, position @@ -258,12 +257,12 @@ Some features should not be changed. See notes below. // precise this. So, there is likely no need to change these, but you can if you need to here. // NOTE: Must be an integer value from 0 to ~4. More than 4 may exhibit round-off errors. // ESP32 Note: These are mostly hard coded, so these values will not change anything -#define N_DECIMAL_COORDVALUE_INCH 4 // Coordinate or position value in inches -#define N_DECIMAL_COORDVALUE_MM 3 // Coordinate or position value in mm -#define N_DECIMAL_RATEVALUE_INCH 1 // Rate or velocity value in in/min -#define N_DECIMAL_RATEVALUE_MM 0 // Rate or velocity value in mm/min -#define N_DECIMAL_SETTINGVALUE 3 // Decimals for floating point setting values -#define N_DECIMAL_RPMVALUE 0 // RPM value in rotations per min. +#define N_DECIMAL_COORDVALUE_INCH 4 // Coordinate or position value in inches +#define N_DECIMAL_COORDVALUE_MM 3 // Coordinate or position value in mm +#define N_DECIMAL_RATEVALUE_INCH 1 // Rate or velocity value in in/min +#define N_DECIMAL_RATEVALUE_MM 0 // Rate or velocity value in mm/min +#define N_DECIMAL_SETTINGVALUE 3 // Decimals for floating point setting values +#define N_DECIMAL_RPMVALUE 0 // RPM value in rotations per min. // If your machine has two limits switches wired in parallel to one axis, you will need to enable // this feature. Since the two switches are sharing a single pin, there is no way for Grbl to tell @@ -280,7 +279,7 @@ Some features should not be changed. See notes below. // Upon a successful probe cycle, this option provides immediately feedback of the probe coordinates // through an automatically generated message. If disabled, users can still access the last probe // coordinates through Grbl '$#' print parameters. -#define MESSAGE_PROBE_COORDINATES // Enabled by default. Comment to disable. +#define MESSAGE_PROBE_COORDINATES // Enabled by default. Comment to disable. // Enables a second coolant control pin via the mist coolant g-code command M7 on the Arduino Uno // analog pin 4. Only use this option if you require a second coolant control pin. @@ -293,12 +292,12 @@ Some features should not be changed. See notes below. // immediately forces a feed hold and then safely de-energizes the machine. Resuming is blocked until // the safety door is re-engaged. When it is, Grbl will re-energize the machine and then resume on the // previous tool path, as if nothing happened. -#define ENABLE_SAFETY_DOOR_INPUT_PIN // ESP32 Leave this enabled for now .. code for undefined not ready +#define ENABLE_SAFETY_DOOR_INPUT_PIN // ESP32 Leave this enabled for now .. code for undefined not ready // After the safety door switch has been toggled and restored, this setting sets the power-up delay // between restoring the spindle and coolant and resuming the cycle. -#define SAFETY_DOOR_SPINDLE_DELAY 4.0 // Float (seconds) -#define SAFETY_DOOR_COOLANT_DELAY 1.0 // Float (seconds) +#define SAFETY_DOOR_SPINDLE_DELAY 4.0 // Float (seconds) +#define SAFETY_DOOR_COOLANT_DELAY 1.0 // Float (seconds) // Enable CoreXY kinematics. Use ONLY with CoreXY machines. // IMPORTANT: If homing is enabled, you must reconfigure the homing cycle #defines above to @@ -317,13 +316,6 @@ Some features should not be changed. See notes below. // NOTE: PLEASE DO NOT USE THIS, unless you have a situation that needs it. // #define INVERT_LIMIT_PIN_MASK (bit(X_AXIS)|bit(Y_AXIS)) // Default disabled. Uncomment to enable. -// Inverts the spindle enable pin from low-disabled/high-enabled to low-enabled/high-disabled. Useful -// for some pre-built electronic boards. -// #define INVERT_SPINDLE_ENABLE_PIN // Default disabled. Uncomment to enable. - -// Inverts the spindle PWM output pin from low-disabled/high-enabled to low-enabled/high-disabled. -// #define INVERT_SPINDLE_OUTPUT_PIN // Default disabled. Uncomment to enable. - // Inverts the selected coolant pin from low-disabled/high-enabled to low-enabled/high-disabled. Useful // for some pre-built electronic boards. // #define INVERT_COOLANT_FLOOD_PIN // Default disabled. Uncomment to enable. @@ -352,27 +344,27 @@ Some features should not be changed. See notes below. // Configure rapid, feed, and spindle override settings. These values define the max and min // allowable override values and the coarse and fine increments per command received. Please // note the allowable values in the descriptions following each define. -#define DEFAULT_FEED_OVERRIDE 100 // 100%. Don't change this value. -#define MAX_FEED_RATE_OVERRIDE 200 // Percent of programmed feed rate (100-255). Usually 120% or 200% -#define MIN_FEED_RATE_OVERRIDE 10 // Percent of programmed feed rate (1-100). Usually 50% or 1% -#define FEED_OVERRIDE_COARSE_INCREMENT 10 // (1-99). Usually 10%. -#define FEED_OVERRIDE_FINE_INCREMENT 1 // (1-99). Usually 1%. +#define DEFAULT_FEED_OVERRIDE 100 // 100%. Don't change this value. +#define MAX_FEED_RATE_OVERRIDE 200 // Percent of programmed feed rate (100-255). Usually 120% or 200% +#define MIN_FEED_RATE_OVERRIDE 10 // Percent of programmed feed rate (1-100). Usually 50% or 1% +#define FEED_OVERRIDE_COARSE_INCREMENT 10 // (1-99). Usually 10%. +#define FEED_OVERRIDE_FINE_INCREMENT 1 // (1-99). Usually 1%. -#define DEFAULT_RAPID_OVERRIDE 100 // 100%. Don't change this value. -#define RAPID_OVERRIDE_MEDIUM 50 // Percent of rapid (1-99). Usually 50%. -#define RAPID_OVERRIDE_LOW 25 // Percent of rapid (1-99). Usually 25%. +#define DEFAULT_RAPID_OVERRIDE 100 // 100%. Don't change this value. +#define RAPID_OVERRIDE_MEDIUM 50 // Percent of rapid (1-99). Usually 50%. +#define RAPID_OVERRIDE_LOW 25 // Percent of rapid (1-99). Usually 25%. // #define RAPID_OVERRIDE_EXTRA_LOW 5 // *NOT SUPPORTED* Percent of rapid (1-99). Usually 5%. -#define DEFAULT_SPINDLE_SPEED_OVERRIDE 100 // 100%. Don't change this value. -#define MAX_SPINDLE_SPEED_OVERRIDE 200 // Percent of programmed spindle speed (100-255). Usually 200%. -#define MIN_SPINDLE_SPEED_OVERRIDE 10 // Percent of programmed spindle speed (1-100). Usually 10%. -#define SPINDLE_OVERRIDE_COARSE_INCREMENT 10 // (1-99). Usually 10%. -#define SPINDLE_OVERRIDE_FINE_INCREMENT 1 // (1-99). Usually 1%. +#define DEFAULT_SPINDLE_SPEED_OVERRIDE 100 // 100%. Don't change this value. +#define MAX_SPINDLE_SPEED_OVERRIDE 200 // Percent of programmed spindle speed (100-255). Usually 200%. +#define MIN_SPINDLE_SPEED_OVERRIDE 10 // Percent of programmed spindle speed (1-100). Usually 10%. +#define SPINDLE_OVERRIDE_COARSE_INCREMENT 10 // (1-99). Usually 10%. +#define SPINDLE_OVERRIDE_FINE_INCREMENT 1 // (1-99). Usually 1%. // When a M2 or M30 program end command is executed, most g-code states are restored to their defaults. // This compile-time option includes the restoring of the feed, rapid, and spindle speed override values // to their default values at program end. -#define RESTORE_OVERRIDES_AFTER_PROGRAM_END // Default enabled. Comment to disable. +#define RESTORE_OVERRIDES_AFTER_PROGRAM_END // Default enabled. Comment to disable. // The status report change for Grbl v1.1 and after also removed the ability to disable/enable most data // fields from the report. This caused issues for GUI developers, who've had to manage several scenarios @@ -380,12 +372,12 @@ Some features should not be changed. See notes below. // be sent without potential performance issues. // NOTE: The options below are here only provide a way to disable certain data fields if a unique // situation demands it, but be aware GUIs may depend on this data. If disabled, it may not be compatible. -#define REPORT_FIELD_BUFFER_STATE // Default enabled. Comment to disable. -#define REPORT_FIELD_PIN_STATE // Default enabled. Comment to disable. -#define REPORT_FIELD_CURRENT_FEED_SPEED // Default enabled. Comment to disable. -#define REPORT_FIELD_WORK_COORD_OFFSET // Default enabled. Comment to disable. -#define REPORT_FIELD_OVERRIDES // Default enabled. Comment to disable. -#define REPORT_FIELD_LINE_NUMBERS // Default enabled. Comment to disable. +#define REPORT_FIELD_BUFFER_STATE // Default enabled. Comment to disable. +#define REPORT_FIELD_PIN_STATE // Default enabled. Comment to disable. +#define REPORT_FIELD_CURRENT_FEED_SPEED // Default enabled. Comment to disable. +#define REPORT_FIELD_WORK_COORD_OFFSET // Default enabled. Comment to disable. +#define REPORT_FIELD_OVERRIDES // Default enabled. Comment to disable. +#define REPORT_FIELD_LINE_NUMBERS // Default enabled. Comment to disable. // Some status report data isn't necessary for realtime, only intermittently, because the values don't // change often. The following macros configures how many times a status report needs to be called before @@ -414,7 +406,7 @@ Some features should not be changed. See notes below. // smoothing the stepping of multi-axis motions. This feature smooths motion particularly at low step // frequencies below 10kHz, where the aliasing between axes of multi-axis motions can cause audible // noise and shake your machine. At even lower step frequencies, AMASS adapts and provides even better -// step smoothing. See stepper.c for more details on the AMASS system works. +// step smoothing. See Stepper.c for more details on the AMASS system works. #define ADAPTIVE_MULTI_AXIS_STEP_SMOOTHING // Default enabled. Comment to disable. // Sets the maximum step rate allowed to be written as a Grbl setting. This option enables an error @@ -444,14 +436,7 @@ Some features should not be changed. See notes below. // Sets which axis the tool length offset is applied. Assumes the spindle is always parallel with // the selected axis with the tool oriented toward the negative direction. In other words, a positive // tool length offset value is subtracted from the current location. -#define TOOL_LENGTH_OFFSET_AXIS Z_AXIS // Default z-axis. Valid values are X_AXIS, Y_AXIS, or Z_AXIS. - -// Alters the behavior of the spindle enable pin. By default Grbl will not disable the enable pin if -// spindle speed is zero and M3/4 is active, but still sets the PWM output to zero. This allows the users -// to know if the spindle is active and use it as an additional control input. -// However, in some use cases, user may want the enable pin to disable with a zero spindle speed and -// re-enable when spindle speed is greater than zero. This option does that. -#define SPINDLE_ENABLE_OFF_WITH_ZERO_SPEED // Default enabled. Comment to disable. +#define TOOL_LENGTH_OFFSET_AXIS Z_AXIS // Default z-axis. Valid values are X_AXIS, Y_AXIS, or Z_AXIS. // With this enabled, Grbl sends back an echo of the line it has received, which has been pre-parsed (spaces // removed, capitalized letters, no comments) and is to be immediately executed by Grbl. Echoes will not be @@ -474,19 +459,19 @@ Some features should not be changed. See notes below. // limits or angle between neighboring block line move directions. This is useful for machines that can't // tolerate the tool dwelling for a split second, i.e. 3d printers or laser cutters. If used, this value // should not be much greater than zero or to the minimum value necessary for the machine to work. -#define MINIMUM_JUNCTION_SPEED 0.0 // (mm/min) +#define MINIMUM_JUNCTION_SPEED 0.0 // (mm/min) // Sets the minimum feed rate the planner will allow. Any value below it will be set to this minimum // value. This also ensures that a planned motion always completes and accounts for any floating-point // round-off errors. Although not recommended, a lower value than 1.0 mm/min will likely work in smaller // machines, perhaps to 0.1mm/min, but your success may vary based on multiple factors. -#define MINIMUM_FEED_RATE 1.0 // (mm/min) +#define MINIMUM_FEED_RATE 1.0 // (mm/min) // Number of arc generation iterations by small angle approximation before exact arc trajectory // correction with expensive sin() and cos() calcualtions. This parameter maybe decreased if there // are issues with the accuracy of the arc generations, or increased if arc execution is getting // bogged down by too many trig calculations. -#define N_ARC_CORRECTION 12 // Integer (1-255) +#define N_ARC_CORRECTION 12 // Integer (1-255) // The arc G2/3 g-code standard is problematic by definition. Radius-based arcs have horrible numerical // errors when arc at semi-circles(pi) or full-circles(2*pi). Offset-based arcs are much more accurate @@ -496,15 +481,14 @@ Some features should not be changed. See notes below. // This define value sets the machine epsilon cutoff to determine if the arc is a full-circle or not. // NOTE: Be very careful when adjusting this value. It should always be greater than 1.2e-7 but not too // much greater than this. The default setting should capture most, if not all, full arc error situations. -#define ARC_ANGULAR_TRAVEL_EPSILON 5E-7 // Float (radians) +#define ARC_ANGULAR_TRAVEL_EPSILON 5E-7 // Float (radians) // Time delay increments performed during a dwell. The default value is set at 50ms, which provides // a maximum time delay of roughly 55 minutes, more than enough for most any application. Increasing // this delay will increase the maximum dwell time linearly, but also reduces the responsiveness of // run-time command executions, like status reports, since these are performed between each dwell // time step. Also, keep in mind that the Arduino delay timer is not very accurate for long delays. -#define DWELL_TIME_STEP 50 // Integer (1-255) (milliseconds) - +#define DWELL_TIME_STEP 50 // Integer (1-255) (milliseconds) // For test use only. This uses the ESP32's RMT peripheral to generate step pulses // It allows the use of the STEP_PULSE_DELAY (see below) and it automatically ends the @@ -568,7 +552,7 @@ Some features should not be changed. See notes below. // switch interrupt unblock a waiting task which will recheck the limit switch pins after // a short delay. Default disabled //#define ENABLE_SOFTWARE_DEBOUNCE // Default disabled. Uncomment to enable. -#define DEBOUNCE_PERIOD 32 // in milliseconds default 32 microseconds +#define DEBOUNCE_PERIOD 32 // in milliseconds default 32 microseconds // Configures the position after a probing cycle during Grbl's check mode. Disabled sets // the position to the probe target, when enabled sets the position to the start position. @@ -597,9 +581,9 @@ Some features should not be changed. See notes below. // Enable the '$RST=*', '$RST=$', and '$RST=#' eeprom restore commands. There are cases where // these commands may be undesirable. Simply comment the desired macro to disable it. // NOTE: See SETTINGS_RESTORE_ALL macro for customizing the `$RST=*` command. -#define ENABLE_RESTORE_EEPROM_WIPE_ALL // '$RST=*' Default enabled. Comment to disable. -#define ENABLE_RESTORE_EEPROM_DEFAULT_SETTINGS // '$RST=$' Default enabled. Comment to disable. -#define ENABLE_RESTORE_EEPROM_CLEAR_PARAMETERS // '$RST=#' Default enabled. Comment to disable. +#define ENABLE_RESTORE_EEPROM_WIPE_ALL // '$RST=*' Default enabled. Comment to disable. +#define ENABLE_RESTORE_EEPROM_DEFAULT_SETTINGS // '$RST=$' Default enabled. Comment to disable. +#define ENABLE_RESTORE_EEPROM_CLEAR_PARAMETERS // '$RST=#' Default enabled. Comment to disable. // Defines the EEPROM data restored upon a settings version change and `$RST=*` command. Whenever the // the settings or other EEPROM data structure changes between Grbl versions, Grbl will automatically @@ -622,7 +606,7 @@ Some features should not be changed. See notes below. // NOTE: If disabled and to ensure Grbl can never alter the build info line, you'll also need to enable // the SETTING_RESTORE_ALL macro above and remove SETTINGS_RESTORE_BUILD_INFO from the mask. // NOTE: See the included grblWrite_BuildInfo.ino example file to write this string seperately. -#define ENABLE_BUILD_INFO_WRITE_COMMAND // '$I=' Default enabled. Comment to disable. +#define ENABLE_BUILD_INFO_WRITE_COMMAND // '$I=' Default enabled. Comment to disable. // AVR processors require all interrupts to be disabled during an EEPROM write. This includes both // the stepper ISRs and serial comm ISRs. In the event of a long EEPROM write, this ISR pause can @@ -636,7 +620,7 @@ Some features should not be changed. See notes below. // NOTE: Most EEPROM write commands are implicitly blocked during a job (all '$' commands). However, // coordinate set g-code commands (G10,G28/30.1) are not, since they are part of an active streaming // job. At this time, this option only forces a planner buffer sync with these g-code commands. -#define FORCE_BUFFER_SYNC_DURING_EEPROM_WRITE // Default enabled. Comment to disable. +#define FORCE_BUFFER_SYNC_DURING_EEPROM_WRITE // Default enabled. Comment to disable. // In Grbl v0.9 and prior, there is an old outstanding bug where the `WPos:` work position reported // may not correlate to what is executing, because `WPos:` is based on the g-code parser state, which @@ -644,7 +628,7 @@ Some features should not be changed. See notes below. // motion whenever there is a command that alters the work coordinate offsets `G10,G43.1,G92,G54-59`. // This is the simplest way to ensure `WPos:` is always correct. Fortunately, it's exceedingly rare // that any of these commands are used need continuous motions through them. -#define FORCE_BUFFER_SYNC_DURING_WCO_CHANGE // Default enabled. Comment to disable. +#define FORCE_BUFFER_SYNC_DURING_WCO_CHANGE // Default enabled. Comment to disable. // By default, Grbl disables feed rate overrides for all G38.x probe cycle commands. Although this // may be different than some pro-class machine control, it's arguable that it should be this way. @@ -669,11 +653,11 @@ Some features should not be changed. See notes below. //#define PARKING_ENABLE // Default disabled. Uncomment to enable // Configure options for the parking motion, if enabled. -#define PARKING_AXIS Z_AXIS // Define which axis that performs the parking motion -#define PARKING_TARGET -5.0 // Parking axis target. In mm, as machine coordinate [-max_travel,0]. -#define PARKING_RATE 500.0 // Parking fast rate after pull-out in mm/min. -#define PARKING_PULLOUT_RATE 100.0 // Pull-out/plunge slow feed rate in mm/min. -#define PARKING_PULLOUT_INCREMENT 5.0 // Spindle pull-out and plunge distance in mm. Incremental distance. +#define PARKING_AXIS Z_AXIS // Define which axis that performs the parking motion +#define PARKING_TARGET -5.0 // Parking axis target. In mm, as machine coordinate [-max_travel,0]. +#define PARKING_RATE 500.0 // Parking fast rate after pull-out in mm/min. +#define PARKING_PULLOUT_RATE 100.0 // Pull-out/plunge slow feed rate in mm/min. +#define PARKING_PULLOUT_INCREMENT 5.0 // Spindle pull-out and plunge distance in mm. Incremental distance. // Must be positive value or equal to zero. // Enables a special set of M-code commands that enables and disables the parking motion. @@ -689,7 +673,7 @@ Some features should not be changed. See notes below. // override immediately after coming to a stop. However, this also means that the laser still may // be reenabled by disabling the spindle stop override, if needed. This is purely a safety feature // to ensure the laser doesn't inadvertently remain powered while at a stop and cause a fire. -#define DISABLE_LASER_DURING_HOLD // Default enabled. Comment to disable. +#define DISABLE_LASER_DURING_HOLD // Default enabled. Comment to disable. // Enables a piecewise linear model of the spindle PWM/speed output. Requires a solution by the // 'fit_nonlinear_spindle.py' script in the /doc/script folder of the repo. See file comments @@ -728,5 +712,3 @@ Some features should not be changed. See notes below. #define RPM_LINE_B2 4.754411e+01 #define RPM_LINE_A3 9.528342e-03 #define RPM_LINE_B3 3.306286e+01 - -#endif diff --git a/Grbl_Esp32/coolant_control.cpp b/Grbl_Esp32/src/CoolantControl.cpp similarity index 78% rename from Grbl_Esp32/coolant_control.cpp rename to Grbl_Esp32/src/CoolantControl.cpp index 3661fecf..9f4016e8 100644 --- a/Grbl_Esp32/coolant_control.cpp +++ b/Grbl_Esp32/src/CoolantControl.cpp @@ -1,5 +1,5 @@ /* - coolant_control.c - coolant control methods + CoolantControl.cpp - coolant control methods Part of Grbl Copyright (c) 2012-2016 Sungeun K. Jeon for Gnea Research LLC @@ -21,8 +21,7 @@ along with Grbl. If not, see . */ -#include "grbl.h" - +#include "Grbl.h" void coolant_init() { #ifdef COOLANT_FLOOD_PIN @@ -34,88 +33,86 @@ void coolant_init() { coolant_stop(); } - // Returns current coolant output state. Overrides may alter it from programmed state. uint8_t coolant_get_state() { uint8_t cl_state = COOLANT_STATE_DISABLE; #ifdef COOLANT_FLOOD_PIN -#ifdef INVERT_COOLANT_FLOOD_PIN - if (! digitalRead(COOLANT_FLOOD_PIN)) { -#else +# ifdef INVERT_COOLANT_FLOOD_PIN + if (!digitalRead(COOLANT_FLOOD_PIN)) { +# else if (digitalRead(COOLANT_FLOOD_PIN)) { -#endif +# endif cl_state |= COOLANT_STATE_FLOOD; } #endif #ifdef COOLANT_MIST_PIN -#ifdef INVERT_COOLANT_MIST_PIN - if (! digitalRead(COOLANT_MIST_PIN)) { -#else +# ifdef INVERT_COOLANT_MIST_PIN + if (!digitalRead(COOLANT_MIST_PIN)) { +# else if (digitalRead(COOLANT_MIST_PIN)) { -#endif +# endif cl_state |= COOLANT_STATE_MIST; } #endif return (cl_state); } - // Directly called by coolant_init(), coolant_set_state(), and mc_reset(), which can be at // an interrupt-level. No report flag set, but only called by routines that don't need it. void coolant_stop() { #ifdef COOLANT_FLOOD_PIN -#ifdef INVERT_COOLANT_FLOOD_PIN +# ifdef INVERT_COOLANT_FLOOD_PIN digitalWrite(COOLANT_FLOOD_PIN, 1); -#else +# else digitalWrite(COOLANT_FLOOD_PIN, 0); -#endif +# endif #endif #ifdef COOLANT_MIST_PIN -#ifdef INVERT_COOLANT_MIST_PIN +# ifdef INVERT_COOLANT_MIST_PIN digitalWrite(COOLANT_MIST_PIN, 1); -#else +# else digitalWrite(COOLANT_MIST_PIN, 0); -#endif +# endif #endif } - // Main program only. Immediately sets flood coolant running state and also mist coolant, // if enabled. Also sets a flag to report an update to a coolant state. // Called by coolant toggle override, parking restore, parking retract, sleep mode, g-code // parser program end, and g-code parser coolant_sync(). void coolant_set_state(uint8_t mode) { - if (sys.abort) return; // Block during abort. + if (sys.abort) + return; // Block during abort. if (mode == COOLANT_DISABLE) coolant_stop(); else { #ifdef COOLANT_FLOOD_PIN if (mode & COOLANT_FLOOD_ENABLE) { -#ifdef INVERT_COOLANT_FLOOD_PIN +# ifdef INVERT_COOLANT_FLOOD_PIN digitalWrite(COOLANT_FLOOD_PIN, 0); -#else +# else digitalWrite(COOLANT_FLOOD_PIN, 1); -#endif +# endif } #endif #ifdef COOLANT_MIST_PIN if (mode & COOLANT_MIST_ENABLE) { -#ifdef INVERT_COOLANT_MIST_PIN +# ifdef INVERT_COOLANT_MIST_PIN digitalWrite(COOLANT_MIST_PIN, 0); -#else +# else digitalWrite(COOLANT_MIST_PIN, 1); -#endif +# endif } #endif } - sys.report_ovr_counter = 0; // Set to report change immediately + sys.report_ovr_counter = 0; // Set to report change immediately } - // G-code parser entry-point for setting coolant state. Forces a planner buffer sync and bails // if an abort or check-mode is active. void coolant_sync(uint8_t mode) { - if (sys.state == STATE_CHECK_MODE) return; - protocol_buffer_synchronize(); // Ensure coolant turns on when specified in program. + if (sys.state == STATE_CHECK_MODE) + return; + protocol_buffer_synchronize(); // Ensure coolant turns on when specified in program. coolant_set_state(mode); } diff --git a/Grbl_Esp32/coolant_control.h b/Grbl_Esp32/src/CoolantControl.h similarity index 80% rename from Grbl_Esp32/coolant_control.h rename to Grbl_Esp32/src/CoolantControl.h index 586b109e..b4696a07 100644 --- a/Grbl_Esp32/coolant_control.h +++ b/Grbl_Esp32/src/CoolantControl.h @@ -1,5 +1,7 @@ +#pragma once + /* - coolant_control.h - spindle control methods + CoolantControl.h - spindle control methods Part of Grbl Copyright (c) 2012-2016 Sungeun K. Jeon for Gnea Research LLC @@ -21,16 +23,12 @@ along with Grbl. If not, see . */ -#ifndef coolant_control_h -#define coolant_control_h - -#define COOLANT_NO_SYNC false -#define COOLANT_FORCE_SYNC true - -#define COOLANT_STATE_DISABLE 0 // Must be zero -#define COOLANT_STATE_FLOOD bit(0) -#define COOLANT_STATE_MIST bit(1) +#define COOLANT_NO_SYNC false +#define COOLANT_FORCE_SYNC true +#define COOLANT_STATE_DISABLE 0 // Must be zero +#define COOLANT_STATE_FLOOD bit(0) +#define COOLANT_STATE_MIST bit(1) // Initializes coolant control pins. void coolant_init(); @@ -46,5 +44,3 @@ void coolant_set_state(uint8_t mode); // G-code parser entry-point for setting coolant states. Checks for and executes additional conditions. void coolant_sync(uint8_t mode); - -#endif diff --git a/Grbl_Esp32/custom_code.cpp b/Grbl_Esp32/src/CustomCode.cpp similarity index 72% rename from Grbl_Esp32/custom_code.cpp rename to Grbl_Esp32/src/CustomCode.cpp index dc6ab41f..7ee3f88a 100644 --- a/Grbl_Esp32/custom_code.cpp +++ b/Grbl_Esp32/src/CustomCode.cpp @@ -1,8 +1,8 @@ // This file loads custom code from the Custom/ subdirectory if // CUSTOM_CODE_FILENAME is defined. -#include "grbl.h" +#include "Grbl.h" #ifdef CUSTOM_CODE_FILENAME - #include CUSTOM_CODE_FILENAME +# include CUSTOM_CODE_FILENAME #endif diff --git a/Grbl_Esp32/src/Defaults.h b/Grbl_Esp32/src/Defaults.h new file mode 100644 index 00000000..c842d3a6 --- /dev/null +++ b/Grbl_Esp32/src/Defaults.h @@ -0,0 +1,501 @@ +#pragma once + +/* + Defaults.h - defaults settings configuration file + Part of Grbl + + Copyright (c) 2012-2016 Sungeun K. Jeon for Gnea Research LLC + + 2018 - Bart Dring This file was modifed 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 . +*/ + +/* The defaults.h file serves as a central default settings selector for different machine + types, from DIY CNC mills to CNC conversions of off-the-shelf machines. The settings + files listed here are supplied by users, so your results may vary. However, this should + give you a good starting point as you get to know your machine and tweak the settings for + your nefarious needs. + NOTE: Ensure one and only one of these DEFAULTS_XXX values is defined in config.h */ + +/* + All of these settings check to see if they have been defined already + before defining them. This allows to to easily set them eslewhere. + You only need to set ones that are important or unique to your + machine. The rest will be pulled from here. + */ + +// Grbl generic default settings. Should work across different machines. +#ifndef DEFAULT_STEP_PULSE_MICROSECONDS +# define DEFAULT_STEP_PULSE_MICROSECONDS 3 // $0 +#endif + +#ifndef DEFAULT_STEPPER_IDLE_LOCK_TIME +# define DEFAULT_STEPPER_IDLE_LOCK_TIME 250 // $1 msec (0-254, 255 keeps steppers enabled) +#endif + +#ifndef DEFAULT_STEPPING_INVERT_MASK +# define DEFAULT_STEPPING_INVERT_MASK 0 // $2 uint8_t +#endif + +#ifndef DEFAULT_DIRECTION_INVERT_MASK +# define DEFAULT_DIRECTION_INVERT_MASK 0 // $3 uint8_ +#endif + +#ifndef DEFAULT_INVERT_ST_ENABLE +# define DEFAULT_INVERT_ST_ENABLE 0 // $4 boolean +#endif + +#ifndef DEFAULT_INVERT_LIMIT_PINS +# define DEFAULT_INVERT_LIMIT_PINS 1 // $5 boolean +#endif + +#ifndef DEFAULT_INVERT_PROBE_PIN +# define DEFAULT_INVERT_PROBE_PIN 0 // $6 boolean +#endif + +#ifndef DEFAULT_STATUS_REPORT_MASK +# define DEFAULT_STATUS_REPORT_MASK 1 // $10 +#endif + +#ifndef DEFAULT_JUNCTION_DEVIATION +# define DEFAULT_JUNCTION_DEVIATION 0.01 // $11 mm +#endif + +#ifndef DEFAULT_ARC_TOLERANCE +# define DEFAULT_ARC_TOLERANCE 0.002 // $12 mm +#endif + +#ifndef DEFAULT_REPORT_INCHES +# define DEFAULT_REPORT_INCHES 0 // $13 false +#endif + +#ifndef DEFAULT_SOFT_LIMIT_ENABLE +# define DEFAULT_SOFT_LIMIT_ENABLE 0 // $20 false +#endif + +#ifndef DEFAULT_HARD_LIMIT_ENABLE +# define DEFAULT_HARD_LIMIT_ENABLE 0 // $21 false +#endif + +#ifndef DEFAULT_HOMING_ENABLE +# define DEFAULT_HOMING_ENABLE 0 // $22 false +#endif + +#ifndef DEFAULT_HOMING_DIR_MASK +# define DEFAULT_HOMING_DIR_MASK 3 // $23 move positive dir Z, negative X,Y +#endif + +#ifndef DEFAULT_HOMING_FEED_RATE +# define DEFAULT_HOMING_FEED_RATE 200.0 // $24 mm/min +#endif + +#ifndef DEFAULT_HOMING_SEEK_RATE +# define DEFAULT_HOMING_SEEK_RATE 2000.0 // $25 mm/min +#endif + +#ifndef DEFAULT_HOMING_DEBOUNCE_DELAY +# define DEFAULT_HOMING_DEBOUNCE_DELAY 250 // $26 msec (0-65k) +#endif + +#ifndef DEFAULT_HOMING_PULLOFF +# define DEFAULT_HOMING_PULLOFF 1.0 // $27 mm +#endif + +// ======== SPINDLE STUFF ==================== +#ifndef SPINDLE_TYPE +# define SPINDLE_TYPE SPINDLE_TYPE_NONE +#endif + +#ifndef DEFAULT_SPINDLE_RPM_MIN // $31 +# define DEFAULT_SPINDLE_RPM_MIN 0.0 // rpm +#endif + +#ifndef DEFAULT_LASER_MODE // $32 +# define DEFAULT_LASER_MODE 0 // false +#endif + +#ifndef DEFAULT_SPINDLE_RPM_MAX // $30 +# define DEFAULT_SPINDLE_RPM_MAX 1000.0 // rpm +#endif + +#ifndef DEFAULT_SPINDLE_FREQ +# define DEFAULT_SPINDLE_FREQ 5000.0 // $33 Hz (extended set) +#endif + +#ifndef DEFAULT_SPINDLE_OFF_VALUE +# define DEFAULT_SPINDLE_OFF_VALUE 0.0 // $34 Percent of full period(extended set) +#endif + +#ifndef DEFAULT_SPINDLE_MIN_VALUE +# define DEFAULT_SPINDLE_MIN_VALUE 0.0 // $35 Percent of full period (extended set) +#endif + +#ifndef DEFAULT_SPINDLE_MAX_VALUE +# define DEFAULT_SPINDLE_MAX_VALUE 100.0 // $36 Percent of full period (extended set) +#endif + +#ifndef DEFAULT_SPINDLE_DELAY_SPINUP +# define DEFAULT_SPINDLE_DELAY_SPINUP 0 +#endif + +#ifndef DEFAULT_SPINDLE_DELAY_SPINDOWN +# define DEFAULT_SPINDLE_DELAY_SPINDOWN 0 +#endif + +#ifndef DEFAULT_INVERT_SPINDLE_OUTPUT_PIN +# define DEFAULT_INVERT_SPINDLE_OUTPUT_PIN 0 +#endif + +#ifndef DEFAULT_INVERT_SPINDLE_ENABLE_PIN +# define DEFAULT_INVERT_SPINDLE_ENABLE_PIN 0 +#endif + +#ifndef DEFAULT_SPINDLE_ENABLE_OFF_WITH_ZERO_SPEED +# define DEFAULT_SPINDLE_ENABLE_OFF_WITH_ZERO_SPEED 0 +#endif + +// ================ user settings ===================== +#ifndef DEFAULT_USER_INT_80 +# define DEFAULT_USER_INT_80 0 // $80 User integer setting +#endif + +#ifndef DEFAULT_USER_INT_81 +# define DEFAULT_USER_INT_81 0 // $81 User integer setting +#endif + +#ifndef DEFAULT_USER_INT_82 +# define DEFAULT_USER_INT_82 0 // $82 User integer setting +#endif + +#ifndef DEFAULT_USER_INT_83 +# define DEFAULT_USER_INT_83 0 // $83 User integer setting +#endif + +#ifndef DEFAULT_USER_INT_84 +# define DEFAULT_USER_INT_84 0 // $84 User integer setting +#endif + +#ifndef DEFAULT_USER_FLOAT_90 +# define DEFAULT_USER_FLOAT_90 0.0 // $90 User integer setting +#endif + +#ifndef DEFAULT_USER_FLOAT_91 +# define DEFAULT_USER_FLOAT_91 0.0 // $92 User integer setting +#endif + +#ifndef DEFAULT_USER_FLOAT_92 +# define DEFAULT_USER_FLOAT_92 0.0 // $92 User integer setting +#endif + +#ifndef DEFAULT_USER_FLOAT_93 +# define DEFAULT_USER_FLOAT_93 0.0 // $93 User integer setting +#endif + +#ifndef DEFAULT_USER_FLOAT_94 +# define DEFAULT_USER_FLOAT_94 0.0 // $94 User integer setting +#endif + +// =========== AXIS RESOLUTION ====== + +#ifndef DEFAULT_X_STEPS_PER_MM +# define DEFAULT_X_STEPS_PER_MM 100.0 +#endif +#ifndef DEFAULT_Y_STEPS_PER_MM +# define DEFAULT_Y_STEPS_PER_MM 100.0 +#endif +#ifndef DEFAULT_Z_STEPS_PER_MM +# define DEFAULT_Z_STEPS_PER_MM 100.0 +#endif +#ifndef DEFAULT_A_STEPS_PER_MM +# define DEFAULT_A_STEPS_PER_MM 100.0 +#endif +#ifndef DEFAULT_B_STEPS_PER_MM +# define DEFAULT_B_STEPS_PER_MM 100.0 +#endif +#ifndef DEFAULT_C_STEPS_PER_MM +# define DEFAULT_C_STEPS_PER_MM 100.0 +#endif + +// ============ AXIS MAX SPPED ========= + +#ifndef DEFAULT_X_MAX_RATE +# define DEFAULT_X_MAX_RATE 1000.0 // mm/min +#endif +#ifndef DEFAULT_Y_MAX_RATE +# define DEFAULT_Y_MAX_RATE 1000.0 // mm/min +#endif +#ifndef DEFAULT_Z_MAX_RATE +# define DEFAULT_Z_MAX_RATE 1000.0 // mm/min +#endif +#ifndef DEFAULT_A_MAX_RATE +# define DEFAULT_A_MAX_RATE 1000.0 // mm/min +#endif +#ifndef DEFAULT_B_MAX_RATE +# define DEFAULT_B_MAX_RATE 1000.0 // mm/min +#endif +#ifndef DEFAULT_C_MAX_RATE +# define DEFAULT_C_MAX_RATE 1000.0 // mm/min +#endif + +// ============== Axis Acceleration ========= +#define SEC_PER_MIN_SQ (60.0 * 60.0) // Seconds Per Minute Squared, for acceleration conversion +// Default accelerations are expressed in mm/sec^2 +#ifndef DEFAULT_X_ACCELERATION +# define DEFAULT_X_ACCELERATION 200.0 +#endif +#ifndef DEFAULT_Y_ACCELERATION +# define DEFAULT_Y_ACCELERATION 200.0 +#endif +#ifndef DEFAULT_Z_ACCELERATION +# define DEFAULT_Z_ACCELERATION 200.0 +#endif +#ifndef DEFAULT_A_ACCELERATION +# define DEFAULT_A_ACCELERATION 200.0 +#endif +#ifndef DEFAULT_B_ACCELERATION +# define DEFAULT_B_ACCELERATION 200.0 +#endif +#ifndef DEFAULT_C_ACCELERATION +# define DEFAULT_C_ACCELERATION 200.0 +#endif + +// ========= AXIS MAX TRAVEL ============ + +#ifndef DEFAULT_X_MAX_TRAVEL +# define DEFAULT_X_MAX_TRAVEL 300.0 // $130 mm NOTE: Must be a positive value. +#endif +#ifndef DEFAULT_Y_MAX_TRAVEL +# define DEFAULT_Y_MAX_TRAVEL 300.0 // mm NOTE: Must be a positive value. +#endif +#ifndef DEFAULT_Z_MAX_TRAVEL +# define DEFAULT_Z_MAX_TRAVEL 300.0 // mm NOTE: Must be a positive value. +#endif +#ifndef DEFAULT_A_MAX_TRAVEL +# define DEFAULT_A_MAX_TRAVEL 300.0 // mm NOTE: Must be a positive value. +#endif +#ifndef DEFAULT_B_MAX_TRAVEL +# define DEFAULT_B_MAX_TRAVEL 300.0 // mm NOTE: Must be a positive value. +#endif +#ifndef DEFAULT_C_MAX_TRAVEL +# define DEFAULT_C_MAX_TRAVEL 300.0 // mm NOTE: Must be a positive value. +#endif + +// ========== Motor current (SPI Drivers ) ============= +#ifndef DEFAULT_X_CURRENT +# define DEFAULT_X_CURRENT 0.25 // $140 current in amps (extended set) +#endif +#ifndef DEFAULT_Y_CURRENT +# define DEFAULT_Y_CURRENT 0.25 // $141 current in amps (extended set) +#endif +#ifndef DEFAULT_Z_CURRENT +# define DEFAULT_Z_CURRENT 0.25 // $142 current in amps (extended set) +#endif +#ifndef DEFAULT_A_CURRENT +# define DEFAULT_A_CURRENT 0.25 // $143 current in amps (extended set) +#endif +#ifndef DEFAULT_B_CURRENT +# define DEFAULT_B_CURRENT 0.25 // $144 current in amps (extended set) +#endif +#ifndef DEFAULT_C_CURRENT +# define DEFAULT_C_CURRENT 0.25 // $145 current in amps (extended set) +#endif + +// ========== Motor hold current (SPI Drivers ) ============= + +#ifndef DEFAULT_X_HOLD_CURRENT +# define DEFAULT_X_HOLD_CURRENT 0.125 // $150 current in amps (extended set) +#endif +#ifndef DEFAULT_Y_HOLD_CURRENT +# define DEFAULT_Y_HOLD_CURRENT 0.125 // $151 current in amps (extended set) +#endif +#ifndef DEFAULT_Z_HOLD_CURRENT +# define DEFAULT_Z_HOLD_CURRENT 0.125 // $152 current in amps (extended set) +#endif +#ifndef DEFAULT_A_HOLD_CURRENT +# define DEFAULT_A_HOLD_CURRENT 0.125 // $153 current in amps (extended set) +#endif +#ifndef DEFAULT_B_HOLD_CURRENT +# define DEFAULT_B_HOLD_CURRENT 0.125 // $154 current in amps (extended set) +#endif +#ifndef DEFAULT_C_HOLD_CURRENT +# define DEFAULT_C_HOLD_CURRENT 0.125 // $154 current in amps (extended set) +#endif + +// ========== Microsteps (SPI Drivers ) ================ + +#ifndef DEFAULT_X_MICROSTEPS +# define DEFAULT_X_MICROSTEPS 16 // $160 micro steps (extended set) +#endif +#ifndef DEFAULT_Y_MICROSTEPS +# define DEFAULT_Y_MICROSTEPS 16 // $161 micro steps (extended set) +#endif +#ifndef DEFAULT_Z_MICROSTEPS +# define DEFAULT_Z_MICROSTEPS 16 // $162 micro steps (extended set) +#endif +#ifndef DEFAULT_A_MICROSTEPS +# define DEFAULT_A_MICROSTEPS 16 // $163 micro steps (extended set) +#endif +#ifndef DEFAULT_B_MICROSTEPS +# define DEFAULT_B_MICROSTEPS 16 // $164 micro steps (extended set) +#endif +#ifndef DEFAULT_C_MICROSTEPS +# define DEFAULT_C_MICROSTEPS 16 // $165 micro steps (extended set) +#endif + +// ========== Stallguard (SPI Drivers ) ================ + +#ifndef DEFAULT_X_STALLGUARD +# define DEFAULT_X_STALLGUARD 16 // $170 stallguard (extended set) +#endif +#ifndef DEFAULT_Y_STALLGUARD +# define DEFAULT_Y_STALLGUARD 16 // $171 stallguard (extended set) +#endif +#ifndef DEFAULT_Z_STALLGUARD +# define DEFAULT_Z_STALLGUARD 16 // $172 stallguard (extended set) +#endif +#ifndef DEFAULT_A_STALLGUARD +# define DEFAULT_A_STALLGUARD 16 // $173 stallguard (extended set) +#endif +#ifndef DEFAULT_B_STALLGUARD +# define DEFAULT_B_STALLGUARD 16 // $174 stallguard (extended set) +#endif +#ifndef DEFAULT_C_STALLGUARD +# define DEFAULT_C_STALLGUARD 16 // $175 stallguard (extended set) +#endif + +// ================== pin defaults ======================== + +// Here is a place to default pins to UNDEFINED_PIN. +// This can eliminate checking to see if the pin is defined because +// the overridden pinMode and digitalWrite functions will deal with it. + +#ifndef STEPPERS_DISABLE_PIN +# define STEPPERS_DISABLE_PIN UNDEFINED_PIN +#endif + +#ifndef X_DISABLE_PIN +# define X_DISABLE_PIN UNDEFINED_PIN +#endif +#ifndef Y_DISABLE_PIN +# define Y_DISABLE_PIN UNDEFINED_PIN +#endif +#ifndef Z_DISABLE_PIN +# define Z_DISABLE_PIN UNDEFINED_PIN +#endif +#ifndef A_DISABLE_PIN +# define A_DISABLE_PIN UNDEFINED_PIN +#endif +#ifndef B_DISABLE_PIN +# define B_DISABLE_PIN UNDEFINED_PIN +#endif +#ifndef C_DISABLE_PIN +# define C_DISABLE_PIN UNDEFINED_PIN +#endif + +#ifndef X2_DISABLE_PIN +# define X2_DISABLE_PIN UNDEFINED_PIN +#endif +#ifndef Y2_DISABLE_PIN +# define Y2_DISABLE_PIN UNDEFINED_PIN +#endif +#ifndef Z2_DISABLE_PIN +# define Z2_DISABLE_PIN UNDEFINED_PIN +#endif +#ifndef A2_DISABLE_PIN +# define A2_DISABLE_PIN UNDEFINED_PIN +#endif +#ifndef B2_DISABLE_PIN +# define B2_DISABLE_PIN UNDEFINED_PIN +#endif +#ifndef C2_DISABLE_PIN +# define C2_DISABLE_PIN UNDEFINED_PIN +#endif + +#ifndef X_LIMIT_PIN +# define X_LIMIT_PIN UNDEFINED_PIN +#endif +#ifndef Y_LIMIT_PIN +# define Y_LIMIT_PIN UNDEFINED_PIN +#endif +#ifndef Z_LIMIT_PIN +# define Z_LIMIT_PIN UNDEFINED_PIN +#endif +#ifndef A_LIMIT_PIN +# define A_LIMIT_PIN UNDEFINED_PIN +#endif +#ifndef B_LIMIT_PIN +# define B_LIMIT_PIN UNDEFINED_PIN +#endif +#ifndef C_LIMIT_PIN +# define C_LIMIT_PIN UNDEFINED_PIN +#endif +#ifndef X2_LIMIT_PIN +# define X2_LIMIT_PIN UNDEFINED_PIN +#endif +#ifndef Y2_LIMIT_PIN +# define Y2_LIMIT_PIN UNDEFINED_PIN +#endif +#ifndef Z2_LIMIT_PIN +# define Z2_LIMIT_PIN UNDEFINED_PIN +#endif +#ifndef A2_LIMIT_PIN +# define A2_LIMIT_PIN UNDEFINED_PIN +#endif +#ifndef B2_LIMIT_PIN +# define B2_LIMIT_PIN UNDEFINED_PIN +#endif +#ifndef C2_LIMIT_PIN +# define C2_LIMIT_PIN UNDEFINED_PIN +#endif + +// assigned all MS3 (microstep pin 3) to UNDEFINED_PIN + +#ifndef X_STEPPER_MS3 +# define X_STEPPER_MS3 UNDEFINED_PIN +#endif +#ifndef Y_STEPPER_MS3 +# define Y_STEPPER_MS3 UNDEFINED_PIN +#endif +#ifndef Z_STEPPER_MS3 +# define Z_STEPPER_MS3 UNDEFINED_PIN +#endif +#ifndef A_STEPPER_MS3 +# define A_STEPPER_MS3 UNDEFINED_PIN +#endif +#ifndef B_STEPPER_MS3 +# define B_STEPPER_MS3 UNDEFINED_PIN +#endif +#ifndef C_STEPPER_MS3 +# define C_STEPPER_MS3 UNDEFINED_PIN +#endif +#ifndef X2_STEPPER_MS3 +# define X2_STEPPER_MS3 UNDEFINED_PIN +#endif +#ifndef Y2_STEPPER_MS3 +# define Y2_STEPPER_MS3 UNDEFINED_PIN +#endif +#ifndef Z2_STEPPER_MS3 +# define Z2_STEPPER_MS3 UNDEFINED_PIN +#endif +#ifndef A2_STEPPER_MS3 +# define A2_STEPPER_MS3 UNDEFINED_PIN +#endif +#ifndef B2_STEPPER_MS3 +# define B2_STEPPER_MS3 UNDEFINED_PIN +#endif +#ifndef C2_STEPPER_MS3 +# define C2_STEPPER_MS3 UNDEFINED_PIN +#endif diff --git a/Grbl_Esp32/grbl_eeprom.cpp b/Grbl_Esp32/src/Eeprom.cpp similarity index 92% rename from Grbl_Esp32/grbl_eeprom.cpp rename to Grbl_Esp32/src/Eeprom.cpp index 658fce07..7096d24f 100644 --- a/Grbl_Esp32/grbl_eeprom.cpp +++ b/Grbl_Esp32/src/Eeprom.cpp @@ -1,5 +1,5 @@ /* - eeprom.cpp - Header for system level commands and real-time processes + Eeprom.cpp - Header for system level commands and real-time processes Part of Grbl Copyright (c) 2014-2016 Sungeun K. Jeon for Gnea Research LLC @@ -18,7 +18,7 @@ along with Grbl. If not, see . */ -#include "grbl.h" +#include "Grbl.h" void memcpy_to_eeprom_with_checksum(unsigned int destination, const char* source, unsigned int size) { unsigned char checksum = 0; @@ -34,7 +34,7 @@ void memcpy_to_eeprom_with_checksum(unsigned int destination, const char* source int memcpy_from_eeprom_with_checksum(char* destination, unsigned int source, unsigned int size) { unsigned char data, checksum = 0; for (; size > 0; size--) { - data = EEPROM.read(source++); + data = EEPROM.read(source++); checksum = (checksum << 1) || (checksum >> 7); checksum += data; *(destination++) = data; diff --git a/Grbl_Esp32/grbl_eeprom.h b/Grbl_Esp32/src/Eeprom.h similarity index 81% rename from Grbl_Esp32/grbl_eeprom.h rename to Grbl_Esp32/src/Eeprom.h index 1c9dcf77..a9c1d3ab 100644 --- a/Grbl_Esp32/grbl_eeprom.h +++ b/Grbl_Esp32/src/Eeprom.h @@ -1,5 +1,7 @@ +#pragma once + /* - eeprom.h - Header for system level commands and real-time processes + Eeprom.h - Header for system level commands and real-time processes Part of Grbl Copyright (c) 2014-2016 Sungeun K. Jeon for Gnea Research LLC @@ -18,14 +20,9 @@ along with Grbl. If not, see . */ -#ifndef eeprom_memcpy_h -#define eeprom_memcpy_h - -#include "grbl.h" +#include "Grbl.h" //unsigned char eeprom_get_char(unsigned int addr); //void eeprom_put_char(unsigned int addr, unsigned char new_value); void memcpy_to_eeprom_with_checksum(unsigned int destination, const char* source, unsigned int size); -int memcpy_from_eeprom_with_checksum(char* destination, unsigned int source, unsigned int size); - -#endif +int memcpy_from_eeprom_with_checksum(char* destination, unsigned int source, unsigned int size); diff --git a/Grbl_Esp32/src/GCode.cpp b/Grbl_Esp32/src/GCode.cpp new file mode 100644 index 00000000..2b7bbaf0 --- /dev/null +++ b/Grbl_Esp32/src/GCode.cpp @@ -0,0 +1,1365 @@ +/* + GCode.cpp - rs274/ngc parser. + Part of Grbl + + Copyright (c) 2011-2016 Sungeun K. Jeon for Gnea Research LLC + Copyright (c) 2009-2011 Simen Svale Skogsrud + + 2018 - Bart Dring This file was modifed 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 . +*/ + +#include "Grbl.h" + +// NOTE: Max line number is defined by the g-code standard to be 99999. It seems to be an +// arbitrary value, and some GUIs may require more. So we increased it based on a max safe +// value when converting a float (7.2 digit precision)s to an integer. +#define MAX_LINE_NUMBER 10000000 +#define MAX_TOOL_NUMBER 255 // Limited by max unsigned 8-bit value + +#define AXIS_COMMAND_NONE 0 +#define AXIS_COMMAND_NON_MODAL 1 +#define AXIS_COMMAND_MOTION_MODE 2 +#define AXIS_COMMAND_TOOL_LENGTH_OFFSET 3 // *Undefined but required + +// Declare gc extern struct +parser_state_t gc_state; +parser_block_t gc_block; + +#define FAIL(status) return (status); + +void gc_init() { + memset(&gc_state, 0, sizeof(parser_state_t)); + // Load default G54 coordinate system. + if (!(settings_read_coord_data(gc_state.modal.coord_select, gc_state.coord_system))) + report_status_message(STATUS_SETTING_READ_FAIL, CLIENT_SERIAL); +} + +// Sets g-code parser position in mm. Input in steps. Called by the system abort and hard +// limit pull-off routines. +void gc_sync_position() { + system_convert_array_steps_to_mpos(gc_state.position, sys_position); +} + +// Edit GCode line in-place, removing whitespace and comments and +// converting to uppercase +void collapseGCode(char* line) { + // parenPtr, if non-NULL, is the address of the character after ( + char* parenPtr = NULL; + // outPtr is the address where newly-processed characters will be placed. + // outPtr is alway less than or equal to inPtr. + char* outPtr = line; + char c; + for (char* inPtr = line; (c = *inPtr) != '\0'; inPtr++) { + if (isspace(c)) { + continue; + } + switch (c) { + case ')': + if (parenPtr) { + // Terminate comment by replacing ) with NUL + *inPtr = '\0'; + report_gcode_comment(parenPtr); + parenPtr = NULL; + } + // Strip out ) that does not follow a ( + break; + case '(': + // Start the comment at the character after ( + parenPtr = inPtr + 1; + break; + case ';': + // NOTE: ';' comment to EOL is a LinuxCNC definition. Not NIST. +#ifdef REPORT_SEMICOLON_COMMENTS + report_gcode_comment(inPtr + 1); +#endif + *outPtr = '\0'; + return; + case '%': + // TODO: Install '%' feature + // Program start-end percent sign NOT SUPPORTED. + // NOTE: This maybe installed to tell Grbl when a program is running vs manual input, + // where, during a program, the system auto-cycle start will continue to execute + // everything until the next '%' sign. This will help fix resuming issues with certain + // functions that empty the planner buffer to execute its task on-time. + break; + case '\r': + // In case one sneaks in + break; + default: + if (!parenPtr) { + *outPtr++ = toupper(c); // make upper case + } + } + } + // On loop exit, *inPtr is '\0' + if (parenPtr) { + // Handle unterminated ( comments + report_gcode_comment(parenPtr); + } + *outPtr = '\0'; +} + +// Executes one line of NUL-terminated G-Code. +// The line may contain whitespace and comments, which are first removed, +// and lower case characters, which are converted to upper case. +// In this function, all units and positions are converted and +// exported to grbl's internal functions in terms of (mm, mm/min) and absolute machine +// coordinates, respectively. +uint8_t gc_execute_line(char* line, uint8_t client) { + // Step 0 - remove whitespace and comments and convert to upper case + collapseGCode(line); +#ifdef REPORT_ECHO_LINE_RECEIVED + report_echo_line_received(line, client); +#endif + + /* ------------------------------------------------------------------------------------- + STEP 1: Initialize parser block struct and copy current g-code state modes. The parser + updates these modes and commands as the block line is parser and will only be used and + executed after successful error-checking. The parser block struct also contains a block + values struct, word tracking variables, and a non-modal commands tracker for the new + block. This struct contains all of the necessary information to execute the block. */ + memset(&gc_block, 0, sizeof(parser_block_t)); // Initialize the parser block struct. + memcpy(&gc_block.modal, &gc_state.modal, sizeof(gc_modal_t)); // Copy current modes + uint8_t axis_command = AXIS_COMMAND_NONE; + uint8_t axis_0, axis_1, axis_linear; + uint8_t coord_select = 0; // Tracks G10 P coordinate selection for execution + // Initialize bitflag tracking variables for axis indices compatible operations. + uint8_t axis_words = 0; // XYZ tracking + uint8_t ijk_words = 0; // IJK tracking + // Initialize command and value words and parser flags variables. + uint16_t command_words = 0; // Tracks G and M command words. Also used for modal group violations. + uint16_t value_words = 0; // Tracks value words. + uint8_t gc_parser_flags = GC_PARSER_NONE; + // Determine if the line is a jogging motion or a normal g-code block. + if (line[0] == '$') { // NOTE: `$J=` already parsed when passed to this function. + // Set G1 and G94 enforced modes to ensure accurate error checks. + gc_parser_flags |= GC_PARSER_JOG_MOTION; + gc_block.modal.motion = MOTION_MODE_LINEAR; + gc_block.modal.feed_rate = FEED_RATE_MODE_UNITS_PER_MIN; +#ifdef USE_LINE_NUMBERS + gc_block.values.n = JOG_LINE_NUMBER; // Initialize default line number reported during jog. +#endif + } + /* ------------------------------------------------------------------------------------- + STEP 2: Import all g-code words in the block line. A g-code word is a letter followed by + a number, which can either be a 'G'/'M' command or sets/assigns a command value. Also, + perform initial error-checks for command word modal group violations, for any repeated + words, and for negative values set for the value words F, N, P, T, and S. */ + uint8_t word_bit = 0; // Bit-value for assigning tracking variables + uint8_t char_counter; + char letter; + float value; + uint8_t int_value = 0; + uint16_t mantissa = 0; + if (gc_parser_flags & GC_PARSER_JOG_MOTION) { + char_counter = 3; // Start parsing after `$J=` + } else + char_counter = 0; + while (line[char_counter] != 0) { // Loop until no more g-code words in line. + // Import the next g-code word, expecting a letter followed by a value. Otherwise, error out. + letter = line[char_counter]; + if ((letter < 'A') || (letter > 'Z')) { + FAIL(STATUS_EXPECTED_COMMAND_LETTER); // [Expected word letter] + } + char_counter++; + if (!read_float(line, &char_counter, &value)) { + FAIL(STATUS_BAD_NUMBER_FORMAT); // [Expected word value] + } + // Convert values to smaller uint8 significand and mantissa values for parsing this word. + // NOTE: Mantissa is multiplied by 100 to catch non-integer command values. This is more + // accurate than the NIST gcode requirement of x10 when used for commands, but not quite + // accurate enough for value words that require integers to within 0.0001. This should be + // a good enough compromise and catch most all non-integer errors. To make it compliant, + // we would simply need to change the mantissa to int16, but this add compiled flash space. + // Maybe update this later. + int_value = trunc(value); + mantissa = round(100 * (value - int_value)); // Compute mantissa for Gxx.x commands. + // NOTE: Rounding must be used to catch small floating point errors. + // Check if the g-code word is supported or errors due to modal group violations or has + // been repeated in the g-code block. If ok, update the command or record its value. + switch (letter) { + /* 'G' and 'M' Command Words: Parse commands and check for modal group violations. + NOTE: Modal group numbers are defined in Table 4 of NIST RS274-NGC v3, pg.20 */ + case 'G': + // Determine 'G' command and its modal group + switch (int_value) { + case 10: + case 28: + case 30: + case 92: + // Check for G10/28/30/92 being called with G0/1/2/3/38 on same block. + // * G43.1 is also an axis command but is not explicitly defined this way. + if (mantissa == 0) { // Ignore G28.1, G30.1, and G92.1 + if (axis_command) { + FAIL(STATUS_GCODE_AXIS_COMMAND_CONFLICT); // [Axis word/command conflict] + } + axis_command = AXIS_COMMAND_NON_MODAL; + } + // No break. Continues to next line. + case 4: + case 53: + word_bit = MODAL_GROUP_G0; + gc_block.non_modal_command = int_value; + if ((int_value == 28) || (int_value == 30) || (int_value == 92)) { + if (!((mantissa == 0) || (mantissa == 10))) + FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); + gc_block.non_modal_command += mantissa; + mantissa = 0; // Set to zero to indicate valid non-integer G command. + } + break; + case 0: + case 1: + case 2: + case 3: + case 38: +#ifndef PROBE_PIN //only allow G38 "Probe" commands if a probe pin is defined. + if (int_value == 38) { + grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "No probe pin defined"); + FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); // [Unsupported G command] + } +#endif + // Check for G0/1/2/3/38 being called with G10/28/30/92 on same block. + // * G43.1 is also an axis command but is not explicitly defined this way. + if (axis_command) { + FAIL(STATUS_GCODE_AXIS_COMMAND_CONFLICT); // [Axis word/command conflict] + } + axis_command = AXIS_COMMAND_MOTION_MODE; + // No break. Continues to next line. + case 80: + word_bit = MODAL_GROUP_G1; + gc_block.modal.motion = int_value; + if (int_value == 38) { + if (!((mantissa == 20) || (mantissa == 30) || (mantissa == 40) || (mantissa == 50))) { + FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); // [Unsupported G38.x command] + } + gc_block.modal.motion += (mantissa / 10) + 100; + mantissa = 0; // Set to zero to indicate valid non-integer G command. + } + break; + case 17: + case 18: + case 19: + word_bit = MODAL_GROUP_G2; + gc_block.modal.plane_select = int_value - 17; + break; + case 90: + case 91: + if (mantissa == 0) { + word_bit = MODAL_GROUP_G3; + gc_block.modal.distance = int_value - 90; + } else { + word_bit = MODAL_GROUP_G4; + if ((mantissa != 10) || (int_value == 90)) { + FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); // [G90.1 not supported] + } + mantissa = 0; // Set to zero to indicate valid non-integer G command. + // Otherwise, arc IJK incremental mode is default. G91.1 does nothing. + } + break; + case 93: + case 94: + word_bit = MODAL_GROUP_G5; + gc_block.modal.feed_rate = 94 - int_value; + break; + case 20: + case 21: + word_bit = MODAL_GROUP_G6; + gc_block.modal.units = 21 - int_value; + break; + case 40: + word_bit = MODAL_GROUP_G7; + // NOTE: Not required since cutter radius compensation is always disabled. Only here + // to support G40 commands that often appear in g-code program headers to setup defaults. + // gc_block.modal.cutter_comp = CUTTER_COMP_DISABLE; // G40 + break; + case 43: + case 49: + word_bit = MODAL_GROUP_G8; + // NOTE: The NIST g-code standard vaguely states that when a tool length offset is changed, + // there cannot be any axis motion or coordinate offsets updated. Meaning G43, G43.1, and G49 + // all are explicit axis commands, regardless if they require axis words or not. + if (axis_command) + FAIL(STATUS_GCODE_AXIS_COMMAND_CONFLICT); + // [Axis word/command conflict] } + axis_command = AXIS_COMMAND_TOOL_LENGTH_OFFSET; + if (int_value == 49) // G49 + gc_block.modal.tool_length = TOOL_LENGTH_OFFSET_CANCEL; + else if (mantissa == 10) // G43.1 + gc_block.modal.tool_length = TOOL_LENGTH_OFFSET_ENABLE_DYNAMIC; + else { + FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); // [Unsupported G43.x command] + } + mantissa = 0; // Set to zero to indicate valid non-integer G command. + break; + case 54: + case 55: + case 56: + case 57: + case 58: + case 59: + // NOTE: G59.x are not supported. (But their int_values would be 60, 61, and 62.) + word_bit = MODAL_GROUP_G12; + gc_block.modal.coord_select = int_value - 54; // Shift to array indexing. + break; + case 61: + word_bit = MODAL_GROUP_G13; + if (mantissa != 0) { + FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); // [G61.1 not supported] + } + // gc_block.modal.control = CONTROL_MODE_EXACT_PATH; // G61 + break; + default: FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); // [Unsupported G command] + } + if (mantissa > 0) { + FAIL(STATUS_GCODE_COMMAND_VALUE_NOT_INTEGER); // [Unsupported or invalid Gxx.x command] + } + // Check for more than one command per modal group violations in the current block + // NOTE: Variable 'word_bit' is always assigned, if the command is valid. + if (bit_istrue(command_words, bit(word_bit))) + FAIL(STATUS_GCODE_MODAL_GROUP_VIOLATION); + command_words |= bit(word_bit); + break; + case 'M': + // Determine 'M' command and its modal group + if (mantissa > 0) { + FAIL(STATUS_GCODE_COMMAND_VALUE_NOT_INTEGER); // [No Mxx.x commands] + } + switch (int_value) { + case 0: + case 1: + case 2: + case 30: + word_bit = MODAL_GROUP_M4; + switch (int_value) { + case 0: gc_block.modal.program_flow = PROGRAM_FLOW_PAUSED; break; // Program pause + case 1: break; // Optional stop not supported. Ignore. + default: gc_block.modal.program_flow = int_value; // Program end and reset + } + break; + case 3: + case 4: + case 5: + word_bit = MODAL_GROUP_M7; + switch (int_value) { + case 3: gc_block.modal.spindle = SPINDLE_ENABLE_CW; break; + case 4: // Supported if SPINDLE_DIR_PIN is defined or laser mode is on. + if (spindle->is_reversable || laser_mode->get()) + gc_block.modal.spindle = SPINDLE_ENABLE_CCW; + else + FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); + break; + case 5: gc_block.modal.spindle = SPINDLE_DISABLE; break; + } + break; + case 6: // tool change + word_bit = MODAL_GROUP_M6; + gc_block.modal.tool_change = TOOL_CHANGE; +#ifdef USE_TOOL_CHANGE + //user_tool_change(gc_state.tool); +#endif + break; + case 7: + case 8: + case 9: + word_bit = MODAL_GROUP_M8; + switch (int_value) { +#ifdef COOLANT_MIST_PIN + case 7: gc_block.modal.coolant = COOLANT_MIST_ENABLE; break; +#endif +#ifdef COOLANT_FLOOD_PIN + case 8: gc_block.modal.coolant = COOLANT_FLOOD_ENABLE; break; +#endif + case 9: gc_block.modal.coolant = COOLANT_DISABLE; break; + } + break; +#ifdef ENABLE_PARKING_OVERRIDE_CONTROL + case 56: + word_bit = MODAL_GROUP_M9; + gc_block.modal.override = OVERRIDE_PARKING_MOTION; + break; +#endif + case 62: + case 63: + //grbl_sendf(CLIENT_SERIAL,"M%d...\r\n", int_value); + word_bit = MODAL_GROUP_M10; + switch (int_value) { + case 62: gc_block.modal.io_control = NON_MODAL_IO_ENABLE; break; + case 63: gc_block.modal.io_control = NON_MODAL_IO_DISABLE; break; + default: break; + } + break; + default: FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); // [Unsupported M command] + } + // Check for more than one command per modal group violations in the current block + // NOTE: Variable 'word_bit' is always assigned, if the command is valid. + if (bit_istrue(command_words, bit(word_bit))) + FAIL(STATUS_GCODE_MODAL_GROUP_VIOLATION); + command_words |= bit(word_bit); + break; + // NOTE: All remaining letters assign values. + default: + /* Non-Command Words: This initial parsing phase only checks for repeats of the remaining + legal g-code words and stores their value. Error-checking is performed later since some + words (I,J,K,L,P,R) have multiple connotations and/or depend on the issued commands. */ + switch (letter) { +#if (N_AXIS > A_AXIS) + case 'A': + word_bit = WORD_A; + gc_block.values.xyz[A_AXIS] = value; + axis_words |= bit(A_AXIS); + break; +#endif +#if (N_AXIS > B_AXIS) + case 'B': + word_bit = WORD_B; + gc_block.values.xyz[B_AXIS] = value; + axis_words |= bit(B_AXIS); + break; +#endif +#if (N_AXIS > C_AXIS) + case 'C': + word_bit = WORD_C; + gc_block.values.xyz[C_AXIS] = value; + axis_words |= bit(C_AXIS); + break; +#endif + // case 'D': // Not supported + case 'F': + word_bit = WORD_F; + gc_block.values.f = value; + break; + // case 'H': // Not supported + case 'I': + word_bit = WORD_I; + gc_block.values.ijk[X_AXIS] = value; + ijk_words |= bit(X_AXIS); + break; + case 'J': + word_bit = WORD_J; + gc_block.values.ijk[Y_AXIS] = value; + ijk_words |= bit(Y_AXIS); + break; + case 'K': + word_bit = WORD_K; + gc_block.values.ijk[Z_AXIS] = value; + ijk_words |= bit(Z_AXIS); + break; + case 'L': + word_bit = WORD_L; + gc_block.values.l = int_value; + break; + case 'N': + word_bit = WORD_N; + gc_block.values.n = trunc(value); + break; + case 'P': + word_bit = WORD_P; + gc_block.values.p = value; + break; + // NOTE: For certain commands, P value must be an integer, but none of these commands are supported. + // case 'Q': // Not supported + case 'R': + word_bit = WORD_R; + gc_block.values.r = value; + break; + case 'S': + word_bit = WORD_S; + gc_block.values.s = value; + break; + case 'T': + word_bit = WORD_T; + if (value > MAX_TOOL_NUMBER) + FAIL(STATUS_GCODE_MAX_VALUE_EXCEEDED); + grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Tool No: %d", int_value); + gc_state.tool = int_value; + break; + case 'X': + word_bit = WORD_X; + gc_block.values.xyz[X_AXIS] = value; + axis_words |= bit(X_AXIS); + break; + case 'Y': + word_bit = WORD_Y; + gc_block.values.xyz[Y_AXIS] = value; + axis_words |= bit(Y_AXIS); + break; + case 'Z': + word_bit = WORD_Z; + gc_block.values.xyz[Z_AXIS] = value; + axis_words |= bit(Z_AXIS); + break; + default: FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); + } + // NOTE: Variable 'word_bit' is always assigned, if the non-command letter is valid. + if (bit_istrue(value_words, bit(word_bit))) { + FAIL(STATUS_GCODE_WORD_REPEATED); // [Word repeated] + } + // Check for invalid negative values for words F, N, P, T, and S. + // NOTE: Negative value check is done here simply for code-efficiency. + if (bit(word_bit) & (bit(WORD_F) | bit(WORD_N) | bit(WORD_P) | bit(WORD_T) | bit(WORD_S))) { + if (value < 0.0) { + FAIL(STATUS_NEGATIVE_VALUE); // [Word value cannot be negative] + } + } + value_words |= bit(word_bit); // Flag to indicate parameter assigned. + } + } + // Parsing complete! + /* ------------------------------------------------------------------------------------- + STEP 3: Error-check all commands and values passed in this block. This step ensures all of + the commands are valid for execution and follows the NIST standard as closely as possible. + If an error is found, all commands and values in this block are dumped and will not update + the active system g-code modes. If the block is ok, the active system g-code modes will be + updated based on the commands of this block, and signal for it to be executed. + + Also, we have to pre-convert all of the values passed based on the modes set by the parsed + block. There are a number of error-checks that require target information that can only be + accurately calculated if we convert these values in conjunction with the error-checking. + This relegates the next execution step as only updating the system g-code modes and + performing the programmed actions in order. The execution step should not require any + conversion calculations and would only require minimal checks necessary to execute. + */ + /* NOTE: At this point, the g-code block has been parsed and the block line can be freed. + NOTE: It's also possible, at some future point, to break up STEP 2, to allow piece-wise + parsing of the block on a per-word basis, rather than the entire block. This could remove + the need for maintaining a large string variable for the entire block and free up some memory. + To do this, this would simply need to retain all of the data in STEP 1, such as the new block + data struct, the modal group and value bitflag tracking variables, and axis array indices + compatible variables. This data contains all of the information necessary to error-check the + new g-code block when the EOL character is received. However, this would break Grbl's startup + lines in how it currently works and would require some refactoring to make it compatible. + */ + // [0. Non-specific/common error-checks and miscellaneous setup]: + // Determine implicit axis command conditions. Axis words have been passed, but no explicit axis + // command has been sent. If so, set axis command to current motion mode. + if (axis_words) { + if (!axis_command) { + axis_command = AXIS_COMMAND_MOTION_MODE; // Assign implicit motion-mode + } + } + // Check for valid line number N value. + if (bit_istrue(value_words, bit(WORD_N))) { + // Line number value cannot be less than zero (done) or greater than max line number. + if (gc_block.values.n > MAX_LINE_NUMBER) { + FAIL(STATUS_GCODE_INVALID_LINE_NUMBER); // [Exceeds max line number] + } + } + // bit_false(value_words,bit(WORD_N)); // NOTE: Single-meaning value word. Set at end of error-checking. + // Track for unused words at the end of error-checking. + // NOTE: Single-meaning value words are removed all at once at the end of error-checking, because + // they are always used when present. This was done to save a few bytes of flash. For clarity, the + // single-meaning value words may be removed as they are used. Also, axis words are treated in the + // same way. If there is an explicit/implicit axis command, XYZ words are always used and are + // are removed at the end of error-checking. + // [1. Comments ]: MSG's NOT SUPPORTED. Comment handling performed by protocol. + // [2. Set feed rate mode ]: G93 F word missing with G1,G2/3 active, implicitly or explicitly. Feed rate + // is not defined after switching to G94 from G93. + // NOTE: For jogging, ignore prior feed rate mode. Enforce G94 and check for required F word. + if (gc_parser_flags & GC_PARSER_JOG_MOTION) { + if (bit_isfalse(value_words, bit(WORD_F))) + FAIL(STATUS_GCODE_UNDEFINED_FEED_RATE); + if (gc_block.modal.units == UNITS_MODE_INCHES) + gc_block.values.f *= MM_PER_INCH; + } else { + if (gc_block.modal.feed_rate == FEED_RATE_MODE_INVERSE_TIME) { // = G93 + // NOTE: G38 can also operate in inverse time, but is undefined as an error. Missing F word check added here. + if (axis_command == AXIS_COMMAND_MOTION_MODE) { + if ((gc_block.modal.motion != MOTION_MODE_NONE) || (gc_block.modal.motion != MOTION_MODE_SEEK)) { + if (bit_isfalse(value_words, bit(WORD_F))) { + FAIL(STATUS_GCODE_UNDEFINED_FEED_RATE); // [F word missing] + } + } + } + // NOTE: It seems redundant to check for an F word to be passed after switching from G94 to G93. We would + // accomplish the exact same thing if the feed rate value is always reset to zero and undefined after each + // inverse time block, since the commands that use this value already perform undefined checks. This would + // also allow other commands, following this switch, to execute and not error out needlessly. This code is + // combined with the above feed rate mode and the below set feed rate error-checking. + // [3. Set feed rate ]: F is negative (done.) + // - In inverse time mode: Always implicitly zero the feed rate value before and after block completion. + // NOTE: If in G93 mode or switched into it from G94, just keep F value as initialized zero or passed F word + // value in the block. If no F word is passed with a motion command that requires a feed rate, this will error + // out in the motion modes error-checking. However, if no F word is passed with NO motion command that requires + // a feed rate, we simply move on and the state feed rate value gets updated to zero and remains undefined. + } else { // = G94 + // - In units per mm mode: If F word passed, ensure value is in mm/min, otherwise push last state value. + if (gc_state.modal.feed_rate == FEED_RATE_MODE_UNITS_PER_MIN) { // Last state is also G94 + if (bit_istrue(value_words, bit(WORD_F))) { + if (gc_block.modal.units == UNITS_MODE_INCHES) + gc_block.values.f *= MM_PER_INCH; + } else { + gc_block.values.f = gc_state.feed_rate; // Push last state feed rate + } + } // Else, switching to G94 from G93, so don't push last state feed rate. Its undefined or the passed F word value. + } + } + // bit_false(value_words,bit(WORD_F)); // NOTE: Single-meaning value word. Set at end of error-checking. + // [4. Set spindle speed ]: S is negative (done.) + if (bit_isfalse(value_words, bit(WORD_S))) + gc_block.values.s = gc_state.spindle_speed; + // bit_false(value_words,bit(WORD_S)); // NOTE: Single-meaning value word. Set at end of error-checking. + // [5. Select tool ]: NOT SUPPORTED. Only tracks value. T is negative (done.) Not an integer. Greater than max tool value. + // bit_false(value_words,bit(WORD_T)); // NOTE: Single-meaning value word. Set at end of error-checking. + // [6. Change tool ]: N/A + // [7. Spindle control ]: N/A + // [8. Coolant control ]: N/A + // [9. Enable/disable feed rate or spindle overrides ]: NOT SUPPORTED. +#ifdef ENABLE_PARKING_OVERRIDE_CONTROL + if (bit_istrue(command_words, bit(MODAL_GROUP_M9))) { // Already set as enabled in parser. + if (bit_istrue(value_words, bit(WORD_P))) { + if (gc_block.values.p == 0.0) + gc_block.modal.override = OVERRIDE_DISABLED; + bit_false(value_words, bit(WORD_P)); + } + } +#endif + // [10. Dwell ]: P value missing. P is negative (done.) NOTE: See below. + if (gc_block.non_modal_command == NON_MODAL_DWELL) { + if (bit_isfalse(value_words, bit(WORD_P))) { + FAIL(STATUS_GCODE_VALUE_WORD_MISSING); // [P word missing] + } + bit_false(value_words, bit(WORD_P)); + } + if ((gc_block.modal.io_control == NON_MODAL_IO_ENABLE) || (gc_block.modal.io_control == NON_MODAL_IO_DISABLE)) { + if (bit_isfalse(value_words, bit(WORD_P))) { + FAIL(STATUS_GCODE_VALUE_WORD_MISSING); // [P word missing] + } + bit_false(value_words, bit(WORD_P)); + } + // [11. Set active plane ]: N/A + switch (gc_block.modal.plane_select) { + case PLANE_SELECT_XY: + axis_0 = X_AXIS; + axis_1 = Y_AXIS; + axis_linear = Z_AXIS; + break; + case PLANE_SELECT_ZX: + axis_0 = Z_AXIS; + axis_1 = X_AXIS; + axis_linear = Y_AXIS; + break; + default: // case PLANE_SELECT_YZ: + axis_0 = Y_AXIS; + axis_1 = Z_AXIS; + axis_linear = X_AXIS; + } + // [12. Set length units ]: N/A + // Pre-convert XYZ coordinate values to millimeters, if applicable. + uint8_t idx; + if (gc_block.modal.units == UNITS_MODE_INCHES) { + for (idx = 0; idx < N_AXIS; idx++) { // Axes indices are consistent, so loop may be used. + if (bit_istrue(axis_words, bit(idx))) + gc_block.values.xyz[idx] *= MM_PER_INCH; + } + } + // [13. Cutter radius compensation ]: G41/42 NOT SUPPORTED. Error, if enabled while G53 is active. + // [G40 Errors]: G2/3 arc is programmed after a G40. The linear move after disabling is less than tool diameter. + // NOTE: Since cutter radius compensation is never enabled, these G40 errors don't apply. Grbl supports G40 + // only for the purpose to not error when G40 is sent with a g-code program header to setup the default modes. + // [14. Cutter length compensation ]: G43 NOT SUPPORTED, but G43.1 and G49 are. + // [G43.1 Errors]: Motion command in same line. + // NOTE: Although not explicitly stated so, G43.1 should be applied to only one valid + // axis that is configured (in config.h). There should be an error if the configured axis + // is absent or if any of the other axis words are present. + if (axis_command == AXIS_COMMAND_TOOL_LENGTH_OFFSET) { // Indicates called in block. + if (gc_block.modal.tool_length == TOOL_LENGTH_OFFSET_ENABLE_DYNAMIC) { + if (axis_words ^ bit(TOOL_LENGTH_OFFSET_AXIS)) + FAIL(STATUS_GCODE_G43_DYNAMIC_AXIS_ERROR); + } + } + // [15. Coordinate system selection ]: *N/A. Error, if cutter radius comp is active. + // TODO: An EEPROM read of the coordinate data may require a buffer sync when the cycle + // is active. The read pauses the processor temporarily and may cause a rare crash. For + // future versions on processors with enough memory, all coordinate data should be stored + // in memory and written to EEPROM only when there is not a cycle active. + float block_coord_system[N_AXIS]; + memcpy(block_coord_system, gc_state.coord_system, sizeof(gc_state.coord_system)); + if (bit_istrue(command_words, bit(MODAL_GROUP_G12))) { // Check if called in block + if (gc_block.modal.coord_select > N_COORDINATE_SYSTEM) { + FAIL(STATUS_GCODE_UNSUPPORTED_COORD_SYS); // [Greater than N sys] + } + if (gc_state.modal.coord_select != gc_block.modal.coord_select) { + if (!(settings_read_coord_data(gc_block.modal.coord_select, block_coord_system))) + FAIL(STATUS_SETTING_READ_FAIL); + } + } + // [16. Set path control mode ]: N/A. Only G61. G61.1 and G64 NOT SUPPORTED. + // [17. Set distance mode ]: N/A. Only G91.1. G90.1 NOT SUPPORTED. + // [18. Set retract mode ]: NOT SUPPORTED. + // [19. Remaining non-modal actions ]: Check go to predefined position, set G10, or set axis offsets. + // NOTE: We need to separate the non-modal commands that are axis word-using (G10/G28/G30/G92), as these + // commands all treat axis words differently. G10 as absolute offsets or computes current position as + // the axis value, G92 similarly to G10 L20, and G28/30 as an intermediate target position that observes + // all the current coordinate system and G92 offsets. + switch (gc_block.non_modal_command) { + case NON_MODAL_SET_COORDINATE_DATA: + // [G10 Errors]: L missing and is not 2 or 20. P word missing. (Negative P value done.) + // [G10 L2 Errors]: R word NOT SUPPORTED. P value not 0 to nCoordSys(max 9). Axis words missing. + // [G10 L20 Errors]: P must be 0 to nCoordSys(max 9). Axis words missing. + if (!axis_words) { + FAIL(STATUS_GCODE_NO_AXIS_WORDS) + }; // [No axis words] + if (bit_isfalse(value_words, (bit(WORD_P) | bit(WORD_L)))) { + FAIL(STATUS_GCODE_VALUE_WORD_MISSING); // [P/L word missing] + } + coord_select = trunc(gc_block.values.p); // Convert p value to int. + if (coord_select > N_COORDINATE_SYSTEM) { + FAIL(STATUS_GCODE_UNSUPPORTED_COORD_SYS); // [Greater than N sys] + } + if (gc_block.values.l != 20) { + if (gc_block.values.l == 2) { + if (bit_istrue(value_words, bit(WORD_R))) { + FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); // [G10 L2 R not supported] + } + } else { + FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); // [Unsupported L] + } + } + bit_false(value_words, (bit(WORD_L) | bit(WORD_P))); + // Determine coordinate system to change and try to load from EEPROM. + if (coord_select > 0) { + coord_select--; // Adjust P1-P6 index to EEPROM coordinate data indexing. + } else { + coord_select = gc_block.modal.coord_select; // Index P0 as the active coordinate system + } + // NOTE: Store parameter data in IJK values. By rule, they are not in use with this command. + // FIXME: Instead of IJK, we'd better use: float vector[N_AXIS]; // [DG] + if (!settings_read_coord_data(coord_select, gc_block.values.ijk)) { + FAIL(STATUS_SETTING_READ_FAIL); // [EEPROM read fail] + } + // Pre-calculate the coordinate data changes. + for (idx = 0; idx < N_AXIS; idx++) { // Axes indices are consistent, so loop may be used. + // Update axes defined only in block. Always in machine coordinates. Can change non-active system. + if (bit_istrue(axis_words, bit(idx))) { + if (gc_block.values.l == 20) { + // L20: Update coordinate system axis at current position (with modifiers) with programmed value + // WPos = MPos - WCS - G92 - TLO -> WCS = MPos - G92 - TLO - WPos + gc_block.values.ijk[idx] = gc_state.position[idx] - gc_state.coord_offset[idx] - gc_block.values.xyz[idx]; + if (idx == TOOL_LENGTH_OFFSET_AXIS) + gc_block.values.ijk[idx] -= gc_state.tool_length_offset; + } else { + // L2: Update coordinate system axis to programmed value. + gc_block.values.ijk[idx] = gc_block.values.xyz[idx]; + } + } // Else, keep current stored value. + } + break; + case NON_MODAL_SET_COORDINATE_OFFSET: + // [G92 Errors]: No axis words. + if (!axis_words) { + FAIL(STATUS_GCODE_NO_AXIS_WORDS); // [No axis words] + } + // Update axes defined only in block. Offsets current system to defined value. Does not update when + // active coordinate system is selected, but is still active unless G92.1 disables it. + for (idx = 0; idx < N_AXIS; idx++) { // Axes indices are consistent, so loop may be used. + if (bit_istrue(axis_words, bit(idx))) { + // WPos = MPos - WCS - G92 - TLO -> G92 = MPos - WCS - TLO - WPos + gc_block.values.xyz[idx] = gc_state.position[idx] - block_coord_system[idx] - gc_block.values.xyz[idx]; + if (idx == TOOL_LENGTH_OFFSET_AXIS) + gc_block.values.xyz[idx] -= gc_state.tool_length_offset; + } else + gc_block.values.xyz[idx] = gc_state.coord_offset[idx]; + } + break; + default: + // At this point, the rest of the explicit axis commands treat the axis values as the traditional + // target position with the coordinate system offsets, G92 offsets, absolute override, and distance + // modes applied. This includes the motion mode commands. We can now pre-compute the target position. + // NOTE: Tool offsets may be appended to these conversions when/if this feature is added. + if (axis_command != AXIS_COMMAND_TOOL_LENGTH_OFFSET) { // TLO block any axis command. + if (axis_words) { + for (idx = 0; idx < N_AXIS; idx++) { // Axes indices are consistent, so loop may be used to save flash space. + if (bit_isfalse(axis_words, bit(idx))) { + gc_block.values.xyz[idx] = gc_state.position[idx]; // No axis word in block. Keep same axis position. + } else { + // Update specified value according to distance mode or ignore if absolute override is active. + // NOTE: G53 is never active with G28/30 since they are in the same modal group. + if (gc_block.non_modal_command != NON_MODAL_ABSOLUTE_OVERRIDE) { + // Apply coordinate offsets based on distance mode. + if (gc_block.modal.distance == DISTANCE_MODE_ABSOLUTE) { + gc_block.values.xyz[idx] += block_coord_system[idx] + gc_state.coord_offset[idx]; + if (idx == TOOL_LENGTH_OFFSET_AXIS) + gc_block.values.xyz[idx] += gc_state.tool_length_offset; + } else // Incremental mode + gc_block.values.xyz[idx] += gc_state.position[idx]; + } + } + } + } + } + // Check remaining non-modal commands for errors. + switch (gc_block.non_modal_command) { + case NON_MODAL_GO_HOME_0: // G28 + case NON_MODAL_GO_HOME_1: // G30 + // [G28/30 Errors]: Cutter compensation is enabled. + // Retreive G28/30 go-home position data (in machine coordinates) from EEPROM + // NOTE: Store parameter data in IJK values. By rule, they are not in use with this command. + if (gc_block.non_modal_command == NON_MODAL_GO_HOME_0) { + if (!settings_read_coord_data(SETTING_INDEX_G28, gc_block.values.ijk)) + FAIL(STATUS_SETTING_READ_FAIL); + } else { // == NON_MODAL_GO_HOME_1 + if (!settings_read_coord_data(SETTING_INDEX_G30, gc_block.values.ijk)) + FAIL(STATUS_SETTING_READ_FAIL); + } + if (axis_words) { + // Move only the axes specified in secondary move. + for (idx = 0; idx < N_AXIS; idx++) { + if (!(axis_words & bit(idx))) + gc_block.values.ijk[idx] = gc_state.position[idx]; + } + } else { + axis_command = AXIS_COMMAND_NONE; // Set to none if no intermediate motion. + } + break; + case NON_MODAL_SET_HOME_0: // G28.1 + case NON_MODAL_SET_HOME_1: // G30.1 + // [G28.1/30.1 Errors]: Cutter compensation is enabled. + // NOTE: If axis words are passed here, they are interpreted as an implicit motion mode. + break; + case NON_MODAL_RESET_COORDINATE_OFFSET: + // NOTE: If axis words are passed here, they are interpreted as an implicit motion mode. + break; + case NON_MODAL_ABSOLUTE_OVERRIDE: + // [G53 Errors]: G0 and G1 are not active. Cutter compensation is enabled. + // NOTE: All explicit axis word commands are in this modal group. So no implicit check necessary. + if (!(gc_block.modal.motion == MOTION_MODE_SEEK || gc_block.modal.motion == MOTION_MODE_LINEAR)) { + FAIL(STATUS_GCODE_G53_INVALID_MOTION_MODE); // [G53 G0/1 not active] + } + break; + } + } + // [20. Motion modes ]: + if (gc_block.modal.motion == MOTION_MODE_NONE) { + // [G80 Errors]: Axis word are programmed while G80 is active. + // NOTE: Even non-modal commands or TLO that use axis words will throw this strict error. + if (axis_words) { + FAIL(STATUS_GCODE_AXIS_WORDS_EXIST); // [No axis words allowed] + } + // Check remaining motion modes, if axis word are implicit (exist and not used by G10/28/30/92), or + // was explicitly commanded in the g-code block. + } else if (axis_command == AXIS_COMMAND_MOTION_MODE) { + if (gc_block.modal.motion == MOTION_MODE_SEEK) { + // [G0 Errors]: Axis letter not configured or without real value (done.) + // Axis words are optional. If missing, set axis command flag to ignore execution. + if (!axis_words) + axis_command = AXIS_COMMAND_NONE; + // All remaining motion modes (all but G0 and G80), require a valid feed rate value. In units per mm mode, + // the value must be positive. In inverse time mode, a positive value must be passed with each block. + } else { + // Check if feed rate is defined for the motion modes that require it. + if (gc_block.values.f == 0.0) { + FAIL(STATUS_GCODE_UNDEFINED_FEED_RATE); // [Feed rate undefined] + } + switch (gc_block.modal.motion) { + case MOTION_MODE_LINEAR: + // [G1 Errors]: Feed rate undefined. Axis letter not configured or without real value. + // Axis words are optional. If missing, set axis command flag to ignore execution. + if (!axis_words) + axis_command = AXIS_COMMAND_NONE; + break; + case MOTION_MODE_CW_ARC: gc_parser_flags |= GC_PARSER_ARC_IS_CLOCKWISE; // No break intentional. + case MOTION_MODE_CCW_ARC: + // [G2/3 Errors All-Modes]: Feed rate undefined. + // [G2/3 Radius-Mode Errors]: No axis words in selected plane. Target point is same as current. + // [G2/3 Offset-Mode Errors]: No axis words and/or offsets in selected plane. The radius to the current + // point and the radius to the target point differs more than 0.002mm (EMC def. 0.5mm OR 0.005mm and 0.1% radius). + // [G2/3 Full-Circle-Mode Errors]: NOT SUPPORTED. Axis words exist. No offsets programmed. P must be an integer. + // NOTE: Both radius and offsets are required for arc tracing and are pre-computed with the error-checking. + if (!axis_words) { + FAIL(STATUS_GCODE_NO_AXIS_WORDS); // [No axis words] + } + if (!(axis_words & (bit(axis_0) | bit(axis_1)))) { + FAIL(STATUS_GCODE_NO_AXIS_WORDS_IN_PLANE); // [No axis words in plane] + } + // Calculate the change in position along each selected axis + float x, y; + x = gc_block.values.xyz[axis_0] - gc_state.position[axis_0]; // Delta x between current position and target + y = gc_block.values.xyz[axis_1] - gc_state.position[axis_1]; // Delta y between current position and target + if (value_words & bit(WORD_R)) { // Arc Radius Mode + bit_false(value_words, bit(WORD_R)); + if (isequal_position_vector(gc_state.position, gc_block.values.xyz)) { + FAIL(STATUS_GCODE_INVALID_TARGET); // [Invalid target] + } + // Convert radius value to proper units. + if (gc_block.modal.units == UNITS_MODE_INCHES) + gc_block.values.r *= MM_PER_INCH; + /* We need to calculate the center of the circle that has the designated radius and passes + through both the current position and the target position. This method calculates the following + set of equations where [x,y] is the vector from current to target position, d == magnitude of + that vector, h == hypotenuse of the triangle formed by the radius of the circle, the distance to + the center of the travel vector. A vector perpendicular to the travel vector [-y,x] is scaled to the + length of h [-y/d*h, x/d*h] and added to the center of the travel vector [x/2,y/2] to form the new point + [i,j] at [x/2-y/d*h, y/2+x/d*h] which will be the center of our arc. + + d^2 == x^2 + y^2 + h^2 == r^2 - (d/2)^2 + i == x/2 - y/d*h + j == y/2 + x/d*h + + O <- [i,j] + - | + r - | + - | + - | h + - | + [0,0] -> C -----------------+--------------- T <- [x,y] + | <------ d/2 ---->| + + C - Current position + T - Target position + O - center of circle that pass through both C and T + d - distance from C to T + r - designated radius + h - distance from center of CT to O + + Expanding the equations: + + d -> sqrt(x^2 + y^2) + h -> sqrt(4 * r^2 - x^2 - y^2)/2 + i -> (x - (y * sqrt(4 * r^2 - x^2 - y^2)) / sqrt(x^2 + y^2)) / 2 + j -> (y + (x * sqrt(4 * r^2 - x^2 - y^2)) / sqrt(x^2 + y^2)) / 2 + + Which can be written: + + i -> (x - (y * sqrt(4 * r^2 - x^2 - y^2))/sqrt(x^2 + y^2))/2 + j -> (y + (x * sqrt(4 * r^2 - x^2 - y^2))/sqrt(x^2 + y^2))/2 + + Which we for size and speed reasons optimize to: + + h_x2_div_d = sqrt(4 * r^2 - x^2 - y^2)/sqrt(x^2 + y^2) + i = (x - (y * h_x2_div_d))/2 + j = (y + (x * h_x2_div_d))/2 + */ + // First, use h_x2_div_d to compute 4*h^2 to check if it is negative or r is smaller + // than d. If so, the sqrt of a negative number is complex and error out. + float h_x2_div_d = 4.0 * gc_block.values.r * gc_block.values.r - x * x - y * y; + if (h_x2_div_d < 0) { + FAIL(STATUS_GCODE_ARC_RADIUS_ERROR); // [Arc radius error] + } + // Finish computing h_x2_div_d. + h_x2_div_d = -sqrt(h_x2_div_d) / hypot_f(x, y); // == -(h * 2 / d) + // Invert the sign of h_x2_div_d if the circle is counter clockwise (see sketch below) + if (gc_block.modal.motion == MOTION_MODE_CCW_ARC) + h_x2_div_d = -h_x2_div_d; + /* The counter clockwise circle lies to the left of the target direction. When offset is positive, + the left hand circle will be generated - when it is negative the right hand circle is generated. + + T <-- Target position + + ^ + Clockwise circles with this center | Clockwise circles with this center will have + will have > 180 deg of angular travel | < 180 deg of angular travel, which is a good thing! + \ | / + center of arc when h_x2_div_d is positive -> x <----- | -----> x <- center of arc when h_x2_div_d is negative + | + | + + C <-- Current position + */ + // Negative R is g-code-alese for "I want a circle with more than 180 degrees of travel" (go figure!), + // even though it is advised against ever generating such circles in a single line of g-code. By + // inverting the sign of h_x2_div_d the center of the circles is placed on the opposite side of the line of + // travel and thus we get the unadvisably long arcs as prescribed. + if (gc_block.values.r < 0) { + h_x2_div_d = -h_x2_div_d; + gc_block.values.r = -gc_block.values.r; // Finished with r. Set to positive for mc_arc + } + // Complete the operation by calculating the actual center of the arc + gc_block.values.ijk[axis_0] = 0.5 * (x - (y * h_x2_div_d)); + gc_block.values.ijk[axis_1] = 0.5 * (y + (x * h_x2_div_d)); + } else { // Arc Center Format Offset Mode + if (!(ijk_words & (bit(axis_0) | bit(axis_1)))) { + FAIL(STATUS_GCODE_NO_OFFSETS_IN_PLANE); // [No offsets in plane] + } + bit_false(value_words, (bit(WORD_I) | bit(WORD_J) | bit(WORD_K))); + // Convert IJK values to proper units. + if (gc_block.modal.units == UNITS_MODE_INCHES) { + for (idx = 0; idx < N_AXIS; idx++) { // Axes indices are consistent, so loop may be used to save flash space. + if (ijk_words & bit(idx)) + gc_block.values.ijk[idx] *= MM_PER_INCH; + } + } + // Arc radius from center to target + x -= gc_block.values.ijk[axis_0]; // Delta x between circle center and target + y -= gc_block.values.ijk[axis_1]; // Delta y between circle center and target + float target_r = hypot_f(x, y); + // Compute arc radius for mc_arc. Defined from current location to center. + gc_block.values.r = hypot_f(gc_block.values.ijk[axis_0], gc_block.values.ijk[axis_1]); + // Compute difference between current location and target radii for final error-checks. + float delta_r = fabs(target_r - gc_block.values.r); + if (delta_r > 0.005) { + if (delta_r > 0.5) { + FAIL(STATUS_GCODE_INVALID_TARGET); // [Arc definition error] > 0.5mm + } + if (delta_r > (0.001 * gc_block.values.r)) { + FAIL(STATUS_GCODE_INVALID_TARGET); // [Arc definition error] > 0.005mm AND 0.1% radius + } + } + } + break; + case MOTION_MODE_PROBE_TOWARD_NO_ERROR: + case MOTION_MODE_PROBE_AWAY_NO_ERROR: gc_parser_flags |= GC_PARSER_PROBE_IS_NO_ERROR; // No break intentional. + case MOTION_MODE_PROBE_TOWARD: + case MOTION_MODE_PROBE_AWAY: + if ((gc_block.modal.motion == MOTION_MODE_PROBE_AWAY) || (gc_block.modal.motion == MOTION_MODE_PROBE_AWAY_NO_ERROR)) + gc_parser_flags |= GC_PARSER_PROBE_IS_AWAY; + // [G38 Errors]: Target is same current. No axis words. Cutter compensation is enabled. Feed rate + // is undefined. Probe is triggered. NOTE: Probe check moved to probe cycle. Instead of returning + // an error, it issues an alarm to prevent further motion to the probe. It's also done there to + // allow the planner buffer to empty and move off the probe trigger before another probing cycle. + if (!axis_words) { + FAIL(STATUS_GCODE_NO_AXIS_WORDS); // [No axis words] + } + if (isequal_position_vector(gc_state.position, gc_block.values.xyz)) { + FAIL(STATUS_GCODE_INVALID_TARGET); // [Invalid target] + } + break; + } + } + } + // [21. Program flow ]: No error checks required. + // [0. Non-specific error-checks]: Complete unused value words check, i.e. IJK used when in arc + // radius mode, or axis words that aren't used in the block. + if (gc_parser_flags & GC_PARSER_JOG_MOTION) { + // Jogging only uses the F feed rate and XYZ value words. N is valid, but S and T are invalid. + bit_false(value_words, (bit(WORD_N) | bit(WORD_F))); + } else { + bit_false(value_words, (bit(WORD_N) | bit(WORD_F) | bit(WORD_S) | bit(WORD_T))); // Remove single-meaning value words. + } + if (axis_command) { + bit_false(value_words, (bit(WORD_X) | bit(WORD_Y) | bit(WORD_Z) | bit(WORD_A) | bit(WORD_B) | bit(WORD_C))); // Remove axis words. + } + if (value_words) { + FAIL(STATUS_GCODE_UNUSED_WORDS); // [Unused words] + } + /* ------------------------------------------------------------------------------------- + STEP 4: EXECUTE!! + Assumes that all error-checking has been completed and no failure modes exist. We just + need to update the state and execute the block according to the order-of-execution. + */ + // Initialize planner data struct for motion blocks. + plan_line_data_t plan_data; + plan_line_data_t* pl_data = &plan_data; + memset(pl_data, 0, sizeof(plan_line_data_t)); // Zero pl_data struct + // Intercept jog commands and complete error checking for valid jog commands and execute. + // NOTE: G-code parser state is not updated, except the position to ensure sequential jog + // targets are computed correctly. The final parser position after a jog is updated in + // protocol_execute_realtime() when jogging completes or is canceled. + if (gc_parser_flags & GC_PARSER_JOG_MOTION) { + // Only distance and unit modal commands and G53 absolute override command are allowed. + // NOTE: Feed rate word and axis word checks have already been performed in STEP 3. + if (command_words & ~(bit(MODAL_GROUP_G3) | bit(MODAL_GROUP_G6) | bit(MODAL_GROUP_G0))) { + FAIL(STATUS_INVALID_JOG_COMMAND) + }; + if (!(gc_block.non_modal_command == NON_MODAL_ABSOLUTE_OVERRIDE || gc_block.non_modal_command == NON_MODAL_NO_ACTION)) + FAIL(STATUS_INVALID_JOG_COMMAND); + // Initialize planner data to current spindle and coolant modal state. + pl_data->spindle_speed = gc_state.spindle_speed; + plan_data.condition = (gc_state.modal.spindle | gc_state.modal.coolant); + uint8_t status = jog_execute(&plan_data, &gc_block); + if (status == STATUS_OK) + memcpy(gc_state.position, gc_block.values.xyz, sizeof(gc_block.values.xyz)); + return (status); + } + // If in laser mode, setup laser power based on current and past parser conditions. + if (laser_mode->get()) { + if (!((gc_block.modal.motion == MOTION_MODE_LINEAR) || (gc_block.modal.motion == MOTION_MODE_CW_ARC) || + (gc_block.modal.motion == MOTION_MODE_CCW_ARC))) + gc_parser_flags |= GC_PARSER_LASER_DISABLE; + // Any motion mode with axis words is allowed to be passed from a spindle speed update. + // NOTE: G1 and G0 without axis words sets axis_command to none. G28/30 are intentionally omitted. + // TODO: Check sync conditions for M3 enabled motions that don't enter the planner. (zero length). + if (axis_words && (axis_command == AXIS_COMMAND_MOTION_MODE)) + gc_parser_flags |= GC_PARSER_LASER_ISMOTION; + else { + // M3 constant power laser requires planner syncs to update the laser when changing between + // a G1/2/3 motion mode state and vice versa when there is no motion in the line. + if (gc_state.modal.spindle == SPINDLE_ENABLE_CW) { + if ((gc_state.modal.motion == MOTION_MODE_LINEAR) || (gc_state.modal.motion == MOTION_MODE_CW_ARC) || + (gc_state.modal.motion == MOTION_MODE_CCW_ARC)) { + if (bit_istrue(gc_parser_flags, GC_PARSER_LASER_DISABLE)) { + gc_parser_flags |= GC_PARSER_LASER_FORCE_SYNC; // Change from G1/2/3 motion mode. + } + } else { + // When changing to a G1 motion mode without axis words from a non-G1/2/3 motion mode. + if (bit_isfalse(gc_parser_flags, GC_PARSER_LASER_DISABLE)) + gc_parser_flags |= GC_PARSER_LASER_FORCE_SYNC; + } + } + } + } + // [0. Non-specific/common error-checks and miscellaneous setup]: + // NOTE: If no line number is present, the value is zero. + gc_state.line_number = gc_block.values.n; +#ifdef USE_LINE_NUMBERS + pl_data->line_number = gc_state.line_number; // Record data for planner use. +#endif + // [1. Comments feedback ]: NOT SUPPORTED + // [2. Set feed rate mode ]: + gc_state.modal.feed_rate = gc_block.modal.feed_rate; + if (gc_state.modal.feed_rate) { + pl_data->condition |= PL_COND_FLAG_INVERSE_TIME; // Set condition flag for planner use. + } + // [3. Set feed rate ]: + gc_state.feed_rate = gc_block.values.f; // Always copy this value. See feed rate error-checking. + pl_data->feed_rate = gc_state.feed_rate; // Record data for planner use. + // [4. Set spindle speed ]: + if ((gc_state.spindle_speed != gc_block.values.s) || bit_istrue(gc_parser_flags, GC_PARSER_LASER_FORCE_SYNC)) { + if (gc_state.modal.spindle != SPINDLE_DISABLE) { + if (bit_isfalse(gc_parser_flags, GC_PARSER_LASER_ISMOTION)) { + if (bit_istrue(gc_parser_flags, GC_PARSER_LASER_DISABLE)) + spindle->sync(gc_state.modal.spindle, 0); + else + spindle->sync(gc_state.modal.spindle, (uint32_t)gc_block.values.s); + } + } + gc_state.spindle_speed = gc_block.values.s; // Update spindle speed state. + } + // NOTE: Pass zero spindle speed for all restricted laser motions. + if (bit_isfalse(gc_parser_flags, GC_PARSER_LASER_DISABLE)) { + pl_data->spindle_speed = gc_state.spindle_speed; // Record data for planner use. + } // else { pl_data->spindle_speed = 0.0; } // Initialized as zero already. + // [5. Select tool ]: NOT SUPPORTED. Only tracks tool value. + // gc_state.tool = gc_block.values.t; + // [6. Change tool ]: NOT SUPPORTED + if (gc_block.modal.tool_change == TOOL_CHANGE) { +#ifdef USE_TOOL_CHANGE + user_tool_change(gc_state.tool); +#endif + } + // [7. Spindle control ]: + if (gc_state.modal.spindle != gc_block.modal.spindle) { + // Update spindle control and apply spindle speed when enabling it in this block. + // NOTE: All spindle state changes are synced, even in laser mode. Also, pl_data, + // rather than gc_state, is used to manage laser state for non-laser motions. + spindle->sync(gc_block.modal.spindle, (uint32_t)pl_data->spindle_speed); + gc_state.modal.spindle = gc_block.modal.spindle; + } + pl_data->condition |= gc_state.modal.spindle; // Set condition flag for planner use. + // [8. Coolant control ]: + if (gc_state.modal.coolant != gc_block.modal.coolant) { + // NOTE: Coolant M-codes are modal. Only one command per line is allowed. But, multiple states + // can exist at the same time, while coolant disable clears all states. + coolant_sync(gc_block.modal.coolant); + if (gc_block.modal.coolant == COOLANT_DISABLE) + gc_state.modal.coolant = COOLANT_DISABLE; + else + gc_state.modal.coolant |= gc_block.modal.coolant; + } + pl_data->condition |= gc_state.modal.coolant; // Set condition flag for planner use. + // turn on/off an i/o pin + if ((gc_block.modal.io_control == NON_MODAL_IO_ENABLE) || (gc_block.modal.io_control == NON_MODAL_IO_DISABLE)) { + if (gc_block.values.p <= MAX_USER_DIGITAL_PIN) + sys_io_control(bit((int)gc_block.values.p), (gc_block.modal.io_control == NON_MODAL_IO_ENABLE)); + else + FAIL(STATUS_P_PARAM_MAX_EXCEEDED); + } + // [9. Override control ]: NOT SUPPORTED. Always enabled. Except for a Grbl-only parking control. +#ifdef ENABLE_PARKING_OVERRIDE_CONTROL + if (gc_state.modal.override != gc_block.modal.override) { + gc_state.modal.override = gc_block.modal.override; + mc_override_ctrl_update(gc_state.modal.override); + } +#endif + // [10. Dwell ]: + if (gc_block.non_modal_command == NON_MODAL_DWELL) + mc_dwell(gc_block.values.p); + // [11. Set active plane ]: + gc_state.modal.plane_select = gc_block.modal.plane_select; + // [12. Set length units ]: + gc_state.modal.units = gc_block.modal.units; + // [13. Cutter radius compensation ]: G41/42 NOT SUPPORTED + // gc_state.modal.cutter_comp = gc_block.modal.cutter_comp; // NOTE: Not needed since always disabled. + // [14. Cutter length compensation ]: G43.1 and G49 supported. G43 NOT SUPPORTED. + // NOTE: If G43 were supported, its operation wouldn't be any different from G43.1 in terms + // of execution. The error-checking step would simply load the offset value into the correct + // axis of the block XYZ value array. + if (axis_command == AXIS_COMMAND_TOOL_LENGTH_OFFSET) { // Indicates a change. + gc_state.modal.tool_length = gc_block.modal.tool_length; + if (gc_state.modal.tool_length == TOOL_LENGTH_OFFSET_CANCEL) // G49 + gc_block.values.xyz[TOOL_LENGTH_OFFSET_AXIS] = 0.0; + // else G43.1 + if (gc_state.tool_length_offset != gc_block.values.xyz[TOOL_LENGTH_OFFSET_AXIS]) { + gc_state.tool_length_offset = gc_block.values.xyz[TOOL_LENGTH_OFFSET_AXIS]; + system_flag_wco_change(); + } + } + // [15. Coordinate system selection ]: + if (gc_state.modal.coord_select != gc_block.modal.coord_select) { + gc_state.modal.coord_select = gc_block.modal.coord_select; + memcpy(gc_state.coord_system, block_coord_system, N_AXIS * sizeof(float)); + system_flag_wco_change(); + } + // [16. Set path control mode ]: G61.1/G64 NOT SUPPORTED + // gc_state.modal.control = gc_block.modal.control; // NOTE: Always default. + // [17. Set distance mode ]: + gc_state.modal.distance = gc_block.modal.distance; + // [18. Set retract mode ]: NOT SUPPORTED + // [19. Go to predefined position, Set G10, or Set axis offsets ]: + switch (gc_block.non_modal_command) { + case NON_MODAL_SET_COORDINATE_DATA: + settings_write_coord_data(coord_select, gc_block.values.ijk); + // Update system coordinate system if currently active. + if (gc_state.modal.coord_select == coord_select) { + memcpy(gc_state.coord_system, gc_block.values.ijk, N_AXIS * sizeof(float)); + system_flag_wco_change(); + } + break; + case NON_MODAL_GO_HOME_0: + case NON_MODAL_GO_HOME_1: + // Move to intermediate position before going home. Obeys current coordinate system and offsets + // and absolute and incremental modes. + pl_data->condition |= PL_COND_FLAG_RAPID_MOTION; // Set rapid motion condition flag. + if (axis_command) { + mc_line(gc_block.values.xyz, pl_data); // kinematics kinematics not used for homing righ now + } + mc_line(gc_block.values.ijk, pl_data); + memcpy(gc_state.position, gc_block.values.ijk, N_AXIS * sizeof(float)); + break; + case NON_MODAL_SET_HOME_0: settings_write_coord_data(SETTING_INDEX_G28, gc_state.position); break; + case NON_MODAL_SET_HOME_1: settings_write_coord_data(SETTING_INDEX_G30, gc_state.position); break; + case NON_MODAL_SET_COORDINATE_OFFSET: + memcpy(gc_state.coord_offset, gc_block.values.xyz, sizeof(gc_block.values.xyz)); + system_flag_wco_change(); + break; + case NON_MODAL_RESET_COORDINATE_OFFSET: + clear_vector(gc_state.coord_offset); // Disable G92 offsets by zeroing offset vector. + system_flag_wco_change(); + break; + } + // [20. Motion modes ]: + // NOTE: Commands G10,G28,G30,G92 lock out and prevent axis words from use in motion modes. + // Enter motion modes only if there are axis words or a motion mode command word in the block. + gc_state.modal.motion = gc_block.modal.motion; + if (gc_state.modal.motion != MOTION_MODE_NONE) { + if (axis_command == AXIS_COMMAND_MOTION_MODE) { + uint8_t gc_update_pos = GC_UPDATE_POS_TARGET; + if (gc_state.modal.motion == MOTION_MODE_LINEAR) { + //mc_line(gc_block.values.xyz, pl_data); + mc_line_kins(gc_block.values.xyz, pl_data, gc_state.position); + } else if (gc_state.modal.motion == MOTION_MODE_SEEK) { + pl_data->condition |= PL_COND_FLAG_RAPID_MOTION; // Set rapid motion condition flag. + //mc_line(gc_block.values.xyz, pl_data); + mc_line_kins(gc_block.values.xyz, pl_data, gc_state.position); + } else if ((gc_state.modal.motion == MOTION_MODE_CW_ARC) || (gc_state.modal.motion == MOTION_MODE_CCW_ARC)) { + mc_arc(gc_block.values.xyz, + pl_data, + gc_state.position, + gc_block.values.ijk, + gc_block.values.r, + axis_0, + axis_1, + axis_linear, + bit_istrue(gc_parser_flags, GC_PARSER_ARC_IS_CLOCKWISE)); + } else { + // NOTE: gc_block.values.xyz is returned from mc_probe_cycle with the updated position value. So + // upon a successful probing cycle, the machine position and the returned value should be the same. +#ifndef ALLOW_FEED_OVERRIDE_DURING_PROBE_CYCLES + pl_data->condition |= PL_COND_FLAG_NO_FEED_OVERRIDE; +#endif + gc_update_pos = mc_probe_cycle(gc_block.values.xyz, pl_data, gc_parser_flags); + } + // As far as the parser is concerned, the position is now == target. In reality the + // motion control system might still be processing the action and the real tool position + // in any intermediate location. + if (gc_update_pos == GC_UPDATE_POS_TARGET) { + memcpy(gc_state.position, gc_block.values.xyz, sizeof(gc_block.values.xyz)); // gc_state.position[] = gc_block.values.xyz[] + } else if (gc_update_pos == GC_UPDATE_POS_SYSTEM) { + gc_sync_position(); // gc_state.position[] = sys_position + } // == GC_UPDATE_POS_NONE + } + } + // [21. Program flow ]: + // M0,M1,M2,M30: Perform non-running program flow actions. During a program pause, the buffer may + // refill and can only be resumed by the cycle start run-time command. + gc_state.modal.program_flow = gc_block.modal.program_flow; + if (gc_state.modal.program_flow) { + protocol_buffer_synchronize(); // Sync and finish all remaining buffered motions before moving on. + if (gc_state.modal.program_flow == PROGRAM_FLOW_PAUSED) { + if (sys.state != STATE_CHECK_MODE) { + system_set_exec_state_flag(EXEC_FEED_HOLD); // Use feed hold for program pause. + protocol_execute_realtime(); // Execute suspend. + } + } else { // == PROGRAM_FLOW_COMPLETED + // Upon program complete, only a subset of g-codes reset to certain defaults, according to + // LinuxCNC's program end descriptions and testing. Only modal groups [G-code 1,2,3,5,7,12] + // and [M-code 7,8,9] reset to [G1,G17,G90,G94,G40,G54,M5,M9,M48]. The remaining modal groups + // [G-code 4,6,8,10,13,14,15] and [M-code 4,5,6] and the modal words [F,S,T,H] do not reset. + gc_state.modal.motion = MOTION_MODE_LINEAR; + gc_state.modal.plane_select = PLANE_SELECT_XY; + gc_state.modal.distance = DISTANCE_MODE_ABSOLUTE; + gc_state.modal.feed_rate = FEED_RATE_MODE_UNITS_PER_MIN; + // gc_state.modal.cutter_comp = CUTTER_COMP_DISABLE; // Not supported. + gc_state.modal.coord_select = 0; // G54 + gc_state.modal.spindle = SPINDLE_DISABLE; + gc_state.modal.coolant = COOLANT_DISABLE; +#ifdef ENABLE_PARKING_OVERRIDE_CONTROL +# ifdef DEACTIVATE_PARKING_UPON_INIT + gc_state.modal.override = OVERRIDE_DISABLED; +# else + gc_state.modal.override = OVERRIDE_PARKING_MOTION; +# endif +#endif + // gc_state.modal.override = OVERRIDE_DISABLE; // Not supported. +#ifdef RESTORE_OVERRIDES_AFTER_PROGRAM_END + sys.f_override = DEFAULT_FEED_OVERRIDE; + sys.r_override = DEFAULT_RAPID_OVERRIDE; + sys.spindle_speed_ovr = DEFAULT_SPINDLE_SPEED_OVERRIDE; +#endif + // Execute coordinate change and spindle/coolant stop. + if (sys.state != STATE_CHECK_MODE) { + if (!(settings_read_coord_data(gc_state.modal.coord_select, gc_state.coord_system))) + FAIL(STATUS_SETTING_READ_FAIL); + system_flag_wco_change(); // Set to refresh immediately just in case something altered. + spindle->set_state(SPINDLE_DISABLE, 0); + coolant_set_state(COOLANT_DISABLE); + } + report_feedback_message(MESSAGE_PROGRAM_END); +#ifdef USE_M30 + user_m30(); +#endif + } + gc_state.modal.program_flow = PROGRAM_FLOW_RUNNING; // Reset program flow. + } + // TODO: % to denote start of program. + return (STATUS_OK); +} + +/* + Not supported: + + - Canned cycles + - Tool radius compensation + - A,B,C-axes + - Evaluation of expressions + - Variables + - Override control (TBD) + - Tool changes + - Switches + + (*) Indicates optional parameter, enabled through config.h and re-compile + group 0 = {G92.2, G92.3} (Non modal: Cancel and re-enable G92 offsets) + group 1 = {G81 - G89} (Motion modes: Canned cycles) + group 4 = {M1} (Optional stop, ignored) + group 6 = {M6} (Tool change) + group 7 = {G41, G42} cutter radius compensation (G40 is supported) + group 8 = {G43} tool length offset (G43.1/G49 are supported) + group 8 = {M7*} enable mist coolant (* Compile-option) + group 9 = {M48, M49} enable/disable feed and speed override switches + group 10 = {G98, G99} return mode canned cycles + group 13 = {G61.1, G64} path control mode (G61 is supported) +*/ diff --git a/Grbl_Esp32/src/GCode.h b/Grbl_Esp32/src/GCode.h new file mode 100644 index 00000000..70dbe62b --- /dev/null +++ b/Grbl_Esp32/src/GCode.h @@ -0,0 +1,261 @@ +#pragma once + +/* + GCode.h - rs274/ngc parser. + Part of Grbl + + Copyright (c) 2011-2015 Sungeun K. Jeon + Copyright (c) 2009-2011 Simen Svale Skogsrud + + 2018 - Bart Dring This file was modifed 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 . +*/ + +// Define modal group internal numbers for checking multiple command violations and tracking the +// type of command that is called in the block. A modal group is a group of g-code commands that are +// mutually exclusive, or cannot exist on the same line, because they each toggle a state or execute +// a unique motion. These are defined in the NIST RS274-NGC v3 g-code standard, available online, +// and are similar/identical to other g-code interpreters by manufacturers (Haas,Fanuc,Mazak,etc). +// NOTE: Modal group define values must be sequential and starting from zero. +#define MODAL_GROUP_G0 0 // [G4,G10,G28,G28.1,G30,G30.1,G53,G92,G92.1] Non-modal +#define MODAL_GROUP_G1 1 // [G0,G1,G2,G3,G38.2,G38.3,G38.4,G38.5,G80] Motion +#define MODAL_GROUP_G2 2 // [G17,G18,G19] Plane selection +#define MODAL_GROUP_G3 3 // [G90,G91] Distance mode +#define MODAL_GROUP_G4 4 // [G91.1] Arc IJK distance mode +#define MODAL_GROUP_G5 5 // [G93,G94] Feed rate mode +#define MODAL_GROUP_G6 6 // [G20,G21] Units +#define MODAL_GROUP_G7 7 // [G40] Cutter radius compensation mode. G41/42 NOT SUPPORTED. +#define MODAL_GROUP_G8 8 // [G43.1,G49] Tool length offset +#define MODAL_GROUP_G12 9 // [G54,G55,G56,G57,G58,G59] Coordinate system selection +#define MODAL_GROUP_G13 10 // [G61] Control mode + +#define MODAL_GROUP_M4 11 // [M0,M1,M2,M30] Stopping +#define MODAL_GROUP_M6 14 // [M6] Tool change +#define MODAL_GROUP_M7 12 // [M3,M4,M5] Spindle turning +#define MODAL_GROUP_M8 13 // [M7,M8,M9] Coolant control +#define MODAL_GROUP_M9 14 // [M56] Override control +#define MODAL_GROUP_M10 15 // [M62, M63] User Defined http://linuxcnc.org/docs/html/gcode/overview.html#_modal_groups + +// #define OTHER_INPUT_F 14 +// #define OTHER_INPUT_S 15 +// #define OTHER_INPUT_T 16 + +// Define command actions for within execution-type modal groups (motion, stopping, non-modal). Used +// internally by the parser to know which command to execute. +// NOTE: Some macro values are assigned specific values to make g-code state reporting and parsing +// compile a litte smaller. Necessary due to being completely out of flash on the 328p. Although not +// ideal, just be careful with values that state 'do not alter' and check both report.c and gcode.c +// to see how they are used, if you need to alter them. + +// Modal Group G0: Non-modal actions +#define NON_MODAL_NO_ACTION 0 // (Default: Must be zero) +#define NON_MODAL_DWELL 4 // G4 (Do not alter value) +#define NON_MODAL_SET_COORDINATE_DATA 10 // G10 (Do not alter value) +#define NON_MODAL_GO_HOME_0 28 // G28 (Do not alter value) +#define NON_MODAL_SET_HOME_0 38 // G28.1 (Do not alter value) +#define NON_MODAL_GO_HOME_1 30 // G30 (Do not alter value) +#define NON_MODAL_SET_HOME_1 40 // G30.1 (Do not alter value) +#define NON_MODAL_ABSOLUTE_OVERRIDE 53 // G53 (Do not alter value) +#define NON_MODAL_SET_COORDINATE_OFFSET 92 // G92 (Do not alter value) +#define NON_MODAL_RESET_COORDINATE_OFFSET 102 //G92.1 (Do not alter value) + +// Modal Group G1: Motion modes +#define MOTION_MODE_SEEK 0 // G0 (Default: Must be zero) +#define MOTION_MODE_LINEAR 1 // G1 (Do not alter value) +#define MOTION_MODE_CW_ARC 2 // G2 (Do not alter value) +#define MOTION_MODE_CCW_ARC 3 // G3 (Do not alter value) +#define MOTION_MODE_PROBE_TOWARD 140 // G38.2 (Do not alter value) +#define MOTION_MODE_PROBE_TOWARD_NO_ERROR 141 // G38.3 (Do not alter value) +#define MOTION_MODE_PROBE_AWAY 142 // G38.4 (Do not alter value) +#define MOTION_MODE_PROBE_AWAY_NO_ERROR 143 // G38.5 (Do not alter value) +#define MOTION_MODE_NONE 80 // G80 (Do not alter value) + +// Modal Group G2: Plane select +#define PLANE_SELECT_XY 0 // G17 (Default: Must be zero) +#define PLANE_SELECT_ZX 1 // G18 (Do not alter value) +#define PLANE_SELECT_YZ 2 // G19 (Do not alter value) + +// Modal Group G3: Distance mode +#define DISTANCE_MODE_ABSOLUTE 0 // G90 (Default: Must be zero) +#define DISTANCE_MODE_INCREMENTAL 1 // G91 (Do not alter value) + +// Modal Group G4: Arc IJK distance mode +#define DISTANCE_ARC_MODE_INCREMENTAL 0 // G91.1 (Default: Must be zero) + +// Modal Group M4: Program flow +#define PROGRAM_FLOW_RUNNING 0 // (Default: Must be zero) +#define PROGRAM_FLOW_PAUSED 3 // M0 +#define PROGRAM_FLOW_OPTIONAL_STOP 1 // M1 NOTE: Not supported, but valid and ignored. +#define PROGRAM_FLOW_COMPLETED_M2 2 // M2 (Do not alter value) +#define PROGRAM_FLOW_COMPLETED_M30 30 // M30 (Do not alter value) + +// Modal Group G5: Feed rate mode +#define FEED_RATE_MODE_UNITS_PER_MIN 0 // G94 (Default: Must be zero) +#define FEED_RATE_MODE_INVERSE_TIME 1 // G93 (Do not alter value) + +// Modal Group G6: Units mode +#define UNITS_MODE_MM 0 // G21 (Default: Must be zero) +#define UNITS_MODE_INCHES 1 // G20 (Do not alter value) + +// Modal Group G7: Cutter radius compensation mode +#define CUTTER_COMP_DISABLE 0 // G40 (Default: Must be zero) + +// Modal Group G13: Control mode +#define CONTROL_MODE_EXACT_PATH 0 // G61 (Default: Must be zero) + +// Modal Group M7: Spindle control +#define SPINDLE_DISABLE 0 // M5 (Default: Must be zero) +#define SPINDLE_ENABLE_CW PL_COND_FLAG_SPINDLE_CW // M3 (NOTE: Uses planner condition bit flag) +#define SPINDLE_ENABLE_CCW PL_COND_FLAG_SPINDLE_CCW // M4 (NOTE: Uses planner condition bit flag) + +// Modal Group M8: Coolant control +#define COOLANT_DISABLE 0 // M9 (Default: Must be zero) +#define COOLANT_FLOOD_ENABLE PL_COND_FLAG_COOLANT_FLOOD // M8 (NOTE: Uses planner condition bit flag) +#define COOLANT_MIST_ENABLE PL_COND_FLAG_COOLANT_MIST // M7 (NOTE: Uses planner condition bit flag) + +// Modal Group M9: Override control +#ifdef DEACTIVATE_PARKING_UPON_INIT +# define OVERRIDE_DISABLED 0 // (Default: Must be zero) +# define OVERRIDE_PARKING_MOTION 1 // M56 +#else +# define OVERRIDE_PARKING_MOTION 0 // M56 (Default: Must be zero) +# define OVERRIDE_DISABLED 1 // Parking disabled. +#endif + +// modal Group M10: User I/O control +#define NON_MODAL_IO_ENABLE 1 +#define NON_MODAL_IO_DISABLE 2 +#define MAX_USER_DIGITAL_PIN 4 + +// Modal Group G8: Tool length offset +#define TOOL_LENGTH_OFFSET_CANCEL 0 // G49 (Default: Must be zero) +#define TOOL_LENGTH_OFFSET_ENABLE_DYNAMIC 1 // G43.1 + +#define TOOL_CHANGE 1 + +// Modal Group G12: Active work coordinate system +// N/A: Stores coordinate system value (54-59) to change to. + +// Define parameter word mapping. +#define WORD_F 0 +#define WORD_I 1 +#define WORD_J 2 +#define WORD_K 3 +#define WORD_L 4 +#define WORD_N 5 +#define WORD_P 6 +#define WORD_R 7 +#define WORD_S 8 +#define WORD_T 9 +#define WORD_X 10 +#define WORD_Y 11 +#define WORD_Z 12 +#define WORD_A 13 +#define WORD_B 14 +#define WORD_C 15 + +// Define g-code parser position updating flags +#define GC_UPDATE_POS_TARGET 0 // Must be zero +#define GC_UPDATE_POS_SYSTEM 1 +#define GC_UPDATE_POS_NONE 2 + +// Define probe cycle exit states and assign proper position updating. +#define GC_PROBE_FOUND GC_UPDATE_POS_SYSTEM +#define GC_PROBE_ABORT GC_UPDATE_POS_NONE +#define GC_PROBE_FAIL_INIT GC_UPDATE_POS_NONE +#define GC_PROBE_FAIL_END GC_UPDATE_POS_TARGET +#ifdef SET_CHECK_MODE_PROBE_TO_START +# define GC_PROBE_CHECK_MODE GC_UPDATE_POS_NONE +#else +# define GC_PROBE_CHECK_MODE GC_UPDATE_POS_TARGET +#endif + +// Define gcode parser flags for handling special cases. +#define GC_PARSER_NONE 0 // Must be zero. +#define GC_PARSER_JOG_MOTION bit(0) +#define GC_PARSER_CHECK_MANTISSA bit(1) +#define GC_PARSER_ARC_IS_CLOCKWISE bit(2) +#define GC_PARSER_PROBE_IS_AWAY bit(3) +#define GC_PARSER_PROBE_IS_NO_ERROR bit(4) +#define GC_PARSER_LASER_FORCE_SYNC bit(5) +#define GC_PARSER_LASER_DISABLE bit(6) +#define GC_PARSER_LASER_ISMOTION bit(7) + +// NOTE: When this struct is zeroed, the above defines set the defaults for the system. +typedef struct { + uint8_t motion; // {G0,G1,G2,G3,G38.2,G80} + uint8_t feed_rate; // {G93,G94} + uint8_t units; // {G20,G21} + uint8_t distance; // {G90,G91} + // uint8_t distance_arc; // {G91.1} NOTE: Don't track. Only default supported. + uint8_t plane_select; // {G17,G18,G19} + // uint8_t cutter_comp; // {G40} NOTE: Don't track. Only default supported. + uint8_t tool_length; // {G43.1,G49} + uint8_t coord_select; // {G54,G55,G56,G57,G58,G59} + // uint8_t control; // {G61} NOTE: Don't track. Only default supported. + uint8_t program_flow; // {M0,M1,M2,M30} + uint8_t coolant; // {M7,M8,M9} + uint8_t spindle; // {M3,M4,M5} + uint8_t tool_change; // {M6} + uint8_t io_control; // {M62, M63} + uint8_t override; // {M56} +} gc_modal_t; + +typedef struct { + float f; // Feed + float ijk[N_AXIS]; // I,J,K Axis arc offsets + uint8_t l; // G10 or canned cycles parameters + int32_t n; // Line number + float p; // G10 or dwell parameters + // float q; // G82 peck drilling + float r; // Arc radius + float s; // Spindle speed + uint8_t t; // Tool selection + float xyz[N_AXIS]; // X,Y,Z Translational axes +} gc_values_t; + +typedef struct { + gc_modal_t modal; + + float spindle_speed; // RPM + float feed_rate; // Millimeters/min + uint8_t tool; // Tracks tool number. NOT USED. + int32_t line_number; // Last line number sent + + float position[N_AXIS]; // Where the interpreter considers the tool to be at this point in the code + + float coord_system[N_AXIS]; // Current work coordinate system (G54+). Stores offset from absolute machine + // position in mm. Loaded from EEPROM when called. + float coord_offset[N_AXIS]; // Retains the G92 coordinate offset (work coordinates) relative to + // machine zero in mm. Non-persistent. Cleared upon reset and boot. + float tool_length_offset; // Tracks tool length offset value when enabled. +} parser_state_t; +extern parser_state_t gc_state; + +typedef struct { + uint8_t non_modal_command; + gc_modal_t modal; + gc_values_t values; +} parser_block_t; + +// Initialize the parser +void gc_init(); + +// Execute one block of rs275/ngc/g-code +uint8_t gc_execute_line(char* line, uint8_t client); + +// Set g-code parser position. Input in steps. +void gc_sync_position(); diff --git a/Grbl_Esp32/grbl.h b/Grbl_Esp32/src/Grbl.h similarity index 63% rename from Grbl_Esp32/grbl.h rename to Grbl_Esp32/src/Grbl.h index 5741f337..25583e10 100644 --- a/Grbl_Esp32/grbl.h +++ b/Grbl_Esp32/src/Grbl.h @@ -1,5 +1,7 @@ +#pragma once + /* - grbl.h - Header for system level commands and real-time processes + Grbl.h - Header for system level commands and real-time processes Part of Grbl Copyright (c) 2014-2016 Sungeun K. Jeon for Gnea Research LLC @@ -18,12 +20,10 @@ along with Grbl. If not, see . */ -#pragma once // Grbl versioning system #define GRBL_VERSION "1.3a" -#define GRBL_VERSION_BUILD "20200727" - +#define GRBL_VERSION_BUILD "20200812" //#include #include @@ -33,72 +33,66 @@ #include #include -#include "driver/timer.h" +#include // Define the Grbl system include files. NOTE: Do not alter organization. -#include "config.h" -#include "nuts_bolts.h" -#include "tdef.h" +#include "Config.h" +#include "NutsBolts.h" -#include "defaults.h" -#include "settings.h" -#include "authentication.h" -#include "system.h" +#include "Defaults.h" +#include "SettingsStorage.h" +#include "WebUI/Authentication.h" +#include "WebUI/Commands.h" +#include "System.h" -#include "planner.h" -#include "coolant_control.h" -#include "grbl_eeprom.h" -#include "gcode.h" -#include "grbl_limits.h" -#include "motion_control.h" -#include "print.h" -#include "probe.h" -#include "protocol.h" -#include "report.h" -#include "serial.h" +#include "Planner.h" +#include "CoolantControl.h" +#include "Eeprom.h" +#include "GCode.h" +#include "Limits.h" +#include "MotionControl.h" +#include "Probe.h" +#include "Protocol.h" +#include "Report.h" +#include "Serial.h" #include "Pins.h" -#include "Spindles/SpindleClass.h" -#include "Motors/MotorClass.h" -#include "stepper.h" -#include "jog.h" -#include "inputbuffer.h" -#include "commands.h" -#include "SettingsClass.h" +#include "Spindles/Spindle.h" +#include "Motors/Motors.h" +#include "Stepper.h" +#include "Jog.h" +#include "WebUI/InputBuffer.h" +#include "Settings.h" #include "SettingsDefinitions.h" -#include "WebSettings.h" +#include "WebUI/WebSettings.h" // Do not guard this because it is needed for local files too -#include "grbl_sd.h" +#include "SDCard.h" #ifdef ENABLE_BLUETOOTH - #include "BTconfig.h" +# include "WebUI/BTConfig.h" #endif #ifdef ENABLE_WIFI - #include "wificonfig.h" - #ifdef ENABLE_HTTP - #include "serial2socket.h" - #endif - #ifdef ENABLE_TELNET - #include "telnet_server.h" - #endif - #ifdef ENABLE_NOTIFICATIONS - #include "notifications_service.h" - #endif +# include "WebUI/WifiConfig.h" +# ifdef ENABLE_HTTP +# include "WebUI/Serial2Socket.h" +# endif +# ifdef ENABLE_TELNET +# include "WebUI/TelnetServer.h" +# endif +# ifdef ENABLE_NOTIFICATIONS +# include "WebUI/NotificationsService.h" +# endif #endif -#include "solenoid_pen.h" +#include "SolenoidPen.h" #ifdef USE_SERVO_AXES - #include "servo_axis.h" -#endif - -#ifdef USE_UNIPOLAR - #include "grbl_unipolar.h" +# include "ServoAxis.h" #endif #ifdef USE_I2S_OUT - #include "i2s_out.h" +# include "I2SOut.h" #endif // Called if USE_MACHINE_INIT is defined diff --git a/Grbl_Esp32/src/I2SOut.cpp b/Grbl_Esp32/src/I2SOut.cpp new file mode 100644 index 00000000..a2fbe0fb --- /dev/null +++ b/Grbl_Esp32/src/I2SOut.cpp @@ -0,0 +1,914 @@ +/* + I2SOut.cpp + Part of Grbl_ESP32 + + Basic GPIO expander using the ESP32 I2S peripheral (output) + + 2020 - Michiyasu Odaki + + Grbl_ESP32 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_ESP32. If not, see . +*/ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] + * + * Based on Sprinter and grbl. + * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#include "Config.h" + +#ifdef USE_I2S_OUT + +# include +# include +# include +# include +# include + +# include + +# include "Pins.h" +# include "I2SOut.h" + +// +// Configrations for DMA connected I2S +// +// One DMA buffer transfer takes about 2 ms +// I2S_OUT_DMABUF_LEN / I2S_SAMPLE_SIZE x I2S_OUT_USEC_PER_PULSE +// = 2000 / 4 x 4 +// = 2000us = 2ms +// If I2S_OUT_DMABUF_COUNT is 5, it will take about 10 ms for all the DMA buffer transfers to finish. +// +// Increasing I2S_OUT_DMABUF_COUNT has the effect of preventing buffer underflow, +// but on the other hand, it leads to a delay with pulse and/or non-pulse-generated I/Os. +// The number of I2S_OUT_DMABUF_COUNT should be chosen carefully. +// +// Reference information: +// FreeRTOS task time slice = portTICK_PERIOD_MS = 1 ms (ESP32 FreeRTOS port) +// +# define I2S_SAMPLE_SIZE 4 /* 4 bytes, 32 bits per sample */ +# define DMA_SAMPLE_COUNT I2S_OUT_DMABUF_LEN / I2S_SAMPLE_SIZE /* number of samples per buffer */ +# define SAMPLE_SAFE_COUNT (20 / I2S_OUT_USEC_PER_PULSE) /* prevent buffer overrun (GRBL's $0 should be less than or equal 20) */ + +# ifdef USE_I2S_OUT_STREAM +typedef struct { + uint32_t** buffers; + uint32_t* current; + uint32_t rw_pos; + lldesc_t** desc; + xQueueHandle queue; +} i2s_out_dma_t; + +static i2s_out_dma_t o_dma; +static intr_handle_t i2s_out_isr_handle; +# endif + +// output value +static atomic_uint_least32_t i2s_out_port_data = ATOMIC_VAR_INIT(0); + +// inner lock +static portMUX_TYPE i2s_out_spinlock = portMUX_INITIALIZER_UNLOCKED; +# define I2S_OUT_ENTER_CRITICAL() portENTER_CRITICAL(&i2s_out_spinlock) +# define I2S_OUT_EXIT_CRITICAL() portEXIT_CRITICAL(&i2s_out_spinlock) +# define I2S_OUT_ENTER_CRITICAL_ISR() portENTER_CRITICAL_ISR(&i2s_out_spinlock) +# define I2S_OUT_EXIT_CRITICAL_ISR() portEXIT_CRITICAL_ISR(&i2s_out_spinlock) + +static int i2s_out_initialized = 0; + +# ifdef USE_I2S_OUT_STREAM +static volatile uint64_t i2s_out_pulse_period; +static uint64_t i2s_out_remain_time_until_next_pulse; // Time remaining until the next pulse (μsec) +static volatile i2s_out_pulse_func_t i2s_out_pulse_func; +# endif + +static uint8_t i2s_out_ws_pin = 255; +static uint8_t i2s_out_bck_pin = 255; +static uint8_t i2s_out_data_pin = 255; + +enum i2s_out_pulser_status_t { + PASSTHROUGH = 0, // Static I2S mode.The i2s_out_write() reflected with very little delay + STEPPING, // Streaming step data. + WAITING, // Waiting for the step DMA completion +}; +static volatile i2s_out_pulser_status_t i2s_out_pulser_status = PASSTHROUGH; + +// outer lock +static portMUX_TYPE i2s_out_pulser_spinlock = portMUX_INITIALIZER_UNLOCKED; +# define I2S_OUT_PULSER_ENTER_CRITICAL() portENTER_CRITICAL(&i2s_out_pulser_spinlock) +# define I2S_OUT_PULSER_EXIT_CRITICAL() portEXIT_CRITICAL(&i2s_out_pulser_spinlock) +# define I2S_OUT_PULSER_ENTER_CRITICAL_ISR() portENTER_CRITICAL_ISR(&i2s_out_pulser_spinlock) +# define I2S_OUT_PULSER_EXIT_CRITICAL_ISR() portEXIT_CRITICAL_ISR(&i2s_out_pulser_spinlock) + +// +// Internal functions +// +static inline void gpio_matrix_out_check(uint8_t gpio, uint32_t signal_idx, bool out_inv, bool oen_inv) { + //if pin == 255, do not need to configure + if (gpio != 255) { + PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[gpio], PIN_FUNC_GPIO); + gpio_set_direction((gpio_num_t)gpio, (gpio_mode_t)GPIO_MODE_DEF_OUTPUT); + gpio_matrix_out(gpio, signal_idx, out_inv, oen_inv); + } +} + +static inline void i2s_out_single_data() { +# if I2S_OUT_NUM_BITS == 16 + uint32_t port_data = atomic_load(&i2s_out_port_data); + port_data <<= 16; // Shift needed. This specification is not spelled out in the manual. + I2S0.conf_single_data = port_data; // Apply port data in real-time (static I2S) +# else + I2S0.conf_single_data = atomic_load(&i2s_out_port_data); // Apply port data in real-time (static I2S) +# endif +} + +static inline void i2s_out_reset_fifo_without_lock() { + I2S0.conf.rx_fifo_reset = 1; + I2S0.conf.rx_fifo_reset = 0; + I2S0.conf.tx_fifo_reset = 1; + I2S0.conf.tx_fifo_reset = 0; +} + +static void IRAM_ATTR i2s_out_reset_fifo() { + I2S_OUT_ENTER_CRITICAL(); + i2s_out_reset_fifo_without_lock(); + I2S_OUT_EXIT_CRITICAL(); +} + +# ifdef USE_I2S_OUT_STREAM +static int IRAM_ATTR i2s_clear_dma_buffer(lldesc_t* dma_desc, uint32_t port_data) { + uint32_t* buf = (uint32_t*)dma_desc->buf; + for (int i = 0; i < DMA_SAMPLE_COUNT; i++) { + buf[i] = port_data; + } + // Restore the buffer length. + // The length may have been changed short when the data was filled in to prevent buffer overrun. + dma_desc->length = I2S_OUT_DMABUF_LEN; + return 0; +} + +static int IRAM_ATTR i2s_clear_o_dma_buffers(uint32_t port_data) { + for (int buf_idx = 0; buf_idx < I2S_OUT_DMABUF_COUNT; buf_idx++) { + // Initialize DMA descriptor + o_dma.desc[buf_idx]->owner = 1; + o_dma.desc[buf_idx]->eof = 1; // set to 1 will trigger the interrupt + o_dma.desc[buf_idx]->sosf = 0; + o_dma.desc[buf_idx]->length = I2S_OUT_DMABUF_LEN; + o_dma.desc[buf_idx]->size = I2S_OUT_DMABUF_LEN; + o_dma.desc[buf_idx]->buf = (uint8_t*)o_dma.buffers[buf_idx]; + o_dma.desc[buf_idx]->offset = 0; + o_dma.desc[buf_idx]->qe.stqe_next = (lldesc_t*)((buf_idx < (I2S_OUT_DMABUF_COUNT - 1)) ? (o_dma.desc[buf_idx + 1]) : o_dma.desc[0]); + i2s_clear_dma_buffer(o_dma.desc[buf_idx], port_data); + } + return 0; +} +# endif + +static int IRAM_ATTR i2s_out_gpio_attach(uint8_t ws, uint8_t bck, uint8_t data) { + // Route the i2s pins to the appropriate GPIO + gpio_matrix_out_check(data, I2S0O_DATA_OUT23_IDX, 0, 0); + gpio_matrix_out_check(bck, I2S0O_BCK_OUT_IDX, 0, 0); + gpio_matrix_out_check(ws, I2S0O_WS_OUT_IDX, 0, 0); + return 0; +} + +# define I2S_OUT_DETACH_PORT_IDX 0x100 + +static int IRAM_ATTR i2s_out_gpio_detach(uint8_t ws, uint8_t bck, uint8_t data) { + // Route the i2s pins to the appropriate GPIO + gpio_matrix_out_check(ws, I2S_OUT_DETACH_PORT_IDX, 0, 0); + gpio_matrix_out_check(bck, I2S_OUT_DETACH_PORT_IDX, 0, 0); + gpio_matrix_out_check(data, I2S_OUT_DETACH_PORT_IDX, 0, 0); + return 0; +} + +static int IRAM_ATTR i2s_out_gpio_shiftout(uint32_t port_data) { + __digitalWrite(i2s_out_ws_pin, LOW); + for (int i = 0; i < I2S_OUT_NUM_BITS; i++) { + __digitalWrite(i2s_out_data_pin, !!(port_data & bit(I2S_OUT_NUM_BITS - 1 - i))); + __digitalWrite(i2s_out_bck_pin, HIGH); + __digitalWrite(i2s_out_bck_pin, LOW); + } + __digitalWrite(i2s_out_ws_pin, HIGH); // Latch + return 0; +} + +static int IRAM_ATTR i2s_out_stop() { + I2S_OUT_ENTER_CRITICAL(); +# ifdef USE_I2S_OUT_STREAM + // Stop FIFO DMA + I2S0.out_link.stop = 1; + + // Disconnect DMA from FIFO + I2S0.fifo_conf.dscr_en = 0; //Unset this bit to disable I2S DMA mode. (R/W) +# endif + // stop TX module + I2S0.conf.tx_start = 0; + + // Force WS to LOW before detach + // This operation prevents unintended WS edge trigger when detach + __digitalWrite(i2s_out_ws_pin, LOW); + + // Now, detach GPIO pin from I2S + i2s_out_gpio_detach(i2s_out_ws_pin, i2s_out_bck_pin, i2s_out_data_pin); + + // Force BCK to LOW + // After the TX module is stopped, BCK always seems to be in LOW. + // However, I'm going to do it manually to ensure the BCK's LOW. + __digitalWrite(i2s_out_bck_pin, LOW); + + // Transmit recovery data to 74HC595 + uint32_t port_data = atomic_load(&i2s_out_port_data); // current expanded port value + i2s_out_gpio_shiftout(port_data); + +# ifdef USE_I2S_OUT_STREAM + //clear pending interrupt + I2S0.int_clr.val = I2S0.int_st.val; +# endif + I2S_OUT_EXIT_CRITICAL(); + return 0; +} + +static int IRAM_ATTR i2s_out_start() { + if (!i2s_out_initialized) { + return -1; + } + + I2S_OUT_ENTER_CRITICAL(); + // Transmit recovery data to 74HC595 + uint32_t port_data = atomic_load(&i2s_out_port_data); // current expanded port value + i2s_out_gpio_shiftout(port_data); + + // Attach I2S to specified GPIO pin + i2s_out_gpio_attach(i2s_out_ws_pin, i2s_out_bck_pin, i2s_out_data_pin); + //start DMA link + i2s_out_reset_fifo_without_lock(); + +# ifdef USE_I2S_OUT_STREAM + if (i2s_out_pulser_status == PASSTHROUGH) { + I2S0.conf_chan.tx_chan_mod = 3; // 3:right+constant 4:left+constant (when tx_msb_right = 1) + I2S0.conf_single_data = port_data; + } else { + I2S0.conf_chan.tx_chan_mod = 4; // 3:right+constant 4:left+constant (when tx_msb_right = 1) + I2S0.conf_single_data = 0; + } +# endif + +# ifdef USE_I2S_OUT_STREAM + //reset DMA + I2S0.lc_conf.in_rst = 1; + I2S0.lc_conf.in_rst = 0; + I2S0.lc_conf.out_rst = 1; + I2S0.lc_conf.out_rst = 0; + + I2S0.out_link.addr = (uint32_t)o_dma.desc[0]; +# endif + + I2S0.conf.tx_reset = 1; + I2S0.conf.tx_reset = 0; + I2S0.conf.rx_reset = 1; + I2S0.conf.rx_reset = 0; + + I2S0.conf1.tx_stop_en = 1; // BCK and WCK are suppressed while FIFO is empty + +# ifdef USE_I2S_OUT_STREAM + // Connect DMA to FIFO + I2S0.fifo_conf.dscr_en = 1; // Set this bit to enable I2S DMA mode. (R/W) + + I2S0.int_clr.val = 0xFFFFFFFF; + I2S0.out_link.start = 1; +# endif + I2S0.conf.tx_start = 1; + // Wait for the first FIFO data to prevent the unintentional generation of 0 data + ets_delay_us(20); + I2S0.conf1.tx_stop_en = 0; // BCK and WCK are generated regardless of the FIFO status + + I2S_OUT_EXIT_CRITICAL(); + + return 0; +} + +# ifdef USE_I2S_OUT_STREAM + +static int IRAM_ATTR i2s_fillout_dma_buffer(lldesc_t* dma_desc) { + uint32_t* buf = (uint32_t*)dma_desc->buf; + o_dma.rw_pos = 0; + // It reuses the oldest (just transferred) buffer with the name "current" + // and fills the buffer for later DMA. + I2S_OUT_PULSER_ENTER_CRITICAL(); // Lock pulser status + if (i2s_out_pulser_status == STEPPING) { + // + // Fillout the buffer for pulse + // + // To avoid buffer overflow, all of the maximum pulse width (normaly about 10us) + // is adjusted to be in a single buffer. + // DMA_SAMPLE_SAFE_COUNT is referred to as the margin value. + // Therefore, if a buffer is close to full and it is time to generate a pulse, + // the generation of the buffer is interrupted (the buffer length is shortened slightly) + // and the pulse generation is postponed until the next buffer is filled. + // + o_dma.rw_pos = 0; + while (o_dma.rw_pos < (DMA_SAMPLE_COUNT - SAMPLE_SAFE_COUNT)) { + // no data to read (buffer empty) + if (i2s_out_remain_time_until_next_pulse < I2S_OUT_USEC_PER_PULSE) { + // pulser status may change in pulse phase func, so I need to check it every time. + if (i2s_out_pulser_status == STEPPING) { + // fillout future DMA buffer (tail of the DMA buffer chains) + if (i2s_out_pulse_func != NULL) { + uint32_t old_rw_pos = o_dma.rw_pos; + I2S_OUT_PULSER_EXIT_CRITICAL(); // Temporarily unlocked status lock as it may be locked in pulse callback. + (*i2s_out_pulse_func)(); // should be pushed into buffer max DMA_SAMPLE_SAFE_COUNT + I2S_OUT_PULSER_ENTER_CRITICAL(); // Lock again. + // Calculate pulse period. About magic number 2, refer to the st_wake_up(). (Ad hoc delay value) + i2s_out_remain_time_until_next_pulse = i2s_out_pulse_period - I2S_OUT_USEC_PER_PULSE * (o_dma.rw_pos - old_rw_pos) + 2; + if (i2s_out_pulser_status == WAITING) { + // i2s_out_set_passthrough() has called from the pulse function. + // It needs to go into pass-through mode. + // This DMA descriptor must be a tail of the chain. + dma_desc->qe.stqe_next = NULL; // Cut the DMA descriptor ring. This allow us to identify the tail of the buffer. + } else if (i2s_out_pulser_status == PASSTHROUGH) { + // i2s_out_reset() has called during the execution of the pulse function. + // I2S has already in static mode, and buffers has cleared to zero. + // To prevent the pulse function from being called back, + // we assume that the buffer is already full. + i2s_out_remain_time_until_next_pulse = 0; // There is no need to fill the current buffer. + o_dma.rw_pos = DMA_SAMPLE_COUNT; // The buffer is full. + break; + } + continue; + } + } + } + // no pulse data in push buffer (pulse off or idle or callback is not defined) + buf[o_dma.rw_pos++] = atomic_load(&i2s_out_port_data); + if (i2s_out_remain_time_until_next_pulse >= I2S_OUT_USEC_PER_PULSE) { + i2s_out_remain_time_until_next_pulse -= I2S_OUT_USEC_PER_PULSE; + } else { + i2s_out_remain_time_until_next_pulse = 0; + } + } + // set filled length to the DMA descriptor + dma_desc->length = o_dma.rw_pos * I2S_SAMPLE_SIZE; + } else if (i2s_out_pulser_status == WAITING) { + i2s_clear_dma_buffer(dma_desc, 0); // Essentially, no clearing is required. I'll make sure I know when I've written something. + o_dma.rw_pos = 0; // If someone calls i2s_out_push_sample, make sure there is no buffer overflow + dma_desc->qe.stqe_next = NULL; // Cut the DMA descriptor ring. This allow us to identify the tail of the buffer. + } else { + // Stepper paused (passthrough state, static I2S control mode) + // In the passthrough mode, there is no need to fill the buffer with port_data. + i2s_clear_dma_buffer(dma_desc, 0); // Essentially, no clearing is required. I'll make sure I know when I've written something. + o_dma.rw_pos = 0; // If someone calls i2s_out_push_sample, make sure there is no buffer overflow + } + I2S_OUT_PULSER_EXIT_CRITICAL(); // Unlock pulser status + + return 0; +} + +// +// I2S out DMA Interrupts handler +// +static void IRAM_ATTR i2s_out_intr_handler(void* arg) { + lldesc_t* finish_desc; + portBASE_TYPE high_priority_task_awoken = pdFALSE; + + if (I2S0.int_st.out_eof || I2S0.int_st.out_total_eof) { + if (I2S0.int_st.out_total_eof) { + // This is tail of the DMA descriptors + I2S_OUT_ENTER_CRITICAL_ISR(); + // Stop FIFO DMA + I2S0.out_link.stop = 1; + // Disconnect DMA from FIFO + I2S0.fifo_conf.dscr_en = 0; //Unset this bit to disable I2S DMA mode. (R/W) + // Stop TX module + I2S0.conf.tx_start = 0; + I2S_OUT_EXIT_CRITICAL_ISR(); + } + // Get the descriptor of the last item in the linkedlist + finish_desc = (lldesc_t*)I2S0.out_eof_des_addr; + + // If the queue is full it's because we have an underflow, + // more than buf_count isr without new data, remove the front buffer + if (xQueueIsQueueFullFromISR(o_dma.queue)) { + lldesc_t* front_desc; + // Remove a descriptor from the DMA complete event queue + xQueueReceiveFromISR(o_dma.queue, &front_desc, &high_priority_task_awoken); + I2S_OUT_PULSER_ENTER_CRITICAL_ISR(); + uint32_t port_data = 0; + if (i2s_out_pulser_status == STEPPING) { + port_data = atomic_load(&i2s_out_port_data); + } + I2S_OUT_PULSER_EXIT_CRITICAL_ISR(); + for (int i = 0; i < DMA_SAMPLE_COUNT; i++) { + front_desc->buf[i] = port_data; + } + front_desc->length = I2S_OUT_DMABUF_LEN; + } + + // Send a DMA complete event to the I2S bitstreamer task with finished buffer + xQueueSendFromISR(o_dma.queue, &finish_desc, &high_priority_task_awoken); + } + + if (high_priority_task_awoken == pdTRUE) + portYIELD_FROM_ISR(); + + // clear interrupt + I2S0.int_clr.val = I2S0.int_st.val; //clear pending interrupt +} + +// +// I2S bitstream generator task +// +static void IRAM_ATTR i2sOutTask(void* parameter) { + lldesc_t* dma_desc; + while (1) { + // Wait a DMA complete event from I2S isr + // (Block until a DMA transfer has complete) + xQueueReceive(o_dma.queue, &dma_desc, portMAX_DELAY); + o_dma.current = (uint32_t*)(dma_desc->buf); + // It reuses the oldest (just transferred) buffer with the name "current" + // and fills the buffer for later DMA. + I2S_OUT_PULSER_ENTER_CRITICAL(); // Lock pulser status + if (i2s_out_pulser_status == STEPPING) { + // + // Fillout the buffer for pulse + // + // To avoid buffer overflow, all of the maximum pulse width (normaly about 10us) + // is adjusted to be in a single buffer. + // DMA_SAMPLE_SAFE_COUNT is referred to as the margin value. + // Therefore, if a buffer is close to full and it is time to generate a pulse, + // the generation of the buffer is interrupted (the buffer length is shortened slightly) + // and the pulse generation is postponed until the next buffer is filled. + // + i2s_fillout_dma_buffer(dma_desc); + dma_desc->length = o_dma.rw_pos * I2S_SAMPLE_SIZE; + } else if (i2s_out_pulser_status == WAITING) { + if (dma_desc->qe.stqe_next == NULL) { + // Tail of the DMA descriptor found + // I2S TX module has alrewdy stopped by ISR + i2s_out_stop(); + i2s_clear_o_dma_buffers(0); // 0 for static I2S control mode (right ch. data is always 0) + // You need to set the status before calling i2s_out_start() + // because the process in i2s_out_start() is different depending on the status. + i2s_out_pulser_status = PASSTHROUGH; + i2s_out_start(); + } else { + // Processing a buffer slightly ahead of the tail buffer. + // We don't need to fill up the buffer by port_data any more. + i2s_clear_dma_buffer(dma_desc, 0); // Essentially, no clearing is required. I'll make sure I know when I've written something. + o_dma.rw_pos = 0; // If someone calls i2s_out_push_sample, make sure there is no buffer overflow + dma_desc->qe.stqe_next = NULL; // Cut the DMA descriptor ring. This allow us to identify the tail of the buffer. + } + } else { + // Stepper paused (passthrough state, static I2S control mode) + // In the passthrough mode, there is no need to fill the buffer with port_data. + i2s_clear_dma_buffer(dma_desc, 0); // Essentially, no clearing is required. I'll make sure I know when I've written something. + o_dma.rw_pos = 0; // If someone calls i2s_out_push_sample, make sure there is no buffer overflow + } + I2S_OUT_PULSER_EXIT_CRITICAL(); // Unlock pulser status + } +} +# endif + +// +// External funtions +// +void IRAM_ATTR i2s_out_delay() { +# ifdef USE_I2S_OUT_STREAM + I2S_OUT_PULSER_ENTER_CRITICAL(); + if (i2s_out_pulser_status == PASSTHROUGH) { + // Depending on the timing, it may not be reflected immediately, + // so wait twice as long just in case. + ets_delay_us(I2S_OUT_USEC_PER_PULSE * 2); + } else { + // Just wait until the data now registered in the DMA descripter + // is reflected in the I2S TX module via FIFO. + delay(I2S_OUT_DELAY_MS); + } + I2S_OUT_PULSER_EXIT_CRITICAL(); +# else + ets_delay_us(I2S_OUT_USEC_PER_PULSE * 2); +# endif +} + +void IRAM_ATTR i2s_out_write(uint8_t pin, uint8_t val) { + uint32_t bit = bit(pin); + if (val) { + atomic_fetch_or(&i2s_out_port_data, bit); + } else { + atomic_fetch_and(&i2s_out_port_data, ~bit); + } +# ifdef USE_I2S_OUT_STREAM + // It needs a lock for access, but I've given up because I need speed. + // This is not a problem as long as there is no overlap between the status change and digitalWrite(). + if (i2s_out_pulser_status == PASSTHROUGH) { + i2s_out_single_data(); + } +# else + i2s_out_single_data(); +# endif +} + +uint8_t IRAM_ATTR i2s_out_state(uint8_t pin) { + uint32_t port_data = atomic_load(&i2s_out_port_data); + return (!!(port_data & bit(pin))); +} + +uint32_t IRAM_ATTR i2s_out_push_sample(uint32_t num) { +# ifdef USE_I2S_OUT_STREAM + if (num > SAMPLE_SAFE_COUNT) { + return 0; + } + // push at least one sample (even if num is zero) + uint32_t port_data = atomic_load(&i2s_out_port_data); + uint32_t n = 0; + do { + o_dma.current[o_dma.rw_pos++] = port_data; + n++; + } while (n < num); + return n; +# else + return 0; +# endif +} + +int IRAM_ATTR i2s_out_set_passthrough() { + I2S_OUT_PULSER_ENTER_CRITICAL(); +# ifdef USE_I2S_OUT_STREAM + if (i2s_out_pulser_status == STEPPING) { + i2s_out_pulser_status = WAITING; // Start stopping the pulser + } +# else + i2s_out_pulser_status = PASSTHROUGH; +# endif + I2S_OUT_PULSER_EXIT_CRITICAL(); + return 0; +} + +int IRAM_ATTR i2s_out_set_stepping() { + I2S_OUT_PULSER_ENTER_CRITICAL(); +# ifdef USE_I2S_OUT_STREAM + if (i2s_out_pulser_status == STEPPING) { + // Re-entered (fail safe) + I2S_OUT_PULSER_EXIT_CRITICAL(); + return 0; + } + + if (i2s_out_pulser_status == WAITING) { + // Wait for complete DMAs + for (;;) { + I2S_OUT_PULSER_EXIT_CRITICAL(); + delay(I2S_OUT_DELAY_DMABUF_MS); + I2S_OUT_PULSER_ENTER_CRITICAL(); + if (i2s_out_pulser_status == WAITING) { + continue; + } + if (i2s_out_pulser_status == PASSTHROUGH) { + // DMA completed + break; + } + // Another function change the I2S state to STEPPING + I2S_OUT_PULSER_EXIT_CRITICAL(); + return 0; + } + } + + // Change I2S state from PASSTHROUGH to STEPPING + i2s_out_stop(); + uint32_t port_data = atomic_load(&i2s_out_port_data); + i2s_clear_o_dma_buffers(port_data); + + // You need to set the status before calling i2s_out_start() + // because the process in i2s_out_start() is different depending on the status. + i2s_out_pulser_status = STEPPING; + i2s_out_start(); +# else + i2s_out_pulser_status = STEPPING; +# endif + I2S_OUT_PULSER_EXIT_CRITICAL(); + return 0; +} + +int IRAM_ATTR i2s_out_set_pulse_period(uint64_t period) { +# ifdef USE_I2S_OUT_STREAM + // Use 64-bit values to avoid overflowing during the calculation. + i2s_out_pulse_period = period * 1000000 / F_STEPPER_TIMER; +# endif + return 0; +} + +int IRAM_ATTR i2s_out_set_pulse_callback(i2s_out_pulse_func_t func) { +# ifdef USE_I2S_OUT_STREAM + i2s_out_pulse_func = func; +# endif + return 0; +} + +int IRAM_ATTR i2s_out_reset() { + I2S_OUT_PULSER_ENTER_CRITICAL(); + i2s_out_stop(); +# ifdef USE_I2S_OUT_STREAM + if (i2s_out_pulser_status == STEPPING) { + uint32_t port_data = atomic_load(&i2s_out_port_data); + i2s_clear_o_dma_buffers(port_data); + } else if (i2s_out_pulser_status == WAITING) { + i2s_clear_o_dma_buffers(0); + i2s_out_pulser_status = PASSTHROUGH; + } +# endif + // You need to set the status before calling i2s_out_start() + // because the process in i2s_out_start() is different depending on the status. + i2s_out_start(); + I2S_OUT_PULSER_EXIT_CRITICAL(); + return 0; +} + +// +// Initialize funtion (external function) +// +int IRAM_ATTR i2s_out_init(i2s_out_init_t& init_param) { + if (i2s_out_initialized) { + // already initialized + return -1; + } + + atomic_store(&i2s_out_port_data, init_param.init_val); + + // To make sure hardware is enabled before any hardware register operations. + periph_module_reset(PERIPH_I2S0_MODULE); + periph_module_enable(PERIPH_I2S0_MODULE); + + // Route the i2s pins to the appropriate GPIO + i2s_out_gpio_attach(init_param.ws_pin, init_param.bck_pin, init_param.data_pin); + + /** + * Each i2s transfer will take + * fpll = PLL_D2_CLK -- clka_en = 0 + * + * fi2s = fpll / N + b/a -- N + b/a = clkm_div_num + * fi2s = 160MHz / 2 + * fi2s = 80MHz + * + * fbclk = fi2s / M -- M = tx_bck_div_num + * fbclk = 80MHz / 2 + * fbclk = 40MHz + * + * fwclk = fbclk / 32 + * + * for fwclk = 250kHz(16-bit: 4µS pulse time), 125kHz(32-bit: 8μS pulse time) + * N = 10, b/a = 0 + * M = 2 + * for fwclk = 500kHz(16-bit: 2µS pulse time), 250kHz(32-bit: 4μS pulse time) + * N = 5, b/a = 0 + * M = 2 + * for fwclk = 1000kHz(16-bit: 1µS pulse time), 500kHz(32-bit: 2μS pulse time) + * N = 2, b/a = 2/1 (N + b/a = 2.5) + * M = 2 + */ + +# ifdef USE_I2S_OUT_STREAM + // Allocate the array of pointers to the buffers + o_dma.buffers = (uint32_t**)malloc(sizeof(uint32_t*) * I2S_OUT_DMABUF_COUNT); + if (o_dma.buffers == nullptr) + return -1; + + // Allocate each buffer that can be used by the DMA controller + for (int buf_idx = 0; buf_idx < I2S_OUT_DMABUF_COUNT; buf_idx++) { + o_dma.buffers[buf_idx] = (uint32_t*)heap_caps_calloc(1, I2S_OUT_DMABUF_LEN, MALLOC_CAP_DMA); + if (o_dma.buffers[buf_idx] == nullptr) + return -1; + } + + // Allocate the array of DMA descriptors + o_dma.desc = (lldesc_t**)malloc(sizeof(lldesc_t*) * I2S_OUT_DMABUF_COUNT); + if (o_dma.desc == nullptr) + return -1; + + // Allocate each DMA descriptor that will be used by the DMA controller + for (int buf_idx = 0; buf_idx < I2S_OUT_DMABUF_COUNT; buf_idx++) { + o_dma.desc[buf_idx] = (lldesc_t*)heap_caps_malloc(sizeof(lldesc_t), MALLOC_CAP_DMA); + if (o_dma.desc[buf_idx] == nullptr) + return -1; + } + + // Initialize + i2s_clear_o_dma_buffers(init_param.init_val); + o_dma.rw_pos = 0; + o_dma.current = NULL; + o_dma.queue = xQueueCreate(I2S_OUT_DMABUF_COUNT, sizeof(uint32_t*)); + + // Set the first DMA descriptor + I2S0.out_link.addr = (uint32_t)o_dma.desc[0]; +# endif + + // stop i2s + I2S0.out_link.stop = 1; + I2S0.conf.tx_start = 0; + + I2S0.int_clr.val = I2S0.int_st.val; //clear pending interrupt + + // + // i2s_param_config + // + + // configure I2S data port interface. + i2s_out_reset_fifo(); + + //reset i2s + I2S0.conf.tx_reset = 1; + I2S0.conf.tx_reset = 0; + I2S0.conf.rx_reset = 1; + I2S0.conf.rx_reset = 0; + + //reset dma + I2S0.lc_conf.in_rst = 1; // Set this bit to reset in DMA FSM. (R/W) + I2S0.lc_conf.in_rst = 0; + I2S0.lc_conf.out_rst = 1; // Set this bit to reset out DMA FSM. (R/W) + I2S0.lc_conf.out_rst = 0; + + //Enable and configure DMA + I2S0.lc_conf.check_owner = 0; + I2S0.lc_conf.out_loop_test = 0; + I2S0.lc_conf.out_auto_wrback = 0; // Disable auto outlink-writeback when all the data has been transmitted + I2S0.lc_conf.out_data_burst_en = 0; + I2S0.lc_conf.outdscr_burst_en = 0; + I2S0.lc_conf.out_no_restart_clr = 0; + I2S0.lc_conf.indscr_burst_en = 0; +# ifdef USE_I2S_OUT_STREAM + I2S0.lc_conf.out_eof_mode = 1; // I2S_OUT_EOF_INT generated when DMA has popped all data from the FIFO; +# endif + I2S0.conf2.lcd_en = 0; + I2S0.conf2.camera_en = 0; + I2S0.pdm_conf.pcm2pdm_conv_en = 0; + I2S0.pdm_conf.pdm2pcm_conv_en = 0; + + I2S0.fifo_conf.dscr_en = 0; + +# ifdef USE_I2S_OUT_STREAM + if (i2s_out_pulser_status == STEPPING) { + // Stream output mode + I2S0.conf_chan.tx_chan_mod = 4; // 3:right+constant 4:left+constant (when tx_msb_right = 1) + I2S0.conf_single_data = 0; + } else { + // Static output mode + I2S0.conf_chan.tx_chan_mod = 3; // 3:right+constant 4:left+constant (when tx_msb_right = 1) + I2S0.conf_single_data = init_param.init_val; + } +# else + // For the static output mode + I2S0.conf_chan.tx_chan_mod = 3; // 3:right+constant 4:left+constant (when tx_msb_right = 1) + I2S0.conf_single_data = init_param.init_val; // initial constant value +# endif +# if I2S_OUT_NUM_BITS == 16 + I2S0.fifo_conf.tx_fifo_mod = 0; // 0: 16-bit dual channel data, 3: 32-bit single channel data + I2S0.fifo_conf.rx_fifo_mod = 0; // 0: 16-bit dual channel data, 3: 32-bit single channel data + I2S0.sample_rate_conf.tx_bits_mod = 16; // default is 16-bits + I2S0.sample_rate_conf.rx_bits_mod = 16; // default is 16-bits +# else + I2S0.fifo_conf.tx_fifo_mod = 3; // 0: 16-bit dual channel data, 3: 32-bit single channel data + I2S0.fifo_conf.rx_fifo_mod = 3; // 0: 16-bit dual channel data, 3: 32-bit single channel data + // Data width is 32-bit. Forgetting this setting will result in a 16-bit transfer. + I2S0.sample_rate_conf.tx_bits_mod = 32; + I2S0.sample_rate_conf.rx_bits_mod = 32; +# endif + I2S0.conf.tx_mono = 0; // Set this bit to enable transmitter’s mono mode in PCM standard mode. + + I2S0.conf_chan.rx_chan_mod = 1; // 1: right+right + I2S0.conf.rx_mono = 0; + +# ifdef USE_I2S_OUT_STREAM + I2S0.fifo_conf.dscr_en = 1; //connect DMA to fifo +# endif + I2S0.conf.tx_start = 0; + I2S0.conf.rx_start = 0; + + I2S0.conf.tx_msb_right = 1; // Set this bit to place right-channel data at the MSB in the transmit FIFO. + I2S0.conf.tx_right_first = 0; // Setting this bit allows the right-channel data to be sent first. + + I2S0.conf.tx_slave_mod = 0; // Master +# ifdef USE_I2S_OUT_STREAM + I2S0.fifo_conf.tx_fifo_mod_force_en = 1; //The bit should always be set to 1. +# endif + I2S0.pdm_conf.rx_pdm_en = 0; // Set this bit to enable receiver’s PDM mode. + I2S0.pdm_conf.tx_pdm_en = 0; // Set this bit to enable transmitter’s PDM mode. + + // I2S_COMM_FORMAT_I2S_LSB + I2S0.conf.tx_short_sync = 0; // Set this bit to enable transmitter in PCM standard mode. + I2S0.conf.rx_short_sync = 0; // Set this bit to enable receiver in PCM standard mode. + I2S0.conf.tx_msb_shift = 0; // Do not use the Philips standard to avoid bit-shifting + I2S0.conf.rx_msb_shift = 0; // Do not use the Philips standard to avoid bit-shifting + + // + // i2s_set_clk + // + + // set clock (fi2s) 160MHz / 5 + I2S0.clkm_conf.clka_en = 0; // Use 160 MHz PLL_D2_CLK as reference + // N + b/a = 0 +# if I2S_OUT_NUM_BITS == 16 + // N = 10 + I2S0.clkm_conf.clkm_div_num = 10; // minimum value of 2, reset value of 4, max 256 (I²S clock divider’s integral value) +# else + // N = 5 + I2S0.clkm_conf.clkm_div_num = 5; // minimum value of 2, reset value of 4, max 256 (I²S clock divider’s integral value) +# endif + // b/a = 0 + I2S0.clkm_conf.clkm_div_b = 0; // 0 at reset + I2S0.clkm_conf.clkm_div_a = 0; // 0 at reset, what about divide by 0? (not an issue) + + // Bit clock configuration bit in transmitter mode. + // fbck = fi2s / tx_bck_div_num = (160 MHz / 5) / 2 = 16 MHz + I2S0.sample_rate_conf.tx_bck_div_num = 2; // minimum value of 2 defaults to 6 + I2S0.sample_rate_conf.rx_bck_div_num = 2; + +# ifdef USE_I2S_OUT_STREAM + // Enable TX interrupts (DMA Interrupts) + I2S0.int_ena.out_eof = 1; // Triggered when rxlink has finished sending a packet. + I2S0.int_ena.out_dscr_err = 0; // Triggered when invalid rxlink descriptors are encountered. + I2S0.int_ena.out_total_eof = 1; // Triggered when all transmitting linked lists are used up. + I2S0.int_ena.out_done = 0; // Triggered when all transmitted and buffered data have been read. + + // default pulse callback period (μsec) + i2s_out_pulse_period = init_param.pulse_period; + i2s_out_pulse_func = init_param.pulse_func; + + // Create the task that will feed the buffer + xTaskCreatePinnedToCore(i2sOutTask, + "I2SOutTask", + 1024 * 10, + NULL, + 1, + nullptr, + CONFIG_ARDUINO_RUNNING_CORE // must run the task on same core + ); + + // Allocate and Enable the I2S interrupt + esp_intr_alloc(ETS_I2S0_INTR_SOURCE, 0, i2s_out_intr_handler, nullptr, &i2s_out_isr_handle); + esp_intr_enable(i2s_out_isr_handle); +# endif + + // Remember GPIO pin numbers + i2s_out_ws_pin = init_param.ws_pin; + i2s_out_bck_pin = init_param.bck_pin; + i2s_out_data_pin = init_param.data_pin; + i2s_out_initialized = 1; + + // Start the I2S peripheral + i2s_out_start(); + + return 0; +} + +# ifndef I2S_OUT_WS +# define I2S_OUT_WS GPIO_NUM_17 +# endif +# ifndef I2S_OUT_BCK +# define I2S_OUT_BCK GPIO_NUM_22 +# endif +# ifndef I2S_OUT_DATA +# define I2S_OUT_DATA GPIO_NUM_21 +# endif +# ifndef I2S_OUT_INIT_VAL +# define I2S_OUT_INIT_VAL 0 +# endif +/* + Initialize I2S out by default parameters. + + return -1 ... already initialized +*/ +int IRAM_ATTR i2s_out_init() { + i2s_out_init_t default_param = { + .ws_pin = I2S_OUT_WS, + .bck_pin = I2S_OUT_BCK, + .data_pin = I2S_OUT_DATA, + .pulse_func = NULL, + .pulse_period = I2S_OUT_USEC_PER_PULSE, + .init_val = I2S_OUT_INIT_VAL, + }; + return i2s_out_init(default_param); +} + +#endif diff --git a/Grbl_Esp32/i2s_out.h b/Grbl_Esp32/src/I2SOut.h similarity index 80% rename from Grbl_Esp32/i2s_out.h rename to Grbl_Esp32/src/I2SOut.h index 4e88ac89..2f9e1246 100644 --- a/Grbl_Esp32/i2s_out.h +++ b/Grbl_Esp32/src/I2SOut.h @@ -1,5 +1,7 @@ +#pragma once + /* - i2s_out.h + I2SOut.h Part of Grbl_ESP32 Header for basic GPIO expander using the ESP32 I2S peripheral 2020 - Michiyasu Odaki @@ -35,44 +37,42 @@ * along with this program. If not, see . * */ -#ifndef i2s_out_h -#define i2s_out_h // It should be included at the outset to know the machine configuration. -#include "config.h" +#include "Config.h" // If USE_I2S_OUT_STREAM is defined // but the prerequisite USE_I2S_OUT is not defined, // it is forced to be defined. #ifdef USE_I2S_OUT_STREAM - #ifndef USE_I2S_OUT - #define USE_I2S_OUT - #endif +# ifndef USE_I2S_OUT +# define USE_I2S_OUT +# endif #endif #ifdef USE_I2S_OUT -#include +# include /* Assert */ -#if defined(I2S_OUT_NUM_BITS) - #if (I2S_OUT_NUM_BITS != 16) && (I2S_OUT_NUM_BITS != 32) - #error "I2S_OUT_NUM_BITS should be 16 or 32" - #endif -#else - #define I2S_OUT_NUM_BITS 32 -#endif +# if defined(I2S_OUT_NUM_BITS) +# if (I2S_OUT_NUM_BITS != 16) && (I2S_OUT_NUM_BITS != 32) +# error "I2S_OUT_NUM_BITS should be 16 or 32" +# endif +# else +# define I2S_OUT_NUM_BITS 32 +# endif -#define I2SO(n) (I2S_OUT_PIN_BASE + n) +# define I2SO(n) (I2S_OUT_PIN_BASE + n) /* 16-bit mode: 1000000 usec / ((160000000 Hz) / 10 / 2) x 16 bit/pulse x 2(stereo) = 4 usec/pulse */ /* 32-bit mode: 1000000 usec / ((160000000 Hz) / 5 / 2) x 32 bit/pulse x 2(stereo) = 4 usec/pulse */ -#define I2S_OUT_USEC_PER_PULSE 4 +# define I2S_OUT_USEC_PER_PULSE 4 -#define I2S_OUT_DMABUF_COUNT 5 /* number of DMA buffers to store data */ -#define I2S_OUT_DMABUF_LEN 2000 /* maximum size in bytes (4092 is DMA's limit) */ +# define I2S_OUT_DMABUF_COUNT 5 /* number of DMA buffers to store data */ +# define I2S_OUT_DMABUF_LEN 2000 /* maximum size in bytes (4092 is DMA's limit) */ -#define I2S_OUT_DELAY_DMABUF_MS (I2S_OUT_DMABUF_LEN / sizeof(uint32_t) * I2S_OUT_USEC_PER_PULSE / 1000) -#define I2S_OUT_DELAY_MS (I2S_OUT_DELAY_DMABUF_MS * (I2S_OUT_DMABUF_COUNT + 1)) +# define I2S_OUT_DELAY_DMABUF_MS (I2S_OUT_DMABUF_LEN / sizeof(uint32_t) * I2S_OUT_USEC_PER_PULSE / 1000) +# define I2S_OUT_DELAY_MS (I2S_OUT_DELAY_DMABUF_MS * (I2S_OUT_DMABUF_COUNT + 1)) typedef void (*i2s_out_pulse_func_t)(void); @@ -90,19 +90,19 @@ typedef struct { If I2S_OUT_PIN_BASE is set to 128, bit0:Expanded GPIO 128, 1: Expanded GPIO 129, ..., v: Expanded GPIO 159 */ - uint8_t ws_pin; - uint8_t bck_pin; - uint8_t data_pin; + uint8_t ws_pin; + uint8_t bck_pin; + uint8_t data_pin; i2s_out_pulse_func_t pulse_func; - uint32_t pulse_period; // aka step rate. - uint32_t init_val; + uint32_t pulse_period; // aka step rate. + uint32_t init_val; } i2s_out_init_t; /* Initialize I2S out by parameters. return -1 ... already initialized */ -int i2s_out_init(i2s_out_init_t &init_param); +int i2s_out_init(i2s_out_init_t& init_param); /* Initialize I2S out by default parameters. @@ -164,10 +164,10 @@ int i2s_out_set_stepping(); void i2s_out_delay(); /* - Set the pulse callback period in microseconds - (like the timer period for the ISR) + Set the pulse callback period in ISR ticks. + (same value of the timer period for the ISR) */ -int i2s_out_set_pulse_period(uint32_t period); +int i2s_out_set_pulse_period(uint64_t period); /* Register a callback function to generate pulse data @@ -188,4 +188,3 @@ int i2s_out_reset(); Reference: "ESP32 Technical Reference Manual" by Espressif Systems https://www.espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_en.pdf */ -#endif diff --git a/Grbl_Esp32/jog.cpp b/Grbl_Esp32/src/Jog.cpp similarity index 87% rename from Grbl_Esp32/jog.cpp rename to Grbl_Esp32/src/Jog.cpp index 902b48f8..b3eb4917 100644 --- a/Grbl_Esp32/jog.cpp +++ b/Grbl_Esp32/src/Jog.cpp @@ -1,5 +1,5 @@ /* - jog.h - Jogging methods + Jog.cpp - Jogging methods Part of Grbl Copyright (c) 2016 Sungeun K. Jeon for Gnea Research LLC @@ -21,8 +21,7 @@ along with Grbl. If not, see . */ -#include "grbl.h" - +#include "Grbl.h" // Sets up valid jog motion received from g-code parser, checks for soft-limits, and executes the jog. uint8_t jog_execute(plan_line_data_t* pl_data, parser_block_t* gc_block) { @@ -34,12 +33,13 @@ uint8_t jog_execute(plan_line_data_t* pl_data, parser_block_t* gc_block) { pl_data->line_number = gc_block->values.n; #endif if (soft_limits->get()) { - if (system_check_travel_limits(gc_block->values.xyz)) return (STATUS_TRAVEL_EXCEEDED); + if (system_check_travel_limits(gc_block->values.xyz)) + return (STATUS_TRAVEL_EXCEEDED); } // Valid jog command. Plan, set state, and execute. mc_line(gc_block->values.xyz, pl_data); if (sys.state == STATE_IDLE) { - if (plan_get_current_block() != NULL) { // Check if there is a block to execute. + if (plan_get_current_block() != NULL) { // Check if there is a block to execute. sys.state = STATE_JOG; st_prep_buffer(); st_wake_up(); // NOTE: Manual start. No state machine required. diff --git a/Grbl_Esp32/jog.h b/Grbl_Esp32/src/Jog.h similarity index 87% rename from Grbl_Esp32/jog.h rename to Grbl_Esp32/src/Jog.h index 07bddba5..2b0f5064 100644 --- a/Grbl_Esp32/jog.h +++ b/Grbl_Esp32/src/Jog.h @@ -1,5 +1,7 @@ +#pragma once + /* - jog.h - Jogging methods + Jog.h - Jogging methods Part of Grbl Copyright (c) 2016 Sungeun K. Jeon for Gnea Research LLC @@ -20,16 +22,10 @@ along with Grbl. If not, see . */ -#ifndef jog_h -#define jog_h - -#include "grbl.h" - +#include "Grbl.h" // System motion line numbers must be zero. #define JOG_LINE_NUMBER 0 // Sets up valid jog motion received from g-code parser, checks for soft-limits, and executes the jog. -uint8_t jog_execute(plan_line_data_t *pl_data, parser_block_t *gc_block); - -#endif +uint8_t jog_execute(plan_line_data_t* pl_data, parser_block_t* gc_block); diff --git a/Grbl_Esp32/grbl_limits.cpp b/Grbl_Esp32/src/Limits.cpp similarity index 62% rename from Grbl_Esp32/grbl_limits.cpp rename to Grbl_Esp32/src/Limits.cpp index abddc9ba..18e71d78 100644 --- a/Grbl_Esp32/grbl_limits.cpp +++ b/Grbl_Esp32/src/Limits.cpp @@ -1,5 +1,5 @@ /* - limits.c - code pertaining to limit-switches and performing the homing cycle + Limits.cpp - code pertaining to limit-switches and performing the homing cycle Part of Grbl Copyright (c) 2012-2016 Sungeun K. Jeon for Gnea Research LLC @@ -25,7 +25,7 @@ along with Grbl. If not, see . */ -#include "grbl.h" +#include "Grbl.h" uint8_t n_homing_locate_cycle = N_HOMING_LOCATE_CYCLE; @@ -33,10 +33,10 @@ xQueueHandle limit_sw_queue; // used by limit switch debouncing // Homing axis search distance multiplier. Computed by this value times the cycle travel. #ifndef HOMING_AXIS_SEARCH_SCALAR - #define HOMING_AXIS_SEARCH_SCALAR 1.1 // Must be > 1 to ensure limit switch will be engaged. +# define HOMING_AXIS_SEARCH_SCALAR 1.1 // Must be > 1 to ensure limit switch will be engaged. #endif #ifndef HOMING_AXIS_LOCATE_SCALAR - #define HOMING_AXIS_LOCATE_SCALAR 5.0 // Must be > 1 to ensure limit switch is cleared. +# define HOMING_AXIS_LOCATE_SCALAR 5.0 // Must be > 1 to ensure limit switch is cleared. #endif void IRAM_ATTR isr_limit_switches() { @@ -52,16 +52,16 @@ void IRAM_ATTR isr_limit_switches() { int evt; xQueueSendFromISR(limit_sw_queue, &evt, NULL); #else -#ifdef HARD_LIMIT_FORCE_STATE_CHECK +# ifdef HARD_LIMIT_FORCE_STATE_CHECK // Check limit pin state. if (limits_get_state()) { - mc_reset(); // Initiate system kill. - system_set_exec_alarm(EXEC_ALARM_HARD_LIMIT); // Indicate hard limit critical event + mc_reset(); // Initiate system kill. + system_set_exec_alarm(EXEC_ALARM_HARD_LIMIT); // Indicate hard limit critical event } -#else - mc_reset(); // Initiate system kill. - system_set_exec_alarm(EXEC_ALARM_HARD_LIMIT); // Indicate hard limit critical event -#endif +# else + mc_reset(); // Initiate system kill. + system_set_exec_alarm(EXEC_ALARM_HARD_LIMIT); // Indicate hard limit critical event +# endif #endif } } @@ -75,10 +75,11 @@ void IRAM_ATTR isr_limit_switches() { // NOTE: Only the abort realtime command can interrupt this process. // TODO: Move limit pin-specific calls to a general function for portability. void limits_go_home(uint8_t cycle_mask) { - if (sys.abort) return; // Block if system reset has been issued. + if (sys.abort) + return; // Block if system reset has been issued. // Initialize plan data struct for homing motion. Spindle and coolant are disabled. - motors_set_homing_mode(cycle_mask, true); // tell motors homing is about to start - plan_line_data_t plan_data; + motors_set_homing_mode(cycle_mask, true); // tell motors homing is about to start + plan_line_data_t plan_data; plan_line_data_t* pl_data = &plan_data; memset(pl_data, 0, sizeof(plan_line_data_t)); pl_data->condition = (PL_COND_FLAG_SYSTEM_MOTION | PL_COND_FLAG_NO_FEED_OVERRIDE); @@ -88,28 +89,29 @@ void limits_go_home(uint8_t cycle_mask) { // Initialize variables used for homing computations. uint8_t n_cycle = (2 * n_homing_locate_cycle + 1); uint8_t step_pin[N_AXIS]; - float target[N_AXIS]; - float max_travel = 0.0; + float target[N_AXIS]; + float max_travel = 0.0; uint8_t idx; for (idx = 0; idx < N_AXIS; idx++) { // Initialize step pin masks step_pin[idx] = get_step_pin_mask(idx); #ifdef COREXY - if ((idx == A_MOTOR) || (idx == B_MOTOR)) step_pin[idx] = (get_step_pin_mask(X_AXIS) | get_step_pin_mask(Y_AXIS)); + if ((idx == A_MOTOR) || (idx == B_MOTOR)) + step_pin[idx] = (get_step_pin_mask(X_AXIS) | get_step_pin_mask(Y_AXIS)); #endif if (bit_istrue(cycle_mask, bit(idx))) { // Set target based on max_travel setting. Ensure homing switches engaged with search scalar. - max_travel = MAX(max_travel, (HOMING_AXIS_SEARCH_SCALAR) * axis_settings[idx]->max_travel->get()); + max_travel = MAX(max_travel, (HOMING_AXIS_SEARCH_SCALAR)*axis_settings[idx]->max_travel->get()); } } // Set search mode with approach at seek rate to quickly engage the specified cycle_mask limit switches. - bool approach = true; - float homing_rate = homing_seek_rate->get(); + bool approach = true; + float homing_rate = homing_seek_rate->get(); uint8_t limit_state, axislock, n_active_axis; do { system_convert_array_steps_to_mpos(target, sys_position); // Initialize and declare variables needed for homing routine. - axislock = 0; + axislock = 0; n_active_axis = 0; for (idx = 0; idx < N_AXIS; idx++) { // Set target location for active axes and setup computation for homing rate. @@ -132,24 +134,28 @@ void limits_go_home(uint8_t cycle_mask) { // NOTE: This happens to compile smaller than any other implementation tried. auto mask = homing_dir_mask->get(); if (bit_istrue(mask, bit(idx))) { - if (approach) target[idx] = -max_travel; - else target[idx] = max_travel; + if (approach) + target[idx] = -max_travel; + else + target[idx] = max_travel; } else { - if (approach) target[idx] = max_travel; - else target[idx] = -max_travel; + if (approach) + target[idx] = max_travel; + else + target[idx] = -max_travel; } // Apply axislock to the step port pins active in this cycle. axislock |= step_pin[idx]; } } - homing_rate *= sqrt(n_active_axis); // [sqrt(N_AXIS)] Adjust so individual axes all move at homing rate. + homing_rate *= sqrt(n_active_axis); // [sqrt(N_AXIS)] Adjust so individual axes all move at homing rate. sys.homing_axis_lock = axislock; // Perform homing cycle. Planner buffer should be empty, as required to initiate the homing cycle. - pl_data->feed_rate = homing_rate; // Set current homing rate. - plan_buffer_line(target, pl_data); // Bypass mc_line(). Directly plan homing motion. - sys.step_control = STEP_CONTROL_EXECUTE_SYS_MOTION; // Set to execute homing motion and clear existing flags. - st_prep_buffer(); // Prep and fill segment buffer from newly planned block. - st_wake_up(); // Initiate motion + pl_data->feed_rate = homing_rate; // Set current homing rate. + plan_buffer_line(target, pl_data); // Bypass mc_line(). Directly plan homing motion. + sys.step_control = STEP_CONTROL_EXECUTE_SYS_MOTION; // Set to execute homing motion and clear existing flags. + st_prep_buffer(); // Prep and fill segment buffer from newly planned block. + st_wake_up(); // Initiate motion do { if (approach) { // Check limit state. Lock out cycle axes when they change. @@ -158,8 +164,10 @@ void limits_go_home(uint8_t cycle_mask) { if (axislock & step_pin[idx]) { if (limit_state & bit(idx)) { #ifdef COREXY - if (idx == Z_AXIS) axislock &= ~(step_pin[Z_AXIS]); - else axislock &= ~(step_pin[A_MOTOR] | step_pin[B_MOTOR]); + if (idx == Z_AXIS) + axislock &= ~(step_pin[Z_AXIS]); + else + axislock &= ~(step_pin[A_MOTOR] | step_pin[B_MOTOR]); #else axislock &= ~(step_pin[idx]); #endif @@ -168,21 +176,25 @@ void limits_go_home(uint8_t cycle_mask) { } sys.homing_axis_lock = axislock; } - st_prep_buffer(); // Check and prep segment buffer. NOTE: Should take no longer than 200us. + st_prep_buffer(); // Check and prep segment buffer. NOTE: Should take no longer than 200us. // Exit routines: No time to run protocol_execute_realtime() in this loop. if (sys_rt_exec_state & (EXEC_SAFETY_DOOR | EXEC_RESET | EXEC_CYCLE_STOP)) { uint8_t rt_exec = sys_rt_exec_state; // Homing failure condition: Reset issued during cycle. - if (rt_exec & EXEC_RESET) system_set_exec_alarm(EXEC_ALARM_HOMING_FAIL_RESET); + if (rt_exec & EXEC_RESET) + system_set_exec_alarm(EXEC_ALARM_HOMING_FAIL_RESET); // Homing failure condition: Safety door was opened. - if (rt_exec & EXEC_SAFETY_DOOR) system_set_exec_alarm(EXEC_ALARM_HOMING_FAIL_DOOR); + if (rt_exec & EXEC_SAFETY_DOOR) + system_set_exec_alarm(EXEC_ALARM_HOMING_FAIL_DOOR); // Homing failure condition: Limit switch still engaged after pull-off motion - if (!approach && (limits_get_state() & cycle_mask)) system_set_exec_alarm(EXEC_ALARM_HOMING_FAIL_PULLOFF); + if (!approach && (limits_get_state() & cycle_mask)) + system_set_exec_alarm(EXEC_ALARM_HOMING_FAIL_PULLOFF); // Homing failure condition: Limit switch not found during approach. - if (approach && (rt_exec & EXEC_CYCLE_STOP)) system_set_exec_alarm(EXEC_ALARM_HOMING_FAIL_APPROACH); - if (sys_rt_exec_alarm) { - motors_set_homing_mode(cycle_mask, false); // tell motors homing is done...failed - mc_reset(); // Stop motors, if they are running. + if (approach && (rt_exec & EXEC_CYCLE_STOP)) + system_set_exec_alarm(EXEC_ALARM_HOMING_FAIL_APPROACH); + if (sys_rt_exec_alarm) { + motors_set_homing_mode(cycle_mask, false); // tell motors homing is done...failed + mc_reset(); // Stop motors, if they are running. protocol_execute_realtime(); return; } else { @@ -197,16 +209,16 @@ void limits_go_home(uint8_t cycle_mask) { delay_ms(I2S_OUT_DELAY_MS); } #endif - st_reset(); // Immediately force kill steppers and reset step segment buffer. - delay_ms(homing_debounce->get()); // Delay to allow transient dynamics to dissipate. + st_reset(); // Immediately force kill steppers and reset step segment buffer. + delay_ms(homing_debounce->get()); // Delay to allow transient dynamics to dissipate. // Reverse direction and reset homing rate for locate cycle(s). approach = !approach; // After first cycle, homing enters locating phase. Shorten search to pull-off distance. if (approach) { - max_travel = homing_pulloff->get() * HOMING_AXIS_LOCATE_SCALAR; + max_travel = homing_pulloff->get() * HOMING_AXIS_LOCATE_SCALAR; homing_rate = homing_feed_rate->get(); } else { - max_travel = homing_pulloff->get(); + max_travel = homing_pulloff->get(); homing_rate = homing_seek_rate->get(); } } while (n_cycle-- > 0); @@ -218,7 +230,7 @@ void limits_go_home(uint8_t cycle_mask) { // triggering when hard limits are enabled or when more than one axes shares a limit pin. int32_t set_axis_position; // Set machine positions for homed limit switches. Don't update non-homed axes. - auto mask = homing_dir_mask->get(); + auto mask = homing_dir_mask->get(); auto pulloff = homing_pulloff->get(); for (idx = 0; idx < N_AXIS; idx++) { auto steps = axis_settings[idx]->steps_per_mm->get(); @@ -228,28 +240,28 @@ void limits_go_home(uint8_t cycle_mask) { #else auto travel = axis_settings[idx]->max_travel->get(); if (bit_istrue(mask, bit(idx))) { -#ifdef HOMING_FORCE_POSITIVE_SPACE - set_axis_position = 0; //lround(settings.homing_pulloff*settings.steps_per_mm[idx]); -#else +# ifdef HOMING_FORCE_POSITIVE_SPACE + set_axis_position = 0; //lround(settings.homing_pulloff*settings.steps_per_mm[idx]); +# else set_axis_position = lround((-travel + pulloff) * steps); -#endif +# endif } else { -#ifdef HOMING_FORCE_POSITIVE_SPACE +# ifdef HOMING_FORCE_POSITIVE_SPACE set_axis_position = lround((-travel - pulloff) * steps); -#else +# else set_axis_position = lround(-pulloff * steps); -#endif +# endif } #endif #ifdef COREXY if (idx == X_AXIS) { int32_t off_axis_position = system_convert_corexy_to_y_axis_steps(sys_position); - sys_position[A_MOTOR] = set_axis_position + off_axis_position; - sys_position[B_MOTOR] = set_axis_position - off_axis_position; + sys_position[A_MOTOR] = set_axis_position + off_axis_position; + sys_position[B_MOTOR] = set_axis_position - off_axis_position; } else if (idx == Y_AXIS) { int32_t off_axis_position = system_convert_corexy_to_x_axis_steps(sys_position); - sys_position[A_MOTOR] = off_axis_position + set_axis_position; - sys_position[B_MOTOR] = off_axis_position - set_axis_position; + sys_position[A_MOTOR] = off_axis_position + set_axis_position; + sys_position[B_MOTOR] = off_axis_position - set_axis_position; } else sys_position[idx] = set_axis_position; #else @@ -257,55 +269,62 @@ void limits_go_home(uint8_t cycle_mask) { #endif } } - sys.step_control = STEP_CONTROL_NORMAL_OP; // Return step control to normal operation. - motors_set_homing_mode(cycle_mask, false); // tell motors homing is done + sys.step_control = STEP_CONTROL_NORMAL_OP; // Return step control to normal operation. + motors_set_homing_mode(cycle_mask, false); // tell motors homing is done } -uint8_t limit_pins[] = { - X_LIMIT_PIN, - Y_LIMIT_PIN, - Z_LIMIT_PIN, - A_LIMIT_PIN, - B_LIMIT_PIN, - C_LIMIT_PIN, -}; +uint8_t limit_pins[MAX_N_AXIS][2] = { { X_LIMIT_PIN, X2_LIMIT_PIN }, { Y_LIMIT_PIN, Y2_LIMIT_PIN }, { Z_LIMIT_PIN, Z2_LIMIT_PIN }, + { A_LIMIT_PIN, A2_LIMIT_PIN }, { B_LIMIT_PIN, B2_LIMIT_PIN }, { C_LIMIT_PIN, C2_LIMIT_PIN } }; uint8_t limit_mask = 0; void limits_init() { limit_mask = 0; - int mode = INPUT_PULLUP; + int mode = INPUT_PULLUP; #ifdef DISABLE_LIMIT_PIN_PULL_UP mode = INPUT; #endif - for (int i=0; iget()) { - attachInterrupt(pin, isr_limit_switches, CHANGE); - } else { - detachInterrupt(pin); + for (int axis = 0; axis < N_AXIS; axis++) { + for (int gang_index = 0; gang_index < 2; gang_index++) { + uint8_t pin; + if ((pin = limit_pins[axis][gang_index]) != UNDEFINED_PIN) { + pinMode(pin, mode); + limit_mask |= bit(axis); + if (hard_limits->get()) { + attachInterrupt(pin, isr_limit_switches, CHANGE); + } else { + detachInterrupt(pin); + } + grbl_msg_sendf(CLIENT_SERIAL, + MSG_LEVEL_INFO, + "%c%s Axis limit switch on pin %s", + report_get_axis_letter(axis), + gang_index ? "2" : " ", + pinName(pin).c_str()); } } } + //grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Limit Mask %d", limit_mask); + // setup task used for debouncing limit_sw_queue = xQueueCreate(10, sizeof(int)); xTaskCreate(limitCheckTask, "limitCheckTask", 2048, NULL, - 5, // priority + 5, // priority NULL); } // Disables hard limits. void limits_disable() { - for (int i=0; iget()) { @@ -338,11 +359,11 @@ void limits_soft_check(float* target) { if (system_check_travel_limits(target)) { // TODO for debugging only 3 axes right now grbl_msg_sendf(CLIENT_SERIAL, - MSG_LEVEL_INFO, - "Soft limit error target WPOS X:%5.2f Y:%5.2f Z:%5.2f", - target[X_AXIS] - gc_state.coord_system[X_AXIS], - target[Y_AXIS] - gc_state.coord_system[Y_AXIS], - target[Z_AXIS] - gc_state.coord_system[Z_AXIS]); + MSG_LEVEL_INFO, + "Soft limit error target WPOS X:%5.2f Y:%5.2f Z:%5.2f", + target[X_AXIS] - gc_state.coord_system[X_AXIS], + target[Y_AXIS] - gc_state.coord_system[Y_AXIS], + target[Z_AXIS] - gc_state.coord_system[Z_AXIS]); sys.soft_limit = true; // Force feed hold if cycle is active. All buffered blocks are guaranteed to be within @@ -352,12 +373,13 @@ void limits_soft_check(float* target) { system_set_exec_state_flag(EXEC_FEED_HOLD); do { protocol_execute_realtime(); - if (sys.abort) return; + if (sys.abort) + return; } while (sys.state != STATE_IDLE); } - mc_reset(); // Issue system reset and ensure spindle and coolant are shutdown. - system_set_exec_alarm(EXEC_ALARM_SOFT_LIMIT); // Indicate soft limit critical event - protocol_execute_realtime(); // Execute to enter critical event loop and system abort + mc_reset(); // Issue system reset and ensure spindle and coolant are shutdown. + system_set_exec_alarm(EXEC_ALARM_SOFT_LIMIT); // Indicate soft limit critical event + protocol_execute_realtime(); // Execute to enter critical event loop and system abort return; } } @@ -366,14 +388,14 @@ void limits_soft_check(float* target) { void limitCheckTask(void* pvParameters) { while (true) { int evt; - xQueueReceive(limit_sw_queue, &evt, portMAX_DELAY); // block until receive queue - vTaskDelay(DEBOUNCE_PERIOD / portTICK_PERIOD_MS); // delay a while + xQueueReceive(limit_sw_queue, &evt, portMAX_DELAY); // block until receive queue + vTaskDelay(DEBOUNCE_PERIOD / portTICK_PERIOD_MS); // delay a while uint8_t switch_state; switch_state = limits_get_state(); if (switch_state) { - grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Limit Switch State %08d", switch_state); - mc_reset(); // Initiate system kill. - system_set_exec_alarm(EXEC_ALARM_HARD_LIMIT); // Indicate hard limit critical event + //grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Limit Switch State %08d", switch_state); + mc_reset(); // Initiate system kill. + system_set_exec_alarm(EXEC_ALARM_HARD_LIMIT); // Indicate hard limit critical event } } } @@ -398,17 +420,17 @@ bool axis_is_squared(uint8_t axis_mask) { return true; #endif } - if (axis_mask == (1 << A_AXIS)) { + if (axis_mask == (1 << A_AXIS)) { #ifdef A_AXIS_SQUARING return true; #endif } - if (axis_mask == (1 << B_AXIS)) { + if (axis_mask == (1 << B_AXIS)) { #ifdef B_AXIS_SQUARING return true; #endif } - if (axis_mask == (1 << C_AXIS)) { + if (axis_mask == (1 << C_AXIS)) { #ifdef C_AXIS_SQUARING return true; #endif diff --git a/Grbl_Esp32/grbl_limits.h b/Grbl_Esp32/src/Limits.h similarity index 86% rename from Grbl_Esp32/grbl_limits.h rename to Grbl_Esp32/src/Limits.h index 5e992a4d..88074e25 100644 --- a/Grbl_Esp32/grbl_limits.h +++ b/Grbl_Esp32/src/Limits.h @@ -1,5 +1,7 @@ +#pragma once + /* - limits.h - code pertaining to limit-switches and performing the homing cycle + Limits.h - code pertaining to limit-switches and performing the homing cycle Part of Grbl Copyright (c) 2012-2016 Sungeun K. Jeon for Gnea Research LLC @@ -25,14 +27,11 @@ along with Grbl. If not, see . */ -#ifndef grbl_limits_h -#define grbl_limits_h - extern uint8_t n_homing_locate_cycle; -#define SQUARING_MODE_DUAL 0 // both motors run -#define SQUARING_MODE_A 1 // A motor runs -#define SQUARING_MODE_B 2 // B motor runs +#define SQUARING_MODE_DUAL 0 // both motors run +#define SQUARING_MODE_A 1 // A motor runs +#define SQUARING_MODE_B 2 // B motor runs // Initialize the limits module void limits_init(); @@ -55,5 +54,3 @@ bool axis_is_squared(uint8_t axis_mask); // A task that runs after a limit switch interrupt. void limitCheckTask(void* pvParameters); - -#endif \ No newline at end of file diff --git a/Grbl_Esp32/machine.h b/Grbl_Esp32/src/Machine.h similarity index 84% rename from Grbl_Esp32/machine.h rename to Grbl_Esp32/src/Machine.h index 3f987a94..11ab8406 100644 --- a/Grbl_Esp32/machine.h +++ b/Grbl_Esp32/src/Machine.h @@ -1,23 +1,14 @@ +#pragma once + // This file is where you choose the machine type, by including // one or more machine definition files as described below. -#ifndef _machine_h -#define _machine_h - -/* -PWM - Tested on arc_arrow.nc - - - -*/ - #ifndef MACHINE_FILENAME // !!! For initial testing, start with test_drive.h which disables // all I/O pins // #include "Machines/atari_1020.h" -#include "Machines/test_drive.h" +# include "Machines/test_drive.h" // !!! For actual use, change the line above to select a board // from Machines/, for example: @@ -48,10 +39,8 @@ PWM // supplied automatically. // MACHINE_PATHNAME_QUOTED constructs a path that is suitable for #include -#define MACHINE_PATHNAME_QUOTED(name) +# define MACHINE_PATHNAME_QUOTED(name) -#include MACHINE_PATHNAME_QUOTED(MACHINE_FILENAME) +# include MACHINE_PATHNAME_QUOTED(MACHINE_FILENAME) -#endif // MACHINE_FILENAME - -#endif // _machine_h +#endif // MACHINE_FILENAME diff --git a/Grbl_Esp32/src/MachineCommon.h b/Grbl_Esp32/src/MachineCommon.h new file mode 100644 index 00000000..0cbc73c1 --- /dev/null +++ b/Grbl_Esp32/src/MachineCommon.h @@ -0,0 +1,35 @@ +#pragma once + +#ifndef SPINDLE_TYPE +# define SPINDLE_TYPE SPINDLE_TYPE_PWM +#endif + +// Grbl setting that are common to all machines +// It should not be necessary to change anything herein + +#ifndef GRBL_SPI_FREQ +// You can override these by defining them in a board file. +// To override, you must set all of them +//-1 means use the default board pin +# define GRBL_SPI_SS -1 +# define GRBL_SPI_MOSI -1 +# define GRBL_SPI_MISO -1 +# define GRBL_SPI_SCK -1 +# define GRBL_SPI_FREQ 4000000 +#endif + +// ESP32 CPU Settings +#define F_TIMERS 80000000 // a reference to the speed of ESP32 timers +#define F_STEPPER_TIMER 20000000 // frequency of step pulse timer +#define STEPPER_OFF_TIMER_PRESCALE 8 // gives a frequency of 10MHz +#define STEPPER_OFF_PERIOD_uSEC 3 // each tick is + +#define STEP_PULSE_MIN 2 // uSeconds +#define STEP_PULSE_MAX 10 // uSeconds + +// =============== Don't change or comment these out ====================== +// They are for legacy purposes and will not affect your I/O + +#define STEP_MASK B111111 + +#define PROBE_MASK 1 diff --git a/Grbl_Esp32/Machines/3axis_v3.h b/Grbl_Esp32/src/Machines/3axis_v3.h similarity index 98% rename from Grbl_Esp32/Machines/3axis_v3.h rename to Grbl_Esp32/src/Machines/3axis_v3.h index e95b0d5c..9c6ad4c2 100644 --- a/Grbl_Esp32/Machines/3axis_v3.h +++ b/Grbl_Esp32/src/Machines/3axis_v3.h @@ -1,3 +1,6 @@ +#pragma once +// clang-format off + /* 3axis_v3.h Part of Grbl_ESP32 diff --git a/Grbl_Esp32/Machines/3axis_v4.h b/Grbl_Esp32/src/Machines/3axis_v4.h similarity index 98% rename from Grbl_Esp32/Machines/3axis_v4.h rename to Grbl_Esp32/src/Machines/3axis_v4.h index 5e27b404..305396b1 100644 --- a/Grbl_Esp32/Machines/3axis_v4.h +++ b/Grbl_Esp32/src/Machines/3axis_v4.h @@ -1,3 +1,6 @@ +#pragma once +// clang-format off + /* 3axis_v4.h Part of Grbl_ESP32 diff --git a/Grbl_Esp32/Machines/3axis_xyx.h b/Grbl_Esp32/src/Machines/3axis_xyx.h similarity index 98% rename from Grbl_Esp32/Machines/3axis_xyx.h rename to Grbl_Esp32/src/Machines/3axis_xyx.h index 4cb21488..1d00a228 100644 --- a/Grbl_Esp32/Machines/3axis_xyx.h +++ b/Grbl_Esp32/src/Machines/3axis_xyx.h @@ -1,3 +1,6 @@ +#pragma once +// clang-format off + /* 3axis_xyx.h Part of Grbl_ESP32 diff --git a/Grbl_Esp32/Machines/4axis_external_driver.h b/Grbl_Esp32/src/Machines/4axis_external_driver.h similarity index 97% rename from Grbl_Esp32/Machines/4axis_external_driver.h rename to Grbl_Esp32/src/Machines/4axis_external_driver.h index d8375b26..0b52d1fc 100644 --- a/Grbl_Esp32/Machines/4axis_external_driver.h +++ b/Grbl_Esp32/src/Machines/4axis_external_driver.h @@ -1,5 +1,8 @@ +#pragma once +// clang-format off + /* - 4axis_external_driver_4x.h + 4axis_external_driver.h Part of Grbl_ESP32 Pin assignments for the buildlog.net 4-axis external driver board diff --git a/Grbl_Esp32/src/Machines/6_pack_MPCNC_stepstick_v1.h b/Grbl_Esp32/src/Machines/6_pack_MPCNC_stepstick_v1.h new file mode 100644 index 00000000..3605e4fa --- /dev/null +++ b/Grbl_Esp32/src/Machines/6_pack_MPCNC_stepstick_v1.h @@ -0,0 +1,95 @@ +#pragma once +// clang-format off + +/* + 6_pack_MPCNC_stepstick_v1.h + + Covers all V1 versions V1p0, V1p1, etc + + Part of Grbl_ESP32 + Pin assignments for the ESP32 I2S 6-axis board + 2018 - Bart Dring + 2020 - Mitch Bradley + 2020 - Michiyasu Odaki + Grbl_ESP32 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_ESP32. If not, see . +*/ +#define MACHINE_NAME "6 Pack MPCNC XYZXY V1 (StepStick)" + +#ifdef N_AXIS + #undef N_AXIS +#endif +#define N_AXIS 3 + +#ifdef ENABLE_SD_CARD + #undef ENABLE_SD_CARD +#endif + +// === Special Features + +// I2S (steppers & other output-only pins) +#define USE_I2S_OUT +// Define USE_I2S_OUT_STREAM if buffering is used. +// (there will be a delay between the specified I/O operation and the actual I/O execution) +#define USE_I2S_OUT_STREAM +#undef USE_RMT_STEPS + +#define USE_STEPSTICK // makes sure MS1,2,3 !reset and !sleep are set + +#define I2S_OUT_BCK GPIO_NUM_22 +#define I2S_OUT_WS GPIO_NUM_17 +#define I2S_OUT_DATA GPIO_NUM_21 + + +#define X_STEPPER_MS3 I2SO(3) // Labeled X_CS +#define Y_STEPPER_MS3 I2SO(6) // Y_CS +#define Z_STEPPER_MS3 I2SO(11) // Z_CS +#define X2_STEPPER_MS3 I2SO(14) // A_CS +#define Y2_STEPPER_MS3 I2SO(19) // B_CS + +#define STEPPER_RESET GPIO_NUM_19 + +#define X_DISABLE_PIN I2SO(0) +#define X_DIRECTION_PIN I2SO(1) +#define X_STEP_PIN I2SO(2) +#define X_AXIS_SQUARING + +#define Y_DIRECTION_PIN I2SO(4) +#define Y_STEP_PIN I2SO(5) +#define Y_DISABLE_PIN I2SO(7) +#define Y_AXIS_SQUARING + +#define Z_DISABLE_PIN I2SO(8) +#define Z_DIRECTION_PIN I2SO(9) +#define Z_STEP_PIN I2SO(10) + +// labeled A on controller +#define X2_DIRECTION_PIN I2SO(12) +#define X2_STEP_PIN I2SO(13) +#define X2_DISABLE_PIN I2SO(15) + +// labeled B on controller +#define Y2_DISABLE_PIN I2SO(16) +#define Y2_DIRECTION_PIN I2SO(17) +#define Y2_STEP_PIN I2SO(18) + +// stepper C unused + +#define X_LIMIT_PIN GPIO_NUM_33 +#define Y_LIMIT_PIN GPIO_NUM_32 +#define Z_LIMIT_PIN GPIO_NUM_35 +#define X2_LIMIT_PIN GPIO_NUM_34 // labeled A +#define Y2_LIMIT_PIN GPIO_NUM_39 // labeled B + + + +// === Default settings +#define DEFAULT_STEP_PULSE_MICROSECONDS I2S_OUT_USEC_PER_PULSE diff --git a/Grbl_Esp32/Machines/6_pack_stepstick_v1.h b/Grbl_Esp32/src/Machines/6_pack_stepstick_v1.h similarity index 92% rename from Grbl_Esp32/Machines/6_pack_stepstick_v1.h rename to Grbl_Esp32/src/Machines/6_pack_stepstick_v1.h index 1c02e4da..16e9cd48 100644 --- a/Grbl_Esp32/Machines/6_pack_stepstick_v1.h +++ b/Grbl_Esp32/src/Machines/6_pack_stepstick_v1.h @@ -1,3 +1,6 @@ +#pragma once +// clang-format off + /* 6_pack_stepstick_v1.h @@ -46,12 +49,12 @@ #define I2S_OUT_DATA GPIO_NUM_21 -#define STEPPER_X_MS3 I2SO(3) // X_CS -#define STEPPER_Y_MS3 I2SO(6) // Y_CS -#define STEPPER_Z_MS3 I2SO(11) // Z_CS -#define STEPPER_A_MS3 I2SO(14) // A_CS -#define STEPPER_B_MS3 I2SO(19) // B_CS -#define STEPPER_C_MS3 I2SO(22) // C_CS +#define X_STEPPER_MS3 I2SO(3) // X_CS +#define Y_STEPPER_MS3 I2SO(6) // Y_CS +#define Z_STEPPER_MS3 I2SO(11) // Z_CS +#define A_STEPPER_MS3 I2SO(14) // A_CS +#define B_STEPPER_MS3 I2SO(19) // B_CS +#define C_STEPPER_MS3 I2SO(22) // C_CS #define STEPPER_RESET GPIO_NUM_19 diff --git a/Grbl_Esp32/Machines/6_pack_trinamic_V1.h b/Grbl_Esp32/src/Machines/6_pack_trinamic_V1.h similarity index 99% rename from Grbl_Esp32/Machines/6_pack_trinamic_V1.h rename to Grbl_Esp32/src/Machines/6_pack_trinamic_V1.h index 038fcff7..e25f6fae 100644 --- a/Grbl_Esp32/Machines/6_pack_trinamic_V1.h +++ b/Grbl_Esp32/src/Machines/6_pack_trinamic_V1.h @@ -1,3 +1,6 @@ +#pragma once +// clang-format off + /* 6_pack_trinamic_V1.h Part of Grbl_ESP32 diff --git a/Grbl_Esp32/Machines/atari_1020.h b/Grbl_Esp32/src/Machines/atari_1020.h similarity index 99% rename from Grbl_Esp32/Machines/atari_1020.h rename to Grbl_Esp32/src/Machines/atari_1020.h index 548cbe65..61d8556b 100644 --- a/Grbl_Esp32/Machines/atari_1020.h +++ b/Grbl_Esp32/src/Machines/atari_1020.h @@ -1,3 +1,6 @@ +#pragma once +// clang-format off + /* atari_1020.h Part of Grbl_ESP32 diff --git a/Grbl_Esp32/Machines/esp32_printer_controller.h b/Grbl_Esp32/src/Machines/esp32_printer_controller.h similarity index 99% rename from Grbl_Esp32/Machines/esp32_printer_controller.h rename to Grbl_Esp32/src/Machines/esp32_printer_controller.h index 6938a33a..0667932f 100644 --- a/Grbl_Esp32/Machines/esp32_printer_controller.h +++ b/Grbl_Esp32/src/Machines/esp32_printer_controller.h @@ -1,3 +1,6 @@ +#pragma once +// clang-format off + /* esp32_printer_controller.h Part of Grbl_ESP32 diff --git a/Grbl_Esp32/Machines/espduino.h b/Grbl_Esp32/src/Machines/espduino.h similarity index 98% rename from Grbl_Esp32/Machines/espduino.h rename to Grbl_Esp32/src/Machines/espduino.h index b049f907..81faf104 100644 --- a/Grbl_Esp32/Machines/espduino.h +++ b/Grbl_Esp32/src/Machines/espduino.h @@ -1,3 +1,6 @@ +#pragma once +// clang-format off + /* espduino.h Part of Grbl_ESP32 diff --git a/Grbl_Esp32/Machines/foo_6axis.h b/Grbl_Esp32/src/Machines/foo_6axis.h similarity index 99% rename from Grbl_Esp32/Machines/foo_6axis.h rename to Grbl_Esp32/src/Machines/foo_6axis.h index 7ce7192d..adaad7c7 100644 --- a/Grbl_Esp32/Machines/foo_6axis.h +++ b/Grbl_Esp32/src/Machines/foo_6axis.h @@ -1,3 +1,6 @@ +#pragma once +// clang-format off + /* foo_6axis.h Part of Grbl_ESP32 diff --git a/Grbl_Esp32/Machines/i2s_out_xxyyzz.h b/Grbl_Esp32/src/Machines/i2s_out_xxyyzz.h similarity index 98% rename from Grbl_Esp32/Machines/i2s_out_xxyyzz.h rename to Grbl_Esp32/src/Machines/i2s_out_xxyyzz.h index a29a21a3..04387ec3 100644 --- a/Grbl_Esp32/Machines/i2s_out_xxyyzz.h +++ b/Grbl_Esp32/src/Machines/i2s_out_xxyyzz.h @@ -1,3 +1,6 @@ +#pragma once +// clang-format off + /* i2s_out_xxyyzz.h Part of Grbl_ESP32 @@ -16,6 +19,7 @@ You should have received a copy of the GNU General Public License along with Grbl_ESP32. If not, see . */ + #define MACHINE_NAME "ESP32 I2S XXYYZZ Axis Driver Board (StepStick)" #ifdef N_AXIS @@ -55,8 +59,6 @@ #define STEPPER_RESET GPIO_NUM_19 -#define USE_GANGED_AXES // allow two motors on an axis - #define X_DISABLE_PIN I2SO(0) #define X_DIRECTION_PIN I2SO(1) #define X_STEP_PIN I2SO(2) diff --git a/Grbl_Esp32/Machines/i2s_out_xyzabc.h b/Grbl_Esp32/src/Machines/i2s_out_xyzabc.h similarity index 99% rename from Grbl_Esp32/Machines/i2s_out_xyzabc.h rename to Grbl_Esp32/src/Machines/i2s_out_xyzabc.h index f2134a23..a1f64600 100644 --- a/Grbl_Esp32/Machines/i2s_out_xyzabc.h +++ b/Grbl_Esp32/src/Machines/i2s_out_xyzabc.h @@ -1,3 +1,6 @@ +#pragma once +// clang-format off + /* i2s_out_xyzabc.h Part of Grbl_ESP32 diff --git a/Grbl_Esp32/Machines/i2s_out_xyzabc_trinamic.h b/Grbl_Esp32/src/Machines/i2s_out_xyzabc_trinamic.h similarity index 99% rename from Grbl_Esp32/Machines/i2s_out_xyzabc_trinamic.h rename to Grbl_Esp32/src/Machines/i2s_out_xyzabc_trinamic.h index ac08274e..77d2538b 100644 --- a/Grbl_Esp32/Machines/i2s_out_xyzabc_trinamic.h +++ b/Grbl_Esp32/src/Machines/i2s_out_xyzabc_trinamic.h @@ -1,3 +1,6 @@ +#pragma once +// clang-format off + /* i2s_out_xyzabc_trinamic.h Part of Grbl_ESP32 diff --git a/Grbl_Esp32/Machines/lowrider_v1p2.h b/Grbl_Esp32/src/Machines/lowrider_v1p2.h similarity index 96% rename from Grbl_Esp32/Machines/lowrider_v1p2.h rename to Grbl_Esp32/src/Machines/lowrider_v1p2.h index e7210218..4255c0aa 100644 --- a/Grbl_Esp32/Machines/lowrider_v1p2.h +++ b/Grbl_Esp32/src/Machines/lowrider_v1p2.h @@ -1,3 +1,6 @@ +#pragma once +// clang-format off + /* lowrider_v1p2.h Part of Grbl_ESP32 @@ -19,19 +22,15 @@ along with Grbl_ESP32. If not, see . */ - - #define MACHINE_NAME "LOWRIDER_V1P2" -#define USE_GANGED_AXES // allow two motors on an axis - #define X_STEP_PIN GPIO_NUM_27 // use Z labeled connector #define X_DIRECTION_PIN GPIO_NUM_33 // use Z labeled connector #define Y_STEP_PIN GPIO_NUM_14 // use Y labeled connector #define Y2_STEP_PIN GPIO_NUM_21 // ganged motor #define Y_DIRECTION_PIN GPIO_NUM_25 // use Y labeled connector -#define Y2_DIRECTION_PIN X_DIRECTION_PIN +#define Y2_DIRECTION_PIN Y_DIRECTION_PIN #define Y_AXIS_SQUARING #define Z_STEP_PIN GPIO_NUM_12 // use X labeled connector diff --git a/Grbl_Esp32/Machines/midtbot.h b/Grbl_Esp32/src/Machines/midtbot.h similarity index 98% rename from Grbl_Esp32/Machines/midtbot.h rename to Grbl_Esp32/src/Machines/midtbot.h index 4e93d823..3b0e4554 100644 --- a/Grbl_Esp32/Machines/midtbot.h +++ b/Grbl_Esp32/src/Machines/midtbot.h @@ -1,3 +1,6 @@ +#pragma once +// clang-format off + /* midtbot.h Part of Grbl_ESP32 diff --git a/Grbl_Esp32/Machines/mpcnc_v1p1.h b/Grbl_Esp32/src/Machines/mpcnc_v1p1.h similarity index 98% rename from Grbl_Esp32/Machines/mpcnc_v1p1.h rename to Grbl_Esp32/src/Machines/mpcnc_v1p1.h index 0896f798..93087cda 100644 --- a/Grbl_Esp32/Machines/mpcnc_v1p1.h +++ b/Grbl_Esp32/src/Machines/mpcnc_v1p1.h @@ -1,5 +1,8 @@ +#pragma once +// clang-format off + /* - mpcnc.h + mpcnc_v1p1.h Part of Grbl_ESP32 Pin assignments for the Buildlog.net MPCNC controller @@ -27,8 +30,6 @@ #define MACHINE_NAME "MPCNC_V1P1" -#define USE_GANGED_AXES // allow two motors on an axis - #define X_STEP_PIN GPIO_NUM_12 #define X2_STEP_PIN GPIO_NUM_22 // ganged motor #define X_AXIS_SQUARING diff --git a/Grbl_Esp32/Machines/mpcnc_v1p2.h b/Grbl_Esp32/src/Machines/mpcnc_v1p2.h similarity index 98% rename from Grbl_Esp32/Machines/mpcnc_v1p2.h rename to Grbl_Esp32/src/Machines/mpcnc_v1p2.h index 92a495bb..fcf44169 100644 --- a/Grbl_Esp32/Machines/mpcnc_v1p2.h +++ b/Grbl_Esp32/src/Machines/mpcnc_v1p2.h @@ -1,3 +1,6 @@ +#pragma once +// clang-format off + /* mpcnc_v1p2.h Part of Grbl_ESP32 @@ -28,8 +31,6 @@ #define MACHINE_NAME "MPCNC_V1P2" -#define USE_GANGED_AXES // allow two motors on an axis - #define X_STEP_PIN GPIO_NUM_12 #define X2_STEP_PIN GPIO_NUM_22 // ganged motor #define X_AXIS_SQUARING diff --git a/Grbl_Esp32/Machines/pen_laser.h b/Grbl_Esp32/src/Machines/pen_laser.h similarity index 99% rename from Grbl_Esp32/Machines/pen_laser.h rename to Grbl_Esp32/src/Machines/pen_laser.h index df2b3f4a..fbe517e1 100644 --- a/Grbl_Esp32/Machines/pen_laser.h +++ b/Grbl_Esp32/src/Machines/pen_laser.h @@ -1,3 +1,6 @@ +#pragma once +// clang-format off + /* pen_laser.h Part of Grbl_ESP32 diff --git a/Grbl_Esp32/Machines/polar_coaster.h b/Grbl_Esp32/src/Machines/polar_coaster.h similarity index 99% rename from Grbl_Esp32/Machines/polar_coaster.h rename to Grbl_Esp32/src/Machines/polar_coaster.h index ad40f7ac..b3caed24 100644 --- a/Grbl_Esp32/Machines/polar_coaster.h +++ b/Grbl_Esp32/src/Machines/polar_coaster.h @@ -1,3 +1,6 @@ +#pragma once +// clang-format off + /* polar_coaster.h Part of Grbl_ESP32 diff --git a/Grbl_Esp32/Machines/spi_daisy_4axis_xyyz.h b/Grbl_Esp32/src/Machines/spi_daisy_4axis_xyyz.h similarity index 98% rename from Grbl_Esp32/Machines/spi_daisy_4axis_xyyz.h rename to Grbl_Esp32/src/Machines/spi_daisy_4axis_xyyz.h index 1c64538e..710895b3 100644 --- a/Grbl_Esp32/Machines/spi_daisy_4axis_xyyz.h +++ b/Grbl_Esp32/src/Machines/spi_daisy_4axis_xyyz.h @@ -1,3 +1,6 @@ +#pragma once +// clang-format off + /* spi_daisy_4axis_xyyz.h Part of Grbl_ESP32 @@ -39,9 +42,6 @@ // The hardware enable pin is tied to ground #define USE_TRINAMIC_ENABLE -// allow two motors on an axis -#define USE_GANGED_AXES - // Y motor connects to the 1st driver #define X_TRINAMIC_DRIVER 2130 // Which Driver Type? #define X_RSENSE TMC2130_RSENSE_DEFAULT diff --git a/Grbl_Esp32/Machines/spi_daisy_4axis_xyz.h b/Grbl_Esp32/src/Machines/spi_daisy_4axis_xyz.h similarity index 98% rename from Grbl_Esp32/Machines/spi_daisy_4axis_xyz.h rename to Grbl_Esp32/src/Machines/spi_daisy_4axis_xyz.h index cdcac161..597f7fa5 100644 --- a/Grbl_Esp32/Machines/spi_daisy_4axis_xyz.h +++ b/Grbl_Esp32/src/Machines/spi_daisy_4axis_xyz.h @@ -1,3 +1,6 @@ +#pragma once +// clang-format off + /* spi_daisy_4axis_xyz.h Part of Grbl_ESP32 diff --git a/Grbl_Esp32/Machines/spi_daisy_4axis_xyza.h b/Grbl_Esp32/src/Machines/spi_daisy_4axis_xyza.h similarity index 88% rename from Grbl_Esp32/Machines/spi_daisy_4axis_xyza.h rename to Grbl_Esp32/src/Machines/spi_daisy_4axis_xyza.h index da20a4ab..507db906 100644 --- a/Grbl_Esp32/Machines/spi_daisy_4axis_xyza.h +++ b/Grbl_Esp32/src/Machines/spi_daisy_4axis_xyza.h @@ -1,3 +1,6 @@ +#pragma once +// clang-format off + /* spi_daisy_4axis_xyza.h Part of Grbl_ESP32 @@ -70,21 +73,11 @@ #define SPINDLE_OUTPUT_PIN GPIO_NUM_25 #define SPINDLE_ENABLE_PIN GPIO_NUM_4 -// Relay operation -// Install Jumper near relay -// For PWM remove jumper to prevent relay damage -// Interlock jumper along top edge needs to be installed for both versions -#define USE_RELAY // comment out to use PWM - -#ifdef USE_RELAY - #define SPINDLE_TYPE SPINDLE_TYPE_RELAY -#else - #define SPINDLE_TYPE SPINDLE_TYPE_PWM -#endif +#define SPINDLE_TYPE SPINDLE_TYPE_RELAY // default use $Spindle/Type=PWM or $Spindle/Type=Laser #define PROBE_PIN GPIO_NUM_22 #define X_LIMIT_PIN GPIO_NUM_36 #define Y_LIMIT_PIN GPIO_NUM_39 #define Z_LIMIT_PIN GPIO_NUM_34 -#define A_LIMIT_PIN GPIO_NUM_35 +#define A_LIMIT_PIN GPIO_NUM_35 \ No newline at end of file diff --git a/Grbl_Esp32/Machines/spindle_class_test.h b/Grbl_Esp32/src/Machines/spindle_class_test.h similarity index 96% rename from Grbl_Esp32/Machines/spindle_class_test.h rename to Grbl_Esp32/src/Machines/spindle_class_test.h index 1a675779..8c6c65c2 100644 --- a/Grbl_Esp32/Machines/spindle_class_test.h +++ b/Grbl_Esp32/src/Machines/spindle_class_test.h @@ -1,5 +1,8 @@ +#pragma once +// clang-format off + /* - 3axis_v4.h + spindle_class_test.h Part of Grbl_ESP32 Pin assignments for the ESP32 Development Controller, v4.1 and later. diff --git a/Grbl_Esp32/Machines/template.h b/Grbl_Esp32/src/Machines/template.h similarity index 99% rename from Grbl_Esp32/Machines/template.h rename to Grbl_Esp32/src/Machines/template.h index 6fa66902..f795338e 100644 --- a/Grbl_Esp32/Machines/template.h +++ b/Grbl_Esp32/src/Machines/template.h @@ -1,3 +1,6 @@ +#pragma once +// clang-format off + /* template.h Part of Grbl_ESP32 diff --git a/Grbl_Esp32/Machines/test_drive.h b/Grbl_Esp32/src/Machines/test_drive.h similarity index 97% rename from Grbl_Esp32/Machines/test_drive.h rename to Grbl_Esp32/src/Machines/test_drive.h index f87e4a46..b94db4be 100644 --- a/Grbl_Esp32/Machines/test_drive.h +++ b/Grbl_Esp32/src/Machines/test_drive.h @@ -1,3 +1,6 @@ +#pragma once +// clang-format off + /* test_drive.h Part of Grbl_ESP32 diff --git a/Grbl_Esp32/Machines/tmc2130_pen.h b/Grbl_Esp32/src/Machines/tmc2130_pen.h similarity index 98% rename from Grbl_Esp32/Machines/tmc2130_pen.h rename to Grbl_Esp32/src/Machines/tmc2130_pen.h index 0975e7a2..182db757 100644 --- a/Grbl_Esp32/Machines/tmc2130_pen.h +++ b/Grbl_Esp32/src/Machines/tmc2130_pen.h @@ -1,3 +1,6 @@ +#pragma once +// clang-format off + /* tmc2130_pen.h Part of Grbl_ESP32 diff --git a/Grbl_Esp32/motion_control.cpp b/Grbl_Esp32/src/MotionControl.cpp similarity index 70% rename from Grbl_Esp32/motion_control.cpp rename to Grbl_Esp32/src/MotionControl.cpp index 190e3710..b9d5f22a 100644 --- a/Grbl_Esp32/motion_control.cpp +++ b/Grbl_Esp32/src/MotionControl.cpp @@ -1,5 +1,5 @@ /* - motion_control.c - high level interface for issuing motion commands + MotionControl.cpp - high level interface for issuing motion commands Part of Grbl Copyright (c) 2011-2016 Sungeun K. Jeon for Gnea Research LLC @@ -22,23 +22,22 @@ along with Grbl. If not, see . */ -#include "grbl.h" +#include "Grbl.h" // M_PI is not defined in standard C/C++ but some compilers // support it anyway. The following suppresses Intellisense // problem reports. #ifndef M_PI - #define M_PI 3.14159265358979323846 +# define M_PI 3.14159265358979323846 #endif uint8_t ganged_mode = SQUARING_MODE_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 +#else // else use kinematics inverse_kinematics(target, pl_data, position); #endif } @@ -55,10 +54,12 @@ void mc_line(float* target, plan_line_data_t* pl_data) { // from everywhere in Grbl. if (soft_limits->get()) { // NOTE: Block jog state. Jogging is a special case and soft limits are handled independently. - if (sys.state != STATE_JOG) limits_soft_check(target); + if (sys.state != STATE_JOG) + limits_soft_check(target); } // If in check gcode mode, prevent motion by blocking planner. Soft limits still work. - if (sys.state == STATE_CHECK_MODE) return; + if (sys.state == STATE_CHECK_MODE) + return; // 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 // plan_check_full_buffer() and check for system abort loop. Also for position reporting @@ -75,17 +76,19 @@ void mc_line(float* target, plan_line_data_t* pl_data) { // If the buffer is full: good! That means we are well ahead of the robot. // Remain in this loop until there is room in the buffer. do { - protocol_execute_realtime(); // Check for any run-time commands - if (sys.abort) return; // Bail, if system abort. - if (plan_check_full_buffer()) protocol_auto_cycle_start(); // Auto-cycle start when buffer is full. - else break; + protocol_execute_realtime(); // Check for any run-time commands + if (sys.abort) + return; // Bail, if system abort. + if (plan_check_full_buffer()) + protocol_auto_cycle_start(); // Auto-cycle start when buffer is full. + else + break; } while (1); // Plan and queue motion into planner buffer // uint8_t plan_status; // Not used in normal operation. plan_buffer_line(target, pl_data); } - // 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 @@ -93,42 +96,50 @@ void mc_line(float* target, plan_line_data_t* pl_data) { // The arc is approximated by generating a huge number of tiny, linear segments. The chordal tolerance // of each segment is configured in the arc_tolerance setting, which is defined to be the maximum normal // distance from segment to the circle when the end points both lie on the circle. -void mc_arc(float* target, plan_line_data_t* pl_data, float* position, float* offset, float radius, - uint8_t axis_0, uint8_t axis_1, uint8_t axis_linear, uint8_t is_clockwise_arc) { +void mc_arc(float* target, + plan_line_data_t* pl_data, + float* position, + float* offset, + float radius, + uint8_t axis_0, + uint8_t axis_1, + uint8_t axis_linear, + uint8_t is_clockwise_arc) { float center_axis0 = position[axis_0] + offset[axis_0]; float center_axis1 = position[axis_1] + offset[axis_1]; - float r_axis0 = -offset[axis_0]; // Radius vector from center to current location - float r_axis1 = -offset[axis_1]; - float rt_axis0 = target[axis_0] - center_axis0; - float rt_axis1 = target[axis_1] - center_axis1; + float r_axis0 = -offset[axis_0]; // Radius vector from center to current location + float r_axis1 = -offset[axis_1]; + float rt_axis0 = target[axis_0] - center_axis0; + float rt_axis1 = target[axis_1] - center_axis1; #ifdef USE_KINEMATICS - float previous_position[N_AXIS]; + float previous_position[N_AXIS]; uint16_t n; 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 - if (angular_travel >= -ARC_ANGULAR_TRAVEL_EPSILON) angular_travel -= 2 * M_PI; + if (is_clockwise_arc) { // Correct atan2 output per direction + if (angular_travel >= -ARC_ANGULAR_TRAVEL_EPSILON) + angular_travel -= 2 * M_PI; } else { - if (angular_travel <= ARC_ANGULAR_TRAVEL_EPSILON) angular_travel += 2 * M_PI; + if (angular_travel <= ARC_ANGULAR_TRAVEL_EPSILON) + angular_travel += 2 * M_PI; } // NOTE: Segment end points are on the arc, which can lead to the arc diameter being smaller by up to // (2x) arc_tolerance. For 99% of users, this is just fine. If a different arc segment fit // is desired, i.e. least-squares, midpoint on arc, just change the mm_per_arc_segment calculation. // For the intended uses of Grbl, this value shouldn't exceed 2000 for the strictest of cases. - uint16_t segments = floor(fabs(0.5 * angular_travel * radius) / - sqrt(arc_tolerance->get() * (2 * radius - arc_tolerance->get()))); + uint16_t segments = floor(fabs(0.5 * angular_travel * radius) / sqrt(arc_tolerance->get() * (2 * radius - arc_tolerance->get()))); if (segments) { // Multiply inverse feed_rate to compensate for the fact that this movement is approximated // by a number of discrete segments. The inverse feed_rate should be correct for the sum of // all segments. if (pl_data->condition & PL_COND_FLAG_INVERSE_TIME) { pl_data->feed_rate *= segments; - bit_false(pl_data->condition, PL_COND_FLAG_INVERSE_TIME); // Force as feed absolute mode over arc segments. + bit_false(pl_data->condition, PL_COND_FLAG_INVERSE_TIME); // Force as feed absolute mode over arc segments. } - float theta_per_segment = angular_travel / segments; + float theta_per_segment = angular_travel / segments; float linear_per_segment = (target[axis_linear] - position[axis_linear]) / segments; /* Vector rotation by transformation matrix: r is the original vector, r_T is the rotated vector, and phi is the angle of rotation. Solution approach by Jens Geisler. @@ -159,12 +170,12 @@ void mc_arc(float* target, plan_line_data_t* pl_data, float* position, float* of float cos_T = 2.0 - theta_per_segment * theta_per_segment; float sin_T = theta_per_segment * 0.16666667 * (cos_T + 4.0); cos_T *= 0.5; - float sin_Ti; - float cos_Ti; - float r_axisi; + float sin_Ti; + float cos_Ti; + float r_axisi; uint16_t i; - uint8_t count = 0; - for (i = 1; i < segments; i++) { // Increment (segments-1). + uint8_t count = 0; + for (i = 1; i < segments; i++) { // Increment (segments-1). if (count < N_ARC_CORRECTION) { // Apply vector rotation matrix. ~40 usec r_axisi = r_axis0 * sin_T + r_axis1 * cos_T; @@ -174,11 +185,11 @@ void mc_arc(float* target, plan_line_data_t* pl_data, float* position, float* of } else { // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments. ~375 usec // Compute exact location by applying transformation matrix from initial radius vector(=-offset). - cos_Ti = cos(i * theta_per_segment); - sin_Ti = sin(i * theta_per_segment); + cos_Ti = cos(i * theta_per_segment); + sin_Ti = sin(i * theta_per_segment); r_axis0 = -offset[axis_0] * cos_Ti + offset[axis_1] * sin_Ti; r_axis1 = -offset[axis_0] * sin_Ti - offset[axis_1] * cos_Ti; - count = 0; + count = 0; } // Update arc_target location position[axis_0] = center_axis0 + r_axis0; @@ -186,14 +197,15 @@ void mc_arc(float* target, plan_line_data_t* pl_data, float* position, float* of position[axis_linear] += linear_per_segment; #ifdef USE_KINEMATICS mc_line_kins(position, pl_data, previous_position); - previous_position[axis_0] = position[axis_0]; - previous_position[axis_1] = position[axis_1]; + 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; + if (sys.abort) + return; } } // Ensure last segment arrives at target location. @@ -204,15 +216,14 @@ void mc_arc(float* target, plan_line_data_t* pl_data, float* position, float* of #endif } - // Execute dwell in seconds. void mc_dwell(float seconds) { - if (sys.state == STATE_CHECK_MODE) return; + if (sys.state == STATE_CHECK_MODE) + return; protocol_buffer_synchronize(); delay_sec(seconds, DELAY_MODE_DWELL); } - // Perform homing cycle to locate and set machine zero. Only '$H' executes this command. // NOTE: There should be no motions in the buffer and Grbl must be in an idle state before // executing the homing cycle. This prevents incorrect buffered plans after homing. @@ -221,23 +232,23 @@ void mc_homing_cycle(uint8_t cycle_mask) { if (user_defined_homing()) return; #endif - // This give kinematics a chance to do something before normal homing - // if it returns true, the homing is canceled. + // 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.c as a function. + // 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. #ifdef LIMITS_TWO_SWITCHES_ON_AXES if (limits_get_state()) { - mc_reset(); // Issue system reset and ensure spindle and coolant are shutdown. + mc_reset(); // Issue system reset and ensure spindle and coolant are shutdown. system_set_exec_alarm(EXEC_ALARM_HARD_LIMIT); return; } #endif - limits_disable(); // Disable hard limits pin change register for cycle duration + limits_disable(); // Disable hard limits pin change register for cycle duration // ------------------------------------------------------------------------------------- // Perform homing routine. NOTE: Special motion case. Only system reset works. n_homing_locate_cycle = N_HOMING_LOCATE_CYCLE; @@ -247,65 +258,65 @@ void mc_homing_cycle(uint8_t cycle_mask) { else */ if (cycle_mask) { - if (! axis_is_squared(cycle_mask)) + if (!axis_is_squared(cycle_mask)) limits_go_home(cycle_mask); // Homing cycle 0 else { - ganged_mode = SQUARING_MODE_DUAL; - n_homing_locate_cycle = 0; // don't do a second touch cycle + ganged_mode = SQUARING_MODE_DUAL; + n_homing_locate_cycle = 0; // don't do a second touch cycle limits_go_home(cycle_mask); - ganged_mode = SQUARING_MODE_A; - n_homing_locate_cycle = N_HOMING_LOCATE_CYCLE; // restore to default value + ganged_mode = SQUARING_MODE_A; + n_homing_locate_cycle = N_HOMING_LOCATE_CYCLE; // restore to default value limits_go_home(cycle_mask); ganged_mode = SQUARING_MODE_B; limits_go_home(cycle_mask); - ganged_mode = SQUARING_MODE_DUAL; // always return to dual + ganged_mode = SQUARING_MODE_DUAL; // always return to dual } - } // Perform homing cycle based on mask. + } // Perform homing cycle based on mask. else #endif { // Search to engage all axes limit switches at faster homing seek rate. - if (! axis_is_squared(HOMING_CYCLE_0)) + if (!axis_is_squared(HOMING_CYCLE_0)) limits_go_home(HOMING_CYCLE_0); // Homing cycle 0 else { - ganged_mode = SQUARING_MODE_DUAL; - n_homing_locate_cycle = 0; // don't do a second touch cycle + ganged_mode = SQUARING_MODE_DUAL; + n_homing_locate_cycle = 0; // don't do a second touch cycle limits_go_home(HOMING_CYCLE_0); - ganged_mode = SQUARING_MODE_A; - n_homing_locate_cycle = N_HOMING_LOCATE_CYCLE; // restore to default value + ganged_mode = SQUARING_MODE_A; + n_homing_locate_cycle = N_HOMING_LOCATE_CYCLE; // restore to default value limits_go_home(HOMING_CYCLE_0); ganged_mode = SQUARING_MODE_B; limits_go_home(HOMING_CYCLE_0); - ganged_mode = SQUARING_MODE_DUAL; // always return to dual + ganged_mode = SQUARING_MODE_DUAL; // always return to dual } #ifdef HOMING_CYCLE_1 - if (! axis_is_squared(HOMING_CYCLE_1)) + if (!axis_is_squared(HOMING_CYCLE_1)) limits_go_home(HOMING_CYCLE_1); else { - ganged_mode = SQUARING_MODE_DUAL; - n_homing_locate_cycle = 0; // don't do a second touch cycle + ganged_mode = SQUARING_MODE_DUAL; + n_homing_locate_cycle = 0; // don't do a second touch cycle limits_go_home(HOMING_CYCLE_1); - ganged_mode = SQUARING_MODE_A; - n_homing_locate_cycle = N_HOMING_LOCATE_CYCLE; // restore to default value + ganged_mode = SQUARING_MODE_A; + n_homing_locate_cycle = N_HOMING_LOCATE_CYCLE; // restore to default value limits_go_home(HOMING_CYCLE_1); ganged_mode = SQUARING_MODE_B; limits_go_home(HOMING_CYCLE_1); - ganged_mode = SQUARING_MODE_DUAL; // always return to dual + ganged_mode = SQUARING_MODE_DUAL; // always return to dual } #endif #ifdef HOMING_CYCLE_2 - if (! axis_is_squared(HOMING_CYCLE_2)) + if (!axis_is_squared(HOMING_CYCLE_2)) limits_go_home(HOMING_CYCLE_2); else { - ganged_mode = SQUARING_MODE_DUAL; - n_homing_locate_cycle = 0; // don't do a second touch cycle + ganged_mode = SQUARING_MODE_DUAL; + n_homing_locate_cycle = 0; // don't do a second touch cycle limits_go_home(HOMING_CYCLE_2); - ganged_mode = SQUARING_MODE_A; - n_homing_locate_cycle = N_HOMING_LOCATE_CYCLE; // restore to default value + ganged_mode = SQUARING_MODE_A; + n_homing_locate_cycle = N_HOMING_LOCATE_CYCLE; // restore to default value limits_go_home(HOMING_CYCLE_2); ganged_mode = SQUARING_MODE_B; limits_go_home(HOMING_CYCLE_2); - ganged_mode = SQUARING_MODE_DUAL; // always return to dual + ganged_mode = SQUARING_MODE_DUAL; // always return to dual } #endif #ifdef HOMING_CYCLE_3 @@ -318,15 +329,15 @@ void mc_homing_cycle(uint8_t cycle_mask) { limits_go_home(HOMING_CYCLE_5); // Homing cycle 5 #endif } - protocol_execute_realtime(); // Check for reset and set system abort. - if (sys.abort) { - return; // Did not complete. Alarm state set by mc_alarm. + protocol_execute_realtime(); // Check for reset and set system abort. + if (sys.abort) { + return; // Did not complete. Alarm state set by mc_alarm. } // Homing cycle complete! Setup system for normal operation. // ------------------------------------------------------------------------------------- // Sync gcode parser and planner positions to homed position. gc_sync_position(); - plan_sync_position(); + plan_sync_position(); #ifdef USE_KINEMATICS // This give kinematics a chance to do something after normal homing kinematics_post_homing(); @@ -335,27 +346,28 @@ void mc_homing_cycle(uint8_t cycle_mask) { limits_init(); } - // Perform tool length probe cycle. Requires probe switch. // NOTE: Upon probe failure, the program will be stopped and placed into ALARM state. uint8_t mc_probe_cycle(float* target, plan_line_data_t* pl_data, uint8_t parser_flags) { // TODO: Need to update this cycle so it obeys a non-auto cycle start. - if (sys.state == STATE_CHECK_MODE) return (GC_PROBE_CHECK_MODE); + if (sys.state == STATE_CHECK_MODE) + return (GC_PROBE_CHECK_MODE); // Finish all queued commands and empty planner buffer before starting probe cycle. protocol_buffer_synchronize(); - if (sys.abort) return (GC_PROBE_ABORT); // Return if system reset has been issued. + if (sys.abort) + return (GC_PROBE_ABORT); // Return if system reset has been issued. // Initialize probing control variables uint8_t is_probe_away = bit_istrue(parser_flags, GC_PARSER_PROBE_IS_AWAY); - uint8_t is_no_error = bit_istrue(parser_flags, GC_PARSER_PROBE_IS_NO_ERROR); - sys.probe_succeeded = false; // Re-initialize probe history before beginning cycle. + uint8_t is_no_error = bit_istrue(parser_flags, GC_PARSER_PROBE_IS_NO_ERROR); + sys.probe_succeeded = false; // Re-initialize probe history before beginning cycle. probe_configure_invert_mask(is_probe_away); // After syncing, check if probe is already triggered. If so, halt and issue alarm. // NOTE: This probe initialization error applies to all probing cycles. - if (probe_get_state()) { // Check probe pin state. + if (probe_get_state()) { // Check probe pin state. system_set_exec_alarm(EXEC_ALARM_PROBE_FAIL_INITIAL); protocol_execute_realtime(); - probe_configure_invert_mask(false); // Re-initialize invert mask before returning. - return (GC_PROBE_FAIL_INIT); // Nothing else to do but bail. + probe_configure_invert_mask(false); // Re-initialize invert mask before returning. + return (GC_PROBE_FAIL_INIT); // Nothing else to do but bail. } // Setup and queue probing motion. Auto cycle-start should not start the cycle. mc_line(target, pl_data); @@ -365,48 +377,54 @@ uint8_t mc_probe_cycle(float* target, plan_line_data_t* pl_data, uint8_t parser_ system_set_exec_state_flag(EXEC_CYCLE_START); do { protocol_execute_realtime(); - if (sys.abort) return (GC_PROBE_ABORT); // Check for system abort + if (sys.abort) + return (GC_PROBE_ABORT); // Check for system abort } while (sys.state != STATE_IDLE); // Probing cycle complete! // Set state variables and error out, if the probe failed and cycle with error is enabled. if (sys_probe_state == PROBE_ACTIVE) { - if (is_no_error) memcpy(sys_probe_position, sys_position, sizeof(sys_position)); - else system_set_exec_alarm(EXEC_ALARM_PROBE_FAIL_CONTACT); + if (is_no_error) + memcpy(sys_probe_position, sys_position, sizeof(sys_position)); + else + system_set_exec_alarm(EXEC_ALARM_PROBE_FAIL_CONTACT); } else { - sys.probe_succeeded = true; // Indicate to system the probing cycle completed successfully. + sys.probe_succeeded = true; // Indicate to system the probing cycle completed successfully. } - sys_probe_state = PROBE_OFF; // Ensure probe state monitor is disabled. - probe_configure_invert_mask(false); // Re-initialize invert mask. - protocol_execute_realtime(); // Check and execute run-time commands + sys_probe_state = PROBE_OFF; // Ensure probe state monitor is disabled. + probe_configure_invert_mask(false); // Re-initialize invert mask. + protocol_execute_realtime(); // Check and execute run-time commands // Reset the stepper and planner buffers to remove the remainder of the probe motion. - st_reset(); // Reset step segment buffer. - plan_reset(); // Reset planner buffer. Zero planner positions. Ensure probing motion is cleared. - plan_sync_position(); // Sync planner position to current machine position. + st_reset(); // Reset step segment buffer. + plan_reset(); // Reset planner buffer. Zero planner positions. Ensure probing motion is cleared. + plan_sync_position(); // Sync planner position to current machine position. #ifdef MESSAGE_PROBE_COORDINATES // All done! Output the probe position as message. report_probe_parameters(CLIENT_ALL); #endif - if (sys.probe_succeeded) return (GC_PROBE_FOUND); // Successful probe cycle. - else return (GC_PROBE_FAIL_END); // Failed to trigger probe within travel. With or without error. + if (sys.probe_succeeded) + return (GC_PROBE_FOUND); // Successful probe cycle. + else + return (GC_PROBE_FAIL_END); // Failed to trigger probe within travel. With or without error. } - // Plans and executes the single special motion case for parking. Independent of main planner buffer. // NOTE: Uses the always free planner ring buffer head to store motion parameters for execution. void mc_parking_motion(float* parking_target, plan_line_data_t* pl_data) { - if (sys.abort) return; // Block during abort. + if (sys.abort) + return; // Block during abort. uint8_t plan_status = plan_buffer_line(parking_target, pl_data); if (plan_status) { bit_true(sys.step_control, STEP_CONTROL_EXECUTE_SYS_MOTION); - bit_false(sys.step_control, STEP_CONTROL_END_MOTION); // Allow parking motion to execute, if feed hold is active. - st_parking_setup_buffer(); // Setup step segment buffer for special parking motion case + bit_false(sys.step_control, STEP_CONTROL_END_MOTION); // Allow parking motion to execute, if feed hold is active. + st_parking_setup_buffer(); // Setup step segment buffer for special parking motion case st_prep_buffer(); st_wake_up(); do { protocol_exec_rt_system(); - if (sys.abort) return; + if (sys.abort) + return; } while (sys.step_control & STEP_CONTROL_EXECUTE_SYS_MOTION); - st_parking_restore_buffer(); // Restore step segment buffer to normal run state. + st_parking_restore_buffer(); // Restore step segment buffer to normal run state. } else { bit_false(sys.step_control, STEP_CONTROL_EXECUTE_SYS_MOTION); protocol_exec_rt_system(); @@ -417,12 +435,12 @@ void mc_parking_motion(float* parking_target, plan_line_data_t* pl_data) { void mc_override_ctrl_update(uint8_t override_state) { // Finish all queued commands before altering override control state protocol_buffer_synchronize(); - if (sys.abort) return; + if (sys.abort) + return; sys.override_ctrl = override_state; } #endif - // Method to ready the system to reset by setting the realtime reset command and killing any // active processes in the system. This also checks if a system reset is issued while Grbl // is in a motion state. If so, kills the steppers and sets the system alarm to flag position @@ -434,7 +452,7 @@ void mc_reset() { system_set_exec_state_flag(EXEC_RESET); // Kill spindle and coolant. spindle->stop(); - coolant_stop(); + coolant_stop(); // turn off all digital I/O immediately fast_sys_io_control(0xFF, false); @@ -451,18 +469,18 @@ void mc_reset() { // the steppers enabled by avoiding the go_idle call altogether, unless the motion state is // violated, by which, all bets are off. if ((sys.state & (STATE_CYCLE | STATE_HOMING | STATE_JOG)) || - (sys.step_control & (STEP_CONTROL_EXECUTE_HOLD | STEP_CONTROL_EXECUTE_SYS_MOTION))) { + (sys.step_control & (STEP_CONTROL_EXECUTE_HOLD | STEP_CONTROL_EXECUTE_SYS_MOTION))) { if (sys.state == STATE_HOMING) { - if (!sys_rt_exec_alarm) system_set_exec_alarm(EXEC_ALARM_HOMING_FAIL_RESET); - } else system_set_exec_alarm(EXEC_ALARM_ABORT_CYCLE); - st_go_idle(); // Force kill steppers. Position has likely been lost. + if (!sys_rt_exec_alarm) + system_set_exec_alarm(EXEC_ALARM_HOMING_FAIL_RESET); + } else + system_set_exec_alarm(EXEC_ALARM_ABORT_CYCLE); + st_go_idle(); // Force kill steppers. Position has likely been lost. } - ganged_mode = SQUARING_MODE_DUAL; // in case an error occurred during squaring + ganged_mode = SQUARING_MODE_DUAL; // in case an error occurred during squaring #ifdef USE_I2S_OUT_STREAM i2s_out_reset(); #endif } } - - diff --git a/Grbl_Esp32/motion_control.h b/Grbl_Esp32/src/MotionControl.h similarity index 76% rename from Grbl_Esp32/motion_control.h rename to Grbl_Esp32/src/MotionControl.h index 24ea92dc..ab904d91 100644 --- a/Grbl_Esp32/motion_control.h +++ b/Grbl_Esp32/src/MotionControl.h @@ -1,5 +1,7 @@ +#pragma once + /* - motion_control.h - high level interface for issuing motion commands + MotionControl.h - high level interface for issuing motion commands Part of Grbl Copyright (c) 2011-2015 Sungeun K. Jeon @@ -22,26 +24,19 @@ along with Grbl. If not, see . */ - - -#ifndef motion_control_h -#define motion_control_h - -#include "grbl.h" - +#include "Grbl.h" // System motion commands must have a line number of zero. #define HOMING_CYCLE_LINE_NUMBER 0 #define PARKING_MOTION_LINE_NUMBER 0 -#define HOMING_CYCLE_ALL 0 // Must be zero. -#define HOMING_CYCLE_X bit(X_AXIS) -#define HOMING_CYCLE_Y bit(Y_AXIS) -#define HOMING_CYCLE_Z bit(Z_AXIS) -#define HOMING_CYCLE_A bit(A_AXIS) -#define HOMING_CYCLE_B bit(B_AXIS) -#define HOMING_CYCLE_C bit(C_AXIS) - +#define HOMING_CYCLE_ALL 0 // Must be zero. +#define HOMING_CYCLE_X bit(X_AXIS) +#define HOMING_CYCLE_Y bit(Y_AXIS) +#define HOMING_CYCLE_Z bit(Z_AXIS) +#define HOMING_CYCLE_A bit(A_AXIS) +#define HOMING_CYCLE_B bit(B_AXIS) +#define HOMING_CYCLE_C bit(C_AXIS) // 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 @@ -53,8 +48,15 @@ void mc_line(float* target, plan_line_data_t* pl_data); // offset == offset from current xyz, axis_XXX defines circle plane in tool space, axis_linear is // the direction of helical travel, radius == circle radius, is_clockwise_arc boolean. Used // for vector transformation direction. -void mc_arc(float* target, plan_line_data_t* pl_data, float* position, float* offset, float radius, - uint8_t axis_0, uint8_t axis_1, uint8_t axis_linear, uint8_t is_clockwise_arc); +void mc_arc(float* target, + plan_line_data_t* pl_data, + float* position, + float* offset, + float radius, + uint8_t axis_0, + uint8_t axis_1, + uint8_t axis_linear, + uint8_t is_clockwise_arc); // Dwell for a specific number of seconds void mc_dwell(float seconds); @@ -73,5 +75,3 @@ void mc_parking_motion(float* parking_target, plan_line_data_t* pl_data); // Performs system reset. If in motion state, kills all motion and sets system alarm. void mc_reset(); - -#endif diff --git a/Grbl_Esp32/src/Motors/Motor.cpp b/Grbl_Esp32/src/Motors/Motor.cpp new file mode 100644 index 00000000..09df8de4 --- /dev/null +++ b/Grbl_Esp32/src/Motors/Motor.cpp @@ -0,0 +1,53 @@ +/* + Motor.cpp + Part of Grbl_ESP32 + 2020 - Bart Dring + 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 . + TODO + Make sure public/private/protected is cleaned up. + Only a few Unipolar axes have been setup in init() + Get rid of Z_SERVO, just reply on Z_SERVO_PIN + Deal with custom machine ... machine_trinamic_setup(); + Class is ready to deal with non SPI pins, but they have not been needed yet. + It would be nice in the config message though + Testing + Done (success) + 3 Axis (3 Standard Steppers) + MPCNC (ganged with shared direction pin) + TMC2130 Pen Laser (trinamics, stallguard tuning) + Unipolar + TODO + 4 Axis SPI (Daisy Chain, Ganged with unique direction pins) + Reference + TMC2130 Datasheet https://www.trinamic.com/fileadmin/assets/Products/ICs_Documents/TMC2130_datasheet.pdf +*/ + +#include "Motor.h" + +namespace Motors { + Motor::Motor() { type_id = MOTOR; } + + void Motor::init() { _homing_mask = 0; } + + void Motor::config_message() {} + void Motor::debug_message() {} + void Motor::read_settings() {} + void Motor::set_disable(bool disable) {} + void Motor::set_direction_pins(uint8_t onMask) {} + void Motor::step(uint8_t step_mask, uint8_t dir_mask) {} + bool Motor::test() { return true; }; // true = OK + void Motor::update() {} + + void Motor::set_axis_name() { sprintf(_axis_name, "%c%s", report_get_axis_letter(axis_index), dual_axis_index ? "2" : " "); } + + void Motor::set_homing_mode(uint8_t homing_mask, bool isHoming) { _homing_mask = homing_mask; } +} diff --git a/Grbl_Esp32/src/Motors/Motor.h b/Grbl_Esp32/src/Motors/Motor.h new file mode 100644 index 00000000..f3bf3629 --- /dev/null +++ b/Grbl_Esp32/src/Motors/Motor.h @@ -0,0 +1,65 @@ +#pragma once + +/* + Motor.h + Header file for Motor Classes + Here is the hierarchy + Motor + Nullmotor + StandardStepper + TrinamicDriver + Unipolar + RC Servo + + These are for motors coordinated by Grbl_ESP32 + See motorClass.cpp for more details + + Part of Grbl_ESP32 + 2020 - Bart Dring + + 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 . +*/ + +#include "Motors.h" + +#include + +namespace Motors { + class Motor { + public: + Motor(); + + virtual void init(); // not in constructor because this also gets called when $$ settings change + virtual void config_message(); + virtual void debug_message(); + virtual void read_settings(); + virtual void set_homing_mode(uint8_t homing_mask, bool isHoming); + virtual void set_disable(bool disable); + virtual void set_direction_pins(uint8_t onMask); + virtual void step(uint8_t step_mask, uint8_t dir_mask); // only used on Unipolar right now + virtual bool test(); + virtual void set_axis_name(); + virtual void update(); + + motor_class_id_t type_id; + uint8_t is_active = false; + + protected: + uint8_t axis_index; // X_AXIS, etc + uint8_t dual_axis_index; // 0 = primary 1=ganged + + bool _showError; + bool _use_mpos = true; + uint8_t _homing_mask; + char _axis_name[10]; // this the name to use when reporting like "X" or "X2" + }; +} diff --git a/Grbl_Esp32/Motors/MotorClass.cpp b/Grbl_Esp32/src/Motors/Motors.cpp similarity index 61% rename from Grbl_Esp32/Motors/MotorClass.cpp rename to Grbl_Esp32/src/Motors/Motors.cpp index def78b8b..ab368d56 100644 --- a/Grbl_Esp32/Motors/MotorClass.cpp +++ b/Grbl_Esp32/src/Motors/Motors.cpp @@ -1,58 +1,65 @@ /* - MotorClass.cpp - Part of Grbl_ESP32 - 2020 - Bart Dring - 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 . - TODO - Make sure public/private/protected is cleaned up. - Only a few Unipolar axes have been setup in init() - Get rid of Z_SERVO, just reply on Z_SERVO_PIN - Deal with custom machine ... machine_trinamic_setup(); - Class is ready to deal with non SPI pins, but they have not been needed yet. - It would be nice in the config message though - Testing - Done (success) - 3 Axis (3 Standard Steppers) - MPCNC (ganged with shared direction pin) - TMC2130 Pen Laser (trinamics, stallguard tuning) - Unipolar - TODO - 4 Axis SPI (Daisy Chain, Ganged with unique direction pins) - Reference - TMC2130 Datasheet https://www.trinamic.com/fileadmin/assets/Products/ICs_Documents/TMC2130_datasheet.pdf + Motors.cpp + Part of Grbl_ESP32 + 2020 - Bart Dring + 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 . + TODO + Make sure public/private/protected is cleaned up. + Only a few Unipolar axes have been setup in init() + Get rid of Z_SERVO, just reply on Z_SERVO_PIN + Deal with custom machine ... machine_trinamic_setup(); + Class is ready to deal with non SPI pins, but they have not been needed yet. + It would be nice in the config message though + Testing + Done (success) + 3 Axis (3 Standard Steppers) + MPCNC (ganged with shared direction pin) + TMC2130 Pen Laser (trinamics, stallguard tuning) + Unipolar + TODO + 4 Axis SPI (Daisy Chain, Ganged with unique direction pins) + Reference + TMC2130 Datasheet https://www.trinamic.com/fileadmin/assets/Products/ICs_Documents/TMC2130_datasheet.pdf */ -#include "../grbl.h" -#include "TrinamicDriverClass.cpp" -#include "StandardStepperClass.cpp" -#include "UnipolarMotorClass.cpp" -#include "RcServoClass.cpp" -//#include "SolenoidClass.cpp" +#include "Motors.h" -Motor* myMotor[MAX_AXES][MAX_GANGED]; // number of axes (normal and ganged) -static TaskHandle_t readSgTaskHandle = 0; // for realtime stallguard data diaplay +#include "Motor.h" +#include "../Grbl.h" + +#include "NullMotor.h" +#include "StandardStepper.h" +#include "UnipolarMotor.h" +#include "RcServo.h" +#include "TrinamicDriver.h" + +Motors::Motor* myMotor[MAX_AXES][MAX_GANGED]; // number of axes (normal and ganged) +static TaskHandle_t readSgTaskHandle = 0; // for realtime stallguard data diaplay static TaskHandle_t servoUpdateTaskHandle = 0; -uint8_t rmt_chan_num[MAX_AXES][MAX_GANGED]; +uint8_t rmt_chan_num[MAX_AXES][MAX_GANGED]; rmt_item32_t rmtItem[2]; rmt_config_t rmtConfig; -bool motor_class_steps; // true if at least one motor class is handling steps +bool motor_class_steps; // true if at least one motor class is handling steps void init_motors() { + using namespace Motors; + grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Init Motors"); #ifdef X_TRINAMIC_DRIVER - myMotor[X_AXIS][0] = new TrinamicDriver(X_AXIS, X_STEP_PIN, X_DIRECTION_PIN, X_DISABLE_PIN, X_CS_PIN, X_TRINAMIC_DRIVER, X_RSENSE, get_next_trinamic_driver_index()); + myMotor[X_AXIS][0] = new TrinamicDriver( + X_AXIS, X_STEP_PIN, X_DIRECTION_PIN, X_DISABLE_PIN, X_CS_PIN, X_TRINAMIC_DRIVER, X_RSENSE, get_next_trinamic_driver_index()); #elif defined(X_SERVO_PIN) myMotor[X_AXIS][0] = new RcServo(X_AXIS, X_SERVO_PIN, X_SERVO_RANGE_MIN, X_SERVO_RANGE_MAX); #elif defined(X_UNIPOLAR) @@ -64,7 +71,8 @@ void init_motors() { #endif #ifdef X2_TRINAMIC_DRIVER - myMotor[X_AXIS][1] = new TrinamicDriver(X2_AXIS, X2_STEP_PIN, X2_DIRECTION_PIN, X2_DISABLE_PIN, X2_CS_PIN, X2_TRINAMIC_DRIVER, X2_RSENSE, get_next_trinamic_driver_index()); + myMotor[X_AXIS][1] = new TrinamicDriver( + X2_AXIS, X2_STEP_PIN, X2_DIRECTION_PIN, X2_DISABLE_PIN, X2_CS_PIN, X2_TRINAMIC_DRIVER, X2_RSENSE, get_next_trinamic_driver_index()); #elif defined(X2_SERVO_PIN) myMotor[X_AXIS][1] = new RcServo(X2_AXIS, X2_SERVO_PIN, X2_SERVO_RANGE_MIN, X2_SERVO_RANGE_MAX); #elif defined(X2_UNIPOLAR) @@ -75,10 +83,10 @@ void init_motors() { myMotor[X_AXIS][1] = new Nullmotor(); #endif - // this WILL be done better with settings #ifdef Y_TRINAMIC_DRIVER - myMotor[Y_AXIS][0] = new TrinamicDriver(Y_AXIS, Y_STEP_PIN, Y_DIRECTION_PIN, Y_DISABLE_PIN, Y_CS_PIN, Y_TRINAMIC_DRIVER, Y_RSENSE, get_next_trinamic_driver_index()); + myMotor[Y_AXIS][0] = new TrinamicDriver( + Y_AXIS, Y_STEP_PIN, Y_DIRECTION_PIN, Y_DISABLE_PIN, Y_CS_PIN, Y_TRINAMIC_DRIVER, Y_RSENSE, get_next_trinamic_driver_index()); #elif defined(Y_SERVO_PIN) myMotor[Y_AXIS][0] = new RcServo(Y_AXIS, Y_SERVO_PIN, Y_SERVO_RANGE_MIN, Y_SERVO_RANGE_MAX); #elif defined(Y_UNIPOLAR) @@ -90,7 +98,8 @@ void init_motors() { #endif #ifdef Y2_TRINAMIC_DRIVER - myMotor[Y_AXIS][1] = new TrinamicDriver(Y2_AXIS, Y2_STEP_PIN, Y2_DIRECTION_PIN, Y2_DISABLE_PIN, Y2_CS_PIN, Y2_TRINAMIC_DRIVER, Y2_RSENSE, get_next_trinamic_driver_index()); + myMotor[Y_AXIS][1] = new TrinamicDriver( + Y2_AXIS, Y2_STEP_PIN, Y2_DIRECTION_PIN, Y2_DISABLE_PIN, Y2_CS_PIN, Y2_TRINAMIC_DRIVER, Y2_RSENSE, get_next_trinamic_driver_index()); #elif defined(Y2_SERVO_PIN) myMotor[Y_AXIS][1] = new RcServo(Y2_AXIS, Y2_SERVO_PIN, Y2_SERVO_RANGE_MIN, Y2_SERVO_RANGE_MAX); #elif defined(Y2_UNIPOLAR) @@ -101,10 +110,10 @@ void init_motors() { myMotor[Y_AXIS][1] = new Nullmotor(); #endif - // this WILL be done better with settings #ifdef Z_TRINAMIC_DRIVER - myMotor[Z_AXIS][0] = new TrinamicDriver(Z_AXIS, Z_STEP_PIN, Z_DIRECTION_PIN, Z_DISABLE_PIN, Z_CS_PIN, Z_TRINAMIC_DRIVER, Z_RSENSE, get_next_trinamic_driver_index()); + myMotor[Z_AXIS][0] = new TrinamicDriver( + Z_AXIS, Z_STEP_PIN, Z_DIRECTION_PIN, Z_DISABLE_PIN, Z_CS_PIN, Z_TRINAMIC_DRIVER, Z_RSENSE, get_next_trinamic_driver_index()); #elif defined(Z_SERVO_PIN) myMotor[Z_AXIS][0] = new RcServo(Z_AXIS, Z_SERVO_PIN, Z_SERVO_RANGE_MIN, Z_SERVO_RANGE_MAX); #elif defined(Z_UNIPOLAR) @@ -116,7 +125,8 @@ void init_motors() { #endif #ifdef Z2_TRINAMIC_DRIVER - myMotor[Z_AXIS][1] = new TrinamicDriver(Z2_AXIS, Z2_STEP_PIN, Z2_DIRECTION_PIN, Z2_DISABLE_PIN, Z2_CS_PIN, Z2_TRINAMIC_DRIVER, Z2_RSENSE, get_next_trinamic_driver_index()); + myMotor[Z_AXIS][1] = new TrinamicDriver( + Z2_AXIS, Z2_STEP_PIN, Z2_DIRECTION_PIN, Z2_DISABLE_PIN, Z2_CS_PIN, Z2_TRINAMIC_DRIVER, Z2_RSENSE, get_next_trinamic_driver_index()); #elif defined(Z2_SERVO_PIN) myMotor[Z_AXIS][1] = new RcServo(Z2_AXIS, Z2_SERVO_PIN, Z2_SERVO_RANGE_MIN, Z2_SERVO_RANGE_MAX); #elif defined(Z2_UNIPOLAR) @@ -129,7 +139,8 @@ void init_motors() { // this WILL be done better with settings #ifdef A_TRINAMIC_DRIVER - myMotor[A_AXIS][0] = new TrinamicDriver(A_AXIS, A_STEP_PIN, A_DIRECTION_PIN, A_DISABLE_PIN, A_CS_PIN, A_TRINAMIC_DRIVER, A_RSENSE, get_next_trinamic_driver_index()); + myMotor[A_AXIS][0] = new TrinamicDriver( + A_AXIS, A_STEP_PIN, A_DIRECTION_PIN, A_DISABLE_PIN, A_CS_PIN, A_TRINAMIC_DRIVER, A_RSENSE, get_next_trinamic_driver_index()); #elif defined(A_SERVO_PIN) myMotor[A_AXIS][0] = new RcServo(A_AXIS, A_SERVO_PIN, A_SERVO_RANGE_MIN, A_SERVO_RANGE_MAX); #elif defined(A_UNIPOLAR) @@ -141,7 +152,8 @@ void init_motors() { #endif #ifdef A2_TRINAMIC_DRIVER - myMotor[A_AXIS][1] = new TrinamicDriver(A2_AXIS, A2_STEP_PIN, A2_DIRECTION_PIN, A2_DISABLE_PIN, A2_CS_PIN, A2_TRINAMIC_DRIVER, A2_RSENSE, get_next_trinamic_driver_index()); + myMotor[A_AXIS][1] = new TrinamicDriver( + A2_AXIS, A2_STEP_PIN, A2_DIRECTION_PIN, A2_DISABLE_PIN, A2_CS_PIN, A2_TRINAMIC_DRIVER, A2_RSENSE, get_next_trinamic_driver_index()); #elif defined(A2_SERVO_PIN) myMotor[A_AXIS][1] = new RcServo(A2_AXIS, A2_SERVO_PIN, A2_SERVO_RANGE_MIN, A2_SERVO_RANGE_MAX); #elif defined(A2_UNIPOLAR) @@ -154,7 +166,8 @@ void init_motors() { // this WILL be done better with settings #ifdef B_TRINAMIC_DRIVER - myMotor[B_AXIS][0] = new TrinamicDriver(B_AXIS, B_STEP_PIN, B_DIRECTION_PIN, B_DISABLE_PIN, B_CS_PIN, B_TRINAMIC_DRIVER, B_RSENSE, get_next_trinamic_driver_index()); + myMotor[B_AXIS][0] = new TrinamicDriver( + B_AXIS, B_STEP_PIN, B_DIRECTION_PIN, B_DISABLE_PIN, B_CS_PIN, B_TRINAMIC_DRIVER, B_RSENSE, get_next_trinamic_driver_index()); #elif defined(B_SERVO_PIN) myMotor[B_AXIS][0] = new RcServo(B_AXIS, B_SERVO_PIN, B_SERVO_RANGE_MIN, B_SERVO_RANGE_MAX); #elif defined(B_UNIPOLAR) @@ -166,7 +179,8 @@ void init_motors() { #endif #ifdef B2_TRINAMIC_DRIVER - myMotor[B_AXIS][1] = new TrinamicDriver(B2_AXIS, B2_STEP_PIN, B2_DIRECTION_PIN, B2_DISABLE_PIN, B2_CS_PIN, B2_TRINAMIC_DRIVER, B2_RSENSE, get_next_trinamic_driver_index()); + myMotor[B_AXIS][1] = new TrinamicDriver( + B2_AXIS, B2_STEP_PIN, B2_DIRECTION_PIN, B2_DISABLE_PIN, B2_CS_PIN, B2_TRINAMIC_DRIVER, B2_RSENSE, get_next_trinamic_driver_index()); #elif defined(B2_SERVO_PIN) myMotor[B_AXIS][1] = new RcServo(B2_AXIS, B2_SERVO_PIN, B2_SERVO_RANGE_MIN, B2_SERVO_RANGE_MAX); #elif defined(B2_UNIPOLAR) @@ -179,7 +193,8 @@ void init_motors() { // this WILL be done better with settings #ifdef C_TRINAMIC_DRIVER - myMotor[C_AXIS][0] = new TrinamicDriver(C_AXIS, C_STEP_PIN, C_DIRECTION_PIN, C_DISABLE_PIN, C_CS_PIN, C_TRINAMIC_DRIVER, C_RSENSE, get_next_trinamic_driver_index()); + myMotor[C_AXIS][0] = new TrinamicDriver( + C_AXIS, C_STEP_PIN, C_DIRECTION_PIN, C_DISABLE_PIN, C_CS_PIN, C_TRINAMIC_DRIVER, C_RSENSE, get_next_trinamic_driver_index()); #elif defined(C_SERVO_PIN) myMotor[C_AXIS][0] = new RcServo(C_AXIS, C_SERVO_PIN, C_SERVO_RANGE_MIN, C_SERVO_RANGE_MAX); #elif defined(C_UNIPOLAR) @@ -191,7 +206,8 @@ void init_motors() { #endif #ifdef C2_TRINAMIC_DRIVER - myMotor[C_AXIS][1] = new TrinamicDriver(C2_AXIS, C2_STEP_PIN, C2_DIRECTION_PIN, C2_DISABLE_PIN, C2_CS_PIN, C2_TRINAMIC_DRIVER, C2_RSENSE, get_next_trinamic_driver_index()); + myMotor[C_AXIS][1] = new TrinamicDriver( + C2_AXIS, C2_STEP_PIN, C2_DIRECTION_PIN, C2_DISABLE_PIN, C2_CS_PIN, C2_TRINAMIC_DRIVER, C2_RSENSE, get_next_trinamic_driver_index()); #elif defined(C2_SERVO_PIN) myMotor[C_AXIS][1] = new RcServo(C2_AXIS, C2_SERVO_PIN, C2_SERVO_RANGE_MIN, C2_SERVO_RANGE_MAX); #elif defined(C2_UNIPOLAR) @@ -202,61 +218,39 @@ void init_motors() { myMotor[C_AXIS][1] = new Nullmotor(); #endif - #ifdef USE_STEPSTICK + grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Using StepStick Mode"); -#ifdef STEPPER_MS1 - digitalWrite(STEPPER_MS1, HIGH); - pinMode(STEPPER_MS1, OUTPUT); -#endif -#ifdef STEPPER_MS2 - digitalWrite(STEPPER_MS2, HIGH); - pinMode(STEPPER_MS2, OUTPUT); -#endif -#ifdef STEPPER_X_MS3 - digitalWrite(STEPPER_X_MS3, HIGH); - pinMode(STEPPER_X_MS3, OUTPUT); -#endif -#ifdef STEPPER_Y_MS3 - digitalWrite(STEPPER_Y_MS3, HIGH); - pinMode(STEPPER_Y_MS3, OUTPUT); -#endif -#ifdef STEPPER_Z_MS3 - digitalWrite(STEPPER_Z_MS3, HIGH); - pinMode(STEPPER_Z_MS3, OUTPUT); -#endif -#ifdef STEPPER_A_MS3 - digitalWrite(STEPPER_A_MS3, HIGH); - pinMode(STEPPER_A_MS3, OUTPUT); -#endif -#ifdef STEPPER_B_MS3 - digitalWrite(STEPPER_B_MS3, HIGH); - pinMode(STEPPER_B_MS3, OUTPUT); -#endif -#ifdef STEPPER_C_MS3 - digitalWrite(STEPPER_C_MS3, HIGH); - pinMode(STEPPER_C_MS3, OUTPUT); -#endif -#ifdef STEPPER_RESET + + uint8_t ms3_pins[MAX_N_AXIS][2] = { { X_STEPPER_MS3, X2_STEPPER_MS3 }, { Y_STEPPER_MS3, Y2_STEPPER_MS3 }, { Z_STEPPER_MS3, Z2_STEPPER_MS3 }, + { A_STEPPER_MS3, A2_STEPPER_MS3 }, { B_STEPPER_MS3, B2_STEPPER_MS3 }, { C_STEPPER_MS3, C2_STEPPER_MS3 } }; + + for (int axis = 0; axis < N_AXIS; axis++) { + for (int gang_index = 0; gang_index < 2; gang_index++) { + uint8_t pin = ms3_pins[axis][gang_index]; + if (pin != UNDEFINED_PIN) { + digitalWrite(pin, HIGH); + pinMode(pin, OUTPUT); + } + } + } + +# ifdef STEPPER_RESET // !RESET pin on steppers (MISO On Schematic) digitalWrite(STEPPER_RESET, HIGH); pinMode(STEPPER_RESET, OUTPUT); -#endif +# endif #endif if (STEPPERS_DISABLE_PIN != UNDEFINED_PIN) { - pinMode(STEPPERS_DISABLE_PIN, OUTPUT); // global motor enable pin - grbl_msg_sendf(CLIENT_SERIAL, - MSG_LEVEL_INFO, - "Global stepper disable pin:%s", - pinName(STEPPERS_DISABLE_PIN)); + pinMode(STEPPERS_DISABLE_PIN, OUTPUT); // global motor enable pin + grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Global stepper disable pin:%s", pinName(STEPPERS_DISABLE_PIN)); } // certain motors need features to be turned on. Check them here for (uint8_t axis = X_AXIS; axis < N_AXIS; axis++) { for (uint8_t gang_index = 0; gang_index < 2; gang_index++) { - if (myMotor[axis][gang_index]->type_id == UNIPOLAR_MOTOR) motor_class_steps = true; @@ -272,39 +266,36 @@ void init_motors() { if (motors_have_type_id(TRINAMIC_SPI_MOTOR)) { grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "TMCStepper Library Ver. 0x%06x", TMCSTEPPER_VERSION); - xTaskCreatePinnedToCore(readSgTask, // task - "readSgTask", // name for task - 4096, // size of task stack - NULL, // parameters - 1, // priority + xTaskCreatePinnedToCore(readSgTask, // task + "readSgTask", // name for task + 4096, // size of task stack + NULL, // parameters + 1, // priority &readSgTaskHandle, - 0 // core - ); + 0 // core + ); if (stallguard_debug_mask->get() != 0) grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Stallguard debug enabled: %d", stallguard_debug_mask->get()); } if (motors_have_type_id(RC_SERVO_MOTOR)) { - xTaskCreatePinnedToCore(servoUpdateTask, // task - "servoUpdateTask", // name for task - 4096, // size of task stack - NULL, // parameters - 1, // priority + xTaskCreatePinnedToCore(servoUpdateTask, // task + "servoUpdateTask", // name for task + 4096, // size of task stack + NULL, // parameters + 1, // priority &servoUpdateTaskHandle, - 0 // core - ); + 0 // core + ); } } - - void servoUpdateTask(void* pvParameters) { - TickType_t xLastWakeTime; + TickType_t xLastWakeTime; const TickType_t xUpdate = SERVO_TIMER_INT_FREQ; // in ticks (typically ms) - - xLastWakeTime = xTaskGetTickCount(); // Initialise the xLastWakeTime variable with the current time. - while (true) { // don't ever return from this or the task dies + xLastWakeTime = xTaskGetTickCount(); // Initialise the xLastWakeTime variable with the current time. + while (true) { // don't ever return from this or the task dies //grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Servo update"); @@ -328,7 +319,6 @@ bool motors_have_type_id(motor_class_id_t id) { return false; } - void motors_set_disable(bool disable) { static bool previous_state = false; @@ -338,7 +328,7 @@ void motors_set_disable(bool disable) { previous_state = disable; if (step_enable_invert->get()) { - disable = !disable; // Apply pin invert. + disable = !disable; // Apply pin invert. } digitalWrite(STEPPERS_DISABLE_PIN, disable); @@ -369,7 +359,6 @@ void motors_set_homing_mode(uint8_t homing_mask, bool isHoming) { } } - void motors_set_direction_pins(uint8_t onMask) { static uint8_t previous_val = 255; // should never be this value if (previous_val == onMask) @@ -388,7 +377,7 @@ void motors_set_direction_pins(uint8_t onMask) { // need to be inserted into the order of axes. uint8_t get_next_trinamic_driver_index() { #ifdef TRINAMIC_DAISY_CHAIN - static uint8_t index = 1; // they start at 1 + static uint8_t index = 1; // they start at 1 return index++; #else return -1; @@ -397,7 +386,7 @@ uint8_t get_next_trinamic_driver_index() { // some motor objects, like unipolar need step signals void motors_step(uint8_t step_mask, uint8_t dir_mask) { - if (motor_class_steps) { // determined in init_motors if any motors need to handle steps + if (motor_class_steps) { // determined in init_motors if any motors need to handle steps for (uint8_t gang_index = 0; gang_index < 2; gang_index++) { for (uint8_t axis = X_AXIS; axis < N_AXIS; axis++) myMotor[axis][gang_index]->step(step_mask, dir_mask); @@ -406,14 +395,14 @@ void motors_step(uint8_t step_mask, uint8_t dir_mask) { } /* - This will print StallGuard data that is useful for tuning. + This will print StallGuard data that is useful for tuning. */ void readSgTask(void* pvParameters) { - TickType_t xLastWakeTime; + TickType_t xLastWakeTime; const TickType_t xreadSg = 200; // in ticks (typically ms) - xLastWakeTime = xTaskGetTickCount(); // Initialise the xLastWakeTime variable with the current time. - while (true) { // don't ever return from this or the task dies + xLastWakeTime = xTaskGetTickCount(); // Initialise the xLastWakeTime variable with the current time. + while (true) { // don't ever return from this or the task dies if (motorSettingChanged) { motors_read_settings(); motorSettingChanged = false; @@ -428,13 +417,12 @@ void readSgTask(void* pvParameters) { myMotor[axis][gang_index]->debug_message(); } } - } // sys.state - } // if mask + } // sys.state + } // if mask vTaskDelayUntil(&xLastWakeTime, xreadSg); } } - #ifdef USE_I2S_OUT // // Override default function and insert a short delay @@ -444,32 +432,3 @@ void TMC2130Stepper::switchCSpin(bool state) { i2s_out_delay(); } #endif - - - -// ============================== Class Methods ================================================ - -Motor :: Motor() { - type_id = MOTOR; -} - -void Motor :: init() { - _homing_mask = 0; -} - -void Motor :: config_message() {} -void Motor :: debug_message() {} -void Motor :: read_settings() {} -void Motor :: set_disable(bool disable) {} -void Motor :: set_direction_pins(uint8_t onMask) {} -void Motor :: step(uint8_t step_mask, uint8_t dir_mask) {} -bool Motor :: test() {return true;}; // true = OK -void Motor :: update() {} - -void Motor :: set_axis_name() { - sprintf(_axis_name, "%c%s", report_get_axis_letter(axis_index), dual_axis_index ? "2" : ""); -} - -void Motor :: set_homing_mode(uint8_t homing_mask, bool isHoming) { - _homing_mask = homing_mask; -} diff --git a/Grbl_Esp32/src/Motors/Motors.h b/Grbl_Esp32/src/Motors/Motors.h new file mode 100644 index 00000000..006e6094 --- /dev/null +++ b/Grbl_Esp32/src/Motors/Motors.h @@ -0,0 +1,53 @@ +#pragma once + +/* + Motors.h + Header file for Motor Classes + Here is the hierarchy + Motor + Nullmotor + StandardStepper + TrinamicDriver + Unipolar + RC Servo + + These are for motors coordinated by Grbl_ESP32 + See motorClass.cpp for more details + + Part of Grbl_ESP32 + 2020 - Bart Dring + + 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 . +*/ + +#include "../Grbl.h" +#include // https://github.com/teemuatlut/TMCStepper + +extern uint8_t rmt_chan_num[MAX_AXES][2]; +extern rmt_item32_t rmtItem[2]; +extern rmt_config_t rmtConfig; + +typedef enum { MOTOR, NULL_MOTOR, STANDARD_MOTOR, TRINAMIC_SPI_MOTOR, UNIPOLAR_MOTOR, RC_SERVO_MOTOR, SOLENOID } motor_class_id_t; + +// These are used for setup and to talk to the motors as a group. +void init_motors(); +uint8_t get_next_trinamic_driver_index(); +bool motors_have_type_id(motor_class_id_t id); +void readSgTask(void* pvParameters); +void motors_read_settings(); +void motors_set_homing_mode(uint8_t homing_mask, bool isHoming); +void motors_set_disable(bool disable); +void motors_set_direction_pins(uint8_t onMask); +void motors_step(uint8_t step_mask, uint8_t dir_mask); +void servoUpdateTask(void* pvParameters); + +extern bool motor_class_steps; // true if at least one motor class is handling steps diff --git a/Grbl_Esp32/src/Motors/NullMotor.h b/Grbl_Esp32/src/Motors/NullMotor.h new file mode 100644 index 00000000..e360392d --- /dev/null +++ b/Grbl_Esp32/src/Motors/NullMotor.h @@ -0,0 +1,7 @@ +#pragma once + +#include "Motor.h" + +namespace Motors { + class Nullmotor : public Motor {}; +} diff --git a/Grbl_Esp32/src/Motors/RcServo.cpp b/Grbl_Esp32/src/Motors/RcServo.cpp new file mode 100644 index 00000000..189cbbd2 --- /dev/null +++ b/Grbl_Esp32/src/Motors/RcServo.cpp @@ -0,0 +1,189 @@ +/* + RcServo.cpp + + This allows an RcServo to be used like any other motor. Serrvos + do have limitation in travel and speed, so you do need to respect that. + + Part of Grbl_ESP32 + + 2020 - Bart Dring + + Servos have a limited travel, so they map the travel across a range in + the current work coordinatee system. The servo can only travel as far + as the range, but the internal axis value can keep going. + + Range: The range is specified in the machine definition file with... + #define X_SERVO_RANGE_MIN 0.0 + #define X_SERVO_RANGE_MAX 5.0 + + Direction: The direction can be changed using the $3 setting for the axis + + Homing: During homing, the servo will move to one of the endpoints. The + endpoint is determined by the $23 or $HomingDirInvertMask setting for the axis. + Do not define a homing cycle for the axis with the servo. + You do need at least 1 homing cycle. TODO: Fix this + + Calibration. You can tweak the endpoints using the $10n or nStepsPerMm and + $13n or $xMaxTravel setting, where n is the axis. + The value is a percent. If you secify a percent outside the + the range specified by the values below, it will be reset to 100.0 (100% ... no change) + The calibration adjusts in direction of positive momement, so a value above 100% moves + towards the higher axis value. + + #define SERVO_CAL_MIN + #define SERVO_CAL_MAX + + Grbl_ESP32 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 . +*/ + +#include "RcServo.h" + +namespace Motors { + RcServo::RcServo() {} + + RcServo::RcServo(uint8_t axis_index, uint8_t pwm_pin, float min, float max) { + type_id = RC_SERVO_MOTOR; + this->axis_index = axis_index % MAX_AXES; + this->dual_axis_index = axis_index < MAX_AXES ? 0 : 1; // 0 = primary 1 = ganged + this->_pwm_pin = pwm_pin; + _position_min = min; + _position_max = max; + init(); + } + + void RcServo::init() { + read_settings(); + _channel_num = sys_get_next_PWM_chan_num(); + ledcSetup(_channel_num, SERVO_PULSE_FREQ, SERVO_PULSE_RES_BITS); + ledcAttachPin(_pwm_pin, _channel_num); + _current_pwm_duty = 0; + is_active = true; // as opposed to NullMotors, this is a real motor + set_axis_name(); + config_message(); + } + + void RcServo::config_message() { + grbl_msg_sendf(CLIENT_SERIAL, + MSG_LEVEL_INFO, + "%s Axis RC Servo motor Output:%d Min:%5.3fmm Max:%5.3fmm", + _axis_name, + _pwm_pin, + _position_min, + _position_max); + } + + void RcServo::_write_pwm(uint32_t duty) { + // to prevent excessive calls to ledcWrite, make sure duty hass changed + if (duty == _current_pwm_duty) + return; + + _current_pwm_duty = duty; + + //grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "%s Servo Pwm %d", _axis_name, duty); + ledcWrite(_channel_num, duty); + } + + // sets the PWM to zero. This allows most servos to be manually moved + void RcServo::set_disable(bool disable) { + return; + _disabled = disable; + if (_disabled) + _write_pwm(0); + } + + void RcServo::set_homing_mode(bool is_homing, bool isHoming) { + float home_pos = 0.0; + + if (!is_homing) + return; + + if (bit_istrue(homing_dir_mask->get(), bit(axis_index))) + home_pos = _position_min; + else + home_pos = _position_max; + + //grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Servo set home %d %3.2f", is_homing, home_pos); + sys_position[axis_index] = home_pos * axis_settings[axis_index]->steps_per_mm->get(); // convert to steps + } + + void RcServo::update() { set_location(); } + + void RcServo::set_location() { + uint32_t servo_pulse_len; + float servo_pos, mpos, offset; + // skip location if we are in alarm mode + + //grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "locate"); + _get_calibration(); + + if (sys.state == STATE_ALARM) { + set_disable(true); + return; + } + + mpos = system_convert_axis_steps_to_mpos(sys_position, axis_index); // get the axis machine position in mm + offset = gc_state.coord_system[axis_index] + gc_state.coord_offset[axis_index]; // get the current axis work offset + servo_pos = mpos - offset; // determine the current work position + + // determine the pulse length + servo_pulse_len = (uint32_t)mapConstrain(servo_pos, _position_min, _position_max, _pwm_pulse_min, _pwm_pulse_max); + + _write_pwm(servo_pulse_len); + } + + void RcServo::read_settings() { _get_calibration(); } + + // this should change to use its own settings. + void RcServo::_get_calibration() { + float _cal_min = 1.0; + float _cal_max = 1.0; + + //grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Read settings"); + + // make sure the min is in range + if ((axis_settings[axis_index]->steps_per_mm->get() < SERVO_CAL_MIN) || + (axis_settings[axis_index]->steps_per_mm->get() > SERVO_CAL_MAX)) { + grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Servo calibration ($10%d) value error. Reset to 100", axis_index); + char reset_val[] = "100"; + axis_settings[axis_index]->steps_per_mm->setStringValue(reset_val); + } + + // make sure the max is in range + // Note: Max travel is set positive via $$, but stored as a negative number + if ((axis_settings[axis_index]->max_travel->get() < SERVO_CAL_MIN) || (axis_settings[axis_index]->max_travel->get() > SERVO_CAL_MAX)) { + grbl_msg_sendf(CLIENT_SERIAL, + MSG_LEVEL_INFO, + "Servo calibration ($13%d) value error. %3.2f Reset to 100", + axis_index, + axis_settings[axis_index]->max_travel->get()); + char reset_val[] = "100"; + axis_settings[axis_index]->max_travel->setStringValue(reset_val); + } + + _pwm_pulse_min = SERVO_MIN_PULSE; + _pwm_pulse_max = SERVO_MAX_PULSE; + + if (bit_istrue(dir_invert_mask->get(), bit(axis_index))) { // normal direction + _cal_min = 2.0 - (axis_settings[axis_index]->steps_per_mm->get() / 100.0); + _cal_max = 2.0 - (axis_settings[axis_index]->max_travel->get() / 100.0); + swap(_pwm_pulse_min, _pwm_pulse_max); + } else { // inverted direction + _cal_min = (axis_settings[axis_index]->steps_per_mm->get() / 100.0); + _cal_max = (axis_settings[axis_index]->max_travel->get() / 100.0); + } + + _pwm_pulse_min *= _cal_min; + _pwm_pulse_max *= _cal_max; + + //grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Servo calibration min:%1.2f max %1.2f", _pwm_pulse_min, _pwm_pulse_max); + } +} diff --git a/Grbl_Esp32/src/Motors/RcServo.h b/Grbl_Esp32/src/Motors/RcServo.h new file mode 100644 index 00000000..60c06a76 --- /dev/null +++ b/Grbl_Esp32/src/Motors/RcServo.h @@ -0,0 +1,55 @@ +#pragma once + +/* + RcServo.h + + Part of Grbl_ESP32 + + 2020 - Bart Dring + + 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 . +*/ + +#include "Motor.h" + +#include "RcServoSettings.h" + +namespace Motors { + class RcServo : public Motor { + public: + RcServo(); + RcServo(uint8_t axis_index, uint8_t pwm_pin, float min, float max); + virtual void config_message(); + virtual void init(); + void _write_pwm(uint32_t duty); + virtual void set_disable(bool disable); + virtual void update(); + void read_settings(); + void set_homing_mode(bool is_homing, bool isHoming); + + protected: + void set_location(); + void _get_calibration(); + + uint8_t _pwm_pin; + uint8_t _channel_num; + uint32_t _current_pwm_duty; + bool _disabled; + + float _position_min; + float _position_max; // position in millimeters + float _homing_position; + + float _pwm_pulse_min; + float _pwm_pulse_max; + }; +} diff --git a/Grbl_Esp32/src/Motors/RcServoSettings.h b/Grbl_Esp32/src/Motors/RcServoSettings.h new file mode 100644 index 00000000..c07655ce --- /dev/null +++ b/Grbl_Esp32/src/Motors/RcServoSettings.h @@ -0,0 +1,26 @@ +#pragma once + +// this is the pulse range of a the servo. Typical servos are 0.001 to 0.002 seconds +// some servos have a wider range. You can adjust this here or in the calibration feature +#define SERVO_MIN_PULSE_SEC 0.001 // min pulse in seconds +#define SERVO_MAX_PULSE_SEC 0.002 // max pulse in seconds + +#define SERVO_POSITION_MIN_DEFAULT 0.0 // mm +#define SERVO_POSITION_MAX_DEFAULT 20.0 // mm + +#define SERVO_PULSE_FREQ 50 // 50Hz ...This is a standard analog servo value. Digital ones can repeat faster + +#define SERVO_PULSE_RES_BITS 16 // bits of resolution of PWM (16 is max) +#define SERVO_PULSE_RES_COUNT 65535 // see above TODO...do the math here 2^SERVO_PULSE_RES_BITS + +#define SERVO_TIME_PER_BIT ((1.0 / (float)SERVO_PULSE_FREQ) / ((float)SERVO_PULSE_RES_COUNT)) // seconds + +#define SERVO_MIN_PULSE (uint16_t)(SERVO_MIN_PULSE_SEC / SERVO_TIME_PER_BIT) // in timer counts +#define SERVO_MAX_PULSE (uint16_t)(SERVO_MAX_PULSE_SEC / SERVO_TIME_PER_BIT) // in timer counts + +#define SERVO_PULSE_RANGE (SERVO_MAX_PULSE - SERVO_MIN_PULSE) + +#define SERVO_CAL_MIN 20.0 // Percent: the minimum allowable calibration value +#define SERVO_CAL_MAX 180.0 // Percent: the maximum allowable calibration value + +#define SERVO_TIMER_INT_FREQ 50.0 // Hz This is the task frequency diff --git a/Grbl_Esp32/src/Motors/Solenoid.h b/Grbl_Esp32/src/Motors/Solenoid.h new file mode 100644 index 00000000..fb973083 --- /dev/null +++ b/Grbl_Esp32/src/Motors/Solenoid.h @@ -0,0 +1,18 @@ +#pragma once + +#include "RcServo.h" + +namespace Motors { + class Solenoid : public RcServo { + public: + Solenoid(); + Solenoid(uint8_t axis_index, gpio_num_t pwm_pin, float transition_poiont); + void config_message(); + void set_location(); + void update(); + void init(); + void set_disable(bool disable); + + float _transition_poiont; + }; +} diff --git a/Grbl_Esp32/src/Motors/StandardStepper.cpp b/Grbl_Esp32/src/Motors/StandardStepper.cpp new file mode 100644 index 00000000..79c6fdbd --- /dev/null +++ b/Grbl_Esp32/src/Motors/StandardStepper.cpp @@ -0,0 +1,103 @@ +/* + StandardStepper.cpp + + This is used for a stepper motor that just requires step and direction + pins. + TODO: Add an enable pin + + Part of Grbl_ESP32 + + 2020 - Bart Dring + + 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 . +*/ + +#include "StandardStepper.h" + +namespace Motors { + StandardStepper::StandardStepper() {} + + StandardStepper::StandardStepper(uint8_t axis_index, uint8_t step_pin, uint8_t dir_pin, uint8_t disable_pin) { + type_id = STANDARD_MOTOR; + this->axis_index = axis_index % MAX_AXES; + this->dual_axis_index = axis_index < MAX_AXES ? 0 : 1; // 0 = primary 1 = ganged + this->step_pin = step_pin; + this->dir_pin = dir_pin; + this->disable_pin = disable_pin; + init(); + } + + void StandardStepper::init() { + _homing_mask = 0; + is_active = true; // as opposed to NullMotors, this is a real motor + set_axis_name(); + init_step_dir_pins(); + config_message(); + } + + void StandardStepper::init_step_dir_pins() { + // TODO Step pin, but RMT complicates things + _invert_step_pin = bit_istrue(step_invert_mask->get(), bit(axis_index)); + pinMode(dir_pin, OUTPUT); + +#ifdef USE_RMT_STEPS + rmtConfig.rmt_mode = RMT_MODE_TX; + rmtConfig.clk_div = 20; + rmtConfig.mem_block_num = 2; + rmtConfig.tx_config.loop_en = false; + rmtConfig.tx_config.carrier_en = false; + rmtConfig.tx_config.carrier_freq_hz = 0; + rmtConfig.tx_config.carrier_duty_percent = 50; + rmtConfig.tx_config.carrier_level = RMT_CARRIER_LEVEL_LOW; + rmtConfig.tx_config.idle_output_en = true; + +# ifdef STEP_PULSE_DELAY + rmtItem[0].duration0 = STEP_PULSE_DELAY * 4; +# else + rmtItem[0].duration0 = 1; +# endif + + rmtItem[0].duration1 = 4 * pulse_microseconds->get(); + rmtItem[1].duration0 = 0; + rmtItem[1].duration1 = 0; + + rmt_chan_num[axis_index][dual_axis_index] = sys_get_next_RMT_chan_num(); + rmt_set_source_clk((rmt_channel_t)rmt_chan_num[axis_index][dual_axis_index], RMT_BASECLK_APB); + rmtConfig.channel = (rmt_channel_t)rmt_chan_num[axis_index][dual_axis_index]; + rmtConfig.tx_config.idle_level = _invert_step_pin ? RMT_IDLE_LEVEL_HIGH : RMT_IDLE_LEVEL_LOW; + rmtConfig.gpio_num = gpio_num_t(step_pin); // c is a wacky lang + rmtItem[0].level0 = rmtConfig.tx_config.idle_level; + rmtItem[0].level1 = !rmtConfig.tx_config.idle_level; + rmt_config(&rmtConfig); + rmt_fill_tx_items(rmtConfig.channel, &rmtItem[0], rmtConfig.mem_block_num, 0); + +#else + pinMode(step_pin, OUTPUT); + +#endif // USE_RMT_STEPS + pinMode(disable_pin, OUTPUT); + } + + void StandardStepper::config_message() { + grbl_msg_sendf(CLIENT_SERIAL, + MSG_LEVEL_INFO, + "%s Axis standard stepper motor Step:%s Dir:%s Disable:%s", + _axis_name, + pinName(step_pin).c_str(), + pinName(dir_pin).c_str(), + pinName(disable_pin).c_str()); + } + + void StandardStepper::set_direction_pins(uint8_t onMask) { digitalWrite(dir_pin, (onMask & bit(axis_index))); } + + void StandardStepper::set_disable(bool disable) { digitalWrite(disable_pin, disable); } +} diff --git a/Grbl_Esp32/src/Motors/StandardStepper.h b/Grbl_Esp32/src/Motors/StandardStepper.h new file mode 100644 index 00000000..8a4d7e8c --- /dev/null +++ b/Grbl_Esp32/src/Motors/StandardStepper.h @@ -0,0 +1,23 @@ +#pragma once + +#include "Motor.h" + +namespace Motors { + class StandardStepper : public Motor { + public: + StandardStepper(); + StandardStepper(uint8_t axis_index, uint8_t step_pin, uint8_t dir_pin, uint8_t disable_pin); + + virtual void config_message(); + virtual void init(); + virtual void set_direction_pins(uint8_t onMask); + void init_step_dir_pins(); + virtual void set_disable(bool disable); + uint8_t step_pin; + + protected: + bool _invert_step_pin; + uint8_t dir_pin; + uint8_t disable_pin; + }; +} diff --git a/Grbl_Esp32/src/Motors/TrinamicDriver.cpp b/Grbl_Esp32/src/Motors/TrinamicDriver.cpp new file mode 100644 index 00000000..4d5bd043 --- /dev/null +++ b/Grbl_Esp32/src/Motors/TrinamicDriver.cpp @@ -0,0 +1,235 @@ +/* + TrinamicDriver.cpp + This is used for Trinamic SPI controlled stepper motor drivers. + + Part of Grbl_ESP32 + 2020 - Bart Dring + + 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 . + +*/ +#include "TrinamicDriver.h" + +#include + +namespace Motors { + TrinamicDriver::TrinamicDriver(uint8_t axis_index, + uint8_t step_pin, + uint8_t dir_pin, + uint8_t disable_pin, + uint8_t cs_pin, + uint16_t driver_part_number, + float r_sense, + int8_t spi_index) { + type_id = TRINAMIC_SPI_MOTOR; + this->axis_index = axis_index % MAX_AXES; + this->dual_axis_index = axis_index < 6 ? 0 : 1; // 0 = primary 1 = ganged + _driver_part_number = driver_part_number; + _r_sense = r_sense; + this->step_pin = step_pin; + this->dir_pin = dir_pin; + this->disable_pin = disable_pin; + this->cs_pin = cs_pin; + this->spi_index = spi_index; + + _homing_mode = TRINAMIC_HOMING_MODE; + _homing_mask = 0; // no axes homing + + if (_driver_part_number == 2130) + tmcstepper = new TMC2130Stepper(cs_pin, _r_sense, spi_index); + else if (_driver_part_number == 5160) + tmcstepper = new TMC5160Stepper(cs_pin, _r_sense, spi_index); + else { + grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Trinamic unsupported p/n:%d", _driver_part_number); + return; + } + + set_axis_name(); + + init_step_dir_pins(); // from StandardStepper + + digitalWrite(cs_pin, HIGH); + pinMode(cs_pin, OUTPUT); + + // use slower speed if I2S + if (cs_pin >= I2S_OUT_PIN_BASE) + tmcstepper->setSPISpeed(TRINAMIC_SPI_FREQ); + + config_message(); + + // init() must be called later, after all TMC drivers have CS pins setup. + } + + void TrinamicDriver::init() { + SPI.begin(); // this will get called for each motor, but does not seem to hurt anything + + tmcstepper->begin(); + test(); // Try communicating with motor. Prints an error if there is a problem. + read_settings(); // pull info from settings + set_mode(false); + + _homing_mask = 0; + is_active = true; // as opposed to NullMotors, this is a real motor + } + + /* + This is the startup message showing the basic definition +*/ + void TrinamicDriver::config_message() { + grbl_msg_sendf(CLIENT_SERIAL, + MSG_LEVEL_INFO, + "%s Axis Trinamic TMC%d Step:%s Dir:%s CS:%s Disable:%s Index:%d", + _axis_name, + _driver_part_number, + pinName(step_pin).c_str(), + pinName(dir_pin).c_str(), + pinName(cs_pin).c_str(), + pinName(disable_pin).c_str(), + spi_index); + } + + bool TrinamicDriver::test() { + switch (tmcstepper->test_connection()) { + case 1: + grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "%s Trinamic driver test failed. Check connection", _axis_name); + return false; + case 2: + grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "%s Trinamic driver test failed. Check motor power", _axis_name); + return false; + default: grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "%s Trinamic driver test passed", _axis_name); return true; + } + } + + /* + Read setting and send them to the driver. Called at init() and whenever related settings change + both are stored as float Amps, but TMCStepper library expects... + uint16_t run (mA) + float hold (as a percentage of run) +*/ + void TrinamicDriver::read_settings() { + uint16_t run_i_ma = (uint16_t)(axis_settings[axis_index]->run_current->get() * 1000.0); + float hold_i_percent; + + if (axis_settings[axis_index]->run_current->get() == 0) + hold_i_percent = 0; + else { + hold_i_percent = axis_settings[axis_index]->hold_current->get() / axis_settings[axis_index]->run_current->get(); + if (hold_i_percent > 1.0) + hold_i_percent = 1.0; + } + //grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "%s Current run %d hold %f", _axis_name, run_i_ma, hold_i_percent); + + tmcstepper->microsteps(axis_settings[axis_index]->microsteps->get()); + tmcstepper->rms_current(run_i_ma, hold_i_percent); + } + + void TrinamicDriver::set_homing_mode(uint8_t homing_mask, bool isHoming) { + _homing_mask = homing_mask; + set_mode(isHoming); + } + + /* + There are ton of settings. I'll start by grouping then into modes for now. + Many people will want quiet and stallgaurd homing. Stallguard only run in + Coolstep mode, so it will need to switch to Coolstep when homing +*/ + void TrinamicDriver::set_mode(bool isHoming) { + if (isHoming) + _mode = TRINAMIC_HOMING_MODE; + else + _mode = TRINAMIC_RUN_MODE; + + if (_lastMode == _mode) + return; + _lastMode = _mode; + + switch (_mode) { + case TRINAMIC_MODE_STEALTHCHOP: + //grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "TRINAMIC_MODE_STEALTHCHOP"); + tmcstepper->en_pwm_mode(true); + tmcstepper->pwm_autoscale(true); + tmcstepper->diag1_stall(false); + break; + case TRINAMIC_MODE_COOLSTEP: + //grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "TRINAMIC_MODE_COOLSTEP"); + tmcstepper->en_pwm_mode(false); + tmcstepper->pwm_autoscale(false); + tmcstepper->TCOOLTHRS(NORMAL_TCOOLTHRS); // when to turn on coolstep + tmcstepper->THIGH(NORMAL_THIGH); + break; + case TRINAMIC_MODE_STALLGUARD: + //grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "TRINAMIC_MODE_STALLGUARD"); + tmcstepper->en_pwm_mode(false); + tmcstepper->pwm_autoscale(false); + tmcstepper->TCOOLTHRS(calc_tstep(homing_feed_rate->get(), 150.0)); + tmcstepper->THIGH(calc_tstep(homing_feed_rate->get(), 60.0)); + tmcstepper->sfilt(1); + tmcstepper->diag1_stall(true); // stallguard i/o is on diag1 + tmcstepper->sgt(axis_settings[axis_index]->stallguard->get()); + break; + default: grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "TRINAMIC_MODE_UNDEFINED"); + } + } + + /* + This is the stallguard tuning info. It is call debug, so it could be generic across all classes. +*/ + void TrinamicDriver::debug_message() { + uint32_t tstep = tmcstepper->TSTEP(); + + if (tstep == 0xFFFFF || tstep < 1) // if axis is not moving return + return; + float feedrate = st_get_realtime_rate(); //* settings.microsteps[axis_index] / 60.0 ; // convert mm/min to Hz + + grbl_msg_sendf(CLIENT_SERIAL, + MSG_LEVEL_INFO, + "%s Stallguard %d SG_Val: %04d Rate: %05.0f mm/min SG_Setting:%d", + _axis_name, + tmcstepper->stallguard(), + tmcstepper->sg_result(), + feedrate, + axis_settings[axis_index]->stallguard->get()); + } + + // calculate a tstep from a rate + // tstep = TRINAMIC_FCLK / (time between 1/256 steps) + // This is used to set the stallguard window from the homing speed. + // The percent is the offset on the window + uint32_t TrinamicDriver::calc_tstep(float speed, float percent) { + float tstep = + speed / 60.0 * axis_settings[axis_index]->steps_per_mm->get() * (float)(256 / axis_settings[axis_index]->microsteps->get()); + tstep = TRINAMIC_FCLK / tstep * percent / 100.0; + + return (uint32_t)tstep; + } + + // this can use the enable feature over SPI. The dedicated pin must be in the enable mode, + // but that can be hardwired that way. + void TrinamicDriver::set_disable(bool disable) { + //grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "%s Axis disable %d", _axis_name, disable); + + digitalWrite(disable_pin, disable); + +#ifdef USE_TRINAMIC_ENABLE + if (disable) + tmcstepper->toff(TRINAMIC_TOFF_DISABLE); + else { + if (_mode == TRINAMIC_MODE_STEALTHCHOP) + tmcstepper->toff(TRINAMIC_TOFF_STEALTHCHOP); + else + tmcstepper->toff(TRINAMIC_TOFF_COOLSTEP); + } +#endif + // the pin based enable could be added here. + // This would be for individual motors, not the single pin for all motors. + } +} diff --git a/Grbl_Esp32/src/Motors/TrinamicDriver.h b/Grbl_Esp32/src/Motors/TrinamicDriver.h new file mode 100644 index 00000000..c4f7df95 --- /dev/null +++ b/Grbl_Esp32/src/Motors/TrinamicDriver.h @@ -0,0 +1,98 @@ +#pragma once + +/* + TrinamicDriver.h + + Part of Grbl_ESP32 + + 2020 - Bart Dring + + 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 . +*/ + +#include "Motor.h" +#include "StandardStepper.h" +#include // https://github.com/teemuatlut/TMCStepper + +#define TRINAMIC_MODE_STEALTHCHOP 0 // very quiet +#define TRINAMIC_MODE_COOLSTEP 1 // everything runs cooler so higher current possible +#define TRINAMIC_MODE_STALLGUARD 2 // coolstep plus generates stall indication + +#define NORMAL_TCOOLTHRS 0xFFFFF // 20 bit is max +#define NORMAL_THIGH 0 + +#define TMC2130_RSENSE_DEFAULT 0.11f +#define TMC5160_RSENSE_DEFAULT 0.075f + +#define TRINAMIC_SPI_FREQ 100000 + +#define TRINAMIC_FCLK 12700000.0 // Internal clock Approx (Hz) used to calculate TSTEP from homing rate + +// ==== defaults OK to define them in your machine definition ==== +#ifndef TRINAMIC_RUN_MODE +# define TRINAMIC_RUN_MODE TRINAMIC_MODE_COOLSTEP +#endif + +#ifndef TRINAMIC_HOMING_MODE +# define TRINAMIC_HOMING_MODE TRINAMIC_RUN_MODE +#endif + +#ifndef TRINAMIC_TOFF_DISABLE +# define TRINAMIC_TOFF_DISABLE 0 +#endif + +#ifndef TRINAMIC_TOFF_STEALTHCHOP +# define TRINAMIC_TOFF_STEALTHCHOP 5 +#endif + +#ifndef TRINAMIC_TOFF_COOLSTEP +# define TRINAMIC_TOFF_COOLSTEP 3 +#endif + +namespace Motors { + class TrinamicDriver : public StandardStepper { + public: + TrinamicDriver(uint8_t axis_index, + uint8_t step_pin, + uint8_t dir_pin, + uint8_t disable_pin, + uint8_t cs_pin, + uint16_t driver_part_number, + float r_sense, + int8_t spi_index); + + void config_message(); + void init(); + void set_mode(bool isHoming); + void read_settings(); + void trinamic_test_response(); + void trinamic_stepper_enable(bool enable); + void debug_message(); + void set_homing_mode(uint8_t homing_mask, bool ishoming); + void set_disable(bool disable); + bool test(); + + private: + uint32_t calc_tstep(float speed, float percent); + + TMC2130Stepper* tmcstepper; // all other driver types are subclasses of this one + uint8_t _homing_mode; + uint8_t cs_pin = UNDEFINED_PIN; // The chip select pin (can be the same for daisy chain) + uint16_t _driver_part_number; // example: use 2130 for TMC2130 + float _r_sense; + int8_t spi_index; + + protected: + uint8_t _mode; + uint8_t _lastMode = 255; + }; +} diff --git a/Grbl_Esp32/src/Motors/UnipolarMotor.cpp b/Grbl_Esp32/src/Motors/UnipolarMotor.cpp new file mode 100644 index 00000000..a2918ee1 --- /dev/null +++ b/Grbl_Esp32/src/Motors/UnipolarMotor.cpp @@ -0,0 +1,139 @@ +#include "UnipolarMotor.h" + +namespace Motors { + UnipolarMotor::UnipolarMotor() {} + + UnipolarMotor::UnipolarMotor(uint8_t axis_index, uint8_t pin_phase0, uint8_t pin_phase1, uint8_t pin_phase2, uint8_t pin_phase3) { + type_id = UNIPOLAR_MOTOR; + this->axis_index = axis_index % MAX_AXES; + this->dual_axis_index = axis_index < MAX_AXES ? 0 : 1; // 0 = primary 1 = ganged + _pin_phase0 = pin_phase0; + _pin_phase1 = pin_phase1; + _pin_phase2 = pin_phase2; + _pin_phase3 = pin_phase3; + + _half_step = true; // TODO read from settings ... microstep > 1 = half step + + set_axis_name(); + init(); + config_message(); + } + + void UnipolarMotor::init() { + pinMode(_pin_phase0, OUTPUT); + pinMode(_pin_phase1, OUTPUT); + pinMode(_pin_phase2, OUTPUT); + pinMode(_pin_phase3, OUTPUT); + _current_phase = 0; + } + + void UnipolarMotor::config_message() { + grbl_msg_sendf(CLIENT_SERIAL, + MSG_LEVEL_INFO, + "%s Axis unipolar stepper motor Ph0:%s Ph1:%s Ph2:%s Ph3:%s", + _axis_name, + pinName(_pin_phase0).c_str(), + pinName(_pin_phase1).c_str(), + pinName(_pin_phase2).c_str(), + pinName(_pin_phase3).c_str()); + } + + void UnipolarMotor::set_disable(bool disable) { + if (disable) { + digitalWrite(_pin_phase0, 0); + digitalWrite(_pin_phase1, 0); + digitalWrite(_pin_phase2, 0); + digitalWrite(_pin_phase3, 0); + } + _enabled = !disable; + } + + void UnipolarMotor::step(uint8_t step_mask, uint8_t dir_mask) { + uint8_t _phase[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; // temporary phase values...all start as off + uint8_t phase_max; + + if (!(step_mask & bit(axis_index))) + return; // a step is not required on this interrupt + + if (!_enabled) + return; // don't do anything, phase is not changed or lost + + if (_half_step) + phase_max = 7; + else + phase_max = 3; + + if (dir_mask & bit(axis_index)) { // count up + if (_current_phase == phase_max) + _current_phase = 0; + else + _current_phase++; + } else { // count down + if (_current_phase == 0) + _current_phase = phase_max; + else + _current_phase--; + } + /* + 8 Step : A – AB – B – BC – C – CD – D – DA + 4 Step : AB – BC – CD – DA + + Step IN4 IN3 IN2 IN1 + A 0 0 0 1 + AB 0 0 1 1 + B 0 0 1 0 + BC 0 1 1 0 + C 0 1 0 0 + CD 1 1 0 0 + D 1 0 0 0 + DA 1 0 0 1 + */ + if (_half_step) { + switch (_current_phase) { + case 0: _phase[0] = 1; break; + case 1: + _phase[0] = 1; + _phase[1] = 1; + break; + case 2: _phase[1] = 1; break; + case 3: + _phase[1] = 1; + _phase[2] = 1; + break; + case 4: _phase[2] = 1; break; + case 5: + _phase[2] = 1; + _phase[3] = 1; + break; + case 6: _phase[3] = 1; break; + case 7: + _phase[3] = 1; + _phase[0] = 1; + break; + } + } else { + switch (_current_phase) { + case 0: + _phase[0] = 1; + _phase[1] = 1; + break; + case 1: + _phase[1] = 1; + _phase[2] = 1; + break; + case 2: + _phase[2] = 1; + _phase[3] = 1; + break; + case 3: + _phase[3] = 1; + _phase[0] = 1; + break; + } + } + digitalWrite(_pin_phase0, _phase[0]); + digitalWrite(_pin_phase1, _phase[1]); + digitalWrite(_pin_phase2, _phase[2]); + digitalWrite(_pin_phase3, _phase[3]); + } +} diff --git a/Grbl_Esp32/src/Motors/UnipolarMotor.h b/Grbl_Esp32/src/Motors/UnipolarMotor.h new file mode 100644 index 00000000..d9594815 --- /dev/null +++ b/Grbl_Esp32/src/Motors/UnipolarMotor.h @@ -0,0 +1,24 @@ +#pragma once + +#include "Motor.h" + +namespace Motors { + class UnipolarMotor : public Motor { + public: + UnipolarMotor(); + UnipolarMotor(uint8_t axis_index, uint8_t pin_phase0, uint8_t pin_phase1, uint8_t pin_phase2, uint8_t pin_phase3); + void init(); + void config_message(); + void set_disable(bool disable); + void step(uint8_t step_mask, uint8_t dir_mask); // only used on Unipolar right now + + private: + uint8_t _pin_phase0; + uint8_t _pin_phase1; + uint8_t _pin_phase2; + uint8_t _pin_phase3; + uint8_t _current_phase; + bool _half_step; + bool _enabled; + }; +} diff --git a/Grbl_Esp32/nuts_bolts.cpp b/Grbl_Esp32/src/NutsBolts.cpp similarity index 76% rename from Grbl_Esp32/nuts_bolts.cpp rename to Grbl_Esp32/src/NutsBolts.cpp index 72e86230..8420da94 100644 --- a/Grbl_Esp32/nuts_bolts.cpp +++ b/Grbl_Esp32/src/NutsBolts.cpp @@ -1,5 +1,5 @@ /* - nuts_bolts.c - Shared functions + NutsBolts.cpp - Shared functions Part of Grbl Copyright (c) 2011-2016 Sungeun K. Jeon for Gnea Research LLC @@ -22,12 +22,9 @@ along with Grbl. If not, see . */ -#include "grbl.h" +#include "Grbl.h" - - - -#define MAX_INT_DIGITS 8 // Maximum number of digits in int32 (and float) +#define MAX_INT_DIGITS 8 // Maximum number of digits in int32 (and float) // Extracts a floating point value from a string. The following code is based loosely on // the avr-libc strtod() function by Michael Stumpf and Dmitry Xmelkov and many freely @@ -37,7 +34,7 @@ // be a g-code word on some CNC systems. So, 'E' notation will not be recognized. // NOTE: Thanks to Radu-Eosif Mihailescu for identifying the issues with using strtod(). uint8_t read_float(const char* line, uint8_t* char_counter, float* float_ptr) { - const char* ptr = line + *char_counter; + const char* ptr = line + *char_counter; unsigned char c; // Grab first character and increment pointer. No spaces assumed in line. c = *ptr++; @@ -45,32 +42,36 @@ uint8_t read_float(const char* line, uint8_t* char_counter, float* float_ptr) { bool isnegative = false; if (c == '-') { isnegative = true; - c = *ptr++; + c = *ptr++; } else if (c == '+') c = *ptr++; // Extract number into fast integer. Track decimal in terms of exponent value. - uint32_t intval = 0; - int8_t exp = 0; - uint8_t ndigit = 0; - bool isdecimal = false; + uint32_t intval = 0; + int8_t exp = 0; + uint8_t ndigit = 0; + bool isdecimal = false; while (1) { c -= '0'; if (c <= 9) { ndigit++; if (ndigit <= MAX_INT_DIGITS) { - if (isdecimal) exp--; - intval = (((intval << 2) + intval) << 1) + c; // intval*10 + c + if (isdecimal) + exp--; + intval = (((intval << 2) + intval) << 1) + c; // intval*10 + c } else { - if (!(isdecimal)) exp++; // Drop overflow digits + if (!(isdecimal)) + exp++; // Drop overflow digits } - } else if (c == (('.' - '0') & 0xff) && !(isdecimal)) + } else if (c == (('.' - '0') & 0xff) && !(isdecimal)) isdecimal = true; else break; c = *ptr++; } // Return if no digits have been read. - if (!ndigit) return (false); ; + if (!ndigit) + return (false); + ; // Convert integer into floating point. float fval; fval = (float)intval; @@ -94,7 +95,7 @@ uint8_t read_float(const char* line, uint8_t* char_counter, float* float_ptr) { *float_ptr = -fval; else *float_ptr = fval; - *char_counter = ptr - line - 1; // Set char_counter to next statement + *char_counter = ptr - line - 1; // Set char_counter to next statement return (true); } @@ -106,40 +107,44 @@ void delay_ms(uint16_t ms) { void delay_sec(float seconds, uint8_t mode) { uint16_t i = ceil(1000 / DWELL_TIME_STEP * seconds); while (i-- > 0) { - if (sys.abort) return; + if (sys.abort) + return; if (mode == DELAY_MODE_DWELL) protocol_execute_realtime(); - else { // DELAY_MODE_SYS_SUSPEND + else { // DELAY_MODE_SYS_SUSPEND // Execute rt_system() only to avoid nesting suspend loops. protocol_exec_rt_system(); - if (sys.suspend & SUSPEND_RESTART_RETRACT) return; // Bail, if safety door reopens. + if (sys.suspend & SUSPEND_RESTART_RETRACT) + return; // Bail, if safety door reopens. } - delay(DWELL_TIME_STEP); // Delay DWELL_TIME_STEP increment + delay(DWELL_TIME_STEP); // Delay DWELL_TIME_STEP increment } } // Simple hypotenuse computation function. -float hypot_f(float x, float y) { return (sqrt(x * x + y * y)); } - +float hypot_f(float x, float y) { + return (sqrt(x * x + y * y)); +} float convert_delta_vector_to_unit_vector(float* vector) { uint8_t idx; - float magnitude = 0.0; + float magnitude = 0.0; for (idx = 0; idx < N_AXIS; idx++) { if (vector[idx] != 0.0) magnitude += vector[idx] * vector[idx]; } - magnitude = sqrt(magnitude); + magnitude = sqrt(magnitude); float inv_magnitude = 1.0 / magnitude; - for (idx = 0; idx < N_AXIS; idx++) vector[idx] *= inv_magnitude; + for (idx = 0; idx < N_AXIS; idx++) + vector[idx] *= inv_magnitude; return (magnitude); } float limit_acceleration_by_axis_maximum(float* unit_vec) { uint8_t idx; - float limit_value = SOME_LARGE_VALUE; + float limit_value = SOME_LARGE_VALUE; for (idx = 0; idx < N_AXIS; idx++) { - if (unit_vec[idx] != 0) // Avoid divide by zero. + if (unit_vec[idx] != 0) // Avoid divide by zero. limit_value = MIN(limit_value, fabs(axis_settings[idx]->acceleration->get() / unit_vec[idx])); } // The acceleration setting is stored and displayed in units of mm/sec^2, @@ -151,15 +156,15 @@ float limit_acceleration_by_axis_maximum(float* unit_vec) { float limit_rate_by_axis_maximum(float* unit_vec) { uint8_t idx; - float limit_value = SOME_LARGE_VALUE; + float limit_value = SOME_LARGE_VALUE; for (idx = 0; idx < N_AXIS; idx++) { - if (unit_vec[idx] != 0) // Avoid divide by zero. + if (unit_vec[idx] != 0) // Avoid divide by zero. limit_value = MIN(limit_value, fabs(axis_settings[idx]->max_rate->get() / unit_vec[idx])); } return (limit_value); } -float map_float(float x, float in_min, float in_max, float out_min, float out_max) { // DrawBot_Badge +float map_float(float x, float in_min, float in_max, float out_min, float out_max) { // DrawBot_Badge return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; } @@ -167,7 +172,7 @@ uint32_t map_uint32_t(uint32_t x, uint32_t in_min, uint32_t in_max, uint32_t out return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; } -float constrain_float(float in, float min, float max) { // DrawBot_Badge +float constrain_float(float in, float min, float max) { // DrawBot_Badge if (in < min) return min; if (in > max) @@ -181,7 +186,5 @@ float mapConstrain(float x, float in_min, float in_max, float out_min, float out } bool char_is_numeric(char value) { - return (value >= '0' && value <='9'); + return (value >= '0' && value <= '9'); } - - diff --git a/Grbl_Esp32/nuts_bolts.h b/Grbl_Esp32/src/NutsBolts.h similarity index 69% rename from Grbl_Esp32/nuts_bolts.h rename to Grbl_Esp32/src/NutsBolts.h index 19ffa2e6..fca88326 100644 --- a/Grbl_Esp32/nuts_bolts.h +++ b/Grbl_Esp32/src/NutsBolts.h @@ -1,5 +1,7 @@ +#pragma once + /* - nuts_bolts.h - Header for system level commands and real-time processes + NutsBolts.h - Header for system level commands and real-time processes Part of Grbl Copyright (c) 2014-2016 Sungeun K. Jeon for Gnea Research LLC @@ -18,10 +20,7 @@ along with Grbl. If not, see . */ -#ifndef nuts_bolts_h -#define nuts_bolts_h - -#include "config.h" +#include "Config.h" #define false 0 #define true 1 @@ -31,7 +30,7 @@ // Axis array index values. Must start with 0 and be continuous. // Note: You set the number of axes used by changing N_AXIS. // Be sure to define pins or servos in the machine definition file. -#define X_AXIS 0 // Axis indexing value. +#define X_AXIS 0 // Axis indexing value. #define Y_AXIS 1 #define Z_AXIS 2 #define A_AXIS 3 @@ -41,8 +40,8 @@ #define MAX_AXES 6 #define MAX_GANGED 2 -#define PRIMARY_MOTOR 0 -#define GANGED_MOTOR 1 +#define PRIMARY_MOTOR 0 +#define GANGED_MOTOR 1 #define X2_AXIS (X_AXIS + MAX_AXES) #define Y2_AXIS (Y_AXIS + MAX_AXES) @@ -53,34 +52,32 @@ // CoreXY motor assignments. DO NOT ALTER. // NOTE: If the A and B motor axis bindings are changed, this effects the CoreXY equations. -#define A_MOTOR X_AXIS // Must be X_AXIS -#define B_MOTOR Y_AXIS // Must be Y_AXIS - - +#define A_MOTOR X_AXIS // Must be X_AXIS +#define B_MOTOR Y_AXIS // Must be Y_AXIS // Conversions #define MM_PER_INCH (25.40) #define INCH_PER_MM (0.0393701) -#define TICKS_PER_MICROSECOND (F_STEPPER_TIMER/1000000) // Different from AVR version +#define TICKS_PER_MICROSECOND (F_STEPPER_TIMER / 1000000) // Different from AVR version -#define DELAY_MODE_DWELL 0 +#define DELAY_MODE_DWELL 0 #define DELAY_MODE_SYS_SUSPEND 1 // Useful macros #define clear_vector(a) memset(a, 0, sizeof(a)) -#define clear_vector_float(a) memset(a, 0.0, sizeof(float)*N_AXIS) +#define clear_vector_float(a) memset(a, 0.0, sizeof(float) * N_AXIS) // #define clear_vector_long(a) memset(a, 0.0, sizeof(long)*N_AXIS) -#define MAX(a,b) (((a) > (b)) ? (a) : (b)) // changed to upper case to remove conflicts with other libraries -#define MIN(a,b) (((a) < (b)) ? (a) : (b)) // changed to upper case to remove conflicts with other libraries -#define isequal_position_vector(a,b) !(memcmp(a, b, sizeof(float)*N_AXIS)) +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) // changed to upper case to remove conflicts with other libraries +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) // changed to upper case to remove conflicts with other libraries +#define isequal_position_vector(a, b) !(memcmp(a, b, sizeof(float) * N_AXIS)) // Bit field and masking macros //Arduino.h:104:0: note: this is the location of the previous definition //#define bit(n) (1 << n) -#define bit_true(x,mask) (x) |= (mask) -#define bit_false(x,mask) (x) &= ~(mask) -#define bit_istrue(x,mask) ((x & mask) != 0) -#define bit_isfalse(x,mask) ((x & mask) == 0) +#define bit_true(x, mask) (x) |= (mask) +#define bit_false(x, mask) (x) &= ~(mask) +#define bit_istrue(x, mask) ((x & mask) != 0) +#define bit_isfalse(x, mask) ((x & mask) == 0) // Read a floating point value from a string. Line points to the input buffer, char_counter // is the indexer pointing to the current character of the line, while float_ptr is @@ -100,15 +97,16 @@ float convert_delta_vector_to_unit_vector(float* vector); float limit_acceleration_by_axis_maximum(float* unit_vec); float limit_rate_by_axis_maximum(float* unit_vec); -float mapConstrain(float x, float in_min, float in_max, float out_min, float out_max); -float map_float(float x, float in_min, float in_max, float out_min, float out_max); +float mapConstrain(float x, float in_min, float in_max, float out_min, float out_max); +float map_float(float x, float in_min, float in_max, float out_min, float out_max); uint32_t map_uint32_t(uint32_t x, uint32_t in_min, uint32_t in_max, uint32_t out_min, uint32_t out_max); -float constrain_float(float in, float min, float max); -bool char_is_numeric(char value); -char* trim(char* value); +float constrain_float(float in, float min, float max); +bool char_is_numeric(char value); +char* trim(char* value); -template void swap(T& a, T& b) { - T c(a); a = b; b = c; +template +void swap(T& a, T& b) { + T c(a); + a = b; + b = c; } - -#endif diff --git a/Grbl_Esp32/Pins.cpp b/Grbl_Esp32/src/Pins.cpp similarity index 97% rename from Grbl_Esp32/Pins.cpp rename to Grbl_Esp32/src/Pins.cpp index a07c929b..596bc4cf 100644 --- a/Grbl_Esp32/Pins.cpp +++ b/Grbl_Esp32/src/Pins.cpp @@ -1,5 +1,5 @@ -#include "grbl.h" -#include "i2s_out.h" +#include "Grbl.h" +#include "I2SOut.h" String pinName(uint8_t pin) { if (pin == UNDEFINED_PIN) { diff --git a/Grbl_Esp32/Pins.h b/Grbl_Esp32/src/Pins.h similarity index 79% rename from Grbl_Esp32/Pins.h rename to Grbl_Esp32/src/Pins.h index 4edb86e0..e06bd302 100644 --- a/Grbl_Esp32/Pins.h +++ b/Grbl_Esp32/src/Pins.h @@ -1,11 +1,12 @@ #pragma once -#include "Arduino.h" + +#include #define UNDEFINED_PIN 255 // Can be used to show a pin has no i/O assigned #define I2S_OUT_PIN_BASE 128 -extern "C" int __digitalRead(uint8_t pin); +extern "C" int __digitalRead(uint8_t pin); extern "C" void __pinMode(uint8_t pin, uint8_t mode); extern "C" void __digitalWrite(uint8_t pin, uint8_t val); diff --git a/Grbl_Esp32/planner.cpp b/Grbl_Esp32/src/Planner.cpp similarity index 77% rename from Grbl_Esp32/planner.cpp rename to Grbl_Esp32/src/Planner.cpp index 50d2bcde..05bd2bae 100644 --- a/Grbl_Esp32/planner.cpp +++ b/Grbl_Esp32/src/Planner.cpp @@ -1,5 +1,5 @@ /* - planner.c - buffers movement commands and manages the acceleration profile plan + Planner.cpp - buffers movement commands and manages the acceleration profile plan Part of Grbl Copyright (c) 2011-2016 Sungeun K. Jeon for Gnea Research LLC @@ -23,43 +23,41 @@ along with Grbl. If not, see . */ -#include "grbl.h" -#include // PSoc Required for labs - +#include "Grbl.h" +#include // PSoc Required for labs static plan_block_t block_buffer[BLOCK_BUFFER_SIZE]; // A ring buffer for motion instructions -static uint8_t block_buffer_tail; // Index of the block to process now -static uint8_t block_buffer_head; // Index of the next block to be pushed -static uint8_t next_buffer_head; // Index of the next buffer head -static uint8_t block_buffer_planned; // Index of the optimally planned block +static uint8_t block_buffer_tail; // Index of the block to process now +static uint8_t block_buffer_head; // Index of the next block to be pushed +static uint8_t next_buffer_head; // Index of the next buffer head +static uint8_t block_buffer_planned; // Index of the optimally planned block // Define planner variables typedef struct { - int32_t position[N_AXIS]; // The planner position of the tool in absolute steps. Kept separate + int32_t position[N_AXIS]; // The planner position of the tool in absolute steps. Kept separate // from g-code position for movements requiring multiple line motions, // i.e. arcs, canned cycles, and backlash compensation. - float previous_unit_vec[N_AXIS]; // Unit vector of previous path line segment - float previous_nominal_speed; // Nominal speed of previous path line segment + float previous_unit_vec[N_AXIS]; // Unit vector of previous path line segment + float previous_nominal_speed; // Nominal speed of previous path line segment } planner_t; static planner_t pl; - // Returns the index of the next block in the ring buffer. Also called by stepper segment buffer. uint8_t plan_next_block_index(uint8_t block_index) { block_index++; - if (block_index == BLOCK_BUFFER_SIZE) block_index = 0; + if (block_index == BLOCK_BUFFER_SIZE) + block_index = 0; return (block_index); } - // Returns the index of the previous block in the ring buffer static uint8_t plan_prev_block_index(uint8_t block_index) { - if (block_index == 0) block_index = BLOCK_BUFFER_SIZE; + if (block_index == 0) + block_index = BLOCK_BUFFER_SIZE; block_index--; return (block_index); } - /* PLANNER SPEED DEFINITION +--------+ <- current->nominal_speed / \ @@ -129,26 +127,29 @@ static void planner_recalculate() { // Initialize block index to the last block in the planner buffer. uint8_t block_index = plan_prev_block_index(block_buffer_head); // Bail. Can't do anything with one only one plan-able block. - if (block_index == block_buffer_planned) return; + if (block_index == block_buffer_planned) + return; // Reverse Pass: Coarsely maximize all possible deceleration curves back-planning from the last // block in buffer. Cease planning when the last optimal planned or tail pointer is reached. // NOTE: Forward pass will later refine and correct the reverse pass to create an optimal plan. - float entry_speed_sqr; + float entry_speed_sqr; plan_block_t* next; plan_block_t* current = &block_buffer[block_index]; // Calculate maximum entry speed for last block in buffer, where the exit speed is always zero. current->entry_speed_sqr = MIN(current->max_entry_speed_sqr, 2 * current->acceleration * current->millimeters); - block_index = plan_prev_block_index(block_index); - if (block_index == block_buffer_planned) { // Only two plannable blocks in buffer. Reverse pass complete. + block_index = plan_prev_block_index(block_index); + if (block_index == block_buffer_planned) { // Only two plannable blocks in buffer. Reverse pass complete. // Check if the first block is the tail. If so, notify stepper to update its current parameters. - if (block_index == block_buffer_tail) st_update_plan_block_parameters(); - } else { // Three or more plan-able blocks + if (block_index == block_buffer_tail) + st_update_plan_block_parameters(); + } else { // Three or more plan-able blocks while (block_index != block_buffer_planned) { - next = current; - current = &block_buffer[block_index]; + next = current; + current = &block_buffer[block_index]; block_index = plan_prev_block_index(block_index); // Check if next block is the tail block(=planned block). If so, update current stepper parameters. - if (block_index == block_buffer_tail) st_update_plan_block_parameters(); + if (block_index == block_buffer_tail) + st_update_plan_block_parameters(); // Compute maximum entry speed decelerating over the current block from its exit speed. if (current->entry_speed_sqr != current->max_entry_speed_sqr) { entry_speed_sqr = next->entry_speed_sqr + 2 * current->acceleration * current->millimeters; @@ -161,11 +162,11 @@ static void planner_recalculate() { } // Forward Pass: Forward plan the acceleration curve from the planned pointer onward. // Also scans for optimal plan breakpoints and appropriately updates the planned pointer. - next = &block_buffer[block_buffer_planned]; // Begin at buffer planned pointer + next = &block_buffer[block_buffer_planned]; // Begin at buffer planned pointer block_index = plan_next_block_index(block_buffer_planned); while (block_index != block_buffer_head) { current = next; - next = &block_buffer[block_index]; + next = &block_buffer[block_index]; // Any acceleration detected in the forward pass automatically moves the optimal planned // pointer forward, since everything before this is all optimal. In other words, nothing // can improve the plan from the buffer tail to the planned pointer by logic. @@ -173,117 +174,118 @@ static void planner_recalculate() { entry_speed_sqr = current->entry_speed_sqr + 2 * current->acceleration * current->millimeters; // If true, current block is full-acceleration and we can move the planned pointer forward. if (entry_speed_sqr < next->entry_speed_sqr) { - next->entry_speed_sqr = entry_speed_sqr; // Always <= max_entry_speed_sqr. Backward pass sets this. - block_buffer_planned = block_index; // Set optimal plan pointer. + next->entry_speed_sqr = entry_speed_sqr; // Always <= max_entry_speed_sqr. Backward pass sets this. + block_buffer_planned = block_index; // Set optimal plan pointer. } } // Any block set at its maximum entry speed also creates an optimal plan up to this // point in the buffer. When the plan is bracketed by either the beginning of the // buffer and a maximum entry speed or two maximum entry speeds, every block in between // cannot logically be further improved. Hence, we don't have to recompute them anymore. - if (next->entry_speed_sqr == next->max_entry_speed_sqr) block_buffer_planned = block_index; + if (next->entry_speed_sqr == next->max_entry_speed_sqr) + block_buffer_planned = block_index; block_index = plan_next_block_index(block_index); } } - void plan_reset() { - memset(&pl, 0, sizeof(planner_t)); // Clear planner struct + memset(&pl, 0, sizeof(planner_t)); // Clear planner struct plan_reset_buffer(); } - void plan_reset_buffer() { - block_buffer_tail = 0; - block_buffer_head = 0; // Empty = tail - next_buffer_head = 1; // plan_next_block_index(block_buffer_head) - block_buffer_planned = 0; // = block_buffer_tail; + block_buffer_tail = 0; + block_buffer_head = 0; // Empty = tail + next_buffer_head = 1; // plan_next_block_index(block_buffer_head) + block_buffer_planned = 0; // = block_buffer_tail; } - void plan_discard_current_block() { - if (block_buffer_head != block_buffer_tail) { // Discard non-empty buffer. + if (block_buffer_head != block_buffer_tail) { // Discard non-empty buffer. uint8_t block_index = plan_next_block_index(block_buffer_tail); // Push block_buffer_planned pointer, if encountered. - if (block_buffer_tail == block_buffer_planned) block_buffer_planned = block_index; + if (block_buffer_tail == block_buffer_planned) + block_buffer_planned = block_index; block_buffer_tail = block_index; } } - // Returns address of planner buffer block used by system motions. Called by segment generator. plan_block_t* plan_get_system_motion_block() { return (&block_buffer[block_buffer_head]); } - // Returns address of first planner block, if available. Called by various main program functions. plan_block_t* plan_get_current_block() { - if (block_buffer_head == block_buffer_tail) return (NULL); // Buffer empty + if (block_buffer_head == block_buffer_tail) + return (NULL); // Buffer empty return (&block_buffer[block_buffer_tail]); } - float plan_get_exec_block_exit_speed_sqr() { uint8_t block_index = plan_next_block_index(block_buffer_tail); - if (block_index == block_buffer_head) return (0.0); + if (block_index == block_buffer_head) + return (0.0); return (block_buffer[block_index].entry_speed_sqr); } - // Returns the availability status of the block ring buffer. True, if full. uint8_t plan_check_full_buffer() { - if (block_buffer_tail == next_buffer_head) return (true); + if (block_buffer_tail == next_buffer_head) + return (true); return (false); } - // Computes and returns block nominal speed based on running condition and override values. // NOTE: All system motion commands, such as homing/parking, are not subject to overrides. float plan_compute_profile_nominal_speed(plan_block_t* block) { float nominal_speed = block->programmed_rate; - if (block->condition & PL_COND_FLAG_RAPID_MOTION) nominal_speed *= (0.01 * sys.r_override); + if (block->condition & PL_COND_FLAG_RAPID_MOTION) + nominal_speed *= (0.01 * sys.r_override); else { - if (!(block->condition & PL_COND_FLAG_NO_FEED_OVERRIDE)) nominal_speed *= (0.01 * sys.f_override); - if (nominal_speed > block->rapid_rate) nominal_speed = block->rapid_rate; + if (!(block->condition & PL_COND_FLAG_NO_FEED_OVERRIDE)) + nominal_speed *= (0.01 * sys.f_override); + if (nominal_speed > block->rapid_rate) + nominal_speed = block->rapid_rate; } - if (nominal_speed > MINIMUM_FEED_RATE) return (nominal_speed); + if (nominal_speed > MINIMUM_FEED_RATE) + return (nominal_speed); return (MINIMUM_FEED_RATE); } - // Computes and updates the max entry speed (sqr) of the block, based on the minimum of the junction's // previous and current nominal speeds and max junction speed. static void plan_compute_profile_parameters(plan_block_t* block, float nominal_speed, float prev_nominal_speed) { // Compute the junction maximum entry based on the minimum of the junction speed and neighboring nominal speeds. - if (nominal_speed > prev_nominal_speed) block->max_entry_speed_sqr = prev_nominal_speed * prev_nominal_speed; - else block->max_entry_speed_sqr = nominal_speed * nominal_speed; - if (block->max_entry_speed_sqr > block->max_junction_speed_sqr) block->max_entry_speed_sqr = block->max_junction_speed_sqr; + if (nominal_speed > prev_nominal_speed) + block->max_entry_speed_sqr = prev_nominal_speed * prev_nominal_speed; + else + block->max_entry_speed_sqr = nominal_speed * nominal_speed; + if (block->max_entry_speed_sqr > block->max_junction_speed_sqr) + block->max_entry_speed_sqr = block->max_junction_speed_sqr; } - // Re-calculates buffered motions profile parameters upon a motion-based override change. void plan_update_velocity_profile_parameters() { - uint8_t block_index = block_buffer_tail; + uint8_t block_index = block_buffer_tail; plan_block_t* block; - float nominal_speed; - float prev_nominal_speed = SOME_LARGE_VALUE; // Set high for first block nominal speed calculation. + float nominal_speed; + float prev_nominal_speed = SOME_LARGE_VALUE; // Set high for first block nominal speed calculation. while (block_index != block_buffer_head) { - block = &block_buffer[block_index]; + block = &block_buffer[block_index]; nominal_speed = plan_compute_profile_nominal_speed(block); plan_compute_profile_parameters(block, nominal_speed, prev_nominal_speed); prev_nominal_speed = nominal_speed; - block_index = plan_next_block_index(block_index); + block_index = plan_next_block_index(block_index); } - pl.previous_nominal_speed = prev_nominal_speed; // Update prev nominal speed for next incoming block. + pl.previous_nominal_speed = prev_nominal_speed; // Update prev nominal speed for next incoming block. } - uint8_t plan_buffer_line(float* target, plan_line_data_t* pl_data) { // Prepare and initialize new block. Copy relevant pl_data for block execution. plan_block_t* block = &block_buffer[block_buffer_head]; - memset(block, 0, sizeof(plan_block_t)); // Zero all block values. - block->condition = pl_data->condition; + memset(block, 0, sizeof(plan_block_t)); // Zero all block values. + block->condition = pl_data->condition; block->spindle_speed = pl_data->spindle_speed; #ifdef USE_LINE_NUMBERS @@ -291,7 +293,7 @@ uint8_t plan_buffer_line(float* target, plan_line_data_t* pl_data) { #endif // Compute and store initial move distance data. int32_t target_steps[N_AXIS], position_steps[N_AXIS]; - float unit_vec[N_AXIS], delta_mm; + float unit_vec[N_AXIS], delta_mm; uint8_t idx; // Copy position data based on type of motion being planned. if (block->condition & PL_COND_FLAG_SYSTEM_MOTION) { @@ -302,7 +304,8 @@ uint8_t plan_buffer_line(float* target, plan_line_data_t* pl_data) { #else memcpy(position_steps, sys_position, sizeof(sys_position)); #endif - } else memcpy(position_steps, pl.position, sizeof(pl.position)); + } else + memcpy(position_steps, pl.position, sizeof(pl.position)); #ifdef COREXY target_steps[A_MOTOR] = lround(target[A_MOTOR] * axis_settings[A_MOTOR]->steps_per_mm->get()); target_steps[B_MOTOR] = lround(target[B_MOTOR] * axis_settings[B_MOTOR]->steps_per_mm->get()); @@ -320,42 +323,48 @@ uint8_t plan_buffer_line(float* target, plan_line_data_t* pl_data) { } block->step_event_count = MAX(block->step_event_count, block->steps[idx]); if (idx == A_MOTOR) - delta_mm = (target_steps[X_AXIS] - position_steps[X_AXIS] + target_steps[Y_AXIS] - position_steps[Y_AXIS]) / axis_settings[idx]->steps_per_mm->get(); + delta_mm = (target_steps[X_AXIS] - position_steps[X_AXIS] + target_steps[Y_AXIS] - position_steps[Y_AXIS]) / + axis_settings[idx]->steps_per_mm->get(); else if (idx == B_MOTOR) - delta_mm = (target_steps[X_AXIS] - position_steps[X_AXIS] - target_steps[Y_AXIS] + position_steps[Y_AXIS]) / axis_settings[idx]->steps_per_mm->get(); + delta_mm = (target_steps[X_AXIS] - position_steps[X_AXIS] - target_steps[Y_AXIS] + position_steps[Y_AXIS]) / + axis_settings[idx]->steps_per_mm->get(); else delta_mm = (target_steps[idx] - position_steps[idx]) / axis_settings[idx]->steps_per_mm->get(); #else - target_steps[idx] = lround(target[idx] * axis_settings[idx]->steps_per_mm->get()); - block->steps[idx] = labs(target_steps[idx] - position_steps[idx]); + target_steps[idx] = lround(target[idx] * axis_settings[idx]->steps_per_mm->get()); + block->steps[idx] = labs(target_steps[idx] - position_steps[idx]); block->step_event_count = MAX(block->step_event_count, block->steps[idx]); - delta_mm = (target_steps[idx] - position_steps[idx]) / axis_settings[idx]->steps_per_mm->get(); + delta_mm = (target_steps[idx] - position_steps[idx]) / axis_settings[idx]->steps_per_mm->get(); #endif - unit_vec[idx] = delta_mm; // Store unit vector numerator + unit_vec[idx] = delta_mm; // Store unit vector numerator // Set direction bits. Bit enabled always means direction is negative. - if (delta_mm < 0.0) block->direction_bits |= get_direction_pin_mask(idx); + if (delta_mm < 0.0) + block->direction_bits |= get_direction_pin_mask(idx); } // Bail if this is a zero-length block. Highly unlikely to occur. - if (block->step_event_count == 0) return (PLAN_EMPTY_BLOCK); + if (block->step_event_count == 0) + return (PLAN_EMPTY_BLOCK); // Calculate the unit vector of the line move and the block maximum feed rate and acceleration scaled // down such that no individual axes maximum values are exceeded with respect to the line direction. // NOTE: This calculation assumes all axes are orthogonal (Cartesian) and works with ABC-axes, // if they are also orthogonal/independent. Operates on the absolute value of the unit vector. - block->millimeters = convert_delta_vector_to_unit_vector(unit_vec); + block->millimeters = convert_delta_vector_to_unit_vector(unit_vec); block->acceleration = limit_acceleration_by_axis_maximum(unit_vec); - block->rapid_rate = limit_rate_by_axis_maximum(unit_vec); + block->rapid_rate = limit_rate_by_axis_maximum(unit_vec); // Store programmed rate. - if (block->condition & PL_COND_FLAG_RAPID_MOTION) block->programmed_rate = block->rapid_rate; + if (block->condition & PL_COND_FLAG_RAPID_MOTION) + block->programmed_rate = block->rapid_rate; else { block->programmed_rate = pl_data->feed_rate; - if (block->condition & PL_COND_FLAG_INVERSE_TIME) block->programmed_rate *= block->millimeters; + if (block->condition & PL_COND_FLAG_INVERSE_TIME) + block->programmed_rate *= block->millimeters; } // TODO: Need to check this method handling zero junction speeds when starting from rest. if ((block_buffer_head == block_buffer_tail) || (block->condition & PL_COND_FLAG_SYSTEM_MOTION)) { // Initialize block entry speed as zero. Assume it will be starting from rest. Planner will correct this later. // If system motion, the system motion block always is assumed to start from rest and end at a complete stop. - block->entry_speed_sqr = 0.0; - block->max_junction_speed_sqr = 0.0; // Starting from rest. Enforce start from zero velocity. + block->entry_speed_sqr = 0.0; + block->max_junction_speed_sqr = 0.0; // Starting from rest. Enforce start from zero velocity. } else { // Compute maximum allowable entry speed at junction by centripetal acceleration approximation. // Let a circle be tangent to both previous and current path line segments, where the junction @@ -395,9 +404,10 @@ uint8_t plan_buffer_line(float* target, plan_line_data_t* pl_data) { } else { convert_delta_vector_to_unit_vector(junction_unit_vec); float junction_acceleration = limit_acceleration_by_axis_maximum(junction_unit_vec); - float sin_theta_d2 = sqrt(0.5 * (1.0 - junction_cos_theta)); // Trig half angle identity. Always positive. - block->max_junction_speed_sqr = MAX(MINIMUM_JUNCTION_SPEED * MINIMUM_JUNCTION_SPEED, - (junction_acceleration * junction_deviation->get() * sin_theta_d2) / (1.0 - sin_theta_d2)); + float sin_theta_d2 = sqrt(0.5 * (1.0 - junction_cos_theta)); // Trig half angle identity. Always positive. + block->max_junction_speed_sqr = + MAX(MINIMUM_JUNCTION_SPEED * MINIMUM_JUNCTION_SPEED, + (junction_acceleration * junction_deviation->get() * sin_theta_d2) / (1.0 - sin_theta_d2)); } } } @@ -407,11 +417,11 @@ uint8_t plan_buffer_line(float* target, plan_line_data_t* pl_data) { plan_compute_profile_parameters(block, nominal_speed, pl.previous_nominal_speed); pl.previous_nominal_speed = nominal_speed; // Update previous path unit_vector and planner position. - memcpy(pl.previous_unit_vec, unit_vec, sizeof(unit_vec)); // pl.previous_unit_vec[] = unit_vec[] - memcpy(pl.position, target_steps, sizeof(target_steps)); // pl.position[] = target_steps[] + memcpy(pl.previous_unit_vec, unit_vec, sizeof(unit_vec)); // pl.previous_unit_vec[] = unit_vec[] + memcpy(pl.position, target_steps, sizeof(target_steps)); // pl.position[] = target_steps[] // New block is all set. Update buffer head and next buffer head indices. block_buffer_head = next_buffer_head; - next_buffer_head = plan_next_block_index(block_buffer_head); + next_buffer_head = plan_next_block_index(block_buffer_head); // Finish up by recalculating the plan with the new block. planner_recalculate(); } @@ -432,27 +442,26 @@ void plan_sync_position() { else pl.position[idx] = sys_position[idx]; #else - pl.position[idx] = sys_position[idx]; + pl.position[idx] = sys_position[idx]; #endif } } - // Returns the number of available blocks are in the planner buffer. uint8_t plan_get_block_buffer_available() { - if (block_buffer_head >= block_buffer_tail) return ((BLOCK_BUFFER_SIZE - 1) - (block_buffer_head - block_buffer_tail)); + if (block_buffer_head >= block_buffer_tail) + return ((BLOCK_BUFFER_SIZE - 1) - (block_buffer_head - block_buffer_tail)); return ((block_buffer_tail - block_buffer_head - 1)); } - // Returns the number of active blocks are in the planner buffer. // NOTE: Deprecated. Not used unless classic status reports are enabled in config.h uint8_t plan_get_block_buffer_count() { - if (block_buffer_head >= block_buffer_tail) return (block_buffer_head - block_buffer_tail); + if (block_buffer_head >= block_buffer_tail) + return (block_buffer_head - block_buffer_tail); return (BLOCK_BUFFER_SIZE - (block_buffer_tail - block_buffer_head)); } - // Re-initialize buffer plan with a partially completed block, assumed to exist at the buffer tail. // Called after a steppers have come to a complete stop for a feed hold and the cycle is stopped. void plan_cycle_reinitialize() { diff --git a/Grbl_Esp32/planner.h b/Grbl_Esp32/src/Planner.h similarity index 63% rename from Grbl_Esp32/planner.h rename to Grbl_Esp32/src/Planner.h index 1ce2d525..e8abc13a 100644 --- a/Grbl_Esp32/planner.h +++ b/Grbl_Esp32/src/Planner.h @@ -1,5 +1,7 @@ +#pragma once + /* - planner.h - buffers movement commands and manages the acceleration profile plan + Planner.h - buffers movement commands and manages the acceleration profile plan Part of Grbl Copyright (c) 2011-2016 Sungeun K. Jeon for Gnea Research LLC @@ -22,16 +24,13 @@ along with Grbl. If not, see . */ -#ifndef planner_h -#define planner_h - // The number of linear motions that can be in the plan at any give time #ifndef BLOCK_BUFFER_SIZE - #ifdef USE_LINE_NUMBERS - #define BLOCK_BUFFER_SIZE 15 - #else - #define BLOCK_BUFFER_SIZE 16 - #endif +# ifdef USE_LINE_NUMBERS +# define BLOCK_BUFFER_SIZE 15 +# else +# define BLOCK_BUFFER_SIZE 16 +# endif #endif // Returned status message from planner. @@ -39,69 +38,64 @@ #define PLAN_EMPTY_BLOCK false // Define planner data condition flags. Used to denote running conditions of a block. -#define PL_COND_FLAG_RAPID_MOTION bit(0) -#define PL_COND_FLAG_SYSTEM_MOTION bit(1) // Single motion. Circumvents planner state. Used by home/park. -#define PL_COND_FLAG_NO_FEED_OVERRIDE bit(2) // Motion does not honor feed override. -#define PL_COND_FLAG_INVERSE_TIME bit(3) // Interprets feed rate value as inverse time when set. -#define PL_COND_FLAG_SPINDLE_CW bit(4) -#define PL_COND_FLAG_SPINDLE_CCW bit(5) -#define PL_COND_FLAG_COOLANT_FLOOD bit(6) -#define PL_COND_FLAG_COOLANT_MIST bit(7) -#define PL_COND_MOTION_MASK (PL_COND_FLAG_RAPID_MOTION|PL_COND_FLAG_SYSTEM_MOTION|PL_COND_FLAG_NO_FEED_OVERRIDE) -#define PL_COND_ACCESSORY_MASK (PL_COND_FLAG_SPINDLE_CW|PL_COND_FLAG_SPINDLE_CCW|PL_COND_FLAG_COOLANT_FLOOD|PL_COND_FLAG_COOLANT_MIST) - - +#define PL_COND_FLAG_RAPID_MOTION bit(0) +#define PL_COND_FLAG_SYSTEM_MOTION bit(1) // Single motion. Circumvents planner state. Used by home/park. +#define PL_COND_FLAG_NO_FEED_OVERRIDE bit(2) // Motion does not honor feed override. +#define PL_COND_FLAG_INVERSE_TIME bit(3) // Interprets feed rate value as inverse time when set. +#define PL_COND_FLAG_SPINDLE_CW bit(4) +#define PL_COND_FLAG_SPINDLE_CCW bit(5) +#define PL_COND_FLAG_COOLANT_FLOOD bit(6) +#define PL_COND_FLAG_COOLANT_MIST bit(7) +#define PL_COND_MOTION_MASK (PL_COND_FLAG_RAPID_MOTION | PL_COND_FLAG_SYSTEM_MOTION | PL_COND_FLAG_NO_FEED_OVERRIDE) +#define PL_COND_ACCESSORY_MASK (PL_COND_FLAG_SPINDLE_CW | PL_COND_FLAG_SPINDLE_CCW | PL_COND_FLAG_COOLANT_FLOOD | PL_COND_FLAG_COOLANT_MIST) // This struct stores a linear movement of a g-code block motion with its critical "nominal" values // are as specified in the source g-code. typedef struct { // Fields used by the bresenham algorithm for tracing the line // NOTE: Used by stepper algorithm to execute the block correctly. Do not alter these values. - uint32_t steps[N_AXIS]; // Step count along each axis - uint32_t step_event_count; // The maximum step axis count and number of steps required to complete this block. - uint8_t direction_bits; // The direction bit set for this block (refers to *_DIRECTION_BIT in config.h) + uint32_t steps[N_AXIS]; // Step count along each axis + uint32_t step_event_count; // The maximum step axis count and number of steps required to complete this block. + uint8_t direction_bits; // The direction bit set for this block (refers to *_DIRECTION_BIT in config.h) // Block condition data to ensure correct execution depending on states and overrides. - uint8_t condition; // Block bitflag variable defining block run conditions. Copied from pl_line_data. + uint8_t condition; // Block bitflag variable defining block run conditions. Copied from pl_line_data. #ifdef USE_LINE_NUMBERS int32_t line_number; // Block line number for real-time reporting. Copied from pl_line_data. #endif // Fields used by the motion planner to manage acceleration. Some of these values may be updated // by the stepper module during execution of special motion cases for replanning purposes. - float entry_speed_sqr; // The current planned entry speed at block junction in (mm/min)^2 - float max_entry_speed_sqr; // Maximum allowable entry speed based on the minimum of junction limit and + float entry_speed_sqr; // The current planned entry speed at block junction in (mm/min)^2 + float max_entry_speed_sqr; // Maximum allowable entry speed based on the minimum of junction limit and // neighboring nominal speeds with overrides in (mm/min)^2 - float acceleration; // Axis-limit adjusted line acceleration in (mm/min^2). Does not change. - float millimeters; // The remaining distance for this block to be executed in (mm). + float acceleration; // Axis-limit adjusted line acceleration in (mm/min^2). Does not change. + float millimeters; // The remaining distance for this block to be executed in (mm). // NOTE: This value may be altered by stepper algorithm during execution. // Stored rate limiting data used by planner when changes occur. - float max_junction_speed_sqr; // Junction entry speed limit based on direction vectors in (mm/min)^2 - float rapid_rate; // Axis-limit adjusted maximum rate for this block direction in (mm/min) - float programmed_rate; // Programmed rate of this block (mm/min). - + float max_junction_speed_sqr; // Junction entry speed limit based on direction vectors in (mm/min)^2 + float rapid_rate; // Axis-limit adjusted maximum rate for this block direction in (mm/min) + float programmed_rate; // Programmed rate of this block (mm/min). // Stored spindle speed data used by spindle overrides and resuming methods. - float spindle_speed; // Block spindle speed. Copied from pl_line_data. + float spindle_speed; // Block spindle speed. Copied from pl_line_data. //#endif } plan_block_t; // Planner data prototype. Must be used when passing new motions to the planner. typedef struct { - float feed_rate; // Desired feed rate for line motion. Value is ignored, if rapid motion. - uint32_t spindle_speed; // Desired spindle speed through line motion. - uint8_t condition; // Bitflag variable to indicate planner conditions. See defines above. + float feed_rate; // Desired feed rate for line motion. Value is ignored, if rapid motion. + uint32_t spindle_speed; // Desired spindle speed through line motion. + uint8_t condition; // Bitflag variable to indicate planner conditions. See defines above. #ifdef USE_LINE_NUMBERS - int32_t line_number; // Desired line number to report when executing. + int32_t line_number; // Desired line number to report when executing. #endif } plan_line_data_t; - - // Initialize and reset the motion plan subsystem -void plan_reset(); // Reset all -void plan_reset_buffer(); // Reset buffer only. +void plan_reset(); // Reset all +void plan_reset_buffer(); // Reset buffer only. // Add a new linear movement to the buffer. target[N_AXIS] is the signed, absolute target position // in millimeters. Feed rate specifies the speed of the motion. If feed rate is inverted, the feed @@ -147,6 +141,3 @@ uint8_t plan_get_block_buffer_count(); uint8_t plan_check_full_buffer(); void plan_get_planner_mpos(float* target); - - -#endif \ No newline at end of file diff --git a/Grbl_Esp32/probe.cpp b/Grbl_Esp32/src/Probe.cpp similarity index 80% rename from Grbl_Esp32/probe.cpp rename to Grbl_Esp32/src/Probe.cpp index d53d2bc6..5ece4a01 100644 --- a/Grbl_Esp32/probe.cpp +++ b/Grbl_Esp32/src/Probe.cpp @@ -1,5 +1,5 @@ /* - probe.c - code pertaining to probing methods + Probe.cpp - code pertaining to probing methods Part of Grbl Copyright (c) 2014-2016 Sungeun K. Jeon for Gnea Research LLC @@ -21,33 +21,32 @@ along with Grbl. If not, see . */ -#include "grbl.h" - +#include "Grbl.h" // Inverts the probe pin state depending on user settings and probing cycle mode. uint8_t probe_invert_mask; - // Probe pin initialization routine. void probe_init() { #ifdef PROBE_PIN -#ifdef DISABLE_PROBE_PIN_PULL_UP +# ifdef DISABLE_PROBE_PIN_PULL_UP pinMode(PROBE_PIN, INPUT); -#else - pinMode(PROBE_PIN, INPUT_PULLUP); // Enable internal pull-up resistors. Normal high operation. -#endif - probe_configure_invert_mask(false); // Initialize invert mask. +# else + pinMode(PROBE_PIN, INPUT_PULLUP); // Enable internal pull-up resistors. Normal high operation. +# endif + probe_configure_invert_mask(false); // Initialize invert mask. #endif } - // Called by probe_init() and the mc_probe() routines. Sets up the probe pin invert mask to // appropriately set the pin logic according to setting for normal-high/normal-low operation // and the probing cycle modes for toward-workpiece/away-from-workpiece. void probe_configure_invert_mask(uint8_t is_probe_away) { - probe_invert_mask = 0; // Initialize as zero. - if (probe_invert->get()) probe_invert_mask ^= PROBE_MASK; - if (is_probe_away) probe_invert_mask ^= PROBE_MASK; + probe_invert_mask = 0; // Initialize as zero. + if (probe_invert->get()) + probe_invert_mask ^= PROBE_MASK; + if (is_probe_away) + probe_invert_mask ^= PROBE_MASK; } // Returns the probe pin state. Triggered = true. Called by gcode parser and probe state monitor. @@ -59,7 +58,6 @@ uint8_t probe_get_state() { #endif } - // Monitors probe pin state and records the system position when detected. Called by the // stepper ISR per ISR tick. // NOTE: This function must be extremely efficient as to not bog down the stepper ISR. diff --git a/Grbl_Esp32/probe.h b/Grbl_Esp32/src/Probe.h similarity index 87% rename from Grbl_Esp32/probe.h rename to Grbl_Esp32/src/Probe.h index f9fabb05..c7111313 100644 --- a/Grbl_Esp32/probe.h +++ b/Grbl_Esp32/src/Probe.h @@ -1,5 +1,7 @@ +#pragma once + /* - probe.h - code pertaining to probing methods + Probe.h - code pertaining to probing methods Part of Grbl Copyright (c) 2014-2016 Sungeun K. Jeon for Gnea Research LLC @@ -21,12 +23,9 @@ along with Grbl. If not, see . */ -#ifndef probe_h -#define probe_h - // Values that define the probing state machine. -#define PROBE_OFF 0 // Probing disabled or not in use. (Must be zero.) -#define PROBE_ACTIVE 1 // Actively watching the input pin. +#define PROBE_OFF 0 // Probing disabled or not in use. (Must be zero.) +#define PROBE_ACTIVE 1 // Actively watching the input pin. // Probe pin initialization routine. void probe_init(); @@ -42,5 +41,3 @@ uint8_t probe_get_state(); // Monitors probe pin state and records the system position when detected. Called by the // stepper ISR per ISR tick. void probe_state_monitor(); - -#endif diff --git a/Grbl_Esp32/ProcessSettings.cpp b/Grbl_Esp32/src/ProcessSettings.cpp similarity index 60% rename from Grbl_Esp32/ProcessSettings.cpp rename to Grbl_Esp32/src/ProcessSettings.cpp index 68cc5e14..ea343718 100644 --- a/Grbl_Esp32/ProcessSettings.cpp +++ b/Grbl_Esp32/src/ProcessSettings.cpp @@ -1,4 +1,4 @@ -#include "grbl.h" +#include "Grbl.h" #include // WG Readable and writable as guest @@ -9,17 +9,16 @@ bool auth_failed(Word* w, const char* value, auth_t auth_level) { permissions_t permissions = w->getPermissions(); switch (auth_level) { - case LEVEL_ADMIN: // Admin can do anything - return false; // Nothing is an Admin auth fail - case LEVEL_GUEST: // Guest can only access open settings + case LEVEL_ADMIN: // Admin can do anything + return false; // Nothing is an Admin auth fail + case LEVEL_GUEST: // Guest can only access open settings return permissions != WG; // Anything other than RG is Guest auth fail - case LEVEL_USER: // User is complicated... - if (!value) { // User can read anything - return false; // No read is a User auth fail + case LEVEL_USER: // User is complicated... + if (!value) { // User can read anything + return false; // No read is a User auth fail } return permissions == WA; // User cannot write WA - default: - return true; + default: return true; } } @@ -32,45 +31,45 @@ void show_setting(const char* name, const char* value, const char* description, } void settings_restore(uint8_t restore_flag) { - #ifdef WIFI_OR_BLUETOOTH - if (restore_flag & SETTINGS_RESTORE_WIFI_SETTINGS) { - #ifdef ENABLE_WIFI - wifi_config.reset_settings(); - #endif - #ifdef ENABLE_BLUETOOTH - bt_config.reset_settings(); - #endif - } - #endif +#ifdef WIFI_OR_BLUETOOTH + if (restore_flag & SETTINGS_RESTORE_WIFI_SETTINGS) { +# ifdef ENABLE_WIFI + wifi_config.reset_settings(); +# endif +# ifdef ENABLE_BLUETOOTH + bt_config.reset_settings(); +# endif + } +#endif if (restore_flag & SETTINGS_RESTORE_DEFAULTS) { bool restore_startup = restore_flag & SETTINGS_RESTORE_STARTUP_LINES; for (Setting* s = Setting::List; s; s = s->next()) { if (!s->getDescription()) { const char* name = s->getName(); - if (restore_startup) // all settings get restored - s->setDefault(); - else if ((strcmp(name, "Line0") != 0) && (strcmp(name, "Line1") != 0)) // non startup settings get restored + if (restore_startup) // all settings get restored + s->setDefault(); + else if ((strcmp(name, "Line0") != 0) && (strcmp(name, "Line1") != 0)) // non startup settings get restored s->setDefault(); } } } if (restore_flag & SETTINGS_RESTORE_PARAMETERS) { uint8_t idx; - float coord_data[N_AXIS]; + float coord_data[N_AXIS]; memset(&coord_data, 0, sizeof(coord_data)); - for (idx = 0; idx <= SETTING_INDEX_NCOORD; idx++) settings_write_coord_data(idx, coord_data); + for (idx = 0; idx <= SETTING_INDEX_NCOORD; idx++) + settings_write_coord_data(idx, coord_data); } if (restore_flag & SETTINGS_RESTORE_BUILD_INFO) { EEPROM.write(EEPROM_ADDR_BUILD_INFO, 0); - EEPROM.write(EEPROM_ADDR_BUILD_INFO + 1, 0); // Checksum + EEPROM.write(EEPROM_ADDR_BUILD_INFO + 1, 0); // Checksum EEPROM.commit(); } } // Get settings values from non volatile storage into memory -void load_settings() -{ - for (Setting *s = Setting::List; s; s = s->next()) { +void load_settings() { + for (Setting* s = Setting::List; s; s = s->next()) { s->load(); } } @@ -78,8 +77,7 @@ void load_settings() extern void make_settings(); extern void make_grbl_commands(); extern void make_web_settings(); -void settings_init() -{ +void settings_init() { EEPROM.begin(EEPROM_SIZE); make_settings(); make_web_settings(); @@ -92,17 +90,18 @@ void settings_init() // sent to gc_execute_line. It is probably also more time-critical // than actual settings, which change infrequently, so handling // it early is probably prudent. -uint8_t jog_set(uint8_t *value, auth_t auth_level, ESPResponseStream* out) { +uint8_t jog_set(uint8_t* value, auth_t auth_level, ESPResponseStream* out) { // Execute only if in IDLE or JOG states. - if (sys.state != STATE_IDLE && sys.state != STATE_JOG) return STATUS_IDLE_ERROR; + if (sys.state != STATE_IDLE && sys.state != STATE_JOG) + return STATUS_IDLE_ERROR; - // restore the $J= prefix because gc_execute_line() expects it + // restore the $J= prefix because gc_execute_line() expects it #define MAXLINE 128 char line[MAXLINE]; strcpy(line, "$J="); - strncat(line, (char *)value, MAXLINE-strlen("$J=")-1); + strncat(line, (char*)value, MAXLINE - strlen("$J=") - 1); - return gc_execute_line(line, out->client()); // NOTE: $J= is ignored inside g-code parser and used to detect jog motions. + return gc_execute_line(line, out->client()); // NOTE: $J= is ignored inside g-code parser and used to detect jog motions. } err_t show_grbl_help(const char* value, auth_t auth_level, ESPResponseStream* out) { @@ -110,13 +109,13 @@ err_t show_grbl_help(const char* value, auth_t auth_level, ESPResponseStream* ou return STATUS_OK; } -err_t report_gcode(const char *value, auth_t auth_level, ESPResponseStream* out) { +err_t report_gcode(const char* value, auth_t auth_level, ESPResponseStream* out) { report_gcode_modes(out->client()); return STATUS_OK; } void show_grbl_settings(ESPResponseStream* out, type_t type, bool wantAxis) { - for (Setting *s = Setting::List; s; s = s->next()) { + for (Setting* s = Setting::List; s; s = s->next()) { if (s->getType() == type && s->getGrblName()) { bool isAxis = s->getAxis() != NO_AXIS; // The following test could be expressed more succinctly with XOR, @@ -128,20 +127,19 @@ void show_grbl_settings(ESPResponseStream* out, type_t type, bool wantAxis) { } } err_t report_normal_settings(const char* value, auth_t auth_level, ESPResponseStream* out) { - show_grbl_settings(out, GRBL, false); // GRBL non-axis settings - show_grbl_settings(out, GRBL, true); // GRBL axis settings + show_grbl_settings(out, GRBL, false); // GRBL non-axis settings + show_grbl_settings(out, GRBL, true); // GRBL axis settings return STATUS_OK; } err_t report_extended_settings(const char* value, auth_t auth_level, ESPResponseStream* out) { - show_grbl_settings(out, GRBL, false); // GRBL non-axis settings - show_grbl_settings(out, EXTENDED, false); // Extended non-axis settings - show_grbl_settings(out, GRBL, true); // GRBL axis settings - show_grbl_settings(out, EXTENDED, true); // Extended axis settings + show_grbl_settings(out, GRBL, false); // GRBL non-axis settings + show_grbl_settings(out, EXTENDED, false); // Extended non-axis settings + show_grbl_settings(out, GRBL, true); // GRBL axis settings + show_grbl_settings(out, EXTENDED, true); // Extended axis settings return STATUS_OK; } -err_t list_grbl_names(const char* value, auth_t auth_level, ESPResponseStream* out) -{ - for (Setting *s = Setting::List; s; s = s->next()) { +err_t list_grbl_names(const char* value, auth_t auth_level, ESPResponseStream* out) { + for (Setting* s = Setting::List; s; s = s->next()) { const char* gn = s->getGrblName(); if (gn) { grbl_sendf(out->client(), "$%s => $%s\r\n", gn, s->getName()); @@ -149,20 +147,16 @@ err_t list_grbl_names(const char* value, auth_t auth_level, ESPResponseStream* o } return STATUS_OK; } -err_t list_settings(const char* value, auth_t auth_level, ESPResponseStream* out) -{ - for (Setting *s = Setting::List; s; s = s->next()) { - const char *displayValue = auth_failed(s, value, auth_level) - ? "" - : s->getStringValue(); +err_t list_settings(const char* value, auth_t auth_level, ESPResponseStream* out) { + for (Setting* s = Setting::List; s; s = s->next()) { + const char* displayValue = auth_failed(s, value, auth_level) ? "" : s->getStringValue(); show_setting(s->getName(), displayValue, NULL, out); } return STATUS_OK; } -err_t list_commands(const char* value, auth_t auth_level, ESPResponseStream* out) -{ - for (Command *cp = Command::List; cp; cp = cp->next()) { - const char* name = cp->getName(); +err_t list_commands(const char* value, auth_t auth_level, ESPResponseStream* out) { + for (Command* cp = Command::List; cp; cp = cp->next()) { + const char* name = cp->getName(); const char* oldName = cp->getGrblName(); if (oldName) { grbl_sendf(out->client(), "$%s or $%s", name, oldName); @@ -185,7 +179,8 @@ err_t toggle_check_mode(const char* value, auth_t auth_level, ESPResponseStream* mc_reset(); report_feedback_message(MESSAGE_DISABLED); } else { - if (sys.state) return (STATUS_IDLE_ERROR); // Requires no alarm mode. + if (sys.state) + return (STATUS_IDLE_ERROR); // Requires no alarm mode. sys.state = STATE_CHECK_MODE; report_feedback_message(MESSAGE_ENABLED); } @@ -199,7 +194,7 @@ err_t disable_alarm_lock(const char* value, auth_t auth_level, ESPResponseStream report_feedback_message(MESSAGE_ALARM_UNLOCK); sys.state = STATE_IDLE; // Don't run startup script. Prevents stored moves in startup from causing accidents. - } // Otherwise, no effect. + } // Otherwise, no effect. return STATUS_OK; } err_t report_ngc(const char* value, auth_t auth_level, ESPResponseStream* out) { @@ -210,12 +205,12 @@ err_t home(int cycle) { if (homing_enable->get() == false) return (STATUS_SETTING_DISABLED); if (system_check_safety_door_ajar()) - return (STATUS_CHECK_DOOR); // Block if safety door is ajar. - sys.state = STATE_HOMING; // Set system state variable + return (STATUS_CHECK_DOOR); // Block if safety door is ajar. + sys.state = STATE_HOMING; // Set system state variable mc_homing_cycle(cycle); - if (!sys.abort) { // Execute startup scripts after successful homing. - sys.state = STATE_IDLE; // Set to IDLE when complete. - st_go_idle(); // Set steppers to the settings idle state before returning. + if (!sys.abort) { // Execute startup scripts after successful homing. + sys.state = STATE_IDLE; // Set to IDLE when complete. + st_go_idle(); // Set steppers to the settings idle state before returning. if (cycle == HOMING_CYCLE_ALL) { char line[128]; system_execute_startup(line); @@ -255,12 +250,12 @@ err_t get_report_build_info(const char* value, auth_t auth_level, ESPResponseStr report_build_info(line, out->client()); return STATUS_OK; } - #ifdef ENABLE_BUILD_INFO_WRITE_COMMAND - settings_store_build_info(value); - return STATUS_OK; - #else - return STATUS_INVALID_STATEMENT; - #endif +#ifdef ENABLE_BUILD_INFO_WRITE_COMMAND + settings_store_build_info(value); + return STATUS_OK; +#else + return STATUS_INVALID_STATEMENT; +#endif } err_t report_startup_lines(const char* value, auth_t auth_level, ESPResponseStream* out) { report_startup_line(0, startup_line_0->get(), out->client()); @@ -269,20 +264,16 @@ err_t report_startup_lines(const char* value, auth_t auth_level, ESPResponseStre } std::map restoreCommands = { - #ifdef ENABLE_RESTORE_EEPROM_DEFAULT_SETTINGS - { "$", SETTINGS_RESTORE_DEFAULTS }, - { "settings", SETTINGS_RESTORE_DEFAULTS }, - #endif - #ifdef ENABLE_RESTORE_EEPROM_CLEAR_PARAMETERS - { "#", SETTINGS_RESTORE_PARAMETERS }, - { "gcode", SETTINGS_RESTORE_PARAMETERS }, - #endif - #ifdef ENABLE_RESTORE_EEPROM_WIPE_ALL - { "*", SETTINGS_RESTORE_ALL }, - { "all", SETTINGS_RESTORE_ALL }, - #endif - { "@", SETTINGS_RESTORE_WIFI_SETTINGS }, - { "wifi", SETTINGS_RESTORE_WIFI_SETTINGS }, +#ifdef ENABLE_RESTORE_EEPROM_DEFAULT_SETTINGS + { "$", SETTINGS_RESTORE_DEFAULTS }, { "settings", SETTINGS_RESTORE_DEFAULTS }, +#endif +#ifdef ENABLE_RESTORE_EEPROM_CLEAR_PARAMETERS + { "#", SETTINGS_RESTORE_PARAMETERS }, { "gcode", SETTINGS_RESTORE_PARAMETERS }, +#endif +#ifdef ENABLE_RESTORE_EEPROM_WIPE_ALL + { "*", SETTINGS_RESTORE_ALL }, { "all", SETTINGS_RESTORE_ALL }, +#endif + { "@", SETTINGS_RESTORE_WIFI_SETTINGS }, { "wifi", SETTINGS_RESTORE_WIFI_SETTINGS }, }; err_t restore_settings(const char* value, auth_t auth_level, ESPResponseStream* out) { if (!value) { @@ -315,61 +306,61 @@ err_t doJog(const char* value, auth_t auth_level, ESPResponseStream* out) { } std::map ErrorCodes = { - { STATUS_OK , "No error", }, - { STATUS_EXPECTED_COMMAND_LETTER , "Expected GCodecommand letter", }, - { STATUS_BAD_NUMBER_FORMAT , "Bad GCode number format", }, - { STATUS_INVALID_STATEMENT , "Invalid $ statement", }, - { STATUS_NEGATIVE_VALUE , "Negative value", }, - { STATUS_SETTING_DISABLED , "Setting disabled", }, - { STATUS_SETTING_STEP_PULSE_MIN , "Step pulse too short", }, - { STATUS_SETTING_READ_FAIL , "Failed to read settings", }, - { STATUS_IDLE_ERROR , "Command requires idle state", }, - { STATUS_SYSTEM_GC_LOCK , "GCode cannot be executed in lock or alarm state", }, - { STATUS_SOFT_LIMIT_ERROR , "Soft limit error", }, - { STATUS_OVERFLOW , "Line too long", }, - { STATUS_MAX_STEP_RATE_EXCEEDED , "Max step rate exceeded", }, - { STATUS_CHECK_DOOR , "Check door", }, - { STATUS_LINE_LENGTH_EXCEEDED , "Startup line too long", }, - { STATUS_TRAVEL_EXCEEDED , "Max travel exceeded during jog", }, - { STATUS_INVALID_JOG_COMMAND , "Invalid jog command", }, - { STATUS_SETTING_DISABLED_LASER , "Laser mode requires PWM output", }, - { STATUS_GCODE_UNSUPPORTED_COMMAND , "Unsupported GCode command", }, - { STATUS_GCODE_MODAL_GROUP_VIOLATION , "Gcode modal group violation", }, - { STATUS_GCODE_UNDEFINED_FEED_RATE , "Gcode undefined feed rate", }, - { STATUS_GCODE_COMMAND_VALUE_NOT_INTEGER , "Gcode command value not integer", }, - { STATUS_GCODE_AXIS_COMMAND_CONFLICT , "Gcode axis command conflict", }, - { STATUS_GCODE_WORD_REPEATED , "Gcode word repeated", }, - { STATUS_GCODE_NO_AXIS_WORDS , "Gcode no axis words", }, - { STATUS_GCODE_INVALID_LINE_NUMBER , "Gcode invalid line number", }, - { STATUS_GCODE_VALUE_WORD_MISSING , "Gcode value word missing", }, - { STATUS_GCODE_UNSUPPORTED_COORD_SYS , "Gcode unsupported coordinate system", }, - { STATUS_GCODE_G53_INVALID_MOTION_MODE , "Gcode G53 invalid motion mode", }, - { STATUS_GCODE_AXIS_WORDS_EXIST , "Gcode extra axis words", }, - { STATUS_GCODE_NO_AXIS_WORDS_IN_PLANE , "Gcode no axis words in plane", }, - { STATUS_GCODE_INVALID_TARGET , "Gcode invalid target", }, - { STATUS_GCODE_ARC_RADIUS_ERROR , "Gcode arc radius error", }, - { STATUS_GCODE_NO_OFFSETS_IN_PLANE , "Gcode no offsets in plane", }, - { STATUS_GCODE_UNUSED_WORDS , "Gcode unused words", }, - { STATUS_GCODE_G43_DYNAMIC_AXIS_ERROR , "Gcode G43 dynamic axis error", }, - { STATUS_GCODE_MAX_VALUE_EXCEEDED , "Gcode max value exceeded", }, - { STATUS_P_PARAM_MAX_EXCEEDED , "P param max exceeded", }, - { STATUS_SD_FAILED_MOUNT , "SD failed mount", }, - { STATUS_SD_FAILED_READ , "SD failed read", }, - { STATUS_SD_FAILED_OPEN_DIR , "SD failed to open directory", }, - { STATUS_SD_DIR_NOT_FOUND , "SD directory not found", }, - { STATUS_SD_FILE_EMPTY , "SD file empty", }, - { STATUS_SD_FILE_NOT_FOUND , "SD file not found", }, - { STATUS_SD_FAILED_OPEN_FILE , "SD failed to open file", }, - { STATUS_SD_FAILED_BUSY , "SD is busy", }, - { STATUS_SD_FAILED_DEL_DIR, "SD failed to delete directory", }, - { STATUS_SD_FAILED_DEL_FILE, "SD failed to delete file", }, - { STATUS_BT_FAIL_BEGIN , "Bluetooth failed to start", }, - { STATUS_WIFI_FAIL_BEGIN , "WiFi failed to start", }, - { STATUS_NUMBER_RANGE , "Number out of range for setting", }, - { STATUS_INVALID_VALUE , "Invalid value for setting", }, - { STATUS_MESSAGE_FAILED , "Failed to send message", }, - { STATUS_NVS_SET_FAILED , "Failed to store setting", }, - { STATUS_AUTHENTICATION_FAILED, "Authentication failed!", }, + { STATUS_OK, "No error" }, + { STATUS_EXPECTED_COMMAND_LETTER, "Expected GCodecommand letter" }, + { STATUS_BAD_NUMBER_FORMAT, "Bad GCode number format" }, + { STATUS_INVALID_STATEMENT, "Invalid $ statement" }, + { STATUS_NEGATIVE_VALUE, "Negative value" }, + { STATUS_SETTING_DISABLED, "Setting disabled" }, + { STATUS_SETTING_STEP_PULSE_MIN, "Step pulse too short" }, + { STATUS_SETTING_READ_FAIL, "Failed to read settings" }, + { STATUS_IDLE_ERROR, "Command requires idle state" }, + { STATUS_SYSTEM_GC_LOCK, "GCode cannot be executed in lock or alarm state" }, + { STATUS_SOFT_LIMIT_ERROR, "Soft limit error" }, + { STATUS_OVERFLOW, "Line too long" }, + { STATUS_MAX_STEP_RATE_EXCEEDED, "Max step rate exceeded" }, + { STATUS_CHECK_DOOR, "Check door" }, + { STATUS_LINE_LENGTH_EXCEEDED, "Startup line too long" }, + { STATUS_TRAVEL_EXCEEDED, "Max travel exceeded during jog" }, + { STATUS_INVALID_JOG_COMMAND, "Invalid jog command" }, + { STATUS_SETTING_DISABLED_LASER, "Laser mode requires PWM output" }, + { STATUS_GCODE_UNSUPPORTED_COMMAND, "Unsupported GCode command" }, + { STATUS_GCODE_MODAL_GROUP_VIOLATION, "Gcode modal group violation" }, + { STATUS_GCODE_UNDEFINED_FEED_RATE, "Gcode undefined feed rate" }, + { STATUS_GCODE_COMMAND_VALUE_NOT_INTEGER, "Gcode command value not integer" }, + { STATUS_GCODE_AXIS_COMMAND_CONFLICT, "Gcode axis command conflict" }, + { STATUS_GCODE_WORD_REPEATED, "Gcode word repeated" }, + { STATUS_GCODE_NO_AXIS_WORDS, "Gcode no axis words" }, + { STATUS_GCODE_INVALID_LINE_NUMBER, "Gcode invalid line number" }, + { STATUS_GCODE_VALUE_WORD_MISSING, "Gcode value word missing" }, + { STATUS_GCODE_UNSUPPORTED_COORD_SYS, "Gcode unsupported coordinate system" }, + { STATUS_GCODE_G53_INVALID_MOTION_MODE, "Gcode G53 invalid motion mode" }, + { STATUS_GCODE_AXIS_WORDS_EXIST, "Gcode extra axis words" }, + { STATUS_GCODE_NO_AXIS_WORDS_IN_PLANE, "Gcode no axis words in plane" }, + { STATUS_GCODE_INVALID_TARGET, "Gcode invalid target" }, + { STATUS_GCODE_ARC_RADIUS_ERROR, "Gcode arc radius error" }, + { STATUS_GCODE_NO_OFFSETS_IN_PLANE, "Gcode no offsets in plane" }, + { STATUS_GCODE_UNUSED_WORDS, "Gcode unused words" }, + { STATUS_GCODE_G43_DYNAMIC_AXIS_ERROR, "Gcode G43 dynamic axis error" }, + { STATUS_GCODE_MAX_VALUE_EXCEEDED, "Gcode max value exceeded" }, + { STATUS_P_PARAM_MAX_EXCEEDED, "P param max exceeded" }, + { STATUS_SD_FAILED_MOUNT, "SD failed mount" }, + { STATUS_SD_FAILED_READ, "SD failed read" }, + { STATUS_SD_FAILED_OPEN_DIR, "SD failed to open directory" }, + { STATUS_SD_DIR_NOT_FOUND, "SD directory not found" }, + { STATUS_SD_FILE_EMPTY, "SD file empty" }, + { STATUS_SD_FILE_NOT_FOUND, "SD file not found" }, + { STATUS_SD_FAILED_OPEN_FILE, "SD failed to open file" }, + { STATUS_SD_FAILED_BUSY, "SD is busy" }, + { STATUS_SD_FAILED_DEL_DIR, "SD failed to delete directory" }, + { STATUS_SD_FAILED_DEL_FILE, "SD failed to delete file" }, + { STATUS_BT_FAIL_BEGIN, "Bluetooth failed to start" }, + { STATUS_WIFI_FAIL_BEGIN, "WiFi failed to start" }, + { STATUS_NUMBER_RANGE, "Number out of range for setting" }, + { STATUS_INVALID_VALUE, "Invalid value for setting" }, + { STATUS_MESSAGE_FAILED, "Failed to send message" }, + { STATUS_NVS_SET_FAILED, "Failed to store setting" }, + { STATUS_AUTHENTICATION_FAILED, "Authentication failed!" }, }; const char* errorString(err_t errorNumber) { @@ -379,7 +370,7 @@ const char* errorString(err_t errorNumber) { err_t listErrorCodes(const char* value, auth_t auth_level, ESPResponseStream* out) { if (value) { - char* endptr = NULL; + char* endptr = NULL; uint8_t errorNumber = strtol(value, &endptr, 10); if (*endptr) { grbl_sendf(out->client(), "Malformed error number: %s\r\n", value); @@ -395,55 +386,52 @@ err_t listErrorCodes(const char* value, auth_t auth_level, ESPResponseStream* ou } } - for (auto it = ErrorCodes.begin(); - it != ErrorCodes.end(); - it++) { + for (auto it = ErrorCodes.begin(); it != ErrorCodes.end(); it++) { grbl_sendf(out->client(), "%d: %s\r\n", it->first, it->second); } return STATUS_OK; } - // Commands use the same syntax as Settings, but instead of setting or // displaying a persistent value, a command causes some action to occur. // That action could be anything, from displaying a run-time parameter // to performing some system state change. Each command is responsible // for decoding its own value string, if it needs one. void make_grbl_commands() { - new GrblCommand("", "Help", show_grbl_help, ANY_STATE); - new GrblCommand("T", "State", showState, ANY_STATE); - new GrblCommand("J", "Jog", doJog, IDLE_OR_JOG); + new GrblCommand("", "Help", show_grbl_help, ANY_STATE); + new GrblCommand("T", "State", showState, ANY_STATE); + new GrblCommand("J", "Jog", doJog, IDLE_OR_JOG); - new GrblCommand("$", "GrblSettings/List", report_normal_settings, NOT_CYCLE_OR_HOLD); - new GrblCommand("+", "ExtendedSettings/List", report_extended_settings, NOT_CYCLE_OR_HOLD); - new GrblCommand("L", "GrblNames/List", list_grbl_names, NOT_CYCLE_OR_HOLD); - new GrblCommand("S", "Settings/List", list_settings, NOT_CYCLE_OR_HOLD); - new GrblCommand("CMD", "Commands/List", list_commands, NOT_CYCLE_OR_HOLD); - new GrblCommand("E", "ErrorCodes/List",listErrorCodes, ANY_STATE); - new GrblCommand("G", "GCode/Modes", report_gcode, ANY_STATE); - new GrblCommand("C", "GCode/Check", toggle_check_mode, ANY_STATE); - new GrblCommand("X", "Alarm/Disable", disable_alarm_lock, ANY_STATE); + new GrblCommand("$", "GrblSettings/List", report_normal_settings, NOT_CYCLE_OR_HOLD); + new GrblCommand("+", "ExtendedSettings/List", report_extended_settings, NOT_CYCLE_OR_HOLD); + new GrblCommand("L", "GrblNames/List", list_grbl_names, NOT_CYCLE_OR_HOLD); + new GrblCommand("S", "Settings/List", list_settings, NOT_CYCLE_OR_HOLD); + new GrblCommand("CMD", "Commands/List", list_commands, NOT_CYCLE_OR_HOLD); + new GrblCommand("E", "ErrorCodes/List", listErrorCodes, ANY_STATE); + new GrblCommand("G", "GCode/Modes", report_gcode, ANY_STATE); + new GrblCommand("C", "GCode/Check", toggle_check_mode, ANY_STATE); + new GrblCommand("X", "Alarm/Disable", disable_alarm_lock, ANY_STATE); new GrblCommand("NVX", "Settings/Erase", Setting::eraseNVS, IDLE_OR_ALARM, WA); - new GrblCommand("V", "Settings/Stats", Setting::report_nvs_stats, IDLE_OR_ALARM); - new GrblCommand("#", "GCode/Offsets", report_ngc, IDLE_OR_ALARM); - new GrblCommand("H", "Home", home_all, IDLE_OR_ALARM); - #ifdef HOMING_SINGLE_AXIS_COMMANDS - new GrblCommand("HX", "Home/X", home_x, IDLE_OR_ALARM); - new GrblCommand("HY", "Home/Y", home_y, IDLE_OR_ALARM); - new GrblCommand("HZ", "Home/Z", home_z, IDLE_OR_ALARM); - #if (N_AXIS > 3) - new GrblCommand("HA", "Home/A", home_a, IDLE_OR_ALARM); - #endif - #if (N_AXIS > 4) - new GrblCommand("HB", "Home/B", home_b, IDLE_OR_ALARM); - #endif - #if (N_AXIS > 5) - new GrblCommand("HC", "Home/C", home_c, IDLE_OR_ALARM); - #endif - #endif + new GrblCommand("V", "Settings/Stats", Setting::report_nvs_stats, IDLE_OR_ALARM); + new GrblCommand("#", "GCode/Offsets", report_ngc, IDLE_OR_ALARM); + new GrblCommand("H", "Home", home_all, IDLE_OR_ALARM); +#ifdef HOMING_SINGLE_AXIS_COMMANDS + new GrblCommand("HX", "Home/X", home_x, IDLE_OR_ALARM); + new GrblCommand("HY", "Home/Y", home_y, IDLE_OR_ALARM); + new GrblCommand("HZ", "Home/Z", home_z, IDLE_OR_ALARM); +# if (N_AXIS > 3) + new GrblCommand("HA", "Home/A", home_a, IDLE_OR_ALARM); +# endif +# if (N_AXIS > 4) + new GrblCommand("HB", "Home/B", home_b, IDLE_OR_ALARM); +# endif +# if (N_AXIS > 5) + new GrblCommand("HC", "Home/C", home_c, IDLE_OR_ALARM); +# endif +#endif new GrblCommand("SLP", "System/Sleep", sleep_grbl, IDLE_OR_ALARM); - new GrblCommand("I", "Build/Info", get_report_build_info, IDLE_OR_ALARM); - new GrblCommand("N", "GCode/StartupLines", report_startup_lines, IDLE_OR_ALARM); + new GrblCommand("I", "Build/Info", get_report_build_info, IDLE_OR_ALARM); + new GrblCommand("N", "GCode/StartupLines", report_startup_lines, IDLE_OR_ALARM); new GrblCommand("RST", "Settings/Restore", restore_settings, IDLE_OR_ALARM, WA); }; @@ -452,7 +440,7 @@ void make_grbl_commands() { // start points to a null-terminated string. // Returns the first substring that does not contain whitespace. // Case is unchanged because comparisons are case-insensitive. -char *normalize_key(char *start) { +char* normalize_key(char* start) { char c; // In the usual case, this loop will exit on the very first test, @@ -469,9 +457,8 @@ char *normalize_key(char *start) { // Having found the beginning of the printable string, // we now scan forward until we find a space character. - char *end; - for (end = start; (c = *end) != '\0' && !isspace(c); end++) { - } + char* end; + for (end = start; (c = *end) != '\0' && !isspace(c); end++) {} // end now points to either a whitespace character of end of string // In either case it is okay to place a null there @@ -482,7 +469,7 @@ char *normalize_key(char *start) { // This is the handler for all forms of settings commands, // $..= and [..], with and without a value. -err_t do_command_or_setting(const char *key, char *value, auth_t auth_level, ESPResponseStream* out) { +err_t do_command_or_setting(const char* key, char* value, auth_t auth_level, ESPResponseStream* out) { // If value is NULL, it means that there was no value string, i.e. // $key without =, or [key] with nothing following. // If value is not NULL, but the string is empty, that is the form @@ -491,7 +478,7 @@ err_t do_command_or_setting(const char *key, char *value, auth_t auth_level, ESP // First search the settings list by text name. If found, set a new // value if one is given, otherwise display the current value - for (Setting *s = Setting::List; s; s = s->next()) { + for (Setting* s = Setting::List; s; s = s->next()) { if (strcasecmp(s->getName(), key) == 0) { if (auth_failed(s, value, auth_level)) { return STATUS_AUTHENTICATION_FAILED; @@ -507,7 +494,7 @@ err_t do_command_or_setting(const char *key, char *value, auth_t auth_level, ESP // Then search the setting list by compatible name. If found, set a new // value if one is given, otherwise display the current value in compatible mode - for (Setting *s = Setting::List; s; s = s->next()) { + for (Setting* s = Setting::List; s; s = s->next()) { if (s->getGrblName() && strcasecmp(s->getGrblName(), key) == 0) { if (auth_failed(s, value, auth_level)) { return STATUS_AUTHENTICATION_FAILED; @@ -523,12 +510,8 @@ err_t do_command_or_setting(const char *key, char *value, auth_t auth_level, ESP // If we did not find a setting, look for a command. Commands // handle values internally; you cannot determine whether to set // or display solely based on the presence of a value. - for (Command *cp = Command::List; cp; cp = cp->next()) { - if ( (strcasecmp(cp->getName(), key) == 0) - || (cp->getGrblName() - && strcasecmp(cp->getGrblName(), key) == 0 - ) - ) { + for (Command* cp = Command::List; cp; cp = cp->next()) { + if ((strcasecmp(cp->getName(), key) == 0) || (cp->getGrblName() && strcasecmp(cp->getGrblName(), key) == 0)) { if (auth_failed(cp, value, auth_level)) { return STATUS_AUTHENTICATION_FAILED; } @@ -547,18 +530,16 @@ err_t do_command_or_setting(const char *key, char *value, auth_t auth_level, ESP // This lets us look at X axis settings with $*x. // $x by itself is the disable alarm lock command if (lcKey.startsWith("*")) { - lcKey.remove(0,1); + lcKey.remove(0, 1); } lcKey.toLowerCase(); bool found = false; - for (Setting *s = Setting::List; s; s = s->next()) { + for (Setting* s = Setting::List; s; s = s->next()) { auto lcTest = String(s->getName()); lcTest.toLowerCase(); if (lcTest.indexOf(lcKey) >= 0) { - const char *displayValue = auth_failed(s, value, auth_level) - ? "" - : s->getStringValue(); + const char* displayValue = auth_failed(s, value, auth_level) ? "" : s->getStringValue(); show_setting(s->getName(), displayValue, NULL, out); found = true; } @@ -573,8 +554,8 @@ err_t do_command_or_setting(const char *key, char *value, auth_t auth_level, ESP uint8_t system_execute_line(char* line, ESPResponseStream* out, auth_t auth_level) { remove_password(line, auth_level); - char *value; - if (*line++ == '[') { // [ESPxxx] form + char* value; + if (*line++ == '[') { // [ESPxxx] form value = strrchr(line, ']'); if (!value) { // Missing ] is an error in this form @@ -595,7 +576,7 @@ uint8_t system_execute_line(char* line, ESPResponseStream* out, auth_t auth_leve } } - char *key = normalize_key(line); + char* key = normalize_key(line); // At this point there are three possibilities for value // NULL - $xxx without = @@ -610,7 +591,7 @@ uint8_t system_execute_line(char* line, uint8_t client, auth_t auth_level) { void system_execute_startup(char* line) { err_t status_code; - char gcline[256]; + char gcline[256]; strncpy(gcline, startup_line_0->get(), 255); if (*gcline) { status_code = gc_execute_line(gcline, CLIENT_SERIAL); @@ -622,4 +603,3 @@ void system_execute_startup(char* line) { report_execute_startup_message(gcline, status_code, CLIENT_SERIAL); } } - diff --git a/Grbl_Esp32/protocol.cpp b/Grbl_Esp32/src/Protocol.cpp similarity index 74% rename from Grbl_Esp32/protocol.cpp rename to Grbl_Esp32/src/Protocol.cpp index 01d77c66..56f9fd2c 100644 --- a/Grbl_Esp32/protocol.cpp +++ b/Grbl_Esp32/src/Protocol.cpp @@ -1,5 +1,5 @@ /* - protocol.c - controls Grbl execution protocol and procedures + Protocol.cpp - controls Grbl execution protocol and procedures Part of Grbl Copyright (c) 2011-2016 Sungeun K. Jeon for Gnea Research LLC @@ -22,14 +22,14 @@ along with Grbl. If not, see . */ -#include "grbl.h" +#include "Grbl.h" static void protocol_exec_rt_suspend(); -static char line[LINE_BUFFER_SIZE]; // Line to be executed. Zero-terminated. -static char comment[LINE_BUFFER_SIZE]; // Line to be executed. Zero-terminated. -static uint8_t line_flags = 0; -static uint8_t char_counter = 0; +static char line[LINE_BUFFER_SIZE]; // Line to be executed. Zero-terminated. +static char comment[LINE_BUFFER_SIZE]; // Line to be executed. Zero-terminated. +static uint8_t line_flags = 0; +static uint8_t char_counter = 0; static uint8_t comment_char_counter = 0; typedef struct { @@ -41,8 +41,8 @@ client_line_t client_lines[CLIENT_COUNT]; static void empty_line(uint8_t client) { client_line_t* cl = &client_lines[client]; - cl->len = 0; - cl->buffer[0] = '\0'; + cl->len = 0; + cl->buffer[0] = '\0'; } static void empty_lines() { for (uint8_t client = 0; client < CLIENT_COUNT; client++) @@ -68,7 +68,7 @@ err_t add_char_to_line(char c, uint8_t client) { return STATUS_EOL; } cl->buffer[cl->len++] = c; - cl->buffer[cl->len] = '\0'; + cl->buffer[cl->len] = '\0'; return STATUS_OK; } @@ -86,6 +86,14 @@ err_t execute_line(char* line, uint8_t client, auth_t auth_level) { return gc_execute_line(line, client); } +bool can_park() { + return +#ifdef ENABLE_PARKING_OVERRIDE_CONTROL + sys.override_ctrl == OVERRIDE_PARKING_MOTION && +#endif + homing_enable->get() && !laser_mode->get(); +} + /* GRBL PRIMARY LOOP: */ @@ -97,7 +105,7 @@ void protocol_main_loop() { #ifdef CHECK_LIMITS_AT_INIT if (hard_limits->get()) { if (limits_get_state()) { - sys.state = STATE_ALARM; // Ensure alarm state is active. + sys.state = STATE_ALARM; // Ensure alarm state is active. report_feedback_message(MESSAGE_CHECK_LIMITS); } } @@ -107,16 +115,16 @@ void protocol_main_loop() { // Re-initialize the sleep state as an ALARM mode to ensure user homes or acknowledges. if (sys.state & (STATE_ALARM | STATE_SLEEP)) { report_feedback_message(MESSAGE_ALARM_LOCK); - sys.state = STATE_ALARM; // Ensure alarm state is set. + sys.state = STATE_ALARM; // Ensure alarm state is set. } else { // Check if the safety door is open. sys.state = STATE_IDLE; if (system_check_safety_door_ajar()) { bit_true(sys_rt_exec_state, EXEC_SAFETY_DOOR); - protocol_execute_realtime(); // Enter safety door mode. Should return as IDLE state. + protocol_execute_realtime(); // Enter safety door mode. Should return as IDLE state. } // All systems go! - system_execute_startup(line); // Execute startup script. + system_execute_startup(line); // Execute startup script. } // --------------------------------------------------------------------------------- // Primary loop! Upon a system abort, this exits back to main() to reset the system. @@ -134,7 +142,7 @@ void protocol_main_loop() { char temp[50]; sd_get_current_filename(temp); grbl_notifyf("SD print done", "%s print is successful", temp); - closeFile(); // close file and clear SD ready/running flags + closeFile(); // close file and clear SD ready/running flags } } #endif @@ -142,40 +150,39 @@ void protocol_main_loop() { // Filtering, if necessary, is done later in gc_execute_line(), so the // filtering is the same with serial and file input. uint8_t client = CLIENT_SERIAL; - char* line; + char* line; for (client = 0; client < CLIENT_COUNT; client++) { while ((c = serial_read(client)) != SERIAL_NO_DATA) { err_t res = add_char_to_line(c, client); switch (res) { - case STATUS_OK: - break; - case STATUS_EOL: - protocol_execute_realtime(); // Runtime command check point. - if (sys.abort) { - return; // Bail to calling function upon system abort - } - line = client_lines[client].buffer; + case STATUS_OK: break; + case STATUS_EOL: + protocol_execute_realtime(); // Runtime command check point. + if (sys.abort) { + return; // Bail to calling function upon system abort + } + line = client_lines[client].buffer; #ifdef REPORT_ECHO_RAW_LINE_RECEIVED - report_echo_line_received(line, client); + report_echo_line_received(line, client); #endif - // auth_level can be upgraded by supplying a password on the command line - report_status_message(execute_line(line, client, LEVEL_GUEST), client); - empty_line(client); - break; - case STATUS_OVERFLOW: - report_status_message(STATUS_OVERFLOW, client); - empty_line(client); - break; + // auth_level can be upgraded by supplying a password on the command line + report_status_message(execute_line(line, client, LEVEL_GUEST), client); + empty_line(client); + break; + case STATUS_OVERFLOW: + report_status_message(STATUS_OVERFLOW, client); + empty_line(client); + break; } - } // while serial read - } // for clients + } // while serial read + } // for clients // If there are no more characters in the serial read buffer to be processed and executed, // this indicates that g-code streaming has either filled the planner buffer or has // completed. In either case, auto-cycle start, if enabled, any queued moves. protocol_auto_cycle_start(); protocol_execute_realtime(); // Runtime command check point. if (sys.abort) { - return; // Bail to main() program loop to reset system. + return; // Bail to main() program loop to reset system. } // check to see if we should disable the stepper drivers ... esp32 work around for disable in main loop. if (stepper_idle) { @@ -186,19 +193,18 @@ void protocol_main_loop() { return; /* Never reached */ } - // Block until all buffered steps are executed or in a cycle state. Works with feed hold // during a synchronize call, if it should happen. Also, waits for clean cycle end. void protocol_buffer_synchronize() { // If system is queued, ensure cycle resumes if the auto start flag is present. protocol_auto_cycle_start(); do { - protocol_execute_realtime(); // Check and execute run-time commands - if (sys.abort) return; // Check for system abort + protocol_execute_realtime(); // Check and execute run-time commands + if (sys.abort) + return; // Check for system abort } while (plan_get_current_block() || (sys.state == STATE_CYCLE)); } - // Auto-cycle start triggers when there is a motion ready to execute and if the main program is not // actively parsing commands. // NOTE: This function is called from the main loop, buffer sync, and mc_line() only and executes @@ -206,12 +212,11 @@ void protocol_buffer_synchronize() { // is finished, single commands), a command that needs to wait for the motions in the buffer to // execute calls a buffer sync, or the planner buffer is full and ready to go. void protocol_auto_cycle_start() { - if (plan_get_current_block() != NULL) { // Check if there are any blocks in the buffer. - system_set_exec_state_flag(EXEC_CYCLE_START); // If so, execute them! + if (plan_get_current_block() != NULL) { // Check if there are any blocks in the buffer. + system_set_exec_state_flag(EXEC_CYCLE_START); // If so, execute them! } } - // This function is the general interface to Grbl's real-time command execution system. It is called // from various check points in the main program, primarily where there may be a while loop waiting // for a buffer to clear space or any point where the execution time from the last check point may @@ -225,26 +230,26 @@ void protocol_auto_cycle_start() { // limit switches, or the main program. void protocol_execute_realtime() { protocol_exec_rt_system(); - if (sys.suspend) protocol_exec_rt_suspend(); + if (sys.suspend) + protocol_exec_rt_suspend(); } - // Executes run-time commands, when required. This function primarily operates as Grbl's state // machine and controls the various real-time features Grbl has to offer. // NOTE: Do not alter this unless you know exactly what you are doing! void protocol_exec_rt_system() { - uint8_t rt_exec; // Temp variable to avoid calling volatile multiple times. - rt_exec = sys_rt_exec_alarm; // Copy volatile sys_rt_exec_alarm. - if (rt_exec) { // Enter only if any bit flag is true + uint8_t rt_exec; // Temp variable to avoid calling volatile multiple times. + rt_exec = sys_rt_exec_alarm; // Copy volatile sys_rt_exec_alarm. + if (rt_exec) { // Enter only if any bit flag is true // System alarm. Everything has shutdown by something that has gone severely wrong. Report // the source of the error to the user. If critical, Grbl disables by entering an infinite // loop until system reset/abort. - sys.state = STATE_ALARM; // Set system alarm state + sys.state = STATE_ALARM; // Set system alarm state report_alarm_message(rt_exec); // Halt everything upon a critical event flag. Currently hard and soft limits flag this. if ((rt_exec == EXEC_ALARM_HARD_LIMIT) || (rt_exec == EXEC_ALARM_SOFT_LIMIT)) { report_feedback_message(MESSAGE_CRITICAL_EVENT); - system_clear_exec_state_flag(EXEC_RESET); // Disable any existing reset + system_clear_exec_state_flag(EXEC_RESET); // Disable any existing reset do { // Block everything, except reset and status reports, until user issues reset or power // cycles. Hard limits typically occur while unattended or not paying attention. Gives @@ -253,14 +258,14 @@ void protocol_exec_rt_system() { // lost, continued streaming could cause a serious crash if by chance it gets executed. } while (bit_isfalse(sys_rt_exec_state, EXEC_RESET)); } - system_clear_exec_alarm(); // Clear alarm + system_clear_exec_alarm(); // Clear alarm } - rt_exec = sys_rt_exec_state; // Copy volatile sys_rt_exec_state. + rt_exec = sys_rt_exec_state; // Copy volatile sys_rt_exec_state. if (rt_exec) { // Execute system abort. if (rt_exec & EXEC_RESET) { sys.abort = true; // Only place this is set true. - return; // Nothing else to do but exit. + return; // Nothing else to do but exit. } // Execute and serial print status if (rt_exec & EXEC_STATUS_REPORT) { @@ -274,28 +279,32 @@ void protocol_exec_rt_system() { if (!(sys.state & (STATE_ALARM | STATE_CHECK_MODE))) { // If in CYCLE or JOG states, immediately initiate a motion HOLD. if (sys.state & (STATE_CYCLE | STATE_JOG)) { - if (!(sys.suspend & (SUSPEND_MOTION_CANCEL | SUSPEND_JOG_CANCEL))) { // Block, if already holding. - st_update_plan_block_parameters(); // Notify stepper module to recompute for hold deceleration. - sys.step_control = STEP_CONTROL_EXECUTE_HOLD; // Initiate suspend state with active flag. - if (sys.state == STATE_JOG) { // Jog cancelled upon any hold event, except for sleeping. - if (!(rt_exec & EXEC_SLEEP)) sys.suspend |= SUSPEND_JOG_CANCEL; + if (!(sys.suspend & (SUSPEND_MOTION_CANCEL | SUSPEND_JOG_CANCEL))) { // Block, if already holding. + st_update_plan_block_parameters(); // Notify stepper module to recompute for hold deceleration. + sys.step_control = STEP_CONTROL_EXECUTE_HOLD; // Initiate suspend state with active flag. + if (sys.state == STATE_JOG) { // Jog cancelled upon any hold event, except for sleeping. + if (!(rt_exec & EXEC_SLEEP)) + sys.suspend |= SUSPEND_JOG_CANCEL; } } } // If IDLE, Grbl is not in motion. Simply indicate suspend state and hold is complete. - if (sys.state == STATE_IDLE) sys.suspend = SUSPEND_HOLD_COMPLETE; + if (sys.state == STATE_IDLE) + sys.suspend = SUSPEND_HOLD_COMPLETE; // Execute and flag a motion cancel with deceleration and return to idle. Used primarily by probing cycle // to halt and cancel the remainder of the motion. if (rt_exec & EXEC_MOTION_CANCEL) { // MOTION_CANCEL only occurs during a CYCLE, but a HOLD and SAFETY_DOOR may been initiated beforehand // to hold the CYCLE. Motion cancel is valid for a single planner block motion only, while jog cancel // will handle and clear multiple planner block motions. - if (!(sys.state & STATE_JOG)) sys.suspend |= SUSPEND_MOTION_CANCEL; // NOTE: State is STATE_CYCLE. + if (!(sys.state & STATE_JOG)) + sys.suspend |= SUSPEND_MOTION_CANCEL; // NOTE: State is STATE_CYCLE. } // Execute a feed hold with deceleration, if required. Then, suspend system. if (rt_exec & EXEC_FEED_HOLD) { // Block SAFETY_DOOR, JOG, and SLEEP states from changing to HOLD state. - if (!(sys.state & (STATE_SAFETY_DOOR | STATE_JOG | STATE_SLEEP))) sys.state = STATE_HOLD; + if (!(sys.state & (STATE_SAFETY_DOOR | STATE_JOG | STATE_SLEEP))) + sys.state = STATE_HOLD; } // Execute a safety door stop with a feed hold and disable spindle/coolant. // NOTE: Safety door differs from feed holds by stopping everything no matter state, disables powered @@ -307,20 +316,21 @@ void protocol_exec_rt_system() { // Check if the safety re-opened during a restore parking motion only. Ignore if // already retracting, parked or in sleep state. if (sys.state == STATE_SAFETY_DOOR) { - if (sys.suspend & SUSPEND_INITIATE_RESTORE) { // Actively restoring + if (sys.suspend & SUSPEND_INITIATE_RESTORE) { // Actively restoring #ifdef PARKING_ENABLE // Set hold and reset appropriate control flags to restart parking sequence. if (sys.step_control & STEP_CONTROL_EXECUTE_SYS_MOTION) { - st_update_plan_block_parameters(); // Notify stepper module to recompute for hold deceleration. + st_update_plan_block_parameters(); // Notify stepper module to recompute for hold deceleration. sys.step_control = (STEP_CONTROL_EXECUTE_HOLD | STEP_CONTROL_EXECUTE_SYS_MOTION); sys.suspend &= ~(SUSPEND_HOLD_COMPLETE); - } // else NO_MOTION is active. + } // else NO_MOTION is active. #endif sys.suspend &= ~(SUSPEND_RETRACT_COMPLETE | SUSPEND_INITIATE_RESTORE | SUSPEND_RESTORE_COMPLETE); sys.suspend |= SUSPEND_RESTART_RETRACT; } } - if (sys.state != STATE_SLEEP) sys.state = STATE_SAFETY_DOOR; + if (sys.state != STATE_SLEEP) + sys.state = STATE_SAFETY_DOOR; } // NOTE: This flag doesn't change when the door closes, unlike sys.state. Ensures any parking motions // are executed if the door switch closes and the state returns to HOLD. @@ -328,7 +338,8 @@ void protocol_exec_rt_system() { } } if (rt_exec & EXEC_SLEEP) { - if (sys.state == STATE_ALARM) sys.suspend |= (SUSPEND_RETRACT_COMPLETE | SUSPEND_HOLD_COMPLETE); + if (sys.state == STATE_ALARM) + sys.suspend |= (SUSPEND_RETRACT_COMPLETE | SUSPEND_HOLD_COMPLETE); sys.state = STATE_SLEEP; } system_clear_exec_state_flag((EXEC_MOTION_CANCEL | EXEC_FEED_HOLD | EXEC_SAFETY_DOOR | EXEC_SLEEP)); @@ -341,7 +352,7 @@ void protocol_exec_rt_system() { // Resume door state when parking motion has retracted and door has been closed. if ((sys.state == STATE_SAFETY_DOOR) && !(sys.suspend & SUSPEND_SAFETY_DOOR_AJAR)) { if (sys.suspend & SUSPEND_RESTORE_COMPLETE) { - sys.state = STATE_IDLE; // Set to IDLE to immediately resume the cycle. + sys.state = STATE_IDLE; // Set to IDLE to immediately resume the cycle. } else if (sys.suspend & SUSPEND_RETRACT_COMPLETE) { // Flag to re-energize powered components and restore original position, if disabled by SAFETY_DOOR. // NOTE: For a safety door to resume, the switch must be closed, as indicated by HOLD state, and @@ -354,18 +365,18 @@ void protocol_exec_rt_system() { // Cycle start only when IDLE or when a hold is complete and ready to resume. if ((sys.state == STATE_IDLE) || ((sys.state & STATE_HOLD) && (sys.suspend & SUSPEND_HOLD_COMPLETE))) { if (sys.state == STATE_HOLD && sys.spindle_stop_ovr) { - sys.spindle_stop_ovr |= SPINDLE_STOP_OVR_RESTORE_CYCLE; // Set to restore in suspend routine and cycle start after. + sys.spindle_stop_ovr |= SPINDLE_STOP_OVR_RESTORE_CYCLE; // Set to restore in suspend routine and cycle start after. } else { // Start cycle only if queued motions exist in planner buffer and the motion is not canceled. - sys.step_control = STEP_CONTROL_NORMAL_OP; // Restore step control to normal operation + sys.step_control = STEP_CONTROL_NORMAL_OP; // Restore step control to normal operation if (plan_get_current_block() && bit_isfalse(sys.suspend, SUSPEND_MOTION_CANCEL)) { - sys.suspend = SUSPEND_DISABLE; // Break suspend state. - sys.state = STATE_CYCLE; - st_prep_buffer(); // Initialize step segment buffer before beginning cycle. + sys.suspend = SUSPEND_DISABLE; // Break suspend state. + sys.state = STATE_CYCLE; + st_prep_buffer(); // Initialize step segment buffer before beginning cycle. st_wake_up(); - } else { // Otherwise, do nothing. Set and resume IDLE state. - sys.suspend = SUSPEND_DISABLE; // Break suspend state. - sys.state = STATE_IDLE; + } else { // Otherwise, do nothing. Set and resume IDLE state. + sys.suspend = SUSPEND_DISABLE; // Break suspend state. + sys.state = STATE_IDLE; } } } @@ -382,70 +393,84 @@ void protocol_exec_rt_system() { // Hold complete. Set to indicate ready to resume. Remain in HOLD or DOOR states until user // has issued a resume command or reset. plan_cycle_reinitialize(); - if (sys.step_control & STEP_CONTROL_EXECUTE_HOLD) sys.suspend |= SUSPEND_HOLD_COMPLETE; + if (sys.step_control & STEP_CONTROL_EXECUTE_HOLD) + sys.suspend |= SUSPEND_HOLD_COMPLETE; bit_false(sys.step_control, (STEP_CONTROL_EXECUTE_HOLD | STEP_CONTROL_EXECUTE_SYS_MOTION)); } else { // Motion complete. Includes CYCLE/JOG/HOMING states and jog cancel/motion cancel/soft limit events. // NOTE: Motion and jog cancel both immediately return to idle after the hold completes. - if (sys.suspend & SUSPEND_JOG_CANCEL) { // For jog cancel, flush buffers and sync positions. + if (sys.suspend & SUSPEND_JOG_CANCEL) { // For jog cancel, flush buffers and sync positions. sys.step_control = STEP_CONTROL_NORMAL_OP; plan_reset(); st_reset(); gc_sync_position(); plan_sync_position(); } - if (sys.suspend & SUSPEND_SAFETY_DOOR_AJAR) { // Only occurs when safety door opens during jog. + if (sys.suspend & SUSPEND_SAFETY_DOOR_AJAR) { // Only occurs when safety door opens during jog. sys.suspend &= ~(SUSPEND_JOG_CANCEL); sys.suspend |= SUSPEND_HOLD_COMPLETE; sys.state = STATE_SAFETY_DOOR; } else { sys.suspend = SUSPEND_DISABLE; - sys.state = STATE_IDLE; + sys.state = STATE_IDLE; } } system_clear_exec_state_flag(EXEC_CYCLE_STOP); } } // Execute overrides. - rt_exec = sys_rt_exec_motion_override; // Copy volatile sys_rt_exec_motion_override + rt_exec = sys_rt_exec_motion_override; // Copy volatile sys_rt_exec_motion_override if (rt_exec) { - system_clear_exec_motion_overrides(); // Clear all motion override flags. - uint8_t new_f_override = sys.f_override; - if (rt_exec & EXEC_FEED_OVR_RESET) new_f_override = DEFAULT_FEED_OVERRIDE; - if (rt_exec & EXEC_FEED_OVR_COARSE_PLUS) new_f_override += FEED_OVERRIDE_COARSE_INCREMENT; - if (rt_exec & EXEC_FEED_OVR_COARSE_MINUS) new_f_override -= FEED_OVERRIDE_COARSE_INCREMENT; - if (rt_exec & EXEC_FEED_OVR_FINE_PLUS) new_f_override += FEED_OVERRIDE_FINE_INCREMENT; - if (rt_exec & EXEC_FEED_OVR_FINE_MINUS) new_f_override -= FEED_OVERRIDE_FINE_INCREMENT; - new_f_override = MIN(new_f_override, MAX_FEED_RATE_OVERRIDE); - new_f_override = MAX(new_f_override, MIN_FEED_RATE_OVERRIDE); + system_clear_exec_motion_overrides(); // Clear all motion override flags. + uint8_t new_f_override = sys.f_override; + if (rt_exec & EXEC_FEED_OVR_RESET) + new_f_override = DEFAULT_FEED_OVERRIDE; + if (rt_exec & EXEC_FEED_OVR_COARSE_PLUS) + new_f_override += FEED_OVERRIDE_COARSE_INCREMENT; + if (rt_exec & EXEC_FEED_OVR_COARSE_MINUS) + new_f_override -= FEED_OVERRIDE_COARSE_INCREMENT; + if (rt_exec & EXEC_FEED_OVR_FINE_PLUS) + new_f_override += FEED_OVERRIDE_FINE_INCREMENT; + if (rt_exec & EXEC_FEED_OVR_FINE_MINUS) + new_f_override -= FEED_OVERRIDE_FINE_INCREMENT; + new_f_override = MIN(new_f_override, MAX_FEED_RATE_OVERRIDE); + new_f_override = MAX(new_f_override, MIN_FEED_RATE_OVERRIDE); uint8_t new_r_override = sys.r_override; - if (rt_exec & EXEC_RAPID_OVR_RESET) new_r_override = DEFAULT_RAPID_OVERRIDE; - if (rt_exec & EXEC_RAPID_OVR_MEDIUM) new_r_override = RAPID_OVERRIDE_MEDIUM; - if (rt_exec & EXEC_RAPID_OVR_LOW) new_r_override = RAPID_OVERRIDE_LOW; + if (rt_exec & EXEC_RAPID_OVR_RESET) + new_r_override = DEFAULT_RAPID_OVERRIDE; + if (rt_exec & EXEC_RAPID_OVR_MEDIUM) + new_r_override = RAPID_OVERRIDE_MEDIUM; + if (rt_exec & EXEC_RAPID_OVR_LOW) + new_r_override = RAPID_OVERRIDE_LOW; if ((new_f_override != sys.f_override) || (new_r_override != sys.r_override)) { - sys.f_override = new_f_override; - sys.r_override = new_r_override; - sys.report_ovr_counter = 0; // Set to report change immediately + sys.f_override = new_f_override; + sys.r_override = new_r_override; + sys.report_ovr_counter = 0; // Set to report change immediately plan_update_velocity_profile_parameters(); plan_cycle_reinitialize(); } } rt_exec = sys_rt_exec_accessory_override; if (rt_exec) { - system_clear_exec_accessory_overrides(); // Clear all accessory override flags. + system_clear_exec_accessory_overrides(); // Clear all accessory override flags. // NOTE: Unlike motion overrides, spindle overrides do not require a planner reinitialization. - uint8_t last_s_override = sys.spindle_speed_ovr; - if (rt_exec & EXEC_SPINDLE_OVR_RESET) last_s_override = DEFAULT_SPINDLE_SPEED_OVERRIDE; - if (rt_exec & EXEC_SPINDLE_OVR_COARSE_PLUS) last_s_override += SPINDLE_OVERRIDE_COARSE_INCREMENT; - if (rt_exec & EXEC_SPINDLE_OVR_COARSE_MINUS) last_s_override -= SPINDLE_OVERRIDE_COARSE_INCREMENT; - if (rt_exec & EXEC_SPINDLE_OVR_FINE_PLUS) last_s_override += SPINDLE_OVERRIDE_FINE_INCREMENT; - if (rt_exec & EXEC_SPINDLE_OVR_FINE_MINUS) last_s_override -= SPINDLE_OVERRIDE_FINE_INCREMENT; + uint8_t last_s_override = sys.spindle_speed_ovr; + if (rt_exec & EXEC_SPINDLE_OVR_RESET) + last_s_override = DEFAULT_SPINDLE_SPEED_OVERRIDE; + if (rt_exec & EXEC_SPINDLE_OVR_COARSE_PLUS) + last_s_override += SPINDLE_OVERRIDE_COARSE_INCREMENT; + if (rt_exec & EXEC_SPINDLE_OVR_COARSE_MINUS) + last_s_override -= SPINDLE_OVERRIDE_COARSE_INCREMENT; + if (rt_exec & EXEC_SPINDLE_OVR_FINE_PLUS) + last_s_override += SPINDLE_OVERRIDE_FINE_INCREMENT; + if (rt_exec & EXEC_SPINDLE_OVR_FINE_MINUS) + last_s_override -= SPINDLE_OVERRIDE_FINE_INCREMENT; last_s_override = MIN(last_s_override, MAX_SPINDLE_SPEED_OVERRIDE); last_s_override = MAX(last_s_override, MIN_SPINDLE_SPEED_OVERRIDE); if (last_s_override != sys.spindle_speed_ovr) { bit_true(sys.step_control, STEP_CONTROL_UPDATE_SPINDLE_RPM); - sys.spindle_speed_ovr = last_s_override; - sys.report_ovr_counter = 0; // Set to report change immediately + sys.spindle_speed_ovr = last_s_override; + sys.report_ovr_counter = 0; // Set to report change immediately // If spinlde is on, tell it the rpm has been overridden if (gc_state.modal.spindle != SPINDLE_DISABLE) spindle->set_rpm(gc_state.spindle_speed); @@ -454,8 +479,10 @@ void protocol_exec_rt_system() { // Spindle stop override allowed only while in HOLD state. // NOTE: Report counters are set in spindle_set_state() when spindle stop is executed. if (sys.state == STATE_HOLD) { - if (!(sys.spindle_stop_ovr)) sys.spindle_stop_ovr = SPINDLE_STOP_OVR_INITIATE; - else if (sys.spindle_stop_ovr & SPINDLE_STOP_OVR_ENABLED) sys.spindle_stop_ovr |= SPINDLE_STOP_OVR_RESTORE; + if (!(sys.spindle_stop_ovr)) + sys.spindle_stop_ovr = SPINDLE_STOP_OVR_INITIATE; + else if (sys.spindle_stop_ovr & SPINDLE_STOP_OVR_ENABLED) + sys.spindle_stop_ovr |= SPINDLE_STOP_OVR_RESTORE; } } // NOTE: Since coolant state always performs a planner sync whenever it changes, the current @@ -479,7 +506,7 @@ void protocol_exec_rt_system() { coolant_state |= COOLANT_MIST_ENABLE; } #endif - coolant_set_state(coolant_state); // Report counter set in coolant_set_state(). + coolant_set_state(coolant_state); // Report counter set in coolant_set_state(). gc_state.modal.coolant = coolant_state; } } @@ -495,7 +522,6 @@ void protocol_exec_rt_system() { st_prep_buffer(); } - // Handles Grbl system suspend procedures, such as feed hold, safety door, and parking motion. // The system will enter this loop, create local variables for suspend tasks, and return to // whatever function that invoked the suspend, such that Grbl resumes normal operation. @@ -504,25 +530,25 @@ void protocol_exec_rt_system() { static void protocol_exec_rt_suspend() { #ifdef PARKING_ENABLE // Declare and initialize parking local variables - float restore_target[N_AXIS]; - float parking_target[N_AXIS]; - float retract_waypoint = PARKING_PULLOUT_INCREMENT; - plan_line_data_t plan_data; + float restore_target[N_AXIS]; + float parking_target[N_AXIS]; + float retract_waypoint = PARKING_PULLOUT_INCREMENT; + plan_line_data_t plan_data; plan_line_data_t* pl_data = &plan_data; memset(pl_data, 0, sizeof(plan_line_data_t)); pl_data->condition = (PL_COND_FLAG_SYSTEM_MOTION | PL_COND_FLAG_NO_FEED_OVERRIDE); -#ifdef USE_LINE_NUMBERS +# ifdef USE_LINE_NUMBERS pl_data->line_number = PARKING_MOTION_LINE_NUMBER; -#endif +# endif #endif plan_block_t* block = plan_get_current_block(); - uint8_t restore_condition; - float restore_spindle_speed; + uint8_t restore_condition; + float restore_spindle_speed; if (block == NULL) { - restore_condition = (gc_state.modal.spindle | gc_state.modal.coolant); + restore_condition = (gc_state.modal.spindle | gc_state.modal.coolant); restore_spindle_speed = gc_state.spindle_speed; } else { - restore_condition = block->condition; + restore_condition = block->condition; restore_spindle_speed = block->spindle_speed; } #ifdef DISABLE_LASER_DURING_HOLD @@ -531,7 +557,8 @@ static void protocol_exec_rt_suspend() { #endif while (sys.suspend) { - if (sys.abort) return; + if (sys.abort) + return; // Block until initial hold is complete and the machine has stopped motion. if (sys.suspend & SUSPEND_HOLD_COMPLETE) { // Parking manager. Handles de/re-energizing, switch state checks, and parking motions for @@ -542,8 +569,8 @@ static void protocol_exec_rt_suspend() { // Ensure any prior spindle stop override is disabled at start of safety door routine. sys.spindle_stop_ovr = SPINDLE_STOP_OVR_DISABLED; #ifndef PARKING_ENABLE - spindle->set_state(SPINDLE_DISABLE, 0); // De-energize - coolant_set_state(COOLANT_DISABLE); // De-energize + spindle->set_state(SPINDLE_DISABLE, 0); // De-energize + coolant_set_state(COOLANT_DISABLE); // De-energize #else // Get current position and store restore location and spindle retract waypoint. system_convert_array_steps_to_mpos(parking_target, sys_position); @@ -555,41 +582,32 @@ static void protocol_exec_rt_suspend() { // Execute slow pull-out parking retract motion. Parking requires homing enabled, the // current location not exceeding the parking target location, and laser mode disabled. // NOTE: State is will remain DOOR, until the de-energizing and retract is complete. -#ifdef ENABLE_PARKING_OVERRIDE_CONTROL - if (homing_enable->get() && - (parking_target[PARKING_AXIS] < PARKING_TARGET) && - !laser_mode->get() && - (sys.override_ctrl == OVERRIDE_PARKING_MOTION)) { -#else - if (homing_enable->get() && - (parking_target[PARKING_AXIS] < PARKING_TARGET) && - !laser_mode->get()) { -#endif + if (can_park() && parking_target[PARKING_AXIS] < PARKING_TARGET) { // Retract spindle by pullout distance. Ensure retraction motion moves away from // the workpiece and waypoint motion doesn't exceed the parking target location. if (parking_target[PARKING_AXIS] < retract_waypoint) { parking_target[PARKING_AXIS] = retract_waypoint; - pl_data->feed_rate = PARKING_PULLOUT_RATE; - pl_data->condition |= (restore_condition & PL_COND_ACCESSORY_MASK); // Retain accessory state + pl_data->feed_rate = PARKING_PULLOUT_RATE; + pl_data->condition |= (restore_condition & PL_COND_ACCESSORY_MASK); // Retain accessory state pl_data->spindle_speed = restore_spindle_speed; mc_parking_motion(parking_target, pl_data); } // NOTE: Clear accessory state after retract and after an aborted restore motion. - pl_data->condition = (PL_COND_FLAG_SYSTEM_MOTION | PL_COND_FLAG_NO_FEED_OVERRIDE); + pl_data->condition = (PL_COND_FLAG_SYSTEM_MOTION | PL_COND_FLAG_NO_FEED_OVERRIDE); pl_data->spindle_speed = 0.0; - spindle->set_state(SPINDLE_DISABLE, 0); // De-energize - coolant_set_state(COOLANT_DISABLE); // De-energize - // Execute fast parking retract motion to parking target location. + spindle->set_state(SPINDLE_DISABLE, 0); // De-energize + coolant_set_state(COOLANT_DISABLE); // De-energize + // Execute fast parking retract motion to parking target location. if (parking_target[PARKING_AXIS] < PARKING_TARGET) { - parking_target[PARKING_AXIS] = PARKING_TARGET; - pl_data->feed_rate = PARKING_RATE; + parking_target[PARKING_AXIS] = PARKING_TARGET; + pl_data->feed_rate = PARKING_RATE; mc_parking_motion(parking_target, pl_data); } } else { // Parking motion not possible. Just disable the spindle and coolant. // NOTE: Laser mode does not start a parking motion to ensure the laser stops immediately. - spindle->set_state(SPINDLE_DISABLE, 0); // De-energize - coolant_set_state(COOLANT_DISABLE); // De-energize + spindle->set_state(SPINDLE_DISABLE, 0); // De-energize + coolant_set_state(COOLANT_DISABLE); // De-energize } #endif sys.suspend &= ~(SUSPEND_RESTART_RETRACT); @@ -598,16 +616,17 @@ static void protocol_exec_rt_suspend() { if (sys.state == STATE_SLEEP) { report_feedback_message(MESSAGE_SLEEP_MODE); // Spindle and coolant should already be stopped, but do it again just to be sure. - spindle->set_state(SPINDLE_DISABLE, 0); // De-energize - coolant_set_state(COOLANT_DISABLE); // De-energize - st_go_idle(); // Disable steppers - while (!(sys.abort)) protocol_exec_rt_system(); // Do nothing until reset. - return; // Abort received. Return to re-initialize. + spindle->set_state(SPINDLE_DISABLE, 0); // De-energize + coolant_set_state(COOLANT_DISABLE); // De-energize + st_go_idle(); // Disable steppers + while (!(sys.abort)) + protocol_exec_rt_system(); // Do nothing until reset. + return; // Abort received. Return to re-initialize. } // Allows resuming from parking/safety door. Actively checks if safety door is closed and ready to resume. if (sys.state == STATE_SAFETY_DOOR) { if (!(system_check_safety_door_ajar())) { - sys.suspend &= ~(SUSPEND_SAFETY_DOOR_AJAR); // Reset door ajar flag to denote ready to resume. + sys.suspend &= ~(SUSPEND_SAFETY_DOOR_AJAR); // Reset door ajar flag to denote ready to resume. } } // Handles parking restore and safety door resume. @@ -615,36 +634,32 @@ static void protocol_exec_rt_suspend() { #ifdef PARKING_ENABLE // Execute fast restore motion to the pull-out position. Parking requires homing enabled. // NOTE: State is will remain DOOR, until the de-energizing and retract is complete. -#ifdef ENABLE_PARKING_OVERRIDE_CONTROL - if (homing_enable->get() && !laser_mode->get()) && - (sys.override_ctrl == OVERRIDE_PARKING_MOTION)) { -#else - if (homing_enable->get() && !laser_mode->get()) { -#endif + if (can_park()) { // Check to ensure the motion doesn't move below pull-out position. if (parking_target[PARKING_AXIS] <= PARKING_TARGET) { parking_target[PARKING_AXIS] = retract_waypoint; - pl_data->feed_rate = PARKING_RATE; + pl_data->feed_rate = PARKING_RATE; mc_parking_motion(parking_target, pl_data); } } #endif // Delayed Tasks: Restart spindle and coolant, delay to power-up, then resume cycle. if (gc_state.modal.spindle != SPINDLE_DISABLE) { - // Block if safety door re-opened during prior restore actions. - if (bit_isfalse(sys.suspend, SUSPEND_RESTART_RETRACT)) { + // Block if safety door re-opened during prior restore actions. + if (bit_isfalse(sys.suspend, SUSPEND_RESTART_RETRACT)) { if (laser_mode->get()) { // When in laser mode, ignore spindle spin-up delay. Set to turn on laser when cycle starts. bit_true(sys.step_control, STEP_CONTROL_UPDATE_SPINDLE_RPM); } else { - spindle->set_state((restore_condition & (PL_COND_FLAG_SPINDLE_CW | PL_COND_FLAG_SPINDLE_CCW)), (uint32_t)restore_spindle_speed); + spindle->set_state((restore_condition & (PL_COND_FLAG_SPINDLE_CW | PL_COND_FLAG_SPINDLE_CCW)), + (uint32_t)restore_spindle_speed); delay_sec(SAFETY_DOOR_SPINDLE_DELAY, DELAY_MODE_SYS_SUSPEND); } } } if (gc_state.modal.coolant != COOLANT_DISABLE) { - // Block if safety door re-opened during prior restore actions. - if (bit_isfalse(sys.suspend, SUSPEND_RESTART_RETRACT)) { + // Block if safety door re-opened during prior restore actions. + if (bit_isfalse(sys.suspend, SUSPEND_RESTART_RETRACT)) { // NOTE: Laser mode will honor this delay. An exhaust system is often controlled by this pin. coolant_set_state((restore_condition & (PL_COND_FLAG_COOLANT_FLOOD | PL_COND_FLAG_COOLANT_FLOOD))); delay_sec(SAFETY_DOOR_COOLANT_DELAY, DELAY_MODE_SYS_SUSPEND); @@ -652,27 +667,22 @@ static void protocol_exec_rt_suspend() { } #ifdef PARKING_ENABLE // Execute slow plunge motion from pull-out position to resume position. -#ifdef ENABLE_PARKING_OVERRIDE_CONTROL - if (homing_enable->get() && !laser_mode->get()) && - (sys.override_ctrl == OVERRIDE_PARKING_MOTION)) { -#else - if (homing_enable->get() && !laser_mode->get()) { -#endif + if (can_park()) { // Block if safety door re-opened during prior restore actions. if (bit_isfalse(sys.suspend, SUSPEND_RESTART_RETRACT)) { // Regardless if the retract parking motion was a valid/safe motion or not, the // restore parking motion should logically be valid, either by returning to the // original position through valid machine space or by not moving at all. pl_data->feed_rate = PARKING_PULLOUT_RATE; - pl_data->condition |= (restore_condition & PL_COND_ACCESSORY_MASK); // Restore accessory state + pl_data->condition |= (restore_condition & PL_COND_ACCESSORY_MASK); // Restore accessory state pl_data->spindle_speed = restore_spindle_speed; mc_parking_motion(restore_target, pl_data); } } #endif if (bit_isfalse(sys.suspend, SUSPEND_RESTART_RETRACT)) { - sys.suspend |= SUSPEND_RESTORE_COMPLETE; - system_set_exec_state_flag(EXEC_CYCLE_START); // Set to resume program. + sys.suspend |= SUSPEND_RESTORE_COMPLETE; + system_set_exec_state_flag(EXEC_CYCLE_START); // Set to resume program. } } } @@ -683,10 +693,10 @@ static void protocol_exec_rt_suspend() { // Handles beginning of spindle stop if (sys.spindle_stop_ovr & SPINDLE_STOP_OVR_INITIATE) { if (gc_state.modal.spindle != SPINDLE_DISABLE) { - spindle->set_state(SPINDLE_DISABLE, 0); // De-energize - sys.spindle_stop_ovr = SPINDLE_STOP_OVR_ENABLED; // Set stop override state to enabled, if de-energized. + spindle->set_state(SPINDLE_DISABLE, 0); // De-energize + sys.spindle_stop_ovr = SPINDLE_STOP_OVR_ENABLED; // Set stop override state to enabled, if de-energized. } else { - sys.spindle_stop_ovr = SPINDLE_STOP_OVR_DISABLED; // Clear stop override state + sys.spindle_stop_ovr = SPINDLE_STOP_OVR_DISABLED; // Clear stop override state } // Handles restoring of spindle state } else if (sys.spindle_stop_ovr & (SPINDLE_STOP_OVR_RESTORE | SPINDLE_STOP_OVR_RESTORE_CYCLE)) { @@ -696,18 +706,20 @@ static void protocol_exec_rt_suspend() { // When in laser mode, ignore spindle spin-up delay. Set to turn on laser when cycle starts. bit_true(sys.step_control, STEP_CONTROL_UPDATE_SPINDLE_RPM); } else - spindle->set_state((restore_condition & (PL_COND_FLAG_SPINDLE_CW | PL_COND_FLAG_SPINDLE_CCW)), (uint32_t)restore_spindle_speed); + spindle->set_state((restore_condition & (PL_COND_FLAG_SPINDLE_CW | PL_COND_FLAG_SPINDLE_CCW)), + (uint32_t)restore_spindle_speed); } if (sys.spindle_stop_ovr & SPINDLE_STOP_OVR_RESTORE_CYCLE) { system_set_exec_state_flag(EXEC_CYCLE_START); // Set to resume program. } - sys.spindle_stop_ovr = SPINDLE_STOP_OVR_DISABLED; // Clear stop override state + sys.spindle_stop_ovr = SPINDLE_STOP_OVR_DISABLED; // Clear stop override state } } else { // Handles spindle state during hold. NOTE: Spindle speed overrides may be altered during hold state. // NOTE: STEP_CONTROL_UPDATE_SPINDLE_RPM is automatically reset upon resume in step generator. if (bit_istrue(sys.step_control, STEP_CONTROL_UPDATE_SPINDLE_RPM)) { - spindle->set_state((restore_condition & (PL_COND_FLAG_SPINDLE_CW | PL_COND_FLAG_SPINDLE_CCW)), (uint32_t)restore_spindle_speed); + spindle->set_state((restore_condition & (PL_COND_FLAG_SPINDLE_CW | PL_COND_FLAG_SPINDLE_CCW)), + (uint32_t)restore_spindle_speed); bit_false(sys.step_control, STEP_CONTROL_UPDATE_SPINDLE_RPM); } } @@ -716,4 +728,3 @@ static void protocol_exec_rt_suspend() { protocol_exec_rt_system(); } } - diff --git a/Grbl_Esp32/protocol.h b/Grbl_Esp32/src/Protocol.h similarity index 93% rename from Grbl_Esp32/protocol.h rename to Grbl_Esp32/src/Protocol.h index 94156506..6b91553d 100644 --- a/Grbl_Esp32/protocol.h +++ b/Grbl_Esp32/src/Protocol.h @@ -1,5 +1,7 @@ +#pragma once + /* - protocol.h - controls Grbl execution protocol and procedures + Protocol.h - controls Grbl execution protocol and procedures Part of Grbl Copyright (c) 2011-2016 Sungeun K. Jeon for Gnea Research LLC @@ -22,9 +24,6 @@ along with Grbl. If not, see . */ -#ifndef protocol_h -#define protocol_h - // Line buffer size from the serial input stream to be executed. // NOTE: Not a problem except for extreme cases, but the line buffer size can be too small // and g-code blocks can get truncated. Officially, the g-code standards support up to 256 @@ -32,7 +31,7 @@ // memory space we can invest into here or we re-write the g-code parser not to have this // buffer. #ifndef LINE_BUFFER_SIZE - #define LINE_BUFFER_SIZE 80 +# define LINE_BUFFER_SIZE 80 #endif // Starts Grbl main loop. It handles all incoming characters from the serial port and executes @@ -51,6 +50,3 @@ void protocol_buffer_synchronize(); // Executes the auto cycle feature, if enabled. void protocol_auto_cycle_start(); - -#endif - diff --git a/Grbl_Esp32/report.cpp b/Grbl_Esp32/src/Report.cpp similarity index 62% rename from Grbl_Esp32/report.cpp rename to Grbl_Esp32/src/Report.cpp index a9b8e697..19c98eda 100644 --- a/Grbl_Esp32/report.cpp +++ b/Grbl_Esp32/src/Report.cpp @@ -1,5 +1,5 @@ /* - report.c - reporting and messaging methods + Report.cpp - reporting and messaging methods Part of Grbl Copyright (c) 2012-2016 Sungeun K. Jeon for Gnea Research LLC @@ -24,7 +24,7 @@ /* This file functions as the primary feedback interface for Grbl. Any outgoing data, such as the protocol status messages, feedback messages, and status reports, are stored here. - For the most part, these functions primarily are called from protocol.c methods. If a + For the most part, these functions primarily are called from Protocol.cpp methods. If a different style feedback is desired (i.e. JSON), then a user can change these following methods to accommodate their needs. @@ -46,7 +46,7 @@ */ -#include "grbl.h" +#include "Grbl.h" #ifdef REPORT_HEAP EspClass esp; #endif @@ -54,18 +54,19 @@ EspClass esp; // 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; + if (client == CLIENT_INPUT) + return; #ifdef ENABLE_BLUETOOTH if (SerialBT.hasClient() && (client == CLIENT_BT || client == CLIENT_ALL)) { SerialBT.print(text); //delay(10); // possible fix for dropped characters } #endif -#if defined (ENABLE_WIFI) && defined(ENABLE_HTTP) && defined(ENABLE_SERIAL2SOCKET_OUT) +#if defined(ENABLE_WIFI) && defined(ENABLE_HTTP) && defined(ENABLE_SERIAL2SOCKET_OUT) if (client == CLIENT_WEBUI || client == CLIENT_ALL) Serial2Socket.write((const uint8_t*)text, strlen(text)); #endif -#if defined (ENABLE_WIFI) && defined(ENABLE_TELNET) +#if defined(ENABLE_WIFI) && defined(ENABLE_TELNET) if (client == CLIENT_TELNET || client == CLIENT_ALL) telnet_server.write((const uint8_t*)text, strlen(text)); #endif @@ -75,9 +76,10 @@ void grbl_send(uint8_t client, const char* text) { // This is a formating version of the grbl_send(CLIENT_ALL,...) function that work like printf void grbl_sendf(uint8_t client, const char* format, ...) { - if (client == CLIENT_INPUT) return; - char loc_buf[64]; - char* temp = loc_buf; + if (client == CLIENT_INPUT) + return; + char loc_buf[64]; + char* temp = loc_buf; va_list arg; va_list copy; va_start(arg, format); @@ -97,10 +99,12 @@ void grbl_sendf(uint8_t client, const char* format, ...) { } // Use to send [MSG:xxxx] Type messages. The level allows messages to be easily suppressed void grbl_msg_sendf(uint8_t client, uint8_t level, const char* format, ...) { - if (client == CLIENT_INPUT) return; - if (level > GRBL_MSG_LEVEL) return; - char loc_buf[100]; - char* temp = loc_buf; + if (client == CLIENT_INPUT) + return; + if (level > GRBL_MSG_LEVEL) + return; + char loc_buf[100]; + char* temp = loc_buf; va_list arg; va_list copy; va_start(arg, format); @@ -127,8 +131,8 @@ void grbl_notify(const char* title, const char* msg) { } void grbl_notifyf(const char* title, const char* format, ...) { - char loc_buf[64]; - char* temp = loc_buf; + char loc_buf[64]; + char* temp = loc_buf; va_list arg; va_list copy; va_start(arg, format); @@ -150,9 +154,9 @@ void grbl_notifyf(const char* title, const char* format, ...) { // formats axis values into a string and returns that string in rpt static void report_util_axis_values(float* axis_value, char* rpt) { uint8_t idx; - char axisVal[20]; - float unit_conv = 1.0; // unit conversion multiplier..default is mm - rpt[0] = '\0'; + char axisVal[20]; + float unit_conv = 1.0; // unit conversion multiplier..default is mm + rpt[0] = '\0'; if (report_inches->get()) unit_conv = 1.0 / MM_PER_INCH; for (idx = 0; idx < N_AXIS; idx++) { @@ -169,19 +173,20 @@ static void report_util_axis_values(float* axis_value, char* rpt) { void get_state(char* foo) { // pad them to same length switch (sys.state) { - case STATE_IDLE: strcpy(foo, " Idle ");; break; - case STATE_CYCLE: strcpy(foo, " Run "); break; - case STATE_HOLD: strcpy(foo, " Hold "); break; - case STATE_HOMING: strcpy(foo, " Home "); break; - case STATE_ALARM: strcpy(foo, " Alarm"); break; - case STATE_CHECK_MODE: strcpy(foo, " Check"); break; - case STATE_SAFETY_DOOR: strcpy(foo, " Door "); break; - default: strcpy(foo, " ? "); break; + case STATE_IDLE: + strcpy(foo, " Idle "); + ; + break; + case STATE_CYCLE: strcpy(foo, " Run "); break; + case STATE_HOLD: strcpy(foo, " Hold "); break; + case STATE_HOMING: strcpy(foo, " Home "); break; + case STATE_ALARM: strcpy(foo, " Alarm"); break; + case STATE_CHECK_MODE: strcpy(foo, " Check"); break; + case STATE_SAFETY_DOOR: strcpy(foo, " Door "); break; + default: strcpy(foo, " ? "); break; } } - - // Handles the primary confirmation protocol response for streaming interfaces and human-feedback. // For every incoming line, this method responds with an 'ok' for a successful command or an // 'error:' to indicate some error event with the line or some critical system error during @@ -190,42 +195,40 @@ void get_state(char* foo) { // responses. void report_status_message(uint8_t status_code, uint8_t client) { switch (status_code) { - case STATUS_OK: // STATUS_OK + case STATUS_OK: // STATUS_OK #ifdef ENABLE_SD_CARD - if (get_sd_state(false) == SDCARD_BUSY_PRINTING) - SD_ready_next = true; // flag so system_execute_line() will send the next line - else - grbl_send(client, "ok\r\n"); + if (get_sd_state(false) == SDCARD_BUSY_PRINTING) + SD_ready_next = true; // flag so system_execute_line() will send the next line + else + grbl_send(client, "ok\r\n"); #else - grbl_send(client, "ok\r\n"); + grbl_send(client, "ok\r\n"); #endif - break; - default: + break; + default: #ifdef ENABLE_SD_CARD - // do we need to stop a running SD job? - if (get_sd_state(false) == SDCARD_BUSY_PRINTING) { - if (status_code == STATUS_GCODE_UNSUPPORTED_COMMAND) { - grbl_sendf(client, "error:%d\r\n", status_code); // most senders seem to tolerate this error and keep on going - grbl_sendf(CLIENT_ALL, "error:%d in SD file at line %d\r\n", status_code, sd_get_current_line_number()); - // don't close file - } else { - grbl_notifyf("SD print error", "Error:%d during SD file at line: %d", status_code, sd_get_current_line_number()); - grbl_sendf(CLIENT_ALL, "error:%d in SD file at line %d\r\n", status_code, sd_get_current_line_number()); - closeFile(); + // do we need to stop a running SD job? + if (get_sd_state(false) == SDCARD_BUSY_PRINTING) { + if (status_code == STATUS_GCODE_UNSUPPORTED_COMMAND) { + grbl_sendf(client, "error:%d\r\n", status_code); // most senders seem to tolerate this error and keep on going + grbl_sendf(CLIENT_ALL, "error:%d in SD file at line %d\r\n", status_code, sd_get_current_line_number()); + // don't close file + } else { + grbl_notifyf("SD print error", "Error:%d during SD file at line: %d", status_code, sd_get_current_line_number()); + grbl_sendf(CLIENT_ALL, "error:%d in SD file at line %d\r\n", status_code, sd_get_current_line_number()); + closeFile(); + } + return; } - return; - } #endif - grbl_sendf(client, "error:%d\r\n", status_code); + grbl_sendf(client, "error:%d\r\n", status_code); } } - - // Prints alarm messages. void report_alarm_message(uint8_t alarm_code) { - grbl_sendf(CLIENT_ALL, "ALARM:%d\r\n", alarm_code); // OK to send to all clients - delay_ms(500); // Force delay to ensure message clears serial write buffer. + grbl_sendf(CLIENT_ALL, "ALARM:%d\r\n", alarm_code); // OK to send to all clients + delay_ms(500); // Force delay to ensure message clears serial write buffer. } // Prints feedback messages. This serves as a centralized method to provide additional @@ -233,40 +236,31 @@ void report_alarm_message(uint8_t alarm_code) { // messages such as setup warnings, switch toggling, and how to exit alarms. // 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(uint8_t message_code) { // OK to send to all clients +void report_feedback_message(uint8_t message_code) { // OK to send to all clients switch (message_code) { - case MESSAGE_CRITICAL_EVENT: - grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Reset to continue"); break; - case MESSAGE_ALARM_LOCK: - grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "'$H'|'$X' to unlock"); break; - case MESSAGE_ALARM_UNLOCK: - grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Caution: Unlocked"); break; - case MESSAGE_ENABLED: - grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Enabled"); break; - case MESSAGE_DISABLED: - grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Disabled"); break; - case MESSAGE_SAFETY_DOOR_AJAR: - grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Check door"); break; - case MESSAGE_CHECK_LIMITS: - grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Check limits"); break; - case MESSAGE_PROGRAM_END: - grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Program End"); break; - case MESSAGE_RESTORE_DEFAULTS: - grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Restoring defaults"); break; - case MESSAGE_SPINDLE_RESTORE: - grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Restoring spindle");; break; - case MESSAGE_SLEEP_MODE: - grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Sleeping"); break; + case MESSAGE_CRITICAL_EVENT: grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Reset to continue"); break; + case MESSAGE_ALARM_LOCK: grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "'$H'|'$X' to unlock"); break; + case MESSAGE_ALARM_UNLOCK: grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Caution: Unlocked"); break; + case MESSAGE_ENABLED: grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Enabled"); break; + case MESSAGE_DISABLED: grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Disabled"); break; + case MESSAGE_SAFETY_DOOR_AJAR: grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Check door"); break; + case MESSAGE_CHECK_LIMITS: grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Check limits"); break; + case MESSAGE_PROGRAM_END: grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Program End"); break; + case MESSAGE_RESTORE_DEFAULTS: grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Restoring defaults"); break; + case MESSAGE_SPINDLE_RESTORE: + grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Restoring spindle"); + ; + break; + case MESSAGE_SLEEP_MODE: grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Sleeping"); break; #ifdef ENABLE_SD_CARD - case MESSAGE_SD_FILE_QUIT: - grbl_notifyf("SD print canceled", "Reset during SD file at line: %d", sd_get_current_line_number()); - grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Reset during SD file at line: %d", sd_get_current_line_number()); - break; + case MESSAGE_SD_FILE_QUIT: + grbl_notifyf("SD print canceled", "Reset during SD file at line: %d", sd_get_current_line_number()); + grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Reset during SD file at line: %d", sd_get_current_line_number()); + break; #endif } } - // Welcome message void report_init_message(uint8_t client) { grbl_send(client, "\r\nGrbl " GRBL_VERSION " ['$' for help]\r\n"); @@ -277,17 +271,15 @@ void report_grbl_help(uint8_t client) { grbl_send(client, "[HLP:$$ $+ $# $S $L $G $I $N $x=val $Nx=line $J=line $SLP $C $X $H $F $E=err ~ ! ? ctrl-x]\r\n"); } - - // Prints current probe parameters. Upon a probe command, these parameters are updated upon a // successful probe or upon a failed probe with the G38.3 without errors command (if supported). // These values are retained until Grbl is power-cycled, whereby they will be re-zeroed. void report_probe_parameters(uint8_t client) { // Report in terms of machine position. float print_position[N_AXIS]; - char probe_rpt[100]; // the probe report we are building here - char temp[60]; - strcpy(probe_rpt, "[PRB:"); // initialize the string with the first characters + char probe_rpt[100]; // the probe report we are building here + char temp[60]; + strcpy(probe_rpt, "[PRB:"); // initialize the string with the first characters // get the machine position and put them into a string and append to the probe report system_convert_array_steps_to_mpos(print_position, sys_probe_position); report_util_axis_values(print_position, temp); @@ -295,18 +287,15 @@ void report_probe_parameters(uint8_t client) { // add the success indicator and add closing characters sprintf(temp, ":%d]\r\n", sys.probe_succeeded); strcat(probe_rpt, temp); - grbl_send(client, probe_rpt); // send the report + grbl_send(client, probe_rpt); // send the report } - - - // Prints Grbl NGC parameters (coordinate offsets, probing) void report_ngc_parameters(uint8_t client) { - float coord_data[N_AXIS]; + float coord_data[N_AXIS]; uint8_t coord_select; - char temp[60]; - char ngc_rpt[500]; + char temp[60]; + char ngc_rpt[500]; ngc_rpt[0] = '\0'; for (coord_select = 0; coord_select <= SETTING_INDEX_NCOORD; coord_select++) { if (!(settings_read_coord_data(coord_select, coord_data))) { @@ -315,23 +304,23 @@ void report_ngc_parameters(uint8_t client) { } strcat(ngc_rpt, "[G"); switch (coord_select) { - case 6: strcat(ngc_rpt, "28"); break; - case 7: strcat(ngc_rpt, "30"); break; - default: - sprintf(temp, "%d", coord_select + 54); - strcat(ngc_rpt, temp); - break; // G54-G59 + case 6: strcat(ngc_rpt, "28"); break; + case 7: strcat(ngc_rpt, "30"); break; + default: + sprintf(temp, "%d", coord_select + 54); + strcat(ngc_rpt, temp); + break; // G54-G59 } strcat(ngc_rpt, ":"); report_util_axis_values(coord_data, temp); strcat(ngc_rpt, temp); strcat(ngc_rpt, "]\r\n"); } - strcat(ngc_rpt, "[G92:"); // Print G92,G92.1 which are not persistent in memory + strcat(ngc_rpt, "[G92:"); // Print G92,G92.1 which are not persistent in memory report_util_axis_values(gc_state.coord_offset, temp); strcat(ngc_rpt, temp); strcat(ngc_rpt, "]\r\n"); - strcat(ngc_rpt, "[TLO:"); // Print tool length offset value + strcat(ngc_rpt, "[TLO:"); // Print tool length offset value if (report_inches->get()) sprintf(temp, "%4.3f]\r\n", gc_state.tool_length_offset * INCH_PER_MM); else @@ -341,8 +330,6 @@ void report_ngc_parameters(uint8_t client) { report_probe_parameters(client); } - - // Print current gcode parser mode state void report_gcode_modes(uint8_t client) { char temp[20]; @@ -366,24 +353,27 @@ void report_gcode_modes(uint8_t client) { if (gc_state.modal.program_flow) { //report_util_gcode_modes_M(); switch (gc_state.modal.program_flow) { - case PROGRAM_FLOW_PAUSED : strcat(modes_rpt, " M0"); //serial_write('0'); break; - // case PROGRAM_FLOW_OPTIONAL_STOP : serial_write('1'); break; // M1 is ignored and not supported. - case PROGRAM_FLOW_COMPLETED_M2 : - case PROGRAM_FLOW_COMPLETED_M30 : - sprintf(temp, " M%d", gc_state.modal.program_flow); - strcat(modes_rpt, temp); - break; + case PROGRAM_FLOW_PAUSED: + strcat(modes_rpt, " M0"); //serial_write('0'); break; + // case PROGRAM_FLOW_OPTIONAL_STOP : serial_write('1'); break; // M1 is ignored and not supported. + case PROGRAM_FLOW_COMPLETED_M2: + case PROGRAM_FLOW_COMPLETED_M30: + sprintf(temp, " M%d", gc_state.modal.program_flow); + strcat(modes_rpt, temp); + break; } } switch (gc_state.modal.spindle) { - case SPINDLE_ENABLE_CW : strcat(modes_rpt, " M3"); break; - case SPINDLE_ENABLE_CCW : strcat(modes_rpt, " M4"); break; - case SPINDLE_DISABLE : strcat(modes_rpt, " M5"); break; + case SPINDLE_ENABLE_CW: strcat(modes_rpt, " M3"); break; + case SPINDLE_ENABLE_CCW: strcat(modes_rpt, " M4"); break; + case SPINDLE_DISABLE: strcat(modes_rpt, " M5"); break; } //report_util_gcode_modes_M(); // optional M7 and M8 should have been dealt with by here - if (gc_state.modal.coolant) { // Note: Multiple coolant states may be active at the same time. - if (gc_state.modal.coolant & PL_COND_FLAG_COOLANT_MIST) strcat(modes_rpt, " M7"); - if (gc_state.modal.coolant & PL_COND_FLAG_COOLANT_FLOOD) strcat(modes_rpt, " M8"); + if (gc_state.modal.coolant) { // Note: Multiple coolant states may be active at the same time. + if (gc_state.modal.coolant & PL_COND_FLAG_COOLANT_MIST) + strcat(modes_rpt, " M7"); + if (gc_state.modal.coolant & PL_COND_FLAG_COOLANT_FLOOD) + strcat(modes_rpt, " M8"); } else strcat(modes_rpt, " M9"); @@ -402,15 +392,13 @@ void report_gcode_modes(uint8_t client) { grbl_send(client, modes_rpt); } - - // Prints specified startup line void report_startup_line(uint8_t n, const char* line, uint8_t client) { - grbl_sendf(client, "$N%d=%s\r\n", n, line); // OK to send to all + grbl_sendf(client, "$N%d=%s\r\n", n, line); // OK to send to all } void report_execute_startup_message(const char* line, uint8_t status_code, uint8_t client) { - grbl_sendf(client, ">%s:", line); // OK to send to all + grbl_sendf(client, ">%s:", line); // OK to send to all report_status_message(status_code, client); } @@ -420,10 +408,10 @@ void report_build_info(char* line, uint8_t client) { strcpy(build_info, "[VER:" GRBL_VERSION "." GRBL_VERSION_BUILD ":"); strcat(build_info, line); strcat(build_info, "]\r\n[OPT:"); - strcat(build_info, "V"); // variable spindle..always on now + strcat(build_info, "V"); // variable spindle..always on now strcat(build_info, "N"); #ifdef COOLANT_MIST_PIN - strcat(build_info, "M"); // TODO Need to deal with M8...it could be disabled + strcat(build_info, "M"); // TODO Need to deal with M8...it could be disabled #endif #ifdef COREXY strcat(build_info, "C"); @@ -432,7 +420,7 @@ void report_build_info(char* line, uint8_t client) { strcat(build_info, "P"); #endif #if (defined(HOMING_FORCE_SET_ORIGIN) || defined(HOMING_FORCE_POSITIVE_SPACE)) - strcat(build_info, "Z"); // homing MPOS bahavior is not the default behavior + strcat(build_info, "Z"); // homing MPOS bahavior is not the default behavior #endif #ifdef HOMING_SINGLE_AXIS_COMMANDS strcat(build_info, "H"); @@ -452,36 +440,36 @@ void report_build_info(char* line, uint8_t client) { #ifdef ENABLE_PARKING_OVERRIDE_CONTROL serial_write('R'); #endif -#if defined (ENABLE_WIFI) +#if defined(ENABLE_WIFI) strcat(build_info, "W"); #endif -#ifndef ENABLE_RESTORE_EEPROM_WIPE_ALL // NOTE: Shown when disabled. +#ifndef ENABLE_RESTORE_EEPROM_WIPE_ALL // NOTE: Shown when disabled. strcat(build_info, "*"); #endif -#ifndef ENABLE_RESTORE_EEPROM_DEFAULT_SETTINGS // NOTE: Shown when disabled. +#ifndef ENABLE_RESTORE_EEPROM_DEFAULT_SETTINGS // NOTE: Shown when disabled. strcat(build_info, "$"); #endif -#ifndef ENABLE_RESTORE_EEPROM_CLEAR_PARAMETERS // NOTE: Shown when disabled. +#ifndef ENABLE_RESTORE_EEPROM_CLEAR_PARAMETERS // NOTE: Shown when disabled. strcat(build_info, "#"); #endif -#ifndef ENABLE_BUILD_INFO_WRITE_COMMAND // NOTE: Shown when disabled. +#ifndef ENABLE_BUILD_INFO_WRITE_COMMAND // NOTE: Shown when disabled. strcat(build_info, "I"); #endif -#ifndef FORCE_BUFFER_SYNC_DURING_EEPROM_WRITE // NOTE: Shown when disabled. +#ifndef FORCE_BUFFER_SYNC_DURING_EEPROM_WRITE // NOTE: Shown when disabled. strcat(build_info, "E"); #endif -#ifndef FORCE_BUFFER_SYNC_DURING_WCO_CHANGE // NOTE: Shown when disabled. +#ifndef FORCE_BUFFER_SYNC_DURING_WCO_CHANGE // NOTE: Shown when disabled. strcat(build_info, "W"); #endif // NOTE: Compiled values, like override increments/max/min values, may be added at some point later. // These will likely have a comma delimiter to separate them. strcat(build_info, "]\r\n"); - grbl_send(client, build_info); // ok to send to all + grbl_send(client, build_info); // ok to send to all report_machine_type(client); -#if defined (ENABLE_WIFI) +#if defined(ENABLE_WIFI) grbl_send(client, (char*)wifi_config.info()); #endif -#if defined (ENABLE_BLUETOOTH) +#if defined(ENABLE_BLUETOOTH) grbl_send(client, (char*)bt_config.info()); #endif } @@ -499,53 +487,55 @@ void report_echo_line_received(char* line, uint8_t client) { // 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[N_AXIS]; // Copy current state of the system position variable + int32_t current_position[N_AXIS]; // Copy current state of the system position variable memcpy(current_position, sys_position, sizeof(sys_position)); float print_position[N_AXIS]; - char status[200]; - char temp[80]; + char status[200]; + char temp[80]; 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 & SUSPEND_JOG_CANCEL)) { - strcat(status, "Hold:"); - if (sys.suspend & SUSPEND_HOLD_COMPLETE) strcat(status, "0"); // Ready to resume - else strcat(status, "1"); // Actively holding - 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_CHECK_MODE: strcat(status, "Check"); break; - case STATE_SAFETY_DOOR: - strcat(status, "Door:"); - if (sys.suspend & SUSPEND_INITIATE_RESTORE) { - strcat(status, "3"); // Restoring - } else { - if (sys.suspend & SUSPEND_RETRACT_COMPLETE) { - if (sys.suspend & SUSPEND_SAFETY_DOOR_AJAR) { - strcat(status, "1"); // Door ajar - } else - strcat(status, "0"); - // Door closed and ready to resume + case STATE_IDLE: strcat(status, "Idle"); break; + case STATE_CYCLE: strcat(status, "Run"); break; + case STATE_HOLD: + if (!(sys.suspend & SUSPEND_JOG_CANCEL)) { + strcat(status, "Hold:"); + if (sys.suspend & SUSPEND_HOLD_COMPLETE) + strcat(status, "0"); // Ready to resume + else + strcat(status, "1"); // Actively holding + 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_CHECK_MODE: strcat(status, "Check"); break; + case STATE_SAFETY_DOOR: + strcat(status, "Door:"); + if (sys.suspend & SUSPEND_INITIATE_RESTORE) { + strcat(status, "3"); // Restoring } else { - strcat(status, "2"); // Retracting + if (sys.suspend & SUSPEND_RETRACT_COMPLETE) { + if (sys.suspend & SUSPEND_SAFETY_DOOR_AJAR) { + strcat(status, "1"); // Door ajar + } else + strcat(status, "0"); + // Door closed and ready to resume + } else { + strcat(status, "2"); // Retracting + } } - } - break; - case STATE_SLEEP: strcat(status, "Sleep"); break; + break; + case STATE_SLEEP: strcat(status, "Sleep"); break; } float wco[N_AXIS]; - if (bit_isfalse(status_mask->get(), BITFLAG_RT_STATUS_POSITION_TYPE) || - (sys.report_wco_counter == 0)) { + if (bit_isfalse(status_mask->get(), BITFLAG_RT_STATUS_POSITION_TYPE) || (sys.report_wco_counter == 0)) { 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 (idx == TOOL_LENGTH_OFFSET_AXIS) + wco[idx] += gc_state.tool_length_offset; if (bit_isfalse(status_mask->get(), BITFLAG_RT_STATUS_POSITION_TYPE)) print_position[idx] -= wco[idx]; } @@ -565,16 +555,16 @@ void report_realtime_status(uint8_t client) { #ifdef REPORT_FIELD_BUFFER_STATE if (bit_istrue(status_mask->get(), BITFLAG_RT_STATUS_BUFFER_STATE)) { int bufsize = DEFAULTBUFFERSIZE; -#if defined (ENABLE_WIFI) && defined(ENABLE_TELNET) +# if defined(ENABLE_WIFI) && defined(ENABLE_TELNET) if (client == CLIENT_TELNET) bufsize = telnet_server.get_rx_buffer_available(); -#endif //ENABLE_WIFI && ENABLE_TELNET -#if defined(ENABLE_BLUETOOTH) +# endif //ENABLE_WIFI && ENABLE_TELNET +# if defined(ENABLE_BLUETOOTH) if (client == CLIENT_BT) { //TODO FIXME bufsize = 512 - SerialBT.available(); } -#endif //ENABLE_BLUETOOTH +# endif //ENABLE_BLUETOOTH if (client == CLIENT_SERIAL) bufsize = serial_get_rx_buffer_available(CLIENT_SERIAL); sprintf(temp, "|Bf:%d,%d", plan_get_block_buffer_available(), bufsize); @@ -582,7 +572,7 @@ void report_realtime_status(uint8_t client) { } #endif #ifdef USE_LINE_NUMBERS -#ifdef REPORT_FIELD_LINE_NUMBERS +# ifdef REPORT_FIELD_LINE_NUMBERS // Report current line number plan_block_t* cur_block = plan_get_current_block(); if (cur_block != NULL) { @@ -592,79 +582,99 @@ void report_realtime_status(uint8_t client) { strcat(status, temp); } } -#endif +# endif #endif // Report realtime feed speed #ifdef REPORT_FIELD_CURRENT_FEED_SPEED if (report_inches->get()) - sprintf(temp, "|FS:%.1f,%d", st_get_realtime_rate()/ MM_PER_INCH, sys.spindle_speed); + sprintf(temp, "|FS:%.1f,%d", st_get_realtime_rate() / MM_PER_INCH, sys.spindle_speed); else sprintf(temp, "|FS:%.0f,%d", st_get_realtime_rate(), sys.spindle_speed); strcat(status, temp); #endif #ifdef REPORT_FIELD_PIN_STATE - uint8_t lim_pin_state = limits_get_state(); + uint8_t lim_pin_state = limits_get_state(); uint8_t ctrl_pin_state = system_control_get_state(); - uint8_t prb_pin_state = probe_get_state(); + uint8_t prb_pin_state = probe_get_state(); if (lim_pin_state | ctrl_pin_state | prb_pin_state) { strcat(status, "|Pn:"); - if (prb_pin_state) strcat(status, "P"); + if (prb_pin_state) + strcat(status, "P"); if (lim_pin_state) { - if (bit_istrue(lim_pin_state, bit(X_AXIS))) strcat(status, "X"); - if (bit_istrue(lim_pin_state, bit(Y_AXIS))) strcat(status, "Y"); - if (bit_istrue(lim_pin_state, bit(Z_AXIS))) strcat(status, "Z"); -#if (N_AXIS > A_AXIS) - if (bit_istrue(lim_pin_state, bit(A_AXIS))) strcat(status, "A"); -#endif -#if (N_AXIS > B_AXIS) - if (bit_istrue(lim_pin_state, bit(B_AXIS))) strcat(status, "B"); -#endif -#if (N_AXIS > C_AXIS) - if (bit_istrue(lim_pin_state, bit(C_AXIS))) strcat(status, "C"); -#endif + if (bit_istrue(lim_pin_state, bit(X_AXIS))) + strcat(status, "X"); + if (bit_istrue(lim_pin_state, bit(Y_AXIS))) + strcat(status, "Y"); + if (bit_istrue(lim_pin_state, bit(Z_AXIS))) + strcat(status, "Z"); +# if (N_AXIS > A_AXIS) + if (bit_istrue(lim_pin_state, bit(A_AXIS))) + strcat(status, "A"); +# endif +# if (N_AXIS > B_AXIS) + if (bit_istrue(lim_pin_state, bit(B_AXIS))) + strcat(status, "B"); +# endif +# if (N_AXIS > C_AXIS) + if (bit_istrue(lim_pin_state, bit(C_AXIS))) + strcat(status, "C"); +# endif } if (ctrl_pin_state) { -#ifdef ENABLE_SAFETY_DOOR_INPUT_PIN - if (bit_istrue(ctrl_pin_state, CONTROL_PIN_INDEX_SAFETY_DOOR)) strcat(status, "D"); -#endif - if (bit_istrue(ctrl_pin_state, CONTROL_PIN_INDEX_RESET)) strcat(status, "R"); - if (bit_istrue(ctrl_pin_state, CONTROL_PIN_INDEX_FEED_HOLD)) strcat(status, "H"); - if (bit_istrue(ctrl_pin_state, CONTROL_PIN_INDEX_CYCLE_START)) strcat(status, "S"); +# ifdef ENABLE_SAFETY_DOOR_INPUT_PIN + if (bit_istrue(ctrl_pin_state, CONTROL_PIN_INDEX_SAFETY_DOOR)) + strcat(status, "D"); +# endif + if (bit_istrue(ctrl_pin_state, CONTROL_PIN_INDEX_RESET)) + strcat(status, "R"); + if (bit_istrue(ctrl_pin_state, CONTROL_PIN_INDEX_FEED_HOLD)) + strcat(status, "H"); + if (bit_istrue(ctrl_pin_state, CONTROL_PIN_INDEX_CYCLE_START)) + strcat(status, "S"); } } #endif #ifdef REPORT_FIELD_WORK_COORD_OFFSET - if (sys.report_wco_counter > 0) sys.report_wco_counter--; + if (sys.report_wco_counter > 0) + sys.report_wco_counter--; else { if (sys.state & (STATE_HOMING | STATE_CYCLE | STATE_HOLD | STATE_JOG | STATE_SAFETY_DOOR)) { - sys.report_wco_counter = (REPORT_WCO_REFRESH_BUSY_COUNT - 1); // Reset counter for slow refresh - } else sys.report_wco_counter = (REPORT_WCO_REFRESH_IDLE_COUNT - 1); - if (sys.report_ovr_counter == 0) sys.report_ovr_counter = 1; // Set override on next report. + sys.report_wco_counter = (REPORT_WCO_REFRESH_BUSY_COUNT - 1); // Reset counter for slow refresh + } else + sys.report_wco_counter = (REPORT_WCO_REFRESH_IDLE_COUNT - 1); + if (sys.report_ovr_counter == 0) + sys.report_ovr_counter = 1; // Set override on next report. strcat(status, "|WCO:"); report_util_axis_values(wco, temp); strcat(status, temp); } #endif #ifdef REPORT_FIELD_OVERRIDES - if (sys.report_ovr_counter > 0) sys.report_ovr_counter--; + if (sys.report_ovr_counter > 0) + sys.report_ovr_counter--; else { if (sys.state & (STATE_HOMING | STATE_CYCLE | STATE_HOLD | STATE_JOG | STATE_SAFETY_DOOR)) { - sys.report_ovr_counter = (REPORT_OVR_REFRESH_BUSY_COUNT - 1); // Reset counter for slow refresh - } else sys.report_ovr_counter = (REPORT_OVR_REFRESH_IDLE_COUNT - 1); + sys.report_ovr_counter = (REPORT_OVR_REFRESH_BUSY_COUNT - 1); // Reset counter for slow refresh + } else + sys.report_ovr_counter = (REPORT_OVR_REFRESH_IDLE_COUNT - 1); sprintf(temp, "|Ov:%d,%d,%d", sys.f_override, sys.r_override, sys.spindle_speed_ovr); strcat(status, temp); - uint8_t sp_state = spindle->get_state(); + uint8_t sp_state = spindle->get_state(); uint8_t cl_state = coolant_get_state(); if (sp_state || cl_state) { strcat(status, "|A:"); - if (sp_state) { // != SPINDLE_STATE_DISABLE - if (sp_state == SPINDLE_STATE_CW) strcat(status, "S"); // CW - else strcat(status, "C"); // CCW + if (sp_state) { // != SPINDLE_STATE_DISABLE + if (sp_state == SPINDLE_STATE_CW) + strcat(status, "S"); // CW + else + strcat(status, "C"); // CCW } - if (cl_state & COOLANT_STATE_FLOOD) strcat(status, "F"); -#ifdef COOLANT_MIST_PIN // TODO Deal with M8 - Flood - if (cl_state & COOLANT_STATE_MIST) strcat(status, "M"); -#endif + if (cl_state & COOLANT_STATE_FLOOD) + strcat(status, "F"); +# ifdef COOLANT_MIST_PIN // TODO Deal with M8 - Flood + if (cl_state & COOLANT_STATE_MIST) + strcat(status, "M"); +# endif } } #endif @@ -692,15 +702,15 @@ void report_realtime_steps() { } void report_gcode_comment(char* comment) { - char msg[80]; + char msg[80]; const uint8_t offset = 4; // ignore "MSG_" part of comment - uint8_t index = offset; + uint8_t index = offset; if (strstr(comment, "MSG")) { while (index < strlen(comment)) { msg[index - offset] = comment[index]; index++; } - msg[index - offset] = 0; // null terminate + msg[index - offset] = 0; // null terminate grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "GCode Comment...%s", msg); } } @@ -709,7 +719,6 @@ void report_machine_type(uint8_t client) { grbl_msg_sendf(client, MSG_LEVEL_INFO, "Using machine:%s", MACHINE_NAME); } - /* Print a message in hex format Ex: report_hex_msg(msg, "Rx:", 6); @@ -725,7 +734,6 @@ void report_hex_msg(char* buf, const char* prefix, int len) { } grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "%s", report); - } void report_hex_msg(uint8_t* buf, const char* prefix, int len) { @@ -738,24 +746,16 @@ void report_hex_msg(uint8_t* buf, const char* prefix, int len) { } grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "%s", report); - } char report_get_axis_letter(uint8_t axis) { switch (axis) { - case X_AXIS: - return 'X'; - case Y_AXIS: - return 'Y'; - case Z_AXIS: - return 'Z'; - case A_AXIS: - return 'A'; - case B_AXIS: - return 'B'; - case C_AXIS: - return 'C'; - default: - return '?'; + case X_AXIS: return 'X'; + case Y_AXIS: return 'Y'; + case Z_AXIS: return 'Z'; + case A_AXIS: return 'A'; + case B_AXIS: return 'B'; + case C_AXIS: return 'C'; + default: return '?'; } } diff --git a/Grbl_Esp32/report.h b/Grbl_Esp32/src/Report.h similarity index 70% rename from Grbl_Esp32/report.h rename to Grbl_Esp32/src/Report.h index 43e8f7a9..0c0fae9d 100644 --- a/Grbl_Esp32/report.h +++ b/Grbl_Esp32/src/Report.h @@ -1,5 +1,7 @@ +#pragma once + /* - report.h - Header for system level commands and real-time processes + Report.h - Header for system level commands and real-time processes Part of Grbl Copyright (c) 2014-2016 Sungeun K. Jeon for Gnea Research LLC @@ -18,9 +20,6 @@ along with Grbl. If not, see . */ -#ifndef report_h -#define report_h - // Define Grbl status codes. Valid values (0-255) #define STATUS_OK 0 #define STATUS_EXPECTED_COMMAND_LETTER 1 @@ -62,22 +61,22 @@ #define STATUS_GCODE_MAX_VALUE_EXCEEDED 38 #define STATUS_P_PARAM_MAX_EXCEEDED 39 -#define STATUS_SD_FAILED_MOUNT 60 // SD Failed to mount -#define STATUS_SD_FAILED_READ 61 // SD Failed to read file -#define STATUS_SD_FAILED_OPEN_DIR 62 // SD card failed to open directory -#define STATUS_SD_DIR_NOT_FOUND 63 // SD Card directory not found -#define STATUS_SD_FILE_EMPTY 64 // SD Card directory not found -#define STATUS_SD_FILE_NOT_FOUND 65 // SD Card file not found -#define STATUS_SD_FAILED_OPEN_FILE 66 // SD card failed to open file -#define STATUS_SD_FAILED_BUSY 67 // SD card is busy +#define STATUS_SD_FAILED_MOUNT 60 // SD Failed to mount +#define STATUS_SD_FAILED_READ 61 // SD Failed to read file +#define STATUS_SD_FAILED_OPEN_DIR 62 // SD card failed to open directory +#define STATUS_SD_DIR_NOT_FOUND 63 // SD Card directory not found +#define STATUS_SD_FILE_EMPTY 64 // SD Card directory not found +#define STATUS_SD_FILE_NOT_FOUND 65 // SD Card file not found +#define STATUS_SD_FAILED_OPEN_FILE 66 // SD card failed to open file +#define STATUS_SD_FAILED_BUSY 67 // SD card is busy #define STATUS_SD_FAILED_DEL_DIR 68 #define STATUS_SD_FAILED_DEL_FILE 69 -#define STATUS_BT_FAIL_BEGIN 70 // Bluetooth failed to start +#define STATUS_BT_FAIL_BEGIN 70 // Bluetooth failed to start #define STATUS_WIFI_FAIL_BEGIN 71 // WiFi failed to start -#define STATUS_NUMBER_RANGE 80 // Setting number range problem -#define STATUS_INVALID_VALUE 81 // Setting string problem +#define STATUS_NUMBER_RANGE 80 // Setting number range problem +#define STATUS_INVALID_VALUE 81 // Setting string problem #define STATUS_MESSAGE_FAILED 90 @@ -86,19 +85,19 @@ #define STATUS_AUTHENTICATION_FAILED 110 #define STATUS_EOL 111 -typedef uint8_t err_t; // For status codes -const char* errorString(err_t errorNumber); +typedef uint8_t err_t; // For status codes +const char* errorString(err_t errorNumber); // Define Grbl alarm codes. Valid values (1-255). 0 is reserved. -#define ALARM_HARD_LIMIT_ERROR EXEC_ALARM_HARD_LIMIT -#define ALARM_SOFT_LIMIT_ERROR EXEC_ALARM_SOFT_LIMIT -#define ALARM_ABORT_CYCLE EXEC_ALARM_ABORT_CYCLE -#define ALARM_PROBE_FAIL_INITIAL EXEC_ALARM_PROBE_FAIL_INITIAL -#define ALARM_PROBE_FAIL_CONTACT EXEC_ALARM_PROBE_FAIL_CONTACT -#define ALARM_HOMING_FAIL_RESET EXEC_ALARM_HOMING_FAIL_RESET -#define ALARM_HOMING_FAIL_DOOR EXEC_ALARM_HOMING_FAIL_DOOR -#define ALARM_HOMING_FAIL_PULLOFF EXEC_ALARM_HOMING_FAIL_PULLOFF -#define ALARM_HOMING_FAIL_APPROACH EXEC_ALARM_HOMING_FAIL_APPROACH +#define ALARM_HARD_LIMIT_ERROR EXEC_ALARM_HARD_LIMIT +#define ALARM_SOFT_LIMIT_ERROR EXEC_ALARM_SOFT_LIMIT +#define ALARM_ABORT_CYCLE EXEC_ALARM_ABORT_CYCLE +#define ALARM_PROBE_FAIL_INITIAL EXEC_ALARM_PROBE_FAIL_INITIAL +#define ALARM_PROBE_FAIL_CONTACT EXEC_ALARM_PROBE_FAIL_CONTACT +#define ALARM_HOMING_FAIL_RESET EXEC_ALARM_HOMING_FAIL_RESET +#define ALARM_HOMING_FAIL_DOOR EXEC_ALARM_HOMING_FAIL_DOOR +#define ALARM_HOMING_FAIL_PULLOFF EXEC_ALARM_HOMING_FAIL_PULLOFF +#define ALARM_HOMING_FAIL_APPROACH EXEC_ALARM_HOMING_FAIL_APPROACH // Define Grbl feedback message codes. Valid values (0-255). #define MESSAGE_CRITICAL_EVENT 1 @@ -112,22 +111,22 @@ const char* errorString(err_t errorNumber); #define MESSAGE_RESTORE_DEFAULTS 9 #define MESSAGE_SPINDLE_RESTORE 10 #define MESSAGE_SLEEP_MODE 11 -#define MESSAGE_SD_FILE_QUIT 60 // mc_reset was called during an SD job +#define MESSAGE_SD_FILE_QUIT 60 // mc_reset was called during an SD job -#define CLIENT_SERIAL 0 -#define CLIENT_BT 1 -#define CLIENT_WEBUI 2 -#define CLIENT_TELNET 3 -#define CLIENT_INPUT 4 -#define CLIENT_ALL 0xFF -#define CLIENT_COUNT 5 // total number of client types regardless if they are used +#define CLIENT_SERIAL 0 +#define CLIENT_BT 1 +#define CLIENT_WEBUI 2 +#define CLIENT_TELNET 3 +#define CLIENT_INPUT 4 +#define CLIENT_ALL 0xFF +#define CLIENT_COUNT 5 // total number of client types regardless if they are used -#define MSG_LEVEL_NONE 0 // set GRBL_MSG_LEVEL in config.h to the level you want to see -#define MSG_LEVEL_ERROR 1 -#define MSG_LEVEL_WARNING 2 -#define MSG_LEVEL_INFO 3 -#define MSG_LEVEL_DEBUG 4 -#define MSG_LEVEL_VERBOSE 5 +#define MSG_LEVEL_NONE 0 // set GRBL_MSG_LEVEL in config.h to the level you want to see +#define MSG_LEVEL_ERROR 1 +#define MSG_LEVEL_WARNING 2 +#define MSG_LEVEL_INFO 3 +#define MSG_LEVEL_DEBUG 4 +#define MSG_LEVEL_VERBOSE 5 // functions to send data to the user. void grbl_send(uint8_t client, const char* text); @@ -182,14 +181,12 @@ void report_build_info(char* line, uint8_t client); void report_gcode_comment(char* comment); #ifdef DEBUG - void report_realtime_debug(); +void report_realtime_debug(); #endif 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); +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); - -#endif diff --git a/Grbl_Esp32/grbl_sd.cpp b/Grbl_Esp32/src/SDCard.cpp similarity index 86% rename from Grbl_Esp32/grbl_sd.cpp rename to Grbl_Esp32/src/SDCard.cpp index 0b0a9bc8..f3f360ad 100644 --- a/Grbl_Esp32/grbl_sd.cpp +++ b/Grbl_Esp32/src/SDCard.cpp @@ -1,5 +1,5 @@ /* - grbl_sd.cpp - Adds SD Card Features to Grbl_ESP32 + SDCard.cpp - Adds SD Card Features to Grbl_ESP32 Part of Grbl_ESP32 Copyright (c) 2018 Barton Dring Buildlog.net @@ -18,13 +18,13 @@ along with Grbl. If not, see . */ -#include "grbl_sd.h" +#include "SDCard.h" -File myFile; -bool SD_ready_next = false; // Grbl has processed a line and is waiting for another -uint8_t SD_client = CLIENT_SERIAL; -uint32_t sd_current_line_number; // stores the most recent line number read from the SD -static char comment[LINE_BUFFER_SIZE]; // Line to be executed. Zero-terminated. +File myFile; +bool SD_ready_next = false; // Grbl has processed a line and is waiting for another +uint8_t SD_client = CLIENT_SERIAL; +uint32_t sd_current_line_number; // stores the most recent line number read from the SD +static char comment[LINE_BUFFER_SIZE]; // Line to be executed. Zero-terminated. // attempt to mount the SD card /*bool sd_mount() @@ -65,7 +65,7 @@ boolean openFile(fs::FS& fs, const char* path) { return false; } set_sd_state(SDCARD_BUSY_PRINTING); - SD_ready_next = false; // this will get set to true when Grbl issues "ok" message + SD_ready_next = false; // this will get set to true when Grbl issues "ok" message sd_current_line_number = 0; return true; } @@ -74,7 +74,7 @@ boolean closeFile() { if (!myFile) return false; set_sd_state(SDCARD_IDLE); - SD_ready_next = false; + SD_ready_next = false; sd_current_line_number = 0; myFile.close(); return true; @@ -119,7 +119,6 @@ uint32_t sd_get_current_line_number() { return sd_current_line_number; } - uint8_t sd_state = SDCARD_IDLE; uint8_t get_sd_state(bool refresh) { @@ -142,13 +141,14 @@ uint8_t get_sd_state(bool refresh) { //using default value for speed ? should be parameter //refresh content if card was removed if (SD.begin((GRBL_SPI_SS == -1) ? SS : GRBL_SPI_SS, SPI, GRBL_SPI_FREQ)) { - if (SD.cardSize() > 0)sd_state = SDCARD_IDLE; + if (SD.cardSize() > 0) + sd_state = SDCARD_IDLE; } return sd_state; } uint8_t set_sd_state(uint8_t flag) { - sd_state = flag; + sd_state = flag; return sd_state; } @@ -158,5 +158,3 @@ void sd_get_current_filename(char* name) { else name[0] = 0; } - - diff --git a/Grbl_Esp32/src/SDCard.h b/Grbl_Esp32/src/SDCard.h new file mode 100644 index 00000000..f2f60643 --- /dev/null +++ b/Grbl_Esp32/src/SDCard.h @@ -0,0 +1,47 @@ +#pragma once + +/* + * Connect the SD card to the following pins: + * + * SD Card | ESP32 + * D2 - + * D3 SS + * CMD MOSI + * VSS GND + * VDD 3.3V + * CLK SCK + * VSS GND + * D0 MISO + * D1 - + */ + +#include "Grbl.h" +#include +#include +#include + +#define FILE_TYPE_COUNT 5 // number of acceptable gcode file types in array + +#define SDCARD_DET_PIN -1 +#define SDCARD_DET_VAL 0 + +#define SDCARD_IDLE 0 +#define SDCARD_NOT_PRESENT 1 +#define SDCARD_BUSY_PRINTING 2 +#define SDCARD_BUSY_UPLOADING 4 +#define SDCARD_BUSY_PARSING 8 + +extern bool SD_ready_next; // Grbl has processed a line and is waiting for another +extern uint8_t SD_client; + +//bool sd_mount(); +uint8_t get_sd_state(bool refresh); +uint8_t set_sd_state(uint8_t flag); +void listDir(fs::FS& fs, const char* dirname, uint8_t levels, uint8_t client); +boolean openFile(fs::FS& fs, const char* path); +boolean closeFile(); +boolean readFileLine(char* line, int len); +void readFile(fs::FS& fs, const char* path); +float sd_report_perc_complete(); +uint32_t sd_get_current_line_number(); +void sd_get_current_filename(char* name); diff --git a/Grbl_Esp32/serial.cpp b/Grbl_Esp32/src/Serial.cpp similarity index 61% rename from Grbl_Esp32/serial.cpp rename to Grbl_Esp32/src/Serial.cpp index c3216295..574cc5bb 100644 --- a/Grbl_Esp32/serial.cpp +++ b/Grbl_Esp32/src/Serial.cpp @@ -1,5 +1,5 @@ /* - serial.cpp - Header for system level commands and real-time processes + Serial.cpp - Header for system level commands and real-time processes Part of Grbl Copyright (c) 2014-2016 Sungeun K. Jeon for Gnea Research LLC @@ -55,7 +55,7 @@ */ -#include "grbl.h" +#include "Grbl.h" portMUX_TYPE myMutex = portMUX_INITIALIZER_UNLOCKED; @@ -72,55 +72,54 @@ void serial_init() { Serial.begin(BAUD_RATE); // reset all buffers serial_reset_read_buffer(CLIENT_ALL); - grbl_send(CLIENT_SERIAL, "\r\n"); // create some white space after ESP32 boot info + grbl_send(CLIENT_SERIAL, "\r\n"); // create some white space after ESP32 boot info serialCheckTaskHandle = 0; // create a task to check for incoming data - xTaskCreatePinnedToCore(serialCheckTask, // task - "serialCheckTask", // name for task - 8192, // size of task stack - NULL, // parameters - 1, // priority + xTaskCreatePinnedToCore(serialCheckTask, // task + "serialCheckTask", // name for task + 8192, // size of task stack + NULL, // parameters + 1, // priority &serialCheckTaskHandle, - 1 // core - ); + 1 // 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 - while (true) { // run continuously + uint8_t data = 0; + uint8_t client = CLIENT_ALL; // who sent the data + while (true) { // run continuously while (any_client_has_data()) { if (Serial.available()) { client = CLIENT_SERIAL; - data = Serial.read(); + data = Serial.read(); } else if (inputBuffer.available()) { client = CLIENT_INPUT; - data = inputBuffer.read(); + data = inputBuffer.read(); } else { //currently is wifi or BT but better to prepare both can be live #ifdef ENABLE_BLUETOOTH if (SerialBT.hasClient() && SerialBT.available()) { client = CLIENT_BT; - data = SerialBT.read(); + data = SerialBT.read(); //Serial.write(data); // echo all data to serial } else { #endif -#if defined (ENABLE_WIFI) && defined(ENABLE_HTTP) && defined(ENABLE_SERIAL2SOCKET_IN) +#if defined(ENABLE_WIFI) && defined(ENABLE_HTTP) && defined(ENABLE_SERIAL2SOCKET_IN) if (Serial2Socket.available()) { client = CLIENT_WEBUI; - data = Serial2Socket.read(); + data = Serial2Socket.read(); } else { #endif -#if defined (ENABLE_WIFI) && defined(ENABLE_TELNET) +#if defined(ENABLE_WIFI) && defined(ENABLE_TELNET) if (telnet_server.available()) { client = CLIENT_TELNET; - data = telnet_server.read(); + data = telnet_server.read(); } #endif -#if defined (ENABLE_WIFI) && defined(ENABLE_HTTP) && defined(ENABLE_SERIAL2SOCKET_IN) +#if defined(ENABLE_WIFI) && defined(ENABLE_HTTP) && defined(ENABLE_SERIAL2SOCKET_IN) } #endif #ifdef ENABLE_BLUETOOTH @@ -144,11 +143,11 @@ void serialCheckTask(void* pvParameters) { #ifdef ENABLE_BLUETOOTH bt_config.handle(); #endif -#if defined (ENABLE_WIFI) && defined(ENABLE_HTTP) && defined(ENABLE_SERIAL2SOCKET_IN) +#if defined(ENABLE_WIFI) && defined(ENABLE_HTTP) && defined(ENABLE_SERIAL2SOCKET_IN) Serial2Socket.handle_flush(); #endif vTaskDelay(1 / portTICK_RATE_MS); // Yield to other tasks - } // while(true) + } // while(true) } void serial_reset_read_buffer(uint8_t client) { @@ -183,13 +182,13 @@ bool any_client_has_data() { #ifdef ENABLE_BLUETOOTH || (SerialBT.hasClient() && SerialBT.available()) #endif -#if defined (ENABLE_WIFI) && defined(ENABLE_HTTP) && defined(ENABLE_SERIAL2SOCKET_IN) +#if defined(ENABLE_WIFI) && defined(ENABLE_HTTP) && defined(ENABLE_SERIAL2SOCKET_IN) || Serial2Socket.available() #endif -#if defined (ENABLE_WIFI) && defined(ENABLE_TELNET) +#if defined(ENABLE_WIFI) && defined(ENABLE_TELNET) || telnet_server.available() #endif - ); + ); } // checks to see if a character is a realtime character @@ -200,48 +199,50 @@ bool is_realtime_command(uint8_t data) { // Act upon a realtime character void execute_realtime_command(uint8_t command, uint8_t client) { switch (command) { - case CMD_RESET: - mc_reset(); // Call motion control reset routine. - break; - case CMD_STATUS_REPORT: - report_realtime_status(client); // direct call instead of setting flag - break; - case CMD_CYCLE_START: - system_set_exec_state_flag(EXEC_CYCLE_START); // Set as true - break; - case CMD_FEED_HOLD: - system_set_exec_state_flag(EXEC_FEED_HOLD); // Set as true - break; - case CMD_SAFETY_DOOR: - system_set_exec_state_flag(EXEC_SAFETY_DOOR); - break; // Set as true - case CMD_JOG_CANCEL: - if (sys.state & STATE_JOG) // Block all other states from invoking motion cancel. - system_set_exec_state_flag(EXEC_MOTION_CANCEL); - break; + case CMD_RESET: + mc_reset(); // Call motion control reset routine. + break; + case CMD_STATUS_REPORT: + report_realtime_status(client); // direct call instead of setting flag + break; + case CMD_CYCLE_START: + system_set_exec_state_flag(EXEC_CYCLE_START); // Set as true + break; + case CMD_FEED_HOLD: + system_set_exec_state_flag(EXEC_FEED_HOLD); // Set as true + break; + case CMD_SAFETY_DOOR: system_set_exec_state_flag(EXEC_SAFETY_DOOR); break; // Set as true + case CMD_JOG_CANCEL: + if (sys.state & STATE_JOG) // Block all other states from invoking motion cancel. + system_set_exec_state_flag(EXEC_MOTION_CANCEL); + break; #ifdef DEBUG - case CMD_DEBUG_REPORT: {uint8_t sreg = SREG; cli(); bit_true(sys_rt_exec_debug, EXEC_DEBUG_REPORT); SREG = sreg;} break; + case CMD_DEBUG_REPORT: { + uint8_t sreg = SREG; + cli(); + bit_true(sys_rt_exec_debug, EXEC_DEBUG_REPORT); + SREG = sreg; + } break; #endif - case CMD_FEED_OVR_RESET: system_set_exec_motion_override_flag(EXEC_FEED_OVR_RESET); break; - case CMD_FEED_OVR_COARSE_PLUS: system_set_exec_motion_override_flag(EXEC_FEED_OVR_COARSE_PLUS); break; - case CMD_FEED_OVR_COARSE_MINUS: system_set_exec_motion_override_flag(EXEC_FEED_OVR_COARSE_MINUS); break; - case CMD_FEED_OVR_FINE_PLUS: system_set_exec_motion_override_flag(EXEC_FEED_OVR_FINE_PLUS); break; - case CMD_FEED_OVR_FINE_MINUS: system_set_exec_motion_override_flag(EXEC_FEED_OVR_FINE_MINUS); break; - case CMD_RAPID_OVR_RESET: system_set_exec_motion_override_flag(EXEC_RAPID_OVR_RESET); break; - case CMD_RAPID_OVR_MEDIUM: system_set_exec_motion_override_flag(EXEC_RAPID_OVR_MEDIUM); break; - case CMD_RAPID_OVR_LOW: system_set_exec_motion_override_flag(EXEC_RAPID_OVR_LOW); break; - case CMD_SPINDLE_OVR_RESET: system_set_exec_accessory_override_flag(EXEC_SPINDLE_OVR_RESET); break; - case CMD_SPINDLE_OVR_COARSE_PLUS: system_set_exec_accessory_override_flag(EXEC_SPINDLE_OVR_COARSE_PLUS); break; - case CMD_SPINDLE_OVR_COARSE_MINUS: system_set_exec_accessory_override_flag(EXEC_SPINDLE_OVR_COARSE_MINUS); break; - case CMD_SPINDLE_OVR_FINE_PLUS: system_set_exec_accessory_override_flag(EXEC_SPINDLE_OVR_FINE_PLUS); break; - case CMD_SPINDLE_OVR_FINE_MINUS: system_set_exec_accessory_override_flag(EXEC_SPINDLE_OVR_FINE_MINUS); break; - case CMD_SPINDLE_OVR_STOP: system_set_exec_accessory_override_flag(EXEC_SPINDLE_OVR_STOP); break; + case CMD_FEED_OVR_RESET: system_set_exec_motion_override_flag(EXEC_FEED_OVR_RESET); break; + case CMD_FEED_OVR_COARSE_PLUS: system_set_exec_motion_override_flag(EXEC_FEED_OVR_COARSE_PLUS); break; + case CMD_FEED_OVR_COARSE_MINUS: system_set_exec_motion_override_flag(EXEC_FEED_OVR_COARSE_MINUS); break; + case CMD_FEED_OVR_FINE_PLUS: system_set_exec_motion_override_flag(EXEC_FEED_OVR_FINE_PLUS); break; + case CMD_FEED_OVR_FINE_MINUS: system_set_exec_motion_override_flag(EXEC_FEED_OVR_FINE_MINUS); break; + case CMD_RAPID_OVR_RESET: system_set_exec_motion_override_flag(EXEC_RAPID_OVR_RESET); break; + case CMD_RAPID_OVR_MEDIUM: system_set_exec_motion_override_flag(EXEC_RAPID_OVR_MEDIUM); break; + case CMD_RAPID_OVR_LOW: system_set_exec_motion_override_flag(EXEC_RAPID_OVR_LOW); break; + case CMD_SPINDLE_OVR_RESET: system_set_exec_accessory_override_flag(EXEC_SPINDLE_OVR_RESET); break; + case CMD_SPINDLE_OVR_COARSE_PLUS: system_set_exec_accessory_override_flag(EXEC_SPINDLE_OVR_COARSE_PLUS); break; + case CMD_SPINDLE_OVR_COARSE_MINUS: system_set_exec_accessory_override_flag(EXEC_SPINDLE_OVR_COARSE_MINUS); break; + case CMD_SPINDLE_OVR_FINE_PLUS: system_set_exec_accessory_override_flag(EXEC_SPINDLE_OVR_FINE_PLUS); break; + case CMD_SPINDLE_OVR_FINE_MINUS: system_set_exec_accessory_override_flag(EXEC_SPINDLE_OVR_FINE_MINUS); break; + case CMD_SPINDLE_OVR_STOP: system_set_exec_accessory_override_flag(EXEC_SPINDLE_OVR_STOP); break; #ifdef COOLANT_FLOOD_PIN - case CMD_COOLANT_FLOOD_OVR_TOGGLE: system_set_exec_accessory_override_flag(EXEC_COOLANT_FLOOD_OVR_TOGGLE); break; + case CMD_COOLANT_FLOOD_OVR_TOGGLE: system_set_exec_accessory_override_flag(EXEC_COOLANT_FLOOD_OVR_TOGGLE); break; #endif #ifdef COOLANT_MIST_PIN - case CMD_COOLANT_MIST_OVR_TOGGLE: system_set_exec_accessory_override_flag(EXEC_COOLANT_MIST_OVR_TOGGLE); break; + case CMD_COOLANT_MIST_OVR_TOGGLE: system_set_exec_accessory_override_flag(EXEC_COOLANT_MIST_OVR_TOGGLE); break; #endif } } - diff --git a/Grbl_Esp32/serial.h b/Grbl_Esp32/src/Serial.h similarity index 85% rename from Grbl_Esp32/serial.h rename to Grbl_Esp32/src/Serial.h index dc858e0a..49bae08c 100644 --- a/Grbl_Esp32/serial.h +++ b/Grbl_Esp32/src/Serial.h @@ -1,5 +1,7 @@ +#pragma once + /* - serial.h - Header for system level commands and real-time processes + Serial.h - Header for system level commands and real-time processes Part of Grbl Copyright (c) 2014-2016 Sungeun K. Jeon for Gnea Research LLC @@ -18,20 +20,17 @@ along with Grbl. If not, see . */ -#ifndef serial_h -#define serial_h - -#include "grbl.h" +#include "Grbl.h" #ifndef RX_BUFFER_SIZE - #define RX_BUFFER_SIZE 128 +# define RX_BUFFER_SIZE 128 #endif #ifndef TX_BUFFER_SIZE - #ifdef USE_LINE_NUMBERS - #define TX_BUFFER_SIZE 112 - #else - #define TX_BUFFER_SIZE 104 - #endif +# ifdef USE_LINE_NUMBERS +# define TX_BUFFER_SIZE 112 +# else +# define TX_BUFFER_SIZE 104 +# endif #endif #define SERIAL_NO_DATA 0xff @@ -55,5 +54,3 @@ uint8_t serial_get_rx_buffer_available(uint8_t client); void execute_realtime_command(uint8_t command, uint8_t client); bool any_client_has_data(); bool is_realtime_command(uint8_t data); - -#endif diff --git a/Grbl_Esp32/servo_axis.cpp b/Grbl_Esp32/src/ServoAxis.cpp similarity index 69% rename from Grbl_Esp32/servo_axis.cpp rename to Grbl_Esp32/src/ServoAxis.cpp index 9d06b86a..7f3753c2 100644 --- a/Grbl_Esp32/servo_axis.cpp +++ b/Grbl_Esp32/src/ServoAxis.cpp @@ -1,5 +1,5 @@ /* - servo_axis.cpp + ServoAxis.cpp Part of Grbl_ESP32 copyright (c) 2018 - Bart Dring. This file was intended for use on the ESP32 @@ -18,211 +18,212 @@ You should have received a copy of the GNU General Public License along with Grbl. If not, see . - See servo_axis.h for more details + See ServoAxis.h for more details */ -#include "grbl.h" +#include "Grbl.h" + +#include "ServoAxis.h" #ifdef USE_SERVO_AXES static TaskHandle_t servosSyncTaskHandle = 0; -#ifdef SERVO_X_PIN - ServoAxis X_Servo_Axis(X_AXIS, SERVO_X_PIN); -#endif -#ifdef SERVO_Y_PIN - ServoAxis Y_Servo_Axis(Y_AXIS, SERVO_Y_PIN); -#endif -#ifdef SERVO_Z_PIN - ServoAxis Z_Servo_Axis(Z_AXIS, SERVO_Z_PIN); -#endif +# ifdef SERVO_X_PIN +ServoAxis X_Servo_Axis(X_AXIS, SERVO_X_PIN); +# endif +# ifdef SERVO_Y_PIN +ServoAxis Y_Servo_Axis(Y_AXIS, SERVO_Y_PIN); +# endif +# ifdef SERVO_Z_PIN +ServoAxis Z_Servo_Axis(Z_AXIS, SERVO_Z_PIN); +# endif -#ifdef SERVO_A_PIN - ServoAxis A_Servo_Axis(A_AXIS, SERVO_A_PIN); -#endif -#ifdef SERVO_B_PIN - ServoAxis B_Servo_Axis(B_AXIS, SERVO_B_PIN); -#endif -#ifdef SERVO_C_PIN - ServoAxis C_Servo_Axis(C_AXIS, SERVO_C_PIN); -#endif +# ifdef SERVO_A_PIN +ServoAxis A_Servo_Axis(A_AXIS, SERVO_A_PIN); +# endif +# ifdef SERVO_B_PIN +ServoAxis B_Servo_Axis(B_AXIS, SERVO_B_PIN); +# endif +# ifdef SERVO_C_PIN +ServoAxis C_Servo_Axis(C_AXIS, SERVO_C_PIN); +# endif void init_servos() { // ======================== X Axis =========================== -#ifdef SERVO_X_PIN +# ifdef SERVO_X_PIN grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "X Servo range %4.3f to %4.3f", SERVO_X_RANGE_MIN, SERVO_X_RANGE_MAX); X_Servo_Axis.init(); X_Servo_Axis.set_range(SERVO_X_RANGE_MIN, SERVO_X_RANGE_MAX); -#ifdef SERVO_X_HOMING_TYPE +# ifdef SERVO_X_HOMING_TYPE X_Servo_Axis.set_homing_type(SERVO_X_HOMING_TYPE); -#endif -#ifdef SERVO_X_HOME_POS +# endif +# ifdef SERVO_X_HOME_POS X_Servo_Axis.set_homing_position(SERVO_X_HOME_POS); -#endif -#ifdef SERVO_X_MPOS // value should be true or false +# endif +# ifdef SERVO_X_MPOS // value should be true or false X_Servo_Axis.set_use_mpos(SERVO_X_MPOS); -#endif -#ifdef SERVO_X_DISABLE_ON_ALARM +# endif +# ifdef SERVO_X_DISABLE_ON_ALARM set_disable_on_alarm(SERVO_X_DISABLE_ON_ALARM); -#endif -#ifdef SERVO_X_DISABLE_WITH_STEPPERS +# endif +# ifdef SERVO_X_DISABLE_WITH_STEPPERS set_disable_with_steppers(SERVO_X_DISABLE_WITH_STEPPERS); -#endif -#endif +# endif +# endif // ======================== Y Axis =========================== -#ifdef SERVO_Y_PIN +# ifdef SERVO_Y_PIN grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Y Servo range %4.3f to %4.3f", SERVO_Y_RANGE_MIN, SERVO_Y_RANGE_MAX); Y_Servo_Axis.init(); Y_Servo_Axis.set_range(SERVO_Y_RANGE_MIN, SERVO_Y_RANGE_MAX); -#ifdef SERVO_Y_HOMING_TYPE +# ifdef SERVO_Y_HOMING_TYPE Y_Servo_Axis.set_homing_type(SERVO_Y_HOMING_TYPE); -#endif -#ifdef SERVO_Y_HOME_POS +# endif +# ifdef SERVO_Y_HOME_POS Y_Servo_Axis.set_homing_position(SERVO_Y_HOME_POS); -#endif -#ifdef SERVO_Y_MPOS // value should be true or false +# endif +# ifdef SERVO_Y_MPOS // value should be true or false Y_Servo_Axis.set_use_mpos(SERVO_Y_MPOS); -#endif -#ifdef SERVO_Y_DISABLE_ON_ALARM +# endif +# ifdef SERVO_Y_DISABLE_ON_ALARM set_disable_on_alarm(SERVO_Y_DISABLE_ON_ALARM); -#endif -#ifdef SERVO_Y_DISABLE_WITH_STEPPERS +# endif +# ifdef SERVO_Y_DISABLE_WITH_STEPPERS set_disable_with_steppers(SERVO_Y_DISABLE_WITH_STEPPERS); -#endif -#endif +# endif +# endif // ======================== Z Axis =========================== -#ifdef SERVO_Z_PIN +# ifdef SERVO_Z_PIN grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Z Servo range %4.3f to %4.3f", SERVO_Z_RANGE_MIN, SERVO_Z_RANGE_MAX); Z_Servo_Axis.init(); Z_Servo_Axis.set_range(SERVO_Z_RANGE_MIN, SERVO_Z_RANGE_MAX); -#ifdef SERVO_Z_HOMING_TYPE +# ifdef SERVO_Z_HOMING_TYPE Z_Servo_Axis.set_homing_type(SERVO_Z_HOMING_TYPE); -#endif -#ifdef SERVO_Z_HOME_POS +# endif +# ifdef SERVO_Z_HOME_POS Z_Servo_Axis.set_homing_position(SERVO_Z_HOME_POS); -#endif -#ifdef SERVO_Z_MPOS // value should be true or false +# endif +# ifdef SERVO_Z_MPOS // value should be true or false Z_Servo_Axis.set_use_mpos(SERVO_Z_MPOS); -#endif -#ifdef SERVO_Z_DISABLE_ON_ALARM +# endif +# ifdef SERVO_Z_DISABLE_ON_ALARM set_disable_on_alarm(SERVO_Z_DISABLE_ON_ALARM); -#endif -#ifdef SERVO_Z_DISABLE_WITH_STEPPERS +# endif +# ifdef SERVO_Z_DISABLE_WITH_STEPPERS set_disable_with_steppers(SERVO_Z_DISABLE_WITH_STEPPERS); -#endif -#endif +# endif +# endif // ======================== A Axis =========================== -#ifdef SERVO_A_PIN +# ifdef SERVO_A_PIN grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "A Servo range %4.3f to %4.3f", SERVO_A_RANGE_MIN, SERVO_A_RANGE_MAX); A_Servo_Axis.init(); A_Servo_Axis.set_range(SERVO_A_RANGE_MIN, SERVO_A_RANGE_MAX); -#ifdef SERVO_A_HOMING_TYPE +# ifdef SERVO_A_HOMING_TYPE A_Servo_Axis.set_homing_type(SERVO_A_HOMING_TYPE); -#endif -#ifdef SERVO_A_HOME_POS +# endif +# ifdef SERVO_A_HOME_POS A_Servo_Axis.set_homing_position(SERVO_A_HOME_POS); -#endif -#ifdef SERVO_A_MPOS // value should be true or false +# endif +# ifdef SERVO_A_MPOS // value should be true or false A_Servo_Axis.set_use_mpos(SERVO_A_MPOS); -#endif -#ifdef SERVO_A_DISABLE_ON_ALARM +# endif +# ifdef SERVO_A_DISABLE_ON_ALARM set_disable_on_alarm(SERVO_A_DISABLE_ON_ALARM); -#endif -#ifdef SERVO_A_DISABLE_WITH_STEPPERS +# endif +# ifdef SERVO_A_DISABLE_WITH_STEPPERS set_disable_with_steppers(SERVO_A_DISABLE_WITH_STEPPERS); -#endif -#endif +# endif +# endif // ======================== B Axis =========================== -#ifdef SERVO_B_PIN +# ifdef SERVO_B_PIN grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "B Servo range %4.3f to %4.3f", SERVO_B_RANGE_MIN, SERVO_B_RANGE_MAX); B_Servo_Axis.init(); B_Servo_Axis.set_range(SERVO_B_RANGE_MIN, SERVO_B_RANGE_MAX); -#ifdef SERVO_B_HOMING_TYPE +# ifdef SERVO_B_HOMING_TYPE B_Servo_Axis.set_homing_type(SERVO_B_HOMING_TYPE); -#endif -#ifdef SERVO_B_HOME_POS +# endif +# ifdef SERVO_B_HOME_POS B_Servo_Axis.set_homing_position(SERVO_B_HOME_POS); -#endif -#ifdef SERVO_B_MPOS // value should be true or false +# endif +# ifdef SERVO_B_MPOS // value should be true or false B_Servo_Axis.set_use_mpos(SERVO_B_MPOS); -#endif -#ifdef SERVO_B_DISABLE_ON_ALARM +# endif +# ifdef SERVO_B_DISABLE_ON_ALARM set_disable_on_alarm(SERVO_B_DISABLE_ON_ALARM); -#endif -#ifdef SERVO_B_DISABLE_WITH_STEPPERS +# endif +# ifdef SERVO_B_DISABLE_WITH_STEPPERS set_disable_with_steppers(SERVO_B_DISABLE_WITH_STEPPERS); -#endif -#endif +# endif +# endif // ======================== C Axis =========================== -#ifdef SERVO_C_PIN +# ifdef SERVO_C_PIN grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "C Servo range %4.3f to %4.3f", SERVO_C_RANGE_MIN, SERVO_C_RANGE_MAX); C_Servo_Axis.init(); C_Servo_Axis.set_range(SERVO_C_RANGE_MIN, SERVO_C_RANGE_MAX); -#ifdef SERVO_C_HOMING_TYPE +# ifdef SERVO_C_HOMING_TYPE C_Servo_Axis.set_homing_type(SERVO_C_HOMING_TYPE); -#endif -#ifdef SERVO_C_HOME_POS +# endif +# ifdef SERVO_C_HOME_POS C_Servo_Axis.set_homing_position(SERVO_C_HOME_POS); -#endif -#ifdef SERVO_C_MPOS // value should be true or false +# endif +# ifdef SERVO_C_MPOS // value should be true or false C_Servo_Axis.set_use_mpos(SERVO_C_MPOS); -#endif -#ifdef SERVO_C_DISABLE_ON_ALARM +# endif +# ifdef SERVO_C_DISABLE_ON_ALARM set_disable_on_alarm(SERVO_C_DISABLE_ON_ALARM); -#endif -#ifdef SERVO_C_DISABLE_WITH_STEPPERS +# endif +# ifdef SERVO_C_DISABLE_WITH_STEPPERS set_disable_with_steppers(SERVO_C_DISABLE_WITH_STEPPERS); -#endif -#endif +# endif +# endif // setup a task that will calculate the determine and set the servo positions - xTaskCreatePinnedToCore(servosSyncTask, // task - "servosSyncTask", // name for task - 4096, // size of task stack - NULL, // parameters - 1, // priority + xTaskCreatePinnedToCore(servosSyncTask, // task + "servosSyncTask", // name for task + 4096, // size of task stack + NULL, // parameters + 1, // priority &servosSyncTaskHandle, - 0 // core - ); + 0 // core + ); } - // this is the task void servosSyncTask(void* pvParameters) { - TickType_t xLastWakeTime; + TickType_t xLastWakeTime; const TickType_t xServoFrequency = SERVO_TIMER_INT_FREQ; // in ticks (typically ms) - xLastWakeTime = xTaskGetTickCount(); // Initialise the xLastWakeTime variable with the current time. - while (true) { // don't ever return from this or the task dies -#ifdef SERVO_X_PIN + xLastWakeTime = xTaskGetTickCount(); // Initialise the xLastWakeTime variable with the current time. + while (true) { // don't ever return from this or the task dies +# ifdef SERVO_X_PIN X_Servo_Axis.set_location(); -#endif -#ifdef SERVO_Y_PIN +# endif +# ifdef SERVO_Y_PIN Y_Servo_Axis.set_location(); -#endif -#ifdef SERVO_Z_PIN +# endif +# ifdef SERVO_Z_PIN Z_Servo_Axis.set_location(); -#endif -#ifdef SERVO_A_PIN +# endif +# ifdef SERVO_A_PIN A_Servo_Axis.set_location(); -#endif -#ifdef SERVO_B_PIN +# endif +# ifdef SERVO_B_PIN B_Servo_Axis.set_location(); -#endif -#ifdef SERVO_C_PIN +# endif +# ifdef SERVO_C_PIN C_Servo_Axis.set_location(); -#endif +# endif vTaskDelayUntil(&xLastWakeTime, xServoFrequency); } } // =============================== Class Stuff ================================= // -ServoAxis::ServoAxis(uint8_t axis, uint8_t pin_num) { // constructor - _axis = axis; - _pin_num = pin_num; +ServoAxis::ServoAxis(uint8_t axis, uint8_t pin_num) { // constructor + _axis = axis; + _pin_num = pin_num; _channel_num = sys_get_next_PWM_chan_num(); - _showError = true; // this will be used to show calibration error only once - _use_mpos = true; // default is to use the machine position rather than work position + _showError = true; // this will be used to show calibration error only once + _use_mpos = true; // default is to use the machine position rather than work position } void ServoAxis::init() { @@ -235,10 +236,10 @@ void ServoAxis::init() { void ServoAxis::set_location() { // These are the pulse lengths for the minimum and maximum positions // Note: Some machines will have the physical max/min inverted with pulse length max/min due to invert setting $3=... - float servo_pulse_min, servo_pulse_max; - float min_pulse_cal, max_pulse_cal; // calibration values in percent 110% = 1.1 + float servo_pulse_min, servo_pulse_max; + float min_pulse_cal, max_pulse_cal; // calibration values in percent 110% = 1.1 uint32_t servo_pulse_len; - float servo_pos, mpos, offset; + float servo_pos, mpos, offset; // skip location if we are in alarm mode if (_disable_on_alarm && (sys.state == STATE_ALARM)) { disable(); @@ -250,14 +251,14 @@ void ServoAxis::set_location() { return; } if ((_homing_type == SERVO_HOMING_TARGET) && (sys.state == STATE_HOMING)) { - servo_pos = _homing_position; // go to servos home position + servo_pos = _homing_position; // go to servos home position } else { mpos = system_convert_axis_steps_to_mpos(sys_position, _axis); // get the axis machine position in mm if (_use_mpos) servo_pos = mpos; else { - offset = gc_state.coord_system[_axis] + gc_state.coord_offset[_axis]; // get the current axis work offset - servo_pos = mpos - offset; // determine the current work position + offset = gc_state.coord_system[_axis] + gc_state.coord_offset[_axis]; // get the current axis work offset + servo_pos = mpos - offset; // determine the current work position } } // 1. Get the pulse ranges of the servos @@ -270,17 +271,17 @@ void ServoAxis::set_location() { if (bit_istrue(dir_invert_mask->get(), bit(_axis))) // this allows the user to change the direction via settings swap(servo_pulse_min, servo_pulse_max); // get the calibration values - if (_cal_is_valid()) { // if calibration settings are OK then apply them + if (_cal_is_valid()) { // if calibration settings are OK then apply them // apply a calibration // the cals apply differently if the direction is reverse (i.e. longer pulse is lower position) - if (bit_isfalse(dir_invert_mask->get(), bit(_axis))) { // normal direction + if (bit_isfalse(dir_invert_mask->get(), bit(_axis))) { // normal direction min_pulse_cal = 2.0 - (axis_settings[_axis]->steps_per_mm->get() / 100.0); max_pulse_cal = (axis_settings[_axis]->max_travel->get() / 100.0); - } else { // inverted direction + } else { // inverted direction min_pulse_cal = (axis_settings[_axis]->steps_per_mm->get() / 100.0); max_pulse_cal = 2.0 - (axis_settings[_axis]->max_travel->get() / -100.0); } - } else { // settings are not valid so don't apply any calibration + } else { // settings are not valid so don't apply any calibration min_pulse_cal = 1.0; max_pulse_cal = 1.0; } @@ -293,7 +294,7 @@ void ServoAxis::set_location() { } void ServoAxis::_write_pwm(uint32_t duty) { - if (ledcRead(_channel_num) != duty) // only write if it is changing + if (ledcRead(_channel_num) != duty) // only write if it is changing ledcWrite(_channel_num, duty); } @@ -319,7 +320,7 @@ bool ServoAxis::_cal_is_valid() { if ((travel < -SERVO_CAL_MAX) || travel > -SERVO_CAL_MIN) { if (_showError) { grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Servo calibration ($13%d) value error. Reset to 100", _axis); - char reset_val[] = "-100"; // stored as a negative + char reset_val[] = "-100"; // stored as a negative axis_settings[_axis]->max_travel->setStringValue(reset_val); } settingsOK = false; @@ -378,6 +379,4 @@ void ServoAxis::set_use_mpos(bool use_mpos) { _use_mpos = use_mpos; } - - #endif diff --git a/Grbl_Esp32/servo_axis.h b/Grbl_Esp32/src/ServoAxis.h similarity index 67% rename from Grbl_Esp32/servo_axis.h rename to Grbl_Esp32/src/ServoAxis.h index 14fc5c66..8632f564 100644 --- a/Grbl_Esp32/servo_axis.h +++ b/Grbl_Esp32/src/ServoAxis.h @@ -1,5 +1,7 @@ +#pragma once + /* - solenoid_pen.h + ServoAxis.h Part of Grbl_ESP32 copyright (c) 2019 - Bart Dring. This file was intended for use on the ESP32 @@ -51,26 +53,22 @@ */ -#ifndef servo_axis_h -#define servo_axis_h +#include "Motors/RcServoSettings.h" -#include "Motors/RcServoClass.h" - -#define SERVO_HOMING_OFF 0 // servo is off during homing -#define SERVO_HOMING_TARGET 1 // servo is send to a location during homing +#define SERVO_HOMING_OFF 0 // servo is off during homing +#define SERVO_HOMING_TARGET 1 // servo is send to a location during homing extern float my_location; void init_servos(); void servosSyncTask(void* pvParameters); - class ServoAxis { - public: - ServoAxis(uint8_t axis, uint8_t pin_num); // constructor +public: + ServoAxis(uint8_t axis, uint8_t pin_num); // constructor void init(); void set_location(); - void disable(); // sets PWM to 0% duty cycle. Most servos can be manually moved in this state + void disable(); // sets PWM to 0% duty cycle. Most servos can be manually moved in this state void set_range(float min, float max); void set_homing_type(uint8_t homing_type); void set_homing_position(float homing_position); @@ -78,30 +76,26 @@ class ServoAxis { void set_disable_with_steppers(bool disable_with_steppers); void set_use_mpos(bool use_mpos); - private: - int _axis; // these should be assign in constructor using Grbl X_AXIS type values - int _pin_num; // The GPIO pin being used - int _channel_num; // The PWM channel +private: + int _axis; // these should be assign in constructor using Grbl X_AXIS type values + int _pin_num; // The GPIO pin being used + int _channel_num; // The PWM channel bool _showError; - uint32_t _pwm_freq = SERVO_PULSE_FREQ; + uint32_t _pwm_freq = SERVO_PULSE_FREQ; uint32_t _pwm_resolution_bits = SERVO_PULSE_RES_BITS; - float _pulse_min = SERVO_MIN_PULSE; // in pwm counts - float _pulse_max = SERVO_MAX_PULSE; // in pwm counts - float _position_min = SERVO_POSITION_MIN_DEFAULT; // position in millimeters - float _position_max = SERVO_POSITION_MAX_DEFAULT; // position in millimeters + float _pulse_min = SERVO_MIN_PULSE; // in pwm counts + float _pulse_max = SERVO_MAX_PULSE; // in pwm counts + float _position_min = SERVO_POSITION_MIN_DEFAULT; // position in millimeters + float _position_max = SERVO_POSITION_MAX_DEFAULT; // position in millimeters - - uint8_t _homing_type = SERVO_HOMING_OFF; - float _homing_position = SERVO_POSITION_MAX_DEFAULT; - bool _disable_on_alarm = true; - bool _disable_with_steppers = false; - bool _use_mpos = true; + uint8_t _homing_type = SERVO_HOMING_OFF; + float _homing_position = SERVO_POSITION_MAX_DEFAULT; + bool _disable_on_alarm = true; + bool _disable_with_steppers = false; + bool _use_mpos = true; bool _validate_cal_settings(); void _write_pwm(uint32_t duty); - bool _cal_is_valid(); // checks to see if calibration values are in acceptable range - + bool _cal_is_valid(); // checks to see if calibration values are in acceptable range }; - -#endif diff --git a/Grbl_Esp32/SettingsClass.cpp b/Grbl_Esp32/src/Settings.cpp similarity index 65% rename from Grbl_Esp32/SettingsClass.cpp rename to Grbl_Esp32/src/Settings.cpp index 4825aae3..d93b6e8e 100644 --- a/Grbl_Esp32/SettingsClass.cpp +++ b/Grbl_Esp32/src/Settings.cpp @@ -1,31 +1,25 @@ -#include "grbl.h" -#include "JSONencoder.h" +#include "Grbl.h" +#include "WebUI/JSONEncoder.h" #include -#include "nvs.h" +#include -Word::Word(type_t type, permissions_t permissions, const char* description, const char* grblName, const char* fullName) - : _description(description) - , _grblName(grblName) - , _fullName(fullName) - , _type(type) - , _permissions(permissions) -{} +Word::Word(type_t type, permissions_t permissions, const char* description, const char* grblName, const char* fullName) : + _description(description), _grblName(grblName), _fullName(fullName), _type(type), _permissions(permissions) {} Command* Command::List = NULL; -Command::Command(const char* description, type_t type, permissions_t permissions, const char* grblName, const char* fullName) - : Word(type, permissions, description, grblName, fullName) -{ +Command::Command(const char* description, type_t type, permissions_t permissions, const char* grblName, const char* fullName) : + Word(type, permissions, description, grblName, fullName) { link = List; List = this; } Setting* Setting::List = NULL; -Setting::Setting(const char* description, type_t type, permissions_t permissions, const char* grblName, const char* fullName, bool (*checker)(char *)) - : Word(type, permissions, description, grblName, fullName) - , _checker(checker) -{ +Setting::Setting( + const char* description, type_t type, permissions_t permissions, const char* grblName, const char* fullName, bool (*checker)(char*)) : + Word(type, permissions, description, grblName, fullName), + _checker(checker) { link = List; List = this; @@ -36,9 +30,9 @@ Setting::Setting(const char* description, type_t type, permissions_t permissions _keyName = _fullName; } else { // This is Donald Knuth's hash function from Vol 3, chapter 6.4 - char *hashName = (char *)malloc(16); - uint32_t hash = len; - for (const char *s = fullName; *s; s++) { + char* hashName = (char*)malloc(16); + uint32_t hash = len; + for (const char* s = fullName; *s; s++) { hash = ((hash << 5) ^ (hash >> 27)) ^ (*s); } sprintf(hashName, "%.7s%08x", fullName, hash); @@ -46,7 +40,7 @@ Setting::Setting(const char* description, type_t type, permissions_t permissions } } -err_t Setting::check(char *s) { +err_t Setting::check(char* s) { if (sys.state != STATE_IDLE && !(sys.state & STATE_ALARM)) { return STATUS_IDLE_ERROR; } @@ -66,18 +60,22 @@ void Setting::init() { } } -IntSetting::IntSetting(const char *description, type_t type, permissions_t permissions, const char* grblName, const char* name, int32_t defVal, int32_t minVal, int32_t maxVal, bool (*checker)(char *) = NULL) - : Setting(description, type, permissions, grblName, name, checker) - , _defaultValue(defVal) - , _currentValue(defVal) - , _minValue(minVal) - , _maxValue(maxVal) -{ } +IntSetting::IntSetting(const char* description, + type_t type, + permissions_t permissions, + const char* grblName, + const char* name, + int32_t defVal, + int32_t minVal, + int32_t maxVal, + bool (*checker)(char*) = NULL) : + Setting(description, type, permissions, grblName, name, checker), + _defaultValue(defVal), _currentValue(defVal), _minValue(minVal), _maxValue(maxVal) {} void IntSetting::load() { esp_err_t err = nvs_get_i32(_handle, _keyName, &_storedValue); if (err) { - _storedValue = std::numeric_limits::min(); + _storedValue = std::numeric_limits::min(); _currentValue = _defaultValue; } else { _currentValue = _storedValue; @@ -96,7 +94,7 @@ err_t IntSetting::setStringValue(char* s) { if (err_t err = check(s)) { return err; } - char* endptr; + char* endptr; int32_t convertedValue = strtol(s, &endptr, 10); if (endptr == s || *endptr != '\0') { return STATUS_BAD_NUMBER_FORMAT; @@ -124,23 +122,27 @@ const char* IntSetting::getStringValue() { return strval; } -void IntSetting::addWebui(JSONencoder *j) { +void IntSetting::addWebui(JSONencoder* j) { if (getDescription()) { j->begin_webui(getName(), getDescription(), "I", getStringValue(), _minValue, _maxValue); j->end_object(); } } -AxisMaskSetting::AxisMaskSetting(const char *description, type_t type, permissions_t permissions, const char* grblName, const char* name, int32_t defVal, bool (*checker)(char *) = NULL) - : Setting(description, type, permissions, grblName, name, checker) - , _defaultValue(defVal) - , _currentValue(defVal) -{ } +AxisMaskSetting::AxisMaskSetting(const char* description, + type_t type, + permissions_t permissions, + const char* grblName, + const char* name, + int32_t defVal, + bool (*checker)(char*) = NULL) : + Setting(description, type, permissions, grblName, name, checker), + _defaultValue(defVal), _currentValue(defVal) {} void AxisMaskSetting::load() { esp_err_t err = nvs_get_i32(_handle, _keyName, &_storedValue); if (err) { - _storedValue = -1; + _storedValue = -1; _currentValue = _defaultValue; } else { _currentValue = _storedValue; @@ -160,7 +162,7 @@ err_t AxisMaskSetting::setStringValue(char* s) { return err; } int32_t convertedValue; - char* endptr; + char* endptr; if (*s == '\0') { convertedValue = 0; } else { @@ -200,8 +202,8 @@ const char* AxisMaskSetting::getCompatibleValue() { const char* AxisMaskSetting::getStringValue() { static char strval[32]; - char *s = strval; - uint32_t mask = get(); + char* s = strval; + uint32_t mask = get(); for (int i = 0; i < MAX_N_AXIS; i++) { if (mask & bit(i)) { *s++ = "XYZABC"[i]; @@ -211,20 +213,24 @@ const char* AxisMaskSetting::getStringValue() { return strval; } -void AxisMaskSetting::addWebui(JSONencoder *j) { +void AxisMaskSetting::addWebui(JSONencoder* j) { if (getDescription()) { - j->begin_webui(getName(), getDescription(), "I", getStringValue(), 0, (1<begin_webui(getName(), getDescription(), "I", getStringValue(), 0, (1 << MAX_N_AXIS) - 1); j->end_object(); } } -FloatSetting::FloatSetting(const char *description, type_t type, permissions_t permissions, const char* grblName, const char* name, float defVal, float minVal, float maxVal, bool (*checker)(char *) = NULL) - : Setting(description, type, permissions, grblName, name, checker) - , _defaultValue(defVal) - , _currentValue(defVal) - , _minValue(minVal) - , _maxValue(maxVal) -{ } +FloatSetting::FloatSetting(const char* description, + type_t type, + permissions_t permissions, + const char* grblName, + const char* name, + float defVal, + float minVal, + float maxVal, + bool (*checker)(char*) = NULL) : + Setting(description, type, permissions, grblName, name, checker), + _defaultValue(defVal), _currentValue(defVal), _minValue(minVal), _maxValue(maxVal) {} void FloatSetting::load() { union { @@ -251,12 +257,10 @@ err_t FloatSetting::setStringValue(char* s) { return err; } - float convertedValue; - uint8_t len = strlen(s); + float convertedValue; + uint8_t len = strlen(s); uint8_t retlen = 0; - if (!read_float(s, &retlen, &convertedValue) - || retlen != len) - { + if (!read_float(s, &retlen, &convertedValue) || retlen != len) { return STATUS_BAD_NUMBER_FORMAT; } if (convertedValue < _minValue || convertedValue > _maxValue) { @@ -299,31 +303,38 @@ const char* FloatSetting::getStringValue() { return strval; } -StringSetting::StringSetting(const char *description, type_t type, permissions_t permissions, const char* grblName, const char* name, const char* defVal, int min, int max, bool (*checker)(char *)) - : Setting(description, type, permissions, grblName, name, checker) -{ +StringSetting::StringSetting(const char* description, + type_t type, + permissions_t permissions, + const char* grblName, + const char* name, + const char* defVal, + int min, + int max, + bool (*checker)(char*)) : + Setting(description, type, permissions, grblName, name, checker) { _defaultValue = defVal; _currentValue = defVal; - _minLength = min; - _maxLength = max; - }; + _minLength = min; + _maxLength = max; +}; void StringSetting::load() { - size_t len = 0; + size_t len = 0; esp_err_t err = nvs_get_str(_handle, _keyName, NULL, &len); - if(err) { - _storedValue = _defaultValue; + if (err) { + _storedValue = _defaultValue; _currentValue = _defaultValue; return; } char buf[len]; err = nvs_get_str(_handle, _keyName, buf, &len); if (err) { - _storedValue = _defaultValue; + _storedValue = _defaultValue; _currentValue = _defaultValue; return; } - _storedValue = String(buf); + _storedValue = String(buf); _currentValue = _storedValue; } @@ -341,7 +352,7 @@ err_t StringSetting::setStringValue(char* s) { if (err_t err = check(s)) { return err; } - _currentValue = s; + _currentValue = s; if (_storedValue != _currentValue) { if (_currentValue == _defaultValue) { nvs_erase_key(_handle, _keyName); @@ -358,41 +369,37 @@ err_t StringSetting::setStringValue(char* s) { const char* StringSetting::getStringValue() { // If the string is a password do not display it - if (_checker && - ( - #ifdef ENABLE_WIFI - _checker == (bool (*)(char *))WiFiConfig::isPasswordValid - || - #endif - _checker == (bool (*)(char *))COMMANDS::isLocalPasswordValid - )) { + if (_checker && ( +#ifdef ENABLE_WIFI + _checker == (bool (*)(char*))WiFiConfig::isPasswordValid || +#endif + _checker == (bool (*)(char*))COMMANDS::isLocalPasswordValid)) { return "******"; } return _currentValue.c_str(); } -void StringSetting::addWebui(JSONencoder *j) { +void StringSetting::addWebui(JSONencoder* j) { if (!getDescription()) { return; } - j->begin_webui( - getName(), getDescription(), "S", getStringValue(), _minLength, _maxLength); + j->begin_webui(getName(), getDescription(), "S", getStringValue(), _minLength, _maxLength); j->end_object(); } -typedef std::map enum_opt_t; +typedef std::map enum_opt_t; -EnumSetting::EnumSetting(const char *description, type_t type, permissions_t permissions, const char* grblName, const char* name, int8_t defVal, enum_opt_t *opts) +EnumSetting::EnumSetting( + const char* description, type_t type, permissions_t permissions, const char* grblName, const char* name, int8_t defVal, enum_opt_t* opts) // No checker function because enumerations have an exact set of value - : Setting(description, type, permissions, grblName, name, NULL) - , _defaultValue(defVal) - , _options(opts) -{ } + : + Setting(description, type, permissions, grblName, name, NULL), + _defaultValue(defVal), _options(opts) {} void EnumSetting::load() { esp_err_t err = nvs_get_i8(_handle, _keyName, &_storedValue); if (err) { - _storedValue = -1; + _storedValue = -1; _currentValue = _defaultValue; } else { _currentValue = _storedValue; @@ -411,7 +418,7 @@ void EnumSetting::setDefault() { // This is necessary for WebUI, which uses the number // for setting. err_t EnumSetting::setStringValue(char* s) { - s = trim(s); + s = trim(s); enum_opt_t::iterator it = _options->find(s); if (it == _options->end()) { // If we don't find the value in keys, look for it in the numeric values @@ -420,7 +427,7 @@ err_t EnumSetting::setStringValue(char* s) { if (!s || !*s) { return STATUS_BAD_NUMBER_FORMAT; } - char *endptr; + char* endptr; uint8_t num = strtol(s, &endptr, 10); // Disallow non-numeric characters in string if (*endptr) { @@ -450,9 +457,7 @@ err_t EnumSetting::setStringValue(char* s) { } const char* EnumSetting::getStringValue() { - for (enum_opt_t::iterator it = _options->begin(); - it != _options->end(); - it++) { + for (enum_opt_t::iterator it = _options->begin(); it != _options->end(); it++) { if (it->second == _currentValue) { return it->first; } @@ -460,32 +465,35 @@ const char* EnumSetting::getStringValue() { return "???"; } -void EnumSetting::addWebui(JSONencoder *j) { +void EnumSetting::addWebui(JSONencoder* j) { if (!getDescription()) { - return; + return; } j->begin_webui(getName(), getDescription(), "B", String(get()).c_str()); j->begin_array("O"); - for (enum_opt_t::iterator it = _options->begin(); - it != _options->end(); - it++) { + for (enum_opt_t::iterator it = _options->begin(); it != _options->end(); it++) { j->begin_object(); j->member(it->first, it->second); j->end_object(); - } + } j->end_array(); j->end_object(); } -FlagSetting::FlagSetting(const char *description, type_t type, permissions_t permissions, const char * grblName, const char* name, bool defVal, bool (*checker)(char *) = NULL) : +FlagSetting::FlagSetting(const char* description, + type_t type, + permissions_t permissions, + const char* grblName, + const char* name, + bool defVal, + bool (*checker)(char*) = NULL) : Setting(description, type, permissions, grblName, name, checker), - _defaultValue(defVal) -{ } + _defaultValue(defVal) {} void FlagSetting::load() { esp_err_t err = nvs_get_i8(_handle, _keyName, &_storedValue); if (err) { - _storedValue = -1; // Neither well-formed false (0) nor true (1) + _storedValue = -1; // Neither well-formed false (0) nor true (1) _currentValue = _defaultValue; } else { _currentValue = !!_storedValue; @@ -499,12 +507,9 @@ void FlagSetting::setDefault() { } err_t FlagSetting::setStringValue(char* s) { - s = trim(s); - _currentValue = (strcasecmp(s, "on") == 0) - || (strcasecmp(s, "true") == 0) - || (strcasecmp(s, "enabled") == 0) - || (strcasecmp(s, "yes") == 0) - || (strcasecmp(s, "1") == 0); + s = trim(s); + _currentValue = (strcasecmp(s, "on") == 0) || (strcasecmp(s, "true") == 0) || (strcasecmp(s, "enabled") == 0) || + (strcasecmp(s, "yes") == 0) || (strcasecmp(s, "1") == 0); // _storedValue is -1, 0, or 1 // _currentValue is 0 or 1 if (_storedValue != (int8_t)_currentValue) { @@ -526,18 +531,27 @@ const char* FlagSetting::getCompatibleValue() { return get() ? "1" : "0"; } - #include -IPaddrSetting::IPaddrSetting(const char *description, type_t type, permissions_t permissions, const char * grblName, const char* name, uint32_t defVal, bool (*checker)(char *) = NULL) - : Setting(description, type, permissions, grblName, name, checker) // There are no GRBL IP settings. - , _defaultValue(defVal) - , _currentValue(defVal) -{ } +IPaddrSetting::IPaddrSetting(const char* description, + type_t type, + permissions_t permissions, + const char* grblName, + const char* name, + uint32_t defVal, + bool (*checker)(char*) = NULL) : + Setting(description, type, permissions, grblName, name, checker) // There are no GRBL IP settings. + , + _defaultValue(defVal), _currentValue(defVal) {} -IPaddrSetting::IPaddrSetting(const char *description, type_t type, permissions_t permissions, const char * grblName, const char* name, const char *defVal, bool (*checker)(char *) = NULL) - : Setting(description, type, permissions, grblName, name, checker) -{ +IPaddrSetting::IPaddrSetting(const char* description, + type_t type, + permissions_t permissions, + const char* grblName, + const char* name, + const char* defVal, + bool (*checker)(char*) = NULL) : + Setting(description, type, permissions, grblName, name, checker) { IPAddress ipaddr; if (ipaddr.fromString(defVal)) { _defaultValue = ipaddr; @@ -548,9 +562,9 @@ IPaddrSetting::IPaddrSetting(const char *description, type_t type, permissions_t } void IPaddrSetting::load() { - esp_err_t err = nvs_get_i32(_handle, _keyName, (int32_t *)&_storedValue); + esp_err_t err = nvs_get_i32(_handle, _keyName, (int32_t*)&_storedValue); if (err) { - _storedValue = 0x000000ff; // Unreasonable value for any IP thing + _storedValue = 0x000000ff; // Unreasonable value for any IP thing _currentValue = _defaultValue; } else { _currentValue = _storedValue; @@ -589,21 +603,19 @@ err_t IPaddrSetting::setStringValue(char* s) { const char* IPaddrSetting::getStringValue() { static String s; - IPAddress ipaddr(get()); + IPAddress ipaddr(get()); s = ipaddr.toString(); return s.c_str(); } -void IPaddrSetting::addWebui(JSONencoder *j) { +void IPaddrSetting::addWebui(JSONencoder* j) { if (getDescription()) { j->begin_webui(getName(), getDescription(), "A", getStringValue()); j->end_object(); } } - AxisSettings::AxisSettings(const char *axisName) : - name(axisName) -{} +AxisSettings::AxisSettings(const char* axisName) : name(axisName) {} err_t GrblCommand::action(char* value, auth_t auth_type, ESPResponseStream* out) { if (sys.state & _disallowedStates) { diff --git a/Grbl_Esp32/src/Settings.h b/Grbl_Esp32/src/Settings.h new file mode 100644 index 00000000..7a446679 --- /dev/null +++ b/Grbl_Esp32/src/Settings.h @@ -0,0 +1,424 @@ +#pragma once + +#include "WebUI/JSONEncoder.h" +#include +#include +#include "WebUI/ESPResponse.h" + +// Command::List is a linked list of all settings, +// so common code can enumerate them. +class Command; +// extern Command *CommandsList; + +// This abstract class defines the generic interface that +// is used to set and get values for all settings independent +// of their underlying data type. The values are always +// represented as human-readable strings. This generic +// interface is used for managing settings via the user interface. + +// Derived classes implement these generic functions for different +// kinds of data. Code that accesses settings should use only these +// generic functions and should not use derived classes directly. + +enum { + NO_AXIS = 255, +}; +typedef enum : uint8_t { + GRBL = 1, // Classic GRBL settings like $100 + EXTENDED, // Settings added by early versions of Grbl_Esp32 + WEBSET, // Settings for ESP3D_WebUI, stored in NVS + GRBLCMD, // Non-persistent GRBL commands like $H + WEBCMD, // ESP3D_WebUI commands that are not directly settings +} type_t; +typedef enum : uint8_t { + WG, // Readable and writable as guest + WU, // Readable and writable as user and admin + WA, // Readable as user and admin, writable as admin +} permissions_t; +typedef uint8_t axis_t; + +class Word { +protected: + const char* _description; + const char* _grblName; + const char* _fullName; + type_t _type; + permissions_t _permissions; + +public: + Word(type_t type, permissions_t permissions, const char* description, const char* grblName, const char* fullName); + type_t getType() { return _type; } + permissions_t getPermissions() { return _permissions; } + const char* getName() { return _fullName; } + const char* getGrblName() { return _grblName; } + const char* getDescription() { return _description; } +}; + +class Command : public Word { +protected: + Command* link; // linked list of setting objects +public: + static Command* List; + Command* next() { return link; } + + ~Command() {} + Command(const char* description, type_t type, permissions_t permissions, const char* grblName, const char* fullName); + + // The default implementation of addWebui() does nothing. + // Derived classes may override it to do something. + virtual void addWebui(JSONencoder*) {}; + + virtual err_t action(char* value, auth_t auth_level, ESPResponseStream* out) = 0; +}; + +class Setting : public Word { +private: +protected: + static nvs_handle _handle; + // group_t _group; + axis_t _axis = NO_AXIS; + Setting* link; // linked list of setting objects + + bool (*_checker)(char*); + const char* _keyName; + +public: + static void init(); + static Setting* List; + Setting* next() { return link; } + + err_t check(char* s); + + static err_t report_nvs_stats(const char* value, auth_t auth_level, ESPResponseStream* out) { + nvs_stats_t stats; + if (err_t err = nvs_get_stats(NULL, &stats)) + return err; + grbl_sendf(out->client(), "[MSG: NVS Used: %d Free: %d Total: %d]\r\n", stats.used_entries, stats.free_entries, stats.total_entries); +#if 0 // The SDK we use does not have this yet + nvs_iterator_t it = nvs_entry_find(NULL, NULL, NVS_TYPE_ANY); + while (it != NULL) { + nvs_entry_info_t info; + nvs_entry_info(it, &info); + it = nvs_entry_next(it); + grbl_sendf(out->client(), "namespace %s key '%s', type '%d' \n", info.namespace_name, info.key, info.type); + } +#endif + return STATUS_OK; + } + + static err_t eraseNVS(const char* value, auth_t auth_level, ESPResponseStream* out) { + nvs_erase_all(_handle); + // return STATUS_OK; + return 0; + } + + ~Setting() {} + // Setting(const char *description, group_t group, const char * grblName, const char* fullName, bool (*checker)(char *)); + Setting(const char* description, type_t type, permissions_t permissions, const char* grblName, const char* fullName, bool (*checker)(char*)); + axis_t getAxis() { return _axis; } + void setAxis(axis_t axis) { _axis = axis; } + + // load() reads the backing store to get the current + // value of the setting. This could be slow so it + // should be done infrequently, typically once at startup. + virtual void load() {}; + virtual void setDefault() {}; + + // The default implementation of addWebui() does nothing. + // Derived classes may override it to do something. + virtual void addWebui(JSONencoder*) {}; + + virtual err_t setStringValue(char* value) = 0; + err_t setStringValue(String s) { return setStringValue(s.c_str()); } + virtual const char* getStringValue() = 0; + virtual const char* getCompatibleValue() { return getStringValue(); } +}; + +class IntSetting : public Setting { +private: + int32_t _defaultValue; + int32_t _currentValue; + int32_t _storedValue; + int32_t _minValue; + int32_t _maxValue; + +public: + IntSetting(const char* description, + type_t type, + permissions_t permissions, + const char* grblName, + const char* name, + int32_t defVal, + int32_t minVal, + int32_t maxVal, + bool (*checker)(char*)); + + IntSetting(type_t type, + permissions_t permissions, + const char* grblName, + const char* name, + int32_t defVal, + int32_t minVal, + int32_t maxVal, + bool (*checker)(char*) = NULL) : + IntSetting(NULL, type, permissions, grblName, name, defVal, minVal, maxVal, checker) {} + + void load(); + void setDefault(); + void addWebui(JSONencoder*); + err_t setStringValue(char* value); + const char* getStringValue(); + + int32_t get() { return _currentValue; } +}; + +class AxisMaskSetting : public Setting { +private: + int32_t _defaultValue; + int32_t _currentValue; + int32_t _storedValue; + +public: + AxisMaskSetting(const char* description, + type_t type, + permissions_t permissions, + const char* grblName, + const char* name, + int32_t defVal, + bool (*checker)(char*)); + + AxisMaskSetting( + type_t type, permissions_t permissions, const char* grblName, const char* name, int32_t defVal, bool (*checker)(char*) = NULL) : + AxisMaskSetting(NULL, type, permissions, grblName, name, defVal, checker) {} + + void load(); + void setDefault(); + void addWebui(JSONencoder*); + err_t setStringValue(char* value); + const char* getCompatibleValue(); + const char* getStringValue(); + + int32_t get() { return _currentValue; } +}; + +class FloatSetting : public Setting { +private: + float _defaultValue; + float _currentValue; + float _storedValue; + float _minValue; + float _maxValue; + +public: + FloatSetting(const char* description, + type_t type, + permissions_t permissions, + const char* grblName, + const char* name, + float defVal, + float minVal, + float maxVal, + bool (*checker)(char*)); + + FloatSetting(type_t type, + permissions_t permissions, + const char* grblName, + const char* name, + float defVal, + float minVal, + float maxVal, + bool (*checker)(char*) = NULL) : + FloatSetting(NULL, type, permissions, grblName, name, defVal, minVal, maxVal, checker) {} + + void load(); + void setDefault(); + // There are no Float settings in WebUI + void addWebui(JSONencoder*) {} + err_t setStringValue(char* value); + const char* getStringValue(); + + float get() { return _currentValue; } +}; + +#define MAX_SETTING_STRING 256 +class StringSetting : public Setting { +private: + String _defaultValue; + String _currentValue; + String _storedValue; + int _minLength; + int _maxLength; + void _setStoredValue(const char* s); + +public: + StringSetting(const char* description, + type_t type, + permissions_t permissions, + const char* grblName, + const char* name, + const char* defVal, + int min, + int max, + bool (*checker)(char*)); + + StringSetting( + type_t type, permissions_t permissions, const char* grblName, const char* name, const char* defVal, bool (*checker)(char*) = NULL) : + StringSetting(NULL, type, permissions, grblName, name, defVal, 0, 0, checker) {}; + + void load(); + void setDefault(); + void addWebui(JSONencoder*); + err_t setStringValue(char* value); + const char* getStringValue(); + + const char* get() { return _currentValue.c_str(); } +}; +struct cmp_str { + bool operator()(char const* a, char const* b) const { return strcasecmp(a, b) < 0; } +}; +typedef std::map enum_opt_t; + +class EnumSetting : public Setting { +private: + int8_t _defaultValue; + int8_t _storedValue; + int8_t _currentValue; + std::map* _options; + +public: + EnumSetting(const char* description, + type_t type, + permissions_t permissions, + const char* grblName, + const char* name, + int8_t defVal, + enum_opt_t* opts); + + EnumSetting(type_t type, permissions_t permissions, const char* grblName, const char* name, int8_t defVal, enum_opt_t* opts) : + EnumSetting(NULL, type, permissions, grblName, name, defVal, opts) {} + + void load(); + void setDefault(); + void addWebui(JSONencoder*); + err_t setStringValue(char* value); + const char* getStringValue(); + + int8_t get() { return _currentValue; } +}; + +class FlagSetting : public Setting { +private: + bool _defaultValue; + int8_t _storedValue; + bool _currentValue; + +public: + FlagSetting(const char* description, + type_t type, + permissions_t permissions, + const char* grblName, + const char* name, + bool defVal, + bool (*checker)(char*)); + FlagSetting(type_t type, permissions_t permissions, const char* grblName, const char* name, bool defVal, bool (*checker)(char*) = NULL) : + FlagSetting(NULL, type, permissions, grblName, name, defVal, checker) {} + + void load(); + void setDefault(); + // There are no Flag settings in WebUI + // The booleans are expressed as Enums + void addWebui(JSONencoder*) {} + err_t setStringValue(char* value); + const char* getCompatibleValue(); + const char* getStringValue(); + + bool get() { return _currentValue; } +}; + +class IPaddrSetting : public Setting { +private: + uint32_t _defaultValue; + uint32_t _currentValue; + uint32_t _storedValue; + +public: + IPaddrSetting(const char* description, + type_t type, + permissions_t permissions, + const char* grblName, + const char* name, + uint32_t defVal, + bool (*checker)(char*)); + IPaddrSetting(const char* description, + type_t type, + permissions_t permissions, + const char* grblName, + const char* name, + const char* defVal, + bool (*checker)(char*)); + + void load(); + void setDefault(); + void addWebui(JSONencoder*); + err_t setStringValue(char* value); + const char* getStringValue(); + + uint32_t get() { return _currentValue; } +}; + +class AxisSettings { +public: + const char* name; + FloatSetting* steps_per_mm; + FloatSetting* max_rate; + FloatSetting* acceleration; + FloatSetting* max_travel; + FloatSetting* run_current; + FloatSetting* hold_current; + IntSetting* microsteps; + IntSetting* stallguard; + + AxisSettings(const char* axisName); +}; +class WebCommand : public Command { +private: + err_t (*_action)(char*, auth_t); + const char* password; + +public: + WebCommand(const char* description, + type_t type, + permissions_t permissions, + const char* grblName, + const char* name, + err_t (*action)(char*, auth_t)) : + Command(description, type, permissions, grblName, name), + _action(action) {} + err_t action(char* value, auth_t auth_level, ESPResponseStream* response); +}; + +enum : uint8_t { + ANY_STATE = 0, + IDLE_OR_ALARM = 0xff & ~STATE_ALARM, + IDLE_OR_JOG = 0xff & ~STATE_JOG, + NOT_CYCLE_OR_HOLD = STATE_CYCLE | STATE_HOLD, +}; + +class GrblCommand : public Command { +private: + err_t (*_action)(const char*, auth_t, ESPResponseStream*); + uint8_t _disallowedStates; + +public: + GrblCommand(const char* grblName, + const char* name, + err_t (*action)(const char*, auth_t, ESPResponseStream*), + uint8_t disallowedStates, + permissions_t auth) : + Command(NULL, GRBLCMD, auth, grblName, name), + _action(action), _disallowedStates(disallowedStates) {} + + GrblCommand(const char* grblName, const char* name, err_t (*action)(const char*, auth_t, ESPResponseStream*), uint8_t disallowedStates) : + GrblCommand(grblName, name, action, disallowedStates, WG) {} + err_t action(char* value, auth_t auth_level, ESPResponseStream* response); +}; diff --git a/Grbl_Esp32/src/SettingsDefinitions.cpp b/Grbl_Esp32/src/SettingsDefinitions.cpp new file mode 100644 index 00000000..d9b712a9 --- /dev/null +++ b/Grbl_Esp32/src/SettingsDefinitions.cpp @@ -0,0 +1,323 @@ +#include "Grbl.h" + +bool motorSettingChanged = false; + +StringSetting* startup_line_0; +StringSetting* startup_line_1; +StringSetting* build_info; + +IntSetting* pulse_microseconds; +IntSetting* stepper_idle_lock_time; + +AxisMaskSetting* step_invert_mask; +AxisMaskSetting* dir_invert_mask; +// TODO Settings - need to call st_generate_step_invert_masks; +AxisMaskSetting* homing_dir_mask; +AxisMaskSetting* stallguard_debug_mask; + +FlagSetting* step_enable_invert; +FlagSetting* limit_invert; +FlagSetting* probe_invert; +FlagSetting* report_inches; +FlagSetting* soft_limits; +// TODO Settings - need to check for HOMING_ENABLE +FlagSetting* hard_limits; +// TODO Settings - need to call limits_init; +FlagSetting* homing_enable; +// TODO Settings - also need to clear, but not set, soft_limits +FlagSetting* laser_mode; +// TODO Settings - also need to call my_spindle->init; + +IntSetting* status_mask; +FloatSetting* junction_deviation; +FloatSetting* arc_tolerance; + +FloatSetting* homing_feed_rate; +FloatSetting* homing_seek_rate; +FloatSetting* homing_debounce; +FloatSetting* homing_pulloff; +FloatSetting* spindle_pwm_freq; +FloatSetting* rpm_max; +FloatSetting* rpm_min; +FloatSetting* spindle_delay_spinup; +FloatSetting* spindle_delay_spindown; +FlagSetting* spindle_enbl_off_with_zero_speed; +FlagSetting* spindle_enable_invert; +FlagSetting* spindle_output_invert; + +FloatSetting* spindle_pwm_off_value; +FloatSetting* spindle_pwm_min_value; +FloatSetting* spindle_pwm_max_value; +IntSetting* spindle_pwm_bit_precision; + +EnumSetting* spindle_type; + +enum_opt_t spindleTypes = { + // clang-format off + { "NONE", SPINDLE_TYPE_NONE }, + { "PWM", SPINDLE_TYPE_PWM }, + { "RELAY", SPINDLE_TYPE_RELAY }, + { "LASER", SPINDLE_TYPE_LASER }, + { "DAC", SPINDLE_TYPE_DAC }, + { "HUANYANG", SPINDLE_TYPE_HUANYANG }, + { "BESC", SPINDLE_TYPE_BESC }, + { "10V", SPINDLE_TYPE_10V }, + // clang-format on +}; + +AxisSettings* x_axis_settings; +AxisSettings* y_axis_settings; +AxisSettings* z_axis_settings; +AxisSettings* a_axis_settings; +AxisSettings* b_axis_settings; +AxisSettings* c_axis_settings; + +AxisSettings* axis_settings[MAX_N_AXIS]; + +typedef struct { + const char* name; + float steps_per_mm; + float max_rate; + float acceleration; + float max_travel; + float run_current; + float hold_current; + uint16_t microsteps; + uint16_t stallguard; +} axis_defaults_t; +axis_defaults_t axis_defaults[] = { { "X", + DEFAULT_X_STEPS_PER_MM, + DEFAULT_X_MAX_RATE, + DEFAULT_X_ACCELERATION, + DEFAULT_X_MAX_TRAVEL, + DEFAULT_X_CURRENT, + DEFAULT_X_HOLD_CURRENT, + DEFAULT_X_MICROSTEPS, + DEFAULT_X_STALLGUARD }, + { "Y", + DEFAULT_Y_STEPS_PER_MM, + DEFAULT_Y_MAX_RATE, + DEFAULT_Y_ACCELERATION, + DEFAULT_Y_MAX_TRAVEL, + DEFAULT_Y_CURRENT, + DEFAULT_Y_HOLD_CURRENT, + DEFAULT_Y_MICROSTEPS, + DEFAULT_Y_STALLGUARD }, + { "Z", + DEFAULT_Z_STEPS_PER_MM, + DEFAULT_Z_MAX_RATE, + DEFAULT_Z_ACCELERATION, + DEFAULT_Z_MAX_TRAVEL, + DEFAULT_Z_CURRENT, + DEFAULT_Z_HOLD_CURRENT, + DEFAULT_Z_MICROSTEPS, + DEFAULT_Z_STALLGUARD }, + { "A", + DEFAULT_A_STEPS_PER_MM, + DEFAULT_A_MAX_RATE, + DEFAULT_A_ACCELERATION, + DEFAULT_A_MAX_TRAVEL, + DEFAULT_A_CURRENT, + DEFAULT_A_HOLD_CURRENT, + DEFAULT_A_MICROSTEPS, + DEFAULT_A_STALLGUARD }, + { "B", + DEFAULT_B_STEPS_PER_MM, + DEFAULT_B_MAX_RATE, + DEFAULT_B_ACCELERATION, + DEFAULT_B_MAX_TRAVEL, + DEFAULT_B_CURRENT, + DEFAULT_B_HOLD_CURRENT, + DEFAULT_B_MICROSTEPS, + DEFAULT_B_STALLGUARD }, + { "C", + DEFAULT_C_STEPS_PER_MM, + DEFAULT_C_MAX_RATE, + DEFAULT_C_ACCELERATION, + DEFAULT_C_MAX_TRAVEL, + DEFAULT_C_CURRENT, + DEFAULT_C_HOLD_CURRENT, + DEFAULT_C_MICROSTEPS, + DEFAULT_C_STALLGUARD } }; + +// Construct e.g. X_MAX_RATE from axisName "X" and tail "_MAX_RATE" +// in dynamically allocated memory that will not be freed. + +static const char* makename(const char* axisName, const char* tail) { + char* retval = (char*)malloc(strlen(axisName) + strlen(tail) + 2); + + strcpy(retval, axisName); + strcat(retval, "/"); + return strcat(retval, tail); +} + +static bool checkStartupLine(char* value) { + if (sys.state != STATE_IDLE) + return STATUS_IDLE_ERROR; + return gc_execute_line(value, CLIENT_SERIAL) == 0; +} + +static bool checkStallguard(char* value) { + motorSettingChanged = true; + return true; +} + +static bool checkMicrosteps(char* value) { + motorSettingChanged = true; + return true; +} + +static bool checkRunCurrent(char* value) { + motorSettingChanged = true; + return true; +} + +static bool checkHoldcurrent(char* value) { + motorSettingChanged = true; + return true; +} + +static bool checkStallguardDebugMask(char* val) { + motorSettingChanged = true; + return true; +} + +// Generates a string like "122" from axisNum 2 and base 120 +static const char* makeGrblName(int axisNum, int base) { + // To omit A,B,C axes: + // if (axisNum > 2) return NULL; + char buf[4]; + snprintf(buf, 4, "%d", axisNum + base); + char* retval = (char*)malloc(strlen(buf)); + return strcpy(retval, buf); +} + +void make_settings() { + Setting::init(); + + // Create the axis settings in the order that people are + // accustomed to seeing. + int axis; + axis_defaults_t* def; + for (axis = 0; axis < N_AXIS; axis++) { + def = &axis_defaults[axis]; + axis_settings[axis] = new AxisSettings(def->name); + } + x_axis_settings = axis_settings[X_AXIS]; + y_axis_settings = axis_settings[Y_AXIS]; + z_axis_settings = axis_settings[Z_AXIS]; + a_axis_settings = axis_settings[A_AXIS]; + b_axis_settings = axis_settings[B_AXIS]; + c_axis_settings = axis_settings[C_AXIS]; + for (axis = N_AXIS - 1; axis >= 0; axis--) { + def = &axis_defaults[axis]; + auto setting = new IntSetting( + EXTENDED, WG, makeGrblName(axis, 170), makename(def->name, "StallGuard"), def->stallguard, -64, 63, checkStallguard); + setting->setAxis(axis); + axis_settings[axis]->stallguard = setting; + } + for (axis = N_AXIS - 1; axis >= 0; axis--) { + def = &axis_defaults[axis]; + auto setting = new IntSetting( + EXTENDED, WG, makeGrblName(axis, 160), makename(def->name, "Microsteps"), def->microsteps, 0, 256, checkMicrosteps); + setting->setAxis(axis); + axis_settings[axis]->microsteps = setting; + } + for (axis = N_AXIS - 1; axis >= 0; axis--) { + def = &axis_defaults[axis]; + auto setting = new FloatSetting( + EXTENDED, WG, makeGrblName(axis, 150), makename(def->name, "Current/Hold"), def->hold_current, 0.05, 20.0, checkHoldcurrent); // Amps + setting->setAxis(axis); + axis_settings[axis]->hold_current = setting; + } + for (axis = N_AXIS - 1; axis >= 0; axis--) { + def = &axis_defaults[axis]; + auto setting = new FloatSetting( + EXTENDED, WG, makeGrblName(axis, 140), makename(def->name, "Current/Run"), def->run_current, 0.0, 20.0, checkRunCurrent); // Amps + setting->setAxis(axis); + axis_settings[axis]->run_current = setting; + } + for (axis = 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); + setting->setAxis(axis); + axis_settings[axis]->max_travel = setting; + } + for (axis = N_AXIS - 1; axis >= 0; axis--) { + def = &axis_defaults[axis]; + auto setting = + new FloatSetting(GRBL, WG, makeGrblName(axis, 120), makename(def->name, "Acceleration"), def->acceleration, 1.0, 100000.0); + setting->setAxis(axis); + axis_settings[axis]->acceleration = setting; + } + for (axis = N_AXIS - 1; axis >= 0; axis--) { + def = &axis_defaults[axis]; + auto setting = new FloatSetting(GRBL, WG, makeGrblName(axis, 110), makename(def->name, "MaxRate"), def->max_rate, 1.0, 100000.0); + setting->setAxis(axis); + axis_settings[axis]->max_rate = setting; + } + for (axis = N_AXIS - 1; axis >= 0; axis--) { + def = &axis_defaults[axis]; + auto setting = + new FloatSetting(GRBL, WG, makeGrblName(axis, 100), makename(def->name, "StepsPerMm"), def->steps_per_mm, 1.0, 100000.0); + setting->setAxis(axis); + axis_settings[axis]->steps_per_mm = setting; + } + + // Spindle Settings + spindle_pwm_max_value = new FloatSetting(EXTENDED, WG, "36", "Spindle/PWM/Max", DEFAULT_SPINDLE_MAX_VALUE, 0.0, 100.0); + spindle_pwm_min_value = new FloatSetting(EXTENDED, WG, "35", "Spindle/PWM/Min", DEFAULT_SPINDLE_MIN_VALUE, 0.0, 100.0); + spindle_pwm_off_value = + new FloatSetting(EXTENDED, WG, "34", "Spindle/PWM/Off", DEFAULT_SPINDLE_OFF_VALUE, 0.0, 100.0); // these are percentages + // IntSetting spindle_pwm_bit_precision(EXTENDED, WG, "Spindle/PWM/Precision", DEFAULT_SPINDLE_BIT_PRECISION, 1, 16); + spindle_pwm_freq = new FloatSetting(EXTENDED, WG, "33", "Spindle/PWM/Frequency", DEFAULT_SPINDLE_FREQ, 0, 100000); + spindle_output_invert = new FlagSetting(GRBL, WG, NULL, "Spindle/PWM/Invert", DEFAULT_INVERT_SPINDLE_OUTPUT_PIN); + + spindle_delay_spinup = new FloatSetting(EXTENDED, WG, NULL, "Spindle/Delay/SpinUp", DEFAULT_SPINDLE_DELAY_SPINUP, 0, 30); + spindle_delay_spindown = new FloatSetting(EXTENDED, WG, NULL, "Spindle/Delay/SpinDown", DEFAULT_SPINDLE_DELAY_SPINUP, 0, 30); + + spindle_enbl_off_with_zero_speed = + new FlagSetting(GRBL, WG, NULL, "Spindle/Enable/OffWithSpeed", DEFAULT_SPINDLE_ENABLE_OFF_WITH_ZERO_SPEED); + + spindle_enable_invert = new FlagSetting(GRBL, WG, NULL, "Spindle/Enable/Invert", DEFAULT_INVERT_SPINDLE_ENABLE_PIN); + + // GRBL Non-numbered settings + startup_line_0 = new StringSetting(GRBL, WG, "N0", "GCode/Line0", "", checkStartupLine); + startup_line_1 = new StringSetting(GRBL, WG, "N1", "GCode/Line1", "", checkStartupLine); + + // GRBL Numbered Settings + laser_mode = new FlagSetting(GRBL, WG, "32", "GCode/LaserMode", DEFAULT_LASER_MODE); + // TODO Settings - also need to call my_spindle->init(); + rpm_min = new FloatSetting(GRBL, WG, "31", "GCode/MinS", DEFAULT_SPINDLE_RPM_MIN, 0, 100000); + rpm_max = new FloatSetting(GRBL, WG, "30", "GCode/MaxS", DEFAULT_SPINDLE_RPM_MAX, 0, 100000); + + homing_pulloff = new FloatSetting(GRBL, WG, "27", "Homing/Pulloff", DEFAULT_HOMING_PULLOFF, 0, 1000); + homing_debounce = new FloatSetting(GRBL, WG, "26", "Homing/Debounce", DEFAULT_HOMING_DEBOUNCE_DELAY, 0, 10000); + homing_seek_rate = new FloatSetting(GRBL, WG, "25", "Homing/Seek", DEFAULT_HOMING_SEEK_RATE, 0, 10000); + homing_feed_rate = new FloatSetting(GRBL, WG, "24", "Homing/Feed", DEFAULT_HOMING_FEED_RATE, 0, 10000); + + // TODO Settings - need to call st_generate_step_invert_masks() + homing_dir_mask = new AxisMaskSetting(GRBL, WG, "23", "Homing/DirInvert", DEFAULT_HOMING_DIR_MASK); + + // TODO Settings - need to call limits_init(); + homing_enable = new FlagSetting(GRBL, WG, "22", "Homing/Enable", DEFAULT_HOMING_ENABLE); + // TODO Settings - need to check for HOMING_ENABLE + hard_limits = new FlagSetting(GRBL, WG, "21", "Limits/Hard", DEFAULT_HARD_LIMIT_ENABLE); + soft_limits = new FlagSetting(GRBL, WG, "20", "Limits/Soft", DEFAULT_SOFT_LIMIT_ENABLE, NULL); + + report_inches = new FlagSetting(GRBL, WG, "13", "Report/Inches", DEFAULT_REPORT_INCHES); + // TODO Settings - also need to clear, but not set, soft_limits + arc_tolerance = new FloatSetting(GRBL, WG, "12", "GCode/ArcTolerance", DEFAULT_ARC_TOLERANCE, 0, 1); + junction_deviation = new FloatSetting(GRBL, WG, "11", "GCode/JunctionDeviation", DEFAULT_JUNCTION_DEVIATION, 0, 10); + status_mask = new IntSetting(GRBL, WG, "10", "Report/Status", DEFAULT_STATUS_REPORT_MASK, 0, 2); + + probe_invert = new FlagSetting(GRBL, WG, "6", "Probe/Invert", DEFAULT_INVERT_PROBE_PIN); + limit_invert = new FlagSetting(GRBL, WG, "5", "Limits/Invert", DEFAULT_INVERT_LIMIT_PINS); + step_enable_invert = new FlagSetting(GRBL, WG, "4", "Stepper/EnableInvert", DEFAULT_INVERT_ST_ENABLE); + dir_invert_mask = new AxisMaskSetting(GRBL, WG, "3", "Stepper/DirInvert", DEFAULT_DIRECTION_INVERT_MASK); + step_invert_mask = new AxisMaskSetting(GRBL, WG, "2", "Stepper/StepInvert", DEFAULT_STEPPING_INVERT_MASK); + stepper_idle_lock_time = new IntSetting(GRBL, WG, "1", "Stepper/IdleTime", DEFAULT_STEPPER_IDLE_LOCK_TIME, 0, 255); + pulse_microseconds = new IntSetting(GRBL, WG, "0", "Stepper/Pulse", DEFAULT_STEP_PULSE_MICROSECONDS, 3, 1000); + spindle_type = new EnumSetting(NULL, EXTENDED, WG, NULL, "Spindle/Type", SPINDLE_TYPE, &spindleTypes); + stallguard_debug_mask = new AxisMaskSetting(EXTENDED, WG, NULL, "Report/StallGuard", 0, checkStallguardDebugMask); +} diff --git a/Grbl_Esp32/SettingsDefinitions.h b/Grbl_Esp32/src/SettingsDefinitions.h similarity index 87% rename from Grbl_Esp32/SettingsDefinitions.h rename to Grbl_Esp32/src/SettingsDefinitions.h index 0e9de442..511f137f 100644 --- a/Grbl_Esp32/SettingsDefinitions.h +++ b/Grbl_Esp32/src/SettingsDefinitions.h @@ -1,4 +1,5 @@ #pragma once + extern bool motorSettingChanged; extern AxisSettings* x_axis_settings; @@ -29,7 +30,7 @@ extern FlagSetting* hard_limits; extern FlagSetting* homing_enable; extern FlagSetting* laser_mode; -extern IntSetting* status_mask; +extern IntSetting* status_mask; extern FloatSetting* junction_deviation; extern FloatSetting* arc_tolerance; @@ -42,11 +43,14 @@ extern FloatSetting* rpm_max; extern FloatSetting* rpm_min; extern FloatSetting* spindle_delay_spinup; extern FloatSetting* spindle_delay_spindown; +extern FlagSetting* spindle_enbl_off_with_zero_speed; +extern FlagSetting* spindle_enable_invert; +extern FlagSetting* spindle_output_invert; extern FloatSetting* spindle_pwm_off_value; extern FloatSetting* spindle_pwm_min_value; extern FloatSetting* spindle_pwm_max_value; -extern IntSetting* spindle_pwm_bit_precision; +extern IntSetting* spindle_pwm_bit_precision; extern EnumSetting* spindle_type; diff --git a/Grbl_Esp32/settings.cpp b/Grbl_Esp32/src/SettingsStorage.cpp similarity index 94% rename from Grbl_Esp32/settings.cpp rename to Grbl_Esp32/src/SettingsStorage.cpp index 94c58333..cec5d164 100644 --- a/Grbl_Esp32/settings.cpp +++ b/Grbl_Esp32/src/SettingsStorage.cpp @@ -1,5 +1,5 @@ /* - settings.c - eeprom configuration handling + SettingsStorage.cpp - EEPROM configuration handling Part of Grbl Copyright (c) 2011-2016 Sungeun K. Jeon for Gnea Research LLC @@ -22,13 +22,12 @@ along with Grbl. If not, see . */ -#include "grbl.h" - +#include "Grbl.h" // Read selected coordinate data from EEPROM. Updates pointed coord_data value. uint8_t settings_read_coord_data(uint8_t coord_select, float* coord_data) { uint32_t addr = coord_select * (sizeof(float) * N_AXIS + 1) + EEPROM_ADDR_PARAMETERS; - if (!(memcpy_from_eeprom_with_checksum((char*)coord_data, addr, sizeof(float)*N_AXIS))) { + if (!(memcpy_from_eeprom_with_checksum((char*)coord_data, addr, sizeof(float) * N_AXIS))) { // Reset with default zero vector clear_vector_float(coord_data); settings_write_coord_data(coord_select, coord_data); @@ -43,7 +42,7 @@ void settings_write_coord_data(uint8_t coord_select, float* coord_data) { protocol_buffer_synchronize(); #endif uint32_t addr = coord_select * (sizeof(float) * N_AXIS + 1) + EEPROM_ADDR_PARAMETERS; - memcpy_to_eeprom_with_checksum(addr, (char*)coord_data, sizeof(float)*N_AXIS); + memcpy_to_eeprom_with_checksum(addr, (char*)coord_data, sizeof(float) * N_AXIS); } // Method to store build info into EEPROM @@ -57,7 +56,7 @@ void settings_store_build_info(const char* line) { uint8_t settings_read_build_info(char* line) { if (!(memcpy_from_eeprom_with_checksum((char*)line, EEPROM_ADDR_BUILD_INFO, LINE_BUFFER_SIZE))) { // Reset line with default value - line[0] = 0; // Empty line + line[0] = 0; // Empty line settings_store_build_info(line); return (false); } @@ -74,4 +73,3 @@ uint8_t get_step_pin_mask(uint8_t axis_idx) { uint8_t get_direction_pin_mask(uint8_t axis_idx) { return bit(axis_idx); } - diff --git a/Grbl_Esp32/settings.h b/Grbl_Esp32/src/SettingsStorage.h similarity index 72% rename from Grbl_Esp32/settings.h rename to Grbl_Esp32/src/SettingsStorage.h index 4e46110c..b0ffcb77 100644 --- a/Grbl_Esp32/settings.h +++ b/Grbl_Esp32/src/SettingsStorage.h @@ -1,5 +1,7 @@ +#pragma once + /* - settings.h - eeprom configuration handling + SettingsStorage.h - eeprom configuration handling Part of Grbl Copyright (c) 2011-2016 Sungeun K. Jeon for Gnea Research LLC @@ -22,43 +24,39 @@ along with Grbl. If not, see . */ -#ifndef settings_h -#define settings_h - -#include "grbl.h" - +#include "Grbl.h" // Define status reporting boolean enable bit flags in status_report_mask -#define BITFLAG_RT_STATUS_POSITION_TYPE bit(0) -#define BITFLAG_RT_STATUS_BUFFER_STATE bit(1) +#define BITFLAG_RT_STATUS_POSITION_TYPE bit(0) +#define BITFLAG_RT_STATUS_BUFFER_STATE bit(1) // Define settings restore bitflags. #define SETTINGS_RESTORE_DEFAULTS bit(0) #define SETTINGS_RESTORE_PARAMETERS bit(1) #define SETTINGS_RESTORE_STARTUP_LINES bit(2) #define SETTINGS_RESTORE_BUILD_INFO bit(3) -#define SETTINGS_RESTORE_WIFI_SETTINGS bit(4) +#define SETTINGS_RESTORE_WIFI_SETTINGS bit(4) #ifndef SETTINGS_RESTORE_ALL - #define SETTINGS_RESTORE_ALL 0xFF // All bitflags +# define SETTINGS_RESTORE_ALL 0xFF // All bitflags #endif // Define EEPROM memory address location values for Grbl settings and parameters // NOTE: The Atmega328p has 1KB EEPROM. The upper half is reserved for parameters and // the startup script. The lower half contains the global settings and space for future // developments. -#define EEPROM_SIZE 1024U -#define EEPROM_ADDR_PARAMETERS 512U -#define EEPROM_ADDR_BUILD_INFO 942U +#define EEPROM_SIZE 1024U +#define EEPROM_ADDR_PARAMETERS 512U +#define EEPROM_ADDR_BUILD_INFO 942U // Define EEPROM address indexing for coordinate parameters -#define N_COORDINATE_SYSTEM 6 // Number of supported work coordinate systems (from index 1) -#define SETTING_INDEX_NCOORD N_COORDINATE_SYSTEM+1 // Total number of system stored (from index 0) +#define N_COORDINATE_SYSTEM 6 // Number of supported work coordinate systems (from index 1) +#define SETTING_INDEX_NCOORD N_COORDINATE_SYSTEM + 1 // Total number of system stored (from index 0) // NOTE: Work coordinate indices are (0=G54, 1=G55, ... , 6=G59) -#define SETTING_INDEX_G28 N_COORDINATE_SYSTEM // Home position 1 -#define SETTING_INDEX_G30 N_COORDINATE_SYSTEM+1 // Home position 2 +#define SETTING_INDEX_G28 N_COORDINATE_SYSTEM // Home position 1 +#define SETTING_INDEX_G30 N_COORDINATE_SYSTEM + 1 // Home position 2 // #define SETTING_INDEX_G92 N_COORDINATE_SYSTEM+2 // Coordinate offset (G92.2,G92.3 not supported) -#define USER_SETTING_COUNT 5 // for user to define for their machine +#define USER_SETTING_COUNT 5 // for user to define for their machine // Initialize the configuration subsystem (load settings from EEPROM) void settings_init(); @@ -66,7 +64,7 @@ void settings_restore(uint8_t restore_flag); void write_global_settings(); uint8_t settings_read_build_info(char* line); -void settings_store_build_info(const char* line); +void settings_store_build_info(const char* line); // Writes selected coordinate data to EEPROM void settings_write_coord_data(uint8_t coord_select, float* coord_data); @@ -79,6 +77,3 @@ uint8_t get_step_pin_mask(uint8_t i); // Returns the direction pin mask according to Grbl's internal axis numbering uint8_t get_direction_pin_mask(uint8_t i); - -#endif - diff --git a/Grbl_Esp32/solenoid_pen.cpp b/Grbl_Esp32/src/SolenoidPen.cpp similarity index 64% rename from Grbl_Esp32/solenoid_pen.cpp rename to Grbl_Esp32/src/SolenoidPen.cpp index 822f6559..7683b5a9 100644 --- a/Grbl_Esp32/solenoid_pen.cpp +++ b/Grbl_Esp32/src/SolenoidPen.cpp @@ -1,5 +1,5 @@ /* - solenoid_pen.cpp + SolenoidPen.cpp Part of Grbl_ESP32 copyright (c) 2018 - Bart Dring This file was modified for use on the ESP32 @@ -19,7 +19,7 @@ along with Grbl. If not, see . */ -#include "grbl.h" +#include "Grbl.h" #ifdef USE_PEN_SOLENOID @@ -28,28 +28,28 @@ int8_t solenoid_pwm_chan_num; static TaskHandle_t solenoidSyncTaskHandle = 0; // used to delay turn on -bool solenoid_pen_enable; +bool solenoid_pen_enable; uint16_t solenoide_hold_count; void solenoid_init() { grbl_send(CLIENT_SERIAL, "[MSG:Solenoid Mode]\r\n"); // startup message //validate_servo_settings(true); // display any calibration errors - solenoid_pen_enable = false; // start delay has not completed yet. - solenoide_hold_count = 0; // initialize + solenoid_pen_enable = false; // start delay has not completed yet. + solenoide_hold_count = 0; // initialize // setup PWM channel solenoid_pwm_chan_num = sys_get_next_PWM_chan_num(); ledcSetup(solenoid_pwm_chan_num, SOLENOID_PWM_FREQ, SOLENOID_PWM_RES_BITS); ledcAttachPin(SOLENOID_PEN_PIN, solenoid_pwm_chan_num); - solenoid_disable(); // start it it off + solenoid_disable(); // start it it off // setup a task that will calculate the determine and set the servo position - xTaskCreatePinnedToCore(solenoidSyncTask, // task - "solenoidSyncTask", // name for task - 4096, // size of task stack - NULL, // parameters - 1, // priority + xTaskCreatePinnedToCore(solenoidSyncTask, // task + "solenoidSyncTask", // name for task + 4096, // size of task stack + NULL, // parameters + 1, // priority &solenoidSyncTaskHandle, - 0 // core - ); + 0 // core + ); } // turn off the PWM (0 duty) @@ -59,20 +59,20 @@ void solenoid_disable() { // this is the task void solenoidSyncTask(void* pvParameters) { - int32_t current_position[N_AXIS]; // copy of current location - float m_pos[N_AXIS]; // machine position in mm - TickType_t xLastWakeTime; - const TickType_t xSolenoidFrequency = SOLENOID_TIMER_INT_FREQ; // in ticks (typically ms) - uint16_t solenoid_delay_counter = 0; - xLastWakeTime = xTaskGetTickCount(); // Initialise the xLastWakeTime variable with the current time. - while (true) { // don't ever return from this or the task dies + int32_t current_position[N_AXIS]; // copy of current location + float m_pos[N_AXIS]; // machine position in mm + TickType_t xLastWakeTime; + const TickType_t xSolenoidFrequency = SOLENOID_TIMER_INT_FREQ; // in ticks (typically ms) + uint16_t solenoid_delay_counter = 0; + xLastWakeTime = xTaskGetTickCount(); // Initialise the xLastWakeTime variable with the current time. + while (true) { // don't ever return from this or the task dies if (!solenoid_pen_enable) { solenoid_delay_counter++; solenoid_pen_enable = (solenoid_delay_counter > SOLENOID_TURNON_DELAY); } else { - memcpy(current_position, sys_position, sizeof(sys_position)); // get current position in step - system_convert_array_steps_to_mpos(m_pos, current_position); // convert to millimeters - calc_solenoid(m_pos[Z_AXIS]); // calculate kinematics and move the servos + memcpy(current_position, sys_position, sizeof(sys_position)); // get current position in step + system_convert_array_steps_to_mpos(m_pos, current_position); // convert to millimeters + calc_solenoid(m_pos[Z_AXIS]); // calculate kinematics and move the servos } vTaskDelayUntil(&xLastWakeTime, xSolenoidFrequency); } @@ -81,11 +81,11 @@ void solenoidSyncTask(void* pvParameters) { // calculate and set the PWM value for the servo void calc_solenoid(float penZ) { uint32_t solenoid_pen_pulse_len; - if (!solenoid_pen_enable) // only proceed if startup delay as expired + if (!solenoid_pen_enable) // only proceed if startup delay as expired return; if (penZ < 0 && (sys.state != STATE_ALARM)) { // alarm also makes it go up - solenoide_hold_count = 0; // reset this count - solenoid_pen_pulse_len = 0; // + solenoide_hold_count = 0; // reset this count + solenoid_pen_pulse_len = 0; // } else { if (solenoide_hold_count < SOLENOID_PULSE_LEN_HOLD) { solenoid_pen_pulse_len = SOLENOID_PULSE_LEN_UP; @@ -105,4 +105,3 @@ void calc_solenoid(float penZ) { } #endif - diff --git a/Grbl_Esp32/solenoid_pen.h b/Grbl_Esp32/src/SolenoidPen.h similarity index 69% rename from Grbl_Esp32/solenoid_pen.h rename to Grbl_Esp32/src/SolenoidPen.h index 49f64911..32332c74 100644 --- a/Grbl_Esp32/solenoid_pen.h +++ b/Grbl_Esp32/src/SolenoidPen.h @@ -1,5 +1,7 @@ +#pragma once + /* - solenoid_pen.h + SolenoidPen.h Part of Grbl_ESP32 copyright (c) 2018 - Bart Dring This file was modified for use on the ESP32 @@ -33,39 +35,34 @@ */ #ifndef SOLENOID_PWM_FREQ - #define SOLENOID_PWM_FREQ 5000 +# define SOLENOID_PWM_FREQ 5000 #endif #ifndef SOLENOID_PWM_RES_BITS - #define SOLENOID_PWM_RES_BITS 8 +# define SOLENOID_PWM_RES_BITS 8 #endif #ifndef SOLENOID_TURNON_DELAY - #define SOLENOID_TURNON_DELAY (SOLENOID_TIMER_INT_FREQ/2) +# define SOLENOID_TURNON_DELAY (SOLENOID_TIMER_INT_FREQ / 2) #endif #ifndef SOLENOID_PULSE_LEN_UP - #define SOLENOID_PULSE_LEN_UP 255 +# define SOLENOID_PULSE_LEN_UP 255 #endif #ifndef SOLENOID_HOLD_DELAY - #define SOLENOID_HOLD_DELAY (SOLENOID_TIMER_INT_FREQ/2) // in task counts...after this delay power will change to hold level +# define SOLENOID_HOLD_DELAY (SOLENOID_TIMER_INT_FREQ / 2) // in task counts...after this delay power will change to hold level #endif #ifndef SOLENOID_PULSE_LEN_HOLD - #define SOLENOID_PULSE_LEN_HOLD 80 // solenoid hold level ... typically a lower value to prevent overheating +# define SOLENOID_PULSE_LEN_HOLD 80 // solenoid hold level ... typically a lower value to prevent overheating #endif #ifndef SOLENOID_TIMER_INT_FREQ - #define SOLENOID_TIMER_INT_FREQ 50 +# define SOLENOID_TIMER_INT_FREQ 50 #endif -#ifndef solenoid_h - #define solenoid_h - - void solenoid_init(); - void solenoid_disable(); - void solenoidSyncTask(void* pvParameters); - void calc_solenoid(float penZ); - -#endif +void solenoid_init(); +void solenoid_disable(); +void solenoidSyncTask(void* pvParameters); +void calc_solenoid(float penZ); diff --git a/Grbl_Esp32/src/Spindles/10vSpindle.cpp b/Grbl_Esp32/src/Spindles/10vSpindle.cpp new file mode 100644 index 00000000..14cb841c --- /dev/null +++ b/Grbl_Esp32/src/Spindles/10vSpindle.cpp @@ -0,0 +1,165 @@ +/* + 10vSpindle.cpp + + + This is basically a PWM spindle with some changes, so a separate forward and + reverse signal can be sent. + + The direction pins will act as enables for the 2 directions. There is usually + a min RPM with VFDs, that speed will remain even if speed is 0. You + must turn off both direction pins when enable is off. + + + Part of Grbl_ESP32 + 2020 - Bart Dring + + 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 . +*/ +#include "10vSpindle.h" + +namespace Spindles { + void _10v::init() { + get_pins_and_settings(); // these gets the standard PWM settings, but many need to be changed for BESC + + // a couple more pins not inherited from PWM Spindle +#ifdef SPINDLE_FORWARD_PIN + _forward_pin = SPINDLE_FORWARD_PIN; +#else + _forward_pin = UNDEFINED_PIN; +#endif + +#ifdef SPINDLE_REVERSE_PIN + _reverse_pin = SPINDLE_REVERSE_PIN; +#else + _reverse_pin = UNDEFINED_PIN; +#endif + + if (_output_pin == UNDEFINED_PIN) { + grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Warning: BESC output pin not defined"); + return; // We cannot continue without the output pin + } + + ledcSetup(_pwm_chan_num, (double)_pwm_freq, _pwm_precision); // setup the channel + ledcAttachPin(_output_pin, _pwm_chan_num); // attach the PWM to the pin + + pinMode(_enable_pin, OUTPUT); + pinMode(_direction_pin, OUTPUT); + pinMode(_forward_pin, OUTPUT); + pinMode(_reverse_pin, OUTPUT); + + set_rpm(0); + + config_message(); + + is_reversable = true; // these VFDs are always reversable + use_delays = true; + } + + // prints the startup message of the spindle config + void _10v::config_message() { + grbl_msg_sendf(CLIENT_SERIAL, + MSG_LEVEL_INFO, + "0-10V spindle Out:%s Enbl:%s, Dir:%s, Fwd:%s, Rev:%s, Freq:%dHz Res:%dbits", + pinName(_output_pin).c_str(), + pinName(_enable_pin).c_str(), + pinName(_direction_pin).c_str(), + pinName(_forward_pin).c_str(), + pinName(_reverse_pin).c_str(), + _pwm_freq, + _pwm_precision); + } + + uint32_t _10v::set_rpm(uint32_t rpm) { + uint32_t pwm_value; + + if (_output_pin == UNDEFINED_PIN) + return rpm; + + // apply speed overrides + rpm = rpm * sys.spindle_speed_ovr / 100; // Scale by spindle speed override value (percent) + + // apply limits limits + if ((_min_rpm >= _max_rpm) || (rpm >= _max_rpm)) + rpm = _max_rpm; + else if (rpm != 0 && rpm <= _min_rpm) + rpm = _min_rpm; + sys.spindle_speed = rpm; + + // determine the pwm value + if (rpm == 0) + pwm_value = _pwm_off_value; + else + pwm_value = map_uint32_t(rpm, _min_rpm, _max_rpm, _pwm_min_value, _pwm_max_value); + + set_output(pwm_value); + return rpm; + } + /* + void _10v::set_state(uint8_t state, uint32_t rpm) { + if (sys.abort) + return; // Block during abort. + + if (state == SPINDLE_DISABLE) { // Halt or set spindle direction and rpm. + sys.spindle_speed = 0; + stop(); + } else { + set_dir_pin(state == SPINDLE_ENABLE_CW); + set_rpm(rpm); + } + + set_enable_pin(state != SPINDLE_DISABLE); + + sys.report_ovr_counter = 0; // Set to report change immediately + } + + */ + + uint8_t _10v::get_state() { + if (_current_pwm_duty == 0 || _output_pin == UNDEFINED_PIN) + return (SPINDLE_STATE_DISABLE); + if (_direction_pin != UNDEFINED_PIN) + return digitalRead(_direction_pin) ? SPINDLE_STATE_CW : SPINDLE_STATE_CCW; + return (SPINDLE_STATE_CW); + } + + void _10v::stop() { + // inverts are delt with in methods + set_enable_pin(false); + set_output(_pwm_off_value); + } + + void _10v::set_enable_pin(bool enable) { + //grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Spindle::_10v::set_enable_pin"); + if (_off_with_zero_speed && sys.spindle_speed == 0) + enable = false; + + + if (spindle_enable_invert->get()) + enable = !enable; + + digitalWrite(_enable_pin, enable); + + // turn off anything that acts like an enable + if (!enable) { + digitalWrite(_direction_pin, enable); + digitalWrite(_forward_pin, enable); + digitalWrite(_reverse_pin, enable); + } + } + + void _10v::set_dir_pin(bool Clockwise) { + //grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Spindle::_10v::set_dir_pin"); + digitalWrite(_direction_pin, Clockwise); + digitalWrite(_forward_pin, Clockwise); + digitalWrite(_reverse_pin, !Clockwise); + } +} diff --git a/Grbl_Esp32/src/Spindles/10vSpindle.h b/Grbl_Esp32/src/Spindles/10vSpindle.h new file mode 100644 index 00000000..dc0e5b22 --- /dev/null +++ b/Grbl_Esp32/src/Spindles/10vSpindle.h @@ -0,0 +1,58 @@ +#pragma once + +/* + 10vSpindle.h + + This is basically a PWM spindle with some changes, so a separate forward and + reverse signal can be sent. + + The direction pins will act as enables for the 2 directions. There is usually + a min RPM with VFDs, that speed will remain even if speed is 0. You + must turn off both direction pins when enable is off. + + + Part of Grbl_ESP32 + 2020 - Bart Dring + + 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 . +*/ + +#include "PWMSpindle.h" + +namespace Spindles { + class _10v : public PWM { + public: + _10v() = default; + + _10v(const _10v&) = delete; + _10v(_10v&&) = delete; + _10v& operator=(const _10v&) = delete; + _10v& operator=(_10v&&) = delete; + + void init() override; + void config_message() override; + uint32_t set_rpm(uint32_t rpm) override; + //void set_state(uint8_t state, uint32_t rpm); + + uint8_t get_state() override; + void stop() override; + + virtual ~_10v() {} + + uint8_t _forward_pin; + uint8_t _reverse_pin; + + protected: + void set_enable_pin(bool enable_pin) override; + void set_dir_pin(bool Clockwise) override; + }; +} diff --git a/Grbl_Esp32/src/Spindles/BESCSpindle.cpp b/Grbl_Esp32/src/Spindles/BESCSpindle.cpp new file mode 100644 index 00000000..4b822033 --- /dev/null +++ b/Grbl_Esp32/src/Spindles/BESCSpindle.cpp @@ -0,0 +1,122 @@ +/* + BESCSpindle.cpp + + This a special type of PWM spindle for RC type Brushless DC Speed + controllers. They use a short pulse for off and a longer pulse for + full on. The pulse is always a small portion of the full cycle. + Some BESCs have a special turn on procedure. This may be a one time + procedure or must be done every time. The user must do that via gcode. + + Part of Grbl_ESP32 + 2020 - Bart Dring + + 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 . + + Important ESC Settings + 50 Hz this is a typical frequency for an ESC + Some ESCs can handle higher frequencies, but there is no advantage to changing it. + + Determine the typical min and max pulse length of your ESC + BESC_MIN_PULSE_SECS is typically 1ms (0.001 sec) or less + BESC_MAX_PULSE_SECS is typically 2ms (0.002 sec) or more + +*/ +#include "BESCSpindle.h" + +// don't change these +#define BESC_PWM_FREQ 50.0f // Hz +#define BESC_PWM_BIT_PRECISION 16 // bits +#define BESC_PULSE_PERIOD (1.0 / BESC_PWM_FREQ) + +// Ok to tweak. These are the pulse lengths in seconds +// #define them in your machine definition file if you want different values +#ifndef BESC_MIN_PULSE_SECS +# define BESC_MIN_PULSE_SECS 0.0009f // in seconds +#endif + +#ifndef BESC_MAX_PULSE_SECS +# define BESC_MAX_PULSE_SECS 0.0022f // in seconds +#endif + +//calculations...don't change +#define BESC_MIN_PULSE_CNT (uint16_t)(BESC_MIN_PULSE_SECS / BESC_PULSE_PERIOD * 65535.0) +#define BESC_MAX_PULSE_CNT (uint16_t)(BESC_MAX_PULSE_SECS / BESC_PULSE_PERIOD * 65535.0) + +namespace Spindles { + void BESC::init() { + get_pins_and_settings(); // these gets the standard PWM settings, but many need to be changed for BESC + + if (_output_pin == UNDEFINED_PIN) { + grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Warning: BESC output pin not defined"); + return; // We cannot continue without the output pin + } + + // override some settings to what is required for a BESC + _pwm_freq = (uint32_t)BESC_PWM_FREQ; + _pwm_precision = 16; + + // override these settings + _pwm_off_value = BESC_MIN_PULSE_CNT; + _pwm_min_value = _pwm_off_value; + _pwm_max_value = BESC_MAX_PULSE_CNT; + + ledcSetup(_pwm_chan_num, (double)_pwm_freq, _pwm_precision); // setup the channel + ledcAttachPin(_output_pin, _pwm_chan_num); // attach the PWM to the pin + + pinMode(_enable_pin, OUTPUT); + + set_rpm(0); + + use_delays = true; + + config_message(); + } + + // prints the startup message of the spindle config + void BESC::config_message() { + grbl_msg_sendf(CLIENT_SERIAL, + MSG_LEVEL_INFO, + "BESC spindle on Pin:%s Min:%0.2fms Max:%0.2fms Freq:%dHz Res:%dbits", + pinName(_output_pin).c_str(), + BESC_MIN_PULSE_SECS * 1000.0, // convert to milliseconds + BESC_MAX_PULSE_SECS * 1000.0, // convert to milliseconds + _pwm_freq, + _pwm_precision); + } + + uint32_t BESC::set_rpm(uint32_t rpm) { + uint32_t pwm_value; + + if (_output_pin == UNDEFINED_PIN) + return rpm; + + // apply speed overrides + rpm = rpm * sys.spindle_speed_ovr / 100; // Scale by spindle speed override value (percent) + + // apply limits limits + if ((_min_rpm >= _max_rpm) || (rpm >= _max_rpm)) + rpm = _max_rpm; + else if (rpm != 0 && rpm <= _min_rpm) + rpm = _min_rpm; + sys.spindle_speed = rpm; + + // determine the pwm value + if (rpm == 0) { + pwm_value = _pwm_off_value; + } else { + pwm_value = map_uint32_t(rpm, _min_rpm, _max_rpm, _pwm_min_value, _pwm_max_value); + } + + set_output(pwm_value); + return rpm; + } +} diff --git a/Grbl_Esp32/src/Spindles/BESCSpindle.h b/Grbl_Esp32/src/Spindles/BESCSpindle.h new file mode 100644 index 00000000..50f51bde --- /dev/null +++ b/Grbl_Esp32/src/Spindles/BESCSpindle.h @@ -0,0 +1,54 @@ +#pragma once + +/* + BESCSpindle.h + + This a special type of PWM spindle for RC type Brushless DC Speed + controllers. They use a short pulse for off and a longer pulse for + full on. The pulse is always a small portion of the full cycle. + Some BESCs have a special turn on procedure. This may be a one time + procedure or must be done every time. The user must do that via gcode. + + Part of Grbl_ESP32 + 2020 - Bart Dring + + 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 . + + Important ESC Settings + 50 Hz this is a typical frequency for an ESC + Some ESCs can handle higher frequencies, but there is no advantage to changing it. + + Determine the typical min and max pulse length of your ESC + BESC_MIN_PULSE_SECS is typically 1ms (0.001 sec) or less + BESC_MAX_PULSE_SECS is typically 2ms (0.002 sec) or more + +*/ + +#include "PWMSpindle.h" + +namespace Spindles { + class BESC : public PWM { + public: + BESC() = default; + + BESC(const BESC&) = delete; + BESC(BESC&&) = delete; + BESC& operator=(const BESC&) = delete; + BESC& operator=(BESC&&) = delete; + + void init() override; + void config_message() override; + uint32_t set_rpm(uint32_t rpm) override; + + virtual ~BESC() {} + }; +} diff --git a/Grbl_Esp32/src/Spindles/DacSpindle.cpp b/Grbl_Esp32/src/Spindles/DacSpindle.cpp new file mode 100644 index 00000000..11ff4b56 --- /dev/null +++ b/Grbl_Esp32/src/Spindles/DacSpindle.cpp @@ -0,0 +1,106 @@ +/* + DacSpindle.cpp + + This uses the Analog DAC in the ESP32 to generate a voltage + proportional to the GCode S value desired. Some spindle uses + a 0-5V or 0-10V value to control the spindle. You would use + an Op Amp type circuit to get from the 0.3.3V of the ESP32 to that voltage. + + Part of Grbl_ESP32 + 2020 - Bart Dring + + 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 . + +*/ +#include "DacSpindle.h" + +namespace Spindles { + // ======================================== Dac ====================================== + void Dac::init() { + get_pins_and_settings(); + + if (_output_pin == UNDEFINED_PIN) + return; + + _min_rpm = rpm_min->get(); + _max_rpm = rpm_max->get(); + _pwm_min_value = 0; // not actually PWM...DAC counts + _pwm_max_value = 255; // not actually PWM...DAC counts + _gpio_ok = true; + + if (_output_pin != GPIO_NUM_25 && _output_pin != GPIO_NUM_26) { // DAC can only be used on these pins + _gpio_ok = false; + grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "DAC spindle pin invalid GPIO_NUM_%d (pin 25 or 26 only)", _output_pin); + return; + } + + pinMode(_enable_pin, OUTPUT); + pinMode(_direction_pin, OUTPUT); + + is_reversable = (_direction_pin != UNDEFINED_PIN); + use_delays = true; + + config_message(); + } + + void Dac::config_message() { + grbl_msg_sendf(CLIENT_SERIAL, + MSG_LEVEL_INFO, + "DAC spindle Output:%s, Enbl:%s, Dir:%s, Res:8bits", + pinName(_output_pin).c_str(), + pinName(_enable_pin).c_str(), + pinName(_direction_pin).c_str()); + } + + uint32_t Dac::set_rpm(uint32_t rpm) { + if (_output_pin == UNDEFINED_PIN) + return rpm; + + uint32_t pwm_value; + + // apply overrides and limits + rpm = rpm * sys.spindle_speed_ovr / 100; // Scale by spindle speed override value (percent) + + // Calculate PWM register value based on rpm max/min settings and programmed rpm. + if ((_min_rpm >= _max_rpm) || (rpm >= _max_rpm)) { + // No PWM range possible. Set simple on/off spindle control pin state. + sys.spindle_speed = _max_rpm; + pwm_value = _pwm_max_value; + } else if (rpm <= _min_rpm) { + if (rpm == 0) { // S0 disables spindle + sys.spindle_speed = 0; + pwm_value = 0; + } else { // Set minimum PWM output + rpm = _min_rpm; + sys.spindle_speed = rpm; + pwm_value = 0; + grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Spindle RPM less than min RPM:%5.2f %d", rpm, pwm_value); + } + } else { + // Compute intermediate PWM value with linear spindle speed model. + // NOTE: A nonlinear model could be installed here, if required, but keep it VERY light-weight. + sys.spindle_speed = rpm; + + pwm_value = map_uint32_t(rpm, _min_rpm, _max_rpm, _pwm_min_value, _pwm_max_value); + } + + set_output(pwm_value); + + return rpm; + } + + void Dac::set_output(uint32_t duty) { + if (_gpio_ok) { + dacWrite(_output_pin, (uint8_t)duty); + } + } +} diff --git a/Grbl_Esp32/src/Spindles/DacSpindle.h b/Grbl_Esp32/src/Spindles/DacSpindle.h new file mode 100644 index 00000000..40c6831a --- /dev/null +++ b/Grbl_Esp32/src/Spindles/DacSpindle.h @@ -0,0 +1,51 @@ +#pragma once + +/* + DacSpindle.h + + This uses the Analog DAC in the ESP32 to generate a voltage + proportional to the GCode S value desired. Some spindle uses + a 0-5V or 0-10V value to control the spindle. You would use + an Op Amp type circuit to get from the 0.3.3V of the ESP32 to that voltage. + + Part of Grbl_ESP32 + 2020 - Bart Dring + + 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 . + +*/ + +#include "PWMSpindle.h" + +namespace Spindles { + // This uses one of the (2) DAC pins on ESP32 to output a voltage + class Dac : public PWM { + public: + Dac() = default; + + Dac(const Dac&) = delete; + Dac(Dac&&) = delete; + Dac& operator=(const Dac&) = delete; + Dac& operator=(Dac&&) = delete; + + void init() override; + void config_message() override; + uint32_t set_rpm(uint32_t rpm) override; + + virtual ~Dac() {} + + private: + bool _gpio_ok; // DAC is on a valid pin + protected: + void set_output(uint32_t duty); // sets DAC instead of PWM + }; +} diff --git a/Grbl_Esp32/src/Spindles/HuanyangSpindle.cpp b/Grbl_Esp32/src/Spindles/HuanyangSpindle.cpp new file mode 100644 index 00000000..1dd2aa41 --- /dev/null +++ b/Grbl_Esp32/src/Spindles/HuanyangSpindle.cpp @@ -0,0 +1,465 @@ +/* + HuanyangSpindle.cpp + + This is for a Huanyang VFD based spindle via RS485 Modbus. + Sorry for the lengthy comments, but finding the details on this + VFD was a PITA. I am just trying to help the next person. + + Part of Grbl_ESP32 + 2020 - Bart Dring + + 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. + + ============================================================================== + + If a user changes state or RPM level, the command to do that is sent. If + the command is not responded to a message is sent to serial that there was + a timeout. If the Grbl is in a critical state, an alarm will be generated and + the machine stopped. + + If there are no commands to execute, various status items will be polled. If there + is no response, it will behave as described above. It will stop any running jobs with + an alarm. + + =============================================================================== + + Protocol Details + + VFD frequencies are in Hz. Multiply by 60 for RPM + + before using spindle, VFD must be setup for RS485 and match your spindle + PD001 2 RS485 Control of run commands + PD002 2 RS485 Control of operating frequency + PD005 400 Maximum frequency Hz (Typical for spindles) + PD011 120 Min Speed (Recommend Aircooled=120 Water=100) + PD014 10 Acceleration time (Test to optimize) + PD015 10 Deceleration time (Test to optimize) + PD023 1 Reverse run enabled + PD142 3.7 Max current Amps (0.8kw=3.7 1.5kw=7.0, 2.2kw=??) + PD143 2 Poles most are 2 (I think this is only used for RPM calc from Hz) + PD163 1 RS485 Address: 1 (Typical. OK to change...see below) + PD164 1 RS485 Baud rate: 9600 (Typical. OK to change...see below) + PD165 3 RS485 Mode: RTU, 8N1 + + The official documentation of the RS485 is horrible. I had to piece it together from + a lot of different sources + + Manuals: https://github.com/RobertOlechowski/Huanyang_VFD/tree/master/Documentations/pdf + Reference: https://github.com/Smoothieware/Smoothieware/blob/edge/src/modules/tools/spindle/HuanyangSpindleControl.cpp + Refernece: https://gist.github.com/Bouni/803492ed0aab3f944066 + VFD settings: https://www.hobbytronics.co.za/Content/external/1159/Spindle_Settings.pdf + Spindle Talker 2 https://github.com/GilchristT/SpindleTalker2/releases + Python https://github.com/RobertOlechowski/Huanyang_VFD + + ========================================================================= + + Commands + ADDR CMD LEN DATA CRC + 0x01 0x03 0x01 0x01 0x31 0x88 Start spindle clockwise + 0x01 0x03 0x01 0x08 0xF1 0x8E Stop spindle + 0x01 0x03 0x01 0x11 0x30 0x44 Start spindle counter-clockwise + + Return values are + 0 = run + 1 = jog + 2 = r/f + 3 = running + 4 = jogging + 5 = r/f + 6 = Braking + 7 = Track start + + ========================================================================== + + Setting RPM + ADDR CMD LEN DATA CRC + 0x01 0x05 0x02 0x09 0xC4 0xBF 0x0F Write Frequency (0x9C4 = 2500 = 25.00HZ) + + Response is same as data sent + + ========================================================================== + + Status registers + Addr Read Len Reg DataH DataL CRC CRC + 0x01 0x04 0x03 0x00 0x00 0x00 CRC CRC // Set Frequency * 100 (25Hz = 2500) + 0x01 0x04 0x03 0x01 0x00 0x00 CRC CRC // Ouput Frequency * 100 + 0x01 0x04 0x03 0x02 0x00 0x00 CRC CRC // Ouput Amps * 10 + 0x01 0x04 0x03 0x03 0x00 0x00 0xF0 0x4E // Read RPM (example CRC shown) + 0x01 0x04 0x03 0x0 0x00 0x00 CRC CRC // DC voltage + 0x01 0x04 0x03 0x05 0x00 0x00 CRC CRC // AC voltage + 0x01 0x04 0x03 0x06 0x00 0x00 CRC CRC // Cont + 0x01 0x04 0x03 0x07 0x00 0x00 CRC CRC // VFD Temp + Message is returned with requested value = (DataH * 16) + DataL (see decimal offset above) + + TODO: + Move CRC Calc to task to free up main task + + +*/ +#include "HuanyangSpindle.h" + +#include + +#define HUANYANG_UART_PORT UART_NUM_2 // hard coded for this port right now +#define ECHO_TEST_CTS UART_PIN_NO_CHANGE // CTS pin is not used +#define HUANYANG_BUF_SIZE 127 +#define HUANYANG_QUEUE_SIZE 10 // numv\ber of commands that can be queued up. +#define RESPONSE_WAIT_TICKS 50 // how long to wait for a response +#define HUANYANG_MAX_MSG_SIZE 16 // more than enough for a modbus message +#define HUANYANG_POLL_RATE 200 // in milliseconds betwwen commands + +// OK to change these +// #define them in your machine definition file if you want different values +#ifndef HUANYANG_ADDR +# define HUANYANG_ADDR 0x01 +#endif + +#ifndef HUANYANG_BAUD_RATE +# define HUANYANG_BAUD_RATE 9600 // PD164 setting +#endif + +namespace Spindles { + // communication task and queue stuff + typedef struct { + uint8_t tx_length; + uint8_t rx_length; + bool critical; + char msg[HUANYANG_MAX_MSG_SIZE]; + } hy_command_t; + + typedef enum : uint8_t { + READ_SET_FREQ = 0, // The set frequency + READ_OUTPUT_FREQ = 1, // The current operating frequency + READ_OUTPUT_AMPS = 2, // + READ_SET_RPM = 3, // This is the last requested freq even in off mode + READ_DC_VOLTAGE = 4, // + READ_AC_VOLTAGE = 5, // + READ_CONT = 6, // counting value??? + READ_TEMP = 7, // + } read_register_t; + + QueueHandle_t hy_cmd_queue; + + static TaskHandle_t vfd_cmdTaskHandle = 0; + + bool hy_ok = true; + + // The communications task + void vfd_cmd_task(void* pvParameters) { + static bool unresponsive = false; // to pop off a message once each time it becomes unresponsive + uint8_t reg_item = 0x00; + hy_command_t next_cmd; + uint8_t rx_message[HUANYANG_MAX_MSG_SIZE]; + + while (true) { + if (xQueueReceive(hy_cmd_queue, &next_cmd, 0) == pdTRUE) { + uart_flush(HUANYANG_UART_PORT); + //report_hex_msg(next_cmd.msg, "Tx: ", next_cmd.tx_length); + uart_write_bytes(HUANYANG_UART_PORT, next_cmd.msg, next_cmd.tx_length); + + uint16_t read_length = uart_read_bytes(HUANYANG_UART_PORT, rx_message, next_cmd.rx_length, RESPONSE_WAIT_TICKS); + + if (read_length < next_cmd.rx_length) { + if (!unresponsive) { + grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Spindle RS485 Unresponsive %d", read_length); + if (next_cmd.critical) { + grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Critical Spindle RS485 Unresponsive"); + system_set_exec_alarm(EXEC_ALARM_SPINDLE_CONTROL); + } + unresponsive = true; + } + } else { + // success + unresponsive = false; + //report_hex_msg(rx_message, "Rx: ", read_length); + uint32_t ret_value = ((uint32_t)rx_message[4] << 8) + rx_message[5]; + //grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Item:%d value:%05d ", rx_message[3], ret_value); + } + + } else { + Huanyang::read_value(reg_item); // only this appears to work all the time. Other registers are flakey. + if (reg_item < 0x03) + reg_item++; + else { + reg_item = 0x00; + } + } + vTaskDelay(HUANYANG_POLL_RATE); // TODO: What is the best value here? + } + } + + // ================== Class methods ================================== + + void Huanyang::init() { + hy_ok = true; // initialize + + // fail if required items are not defined + if (!get_pins_and_settings()) { + grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Huanyang spindle errors"); + return; + } + + if (!_task_running) { // init can happen many times, we only want to start one task + hy_cmd_queue = xQueueCreate(HUANYANG_QUEUE_SIZE, sizeof(hy_command_t)); + xTaskCreatePinnedToCore(vfd_cmd_task, // task + "vfd_cmdTaskHandle", // name for task + 2048, // size of task stack + NULL, // parameters + 1, // priority + &vfd_cmdTaskHandle, + 0 // core + ); + _task_running = true; + } + + // this allows us to init() again later. + // If you change certain settings, init() gets called agian + uart_driver_delete(HUANYANG_UART_PORT); + + uart_config_t uart_config = { + .baud_rate = HUANYANG_BAUD_RATE, + .data_bits = UART_DATA_8_BITS, + .parity = UART_PARITY_DISABLE, + .stop_bits = UART_STOP_BITS_1, + .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, + .rx_flow_ctrl_thresh = 122, + }; + + uart_param_config(HUANYANG_UART_PORT, &uart_config); + + uart_set_pin(HUANYANG_UART_PORT, _txd_pin, _rxd_pin, _rts_pin, UART_PIN_NO_CHANGE); + + uart_driver_install(HUANYANG_UART_PORT, HUANYANG_BUF_SIZE * 2, 0, 0, NULL, 0); + + uart_set_mode(HUANYANG_UART_PORT, UART_MODE_RS485_HALF_DUPLEX); + + is_reversable = true; // these VFDs are always reversable + use_delays = true; + + // + _current_rpm = 0; + _state = SPINDLE_DISABLE; + + config_message(); + } + + // Checks for all the required pin definitions + // It returns a message for each missing pin + // Returns true if all pins are defined. + bool Huanyang::get_pins_and_settings() { +#ifdef HUANYANG_TXD_PIN + _txd_pin = HUANYANG_TXD_PIN; +#else + grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Undefined HUANYANG_TXD_PIN"); + hy_ok = false; +#endif + +#ifdef HUANYANG_RXD_PIN + _rxd_pin = HUANYANG_RXD_PIN; +#else + grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Undefined HUANYANG_RXD_PIN"); + hy_ok = false; +#endif + +#ifdef HUANYANG_RTS_PIN + _rts_pin = HUANYANG_RTS_PIN; +#else + grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Undefined HUANYANG_RTS_PIN"); + hy_ok = false; +#endif + + if (laser_mode->get()) { + grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Huanyang spindle disabled in laser mode. Set $GCode/LaserMode=Off and restart"); + hy_ok = false; + } + + _min_rpm = rpm_min->get(); + _max_rpm = rpm_max->get(); + + return hy_ok; + } + + void Huanyang::config_message() { + grbl_msg_sendf(CLIENT_SERIAL, + MSG_LEVEL_INFO, + "Huanyang Tx:%s Rx:%s RTS:%s", + pinName(_txd_pin).c_str(), + pinName(_rxd_pin).c_str(), + pinName(_rts_pin).c_str()); + } + + void Huanyang::set_state(uint8_t state, uint32_t rpm) { + if (sys.abort) + return; // Block during abort. + + bool critical = (sys.state == STATE_CYCLE || state != SPINDLE_DISABLE); + + if (_current_state != state) { // already at the desired state. This function gets called a lot. + set_mode(state, critical); // critical if we are in a job + set_rpm(rpm); + if (state == SPINDLE_DISABLE) { + sys.spindle_speed = 0; + if (_current_state != state) + mc_dwell(spindle_delay_spindown->get()); + } else { + if (_current_state != state) + mc_dwell(spindle_delay_spinup->get()); + } + } else { + if (_current_rpm != rpm) + set_rpm(rpm); + } + + _current_state = state; // store locally for faster get_state() + + sys.report_ovr_counter = 0; // Set to report change immediately + + return; + } + + bool Huanyang::set_mode(uint8_t mode, bool critical) { + if (!hy_ok) + return false; + + hy_command_t mode_cmd; + + mode_cmd.tx_length = 6; + mode_cmd.rx_length = 6; + + mode_cmd.msg[0] = HUANYANG_ADDR; + mode_cmd.msg[1] = 0x03; + mode_cmd.msg[2] = 0x01; + + if (mode == SPINDLE_ENABLE_CW) + mode_cmd.msg[3] = 0x01; + else if (mode == SPINDLE_ENABLE_CCW) + mode_cmd.msg[3] = 0x11; + else { //SPINDLE_DISABLE + mode_cmd.msg[3] = 0x08; + + if (!xQueueReset(hy_cmd_queue)) { + grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "VFD spindle off, queue could not be reset"); + } + } + + add_ModRTU_CRC(mode_cmd.msg, mode_cmd.rx_length); + + mode_cmd.critical = critical; + + if (xQueueSend(hy_cmd_queue, &mode_cmd, 0) != pdTRUE) + grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "VFD Queue Full"); + + return true; + } + + uint32_t Huanyang::set_rpm(uint32_t rpm) { + if (!hy_ok) + return 0; + + hy_command_t rpm_cmd; + + // apply override + rpm = rpm * sys.spindle_speed_ovr / 100; // Scale by spindle speed override value (uint8_t percent) + + // apply limits + if ((_min_rpm >= _max_rpm) || (rpm >= _max_rpm)) + rpm = _max_rpm; + else if (rpm != 0 && rpm <= _min_rpm) + rpm = _min_rpm; + + sys.spindle_speed = rpm; + + if (rpm == _current_rpm) // prevent setting same RPM twice + return rpm; + + _current_rpm = rpm; + + // TODO add the speed modifiers override, linearization, etc. + + rpm_cmd.tx_length = 7; + rpm_cmd.rx_length = 6; + + rpm_cmd.msg[0] = HUANYANG_ADDR; + rpm_cmd.msg[1] = 0x05; + rpm_cmd.msg[2] = 0x02; + + uint16_t data = (uint16_t)(rpm * 100 / 60); // send Hz * 10 (Ex:1500 RPM = 25Hz .... Send 2500) + + rpm_cmd.msg[3] = (data & 0xFF00) >> 8; + rpm_cmd.msg[4] = (data & 0xFF); + + add_ModRTU_CRC(rpm_cmd.msg, rpm_cmd.tx_length); + + rpm_cmd.critical = false; + + if (xQueueSend(hy_cmd_queue, &rpm_cmd, 0) != pdTRUE) + grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "VFD Queue Full"); + + return rpm; + } + + // This appears to read the control register and will return an RPM running or not. + void Huanyang::read_value(uint8_t reg) { + uint16_t ret_value = 0; + hy_command_t read_cmd; + uint8_t rx_message[HUANYANG_MAX_MSG_SIZE]; + + read_cmd.tx_length = 8; + read_cmd.rx_length = 8; + + read_cmd.msg[0] = HUANYANG_ADDR; + read_cmd.msg[1] = 0x04; + read_cmd.msg[2] = 0x03; + + read_cmd.msg[3] = reg; + + read_cmd.msg[4] = 0x00; + read_cmd.msg[5] = 0x00; + + read_cmd.critical = (sys.state == STATE_CYCLE); // only critical if running a job TBD.... maybe spindle on? + + add_ModRTU_CRC(read_cmd.msg, read_cmd.tx_length); + + if (xQueueSend(hy_cmd_queue, &read_cmd, 0) != pdTRUE) + grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "VFD Queue Full"); + } + + void Huanyang::stop() { set_mode(SPINDLE_DISABLE, false); } + + // state is cached rather than read right now to prevent delays + uint8_t Huanyang::get_state() { return _state; } + + // Calculate the CRC on all of the byte except the last 2 + // It then added the CRC to those last 2 bytes + // full_msg_len This is the length of the message including the 2 crc bytes + // Source: https://ctlsys.com/support/how_to_compute_the_modbus_rtu_message_crc/ + void Huanyang::add_ModRTU_CRC(char* buf, int full_msg_len) { + uint16_t crc = 0xFFFF; + for (int pos = 0; pos < full_msg_len - 2; pos++) { + crc ^= (uint16_t)buf[pos]; // XOR byte into least sig. byte of crc + for (int i = 8; i != 0; i--) { // Loop over each bit + if ((crc & 0x0001) != 0) { // If the LSB is set + crc >>= 1; // Shift right and XOR 0xA001 + crc ^= 0xA001; + } else // Else LSB is not set + crc >>= 1; // Just shift right + } + } + // add the calculated Crc to the message + buf[full_msg_len - 1] = (crc & 0xFF00) >> 8; + buf[full_msg_len - 2] = (crc & 0xFF); + } +} diff --git a/Grbl_Esp32/src/Spindles/HuanyangSpindle.h b/Grbl_Esp32/src/Spindles/HuanyangSpindle.h new file mode 100644 index 00000000..6dc341e3 --- /dev/null +++ b/Grbl_Esp32/src/Spindles/HuanyangSpindle.h @@ -0,0 +1,62 @@ +#pragma once + +/* + HuanyangSpindle.h + + Part of Grbl_ESP32 + 2020 - Bart Dring + + 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 . + +*/ +#include "Spindle.h" + +namespace Spindles { + class Huanyang : public Spindle { + private: + uint16_t ModRTU_CRC(char* buf, int len); + + bool set_mode(uint8_t mode, bool critical); + + bool get_pins_and_settings(); + + uint32_t _current_rpm; + uint8_t _txd_pin; + uint8_t _rxd_pin; + uint8_t _rts_pin; + uint8_t _state; + bool _task_running; + + public: + Huanyang() : _task_running(false) {} + + Huanyang(const Huanyang&) = delete; + Huanyang(Huanyang&&) = delete; + Huanyang& operator=(const Huanyang&) = delete; + Huanyang& operator=(Huanyang&&) = delete; + + void init(); + void config_message(); + void set_state(uint8_t state, uint32_t rpm); + uint8_t get_state(); + uint32_t set_rpm(uint32_t rpm); + void stop(); + static void read_value(uint8_t reg); + static void add_ModRTU_CRC(char* buf, int full_msg_len); + + virtual ~Huanyang() {} + + protected: + uint32_t _min_rpm; + uint32_t _max_rpm; + }; +} diff --git a/Grbl_Esp32/Spindles/Laser.cpp b/Grbl_Esp32/src/Spindles/Laser.cpp similarity index 54% rename from Grbl_Esp32/Spindles/Laser.cpp rename to Grbl_Esp32/src/Spindles/Laser.cpp index 53d82712..a0778a92 100644 --- a/Grbl_Esp32/Spindles/Laser.cpp +++ b/Grbl_Esp32/src/Spindles/Laser.cpp @@ -1,7 +1,7 @@ /* Laser.cpp - This is similar the the PWM Spindle except that it allows the + This is similar to the PWM Spindle except that it allows the M4 speed vs. power copensation. Part of Grbl_ESP32 @@ -19,23 +19,24 @@ along with Grbl. If not, see . */ -#include "SpindleClass.h" +#include "Laser.h" // ===================================== Laser ============================================== +namespace Spindles { + bool Laser::isRateAdjusted() { + return true; // can use M4 (CCW) laser mode. + } -bool Laser :: isRateAdjusted() { - return true; // can use M4 (CCW) laser mode. -} - -void Laser :: config_message() { - grbl_msg_sendf(CLIENT_SERIAL, - MSG_LEVEL_INFO, - "Laser spindle on Pin:%s, Freq:%.2fHz, Res:%dbits Laser mode:$32=%d", - pinName(_output_pin).c_str(), - _pwm_freq, - _pwm_precision, - isRateAdjusted()); // the current mode - - use_delays = false; // this will override the value set in PWMSpindle intit() + void Laser::config_message() { + grbl_msg_sendf(CLIENT_SERIAL, + MSG_LEVEL_INFO, + "Laser spindle on Pin:%s, Freq:%.2fHz, Res:%dbits Laser mode:$32=%d", + pinName(_output_pin).c_str(), + _pwm_freq, + _pwm_precision, + isRateAdjusted()); // the current mode + + use_delays = false; // this will override the value set in Spindle::PWM::init() + } } diff --git a/Grbl_Esp32/src/Spindles/Laser.h b/Grbl_Esp32/src/Spindles/Laser.h new file mode 100644 index 00000000..220770b0 --- /dev/null +++ b/Grbl_Esp32/src/Spindles/Laser.h @@ -0,0 +1,42 @@ +#pragma once + +/* + Laser.h + + This is similar to the PWM Spindle except that it allows the + M4 speed vs. power copensation. + + Part of Grbl_ESP32 + 2020 - Bart Dring + + 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 . + +*/ +#include "PWMSpindle.h" + +namespace Spindles { + // this is the same as a PWM spindle but the M4 compensation is supported. + class Laser : public PWM { + public: + Laser() = default; + + Laser(const Laser&) = delete; + Laser(Laser&&) = delete; + Laser& operator=(const Laser&) = delete; + Laser& operator=(Laser&&) = delete; + + bool isRateAdjusted() override; + void config_message() override; + + virtual ~Laser() {} + }; +} diff --git a/Grbl_Esp32/Spindles/NullSpindle.cpp b/Grbl_Esp32/src/Spindles/NullSpindle.cpp similarity index 55% rename from Grbl_Esp32/Spindles/NullSpindle.cpp rename to Grbl_Esp32/src/Spindles/NullSpindle.cpp index d3be3ea7..518e8f59 100644 --- a/Grbl_Esp32/Spindles/NullSpindle.cpp +++ b/Grbl_Esp32/src/Spindles/NullSpindle.cpp @@ -19,24 +19,20 @@ along with Grbl. If not, see . */ -#include "SpindleClass.h" +#include "NullSpindle.h" -// ======================= NullSpindle ============================== -// NullSpindle is just bunch of do nothing (ignore) methods to be used when you don't want a spindle +namespace Spindles { + // ======================= Null ============================== + // Null is just bunch of do nothing (ignore) methods to be used when you don't want a spindle -void NullSpindle :: init() { - is_reversable = false; - use_delays = false; - config_message(); -} -uint32_t NullSpindle :: set_rpm(uint32_t rpm) { - return rpm; -} -void NullSpindle :: set_state(uint8_t state, uint32_t rpm) {} -uint8_t NullSpindle :: get_state() { - return (SPINDLE_STATE_DISABLE); -} -void NullSpindle :: stop() {} -void NullSpindle :: config_message() { - grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "No spindle"); + void Null::init() { + is_reversable = false; + use_delays = false; + config_message(); + } + uint32_t Null::set_rpm(uint32_t rpm) { return rpm; } + void Null::set_state(uint8_t state, uint32_t rpm) {} + uint8_t Null::get_state() { return (SPINDLE_STATE_DISABLE); } + void Null::stop() {} + void Null::config_message() { grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "No spindle"); } } diff --git a/Grbl_Esp32/src/Spindles/NullSpindle.h b/Grbl_Esp32/src/Spindles/NullSpindle.h new file mode 100644 index 00000000..1d6a2e46 --- /dev/null +++ b/Grbl_Esp32/src/Spindles/NullSpindle.h @@ -0,0 +1,47 @@ +#pragma once + +/* + NullSpindle.h + + This is used when you don't want to use a spindle No I/O will be used + and most methods don't do anything + + Part of Grbl_ESP32 + 2020 - Bart Dring + + 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 . + +*/ +#include "Spindle.h" + +namespace Spindles { + // This is a dummy spindle that has no I/O. + // It is used to ignore spindle commands when no spinde is desired + class Null : public Spindle { + public: + Null() = default; + + Null(const Null&) = delete; + Null(Null&&) = delete; + Null& operator=(const Null&) = delete; + Null& operator=(Null&&) = delete; + + void init() override; + uint32_t set_rpm(uint32_t rpm) override; + void set_state(uint8_t state, uint32_t rpm) override; + uint8_t get_state() override; + void stop() override; + void config_message() override; + + virtual ~Null() {} + }; +} diff --git a/Grbl_Esp32/src/Spindles/PWMSpindle.cpp b/Grbl_Esp32/src/Spindles/PWMSpindle.cpp new file mode 100644 index 00000000..9a1ca848 --- /dev/null +++ b/Grbl_Esp32/src/Spindles/PWMSpindle.cpp @@ -0,0 +1,253 @@ +/* + PWMSpindle.cpp + + This is a full featured TTL PWM spindle This does not include speed/power + compensation. Use the Laser class for that. + + Part of Grbl_ESP32 + 2020 - Bart Dring + + 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 . + +*/ +#include "PWMSpindle.h" + +// ======================= PWM ============================== +/* + This gets called at startup or whenever a spindle setting changes + If the spindle is running it will stop and need to be restarted with M3Snnnn +*/ + +//#include "grbl.h" + +namespace Spindles { + void PWM::init() { + get_pins_and_settings(); + + if (_output_pin == UNDEFINED_PIN) { + grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Warning: Spindle output pin not defined"); + return; // We cannot continue without the output pin + } + + if (_output_pin >= I2S_OUT_PIN_BASE) { + grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Warning: Spindle output pin %s cannot do PWM", pinName(_output_pin).c_str()); + return; + } + + ledcSetup(_pwm_chan_num, (double)_pwm_freq, _pwm_precision); // setup the channel + ledcAttachPin(_output_pin, _pwm_chan_num); // attach the PWM to the pin + + pinMode(_enable_pin, OUTPUT); + pinMode(_direction_pin, OUTPUT); + + use_delays = true; + + config_message(); + } + + // Get the GPIO from the machine definition + void PWM::get_pins_and_settings() { + // setup all the pins + +#ifdef SPINDLE_OUTPUT_PIN + _output_pin = SPINDLE_OUTPUT_PIN; +#else + _output_pin = UNDEFINED_PIN; +#endif + + _invert_pwm = spindle_output_invert->get(); + +#ifdef SPINDLE_ENABLE_PIN + _enable_pin = SPINDLE_ENABLE_PIN; +#else + _enable_pin = UNDEFINED_PIN; +#endif + + _off_with_zero_speed = spindle_enbl_off_with_zero_speed->get(); + +#ifdef SPINDLE_DIR_PIN + _direction_pin = SPINDLE_DIR_PIN; +#else + _direction_pin = UNDEFINED_PIN; +#endif + + is_reversable = (_direction_pin != UNDEFINED_PIN); + + _pwm_freq = spindle_pwm_freq->get(); + _pwm_precision = calc_pwm_precision(_pwm_freq); // detewrmine the best precision + _pwm_period = (1 << _pwm_precision); + + if (spindle_pwm_min_value->get() > spindle_pwm_min_value->get()) + grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Warning: Spindle min pwm is greater than max. Check $35 and $36"); + + // pre-caculate some PWM count values + _pwm_off_value = (_pwm_period * spindle_pwm_off_value->get() / 100.0); + _pwm_min_value = (_pwm_period * spindle_pwm_min_value->get() / 100.0); + _pwm_max_value = (_pwm_period * spindle_pwm_max_value->get() / 100.0); + +#ifdef ENABLE_PIECEWISE_LINEAR_SPINDLE + _min_rpm = RPM_MIN; + _max_rpm = RPM_MAX; + _piecewide_linear = true; +#else + _min_rpm = rpm_min->get(); + _max_rpm = rpm_max->get(); + _piecewide_linear = false; +#endif + // The pwm_gradient is the pwm duty cycle units per rpm + // _pwm_gradient = (_pwm_max_value - _pwm_min_value) / (_max_rpm - _min_rpm); + + _pwm_chan_num = 0; // Channel 0 is reserved for spindle use + } + + uint32_t PWM::set_rpm(uint32_t rpm) { + uint32_t pwm_value; + + if (_output_pin == UNDEFINED_PIN) + return rpm; + + //grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "set_rpm(%d)", rpm); + + // apply override + rpm = rpm * sys.spindle_speed_ovr / 100; // Scale by spindle speed override value (uint8_t percent) + + // apply limits + if ((_min_rpm >= _max_rpm) || (rpm >= _max_rpm)) + rpm = _max_rpm; + else if (rpm != 0 && rpm <= _min_rpm) + rpm = _min_rpm; + + sys.spindle_speed = rpm; + + if (_piecewide_linear) { + //pwm_value = piecewise_linear_fit(rpm); TODO + pwm_value = 0; + grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Warning: Linear fit not implemented yet."); + + } else { + if (rpm == 0) + pwm_value = _pwm_off_value; + else + pwm_value = map_uint32_t(rpm, _min_rpm, _max_rpm, _pwm_min_value, _pwm_max_value); + } + + set_enable_pin(_current_state != SPINDLE_DISABLE); + set_output(pwm_value); + + return 0; + } + + void PWM::set_state(uint8_t state, uint32_t rpm) { + if (sys.abort) + return; // Block during abort. + + _current_state = state; + + if (_current_state == SPINDLE_DISABLE) { // Halt or set spindle direction and rpm. + sys.spindle_speed = 0; + stop(); + if (use_delays && (_current_state != state)) { + //grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "SpinDown Start "); + mc_dwell(spindle_delay_spindown->get()); + //grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "SpinDown Done"); + } + } else { + set_dir_pin(_current_state == SPINDLE_ENABLE_CW); + set_rpm(rpm); + set_enable_pin(_current_state != SPINDLE_DISABLE); // must be done after setting rpm for enable features to work + if (use_delays && (_current_state != state)) { + //grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "SpinUp Start %d", rpm); + mc_dwell(spindle_delay_spinup->get()); + //grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "SpinUp Done"); + } + } + + _current_state = state; + + sys.report_ovr_counter = 0; // Set to report change immediately + } + + uint8_t PWM::get_state() { + if (_current_pwm_duty == 0 || _output_pin == UNDEFINED_PIN) + return (SPINDLE_STATE_DISABLE); + if (_direction_pin != UNDEFINED_PIN) + return digitalRead(_direction_pin) ? SPINDLE_STATE_CW : SPINDLE_STATE_CCW; + return (SPINDLE_STATE_CW); + } + + void PWM::stop() { + // inverts are delt with in methods + set_enable_pin(false); + set_output(_pwm_off_value); + } + + // prints the startup message of the spindle config + void PWM::config_message() { + grbl_msg_sendf(CLIENT_SERIAL, + MSG_LEVEL_INFO, + "PWM spindle Output:%s, Enbl:%s, Dir:%s, Freq:%dHz, Res:%dbits", + pinName(_output_pin).c_str(), + pinName(_enable_pin).c_str(), + pinName(_direction_pin).c_str(), + _pwm_freq, + _pwm_precision); + } + + void PWM::set_output(uint32_t duty) { + if (_output_pin == UNDEFINED_PIN) + return; + + // to prevent excessive calls to ledcWrite, make sure duty hass changed + if (duty == _current_pwm_duty) + return; + + _current_pwm_duty = duty; + + if (_invert_pwm) + duty = (1 << _pwm_precision) - duty; + + //grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "set_output(%d)", duty); + + ledcWrite(_pwm_chan_num, duty); + } + + void PWM::set_enable_pin(bool enable) { + if (_enable_pin == UNDEFINED_PIN) + return; + + if (_off_with_zero_speed && sys.spindle_speed == 0) + enable = false; + + if (spindle_enable_invert->get()) + enable = !enable; + + digitalWrite(_enable_pin, enable); + } + + void PWM::set_dir_pin(bool Clockwise) { digitalWrite(_direction_pin, Clockwise); } + + /* + Calculate the highest precision of a PWM based on the frequency in bits + + 80,000,000 / freq = period + determine the highest precision where (1 << precision) < period + */ + uint8_t PWM::calc_pwm_precision(uint32_t freq) { + uint8_t precision = 0; + + // increase the precision (bits) until it exceeds allow by frequency the max or is 16 + while ((1 << precision) < (uint32_t)(80000000 / freq) && precision <= 16) + precision++; + + return precision - 1; + } +} diff --git a/Grbl_Esp32/src/Spindles/PWMSpindle.h b/Grbl_Esp32/src/Spindles/PWMSpindle.h new file mode 100644 index 00000000..a12b4739 --- /dev/null +++ b/Grbl_Esp32/src/Spindles/PWMSpindle.h @@ -0,0 +1,72 @@ +#pragma once + +/* + PWMSpindle.h + + This is a full featured TTL PWM spindle This does not include speed/power + compensation. Use the Laser class for that. + + Part of Grbl_ESP32 + 2020 - Bart Dring + + 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 . + +*/ +#include "Spindle.h" + +namespace Spindles { + // This adds support for PWM + class PWM : public Spindle { + public: + PWM() = default; + + PWM(const PWM&) = delete; + PWM(PWM&&) = delete; + PWM& operator=(const PWM&) = delete; + PWM& operator=(PWM&&) = delete; + + void init() override; + virtual uint32_t set_rpm(uint32_t rpm) override; + void set_state(uint8_t state, uint32_t rpm) override; + uint8_t get_state() override; + void stop() override; + void config_message() override; + + virtual ~PWM() {} + + protected: + int32_t _current_pwm_duty; + uint32_t _min_rpm; + uint32_t _max_rpm; + uint32_t _pwm_off_value; + uint32_t _pwm_min_value; + uint32_t _pwm_max_value; + uint8_t _output_pin; + uint8_t _enable_pin; + uint8_t _direction_pin; + uint8_t _pwm_chan_num; + uint32_t _pwm_freq; + uint32_t _pwm_period; // how many counts in 1 period + uint8_t _pwm_precision; + bool _piecewide_linear; + bool _off_with_zero_speed; + bool _invert_pwm; + //uint32_t _pwm_gradient; // Precalulated value to speed up rpm to PWM conversions. + + virtual void set_dir_pin(bool Clockwise); + virtual void set_output(uint32_t duty); + virtual void set_enable_pin(bool enable_pin); + + void get_pins_and_settings(); + uint8_t calc_pwm_precision(uint32_t freq); + }; +} diff --git a/Grbl_Esp32/src/Spindles/RelaySpindle.cpp b/Grbl_Esp32/src/Spindles/RelaySpindle.cpp new file mode 100644 index 00000000..95a16a68 --- /dev/null +++ b/Grbl_Esp32/src/Spindles/RelaySpindle.cpp @@ -0,0 +1,72 @@ +/* + RelaySpindle.cpp + + This is used for a basic on/off spindle All S Values above 0 + will turn the spindle on. + + Part of Grbl_ESP32 + 2020 - Bart Dring + + 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 . + +*/ +#include "RelaySpindle.h" + +// ========================= Relay ================================== + +namespace Spindles { + /* + This is a sub class of PWM but is a digital rather than PWM output +*/ + void Relay::init() { + get_pins_and_settings(); + + if (_output_pin == UNDEFINED_PIN) + return; + + pinMode(_output_pin, OUTPUT); + pinMode(_enable_pin, OUTPUT); + pinMode(_direction_pin, OUTPUT); + + is_reversable = (_direction_pin != UNDEFINED_PIN); + use_delays = true; + + config_message(); + } + + // prints the startup message of the spindle config + void Relay ::config_message() { + grbl_msg_sendf(CLIENT_SERIAL, + MSG_LEVEL_INFO, + "Relay spindle Output:%s, Enbl:%s, Dir:%s", + pinName(_output_pin).c_str(), + pinName(_enable_pin).c_str(), + pinName(_direction_pin).c_str()); + } + + uint32_t Relay::set_rpm(uint32_t rpm) { + if (_output_pin == UNDEFINED_PIN) + return rpm; + + sys.spindle_speed = rpm; + set_output(rpm != 0); + + return rpm; + } + + void Relay::set_output(uint32_t duty) { +#ifdef INVERT_SPINDLE_PWM + duty = (duty == 0); // flip duty +#endif + digitalWrite(_output_pin, duty > 0); // anything greater + } +} diff --git a/Grbl_Esp32/src/Spindles/RelaySpindle.h b/Grbl_Esp32/src/Spindles/RelaySpindle.h new file mode 100644 index 00000000..96e55b15 --- /dev/null +++ b/Grbl_Esp32/src/Spindles/RelaySpindle.h @@ -0,0 +1,46 @@ +#pragma once + +/* + RelaySpindle.h + + This is used for a basic on/off spindle All S Values above 0 + will turn the spindle on. + + Part of Grbl_ESP32 + 2020 - Bart Dring + + 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 . + +*/ +#include "PWMSpindle.h" + +namespace Spindles { + // This is for an on/off spindle all RPMs above 0 are on + class Relay : public PWM { + public: + Relay() = default; + + Relay(const Relay&) = delete; + Relay(Relay&&) = delete; + Relay& operator=(const Relay&) = delete; + Relay& operator=(Relay&&) = delete; + + void init() override; + void config_message() override; + uint32_t set_rpm(uint32_t rpm) override; + + virtual ~Relay() {} + + protected: + void set_output(uint32_t duty); + }; +} diff --git a/Grbl_Esp32/src/Spindles/Spindle.cpp b/Grbl_Esp32/src/Spindles/Spindle.cpp new file mode 100644 index 00000000..75c9824b --- /dev/null +++ b/Grbl_Esp32/src/Spindles/Spindle.cpp @@ -0,0 +1,81 @@ +/* + Spindle.cpp + + A Base class for spindles and spinsle like things such as lasers + + Part of Grbl_ESP32 + + 2020 - Bart Dring + + 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 . + + TODO + Add Spindle spin up/down delays + + Get rid of dependance on machine definition #defines + SPINDLE_OUTPUT_PIN + SPINDLE_ENABLE_PIN + SPINDLE_DIR_PIN + +*/ +#include "Spindle.h" + +#include "NullSpindle.h" +#include "PWMSpindle.h" +#include "RelaySpindle.h" +#include "Laser.h" +#include "DacSpindle.h" +#include "HuanyangSpindle.h" +#include "BESCSpindle.h" +#include "10vSpindle.h" + +namespace Spindles { + // An instance of each type of spindle is created here. + // This allows the spindle to be dynamicly switched + Null null; + PWM pwm; + Relay relay; + Laser laser; + Dac dac; + Huanyang huanyang; + BESC besc; + _10v _10v; + + void Spindle::select() { + switch (spindle_type->get()) { + case SPINDLE_TYPE_PWM: spindle = &pwm; break; + case SPINDLE_TYPE_RELAY: spindle = &relay; break; + case SPINDLE_TYPE_LASER: spindle = &laser; break; + case SPINDLE_TYPE_DAC: spindle = &dac; break; + case SPINDLE_TYPE_HUANYANG: spindle = &huanyang; break; + case SPINDLE_TYPE_BESC: spindle = &besc; break; + case SPINDLE_TYPE_10V: spindle = &_10v; break; + case SPINDLE_TYPE_NONE: + default: spindle = &null; break; + } + + spindle->init(); + } + + // ========================= Spindle ================================== + + bool Spindle::isRateAdjusted() { + return false; // default for basic spindle is false + } + + void Spindle::sync(uint8_t state, uint32_t rpm) { + if (sys.state == STATE_CHECK_MODE) + return; + protocol_buffer_synchronize(); // Empty planner buffer to ensure spindle is set when programmed. + set_state(state, rpm); + } +} diff --git a/Grbl_Esp32/src/Spindles/Spindle.h b/Grbl_Esp32/src/Spindles/Spindle.h new file mode 100644 index 00000000..ba96ff49 --- /dev/null +++ b/Grbl_Esp32/src/Spindles/Spindle.h @@ -0,0 +1,80 @@ +#pragma once + +/* + Spindle.h + + Header file for a Spindle Class + See Spindle.cpp for more details + + Part of Grbl_ESP32 + + 2020 - 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 . + + See SpindleClass.cpp for more info and references + +*/ + +#define SPINDLE_STATE_DISABLE 0 // Must be zero. +#define SPINDLE_STATE_CW bit(0) +#define SPINDLE_STATE_CCW bit(1) + +#define SPINDLE_TYPE_NONE 0 +#define SPINDLE_TYPE_PWM 1 +#define SPINDLE_TYPE_RELAY 2 +#define SPINDLE_TYPE_LASER 3 +#define SPINDLE_TYPE_DAC 4 +#define SPINDLE_TYPE_HUANYANG 5 +#define SPINDLE_TYPE_BESC 6 +#define SPINDLE_TYPE_10V 7 + +#include "../Grbl.h" +#include +#include + +// =============== No floats! =========================== +// ================ NO FLOATS! ========================== + +namespace Spindles { + // This is the base class. Do not use this as your spindle + class Spindle { + public: + Spindle() = default; + + Spindle(const Spindle&) = delete; + Spindle(Spindle&&) = delete; + Spindle& operator=(const Spindle&) = delete; + Spindle& operator=(Spindle&&) = delete; + + virtual void init() = 0; // not in constructor because this also gets called when $$ settings change + virtual uint32_t set_rpm(uint32_t rpm) = 0; + virtual void set_state(uint8_t state, uint32_t rpm) = 0; + virtual uint8_t get_state() = 0; + virtual void stop() = 0; + virtual void config_message() = 0; + virtual bool isRateAdjusted(); + virtual void sync(uint8_t state, uint32_t rpm); + + virtual ~Spindle() {} + + bool is_reversable; + bool use_delays; // will SpinUp and SpinDown delays be used. + uint8_t _current_state; + + static void select(); + }; + +} + +extern Spindles::Spindle* spindle; diff --git a/Grbl_Esp32/stepper.cpp b/Grbl_Esp32/src/Stepper.cpp similarity index 73% rename from Grbl_Esp32/stepper.cpp rename to Grbl_Esp32/src/Stepper.cpp index ad8e54cd..f64caa8d 100644 --- a/Grbl_Esp32/stepper.cpp +++ b/Grbl_Esp32/src/Stepper.cpp @@ -1,5 +1,5 @@ /* - stepper.c - stepper motor driver: executes motion plans using stepper motors + Stepper.cpp - stepper motor driver: executes motion plans using stepper motors Part of Grbl Copyright (c) 2011-2016 Sungeun K. Jeon for Gnea Research LLC @@ -23,7 +23,7 @@ along with Grbl. If not, see . */ -#include "grbl.h" +#include "Grbl.h" // Stores the planner block Bresenham algorithm execution data for the segments in the segment // buffer. Normally, this buffer is partially in-use, but, for the worst case scenario, it will @@ -34,8 +34,8 @@ typedef struct { uint32_t steps[N_AXIS]; uint32_t step_event_count; - uint8_t direction_bits; - uint8_t is_pwm_rate_adjusted; // Tracks motions that require constant laser power/rate + uint8_t direction_bits; + uint8_t is_pwm_rate_adjusted; // Tracks motions that require constant laser power/rate } st_block_t; static st_block_t st_block_buffer[SEGMENT_BUFFER_SIZE - 1]; @@ -48,9 +48,9 @@ typedef struct { uint16_t cycles_per_tick; // Step distance traveled per ISR tick, aka step rate. uint8_t st_block_index; // Stepper block data index. Uses this information to execute this segment. #ifdef ADAPTIVE_MULTI_AXIS_STEP_SMOOTHING - uint8_t amass_level; // Indicates AMASS level for the ISR to execute this segment + uint8_t amass_level; // Indicates AMASS level for the ISR to execute this segment #else - uint8_t prescaler; // Without AMASS, a prescaler is required to adjust for slow timing. + uint8_t prescaler; // Without AMASS, a prescaler is required to adjust for slow timing. #endif uint16_t spindle_rpm; // TODO get rid of this. } segment_t; @@ -59,42 +59,44 @@ static segment_t segment_buffer[SEGMENT_BUFFER_SIZE]; // Stepper ISR data struct. Contains the running data for the main stepper ISR. typedef struct { // Used by the bresenham line algorithm - uint32_t counter_x, // Counter variables for the bresenham line tracer - counter_y, - counter_z + uint32_t counter_x, // Counter variables for the bresenham line tracer + counter_y, counter_z #if (N_AXIS > A_AXIS) - , counter_a + , + counter_a #endif #if (N_AXIS > B_AXIS) - , counter_b + , + counter_b #endif #if (N_AXIS > C_AXIS) - , counter_c + , + counter_c #endif - ; + ; #ifdef STEP_PULSE_DELAY uint8_t step_bits; // Stores out_bits output to complete the step pulse delay #endif uint8_t execute_step; // Flags step execution for each interrupt. uint8_t step_pulse_time; // Step pulse reset time after step rise - uint8_t step_outbits; // The next stepping-bits to be output + uint8_t step_outbits; // The next stepping-bits to be output uint8_t dir_outbits; #ifdef ADAPTIVE_MULTI_AXIS_STEP_SMOOTHING uint32_t steps[N_AXIS]; #endif - uint16_t step_count; // Steps remaining in line segment motion - uint8_t exec_block_index; // Tracks the current st_block index. Change indicates new block. - st_block_t* exec_block; // Pointer to the block data for the segment being executed - segment_t* exec_segment; // Pointer to the segment being executed + uint16_t step_count; // Steps remaining in line segment motion + uint8_t exec_block_index; // Tracks the current st_block index. Change indicates new block. + st_block_t* exec_block; // Pointer to the block data for the segment being executed + segment_t* exec_segment; // Pointer to the segment being executed } stepper_t; static stepper_t st; // Step segment ring buffer indices static volatile uint8_t segment_buffer_tail; -static uint8_t segment_buffer_head; -static uint8_t segment_next_head; +static uint8_t segment_buffer_head; +static uint8_t segment_next_head; // Step and direction port invert masks. static uint8_t step_port_invert_mask; @@ -105,12 +107,12 @@ static volatile uint8_t busy; // Pointers for the step segment being prepped from the planner buffer. Accessed only by the // main program. Pointers may be planning segments or planner blocks ahead of what being executed. -static plan_block_t* pl_block; // Pointer to the planner block being prepped -static st_block_t* st_prep_block; // Pointer to the stepper block data being prepped +static plan_block_t* pl_block; // Pointer to the planner block being prepped +static st_block_t* st_prep_block; // Pointer to the stepper block data being prepped // esp32 work around for diable in main loop -uint64_t stepper_idle_counter; // used to count down until time to disable stepper drivers -bool stepper_idle; +uint64_t stepper_idle_counter; // used to count down until time to disable stepper drivers +bool stepper_idle; // Segment preparation data struct. Contains all the necessary information to compute new segments // based on the current executing planner block. @@ -125,30 +127,27 @@ typedef struct { #ifdef PARKING_ENABLE uint8_t last_st_block_index; - float last_steps_remaining; - float last_step_per_mm; - float last_dt_remainder; + float last_steps_remaining; + float last_step_per_mm; + float last_dt_remainder; #endif - uint8_t ramp_type; // Current segment ramp state - float mm_complete; // End of velocity profile from end of current planner block in (mm). + uint8_t ramp_type; // Current segment ramp state + float mm_complete; // End of velocity profile from end of current planner block in (mm). // NOTE: This value must coincide with a step(no mantissa) when converted. - float current_speed; // Current speed at the end of the segment buffer (mm/min) - float maximum_speed; // Maximum speed of executing block. Not always nominal speed. (mm/min) - float exit_speed; // Exit speed of executing block (mm/min) - float accelerate_until; // Acceleration ramp end measured from end of block (mm) - float decelerate_after; // Deceleration ramp start measured from end of block (mm) + float current_speed; // Current speed at the end of the segment buffer (mm/min) + float maximum_speed; // Maximum speed of executing block. Not always nominal speed. (mm/min) + float exit_speed; // Exit speed of executing block (mm/min) + float accelerate_until; // Acceleration ramp end measured from end of block (mm) + float decelerate_after; // Deceleration ramp start measured from end of block (mm) - - float inv_rate; // Used by PWM laser mode to speed up segment calculations. + float inv_rate; // Used by PWM laser mode to speed up segment calculations. //uint16_t current_spindle_pwm; // todo remove float current_spindle_rpm; } st_prep_t; static st_prep_t prep; - - /* "The Stepper Driver Interrupt" - This timer interrupt is the workhorse of Grbl. Grbl employs the venerable Bresenham line algorithm to manage and exactly synchronize multi-axis moves. Unlike the popular DDA algorithm, the Bresenham algorithm is not susceptible to numerical @@ -205,7 +204,7 @@ static st_prep_t prep; */ #ifdef USE_RMT_STEPS - inline IRAM_ATTR static void stepperRMT_Outputs(); +inline IRAM_ATTR static void stepperRMT_Outputs(); #endif static void stepper_pulse_func(); @@ -213,18 +212,19 @@ static void stepper_pulse_func(); // TODO: Replace direct updating of the int32 position counters in the ISR somehow. Perhaps use smaller // int8 variables and update position counters only when a segment completes. This can get complicated // with probing and homing cycles that require true real-time positions. -void IRAM_ATTR onStepperDriverTimer(void* para) { // ISR It is time to take a step ======================================================================================= +void IRAM_ATTR onStepperDriverTimer( + void* para) { // ISR It is time to take a step ======================================================================================= //const int timer_idx = (int)para; // get the timer index TIMERG0.int_clr_timers.t0 = 1; if (busy) { - return; // The busy-flag is used to avoid reentering this interrupt + return; // The busy-flag is used to avoid reentering this interrupt } busy = true; stepper_pulse_func(); TIMERG0.hw_timer[STEP_TIMER_INDEX].config.alarm_en = TIMER_ALARM_EN; - busy = false; + busy = false; } /** @@ -240,9 +240,9 @@ static void stepper_pulse_func() { stepperRMT_Outputs(); #else set_stepper_pins_on(st.step_outbits); -#ifndef USE_I2S_OUT_STREAM +# ifndef USE_I2S_OUT_STREAM uint64_t step_pulse_start_time = esp_timer_get_time(); -#endif +# endif #endif // some motor objects, like unipolar, handle steps themselves @@ -256,12 +256,12 @@ static void stepper_pulse_func() { st.exec_segment = &segment_buffer[segment_buffer_tail]; // Initialize step segment timing per step and load number of steps to execute. Stepper_Timer_WritePeriod(st.exec_segment->cycles_per_tick); - st.step_count = st.exec_segment->n_step; // NOTE: Can sometimes be zero when moving slow. + st.step_count = st.exec_segment->n_step; // NOTE: Can sometimes be zero when moving slow. // If the new segment starts a new planner block, initialize stepper variables and counters. // NOTE: When the segment data index changes, this indicates a new planner block. if (st.exec_block_index != st.exec_segment->st_block_index) { st.exec_block_index = st.exec_segment->st_block_index; - st.exec_block = &st_block_buffer[st.exec_block_index]; + st.exec_block = &st_block_buffer[st.exec_block_index]; // Initialize Bresenham line and distance counters st.counter_x = st.counter_y = st.counter_z = (st.exec_block->step_event_count >> 1); // TODO ABC @@ -272,15 +272,15 @@ static void stepper_pulse_func() { st.steps[X_AXIS] = st.exec_block->steps[X_AXIS] >> st.exec_segment->amass_level; st.steps[Y_AXIS] = st.exec_block->steps[Y_AXIS] >> st.exec_segment->amass_level; st.steps[Z_AXIS] = st.exec_block->steps[Z_AXIS] >> st.exec_segment->amass_level; -#if (N_AXIS > A_AXIS) +# if (N_AXIS > A_AXIS) st.steps[A_AXIS] = st.exec_block->steps[A_AXIS] >> st.exec_segment->amass_level; -#endif -#if (N_AXIS > B_AXIS) +# endif +# if (N_AXIS > B_AXIS) st.steps[B_AXIS] = st.exec_block->steps[B_AXIS] >> st.exec_segment->amass_level; -#endif -#if (N_AXIS > C_AXIS) +# endif +# if (N_AXIS > C_AXIS) st.steps[C_AXIS] = st.exec_block->steps[C_AXIS] >> st.exec_segment->amass_level; -#endif +# endif #endif // Set real-time spindle output as segment is loaded, just prior to the first step. spindle->set_rpm(st.exec_segment->spindle_rpm); @@ -293,8 +293,8 @@ static void stepper_pulse_func() { spindle->set_rpm(0); } - system_set_exec_state_flag(EXEC_CYCLE_STOP); // Flag main program for cycle end - return; // Nothing to do but exit. + system_set_exec_state_flag(EXEC_CYCLE_STOP); // Flag main program for cycle end + return; // Nothing to do but exit. } } // Check probing state. @@ -343,48 +343,54 @@ static void stepper_pulse_func() { sys_position[Z_AXIS]++; } #if (N_AXIS > A_AXIS) -#ifdef ADAPTIVE_MULTI_AXIS_STEP_SMOOTHING +# ifdef ADAPTIVE_MULTI_AXIS_STEP_SMOOTHING st.counter_a += st.steps[A_AXIS]; -#else +# else st.counter_a += st.exec_block->steps[A_AXIS]; -#endif +# endif if (st.counter_a > st.exec_block->step_event_count) { st.step_outbits |= bit(A_AXIS); st.counter_a -= st.exec_block->step_event_count; - if (st.exec_block->direction_bits & bit(A_AXIS)) sys_position[A_AXIS]--; - else sys_position[A_AXIS]++; + if (st.exec_block->direction_bits & bit(A_AXIS)) + sys_position[A_AXIS]--; + else + sys_position[A_AXIS]++; } #endif #if (N_AXIS > B_AXIS) -#ifdef ADAPTIVE_MULTI_AXIS_STEP_SMOOTHING +# ifdef ADAPTIVE_MULTI_AXIS_STEP_SMOOTHING st.counter_b += st.steps[B_AXIS]; -#else +# else st.counter_b += st.exec_block->steps[B_AXIS]; -#endif +# endif if (st.counter_b > st.exec_block->step_event_count) { st.step_outbits |= bit(B_AXIS); st.counter_b -= st.exec_block->step_event_count; - if (st.exec_block->direction_bits & bit(B_AXIS)) sys_position[B_AXIS]--; - else sys_position[B_AXIS]++; + if (st.exec_block->direction_bits & bit(B_AXIS)) + sys_position[B_AXIS]--; + else + sys_position[B_AXIS]++; } #endif #if (N_AXIS > C_AXIS) -#ifdef ADAPTIVE_MULTI_AXIS_STEP_SMOOTHING +# ifdef ADAPTIVE_MULTI_AXIS_STEP_SMOOTHING st.counter_c += st.steps[C_AXIS]; -#else +# else st.counter_c += st.exec_block->steps[C_AXIS]; -#endif +# endif if (st.counter_c > st.exec_block->step_event_count) { st.step_outbits |= bit(C_AXIS); st.counter_c -= st.exec_block->step_event_count; - if (st.exec_block->direction_bits & bit(C_AXIS)) sys_position[C_AXIS]--; - else sys_position[C_AXIS]++; + if (st.exec_block->direction_bits & bit(C_AXIS)) + sys_position[C_AXIS]--; + else + sys_position[C_AXIS]++; } #endif // During a homing cycle, lock out and prevent desired axes from moving. if (sys.state == STATE_HOMING) st.step_outbits &= sys.homing_axis_lock; - st.step_count--; // Decrement step events count + st.step_count--; // Decrement step events count if (st.step_count == 0) { // Segment is complete. Discard current segment and advance segment indexing. st.exec_segment = NULL; @@ -393,28 +399,27 @@ static void stepper_pulse_func() { } #ifndef USE_RMT_STEPS -#ifdef USE_I2S_OUT_STREAM +# ifdef USE_I2S_OUT_STREAM // // Generate pulse (at least one pulse) // The pulse resolution is limited by I2S_OUT_USEC_PER_PULSE // st.step_outbits ^= step_port_invert_mask; // Apply step port invert mask i2s_out_push_sample(pulse_microseconds->get() / I2S_OUT_USEC_PER_PULSE); - set_stepper_pins_on(0); // turn all off -#else + set_stepper_pins_on(0); // turn all off +# else st.step_outbits ^= step_port_invert_mask; // Apply step port invert mask // wait for step pulse time to complete...some of it should have expired during code above while (esp_timer_get_time() - step_pulse_start_time < pulse_microseconds->get()) { - NOP(); // spin here until time to turn off step + NOP(); // spin here until time to turn off step } - set_stepper_pins_on(0); // turn all off -#endif + set_stepper_pins_on(0); // turn all off +# endif #endif return; } void stepper_init() { - grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Axis count %d", N_AXIS); // make the step pins outputs #ifdef USE_RMT_STEPS @@ -441,11 +446,8 @@ void stepper_init() { timer_enable_intr(STEP_TIMER_GROUP, STEP_TIMER_INDEX); timer_isr_register(STEP_TIMER_GROUP, STEP_TIMER_INDEX, onStepperDriverTimer, NULL, 0, NULL); #endif - } - - // enabled. Startup init and limits call this function but shouldn't start the cycle. void st_wake_up() { //grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "st_wake_up"); @@ -457,7 +459,7 @@ void st_wake_up() { // Initialize step pulse timing from settings. Here to ensure updating after re-writing. #ifdef STEP_PULSE_DELAY // Step pulse delay handling is not require with ESP32...the RMT function does it. -#else // Normal operation +#else // Normal operation // Set step pulse time. Ad hoc computation from oscilloscope. Uses two's complement. st.step_pulse_time = -(((pulse_microseconds->get() - 2) * TICKS_PER_MICROSECOND) >> 3); #endif @@ -478,198 +480,194 @@ void st_reset() { // Initialize stepper algorithm variables. memset(&prep, 0, sizeof(st_prep_t)); memset(&st, 0, sizeof(stepper_t)); - st.exec_segment = NULL; - pl_block = NULL; // Planner block pointer used by segment buffer + st.exec_segment = NULL; + pl_block = NULL; // Planner block pointer used by segment buffer segment_buffer_tail = 0; - segment_buffer_head = 0; // empty = tail - segment_next_head = 1; - busy = false; + segment_buffer_head = 0; // empty = tail + segment_next_head = 1; + busy = false; st_generate_step_dir_invert_masks(); - st.dir_outbits = dir_port_invert_mask; // Initialize direction bits to default. + st.dir_outbits = dir_port_invert_mask; // Initialize direction bits to default. // TODO do we need to turn step pins off? } - void set_stepper_pins_on(uint8_t onMask) { - onMask ^= step_invert_mask->get(); // invert pins as required by invert mask + onMask ^= step_invert_mask->get(); // invert pins as required by invert mask #ifdef X_STEP_PIN -#ifndef X2_STEP_PIN // if not a ganged axis +# ifndef X2_STEP_PIN // if not a ganged axis digitalWrite(X_STEP_PIN, (onMask & bit(X_AXIS))); -#else // is a ganged axis +# else // is a ganged axis if ((ganged_mode == SQUARING_MODE_DUAL) || (ganged_mode == SQUARING_MODE_A)) digitalWrite(X_STEP_PIN, (onMask & bit(X_AXIS))); if ((ganged_mode == SQUARING_MODE_DUAL) || (ganged_mode == SQUARING_MODE_B)) digitalWrite(X2_STEP_PIN, (onMask & bit(X_AXIS))); -#endif +# endif #endif #ifdef Y_STEP_PIN -#ifndef Y2_STEP_PIN // if not a ganged axis +# ifndef Y2_STEP_PIN // if not a ganged axis digitalWrite(Y_STEP_PIN, (onMask & bit(Y_AXIS))); -#else // is a ganged axis +# else // is a ganged axis if ((ganged_mode == SQUARING_MODE_DUAL) || (ganged_mode == SQUARING_MODE_A)) digitalWrite(Y_STEP_PIN, (onMask & bit(Y_AXIS))); if ((ganged_mode == SQUARING_MODE_DUAL) || (ganged_mode == SQUARING_MODE_B)) digitalWrite(Y2_STEP_PIN, (onMask & bit(Y_AXIS))); +# endif #endif -#endif - #ifdef Z_STEP_PIN -#ifndef Z2_STEP_PIN // if not a ganged axis +# ifndef Z2_STEP_PIN // if not a ganged axis digitalWrite(Z_STEP_PIN, (onMask & bit(Z_AXIS))); -#else // is a ganged axis +# else // is a ganged axis if ((ganged_mode == SQUARING_MODE_DUAL) || (ganged_mode == SQUARING_MODE_A)) digitalWrite(Z_STEP_PIN, (onMask & bit(Z_AXIS))); if ((ganged_mode == SQUARING_MODE_DUAL) || (ganged_mode == SQUARING_MODE_B)) digitalWrite(Z2_STEP_PIN, (onMask & bit(Z_AXIS))); -#endif +# endif #endif #ifdef A_STEP_PIN -#ifndef A2_STEP_PIN // if not a ganged axis +# ifndef A2_STEP_PIN // if not a ganged axis digitalWrite(A_STEP_PIN, (onMask & bit(A_AXIS))); -#else // is a ganged axis +# else // is a ganged axis if ((ganged_mode == SQUARING_MODE_DUAL) || (ganged_mode == SQUARING_MODE_A)) digitalWrite(A_STEP_PIN, (onMask & bit(A_AXIS))); if ((ganged_mode == SQUARING_MODE_DUAL) || (ganged_mode == SQUARING_MODE_B)) digitalWrite(A2_STEP_PIN, (onMask & bit(A_AXIS))); -#endif +# endif #endif #ifdef B_STEP_PIN -#ifndef B2_STEP_PIN // if not a ganged axis +# ifndef B2_STEP_PIN // if not a ganged axis digitalWrite(B_STEP_PIN, (onMask & bit(B_AXIS))); -#else // is a ganged axis +# else // is a ganged axis if ((ganged_mode == SQUARING_MODE_DUAL) || (ganged_mode == SQUARING_MODE_A)) digitalWrite(B_STEP_PIN, (onMask & bit(B_AXIS))); if ((ganged_mode == SQUARING_MODE_DUAL) || (ganged_mode == SQUARING_MODE_B)) digitalWrite(B2_STEP_PIN, (onMask & bit(B_AXIS))); -#endif +# endif #endif #ifdef C_STEP_PIN -#ifndef C2_STEP_PIN // if not a ganged axis +# ifndef C2_STEP_PIN // if not a ganged axis digitalWrite(C_STEP_PIN, (onMask & bit(C_AXIS))); -#else // is a ganged axis +# else // is a ganged axis if ((ganged_mode == SQUARING_MODE_DUAL) || (ganged_mode == SQUARING_MODE_A)) digitalWrite(C_STEP_PIN, (onMask & bit(C_AXIS))); if ((ganged_mode == SQUARING_MODE_DUAL) || (ganged_mode == SQUARING_MODE_B)) digitalWrite(C2_STEP_PIN, (onMask & bit(C_AXIS))); -#endif +# endif #endif } //#endif #ifdef USE_RMT_STEPS inline IRAM_ATTR static void stepperRMT_Outputs() { -#ifdef X_STEP_PIN +# ifdef X_STEP_PIN if (st.step_outbits & bit(X_AXIS)) { -#ifndef X2_STEP_PIN // if not a ganged axis +# ifndef X2_STEP_PIN // if not a ganged axis RMT.conf_ch[rmt_chan_num[X_AXIS][PRIMARY_MOTOR]].conf1.mem_rd_rst = 1; - RMT.conf_ch[rmt_chan_num[X_AXIS][PRIMARY_MOTOR]].conf1.tx_start = 1; -#else // it is a ganged axis + RMT.conf_ch[rmt_chan_num[X_AXIS][PRIMARY_MOTOR]].conf1.tx_start = 1; +# else // it is a ganged axis if ((ganged_mode == SQUARING_MODE_DUAL) || (ganged_mode == SQUARING_MODE_A)) { RMT.conf_ch[rmt_chan_num[X_AXIS][PRIMARY_MOTOR]].conf1.mem_rd_rst = 1; - RMT.conf_ch[rmt_chan_num[X_AXIS][PRIMARY_MOTOR]].conf1.tx_start = 1; + RMT.conf_ch[rmt_chan_num[X_AXIS][PRIMARY_MOTOR]].conf1.tx_start = 1; } if ((ganged_mode == SQUARING_MODE_DUAL) || (ganged_mode == SQUARING_MODE_B)) { RMT.conf_ch[rmt_chan_num[X_AXIS][GANGED_MOTOR]].conf1.mem_rd_rst = 1; - RMT.conf_ch[rmt_chan_num[X_AXIS][GANGED_MOTOR]].conf1.tx_start = 1; + RMT.conf_ch[rmt_chan_num[X_AXIS][GANGED_MOTOR]].conf1.tx_start = 1; } -#endif +# endif } -#endif -#ifdef Y_STEP_PIN +# endif +# ifdef Y_STEP_PIN if (st.step_outbits & bit(Y_AXIS)) { -#ifndef Y2_STEP_PIN // if not a ganged axis +# ifndef Y2_STEP_PIN // if not a ganged axis RMT.conf_ch[rmt_chan_num[Y_AXIS][PRIMARY_MOTOR]].conf1.mem_rd_rst = 1; - RMT.conf_ch[rmt_chan_num[Y_AXIS][PRIMARY_MOTOR]].conf1.tx_start = 1; -#else // it is a ganged axis + RMT.conf_ch[rmt_chan_num[Y_AXIS][PRIMARY_MOTOR]].conf1.tx_start = 1; +# else // it is a ganged axis if ((ganged_mode == SQUARING_MODE_DUAL) || (ganged_mode == SQUARING_MODE_A)) { RMT.conf_ch[rmt_chan_num[Y_AXIS][PRIMARY_MOTOR]].conf1.mem_rd_rst = 1; - RMT.conf_ch[rmt_chan_num[Y_AXIS][PRIMARY_MOTOR]].conf1.tx_start = 1; + RMT.conf_ch[rmt_chan_num[Y_AXIS][PRIMARY_MOTOR]].conf1.tx_start = 1; } if ((ganged_mode == SQUARING_MODE_DUAL) || (ganged_mode == SQUARING_MODE_B)) { RMT.conf_ch[rmt_chan_num[Y_AXIS][GANGED_MOTOR]].conf1.mem_rd_rst = 1; - RMT.conf_ch[rmt_chan_num[Y_AXIS][GANGED_MOTOR]].conf1.tx_start = 1; + RMT.conf_ch[rmt_chan_num[Y_AXIS][GANGED_MOTOR]].conf1.tx_start = 1; } -#endif +# endif } -#endif +# endif -#ifdef Z_STEP_PIN +# ifdef Z_STEP_PIN if (st.step_outbits & bit(Z_AXIS)) { -#ifndef Z2_STEP_PIN // if not a ganged axis +# ifndef Z2_STEP_PIN // if not a ganged axis RMT.conf_ch[rmt_chan_num[Z_AXIS][PRIMARY_MOTOR]].conf1.mem_rd_rst = 1; - RMT.conf_ch[rmt_chan_num[Z_AXIS][PRIMARY_MOTOR]].conf1.tx_start = 1; -#else // it is a ganged axis + RMT.conf_ch[rmt_chan_num[Z_AXIS][PRIMARY_MOTOR]].conf1.tx_start = 1; +# else // it is a ganged axis if ((ganged_mode == SQUARING_MODE_DUAL) || (ganged_mode == SQUARING_MODE_A)) { RMT.conf_ch[rmt_chan_num[Z_AXIS][PRIMARY_MOTOR]].conf1.mem_rd_rst = 1; - RMT.conf_ch[rmt_chan_num[Z_AXIS][PRIMARY_MOTOR]].conf1.tx_start = 1; + RMT.conf_ch[rmt_chan_num[Z_AXIS][PRIMARY_MOTOR]].conf1.tx_start = 1; } if ((ganged_mode == SQUARING_MODE_DUAL) || (ganged_mode == SQUARING_MODE_B)) { RMT.conf_ch[rmt_chan_num[Z_AXIS][GANGED_MOTOR]].conf1.mem_rd_rst = 1; - RMT.conf_ch[rmt_chan_num[Z_AXIS][GANGED_MOTOR]].conf1.tx_start = 1; + RMT.conf_ch[rmt_chan_num[Z_AXIS][GANGED_MOTOR]].conf1.tx_start = 1; } -#endif +# endif } -#endif +# endif -#ifdef A_STEP_PIN +# ifdef A_STEP_PIN if (st.step_outbits & bit(A_AXIS)) { -#ifndef A2_STEP_PIN // if not a ganged axis +# ifndef A2_STEP_PIN // if not a ganged axis RMT.conf_ch[rmt_chan_num[A_AXIS][PRIMARY_MOTOR]].conf1.mem_rd_rst = 1; - RMT.conf_ch[rmt_chan_num[A_AXIS][PRIMARY_MOTOR]].conf1.tx_start = 1; -#else // it is a ganged axis + RMT.conf_ch[rmt_chan_num[A_AXIS][PRIMARY_MOTOR]].conf1.tx_start = 1; +# else // it is a ganged axis if ((ganged_mode == SQUARING_MODE_DUAL) || (ganged_mode == SQUARING_MODE_A)) { RMT.conf_ch[rmt_chan_num[A_AXIS][PRIMARY_MOTOR]].conf1.mem_rd_rst = 1; - RMT.conf_ch[rmt_chan_num[A_AXIS][PRIMARY_MOTOR]].conf1.tx_start = 1; + RMT.conf_ch[rmt_chan_num[A_AXIS][PRIMARY_MOTOR]].conf1.tx_start = 1; } if ((ganged_mode == SQUARING_MODE_DUAL) || (ganged_mode == SQUARING_MODE_B)) { RMT.conf_ch[rmt_chan_num[A_AXIS][GANGED_MOTOR]].conf1.mem_rd_rst = 1; - RMT.conf_ch[rmt_chan_num[A_AXIS][GANGED_MOTOR]].conf1.tx_start = 1; + RMT.conf_ch[rmt_chan_num[A_AXIS][GANGED_MOTOR]].conf1.tx_start = 1; } -#endif +# endif } -#endif +# endif -#ifdef B_STEP_PIN +# ifdef B_STEP_PIN if (st.step_outbits & bit(B_AXIS)) { -#ifndef Z2_STEP_PIN // if not a ganged axis +# ifndef Z2_STEP_PIN // if not a ganged axis RMT.conf_ch[rmt_chan_num[B_AXIS][PRIMARY_MOTOR]].conf1.mem_rd_rst = 1; - RMT.conf_ch[rmt_chan_num[B_AXIS][PRIMARY_MOTOR]].conf1.tx_start = 1; -#else // it is a ganged axis + RMT.conf_ch[rmt_chan_num[B_AXIS][PRIMARY_MOTOR]].conf1.tx_start = 1; +# else // it is a ganged axis if ((ganged_mode == SQUARING_MODE_DUAL) || (ganged_mode == SQUARING_MODE_A)) { RMT.conf_ch[rmt_chan_num[B_AXIS][PRIMARY_MOTOR]].conf1.mem_rd_rst = 1; - RMT.conf_ch[rmt_chan_num[B_AXIS][PRIMARY_MOTOR]].conf1.tx_start = 1; + RMT.conf_ch[rmt_chan_num[B_AXIS][PRIMARY_MOTOR]].conf1.tx_start = 1; } if ((ganged_mode == SQUARING_MODE_DUAL) || (ganged_mode == SQUARING_MODE_B)) { RMT.conf_ch[rmt_chan_num[B_AXIS][GANGED_MOTOR]].conf1.mem_rd_rst = 1; - RMT.conf_ch[rmt_chan_num[B_AXIS][GANGED_MOTOR]].conf1.tx_start = 1; + RMT.conf_ch[rmt_chan_num[B_AXIS][GANGED_MOTOR]].conf1.tx_start = 1; } -#endif +# endif } -#endif +# endif -#ifdef C_STEP_PIN +# ifdef C_STEP_PIN if (st.step_outbits & bit(C_AXIS)) { -#ifndef Z2_STEP_PIN // if not a ganged axis +# ifndef Z2_STEP_PIN // if not a ganged axis RMT.conf_ch[rmt_chan_num[C_AXIS][PRIMARY_MOTOR]].conf1.mem_rd_rst = 1; - RMT.conf_ch[rmt_chan_num[C_AXIS][PRIMARY_MOTOR]].conf1.tx_start = 1; -#else // it is a ganged axis + RMT.conf_ch[rmt_chan_num[C_AXIS][PRIMARY_MOTOR]].conf1.tx_start = 1; +# else // it is a ganged axis if ((ganged_mode == SQUARING_MODE_DUAL) || (ganged_mode == SQUARING_MODE_A)) { RMT.conf_ch[rmt_chan_num[C_AXIS][PRIMARY_MOTOR]].conf1.mem_rd_rst = 1; - RMT.conf_ch[rmt_chan_num[C_AXIS][PRIMARY_MOTOR]].conf1.tx_start = 1; + RMT.conf_ch[rmt_chan_num[C_AXIS][PRIMARY_MOTOR]].conf1.tx_start = 1; } if ((ganged_mode == SQUARING_MODE_DUAL) || (ganged_mode == SQUARING_MODE_B)) { RMT.conf_ch[rmt_chan_num[C_AXIS][GANGED_MOTOR]].conf1.mem_rd_rst = 1; - RMT.conf_ch[rmt_chan_num[C_AXIS][GANGED_MOTOR]].conf1.tx_start = 1; + RMT.conf_ch[rmt_chan_num[C_AXIS][GANGED_MOTOR]].conf1.tx_start = 1; } -#endif +# endif } -#endif - - +# endif } #endif @@ -686,8 +684,8 @@ void st_go_idle() { if (sys.state == STATE_SLEEP || sys_rt_exec_alarm) { motors_set_disable(true); } else { - stepper_idle = true; // esp32 work around for disable in main loop - stepper_idle_counter = esp_timer_get_time() + (stepper_idle_lock_time->get() * 1000); // * 1000 because the time is in uSecs + stepper_idle = true; // esp32 work around for disable in main loop + stepper_idle_counter = esp_timer_get_time() + (stepper_idle_lock_time->get() * 1000); // * 1000 because the time is in uSecs // after idle countdown will be disabled in protocol loop } } else @@ -698,10 +696,10 @@ void st_go_idle() { // Called by planner_recalculate() when the executing block is updated by the new plan. void st_update_plan_block_parameters() { - if (pl_block != NULL) { // Ignore if at start of a new block. + if (pl_block != NULL) { // Ignore if at start of a new block. prep.recalculate_flag |= PREP_FLAG_RECALCULATE; - pl_block->entry_speed_sqr = prep.current_speed * prep.current_speed; // Update entry speed. - pl_block = NULL; // Flag st_prep_segment() to load and check active velocity profile. + pl_block->entry_speed_sqr = prep.current_speed * prep.current_speed; // Update entry speed. + pl_block = NULL; // Flag st_prep_segment() to load and check active velocity profile. } } @@ -710,32 +708,31 @@ void st_update_plan_block_parameters() { void st_parking_setup_buffer() { // Store step execution data of partially completed block, if necessary. if (prep.recalculate_flag & PREP_FLAG_HOLD_PARTIAL_BLOCK) { - prep.last_st_block_index = prep.st_block_index; + prep.last_st_block_index = prep.st_block_index; prep.last_steps_remaining = prep.steps_remaining; - prep.last_dt_remainder = prep.dt_remainder; - prep.last_step_per_mm = prep.step_per_mm; + prep.last_dt_remainder = prep.dt_remainder; + prep.last_step_per_mm = prep.step_per_mm; } // Set flags to execute a parking motion prep.recalculate_flag |= PREP_FLAG_PARKING; prep.recalculate_flag &= ~(PREP_FLAG_RECALCULATE); - pl_block = NULL; // Always reset parking motion to reload new block. + pl_block = NULL; // Always reset parking motion to reload new block. } - // Restores the step segment buffer to the normal run state after a parking motion. void st_parking_restore_buffer() { // Restore step execution data and flags of partially completed block, if necessary. if (prep.recalculate_flag & PREP_FLAG_HOLD_PARTIAL_BLOCK) { - st_prep_block = &st_block_buffer[prep.last_st_block_index]; - prep.st_block_index = prep.last_st_block_index; - prep.steps_remaining = prep.last_steps_remaining; - prep.dt_remainder = prep.last_dt_remainder; - prep.step_per_mm = prep.last_step_per_mm; + st_prep_block = &st_block_buffer[prep.last_st_block_index]; + prep.st_block_index = prep.last_st_block_index; + prep.steps_remaining = prep.last_steps_remaining; + prep.dt_remainder = prep.last_dt_remainder; + prep.step_per_mm = prep.last_step_per_mm; prep.recalculate_flag = (PREP_FLAG_HOLD_PARTIAL_BLOCK | PREP_FLAG_RECALCULATE); - prep.req_mm_increment = REQ_MM_INCREMENT_SCALAR / prep.step_per_mm; // Recompute this value. + prep.req_mm_increment = REQ_MM_INCREMENT_SCALAR / prep.step_per_mm; // Recompute this value. } else prep.recalculate_flag = false; - pl_block = NULL; // Set to reload next block. + pl_block = NULL; // Set to reload next block. } #endif @@ -752,7 +749,7 @@ void st_generate_step_dir_invert_masks() { */ // simpler with ESP32, but let's do it here for easier change management step_port_invert_mask = step_invert_mask->get(); - dir_port_invert_mask = dir_invert_mask->get(); + dir_port_invert_mask = dir_invert_mask->get(); } // Increments the step segment buffer block data ring buffer. @@ -780,7 +777,7 @@ void st_prep_buffer() { // Block step prep buffer, while in a suspend state and there is no suspend motion to execute. if (bit_istrue(sys.step_control, STEP_CONTROL_END_MOTION)) return; - while (segment_buffer_tail != segment_next_head) { // Check if we need to fill the buffer. + while (segment_buffer_tail != segment_next_head) { // Check if we need to fill the buffer. // Determine if we need to load a new planner block or if the block needs to be recomputed. if (pl_block == NULL) { // Query planner for a queued block @@ -789,7 +786,7 @@ void st_prep_buffer() { else pl_block = plan_get_current_block(); if (pl_block == NULL) { - return; // No planner blocks. Exit. + return; // No planner blocks. Exit. } // Check if we need to only recompute the velocity profile or load a new block. if (prep.recalculate_flag & PREP_FLAG_RECALCULATE) { @@ -807,7 +804,7 @@ void st_prep_buffer() { // Prepare and copy Bresenham algorithm segment data from the new planner block, so that // when the segment buffer completes the planner block, it may be discarded when the // segment buffer finishes the prepped block, but the stepper ISR is still executing it. - st_prep_block = &st_block_buffer[prep.st_block_index]; + st_prep_block = &st_block_buffer[prep.st_block_index]; st_prep_block->direction_bits = pl_block->direction_bits; uint8_t idx; #ifndef ADAPTIVE_MULTI_AXIS_STEP_SMOOTHING @@ -823,27 +820,25 @@ void st_prep_buffer() { st_prep_block->step_event_count = pl_block->step_event_count << MAX_AMASS_LEVEL; #endif // Initialize segment buffer data for generating the segments. - prep.steps_remaining = (float)pl_block->step_event_count; - prep.step_per_mm = prep.steps_remaining / pl_block->millimeters; + prep.steps_remaining = (float)pl_block->step_event_count; + prep.step_per_mm = prep.steps_remaining / pl_block->millimeters; prep.req_mm_increment = REQ_MM_INCREMENT_SCALAR / prep.step_per_mm; - prep.dt_remainder = 0.0; // Reset for new segment block + prep.dt_remainder = 0.0; // Reset for new segment block if ((sys.step_control & STEP_CONTROL_EXECUTE_HOLD) || (prep.recalculate_flag & PREP_FLAG_DECEL_OVERRIDE)) { // New block loaded mid-hold. Override planner block entry speed to enforce deceleration. - prep.current_speed = prep.exit_speed; + prep.current_speed = prep.exit_speed; pl_block->entry_speed_sqr = prep.exit_speed * prep.exit_speed; prep.recalculate_flag &= ~(PREP_FLAG_DECEL_OVERRIDE); } else prep.current_speed = sqrt(pl_block->entry_speed_sqr); - - if (spindle->isRateAdjusted() ){ // laser_mode->get() { + if (spindle->isRateAdjusted()) { // laser_mode->get() { if (pl_block->condition & PL_COND_FLAG_SPINDLE_CCW) { // Pre-compute inverse programmed rate to speed up PWM updating per step segment. - prep.inv_rate = 1.0 / pl_block->programmed_rate; + prep.inv_rate = 1.0 / pl_block->programmed_rate; st_prep_block->is_pwm_rate_adjusted = true; } } - } /* --------------------------------------------------------------------------------- Compute the velocity profile of a new planner block based on its entry and exit @@ -851,9 +846,9 @@ void st_prep_buffer() { planner has updated it. For a commanded forced-deceleration, such as from a feed hold, override the planner velocities and decelerate to the target exit speed. */ - prep.mm_complete = 0.0; // Default velocity profile complete at 0.0mm from end of block. + prep.mm_complete = 0.0; // Default velocity profile complete at 0.0mm from end of block. float inv_2_accel = 0.5 / pl_block->acceleration; - if (sys.step_control & STEP_CONTROL_EXECUTE_HOLD) { // [Forced Deceleration to Zero Velocity] + if (sys.step_control & STEP_CONTROL_EXECUTE_HOLD) { // [Forced Deceleration to Zero Velocity] // Compute velocity profile parameters for a feed hold in-progress. This profile overrides // the planner block profile, enforcing a deceleration to zero speed. prep.ramp_type = RAMP_DECEL; @@ -863,48 +858,47 @@ void st_prep_buffer() { // Deceleration through entire planner block. End of feed hold is not in this block. prep.exit_speed = sqrt(pl_block->entry_speed_sqr - 2 * pl_block->acceleration * pl_block->millimeters); } else { - prep.mm_complete = decel_dist; // End of feed hold. - prep.exit_speed = 0.0; + prep.mm_complete = decel_dist; // End of feed hold. + prep.exit_speed = 0.0; } - } else { // [Normal Operation] + } else { // [Normal Operation] // Compute or recompute velocity profile parameters of the prepped planner block. - prep.ramp_type = RAMP_ACCEL; // Initialize as acceleration ramp. + prep.ramp_type = RAMP_ACCEL; // Initialize as acceleration ramp. prep.accelerate_until = pl_block->millimeters; float exit_speed_sqr; float nominal_speed; if (sys.step_control & STEP_CONTROL_EXECUTE_SYS_MOTION) { - prep.exit_speed = exit_speed_sqr = 0.0; // Enforce stop at end of system motion. + prep.exit_speed = exit_speed_sqr = 0.0; // Enforce stop at end of system motion. } else { - exit_speed_sqr = plan_get_exec_block_exit_speed_sqr(); + exit_speed_sqr = plan_get_exec_block_exit_speed_sqr(); prep.exit_speed = sqrt(exit_speed_sqr); } - nominal_speed = plan_compute_profile_nominal_speed(pl_block); - float nominal_speed_sqr = nominal_speed * nominal_speed; - float intersect_distance = - 0.5 * (pl_block->millimeters + inv_2_accel * (pl_block->entry_speed_sqr - exit_speed_sqr)); - if (pl_block->entry_speed_sqr > nominal_speed_sqr) { // Only occurs during override reductions. + nominal_speed = plan_compute_profile_nominal_speed(pl_block); + float nominal_speed_sqr = nominal_speed * nominal_speed; + float intersect_distance = 0.5 * (pl_block->millimeters + inv_2_accel * (pl_block->entry_speed_sqr - exit_speed_sqr)); + if (pl_block->entry_speed_sqr > nominal_speed_sqr) { // Only occurs during override reductions. prep.accelerate_until = pl_block->millimeters - inv_2_accel * (pl_block->entry_speed_sqr - nominal_speed_sqr); - if (prep.accelerate_until <= 0.0) { // Deceleration-only. + if (prep.accelerate_until <= 0.0) { // Deceleration-only. prep.ramp_type = RAMP_DECEL; // prep.decelerate_after = pl_block->millimeters; // prep.maximum_speed = prep.current_speed; // Compute override block exit speed since it doesn't match the planner exit speed. prep.exit_speed = sqrt(pl_block->entry_speed_sqr - 2 * pl_block->acceleration * pl_block->millimeters); - prep.recalculate_flag |= PREP_FLAG_DECEL_OVERRIDE; // Flag to load next block as deceleration override. + prep.recalculate_flag |= PREP_FLAG_DECEL_OVERRIDE; // Flag to load next block as deceleration override. // TODO: Determine correct handling of parameters in deceleration-only. // Can be tricky since entry speed will be current speed, as in feed holds. // Also, look into near-zero speed handling issues with this. } else { // Decelerate to cruise or cruise-decelerate types. Guaranteed to intersect updated plan. prep.decelerate_after = inv_2_accel * (nominal_speed_sqr - exit_speed_sqr); - prep.maximum_speed = nominal_speed; - prep.ramp_type = RAMP_DECEL_OVERRIDE; + prep.maximum_speed = nominal_speed; + prep.ramp_type = RAMP_DECEL_OVERRIDE; } } else if (intersect_distance > 0.0) { - if (intersect_distance < pl_block->millimeters) { // Either trapezoid or triangle types + if (intersect_distance < pl_block->millimeters) { // Either trapezoid or triangle types // NOTE: For acceleration-cruise and cruise-only types, following calculation will be 0.0. prep.decelerate_after = inv_2_accel * (nominal_speed_sqr - exit_speed_sqr); - if (prep.decelerate_after < intersect_distance) { // Trapezoid type + if (prep.decelerate_after < intersect_distance) { // Trapezoid type prep.maximum_speed = nominal_speed; if (pl_block->entry_speed_sqr == nominal_speed_sqr) { // Cruise-deceleration or cruise-only type. @@ -913,25 +907,24 @@ void st_prep_buffer() { // Full-trapezoid or acceleration-cruise types prep.accelerate_until -= inv_2_accel * (nominal_speed_sqr - pl_block->entry_speed_sqr); } - } else { // Triangle type + } else { // Triangle type prep.accelerate_until = intersect_distance; prep.decelerate_after = intersect_distance; - prep.maximum_speed = sqrt(2.0 * pl_block->acceleration * intersect_distance + exit_speed_sqr); + prep.maximum_speed = sqrt(2.0 * pl_block->acceleration * intersect_distance + exit_speed_sqr); } - } else { // Deceleration-only type + } else { // Deceleration-only type prep.ramp_type = RAMP_DECEL; // prep.decelerate_after = pl_block->millimeters; // prep.maximum_speed = prep.current_speed; } - } else { // Acceleration-only type + } else { // Acceleration-only type prep.accelerate_until = 0.0; // prep.decelerate_after = 0.0; prep.maximum_speed = prep.exit_speed; } } - bit_true(sys.step_control, STEP_CONTROL_UPDATE_SPINDLE_RPM); // Force update whenever updating block. - + bit_true(sys.step_control, STEP_CONTROL_UPDATE_SPINDLE_RPM); // Force update whenever updating block. } // Initialize new segment segment_t* prep_segment = &segment_buffer[segment_buffer_head]; @@ -951,90 +944,90 @@ void st_prep_buffer() { the end of planner block (typical) or mid-block at the end of a forced deceleration, such as from a feed hold. */ - float dt_max = DT_SEGMENT; // Maximum segment time - float dt = 0.0; // Initialize segment time - float time_var = dt_max; // Time worker variable - float mm_var; // mm-Distance worker variable - float speed_var; // Speed worker variable - float mm_remaining = pl_block->millimeters; // New segment distance from end of block. - float minimum_mm = mm_remaining - prep.req_mm_increment; // Guarantee at least one step. + float dt_max = DT_SEGMENT; // Maximum segment time + float dt = 0.0; // Initialize segment time + float time_var = dt_max; // Time worker variable + float mm_var; // mm-Distance worker variable + float speed_var; // Speed worker variable + float mm_remaining = pl_block->millimeters; // New segment distance from end of block. + float minimum_mm = mm_remaining - prep.req_mm_increment; // Guarantee at least one step. if (minimum_mm < 0.0) minimum_mm = 0.0; do { switch (prep.ramp_type) { - case RAMP_DECEL_OVERRIDE: - speed_var = pl_block->acceleration * time_var; - mm_var = time_var * (prep.current_speed - 0.5 * speed_var); - mm_remaining -= mm_var; - if ((mm_remaining < prep.accelerate_until) || (mm_var <= 0)) { - // Cruise or cruise-deceleration types only for deceleration override. - mm_remaining = prep.accelerate_until; // NOTE: 0.0 at EOB - time_var = 2.0 * (pl_block->millimeters - mm_remaining) / (prep.current_speed + prep.maximum_speed); - prep.ramp_type = RAMP_CRUISE; - prep.current_speed = prep.maximum_speed; - } else // Mid-deceleration override ramp. - prep.current_speed -= speed_var; - break; - case RAMP_ACCEL: - // NOTE: Acceleration ramp only computes during first do-while loop. - speed_var = pl_block->acceleration * time_var; - mm_remaining -= time_var * (prep.current_speed + 0.5 * speed_var); - if (mm_remaining < prep.accelerate_until) { // End of acceleration ramp. - // Acceleration-cruise, acceleration-deceleration ramp junction, or end of block. - mm_remaining = prep.accelerate_until; // NOTE: 0.0 at EOB - time_var = 2.0 * (pl_block->millimeters - mm_remaining) / (prep.current_speed + prep.maximum_speed); - if (mm_remaining == prep.decelerate_after) - prep.ramp_type = RAMP_DECEL; - else - prep.ramp_type = RAMP_CRUISE; - prep.current_speed = prep.maximum_speed; - } else // Acceleration only. - prep.current_speed += speed_var; - break; - case RAMP_CRUISE: - // NOTE: mm_var used to retain the last mm_remaining for incomplete segment time_var calculations. - // NOTE: If maximum_speed*time_var value is too low, round-off can cause mm_var to not change. To - // prevent this, simply enforce a minimum speed threshold in the planner. - mm_var = mm_remaining - prep.maximum_speed * time_var; - if (mm_var < prep.decelerate_after) { // End of cruise. - // Cruise-deceleration junction or end of block. - time_var = (mm_remaining - prep.decelerate_after) / prep.maximum_speed; - mm_remaining = prep.decelerate_after; // NOTE: 0.0 at EOB - prep.ramp_type = RAMP_DECEL; - } else // Cruising only. - mm_remaining = mm_var; - break; - default: // case RAMP_DECEL: - // NOTE: mm_var used as a misc worker variable to prevent errors when near zero speed. - speed_var = pl_block->acceleration * time_var; // Used as delta speed (mm/min) - if (prep.current_speed > speed_var) { // Check if at or below zero speed. - // Compute distance from end of segment to end of block. - mm_var = mm_remaining - time_var * (prep.current_speed - 0.5 * speed_var); // (mm) - if (mm_var > prep.mm_complete) { // Typical case. In deceleration ramp. - mm_remaining = mm_var; + case RAMP_DECEL_OVERRIDE: + speed_var = pl_block->acceleration * time_var; + mm_var = time_var * (prep.current_speed - 0.5 * speed_var); + mm_remaining -= mm_var; + if ((mm_remaining < prep.accelerate_until) || (mm_var <= 0)) { + // Cruise or cruise-deceleration types only for deceleration override. + mm_remaining = prep.accelerate_until; // NOTE: 0.0 at EOB + time_var = 2.0 * (pl_block->millimeters - mm_remaining) / (prep.current_speed + prep.maximum_speed); + prep.ramp_type = RAMP_CRUISE; + prep.current_speed = prep.maximum_speed; + } else // Mid-deceleration override ramp. prep.current_speed -= speed_var; - break; // Segment complete. Exit switch-case statement. Continue do-while loop. + break; + case RAMP_ACCEL: + // NOTE: Acceleration ramp only computes during first do-while loop. + speed_var = pl_block->acceleration * time_var; + mm_remaining -= time_var * (prep.current_speed + 0.5 * speed_var); + if (mm_remaining < prep.accelerate_until) { // End of acceleration ramp. + // Acceleration-cruise, acceleration-deceleration ramp junction, or end of block. + mm_remaining = prep.accelerate_until; // NOTE: 0.0 at EOB + time_var = 2.0 * (pl_block->millimeters - mm_remaining) / (prep.current_speed + prep.maximum_speed); + if (mm_remaining == prep.decelerate_after) + prep.ramp_type = RAMP_DECEL; + else + prep.ramp_type = RAMP_CRUISE; + prep.current_speed = prep.maximum_speed; + } else // Acceleration only. + prep.current_speed += speed_var; + break; + case RAMP_CRUISE: + // NOTE: mm_var used to retain the last mm_remaining for incomplete segment time_var calculations. + // NOTE: If maximum_speed*time_var value is too low, round-off can cause mm_var to not change. To + // prevent this, simply enforce a minimum speed threshold in the planner. + mm_var = mm_remaining - prep.maximum_speed * time_var; + if (mm_var < prep.decelerate_after) { // End of cruise. + // Cruise-deceleration junction or end of block. + time_var = (mm_remaining - prep.decelerate_after) / prep.maximum_speed; + mm_remaining = prep.decelerate_after; // NOTE: 0.0 at EOB + prep.ramp_type = RAMP_DECEL; + } else // Cruising only. + mm_remaining = mm_var; + break; + default: // case RAMP_DECEL: + // NOTE: mm_var used as a misc worker variable to prevent errors when near zero speed. + speed_var = pl_block->acceleration * time_var; // Used as delta speed (mm/min) + if (prep.current_speed > speed_var) { // Check if at or below zero speed. + // Compute distance from end of segment to end of block. + mm_var = mm_remaining - time_var * (prep.current_speed - 0.5 * speed_var); // (mm) + if (mm_var > prep.mm_complete) { // Typical case. In deceleration ramp. + mm_remaining = mm_var; + prep.current_speed -= speed_var; + break; // Segment complete. Exit switch-case statement. Continue do-while loop. + } } - } - // Otherwise, at end of block or end of forced-deceleration. - time_var = 2.0 * (mm_remaining - prep.mm_complete) / (prep.current_speed + prep.exit_speed); - mm_remaining = prep.mm_complete; - prep.current_speed = prep.exit_speed; + // Otherwise, at end of block or end of forced-deceleration. + time_var = 2.0 * (mm_remaining - prep.mm_complete) / (prep.current_speed + prep.exit_speed); + mm_remaining = prep.mm_complete; + prep.current_speed = prep.exit_speed; } - dt += time_var; // Add computed ramp time to total segment time. + dt += time_var; // Add computed ramp time to total segment time. if (dt < dt_max) { - time_var = dt_max - dt; // **Incomplete** At ramp junction. + time_var = dt_max - dt; // **Incomplete** At ramp junction. } else { - if (mm_remaining > minimum_mm) { // Check for very slow segments with zero steps. + if (mm_remaining > minimum_mm) { // Check for very slow segments with zero steps. // Increase segment time to ensure at least one step in segment. Override and loop // through distance calculations until minimum_mm or mm_complete. dt_max += DT_SEGMENT; time_var = dt_max - dt; } else { - break; // **Complete** Exit loop. Segment execution time maxed. + break; // **Complete** Exit loop. Segment execution time maxed. } } - } while (mm_remaining > prep.mm_complete); // **Complete** Exit loop. Profile complete. + } while (mm_remaining > prep.mm_complete); // **Complete** Exit loop. Profile complete. /* ----------------------------------------------------------------------------------- Compute spindle speed PWM output for step segment @@ -1053,13 +1046,12 @@ void st_prep_buffer() { prep.current_spindle_rpm = rpm; } else { - sys.spindle_speed = 0.0; + sys.spindle_speed = 0.0; prep.current_spindle_rpm = 0.0; - } bit_false(sys.step_control, STEP_CONTROL_UPDATE_SPINDLE_RPM); } - prep_segment->spindle_rpm = prep.current_spindle_rpm; // Reload segment PWM value + prep_segment->spindle_rpm = prep.current_spindle_rpm; // Reload segment PWM value /* ----------------------------------------------------------------------------------- Compute segment step rate, steps to execute, and apply necessary rate corrections. @@ -1071,10 +1063,10 @@ void st_prep_buffer() { Fortunately, this scenario is highly unlikely and unrealistic in CNC machines supported by Grbl (i.e. exceeding 10 meters axis travel at 200 step/mm). */ - float step_dist_remaining = prep.step_per_mm * mm_remaining; // Convert mm_remaining to steps - float n_steps_remaining = ceil(step_dist_remaining); // Round-up current steps remaining - float last_n_steps_remaining = ceil(prep.steps_remaining); // Round-up last steps remaining - prep_segment->n_step = last_n_steps_remaining - n_steps_remaining; // Compute number of steps to execute. + float step_dist_remaining = prep.step_per_mm * mm_remaining; // Convert mm_remaining to steps + float n_steps_remaining = ceil(step_dist_remaining); // Round-up current steps remaining + float last_n_steps_remaining = ceil(prep.steps_remaining); // Round-up last steps remaining + prep_segment->n_step = last_n_steps_remaining - n_steps_remaining; // Compute number of steps to execute. // Bail if we are at the end of a feed hold and don't have a step to execute. if (prep_segment->n_step == 0) { if (sys.step_control & STEP_CONTROL_EXECUTE_HOLD) { @@ -1085,7 +1077,7 @@ void st_prep_buffer() { if (!(prep.recalculate_flag & PREP_FLAG_PARKING)) prep.recalculate_flag |= PREP_FLAG_HOLD_PARTIAL_BLOCK; #endif - return; // Segment not generated, but current step data still retained. + return; // Segment not generated, but current step data still retained. } } // Compute segment step rate. Since steps are integers and mm distances traveled are not, @@ -1096,10 +1088,10 @@ void st_prep_buffer() { // adjusts the whole segment rate to keep step output exact. These rate adjustments are // typically very small and do not adversely effect performance, but ensures that Grbl // outputs the exact acceleration and velocity profiles as computed by the planner. - dt += prep.dt_remainder; // Apply previous segment partial step execute time - float inv_rate = dt / (last_n_steps_remaining - step_dist_remaining); // Compute adjusted step rate inverse + dt += prep.dt_remainder; // Apply previous segment partial step execute time + float inv_rate = dt / (last_n_steps_remaining - step_dist_remaining); // Compute adjusted step rate inverse // Compute CPU cycles per step for the prepped segment. - uint32_t cycles = ceil((TICKS_PER_MICROSECOND * 1000000 * 60) * inv_rate); // (cycles/step) + uint32_t cycles = ceil((TICKS_PER_MICROSECOND * 1000000 * 60) * inv_rate); // (cycles/step) #ifdef ADAPTIVE_MULTI_AXIS_STEP_SMOOTHING // Compute step timing and multi-axis smoothing level. // NOTE: AMASS overdrives the timer with each level, so only one prescalar is required. @@ -1116,23 +1108,23 @@ void st_prep_buffer() { prep_segment->n_step <<= prep_segment->amass_level; } if (cycles < (1UL << 16)) { - prep_segment->cycles_per_tick = cycles; // < 65536 (4.1ms @ 16MHz) + prep_segment->cycles_per_tick = cycles; // < 65536 (4.1ms @ 16MHz) } else { - prep_segment->cycles_per_tick = 0xffff; // Just set the slowest speed possible. + prep_segment->cycles_per_tick = 0xffff; // Just set the slowest speed possible. } #else // Compute step timing and timer prescalar for normal step generation. - if (cycles < (1UL << 16)) { // < 65536 (4.1ms @ 16MHz) - prep_segment->prescaler = 1; // prescaler: 0 + if (cycles < (1UL << 16)) { // < 65536 (4.1ms @ 16MHz) + prep_segment->prescaler = 1; // prescaler: 0 prep_segment->cycles_per_tick = cycles; - } else if (cycles < (1UL << 19)) { // < 524288 (32.8ms@16MHz) - prep_segment->prescaler = 2; // prescaler: 8 + } else if (cycles < (1UL << 19)) { // < 524288 (32.8ms@16MHz) + prep_segment->prescaler = 2; // prescaler: 8 prep_segment->cycles_per_tick = cycles >> 3; } else { - prep_segment->prescaler = 3; // prescaler: 64 - if (cycles < (1UL << 22)) // < 4194304 (262ms@16MHz) - prep_segment->cycles_per_tick = cycles >> 6; - else // Just set the slowest speed possible. (Around 4 step/sec.) + prep_segment->prescaler = 3; // prescaler: 64 + if (cycles < (1UL << 22)) // < 4194304 (262ms@16MHz) + prep_segment->cycles_per_tick = cycles >> 6; + else // Just set the slowest speed possible. (Around 4 step/sec.) prep_segment->cycles_per_tick = 0xffff; } #endif @@ -1142,12 +1134,12 @@ void st_prep_buffer() { segment_next_head = 0; // Update the appropriate planner and segment data. pl_block->millimeters = mm_remaining; - prep.steps_remaining = n_steps_remaining; - prep.dt_remainder = (n_steps_remaining - step_dist_remaining) * inv_rate; + prep.steps_remaining = n_steps_remaining; + prep.dt_remainder = (n_steps_remaining - step_dist_remaining) * inv_rate; // Check for exit conditions and flag to load next planner block. if (mm_remaining == prep.mm_complete) { // End of planner block or forced-termination. No more distance to be executed. - if (mm_remaining > 0.0) { // At end of forced-termination. + if (mm_remaining > 0.0) { // At end of forced-termination. // Reset prep parameters for resuming and then bail. Allow the stepper ISR to complete // the segment queue, where realtime protocol will set new state upon receiving the // cycle stop flag from the ISR. Prep_segment is blocked until then. @@ -1156,22 +1148,20 @@ void st_prep_buffer() { if (!(prep.recalculate_flag & PREP_FLAG_PARKING)) prep.recalculate_flag |= PREP_FLAG_HOLD_PARTIAL_BLOCK; #endif - return; // Bail! - } else { // End of planner block + return; // Bail! + } else { // End of planner block // The planner block is complete. All steps are set to be executed in the segment buffer. if (sys.step_control & STEP_CONTROL_EXECUTE_SYS_MOTION) { bit_true(sys.step_control, STEP_CONTROL_END_MOTION); return; } - pl_block = NULL; // Set pointer to indicate check and load next planner block. + pl_block = NULL; // Set pointer to indicate check and load next planner block. plan_discard_current_block(); } } } } - - // Called by realtime status reporting to fetch the current speed being executed. This value // however is not exactly the current speed, but the speed computed in the last step segment // in the segment buffer. It will always be behind by up to the number of segment blocks (-1) @@ -1186,7 +1176,7 @@ void IRAM_ATTR Stepper_Timer_WritePeriod(uint64_t alarm_val) { #ifdef USE_I2S_OUT_STREAM // 1 tick = F_TIMERS / F_STEPPER_TIMER // Pulse ISR is called for each tick of alarm_val. - i2s_out_set_pulse_period(alarm_val / 60); + i2s_out_set_pulse_period(alarm_val); #else timer_set_alarm_value(STEP_TIMER_GROUP, STEP_TIMER_INDEX, alarm_val); #endif @@ -1216,15 +1206,15 @@ void IRAM_ATTR Stepper_Timer_Stop() { #endif } -bool get_stepper_disable() { // returns true if steppers are disabled +bool get_stepper_disable() { // returns true if steppers are disabled bool disabled = false; #ifdef STEPPERS_DISABLE_PIN disabled = digitalRead(STEPPERS_DISABLE_PIN); #else - return false; // thery are never disabled if there is no pin defined + return false; // thery are never disabled if there is no pin defined #endif if (step_enable_invert->get()) { - disabled = !disabled; // Apply pin invert. + disabled = !disabled; // Apply pin invert. } return disabled; } diff --git a/Grbl_Esp32/stepper.h b/Grbl_Esp32/src/Stepper.h similarity index 83% rename from Grbl_Esp32/stepper.h rename to Grbl_Esp32/src/Stepper.h index c3666af3..99cd0b23 100644 --- a/Grbl_Esp32/stepper.h +++ b/Grbl_Esp32/src/Stepper.h @@ -1,5 +1,7 @@ +#pragma once + /* - stepper.h - stepper motor driver: executes motion plans of planner.c using the stepper motors + Stepper.h - stepper motor driver: executes motion plans of planner.c using the stepper motors Part of Grbl Copyright (c) 2011-2016 Sungeun K. Jeon for Gnea Research LLC @@ -22,20 +24,15 @@ along with Grbl. If not, see . */ -#ifndef stepper_h -#define stepper_h - #ifndef SEGMENT_BUFFER_SIZE - #define SEGMENT_BUFFER_SIZE 6 +# define SEGMENT_BUFFER_SIZE 6 #endif - - -#include "grbl.h" -#include "config.h" +#include "Grbl.h" +#include "Config.h" // Some useful constants. -#define DT_SEGMENT (1.0/(ACCELERATION_TICKS_PER_SECOND*60.0)) // min/segment +#define DT_SEGMENT (1.0 / (ACCELERATION_TICKS_PER_SECOND * 60.0)) // min/segment #define REQ_MM_INCREMENT_SCALAR 1.25 #define RAMP_ACCEL 0 #define RAMP_CRUISE 1 @@ -60,21 +57,21 @@ #define MAX_AMASS_LEVEL 3 // AMASS_LEVEL0: Normal operation. No AMASS. No upper cutoff frequency. Starts at LEVEL1 cutoff frequency. // Note ESP32 use F_STEPPER_TIMER rather than the AVR F_CPU -#define AMASS_LEVEL1 (F_STEPPER_TIMER/8000) // Over-drives ISR (x2). Defined as F_CPU/(Cutoff frequency in Hz) -#define AMASS_LEVEL2 (F_STEPPER_TIMER/4000) // Over-drives ISR (x4) -#define AMASS_LEVEL3 (F_STEPPER_TIMER/2000) // Over-drives ISR (x8) +#define AMASS_LEVEL1 (F_STEPPER_TIMER / 8000) // Over-drives ISR (x2). Defined as F_CPU/(Cutoff frequency in Hz) +#define AMASS_LEVEL2 (F_STEPPER_TIMER / 4000) // Over-drives ISR (x4) +#define AMASS_LEVEL3 (F_STEPPER_TIMER / 2000) // Over-drives ISR (x8) #if MAX_AMASS_LEVEL <= 0 - error "AMASS must have 1 or more levels to operate correctly." +error "AMASS must have 1 or more levels to operate correctly." #endif //#endif #define STEP_TIMER_GROUP TIMER_GROUP_0 #define STEP_TIMER_INDEX TIMER_0 -// esp32 work around for diable in main loop -extern uint64_t stepper_idle_counter; -extern bool stepper_idle; + // esp32 work around for diable in main loop + extern uint64_t stepper_idle_counter; +extern bool stepper_idle; extern uint8_t ganged_mode; @@ -112,7 +109,7 @@ void st_update_plan_block_parameters(); float st_get_realtime_rate(); // disable (or enable) steppers via STEPPERS_DISABLE_PIN -bool get_stepper_disable(); // returns the state of the pin +bool get_stepper_disable(); // returns the state of the pin void set_stepper_pins_on(uint8_t onMask); void set_direction_pins_on(uint8_t onMask); @@ -120,5 +117,3 @@ void set_direction_pins_on(uint8_t onMask); void Stepper_Timer_WritePeriod(uint64_t alarm_val); void Stepper_Timer_Start(); void Stepper_Timer_Stop(); - -#endif \ No newline at end of file diff --git a/Grbl_Esp32/system.cpp b/Grbl_Esp32/src/System.cpp similarity index 80% rename from Grbl_Esp32/system.cpp rename to Grbl_Esp32/src/System.cpp index f735cd75..067e9a82 100644 --- a/Grbl_Esp32/system.cpp +++ b/Grbl_Esp32/src/System.cpp @@ -1,5 +1,5 @@ /* - system.cpp - Header for system level commands and real-time processes + System.cpp - Header for system level commands and real-time processes Part of Grbl Copyright (c) 2014-2016 Sungeun K. Jeon for Gnea Research LLC @@ -18,13 +18,13 @@ along with Grbl. If not, see . */ -#include "grbl.h" -#include "config.h" +#include "Grbl.h" +#include "Config.h" -xQueueHandle control_sw_queue; // used by control switch debouncing -bool debouncing = false; // debouncing in process +xQueueHandle control_sw_queue; // used by control switch debouncing +bool debouncing = false; // debouncing in process -void system_ini() { // Renamed from system_init() due to conflict with esp32 files +void system_ini() { // Renamed from system_init() due to conflict with esp32 files // setup control inputs #ifdef CONTROL_SAFETY_DOOR_PIN @@ -70,7 +70,7 @@ void system_ini() { // Renamed from system_init() due to conflict with esp32 fil "controlCheckTask", 2048, NULL, - 5, // priority + 5, // priority NULL); #endif @@ -81,19 +81,19 @@ void system_ini() { // Renamed from system_init() due to conflict with esp32 fil // Setup USER_DIGITAL_PINs controlled by M62 and M63 #ifdef USER_DIGITAL_PIN_1 pinMode(USER_DIGITAL_PIN_1, OUTPUT); - sys_io_control(bit(1), false); // turn off + sys_io_control(bit(1), false); // turn off #endif #ifdef USER_DIGITAL_PIN_2 pinMode(USER_DIGITAL_PIN_2, OUTPUT); - sys_io_control(bit(2), false); // turn off + sys_io_control(bit(2), false); // turn off #endif #ifdef USER_DIGITAL_PIN_3 pinMode(USER_DIGITAL_PIN_3, OUTPUT); - sys_io_control(bit(3), false); // turn off + sys_io_control(bit(3), false); // turn off #endif #ifdef USER_DIGITAL_PIN_4 pinMode(USER_DIGITAL_PIN_4, OUTPUT); - sys_io_control(bit(4), false); // turn off + sys_io_control(bit(4), false); // turn off #endif } @@ -102,8 +102,8 @@ void system_ini() { // Renamed from system_init() due to conflict with esp32 fil void controlCheckTask(void* pvParameters) { while (true) { int evt; - xQueueReceive(control_sw_queue, &evt, portMAX_DELAY); // block until receive queue - vTaskDelay(CONTROL_SW_DEBOUNCE_PERIOD); // delay a while + xQueueReceive(control_sw_queue, &evt, portMAX_DELAY); // block until receive queue + vTaskDelay(CONTROL_SW_DEBOUNCE_PERIOD); // delay a while uint8_t pin = system_control_get_state(); if (pin) system_exec_control_pin(pin); @@ -116,7 +116,7 @@ void IRAM_ATTR isr_control_inputs() { #ifdef ENABLE_CONTROL_SW_DEBOUNCE // we will start a task that will recheck the switches after a small delay int evt; - if (!debouncing) { // prevent resending until debounce is done + if (!debouncing) { // prevent resending until debounce is done debouncing = true; xQueueSendFromISR(control_sw_queue, &evt, NULL); } @@ -131,7 +131,7 @@ uint8_t system_check_safety_door_ajar() { #ifdef ENABLE_SAFETY_DOOR_INPUT_PIN return (system_control_get_state() & CONTROL_PIN_INDEX_SAFETY_DOOR); #else - return (false); // Input pin not enabled, so just return that it's closed. + return (false); // Input pin not enabled, so just return that it's closed. #endif } @@ -192,7 +192,6 @@ void system_clear_exec_accessory_overrides() { //SREG = sreg; } - void system_flag_wco_change() { #ifdef FORCE_BUFFER_SYNC_DURING_WCO_CHANGE protocol_buffer_synchronize(); @@ -200,7 +199,6 @@ void system_flag_wco_change() { sys.report_wco_counter = 0; } - // Returns machine position of axis 'idx'. Must be sent a 'step' array. // NOTE: If motor steps and machine position are not in the same coordinate frame, this function // serves as a central place to compute the transformation. @@ -236,16 +234,20 @@ uint8_t system_check_travel_limits(float* target) { uint8_t mask = homing_dir_mask->get(); // When homing forced set origin is enabled, soft limits checks need to account for directionality. if (bit_istrue(mask, bit(idx))) { - if (target[idx] < 0 || target[idx] > travel) return (true); + if (target[idx] < 0 || target[idx] > travel) + return (true); } else { - if (target[idx] > 0 || target[idx] < -travel) return (true); + if (target[idx] > 0 || target[idx] < -travel) + return (true); } #else -#ifdef HOMING_FORCE_POSITIVE_SPACE - if (target[idx] < 0 || target[idx] > travel) return (true); -#else - if (target[idx] > 0 || target[idx] < -travel) return (true); -#endif +# ifdef HOMING_FORCE_POSITIVE_SPACE + if (target[idx] < 0 || target[idx] > travel) + return (true); +# else + if (target[idx] > 0 || target[idx] < -travel) + return (true); +# endif #endif } return (false); @@ -255,40 +257,48 @@ uint8_t system_check_travel_limits(float* target) { // triggered is 1 and not triggered is 0. Invert mask is applied. Bitfield organization is // defined by the CONTROL_PIN_INDEX in the header file. uint8_t system_control_get_state() { - uint8_t defined_pin_mask = 0; // a mask of defined pins - uint8_t control_state = 0; - + uint8_t defined_pin_mask = 0; // a mask of defined pins + uint8_t control_state = 0; + #ifdef CONTROL_SAFETY_DOOR_PIN defined_pin_mask |= CONTROL_PIN_INDEX_SAFETY_DOOR; - if (digitalRead(CONTROL_SAFETY_DOOR_PIN)) control_state |= CONTROL_PIN_INDEX_SAFETY_DOOR; + if (digitalRead(CONTROL_SAFETY_DOOR_PIN)) + control_state |= CONTROL_PIN_INDEX_SAFETY_DOOR; #endif #ifdef CONTROL_RESET_PIN defined_pin_mask |= CONTROL_PIN_INDEX_RESET; - if (digitalRead(CONTROL_RESET_PIN)) control_state |= CONTROL_PIN_INDEX_RESET; + if (digitalRead(CONTROL_RESET_PIN)) + control_state |= CONTROL_PIN_INDEX_RESET; #endif #ifdef CONTROL_FEED_HOLD_PIN defined_pin_mask |= CONTROL_PIN_INDEX_FEED_HOLD; - if (digitalRead(CONTROL_FEED_HOLD_PIN)) control_state |= CONTROL_PIN_INDEX_FEED_HOLD; + if (digitalRead(CONTROL_FEED_HOLD_PIN)) + control_state |= CONTROL_PIN_INDEX_FEED_HOLD; #endif #ifdef CONTROL_CYCLE_START_PIN defined_pin_mask |= CONTROL_PIN_INDEX_CYCLE_START; - if (digitalRead(CONTROL_CYCLE_START_PIN)) control_state |= CONTROL_PIN_INDEX_CYCLE_START; + if (digitalRead(CONTROL_CYCLE_START_PIN)) + control_state |= CONTROL_PIN_INDEX_CYCLE_START; #endif #ifdef MACRO_BUTTON_0_PIN defined_pin_mask |= CONTROL_PIN_INDEX_MACRO_0; - if (digitalRead(MACRO_BUTTON_0_PIN)) control_state |= CONTROL_PIN_INDEX_MACRO_0; + if (digitalRead(MACRO_BUTTON_0_PIN)) + control_state |= CONTROL_PIN_INDEX_MACRO_0; #endif #ifdef MACRO_BUTTON_1_PIN defined_pin_mask |= CONTROL_PIN_INDEX_MACRO_1; - if (digitalRead(MACRO_BUTTON_1_PIN)) control_state |= CONTROL_PIN_INDEX_MACRO_1; + if (digitalRead(MACRO_BUTTON_1_PIN)) + control_state |= CONTROL_PIN_INDEX_MACRO_1; #endif #ifdef MACRO_BUTTON_2_PIN defined_pin_mask |= CONTROL_PIN_INDEX_MACRO_2; - if (digitalRead(MACRO_BUTTON_2_PIN)) control_state |= CONTROL_PIN_INDEX_MACRO_2; + if (digitalRead(MACRO_BUTTON_2_PIN)) + control_state |= CONTROL_PIN_INDEX_MACRO_2; #endif #ifdef MACRO_BUTTON_3_PIN defined_pin_mask |= CONTROL_PIN_INDEX_MACRO_3; - if (digitalRead(MACRO_BUTTON_3_PIN)) control_state |= CONTROL_PIN_INDEX_MACRO_3; + if (digitalRead(MACRO_BUTTON_3_PIN)) + control_state |= CONTROL_PIN_INDEX_MACRO_3; #endif #ifdef INVERT_CONTROL_PIN_MASK control_state ^= (INVERT_CONTROL_PIN_MASK & defined_pin_mask); @@ -309,22 +319,22 @@ void system_exec_control_pin(uint8_t pin) { bit_true(sys_rt_exec_state, EXEC_SAFETY_DOOR); #ifdef MACRO_BUTTON_0_PIN else if (bit_istrue(pin, CONTROL_PIN_INDEX_MACRO_0)) { - user_defined_macro(CONTROL_PIN_INDEX_MACRO_0); // function must be implemented by user + user_defined_macro(CONTROL_PIN_INDEX_MACRO_0); // function must be implemented by user } #endif #ifdef MACRO_BUTTON_1_PIN else if (bit_istrue(pin, CONTROL_PIN_INDEX_MACRO_1)) { - user_defined_macro(CONTROL_PIN_INDEX_MACRO_1); // function must be implemented by user + user_defined_macro(CONTROL_PIN_INDEX_MACRO_1); // function must be implemented by user } #endif #ifdef MACRO_BUTTON_2_PIN else if (bit_istrue(pin, CONTROL_PIN_INDEX_MACRO_2)) { - user_defined_macro(CONTROL_PIN_INDEX_MACRO_2); // function must be implemented by user + user_defined_macro(CONTROL_PIN_INDEX_MACRO_2); // function must be implemented by user } #endif #ifdef MACRO_BUTTON_3_PIN else if (bit_istrue(pin, CONTROL_PIN_INDEX_MACRO_3)) { - user_defined_macro(CONTROL_PIN_INDEX_MACRO_3); // function must be implemented by user + user_defined_macro(CONTROL_PIN_INDEX_MACRO_3); // function must be implemented by user } #endif } @@ -374,12 +384,11 @@ void fast_sys_io_control(uint8_t io_num_mask, bool turnOn) { #endif } - // Call this function to get an RMT channel number // returns -1 for error int8_t sys_get_next_RMT_chan_num() { - static uint8_t next_RMT_chan_num = 0; // channels 0-7 are valid - if (next_RMT_chan_num < 8) // 7 is the max PWM channel number + static uint8_t next_RMT_chan_num = 0; // channels 0-7 are valid + if (next_RMT_chan_num < 8) // 7 is the max PWM channel number return next_RMT_chan_num++; else { grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_ERROR, "Error: out of RMT channels"); @@ -387,7 +396,6 @@ int8_t sys_get_next_RMT_chan_num() { } } - /* This returns an unused pwm channel. The 8 channels share 4 timers, so pairs 0,1 & 2,3 , etc @@ -398,8 +406,8 @@ int8_t sys_get_next_RMT_chan_num() { TODO: Make this more robust. */ int8_t sys_get_next_PWM_chan_num() { - static uint8_t next_PWM_chan_num = 2; // start at 2 to avoid spindle - if (next_PWM_chan_num < 8) // 7 is the max PWM channel number + static uint8_t next_PWM_chan_num = 2; // start at 2 to avoid spindle + if (next_PWM_chan_num < 8) // 7 is the max PWM channel number return next_PWM_chan_num++; else { grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_ERROR, "Error: out of PWM channels"); diff --git a/Grbl_Esp32/system.h b/Grbl_Esp32/src/System.h similarity index 56% rename from Grbl_Esp32/system.h rename to Grbl_Esp32/src/System.h index aa102c24..b652c05b 100644 --- a/Grbl_Esp32/system.h +++ b/Grbl_Esp32/src/System.h @@ -1,5 +1,7 @@ +#pragma once + /* - system.h - Header for system level commands and real-time processes + System.h - Header for system level commands and real-time processes Part of Grbl Copyright (c) 2014-2016 Sungeun K. Jeon for Gnea Research LLC @@ -18,11 +20,7 @@ along with Grbl. If not, see . */ -#ifndef system_h -#define system_h -#include "grbl.h" -#include "tdef.h" -#include "commands.h" +#include "Grbl.h" // Define global system variables typedef struct { @@ -40,7 +38,7 @@ typedef struct { uint8_t report_ovr_counter; // Tracks when to add override data to status reports. uint8_t report_wco_counter; // Tracks when to add work coordinate offset data to status reports. #ifdef ENABLE_PARKING_OVERRIDE_CONTROL - uint8_t override_ctrl; // Tracks override control states. + uint8_t override_ctrl; // Tracks override control states. #endif uint32_t spindle_speed; @@ -48,98 +46,95 @@ typedef struct { } system_t; extern system_t sys; - - - // Define system executor bit map. Used internally by realtime protocol as realtime command flags, // which notifies the main program to execute the specified realtime command asynchronously. // NOTE: The system executor uses an unsigned 8-bit volatile variable (8 flag limit.) The default // flags are always false, so the realtime protocol only needs to check for a non-zero value to // know when there is a realtime command to execute. -#define EXEC_STATUS_REPORT bit(0) // bitmask 00000001 -#define EXEC_CYCLE_START bit(1) // bitmask 00000010 -#define EXEC_CYCLE_STOP bit(2) // bitmask 00000100 -#define EXEC_FEED_HOLD bit(3) // bitmask 00001000 -#define EXEC_RESET bit(4) // bitmask 00010000 -#define EXEC_SAFETY_DOOR bit(5) // bitmask 00100000 -#define EXEC_MOTION_CANCEL bit(6) // bitmask 01000000 -#define EXEC_SLEEP bit(7) // bitmask 10000000 +#define EXEC_STATUS_REPORT bit(0) // bitmask 00000001 +#define EXEC_CYCLE_START bit(1) // bitmask 00000010 +#define EXEC_CYCLE_STOP bit(2) // bitmask 00000100 +#define EXEC_FEED_HOLD bit(3) // bitmask 00001000 +#define EXEC_RESET bit(4) // bitmask 00010000 +#define EXEC_SAFETY_DOOR bit(5) // bitmask 00100000 +#define EXEC_MOTION_CANCEL bit(6) // bitmask 01000000 +#define EXEC_SLEEP bit(7) // bitmask 10000000 // Alarm executor codes. Valid values (1-255). Zero is reserved. -#define EXEC_ALARM_HARD_LIMIT 1 -#define EXEC_ALARM_SOFT_LIMIT 2 -#define EXEC_ALARM_ABORT_CYCLE 3 -#define EXEC_ALARM_PROBE_FAIL_INITIAL 4 -#define EXEC_ALARM_PROBE_FAIL_CONTACT 5 -#define EXEC_ALARM_HOMING_FAIL_RESET 6 -#define EXEC_ALARM_HOMING_FAIL_DOOR 7 -#define EXEC_ALARM_HOMING_FAIL_PULLOFF 8 +#define EXEC_ALARM_HARD_LIMIT 1 +#define EXEC_ALARM_SOFT_LIMIT 2 +#define EXEC_ALARM_ABORT_CYCLE 3 +#define EXEC_ALARM_PROBE_FAIL_INITIAL 4 +#define EXEC_ALARM_PROBE_FAIL_CONTACT 5 +#define EXEC_ALARM_HOMING_FAIL_RESET 6 +#define EXEC_ALARM_HOMING_FAIL_DOOR 7 +#define EXEC_ALARM_HOMING_FAIL_PULLOFF 8 #define EXEC_ALARM_HOMING_FAIL_APPROACH 9 -#define EXEC_ALARM_SPINDLE_CONTROL 10 +#define EXEC_ALARM_SPINDLE_CONTROL 10 // Override bit maps. Realtime bitflags to control feed, rapid, spindle, and coolant overrides. // Spindle/coolant and feed/rapids are separated into two controlling flag variables. -#define EXEC_FEED_OVR_RESET bit(0) -#define EXEC_FEED_OVR_COARSE_PLUS bit(1) -#define EXEC_FEED_OVR_COARSE_MINUS bit(2) -#define EXEC_FEED_OVR_FINE_PLUS bit(3) -#define EXEC_FEED_OVR_FINE_MINUS bit(4) -#define EXEC_RAPID_OVR_RESET bit(5) -#define EXEC_RAPID_OVR_MEDIUM bit(6) -#define EXEC_RAPID_OVR_LOW bit(7) +#define EXEC_FEED_OVR_RESET bit(0) +#define EXEC_FEED_OVR_COARSE_PLUS bit(1) +#define EXEC_FEED_OVR_COARSE_MINUS bit(2) +#define EXEC_FEED_OVR_FINE_PLUS bit(3) +#define EXEC_FEED_OVR_FINE_MINUS bit(4) +#define EXEC_RAPID_OVR_RESET bit(5) +#define EXEC_RAPID_OVR_MEDIUM bit(6) +#define EXEC_RAPID_OVR_LOW bit(7) // #define EXEC_RAPID_OVR_EXTRA_LOW bit(*) // *NOT SUPPORTED* -#define EXEC_SPINDLE_OVR_RESET bit(0) -#define EXEC_SPINDLE_OVR_COARSE_PLUS bit(1) -#define EXEC_SPINDLE_OVR_COARSE_MINUS bit(2) -#define EXEC_SPINDLE_OVR_FINE_PLUS bit(3) -#define EXEC_SPINDLE_OVR_FINE_MINUS bit(4) -#define EXEC_SPINDLE_OVR_STOP bit(5) -#define EXEC_COOLANT_FLOOD_OVR_TOGGLE bit(6) -#define EXEC_COOLANT_MIST_OVR_TOGGLE bit(7) +#define EXEC_SPINDLE_OVR_RESET bit(0) +#define EXEC_SPINDLE_OVR_COARSE_PLUS bit(1) +#define EXEC_SPINDLE_OVR_COARSE_MINUS bit(2) +#define EXEC_SPINDLE_OVR_FINE_PLUS bit(3) +#define EXEC_SPINDLE_OVR_FINE_MINUS bit(4) +#define EXEC_SPINDLE_OVR_STOP bit(5) +#define EXEC_COOLANT_FLOOD_OVR_TOGGLE bit(6) +#define EXEC_COOLANT_MIST_OVR_TOGGLE bit(7) // Define system state bit map. The state variable primarily tracks the individual functions // of Grbl to manage each without overlapping. It is also used as a messaging flag for // critical events. -#define STATE_IDLE 0 // Must be zero. No flags. -#define STATE_ALARM bit(0) // In alarm state. Locks out all g-code processes. Allows settings access. -#define STATE_CHECK_MODE bit(1) // G-code check mode. Locks out planner and motion only. -#define STATE_HOMING bit(2) // Performing homing cycle -#define STATE_CYCLE bit(3) // Cycle is running or motions are being executed. -#define STATE_HOLD bit(4) // Active feed hold -#define STATE_JOG bit(5) // Jogging mode. -#define STATE_SAFETY_DOOR bit(6) // Safety door is ajar. Feed holds and de-energizes system. -#define STATE_SLEEP bit(7) // Sleep state. +#define STATE_IDLE 0 // Must be zero. No flags. +#define STATE_ALARM bit(0) // In alarm state. Locks out all g-code processes. Allows settings access. +#define STATE_CHECK_MODE bit(1) // G-code check mode. Locks out planner and motion only. +#define STATE_HOMING bit(2) // Performing homing cycle +#define STATE_CYCLE bit(3) // Cycle is running or motions are being executed. +#define STATE_HOLD bit(4) // Active feed hold +#define STATE_JOG bit(5) // Jogging mode. +#define STATE_SAFETY_DOOR bit(6) // Safety door is ajar. Feed holds and de-energizes system. +#define STATE_SLEEP bit(7) // Sleep state. // Define system suspend flags. Used in various ways to manage suspend states and procedures. -#define SUSPEND_DISABLE 0 // Must be zero. -#define SUSPEND_HOLD_COMPLETE bit(0) // Indicates initial feed hold is complete. -#define SUSPEND_RESTART_RETRACT bit(1) // Flag to indicate a retract from a restore parking motion. -#define SUSPEND_RETRACT_COMPLETE bit(2) // (Safety door only) Indicates retraction and de-energizing is complete. -#define SUSPEND_INITIATE_RESTORE bit(3) // (Safety door only) Flag to initiate resume procedures from a cycle start. -#define SUSPEND_RESTORE_COMPLETE bit(4) // (Safety door only) Indicates ready to resume normal operation. -#define SUSPEND_SAFETY_DOOR_AJAR bit(5) // Tracks safety door state for resuming. -#define SUSPEND_MOTION_CANCEL bit(6) // Indicates a canceled resume motion. Currently used by probing routine. -#define SUSPEND_JOG_CANCEL bit(7) // Indicates a jog cancel in process and to reset buffers when complete. +#define SUSPEND_DISABLE 0 // Must be zero. +#define SUSPEND_HOLD_COMPLETE bit(0) // Indicates initial feed hold is complete. +#define SUSPEND_RESTART_RETRACT bit(1) // Flag to indicate a retract from a restore parking motion. +#define SUSPEND_RETRACT_COMPLETE bit(2) // (Safety door only) Indicates retraction and de-energizing is complete. +#define SUSPEND_INITIATE_RESTORE bit(3) // (Safety door only) Flag to initiate resume procedures from a cycle start. +#define SUSPEND_RESTORE_COMPLETE bit(4) // (Safety door only) Indicates ready to resume normal operation. +#define SUSPEND_SAFETY_DOOR_AJAR bit(5) // Tracks safety door state for resuming. +#define SUSPEND_MOTION_CANCEL bit(6) // Indicates a canceled resume motion. Currently used by probing routine. +#define SUSPEND_JOG_CANCEL bit(7) // Indicates a jog cancel in process and to reset buffers when complete. // Define step segment generator state flags. -#define STEP_CONTROL_NORMAL_OP 0 // Must be zero. -#define STEP_CONTROL_END_MOTION bit(0) -#define STEP_CONTROL_EXECUTE_HOLD bit(1) -#define STEP_CONTROL_EXECUTE_SYS_MOTION bit(2) -#define STEP_CONTROL_UPDATE_SPINDLE_RPM bit(3) +#define STEP_CONTROL_NORMAL_OP 0 // Must be zero. +#define STEP_CONTROL_END_MOTION bit(0) +#define STEP_CONTROL_EXECUTE_HOLD bit(1) +#define STEP_CONTROL_EXECUTE_SYS_MOTION bit(2) +#define STEP_CONTROL_UPDATE_SPINDLE_RPM bit(3) // Define control pin index for Grbl internal use. Pin maps may change, but these values don't. //#ifdef ENABLE_SAFETY_DOOR_INPUT_PIN #define N_CONTROL_PIN 4 -#define CONTROL_PIN_INDEX_SAFETY_DOOR bit(0) -#define CONTROL_PIN_INDEX_RESET bit(1) -#define CONTROL_PIN_INDEX_FEED_HOLD bit(2) -#define CONTROL_PIN_INDEX_CYCLE_START bit(3) -#define CONTROL_PIN_INDEX_MACRO_0 bit(4) -#define CONTROL_PIN_INDEX_MACRO_1 bit(5) -#define CONTROL_PIN_INDEX_MACRO_2 bit(6) -#define CONTROL_PIN_INDEX_MACRO_3 bit(7) +#define CONTROL_PIN_INDEX_SAFETY_DOOR bit(0) +#define CONTROL_PIN_INDEX_RESET bit(1) +#define CONTROL_PIN_INDEX_FEED_HOLD bit(2) +#define CONTROL_PIN_INDEX_CYCLE_START bit(3) +#define CONTROL_PIN_INDEX_MACRO_0 bit(4) +#define CONTROL_PIN_INDEX_MACRO_1 bit(5) +#define CONTROL_PIN_INDEX_MACRO_2 bit(6) +#define CONTROL_PIN_INDEX_MACRO_3 bit(7) //#else //#define N_CONTROL_PIN 3 //#define CONTROL_PIN_INDEX_RESET bit(0) @@ -148,29 +143,28 @@ extern system_t sys; //#endif // Define spindle stop override control states. -#define SPINDLE_STOP_OVR_DISABLED 0 // Must be zero. -#define SPINDLE_STOP_OVR_ENABLED bit(0) -#define SPINDLE_STOP_OVR_INITIATE bit(1) -#define SPINDLE_STOP_OVR_RESTORE bit(2) -#define SPINDLE_STOP_OVR_RESTORE_CYCLE bit(3) +#define SPINDLE_STOP_OVR_DISABLED 0 // Must be zero. +#define SPINDLE_STOP_OVR_ENABLED bit(0) +#define SPINDLE_STOP_OVR_INITIATE bit(1) +#define SPINDLE_STOP_OVR_RESTORE bit(2) +#define SPINDLE_STOP_OVR_RESTORE_CYCLE bit(3) // NOTE: These position variables may need to be declared as volatiles, if problems arise. -extern int32_t sys_position[N_AXIS]; // Real-time machine (aka home) position vector in steps. -extern int32_t sys_probe_position[N_AXIS]; // Last probe position in machine coordinates and steps. +extern int32_t sys_position[N_AXIS]; // Real-time machine (aka home) position vector in steps. +extern int32_t sys_probe_position[N_AXIS]; // Last probe position in machine coordinates and steps. -extern volatile uint8_t sys_probe_state; // Probing state value. Used to coordinate the probing cycle with stepper ISR. -extern volatile uint8_t sys_rt_exec_state; // Global realtime executor bitflag variable for state management. See EXEC bitmasks. -extern volatile uint8_t sys_rt_exec_alarm; // Global realtime executor bitflag variable for setting various alarms. -extern volatile uint8_t sys_rt_exec_motion_override; // Global realtime executor bitflag variable for motion-based overrides. -extern volatile uint8_t sys_rt_exec_accessory_override; // Global realtime executor bitflag variable for spindle/coolant overrides. +extern volatile uint8_t sys_probe_state; // Probing state value. Used to coordinate the probing cycle with stepper ISR. +extern volatile uint8_t sys_rt_exec_state; // Global realtime executor bitflag variable for state management. See EXEC bitmasks. +extern volatile uint8_t sys_rt_exec_alarm; // Global realtime executor bitflag variable for setting various alarms. +extern volatile uint8_t sys_rt_exec_motion_override; // Global realtime executor bitflag variable for motion-based overrides. +extern volatile uint8_t sys_rt_exec_accessory_override; // Global realtime executor bitflag variable for spindle/coolant overrides. #ifdef DEBUG - #define EXEC_DEBUG_REPORT bit(0) - extern volatile uint8_t sys_rt_exec_debug; +# define EXEC_DEBUG_REPORT bit(0) +extern volatile uint8_t sys_rt_exec_debug; #endif - -void system_ini(); // Renamed from system_init() due to conflict with esp32 files +void system_ini(); // Renamed from system_init() due to conflict with esp32 files // Returns bitfield of control pin states, organized by CONTROL_PIN_INDEX. (1=triggered, 0=not triggered). uint8_t system_control_get_state(); @@ -191,12 +185,12 @@ void system_clear_exec_motion_overrides(); void system_clear_exec_accessory_overrides(); // Execute the startup script lines stored in EEPROM upon initialization -void system_execute_startup(char* line); +void system_execute_startup(char* line); uint8_t execute_line(char* line, uint8_t client, auth_t auth_level); uint8_t system_execute_line(char* line, ESPResponseStream*, auth_t); uint8_t system_execute_line(char* line, uint8_t client, auth_t); -uint8_t do_command_or_setting(const char *key, char *value, auth_t auth_level, ESPResponseStream*); -void system_flag_wco_change(); +uint8_t do_command_or_setting(const char* key, char* value, auth_t auth_level, ESPResponseStream*); +void system_flag_wco_change(); // Returns machine position of axis 'idx'. Must be sent a 'step' array. float system_convert_axis_steps_to_mpos(int32_t* steps, uint8_t idx); @@ -217,7 +211,6 @@ void system_set_exec_accessory_override_flag(uint8_t mask); void system_clear_exec_motion_overrides(); void system_clear_exec_accessory_overrides(); - int32_t system_convert_corexy_to_x_axis_steps(int32_t* steps); int32_t system_convert_corexy_to_y_axis_steps(int32_t* steps); @@ -231,5 +224,3 @@ void fast_sys_io_control(uint8_t io_num_mask, bool turnOn); // int8_t sys_get_next_RMT_chan_num(); int8_t sys_get_next_PWM_chan_num(); - -#endif diff --git a/Grbl_Esp32/authentication.cpp b/Grbl_Esp32/src/WebUI/Authentication.cpp similarity index 81% rename from Grbl_Esp32/authentication.cpp rename to Grbl_Esp32/src/WebUI/Authentication.cpp index 59f75c6f..9f877f5e 100644 --- a/Grbl_Esp32/authentication.cpp +++ b/Grbl_Esp32/src/WebUI/Authentication.cpp @@ -1,13 +1,13 @@ -#include "grbl.h" +#include "../Grbl.h" #ifdef ENABLE_AUTHENTICATION // TODO Settings - need ADMIN_ONLY and if it is called without a parameter it sets the default StringSetting* user_password; StringSetting* admin_password; -void remove_password(char *str, auth_t& auth_level) { +void remove_password(char* str, auth_t& auth_level) { String paramStr = String((const char*)str); - int pos = paramStr.indexOf("pwd="); + int pos = paramStr.indexOf("pwd="); if (pos == -1) { return; } @@ -15,7 +15,7 @@ void remove_password(char *str, auth_t& auth_level) { // Truncate the str string at the pwd= . // If the pwd= is preceded by a space, take off that space too. int endpos = pos; - if (endpos && str[endpos-1] == ' ') { + if (endpos && str[endpos - 1] == ' ') { --endpos; } str[endpos] = '\0'; @@ -35,7 +35,7 @@ void remove_password(char *str, auth_t& auth_level) { } } #else -void remove_password(char *str, auth_t& auth_level) { +void remove_password(char* str, auth_t& auth_level) { auth_level = LEVEL_ADMIN; } #endif diff --git a/Grbl_Esp32/src/WebUI/Authentication.h b/Grbl_Esp32/src/WebUI/Authentication.h new file mode 100644 index 00000000..3e11d797 --- /dev/null +++ b/Grbl_Esp32/src/WebUI/Authentication.h @@ -0,0 +1,9 @@ +#pragma once + +//Authentication level +typedef enum { LEVEL_GUEST = 0, LEVEL_USER = 1, LEVEL_ADMIN = 2 } auth_t; + +#define MIN_LOCAL_PASSWORD_LENGTH 1 +#define MAX_LOCAL_PASSWORD_LENGTH 16 + +void remove_password(char* str, auth_t& auth_level); diff --git a/Grbl_Esp32/BTconfig.cpp b/Grbl_Esp32/src/WebUI/BTConfig.cpp similarity index 73% rename from Grbl_Esp32/BTconfig.cpp rename to Grbl_Esp32/src/WebUI/BTConfig.cpp index f761e552..4f06fe02 100644 --- a/Grbl_Esp32/BTconfig.cpp +++ b/Grbl_Esp32/src/WebUI/BTConfig.cpp @@ -1,5 +1,5 @@ /* - BTconfig.cpp - Bluetooth functions class + BTConfig.cpp - Bluetooth functions class Copyright (c) 2014 Luc Lebosse. All rights reserved. @@ -18,29 +18,26 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifdef ARDUINO_ARCH_ESP32 - -#include "grbl.h" +#include "../Grbl.h" #ifdef ENABLE_BLUETOOTH -#include "BluetoothSerial.h" -#include "BTconfig.h" +# include +# include "BTConfig.h" -BTConfig bt_config; +BTConfig bt_config; BluetoothSerial SerialBT; -#ifdef __cplusplus +# ifdef __cplusplus extern "C" { -#endif +# endif const uint8_t* esp_bt_dev_get_address(void); -#ifdef __cplusplus +# ifdef __cplusplus } -#endif +# endif -String BTConfig::_btname = ""; +String BTConfig::_btname = ""; String BTConfig::_btclient = ""; -BTConfig::BTConfig() { -} +BTConfig::BTConfig() {} BTConfig::~BTConfig() { end(); @@ -48,27 +45,25 @@ BTConfig::~BTConfig() { static void my_spp_cb(esp_spp_cb_event_t event, esp_spp_cb_param_t* param) { switch (event) { - case ESP_SPP_SRV_OPEN_EVT: { //Server connection open - char str[18]; - str[17] = '\0'; - uint8_t* addr = param->srv_open.rem_bda; - sprintf(str, "%02X:%02X:%02X:%02X:%02X:%02X", addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]); - BTConfig::_btclient = str; - grbl_sendf(CLIENT_ALL, "[MSG:BT Connected with %s]\r\n", str); - } - break; - case ESP_SPP_CLOSE_EVT://Client connection closed - grbl_send(CLIENT_ALL, "[MSG:BT Disconnected]\r\n"); - BTConfig::_btclient = ""; - break; - default: - break; + case ESP_SPP_SRV_OPEN_EVT: { //Server connection open + char str[18]; + str[17] = '\0'; + uint8_t* addr = param->srv_open.rem_bda; + sprintf(str, "%02X:%02X:%02X:%02X:%02X:%02X", addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]); + BTConfig::_btclient = str; + grbl_sendf(CLIENT_ALL, "[MSG:BT Connected with %s]\r\n", str); + } break; + case ESP_SPP_CLOSE_EVT: //Client connection closed + grbl_send(CLIENT_ALL, "[MSG:BT Disconnected]\r\n"); + BTConfig::_btclient = ""; + break; + default: break; } } const char* BTConfig::info() { static String result; - String tmp; + String tmp; result = "[MSG:"; if (Is_BT_on()) { result += "Mode=BT:Name="; @@ -78,8 +73,10 @@ const char* BTConfig::info() { result += "):Status="; if (SerialBT.hasClient()) result += "Connected with " + _btclient; - else result += "Not connected"; - } else result += "No BT"; + else + result += "Not connected"; + } else + result += "No BT"; result += "]\r\n"; return result.c_str(); } @@ -102,7 +99,7 @@ bool BTConfig::isBTnameValid(const char* hostname) { const char* BTConfig::device_address() { const uint8_t* point = esp_bt_dev_get_address(); - static char str[18]; + static char str[18]; str[17] = '\0'; sprintf(str, "%02X:%02X:%02X:%02X:%02X:%02X", (int)point[0], (int)point[1], (int)point[2], (int)point[3], (int)point[4], (int)point[5]); return str; @@ -122,7 +119,8 @@ void BTConfig::begin() { SerialBT.register_callback(&my_spp_cb); grbl_sendf(CLIENT_ALL, "[MSG:BT Started with %s]\r\n", _btname.c_str()); } - } else end(); + } else + end(); } /** @@ -156,7 +154,4 @@ void BTConfig::handle() { COMMANDS::wait(0); } - -#endif // ENABLE_BLUETOOTH - -#endif // ARDUINO_ARCH_ESP32 +#endif // ENABLE_BLUETOOTH diff --git a/Grbl_Esp32/BTconfig.h b/Grbl_Esp32/src/WebUI/BTConfig.h similarity index 67% rename from Grbl_Esp32/BTconfig.h rename to Grbl_Esp32/src/WebUI/BTConfig.h index 4bba20b5..e4dae377 100644 --- a/Grbl_Esp32/BTconfig.h +++ b/Grbl_Esp32/src/WebUI/BTConfig.h @@ -1,5 +1,7 @@ +#pragma once + /* - BTconfig.h - Bluetooth functions class + BTConfig.h - Bluetooth functions class Copyright (c) 2014 Luc Lebosse. All rights reserved. @@ -18,45 +20,40 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #if !defined(CONFIG_BT_ENABLED) || !defined(CONFIG_BLUEDROID_ENABLED) - #error Bluetooth is not enabled! Please run `make menuconfig` to and enable it +# error Bluetooth is not enabled! Please run `make menuconfig` to and enable it #endif //defaults values #define DEFAULT_BT_NAME "btgrblesp" - //boundaries -#define MAX_BTNAME_LENGTH 32 -#define MIN_BTNAME_LENGTH 1 +#define MAX_BTNAME_LENGTH 32 +#define MIN_BTNAME_LENGTH 1 #define BT_EVENT_DISCONNECTED 0 #define BT_EVENT_CONNECTED 1 - -#ifndef _BT_CONFIG_H -#define _BT_CONFIG_H -#include "BluetoothSerial.h" +#include extern BluetoothSerial SerialBT; class BTConfig { - public: +public: BTConfig(); ~BTConfig(); static const char* info(); - static void BTEvent(uint8_t event); - static bool isBTnameValid(const char* hostname); - static String BTname() {return _btname;} + static void BTEvent(uint8_t event); + static bool isBTnameValid(const char* hostname); + static String BTname() { return _btname; } static const char* device_address(); - static void begin(); - static void end(); - static void handle(); - static void reset_settings(); - static bool Is_BT_on(); - static String _btclient; - private : + static void begin(); + static void end(); + static void handle(); + static void reset_settings(); + static bool Is_BT_on(); + static String _btclient; + +private: static String _btname; }; extern BTConfig bt_config; - -#endif diff --git a/Grbl_Esp32/commands.cpp b/Grbl_Esp32/src/WebUI/Commands.cpp similarity index 93% rename from Grbl_Esp32/commands.cpp rename to Grbl_Esp32/src/WebUI/Commands.cpp index 62791b71..b283ef27 100644 --- a/Grbl_Esp32/commands.cpp +++ b/Grbl_Esp32/src/WebUI/Commands.cpp @@ -1,5 +1,5 @@ /* - commands.cpp - GRBL_ESP command class + Commands.cpp - GRBL_ESP command class Copyright (c) 2014 Luc Lebosse. All rights reserved. @@ -17,7 +17,7 @@ License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -#include "grbl.h" +#include "../Grbl.h" #ifdef __cplusplus extern "C" { @@ -34,7 +34,7 @@ bool COMMANDS::restart_ESP_module = false; */ void COMMANDS::wait(uint32_t milliseconds) { uint32_t timeout = millis(); - esp_task_wdt_reset(); //for a wait 0; + esp_task_wdt_reset(); //for a wait 0; //wait feeding WDT while ((millis() - timeout) < milliseconds) esp_task_wdt_reset(); @@ -69,6 +69,7 @@ void COMMANDS::handle() { //in case of restart requested if (restart_ESP_module) { ESP.restart(); - while (1) ; + while (1) + ; } } diff --git a/Grbl_Esp32/commands.h b/Grbl_Esp32/src/WebUI/Commands.h similarity index 88% rename from Grbl_Esp32/commands.h rename to Grbl_Esp32/src/WebUI/Commands.h index 544ad9ef..fc412307 100644 --- a/Grbl_Esp32/commands.h +++ b/Grbl_Esp32/src/WebUI/Commands.h @@ -1,5 +1,7 @@ +#pragma once + /* - commands.h - ESP3D configuration class + Commands.h - ESP3D configuration class Copyright (c) 2014 Luc Lebosse. All rights reserved. @@ -18,20 +20,17 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef COMMANDS_h -#define COMMANDS_h -#include "config.h" +#include "Config.h" class ESPResponseStream; class COMMANDS { - public: +public: static void wait(uint32_t milliseconds); static void handle(); static void restart_ESP(); static bool isLocalPasswordValid(char* password); - private : + +private: static bool restart_ESP_module; }; - -#endif diff --git a/Grbl_Esp32/espresponse.cpp b/Grbl_Esp32/src/WebUI/ESPResponse.cpp similarity index 72% rename from Grbl_Esp32/espresponse.cpp rename to Grbl_Esp32/src/WebUI/ESPResponse.cpp index 96da1c4a..c69c4cc3 100644 --- a/Grbl_Esp32/espresponse.cpp +++ b/Grbl_Esp32/src/WebUI/ESPResponse.cpp @@ -1,5 +1,5 @@ /* - espresponse.cpp - GRBL_ESP response class + ESPResponse.cpp - GRBL_ESP response class Copyright (c) 2014 Luc Lebosse. All rights reserved. @@ -17,42 +17,44 @@ License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -#include "grbl.h" -#include "espresponse.h" -#if defined (ENABLE_HTTP) && defined(ENABLE_WIFI) - #include "web_server.h" - #include +#include "../Grbl.h" +#include "ESPResponse.h" +#if defined(ENABLE_HTTP) && defined(ENABLE_WIFI) +# include "WebServer.h" +# include #endif -#if defined (ENABLE_HTTP) && defined(ENABLE_WIFI) +#if defined(ENABLE_HTTP) && defined(ENABLE_WIFI) ESPResponseStream::ESPResponseStream(WebServer* webserver) { _header_sent = false; - _webserver = webserver; - _client = CLIENT_WEBUI; + _webserver = webserver; + _client = CLIENT_WEBUI; } #endif ESPResponseStream::ESPResponseStream() { _client = CLIENT_INPUT; -#if defined (ENABLE_HTTP) && defined(ENABLE_WIFI) +#if defined(ENABLE_HTTP) && defined(ENABLE_WIFI) _header_sent = false; - _webserver = NULL; + _webserver = NULL; #endif } ESPResponseStream::ESPResponseStream(uint8_t client, bool byid) { - (void)byid; //fake parameter to avoid confusion with pointer one (NULL == 0) + (void)byid; //fake parameter to avoid confusion with pointer one (NULL == 0) _client = client; -#if defined (ENABLE_HTTP) && defined(ENABLE_WIFI) +#if defined(ENABLE_HTTP) && defined(ENABLE_WIFI) _header_sent = false; - _webserver = NULL; + _webserver = NULL; #endif } void ESPResponseStream::println(const char* data) { print(data); - if (_client == CLIENT_TELNET) print("\r\n"); - else print("\n"); + if (_client == CLIENT_TELNET) + print("\r\n"); + else + print("\n"); } //helper to format size to readable string @@ -68,8 +70,9 @@ String ESPResponseStream::formatBytes(uint64_t bytes) { } void ESPResponseStream::print(const char* data) { - if (_client == CLIENT_INPUT) return; -#if defined (ENABLE_HTTP) && defined(ENABLE_WIFI) + if (_client == CLIENT_INPUT) + return; +#if defined(ENABLE_HTTP) && defined(ENABLE_WIFI) if (_webserver) { if (!_header_sent) { _webserver->setContentLength(CONTENT_LENGTH_UNKNOWN); @@ -88,21 +91,23 @@ void ESPResponseStream::print(const char* data) { return; } #endif - if (_client == CLIENT_WEBUI) return; //this is sanity check + if (_client == CLIENT_WEBUI) + return; //this is sanity check grbl_send(_client, data); } void ESPResponseStream::flush() { -#if defined (ENABLE_HTTP) && defined(ENABLE_WIFI) +#if defined(ENABLE_HTTP) && defined(ENABLE_WIFI) if (_webserver) { if (_header_sent) { //send data - if (_buffer.length() > 0)_webserver->sendContent(_buffer); + if (_buffer.length() > 0) + _webserver->sendContent(_buffer); //close connection _webserver->sendContent(""); } _header_sent = false; - _buffer = ""; + _buffer = ""; } #endif } diff --git a/Grbl_Esp32/espresponse.h b/Grbl_Esp32/src/WebUI/ESPResponse.h similarity index 67% rename from Grbl_Esp32/espresponse.h rename to Grbl_Esp32/src/WebUI/ESPResponse.h index 3986a67f..67e15a51 100644 --- a/Grbl_Esp32/espresponse.h +++ b/Grbl_Esp32/src/WebUI/ESPResponse.h @@ -1,5 +1,7 @@ +#pragma once + /* - espresponse.h - GRBL_ESP response class + ESPResponse.h - GRBL_ESP response class Copyright (c) 2014 Luc Lebosse. All rights reserved. @@ -18,33 +20,29 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef ESPRESPONSE_h -#define ESPRESPONSE_h - -#if defined (ENABLE_HTTP) && defined(ENABLE_WIFI) - class WebServer; +#if defined(ENABLE_HTTP) && defined(ENABLE_WIFI) +class WebServer; #endif class ESPResponseStream { - public: - void print(const char* data); - void println(const char* data); - void flush(); - bool anyOutput() { return _header_sent; } +public: + void print(const char* data); + void println(const char* data); + void flush(); + bool anyOutput() { return _header_sent; } static String formatBytes(uint64_t bytes); - uint8_t client() {return _client;} -#if defined (ENABLE_HTTP) && defined(ENABLE_WIFI) + uint8_t client() { return _client; } +#if defined(ENABLE_HTTP) && defined(ENABLE_WIFI) ESPResponseStream(WebServer* webserver); #endif ESPResponseStream(uint8_t client, bool byid = true); ESPResponseStream(); - private: + +private: uint8_t _client; - bool _header_sent; -#if defined (ENABLE_HTTP) && defined(ENABLE_WIFI) + bool _header_sent; +#if defined(ENABLE_HTTP) && defined(ENABLE_WIFI) WebServer* _webserver; - String _buffer; + String _buffer; #endif }; - -#endif diff --git a/Grbl_Esp32/inputbuffer.cpp b/Grbl_Esp32/src/WebUI/InputBuffer.cpp similarity index 80% rename from Grbl_Esp32/inputbuffer.cpp rename to Grbl_Esp32/src/WebUI/InputBuffer.cpp index 35f68bc0..6f0d6364 100644 --- a/Grbl_Esp32/inputbuffer.cpp +++ b/Grbl_Esp32/src/WebUI/InputBuffer.cpp @@ -1,5 +1,5 @@ /* - inputbuffer.cpp - inputbuffer functions class + InputBuffer.cpp - inputbuffer functions class Copyright (c) 2014 Luc Lebosse. All rights reserved. @@ -18,31 +18,27 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ - -#ifdef ARDUINO_ARCH_ESP32 - -#include "config.h" -#include "inputbuffer.h" +#include "../Config.h" +#include "InputBuffer.h" InputBuffer inputBuffer; - InputBuffer::InputBuffer() { _RXbufferSize = 0; - _RXbufferpos = 0; + _RXbufferpos = 0; } InputBuffer::~InputBuffer() { _RXbufferSize = 0; - _RXbufferpos = 0; + _RXbufferpos = 0; } void InputBuffer::begin() { _RXbufferSize = 0; - _RXbufferpos = 0; + _RXbufferpos = 0; } void InputBuffer::end() { _RXbufferSize = 0; - _RXbufferpos = 0; + _RXbufferpos = 0; } InputBuffer::operator bool() const { @@ -65,7 +61,7 @@ size_t InputBuffer::write(uint8_t c) { if (current > (RXBUFFERSIZE - 1)) current = 0; _RXbuffer[current] = c; - current ++; + current++; _RXbufferSize += 1; return 1; } @@ -79,19 +75,23 @@ size_t InputBuffer::write(const uint8_t* buffer, size_t size) { } int InputBuffer::peek(void) { - if (_RXbufferSize > 0)return _RXbuffer[_RXbufferpos]; - else return -1; + if (_RXbufferSize > 0) + return _RXbuffer[_RXbufferpos]; + else + return -1; } bool InputBuffer::push(const char* data) { int data_size = strlen(data); if ((data_size + _RXbufferSize) <= RXBUFFERSIZE) { int current = _RXbufferpos + _RXbufferSize; - if (current > RXBUFFERSIZE) current = current - RXBUFFERSIZE; + if (current > RXBUFFERSIZE) + current = current - RXBUFFERSIZE; for (int i = 0; i < data_size; i++) { - if (current > (RXBUFFERSIZE - 1)) current = 0; + if (current > (RXBUFFERSIZE - 1)) + current = 0; _RXbuffer[current] = data[i]; - current ++; + current++; } _RXbufferSize += strlen(data); return true; @@ -103,16 +103,15 @@ int InputBuffer::read(void) { if (_RXbufferSize > 0) { int v = _RXbuffer[_RXbufferpos]; _RXbufferpos++; - if (_RXbufferpos > (RXBUFFERSIZE - 1))_RXbufferpos = 0; + if (_RXbufferpos > (RXBUFFERSIZE - 1)) + _RXbufferpos = 0; _RXbufferSize--; return v; - } else return -1; + } else + return -1; } void InputBuffer::flush(void) { //No need currently //keep for compatibility } - - -#endif // ARDUINO_ARCH_ESP32 diff --git a/Grbl_Esp32/inputbuffer.h b/Grbl_Esp32/src/WebUI/InputBuffer.h similarity index 55% rename from Grbl_Esp32/inputbuffer.h rename to Grbl_Esp32/src/WebUI/InputBuffer.h index 8a7d8d9c..fd441297 100644 --- a/Grbl_Esp32/inputbuffer.h +++ b/Grbl_Esp32/src/WebUI/InputBuffer.h @@ -1,5 +1,7 @@ +#pragma once + /* - inputbuffer.h - inputbuffer functions class + InputBuffer.h - inputbuffer functions class Copyright (c) 2014 Luc Lebosse. All rights reserved. @@ -18,50 +20,36 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#include -#ifndef _INPUT_BUFFER_H_ -#define _INPUT_BUFFER_H_ - -#include "Print.h" #define RXBUFFERSIZE 128 -class InputBuffer: public Print { - public: + +class InputBuffer : public Print { +public: InputBuffer(); ~InputBuffer(); size_t write(uint8_t c); size_t write(const uint8_t* buffer, size_t size); - inline size_t write(const char* s) { - return write((uint8_t*) s, strlen(s)); - } - inline size_t write(unsigned long n) { - return write((uint8_t) n); - } - inline size_t write(long n) { - return write((uint8_t) n); - } - inline size_t write(unsigned int n) { - return write((uint8_t) n); - } - inline size_t write(int n) { - return write((uint8_t) n); - } - void begin(); - void end(); - int available(); - int availableforwrite(); - int peek(void); - int read(void); - bool push(const char* data); - void flush(void); - operator bool() const; - private: - uint8_t _RXbuffer[RXBUFFERSIZE]; + inline size_t write(const char* s) { return write((uint8_t*)s, strlen(s)); } + inline size_t write(unsigned long n) { return write((uint8_t)n); } + inline size_t write(long n) { return write((uint8_t)n); } + inline size_t write(unsigned int n) { return write((uint8_t)n); } + inline size_t write(int n) { return write((uint8_t)n); } + void begin(); + void end(); + int available(); + int availableforwrite(); + int peek(void); + int read(void); + bool push(const char* data); + void flush(void); + operator bool() const; + +private: + uint8_t _RXbuffer[RXBUFFERSIZE]; uint16_t _RXbufferSize; uint16_t _RXbufferpos; }; - extern InputBuffer inputBuffer; - -#endif diff --git a/Grbl_Esp32/JSONencoder.cpp b/Grbl_Esp32/src/WebUI/JSONEncoder.cpp similarity index 78% rename from Grbl_Esp32/JSONencoder.cpp rename to Grbl_Esp32/src/WebUI/JSONEncoder.cpp index 19fb50a0..45ce46da 100644 --- a/Grbl_Esp32/JSONencoder.cpp +++ b/Grbl_Esp32/src/WebUI/JSONEncoder.cpp @@ -1,16 +1,12 @@ // Class for creating JSON-encoded strings. -#include "grbl.h" +#include "../Grbl.h" -#include "JSONencoder.h" +#include "JSONEncoder.h" // Constructor. If _pretty is true, newlines are // inserted into the JSON string for easy reading. -JSONencoder::JSONencoder(bool pretty) : - pretty(pretty), - level(0), - str("") -{ +JSONencoder::JSONencoder(bool pretty) : pretty(pretty), level(0), str("") { count[level] = 0; } @@ -38,8 +34,7 @@ void JSONencoder::comma() { } // Private function to add a name enclosed with quotes. -void JSONencoder::quoted(const char *s) -{ +void JSONencoder::quoted(const char* s) { add('"'); str.concat(s); add('"'); @@ -65,17 +60,14 @@ void JSONencoder::dec_level() { void JSONencoder::line() { if (pretty) { add('\n'); - for (int i=0; i < 2*level; i++) { + for (int i = 0; i < 2 * level; i++) { add(' '); } } } // Constructor that supplies a default falue for "pretty" -JSONencoder::JSONencoder() : - JSONencoder(false) -{ } - +JSONencoder::JSONencoder() : JSONencoder(false) {} // Begins the JSON encoding process, creating an unnamed object void JSONencoder::begin() { @@ -90,14 +82,14 @@ String JSONencoder::end() { } // Starts a member element. -void JSONencoder::begin_member(const char *tag) { +void JSONencoder::begin_member(const char* tag) { comma_line(); quoted(tag); add(':'); } // Starts an array with "tag":[ -void JSONencoder::begin_array(const char *tag) { +void JSONencoder::begin_array(const char* tag) { begin_member(tag); add('['); inc_level(); @@ -122,32 +114,32 @@ void JSONencoder::begin_object() { // Ends an object with }. void JSONencoder::end_object() { dec_level(); - if (count[level+1] > 1) { + if (count[level + 1] > 1) { line(); } add('}'); } // Creates a "tag":"value" member from a C-style string -void JSONencoder::member(const char *tag, const char *value) { +void JSONencoder::member(const char* tag, const char* value) { begin_member(tag); quoted(value); } // Creates a "tag":"value" member from an Arduino string -void JSONencoder::member(const char *tag, String value) { +void JSONencoder::member(const char* tag, String value) { begin_member(tag); quoted(value.c_str()); } // Creates a "tag":"value" member from an integer -void JSONencoder::member(const char *tag, int value) { +void JSONencoder::member(const char* tag, int value) { member(tag, String(value)); } // Creates an Esp32_WebUI configuration item specification from // a value passed in as a C-style string. -void JSONencoder::begin_webui(const char *p, const char *help, const char *type, const char *val) { +void JSONencoder::begin_webui(const char* p, const char* help, const char* type, const char* val) { begin_object(); member("F", "network"); member("P", p); @@ -158,13 +150,13 @@ void JSONencoder::begin_webui(const char *p, const char *help, const char *type, // Creates an Esp32_WebUI configuration item specification from // an integer value. -void JSONencoder::begin_webui(const char *p, const char *help, const char *type, int val) { +void JSONencoder::begin_webui(const char* p, const char* help, const char* type, int val) { begin_webui(p, help, type, String(val).c_str()); } // Creates an Esp32_WebUI configuration item specification from // a C-style string value, with additional min and max arguments. -void JSONencoder::begin_webui(const char *p, const char *help, const char *type, const char *val, int min, int max) { +void JSONencoder::begin_webui(const char* p, const char* help, const char* type, const char* val, int min, int max) { begin_webui(p, help, type, val); member("S", max); member("M", min); diff --git a/Grbl_Esp32/JSONencoder.h b/Grbl_Esp32/src/WebUI/JSONEncoder.h similarity index 69% rename from Grbl_Esp32/JSONencoder.h rename to Grbl_Esp32/src/WebUI/JSONEncoder.h index eae79625..863f1e83 100644 --- a/Grbl_Esp32/JSONencoder.h +++ b/Grbl_Esp32/src/WebUI/JSONEncoder.h @@ -1,21 +1,23 @@ +#pragma once + // Class for creating JSON-encoded strings. -#pragma once #define MAX_JSON_LEVEL 16 class JSONencoder { - private: - bool pretty; - int level; +private: + bool pretty; + int level; String str; - int count[MAX_JSON_LEVEL]; - void add(char c) { str += c; } - void comma_line(); - void comma(); - void quoted(const char *s); - void inc_level(); - void dec_level(); - void line(); - public: + int count[MAX_JSON_LEVEL]; + void add(char c) { str += c; } + void comma_line(); + void comma(); + void quoted(const char* s); + void inc_level(); + void dec_level(); + void line(); + +public: // Constructor; set _pretty true for pretty printing JSONencoder(bool pretty); // If you don't set _pretty it defaults to false @@ -28,12 +30,12 @@ class JSONencoder { String end(); // member() creates a "tag":"value" element - void member(const char *tag, const char *value); - void member(const char *tag, String value); - void member(const char *tag, int value); + void member(const char* tag, const char* value); + void member(const char* tag, String value); + void member(const char* tag, int value); // begin_array() starts a "tag":[ array element - void begin_array(const char *tag); + void begin_array(const char* tag); // end_array() closes the array with ] void end_array(); @@ -47,7 +49,7 @@ class JSONencoder { // begin_member() starts the creation of a member. // The only case where you need to use it directly // is when you want a member whose value is an object. - void begin_member(const char *tag); + void begin_member(const char* tag); // The begin_webui() methods are specific to Esp3D_WebUI // WebUI sends JSON objects to the UI to generate configuration @@ -70,7 +72,7 @@ class JSONencoder { // S => 0 .. 255 // A => 7 .. 15 (0.0.0.0 .. 255.255.255.255) // I => 0 .. 2^31-1 - void begin_webui(const char *p, const char *help, const char *type, const char *val); - void begin_webui(const char *p, const char *help, const char *type, const int val); - void begin_webui(const char *p, const char *help, const char *type, const char *val, int min, int max); + void begin_webui(const char* p, const char* help, const char* type, const char* val); + void begin_webui(const char* p, const char* help, const char* type, const int val); + void begin_webui(const char* p, const char* help, const char* type, const char* val, int min, int max); }; diff --git a/Grbl_Esp32/nofile.h b/Grbl_Esp32/src/WebUI/NoFile.h similarity index 99% rename from Grbl_Esp32/nofile.h rename to Grbl_Esp32/src/WebUI/NoFile.h index 7c34dd3c..85c44f24 100644 --- a/Grbl_Esp32/nofile.h +++ b/Grbl_Esp32/src/WebUI/NoFile.h @@ -1,5 +1,8 @@ +#pragma once +// clang-format off + /* - nofile.h - ESP3D data file + NoFile.h - ESP3D data file Copyright (c) 2014 Luc Lebosse. All rights reserved. @@ -20,8 +23,7 @@ //data generated by https://github.com/AraHaan/bin2c //bin2c Conversion Tool v0.14.0 - Windows - [FINAL]. -#ifndef __nofile_h -#define __nofile_h + /* Generated by bin2c, do not edit manually */ /* Contents of file tool.html.gz */ @@ -449,4 +451,3 @@ const char PAGE_NOFILES[6728] PROGMEM = { 0x1E, 0x1E, 0xB5, 0xE4, 0x31, 0xF2, 0xA3, 0x96, 0xFC, 0xF5, 0x0A, 0xFD, 0xA3, 0xD1, 0xFF, 0x0B, 0xB4, 0xEA, 0x2E, 0x52, 0x3B, 0x5A, 0x00, 0x00 }; -#endif //__nofile_h diff --git a/Grbl_Esp32/notifications_service.cpp b/Grbl_Esp32/src/WebUI/NotificationsService.cpp similarity index 71% rename from Grbl_Esp32/notifications_service.cpp rename to Grbl_Esp32/src/WebUI/NotificationsService.cpp index 1a4ee36c..8dcb306d 100644 --- a/Grbl_Esp32/notifications_service.cpp +++ b/Grbl_Esp32/src/WebUI/NotificationsService.cpp @@ -1,5 +1,5 @@ /* - notifications_service.cpp - notifications service functions class + NotificationsService.cpp - notifications service functions class Copyright (c) 2014 Luc Lebosse. All rights reserved. @@ -28,27 +28,27 @@ // - https://github.com/CosmicBoris/ESP8266SMTP // - https://www.electronicshub.org/send-an-email-using-esp8266/ -#include "grbl.h" +#include "../Grbl.h" #ifdef ENABLE_NOTIFICATIONS -#include "notifications_service.h" -#include -#include +# include "NotificationsService.h" +# include +# include -#define PUSHOVERTIMEOUT 5000 -#define PUSHOVERSERVER "api.pushover.net" -#define PUSHOVERPORT 443 +# define PUSHOVERTIMEOUT 5000 +# define PUSHOVERSERVER "api.pushover.net" +# define PUSHOVERPORT 443 -#define LINETIMEOUT 5000 -#define LINESERVER "notify-api.line.me" -#define LINEPORT 443 +# define LINETIMEOUT 5000 +# define LINESERVER "notify-api.line.me" +# define LINEPORT 443 -#define EMAILTIMEOUT 5000 +# define EMAILTIMEOUT 5000 NotificationsService notificationsservice; -bool Wait4Answer(WiFiClientSecure& client, const char* linetrigger, const char* expected_answer, uint32_t timeout) { +bool Wait4Answer(WiFiClientSecure& client, const char* linetrigger, const char* expected_answer, uint32_t timeout) { if (client.connected()) { - String answer; + String answer; uint32_t starttimeout = millis(); while (client.connected() && ((millis() - starttimeout) < timeout)) { answer = client.readStringUntil('\n'); @@ -74,11 +74,11 @@ bool Wait4Answer(WiFiClientSecure& client, const char* linetrigger, const char* } NotificationsService::NotificationsService() { - _started = false; + _started = false; _notificationType = 0; - _token1 = ""; - _token1 = ""; - _settings = ""; + _token1 = ""; + _token1 = ""; + _settings = ""; } NotificationsService::~NotificationsService() { end(); @@ -90,33 +90,23 @@ bool NotificationsService::started() { const char* NotificationsService::getTypeString() { switch (_notificationType) { - case ESP_PUSHOVER_NOTIFICATION: - return "Pushover"; - case ESP_EMAIL_NOTIFICATION: - return "Email"; - case ESP_LINE_NOTIFICATION: - return "Line"; - default: - break; + case ESP_PUSHOVER_NOTIFICATION: return "Pushover"; + case ESP_EMAIL_NOTIFICATION: return "Email"; + case ESP_LINE_NOTIFICATION: return "Line"; + default: break; } return "None"; } bool NotificationsService::sendMSG(const char* title, const char* message) { - if (!_started) return false; + if (!_started) + return false; if (!((strlen(title) == 0) && (strlen(message) == 0))) { switch (_notificationType) { - case ESP_PUSHOVER_NOTIFICATION: - return sendPushoverMSG(title, message); - break; - case ESP_EMAIL_NOTIFICATION: - return sendEmailMSG(title, message); - break; - case ESP_LINE_NOTIFICATION : - return sendLineMSG(title, message); - break; - default: - break; + case ESP_PUSHOVER_NOTIFICATION: return sendPushoverMSG(title, message); break; + case ESP_EMAIL_NOTIFICATION: return sendEmailMSG(title, message); break; + case ESP_LINE_NOTIFICATION: return sendLineMSG(title, message); break; + default: break; } } return false; @@ -124,9 +114,9 @@ bool NotificationsService::sendMSG(const char* title, const char* message) { //Messages are currently limited to 1024 4-byte UTF-8 characters //but we do not do any check bool NotificationsService::sendPushoverMSG(const char* title, const char* message) { - String data; - String postcmd; - bool res; + String data; + String postcmd; + bool res; WiFiClientSecure Notificationclient; if (!Notificationclient.connect(_serveraddress.c_str(), _port)) { log_d("Error connecting server %s:%d", _serveraddress.c_str(), _port); @@ -136,7 +126,8 @@ bool NotificationsService::sendPushoverMSG(const char* title, const char* messag data = "user="; data += _token1; data += "&token="; - data += _token2;; + data += _token2; + ; data += "&title="; data += title; data += "&message="; @@ -144,14 +135,15 @@ bool NotificationsService::sendPushoverMSG(const char* title, const char* messag data += "&device="; data += wifi_config.Hostname(); //build post query - postcmd = "POST /1/messages.json HTTP/1.1\r\nHost: api.pushover.net\r\nConnection: close\r\nCache-Control: no-cache\r\nUser-Agent: ESP3D\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nContent-Length: "; - postcmd += data.length(); - postcmd += "\r\n\r\n"; - postcmd += data; + postcmd = "POST /1/messages.json HTTP/1.1\r\nHost: api.pushover.net\r\nConnection: close\r\nCache-Control: no-cache\r\nUser-Agent: " + "ESP3D\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nContent-Length: "; + postcmd += data.length(); + postcmd += "\r\n\r\n"; + postcmd += data; log_d("Query: %s", postcmd.c_str()); //send query Notificationclient.print(postcmd); - res = Wait4Answer(Notificationclient, "{", "\"status\":1", PUSHOVERTIMEOUT); + res = Wait4Answer(Notificationclient, "{", "\"status\":1", PUSHOVERTIMEOUT); Notificationclient.stop(); return res; } @@ -240,9 +232,9 @@ bool NotificationsService::sendEmailMSG(const char* title, const char* message) return true; } bool NotificationsService::sendLineMSG(const char* title, const char* message) { - String data; - String postcmd; - bool res; + String data; + String postcmd; + bool res; WiFiClientSecure Notificationclient; (void)title; if (!Notificationclient.connect(_serveraddress.c_str(), _port)) { @@ -253,24 +245,26 @@ bool NotificationsService::sendLineMSG(const char* title, const char* message) { data = "message="; data += message; //build post query - postcmd = "POST /api/notify HTTP/1.1\r\nHost: notify-api.line.me\r\nConnection: close\r\nCache-Control: no-cache\r\nUser-Agent: ESP3D\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nContent-Type: application/x-www-form-urlencoded\r\n"; - postcmd += "Authorization: Bearer "; - postcmd += _token1 + "\r\n"; - postcmd += "Content-Length: "; - postcmd += data.length(); - postcmd += "\r\n\r\n"; - postcmd += data; + postcmd = "POST /api/notify HTTP/1.1\r\nHost: notify-api.line.me\r\nConnection: close\r\nCache-Control: no-cache\r\nUser-Agent: " + "ESP3D\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nContent-Type: " + "application/x-www-form-urlencoded\r\n"; + postcmd += "Authorization: Bearer "; + postcmd += _token1 + "\r\n"; + postcmd += "Content-Length: "; + postcmd += data.length(); + postcmd += "\r\n\r\n"; + postcmd += data; log_d("Query: %s", postcmd.c_str()); //send query Notificationclient.print(postcmd); - res = Wait4Answer(Notificationclient, "{", "\"status\":200", LINETIMEOUT); + res = Wait4Answer(Notificationclient, "{", "\"status\":200", LINETIMEOUT); Notificationclient.stop(); return res; } //Email#serveraddress:port bool NotificationsService::getPortFromSettings() { String tmp = notification_ts->get(); - int pos = tmp.lastIndexOf(':'); + int pos = tmp.lastIndexOf(':'); if (pos == -1) return false; _port = tmp.substring(pos + 1).toInt(); @@ -279,9 +273,9 @@ bool NotificationsService::getPortFromSettings() { } //Email#serveraddress:port bool NotificationsService::getServerAddressFromSettings() { - String tmp = notification_ts->get(); - int pos1 = tmp.indexOf('#'); - int pos2 = tmp.lastIndexOf(':'); + String tmp = notification_ts->get(); + int pos1 = tmp.indexOf('#'); + int pos2 = tmp.lastIndexOf(':'); if ((pos1 == -1) || (pos2 == -1)) return false; //TODO add a check for valid email ? @@ -292,7 +286,7 @@ bool NotificationsService::getServerAddressFromSettings() { //Email#serveraddress:port bool NotificationsService::getEmailFromSettings() { String tmp = notification_ts->get(); - int pos = tmp.indexOf('#'); + int pos = tmp.indexOf('#'); if (pos == -1) return false; _settings = tmp.substring(0, pos); @@ -301,34 +295,31 @@ bool NotificationsService::getEmailFromSettings() { return true; } - bool NotificationsService::begin() { end(); _notificationType = notification_type->get(); switch (_notificationType) { - case 0: //no notification = no error but no start - return true; - case ESP_PUSHOVER_NOTIFICATION: - _token1 = notification_t1->get(); - _token2 = notification_t2->get(); - _port = PUSHOVERPORT; - _serveraddress = PUSHOVERSERVER; - break; - case ESP_LINE_NOTIFICATION: - _token1 = notification_t1->get(); - _port = LINEPORT; - _serveraddress = LINESERVER; - break; - case ESP_EMAIL_NOTIFICATION: - _token1 = base64::encode(notification_t1->get()); - _token2 = base64::encode(notification_t2->get()); - if (!getEmailFromSettings() || !getPortFromSettings() || !getServerAddressFromSettings()) { - return false; - } - break; - default: - return false; - break; + case 0: //no notification = no error but no start + return true; + case ESP_PUSHOVER_NOTIFICATION: + _token1 = notification_t1->get(); + _token2 = notification_t2->get(); + _port = PUSHOVERPORT; + _serveraddress = PUSHOVERSERVER; + break; + case ESP_LINE_NOTIFICATION: + _token1 = notification_t1->get(); + _port = LINEPORT; + _serveraddress = LINESERVER; + break; + case ESP_EMAIL_NOTIFICATION: + _token1 = base64::encode(notification_t1->get()); + _token2 = base64::encode(notification_t2->get()); + if (!getEmailFromSettings() || !getPortFromSettings() || !getServerAddressFromSettings()) { + return false; + } + break; + default: return false; break; } bool res = true; if (WiFi.getMode() != WIFI_STA) @@ -341,18 +332,17 @@ bool NotificationsService::begin() { void NotificationsService::end() { if (!_started) return; - _started = false; + _started = false; _notificationType = 0; - _token1 = ""; - _token1 = ""; - _settings = ""; - _serveraddress = ""; - _port = 0; + _token1 = ""; + _token1 = ""; + _settings = ""; + _serveraddress = ""; + _port = 0; } void NotificationsService::handle() { - if (_started) { - } + if (_started) {} } -#endif //ENABLE_NOTIFICATIONS +#endif //ENABLE_NOTIFICATIONS diff --git a/Grbl_Esp32/notifications_service.h b/Grbl_Esp32/src/WebUI/NotificationsService.h similarity index 56% rename from Grbl_Esp32/notifications_service.h rename to Grbl_Esp32/src/WebUI/NotificationsService.h index c902365d..f1190de1 100644 --- a/Grbl_Esp32/notifications_service.h +++ b/Grbl_Esp32/src/WebUI/NotificationsService.h @@ -1,5 +1,7 @@ +#pragma once + /* - notifications_service.h - notifications service functions class + NotificationsService.h - notifications service functions class Copyright (c) 2014 Luc Lebosse. All rights reserved. @@ -18,39 +20,31 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ - - -#ifndef _NOTIFICATIONS_SERVICE_H -#define _NOTIFICATIONS_SERVICE_H - - class NotificationsService { - public: +public: NotificationsService(); ~NotificationsService(); - bool begin(); - void end(); - void handle(); - bool sendMSG(const char* title, const char* message); + bool begin(); + void end(); + void handle(); + bool sendMSG(const char* title, const char* message); const char* getTypeString(); - bool started(); - private: - bool _started; - uint8_t _notificationType; - String _token1; - String _token2; - String _settings; - String _serveraddress; + bool started(); + +private: + bool _started; + uint8_t _notificationType; + String _token1; + String _token2; + String _settings; + String _serveraddress; uint16_t _port; - bool sendPushoverMSG(const char* title, const char* message); - bool sendEmailMSG(const char* title, const char* message); - bool sendLineMSG(const char* title, const char* message); - bool getPortFromSettings(); - bool getServerAddressFromSettings(); - bool getEmailFromSettings(); + bool sendPushoverMSG(const char* title, const char* message); + bool sendEmailMSG(const char* title, const char* message); + bool sendLineMSG(const char* title, const char* message); + bool getPortFromSettings(); + bool getServerAddressFromSettings(); + bool getEmailFromSettings(); }; extern NotificationsService notificationsservice; - -#endif //_NOTIFICATIONS_SERVICE_H - diff --git a/Grbl_Esp32/serial2socket.cpp b/Grbl_Esp32/src/WebUI/Serial2Socket.cpp similarity index 76% rename from Grbl_Esp32/serial2socket.cpp rename to Grbl_Esp32/src/WebUI/Serial2Socket.cpp index 20ffef24..8949fdd7 100644 --- a/Grbl_Esp32/serial2socket.cpp +++ b/Grbl_Esp32/src/WebUI/Serial2Socket.cpp @@ -1,5 +1,5 @@ /* - serial2socket.cpp - serial 2 socket functions class + Serial2Socket.cpp - serial 2 socket functions class Copyright (c) 2014 Luc Lebosse. All rights reserved. @@ -18,43 +18,39 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#include "../Grbl.h" -#ifdef ARDUINO_ARCH_ESP32 +#if defined(ENABLE_WIFI) && defined(ENABLE_HTTP) -#include "grbl.h" - -#if defined (ENABLE_WIFI) && defined(ENABLE_HTTP) - - -#include "serial2socket.h" -#include "web_server.h" -#include -#include +# include "Serial2Socket.h" +# include "WebServer.h" +# include +# include Serial_2_Socket Serial2Socket; - Serial_2_Socket::Serial_2_Socket() { - _web_socket = NULL; + _web_socket = NULL; _TXbufferSize = 0; _RXbufferSize = 0; - _RXbufferpos = 0; + _RXbufferpos = 0; } Serial_2_Socket::~Serial_2_Socket() { - if (_web_socket) detachWS(); + if (_web_socket) + detachWS(); _TXbufferSize = 0; _RXbufferSize = 0; - _RXbufferpos = 0; + _RXbufferpos = 0; } void Serial_2_Socket::begin(long speed) { _TXbufferSize = 0; _RXbufferSize = 0; - _RXbufferpos = 0; + _RXbufferpos = 0; } void Serial_2_Socket::end() { _TXbufferSize = 0; _RXbufferSize = 0; - _RXbufferpos = 0; + _RXbufferpos = 0; } long Serial_2_Socket::baudRate() { @@ -63,7 +59,7 @@ long Serial_2_Socket::baudRate() { bool Serial_2_Socket::attachWS(void* web_socket) { if (web_socket) { - _web_socket = web_socket; + _web_socket = web_socket; _TXbufferSize = 0; return true; } @@ -82,9 +78,9 @@ int Serial_2_Socket::available() { return _RXbufferSize; } - size_t Serial_2_Socket::write(uint8_t c) { - if (!_web_socket) return 0; + if (!_web_socket) + return 0; write(&c, 1); return 1; } @@ -97,10 +93,12 @@ size_t Serial_2_Socket::write(const uint8_t* buffer, size_t size) { log_i("[SOCKET]No socket"); return 0; } -#if defined(ENABLE_SERIAL2SOCKET_OUT) - if (_TXbufferSize == 0)_lastflush = millis(); +# if defined(ENABLE_SERIAL2SOCKET_OUT) + if (_TXbufferSize == 0) + _lastflush = millis(); //send full line - if (_TXbufferSize + size > TXBUFFERSIZE) flush(); + if (_TXbufferSize + size > TXBUFFERSIZE) + flush(); //need periodic check to force to flush in case of no end for (int i = 0; i < size; i++) { _TXbuffer[_TXbufferSize] = buffer[i]; @@ -108,43 +106,49 @@ size_t Serial_2_Socket::write(const uint8_t* buffer, size_t size) { } log_i("[SOCKET]buffer size %d", _TXbufferSize); handle_flush(); -#endif +# endif return size; } int Serial_2_Socket::peek(void) { - if (_RXbufferSize > 0)return _RXbuffer[_RXbufferpos]; - else return -1; + if (_RXbufferSize > 0) + return _RXbuffer[_RXbufferpos]; + else + return -1; } bool Serial_2_Socket::push(const char* data) { -#if defined(ENABLE_SERIAL2SOCKET_IN) +# if defined(ENABLE_SERIAL2SOCKET_IN) int data_size = strlen(data); if ((data_size + _RXbufferSize) <= RXBUFFERSIZE) { int current = _RXbufferpos + _RXbufferSize; - if (current > RXBUFFERSIZE) current = current - RXBUFFERSIZE; + if (current > RXBUFFERSIZE) + current = current - RXBUFFERSIZE; for (int i = 0; i < data_size; i++) { - if (current > (RXBUFFERSIZE - 1)) current = 0; + if (current > (RXBUFFERSIZE - 1)) + current = 0; _RXbuffer[current] = data[i]; - current ++; + current++; } _RXbufferSize += strlen(data); return true; } return false; -#else +# else return true; -#endif +# endif } int Serial_2_Socket::read(void) { if (_RXbufferSize > 0) { int v = _RXbuffer[_RXbufferpos]; _RXbufferpos++; - if (_RXbufferpos > (RXBUFFERSIZE - 1))_RXbufferpos = 0; + if (_RXbufferpos > (RXBUFFERSIZE - 1)) + _RXbufferpos = 0; _RXbufferSize--; return v; - } else return -1; + } else + return -1; } void Serial_2_Socket::handle_flush() { @@ -170,6 +174,4 @@ void Serial_2_Socket::flush(void) { } } -#endif // ENABLE_WIFI - -#endif // ARDUINO_ARCH_ESP32 +#endif // ENABLE_WIFI diff --git a/Grbl_Esp32/serial2socket.h b/Grbl_Esp32/src/WebUI/Serial2Socket.h similarity index 53% rename from Grbl_Esp32/serial2socket.h rename to Grbl_Esp32/src/WebUI/Serial2Socket.h index f162acc6..9f51dbb9 100644 --- a/Grbl_Esp32/serial2socket.h +++ b/Grbl_Esp32/src/WebUI/Serial2Socket.h @@ -1,5 +1,7 @@ +#pragma once + /* - serial2socket.h - serial 2 socket functions class + Serial2Socket.h - serial 2 socket functions class Copyright (c) 2014 Luc Lebosse. All rights reserved. @@ -18,59 +20,45 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#include -#ifndef _SERIAL_2_SOCKET_H_ -#define _SERIAL_2_SOCKET_H_ - -#include "Print.h" #define TXBUFFERSIZE 1200 #define RXBUFFERSIZE 128 #define FLUSHTIMEOUT 500 -class Serial_2_Socket: public Print { - public: + +class Serial_2_Socket : public Print { +public: Serial_2_Socket(); ~Serial_2_Socket(); size_t write(uint8_t c); size_t write(const uint8_t* buffer, size_t size); - inline size_t write(const char* s) { - return write((uint8_t*) s, strlen(s)); - } - inline size_t write(unsigned long n) { - return write((uint8_t) n); - } - inline size_t write(long n) { - return write((uint8_t) n); - } - inline size_t write(unsigned int n) { - return write((uint8_t) n); - } - inline size_t write(int n) { - return write((uint8_t) n); - } - long baudRate(); - void begin(long speed); - void end(); - int available(); - int peek(void); - int read(void); - bool push(const char* data); - void flush(void); - void handle_flush(); - operator bool() const; - bool attachWS(void* web_socket); - bool detachWS(); - private: + inline size_t write(const char* s) { return write((uint8_t*)s, strlen(s)); } + inline size_t write(unsigned long n) { return write((uint8_t)n); } + inline size_t write(long n) { return write((uint8_t)n); } + inline size_t write(unsigned int n) { return write((uint8_t)n); } + inline size_t write(int n) { return write((uint8_t)n); } + long baudRate(); + void begin(long speed); + void end(); + int available(); + int peek(void); + int read(void); + bool push(const char* data); + void flush(void); + void handle_flush(); + operator bool() const; + bool attachWS(void* web_socket); + bool detachWS(); + +private: uint32_t _lastflush; - void* _web_socket; - uint8_t _TXbuffer[TXBUFFERSIZE]; + void* _web_socket; + uint8_t _TXbuffer[TXBUFFERSIZE]; uint16_t _TXbufferSize; - uint8_t _RXbuffer[RXBUFFERSIZE]; + uint8_t _RXbuffer[RXBUFFERSIZE]; uint16_t _RXbufferSize; uint16_t _RXbufferpos; }; - extern Serial_2_Socket Serial2Socket; - -#endif diff --git a/Grbl_Esp32/telnet_server.cpp b/Grbl_Esp32/src/WebUI/TelnetServer.cpp similarity index 74% rename from Grbl_Esp32/telnet_server.cpp rename to Grbl_Esp32/src/WebUI/TelnetServer.cpp index 13d36667..d63805ec 100644 --- a/Grbl_Esp32/telnet_server.cpp +++ b/Grbl_Esp32/src/WebUI/TelnetServer.cpp @@ -1,5 +1,5 @@ /* - telnet_server.cpp - telnet server functions class + TelnetServer.cpp - telnet server functions class Copyright (c) 2014 Luc Lebosse. All rights reserved. @@ -18,30 +18,28 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifdef ARDUINO_ARCH_ESP32 +#include "../Grbl.h" -#include "grbl.h" +#if defined(ENABLE_WIFI) && defined(ENABLE_TELNET) -#if defined (ENABLE_WIFI) && defined (ENABLE_TELNET) +# include "WifiServices.h" -#include "wifiservices.h" - -#include "telnet_server.h" -#include "wificonfig.h" -#include +# include "TelnetServer.h" +# include "WifiConfig.h" +# include Telnet_Server telnet_server; -bool Telnet_Server::_setupdone = false; -uint16_t Telnet_Server::_port = 0; -WiFiServer* Telnet_Server::_telnetserver = NULL; -WiFiClient Telnet_Server::_telnetClients[MAX_TLNT_CLIENTS]; -#ifdef ENABLE_TELNET_WELCOME_MSG - IPAddress Telnet_Server::_telnetClientsIP[MAX_TLNT_CLIENTS]; -#endif +bool Telnet_Server::_setupdone = false; +uint16_t Telnet_Server::_port = 0; +WiFiServer* Telnet_Server::_telnetserver = NULL; +WiFiClient Telnet_Server::_telnetClients[MAX_TLNT_CLIENTS]; +# ifdef ENABLE_TELNET_WELCOME_MSG +IPAddress Telnet_Server::_telnetClientsIP[MAX_TLNT_CLIENTS]; +# endif Telnet_Server::Telnet_Server() { _RXbufferSize = 0; - _RXbufferpos = 0; + _RXbufferpos = 0; } Telnet_Server::~Telnet_Server() { end(); @@ -51,7 +49,8 @@ bool Telnet_Server::begin() { bool no_error = true; end(); _RXbufferSize = 0; - _RXbufferpos = 0;; + _RXbufferpos = 0; + ; if (telnet_enable->get() == 0) { return false; } @@ -69,9 +68,9 @@ bool Telnet_Server::begin() { } void Telnet_Server::end() { - _setupdone = false; + _setupdone = false; _RXbufferSize = 0; - _RXbufferpos = 0; + _RXbufferpos = 0; if (_telnetserver) { delete _telnetserver; _telnetserver = NULL; @@ -85,10 +84,11 @@ void Telnet_Server::clearClients() { for (i = 0; i < MAX_TLNT_CLIENTS; i++) { //find free/disconnected spot if (!_telnetClients[i] || !_telnetClients[i].connected()) { -#ifdef ENABLE_TELNET_WELCOME_MSG +# ifdef ENABLE_TELNET_WELCOME_MSG _telnetClientsIP[i] = IPAddress(0, 0, 0, 0); -#endif - if (_telnetClients[i]) _telnetClients[i].stop(); +# endif + if (_telnetClients[i]) + _telnetClients[i].stop(); _telnetClients[i] = _telnetserver->available(); break; } @@ -129,19 +129,21 @@ void Telnet_Server::handle() { //uint8_t c; for (uint8_t i = 0; i < MAX_TLNT_CLIENTS; i++) { if (_telnetClients[i] && _telnetClients[i].connected()) { -#ifdef ENABLE_TELNET_WELCOME_MSG +# ifdef ENABLE_TELNET_WELCOME_MSG if (_telnetClientsIP[i] != _telnetClients[i].remoteIP()) { report_init_message(CLIENT_TELNET); _telnetClientsIP[i] = _telnetClients[i].remoteIP(); } -#endif +# endif if (_telnetClients[i].available()) { uint8_t buf[1024]; COMMANDS::wait(0); - int readlen = _telnetClients[i].available(); + int readlen = _telnetClients[i].available(); int writelen = TELNETRXBUFFERSIZE - available(); - if (readlen > 1024) readlen = 1024; - if (readlen > writelen) readlen = writelen; + if (readlen > 1024) + readlen = 1024; + if (readlen > writelen) + readlen = writelen; if (readlen > 0) { _telnetClients[i].read(buf, readlen); push(buf, readlen); @@ -150,9 +152,9 @@ void Telnet_Server::handle() { } } else { if (_telnetClients[i]) { -#ifdef ENABLE_TELNET_WELCOME_MSG +# ifdef ENABLE_TELNET_WELCOME_MSG _telnetClientsIP[i] = IPAddress(0, 0, 0, 0); -#endif +# endif _telnetClients[i].stop(); } } @@ -161,8 +163,10 @@ void Telnet_Server::handle() { } int Telnet_Server::peek(void) { - if (_RXbufferSize > 0)return _RXbuffer[_RXbufferpos]; - else return -1; + if (_RXbufferSize > 0) + return _RXbuffer[_RXbufferpos]; + else + return -1; } int Telnet_Server::available() { @@ -177,8 +181,10 @@ bool Telnet_Server::push(uint8_t data) { log_i("[TELNET]push %c", data); if ((1 + _RXbufferSize) <= TELNETRXBUFFERSIZE) { int current = _RXbufferpos + _RXbufferSize; - if (current > TELNETRXBUFFERSIZE) current = current - TELNETRXBUFFERSIZE; - if (current > (TELNETRXBUFFERSIZE - 1)) current = 0; + if (current > TELNETRXBUFFERSIZE) + current = current - TELNETRXBUFFERSIZE; + if (current > (TELNETRXBUFFERSIZE - 1)) + current = 0; _RXbuffer[current] = data; _RXbufferSize++; log_i("[TELNET]buffer size %d", _RXbufferSize); @@ -190,13 +196,15 @@ bool Telnet_Server::push(uint8_t data) { bool Telnet_Server::push(const uint8_t* data, int data_size) { if ((data_size + _RXbufferSize) <= TELNETRXBUFFERSIZE) { int data_processed = 0; - int current = _RXbufferpos + _RXbufferSize; - if (current > TELNETRXBUFFERSIZE) current = current - TELNETRXBUFFERSIZE; + int current = _RXbufferpos + _RXbufferSize; + if (current > TELNETRXBUFFERSIZE) + current = current - TELNETRXBUFFERSIZE; for (int i = 0; i < data_size; i++) { - if (current > (TELNETRXBUFFERSIZE - 1)) current = 0; + if (current > (TELNETRXBUFFERSIZE - 1)) + current = 0; if (char(data[i]) != '\r') { _RXbuffer[current] = data[i]; - current ++; + current++; data_processed++; } COMMANDS::wait(0); @@ -213,12 +221,12 @@ int Telnet_Server::read(void) { int v = _RXbuffer[_RXbufferpos]; //log_d("[TELNET]read %c",char(v)); _RXbufferpos++; - if (_RXbufferpos > (TELNETRXBUFFERSIZE - 1))_RXbufferpos = 0; + if (_RXbufferpos > (TELNETRXBUFFERSIZE - 1)) + _RXbufferpos = 0; _RXbufferSize--; return v; - } else return -1; + } else + return -1; } -#endif // Enable TELNET && ENABLE_WIFI - -#endif // ARDUINO_ARCH_ESP32 +#endif // Enable TELNET && ENABLE_WIFI diff --git a/Grbl_Esp32/telnet_server.h b/Grbl_Esp32/src/WebUI/TelnetServer.h similarity index 60% rename from Grbl_Esp32/telnet_server.h rename to Grbl_Esp32/src/WebUI/TelnetServer.h index 94146329..7e1dfde8 100644 --- a/Grbl_Esp32/telnet_server.h +++ b/Grbl_Esp32/src/WebUI/TelnetServer.h @@ -1,5 +1,7 @@ +#pragma once + /* - telnet_server.h - telnet service functions class + TelnetServer.h - telnet service functions class Copyright (c) 2014 Luc Lebosse. All rights reserved. @@ -21,11 +23,7 @@ //how many clients should be able to telnet to this ESP32 #define MAX_TLNT_CLIENTS 1 -#ifndef _TELNET_SERVER_H -#define _TELNET_SERVER_H - - -#include "config.h" +#include "../Config.h" class WiFiServer; class WiFiClient; @@ -33,36 +31,34 @@ class WiFiClient; #define FLUSHTIMEOUT 500 class Telnet_Server { - public: +public: Telnet_Server(); ~Telnet_Server(); - bool begin(); - void end(); - void handle(); - size_t write(const uint8_t* buffer, size_t size); - int read(void); - int peek(void); - int available(); - int get_rx_buffer_available(); - bool push(uint8_t data); - bool push(const uint8_t* data, int datasize); - static uint16_t port() {return _port;} - private: - static bool _setupdone; + bool begin(); + void end(); + void handle(); + size_t write(const uint8_t* buffer, size_t size); + int read(void); + int peek(void); + int available(); + int get_rx_buffer_available(); + bool push(uint8_t data); + bool push(const uint8_t* data, int datasize); + static uint16_t port() { return _port; } + +private: + static bool _setupdone; static WiFiServer* _telnetserver; - static WiFiClient _telnetClients[MAX_TLNT_CLIENTS]; + static WiFiClient _telnetClients[MAX_TLNT_CLIENTS]; #ifdef ENABLE_TELNET_WELCOME_MSG static IPAddress _telnetClientsIP[MAX_TLNT_CLIENTS]; #endif static uint16_t _port; - void clearClients(); - uint32_t _lastflush; - uint8_t _RXbuffer[TELNETRXBUFFERSIZE]; - uint16_t _RXbufferSize; - uint16_t _RXbufferpos; + void clearClients(); + uint32_t _lastflush; + uint8_t _RXbuffer[TELNETRXBUFFERSIZE]; + uint16_t _RXbufferSize; + uint16_t _RXbufferpos; }; extern Telnet_Server telnet_server; - -#endif - diff --git a/Grbl_Esp32/src/WebUI/WebServer.cpp b/Grbl_Esp32/src/WebUI/WebServer.cpp new file mode 100644 index 00000000..221c648a --- /dev/null +++ b/Grbl_Esp32/src/WebUI/WebServer.cpp @@ -0,0 +1,1809 @@ +/* + WebServer.cpp - web server functions class + + Copyright (c) 2014 Luc Lebosse. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "../Grbl.h" + +#if defined(ENABLE_WIFI) && defined(ENABLE_HTTP) + +# include "WifiServices.h" + +# include "ESPResponse.h" +# include "Serial2Socket.h" +# include "WebServer.h" +# include +# include +# include +# include +# ifdef ENABLE_SD_CARD +# include +# include "../SDCard.h" +# endif +# include +# include +# include +# include +# include +# ifdef ENABLE_MDNS +# include +# endif +# ifdef ENABLE_SSDP +# include +# endif +# ifdef ENABLE_CAPTIVE_PORTAL +# include +const byte DNS_PORT = 53; +DNSServer dnsServer; +# endif +# include + +//embedded response file if no files on SPIFFS +# include "NoFile.h" + +//Upload status +typedef enum { + UPLOAD_STATUS_NONE = 0, + UPLOAD_STATUS_FAILED = 1, + UPLOAD_STATUS_CANCELLED = 2, + UPLOAD_STATUS_SUCCESSFUL = 3, + UPLOAD_STATUS_ONGOING = 4 +} upload_status_type; + +//Default 404 +const char PAGE_404[] = + "\n\nRedirecting... \n\n\n
Unknown page : $QUERY$- you will be " + "redirected...\n

\nif not redirected, click here\n

\n\n\n\n
\n\n\n\n"; +const char PAGE_CAPTIVE[] = + "\n\nCaptive Portal \n\n\n
Captive Portal page : $QUERY$- you will be " + "redirected...\n

\nif not redirected, click here\n

\n\n\n\n
\n\n\n\n"; + +//error codes fo upload +# define ESP_ERROR_AUTHENTICATION 1 +# define ESP_ERROR_FILE_CREATION 2 +# define ESP_ERROR_FILE_WRITE 3 +# define ESP_ERROR_UPLOAD 4 +# define ESP_ERROR_NOT_ENOUGH_SPACE 5 +# define ESP_ERROR_UPLOAD_CANCELLED 6 +# define ESP_ERROR_FILE_CLOSE 7 +# define ESP_ERROR_NO_SD 8 + +Web_Server web_server; +bool Web_Server::_setupdone = false; +uint16_t Web_Server::_port = 0; +long Web_Server::_id_connection = 0; +uint8_t Web_Server::_upload_status = UPLOAD_STATUS_NONE; +WebServer* Web_Server::_webserver = NULL; +WebSocketsServer* Web_Server::_socket_server = NULL; +# ifdef ENABLE_AUTHENTICATION +auth_ip* Web_Server::_head = NULL; +uint8_t Web_Server::_nb_ip = 0; +# define MAX_AUTH_IP 10 +# endif +Web_Server::Web_Server() {} +Web_Server::~Web_Server() { + end(); +} + +long Web_Server::get_client_ID() { + return _id_connection; +} + +bool Web_Server::begin() { + bool no_error = true; + _setupdone = false; + if (http_enable->get() == 0) { + return false; + } + _port = http_port->get(); + + //create instance + _webserver = new WebServer(_port); +# ifdef ENABLE_AUTHENTICATION + //here the list of headers to be recorded + const char* headerkeys[] = { "Cookie" }; + size_t headerkeyssize = sizeof(headerkeys) / sizeof(char*); + //ask server to track these headers + _webserver->collectHeaders(headerkeys, headerkeyssize); +# endif + _socket_server = new WebSocketsServer(_port + 1); + _socket_server->begin(); + _socket_server->onEvent(handle_Websocket_Event); + + //Websocket output + Serial2Socket.attachWS(_socket_server); + + //events functions + //_web_events->onConnect(handle_onevent_connect); + //events management + // _webserver->addHandler(_web_events); + + //Websocket function + //_web_socket->onEvent(handle_Websocket_Event); + //Websocket management + //_webserver->addHandler(_web_socket); + + //Web server handlers + //trick to catch command line on "/" before file being processed + _webserver->on("/", HTTP_ANY, handle_root); + + //Page not found handler + _webserver->onNotFound(handle_not_found); + + //need to be there even no authentication to say to UI no authentication + _webserver->on("/login", HTTP_ANY, handle_login); + + //web commands + _webserver->on("/command", HTTP_ANY, handle_web_command); + _webserver->on("/command_silent", HTTP_ANY, handle_web_command_silent); + + //SPIFFS + _webserver->on("/files", HTTP_ANY, handleFileList, SPIFFSFileupload); + + //web update + _webserver->on("/updatefw", HTTP_ANY, handleUpdate, WebUpdateUpload); + +# ifdef ENABLE_SD_CARD + //Direct SD management + _webserver->on("/upload", HTTP_ANY, handle_direct_SDFileList, SDFile_direct_upload); + //_webserver->on("/SD", HTTP_ANY, handle_SDCARD); +# endif + +# ifdef ENABLE_CAPTIVE_PORTAL + if (WiFi.getMode() == WIFI_AP) { + // if DNSServer is started with "*" for domain name, it will reply with + // provided IP to all DNS request + dnsServer.start(DNS_PORT, "*", WiFi.softAPIP()); + grbl_send(CLIENT_ALL, "[MSG:Captive Portal Started]\r\n"); + _webserver->on("/generate_204", HTTP_ANY, handle_root); + _webserver->on("/gconnectivitycheck.gstatic.com", HTTP_ANY, handle_root); + //do not forget the / at the end + _webserver->on("/fwlink/", HTTP_ANY, handle_root); + } +# endif + +# ifdef ENABLE_SSDP + //SSDP service presentation + if (WiFi.getMode() == WIFI_STA) { + _webserver->on("/description.xml", HTTP_GET, handle_SSDP); + //Add specific for SSDP + SSDP.setSchemaURL("description.xml"); + SSDP.setHTTPPort(_port); + SSDP.setName(wifi_config.Hostname()); + SSDP.setURL("/"); + SSDP.setDeviceType("upnp:rootdevice"); + /*Any customization could be here + SSDP.setModelName (ESP32_MODEL_NAME); + SSDP.setModelURL (ESP32_MODEL_URL); + SSDP.setModelNumber (ESP_MODEL_NUMBER); + SSDP.setManufacturer (ESP_MANUFACTURER_NAME); + SSDP.setManufacturerURL (ESP_MANUFACTURER_URL); + */ + + //Start SSDP + grbl_send(CLIENT_ALL, "[MSG:SSDP Started]\r\n"); + SSDP.begin(); + } +# endif + grbl_send(CLIENT_ALL, "[MSG:HTTP Started]\r\n"); + //start webserver + _webserver->begin(); +# ifdef ENABLE_MDNS + //add mDNS + if (WiFi.getMode() == WIFI_STA) { + MDNS.addService("http", "tcp", _port); + } +# endif + _setupdone = true; + return no_error; +} + +void Web_Server::end() { + _setupdone = false; +# ifdef ENABLE_SSDP + SSDP.end(); +# endif //ENABLE_SSDP +# ifdef ENABLE_MDNS + //remove mDNS + mdns_service_remove("_http", "_tcp"); +# endif + if (_socket_server) { + delete _socket_server; + _socket_server = NULL; + } + if (_webserver) { + delete _webserver; + _webserver = NULL; + } +# ifdef ENABLE_AUTHENTICATION + while (_head) { + auth_ip* current = _head; + _head = _head->_next; + delete current; + } + _nb_ip = 0; +# endif +} + +//Root of Webserver///////////////////////////////////////////////////// + +void Web_Server::handle_root() { + String path = "/index.html"; + String contentType = getContentType(path); + String pathWithGz = path + ".gz"; + //if have a index.html or gzip version this is default root page + if ((SPIFFS.exists(pathWithGz) || SPIFFS.exists(path)) && !_webserver->hasArg("forcefallback") && + _webserver->arg("forcefallback") != "yes") { + if (SPIFFS.exists(pathWithGz)) { + path = pathWithGz; + } + File file = SPIFFS.open(path, FILE_READ); + _webserver->streamFile(file, contentType); + file.close(); + return; + } + //if no lets launch the default content + _webserver->sendHeader("Content-Encoding", "gzip"); + _webserver->send_P(200, "text/html", PAGE_NOFILES, PAGE_NOFILES_SIZE); +} + +//Handle not registred path on SPIFFS neither SD /////////////////////// +void Web_Server::handle_not_found() { + if (is_authenticated() == LEVEL_GUEST) { + _webserver->sendContent_P("HTTP/1.1 301 OK\r\nLocation: /\r\nCache-Control: no-cache\r\n\r\n"); + //_webserver->client().stop(); + return; + } + bool page_not_found = false; + String path = _webserver->urlDecode(_webserver->uri()); + String contentType = getContentType(path); + String pathWithGz = path + ".gz"; + +# ifdef ENABLE_SD_CARD + if ((path.substring(0, 4) == "/SD/")) { + //remove /SD + path = path.substring(3); + if (SD.exists((char*)pathWithGz.c_str()) || SD.exists((char*)path.c_str())) { + if (SD.exists((char*)pathWithGz.c_str())) { + path = pathWithGz; + } + File datafile = SD.open((char*)path.c_str()); + if (datafile) { + vTaskDelay(1 / portTICK_RATE_MS); + size_t totalFileSize = datafile.size(); + size_t i = 0; + bool done = false; + _webserver->setContentLength(totalFileSize); + _webserver->send(200, contentType, ""); + uint8_t buf[1024]; + while (!done) { + vTaskDelay(1 / portTICK_RATE_MS); + int v = datafile.read(buf, 1024); + if ((v == -1) || (v == 0)) { + done = true; + } else { + _webserver->client().write(buf, 1024); + i += v; + } + if (i >= totalFileSize) + done = true; + } + datafile.close(); + if (i != totalFileSize) { + //error: TBD + } + return; + } + } + String content = "cannot find "; + content += path; + _webserver->send(404, "text/plain", content.c_str()); + return; + } else +# endif + if (SPIFFS.exists(pathWithGz) || SPIFFS.exists(path)) { + if (SPIFFS.exists(pathWithGz)) { + path = pathWithGz; + } + File file = SPIFFS.open(path, FILE_READ); + _webserver->streamFile(file, contentType); + file.close(); + return; + } else { + page_not_found = true; + } + + if (page_not_found) { +# ifdef ENABLE_CAPTIVE_PORTAL + if (WiFi.getMode() == WIFI_AP) { + String contentType = PAGE_CAPTIVE; + String stmp = WiFi.softAPIP().toString(); + //Web address = ip + port + String KEY_IP = "$WEB_ADDRESS$"; + String KEY_QUERY = "$QUERY$"; + if (_port != 80) { + stmp += ":"; + stmp += String(_port); + } + contentType.replace(KEY_IP, stmp); + contentType.replace(KEY_IP, stmp); + contentType.replace(KEY_QUERY, _webserver->uri()); + _webserver->send(200, "text/html", contentType); + //_webserver->sendContent_P(NOT_AUTH_NF); + //_webserver->client().stop(); + return; + } +# endif + path = "/404.htm"; + contentType = getContentType(path); + pathWithGz = path + ".gz"; + if (SPIFFS.exists(pathWithGz) || SPIFFS.exists(path)) { + if (SPIFFS.exists(pathWithGz)) { + path = pathWithGz; + } + File file = SPIFFS.open(path, FILE_READ); + _webserver->streamFile(file, contentType); + file.close(); + + } else { + //if not template use default page + contentType = PAGE_404; + String stmp; + if (WiFi.getMode() == WIFI_STA) { + stmp = WiFi.localIP().toString(); + } else { + stmp = WiFi.softAPIP().toString(); + } + //Web address = ip + port + String KEY_IP = "$WEB_ADDRESS$"; + String KEY_QUERY = "$QUERY$"; + if (_port != 80) { + stmp += ":"; + stmp += String(_port); + } + contentType.replace(KEY_IP, stmp); + contentType.replace(KEY_QUERY, _webserver->uri()); + _webserver->send(200, "text/html", contentType); + } + } +} + +# ifdef ENABLE_SSDP +//http SSDP xml presentation +void Web_Server::handle_SSDP() { + StreamString sschema; + if (sschema.reserve(1024)) { + String templ = "" + "" + "" + "1" + "0" + "" + "http://%s:%u/" + "" + "upnp:rootdevice" + "%s" + "/" + "%s" + "ESP32" + "Marlin" + "http://espressif.com/en/products/hardware/esp-wroom-32/overview" + "Espressif Systems" + "http://espressif.com" + "uuid:%s" + "" + "\r\n" + "\r\n"; + char uuid[37]; + String sip = WiFi.localIP().toString(); + uint32_t chipId = (uint16_t)(ESP.getEfuseMac() >> 32); + sprintf(uuid, + "38323636-4558-4dda-9188-cda0e6%02x%02x%02x", + (uint16_t)((chipId >> 16) & 0xff), + (uint16_t)((chipId >> 8) & 0xff), + (uint16_t)chipId & 0xff); + String serialNumber = String(chipId); + sschema.printf(templ.c_str(), sip.c_str(), _port, wifi_config.Hostname().c_str(), serialNumber.c_str(), uuid); + _webserver->send(200, "text/xml", (String)sschema); + } else { + _webserver->send(500); + } +} +# endif + +bool Web_Server::is_realtime_cmd(char c) { + if (c == CMD_STATUS_REPORT) + return true; + if (c == CMD_CYCLE_START) + return true; + if (c == CMD_RESET) + return true; + if (c == CMD_FEED_HOLD) + return true; + if (c == CMD_SAFETY_DOOR) + return true; + if (c == CMD_JOG_CANCEL) + return true; + if (c == CMD_DEBUG_REPORT) + return true; + if (c == CMD_FEED_OVR_RESET) + return true; + if (c == CMD_FEED_OVR_COARSE_PLUS) + return true; + if (c == CMD_FEED_OVR_COARSE_MINUS) + return true; + if (c == CMD_FEED_OVR_FINE_PLUS) + return true; + if (c == CMD_FEED_OVR_FINE_MINUS) + return true; + if (c == CMD_RAPID_OVR_RESET) + return true; + if (c == CMD_RAPID_OVR_MEDIUM) + return true; + if (c == CMD_RAPID_OVR_LOW) + return true; + if (c == CMD_SPINDLE_OVR_COARSE_PLUS) + return true; + if (c == CMD_SPINDLE_OVR_COARSE_MINUS) + return true; + if (c == CMD_SPINDLE_OVR_FINE_PLUS) + return true; + if (c == CMD_SPINDLE_OVR_FINE_MINUS) + return true; + if (c == CMD_SPINDLE_OVR_STOP) + return true; + if (c == CMD_COOLANT_FLOOD_OVR_TOGGLE) + return true; + if (c == CMD_COOLANT_MIST_OVR_TOGGLE) + return true; + return false; +} + +void Web_Server::_handle_web_command(bool silent) { + //to save time if already disconnected + //if (_webserver->hasArg ("PAGEID") ) { + // if (_webserver->arg ("PAGEID").length() > 0 ) { + // if (_webserver->arg ("PAGEID").toInt() != _id_connection) { + // _webserver->send (200, "text/plain", "Invalid command"); + // return; + // } + // } + //} + auth_t auth_level = is_authenticated(); + String cmd = ""; + if (_webserver->hasArg("plain")) { + cmd = _webserver->arg("plain"); + } else if (_webserver->hasArg("commandText")) { + cmd = _webserver->arg("commandText"); + } else { + _webserver->send(200, "text/plain", "Invalid command"); + return; + } + //if it is internal command [ESPXXX] + cmd.trim(); + int ESPpos = cmd.indexOf("[ESP"); + if (ESPpos > -1) { + char line[256]; + strncpy(line, cmd.c_str(), 255); + ESPResponseStream* espresponse = silent ? NULL : new ESPResponseStream(_webserver); + err_t err = system_execute_line(line, espresponse, auth_level); + String answer; + if (err == STATUS_OK) { + answer = "ok"; + } else { + const char* msg = errorString(err); + answer = "Error: "; + if (msg) { + answer += msg; + } else { + answer += err; + } + } + if (silent || !espresponse->anyOutput()) { + _webserver->send(err ? 401 : 200, "text/plain", answer.c_str()); + } else { + espresponse->flush(); + } + } else { //execute GCODE + if (auth_level == LEVEL_GUEST) { + _webserver->send(401, "text/plain", "Authentication failed!\n"); + return; + } + //Instead of send several commands one by one by web / send full set and split here + String scmd; + const char* res = ""; + uint8_t sindex = 0; + // TODO Settings - this is very inefficient. get_Splited_Value() is O(n^2) + // when it could easily be O(n). Also, it would be just as easy to push + // the entire string into Serial2Socket and pull off lines from there. + for (uint8_t sindex = 0; (scmd = get_Splited_Value(cmd, '\n', sindex)) != ""; sindex++) { + // 0xC2 is an HTML encoding prefix that, in UTF-8 mode, + // precede 0x90 and 0xa0-0bf, which are GRBL realtime commands. + // There are other encodings for 0x91-0x9f, so I am not sure + // how - or whether - those commands work. + // Ref: https://www.w3schools.com/tags/ref_urlencode.ASP + if (!silent && (scmd.length() == 2) && (scmd[0] == 0xC2)) { + scmd[0] = scmd[1]; + scmd.remove(1, 1); + } + if (scmd.length() > 1) + scmd += "\n"; + else if (!is_realtime_cmd(scmd[0])) + scmd += "\n"; + if (!Serial2Socket.push(scmd.c_str())) + res = "Error"; + } + _webserver->send(200, "text/plain", res); + } +} + +//login status check +void Web_Server::handle_login() { +# ifdef ENABLE_AUTHENTICATION + String smsg; + String sUser, sPassword; + String auths; + int code = 200; + bool msg_alert_error = false; + //disconnect can be done anytime no need to check credential + if (_webserver->hasArg("DISCONNECT")) { + String cookie = _webserver->header("Cookie"); + int pos = cookie.indexOf("ESPSESSIONID="); + String sessionID; + if (pos != -1) { + int pos2 = cookie.indexOf(";", pos); + sessionID = cookie.substring(pos + strlen("ESPSESSIONID="), pos2); + } + ClearAuthIP(_webserver->client().remoteIP(), sessionID.c_str()); + _webserver->sendHeader("Set-Cookie", "ESPSESSIONID=0"); + _webserver->sendHeader("Cache-Control", "no-cache"); + String buffer2send = "{\"status\":\"Ok\",\"authentication_lvl\":\"guest\"}"; + _webserver->send(code, "application/json", buffer2send); + //_webserver->client().stop(); + return; + } + + auth_t auth_level = is_authenticated(); + if (auth_level == LEVEL_GUEST) + auths = "guest"; + else if (auth_level == LEVEL_USER) + auths = "user"; + else if (auth_level == LEVEL_ADMIN) + auths = "admin"; + else + auths = "???"; + + //check is it is a submission or a query + if (_webserver->hasArg("SUBMIT")) { + //is there a correct list of query? + if (_webserver->hasArg("PASSWORD") && _webserver->hasArg("USER")) { + //USER + sUser = _webserver->arg("USER"); + if (!((sUser == DEFAULT_ADMIN_LOGIN) || (sUser == DEFAULT_USER_LOGIN))) { + msg_alert_error = true; + smsg = "Error : Incorrect User"; + code = 401; + } + if (msg_alert_error == false) { + //Password + sPassword = _webserver->arg("PASSWORD"); + String sadminPassword = admin_password->get(); + String suserPassword = user_password->get(); + + if (!(((sUser == DEFAULT_ADMIN_LOGIN) && (strcmp(sPassword.c_str(), sadminPassword.c_str()) == 0)) || + ((sUser == DEFAULT_USER_LOGIN) && (strcmp(sPassword.c_str(), suserPassword.c_str()) == 0)))) { + msg_alert_error = true; + smsg = "Error: Incorrect password"; + code = 401; + } + } + } else { + msg_alert_error = true; + smsg = "Error: Missing data"; + code = 500; + } + //change password + if (_webserver->hasArg("PASSWORD") && _webserver->hasArg("USER") && _webserver->hasArg("NEWPASSWORD") && (msg_alert_error == false)) { + String newpassword = _webserver->arg("NEWPASSWORD"); + if (COMMANDS::isLocalPasswordValid((char*)newpassword.c_str())) { + err_t err; + if (sUser == DEFAULT_ADMIN_LOGIN) { + err = admin_password->setStringValue((char*)newpassword.c_str()); + } else { + err = user_password->setStringValue((char*)newpassword.c_str()); + } + if (err) { + msg_alert_error = true; + smsg = "Error: Cannot apply changes"; + code = 500; + } + } else { + msg_alert_error = true; + smsg = "Error: Incorrect password"; + code = 500; + } + } + if ((code == 200) || (code == 500)) { + auth_t current_auth_level; + if (sUser == DEFAULT_ADMIN_LOGIN) { + current_auth_level = LEVEL_ADMIN; + } else if (sUser == DEFAULT_USER_LOGIN) { + current_auth_level = LEVEL_USER; + } else { + current_auth_level = LEVEL_GUEST; + } + //create Session + if ((current_auth_level != auth_level) || (auth_level == LEVEL_GUEST)) { + auth_ip* current_auth = new auth_ip; + current_auth->level = current_auth_level; + current_auth->ip = _webserver->client().remoteIP(); + strcpy(current_auth->sessionID, create_session_ID()); + strcpy(current_auth->userID, sUser.c_str()); + current_auth->last_time = millis(); + if (AddAuthIP(current_auth)) { + String tmps = "ESPSESSIONID="; + tmps += current_auth->sessionID; + _webserver->sendHeader("Set-Cookie", tmps); + _webserver->sendHeader("Cache-Control", "no-cache"); + switch (current_auth->level) { + case LEVEL_ADMIN: auths = "admin"; break; + case LEVEL_USER: auths = "user"; break; + default: auths = "guest"; break; + } + } else { + delete current_auth; + msg_alert_error = true; + code = 500; + smsg = "Error: Too many connections"; + } + } + } + if (code == 200) + smsg = "Ok"; + + //build JSON + String buffer2send = "{\"status\":\"" + smsg + "\",\"authentication_lvl\":\""; + buffer2send += auths; + buffer2send += "\"}"; + _webserver->send(code, "application/json", buffer2send); + } else { + if (auth_level != LEVEL_GUEST) { + String cookie = _webserver->header("Cookie"); + int pos = cookie.indexOf("ESPSESSIONID="); + String sessionID; + if (pos != -1) { + int pos2 = cookie.indexOf(";", pos); + sessionID = cookie.substring(pos + strlen("ESPSESSIONID="), pos2); + auth_ip* current_auth_info = GetAuth(_webserver->client().remoteIP(), sessionID.c_str()); + if (current_auth_info != NULL) { + sUser = current_auth_info->userID; + } + } + } + String buffer2send = "{\"status\":\"200\",\"authentication_lvl\":\""; + buffer2send += auths; + buffer2send += "\",\"user\":\""; + buffer2send += sUser; + buffer2send += "\"}"; + _webserver->send(code, "application/json", buffer2send); + } +# else + _webserver->sendHeader("Cache-Control", "no-cache"); + _webserver->send(200, "application/json", "{\"status\":\"Ok\",\"authentication_lvl\":\"admin\"}"); +# endif +} +//SPIFFS +//SPIFFS files list and file commands +void Web_Server::handleFileList() { + auth_t auth_level = is_authenticated(); + if (auth_level == LEVEL_GUEST) { + _upload_status = UPLOAD_STATUS_NONE; + _webserver->send(401, "text/plain", "Authentication failed!\n"); + return; + } + String path; + String status = "Ok"; + if (_upload_status == UPLOAD_STATUS_FAILED) { + status = "Upload failed"; + _upload_status = UPLOAD_STATUS_NONE; + } + _upload_status = UPLOAD_STATUS_NONE; + //be sure root is correct according authentication + if (auth_level == LEVEL_ADMIN) { + path = "/"; + } else { + path = "/user"; + } + //get current path + if (_webserver->hasArg("path")) { + path += _webserver->arg("path"); + } + //to have a clean path + path.trim(); + path.replace("//", "/"); + if (path[path.length() - 1] != '/') { + path += "/"; + } + //check if query need some action + if (_webserver->hasArg("action")) { + //delete a file + if (_webserver->arg("action") == "delete" && _webserver->hasArg("filename")) { + String filename; + String shortname = _webserver->arg("filename"); + shortname.replace("/", ""); + filename = path + _webserver->arg("filename"); + filename.replace("//", "/"); + if (!SPIFFS.exists(filename)) { + status = shortname + " does not exists!"; + } else { + if (SPIFFS.remove(filename)) { + status = shortname + " deleted"; + //what happen if no "/." and no other subfiles ? + String ptmp = path; + if ((path != "/") && (path[path.length() - 1] = '/')) { + ptmp = path.substring(0, path.length() - 1); + } + File dir = SPIFFS.open(ptmp); + File dircontent = dir.openNextFile(); + if (!dircontent) { + //keep directory alive even empty + File r = SPIFFS.open(path + "/.", FILE_WRITE); + if (r) { + r.close(); + } + } + } else { + status = "Cannot deleted "; + status += shortname; + } + } + } + //delete a directory + if (_webserver->arg("action") == "deletedir" && _webserver->hasArg("filename")) { + String filename; + String shortname = _webserver->arg("filename"); + shortname.replace("/", ""); + filename = path + _webserver->arg("filename"); + filename += "/"; + filename.replace("//", "/"); + if (filename != "/") { + bool delete_error = false; + File dir = SPIFFS.open(path + shortname); + { + File file2deleted = dir.openNextFile(); + while (file2deleted) { + String fullpath = file2deleted.name(); + if (!SPIFFS.remove(fullpath)) { + delete_error = true; + status = "Cannot deleted "; + status += fullpath; + } + file2deleted = dir.openNextFile(); + } + } + if (!delete_error) { + status = shortname; + status += " deleted"; + } + } + } + //create a directory + if (_webserver->arg("action") == "createdir" && _webserver->hasArg("filename")) { + String filename; + filename = path + _webserver->arg("filename") + "/."; + String shortname = _webserver->arg("filename"); + shortname.replace("/", ""); + filename.replace("//", "/"); + if (SPIFFS.exists(filename)) { + status = shortname + " already exists!"; + } else { + File r = SPIFFS.open(filename, FILE_WRITE); + if (!r) { + status = "Cannot create "; + status += shortname; + } else { + r.close(); + status = shortname + " created"; + } + } + } + } + String jsonfile = "{"; + String ptmp = path; + if ((path != "/") && (path[path.length() - 1] = '/')) { + ptmp = path.substring(0, path.length() - 1); + } + File dir = SPIFFS.open(ptmp); + jsonfile += "\"files\":["; + bool firstentry = true; + String subdirlist = ""; + File fileparsed = dir.openNextFile(); + while (fileparsed) { + String filename = fileparsed.name(); + String size = ""; + bool addtolist = true; + //remove path from name + filename = filename.substring(path.length(), filename.length()); + //check if file or subfile + if (filename.indexOf("/") > -1) { + //Do not rely on "/." to define directory as SPIFFS upload won't create it but directly files + //and no need to overload SPIFFS if not necessary to create "/." if no need + //it will reduce SPIFFS available space so limit it to creation + filename = filename.substring(0, filename.indexOf("/")); + String tag = "*"; + tag += filename + "*"; + if (subdirlist.indexOf(tag) > -1 || filename.length() == 0) { //already in list + addtolist = false; //no need to add + } else { + size = "-1"; //it is subfile so display only directory, size will be -1 to describe it is directory + if (subdirlist.length() == 0) { + subdirlist += "*"; + } + subdirlist += filename + "*"; //add to list + } + } else { + //do not add "." file + if (!((filename == ".") || (filename == ""))) { + size = ESPResponseStream::formatBytes(fileparsed.size()); + } else { + addtolist = false; + } + } + if (addtolist) { + if (!firstentry) { + jsonfile += ","; + } else { + firstentry = false; + } + jsonfile += "{"; + jsonfile += "\"name\":\""; + jsonfile += filename; + jsonfile += "\",\"size\":\""; + jsonfile += size; + jsonfile += "\""; + jsonfile += "}"; + } + fileparsed = dir.openNextFile(); + } + jsonfile += "],"; + jsonfile += "\"path\":\"" + path + "\","; + jsonfile += "\"status\":\"" + status + "\","; + size_t totalBytes; + size_t usedBytes; + totalBytes = SPIFFS.totalBytes(); + usedBytes = SPIFFS.usedBytes(); + jsonfile += "\"total\":\"" + ESPResponseStream::formatBytes(totalBytes) + "\","; + jsonfile += "\"used\":\"" + ESPResponseStream::formatBytes(usedBytes) + "\","; + jsonfile.concat(F("\"occupation\":\"")); + jsonfile += String(100 * usedBytes / totalBytes); + jsonfile += "\""; + jsonfile += "}"; + path = ""; + _webserver->sendHeader("Cache-Control", "no-cache"); + _webserver->send(200, "application/json", jsonfile); + _upload_status = UPLOAD_STATUS_NONE; +} + +//push error code and message to websocket +void Web_Server::pushError(int code, const char* st, bool web_error, uint16_t timeout) { + if (_socket_server && st) { + String s = "ERROR:" + String(code) + ":"; + s += st; + _socket_server->sendTXT(_id_connection, s); + if (web_error != 0) { + if (_webserver) { + if (_webserver->client().available() > 0) { + _webserver->send(web_error, "text/xml", st); + } + } + } + uint32_t t = millis(); + while (millis() - t < timeout) { + _socket_server->loop(); + delay(10); + } + } +} + +//abort reception of packages +void Web_Server::cancelUpload() { + if (_webserver) { + if (_webserver->client().available() > 0) { + HTTPUpload& upload = _webserver->upload(); + upload.status = UPLOAD_FILE_ABORTED; + errno = ECONNABORTED; + _webserver->client().stop(); + delay(100); + } + } +} + +//SPIFFS files uploader handle +void Web_Server::SPIFFSFileupload() { + static String filename; + static File fsUploadFile = (File)0; + //get authentication status + auth_t auth_level = is_authenticated(); + //Guest cannot upload - only admin + if (auth_level == LEVEL_GUEST) { + _upload_status = UPLOAD_STATUS_FAILED; + grbl_send(CLIENT_ALL, "[MSG:Upload rejected]\r\n"); + pushError(ESP_ERROR_AUTHENTICATION, "Upload rejected", 401); + } else { + HTTPUpload& upload = _webserver->upload(); + if ((_upload_status != UPLOAD_STATUS_FAILED) || (upload.status == UPLOAD_FILE_START)) { + //Upload start + //************** + if (upload.status == UPLOAD_FILE_START) { + _upload_status = UPLOAD_STATUS_ONGOING; + String upload_filename = upload.filename; + if (upload_filename[0] != '/') + filename = "/" + upload_filename; + else + filename = upload.filename; + //according User or Admin the root is different as user is isolate to /user when admin has full access + if (auth_level != LEVEL_ADMIN) { + upload_filename = filename; + filename = "/user" + upload_filename; + } + + if (SPIFFS.exists(filename)) { + SPIFFS.remove(filename); + } + if (fsUploadFile) { + fsUploadFile.close(); + } + String sizeargname = upload.filename + "S"; + if (_webserver->hasArg(sizeargname.c_str())) { + uint32_t filesize = _webserver->arg(sizeargname.c_str()).toInt(); + uint32_t freespace = SPIFFS.totalBytes() - SPIFFS.usedBytes(); + if (filesize > freespace) { + _upload_status = UPLOAD_STATUS_FAILED; + grbl_send(CLIENT_ALL, "[MSG:Upload error]\r\n"); + pushError(ESP_ERROR_NOT_ENOUGH_SPACE, "Upload rejected, not enough space"); + } + } + if (_upload_status != UPLOAD_STATUS_FAILED) { + //create file + fsUploadFile = SPIFFS.open(filename, FILE_WRITE); + //check If creation succeed + if (fsUploadFile) { + //if yes upload is started + _upload_status = UPLOAD_STATUS_ONGOING; + } else { + //if no set cancel flag + _upload_status = UPLOAD_STATUS_FAILED; + grbl_send(CLIENT_ALL, "[MSG:Upload error]\r\n"); + pushError(ESP_ERROR_FILE_CREATION, "File creation failed"); + } + } + //Upload write + //************** + } else if (upload.status == UPLOAD_FILE_WRITE) { + vTaskDelay(1 / portTICK_RATE_MS); + //check if file is available and no error + if (fsUploadFile && _upload_status == UPLOAD_STATUS_ONGOING) { + //no error so write post date + if (upload.currentSize != fsUploadFile.write(upload.buf, upload.currentSize)) { + _upload_status = UPLOAD_STATUS_FAILED; + grbl_send(CLIENT_ALL, "[MSG:Upload error]\r\n"); + pushError(ESP_ERROR_FILE_WRITE, "File write failed"); + } + } else { + //we have a problem set flag UPLOAD_STATUS_FAILED + _upload_status = UPLOAD_STATUS_FAILED; + grbl_send(CLIENT_ALL, "[MSG:Upload error]\r\n"); + pushError(ESP_ERROR_FILE_WRITE, "File write failed"); + } + //Upload end + //************** + } else if (upload.status == UPLOAD_FILE_END) { + //check if file is still open + if (fsUploadFile) { + //close it + fsUploadFile.close(); + //check size + String sizeargname = upload.filename + "S"; + fsUploadFile = SPIFFS.open(filename, FILE_READ); + uint32_t filesize = fsUploadFile.size(); + fsUploadFile.close(); + if (_webserver->hasArg(sizeargname.c_str())) { + if (_webserver->arg(sizeargname.c_str()) != String(filesize)) { + _upload_status = UPLOAD_STATUS_FAILED; + } + } + if (_upload_status == UPLOAD_STATUS_ONGOING) { + _upload_status = UPLOAD_STATUS_SUCCESSFUL; + } else { + grbl_send(CLIENT_ALL, "[MSG:Upload error]\r\n"); + pushError(ESP_ERROR_UPLOAD, "File upload failed"); + } + } else { + //we have a problem set flag UPLOAD_STATUS_FAILED + _upload_status = UPLOAD_STATUS_FAILED; + pushError(ESP_ERROR_FILE_CLOSE, "File close failed"); + grbl_send(CLIENT_ALL, "[MSG:Upload error]\r\n"); + } + //Upload cancelled + //************** + } else { + _upload_status = UPLOAD_STATUS_FAILED; + //pushError(ESP_ERROR_UPLOAD, "File upload failed"); + return; + } + } + } + + if (_upload_status == UPLOAD_STATUS_FAILED) { + cancelUpload(); + if (SPIFFS.exists(filename)) { + SPIFFS.remove(filename); + } + } + COMMANDS::wait(0); +} + +//Web Update handler +void Web_Server::handleUpdate() { + auth_t auth_level = is_authenticated(); + if (auth_level != LEVEL_ADMIN) { + _upload_status = UPLOAD_STATUS_NONE; + _webserver->send(403, "text/plain", "Not allowed, log in first!\n"); + return; + } + String jsonfile = "{\"status\":\""; + jsonfile += String(_upload_status); + jsonfile += "\"}"; + //send status + _webserver->sendHeader("Cache-Control", "no-cache"); + _webserver->send(200, "application/json", jsonfile); + //if success restart + if (_upload_status == UPLOAD_STATUS_SUCCESSFUL) { + COMMANDS::wait(1000); + COMMANDS::restart_ESP(); + } else { + _upload_status = UPLOAD_STATUS_NONE; + } +} + +//File upload for Web update +void Web_Server::WebUpdateUpload() { + static size_t last_upload_update; + static uint32_t maxSketchSpace = 0; + //only admin can update FW + if (is_authenticated() != LEVEL_ADMIN) { + _upload_status = UPLOAD_STATUS_FAILED; + grbl_send(CLIENT_ALL, "[MSG:Upload rejected]\r\n"); + pushError(ESP_ERROR_AUTHENTICATION, "Upload rejected", 401); + } else { + //get current file ID + HTTPUpload& upload = _webserver->upload(); + if ((_upload_status != UPLOAD_STATUS_FAILED) || (upload.status == UPLOAD_FILE_START)) { + //Upload start + //************** + if (upload.status == UPLOAD_FILE_START) { + grbl_send(CLIENT_ALL, "[MSG:Update Firmware]\r\n"); + _upload_status = UPLOAD_STATUS_ONGOING; + String sizeargname = upload.filename + "S"; + if (_webserver->hasArg(sizeargname.c_str())) { + maxSketchSpace = _webserver->arg(sizeargname).toInt(); + } + //check space + size_t flashsize = 0; + if (esp_ota_get_running_partition()) { + const esp_partition_t* partition = esp_ota_get_next_update_partition(NULL); + if (partition) { + flashsize = partition->size; + } + } + if (flashsize < maxSketchSpace) { + pushError(ESP_ERROR_NOT_ENOUGH_SPACE, "Upload rejected, not enough space"); + _upload_status = UPLOAD_STATUS_FAILED; + grbl_send(CLIENT_ALL, "[MSG:Update cancelled]\r\n"); + } + if (_upload_status != UPLOAD_STATUS_FAILED) { + last_upload_update = 0; + if (!Update.begin()) { //start with max available size + _upload_status = UPLOAD_STATUS_FAILED; + grbl_send(CLIENT_ALL, "[MSG:Update cancelled]\r\n"); + pushError(ESP_ERROR_NOT_ENOUGH_SPACE, "Upload rejected, not enough space"); + } else { + grbl_send(CLIENT_ALL, "\n[MSG:Update 0%]\r\n"); + } + } + //Upload write + //************** + } else if (upload.status == UPLOAD_FILE_WRITE) { + vTaskDelay(1 / portTICK_RATE_MS); + //check if no error + if (_upload_status == UPLOAD_STATUS_ONGOING) { + if (((100 * upload.totalSize) / maxSketchSpace) != last_upload_update) { + if (maxSketchSpace > 0) + last_upload_update = (100 * upload.totalSize) / maxSketchSpace; + else + last_upload_update = upload.totalSize; + String s = "Update "; + s += String(last_upload_update); + s += "%"; + grbl_sendf(CLIENT_ALL, "[MSG:%s]\r\n", s.c_str()); + } + if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) { + _upload_status = UPLOAD_STATUS_FAILED; + grbl_send(CLIENT_ALL, "[MSG:Update write failed]\r\n"); + pushError(ESP_ERROR_FILE_WRITE, "File write failed"); + } + } + //Upload end + //************** + } else if (upload.status == UPLOAD_FILE_END) { + if (Update.end(true)) { //true to set the size to the current progress + //Now Reboot + grbl_send(CLIENT_ALL, "[MSG:Update 100%]\r\n"); + _upload_status = UPLOAD_STATUS_SUCCESSFUL; + } else { + _upload_status = UPLOAD_STATUS_FAILED; + grbl_send(CLIENT_ALL, "[MSG:Update failed]\r\n"); + pushError(ESP_ERROR_UPLOAD, "Update upload failed"); + } + } else if (upload.status == UPLOAD_FILE_ABORTED) { + grbl_send(CLIENT_ALL, "[MSG:Update failed]\r\n"); + _upload_status = UPLOAD_STATUS_FAILED; + return; + } + } + } + if (_upload_status == UPLOAD_STATUS_FAILED) { + cancelUpload(); + Update.end(); + } + COMMANDS::wait(0); +} + +# ifdef ENABLE_SD_CARD + +//Function to delete not empty directory on SD card +bool Web_Server::deleteRecursive(String path) { + bool result = true; + File file = SD.open((char*)path.c_str()); + //failed + if (!file) { + return false; + } + if (!file.isDirectory()) { + file.close(); + //return if success or not + return SD.remove((char*)path.c_str()); + } + file.rewindDirectory(); + while (true) { + File entry = file.openNextFile(); + if (!entry) { + break; + } + String entryPath = entry.name(); + if (entry.isDirectory()) { + entry.close(); + if (!deleteRecursive(entryPath)) { + result = false; + } + } else { + entry.close(); + if (!SD.remove((char*)entryPath.c_str())) { + result = false; + break; + } + } + COMMANDS::wait(0); //wdtFeed + } + file.close(); + if (result) + return SD.rmdir((char*)path.c_str()); + else + return false; +} + +//direct SD files list////////////////////////////////////////////////// +void Web_Server::handle_direct_SDFileList() { + //this is only for admin and user + if (is_authenticated() == LEVEL_GUEST) { + _upload_status = UPLOAD_STATUS_NONE; + _webserver->send(401, "application/json", "{\"status\":\"Authentication failed!\"}"); + return; + } + + String path = "/"; + String sstatus = "Ok"; + if ((_upload_status == UPLOAD_STATUS_FAILED) || (_upload_status == UPLOAD_STATUS_FAILED)) { + sstatus = "Upload failed"; + _upload_status = UPLOAD_STATUS_NONE; + } + bool list_files = true; + uint64_t totalspace = 0; + uint64_t usedspace = 0; + if (get_sd_state(true) != SDCARD_IDLE) { + _webserver->sendHeader("Cache-Control", "no-cache"); + _webserver->send(200, "application/json", "{\"status\":\"No SD Card\"}"); + return; + } + set_sd_state(SDCARD_BUSY_PARSING); + //get current path + if (_webserver->hasArg("path")) { + path += _webserver->arg("path"); + } + //to have a clean path + path.trim(); + path.replace("//", "/"); + if (path[path.length() - 1] != '/') { + path += "/"; + } + //check if query need some action + if (_webserver->hasArg("action")) { + //delete a file + if (_webserver->arg("action") == "delete" && _webserver->hasArg("filename")) { + String filename; + String shortname = _webserver->arg("filename"); + filename = path + shortname; + shortname.replace("/", ""); + filename.replace("//", "/"); + if (!SD.exists((char*)filename.c_str())) { + sstatus = shortname + " does not exist!"; + } else { + if (SD.remove((char*)filename.c_str())) { + sstatus = shortname + " deleted"; + } else { + sstatus = "Cannot deleted "; + sstatus += shortname; + } + } + } + //delete a directory + if (_webserver->arg("action") == "deletedir" && _webserver->hasArg("filename")) { + String filename; + String shortname = _webserver->arg("filename"); + shortname.replace("/", ""); + filename = path + "/" + shortname; + filename.replace("//", "/"); + if (filename != "/") { + if (!SD.exists((char*)filename.c_str())) { + sstatus = shortname + " does not exist!"; + } else { + if (!deleteRecursive(filename)) { + sstatus = "Error deleting: "; + sstatus += shortname; + } else { + sstatus = shortname; + sstatus += " deleted"; + } + } + } else { + sstatus = "Cannot delete root"; + } + } + //create a directory + if (_webserver->arg("action") == "createdir" && _webserver->hasArg("filename")) { + String filename; + String shortname = _webserver->arg("filename"); + filename = path + shortname; + shortname.replace("/", ""); + filename.replace("//", "/"); + if (SD.exists((char*)filename.c_str())) { + sstatus = shortname + " already exists!"; + } else { + if (!SD.mkdir((char*)filename.c_str())) { + sstatus = "Cannot create "; + sstatus += shortname; + } else { + sstatus = shortname + " created"; + } + } + } + } + //check if no need build file list + if (_webserver->hasArg("dontlist")) { + if (_webserver->arg("dontlist") == "yes") { + list_files = false; + } + } + // TODO Settings - consider using the JSONEncoder class + String jsonfile = "{"; + jsonfile += "\"files\":["; + + if (path != "/") + path = path.substring(0, path.length() - 1); + if (path != "/" && !SD.exists((char*)path.c_str())) { + String s = "{\"status\":\" "; + s += path; + s += " does not exist on SD Card\"}"; + _webserver->send(200, "application/json", s.c_str()); + return; + } + if (list_files) { + File dir = SD.open((char*)path.c_str()); + if (!dir) {} + if (!dir.isDirectory()) { + dir.close(); + } + dir.rewindDirectory(); + File entry = dir.openNextFile(); + int i = 0; + while (entry) { + COMMANDS::wait(1); + if (i > 0) { + jsonfile += ","; + } + jsonfile += "{\"name\":\""; + String tmpname = entry.name(); + int pos = tmpname.lastIndexOf("/"); + tmpname = tmpname.substring(pos + 1); + jsonfile += tmpname; + jsonfile += "\",\"shortname\":\""; //No need here + jsonfile += tmpname; + jsonfile += "\",\"size\":\""; + if (entry.isDirectory()) { + jsonfile += "-1"; + } else { + // files have sizes, directories do not + jsonfile += ESPResponseStream::formatBytes(entry.size()); + } + jsonfile += "\",\"datetime\":\""; + //TODO - can be done later + jsonfile += "\"}"; + i++; + entry.close(); + entry = dir.openNextFile(); + } + dir.close(); + } + jsonfile += "],\"path\":\""; + jsonfile += path + "\","; + jsonfile += "\"total\":\""; + String stotalspace, susedspace; + //SDCard are in GB or MB but no less + totalspace = SD.totalBytes(); + usedspace = SD.usedBytes(); + stotalspace = ESPResponseStream::formatBytes(totalspace); + susedspace = ESPResponseStream::formatBytes(usedspace + 1); + + uint32_t occupedspace = 1; + uint32_t usedspace2 = usedspace / (1024 * 1024); + uint32_t totalspace2 = totalspace / (1024 * 1024); + occupedspace = (usedspace2 * 100) / totalspace2; + //minimum if even one byte is used is 1% + if (occupedspace <= 1) { + occupedspace = 1; + } + if (totalspace) { + jsonfile += stotalspace; + } else { + jsonfile += "-1"; + } + jsonfile += "\",\"used\":\""; + jsonfile += susedspace; + jsonfile += "\",\"occupation\":\""; + if (totalspace) { + jsonfile += String(occupedspace); + } else { + jsonfile += "-1"; + } + jsonfile += "\","; + jsonfile += "\"mode\":\"direct\","; + jsonfile += "\"status\":\""; + jsonfile += sstatus + "\""; + jsonfile += "}"; + _webserver->sendHeader("Cache-Control", "no-cache"); + _webserver->send(200, "application/json", jsonfile.c_str()); + _upload_status = UPLOAD_STATUS_NONE; + set_sd_state(SDCARD_IDLE); +} + +//SD File upload with direct access to SD/////////////////////////////// +void Web_Server::SDFile_direct_upload() { + static String filename; + static File sdUploadFile; + //this is only for admin and user + if (is_authenticated() == LEVEL_GUEST) { + _upload_status = UPLOAD_STATUS_FAILED; + _webserver->send(401, "application/json", "{\"status\":\"Authentication failed!\"}"); + pushError(ESP_ERROR_AUTHENTICATION, "Upload rejected", 401); + } else { + //retrieve current file id + HTTPUpload& upload = _webserver->upload(); + if ((_upload_status != UPLOAD_STATUS_FAILED) || (upload.status == UPLOAD_FILE_START)) { + //Upload start + //************** + if (upload.status == UPLOAD_FILE_START) { + _upload_status = UPLOAD_STATUS_ONGOING; + filename = upload.filename; + //on SD need to add / if not present + if (filename[0] != '/') { + filename = "/" + upload.filename; + } + //check if SD Card is available + if (get_sd_state(true) != SDCARD_IDLE) { + _upload_status = UPLOAD_STATUS_FAILED; + grbl_send(CLIENT_ALL, "[MSG:Upload cancelled]\r\n"); + pushError(ESP_ERROR_UPLOAD_CANCELLED, "Upload cancelled"); + + } else { + set_sd_state(SDCARD_BUSY_UPLOADING); + //delete file on SD Card if already present + if (SD.exists((char*)filename.c_str())) { + SD.remove((char*)filename.c_str()); + } + String sizeargname = upload.filename + "S"; + if (_webserver->hasArg(sizeargname.c_str())) { + uint32_t filesize = _webserver->arg(sizeargname.c_str()).toInt(); + uint64_t freespace = SD.totalBytes() - SD.usedBytes(); + if (filesize > freespace) { + _upload_status = UPLOAD_STATUS_FAILED; + grbl_send(CLIENT_ALL, "[MSG:Upload error]\r\n"); + pushError(ESP_ERROR_NOT_ENOUGH_SPACE, "Upload rejected, not enough space"); + } + } + if (_upload_status != UPLOAD_STATUS_FAILED) { + //Create file for writing + sdUploadFile = SD.open((char*)filename.c_str(), FILE_WRITE); + //check if creation succeed + if (!sdUploadFile) { + //if creation failed + _upload_status = UPLOAD_STATUS_FAILED; + grbl_send(CLIENT_ALL, "[MSG:Upload failed]\r\n"); + pushError(ESP_ERROR_FILE_CREATION, "File creation failed"); + } + //if creation succeed set flag UPLOAD_STATUS_ONGOING + else { + _upload_status = UPLOAD_STATUS_ONGOING; + } + } + } + //Upload write + //************** + } else if (upload.status == UPLOAD_FILE_WRITE) { + vTaskDelay(1 / portTICK_RATE_MS); + if (sdUploadFile && (_upload_status == UPLOAD_STATUS_ONGOING) && (get_sd_state(false) == SDCARD_BUSY_UPLOADING)) { + //no error write post data + if (upload.currentSize != sdUploadFile.write(upload.buf, upload.currentSize)) { + _upload_status = UPLOAD_STATUS_FAILED; + grbl_send(CLIENT_ALL, "[MSG:Upload failed]\r\n"); + pushError(ESP_ERROR_FILE_WRITE, "File write failed"); + } + } else { //if error set flag UPLOAD_STATUS_FAILED + _upload_status = UPLOAD_STATUS_FAILED; + grbl_send(CLIENT_ALL, "[MSG:Upload failed]\r\n"); + pushError(ESP_ERROR_FILE_WRITE, "File write failed"); + } + //Upload end + //************** + } else if (upload.status == UPLOAD_FILE_END) { + //if file is open close it + if (sdUploadFile) { + sdUploadFile.close(); + //TODO Check size + String sizeargname = upload.filename + "S"; + if (_webserver->hasArg(sizeargname.c_str())) { + uint32_t filesize = 0; + sdUploadFile = SD.open(filename.c_str(), FILE_READ); + filesize = sdUploadFile.size(); + sdUploadFile.close(); + if (_webserver->arg(sizeargname.c_str()) != String(filesize)) { + _upload_status = UPLOAD_STATUS_FAILED; + pushError(ESP_ERROR_UPLOAD, "File upload mismatch"); + grbl_send(CLIENT_ALL, "[MSG:Upload failed]\r\n"); + } + } + } else { + _upload_status = UPLOAD_STATUS_FAILED; + grbl_send(CLIENT_ALL, "[MSG:Upload failed]\r\n"); + pushError(ESP_ERROR_FILE_CLOSE, "File close failed"); + } + if (_upload_status == UPLOAD_STATUS_ONGOING) { + _upload_status = UPLOAD_STATUS_SUCCESSFUL; + set_sd_state(SDCARD_IDLE); + } else { + _upload_status = UPLOAD_STATUS_FAILED; + pushError(ESP_ERROR_UPLOAD, "Upload error"); + } + + } else { //Upload cancelled + _upload_status = UPLOAD_STATUS_FAILED; + set_sd_state(SDCARD_IDLE); + grbl_send(CLIENT_ALL, "[MSG:Upload failed]\r\n"); + if (sdUploadFile) { + sdUploadFile.close(); + } + return; + } + } + } + if (_upload_status == UPLOAD_STATUS_FAILED) { + cancelUpload(); + if (sdUploadFile) { + sdUploadFile.close(); + } + if (SD.exists((char*)filename.c_str())) { + SD.remove((char*)filename.c_str()); + } + set_sd_state(SDCARD_IDLE); + } + COMMANDS::wait(0); +} +# endif + +void Web_Server::handle() { + static uint32_t timeout = millis(); + COMMANDS::wait(0); +# ifdef ENABLE_CAPTIVE_PORTAL + if (WiFi.getMode() == WIFI_AP) { + dnsServer.processNextRequest(); + } +# endif + if (_webserver) + _webserver->handleClient(); + if (_socket_server && _setupdone) + _socket_server->loop(); + if ((millis() - timeout) > 10000) { + if (_socket_server) { + String s = "PING:"; + s += String(_id_connection); + _socket_server->broadcastTXT(s); + timeout = millis(); + } + } +} + +void Web_Server::handle_Websocket_Event(uint8_t num, uint8_t type, uint8_t* payload, size_t length) { + switch (type) { + case WStype_DISCONNECTED: + //USE_SERIAL.printf("[%u] Disconnected!\n", num); + break; + case WStype_CONNECTED: { + IPAddress ip = _socket_server->remoteIP(num); + //USE_SERIAL.printf("[%u] Connected from %d.%d.%d.%d url: %s\n", num, ip[0], ip[1], ip[2], ip[3], payload); + String s = "CURRENT_ID:" + String(num); + // send message to client + _id_connection = num; + _socket_server->sendTXT(_id_connection, s); + s = "ACTIVE_ID:" + String(_id_connection); + _socket_server->broadcastTXT(s); + } break; + case WStype_TEXT: + //USE_SERIAL.printf("[%u] get Text: %s\n", num, payload); + + // send message to client + // webSocket.sendTXT(num, "message here"); + + // send data to all connected clients + // webSocket.broadcastTXT("message here"); + break; + case WStype_BIN: + //USE_SERIAL.printf("[%u] get binary length: %u\n", num, length); + //hexdump(payload, length); + + // send message to client + // webSocket.sendBIN(num, payload, length); + break; + default: break; + } +} + +// The separator that is passed in to this function is always '\n' +// The string that is returned does not contain the separator +// The calling code adds back the separator, unless the string is +// a one-character realtime command. +String Web_Server::get_Splited_Value(String data, char separator, int index) { + int found = 0; + int strIndex[] = { 0, -1 }; + int maxIndex = data.length() - 1; + + for (int i = 0; i <= maxIndex && found <= index; i++) { + if (data.charAt(i) == separator || i == maxIndex) { + found++; + strIndex[0] = strIndex[1] + 1; + strIndex[1] = (i == maxIndex) ? i + 1 : i; + } + } + + return found > index ? data.substring(strIndex[0], strIndex[1]) : ""; +} + +//helper to extract content type from file extension +//Check what is the content tye according extension file +String Web_Server::getContentType(String filename) { + String file_name = filename; + file_name.toLowerCase(); + if (filename.endsWith(".htm")) { + return "text/html"; + } else if (file_name.endsWith(".html")) { + return "text/html"; + } else if (file_name.endsWith(".css")) { + return "text/css"; + } else if (file_name.endsWith(".js")) { + return "application/javascript"; + } else if (file_name.endsWith(".png")) { + return "image/png"; + } else if (file_name.endsWith(".gif")) { + return "image/gif"; + } else if (file_name.endsWith(".jpeg")) { + return "image/jpeg"; + } else if (file_name.endsWith(".jpg")) { + return "image/jpeg"; + } else if (file_name.endsWith(".ico")) { + return "image/x-icon"; + } else if (file_name.endsWith(".xml")) { + return "text/xml"; + } else if (file_name.endsWith(".pdf")) { + return "application/x-pdf"; + } else if (file_name.endsWith(".zip")) { + return "application/x-zip"; + } else if (file_name.endsWith(".gz")) { + return "application/x-gzip"; + } else if (file_name.endsWith(".txt")) { + return "text/plain"; + } + return "application/octet-stream"; +} + +//check authentification +auth_t Web_Server::is_authenticated() { +# ifdef ENABLE_AUTHENTICATION + if (_webserver->hasHeader("Cookie")) { + String cookie = _webserver->header("Cookie"); + int pos = cookie.indexOf("ESPSESSIONID="); + if (pos != -1) { + int pos2 = cookie.indexOf(";", pos); + String sessionID = cookie.substring(pos + strlen("ESPSESSIONID="), pos2); + IPAddress ip = _webserver->client().remoteIP(); + //check if cookie can be reset and clean table in same time + return ResetAuthIP(ip, sessionID.c_str()); + } + } + return LEVEL_GUEST; +# else + return LEVEL_ADMIN; +# endif +} + +# ifdef ENABLE_AUTHENTICATION + +//add the information in the linked list if possible +bool Web_Server::AddAuthIP(auth_ip* item) { + if (_nb_ip > MAX_AUTH_IP) { + return false; + } + item->_next = _head; + _head = item; + _nb_ip++; + return true; +} + +//Session ID based on IP and time using 16 char +char* Web_Server::create_session_ID() { + static char sessionID[17]; + //reset SESSIONID + for (int i = 0; i < 17; i++) { + sessionID[i] = '\0'; + } + //get time + uint32_t now = millis(); + //get remote IP + IPAddress remoteIP = _webserver->client().remoteIP(); + //generate SESSIONID + if (0 > sprintf(sessionID, + "%02X%02X%02X%02X%02X%02X%02X%02X", + remoteIP[0], + remoteIP[1], + remoteIP[2], + remoteIP[3], + (uint8_t)((now >> 0) & 0xff), + (uint8_t)((now >> 8) & 0xff), + (uint8_t)((now >> 16) & 0xff), + (uint8_t)((now >> 24) & 0xff))) { + strcpy(sessionID, "NONE"); + } + return sessionID; +} + +bool Web_Server::ClearAuthIP(IPAddress ip, const char* sessionID) { + auth_ip* current = _head; + auth_ip* previous = NULL; + bool done = false; + while (current) { + if ((ip == current->ip) && (strcmp(sessionID, current->sessionID) == 0)) { + //remove + done = true; + if (current == _head) { + _head = current->_next; + _nb_ip--; + delete current; + current = _head; + } else { + previous->_next = current->_next; + _nb_ip--; + delete current; + current = previous->_next; + } + } else { + previous = current; + current = current->_next; + } + } + return done; +} + +//Get info +auth_ip* Web_Server::GetAuth(IPAddress ip, const char* sessionID) { + auth_ip* current = _head; + //auth_ip * previous = NULL; + //get time + //uint32_t now = millis(); + while (current) { + if (ip == current->ip) { + if (strcmp(sessionID, current->sessionID) == 0) { + //found + return current; + } + } + //previous = current; + current = current->_next; + } + return NULL; +} + +//Review all IP to reset timers +auth_t Web_Server::ResetAuthIP(IPAddress ip, const char* sessionID) { + auth_ip* current = _head; + auth_ip* previous = NULL; + //get time + //uint32_t now = millis(); + while (current) { + if ((millis() - current->last_time) > 360000) { + //remove + if (current == _head) { + _head = current->_next; + _nb_ip--; + delete current; + current = _head; + } else { + previous->_next = current->_next; + _nb_ip--; + delete current; + current = previous->_next; + } + } else { + if (ip == current->ip) { + if (strcmp(sessionID, current->sessionID) == 0) { + //reset time + current->last_time = millis(); + return (auth_t)current->level; + } + } + previous = current; + current = current->_next; + } + } + return LEVEL_GUEST; +} +# endif + +#endif // Enable HTTP && ENABLE_WIFI diff --git a/Grbl_Esp32/web_server.h b/Grbl_Esp32/src/WebUI/WebServer.h similarity index 62% rename from Grbl_Esp32/web_server.h rename to Grbl_Esp32/src/WebUI/WebServer.h index 9028898f..4ccf1a3e 100644 --- a/Grbl_Esp32/web_server.h +++ b/Grbl_Esp32/src/WebUI/WebServer.h @@ -1,5 +1,7 @@ +#pragma once + /* - web_server.h - wifi services functions class + WebServer.h - wifi services functions class Copyright (c) 2014 Luc Lebosse. All rights reserved. @@ -18,54 +20,50 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ - -#ifndef _WEB_SERVER_H -#define _WEB_SERVER_H - - -#include "config.h" -#include "commands.h" +#include "../Config.h" +#include "Commands.h" class WebSocketsServer; class WebServer; #ifdef ENABLE_AUTHENTICATION struct auth_ip { IPAddress ip; - auth_t level; - char userID[17]; - char sessionID[17]; - uint32_t last_time; - auth_ip* _next; + auth_t level; + char userID[17]; + char sessionID[17]; + uint32_t last_time; + auth_ip* _next; }; #endif class Web_Server { - public: +public: Web_Server(); ~Web_Server(); - bool begin(); - void end(); - void handle(); - static long get_client_ID(); - static uint16_t port() {return _port;} - private: - static bool _setupdone; - static WebServer* _webserver; - static long _id_connection; + bool begin(); + void end(); + void handle(); + static long get_client_ID(); + static uint16_t port() { return _port; } + +private: + static bool _setupdone; + static WebServer* _webserver; + static long _id_connection; static WebSocketsServer* _socket_server; - static uint16_t _port; - static uint8_t _upload_status; - static String getContentType(String filename); - static String get_Splited_Value(String data, char separator, int index); - static auth_t is_authenticated(); + static uint16_t _port; + static uint8_t _upload_status; + static String getContentType(String filename); + static String get_Splited_Value(String data, char separator, int index); + static auth_t is_authenticated(); #ifdef ENABLE_AUTHENTICATION static auth_ip* _head; - static uint8_t _nb_ip; - static bool AddAuthIP(auth_ip* item); - static char* create_session_ID(); - static bool ClearAuthIP(IPAddress ip, const char* sessionID); + static uint8_t _nb_ip; + static bool AddAuthIP(auth_ip* item); + static char* create_session_ID(); + static bool ClearAuthIP(IPAddress ip, const char* sessionID); static auth_ip* GetAuth(IPAddress ip, const char* sessionID); - static auth_t ResetAuthIP(IPAddress ip, const char* sessionID); + static auth_t ResetAuthIP(IPAddress ip, const char* sessionID); #endif #ifdef ENABLE_SSDP static void handle_SSDP(); @@ -82,7 +80,7 @@ class Web_Server { static void handleUpdate(); static void WebUpdateUpload(); static bool is_realtime_cmd(char c); - static void pushError(int code, const char* st, bool web_error = 500, uint16_t timeout = 1000); + static void pushError(int code, const char* st, bool web_error = 500, uint16_t timeout = 1000); static void cancelUpload(); #ifdef ENABLE_SD_CARD static void handle_direct_SDFileList(); @@ -92,6 +90,3 @@ class Web_Server { }; extern Web_Server web_server; - -#endif - diff --git a/Grbl_Esp32/WebSettings.cpp b/Grbl_Esp32/src/WebUI/WebSettings.cpp similarity index 50% rename from Grbl_Esp32/WebSettings.cpp rename to Grbl_Esp32/src/WebUI/WebSettings.cpp index 74e79647..afbc6f09 100644 --- a/Grbl_Esp32/WebSettings.cpp +++ b/Grbl_Esp32/src/WebUI/WebSettings.cpp @@ -20,7 +20,7 @@ along with Grbl. If not, see . */ -#include "grbl.h" +#include "../Grbl.h" #include #include @@ -28,9 +28,9 @@ #include #include -#include "espresponse.h" -#include "web_server.h" -#include "string.h" +#include "ESPResponse.h" +#include "WebServer.h" +#include #ifdef ENABLE_WIFI StringSetting* wifi_sta_ssid; @@ -48,35 +48,35 @@ IPaddrSetting* wifi_ap_ip; IntSetting* wifi_ap_channel; -StringSetting* wifi_hostname; -EnumSetting* http_enable; -IntSetting* http_port; -EnumSetting* telnet_enable; -IntSetting* telnet_port; -typedef std::map enum_opt_t; -enum_opt_t staModeOptions = { - { "DHCP", DHCP_MODE , }, - { "Static", STATIC_MODE , }, +StringSetting* wifi_hostname; +EnumSetting* http_enable; +IntSetting* http_port; +EnumSetting* telnet_enable; +IntSetting* telnet_port; +typedef std::map enum_opt_t; +enum_opt_t staModeOptions = { + { "DHCP", DHCP_MODE }, + { "Static", STATIC_MODE }, }; #endif #ifdef WIFI_OR_BLUETOOTH EnumSetting* wifi_radio_mode; -enum_opt_t radioOptions = { - { "None", ESP_RADIO_OFF, }, - { "STA", ESP_WIFI_STA, }, - { "AP", ESP_WIFI_AP, }, - { "BT", ESP_BT, }, +enum_opt_t radioOptions = { + { "None", ESP_RADIO_OFF }, + { "STA", ESP_WIFI_STA }, + { "AP", ESP_WIFI_AP }, + { "BT", ESP_BT }, }; enum_opt_t radioEnabledOptions = { - { "NONE", ESP_RADIO_OFF, }, -#ifdef ENABLE_WIFI - { "STA", ESP_WIFI_STA, }, - { "AP", ESP_WIFI_AP, }, -#endif -#ifdef ENABLE_BLUETOOTH - { "BT", ESP_BT, }, -#endif + { "NONE", ESP_RADIO_OFF }, +# ifdef ENABLE_WIFI + { "STA", ESP_WIFI_STA }, + { "AP", ESP_WIFI_AP }, +# endif +# ifdef ENABLE_BLUETOOTH + { "BT", ESP_BT }, +# endif }; #endif @@ -84,24 +84,20 @@ enum_opt_t radioEnabledOptions = { StringSetting* bt_name; #endif - #ifdef ENABLE_NOTIFICATIONS enum_opt_t notificationOptions = { - { "NONE", 0, }, - { "LINE", 3, }, - { "PUSHOVER", 1, }, - { "EMAIL", 2, }, + { "NONE", 0 }, + { "LINE", 3 }, + { "PUSHOVER", 1 }, + { "EMAIL", 2 }, }; -EnumSetting* notification_type; +EnumSetting* notification_type; StringSetting* notification_t1; StringSetting* notification_t2; StringSetting* notification_ts; #endif -enum_opt_t onoffOptions = { - { "OFF", 0, }, - { "ON", 1, } -}; +enum_opt_t onoffOptions = { { "OFF", 0 }, { "ON", 1 } }; static ESPResponseStream* espresponse; @@ -111,26 +107,26 @@ typedef struct { } keyval_t; static keyval_t params[10]; -bool split_params(char *parameter) { +bool split_params(char* parameter) { int i = 0; for (char* s = parameter; *s; s++) { if (*s == '=') { - params[i].value = s+1; - *s = '\0'; + params[i].value = s + 1; + *s = '\0'; // Search backward looking for the start of the key, // either just after a space or at the beginning of the strin if (s == parameter) { return false; } - for (char* k = s-1; k >= parameter; --k) { + for (char* k = s - 1; k >= parameter; --k) { if (*k == '\0') { // If we find a NUL - i.e. the end of the previous key - // before finding a space, the string is malformed. return false; } if (*k == ' ') { - *k = '\0'; - params[i++].key = k+1; + *k = '\0'; + params[i++].key = k + 1; break; } if (k == parameter) { @@ -143,8 +139,8 @@ bool split_params(char *parameter) { return true; } -char nullstr[1] = { '\0' }; -char* get_param(const char *key, bool allowSpaces) { +char nullstr[1] = { '\0' }; +char* get_param(const char* key, bool allowSpaces) { for (keyval_t* p = params; p->key; p++) { if (!strcasecmp(key, p->key)) { if (!allowSpaces) { @@ -173,8 +169,7 @@ err_t WebCommand::action(char* value, auth_t auth_level, ESPResponseStream* out) static int webColumn = 0; // We create a variety of print functions to make the rest // of the code more compact and readable. -static void webPrint(const char *s) -{ +static void webPrint(const char* s) { if (espresponse) { espresponse->print(s); webColumn += strlen(s); @@ -185,64 +180,54 @@ static void webPrintSetColumn(int column) { webPrint(" "); } } -static void webPrint(String s) -{ +static void webPrint(String s) { webPrint(s.c_str()); } -static void webPrint(const char *s1, const char *s2) -{ +static void webPrint(const char* s1, const char* s2) { webPrint(s1); webPrint(s2); } -static void webPrint(const char *s1, String s2) -{ +static void webPrint(const char* s1, String s2) { webPrint(s1); webPrint(s2.c_str()); } -static void webPrint(const char *s1, const char *s2, const char *s3) -{ +static void webPrint(const char* s1, const char* s2, const char* s3) { webPrint(s1); webPrint(s2); webPrint(s3); } -static void webPrintln(const char *s) -{ +static void webPrintln(const char* s) { webPrint(s); webPrint("\r\n"); webColumn = 0; } -static void webPrintln(String s) -{ +static void webPrintln(String s) { webPrintln(s.c_str()); } -static void webPrintln(const char *s1, const char *s2) -{ +static void webPrintln(const char* s1, const char* s2) { webPrint(s1); webPrintln(s2); } -static void webPrintln(const char *s, IPAddress ip) -{ +static void webPrintln(const char* s, IPAddress ip) { webPrint(s); webPrintln(ip.toString().c_str()); } -static void webPrintln(const char *s, String s2) -{ +static void webPrintln(const char* s, String s2) { webPrintln(s, s2.c_str()); } -char *trim(char *str) -{ - char *end; +char* trim(char* str) { + char* end; // Trim leading space - while(isspace((unsigned char)*str)) { + while (isspace((unsigned char)*str)) { str++; } - if(*str == 0) { // All spaces? + if (*str == 0) { // All spaces? return str; } // Trim trailing space end = str + strlen(str) - 1; - while(end > str && isspace((unsigned char)*end)) { + while (end > str && isspace((unsigned char)*end)) { end--; } // Write new null terminator character @@ -250,64 +235,56 @@ char *trim(char *str) return str; } -static void print_mac(const char *s, String mac) -{ +static void print_mac(const char* s, String mac) { webPrint(s); webPrint(" ("); webPrint(mac); webPrintln(")"); } -static err_t showFwInfo(char *parameter, auth_t auth_level) { // ESP800 - webPrint("FW version:" GRBL_VERSION " (" GRBL_VERSION_BUILD ")" " # FW target:grbl-embedded # FW HW:"); - #ifdef ENABLE_SD_CARD - webPrint("Direct SD"); - #else - webPrint("No SD"); - #endif +static err_t showFwInfo(char* parameter, auth_t auth_level) { // ESP800 + webPrint("FW version:" GRBL_VERSION " (" GRBL_VERSION_BUILD ")" + " # FW target:grbl-embedded # FW HW:"); +#ifdef ENABLE_SD_CARD + webPrint("Direct SD"); +#else + webPrint("No SD"); +#endif webPrint(" # primary sd:/sd # secondary sd:none # authentication:"); #ifdef ENABLE_AUTHENTICATION webPrint("yes"); #else webPrint("no"); #endif -#if defined (ENABLE_WIFI) - #if defined (ENABLE_HTTP) - webPrint(" # webcommunication: Sync: ", String(web_server.port() + 1)); - webPrint(":"); - switch (WiFi.getMode()) { - case WIFI_MODE_AP: - webPrint(WiFi.softAPIP().toString()); - break; - case WIFI_MODE_STA: - webPrint(WiFi.localIP().toString()); - break; - case WIFI_MODE_APSTA: - webPrint(WiFi.softAPIP().toString()); - break; - default: - webPrint("0.0.0.0"); - break; - } - #endif - webPrint(" # hostname:", wifi_config.Hostname()); - if (WiFi.getMode() == WIFI_AP) { - webPrint("(AP mode)"); - } +#if defined(ENABLE_WIFI) +# if defined(ENABLE_HTTP) + webPrint(" # webcommunication: Sync: ", String(web_server.port() + 1)); + webPrint(":"); + switch (WiFi.getMode()) { + case WIFI_MODE_AP: webPrint(WiFi.softAPIP().toString()); break; + case WIFI_MODE_STA: webPrint(WiFi.localIP().toString()); break; + case WIFI_MODE_APSTA: webPrint(WiFi.softAPIP().toString()); break; + default: webPrint("0.0.0.0"); break; + } +# endif + webPrint(" # hostname:", wifi_config.Hostname()); + if (WiFi.getMode() == WIFI_AP) { + webPrint("(AP mode)"); + } #endif - //to save time in decoding `?` - webPrintln(" # axis:", String(N_AXIS)); + //to save time in decoding `?` + webPrintln(" # axis:", String(N_AXIS)); return STATUS_OK; } -static err_t SPIFFSSize(char *parameter, auth_t auth_level) { // ESP720 +static err_t SPIFFSSize(char* parameter, auth_t auth_level) { // ESP720 webPrint(parameter); webPrint("SPIFFS Total:", ESPResponseStream::formatBytes(SPIFFS.totalBytes())); webPrintln(" Used:", ESPResponseStream::formatBytes(SPIFFS.usedBytes())); return STATUS_OK; } -static err_t formatSpiffs(char *parameter, auth_t auth_level) { // ESP710 +static err_t formatSpiffs(char* parameter, auth_t auth_level) { // ESP710 if (strcmp(parameter, "FORMAT") != 0) { webPrintln("Parameter must be FORMAT"); return STATUS_INVALID_VALUE; @@ -318,7 +295,7 @@ static err_t formatSpiffs(char *parameter, auth_t auth_level) { // ESP710 return STATUS_OK; } -static err_t runFile(char *parameter, auth_t auth_level) { // ESP700 +static err_t runFile(char* parameter, auth_t auth_level) { // ESP700 String path = trim(parameter); if ((path.length() > 0) && (path[0] != '/')) { path = "/" + path; @@ -328,7 +305,7 @@ static err_t runFile(char *parameter, auth_t auth_level) { // ESP700 return STATUS_SD_FILE_NOT_FOUND; } File currentfile = SPIFFS.open(path, FILE_READ); - if (!currentfile) {//if file open success + if (!currentfile) { //if file open success return STATUS_SD_FAILED_OPEN_FILE; } //until no line in file @@ -341,7 +318,7 @@ static err_t runFile(char *parameter, auth_t auth_level) { // ESP700 currentline.getBytes(line, 255); // TODO Settings - feed into command interpreter // while accumulating error codes - err = execute_line((char *)line, CLIENT_WEBUI, auth_level); + err = execute_line((char*)line, CLIENT_WEBUI, auth_level); if (err != STATUS_OK) { accumErr = err; } @@ -353,7 +330,7 @@ static err_t runFile(char *parameter, auth_t auth_level) { // ESP700 } #ifdef ENABLE_NOTIFICATIONS -static err_t showSetNotification(char *parameter, auth_t auth_level) { // ESP610 +static err_t showSetNotification(char* parameter, auth_t auth_level) { // ESP610 if (*parameter == '\0') { webPrint("", notification_type->getStringValue()); webPrintln(" ", notification_ts->getStringValue()); @@ -362,10 +339,10 @@ static err_t showSetNotification(char *parameter, auth_t auth_level) { // ESP610 if (!split_params(parameter)) { return STATUS_INVALID_VALUE; } - char *ts = get_param("TS", false); - char *t2 = get_param("T2", false); - char *t1 = get_param("T1", false); - char *ty = get_param("type", false); + char* ts = get_param("TS", false); + char* t2 = get_param("T2", false); + char* t1 = get_param("T1", false); + char* ty = get_param("type", false); err_t err = notification_type->setStringValue(ty); if (!err) { err = notification_t1->setStringValue(t1); @@ -377,10 +354,9 @@ static err_t showSetNotification(char *parameter, auth_t auth_level) { // ESP610 err = notification_ts->setStringValue(ts); } return err; - } -static err_t sendMessage(char *parameter, auth_t auth_level) { // ESP600 +static err_t sendMessage(char* parameter, auth_t auth_level) { // ESP600 if (*parameter == '\0') { webPrintln("Invalid message!"); return STATUS_INVALID_VALUE; @@ -394,7 +370,7 @@ static err_t sendMessage(char *parameter, auth_t auth_level) { // ESP600 #endif #ifdef ENABLE_AUTHENTICATION -static err_t setUserPassword(char *parameter, auth_t auth_level) { // ESP555 +static err_t setUserPassword(char* parameter, auth_t auth_level) { // ESP555 if (*parameter == '\0') { user_password->setDefault(); return STATUS_OK; @@ -407,7 +383,7 @@ static err_t setUserPassword(char *parameter, auth_t auth_level) { // ESP555 } #endif -static err_t setSystemMode(char *parameter, auth_t auth_level) { // ESP444 +static err_t setSystemMode(char* parameter, auth_t auth_level) { // ESP444 parameter = trim(parameter); if (strcasecmp(parameter, "RESTART") != 0) { webPrintln("Incorrect command"); @@ -418,7 +394,7 @@ static err_t setSystemMode(char *parameter, auth_t auth_level) { // ESP444 return STATUS_OK; } -static err_t showSysStats(char *parameter, auth_t auth_level) { // ESP420 +static err_t showSysStats(char* parameter, auth_t auth_level) { // ESP420 webPrintln("Chip ID: ", String((uint16_t)(ESP.getEfuseMac() >> 32))); webPrintln("CPU Frequency: ", String(ESP.getCpuFreqMHz()) + "Mhz"); webPrintln("CPU Temperature: ", String(temperatureRead(), 1) + "C"); @@ -427,7 +403,7 @@ static err_t showSysStats(char *parameter, auth_t auth_level) { // ESP420 webPrintln("Flash Size: ", ESPResponseStream::formatBytes(ESP.getFlashChipSize())); // Round baudRate to nearest 100 because ESP32 can say e.g. 115201 - webPrintln("Baud rate: ", String((Serial.baudRate()/100) * 100)); + webPrintln("Baud rate: ", String((Serial.baudRate() / 100) * 100)); webPrintln("Sleep mode: ", WiFi.getSleep() ? "Modem" : "None"); #ifdef ENABLE_WIFI @@ -443,112 +419,105 @@ static err_t showSysStats(char *parameter, auth_t auth_level) { // ESP420 webPrintln("Available Size for update: ", ESPResponseStream::formatBytes(flashsize)); webPrintln("Available Size for SPIFFS: ", ESPResponseStream::formatBytes(SPIFFS.totalBytes())); -#if defined (ENABLE_HTTP) +# if defined(ENABLE_HTTP) webPrintln("Web port: ", String(web_server.port())); -#endif -#if defined (ENABLE_TELNET) +# endif +# if defined(ENABLE_TELNET) webPrintln("Data port: ", String(telnet_server.port())); -#endif +# endif webPrintln("Hostname: ", wifi_config.Hostname()); } webPrint("Current WiFi Mode: "); switch (mode) { - case WIFI_STA: - print_mac("STA", WiFi.macAddress()); + case WIFI_STA: + print_mac("STA", WiFi.macAddress()); - webPrint("Connected to: "); - if (WiFi.isConnected()) { //in theory no need but ... - webPrintln(WiFi.SSID()); - webPrintln("Signal: ", String(wifi_config.getSignal(WiFi.RSSI())) + "%"); + webPrint("Connected to: "); + if (WiFi.isConnected()) { //in theory no need but ... + webPrintln(WiFi.SSID()); + webPrintln("Signal: ", String(wifi_config.getSignal(WiFi.RSSI())) + "%"); - uint8_t PhyMode; - esp_wifi_get_protocol(ESP_IF_WIFI_STA, &PhyMode); - const char *modeName; - switch (PhyMode) { - case WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G | WIFI_PROTOCOL_11N: - modeName = "11n"; - break; - case WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G: - modeName = "11g"; - break; - case WIFI_PROTOCOL_11B: - modeName = "11b"; - break; - default: - modeName = "???"; + uint8_t PhyMode; + esp_wifi_get_protocol(ESP_IF_WIFI_STA, &PhyMode); + const char* modeName; + switch (PhyMode) { + case WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G | WIFI_PROTOCOL_11N: modeName = "11n"; break; + case WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G: modeName = "11g"; break; + case WIFI_PROTOCOL_11B: modeName = "11b"; break; + default: modeName = "???"; + } + webPrintln("Phy Mode: ", modeName); + + webPrintln("Channel: ", String(WiFi.channel())); + + tcpip_adapter_dhcp_status_t dhcp_status; + tcpip_adapter_dhcpc_get_status(TCPIP_ADAPTER_IF_STA, &dhcp_status); + webPrintln("IP Mode: ", dhcp_status == TCPIP_ADAPTER_DHCP_STARTED ? "DHCP" : "Static"); + webPrintln("IP: ", WiFi.localIP()); + webPrintln("Gateway: ", WiFi.gatewayIP()); + webPrintln("Mask: ", WiFi.subnetMask()); + webPrintln("DNS: ", WiFi.dnsIP()); + + } //this is web command so connection => no command + webPrint("Disabled Mode: "); + print_mac("AP", WiFi.softAPmacAddress()); + break; + case WIFI_AP: + print_mac("AP", WiFi.softAPmacAddress()); + + wifi_config_t conf; + esp_wifi_get_config(ESP_IF_WIFI_AP, &conf); + webPrintln("SSID: ", (const char*)conf.ap.ssid); + webPrintln("Visible: ", (conf.ap.ssid_hidden == 0) ? "Yes" : "No"); + + const char* mode; + switch (conf.ap.authmode) { + case WIFI_AUTH_OPEN: mode = "None"; break; + case WIFI_AUTH_WEP: mode = "WEP"; break; + case WIFI_AUTH_WPA_PSK: mode = "WPA"; break; + case WIFI_AUTH_WPA2_PSK: mode = "WPA2"; break; + default: mode = "WPA/WPA2"; } - webPrintln("Phy Mode: ", modeName); - webPrintln("Channel: ", String(WiFi.channel())); + webPrintln("Authentication: ", mode); + webPrintln("Max Connections: ", String(conf.ap.max_connection)); tcpip_adapter_dhcp_status_t dhcp_status; - tcpip_adapter_dhcpc_get_status(TCPIP_ADAPTER_IF_STA, &dhcp_status); - webPrintln("IP Mode: ", dhcp_status == TCPIP_ADAPTER_DHCP_STARTED ? "DHCP" : "Static"); - webPrintln("IP: ", WiFi.localIP()); - webPrintln("Gateway: ", WiFi.gatewayIP()); - webPrintln("Mask: ", WiFi.subnetMask()); - webPrintln("DNS: ", WiFi.dnsIP()); + tcpip_adapter_dhcps_get_status(TCPIP_ADAPTER_IF_AP, &dhcp_status); + webPrintln("DHCP Server: ", dhcp_status == TCPIP_ADAPTER_DHCP_STARTED ? "Started" : "Stopped"); - } //this is web command so connection => no command - webPrint("Disabled Mode: "); - print_mac("AP", WiFi.softAPmacAddress()); - break; - case WIFI_AP: - print_mac("AP", WiFi.softAPmacAddress()); + webPrintln("IP: ", WiFi.softAPIP()); - wifi_config_t conf; - esp_wifi_get_config(ESP_IF_WIFI_AP, &conf); - webPrintln("SSID: ", (const char*) conf.ap.ssid); - webPrintln("Visible: ", (conf.ap.ssid_hidden == 0) ? "Yes" : "No"); + tcpip_adapter_ip_info_t ip_AP; + tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_AP, &ip_AP); + webPrintln("Gateway: ", IPAddress(ip_AP.gw.addr)); + webPrintln("Mask: ", IPAddress(ip_AP.netmask.addr)); - const char *mode; - switch (conf.ap.authmode) { - case WIFI_AUTH_OPEN: mode = "None"; break; - case WIFI_AUTH_WEP: mode = "WEP"; break; - case WIFI_AUTH_WPA_PSK: mode = "WPA"; break; - case WIFI_AUTH_WPA2_PSK: mode = "WPA2"; break; - default: mode = "WPA/WPA2"; - } + wifi_sta_list_t station; + tcpip_adapter_sta_list_t tcpip_sta_list; + esp_wifi_ap_get_sta_list(&station); + tcpip_adapter_get_sta_list(&station, &tcpip_sta_list); + webPrintln("Connected clients: ", String(station.num)); - webPrintln("Authentication: ", mode); - webPrintln("Max Connections: ", String(conf.ap.max_connection)); + for (int i = 0; i < station.num; i++) { + webPrint(wifi_config.mac2str(tcpip_sta_list.sta[i].mac)); + webPrintln(" ", IPAddress(tcpip_sta_list.sta[i].ip.addr)); + } + webPrint("Disabled Mode: "); + print_mac("STA", WiFi.macAddress()); + break; + case WIFI_AP_STA: //we should not be in this state but just in case .... + webPrintln("Mixed"); - tcpip_adapter_dhcp_status_t dhcp_status; - tcpip_adapter_dhcps_get_status(TCPIP_ADAPTER_IF_AP, &dhcp_status); - webPrintln("DHCP Server: ", dhcp_status == TCPIP_ADAPTER_DHCP_STARTED ? "Started" : "Stopped"); - - webPrintln("IP: ", WiFi.softAPIP()); - - tcpip_adapter_ip_info_t ip_AP; - tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_AP, &ip_AP); - webPrintln("Gateway: ", IPAddress(ip_AP.gw.addr)); - webPrintln("Mask: ", IPAddress(ip_AP.netmask.addr)); - - wifi_sta_list_t station; - tcpip_adapter_sta_list_t tcpip_sta_list; - esp_wifi_ap_get_sta_list(&station); - tcpip_adapter_get_sta_list(&station, &tcpip_sta_list); - webPrintln("Connected clients: ", String(station.num)); - - for (int i = 0; i < station.num; i++) { - webPrint(wifi_config.mac2str(tcpip_sta_list.sta[i].mac)); - webPrintln(" ", IPAddress(tcpip_sta_list.sta[i].ip.addr)); - } - webPrint("Disabled Mode: "); - print_mac("STA", WiFi.macAddress()); - break; - case WIFI_AP_STA: //we should not be in this state but just in case .... - webPrintln("Mixed"); - - print_mac("STA", WiFi.macAddress()); - print_mac("AP", WiFi.softAPmacAddress()); - break; - default: //we should not be there if no wifi .... - webPrintln("Off"); - break; + print_mac("STA", WiFi.macAddress()); + print_mac("AP", WiFi.softAPmacAddress()); + break; + default: //we should not be there if no wifi .... + webPrintln("Off"); + break; } -#endif // ENABLE_WIFI +#endif // ENABLE_WIFI #ifdef ENABLE_BLUETOOTH webPrint("Current BT Mode: "); if (bt_config.Is_BT_on()) { @@ -588,7 +557,7 @@ static err_t showSysStats(char *parameter, auth_t auth_level) { // ESP420 } #ifdef ENABLE_WIFI -static err_t listAPs(char *parameter, auth_t auth_level) { // ESP410 +static err_t listAPs(char* parameter, auth_t auth_level) { // ESP410 JSONencoder* j = new JSONencoder(espresponse->client() != CLIENT_WEBUI); j->begin(); j->begin_array("AP_LIST"); @@ -596,8 +565,8 @@ static err_t listAPs(char *parameter, auth_t auth_level) { // ESP410 // is a good chance that scan information is already available. int n = WiFi.scanComplete(); switch (n) { - case -2: // Scan not triggered - WiFi.scanNetworks(true); // Begin async scan + case -2: // Scan not triggered + WiFi.scanNetworks(true); // Begin async scan break; case -1: // Scan in progress break; @@ -629,14 +598,14 @@ static err_t listAPs(char *parameter, auth_t auth_level) { // ESP410 } #endif -static err_t setWebSetting(char *parameter, auth_t auth_level) { // ESP401 +static err_t setWebSetting(char* parameter, auth_t auth_level) { // ESP401 // We do not need the "T=" (type) parameter because the // Setting objects know their own type if (!split_params(parameter)) { return STATUS_INVALID_VALUE; } - char *sval = get_param("V", true); - const char *spos = get_param("P", false); + char* sval = get_param("V", true); + const char* spos = get_param("P", false); if (*spos == '\0') { webPrintln("Missing parameter"); return STATUS_INVALID_VALUE; @@ -645,11 +614,11 @@ static err_t setWebSetting(char *parameter, auth_t auth_level) { // ESP401 return ret; } -static err_t listSettings(char *parameter, auth_t auth_level) { // ESP400 +static err_t listSettings(char* parameter, auth_t auth_level) { // ESP400 JSONencoder* j = new JSONencoder(espresponse->client() != CLIENT_WEBUI); j->begin(); j->begin_array("EEPROM"); - for (Setting *js = Setting::List; js; js = js->next()) { + for (Setting* js = Setting::List; js; js = js->next()) { if (js->getType() == WEBSET) { js->addWebui(j); } @@ -661,7 +630,7 @@ static err_t listSettings(char *parameter, auth_t auth_level) { // ESP400 } #ifdef ENABLE_SD_CARD -static err_t runSDFile(char *parameter, auth_t auth_level) { // ESP220 +static err_t runSDFile(char* parameter, auth_t auth_level) { // ESP220 parameter = trim(parameter); if (*parameter == '\0') { webPrintln("Missing file name!"); @@ -694,13 +663,14 @@ static err_t runSDFile(char *parameter, auth_t auth_level) { // ESP220 return STATUS_OK; } SD_client = (espresponse) ? espresponse->client() : CLIENT_ALL; - report_status_message(gc_execute_line(fileLine, (espresponse) ? espresponse->client() : CLIENT_ALL), (espresponse) ? espresponse->client() : CLIENT_ALL); // execute the first line + report_status_message(gc_execute_line(fileLine, (espresponse) ? espresponse->client() : CLIENT_ALL), + (espresponse) ? espresponse->client() : CLIENT_ALL); // execute the first line report_realtime_status((espresponse) ? espresponse->client() : CLIENT_ALL); webPrintln(""); return STATUS_OK; } -static err_t deleteSDObject(char *parameter, auth_t auth_level) { // ESP215 +static err_t deleteSDObject(char* parameter, auth_t auth_level) { // ESP215 parameter = trim(parameter); if (*parameter == '\0') { webPrintln("Missing file name!"); @@ -708,7 +678,7 @@ static err_t deleteSDObject(char *parameter, auth_t auth_level) { // ESP215 } int8_t state = get_sd_state(true); if (state != SDCARD_IDLE) { - webPrintln( (state == SDCARD_NOT_PRESENT) ? "No SD card" : "Busy"); + webPrintln((state == SDCARD_NOT_PRESENT) ? "No SD card" : "Busy"); return STATUS_OK; } String path = parameter; @@ -737,7 +707,7 @@ static err_t deleteSDObject(char *parameter, auth_t auth_level) { // ESP215 return STATUS_OK; } -static err_t listSDFiles(char *parameter, auth_t auth_level) { // ESP210 +static err_t listSDFiles(char* parameter, auth_t auth_level) { // ESP210 int8_t state = get_sd_state(true); if (state != SDCARD_IDLE) { if (state == SDCARD_NOT_PRESENT) { @@ -759,7 +729,7 @@ static err_t listSDFiles(char *parameter, auth_t auth_level) { // ESP210 } #endif -static err_t listLocalFiles(char *parameter, auth_t auth_level) { // No ESP command +static err_t listLocalFiles(char* parameter, auth_t auth_level) { // No ESP command webPrintln(""); listDir(SPIFFS, "/", 10, espresponse->client()); String ssd = "[Local FS Free:" + ESPResponseStream::formatBytes(SPIFFS.totalBytes() - SPIFFS.usedBytes()); @@ -775,7 +745,7 @@ static void listDirJSON(fs::FS& fs, const char* dirname, uint8_t levels, JSONenc File file = root.openNextFile(); while (file) { const char* tailName = strchr(file.name(), '/'); - tailName = tailName ? tailName + 1 : file.name(); + tailName = tailName ? tailName + 1 : file.name(); if (file.isDirectory() && levels) { j->begin_array(tailName); listDirJSON(fs, file.name(), levels - 1, j); @@ -790,7 +760,7 @@ static void listDirJSON(fs::FS& fs, const char* dirname, uint8_t levels, JSONenc } } -static err_t listLocalFilesJSON(char *parameter, auth_t auth_level) { // No ESP command +static err_t listLocalFilesJSON(char* parameter, auth_t auth_level) { // No ESP command JSONencoder* j = new JSONencoder(espresponse->client() != CLIENT_WEBUI); j->begin(); j->begin_array("files"); @@ -806,34 +776,31 @@ static err_t listLocalFilesJSON(char *parameter, auth_t auth_level) { // No ESP return STATUS_OK; } -static err_t showSDStatus(char *parameter, auth_t auth_level) { // ESP200 +static err_t showSDStatus(char* parameter, auth_t auth_level) { // ESP200 const char* resp = "No SD card"; #ifdef ENABLE_SD_CARD switch (get_sd_state(true)) { - case SDCARD_IDLE: - resp = "SD card detected"; - break; - case SDCARD_NOT_PRESENT: - resp = "No SD card"; - break; - default: - resp = "Busy"; + case SDCARD_IDLE: resp = "SD card detected"; break; + case SDCARD_NOT_PRESENT: resp = "No SD card"; break; + default: resp = "Busy"; } #endif webPrintln(resp); return STATUS_OK; } -static err_t setRadioState(char *parameter, auth_t auth_level) { // ESP115 +static err_t setRadioState(char* parameter, auth_t auth_level) { // ESP115 parameter = trim(parameter); if (*parameter == '\0') { // Display the radio state bool on = false; -#if defined (ENABLE_WIFI) - if (WiFi.getMode() != WIFI_MODE_NULL)on = true; +#if defined(ENABLE_WIFI) + if (WiFi.getMode() != WIFI_MODE_NULL) + on = true; #endif -#if defined (ENABLE_BLUETOOTH) - if (bt_config.Is_BT_on())on = true; +#if defined(ENABLE_BLUETOOTH) + if (bt_config.Is_BT_on()) + on = true; #endif webPrintln(on ? "ON" : "OFF"); return STATUS_OK; @@ -849,12 +816,12 @@ static err_t setRadioState(char *parameter, auth_t auth_level) { // ESP115 return STATUS_INVALID_VALUE; } //Stop everything -#if defined (ENABLE_WIFI) +#if defined(ENABLE_WIFI) if (WiFi.getMode() != WIFI_MODE_NULL) { wifi_config.StopWiFi(); } #endif -#if defined (ENABLE_BLUETOOTH) +#if defined(ENABLE_BLUETOOTH) if (bt_config.Is_BT_on()) { bt_config.end(); } @@ -867,39 +834,37 @@ static err_t setRadioState(char *parameter, auth_t auth_level) { // ESP115 //On #ifdef WIFI_OR_BLUETOOTH switch (wifi_radio_mode->get()) { - case ESP_WIFI_AP: - case ESP_WIFI_STA: - #if !defined (ENABLE_WIFI) + case ESP_WIFI_AP: + case ESP_WIFI_STA: +# if !defined(ENABLE_WIFI) webPrintln("WiFi is not enabled!"); return STATUS_WIFI_FAIL_BEGIN; - #else +# else wifi_config.begin(); return STATUS_OK; - #endif - case ESP_BT: - #if !defined (ENABLE_BLUETOOTH) +# endif + case ESP_BT: +# if !defined(ENABLE_BLUETOOTH) webPrintln("Bluetooth is not enabled!"); return STATUS_BT_FAIL_BEGIN; - #else +# else bt_config.begin(); return STATUS_OK; - #endif - default: - webPrintln("[MSG: Radio is Off]"); - return STATUS_OK; +# endif + default: webPrintln("[MSG: Radio is Off]"); return STATUS_OK; } #endif return STATUS_OK; } #ifdef ENABLE_WIFI -static err_t showIP(char *parameter, auth_t auth_level) { // ESP111 +static err_t showIP(char* parameter, auth_t auth_level) { // ESP111 webPrintln(parameter, WiFi.getMode() == WIFI_STA ? WiFi.localIP() : WiFi.softAPIP()); return STATUS_OK; } -static err_t showSetStaParams(char *parameter, auth_t auth_level) { // ESP103 +static err_t showSetStaParams(char* parameter, auth_t auth_level) { // ESP103 if (*parameter == '\0') { webPrint("IP:", wifi_sta_ip->getStringValue()); webPrint(" GW:", wifi_sta_gateway->getStringValue()); @@ -909,9 +874,9 @@ static err_t showSetStaParams(char *parameter, auth_t auth_level) { // ESP103 if (!split_params(parameter)) { return STATUS_INVALID_VALUE; } - char *gateway = get_param("GW", false); - char *netmask = get_param("MSK", false); - char *ip = get_param("IP", false); + char* gateway = get_param("GW", false); + char* netmask = get_param("MSK", false); + char* ip = get_param("IP", false); err_t err = wifi_sta_ip->setStringValue(ip); if (!err) { @@ -924,11 +889,11 @@ static err_t showSetStaParams(char *parameter, auth_t auth_level) { // ESP103 } #endif -static err_t showWebHelp(char *parameter, auth_t auth_level) { // ESP0 +static err_t showWebHelp(char* parameter, auth_t auth_level) { // ESP0 webPrintln("Persistent web settings - $name to show, $name=value to set"); webPrintln("ESPname FullName Description"); webPrintln("------- -------- -----------"); - for (Setting *s = Setting::List; s; s = s->next()) { + for (Setting* s = Setting::List; s; s = s->next()) { if (s->getType() == WEBSET) { if (s->getGrblName()) { webPrint(" ", s->getGrblName()); @@ -943,7 +908,7 @@ static err_t showWebHelp(char *parameter, auth_t auth_level) { // ESP0 webPrintln("Other web commands: $name to show, $name=value to set"); webPrintln("ESPname FullName Values"); webPrintln("------- -------- ------"); - for (Command *cp = Command::List; cp; cp = cp->next()) { + for (Command* cp = Command::List; cp; cp = cp->next()) { if (cp->getType() == WEBCMD) { if (cp->getGrblName()) { webPrint(" ", cp->getGrblName()); @@ -965,98 +930,169 @@ static err_t showWebHelp(char *parameter, auth_t auth_level) { // ESP0 // line up while allowing VSCode code folding to work correction. #define WEB_COMMON -void make_web_settings() -{ - // If authentication enabled, display_settings skips or displays - // RU - need user or admin password to read - // WU - need user or admin password to set - // WA - need admin password to set - #ifdef WEB_COMMON - new WebCommand(NULL, WEBCMD, WG, "ESP800", "Firmware/Info", showFwInfo); - new WebCommand(NULL, WEBCMD, WU, "ESP720", "LocalFS/Size", SPIFFSSize); - new WebCommand("FORMAT", WEBCMD, WA, "ESP710", "LocalFS/Format", formatSpiffs); - new WebCommand("path", WEBCMD, WU, "ESP700", "LocalFS/Run", runFile); - new WebCommand("path", WEBCMD, WU, NULL, "LocalFS/List", listLocalFiles); - new WebCommand("path", WEBCMD, WU, NULL, "LocalFS/ListJSON",listLocalFilesJSON); - #endif - #ifdef ENABLE_NOTIFICATIONS - new WebCommand("TYPE=NONE|PUSHOVER|EMAIL|LINE T1=token1 T2=token2 TS=settings", - WEBCMD, WA, "ESP610", "Notification/Setup",showSetNotification); - new WebCommand("message", WEBCMD, WU, "ESP600", "Notification/Send", sendMessage); - #endif - #ifdef ENABLE_AUTHENTICATION - new WebCommand("password",WEBCMD, WA, "ESP555", "WebUI/SetUserPassword", setUserPassword); - #endif - #ifdef WEB_COMMON - new WebCommand("RESTART", WEBCMD, WA, "ESP444", "System/Control",setSystemMode); - new WebCommand(NULL, WEBCMD, WU, "ESP420", "System/Stats", showSysStats); - #endif - #ifdef ENABLE_WIFI - new WebCommand(NULL, WEBCMD, WU, "ESP410", "WiFi/ListAPs", listAPs); - #endif - #ifdef WEB_COMMON - new WebCommand("P=position T=type V=value", - WEBCMD, WA, "ESP401", "WebUI/Set", setWebSetting); - new WebCommand(NULL, WEBCMD, WU, "ESP400", "WebUI/List", listSettings); - #endif - #ifdef ENABLE_SD_CARD - new WebCommand("path", WEBCMD, WU, "ESP220", "SD/Run", runSDFile); - new WebCommand("file_or_directory_path", - WEBCMD, WU, "ESP215", "SD/Delete", deleteSDObject); - new WebCommand(NULL, WEBCMD, WU, "ESP210", "SD/List", listSDFiles); - #endif - #ifdef WEB_COMMON - new WebCommand(NULL, WEBCMD, WU, "ESP200", "SD/Status", showSDStatus); - new WebCommand("STA|AP|BT|OFF", - WEBCMD, WA, "ESP115", "Radio/State", setRadioState); - #endif - #ifdef ENABLE_WIFI - new WebCommand(NULL, WEBCMD, WG,"ESP111", "System/IP", showIP); - new WebCommand("IP=ipaddress MSK=netmask GW=gateway", - WEBCMD, WA, "ESP103", "Sta/Setup", showSetStaParams); - #endif - #ifdef WEB_COMMON - new WebCommand(NULL, WEBCMD, WG, "ESP0", "WebUI/Help", showWebHelp); - new WebCommand(NULL, WEBCMD, WG, "ESP", "WebUI/Help", showWebHelp); - #endif - // WebUI Settings - // Standard WEBUI authentication is user+ to get, admin to set unless otherwise specified - #ifdef ENABLE_NOTIFICATIONS - notification_ts = new StringSetting("Notification Settings", WEBSET, WA, NULL, "Notification/TS", DEFAULT_TOKEN, 0, MAX_NOTIFICATION_SETTING_LENGTH, NULL); - notification_t2 = new StringSetting("Notification Token 2", WEBSET, WA, NULL, "Notification/T2", DEFAULT_TOKEN, MIN_NOTIFICATION_TOKEN_LENGTH, MAX_NOTIFICATION_TOKEN_LENGTH, NULL); - notification_t1 = new StringSetting("Notification Token 1", WEBSET, WA, NULL, "Notification/T1", DEFAULT_TOKEN , MIN_NOTIFICATION_TOKEN_LENGTH, MAX_NOTIFICATION_TOKEN_LENGTH, NULL); - notification_type = new EnumSetting("Notification type", WEBSET, WA, NULL, "Notification/Type", DEFAULT_NOTIFICATION_TYPE, ¬ificationOptions); - #endif - #ifdef ENABLE_AUTHENTICATION - user_password = new StringSetting("User password", WEBSET, WA, NULL, "WebUI/UserPassword", DEFAULT_USER_PWD, MIN_LOCAL_PASSWORD_LENGTH, MAX_LOCAL_PASSWORD_LENGTH, &COMMANDS::isLocalPasswordValid); - admin_password = new StringSetting("Admin password", WEBSET, WA, NULL, "WebUI/AdminPassword", DEFAULT_ADMIN_PWD, MIN_LOCAL_PASSWORD_LENGTH, MAX_LOCAL_PASSWORD_LENGTH, &COMMANDS::isLocalPasswordValid); - #endif - #ifdef ENABLE_BLUETOOTH - bt_name = new StringSetting("Bluetooth name", WEBSET, WA, "ESP140", "Bluetooth/Name", DEFAULT_BT_NAME, MIN_BTNAME_LENGTH, MAX_BTNAME_LENGTH, (bool (*)(char*))BTConfig::isBTnameValid); - #endif +void make_web_settings() { +// If authentication enabled, display_settings skips or displays +// RU - need user or admin password to read +// WU - need user or admin password to set +// WA - need admin password to set +#ifdef WEB_COMMON + new WebCommand(NULL, WEBCMD, WG, "ESP800", "Firmware/Info", showFwInfo); + new WebCommand(NULL, WEBCMD, WU, "ESP720", "LocalFS/Size", SPIFFSSize); + new WebCommand("FORMAT", WEBCMD, WA, "ESP710", "LocalFS/Format", formatSpiffs); + new WebCommand("path", WEBCMD, WU, "ESP700", "LocalFS/Run", runFile); + new WebCommand("path", WEBCMD, WU, NULL, "LocalFS/List", listLocalFiles); + new WebCommand("path", WEBCMD, WU, NULL, "LocalFS/ListJSON", listLocalFilesJSON); +#endif +#ifdef ENABLE_NOTIFICATIONS + new WebCommand( + "TYPE=NONE|PUSHOVER|EMAIL|LINE T1=token1 T2=token2 TS=settings", WEBCMD, WA, "ESP610", "Notification/Setup", showSetNotification); + new WebCommand("message", WEBCMD, WU, "ESP600", "Notification/Send", sendMessage); +#endif +#ifdef ENABLE_AUTHENTICATION + new WebCommand("password", WEBCMD, WA, "ESP555", "WebUI/SetUserPassword", setUserPassword); +#endif +#ifdef WEB_COMMON + new WebCommand("RESTART", WEBCMD, WA, "ESP444", "System/Control", setSystemMode); + new WebCommand(NULL, WEBCMD, WU, "ESP420", "System/Stats", showSysStats); +#endif +#ifdef ENABLE_WIFI + new WebCommand(NULL, WEBCMD, WU, "ESP410", "WiFi/ListAPs", listAPs); +#endif +#ifdef WEB_COMMON + new WebCommand("P=position T=type V=value", WEBCMD, WA, "ESP401", "WebUI/Set", setWebSetting); + new WebCommand(NULL, WEBCMD, WU, "ESP400", "WebUI/List", listSettings); +#endif +#ifdef ENABLE_SD_CARD + new WebCommand("path", WEBCMD, WU, "ESP220", "SD/Run", runSDFile); + new WebCommand("file_or_directory_path", WEBCMD, WU, "ESP215", "SD/Delete", deleteSDObject); + new WebCommand(NULL, WEBCMD, WU, "ESP210", "SD/List", listSDFiles); +#endif +#ifdef WEB_COMMON + new WebCommand(NULL, WEBCMD, WU, "ESP200", "SD/Status", showSDStatus); + new WebCommand("STA|AP|BT|OFF", WEBCMD, WA, "ESP115", "Radio/State", setRadioState); +#endif +#ifdef ENABLE_WIFI + new WebCommand(NULL, WEBCMD, WG, "ESP111", "System/IP", showIP); + new WebCommand("IP=ipaddress MSK=netmask GW=gateway", WEBCMD, WA, "ESP103", "Sta/Setup", showSetStaParams); +#endif +#ifdef WEB_COMMON + new WebCommand(NULL, WEBCMD, WG, "ESP0", "WebUI/Help", showWebHelp); + new WebCommand(NULL, WEBCMD, WG, "ESP", "WebUI/Help", showWebHelp); +#endif +// WebUI Settings +// Standard WEBUI authentication is user+ to get, admin to set unless otherwise specified +#ifdef ENABLE_NOTIFICATIONS + notification_ts = new StringSetting( + "Notification Settings", WEBSET, WA, NULL, "Notification/TS", DEFAULT_TOKEN, 0, MAX_NOTIFICATION_SETTING_LENGTH, NULL); + notification_t2 = new StringSetting("Notification Token 2", + WEBSET, + WA, + NULL, + "Notification/T2", + DEFAULT_TOKEN, + MIN_NOTIFICATION_TOKEN_LENGTH, + MAX_NOTIFICATION_TOKEN_LENGTH, + NULL); + notification_t1 = new StringSetting("Notification Token 1", + WEBSET, + WA, + NULL, + "Notification/T1", + DEFAULT_TOKEN, + MIN_NOTIFICATION_TOKEN_LENGTH, + MAX_NOTIFICATION_TOKEN_LENGTH, + NULL); + notification_type = + new EnumSetting("Notification type", WEBSET, WA, NULL, "Notification/Type", DEFAULT_NOTIFICATION_TYPE, ¬ificationOptions); +#endif +#ifdef ENABLE_AUTHENTICATION + user_password = new StringSetting("User password", + WEBSET, + WA, + NULL, + "WebUI/UserPassword", + DEFAULT_USER_PWD, + MIN_LOCAL_PASSWORD_LENGTH, + MAX_LOCAL_PASSWORD_LENGTH, + &COMMANDS::isLocalPasswordValid); + admin_password = new StringSetting("Admin password", + WEBSET, + WA, + NULL, + "WebUI/AdminPassword", + DEFAULT_ADMIN_PWD, + MIN_LOCAL_PASSWORD_LENGTH, + MAX_LOCAL_PASSWORD_LENGTH, + &COMMANDS::isLocalPasswordValid); +#endif +#ifdef ENABLE_BLUETOOTH + bt_name = new StringSetting("Bluetooth name", + WEBSET, + WA, + "ESP140", + "Bluetooth/Name", + DEFAULT_BT_NAME, + MIN_BTNAME_LENGTH, + MAX_BTNAME_LENGTH, + (bool (*)(char*))BTConfig::isBTnameValid); +#endif - #ifdef WIFI_OR_BLUETOOTH - // user+ to get, admin to set - wifi_radio_mode = new EnumSetting("Radio mode", WEBSET, WA, "ESP110", "Radio/Mode", DEFAULT_RADIO_MODE, &radioEnabledOptions); - #endif +#ifdef WIFI_OR_BLUETOOTH + // user+ to get, admin to set + wifi_radio_mode = new EnumSetting("Radio mode", WEBSET, WA, "ESP110", "Radio/Mode", DEFAULT_RADIO_MODE, &radioEnabledOptions); +#endif - #ifdef ENABLE_WIFI - telnet_port = new IntSetting("Telnet Port", WEBSET, WA, "ESP131", "Telnet/Port", DEFAULT_TELNETSERVER_PORT, MIN_TELNET_PORT, MAX_TELNET_PORT, NULL); - telnet_enable = new EnumSetting("Telnet Enable", WEBSET, WA, "ESP130", "Telnet/Enable", DEFAULT_TELNET_STATE, &onoffOptions); - http_port = new IntSetting("HTTP Port", WEBSET, WA, "ESP121", "Http/Port", DEFAULT_WEBSERVER_PORT, MIN_HTTP_PORT, MAX_HTTP_PORT, NULL); - http_enable = new EnumSetting("HTTP Enable", WEBSET, WA, "ESP120", "Http/Enable", DEFAULT_HTTP_STATE, &onoffOptions); - wifi_hostname = new StringSetting("Hostname", WEBSET, WA, "ESP112", "System/Hostname", DEFAULT_HOSTNAME, MIN_HOSTNAME_LENGTH, MAX_HOSTNAME_LENGTH, (bool (*)(char*))WiFiConfig::isHostnameValid); - wifi_ap_channel = new IntSetting("AP Channel", WEBSET, WA, "ESP108", "AP/Channel", DEFAULT_AP_CHANNEL, MIN_CHANNEL, MAX_CHANNEL, NULL); - wifi_ap_ip = new IPaddrSetting("AP Static IP", WEBSET, WA, "ESP107", "AP/IP", DEFAULT_AP_IP, NULL); - // no get, admin to set - wifi_ap_password = new StringSetting("AP Password", WEBSET, WA, "ESP106", "AP/Password", DEFAULT_AP_PWD, MIN_PASSWORD_LENGTH, MAX_PASSWORD_LENGTH, (bool (*)(char*))WiFiConfig::isPasswordValid); - wifi_ap_ssid = new StringSetting("AP SSID", WEBSET, WA, "ESP105", "AP/SSID", DEFAULT_AP_SSID, MIN_SSID_LENGTH, MAX_SSID_LENGTH, (bool (*)(char*))WiFiConfig::isSSIDValid); - wifi_sta_netmask = new IPaddrSetting("Station Static Mask", WEBSET, WA, NULL, "Sta/Netmask", DEFAULT_STA_MK, NULL); - wifi_sta_gateway = new IPaddrSetting("Station Static Gateway", WEBSET, WA, NULL, "Sta/Gateway", DEFAULT_STA_GW, NULL); - wifi_sta_ip = new IPaddrSetting("Station Static IP", WEBSET, WA, NULL, "Sta/IP", DEFAULT_STA_IP, NULL); - wifi_sta_mode = new EnumSetting("Station IP Mode", WEBSET, WA, "ESP102", "Sta/IPMode", DEFAULT_STA_IP_MODE, &staModeOptions); - // no get, admin to set - wifi_sta_password = new StringSetting("Station Password", WEBSET, WA, "ESP101", "Sta/Password", DEFAULT_STA_PWD, MIN_PASSWORD_LENGTH, MAX_PASSWORD_LENGTH, (bool (*)(char*))WiFiConfig::isPasswordValid); - wifi_sta_ssid = new StringSetting("Station SSID", WEBSET, WA, "ESP100", "Sta/SSID", DEFAULT_STA_SSID, MIN_SSID_LENGTH, MAX_SSID_LENGTH, (bool (*)(char*))WiFiConfig::isSSIDValid); - #endif +#ifdef ENABLE_WIFI + telnet_port = new IntSetting( + "Telnet Port", WEBSET, WA, "ESP131", "Telnet/Port", DEFAULT_TELNETSERVER_PORT, MIN_TELNET_PORT, MAX_TELNET_PORT, NULL); + telnet_enable = new EnumSetting("Telnet Enable", WEBSET, WA, "ESP130", "Telnet/Enable", DEFAULT_TELNET_STATE, &onoffOptions); + http_port = new IntSetting("HTTP Port", WEBSET, WA, "ESP121", "Http/Port", DEFAULT_WEBSERVER_PORT, MIN_HTTP_PORT, MAX_HTTP_PORT, NULL); + http_enable = new EnumSetting("HTTP Enable", WEBSET, WA, "ESP120", "Http/Enable", DEFAULT_HTTP_STATE, &onoffOptions); + wifi_hostname = new StringSetting("Hostname", + WEBSET, + WA, + "ESP112", + "System/Hostname", + DEFAULT_HOSTNAME, + MIN_HOSTNAME_LENGTH, + MAX_HOSTNAME_LENGTH, + (bool (*)(char*))WiFiConfig::isHostnameValid); + wifi_ap_channel = new IntSetting("AP Channel", WEBSET, WA, "ESP108", "AP/Channel", DEFAULT_AP_CHANNEL, MIN_CHANNEL, MAX_CHANNEL, NULL); + wifi_ap_ip = new IPaddrSetting("AP Static IP", WEBSET, WA, "ESP107", "AP/IP", DEFAULT_AP_IP, NULL); + // no get, admin to set + wifi_ap_password = new StringSetting("AP Password", + WEBSET, + WA, + "ESP106", + "AP/Password", + DEFAULT_AP_PWD, + MIN_PASSWORD_LENGTH, + MAX_PASSWORD_LENGTH, + (bool (*)(char*))WiFiConfig::isPasswordValid); + wifi_ap_ssid = new StringSetting( + "AP SSID", WEBSET, WA, "ESP105", "AP/SSID", DEFAULT_AP_SSID, MIN_SSID_LENGTH, MAX_SSID_LENGTH, (bool (*)(char*))WiFiConfig::isSSIDValid); + wifi_sta_netmask = new IPaddrSetting("Station Static Mask", WEBSET, WA, NULL, "Sta/Netmask", DEFAULT_STA_MK, NULL); + wifi_sta_gateway = new IPaddrSetting("Station Static Gateway", WEBSET, WA, NULL, "Sta/Gateway", DEFAULT_STA_GW, NULL); + wifi_sta_ip = new IPaddrSetting("Station Static IP", WEBSET, WA, NULL, "Sta/IP", DEFAULT_STA_IP, NULL); + wifi_sta_mode = new EnumSetting("Station IP Mode", WEBSET, WA, "ESP102", "Sta/IPMode", DEFAULT_STA_IP_MODE, &staModeOptions); + // no get, admin to set + wifi_sta_password = new StringSetting("Station Password", + WEBSET, + WA, + "ESP101", + "Sta/Password", + DEFAULT_STA_PWD, + MIN_PASSWORD_LENGTH, + MAX_PASSWORD_LENGTH, + (bool (*)(char*))WiFiConfig::isPasswordValid); + wifi_sta_ssid = new StringSetting("Station SSID", + WEBSET, + WA, + "ESP100", + "Sta/SSID", + DEFAULT_STA_SSID, + MIN_SSID_LENGTH, + MAX_SSID_LENGTH, + (bool (*)(char*))WiFiConfig::isSSIDValid); +#endif } diff --git a/Grbl_Esp32/WebSettings.h b/Grbl_Esp32/src/WebUI/WebSettings.h similarity index 89% rename from Grbl_Esp32/WebSettings.h rename to Grbl_Esp32/src/WebUI/WebSettings.h index 8fea8fc7..c9606364 100644 --- a/Grbl_Esp32/WebSettings.h +++ b/Grbl_Esp32/src/WebUI/WebSettings.h @@ -1,3 +1,5 @@ +#pragma once + /* WebSettings.h - Definitions for WebUI-related settings. @@ -18,8 +20,6 @@ along with Grbl. If not, see . */ -#pragma once - extern StringSetting* wifi_sta_ssid; extern StringSetting* wifi_sta_password; @@ -37,10 +37,10 @@ extern IPaddrSetting* wifi_ap_ip; extern IntSetting* wifi_ap_channel; extern StringSetting* wifi_hostname; -extern EnumSetting* http_enable; -extern IntSetting* http_port; -extern EnumSetting* telnet_enable; -extern IntSetting* telnet_port; +extern EnumSetting* http_enable; +extern IntSetting* http_port; +extern EnumSetting* telnet_enable; +extern IntSetting* telnet_port; #endif #ifdef WIFI_OR_BLUETOOTH @@ -57,7 +57,7 @@ extern StringSetting* admin_password; #endif #ifdef ENABLE_NOTIFICATIONS -extern EnumSetting* notification_type; +extern EnumSetting* notification_type; extern StringSetting* notification_t1; extern StringSetting* notification_t2; extern StringSetting* notification_ts; diff --git a/Grbl_Esp32/wificonfig.cpp b/Grbl_Esp32/src/WebUI/WifiConfig.cpp similarity index 81% rename from Grbl_Esp32/wificonfig.cpp rename to Grbl_Esp32/src/WebUI/WifiConfig.cpp index 80857f7f..df326999 100644 --- a/Grbl_Esp32/wificonfig.cpp +++ b/Grbl_Esp32/src/WebUI/WifiConfig.cpp @@ -1,5 +1,5 @@ /* - wificonfig.cpp - wifi functions class + WifiConfig.cpp - wifi functions class Copyright (c) 2014 Luc Lebosse. All rights reserved. @@ -18,34 +18,30 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifdef ARDUINO_ARCH_ESP32 - -#include "grbl.h" +#include "../Grbl.h" #ifdef ENABLE_WIFI -#include -#include -#include -#include -#include -#include "wifiservices.h" - +# include +# include +# include +# include +# include +# include "WifiServices.h" WiFiConfig wifi_config; -String WiFiConfig::_hostname = ""; -bool WiFiConfig::_events_registered = false; -WiFiConfig::WiFiConfig() { -} +String WiFiConfig::_hostname = ""; +bool WiFiConfig::_events_registered = false; +WiFiConfig::WiFiConfig() {} WiFiConfig::~WiFiConfig() { end(); } //just simple helper to convert mac address to string -char* WiFiConfig::mac2str(uint8_t mac [8]) { - static char macstr [18]; +char* WiFiConfig::mac2str(uint8_t mac[8]) { + static char macstr[18]; if (0 > sprintf(macstr, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5])) strcpy(macstr, "00:00:00:00:00:00"); return macstr; @@ -53,7 +49,7 @@ char* WiFiConfig::mac2str(uint8_t mac [8]) { const char* WiFiConfig::info() { static String result; - String tmp; + String tmp; result = "[MSG:"; if ((WiFi.getMode() == WIFI_MODE_STA) || (WiFi.getMode() == WIFI_MODE_APSTA)) { result += "Mode=STA:SSID="; @@ -81,7 +77,8 @@ const char* WiFiConfig::info() { tmp.replace(":", "-"); result += tmp; } - if (WiFi.getMode() == WIFI_MODE_NULL)result += "No Wifi"; + if (WiFi.getMode() == WIFI_MODE_NULL) + result += "No Wifi"; result += "]\r\n"; return result.c_str(); } @@ -91,9 +88,10 @@ const char* WiFiConfig::info() { */ uint32_t WiFiConfig::IP_int_from_string(String& s) { - uint32_t ip_int = 0; + uint32_t ip_int = 0; IPAddress ipaddr; - if (ipaddr.fromString(s)) ip_int = ipaddr; + if (ipaddr.fromString(s)) + ip_int = ipaddr; return ip_int; } @@ -125,7 +123,6 @@ bool WiFiConfig::isHostnameValid(const char* hostname) { return true; } - /** * Check if SSID string is valid */ @@ -147,7 +144,8 @@ bool WiFiConfig::isSSIDValid(const char* ssid) { */ bool WiFiConfig::isPasswordValid(const char* password) { - if (strlen(password) == 0) return true; //open network + if (strlen(password) == 0) + return true; //open network //limited size // length is checked automatically by string setting //no space allowed ? @@ -166,7 +164,6 @@ bool WiFiConfig::isValidIP(const char* string) { return ip.fromString(string); } - /** * WiFi events * SYSTEM_EVENT_WIFI_READY < ESP32 WiFi ready @@ -198,14 +195,9 @@ bool WiFiConfig::isValidIP(const char* string) { void WiFiConfig::WiFiEvent(WiFiEvent_t event) { switch (event) { - case SYSTEM_EVENT_STA_GOT_IP: - grbl_sendf(CLIENT_ALL, "[MSG:Connected with %s]\r\n", WiFi.localIP().toString().c_str()); - break; - case SYSTEM_EVENT_STA_DISCONNECTED: - grbl_send(CLIENT_ALL, "[MSG:Disconnected]\r\n"); - break; - default: - break; + case SYSTEM_EVENT_STA_GOT_IP: grbl_sendf(CLIENT_ALL, "[MSG:Connected with %s]\r\n", WiFi.localIP().toString().c_str()); break; + case SYSTEM_EVENT_STA_DISCONNECTED: grbl_send(CLIENT_ALL, "[MSG:Disconnected]\r\n"); break; + default: break; } } @@ -225,29 +217,24 @@ int32_t WiFiConfig::getSignal(int32_t RSSI) { */ bool WiFiConfig::ConnectSTA2AP() { - String msg, msg_out; - uint8_t count = 0; - uint8_t dot = 0; + String msg, msg_out; + uint8_t count = 0; + uint8_t dot = 0; wl_status_t status = WiFi.status(); while (status != WL_CONNECTED && count < 40) { switch (status) { - case WL_NO_SSID_AVAIL: - msg = "No SSID"; - break; - case WL_CONNECT_FAILED: - msg = "Connection failed"; - break; - case WL_CONNECTED: - break; - default: - if ((dot > 3) || (dot == 0)) { - dot = 0; - msg_out = "Connecting"; - } - msg_out += "."; - msg = msg_out; - dot++; - break; + case WL_NO_SSID_AVAIL: msg = "No SSID"; break; + case WL_CONNECT_FAILED: msg = "Connection failed"; break; + case WL_CONNECTED: break; + default: + if ((dot > 3) || (dot == 0)) { + dot = 0; + msg_out = "Connecting"; + } + msg_out += "."; + msg = msg_out; + dot++; + break; } grbl_sendf(CLIENT_ALL, "[MSG:%s]\r\n", msg.c_str()); COMMANDS::wait(500); @@ -265,8 +252,10 @@ bool WiFiConfig::StartSTA() { //stop active service wifi_services.end(); //Sanity check - if ((WiFi.getMode() == WIFI_STA) || (WiFi.getMode() == WIFI_AP_STA))WiFi.disconnect(); - if ((WiFi.getMode() == WIFI_AP) || (WiFi.getMode() == WIFI_AP_STA))WiFi.softAPdisconnect(); + if ((WiFi.getMode() == WIFI_STA) || (WiFi.getMode() == WIFI_AP_STA)) + WiFi.disconnect(); + if ((WiFi.getMode() == WIFI_AP) || (WiFi.getMode() == WIFI_AP_STA)) + WiFi.softAPdisconnect(); WiFi.enableAP(false); WiFi.mode(WIFI_STA); //Get parameters for STA @@ -274,13 +263,14 @@ bool WiFiConfig::StartSTA() { WiFi.setHostname(h.c_str()); //SSID String SSID = wifi_sta_ssid->get(); - if (SSID.length() == 0) SSID = DEFAULT_STA_SSID; + if (SSID.length() == 0) + SSID = DEFAULT_STA_SSID; //password - String password = wifi_sta_password->get(); - int8_t IP_mode = wifi_sta_mode->get(); - int32_t IP = wifi_sta_ip->get(); - int32_t GW = wifi_sta_gateway->get(); - int32_t MK = wifi_sta_netmask->get(); + String password = wifi_sta_password->get(); + int8_t IP_mode = wifi_sta_mode->get(); + int32_t IP = wifi_sta_ip->get(); + int32_t GW = wifi_sta_gateway->get(); + int32_t MK = wifi_sta_netmask->get(); //if not DHCP if (IP_mode != DHCP_MODE) { IPAddress ip(IP), mask(MK), gateway(GW); @@ -304,21 +294,25 @@ bool WiFiConfig::StartAP() { //stop active services wifi_services.end(); //Sanity check - if ((WiFi.getMode() == WIFI_STA) || (WiFi.getMode() == WIFI_AP_STA))WiFi.disconnect(); - if ((WiFi.getMode() == WIFI_AP) || (WiFi.getMode() == WIFI_AP_STA))WiFi.softAPdisconnect(); + if ((WiFi.getMode() == WIFI_STA) || (WiFi.getMode() == WIFI_AP_STA)) + WiFi.disconnect(); + if ((WiFi.getMode() == WIFI_AP) || (WiFi.getMode() == WIFI_AP_STA)) + WiFi.softAPdisconnect(); WiFi.enableSTA(false); WiFi.mode(WIFI_AP); //Get parameters for AP //SSID String SSID = wifi_ap_ssid->get(); - if (SSID.length() == 0) SSID = DEFAULT_AP_SSID; + if (SSID.length() == 0) + SSID = DEFAULT_AP_SSID; String password = wifi_ap_password->get(); int8_t channel = wifi_ap_channel->get(); - if (channel == 0) channel = DEFAULT_AP_CHANNEL; + if (channel == 0) + channel = DEFAULT_AP_CHANNEL; - int32_t IP = wifi_ap_ip->get(); + int32_t IP = wifi_ap_ip->get(); IPAddress ip(IP); IPAddress mask; mask.fromString(DEFAULT_AP_MK); @@ -340,8 +334,10 @@ bool WiFiConfig::StartAP() { void WiFiConfig::StopWiFi() { //Sanity check - if ((WiFi.getMode() == WIFI_STA) || (WiFi.getMode() == WIFI_AP_STA))WiFi.disconnect(true); - if ((WiFi.getMode() == WIFI_AP) || (WiFi.getMode() == WIFI_AP_STA))WiFi.softAPdisconnect(true); + if ((WiFi.getMode() == WIFI_STA) || (WiFi.getMode() == WIFI_AP_STA)) + WiFi.disconnect(true); + if ((WiFi.getMode() == WIFI_AP) || (WiFi.getMode() == WIFI_AP_STA)) + WiFi.softAPdisconnect(true); wifi_services.end(); WiFi.enableSTA(false); WiFi.enableAP(false); @@ -362,7 +358,7 @@ void WiFiConfig::begin() { _events_registered = true; } //Get hostname - _hostname = wifi_hostname->get(); + _hostname = wifi_hostname->get(); int8_t wifiMode = wifi_radio_mode->get(); if (wifiMode == ESP_WIFI_AP) { StartAP(); @@ -375,7 +371,8 @@ void WiFiConfig::begin() { } //start services wifi_services.begin(); - } else WiFi.mode(WIFI_OFF); + } else + WiFi.mode(WIFI_OFF); } /** @@ -390,12 +387,12 @@ void WiFiConfig::end() { */ void WiFiConfig::reset_settings() { bool error = false; - for (Setting *s = Setting::List; s; s = s->next()) { + for (Setting* s = Setting::List; s; s = s->next()) { if (s->getDescription()) { s->setDefault(); } - } - // TODO commit the changes and check that for errors + } + // TODO commit the changes and check that for errors if (error) grbl_send(CLIENT_ALL, "[MSG:WiFi reset error]\r\n"); grbl_send(CLIENT_ALL, "[MSG:WiFi reset done]\r\n"); @@ -413,7 +410,4 @@ void WiFiConfig::handle() { wifi_services.handle(); } - -#endif // ENABLE_WIFI - -#endif // ARDUINO_ARCH_ESP32 +#endif // ENABLE_WIFI diff --git a/Grbl_Esp32/src/WebUI/WifiConfig.h b/Grbl_Esp32/src/WebUI/WifiConfig.h new file mode 100644 index 00000000..6bf13e71 --- /dev/null +++ b/Grbl_Esp32/src/WebUI/WifiConfig.h @@ -0,0 +1,114 @@ +#pragma once + +/* + WifiConfig.h - wifi functions class + + Copyright (c) 2014 Luc Lebosse. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +//Preferences entries + +//Notifications +#define ESP_PUSHOVER_NOTIFICATION 1 +#define ESP_EMAIL_NOTIFICATION 2 +#define ESP_LINE_NOTIFICATION 3 + +#define DHCP_MODE 0 +#define STATIC_MODE 1 + +//Switch +#define ESP_SAVE_ONLY 0 +#define ESP_APPLY_NOW 1 + +//defaults values +#define DEFAULT_HOSTNAME "grblesp" +#ifdef CONNECT_TO_SSID +# define DEFAULT_STA_SSID CONNECT_TO_SSID +# define DEFAULT_STA_PWD SSID_PASSWORD +#else //!CONNECT_TO_SSID +# define DEFAULT_STA_SSID "GRBL_ESP" +# define DEFAULT_STA_PWD "12345678" +#endif //CONNECT_TO_SSID +#define DEFAULT_STA_IP "0.0.0.0" +#define DEFAULT_STA_GW "0.0.0.0" +#define DEFAULT_STA_MK "0.0.0.0" +#define DEFAULT_AP_SSID "GRBL_ESP" +#define DEFAULT_AP_PWD "12345678" +#define DEFAULT_AP_IP "192.168.0.1" +#define DEFAULT_AP_MK "255.255.255.0" +#define DEFAULT_AP_CHANNEL 1 +#define DEFAULT_WEBSERVER_PORT 80 +#define DEFAULT_HTTP_STATE 1 +#define DEFAULT_TELNETSERVER_PORT 23 +#define DEFAULT_TELNET_STATE 1 +#define DEFAULT_STA_IP_MODE DHCP_MODE +#define HIDDEN_PASSWORD "********" +#define DEFAULT_TOKEN "" +#define DEFAULT_NOTIFICATION_TYPE 0 + +//boundaries +#define MAX_SSID_LENGTH 32 +#define MIN_SSID_LENGTH 1 +#define MAX_PASSWORD_LENGTH 64 +//min size of password is 0 or upper than 8 char +//so let set min is 8 +#define MIN_PASSWORD_LENGTH 8 +#define MAX_HOSTNAME_LENGTH 32 +#define MIN_HOSTNAME_LENGTH 1 +#define MAX_HTTP_PORT 65001 +#define MIN_HTTP_PORT 1 +#define MAX_TELNET_PORT 65001 +#define MIN_TELNET_PORT 1 +#define MIN_CHANNEL 1 +#define MAX_CHANNEL 14 +#define MIN_NOTIFICATION_TOKEN_LENGTH 0 +#define MAX_NOTIFICATION_TOKEN_LENGTH 63 +#define MAX_NOTIFICATION_SETTING_LENGTH 127 + +#include + +class WiFiConfig { +public: + WiFiConfig(); + ~WiFiConfig(); + static const char* info(); + static bool isValidIP(const char* string); + static bool isPasswordValid(const char* password); + static bool isSSIDValid(const char* ssid); + static bool isHostnameValid(const char* hostname); + static uint32_t IP_int_from_string(String& s); + static String IP_string_from_int(uint32_t ip_int); + static String Hostname() { return _hostname; } + static char* mac2str(uint8_t mac[8]); + static bool StartAP(); + static bool StartSTA(); + static void StopWiFi(); + static int32_t getSignal(int32_t RSSI); + static void begin(); + static void end(); + static void handle(); + static void reset_settings(); + static bool Is_WiFi_on(); + +private: + static bool ConnectSTA2AP(); + static void WiFiEvent(WiFiEvent_t event); + static String _hostname; + static bool _events_registered; +}; + +extern WiFiConfig wifi_config; diff --git a/Grbl_Esp32/src/WebUI/WifiServices.cpp b/Grbl_Esp32/src/WebUI/WifiServices.cpp new file mode 100644 index 00000000..43f65232 --- /dev/null +++ b/Grbl_Esp32/src/WebUI/WifiServices.cpp @@ -0,0 +1,162 @@ +/* + WifiServices.cpp - wifi services functions class + + Copyright (c) 2014 Luc Lebosse. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "../Grbl.h" + +#ifdef ENABLE_WIFI + +# include +# include +# include +# include "WifiServices.h" +# ifdef ENABLE_MDNS +# include +# endif +# ifdef ENABLE_OTA +# include +# endif +# ifdef ENABLE_HTTP +# include "WebServer.h" +# endif +# ifdef ENABLE_TELNET +# include "TelnetServer.h" +# endif +# ifdef ENABLE_NOTIFICATIONS +# include "NotificationsService.h" +# endif +# include "Commands.h" + +WiFiServices wifi_services; + +WiFiServices::WiFiServices() {} +WiFiServices::~WiFiServices() { + end(); +} + +bool WiFiServices::begin() { + bool no_error = true; + //Sanity check + if (WiFi.getMode() == WIFI_OFF) + return false; + String h = wifi_hostname->get(); + + //Start SPIFFS + SPIFFS.begin(true); +# ifdef ENABLE_OTA + ArduinoOTA + .onStart([]() { + String type; + if (ArduinoOTA.getCommand() == U_FLASH) + type = "sketch"; + else { // U_SPIFFS + // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end() + type = "filesystem"; + SPIFFS.end(); + } + grbl_sendf(CLIENT_ALL, "[MSG:Start OTA updating %s]\r\n", type.c_str()); + }) + .onEnd([]() { grbl_sendf(CLIENT_ALL, "[MSG:End OTA]\r\n"); }) + .onProgress([](unsigned int progress, unsigned int total) { + grbl_sendf(CLIENT_ALL, "[MSG:OTA Progress: %u%%]\r\n", (progress / (total / 100))); + }) + .onError([](ota_error_t error) { + grbl_sendf(CLIENT_ALL, "[MSG:OTA Error(%u):]\r\n", error); + if (error == OTA_AUTH_ERROR) + grbl_send(CLIENT_ALL, "[MSG:Auth Failed]\r\n"); + else if (error == OTA_BEGIN_ERROR) + grbl_send(CLIENT_ALL, "[MSG:Begin Failed]\r\n"); + else if (error == OTA_CONNECT_ERROR) + grbl_send(CLIENT_ALL, "[MSG:Connect Failed]\r\n"); + else if (error == OTA_RECEIVE_ERROR) + grbl_send(CLIENT_ALL, "[MSG:Receive Failed]\r\n"); + else if (error == OTA_END_ERROR) + grbl_send(CLIENT_ALL, "[MSG:End Failed]\r\n"); + }); + ArduinoOTA.begin(); +# endif +# ifdef ENABLE_MDNS + //no need in AP mode + if (WiFi.getMode() == WIFI_STA) { + //start mDns + if (!MDNS.begin(h.c_str())) { + grbl_send(CLIENT_ALL, "[MSG:Cannot start mDNS]\r\n"); + no_error = false; + } else + grbl_sendf(CLIENT_ALL, "[MSG:Start mDNS with hostname:http://%s.local/]\r\n", h.c_str()); + } +# endif +# ifdef ENABLE_HTTP + web_server.begin(); +# endif +# ifdef ENABLE_TELNET + telnet_server.begin(); +# endif +# ifdef ENABLE_NOTIFICATIONS + notificationsservice.begin(); +# endif + //be sure we are not is mixed mode in setup + WiFi.scanNetworks(true); + return no_error; +} +void WiFiServices::end() { +# ifdef ENABLE_NOTIFICATIONS + notificationsservice.end(); +# endif +# ifdef ENABLE_TELNET + telnet_server.end(); +# endif +# ifdef ENABLE_HTTP + web_server.end(); +# endif + //stop OTA +# ifdef ENABLE_OTA + ArduinoOTA.end(); +# endif + //Stop SPIFFS + SPIFFS.end(); +# ifdef ENABLE_MDNS + //Stop mDNS + MDNS.end(); +# endif +} + +void WiFiServices::handle() { + COMMANDS::wait(0); + //to avoid mixed mode due to scan network + if (WiFi.getMode() == WIFI_AP_STA) { + // In principle it should be sufficient to check for != WIFI_SCAN_RUNNING, + // but that does not work well. Doing so makes scans in AP mode unreliable. + // Sometimes the first try works, but subsequent scans fail. + if (WiFi.scanComplete() >= 0) { + WiFi.enableSTA(false); + } + } +# ifdef ENABLE_OTA + ArduinoOTA.handle(); +# endif +# ifdef ENABLE_HTTP + web_server.handle(); +# endif +# ifdef ENABLE_TELNET + telnet_server.handle(); +# endif +} + +#endif // ENABLE_WIFI diff --git a/Grbl_Esp32/wifiservices.h b/Grbl_Esp32/src/WebUI/WifiServices.h similarity index 88% rename from Grbl_Esp32/wifiservices.h rename to Grbl_Esp32/src/WebUI/WifiServices.h index 0b493c69..b3f2c704 100644 --- a/Grbl_Esp32/wifiservices.h +++ b/Grbl_Esp32/src/WebUI/WifiServices.h @@ -1,5 +1,7 @@ +#pragma once + /* - wifiservices.h - wifi services functions class + WifiServices.h - wifi services functions class Copyright (c) 2014 Luc Lebosse. All rights reserved. @@ -18,14 +20,8 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ - - -#ifndef _WIFI_SERVICES_H -#define _WIFI_SERVICES_H - - class WiFiServices { - public: +public: WiFiServices(); ~WiFiServices(); static bool begin(); @@ -34,6 +30,3 @@ class WiFiServices { }; extern WiFiServices wifi_services; - -#endif - diff --git a/Grbl_Esp32/data/favicon.ico b/Grbl_Esp32/src/data/favicon.ico similarity index 100% rename from Grbl_Esp32/data/favicon.ico rename to Grbl_Esp32/src/data/favicon.ico diff --git a/Grbl_Esp32/data/index.html.gz b/Grbl_Esp32/src/data/index.html.gz similarity index 100% rename from Grbl_Esp32/data/index.html.gz rename to Grbl_Esp32/src/data/index.html.gz diff --git a/Grbl_Esp32/grbl_unipolar.cppold b/Grbl_Esp32/src/grbl_unipolar.cppold similarity index 100% rename from Grbl_Esp32/grbl_unipolar.cppold rename to Grbl_Esp32/src/grbl_unipolar.cppold diff --git a/Grbl_Esp32/grbl_unipolar.hold b/Grbl_Esp32/src/grbl_unipolar.hold similarity index 96% rename from Grbl_Esp32/grbl_unipolar.hold rename to Grbl_Esp32/src/grbl_unipolar.hold index 378280d6..a7a782c4 100644 --- a/Grbl_Esp32/grbl_unipolar.hold +++ b/Grbl_Esp32/src/grbl_unipolar.hold @@ -1,3 +1,5 @@ +#pragma once + /* grbl_unipolar.h Part of Grbl_ESP32 @@ -27,8 +29,6 @@ To take a step simply call the step(direction) function. It will take */ -#ifndef grbl_unipolar_h -#define grbl_unipolar_h void unipolar_init(); void unipolar_step(uint8_t step_mask, uint8_t dir_mask); @@ -51,4 +51,3 @@ class Unipolar { uint8_t _pin_phase3; }; -#endif \ No newline at end of file diff --git a/Grbl_Esp32/tests/arcs_arrows.nc b/Grbl_Esp32/src/tests/arcs_arrows.nc similarity index 100% rename from Grbl_Esp32/tests/arcs_arrows.nc rename to Grbl_Esp32/src/tests/arcs_arrows.nc diff --git a/Grbl_Esp32/tests/parsetest.nc b/Grbl_Esp32/src/tests/parsetest.nc similarity index 100% rename from Grbl_Esp32/tests/parsetest.nc rename to Grbl_Esp32/src/tests/parsetest.nc diff --git a/Grbl_Esp32/tests/raster_tree.nc b/Grbl_Esp32/src/tests/raster_tree.nc similarity index 100% rename from Grbl_Esp32/tests/raster_tree.nc rename to Grbl_Esp32/src/tests/raster_tree.nc diff --git a/Grbl_Esp32/tests/spindle/arcs_arrows.nc b/Grbl_Esp32/src/tests/spindle/arcs_arrows.nc similarity index 100% rename from Grbl_Esp32/tests/spindle/arcs_arrows.nc rename to Grbl_Esp32/src/tests/spindle/arcs_arrows.nc diff --git a/Grbl_Esp32/tests/spindle/spindle_modal.nc b/Grbl_Esp32/src/tests/spindle/spindle_modal.nc similarity index 100% rename from Grbl_Esp32/tests/spindle/spindle_modal.nc rename to Grbl_Esp32/src/tests/spindle/spindle_modal.nc diff --git a/Grbl_Esp32/tests/spindle/tree_laser_mode.nc b/Grbl_Esp32/src/tests/spindle/tree_laser_mode.nc similarity index 100% rename from Grbl_Esp32/tests/spindle/tree_laser_mode.nc rename to Grbl_Esp32/src/tests/spindle/tree_laser_mode.nc diff --git a/Grbl_Esp32/tests/spindle_testing.nc b/Grbl_Esp32/src/tests/spindle_testing.nc similarity index 100% rename from Grbl_Esp32/tests/spindle_testing.nc rename to Grbl_Esp32/src/tests/spindle_testing.nc diff --git a/Grbl_Esp32/tdef.h b/Grbl_Esp32/tdef.h deleted file mode 100644 index 5e73f4c8..00000000 --- a/Grbl_Esp32/tdef.h +++ /dev/null @@ -1,7 +0,0 @@ -#ifndef tdef_h -#define tdef_h - -#include "grbl.h" - - -#endif diff --git a/Grbl_Esp32/web_server.cpp b/Grbl_Esp32/web_server.cpp deleted file mode 100644 index 18678f6c..00000000 --- a/Grbl_Esp32/web_server.cpp +++ /dev/null @@ -1,1808 +0,0 @@ -/* - web_server.cpp - web server functions class - - Copyright (c) 2014 Luc Lebosse. All rights reserved. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#ifdef ARDUINO_ARCH_ESP32 - -#include "grbl.h" - -#if defined (ENABLE_WIFI) && defined (ENABLE_HTTP) - -#include "wifiservices.h" - -#include "espresponse.h" -#include "serial2socket.h" -#include "web_server.h" -#include -#include -#include -#include -#ifdef ENABLE_SD_CARD -#include -#include "grbl_sd.h" -#endif -#include -#include -#include -#include -#include -#ifdef ENABLE_MDNS -#include -#endif -#ifdef ENABLE_SSDP -#include -#endif -#ifdef ENABLE_CAPTIVE_PORTAL -#include -const byte DNS_PORT = 53; -DNSServer dnsServer; -#endif -#include - -//embedded response file if no files on SPIFFS -#include "nofile.h" - -//Upload status -typedef enum { - UPLOAD_STATUS_NONE = 0, - UPLOAD_STATUS_FAILED = 1, - UPLOAD_STATUS_CANCELLED = 2, - UPLOAD_STATUS_SUCCESSFUL = 3, - UPLOAD_STATUS_ONGOING = 4 -} upload_status_type; - - -//Default 404 -const char PAGE_404 [] = "\n\nRedirecting... \n\n\n
Unknown page : $QUERY$- you will be redirected...\n

\nif not redirected, click here\n

\n\n\n\n
\n\n\n\n"; -const char PAGE_CAPTIVE [] = "\n\nCaptive Portal \n\n\n
Captive Portal page : $QUERY$- you will be redirected...\n

\nif not redirected, click here\n

\n\n\n\n
\n\n\n\n"; - -//error codes fo upload -#define ESP_ERROR_AUTHENTICATION 1 -#define ESP_ERROR_FILE_CREATION 2 -#define ESP_ERROR_FILE_WRITE 3 -#define ESP_ERROR_UPLOAD 4 -#define ESP_ERROR_NOT_ENOUGH_SPACE 5 -#define ESP_ERROR_UPLOAD_CANCELLED 6 -#define ESP_ERROR_FILE_CLOSE 7 -#define ESP_ERROR_NO_SD 8 - -Web_Server web_server; -bool Web_Server::_setupdone = false; -uint16_t Web_Server::_port = 0; -long Web_Server::_id_connection = 0; -uint8_t Web_Server::_upload_status = UPLOAD_STATUS_NONE; -WebServer * Web_Server::_webserver = NULL; -WebSocketsServer * Web_Server::_socket_server = NULL; -#ifdef ENABLE_AUTHENTICATION -auth_ip * Web_Server::_head = NULL; -uint8_t Web_Server::_nb_ip = 0; -#define MAX_AUTH_IP 10 -#endif -Web_Server::Web_Server(){ - -} -Web_Server::~Web_Server(){ - end(); -} - -long Web_Server::get_client_ID() { - return _id_connection; -} - -bool Web_Server::begin(){ - - bool no_error = true; - _setupdone = false; - if (http_enable->get() == 0) { - return false; - } - _port = http_port->get(); - - //create instance - _webserver= new WebServer(_port); -#ifdef ENABLE_AUTHENTICATION - //here the list of headers to be recorded - const char * headerkeys[] = {"Cookie"} ; - size_t headerkeyssize = sizeof (headerkeys) / sizeof (char*); - //ask server to track these headers - _webserver->collectHeaders (headerkeys, headerkeyssize ); -#endif - _socket_server = new WebSocketsServer(_port + 1); - _socket_server->begin(); - _socket_server->onEvent(handle_Websocket_Event); - - - //Websocket output - Serial2Socket.attachWS(_socket_server); - - //events functions - //_web_events->onConnect(handle_onevent_connect); - //events management - // _webserver->addHandler(_web_events); - - //Websocket function - //_web_socket->onEvent(handle_Websocket_Event); - //Websocket management - //_webserver->addHandler(_web_socket); - - //Web server handlers - //trick to catch command line on "/" before file being processed - _webserver->on("/",HTTP_ANY, handle_root); - - //Page not found handler - _webserver->onNotFound (handle_not_found); - - //need to be there even no authentication to say to UI no authentication - _webserver->on("/login", HTTP_ANY, handle_login); - - //web commands - _webserver->on ("/command", HTTP_ANY, handle_web_command); - _webserver->on ("/command_silent", HTTP_ANY, handle_web_command_silent); - - //SPIFFS - _webserver->on ("/files", HTTP_ANY, handleFileList, SPIFFSFileupload); - - //web update - _webserver->on ("/updatefw", HTTP_ANY, handleUpdate, WebUpdateUpload); - -#ifdef ENABLE_SD_CARD - //Direct SD management - _webserver->on("/upload", HTTP_ANY, handle_direct_SDFileList,SDFile_direct_upload); - //_webserver->on("/SD", HTTP_ANY, handle_SDCARD); -#endif - -#ifdef ENABLE_CAPTIVE_PORTAL - if(WiFi.getMode() == WIFI_AP){ - // if DNSServer is started with "*" for domain name, it will reply with - // provided IP to all DNS request - dnsServer.start(DNS_PORT, "*", WiFi.softAPIP()); - grbl_send(CLIENT_ALL,"[MSG:Captive Portal Started]\r\n"); - _webserver->on ("/generate_204", HTTP_ANY, handle_root); - _webserver->on ("/gconnectivitycheck.gstatic.com", HTTP_ANY, handle_root); - //do not forget the / at the end - _webserver->on ("/fwlink/", HTTP_ANY, handle_root); - } -#endif - -#ifdef ENABLE_SSDP - //SSDP service presentation - if(WiFi.getMode() == WIFI_STA){ - _webserver->on ("/description.xml", HTTP_GET, handle_SSDP); - //Add specific for SSDP - SSDP.setSchemaURL ("description.xml"); - SSDP.setHTTPPort (_port); - SSDP.setName (wifi_config.Hostname()); - SSDP.setURL ("/"); - SSDP.setDeviceType ("upnp:rootdevice"); - /*Any customization could be here - SSDP.setModelName (ESP32_MODEL_NAME); - SSDP.setModelURL (ESP32_MODEL_URL); - SSDP.setModelNumber (ESP_MODEL_NUMBER); - SSDP.setManufacturer (ESP_MANUFACTURER_NAME); - SSDP.setManufacturerURL (ESP_MANUFACTURER_URL); - */ - - //Start SSDP - grbl_send(CLIENT_ALL,"[MSG:SSDP Started]\r\n"); - SSDP.begin(); - } -#endif - grbl_send(CLIENT_ALL,"[MSG:HTTP Started]\r\n"); - //start webserver - _webserver->begin(); -#ifdef ENABLE_MDNS - //add mDNS - if(WiFi.getMode() == WIFI_STA){ - MDNS.addService("http","tcp",_port); - } -#endif - _setupdone = true; - return no_error; -} - -void Web_Server::end(){ - _setupdone = false; -#ifdef ENABLE_SSDP - SSDP.end(); -#endif //ENABLE_SSDP -#ifdef ENABLE_MDNS - //remove mDNS - mdns_service_remove("_http", "_tcp"); -#endif - if (_socket_server) { - delete _socket_server; - _socket_server = NULL; - } - if (_webserver) { - delete _webserver; - _webserver = NULL; - } -#ifdef ENABLE_AUTHENTICATION - while (_head) { - auth_ip * current = _head; - _head = _head->_next; - delete current; - } - _nb_ip = 0; -#endif -} - -//Root of Webserver///////////////////////////////////////////////////// - -void Web_Server::handle_root() -{ - String path = "/index.html"; - String contentType = getContentType(path); - String pathWithGz = path + ".gz"; - //if have a index.html or gzip version this is default root page - if((SPIFFS.exists(pathWithGz) || SPIFFS.exists(path)) && !_webserver->hasArg("forcefallback") && _webserver->arg("forcefallback")!="yes") { - if(SPIFFS.exists(pathWithGz)) { - path = pathWithGz; - } - File file = SPIFFS.open(path, FILE_READ); - _webserver->streamFile(file, contentType); - file.close(); - return; - } - //if no lets launch the default content - _webserver->sendHeader("Content-Encoding", "gzip"); - _webserver->send_P(200,"text/html",PAGE_NOFILES,PAGE_NOFILES_SIZE); -} - -//Handle not registred path on SPIFFS neither SD /////////////////////// -void Web_Server:: handle_not_found() -{ - if (is_authenticated() == LEVEL_GUEST) { - _webserver->sendContent_P("HTTP/1.1 301 OK\r\nLocation: /\r\nCache-Control: no-cache\r\n\r\n"); - //_webserver->client().stop(); - return; - } - bool page_not_found = false; - String path = _webserver->urlDecode(_webserver->uri()); - String contentType = getContentType(path); - String pathWithGz = path + ".gz"; - -#ifdef ENABLE_SD_CARD - if ((path.substring(0,4) == "/SD/")) { - //remove /SD - path = path.substring(3); - if(SD.exists((char *)pathWithGz.c_str()) || SD.exists((char *)path.c_str())) { - if(SD.exists((char *)pathWithGz.c_str())) { - path = pathWithGz; - } - File datafile = SD.open((char *)path.c_str()); - if (datafile) { - vTaskDelay(1 / portTICK_RATE_MS); - size_t totalFileSize = datafile.size(); - size_t i = 0; - bool done = false; - _webserver->setContentLength(totalFileSize); - _webserver->send(200, contentType, ""); - uint8_t buf[1024]; - while (!done){ - vTaskDelay(1 / portTICK_RATE_MS); - int v = datafile.read(buf,1024); - if ((v == -1) || (v == 0)) { - done = true; - } else { - _webserver->client().write(buf,1024); - i+=v; - } - if (i >= totalFileSize) done = true; - } - datafile.close(); - if ( i != totalFileSize) { - //error: TBD - } - return; - } - } - String content = "cannot find "; - content+=path; - _webserver->send(404,"text/plain",content.c_str()); - return; - } else -#endif - if(SPIFFS.exists(pathWithGz) || SPIFFS.exists(path)) { - if(SPIFFS.exists(pathWithGz)) { - path = pathWithGz; - } - File file = SPIFFS.open(path, FILE_READ); - _webserver->streamFile(file, contentType); - file.close(); - return; - } else { - page_not_found = true; - } - - if (page_not_found ) { -#ifdef ENABLE_CAPTIVE_PORTAL - if(WiFi.getMode() == WIFI_AP) { - String contentType= PAGE_CAPTIVE; - String stmp = WiFi.softAPIP().toString(); - //Web address = ip + port - String KEY_IP = "$WEB_ADDRESS$"; - String KEY_QUERY = "$QUERY$"; - if (_port != 80) { - stmp+=":"; - stmp+=String(_port); - } - contentType.replace(KEY_IP,stmp); - contentType.replace(KEY_IP,stmp); - contentType.replace(KEY_QUERY,_webserver->uri()); - _webserver->send(200,"text/html",contentType); - //_webserver->sendContent_P(NOT_AUTH_NF); - //_webserver->client().stop(); - return; - } -#endif - path = "/404.htm"; - contentType = getContentType(path); - pathWithGz = path + ".gz"; - if(SPIFFS.exists(pathWithGz) || SPIFFS.exists(path)) { - if(SPIFFS.exists(pathWithGz)) { - path = pathWithGz; - } - File file = SPIFFS.open(path, FILE_READ); - _webserver->streamFile(file, contentType); - file.close(); - - } else { - //if not template use default page - contentType = PAGE_404; - String stmp; - if (WiFi.getMode()==WIFI_STA ) { - stmp=WiFi.localIP().toString(); - } else { - stmp=WiFi.softAPIP().toString(); - } - //Web address = ip + port - String KEY_IP = "$WEB_ADDRESS$"; - String KEY_QUERY = "$QUERY$"; - if ( _port != 80) { - stmp+=":"; - stmp+=String(_port); - } - contentType.replace(KEY_IP,stmp); - contentType.replace(KEY_QUERY,_webserver->uri()); - _webserver->send(200,"text/html",contentType); - } - } -} - -#ifdef ENABLE_SSDP -//http SSDP xml presentation -void Web_Server::handle_SSDP () -{ - StreamString sschema ; - if (sschema.reserve (1024) ) { - String templ = "" - "" - "" - "1" - "0" - "" - "http://%s:%u/" - "" - "upnp:rootdevice" - "%s" - "/" - "%s" - "ESP32" - "Marlin" - "http://espressif.com/en/products/hardware/esp-wroom-32/overview" - "Espressif Systems" - "http://espressif.com" - "uuid:%s" - "" - "\r\n" - "\r\n"; - char uuid[37]; - String sip = WiFi.localIP().toString(); - uint32_t chipId = (uint16_t) (ESP.getEfuseMac() >> 32); - sprintf (uuid, "38323636-4558-4dda-9188-cda0e6%02x%02x%02x", - (uint16_t) ( (chipId >> 16) & 0xff), - (uint16_t) ( (chipId >> 8) & 0xff), - (uint16_t) chipId & 0xff ); - String serialNumber = String (chipId); - sschema.printf (templ.c_str(), - sip.c_str(), - _port, - wifi_config.Hostname().c_str(), - serialNumber.c_str(), - uuid); - _webserver->send (200, "text/xml", (String) sschema); - } else { - _webserver->send (500); - } -} -#endif - -bool Web_Server::is_realtime_cmd(char c){ - if (c == CMD_STATUS_REPORT) return true; - if (c == CMD_CYCLE_START) return true; - if (c == CMD_RESET) return true; - if (c == CMD_FEED_HOLD) return true; - if (c == CMD_SAFETY_DOOR) return true; - if (c == CMD_JOG_CANCEL) return true; - if (c == CMD_DEBUG_REPORT) return true; - if (c == CMD_FEED_OVR_RESET) return true; - if (c == CMD_FEED_OVR_COARSE_PLUS) return true; - if (c == CMD_FEED_OVR_COARSE_MINUS) return true; - if (c == CMD_FEED_OVR_FINE_PLUS) return true; - if (c == CMD_FEED_OVR_FINE_MINUS) return true; - if (c == CMD_RAPID_OVR_RESET) return true; - if (c == CMD_RAPID_OVR_MEDIUM) return true; - if (c == CMD_RAPID_OVR_LOW) return true; - if (c == CMD_SPINDLE_OVR_COARSE_PLUS) return true; - if (c == CMD_SPINDLE_OVR_COARSE_MINUS) return true; - if (c == CMD_SPINDLE_OVR_FINE_PLUS) return true; - if (c == CMD_SPINDLE_OVR_FINE_MINUS) return true; - if (c == CMD_SPINDLE_OVR_STOP) return true; - if (c == CMD_COOLANT_FLOOD_OVR_TOGGLE) return true; - if (c == CMD_COOLANT_MIST_OVR_TOGGLE) return true; - return false; -} - -void Web_Server::_handle_web_command (bool silent) -{ - //to save time if already disconnected - //if (_webserver->hasArg ("PAGEID") ) { - // if (_webserver->arg ("PAGEID").length() > 0 ) { - // if (_webserver->arg ("PAGEID").toInt() != _id_connection) { - // _webserver->send (200, "text/plain", "Invalid command"); - // return; - // } - // } - //} - auth_t auth_level = is_authenticated(); - String cmd = ""; - if (_webserver->hasArg ("plain")) { - cmd = _webserver->arg ("plain"); - } else if (_webserver->hasArg ("commandText")) { - cmd = _webserver->arg ("commandText"); - } else { - _webserver->send (200, "text/plain", "Invalid command"); - return; - } - //if it is internal command [ESPXXX] - cmd.trim(); - int ESPpos = cmd.indexOf ("[ESP"); - if (ESPpos > -1) { - char line[256]; - strncpy(line, cmd.c_str(), 255); - ESPResponseStream* espresponse = silent ? NULL : new ESPResponseStream(_webserver); - err_t err = system_execute_line(line, espresponse, auth_level); - String answer; - if (err == STATUS_OK) { - answer = "ok"; - } else { - const char* msg = errorString(err); - answer = "Error: "; - if (msg) { - answer += msg; - } else { - answer += err; - } - } - if (silent || !espresponse->anyOutput()) { - _webserver->send (err ? 401 : 200, "text/plain", answer.c_str()); - } else { - espresponse->flush(); - } - } else { //execute GCODE - if (auth_level == LEVEL_GUEST) { - _webserver->send (401, "text/plain", "Authentication failed!\n"); - return; - } - //Instead of send several commands one by one by web / send full set and split here - String scmd; - const char *res = ""; - uint8_t sindex = 0; - // TODO Settings - this is very inefficient. get_Splited_Value() is O(n^2) - // when it could easily be O(n). Also, it would be just as easy to push - // the entire string into Serial2Socket and pull off lines from there. - for (uint8_t sindex = 0; - (scmd = get_Splited_Value(cmd,'\n', sindex)) != ""; - sindex++) { - // 0xC2 is an HTML encoding prefix that, in UTF-8 mode, - // precede 0x90 and 0xa0-0bf, which are GRBL realtime commands. - // There are other encodings for 0x91-0x9f, so I am not sure - // how - or whether - those commands work. - // Ref: https://www.w3schools.com/tags/ref_urlencode.ASP - if (!silent && (scmd.length() == 2) && (scmd[0] == 0xC2)) { - scmd[0]=scmd[1]; - scmd.remove(1,1); - } - if (scmd.length() > 1) - scmd += "\n"; - else if (!is_realtime_cmd(scmd[0]) ) - scmd += "\n"; - if (!Serial2Socket.push(scmd.c_str())) - res = "Error"; - } - _webserver->send (200, "text/plain", res); - } -} - -//login status check -void Web_Server::handle_login() -{ -#ifdef ENABLE_AUTHENTICATION - String smsg; - String sUser,sPassword; - String auths; - int code = 200; - bool msg_alert_error=false; - //disconnect can be done anytime no need to check credential - if (_webserver->hasArg("DISCONNECT")) { - String cookie = _webserver->header("Cookie"); - int pos = cookie.indexOf("ESPSESSIONID="); - String sessionID; - if (pos!= -1) { - int pos2 = cookie.indexOf(";",pos); - sessionID = cookie.substring(pos+strlen("ESPSESSIONID="),pos2); - } - ClearAuthIP(_webserver->client().remoteIP(), sessionID.c_str()); - _webserver->sendHeader("Set-Cookie","ESPSESSIONID=0"); - _webserver->sendHeader("Cache-Control","no-cache"); - String buffer2send = "{\"status\":\"Ok\",\"authentication_lvl\":\"guest\"}"; - _webserver->send(code, "application/json", buffer2send); - //_webserver->client().stop(); - return; - } - - auth_t auth_level = is_authenticated(); - if (auth_level == LEVEL_GUEST) auths = "guest"; - else if (auth_level == LEVEL_USER) auths = "user"; - else if (auth_level == LEVEL_ADMIN) auths = "admin"; - else auths = "???"; - - //check is it is a submission or a query - if (_webserver->hasArg("SUBMIT")) { - //is there a correct list of query? - if ( _webserver->hasArg("PASSWORD") && _webserver->hasArg("USER")) { - //USER - sUser = _webserver->arg("USER"); - if ( !((sUser == DEFAULT_ADMIN_LOGIN) || (sUser == DEFAULT_USER_LOGIN))) { - msg_alert_error=true; - smsg = "Error : Incorrect User"; - code = 401; - } - if (msg_alert_error == false) { - //Password - sPassword = _webserver->arg("PASSWORD"); - String sadminPassword = admin_password->get(); - String suserPassword = user_password->get(); - - if(!(((sUser == DEFAULT_ADMIN_LOGIN) && (strcmp(sPassword.c_str(),sadminPassword.c_str()) == 0)) || - ((sUser == DEFAULT_USER_LOGIN) && (strcmp(sPassword.c_str(),suserPassword.c_str()) == 0)))) { - msg_alert_error=true; - smsg = "Error: Incorrect password"; - code = 401; - } - } - } else { - msg_alert_error=true; - smsg = "Error: Missing data"; - code = 500; - } - //change password - if (_webserver->hasArg("PASSWORD") && _webserver->hasArg("USER") && _webserver->hasArg("NEWPASSWORD") && (msg_alert_error==false) ) { - String newpassword = _webserver->arg("NEWPASSWORD"); - if (COMMANDS::isLocalPasswordValid((char *)newpassword.c_str())) { - err_t err; - if (sUser == DEFAULT_ADMIN_LOGIN) { - err = admin_password->setStringValue((char *)newpassword.c_str()); - } else { - err = user_password->setStringValue((char *)newpassword.c_str()); - } - if (err) { - msg_alert_error = true; - smsg = "Error: Cannot apply changes"; - code = 500; - } - } else { - msg_alert_error=true; - smsg = "Error: Incorrect password"; - code = 500; - } - } - if ((code == 200) || (code == 500)) { - auth_t current_auth_level; - if(sUser == DEFAULT_ADMIN_LOGIN) { - current_auth_level = LEVEL_ADMIN; - } else if(sUser == DEFAULT_USER_LOGIN){ - current_auth_level = LEVEL_USER; - } else { - current_auth_level = LEVEL_GUEST; - } - //create Session - if ((current_auth_level != auth_level) || (auth_level== LEVEL_GUEST)) { - auth_ip * current_auth = new auth_ip; - current_auth->level = current_auth_level; - current_auth->ip=_webserver->client().remoteIP(); - strcpy(current_auth->sessionID,create_session_ID()); - strcpy(current_auth->userID,sUser.c_str()); - current_auth->last_time=millis(); - if (AddAuthIP(current_auth)) { - String tmps ="ESPSESSIONID="; - tmps+=current_auth->sessionID; - _webserver->sendHeader("Set-Cookie",tmps); - _webserver->sendHeader("Cache-Control","no-cache"); - switch(current_auth->level) { - case LEVEL_ADMIN: - auths = "admin"; - break; - case LEVEL_USER: - auths = "user"; - break; - default: - auths = "guest"; - break; - } - } else { - delete current_auth; - msg_alert_error=true; - code = 500; - smsg = "Error: Too many connections"; - } - } - } - if (code == 200) smsg = "Ok"; - - //build JSON - String buffer2send = "{\"status\":\"" + smsg + "\",\"authentication_lvl\":\""; - buffer2send += auths; - buffer2send += "\"}"; - _webserver->send(code, "application/json", buffer2send); - } else { - if (auth_level != LEVEL_GUEST) { - String cookie = _webserver->header("Cookie"); - int pos = cookie.indexOf("ESPSESSIONID="); - String sessionID; - if (pos!= -1) { - int pos2 = cookie.indexOf(";",pos); - sessionID = cookie.substring(pos+strlen("ESPSESSIONID="),pos2); - auth_ip * current_auth_info = GetAuth(_webserver->client().remoteIP(), sessionID.c_str()); - if (current_auth_info != NULL){ - sUser = current_auth_info->userID; - } - } - } - String buffer2send = "{\"status\":\"200\",\"authentication_lvl\":\""; - buffer2send += auths; - buffer2send += "\",\"user\":\""; - buffer2send += sUser; - buffer2send +="\"}"; - _webserver->send(code, "application/json", buffer2send); - } -#else - _webserver->sendHeader("Cache-Control","no-cache"); - _webserver->send(200, "application/json", "{\"status\":\"Ok\",\"authentication_lvl\":\"admin\"}"); -#endif -} -//SPIFFS -//SPIFFS files list and file commands -void Web_Server::handleFileList () -{ - auth_t auth_level = is_authenticated(); - if (auth_level == LEVEL_GUEST) { - _upload_status = UPLOAD_STATUS_NONE; - _webserver->send (401, "text/plain", "Authentication failed!\n"); - return; - } - String path ; - String status = "Ok"; - if (_upload_status == UPLOAD_STATUS_FAILED) { - status = "Upload failed"; - _upload_status = UPLOAD_STATUS_NONE; - } - _upload_status = UPLOAD_STATUS_NONE; - //be sure root is correct according authentication - if (auth_level == LEVEL_ADMIN) { - path = "/"; - } else { - path = "/user"; - } - //get current path - if (_webserver->hasArg ("path") ) { - path += _webserver->arg ("path") ; - } - //to have a clean path - path.trim(); - path.replace ("//", "/"); - if (path[path.length() - 1] != '/') { - path += "/"; - } - //check if query need some action - if (_webserver->hasArg ("action") ) { - //delete a file - if (_webserver->arg ("action") == "delete" && _webserver->hasArg ("filename") ) { - String filename; - String shortname = _webserver->arg ("filename"); - shortname.replace ("/", ""); - filename = path + _webserver->arg ("filename"); - filename.replace ("//", "/"); - if (!SPIFFS.exists (filename) ) { - status = shortname + " does not exists!"; - } else { - if (SPIFFS.remove (filename) ) { - status = shortname + " deleted"; - //what happen if no "/." and no other subfiles ? - String ptmp = path; - if ( (path != "/") && (path[path.length() - 1] = '/') ) { - ptmp = path.substring (0, path.length() - 1); - } - File dir = SPIFFS.open (ptmp); - File dircontent = dir.openNextFile(); - if (!dircontent) { - //keep directory alive even empty - File r = SPIFFS.open (path + "/.", FILE_WRITE); - if (r) { - r.close(); - } - } - } else { - status = "Cannot deleted " ; - status += shortname ; - } - } - } - //delete a directory - if (_webserver->arg ("action") == "deletedir" && _webserver->hasArg ("filename") ) { - String filename; - String shortname = _webserver->arg ("filename"); - shortname.replace ("/", ""); - filename = path + _webserver->arg ("filename"); - filename += "/"; - filename.replace ("//", "/"); - if (filename != "/") { - bool delete_error = false; - File dir = SPIFFS.open (path + shortname); - { - File file2deleted = dir.openNextFile(); - while (file2deleted) { - String fullpath = file2deleted.name(); - if (!SPIFFS.remove (fullpath) ) { - delete_error = true; - status = "Cannot deleted " ; - status += fullpath; - } - file2deleted = dir.openNextFile(); - } - } - if (!delete_error) { - status = shortname ; - status += " deleted"; - } - } - } - //create a directory - if (_webserver->arg ("action") == "createdir" && _webserver->hasArg ("filename") ) { - String filename; - filename = path + _webserver->arg ("filename") + "/."; - String shortname = _webserver->arg ("filename"); - shortname.replace ("/", ""); - filename.replace ("//", "/"); - if (SPIFFS.exists (filename) ) { - status = shortname + " already exists!"; - } else { - File r = SPIFFS.open (filename, FILE_WRITE); - if (!r) { - status = "Cannot create "; - status += shortname ; - } else { - r.close(); - status = shortname + " created"; - } - } - } - } - String jsonfile = "{"; - String ptmp = path; - if ( (path != "/") && (path[path.length() - 1] = '/') ) { - ptmp = path.substring (0, path.length() - 1); - } - File dir = SPIFFS.open (ptmp); - jsonfile += "\"files\":["; - bool firstentry = true; - String subdirlist = ""; - File fileparsed = dir.openNextFile(); - while (fileparsed) { - String filename = fileparsed.name(); - String size = ""; - bool addtolist = true; - //remove path from name - filename = filename.substring (path.length(), filename.length() ); - //check if file or subfile - if (filename.indexOf ("/") > -1) { - //Do not rely on "/." to define directory as SPIFFS upload won't create it but directly files - //and no need to overload SPIFFS if not necessary to create "/." if no need - //it will reduce SPIFFS available space so limit it to creation - filename = filename.substring (0, filename.indexOf ("/") ); - String tag = "*"; - tag += filename + "*"; - if (subdirlist.indexOf (tag) > -1 || filename.length() == 0) { //already in list - addtolist = false; //no need to add - } else { - size = "-1"; //it is subfile so display only directory, size will be -1 to describe it is directory - if (subdirlist.length() == 0) { - subdirlist += "*"; - } - subdirlist += filename + "*"; //add to list - } - } else { - //do not add "." file - if (! ( (filename == ".") || (filename == "") ) ) { - size = ESPResponseStream::formatBytes (fileparsed.size() ); - } else { - addtolist = false; - } - } - if (addtolist) { - if (!firstentry) { - jsonfile += ","; - } else { - firstentry = false; - } - jsonfile += "{"; - jsonfile += "\"name\":\""; - jsonfile += filename; - jsonfile += "\",\"size\":\""; - jsonfile += size; - jsonfile += "\""; - jsonfile += "}"; - } - fileparsed = dir.openNextFile(); - } - jsonfile += "],"; - jsonfile += "\"path\":\"" + path + "\","; - jsonfile += "\"status\":\"" + status + "\","; - size_t totalBytes; - size_t usedBytes; - totalBytes = SPIFFS.totalBytes(); - usedBytes = SPIFFS.usedBytes(); - jsonfile += "\"total\":\"" + ESPResponseStream::formatBytes (totalBytes) + "\","; - jsonfile += "\"used\":\"" + ESPResponseStream::formatBytes (usedBytes) + "\","; - jsonfile.concat (F ("\"occupation\":\"") ); - jsonfile += String (100 * usedBytes / totalBytes); - jsonfile += "\""; - jsonfile += "}"; - path = ""; - _webserver->sendHeader("Cache-Control", "no-cache"); - _webserver->send(200, "application/json", jsonfile); - _upload_status = UPLOAD_STATUS_NONE; -} - -//push error code and message to websocket -void Web_Server::pushError(int code, const char * st, bool web_error, uint16_t timeout){ - if (_socket_server && st) { - String s = "ERROR:" + String(code) + ":"; - s+=st; - _socket_server->sendTXT(_id_connection, s); - if (web_error != 0) { - if (_webserver) { - if (_webserver->client().available() > 0) { - _webserver->send (web_error, "text/xml", st); - } - } - } - uint32_t t = millis(); - while (millis() - t < timeout) { - _socket_server->loop(); - delay(10); - } - } -} - -//abort reception of packages -void Web_Server::cancelUpload(){ - if (_webserver) { - if (_webserver->client().available() > 0) { - HTTPUpload& upload = _webserver->upload(); - upload.status = UPLOAD_FILE_ABORTED; - errno = ECONNABORTED; - _webserver->client().stop(); - delay(100); - } - } -} - -//SPIFFS files uploader handle -void Web_Server::SPIFFSFileupload () -{ - static String filename; - static File fsUploadFile = (File)0; - //get authentication status - auth_t auth_level= is_authenticated(); - //Guest cannot upload - only admin - if (auth_level == LEVEL_GUEST) { - _upload_status = UPLOAD_STATUS_FAILED; - grbl_send(CLIENT_ALL,"[MSG:Upload rejected]\r\n"); - pushError(ESP_ERROR_AUTHENTICATION, "Upload rejected", 401); - } else { - HTTPUpload& upload = _webserver->upload(); - if((_upload_status != UPLOAD_STATUS_FAILED)|| (upload.status == UPLOAD_FILE_START)){ - //Upload start - //************** - if(upload.status == UPLOAD_FILE_START) { - _upload_status= UPLOAD_STATUS_ONGOING; - String upload_filename = upload.filename; - if (upload_filename[0] != '/') filename = "/" + upload_filename; - else filename = upload.filename; - //according User or Admin the root is different as user is isolate to /user when admin has full access - if(auth_level != LEVEL_ADMIN) { - upload_filename = filename; - filename = "/user" + upload_filename; - } - - if (SPIFFS.exists (filename) ) { - SPIFFS.remove (filename); - } - if (fsUploadFile ) { - fsUploadFile.close(); - } - String sizeargname = upload.filename + "S"; - if (_webserver->hasArg (sizeargname.c_str()) ) { - uint32_t filesize = _webserver->arg (sizeargname.c_str()).toInt(); - uint32_t freespace = SPIFFS.totalBytes() - SPIFFS.usedBytes(); - if (filesize > freespace) { - _upload_status=UPLOAD_STATUS_FAILED; - grbl_send(CLIENT_ALL,"[MSG:Upload error]\r\n"); - pushError(ESP_ERROR_NOT_ENOUGH_SPACE, "Upload rejected, not enough space"); - } - - } - if (_upload_status != UPLOAD_STATUS_FAILED) { - //create file - fsUploadFile = SPIFFS.open(filename, FILE_WRITE); - //check If creation succeed - if (fsUploadFile) { - //if yes upload is started - _upload_status= UPLOAD_STATUS_ONGOING; - } else { - //if no set cancel flag - _upload_status=UPLOAD_STATUS_FAILED; - grbl_send(CLIENT_ALL,"[MSG:Upload error]\r\n"); - pushError(ESP_ERROR_FILE_CREATION, "File creation failed"); - } - } - //Upload write - //************** - } else if(upload.status == UPLOAD_FILE_WRITE) { - vTaskDelay(1 / portTICK_RATE_MS); - //check if file is available and no error - if(fsUploadFile && _upload_status == UPLOAD_STATUS_ONGOING) { - //no error so write post date - if (upload.currentSize != fsUploadFile.write(upload.buf, upload.currentSize)) { - _upload_status=UPLOAD_STATUS_FAILED; - grbl_send(CLIENT_ALL,"[MSG:Upload error]\r\n"); - pushError(ESP_ERROR_FILE_WRITE, "File write failed"); - } - } else { - //we have a problem set flag UPLOAD_STATUS_FAILED - _upload_status=UPLOAD_STATUS_FAILED; - grbl_send(CLIENT_ALL,"[MSG:Upload error]\r\n"); - pushError(ESP_ERROR_FILE_WRITE, "File write failed"); - } - //Upload end - //************** - } else if(upload.status == UPLOAD_FILE_END) { - //check if file is still open - if(fsUploadFile) { - //close it - fsUploadFile.close(); - //check size - String sizeargname = upload.filename + "S"; - fsUploadFile = SPIFFS.open (filename, FILE_READ); - uint32_t filesize = fsUploadFile.size(); - fsUploadFile.close(); - if (_webserver->hasArg (sizeargname.c_str()) ) { - if (_webserver->arg (sizeargname.c_str()) != String(filesize)) { - _upload_status = UPLOAD_STATUS_FAILED; - } - } - if (_upload_status == UPLOAD_STATUS_ONGOING) { - _upload_status = UPLOAD_STATUS_SUCCESSFUL; - } else { - grbl_send(CLIENT_ALL,"[MSG:Upload error]\r\n"); - pushError(ESP_ERROR_UPLOAD, "File upload failed"); - } - } else { - //we have a problem set flag UPLOAD_STATUS_FAILED - _upload_status=UPLOAD_STATUS_FAILED; - pushError(ESP_ERROR_FILE_CLOSE, "File close failed"); - grbl_send(CLIENT_ALL,"[MSG:Upload error]\r\n"); - - } - //Upload cancelled - //************** - } else { - _upload_status = UPLOAD_STATUS_FAILED; - //pushError(ESP_ERROR_UPLOAD, "File upload failed"); - return; - } - } - } - - if (_upload_status == UPLOAD_STATUS_FAILED) { - cancelUpload(); - if (SPIFFS.exists (filename) ) { - SPIFFS.remove (filename); - } - } - COMMANDS::wait(0); -} - -//Web Update handler -void Web_Server::handleUpdate () -{ - auth_t auth_level = is_authenticated(); - if (auth_level != LEVEL_ADMIN) { - _upload_status = UPLOAD_STATUS_NONE; - _webserver->send (403, "text/plain", "Not allowed, log in first!\n"); - return; - } - String jsonfile = "{\"status\":\"" ; - jsonfile += String(_upload_status); - jsonfile += "\"}"; - //send status - _webserver->sendHeader("Cache-Control", "no-cache"); - _webserver->send(200, "application/json", jsonfile); - //if success restart - if (_upload_status == UPLOAD_STATUS_SUCCESSFUL) { - COMMANDS::wait(1000); - COMMANDS::restart_ESP(); - } else { - _upload_status = UPLOAD_STATUS_NONE; - } -} - -//File upload for Web update -void Web_Server::WebUpdateUpload () -{ - static size_t last_upload_update; - static uint32_t maxSketchSpace = 0; - //only admin can update FW - if (is_authenticated() != LEVEL_ADMIN) { - _upload_status = UPLOAD_STATUS_FAILED; - grbl_send(CLIENT_ALL,"[MSG:Upload rejected]\r\n"); - pushError(ESP_ERROR_AUTHENTICATION, "Upload rejected", 401); - } else { - //get current file ID - HTTPUpload& upload = _webserver->upload(); - if((_upload_status != UPLOAD_STATUS_FAILED)|| (upload.status == UPLOAD_FILE_START)){ - //Upload start - //************** - if(upload.status == UPLOAD_FILE_START) { - grbl_send(CLIENT_ALL,"[MSG:Update Firmware]\r\n"); - _upload_status= UPLOAD_STATUS_ONGOING; - String sizeargname = upload.filename + "S"; - if (_webserver->hasArg (sizeargname.c_str()) ) { - maxSketchSpace = _webserver->arg (sizeargname).toInt(); - } - //check space - size_t flashsize = 0; - if (esp_ota_get_running_partition()) { - const esp_partition_t* partition = esp_ota_get_next_update_partition(NULL); - if (partition) { - flashsize = partition->size; - } - } - if (flashsize < maxSketchSpace) { - pushError(ESP_ERROR_NOT_ENOUGH_SPACE, "Upload rejected, not enough space"); - _upload_status=UPLOAD_STATUS_FAILED; - grbl_send(CLIENT_ALL,"[MSG:Update cancelled]\r\n"); - } - if (_upload_status != UPLOAD_STATUS_FAILED) { - last_upload_update = 0; - if(!Update.begin()) { //start with max available size - _upload_status=UPLOAD_STATUS_FAILED; - grbl_send(CLIENT_ALL,"[MSG:Update cancelled]\r\n"); - pushError(ESP_ERROR_NOT_ENOUGH_SPACE, "Upload rejected, not enough space"); - } else { - grbl_send(CLIENT_ALL,"\n[MSG:Update 0%]\r\n"); - } - } - //Upload write - //************** - } else if(upload.status == UPLOAD_FILE_WRITE) { - vTaskDelay(1 / portTICK_RATE_MS); - //check if no error - if (_upload_status == UPLOAD_STATUS_ONGOING) { - if ( ((100 * upload.totalSize) / maxSketchSpace) !=last_upload_update) { - if ( maxSketchSpace > 0)last_upload_update = (100 * upload.totalSize) / maxSketchSpace; - else last_upload_update = upload.totalSize; - String s = "Update "; - s+= String(last_upload_update); - s+="%"; - grbl_sendf(CLIENT_ALL,"[MSG:%s]\r\n", s.c_str()); - } - if(Update.write(upload.buf, upload.currentSize) != upload.currentSize) { - _upload_status=UPLOAD_STATUS_FAILED; - grbl_send(CLIENT_ALL,"[MSG:Update write failed]\r\n"); - pushError(ESP_ERROR_FILE_WRITE, "File write failed"); - } - } - //Upload end - //************** - } else if(upload.status == UPLOAD_FILE_END) { - if(Update.end(true)) { //true to set the size to the current progress - //Now Reboot - grbl_send(CLIENT_ALL,"[MSG:Update 100%]\r\n"); - _upload_status=UPLOAD_STATUS_SUCCESSFUL; - } else { - _upload_status=UPLOAD_STATUS_FAILED; - grbl_send(CLIENT_ALL,"[MSG:Update failed]\r\n"); - pushError(ESP_ERROR_UPLOAD, "Update upload failed"); - } - } else if(upload.status == UPLOAD_FILE_ABORTED) { - grbl_send(CLIENT_ALL,"[MSG:Update failed]\r\n"); - _upload_status=UPLOAD_STATUS_FAILED; - return; - } - } - } - if (_upload_status == UPLOAD_STATUS_FAILED) { - cancelUpload(); - Update.end(); - } - COMMANDS::wait(0); -} - - -#ifdef ENABLE_SD_CARD - -//Function to delete not empty directory on SD card -bool Web_Server::deleteRecursive(String path) -{ - bool result = true; - File file = SD.open((char *)path.c_str()); - //failed - if (!file) { - return false; - } - if(!file.isDirectory()) { - file.close(); - //return if success or not - return SD.remove((char *)path.c_str()); - } - file.rewindDirectory(); - while(true) { - File entry = file.openNextFile(); - if (!entry) { - break; - } - String entryPath = entry.name(); - if(entry.isDirectory()) { - entry.close(); - if(!deleteRecursive(entryPath)) { - result = false; - } - } else { - entry.close(); - if (!SD.remove((char *)entryPath.c_str())) { - result = false; - break; - } - } - COMMANDS::wait(0); //wdtFeed - } - file.close(); - if (result) return SD.rmdir((char *)path.c_str()); - else return false; -} - -//direct SD files list////////////////////////////////////////////////// -void Web_Server::handle_direct_SDFileList() -{ - //this is only for admin and user - if (is_authenticated() == LEVEL_GUEST) { - _upload_status=UPLOAD_STATUS_NONE; - _webserver->send(401, "application/json", "{\"status\":\"Authentication failed!\"}"); - return; - } - - String path="/"; - String sstatus="Ok"; - if ((_upload_status == UPLOAD_STATUS_FAILED) || (_upload_status == UPLOAD_STATUS_FAILED)) { - sstatus = "Upload failed"; - _upload_status = UPLOAD_STATUS_NONE; - } - bool list_files = true; - uint64_t totalspace = 0; - uint64_t usedspace = 0; - if (get_sd_state(true) != SDCARD_IDLE) { - _webserver->sendHeader("Cache-Control","no-cache"); - _webserver->send(200, "application/json", "{\"status\":\"No SD Card\"}"); - return; - } - set_sd_state(SDCARD_BUSY_PARSING); - //get current path - if(_webserver->hasArg("path")) { - path += _webserver->arg("path") ; - } - //to have a clean path - path.trim(); - path.replace("//","/"); - if (path[path.length()-1] !='/') { - path +="/"; - } - //check if query need some action - if(_webserver->hasArg("action")) { - //delete a file - if(_webserver->arg("action") == "delete" && _webserver->hasArg("filename")) { - String filename; - String shortname = _webserver->arg("filename"); - filename = path + shortname; - shortname.replace("/",""); - filename.replace("//","/"); - if(!SD.exists((char *)filename.c_str())) { - sstatus = shortname + " does not exist!"; - } else { - if (SD.remove((char *)filename.c_str())) { - sstatus = shortname + " deleted"; - } else { - sstatus = "Cannot deleted " ; - sstatus+=shortname ; - } - } - } - //delete a directory - if( _webserver->arg("action") == "deletedir" && _webserver->hasArg("filename")) { - String filename; - String shortname = _webserver->arg("filename"); - shortname.replace("/",""); - filename = path + "/" + shortname; - filename.replace("//","/"); - if (filename != "/") { - if(!SD.exists((char *)filename.c_str())) { - sstatus = shortname + " does not exist!"; - } else { - if (!deleteRecursive(filename)) { - sstatus ="Error deleting: "; - sstatus += shortname ; - } else { - sstatus = shortname ; - sstatus+=" deleted"; - } - } - } else { - sstatus ="Cannot delete root"; - } - } - //create a directory - if( _webserver->arg("action")=="createdir" && _webserver->hasArg("filename")) { - String filename; - String shortname = _webserver->arg("filename"); - filename = path + shortname; - shortname.replace("/",""); - filename.replace("//","/"); - if(SD.exists((char *)filename.c_str())) { - sstatus = shortname + " already exists!"; - } else { - if (!SD.mkdir((char *)filename.c_str())) { - sstatus = "Cannot create "; - sstatus += shortname ; - } else { - sstatus = shortname + " created"; - } - } - } - } - //check if no need build file list - if( _webserver->hasArg("dontlist")) { - if( _webserver->arg("dontlist") == "yes") { - list_files = false; - } - } - // TODO Settings - consider using the JSONEncoder class - String jsonfile = "{" ; - jsonfile+="\"files\":["; - - if (path!="/")path = path.substring(0,path.length()-1); - if (path!="/" && !SD.exists((char *)path.c_str())) { - - String s = "{\"status\":\" "; - s += path; - s+= " does not exist on SD Card\"}"; - _webserver->send(200, "application/json", s.c_str()); - return; - } - if (list_files) { - File dir = SD.open((char *)path.c_str()); - if (!dir) { - } - if(!dir.isDirectory()) { - dir.close(); - } - dir.rewindDirectory(); - File entry = dir.openNextFile(); - int i = 0; - while(entry) { - COMMANDS::wait (1); - if (i>0) { - jsonfile+=","; - } - jsonfile+="{\"name\":\""; - String tmpname = entry.name(); - int pos = tmpname.lastIndexOf("/"); - tmpname = tmpname.substring(pos+1); - jsonfile+=tmpname; - jsonfile+="\",\"shortname\":\""; //No need here - jsonfile+=tmpname; - jsonfile+="\",\"size\":\""; - if (entry.isDirectory()) { - jsonfile+="-1"; - } else { - // files have sizes, directories do not - jsonfile+=ESPResponseStream::formatBytes(entry.size()); - } - jsonfile+="\",\"datetime\":\""; - //TODO - can be done later - jsonfile+="\"}"; - i++; - entry.close(); - entry = dir.openNextFile(); - } - dir.close(); - } - jsonfile+="],\"path\":\""; - jsonfile+=path + "\","; - jsonfile+="\"total\":\""; - String stotalspace,susedspace; - //SDCard are in GB or MB but no less - totalspace = SD.totalBytes(); - usedspace = SD.usedBytes(); - stotalspace = ESPResponseStream::formatBytes(totalspace); - susedspace = ESPResponseStream::formatBytes(usedspace+1); - - uint32_t occupedspace = 1; - uint32_t usedspace2 = usedspace/(1024*1024); - uint32_t totalspace2 = totalspace/(1024*1024); - occupedspace = (usedspace2 * 100)/totalspace2; - //minimum if even one byte is used is 1% - if ( occupedspace <= 1) { - occupedspace=1; - } - if (totalspace) { - jsonfile+= stotalspace ; - } else { - jsonfile+= "-1"; - } - jsonfile+="\",\"used\":\""; - jsonfile+= susedspace ; - jsonfile+="\",\"occupation\":\""; - if (totalspace) { - jsonfile+= String(occupedspace); - } else { - jsonfile+= "-1"; - } - jsonfile+= "\","; - jsonfile+= "\"mode\":\"direct\","; - jsonfile+= "\"status\":\""; - jsonfile+=sstatus + "\""; - jsonfile+= "}"; - _webserver->sendHeader("Cache-Control","no-cache"); - _webserver->send (200, "application/json", jsonfile.c_str()); - _upload_status=UPLOAD_STATUS_NONE; - set_sd_state(SDCARD_IDLE); -} - -//SD File upload with direct access to SD/////////////////////////////// -void Web_Server::SDFile_direct_upload() -{ - static String filename ; - static File sdUploadFile; - //this is only for admin and user - if (is_authenticated() == LEVEL_GUEST) { - _upload_status=UPLOAD_STATUS_FAILED; - _webserver->send(401, "application/json", "{\"status\":\"Authentication failed!\"}"); - pushError(ESP_ERROR_AUTHENTICATION, "Upload rejected", 401); - } else { - //retrieve current file id - HTTPUpload& upload = _webserver->upload(); - if((_upload_status != UPLOAD_STATUS_FAILED)|| (upload.status == UPLOAD_FILE_START)){ - //Upload start - //************** - if(upload.status == UPLOAD_FILE_START) { - _upload_status= UPLOAD_STATUS_ONGOING; - filename= upload.filename; - //on SD need to add / if not present - if (filename[0]!='/') { - filename= "/"+upload.filename; - } - //check if SD Card is available - if ( get_sd_state(true) != SDCARD_IDLE) { - _upload_status=UPLOAD_STATUS_FAILED; - grbl_send(CLIENT_ALL,"[MSG:Upload cancelled]\r\n"); - pushError(ESP_ERROR_UPLOAD_CANCELLED, "Upload cancelled"); - - } else { - set_sd_state(SDCARD_BUSY_UPLOADING); - //delete file on SD Card if already present - if(SD.exists((char *)filename.c_str())) { - SD.remove((char *)filename.c_str()); - } - String sizeargname = upload.filename + "S"; - if (_webserver->hasArg (sizeargname.c_str()) ) { - uint32_t filesize = _webserver->arg (sizeargname.c_str()).toInt(); - uint64_t freespace = SD.totalBytes() - SD.usedBytes(); - if (filesize > freespace) { - _upload_status=UPLOAD_STATUS_FAILED; - grbl_send(CLIENT_ALL,"[MSG:Upload error]\r\n"); - pushError(ESP_ERROR_NOT_ENOUGH_SPACE, "Upload rejected, not enough space"); - } - - } - if (_upload_status != UPLOAD_STATUS_FAILED){ - //Create file for writing - sdUploadFile = SD.open((char *)filename.c_str(), FILE_WRITE); - //check if creation succeed - if (!sdUploadFile) { - //if creation failed - _upload_status=UPLOAD_STATUS_FAILED; - grbl_send(CLIENT_ALL,"[MSG:Upload failed]\r\n"); - pushError(ESP_ERROR_FILE_CREATION, "File creation failed"); - } - //if creation succeed set flag UPLOAD_STATUS_ONGOING - else { - _upload_status= UPLOAD_STATUS_ONGOING; - } - } - } - //Upload write - //************** - } else if(upload.status == UPLOAD_FILE_WRITE) { - vTaskDelay(1 / portTICK_RATE_MS); - if(sdUploadFile && (_upload_status == UPLOAD_STATUS_ONGOING) && (get_sd_state(false) == SDCARD_BUSY_UPLOADING)) { - //no error write post data - if (upload.currentSize != sdUploadFile.write(upload.buf, upload.currentSize)) { - _upload_status = UPLOAD_STATUS_FAILED; - grbl_send(CLIENT_ALL,"[MSG:Upload failed]\r\n"); - pushError(ESP_ERROR_FILE_WRITE, "File write failed"); - } - } else { //if error set flag UPLOAD_STATUS_FAILED - _upload_status = UPLOAD_STATUS_FAILED; - grbl_send(CLIENT_ALL,"[MSG:Upload failed]\r\n"); - pushError(ESP_ERROR_FILE_WRITE, "File write failed"); - } - //Upload end - //************** - } else if(upload.status == UPLOAD_FILE_END) { - //if file is open close it - if(sdUploadFile) { - sdUploadFile.close(); - //TODO Check size - String sizeargname = upload.filename + "S"; - if (_webserver->hasArg (sizeargname.c_str()) ) { - uint32_t filesize = 0; - sdUploadFile = SD.open (filename.c_str(), FILE_READ); - filesize = sdUploadFile.size(); - sdUploadFile.close(); - if (_webserver->arg (sizeargname.c_str()) != String(filesize)) { - _upload_status = UPLOAD_STATUS_FAILED; - pushError(ESP_ERROR_UPLOAD, "File upload mismatch"); - grbl_send(CLIENT_ALL,"[MSG:Upload failed]\r\n"); - } - } - } else { - _upload_status = UPLOAD_STATUS_FAILED; - grbl_send(CLIENT_ALL,"[MSG:Upload failed]\r\n"); - pushError(ESP_ERROR_FILE_CLOSE, "File close failed"); - } - if (_upload_status == UPLOAD_STATUS_ONGOING) { - _upload_status = UPLOAD_STATUS_SUCCESSFUL; - set_sd_state(SDCARD_IDLE); - } else { - _upload_status = UPLOAD_STATUS_FAILED; - pushError(ESP_ERROR_UPLOAD, "Upload error"); - } - - } else {//Upload cancelled - _upload_status=UPLOAD_STATUS_FAILED; - set_sd_state(SDCARD_IDLE); - grbl_send(CLIENT_ALL,"[MSG:Upload failed]\r\n"); - if(sdUploadFile) { - sdUploadFile.close(); - } - return; - } - } - } - if (_upload_status == UPLOAD_STATUS_FAILED) { - cancelUpload(); - if(sdUploadFile) { - sdUploadFile.close(); - } - if(SD.exists((char *)filename.c_str())) { - SD.remove((char *)filename.c_str()); - } - set_sd_state(SDCARD_IDLE); - } - COMMANDS::wait(0); -} -#endif - -void Web_Server::handle(){ -static uint32_t timeout = millis(); - COMMANDS::wait(0); -#ifdef ENABLE_CAPTIVE_PORTAL - if(WiFi.getMode() == WIFI_AP){ - dnsServer.processNextRequest(); - } -#endif - if (_webserver)_webserver->handleClient(); - if (_socket_server && _setupdone)_socket_server->loop(); - if ((millis() - timeout) > 10000) { - if (_socket_server){ - String s = "PING:"; - s+=String(_id_connection); - _socket_server->broadcastTXT(s); - timeout=millis(); - } - } - -} - - -void Web_Server::handle_Websocket_Event(uint8_t num, uint8_t type, uint8_t * payload, size_t length) { - - switch(type) { - case WStype_DISCONNECTED: - //USE_SERIAL.printf("[%u] Disconnected!\n", num); - break; - case WStype_CONNECTED: - { - IPAddress ip = _socket_server->remoteIP(num); - //USE_SERIAL.printf("[%u] Connected from %d.%d.%d.%d url: %s\n", num, ip[0], ip[1], ip[2], ip[3], payload); - String s = "CURRENT_ID:" + String(num); - // send message to client - _id_connection = num; - _socket_server->sendTXT(_id_connection, s); - s = "ACTIVE_ID:" + String(_id_connection); - _socket_server->broadcastTXT(s); - } - break; - case WStype_TEXT: - //USE_SERIAL.printf("[%u] get Text: %s\n", num, payload); - - // send message to client - // webSocket.sendTXT(num, "message here"); - - // send data to all connected clients - // webSocket.broadcastTXT("message here"); - break; - case WStype_BIN: - //USE_SERIAL.printf("[%u] get binary length: %u\n", num, length); - //hexdump(payload, length); - - // send message to client - // webSocket.sendBIN(num, payload, length); - break; - default: - break; - } - -} - -// The separator that is passed in to this function is always '\n' -// The string that is returned does not contain the separator -// The calling code adds back the separator, unless the string is -// a one-character realtime command. -String Web_Server::get_Splited_Value(String data, char separator, int index) -{ - int found = 0; - int strIndex[] = {0, -1}; - int maxIndex = data.length()-1; - - for(int i=0; i<=maxIndex && found<=index; i++){ - if(data.charAt(i)==separator || i==maxIndex){ - found++; - strIndex[0] = strIndex[1]+1; - strIndex[1] = (i == maxIndex) ? i+1 : i; - } - } - - return found>index ? data.substring(strIndex[0], strIndex[1]) : ""; -} - - -//helper to extract content type from file extension -//Check what is the content tye according extension file -String Web_Server::getContentType (String filename) -{ - String file_name = filename; - file_name.toLowerCase(); - if (filename.endsWith (".htm") ) { - return "text/html"; - } else if (file_name.endsWith (".html") ) { - return "text/html"; - } else if (file_name.endsWith (".css") ) { - return "text/css"; - } else if (file_name.endsWith (".js") ) { - return "application/javascript"; - } else if (file_name.endsWith (".png") ) { - return "image/png"; - } else if (file_name.endsWith (".gif") ) { - return "image/gif"; - } else if (file_name.endsWith (".jpeg") ) { - return "image/jpeg"; - } else if (file_name.endsWith (".jpg") ) { - return "image/jpeg"; - } else if (file_name.endsWith (".ico") ) { - return "image/x-icon"; - } else if (file_name.endsWith (".xml") ) { - return "text/xml"; - } else if (file_name.endsWith (".pdf") ) { - return "application/x-pdf"; - } else if (file_name.endsWith (".zip") ) { - return "application/x-zip"; - } else if (file_name.endsWith (".gz") ) { - return "application/x-gzip"; - } else if (file_name.endsWith (".txt") ) { - return "text/plain"; - } - return "application/octet-stream"; -} - -//check authentification -auth_t Web_Server::is_authenticated() -{ -#ifdef ENABLE_AUTHENTICATION - if (_webserver->hasHeader ("Cookie") ) { - String cookie = _webserver->header ("Cookie"); - int pos = cookie.indexOf ("ESPSESSIONID="); - if (pos != -1) { - int pos2 = cookie.indexOf (";", pos); - String sessionID = cookie.substring (pos + strlen ("ESPSESSIONID="), pos2); - IPAddress ip = _webserver->client().remoteIP(); - //check if cookie can be reset and clean table in same time - return ResetAuthIP (ip, sessionID.c_str() ); - } - } - return LEVEL_GUEST; -#else - return LEVEL_ADMIN; -#endif -} - -#ifdef ENABLE_AUTHENTICATION - -//add the information in the linked list if possible -bool Web_Server::AddAuthIP (auth_ip * item) -{ - if (_nb_ip > MAX_AUTH_IP) { - return false; - } - item->_next = _head; - _head = item; - _nb_ip++; - return true; -} - -//Session ID based on IP and time using 16 char -char * Web_Server::create_session_ID() -{ - static char sessionID[17]; -//reset SESSIONID - for (int i = 0; i < 17; i++) { - sessionID[i] = '\0'; - } -//get time - uint32_t now = millis(); -//get remote IP - IPAddress remoteIP = _webserver->client().remoteIP(); -//generate SESSIONID - if (0 > sprintf (sessionID, "%02X%02X%02X%02X%02X%02X%02X%02X", remoteIP[0], remoteIP[1], remoteIP[2], remoteIP[3], (uint8_t) ( (now >> 0) & 0xff), (uint8_t) ( (now >> 8) & 0xff), (uint8_t) ( (now >> 16) & 0xff), (uint8_t) ( (now >> 24) & 0xff) ) ) { - strcpy (sessionID, "NONE"); - } - return sessionID; -} - - -bool Web_Server::ClearAuthIP (IPAddress ip, const char * sessionID) -{ - auth_ip * current = _head; - auth_ip * previous = NULL; - bool done = false; - while (current) { - if ( (ip == current->ip) && (strcmp (sessionID, current->sessionID) == 0) ) { - //remove - done = true; - if (current == _head) { - _head = current->_next; - _nb_ip--; - delete current; - current = _head; - } else { - previous->_next = current->_next; - _nb_ip--; - delete current; - current = previous->_next; - } - } else { - previous = current; - current = current->_next; - } - } - return done; -} - -//Get info -auth_ip * Web_Server::GetAuth (IPAddress ip, const char * sessionID) -{ - auth_ip * current = _head; - //auth_ip * previous = NULL; - //get time - //uint32_t now = millis(); - while (current) { - if (ip == current->ip) { - if (strcmp (sessionID, current->sessionID) == 0) { - //found - return current; - } - } - //previous = current; - current = current->_next; - } - return NULL; -} - -//Review all IP to reset timers -auth_t Web_Server::ResetAuthIP (IPAddress ip, const char * sessionID) -{ - auth_ip * current = _head; - auth_ip * previous = NULL; - //get time - //uint32_t now = millis(); - while (current) { - if ( (millis() - current->last_time) > 360000) { - //remove - if (current == _head) { - _head = current->_next; - _nb_ip--; - delete current; - current = _head; - } else { - previous->_next = current->_next; - _nb_ip--; - delete current; - current = previous->_next; - } - } else { - if (ip == current->ip) { - if (strcmp (sessionID, current->sessionID) == 0) { - //reset time - current->last_time = millis(); - return (auth_t) current->level; - } - } - previous = current; - current = current->_next; - } - } - return LEVEL_GUEST; -} -#endif - -#endif // Enable HTTP && ENABLE_WIFI - -#endif // ARDUINO_ARCH_ESP32 diff --git a/Grbl_Esp32/wificonfig.h b/Grbl_Esp32/wificonfig.h deleted file mode 100644 index 011b62d0..00000000 --- a/Grbl_Esp32/wificonfig.h +++ /dev/null @@ -1,115 +0,0 @@ -/* - wificonfig.h - wifi functions class - - Copyright (c) 2014 Luc Lebosse. All rights reserved. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -//Preferences entries - -//Notifications -#define ESP_PUSHOVER_NOTIFICATION 1 -#define ESP_EMAIL_NOTIFICATION 2 -#define ESP_LINE_NOTIFICATION 3 - -#define DHCP_MODE 0 -#define STATIC_MODE 1 - -//Switch -#define ESP_SAVE_ONLY 0 -#define ESP_APPLY_NOW 1 - -//defaults values -#define DEFAULT_HOSTNAME "grblesp" -#ifdef CONNECT_TO_SSID - #define DEFAULT_STA_SSID CONNECT_TO_SSID - #define DEFAULT_STA_PWD SSID_PASSWORD -#else //!CONNECT_TO_SSID - #define DEFAULT_STA_SSID "GRBL_ESP" - #define DEFAULT_STA_PWD "12345678" -#endif //CONNECT_TO_SSID -#define DEFAULT_STA_IP "0.0.0.0" -#define DEFAULT_STA_GW "0.0.0.0" -#define DEFAULT_STA_MK "0.0.0.0" -#define DEFAULT_AP_SSID "GRBL_ESP" -#define DEFAULT_AP_PWD "12345678" -#define DEFAULT_AP_IP "192.168.0.1" -#define DEFAULT_AP_MK "255.255.255.0" -#define DEFAULT_AP_CHANNEL 1 -#define DEFAULT_WEBSERVER_PORT 80 -#define DEFAULT_HTTP_STATE 1 -#define DEFAULT_TELNETSERVER_PORT 23 -#define DEFAULT_TELNET_STATE 1 -#define DEFAULT_STA_IP_MODE DHCP_MODE -#define HIDDEN_PASSWORD "********" -#define DEFAULT_TOKEN "" -#define DEFAULT_NOTIFICATION_TYPE 0 - -//boundaries -#define MAX_SSID_LENGTH 32 -#define MIN_SSID_LENGTH 1 -#define MAX_PASSWORD_LENGTH 64 -//min size of password is 0 or upper than 8 char -//so let set min is 8 -#define MIN_PASSWORD_LENGTH 8 -#define MAX_HOSTNAME_LENGTH 32 -#define MIN_HOSTNAME_LENGTH 1 -#define MAX_HTTP_PORT 65001 -#define MIN_HTTP_PORT 1 -#define MAX_TELNET_PORT 65001 -#define MIN_TELNET_PORT 1 -#define MIN_CHANNEL 1 -#define MAX_CHANNEL 14 -#define MIN_NOTIFICATION_TOKEN_LENGTH 0 -#define MAX_NOTIFICATION_TOKEN_LENGTH 63 -#define MAX_NOTIFICATION_SETTING_LENGTH 127 - -#ifndef _WIFI_CONFIG_H -#define _WIFI_CONFIG_H -#include "WiFi.h" - -class WiFiConfig { - public: - WiFiConfig(); - ~WiFiConfig(); - static const char* info(); - static bool isValidIP(const char* string); - static bool isPasswordValid(const char* password); - static bool isSSIDValid(const char* ssid); - static bool isHostnameValid(const char* hostname); - static uint32_t IP_int_from_string(String& s); - static String IP_string_from_int(uint32_t ip_int); - static String Hostname() {return _hostname;} - static char* mac2str(uint8_t mac [8]); - static bool StartAP(); - static bool StartSTA(); - static void StopWiFi(); - static int32_t getSignal(int32_t RSSI); - static void begin(); - static void end(); - static void handle(); - static void reset_settings(); - static bool Is_WiFi_on(); - private : - static bool ConnectSTA2AP(); - static void WiFiEvent(WiFiEvent_t event); - static String _hostname; - static bool _events_registered; -}; - -extern WiFiConfig wifi_config; - -#endif diff --git a/Grbl_Esp32/wifiservices.cpp b/Grbl_Esp32/wifiservices.cpp deleted file mode 100644 index fcc52d34..00000000 --- a/Grbl_Esp32/wifiservices.cpp +++ /dev/null @@ -1,163 +0,0 @@ -/* - wifiservices.cpp - wifi services functions class - - Copyright (c) 2014 Luc Lebosse. All rights reserved. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#ifdef ARDUINO_ARCH_ESP32 - -#include "grbl.h" - -#ifdef ENABLE_WIFI - -#include -#include -#include -#include "wifiservices.h" -#ifdef ENABLE_MDNS - #include -#endif -#ifdef ENABLE_OTA - #include -#endif -#ifdef ENABLE_HTTP - #include "web_server.h" -#endif -#ifdef ENABLE_TELNET - #include "telnet_server.h" -#endif -#ifdef ENABLE_NOTIFICATIONS - #include "notifications_service.h" -#endif -#include "commands.h" - -WiFiServices wifi_services; - -WiFiServices::WiFiServices() { -} -WiFiServices::~WiFiServices() { - end(); -} - -bool WiFiServices::begin() { - bool no_error = true; - //Sanity check - if (WiFi.getMode() == WIFI_OFF) return false; - String h = wifi_hostname->get(); - - //Start SPIFFS - SPIFFS.begin(true); -#ifdef ENABLE_OTA - ArduinoOTA - .onStart([]() { - String type; - if (ArduinoOTA.getCommand() == U_FLASH) - type = "sketch"; - else {// U_SPIFFS - // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end() - type = "filesystem"; - SPIFFS.end(); - } - grbl_sendf(CLIENT_ALL, "[MSG:Start OTA updating %s]\r\n", type.c_str()); - }) - .onEnd([]() { - grbl_sendf(CLIENT_ALL, "[MSG:End OTA]\r\n"); - }) - .onProgress([](unsigned int progress, unsigned int total) { - grbl_sendf(CLIENT_ALL, "[MSG:OTA Progress: %u%%]\r\n", (progress / (total / 100))); - }) - .onError([](ota_error_t error) { - grbl_sendf(CLIENT_ALL, "[MSG:OTA Error(%u):]\r\n", error); - if (error == OTA_AUTH_ERROR) grbl_send(CLIENT_ALL, "[MSG:Auth Failed]\r\n"); - else if (error == OTA_BEGIN_ERROR) grbl_send(CLIENT_ALL, "[MSG:Begin Failed]\r\n"); - else if (error == OTA_CONNECT_ERROR) grbl_send(CLIENT_ALL, "[MSG:Connect Failed]\r\n"); - else if (error == OTA_RECEIVE_ERROR) grbl_send(CLIENT_ALL, "[MSG:Receive Failed]\r\n"); - else if (error == OTA_END_ERROR) grbl_send(CLIENT_ALL, "[MSG:End Failed]\r\n"); - }); - ArduinoOTA.begin(); -#endif -#ifdef ENABLE_MDNS - //no need in AP mode - if (WiFi.getMode() == WIFI_STA) { - //start mDns - if (!MDNS.begin(h.c_str())) { - grbl_send(CLIENT_ALL, "[MSG:Cannot start mDNS]\r\n"); - no_error = false; - } else - grbl_sendf(CLIENT_ALL, "[MSG:Start mDNS with hostname:http://%s.local/]\r\n", h.c_str()); - } -#endif -#ifdef ENABLE_HTTP - web_server.begin(); -#endif -#ifdef ENABLE_TELNET - telnet_server.begin(); -#endif -#ifdef ENABLE_NOTIFICATIONS - notificationsservice.begin(); -#endif - //be sure we are not is mixed mode in setup - WiFi.scanNetworks(true); - return no_error; -} -void WiFiServices::end() { -#ifdef ENABLE_NOTIFICATIONS - notificationsservice.end(); -#endif -#ifdef ENABLE_TELNET - telnet_server.end(); -#endif -#ifdef ENABLE_HTTP - web_server.end(); -#endif - //stop OTA -#ifdef ENABLE_OTA - ArduinoOTA.end(); -#endif - //Stop SPIFFS - SPIFFS.end(); -#ifdef ENABLE_MDNS - //Stop mDNS - MDNS.end(); -#endif -} - -void WiFiServices::handle() { - COMMANDS::wait(0); - //to avoid mixed mode due to scan network - if (WiFi.getMode() == WIFI_AP_STA) { - // In principle it should be sufficient to check for != WIFI_SCAN_RUNNING, - // but that does not work well. Doing so makes scans in AP mode unreliable. - // Sometimes the first try works, but subsequent scans fail. - if (WiFi.scanComplete() >= 0) { - WiFi.enableSTA(false); - } - } -#ifdef ENABLE_OTA - ArduinoOTA.handle(); -#endif -#ifdef ENABLE_HTTP - web_server.handle(); -#endif -#ifdef ENABLE_TELNET - telnet_server.handle(); -#endif -} - -#endif // ENABLE_WIFI - -#endif // ARDUINO_ARCH_ESP32 diff --git a/build-all.ps1 b/build-all.ps1 index 113513d2..3b15bdb0 100644 --- a/build-all.ps1 +++ b/build-all.ps1 @@ -15,7 +15,7 @@ Function BuildMachine($names) { } # Build all the machines -foreach ($filepath in Get-ChildItem -file .\Grbl_Esp32\Machines\*) { +foreach ($filepath in Get-ChildItem -file .\Grbl_Esp32\src\Machines\*) { BuildMachine($filepath.name, "") } diff --git a/build-all.py b/build-all.py index 61ddd443..9b99c229 100755 --- a/build-all.py +++ b/build-all.py @@ -22,7 +22,7 @@ cmd = ['platformio','run'] verbose = '-v' in sys.argv numErrors = 0 -for name in os.listdir('Grbl_Esp32/Machines'): +for name in os.listdir('Grbl_Esp32/src/Machines'): exitCode = buildMachine(name, verbose=verbose) if exitCode != 0: numErrors += 1 diff --git a/build-all.sh b/build-all.sh index f0644acf..da12b6dc 100755 --- a/build-all.sh +++ b/build-all.sh @@ -33,7 +33,7 @@ BuildMachine () { } # Build all the machines -for file in `ls ./Grbl_Esp32/Machines/*`; do +for file in `ls ./Grbl_Esp32/src/Machines/*`; do base=`basename $file` BuildMachine $base "" done