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