            /* 
.nf
.pl 66
.po 10
            Copyright 1993  Novatech Instruments, Inc.
            Seattle, WA 98102

FILE:       dds3pc.c

This file, written for and compiled by Power C, controls the Novatech
Instruments, Inc. model DDS3-PC Direct Digital Synthesizer.  It
is provided for exclusive use with Novatech Instruments, Inc. products
and is without warranty, expressed or implied.

Author:

     Steven D. Swift, P.E.
     Principal Engineer
     Novatech Instruments, Inc.

*/

#include <stdio.h>
#include <conio.h>
#include <sys\types.h>
#include <sys\timeb.h>

#define NO         0
#define YES        1
#define BUFSIZE   80
#define MAXOUT  1000
#define BASE  0x0338

/* BASE is the factory set address of the DDS3-PC board */

    FILE *fileptr, *fopen();

void main( int argc, char *argv[] )
{
    char buf[BUFSIZE]; /* buffer for each text input line */
    char *bufp;        /* pointer into text buffer */
    int ch;            /* character holder */
    int count=0;       /* index number of out records read in file */
    int START=0;       /* value of count at the start of a sweep file */
    int STOP=0;        /* value of count at the end of a sweep file */
    int RESET=NO;      /* if YES then a reset command is requested */
    int i;             /* dummy counter used as necessary */
    int control;       /* bin control word with clock and atten */
    int eof;

    unsigned long DDS_base = 16777216;  /* Qualcomm chip is 24-bits */
    unsigned int addr=BASE;             /*address of board*/
    double defclock=33.554432e6; /*clock input frequency, if <1 then default */
    double clock;

    static double   freq[MAXOUT];
    static unsigned long DDS_m[MAXOUT]; /*binary value of programming word*/
    static double   amp[MAXOUT];
    static int      atten[MAXOUT];
    static double   dwell[MAXOUT];
    static unsigned long msec[MAXOUT]; /*dwell in integer msec for each out */

/* variables used in immediate mode only */
    double   ampi;  
    int      atteni;
    double   freqi;
    unsigned long DDS_mi; /*programmed value sent to DDS IC */

/* timing parameters */
    struct timeb start, end;     /* used for measuring time of system */
    unsigned long counter=0x049a; /* number to give 1ms with 486/33 LB */
    unsigned int c1 = 0;  /*loop counter in timing adjust routine */
    int c;  /* lsb of timer */
    int d;  /* msb of timer */
    unsigned long DEFCOUNT = 10000 ; /* default timing adjust for 10 seconds */
/* change DEFCOUNT to 0 to skip this adjustment */
    double startime=0;
    double endtime=0;
    double fudge;

    double scratch; /* scratch variable used where needed */

/* MAIN BEGINS HERE */

   reset(addr);  /*sends a reset to BASE addr, doesn't affect changed addr*/

/* get an estimate of system speed by measuring length
   of time it takes to execute this loop and calculate a 
   factor for use in the timing of "dwell."  */

   printf("\n\n\nModel DDS3-PC Direct Digital Synthesizer Control Software\n");
   printf("\nCopyright 1993 NOVATECH INSTRUMENTS, INC., Seattle, WA 98102\n\n");

if (DEFCOUNT > 0 && argc > 1)  {
   printf("\nPlease wait %ld seconds while I adjust to your system speed...\n",
         DEFCOUNT/1000 );
/* get counter as two bytes */
    c = (int) 0x0ff & counter;  /*lsb*/
    d = (int) (0x0ff & ( counter >> 8) ); /*msb*/

    ftime(&start);
        setdwell( DEFCOUNT, c, d ) ;  
    ftime(&end);
    startime = ( (double) start.time ) + ( (double) start.millitm)/1000.0;
    endtime = ( (double) end.time ) + ( (double) end.millitm)/1000.0;

    fudge = ( (double) DEFCOUNT/1000 ) / ( endtime - startime);
    fudge = ( (double) counter ) * fudge;
    counter = (unsigned long) fudge;
    if ( fudge - (double) counter >= 0.5 ) counter++;

/* find new integers for use in setdwell() function */
    c = (int) 0x0ff & counter;  /*lsb*/
    d = (int) (0x0ff & ( counter >> 8) ); /*msb*/
}
/* use default values if DEFCOUNT = 0 */
/* end of system measurement */

/******* IMMEDIATE MODE OF OPERATION ************/

    if (argc == 1)     /* must be in immediate mode */
       {
     printf("\nInput your board address in Hex (0=default): ");
     scanf("%lx", &addr);
     if ( addr < 1) addr = BASE;
     if( (addr < 0x0200) || (addr > 0x03f8) ) { 
         printf("\nBOGUS Address:  address out of range.  Check hardware.\n");
         exit(); }
     printf("Input your clock frequency in Hz (0=default): ");
     scanf("%lf", &clock);
     if (clock < 1.0) clock = defclock;
     if (clock == defclock)
        control = 0x00;
     else 
        control = 0x10;  /* set clock for external input */
     if( clock > 40.0e6 ) {
         printf("\nBOGUS Clock: greater than 40MHz. Exceeds specifications.\n");
         exit(); }

     while(1)  /* always loop until exit() is signalled */
       {
       printf("\nInput your output frequency in Hz (0=quit): ");
       scanf("%lf", &freqi);
       if ( freqi <=  0.0 ) exit();
       scratch = ( (double) DDS_base)*freqi/clock;
       DDS_mi =  (unsigned long) scratch;
       if ( (scratch - ( (double) DDS_mi ) ) >= 0.5 )  DDS_mi++;
       if ( DDS_mi >= DDS_base/2 ) {
            printf("BOGUS frequency, violates Nyquist\n");
            exit();
            }
       printf("Input your attenuation in dB (max 70db): ");
       scanf("%lf", &ampi);
       ampi = ampi/10.0;  /* change to # of steps */
       atteni = (int) ampi;  /* cast to integer */
       if( (ampi - ((double)atteni)) >= 0.5 ) atteni++;
       if (atteni > 7) atteni=7;
/* adjust for bit order in output attenuator */
       atteni = ((atteni<<2)&0x04)|((atteni>>2)&0x01)|(atteni&0x02);
/* bits 0 and 2 now swapped */
       atteni = atteni << 5 ; /* shift bits to proper position, low bits=0 */
       atteni = atteni | control ;  /* combine in clock bits */
       writeout(DDS_mi,atteni,addr); /*send to output-- note that clock and
                                    attenuation are set at same time */
       }
       exit();  /*this is normal exit from immediate mode*/
      }

/********** FILE MODE OF OPERATION **************/

    if (argc == 2)     /* must be in file mode */
       {
    if (( fileptr = fopen(argv[1],"r")) == NULL)
       { printf("CAN'T OPEN FILE %s\n", argv[1]);
         exit(); }
    clock = 0.0;  /* set up to get default clock unless changed */

/* read file and store all the values */
while (!eof) {
    bufp = buf;
    /* get a line of text */
    do {               if ((ch = getc(fileptr)) == EOF) eof = YES;
           else *bufp = tolower(ch);  /*convert to lower to accept either */
       }
    while ((!eof) && (*bufp++ != '\n'));
    *bufp = '\0';          /* need null terminator for puts() */

/* if 'out' process the freq record */
if (!strncmp (buf, "out", 3)) {
        if (count > MAXOUT) {
        printf("\nERROR-- too many output points.\n",buf);
        exit(); }

 if (sscanf (buf,"out %lf %lf %lf", &freq[count],
                        &amp[count],
                        &dwell[count]) != 3) 
           { fprintf (stderr, "ERROR! - Bad output record: %s", buf);
            exit(); }
     count++;
              }

/* else ignore commented lines */
   else if (*buf == '#') ;

/* else if 'clock' process the clock select input */
else if (!strncmp (buf, "clock", 5))
          { if (sscanf (buf, "clock %lf",&clock) != 1)
              { fprintf (stderr, "ERROR! - Bad clock record: %s", buf);
               exit(); }
          }

/* else if 'start' process a start flag input */
else if (!strncmp (buf, "start", 5)) {
     START = count; }

/* else if 'stop' process an stop flag input */
else if (!strncmp (buf, "stop", 4)) {
     STOP = count; }

/* else if 'addr' process the addr select input */
else if (!strncmp (buf, "addr", 4))
          {
            if (sscanf (buf, "addr %lx",&addr) != 1)
              { fprintf (stderr, "ERROR! - Bad addr record: %s", buf);
               exit(); }
            if( (addr < 0x0200) || (addr > 0x03f8) )
              { fprintf (stderr, "ERROR! - Bad addr record: %s", buf);
               exit(); }
         }

/* else if 'reset', process a reset record */
    else if (!strncmp (buf, "reset", 5)) {
     RESET = YES; }
}
}     
    fclose(fileptr);  /* close the file as we are finished with it */
/*
   all data is now collected so process the outputs
   this area uses functions to output the
   addresses and binary data to the pc-card
*/
/* only the last clock record counts */

     if (clock < 1.0) clock = defclock;
     if (clock > 40.0e6) {
        printf("BOGUS: Clock > 40 MHz exceeds specifications.\n");
        exit();  }
     if (clock == defclock)
        control = 0x00;
     else
        control = 0x10;  /* set clock for external input */

   if ( RESET == YES ) reset(addr);

/* convert all frequency inputs into a long integer representing
   the value to send to the DDS IC */

   i = 0;
   while ( i < count ) {
       msec[i] = (unsigned long) ( 1000.0*dwell[i] ); /* convert to integer */
       if ( freq[i] <= 0.0 ) {
          printf("BOGUS Frequency: zero or negative!\n");
          exit();  }
       scratch = ( (double) DDS_base)*freq[i]/clock;
       DDS_m[i] = (unsigned long) scratch;
       if ( (scratch - ( (double) DDS_m[i] ) ) >= 0.5 )  DDS_m[i]++;
       if ( DDS_m[i] >= DDS_base/2 ) {
            printf("BOGUS frequency, violates Nyquist\n");
            exit(); }
       amp[i] = amp[i]/10.0;  /* change to # of steps */
       atten[i] = (int) amp[i];  /* cast to integer */
       if( (amp[i] - ((double)atten[i])) >= 0.5 )  atten[i]++;
       if (atten[i] > 7) atten[i]=7;
/* adjust for bit order in output attenuator */
       atten[i] = ((atten[i]<<2)&0x04)|((atten[i]>>2)&0x01)|(atten[i]&0x02);
/* bits 0 and 2 now swapped */
       atten[i] = atten[i] << 5 ; /*shift bits to proper position, low bits=0 */
       atten[i] = atten[i] | control ;  /* combine in clock bits */
       i++;
     }

/* now output the values and time them appropriately */

/* this is a once through loop and is executed one time only.
   if START or STOP records exists then the values between start
   and stop records are continuously swept */

if( (START==0) && (STOP==0) ) {
      i = 0;
      printf("\nBegin once-through output\n");
      while ( i < count ) {
           writeout(DDS_m[i],atten[i],addr);
          if 
               (msec[i] < 1 ) {
     printf("Output is %lf Hz.  Hit return to resume, ^c to abort.\n",freq[i]);
               while(!getchar()); }
          else
               setdwell(msec[i],c,d);
               i++; }
      printf("\nEnd once-through output\n");
}

/* this is the continuous sweep mode */
if( (START>0) || (STOP>0) ) {
if(START >= STOP) printf("BOGUS Sweep Record: check output file!\n");
printf("\nSweeping...\n");
printf("Unless paused, hit any key to abort sweep\n");
     while(!kbhit()){
      i = START; 
      while ( i < STOP ) {
          writeout(DDS_m[i],atten[i],addr);
          if
               ( msec[i] < 1 ) {
printf("Sweep paused at %lf Hz. Hit return key to resume, ^c to abort.\n",freq[i]);
               while(!getchar()); }
          else
               setdwell(msec[i],c,d);
          if(kbhit()) exit();
          i++; }
     }
}
       exit();  /*this is normal exit from file mode*/

    if (argc > 2)   /* must be bogus input command line */
       { printf("\nBOGUS INPUT LINE: check your command and run again.\n");
         exit(); }
}       
/* end of main() */

/***************FUNCTIONS FOLLOW************************/

/* No values are returned by any of these function */

/* WRITE THE OUTPUT SIGNAL FUNCTION */

/* this function assumes that the amplitude value has been converted
    to an attenuation value and combined with the clock select control
    lines to prevent problems with the clock lines */

    void writeout(unsigned long DDS_m, int atten, unsigned int addr)
       {  int lowbyte;
          int midbyte;
          int highbyte;
          unsigned long mask=0x0ff;

          lowbyte = (int) ( mask & DDS_m );
          midbyte = (int) ( mask & (DDS_m >> 8) );
          highbyte = (int) ( mask & (DDS_m >> 16) );

          outp(addr+3,lowbyte);  /* send DDS_m a byte at a time */
          outp(addr+4,midbyte);
          outp(addr+5,highbyte);
          outp(addr+1,0xff); /* send hop clock, value is don't care */
          outp(addr+0,atten); /* send the attenuation value */
       }

 /* RESET THE BOARD FUNCTION */

    void reset(unsigned int addr)
     {
       outp(addr+0,0xe0); /* internal clock, max attenuation */
       outp(addr+2,0xff); /* data is don't care */
     }

/* SET THE DWELL TIME FUNCTION */

/* This timing function reprograms timer #2 in the PC.
   No attempt is made to control other timing functions
   going on inside the PC, so at best it is only good to
   1-2%.  This should be fine for most applications.
   The default timing parameters give good timing on
   fast machines.  The slower your PC, the worse the overhead */

void setdwell(unsigned long counter, int lsb, int msb)
     {  int a,b;
/* overhead function to make sure this is cool */
/* set up control register in PC */
    a = inp(0x061);  /* get control register status from PC */
    b = a;  /* save it for now */
    a = a & 0xfc; /*enable clock, disable speaker*/
    a = a | 0x01;
    outp(0x061,a); /* write it */

/* set timer control word to 1011 0000: counter 2, lsb-msb, mode 0, binary */
    outp(0x043,0xb0);  /* two byte mode*/

     while( counter > 0 ) {
       outp(0x042,lsb);
       outp(0x042,msb+1);  /*msb to msb + 1 because of > 0 compare */

       do { outp(0x43,0x80);  /*latch timer value */
            inp(0x42); }    /*get lsb*/ 
       while ( inp(0x042) ) ; /*repeat until msb goes to zero*/

       counter--; }
       outp(0x061,b); /* put back control register as it was */
     }

