list p=16f88 errorlevel -306 ; no page boundary warnings errorlevel -305 ; no default destination messages errorlevel -302 ; no bank 0 warnings errorlevel -202 ; no 'argument out of range' warning ; Configuration Bits: ; 32 1098 7654 3210 <-- bit number ; 11 1111 0001 1000 <-- value ; Hex: 3f18 ; For the 16F88, which has two configuration words, we must specify which word we are writing. __config 0x2007 , 0x3f18 ; Config word one. All defaults, but no BOR, Power On Timer enabled, MCLR tied to Vdd internally __config 0x2008 , 0x3ffc ; Special features disabled. include org 0 ; Compiler instructions ; Connect LEDs to PORTB 0,1,6,7 led1 equ b'00000001' led2 equ b'00000010' led3 equ b'01000000' led4 equ b'10000000' RawCount equ 20h BeatCount equ 21h BarCount equ 22h MsgBuffer equ 23h CalcBuffer equ 24h start ; Set the internal oscillator frequency bsf STATUS, RP0 movlw b'01110110' ; Internal frequency = 8MHz, OSTS uses INTOSC. movwf OSCCON ; Set PORTA as output, PORTB as output except for the RX pin clrf ANSEL ; Turn Off Analog Converter clrf TRISA ; Make PORTA output clrf TRISB ; Make PORTB output bsf TRISB, 2 ; Set up UART to 31.250 kBaud for MIDI. ; We're using the internal 8MHz oscillator clock. ; Baud low speed, X=3 (gives exact match) movlw 3h movwf SPBRG bcf TXSTA, BRGH ; Low Speed bcf TXSTA, SYNC bcf STATUS, RP0 bsf RCSTA, SPEN ; Enable Serial Port bsf RCSTA, CREN ; Enable Continuous Receive ; Initialization of the setup reset clrf PORTB ; Turn off all beat LEDs clrf PORTA ; Set 7 seg to zero. movlw d'25' ; Set the Raw Count to 24 movwf RawCount clrf BarCount clrf BeatCount ; There are two options for counting the beats: ; 1. Using the MIDI Timing Clock ; ; MIDI Sends the Timing Clock message 24 times per quarter note. ; We must therefore count to 24 before changing the beat ; And 4 beats to increase the bar ; ; The Timing Clock message is b'11111000' ; ; At the start of the sequence, the message b'11111010' will be sent. ; We can use this to reset the counter? This seems like a mess. ; ; ; 2. Using the Song Position Pointer ; This increases 4 times per beat and is a 14-bit counter. ; Max 16384 MIDI beats = = 4096 music beats = 1024 bars > 30 minutes @ 120 bpm. ; ; We can then decide to use absolute timing or relative (counting). ; Let's see where we get with absolute timing. ; ; The Song Position Counter message is b'11110010' = 0F2h ; Followed by the LSB (7 bit value) and the MSB (7 bit value) ; So we get 128 16th notes = 32 beats = 4 bars per LSB cycle. ; We are only interested in every 4 of these messages. ; We could therefore do two RRFs on this register and compare to the previous one. ; ; For the bar counter, we are interested in every 16 of these messages. ; We could do 4 RRFs on this register and read the bar count. ; This means we have 8 bars on this LSB register, which is what we need. ; ; MIDI devices will send this thing when they scrub position. wait_for_start btfss PIR1, RCIF goto wait_for_start movf RCREG, W movwf MsgBuffer sublw 0FA ; Is this a Start message? btfsc STATUS, Z goto get_started movf MsgBuffer, W sublw 0FB ; Is it a Continue message? btfsc STATUS, Z goto loop movf MsgBuffer, W sublw 0F2 ; Is it a Song Position Pointer message? btfsc STATUS, Z goto wait_for_position goto wait_for_start ; If it is neither of these, ignore. wait_for_position ; If we have received a Song Position Counter, ; we will now receive two bytes that represent the song position. ; The resolution is 4 per quarter note, so 16 per bar. ; We use this to calculate where we are by shifting the bits to the right. ; Since we only need the LSB (which comes first), we can safely ignore the second one. ; This is a 7-bit value, so the maximum can be 127. ; This works out to exactly 8 bars in this 7-bit register, ; so it's easy to convert to beats and bars in this case. btfss PIR1, RCIF goto wait_for_position movf RCREG, W movwf MsgBuffer movwf CalcBuffer ; Derive the bar number by dividing by 16: rrf CalcBuffer, f rrf CalcBuffer, f rrf CalcBuffer, f rrf CalcBuffer, W andlw b'00001111' ; Mask whatever was carried in movwf BarCount ; Copy to BarCount sublw 8 ; Are we past bar 8? btfsc STATUS, Z clrf BarCount ; If so, reset bar count. incf BarCount, W movwf PORTA ; Immediately display on the indicator ; Get the beat position ; The Song Position Counter must be multiplied by 6 to get the Count as we know it in ; the clock signal. The clock uses 24 per quarter, the Song Position just 4 per quarter. ; We first have to get the beat we are in, ; then reconcile the remainder of the position to the RawCount. ; Get the current beat: divide by 4. movf MsgBuffer, W andlw b'00001111' movwf CalcBuffer rrf CalcBuffer, f rrf CalcBuffer, W andlw b'00111111' movwf BeatCount ; Get the position within the beat. movf MsgBuffer, W andlw b'00000011' ; Only the remainder part movwf CalcBuffer bcf STATUS, C rlf CalcBuffer, f ; Multiply by two and update CalcBuffer rlf CalcBuffer, W ; Multiply by two and keep in W addwf CalcBuffer, W ; Now we have the 24-step raw count in W. Subtract from 24. sublw d'24' btfsc STATUS, Z movlw d'24' addlw 1 movwf RawCount call get_beat ; Display the new beat count on the LEDS movwf PORTB goto wait_for_start get_started movlw d'25' ; Set the Raw Count to 25 movwf RawCount clrf BarCount clrf BeatCount movlw led1 movwf PORTB ; Display the new Beat Count incf BarCount, W movwf PORTA ; Display the new Bar Count goto loop loop ; We will now receive 24 MIDI clocks per beat. btfss PIR1, RCIF ; Check if there is a new MIDI message goto loop movf RCREG, W movwf MsgBuffer ; Copy the MIDI message to the buffer sublw 0FC ; Is it a Stop message? btfsc STATUS, Z goto wait_for_start movf MsgBuffer, W ; Read MIDI message from the buffer sublw 0F8 ; Is this a MIDI clock beat? btfss STATUS, Z goto loop ; If it is neither a Stop nor a Clock message, go back and wait for more. decf RawCount, f ; Counting down from 24 every time. movf RawCount, W sublw d'18' btfsc STATUS, C clrf PORTB ; If we are at a quarter of the beat, clear the leds movf RawCount, W btfss STATUS, Z goto loop ; If we have not reached the next beat, go back to the loop movlw d'24' ; Reset the Raw Counter movwf RawCount incf BeatCount, f ; Increase the Beat Count movf BeatCount, W sublw 4 ; Are we past beat 4? btfsc STATUS, Z call next_bar call get_beat movwf PORTB goto loop next_bar clrf BeatCount incf BarCount, f movf BarCount, W sublw 8 btfsc STATUS, Z clrf BarCount incf BarCount, W movwf PORTA return get_beat movf BeatCount, W addwf PCL, f retlw led1 retlw led2 retlw led3 retlw led4 end