;;; ;;; 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 sizes. ;;; The picture size and quality is set with port A pins 1-3 (1=4/6bpp; 2=160x120; 3=80x60). ;;; The default picture (portA==0) is 320x240@4bpp. ;;; - 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). 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. ;;; #define LP_STATUS PORTB #define DR_STATUS DDRB #define S3 3 ; RB3 = parallel port S3 #define LP_CONTROL PORTC #define DR_CONTROL DDRC #define C3 3 #define LP_DATA PORTD #define DR_DATA DDRD #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". ;;; ;;; Variable space ;;; ;;; temporary space for storing/restoring registers. ;;; TEMP_WREG EQU 0x1C TMP_QCMD EQU 0x1D ; used to store the command being sent to the camera TMP_QCMD2 EQU 0x1E ; in order to test for errors. QC_FLAGS EQU 0x1F ; 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 0x20 SerOutData EQU 0x121 ; page1 variable. HSVAL EQU 0x22 ; must be in page 0 (same page as status port). ;;; ;;; loop counters. ;;; COUNT1 EQU 0x123 ; used in qc_init; page 1. COUNT2 EQU 0x124 SAVE_RETVAL EQU 0x25 ; returned value from Command. ;;; ;;; Quickcam parameters. ;;; CONTRAST EQU 0x126 BRIGHTNESS EQU 0x127 MODE EQU 0x128 ; 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 0x29 BR0H EQU 0x2A TEMP_BR EQU 0x2B ;;; ;;; BSR save registers (for various routines). ;;; BSR_ASYNC_MODE EQU 0x40 BSR_GET_DATA_POLL EQU 0x41 ; (see SAVE_BSR and RESTORE_BSR macros) BSR_SEND_DATA_POLL EQU 0x42 BSR_SETUP_PORTLINESS EQU 0x43 BSR_QC_INIT EQU 0x44 BSR_COMMAND EQU 0x45 BSR_HANDSHAKE EQU 0x46 BSR_SET_PARAMS EQU 0x47 BSR_CLEARBRIGHTNESS EQU 0x48 BSR_STOREAVG EQU 0x49 ;;; ;;; NUMBER_7E is for comparison. Its value is set to 0x7E. ;;; NUMBER_7E EQU 0x4A #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 movlw A call Send_Serial_Data_Poll 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 movwf TMP_QCMD bcf QC_FLAGS, RESEND call Command cpfseq TMP_QCMD call send_error movlw B movwf TMP_QCMD call Command cpfseq TMP_QCMD call send_error ENDM ;;; ************* ;;; BEGIN PROGRAM ;;; ************* org 0 goto main ;;; skip reserved memory addresses org 0x21 ;;; 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. 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 Setup_Portliness SAVE_BSR BSR_SETUP_PORTLINESS movlb 1 ; initialize CONTROL to something useful (like its normal state). movlw B'00000100' ; inverted C0,1,3 movwf LP_CONTROL^0x100 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. movlb 1 clrf PORTE^0x100, F ; PORT E is hardware handshaking and debugging. movlw B'001' ; E0 input; E1, E2 outputs. movwf DDRE^0x100 ; outputs clrf DR_CONTROL^0x100, F clrf DR_DATA^0x100, F RESTORE_BSR BSR_SETUP_PORTLINESS return ;;; In the event that there's an error communicating with the QuickCam, notify me via the serial port. ;;; This won't work with the final version, but is fine for testing. send_error movwf TMP_QCMD2 SEND_SERIAL '*' movlw 'A' addwf TMP_QCMD, W call Send_Serial_Data_Poll movlw 'A' addwf TMP_QCMD2, W call Send_Serial_Data_Poll bsf QC_FLAGS, RESEND ; set an error flag 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 movlw B'00000000' ; inverted C0,1,3 on the parallel port (%1101) movwf LP_CONTROL^0x100 ;; 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 movlw B'0101' ; inverted C0,1,3 again. (%1110) movwf LP_CONTROL^0x100 ;;; 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. ; SEND_SERIAL 'C' ; SEND_SERIAL 'M' ; SEND_SERIAL 'D' ; SEND_SERIAL ':' ; movfp LP_DATA^0x100, WREG ; call Send_Serial_Data_Poll ; 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 ;;; 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 ;;; 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 call Send_Serial_Data_Poll ; 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. movlb 0 SEND_SERIAL 'B' SEND_SERIAL 'R' SEND_SERIAL '0' SEND_SERIAL ':' movfp BR0H, WREG call Send_Serial_Data_Poll ; to let us know what's going on, send it out the serial port. movfp BR0L, WREG call Send_Serial_Data_Poll ; 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' call Send_Serial_Data_Poll 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 btfss QC_FLAGS, AVERAGING call Send_Serial_Data_Poll ; send WREG via serial if we're not averaging. 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 ; btfsc QC_FLAGS, AVERAGING ; return ; don't send the EOF if we're averaging. SEND_SERIAL 'E' SEND_SERIAL 'O' SEND_SERIAL 'F' 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 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' 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. bcf PORTE^0x100, 1 goto all_done ;;; ;;; 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 END