diff --git a/LibSidServo/main.c b/LibSidServo/main.c
index 1a392fc..147eb3f 100644
--- a/LibSidServo/main.c
+++ b/LibSidServo/main.c
@@ -16,28 +16,82 @@
* along with this program. If not, see .
*/
+/*
+ * main functions to fill struct `mount_t`
+ */
+
#include
+#include
+#include
#include
#include
-#include "dbg.h"
+#include "main.h"
+#include "movingmodel.h"
#include "serial.h"
#include "ssii.h"
conf_t Conf = {0};
-
+// parameters for model
+static movemodel_t *Xmodel, *Ymodel;
+// radians, rad/sec, rad/sec^2
+static limits_t
+ Xlimits = {
+ .min = {.coord = -3.1241, .speed = 1e-8, .accel = 1e-6},
+ .max = {.coord = 3.1241, .speed = MCC_MAX_X_SPEED, .accel = MCC_X_ACCELERATION}},
+ Ylimits = {
+ .min = {.coord = -3.1241, .speed = 1e-8, .accel = 1e-6},
+ .max = {.coord = 3.1241, .speed = MCC_MAX_Y_SPEED, .accel = MCC_Y_ACCELERATION}}
+;
static mcc_errcodes_t shortcmd(short_command_t *cmd);
+/**
+ * @brief nanotime - monotonic time from first run
+ * @return time in seconds
+ */
+double nanotime(){
+ static struct timespec *start = NULL;
+ struct timespec now;
+ if(!start){
+ start = malloc(sizeof(struct timespec));
+ if(!start) return -1.;
+ if(clock_gettime(CLOCK_MONOTONIC, start)) return -1.;
+ }
+ if(clock_gettime(CLOCK_MONOTONIC, &now)) return -1.;
+ double nd = ((double)now.tv_nsec - (double)start->tv_nsec) * 1e-9;
+ double sd = (double)now.tv_sec - (double)start->tv_sec;
+ return sd + nd;
+}
+
/**
* @brief quit - close all opened and return to default state
*/
static void quit(){
- DBG("Close serial devices");
+ if(Conf.RunModel) return;
for(int i = 0; i < 10; ++i) if(SSstop(TRUE)) break;
+ DBG("Close all serial devices");
closeSerial();
DBG("Exit");
}
+void getModData(mountdata_t *mountdata){
+ if(!mountdata || !Xmodel || !Ymodel) return;
+ double tnow = nanotime();
+ moveparam_t Xp, Yp;
+ movestate_t Xst = Xmodel->get_state(&Xp);
+ if(Xst == ST_MOVE) Xst = Xmodel->proc_move(&Xp, tnow);
+ movestate_t Yst = Ymodel->get_state(&Yp);
+ if(Yst == ST_MOVE) Yst = Ymodel->proc_move(&Yp, tnow);
+ bzero(mountdata, sizeof(mountdata_t));
+ mountdata->motXposition.t = mountdata->encXposition.t = mountdata->motYposition.t = mountdata->encYposition.t = tnow;
+ mountdata->motXposition.val = mountdata->encXposition.val = Xp.coord;
+ mountdata->motYposition.val = mountdata->encYposition.val = Yp.coord;
+ mountdata->encXspeed.t = mountdata->encYspeed.t = tnow;
+ mountdata->encXspeed.val = Xp.speed;
+ mountdata->encYspeed.val = Yp.speed;
+ mountdata->millis = (uint32_t)(tnow * 1e3);
+}
+
/**
* @brief init - open serial devices and do other job
* @param c - initial configuration
@@ -48,6 +102,12 @@ static mcc_errcodes_t init(conf_t *c){
if(!c) return MCC_E_BADFORMAT;
Conf = *c;
mcc_errcodes_t ret = MCC_E_OK;
+ Xmodel = model_init(&Xlimits);
+ Ymodel = model_init(&Ylimits);
+ if(Conf.RunModel){
+ if(!Xmodel || !Ymodel) return MCC_E_FAILED;
+ return MCC_E_OK;
+ }
if(!Conf.MountDevPath || Conf.MountDevSpeed < 1200){
DBG("Define mount device path and speed");
ret = MCC_E_BADFORMAT;
@@ -102,6 +162,7 @@ static int chkYs(double s){
static mcc_errcodes_t slew2(const coordpair_t *target, slewflags_t flags){
(void)target;
(void)flags;
+ //if(Conf.RunModel) return ... ;
if(MCC_E_OK != updateMotorPos()) return MCC_E_FAILED;
//...
setStat(MNT_SLEWING, MNT_SLEWING);
@@ -119,6 +180,14 @@ static mcc_errcodes_t slew2(const coordpair_t *target, slewflags_t flags){
static mcc_errcodes_t move2(const coordpair_t *target){
if(!target) return MCC_E_BADFORMAT;
if(!chkX(target->X) || !chkY(target->Y)) return MCC_E_BADFORMAT;
+ if(Conf.RunModel){
+ double curt = nanotime();
+ moveparam_t param = {0};
+ param.coord = target->X; param.speed = MCC_MAX_X_SPEED;
+ if(!model_move2(Xmodel, ¶m, curt)) return MCC_E_FAILED;
+ param.coord = target->Y; param.speed = MCC_MAX_Y_SPEED;
+ if(!model_move2(Ymodel, ¶m, curt)) return MCC_E_FAILED;
+ }
if(MCC_E_OK != updateMotorPos()) return MCC_E_FAILED;
short_command_t cmd = {0};
DBG("x,y: %g, %g", target->X, target->Y);
@@ -140,6 +209,7 @@ static mcc_errcodes_t move2(const coordpair_t *target){
*/
static mcc_errcodes_t setspeed(const coordpair_t *tagspeed){
if(!tagspeed || !chkXs(tagspeed->X) || !chkYs(tagspeed->Y)) return MCC_E_BADFORMAT;
+ if(Conf.RunModel) return MCC_E_FAILED;
int32_t spd = X_RS2MOTSPD(tagspeed->X);
if(!SSsetterI(CMD_SPEEDX, spd)) return MCC_E_FAILED;
spd = Y_RS2MOTSPD(tagspeed->Y);
@@ -157,6 +227,14 @@ static mcc_errcodes_t move2s(const coordpair_t *target, const coordpair_t *speed
if(!target || !speed) return MCC_E_BADFORMAT;
if(!chkX(target->X) || !chkY(target->Y)) return MCC_E_BADFORMAT;
if(!chkXs(speed->X) || !chkYs(speed->Y)) return MCC_E_BADFORMAT;
+ if(Conf.RunModel){
+ double curt = nanotime();
+ moveparam_t param = {0};
+ param.coord = target->X; param.speed = speed->X;
+ if(!model_move2(Xmodel, ¶m, curt)) return MCC_E_FAILED;
+ param.coord = target->Y; param.speed = speed->Y;
+ if(!model_move2(Ymodel, ¶m, curt)) return MCC_E_FAILED;
+ }
if(MCC_E_OK != updateMotorPos()) return MCC_E_FAILED;
short_command_t cmd = {0};
cmd.Xmot = target->X;
@@ -174,11 +252,23 @@ static mcc_errcodes_t move2s(const coordpair_t *target, const coordpair_t *speed
* @return errcode
*/
static mcc_errcodes_t emstop(){
+ if(Conf.RunModel){
+ double curt = nanotime();
+ Xmodel->emergency_stop(curt);
+ Ymodel->emergency_stop(curt);
+ return MCC_E_OK;
+ }
if(!SSstop(TRUE)) return MCC_E_FAILED;
return MCC_E_OK;
}
// normal stop
static mcc_errcodes_t stop(){
+ if(Conf.RunModel){
+ double curt = nanotime();
+ Xmodel->stop(curt);
+ Ymodel->stop(curt);
+ return MCC_E_OK;
+ }
if(!SSstop(FALSE)) return MCC_E_FAILED;
return MCC_E_OK;
}
@@ -190,6 +280,7 @@ static mcc_errcodes_t stop(){
*/
static mcc_errcodes_t shortcmd(short_command_t *cmd){
if(!cmd) return MCC_E_BADFORMAT;
+ if(Conf.RunModel) return MCC_E_FAILED;
SSscmd s = {0};
DBG("tag: xmot=%g rad, ymot=%g rad", cmd->Xmot, cmd->Ymot);
s.Xmot = X_RAD2MOT(cmd->Xmot);
@@ -205,12 +296,13 @@ static mcc_errcodes_t shortcmd(short_command_t *cmd){
}
/**
- * @brief shortcmd - send and receive long binary command
+ * @brief longcmd - send and receive long binary command
* @param cmd (io) - command
* @return errcode
*/
static mcc_errcodes_t longcmd(long_command_t *cmd){
if(!cmd) return MCC_E_BADFORMAT;
+ if(Conf.RunModel) return MCC_E_FAILED;
SSlcmd l = {0};
l.Xmot = X_RAD2MOT(cmd->Xmot);
l.Ymot = Y_RAD2MOT(cmd->Ymot);
@@ -226,6 +318,7 @@ static mcc_errcodes_t longcmd(long_command_t *cmd){
static mcc_errcodes_t get_hwconf(hardware_configuration_t *hwConfig){
if(!hwConfig) return MCC_E_BADFORMAT;
+ if(Conf.RunModel) return MCC_E_FAILED;
SSconfig config;
if(!cmdC(&config, FALSE)) return MCC_E_FAILED;
// Convert acceleration (ticks per loop^2 to rad/s^2)
@@ -266,8 +359,8 @@ static mcc_errcodes_t get_hwconf(hardware_configuration_t *hwConfig){
// Copy ticks per revolution
hwConfig->Xsetpr = __bswap_32(config.Xsetpr);
hwConfig->Ysetpr = __bswap_32(config.Ysetpr);
- hwConfig->Xmetpr = __bswap_32(config.Xmetpr);
- hwConfig->Ymetpr = __bswap_32(config.Ymetpr);
+ hwConfig->Xmetpr = __bswap_32(config.Xmetpr) / 4; // as documentation said, real ticks are 4 times less
+ hwConfig->Ymetpr = __bswap_32(config.Ymetpr) / 4;
// Convert slew rates (ticks per loop to rad/s)
hwConfig->Xslewrate = X_MOTSPD2RS(config.Xslewrate);
hwConfig->Yslewrate = Y_MOTSPD2RS(config.Yslewrate);
@@ -290,6 +383,7 @@ static mcc_errcodes_t get_hwconf(hardware_configuration_t *hwConfig){
static mcc_errcodes_t write_hwconf(hardware_configuration_t *hwConfig){
SSconfig config;
+ if(Conf.RunModel) return MCC_E_FAILED;
// Convert acceleration (rad/s^2 to ticks per loop^2)
config.Xconf.accel = X_RS2MOTACC(hwConfig->Xconf.accel);
config.Yconf.accel = Y_RS2MOTACC(hwConfig->Yconf.accel);
@@ -359,5 +453,5 @@ mount_t Mount = {
.longCmd = longcmd,
.getHWconfig = get_hwconf,
.saveHWconfig = write_hwconf,
- .currentT = dtime,
+ .currentT = nanotime,
};
diff --git a/LibSidServo/main.h b/LibSidServo/main.h
new file mode 100644
index 0000000..badc8b9
--- /dev/null
+++ b/LibSidServo/main.h
@@ -0,0 +1,70 @@
+/*
+ * This file is part of the libsidservo project.
+ * Copyright 2025 Edward V. Emelianov .
+ *
+ * This program 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.
+ *
+ * This program 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 this program. If not, see .
+ */
+
+/*
+ * Almost all here used for debug purposes
+ */
+
+#pragma once
+
+#include
+
+#include "sidservo.h"
+
+extern conf_t Conf;
+double nanotime();
+void getModData(mountdata_t *mountdata);
+
+// unused arguments of functions
+#define _U_ __attribute__((__unused__))
+// break absent in `case`
+#define FALLTHRU __attribute__ ((fallthrough))
+// and synonym for FALLTHRU
+#define NOBREAKHERE __attribute__ ((fallthrough))
+// weak functions
+#define WEAK __attribute__ ((weak))
+
+#ifndef DBL_EPSILON
+#define DBL_EPSILON (2.2204460492503131e-16)
+#endif
+
+#ifndef FALSE
+#define FALSE (0)
+#endif
+
+#ifndef TRUE
+#define TRUE (1)
+#endif
+
+
+#ifdef EBUG
+#include
+ #define COLOR_RED "\033[1;31;40m"
+ #define COLOR_GREEN "\033[1;32;40m"
+ #define COLOR_OLD "\033[0;0;0m"
+ #define FNAME() do{ fprintf(stderr, COLOR_GREEN "\n%s " COLOR_OLD, __func__); \
+ fprintf(stderr, "(%s, line %d)\n", __FILE__, __LINE__);} while(0)
+ #define DBG(...) do{ fprintf(stderr, COLOR_RED "\n%s " COLOR_OLD, __func__); \
+ fprintf(stderr, "(%s, line %d): ", __FILE__, __LINE__); \
+ fprintf(stderr, __VA_ARGS__); \
+ fprintf(stderr, "\n");} while(0)
+
+#else // EBUG
+ #define FNAME()
+ #define DBG(...)
+#endif // EBUG
diff --git a/LibSidServo/movingmodel.c b/LibSidServo/movingmodel.c
new file mode 100644
index 0000000..0bfa9c0
--- /dev/null
+++ b/LibSidServo/movingmodel.c
@@ -0,0 +1,63 @@
+/*
+ * This file is part of the moving_model project.
+ * Copyright 2025 Edward V. Emelianov .
+ *
+ * This program 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.
+ *
+ * This program 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 this program. If not, see .
+ */
+
+#include
+#include
+#include
+#include
+
+#include "main.h"
+#include "movingmodel.h"
+#include "ramp.h"
+
+extern movemodel_t trapez;
+
+static void chkminmax(double *min, double *max){
+ if(*min <= *max) return;
+ double t = *min;
+ *min = *max;
+ *max = t;
+}
+
+movemodel_t *model_init(limits_t *l){
+ if(!l) return FALSE;
+ movemodel_t *model = calloc(1, sizeof(movemodel_t));
+ memcpy(model, &trapez, sizeof(movemodel_t));
+ if(!model->init_limits) goto fail;
+ moveparam_t *max = &l->max, *min = &l->min;
+ if(min->speed < 0.) min->speed = -min->speed;
+ if(max->speed < 0.) max->speed = -max->speed;
+ if(min->accel < 0.) min->accel = -min->accel;
+ if(max->accel < 0.) max->accel = -max->accel;
+ chkminmax(&min->coord, &max->coord);
+ chkminmax(&min->speed, &max->speed);
+ chkminmax(&min->accel, &max->accel);
+ if(!model->init_limits(l)) return NULL;
+ return model;
+fail:
+ free(model); return NULL;
+}
+
+int model_move2(movemodel_t *model, moveparam_t *target, double t){
+ if(!target || !model) return FALSE;
+ DBG("MOVE to %g at speed %g", target->coord, target->speed);
+ // only positive velocity
+ if(target->speed < 0.) target->speed = -target->speed;
+ // don't mind about acceleration - user cannot set it now
+ return model->calculate(target, t);
+}
diff --git a/LibSidServo/movingmodel.h b/LibSidServo/movingmodel.h
new file mode 100644
index 0000000..0c0fab3
--- /dev/null
+++ b/LibSidServo/movingmodel.h
@@ -0,0 +1,60 @@
+/*
+ * This file is part of the moving_model project.
+ * Copyright 2025 Edward V. Emelianov .
+ *
+ * This program 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.
+ *
+ * This program 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 this program. If not, see .
+ */
+
+#pragma once
+
+#include "sidservo.h"
+
+// tolerance, time ticks
+#define COORD_TOLERANCE_DEFAULT (0.01)
+#define COORD_TOLERANCE_MIN (0.0001)
+#define COORD_TOLERANCE_MAX (10.)
+#define TIME_TICK_DEFAULT (0.0001)
+#define TIME_TICK_MIN (1e-9)
+#define TIME_TICK_MAX (10.)
+
+typedef enum{
+ ST_STOP, // stopped
+ ST_MOVE, // moving
+ ST_AMOUNT
+} movestate_t;
+
+typedef struct{
+ double coord;
+ double speed;
+ double accel;
+} moveparam_t;
+
+typedef struct{
+ moveparam_t min;
+ moveparam_t max;
+ double acceleration;
+} limits_t;
+
+typedef struct{
+ int (*init_limits)(limits_t *lim); // init values of limits, jerk
+ int (*calculate)(moveparam_t *target, double t); // calculate stages of traectory beginning from t
+ movestate_t (*proc_move)(moveparam_t *next, double t); // calculate next model point for time t
+ movestate_t (*get_state)(moveparam_t *cur); // get current moving state
+ void (*stop)(double t); // stop by ramp
+ void (*emergency_stop)(double t); // stop with highest acceleration
+ double (*stoppenanotime)(); // time when moving will ends
+} movemodel_t;
+
+movemodel_t *model_init(limits_t *l);
+int model_move2(movemodel_t *model, moveparam_t *target, double t);
diff --git a/LibSidServo/ramp.c b/LibSidServo/ramp.c
new file mode 100644
index 0000000..efa209c
--- /dev/null
+++ b/LibSidServo/ramp.c
@@ -0,0 +1,220 @@
+/*
+ * This file is part of the moving_model project.
+ * Copyright 2025 Edward V. Emelianov .
+ *
+ * This program 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.
+ *
+ * This program 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 this program. If not, see .
+ */
+
+// simplest trapezioidal ramp
+
+#include
+#include
+
+#include "main.h"
+#include "ramp.h"
+
+static double coord_tolerance = COORD_TOLERANCE_DEFAULT;
+static movestate_t state = ST_STOP;
+static moveparam_t Min, Max;
+
+typedef enum{
+ STAGE_ACCEL, // start from zero speed and accelerate to Max speed
+ STAGE_MAXSPEED, // go with target speed
+ STAGE_DECEL, // go from target speed to zero
+ STAGE_STOPPED, // stop
+ STAGE_AMOUNT
+} movingstage_t;
+
+static movingstage_t movingstage = STAGE_STOPPED;
+static double Times[STAGE_AMOUNT] = {0}; // time when each stage starts
+static moveparam_t Params[STAGE_AMOUNT] = {0}; // starting parameters for each stage
+static moveparam_t curparams = {0}; // current coordinate/speed/acceleration
+
+static int initlims(limits_t *lim){
+ if(!lim) return FALSE;
+ Min = lim->min;
+ Max = lim->max;
+ return TRUE;
+}
+
+static void emstop(double _U_ t){
+ curparams.accel = 0.;
+ curparams.speed = 0.;
+ bzero(Times, sizeof(Times));
+ bzero(Params, sizeof(Params));
+ state = ST_STOP;
+ movingstage = STAGE_STOPPED;
+}
+
+static void stop(double t){
+ if(state == ST_STOP || movingstage == STAGE_STOPPED) return;
+ movingstage = STAGE_DECEL;
+ state = ST_MOVE;
+ Times[STAGE_DECEL] = t;
+ Params[STAGE_DECEL].speed = curparams.speed;
+ if(curparams.speed > 0.) Params[STAGE_DECEL].accel = -Max.accel;
+ else Params[STAGE_DECEL].accel = Max.accel;
+ Params[STAGE_DECEL].coord = curparams.coord;
+ // speed: v=v2+a2(t-t2), v2 and a2 have different signs; t3: v3=0 -> t3=t2-v2/a2
+ Times[STAGE_STOPPED] = t - curparams.speed / Params[STAGE_DECEL].accel;
+ // coordinate: x=x2+v2(t-t2)+a2(t-t2)^2/2 -> x3=x2+v2(t3-t2)+a2(t3-t2)^2/2
+ double dt = Times[STAGE_STOPPED] - t;
+ Params[STAGE_STOPPED].coord = curparams.coord + curparams.speed * dt +
+ Params[STAGE_DECEL].accel * dt * dt / 2.;
+}
+
+/**
+ * @brief calc - moving calculation
+ * @param x - using max speed (>0!!!) and coordinate
+ * @param t - current time value
+ * @return FALSE if can't move with given parameters
+ */
+static int calc(moveparam_t *x, double t){
+ if(!x) return FALSE;
+ if(x->coord < Min.coord || x->coord > Max.coord) return FALSE;
+ if(x->speed < Min.speed || x->speed > Max.speed) return FALSE;
+ double Dx = fabs(x->coord - curparams.coord); // full distance
+ double sign = (x->coord > curparams.coord) ? 1. : -1.; // sign of target accelerations and speeds
+ // we have two variants: with or without stage with constant speed
+ double dt23 = x->speed / Max.accel; // time of deceleration stage for given speed
+ double dx23 = x->speed * dt23 / 2.; // distance on dec stage (abs)
+ DBG("Dx=%g, sign=%g, dt23=%g, dx23=%g", Dx, sign, dt23, dx23);
+ double setspeed = x->speed; // new max speed (we can change it if need)
+ double dt01, dx01; // we'll fill them depending on starting conditions
+ Times[0] = t;
+ Params[0].speed = curparams.speed;
+ Params[0].coord = curparams.coord;
+
+ double curspeed = fabs(curparams.speed);
+ double dt0s = curspeed / Max.accel; // time of stopping phase
+ double dx0s = curspeed * dt0s / 2.; // distance
+ DBG("dt0s=%g, dx0s=%g", dt0s, dx0s);
+ if(dx0s > Dx){
+ DBG("distance too short");
+ return FALSE;
+ }
+ if(fabs(Dx - dx0s) < coord_tolerance){ // just stop and we'll be on target
+ DBG("Distance good to just stop");
+ stop(t);
+ return TRUE;
+ }
+ if(curparams.speed * sign < 0. || state == ST_STOP){ // we should change speed sign
+ // after stop we will have full profile
+ double dxs3 = Dx - dx0s;
+ double newspeed = sqrt(Max.accel * dxs3);
+ if(newspeed < setspeed) setspeed = newspeed; // we can't reach user speed
+ DBG("dxs3=%g, setspeed=%g", dxs3, setspeed);
+ dt01 = fabs(sign*setspeed - curparams.speed) / Max.accel;
+ Params[0].accel = sign * Max.accel;
+ if(state == ST_STOP) dx01 = setspeed * dt01 / 2.;
+ else dx01 = dt01 * (dt01 / 2. * Max.accel - curspeed);
+ DBG("dx01=%g, dt01=%g", dx01, dt01);
+ }else{ // increase or decrease speed without stopping phase
+ dt01 = fabs(sign*setspeed - curparams.speed) / Max.accel;
+ double a = sign * Max.accel;
+ if(sign * curparams.speed < 0.){DBG("change direction"); a = -a;}
+ else if(curspeed > setspeed){ DBG("lower speed @ this direction"); a = -a;}
+ //double a = (curspeed > setspeed) ? -Max.accel : Max.accel;
+ dx01 = curspeed * dt01 + a * dt01 * dt01 / 2.;
+ DBG("dt01=%g, a=%g, dx01=%g", dt01, a, dx01);
+ if(dx01 + dx23 > Dx){ // calculate max speed
+ setspeed = sqrt(Max.accel * Dx - curspeed * curspeed / 2.);
+ if(setspeed < curspeed){
+ setspeed = curparams.speed;
+ dt01 = 0.; dx01 = 0.;
+ Params[0].accel = 0.;
+ }else{
+ Params[0].accel = a;
+ dt01 = fabs(setspeed - curspeed) / Max.accel;
+ dx01 = curspeed * dt01 + Max.accel * dt01 * dt01 / 2.;
+ }
+ }else Params[0].accel = a;
+ }
+ if(setspeed < Min.speed){
+ DBG("New speed should be too small");
+ return FALSE;
+ }
+ moveparam_t *p = &Params[STAGE_MAXSPEED];
+ p->accel = 0.; p->speed = sign * setspeed;
+ p->coord = curparams.coord + dx01 * sign;
+ Times[STAGE_MAXSPEED] = Times[0] + dt01;
+ dt23 = setspeed / Max.accel;
+ dx23 = setspeed * dt23 / 2.;
+ // calculate dx12 and dt12
+ double dx12 = Dx - dx01 - dx23;
+ if(dx12 < -coord_tolerance){
+ DBG("Oops, WTF dx12=%g?", dx12);
+ return FALSE;
+ }
+ double dt12 = dx12 / setspeed;
+ p = &Params[STAGE_DECEL];
+ p->accel = -sign * Max.accel;
+ p->speed = sign * setspeed;
+ p->coord = Params[STAGE_MAXSPEED].coord + sign * dx12;
+ Times[STAGE_DECEL] = Times[STAGE_MAXSPEED] + dt12;
+ p = &Params[STAGE_STOPPED];
+ p->accel = 0.; p->speed = 0.; p->coord = x->coord;
+ Times[STAGE_STOPPED] = Times[STAGE_DECEL] + dt23;
+ for(int i = 0; i < 4; ++i)
+ DBG("%d: t=%g, coord=%g, speed=%g, accel=%g", i,
+ Times[i], Params[i].coord, Params[i].speed, Params[i].accel);
+ state = ST_MOVE;
+ movingstage = STAGE_ACCEL;
+ return TRUE;
+}
+
+static movestate_t proc(moveparam_t *next, double t){
+ if(state == ST_STOP) goto ret;
+ for(movingstage_t s = STAGE_STOPPED; s >= 0; --s){
+ if(Times[s] <= t){ // check time for current stage
+ movingstage = s;
+ break;
+ }
+ }
+ if(movingstage == STAGE_STOPPED){
+ curparams.coord = Params[STAGE_STOPPED].coord;
+ emstop(t);
+ goto ret;
+ }
+ // calculate current parameters
+ double dt = t - Times[movingstage];
+ double a = Params[movingstage].accel;
+ double v0 = Params[movingstage].speed;
+ double x0 = Params[movingstage].coord;
+ curparams.accel = a;
+ curparams.speed = v0 + a * dt;
+ curparams.coord = x0 + v0 * dt + a * dt * dt / 2.;
+ret:
+ if(next) *next = curparams;
+ return state;
+}
+
+static movestate_t getst(moveparam_t *cur){
+ if(cur) *cur = curparams;
+ return state;
+}
+
+static double gettstop(){
+ return Times[STAGE_STOPPED];
+}
+
+movemodel_t trapez = {
+ .init_limits = initlims,
+ .stop = stop,
+ .emergency_stop = emstop,
+ .get_state = getst,
+ .calculate = calc,
+ .proc_move = proc,
+ .stoppenanotime = gettstop,
+};
diff --git a/LibSidServo/ramp.h b/LibSidServo/ramp.h
new file mode 100644
index 0000000..486a367
--- /dev/null
+++ b/LibSidServo/ramp.h
@@ -0,0 +1,23 @@
+/*
+ * This file is part of the moving_model project.
+ * Copyright 2025 Edward V. Emelianov .
+ *
+ * This program 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.
+ *
+ * This program 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 this program. If not, see .
+ */
+
+#pragma once
+
+#include "movingmodel.h"
+
+extern movemodel_t trapez;
diff --git a/LibSidServo/serial.c b/LibSidServo/serial.c
index b6d717d..2d5e845 100644
--- a/LibSidServo/serial.c
+++ b/LibSidServo/serial.c
@@ -21,16 +21,17 @@
#include
#include
#include
+#include
#include
#include
#include
#include
#include
#include
-#include
#include
-#include "dbg.h"
+#include "main.h"
+#include "movingmodel.h"
#include "serial.h"
// serial devices FD
@@ -55,45 +56,6 @@ typedef struct __attribute__((packed)){
uint8_t CRC[4];
} enc_t;
-/**
- * @brief dtime - monotonic time from first run
- * @return
- */
-double dtime(){
- struct timespec start_time = {0}, cur_time;
- if(start_time.tv_sec == 0 && start_time.tv_nsec == 0){
- clock_gettime(CLOCK_MONOTONIC, &start_time);
- }
- clock_gettime(CLOCK_MONOTONIC, &cur_time);
- return ((double)(cur_time.tv_sec - start_time.tv_sec) +
- (cur_time.tv_nsec - start_time.tv_nsec) * 1e-9);
-}
-#if 0
-double dtime(){
- double t;
- struct timeval tv;
- gettimeofday(&tv, NULL);
- t = tv.tv_sec + ((double)tv.tv_usec)/1e6;
- return t;
-}
-#endif
-#if 0
-double tv2d(struct timeval *tv){
- if(!tv) return 0.;
- double t = tv->tv_sec + ((double)tv->tv_usec) / 1e6;
- return t;
-}
-#endif
-#if 0
-// init start time
-static void gttime(){
- struct timeval tv;
- gettimeofday(&tv, NULL);
- tv_sec_got = tv.tv_sec;
- tv_usec_got = tv.tv_usec;
-}
-#endif
-
// calculate current X/Y speeds
static void getXspeed(double t){
mountdata.encXspeed.val = (mountdata.encXposition.val - lastXenc.val) / (t - lastXenc.t);
@@ -170,7 +132,7 @@ static int getencval(int fd, double *val, double *t){
if(fd < 0) return FALSE;
char buf[128];
int got = 0, Lmax = 127;
- double t0 = dtime();
+ double t0 = nanotime();
do{
fd_set rfds;
FD_ZERO(&rfds);
@@ -189,7 +151,7 @@ static int getencval(int fd, double *val, double *t){
buf[got] = 0;
} else continue;
if(strchr(buf, '\n')) break;
- }while(Lmax && dtime() - t0 < Conf.EncoderReqInterval);
+ }while(Lmax && nanotime() - t0 < Conf.EncoderReqInterval);
if(got == 0) return 0; // WTF?
char *estr = strrchr(buf, '\n');
if(!estr) return 0;
@@ -277,7 +239,7 @@ static void *encoderthread1(void _U_ *u){
if((uint8_t)b == ENC_MAGICK){
// DBG("Got magic -> start filling packet");
databuf[wridx++] = (uint8_t) b;
- t = dtime();
+ t = nanotime();
}
continue;
}else databuf[wridx++] = (uint8_t) b;
@@ -298,13 +260,13 @@ static void *encoderthread2(void _U_ *u){
if(Conf.SepEncoder != 2) return NULL;
DBG("Thread started");
int errctr = 0;
- double t0 = dtime();
- const char *req = "next\n";
+ double t0 = nanotime();
+ const char *req = "\n";
int need2ask = 0; // need or not to ask encoder for new data
while(encfd[0] > -1 && encfd[1] > -1 && errctr < MAX_ERR_CTR){
if(need2ask){
- if(5 != write(encfd[0], req, 5)) { ++errctr; continue; }
- else if(5 != write(encfd[1], req, 5)) { ++errctr; continue; }
+ if(1 != write(encfd[0], req, 1)) { ++errctr; continue; }
+ else if(1 != write(encfd[1], req, 1)) { ++errctr; continue; }
}
double v, t;
if(getencval(encfd[0], &v, &t)){
@@ -329,9 +291,9 @@ static void *encoderthread2(void _U_ *u){
else need2ask = 1;
continue;
}
- while(dtime() - t0 < Conf.EncoderReqInterval){ usleep(10); }
- //DBG("DT=%g (RI=%g)", dtime()-t0, Conf.EncoderReqInterval);
- t0 = dtime();
+ while(nanotime() - t0 < Conf.EncoderReqInterval){ usleep(50); }
+ //DBG("DT=%g (RI=%g)", nanotime()-t0, Conf.EncoderReqInterval);
+ t0 = nanotime();
}
DBG("ERRCTR=%d", errctr);
for(int i = 0; i < 2; ++i){
@@ -364,20 +326,25 @@ static void *mountthread(void _U_ *u){
int errctr = 0;
uint8_t buf[2*sizeof(SSstat)];
SSstat *status = (SSstat*) buf;
+ if(Conf.RunModel)
+ while(1){
+ pthread_mutex_lock(&datamutex);
+ // now change data
+ getModData(&mountdata);
+ pthread_mutex_unlock(&datamutex);
+ double t0 = nanotime();
+ while(nanotime() - t0 < Conf.MountReqInterval) usleep(500);
+ t0 = nanotime();
+ }
// data to get
data_t d = {.buf = buf, .maxlen = sizeof(buf)};
// cmd to send
data_t *cmd_getstat = cmd2dat(CMD_GETSTAT);
if(!cmd_getstat) goto failed;
- double t0 = dtime();
-/*
-#ifdef EBUG
- double t00 = t0;
-#endif
-*/
while(mntfd > -1 && errctr < MAX_ERR_CTR){
// read data to status
- double tgot = dtime();
+ double tgot = nanotime(), t0 = tgot;
+ DBG("tgot=%g", tgot);
if(!MountWriteRead(cmd_getstat, &d) || d.len != sizeof(SSstat)){
#ifdef EBUG
DBG("Can't read SSstat, need %zd got %zd bytes", sizeof(SSstat), d.len);
@@ -396,10 +363,8 @@ static void *mountthread(void _U_ *u){
SSconvstat(status, &mountdata, tgot);
pthread_mutex_unlock(&datamutex);
// allow writing & getters
- //DBG("t0=%g, tnow=%g", t0-t00, dtime()-t00);
- if(dtime() - t0 >= Conf.MountReqInterval) usleep(50);
- while(dtime() - t0 < Conf.MountReqInterval);
- t0 = dtime();
+ while(nanotime() - t0 < Conf.MountReqInterval) usleep(500);
+ t0 = nanotime();
}
data_free(&cmd_getstat);
failed:
@@ -435,6 +400,7 @@ static int ttyopen(const char *path, speed_t speed){
// return FALSE if failed
int openEncoder(){
+ if(Conf.RunModel) return TRUE;
if(!Conf.SepEncoder) return FALSE; // try to open separate encoder when it's absent
if(Conf.SepEncoder == 1){ // only one device
DBG("One device");
@@ -442,7 +408,7 @@ int openEncoder(){
encfd[0] = ttyopen(Conf.EncoderDevPath, (speed_t) Conf.EncoderDevSpeed);
if(encfd[0] < 0) return FALSE;
encRtmout.tv_sec = 0;
- encRtmout.tv_usec = 200000000 / Conf.EncoderDevSpeed; // 20 bytes
+ encRtmout.tv_usec = 100000000 / Conf.EncoderDevSpeed; // 10 bytes
if(pthread_create(&encthread, NULL, encoderthread1, NULL)){
close(encfd[0]);
encfd[0] = -1;
@@ -472,6 +438,7 @@ int openEncoder(){
// return FALSE if failed
int openMount(){
+ if(Conf.RunModel) return TRUE;
if(mntfd > -1) close(mntfd);
DBG("Open mount %s @ %d", Conf.MountDevPath, Conf.MountDevSpeed);
mntfd = ttyopen(Conf.MountDevPath, (speed_t) Conf.MountDevSpeed);
@@ -487,7 +454,7 @@ int openMount(){
DBG("got %zd", l);
}while(1);*/
mntRtmout.tv_sec = 0;
- mntRtmout.tv_usec = 500000000 / Conf.MountDevSpeed; // 50 bytes
+ mntRtmout.tv_usec = 500000000 / Conf.MountDevSpeed; // 50 bytes * 10bits / speed
if(pthread_create(&mntthread, NULL, mountthread, NULL)){
DBG("Can't create thread");
close(mntfd);
@@ -500,21 +467,22 @@ int openMount(){
// close all opened serial devices and quit threads
void closeSerial(){
+ if(Conf.RunModel) return;
if(mntfd > -1){
- DBG("Kill mount thread");
+ DBG("Cancel mount thread");
pthread_cancel(mntthread);
DBG("join mount thread");
pthread_join(mntthread, NULL);
- DBG("close fd");
+ DBG("close mount fd");
close(mntfd);
mntfd = -1;
}
if(encfd[0] > -1){
- DBG("Kill encoder thread");
+ DBG("Cancel encoder thread");
pthread_cancel(encthread);
DBG("join encoder thread");
pthread_join(encthread, NULL);
- DBG("close fd");
+ DBG("close encoder's fd");
close(encfd[0]);
encfd[0] = -1;
if(Conf.SepEncoder == 2){
@@ -548,7 +516,7 @@ static int wr(const data_t *out, data_t *in, int needeol){
DBG("written bytes not equal to need");
return FALSE;
}
- //DBG("Send to mount %zd bytes: %s", out->len, out->buf);
+ DBG("Send to mount %zd bytes: %s", out->len, out->buf);
if(needeol){
int g = write(mntfd, "\r", 1); // add EOL
(void) g;
@@ -563,8 +531,9 @@ static int wr(const data_t *out, data_t *in, int needeol){
if(b < 0) break; // nothing to read -> go out
in->buf[in->len++] = (uint8_t) b;
}
+ DBG("got %zd bytes", in->len);
//DBG("Clear trashing input");
- while(getmntbyte() > -1);
+ //while(getmntbyte() > -1);
return TRUE;
}
@@ -575,6 +544,7 @@ static int wr(const data_t *out, data_t *in, int needeol){
* @return FALSE if failed
*/
int MountWriteRead(const data_t *out, data_t *in){
+ if(Conf.RunModel) return -1;
pthread_mutex_lock(&mntmutex);
int ret = wr(out, in, 1);
pthread_mutex_unlock(&mntmutex);
@@ -582,6 +552,7 @@ int MountWriteRead(const data_t *out, data_t *in){
}
// send binary data - without EOL
int MountWriteReadRaw(const data_t *out, data_t *in){
+ if(Conf.RunModel) return -1;
pthread_mutex_lock(&mntmutex);
int ret = wr(out, in, 0);
pthread_mutex_unlock(&mntmutex);
@@ -605,6 +576,7 @@ static void loglcmd(SSlcmd *c){
// send short/long binary command; return FALSE if failed
static int bincmd(uint8_t *cmd, int len){
+ if(Conf.RunModel) return FALSE;
static data_t *dscmd = NULL, *dlcmd = NULL;
if(!dscmd) dscmd = cmd2dat(CMD_SHORTCMD);
if(!dlcmd) dlcmd = cmd2dat(CMD_LONGCMD);
@@ -641,6 +613,7 @@ rtn:
return ret;
}
+// short, long and config text-binary commands
// return TRUE if OK
int cmdS(SSscmd *cmd){
return bincmd((uint8_t *)cmd, sizeof(SSscmd));
@@ -650,6 +623,7 @@ int cmdL(SSlcmd *cmd){
}
// rw == 1 to write, 0 to read
int cmdC(SSconfig *conf, int rw){
+ if(Conf.RunModel) return FALSE;
static data_t *wcmd = NULL, *rcmd = NULL;
int ret = FALSE;
// dummy buffer to clear trash in input
diff --git a/LibSidServo/serial.h b/LibSidServo/serial.h
index b488f72..47390b7 100644
--- a/LibSidServo/serial.h
+++ b/LibSidServo/serial.h
@@ -28,7 +28,6 @@
// max error counter (when read() returns -1)
#define MAX_ERR_CTR (100)
-double dtime();
data_t *cmd2dat(const char *cmd);
void data_free(data_t **x);
int openEncoder();
diff --git a/LibSidServo/sidservo.h b/LibSidServo/sidservo.h
index 9efdab0..0ae7fd4 100644
--- a/LibSidServo/sidservo.h
+++ b/LibSidServo/sidservo.h
@@ -16,6 +16,11 @@
* along with this program. If not, see .
*/
+/*
+ * This file contains all need for external usage
+ */
+
+
#pragma once
#ifdef __cplusplus
@@ -30,6 +35,10 @@ extern "C"
// max speeds (rad/s): xs=10 deg/s, ys=8 deg/s
#define MCC_MAX_X_SPEED (0.174533)
#define MCC_MAX_Y_SPEED (0.139626)
+// accelerations by both axis (for model); TODO: move speeds/accelerations into config?
+// xa=12.6 deg/s^2, ya= 9.5 deg/s^2
+#define MCC_X_ACCELERATION (0.219911)
+#define MCC_Y_ACCELERATION (0.165806)
// max speed interval, seconds
#define MCC_CONF_MAX_SPEEDINT (2.)
@@ -57,6 +66,7 @@ typedef struct{
double MountReqInterval; // interval between subsequent mount requests (seconds)
double EncoderReqInterval; // interval between subsequent encoder requests (seconds)
double EncoderSpeedInterval;// interval between speed calculations
+ int RunModel; // == 1 if you want to use model instead of real mount
} conf_t;
// coordinates/speeds in degrees or d/s: X, Y
@@ -224,7 +234,6 @@ typedef struct{
extern mount_t Mount;
-
#ifdef __cplusplus
}
#endif
diff --git a/LibSidServo/ssii.c b/LibSidServo/ssii.c
index e695c8d..2626762 100644
--- a/LibSidServo/ssii.c
+++ b/LibSidServo/ssii.c
@@ -19,8 +19,9 @@
#include
#include
#include
+#include
-#include "dbg.h"
+#include "main.h"
#include "serial.h"
#include "ssii.h"
@@ -65,13 +66,6 @@ static void ChkStopped(const SSstat *s, mountdata_t *m){
*/
void SSconvstat(const SSstat *s, mountdata_t *m, double t){
if(!s || !m) return;
-/*
-#ifdef EBUG
- static double t0 = -1.;
- if(t0 < 0.) t0 = dtime();
-#endif
- DBG("Convert, t=%g", dtime()-t0);
-*/
m->motXposition.val = X_MOT2RAD(s->Xmot);
m->motYposition.val = Y_MOT2RAD(s->Ymot);
ChkStopped(s, m);
@@ -82,9 +76,6 @@ void SSconvstat(const SSstat *s, mountdata_t *m, double t){
m->encYposition.val = Y_ENC2RAD(s->Yenc);
m->encXposition.t = m->encYposition.t = t;
}
- //m->lastmotposition.X = X_MOT2RAD(s->XLast);
- //m->lastmotposition.Y = Y_MOT2RAD(s->YLast);
- //m->lastmotposition.msrtime = *tdat;
m->keypad = s->keypad;
m->extradata.ExtraBits = s->ExtraBits;
m->extradata.ain0 = s->ain0;
@@ -170,7 +161,9 @@ int SSstop(int emerg){
const char *cmdx = (emerg) ? CMD_EMSTOPX : CMD_STOPX;
const char *cmdy = (emerg) ? CMD_EMSTOPY : CMD_STOPY;
for(; i < 10; ++i){
+ DBG("t1: %g", nanotime());
if(!SStextcmd(cmdx, NULL)) continue;
+ DBG("t2: %g", nanotime());
if(SStextcmd(cmdy, NULL)) break;
}
if(i == 10) return FALSE;
@@ -180,12 +173,19 @@ int SSstop(int emerg){
// update motors' positions due to encoders'
mcc_errcodes_t updateMotorPos(){
mountdata_t md = {0};
- double t0 = dtime(), t = 0.;
+ double t0 = nanotime(), t = 0.;
DBG("start @ %g", t0);
do{
- t = dtime();
+ t = nanotime();
if(MCC_E_OK == getMD(&md)){
- DBG("got");
+ if(md.encXposition.t == 0 || md.encYposition.t == 0){
+ DBG("Just started, t-t0 = %g!", t - t0);
+ sleep(1);
+ DBG("t-t0 = %g", nanotime() - t0);
+ //usleep(10000);
+ continue;
+ }
+ DBG("got; t pos x/y: %g/%g; tnow: %g", md.encXposition.t, md.encYposition.t, t);
if(fabs(md.encXposition.t - t) < 0.1 && fabs(md.encYposition.t - t) < 0.1){
DBG("FIX motors position to encoders");
int32_t Xpos = X_RAD2MOT(md.encXposition.val), Ypos = Y_RAD2MOT(md.encYposition.val);
diff --git a/LibSidServo/ssii.h b/LibSidServo/ssii.h
index 0db9251..f556659 100644
--- a/LibSidServo/ssii.h
+++ b/LibSidServo/ssii.h
@@ -16,6 +16,10 @@
* along with this program. If not, see .
*/
+/*
+ * This file contains stuff for sidereal-servo specific protocol
+ */
+
#pragma once
#include
@@ -171,16 +175,30 @@
// amount of consequent same coordinates to detect stop
#define MOTOR_STOPPED_CNT (20)
+// TODO: take it from settings?
// steps per revolution (SSI - x4 - for SSI)
-// 13312000 / 4 = 3328000
#define X_MOT_STEPSPERREV_SSI (13312000.)
-//#define X_MOT_STEPSPERREV (3325952.)
+// 13312000 / 4 = 3328000
#define X_MOT_STEPSPERREV (3328000.)
-// 17578668 / 4 = 4394667
#define Y_MOT_STEPSPERREV_SSI (17578668.)
-//#define Y_MOT_STEPSPERREV (4394960.)
+// 17578668 / 4 = 4394667
#define Y_MOT_STEPSPERREV (4394667.)
+// encoder per revolution
+#define X_ENC_STEPSPERREV (67108864.)
+#define Y_ENC_STEPSPERREV (67108864.)
+// encoder zero position
+#define X_ENC_ZERO (61245239)
+#define Y_ENC_ZERO (36999830)
+// encoder reversed (no: +1)
+#define X_ENC_SIGN (-1.)
+#define Y_ENC_SIGN (-1.)
+// encoder position to radians and back
+#define X_ENC2RAD(n) ang2half(X_ENC_SIGN * 2.*M_PI * ((double)(n-X_ENC_ZERO)) / X_ENC_STEPSPERREV)
+#define Y_ENC2RAD(n) ang2half(Y_ENC_SIGN * 2.*M_PI * ((double)(n-Y_ENC_ZERO)) / Y_ENC_STEPSPERREV)
+#define X_RAD2ENC(r) ((uint32_t)((r) / 2./M_PI * X_ENC_STEPSPERREV))
+#define Y_RAD2ENC(r) ((uint32_t)((r) / 2./M_PI * Y_ENC_STEPSPERREV))
+
// convert angle in radians to +-pi
static inline double ang2half(double ang){
if(ang < -M_PI) ang += 2.*M_PI;
@@ -214,21 +232,6 @@ static inline double ang2full(double ang){
#define ADDER2S(a) ((a) / SITECH_LOOP_FREQUENCY)
#define S2ADDER(s) ((s) * SITECH_LOOP_FREQUENCY)
-// encoder per revolution
-#define X_ENC_STEPSPERREV (67108864.)
-#define Y_ENC_STEPSPERREV (67108864.)
-// encoder zero position
-#define X_ENC_ZERO (61245239)
-#define Y_ENC_ZERO (36999830)
-// encoder reversed (no: +1)
-#define X_ENC_SIGN (-1.)
-#define Y_ENC_SIGN (-1.)
-// encoder position to radians and back
-#define X_ENC2RAD(n) ang2half(X_ENC_SIGN * 2.*M_PI * ((double)(n-X_ENC_ZERO)) / X_ENC_STEPSPERREV)
-#define Y_ENC2RAD(n) ang2half(Y_ENC_SIGN * 2.*M_PI * ((double)(n-Y_ENC_ZERO)) / Y_ENC_STEPSPERREV)
-#define X_RAD2ENC(r) ((uint32_t)((r) / 2./M_PI * X_ENC_STEPSPERREV))
-#define Y_RAD2ENC(r) ((uint32_t)((r) / 2./M_PI * Y_ENC_STEPSPERREV))
-
// encoder's tolerance (ticks)
#define YencTOL (25.)
#define XencTOL (25.)
@@ -334,5 +337,4 @@ int SSrawcmd(const char *cmd, data_t *answer);
int SSgetint(const char *cmd, int64_t *ans);
int SSsetterI(const char *cmd, int32_t ival);
int SSstop(int emerg);
-int SSshortCmd(SSscmd *cmd);
mcc_errcodes_t updateMotorPos();