home *** CD-ROM | disk | FTP | other *** search
/ Liren Large Software Subsidy 15 / 15.iso / s / s300 / 1.ddi / CHAP5 / CNTRL3.C < prev    next >
Encoding:
C/C++ Source or Header  |  1988-05-02  |  22.6 KB  |  747 lines

  1. /***********************************************************************
  2.  
  3. FILE
  4.     cntrl3.c  -  PI Control of Servo Motor Speed for Multiple Motors
  5.  
  6. ENTRY ROUTINES
  7.     control  -  execute control algorithm
  8.     init     -  interpret command line for initialization 
  9.     gain     -  interpret command line for PI gains
  10.     setv     -  interpret command line for setpoint
  11.     setsmp   -  interpret command line for sampling rate
  12.     setmot   -  allocate and initialize motor structures
  13.  
  14. PRIVATE ROUTINES
  15.     pimotor  -  apply motor control
  16.     cpmotor  -  copy motor descriptors
  17.     getv     -  get velocity reading
  18.     mact     -  send controller output to actuator
  19.  
  20. REMARKS
  21.     This version implements multiple motor control; each motor has 
  22.     completely independent sampling interval, control parameters, etc.
  23.  
  24. LAST UPDATE
  25.     20 March 1988
  26.         include ANSI features
  27.     
  28.     Copyright (c) 1984-1988  D.M.Auslander and C.H. Tham
  29.     
  30. ***********************************************************************/
  31.  
  32. /***********************************************************************
  33.                     I M P O R T S
  34. ***********************************************************************/
  35.  
  36. #include <stdio.h>
  37. #include <stdlib.h>
  38. #include <math.h>
  39.  
  40. #include "envir.h"      /* environment definitions */
  41. #include "dash8.h"      /* declarations for dash8.c */
  42. #include "dac2.h"       /* declarations for dac2.c */
  43. #include "time0.h"      /* declarations for time0.c */
  44. #include "cntrl3.h"     /* exported declarations for this module */
  45.  
  46. #ifdef SIMRT
  47. #ifdef ANSI
  48. extern void tstep(void);
  49. extern void sim_init(int);
  50. extern void sim_reset(char *);
  51. #else
  52. extern void tstep();
  53. extern void sim_init();
  54. extern void sim_reset();
  55. #endif
  56. #endif
  57.  
  58. /***********************************************************************
  59.            MODULE  PRIVATE  DATA  STRUCTURES  AND  VARIABLES
  60. ***********************************************************************/
  61.  
  62. #define ADCTOV      1.0         /* ADC units to velocity units */
  63. #define MTODAC      1.0         /* output units to DAC units */
  64.  
  65. #define TIME_GRAIN  0.001       /* left time granularity */
  66.  
  67. /*-----------------------------------------------------------*/
  68. /*  This structure describes the control and configurational */
  69. /*  parameters for velocity control of a d.c. motor.         */
  70. /*-----------------------------------------------------------*/
  71.  
  72. typedef struct motor {
  73.     int ichan;      /* A/D channel number */
  74.     int mchan;      /* D/A channel number */
  75.     double v;       /* velocity */
  76.     double vref;    /* velocity setpoint */
  77.     double kp;      /* proportional gain */
  78.     double ki;      /* integral gain */
  79.     double ival;    /* integrator accumulated value */
  80.     double vcon;    /* constant in control equation */
  81.     double m;       /* controller output */
  82.     double mmin;    /* lower limit for controller output */
  83.     double mmax;    /* upper limit for controller output */
  84.     double tsamp;   /* sample interval */
  85.     double tleft;   /* time left to next sample */
  86. } MOTOR;
  87.  
  88. static MOTOR *pm0;      /* pointer to the structure for motor #0 */
  89. static int nmotors;     /* number of motors */
  90.  
  91. /*----------------------------------------------------------------*/
  92. /*  motdef is a set of default values that is used to initialize  */
  93. /*  newly allocated motor control structure.                      */
  94. /*----------------------------------------------------------------*/
  95.  
  96. static MOTOR motdef = {
  97.           0,    /* A/D channel defaults to 0 */
  98.           0,    /* D/A channel defaults to 0 */
  99.         0.0,    /* zero initial velocity */
  100.      1000.0,    /* default velocity setpoint */
  101.         1.0,    /* kp defaults to 1.0 */
  102.         0.0,    /* no integrator by default, P control by default */
  103.         0.0,    /* zero integration sum */
  104.         0.0,    /* constant term in control equation */
  105.         0.0,    /* no controller output initially */
  106.     -2048.0,    /* lower output limit */
  107.      2047.0,    /* upper output limit */
  108.         0.5,    /* default sampling interval of 0.5 seconds */
  109.        0.0      /* time left to next sample */
  110. };
  111.  
  112. /***********************************************************************
  113.                F O R W A R D    D E C L A R A T I O N S
  114. ***********************************************************************/
  115.  
  116. #ifdef ANSI
  117.  
  118. double  getv(int);
  119. void    mact(int, double);
  120. void    pimotor(MOTOR *, int);
  121. void    cpmotor(MOTOR *, MOTOR *);
  122.  
  123. #else
  124.  
  125. double  getv();
  126. void    mact();
  127. void    pimotor();
  128. void    cpmotor();
  129.  
  130. #endif
  131.  
  132. /***********************************************************************
  133.                     E N T R Y    R O U T I N E S
  134. ***********************************************************************/
  135.  
  136. /*----------------------------------------------------------------------
  137. PROCEDURE
  138.     CONTROL  -  this function does the control
  139.  
  140. SYNOPSIS
  141.     void control(cline)
  142.     char cline[];
  143.  
  144. PARAMETER
  145.     cline  -  user's input line that specifies the number of iterations
  146.               and whether to trace the control action.
  147.  
  148. REMARKS
  149.     The control output is calculated in two parts - first with only P
  150.     action and then with I action added.  The output is limited to what
  151.     the actuator can put out, with provisions against reset windup.
  152.  
  153. LAST UPDATE
  154.     20 March 1988
  155.         use ANSI features
  156. ----------------------------------------------------------------------*/
  157.  
  158. void control(cline)
  159. char *cline;
  160. {
  161.     MOTOR *pm;          /* pointer to motor data structure */
  162.     long nitr;          /* number of control iterations */
  163.     long i;             /* iteration counter */
  164.     int m;              /* motor number index */
  165.     int prnt;           /* if set to 1, print output every iteration */
  166.     double tlmin;       /* minimum time left to next sample */
  167.  
  168.  
  169.     pm = pm0;           /* set pm to point at first motor structure */
  170.  
  171.     for (m = 0; m < nmotors; m++)
  172.     {
  173.         pm->ival = 0.0;     /* initialize the integrator */
  174.         pm++;               /* next motor */
  175.     }
  176.  
  177.     /*  next get user specified iteration limit and print switch  */
  178.  
  179.     sscanf(cline, "%ld %d", &nitr, &prnt);
  180.  
  181. #ifdef DEBUG
  182.     printf("number of iterations = %ld\n", nitr);   /* verify/debug */
  183. #endif
  184.  
  185.     for (i = 0L; i < nitr; i++)  /* main iteration loop */
  186.     {
  187.         pm = pm0;       /* point to first motor structure */
  188.  
  189.         /*-------------------------------------------------------------
  190.             Tlmin is initialized to a large number so we can use it
  191.             to find the task with minimum time left by comparing its
  192.             time left against tlmin and setting tlmin to the minimum
  193.             time left until the motor request control.
  194.         --------------------------------------------------------------*/
  195.  
  196.         tlmin = 1.e+37;     /* 10 to the power of 37 */
  197.  
  198.         /*--------------------------------------------------------
  199.             Check each motor to see how much time is left until
  200.             it will request control.  tlmin is set in this loop.
  201.         ---------------------------------------------------------*/
  202.  
  203.         for (m = 0; m < nmotors; m++)
  204.         {
  205.             double ttest;    /* time to next sample for this motor */
  206.         
  207.             /*--------------------------------------------------------
  208.                 A time-left of zero implies that this is the current
  209.                 sampling instance for the motor.  Hence, the time
  210.                 to next sample from now is the sample interval.
  211.             ---------------------------------------------------------*/
  212.         
  213.             if (pm->tleft <= 0.0)
  214.                 ttest = pm->tsamp;
  215.             else
  216.                 ttest = pm->tleft;
  217.         
  218.             if (ttest < tlmin)      /* update new tlmin */
  219.                 tlmin = ttest;
  220.         
  221.             ++pm;                   /* next motor */
  222.         }
  223.  
  224.         /*-----------------------------------------------------------
  225.             Set timer for next sample interval and adjust tleft's.
  226.             Note that argument to settimer() is in milliseconds.
  227.         ------------------------------------------------------------*/
  228.         
  229.         SetTimer((long)(tlmin * 1000));
  230.         
  231.         /*--------------------------------------------------------------
  232.             Now apply control on any motor requesting it (tleft <= 0).
  233.             The timer should be set before applying control because
  234.             the control action can take a significant amount of time,
  235.             especially if there are many motors to control.
  236.         --------------------------------------------------------------*/
  237.         
  238.         pm = pm0;       /* point at first motor structure */
  239.         
  240.         for (m = 0; m < nmotors; m++)
  241.         {
  242.             if (pm->tleft <= 0.0)
  243.             {
  244.                 pimotor(pm, prnt);      /* perform PI control */
  245.  
  246.                 /*------------------------------------------------
  247.                     Set time-left to the sampling interval to
  248.                     await the next execution instance for this
  249.                     particular motor.
  250.                 ------------------------------------------------*/
  251.  
  252.                 pm->tleft = pm->tsamp;
  253.             }
  254.         
  255.             pm->tleft -= tlmin;     /* subtract timer setting from */
  256.                                     /*   time left for all motors. */
  257.  
  258.             /*----------------------------------------------------------
  259.                 tleft will be compared against 0.0 in the previous
  260.                 for(;;) loop; hence we have to impose a comparison
  261.                 granularity as tleft is a floating point number.
  262.             ----------------------------------------------------------*/
  263.  
  264.             if (fabs(pm->tleft) < TIME_GRAIN)   /* adjust for time */
  265.                 pm->tleft = 0.0;                /*  granularity    */
  266.  
  267.             pm++;                   /* next motor */
  268.         }
  269.         
  270.         if (TimeUp())       /* timeout before calculations were done */
  271.         {
  272.             printf("sampling time constraints violated\n");
  273.             exit(1);
  274.         }
  275.         
  276.         while (!TimeUp())  /* wait for next sampling instance */
  277.         {
  278. #if SIMRT                   /* if this is a simulation, call tstep() */
  279.             tstep();        /*   which advances time and simulates   */
  280. #endif                      /*   the motor plant(s).                 */
  281.         }
  282.     }
  283.  
  284.  
  285.  
  286. /*----------------------------------------------------------------------
  287. PROCEDURE
  288.     INIT  -  process system initialization command
  289.  
  290. SYNOPSIS
  291.     void init(cline)
  292.     char *cline;
  293.  
  294. PARAMETER
  295.     cline  -  data part of input command line, specifying step size
  296.  
  297. REMARKS
  298.     This routine must be called before control() is called.
  299.  
  300. LAST UPDATE
  301.     20 March 1988
  302.         change sim_init()
  303. ----------------------------------------------------------------------*/
  304.  
  305. void init(cline)
  306. char *cline;
  307. {
  308.  
  309.     ad_init();              /* initialize A/D */
  310.     da_init();              /* initialize D/A */
  311.  
  312. #ifdef SIMRT
  313.     sim_reset(cline);
  314. #endif
  315.  
  316. }
  317.  
  318.  
  319.  
  320. /*----------------------------------------------------------------------
  321. PROCEDURE
  322.     GAIN  -  interpret input line for controller gains specification
  323.  
  324. SYNOPSIS
  325.     void gain(cline)
  326.     char *cline;
  327.  
  328. PARAMETER
  329.     cline  -  pointer to null terminated input command line
  330.  
  331. REMARKS
  332.     Note that in gain(), setv() and setsmp(), the input data is put
  333.     into temporary storage first.  This is because the specified
  334.     motor number may be out of range.
  335.  
  336. LAST UPDATE
  337.     20 March 1988
  338.         use ANSI features
  339. ----------------------------------------------------------------------*/
  340.  
  341. void gain(cline)
  342. char *cline;
  343. {
  344.     int mn;                 /* motor number */
  345.     double kp, ki, vcon;    /* temporary gain and constant terms */
  346.  
  347.  
  348.     if (sscanf(cline, "%d %lf %lf %lf", &mn, &kp, &ki, &vcon) == 4)
  349.     {
  350.         if ((mn >= 0) && (mn < nmotors))
  351.         {
  352.             /*
  353.              *  Set the appropriate fields of the motor descriptor.
  354.              *  Note that (pm0 + mn) is a pointer to the descriptor
  355.              *  for the mn-th motor; hence the use of "->" to
  356.              *  dereference the descriptor fields.
  357.              */
  358.  
  359.             (pm0 + mn)->kp = kp;
  360.             (pm0 + mn)->ki = ki;
  361.             (pm0 + mn)->vcon = vcon;
  362.  
  363. #ifdef DEBUG
  364.             printf("<gain> motor = %d, ", mn);
  365.             printf("kp = %lf, ki = %lf, vcon = %lf\n", kp, ki, vcon);
  366. #endif
  367.         }
  368.         else
  369.             printf("gain: invalid motor number: %d\n", mn);
  370.     }
  371.     else
  372.         printf("gain: invalid or insufficient parameters\n");
  373.  
  374. }
  375.  
  376.  
  377.  
  378. /*----------------------------------------------------------------------
  379. PROCEDURE
  380.     SETV  -  get velocity setpoint from command line
  381.  
  382. SYNOPSIS
  383.     void setv(cline)
  384.     char *cline;
  385.  
  386. PARAMETER
  387.     cline  -  pointer to null terminated input command line
  388.  
  389. LAST UPDATE
  390.     20 March 1988
  391.         use ANSI features
  392. ----------------------------------------------------------------------*/
  393.  
  394. void setv(cline)
  395. char *cline;
  396. {
  397.     int mn;         /* motor number */
  398.     double vref;    /* temporary holder for setpoint value */
  399.  
  400.  
  401.     if (sscanf(cline, "%d %lf", &mn ,&vref) == 2)
  402.     {
  403.         if ((mn >= 0) && (mn < nmotors))
  404.         {
  405.             (pm0 + mn)->vref = vref;        /* record velocity reference */
  406.  
  407. #ifdef DEBUG
  408.             printf("<setv> motor = %d, vref = %lf\n", mn, vref);
  409. #endif
  410.         }
  411.         else
  412.             printf("setv: bad motor number: %d\n", mn);
  413.     }
  414.     else
  415.         printf("setv: invalid or insufficient parameters\n");
  416.  
  417. }
  418.  
  419.  
  420.  
  421. /*----------------------------------------------------------------------
  422. PROCEDURE
  423.     SETSMP  -  set sampling interval from command line specification
  424.  
  425. SYNOPSIS
  426.     void setsmp(cline)
  427.     char *cline;
  428.  
  429. PARAMETER
  430.     cline  -  pointer to null terminated input command line
  431.  
  432. LAST UPDATE
  433.     20 March 1988
  434.         use ANSI features
  435. ----------------------------------------------------------------------*/
  436.  
  437. void setsmp(cline)
  438. char *cline;
  439. {
  440.     int mn;             /* motor number */
  441.     double tsamp;       /* temporary holder for sample time */
  442.  
  443.  
  444.     if (sscanf(cline,"%d %lf", &mn, &tsamp) == 2)
  445.     {
  446.         if ((mn >= 0) && (mn < nmotors))
  447.         {
  448.             (pm0 + mn)->tsamp = tsamp;      /* set sample interval */
  449.  
  450. #ifdef DEBUG
  451.             printf("<setsmp> motor = %d, tsamp = %f\n", mn, tsamp);
  452. #endif
  453.         }
  454.         else
  455.             printf("setsmp: bad motor number: %d\n", mn);
  456.     }
  457.     else
  458.         printf("setsmp: invalid or insufficient parameters\n");
  459.  
  460. }
  461.  
  462.  
  463.  
  464. /*----------------------------------------------------------------------
  465. PROCEDURE
  466.     SETMOT  -  allocate and initialize data structures for the number
  467.                 of motors requested by the user.
  468. SYNOPSIS
  469.     void setmot(void)
  470.  
  471. REMARKS
  472.     This routine must be called as the first action of the main program.
  473.  
  474.     Motor descriptors initialized from defaults specified in "motdef"
  475.  
  476. LAST UPDATE
  477.     20 March 1988
  478.         use ANSI features
  479. ----------------------------------------------------------------------*/
  480.  
  481. void setmot()
  482. {
  483.     MOTOR *pm;          /* pointer to motor data structures */
  484.     int nm;             /* number of motors */
  485.     char cbuf[20];      /* input line */
  486.     int damin, damax;   /* D/A output limits */
  487.     int done;           /* flag, if set, o.k. to return */
  488.     int i;              /* loop index */
  489.  
  490.  
  491.     do
  492.     {
  493.         done = 1;
  494.  
  495.         /*
  496.          *  Prompt user and get response line.  Because scanf() does not
  497.          *  remove the newline character at the end of lines, fgets() is
  498.          *  used instead since the newline character may cause trouble
  499.          *  with other console functions.
  500.          */
  501.  
  502.         fputs("How many motors? ", stdout);
  503.         fgets(cbuf, 20, stdin);         /* get at most 19 characters */
  504.  
  505.         if (sscanf(cbuf, "%d", &nm) == 1)   /* decode input */
  506.         {
  507.             da_limits(&damin, &damax);      /* find DAC limits */
  508.  
  509.             motdef.mmin = (double)damin / MTODAC;   /* set output limits */
  510.             motdef.mmax = (double)damax / MTODAC;
  511.  
  512.             /*
  513.              *  Dynamically allocate memory for the required number of
  514.              *  motor structures.  Note that calloc() is used instead of
  515.              *  malloc() since calloc() is suppose to guarantee that the
  516.              *  allocated block of memory has the correct alignment.
  517.              *  Note also the use of "casts" in the allocation statement;
  518.              *  though not strictly necessary, it is recommended as good
  519.              *  programming style.
  520.              */
  521.  
  522.             if ((pm0 = (MOTOR *)calloc(nm, sizeof(MOTOR))) == (MOTOR *)NULL)
  523.             {
  524.                 printf("INSUFFICIENT MEMORY: ");
  525.                 printf("cannot allocate %d motor blocks\n", nm);
  526.                 exit(1);
  527.             }
  528.  
  529.             for (i = 0, pm = pm0; i < nm; i++, pm++)
  530.             {
  531.                 cpmotor(&motdef, pm);   /* init with defaults from motdef */
  532.             
  533.                 pm->ichan = i;  /* set both A/D and D/A channels to i; the */
  534.                 pm->mchan = i;  /*   actual channels depends on your setup */
  535.             }
  536.  
  537.             nmotors = nm;       /* record number of motor descriptors */
  538.  
  539. #ifdef SIMRT
  540.             sim_init(nm);   /* if real-time simulation is to be used, */
  541. #endif                      /*   initialize the simulation module.    */
  542.  
  543.             if (nm == 1)
  544.                 printf("\nmotor 0 has been initialized\n");
  545.             else
  546.                 printf("\nmotors 0 to %d have been initialized\n", nm - 1);
  547.         }
  548.         else
  549.         {
  550.             printf("invalid entry\n");
  551.             done = 0;
  552.         }
  553.     }
  554.     while (!done);
  555.  
  556. }
  557.  
  558.  
  559. /***********************************************************************
  560.                     P R I V A T E    R O U T I N E S 
  561. ***********************************************************************/
  562.  
  563. /*----------------------------------------------------------------------
  564. PROCEDURE
  565.     PIMOTOR  -  apply PI control for motor
  566.  
  567. SYNOPSIS
  568.     static void pimotor(pm, prnt)
  569.     MOTOR *pm;
  570.     int prnt;
  571.  
  572. PARAMETERS
  573.     pm    -  pointer to motor structure requiring control
  574.     prnt  -  if 1, print control output
  575.  
  576. LAST UPDATE
  577.     20 March 1988
  578.         use ANSI features
  579. ----------------------------------------------------------------------*/
  580.  
  581. static void pimotor(pm, prnt)
  582. MOTOR *pm;
  583. int prnt;
  584. {
  585.     double m1;      /* intermediate result in control calculation. */
  586.     double error;   /* error between reference and measured */
  587.  
  588.  
  589.     pm->v = getv(pm->ichan);            /* obtain motor velocity */
  590.  
  591.     error = pm->vref - pm->v;           /* calculate error term */
  592.     pm->ival += error;                  /* update integral of error */
  593.     m1 = pm->kp * error + pm->vcon;     /* do P control first */
  594.  
  595.     /*
  596.      *  Check for actuator limits and apply "reset windup" control.
  597.      *  If the output would exceed actuator limits, set the integral
  598.      *  term to zero to prevent the integral term from dominating
  599.      *  the output and slow down corrective action.
  600.      */
  601.  
  602.     if (m1 > pm->mmax)
  603.     {
  604.         pm->m = pm->mmax;
  605.         pm->ival = 0.0;
  606.     }
  607.     else if (m1 < pm->mmin)
  608.     {
  609.         pm->m = pm->mmin;
  610.         pm->ival = 0.0;
  611.     }
  612.     else    /* output withing actuator limits */
  613.     {
  614.         pm->m = m1 + (pm->ki * pm->ival);   /* now add integral term */
  615.     }
  616.  
  617.     if (prnt)               /* print controller output */
  618.     {
  619.         int mn;             /* motor number */
  620.     
  621.         mn = (pm - pm0);    /* compute motor number for output */
  622.     
  623.         printf("motor = %d, v = %7.3lf, m = %7.3lf\n", mn, pm->v, pm->m);
  624.     }
  625.  
  626.     mact(pm->mchan, pm->m); /* send controller output to actuator */
  627.  
  628. }
  629.  
  630.  
  631.  
  632. /*----------------------------------------------------------------------
  633. PROCEDURE
  634.     CPMOTOR  -  copy one motor descriptor to another
  635.  
  636. SYNOPSIS
  637.     static void cpmotor(from, to)
  638.     MOTOR *from, *to;
  639.  
  640. PARAMETERS
  641.     from  -  pointer to source
  642.     to    -  pointer to destination
  643.  
  644. REMARKS
  645.     Standard (K&R) C does not allow entire structures to be assigned
  646.     (this will change with the proposed ANSI X3J11 standards).  This
  647.     restriction is very inconvenient and this routine gets around this
  648.     by doing a byte-by-byte copy of the descriptor contents.  This
  649.     technique may not work on all computers, but it is reasonably
  650.     robust and is much easier than doing field by field assignments.
  651.  
  652.     If you are using an ANSI conforming C compiler, just do a structure
  653.     assignment.  The compiler will take care of the rest.
  654.  
  655. LAST UPDATE
  656.     20 March 1988
  657.         use ANSI features
  658. ----------------------------------------------------------------------*/
  659.  
  660. static void cpmotor(from, to)
  661. MOTOR *from, *to;
  662. {
  663.  
  664. #ifdef ANSI     /* can do structure assignment */
  665.  
  666.     *to = *from;
  667.  
  668. #else           /* do explicit byte by byte copy */
  669.  
  670.     register char *src, *dst;   /* source and destination pointers */
  671.     unsigned count;             /* byte count */
  672.  
  673.  
  674.     src = (char *)from;     /* make fast copy of source pointer */
  675.     dst = (char *)to;       /* make fast copy of destination pointer */
  676.  
  677.     for (count = 0; count < sizeof(MOTOR); count++)
  678.         *dst++ = *src++;
  679.  
  680. #endif
  681.  
  682. }
  683.  
  684.  
  685.  
  686. /*----------------------------------------------------------------------
  687. FUNCTION
  688.     GETV  -  get current motor velocity
  689.  
  690. SYNOPSIS
  691.     static double getv(channel)
  692.     int channel;
  693.  
  694. PARAMETER
  695.     channel  -  A/D channel from which to read the velocity
  696.  
  697. RETURNS
  698.     current motor velocity
  699.  
  700. REMARKS
  701.     Note that the conversion of raw ADC units to velocity units
  702.     uses the ADCTOV conversion factor.
  703.  
  704. LAST UPDATE
  705.     20 March 1988
  706.         use ANSI features
  707. ----------------------------------------------------------------------*/
  708.  
  709. static double getv(channel)
  710. int channel;
  711. {
  712.  
  713.     return((double)ad_wread(channel) * ADCTOV);
  714. }
  715.  
  716.  
  717.  
  718. /*----------------------------------------------------------------------
  719. PROCEDURE
  720.     mact  -  send controller output to D/A converter
  721.  
  722. SYNOPSIS
  723.     static void mact(channel, output)
  724.     int channel;
  725.     double output;
  726.  
  727. PARAMETERS
  728.     channel -  D/A channel number
  729.     output  -  controller output
  730.  
  731. REMARKS
  732.     Multiply by conversion factor and add 0.5 for rounding.
  733.  
  734. LAST UPDATE
  735.     20 March 1988
  736.         use ANSI features
  737. ----------------------------------------------------------------------*/
  738.  
  739. static void mact(channel, output)
  740. int channel;
  741. double output;
  742. {
  743.     
  744.     da_write(channel, (int)((output * MTODAC) + 0.5));
  745. }
  746.