/* Name: main.c

 * Project: Thermostat based on AVR USB driver

 * Author: Christian Starkjohann

 * Creation Date: 2006-04-23

 * Tabsize: 4

 * Copyright: (c) 2006 by OBJECTIVE DEVELOPMENT Software GmbH

 * License: Proprietary, free under certain conditions. See Documentation.

 * This Revision: $Id: main.c 537 2008-02-28 21:13:01Z cs $

 */


 /* Modified by Nick Lott [NAL] 2009 

 */

#include <avr/io.h>

#include <avr/wdt.h>

#include <avr/eeprom.h>

#include <avr/interrupt.h>

#include <avr/pgmspace.h>

#include <util/delay.h>


#include "usbdrv.h"

#include "oddebug.h"


/*

Pin assignment:

PB1 = key input (active low with pull-up)

PB3 = analog input (ADC3)

PB4 = LED output (active high)


PB0, PB2 = USB data lines

*/


#define BIT_LED 4

#define BIT_KEY 1



#define UTIL_BIN4(x)        (uchar)((0##x & 01000)/64 + (0##x & 0100)/16 + (0##x & 010)/4 + (0##x & 1))

#define UTIL_BIN8(hi, lo)   (uchar)(UTIL_BIN4(hi) * 16 + UTIL_BIN4(lo))


#ifndef NULL

#define NULL    ((void *)0)

#endif

int position;


 int distance;

 int last_position=-1;

int velocity;


/* ------------------------------------------------------------------------- */


static uchar    reportBuffer[1];    /* buffer for HID reports */ // [NAL] only one byte report needed

static uchar    idleRate;           /* in 4 ms units */


static uchar    adcPending;

static uchar    isRecording;


static uchar    valueBuffer[64];   // [NAL] This was increased during testing as HID Keyboard, not used.

static uchar    newValueAvalable;  // [NAL] This is used to flag when we have moved the Dial/wheel 


/* ------------------------------------------------------------------------- */


PROGMEM char usbHidReportDescriptor[USB_CFG_HID_REPORT_DESCRIPTOR_LENGTH] = { /* USB report descriptor */

    0x05, 0x01,         // USAGE_PAGE (Generic Desktop)

    0x09, 0x02,         // USAGE (Mouse)              [NAL] now using HID Mouse rather than Keyboard

    0xa1, 0x01,         // COLLECTION (Application)

        0x09, 0x38,        // USAGE (wheel)           [NAL] I got the numbers for this descriptor by snooping

        0x15, 0x81,        // LOGICAL_MINIMUM (-127)  [NAL] .. on my USB mouse and looking at its descriptor

        0x25, 0x7f,        // LOGICAL_MAXIMUM (127)   [NAL] .. rather than reading the documentation properly

        0x75, 0x08,        // REPORT_SIZE (8)         [NAL] Make sure we update the definition of  

        0x95, 0x01,        // REPORT_COUNT (1)        [NAL] .. USB_CFG_HID_REPORT_DESCRIPTOR_LENGTH

        0x81, 0x06,        // INPUT (Data,Var,Rel)    [NAL] .. in "usbconfig.h"

    0xc0                // END_COLLECTION

};

/* We use a simplifed keyboard report descriptor which does not support the

 * boot protocol. We don't allow setting status LEDs and we only allow one

 * simultaneous key press (except modifiers). We can therefore use short

 * 2 byte input reports.

 * The report descriptor has been created with usb.org's "HID Descriptor Tool"

 * which can be downloaded from http://www.usb.org/developers/hidpage/.

 * Redundant entries (such as LOGICAL_MINIMUM and USAGE_PAGE) have been omitted

 * for the second INPUT item.

 */


/* Keyboard usage values, see usb.org's HID-usage-tables document, chapter

 * 10 Keyboard/Keypad Page for more codes.

 */

#define MOD_CONTROL_LEFT    (1<<0)

#define MOD_SHIFT_LEFT      (1<<1)

#define MOD_ALT_LEFT        (1<<2)

#define MOD_GUI_LEFT        (1<<3)

#define MOD_CONTROL_RIGHT   (1<<4)

#define MOD_SHIFT_RIGHT     (1<<5)

#define MOD_ALT_RIGHT       (1<<6)

#define MOD_GUI_RIGHT       (1<<7)


#define KEY_1       30

#define KEY_2       31

#define KEY_3       32

#define KEY_4       33

#define KEY_5       34

#define KEY_6       35

#define KEY_7       36

#define KEY_8       37

#define KEY_9       38

#define KEY_0       39

#define KEY_RETURN  40

#define KEY_PAGE_DOWN 78    // [NAL] An early test used the HID Keyboard descriptor

#define KEY_PAGE_UP 75      // [NAL] .. after checking the velocity of the wheel

#define KEY_DOWN_ARROW 81   // [NAL] .. the apprpriate key is sent.

#define KEY_UP_ARROW 82     // [NAL] .. These are not currently used.

/* ------------------------------------------------------------------------- */


static void buildReport(void)

{

    reportBuffer[0] = -velocity; // [NAL] build a very simple report 

}


static void evaluateADC(unsigned int value)

{

    // [NAL] This section takes the ADC input and calculates speed and direction of the latest movement

    // [NAL] .. it also compensates for wrap around where the signal goes from maximum to minimum at 360 <-> 0 degrees


    int max_position; // This is the maximum position the wheel can have. Equivalent to 360 degrees

    int max_velocity; // This is the maximum speed the wheel can move between two readings

    

    //calculate position

    position = value;                // we just use the ADC count.

    max_position = 1023;  // max position is max ADC count

    max_velocity = max_position>>1;  // Set the max velocity to 180 degrees per reading

    

    // calculate velocity

    if (last_position==-1) last_position=position;  // set the last position on first ADC read

    velocity = position-last_position;              // a simple calculation of velocity

    last_position = position;                       // remember this for next ADC evaluation

    

    //detect and correct for 360deg to 0deg wrap around

    if (velocity > max_velocity )  velocity -= max_position-1;// -1 is to compensate for deadband

    if (velocity < -max_velocity ) velocity += max_position-1;// -1 is to compensate for deadband

    

    // calculate cumlative value

    distance += velocity; // this can give us an absolute value equivalent to a many turn Potentiometer

    

    velocity >>=1 ; // divide by two to limit noise

    

    // limit to signed 8 Bit value

    if (velocity>127) {

        velocity = 127;

    }else if (velocity<-127){

        velocity = -127;

    }   

    

// add deadband around zero to avoid jumpy behaviour 

    // .. when wheel is not being manipulated

    if ((velocity<10)&&(velocity>-10)) velocity = 0; 

    

    // Let the main loop know we need to send a new report to the computer

    newValueAvalable=1;


}


/* ------------------------------------------------------------------------- */


static void setIsRecording(uchar newValue)

{

    isRecording = newValue;

    if(isRecording){

        PORTB |= 1 << BIT_LED;      /* LED on */

    }else{

        PORTB &= ~(1 << BIT_LED);   /* LED off */

    }

}


/* ------------------------------------------------------------------------- */


static void keyPoll(void)

{

static uchar    keyMirror;

uchar           key;


    key = PINB & (1 << BIT_KEY);

    if(keyMirror != key){   /* status changed */

        keyMirror = key;

        if(!key){           /* key was pressed */

            setIsRecording(!isRecording);

        }

    }

}


static void adcPoll(void)

{

    if(adcPending && !(ADCSRA & (1 << ADSC))){

        adcPending = 0;

        evaluateADC(ADC);

    }

}


static void timerPoll(void)

{

static uchar timerCnt;


    if(TIFR & (1 << TOV1)){

        TIFR = (1 << TOV1); /* clear overflow */

        keyPoll();

       if(++timerCnt >= 6){       /* ~ 6/63 second interval */ // [NAL] sped this up for responsiveness 

            timerCnt = 0;

            if(isRecording){

                adcPending = 1;

                ADCSRA |= (1 << ADSC);  /* start next conversion */

            }

        }

    }

}


/* ------------------------------------------------------------------------- */


static void timerInit(void)

{

    TCCR1 = 0x0b;           /* select clock: 16.5M/1k -> overflow rate = 16.5M/256k = 62.94 Hz */

}


static void adcInit(void)

{

    ADMUX = UTIL_BIN8(1001, 0011);  /* Vref=2.56V, measure ADC0 */

    ADCSRA = UTIL_BIN8(1000, 0111); /* enable ADC, not free running, interrupt disable, rate = 1/128 */

}


/* ------------------------------------------------------------------------- */

/* ------------------------ interface to USB driver ------------------------ */

/* ------------------------------------------------------------------------- */


uchar usbFunctionSetup(uchar data[8])

{

usbRequest_t    *rq = (void *)data;


    usbMsgPtr = reportBuffer;

    if((rq->bmRequestType & USBRQ_TYPE_MASK) == USBRQ_TYPE_CLASS){    /* class request type */

        if(rq->bRequest == USBRQ_HID_GET_REPORT){  /* wValue: ReportType (highbyte), ReportID (lowbyte) */

            /* we only have one report type, so don't look at wValue */

            buildReport();

            return sizeof(reportBuffer);

        }else if(rq->bRequest == USBRQ_HID_GET_IDLE){

            usbMsgPtr = &idleRate;

            return 1;

        }else if(rq->bRequest == USBRQ_HID_SET_IDLE){

            idleRate = rq->wValue.bytes[1];

        }

    }else{

        /* no vendor specific requests implemented */

    }

return 0;

}


/* ------------------------------------------------------------------------- */

/* ------------------------ Oscillator Calibration ------------------------- */

/* ------------------------------------------------------------------------- */


/* Calibrate the RC oscillator to 8.25 MHz. The core clock of 16.5 MHz is

 * derived from the 66 MHz peripheral clock by dividing. Our timing reference

 * is the Start Of Frame signal (a single SE0 bit) available immediately after

 * a USB RESET. We first do a binary search for the OSCCAL value and then

 * optimize this value with a neighboorhod search.

 * This algorithm may also be used to calibrate the RC oscillator directly to

 * 12 MHz (no PLL involved, can therefore be used on almost ALL AVRs), but this

 * is wide outside the spec for the OSCCAL value and the required precision for

 * the 12 MHz clock! Use the RC oscillator calibrated to 12 MHz for

 * experimental purposes only!

 */

static void calibrateOscillator(void)

{

uchar       step = 128;

uchar       trialValue = 0, optimumValue;

int         x, optimumDev, targetValue = (unsigned)(1499 * (double)F_CPU / 10.5e6 + 0.5);


    /* do a binary search: */

    do{

        OSCCAL = trialValue + step;

        x = usbMeasureFrameLength();    /* proportional to current real frequency */

        if(x < targetValue)             /* frequency still too low */

            trialValue += step;

        step >>= 1;

    }while(step > 0);

    /* We have a precision of +/- 1 for optimum OSCCAL here */

    /* now do a neighborhood search for optimum value */

    optimumValue = trialValue;

    optimumDev = x; /* this is certainly far away from optimum */

    for(OSCCAL = trialValue - 1; OSCCAL <= trialValue + 1; OSCCAL++){

        x = usbMeasureFrameLength() - targetValue;

        if(x < 0)

            x = -x;

        if(x < optimumDev){

            optimumDev = x;

            optimumValue = OSCCAL;

        }

    }

    OSCCAL = optimumValue;

}

/*

Note: This calibration algorithm may try OSCCAL values of up to 192 even if

the optimum value is far below 192. It may therefore exceed the allowed clock

frequency of the CPU in low voltage designs!

You may replace this search algorithm with any other algorithm you like if

you have additional constraints such as a maximum CPU clock.

For version 5.x RC oscillators (those with a split range of 2x128 steps, e.g.

ATTiny25, ATTiny45, ATTiny85), it may be useful to search for the optimum in

both regions.

*/


void    usbEventResetReady(void)

{

    calibrateOscillator();

    eeprom_write_byte(0, OSCCAL);   /* store the calibrated value in EEPROM */

}


/* ------------------------------------------------------------------------- */

/* --------------------------------- main ---------------------------------- */

/* ------------------------------------------------------------------------- */


int main(void)

{

uchar   i;

uchar   calibrationValue;


    calibrationValue = eeprom_read_byte(0); /* calibration value from last time */

    if(calibrationValue != 0xff){

        OSCCAL = calibrationValue;

    }

    odDebugInit();

    usbDeviceDisconnect();

    for(i=0;i<20;i++){  /* 300 ms disconnect */

        _delay_ms(15);

    }

    usbDeviceConnect();

    DDRB |= 1 << BIT_LED;   /* output for LED */

    PORTB |= 1 << BIT_KEY;  /* pull-up on key input */

    wdt_enable(WDTO_1S);

    timerInit();

    adcInit();

    usbInit();

    sei();

    for(;;){    /* main event loop */

        wdt_reset();

        usbPoll();

        if(usbInterruptIsReady() && newValueAvalable && velocity != 0){ /* we can send another value */ // [NAL] only send a report when we need to.

            buildReport();

            usbSetInterrupt(reportBuffer, sizeof(reportBuffer));

            newValueAvalable = 0; // [NAL] clear the flag and get back to monitoring the push button and ADC

        }

        timerPoll();

        adcPoll();

    }

    return 0;

}