home *** CD-ROM | disk | FTP | other *** search
- /***********************************************************************
-
- FILE
- cntrl3.c - PI Control of Servo Motor Speed for Multiple Motors
-
- ENTRY ROUTINES
- control - execute control algorithm
- init - interpret command line for initialization
- gain - interpret command line for PI gains
- setv - interpret command line for setpoint
- setsmp - interpret command line for sampling rate
- setmot - allocate and initialize motor structures
-
- PRIVATE ROUTINES
- pimotor - apply motor control
- cpmotor - copy motor descriptors
- getv - get velocity reading
- mact - send controller output to actuator
-
- REMARKS
- This version implements multiple motor control; each motor has
- completely independent sampling interval, control parameters, etc.
-
- LAST UPDATE
- 20 March 1988
- include ANSI features
-
- Copyright (c) 1984-1988 D.M.Auslander and C.H. Tham
-
- ***********************************************************************/
-
- /***********************************************************************
- I M P O R T S
- ***********************************************************************/
-
- #include <stdio.h>
- #include <stdlib.h>
- #include <math.h>
-
- #include "envir.h" /* environment definitions */
- #include "dash8.h" /* declarations for dash8.c */
- #include "dac2.h" /* declarations for dac2.c */
- #include "time0.h" /* declarations for time0.c */
- #include "cntrl3.h" /* exported declarations for this module */
-
- #ifdef SIMRT
- #ifdef ANSI
- extern void tstep(void);
- extern void sim_init(int);
- extern void sim_reset(char *);
- #else
- extern void tstep();
- extern void sim_init();
- extern void sim_reset();
- #endif
- #endif
-
- /***********************************************************************
- MODULE PRIVATE DATA STRUCTURES AND VARIABLES
- ***********************************************************************/
-
- #define ADCTOV 1.0 /* ADC units to velocity units */
- #define MTODAC 1.0 /* output units to DAC units */
-
- #define TIME_GRAIN 0.001 /* left time granularity */
-
- /*-----------------------------------------------------------*/
- /* This structure describes the control and configurational */
- /* parameters for velocity control of a d.c. motor. */
- /*-----------------------------------------------------------*/
-
- typedef struct motor {
- int ichan; /* A/D channel number */
- int mchan; /* D/A channel number */
- double v; /* velocity */
- double vref; /* velocity setpoint */
- double kp; /* proportional gain */
- double ki; /* integral gain */
- double ival; /* integrator accumulated value */
- double vcon; /* constant in control equation */
- double m; /* controller output */
- double mmin; /* lower limit for controller output */
- double mmax; /* upper limit for controller output */
- double tsamp; /* sample interval */
- double tleft; /* time left to next sample */
- } MOTOR;
-
- static MOTOR *pm0; /* pointer to the structure for motor #0 */
- static int nmotors; /* number of motors */
-
- /*----------------------------------------------------------------*/
- /* motdef is a set of default values that is used to initialize */
- /* newly allocated motor control structure. */
- /*----------------------------------------------------------------*/
-
- static MOTOR motdef = {
- 0, /* A/D channel defaults to 0 */
- 0, /* D/A channel defaults to 0 */
- 0.0, /* zero initial velocity */
- 1000.0, /* default velocity setpoint */
- 1.0, /* kp defaults to 1.0 */
- 0.0, /* no integrator by default, P control by default */
- 0.0, /* zero integration sum */
- 0.0, /* constant term in control equation */
- 0.0, /* no controller output initially */
- -2048.0, /* lower output limit */
- 2047.0, /* upper output limit */
- 0.5, /* default sampling interval of 0.5 seconds */
- 0.0 /* time left to next sample */
- };
-
- /***********************************************************************
- F O R W A R D D E C L A R A T I O N S
- ***********************************************************************/
-
- #ifdef ANSI
-
- double getv(int);
- void mact(int, double);
- void pimotor(MOTOR *, int);
- void cpmotor(MOTOR *, MOTOR *);
-
- #else
-
- double getv();
- void mact();
- void pimotor();
- void cpmotor();
-
- #endif
-
- /***********************************************************************
- E N T R Y R O U T I N E S
- ***********************************************************************/
-
- /*----------------------------------------------------------------------
- PROCEDURE
- CONTROL - this function does the control
-
- SYNOPSIS
- void control(cline)
- char cline[];
-
- PARAMETER
- cline - user's input line that specifies the number of iterations
- and whether to trace the control action.
-
- REMARKS
- The control output is calculated in two parts - first with only P
- action and then with I action added. The output is limited to what
- the actuator can put out, with provisions against reset windup.
-
- LAST UPDATE
- 20 March 1988
- use ANSI features
- ----------------------------------------------------------------------*/
-
- void control(cline)
- char *cline;
- {
- MOTOR *pm; /* pointer to motor data structure */
- long nitr; /* number of control iterations */
- long i; /* iteration counter */
- int m; /* motor number index */
- int prnt; /* if set to 1, print output every iteration */
- double tlmin; /* minimum time left to next sample */
-
-
- pm = pm0; /* set pm to point at first motor structure */
-
- for (m = 0; m < nmotors; m++)
- {
- pm->ival = 0.0; /* initialize the integrator */
- pm++; /* next motor */
- }
-
- /* next get user specified iteration limit and print switch */
-
- sscanf(cline, "%ld %d", &nitr, &prnt);
-
- #ifdef DEBUG
- printf("number of iterations = %ld\n", nitr); /* verify/debug */
- #endif
-
- for (i = 0L; i < nitr; i++) /* main iteration loop */
- {
- pm = pm0; /* point to first motor structure */
-
- /*-------------------------------------------------------------
- Tlmin is initialized to a large number so we can use it
- to find the task with minimum time left by comparing its
- time left against tlmin and setting tlmin to the minimum
- time left until the motor request control.
- --------------------------------------------------------------*/
-
- tlmin = 1.e+37; /* 10 to the power of 37 */
-
- /*--------------------------------------------------------
- Check each motor to see how much time is left until
- it will request control. tlmin is set in this loop.
- ---------------------------------------------------------*/
-
- for (m = 0; m < nmotors; m++)
- {
- double ttest; /* time to next sample for this motor */
-
- /*--------------------------------------------------------
- A time-left of zero implies that this is the current
- sampling instance for the motor. Hence, the time
- to next sample from now is the sample interval.
- ---------------------------------------------------------*/
-
- if (pm->tleft <= 0.0)
- ttest = pm->tsamp;
- else
- ttest = pm->tleft;
-
- if (ttest < tlmin) /* update new tlmin */
- tlmin = ttest;
-
- ++pm; /* next motor */
- }
-
- /*-----------------------------------------------------------
- Set timer for next sample interval and adjust tleft's.
- Note that argument to settimer() is in milliseconds.
- ------------------------------------------------------------*/
-
- SetTimer((long)(tlmin * 1000));
-
- /*--------------------------------------------------------------
- Now apply control on any motor requesting it (tleft <= 0).
- The timer should be set before applying control because
- the control action can take a significant amount of time,
- especially if there are many motors to control.
- --------------------------------------------------------------*/
-
- pm = pm0; /* point at first motor structure */
-
- for (m = 0; m < nmotors; m++)
- {
- if (pm->tleft <= 0.0)
- {
- pimotor(pm, prnt); /* perform PI control */
-
- /*------------------------------------------------
- Set time-left to the sampling interval to
- await the next execution instance for this
- particular motor.
- ------------------------------------------------*/
-
- pm->tleft = pm->tsamp;
- }
-
- pm->tleft -= tlmin; /* subtract timer setting from */
- /* time left for all motors. */
-
- /*----------------------------------------------------------
- tleft will be compared against 0.0 in the previous
- for(;;) loop; hence we have to impose a comparison
- granularity as tleft is a floating point number.
- ----------------------------------------------------------*/
-
- if (fabs(pm->tleft) < TIME_GRAIN) /* adjust for time */
- pm->tleft = 0.0; /* granularity */
-
- pm++; /* next motor */
- }
-
- if (TimeUp()) /* timeout before calculations were done */
- {
- printf("sampling time constraints violated\n");
- exit(1);
- }
-
- while (!TimeUp()) /* wait for next sampling instance */
- {
- #if SIMRT /* if this is a simulation, call tstep() */
- tstep(); /* which advances time and simulates */
- #endif /* the motor plant(s). */
- }
- }
- }
-
-
-
- /*----------------------------------------------------------------------
- PROCEDURE
- INIT - process system initialization command
-
- SYNOPSIS
- void init(cline)
- char *cline;
-
- PARAMETER
- cline - data part of input command line, specifying step size
-
- REMARKS
- This routine must be called before control() is called.
-
- LAST UPDATE
- 20 March 1988
- change sim_init()
- ----------------------------------------------------------------------*/
-
- void init(cline)
- char *cline;
- {
-
- ad_init(); /* initialize A/D */
- da_init(); /* initialize D/A */
-
- #ifdef SIMRT
- sim_reset(cline);
- #endif
-
- }
-
-
-
- /*----------------------------------------------------------------------
- PROCEDURE
- GAIN - interpret input line for controller gains specification
-
- SYNOPSIS
- void gain(cline)
- char *cline;
-
- PARAMETER
- cline - pointer to null terminated input command line
-
- REMARKS
- Note that in gain(), setv() and setsmp(), the input data is put
- into temporary storage first. This is because the specified
- motor number may be out of range.
-
- LAST UPDATE
- 20 March 1988
- use ANSI features
- ----------------------------------------------------------------------*/
-
- void gain(cline)
- char *cline;
- {
- int mn; /* motor number */
- double kp, ki, vcon; /* temporary gain and constant terms */
-
-
- if (sscanf(cline, "%d %lf %lf %lf", &mn, &kp, &ki, &vcon) == 4)
- {
- if ((mn >= 0) && (mn < nmotors))
- {
- /*
- * Set the appropriate fields of the motor descriptor.
- * Note that (pm0 + mn) is a pointer to the descriptor
- * for the mn-th motor; hence the use of "->" to
- * dereference the descriptor fields.
- */
-
- (pm0 + mn)->kp = kp;
- (pm0 + mn)->ki = ki;
- (pm0 + mn)->vcon = vcon;
-
- #ifdef DEBUG
- printf("<gain> motor = %d, ", mn);
- printf("kp = %lf, ki = %lf, vcon = %lf\n", kp, ki, vcon);
- #endif
- }
- else
- printf("gain: invalid motor number: %d\n", mn);
- }
- else
- printf("gain: invalid or insufficient parameters\n");
-
- }
-
-
-
- /*----------------------------------------------------------------------
- PROCEDURE
- SETV - get velocity setpoint from command line
-
- SYNOPSIS
- void setv(cline)
- char *cline;
-
- PARAMETER
- cline - pointer to null terminated input command line
-
- LAST UPDATE
- 20 March 1988
- use ANSI features
- ----------------------------------------------------------------------*/
-
- void setv(cline)
- char *cline;
- {
- int mn; /* motor number */
- double vref; /* temporary holder for setpoint value */
-
-
- if (sscanf(cline, "%d %lf", &mn ,&vref) == 2)
- {
- if ((mn >= 0) && (mn < nmotors))
- {
- (pm0 + mn)->vref = vref; /* record velocity reference */
-
- #ifdef DEBUG
- printf("<setv> motor = %d, vref = %lf\n", mn, vref);
- #endif
- }
- else
- printf("setv: bad motor number: %d\n", mn);
- }
- else
- printf("setv: invalid or insufficient parameters\n");
-
- }
-
-
-
- /*----------------------------------------------------------------------
- PROCEDURE
- SETSMP - set sampling interval from command line specification
-
- SYNOPSIS
- void setsmp(cline)
- char *cline;
-
- PARAMETER
- cline - pointer to null terminated input command line
-
- LAST UPDATE
- 20 March 1988
- use ANSI features
- ----------------------------------------------------------------------*/
-
- void setsmp(cline)
- char *cline;
- {
- int mn; /* motor number */
- double tsamp; /* temporary holder for sample time */
-
-
- if (sscanf(cline,"%d %lf", &mn, &tsamp) == 2)
- {
- if ((mn >= 0) && (mn < nmotors))
- {
- (pm0 + mn)->tsamp = tsamp; /* set sample interval */
-
- #ifdef DEBUG
- printf("<setsmp> motor = %d, tsamp = %f\n", mn, tsamp);
- #endif
- }
- else
- printf("setsmp: bad motor number: %d\n", mn);
- }
- else
- printf("setsmp: invalid or insufficient parameters\n");
-
- }
-
-
-
- /*----------------------------------------------------------------------
- PROCEDURE
- SETMOT - allocate and initialize data structures for the number
- of motors requested by the user.
- SYNOPSIS
- void setmot(void)
-
- REMARKS
- This routine must be called as the first action of the main program.
-
- Motor descriptors initialized from defaults specified in "motdef"
-
- LAST UPDATE
- 20 March 1988
- use ANSI features
- ----------------------------------------------------------------------*/
-
- void setmot()
- {
- MOTOR *pm; /* pointer to motor data structures */
- int nm; /* number of motors */
- char cbuf[20]; /* input line */
- int damin, damax; /* D/A output limits */
- int done; /* flag, if set, o.k. to return */
- int i; /* loop index */
-
-
- do
- {
- done = 1;
-
- /*
- * Prompt user and get response line. Because scanf() does not
- * remove the newline character at the end of lines, fgets() is
- * used instead since the newline character may cause trouble
- * with other console functions.
- */
-
- fputs("How many motors? ", stdout);
- fgets(cbuf, 20, stdin); /* get at most 19 characters */
-
- if (sscanf(cbuf, "%d", &nm) == 1) /* decode input */
- {
- da_limits(&damin, &damax); /* find DAC limits */
-
- motdef.mmin = (double)damin / MTODAC; /* set output limits */
- motdef.mmax = (double)damax / MTODAC;
-
- /*
- * Dynamically allocate memory for the required number of
- * motor structures. Note that calloc() is used instead of
- * malloc() since calloc() is suppose to guarantee that the
- * allocated block of memory has the correct alignment.
- * Note also the use of "casts" in the allocation statement;
- * though not strictly necessary, it is recommended as good
- * programming style.
- */
-
- if ((pm0 = (MOTOR *)calloc(nm, sizeof(MOTOR))) == (MOTOR *)NULL)
- {
- printf("INSUFFICIENT MEMORY: ");
- printf("cannot allocate %d motor blocks\n", nm);
- exit(1);
- }
-
- for (i = 0, pm = pm0; i < nm; i++, pm++)
- {
- cpmotor(&motdef, pm); /* init with defaults from motdef */
-
- pm->ichan = i; /* set both A/D and D/A channels to i; the */
- pm->mchan = i; /* actual channels depends on your setup */
- }
-
- nmotors = nm; /* record number of motor descriptors */
-
- #ifdef SIMRT
- sim_init(nm); /* if real-time simulation is to be used, */
- #endif /* initialize the simulation module. */
-
- if (nm == 1)
- printf("\nmotor 0 has been initialized\n");
- else
- printf("\nmotors 0 to %d have been initialized\n", nm - 1);
- }
- else
- {
- printf("invalid entry\n");
- done = 0;
- }
- }
- while (!done);
-
- }
-
-
- /***********************************************************************
- P R I V A T E R O U T I N E S
- ***********************************************************************/
-
- /*----------------------------------------------------------------------
- PROCEDURE
- PIMOTOR - apply PI control for motor
-
- SYNOPSIS
- static void pimotor(pm, prnt)
- MOTOR *pm;
- int prnt;
-
- PARAMETERS
- pm - pointer to motor structure requiring control
- prnt - if 1, print control output
-
- LAST UPDATE
- 20 March 1988
- use ANSI features
- ----------------------------------------------------------------------*/
-
- static void pimotor(pm, prnt)
- MOTOR *pm;
- int prnt;
- {
- double m1; /* intermediate result in control calculation. */
- double error; /* error between reference and measured */
-
-
- pm->v = getv(pm->ichan); /* obtain motor velocity */
-
- error = pm->vref - pm->v; /* calculate error term */
- pm->ival += error; /* update integral of error */
- m1 = pm->kp * error + pm->vcon; /* do P control first */
-
- /*
- * Check for actuator limits and apply "reset windup" control.
- * If the output would exceed actuator limits, set the integral
- * term to zero to prevent the integral term from dominating
- * the output and slow down corrective action.
- */
-
- if (m1 > pm->mmax)
- {
- pm->m = pm->mmax;
- pm->ival = 0.0;
- }
- else if (m1 < pm->mmin)
- {
- pm->m = pm->mmin;
- pm->ival = 0.0;
- }
- else /* output withing actuator limits */
- {
- pm->m = m1 + (pm->ki * pm->ival); /* now add integral term */
- }
-
- if (prnt) /* print controller output */
- {
- int mn; /* motor number */
-
- mn = (pm - pm0); /* compute motor number for output */
-
- printf("motor = %d, v = %7.3lf, m = %7.3lf\n", mn, pm->v, pm->m);
- }
-
- mact(pm->mchan, pm->m); /* send controller output to actuator */
-
- }
-
-
-
- /*----------------------------------------------------------------------
- PROCEDURE
- CPMOTOR - copy one motor descriptor to another
-
- SYNOPSIS
- static void cpmotor(from, to)
- MOTOR *from, *to;
-
- PARAMETERS
- from - pointer to source
- to - pointer to destination
-
- REMARKS
- Standard (K&R) C does not allow entire structures to be assigned
- (this will change with the proposed ANSI X3J11 standards). This
- restriction is very inconvenient and this routine gets around this
- by doing a byte-by-byte copy of the descriptor contents. This
- technique may not work on all computers, but it is reasonably
- robust and is much easier than doing field by field assignments.
-
- If you are using an ANSI conforming C compiler, just do a structure
- assignment. The compiler will take care of the rest.
-
- LAST UPDATE
- 20 March 1988
- use ANSI features
- ----------------------------------------------------------------------*/
-
- static void cpmotor(from, to)
- MOTOR *from, *to;
- {
-
- #ifdef ANSI /* can do structure assignment */
-
- *to = *from;
-
- #else /* do explicit byte by byte copy */
-
- register char *src, *dst; /* source and destination pointers */
- unsigned count; /* byte count */
-
-
- src = (char *)from; /* make fast copy of source pointer */
- dst = (char *)to; /* make fast copy of destination pointer */
-
- for (count = 0; count < sizeof(MOTOR); count++)
- *dst++ = *src++;
-
- #endif
-
- }
-
-
-
- /*----------------------------------------------------------------------
- FUNCTION
- GETV - get current motor velocity
-
- SYNOPSIS
- static double getv(channel)
- int channel;
-
- PARAMETER
- channel - A/D channel from which to read the velocity
-
- RETURNS
- current motor velocity
-
- REMARKS
- Note that the conversion of raw ADC units to velocity units
- uses the ADCTOV conversion factor.
-
- LAST UPDATE
- 20 March 1988
- use ANSI features
- ----------------------------------------------------------------------*/
-
- static double getv(channel)
- int channel;
- {
-
- return((double)ad_wread(channel) * ADCTOV);
- }
-
-
-
- /*----------------------------------------------------------------------
- PROCEDURE
- mact - send controller output to D/A converter
-
- SYNOPSIS
- static void mact(channel, output)
- int channel;
- double output;
-
- PARAMETERS
- channel - D/A channel number
- output - controller output
-
- REMARKS
- Multiply by conversion factor and add 0.5 for rounding.
-
- LAST UPDATE
- 20 March 1988
- use ANSI features
- ----------------------------------------------------------------------*/
-
- static void mact(channel, output)
- int channel;
- double output;
- {
-
- da_write(channel, (int)((output * MTODAC) + 0.5));
- }
-