;********************************************************************************** ; A/D to RS232 for PIC12F675 ; Scott Olson, 03/07/2007 ; ; RS232, GP3 input (Rx), GP4 output (Tx). Analog inputs on AN0-AN2 (AN1=Vref) ; GP5 is a calibration output, it outputs a square wave of programable frequency ; ; Sends data as two's complement numbers, lsb first. ; 10 bit A/D values of 0-1023 are mapped to 0xFE00-0x01FF ; This is compatible with the "1 Channel Raw Data" Device in Chris Veigl's Brainbay ; Joerg Hansmann used this format in some early EEG archives ; ; Intitialized with 256 Hz sampling frequency (3906 usec delay) ; Cal output at 256/2/8 = 16 Hz ; ;********************************************************************************** list p=12f675 ; list directive to define processor #include ; processor specific variable definitions errorlevel -302 ; suppress message 302 from list file __CONFIG _CP_OFF & _CPD_OFF & _BODEN_OFF & _MCLRE_OFF & _WDT_OFF & _PWRTE_ON & _INTRC_OSC_NOCLKOUT ; Define variables ; Timer count. 65536-(time in usec) - instruction overhead (8) => 65528-time Tmsb equ 0xf0 ; count=3906 (3906.25 usec -> 256 Hz, so I'm off a little) Tlsb equ 0xb6 CalC equ D'08' ; initialize Calibration count to 8 (equates to 16 Hz, 256Hz/2/8) ; Define digital GPIO port pins RX equ 3 ; Receive Pin (GP3) TX equ 4 ; Transmit Pin (GP4) CAL equ 5 ; calibration output (GP5) TRISp equ B'001111' ; 0 is output, 1 is input ; this is for 9600 baud with 4 MHz clock BAUD_1 equ D'31' ; 11+3X = CLKOUT/Baud, 31 (66 for 4800) BAUD_X equ D'29' ; 16+3X = CLKOUT/Baud, 29 (64 for 4800) BAUD_4 equ D'39' ; 13+3X = 1.25*CLKOUT/Baud, 39 (82 for 4800) BAUD_Y equ D'30' ; 14+3X = CLKOUT/Baud, 30 (65 for 4800) CBLOCK 0x20 ; these 4 items are for RS232 code TxReg ; data to be transmitted RxReg ; Data received Count ; Counter for #of Bits Transmitted DlyCnt ADflag c_msb c_lsb RampH ; ramp counter RampL Tout1 ; These registers are from other code, only Tout3 is used here Tout2 ; 10 bit A/D 0-1023 maps to 0xFE00-0x01FF Tout3 ; Tout3 is used to store msb CalCnt ; counter for calibration output CalN ; count value for Cal counter ENDC ORG 0x3ff ; Internal RC oscillator callibration value RCosc retlw 0x80 ; Device ID 0FC3, 0x3460 TB MACRO char ; macro to transmit a byte movlw char call TxByte ENDM ;********************************************************************** ORG 0x000 ; processor reset vector goto Init ; go to beginning of program ORG 0x004 goto TimerInt ; these first 4 instructions are not required if the internal oscillator is not used Init bsf STATUS, RP0 ; Bank 1 call RCosc ; retrieve factory calibration value movwf OSCCAL ; update register with factory cal value bcf STATUS, RP0 ; Bank 0 ; Initialize ports movlw B'010000' ; Initial value of ports movwf GPIO movlw 0x07 ; set GP<2:0> comparator off movwf CMCON bsf STATUS, RP0 ; Bank 1 movlw B'01010111' ; 16*Tosc, analog inputs on AN2:AN0 , digital GP3 movwf ANSEL movlw TRISp ; see port definition equates movwf TRISIO bcf STATUS, RP0 ; Bank 0 movlw B'11000001' ; right justified, ref=Vref pin, chan AN0, turn on A/D movwf ADCON0 ; setup Timer1 and A/D movlw Tmsb ; initialize timer count registers movwf c_msb movwf TMR1H movlw Tlsb movwf c_lsb movwf TMR1L movlw CalC ; initialize calibration count movwf CalN movwf CalCnt clrf PIR1 ; clear interupt flags movlw B'00000001' ; timer1 on, 1:1 prescaler movwf T1CON bsf STATUS, RP0 ; Bank 1 movlw B'00000001' ; Enable Timer1 interupt movwf PIE1 bcf STATUS, RP0 ; Bank 0 movlw B'11000000' ; Enable interupts movwf INTCON bcf ADflag, 0 ; clear flag, ADstart bsf ADflag, 1 ; 0=stop, 1=go bcf ADflag, 2 ; 0=AD->RS232, 1=ramp->RS232 bcf ADflag, 3 ; 0=1 channel, 1=2 channels (always channels 0 and 2) ; *** 2 channels not valid in this code bsf ADflag, 4 ; cal output on bsf Tout3, 7 ; set pilot bits *** this is not needed for bcf Tout3, 3 ; *** for raw format Main btfsc ADflag, 0 ; wait for flag to start A/D data goto startAD btfss GPIO, RX ; check for a RS232 Start Bit goto GetCmd goto Main ; loop if nothing there ;***************************************************************** ; all commands start with an ASCII '0' (one long low), use this to resync to next start bit GetCmd bcf T1CON, TMR1ON ; stop timer wRE btfss GPIO, RX ; wait for next rising edge goto wRE call RxByte ; command is now in RxReg movlw D'65' ; "A" is A/D->232 subwf RxReg, w ; A specifies A/D channels: A0, A1, A2 or A3. A3 means A0 & A2 btfsc STATUS, Z goto AD movlw D'67' ; "C" is Calibrate subwf RxReg, w ; C specifies calibration output count which is A/D sample count btfsc STATUS, Z ; times n. C0 turns off cal output. goto cal movlw D'71' ; "G" is go subwf RxReg, w ; Starts RS232 output btfsc STATUS, Z goto go movlw D'78' ; "N" is Counter subwf RxReg, w ; N specifies the A/D sample count. The actual number of the btfsc STATUS, Z ; count is in milliseconds is 65528-N. Also effects cal output frequency goto counter movlw D'80' ; "P" is Prescaler subwf RxReg, w ; A/D sample count (and cal frequency) is also a function of the prescaler btfsc STATUS, Z ; Use this only to get really slow sampling (non EEG applications) goto prescaler movlw D'83' ; "S" means stop subwf RxReg, w ; Stops RS232 output btfsc STATUS, Z goto stop movlw D'82' ; "R" is ramp->RS232 subwf RxReg, w ; Send ramp signals instead of A/D samples btfsc STATUS, Z ; (an artifact from early software debug) goto ramp movlw D'86' ; "V" is Vref (also Version information) subwf RxReg, w ; V0 = use Vref pin input (A1) as A/D reference voltage btfsc STATUS, Z ; V1 = use Vdd as A/D reference voltage goto vref ; V2 = use Vref, also send software version info, and stop ; V3 = use Vdd, also send software version info, and stop TB "B" ; Whoops! we didn't see a valid command. Say so and stop. TB "a" TB "d" TB " " TB "C" TB "m" TB "d" goto stop ; not a command ;***************************************************************** ; Command functions AD bcf ADflag, 3 ; clear number of channels flag (1 channel) bcf ADflag, 2 ; "A" is A/D channel where call RxByte ; 0=chan0, 1=chan1, 2=chan2, 3=chan0 and chan2 bcf ADCON0, 2 ; clear lsb of A/D selected channel bcf ADCON0, 3 ; clear msb of A/D selected channel btfsc RxReg, 1 goto tor2 ; msbit (of 2 bits) was a 1, which means either chan2 or 2-channels zor1 btfsc RxReg, 0 ; channel 0 or 1 bsf ADCON0, 2 ; channel 1 goto resume tor2 bsf ADCON0, 3 ; channel 2 or 0and2 btfss RxReg, 0 goto resume ; channel 2 bcf ADCON0, 3 ; channels 0 and 2, reset for channel 0 first bsf ADflag, 3 ; set 2 channel flag goto resume cal call RxByte movf RxReg, w iorlw 0 ; is cal count a 0? btfss STATUS, Z goto scal ; no, set cal counter bcf ADflag, 4 ; turn off cal function goto resume scal movwf CalN ; store new cal counter value bsf ADflag, 4 ; turn on cal function goto resume go bsf ADflag, 1 goto resume counter call RxByte movf RxReg, w movwf c_msb call RxByte movf RxReg, w movwf c_lsb goto resume prescaler call RxByte movf T1CON, w ; get present register state andlw B'11001111' ; keep all but prescaler bits movwf T1CON ; put it back rlf RxReg, f ; align prescaler RS232 command with control reg position rlf RxReg, f rlf RxReg, f rlf RxReg, f movf RxReg, w ; get number from RS232 buffer andlw B'00110000' ; mask off all but prescaler bits iorwf T1CON, f ; put new prescaler bits in control reg goto resume ramp bsf ADflag, 2 goto resume stop bcf ADflag, 1 goto resume vref call RxByte ; use Vdd or Vref pin for A/D reference? (0=Vref pin, 1=Vdd) bcf ADCON0, 6 ; to use Vdd btfss RxReg, 0 bsf ADCON0, 6 ; to use Vref pin btfss RxReg, 1 ; also send version info? goto resume ; no, just resume TB "p" TB "i" TB "c" TB "A" TB "D" TB " " TB "v" TB "0" TB "." TB "0" TB "1" TB "d" goto stop resume bsf T1CON, TMR1ON ; start timer goto Main ;***************************************************************** ; Start A/D conversion. First check if cal output should be toggled startAD btfss ADflag, 4 ; do we want a cal output? goto AD7 ; no, just do A/D [3] [number is how many cycles] decfsz CalCnt, f ; yes, decrement cal counter goto AD5 ; don't do anything if count not complete [5] btfsc GPIO, CAL ; toggle cal output goto Clo ; cal output is high, make it go low nop bsf GPIO, CAL ; cal output is low, make it go high [7] movf CalN, w ; reset cal counter movwf CalCnt goto AD0 ; [10] Clo bcf GPIO, CAL ; [7] movf CalN, w ; reset cal counter movwf CalCnt goto AD0 ; [10] AD7 nop ; these nops are so that the timing is the same AD6 nop ; no matter what path it took to get to AD0 AD5 nop AD4 nop AD3 nop AD2 nop AD1 nop AD0 bcf ADflag, 0 ; clear flag bcf PIR1, 6 bsf ADCON0, 1 ; start A/D wAD btfss PIR1, 6 ; wait for A/D completion goto wAD btfss ADflag, 1 ; go/stop flag goto Main btfss ADflag, 2 ; AD/Ramp flag goto sendAD goto sendINC ;***************************************************************** ; this is legacy stuff to test RS232 hardware and protocol sendINC incf RampL, f ; increment 16 bit counter btfsc STATUS, Z ; (only care about 10 bits) incf RampH, f movf RampL, w ; send out lsb call TxByte ; 1st packet byte btfss GPIO, RX ; check for a RS232 Start Bit (incoming command?) goto GetCmd movf RampH, w ; get msb andlw B'00000011' ; mask off top 6 bits movwf Tout3 decf Tout3, f ; format for 1 raw decf Tout3, f srT3 movf Tout3, w call TxByte ; send last byte in packet goto Main ;***************************************************************** sendAD movf ADRESH, w ; get msb andlw B'00000011' ; mask off top 6 bits movwf Tout3 ; save decf Tout3, f ; format for 1 raw decf Tout3, f bsf STATUS, RP0 ; Bank 1 (i hate banks) movf ADRESL, w ; get lsb bcf STATUS, RP0 ; Bank 0 (i still hate banks) call TxByte ; send lsb btfss GPIO, RX ; check for a RS232 Start Bit (incoming command?) goto GetCmd sT3 movf Tout3, w ; send last packet byte (formated msb) call TxByte ; send it goto Main ;***************************************************************** ; Interupt service routine TimerInt bcf T1CON, TMR1ON ; stop timer movf c_msb, w ; initialize timer movwf TMR1H movf c_lsb, w movwf TMR1L bsf ADflag, 0 ; set A/D flag to tell main routine to do a converstion bcf PIR1, TMR1IF ; clear interupt flag bsf T1CON, TMR1ON ; start timer retfie ; return from interupt ;***************************************************************** ; Byte is returned in RxReg RxByte: btfsc GPIO, RX ; wait for a Start Bit goto RxByte Rcvr: call Delay4 ; delay for 104+104/4 movlw 8 ; 8 Data bits movwf Count R_next: bcf STATUS,C rrf RxReg,f btfsc GPIO, RX bsf RxReg,7 call DelayY decfsz Count,f goto R_next return ;********************************************************** ; put byte for transmission in w TxByte: movwf TxReg Xmtr: movlw 8 ; initialize bit counter movwf Count bcf GPIO,TX ; Send Start Bit call Delay1 X_next: rrf TxReg, f btfsc STATUS, C goto Tx1 Tx0 nop ; see that bit is send at same time whether 0 or 1 bcf GPIO, TX ; send a 0 goto D1b Tx1 bsf GPIO, TX ; send a 1 nop ; see that timing is the same as that of a 0 nop D1b call DelayX decfsz Count, f goto X_next bsf GPIO, TX ; Send Stop Bit call Delay1 ; close enough (maybe 1 cycle off) retlw 0 DelayY: movlw BAUD_Y goto save DelayX: movlw BAUD_X goto save Delay4: movlw BAUD_4 goto save Delay1: movlw BAUD_1 goto save save: movwf DlyCnt redo_1: decfsz DlyCnt,f goto redo_1 retlw 0 END