;**********************************************************************************
; A/D to RS232 for PIC12F675
; Scott Olson, 7/10/2006
;
; 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
;
; packets are ModularEEG -p3 like.
; -p3 only has even number of channels, and it has a header. Mine has one or two channels and no header
;
; One channel packet (AN0,AN1 or AN2)     Two channel packet (always AN0 and AN2)
; 0aaaaaaa                                0aaaaaaa
; 1aaa0000                                0bbbbbbb    
; repeat                                  1aaa0bbb     
;                                         repeat           
;                                  
;**********************************************************************************

	list p=12f675           ; list directive to define processor
	#include <p12f675.inc>	; 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=4000 (4 msec -> 250 Hz)
Tlsb    equ     0x58
CalC    equ     D'10'   ; initialize Calibration count to 10 (equates to 12.5 Hz, 250Hz/2/10)

; 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
RampL
Tout1                  ;  first byte in packet 0aaaaaaa : 7 lsb of 1st channel
Tout2                  ; second byte in packet 0bbbbbbb : (or 1aaa00000 if only one channel)
Tout3                  ;  third byte in packet 1aaa0bbb : 3 msb of both channels
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)
      bcf    ADflag, 4        ; cal output off
      bsf    Tout3, 7         ; set pilot bits
      bcf    Tout3, 3

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<n> 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<n> 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<msb><lsb> 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     "c"
      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
      incf   RampH, f
      movf   RampL, w         ; send out lsb
      movwf  Tout1
      bcf    Tout1, 7
      movf   Tout1, w
      call   TxByte           ; 1st packet byte
      btfss  GPIO, RX         ; check for a RS232 Start Bit (incoming command?)
      goto   GetCmd
      clrf   Tout3            ; format last byte
      bsf    Tout3, 7
      btfsc  RampL, 7
      bsf    Tout3, 4
      btfsc  RampH, 0
      bsf    Tout3, 5
      btfsc  RampH, 1
      bsf    Tout3, 6
      btfss  ADflag, 3        ; are we in 2 channel mode?
      goto   srT3             ;    if not, just send last byte
      movf   RampL, w         ; just send RampL <7:0>
      movwf  Tout2
      comf   Tout2, f         ; compliment it to make it more interesting
      btfsc  Tout2, 7
      bsf    Tout3, 0         ; do 7th bit
      bcf    Tout2, 7
      movf   Tout2, w
      call   TxByte           ; send 2nd byte in 3 byte packet
      btfss  GPIO, RX         ; check for a RS232 Start Bit (incoming command?)
      goto   GetCmd
srT3  movf   Tout3, w
      call   TxByte           ; send last byte in packet
      goto   Main

;*****************************************************************
sendAD
      movf   ADRESH, w        ; get msb
      movwf  Tout3            
      swapf  Tout3, f         ; shift bits 4 to left         
      bcf    STATUS, C
      rlf    Tout3, f
      bsf    STATUS, RP0      ; Bank 1 (i hate banks)
      movf   ADRESL, w        ; get lsb
      bcf    STATUS, RP0      ; Bank 0 (i still hate banks)
      movwf  Tout1
      btfsc  Tout1, 7         ; put bit 7 in Tout3 bit 4 (if zero, its already there)
      bsf    Tout3, 4
      bcf    Tout1, 7         ; make sure bit 7 of  Tout is clear
      bsf    Tout3, 7         ; set bit 7 of Tout3 (sync bit)
      movf   Tout1, w         ; get 1st byte to send
      call   TxByte           ; send 1st channel lsb <6:0>
      btfss  GPIO, RX         ; check for a RS232 Start Bit (incoming command?)
      goto   GetCmd
      btfss  ADflag, 3        ; do we send 2 channels?
      goto   sT3              ;   no, jump to send Tout3
      bsf    ADCON0, 3        ; prepare for A/D on channel 2
                              ; at this point there needs to be a 10us setup time ?
                              ; but did we already get it from the TxByte time, or do
                              ; we need it after setting the channel mux?
      movlw  D'10'            ; for safety, I'll put in the delay
      movwf  DlyCnt
sud   decfsz DlyCnt, f
      goto   sud

      bcf    PIR1, 6          ; clear "A/D done" flag
      bsf    ADCON0, 1        ; start A/D
wAD2  btfss  PIR1, 6          ; wait for A/D completion
      goto   wAD2
      ; we have a second channel to send (2nd byte of 3 byte packet)
      btfsc  ADRESH, 1
      bsf    Tout3, 2         ; put bit 9 in place
      btfsc  ADRESH, 0
      bsf    Tout3, 1         ; put bit 8 in place
      bsf    STATUS, RP0      ; Bank 1 (i hate banks)
      movf   ADRESL, w        ; get lsb
      bcf    STATUS, RP0      ; Bank 0 (i still hate banks)
      movwf  Tout2
      btfsc  Tout2, 7
      bsf    Tout3, 0         ; put bit 7 in place
      bcf    Tout2, 7         ; make sure bit 7 is clear 
      bcf    ADCON0, 3        ; prepare for A/D channel 0 on next round
      movf   Tout2, w
      call   TxByte           ; send second channel lsb <6:0>
      btfss  GPIO, RX         ; check for a RS232 Start Bit (incoming command?)
      goto   GetCmd
sT3   movf   Tout3, w         ; get last byte to send <1aaa0bbb>
      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
