led7seg.s
author Tero Marttila <terom@fixme.fi>
Fri, 07 May 2010 01:46:22 +0300
changeset 8 449fee4187f6
parent 7 697ea917c901
child 15 c8f3a514bbef
permissions -rw-r--r--
ADC\!
.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