//============================================================================= /* * Project name; MIDI_Bash1.c v1.0 (set TABS to 4) * Description; PIC16F876 based MIDI drum controller with 10 digital pads. This is my first MIDI-Bash (the lap drums). When any button is pressed it sends a MIDI note-on signal to MIDI-out plug, which makes a MIDI device play a drum sound. v1.0 has no velocity info, all drum sounds are the same velocity (volume). v1.0 has 10 drum hit buttons. There are 2 other buttons; up/down If a drum is hit while either button is pressed the drum sound changes up/down to the next note (next drum sound). All notes are limited to MIDI channel 10 (standard for drums). * Version; v1.0 initial testing, started 31/01/2009 * Test configuration; MCU: PIC16F876 (NOT A) Dev.Board: EasyPIC4 Oscillator: HS, 08.0000 MHz (2pin short-can xtal) SW: mikroC v7.0.0.3 * PIC pins; RC6 out serial MIDI out, manual bit banging (no usart needed) RC5 out drum led, flashes at power up and when any pad is hit RC0 in Note down button, pullup resistor, low=pressed RC1 in Note up button, pullup resistor, low=pressed RB0-RB7 in 8 "drum" buttons (0-7), pullup resistor, low=pressed RC2 in drum button (8), " RC3 in drum button (9), " (total 0-9 =10 drums) */ //============================================================================= // global vars char sdata; // disposable 2 bytes used for serial bit-banging; char sbit; // (must be first, need low place in ram for asm use) //char i; // used for anything char the_note; // the note to send to MIDI for this drum hit char note0; // MIDI note value for each of the 10 drum buttons char note1; // (which can be changed when device is operating) char note2; // char note3; // char note4; // char note5; // char note6; // char note7; // char note8; // char note9; // char wait_n0; // used for debounce and afterpause for each drum note char wait_n1; // char wait_n2; // char wait_n3; // char wait_n4; // char wait_n5; // char wait_n6; // char wait_n7; // char wait_n8; // char wait_n9; // // Constants used in my MIDI transmit funcions; #define MIDI_CHANNEL 10 // MIDI output channel (1-16) to drum machine //#define MIDI_NOTE_ON (0b10010000 + MIDI_CHANNEL-1) // byte used to send MIDI note-on command #define MIDI_NOTE_ON 0b10011001 // byte used to send MIDI note-on command #define NOTE_MIN 30 // lowest MIDI note that can be selected #define NOTE_MAX 98 // highest " (Zoom MRS1608 range is 31-70) 40 sounds // (Roland D-20 drum range is 35-97) 63 sounds // test this a bit... // starting values for drum sounds #define DEFNOTE_0 31 // kick (using Zoom MRS1608 standard drum notes) #define DEFNOTE_1 35 // snare #define DEFNOTE_2 40 // hat open #define DEFNOTE_3 45 // hat closed #define DEFNOTE_4 50 // tom1 #define DEFNOTE_5 55 // tom2 #define DEFNOTE_6 60 // tom3 #define DEFNOTE_7 65 // crash #define DEFNOTE_8 70 // crash #define DEFNOTE_9 75 // crash #define DEB_COUNT 21 // debounce wait count for debounce timer; // PIC 8Mhz prescale 32:1, so 21 TMR0 overflows = 84mS #define DEB_COUNTSAFE 5 // safe period for after debounce special situations #define PORT_MIDI PORTC // used for bit-banged serial out to MIDI #define PIN_MIDI 6 // " #define PIN_LED_DRUM PORTC.F5 // flashes a led when any drum button is pressed #define PIN_DOWNBUT PORTC.F0 #define PIN_UPBUT PORTC.F1 // drums 0-7 input pins is PORTB.F0 - PORTB.F7 #define PIN_DRUM8 PORTC.F2 // with these 2 is 10 drums total #define PIN_DRUM9 PORTC.F3 //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //void interrupt(void) //{ // is (TMR1) CCP2 capture int, so just clear TMR1 ?? //} //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //============================================================================= // SEND_MIDI_BYTE //============================================================================= void send_midi_byte(void) { //---------------------------------------------------- // This version uses TMR0 for bit-banging, no usart needed. // I wrote this function in PIC assembler to speed it up as the // MikroC compiler SUCKS when it comes to fast bit manipulation. //------------------------------------------------- // NOTE! MIDI data is electrically inverted! ie MIDI bit=1 is PIC pin=O // // start bit, 8 data bits (LSBit first), then stop bit // // PIC 8MHz = 2MHz ticks, so MIDI at 31250 baud = 64 timer ticks // using TMR0 prescaled at 32:1 so 31250 baud = 2 timer ticks // therefore whenever TMR0 bit1 changes = a baud // // This is a zero cumulative error system, even though there // is a variable 0-6 instruction delay for each bit // it still always re-syncs to TMR0,bit1 for the next bit. // Given that there is 64 insts between bits and that // the receiving device samples the bit right in the middle // (which is standard) this should give reliable serial // transmission. It will probably still work with an // internal RC osc at 4MHz too.. test later. //---------------------------------------------------- sbit = 0; // this is needed to make MikroC compiler recognise // variables in the asm block below... // weird, but it is covered in MikroC help file. asm { ;---------------------------- ; PIC 31250 baud MIDI bit-banging serial code - RomanBlack 2009 ; sends one byte out to MIDI port. byte is in sdata variable. ; PIC 8 MHz, TMR0 is free-running and prescaled at 32:1 ; it is easily adapted to 4 Mhz or 16 Mhz, just change the prescale. ; This assembler cource code is public domain, use it as you wish. ;---------------------------- ; pre-delay, to sync to a "baud", then send start bit predelay: BTFSC TMR0, 1 ; pre-delay to sync baud with TMR0 GOTO predelay ; wbStart: BTFSS TMR0, 1 ; again wait to ensure sync with baud GOTO wbStart ; BCF PORT_MIDI,PIN_MIDI ; send start bit (inverted remember) ;---------------------------- ; now send the 8 serial bits, as 4 pairs of 2 bits (even then odd). ; each bit tested first, then sync to baud using TMR0, then send bit. ; 8 MHz PIC, each baud is 64 insts. jitter per baud is 0 to 2 insts. ; system is zero-cumulative error, ie; it re syncs with every baud. ; this gives very solid performance without needing a usart. MOVLW 4 ; 4 bit pairs to send MOVWF sbit ; serial_even: BTFSC sdata,0 ; test serial even bit GOTO even1 ; even0: BTFSC TMR0, 1 ; wait for baud GOTO even0 ; BCF PORT_MIDI,PIN_MIDI ; send bit (inverted remember) GOTO serial_odd ; even1: BTFSC TMR0, 1 ; wait for baud GOTO even1 ; BSF PORT_MIDI,PIN_MIDI ; send bit (inverted remember) serial_odd: RRF sdata,f ; load next serial bit into sdata,0 BTFSC sdata,0 ; test serial odd bit GOTO odd1 ; odd0: BTFSS TMR0, 1 ; wait for baud GOTO odd0 ; BCF PORT_MIDI,PIN_MIDI ; send bit (inverted remember) GOTO pair_done ; odd1: BTFSS TMR0, 1 ; wait for baud GOTO odd1 ; BSF PORT_MIDI,PIN_MIDI ; send bit (inverted remember) pair_done: RRF sdata,f ; load next serial bit into sdata,0 DECFSZ sbit,f ; another bit pair was done, so test GOTO serial_even ; bits left! so send another pair ;---------------------------- ; send the stop bit wbStop: BTFSC TMR0, 1 ; wait to sync with baud GOTO wbStop ; BSF PORT_MIDI,PIN_MIDI ; send stop bit (inverted remember) } } //----------------------------------------------------------------------------- //============================================================================= // SEND_NOTE_ON //============================================================================= void send_note_on(void) { //------------------------------------------------- // Sends 3 serial bytes out to MIDI port at 31250 baud; // byte0 MIDI note on = 1001cccc (cccc = MIDI channel(1-16) -1) // byte1 note 0-127 (the specific drum sound) // byte2 velocity 0-127 (volume) i'm using fixed value of 100? // // This system just ties up the PIC while it sends // all 3 bytes. At standard MIDI 31250 baud that is less // than 1mS to send all 3 bytes so it shouldn't matter. // That is the fastest that MIDI can send drum messages anyway. //---------------------------------------------------- // byte0 = MIDI specific note-on command sdata = MIDI_NOTE_ON; // data byte to send send_midi_byte(); // byte1 = the note (which drum sound to play) MIDI range 0-127 sdata = the_note; // data byte to send send_midi_byte(); // byte2 = velocity (note volume) MIDI range 0-127 // for now using a fixed note velocity of 100 (near max) sdata = 100; // data byte to send send_midi_byte(); //---------------------------------------------------- } //----------------------------------------------------------------------------- //============================================================================= // MAIN //============================================================================= void main() { //------------------------------------------------- // Setup PIC registers etc //------------------------------------------------- // rough PIC setup for now! // Setup 16F876 ADCON0 = 0; // 0= ADC is off ADCON1 = 0x07; // 0x07 = PORTA digital pins, NOT adc pins PORTA = 0; TRISA = 0b00000000; // PORTA all outs, (unused?) PORTB = 0; TRISB = 0b11111111; // PORTB all in (drum0-drum7) PORTC = 0; TRISC = 0b00001111; // PORTC; RC6 = serial out to MIDI) // RC0=down button in, RC1=up button in // RC2=drum8 in, RC3=drum9 in INTCON = 0; // clear INTCON //------------------------------------------------- // TMR0 setup /* ; OPTION_REG setup 16F876 movlw b'00000100' ; 7 portb pullups, 1=disabled, 0=enabled ; 6 RBO/INT pin edge select, 1= rising, 0=falling edge ; 5 TMR0 clock source, 1=ext RA4, 0=int clock ; 4 TMR0 source ext RA4 edge, 0=rising edge ; 3 PSA prescaler assign, 1=WDT, 0=TMR0 ; 2 these three are the 3 bit prescaler rate; ; 1 000-111 = 2-256 for TMR0, 1-128 for WDT ; 0 (using 100 = 32:1 prescale) */ OPTION_REG = 0b00000100; // PIC 8MHz = 2MHz ticks, so MIDI at 31250 baud = 64 timer ticks // using TMR0 prescaled at 32:1 so 31250 baud = 2 timer ticks // therefore whenever TMR0 bit1 changes = a baud // I think for different PIC clock speeds you can just // change the prescale value and leave the rest of the code as is. // PIC 4MHz use 16:1 // PIC 8MHz use 32:1 (this code) // PIC 16MHz use 64:1 //------------------------------------------------- // TMR1 setup /* ; timer1 control movlw b'00000000' ; 7------- na ; -6------ na ; --5----- TIMER1 prescaler... two bits (5,4) ; ---4---- 00=1, 01=2, 10=4, 11=8 prescale (using 00= 1:1) ; ----3--- T1OSCEN - timer1 osc enable, 1=en ; -----2-- T1SYNC - ext sync ; ------1- TMR1CS - ext/inter, 0=internal clock ; -------0 TMR1ON - 1=timer1 enabled */ T1CON = 0b00000000; // TMR1 not used //------------------------------------------------- // Initialise vars //------------------------------------------------- // load the starting (default) note values for the 8 drums note0 = DEFNOTE_0; note1 = DEFNOTE_1; note2 = DEFNOTE_2; note3 = DEFNOTE_3; note4 = DEFNOTE_4; note5 = DEFNOTE_5; note6 = DEFNOTE_6; note7 = DEFNOTE_7; note8 = DEFNOTE_7; note9 = DEFNOTE_7; // load the wait timers for each drum; // this gives a bootup delay before any drums can be // triggered, 200 gives about 0.8 seconds. // (the drum led will also be on for this 0.8 secs) wait_n0 = 200; wait_n1 = 200; wait_n2 = 200; wait_n3 = 200; wait_n4 = 200; wait_n5 = 200; wait_n6 = 200; wait_n7 = 200; wait_n8 = 200; wait_n9 = 200; // set the MIDI output to off (HI) PORTC = 0; // safe; drum led off etc PORT_MIDI.F6 = 1; // MIDI output pin off (HI) //------------------------------------------------- // MAIN RUN MODE AFTER HERE //------------------------------------------------- // Operation; // * the 8 inputs are checked to see if button pressed (LO) // * if pressed, send 3 bytes to MIDI (make one drum sound) // * debounce inputs with a wait timer to stop retriggering drums! // // Details; // * test all inputs; only test an input if its wait timer==0 // * if a button is pressed; // * set its wait timer to a "debounce" value // * send 3 MIDI bytes, do nothing else until all 3 sent ok. // * every TMR0 overflow; // * if wait timer >1; dec wait timer // * if wait timer==1, dec only if button is released (see below) // // This is about the simplest system to sense 8 buttons then fire drum // sounds off to MIDI, then ensure a simple delay with a countdown timer // to stop a drum being retriggered too soon. The simplest debounce // is used so it just counts a time period after the button was first // pressed, after a set time it is tested to make sure it is released, // then resets ready to be retriggered again. So if a button is held // down it will only make the drum sound once. This gives a faster // max drumroll speed then a long delay which would be needed for // other debounce systems. //------------------------------------------------- TMR0 = 0; // reset timer0 INTCON.T0IF = 0; // clear TMR0 overflow flag // main run loop while(1) { //--------------------------------------- // this loop mainly just checks the 10 drum buttons. //--------------------------------------- // if any button has JUST been pressed; send drum note if(wait_n0==0 && PORTB.F0==0) // if debounce is reset && button pressed { // start debounce timer wait_n0 = DEB_COUNT; // TEMP!! test to led //PORTC.F7 = 1; // if up/down buttons pressed (LO), change the selected note (drum) if(PIN_DOWNBUT == 0) note0--; // change note if(PIN_UPBUT == 0) note0++; if(note0 < NOTE_MIN) note0=NOTE_MIN; // and limit at min/max if(note0 > NOTE_MAX) note0=NOTE_MAX; // now play the drum sound the_note = note0; // the drum note to send out to MIDI send_note_on(); // send it! } if(wait_n1==0 && PORTB.F1==0) { // TEMP!! test to led //PORTC.F7 = 0; wait_n1 = DEB_COUNT; if(PIN_DOWNBUT == 0) note1--; if(PIN_UPBUT == 0) note1++; if(note1 < NOTE_MIN) note1=NOTE_MIN; if(note1 > NOTE_MAX) note1=NOTE_MAX; the_note = note1; send_note_on(); } if(wait_n2==0 && PORTB.F2==0) { wait_n2 = DEB_COUNT; if(PIN_DOWNBUT == 0) note2--; if(PIN_UPBUT == 0) note2++; if(note2 < NOTE_MIN) note2=NOTE_MIN; if(note2 > NOTE_MAX) note2=NOTE_MAX; the_note = note2; send_note_on(); } if(wait_n3==0 && PORTB.F3==0) { wait_n3 = DEB_COUNT; if(PIN_DOWNBUT == 0) note3--; if(PIN_UPBUT == 0) note3++; if(note3 < NOTE_MIN) note3=NOTE_MIN; if(note3 > NOTE_MAX) note3=NOTE_MAX; the_note = note3; send_note_on(); } if(wait_n4==0 && PORTB.F4==0) { wait_n4 = DEB_COUNT; if(PIN_DOWNBUT == 0) note4--; if(PIN_UPBUT == 0) note4++; if(note4 < NOTE_MIN) note4=NOTE_MIN; if(note4 > NOTE_MAX) note4=NOTE_MAX; the_note = note4; send_note_on(); } if(wait_n5==0 && PORTB.F5==0) { wait_n5 = DEB_COUNT; if(PIN_DOWNBUT == 0) note5--; if(PIN_UPBUT == 0) note5++; if(note5 < NOTE_MIN) note5=NOTE_MIN; if(note5 > NOTE_MAX) note5=NOTE_MAX; the_note = note5; send_note_on(); } if(wait_n6==0 && PORTB.F6==0) { wait_n6 = DEB_COUNT; if(PIN_DOWNBUT == 0) note6--; if(PIN_UPBUT == 0) note6++; if(note6 < NOTE_MIN) note6=NOTE_MIN; if(note6 > NOTE_MAX) note6=NOTE_MAX; the_note = note6; send_note_on(); } if(wait_n7==0 && PORTB.F7==0) { wait_n7 = DEB_COUNT; if(PIN_DOWNBUT == 0) note7--; if(PIN_UPBUT == 0) note7++; if(note7 < NOTE_MIN) note7=NOTE_MIN; if(note7 > NOTE_MAX) note7=NOTE_MAX; the_note = note7; send_note_on(); } if(wait_n8==0 && PIN_DRUM8==0) { wait_n8 = DEB_COUNT; if(PIN_DOWNBUT == 0) note8--; if(PIN_UPBUT == 0) note8++; if(note8 < NOTE_MIN) note8=NOTE_MIN; if(note8 > NOTE_MAX) note8=NOTE_MAX; the_note = note8; send_note_on(); } if(wait_n9==0 && PIN_DRUM9==0) { wait_n9 = DEB_COUNT; if(PIN_DOWNBUT == 0) note9--; if(PIN_UPBUT == 0) note9++; if(note9 < NOTE_MIN) note9=NOTE_MIN; if(note9 > NOTE_MAX) note9=NOTE_MAX; the_note = note9; send_note_on(); } //--------------------------------------- // Now check for TMR0 event! if so, dec the 8 wait timers. // // PIC 8MHz needs TMR0 prescaled at 32:1 // so each overflow occurs at 2Mhz / 32 / 256 = 244Hz // 1/244 = 0.004096 so time between each overflow is 4mS // // To get good drum speed for drumroll etc we need to be able // to get about 12 drum hits per second. However to stop // double triggering with one hit (that's bad!) the debounce // needs to be as long as possible. So we use the max debounce; // ie use; 1 second /12 = 83mS. // as each overflow is 4mS, we use 21 overflows = 84mS // // That value 21 is set in the DEB_COUNT constant. // if using other PIC clock speeds just change TMR0 // prescale value - see timer0 setup. //--------------------------------------- // * every TMR0 overflow; // * if wait timer >1; dec wait timer // * if wait timer==1, dec only if button is released (see below) //--------------------------------------- // check for TMR0 event! if so, dec the 8 wait timers. if(INTCON.T0IF == 1) // if TMR0 overflowed { // first clear overflow flag INTCON.T0IF = 0; // set the drum led to off (LO) by default PIN_LED_DRUM = 0; // then process all 8 drum button timers if(wait_n0 > 0) { wait_n0--; // subtract another overflow period // if button is still pressed (LO), thats bad! // so add some more debounce time to allow for // any button release bounce to be past. if(wait_n0==0 && PORTB.F0==0) wait_n0=DEB_COUNTSAFE; // if any wait timer is >0 then set the drum led to on! // this makes the drum led give a quick flash // every time a drum button is hit. PIN_LED_DRUM = 1; // led on = HI } if(wait_n1 > 0) { wait_n1--; if(wait_n1==0 && PORTB.F1==0) wait_n1=DEB_COUNTSAFE; PIN_LED_DRUM = 1; } if(wait_n2 > 0) { wait_n2--; if(wait_n2==0 && PORTB.F2==0) wait_n2=DEB_COUNTSAFE; PIN_LED_DRUM = 1; } if(wait_n3 > 0) { wait_n3--; if(wait_n3==0 && PORTB.F3==0) wait_n3=DEB_COUNTSAFE; PIN_LED_DRUM = 1; } if(wait_n4 > 0) { wait_n4--; if(wait_n4==0 && PORTB.F4==0) wait_n4=DEB_COUNTSAFE; PIN_LED_DRUM = 1; } if(wait_n5 > 0) { wait_n5--; if(wait_n5==0 && PORTB.F5==0) wait_n5=DEB_COUNTSAFE; PIN_LED_DRUM = 1; } if(wait_n6 > 0) { wait_n6--; if(wait_n6==0 && PORTB.F6==0) wait_n6=DEB_COUNTSAFE; PIN_LED_DRUM = 1; } if(wait_n7 > 0) { wait_n7--; if(wait_n7==0 && PORTB.F7==0) wait_n7=DEB_COUNTSAFE; PIN_LED_DRUM = 1; } if(wait_n8 > 0) { wait_n8--; if(wait_n8==0 && PIN_DRUM8==0) wait_n8=DEB_COUNTSAFE; PIN_LED_DRUM = 1; } if(wait_n9 > 0) { wait_n9--; if(wait_n9==0 && PIN_DRUM9==0) wait_n9=DEB_COUNTSAFE; PIN_LED_DRUM = 1; } } //--------------------------------------- } } //----------------------------------------------------------------------------- //============================================================================= // BLANK //============================================================================= void blank(void) { //------------------------------------------------- //------------------------------------------------ } //-----------------------------------------------------------------------------