AtTiny26L-8pi_counter/counter.asm
2025-07-08 12:12:25 +02:00

630 lines
22 KiB
NASM

; A digital 4x7 digit display witch keeps track of button presses
;
; 2020 R. Branten
; Written for use with the AtTiny26L-8pi
;
; Clock set to 1Mhz internal clock
.NOLIST
.include "tn26def.inc"
.LIST
;===============================================================================
; REGISTERS
;
; Register | Name | Function
;-----------|-------------------------------------------------------
; R1 | arithmicRegisterA | Calculations
; R2 | arithmicRegisterB | Calculations
; R3 | arithmicRegisterC | Calculations
; R4 | arithmicRegisterD | Calculations
; R16 | registerA | General purpose
; R17 | registerB | General purpose
; R18 | buttonPushRegister | Button push detection
; R19 | delayRegisterB | Delaying program execution
; R20 | digit1 | Storing ones of 7-segment
; R21 | digit2 | Storing tens of 7-segment
; R22 | digit3 | Storing hundreds of 7-segment
; R23 | digit4 | Storing thousands of 7-segment
; R24 | counterLow | LSB of counter
; R25 | counterHigh | MSB of counter
; R10 | incrementButtonStatus | Button debounce state for increment
; R11 | decrementButtonStatus | Button debounce state for decrement
;
.DEF registerA = R16
.DEF registerB = R17
.DEF arithmicRegisterA = R1
.DEF arithmicRegisterB = R2
.DEF arithmicRegisterC = R3
.DEF arithmicRegisterD = R4
.DEF buttonPushRegister = R18
.DEF counterHigh = R25
.DEF counterLow = R24
.DEF delayRegisterA = R18
.DEF delayRegisterB = R19
.DEF incrementButtonStatus = R10
.DEF decrementButtonStatus = R11
; Digit numbering
; +-----+ +-----+ +-----+ +-----+
; | | | | | | | |
; | 4 | | 3 | | 2 | | 1 |
; | R23 | | R22 | | R21 | | R22 |
; +-----+ +-----+ +-----+ +-----+
;
.DEF digit1 = R20
.DEF digit2 = R21
.DEF digit3 = R22
.DEF digit4 = R23
;===============================================================================
; PINS
;
; Port | Name | Function | Direction | Init
;-------|---------------|-------------------------------|-----------|------
; PA0 | display4 | Ground fourth segment | OUTPUT | LOW
; PA1 | display2 | Ground third segment | OUTPUT | LOW
; PA2 | display3 | Ground second segment | OUTPUT | LOW
; PA3 | display1 | Ground first segment | OUTPUT | LOW
; PB0 | incrementPin | Trigger ISR | INPUT | LOW
; PB1 | decrementPin | Trigger ISR | INPUT | LOW
; PB4 | latchPin | Latching of shift register | OUTPUT | LOW
; PB5 | clockPin | Clock of shift register | OUTPUT | LOW
; PB6 | dataPin | Data to shift register | OUTPUT | LOW
;
; Inputs
.EQU incrementPin = PB0 ; PCINT0
.EQU decrementPin = PB1 ; PCINT0
; Outputs
.EQU latchPin = PB4 ; pin 07 of the Attiny to pin number 12 of the 74HC595
.EQU clockPin = PB5 ; pin 08 of the Attiny to pin number 11 of the 74HC595
.EQU dataPin = PB6 ; pin 09 of the Attiny to pin number 14 of the 74HC595
.EQU display4 = PA0
.EQU display2 = PA1
.EQU display3 = PA2
.EQU display1 = PA3
;===============================================================================
; CONSTANTS
; Segment numbers
; +--[1]--+
; | |
;[2] [3]
; | |
; +--[4]--+
; | |
;[5] [6]
; | |
; +--[7]--+ [8]
;
.EQU displayDigit0 = 0b00000011
.EQU displayDigit1 = 0b01111011
.EQU displayDigit2 = 0b00100101
.EQU displayDigit3 = 0b00110001
.EQU displayDigit4 = 0b01011001
.EQU displayDigit5 = 0b10010001
.EQU displayDigit6 = 0b10000001
.EQU displayDigit7 = 0b00111011
.EQU displayDigit8 = 0b00000001
.EQU displayDigit9 = 0b00010001
; Button debounce states
.EQU BUTTON_IDLE = 0
.EQU BUTTON_DEBOUNCE = 1
.EQU BUTTON_PRESSED = 2
;===============================================================================
; VECTORS
;
.CSEG
.ORG $0000
RJMP RESET ; Hardware Pin and Watchdog Reset
RETI ; External Interrupt Request 0
RJMP PIN_CHANGE ; Pin Change Interrupt
RETI ; Timer/Counter1 Compare Match 1A
RETI ; Timer/Counter1 Compare Match 1B
RETI ; Timer/Counter1 Overflow
RJMP TIM0_OVF ; Timer/Counter0 Overflow
RETI ; USI Start
RETI ; USI Overflow
RETI ; EEPROM Ready
RETI ; Analog Comparator
RETI ; ADC Conversion Complete
;===============================================================================
; SETUP
;
RESET:
CLI ; Disable interrupts
LDI registerA, RAMEND
OUT SP, registerA ; Set stack pointer, 8 bits on the attiny26
; Port A, direction
LDI registerA, (1<<display4) | (1<<display3) | (1<<display2) | (1<<display1)
OUT DDRA, registerA
; Port A, init status
CBI PORTA, display1
CBI PORTA, display2
CBI PORTA, display3
CBI PORTA, display1
; Port B, direction
LDI registerA, (1<<latchPin) | (1<<clockPin) | (1<<dataPin)
OUT DDRB, registerA
; Port B, init status
CBI PORTB, latchPin
CBI PORTB, clockPin
CBI PORTB, dataPin
; Interrupt PCINT1 enable
LDI registerA, (1<<PCIE0)
OUT GIMSK, registerA
; Set counter to zero
LDI counterLow, 0
LDI counterHigh, 0
; Initialize button states
LDI registerA, BUTTON_IDLE
MOV incrementButtonStatus, registerA
MOV decrementButtonStatus, registerA
SEI ; Enable interrupts
RJMP main
;===============================================================================
; MAIN PROGRAM LOOP
;
main:
RCALL checkButtonRelease ; Check for button releases
RCALL setDigits
RCALL outputCounter
RJMP main
;===============================================================================
; REGULAR SUBROUTINES
;
;-------------------------------------------------------------------------------
; INCREMENT COUNTER
;
incrementCounter:
LDI registerA, HIGH(9999)
CP counterHigh, registerA
BRLO doIncrement
LDI registerA, LOW(9999) ; Above 9984, check lower half
CP counterLow, registerA
BRSH maximumReached
doIncrement:
ADIW counterHigh:counterLow, 1
BRCS maximumReached
RET
maximumReached:
LDI counterLow, LOW(9999) ; Above 65535, fill low bytes
LDI counterHigh, HIGH(9999) ; Above 65535, fill high bytes
RET
;-------------------------------------------------------------------------------
; DECREMENT COUNTER
;
decrementCounter:
LDI registerA, 0
CP registerA, counterHigh
BRSH doDecrement
CP registerA, counterLow
BREQ minimumReached
doDecrement:
SBIW counterHigh:counterLow, 1
BRCS minimumReached
RET
minimumReached:
CLR counterLow
CLR counterHigh
RET
;-------------------------------------------------------------------------------
; GETTING DIGITS FROM COUNTER
;
setDigits:
; Push original counter to stack
PUSH counterHigh
PUSH counterLow
RCALL setThousands
RCALL setHundreds
RCALL setTens
RCALL setOnes
; Pop original counter from stack
POP counterLow
POP counterHigh
RET
setThousands:
; Should be above 00000011 11101000 (1000)
LDI digit4, 0 ; Default digit 4 to zero
LDI registerA, 0b00000011
CP counterHigh, registerA
BRSH thousandStart ; More than 1000, start counting
RET
thousandStart:
INC digit4
SUBI counterLow, LOW(1000) ; Subtract two low bytes: AL=AL-LOW(420)
SBCI counterHigh, HIGH(1000) ; Subtract high bytes and include Carry Flag
CP counterHigh, registerA
BRSH thousandStart ; More than 100, substract some more
RET
setHundreds:
; Should be above 00000000 01100100 (100)
LDI digit3, 0 ; Default digit 3 to zero
LDI registerA, 0b00000001
LDI registerB, 0b01100100
CP counterHigh, registerA
BRSH hundredStart ; More than 100, start counting
CP counterLow, registerB
BRSH hundredStart ; More than 100, start counting
RET
hundredStart:
INC digit3
SBIW counterHigh:counterLow, 50
SBIW counterHigh:counterLow, 50
LDI registerA, 0b00000001
CP counterHigh, registerA
BRSH hundredStart ; More than 100, substract some more
CP counterLow, registerB
BRSH hundredStart ; More than 100, substract some more
RET
setTens:
; Should be above 00000000 00001010 (10)
LDI digit2, 0 ; Default digit 4 to zero
LDI registerA, 0b00001010
CP counterLow, registerA
BRSH tenStart ; More than 10, start counting
RET
tenStart:
INC digit2
SBCI counterLow, 10
CP counterLow, registerA
BRSH tenStart ; More than 10, substract some more
RET
setOnes:
; Should be above 00000000 00000001 (1)
LDI digit1, 0
LDI registerA, 0b00000001
CP counterLow, registerA
BRSH oneStart
RET
oneStart:
INC digit1
DEC counterLow
CP counterLow, registerA
BRSH oneStart ; More then 1, substract some more
RET
;-------------------------------------------------------------------------------
; DRIVING SEGMENT DISPLAY
;
outputCounter:
CLI ; Disable interrupts
; drive digit 1 [X][ ][ ][ ]
MOV registerA, digit4
RCALL getShiftValue ; Get value to be shifted out
MOV arithmicRegisterA, registerB
LDI registerB, (1<<display1)
RCALL driveDisplay
; drive digit 2 [ ][X][ ][ ]
MOV registerA, digit3
RCALL getShiftValue ; Get value to be shifted out
MOV arithmicRegisterA, registerB
LDI registerB, (1<<display2)
RCALL driveDisplay
; drive digit 3 [ ][ ][X][ ]
MOV registerA, digit2
RCALL getShiftValue ; Get value to be shifted out
MOV arithmicRegisterA, registerB
LDI registerB, (1<<display3)
RCALL driveDisplay
; drive digit 4 [ ][ ][ ][X]
MOV registerA, digit1
RCALL getShiftValue ; Get value to be shifted out
MOV arithmicRegisterA, registerB
LDI registerB, (1<<display4)
RCALL driveDisplay
SEI ; Enable interrupts
RET
driveDisplay:
; Shift in bits
RCALL shiftStart
; Set correct display pin high, see registerB (display(n))
OUT PORTA, registerB
NOP ; Wait
NOP ; Wait
RCALL delay
; Stop driving display pin
EOR registerB, registerB
OUT PORTA, registerB
RET
shiftStart:
; Repeat shift 8 times
LDI registerA, 8
MOV arithmicRegisterB, registerA
LDI registerA, 0
shiftIn:
; Logic shift left to get value to be shifted in SREG 'C' flag
LSL arithmicRegisterA
BRCS shiftHigh
CBI PORTB, dataPin
RJMP doShift
shiftHigh:
SBI PORTB, dataPin
doShift:
; Cycle clock
SBI PORTB, clockPin
NOP
NOP
CBI PORTB, clockPin
; End if all shifted out
DEC arithmicRegisterB
CP arithmicRegisterB, registerA
BRNE shiftIn
; Toggle latch
SBI PORTB, latchPin
NOP
NOP
CBI PORTB, latchPin
RET
getShiftValue:
; Set data to be shifted out
; The digit should be loaded in registerA
LDI registerB, 0
CP registerA, registerB
BRNE shiftValue_1
LDI registerB, displayDigit0 ; Set shift bits to 0
RET
shiftValue_1:
INC registerB
CP registerA, registerB
BRNE shiftValue_2
LDI registerB, displayDigit1 ; Set shift bits to 1
RET
shiftValue_2:
INC registerB
CP registerA, registerB
BRNE shiftValue_3
LDI registerB, displayDigit2 ; Set shift bits to 2
RET
shiftValue_3:
INC registerB
CP registerA, registerB
BRNE shiftValue_4
LDI registerB, displayDigit3 ; Set shift bits to 3
RET
shiftValue_4:
INC registerB
CP registerA, registerB
BRNE shiftValue_5
LDI registerB, displayDigit4 ; Set shift bits to 4
RET
shiftValue_5:
INC registerB
CP registerA, registerB
BRNE shiftValue_6
LDI registerB, displayDigit5 ; Set shift bits to 5
RET
shiftValue_6:
INC registerB
CP registerA, registerB
BRNE shiftValue_7
LDI registerB, displayDigit6 ; Set shift bits to 6
RET
shiftValue_7:
INC registerB
CP registerA, registerB
BRNE shiftValue_8
LDI registerB, displayDigit7 ; Set shift bits to 7
RET
shiftValue_8:
INC registerB
CP registerA, registerB
BRNE shiftValue_9
LDI registerB, displayDigit8 ; Set shift bits to 8
RET
shiftValue_9:
LDI registerB, displayDigit9 ; Set shift bits to 9
RET
delay:
LDI delayRegisterA, 255
LDI delayRegisterB, 0
delayStart:
DEC delayRegisterA
CP delayRegisterA, delayRegisterB
BRNE delayStart
RET
setTimer:
; Use the 8-bit timer0
; We want to wait about 1.6ms for faster button response
; Using prescaler of 64 gives about 1.6ms which is much faster
LDI registerA, 0b00000011 ; CS01=1, CS00=1 for prescaler /64
OUT TCCR0, registerA ; Set prescaler to /64
LDI registerA, (1<<TOV0) ; Load timer overflow flag
OUT TIFR, registerA ; Reset by writing a 1
LDI registerA, (1<<TOIE0) ; Load overflow interrupt flag for timer 0
OUT TIMSK, registerA ; Enable overflow interrupt flag for timer 0
RET
;===============================================================================
; ISR SUBROUTINES
;
;-------------------------------------------------------------------------------
; CHECK BUTTON RELEASE
; This routine should be called periodically to detect button releases
;
checkButtonRelease:
; Check increment button release
LDI registerA, BUTTON_PRESSED
CP incrementButtonStatus, registerA
BRNE checkDecrementRelease ; Not pressed, check decrement
IN registerA, PINB ; Read pin states
LDI registerB, (1<<incrementPin)
AND registerA, registerB ; Check if increment button is still pressed
CP registerA, registerB
BRNE incrementReleased ; Button released
RJMP checkDecrementRelease ; Still pressed, check decrement
incrementReleased:
LDI registerA, BUTTON_IDLE
MOV incrementButtonStatus, registerA ; Reset to idle state
checkDecrementRelease:
LDI registerA, BUTTON_PRESSED
CP decrementButtonStatus, registerA
BRNE releaseExit ; Not pressed, exit
IN registerA, PINB ; Read pin states
LDI registerB, (1<<decrementPin)
AND registerA, registerB ; Check if decrement button is still pressed
CP registerA, registerB
BRNE decrementReleased ; Button released
RJMP releaseExit ; Still pressed, exit
decrementReleased:
LDI registerA, BUTTON_IDLE
MOV decrementButtonStatus, registerA ; Reset to idle state
releaseExit:
RET
;
PIN_CHANGE:
CLI ; Disable interrupt
; Push registers on stack
PUSH registerA
PUSH registerB
; Check which button caused the interrupt
IN registerA, PINB ; Read current pin states
LDI registerB, (1<<incrementPin)
AND registerA, registerB ; Check increment button
CP registerA, registerB
BRNE checkDecrementPin ; Not increment, check decrement
; Increment button was pressed
LDI registerA, BUTTON_IDLE
CP incrementButtonStatus, registerA ; Check if button was idle
BRNE pinChangeExit ; Already processing, ignore
LDI registerA, BUTTON_DEBOUNCE
MOV incrementButtonStatus, registerA ; Set to debounce state
RCALL setTimer ; Start debounce timer
RJMP pinChangeExit
checkDecrementPin:
IN registerA, PINB ; Read current pin states again
LDI registerB, (1<<decrementPin)
AND registerA, registerB ; Check decrement button
CP registerA, registerB
BRNE pinChangeExit ; Neither button, exit
; Decrement button was pressed
LDI registerA, BUTTON_IDLE
CP decrementButtonStatus, registerA ; Check if button was idle
BRNE pinChangeExit ; Already processing, ignore
LDI registerA, BUTTON_DEBOUNCE
MOV decrementButtonStatus, registerA ; Set to debounce state
RCALL setTimer ; Start debounce timer
pinChangeExit:
; Pop registers from stack
POP registerB
POP registerA
SEI ; Enable interrupt
RETI
TIM0_OVF:
CLI ; Disable interrupt
; Push registers on stack
PUSH registerA
PUSH registerB
; Clear stop the timer by setting Clock select to 0
LDI registerA, 0x000000
OUT TCCR0, registerA
; Check current pin states after debounce period
IN registerA, PINB ; Read current pin states
; Check increment button
LDI registerB, BUTTON_DEBOUNCE
CP incrementButtonStatus, registerB
BRNE checkDecrementTimer ; Not in debounce state, check decrement
LDI registerB, (1<<incrementPin)
AND registerA, registerB ; Check if increment button is still pressed
CP registerA, registerB
BRNE incrementNotPressed ; Button not pressed, reset state
; Button is still pressed after debounce, process it
LDI registerA, BUTTON_PRESSED
MOV incrementButtonStatus, registerA
RCALL incrementCounter
RJMP timerExit
incrementNotPressed:
LDI registerA, BUTTON_IDLE
MOV incrementButtonStatus, registerA ; Reset to idle state
checkDecrementTimer:
LDI registerB, BUTTON_DEBOUNCE
CP decrementButtonStatus, registerB
BRNE timerExit ; Not in debounce state, exit
IN registerA, PINB ; Read current pin states again
LDI registerB, (1<<decrementPin)
AND registerA, registerB ; Check if decrement button is still pressed
CP registerA, registerB
BRNE decrementNotPressed ; Button not pressed, reset state
; Button is still pressed after debounce, process it
LDI registerA, BUTTON_PRESSED
MOV decrementButtonStatus, registerA
RCALL decrementCounter
RJMP timerExit
decrementNotPressed:
LDI registerA, BUTTON_IDLE
MOV decrementButtonStatus, registerA ; Reset to idle state
timerExit:
; Pop registers from stack
POP registerB
POP registerA
SEI ; Enable interrupt
RETI