;;; ;;; 2000 Bike-trip microcontroller code ;;; ;;; -- Jorj Bauer ;;; ;;; This code is in the public domain. Feel free to use it for anything you like. I make no ;;; warantees or guarantees as to its fitness for any specific purpose, yadda yadda yadda, ;;; will not be held responsible for any consequences, whatever. ;;; ;;; Version 0.1 (2/4/00): takes the picture, but doesn't end properly. Some quick notes: ;;; - the parallel port inverts several lines in hardware (C0, C1, C3, and S7). I'm inverting ;;; these in software, and the notes about "inverting C013" refer to this. ;;; - we probably don't need to set all of the control lines everywhere. C1, for example, isn't ;;; really used and could be tied (low, I think). ;;; - the picture is a little streaky in this version. I'm assuming that's a hardware problem, ;;; not a software problem. ;;; ;;; Version 0.2 (2/11/00): should now guess the brightness before taking the picture (by ;;; taking several small pictures first). ;;; - The problem with the picture streakiness isn't the wiring (running through the ;;; parallel port to a real PC doesn't suffer from any quality problems). This means ;;; it's either software in the PIC, software in the picture re-correction, or the ;;; breadboard. The last option is unlikely, since I've moved to another breadboard ;;; (purely for portability) and the picture problems are exactly the same (the exact same ;;; streaks show up in the same places). ;;; - Multiple sizes and bit depths are now supported. The picture-taking code ends ;;; correctly for all picture sizes. ;;; - I've started putting in color quickcam support. Color quickcams have a couple of ;;; differences from their B&W cousins. They scan in 24bpp mode, for starters, which means ;;; the loop counters will have to be much larger. (I haven't touched them yet.) While ;;; waiting for a scan to begin, the camera sends 0x7E data bytes which should be ignored. ;;; (This is done.) The camera sends three extra bytes at the end of a scan: 0xE, 0x0, and ;;; 0xF (to signify the end of the scan). I'm now reading these bytes, but ignoring the ;;; values. (This may also work for B&W qcams; I'm not sure yet.) The mode numbers for ;;; size and bit depth are probably different; I haven't looked at them yet (which is why I ;;; haven't started changing the counters). ;;; ;;; Version 0.3 (??/??/00): will talk to the GPS, and spit data out the serial port. ;;; - Canned the relay idea. I realized that last year, I also sent tracking data from the ;;; GPS to the web site; this would be impossible to buffer (as it tends to be gigantic). ;;; Instead, I'm using a bit-banging serial interface on port C (which is also the parallel ;;; control port). As a result, we can't specifically set port C to any value; the ;;; individual bits need to be toggled. ;;; - Hardware handshaking is new to this version. The GPS and modem will need to talk to ;;; eachother, and we need to prevent data overrun to the modem. Port E is used for ;;; the hardware flow control lines to the phone. (The GPS has no flow control capability.) ;;; - We're using 3 stop bits for the bit-banging interface for reliability (and to prevent ;;; clock drift problems). Two would probably be sufficient, but I feel safer with three. ;;; STILL TODO 0.3: ;;; - bit-banging GPS interface (interrupt driven) ;;; - flow control modem interface ;;; - might be broken for all-white pictures (which may be inverted, but are definetly 0's). ;;; - is TXIE on or off? ;;; - can we remove some of the WAITs? LIST p=17C43 #define ClkFreq 33000000 ; 33MHz #define baud(X) (((((10*ClkFreq)/(64*X))+5)/10)-1) ; baud specification. I'm having problems ; making this work, actually, so I have ; the constant hard-coded below at the moment. ;;; ;;; Port definitions. The LP_STATUS port is port B, control is port C, ;;; and data is port D. ;;; Serial in/out for the GPS is done via port C also. ;;; #define LP_STATUS PORTB #define DR_STATUS DDRB ;;; 0, 1, 2 unused. 3 is S3, 4-7 are data. #define S3 3 ; RB3 = parallel port S3 #define GPS_PORT PORTB ; the GPS bit-banging serial is also on port B. #define DR_GPS DDRB #define GPS_SEND 0 #define GPS_RCV 1 #define LP_CONTROL PORTC #define DR_CONTROL DDRC #define C0 0 #define C1 1 ; C1 is always 0. We can tie this low if we run out of pins. #define C2 2 #define C3 3 ;;; 4-5 used for GPS (below). 6-7 unused. #define LP_DATA PORTD #define DR_DATA DDRD #define HWHS_PORT PORTE #define DR_HWHS PORTE ; hardware handshaking (modem flow control). #define RTS 0 ; we set this when we have data pending. #define CTS 1 ; the phone sets this when we can send to it. #define DCD 2 ; should be high when the phone is connected. ;;; DTR on our end is always high (telling the phone we're always clear to receive). #define TOO_DARK 0x3 #define HANDSHAKE_PORT PORTE ; hardware handshaking done on port E. #define SERIAL_HSOUT 0 ; E0 is an input. ;;; ;;; Quickcam definitions. Bit flags for MODE. ;;; #define SCANBIDIR 0 ; = bidirectional data. We don't use this, since the ; serial port is a bottleneck even with unidir. #define SCAN6BPP 1 ; = if this bit is on, it's a 6bpp scan. Otherwise, it's a 4bpp scan. #define SCAN160 2 ; = mode 4 (160x120 scan) #define SCAN80 3 ; = mode 8 (80x60 scan) ;;; (if neither SCAN160 nor SCAN80 is on, it's a 320x240 scan.) #define clc bcf ALUSTA, C ; A quick definition: CLC is "Clear Carry". #define sec bsf ALUSTA, C ; SEC is "Set Carry". ;;; ;;; Variable space ;;; ;;; temporary space for storing/restoring registers. ;;; TEMP_WREG_INTERRUPT EQU 0x1C TEMP_ALUSTA_INTERRUPT EQU 0x1D TEMP_BSR_INTERRUPT EQU 0x1E TEMP_WREG EQU 0x1F QC_FLAGS EQU 0x20 ; error, send mode, etc. #define RESEND 0 ; bit 0 of QC_FLAGS denotes an error. #define AVERAGING 1 ; bit 1 of QC_FLAGS denotes "averaging" mode. This sends no data. #define COLOR_QCAM 2 ; bit 2 of QC_FLAGS tells us it's a color quickcam (instead of B&W). #define SCAN_STARTED 3 ; bit 3 tells whether or not the scan has received any valid data yet. ;;; ;;; input/output buffers. ;;; SerInData EQU 0x21 SerOutData EQU 0x122 ; page1 variable. HSVAL EQU 0x23 ; must be in page 0 (same page as status port). ;;; ;;; loop counters. ;;; COUNT1 EQU 0x124 ; used in qc_init; page 1. COUNT2 EQU 0x125 SAVE_RETVAL EQU 0x26 ; returned value from Command. ;;; ;;; Quickcam parameters. ;;; CONTRAST EQU 0x127 BRIGHTNESS EQU 0x128 MODE EQU 0x129 ; Mode 0=320x240; 4=160x120; 8=80x60. Add 2 to these for 6bpp. ;;; ;;; Brightness counters while determining the brightness with which to take the picture. ;;; BR0L EQU 0x2A BR0H EQU 0x2B TEMP_BR EQU 0x2C ;;; ;;; BSR save registers (for various routines). ;;; BSR_ASYNC_MODE EQU 0x2D BSR_GET_DATA_POLL EQU 0x2E ; (see SAVE_BSR and RESTORE_BSR macros) BSR_SEND_DATA_POLL EQU 0x2F BSR_SETUP_PORTLINESS EQU 0x30 BSR_QC_INIT EQU 0x31 BSR_COMMAND EQU 0x32 BSR_HANDSHAKE EQU 0x33 BSR_SET_PARAMS EQU 0x34 BSR_CLEARBRIGHTNESS EQU 0x35 BSR_STOREAVG EQU 0x36 BSR_SETUP_INTERRUPTS EQU 0x37 SERSEND_BSR EQU 0x38 SERSENDW_BSR EQU 0x39 ;;; ;;; NUMBER_7E is for comparison. Its value is set to 0x7E. ;;; NUMBER_7E EQU 0x3A ;;; ;;; bit-banging serial registers. ;;; DEBUG EQU 0x3B ;;; ;;; GPS bitbanging queue constants; two ring buffers ;;; TxQueue EQU 0x80 TxPtr EQU 0x90 TxLen EQU 0x91 TxTemp EQU 0x92 TxBitnum EQU 0x93 ; 0 through 0xA. 0=no data to send. 1=start bit, 2-9=data, A=stop bit. TxSkipping EQU 0x94 ; set when we're syncing the stream. RxQueue EQU 0xC0 RxPtr EQU 0xD0 RxLen EQU 0xD1 RxTemp EQU 0xD2 #include "P17C43.inc" ;;; SAVE_BSR and RESTORE_BSR assume WHERE is a page 0 address. They put ;;; the BSR into that saved location and restore it. SAVE_BSR MACRO WHERE movwf TEMP_WREG movfp BSR, WREG movlb 0 ; must be in page 0 to store to WHERE movwf WHERE movfp TEMP_WREG, WREG ENDM RESTORE_BSR MACRO WHERE movlb 0 movfp WHERE, BSR ENDM ;;; WAIT will delay (3*A)+4 instruction cycles (or something like that). WAIT MACRO A movlw A call Waiter ENDM ;;; SEND_SERIAL sends the byte (which corrupts W) out the serial port. SEND_SERIAL MACRO A SAVE_BSR SERSEND_BSR movlb 0 movlw A btfss PORTA, 0 call Send_Serial_Data_Poll btfsc PORTA, 0 call TxByte RESTORE_BSR SERSEND_BSR ENDM ;; SEND_SERIAL_W sends the byte already in W. SEND_SERIAL_W MACRO SAVE_BSR SERSENDW_BSR movlb 0 btfss PORTA, 0 call Send_Serial_Data_Poll btfsc PORTA, 0 call TxByte RESTORE_BSR SERSENDW_BSR ENDM ;;; QCMD sends a command to the quickcam. It takes two bytes of data. ;;; If the camera doesn't respond with the correct return byte, it sets ;;; RESEND to 0x1 (in send_error, actually); all QCMD calls that we care ;;; about are wrapped with "resend this command" gotos. QCMD MACRO A, B movlw A call Command movlw B call Command ENDM ;;; ************* ;;; BEGIN PROGRAM ;;; ************* org 0 goto main org 0x10 goto t0_interrupt_routine ;;; skip reserved memory addresses org 0x21 ;;; ;;; t0_interrupt_routine is called when the timer0 interrupt occurrs. ;;; t0_interrupt_routine movfp WREG, TEMP_WREG_INTERRUPT movfp ALUSTA, TEMP_ALUSTA_INTERRUPT movfp BSR, TEMP_BSR_INTERRUPT movlb 0 bcf T0STA, T0CS ; turn off interrupt (actually, sets clock external - RA4 doesn't change, so we're safe). ;; ;; change T0 to interrupt at the right time again... ;; movlw 0xFF-0x03 ; 0x35B cycles are about 104.121 uS. 9600 baud is 104.167uS. 0.044% timing error. movwf TMR0H movlw 0xFF-(0x5B-0xF) ; 0x4 delay, plus 0xB instructions before we turn on interrupts again (includes the interrupt goto). movwf TMR0L bsf T0STA, T0CS ; ... and turn back on the interrupt (set clock back internal). t0_tx bsf ALUSTA, FS3 ;set up FSR1 not to auto-inc. btfsc TxSkipping, 0 goto t0_inc_ptr ; if we're skipping to sync the bits, we have to finish. clrf WREG, F cpfseq TxLen goto t0_check_startbit bsf TxSkipping, 0 ; set the syncing flag, because... goto t0_inc_ptr ; ... there is no data pending. We count anyway, so that the bytes start at the right time. t0_check_startbit movfp TxPtr, FSR1 ; redundant... ; clrf WREG, F cpfseq TxBitnum ; if TxBitnum==0, it's a start bit. goto t0_tx_data bcf GPS_PORT, GPS_SEND ; send the start bit (=0). goto t0_inc_ptr t0_tx_data sec rrcf INDF1, F btfsc ALUSTA, C goto t0_send_1 bcf GPS_PORT, GPS_SEND goto t0_inc_ptr t0_send_1 bsf GPS_PORT, GPS_SEND t0_inc_ptr incf TxBitnum, F movlw 0xA ; 0xB is two stop bits (to work around the sync drift, since our timing is imperfect). cpfsgt TxBitnum goto t0_return clrf TxBitnum, F ; if we've sent all of the bits, then start on the next byte. clrf WREG, F clc rrcf TxSkipping, F ; Now the old skipping bit is in the carry, ; and TxSkipping has been cleared. btfsc ALUSTA, C ; If we were skipping, then don't change the TxPtr or TxLen, goto t0_return ; because it has no bearing on the data in the queue. If a byte ; was queued while we were skipping, we don't want to flush it here! ; If we weren't skipping, then there must have been data in the incf TxPtr, F ; queue. Increment the pointer and movlw 0xCF ; decrement the amount of data in the queue. This lets us keep andwf TxPtr, F ; running the timer to make sure that all of the bytes line up decf TxLen, F ; properly (the first bit is always at the right time). t0_return movpf TEMP_BSR_INTERRUPT, BSR movpf TEMP_ALUSTA_INTERRUPT, ALUSTA movpf TEMP_WREG_INTERRUPT, WREG retfie ;;; ;;; ;;; RxByteBlocking bsf ALUSTA, FS1 ; set up FSR0 to not increment. movlb 0 clrf WREG, F cpfsgt RxLen goto RxByteBlocking RxByteBlock clrf WREG, F cpfseq TMR0H goto RxByteNow movlw D'10' cpfsgt TMR0L goto RxByteBlock RxByteNow movfp RxPtr, FSR0 movpf INDF0, RxTemp incf RxPtr, F movlw 0xD0 cpfseq RxPtr goto RxFinish movlw 0xC0 movwf RxPtr RxFinish decf RxLen, F movfp RxTemp, WREG return ;;; ;;; ;;; TxByte SAVE_BSR BSR_SEND_DATA_POLL bsf ALUSTA, FS1 ; set up FSR0 to not increment. movlb 0 movwf TxTemp ;;; for debugging: only transmit when the queue is empty. TxDebugWait clrf WREG, F cpfseq TxLen goto TxDebugWait ; movlw D'16' ; cpfslt TxLen ; goto TxByte TxByteBlock clrf WREG, F cpfseq TMR0H goto TxByteNow movlw D'14' cpfsgt TMR0L goto TxByteBlock TxByteNow movfp TxPtr, FSR0 ; movfp TxLen, WREG ; addwf FSR0, F ; incf FSR0, F ; movlw 0x8F ; cpfsgt FSR0 ; goto TxByteQueue ; movlw 0x10 ; subwf FSR0, F TxByteQueue movfp TxTemp, INDF0 incf TxLen, F RESTORE_BSR BSR_SEND_DATA_POLL return ;;; ;;; ;;; Setup_Interrupts SAVE_BSR BSR_SETUP_INTERRUPTS movlb 0 movlw TxQueue ; initialize the ring buffers. movwf TxPtr movlw RxQueue movwf RxPtr clrf TxLen, F clrf TxBitnum, F clrf TxSkipping, F clrf RxLen, F bsf GPS_PORT, GPS_SEND ; the quiescent state of the rs232 bus is 1. bsf T0STA, T0CS ; timer 0 from internal clock bcf T0STA, PS0 ; 1x prescalar. bcf T0STA, PS1 ; 1x prescalar. bcf T0STA, PS2 ; 1x prescalar. bcf T0STA, PS3 ; 1x prescalar. bsf INTSTA, T0IE ; turn on the interrupt. RESTORE_BSR BSR_SETUP_INTERRUPTS retfie ; return and enable interrupts. ;;; The serial routines here are from one of the application notes on Microchip's web site. Setup_Async_Mode SAVE_BSR BSR_ASYNC_MODE movlb 0 ; movlw D'52' ; for some reason, this doesn't work as ; ; baud(9600). 52 (decimal) is 9600 for 33MHz. movlw D'26' ; Testing 19.2kbps. We'll be hardware handshaking, so this should be ok. movwf SPBRG movlw B'00100000' ; turn on serial port transmitter movwf TXSTA movlw B'10010000' ; turn on serial port receiver and the serial port itself movwf RCSTA RESTORE_BSR BSR_ASYNC_MODE return Get_Serial_Data_Poll SAVE_BSR BSR_GET_DATA_POLL movlb 1 PollRcv btfss PIR^0x100, 0 ; check RBIF bit goto PollRcv ; loop until a char is received movlb 0 movpf RCREG, SerInData RESTORE_BSR BSR_GET_DATA_POLL return ;;; Send_Serial_Data_Poll sends whatever's in WREG. Send_Serial_Data_Poll SAVE_BSR BSR_SEND_DATA_POLL movlb 1 PollTXIF btfss PIR^0x100, 1 ; check TXIF bit (PIR is bank 1). goto PollTXIF movlb 0 movwf TXREG ; TXREG is in bank 0. RESTORE_BSR BSR_SEND_DATA_POLL return qc_initparams ;; set the file registers associated with the quickcam to their initial values. movlb 0 clrf QC_FLAGS, F ; no errors, B&W qcam. movlb 1 movlw D'180' movwf CONTRAST^0x100 movlw D'150' movwf BRIGHTNESS^0x100 movlw D'0' ; 320x240 @ 4bpp scan. movwf MODE^0x100 return qc_init SAVE_BSR BSR_QC_INIT ;; determine color or B&W qcam. ; movfp PORTA, WREG ; in case it changes while we're checking... ; movlb 0 ; btfsc WREG, 0 ; color or B&W qcam? ; bsf QC_FLAGS, COLOR_QCAM ; color if portA<0> is high. ; btfss WREG, 0 ; bcf QC_FLAGS, COLOR_QCAM ; B&W if portA<0> is low. movlb 1 ;; now initialize the hardware. movlw 0x75 movwf LP_DATA^0x100 WAIT 0x1 ;; set the individual bits for the control port. Can't set the entire word ;; because the port also does other things. bcf LP_CONTROL^0x100, C0 ; inverted C0,1,3 on the parallel port (%1101) bcf LP_CONTROL^0x100, C1 bcf LP_CONTROL^0x100, C2 bcf LP_CONTROL^0x100, C3 ;; sleep 250 usecs (8250 clock ticks @ 33MHz, 2063 (0x80F) cycles) movlw 0x3 ; 0x200 loops (= 0x3) movwf COUNT2^0x100 movlw 0xB0 ; 0xA9 cycles (= 0xB0) movwf COUNT1^0x100 ;; each loop takes at least 3 cycles, so we loop 1/3 of 2063 loops. qc_init_loop decfsz COUNT1^0x100, F ; 3 cycles to each lower count, 2 at the end goto qc_init_loop decfsz COUNT2^0x100, F ; 3 cycles to each high cont, 2 at the end goto qc_init_loop ;; again, set the individual bits. bsf LP_CONTROL^0x100, C0 ; inverted C0,1,3 again. (%1110) bcf LP_CONTROL^0x100, C1 bsf LP_CONTROL^0x100, C2 bcf LP_CONTROL^0x100, C3 ;;; pause a little before continuing. WAIT 0x1 RESTORE_BSR BSR_QC_INIT return ;;; Command takes a command in WREG. Returns the value that the camera sends us ;;; on S4-S7 (in two nibbles) in SAVE_RETVAL and W (just in case we want to use ;;; it one way or the other). Command SAVE_BSR BSR_COMMAND movlb 1 movwf LP_DATA^0x100 ;;; for debugging: tell us (via serial port) what's going on. ; WAIT 0x10 ; give time for the data to settle bsf LP_CONTROL^0x100, C3 ; set C3. WAIT 0x1 ; wait for the cam to have time... movlb 0 bsf HSVAL, 0 ; wait for a 1 from the qcam on S3 call Handshake movwf SAVE_RETVAL ; save the command it returns in S4-S7 movlb 1 bcf LP_CONTROL^0x100, C3 ; clear C3 to tell the cam we got it WAIT 0x1 ; give the cam time to see it movlb 0 bcf HSVAL, 0 ; wait for a 0 from the qcam on S3 call Handshake clc ; take this retval and roll right 4 rrcf WREG, F ; bits (it's the low nibble of its clc ; response). Then OR that with the rrcf WREG, F ; high nibble (returned from the clc ; previous Handshake) and return rrcf WREG, F ; that value as the "result" of clc ; sending this command. (For actual rrcf WREG, F ; commands, this should be the same iorwf SAVE_RETVAL, F ; as the command we sent; when taking movfp SAVE_RETVAL, WREG ; a picture, this is a data value.) RESTORE_BSR BSR_COMMAND return ;;; Handshake takes a 1 or 0 in HSVAL, returns when S3==HSVAL<0> ;;; We save BSR in TEMP_BSR2 instead of TEMP_BSR because this is called from ;;; within other routines that also save the BSR. It is never called from ;;; itself, so we give it its own register location. ;;; Note that HSVAL is a PAGE0 address (since we're dealing with page0, ;;; it's just easier that way). Handshake SAVE_BSR BSR_HANDSHAKE HSLOOP movlb 0 WAIT 0x1 movfp LP_STATUS, WREG ; copy LP_STATUS into WREG so that we don't btfsc HSVAL, 0 ; read it twice to get the returned value. goto HS2 ; If waiting for a 1, goto HS2. btfss WREG, S3 ; waiting for a 0. goto HS_DONE ; got a 0, so clean up and exit. goto HSLOOP ; didn't get a zero, so do it again. HS2 btfsc WREG, S3 ; waiting for a 1. goto HS_DONE ; got a 1, so clean up and exit. goto HSLOOP ; didn't get a 1, so do it again. HS_DONE btg WREG, 7 ; bit 7 of status is inverted. Un-invert it. movwf TEMP_WREG ; We only want the high 4 bits of the returned movlw 0xf0 ; value. Strip off the low nibble, and return andwf TEMP_WREG, W ; the high nibble in W. (TEMP_WREG has the RESTORE_BSR BSR_HANDSHAKE ; un-anded value in it.) return ;;; Set up the quickcam properties (brightness, height, width...). ;;; The brightness and contrast values are in the page 1 file locations ;;; BRIGHTNESS and CONTRAST. (Likewise the other params.) ;;; We're now ignoring errors and not reporting them in any way. We ;;; used to report them, but everything's working smoothly now; we ;;; just need to put a timeout on the picture taking itself. Quickcam_Set_Params SAVE_BSR BSR_SET_PARAMS movlb 1 movlw 0xB ; brightness call Command movfp BRIGHTNESS^0x100, WREG call Command movlb 0 ;; the mode is either 0, 4, or 8 (for 320x240, 160x120, or 80x60) and +2 for 6bpp. ;; figure out the right size based on the mode. ; movlb 1 ; redundant movlw HIGH(scansizes) movpf WREG, TBLPTRH movlw LOW(scansizes) movpf WREG, TBLPTRL movfp MODE^0x100, WREG clc rrcf WREG, F ; WREG /= 2 addwf TBLPTRL, F btfsc ALUSTA, C incf TBLPTRH, F tablrd 0, 0, WREG movlw 0x11 ; height call Command tlrd 1, WREG call Command movlw 0x13 ; "width" (not really width; more like "scans per line") call Command tablrd 0, 1, WREG call Command QCMD 0xD, 0x1 ; top QCMD 0xF, 0x7 ; left movlw 0x19 ; contrast call Command movfp CONTRAST^0x100, WREG call Command QCMD 0x1F, D'70' ; white balance ("default" is 105 in the original C code...) RESTORE_BSR BSR_SET_PARAMS return Quickcam_ClearBrightnessTable clrf BR0L, F clrf BR0H, F return ;;; ;;; Quickcam_GuessBrightness repeatedly calls qc_scan in "averaging" mode (which doesn't send ;;; serial data) until it guesses a good brightness. Then it lets it go. ;;; Quickcam_GuessBrightness movlb 0 bsf QC_FLAGS, AVERAGING ; averaging. Don't send. movlw D'10' ; start with a brightness of 10. movlb 1 movwf BRIGHTNESS^0x100 guessagain movlb 1 btg PORTE^0x100, 2 ; flash the LED on port E, pin 2 so we can tell. call Quickcam_ClearBrightnessTable ;; If we're averaging, then we're taking an 80x60 4bpp picture. movlw 0x04 ; mode 4 is 160x120 4bpp. movlb 1 movwf MODE^0x100 call qc_scan SEND_SERIAL 'B' SEND_SERIAL 'R' SEND_SERIAL 'I' SEND_SERIAL ':' movlb 1 movfp BRIGHTNESS^0x100, WREG SEND_SERIAL_W ; to let us know what's going on, send it out the serial port. movlb 1 movlw D'250' cpfslt BRIGHTNESS^0x100 return ; if the brightness is 250 or above, we've gone too far. Bail out. SEND_SERIAL 'B' SEND_SERIAL 'R' SEND_SERIAL '0' SEND_SERIAL ':' movlb 0 movfp BR0H, WREG SEND_SERIAL_W ; to let us know what's going on, send it out the serial port. movfp BR0L, WREG SEND_SERIAL_W ; to let us know what's going on, send it out the serial port. ; if more than 50% of the pixels were counted as "near black", then it's too dark ; (160*120/2 = 0x2580 pixels). For simplicity, we use 0x2600 pixels (9728) instead (50.7%). ; ; 0x21 is a good value, but a little washed out in a dark mixed environment. movlb 0 movlw 0x25 ; test high byte of counter. Return if <= 0x25. cpfsgt BR0H return ;; The picture is too dark. Increment the brightness and try again. SEND_SERIAL '(' SEND_SERIAL 'I' SEND_SERIAL 'N' SEND_SERIAL 'C' SEND_SERIAL ')' movlb 1 movfp BRIGHTNESS^0x100, WREG addlw D'10' ; add 10 to the brightness and try again. movwf BRIGHTNESS^0x100 call qc_init ; re-initialize the quickcam. goto guessagain increment_br_counter clc incf BR0L, F ; increment BR0L. btfsc ALUSTA, C ; If BR0L rolled over, increment BR0H. incf BR0H, F return Quickcam_Average_StoreW SAVE_BSR BSR_STOREAVG movlb 0 movwf TEMP_WREG ;; store the high nibble (WREG >> 4) clc rrcf WREG, F clc rrcf WREG, F clc rrcf WREG, F clc rrcf WREG, F movwf TEMP_BR ; if the brightness is near black, increment BR0L / BR0H. movlw TOO_DARK cpfsgt TEMP_BR call increment_br_counter BR_STORELOW movlb 0 ;; store the low nibble (WREG & 0x0F) movlw 0x0f andwf TEMP_WREG, W ; get back the data (into W) movwf TEMP_BR ; if the brightness is near black, increment br0l/br0h. movlw TOO_DARK cpfsgt TEMP_BR call increment_br_counter BR_DONE RESTORE_BSR BSR_STOREAVG return Quickcam_ScanAndSend movlb 0 bcf QC_FLAGS, AVERAGING ; sending data, not averaging. call qc_read_switches ; get the current mode from the switches on PORTA ;; fall through... qc_scan call Quickcam_Set_Params ; reset the camera state. SEND_SERIAL 'M' SEND_SERIAL 'O' SEND_SERIAL 'D' SEND_SERIAL 'E' SEND_SERIAL ':' movlb 1 movfp MODE^0x100, WREG addlw 'A' SEND_SERIAL_W movlb 0 SEND_SERIAL 'S' SEND_SERIAL 'C' SEND_SERIAL 'A' SEND_SERIAL 'N' SEND_SERIAL ':' movlw 0x7 ; send a scan command to the camera with the mode call Command ; that's specified in the configuration. The mode movlb 1 movfp MODE^0x100, WREG ; tells it what size and bit-depth to use. call Command readbytes ;; Figure out how many bytes to read. Once we've got the right number, start ;; scanning. movlb 1 ;; read the correct values out of the table 'scannums'. movlw HIGH (scannums) movpf WREG, TBLPTRH movlw LOW (scannums) movpf WREG, TBLPTRL movfp MODE^0x100, WREG clc rrcf WREG, F ; w /= 2 addwf TBLPTRL, F btfsc ALUSTA, C ; if carry is set, we need to increment tablptrh (table incf TBLPTRH, F ; spans two pages). tablrd 0, 0, WREG ; dummy read; updates tablatch. tlrd 1, COUNT1^0x100 ; Read hi byte of tablatch. tablrd 0, 1, COUNT2^0x100 ; Read low byte of TABLATCH and increment it. movlb 0 bcf QC_FLAGS, SCAN_STARTED ; for color quickcam start-of-picture detection. ;;; Actually read the bytes here. This loops until it should be done. There should be a time-out ;;; on this action in case something happens in the middle of it! readbytes_loop movlb 1 movlw 0x0 ; clear W so that we don't send a command. call Command ; This doesn't actually send a command; it just ; handshakes and reads the result. movlb 0 btfsc QC_FLAGS, AVERAGING goto readbytes_1 SEND_SERIAL_W ; send WREG via serial if we're not averaging. readbytes_1 movlb 0 btfsc QC_FLAGS, AVERAGING call Quickcam_Average_StoreW ; or store the data if we're averaging movlb 1 decfsz COUNT2^0x100, F ; COUNT2--; loop if it's not zero yet goto readbytes_loop decfsz COUNT1^0x100, F ; COUNT1--; loop if it's not zero yet goto readbytes_loop done_readbytes ; movlb 0 ; btfsc QC_FLAGS, AVERAGING ; return ; don't send the EOF if we're averaging. SEND_SERIAL 'E' SEND_SERIAL 'O' SEND_SERIAL 'F' return ;;; ;;; Data tables. ;;; scannums ;; number of line scans for each scan mode: dw 0x9600, 0xE100 ; 320x240, 4bpp or 6bpp dw 0x2680, 0x3940 ; 160x120, 4bpp or 6bpp dw 0x0A60, 0x0F10 ; 80x60, 4bpp or 6bpp scansizes ;; size (high byte=height, low byte=width) for each scan mode. ;; these are the numbers passed to the height and width commands ;; when configuring the quickcam. dw 0xF0A0, 0xF050 ; 320x240, 4bpp or 6bpp dw 0x7850, 0x7828 ; 160x120, 4bpp or 6bpp dw 0x3C28, 0x3C14 ; 80x60, 4bpp or 6bpp Setup_Portliness SAVE_BSR BSR_SETUP_PORTLINESS movlb 1 ; initialize CONTROL to something useful (like its normal state). ; we can't set the entire port, since it's used for other things. Set the ; individual bits. bsf LP_CONTROL^0x100, C2 ; inverted C0,1,3. bcf LP_CONTROL^0x100, C0 bcf LP_CONTROL^0x100, C1 bcf LP_CONTROL^0x100, C3 clrf LP_DATA^0x100, F movlb 0 clrf LP_STATUS, F movlw 0xFF ; inputs movwf DR_STATUS bsf PORTA, 7 ; turnoff pull-ups on port B (yes, the bit to do this is on PORTA) ; port A is always inputs. Pins 1-3 used to set camera mode. bsf DR_GPS, GPS_RCV ; the GPS receive line is an input. bcf DR_GPS, GPS_SEND ; the GPS send line is an output. bsf GPS_PORT, GPS_SEND ; the quiescent state of RS232 is high. Set it. movlb 1 clrf PORTE^0x100, F ; PORT E is hardware handshaking and debugging. movlw B'001' ; E0 input; E1, E2 outputs. movwf DDRE^0x100 bcf DR_CONTROL^0x100, C0 ; C0, 1, 2, 3 are outputs. bcf DR_CONTROL^0x100, C1 ; C0, 1, 2, 3 are outputs. bcf DR_CONTROL^0x100, C2 ; C0, 1, 2, 3 are outputs. bcf DR_CONTROL^0x100, C3 ; C0, 1, 2, 3 are outputs. clrf DR_DATA^0x100, F ; outputs. RESTORE_BSR BSR_SETUP_PORTLINESS return ;;; Waiter is the routine that does the waiting (for the WAIT macro). The ;;; number of loops (-1) that it will perform is in WREG. We leave as soon as ;;; WREG reaches 0 (therefore, passing in a 0 will do 0xFF loops). Waiter decfsz WREG, F goto Waiter return ;;; ;;; qc_read_switches: read the physical switches on port a and set the appropriate ;;; mode. ;;; qc_read_switches movlb 0 movfp PORTA, WREG movlb 1 ; set mode based on low 4 bits of port a. clrf MODE^0x100, F ; bcf MODE^0x100, 0 ; bit 0 is always off (always unidirectional). btfsc WREG, 1 bsf MODE^0x100, 1 btfsc WREG, 2 bsf MODE^0x100, 2 btfsc WREG, 3 bsf MODE^0x100, 3 ; movlb 0 ; btfsc WREG, 0 ; color or B&W qcam? ; bsf QC_FLAGS, COLOR_QCAM ; btfss WREG, 0 ; bcf QC_FLAGS, COLOR_QCAM return ;;; ;;; Main entry point. ;;; main WAIT 0x0 ; Take a little nap when we start up (256x). movlw 0x7E movwf NUMBER_7E ; initialize NUMBER_7E. call Setup_Async_Mode call Setup_Portliness WAIT 0x0 ; let the RS232 lines settle. call Setup_Interrupts call qc_initparams ; set the default values for the quickcam params. call qc_init ; initialize the quickcam hardware. SEND_SERIAL 'H' ; Just so we know it's running properly. SEND_SERIAL 'i' SEND_SERIAL '!' call Quickcam_GuessBrightness movlb 1 bsf PORTE^0x100, 1 ; turn on the LED call qc_init ; re-initialize the quickcam. call Quickcam_ScanAndSend all_done ; finished! Turn off the LED again. movlb 1 bcf PORTE^0x100, 1 goto all_done END