;
; ***************************************************
; * Railway gate with an ATtiny24 and a servo motor *
; * V1, 19.07.2014, translated 17.03.2018           *
; * (C)2014-2018 by http://www.avr-asm-tutorial.net *
; ***************************************************
;
; Include file for the AVR type
.NOLIST
.INCLUDE "tn24def.inc" ; Header file for ATtiny24
.LIST
;
; Debug switches (final: all zero)
.equ debugparam=0
;
; ============================================
;   H A R D W A R E   I N F O R M A T I O N 
; ============================================
;
;             ______________
;            /   ATtiny24   |
;      +U o--|VCC        GND|--o -U
; Duo LED    |              |
;   Red   o--|PB0       ADC0|--o Lower trim
;   Anode    |              |
;   Red   o--|PB1       ADC1|--o Upper trim
;   Cat      |              |
;   RESET o--|RESET     ADC3|--o Speed trim
;            |              |
;     DWN o--|INT0       PA4|--o NC
;            |              |
;      UP o--|PCINT7    USC0|--o SCK
;            |              |
; PWM-SIG o--|OC1A/MOSI MISO|--o MISO
;  & MOSI    |______________|
;
;
; ============================================
;      P O R T S   A N D   P I N S 
; ============================================
;
; Ports for LED control
.EQU pLedO = PORTB
.EQU pLedI = PINB
.EQU pLedD = DDRB
.EQU bLedPlO = PORTB0
.EQU bLedPlD = DDB0
.EQU bLedPlI = PINB0
.EQU bLedMiO = PORTB1
.EQU bLedMiD = DDB1
.EQU bLedMiI = PINB1
; Ports fuer Tastensteuerung
.EQU pDwnO = PORTB
.EQU pDwnD = DDRB
.EQU bDwn = PORTB2
.EQU pUpO = PORTA
.EQU pUpD = DDRA
.EQU pUpI = PINA
.EQU bUpO = PORTA7
.EQU bUpD = DDA7
.EQU bUpI = PINA7
; Ports fuer PWM-Signal
.EQU pPwmO = PORTA
.EQU pPwmI = PINA
.EQU pPwmD = DDRA
.EQU bPwmO = PORTA6
.EQU bPwmD = DDA6
.EQU bPwmI = PINA6
;
; =============================================
;    C O N S T A N T S   T O   A D J U S T
; =============================================
;
.EQU cMinMin = 900 ; us shortest PWM signal duration
.EQU cMaxMax = 2100 ; us longest PWM signal duration
.EQU cMinMed = 1000 ; us lower regulation limit
.EQU cMaxMed = 2000 ; us upper regulation limit
.EQU cMid = (cMaxMax+cMinMin) / 2 ; Middle position
.EQU cPwm = 20000 ; us total PWM duration
.EQU cSpeedHigh = 1000000 ; us duration fastest closing
.EQU cSpeedLow = 7000000 ; us duration slowest closing
;
; ===============================================
;  F I X E D + D E R I V E D   C O N S T A N T S 
; ===============================================
;
.EQU cMaxMin = cMaxMax - 511 ; Lower limit closing
.EQU cRoundsFast = cSpeedHigh / cPwm
.EQU cRoundsSlow = cSpeedLow / cPwm
.EQU cAdderFast = (cMaxMed - cMinMed) / cRoundsFast
.EQU cAdderSlow = 1 ; Slow adder
.EQU cSpeedMulti = cAdderFast - cAdderSlow
;
; ===================================
;  P R O G R A M   S T R U C T U R E
; ===================================
;
; All timing is based on a clock frequency of 1 MHz,
; which is the default clock frequency with CLKDIV8
; set and the 8 MHz RC oscillator selected.
;
; ADC values
; Measuring the trim potentiometer settings runs with
; an ADC clock prescaler of 128. 64 measurements per
; channel are added up in the registers R0 to R5 and
; averaged.
; From this a measuring frequency of 1 Mhz / 128 /
; 13 clock cycles per conversion / 3 channels / 64
; single measurements = approx. 3 Hz results.
; Calculation of those measurements goes as follows:
; Trim 1: Sum/128+cMinMin(900) ==> rClose (Minimum)
; Trim 2: cMaxMax(2100)-Sum/128 ==> rOpen (Maximum)
; Trim 3: (Sum/256*cSpeedMulti)/256+1 ==> rDelta
;   (Speed), values vary between 1 and 23
;   (1 = 20 seconds closure time, 23 = one second
;   closure time
;
; Timer1: Generation of the PWM signal
;   TC1 is clocked by the clock signal and a prescaler
;   of 1, one tick is therefore 1 us.
;   TC1 runs as CTC and generates the PWM signal on the
;   output pin OC1A on PA6. The output pin toggles on
;   compare match A, TC1 restarts and triggers a
;   compare match A intterupt. Depending from the state
;   of the output pin either the duration in rCtcAct or
;   in rCtcIna is written to the compare register A. The
;   resulting looks as follows:
;
;       _______                   _______                   ______
; _____|       |_________________|       |_________________|
;       rCtcAct    rCtcIna        rCtcAct      rCtcIna
;      |<======= 20.000 us =====>|<======= 20.000 us =====>|
;
; rCtcAct = 900 .. 2.100 us
;   Lower limit adjustable with trim 1 between 900 and 1410 us
;   Upper limit adjustable with trim 2 between 1590 and 2100 us
; rCtcIna = 20.000 - rCtcAct
;
; rCtcAct plus rCtcIna always yields 20.000 us, a 50 Hz signal.
; When the OC1A pin is set (after writing rCtcIna) the bPwm flag
; is set, which triggers execution of the routine PwmRdy. This
; determines if the gate is currently moving or not.
; If this is not the case, 25 following PWM signal bursts are
; sent with the current position (0.5 seconds). After that the
; PWM signal output is cleared (by clearing the output pin on
; compare match).
; If the gate is moving the duration of the PWM signal is increased
; by the content of rDelta, if the gate opens, or decreased if
; the gate closes. If, on opening, the upper limit in rOpen is
; exceeded the LED is switched to permanently green and the
; post-PWM period is initiated.
; If, on closing, the lower limit in rClose is reached the LED
; is switched to permanent red and the post-PWM period initiated.
; Bei aktiver Schranke wird die Dauer des PWM-Signals um den Betrag
; rDelta verlaengert (Schranke oeffnet sich) bzw. verkuerzt (Schranke
; schliesst sich). Ist beim Oeffnen die obere Grenze in rOben ueber-
; schritten wird die LED auf gruenes Dauerlicht geschaltet und der
; Nachlauf eingeleitet. Ist beim Schliessen die untere Grenze unter-
; schritten wird die LED auf rotes Dauerlicht geschaltet und der
; Nachlauf eingeleitet.
; If neither the upper nor the lower limit has been reached the
; LED blinks with 0.2 Hz.
;
; Triggering opening and closing of the gate
; If push button 1 is activated, an INT0 interrupt is triggered.
; This clears the upward flag.
; If push button 2 is activated, the PCINT0 interrupt is triggered.
; Following both interrupts and if the toggle bit of TC1 is not
; set, the rCtcIna value is written to the compare A port, the
; toggle mode in TC1 is set and the compare match A interrupt is
; enabled. Finally the &quot;Gate active&quot; flag is set.
;
; Adjusting
; If the value of the up- or down-trim-poentiometer is changed,
; the new value is immediately applied. If the gate is inactive
; (bActive flag cleared), the gate only moves if it is completely
; open (last movement upwards) or completely closed (last movement
; downwards).
; Changes of the speed trim potentiometer come only into effect
; if the gate moves.
;
; Start conditions
; On start up the gate is moved to to its middle position and then
; upwards to the upper limit.
;
; ============================================
;   R E G I S T E R   D E F I N I T I O N S
; ============================================
;
; R0 .. R5 : ADC sum values, three channels
.DEF rCtcActL = R6 ; Active signal duration, us
.DEF rCtcActH = R7
.DEF rCtcInaL = R8 ; Inactive signal duration, us
.DEF rCtcInaH = R9
.DEF rCloseL = R10 ; Lower limit PWM signal
.DEF rCloseH = R11
.DEF rOpenL = R12 ; Upper limit PWM signal
.DEF rOpenH = R13
.DEF rDelta = R14 ; Step length for gate movement
.DEF rSreg = R15 ; for SREG interim storage in ints
.DEF rmp = R16 ; Multi purpose register
.DEF rimp = R17 ; Multi purpose inside interrupts
.DEF rimp2 = R18 ; 16 bit adder register in interrupts
.DEF rAdcCnt = R19 ; Counter for ADC measurements
.DEF rFlg = R20 ; Flag register
	.equ bActive = 0 ; Gate is moving
	.equ bHigh = 1 ; Gate is moving up
	.equ bAdc = 2 ; ADC sequence complete
	.equ bPwmIna = 3 ; Inactive PWM signal, handle flag
	.equ bOpen = 4 ; Gate is open
	.equ bClose = 5 ; Gate is closed
	.equ bToggle = 6 ; CTC toggle is active
.DEF rPwmCtr = R21 ; Downcounter for post-PWM phase
; R22 .. R25 not used
; X = XH:XL Multiplication for speed adjustment
; Y = YH:YL ADC pointer to SRAM
; Z = ZH:ZL Multi purpose register pair outside interrupts
;
; ============================================
;       S R A M   D E F I N I T I O N S
; ============================================
;
.DSEG
.ORG  0X0060
sAdc: .Byte 6
;
; ==============================================
;   R E S E T   A N D   I N T   V E C T O R E N
; ==============================================
;
.CSEG
.ORG $0000
	rjmp Main ; Reset-Vector
	rjmp IntDwn ; Int Vector INT0
	rjmp IntUp ; Int Vector PCINT0
	reti ; Int Vector PCINT1
	reti ; Int Vector WDT
	reti ; Int Vector TC1 Capture
	rjmp IntPwmA ; Int Vector TC1 Compare A
	reti ; Int Vector TC1 Compare B
	reti ; Int Vector TC1 OVF
	reti ; Int Vector TC0 Compare A
	reti ; Int Vector TC0 Compare B
	reti ; Int Vector TC0 OVF
	reti ; Int Vector Ana_Comp
	rjmp IntAdc ; Int Vector ADC
	reti ; Int Vector EEPROM Ready
	reti ; Int Vector USI START
	reti ; Int Vector USI Overflow
;
; ==========================================
;    I N T E R R U P T   S E R V I C E
; ==========================================
;
; Downwards push button
IntDwn:
	in rSreg,SREG ; Save SREG
	cbr rFlg,1<<bHigh ; Clear upward flag
	rjmp IntUD
;
; Upwards push button
IntUp:
	in rSreg,SREG ; Save SREG
	sbic pUpI,bUpI ; Falling edge?
	reti ; No, ignore
	sbr rFlg,1<<bHigh ; Set upwards flag
IntUD:
	; Start move cycle
	sbrc rFlg,bActive ; Already active?
	rjmp IntUD1 ; Yes, ignore
	sbr rFlg,1<<bActive ; Set gate aktice flag
	sbrc rFlg,bToggle ; Skip if not in toggle mode
	rjmp IntUD1 ; Toggle-Mode is on
	ldi rPwmCtr,1 ; Start blink counter
	out OCR1AH,rCtcInaH ; Compare match inactive duration
	out OCR1AL,rCtcInaL
	ldi rimp,1<<COM1A0 ; Enable toggle of output pin
	out TCCR1A,rimp
	ldi rimp,1<<OCIE1A ; Enable TC1COMPA interrupt
	out TIMSK1,rimp
	sbr rFlg,1<<bToggle
IntUD1:
	out SREG,rSreg ; Restore SREG
	reti
;
; TC1 Compare match A interrupt service routine
IntPwmA:
	in rSreg,SREG ; Save SREG
	sbic pPwmI,bPwmI ; Skip if PWM signal=0
	rjmp IntPwmA1
	out OCR1AH,rCtcInaH ; Long inactive pause
	out OCR1AL,rCtcInaL
	sbr rFlg,1<<bPwmIna ; Set flag
	rjmp IntPwmA2
IntPwmA1:
	out OCR1AH,rCtcActH ; Short active signal
	out OCR1AL,rCtcActL
IntPwmA2: 
	out SREG,rSreg ; Restore SREG
	reti
;
; AD conversion complete interrupt service routine
IntAdc:
	in rSreg,SREG ; Save SREG
	in rimp,ADCL ; Read LSB result
	ld rimp2,Y ; Read current sum LSB
	add rimp,rimp2 ; Add LSB to current
	st Y+,rimp ; and store in SRAM
	in rimp,ADCH ; Read MSB result
	ld rimp2,Y ; Read MSB current sum
	adc rimp,rimp2 ; Add MSBs with carry
	st Y+,rimp ; Store MSB
	cpi YL,6 ; End of round?
	brcs IntAdcRet ; No, continue
	clr YL ; Restart
	dec rAdcCnt ; Count rounds down
	brne IntAdcRet
AdcCopy:
	; End of ADC measuring cycle
	ldi YL,LOW(sAdc) ; Copy to SRAM
	st Y+,R0
	st Y+,R1
	st Y+,R2
	st Y+,R3
	st Y+,R4
	st Y,R5
	clr YL
	clr R0 ; Clear sums 
	clr R1
	clr R2
	clr R3
	clr R4
	clr R5
	sbr rFlg,1<<bAdc ; Set flag
IntAdcRet:
	mov rimp,YL ; Copy MUX channel
	lsr rimp ; Divide by two
	out ADMUX,rimp ; MUX to next channel
	; Start next conversion
	ldi rimp,(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)
	out ADCSRA,rimp
	out SREG,rSreg ; Restore SREG
	reti
;
; ============================================
;    M A I N   P R O G R A M   I N I T
; ============================================
;
Main:
; Init stack
	ldi rmp, LOW(RAMEND) ; Init LSB stack
	out SPL,rmp
; Init port bits
	; Init LED pins
	sbi pLedD,bLedPlD ; LED pins as output
	sbi pLedD,bLedMiD
	sbi pLedO,bLedPlO ; Turn red LED on
	cbi pLedO,bLedMiO
	; Init the push button pins
	cbi pDwnD,bDwn ; Close push button as input
	sbi pDwnO,bDwn ; Pull up resistor on
	cbi pUpD,bUpD ; Close push button as input
	sbi pUpO,bUpO ; Pull up resistor on
	; Init PWM output pin
	sbi pPwmD,bPwmD ; PWM output pin as output
	cbi pPwmO,bPwmO ; To low
; Init ADC
	; Single measurement at start up
.if debugparam==1
	rjmp debugcalc
	nop
	.endif 
	rcall AdcGet ; Get first cycle
	ldi rmp,HIGH(cMid) ; Position to middle
	mov rCtcActH,rmp
	ldi rmp,LOW(cMid)
	mov rCtcActL,rmp
	clr rFlg ; Clear flags
	sbr rFlg,(1<<bActive)|(1<<bHigh) ; Move gate up
	ldi rPwmCtr,1 ; Init LED blink counter
	rcall PwmRdyO ; Blink and correct Inactive duration
	; Prepare ADC sequence
	ldi YH,HIGH(sAdc) ; Pointer to SRAM
	ldi YL,LOW(sAdc)
	ldi rmp,0 ; MUX to channel 0
	out ADMUX,rmp
	ldi rmp,(1<<ADC0D)|(1<<ADC1D)|(1<<ADC0D) ; Digital input driver disable
	out DIDR0,rmp
	clr rmp ; Results not left adjust
	out ADCSRB,rmp
	ldi rmp,(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)
	out ADCSRA,rmp
; Init timer 1
	out OCR1AH,rCtcInaH ; CTC to inactive duration
	out OCR1AL,rCtcInaL
	ldi rmp,1<<COM1A0 ; Toggle output A on compare match
	out TCCR1A,rmp
	ldi rmp,(1<<WGM12)|(1<<CS10) ; CTC mode, prescaler = 1
	out TCCR1B,rmp
	ldi rmp,1<<OCIE1A ; TC1 Compare Match A Interrupt Enable
	out TIMSK1,rmp
	sbr rFlg,1<<bToggle ; Toggle is active
; Init PCINT7
	ldi rmp,1<<PCINT7 ; Pin-Change Int push button 2
	out PCMSK0,rmp
; Init external interrupts and Sleep mode idle
	ldi rmp,(1<<SE)|(1<<ISC01) ; Enable sleep and INT0
	out MCUCR,rmp
	; External interrupts
	ldi rmp,(1<<INT0)|(1<<PCIE0) ; Enable INT0 and PCINT0 interrupts
	out GIMSK,rmp
	sei ; Enable interrupts
;
; ============================================
;        P R O G R A M - L O O P
; ============================================
;
Loop:
	sleep ; Send to sleep
	nop ; Dummy for wake-up
	sbrc rFlg,bPwmIna ; PWM flag set?
	rcall PwmRdy ; Yes, handle PWM
	sbrc rFlg,bAdc ; ADC flag set?
	rcall AdcRdy ; Yes, handle ADC
	rjmp loop ; Back to loop
;
; PWM ready
PwmRdy:
	cbr rFlg,1<<bPwmIna ; Clear flag
	sbrc rFlg,bActive ; Skip if gate inactive
	rjmp PwmRdyA ; Gate active
	; Gate inactive
	sbrs rFlg,bToggle ; Skip if toggle is on
	ret ; Toggle is off, return
	; TC1 toggle is on
	dec rPwmCtr ; Count post-PWM down
	brne PwmRdy1 ; Not finished yet
	ldi rmp,1<<COM1A1 ; Set TC output pin to clear
	out TCCR1A,rmp ; Switch toggle off
	clr rmp ; Disable TC1 interrupt
	out TIMSK1,rmp
	cbr rFlg,1<<bToggle ; Toggle flag off
PwmRdy1:
	ret
PwmRdyA: ; Gate ist active
	sbrc rFlg,bHigh ; Skip if gate is closed
	rjmp PwmRdyH ; Gate upwards
	; Gate downwards
	sub rCtcActL,rDelta ; Reduce PWM signal duration
	brcc PwmRdyA1 ; No carry
	dec rCtcActH ; MSB downwards
PwmRdyA1:
	cp rCtcActL,rCloseL ; Compare with lower limit
	cpc rCtcActH,rCloseH
	brcc PwmRdyA2 ; Not yet reached
	mov rCtcActL,rCloseL ; Reached, set close value
	mov rCtcActH,rCloseH
	cbr rFlg,1<<bActive ; End gate movement
	sbr rFlg,1<<bClose ; Set close flag
	sbi pLedO,bLedPlO ; Red LED permanently on
	cbi pLedO,bLedMiO
	ldi rPwmCtr,25 ; Start repeat counter for post-PWM
	rjmp PwmRdyK ; Correct duration PWM
	; Not yet on close position
PwmRdyA2:
	cbr rFlg,1<<bClose ; Close flag off
	rjmp PwmRdyO ; Blink and correct PWM duration
PwmRdyH:
	; Gate upwards
	add rCtcActL,rDelta ; Add delta to signal duration
	brcc PwmRdyH1 ; No carry
	inc rCtcActH ; Carry to MSB
PwmRdyH1:
	cp rCtcActL,rOpenL ; Compare with upper limit
	cpc rCtcActH,rOpenH
	brcs PwmRdyH2 ; Not yet reached
	mov rCtcActL,rOpenL ; Reached, set upper limit
	mov rCtcActH,rOpenH
	cbr rFlg,1<<bActive ; End gate movement
	sbr rFlg,1<<bOpen ; Gate open flag
	cbi pLedO,bLedPlO ; LED permanently green
	sbi pLedO,bLedMiO
	ldi rPwmCtr,25 ; Start repeat counter for post-PWM
	rjmp PwmRdyK ; Correct duration of PWM inactive
PwmRdyH2:
	cbr rFlg,1<<bOpen ; Open flag off
	rjmp PwmRdyO ; LED blink and CTC-Inactive correction 
PwmRdyO:
	; LED blink
	cbi pLedO,bLedMiO
	dec rPwmCtr ; Counter for blink LED
	brne PwmRdyK ; Correct inactive duration PWM
	ldi rPwmCtr,12 ; Restart counter
	sbic pLedI,bLedPlI ; Skip if LED is off
	rjmp PwmRdyO1 ; LED is on
	sbi pLedO,bLedPlO ; LED on
	rjmp PwmRdyK
PwmRdyO1:
	cbi pLedO,bLedPlO ; LED off
PwmRdyK:
	; Correct inactive duration PWM
	ldi rmp,LOW(cPwm) ; Total duration - Active duration
	sub rmp,rCtcActL
	mov rCtcInaL,rmp ; Copy to inactive duration
	ldi rmp,HIGH(cPwm)
	sbc rmp,rCtcActH
	mov rCtcInaH,rmp
	ret
;
; ADC measuring sequence ended
;
AdcRdy:
	cbr rFlg,1<<bAdc
	push rCloseL
	push rCloseH
	push rOpenL
	push rOpenH
	rcall CalcSpeed
	pop XH
	pop XL
	pop ZH
	pop ZL
	sbrc rFlg,bActive ; Skip if gate still active
	ret ; Yes
	sbrc rFlg,bToggle ; Skip if toggle active
	ret ; Yes
	ldi rmp,LOW(cMid) ; Below or above middle?
	cp rCtcActL,rmp
	ldi rmp,HIGH(cMid)
	cpc rCtcActH,rmp
	brcc AdcRdyU 
	; Gate is below middle
	mov rCtcActH,rCloseH
	mov rCtcActL,rCloseL
	rjmp AdcRdyS ; Start move cycle
AdcRdyU:
	; Gate is above middle
	mov rCtcActH,rOpenH
	mov rCtcActL,rOpenL
AdcRdyS:
	; Start adjustment
	rcall PwmRdyK ; Calculate inactive duration
	ldi rPwmCtr,10 ; Start post-PWM cycle
	ldi rmp,1<<COM1A0 ; Start toggle
	out TCCR1A,rmp
	ldi rmp,1<<OCIE1A ; Start interrupt
	out TIMSK1,rmp
	sbr rFlg,1<<bToggle ; Set toggle flank
	ret
;
; =============================================
;     A S Y N C H   S U B R O U T I N E S
; =============================================
;
; Get ADC results during init
AdcGet:
	ldi rmp,(1<<ADC0D)|(1<<ADC1D)|(1<<ADC2D) ; Digital drivers off
	out DIDR0,rmp
	clr rmp ; Result not left adjusted
	out ADCSRB,rmp
	ldi YH,HIGH(sAdc) ; Channel counter Y points to SRAM
	ldi YL,LOW(sAdc)
GetAdc1:
	clr R0 ; Clear result
	clr R1
	ldi rAdcCnt,64 ; 64 measurements
	mov rmp,YL ; MUX channel
	lsr rmp
	andi rmp,0x03 ; Isolate channel bits 
	out ADMUX,rmp
GetAdc2:
	ldi rmp,(1<<ADEN)|(1<<ADSC)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)
	out ADCSRA,rmp
GetAdc3:
	sbic ADCSRA,ADSC ; Wait for result
	rjmp GetAdc3
	in rmp,ADCL ; Add LSB to result
	add R0,rmp
	in rmp,ADCH ; Add MSB to result
	adc R1,rmp
	dec rAdcCnt
	brne GetAdc2 ; Again convert
	st Y+,R0 ; Store LSB
	st Y+,R1 ; Store MSB
	cpi YL,sAdc+6 ; All three channels read?
	brcs GetAdc1
;
; Calculate delta from speed position
CalcSpeed:
	lds rmp,sAdc+5 ; ADC result MSB channel 3
	clr XH ; Multiplicator MSB
	ldi XL,cSpeedMulti ; LSB = Multiplicator for speed
	clr ZH ; Z is result
	clr ZL
CalcSpeed1:
	lsr rmp ; Bit = 1?
	brcc CalcSpeed2 ; No, do not add
	add ZL,XL ; Add LSB multiplicator
	adc ZH,XH ; Add MSB and carry
CalcSpeed2:
	lsl XL ; Double multiplicator
	rol XH
	tst rmp ; Already done?
	brne CalcSpeed1 ; No, further bits
	ldi rmp,cAdderSlow
	add rmp,ZH ; Add MSB of the result
	mov rDelta,rmp
;
; Lower and upper trim potentiometer
AdcConv:
	lds rmp,sAdc ; Read LSB lower trim
	lds ZL,sAdc+1 ; Read MSB upper trim
	clr ZH
	lsl rmp ; Upper bit LSB into lower bit LSB
	rol ZL ; Rotate into LSB
	rol ZH ; And into MSB
	ldi rmp,LOW(cMinMin) ; Minimum duration lower limit
	add ZL,rmp ; Add to result
	ldi rmp,HIGH(cMinMin)
	adc ZH,rmp
	andi ZL,0xFC ; Clear lowest two bits
	mov rCloseH,ZH ; Copy to closure position
	mov rCloseL,ZL
	lds rmp,sAdc+2 ; Read LSB upper trim
	lds ZL,sAdc+3 ; Read MSB upper trim
	clr ZH
	lsl rmp ; Upper bit LSB to lowest bit LSB
	rol ZL ; Rotate bit into LSB in ZL
	rol ZH ; And into MSB
	ldi rmp,LOW(cMaxMin) ; LSB maximum duration upper position
	add ZL,rmp ; Add to LSB
	ldi rmp,HIGH(cMaxMin)
	adc ZH,rmp ; Add MSB and carry
	andi ZL,0xFC ; Clear lowest two bits
	mov rOpenL,ZL
	mov rOpenH,ZH
	ret
.if debugparam==1
debugcalc:
	ldi rmp,0xFF
	ldi ZH,HIGH(sAdc)
	ldi ZL,LOW(sAdc)
	st Z+,rmp
	st Z+,rmp
	st Z+,rmp
	st Z+,rmp
	st Z+,rmp
	st Z+,rmp
	rjmp CalcSpeed
	.endif
;
; End of source code
; Copyright
.db "(C)2014 by Gerhard Schmidt  " ; human readable
.db "C(2)10 4ybG reahdrS hcimtd  " ; wordwise
;
