/*
nuts_bolts.c - Shared functions
Part of grblHAL
Copyright (c) 2017-2024 Terje Io
Copyright (c) 2011-2016 Sungeun K. Jeon for Gnea Research LLC
Copyright (c) 2009-2011 Simen Svale Skogsrud
grblHAL 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.
grblHAL 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 grblHAL. If not, see .
*/
#include
#include
#include
#include
#include
#include "hal.h"
#include "protocol.h"
#include "state_machine.h"
#include "nuts_bolts.h"
#ifndef DWELL_TIME_STEP
#define DWELL_TIME_STEP 50 // Integer (1-255) (milliseconds)
#endif
#define MAX_PRECISION 10
static char buf[STRLEN_COORDVALUE + 1];
static const float froundvalues[MAX_PRECISION + 1] =
{
0.5f, // 0
0.05f, // 1
0.005f, // 2
0.0005f, // 3
0.00005f, // 4
0.000005f, // 5
0.0000005f, // 6
0.00000005f, // 7
0.000000005f, // 8
0.0000000005f, // 9
0.00000000005f // 10
};
#if N_AXIS > 6 && defined(AXIS_REMAP_ABC2UVW)
#error "Illegal remapping of ABC axes!"
#endif
const coord_data_t null_vector = {0};
static const char axis_letters[] = {
AXIS0_LETTER, '\0',
AXIS1_LETTER, '\0',
AXIS2_LETTER, '\0',
#if N_AXIS > 3
AXIS3_LETTER, '\0',
#endif
#if N_AXIS > 4
AXIS4_LETTER, '\0',
#endif
#if N_AXIS > 5
AXIS5_LETTER, '\0',
#endif
#if N_AXIS > 6
AXIS6_LETTER, '\0',
#endif
#if N_AXIS > 7
AXIS7_LETTER, '\0',
#endif
};
char const *const axis_letter[N_AXIS] = {
&axis_letters[0 << 1],
&axis_letters[1 << 1],
&axis_letters[2 << 1],
#if N_AXIS > 3
&axis_letters[3 << 1],
#endif
#if N_AXIS > 4
&axis_letters[4 << 1],
#endif
#if N_AXIS > 5
&axis_letters[5 << 1],
#endif
#if N_AXIS > 6
&axis_letters[6 << 1],
#endif
#if N_AXIS > 7
&axis_letters[7 << 1],
#endif
};
// Converts an uint32 variable to string.
char *uitoa (uint32_t n)
{
char *bptr = buf + sizeof(buf);
*--bptr = '\0';
if (n == 0)
*--bptr = '0';
else while (n) {
*--bptr = '0' + (n % 10);
n /= 10;
}
return bptr;
}
// Convert float to string by immediately converting to integers.
// Number of decimal places, which are tracked by a counter, must be set by the user.
// The integers is then efficiently converted to a string.
char *ftoa (float n, uint8_t decimal_places)
{
bool isNegative;
char *bptr = buf + sizeof(buf);
*--bptr = '\0';
if ((isNegative = n < 0.0f))
n = -n;
n += froundvalues[decimal_places];
uint32_t a = (uint32_t)n;
if (decimal_places) {
n -= (float)a;
uint_fast8_t decimals = decimal_places;
while (decimals >= 2) { // Quickly convert values expected to be E0 to E-4.
n *= 100.0f;
decimals -= 2;
}
if (decimals)
n *= 10.0f;
uint32_t b = (uint32_t)n;
while(decimal_places--) {
if(b) {
*--bptr = (b % 10) + '0'; // Get digit
b /= 10;
} else
*--bptr = '0';
}
}
*--bptr = '.'; // Always add decimal point (TODO: is this really needed?)
if(a == 0)
*--bptr = '0';
else while(a) {
*--bptr = (a % 10) + '0'; // Get digit
a /= 10;
}
if(isNegative)
*--bptr = '-';
return bptr;
}
// Trim trailing zeros and possibly decimal point
char *trim_float (char *s)
{
if(strchr(s, '.')) {
char *s2 = strchr(s, '\0') - 1;
while(*s2 == '0')
*s2-- = '\0';
if(*s2 == '.')
*s2 = '\0';
}
return s;
}
// Extracts an unsigned integer value from a string.
status_code_t read_uint (const char *line, uint_fast8_t *char_counter, uint32_t *uint_ptr)
{
const char *ptr = line + *char_counter;
int_fast8_t exp = 0;
uint_fast8_t ndigit = 0, c;
uint32_t intval = 0;
bool isdecimal = false, ok = false;
// Grab first character and increment pointer. No spaces assumed in line.
c = *ptr++;
if (c == '-')
return Status_NegativeValue;
// Skip initial sign character
if (c == '+')
c = *ptr++;
// Extract number into fast integer. Track decimal in terms of exponent value.
while(c) {
c -= '0';
if (c <= 9) {
ok = true;
if(!isdecimal && (c != 0 || intval))
ndigit++;
if (isdecimal && c != 0)
return Status_GcodeCommandValueNotInteger;
if ((ndigit <= 9 || c <= 4) && intval <= 429496729) {
intval = (((intval << 2) + intval) << 1) + c; // intval * 10 + c
} else if (!isdecimal)
exp++; // Drop overflow digits
} else if (c == (uint_fast8_t)('.' - '0') && !isdecimal)
isdecimal = true;
else
break;
c = *ptr++;
}
// Return if no digits have been read.
if (!ok)
return Status_BadNumberFormat;
*uint_ptr = intval; // Assign value.
*char_counter = ptr - line - 1; // Set char_counter to next statement
return Status_OK;
}
// 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
// available conversion method examples, but has been highly optimized for Grbl. For known
// CNC applications, the typical decimal value is expected to be in the range of E0 to E-4.
// Scientific notation is officially not supported by g-code, and the 'E' character may
// 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().
bool read_float (const char *line, uint_fast8_t *char_counter, float *float_ptr)
{
const char *ptr = line + *char_counter;
int_fast8_t exp = 0;
uint_fast8_t ndigit = 0, c;
uint32_t intval = 0;
bool isnegative, isdecimal = false, ok = false;
// Grab first character and increment pointer. No spaces assumed in line.
c = *ptr++;
// Capture initial sign character
if ((isnegative = (c == '-')) || c == '+')
c = *ptr++;
// Extract number into fast integer. Track decimal in terms of exponent value.
while(c) {
c -= '0';
if (c <= 9) {
ok = true;
if(c != 0 || intval)
ndigit++;
if (ndigit <= MAX_INT_DIGITS) {
if (isdecimal)
exp--;
intval = (((intval << 2) + intval) << 1) + c; // intval * 10 + c
} else if (!isdecimal)
exp++; // Drop overflow digits
} else if (c == (uint_fast8_t)('.' - '0') && !isdecimal)
isdecimal = true;
else
break;
c = *ptr++;
}
// Return if no digits have been read.
if (!ok)
return false;
// Convert integer into floating point.
float fval = (float)intval;
// Apply decimal. Should perform no more than two floating point multiplications for the
// expected range of E0 to E-4.
if (fval != 0.0f) {
while (exp <= -2) {
fval *= 0.01f;
exp += 2;
}
if (exp < 0)
fval *= 0.1f;
else if (exp > 0) do {
fval *= 10.0f;
} while (--exp > 0);
}
// Assign floating point value with correct sign.
*float_ptr = isnegative ? - fval : fval;
*char_counter = ptr - line - 1; // Set char_counter to next statement
return true;
}
// Returns true if float value is a whole number (integer)
bool isintf (float value)
{
return !isnan(value) && fabsf(value - truncf(value)) < 0.001f;
}
// Non-blocking delay function used for general operation and suspend features.
bool delay_sec (float seconds, delaymode_t mode)
{
bool ok = true;
uint_fast16_t i = (uint_fast16_t)ceilf((1000.0f / DWELL_TIME_STEP) * seconds) + 1;
while(--i && ok) {
if(mode == DelayMode_Dwell)
ok = protocol_execute_realtime();
else // DelayMode_SysSuspend, execute rt_system() only to avoid nesting suspend loops.
ok = protocol_exec_rt_system() && !state_door_reopened(); // Bail, if safety door reopens.
if(ok)
hal.delay_ms(DWELL_TIME_STEP, NULL); // Delay DWELL_TIME_STEP increment
}
return ok;
}
float convert_delta_vector_to_unit_vector (float *vector)
{
uint_fast8_t idx = N_AXIS;
float magnitude = 0.0f, inv_magnitude;
do {
if (vector[--idx] != 0.0f)
magnitude += vector[idx] * vector[idx];
} while(idx);
idx = N_AXIS;
magnitude = sqrtf(magnitude);
inv_magnitude = 1.0f / magnitude;
do {
vector[--idx] *= inv_magnitude;
} while(idx);
return magnitude;
}
void rotate (coord_data_t *pt, plane_t plane, float angle /*rad*/)
{
float cos = cosf(angle), sin = sinf(angle),
t0 = pt->values[plane.axis_0] * cos - pt->values[plane.axis_1] * sin,
t1 = pt->values[plane.axis_0] * sin + pt->values[plane.axis_1] * cos;
pt->values[plane.axis_0] = t0;
pt->values[plane.axis_1] = t1;
}
// parse ISO8601 datetime: YYYY-MM-DDTHH:MM:SSZxxx
struct tm *get_datetime (const char *s)
{
static struct tm dt;
PROGMEM static const uint8_t mdays[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
char *s1 = (char *)s, c;
uint_fast16_t idx = 0, value = 0;
memset(&dt, 0, sizeof(struct tm));
dt.tm_year = dt.tm_mon = dt.tm_mday = dt.tm_hour = dt.tm_min = dt.tm_sec = -1;
do {
c = *s1++;
if(isdigit(c))
value = (value * 10) + c - '0';
else if(!(c == '-' || c == ':' || c == 'T' || c == 'Z' || c == '\0'))
break;
else {
switch(idx) {
case 0:
if(c == '-' && value >= 1970 && value <= 2099)
dt.tm_year = value - 1900;
break;
case 1:
if(c == '-' && value >= 1 && value <= 12)
dt.tm_mon = value - 1;
break;
case 2:
if(c == 'T' && value >= 1 && value <= (mdays[dt.tm_mon >= 0 ? dt.tm_mon : 0] + (dt.tm_mon == 1 && dt.tm_year != 100 && (dt.tm_year % 4) == 0 ? 1 : 0)))
dt.tm_mday = value;
break;
case 3:
if(c == ':' && value <= 23)
dt.tm_hour = value;
break;
case 4:
if(c == ':' && value <= 59)
dt.tm_min = value;
break;
case 5:
if((c == 'Z' || c == '\0') && value <= 59)
dt.tm_sec = value;
break;
}
idx++;
value = 0;
}
} while(c);
return (dt.tm_year | dt.tm_mon | dt.tm_mday | dt.tm_hour | dt.tm_min | dt.tm_sec) > 0 ? &dt : NULL;
}
// Remove spaces from and convert string to uppercase (in situ)
char *strcaps (char *s)
{
char c, *s1 = s, *s2 = s;
do {
c = *s1++;
if(c != ' ')
*s2++ = CAPS(c);
} while(c);
if(s1 != s2)
*s2 = '\0';
return s;
}
uint_fast8_t bit_count (uint32_t bits)
{
uint_fast8_t count = 0;
while(bits) {
bits &= (bits - 1);
count++;
}
return count;
}
void dummy_handler (void)
{
// NOOP
}