.nolist
.include "m168def.inc" ; Same family as 328P
.list
;; Interrupt Vector
.org 0x00
rjmp init
.org SPIaddr
rjmp SPI_Interrupt
.org ADCCaddr
rjmp ADC_Interrupt
;; SPI
.equ SPI_DDR = DDRB
.equ SPI_PORT = PORTB
.equ SPI_SCK = PORTB5
.equ SPI_MISO = PORTB4
.equ SPI_MOSI = PORTB3
.equ SPI_SS = PORTB2
.equ SPI_FLAGS = GPIOR0
.equ SPI_BUSY = 0
;; Initialize SPI subsystem for master operation
SPI_Init:
; Set modes
sbi SPI_DDR, SPI_SCK ; Out
sbi SPI_DDR, SPI_MOSI ; Out
sbi SPI_DDR, SPI_SS ; Out
; Drive SS high (off)
sbi SPI_PORT, SPI_SS
; Set control mode
; Enable interrupt
; Enable SPI
; MSB first
; Master mode
; Polarity/phase: Mode 0 (sample on rising edge)
; Clock rate 1/16
ldi r16, (1 << SPIE) | (1 << SPE) | (0 << DORD) | (1 << MSTR) | (0 << CPOL) | (0 << CPHA) | (0b01 << SPR0)
out SPCR, r16
; Flags
clr r0
out SPI_FLAGS, r0
; Done
ret
;; Send byte
;; Input: r16
;; XXX: should not be busy...
SPI_Send:
; Flag
sbi SPI_FLAGS, SPI_BUSY
; Enable slave (low)
cbi SPI_PORT, SPI_SS
; Write byte (starts SCK)
out SPDR, r16
; Wait for interrupt
; Done
ret
;; Wait for byte to be sent
SPI_Wait:
wait_idle:
sbic SPI_FLAGS, SPI_BUSY ; Test for busy flag
rjmp wait_idle ; loop
; Done
ret
;; Service SPI interrupt
SPI_Interrupt:
; Store SREG
in r16, SREG
; Drive SS high (off)
sbi SPI_PORT, SPI_SS
; Flag
cbi SPI_FLAGS, SPI_BUSY
; Done
out SREG, r16
reti
;; LCD
.equ LCD_DDR = DDRB
.equ LCD_PORT = PORTB
.equ LCD_OE = PORTB1 ; Output Enable (Low)
; Output font for 7-segment display
LCD_Font:
.db 0b00111111, 0b00000110 ; 0, 1
.db 0b01011011, 0b01001111 ; 2, 3
.db 0b01100110, 0b01101101 ; 4, 5
.db 0b01111101, 0b00000111 ; 6, 7
.db 0b01111111, 0b01100111 ; 8, 9
.db 0b10000000, 0b00000000 ; .,
;.db 0b00111111, ; 0
; 0b00000110, ; 1
; 0b01011011, ; 2
; 0b01001111, ; 3
; 0b01100110, ; 4
; 0b01101101, ; 5
; 0b01111101, ; 6
; 0b00000111, ; 7
; 0b01111111, ; 8
; 0b01100111, ; 9
; 0b10000000, ; .
; 0b01000000 ;
.equ LCD_0 = 0
.equ LCD_1 = 1
.equ LCD_2 = 2
.equ LCD_3 = 3
.equ LCD_4 = 4
.equ LCD_5 = 5
.equ LCD_6 = 6
.equ LCD_7 = 7
.equ LCD_8 = 8
.equ LCD_9 = 9
.equ LCD_DOT = 10
.equ LCD_EMPTY = 11
;; Initialize LCD to empty, and enable
LCD_Init:
; Setup ENable port
sbi LCD_PORT, LCD_OE ; Disabled (Low)
sbi LCD_DDR, LCD_OE ; Out
; empty
ldi r16, 0b11111111
; Output
rcall SPI_Send
rcall SPI_Wait
; Enable
cbi LCD_PORT, LCD_OE
; Done
ret
;; Display a single digit on the display
;; Input: r16
LCD_Show:
clr r0, 0
; Prep address
; base addr for font table
ldi ZH, high(2*LCD_Font)
ldi ZL, low(2*LCD_Font)
; offset
add ZL, r16
adc ZH, r0
; Load char
lpm r16, Z
;; Continue
;; Display a raw segment mask
;; Input: r16
LCD_ShowRaw:
; Invert
com r16
; Display
rjmp SPI_Send
;; ADC
.equ ADC_DDR = DDRC
.equ ADC_PORT = PORTC
.equ ADC_PIN = PORTC0
;; Initialize the ADC for starting conversions
ADC_Init:
; Setup ADMUX
; Use AVcc as ref
; Left-adjust result (for 8-bit access)
; Select ADC0
ldi r16, (0b01 << REFS0) | (1 << ADLAR) | (0b000 << MUX0)
sts ADMUX, r16
; Setup ADCSRB
; Free-running mode
ldi r16, (0b000 << ADTS0)
sts ADCSRB, r16
; Setup ADCSRA
; Enable
; Start right away...
; Auto-trigger
; Enable Interrupt
; Scale 1/128
ldi r16, (1 << ADEN) | (1 << ADSC) | (1 << ADATE) | (1 << ADIE) | (0b111 << ADPS0)
sts ADCSRA, r16
; Disable digital circuit for pin
ldi r16, (1 << ADC_PIN)
sts DIDR0, r16
; Debug LED
sbi DDRD, PORTD7
sbi PORTD, PORTD7
; Done
ret
ADC_Interrupt:
; Off
cbi PORTD, PORTD7
; Done
reti
;; Read the current 8-bit ADC sample
; Returns value in r16
ADC_Read8:
; Copy
lds r16, ADCH
; Done
ret
;; Delay for approx. one second
Delay_1s:
; 40 * 64k = 2.6M loops
ldi r20, 40
;; Delay for r20 * 64k cycles
VarDelay:
ldi r21, 255
;; Delay for r20 * r21 * 255 cycles
ShortDelay:
ldi r22, 255
delay:
dec r22
brne delay
dec r21
brne delay
dec r20
brne delay
ret
;; Count down from 9
; Returns once we've hit zero
Main_Countdown:
; init from 9
ldi r24, LCD_9
loop:
; display
mov r16, r24
rcall LCD_Show
; exit if zero
tst r24
ret
; count down
dec r24
; wait...
rcall Delay_1s
; next
rjmp loop
;; Blink between dot and empty
Main_Blink:
_blink_loop:
; dot
ldi r16, LCD_DOT
rcall LCD_Show
; wait...
rcall Delay_1s
; empty
ldi r16, LCD_EMPTY
rcall LCD_Show
rcall Delay_1s
; loop
rjmp _blink_loop
;; Chase segments
Main_Spin:
_spin_init:
; init from top
ldi r24, 0b00000001
_spin_next:
; display
mov r16, r24
rcall LCD_ShowRaw
; variable delay -> r16
rcall ADC_Read8
; short delay, from ADC
mov r20, r16
ldi r21, 1
rcall ShortDelay
; next segment
lsl r24
; go back to A if we hit G
sbrc r24, 6
rjmp _spin_init
rjmp _spin_next
Main:
init:
; Stack
ldi r16, high(RAMEND)
ldi r17, low(RAMEND)
out SPH, r16
out SPL, r17
; Enable interrupts
sei
; ADC (slowest to start up)
rcall ADC_Init
; SPI
rcall SPI_Init
; LCD (requires interrupts, blocks)
rcall LCD_Init
; Run
; spin!
rcall Main_Spin
end:
rjmp end