#include #include #include // for sine #include "common.h" //common between audio & video boards //sound generation variables unsigned long accumulator @0x2f0; unsigned char highbyte @0x2f3; //the HIGH byte of the accumulator variable unsigned long increment; unsigned long temp_inc; char sineTable[256] @0x300; //need loc to avoid glitch char SinEntry; //Envelope shape variable unsigned int EnvelopeAccumulator; //Envelope rates of change and targets #define AttackTarget 0xFF00 #define AttackIncrement 0x7F80 #define DecayTarget 0xAF80 #define DecayIncrement 0x000E #define SustainTarget 0x0005 #define SustainIncrement 0x0001 #define DieIncrement 0x0000 //Envelope state variable unsigned char EnvelopeState; //Envelope state machine state names #define Attack 1 #define Decay 2 #define Sustain 3 #define Die 4 #define NoSound 5 //PSX SPI variables unsigned char PSXFlag; //flag to initialize transmission unsigned char PSXCount; //index into receive buffer unsigned char PSXRecBuf[9]; //received data from the controller unsigned char PSXStrumFlag; //strum bar pressed either direction unsigned char PSXStrumFlagLast; unsigned char PSXUpFlag; //strum bar pressed up unsigned char PSXUpFlagLast; unsigned char PSXDownFlag; //strum bar pressed down unsigned char PSXDownFlagLast; unsigned char PSXStartFlag; //start button pressed unsigned char PSXStartFlagLast; unsigned char PSXSelFlag; //select button pressed unsigned char PSXSelFlagLast; unsigned char PSXTemp1; //temporary storage for PSXRexBuf[3] unsigned char PSXTemp2; //temporary storage for PSXRecBuf[4] //Video communication variables unsigned char VideoCmnd; //command to send unsigned char VideoFlag; //flag to initialize transmission unsigned char VideoState; //main state machine variable unsigned char VideoStateLast; unsigned char VideoStateNext; unsigned char MenuSel; //current selected menu item unsigned char MenuLen; //length of current menu unsigned char nada; //holds dummy read of SPDR unsigned char SongIdx; //index into current song (note to be played) unsigned char SongDispIdx; //index into current song (fifth future note) unsigned char eeprom *Tune; //points to EEPROM (for song storage) unsigned char NoteDisp; //note to display unsigned char note; //note played //main state machine definitons #define Freeplay 1 #define Song 2 #define Menu 3 #define LoadSong 4 #define Transition 5 //SPI state machine variable unsigned char SPIState; //SPI state machine states #define Idle 0 #define PSX 1 #define Video 2 //lonely little variable used for array indeces unsigned int i; //function prototypes int multfrac(int a,int b); //Fixed point fractional multiply void initialize(void); //all the usual mcu stuff //SPI data received interrupt [SPI_STC] void spi_stc(void) { switch (SPIState) { case PSX: PSXRecBuf[PSXCount++] = SPDR; //Capture received data //If PSX communication finished if (PSXCount == 5) { PORTB.4 = 1; //release SPI SPIState = Idle; } break; //If Video comminication finished case Video: nada = SPDR; PORTB.3 = 1; //Release SPI VideoCmnd = NOP; SPIState = Idle; break; } } //Request for Video Data interrupt [EXT_INT1] void ext_int1(void) { //Flash the LED PORTD.7 = !PORTD.7; VideoFlag = 1; } //Request for PSX data interrupt [EXT_INT0] void ext_int0(void) { if (SPIState == PSX) { //Transmit data to PSX controller if (PSXCount == 1) { SPDR = 0x42; } else { SPDR = 0x00; } } } //Audio generation interrupt interrupt [TIM0_OVF] void sgen(void) { //the DDR code and scaling accumulator = accumulator + increment ; SinEntry = sineTable[highbyte]; //Output scaled Sine entry to DAC PORTC = 128 + multfrac(SinEntry,EnvelopeAccumulator); //Envelope state machine switch (EnvelopeState) { //if in the attack phase case Attack: if (EnvelopeAccumulator < AttackTarget) { //ramp up the volume EnvelopeAccumulator += AttackIncrement; EnvelopeState = Attack; } else EnvelopeState = Decay; break; //if in the decay phase case Decay: if(EnvelopeAccumulator > DecayTarget) { //decrease the volume EnvelopeAccumulator -= DecayIncrement; EnvelopeState = Decay; } else EnvelopeState = Sustain; break; //if in the sustain phase case Sustain: if(EnvelopeAccumulator > SustainTarget) { //sustain volume EnvelopeAccumulator -= SustainIncrement; EnvelopeState = Sustain; } else EnvelopeState = Die; break; //if in the die phase case Die: if (EnvelopeAccumulator > 0) { //kill volume EnvelopeAccumulator -= DieIncrement; EnvelopeState = Die; } else EnvelopeState = NoSound; break; //if done with note case NoSound: EnvelopeAccumulator = 0; EnvelopeState = NoSound; break; } } //********************************************************** //timer 2 comare match ISR interrupt [TIM1_COMPA] void timer1_cmpa(void) { //talk to the PS2 controller every 20 ms PSXFlag = 1; } //********************************************************** //Entry point void main(void) { initialize(); //main loop while(1) { //record captured PSX data PSXTemp2 = PSXRecBuf[4]; PSXTemp1 = PSXRecBuf[3]; //indicators for button presses PSXUpFlag = (~PSXTemp1 & 0x10) ? 1 : 0; PSXDownFlag = (~PSXTemp1 & 0x40) ? 1 : 0; PSXStartFlag = (~PSXTemp1 & 0x08) ? 1 : 0; PSXSelFlag = (~PSXTemp1 & 0x01) ? 1 : 0; PSXStrumFlag = PSXUpFlag || PSXDownFlag; //main video game state machine switch (VideoState) { //freeplay mode case Freeplay: //if start, go to menu mode if (PSXStartFlag && !PSXStartFlagLast) { VideoStateLast = Freeplay; VideoState = Menu; VideoCmnd = MENU | 0x01; MenuSel = 0; MenuLen = 5; } else { if strum with a fret combo, init the DDS switch(~PSXTemp2 & 0xF2) { case 0x80: //high G note = hG; temp_inc = 0x019B0AB2; break; case 0xC0: //high F note = hF; temp_inc = 0x016F0068; break; case 0x40: //E note = E; temp_inc = 0x015A07B3; break; case 0x50: //D note = D; temp_inc = 0x01344806; break; case 0x10: //C note = C; temp_inc = 0x0111ADA7; break; case 0x30: //B note = B; temp_inc = 0x0102FF8E; break; case 0x20: //A note = A; temp_inc = 0x00E6AFCC; break; case 0x22: //low G note = lG; temp_inc = 0x00CD8559; break; case 0x02: //low F note = lF; temp_inc = 0x00B78034; break; default: note = NoNote; break; } //if strum detected, start the DDS if (PSXStrumFlag && !PSXStrumFlagLast) { if (note != NoNote) { VideoCmnd = SETSL | note; //VideoFlag = 1; increment = temp_inc; TCNT0 = 0; OCR0 = 128; accumulator = 0; EnvelopeAccumulator = 0; EnvelopeState = Attack; TCCR0 = 0b01001001; } } else { //if the audio board is not sending any video commands, draw the pressed frets to the screen if (VideoCmnd == NOP) VideoCmnd = SET | note; } } break; //menu mode case Menu: //if start button pressed, clear the menu if (PSXStartFlag && !PSXStartFlagLast) { VideoState = VideoStateLast; VideoCmnd = CLRM; } //if select button pressed, enter the mode selected else if (PSXSelFlag && !PSXSelFlagLast) { switch (MenuSel) { case 0: VideoState = VideoStateLast; VideoCmnd = CLRM; break; case 1: VideoStateNext = Freeplay; VideoState = Transition; //VideoCmndNext = MODE | FreeMode; VideoCmnd = CLRM; break; case 2: VideoStateNext = LoadSong; VideoState = Transition; SongIdx = 0; SongDispIdx = 0; VideoCmnd = CLRM; break; case 3: VideoStateNext = LoadSong; VideoState = Transition; SongIdx = 64; SongDispIdx = 64; VideoCmnd = CLRM; break; case 4: VideoStateNext = LoadSong; VideoState = Transition; SongIdx = 128; SongDispIdx = 128; VideoCmnd = CLRM; break; } } //if up or down pressed, change selected option else if (PSXUpFlag && !PSXUpFlagLast) { if (MenuSel > 0) { MenuSel--; VideoCmnd = MENUS | MenuSel; } } else if (PSXDownFlag && !PSXDownFlagLast) { if (MenuSel < MenuLen-1) { MenuSel++; VideoCmnd = MENUS | MenuSel; } } break; //song mode case Song: //if start button pressed, go to menu mode if (PSXStartFlag && !PSXStartFlagLast) { VideoStateLast = Song; VideoState = Menu; VideoCmnd = MENU | 0x01; MenuSel = 0; MenuLen = 5; } else { //if fret button pressed, init the DDS switch(~PSXTemp2 & 0xF2) { case 0x80: //high G note = hG; temp_inc = 0x019B0AB2; break; case 0xC0: //high F note = hF; temp_inc = 0x016F0068; break; case 0x40: //E note = E; temp_inc = 0x015A07B3; break; case 0x50: //D note = D; temp_inc = 0x01344806; break; case 0x10: //C note = C; temp_inc = 0x0111ADA7; break; case 0x30: //B note = B; temp_inc = 0x0102FF8E; break; case 0x20: //A note = A; temp_inc = 0x00E6AFCC; break; case 0x22: //low G note = lG; temp_inc = 0x00CD8559; break; case 0x02: //low F note = lF; temp_inc = 0x00B78034; break; default: note = NoNote; break; } //if strum if (PSXStrumFlag && !PSXStrumFlagLast) { NoteDisp = Tune[SongDispIdx]; //if corrent note played, start the DDS if (Tune[SongIdx] == note) { if (note != NoNote) { increment = temp_inc; TCNT0 = 0; OCR0 = 128; accumulator = 0; EnvelopeAccumulator = 0; EnvelopeState = Attack; TCCR0 = 0b01001001; } //shift in new notes until the end of the song if (NoteDisp != 0xFF) { VideoCmnd = SLSET | NoteDisp; SongDispIdx++; } else { VideoCmnd = SLSET | NoNote; } SongIdx++; } else { //if wrong note played, display 'x' on screen VideoCmnd = BAD; } } } break; //prep for song mode case LoadSong: if (VideoCmnd == NOP) { //shift notes into a non displayed buffer until the screen is full if ((SongDispIdx-SongIdx) == 6) { VideoCmnd = DISP; VideoState = Song; } else { VideoCmnd = LOAD | Tune[SongDispIdx++]; } } break; //transition mode case Transition: if (VideoCmnd == NOP) { VideoState = VideoStateNext; if (VideoStateNext == LoadSong) VideoCmnd = MODE | SongMode; else VideoCmnd = MODE | FreeMode; } } //update debouncers PSXUpFlagLast = PSXUpFlag; PSXDownFlagLast = PSXDownFlag; PSXStartFlagLast = PSXStartFlag; PSXSelFlagLast = PSXSelFlag; PSXStrumFlagLast = PSXStrumFlag; //init and start spi comm for PSX and video board if (SPIState == Idle) { if (PSXFlag) { SPIState = PSX; PSXFlag = 0; PORTB.3 = 1; PORTB.4 = 0; PSXCount = 0; delay_us(7); SPDR = 0x01; } else if (VideoFlag) { SPIState = Video; VideoFlag = 0; PORTB.3 = 0; PORTB.4 = 1; SPDR = VideoCmnd; } } } } //********************************************************** //Set it all up void initialize(void) { //set up the ports DDRD = 0x80; // PD7 is output (LED), rest are inputs DDRC = 0xFF; // PORTC is output (audio DAC) DDRB = 0b10111000; // SCK, MOSI, PB4, PB3 are outputs, rest are inputs //enable internal pullups PORTB.6 = 1; // MISO PORTD.2 = 1; // INT0 //initialize output pins PORTB.3 = 1; // video slave deselected PORTB.4 = 1; // PSX slave deselected PORTD.7 = 1; // LED off //fast PWM mode, full clock rate, toggle oc0 (pin B3) //16 microsec per PWM cycle implies max freq for 16 samples of // 1/(16e-6*16) = 3900 Hz. //TCCR0 = 0b01101001 ; //turn on timer 0 overflow ISR TIMSK = 0b00000001 ; //init the sine table for (i=0; i<256; i++) { sineTable[i] = (char)(40.0 * sin(6.283*((float)i)/256.0)) +(12.0 * sin(6.283*(2*(float)i)/256.0)) +(10.0 * sin(6.283*(3*(float)i)/256.0)) + (8.0 * sin(6.283*(4*(float)i)/256.0)); } //init the DDS phase increment increment = 0x0111ADA7 ; TCCR0 = 0b00000001 ; //set up timer 1, CTC mode, interrupt every ~20ms TCNT1 = 0; TIMSK |= 0b00010000; TCCR1B = 0b00001101; OCR1A = 781; //set up SPI: master, mode 3, 250kHz clock, LSB first SPCR = 0xFE; //set up external interrupts 0,1 for rising edge MCUCR |= 0x0F; GICR |= 0xC0; //initialize PSX variables PSXFlag = 0; PSXCount = 0; for (i=0; i<9; i++) PSXRecBuf[i] = 0xFF; PSXStrumFlag = 0; PSXStrumFlagLast = 0; //initialize video variables VideoFlag = 0; VideoCmnd = NOP; VideoState = Freeplay; //initialize SPI state machine SPIState = Idle; //crank up the ISRs #asm sei #endasm } //fixed point fractional multiply int multfrac(int a,int b) { #asm ;****************************************************************************** ;* ;* FUNCTION ;* muls16x8_24 ;* DECRIPTION ;* Signed multiply of a 16-bit int to a fixed 8-bit fraction, outputting a 24-bit fixed. ;* USAGE ;* r31:r30:rxx = r23:r22 * 0.r21 ;****************************************************************************** push r21 LDD R22,Y+2 ;load a LDD R23,Y+3 LDD R21,Y+1 ;load b CLR R31 MUL R22, R21 MOV R30, R1 MULSU R23, R21 ADD R30, R0 ADC R31, R1 POP R21 #endasm }