;************************************************************************************
;*	DVM (Digitales Voltmeter)														*
;*	-------------------------														*
;*																					*
;*	Hardware: Portview-Modul mit AT-Mega8 http://www.avr-projekte.de/portview.htm	*
;*	Version vom 18.07.2011 															*
;*																					*
;*	Autor: J.Woetzel / juergen@avr-projekte.de										*
;************************************************************************************
#include "m8def.inc"
;---------------------------------------------------------------------------------------------------
;Konstanten für die Berechnung der Anzeige bei 1023 am ADC Ausgang
;für die 4 möglichen Jumperstellungen
;---------------------------------------------------------------------------------------------------
;Endausschlag
;------------
;Durch ändern der Formel z.B. "faktor1 =123*$10000/1024", zeigt das Display bei 5V
;am ADC Eingang 12.3 V an.
;(DP der Anzeige vor der letzten Stelle, Jumper offen)
;Ein passender Spannungsteiler muss vorgeschaltet werden
.equ	faktor1	= 322*$10000/1024	;Jumper offen
.equ	faktor2	= 200*$10000/1024	;200= Anzeige bei +5V am Analogeingang. Spannungsteiler 4:1
.equ	faktor3	= 300*$10000/1024	;300, Spannungsteiler 6:1
.equ	faktor4	= 400*$10000/1024	;400, Spannungsteiler 8:1
;Danach wird wieder durch $10000 geteilt (die unteren 16Bit der Multiplikation verworfen)
;---------------------------------------------------------------------------------------------------
;Nullabgleich
;------------
.equ	offset	= 3	;Wird zum ausgelesenen ADC-Wert addiert. Der Offset wird so gewählt, das 
;wenn der ADC-Eingang mit GND verbunden wird, die Anzeige gerade noch 00,0 anzeigt.
;Dadurch kann sich der Endausschlag um 0,1V nach oben verschieben, sodas man diesen nachbessern muss.
;---------------------------------------------------------------------------------------------------
;Referenzspannung
;----------------
;Bei z.B. vref=5V müssen am Spannungsteiler 5V Abfallen damit der Endausschlag angezeigt wird.
;Einen der beiden Werte Auskommentieren.
;.equ	vref	= 1<<REFS0|1<<REFS1	;2,56V intern
.equ	vref	= 1<<REFS0			;5V intern
;---------------------------------------------------------------------------------------------------
.def	null	=	r0		;Konstante
.def	s		=	r5		;S-Register
.def	temp1	=	r16		;Trash       
.def	temp2	=	r17       
.def	temp3	=	r18       
.def	temp4	=	r19
.def	temp5	=	r25
.def	itemp	=	r20		;ISR-Variable
.def	seg0	=	r21		;7Seg. Digits, Hunderter
.def	seg1	=	r22		;Zehner
.def	seg2	=	r23		;Einer
.def	tick	=	r24
.org 0
	rjmp	res
;***************************************************************************************************************
;ISR, ca. alle 2mS
;***************************************************************************************************************
.org OVF0addr
ISR:in		s,sreg		
	;-----------------------------------------------------
	;Display muxen
	;Bei 3 Anzeigen *2mS sind das 6mS für einen Durchlauf.
	;Die Muxfreq. ist somit 1/6mS = 166,66 Hz
	;-----------------------------------------------------
	ser		itemp			
	out		PortB,itemp				;Segmente aus

	sbic	PortD,PD5				;War zuletzt die 3. Anzeige eingeschaltet, 1. Anzeige einschalten
	rjmp	_mux1
	out		PortB,seg0				;Saft auf die Kathoden
	ldi		itemp,1<<PD5|1<<PD6|$0F	;PNP Stelle 1 Einschalten (PD7=0),+ Pullups Jumper
	rjmp	exit0
_mux1:
	sbic	PortD,PD7				;Wenn D1 an ist, D2 einschalten
	rjmp	_mux2
	out		PortB,seg1				;Kathoden
	ldi		itemp,1<<PD5|1<<PD7|$0F	;Stelle 2 Einschalten (PD6=0),+ Pullups Jumper
	rjmp	exit0
_mux2:
	out		PortB,seg2				;kann nur noch Stelle 3 sein
	ldi		itemp,1<<PD7|1<<PD6|$0F	;Stelle 3 Einschalten (PD5=0),+ Pullups Jumper
exit0:
	out		PortD,itemp				;Einen der 3 PNPs einschalten
	inc		tick
	out		sreg,s					;Statusbits zurücklesen
	reti							;in der Main weitermachen
;***************************************************************************************************************
;									--- ENDE ISR ---
;***************************************************************************************************************														
;										Init
;***************************************************************************************************************
	;-------------------------------------
	;Stack
	;-------------------------------------
res:ldi		temp1, LOW(RAMEND)	
	out		SPL, temp1
	ldi		temp1, HIGH(RAMEND)
	out		SPH, temp1
	;-------------------------------------
	;Ports init
	;-------------------------------------
	ldi		temp1,$ff
	out		ddrb,temp1				;Segmente (Kathoden) Ausgang
	ldi		temp1,$fe
	out		portc,temp1				;Pullups Binäreingang Bit 1..5
	ldi		temp1,0b11100000
	out		ddrd,temp1				;gem. Anoden Ausgang
	ldi		temp1,$ff
	out		portd,temp1				;Pullups Jumper, Binäreingang Bit 6 & 7, gem. Anoden Aus
	;-------------------------------------
	;Timerinterrupt init
	;-------------------------------------
	ldi		temp1,1<<cs00|1<<cs01	;TC0/64, ISR ca. alle 2mS (8MHz/256/64)
	out		TCCR0,temp1
	ldi		temp1,1<<TOIE0			;Timer0 overflow interrupt
	out		TIMSK,temp1
	;-------------------------------------
	;ADC initialisieren
	;-------------------------------------
	ldi		temp1,vref				; Kanal und vref wird oben definiert
	out		ADMUX,temp1
	ldi		temp1,1<<ADPS1|1<<ADPS2	|1<<ADEN
	out		ADCSRA,temp1 			;Prescaler /64=125 khz
	;-------------------------------------
	sei
	clr		null					;Konstante
;************************************************************************************************
;*	Hauptschleife. Zuerst wird der 10-Bit A/D Wandler ausgelesen,
;*	dann anhand der Jumperstellung der 3 Stellige Wert berechnet und ausgegeben
;************************************************************************************************
main:
	rcall	wait			;Um die Anzeige zu beruhigen, 100 mS vertrödeln
	rcall	sample			;Analogwert (Mittelwert aus 256 Messungen)
	;---------------------------------------------
	;Bei maximalem ADC-Wert "HH.H" auf das Display
	;---------------------------------------------
	ldi		temp1,low(1023+offset)
	ldi		temp2,high(1023+offset)
	cp		yl,temp1
	cpc		yh,temp2
	brne	m1
	ldi		seg0,$c8		;7Seg.- Code für "H" in alle
	ldi		seg1,$c8		;3 Anzeigen schreiben
	ldi		seg2,$c8
	rjmp	main
	;---------------------------------------------
	;Jumper einlesen und verzweigen
	;---------------------------------------------
m1:	in		temp1,pind		
	andi	temp1,0b1100	;Jumper ausmaskieren	
	breq	math20			;beide Jumper
	cpi		temp1,0b1100
	breq	math50			;kein Jumper
	cpi		temp1,0b100
	breq	math40			;Jumper links
	rjmp	math30			;Jumper rechts
	;---------------------------------------------------------------
	;Abhängig von der Jumperstellung den 10-Bit Analogwert in Spannungswert
	;umrechnen und ins Display schreiben
	;---------------------------------------------------------------
math50:	ldi		zl,low(faktor1)
		ldi		zh,high(faktor1)
		rjmp	mal
math20:	ldi		zl,low(faktor2)
		ldi		zh,high(faktor2)
		rjmp	mal
math30:	ldi		zl,low(faktor3)
		ldi		zh,high(faktor3)
		rjmp	mal
math40:	ldi		zl,low(faktor4)
		ldi		zh,high(faktor4)
mal:	rcall	mul16
		rcall	dez
		rjmp	main
;************************************************************************************************
;	AD-Wandler
;************************************************************************************************
;-------------------------------------
;256 Messungen
;-------------------------------------
sample:
	clr		yl				;Den 10-Bit Wert nach Y
	clr		yh
	clr		temp1
	ldi		temp4,0			;256 Messungen, dann Mittelwert bilden (temp1 verwerfen)
sa1:rcall	sample1			;1 mal messen..
	add		temp1,xl
	adc		yl,xh			;.. und addieren
	adc		yh,null
	dec		temp4			;Messung-1
	brne	sa1				;Letzte Messung ? Wenn nein, Sprung
	sbrs	temp1,7
	rjmp	sa2
	subi	yl,low(-1)		;ggf. aufrunden
	sbci	yh,high(-1)
sa2:subi	yl,low(-offset)	;Offset addieren
	sbci	yh,high(-offset)
	ret
	;------------------------------------------------------
	;A/D Wandler für eine Messung starten
	;------------------------------------------------------
sample1:
    sbi     ADCSRA, ADSC	;ADC Los
wait_adc:
    sbic    ADCSRA, ADSC 
	rjmp	wait_adc		;ADSC Bit pollen

	in		xl, ADCL		;ADC-Wert wegspeichern
	in		xh, ADCH
	ret
	;-------------------------------------
	;Zwischen den Messungen 100mS warten
	;-------------------------------------
wait:
	ldi		tick,206		;Wird in der ISR hochgezählt
w1:	tst		tick
	brne	w1
	ret
;-------------------------------------------------------------------------
;Multiplikation 16*16=32 Bit. Faktor1=Y, Faktor2=Z, Ergebniss temp1..temp4
;-------------------------------------------------------------------------
mul16:	ldi		temp5,16		;16*schieben
		clr		temp1			;Ergebnis (Produkt)löschen
		clr		temp2
		clr		temp3	
		clr		temp4
mul01:	lsl		temp1			;Ergebniss schieben
		rol		temp2
		rol		temp3
		rol		temp4
		lsl		yl				;Herausgeschobenes Bit aus Multiplikator (ADC)..
		rol		yh 				;..entscheidet ob addiert wird
		brcc	mul02			;Wenn Bit=0 nicht addieren
		add		temp1,zl		;Wenn Bit=1 Multiplikant (Formel) zum Ergebniss addieren
		adc		temp2,zh
		adc		temp3,null
		adc		temp4,null
mul02:	dec		temp5			;Alle 16 Bit durch ?
		brne	mul01
		ret
;**********************************************************************************************
;Dezimal ausgeben
;**********************************************************************************************
dez:	rcall	div10			;Durch 10 teilen, Z-Pointer + Rest (der Division) auf 7Segment-Tab.
		lpm		seg2,z			;Erste Ziffer hinten rein ..
		rcall	div10
		lpm		seg1,z			;.. Mitte..
		;----------------------------------------------
		;Wird eine Null in der ersten Stelle gewünscht,
		;die folgenden 5 Befehle auskommentieren
		;----------------------------------------------
		cp		temp3,null		;Ist schon alles wegdividiert ?
		cpc		temp4,null
		brne	dez1			;Wenn nein, Sprung
		ldi		seg0,$ff		;1. Stelle Dunkel
		ret
		;----------------------------------------------
dez1:	rcall	div10
		lpm		seg0,z			;.. Vorne
		ret
		;-----------------------------------------
		;16Bit Division durch 10
		;Dividend und Ergebniss in temp3 und temp4 , Rest in temp1
		;-----------------------------------------
div10:	clr		temp1			;Rest
		ldi		temp2,16		;16* schieben
div101:	lsl		temp3			;Solange Bits vom Divident in Rest schieben bis >= 10 ...			
		rol		temp4
		rol		temp1			;Rest
		cpi		temp1,10
		brcs	div102			;Wenn der Rest <10 ist, 0-Bit (LSL oben) im Ergebniss stehen lassen
		inc		temp3			;Ansonsten im Ergebniss Bit0=1 ...
		subi	temp1,10		;.. und 10 vom Rest abziehen
div102:	dec		temp2			;das ganze 8 mal
		brne	div101
		;-----------------------------------------
		;Ende Division
		;Aus dem Rest, den 7-Segmentcode ermitteln
		;-----------------------------------------
seg7:	ldi		zl,low(segtab*2)	;Z auf 7-Segmenttab.
		ldi		zh,high(segtab*2)
		add		zl,temp1			;+ Rest
		adc		zh,null				;Carry dazu
		ret
;**********************************************************************************************
;Ende Code
;**********************************************************************************************
;Segment | Portpin
;-----------------------------------------------------------------------------------------------
;	a		6		|-a-|	Die 7 Segmenttabelle (a..g) ist dem Layout angepasst
;	b		5		f   b	Durch den gemeinsamen Anodenanschluss, ist eine Null am Port nötig
;	c		4		|-g-|	damit ein Segment leuchtet
;	d		3		e   c
;	e		2		|-d-|
;	f		1
;	g		0
;-------------------------------------------------------------------
;Die Zeichen 0..F als 7Segmentcode
segtab: 
.db $81,  $CF,\
	$92,  $86,\
	$CC,  $A4,\
	$A0,  $8F,\
	$80,  $84,\
	$88,  $E0,\
	$B1,  $C2,\
	$B0,  $B8
blank:
.db	$FF,0		;Leerzeichen

