' ****************************************************************************** ' * ThunderWood - Squeaks * ' * Fundamental MIDI Robot Control Firmware * ' * for Proton+ compiler on 18F2525 microchip controller * ' * based on the template for all boards using 28 pin 18F PIC's * ' * source file: MMRobots18F * ' * Godfried-Willem Raes * ' ****************************************************************************** ' board description: MidiHub board, rev. 3, nov.2006 ' 28.09.2010: first version by gwr. ' The two squeek mechanisms use both PWM outputs. ' Note aftertouch for these two notes (21 and 23) implemented here! ' First debug session on Amicus platform with LED's o.k. ' 29.09.2010: In the hardware we will use P-channel logic level Mosfets, thus ' the logic levels on the ports will have to be inverted. ' 30.09.2010: Processor board made on prototype PC board. ' First flash and test of firmware. ' range for squeakers adapted ' 16.09.2010: note-off's seem not very reliable. ' This code does not use the timers At all! ' 25.05.2011: revision. Adding code for periodic light flashing ' note-off checked and improved. ' Now the timer based multitasker is in full use with 6 tasks. ' not working in 18F2520... changing the include for the chip made it work again. ' 28.05.2011: Further improvements. 'Include "18F25K20.inc" 'for test & debug on an Amicus board. 'Include "18F2525.inc" 'version for the thunderwood squaker board Include "18F2520.inc" 'also possible 'constant definitions: 'initialisations for the midi input parser: Symbol Midichannel = 5 ' Thunderwood_Channel Symbol NoteOff_Status = 128 + Midichannel ' 2 bytes follow Symbol NoteOn_Status = 144 + Midichannel Symbol Keypres_Status = 160 + Midichannel Symbol Control_Status = 176 + Midichannel Symbol ProgChange_Status = 192 + Midichannel ' 1 byte message Symbol Aftertouch_Status = 208 + Midichannel ' 1 byte follows Symbol Pitchbend_Status = 224 + Midichannel ' lsb msb follow Symbol NrTasks = 6 ' Setup the USART Declare Hserial_Baud = 31250 ' Set baud rate for the USART to MIDI specs. Declare Hserial_TXSTA = 0x24 ' instead of the normal 0x20 - ?? 0x24 ' Declare Hserial_Clear = On ' should clear on errors. Bytes get lost of course... This must be 31250 for MIDI ' Create variables Dim Cntl As TMR0L.Word 'this is the trick to read both TMR0L and TMR0H ? YES 'it makes Cntl the low word of cnt Dim Bytein As Byte System ' midi byte read from buffer Dim StBit As Bytein.7 ' highest bit of ByteIn Dim k As Byte System ' general purpose variable ' midi variables Dim statusbyte As Byte System Dim noteUit As Byte System ' note off + release value Dim release As Byte System Dim noteAan As Byte System ' note on + release value Dim velo As Byte System Dim notePres As Byte System ' note pressure + pressure value Dim pres As Byte System Dim Ctrl As Byte System ' continuous controller + value Dim value As Byte System Dim prog As Byte System ' program change + program-byte Dim aft As Byte System ' channel aftertouch Dim pblsb As Byte System ' pitch bend lsb Dim pbmsb As Byte System ' pitch bend msb Dim veltim As Dword System ' 32 bit velo Dim VelFlags As Word System ' bits 0 - 15 used as flags for active velo-timers Dim VelFlags0 As VelFlags.Byte0 ' alias for bits 0-7 ' Dim VelFlags1 As VelFlags.Byte1 ' bits 8-15 Dim CC66 As Byte System ' global on/off switch Dim st As Byte System Dim b1 As Byte System Dim b2 As Byte System Dim Cnt As Dword System Dim CntHw As Cnt.Word1 ' 25.05.2011 Dim SqFlags As Byte System Dim Sq1Flag As SqFlags.0 Dim Sq2Flag As SqFlags.1 ' Mapping defines for midi-events on pin outputs: ' There are only 16 pins free: RB0-5, RC0, RC3-5, RA0-RA5 $define Note0 PORTB.0 ' RB0, pin 21 120 groen $define Note1 PORTB.1 ' RB1, pin 22 121 rood $define Note2 PORTB.2 ' RB2, pin 23 122 geel $define Note3 PORTB.3 ' RB3, pin 24 123 nog niet aangesloten $define Note4 PORTB.4 ' RB4, pin 25 124 nog niet aangesloten $define Note5 PORTC.0 ' pin 11 125 blauwe led ' $define Note6 PORTC.1 ' PWM channel -RC1 HPWM1 Note 21 ' $define Note7 PORTC.2 ' PWM channel -RC2 HPWM0 Note 23 'red LED for debug: $define DebugLed PORTB.5 ' our classic debug LED '----------------------------------------------------------------------------------------- ' Load the USART Interrupt handler And buffer read subroutines into memory Include "TW_Squeeks_Irq.inc" ' our own version for UART And Timer0 Interrupt 'Include "Timers.inc" ' no longer required. 25.05.2011 - macro's replaced with inline code 'declare and dimension all scalars: Dim Task_rsi[NrTasks] As Word ' system Dim VelLsb[NrTasks] As Word 'system Dim Velmsb[NrTasks] As Word 'system '----------------------------------------------------------------------------------------- ' Main program starts here ' MAIN: ' configure the output pins: TRISA = %01000000 'bits set to 0 are output, 1 = input TRISB = %11100000 TRISC = %11000000 'RC1 en RC2 zijn pwm outputs and must be set to output 'RC6 en RC7 zijn USART I/O and must be set to input 'make sure we initialize those pins to 0 on start up: (test LED's will be ON if pulled up to +Vcc) High Note0 High Note1 High Note2 High Note3 High Note4 Low Note5 ' blue HPWM 1, 255, 3906 HPWM 0, 255, 3906 Low DebugLed DelayMS 10 ' wait for stability Init_Usart_Interrupt ' Initiate the USART serial buffer interrupt ' this procedure is in the include file Clear_Serial_Buffer ' Clear the serial buffer and reset its pointers ' in the include as well High DebugLed ' Configure Timer0 for: ' Clear TMR0L and TMR0H registers ' Interrupt on Timer0 overflow ' 16-bit operation ' Internal clock source 64MHz ' 1:256 Prescaler : thus 16MHz / 256 = 62.500kHz on 18F25K20 ' 10MHz / 256 = 39062.5Hz on 18F2525 ' Opentimer0 (Timer_INT_On & T0_16BIT & T0_SOURCE_INT & T0_PS_1_256) Clear T0CON Clear IntConBits_T0IF ' clear interrupt flag Set INTCONBITS_T0IE ' enable interrupt on overflow T0CON = %10000111 ' Setup the High priorities for the interrupts ' required initialisation Clear VelFlags0 ' no timer active on start up Clear SqFlags ' no flashing lights ' start the main program loop: LOOP: ' Create an infinite loop Bytein = HRSIn ' Read data from the serial buffer, with timeout ' timeout values set to bare minimum. ' Here we can start the midi parser. Midi_Parse: If Bytein > ProgChange_Status Then ' was Pitchbend_Status Then , but here this is not implemented. If Bytein > 253 Then '254 = midiclock, 255= reset 'midiclock can interrupt all other msg's... '255 had to be intercepted since thats what we 'get when no new byte flows in (?) GoTo Midi_Parse_End 'throw away... Else Clear statusbyte 'reset the status byte GoTo Midi_Parse_End 'throw away End If EndIf If StBit =1 Then 'should be faster than If Bytein > 127 Then 'status byte received, bit 7 is set Clear statusbyte 'if on another channel, the statusbyte needs a reset Select Bytein 'eqv to Select case ByteIn Case NoteOff_Status statusbyte = Bytein noteUit = 255 'reset value. Cannot be 0 !!! release = 255 '0 is a valid midi note! Case NoteOn_Status statusbyte = Bytein noteAan = 255 velo = 255 Case Keypres_Status statusbyte = Bytein notePres = 255 pres = 255 Case Control_Status statusbyte = Bytein Ctrl = 255 value = 255 Case ProgChange_Status statusbyte = Bytein prog = 255 ' Case Aftertouch_Status ' statusbyte = Bytein ' aft = 255 ' Case Pitchbend_Status ' statusbyte = Bytein ' pblsb = 255 ' pbmsb = 255 End Select Else 'midi byte is 7 bits Select statusbyte Case 0 'not a message for this channel GoTo Midi_Parse_End 'disregard Case NoteOff_Status If noteUit = 255 Then noteUit = Bytein Else release = Bytein 'message complete, so we can do the action... Select noteUit Case 21 HPWM 1, 255, 3906 Clear Sq1Flag Case 23 HPWM 0, 255, 3906 Clear Sq2Flag Case 120 Set Note0 Clear VelFlags0.0 Case 121 Set Note1 Clear VelFlags0.1 Case 122 Set Note2 Clear VelFlags0.2 Case 123 Set Note3 Clear VelFlags0.3 Case 124 Set Note4 Clear VelFlags0.4 Case 125 Clear Note5 ' not inverted, Blue LED's Clear VelFlags0.5 End Select Set noteUit 'reset to 255 EndIf GoTo Midi_Parse_End Case NoteOn_Status If noteAan = 255 Then noteAan = Bytein Else velo = Bytein Select noteAan Case 21 ' squeak 1 metal resonator Select Case velo Case 0 HPWM 1, 255, 3906 ' note off Clear Sq1Flag Case 127 HPWM 1, 0, 3906 ' geval 127, maximum force Set Sq1Flag Case Else HPWM 1, 128 - velo, 3906 'should give better range Set Sq1Flag End Select Case 23 'squeak 2 - note 23 - wood resonator Select Case velo Case 0 HPWM 0, 255, 3906 ' note off Clear Sq2Flag Case 127 HPWM 0, 0, 3906 ' max. force ON Set Sq2Flag Case Else k = velo >> 1 'divide by 2- multiply * 1.5 k = velo + k '0 - 189 range HPWM 0, 189 - k, 3906 'should give better range Set Sq2Flag End Select Case 120 If velo > 0 Then Clear Note0 ' switch lite ON If velo = 127 Then Clear VelFlags0.0 Else Set VelFlags0.0 ' set periodic flashing Task_rsi[0] = (~velo & 127) << 9 Cnt.Word0 = Cntl veltim = Cnt + Task_rsi[0] Velmsb[0] = veltim.Word1 VelLsb[0] = veltim.Word0 EndIf Else Set Note0 Clear VelFlags0.0 EndIf Case 121 If velo > 0 Then Clear Note1 If velo = 127 Then Clear VelFlags0.1 Else Set VelFlags0.1 Task_rsi[1] = (~velo & 127) << 9 Cnt.Word0 = Cntl veltim = Cnt + Task_rsi[1] Velmsb[1] = veltim.Word1 VelLsb[1] = veltim.Word0 EndIf Else Set Note1 'Gosub NO1 Clear VelFlags0.1 EndIf Case 122 If velo > 0 Then Clear Note2 'GoSub NA2 If velo = 127 Then Clear VelFlags0.2 Else Set VelFlags0.2 Task_rsi[2] = (~velo & 127) << 9 Cnt.Word0 = Cntl veltim = Cnt + Task_rsi[2] Velmsb[2] = veltim.Word1 VelLsb[2] = veltim.Word0 EndIf Else Set Note2 ' Gosub NO2 Clear VelFlags0.2 EndIf Case 123 If velo > 0 Then Clear Note3 'GoSub NA3 If velo = 127 Then Clear VelFlags0.3 Else Set VelFlags0.3 Task_rsi[3] = (~velo & 127) << 9 Cnt.Word0 = Cntl veltim = Cnt + Task_rsi[3] Velmsb[3] = veltim.Word1 VelLsb[3] = veltim.Word0 EndIf Else Set Note3 ' Gosub NO3 Clear VelFlags0.3 EndIf Case 124 If velo > 0 Then Clear Note4 'GoSub NA4 If velo = 127 Then Clear VelFlags0.4 Else Set VelFlags0.4 Task_rsi[4] = (~velo & 127) << 9 Cnt.Word0 = Cntl veltim = Cnt + Task_rsi[4] Velmsb[4] = veltim.Word1 VelLsb[4] = veltim.Word0 EndIf Else Set Note4 ' Gosub NO4 Clear VelFlags.4 EndIf Case 125 If velo > 0 Then Set Note5 ' not inverted - blue LED's If velo = 127 Then Clear VelFlags0.5 Else Set VelFlags0.5 Task_rsi[5] = (~velo & 127) << 9 Cnt.Word0 = Cntl veltim = Cnt + Task_rsi[5] Velmsb[5] = veltim.Word1 VelLsb[5] = veltim.Word0 EndIf Else Clear Note5 ' led's uit Clear VelFlags0.5 EndIf End Select Set noteAan 'reset EndIf GoTo Midi_Parse_End Case Keypres_Status If notePres = 255 Then notePres = Bytein Else pres = Bytein GoSub KeyPres EndIf GoTo Midi_Parse_End Case Control_Status If Ctrl = 255 Then Ctrl = Bytein Else value = Bytein GoSub Controller EndIf GoTo Midi_Parse_End Case ProgChange_Status If prog = 255 Then 'single byte message prog = Bytein 'weak coding... GoSub ProgChange EndIf End Select EndIf Midi_Parse_End: 'jump out of parser label ' here we check the velo counters and compare them with the cnt value ' new construction using the Velflags dword variable: If VelFlags0 > 0 Then 'if any bit is set here, there is a velo timer running If VelFlags0.0 = 1 Then veltim.Word1 = Velmsb[0] ' reconstruct the 32-bit timer value veltim.Word0 = VelLsb[0] Cnt.Word0 = Cntl 'read counter If Cnt >= veltim Then btg Note0 ' toggle output veltim = Cnt + Task_rsi[0] ' re-load timer Velmsb[0] = veltim.Word1 VelLsb[0] = veltim.Word0 EndIf End If If VelFlags0.1 = 1 Then veltim.Word1 = Velmsb[1] veltim.Word0 = VelLsb[1] Cnt.Word0 = Cntl 'read counter If Cnt >= veltim Then btg Note1 ' toggle veltim = Cnt + Task_rsi[1] Velmsb[1] = veltim.Word1 VelLsb[1] = veltim.Word0 End If End If If VelFlags0.2 = 1 Then veltim.Word1 = Velmsb[2] veltim.Word0 = VelLsb[2] Cnt.Word0 = Cntl 'read counter If Cnt >= veltim Then btg Note2 ' toggle veltim = Cnt + Task_rsi[2] Velmsb[2] = veltim.Word1 VelLsb[2] = veltim.Word0 End If End If If VelFlags0.3 = 1 Then veltim.Word1 = Velmsb[3] veltim.Word0 = VelLsb[3] Cnt.Word0 = Cntl 'read counter If Cnt >= veltim Then btg Note3 ' toggle veltim = Cnt + Task_rsi[3] Velmsb[3] = veltim.Word1 VelLsb[3] = veltim.Word0 End If End If If VelFlags0.4 = 1 Then veltim.Word1 = Velmsb[4] veltim.Word0 = VelLsb[4] Cnt.Word0 = Cntl 'read counter If Cnt >= veltim Then btg Note4 ' toggle veltim = Cnt + Task_rsi[4] Velmsb[4] = veltim.Word1 VelLsb[4] = veltim.Word0 End If End If If VelFlags0.5 = 1 Then veltim.Word1 = Velmsb[5] veltim.Word0 = VelLsb[5] Cnt.Word0 = Cntl 'read counter If Cnt >= veltim Then btg Note5 ' toggle veltim = Cnt + Task_rsi[5] Velmsb[5] = veltim.Word1 VelLsb[5] = veltim.Word0 End If End If EndIf GoTo LOOP 'end of the main loop '------------------------------------------------------------ 'code for what has to happen on reception of midi commands: '------------------------------------------------------------ KeyPres: 'the note to which the pressure should be applied is passed in NotePres, the value in Pres 'modulate the loudness of playing notes. Select notePres Case 21 'squeak 1, note 21 (Brass resonator) If Sq1Flag = 1 Then 'HPWM 1, 255- pres - pres, 3906 HPWM 1, 128 - pres, 3906 EndIf Case 23 'squeak 2, note 23 (Wood resonator) If Sq2Flag = 1 Then k = pres >> 1 k = k + pres HPWM 0, 189 - k, 3906 EndIf End Select notePres = 255 Return ProgChange: prog = 255 'this is not realy required Return Pitchbend: 'only implemented on dsPIC based robots pblsb = 255 Return Aftertouch: ' 'this is the channel aftertouch, affecting all notes aft = 255 'not mandatory Return Controller: Select Ctrl Case 66 'on/off for robot If value = 0 Then GoSub AllNotesOff EndIf CC66 = value GoTo Ctrl_Parse_End Case 123 'all notes off GoSub AllNotesOff End Select Ctrl_Parse_End: Ctrl = 255 'mandatory reset Return AllNotesOff: High Note0 '120 green High Note1 '121 red High Note2 '122 yellow High Note3 High Note4 Low Note5 '125 blue HPWM 1, 255, 3906 HPWM 0, 255, 3906 Clear SqFlags Clear VelFlags0 Return '[EOF]