Skip to content

File syn_stepper.c

File List > motor > syn_stepper.c

Go to the documentation of this file

#if __has_include("syn_config.h")
#include "syn_config.h"
#endif

#if !defined(SYN_USE_STEPPER) || SYN_USE_STEPPER

#include "../util/syn_assert.h"
#include "syn_stepper.h"

#include <string.h>

/* ── Helpers ────────────────────────────────────────────────────────────── */

static void set_direction(const SYN_Stepper *s, bool forward)
{
    SYN_GPIO_State lvl = forward ? SYN_GPIO_HIGH : SYN_GPIO_LOW;
    if (s->dir_invert) {
        lvl = (lvl == SYN_GPIO_HIGH) ? SYN_GPIO_LOW : SYN_GPIO_HIGH;
    }
    syn_port_gpio_write(s->dir_pin, lvl);
}

/* ── API ────────────────────────────────────────────────────────────────── */

void syn_stepper_init(SYN_Stepper *s, SYN_GPIO_Pin step_pin, SYN_GPIO_Pin dir_pin)
{
    SYN_ASSERT(s != NULL);
    if (s == NULL) {
        return;
    }

    memset(s, 0, sizeof(*s));
    s->step_pin = step_pin;
    s->dir_pin = dir_pin;
    s->enable_pin = (SYN_GPIO_Pin)-1;
    s->state = (uint8_t)SYN_STEPPER_IDLE;

    syn_port_gpio_write(step_pin, SYN_GPIO_LOW);
    syn_port_gpio_write(dir_pin, SYN_GPIO_LOW);
}

void syn_stepper_set_enable_pin(SYN_Stepper *s, SYN_GPIO_Pin pin, bool active_low)
{
    SYN_ASSERT(s != NULL);
    if (s == NULL) {
        return;
    }
    s->enable_pin = pin;
    s->enable_invert = active_low;
}

void syn_stepper_set_speed(SYN_Stepper *s, uint32_t max_sps, uint32_t accel_sps2)
{
    SYN_ASSERT(s != NULL);
    SYN_ASSERT(max_sps > 0);
    SYN_ASSERT(accel_sps2 > 0);

    if (s == NULL || max_sps == 0 || accel_sps2 == 0) {
        return;
    }

    s->max_speed = max_sps;
    s->accel = accel_sps2;
}

void syn_stepper_move(SYN_Stepper *s, int32_t steps)
{
    SYN_ASSERT(s != NULL);

    if (steps == 0)
        return;

    bool forward = (steps > 0);
    int32_t abs_steps = forward ? steps : -steps;

    set_direction(s, forward);

    s->steps_to_go = abs_steps;
    s->target = s->position + steps;

    /* Compute deceleration start point:
     * For a symmetric trapezoidal profile, decel starts at half the
     * total steps. If max speed can't be reached, it's a triangle. */
    int32_t accel_steps = 0;
    if (s->accel > 0) {
        /* v² = 2 * a * d → d = v² / (2a) */
        accel_steps = (int32_t)(((uint64_t)s->max_speed * s->max_speed) / (2u * s->accel));
        if (accel_steps > abs_steps / 2) {
            accel_steps = abs_steps / 2;
        }
    }
    s->decel_start = abs_steps - accel_steps;

    s->speed = 0;
    s->step_interval = 0;
    s->last_step_tick = syn_port_get_tick_ms();
    s->state = (uint8_t)SYN_STEPPER_ACCEL;
}

void syn_stepper_move_to(SYN_Stepper *s, int32_t position)
{
    syn_stepper_move(s, position - s->position);
}

void syn_stepper_tick(SYN_Stepper *s)
{
    SYN_ASSERT(s != NULL);

    if (s->state == (uint8_t)SYN_STEPPER_IDLE)
        return;

    uint32_t now = syn_port_get_tick_ms();
    uint32_t dt = now - s->last_step_tick;

    /* Compute current speed based on state */
    uint32_t target_speed = 0;

    switch ((SYN_StepperState)s->state) {
    case SYN_STEPPER_ACCEL:
        /* Accelerating — increase speed by accel * dt */
        s->speed += (s->accel * dt) / 1000u;
        if (s->speed >= s->max_speed) {
            s->speed = s->max_speed;
            s->state = (uint8_t)SYN_STEPPER_CRUISE;
        }
        target_speed = s->speed;
        break;

    case SYN_STEPPER_CRUISE:
        target_speed = s->max_speed;
        break;

    case SYN_STEPPER_DECEL:
        if (s->speed > (s->accel * dt) / 1000u) {
            s->speed -= (s->accel * dt) / 1000u;
        } else {
            s->speed = 1; /* minimum speed to finish */
        }
        target_speed = s->speed;
        break;

    case SYN_STEPPER_IDLE:
        return;
    }

    /* Compute step interval from speed */
    if (target_speed == 0)
        target_speed = 1;
    uint32_t interval_ms = 1000u / target_speed;
    if (interval_ms == 0)
        interval_ms = 1;

    /* Time to step? */
    if (dt >= interval_ms) {
        /* Generate step pulse */
        syn_port_gpio_write(s->step_pin, SYN_GPIO_HIGH);
        /* In a real system you'd want a tiny delay here,
         * but for compatibility we just toggle */
        syn_port_gpio_write(s->step_pin, SYN_GPIO_LOW);

        /* Update position */
        if (s->target > s->position) {
            s->position++;
        } else {
            s->position--;
        }
        s->steps_to_go--;
        s->last_step_tick = now;

        /* Check if we should start decelerating */
        if (s->state != (uint8_t)SYN_STEPPER_DECEL &&
            s->steps_to_go <= (s->decel_start > 0
                                   ? (int32_t)(((uint64_t)s->speed * s->speed) / (2u * s->accel))
                                   : 0)) {
            s->state = (uint8_t)SYN_STEPPER_DECEL;
        }

        /* Check if move is complete */
        if (s->steps_to_go <= 0) {
            s->state = (uint8_t)SYN_STEPPER_IDLE;
            s->speed = 0;
        }
    }
}

void syn_stepper_stop(SYN_Stepper *s)
{
    SYN_ASSERT(s != NULL);
    s->state = (uint8_t)SYN_STEPPER_IDLE;
    s->speed = 0;
    s->steps_to_go = 0;
}

void syn_stepper_enable(const SYN_Stepper *s, bool enable)
{
    SYN_ASSERT(s != NULL);
    if (s->enable_pin == (SYN_GPIO_Pin)-1)
        return;

    SYN_GPIO_State lvl = enable ? SYN_GPIO_HIGH : SYN_GPIO_LOW;
    if (s->enable_invert) {
        lvl = (lvl == SYN_GPIO_HIGH) ? SYN_GPIO_LOW : SYN_GPIO_HIGH;
    }
    syn_port_gpio_write(s->enable_pin, lvl);
}

/* ── SYN_MotorOutput adapter ───────────────────────────────────────────── */

static void stepper_output_set(void *ctx, int32_t output)
{
    SYN_Stepper *s = (SYN_Stepper *)ctx;
    (void)output;
    syn_stepper_tick(s);
}

static void stepper_output_coast(void *ctx)
{
    syn_stepper_stop((SYN_Stepper *)ctx);
}

static void stepper_output_brake(void *ctx)
{
    syn_stepper_stop((SYN_Stepper *)ctx);
}

SYN_MotorOutput syn_stepper_output(SYN_Stepper *stepper)
{
    SYN_MotorOutput out = {
        .set_output = stepper_output_set,
        .coast = stepper_output_coast,
        .brake = stepper_output_brake,
        .ctx = stepper,
    };
    return out;
}

#endif /* SYN_USE_STEPPER */