;
; ********************************************
; * ATtiny2313 timer with 7-segment display *
; * Muxed four digit output, digikey op *
; * (C)2012 by avr-asm-tutorial.net *
; ********************************************
;
; Included header file for target AVR type ATtiny2313
.NOLIST
.INCLUDE "tn2313def.inc" ; Header for AT90S2313
.LIST
;
; ============================================
; H A R D W A R E I N F O R M A T I O N
; ============================================
;
; _____________
; / |
; Reset o--| |--รถ + 3 V
; Key 1 Dwn o--| |--o LEDs active low
; Key 2 Up o--| |--o Segment g
; S2A Cnt-mod o--| A T |--o Segment f
; Output o--| |--o Segment e
; Key 3 Go o--| t i n y |--o Segment d
; Anode Dig1 o--| |--o Segment c
; Anode Dig2 o--| 2 3 1 3 |--o Segment b
; Anode Dig3 o--| |--o Segment a
; GND o--| |--o Anode Dig4
; |______________|
;
; =========================
; H O W I T W O R K S
; =========================
;
; Description:
; This provides a timer with a four digit 7-segment
; display. It can count down or up, depending from
; the switch 2A. During active counting, the two
; LEDs in between the minutes and seconds blinks
; fast and the output pin is active high.
;
; Internal Functioning
; 0: Processor
; Runs with internal RC oscillator at default,
; clock rate 1 Mcs/s
;
; 1. 16-bit-Timer 1
; Runs with internal clock, prescaled by 8, and
; in CTC mode, with ICR1 set to 625, so an
; interrupt occurs every 0.005 seconds to drive
; the mux of the four digits with 50 Hz.
; The timer sets the f5ms flag. If a second is
; over, the timer sets the f1s flag additionally.
;
; 2. Flags
; 2a) 5 ms flag:
; Inactive mode (counter is not started):
; If set following a timer int, the routine
; scans the key inputs. If one of the keys is
; pressed, the respective routine is called:
; - Key Down pressed: this reduces the alarm
; time by 5 seconds. The new alarm time is
; written to the EEPROM.
; - Key Up pressed: This increases the alarm
; time by 5 seconds. The new alarm time is
; written to the EEPROM.
; - Key Go pressed: This starts the timer. If
; counting down is selected (switch 2A off)
; the time is set to the alarm time. If up-
; counting is selected (switch 2A on) the
; time is cleared. The output pin A0 is set
; to high, the double-LED in the middle of
; the displays blinks. Each second pulse
; advances the time count. If the time reaches
; zero (down counting) resp. the alarm time (up
; counting) the output pin A0 is switched off.
; Active mode (counter is started):
; - Key Down pressed: The elaspsed time is
; reduced by 5.
; - Key Up pressed: The elapsed time is advanced
; by 5.
; - Key Go pressed: The counter restarts.
; Any active key blocks further key processing
; until none of the keys is pressed for at least
; 20 ms duration.
; 2b) 1 s flag:
; If not activated, the inactive time counter
; is decreased. If it reaches zero (after 20
; seconds) the digit displays are switched off
; and the two LEDs in the middle are switched
; to 1/4 to save power.
; If activated: The time elapsed is advanced
; by one and is compared with the alarm time.
; If the alarm time is reached the output pin
; is switched off (zero).
;
; ============================================
; P O R T S A N D P I N S
; ============================================
;
.EQU ppSegment = PORTB ; segments output port
.EQU pdSegment = DDRB ; segments direction port
.EQU ppAnodes = PORTD ; anode driver port
.EQU pdAnodes = DDRD ; anode driver and key inputs direction
.EQU piKeys = PIND ; key input port
.EQU biKey1 = 0 ; key 1 input bit
.EQU biKey2 = 1 ; key 2 input bit
.EQU biKey3 = 2 ; key 3 input bit
.EQU ppOutput = PORTA ; output port
.EQU pdOutput = DDRA ; direction output port
.EQU boOutput = 0 ; output bit
.EQU piJumper = PINA ; jumper input port
.EQU biJumper = 1 ; jumper input bit
;
; ============================================
; C O N S T A N T S T O C H A N G E
; ============================================
;
.EQU cMux = 50 ; MUX frequency for all four digits
;
; ============================================
; F I X + D E R I V E D C O N S T A N T S
; ============================================
;
.EQU cClock = 1000000 ; processor clock
.EQU cPresc = 8 ; prescaler TC1
.EQU cTc1Clk = cClock / cPresc ; TC1 clock
.EQU cCtc = cTc1Clk / cMux / 4 ; CTC TC1 value
.EQU c1s = 4 * cMux
;
; ============================================
; R E G I S T E R D E F I N I T I O N S
; ============================================
;
; used: R0..R1 for 7-segment conversion
; used: R2..R9 as MUX sequence storage
; free: R10..R12
.DEF rDOff = R13 ; downcounter to switch display off
.DEF rDisp = R14 ; counter to display alarm time instead of time
.DEF rSreg = R15 ; Save SREG register
.DEF rmp = R16 ; Multipurpose register
.DEF rimp = R17 ; Multi purpose inside interrupts
.DEF rFlg = R18 ; Flag register
.EQU f5ms = 0 ; 5 ms over flag
.EQU f1s = 1 ; 1 second over flag
.EQU fAct = 2 ; count and output is active
.EQU fKey = 3 ; key blocked
.DEF rTimeL = R19 ; time counter
.DEF rTimeH = R20 ;
.DEF rAlrmL = R21 ; alarm time storage
.DEF rAlrmH = R22
.DEF rKey = R23 ; for key counting
.DEF rC1sL = R24 ; downcounter for seconds
.DEF rC1sH = R25
; used: X (XH:XL) as mux pointer to SRAM
; used; Y (YH:YL) for conversion to 7-segment
; used: Z (ZH:ZL) multipurpose outside ints
;
; ============================================
; S R A M D E F I N I T I O N S
; ============================================
;
; (none, SRAM only used for stack operations)
;
; ============================================
; R E S E T A N D I N T V E C T O R S
; ============================================
;
.CSEG
.ORG $0000
rjmp Main ; Reset vector
reti ; INT0 vector
reti ; INT1 vector
rjmp Tc1CaptIsr ; TC1 capture vector
reti ; TC1COMPA vector
reti ; TC1OVF vector
reti ; TC0OVF vector
reti ; USART-RX vector
reti ; USART-UDRE vector
reti ; USART-TX vector
reti ; ANACOMP vector
reti ; PCINT vector
reti ; TC1COMPB vector
reti ; TC0COMPA vector
reti ; TC0COMPB vector
reti ; USI-START vector
reti ; USI-OVF vector
reti ; EE-READY vector
reti ; WDT-OVF vector
;
; ============================================
; I N T E R R U P T S E R V I C E S
; ============================================
;
; TC1 ICF Isr (MUX and timing)
Tc1CaptIsr:
in rSreg,SREG ; save SREG
ldi rimp,0b01111111 ; clear anode drivers
out ppAnodes,rimp
ld rimp,X+ ; read next mux bytes
bst rC1sL,5 ; copy bit 7 of second counter to T
sbrc rFlg,fAct
bld rimp,7 ; copy T flag to bit 7 to blink
out ppSegment,rimp ; output to segments
ld rimp,X+ ; read next anode driver
out ppAnodes,rimp ; set anode drivers
sbr rFlg,1<<f5ms ; set 5-ms-flag
cpi XL,10 ; End of mux field in register?
brcs Tc1CaptIsr1 ; no, go on
ldi XL,2 ; start new
Tc1CaptIsr1:
sbiw rC1sL,1 ; count til end of second
brne Tc1CaptIsr2 ; not end of second
ldi rC1sH,HIGH(c1s) ; start counter new
ldi rC1sL,LOW(c1s)
sbr rFlg,1<<f1s ; set 1-s-flag
Tc1CaptIsr2:
out SREG,rSreg ; restore SREG
reti ; return
;
; ============================================
; 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 segment driver port
ldi rmp,0xFF ; Direction segment driver port
out pdSegment,rmp ; set all port bits to 1
out pdSegment,rmp ; set all port bits output
; Init anode driver port
ldi rmp,0b01111000 ; set anode driver bits to be output
out pdAnodes,rmp
ldi rmp,0b01111111 ; set anode drivers off and inputs high
out ppAnodes,rmp
; Init switch and output port
sbi pdOutput,boOutput
sbi ppOutput,biJumper
; Read stored time from EEPROM
ldi rmp,0 ; set address
out EEAR,rmp ; to EEPROM
ldi rmp,1<<EERE ; set read enable
out EECR,rmp
in rAlrmL,EEDR ; read data
ldi rmp,1 ; set address
out EEAR,rmp ; to EEPROM
ldi rmp,1<<EERE ; set read enable
out EECR,rmp
in rAlrmH,EEDR ; read data
; inactivate display off time
ldi rmp,20
mov rDOff,rmp
; clear flag register
clr rFlg
; restart the counter
clr rDisp ; show display time for 2 sec
inc rDisp
inc rDisp
rcall Restart
; X is mux position counter
ldi XH,HIGH(2)
ldi XL,LOW(2)
ldi rC1sH,HIGH(c1s) ; start counter new
ldi rC1sL,LOW(c1s)
; Init and start timer
ldi rmp,HIGH(cCtc) ; set ICR1 register
out ICR1H,rmp
ldi rmp,LOW(cCtc)
out ICR1L,rmp
ldi rmp,0 ; set mode
out TCCR1A,rmp
ldi rmp,(1<<WGM12)|(1<<WGM13)|(1<<CS11) ; CTC, presc=8
out TCCR1B,rmp
ldi rmp,1<<ICIE1 ; enable ICI interrupt
out TIMSK,rmp
; Start loop
ldi rmp,1<<SE ; enable sleep
out MCUCR,rmp
sei ; enable interrupts
;
; ============================================
; P R O G R A M L O O P
; ============================================
;
Loop:
sleep ; go to sleep
nop ; dummy for wake up
sbrc rFlg,f5ms
rcall Flag5ms ; call 5-ms-flag
sbrc rFlg,f1s
rcall Flag1s ; call 1 second flag
rjmp loop ; go back to loop
;
; Interrupt generated 5-ms time pulse
;
.EQU cKey = (1<<biKey1)|(1<<biKey2)|(1<<biKey3)
;
Flag5ms:
cbr rFlg,1<<f5ms ; clear flag
sbrc rFlg,fKey ; key is logged?
rjmp Flag5ms1 ; yes
in rmp,piKeys ; read keys
andi rmp,cKey ; clear anything but key bits
cpi rmp,cKey ; no key pressed?
brne Flag5msKey ; key pressed
ret
Flag5msKey:
clr rKey ; set rKey to 20 ms
inc rKey
inc rKey
inc rKey
inc rKey
sbr rFlg,1<<fKey ; set key logged
sbrs rmp,biKey3 ; key 3 pressed?
rjmp Flag5msKey3
sbrc rFlg,fAct ; count active?
rjmp Flag5msKeyAct
sbrs rmp,biKey2 ; key 2 pressed?
rjmp Flag5msKey2
sbrs rmp,biKey1 ; key 1 pressed?
rjmp Flag5msKey1
cbr rFlg,1<<fKey ; clear key flag
ret
Flag5msKeyAct: ; key during active
sbrs rmp,biKey2 ; key 2 active?
rjmp Flag5msKey2Act ; yes
sbrs rmp,biKey1 ; key 1 active?
rjmp Flag5msKey1Act ; yes
ret
; Down key is pressed during inactive
Flag5msKey1:
ldi rmp,5 ; add 5 seconds
sub rAlrmL,rmp
ldi rmp,0
sbc rAlrmH,rmp
brcc Flag5msUpdate
ldi rAlrmL,5 ; underflow
clr rAlrmH
Flag5msUpdate: ; Write to EEPROM
sbic EECR,EEPE ; write clear?
rjmp Flag5msUpdate
ldi rmp,0 ; address to 0
out EEAR,rmp
out EEDR,rAlrmL ; data
cli ; clear interrupt flag
sbi EECR,EEMPE ; enable write
sbi EECR,EEPE ; start write
sei
Flag5msWriteMSB:
sbic EECR,EEPE ; write clear?
rjmp Flag5msWriteMsb
ldi rmp,1 ; address = 1
out EEAR,rmp
out EEDR,rAlrmH ; data
cli ; clear interrupt flag
sbi EECR,EEMPE ; enable write
sbi EECR,EEPE ; start write
sei
inc rDisp
inc rDisp
rjmp Restart
Flag5msKey2:
ldi rmp,5
add rAlrmL,rmp
ldi rmp,0
adc rAlrmH,rmp
cpi rAlrmL,LOW(3599)
brcs Flag5msUpdate
cpi rAlrmH,HIGH(3599)
brcs Flag5msUpdate
ldi rAlrmL,LOW(3599)
ldi rAlrmH,HIGH(3599)
rjmp Flag5msUpdate
Flag5msKey3:
rcall Restart
ldi rC1sH,HIGH(c1s) ; start counter new
ldi rC1sL,LOW(c1s)
sbr rFlg,1<<fAct ; set active flag
sbi ppOutput,boOutput ; set output port pin high
ret
Flag5msKey1Act:
ldi rmp,5
sub rTimeL,rmp
ldi rmp,0
sbc rTimeH,rmp
brcc UpdateDisplay
ldi rTimeL,1
ldi rTimeH,0
rjmp UpdateDisplay
Flag5msKey2Act:
ldi rmp,5
add rTimeL,rmp
ldi rmp,0
adc rTimeH,rmp
rjmp UpdateDisplay
; Key is logged, wait until key is inactive for 20 ms
Flag5ms1:
in rmp,piKeys ; read keys
andi rmp,cKey ; clear anything but key bits
cpi rmp,cKey ; no key pressed?
brne Flag5msSetNew ; key still pressed
dec rKey ; count 10 ms down
brne Flag5msRet ; not zero
cbr rFlg,1<<fKey ; clear key flag
ret
Flag5msSetNew:
ldi rmp,4
mov rKey,rmp
Flag5msRet:
ret
;
; Interrupt generated second pulse
;
Flag1s:
cbr rFlg,1<<f1s ; clear flag
sbrs rFlg,fAct ; counting active?
rjmp Flag1sOff ; no, check display off
sbis piJumper,biJumper ; count down?
rjmp Flag1sUp ; count up
subi rTimeL,1 ; decrease LSB counter
brcc UpDateDisplay ; no underflow
subi rTimeH,1 ; decrease MSB counter
brcc UpDateDisplay
mov rTimeL,rAlrmL ; restart timer
mov rTimeH,rAlrmH
cbr rFlg,1<<fAct ; clear active flag
cbi ppOutput,boOutput ; clear output portbit
rcall SetDisplayOffTime
rjmp UpDateDisplay
Flag1sUp:
inc rTimeL ; next second
brne Flag1sEq
inc rTimeH
Flag1sEq:
cp rTimeL,rAlrmL ; compare with alarm time
brne UpDateDisplay
cp rTimeH,rAlrmH
brne UpDateDisplay
clr rTimeL ; restart new
clr rTimeH
cbr rFlg,1<<fAct ; clear active flag
cbi ppOutput,boOutput ; clear output portbit
rcall SetDisplayOffTime
UpdateDisplay:
ldi YH,HIGH(2) ; Point to mux buffer
ldi YL,LOW(2)
tst rDisp ; show display?
breq UpdateDisplayNormal
dec rDisp
mov R0,rAlrmL ; display Alarm time
mov R1,rAlrmH
rjmp UpdateDisplayCalc
UpDateDisplayNormal:
mov R0,rTimeL ; LSB to R0
mov R1,rTimeH ; MSB to R1
UpDateDisplayCalc:
ldi ZH,HIGH(600)
ldi ZL,LOW(600)
rcall GetDigit2
ldi ZH,HIGH(60)
ldi ZL,LOW(60)
rcall GetDigit2
ldi ZL,10
rcall GetDigit1
rcall GetDigit
ret
;
; Inactive, downcount display off
;
Flag1sOff:
tst rDOff ; check at zero
breq Flag1sOffRet ; already zero
dec rDOff ; decrease wait time
brne Flag1sOffRet
ldi rmp,0x7F ; switch all displays dark
mov R3,rmp ; switch anode drivers off
mov R5,rmp
mov R7,rmp
mov R9,rmp
mov R2,rmp ; switch cathode drivers off
ldi rmp,0xFF ; switch middle LEDs dark
mov R4,rmp
mov R6,rmp
mov R8,rmp
Flag1sOffRet:
ret
;
; Set display off time to 20 seconds
;
SetDisplayOffTime:
ldi rmp,20 ; 20 seconds to display off
mov rDOff,rmp
ret
;
; Convert two-byte digits
; R1:R0 is two-byte number of seconds
; ZH:ZL is subtractor (ZH:ZL = 600 or 60)
;
GetDigit2:
clr rmp ; rmp is counter
GetDigit2Count:
sub R0,ZL ; subtract LSB
sbc R1,ZH ; subtract MSB
brcs GetDigit2Ok
inc rmp
rjmp GetDigit2Count
GetDigit2Ok:
add R0,ZL ; restore last
adc R1,ZH
ConvertDigit:
ldi ZH,HIGH(2*SegTab) ; point Z to 7-seg-table
ldi ZL,LOW(2*SegTab)
add ZL,rmp ; add digit number
lpm rmp,Z ; read result from table
st Y,rmp ; write to MUX register
adiw YL,2 ; point to next display position
ret
; convert second last digit (ZL = 10)
GetDigit1:
clr rmp ; rmp is counter
GetDigit1Count:
sub R0,ZL ; subtract number base
brcs GetDigit1Ok
inc rmp
rjmp GetDigit1Count
GetDigit1Ok:
add R0,ZL ; add digit base
rjmp ConvertDigit
; convert the last digit
GetDigit:
mov rmp,R0 ; copy last digit
rjmp ConvertDigit
;
; Restart the counter
;
Restart:
sbis piJumper,biJumper ; start up?
rjmp RestartZero ; no, down
mov rTimeL,rAlrmL ;restart time
mov rTimeH,rAlrmH
rjmp RestartMux
RestartZero:
clr rTimeL ; clear value for upcounting
clr rTimeH
; Convert time to buffer in SRAM
RestartMux:
rcall UpdateDisplay ; convert to 7-seg
ldi rmp,0b00111111 ; display Digit 1
mov R9,rmp
ldi rmp,0b01011111 ; display Digit 2
mov R7,rmp
ldi rmp,0b01101111 ; display Digit 3
mov R5,rmp
ldi rmp,0b01110111 ; display Digit 4
mov R3,rmp
ret
;
; Seven-Segment table
;
; a 0: _gFEDCBA 01000000
; --- 1: _gfedCBa 01111001
; f/g /b 2: _GfEDcBA 00100100
; --- 3: _GfeDCBA 00110000
; e/ /c 4: _GFedCBa 00011001
; --- 5: _GFeDCbA 00010010
; d 6: _GFEDCba 00000011
; 7: _gfedCBA 01111000
; 8: _GFEDCBA 00000000
; 9: _GFedCBA 00011000
SegTab:
.DW 0b0111100101000000 ; 1, 0
.DW 0b0011000000100100 ; 3, 2
.DW 0b0001001000011001 ; 5, 4
.DW 0b0111100000000011 ; 7, 6
.DW 0b0001100000000000 ; 9, 8
;
; EEPROM content for starting up
;
.EQU cAlrm = 1*60+30 ; startup at 01:30
.ESEG
.ORG 0x0000
.DB LOW(cAlrm), HIGH(cAlrm)
;
; End of source code
;