Rev 10 | Blame | Compare with Previous | Last modification | View Log | RSS feed
#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 "config.h"
#include "avrutil.h"
#include "usbdrv.h"
#include "i2cbb.h"
#ifndef NULL
#define NULL ((void *)0)
#endif
#define MAX_DISPLAYS 4 // Address space for up to for displays
#define INPUT_REFRESH 50
#define FULL_REFRESH 10
#define I2C_BASE_ADDR 0x26 // All display addresses start from here
#define I2C_GET_VERSION 0x01
#define I2C_SET_DEBUG 0x03
#define I2C_SET_DIGITS 0x05
#define I2C_SET_DECIMAL_PTS 0x08
#define I2C_RESET_ROTARY 0x09
#define I2C_GET_OUTER_ROTARY 0x0a
#define I2C_GET_INNER_ROTARY 0x0b
#define I2C_GET_BUTTON_DATA 0x0c
#define I2C_GET_CONFIG_DATA 0x0d
#define USB_GET_VERSION 01
#define USB_GET_DISPLAY_COUNT 10
#define USB_GET_DISPLAY_META 11
#define USB_SET_LATCH 20
#define USB_SET_DISPLAY_DIGIT 21
#define USB_SET_POINTS 23
#define USB_GET_INPUT 30
void usbEventResetReady(void);
static void calibrateOscillator(void);
static void updateDecimals(uint8_t dis);
static void updateDisplay(uint8_t dis);
static void updateInput();
static uint16_t getDisplayVersion(uint8_t i2c_addr);
static uint8_t getDisplayConfigData(uint8_t i2c_addr);
struct display_type {
uint8_t address;
uint16_t version; // HB = HW, LB = SW
uint8_t config; // State of the config settings
uint8_t value[10];
uint16_t decpts;
uint8_t decpts_refresh; // Decimal point refresh
int8_t outer; // State of the outer rotary encoder
int8_t inner; // State of the inner rotary encoder
uint8_t buttons; // State of the buttons
} display[MAX_DISPLAYS];
static uint8_t usbReplyBuf[8];
static uint8_t latchDisplay = 255;
static uint8_t displays_attached = 0;
volatile uint8_t tmr0_ovf = 0;
int main(void) {
// calibration value from last time
uchar calibrationValue;
calibrationValue = eeprom_read_byte(EEPROM_USBVCALVAL);
if(calibrationValue != 0xff){
OSCCAL = calibrationValue;
}
/*
DDR : 1 = Output, 0 = Input
PORT: 1 = Pullup for Input, otherwise set output
PIN : Read input pin
PB0 - - LED
PB1 - - USB D- Low Speed
PB2 - - USB D+
PB3 - - SCL i2c bb
PB4 - - SDA i2c bb
PB5 - - Reset
*/
DDRB = 0B00000001;
PORTB = 0B00000001;
usbDeviceDisconnect();
_delay_ms(500);
usbDeviceConnect();
systime = 0;
uint32_t refresh = 0;
sysclockInit();
wdt_enable(WDTO_1S);
usbInit();
sei();
uint8_t i;
for (i=0; i<MAX_DISPLAYS; i++) {
if (i2cbb_Sniff(I2C_BASE_ADDR + i))
continue;
displays_attached++;
display[i].address = I2C_BASE_ADDR + i;
display[i].decpts = 0x0000;
display[i].decpts_refresh = 0;
uint8_t j;
for (j=0; j<10; j++)
display[i].value[j] = 0x01;
updateDisplay(i);
display[i].version = getDisplayVersion(display[i].address);
// Only get the config if version >= 0x02xx
if (display[i].version > 0x0200)
display[i].config = getDisplayConfigData(display[i].address);
else
display[i].config = 0x00;
}
for(;;){
wdt_reset();
usbPoll();
// Latch requests from the the USB host
if (latchDisplay != 255) {
updateDisplay(latchDisplay);
latchDisplay = 255;
}
for (i=0; i<displays_attached; i++) {
if (display[i].decpts_refresh) {
updateDecimals(i);
display[i].decpts_refresh = 0;
}
}
// Refresh time for getting user input data
if (systime > refresh) {
refresh = systime + INPUT_REFRESH;
updateInput();
}
}
return 0;
}
static uint16_t getDisplayVersion(uint8_t i2c_addr) {
uint8_t hw = 0x00;
uint8_t sw = 0x00;
i2cbb_Init();
i2cbb_Start();
i2cbb_Write( i2c_addr << 1 );
i2cbb_Write( I2C_GET_VERSION );
i2cbb_Stop();
i2cbb_Start();
i2cbb_Write( (i2c_addr << 1) + 1 );
hw += (int8_t)i2cbb_Read(1);
sw += (int8_t)i2cbb_Read(1);
i2cbb_Stop();
return ((uint16_t)hw << 8) | ((uint16_t)sw);
}
static uint8_t getDisplayConfigData(uint8_t i2c_addr) {
uint8_t config = 0;
// Request the button data
i2cbb_Init();
i2cbb_Start();
i2cbb_Write( i2c_addr << 1 );
i2cbb_Write( I2C_GET_CONFIG_DATA );
i2cbb_Stop();
// Receive the button data
i2cbb_Start();
i2cbb_Write( (i2c_addr << 1) + 1 );
config = i2cbb_Read(1);
i2cbb_Stop();
return config;
}
// Get the user input data from each display board
// @TODO: Changes to display board are needed to reduce
// the amount of i2c calls neede to retrieve all
// the button data. One i2c command should be able
// to do all of this
static void updateInput() {
uint8_t i;
for (i = 0; i < displays_attached; i++) {
// Only check the inner rotary if HW version > 2 and
// the config tells us this is a PRO model
if (display[i].version >= 0x200 &&
rbi(display[i].config, 0)) {
// Request for the inner rotary data
i2cbb_Init();
i2cbb_Start();
i2cbb_Write( display[i].address << 1 );
i2cbb_Write( I2C_GET_INNER_ROTARY );
i2cbb_Stop();
// Receive inner rotary data
i2cbb_Start();
i2cbb_Write( (display[i].address << 1) + 1 );
display[i].inner += (int8_t)i2cbb_Read(1);
i2cbb_Stop();
} else {
display[i].inner = 0x00;
}
// Request for the outer rotary data
i2cbb_Init();
i2cbb_Start();
i2cbb_Write( display[i].address << 1 );
i2cbb_Write( I2C_GET_OUTER_ROTARY );
i2cbb_Stop();
// Receive outer rotary data
i2cbb_Start();
i2cbb_Write( (display[i].address << 1) + 1 );
display[i].outer += (int8_t)i2cbb_Read(1);
i2cbb_Stop();
// Reset the rotary on display board
i2cbb_Init();
i2cbb_Start();
i2cbb_Write( display[i].address << 1 );
i2cbb_Write( I2C_RESET_ROTARY );
i2cbb_Stop();
// Request the button data
i2cbb_Init();
i2cbb_Start();
i2cbb_Write( display[i].address << 1 );
i2cbb_Write( I2C_GET_BUTTON_DATA );
i2cbb_Stop();
// Receive the button data
i2cbb_Start();
i2cbb_Write( (display[i].address << 1) + 1 );
display[i].buttons = i2cbb_Read(1);
i2cbb_Stop();
}
}
static void updateDecimals(uint8_t dis) {
i2cbb_Init();
i2cbb_Start();
i2cbb_Write( display[dis].address << 1 );
i2cbb_Write( I2C_SET_DECIMAL_PTS );
i2cbb_Write((uint8_t)(display[dis].decpts>>8));
i2cbb_Write((uint8_t)display[dis].decpts);
i2cbb_Stop();
}
// We can select which display to update as this can
// get slow if updates are being done all the time,
// which might affect the user input data tasks
static void updateDisplay(uint8_t dis) {
// Send the display buffer to display board
uint8_t update = 0;
uint8_t n;
for (n=0; n<10; n++) {
if (rbi(display[dis].value[n], 7)) {
update = 1;
break;
}
}
if (!update) return;
i2cbb_Init();
i2cbb_Start();
i2cbb_Write( display[dis].address << 1);
i2cbb_Write( I2C_SET_DIGITS );
for (n=0; n<10; n++) {
if (rbi(display[dis].value[n], 7)) {
cbi(display[dis].value[n], 7);
uint8_t send = (n << 4) | display[dis].value[n];
i2cbb_Write( send );
}
}
i2cbb_Stop();
}
// The USB functions to transmit/receive data from USB host.
usbMsgLen_t usbFunctionSetup(uchar data[8])
{
usbRequest_t *rq = (void *)data;
switch (rq->bRequest ) {
// Request for a display boards digits to be updated
case USB_SET_LATCH: {
latchDisplay = rq->wValue.bytes[0];
break;
}
case USB_SET_POINTS: {
if (display[0].decpts != rq->wValue.word) {
display[0].decpts_refresh = 1;
display[0].decpts = rq->wValue.word;
}
if (display[1].decpts != rq->wIndex.word) {
display[1].decpts_refresh = 1;
display[1].decpts = rq->wIndex.word;
}
}
// Sets the display boards digit buffer. Only on display
// board is updated per request. Also does decimal points
case USB_SET_DISPLAY_DIGIT: {
uint8_t dis = rq->wValue.bytes[1];
uint8_t dig = rq->wValue.bytes[0];
uint8_t val = rq->wIndex.bytes[0];
if ((display[dis].value[dig] & 0x0f) != val) {
display[dis].value[dig] = val;
sbi(display[dis].value[dig], 7);
}
break;
}
// Return the user input data all at once. Its populated from
// buffered data from the updateInput() function.
case USB_GET_INPUT: {
uint8_t disnum = rq->wValue.bytes[0];
usbReplyBuf[0] = display[disnum].buttons;
usbReplyBuf[1] = display[disnum].outer;
usbReplyBuf[2] = display[disnum].inner;
usbMsgPtr = usbReplyBuf;
return sizeof(usbReplyBuf);
break;
}
// Return the version numbers for the controller board
// and for all attached display boards.
case USB_GET_VERSION: {
usbReplyBuf[0] = HW_VERSION;
usbReplyBuf[1] = SW_VERSION;
//uint8_t i;
//for (i=0; i<displays_attached; i++) {
// usbReplyBuf[2+(i*2)] = (uint8_t)(display[i].version >> 8);
// usbReplyBuf[2+(i*2)+1] = (uint8_t)(display[i].version && 0xff);
//}
usbMsgPtr = usbReplyBuf;
return sizeof(usbReplyBuf);
break;
}
case USB_GET_DISPLAY_COUNT: {
usbReplyBuf[0] = displays_attached;
usbMsgPtr = usbReplyBuf;
return sizeof(usbReplyBuf);
break;
}
case USB_GET_DISPLAY_META: {
uint8_t disnum = rq->wValue.bytes[0];
usbReplyBuf[0] = (uint8_t)(display[disnum].version >> 8);
usbReplyBuf[1] = (uint8_t)(display[disnum].version & 0xff);
usbReplyBuf[2] = display[disnum].address;
usbReplyBuf[3] = display[disnum].config;
usbMsgPtr = usbReplyBuf;
return sizeof(usbReplyBuf);
break;
}
}
return 0;
}
static void calibrateOscillator(void) {
uchar step = 128;
uchar trialValue = 0, optimumValue;
int x, optimumDev;
int 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;
}
void usbEventResetReady(void) {
cli();
calibrateOscillator();
sei();
eeprom_write_byte(0, OSCCAL); /* store the calibrated value in EEPROM */
}
ISR(TIM0_OVF_vect) {
tmr0_ovf++;
// Clk/1 TCCR0B = (1<< CS00);
//20.0Mhz, 1ms = 78ovf
//16.5Mhz, 1ms = 64ovf
//16.0Mhz, 1ms = 62ovf
//12.0Mhz, 1ms = 46ovf
// 8.0Mhz, 1ms = 31ovf
// 8.0Mhz, .5ms = 15ovf, 160r
if (tmr0_ovf>=64) {
systime++;
tmr0_ovf = 0;
}
}