/* 
            Copyright 1999  Novatech Instruments, Inc.
            Shoreline, WA 98133

FILE:       dds7pc_linux.c

This program, written for Linux (2.0.36) and gcc (2.7.2.3), controls the
Novatech Instruments, Inc. model DDS7pc Direct Digital Synthesizer.

It was derived from dds7pc.c, the DOS/Windows version.

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.

     Ported to Linux by Scott Kazimour

Revision History:
	Rev  1.0        May 2, 1999         First Linux version.
		
*/

#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <termios.h>
#include <ctype.h>
#include <signal.h>
#include <sys/perm.h>
#include <asm/io.h>

#define outp(port, value) outb(value, port)

#define NO         0
#define YES        1
#define BUFSIZE   80
#define MAXOUT  1000
#define PAGESIZE 8   /* size of interface to DDS7pc board, in bytes */
#define BASE  0x0338 /* BASE is the factory set address of the DDS7pc board */
                     /* This must be <= 0x03f8 */


void reset(unsigned int addr);
void writeout(unsigned long DDS_m, int atten, unsigned int addr);
void setdwell(unsigned long counter);
int kbhit(void);
void reset_keypress(void);
void set_keypress(void);
void term_cleanup(int signal);
void term_init(void);
void quit(int status);

FILE *fileptr;
static struct termios stored;
uid_t uid;

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=NO;
    int rc;

    double DDS_base = 4294967296.0;  /* AD9851 chip is 32-bits */
    unsigned int addr=BASE;             /*address of board*/
    double defclock=DDS_base/150.0; /*clock input freq, 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 */

    /*
       address definitions:
       BASE+0	FS0-FS7
       BASE+1	FS8-FS15
       BASE+2	FS16-FS23
       BASE+3	FS24-FS31 (msb)
       BASE+4	AMP0-AMP3, Bit 7=HIGH -> Ext Clk Sel
       BASE+5	PS0-PS5, bit7=HIGH -> DDS chip reset.
       BASE+6  unused
       BASE+7	data not used, write to here generates update.
    */

    double scratch; /* scratch variable used where needed */

    /* MAIN BEGINS HERE */

    uid = getuid(); /* save real user id */

    /* set real and saved uids to 0 so we can get back */
    if (setuid(0)) {
        printf("File must be setuid root to run.\n");
        exit(1);
    }

    if (ioperm((unsigned long)BASE, (unsigned long)PAGESIZE, 1)) {
        perror("call to ioperm() failed");
        exit(-1);
    }

    if (seteuid(uid)) {
        printf("Unknown error\n");
        exit(1);
    }

    term_init();

    printf("\n\n\nModel DDS7pc Direct Digital Synthesizer Control Software\n");
    printf("\nCopyright 1999 NOVATECH INSTRUMENTS, INC., Seattle, WA 98133\n\n");

    /******* IMMEDIATE MODE OF OPERATION ************/

    if (argc == 1) {    /* must be in immediate mode */
        printf("\nInput your board address in Hex (0=default): ");
        rc = scanf("%x", &addr);
        if ( addr < 1 ) {
            addr = BASE;
        }
        if (addr < 0x0200 || addr > 0x03f8) { 
            printf("\nBOGUS Address:  address out of range.  Check hardware.\n");
            quit(1);
        }

        printf("Input your clock frequency in Hz (0=default): ");
        rc = scanf("%lf", &clock);
        if (clock < 1.0) {
            clock = defclock;
            control = 0x00;
        } else {
            control = 0x80;  /* set clock for external input */
        }

        if ( clock > 30.0e6 ) {
            printf("\nBOGUS Clock: greater than 30MHz. Exceeds specifications.\n");
            quit(1);
        }

        if ( clock < 5.0e6 ) {
            printf("\nBOGUS Clock: less than 5MHz. Below specifications.\n");
            quit(1);
        }

        reset(addr);  /* sends a reset to addr*/

        while (1)  /* always loop until quit() is signalled */
        {
            printf("\nInput your output frequency in Hz (0=quit, 1=reset): ");
            scanf("%lf", &freqi);

            if (freqi <=  0.0) {
                quit(0);
            }

            /* add reset for low values of frequency.  If reset twice, 
               then exit */
            if (freqi < 5.0 ) {
                reset(addr);
                printf("\n\nInput your output frequency in Hz (0=quit, 1=reset): ");
                scanf("%lf", &freqi);

                if (freqi <=  0.0) {
                    quit(0);
                }
                if (freqi < 5.0 && freqi) {
                    quit(0);
                }
            }

            scratch = (DDS_base)*freqi/(6.0*clock);
            /* 6.0 factor because AD9851 has internal mult */

            DDS_mi =  (unsigned long) scratch;
            if ( (scratch - ( (double) DDS_mi ) ) >= 0.5 ) {
                DDS_mi++;
            }
            if ( DDS_mi >= DDS_base/2.5 ) {
                printf("BOGUS frequency, violates Nyquist\n");
                quit(1);
            }
            printf("Input your attenuation in dB (max 60dB): ");
            scanf("%lf", &ampi);
            ampi = ampi/4.0;  /* change to # of steps */
            atteni = (int) ampi;  /* cast to integer */
            if( (ampi - ((double)atteni)) >= 0.5 ) {
                atteni++;
            }
            if (atteni > 15) {
                atteni=15;
            }

            /* invert as 0=atten in place, XOR with FF */
            atteni = atteni^0x0ff ;
            atteni = atteni&0x0f;  /* mask off upper bits */
            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 */
        }
        quit(0);  /*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]);
            quit(1);
        }
        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: %s\n",buf);
                    quit(1);
                }

                if (sscanf (buf,"out %lf %lf %lf", &freq[count],
                            &amp[count],
                            &dwell[count]) != 3) 
                {
                    fprintf (stderr, "ERROR! - Bad output record: %s", buf);
                    quit(1);
                }
                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);
                    quit(1);
                }
            }

            /* 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 %x",&addr) != 1) {
                    fprintf (stderr, "ERROR! - Bad addr record: %s", buf);
                    quit(1);
                }
                if ( (addr < 0x0200) || (addr > 0x03f8) ) {
                    fprintf (stderr, "ERROR! - Bad addr record: %s", buf);
                    quit(1);
                }
            }

            /* 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 */

    reset(addr);  /* sends a reset to addr */

    if (clock < 1.0) {
        clock = defclock;
    }
    if (clock > 30.0e6) {
        printf("BOGUS: Clock > 30 MHz exceeds specifications.\n");
        quit(1);
    }
    if (clock < 5.0e6) {
        printf("BOGUS: Clock < 5 MHz below specifications.\n");
        quit(1);
    }
    if (clock == defclock) {
        control = 0x00;
    } else {
        control = 0x80;  /* 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");
            quit(1);
        }
        scratch = DDS_base * freq[i]/(6.0 * clock);
        /* x6 because of internal multiplier on AD9851 */
        DDS_m[i] = (unsigned long) scratch;
        if ((scratch - ((double) DDS_m[i])) >= 0.5) {
            DDS_m[i]++;
        }
        if (DDS_m[i] >= DDS_base/2.5) {
            printf("BOGUS frequency, violates Nyquist\n");
            quit(1);
        }
        amp[i] = amp[i]/4.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] > 15) {
            atten[i] = 15;
        }
        /* invert as 0=atten in place, XOR with FF */
        atten[i] = atten[i] ^ 0x0ff ;
        atten[i] = atten[i] & 0x0f;  /* mask off upper bits */
        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 %f Hz.  Hit return to resume, ^c to abort.\n",freq[i]);
                while(!getchar());
            } else {
                setdwell(msec[i]);
            }
            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");
        set_keypress(); // set input attributes to single-character mode
        while (!kbhit()) {
            i = START; 
            while (i < STOP) {
                writeout(DDS_m[i], atten[i], addr);
                if (msec[i] < 1) {
                    printf("Sweep paused at %f Hz. Hit return key to resume, ^c to abort.\n",freq[i]);
                    while (!getchar());
                } else {
                    setdwell(msec[i]);
                }
                if (kbhit()) {
                    quit(0);
                }
                i++;
            }
        }
        reset_keypress(); // reset input attributes
    }
    quit(0);  /*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");
        quit(1);
    }
}       
/* end of main() */

/***************FUNCTIONS FOLLOW************************/


/* 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;
    int highestbyte;
    unsigned long mask=0x0ff;

    lowbyte = (int) ( mask & DDS_m );
    midbyte = (int) ( mask & (DDS_m >> 8) );
    highbyte = (int) ( mask & (DDS_m >> 16) );
    highestbyte = (int) ( mask & (DDS_m >> 24) );

    outp(addr+4, atten); /* send the attenuation value */
    outp(addr+0, lowbyte);  /* send DDS_m a byte at a time */
    outp(addr+1, midbyte);
    outp(addr+2, highbyte);
    outp(addr+3, highestbyte);
    outp(addr+7, 0xff); /* send update pulse, value is don't care */
}

/* RESET THE BOARD FUNCTION */

void
reset(unsigned int addr)
{
    outp(addr+4, 0x00); /* send max attenuation value, int clock */
    sleep(1); /* wait for int clock to stabilize */

    outp(addr+5, 0x00);  /* phase =0, reset=low*/
    outp(addr+5, 0x80);  /*phase =0, reset=HIGH*/
    outp(addr+5, 0x00);  /* phase =0, reset=low*/

    writeout(0x00, 0x00, addr);  /* dummy write to clear registers */
}

/* SET THE DWELL TIME FUNCTION */
void
setdwell(unsigned long counter)
{
    struct timespec req;

    req.tv_sec = (time_t) (counter / 1000);
    req.tv_nsec = (counter % 1000) * 1000000;

    if (nanosleep(&req, 0)) {
        perror("nanosleep() returned error!");
    }
}


void 
set_keypress(void)
{
    struct termios new;

    memcpy(&new, &stored, sizeof(struct termios));

    /* Disable canonical mode and echo, and set buffer size to 1 byte */
    new.c_lflag &= (~ICANON);
    new.c_lflag &= (~ECHO);
    new.c_cc[VTIME] = 0;
    new.c_cc[VMIN] = 1;

    tcsetattr(0, TCSANOW, &new);
    return;
}

void 
reset_keypress(void)
{
    tcsetattr(0, TCSANOW, &stored);
    return;
}

int 
kbhit(void)
{
    fd_set readfds;
    struct timeval timeout;
    int rc;

    timeout.tv_sec = 0;
    timeout.tv_usec = 0;

    FD_ZERO(&readfds);
    FD_SET(STDIN_FILENO, &readfds);

    rc = select(STDIN_FILENO + 1, &readfds, 0, 0, &timeout);

    if (rc < 0) {
        perror("call to select() failed");
    } else if (rc > 0) {
        getchar(); /* get rid of input character */
    }

    return rc;
}

void 
term_cleanup(int signal)
{
    reset_keypress();
    exit(0);
}

void 
quit(int status)
{
    reset_keypress();
    exit(status);
}

void 
term_init(void)
{
    struct sigaction sact;

    /* 
     * save terminal settings so we can restore them on exit
     */
    tcgetattr(0, &stored); 

    /*
     * set up signal handler to restore terminal state
     */
    sact.sa_handler = term_cleanup;
    sigaction(SIGINT, &sact, 0);
    sigaction(SIGTERM, &sact, 0);
}

