Skip to content

File syn_servo.c

File List > motor > syn_servo.c

Go to the documentation of this file

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

#if !defined(SYN_USE_SERVO) || SYN_USE_SERVO

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

#include <string.h>

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

static uint16_t angle_to_us(const SYN_Servo *s, uint16_t angle)
{
    if (s == NULL || s->angle_range == 0)
        return 0;
    if (angle > s->angle_range)
        angle = s->angle_range;
    uint32_t range_us = (uint32_t)(s->pulse_max - s->pulse_min);
    return (uint16_t)(s->pulse_min + (range_us * angle) / s->angle_range);
}

static uint16_t clamp_us(const SYN_Servo *s, uint16_t us)
{
    if (s == NULL)
        return us;
    if (us < s->pulse_min)
        return s->pulse_min;
    if (us > s->pulse_max)
        return s->pulse_max;
    return us;
}

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

void syn_servo_init(SYN_Servo *servo, uint16_t pulse_min, uint16_t pulse_max, uint16_t angle_range)
{
    if (servo == NULL || pulse_max <= pulse_min || angle_range == 0) {
        return;
    }
    SYN_ASSERT(servo != NULL);
    SYN_ASSERT(pulse_max > pulse_min);
    SYN_ASSERT(angle_range > 0);

    memset(servo, 0, sizeof(*servo));
    servo->pulse_min = pulse_min;
    servo->pulse_max = pulse_max;
    servo->angle_range = angle_range;

    /* Default to center */
    uint16_t center = (uint16_t)(pulse_min + (pulse_max - pulse_min) / 2);
    servo->current_us = center;
    servo->target_us = center;
}

void syn_servo_set_angle(SYN_Servo *servo, uint16_t angle)
{
    if (servo == NULL)
        return;
    SYN_ASSERT(servo != NULL);
    uint16_t us = angle_to_us(servo, angle);
    servo->current_us = us;
    servo->target_us = us;
    servo->rate = 0;
}

void syn_servo_set_pulse(SYN_Servo *servo, uint16_t us)
{
    if (servo == NULL)
        return;
    SYN_ASSERT(servo != NULL);
    us = clamp_us(servo, us);
    servo->current_us = us;
    servo->target_us = us;
    servo->rate = 0;
}

void syn_servo_move_to(SYN_Servo *servo, uint16_t angle, uint16_t duration)
{
    if (servo == NULL)
        return;
    SYN_ASSERT(servo != NULL);

    uint16_t target = angle_to_us(servo, angle);
    servo->target_us = target;

    if (duration == 0) {
        servo->current_us = target;
        servo->rate = 0;
        return;
    }

    int32_t delta = (int32_t)target - (int32_t)servo->current_us;
    /* Rate in µs per ms (can be negative for reverse movement).
     * Scale up by 256 for fixed-point precision. */
    servo->rate = (delta * 256) / (int32_t)duration;
    if (servo->rate == 0 && delta != 0) {
        servo->rate = (delta > 0) ? 1 : -1;
    }
    servo->last_tick = syn_port_get_tick_ms();
}

void syn_servo_update(SYN_Servo *servo)
{
    SYN_ASSERT(servo != NULL);
    if (servo == NULL) {
        return;
    }

    if (servo->current_us == servo->target_us || servo->rate == 0)
        return;

    uint32_t now = syn_port_get_tick_ms();
    uint32_t dt = now - servo->last_tick;
    if (dt == 0)
        return;

    servo->last_tick = now;

    int32_t delta = (servo->rate * (int32_t)dt) / 256;
    if (delta == 0) {
        delta = (servo->rate > 0) ? 1 : -1;
    }

    int32_t new_us = (int32_t)servo->current_us + delta;

    /* Check if we've reached or passed the target */
    if ((servo->rate > 0 && new_us >= (int32_t)servo->target_us) ||
        (servo->rate < 0 && new_us <= (int32_t)servo->target_us)) {
        servo->current_us = servo->target_us;
        servo->rate = 0;
    } else {
        servo->current_us = clamp_us(servo, (uint16_t)new_us);
    }
}

#endif /* SYN_USE_SERVO */