'**************************************************************** '* Name : Bomi_Hub.BAS * '* Author : Godfried-Willem RAES * '* Notice : Copyleft (c) 2010 Logosoft Public Domain * '* Date : 14-Oct-10 / 28.05.2011 * '* Version : 2.2 * '* Notes : Version 1.0 was coded in C by Johannes Taelman * '* : * '**************************************************************** '14.10.2010: First version. '15.10.2010: Minor bugs killed. There still is an issue with the noteoff's on the lites. '28.05.2011: Code revisited and improved. ' X-tof suggestions implemented 'PIC: 18F2525 On MidiHub board Compressor motor, tremulant solenoid, lites ' - X17.2: PIC pin 12 CCP1 -rc2 PWM analog compressor motor speed (0-10V) Ctrl 7 ' - X17.3: PIC pin 13 CCP2 -rc1 PWM power output Softshift solenoid Ctrl 1 ' - X11.2: PIC pin 16 RC5 motor ON/OFF - Ctrl 66 ' - X11.3: PIC pin 15 RC4 optional motor controller reset: CC86 ' - X12.2: PIC pin 21 RB0 lite - map on note 125 on/off + pres YELLOW LED (3W) ' - X12.3: PIC pin 22 RB1 lite - map on note 126 on/off + pres ' - X15.2: PIC pin 14 RC3 lite - map on note 127 on/off + pres ' - X15.3: PIC pin 11 RC0 lite - map on note 0 on/off + pres RED LED (1W) 'NOTE: the PIC should track whether any note is playing or not. ' All notes off should unpower the softshift solenoid and reset controllers 1 and 11. 'Controller 1: steers the main value for the PWM on the softshift magnet. ' Note: 0= fully opened, 127=fully closed!!! 'Controller 7: Motor speed 'Controller 86: reset motor on error. (one shot task) 'Controller 11: steers the periodic modulation frequency around the value set with controller 1. ' Tremulant must be off with controller set to zero. 'controller 12: modulation depth ' cfr. code prototype in GMT Include "18F2525.inc" 'version for the bomi board. 'Include "18F25K20.inc" 'for test & debug on an Amicus board. 'Include "18F4620.inc" 'not applicable to this code/hardware. 'constant definitions: 'initialisations for the midi input parser: Symbol Midichannel = 3 ' Bomi_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 ' maximum 16 ' 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 ' Declare Hserial_Clear = On ' should clear on errors. Bytes get lost of course... This must be 31250 for MIDI ' Create variables Dim Cnt As Dword System Dim CntHw As Cnt.Word1 'only used in the timer0 interrupt. Dim CntLw As TMR0L.Word 'this is the trick to read both TMR0L and TMR0H 'it makes Cntlw 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 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 ' 32 bit velo ' Dim newtim 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 - not used in this code. Dim CC1 As Byte System 'softshift valve pwm Dim CC7 As Byte System 'motor speed pwm Dim CC11 As Byte System 'valve modulation speed Dim CC12 As Byte System 'valve modulation depth 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 Lites As Byte System 'bits used as flags Dim CC1min As Byte System Dim CC1max As Byte System Dim Tremolo As Byte System 'only bit 0 is toggled Dim wvar As Word System Dim tveltim As Dword System 'added by xof for better algorithm for repeats.. '----------------------------------------------------------------------------------------- ' Load the USART Interrupt handler And buffer read subroutines into memory Include "Bomi_Hub_Irq.inc" ' our own version for UART And Timer0 Interrupt 'Include "Timers.inc" ' required for velo support with timed pulses and periods. 'Include "DwordArrays.inc" ' support for dword arrays. 'framework for a multitasker: Dim Task_rsi[NrTasks] As Word 'task reschedule interval (period), if 0 the task is not active 'max. value limited to 65535. For longer periods, it will have to 'become dword!!! Dim Velmsb[NrTasks] As Word 'the application for velo-timers, is in fact just a one-shot task Dim VelLsb[NrTasks] As Word 'DeclareDwordArray(TimeVals , NrTasks) 'alternative using the macro's. [not yet used] ' assigning values syntax: DwordArray Timvals,[i], value ' reading values syntax: value = DwordArray TimVals,[i] ' Mapping defines for midi-events on pin outputs and inputs: ' $define Softshift PORTC.2 'CCP1 - PWM channel 1 ' $define MotorSpeed PORTC.1 'CCP2 - PWM Channel 2 $define MotorSwitch PORTC.5 'ón/off switch for motor $define MotorReset PORTC.4 'reset after failure one shot ' lights: $define Lite125 PORTB.0 $define Lite126 PORTB.1 $define Lite127 PORTC.3 $define Lite0 PORTC.0 'red LED for debug: $define Debug_Led PORTB.5 ' for testing - red led ' configure the input and 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 on start up: Low Lite0 Low Lite125 Low Lite126 Low Lite127 Low Debug_Led Low MotorSwitch 'motor switch off HPWM 1, 255, PWMminF 'motor speed off - inverted pwm HPWM 2, 0, PWMminF 'klep open Low MotorReset 'ínv CC66 = 0 CC7 = 0 CC1 = 0 CC11 = 0 CC12 = 0 Clear VelFlags0 Clear Lites Clear Cnt '----------------------------------------------------------------------------------------- ' Main program starts here MAIN: High Debug_Led DelayMS 50 ' wait for stability Low Debug_Led 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 ' following only required for pulse outputs: ' Note that we can only use timer0 since the other timers ' are used by the PWM subsystem ' 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) Clear T0CON Clear IntConBits_T0IF Set INTCONBITS_T0IE T0CON = %10000111 ' Setup the High priorities for the interrupts ' 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 ' this is the main thing to listen to.... 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 0 Clear Lites.0 Clear VelFlags0.0 Clear Lite0 Case 125 Clear Lites.1 Clear VelFlags0.1 Clear Lite125 Case 126 Clear Lites.2 Clear VelFlags0.2 Clear Lite126 Case 127 Clear Lites.3 Clear VelFlags0.3 Clear Lite127 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 0 Lites.0 = 0 Clear VelFlags0.0 Clear Lite0 Case 125 Lites.1 = 0 Clear VelFlags0.1 Clear Lite125 Case 126 Lites.2 = 0 Clear VelFlags0.2 Clear Lite126 Case 127 Lites.3 = 0 Clear VelFlags0.3 Clear Lite127 End Select Set noteAan '= 255 'reset !!! GoTo Check_Timers 'jump out EndIf 'If CC66.0 = 1 Then 'only if powered up Select noteAan Case 0 Set Lites.0 Clear VelFlags0.0 Set Lite0 Case 125 Set Lites.1 Clear VelFlags0.1 'so flashing is reset Set Lite125 Case 126 Set Lites.2 Clear VelFlags0.2 Set Lite126 Case 127 Set Lites.3 Clear VelFlags0.3 Set Lite127 End Select 'EndIf Set noteAan '= 255 'reset EndIf GoTo Check_Timers Case Keypres_Status 'used for lite flashing 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 'lite0 pulse veltim.Word1 = Velmsb[0] veltim.Word0 = VelLsb[0] 'Cnt.Word1 = CntHw Cnt.Word0 = CntLw 'read counter If Cnt >= veltim Then GoSub Task0 'lite0 EndIf If VelFlags0.1 = 1 Then veltim.Word1 = Velmsb[1] veltim.Word0 = VelLsb[1] 'Cnt.Word1 = CntHw Cnt.Word0 = CntLw 'read counter If Cnt >= veltim Then GoSub Task1 'lite125 EndIf If VelFlags0.2 = 1 Then veltim.Word1 = Velmsb[2] veltim.Word0 = VelLsb[2] 'Cnt.Word1 = CntHw Cnt.Word0 = CntLw 'read counter If Cnt >= veltim Then GoSub Task2 'lite126 EndIf If VelFlags0.3 = 1 Then veltim.Word1 = Velmsb[3] veltim.Word0 = VelLsb[3] 'Cnt.Word1 = CntHw Cnt.Word0 = CntLw 'read counter If Cnt >= veltim Then GoSub Task3 'lite127 EndIf If VelFlags0.4 = 1 Then 'tremulant veltim.Word1 = Velmsb[4] veltim.Word0 = VelLsb[4] 'Cnt.Word1 = CntHw Cnt.Word0 = CntLw 'read counter If Cnt >= veltim Then GoSub Task4 'tremulant EndIf If VelFlags0.5 = 1 Then veltim.Word1 = Velmsb[5] veltim.Word0 = VelLsb[5] 'Cnt.Word1 = CntHw Cnt.Word0 = CntLw 'read counter If Cnt >= veltim Then GoSub Task5 'resetpulse EndIf Else If CntHw > 0xFF Then Clear CntHw EndIf 'end of the main loop GoTo 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 Bomi Select notePres Case 0 If Lites.0 = 1 Then Set VelFlags0.0 'Cnt.Word1 = CntHw Task_rsi[0] = (~pres & 127) << 9 Cnt.Word0 = CntLw 'read timer veltim = Cnt + Task_rsi[0] 'add the period duration Velmsb[0] = veltim.Word1 VelLsb[0] = veltim.Word0 Else Clear VelFlags0.0 Clear Lite0 EndIf Case 125 If Lites.1 = 1 Then Set VelFlags0.1 Task_rsi[1] = (~pres & 127) << 9 Cnt.Word0 = CntLw 'read timer veltim = Cnt + Task_rsi[1] 'add the period duration Velmsb[1] = veltim.Word1 VelLsb[1] = veltim.Word0 Else Clear VelFlags0.1 Clear Lite125 EndIf Case 126 If Lites.2 = 1 Then Set VelFlags0.2 Task_rsi[2] = (~pres & 127) << 9 Cnt.Word0 = CntLw 'read timer veltim = Cnt + Task_rsi[2] 'add the period duration Velmsb[2] = veltim.Word1 VelLsb[2] = veltim.Word0 Else Clear VelFlags0.2 Clear Lite126 EndIf Case 127 If Lites.3 = 1 Then Set VelFlags0.3 Task_rsi[3] = (~pres & 127) << 9 Cnt.Word0 = CntLw 'read timer veltim = Cnt + Task_rsi[3] 'add the period duration Velmsb[3] = veltim.Word1 VelLsb[3] = veltim.Word0 Else Clear VelFlags0.3 Clear Lite127 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 1 'tremulant valve - PWM 'when the valve has no voltage, it is maximum opened. 'with maximum voltage, it is closed. If value = 0 Then HPWM 2, 0, PWMminF 'klep volledig open, omhoog, spanningsloos Else HPWM 2, value+value,PWMminF EndIf CC1 = value 'CC1min and CC1max have to be recalculated as well now: If CC12 < CC1 Then CC1min = CC1 - CC12 Else Clear CC1min EndIf CC1max = CC1 + CC12 If CC1max > 127 Then CC1max = 127 Case 7 'motor speed PWM - inverted!!! If value = 0 Then HPWM 1, 255, PWMminF Else HPWM 1, 255 -(value + value), PWMminF EndIf CC7 = value Case 11 'tremulant frequency CC11 = value If CC11 > 0 Then Set VelFlags0.4 wvar = CC11 << 7 ' = * 128 now range 0 - 16256 Task_rsi[4] = 16384 - wvar 'xof's idea: by resetting the veltim here, we create timing errors. we might be halfway a period already ' and now prolong it by rescheduling. ' if we'd just omit the following 3 lines, that wouldn't happen and the speed would ' ' be reset at the next pulse ' downside is indeed that when we where at a very slow speed then, we wouldn't speed up immediately.. ' ->> ideally we would compare what the old veltime was with what the new one would be ' and take the smallest of the two values? 'an attempt to code this: veltim.Word1 = Velmsb[4] 'get original veltim according to old rsi back veltim.Word0 = VelLsb[4] Cnt.Word0 = CntLw 'read timer tveltim = Cnt + Task_rsi[4] 'this is the one according to new rsi 'veltim = min(veltim, tveltim) ' not supported with dword var's... If veltim > tveltim Then veltim = tveltim 'we take the one happening first. next veltim will automatically be scheduled 'according to the new rsi.. 'Cnt.Word0 = CntLw 'read timer 'veltim = Cnt + Task_rsi[4] 'add the period duration Velmsb[4] = veltim.Word1 VelLsb[4] = veltim.Word0 Else Clear VelFlags0.4 'reset valve to value set by CC1: HPWM 2, CC1+CC1,PWMminF EndIf Case 12 'tremulant modulation depth 'needs to be recalculated if CC1 changes. CC12 = value If CC12 < CC1 Then CC1min = CC1 - CC12 Else Clear CC1min EndIf CC1max = CC1 + CC12 If CC1max > 127 Then CC1max = 127 Case 66 'on/off for the robot If value = 0 Then Clear CC66.0 Set MotorSwitch GoSub AllNotesOff Else Clear MotorSwitch Set CC66.0 EndIf Case 86 'motor reset - one shot pulse 'the value of the controller is irrelevant here. Set VelFlags0.5 Cnt.Word0 = CntLw 'read timer Task_rsi[5] = 512 'duration of the pulse veltim = Cnt + Task_rsi[5] 'add the period duration Velmsb[5] = veltim.Word1 VelLsb[5] = veltim.Word0 btg MotorReset Case 123 GoSub AllNotesOff End Select Ctrl = 255 'mandatory reset Return AllNotesOff: Clear VelFlags0 'stop all running timers Clear Lite0 Clear Lite125 Clear Lite126 Clear Lite127 Clear CC1 Clear CC11 Clear CC12 HPWM 2, 0, PWMminF 'softshift open 'no change on motor speed!!! 'no motor turn off ' Clear Debug_Led Clear Lites Return Task0: If Lites.0 = 0 Then Clear VelFlags0.0 'stop task, as lite is switched off Clear Lite0 Else 'reload task0 - light 'Set VelFlags0.0 'can just stay set Cnt.Word0 = CntLw veltim = Cnt + Task_rsi[0] 'add the period duration Velmsb[0] = veltim.Word1 VelLsb[0] = veltim.Word0 btg Lite0 EndIf Return Task1: If Lites.1 = 0 Then Clear VelFlags0.1 'stop task, as lite is switched off Clear Lite125 Else 'reload task1 light: 'Set VelFlags0.1 'can just stay set Cnt.Word0 = CntLw veltim = Cnt + Task_rsi[1] 'add the period duration in Task_rsi[1] Velmsb[1] = veltim.Word1 VelLsb[1] = veltim.Word0 btg Lite125 EndIf Return Task2: If Lites.2 = 0 Then Clear VelFlags0.2 'stop task, as lite is switched off Clear Lite126 Else 'reload task2 - light 'Set VelFlags0.2 'can just stay set Cnt.Word0 = CntLw veltim = Cnt + Task_rsi[2] 'add the period duration Velmsb[2] = veltim.Word1 VelLsb[2] = veltim.Word0 btg Lite126 EndIf Return Task3: If Lites.3 = 0 Then Clear VelFlags0.3 'stop task, as lite is switched off Clear Lite127 Else 'reload task3 - light 'Set VelFlags0.3 'can just stay set Cnt.Word0 = CntLw veltim = Cnt + Task_rsi[3] 'add the period duration Velmsb[3] = veltim.Word1 VelLsb[3] = veltim.Word0 btg Lite127 EndIf Return Task4: If CC11 = 0 Then Clear VelFlags0.4 'stop task, as tremulant is switched off 'reset the valve to the position determined by CC1 HPWM 2,CC1+CC1,PWMminF Else 'reload task4 - tremulant 'Set VelFlags0.4 'can just stay set Cnt.Word0 = CntLw veltim = Cnt + Task_rsi[4] 'add the period duration Velmsb[4] = veltim.Word1 VelLsb[4] = veltim.Word0 If Tremolo.0 = 1 Then HPWM 2, CC1min+CC1min,PWMminF Clear Tremolo.0 Else HPWM 2, CC1max+CC1max,PWMminF Set Tremolo.0 EndIf EndIf Return Task5: 'one shot task for reset pulse motor controller Clear VelFlags0.5 'stop task btg MotorReset Return '[EOF]