;Copyright 2003 Richard Cappels, projects@cappels.org http://projects.cappels.org ; ;A Microcontroller Based Digital Lock-In Millihommeter ;Using the Atmel AT90S2313 Microcontroller or Equivalent.;Firmware expects a 4 Mhz AT90S2313. 313def.inc, available ;Atmel (www.atmel.com) is needed to assemble this file. ;You may need to correct the path to this file.;This microcontroller provides 1 kHz pulses on opposite ;phases on PB4 and PB6 to drive the switches in the ;synchronous demodulator, 1 Khz bursts on PB3 to drive ;current into the resistance under test via an inverting ;driver during every other measurement cycle, charge measure ;and capacitor clamp signals for the A/D conversion on PD5 ;and PD4 respectively, a comparator to measure the analog ;signal on the non-inverting comparator input, a zero button ;input with and internal weak pull-up on PD6, and a positive ;going measurement in progress LED drive pulse that goes high ;while the 1 kHz burst is being applied to the resistance ;under load on PD7. The output of this circuit is ASCII on EIA-232 ;and an inverting buffer is expected on the transmit data pin. .include "2313def.inc" ;Include file in same directory as project. .def chargecount = r2 ;Value of measuered charge in integrator .def counterlow = r3 ;Lower byte of the preload for 4 kHz interrupt. Used for programmability .def zerochargecount = r4 ;Value of charge on prior measurement .def quarterycles = r5 ;Value to change timer low .def meascount = r6 ;Count of measurements modulus 256. Used for alternative meas output .def zerovalue = r7 ;Value measured when looking at "zero" ohms. .def temp =r16 ;Scratch register .def temp2 =r17 ;Second scratch register .def outpulse =r18 ;Interrupt counter .def datab =r19 ;Temporary data for PORTB, to be output every interrupt time (only 4 lsb available). .def flagreg =r21 ;Flags stored here .def H = r22 ;Used to send decimal ascii (Hundreds) .def T = r23 ;Used to send decimal ascii (Tens) .def U = r24 ;Used to send decimal ascii (Units) ;NOTE: Y register is used as integration timer ;definition of I/O ;B0 COMPARITOR NONINVERTING ;B1 COMPARITOR INVERTING ;B2 (not assigned) ;B3 SAMPLE CLOCK PHASE 1 ON EVEN MEASUREMENTS ;B4 SAMPLE CLOCK PHASE 1 ;B5 SAMPLE CLOCK PHASE 2 ;B6 SAMPLE CLOCK PHASE 3 ;B7 SAMPLE CLOCK PHASE 4 ;D0 USED FOR UART RECEIVE INPUT ;D1 Reserved FOR UART TRANSMIT ;D2 (not assigned - configure as INPUT) ;D3 LED - SIGNAL ABOVE THRESOLD ;D4 CLAMP INTEGRATOR TO ZERO ;D5 MEASURE ;D6 Zero pushbutton (closure to ground) INPUT ;D7 Not available on AT90S2313 ;definition of flagreg bit assignments .equ meastaken =0 ;Set after new measurement taken. .equ adcover =1 ;Set by measurecharge if ADC counts past max.(Overflow) .equ zeropend =2 ;Set to indicate zero button has been pressed ;3 ;4 ;5 ;6 ;7 .equ counterhigh =$03 ;High byte of the perolaod for 4 kHz interrupt .equ measuretime =$E7 ;Points to detector cycle that measurement is taking place .equ measuretimeH =$03 .equ resettime =$E8 .equ resettimeH =$03 .equ outpulsepattern =0b00110011 ;Note: As used here, the high and low nybble are used sequentially,and ;repeats every two cycles. For odd and even cycles to be identicle, both nybbles should be identicle. ;The pattern "0b00110011 would result in a 50% duty cycle at 1 kHz. .equ Zeroloc =$2A ;EEPROM location in which zero value is stored. ;UART baud rate calculation .equ clock = 4000000 ;clock frequency .equ baudrate = 9600 ;choose a baudrate .equ baudconstant = (clock/(16*baudrate))-1 .cseg .ORG $0000 ;Main program entry point on reset rjmp start .ORG $0004 rjmp timerservice ;Timer/counter compare interrupt handler HelloString: ;TEXT TO BE TYPED ON FIRST LINE WHEN POWER IS APPLIED .db "V031002A Max=190" ;Version number, reminder of A/D maximum count .db $0A,$0D .db 00,00 start: ldi r16,RAMEND ;Init Stack Pointer out spl,r16 ldi temp,baudconstant out ubrr,temp ;Load baudrate. ; sbi ucr,rxen ;Enable WAUT receive ;Uart receive not used so not enabled bere. sbi ucr,txen ;Enable UART Transmit ldi outpulse,outpulsepattern;Initial pulse ouput pattern ldi flagreg, 0b00000000 ;Initialize all flags clr YL ;Initialize integration cycle counter clr YH ldi temp,$04 ;Initialize quarter cycle counter mov quarterycles,temp ldi temp,$E8 ;Initialize the variable for interrupt timer mov counterlow,temp ldi temp,$FF ;Set data on PORTD and PORTB high to act as weak pullups. out PORTD,temp out PORTB,temp ldi temp,0b11111100 ;Make pins on PORTB, except comparitor pins, ouputs. out DDRB,temp ldi temp,0b10111010 ;Establish direction of PORTD pins. out DDRD,temp ldi temp,$09 ;Set timer 1 to reset 0000 after compare match. Prescaler = 1X out TCCR1B,temp ldi temp,counterhigh ;Set compare register to $03E8 for 4 khZ interrupt out OCR1AH,temp mov temp,counterlow out OCR1AL,temp ;Read contents of EEPROM into zerovalue register (recall zero refernece) wratzA: sbic eecr,eewe ;Wait for EEPROM write to not be busy rjmp wratzA ldi temp,Zeroloc out eear,temp ;Move to EEPROM address register sbi eecr,eere ;Trigger the read in zerovalue,eedr ;Get the data into temp clr meascount ;Set measurement counter mod 256 to zero ldi temp,$40 ;Enable interrupt on compare match out TIMSK,temp sei ;Enable interrupts -start measurements Wait1: mov temp,meascount ;Wait until meascount = 1 cpi temp,1 ;Wait for display to come on before hello message. brne Wait1 ;This is useful for display on the same power switch. rcall SendHello Wait2: mov temp,meascount cpi temp,3 ;(wait for circuit to settle) brne Wait2 rjmp Main ;Go to main loop and wait for interrupt SendHello: ;Send HelloString ldi ZH,high(2*HelloString) ; Load high part of byte address into ZH ldi ZL,low(2*HelloString) ; Load low part of byte address into ZL rjmp sendstring SendHedding: ;Send Hedding ldi ZH,high(2*Hedding) ; Load high part of byte address into ZH ldi ZL,low(2*Hedding) ; Load low part of byte address into ZL rjmp sendstring Hedding: ;TEXT TO BE TYPED ON FIRST LINE BEFORE DISPLAYING EACH MEASUREMENT .db "mOhms Offset " .db 00,00 Sendoverrange: ;Send Overrange message ldi ZH,high(2*erormessage) ; Load high part of byte address into ZH ldi ZL,low(2*erormessage) ; Load low part of byte address into ZL rjmp sendstring erormessage: ;TEXT TO BE TYPED WHEN ADC OVER RANGE IS OBSERVED .db "ERROR ADC>190 " .db 00,00 Sendzero: ;Send Zeroing message ldi ZH,high(2*zeromessage) ; Load high part of byte address into ZH ldi ZL,low(2*zeromessage) ; Load low part of byte address into ZL rjmp sendstring zeromessage: ;TEXT TO BE TYPED AT START OF ZEROING COUNT .db " SETTING ZERO" .db $0A,$0D .db " ************" .db 00,00 Sendstring: ;Send string pointed to by ZH,ZL. lpm ; Load byte from program memory into r0 tst r0 ; Check if we've reached the end of the message breq stringdone ; If so, return mov temp,r0 rcall emitchar adiw ZL,1 ; Increment Z registers rjmp Sendstring stringdone: ret Main: sbic PIND,6 ;Check to see if zero button is down. rjmp nozbutdown ;If not down, skip to next task. sbrc flagreg,zeropend rjmp nozbutdown rcall sendzero ;Send message to dispalay. rcall crlf ori flagreg,0b00000100 ;Set flag to make zero operation pending. nozbutdown: cbi PORTD,3 mov temp,meascount andi temp,0b00000001 brne notdoinit sbi PORTD,3 ;Light LED during measurement notdoinit: sbrs flagreg,meastaken rjmp Main ;Return if this is not first call after signal measurement andi flagreg,0b11111110 ;Clear the flag mov temp,meascount ;Label if offset andi temp,0b00000001 brne notoffset rjmp main notoffset: ;If actual meausrement either store as zero reference ;or send out via serial port sbrc flagreg,zeropend rjmp dozero ;Zero button is down, so save zero reference sbrs flagreg,adcover ;If ADC has overflowed then show error message rjmp dohedding ;instead of reagular hedding. rcall sendoverrange andi flagreg,0b11111101 ;Clear ADC overflow flag rjmp nohedding dohedding: rcall sendhedding nohedding: rcall crlf mov temp,chargecount ;Send the latest measuremnet followed by a CR and LF. sub temp,zerovalue ;Subtract zero offset. brcc notnegative push temp ;Save temp on stack. ldi temp,'-' ;Send minus sign. rcall emitchar clr temp pop temp2 sub temp,temp2 ;Make number positive. notnegative: rcall sendnumber ;Send resistance in milliohms to serial port. ldi temp,$20 rcall emitchar ldi temp,$20 rcall emitchar ldi temp,$20 rcall emitchar ldi temp,$20 rcall emitchar ldi temp,$20 rcall emitchar mov temp,zerochargecount ;Send the amplifier zero SIGNAL offset rcall sendnumber ldi temp,$20 rcall emitchar ldi temp,'[' ;Send data separator rcall emitchar mov temp,zerovalue ;Send zero ohm reference count rcall sendnumber ldi temp,']' ;Send data separator rcall emitchar rcall crlf rjmp Main dozero: ;Save measurement value as zero reference ; rcall sendzero ;Send message to dispalay. ; rcall crlf ; ldi temp,$2A ; rcall emitchar andi flagreg,0b11111011 mov zerovalue,chargecount ldi temp,Zeroloc wrat1: sbic eecr,eewe ;Wait for EEPROM write to not be busy rjmp wrat1 out eear,temp out eedr,zerovalue ;Set up the write data cli ;Inhibit interrupts -**** INTERRUPTS NOT RE-ENABLED CAUTION! sbi eecr,eemwe sbi eecr,eewe sei ;Re-enable interrupts butdown: sbic PORTD,6 ; rjmp Main rjmp butdown ;Wait for button to come up sendnumber: ;Enter with value to be sent in temp, sends as three digit ascii ;via serial port. mov U,temp ;Enter with 8 bit value in U, Exits with numerals in H,T,U clr H ;(Hundreds, Tens, and Units). clr T anotherh: subi U,100 ;Find out how many hundreds in U. brcs hdone inc H rjmp anotherh hdone: subi U,-100 ;Subtracted one too many -add back. anothert: subi U,10 brcs tdone inc T rjmp anotherT tdone: subi U,-10 cpi H,0 breq dontsendh subi H,-48 mov temp,H rcall emitchar cpi t,0 brne dontsendh ldi temp,$30 ;If U=0 then don't emit this zero rcall emitchar dontsendh: cpi t,0 breq dontsendt subi T,-48 mov temp,T rcall emitchar dontsendt: subi U,-48 mov temp,U rcall emitchar ret crlf: ;Send return and linefeed ldi temp,$0A ;Send return and line feed rcall emitchar ldi temp,$0D rcall emitchar ret emitchar: ;Send character contained in temp with two stop bits. sbis usr,udre ;wait until the register is cleared. rjmp emitchar cbi ucr,0 ;Set 9th bit to zero (second stop bit). sbi ucr,2 ;Set to send 9 data bits. out udr,temp ;send the character. ret measurecharge: ;Measure time it takes to zero integrator. Output range is $80 to $00, ldi temp,$42 ;WAS $42 mov chargecount,temp;Set chargevalue to $42 so it will only spend 190 cycles in this routine. ori flagreg,1 ;Set meastaken flag. cbi PORTB,4 ;Make both phase 1 and phase 3 low during the measurement cbi PORTB,6 ;so incoming signals don't affect measurement accuracy. sbi PORTD,5 ;Turn on discharge resistor.;(Discharge resistor turned off after return). sbic acsr,aco ;If comparitor is high, then charge is zero or negative. In this case. ret ;Skip measurement and go on to make sure capacitor is discharged. morch: inc chargecount ;Stay in a counting loop until the comparitor goes high. breq saturated ;Or chargecount counts up to zero sbis acsr,aco rjmp morch ret ;Normal return - measurement completed. saturated: ori flagreg,0b00000010 ;Set ADC overflow flag ret ;(Discharge resistor turned off after return) writeportb: ;Output outpulse combined with datab lower 4 bits to PORTB mov temp,datab andi temp,$0F mov temp2,outpulse andi temp2,$F0 or temp,temp2 out PORTB,temp ret timerservice: ;Timer 1 interrupt service push temp push temp2 in temp,sreg push temp clc rol outpulse ;Rotate bit image and output to PORTB. brcc B1 ;If carry set, set bit 0. ori outpulse,$01 B1: ;If meascount cycle is even, copy datab bit 6 to data bit 3 ;so that pulses are sent from B3 on alternate measurements mov temp,meascount ;Test to see if Meascount is even or not andi temp,0b00000001 brne noclockout ;If meascount lsb is not zero, skip to noclockout ;If meascount is zero, then copy outputle bit 4 into datab bit 3 mov temp,datab andi temp,0b11110111 ;Mask off all but the bit of interest mov datab,temp mov temp,outpulse lsr temp ;Shift the bit right to get it into position lsr temp lsr temp andi temp,0b00001000 or datab,temp noclockout: rcall writeportb dec quarterycles ;If four quarter cycles haven't passed, then don't brne notfullcycle ;increment resetcounter or see if its time to measure charge. ldi temp,$04 mov quarterycles,temp adiw YL,1 ;There will be a glitch on D4 during resets. mov temp,YL mov temp2,YH subi temp,measuretime sbci temp2,measuretimeH brcs noreset brne nomeasure ;If this is the first time through, measure the charge (250 us). rcall measurecharge ;First time is when resercounter = measuretime. inc meascount ;Increment measurement count mov temp,chargecount ;After measuring charge, adjust offset and save previoius charge subi temp,$42 mov chargecount,temp ;Remove offset from count. mov temp,meascount ;On odd cycles, save measured value as zero andi temp,0b00000001 breq savezero sub chargecount,zerochargecount ;If not saving it,then we just measured zero, so subrtact zero offset rjmp measuredone savezero: mov zerochargecount,chargecount ;Save zero reference measuredone: nomeasure: mov temp,YL mov temp2,YH subi temp,resettime sbci temp2,resettimeH brne nozeroY clr YL clr YH nozeroY: cbi PORTD,5 ;Turn off discharge resistor. sbi PORTD,4 ;Turn on dump switch. rcall writeportb ;Restore PORTB rjmp finishedinterupt noreset: cbi PORTD,4 ;Turn off dump switch. notfullcycle: ;Skip to here if this is not at the end of a full cycle. finishedinterupt: pop temp out sreg,temp pop temp2 pop temp reti .exit ;don't assemble past here.