'**************************************************************** '* Name : Snar2_Hub.BAS * '* Author : Godfried-Willem RAES * '* Notice : Copyleft (c) 2013 Logosoft Public Domain * '* Date : 22.04.2014 * '* Version : 1.2 * '* Notes : Based On Horny/Klar/Asa-hub code model * '**************************************************************** '08.06.2013: PIC: 18F2525 On MidiHub board, solenoids, lites '17.04.2014: Firmware adapted to Snar2 '19.04.2014: Code version 1.0. This can be tested. ' safety timers for snare solenoids implemented. '20.04.2014: First tests. GMT test code written in the Troms module. ' Changes after tests: ' PWMfreq introduced to prevent audible artifacts. Now 14.6kHz '22.04.2014: CC15 introduced to set minvel for the rimshots ' CC16 for mimimum force on the snare solenoids ' Version 1.2 programmed into chip. Include "18F2525.inc" 'version for the Snar2 board. (40MHz) 'Include "18F2520.inc" 'also possible. (40MHz) 'Include "18F25K20.inc" 'for test & debug on an Amicus board. (64MHz) ' Mapping defines for midi-events on pin outputs and inputs: ' bidirectional solenoids for the movement: (powered frum unstabilised 24V) $define Rimshot1 PORTC.5 ' X11-2 $define Rimshot2 PORTC.4 ' X11-3 $define Snares1 PORTC.1 ' X17-2 - pwm $define Snares2 PORTC.2 ' X17-3 - pwm ' lights: (powered from stabilized 24V) $define Lite1 PORTB.0 ' X12-2 - white LED strip 1 inside drum - note 120 $define Lite2 PORTB.1 ' X12-3 - white LED strip 2 inside drum = note 121 $define Lite3 PORTC.3 ' X15-2 - not mounted yet $define Lite4 PORTC.0 ' X15-3 - not mounted yet $define BlueLed PORTA.3 'snare 2 solenoid above allowable 100% duty cycle level $define GreenLed PORTA.4 'for code monitoring and debug : green led on when snare solenoids are safe and on $define YellowLed PORTA.5 'snare 1 solenoid above allowable 100% duty cycle level ' $define Sensor PORTA.0 'input port - optional temperature sensor ' $define SensorLR PORTA.1 'input port - optional temperature sensor 'red LED for debug: $define Debug_Led PORTB.5 ' for testing - red led - watchdog ' configure the input and output pins: Clear SSPCON1.5 'RC3 must be available for I/O TRISA = %01000111 '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 'constant definitions: 'initialisations for the midi input parser: Symbol Midichannel = 9 ' Snar2_Channel Symbol NoteOff_Status = 128 + Midichannel ' 2 bytes follow Symbol NoteOn_Status = 144 + Midichannel Symbol Keypres_Status = 160 + Midichannel ' 2 bytes follow 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 'application specific constants Symbol Minvel_Rimshot = 132 ' mimimum velocity for the rimshots ' 20 was the value used for high blocks on temblo ' 84 was a good value for rodo ' 132 was calculated after measurement 20.04.2014 Symbol SafeLimit = 174 ' max. pwm value for 100% duty cycle on the snare solenoids (24V) ' value confirmed through measurement 20.04.2014 ' = midi value 87 Symbol PWMfreq = PWMminF * 6 ' x 4 = 9.768kHz, x 6 = 14.652kHz Symbol pwmin_snares = 48 ' midi 24, or 48 was found to be the minimum value to cause action ' on the snare solenoids ' we could also use a controller to set this value (CC16) ' must be <64 !!! ' 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 All_Digital = True ' not the case here!!! ' Declare Hserial_Clear = On ' should clear on errors. Bytes get lost of course... ' Create variables Dim Cnt As Dword System Dim CntHw As Cnt.Word1 'used in the timer0 interrupt, to create a 32 bit timer Dim CntLw As TMR0L.Word 'this is the trick to read both TMR0L and TMR0H 'it makes Cntlw the low word of cnt 'We still have to copy the contents of Lw to Cnt Dim Tim3 As TMR3L.Word ' 16 bit counter for sampler - not used here. ' Dim Sr as TMR0L.7 '512 S/s ' As TMR0H.1 would be 128 S/s ' As TMR0H.2 would be 64 S/s ' As TMR0H.3 would be 32 S/s ' As TMR0H.4 would be 16 S/s Dim Bytein As Byte System ' midi byte read from buffer Dim StBit As Bytein.7 ' highest bit of ByteIn Dim i As Byte System ' general purpose counter ' 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 Dim veltim0 As Dword System ' 32 bit velo for rimshot 1 Dim veltim1 As Dword System ' 32 bit velo for rimshot 2 Dim snaretim1 As Dword System ' 32 bit timer for snare 1 Dim snaretim2 As Dword System ' 32 bit timer for snare 2 Dim snaretim3 As Dword System ' 32 bit timer for both snares together Dim Lite1tim As Dword System Dim Lite2tim As Dword System Dim Lite1period As Dword System Dim Lite2period As Dword System Dim VelFlags As Word System ' bits 0 - 15 used as flags for active timers Dim VelFlags0 As VelFlags.Byte0 ' alias for bits 0-7 Dim Velflags1 As VelFlags.Byte1 ' bits 8-15 Dim CC9 As Byte System ' Snares1 Dim CC10 As Byte System ' Snares2 Dim CC11 As Byte System ' snares 1 and 2 Dim CC66 As Byte System ' global on/off switch Dim PowerOn As CC66.0 Dim st As Byte System Dim b1 As Byte System Dim b2 As Byte System Dim Lites As Byte System ' bits used as flags Dim pw1 As Byte System ' for Snares1-Snares2 Dim pw2 As Byte System Dim v As Byte System Dim vel As Word System Dim Minvel As Byte System ' for the rimshots - controlled by CC15 Dim Pwmin As Byte System ' for the snares - controlled by CC16 '----------------------------------------------------------------------------------------- ' Load the USART Interrupt handler And buffer read subroutines into memory 'Include "ADC.inc" ' Load the ADC macros into the program - used in the IRQ include. Include "Snar2_Hub_Irq.inc" ' our own version for UART And Timer0/3 Interrupt 'Include "Display_Irq.inc" 'Include "Timers.inc" ' required for velo support with timed pulses and periods. 'Include "DwordArrays.inc" ' support for dword arrays. 'make sure we initialize those pins on start up: 'fault?: there should be no executable statements outside the main program. Low Snares1 Low Snares2 Low Rimshot1 Low Rimshot2 Low Lite1 Low Lite2 Low Lite3 Low Lite4 Low Debug_Led HPWM 2, 0, PWMfreq ' movement bidirectional solenoid HPWM 1, 0, PWMfreq ' connected to RC2 Low GreenLED ' snare solenoid voltages are on but safe Low YellowLed ' snare1 solenoid voltage is above 100% duty cycle value Low BlueLED Clear CC66 Clear CC9 Clear CC10 Clear CC11 '----------------------------------------------------------------------------------------- ' Main program starts here MAIN: High Debug_Led DelayMS 50 ' wait for stability Low Debug_Led Clear VelFlags Clear Lites Minvel = Minvel_Rimshot ' cold boot default CC15 Pwmin = pwmin_snares ' cold boot default CC16 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 ' Configure Timer0 for: ' Clear TMR0L and TMR0H registers ' Interrupt on Timer0 overflow ' 16-bit operation ' Internal clock source 40MHz ' 1:256 Prescaler : thus 40MHz / 256 = 156.250kHz ' Opentimer0 (Timer_INT_On & T0_16BIT & T0_SOURCE_INT & T0_PS_1_256) in macro file. Clear T1CON Clear IntConBits_T0IF ' clear interrupt flag Set INTCONBITS_T0IE ' enable interrupt on overflow T0CON = %10000111 ' bit 7 = enable/disable ' bit 6 = 1=8 bot, 0=16 bit ' bit 5 = 1 pin input, 0= Internal Clk0 ' bit 4 = HL or LH transition when bit5 =1 ' bit 3 = 1= bypass prescaler, 0= input from prescaler ' bit 2-0 = prescaler select: 111= 1:256 ' Setup the High priorities for the interrupts ' open and start timer3 for sampling: Clear T3CON Clear PIR2BITS_TMR3IF ' clear IRQ flag Set PIE2BITS_TMR3IE ' irq on Clear Tim3 ' Clear TMR3L And TMR3H registers Set RCONbits_IPEN ' Enable priority interrupts Clear IPR2bits_TMR3IP ' Set Timer3 as a low priority interrupt source ' we can also set T3Con in one instruction as: T3CON = %10110001 ' oef, now it works... ' bit 7 = 16 bit mode ' bit 6,3 = 0, 0 ' bit 5,4 = 1:8 prescale ' bit 2 = 0 ' bit 1 = 0 Internal clock = Fosc/4 ' bit 0 : 1= enable timer 3, 0= disable ' maximum count = 52.42ms, 1 tick =0.8uS, lowest freq.=19Hz ' start the main program loop: LOOP: ' Create an infinite loop Bytein = HRSIn ' Read data from the serial buffer, with no timeout ' Start the midi parser. Midi_Parse: If Bytein > Control_Status Then ' here higher statusses are 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 Check_Timers 'throw away... Else Clear statusbyte 'reset the status byte GoTo Check_Timers '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 Set noteUit '= 255 'reset value. Cannot be 0 !!! Set release '= 255 '0 is a valid midi note! Case NoteOn_Status statusbyte = Bytein Set noteAan '= 255 Set velo '= 255 Case Keypres_Status ' used for lights statusbyte = Bytein Set notePres '= 255 Set pres '= 255 Case Control_Status ' movement control, choice of valve lookup table statusbyte = Bytein Set Ctrl '= 255 Set 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 Check_Timers 'disregard Case NoteOff_Status If noteUit = 255 Then noteUit = Bytein Else release = Bytein 'message complete, so we can do the action... Select noteUit Case 73 Clear Rimshot1 Clear Velflags1.0 Case 74 Clear Rimshot2 Clear Velflags1.1 Case 120 Clear Lites.0 Clear VelFlags0.0 Clear Lite1 Case 121 Clear Lites.1 Clear VelFlags0.1 Clear Lite2 End Select Set noteUit '= 255 'reset EndIf GoTo Check_Timers Case NoteOn_Status If noteAan = 255 Then noteAan = Bytein Else velo = Bytein If velo = 0 Then Select noteAan Case 73 Clear Rimshot1 Clear Velflags1.0 Case 74 Clear Rimshot2 Clear Velflags1.1 Case 120 Clear Lites.0 '= 0 Clear VelFlags0.0 Clear Lite1 Case 121 Clear Lites.1 '= 0 Clear VelFlags0.1 Clear Lite2 End Select Set noteAan '= 255 'reset !!! GoTo Check_Timers 'jump out EndIf Select noteAan Case 73 vel = velo << 2 '1 ' vel is word, 16 bits, required of we do <<2 ' to protect the coils we could add following condition: If Velflags1.0 = 0 Then Set Velflags1.0 ' start velo-timer: Cnt.Word0 = CntLw veltim0 = Cnt + Minvel + vel ' Minvel is byte and controllable with CC15 Set Rimshot1 Else ' we refuse to activate the coil again and wait for the timer to run out. ' we could also set the timer and clear the note, such that we would guarantee a 50% duty cycle. Set Velflags1.0 ' start velo-timer: Cnt.Word0 = CntLw veltim0 = Cnt + Minvel + vel Clear Rimshot1 EndIf Case 74 vel = velo << 2 '1 If Velflags1.1 = 0 Then Set Velflags1.1 Cnt.Word0 = CntLw veltim1 = Cnt + Minvel + vel Set Rimshot2 Else Set Velflags1.1 Cnt.Word0 = CntLw veltim1 = Cnt + Minvel + vel Clear Rimshot2 EndIf Case 120 If velo < 127 Then Set VelFlags0.0 Cnt.Word0 = CntLw Lite1period = (~velo & 127) << 9 Lite1tim = Cnt + Lite1period Set Lite1 Else Set Lites.0 Clear VelFlags0.0 Set Lite1 EndIf Case 121 If velo < 127 Then Set VelFlags0.1 Cnt.Word0 = CntLw Lite2period = (~velo & 127) << 9 Lite2tim = Cnt + Lite2period Set Lite2 Else Set Lites.1 Clear VelFlags0.1 Set Lite2 EndIf End Select Set noteAan '= 255 'reset EndIf GoTo Check_Timers Case Keypres_Status 'used for lite flashing speed modulation If notePres = 255 Then notePres = Bytein Else pres = Bytein GoSub KeyPres EndIf GoTo Check_Timers Case Control_Status 'this is where the action takes place for controllers If Ctrl = 255 Then Ctrl = Bytein Else value = Bytein GoSub Controller EndIf GoTo Check_Timers ' Case ProgChange_Status ' If prog = 255 Then 'single byte message ' prog = Bytein 'weak coding... ' GoSub ProgChange ' EndIf End Select EndIf Check_Timers: ' here we check the Task counters and compare them with the 32 bit cnt value ' using the Velflags dword variable: If VelFlags0 > 0 Then 'if any bit is set here, there is a timer running If VelFlags0.0 = 1 Then Cnt.Word0 = CntLw 'read counter If Cnt >= Lite1tim Then Lite1tim = Cnt + Lite1period 'add the period duration btg Lite1 'Toggle EndIf EndIf If VelFlags0.1 = 1 Then Cnt.Word0 = CntLw 'read counter If Cnt >= Lite2tim Then 'note 121 Lite2 Lite2tim = Cnt + Lite2period btg Lite2 EndIf EndIf 'Else ' If CntHw > 0xFF Then Clear CntHw EndIf If Velflags1 > 0 Then If Velflags1.0 = 1 Then Cnt.Word0 = CntLw If Cnt >= veltim0 Then Clear Rimshot1 Clear Velflags1.0 ' one shot EndIf EndIf If Velflags1.1 = 1 Then Cnt.Word0 = CntLw If Cnt >= veltim1 Then Clear Rimshot2 Clear Velflags1.1 ' one shot EndIf EndIf If Velflags1.2 = 1 Then Cnt.Word0 = CntLw If Cnt >= snaretim1 Then pw1 = SafeLimit ' 0- 254 HPWM 1,pw1, PWMfreq Clear Velflags1.2 Clear YellowLED EndIf EndIf If Velflags1.3 = 1 Then Cnt.Word0 = CntLw If Cnt >= snaretim2 Then pw2 = SafeLimit HPWM 2,pw2, PWMfreq Clear Velflags1.3 Clear BlueLED EndIf EndIf If Velflags1.4 = 1 Then Cnt.Word0 = CntLw If Cnt >= snaretim3 Then pw2 = SafeLimit pw1 = SafeLimit HPWM 2,pw2, PWMfreq HPWM 1,pw1, PWMfreq Clear Velflags1.4 Clear BlueLED Clear YellowLED Set GreenLED EndIf EndIf End If GoTo LOOP ' end of the main loop KeyPres: 'the note to which the pressure should be applied is passed in NotePres, the value in Pres 'here we use it for flashing lights on Snar2. Select notePres Case 120 If Lites.0 = 1 Then Set VelFlags0.0 Cnt.Word0 = CntLw 'read timer Lite1period = (~pres & 127) << 9 Lite1tim = Cnt + Lite1period 'add the period duration Else Clear VelFlags0.0 Clear Lite1 EndIf Case 121 If Lites.1 = 1 Then Set VelFlags0.1 Cnt.Word0 = CntLw 'read timer Lite2period = (~pres & 127) << 9 Lite2tim = Cnt + Lite2period 'add the period duration Else Clear VelFlags0.1 Clear Lite2 EndIf End Select Set notePres '= 255 Return ProgChange: Set prog '= 255 'this is not realy required Return Pitchbend: 'only implemented on dsPIC based robots Set pblsb '= 255 Return Aftertouch: 'this is the channel aftertouch, affecting all notes Set aft '= 255 'not mandatory Return Controller: Select Ctrl Case 9 ' snares1 solenoid ' pw1 = value << 1 ' 0- 254 v = value >> 1 ' divide by 2 pw1 = Pwmin + value + v ' maximum 255 If pw1 < SafeLimit Then If value = 0 Then HPWM 1, 0, PWMfreq Else HPWM 1,pw1, PWMfreq Clear YellowLED EndIf Else ' here we have to start timers in case pw1 > Safevalue (midi value 87, corresponding to 24V over the coils) Set Velflags1.2 Cnt.Word0 = CntLw 'read timer snaretim1 = (~pw1) << 12 ' (~value & 127) << 11 '9 ' the larger the value, the shorter the timer should be programmed snaretim1 = Cnt + snaretim1 'add the period duration HPWM 1, pw1, PWMfreq Set YellowLED EndIf Case 10 v = value >> 1 pw2 = Pwmin + value + v If pw2 < SafeLimit Then If value = 0 Then HPWM 2, 0, PWMfreq Else HPWM 2,pw2, PWMfreq Clear BlueLED EndIf Else Set Velflags1.3 Cnt.Word0 = CntLw 'read timer snaretim2 = (~pw2) << 12 '(~value & 127) << 11 '9 snaretim2 = Cnt + snaretim2 'add the period duration HPWM 2, pw2, PWMfreq Set BlueLED EndIf Case 11 ' both solenoids together v = value >> 1 'pw1 = value << 1 pw1 = Pwmin + value + v pw2 = pw1 If pw1 < SafeLimit Then If value = 0 Then HPWM 1, 0, PWMfreq HPWM 2, 0, PWMfreq Clear GreenLED Else HPWM 1,pw1, PWMfreq HPWM 2,pw2, PWMfreq Clear YellowLED Clear BlueLED Set GreenLED EndIf Else Set Velflags1.4 Cnt.Word0 = CntLw 'read timer snaretim3 = (~pw1) << 12 '(~value & 127) << 11 '9 snaretim3 = Cnt + snaretim3 'add the period duration HPWM 2, pw2, PWMfreq HPWM 1, pw1, PWMfreq Set YellowLED Set BlueLED Clear GreenLED EndIf Case 15 ' set minvel for the rimshots Minvel = value << 1 Case 16 ' set mimimum pwm value for the snares Pwmin = value >> 1 ' must be < 64 Case 66 'on/off for the robot If value = 0 Then Clear PowerOn 'CC66.0 GoSub PowerDown Else Set PowerOn 'CC66.0 EndIf Case 123 GoSub AllNotesOff End Select Set Ctrl '= 255 'mandatory reset Return AllNotesOff: ' no controller reset! Clear VelFlags0 Clear Velflags1 Clear Rimshot1 Clear Rimshot2 Clear Snares1 Clear Snares2 Clear Lite1 Clear Lite2 HPWM 1, 0, PWMfreq ' connected to RC2 HPWM 2, 0, PWMfreq ' connected to RC1 Clear Lites Clear GreenLED Clear YellowLED Clear BlueLED Clear pw1 Clear pw2 Return PowerDown: Clear VelFlags0 'stop all running timers Clear Velflags1 Clear Rimshot1 Clear Rimshot2 Clear Snares1 Clear Snares2 Clear Lite1 Clear Lite2 Clear pw1 Clear pw2 Clear Lites HPWM 1, 0, PWMfreq ' connected to RC2 HPWM 2, 0, PWMfreq ' connected to RC1 Clear YellowLED Clear BlueLED Clear GreenLED Clear PowerOn Minvel = Minvel_Rimshot ' reset controllers to cold boot values Pwmin = pwmin_snares Return '[EOF]