![]() |
AVR-Anwendungen Groß-Uhr mit ATmega48 Assembler Software für die Groß-Uhr |
;
; *********************************
; * Grossuhr mit ATmega48 *
; * (C)2019 avr-asm-tutorial.net *
; *********************************
;
.nolist
.include "m48adef.inc" ; Define device ATmega48A
.list
;
; **********************************
; D E B U G S W I T C H E S
; **********************************
;
; Switches debug options on/off
; Make sure that all switches are off in the final version
;
.equ Yes = 1 ; For debug switches
.equ No = 0
;
; Debug the green led
.equ Debug_ledgreen = No ; Debug the green led only
;
; Debug the leds on start-up
.equ Debug_leds = No ; Debug the leds on start-up
;
; Debug the currents and blink the green led
.equ Debug_current = No ; Switch the current drivers on
;
; Debug the segments of the display
.equ Debug_segments = No ; Switch the segments on
; 1 = 9 seconds for all four digits
; 10 = slow, 80 seconds for all four digits
; 100 = extremely slow, 25 seconds per digit
.equ cDebug_segDelay = 100 ; Delay the active segment
;
; Debug the muxing
.equ Debug_mux8 = No ; Multiplex the four displays
; The mux frequency for all four digits once
; Must be between 4 and 10,000 Hz
.equ cDebug_muxfreq = 150 ; MUX frequency in Hz
;
; Debug the ADC results
.equ Debug_adc = No ; Displays the ADC results
;
; Debug the two keys
.equ Debug_keys = No ; Displays the key status
;
; Debug errors in DCF signal reception
; Displays E and error number instead of hours
.equ Debug_dcferr = No ; Yes/No
.equ Debug_dcfany = No ; Displays any signals
.equ Debug_dcfdur = No ; Displays signal durations
;
; **********************************
; H A R D W A R E
; **********************************
;
; Device: ATmega48A, Package: 28-pin-PDIP-S
;
; __________
; 1 / |28
; Res o--|RESET PC5|--o DCF77-In
; a o--|PD0 PC4|--o Key1-In
; b o--|PD1 PC3|--o Key2-In
; c o--|PD2 PC2|--o Led green cathode
; d o--|PD3 PC1|--o Optosensor
; e o--|PD4 PC0|--o Pot
; +5V o--|VCC GND|--o 0V
; 0V o--|GND AREF|--o 100nF
; X1 o--|PB6 AVCC|--o +5V
; X2 o--|PB7 PB5|--o SCK
; f o--|PD5 PB4|--o MISO
; g o--|PD6 PB3|--o MOSI+A4
; h o--|PD7 PB2|--o A3
; A1 o--|PB0 PB1|--o A2
; 14|___________|15
;
; **********************************
; P O R T S A N D P I N S
; **********************************
;
.equ p7SegO = PORTD ; Seven-segment output port
.equ p7SegD = DDRD ; Seven-segment direction port
.equ pAnodeO = PORTB ; Anode driver output port
.equ pAnodeD = DDRB ; Anode driver direction port
.equ pLedGO = PORTC ; Green led output port
.equ pLedGD = DDRC ; Green led direction port
.equ pLedGI = PINC ; Green led input port
.equ bLedGO = PORTC2 ; Green led portbit output
.equ pDcfKeyO = PORTC ; DCF and key port output port
.equ pDcfKeyD = DDRC ; DCF and key direction port
.equ pDcfKeyI = PINC ; DCF&Key input port
.equ bDcfI = PINC5 ; DCF77 input pin
.equ bKey1I = PINC4 ; Key1 input portpin
.equ bKey2I = PINC3 ; Key2 input portpin
;
; **********************************
; A D J U S T A B L E C O N S T
; **********************************
;
; Frequency of the external xtal
; has to be dividable by 2/4, 8 and 256 (=4096/8192)
; without any fractional remainder
.equ xtal = 4096000 ; in Hz
;
; Clock prescaler applied
.equ cClkDiv = 4 ; Either one, two, four or eight
;
; Start time setting of the clock
; Packed BCD format
.equ cStartHours = 0x20 ; Start at 20:00 h
.equ cStartMinutes = 0x00
;
; DCF time only (clear display as long as not synced with DCF77)
.equ cDcfOnly = No ; Display/Clear unsynced time
;
; DCF signal durations in ms
.equ dcfdur_personally = No ; No = set default durations
;
.if dcfdur_personally != Yes
.equ tDcf0 = 100 ; 100 ms for a 0
.equ tDcf1 = 200 ; 200 ms for a 1
.equ tDcfP = 850 ; Pause for 0 and 1 to next second
.equ tDcfM = 1850 ; Pause for 59th second pulse
.equ tDcfT = 3000 ; Time-out of DCF signal
; DCF77 signal duration tolerance
.equ cDcfTol = 15; Tolerance in %
.else
.equ tDcf0 = 80 ; for too short signals
.equ tDcf1 = 190 ; 200 ms for a 1
.equ tDcfP = 850 ; Pause for 0 and 1 to next second
.equ tDcfM = 1850 ; Shorter period for 59th second pulse
.equ tDcfT = 3000 ; Time-out of DCF signal
; DCF77 signal duration tolerance
.equ cDcfTol = 25; Tolerance in %
.endif
;
;
; DCF77 signal input pull-up resistor
.equ cDcfPullUp = No ; Yes or no
;
; The anode line to which the double point in the
; middle is attached to
.equ cAnDp = 3 ; Should be between 1 and 4
;
; Key bouncing period
.equ tBounce = 50 ; Bouncing time, in ms
;
; Skip input mode after inactive time
.equ cSkipInpMinutes = 10 ; Minutes until input has to be finished
;
; Select the dimming source
; 0: Selects the potentiometer
; 1: Selects the opto sensor
.equ cDimOpto = No ; Select the source
;
; **********************************
; F I X & D E R I V. C O N S T
; **********************************
;
; Clock divider conversion
.if cClkDiv == 1
.equ cClkPr = 0
.else
.if cClkDiv == 2
.equ cClkPr = 1
.else
.if cClkDiv == 4
.equ cClkPr = 2
.else
.if cClkDiv == 8
.equ cClkPr = 3
.else
.error "cClkDiv has illegal value!"
.endif
.endif
.endif
.endif
;
; Clock frequency
.equ clock=xtal/cClkDiv ; Define clock frequency
;
; Half seconds and minute dividers
.equ cTc0Prsc = 8 ; TC0 prescaler
.equ cTc0Frq = clock / cTc0Prsc / 256 ; TC0 int frequency
.equ cSec2 = (cTc0Frq+1) / 2 ; Half second counter
.equ c75pcon = cSec2 / 4 ; Period over which the
; selected digits are displayed when input is
; active
.if c75pcon>255
.error "Off period too long, reduce c75pcon!"
.endif
;
; DCF signal counts, with rounding
.equ cTc1Tick = (clock+512) / 1024 ; Timer TC1 tick in Hz, @4.096 MHz = 4000 Hz
.equ cDcf0Min = ((tDcf0-tDcf0*cDcfTol/100)*cTc1Tick+500)/1000 ; Count 0 minimum
.equ cDcf0Max = ((tDcf0+tDcf0*cDcfTol/100)*cTc1Tick+500)/1000+1 ; Count 0 maximum
.equ cDcf1Min = ((tDcf1-tDcf1*cDcfTol/100)*cTc1Tick+500)/1000 ; Count 1 minimum
.equ cDcf1Max = ((tDcf1+tDcf1*cDcfTol/100)*cTc1Tick+500)/1000+1 ; Count 1 minimum
.equ cDcfPMin = ((tDcfP-tDcfP*cDcfTol/100)*cTc1Tick+500)/1000 ; Count pause minimum
.equ cDcfPMax = ((tDcfP+tDcfP*cDcfTol/100)*cTc1Tick+500)/1000+1 ; Count pause maximum
.equ cDcfMMin = ((tDcfM-tDcfM*cDcfTol/100)*cTc1Tick+500)/1000 ; Count minute minimum
.equ cDcfMMax = ((tDcfM+tDcfM*cDcfTol/100)*cTc1Tick+500)/1000+1 ; Count minute maximum
.equ cDcfT = (tDcfT*cTc1Tick+500)/1000+1 ; Counter time out
.if cDcfT>65535
.error "Clock frequency too high for DCF duration counting"
.endif
.if (cDcf0Max>=cDcf1Min)||(cDcf1Max>=cDcfPMin)||(cDcfPMax>=cDcfMMin)
.error "Overlapping DCF duration(s), reduce cDcfTol!"
.endif
;
.if cDcfPullUp == Yes
.equ mDcfKeyO = (1<<PORTC5)|(1<<PORTC4)|(1<<PORTC3)
.else
.equ mDcfKeyO = (1<<PORTC4)|(1<<PORTC3)
.endif
;
; Key bouncing constant
.equ cTc0Presc = 64 ; TC0 prescaler value
.equ cTc0Mux = clock / cTc0Presc / 256 ; MUX interrupt frequency
.equ cBounce = (tBounce*cTc0Mux+500)/1000 ; Bounce constant
;
; **********************************
; C L O C K S
; **********************************
;
; Default xtal frequency 4.096 MHz, CLKPR=4, effective clock=1.024 MHz
;
; TC0:
; Clocked with a prescaler of 8, clock tick = 128 kHz @ 1.024 MHz
; Fast PWM counting, TOP = 0xFF, overflow int = 500 Hz (2 ms) @ 1.024 MHz
; MUX-frequency = 500 / 4 = 125 Hz
; 16-Bit-Register downcount from 1,000, yields 2 Hz signal for blinking the double dot
; If zero: Set bSec flag, register downcount from 120 to yield minute for clock,
; If zero: Set bMin flag
; If rBounce not at zero:
; Both key inputs high: downcount rBounce, otherwise rBounce = cBounce
; Timer interrupt on overflow
; Compare match A: OCR0A interrupt (clears anode driver for dimming the LEDs)
; TC1:
; Counts the duration of DCF77 signals
; Clocked with a prescaler of 1,024 = 1 kHz (1.0 ms) @ 1.024 MHz
; Normal counting (cleared by PCINT on DCF signal input)
; Timer interrupt on compare match A: sets the bDcfTO flag
; ADC:
; Converts measurements on ADC0/ADC1 inputs
; Clock prescaler = 128
; N clock cycles per conversion = 13
; Conversion frequency = 615.38 Hz (1.625 ms) @ 1.024 MHz
; Sums up 64 ADC results = 104 ms @ 1.024 MHz
; If 64 adders complete: Set bAdc flag
; ca. 1 MUX cycle per update
; As the TC0 compare match update is done at the beginning
; of the next mux event (each 2 ms), no missing compare matches
; occur (no flickering)
;
; **********************************
; R E G I S T E R S
; **********************************
;
; used: R1:R0 for DCF signal duration and
; for hardw multiplication
.def rAdcCtr = R2 ; ADC sum counter
.def rAdcSumL = R3 ; ADC result sum, LSB
.def rAdcSumH = R4 ; dtp., MSB
.def rAdc = R5 ; ADC result MSB
.def rInput = R6 ; Input pins
.def rDcfBits = R7 ; Counter for DCF bits
.def rDcf3 = R8 ; DCF bits, byte 4
.def rDcf4 = R9 ; DCF bits, byte 5
.def rDcf5 = R10 ; DCF bits, byte 6
.def rDcf6 = R11 ; DCF bits, byte 7
.def rDcf7 = R12 ; DCF bits, byte 8
.def rDcfErr = R13 ; DCF77 signal error
.def rMux = R14 ; Mux channel
.def rSreg = R15 ; Save status register
.def rmp = R16 ; Define multipurpose register
.def rimp = R17 ; Multipurpose inside ints
.def rFlag = R18 ; Flag register
.equ bMin = 0 ; A minute is over
.equ bSec2 = 1 ; A half second is over
.equ bDcf = 2 ; Level change on DCF input
.equ bDcfTO = 3 ; Time out Dcf input signal
.equ bKey1 = 4 ; Key1 pressed
.equ bKey2 = 5 ; Key2 pressed
.equ bKeyA = 6 ; Key input active
.equ bKeyM = 7 ; Minute key input active
.def rFlag2 = R19 ; Second flag register
.equ bAdc = 0 ; An ADC result is available
.def rHours = R20 ; Hours time, packed BCD
.def rMinutes = R21 ; Minutes time, packed BCD
.def rBounce = R22 ; Debouncing key counter
.def rMin = R23 ; Minute counter
.def rSec2L = R24 ; 1/2 seconds counter, LSB
.def rSec2H = R25 ; dto., MSB
; used: R27:R26 = X as pointer
; used: R29:R28 = Y for MUX
; used: R31:R30 = Z for multiple purposes outside int
;
; **********************************
; S R A M
; **********************************
;
.dseg
.org SRAM_START
sMux:
.byte 4 ; 4 bytes for muxing the display
sMuxEnd:
;
sInpTime:
.byte 2 ; 2 bytes input time buffer
;
sSkipInp:
.byte 1 ; Skip input after inactive time
;
; If debug any DCF signals
sDcfPos:
.byte 1 ; Position of the next display (+1)
;
; **********************************
; C O D E
; **********************************
;
.cseg
.org 000000
;
; **********************************
; R E S E T & I N T - V E C T O R S
; **********************************
rjmp Main ; Reset vector
reti ; INT0
reti ; INT1
reti ; PCI0
rjmp Pci1Isr ; PCI1 for DCF and key input changes
reti ; PCI2
reti ; WDT
reti ; OC2A
reti ; OC2B
reti ; OVF2
reti ; ICP1
rjmp Tc1CmpAIsr ; OC1A, Dcf77 time-out
reti ; OC1B
reti ; OVF1
rjmp Tc0CmpAIsr ; OC0A: clear anode driver
reti ; OC0B
rjmp Tc0OvfIsr ; MUX and time
reti ; SPI
reti ; URXC
reti ; UDRE
reti ; UTXC
rjmp AdcIsr ; ADCC, Conversion complete
reti ; ERDY
reti ; ACI
reti ; TWI
reti ; SPMR
;
; **********************************
; I N T - S E R V I C E R O U T .
; **********************************
;
; PCI1 Interrupt service routine
Pci1Isr:
in rSreg,SREG ; Save SREG
in rimp,pDcfKeyI ; Read DCF signal and keys
eor rimp,rInput ; Compare with last input
sbrc rimp,bDcfI ; DCF77 bit set?
sbr rFlag,1<<bDcf ; Set DCF flag
Pci1Isr1:
tst rBounce ; Check bouncing counter
brne Pci1Isr3 ; Still bouncing, ignore
; rInput key bit was 1/0, now is 0/1: EOR bit is one
sbrs rimp,bKey1I ; Key 1 input changed?
rjmp Pci1Isr2 ; No, skip
sbis pDcfKeyI,bKey1I ; Input is one?
sbr rFlag,1<<bKey1 ; Set key 1 flag
Pci1Isr2:
sbrs rimp,bKey2I ; Key 2 input changed?
rjmp Pci1Isr3 ; No, skip
sbis pDcfKeyI,bKey2I ; Input is one?
sbr rFlag,1<<bKey2 ; Set key 2 flag
Pci1Isr3:
in rInput,pDcfKeyI ; Read DCF signal and keys again
out SREG,rSreg ; Restore SREG
reti
;
; TC0 overflow interrupt service routine
Tc0OvfIsr:
in rSreg,SREG ; Save SREG
clr rimp ; Anodes off
out pAnodeO,rimp
ld rimp,-Y ; Read next mux byte
out p7SegO,rimp ; Cathodes out
out pAnodeO,rMux ; Set anodes
lsr rMux ; Next lower anode
brcc Tc0OvfIsr1 ; Not at the end
ldi rimp,0b00001000 ; Start with anode 4
mov rMux,rimp ; in rMux
ldi YH,High(sMuxEnd) ; Restart from the end
ldi YL,Low(sMuxEnd)
Tc0OvfIsr1:
tst rBounce ; Check if bouncing active
breq Tc0OvfIsr4 ; No, skip
sbis pDcfKeyI,bKey1I ; Key 1 not pressed?
rjmp Tc0OvfIsr2 ; No, restart bouncing
sbic pDcfKeyI,bKey2I ; Key 2 pressed?
rjmp Tc0OvfIsr3 ; No, decrease bounce count
Tc0OvfIsr2:
ldi rBounce,cBounce ; Restart bouncing
rjmp Tc0OvfIsr4 ; Continue ISR
Tc0OvfIsr3:
dec rBounce ; Down count rBounce
Tc0OvfIsr4:
sbiw rSec2L,1 ; Decrease seconds divider
brne Tc0OvfIsr5 ; Not zero, skip
ldi rSec2H,High(cSec2) ; Restart seconds divider
ldi rSec2L,Low(cSec2)
sbr rFlag,1<<bSec2 ; Set half second flag
dec rMin ; Decrease minute divider
brne Tc0OvfIsr5 ; Not zero, skip
ldi rMin,120 ; Restart minute counter divider
sbr rFlag,1<<bMin ; Set minute flag
Tc0OvfIsr5:
out SREG,rSreg ; Restore SREG
reti
;
Tc1CmpAIsr:
in rSreg,SREG ; Save SREG
sbr rFlag,1<<bDcfTO ; Set DCF time-out flag
out SREG,rSreg ; Restore SREG
reti
;
; TC0 Compare A Interrupt service routine
Tc0CmpAIsr:
ldi rimp,0 ; Clear anode driver
out pAnodeO,rimp ; in anode port
reti
;
; ADC conversion complete interrupt service routine
AdcIsr:
in rSreg,SREG ; Save SREG
lds rimp,ADCL ; Read LSB result
add rAdcSumL,rimp ; Add to LSB sum
lds rimp,ADCH ; Read MSB
adc rAdcSumH,rimp ; Add this and carry to MSB sum
dec rAdcCtr ; Decrease counter
brne AdcIsr1 ; Not zero
sbr rFlag2,1<<bAdc ; Set flag
out SREG,rSreg ; Restore SREG
reti
AdcIsr1:
ldi rimp,(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)
sts ADCSRA,rimp ; Restart ADC
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,High(RAMEND)
out SPH,rmp ; Init MSB stack pointer
ldi rmp,Low(RAMEND)
out SPL,rmp ; Init LSB stack pointer
; Init clock prescaler
ldi rmp,1<<CLKPCE ; Activate clock prescaler
sts CLKPR,rmp ; in clock prescaler port
ldi rmp,cClkPr ; Set new prescaler
sts CLKPR,rmp ; in clock prescaler port
;
; ******************************
; H A R D W A R E D E B U G
; ******************************
;
; Debug hardware options
;
; Debug the current drivers
.if Debug_current == Yes
ldi rmp,0xFF ; All driver pins as output
out p7SegD,rmp ; in direction port
out p7SegO,rmp ; and activated
.endif
;
; Blink the green LED
.if (Debug_ledgreen == Yes)||(Debug_current == Yes)
sbi pLedGD,bLedGO ; enable output
Debug_ledgreen1:
rcall ToggleGreen
Debug_ledgreen2:
sbiw ZL,1
brne Debug_ledgreen2
rjmp Debug_ledgreen1
.endif
;
; Debug the segments
.if Debug_segments == Yes
; Hint: uses rHours as anode driver and
; rMinutes as cathode driver
ldi rmp,1<<bLedGO ; Green led as output
out pLedGD,rmp ; Set portpin direction
rcall ToggleGreen ; Toggle the green led
ldi rmp,0xFF ; All cathode pins as outputs
out p7SegD,rmp ; in direction port
clr rmp ; All cathodes off
ldi rmp,0b00001000 ; Anode driver 4 as output
out pAnodeD,rmp ; in direction port
clr rmp ; Anode drivers off
out pAnodeO,rmp ; in anode port
Debug_seg1:
; Start with digit 1
ldi rHours,0x01 ; Start with digit 1 anode
Debug_seg2:
ldi rmp,0
out pAnodeO,rmp
ldi rMinutes,0x01 ; and with the first segment
Debug_seg3:
out p7SegO,rMinutes ; Activate the cathodes
out pAnodeO,rHours ; and the anode
ldi rmp,cDebug_segDelay ; Load segment delay counter
Debug_seg4:
sbiw ZL,1 ; down-count delay
brne Debug_seg4 ; until zero
dec rmp ; Repeat counter
brne Debug_seg4 ; Additional delay
lsl rMinutes ; next segment
brcc Debug_seg3
lsl rHours
sbrs rHours,4 ; Bit 4 zero?
rjmp Debug_seg2 ; No, next digit
rjmp Debug_seg1 ; Restart with digit 1
.endif
;
; Debug muxing
.if Debug_mux8 == Yes ; Multiplex the four displays
; Wait time for MUX frequency
; Delay of loop is N = 2 + 4 * (c-1) + 3
; c = (N-5) / 4 + 1
; c= (clock/fMux/4-5)/4+1
.equ cDebug_muxdelay =(clock/cDebug_muxfreq/4-5)/4+1
; Hint: uses rHours as anode driver
ldi rmp,1<<bLedGO ; Green led as output
out pLedGD,rmp ; Set portpin direction
rcall ToggleGreen ; Toggle the green led
ldi rmp,0xFF ; All cathode pins as outputs
out p7SegD,rmp ; in direction port
ldi rmp,0xFF ; All cathodes on
out p7SegO,rmp
ldi rmp,0b00001000 ; Anode driver 4 as output
out pAnodeD,rmp ; in direction port
Debug_mux8a:
ldi rHours,0x01 ; Anode driver 1 on
Debug_mux8b:
out pAnodeO,rHours
ldi ZH,High(cDebug_muxdelay)
ldi ZL,Low(cDebug_muxdelay)
Debug_mux8c:
sbiw ZL,1
brne Debug_mux8c
lsl rHours
sbrs rHours,4
rjmp Debug_mux8b
rjmp Debug_mux8a
.endif
;
; ********************************
; N O R M A L I N I T
; ********************************
;
; Init ports
clr rmp ; Anodes off
out pAnodeO,rmp ; in Anode port
ldi rmp,0b00001000 ; Anode 4 as output pin
out pAnodeD,rmp ; in anode port
clr rmp ; Outputs cathodes low
out p7SegO,rmp ; to portpins
ldi rmp,0xFF ; Port as output
out p7SegD,rmp ; to direction port
clr rmp ; DCF and key inputs off
out pDcfKeyD,rmp ; in DCF and key inputs
ldi rmp,mDcfKeyO ; Set pull-ups
out pDcfKeyO,rmp ; in DCF and key pins
; Init TC0 for MUX
ldi rmp,0x01 ; Start with very short dim period
out OCR0A,rmp
ldi rmp,(1<<WGM00)|(1<<WGM01) ; Fast PWM mode
out TCCR0A,rmp
ldi rmp,(1<<CS01) ; Prescaler=8
out TCCR0B,rmp
ldi rmp,(1<<TOIE0)|(1<<OCIE0A) ; Interrupt on overflow and compare A
sts TIMSK0,rmp
; Init TC1
ldi rmp,High(cDcfT) ; DCF time-out, MSB
sts OCR1AH,rmp
ldi rmp,Low(cDcfT) ; dto, LSB
sts OCR1AL,rmp
clr rmp ; CTC mode on compare A
sts TCCR1A,rmp
ldi rmp,(1<<CS10)|(1<<CS12)|(1<<WGM12) ; CTC on compare A, presc=1024
sts TCCR1B,rmp
ldi rmp,1<<OCIE1A ; Timer int mask for compare A
sts TIMSK1,rmp ; to int mask port TC1
; Init ADC
ldi rmp,64 ; Start ADC counter
mov rAdcCtr,rmp ; ... with 64
ldi rmp,(1<<REFS0) ; MUX to channel ADC0
sts ADMUX,rmp ; Set channel selection
ldi rmp,(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)
sts ADCSRA,rmp ; Restart ADC
; Init flags and other parameters
clr rFlag
ldi rSec2H,High(cSec2) ; Init second counter, MSB
ldi rSec2L,Low(cSec2) ; dto., LSB
ldi rMin,120 ; Init minute counter
ldi rHours,cStartHours ; Set initial time, hours
ldi rMinutes,cStartMinutes ; dto., minutes
rcall SetTime ; Convert time to display mux
ldi YH,High(sMuxEnd)
ldi YL,Low(sMuxEnd)
ldi rmp,0b00010000
mov rMux,rmp
ldi rmp,Low(sMuxEnd) ; DCF output position
sts sDcfPos,rmp
; Init PCINT
in rInput,pDcfKeyI ; Read inputs
ldi rmp,(1<<PCINT13)|(1<<PCINT12)|(1<<PCINT11) ; The interrupt generators
sts PCMSK1,rmp
ldi rmp,1<<PCIE1 ; PCINT1 enable
sts PCICR,rmp
; Sleep mode
ldi rmp,1<<SE ; Sleep mode idle
out SMCR,rmp ; in SMCR
; Enable interrupts
sei ; Enable interrupts
;
; **********************************
; P R O G R A M L O O P
; **********************************
;
Loop:
sleep ; Go to sleep
nop ; after wake-up
sbrc rFlag,bDcfTo ; DCF77 time-out clear?
rcall DcfTimeOut ; Yes, time out
sbrc rFlag,bDcf ; DCF input level change clear?
rcall Dcf ; Yes, analyze
sbrc rFlag,bKey1 ; Key 1 pressed?
rcall Key1 ; Yes, react
sbrc rFlag,bKey2 ; Key 2 pressed?
rcall Key2 ; Yes, react
sbrc rFlag,bMin ; Minute flag clear?
rcall Minute ; Set, go to minutes
sbrc rFlag,bSec2 ; Second flag clear?
rcall Second ; Set, go to seconds
sbrc rFlag2,bAdc ; ADC flag clear?
rcall AdcFlag ; Set, go to ADC conversion
.if Debug_keys == Yes
rcall KeyDisplay
.endif
rjmp loop ; Restart loop from the beginning
;
; **********************************
; F L A G R E A C T I O N S
; **********************************
;
; **********************************
; H A L F S E C O N D O V E R
; **********************************
;
; Half second over, blink double point
Second:
cbr rFlag,1<<bSec2 ; Clear flag
lds rmp,sMux+cAnDp-1 ; Read mux byte where double point is attached to
sbrc rmp,7 ; Seventh bit clear?
rjmp Second1
ori rmp,0x80 ; Set seventh bit
sts sMux+cAnDp-1,rmp ; Bit 7 high to mux
ret
Second1:
andi rmp,0x7F ; Clear seventh bit
sts sMux+cAnDp-1,rmp ; Bit 7 low to mux
ret
;
; **********************************
; M I N U T E O V E R
; **********************************
;
; A minute is over, increase time
Minute:
cbr rFlag,1<<bMin ; Clear flag
lds rmp,sSkipInp ; Read skip input time
tst rmp ; At zero?
breq Minute0
dec rmp
sts sSkipInp,rmp
brne Minute0
cbr rFlag,(1<<bKeyA)|(1<<bKeyM) ; Clear input flags
Minute0:
ldi rmp,0x07 ; Add 7 to BCD
add rMinutes,rmp ; to minutes
brhs Minute1 ; Half overflow
ldi rmp,0x06 ; Subtract 6
sub rMinutes,rmp ; from minutes
Minute1:
cpi rMinutes,0x60 ; 60 minutes over?
brcs SetTime ; Set the time
clr rMinutes ; Restart at zero
ldi rmp,0x07 ; Add 7 to BCD
add rHours,rmp ; to hours
brhs Minute2 ; Half overflow
ldi rmp,0x06 ; Subtract 6
sub rHours,rmp ; from hours
Minute2:
cpi rHours,0x24 ; Next day?
brcs SetTime ; No
clr rHours ; Restart day
;
; **********************************
; D I S P L A Y T I M E
; **********************************
;
; Convert the hhmm time and display
SetTime:
.if cDcfOnly == Yes
; Do not update time
ret
.endif
SetTime1:
sbrc rFlag,bKeyA ; Is a key input active?
ret ; Yes, skip time output
ldi XH,High(sMux)
ldi XL,Low(sMux)
mov rmp,rHours ; Read hours
rcall Convert2Seven
mov rmp,rMinutes
rjmp Convert2Seven
;
; *******************************
; D C F 7 7 S I G N A L S
; *******************************
;
; DCF77 Time-Out signal input
DcfTimeOut:
cbr rFlag,1<<bDcfTo ; Clear flag
.if cDcfOnly == Yes
ldi rmp,0 ; Clear the MUX area
sts sMux,rmp
sts sMux+1,rmp
sts sMux+2,rmp
sts sMux+3,rmp
.endif
ret
;
; Active DCF signal
Dcf:
cbr rFlag,1<<bDcf ; Clear flag
lds XL,TCNT1L ; Read TC1 count, LSB first
lds XH,TCNT1H ; dto., MSB next
tst XH ; Check MSB
brne Dcf1 ; Larger than zero, fine
cpi XL,4 ; Minimum is 1 ms
brcc Dcf1 ; Ok
ret ; Ignore pulse, too short!
Dcf1:
.if Debug_dcfdur == Yes
; Display signal duration in hex
mov R1,XH ; Copy duration to R1:R0
mov R0,XL
ldi XH,High(sMux) ; X to sMux
ldi XL,Low(sMux)
mov rmp,R1 ; MSB first
rcall Convert2Seven ; Write first two nibbles
mov rmp,R0 ; LSB next
rcall Convert2Seven ; Write second two nibbles
mov XL,R0 ; Copy duration to X again
mov XH,R1
.endif
ldi rmp,0xFF ; Counter for compares
clr rDcfErr ; Error counter
ldi ZH,High(2*DcfDur) ; Point Z to value table
ldi ZL,Low(2*DcfDur)
Dcf2:
inc rDcfErr ; Next DCF error
inc rmp ; Next count
cpi rmp,5 ; Maximum correct count = 4
brcc DcfErr9 ; Error 9 (Signal too long)
lpm R0,Z+ ; Read LSB min from table
cp XL,R0 ; Compare with LSB min
lpm R0,Z+ ; Read MSB min from table
cpc XH,R0 ; Compare with MSB min
brcs DcfError ; Error, signal too short
lpm R0,Z+ ; Read LSB max from table
cp XL,R0 ; Compare with LSB min
lpm R0,Z+ ; Read MSB max from table
brcc Dcf2 ; Larger than max table value
cpi rmp,2 ; Zero or one?
brcc Dcf4 ; No
; Received a correct bit
.if Debug_dcfAny == Yes
push rmp
lsr rmp
ldi rmp,0b01011100
brcc Dcf2a
ldi rmp,0b00000100
Dcf2a:
rcall DcfReport
pop rmp
.endif
lsr rmp ; Shift counter bit 0 to carry
ror rDcf7 ; Roll carry into DCF bit buffer
ror rDcf6
ror rDcf5
ror rDcf4
ror rDcf3
inc rDcfBits
rjmp DcfErrClear
Dcf3:
.if Debug_dcfAny == Yes
ldi rmp,0b01110011
rcall DcfReport
.endif
rjmp DcfErrClear
Dcf4:
cpi rmp,4
brcs Dcf3 ; Pause, ignore
; Received a correct minute signal
inc rDcfErr ; Next error
ldi rmp,59 ; 59 bits received?
cp rmp,rDcfBits ; Number of bits
ldi rmp,0 ; Clear number of bits
mov rDcfBits,rmp ; in counter register
brne DcfError ; Next error
inc rDcfErr ; DCF error 7
lsr rDcf5 ; Shift Parity2 to carry
ror rDcf4 ; and into Byte 5
ror rDcf3 ; Minute 40s to byte 4
lsr rDcf4 ; Shift hours right
ror rDcf3 ; Shift parity1 to minutes
mov rmp,rDcf4 ; Minutes to rmp
rcall Parity ; Check parity in rmp
brne DcfError ; Parity odd
inc rDcfErr ; Next error
mov rmp,rDcf3 ; Check parity minutes
rcall Parity ; Check parity in rmp
brne DcfError ; Parity odd
; All checks performed and errorfree
mov rHours,rDcf4 ; Read hours
andi rHours,0x3F ; Remove upper two bits
mov rMinutes,rDcf3
andi rMinutes,0x7F ; Isolate minutes
rcall DcfErrClear
rjmp SetTime1 ; Display time
;
; DCF signal errors
; 0: No error
; 1: Signal shorter than 0
; 2: Signal shorter than 1
; 3: Signal shorter than pause
; 4: Signal shorter than missing second
; 5: Signal longer than missing second
; 6: Not 59 seconds received
; 7: Minute parity is odd
; 8: Hour parity is odd
; 9: Time out of signal input
;
DcfErr9:
; Error 9: signal too long
ldi rmp,9
mov rDcfErr,rmp
DcfError:
.if Debug_dcferr==Yes
ldi XH,High(sMux)
ldi XL,Low(sMux)
mov rmp,rDcfErr
ori rmp,0xE0 ; Error sign
rcall Convert2Seven
clr rmp ; Clear the last two digits
st X+,rmp
st X+,rmp
.endif
ret
;
; Clear the DCF error number
DcfErrClear:
.if Debug_dcferr == Yes
ldi rmp,0
sts sMux+1,rmp
.endif
ret
;
; Display DCF report in rmp
.if Debug_dcfAny == Yes
DcfReport:
ldi XH,High(sMux)
lds XL,sDcfPos
st -X,rmp
cpi XL,Low(sMux)
brne DcfReport1
ldi XL,Low(sMuxEnd)
DcfReport1:
sts sDcfPos,XL
ret
.endif
;
; Check parity of rmp
Parity:
clr ZL ; ZL is bit counter
Parity1:
lsr rmp
brcc Parity2
inc ZL ; Count
Parity2:
brne Parity1
andi ZL,1
ret
;
; DCF77 signal durations
DcfDur:
.dw cDcf0Min,cDcf0Max
.dw cDcf1Min,cDcf1Max
.dw cDcfPMin,cDcfPMax
.dw cDcfMMin,cDcfMMax
DcfDurEnd:
;
; *******************************
; K E Y P R O C E S S I N G
; *******************************
;
; Key1 is pressed
Key1:
cbr rFlag,1<<bKey1 ; Clear flag
ldi rmp,0b10101010
sts sMux,rmp
ldi rBounce,cBounce ; Start bouncing period
sbrc rFlag,bKeyA ; Key input inactive?
rjmp Key1Active ; Yes, key flag is active
sbr rFlag,1<<bKeyA ; Set key flag active
ret
Key1Active:
sbrc rFlag,bKeyM ; Minute flag clear?
rjmp Key1Minute ; No, go to minute
sbr rFlag,1<<bKeyM ; Set M flag
ldi rmp,24 ; Convert ADC to hours
mul rmp,rAdc
ldi XH,High(sInpTime) ; Point to hours input
ldi XL,Low(sInpTime)
rcall ToBcd ; Convert binary in rmp to packed BCD
mov rmp,ZL ; Result to rmp
ldi XH,High(sMux) ; Hours display
ldi XL,Low(sMux)
rjmp Convert2Seven ; Display the selected hours
;
Key1Minute:
ldi rmp,60 ; Convert ADC to minutes
mul rmp,rAdc
ldi XH,High(sInpTime+1) ; Point X to minutes
ldi XL,Low(sInpTime+1)
rcall ToBcd ; Convert to BCD
adiw XL,1 ; Point to behind minutes
ld rMinutes,-X ; Result to minutes
ld rHours,-X ; and hours
ldi rSec2L,Low(cSec2) ; Restart half second divider
ldi rSec2H,High(cSec2)
ldi rMin,120 ; Restart minute counter
cbr rFlag,(1<<bMin)|(1<<bSec2)|(1<<bKeyA)|(1<<bKeyM)
rjmp SetTime ; Set the current time
;
; Key2 is pressed
Key2:
cbr rFlag,1<<bKey2 ; Clear flag
ldi rBounce,cBounce ; Start bouncing period
sbrs rFlag,bKeyA ; Key input active?
ret ; No, ignore key
sbrc rFlag,bKeyM ; Minute active?
rjmp Key2Minute ; Yes
cbr rFlag,1<<bKeyA ; Stop input
rjmp SetTime
;
Key2Minute:
cbr rFlag,1<<bKeyM ; Return to hour input
rjmp SetTime
;
; Convert the binary number in R1 to packed BCD in ZL
; and write result to the X location
ToBcd:
ldi ZL,-0x10
ldi rmp,10
ToBcd1:
subi ZL,-0x10
sub R1,rmp
brcc ToBcd1
add R1,rmp
add ZL,R1 ; Add the ones
st X,ZL ; Store packed BCD in SRAM
ret
;
; Displays the keys
KeyDisplay:
ldi rmp,0b01011100 ; Small 0
sbic pDcfKeyI,bKey1I ; Key 1
ldi rmp,0b00000100 ; Small 1
sts sMux,rmp
ldi rmp,0b01011100 ; Small 0
sbic pDcfKeyI,bKey2I ; Key 1
ldi rmp,0b00000100 ; Small 1
sts sMux+1,rmp
ret
;
; **************************************
; A D C R E S U L T R E A D Y
; **************************************
;
AdcFlag:
cbr rFlag2,1<<bAdc ; Clear ADC flag
mov rAdc,rAdcSumH ; Copy MSB sum
clr rAdcSumL ; Restart sum
clr rAdcSumH
ldi rmp,64 ; Restart counter
mov rAdcCtr,rmp ; into rAdcCtr
.if cDimOpto == Yes
ldi rmp,(1<<REFS0)|(1<<MUX0) ; Mux to channel ADC1
sbrc rFlag,bKeyA ; Clock setting active?
ldi rmp,(1<<REFS0) ; Yes, MUX to channel ADC0
.else
ldi rmp,(1<<REFS0) ; MUX to channel ADC0
.endif
sts ADMUX,rmp ; Set new channel selection
ldi rmp,(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)
sts ADCSRA,rmp ; Restart ADC
.if Debug_Adc == Yes
rjmp AdcOut ; Display the ADC result
.endif
sbrc rFlag,bKeyA ; Key input active?
rjmp AdcFlag2 ; Update the current input digit
.if cDimOpto == Yes
com rAdc ; Invert result
.endif
AdcFlag1:
out OCR0A,rAdc ; Write result to compare port TC0
; for dimming
ret
AdcFlag2:
tst rSec2H ; MSB larger than 0?
brne AdcFlag3 ; Yes, display number
cpi rSec2L,c75pcon ; LSB smaller than 75%
brcc AdcFlag3
; Switch the two digits off
ldi XH,High(sMux) ; Point X to hours
ldi XL,Low(sMux)
sbrc rFlag,bKeyM ; Minute key clear?
ldi XL,Low(sMux+2) ; No, point to minutes
clr rmp ; Write zeroes to digit
st X+,rmp
st X,rmp
ret
AdcFlag3:
; Key input is active, calculate digit
ldi rmp,24 ; Multiply ADC result by 24
sbrc rFlag,bKeyM ; Minute flag clear?
ldi rmp,60 ; Multiply ADC result by 60
mul rmp,rAdc ; Hardware multiplication
; Convert MSB result to packed BCD
ldi ZL,-0x10 ; Start with minus 10
ldi rmp,10
AdcFlag4:
subi ZL,-0x10 ; Add ten to result
sub R1,rmp ; Subtract 10 from ADC MSB
brcc AdcFlag4 ; If not carry continue subtracting
add R1,rmp ; Undo last subtraction
add ZL,R1 ; Add rest to result
mov rmp,ZL
ldi XH,High(sMux) ; Point X to hours
ldi XL,Low(sMux)
sbrc rFlag,bKeyM ; Minute key clear?
ldi XL,Low(sMux+2) ; No, point to minutes
rjmp Convert2Seven ; Convert to display
;
; Display the ADC results
AdcOut:
ldi XH,High(sMux) ; Point X to sMux
ldi XL,Low(sMux)
ldi rmp,0b01110111 ; A sign
st X+,rmp ; to hour tens
mov R1,rAdc ; Move the rAdc to R1
ldi rmp,100 ; Hundreds
rcall AdcOutDec ; Count hundreds
ldi rmp,10
rcall AdcOutDec
mov R0,R1 ; Display the rest
rjmp AdcOutDec2
;
; Convert R1 to a decimal and display
; rmp is the decimal (100, 10)
; Uses R0
AdcOutDec:
clr R0
dec R0
AdcOutDec1:
inc R0
sub R1,rmp
brcc AdcOutDec1
add R1,rmp
; Convert R0 to 7segment and display
AdcOutDec2:
ldi ZH,High(2*SevenSeg)
ldi ZL,Low(2*SevenSeg)
add ZL,R0
ldi rmp,0
adc ZH,rmp
lpm rmp,Z
st X+,rmp
ret
;
; **************************************
; B A S I C S U B R O U T I N E S
; **************************************
;
; Toggles the green led
; by outputting on the in port
ToggleGreen:
ldi rmp,1<<bLedGO ; The green led
out pLedGI,rmp ; Toggle the led
ret
;
; Convert rmp to 7segment and write result to X
Convert2Seven:
push rmp ; Save for LSB
swap rmp
rcall Convert2SevenDigit
pop rmp
Convert2SevenDigit:
andi rmp,0x0F
ldi ZH,High(2*SevenSeg) ; Load table, MSB
ldi ZL,Low(2*SevenSeg) ; dto., LSB
add ZL,rmp ; Add number, LSB
ldi rmp,0 ; Zero
adc ZH,rmp ; Add carry
lpm rmp,Z ; Read from flash
st X+,rmp
ret
;
; Seven-segment table
; ---- a hgfedcba
; f | | b
; -g--
; e | | c
; ---- d
;
SevenSeg:
.db 0b00111111,0b00000110 ; 0+1
.db 0b01011011,0b01001111 ; 2+3
.db 0b01100110,0b01101101 ; 4+5
.db 0b01111101,0b00000111 ; 6+7
.db 0b01111111,0b01101111 ; 8+9
.db 0b01110111,0b01111100 ; 10(A)+11(b)
.db 0b00111001,0b01011110 ; 12(C)+13(d)
.db 0b01111001,0b01110001 ; 14(E)+15(F)
;
; End of source code
; Copyright
.db "(C)2019 by avr-asm-tutorial.net "
.db "C(2)10 9yba rva-mst-turoai.lrn t"
;