MCV876 Controller Update: Source Code: Unterschied zwischen den Versionen
Fetz (Diskussion | Beiträge) K (→Quelltext) |
Fetz (Diskussion | Beiträge) K (→Übersetzen) |
||
(2 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt) | |||
Zeile 1: | Zeile 1: | ||
=Übersetzen= | =Übersetzen= | ||
Der Code für den ATMega 88 wird mit dem AVR-Studio 4 erstellt. Das kann man auf der Atmel Webseite (kostenlos) runter laden. | Der Code für den ATMega 88 wird mit dem AVR-Studio 4 erstellt. Das kann man auf der Atmel Webseite (kostenlos) runter laden. | ||
Im Studio ein neues, leeres Projekt mit Standard-Einstellungen, einem sinnvollen Namen und an einem Ort, wo man es wiederfindet erstellen | Im Studio ein neues, leeres Projekt mit Standard-Einstellungen, einem sinnvollen Namen und an einem Ort, wo man es wiederfindet erstellen. | ||
In den Projekteinstellungen die passenden CPU wählen und die Taktfrequenz einstellen. Project->Configuration-Options: | |||
[[File:ProjectSettings.gif]] | |||
Zu den Atmel Controllern findet man eine Menge Info mit Tante Google, [http://www.mikrocontroller.net http://www.mikrocontroller.net] kommt häufig vor, und hat ein ergiebiges Forum und gute Artikel. Auf der Atmel Webseite gibt es auch einige Info, das [http://www.atmel.com/dyn/resources/prod_documents/doc2545.pdf ATMega 88 Datenblatt] ist spätestens dann nötig, wenn es um die Hardware geht. | Dann Quelltext in das (automatisch erzeugte, leere) C-File kopieren, auf Build->Build klicken, fertig. | ||
Das resultierende HEX File kann man mit einem üblichen Programmieradapter in der Chip flashen, für uns zweckmäßiger ist allerdings, das in ein Sys-Ex File zu Konvertieren ( [[MCV876_Controller_Update:_File_Converter]]), und das über Midi an den Controller zu schicken. Dann programmiert der sich selber um. Dazu muss er allerdings einmalig mit dem Bootlader ([[MCV876_Controller_Update:_Bootloader]]) programmiert worden sein. | |||
Zu den Atmel Controllern findet man eine Menge Info mit Tante Google, [http://www.mikrocontroller.net http://www.mikrocontroller.net] kommt häufig vor, und hat ein ergiebiges Forum und gute Artikel. Auf der Atmel Webseite gibt es auch [http://www.atmel.com/dyn/products/product_card.asp?part_id=3302&category_id=163&family_id=607&subfamily_id=760 einige Info], das [http://www.atmel.com/dyn/resources/prod_documents/doc2545.pdf ATMega 88 Datenblatt] ist spätestens dann nötig, wenn es um die Hardware geht. | |||
=Quelltext= | =Quelltext= |
Aktuelle Version vom 27. Juni 2011, 21:03 Uhr
Übersetzen
Der Code für den ATMega 88 wird mit dem AVR-Studio 4 erstellt. Das kann man auf der Atmel Webseite (kostenlos) runter laden. Im Studio ein neues, leeres Projekt mit Standard-Einstellungen, einem sinnvollen Namen und an einem Ort, wo man es wiederfindet erstellen. In den Projekteinstellungen die passenden CPU wählen und die Taktfrequenz einstellen. Project->Configuration-Options:
Dann Quelltext in das (automatisch erzeugte, leere) C-File kopieren, auf Build->Build klicken, fertig.
Das resultierende HEX File kann man mit einem üblichen Programmieradapter in der Chip flashen, für uns zweckmäßiger ist allerdings, das in ein Sys-Ex File zu Konvertieren ( MCV876_Controller_Update:_File_Converter), und das über Midi an den Controller zu schicken. Dann programmiert der sich selber um. Dazu muss er allerdings einmalig mit dem Bootlader (MCV876_Controller_Update:_Bootloader) programmiert worden sein.
Zu den Atmel Controllern findet man eine Menge Info mit Tante Google, http://www.mikrocontroller.net kommt häufig vor, und hat ein ergiebiges Forum und gute Artikel. Auf der Atmel Webseite gibt es auch einige Info, das ATMega 88 Datenblatt ist spätestens dann nötig, wenn es um die Hardware geht.
Quelltext
/* MCV876 with ATMega 88 Midi to 4 Channel Control Voltage and six Gates Interface Preliminary Version 0.22 2011-06-26 This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include <avr/io.h> // yes, we want to talk to the port pins by name #include <avr/eeprom.h> // ... oh.. and some eeprom access would be nice #include <avr/interrupt.h> // ... and w/o the interupts it wouldn't do anything... #include <util/delay.h> /* ===================ZERO LINE================================= WARNING: everthing below the zero line counts from zero! e.g. whats Gate 6 in manual is GATE5 here, CVs are CV0 to 3 */ /* where should the LED output be */ #define LEDLED 6 // LED at Reset-Pin, standard for series, but no debugging possible. #define LEDGATE0 0 // Led at Gate 0, no LED (Test I: no LED ) #define LEDGATE4 4 // LED at Gate 5, Test II: no Gate 5 Function (Midi Start/Stop) #define LEDOUT LEDLED // select your evil... #define ACTIVE_SENSE_TICKS 160 // 2ms Timer Ticks for active sense time out #define BUTTON (!(PIND & (1<<4))) // True if Button pressed #define NUM_CV 4 #define PARAMETEROFFSET 0x23 // First paramter has adress 0x23 #define NUM_PARAMTERS 17 // and there are 17 parameters #define MODE_MONO 0x00 // monophon, all assigned to last note on a single channel #define MODE_MULTI2 0x40 // two channel, 1/2 for midiChannel, 2/3 for midiChannel+1 #define MODE_MULTI4 0x60 // for channel midiChannel to midiChannel+3 #define MODE_POLY2 0x80 // not supported #define MODE_POLY4 0xA0 // not supported struct CONFIG { unsigned char midiChannel; unsigned char noteOffset; unsigned char cvMidiCC[NUM_CV]; unsigned char gateMidiCC[NUM_CV]; unsigned char gateTreshhold; unsigned char clockDivider; unsigned char clockTime; unsigned char ifMode0; // bit 0 ... bit 3 = CV0 .. CV3, 0=Pitch, 1=CC (if pitch, CC assignement is ignored) // bit 5: voices: 0=2; 1=4 // bit 7:6= 00 Mono; 01:Multi-Channel; 10: Poly (not supported); unsigned char ifMode1; // Gate Trigger Mode Bits (0=Re-Trigger, 1=TriggerControl) unsigned char ifMode2; // bit0: Velo0=>CV0; bit1:Velo0=>CV1; bit2:Velo-Poly2=>CV2; bit3:Velo1=>CV3 // bit4: PB=>CV0; bit5:PB=>CV1; bit6:PB=>CV2; bit7:PB=>CV3 unsigned char ifMode3; // bit0..bit3 = CV0... CV3; 0=10V, 1=5V Mode // internal evaluated configuration unsigned char numMidiChannels; } shadow_config, // her goes a copy, when config should be written, because that might take a while config // current configuration, mostly direct evaluated troughout the program = { // default values (Mono-Mode, Midi-Ch 1, CV1=Pitch, CV2=Velocity CV3=ModWheel CV4=Pitch Bend 0, // Midi Channel 24, // Note Offset (2 Oktaves) { 255,255,1,255, }, // CV - assigned Midi Controllers (used if not pitch output) 255=none { 255,18,19,20, }, // Gate - asigned Controllers (if not pitch) 255=none 63, // Treshold 6, // Divider 6 (1/16 Note) 1, // clock time (unused) 0x0e, // mode 0 Low nibble CC-Bits, high: interface mode 0, // mode 1 re-trigger bits 0x82, // mode 2 High Nibble: PB-bits, Low: Velo-Bits 0, // mode 3 (5V-Bits) }; #define EE_MAGIC 17 unsigned char ee_magic EEMEM; // to check it eeprom was ever written and should be read back unsigned char config_eeprom[NUM_PARAMTERS] EEMEM; // Config in EEPROM, saves it during power down. volatile unsigned char eepromWriteFlag; // set to one, if main-loop should write eeprom volatile unsigned char button; // debounced button status volatile unsigned char learn; // (channel/base note/cc) "do learn" status volatile unsigned char blinker; // some count for blinkenlights volatile unsigned char gate5off; // timer counter for when clock gate output should go low again. volatile unsigned char watchTicker; // midi active sense counter void setConfig(void); void setLed(unsigned char on); void resetAll(void); /* main() Main does initialization, than runs a loop, which does nearly nothing: Programming the EEPROM (because its slow) Blinking Button evaluation All the important things are done by the interupts. (Thanks to the 20MHZ RISC power everything can be done in less than 320µs=one Midi-Character) */ int main(void) { unsigned char blink=4; unsigned char i,x; unsigned char *cp; static unsigned char lastButton; /* HARDWARE INIT */ // Input/Output Ports initialization // DDRx = Define I/O direction for each pins // PORTx = If pin is Output then `0' = lo, `1' = hi // If pin is input then `0' = no pullup, `1' = pullup enabled // `0' = Input `1' = Output // Port B initialization DDRB = 0x3f; // PB0=G4; PB1=G5 (Opt. LED); PB2=G6 ; PB3=WR1; PB4=D0; PB5=D1 (all output) PORTB = 0x04; // WR1 High, Rest Low // Port C initialization DDRC = 0x7F; // PC0=D2; PC1=D3; PC2=D4; PC3=D5; PC4=D6; PC5=D7; PC6=LED (opt.) (all output) PORTC = 0x00; // all Low // Port D initialization DDRD = 0xee; // [PD0=RX]; PD1=WR2; PD2=WR3; PD3=WR4; PD4=Button; PD5=G1; PD6=G2; PD7=G3 PORTD = 0x1F; // Pullup on RX & Button, all WR High, Gates Low // Timer/Counter 1 initialization // Clock source: System Clock // Clock value: 20Mhz /64 // Mode: CTC // OC0 output: Disconnected OCR1A= 625; // 2 ms TCCR1A =0; TCCR1B =8+3; // CTC Mode, TOP from OCR1A + Clock/64 TIMSK1 =2; // OCF1A Interupt // USART initialization // Communication Parameters: 8 Data, 1 Stop, No Parity // USART Receiver: On // USART Transmitter: OFF // USART Mode: Asynchronous UCSR0A = 0x00; /* Set frame format: 8data, 1stop bit */ UCSR0C = 6; // USART Baud rate: 31250; 20E6/16/31250 - 1 UBRR0H = 0; UBRR0L = 39; /*Enable receiver , Enable Rx-int */ UCSR0B = ((1<<RXEN0)|(1<<RXCIE0) ) ; /* HARDWARE READY */ // Read Configuration _delay_ms(10); // wait for button pin to get high if open. if( eeprom_read_byte(&ee_magic) == EE_MAGIC && !BUTTON) // if eeprom valid AND Button not pressed { cp=(unsigned char* )&config; for(i=0;i<NUM_PARAMTERS;++i) { x=eeprom_read_byte(config_eeprom+i); // read config from eeprom *cp=x; cp++; } } else blink=8; setConfig(); // enable interrupts sei(); while(1) { if(eepromWriteFlag) { // save configuration cp=(unsigned char* )&shadow_config; for(i=0;i<NUM_PARAMTERS;++i) { x=*cp; eeprom_write_byte(config_eeprom+i,x); cp++; } eeprom_write_byte(&ee_magic,EE_MAGIC); setLed(1); blink=6; blinker=0; eepromWriteFlag=0; } if(blink && blinker>2) // blink after power up and after storing in nv-memory { blink--; setLed(blink&1); blinker=0; } if(button != lastButton) { if(button==1 ) { resetAll(); if(learn==1) { learn=0; setLed(0); } } if(button==6) { if(learn ==0) { learn=1; setLed(1); } } lastButton=button; } } return 0; } /* *********************************************************************************** Hardware Interface */ /* Pin assignment Atmel Fkt [Orig] 1 Reset LED [MCLR] 2 RX Midi-Rx[WR1] 3 PD1 WR2 4 PD2 WR3 5 PD3 WR4 6 PD4 Button 7 VCC +5 [LED] (Either from Atmel-1(Series) or Atmel-15(w/Debug)) 8 GND GND 9 OSC OSC 10 OSC OSC 11 PD5 G1 12 PD6 G2 13 PD7 G3 14 PB0 G4 15 PB1 G5 (Optional + LED) 16 PB2 G6 17 PB3 WR1 [---] 18 PB4 D0 [RX] 19 PB5 D1 [GND] 20 AVCC +5 21 ARef +5 [D0] 22 GND GND [D1] 23 PC0 D2 24 PC1 D3 25 PC2 D4 26 PC3 D5 27 PC4 D6 28 PC5 D7 */ // put "val" to the right port bits for the DA-Converter // ... better program than solder *that*... void adByteOut(unsigned char val) { unsigned char b; b=val; b>>=2; PORTC=b; b=val&3; b<<=4; val=PORTB; val &=0xCF; val|=b; PORTB=val; } // set DA-Output voltage of one CV Channel void set_cv(unsigned char val, unsigned char channel) { adByteOut(val); // set D0..D7 converter data bits switch (channel) // pulse with write pin { case 0: //PB3 PORTB &= ~(1<<3); PORTB |= (1<<3); break; case 1: // PD1 PORTD &= ~(1<<1); PORTD |= (1<<1); break; case 2: // PD2 PORTD &= ~(1<<2); PORTD |= (1<<2); break; case 3: // PD3 PORTD &= ~(1<<3); PORTD |= (1<<3); break; } } // set on of the gate outputs active void gate_on(unsigned char channel) { switch (channel) { case 0: // PD5 PORTD |= (1<< 5); break; case 1: // PD6 PORTD |= (1<< 6); break; case 2: // PD7 PORTD |= (1<< 7); break; case 3: // PB0 PORTB |= (1<< 0); break; case 4: // PB1 #if LEDOUT != LEDGATE4 PORTB |= (1<< 1); #endif break; case 5: // PB2 PORTB |= (1<< 2); break; } } // clear one of the gate outputs void gate_off(unsigned char channel) { switch (channel) { case 0: // PD5 PORTD &=~(1<< 5); break; case 1: // PD6 PORTD &=~(1<< 6); break; case 2: // PD7 PORTD &=~(1<< 7); break; case 3: // PB0 PORTB &=~(1<< 0); break; case 4: // PB1 #if LEDOUT != LEDGATE4 PORTB &=~(1<< 1); #endif break; case 5: // PB2 PORTB &=~(1<< 2); break; } } /* *********************************************************************************** LED */ void setLed(unsigned char on) { #if LEDOUT == LEDLED if (on) // PC6 PORTC |= (1<< 6); else PORTC &=~(1<< 6); #endif #if LEDOUT == LEDGATE4 if (on) // PB1 PORTB |= (1<< 1); else PORTB &=~(1<< 1); #endif } /* *********************************************************************************** Data Handling */ typedef struct { unsigned char channel; // (relative) Midi channel (0..3) Mode dependant unsigned char cvOut; // for which CV-Ouput (0..3) fixed unsigned char ccVal; // current CC Value unsigned char note; // current Note unsigned char velo; // current Velocity unsigned char on; // if Note is on signed char pb; // current Pitch Bend Value unsigned char gateDipper; } cvoutput; cvoutput cv_out[NUM_CV]; // current state for each cv out unsigned char gates; // which gates are on => used for LED setting // set cv output (and assigned gate, if pitch output) // called after anything that changes the cv_out structure void updateCv(cvoutput *cvo) { unsigned char bm; unsigned char gated=0; int val=0; bm=1<<cvo->cvOut; // bit mask for configuration flags if(config.ifMode0 & bm) // 1=CC-Mode { if(config.cvMidiCC[cvo->cvOut]>127) // if no Controller assigned { if(config.ifMode2 & (bm<<4)) // if PB is active val=cvo->pb+0x80; // pitch sets unipolar value else // else val=0; // value is zero } else // else a controller is assigned { val=cvo->ccVal; // set current controler value ; if(config.ifMode2 & (bm<<4)) // if PB active val+=cvo->pb; // add it (bipolar) } if( (config.ifMode1 & bm) && cvo->on==0) // if Trig Flag and note off gated=1; // than gate CV output (to zero) /* if( (config.ifMode3 & bm) && cvo->on==0) // 5V-Flag, abused as Gated-Mode-CV Flag gated=1; */ } else // Note { val=cvo->note; val -= config.noteOffset; if(config.ifMode3 & bm) // 5V val <<= 2; else val <<= 1; if(config.ifMode2 & (bm<<4)) // PB val+=(cvo->pb>>2); // scale down pb - value is empirical and works "not too bad" // (for "good" the converter should have 12bits a least... ) } if(config.ifMode2 & bm) // Velocity val+=cvo->velo; if(val<0 || gated) // clip values to converter range val=0; if(val>255) val=255; set_cv((unsigned char)val, cvo->cvOut); if((config.ifMode0 & bm)==0) { if(cvo->on) { if(cvo->gateDipper==0) // no retrigger required gate_on(cvo->cvOut); else // dip gate for a few milliseconds gate_off(cvo->cvOut); gates |=bm; } else { gate_off(cvo->cvOut); gates &=~bm; } } } // calculate/initialize control variables after // mode-configuration change void setConfig() { unsigned char i; cvoutput *cvo; cvo=cv_out; for(i=0;i<NUM_CV;++i) { cvo->cvOut=i; cvo->pb=0; cvo->ccVal=0; cvo++; } cvo=cv_out; switch(config.ifMode0 & 0xf0 ) { default: case MODE_MONO: for(i=0;i<NUM_CV;++i) { cvo->channel=0; cvo++; } config.numMidiChannels=1; break; case MODE_MULTI2: for(i=0;i<NUM_CV/2;++i) { cvo->channel=0; cvo++; } for(;i<NUM_CV;++i) { cvo->channel=1; cvo++; } config.numMidiChannels=2; break; case MODE_MULTI4: for(i=0;i<NUM_CV;++i) { cvo->channel=i; cvo++; } config.numMidiChannels=NUM_CV; break; } } // assign new note state to cv outputs void setNote(unsigned char note, unsigned char velo, unsigned char onOff, unsigned char channel) { unsigned char i; cvoutput *cvo; cvo=cv_out; for(i=0;i<NUM_CV;i++) { if(cvo->channel==channel) { cvo->note=note; cvo->velo=velo; if(onOff==2 && cvo->on && (config.ifMode1 & (1<<i))) cvo->gateDipper=4; cvo->on=onOff; updateCv(cvo); } cvo++; } setLed(gates); } // process new continous controller value void setCC(unsigned char cc, unsigned char value, unsigned char channel) { unsigned char i; cvoutput *cvo; cvo=cv_out; for(i=0;i<NUM_CV;++i) { if(channel== cvo->channel ) { if(config.ifMode0 &(1<<i)) { if(config.cvMidiCC[i]==cc) { cvo->ccVal=value; updateCv(cvo); } if(config.gateMidiCC[i]==cc ) { if(value>config.gateTreshhold) gate_on(i); else gate_off(i); } } } cvo++; } } // process new pitch bend value void setPb(signed int value, unsigned char channel) { unsigned char i; cvoutput *cvo; cvo=cv_out; for(i=0;i<NUM_CV;++i) { if(channel==cvo->channel && (config.ifMode2 & (16<<i)) ) { cvo->pb =(signed char) (value>>5); updateCv(cvo); } cvo++; } } // prepare for (asynchronous) configuration write to eeprom void setEeWrite(void) { shadow_config=config; learn=0; eepromWriteFlag=1; } void (*bootloader)(void) = (void*)0x1800; // Bootloader sits at adress 0x1800 ... // process sys ex command void setSyx(unsigned char adr, unsigned char val) { unsigned char *cp; cp=(unsigned char *) &config; if(adr==0x14 && val ==0x7b) { // write eeprom if(eepromWriteFlag==0) { setEeWrite(); } return; } // 0xF0, 0x70, 0x7D,0x00,0x16,0x04, 0x02, 0xF7 if(adr==0x16 && val ==0x42) // Software update { cli(); // interupts off resetAll(); // outputs off GPIOR1 = 0xAB; // tell bootloader it's an application boot request // (hardware reset sets GPIOR1 to zero, 0xab == magic value for bootloader ) bootloader(); // bye bye! return; // <-- never! } adr-=PARAMETEROFFSET; if(adr < NUM_PARAMTERS) // if valid parameter { *(cp+adr)=val; // set in if(adr==0x30-PARAMETEROFFSET) // if Mode Byte 0, setConfig(); // .. cv assignement must be recalculated } } /* *********************************************************************************** Note Puffer Handling */ #define MAX_NOTES 0x10 // one for each finger of a real programmer #define MAX_CHANNELS 4 unsigned char notes[MAX_CHANNELS][MAX_NOTES]; unsigned char velocities[MAX_CHANNELS][MAX_NOTES]; unsigned char playedNote[MAX_CHANNELS]; // number of currently pressed notes // update note puffer for a midi note-on message void note_on(unsigned char note ,unsigned char velocity, unsigned char channel) { unsigned char n; n=playedNote[channel]; if(n<MAX_NOTES) { playedNote[channel]++; } else { for (n=MAX_NOTES-1;n>0;--n) { notes[channel][n-1]=notes[channel][n]; velocities[channel][n-1]=velocities[channel][n]; } n=MAX_NOTES-1; } notes[channel][n]=note; velocities[channel][n]=velocity; setNote(note, velocity, 2, channel); } // update note puffer for a new midi note-off message void note_off(unsigned char note ,unsigned char velocity, unsigned char channel) { unsigned char i; signed char n; n=playedNote[channel]; --n; for (i=0;i<=n;i++) { if(notes[channel][i]==note) { if(i==n) // last pressed (== the on which playes) note released { if(n==0) // it was the only pressed note { setNote(note, velocities[channel][n], 0, channel); playedNote[channel]--; } else { --n; setNote(notes[channel][n], velocities[channel][n], 1, channel); playedNote[channel]--; } } else // non playing note released { // => just remove it from list int j; for(j=i+1;j<=n;++j) { notes[channel][j-1]=notes[channel][j]; velocities[channel][j-1]=velocities[channel][j]; } playedNote[channel]--; } break; } } } /* *********************************************************************************** All Notes off, empty Note Buffer, All Controllers Reset */ void resetAll(void) { unsigned char i; cvoutput *cvo; cvo=cv_out; cli(); // this might be called asynchronous to midi stream for(i=0;i<MAX_CHANNELS;++i) playedNote[i]=0; for(i=0;i<NUM_CV;++i) { cvo->cvOut=i; cvo->ccVal=0; cvo->on=0; cvo->pb=0; updateCv(cvo); cvo++; } sei(); setLed(0); } /* *********************************************************************************** 2ms Timer Interupt */ ISR(TIMER1_COMPA_vect) { unsigned char i; static unsigned char buttonCount; static signed char blinkCount; // LED blinker if(--blinkCount<=0) { blinkCount=40; blinker++; if(button) { if(button<10) button++; } } // Button debouncer if(BUTTON) { // down if(buttonCount < 10) { buttonCount++; if(buttonCount==10) { button=1; blinkCount=40; } } } else { // up if(buttonCount > 0) { buttonCount--; if(buttonCount==0) button=0; } } // Gate 5 timing (clock output) if(gate5off) { --gate5off; if(gate5off==0) gate_off(5); } // activ sense if(watchTicker) // if active sense is activated { watchTicker--; // decrement time if(watchTicker==0) // if time elapsed resetAll(); // switch all notes off } // retrigger timing for(i=0;i<NUM_CV;i++) // for all cv output channels { if(cv_out[i].gateDipper) // if gate timer is active { --cv_out[i].gateDipper; // decrement timer if(cv_out[i].gateDipper==0) // if timer elapsed gate_on(i); // activate gate } } } /* *********************************************************************************** Eval Midi Data */ #define NOTE_ON 0x90 // Midi note on message #define NOTE_OFF 0x80 // Midi note off message #define PITCH 0xE0 // pitch bender #define AFTERTOUCH 0xD0 // after touch command (unused) #define CC 0xB0 // Continuous Controller #define CHAN_PRES 0xD0 // Channel Pressure (unused) #define SONG_POSITION_POINTER 0xF2 // used for clock synchronization (first beat) #define SYSEX 0xF0 // System exclusive beginn #define SYSEND 0xF7 // System exclusive end struct { unsigned char runningStatus; // Midi runnig status (*) unsigned char msgByteCnt; // number of current midi-message byte received unsigned char byte1; // first data byte unsigned char syxAdr; // Sys-Ex Adress Byte unsigned char startStop; // current Midi Start/Stop Status unsigned char startFlag; // Start on next clock unsigned char clockTick; // counter for clock division unsigned char channel; // channel of current midi-command (relative, e.g. 0..3 ) unsigned int spp; // song position pointer (from midi message), needed to have the clock synced to song position }midi; // (*) running status is also used as 'current status' - it remembers allways, what next data byte is for. Or its zero, than data is ignored. #define FRAMING_ERROR (1<<FE0) #define PARITY_ERROR (1<<UPE0) #define DATA_OVERRUN (1<<DOR0) // USART Receiver interrupt service routine, // complete midi processing is done here, as the CPU is fast enough to // do everything before next midi byte can come in. ISR(USART_RX_vect) { unsigned char status,byte; status = UCSR0A; byte = UDR0; if ((status & (FRAMING_ERROR | PARITY_ERROR | DATA_OVERRUN))==0 ) { if(watchTicker) // if there had been an active sense watchTicker=ACTIVE_SENSE_TICKS; // every new byte is ok, to "feed the watchdog" if (byte > 0x7F) // if command byte (MSB set) { if(byte>= 0xF8) // if real-time msg { switch (byte) { case 0xF8: // Clock { if(midi.startFlag) // if synchronized start { midi.startStop=1; midi.startFlag=0; if(config.clockDivider>1 )// if divided clock { if( midi.spp) // if start not from zero { midi.clockTick=midi.spp%config.clockDivider; // calculate clock divider count for current song position if(midi.clockTick >= config.clockDivider>>1) // if calculated divider count is mor than half elapsed gate_off(5); // than set clock output off else gate_on(5); // else set it on } else { gate_on(5); midi.clockTick=0; } } } else { if(midi.startStop) // if (already) running ++midi.spp; ++midi.clockTick; // increment counter for clock output } if(config.clockDivider<=1) { gate_on(5); // do clock output gate5off=3; } else { if(midi.clockTick >= config.clockDivider) // if divider count elapsed { midi.clockTick=0; gate_on(5); // do clock output } else if(midi.clockTick == config.clockDivider>>1) // if divider count half elapsed gate_off(5); // set clock output off } break; } case 0xF9: // Tick (unused) break; case 0xFA: // Start if(! midi.startStop) // if not already running { midi.spp=0; midi.startFlag=1; gate_on(4); if(config.clockDivider>1) { gate_off(5); // set clock output off midi.clockTick=0; } } break; case 0xFB: // Continue if(! midi.startStop) // if not already running { midi.startFlag=1; gate_on(4); } break; case 0xFC: // Stop midi.startStop=0; gate_off(4); break; case 0xFD: // unassigned break; case 0xFE: // Active Sense watchTicker=ACTIVE_SENSE_TICKS; break; case 0xFF: // Reset break; } return ; } if(byte >= 0xF0) // if System Common Category message { if(byte == SYSEX) { midi.runningStatus=byte; midi.msgByteCnt = 0; return; } else if(byte==SYSEND) { if(midi.runningStatus==SYSEX && midi.msgByteCnt==6) // if a complete valid Sys-Ex Frame has been received setSyx(midi.syxAdr, midi.byte1); // change configuration midi.msgByteCnt = 0; midi.runningStatus = 0; return ; } else if(byte ==SONG_POSITION_POINTER) { midi.runningStatus=byte; midi.msgByteCnt = 0; return ; } // // every other System Common is ignored midi.runningStatus = 0; // just reset running status return ; // and done } midi.runningStatus = 0; // just new status messages left, so running status is lost // only channel messages left here ... if(learn) // if learn mode { if(config.midiChannel != (byte&0xf)) // if message channel not current channel { config.midiChannel=byte&0xf; // assign channel setEeWrite(); // and store it } } midi.channel=(byte & 0xf)-config.midiChannel ; if ( midi.channel<config.numMidiChannels) // if MIDI channel matches, { midi.runningStatus = byte & 0xF0 ; // set running status (==Midi command w/o channel) midi.msgByteCnt = 0; return ; } return ; } else // data bytes goes here { switch (midi.runningStatus) // last command byte (or zero, if data is not for us) { case SONG_POSITION_POINTER: if(midi.msgByteCnt) // 2nd Byte { midi.spp= (((int)byte << 7 ) | midi.byte1) * 6; midi.runningStatus=0; } else { midi.byte1 = byte; midi.msgByteCnt=1; } break; case (NOTE_ON): // if we've had a note on byte if (midi.msgByteCnt) // if counter = 1 { // then we've just received a velocity byte midi.msgByteCnt = 0; // msg complete - clear byte count if (byte == 0) // is it a note off (velo = 0) ? note_off(midi.byte1,byte,midi.channel); else // NOTE ON { if(learn) { config.noteOffset=midi.byte1; setEeWrite(); } note_on(midi.byte1, byte,midi.channel); // ... and do what the note on has told us } } else // first data byte { midi.byte1 = byte; // save it midi.msgByteCnt = 1; // set counter to get 2nd data byte } break; case (NOTE_OFF): // command note off if (midi.msgByteCnt) // if complete { midi.msgByteCnt = 0; note_off(midi.byte1,byte,midi.channel); // switch note off } else { midi.byte1 = byte; midi.msgByteCnt = 1; } break; case (PITCH): // PITCH BEND command if (midi.msgByteCnt) // if second data byte (MSB) { int v; v=byte; v<<=6; v|=midi.byte1; v-=0x1000; setPb(v,midi.channel); midi.msgByteCnt = 0; } else { midi.byte1 = byte; midi.msgByteCnt = 1; return; } break; case (CC): // Continuous Controller Message if (midi.msgByteCnt) { midi.msgByteCnt = 0; if(learn) { config.cvMidiCC[2]=midi.byte1; setEeWrite(); } setCC(midi.byte1, byte,midi.channel); } else { midi.byte1 = byte; midi.msgByteCnt = 1; return; } break; // case (CHAN_PRES): // if channel pressure command // channel_pressure (byte); // set Aftertouch case SYSEX: // SYS-EX command midi.msgByteCnt++; // message Byte counter switch(midi.msgByteCnt) // which byte is on? { case 1: // Manufactorer if(byte !=0x70) // if not the right one midi.msgByteCnt=0; // frame error / wrong byte break; case 2: // Unit - ID if(byte!=0x7D) midi.msgByteCnt=0; break; case 3: // Midi-Adress (or Unit ID) (ignored!) if(byte>=0x10) // if not in legal range midi.msgByteCnt=0; // frame error break; case 4: // adress byte midi.syxAdr=byte; // store it for later break; case 5: // high data nibble if(byte<0x10) // if in range midi.byte1=(byte<<4); // set half the data else midi.msgByteCnt=0; break; case 6: // low data nibble if(byte<0x10) // if in range midi.byte1 |= byte; // or together the complete data byte else // evaluation is done in the code for SYSEND above! midi.msgByteCnt=0; break; default: // not our framing midi.msgByteCnt=0; break; } if(midi.msgByteCnt==0) // something did not fit midi.runningStatus=0; break; case 0: // bnwtko (bytes no one wants to know off ) return ; default: break; } } } }