Saturday, 14 February 2015

Code for displaying scales as binary on a PIC 18F4550 microcontroller

Here's the code so far, for selecting a scale/chord and displaying all the associated notes across a keyboard. It's by no means complete - there's no key-scanning to find out which keys are pressed, but you can dial in a chord or scale, and set the key, and see what the binary output would look like.

Define CONFIG1L = 0x00
Define CONFIG1H = 0x0c
Define CONFIG2L = 0x18
Define CONFIG2H = 0x1e
Define CONFIG3L = 0x00
Define CONFIG3H = 0x01
Define CONFIG4L = 0x80
Define CONFIG4H = 0x00
Define CONFIG5L = 0x0f
Define CONFIG5H = 0xc0
Define CONFIG6L = 0x0f
Define CONFIG6H = 0xe0
Define CONFIG7L = 0x0f
Define CONFIG7H = 0x40

Define CLOCK_FREQUENCY = 20
AllDigital

declarations:

     Define LCD_BITS = 4 'allowed values are 4 and 8 - the number of data interface lines
     Define LCD_DREG = PORTA
     Define LCD_DBIT = 0 '0 or 4 for 4-bit interface, ignored for 8-bit interface
     Define LCD_RSREG = PORTA
     Define LCD_RSBIT = 4
     Define LCD_EREG = PORTA
     Define LCD_EBIT = 5
     Define LCD_RWREG = PORTA 'set to 0 if not used, 0 is default
     Define LCD_RWBIT = 0 'set to 0 if not used, 0 is default

     Define LCD_COMMANDUS = 2000 'delay after LCDCMDOUT, default value is 5000
     Define LCD_DATAUS = 50 'delay after LCDOUT, default value is 100
     Define LCD_INITMS = 2 'delay used by LCDINIT, default value is 100
     'the last three Define directives set the values suitable for simulation; they should be omitted for a real device

     Dim state As Byte
     Dim mode As Byte
     Dim output_buffer(8) As Byte 'we can display up to 64 leds
     Dim scale_notes(8) As Byte 'we can store up to 64 notes
     Dim scale_pattern(3) As Byte 'each 24 note (two octave) pattern repeats over 3 bytes
     Dim req_pattern_type As Byte

     Symbol rotary_pulse = PORTE.1
     Symbol rotary_direction = PORTE.0
     Symbol rotary_button = PORTE.2

     Dim last_pulse As Bit
     Dim curr_direction As Byte
     Dim curr_button As Bit

     Dim current_key As Byte
     Dim notes_pattern As Long '(4 byte value, 3 bytes + 1st byte repeated)
     Dim pattern_mask As Long
     Dim chord_scale_type As Byte

     Dim i As Byte
     Dim j As Byte
     Dim k As Byte
     Dim h As Word

initialise:

     state = 0
     req_pattern_type = 0
     notes_pattern = 0
     current_key = 0 '(0=C, 1=C#, 2=D, 3=Eb, 4=E, 5=F, 6=F#, 7=G, 8=G#, 9=A, 10=Bb, 11=B)
     last_pulse = rotary_pulse
     Lcdinit

     INTCON2.RBPU = 0 'enable pullups on PORTB
     PORTE.7 = 1 'enable pullups on PORTD

     ConfigPin PORTB = Input
     ConfigPin PORTD = Input
     ConfigPin PORTE = Input 'portE has external pull-ups on E0-E2

loop:

     Select Case state


           Case 0
           Lcdcmdout LcdClear
           Lcdout "Keyboard"
           Lcdcmdout LcdLine2Home
           Lcdout "Controller v1.0"
           WaitMs 1500
           mode = 0
           state = 1

           Case 1
           Lcdcmdout LcdClear
           Lcdout "Select mode:"
           Lcdcmdout LcdLine2Home
           Select Case mode
                 Case 0
                 Lcdout "Single chord "

                 Case 1
                 Lcdout "All chord notes "

                 Case 2
                 Lcdout "Full scale "

                 Case 3
                 Lcdout "Walking bassline"

                 Case 4
                 Lcdout "MIDI input mode "
           EndSelect
           state = 2


           Case 2
           'wait for the rotary dial
           curr_direction = 0
           curr_button = 0
           Gosub read_rotary

           If curr_direction = 1 Then
                 If mode > 0 Then mode = mode - 1 Else mode = 4
                 state = 1
           Endif

           If curr_direction = 2 Then
                 mode = mode + 1
                 If mode > 4 Then mode = 0
                 state = 1
           Endif

           If curr_button = 1 Then
                 state = 3
           Endif


           Case 3
           Lcdcmdout LcdClear
           If mode = 0 Then
                 Lcdout "Chord type:"
                 chord_scale_type = 0
                 state = 4
           Endif
           If mode = 1 Then
                 Lcdout "Chord type:"
                 chord_scale_type = 0
                 state = 4
           Endif
           If mode = 2 Then
                 Lcdout "Scale type:"
                 chord_scale_type = 100
                 state = 10
           Endif
           If mode = 3 Then
                 Lcdout "Walking bassline"
                 chord_scale_type = 120
                 state = 20
           Endif
         
           If mode = 4 Then
                 Lcdout "MIDI mode"
                 state = 200
           Endif


           Case 4
           Lcdcmdout LcdLine2Home
           Select Case chord_scale_type
                 Case 0
                 Lcdout "Major chord "

                 Case 1
                 Lcdout "Minor chord "

                 Case 2
                 Lcdout "Dominant 7th "

                 Case 3
                 Lcdout "Major 7th chord "

                 Case 4
                 Lcdout "Minor 7th chord "
           EndSelect
           state = 5


           Case 5
           'wait for the rotary dial
           curr_direction = 0
           curr_button = 0
           Gosub read_rotary

           If curr_direction = 1 Then
                 If chord_scale_type > 0 Then chord_scale_type = chord_scale_type - 1 Else chord_scale_type = 4
                 state = 4
           Endif

           If curr_direction = 2 Then
                 chord_scale_type = chord_scale_type + 1
                 If chord_scale_type > 4 Then chord_scale_type = 0
                 state = 4
           Endif

           If curr_button = 1 Then
                 Lcdcmdout LcdClear
                 Lcdout "Select chord:"
                 state = 6
           Endif
           Gosub read_from_keys


           Case 6
           Lcdcmdout LcdLine2Home
           Gosub display_key
           If chord_scale_type = 0 Then Lcdout "major "
           If chord_scale_type = 1 Then Lcdout "minor "
           If chord_scale_type = 1 Then Lcdout "dominant 7th "
           If chord_scale_type = 3 Then Lcdout "major 7th "
           If chord_scale_type = 4 Then Lcdout "minor 7th "
           Gosub display_output
           state = 7


           Case 7
           'wait for the rotary dial
           curr_direction = 0
           curr_button = 0
           Gosub read_rotary

           If curr_direction = 1 Then
                 If current_key > 0 Then current_key = current_key - 1 Else current_key = 11
                 state = 6
           Endif

           If curr_direction = 2 Then
                 current_key = current_key + 1
                 If current_key > 11 Then current_key = 0
                 state = 6
           Endif

           If curr_button = 1 Then
                 state = 1
           Endif
           Gosub read_from_keys



           Case 10
           Lcdcmdout LcdLine2Home
           Select Case chord_scale_type
                 Case 100
                 Lcdout "Major scale "

                 Case 101
                 Lcdout "Minor scale "

                 Case 102
                 Lcdout "Penatonic scale "

                 Case 103
                 Lcdout "Blues scale "
           EndSelect
           state = 11

           Case 11
           'wait for the rotary dial
           curr_direction = 0
           curr_button = 0
           Gosub read_rotary

           If curr_direction = 1 Then
                 If chord_scale_type > 100 Then chord_scale_type = chord_scale_type - 1 Else chord_scale_type = 103
                 state = 10
           Endif

           If curr_direction = 2 Then
                 chord_scale_type = chord_scale_type + 1
                 If chord_scale_type > 103 Then chord_scale_type = 100
                 state = 10
           Endif

           If curr_button = 1 Then
                 Lcdcmdout LcdClear
                 Lcdout "Select scale:"
                 state = 12
           Endif


           Case 12
           Lcdcmdout LcdLine2Home
           Gosub display_key
           If chord_scale_type = 100 Then Lcdout "major scale "
           If chord_scale_type = 101 Then Lcdout "minor scale "
           If chord_scale_type = 102 Then Lcdout "pentatonic "
           If chord_scale_type = 103 Then Lcdout "blues scale "
           Gosub display_output
           state = 13


           Case 13
           'wait for the rotary dial
           curr_direction = 0
           curr_button = 0
           Gosub read_rotary

           If curr_direction = 1 Then
                 If current_key > 0 Then current_key = current_key - 1 Else current_key = 11
                 state = 12
           Endif

           If curr_direction = 2 Then
                 current_key = current_key + 1
                 If current_key > 11 Then current_key = 0
                 state = 12
           Endif

           If curr_button = 1 Then
                 state = 1
           Endif


           Case 20
           Lcdcmdout LcdLine2Home
           Lcdout "Walking bass: "
           Gosub display_key
           Gosub display_output
           state = 21


           Case 21
           'wait for the rotary dial
           curr_direction = 0
           curr_button = 0
           Gosub read_rotary

           If curr_direction = 1 Then
                 If current_key > 0 Then current_key = current_key - 1 Else current_key = 11
                 state = 20
           Endif

           If curr_direction = 2 Then
                 current_key = current_key + 1
                 If current_key > 11 Then current_key = 0
                 state = 20
           Endif

           If curr_button = 1 Then
                 state = 1
           Endif
           Gosub read_from_keys




     EndSelect

     'now if the output buffers have changed since last time, update the shift register(s)

     Goto loop
     End

set_scale_pattern:

     Select Case req_pattern_type
           Case 0 'major chord (10001001 00001000 10010000)
           scale_pattern(0) = 10001001b
           scale_pattern(1) = 00001000b
           scale_pattern(2) = 10010000b

           Case 1 'minor chord (10010001 00001001 00010000)
           scale_pattern(0) = 10010001b
           scale_pattern(1) = 00001001b
           scale_pattern(2) = 00010000b

           Case 2 'dominant 7th chord (10001001 00101000 10010010)
           scale_pattern(0) = 10001001b
           scale_pattern(1) = 00101000b
           scale_pattern(2) = 10010010b

           Case 3 'major 7th chord (10001001 00011000 10010001)
           scale_pattern(0) = 10001001b
           scale_pattern(1) = 00011000b
           scale_pattern(2) = 10010001b

           Case 4 'minor 7th chord (10010001 00011001 00010001)
           scale_pattern(0) = 10010001b
           scale_pattern(1) = 00011001b
           scale_pattern(2) = 00010001b

           Case 5 'major scale (10101101 01011010 11010101)
           scale_pattern(0) = 10101101b
           scale_pattern(1) = 01011010b
           scale_pattern(2) = 11010101b

           Case 6 'minor scale (10110101 10101011 01011010)
           scale_pattern(0) = 10110101b
           scale_pattern(1) = 10101011b
           scale_pattern(2) = 01011010b

           Case 7 'minor pentatonic scale (10010101 00101001 01010010)
           scale_pattern(0) = 10010101b
           scale_pattern(1) = 00101001b
           scale_pattern(2) = 01010010b

           Case 8 'minor blues scale (10010111 00101001 01110010)
           scale_pattern(0) = 10010111b
           scale_pattern(1) = 00101001b
           scale_pattern(2) = 01110010b

           Case 10 'walking bass line (10001001 01101000 10010110)
           scale_pattern(0) = 10001001b
           scale_pattern(1) = 01101000b
           scale_pattern(2) = 10010110b

     EndSelect

Return

read_rotary:
     If rotary_pulse = 0 And last_pulse <> 0 Then
           'on any edge (rising or falling) check to see
           'which way the rotary is being turned
           If rotary_direction = 0 Then
                 curr_direction = 2
                 Else
                 curr_direction = 1
           Endif
           WaitMs 25
     Endif
     last_pulse = rotary_pulse

     If rotary_button = 0 Then
           'debounce:
           WaitMs 10
           If rotary_button = 0 Then
                 While rotary_button = 0
                       'wait for release
                 Wend
                 curr_button = 1
           Endif
     Endif
Return

display_key:
     If current_key = 0 Then Lcdout "C "
     If current_key = 1 Then Lcdout "C# "
     If current_key = 2 Then Lcdout "D "
     If current_key = 3 Then Lcdout "Eb "
     If current_key = 4 Then Lcdout "E "
     If current_key = 5 Then Lcdout "F "
     If current_key = 6 Then Lcdout "F# "
     If current_key = 7 Then Lcdout "G "
     If current_key = 8 Then Lcdout "G# "
     If current_key = 9 Then Lcdout "A "
     If current_key = 10 Then Lcdout "Bb "
     If current_key = 11 Then Lcdout "B "

Return

display_output:

     'this is where we actually light up the LEDs
     If chord_scale_type >= 0 And chord_scale_type <= 4 Then req_pattern_type = chord_scale_type
     If chord_scale_type >= 100 And chord_scale_type <= 103 Then req_pattern_type = chord_scale_type - 95
     If chord_scale_type = 120 Then req_pattern_type = 10
         
     Gosub set_scale_pattern

     'load the pattern into a 4-byte Long, repeating the first byte at the fourth (reading from right to left)
     notes_pattern.4B = scale_pattern(0)
     notes_pattern.3B = scale_pattern(1)
     notes_pattern.HB = scale_pattern(2)
     notes_pattern.LB = scale_pattern(0)

     'now shift the pattern into the appropriate key
     If current_key <= 8 Then
           'bit-shift the entire pattern to the right
           If current_key > 0 Then notes_pattern = ShiftRight(notes_pattern, current_key)
           Else
           'to raise by 9 or more steps, we need to "flatten" by shifting left
           'so a raise of 9 steps is the same as a lowering by 3, raising 10 is the same as lowering by 2,
           'raising by 11 is the same a lowering by one (raising by 12 is just an octave higher)
           k = 12 - current_key
           notes_pattern = ShiftLeft(notes_pattern, k)

           'but now we could have blanks in the lowest (fourth) byte, but since this is always
           'just a copy of the highest (first) byte, simply copy it over
           k = notes_pattern.4B
           notes_pattern.LB = k
     Endif

     'if we've shifted left, this following function won't do anything
     'but if we've shifted right, we could potentially have some leading blanks/zeros,
     'so we need to OR the fourth byte with the first, to fill in any missing notes
     k = notes_pattern.4B
     j = notes_pattern.LB
     k = k Or j
     notes_pattern.4B = k

     'if we only want to display a single chord, we only want to display 11 intervals,
     'starting at the root note of the key we're in (maybe do the same for walking bass line?)
     If mode = 0 Or mode = 3 Then
           pattern_mask = 0
           pattern_mask.4B = 11111111b
           pattern_mask.3B = 11110000b

           If current_key > 0 Then pattern_mask = ShiftRight(pattern_mask, current_key)
           notes_pattern = notes_pattern And pattern_mask
     Endif


     'now put the pattern out onto the LEDs

     '(for now we'll write it out to the LCD)
     Lcdcmdout LcdLine1Home
     k = notes_pattern.4B
     Gosub show_k_as_hex
     Lcdout " "
     k = notes_pattern.3B
     Gosub show_k_as_hex
     Lcdout " "
     k = notes_pattern.HB
     Gosub show_k_as_hex
     Lcdout " "

Return

read_from_keys:
     'here we do some multiplexing to see which keys (if any) have been pressed/released
     'since the last scan. The keyboard hardware already has pushbutton type pads in groups
     'of eight, so multiplexing seems the obvious way to go.


Return

show_k_as_hex:
     j = k And 11110000b
     j = ShiftRight(j, 4)
     Gosub show_j_as_hex
     j = k And 00001111b
     Gosub show_j_as_hex
Return

show_j_as_hex:
     If j < 10 Then Lcdout #j
     If j = 10 Then Lcdout "A"
     If j = 11 Then Lcdout "B"
     If j = 12 Then Lcdout "C"
     If j = 13 Then Lcdout "D"
     If j = 14 Then Lcdout "E"
     If j = 15 Then Lcdout "F"
Return
   
     

While it's very much a work in progress, this should be enough to get you started with the basic chord and scale types (major/minor/pentatonic etc). Other scales and modes can easily be added in future, as they are required.

Once we've got the hardware wired up, we may even add in some way of reading which chord is being played on the lower deck, and have the appropriate notes for the corresponding scale appear on the upper deck. That's a way off yet, but this code is at least a start....