;;; ;;; 2000 Bike-trip microcontroller code ;;; ;;; -- Jorj Bauer , 2/4/00 ;;; ;;; 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: 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. 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 clc bcf ALUSTA, C ; A quick definition: CLC is "Clear Carry". ;;; ;;; Variable space ;;; ;;; temporary space for storing/restoring registers. ;;; TEMP_BSR EQU 0x1A ; TEMP_BSR and TEMP_BSR2 must be in page 0 TEMP_BSR2 EQU 0x1B ; (see SAVE_BSR and RESTORE_BSR macros). 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. RESEND EQU 0x1F ; set to 0x1 if an error occurred while sending a command. ;;; ;;; 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. #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 clrf RESEND, F 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 TEMP_BSR 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 TEMP_BSR return Get_Serial_Data_Poll SAVE_BSR TEMP_BSR 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 TEMP_BSR return ;;; Send_Serial_Data_Poll sends whatever's in WREG. Send_Serial_Data_Poll SAVE_BSR TEMP_BSR 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 TEMP_BSR return Setup_Portliness SAVE_BSR TEMP_BSR 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) movlb 1 clrf PORTE^0x100, F ; for debugging, port E is an output. clrf DDRE^0x100, F ; outputs clrf DR_CONTROL^0x100, F clrf DR_DATA^0x100, F RESTORE_BSR TEMP_BSR 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 RESEND, 0 ; set an error flag return qc_init SAVE_BSR TEMP_BSR movlb 1 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 TEMP_BSR 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 TEMP_BSR movlb 1 movwf LP_DATA^0x100 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 TEMP_BSR 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 TEMP_BSR2 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 TEMP_BSR2 ; 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 ;;; ;;; Main entry point. ;;; main WAIT 0x0 ; Take a little nap when we start up (256x). call Setup_Async_Mode call Setup_Portliness call qc_init ; initialize the quickcam. call qc_init ; initialize the quickcam. Do we still have to ;; do this twice? SEND_SERIAL 'H' ; Just so we know it's running properly. SEND_SERIAL 'i' ;; Set up the quickcam properties (brightness, height, width...). ;; If any of them fail, we re-send it until it gets it right. We ;; should actually keep some count of how many failures we've had, ;; and bail out if it gets to be too bad (because the quickcam is ;; unplugged, or whatever). qcmd_brightness QCMD 0xB, D'150' ; brightness btfsc RESEND, 0 goto qcmd_brightness SEND_SERIAL '.' qcmd_height QCMD 0x11, D'240' ; height in pixels btfsc RESEND, 0 goto qcmd_height SEND_SERIAL 'o' qcmd_width QCMD 0x13, D'160' ; "width" (= width/2 for 4bpp) btfsc RESEND, 0 goto qcmd_width SEND_SERIAL 'O' qcmd_top QCMD 0xD, 0x1 ; top btfsc RESEND, 0 goto qcmd_top SEND_SERIAL 'o' qcmd_left QCMD 0xF, 0x7 ; left btfsc RESEND, 0 goto qcmd_left SEND_SERIAL '.' qcmd_contrast QCMD 0x19, D'180' ; contrast btfsc RESEND, 0 goto qcmd_contrast SEND_SERIAL 'o' qcmd_whitebal QCMD 0x1F, D'70' ; white balance btfsc RESEND, 0 goto qcmd_whitebal SEND_SERIAL 'O' qcmd_scan QCMD 0x7, 0 ; scan a 320x240 4bpp picture. btfsc RESEND, 0 goto qcmd_scan SEND_SERIAL 'o' readbytes movlb 1 movlw 0xF1 ; 240 lines to transfer (1-based, not 0-based) movwf COUNT1^0x100 movlw 0xA1 ; 160 bytes per line (also 1-based) movwf COUNT2^0x100 ;;; The number of bytes we're reading is actually incorrect here, but I just stop the data ;;; capture when the quickcam is finished ("EOF" is never reached). I'll fix this in the final ;;; version... 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. incf WREG, F ; add one to the result (so we don't send lots of 0's) call Send_Serial_Data_Poll ; send WREG via serial. 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 SEND_SERIAL 'E' SEND_SERIAL 'O' SEND_SERIAL 'F' all_done ; finished! Turn on an LED to show it. movlw 0x07 ; not currently reached... movwf PORTE^0x100 goto all_done END