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:
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
;********************************************************************************** ; 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:
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
Yorum Gönder