/*
 * Name:        1bitla  (one bit logic analyzer)
 *
 * Description: A program to drive the one-bit data acquisition circuit
 *              and to perform post processing (logic analysis) on the
 *              data stream.   Please see http://www.linuxtoys.com for
 *              the schematic of the data acquisition circuit.
 *
 * Syntax:      1bitla [options] tty_port
 *              Options: -r   raw data
 *                       -c   value,count output (default)
 *                       -t   value,seconds output
 *
 * Example:     1bitla -c /dev/ttyS1
 *              0,44
 *              1,45
 *              0,45
 *              ..........
 *
 */

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <termio.h>
#include <termios.h>
#include <sys/types.h>
#include <sys/fcntl.h>
#include <sys/signal.h>
#include <linux/serial.h>

#define NREAD  512

void proc_cmdline(int, char *[], char *, char *);
int  setup_port(char *);
void display_smpl(unsigned char *, int, char);
void logic_analysis(int, int, char);
static float period=(1.0/46080.0);


int main (int argc, char *argv[])
{
    char    portname[FILENAME_MAX];  /* serial port as a string */
    int     fd_serial;               /* file descriptor to serial port */
    char    mode;                    /* output mode:raw,count,time  -r,-c,-t */
    int     count;                   /* count of bytes read */
    unsigned char inbuf[NREAD];      /* the array of bytes read */


    /* Process command line arguments.  Get the name of the serial port */
    proc_cmdline(argc, argv, portname, &mode);


    /* Open the serial port at 8,N,1,57600.  */
    fd_serial = setup_port(portname);


    /* Loop forever reading data bytes */
    while (1) {
        /* There is no background processing, so we use a blocking read */
        count = read(fd_serial, inbuf, NREAD);
        if (count < 0) {
            /* we've encountered an error of some kind.  Just exit for now. */
            exit(0);
        }

        display_smpl(inbuf, count, mode);
    }
    return(0);
}


/******************************************************************
* proc_cmdline()
* Get and process the command line arguments.  Get the serial port.
******************************************************************/
void proc_cmdline(
    int argc,           /* count of command line arguments */
    char *argv[],       /* strings of command line arguments */
    char *portname,     /* where to copy the name of the serial port */
    char *mode)         /* r,c,t: raw, count, or time mode */
{
    if (argc == 2)
        *mode = 'c';
    else
        *mode =  getopt(argc, argv, "rct");

    if (*mode == '?') {
        printf("Usage:\n%s [option] serial_port\noption -r : ", argv[0]);
        printf("raw\n       -c : value,count\n       -t : value,time\n");
        exit(0);
    }

    strncpy(portname, argv[argc-1], FILENAME_MAX);
}


/******************************************************************
* setup_port()
* Open the serial port specified and return the file descriptor.
* Use 8,N,1,57600 and exit() on any errors.
******************************************************************/
int  setup_port(
    char *portname)     /* the name of the port */
{
    int              fd_dev;
    struct termios   tbuf;

    if (( fd_dev = open(portname, O_RDWR,0)) < 0 ) {
        printf("Unable to open port '%s'.\n", portname);
        exit(1);
    }

    tbuf.c_cflag = CS8|CREAD|B57600|CLOCAL;
    tbuf.c_iflag = IGNBRK;
    tbuf.c_oflag = 0;
    tbuf.c_lflag = 0;
    tbuf.c_cc[VMIN] = 1; /* character-by-character input */
    tbuf.c_cc[VTIME]= 0; /* no delay waiting for characters */
    if (tcsetattr(fd_dev, TCSANOW, &tbuf) < 0) {
        printf("Unable to set device '%s' parameters\n", portname);
        exit(1);
    }

    return(fd_dev);
}


/******************************************************************
* display_smpl()
* Display the samples in the appropriate mode
******************************************************************/
void display_smpl(
    unsigned char *inbuf,   /* array of input samples */
    int count,              /* count of samples in inbuf */
    char mode)              /* mode of display = r,c,t */
{
    static int value=0;     /* the "current" value */
    static int vcnt=0;      /* the sample count at value */
    unsigned char smpl;     /* the 8 bit sample */
    int i;                  /* loop counter */
    int bitmask;            /* used to detect value of individual bit */


    /* if raw mode, just display the values in hex */
    if (mode == 'r') {
        for (i=0; i<count; i++) {
            printf("%02x\n", ~(inbuf[i]) & 0xff);
        }
        return;
    }


    /* else display as value, comma, (count or time), newline */
    for (i=0; i<count; i++) {
        smpl = ~(inbuf[i]);

        if (smpl == 0xff) {
            if (value == 1)
                vcnt += 8;
            else {
                logic_analysis(value, vcnt, mode);
                value = 1;
                vcnt = 8;
            }
        }
        else if (smpl == 0x00) {
            if (value == 0)
                vcnt += 8;
            else {
                logic_analysis(value, vcnt, mode);
                value = 0;
                vcnt = 8;
            }
        }
        else {
            bitmask = 0x80;
            while (bitmask) {
                if ((value && (smpl & bitmask))    /* if both 1 */
                  ||(!value && !(smpl & bitmask))) /* or if both 0 */
                    vcnt++;
                else {
                    logic_analysis(value, vcnt, mode);
                    value = value ^ 1;  /* value just inverts */ 
                    vcnt = 1;
                }
                bitmask = bitmask >> 1;
            }
        }
    }
}


/******************************************************************
* logic_analysis()
* Perform state machine evaluation of input
******************************************************************/
void logic_analysis(
    int value,              /* 0 or 1 */
    int count,              /* count of samples with value */
    char mode)              /* mode of output display */
{
    /* This is where we perform all of the "logic analysis" to  */
    /* relate the input stream to the output stream.            */

    /* In the simple case, just output the data. */
    if (mode == 'c')
        printf("%1d, %d\n", value, count);
    else
        printf("%1d, %f\n", value, (float)count * period);
}