;; Software to control my laser scanner. ;; C. Scott Ananian (MAS863 assignment 3) ;; (c) 2002 processor p16f876 ; EXPAND include "p16f876.inc" ;; DEBUGGING OPTIONS ;#define DEBUG_FORCE_VERTICAL_LINES ;#define DEBUG_NO_ALPHABET_LOOKUP ;#define DEBUG_ALL_MESSAGE_CHARS_ZERO ;#define DEBUG_DISABLE_SCROLL ;#define DEBUG_8MHz_CLOCK ;; define a macro to ensure safe table math (no overflows of PCL) padtab macro tablesize if (($ & 0xFF) > (0xFF-tablesize)) nop padtab tablesize endif endm ;; note that 70h-7fh is the same no matter what bank we're in. W_TEMP EQU H'7F' ; W storage during interrupt. STATUS_TEMP EQU H'7E' ; STATUS storage during interrupt PCLATH_TEMP EQU H'75' ; PCLATH storage during interrupt CUR_PERIOD_L EQU H'7C' ; the current rotation quarter-period CUR_PERIOD_H EQU H'7D' ; " " (high byte) SCAN_START_L EQU H'7A' ; the starting time of this scan SCAN_START_H EQU H'7B' ; " " (high byte) WHICH_SCAN EQU H'79' ; keep track of 1 of 4 rising/falling edges SCAN_STATE EQU H'78' ; bit 0: "scan start" flag. ; bit 1: temp for table lookup LOOKUP_TEMP EQU H'77' ; temporary for use of table lookup routines SCROLL_TEMP EQU H'76' ; temporary for use during do_scroll interrupt SCROLL_SPEED EQU H'73' ; scroll speed. 1=fastest, 255=slowest ; 0=no scroll CURRENT_SCROLL EQU H'72' ; # of interrupts until next scroll event. DEBUG_TEMP EQU H'71' ; used for serial debugging. DEBUG_TEMP2 EQU H'70' ;; this stuff is only in bank 0. CURCHARNUM EQU H'20' ; current character # in message. CURLINENUM EQU H'21' ; current line # we're scanning CURDOTS EQU H'22' ; a byte w/ the current pixels we're outputting ; self-delimiting; bit 7 is the 'next' dot. LINE_TIME_L EQU H'23' ; timer ticks for one line LINE_TIME_H EQU H'24' ; (1/6 of 4*CUR_PERIOD_H/L) LINE_START_L EQU H'25' ; last line start time. LINE_START_H EQU H'26' ; last line start time. LINECHARNUM EQU H'27' ; which char starts the line LINEDOTOFF EQU H'28' ; how many dots into that char do we start NUM_PIXELS EQU H'29' ; number of pixels left in the line. PIXEL_TIME EQU H'30' ; timer ticks for one pixel TEMP_L EQU H'31' ; next start-of-bit time. TEMP_H EQU H'32' MESSAGE_LOW EQU H'33' ; starting address of current message MESSAGE_HIGH EQU H'34' MESSAGE_CHARS EQU H'35' ; number of characters in this message org 0x4 goto tach_int ; tachometer edge interrupt. org 0x80 goto start ;; ** THIS ARE THE MESSAGES WE'RE GOING TO DISPLAY ** NUM_MESSAGES EQU D'7' ; how many of them do we have? MESSAGE1_CHARS EQU D'6'*D'12' ; number of characters in this message. message1: ;; 12 characters per line. dt 'T', 'A', 'K', 'E', ' ', 'M', 'A', 'S', '8', '6', '3', '!' dt ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' dt 'C', '.', ' ', 'S', 'C', 'O', 'T', 'T', ' ', 'A', 'N', 'A' dt 'N', 'I', 'A', 'N', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' dt 'A', 'S', 'S', 'I', 'G', 'N', 'M', 'E', 'N', 'T', ' ', '3' dt ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' MESSAGE2_CHARS EQU D'1'*D'12' ; number of characters in this message. message2: dt 'H', 'I', ' ', 'K', 'A', 'T', 'H', 'Y', '!', ' ', ' ', ' ' MESSAGE3_CHARS EQU D'1'*D'12' ; number of characters in this message. message3: dt 'H', 'i', ' ', 'M', 'o', 'm', '!', ' ', ' ', ' ', ' ', ' ' MESSAGE4_CHARS EQU D'1'*D'12' ; number of characters in this message. message4: dt 'P', 'U', 'S', 'H', ',', ' ', 'N', 'V', ' ', ' ', ' ', ' ' MESSAGE5_CHARS EQU D'6'*D'12' ; number of characters in this message. message5: dt 'C', '.', ' ', 'S', 'c', 'o', 't', 't', ' ', 'A', 'n', 'a' dt 'n', 'i', 'a', 'n', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' dt 'c', 's', 'c', 'o', 't', 't', '@', 'c', 's', 'c', 'o', 't' dt 't', '.', 'n', 'e', 't', ' ', ' ', ' ', ' ', ' ', ' ', ' ' dt 'h', 't', 't', 'p', ':', '/', '/', 'c', 's', 'c', 'o', 't' dt 't', '.', 'n', 'e', 't', '/', ' ', ' ', ' ', ' ', ' ', ' ' MESSAGE6_CHARS EQU D'12'*D'6' ; number of characters in this message. message6: dt 'I', 'S', ' ', 'I', 'T', ' ', 'N', 'O', 'T', ' ', 'N', 'I' dt 'F', 'T', 'Y', '?', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' dt 'S', 'L', 'U', 'G', 'G', 'Y', ' ', 'F', 'R', 'E', 'E', 'L' dt 'A', 'N', 'C', 'E', ':', ' ', 'h', 't', 't', 'p', ':', '/' dt '/', 'w', 'w', 'w', '.', 's', 'l', 'u', 'g', 'g', 'y', '.' dt 'c', 'o', 'm', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' MESSAGE7_CHARS EQU D'12'*D'21' ; number of characters in this message. message7: dt ' ', ' ', 'A', 'r', 't', 'i', 'c', 'l', 'e', ' ', '1', ',' dt ' ', 'S', 'e', 'c', 't', 'i', 'o', 'n', ' ', '8', ',', ' ' dt 'C', 'l', 'a', 'u', 's', 'e', ' ', '8', ':', ' ', 'T', 'h' dt 'e', ' ', 'C', 'o', 'n', 'g', 'r', 'e', 's', 's', ' ', 's' dt 'h', 'a', 'l', 'l', ' ', 'h', 'a', 'v', 'e', ' ', 'p', 'o' dt 'w', 'e', 'r', ' ', '.', '.', '.', ' ', 't', 'o', ' ', 'p' dt 'r', 'o', 'm', 'o', 't', 'e', ' ', 't', 'h', 'e', ' ', 'p' dt 'r', 'o', 'g', 'r', 'e', 's', 's', ' ', 'o', 'f', ' ', 's' dt 'c', 'i', 'e', 'n', 'c', 'e', ' ', 'a', 'n', 'd', ' ', 'u' dt 's', 'e', 'f', 'u', 'l', ' ', 'a', 'r', 't', 's', ',', ' ' dt 'b', 'y', ' ', 's', 'e', 'c', 'u', 'r', 'i', 'n', 'g', ' ' dt 'f', 'o', 'r', ' ', 'l', 'i', 'm', 'i', 't', 'e', 'd', ' ' dt 't', 'i', 'm', 'e', 's', ' ', 't', 'o', ' ', 'a', 'u', 't' dt 'h', 'o', 'r', 's', ' ', 'a', 'n', 'd', ' ', 'i', 'n', 'v' dt 'e', 'n', 't', 'o', 'r', 's', ' ', 't', 'h', 'e', ' ', 'e' dt 'x', 'c', 'l', 'u', 's', 'i', 'v', 'e', ' ', 'r', 'i', 'g' dt 'h', 't', ' ', 't', 'o', ' ', 't', 'h', 'e', 'i', 'r', ' ' dt 'r', 'e', 's', 'p', 'e', 'c', 't', 'i', 'v', 'e', ' ', 'w' dt 'r', 'i', 't', 'i', 'n', 'g', 's', ' ', 'a', 'n', 'd', ' ' dt 'd', 'i', 's', 'c', 'o', 'v', 'e', 'r', 'i', 'e', 's', '.' dt ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' ;; this is a table of line offsets (in pixels) to compensate for ;; the fact that the hexagonal mirrors aren't precisely hexagonal. get_line_offset: movlw HIGH line_offset_table movwf PCLATH ; load PCLATH with MSB of adjust_table movf CURLINENUM, W padtab D'7' ; ensure that we're not too near page boundary. addwf PCL, F line_offset_table: dt D'7' ; for line 0 dt D'5' ; for line 1 dt D'3' ; for line 2 dt D'2' ; for line 3 dt D'1' ; for line 4 dt D'0' ; for line 5 ;; This is where the code actually starts. start: ;; tachometer is hooked up to CCP1 and CCP2 ;; laser modulation is PB0 ;; pushbutton on RC5 ;; set up our state variables clrf STATUS ; bank 0 clrf WHICH_SCAN clrf SCAN_STATE movlw 0x0c ; start twelve chars into message. movwf LINECHARNUM clrf LINEDOTOFF ; no dot offset. movlw 0x18 ; medium-fast scroll speed movwf SCROLL_SPEED movwf CURRENT_SCROLL ;; set up ports. clrf PORTB bsf STATUS, RP0 ; bank 1 movlw B'11110000' movwf TRISB ; PB0-3 are outputs; PB4-7 are inputs movlw B'00100110' ; RC5/CCP1/CCP2 are inputs movwf TRISC IFNDEF DEBUG_DISABLE_SCROLL ;; set up timer 0 movlw B'01010101' ; 1:64 prescaler; port b pullups enabled movwf OPTION_REG ENDIF ; DEBUG_DISABLE_SCROLL ;; set up timer 1 bcf STATUS, RP0 ; bank 0 movlw B'00000100' ; ccp1 captures on falling edges movwf CCP1CON movlw B'00000101' ; ccp2 captures on rising edges movwf CCP2CON movlw B'00010101' ; enables timer1 in timer mode. movwf T1CON ;; initialize starting message (also read port B before we set RBIE)) call read_port_and_setup_message ;; enable interrupts bsf STATUS, RP0 ; bank 1 movlw B'00000100' movwf PIE1 movlw B'00000001' movwf PIE2 bcf STATUS, RP0 ; bank 0 clrf PIR1 ; clear all pending interrupt flags clrf PIR2 clrf INTCON IFNDEF DEBUG_DISABLE_SCROLL bsf INTCON, T0IE ; enable timer 0 (scroll) interrupt ENDIF ; DEBUG_DISABLE_SCROLL bsf INTCON, PEIE ; enable peripheral interrupts bsf INTCON, GIE ; set the global interrupt enable ;; wait for one complete cycle to complete first. bcf SCAN_STATE, 0 wait1: btfss SCAN_STATE, 0 goto wait1 bcf SCAN_STATE, 0 ;; sweep starts when bit 0 of SCAN_STATE is *set* frame_top: wait3: btfss SCAN_STATE, 0 goto wait3 bcf SCAN_STATE, 0 ; reset so it can be set at start of next scan ;; check RC5 -> if switch is depressed (RC5 low), then skip 1/4 frame. btfsc PORTC, 5 goto no_pushbutton incf WHICH_SCAN, F ;; wait for switch to be unpressed (debounce) wait4: btfss PORTC, 5 goto wait4 ;; now wait for start of next frame bcf SCAN_STATE, 0 goto frame_top no_pushbutton: bcf INTCON, GIE ; disable interrupts during message setup btfsc INTCON, RBIF ; change current message if PORTB has changed call read_port_and_setup_message ; can't scroll during this! bsf INTCON, GIE ; re-enable interrupts. ;; we're going to divide the rev time (4*CUR_PERIOD) by 1024 to get ;; a per-pixel time, and divide by 6 to get a per-line time. ;; this gives us 170 2/3 pixels per line; we're going to give ;; ourselves timing margins by only emitting 160 pixels per line. ;; give a sufficient offset to start at the beginning of a line. ;; should be around 50 *pixel times* (can be up to 16 fewer) ;; we're using 48: ;; so -> add (CUR_PERIOD_H/L << 4 + CUR_PERIOD_H/L << 5) >> 8 ;; += CUR_PERIOD_H/L>>4 += CUR_PERIOD_H/L>>3 ;; also compute LINE_TIME_H/L = 2/3 * CUR_PERIOD_H/L ;; = (CUR_PERIOD<<1+CUR_PERIOD<<3+CUR_PERIOD<<5+CUR_PERIOD<<7)>>8 ;; = CUR_PERIOD>>7 + CUR_PERIOD>>5 + CUR_PERIOD>>3 + CUR_PERIOD>>1 movf CUR_PERIOD_L, W movwf TEMP_L movf CUR_PERIOD_H, W movwf TEMP_H ;; TEMP_H/L = CUR_PERIOD bcf STATUS, C rrf TEMP_H, F rrf TEMP_L, F ;; TEMP_H/L = CUR_PERIOD>>1 movf TEMP_H, W movwf LINE_TIME_H movf TEMP_L, W movwf LINE_TIME_L ;; LINE_TIME = CUR_PERIOD>>1 bcf STATUS, C rrf TEMP_H, F rrf TEMP_L, F ;; TEMP_H/L = CUR_PERIOD>>2 bcf STATUS, C rrf TEMP_H, F rrf TEMP_L, F ;; TEMP_H/L = CUR_PERIOD>>3 movf TEMP_L, W addwf SCAN_START_L, F btfsc STATUS, C incf SCAN_START_H, F movf TEMP_H, W addwf SCAN_START_H, F ;; SCAN_START += CUR_PERIOD>>3 movf TEMP_L, W addwf LINE_TIME_L, F btfsc STATUS, C incf LINE_TIME_H, F movf TEMP_H, W addwf LINE_TIME_H, F ;; LINE_TIME += CUR_PERIOD>>3 bcf STATUS, C rrf TEMP_H, F rrf TEMP_L, F ;; TEMP_H/L = CUR_PERIOD>>4 movf TEMP_L, W addwf SCAN_START_L, F btfsc STATUS, C incf SCAN_START_H, F movf TEMP_H, W addwf SCAN_START_H, F ;; SCAN_START += CUR_PERIOD>>4 bcf STATUS, C rrf TEMP_H, F rrf TEMP_L, F ;; TEMP_H/L = CUR_PERIOD>>5 movf TEMP_L, W addwf LINE_TIME_L, F btfsc STATUS, C incf LINE_TIME_H, F movf TEMP_H, W addwf LINE_TIME_H, F ;; LINE_TIME += CUR_PERIOD>>5 bcf STATUS, C rrf TEMP_H, F rrf TEMP_L, F ;; TEMP_H/L = CUR_PERIOD>>6 bcf STATUS, C rrf TEMP_H, F rrf TEMP_L, F ;; TEMP_H/L = CUR_PERIOD>>7 movf TEMP_L, W addwf LINE_TIME_L, F btfsc STATUS, C incf LINE_TIME_H, F movf TEMP_H, W addwf LINE_TIME_H, F ;; LINE_TIME += CUR_PERIOD>>7 movf SCAN_START_L, W movwf LINE_START_L movf SCAN_START_H, W movwf LINE_START_H ;; LINE_START_H/L = OLD_SCAN_START + CUR_PERIOD>>3 + CUR_PERIOD>>5 movf CUR_PERIOD_H, W movwf PIXEL_TIME ;; PIXEL_TIME = CUR_PERIOD>>8 IF (0) ;; XXX DEBUGGING: show the values OF LINE_TIME and LINE_START movf LINE_TIME_H, W call serial_debug movf LINE_TIME_L, W call serial_debug nop movf LINE_START_H, W call serial_debug movf LINE_START_L, W call serial_debug ENDIF ;; END DEBUGGING clrf CURLINENUM ; we start scanning w/ line # 0 next_line: ;; set up CURCHARNUM / CURDOTS movf LINECHARNUM, W movwf CURCHARNUM call lookup_alphabet_data movwf CURDOTS incf LINEDOTOFF, W movwf LOOKUP_TEMP ; use LOOKUP_TEMP to count rotations goto test_rot_val rotate_again: bsf STATUS, C rlf CURDOTS, F test_rot_val: decfsz LOOKUP_TEMP, F goto rotate_again ;; ASSERT that CURDOTS!=0xFF at this point. ;; initialize the number of bits/line IFDEF DEBUG_8MHz_CLOCK movlw 0x70 ; char lookup's too slow at 8MHz for all pixels ELSE movlw 0x90 ; can put more pixels @ faster clock rate. ENDIF movwf NUM_PIXELS ; number of bits/line. ;; compute the starting time of this line. movf LINE_START_L, W ; TEMP_H/L = LINE_START_H/L movwf TEMP_L movf LINE_START_H, W movwf TEMP_H ; (done) movf LINE_TIME_L, W ; LINE_START += LINE_TIME addwf LINE_START_L, F btfsc STATUS, C incf LINE_START_H, F movf LINE_TIME_H, W addwf LINE_START_H, F ; (done) ;; each line needs a little tweak in the offset, because the mirrors ;; aren't perfectly hexagonal. call get_line_offset addlw 0x1 ; compensate for addition at start of next_bit movwf LOOKUP_TEMP subtract_another_pixel_offset: movf PIXEL_TIME, W ; TEMP_H/L -= PIXEL_TIME subwf TEMP_L, F btfss STATUS, C ; carry=1 means borrow=0 decf TEMP_H, F decfsz LOOKUP_TEMP, F goto subtract_another_pixel_offset ;; done with line offset. IF (0) ;; XXX DEBUGGING: show the values OF TEMP and TMR1 movf TEMP_H, W call serial_debug movf TEMP_L, W call serial_debug movf TMR1H, W call serial_debug movf TMR1L, W call serial_debug ENDIF ;; END DEBUGGING next_bit: ;; when's the next bit time? movf PIXEL_TIME, W ; TEMP_H/L += PIXEL_TIME addwf TEMP_L, F btfsc STATUS, C incf TEMP_H, F ;; do we need to wait for an overflow to occur? ;; (if TEMP_H is 0x0X and TMR1H bit 7 is 0xFX) movf TEMP_H, W andlw 0xE0 btfss STATUS, Z goto no_overflow comf TMR1H, W andlw 0xE0 btfss STATUS, Z goto no_overflow ;; oops! overflow! ;; wait until timer has overflowed. ;; (until high bit of timer is zero) wait_for_overflow: btfsc TMR1H, 7 goto wait_for_overflow no_overflow: ;; wait until timer is greater than or equal to TEMP_H/TEMP_L ;; three cases: TMR1H/L >= TEMP_H/TEMP_L (wait is over) ;; TMR1H/L < TEMP_H/TEMP_L (keep waiting) ;; TMR1H=00X..X and TEMP_H=11X..X (overflow; wait is over) wait_for_timer: movf TMR1H, W andlw 0xE0 btfss STATUS, Z goto tmr1h_nonzero ;; TMR1H==0 ---> if TEMP_H=0xFF, then we may have an overflow situation comf TEMP_H, W andlw 0xE0 btfsc STATUS, Z goto wait_done ; TMR1H=0X && TEMP_H=FX: overflow. wait over. tmr1h_nonzero: movf TMR1H, W subwf TEMP_H, W ; W = TEMP_H - TMR1H btfsc STATUS, Z goto high_eq ; TEMP_H == TMR1H btfsc STATUS, C ; borrow is !carry goto wait_for_timer ; keep waiting if TEMP_H > TMR1H (no borrow) goto wait_done ; the wait is over if TEMP_H < TMR1H (borrow) high_eq: ; TMR1H==TEMP_H movf TMR1L, W subwf TEMP_L, W ; W = TEMP_L - TMR1L btfsc STATUS, Z goto wait_done ; TEMP_H/TEMP_L == TMR1H/TMR1L btfsc STATUS, C goto wait_for_timer ; keep waiting if TEMP_L > TMR1L (no borrow) ;; wait is over! (TEMP_H==TMR1H && TEMP_L < TMR1L ) wait_done: ;; OK! emit this pixel! IFDEF DEBUG_FORCE_VERTICAL_LINES bcf PORTB, 0 ; start by turning off the laser btfsc NUM_PIXELS, 0 ; use LSB of NUM_PIXELS to alternate bsf PORTB, 0 ; turn on laser if pixel is on. ELSE ; ! DEBUG_VERTICAL_LINES bcf PORTB, 0 ; start by turning off the laser btfsc CURDOTS, 7 ; current pixel is MSB of CURDOTS bsf PORTB, 0 ; turn on laser if pixel is on. ;; now shift CURDOTS for the next go-round. bsf STATUS, C rlf CURDOTS, F ;; test if CURDOTS==0xFF. if so, time to advance to next char. incfsz CURDOTS, W goto no_advance ; no advance needed ;; okay, advance to next char: movf CURCHARNUM, W ; W = CURCHARNUM btfsc STATUS, Z ; if (CURCHARNUM==0) W=MESSAGE_CHARS movf MESSAGE_CHARS, W addlw 0xFF ; W = W - 1 movwf CURCHARNUM ; can we do this in less than five cycles? ;; set CURDOTS call lookup_alphabet_data movwf CURDOTS ;; okay, done w/ advance. ENDIF ; DEBUG_VERTICAL_LINES no_advance: decfsz NUM_PIXELS, F ; NUM_PIXELS-- goto next_bit bcf PORTB, 0 ; turn laser off for line retrace. incf CURLINENUM, F ; CURLINENUM++ btfsc CURLINENUM, 2 ; (comment this line out to get only 1 line) btfss CURLINENUM, 1 goto next_line ; CURLINENUM&6!=6, so keep doing lines. ;; ooh, we've done all the lines. goto frame_top ; sync with scanner edge again. serial_debug: ; debug: put the value of W out on PB2/3 ;; sync/clock on PB2, data on PB3 movwf DEBUG_TEMP movlw 0x8 ; eight bits movwf DEBUG_TEMP2 serloop1: bcf PORTB, 2 ; falling edge: data invalid. btfsc DEBUG_TEMP, 7 ; msb bsf PORTB, 3 ; data btfss DEBUG_TEMP, 7 bcf PORTB, 3 bsf PORTB, 2 ; rising edge means data ready. nop ; give an extra two cycles nop ; to make waveform easier to see. rlf DEBUG_TEMP, F ; next bit. decfsz DEBUG_TEMP2, F goto serloop1 bcf PORTB, 2 ; maintain low between words. bcf PORTB, 3 return ;; lookup character CURCHARNUM in 'message' ;; CURCHARNUM = 0 to MESSAGE_CHARS-1 : which character we're doing ;; returns byte in W lookup_char_in_message: movf CURCHARNUM, W lookup_char_in_message2a: ; alternate entry point for use of 'do_scroll' IFDEF DEBUG_ALL_MESSAGE_CHARS_ZERO retlw H'00' ; use the test character. ENDIF ;; DEBUG_ALL_MESSAGE_CHARS_ZERO addwf MESSAGE_LOW, W movwf LOOKUP_TEMP movf MESSAGE_HIGH, W lookup_char_in_message2b: ; alternate entry point for use of 'do_scroll' btfsc STATUS, C addlw 0x1 movwf PCLATH ; setup PCLATH movf LOOKUP_TEMP, W movwf PCL ; does return to caller of 'lookup_char' ;; NOTE we don't have to restore PCLATH as long as our program ;; fits entirely in bank 0 (i.e. we don't have to use top two ;; bits of address) ;; lookup alphabet data. ;; CURLINENUM = 0 to 5, which line we're on. ;; (from call to lookup_char_in_message: ;; CURCHARNUM = 0 to MESSAGE_CHARS-1, which character we're doing) ;; returns byte in W lookup_alphabet_data: IFDEF DEBUG_NO_ALPHABET_LOOKUP swapf CURLINENUM, W ; don't actually look up character iorlw 0x87 return ENDIF call lookup_char_in_message movwf LOOKUP_TEMP ; lookup temp = value of char # CURCHARNUM call lookup_low_of_line addwf LOOKUP_TEMP, F ; LOOKUP_TEMP now has LSB of table addr. ;; now deal with carry from the add bcf SCAN_STATE, 1 btfsc STATUS, C bsf SCAN_STATE, 1 ; store carry bit in bit 1 of SCAN_STATE ;; okay, now compute MSB. call lookup_high_of_line btfsc SCAN_STATE, 1 addlw 0x1 ; do the carry movwf PCLATH ; setup PCLATH with MSB of table addr. movf LOOKUP_TEMP, W ; W=LSB of table addr. movwf PCL ; does return to caller of 'lookup_alphabet_data' ;; NOTE we don't have to restore PCLATH as long as our program ;; fits entirely in bank 0 (i.e. we don't have to use top two ;; bits of address) padtab D'10' lookup_low_of_line: movlw HIGH lookup_low_of_line movwf PCLATH movf CURLINENUM, W addwf PCL, F dt LOW line1 dt LOW line4 dt LOW line5 dt LOW line6 dt LOW line3 dt LOW line2 padtab D'10' lookup_high_of_line: movlw HIGH lookup_high_of_line movwf PCLATH movf CURLINENUM, W addwf PCL, F dt HIGH line1 dt HIGH line4 dt HIGH line5 dt HIGH line6 dt HIGH line3 dt HIGH line2 ;; use PB4-7 to setup MESSAGE_HIGH, MESSAGE_LOW, and MESSAGE_CHARS ;; also reset CURCHARNUM, LINECHARNUM, and LINEDOTOFF to be safe ;; (because # of characters in the message can change) padtab (((0x8*D'15')+0x1)+0x9) read_port_and_setup_message: clrf LINECHARNUM clrf LINEDOTOFF movlw HIGH message_table movwf PCLATH rrf PORTB, W ; read port B and shift, W=(PB7-PB4)*8 bcf INTCON, RBIF ; reset "port B changed" flag xorlw 0xFF ; flip sense of bits, so that 'on' is 1 (not 0) andlw 0x78 ; mask out irrelevant bits. addwf PCL, F ; jump! message_table_entry macro num movlw LOW message#v(num) movwf MESSAGE_LOW movlw HIGH message#v(num) movwf MESSAGE_HIGH movlw MESSAGE#v(num)_CHARS movwf MESSAGE_CHARS return nop ; pad out to exactly 8 instructions. endm do_entries macro numleft,curnum if (numleft>0) mentry#v(numleft) ; make a label for this entry message_table_entry (curnum+0x1) do_entries (numleft-0x1), ((curnum+0x1)%NUM_MESSAGES) endif endm message_table: do_entries 0x10, 0x00 ; 16 entries do_scroll: bcf INTCON, T0IF ; clear the timer 0 overflow interrupt flag decfsz CURRENT_SCROLL, F goto tach_done ; only continue 1 in SCROLL_SPEED times. movf SCROLL_SPEED, W btfsc STATUS, Z goto tach_done ; never scroll if SCROLL_SPEED==0 ;; IF (0) ;; DEBUGGING: indicate that we're servicing an interrupt movlw 0x10 xorwf PORTB, F ENDIF ;; END DEBUGGING ;; okay, really do the scroll. decf LINEDOTOFF, F ; LINEDOTOFF-- incfsz LINEDOTOFF, W ; if LINEDOTOFF>=0 goto skip_char_inc ; we're done with the scroll! ;; else increment LINECHARNUM incf LINECHARNUM, F ; LINECHARNUM++ movf MESSAGE_CHARS, W subwf LINECHARNUM, W btfsc STATUS, Z ; if LINECHARNUM==MESSAGE_CHARS clrf LINECHARNUM ; LINECHARNUM=0 ;; now we need to set LINEDOTOFF properly. movf LOOKUP_TEMP, W movwf CURRENT_SCROLL ; use CUR..SCROLL as temp storage for LOOKUP.. movf PCLATH, W movwf PCLATH_TEMP ; store away PCLATH value movf LINECHARNUM, W call lookup_char_in_message2a ; W = message[W] addlw LOW line1 movwf LOOKUP_TEMP movlw HIGH line1 call lookup_char_in_message2b; a little more efficient than has_high ;; now W has CURDOTS for the first character in the scan line. movwf SCROLL_TEMP ;; increment LINEDOTOFF until we've reached the delimiter ;; note that LINEDOTOFF==-1 at this point. rotate_again2: incf LINEDOTOFF, F bsf STATUS, C rlf SCROLL_TEMP, F incfsz SCROLL_TEMP, W goto rotate_again2 ;; restore PCLATH movf PCLATH_TEMP, W movwf PCLATH ;; restore LOOKUP_TEMP movf CURRENT_SCROLL, W movwf LOOKUP_TEMP skip_char_inc: ;; reset CURRENT_SCROLL to SCROLL_SPEED movf SCROLL_SPEED, W movwf CURRENT_SCROLL ;; done! goto tach_done tach_int: ; tachometer interrupt. ;; save state. movwf W_TEMP ; copy W to a temp register in whatever ; bank we're currently in. swapf STATUS,W ; swap STATUS nibbles and place in W movwf STATUS_TEMP ; store away STATUS in common ram. ;; okay, now figure out what interrupt this was. bcf STATUS, RP0 ; bank 0 bcf STATUS, RP1 ; bank 0 IFNDEF DEBUG_DISABLE_SCROLL btfsc INTCON, T0IF goto do_scroll ; scroll on timer0 overflow ENDIF ; DEBUG_DISABLE_SCROLL IF (0) ;; DEBUGGING: indicate that we're servicing an interrupt movlw 0x02 xorwf PORTB, F ENDIF ;; END DEBUGGING incf WHICH_SCAN, F ; increment the 'edges I've seen' counter. btfsc PIR1, CCP1IF ; was this ccp1? goto falling_edge rising_edge: ; this was ccp2. bcf PIR2, CCP2IF ; clear the flag. movf CCPR1L, W ; last capture is sitting in CCPR1 subwf CCPR2L, W ; W=CCPR2L-CCPR1L movwf CUR_PERIOD_L movf CCPR1H, W ; last capture was from CCPR1 btfss STATUS, C ; borrow is !carry addlw 0x1 ; add one to W subwf CCPR2H, W ; W=CCPR2H-CCPR1H movwf CUR_PERIOD_H ;; is this the 'start scan' edge? btfss WHICH_SCAN, 0 btfsc WHICH_SCAN, 1 goto tach_done ; if WHICH_SCAN&3!=0, then not the 'start scan' ;; ELSE (if which_scan&3==0), record this as SCAN_START and ;; set bit 0 of SCAN_STATE movf CCPR2L, W movwf SCAN_START_L movf CCPR2H, W movwf SCAN_START_H goto start_scan_edge falling_edge: ; like rising_edge, but reversed. bcf PIR1, CCP1IF ; clear the flag. movf CCPR2L, W ; last capture is sitting in CCPR2 subwf CCPR1L, W ; W=CCPR1L-CCPR2L movwf CUR_PERIOD_L movf CCPR2H, W ; last capture was from CCPR2 btfss STATUS, C ; borrow is !carry addlw 0x1 ; add one to W subwf CCPR1H, W ; W=CCPR1H-CCPR2H movwf CUR_PERIOD_H ;; is this the 'start scan' edge? btfss WHICH_SCAN, 0 btfsc WHICH_SCAN, 1 goto tach_done ; if WHICH_SCAN&3!=0, then not the 'start scan' ;; ELSE (if which_scan&3==0), record this as SCAN_START and ;; set bit 0 of SCAN_STATE movf CCPR1L, W movwf SCAN_START_L movf CCPR1H, W movwf SCAN_START_H start_scan_edge: bsf SCAN_STATE, 0 ;; fall through to tach_done tach_done: ;; end of interrupt service routine. swapf STATUS_TEMP,W ; swap status into W movwf STATUS ; restore status register. swapf W_TEMP,F ; swap W_TEMP nibbles and place back in W_TEMP swapf W_TEMP,W ; restore W_TEMP to W without affecting STATUS retfie ; done! ;; now include the lookup tables for alphabetic characters. IFNDEF DEBUG_ALL_MESSAGE_CHARS_ZERO include alphabet.asm ELSE ; DEBUG_ALL_MESSAGE_CHARS_ZERO line1: retlw H'57' line2: retlw H'17' line3: retlw H'27' line4: retlw H'47' line5: retlw H'87' line6: retlw H'A7' ENDIF ; DEBUG_ALL_MESSAGE_CHARS_ZERO END