File syn_gcode.c¶
File List > motor > syn_gcode.c
Go to the documentation of this file
#if __has_include("syn_config.h")
#include "syn_config.h"
#endif
#if !defined(SYN_USE_GCODE) || SYN_USE_GCODE
#include "syn_gcode.h"
#include <ctype.h>
#include <math.h>
#include <string.h>
#define SYN_GCODE_DEFAULT_FEEDRATE 100.0f
#define SYN_GCODE_DEFAULT_ACCEL 500.0f
#define SYN_GCODE_DEFAULT_JERK 2000.0f
#define SYN_GCODE_DEFAULT_STEP_RES 0.001f
/* ── Fast ASCII Number Scanners ─────────────────────────────────────────── */
static const char *skip_whitespace(const char *p)
{
while (*p != '\0' && (isspace((unsigned char)*p) || *p == '\r' || *p == '\n')) {
p++;
}
return p;
}
static const char *parse_float_val(const char *p, float *out_val)
{
p = skip_whitespace(p);
float sign = 1.0f;
if (*p == '-') {
sign = -1.0f;
p++;
} else if (*p == '+') {
p++;
}
float int_part = 0.0f;
bool has_digits = false;
while (*p >= '0' && *p <= '9') {
int_part = (int_part * 10.0f) + (float)(*p - '0');
has_digits = true;
p++;
}
float frac_part = 0.0f;
float div = 1.0f;
if (*p == '.') {
p++;
while (*p >= '0' && *p <= '9') {
frac_part = (frac_part * 10.0f) + (float)(*p - '0');
div *= 10.0f;
has_digits = true;
p++;
}
}
if (!has_digits) {
return NULL;
}
*out_val = sign * (int_part + (frac_part / div));
return p;
}
static const char *parse_uint_val(const char *p, uint32_t *out_val)
{
p = skip_whitespace(p);
uint32_t val = 0U;
bool has_digits = false;
while (*p >= '0' && *p <= '9') {
val = (val * 10U) + (uint32_t)(*p - '0');
has_digits = true;
p++;
}
if (!has_digits) {
return NULL;
}
*out_val = val;
return p;
}
/* ── Public API Implementation ───────────────────────────────────────────── */
SYN_Status syn_gcode_init(SYN_GCode_Controller *ctrl, const SYN_GCode_Config *cfg)
{
if (ctrl == NULL) {
return SYN_INVALID_PARAM;
}
(void)memset(ctrl, 0, sizeof(*ctrl));
if (cfg != NULL) {
ctrl->cfg = *cfg;
}
if (ctrl->cfg.default_feedrate <= 0.0f) {
ctrl->cfg.default_feedrate = SYN_GCODE_DEFAULT_FEEDRATE;
}
if (ctrl->cfg.max_acceleration <= 0.0f) {
ctrl->cfg.max_acceleration = SYN_GCODE_DEFAULT_ACCEL;
}
if (ctrl->cfg.max_jerk <= 0.0f) {
ctrl->cfg.max_jerk = SYN_GCODE_DEFAULT_JERK;
}
if (ctrl->cfg.step_resolution <= 0.0f) {
ctrl->cfg.step_resolution = SYN_GCODE_DEFAULT_STEP_RES;
}
ctrl->feedrate = ctrl->cfg.default_feedrate;
ctrl->motion_mode = SYN_GCODE_MOTION_RAPID;
ctrl->dist_mode = SYN_GCODE_DIST_ABSOLUTE;
ctrl->plane = SYN_GCODE_PLANE_XY;
ctrl->units = SYN_GCODE_UNITS_MM;
ctrl->spindle = SYN_GCODE_SPINDLE_OFF;
ctrl->coolant = SYN_GCODE_COOLANT_OFF;
if (ctrl->cfg.interpolator != NULL) {
syn_interpolator_init(ctrl->cfg.interpolator);
}
return SYN_OK;
}
SYN_Status syn_gcode_parse_line(const char *line, SYN_GCode_Block *block)
{
if (line == NULL || block == NULL) {
return SYN_INVALID_PARAM;
}
(void)memset(block, 0, sizeof(*block));
const char *p = line;
while (*p != '\0') {
p = skip_whitespace(p);
if (*p == '\0' || *p == ';') {
break; /* End of line or comment */
}
if (*p == '(') {
/* Skip parenthesized comment */
while (*p != '\0' && *p != ')') {
p++;
}
if (*p == ')') {
p++;
}
continue;
}
char letter = (char)toupper((unsigned char)*p);
p++;
switch (letter) {
case 'G': {
uint32_t val = 0U;
p = parse_uint_val(p, &val);
if (p != NULL) {
block->has_g = true;
block->g_code = val;
}
break;
}
case 'M': {
uint32_t val = 0U;
p = parse_uint_val(p, &val);
if (p != NULL) {
block->has_m = true;
block->m_code = val;
}
break;
}
case 'X':
p = parse_float_val(p, &block->x);
if (p != NULL) {
block->has_x = true;
}
break;
case 'Y':
p = parse_float_val(p, &block->y);
if (p != NULL) {
block->has_y = true;
}
break;
case 'Z':
p = parse_float_val(p, &block->z);
if (p != NULL) {
block->has_z = true;
}
break;
case 'I':
p = parse_float_val(p, &block->i);
if (p != NULL) {
block->has_i = true;
}
break;
case 'J':
p = parse_float_val(p, &block->j);
if (p != NULL) {
block->has_j = true;
}
break;
case 'K':
p = parse_float_val(p, &block->k);
if (p != NULL) {
block->has_k = true;
}
break;
case 'F':
p = parse_float_val(p, &block->f);
if (p != NULL) {
block->has_f = true;
}
break;
case 'S':
p = parse_float_val(p, &block->s);
if (p != NULL) {
block->has_s = true;
}
break;
case 'P':
p = parse_float_val(p, &block->p);
if (p != NULL) {
block->has_p = true;
}
break;
case 'T': {
uint32_t val = 0U;
p = parse_uint_val(p, &val);
if (p != NULL) {
block->has_t = true;
block->t = val;
}
break;
}
default:
/* Skip unknown characters */
break;
}
if (p == NULL) {
return SYN_INVALID_PARAM;
}
}
return SYN_OK;
}
SYN_Status syn_gcode_execute_block(SYN_GCode_Controller *ctrl, const SYN_GCode_Block *block)
{
if (ctrl == NULL || block == NULL) {
return SYN_INVALID_PARAM;
}
/* 1. Process Feedrate & Spindle Speed updates */
if (block->has_f && block->f > 0.0f) {
ctrl->feedrate = block->f;
}
if (block->has_s && block->s >= 0.0f) {
ctrl->spindle_speed = block->s;
}
if (block->has_t) {
ctrl->active_tool = block->t;
if (ctrl->cfg.on_tool != NULL) {
ctrl->cfg.on_tool(block->t, ctrl->cfg.user_data);
}
}
/* 2. Process M-Codes */
if (block->has_m) {
switch (block->m_code) {
case 3: /* Spindle CW */
ctrl->spindle = SYN_GCODE_SPINDLE_CW;
if (ctrl->cfg.on_spindle != NULL) {
ctrl->cfg.on_spindle(SYN_GCODE_SPINDLE_CW, ctrl->spindle_speed,
ctrl->cfg.user_data);
}
break;
case 4: /* Spindle CCW */
ctrl->spindle = SYN_GCODE_SPINDLE_CCW;
if (ctrl->cfg.on_spindle != NULL) {
ctrl->cfg.on_spindle(SYN_GCODE_SPINDLE_CCW, ctrl->spindle_speed,
ctrl->cfg.user_data);
}
break;
case 5: /* Spindle Stop */
ctrl->spindle = SYN_GCODE_SPINDLE_OFF;
if (ctrl->cfg.on_spindle != NULL) {
ctrl->cfg.on_spindle(SYN_GCODE_SPINDLE_OFF, 0.0f, ctrl->cfg.user_data);
}
break;
case 7: /* Coolant Mist */
ctrl->coolant = SYN_GCODE_COOLANT_MIST;
if (ctrl->cfg.on_coolant != NULL) {
ctrl->cfg.on_coolant(SYN_GCODE_COOLANT_MIST, ctrl->cfg.user_data);
}
break;
case 8: /* Coolant Flood */
ctrl->coolant = SYN_GCODE_COOLANT_FLOOD;
if (ctrl->cfg.on_coolant != NULL) {
ctrl->cfg.on_coolant(SYN_GCODE_COOLANT_FLOOD, ctrl->cfg.user_data);
}
break;
case 9: /* Coolant Off */
ctrl->coolant = SYN_GCODE_COOLANT_OFF;
if (ctrl->cfg.on_coolant != NULL) {
ctrl->cfg.on_coolant(SYN_GCODE_COOLANT_OFF, ctrl->cfg.user_data);
}
break;
default:
break;
}
}
/* 3. Process G-Codes (Modal state updates) */
if (block->has_g) {
switch (block->g_code) {
case 0:
ctrl->motion_mode = SYN_GCODE_MOTION_RAPID;
break;
case 1:
ctrl->motion_mode = SYN_GCODE_MOTION_LINEAR;
break;
case 2:
ctrl->motion_mode = SYN_GCODE_MOTION_ARC_CW;
break;
case 3:
ctrl->motion_mode = SYN_GCODE_MOTION_ARC_CCW;
break;
case 4:
ctrl->motion_mode = SYN_GCODE_MOTION_DWELL;
break;
case 17:
ctrl->plane = SYN_GCODE_PLANE_XY;
break;
case 18:
ctrl->plane = SYN_GCODE_PLANE_XZ;
break;
case 19:
ctrl->plane = SYN_GCODE_PLANE_YZ;
break;
case 20:
ctrl->units = SYN_GCODE_UNITS_INCH;
break;
case 21:
ctrl->units = SYN_GCODE_UNITS_MM;
break;
case 90:
ctrl->dist_mode = SYN_GCODE_DIST_ABSOLUTE;
break;
case 91:
ctrl->dist_mode = SYN_GCODE_DIST_INCREMENTAL;
break;
case 92:
/* Coordinate system preset / offset */
if (block->has_x) {
ctrl->offset_pos.x = ctrl->current_pos.x - block->x;
}
if (block->has_y) {
ctrl->offset_pos.y = ctrl->current_pos.y - block->y;
}
if (block->has_z) {
ctrl->offset_pos.z = ctrl->current_pos.z - block->z;
}
return SYN_OK;
default:
break;
}
}
/* 4. Motion Execution */
if (block->has_x || block->has_y || block->has_z || block->has_i || block->has_j ||
block->has_k) {
SYN_Vector3F target = ctrl->current_pos;
if (ctrl->dist_mode == SYN_GCODE_DIST_ABSOLUTE) {
if (block->has_x) {
target.x = block->x + ctrl->offset_pos.x;
}
if (block->has_y) {
target.y = block->y + ctrl->offset_pos.y;
}
if (block->has_z) {
target.z = block->z + ctrl->offset_pos.z;
}
} else {
/* Incremental */
if (block->has_x) {
target.x += block->x;
}
if (block->has_y) {
target.y += block->y;
}
if (block->has_z) {
target.z += block->z;
}
}
if (ctrl->cfg.interpolator != NULL) {
if (ctrl->motion_mode == SYN_GCODE_MOTION_RAPID ||
ctrl->motion_mode == SYN_GCODE_MOTION_LINEAR) {
SYN_Status st = syn_interpolator_plan_linear(
ctrl->cfg.interpolator, ctrl->current_pos, target, ctrl->feedrate,
ctrl->cfg.max_acceleration, ctrl->cfg.max_jerk, ctrl->cfg.step_resolution);
if (st == SYN_OK) {
ctrl->move_in_progress = true;
}
} else if (ctrl->motion_mode == SYN_GCODE_MOTION_ARC_CW ||
ctrl->motion_mode == SYN_GCODE_MOTION_ARC_CCW) {
SYN_Vector3F center = {
.x = block->has_i ? block->i : 0.0f,
.y = block->has_j ? block->j : 0.0f,
.z = block->has_k ? block->k : 0.0f,
};
bool is_cw = (ctrl->motion_mode == SYN_GCODE_MOTION_ARC_CW);
SYN_Status st = syn_interpolator_plan_circular(
ctrl->cfg.interpolator, ctrl->current_pos, target, center, is_cw,
ctrl->feedrate, ctrl->cfg.max_acceleration, ctrl->cfg.max_jerk,
ctrl->cfg.step_resolution);
if (st == SYN_OK) {
ctrl->move_in_progress = true;
}
}
} else {
ctrl->current_pos = target;
}
}
return SYN_OK;
}
SYN_Status syn_gcode_execute_line(SYN_GCode_Controller *ctrl, const char *line)
{
if (ctrl == NULL || line == NULL) {
return SYN_INVALID_PARAM;
}
SYN_GCode_Block block;
SYN_Status st = syn_gcode_parse_line(line, &block);
if (st != SYN_OK) {
return st;
}
return syn_gcode_execute_block(ctrl, &block);
}
bool syn_gcode_step(SYN_GCode_Controller *ctrl, SYN_Vector3F *out_pos)
{
if (ctrl == NULL) {
return false;
}
if (ctrl->cfg.interpolator != NULL && ctrl->move_in_progress) {
bool active = syn_interpolator_step(ctrl->cfg.interpolator, &ctrl->current_pos);
if (out_pos != NULL) {
*out_pos = ctrl->current_pos;
}
if (!active) {
ctrl->move_in_progress = false;
}
return active;
}
if (out_pos != NULL) {
*out_pos = ctrl->current_pos;
}
return false;
}
SYN_PT_Status syn_gcode_task_pt(SYN_PT *pt, SYN_Task *task)
{
if (pt == NULL || task == NULL || task->user_data == NULL) {
return PT_ENDED;
}
SYN_GCode_Controller *ctrl = (SYN_GCode_Controller *)task->user_data;
PT_BEGIN(pt);
for (;;) {
if (ctrl->move_in_progress) {
SYN_Vector3F pos;
(void)syn_gcode_step(ctrl, &pos);
}
PT_YIELD(pt);
}
PT_END(pt); /* LCOV_EXCL_LINE: Infinite task loop end */
}
#endif /* SYN_USE_GCODE */