;*****************************************************************
;
; File Name : 'mp3.asm'
; Title : MP3 Player
; Date : 2001.11.25
; Version : 0.01
; Author : Baris Inan
; Target MCU : ATmega163
;
;*****************************************************************
;.device ATMEGA163 ; Prohibits use of non-implemented instructions;
.nolist
.include "m163def.inc"
.list
;.equ CPU_CLOCK =7872800
.equ BAUD_RATE =38400
.equ CPU_CLOCK =8000000
.equ UART_BAUD_DIV =((CPU_CLOCK+8*BAUD_RATE)/(16*BAUD_RATE))-1
; Number of MP3 files stored in SRAM[$60]
.equ NUM_TRACKS =$0060
.def SECTOR =r2
.def CYL_LO =r3
.def CYL_HI =r4
.def HEAD =r5
.def TRACK =r6 ; Keeps the current track no
.def CLUS_LO =r7 ; Current cluster low
.def CLUS_HI =r8 ; Current cluster hi
.def UARTREG =r10 ; Uart receive register
.def RESETREG =r11 ;
.def FLAGREG =r12 ;
.def TEMP0 =r16 ; temporary register 0
.def TEMP1 =r17 ; temporary register 1
.def TEMP2 =r18 ; temporary register 2
.def RETURN0 =r19 ; function return 0
.def RETURN1 =r20 ; function return 1
.def ARG0 =r21 ; function argument 0
.def ARG1 =r22 ; function argument 1
.def ARG2 =r23 ; function argument 2
.def TEMP3 =r24
; and as a reminder
; m163def.inc contains
;.def XL =r26
;.def XH =r27
;.def YL =r28
;.def YH =r29
;.def ZL =r30
;.def ZH =r31
; code segment
.cseg
.org 0x000
jmp reset ; $000 Reset Handler
jmp Int0Isr ; $002 IRQ0 Handler
jmp reset ; $004 IRQ1 Handler
jmp reset ; $006 Timer2 Compare Handler
jmp Timer2Isr ; $008 Timer2 Overflow Handler
jmp reset ; $00a Timer1 Capture Handler
jmp reset ; $00c Timer1 CompareA Handler
jmp reset ; $00e Timer1 CompareB Handler
jmp reset ; $010 Timer1 Overflow Handler
jmp reset ; $012 Timer0 Overflow Handler
jmp SPICompIsr ; $014 SPI Transfer Complete Handler
jmp reset ; $016 UART RX Complete Handler
jmp reset ; $018 UDR Empty Handler
jmp reset ; $01a UART TX Complete Handler
jmp reset ; $01c ADC Conversion Complete Interrupt Handler
jmp reset ; $01e EEPROM Ready Handler
jmp reset ; $020 Analog Comparator Handler
jmp reset ; $022 2-wire Serial Interface Interrupt Handler
;File size 2020 (07E4 hex)words
.include "p02_0609.inc"
;File size 2011 words (07DB hex)
;.include "p02_0609_mine.inc"
reset:
; initialize stack pointer
ldi TEMP0, low(RAMEND)
out SPL, TEMP0
ldi TEMP0, high(RAMEND)
out SPH, TEMP0
clr TEMP0
out UBRRHI, TEMP0
ldi TEMP0, UART_BAUD_DIV ; set baud rate
out UBRR, TEMP0
sbi UCSRB, TXEN ; enable serial transmitter
sbi UCSRB, RXEN ; enable serial receiver
; sbi UCSRB, RXCIE ; enable receiver interrupts
; Timer2
ldi TEMP0, $07 ; Prescale 1024
out TCCR2, TEMP0
in TEMP0, TIMSK
sbr TEMP0, 1<<TOIE2 ; Enable timer2 interrupt
out TIMSK, TEMP0
;I2C bit rate register
; TWBR = 80 gives around 45Kbps for 8Mhz
ldi TEMP0, $C0 ; 80
out TWBR, TEMP0
;SPI configuration
;Fclk/16=500Kbps
;Fclk/2 and Fclk/4 doesn't work
;Fclk/8 has some skips
;Fclk/16 can support 128Kbps well and some skips for 192Kbps
ldi TEMP0, 0b11010101 ;
out SPCR, TEMP0
cbi SPSR, 0 ; SPI2x=0
; INT0 configuration
in TEMP0, GIMSK
sbr TEMP0, 1<<INT0 ; Enable external interrupt INT0
out GIMSK, TEMP0
ldi TEMP0, $03 ; 0000-0011
out MCUCR, TEMP0 ; Rising edge on INT0
ldi ARG0, $41 ; A
rcall putchar ; Debug
ldi TEMP0, $FF ; port C all outputs
out DDRC, TEMP0
ldi TEMP0, $7F ; 0111-1111
out PORTC, TEMP0 ; CS1=CS0=1 -> CF in standby, PC7=0 -> display off
ldi TEMP0, $B0 ; 0b10110000: MISO is input, B0-B3 input
out DDRB, TEMP0
ldi TEMP0, $30 ; 0b00110000: SCK=0 MOSI=1 SS=1
out PORTB, TEMP0
ldi TEMP0, $E0 ; 7-6-5 output, 2 input 111-00-0-00
out DDRD, TEMP0 ; Port D
clr TEMP0
out PORTD, TEMP0 ; All zeros
; Wait for CF reset
rcall chbsy
ldi ARG0, $61 ; a
rcall putchar ; Debug
; Set feature to 8-bit mode
ldi ARG1, $FF ; Write operation
ldi ARG0, $04 ; 000-001-00: A2-A0 = 001 feature reg.
ldi ARG2, $01 ; Enable 8-bit data transfers
rcall memrw
ldi ARG0, $42 ; B
rcall putchar ; Debug
; Send Set Feature Command
ldi ARG1, $FF ; Write operation
ldi ARG0, $1C ; 000-111-00: A2-A0 = 111 command reg.
ldi ARG2, $EF ; Set Features
rcall memrw
ldi ARG0, $62 ; b
rcall putchar ; Debug
;Wait for CF
rcall chbsy ; Check if CF busy
rcall is_lcd_busy ; Check if LCD is busy
; Timer 1 for LCD's PWM input
ldi TEMP0, $81 ; 1000-0001: COM1A1=PWM10=1
out TCCR1A, TEMP0
ldi TEMP0, $01 ; 0000-0001: CS10=1 (Prescale=1)
out TCCR1B, TEMP0
clr TEMP0
out OCR1AH, TEMP0
ldi TEMP0, 21 ; 21/256 duty cycle for contrast
out OCR1AL, TEMP0
; Initialize LCD
ldi ARG0, $38 ; 8-bit, 2 lines, 5x7
rcall lcd_command_write
ldi ARG0, $0C ; Display ON
rcall lcd_command_write
ldi ARG0, $06 ; Increment, no shift
ldi ARG0, $43 ; C
rcall putchar ; Debug
;Wait for "C"
;mp3_pause1:
; rcall getchar
; cpi RETURN0, $43 ; "C"
; brne mp3_pause1
; Read IDENT register in MP3 decoder
ldi ARG1, $01
rcall I2C_read_byte
; Write DATA_REQ_ENABLE register
ldi ARG1, $18
ldi ARG2, $04
rcall I2C_write_byte
; Read DATA_REQ_ENABLE register
ldi ARG1, $18
rcall I2C_read_byte
;mp3_pause4:
; rcall getchar
; cpi RETURN0, $43 ; "C"
; brne mp3_pause4
; Send the MP3 decoder configuration file
clr XH ; Counter for .inc file
clr XL
ldi ZH, high(config*2)
ldi ZL, low (config*2)
mp3_wr:
lpm ; read least signif. byte
mov ARG1, r0 ; Register address
adiw ZL, 1
lpm ; read most signif. byte
mov ARG2, r0 ; Data
adiw ZL, 1 ; increment
rcall I2C_write_byte
;30 ms delay after SOFT RESET command (addr=16)
cpi ARG1, 16
brne mp3_wr_skip
clr TEMP0
out TCNT2, TEMP0 ; Start timer
clr TEMP3
sei
timer2: cpi TEMP3, $00
nop
nop
breq timer2
cli
in TEMP0, TIMSK
cbr TEMP0, 1<<TOIE2 ; Disable timer2 interrupt
out TIMSK, TEMP0
mp3_wr_skip:
adiw XL, 1
cpi XL, $E4 ;Compare with file size
brne mp3_wr
cpi XH, $07 ;
brne mp3_wr
;Send RUN command to decoder
ldi ARG1, 114 ; RUN
ldi ARG2, 1 ; RUN
rcall I2C_write_byte
;Send Play Command to decoder
ldi ARG1, 19 ; PLAY
ldi ARG2, 1 ; PLAY
rcall I2C_write_byte
;Read PLAY register
; rcall I2C_read_byte
;Read SCKL_POL register
; ldi ARG1, $0D
; rcall I2C_read_byte
;Wait for "C"
;mp3_pause:
; rcall getchar
; cpi RETURN0, $43 ; "C"
; brne mp3_pause
rcall read_root_dir
rcall LF_CR
; Print Number of Files
clr ARG2 ; Don't send to LCD
lds ARG1, NUM_TRACKS
mov TEMP0, ARG1
rcall print_hex
ldi ARG0, $2E ; .
rcall putchar
ldi ARG0, $2E ; .
rcall putchar
;Print The first cluster no's for files
ldi YH, $00
ldi YL, $62
files_loop:
clr ARG2 ; Don't send to LCD
ld ARG1, Y+ ; Low byte
rcall print_hex
ld ARG1, Y+ ; High byte
rcall print_hex
ldi ARG0, $2E ; .
rcall putchar
ldi ARG0, $2E ; .
rcall putchar
dec TEMP0
cpi TEMP0, 0
brne files_loop
;Print the last files last cluster no + 1
clr ARG2 ; Don't send to LCD
ld ARG1, Y+ ; Low byte
rcall print_hex
ld ARG1, Y+ ; High byte
rcall print_hex
; Print the sector addresses of tracks
rcall LF_CR
clr TRACK
inc TRACK ; Start at Track #1
lds TEMP0, NUM_TRACKS
inc TEMP0
print_tracks:
rcall compute_addr
clr ARG2 ; Don't send to LCD
mov ARG1, CYL_HI
rcall print_hex
ldi ARG0, $2E ; .
rcall putchar
mov ARG1, CYL_LO
rcall print_hex
ldi ARG0, $2E ; .
rcall putchar
mov ARG1, HEAD
rcall print_hex
ldi ARG0, $2E ; .
rcall putchar
mov ARG1, SECTOR
rcall print_hex
inc TRACK
rcall LF_CR
cp TRACK, TEMP0
brne print_tracks
; Initialize to TRACK #1
clr TRACK
inc TRACK
rcall compute_addr
rcall read_tag
; Wait here for PLAY input
play_loop2:
sbic PINB, 3 ; Skip rjmp if button is pressed
rjmp play_loop2
ldi YH, $02
clr YL
clr RESETREG
sei
main:
;Set Cylinder Low Reg.
ldi ARG1, $FF ; Write operation
ldi ARG0, $10 ; 000-100-00: cyl low reg.
mov ARG2, CYL_LO ; cyl_lo = CYL_LO
rcall memrw
;Set Cylinder Hi Reg.
ldi ARG1, $FF ; Write operation
ldi ARG0, $14 ; 000-101-00: cyl hi reg.
mov ARG2, CYL_HI ; cyl_hi = 00
rcall memrw
;Set Head No.
ldi ARG1, $FF ; Write operation
ldi ARG0, $18 ; 000-110-00: select card/head reg.
mov ARG2, HEAD ; 1010-0000: head no = 0
rcall memrw
;Set Sector Number Register
ldi ARG1, $FF ; Write operation
ldi ARG0, $0C ; 000-011-00: sec num reg.
mov ARG2, SECTOR ; sec_num = SECTOR
rcall memrw
;Set 01H in sector count register (read one sector)
ldi ARG1, $FF ; Write operation
ldi ARG0, $08 ; 000-010-00: sec cnt.
ldi ARG2, $01 ; 1 sector
rcall memrw
; Set 20H in Command register
ldi ARG1, $FF ; Write operation
ldi ARG0, $1C ; 000-111-00: A2-A0 = 111 command reg.
ldi ARG2, $20 ; Read Sector(s)
rcall memrw
rcall chbsy ; Check if busy
;cbi PORTB, 0 ; LED0 on
nop ; instead of upper instr.
sbrs RESETREG, 0
rjmp rd_lp_2a_end
; Z=512
; Case1: Y<256 pause here until Y=256
; Case2: Y>=256 continue
rd_lp2a:
sbrs YH, 0
rjmp rd_lp2a
rd_lp_2a_end:
;sbi PORTB, 0 ; LED0 off
nop ; instead of upper instr.
; Write to SRAM locations 512-1023
ldi ZH, $02 ;
clr ZL
clr ARG0
clr ARG1
;Read loop 1
; 0 <= Z <= 255 read into SRAM and SRAM+1
; 256 <= Z <= 511 ignore twice
rd_lp1:
; Fast read
ldi TEMP0, 0b01000000 ; Set PC6 to 1 and 000 address
out PORTC, TEMP0
cbi PORTC, 5 ; clear PC5 = -CS0
sbi PORTD, 7 ; set PD7 = -IOWR
cbi PORTD, 6 ; clear PD6 = -IORD
ldi TEMP0, $00 ; also delay
out DDRA, TEMP0 ; PORTA input
in RETURN0, PINA ; Read from the memory bus
sbi PORTD, 6 ; set -IORD
sbi PORTC, 5 ; set -CS0
sbrs ZH, 0 ; skip store if greater than 256
st Z, RETURN0 ; Store data
adiw ZL, 1
; Fast read
ldi TEMP0, 0b01000000 ; Set PC6 to 1 and 000 address
out PORTC, TEMP0
cbi PORTC, 5 ; clear PC5 = -CS0
sbi PORTD, 7 ; set PD7 = -IOWR
cbi PORTD, 6 ; clear PD6 = -IORD
ldi TEMP0, $00 ; also delay
out DDRA, TEMP0 ; PORTA input
in RETURN0, PINA ; Read from the memory bus
sbi PORTD, 6 ; set -IORD
sbi PORTC, 5 ; set -CS0
sbrs ZH, 0
st Z, RETURN0
adiw ZL, 1 ; increment
cpi ZH, $04
brne rd_lp1
rcall chbsy
;RESETREG is cleared at reset and set to $FF after on.
;So, the start_spi_tx function call is executed only once
sbrs RESETREG, 0
rcall start_spi_tx
; Set 20H in Command register
ldi ARG1, $FF ; Write operation
ldi ARG0, $1C ; 000-111-00: A2-A0 = 111 command reg.
ldi ARG2, $20 ; Read Sector(s)
rcall memrw
rcall chbsy ; Check if busy
sbrs RESETREG, 0
rjmp rd_lp_1a_end
; Z=512
; Case1: Y>=256 pause here until Y=0
; Case2: Y<256 continue
rd_lp1a:
sbrc YH, 0
rjmp rd_lp1a
rd_lp_1a_end:
ldi ZH, $02 ;
clr ZL
;Read loop 2
; 0 <= Z <= 255 ignore twice
; 256 <= Z <= 511 read into SRAM and SRAM+1
rd_lp2:
; Fast read
ldi TEMP0, 0b01000000 ; Set PC6 to 1 and 000 address
out PORTC, TEMP0
cbi PORTC, 5 ; clear PC5 = -CS0
sbi PORTD, 7 ; set PD7 = -IOWR
cbi PORTD, 6 ; clear PD6 = -IORD
ldi TEMP0, $00 ; also delay
out DDRA, TEMP0 ; PORTA input
in RETURN0, PINA ; Read from the memory bus
sbi PORTD, 6 ; set -IORD
sbi PORTC, 5 ; set -CS0
sbrc ZH, 0 ; skip store if less than 256
st Z, RETURN0 ; Store data
adiw ZL, 1
; Fast read
ldi TEMP0, 0b01000000 ; Set PC6 to 1 and 000 address
out PORTC, TEMP0
cbi PORTC, 5 ; clear PC5 = -CS0
sbi PORTD, 7 ; set PD7 = -IOWR
cbi PORTD, 6 ; clear PD6 = -IORD
ldi TEMP0, $00 ; also delay
out DDRA, TEMP0 ; PORTA input
in RETURN0, PINA ; Read from the memory bus
sbi PORTD, 6 ; set -IORD
sbi PORTC, 5 ; set -CS0
sbrc ZH, 0
st Z, RETURN0
adiw ZL, 1 ; increment
cpi ZH, $04
brne rd_lp2
rcall chbsy
ldi ARG0, 1
rcall inc_sector ; increment sector by one
mov TEMP0, SECTOR ; Check if SECTOR == xxxxxx00
andi TEMP0, $03 ; increment CLUS if so
cpi TEMP0, $00
brne skip_inc_clus1
ldi TEMP0, 1 ; increment CLUS by 1
clr TEMP1
add CLUS_LO, TEMP0
adc CLUS_HI, TEMP1
rcall check_overflow
skip_inc_clus1:
;----------------------------
; Check buttons for commands
; B3:PLAY - B2:STOP - B1:FF - B0:REV
; Check FF button
sbic PINB, 1 ; Skip rjmp if button is pressed
rjmp ff_skip1
in TEMP0, TIMSK
sbr TEMP0, 1<<TOIE2 ; Enable timer2 interrupt
out TIMSK, TEMP0
clr TEMP0
out TCNT2, TEMP0 ; Start timer
clr TEMP3
timer2_loop1: ; 200ms delay
cpi TEMP3, $06
nop
nop
brne timer2_loop1
sbis PINB, 1 ; Skip rjmp if button is not pressed
rjmp ff_skip2
inc TRACK
lds TEMP0, NUM_TRACKS
inc TEMP0
cp TRACK, TEMP0
brne ff_skip4
; This is the last Track
clr TRACK
inc TRACK ; Reset to Track #1
; Wait here for PLAY input
play_loop1:
sbic PINB, 3 ; Skip rjmp if button is pressed
rjmp play_loop1
ff_skip4:
rcall compute_addr
rcall read_tag
rjmp ff_skip3
ff_skip2: ; Button is still pressed
sbic PINB, 1 ; Skip rjmp if button is pressed
rjmp ff_skip3
clr TEMP0
out TCNT2, TEMP0 ; Start timer
clr TEMP3
timer2_loop2: ; 33ms delay
cpi TEMP3, $01
nop
nop
brne timer2_loop2
; 33ms ~= 500 bytes (128 kbps)
; 16 increment = 16X fast forward (slower for higher bitrates)
ldi ARG0, 16
rcall inc_sector ; increment sector by 16
ldi TEMP0, 4 ; increment CLUS by 4
clr TEMP1
add CLUS_LO, TEMP0
adc CLUS_HI, TEMP1
rcall check_overflow
rjmp ff_skip2
ff_skip3:
in TEMP0, TIMSK
cbr TEMP0, 1<<TOIE2 ; Disable timer2 interrupt
out TIMSK, TEMP0
ff_skip1:
;------------------------------
; Check REV button
sbic PINB, 0 ; Skip rjmp if button is pressed
rjmp rev_skip1
in TEMP0, TIMSK
sbr TEMP0, 1<<TOIE2 ; Enable timer2 interrupt
out TIMSK, TEMP0
clr TEMP0
out TCNT2, TEMP0 ; Start timer
clr TEMP3
timer2_loop3: ; 200ms delay
cpi TEMP3, $06
nop
nop
brne timer2_loop3
rev_skip2:
sbis PINB, 0 ; Skip rjmp if button is not pressed
rjmp rev_skip2
dec TRACK
clr TEMP0
cp TRACK, TEMP0
brne rev_skip4
; This is the first Track
clr TRACK
inc TRACK ; Reset to Track #1
rev_skip4:
rcall compute_addr
rcall read_tag
rjmp rev_skip3
; Did not implement Rewinding as inc_sector and check_overflow functions
; need to be modified.
;rev_skip2: ; Button is still pressed
; sbic PINB, 0 ; Skip rjmp if button is pressed
; rjmp rev_skip3
; clr TEMP0
; out TCNT2, TEMP0 ; Start timer
; clr TEMP3
;timer2_loop4: ; 33ms delay
; cpi TEMP3, $01
; nop
; nop
; brne timer2_loop4
;
; ; 33ms ~= 500 bytes (128 kbps)
; ; 16 increment = 16X fast forward (slower for higher bitrates)
; ldi ARG0, 16
; rcall inc_sector ; increment sector by 16
; ldi TEMP0, 4 ; increment CLUS by 4
; clr TEMP1
; add CLUS_LO, TEMP0
; adc CLUS_HI, TEMP1
; rcall check_overflow
;
; rjmp rev_skip2
rev_skip3:
in TEMP0, TIMSK
cbr TEMP0, 1<<TOIE2 ; Disable timer2 interrupt
out TIMSK, TEMP0
rev_skip1:
;------------------------------
ldi TEMP0, $FF
mov RESETREG, TEMP0
rjmp main
;No arguments: Address always 000
;RETURN0 is output
fastread:
push TEMP0
ldi TEMP0, 0b01000000 ; Set PC6 to 1 and 000 address
out PORTC, TEMP0
cbi PORTC, 5 ; clear PC5 = -CS0
sbi PORTD, 7 ; set PD7 = -IOWR
cbi PORTD, 6 ; clear PD6 = -IORD
ldi TEMP0, $00 ; also delay
out DDRA, TEMP0 ; PORTA input
in RETURN0, PINA ; Read from the memory bus
sbi PORTD, 6 ; set -IORD
sbi PORTC, 5 ; set -CS0
pop TEMP0
ret
; Function memrw
; Read and Write to the Flash Card.
; CS1=1 and CS0=0 when reading or writing.
; So, don't use for Device Control, Alt Status and Drive Address regs.
; ARG0: Address 0-2 : PC2-4 (xxxnnnxx)
; ARG1= $00 -> read
; ARG1= $FF -> write
; ARG2: Write value (not used in read)
; RETURN0: Read value (unchanged in write)
memrw:
push TEMP0
push TEMP1
push TEMP2
push ARG0
mov TEMP1, ARG0
andi TEMP1, $1C ; 00011100 and TEMP1 = 000xxx00
ldi TEMP2, $00 ; port C all inputs
out DDRC, TEMP2 ;
in TEMP0, PINC ; Read current bus value
ldi TEMP2, $FF ; port C all outputs
out DDRC, TEMP2 ;
or TEMP0, TEMP1 ; Set ones
mov TEMP1, ARG0
ori TEMP1, $E3 ; 11100011 or TEMP1 = 111xxx11
and TEMP0, TEMP1 ; Set zeros
out PORTC, TEMP0 ; Addr 0-2
sbi PORTC, 6 ; set PC6 = -CS1
cbi PORTC, 5 ; clear PC5 = -CS0
cpi ARG1, $00
brne wr_jmp
;Read
sbi PORTD, 7 ; set PD7 = -IOWR
cbi PORTD, 6 ; clear PD6 = -IORD
ldi TEMP1, $00 ; also delay
out DDRA, TEMP1 ; PORTA input
in RETURN0, PINA ; Read from the memory bus
sbi PORTD, 6 ; set -IORD
rjmp wr_end
;Write
wr_jmp:
sbi PORTD, 6 ; set PD6 = -IORD
cbi PORTD, 7 ; clear PD7 = -IOWR
ldi TEMP1, $FF
out DDRA, TEMP1 ; PORTA output
out PORTA, ARG2 ; Write to the memory bus
nop ; tsu(IOWR)
sbi PORTD, 7 ; set -IOWR
wr_end: sbi PORTC, 5 ; set -CS0
pop ARG0
pop TEMP2
pop TEMP1
pop TEMP0
ret
; Read CF status register for busy
chbsy: push ARG1
push ARG0
push TEMP0
bsy_lp: ldi ARG1, $00 ; Read operation
ldi ARG0, $1C ; 000-111-00: status reg.
rcall memrw
mov TEMP0, RETURN0 ; Save original status
andi RETURN0, $F7 ; ignore DRQ bit
cpi RETURN0, $51
brne no_err
; Ready but, Error bit is set
ldi ARG1, $00 ; Read operation
ldi ARG0, $04 ; 000-001-00: error reg.
rcall memrw
com RETURN0
;out PORTB, RETURN0 ; Display error
rjmp bsy_lp
no_err: mov RETURN0, TEMP0
andi RETURN0, $F7
cpi RETURN0, $50 ; if still busy, just loop back
brne bsy_lp
pop TEMP0
pop ARG0
pop ARG1
ret
; Function print_hex: Gets ARG1 as 8-bit input and outputs hex value
; If ARG2(0) is cleared, function does not send characters to LCD
print_hex:
push TEMP0
push ARG0
;higher nibble
mov TEMP0, ARG1
swap TEMP0
andi TEMP0, $0F
cpi TEMP0, $0A
brsh let1
ldi ARG0, $30
add ARG0, TEMP0
rcall putchar
sbrc ARG2, 0
rcall lcd_data_write
rjmp nib1
let1: ldi ARG0, $37
add ARG0, TEMP0
rcall putchar
sbrc ARG2, 0
rcall lcd_data_write
;lower nibble
nib1: mov TEMP0, ARG1
andi TEMP0, $0F ; Lower nibble
cpi TEMP0, $0A
brsh let2 ; Branch if same or higher than "A"
ldi ARG0, $30
add ARG0, TEMP0
rcall putchar
sbrc ARG2, 0
rcall lcd_data_write
rjmp nib2
let2: ldi ARG0, $37
add ARG0, TEMP0
rcall putchar
sbrc ARG2, 0
rcall lcd_data_write
nib2:
pop ARG0
pop TEMP0
ret
; Function LF_CR
; Take the cursor to new line in terminal
LF_CR: push ARG0
ldi ARG0, $0A ; Line Feed
rcall putchar
ldi ARG0, $0D ; Carriage Return
rcall putchar
pop ARG0
ret
;Function putchar
;Transmit to UART, waits for empty buffer
putchar:
sbis UCSRA, UDRE ; loop until USR:UDRE is 1
rjmp putchar
out UDR, ARG0 ; write ARG0 to transmitter buffer
ret
;Function putchar2
;Transmit to UART, both true value and MSB inverted waits for empty buffer
putchar2:
push TEMP0
putc1: sbis UCSRA, UDRE ; loop until USR:UDRE is 1
rjmp putc1
out UDR, ARG0 ; write ARG0 to transmitter buffer
putc2: sbis UCSRA, UDRE
rjmp putc2
ldi TEMP0, $80
eor TEMP0, ARG0
out UDR, TEMP0
pop TEMP0
ret
;Function getchar
;Waits to receive data from UART
getchar:
sbis UCSRA, RXC ; loop until USR:RXC is 1
rjmp getchar
in RETURN0, UDR ; return receive data in RETURN0
ret
;I2C master transmitter Function
; Writes ARG2 into the MP3 decoder register ARG1
; Refer to Mega163 manual
I2C_write_byte:
push TEMP0
push ARG0
; Initialization for writing
ldi TEMP0, $04 ; 00000100
out TWCR, TEMP0
nop
nop
; Initialize TWBR
ldi TEMP0, $C0 ; 30
out TWBR, TEMP0
nop
nop
; Send Start Condition
ldi TEMP0, (1<<TWSTA) | (1<<TWEN)
retry_w:out TWCR, TEMP0
nop
nop
wait1: in TEMP0, TWCR
nop
sbrs TEMP0, TWINT
rjmp wait1
; Check Status
in TEMP0, TWSR
cpi TEMP0, $08 ; START
brne WR_ERR
; Select decoder chip for Write
ldi TEMP0, 0x86
out TWDR, TEMP0
ldi TEMP0, (1<<TWINT) | (1<<TWEN)
out TWCR, TEMP0
nop
nop
wait2: in TEMP0, TWCR
nop
sbrs TEMP0, TWINT
rjmp wait2
;Check Status
in TEMP0, TWSR
cpi TEMP0, $18 ; MT_SLA_ACK
brne WR_ERR
; Write the address
out TWDR, ARG1
ldi TEMP0, (1<<TWINT) | (1<<TWEN)
out TWCR, TEMP0
nop
nop
wait3: in TEMP0, TWCR
nop
sbrs TEMP0, TWINT
rjmp wait3
;Check Status
in TEMP0, TWSR
cpi TEMP0, $28 ; MT_DATA_ACK
brne WR_ERR
; Write the data
out TWDR, ARG2
ldi TEMP0, (1<<TWINT) | (1<<TWEN)
out TWCR, TEMP0
nop
nop
wait4: in TEMP0, TWCR
nop
sbrs TEMP0, TWINT
rjmp wait4
;Check Status
in TEMP0, TWSR
cpi TEMP0, $28 ; MT_DATA_ACK
brne WR_ERR
; Send Stop Condition
ldi TEMP0, (1<<TWINT) | (1<<TWSTO) | (1<<TWEN)
out TWCR, TEMP0
nop
nop
rjmp skip_w
; Send Status to UART
WR_ERR:
ldi ARG0, $57 ; "W"
rcall putchar
;ldi TEMP0, (1<<TWSTA) | (1<<TWEN) | (1<<TWINT)
;rjmp retry_w
wr_pause2:
call getchar
cpi RETURN0, $43 ; "C"
brne wr_pause2
mov TEMP0, ARG1
com TEMP0
;out PORTB, TEMP0
;Wait for "C"
wr_pause1:
rcall getchar
cpi RETURN0, $43 ; "C"
brne wr_pause1
; Deactivate interface before ending
skip_w: clr TEMP0
out TWCR, TEMP0
nop
nop
pop ARG0
pop TEMP0
ret
;I2C master receiver Function
; Reads the address in MP3 decoder chip.
; First writes the address to be read (without writing a data)
; Then reads one byte
; ARG1: read address
; RETURN0: return data
; Refer to Mega163 manual
I2C_read_byte:
push TEMP0
push ARG0
; WRITING the address to be read
; Initialization for writing
ldi TEMP0, $04 ; 00000100
out TWCR, TEMP0
nop
nop
; Initialize TWBR
ldi TEMP0, $C0 ; 30
out TWBR, TEMP0
; Send Start Condition
ldi TEMP0, (1<<TWSTA) | (1<<TWEN)
out TWCR, TEMP0
nop
nop
wait1r: in TEMP0, TWCR
nop
sbrs TEMP0, TWINT
rjmp wait1r
ldi ARG0, $31 ; "1"
rcall putchar
; Check Status
in TEMP0, TWSR
cpi TEMP0, $08 ; START
brne RD_ERR
; Select decoder chip for Write
ldi TEMP0, 0x86
out TWDR, TEMP0
ldi TEMP0, (1<<TWINT) | (1<<TWEN)
out TWCR, TEMP0
nop
nop
wait2r: in TEMP0, TWCR
nop
sbrs TEMP0, TWINT
rjmp wait2r
ldi ARG0, $32 ; "2"
rcall putchar
;Check Status
in TEMP0, TWSR
cpi TEMP0, $18 ; MT_SLA_ACK
brne RD_ERR
; Write the address
out TWDR, ARG1
ldi TEMP0, (1<<TWINT) | (1<<TWEN)
out TWCR, TEMP0
nop
nop
wait3r: in TEMP0, TWCR
nop
sbrs TEMP0, TWINT
rjmp wait3r
ldi ARG0, $33 ; "3"
rcall putchar
;Check Status
in TEMP0, TWSR
;out PORTB, TEMP0
cpi TEMP0, $28 ; MT_DATA_ACK
brne RD_ERR
; Send Stop Condition
;ldi TEMP0, (1<<TWINT) | (1<<TWSTO) | (1<<TWEN)
;out TWCR, TEMP0
; Branch out of reach
rjmp skip_next
RD_ERR: rjmp RD_ERR2
skip_next:
; READING
; Send a REPEATED Start Condition
;ldi TEMP0, (1<<TWINT) | (1<<TWSTO) | (1<<TWSTA) | (1<<TWEN)
;out TWCR, TEMP0
ldi TEMP0, (1<<TWINT) | (1<<TWSTO) | (1<<TWEN)
out TWCR, TEMP0
;sbi TWCR, TWSTA
ori TEMP0, $20 ; TWSTA: 00100000
out TWCR, TEMP0
nop
nop
wait5: in TEMP0, TWCR
nop
sbrs TEMP0, TWINT
rjmp wait5
ldi ARG0, $34 ; "4"
rcall putchar
; Check Status
in TEMP0, TWSR
cpi TEMP0, $08 ; START
brne RD_ERR
; Select decoder chip for Read
ldi TEMP0, 0x87
out TWDR, TEMP0
ldi TEMP0, (1<<TWINT) | (1<<TWEN)
out TWCR, TEMP0
nop
nop
wait6: in TEMP0, TWCR
nop
sbrs TEMP0, TWINT
rjmp wait6
ldi ARG0, $35 ; "5"
rcall putchar
; Check Status
in TEMP0, TWSR
cpi TEMP0, $40 ; MR_SLA_ACK
brne RD_ERR
; Read one byte (TWEA not set to send NACK after reading which stops reading)
ldi TEMP0, (1<<TWINT) | (1<<TWEN)
out TWCR, TEMP0
nop
nop
wait9: in TEMP0, TWCR
nop
sbrs TEMP0, TWINT
rjmp wait9
ldi ARG0, $36 ; "6"
rcall putchar
; Check Status
in TEMP0, TWSR
cpi TEMP0, $58 ; MR_DATA_NACK
brne RD_ERR
; Get the return value
in RETURN0, TWDR
; Send Stop Condition
ldi TEMP0, (1<<TWINT) | (1<<TWSTO) | (1<<TWEN)
out TWCR, TEMP0
nop
nop
rjmp skip_r
; Send Status to UART
RD_ERR2:
ldi ARG0, $52 ; "R"
rcall putchar
com ARG0
;out PORTB, ARG0
;Wait for "C"
rd_pause1:
rcall getchar
cpi RETURN0, $43 ; "C"
brne rd_pause1
com TEMP0
;out PORTB, TEMP0
;Wait for "C"
rd_pause2:
rcall getchar
cpi RETURN0, $43 ; "C"
brne rd_pause2
; Disable interface before exit
skip_r: clr TEMP0
out TWCR, TEMP0
nop
pop ARG0
pop TEMP0
ret
; Function start_spi_tx
; Y register has the current reading position
; Takes the byte from SRAM(Y) and transmits over SPI
; This function starts a burst of transfers until DATA_REQ = 0
start_spi_tx:
push TEMP0
; Load from current position
ld TEMP0, Y
; Transmit over SPI
out SPDR, TEMP0
adiw YL, 1 ; increment to next location
sbrc YH, 2 ; Wrap around at 1024 (YH=$04)
; doesn't reset YH if YH=010 or 011
ldi YH, $02
pop TEMP0
ret
; Function read_tag
;
read_tag:
push SECTOR
push CYL_LO
push CYL_HI
push HEAD
push CLUS_LO
push CLUS_HI
push TEMP0
push TEMP1
push TEMP2
clr TEMP1
; State=0 no letter found
; State=1 "T" has been found
; State=2 "TA" has been found
; State=3 "TAG" has been found
rcall lcd_clear
; Set SECTOR, CYL_LO etc. to next track's beginning
inc TRACK
rcall compute_addr
; Note: compute_addr goes to the location one cluster before next song
; So, no need to decrement
; Decrement sector by 1 to read the last 4 sectors of current TRACK
;ldi TEMP0, 4
;tag_dec_sec:
; rcall dec_sector
; dec TEMP0
; cpi TEMP0, 0
; brne tag_dec_sec
clr ZH
clr ZL
tag_loop:
;Set Cylinder Low Reg.
ldi ARG1, $FF ; Write operation
ldi ARG0, $10 ; 000-100-00: cyl low reg.
mov ARG2, CYL_LO ; cyl_lo = CYL_LO
rcall memrw
;Set Cylinder Hi Reg.
ldi ARG1, $FF ; Write operation
ldi ARG0, $14 ; 000-101-00: cyl hi reg.
mov ARG2, CYL_HI ; cyl_hi = 00
rcall memrw
;Set Head No.
ldi ARG1, $FF ; Write operation
ldi ARG0, $18 ; 000-110-00: select card/head reg.
mov ARG2, HEAD ; 1010-0000: head no = 0
rcall memrw
;Set Sector Number Register
ldi ARG1, $FF ; Write operation
ldi ARG0, $0C ; 000-011-00: sec num reg.
mov ARG2, SECTOR ; sec_num = SECTOR
rcall memrw
;Set 01H in sector count register (read one sector)
ldi ARG1, $FF ; Write operation
ldi ARG0, $08 ; 000-010-00: sec cnt.
ldi ARG2, $01 ; 1 sector
rcall memrw
; Set 20H in Command register
ldi ARG1, $FF ; Write operation
ldi ARG0, $1C ; 000-111-00: A2-A0 = 111 command reg.
ldi ARG2, $20 ; Read Sector(s)
rcall memrw
rcall chbsy ; Check if busy
rjmp tag_rd_lp ; Relative branch was out of reach
tag_loop1:
rjmp tag_loop
tag_rd_lp:
rcall fastread
;mov ARG0, RETURN0
;rcall putchar
; State=0
cpi TEMP1, 0
brne state_1
cpi RETURN0, $54 ; "T"
brne tag_skip
ldi TEMP1, 1 ; Found first letter
mov TEMP2, ZL ; Record the starting offset
rjmp tag_skip
; State=1
state_1:
cpi TEMP1, 1
brne state_2
cpi RETURN0, $41 ; "A"
brne state_1T
ldi TEMP1, 2 ; Found second letter
rjmp tag_skip
state_1T:
cpi RETURN0, $54 ; "T"
brne state_1_other
ldi TEMP1, 1 ; Found first letter
mov TEMP2, ZL ; Record the starting offset
rjmp tag_skip
state_1_other:
ldi TEMP1, 0 ; Not "A" or "T"
rjmp tag_skip
; State=2
state_2:
cpi TEMP1, 2
brne state_3
cpi RETURN0, $47 ; "G"
brne state_2T
ldi TEMP1, 3 ; Found third letter
rjmp tag_skip
state_2T:
cpi RETURN0, $54 ; "T"
brne state_2_other
ldi TEMP1, 1 ; Found first letter
mov TEMP2, ZL ; Record the starting offset
rjmp tag_skip
state_2_other:
ldi TEMP1, 0 ; Not "G" or "T"
rjmp tag_skip
; State=3
state_3:
sub ZL, TEMP2
cpi ZL, 33 ; (3-32): Title
brsh tag_artist
cpi ZL, 3
brne tag_title_skip
ldi ARG0, $80
rcall lcd_command_write ; Go to first line of LCD
tag_title_skip:
mov ARG0, RETURN0
cpi ARG0, $00 ; If char = $00 make it $20
brne space_skip1
ldi ARG0, $20
rjmp space_skip1 ; Relative branch was out of reach
tag_loop2:
rjmp tag_loop1
tag_rd_lp1:
rjmp tag_rd_lp
space_skip1:
rcall lcd_data_write
rcall putchar
rjmp state_3_end
tag_artist:
cpi ZL, 63 ; (33-62): Artist
brsh tag_escape ; SHOULD I READ UNTIL THE END OF SECTOR?
cpi ZL, 33
brne tag_artist_skip
ldi ARG0, $C0
rcall lcd_command_write ; Go to second line of LCD
tag_artist_skip:
mov ARG0, RETURN0
cpi ARG0, $00
brne space_skip2
ldi ARG0, $20
space_skip2:
rcall lcd_data_write
rcall putchar
state_3_end:
add ZL, TEMP2 ; restore ZL
tag_skip:
adiw ZL, 1 ; increment
mov TEMP0, ZH
andi TEMP0, $01
cpi TEMP0, $00
brne tag_rd_lp1
cpi ZL, $00
brne tag_rd_lp1
rcall chbsy
ldi ARG0, $01
rcall inc_sector
cpi ZH, $08
brne tag_loop2
tag_escape:
dec TRACK
ldi ARG0, $E0
rcall lcd_command_write ; Go to address $60
ldi ARG0, $54 ; "T"
rcall lcd_data_write
ldi ARG0, $52 ; "R"
rcall lcd_data_write
ldi ARG0, $41 ; "A"
rcall lcd_data_write
ldi ARG0, $43 ; "C"
rcall lcd_data_write
ldi ARG0, $4B ; "K"
rcall lcd_data_write
ldi ARG0, $20 ; Space
rcall lcd_data_write
ldi ARG2, $FF ; Send to display
mov ARG1, TRACK
rcall print_hex ; Display track number
pop TEMP2
pop TEMP1
pop TEMP0
pop CLUS_HI
pop CLUS_LO
pop HEAD
pop CYL_HI
pop CYL_LO
pop SECTOR
ret
; Function read_root_dir
; Reads the Root Directory in FAT(0, 00, 01, 1C). Stores the first cluster
; address of each file in SRAM starting at SRAM[0x62]. Each entry is 2 bytes.
; Maximum entries is around 200 NUM_TRACKS is the number of files. Also reads
; the file size and stores it in TEMP:0-1-2. This is used to find the last
; cluster number for the last file.
read_root_dir:
push ARG0
push ARG1
push ARG2
push TEMP0
push TEMP1
push TEMP2
push TEMP3 ; used as temporary
ldi TEMP3, $1C
mov SECTOR, TEMP3 ; Sector = $1C
ldi TEMP3, $01
mov CYL_LO, TEMP3 ; cyl_lo = $01
clr CYL_HI ; cyl_hi = $00
ldi TEMP3, $A0 ; Head = 0
mov HEAD, TEMP3
ldi YH, $00
ldi YL, $62
clr FLAGREG
root_dir_loop:
;Set Cylinder Low Reg.
ldi ARG1, $FF ; Write operation
ldi ARG0, $10 ; 000-100-00: cyl low reg.
mov ARG2, CYL_LO ; cyl_lo = CYL_LO
rcall memrw
;Set Cylinder Hi Reg.
ldi ARG1, $FF ; Write operation
ldi ARG0, $14 ; 000-101-00: cyl hi reg.
mov ARG2, CYL_HI ; cyl_hi = 00
rcall memrw
;Set Head No.
ldi ARG1, $FF ; Write operation
ldi ARG0, $18 ; 000-110-00: select card/head reg.
mov ARG2, HEAD ; 1010-0000: head no = 0
rcall memrw
;Set Sector Number Register
ldi ARG1, $FF ; Write operation
ldi ARG0, $0C ; 000-011-00: sec num reg.
mov ARG2, SECTOR ; sec_num = SECTOR
rcall memrw
;Set 01H in sector count register (read one sector)
ldi ARG1, $FF ; Write operation
ldi ARG0, $08 ; 000-010-00: sec cnt.
ldi ARG2, $01 ; 1 sector
rcall memrw
; Set 20H in Command register
ldi ARG1, $FF ; Write operation
ldi ARG0, $1C ; 000-111-00: A2-A0 = 111 command reg.
ldi ARG2, $20 ; Read Sector(s)
rcall memrw
rcall chbsy ; Check if busy
;Read 512 times the data register. Write to SRAM
ldi ZH, $02 ; 512
clr ZL
root_rd_lp:
rcall fastread
st Z, RETURN0 ; Store data
adiw ZL, 1 ; increment
cpi ZH, $04
brne root_rd_lp
rcall chbsy
; Parse the stored Sector to find first clusters
ldi ZH, $02
ldi ZL, $00
mov TEMP3, FLAGREG
clr FLAGREG
cpi TEMP3, 0
brne root_skip2 ; If 1 was at last 32 bytes of previous sector
; Just start reading the first cluster and file size
root_parse:
ld TEMP3, Z
cpi ZH, $04
breq root_skip
adiw ZL, 32
cpi TEMP3, 1 ; 44-3-2-1 if one Z
brne root_parse
cpi ZH, $04
brne root_skip2
inc FLAGREG ; If 1 is at last 32 bytes of sector set this flag
rjmp root_skip
root_skip2:
; Store first cluster address
adiw ZL, 26
ld TEMP3, Z+ ; FirstCluster lower byte (location 26)
st Y+, TEMP3
ld TEMP3, Z+ ; FirstCluster higher byte (location 27)
st Y+, TEMP3
; Store file size into temporary registers
; $0061-$0064: Lower to Higher
adiw ZL, 1 ; Skip FileSize byte[0]
ld TEMP0, Z+ ; FileSize byte[1] (lowest byte - loc. 29)
ld TEMP1, Z+ ; FileSize byte[2] (lowest byte - loc. 30)
ld TEMP2, Z+ ; FileSize byte[3] (lowest byte - loc. 31)
cpi ZH, $04
breq root_skip
; Z=0 (mod 32) here
; LOCK TEMP0, TEMP1, TEMP2 here
ld TEMP3, Z
cpi TEMP3, $E5 ; End of entries
brne root_parse
; Divide file size by 2048 (shift by 11). Done by shifting right by three
; TEMP0 and TEMP1 holds the file size in clusters
ldi TEMP3, 3
div_8: lsr TEMP0
lsr TEMP1
brcc div_skip1 ; Skip next instruction if C bit is zero
ori TEMP0, $80 ; Shift TEMP1[0] into TEMP0[7]
div_skip1:
lsr TEMP2
brcc div_skip_2 ; Skip next instruction if C bit is zero
ori TEMP1, $80 ; Shift TEMP2[0] into TEMP1[7]
div_skip_2:
dec TEMP3
cpi TEMP3, 0
brne div_8
;NUM_TRACKS = Y/2 - $31 (Y<256 so YH = 0)
mov TEMP3, YL
lsr TEMP3
subi TEMP3, $31
sts NUM_TRACKS, TEMP3
mov ZL, YL
mov ZH, YL
sbiw ZL, 2 ; Z = address of last entry in SRAM
ld TEMP2, Z+ ; Last cluster entry lower byte
ld TEMP3, Z+ ; Last cluster entry higher byte
add TEMP0, TEMP2
adc TEMP1, TEMP3
;TEMP0 and TEMP1 holds last files last cluster
mov ZL, TEMP0
mov ZH, TEMP1
adiw ZL, 1
;Store lastfile_lastcluster+1 into the next empty location in SRAM
st Y+, ZL
st Y+, ZH
rjmp root_exit
root_skip:
; Increment sector. If sector = 20h increment HEAD
mov TEMP3, SECTOR
cpi TEMP3, $20
brne root_inc_sc
clr SECTOR
inc HEAD
mov TEMP3, HEAD
cpi TEMP3, $A4
brne root_inc_sc
ldi TEMP3, $A0
mov HEAD, TEMP3
inc CYL_LO
; CYL_HI = 0 $00<=CYL_LO<=$FF
mov TEMP3, CYL_HI
cpi TEMP3, $00
brne root_hi_1
mov TEMP3, CYL_LO
cpi TEMP3, $00 ; Overflow
brne root_inc_sc
inc CYL_HI
rjmp root_inc_sc
; CYL_HI = 1 $00<=CYL_LO<=$E9
root_hi_1:
mov TEMP3, CYL_LO
cpi TEMP3, $EA
brne root_inc_sc
clr CYL_HI
clr CYL_LO
root_inc_sc:
inc SECTOR
rjmp root_dir_loop
root_exit:
pop TEMP3
pop TEMP2
pop TEMP1
pop TEMP0
pop ARG2
pop ARG1
pop ARG0
ret
; Function inc_sector
; Increments SECTOR by the number specified with ARG0
inc_sector:
push TEMP2
; Increment sector. If sector = 20h increment HEAD
mov TEMP2, SECTOR
cpi TEMP2, $20
brne inc_sc
clr SECTOR
inc HEAD
mov TEMP2, HEAD
cpi TEMP2, $A4
brne inc_sc
ldi TEMP2, $A0
mov HEAD, TEMP2
inc CYL_LO
; CYL_HI = 0 $00<=CYL_LO<=$FF
mov TEMP2, CYL_HI
cpi TEMP2, $00
brne hi_1
mov TEMP2, CYL_LO
cpi TEMP2, $00 ; Overflow
brne inc_sc
inc CYL_HI
rjmp inc_sc
; CYL_HI = 1 $00<=CYL_LO<=$E9
hi_1: mov TEMP2, CYL_LO
cpi TEMP2, $EA
brne inc_sc
clr CYL_HI
clr CYL_LO
inc_sc: add SECTOR, ARG0 ; Increment by AGR0
mov TEMP2, SECTOR
cpi TEMP2, $20 ; If SECTOR>=$20 SECTOR=$20
brlo sc_skip ; This gives a maximum increment amount
ldi TEMP2, $20 ; of 16. e.g.: (20+12)/2 = 16
mov SECTOR, TEMP2
sc_skip:
pop TEMP2
ret
; Function dec_sector
; Decrements SECTOR by 1
dec_sector:
push TEMP2
; Decrement sector. If sector = 01h decrement HEAD
mov TEMP2, SECTOR
cpi TEMP2, $01
brne dec_sc
ldi TEMP2, $21
mov SECTOR, TEMP2
dec HEAD
mov TEMP2, HEAD
cpi TEMP2, $9F
brne dec_sc
ldi TEMP2, $A3
mov HEAD, TEMP2
dec CYL_LO
; CYL_HI = 0 $00<=CYL_LO<=$FF
mov TEMP2, CYL_HI
cpi TEMP2, $00
brne dec_hi_1
mov TEMP2, CYL_LO
cpi TEMP2, $FF ; Overflow
brne dec_sc
inc CYL_HI
ldi TEMP2, $E9
mov CYL_LO, TEMP2
rjmp dec_sc
; CYL_HI = 1 $00<=CYL_LO<=$E9
dec_hi_1:
mov TEMP2, CYL_LO
cpi TEMP2, $FF
brne dec_sc
clr CYL_HI
dec_sc: dec SECTOR
pop TEMP2
ret
; Function check_overflow
; Checks whether the current cluster no exceeds the next track's cluster no
; If the cluster no overflows there are two cases:
; -this was the last track: resets TRACK# to 1 and waits for PLAY button
; -else: Increment TRACK, Read and Display Tag information of next track
check_overflow:
push TEMP0
push TEMP1
push YH
push YL
;Read next TRACK's first cluster no
;SRAM offset (r1:r0) = 2*(TRACK+1) + $60
ldi TEMP0, 2
ldi TEMP1, 1
add TEMP1, TRACK ; TEMP1 = TRACK+1
mul TEMP1, TEMP0 ; 2*(TRACK+1) -> r1:r0
mov YH, r1
mov YL, r0
adiw YL, $30
adiw YL, $30 ; Y = Y + $60
; Y has the next TRACK's first cluster no
; TEMP0 = Cluster lower byte, TEMP1 = Cluster higher byte
ld TEMP0, Y+
ld TEMP1, Y+
cp CLUS_HI, TEMP1
brlo ovfl_skip1 ; skip if CLUS_HI < TEMP1
cp TEMP1, CLUS_HI
brlo ovfl_skip2 ; skip if CLUS_HI > TEMP1
cp CLUS_LO, TEMP0 ; CLUS_HI = TEMP1 here
brlo ovfl_skip1 ; skip if CLUS_LO < TEMP0
; Overflow detected
ovfl_skip2:
lds TEMP0, NUM_TRACKS
cp TRACK, TEMP0
brne ovfl_skip3
; This is the last Track
clr TRACK
inc TRACK ; Reset to Track #1
rcall compute_addr
rcall read_tag
; Wait here for PLAY input
play_loop:
sbic PINB, 3 ; Skip rjmp if button is pressed
rjmp play_loop
rjmp ovfl_skip1
ovfl_skip3:
; Not Last Track: Skip to next track
inc TRACK
ldi ARG0, $54 ; T
rcall putchar ; Debug
ldi ARG0, $52 ; R
rcall putchar ; Debug
ldi ARG0, $30
add ARG0, TRACK
rcall putchar
rcall compute_addr
rcall read_tag
ovfl_skip1:
pop YL
pop YH
pop TEMP1
pop TEMP0
ret
; Function compute_addr
; Input is the TRACK register
; Grabs the FirstCluster no from SRAM and computes HEAD, CYL_HI,
; CYL_LO and SECTOR values
; Also sets CLUS_LO and CLUS_HI registers to FisrtCluster no
; SRAM offset = $60 + 2*TRACK (2 bytes)
; Sector = $AF + 4*FirstCluster (2 bytes)
; 15 | 14 13 12 11 10 9 8 7 | 6 5 | 4 3 2 1 0
; CYL_HI | CYL_LO | HEAD | SECTOR
; Add 1 to SECTOR as it starts from 1
compute_addr:
push TEMP0
push TEMP1
push YH
push YL
;SRAM offset (r1:r0) = 2*TRACK + $60
ldi TEMP0, 2
mul TRACK, TEMP0 ; 2*TRACK -> r1:r0
mov YH, r1
mov YL, r0
adiw YL, $30
adiw YL, $30 ; Y = Y + $60
; TEMP0 = Cluster lower byte, TEMP1 = Cluster higher byte
ld TEMP0, Y+
ld TEMP1, Y+
mov CLUS_LO, TEMP0
mov CLUS_HI, TEMP1
;Sector(r1:r0) = $AF + 4*FirstCluster
;MaxCluster= $3CF1 $3C*4 = $00F0 -----> stored in YH
; $FF*4 = $ 03FC
; $ B0
; +________
ldi YL, 4
mul TEMP1, YL ; 4*Cluster_hi -> r1:r0
mov YH, r0 ; r1 is always 0
mul TEMP0, YL ; 4*Cluster_lo -> r1:r0
ldi YL, $AF
add r0, YL
adc r1, YH ; r1:r0 = sector(15:0)
clr CYL_HI
bst r1, 7 ; Store bit 15 to T
bld CYL_HI, 0 ; Load T into CYL_HI(0)
mov CYL_LO, r1 ; CYL_LO = bits 15:8
lsl CYL_LO ; CYL_LO(7:1) = bits 14:8
bst r0, 7 ; Store bit 7 to T
bld CYL_LO, 0 ; CYL_LO(0) = bit 7
ldi TEMP0, $A0
mov HEAD, TEMP0
bst r0, 6 ; Store bit 6 to T
bld HEAD, 1 ; HEAD(1) = bit 6
bst r0, 5 ; Store bit 5 to T
bld HEAD, 0 ; HEAD(0) = bit 5
mov TEMP0, r0
andi TEMP0, $1F ; and with 0001-1111
mov SECTOR, TEMP0 ; SECTOR(4:0) = bits 4:0
inc SECTOR ; SECTOR starts at 1 and ends at 32
pop YL
pop YH
pop TEMP1
pop TEMP0
ret
; Function is_lcd_busy
is_lcd_busy:
push TEMP0
push TEMP1
rcall five_nops ; Enable cycle delay
rcall five_nops
cbi PORTC, 2 ; Control word
sbi PORTD, 7 ; read
clr TEMP1
out PORTA, TEMP1
out DDRA, TEMP1 ; Port A input
sbi PORTC, 7 ; Enable
rcall five_nops ; Min 160ns delay
in TEMP0, PINA ; Read LCD data
nop
nop
cbi PORTC, 7
;Wait here if LCD is busy
lcd_busy_loop:
sbrs TEMP0, 7 ; skip rjmp if set (busy)
rjmp lcd_busy_end
rcall five_nops ; Enable cycle delay
rcall five_nops
sbi PORTC, 7 ; Enable
rcall five_nops ; Min 160ns delay
in TEMP0, PINA ; Read LCD data
nop
nop
cbi PORTC, 7
rjmp lcd_busy_loop
lcd_busy_end:
pop TEMP1
pop TEMP0
ret
; Function five_nops
five_nops:
nop
nop
nop
nop
nop
ret
; Function lcd_data_write
; ARG0 is the data to write
lcd_data_write:
push TEMP0
sbi PORTC, 2 ; Data word
cbi PORTD, 7 ; Write
out PORTA, ARG0
ldi TEMP0, $FF
out DDRA, TEMP0 ; Port A output
sbi PORTC, 7 ; Enable
rcall five_nops
rcall five_nops
cbi PORTC, 7
rcall is_lcd_busy
pop TEMP0
ret
; Function lcd_command_write
; ARG0 is the command to write
lcd_command_write:
push TEMP0
cbi PORTC, 2 ; Control word
cbi PORTD, 7 ; Write
out PORTA, ARG0
ldi TEMP0, $FF
out DDRA, TEMP0 ; Port A output
sbi PORTC, 7 ; Enable
rcall five_nops
rcall five_nops
cbi PORTC, 7
rcall is_lcd_busy
pop TEMP0
ret
; Function lcd_clear
lcd_clear:
push ARG0
ldi ARG0, $01
rcall lcd_command_write
pop ARG0
ret
; SPI transfer Complete Interrupt Handler
; Transmits next byte and increments Y register
; Continously transfer until DATA_REQ=0
; For a rate of F/32 there are 256 clocks of execution
; until the next interrupt.
SPICompIsr:
push TEMP0
push TEMP1 ; Stores only SREG in here
in TEMP1, SREG ; Save Status register
;cbi PORTB, 1 ; LED1 on
nop ; Instead of instr. above
sbis PIND, 2 ; skip rjmp if DATA_REQ is high
rjmp spi1
; Load from current position
ld TEMP0, Y
; Transmit over SPI
out SPDR, TEMP0
adiw YL, 1 ; increment to next location
sbrc YH, 2 ; Wrap around at 1024 (YH=$04)
; doesn't reset YH if YH=010 or 011
ldi YH, $02
;sbi PORTB, 1 ; LED1 off
nop ; Instead of instr. above
spi1: out SREG, TEMP1 ; Save Status register
pop TEMP1
pop TEMP0
reti
; INT0 interrupt handler.
; Triggered by a rising edge on DATA_REQ input from decoder
Int0Isr:
push TEMP0 ; save registers
in TEMP0, SREG ; save status register
rcall start_spi_tx ; Start a burst of transfer
out SREG, TEMP0 ; restore status register
pop TEMP0 ; restore registers
reti ; return from the interrupt
; Timer2 interrupt handler
; This routine will be automatically called whenever timer 2 overflows
; Overflows after 33ms
Timer2Isr:
push TEMP0 ; save registers
in TEMP0, SREG ; save status register
inc TEMP3
out SREG, TEMP0 ; restore status register
pop TEMP0 ; restore registers
reti ; return from the interrupt