8 Aralık 2012 Cumartesi

EEG MACHINE

SOURCE :http://www.pkfamily.com/users/solson/eeg/eeg.html


Pictures:
My first breadboarded EEG hardware. The basic design was INA114 inst-amp, TLC1078 op-amp, MAX7401 filter, PIC12F675, RS232 interface, PC running LabVIEW. Single 5.0 volt supply, with a TLE2426 "rail splitter" driving a 2.5v VGND. Another TLC1078 is used for a DRL (driven right leg) circuit. It is basically a simplified ModularEEG system with NO input protection, op-amp filter replaced with MAXIM filter, and Atmel processor replaced by a PIC. In the picture, the green board on the right has a MAX232 RS232 interface on it. Unit draws 14 mA with RS232 interface, 5 mA without. With this hardware I measured these ECG signals on my chest. On the blue trace (marked ECG G10) the instrumentation amp (inst-amp) had a gain of 10, and my system had a gain of 1000 (ECG mode). On the red trace (marked ECG G100) the inst-amp had a gain of 100, and my system had a gain of 10000 (EEG mode). In EEG mode my signals clip at 175uV, caused by non rail-to-rail output of the TLC1078. I have since replaced the TLC1078 with an AD8605 to remedy this. In EEG mode it had more 60 Hz noise which was improved with a better layout and gain line up.
My application software is written in LabVIEW. Here is a snapshot of the panel It reads in one or two channels of data and does an FFT. The PIC software takes commands from a PC via RS232, and sends 10 bit A/D samples to the PC. The PIC has a 4 channel A/D, I use 3 of them. PIC is configurable through RS232 commands for variable sample rate, variable calibration output frequency, which A/D channels to read, what to use as a reference voltage (5v supply or one of the A/D inputs). I use a software UART at 9600 baud, so I only have time to send two A/D channels at 256 Hz. (3 bytes for 2 10bit samples, sent ModEEG -p3 like). This lets me get away with using the PIC's internal 4MHz oscillator (look Mom, no crystal!). My target was something with very low part count (something I can easily breadboard), very low cost (my cost so far has been a $10 jar of Ten20 EEG paste!), and performance on par with similar home units.
Here is a cleaned up breadboard. This less sprawling version has less 60 Hz noise (no surprise). Here is a picture with electrodes attached. The static wrist strap I used for my DRL. I used two of the white round Meditrace electrodes to take the ECG data I showed previously. The gold plate is an unfinished homemade EEG electrode. The PCB sticking out of the breadboard in an opto-isolated RS232 interface. Here is two seconds of EEG data with an FFT. You can see some 60 Hz noise (this is actually quite low, which is good), and some brain activity around 10 Hz.


Here is the EEG amplifier and microprocessor. The schematic shows a TL062 for the DRL op-amp, but my breadboard still has a TLC1078. Here is a schematic of my opto-isolated RS232 interface. It may not be bullet proof, but I've used it with one laptop and two desktop PCs at 9600 baud. It derives power from the RS232 lines. I only use it half duplex. I based the RS232 interface design on this one, by M Asim Khan, but I removed the LEDs and the buffers (among other things). A PIC12F675 running RS232 loopback code with this interface draws 0.8 mA sending 0x00 and 1.4 mA sending 0xFF (or vice versa, I forget). Here is a parts list with prices.
My planned to do list:



  • run it off of 6 volts (4 AA cells) with 5V LDO regulator (I use a wall wart with a 7805 regulator)
  • finish making EEG electrodes
  • compare inst-amps (I have INA114, INA128, AD8553 and LT1168)
  • add beta/theta training to LabVIEW software (maybe not since I can now use Brainbay)


  • Prices of various parts taken off of the web sites of Digikey and Futurlec
    (I've sampled all my semiconductors, so they haven't cost me anything)
    
    
    
    If you wanted to build my EEG system:
    
    ------------------------------------------------
    Analog section
    --------------
    INA114    $7.90 Futurlec
    AD8605    $1.68 Digikey
    MAX7401   $2.65 Digikey
    TL062     $0.25 Futurlec
    ------------------------------------------------
    Digital section
    ---------------
    PIC12f675 $1.90 @Futurlec, $1.93 @Digikey
    78L05     $0.20 Futurlec
    TLE2426   $1.30 Digikey
    PC817     $0.15 Futurlec (need 2)
    DSUBPCF9  $0.55 Futurlec
    4xAA Battery Holder:  $0.30 Futurlec
    ------------------------------------------------
    various resistors and capacitors (price unknown)
    ------------------------------------------------
    
    
    ==================================================
    
    
    Various parts that I have priced:
    
    Instrumentation Amps
    INA114      7.90 Futurlec, 8.40 Digikey (5.25/1000)
    INA128U     9.40 Digikey
    INA128UA    5.80 Digikey
    LT1168      3.70/1000 (Linear's listed price)
    AD8553      2.26/250, 1.64/3000 Digikey
    
    Op Amps
    TL062       0.25 Futurlec
    TLC277      1.78 Digikey
    OPA277U     2.03 Digikey
    AD8605      1.68 Digikey
    
    Filters
    MAX296      4.43 Digikey
    MAX7401     1.98 in 1k quantities, $2.65 Digikey
    
    Power supply / Voltage reference
    78L05       0.20 Futurlec
    TL431       0.35 Futurlec
    TLE2426CLP  1.30 Digikey
    
    Digital stuff
    PIC12f675   1.90 Futurlec, 1.93 Digikey
    MAX232N     1.50 Futurlec, 0.90 Digikey
    MAX232DR     .85 Futurlec
    
    Hardware
    4xAA Battery Holder  $0.30 Futurlec
    DSUBPCF9             $0.55 Futurlec





  • CODE FOR PIC

  • ;**********************************************************************************
    ; 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 <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=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<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     "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
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    OLD CODE 
    
    
    
    
    ;**********************************************************************************
    ; 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
    
    
    
    
    
    
  • 1 yorum:

    gustux dedi ki...

    Hi,
    im an egineering student and im developing a EEG system based on the OpenEEG project using LabVIEW to capture and process the signals.
    Reading your article, i realize that you successfully captured the serial signals from the OpenEEG and aplied the FFT transform to it, and im wondering if you could help me with it.
    Thank you
    Gustavo gustux@gmail.com