/* * PIC MCU code for Digital Theremin * Nathan Palmer,Alex Milevsky * Oct. 2002 * theremin.c * * Interfaces the Hitachi HD44780 in 4 bit mode (the standard 14 pin * LCD connector is used). Interfaces standard MIDI devices. This software * is used to interface an analog Theremin with a PC useing stdandard * MIDI protocal messages. The current note/volume being played on the * Theremin is displayed on an LCD screen and also sent to a PC for * use with custom teaching software. * * This code was tested on a 16F876@10Mhz ONLY! * (note!-the send_midi function is device specific) * PINOUT: * * * Revisions: -------------------------------------------------------------- 1.00: Nov, 2002 (nathan) - designed for 16F84A - MIDI output capability - designed for 10Mhz 1.02: Nov. 2002 (nathan) - added LCD functions 1.03: Oct. 2002 (alex) - added expanded MIDI functionality - pitchwheel support - MIDI volume control - MIDI external device wakeup 1.04: Oct. 2002 (nathan) - converted to 16F876 code - rewrote delay functions - added __CONFIG() directive - reorganized init functions - added interrupt TMR0 for periodic polling - made the code less freq dependent 1.05: Oct. 2002 (nathan) - disabled PORTA analog featuresto allow digital I/O - moved ALL LCD pins to port A - PB0 Interrupt is enabled for freq detection - freq dectection (using TMR1) functionality - fixed problems with lcd_number(...) 1.06: Nov. 2002 (nathan) - Enabled ADC on porA - Volume input is now measured on PA0 - with referance to VREF+ and VREF-. Curr_volume returns the 10-bit digital value --------------------------------------------------------------*/ //#define _16F876 #include #include __CONFIG(WDTDIS & HS & UNPROTECT & PWRTEN & LVPDIS & BORDIS & DEBUGDIS & WRTEN); /*------ function prototypes --------*/ void Delay10Us(long t); // for 4MHz void DelayMs(long t); // delays t millisecs void lcd_write(unsigned char c); void lcd_clear(void); void lcd_home(void); void lcd_puts(const char * s); void lcd_putch(char c); void lcd_goto(unsigned char pos); void lcd_init(void); void lcd_custom(void); void lcd_number(unsigned int t); void lcd_display_note(); void sendMIDI(unsigned char bytecode); void beginMIDI(); void PitchWheel(unsigned char pitchhigh,unsigned char pitchlow); void Volume(unsigned char volumebyte); void ChangeMIDI_up(); void ChangeMIDI_down(); void interrupt tc_int(void); void init_ports(); void LCD_Update(int myperiod); int curr_volume(); float curr_freq(); /*------ General Defines -------------------------------*/ #define PORTBIT(adr, bit) ((unsigned)(&adr)*8+(bit)) #define NOP asm("nop") #define PORTA_IO 0b00001101 /* 0 = output */ #define PORTB_IO 0b00000001 #define PORTC_IO 0b00000000 #define MHZ *1000L /* number of kHz in a MHz */ #define KHZ *1L /* number of kHz in a kHz */ #define XTAL_FREQ 10MHZ #define PERIOD 1000 // period in uS - one second here #define XTAL 10000000 #define ICLK (XTAL/4) /* crystal is divided by four - 2500000Hz */ #define IPRD .0000004 /* 1/ICLK */ #define SCALE 256 // prescale by 16 - check for overflow! //#define PRELOAD PERIOD*ICLK/SCALE/1000000 #define PRELOAD 195 /* 195 = 100 Hz */ //#define PRELOAD (PERIOD*ICLK)/(255*SCALE) /*------ General Defines ----------------------------*/ static bit Volume_in @ PORTBIT(PORTA, 0); static bit DEBUG @ PORTBIT(PORTB, 6); static bit button_1 @ PORTBIT(PORTB, 5); // Register select static bit led_out @ PORTBIT(PORTB, 7); /*------ MIDI Defines -------------------------------*/ #define NOTE_ON 0x90 #define NOTE_OFF 0x80 #define MidA 57 #define MidB 59 #define MidC 0x3C #define MidD 62 #define MidVel 0x7F static bit midi_out @ PORTBIT(PORTB, 1); /*------ LCD Defines --------------------------------*/ #define LCD_PORT PORTC #define LCD_CTRL 0x08 #define LCD_BLINK 0x01 #define LCD_CURSOR 0x02 #define LCD_DISPLAY_ON 0x04 #define LCD_DISPLAY_OFF 0x08 #define LCD_ENTRY 0x06 #define LCD_MODE 0x20 #define LCD_LINES 0x08 /* 8=2 lines 0=1 line */ #define LCD_DL 0x00 /* 1=8 bit 0=4 bit */ #define LCD_RES 0x00 /* 0=5x8 4=5x10 */ #define LCD_SETUP (LCD_CTRL | LCD_DISPLAY_ON) #define LCD_CONFIG (LCD_MODE | LCD_LINES | LCD_DL | LCD_RES) #define LCD_STROBE LCD_EN = 1;Delay10Us(1);LCD_EN=0; static bit LCD_RS @ PORTBIT(PORTA, 4); // Register select static bit LCD_EN @ PORTBIT(PORTA, 5); // Enable /*------------------------------------------------------ Macros FROM HI-TECH PICC ------------------------------------------------------*/ #if XTAL_FREQ >= 12MHZ #define delay_us(x) { unsigned char _dcnt; \ _dcnt = (x)*((XTAL_FREQ)/(12MHZ)); \ while(--_dcnt != 0) \ continue; } #else #define delay_us(x) { unsigned char _dcnt; \ _dcnt = (x)/((12MHZ)/(XTAL_FREQ))|1; \ while(--_dcnt != 0) \ continue; } #endif /*------------------------------------------------------ Global Variables ------------------------------------------------------*/ long tick_count = 0; int glo_temp = 0; bit lcd_update = 0; bit freq_count_busy = 0; int period = 0; // 16bit timer unsigned char overflow=0; unsigned char asm_var=0; unsigned char xmit=0; unsigned char j=0; unsigned char pitchL=0; unsigned char pitchH=0; unsigned char variance=0; /*----------------------------------------------------- Delay Functions ------------------------------------------------------*/ void Delay10Us(long t) // for 4MHz { do { delay_us(8); }while(--t); } void DelayMs(long t) // delays t millisecs { do { delay_us(100); delay_us(100); delay_us(100); delay_us(100); delay_us(100); delay_us(100); delay_us(100); delay_us(100); delay_us(100); delay_us(100); } while(--t); } /*------------------------------------------------------ LCD Functions ------------------------------------------------------*/ /* write a byte to the LCD in 4 bit mode */ void lcd_write(unsigned char c) { LCD_PORT = (LCD_PORT & 0xF0) | (c >> 4); LCD_STROBE; LCD_PORT = (LCD_PORT & 0xF0) | (c & 0x0F); LCD_STROBE; delay_us(200); } /* * Clear and home the LCD */ void lcd_clear(void) { LCD_RS = 0; lcd_write(0x1); DelayMs(5); } void lcd_home(void) { LCD_RS = 0; lcd_write(0x2); DelayMs(5); } /* write a string of chars to the LCD */ void lcd_puts(const char * s) { LCD_RS = 1; // write characters while(*s) lcd_write(*s++); } /* write one character to the LCD */ void lcd_putch(char c) { LCD_RS = 1; // write characters LCD_PORT = (LCD_PORT & 0xF0) | (c >> 4); LCD_STROBE; LCD_PORT = (LCD_PORT & 0xF0) | (c & 0x0F); LCD_STROBE; delay_us(200); } /* * Go to the specified position */ void lcd_goto(unsigned char pos) { LCD_RS = 0; lcd_write(0x80+pos); } /* initialise the LCD - put into 4 bit mode */ void lcd_init(void) { LCD_RS = 0; // write control bytes DelayMs(15); // power on delay LCD_PORT = 0x3; // attention! LCD_STROBE; DelayMs(5); LCD_STROBE; delay_us(100); LCD_STROBE; DelayMs(5); LCD_PORT = 0x2; // set 4 bit mode LCD_STROBE; delay_us(40); lcd_write(LCD_CONFIG); // 4 bit mode, 1/16 duty, 5x8 font lcd_write(LCD_DISPLAY_OFF); // display off lcd_write(LCD_SETUP); // display on, blink curson on lcd_write(LCD_ENTRY); // entry mode } void lcd_custom(void) { LCD_RS=0; lcd_write(0x40 | 0); // enter CGRAM space lcd_putch(0b00000001); lcd_putch(0b00000111); lcd_putch(0b00001101); lcd_putch(0b00001001); lcd_putch(0b00001011); lcd_putch(0b00011011); lcd_putch(0b00011000); lcd_putch(0b00000000); LCD_RS=0; lcd_write(0x40 | (1*8)); // enter CGRAM space lcd_putch(0b00000111); lcd_putch(0b00001101); lcd_putch(0b00001001); lcd_putch(0b00001011); lcd_putch(0b00011011); lcd_putch(0b00011000); lcd_putch(0b00000000); lcd_putch(0b00000000); LCD_RS=0; lcd_write(0x40 | (2*8)); // enter CGRAM space lcd_putch(0b00000000); lcd_putch(0b00000000); lcd_putch(0b00010000); lcd_putch(0b00001000); lcd_putch(0b00010000); lcd_putch(0b00000000); lcd_putch(0b00000000); lcd_putch(0b00000000); LCD_RS=0; lcd_write(0x40 | (3*8)); // enter CGRAM space lcd_putch(0b00011000); lcd_putch(0b00000100); lcd_putch(0b00010010); lcd_putch(0b00001010); lcd_putch(0b00010010); lcd_putch(0b00000100); lcd_putch(0b00011000); lcd_putch(0b00000000); LCD_RS=0; lcd_write(0x40 | (4*8)); // enter CGRAM space lcd_putch(0b00011010); lcd_putch(0b00000101); lcd_putch(0b00010010); lcd_putch(0b00001010); lcd_putch(0b00010010); lcd_putch(0b00000101); lcd_putch(0b00011010); lcd_putch(0b00000000); LCD_RS=0; lcd_write(0x40 | (5*8)); // enter CGRAM space lcd_putch(0b00000001); lcd_putch(0b00000011); lcd_putch(0b00011111); lcd_putch(0b00011111); lcd_putch(0b00011111); lcd_putch(0b00000011); lcd_putch(0b00000001); lcd_putch(0b00000000); LCD_RS=0; lcd_write(0x40 | (6*8)); // enter CGRAM space lcd_putch(0b00000000); lcd_putch(0b00001110); lcd_putch(0b00000000); lcd_putch(0b00000000); lcd_putch(0b00000000); lcd_putch(0b00000000); lcd_putch(0b00000000); lcd_putch(0b00000000); LCD_RS=0; lcd_write(0x40 | (6*8)); // enter CGRAM space lcd_putch(0b00000100); lcd_putch(0b00001110); lcd_putch(0b00000100); lcd_putch(0b00000000); lcd_putch(0b00000000); lcd_putch(0b00000000); lcd_putch(0b00000000); lcd_putch(0b00000000); LCD_RS=0; lcd_write(0b10000000); // back to DDRAM } void lcd_number(unsigned int t) { if(t < 10) { lcd_putch('0'); lcd_putch(0x30 | (char)t); return; }else if(t < 100) { lcd_putch(0x30 | (int)(t/10)); lcd_putch(0x30 | (t%10)); return; }else if(t < 1000) { lcd_putch(0x30 | (int)(t/100)); lcd_putch(0x30 | (t%100)/10); lcd_putch(0x30 | (t%10)); return; }else if(t < 10000) { lcd_putch(0x30 | (int)(t/1000)); lcd_putch(0x30 | (t%1000)/100); lcd_putch(0x30 | (t%100)/10); lcd_putch(0x30 | (t%10)); return; } else if(t < 65535) { lcd_putch(0x30 | (int)(t/10000)); lcd_putch(0x30 | (t%10000)/1000); lcd_putch(0x30 | (t%1000)/100); lcd_putch(0x30 | (t%100)/10); lcd_putch(0x30 | (t%10)); return; } } //added by alex: //displays current pitch as stored in global variables pitchL and pitchH void display_note() { if (pitchH==0) { if (pitchL <=0x10) lcd_puts("low"); if ((pitchL >0x10 && pitchL <=0x30)) lcd_puts("C3"); if ((pitchL >0x30 && pitchL <=0x50)) lcd_puts("Db3"); if ((pitchL >0x50 && pitchL <=0x70)) lcd_puts("D3"); if (pitchL >0x70) lcd_puts("Eb3"); } if (pitchH==1) { if (pitchL <=0x10) lcd_puts("Eb3"); if ((pitchL >0x10 && pitchL <=0x30)) lcd_puts("E3"); if ((pitchL >0x30 && pitchL <=0x50)) lcd_puts("F3"); if ((pitchL >0x50 && pitchL <=0x70)) lcd_puts("Gb3"); if (pitchL >0x70) lcd_puts("G3"); } if (pitchH==2) { if (pitchL <=0x10) lcd_puts("G3"); if ((pitchL >0x10 && pitchL <=0x30)) lcd_puts("Ab3"); if ((pitchL >0x30 && pitchL <=0x50)) lcd_puts("A3"); if ((pitchL >0x50 && pitchL <=0x70)) lcd_puts("Bb3"); if (pitchL >0x70) lcd_puts("B3"); } if (pitchH==3) { if (pitchL <=0x10) lcd_puts("B3"); if ((pitchL >0x10 && pitchL <=0x30)) lcd_puts("C4"); if ((pitchL >0x30 && pitchL <=0x50)) lcd_puts("Db4"); if ((pitchL >0x50 && pitchL <=0x70)) lcd_puts("D4"); if (pitchL >0x70) lcd_puts("Eb4"); } if (pitchH==4) { if (pitchL <=0x10) lcd_puts("Eb4"); if ((pitchL >0x10 && pitchL <=0x30)) lcd_puts("E4"); if ((pitchL >0x30 && pitchL <=0x50)) lcd_puts("F4"); if ((pitchL >0x50 && pitchL <=0x70)) lcd_puts("Gb4"); if (pitchL >0x70) lcd_puts("G4"); } if (pitchH==5) { if (pitchL <=0x10) lcd_puts("G4"); if ((pitchL >0x10 && pitchL <=0x30)) lcd_puts("Ab4"); if ((pitchL >0x30 && pitchL <=0x50)) lcd_puts("A4"); if ((pitchL >0x50 && pitchL <=0x70)) lcd_puts("Bb4"); if (pitchL >0x70) lcd_puts("B4"); } if (pitchH==6) { if (pitchL <=0x10) lcd_puts("B4"); if ((pitchL >0x10 && pitchL <=0x30)) lcd_puts("C5"); if ((pitchL >0x30 && pitchL <=0x50)) lcd_puts("Db5"); if ((pitchL >0x50 && pitchL <=0x70)) lcd_puts("D5"); if (pitchL >0x70) lcd_puts("Eb5"); } if (pitchH==7) { if (pitchL <=0x10) lcd_puts("Eb5"); if ((pitchL >0x10 && pitchL <=0x30)) lcd_puts("E5"); if ((pitchL >0x30 && pitchL <=0x50)) lcd_puts("F5"); if ((pitchL >0x50 && pitchL <=0x70)) lcd_puts("Gb5"); if (pitchL >0x70) lcd_puts("G5"); } if (pitchH==8) { if (pitchL <=0x10) lcd_puts("G5"); if ((pitchL >0x10 && pitchL <=0x30)) lcd_puts("Ab5"); if ((pitchL >0x30 && pitchL <=0x50)) lcd_puts("A5"); if ((pitchL >0x50 && pitchL <=0x70)) lcd_puts("Bb5"); if (pitchL >0x70) lcd_puts("B5"); } if (pitchH ==9) { if (pitchL <=0x10) lcd_puts("B5"); else lcd_puts("hig"); } if(variance) lcd_putch(variance); else lcd_putch(' '); } /*------------------------------------------------------ MIDI functions ------------------------------------------------------*/ void sendMIDI(unsigned char bytecode) /* HARD CODED TO PORTA PIN1 */ { xmit = bytecode; #asm startb: bcf porta, 0x01 ; start bit movlw 24 ; delay 73 clocks: 2 + (23 * 3 + 1 * 2) movwf _asm_var ; | loop1: decfsz _asm_var,f ; | goto loop1 ; end delay movlw 8 movwf _j sendloop: ; executes 5 instuctions before setting bit rrf _xmit,f btfsc status, 0 goto send1 ; remember midi bits are opposite from our representation send0: nop bcf porta, 0x01 ;send a 0 bit goto endloop send1: bsf porta, 0x01 ;send a 1 bit nop nop endloop: ; movlw 23 ;delay 70 instructions 2 + (22 * 3 + 1 * 2) movwf _asm_var ; | loop2: decfsz _asm_var,f ; | goto loop2 ; end delay decfsz _j,f ; goto sendloop stopb: nop nop nop nop nop bsf porta, 0x01 ; stop bit movlw 26 ; delay 79 clocks: 2 + (25 * 3 + 1 * 2) movwf _asm_var ; | loop3: decfsz _asm_var,f ; | goto loop3 ; end delay #endasm return; } void beginMIDI() { //this will be converted to a "MIDI START BUTTON" //send instrument change message sendMIDI(192); //C0 //send instrument type sendMIDI(48); //30 //send a second program chanage for synchronizing the midi buffer(delay b/w messages) delay_us(2); sendMIDI(192); sendMIDI(48); //send NOTEON status byte sendMIDI(144); //90 //send noteon base pitch sendMIDI(50); //32 //send noteon velocity sendMIDI(MidVel); //7F return; } //these 2 functions need to be sent every 5 milliseconds: void PitchWheel(unsigned char pitchhigh,unsigned char pitchlow) { //send status byte sendMIDI(224); //E0 //send pitchhigh byte value sendMIDI(pitchhigh); //send pitchlow byte value sendMIDI(pitchlow); return; } void Volume(unsigned char volumebyte) { //send status byte sendMIDI(176); //B0 //send control type (volume) sendMIDI(07); //send volume level sendMIDI(volumebyte); return; } void ChangeMIDI_up() { //uses pitchL and pitchH as global variables to increase pitch if (pitchL == 0x7F) //then overflow { pitchL = 0; pitchH++; } else pitchL++; PitchWheel(pitchH,pitchL); } void ChangeMIDI_down() { //uses pitchL and pitchH as GLOBAL variables to decrease pitch if (pitchL == 0) //overflow { pitchL = 0x7F; pitchH--; } else pitchL--; PitchWheel(pitchH,pitchL); } void PeriodtoPitch(int per) { int temp=0; variance=0; if (per > 7645) // freq too low { pitchL =20; pitchH =00; return; } if (per < 1012) // freq too high { pitchL =00; pitchH =9; return; } temp = (log(per)*553.995)-3833.47; // magic code (adjust 3833.47 for higher range) temp = 1152-temp; pitchH = (temp>>7)&0x7F; pitchL = (temp)&0x7F; if(temp%32) { if(pitchL&0b10000) { variance = 6;// custom char #6 (low) }else { variance = 7;// custom char #7 (high) } } } /*------------------------------------------------------ Interrupt ------------------------------------------------------*/ void interrupt tc_int(void) { int temp = period; /* ------- PB0 Interrupt -----------*/ if(INTF==1) { if(freq_count_busy==0) // beginning of period { freq_count_busy = 1; TMR1L = 0; // set the timer to 0 TMR1H = 0; }else // end of period { freq_count_busy = 0; period = 0; // 16bit value period += TMR1H<<8; period += TMR1L; // save the timer (holds the ticks elapsed since beginning of period) } if((period & 0xF000) ^ (temp & 0xF000)) // if it changes more than 16us then update { lcd_update = 1; } INTF=0; // done servicing } /* ------- TMR0 Interrupt -----------*/ if(T0IF==1) { //if(!overflow++) //{ if(led_out==1) led_out=0; else led_out=1; DEBUG = led_out; //} T0IF = 0; TMR0 += -PRELOAD; // re-load timer } } /*------------------------------------------------------ Init Functions ------------------------------------------------------*/ void init_ports() { TRISA = PORTA_IO; TRISB = PORTB_IO; TRISC = PORTC_IO; /* ADCON0 (BANK1)*/ // Analog config register ADCON0 = 0b10000001; // Configure analog/digital /* ADCON1 (BANK1)*/ // Analog config register ADCON1 = 0b10000110; // All Digital Configure analog/digital PCFG3 = 1; // see table in Data sheet PCFG2 = 1; // if you change these !! PCFG1 = 1; PCFG0 = 1; // ----------initialize Interupts -------------- /* OPTION_REG */ RBPU = 0; // 0 = PORTB pullups are enabled INTEDG = 1; // 1 = Riseing edge trigger (INTRB0) T0CS = 0; // select internal clock (XTAL_FREQ/4) PSA=0 ; // prescale by 64 PS2=1 ; PS1=0 ; PS0=1 ; /* INTCON */ GIE = 1; // 1 = Enable all unmasked Interupts PEIE = 0; // 1 = Enable all unmasked cool interupts T0IE = 0; // 1 = enable timer interrupt INTE = 1; // 1 = enable RB0 interups RBIE = 0; // 1 = enable RB change interupts /* PIE Register */ PIE1 = 0b00000000; // disable all extra interupts ADIE = 0; // A/D interrupts will not be used /* PIE2 Register */ PIE2 = 0b00000000; // -------- Modules ----------------------- /* T1CON (timer 1)*/ T1CKPS1 = 0; // prescale value T1CKPS0 = 0; T1OSCEN = 0; // 1 = enable T1 external Osc TMR1CS = 0; // 0 = internal instruction cycle (XTAL/4)= Timer Mode TMR1ON = 1; // 1 = enable TMR1 TMR0 = -PRELOAD; // preload timer } /* LCD Update Function */ void LCD_Update(int myperiod) { int temp; di(); lcd_home(); //lcd_puts("F="); //lcd_number(floor(freq)); //lcd_putch('.'); //freq=freq-floor(freq); // keep the # small //freq=freq*100; // extract two decimal points //freq=floor(freq); // mask off . //lcd_number(floor(freq)); //lcd_puts("Hz"); lcd_number(myperiod); lcd_puts("us "); display_note(); overflow=~overflow; lcd_putch((1 & overflow)); // dancing note lcd_puts(" "); temp = curr_volume(); lcd_number(temp); // output volume lcd_putch(5); lcd_putch(2+(temp/342)); // output user char 2..4 for volume bar lcd_puts(" "); ei(); lcd_update = 0; return; } /*---------------------------------------------------------*/ int curr_volume() // returns a value between 0..1023 representing the current volume { int temp=0; // 16bit value di(); ADGO = 1; // start conversion while(ADGO==1){}; // wait for the conversion to finish ei(); temp += ADRESH<<8; temp += ADRESL; return temp; } int curr_period() { float temp=0; temp = period * .4; // period/(XTAL/4) period in msec return (int)temp; } void midi_test() { unsigned char myvolume= 127; //holds value of current volume int i=0; //(increment for loop) //MIDI TEST LOOP... sends incrementally different pitches with varying volumes: for (i = 0;i<400;i++) { //INSERT PITCH WHEELS (weeee) ChangeMIDI_up(); //pitchL=pitchL + 1; //change by .5 cent up //INSERT VOLUME MESSAGES if (myvolume <=40) myvolume=myvolume; else myvolume=myvolume-2; Volume(myvolume); //delay for __ milliseconds between pitches lcd_clear(); lcd_number((int)pitchL); DelayMs(50); } beginMIDI(); for (i = 0;i<400;i++) { //INSERT PITCH WHEELS (woooo) ChangeMIDI_down(); //pitchL=pitchL - 1; //change by .5 cent down //INSERT VOLUME MESSAGES if (myvolume >=125) myvolume=127; else myvolume=myvolume+2; Volume(myvolume); //delay for __ milliseconds between pitches //temp=pitchL; lcd_clear(); lcd_number((int)pitchL); DelayMs(50); } beginMIDI(); } /*------------------------------------------------------ Entry Code ------------------------------------------------------*/ void main() { int period_value=0; init_ports(); lcd_init(); lcd_custom(); lcd_clear(); lcd_puts("LCD initialized"); DelayMs(500); beginMIDI(); ei(); // enable all unmasked interrupts for(;;) { period_value=curr_period(); PeriodtoPitch(period_value); //sets periodH and periodL LCD_Update(period_value); di(); PitchWheel(pitchH,pitchL); ei(); DelayMs(50); } }