' ********************************************************************
' *                    PIC firmware for <Ekla>                       *
' *                           Hub board                              *
' *                  coded by dr.Godfried-Willem Raes                *
' * https://www.logosfoundation.org/instrum_gwr/ekla/picworks        *
' *                  source code development directory:              *
' *                         Ekla_Hub.bas                             *  
' *                         Version 1.4                              *
' *                Proton Compiler version 3.5.8.1                   *
' ********************************************************************
' hardware: single side PCB 08.10.2025 
' 08.10.2025: start coding
'             6 mosfet outputs
'             2 on board zero cross solid state relays used for power and motor control
'             This board performs midi-filtering and has TTL parsed midi out.
'             This approach will cause a 1 ms latency in the instrument.
' 09.10.2025: Trying to implement motor control under timer3 irq
' 10.10.2025: further work on motor pwm coding. Motorspeed intelligent mode implemented.
'             Scalings to be tested with loads.
' 12.10.2025: Tests with motor passed.
' 18.10.2025: <Ubu> plays its first scales...
' 19.10.2025: for some reason the red light doesn't flash anymore... Solved now.
'             250ms, 500ms, 1000ms periods implemented for motor control
' 20.10.2025: Default velo subsitution value (dVel) could be 6 now.
'             250ms period works fine. Lets try 125ms now...
'             motor range modified as volume values < 30 made the motor stop alltogether...
'             So  now the usable range for the user becomes 27-127.
'             Velo scalings on the PH boards changed: divided by 3. Hence we have to
'             reprogram all four boards now.
' 21.10.2025: dVel now ought to be 45, with the new scaling on the pulse-hold boards.
'             controller 100 implemented as output only to the PH3 board for lightparams.
' 22.10.2025: uploaded and tested o.k. on <Ubu>
' 23.10.2025: Considering to implement a timer to switch the motor off after 60 seconds of inactivity...
'             This feature can be disabled with controller #71.
' 25.10.2025: too many if's for the compiler?
'             CC30 no longer implemented.
'             MotorFlags reorganised and nr. tasks reduced
'             seems to work now. Tested on <Ubu> o.k.
'             In fact we could go with only a single timer.
'             To consider: use timers for gradual speed-up and slowdown for the motor.
'             All light-flashing code eliminated. So we have just 1 timer now.
'             Logos display implemented. Follows motor running now. Tested o.k.
' 26.10.2025: In fact, it would be quite ecological to have our robots switch off
'             automatically when not in use for a given time...
'             setting maxtim to Time.28 , achieves this after 107 minutes (1h47').
'             Tested and found o.k. Frontale LED strip op de voorkant gemonteerd.
' 27.10.2025: Red led lites added under the hub board. Mapping on 122 and 123
' 28.10.2025: code for lights brougth in-line with board 3.
' 29.10.2025: PWM added on the red lights. Upload o.k. Now version 1.4
' -------------------------------------------------------------------
' 05.08.2026: Same board as <Ubu> used for <Ekla>
' 06.08.2026: start coding for <Ekla>
' 18.08.2026: further work on ekla code.
 
Include "18F2620.inc"      'version for the Ekla board.         (40MHz)
'Include "18F2520.inc"     'also possible.                       (40MHz)
Clear

'$define DebugMode          'conditional compile, using very short sleeptime

' Mapping defines for midi-events on pin outputs and inputs:
' zero cross opto-relay's
    $define Power_Relay    PORTA.4     ' o.k. for <Ekla>
    $define Motor_Relay    PORTA.3 
  
' Mosfet outputs:  
    $define White1           PORTA.5   ' white under front   120
    $define White2           PORTC.0   ' white under front   121
    $define Red1             PORTC.1   ' can pwm HPWM2       122
    $define Red2             PORTC.2   ' can pwm HPWM1       123
    $define Lite3            PORTC.3   ' free     ? 117
    $define Display          PORTC.4   ' Logos-display      
' inputs:    
'    $define sensor1        PORTA.0     ' not used 
'    $define sensor2        PORTA.1     ' not used  
' LED's:    
     $define GreenLed      PORTB.2       ' on/off   
     $define BlueLed       PORTB.1       ' motor running
     $define Debug_Led     PORTB.5       ' for testing - red led - watchdog
    
Declare All_Digital = True        ' makes all analog pins, digital for I/O
Clear SSPCON1.5                   ' make sure RC3 is free for use.
' configure the input and output pins:
TRISA = %11000011     'bits set to 0 are output, 1 = input  - bits 6 and 7 are the clock!
TRISB = %11100000     'bits 6 and 7 are for the ICP, bit 5 is the red LED
TRISC = %11000000     'RC6 en RC7 zijn USART  I/O and must be set to input     
'Input sensor1        
'Input sensor2     
   
'constant definitions:
Symbol fPwm  = 21000   ' PWMminF
    ' midi note mapping
Symbol White1_note = 120   ' witte led strip onderaan - automatisch
Symbol White2_note = 121   ' witte led strip onderaan - automatisch
Symbol Red1_note = 122     ' rood licht onder midihub board?
Symbol Red2_note = 123  
Symbol Green_Note = 124 

Symbol lowest_note = 54   ' f#
Symbol highest_note = 105 ' voorlopig 
Symbol NrTasks = 1 '3 '7
Symbol LastTask = NrTasks - 1

' constants:
Symbol True = %1
Symbol False = %0
Symbol Period10 = 53039  ' for 10ms pulses motor control slow pwm , value for TIM3
Symbol Period5 = 59288   ' for 5ms pulses motor control slow pwm
Symbol Period25 = 62412  ' for 2.5ms pulses 4 Hz or period = 250ms.  3124 ticks
Symbol Period125 = 63974 ' for 1.25ms pulses, 8Hz or period = 125ms. 1562 ticks

'Symbol Crit = %101        ' = 5
                          ' MotorFlags.1 = MotWait, moet 0 zijn
                          ' MotorFlags.0 = MotorOn, moet 1 zijn
                          ' MotorFlags.2 = Autosleep, moet 1 zijn.
Symbol Period_default = Period5            

'initialisations for the midi input parser:
Symbol Midichannel = 6                         ' Ekla 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
' 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...
' Create variables:
    Dim Cnt As Dword System                    ' 1 cnt-unit ought to be 24 us.
    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 time As Cnt                            ' was : Dword  'frozen copy of Cnt
    Dim MaxTim As time.28                      ' overflow flag used to reset timer and to power down
                                               ' time.30 is 7 hours.
                                               ' time.29 is 214 minutes or 3h30
                                               ' time.28 would be 107 minutes - checked and found o.k.
 
   ' Dim T3 As Dword                            ' make 32-bit timer
   ' Dim Tim3Hw As T3.Word1 
    Dim MotTim As Byte                         ' for duty cycle control, alternating on and off value for motor 
    Dim MotCnt As Byte                         ' PWM counter increments in 10ms units in the tim3 irq 
    Dim Tim3 As TMR3L.Word                     ' same trick for timer3 , 16-bits
    
    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 CC66 As Byte System                     ' global on/off switch
        Dim PowerOn As CC66.0                        ' handled on the hub board.
        Dim Mtog As CC66.2                           ' used in timer 3 irq

    Dim MotPer As Word System                    ' 1.25ms, 2.5ms, 5 ms or 10 ms value for Tim3

    Dim Volume As Byte System             ' controller 7
    Dim oldVolume As Byte 
    Dim CC7 As Volume
    Dim CC19 As Byte                      ' release controller
    Dim Motspeed As Byte System           ' snelheid 0-100
    Dim Period As Word System             ' controller 67
    Dim CC67 As Byte     
    Dim CC68 As Byte                      ' motspeed controller               

    Dim MotorFlags As Byte System
        Dim MotorOn As MotorFlags.0         ' 25.10.2025
        Dim MotWait As MotorFlags.1         ' if 1, motor wait timer is running.
     
    Dim TimVals[NrTasks] As Dword         ' lijst met timer waarden - de kleinste is eerst aan de beurt
    Dim Nxt As Dword System               ' waarde voor de eerstvolgende timer
    Dim idx As Byte System                ' index voor de eerstvolgende timer 
    Dim j As Float
    ' for light automation:
    Dim NrNotes As Bit   'Byte System                  ' aantal klinkende noten 0 of 1
    Dim LiTog As Bit
    Dim CC69 As Bit
    Dim Liflags As Byte
        Dim BassFlag As Liflags.0
        Dim MidFlag As Liflags.1
        Dim HighFlag As Liflags.2
    Dim OldLiFlags As Byte
'-----------------------------------------------------------------------------------------
' Load the USART Interrupt handler and buffer read subroutines into memory
Dim Ringbuffer[256] As Byte           ' Array for holding received characters
Include "Ekla_Hub_Irq.inc"                ' for UART,Timer0, Timer3 Interrupt
Dim Dur5[128] As Word                    ' duration lookup for lite flashing repetitions
Dim Toff[128] As Byte 'Dword             ' lookup for motor slow pwm
Dim Ton[128] As Byte  'Dword

Low Power_Relay
Low Motor_Relay
High Debug_Led        ' we must make sure the port is set to output
Clear CC66            ' all flags
Clear MotorFlags      ' 3 flags
Clear NrNotes

' Main program starts here:
MAIN:
    Low GreenLed
    Low BlueLed
    Low White1
    Low White2
    Low Lite3   ' nc
    Low Red2   
    Low Red1
    Low Display   
    Clear NrNotes
    Set TimVals                      ' no timers running on startup
    
    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
'                                      6.4 us per clock-tick
         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 bit, 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
' TIMER1: if enabled, all midi-in is blocked, so it must interfere with the UART                                     
             ' Configure Timer1 for:
'                        Clear TMR1L and TMR1H registers
'                        Interrupt on Timer1 overflow
'                        16-bit read/write mode
'                        Internal clock source
'                        1:8 Prescaler
'    OpenTimer1(TIMER_INT_ON & T1_16BIT_RW & T1_SOURCE_INT & T1_PS_1_8)    ' dit kompileert o.k.                               
' TIMER2: if enabled, the UART stops working...
'    Opentimer2 (Timer_Int_On & T2_POST_1_16 & T2_PS_1_16)     ' dit lukt... maar de timer is nodig voor de UART...
' TIMER3:                                                              
' Configure Timer3 for:                     
'                        Interrupt on Timer3 overflow
'                        16-bit read/write mode
'                        Internal clock source
'                        1:8 Prescaler
'                        Dont sync external clock input

Period = Period_default      ' controller 67

 ' doing it this way seems to work:
    Clear T3CON
    Clear PIR2BITS_TMR3IF   ' clear IRQ flag
    Set PIE2BITS_TMR3IE     ' irq on
  ' Clear Tim3              ' Clear TMR3L And TMR3H registers
    Tim3 = Period           ' period5 = 59288            ' 5 ms - measured and found correct.
    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, freq.=19Hz
    ' Set up priority interrupts.
  '  IPR1bits_TMR1IP = 0    ' Set Timer1 as a low priority interrupt source
  '  INTCONbits_PEIE = 1    ' Enable peripheral interrupts
  '  INTCONbits_GIE = 1     ' Enable global interrupts                        
    Clear PIE2Bits_TMR3IE   ' no motor on start-up!  - disable irq
    Clear T3CON.0           ' disable timer 3
Dur_Lookup ()
Motor_PWM_Lookup ()                     

idx = SortTimers () 
Set CC69                     ' by default light automation is ON


    ' send a power off to the pulse-hold boards and the generator board:
HRSOut Control_Status, 66, 0               

    ' start the main program loop:
Do                                   
        Cnt.Word0 = CntLw            ' read timer 0
                                     ' now time is an alias for Cnt
        Bytein = GetMidiIn ()        ' 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.
                Else
                    Clear statusbyte       'reset the status byte
                End If
                GoTo Check_Timers          'throw away...
            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          'reset value. Cannot be 0 !!!
                            Set release          '0 is a valid midi note!
                       Case NoteOn_Status
                            statusbyte = Bytein
                            Set noteAan             '= 255
                            Set velo                '= 255
                       Case Keypres_Status          
                            statusbyte = Bytein
                            Set notePres 
                            Set pres 
                       Case Control_Status          ' controllers and switches
                            statusbyte = Bytein
                            Set Ctrl 
                            Set value 
                       Case ProgChange_Status       ' used for different lookup tables
                            statusbyte = Bytein
                            Set prog
                       Case Aftertouch_Status        ' not used on this board
                            statusbyte = Bytein
                            Set aft 
                       Case Pitchbend_Status        
                            statusbyte = Bytein
                            Set pblsb 
                            Set pbmsb
                EndSelect
            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 lowest_note To highest_note               ' notes for Ekla
                                             If release = 0 Then release = CC19       ' new for <Ekla>
                                             HRSOut NoteOff_Status, noteUit, release  '  send to the pulse-hold boards
                                             Clear NrNotes
                                             If MotorOn = 1 Then
                                             '   start timer to switch motor off
                                                TimVals[0] = time + 500
                                                idx = SortTimers ()
                                             EndIf
                                     '  Case 124 To 127 'PH31_note To PH33_note   ' = 125 To 127  lights on PH3 pulse board 
                                     '        HRSOut NoteOff_Status, noteUit, 0   ' 124 = green strip
                                       Case White1_note                   
                                             Clear White1                                      
                                       Case White2_note                   
                                             Clear White2
                                       Case Red1_note         ' can pwm  122 
                                             HPWM 2, 0, fPwm       
                                             'Clear Red1  
                                       Case Red2_note         ' can pwm  123        
                                             'Clear Red2
                                             HPWM 1, 0, fPwm                          
                                EndSelect
                                Set noteUit '= 255                         'reset
                            EndIf
                       Case NoteOn_Status
                            If noteAan = 255 Then
                                noteAan = Bytein
                            Else
                                velo = Bytein
                                If velo = 0 Then                      ' note off via velo=0
                                    Select noteAan
                                       Case lowest_note To highest_note            ' notes for Ekla
                                             HRSOut NoteOff_Status, noteAan, CC19  ' not a bug!
                                             Clear NrNotes
                                             If MotorOn = 1 Then
                                                '  start timer to switch motor off
                                                 TimVals[0] = time + 500
                                                 idx = SortTimers ()
                                             EndIf                                             
                                      ' Case 124 To 127                           ' lights on PH boards 
                                      '       HRSOut NoteOff_Status, noteAan, 0  ' forward to board3    
                                       Case White1_note                   
                                             Clear White1                                          
                                       Case White2_note                   
                                             Clear White2     
                                       Case Red2_note                   
                                             'Clear Red2
                                             HPWM 1, 0, fPwm
                                       Case Red1_note                   
                                             'Clear Red1
                                             HPWM 2, 0, fPwm                                                                               
                                    EndSelect
                                Else                                       ' else for if velo = 0
                                    If PowerOn = 1 Then                    ' dont do lights nor send note-on's if power is off
                                    Select noteAan
                                           Case lowest_note To highest_note          ' notes for Ekla
                                                 HRSOut NoteOn_Status, noteAan, velo
                                                 Set NrNotes 
                                                 'Set noteAan.7      ' note-on flag
                                                 If MotorOn = 0 Then MotorCtrl ()
                                          ' Case 124 To 127 'PH31_note To PH33_note    ' 126 To 127
                                          '       HRSOut NoteOn_Status, noteAan, velo  ' lights on the note boards
                                           Case White1_note
                                                 Set White1                    ' licht aan
                                           Case White2_note                      
                                                 Set White2
                                           Case Red1_note
                                                 'Set Red1     ' can do pwm
                                                 If velo < 127 Then
                                                    velo = velo + velo    ' 8 bits
                                                    HPWM 2,velo, fPwm
                                                 Else
                                                    HPWM 2, 255, fPwm
                                                 EndIf
                                           Case Red2_note
                                                 If velo < 127 Then
                                                    velo = velo + velo
                                                    HPWM 1, velo, fPwm
                                                 Else
                                                    HPWM 1, 255, fPwm
                                                 EndIf
                                                 'Set Red2     ' can do pwm
'                                           Case Lite3_note
'                                                 Set Lite3                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            
                                          ' Case Else
                                          '      Set noteAan
                                          '      GoTo Check_Timers                                               
                                    EndSelect     
                                    EndIf                                    
                                    Set noteAan                 
                                EndIf  
                            EndIf
                       Case Keypres_Status           
                            If notePres = 255 Then
                                notePres = Bytein       ' = note
                            Else
                                pres = Bytein
                                KeyPres ()     
                                ' changing brightness on the led's required this!
                            EndIf
                       Case Control_Status            'this is where the action takes place for controllers
                            If Ctrl = 255 Then
                                Ctrl = Bytein
                            Else
                                value = Bytein
                                Select Ctrl                      ' this is filtered though.
                                    Case 1 To 7
                                         HRSOut Control_Status, Ctrl, value
                                    Case 19
                                         CC19 = value
                                         HRSOut Control_Status, Ctrl, value 
                                    Case 68
                                         CC68 = value         ' no forward!
                                         Motspeed = CC68   
                                    Case 66, 69, 123      
                                         HRSOut Control_Status, Ctrl, value    ' so only relevant controllers are passed to midi out
                                               ' 66 = power switch
                                               ' 67 = motor period
                                               ' 69 = light automation  - also used on PH_board3             
                                               ' 123 = all notes off
                                EndSelect
                                Controller ()  ' handles all controllers relevant for this board
                            EndIf
                       Case ProgChange_Status                 'implemented for presets
                            If prog = 255 Then                'single byte message
                                 prog = Bytein     
                                ' If prog < 8 Then
                                '    HRSOut ProgChange_Status, prog  ' prog changes 0-7 passed.
                                '                                    ' check this with coding on the note boards!  ok 
                                ' EndIf         
                                 Set prog
                            EndIf
                        Case Aftertouch_Status              
                             If aft = 255 Then
                                  aft = Bytein
                                  Aftertouch ()
                                  Set aft
                             EndIf
                       Case Pitchbend_Status                
                            If pblsb = 255 Then
                                pblsb = Bytein
                            Else
                                pbmsb = Bytein
                                Pitchbend ()
                                Set pblsb
                            EndIf
                     
                EndSelect
            EndIf

Check_Timers:
If idx < NrTasks Then          ' we moeten alleen checken wanneer er een timer loopt
   If time >= Nxt Then         ' nagaan of de eerstvolgende timer afgelopen is...
        ' in dit geval is de eerste timer afgelopen en moeten we de juiste aktie ondernemen:
        Set Nxt.31                                    ' timer reset, is immers afgelopen
        ' aan de hand van idx weten we welke timer dit is
        Select idx
               Case 0                             ' used for automatic motor timeout
                                                  ' when no notes are playing
                     Clear MotorOn  '= False: motor stops
                     Clear MotWait  '= False: timer is afgelopen
                     Set TimVals[0]
                     Clear PIR2BITS_TMR3IF     ' clear IRQ flag for timer3
                     Clear T3CON.0
                     Clear PIE2Bits_TMR3IE
                     Clear Motor_Relay
                     Clear BlueLed    
                     idx = SortTimers ()
                     ' a better implementation could decrease motorspeed step by step
                     ' at a rate of ca. 500ms per step.        
               Case 1                             
'                     If notes.1 = 0 Then
'                       Clear White1
'                       Set TimVals[1]
'                     Else
'                       Toggle White1
'                       TimVals[1] = time + Dur5[pres1] 
'                     EndIf
'                     idx = SortTimers ()
               Case 2
'                     If notes.2 = 0 Then
'                       Clear White2
'                       Set TimVals[2]
'                     Else
'                       Toggle White2
'                       TimVals[2] = time + Dur5[pres2] 
'                     EndIf
'                     idx = SortTimers ()   
               'Case Else
               '     ' in dit geval is idx geset
               '     GoTo jumpout               
        EndSelect
   EndIf
      ' beveiliging tegen overflow crashes...
      ' in dit geval loopt er weliswaar een timer!
      If MaxTim = 1 Then
                    Clear time
                  ' Clear notes
                    Set TimVals
      EndIf
Else
      ' idx > LastTask, no timers running, so to avoid overflows, we can reset the loop timer
      ' 26.10.2025: now we switch off power completely (Ubu...):
      ' If MaxTim = 1 Then PowerDown ()   'Clear time 
      If MaxTim = 1 Then Clear time    
EndIf        

' light automation:
    ' this slows down the main loop!!!
If CC69 = 1 Then
    If time.13 <> LiTog  Then     ' time.12 = 98 ms, time.13 = 0.1966 s
        Toggle LiTog
        Select Case NrNotes
            Case 0
                Clear White1
                Clear White2
            Case Else
                Set White1
                Set White2 
        EndSelect
    EndIf
EndIf

              
  Btg PORTB.0     ' average loop-speed: 
                  ' 121kHz with no load. (9.10.2025)
                  ' with autolights in the main loop: 112kHz, 11.10.2025
                  ' with motor control in the main loop: 80kHz
Loop
     

Proc SortTimers (),Byte
        'look up the next smallest timer value in the Timvals array
        ' zoek de de volgende kleinste timer waarde:
        Set Result           ' was idx, byte
        Set Nxt.31           ' nxt is set on entry. - for speed, we just set the highest bit
        For i = 0 To LastTask           
            If TimVals[i] < Nxt Then
               Nxt = TimVals[i]         
               Result = i
            EndIf
        Next i
EndProc

Proc MotorCtrl ()
    'called whenever a motorcondition or parameter changes...
    'if bit 7 is set, we have a note on, else a noteoff
    'this procedure is only called if nrnotes > 0
    'the nrnotes = 0 case should be handled in  the main loop.
        If MotWait = True Then   ' in this case a timer is running to put the motor to sleep
                                 ' and the motor is still running!
            Clear MotWait
            Set TimVals[0]       ' we cancel the wait timer, nothing changes for the motor
            idx = SortTimers ()
        EndIf

                Select Motspeed                     ' set with ctrl.68
                    Case < 1                      ' stop motor
                        Clear PIR2BITS_TMR3IF     ' clear IRQ flag for timer3
                        Clear T3CON.0
                        Clear PIE2Bits_TMR3IE
                        Clear Motor_Relay
                        Clear BlueLed
                        Clear MotorOn
                    Case > 99 '126
                        Clear PIR2BITS_TMR3IF     ' clear IRQ flag for timer3
                        Clear T3CON.0
                        Clear PIE2Bits_TMR3IE
                        Set Motor_Relay
                        Set BlueLed
                        Set MotorOn
                    Case Else
                        If T3CON.0 = 0 Then  
                            Clear PIR2BITS_TMR3IF     ' clear IRQ flag for timer3
                            Set Mtog
                            Clear MotCnt 
                            MotTim = Ton[Motspeed]  
                            Set T3CON.0
                            Set PIE2Bits_TMR3IE    ' enable timer3 interrupt
                            Tim3 = Period          ' 59288 for 5 ms units and period = 500ms.
                        EndIf
                        Set MotorOn
                 EndSelect                                                   
EndProc

Proc KeyPres () 
    ' 06.08.2026: To be implemented for <Ekla> !!!
    Select notePres
        Case lowest_note To highest_note       ' gives compiler problem...
            HRSOut Keypres_Status, notePres, pres
        Case Red1_note
            pres = pres + pres 
            HPWM 2, pres, fPwm
        Case Red2_note
            pres = pres + pres
            HPWM 1, pres, fPwm
        Case Green_Note   ' forward for PH3 board
            HRSOut Keypres_Status, Green_Note, pres
    EndSelect                
    Set notePres '= 255
EndProc

Proc ProgChange ()
    ' not implemented on this board
    Set prog 
EndProc

Proc Pitchbend ()
    ' to do on <Ekla>
    Set pblsb 
EndProc

Proc Aftertouch ()
     'this is the channel aftertouch, affecting any playing note
     'not implemented here.
    Set aft                             
EndProc

Proc Controller ()
    Select Ctrl
           Case 7
                 Volume = value                           ' save setting 
'                 If PowerOn = 1 Then     
'                     Select Motspeed
'                        Case < 1           
'                            Clear T3CON.0
'                            Clear PIE2Bits_TMR3IE
'                            Clear PIR2BITS_TMR3IF   ' clear IRQ flag for timer3
'                            Clear Motor_Relay
'                            Clear BlueLed
'                            If MotWait = 1 Then    ' if timer is running
'                                Set TimVals[0]        ' cancel it
'                                idx = SortTimers ()
'                                Clear MotWait      '  = False
'                            EndIf
'                            Clear MotorOn
'                            Clear Display
'                        Case > 99 
'                            Clear T3CON.0
'                            Clear PIE2Bits_TMR3IE
'                            Clear PIR2BITS_TMR3IF   ' clear IRQ flag for timer3
'                            Set Motor_Relay
'                            Set BlueLed
'                            If MotWait = 1 Then
'                                Set TimVals[0]
'                                idx = SortTimers ()
'                                Clear MotWait        ' = False
'                            EndIf
'                            Set MotorOn
'                            Set Display
'                        Case Else                         
'                            If T3CON.0 = 0 Then 
'                                Clear PIR2BITS_TMR3IF   ' clear IRQ flag for timer3
'                                Set Mtog
'                                MotTim = Ton[Motspeed]
'                                Clear MotCnt
'                                Tim3 = Period          ' 59288 for 5 ms units and period = 500ms.
'                                Set T3CON.0
'                                Set PIE2Bits_TMR3IE    ' enable timer3 interrupt
'                            EndIf
'                            If MotWait = 1 Then
'                                Set TimVals[0]
'                                idx = SortTimers ()
'                                Clear MotWait          ' = False
'                            EndIf
'                            Set MotorOn
'                            Set Display
'                     EndSelect
'                     ' so the motor will allways respond to this controller, even when nrnotes = 0.
'                     ' the controller allways clears the sleeptimer!
                 EndIf
                 oldVolume = Volume
           Case 66
                'on/off for the robot
                ' this should also go to the note boards!
                If value = 0 Then
                    PowerDown ()
                Else
                    Set Power_Relay      ' stroom AAN via optorelais
                    Set GreenLed 
                    Set PowerOn      ' CC66.0  =1
                    Set CC69         ' autolights on
                EndIf
           Case 67
                ' sets the period for motor control
                CC67 = value
                Select CC67   
                    Case 0
                        Period = Period_default  ' = Period5
                    Case 1
                        Period = Period10   ' 10ms and 1 second
                    Case 2
                        Period = Period5    ' 5 ms and 500 ms
                    Case 3
                        Period = Period25   ' 2.5 ms and 250 ms
                    Case 4
                        Period = Period125   ' 125ms  - tested - dangerous: ubu motor runs very hot.
                    Case Else
                        ' disregard controller
                EndSelect
           Case 68
                 CC68 = value
                 If Motspeed <> CC68 Then
                    Motspeed = CC68
                    MotorCtrl ()
                 EndIf
           Case 69 
                If value > 0 Then 
                    CC69 = True          ' on this board we have but 1 light program.
                    Clear LiTog
                    Set CC69
                Else
                    Clear CC69           '= False
                    Clear White1
                    Clear White2
                EndIf
           Case 123
                ' all notes/lites off - no controller resets
                Clear NrNotes       
                HPWM 1, 0, fPwm
                HPWM 2, 0, fPwm
                Clear White1
                Clear White2  
                idx = SortTimers () 
    EndSelect
    Set Ctrl                                  'mandatory reset
EndProc


Proc PowerDown ()
               Clear Power_Relay      ' power and motor off
               Clear Motor_Relay
               Clear PowerOn   'CC66.0  =0               
               Clear MotorOn          ' MotorFlags.1
               Set TimVals            ' cancel all timers
               Clear time
               Clear NrNotes
               Clear GreenLed
               Clear BlueLed
               Clear White1             
               Clear White2
              ' Clear Lite3
               HPWM 2,0,fPwm
               HPWM 1,0,fPwm
               Clear T3CON.0
               Clear PIE2Bits_TMR3IE
               Clear MotWait          ' MotorFlags.0
               idx = SortTimers ()    ' idx should be 255 here.
EndProc

Proc Dur_Lookup ()
    'lookup for flashing lights:
    Set Dur5[0]         ' not used 
    Dur5[127] =  17218  
    j = Dur5[127] / 127
    For i = 1 To 127
        Dur5[i] = Dur5[0] - (j * i)
    Next i                                      
EndProc  

Proc Motor_PWM_Lookup ()
    ' lookup for <Ubu>, using timer3
    ' 10.10.2025, counting in 10ms or 5ms units
    ' duty cycle values
    ' 100 x 10ms = period 1s
    ' 100 x 5ms  = period 500ms 
    ' 100 x 2.5ms = period 250ms or 4Hz
    ' 100 x 1.25ms = period 125ms or 8Hz
    Ton[0] = 0
    Toff[0] = 100
    For i  = 1 To 99 Step 1  ' step 2 for 5ms units
        Ton[i] = i
        Toff[i] = 100 - i
    Next i 
    For i = 100 To 127
        Ton[i] = 100
        Toff[i] = 0
    Next i
EndProc

Proc Motor_PWM_Lookup_4Hz ()
' NOT USED HERE!
' lookup for <Ubu>, using timer3
' counting in 10ms or 5ms units
' duty cycle values
' 25 x 10ms = period 1s
' 25 x 5ms  = period 500ms 
' 25 x 2.5ms = period 250ms
' here we should have only 25 steps
Clear Ton
Clear Toff
Ton[0] = 0
Ton[1] = 0
Ton[2] = 0
Ton[3] = 0
Toff[0] = 100
Toff[1] = 100
Toff[2] = 100
Toff[3] = 100
For i  = 4 To 99 Step 4  ' step 4 for 250ms periods
    Ton[i] = i
    Ton[i+1] = i
    Ton[i+2] = i
    Ton[i+3] = i
    Toff[i] = 100 - i
    Toff[i+1] = Toff[i]
    Toff[i+2] = Toff[i]
    Toff[i+3] = Toff[i]
Next i 
For i = 100 To 127
    Ton[i] = 100
    Toff[i] = 0
Next i
EndProc

'[EOF]

