Subversion Repositories group.electronics

Rev

Blame | Last modification | View Log | RSS feed

#include <avr/io.h>
#include <avr/pgmspace.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <avr/wdt.h>
#include <avr/eeprom.h>
#include <usbdrv/usbdrv.h>

#include <stdlib.h>
#include <string.h>

#include "config.h"
#include "nes.h"

FUSES = 
{
  .low = (FUSE_CKSEL1 & FUSE_CKSEL2 & FUSE_CKSEL3 & FUSE_SUT0), // Int osc HF PLL, slow rising power
  .high = (FUSE_SPIEN & FUSE_BODLEVEL1), // Enable serial programming, BOD 2.7V
  .extended = EFUSE_DEFAULT,
};

struct{
  union {
    uint8_t data;
    struct {
      uint8_t X:2;
      uint8_t Y:2;
      uint8_t B:1;
      uint8_t A:1;
      uint8_t SELECT:1;
      uint8_t START:1;
    };
  };
} reportBuffer;

usbMsgLen_t usbFunctionSetup(uchar data[8]) {
  usbRequest_t *req = (void *)data;

  return 0; // Nothing implemented
}

/* ------------------------------------------------------------------------- */
/* ------------------------ 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 hadUsbReset(void) {
  calibrateOscillator();
  eeprom_write_byte(0, OSCCAL);   /* store the calibrated value in EEPROM */
}

// Clock pulse width >= 500ns 
#define STROBE_CLK() sbi(NES_PORT, CLK); \
                     _delay_us(1); \
                     cbi(NES_PORT, CLK); \
                     _delay_us(1); 

int main(void) {
  uchar calibrationValue;
  while (!eeprom_is_ready());
  calibrationValue = eeprom_read_byte(0); /* calibration value from last time */
  if(calibrationValue != 0xff) {
    OSCCAL = calibrationValue;
  }

  _delay_ms(10);
  ACSR |= (1<<ACD); // Disable analog comparator

  
  usbDeviceDisconnect();  /* enforce re-enumeration, do this while interrupts are disabled! */
  _delay_ms(500);
  usbDeviceConnect();

  wdt_enable(WDTO_1S);
  usbInit();
  sei();
  
  sbi(NES_DDR, CLK);
  cbi(NES_DDR, OUT);
  sbi(NES_DDR, LATCH);
  
  cbi(NES_PORT, CLK);
  cbi(NES_PORT, LATCH);

  for(;;) {
    wdt_reset();
    usbPoll();
    if(usbInterruptIsReady()){
      
      reportBuffer.data = 0x05; // Center pad, little endian
      
      sbi(NES_PORT, LATCH);
      _delay_us(1); // Latch pulse width >= 500ns
      cbi(NES_PORT, LATCH);
      _delay_us(1); // Propagation time <= 1000ns
      
      if(bit_is_clear(NES_PIN, OUT))
        reportBuffer.A = 1;

      STROBE_CLK();
      
      if(bit_is_clear(NES_PIN, OUT))
        reportBuffer.B = 1;

      STROBE_CLK();
      
      if(bit_is_clear(NES_PIN, OUT))
        reportBuffer.SELECT = 1;

      STROBE_CLK();

      if(bit_is_clear(NES_PIN, OUT))
        reportBuffer.START = 1;

      STROBE_CLK();

      if(bit_is_clear(NES_PIN, OUT))
        reportBuffer.Y--;

      STROBE_CLK();

      if(bit_is_clear(NES_PIN, OUT))
        reportBuffer.Y++;

      STROBE_CLK();

      if(bit_is_clear(NES_PIN, OUT))
        reportBuffer.X--;

      STROBE_CLK();

      if(bit_is_clear(NES_PIN, OUT))
        reportBuffer.X++;
       
      /* called after every poll of the interrupt endpoint */
      usbSetInterrupt(&reportBuffer, sizeof(reportBuffer));

    }

  }

}