Skip to content

Commit

Permalink
Implement GCode M66 (Immediate mode only)
Browse files Browse the repository at this point in the history
- Update M modal group enum to reflect LinuxCNC
- Add UserInputs and basic structure for M66 in GCode
- Add numbered paramter 5399 - M66 input read
- GCode value words need to be cleared, debug logs
  • Loading branch information
dymk committed Sep 26, 2024
1 parent 3a11c26 commit ec5ecca
Show file tree
Hide file tree
Showing 18 changed files with 577 additions and 118 deletions.
1 change: 1 addition & 0 deletions FluidNC/src/Error.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,5 @@ const std::map<Error, const char*> ErrorNames = {
{ Error::FlowControlOutOfMemory, "Flow Control Out of Memory" },
{ Error::FlowControlStackOverflow, "Flow Control Stack Overflow" },
{ Error::ParameterAssignmentFailed, "Parameter Assignment Failed" },
{ Error::GcodeValueWordInvalid, "Gcode invalid word value" },
};
1 change: 1 addition & 0 deletions FluidNC/src/Error.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ enum class Error : uint8_t {
FlowControlOutOfMemory = 178,
FlowControlStackOverflow = 179,
ParameterAssignmentFailed = 180,
GcodeValueWordInvalid = 181,
};

const char* errorString(Error errorNumber);
Expand Down
154 changes: 138 additions & 16 deletions FluidNC/src/GCode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include "Protocol.h" // protocol_buffer_synchronize
#include "MotionControl.h" // mc_override_ctrl_update
#include "Machine/UserOutputs.h" // setAnalogPercent
#include "Machine/UserInputs.h" // read digital/analog inputs
#include "Platform.h" // WEAK_LINK
#include "Job.h" // Job::active() and Job::channel()

Expand Down Expand Up @@ -143,6 +144,9 @@ static void gcode_comment_msg(char* comment) {
}
}

static std::optional<WaitOnInputMode> validate_wait_on_input_mode_value(uint8_t);
static Error gc_wait_on_input(bool is_digital, uint8_t input_number, WaitOnInputMode mode, float timeout);

// Edit GCode line in-place, removing whitespace and comments and
// converting to uppercase
void collapseGCode(char* line) {
Expand Down Expand Up @@ -252,16 +256,17 @@ Error gc_execute_line(char* line) {
uint32_t command_words = 0; // Tracks G and M command words. Also used for modal group violations.
uint32_t value_words = 0; // Tracks value words.

bool jogMotion = false;
bool checkMantissa = false;
bool clockwiseArc = false;
bool probeExplicit = false;
bool probeAway = false;
bool probeNoError = false;
bool syncLaser = false;
bool disableLaser = false;
bool laserIsMotion = false;
bool nonmodalG38 = false; // Used for G38.6-9
bool jogMotion = false;
bool checkMantissa = false;
bool clockwiseArc = false;
bool probeExplicit = false;
bool probeAway = false;
bool probeNoError = false;
bool syncLaser = false;
bool disableLaser = false;
bool laserIsMotion = false;
bool nonmodalG38 = false; // Used for G38.6-9
bool isWaitOnInputDigital = false;

auto n_axis = config->_axes->_numberAxis;
float coord_data[MAX_N_AXIS]; // Used by WCO-related commands
Expand Down Expand Up @@ -682,27 +687,31 @@ Error gc_execute_line(char* line) {
break;
case 62:
gc_block.modal.io_control = IoControl::DigitalOnSync;
mg_word_bit = ModalGroup::MM10;
mg_word_bit = ModalGroup::MM5;
break;
case 63:
gc_block.modal.io_control = IoControl::DigitalOffSync;
mg_word_bit = ModalGroup::MM10;
mg_word_bit = ModalGroup::MM5;
break;
case 64:
gc_block.modal.io_control = IoControl::DigitalOnImmediate;
mg_word_bit = ModalGroup::MM10;
mg_word_bit = ModalGroup::MM5;
break;
case 65:
gc_block.modal.io_control = IoControl::DigitalOffImmediate;
mg_word_bit = ModalGroup::MM10;
mg_word_bit = ModalGroup::MM5;
break;
case 66:
gc_block.modal.io_control = IoControl::WaitOnInput;
mg_word_bit = ModalGroup::MM5;
break;
case 67:
gc_block.modal.io_control = IoControl::SetAnalogSync;
mg_word_bit = ModalGroup::MM10;
mg_word_bit = ModalGroup::MM5;
break;
case 68:
gc_block.modal.io_control = IoControl::SetAnalogImmediate;
mg_word_bit = ModalGroup::MM10;
mg_word_bit = ModalGroup::MM5;
break;
default:
FAIL(Error::GcodeUnsupportedCommand); // [Unsupported M command]
Expand Down Expand Up @@ -1002,6 +1011,51 @@ Error gc_execute_line(char* line) {
clear_bitnum(value_words, GCodeWord::E);
clear_bitnum(value_words, GCodeWord::Q);
}
if ((gc_block.modal.io_control == IoControl::WaitOnInput)) {
// M66 P<digital input> L<wait mode type> Q<timeout>
// M66 E<analog input> L<wait mode type> Q<timeout>
// Exactly one of P or E must be present
if (bitnum_is_false(value_words, GCodeWord::P) && bitnum_is_false(value_words, GCodeWord::E)) {
// need at least one of P or E
FAIL(Error::GcodeValueWordMissing);
}
if (bitnum_is_true(value_words, GCodeWord::P) && bitnum_is_true(value_words, GCodeWord::E)) {
// need at most one of P or E
FAIL(Error::GcodeValueWordInvalid);
}
isWaitOnInputDigital = bitnum_is_true(value_words, GCodeWord::P);
clear_bitnum(value_words, GCodeWord::P);
clear_bitnum(value_words, GCodeWord::E);
if (bitnum_is_false(value_words, GCodeWord::L)) {
FAIL(Error::GcodeValueWordMissing);
}
clear_bitnum(value_words, GCodeWord::L);
auto const wait_mode = validate_wait_on_input_mode_value(gc_block.values.l);
if (!wait_mode) {
FAIL(Error::GcodeValueWordInvalid);
}
// Only Immediate mode is valid for analog input
if (!isWaitOnInputDigital && wait_mode != WaitOnInputMode::Immediate) {
FAIL(Error::GcodeValueWordInvalid);
}
// Q is the timeout in seconds (conditionally optional)
// - Ignored if L is 0 (Immediate).
// - Error if value 0 seconds, and L is not 0 (Immediate).
if (bitnum_is_true(value_words, GCodeWord::Q)) {
if (gc_block.values.q != 0.0) {
if (wait_mode != WaitOnInputMode::Immediate) {
// Non-immediate waits must have a non-zero timeout
FAIL(Error::GcodeValueWordInvalid);
}
}
} else {
if (wait_mode != WaitOnInputMode::Immediate) {
// Non-immediate waits must have a timeout
FAIL(Error::GcodeValueWordMissing);
}
}
clear_bitnum(value_words, GCodeWord::Q);
}
if (gc_block.modal.set_tool_number == SetToolNumber::Enable) {
if (bitnum_is_false(value_words, GCodeWord::Q)) {
FAIL(Error::GcodeValueWordMissing);
Expand Down Expand Up @@ -1656,6 +1710,29 @@ Error gc_execute_line(char* line) {
FAIL(Error::PParamMaxExceeded);
}
}
if (gc_block.modal.io_control == IoControl::WaitOnInput) {
auto const validate_input_number = [&](const float input_number) -> std::optional<uint8_t> {
if (input_number < 0) {
return std::nullopt;
}
if (isWaitOnInputDigital) {
if (input_number > MaxUserDigitalPin) {
return std::nullopt;
} else if (input_number > MaxUserAnalogPin) {
return std::nullopt;
}
}
return (uint8_t)input_number;
};
auto const maybe_input_number = validate_input_number(isWaitOnInputDigital ? gc_block.values.p : gc_block.values.e);
if (!maybe_input_number.has_value()) {
FAIL(Error::PParamMaxExceeded);
}
auto const input_number = *maybe_input_number;
auto const wait_mode = *validate_wait_on_input_mode_value(gc_block.values.l);
auto const timeout = gc_block.values.q;
gc_wait_on_input(isWaitOnInputDigital, input_number, wait_mode, timeout);
}

// [9. Override control ]: NOT SUPPORTED. Always enabled, except for parking control.
if (config->_enableParkingOverrideControl) {
Expand Down Expand Up @@ -1917,3 +1994,48 @@ void gc_exec_linef(bool sync_after, Channel& out, const char* format, ...) {
group 10 = {G98, G99} return mode canned cycles
group 13 = {G61.1, G64} path control mode (G61 is supported)
*/

static std::optional<WaitOnInputMode> validate_wait_on_input_mode_value(uint8_t value) {
switch (value) {
case 0:
return WaitOnInputMode::Immediate;
case 1:
return WaitOnInputMode::Rise;
case 2:
return WaitOnInputMode::Fall;
case 3:
return WaitOnInputMode::High;
case 4:
return WaitOnInputMode::Low;
default:
return std::nullopt;
}
}

template <class... Ts>
struct overloaded : Ts... {
using Ts::operator()...;
};
template <class... Ts>
overloaded(Ts...) -> overloaded<Ts...>;

static Error gc_wait_on_input(bool is_digital, uint8_t input_number, WaitOnInputMode mode, float timeout) {
// TODO - only Immediate read mode is supported
if (mode == WaitOnInputMode::Immediate) {
auto const result = is_digital ? config->_userInputs->readDigitalInput(input_number) :
config->_userInputs->readAnalogInput(input_number);
auto const on_ok = [&](bool result) {
log_debug("M66: " << (is_digital ? "digital" : "analog") << "_input" << input_number << " result=" << result);
set_numbered_param(5399, result ? 1.0 : 0.0);
return Error::Ok;
};
auto const on_error = [&](Error error) {
log_error("M66: " << (is_digital ? "digital" : "analog") << "_input" << input_number << " failed");
return error;
};
return std::visit(overloaded { on_ok, on_error }, result);
}

// TODO - implement rest of modes
return Error::GcodeValueWordInvalid;
}
39 changes: 27 additions & 12 deletions FluidNC/src/GCode.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "SpindleDatatypes.h"

#include <cstdint>
#include <optional>

typedef uint16_t gcodenum_t;

Expand All @@ -25,7 +26,9 @@ enum class Override : uint8_t {
// and are similar/identical to other g-code interpreters by manufacturers (Haas,Fanuc,Mazak,etc).
// NOTE: Modal group values must be sequential and starting from zero.

// http://linuxcnc.org/docs/html/gcode/overview.html#gcode:modal-groups
enum class ModalGroup : uint8_t {
// Table 5. G-code Modal Groups
MG0 = 0, // [G4,G10,G28,G28.1,G30,G30.1,G53,G92,G92.1] Non-modal
MG1 = 1, // [G0,G1,G2,G3,G38.2,G38.3,G38.4,G38.5,G80] Motion
MG2 = 2, // [G17,G18,G19] Plane selection
Expand All @@ -37,12 +40,14 @@ enum class ModalGroup : uint8_t {
MG8 = 8, // [G43.1,G49] Tool length offset
MG12 = 9, // [G54,G55,G56,G57,G58,G59] Coordinate system selection
MG13 = 10, // [G61] Control mode
// Table 6. M-code Modal Groups
MM4 = 11, // [M0,M1,M2,M30] Stopping
MM6 = 14, // [M6] [M61] Tool change
MM7 = 12, // [M3,M4,M5] Spindle turning
MM8 = 13, // [M7,M8,M9] Coolant control
MM9 = 14, // [M56] Override control
MM10 = 15, // [M62, M63, M64, M65, M67, M68] User Defined http://linuxcnc.org/docs/html/gcode/overview.html#_modal_groups
MM5 = 12, // [M62,M63,M64,M65,M66,M67,M68] Digital/analog output/input
MM6 = 13, // [M6] [M61] Tool change
MM7 = 14, // [M3,M4,M5] Spindle turning
MM8 = 15, // [M7,M8,M9] Coolant control
MM9 = 16, // [M56] Override control
MM10 = 17, // [M100-M199] User Defined
};

// Command actions for within execution-type modal groups (motion, stopping, non-modal). Used
Expand Down Expand Up @@ -151,15 +156,25 @@ struct CoolantState {
// Modal Group M8: Coolant control
// Modal Group M9: Override control

// Modal Group M10: User I/O control
// Modal Group M5: User I/O control
enum class IoControl : gcodenum_t {
None = 0,
DigitalOnSync = 1, // M62
DigitalOffSync = 2, // M63
DigitalOnImmediate = 3, // M64
DigitalOffImmediate = 4, // M65
SetAnalogSync = 5, // M67
SetAnalogImmediate = 6, // M68
WaitOnInput = 5, // M66
SetAnalogSync = 6, // M67
SetAnalogImmediate = 7, // M68
};

// {M66} L word value, indicates wait mode
enum class WaitOnInputMode : int8_t {
Immediate,
Rise,
Fall,
High,
Low,
};

static const int MaxUserDigitalPin = 8;
Expand Down Expand Up @@ -266,14 +281,14 @@ struct gc_modal_t {
};

struct gc_values_t {
uint8_t e; // M67
uint8_t e; // {M66,M67}
float f; // Feed
float ijk[3]; // I,J,K Axis arc offsets - only 3 are possible
uint8_t l; // G10 or canned cycles parameters
uint8_t l; // {M66,G10}, or canned cycles parameters
int32_t n; // Line number
uint32_t o; // Subroutine identifier - single-meaning word (not used by the core)
float p; // G10 or dwell parameters
float q; // M67
float p; // {M66,G10}, or dwell parameters
float q; // {M66,M67}
float r; // Arc radius
float s; // Spindle speed
uint32_t t; // Tool selection
Expand Down
5 changes: 5 additions & 0 deletions FluidNC/src/Machine/MachineConfig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ namespace Machine {
handler.section("parking", _parking);

handler.section("user_outputs", _userOutputs);
handler.section("user_inputs", _userInputs);

ConfigurableModuleFactory::factory(handler);
ATCs::ATCFactory::factory(handler);
Expand Down Expand Up @@ -104,6 +105,10 @@ namespace Machine {
_userOutputs = new UserOutputs();
}

if (_userInputs == nullptr) {
_userInputs = new UserInputs();
}

if (_sdCard == nullptr) {
_sdCard = new SDCard();
}
Expand Down
30 changes: 16 additions & 14 deletions FluidNC/src/Machine/MachineConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include "I2CBus.h"
#include "I2SOBus.h"
#include "UserOutputs.h"
#include "UserInputs.h"
#include "Macros.h"

#include <string_view>
Expand Down Expand Up @@ -59,20 +60,21 @@ namespace Machine {
public:
MachineConfig() = default;

Axes* _axes = nullptr;
Kinematics* _kinematics = nullptr;
SPIBus* _spi = nullptr;
I2CBus* _i2c[MAX_N_I2C] = { nullptr };
I2SOBus* _i2so = nullptr;
Stepping* _stepping = nullptr;
CoolantControl* _coolant = nullptr;
Probe* _probe = nullptr;
Control* _control = nullptr;
UserOutputs* _userOutputs = nullptr;
SDCard* _sdCard = nullptr;
Macros* _macros = nullptr;
Start* _start = nullptr;
Parking* _parking = nullptr;
Axes* _axes = nullptr;
Kinematics* _kinematics = nullptr;
SPIBus* _spi = nullptr;
I2CBus* _i2c[MAX_N_I2C] = { nullptr };
I2SOBus* _i2so = nullptr;
Stepping* _stepping = nullptr;
CoolantControl* _coolant = nullptr;
Probe* _probe = nullptr;
Control* _control = nullptr;
UserOutputs* _userOutputs = nullptr;
UserInputs* _userInputs = nullptr;
SDCard* _sdCard = nullptr;
Macros* _macros = nullptr;
Start* _start = nullptr;
Parking* _parking = nullptr;

UartChannel* _uart_channels[MAX_N_UARTS] = { nullptr };
Uart* _uarts[MAX_N_UARTS] = { nullptr };
Expand Down
Loading

0 comments on commit ec5ecca

Please sign in to comment.