'**************************************************************** '* Name : Asa_Hub.BAS * '* Author : Godfried-Willem RAES * '* Notice : Copyleft (c) 2013 Logosoft Public Domain * '* Date : 08-06-2013 * '* Version : 1. * '* Notes : Based On Horny/Klar-hub code model * '**************************************************************** '08.06.2013: PIC: 18F2525 On MidiHub board, bidirectional solenoids, lites ' First sketch. ' Optional: ' Analog tilt sensor design with Penny + Giles device. ' For -60 degree to + 60 degree we should read values between 102 and 922 from the ADC ' For -45 degree to + 45 degree we should read values between 205 and 819 from the ADC ' reduced to 7 bits, with 64 as center value we get 25 and 102 as extremes for 45 degrees '12.06.2013: First version. No sensors. '13.06.2013: First test run on . No failures detected. '15.06.2013: Worked out version 1.0 for the fingering lookup tables Include "18F2525.inc" 'version for the Asa 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 Left PORTC.5 ' X11-2 $define Right PORTC.4 ' X11-3 $define Front PORTC.1 ' X17-2 - pwm $define Back PORTC.2 ' X17-3 - pwm ' lights: (powered from stabilized 12V) $define Lite1 PORTB.0 ' X12-2 - yellow $define Lite2 PORTB.1 ' X12-3 - yellow $define Lite3 PORTC.3 ' X15-2 - Red $define Lite4 PORTC.0 ' X15-3 $define BlueLed PORTA.3 'position reached indicator. $define GreenLed PORTA.4 'for code monitoring and debug - movement upwards $define YellowLed PORTA.5 'for code monitoring and debug - movement downward $define Sensor PORTA.0 'input port - optional Penny& Giles tilt sensor $define SensorLR PORTA.1 'input port - optional 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 = 3 ' Asa_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 NrTasks = 4 ' 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 ' 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 ' 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 ' 32 bit velo Dim veltim3 As Dword System ' duration for the valve release timeout 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 CC22 As Byte System ' front/back soll-position - midi value 64 = equilibrium Dim CC23 As Byte System ' left right 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 SensorVal As Word System ' holds the 10bit value read from ADC0 ' has to be word, as we integrate in the low interrupt ' Dim RestVal As Word System ' sensor value read on standstill - 10 bit ' Dim EqPos As Byte System ' 8-bit version of shifted standstill value Dim Newval As Word System ' used for integration in the low IRQ Dim TmpVal As Word System ' Dim nowval As Byte System ' 8 bit and shifted value of above. ' Dim Sollpos As Byte System ' 8 bit = CC22 * 2 ' Dim sBit As Byte System ' sampling rate bit toggle Dim pw1 As Byte System ' for the bidirectional solenoid. front-back Dim pw2 As Byte System ' Dim tog1 As Byte System ' Dim tog2 As Byte System ' Dim tel As Byte System '----------------------------------------------------------------------------------------- ' 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 "Asa_Hub_Irq.inc" ' our own version for UART And Timer0/3 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] 'make sure we initialize those pins on start up: 'fault?: there should be no executable statements outside the main program. Low Front Low Back Low Left Low Right Low Lite1 Low Lite2 Low Lite3 Low Lite4 Low Debug_Led HPWM 2, 0, PWMminF ' movement bidirectional solenoid HPWM 1, 0, PWMminF ' connected to RC2 Low GreenLED Low YellowLed Low BlueLED Clear CC66 CC22 = 64 CC23 = 64 '----------------------------------------------------------------------------------------- ' Main program starts here MAIN: High Debug_Led DelayMS 50 ' wait for stability Low Debug_Led Clear VelFlags0 Clear Lites 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 the ADC: ' Fosc/32 ' Right justified for 10-bit operation ' Tad value of 0 ' Vref+ at Vcc : Vref- at Gnd ' Make AN0 an analogue input ' ' OpenADC(ADC_FOSC_32 & ADC_RIGHT_JUST & ADC_0_TAD, ADC_REF_VDD_VSS, ADC_1ANA) ' could be replaced with: ADCON2 = %10000010 ADCON1 = %00001110 ADCON0 = %00000001 SensorVal = ReadADC 0 ' initialize with the value on startup - 10 bit resolution ' 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 ' --------------------------------------------------- 'Calibrate: ' if we can safely assume that on power up, the robot is not moved, we can autocalibrate the ' zero position. At this point the ADC should be functional already with sampling in the low interrupt. ' This procedure determines the value of Eqpos (8 bit) and thus the default reset value for CC22 ' Following is Klar coding and has to be changed!!!! ' DelayMS 5000 ' let the sampler work for 5 seconds ? does this leave the IRQ's intact??? 'RestVal = SensorVal ' save 10bit at-rest value 'If RestVal >= 256 Then TmpVal = RestVal - 256 Else Clear TmpVal ' ' tmpval now has a range of 0-512, with 256 in the middle : 9 bits 'If TmpVal > 511 Then TmpVal = 511 ' make sure we do not overflow 'nowval = TmpVal >> 1 ' divide by 2, to get a 8 bit range again. (practical: 38... 129 ... 219) 'EqPos = nowval ' 8-bits 'CC22 = EqPos >> 1 ' set movement controller to this same value, in 7-bit 'Sollpos = EqPos ' so we do not move 'nowval = nowval >>1 ' 7-bits ' --------------------------------------------------- ' 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 120 Clear Lites.0 Clear VelFlags0.0 Clear Lite1 Case 121 Clear Lites.1 Clear VelFlags0.1 Clear Lite2 Case 122 Clear Lites.2 Clear VelFlags0.2 Clear Lite3 Case 123 Clear Lites.3 Clear VelFlags0.3 Clear Lite4 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 120 Clear Lites.0 '= 0 Clear VelFlags0.0 Clear Lite1 Case 121 Clear Lites.1 '= 0 Clear VelFlags0.1 Clear Lite2 Case 122 Clear Lites.2 '= 0 Clear VelFlags0.2 Clear Lite3 Case 123 Clear Lites.3 '= 0 Clear VelFlags0.3 Clear Lite4 End Select Set noteAan '= 255 'reset !!! GoTo Check_Timers 'jump out EndIf Select noteAan Case 120 If velo < 127 Then 'omleiding van de kode naar keypres: notePres = 120 pres = velo GoSub KeyPres Else Set Lites.0 Clear VelFlags0.0 Set Lite1 EndIf Case 121 If velo < 127 Then 'omleiding van de kode naar keypres: notePres = 121 pres = velo GoSub KeyPres Else Set Lites.1 Clear VelFlags0.1 Set Lite2 EndIf Case 122 If velo < 127 Then 'omleiding van de kode naar keypres: notePres = 122 pres = velo GoSub KeyPres Else Set Lites.2 Clear VelFlags0.2 Set Lite3 EndIf Case 123 If velo < 127 Then 'omleiding van de kode naar keypres: notePres = 122 pres = velo GoSub KeyPres Else Set Lites.3 Clear VelFlags0.3 Set Lite4 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 veltim.Word1 = Velmsb[0] veltim.Word0 = VelLsb[0] Cnt.Word0 = CntLw 'read counter If Cnt >= veltim Then GoSub Task0 'note 120 Lite1 EndIf If VelFlags0.1 = 1 Then veltim.Word1 = Velmsb[1] veltim.Word0 = VelLsb[1] Cnt.Word0 = CntLw 'read counter If Cnt >= veltim Then GoSub Task1 'note 121 Lite2 EndIf If VelFlags0.2 = 1 Then veltim.Word1 = Velmsb[2] veltim.Word0 = VelLsb[2] Cnt.Word0 = CntLw 'read counter If Cnt >= veltim Then GoSub Task2 'note 122 Lite3 EndIf If VelFlags0.3 = 1 Then veltim.Word1 = Velmsb[3] veltim.Word0 = VelLsb[3] Cnt.Word0 = CntLw 'read counter If Cnt >= veltim Then GoSub Task3 'note 123 Lite4 EndIf 'Else ' If CntHw > 0xFF Then Clear CntHw EndIf ' alternate sensor reading and motor drive: ' Cnt.Word0 = CntLw ' read counter ' If Cnt.10 <> sBit Then ' Cnt.14 sets the refresh rate at 4 S/s ' ' Cnt.13 at 8 S/s ' ' Cnt.12 at 16 S/s was Cnt.12 in code version 2.0 On ' ' Cnt.11 at 32 S/s was Cnt.11 On ' ' Cnt.10 at 64 S/s ' sBit = Cnt.10 ' Inc tel ' If tel.1 = 0 Then 'Sensor_check: 'SensorVal = ReadADC(0) ' Read the ADC value from the tilt sensor - using the macro ' now done in the low interrupt handler, at 64 S/s 'this is a 10 bit value, right adjusted. 'so we do level shifting: ' If SensorVal >= 256 Then TmpVal = SensorVal - 256 Else Clear TmpVal 'nowval is byte ' ' tmpval now has a range of 0-512, with 256 in the middle : 9 bits ' If TmpVal > 511 Then TmpVal = 511 ' make sure we do not overflow ' ' nowval = TmpVal >> 1 ' divide by 2, to get a 8 bit range again. (practical: 38... 129 ... 219) ' ' by dividing by 4, we could add hysteresis as we work on 7 bits only ' nowval = TmpVal >> 1 ' 8 bit ' ' wouldn't it be better to place all the sensor handling in the low interrupt? ' If nowval > EqPos + HalfHysteresis Then ' EqPos is 8-bit ' Set YellowLed ' Clear GreenLed ' Clear BlueLed ' EndIf ' If nowval < EqPos - HalfHysteresis Then ' Set GreenLed ' Clear YellowLed ' Clear BlueLed ' EndIf 'Set Valve1 ' Else ' following is coded for horny! should be changed for Asa. Solenoid_Action: ' If pw1 > 0 Then ' front movement ' Dec pw1 ' HPWM 1,pw1, PWMminF ' EndIf ' If pw2 > 0 Then ' backwards movement ' Dec pw2 ' HPWM 2,pw2, PWMminF ' EndIf ' EndIf 'EndIf 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 Horny. Select notePres Case 120 If Lites.0 = 1 Then Set VelFlags0.0 Cnt.Word0 = CntLw 'read timer Task_rsi[0] = (~pres & 127) << 9 veltim = Cnt + Task_rsi[0] 'add the period duration Velmsb[0] = veltim.Word1 VelLsb[0] = veltim.Word0 Else Clear VelFlags0.0 Clear Lite1 EndIf Case 121 If Lites.1 = 1 Then Set VelFlags0.1 Cnt.Word0 = CntLw 'read timer Task_rsi[1] = (~pres & 127) << 9 veltim = Cnt + Task_rsi[1] 'add the period duration Velmsb[1] = veltim.Word1 VelLsb[1] = veltim.Word0 Else Clear VelFlags0.1 Clear Lite2 EndIf Case 122 If Lites.2 = 1 Then Set VelFlags0.2 Cnt.Word0 = CntLw 'read timer Task_rsi[2] = (~pres & 127) << 9 veltim = Cnt + Task_rsi[2] 'add the period duration Velmsb[2] = veltim.Word1 VelLsb[2] = veltim.Word0 Else Clear VelFlags0.2 Clear Lite3 EndIf Case 123 If Lites.3 = 1 Then Set VelFlags0.3 Cnt.Word0 = CntLw 'read timer Task_rsi[3] = (~pres & 127) << 9 veltim = Cnt + Task_rsi[3] 'add the period duration Velmsb[3] = veltim.Word1 VelLsb[3] = veltim.Word0 Else Clear VelFlags0.3 Clear Lite4 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 22 ' this controller is the command for front/back motion Select value Case < 64 ' naar achter If value = 0 Then value = 1 pw2 = (64 - value) << 2 ' 0-255 Clear pw1 Case > 64 ' naar voor pw1 = (value - 64) << 2 ' 0-255 Clear pw2 Case 64 Clear pw1 Clear pw2 End Select HPWM 1,pw1, PWMminF HPWM 2,pw2, PWMminF CC22 = value Case 23 ' this controller steers the left/right movement. Select value Case 64 Low Left Low Right Case < 64 High Left Low Right Case > 64 Low Left High Right End Select CC23 = value 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: 'Clear VelFlags0 'would stop all running timers, also the motor clock... Clear VelFlags0.0 Clear VelFlags0.1 Clear VelFlags0.2 Clear VelFlags0.3 Clear Left Clear Right Clear Front Clear Back HPWM 1, 0, PWMminF ' connected to RC2 HPWM 2, 0, PWMminF ' connected to RC1 Clear Lites CC22 = 64 CC23 = 64 Return PowerDown: Clear VelFlags0 'stop all running timers Clear Left Clear Right Clear Front Clear Back Clear Lite1 Clear Lite2 Clear Lite3 Clear Lite4 Clear Lites Clear pw1 Clear pw2 HPWM 1, 0, PWMminF ' connected to RC2 HPWM 2, 0, PWMminF ' connected to RC1 CC22 = 64 CC23 = 64 'Sollpos = EqPos 'CC22 = EqPos >> 1 Return Task0: If Lites.0 = 0 Then Clear VelFlags0.0 'stop task, as lite is switched off Clear Lite1 '= 0 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 Lite1 'Toggle EndIf Return Task1: If Lites.1 = 0 Then Clear VelFlags0.1 'stop task, as lite is switched off Clear Lite2 Else 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 Lite2 EndIf Return Task2: If Lites.2 = 0 Then Clear VelFlags0.2 'stop task, as lite is switched off Clear Lite3 Else Cnt.Word0 = CntLw veltim = Cnt + Task_rsi[2] 'add the period duration Velmsb[2] = veltim.Word1 VelLsb[2] = veltim.Word0 btg Lite3 EndIf Return Task3: If Lites.3 = 0 Then Clear VelFlags0.3 'stop task, as lite is switched off Clear Lite4 Else Cnt.Word0 = CntLw veltim = Cnt + Task_rsi[3] 'add the period duration Velmsb[3] = veltim.Word1 VelLsb[3] = veltim.Word0 btg Lite4 EndIf Return '[EOF]