MCV876 Controller Update: Source Code: Unterschied zwischen den Versionen

Aus Synthesizer Wiki
Zur Navigation springen Zur Suche springen
(Created page with '=Ü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 m…')
 
Zeile 9: Zeile 9:


=Quelltext=
=Quelltext=
<pre>
<pre>/*
/*
MCV876 with ATMega 88  
MCV876 with ATMega 88  


Zeile 16: Zeile 15:


Preliminary  
Preliminary  
Version 0.20 2011-06-22
Version 0.20 2011-06-23




Zeile 57: Zeile 56:


#define LEDOUT LEDGATE4 // select your evil...  
#define LEDOUT LEDGATE4 // 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 BUTTON (!(PIND & (1<<4))) // True if Button pressed  
Zeile 65: Zeile 67:
#define NUM_PARAMTERS 17 // and there are 17 parameters  
#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 {
struct CONFIG {
Zeile 106: Zeile 113:


#define EE_MAGIC 17
#define EE_MAGIC 17
unsigned char ee_magic EEMEM;
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.  
unsigned char config_eeprom[NUM_PARAMTERS] EEMEM; // Config in EEPROM, saves it during power down.  
#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




Zeile 851: Zeile 850:


// activ sense  
// activ sense  
if(watchTicker)
if(watchTicker) // if active sense is activated
{
{
watchTicker--;
watchTicker--; // decrement time
if(watchTicker==0)
if(watchTicker==0) // if time elapsed
resetAll();  
resetAll(); // switch all notes off
}
}


// retrigger timing  
// retrigger timing  
for(i=0;i<NUM_CV;i++)
for(i=0;i<NUM_CV;i++) // for all cv output channels
{
{
if(cv_out[i].gateDipper)
if(cv_out[i].gateDipper) // if gate timer is active
{
{
--cv_out[i].gateDipper;
--cv_out[i].gateDipper; // decrement timer
if(cv_out[i].gateDipper==0)
if(cv_out[i].gateDipper==0) // if timer elapsed
gate_on(i);
gate_on(i); // activate gate
}
}
}
}
Zeile 923: Zeile 922:
     if ((status & (FRAMING_ERROR | PARITY_ERROR | DATA_OVERRUN))==0 )
     if ((status & (FRAMING_ERROR | PARITY_ERROR | DATA_OVERRUN))==0 )
{
{
if(watchTicker) // if there had been an active sense  
if(watchTicker) // if there had been an active sense  
watchTicker=160; // every byte is ok, to see that cable is still plugged in
watchTicker=ACTIVE_SENSE_TICKS; // every new byte is ok, to "feed the watchdog"


if (byte > 0x7F)                 // if command byte (MSB set)
if (byte > 0x7F)                 // if command byte (MSB set)
Zeile 934: Zeile 933:
case 0xF8: // Clock  
case 0xF8: // Clock  
{
{
if(midi.startFlag)  // synchronized start  
if(midi.startFlag)  // if synchronized start  
{
{
midi.startStop=1;
midi.startStop=1;
Zeile 942: Zeile 941:
gate_on(4);
gate_on(4);
}
}
else if(midi.startStop) // running  
else if(midi.startStop) // if (already) running  
{
{
++spp;
++spp;
Zeile 949: Zeile 948:
barTick=0;  
barTick=0;  
}
}
++midi.clockTick;  
++midi.clockTick; // increment counter for clock output
if(midi.clockTick >= config.clockDivider) // clock output  
if(midi.clockTick >= config.clockDivider) // if divider cout elapsed do clock output  
{
{
midi.clockTick=0;
midi.clockTick=0;
gate_on(5);
gate_on(5);
gate5off=clocker>>1; // pulse-width == half of a midi clock  
gate5off=clocker>>1; // pulse-width counter = half of current midi clock pulse width
}
}


if(midi.syncStartFlag && barTick==0 && midi.startStop ) // synchronize clock out to bars   
if(midi.syncStartFlag && barTick==0 && midi.startStop ) // synchronize clock output to bars   
{
{
midi.clockTick=0;
midi.clockTick=0;
Zeile 967: Zeile 966:
break;
break;
}
}
case 0xF9: // Tick
case 0xF9: // Tick (unused)
break;  
break;  
case 0xFA: // Start  
case 0xFA: // Start  
spp=0;
if(! midi.startStop) // if not already running
barTick=0;
{
midi.startFlag=1;
spp=0;
barTick=0;
midi.startFlag=1;
}
break;
break;
case 0xFB: // Continue
case 0xFB: // Continue
midi.startFlag=1;
if(! midi.startStop) // if not already running
midi.startFlag=1;
break;  
break;  
case 0xFC: // Stop
case 0xFC: // Stop
Zeile 984: Zeile 987:
break;  
break;  
case 0xFE: //  Active Sense
case 0xFE: //  Active Sense
watchTicker=160;
watchTicker=ACTIVE_SENSE_TICKS;
break;
break;
case 0xFF: // Reset
case 0xFF: // Reset  
break;  
break;  
}
}
return ;  
return ;  
}
}  
 
 


if(byte >= 0xF0) // if System Common Category message
if(byte >= 0xF0) // if System Common Category message
Zeile 1.004: Zeile 1.005:
else if(byte==SYSEND)
else if(byte==SYSEND)
{
{
if(midi.runningStatus==SYSEX && midi.msgByteCnt==6)
if(midi.runningStatus==SYSEX && midi.msgByteCnt==6) // if a complete valid Sys-Ex Frame has been received
setSyx(midi.syxAdr, midi.byte1);  
setSyx(midi.syxAdr, midi.byte1); // change configuration
midi.msgByteCnt = 0;
midi.msgByteCnt = 0;
midi.runningStatus = 0;
return ;
}
}
else if(byte ==SONG_POSITION_POINTER)
else if(byte ==SONG_POSITION_POINTER)
Zeile 1.013: Zeile 1.016:
midi.msgByteCnt = 0;
midi.msgByteCnt = 0;
return ;  
return ;  
}
}
midi.runningStatus = 0;
// // every other System Common is ignored
return ;  
midi.runningStatus = 0; // just reset running status
return ; // and done
}
}


Zeile 1.021: Zeile 1.025:


// only channel messages left here ...  
// only channel messages left here ...  
if(learn)
if(learn) // if learn mode
{
{
if(config.midiChannel != (byte&0xf)) // if message channel not current channel
if(config.midiChannel != (byte&0xf)) // if message channel not current channel
{
{
config.midiChannel=byte&0xf; // assign channel  
config.midiChannel=byte&0xf; // assign channel  
setEeWrite(); // and store it  
setEeWrite(); // and store it  
}
}
}
}
Zeile 1.039: Zeile 1.043:
return ;  
return ;  
}
}
else
else // data bytes goes here
{
{
switch (midi.runningStatus)     // last command byte (or zero, if data is not for us)  
switch (midi.runningStatus)     // last command byte (or zero, if data is not for us)  
Zeile 1.079: Zeile 1.083:
}
}
break;
break;
case (NOTE_OFF):         // if we've had a note off byte
case (NOTE_OFF):         // command note off
if (midi.msgByteCnt) // is second data byte ?
if (midi.msgByteCnt) // if complete
{
{
midi.msgByteCnt = 0; // clear byte count
midi.msgByteCnt = 0;
note_off(midi.byte1,byte,midi.channel); // switch note off
note_off(midi.byte1,byte,midi.channel); // switch note off
}
}
Zeile 1.088: Zeile 1.092:
{
{
midi.byte1 = byte;
midi.byte1 = byte;
midi.msgByteCnt = 1; // set counter to get 2nd byte
midi.msgByteCnt = 1;
}
}
break;
break;
case (PITCH):         // PITCH BEND command
case (PITCH):         // PITCH BEND command
if (midi.msgByteCnt)  // second data byte (MSB)  
if (midi.msgByteCnt)  // if second data byte (MSB)  
{
{
int v;
int v;
Zeile 1.101: Zeile 1.105:
setPb(v,midi.channel);
setPb(v,midi.channel);


midi.msgByteCnt = 0; // clear byte count
midi.msgByteCnt = 0;
}
}
else
else
{
{
midi.byte1 = byte;
midi.byte1 = byte;
midi.msgByteCnt = 1; // set counter to get next byte
midi.msgByteCnt = 1;
return;
return;
}
}
break;
break;
case (CC):             // Continuous Controller Message  
case (CC):             // Continuous Controller Message  
if (midi.msgByteCnt) // if we've had first byte
if (midi.msgByteCnt)
{
{
midi.msgByteCnt = 0; // clear byte count
midi.msgByteCnt = 0;


if(learn)
if(learn)
Zeile 1.126: Zeile 1.130:
{
{
  midi.byte1 = byte;
  midi.byte1 = byte;
  midi.msgByteCnt = 1; // set counter ready to get 3rd byte
  midi.msgByteCnt = 1;
  return;  
  return;  
}
}
break;
break;
// case (CHAN_PRES):         // if channel pressure command
// case (CHAN_PRES):         // if channel pressure command
// channel_pressure (byte); // set Aftertouch  
// channel_pressure (byte); // set Aftertouch  
case SYSEX:
case SYSEX: // SYS-EX command
midi.msgByteCnt++;
midi.msgByteCnt++; // message Byte counter
switch(midi.msgByteCnt)
switch(midi.msgByteCnt) // which byte is on?
{
{
case 1: // Manufactorer  
case 1: // Manufactorer  
if(byte==0x70)
if(byte !=0x70) // if not the right one
;
midi.msgByteCnt=0; // frame error / wrong byte
else
midi.msgByteCnt=0;
break;  
break;  
case 2: // Unit - ID  
case 2: // Unit - ID  
if(byte==0x7D)
if(byte!=0x7D)
;
else
midi.msgByteCnt=0;
midi.msgByteCnt=0;
break;  
break;  
case 3: // Midi-Adress (ignored!)
case 3: // Midi-Adress (or Unit ID) (ignored!)
if(byte<0x10)
if(byte>=0x10) // if not in legal range
;
midi.msgByteCnt=0; // frame error
else
midi.msgByteCnt=0;
break;
break;
case 4: // adress byte
case 4: // adress byte
midi.syxAdr=byte;
midi.syxAdr=byte; // store it for later
break;
break;
case 5: // high data nibble  
case 5: // high data nibble  
if(byte<0x10)
if(byte<0x10) // if in range
{
midi.byte1=(byte<<4); // set half the data
midi.byte1=(byte<<4);
}
else  
else  
midi.msgByteCnt=0;
midi.msgByteCnt=0;
break;
break;
case 6: // low data nibble  
case 6: // low data nibble  
if(byte<0x10)
if(byte<0x10) // if in range
{
midi.byte1 |= byte; // or together the complete data byte
midi.byte1 |= byte;
else // evaluation is  done in the code for SYSEND above!
}
else  
midi.msgByteCnt=0;
midi.msgByteCnt=0;
break;
break;
default: // not our framing  
default: // not our framing  
midi.msgByteCnt=0;
midi.msgByteCnt=0;
break;
break;
Zeile 1.188: Zeile 1.182:
}
}
}
}


</pre>
</pre>

Version vom 23. Juni 2011, 09:27 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, 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 [->Link auf Konverter folgt...], und das an den Controller zu schicken. Dann programmiert der sich selber um. Dazu muss er allerdings einmalig mit dem Bootlader [hier Link auf Bootlader imaginieren.. ] 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.20 2011-06-23 


    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... 




/*	===================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 LEDGATE4 // 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 1 Low nibble CC-Bits, high: interface mode 
			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. 


// to have the clock synced to "beat 1" of a bar ... 
volatile unsigned int spp; 	// song position pointer (from midi message) 
unsigned char ticksPerBar=96; // How many ticks per bar (is not tx-ed: guess 96 for 4/4 ...  ) 
volatile unsigned char barTick; // where are we inside the bar (counted, because calculation from spp takes some cpu-cycles ) 

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 clocker;	 // timer counter for half a midi-clock (which is needed for divider 1)
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=6;

	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 
	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++;
		}
	}
	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
	{
		val=cvo->ccVal; // <<1;
/*
		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) // Velocity 
		val+=cvo->velo;
	if(config.ifMode2 & (bm<<4)) // PB 
		val+=cvo->pb;

	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=0x80;
		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(channel);
					else 
						gate_off(channel);
				}
			}
		}
		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>>6);	
			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=0x80;
		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) 
	clocker++;
	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 char syncStartFlag;	// start on next 1 
}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;
							midi.syncStartFlag=1;
							midi.clockTick=0;
							gate_on(4);
						}
						else if(midi.startStop)	// if (already) running 
						{
							++spp;
							++barTick;
							if(barTick >= ticksPerBar)
								barTick=0; 
						}
						++midi.clockTick; 	// increment counter for clock output 
						if(midi.clockTick >= config.clockDivider)	// if divider cout elapsed do clock output 
						{
							midi.clockTick=0;
							gate_on(5);
							gate5off=clocker>>1; // pulse-width counter = half of current midi clock pulse width
						}

						if(midi.syncStartFlag && barTick==0 && midi.startStop ) // synchronize clock output to bars   
						{	
							midi.clockTick=0;
							gate_on(5);
							gate5off=clocker>>1;
							midi.syncStartFlag=0;
						}
						clocker=0;  // restart midi clock pulse width counter. 
						break;
					}
					case 0xF9:	// Tick (unused) 
						break; 
					case 0xFA: 	// Start 
						if(! midi.startStop) // if not already running 
						{
							spp=0;
							barTick=0;
							midi.startFlag=1;
						}
						break;
					case 0xFB:	// Continue
						if(! midi.startStop) // if not already running 
							midi.startFlag=1;
						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
				{
					spp= (((int)byte << 7 ) | midi.byte1) * 6;
					barTick = spp % (unsigned int) ticksPerBar; 
					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-=0x2000;
					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[3]=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;
			}
		}
	}
}