MCV876 Controller Update: Source Code: Unterschied zwischen den Versionen

Aus Synthesizer Wiki
Zur Navigation springen Zur Suche springen
 
(5 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, Quelltext in das (automatisch erzeugte, leere) C-File kopieren, auf Build->Build klicken, fertig.  
Im Studio ein neues, leeres Projekt mit Standard-Einstellungen, einem sinnvollen Namen und an einem Ort, wo man es wiederfindet erstellen.
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.
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=
Zeile 15: Zeile 21:


Preliminary  
Preliminary  
Version 0.20 2011-06-23
Version 0.22 2011-06-26




Zeile 37: Zeile 43:
#include <avr/eeprom.h> // ... oh.. and some eeprom access would be nice
#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 <avr/interrupt.h> // ... and w/o the interupts it wouldn't do anything...  
 
#include <util/delay.h>




Zeile 55: Zeile 61:




#define LEDOUT LEDGATE4 // select your evil...  
#define LEDOUT LEDLED // select your evil...  




Zeile 107: Zeile 113:
6,  // Divider 6 (1/16 Note)  
6,  // Divider 6 (1/16 Note)  
1,  // clock time (unused)  
1,  // clock time (unused)  
0x0e, // mode 1 Low nibble CC-Bits, high: interface mode  
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
0x82, // mode 2 High Nibble: PB-bits, Low: Velo-Bits
0,    // mode 3 (5V-Bits)  
0,    // mode 3 (5V-Bits)  
Zeile 116: Zeile 123:
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.  


// 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  eepromWriteFlag; // set to one, if main-loop should write eeprom  
Zeile 128: Zeile 130:
volatile unsigned char blinker; // some count for blinkenlights  
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 gate5off; // timer counter for when clock gate output should go low again.  


Zeile 153: Zeile 154:
int main(void)
int main(void)
{
{
unsigned char blink=6;
unsigned char blink=4;


unsigned char i,x;  
unsigned char i,x;  
Zeile 215: Zeile 216:


// Read Configuration  
// Read Configuration  
if( eeprom_read_byte(ee_magic) == EE_MAGIC  && !BUTTON) // if eeprom valid AND Button not pressed  
_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;  
cp=(unsigned char* )&config;  
Zeile 225: Zeile 227:
}
}
}
}
else
blink=8;
setConfig();
setConfig();


Zeile 241: Zeile 246:
cp++;
cp++;
}
}
eeprom_write_byte(ee_magic,EE_MAGIC);  
eeprom_write_byte(&ee_magic,EE_MAGIC);  


setLed(1);
setLed(1);
Zeile 465: Zeile 470:
if(config.ifMode0 & bm) // 1=CC-Mode
if(config.ifMode0 & bm) // 1=CC-Mode
{
{
val=cvo->ccVal; // <<1;
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  
if( (config.ifMode3 & bm) && cvo->on==0) // 5V-Flag, abused as Gated-Mode-CV Flag  
Zeile 480: Zeile 502:
else
else
val <<= 1;
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  
if(config.ifMode2 & bm) // Velocity  
val+=cvo->velo;
val+=cvo->velo;
if(config.ifMode2 & (bm<<4)) // PB
 
val+=cvo->pb;


if(val<0 || gated) // clip values to converter range  
if(val<0 || gated) // clip values to converter range  
Zeile 522: Zeile 547:
{
{
cvo->cvOut=i;
cvo->cvOut=i;
cvo->pb=0x80;
cvo->pb=0;
cvo->ccVal=0;  
cvo++;
cvo++;
}  
}  
Zeile 607: Zeile 633:
{
{
if(value>config.gateTreshhold)
if(value>config.gateTreshhold)
gate_on(channel);
gate_on(i);
else  
else  
gate_off(channel);
gate_off(i);
}
}
}
}
Zeile 628: Zeile 654:
if(channel==cvo->channel && (config.ifMode2 & (16<<i)) )
if(channel==cvo->channel && (config.ifMode2 & (16<<i)) )
{
{
cvo->pb =(signed char) (value>>6);
cvo->pb =(signed char) (value>>5);
updateCv(cvo);
updateCv(cvo);
}
}
Zeile 782: Zeile 808:
cvo->ccVal=0;
cvo->ccVal=0;
cvo->on=0;
cvo->on=0;
cvo->pb=0x80;
cvo->pb=0;
updateCv(cvo);
updateCv(cvo);
cvo++;
cvo++;
Zeile 841: Zeile 867:


// Gate 5 timing (clock output)  
// Gate 5 timing (clock output)  
clocker++;
if(gate5off)
if(gate5off)
{
{
Zeile 897: Zeile 922:
unsigned char startFlag; // Start on next clock
unsigned char startFlag; // Start on next clock
unsigned char clockTick; // counter for clock division
unsigned char clockTick; // counter for clock division
unsigned char channel; // channel of current midi-command (relative, e.g. 0..3 )  
unsigned char channel; // channel of current midi-command (relative, e.g. 0..3 )
unsigned char syncStartFlag; // start on next 1
unsigned int  spp; // song position pointer (from midi message), needed to have the clock synced to song position
}midi;  
}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.  
// (*) running status is also used as 'current status' - it remembers allways, what next data byte is for. Or its zero, than data is ignored.  
Zeile 937: Zeile 962:
midi.startStop=1;
midi.startStop=1;
midi.startFlag=0;
midi.startFlag=0;
midi.syncStartFlag=1;
 
midi.clockTick=0;
if(config.clockDivider>1 )// if divided clock
gate_on(4);
{
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
else  
{
{
++spp;
if(midi.startStop) // if (already) running
++barTick;
++midi.spp;
if(barTick >= ticksPerBar)
++midi.clockTick; // increment counter for clock output
barTick=0;  
}
}
++midi.clockTick; // increment counter for clock output
 
if(midi.clockTick >= config.clockDivider) // if divider cout elapsed do clock output
if(config.clockDivider<=1)
{
{
midi.clockTick=0;
gate_on(5); // do clock output
gate_on(5);
gate5off=3;
gate5off=clocker>>1; // pulse-width counter = half of current midi clock pulse width
}
}
 
else
if(midi.syncStartFlag && barTick==0 && midi.startStop ) // synchronize clock output to bars 
{
{
if(midi.clockTick >= config.clockDivider) // if divider count elapsed
midi.clockTick=0;
{
gate_on(5);
midi.clockTick=0;
gate5off=clocker>>1;
gate_on(5); // do clock output
midi.syncStartFlag=0;
}
else if(midi.clockTick == config.clockDivider>>1) // if divider count half elapsed
gate_off(5); // set clock output off
}
}
clocker=0;  // restart midi clock pulse width counter.
break;
break;
}
}
Zeile 971: Zeile 1.009:
if(! midi.startStop) // if not already running  
if(! midi.startStop) // if not already running  
{
{
spp=0;
midi.spp=0;
barTick=0;
midi.startFlag=1;
midi.startFlag=1;
gate_on(4);
if(config.clockDivider>1)
{
gate_off(5); // set clock output off
midi.clockTick=0;
}
}
}
break;
break;
case 0xFB: // Continue
case 0xFB: // Continue
if(! midi.startStop) // if not already running  
if(! midi.startStop) // if not already running  
{
midi.startFlag=1;
midi.startFlag=1;
gate_on(4);
}
break;  
break;  
case 0xFC: // Stop
case 0xFC: // Stop
Zeile 1.050: Zeile 1.096:
if(midi.msgByteCnt) // 2nd Byte
if(midi.msgByteCnt) // 2nd Byte
{
{
spp= (((int)byte << 7 ) | midi.byte1) * 6;
midi.spp= (((int)byte << 7 ) | midi.byte1) * 6;
barTick = spp % (unsigned int) ticksPerBar;  
midi.runningStatus=0;
midi.runningStatus=0;
}
}
Zeile 1.102: Zeile 1.147:
v<<=6;
v<<=6;
v|=midi.byte1;
v|=midi.byte1;
v-=0x2000;
v-=0x1000;
setPb(v,midi.channel);
setPb(v,midi.channel);


Zeile 1.121: Zeile 1.166:
if(learn)
if(learn)
{
{
config.cvMidiCC[3]=midi.byte1;
config.cvMidiCC[2]=midi.byte1;
setEeWrite();
setEeWrite();
}
}

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;
			}
		}
	}
}