Skip to content

File sstep_math.h

File List > src > sstep_math.h

Go to the documentation of this file

/*******************************************************************************
 * Stepper-C: Stepper Motor Control Implementation
 *
 * Project:     Stepper-C - Hardware-Agnostic Stepper Motor Control Library
 * Description: Numeric backend: one set of types and operations for fixed point (Q15) and float.
 *
 * Author:      Anton Vynohradov
 * Email:       avynohradov@systemfromscratch.com
 *
 * License:     Apache License 2.0
 *
 * Copyright (c) 2026 Anton Vynohradov
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * SPDX-License-Identifier: Apache-2.0
 ******************************************************************************/


#ifndef SSTEP_MATH_H
#define SSTEP_MATH_H

/* ==============================================================================
 * INCLUDES
 * =============================================================================*/

#include <stdint.h>

#include "sstep_config.h"

#ifdef __cplusplus
extern "C" {
#endif

/* ==============================================================================
 * TYPE DEFINITIONS
 * =============================================================================*/

typedef uint32_t sstep_angle_t;

#if (SSTEP_CFG_MATH_BACKEND == SSTEP_MATH_BACKEND_FIXED)

typedef int16_t sstep_real_t;

typedef int32_t sstep_acc_t;

struct sstep_gain
{
    int16_t mant; 
    int8_t exp;   
};

#define SSTEP_REAL_FRAC_BITS 15

#define SSTEP_REAL_MAX ((sstep_real_t)INT16_MAX)

#define SSTEP_REAL_MIN ((sstep_real_t)INT16_MIN)

#define SSTEP_ACC_MAX ((sstep_acc_t)INT32_MAX)

#define SSTEP_ACC_MIN ((sstep_acc_t)INT32_MIN)

#define SSTEP_ACC_C(x) ((sstep_acc_t)((x) * 32768.0))

#define SSTEP_REAL_ZERO ((sstep_real_t)0)

#define SSTEP_ACC_ZERO ((sstep_acc_t)0)

#define SSTEP_ACC_HALF ((sstep_acc_t)16384)

#define SSTEP_ACC_ONE ((sstep_acc_t)32768)

#define SSTEP_ACC_IN_MAX ((sstep_acc_t)131071)

#define SSTEP_ACC_SCALE_MAX ((sstep_acc_t)65535)

typedef int32_t sstep_integ_t;

#define SSTEP_INTEG_FRAC_BITS 27

#define SSTEP_REAL_C(x)              \
    (((x) >= 1.0) ? SSTEP_REAL_MAX : \
                    (((x) <= -1.0) ? SSTEP_REAL_MIN : (sstep_real_t)((x) * 32768.0)))

#else /* SSTEP_MATH_BACKEND_FLOAT */

typedef float sstep_real_t;

typedef float sstep_acc_t;

struct sstep_gain
{
    float value; 
};

#define SSTEP_REAL_MAX (0.999969482421875f)

#define SSTEP_REAL_MIN (-1.0f)

#define SSTEP_ACC_MAX (65535.99609375f)

#define SSTEP_ACC_MIN (-65536.0f)

#define SSTEP_ACC_C(x) ((sstep_acc_t)(x))

#define SSTEP_REAL_ZERO (0.0f)

#define SSTEP_ACC_ZERO (0.0f)

#define SSTEP_ACC_HALF (0.5f)

#define SSTEP_ACC_ONE (1.0f)

#define SSTEP_ACC_IN_MAX (3.999969482421875f)

#define SSTEP_ACC_SCALE_MAX (1.999969482421875f)

typedef float sstep_integ_t;

#define SSTEP_REAL_C(x) \
    (((x) >= 1.0) ? SSTEP_REAL_MAX : (((x) <= -1.0) ? SSTEP_REAL_MIN : (sstep_real_t)(x)))

#endif /* SSTEP_CFG_MATH_BACKEND */

#define SSTEP_GAIN_EXP_MIN (-16)

#define SSTEP_GAIN_EXP_MAX 15

struct sstep_sincos
{
    sstep_real_t sin; 
    sstep_real_t cos; 
};

/* ==============================================================================
 * FIXED POINT OPERATIONS
 * =============================================================================*/

#if (SSTEP_CFG_MATH_BACKEND == SSTEP_MATH_BACKEND_FIXED)

static inline int32_t sstep_asr(int32_t x, uint32_t n)
{
    int32_t result;
    uint32_t u;
    if (x >= 0)
    {
        u = (uint32_t)x >> n;
        result = (int32_t)u;
    }
    else
    {
        u = (uint32_t)(-(x + 1)) >> n;
        result = -(int32_t)u - 1;
    }
    return result;
}

static inline sstep_real_t sstep_sat(int32_t x)
{
    int32_t r = x;
    if (r > (int32_t)INT16_MAX)
    {
        r = (int32_t)INT16_MAX;
    }
    else if (r < (int32_t)INT16_MIN)
    {
        r = (int32_t)INT16_MIN;
    }
    else
    {
        /* in range */
    }
    return (sstep_real_t)r;
}

static inline sstep_real_t sstep_add(sstep_real_t a, sstep_real_t b)
{
    return sstep_sat((int32_t)a + (int32_t)b);
}

static inline sstep_real_t sstep_sub(sstep_real_t a, sstep_real_t b)
{
    return sstep_sat((int32_t)a - (int32_t)b);
}

static inline sstep_real_t sstep_neg(sstep_real_t a)
{
    return sstep_sat(-(int32_t)a);
}

static inline sstep_real_t sstep_abs(sstep_real_t a)
{
    return sstep_sat((a < 0) ? -(int32_t)a : (int32_t)a);
}

static inline sstep_real_t sstep_mul(sstep_real_t a, sstep_real_t b)
{
    int32_t p = (int32_t)a * (int32_t)b; /* |p| <= 2^30: no overflow */
    return sstep_sat(sstep_asr(p + 0x4000, 15U));
}

static inline sstep_acc_t sstep_acc_from_real(sstep_real_t a)
{
    return (sstep_acc_t)a;
}

static inline sstep_real_t sstep_acc_to_real(sstep_acc_t a)
{
    return sstep_sat(a);
}

static inline sstep_acc_t sstep_acc_add(sstep_acc_t a, sstep_acc_t b)
{
    sstep_acc_t r;
    if ((b > 0) && (a > (INT32_MAX - b)))
    {
        r = INT32_MAX;
    }
    else if ((b < 0) && (a < (INT32_MIN - b)))
    {
        r = (int32_t)INT32_MIN;
    }
    else
    {
        r = a + b;
    }
    return r;
}

static inline sstep_acc_t sstep_acc_clamp(sstep_acc_t x, sstep_acc_t lo, sstep_acc_t hi)
{
    sstep_acc_t r = x;
    if (r > hi)
    {
        r = hi;
    }
    else if (r < lo)
    {
        r = lo;
    }
    else
    {
        /* in range */
    }
    return r;
}

static inline sstep_acc_t sstep_gain_mul(const struct sstep_gain* g, sstep_real_t x)
{
    int32_t p = (int32_t)x * (int32_t)g->mant; /* Q30, |p| <= 2^30 */
    int32_t s = 15 - (int32_t)g->exp;          /* 0 .. 31 */
    uint32_t sh = (uint32_t)s;
    int32_t r;
    if (sh == 0U)
    {
        r = p;
    }
    else
    {
        r = sstep_asr(sstep_asr(p, sh - 1U) + 1, 1U); /* round half up, no overflow */
    }
    return r;
}

static inline sstep_acc_t sstep_gain_mul_int(const struct sstep_gain* g, int32_t n)
{
    int32_t p = (int32_t)sstep_sat(n) * (int32_t)g->mant; /* |p| <= 2^30 */
    int32_t e = (int32_t)g->exp;
    int32_t r;
    if (e >= 0)
    {
        int32_t lim = sstep_asr(INT32_MAX, (uint32_t)e);
        if (p > lim)
        {
            r = INT32_MAX;
        }
        else if (p < -lim)
        {
            r = (int32_t)INT32_MIN;
        }
        else
        {
            uint32_t pw = (uint32_t)1U << (uint32_t)e;
            r = p * (int32_t)pw; /* |p| <= lim: no overflow */
        }
    }
    else
    {
        uint32_t sh = (uint32_t)(-e); /* 1 .. 16 */
        r = sstep_asr(sstep_asr(p, sh - 1U) + 1, 1U);
    }
    return r;
}


static inline int32_t sstep_shift_round(int32_t p, int32_t sh)
{
    int32_t r;
    if (sh > 0)
    {
        uint32_t n = (uint32_t)sh;
        r = sstep_asr(sstep_asr(p, n - 1U) + 1, 1U);
    }
    else if (sh == 0)
    {
        r = p;
    }
    else
    {
        uint32_t n = (uint32_t)(-sh);
        int32_t lim = sstep_asr(INT32_MAX, n);
        if (p > lim)
        {
            r = INT32_MAX;
        }
        else if (p < -lim)
        {
            r = (int32_t)INT32_MIN;
        }
        else
        {
            uint32_t pw = (uint32_t)1U << n;
            r = p * (int32_t)pw; /* |p| <= lim: no overflow */
        }
    }
    return r;
}

static inline sstep_acc_t sstep_acc_abs(sstep_acc_t a)
{
    return (a < 0) ? ((a == INT32_MIN) ? INT32_MAX : -a) : a;
}

static inline sstep_acc_t sstep_acc_halve(sstep_acc_t a)
{
    return sstep_asr(a, 1U);
}

static inline sstep_acc_t sstep_acc_in(sstep_acc_t x)
{
    return sstep_acc_clamp(x, -SSTEP_ACC_IN_MAX, SSTEP_ACC_IN_MAX);
}

static inline sstep_acc_t sstep_gain_mul_acc(const struct sstep_gain* g, sstep_acc_t x)
{
    int32_t p = sstep_asr(sstep_acc_in(x), 2U) * (int32_t)g->mant; /* |p| < 2^30 */
    return sstep_shift_round(p, 13 - (int32_t)g->exp);
}

static inline sstep_integ_t sstep_integ_add(sstep_integ_t integ, const struct sstep_gain* g,
                                            sstep_real_t x)
{
    int32_t p = (int32_t)x * (int32_t)g->mant; /* Q30, |p| <= 2^30 */
    return sstep_acc_add(integ, sstep_shift_round(p, 3 - (int32_t)g->exp));
}

static inline sstep_integ_t sstep_integ_add_acc(sstep_integ_t integ, const struct sstep_gain* g,
                                                sstep_acc_t x)
{
    int32_t p = sstep_asr(sstep_acc_in(x), 2U) * (int32_t)g->mant;
    return sstep_acc_add(integ, sstep_shift_round(p, 1 - (int32_t)g->exp));
}

static inline sstep_acc_t sstep_integ_to_acc(sstep_integ_t integ)
{
    return sstep_shift_round(integ, SSTEP_INTEG_FRAC_BITS - SSTEP_REAL_FRAC_BITS);
}

static inline sstep_real_t sstep_div_unit(sstep_acc_t num, sstep_acc_t den)
{
    uint32_t n = (uint32_t)num;
    uint32_t d = (uint32_t)den;
    uint32_t r;
    while (d >= 65536U) /* at most 16 iterations; keeps n << 15 below 2^31 */
    {
        n >>= 1U;
        d >>= 1U;
    }
    r = ((n << 15U) + (d >> 1U)) / d;
    if (r > 32767U)
    {
        r = 32767U;
    }
    return (sstep_real_t)r;
}

static inline sstep_acc_t sstep_acc_scale(sstep_acc_t x, sstep_real_t k)
{
    int32_t xc = sstep_acc_clamp(x, -SSTEP_ACC_SCALE_MAX, SSTEP_ACC_SCALE_MAX);
    return sstep_shift_round(xc * (int32_t)k, 15); /* |xc * k| < 2^31 */
}

static inline uint16_t sstep_acc_to_ticks(sstep_acc_t duty, uint16_t period)
{
    uint32_t d = (uint32_t)sstep_acc_clamp(duty, 0, SSTEP_ACC_ONE);
    return (uint16_t)(((d * (uint32_t)period) + 16384U) >> 15U);
}

/* ==============================================================================
 * FLOAT OPERATIONS
 * =============================================================================*/

#else /* SSTEP_MATH_BACKEND_FLOAT */

static inline sstep_real_t sstep_sat_f(float x)
{
    float r = x;
    if (r > SSTEP_REAL_MAX)
    {
        r = SSTEP_REAL_MAX;
    }
    else if (r < SSTEP_REAL_MIN)
    {
        r = SSTEP_REAL_MIN;
    }
    else
    {
        /* in range */
    }
    return r;
}

static inline sstep_real_t sstep_add(sstep_real_t a, sstep_real_t b)
{
    return sstep_sat_f(a + b);
}

static inline sstep_real_t sstep_sub(sstep_real_t a, sstep_real_t b)
{
    return sstep_sat_f(a - b);
}

static inline sstep_real_t sstep_neg(sstep_real_t a)
{
    return sstep_sat_f(-a);
}

static inline sstep_real_t sstep_abs(sstep_real_t a)
{
    return sstep_sat_f((a < 0.0f) ? -a : a);
}

static inline sstep_real_t sstep_mul(sstep_real_t a, sstep_real_t b)
{
    return sstep_sat_f(a * b);
}

static inline sstep_acc_t sstep_acc_from_real(sstep_real_t a)
{
    return a;
}

static inline sstep_real_t sstep_acc_to_real(sstep_acc_t a)
{
    return sstep_sat_f(a);
}

static inline sstep_acc_t sstep_acc_clamp(sstep_acc_t x, sstep_acc_t lo, sstep_acc_t hi)
{
    sstep_acc_t r = x;
    if (r > hi)
    {
        r = hi;
    }
    else if (r < lo)
    {
        r = lo;
    }
    else
    {
        /* in range */
    }
    return r;
}

static inline sstep_acc_t sstep_acc_add(sstep_acc_t a, sstep_acc_t b)
{
    return sstep_acc_clamp(a + b, SSTEP_ACC_MIN, SSTEP_ACC_MAX);
}

static inline sstep_acc_t sstep_gain_mul(const struct sstep_gain* g, sstep_real_t x)
{
    return sstep_acc_clamp(g->value * x, SSTEP_ACC_MIN, SSTEP_ACC_MAX);
}

static inline sstep_acc_t sstep_gain_mul_int(const struct sstep_gain* g, int32_t n)
{
    int32_t k = n;
    if (k > (int32_t)INT16_MAX)
    {
        k = (int32_t)INT16_MAX;
    }
    else if (k < (int32_t)INT16_MIN)
    {
        k = (int32_t)INT16_MIN;
    }
    else
    {
        /* in range */
    }
    return sstep_acc_clamp(g->value * (float)k, SSTEP_ACC_MIN, SSTEP_ACC_MAX);
}


static inline sstep_acc_t sstep_acc_abs(sstep_acc_t a)
{
    return (a < 0.0f) ? -a : a;
}

static inline sstep_acc_t sstep_acc_halve(sstep_acc_t a)
{
    return a * 0.5f;
}

static inline sstep_acc_t sstep_acc_in(sstep_acc_t x)
{
    return sstep_acc_clamp(x, -SSTEP_ACC_IN_MAX, SSTEP_ACC_IN_MAX);
}

static inline sstep_acc_t sstep_gain_mul_acc(const struct sstep_gain* g, sstep_acc_t x)
{
    return sstep_acc_clamp(g->value * sstep_acc_in(x), SSTEP_ACC_MIN, SSTEP_ACC_MAX);
}

static inline sstep_integ_t sstep_integ_add(sstep_integ_t integ, const struct sstep_gain* g,
                                            sstep_real_t x)
{
    return sstep_acc_clamp(integ + (g->value * x), -16.0f, 15.999999f);
}

static inline sstep_integ_t sstep_integ_add_acc(sstep_integ_t integ, const struct sstep_gain* g,
                                                sstep_acc_t x)
{
    return sstep_acc_clamp(integ + (g->value * sstep_acc_in(x)), -16.0f, 15.999999f);
}

static inline sstep_acc_t sstep_integ_to_acc(sstep_integ_t integ)
{
    return integ;
}

static inline sstep_real_t sstep_div_unit(sstep_acc_t num, sstep_acc_t den)
{
    return sstep_sat_f(num / den);
}

static inline sstep_acc_t sstep_acc_scale(sstep_acc_t x, sstep_real_t k)
{
    return sstep_acc_clamp(x, -SSTEP_ACC_SCALE_MAX, SSTEP_ACC_SCALE_MAX) * k;
}

static inline uint16_t sstep_acc_to_ticks(sstep_acc_t duty, uint16_t period)
{
    float d = sstep_acc_clamp(duty, 0.0f, 1.0f);
    return (uint16_t)((d * (float)period) + 0.5f);
}

#endif /* SSTEP_CFG_MATH_BACKEND */

/* ==============================================================================
 * COMMON OPERATIONS
 * =============================================================================*/

static inline sstep_real_t sstep_clamp(sstep_real_t x, sstep_real_t lo, sstep_real_t hi)
{
    sstep_real_t r = x;
    if (r > hi)
    {
        r = hi;
    }
    else if (r < lo)
    {
        r = lo;
    }
    else
    {
        /* in range */
    }
    return r;
}

/* ==============================================================================
 * PUBLIC FUNCTION DECLARATIONS
 * =============================================================================*/

struct sstep_sincos sstep_sin_cos(sstep_angle_t angle);

int sstep_gain_from_ratio(int32_t num, int32_t den, struct sstep_gain* gain);

int sstep_gain_from_ratio64(int64_t num, int64_t den, struct sstep_gain* gain);

int sstep_real_from_ratio(int32_t num, int32_t den, sstep_real_t* out);

int32_t sstep_real_to_units(sstep_acc_t x, int32_t base);

#ifdef __cplusplus
}
#endif

#endif /* SSTEP_MATH_H */