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