
;; SPI SD/DAC Sound Player - Copyright 2009 by Michael Kohn
;; Email: mike@mikekohn.net
;;   Web: http://www.mikekohn.net/
;;
;; Read from an SD/MMC card with SPI and write out to an SPI DAC to
;; play sound samples.

.include "m168def.inc"

; Help pages:
; http://www.mcu.fluxfocus.com/documents/SDSPI/sdspi.htm
; http://www.maxim-ic.com/appnotes.cfm/an_pk/3969
; http://www.avrfreaks.net/index.php?name=PNphpBB2&file=printview&t=83412&start=0
; http://elm-chan.org/docs/mmc/mmc_e.html
; http://www.dharmanitech.com/2009/01/sd-card-interfacing-with-atmega8-fat32.html

.equ SPI_SS = PB2
.equ SPI_MOSI = PB3
.equ SPI_MISO = PB4
.equ SPI_SCK = PB5

; r0  = 0
; r1  = 1
; r7  = saved status register
; r10 = LSB block num
; r11 = MSB block num  [ currblock = 0x00, r11, r10, 0x00 ]
; r14 = temp in function
; r15 = 255
; r16 = function return value
; r17 = temp
; r18 = temp in function
; r22 = state [ 0 = poll input, 1 = play sound ]
; r24 = function parameter
; r26 = LSB pointer to SRAM
; r27 = MSB pointer to SRAM
;

; note: CLKSEL 0110
;       8MHz

.cseg

.org 0x000
  rjmp start
.org 0x016
  rjmp the_great_interrupt
.org 0x01a
  rjmp the_great_interrupt

;; FIXME - erase this padding.. it's dumb
.org 0x020

start:
  ;; I'm busy.  Don't interrupt me!
  cli

  ;; Set up constants registers
  eor r0, r0   ; r0 = constant 0
  eor r1, r1   ; r1 = constant 1
  inc r1
  eor r15, r15 ; r15 = constant 255
  dec r15

  ;; Pointer to block to read [ 0x00, r11, r10, 0x00 ]
  clr r11
  clr r10

  ;; Set up stack ptr
  ldi r17, RAMEND>>8
  out SPH, r17
  ldi r17, RAMEND&255
  out SPL, r17

  ;; Set up rs232 baud rate
  ldi r17, ((20000000/(16*9600))-1)>>8
  sts UBRR0H, r17
  ldi r17, ((20000000/(16*9600))-1)&0xff
  sts UBRR0L, r17

  ;; Set up rs232 options
  ldi r17, (1<<UCSZ00)|(1<<UCSZ01)    ; sets up data as 8N1
  sts UCSR0C, r17
  ldi r17, (1<<TXEN0)|(1<<RXEN0)      ; enables send/receive
  sts UCSR0B, r17
  eor r17, r17
  sts UCSR0A, r17

  ;; Set up SPI
  out PORTB, r1
  ldi r17, (1<<SPI_MOSI)|(1<<SPI_SCK)|(1<<SPI_SS)|(1<<PB1)|(1<<PB0)
  out DDRB, r17
  ldi r17, (1<<SPE)|(1<<MSTR)|(1<<SPR1)
  out SPCR, r17
  sbi PORTB, SPI_SS

  ;; Set up TIMER1
  ;lds r17, PRR
  ;andi r17, 255 ^ (1<<PRTIM1)
  ;sts PRR, r17                   ; turn of power management bit on TIM1

  ldi r17, (909>>8)
  sts OCR1AH, r17
  ldi r17, (909&0xff)            ; compare to 22000 interrupts a second
  sts OCR1AL, r17

  ldi r17, (1<<OCIE1A)
  sts TIMSK1, r17                ; enable interrupt comare A 
  sts TCCR1C, r0
  sts TCCR1A, r0                 ; normal counting (0xffff is top, count up)
  ldi r17, (1<<CS10)|(1<<WGM12)  ; CTC OCR1A
  sts TCCR1B, r17                ; prescale = 1 from clock source

  ;; Set up ADC
  out PORTC, r0
  out DDRC, r0
  ;ldi r17, (1<<MUX0)
  sts ADMUX, r0  ; use ADC0 and Aref
  ldi r17, (1<<ADEN)|(1<<ADATE)|(1<<ADSC)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)
  sts ADCSRA, r17
  ldi r17, (1<<ADC5D)|(1<<ADC4D)|(1<<ADC3D)|(1<<ADC2D)|(1<<ADC1D)|(1<<ADC0D)
  sts DIDR0, r17

  ;; Send something to the DAC (cuts out some noise :( )
  cbi PORTB, PB0
  ldi r24, 0x3f
  rcall send_char_spi
  ldi r24, 0xff
  rcall send_char_spi
  sbi PORTB, PB0
 
  ;; Delay at least 1ms for SD
  ser r17
delay_l1:
  ser r18
delay_l2:
  dec r18
  brne delay_l2
  dec r17
  brne delay_l1

  ;; Init SD by setting SS high, send 10 0xff's
  ;; This de-selects the card so no data on the SPI bus
  ;; can affect it, but it gets clock cycles it can use
  ;; to initialize itself
  sbi PORTB, SPI_SS
  ldi r17, 10
loop_spi_init:
  ser r24
  rcall send_char_spi
  dec r17
  brne loop_spi_init

  ;; Send GO_IDLE_STATE command CMD0
init_again:
  cbi PORTB, SPI_SS
  ldi r24, 0x40      ; command
  rcall send_char_spi
  clr r24           ; argument
  rcall send_char_spi
  clr r24           ; argument
  rcall send_char_spi
  clr r24           ; argument
  rcall send_char_spi
  clr r24           ; argument
  rcall send_char_spi
  ldi r24, 0x95     ; CRC
  rcall send_char_spi
  ser r24
  rcall read_char_spi ; 1 char delay
try_again:
  ser r24
  rcall read_char_spi ; read response
  cpi r24, 1
  breq done
  rcall send_hex ;;; ZOMG ;;;
  rjmp init_again

done:
  sbi PORTB, SPI_SS

  ldi r24, '@'
  rcall send_char

  ;; Send SEND_OP_CON command
repeat_init:
  ser r24             ; I don't understand why I need this here, but not
  rcall send_char_spi ; sending a 0xff causes problems
  cbi PORTB, SPI_SS
  ldi r24, 0x41
  rcall send_char_spi
  clr r24           ; argument
  rcall send_char_spi
  clr r24           ; argument
  rcall send_char_spi
  clr r24           ; argument
  rcall send_char_spi
  clr r24           ; argument
  rcall send_char_spi
  ser r24           ; CRC
  rcall send_char_spi
  ser r24           ; 1 char delay
  rcall send_char_spi
  ser r24           ; Response
  rcall send_char_spi
  sbi PORTB, SPI_SS
  mov r20, r24
  rcall send_hex
  tst r20
  brne repeat_init

  ;; Oh yes.. faster...
  ldi r17, (1<<SPE)|(1<<MSTR)
  out SPCR, r17
  out SPSR, r1

  ;; ZOMG ANOTHER DELAY?  (FIXME - probably a debug issue?)
  ser r24
  rcall read_char_spi

  ldi r24, 'R'        ; ZOMG
  rcall send_char

  ;; Send READ_SINGLE_BLOCK command (r11:r10 should be 0x0000)
  rcall read_current_block
  ;ldi r26, (SRAM_START&0xff)   ; X = SRAM_START
  ;ldi r27, (SRAM_START>>8)
  ;rcall read_current_block_head

;  ldi r17, 8
;read_mike:
;  ld r24, X+
;  rcall send_hex
;  ldi r24, '\r'
;  rcall send_char
;  ldi r24, '\n'
;  rcall send_char
;  dec r17
;  brne read_mike

  ;ldi r22, 1          ; let there be sound
  clr r22

  ldi r24, 'K'        ; ZOMG
  rcall send_char

  ; Fine, I can be interrupted now
  sei

main:
  ;lds r20, UCSR0A         ; poll uart to see if there is a data waiting
  ;sbrs r20, RXC0
  ;rjmp main               ; if no data, loop around

  ;lds r20, UDR0

  ;; ECHO - just for checking things are okay
  ;mov r24, r20
  ;call send_char

  rjmp main

; The Great Interrupt Routine!
the_great_interrupt:
  ; save status register
  ;in r7, SREG

  sbrc r22, 0
  rjmp play_sound

  ;ldi r17, (1<<ADEN)|(1<<ADATE)|(1<<ADSC)
  ;sts ADCSRA, r17

  lds r19, ADCL
  lds r18, ADCH

  tst r22
  brne check_second_sound
  tst r18
  breq escape

  ldi r24, '+'
  rcall send_char

  lds r11, SRAM_START+1
  lds r10, SRAM_START+2
  inc r22
  rcall read_current_block_head

escape:
  reti

check_second_sound:
  tst r18
  brne escape

  ldi r24, '2'
  rcall send_char

  lds r11, SRAM_START+5
  lds r10, SRAM_START+6
  inc r22

  ;cbi PORTB, SPI_SS
  ;rcall read_char_spi
  ;sbi PORTB, SPI_SS

  rcall read_current_block_head

  reti

play_sound:
  cbi PORTB, SPI_SS
  rcall read_char_spi         ; read upper byte
  mov r17, r24
  rcall read_char_spi         ; read lower byte
  mov r19, r24
  sbi PORTB, SPI_SS

  cpi r17, 0xff
  breq stopit_exclaimation_point

  ; call send_hex

  cbi PORTB, PB0
  mov r24, r17
  rcall send_char_spi
  mov r24, r19
  rcall send_char_spi
  sbi PORTB, PB0

  dec r26
  brne block_not_over

  ;ldi r24, '#'        ; ROAR
  ;call send_char

  ;; read checksum (why just calle read_char_spi?)
  cbi PORTB, SPI_SS
  ser r24
  out SPDR, r24
wait_transmit1:
  in r24, SPSR
  sbrs r24, SPIF
  rjmp wait_transmit1
  in r24, SPDR

  ser r24
  out SPDR, r24
wait_transmit2:
  in r24, SPSR
  sbrs r24, SPIF
  rjmp wait_transmit2
  in r24, SPDR
  sbi PORTB, SPI_SS
  ;; end checksum

  ;ldi r24, '$'        ; ROAR
  ;call send_char

  rcall read_current_block_head

block_not_over:
  ; restore status register
  ;out SREG, r7
  reti

stopit_exclaimation_point:
  ;ldi r24, '!'        ; ROAR
  ;call send_char

  ;mov r24, r26
  ;call send_hex

  ldi r26, 0xff
  cbi PORTB, SPI_SS
flush_the_sd_card_toilet:
  rcall read_char_spi         ; read upper byte
  ;mov r17, r24
  rcall read_char_spi         ; read lower byte
  ;mov r19, r24

  dec r26
  brne flush_the_sd_card_toilet

  ;; Read 2 byte CRC
  ;rcall read_char_spi
  ;rcall read_char_spi
  sbi PORTB, SPI_SS

  inc r22
  andi r22, 3

  ldi r24, 'E'        ; ROAR
  rcall send_char

  reti

; void send_char(r24)  : r14 trashed
send_char:
  lds r14, UCSR0A     ; check to see if it's okay to send a char
  sbrs r14, UDRE0
  rjmp send_char      ; if it's not okay, loop around :)
  sts UDR0, r24       ; output a char over rs232
  ret

; void send_nibble(r24)  : r14, r18 trashed
send_nibble:
  cpi r24, 10
  brlo hex_nibble_under_10
  subi r24, 10
  ldi r18, 'A'
  add r24, r18
  rcall send_char
  ret
hex_nibble_under_10:
  ldi r18, '0'
  add r24, r18
  rcall send_char
  ret

; void send_hex(r24)  : r13, r14, r18, r24 trashed
send_hex:
  mov r13, r24
  swap r24
  andi r24, 0x0f
  rcall send_nibble
  mov r24, r13
  andi r24, 0x0f
  rcall send_nibble
  ldi r24, ' '
  rcall send_char
  ret

; char send_char_spi(r24) : r24 returns the byte received
send_char_spi:
  out SPDR, r24
wait_transmit:
  in r24, SPSR
  sbrs r24, SPIF
  rjmp wait_transmit
  in r24, SPDR
  ret

; char read_char_spi() : r24 returns the byte received
read_char_spi:
  ser r24
  out SPDR, r24
wait_transmit_r:
  in r24, SPSR
  sbrs r24, SPIF
  rjmp wait_transmit_r
  in r24, SPDR
  ret

; void read_current_block() | r24, r26, r27, r18, 19 trashed
read_current_block:
  cbi PORTB, SPI_SS

  ldi r24, 17|0x40
  rcall send_char_spi
  clr r24           ; argument
  rcall send_char_spi
  mov r24, r11      ; argument
  rcall send_char_spi
  mov r24, r10      ; argument
  rcall send_char_spi
  clr r24           ; argument
  rcall send_char_spi
  ser r24           ; CRC
  rcall send_char_spi
  rcall read_char_spi ; 1 char delay

wait_for_fe:
  rcall read_char_spi ; response
  cpi r24, 0xfe
  brne wait_for_fe

  ;ldi r24, '#'                 ; ZOMG
  ;rcall send_char

  ;; FIXME - faster to compare against SRAM_START+512
  ldi r26, (SRAM_START&0xff)   ; X = SRAM_START
  ldi r27, (SRAM_START>>8)
  clr r18
read_1_byte_to_ram:
  rcall read_char_spi
  st X+, r24
  rcall read_char_spi
  st X+, r24
  dec r18
  brne read_1_byte_to_ram

  rcall read_char_spi          ; CRC16
  st X+, r24
  rcall read_char_spi          ; CRC16
  st X+, r24
  sbi PORTB, SPI_SS

  inc r10
  inc r10
  brne no_r10_overflow
  inc r11
no_r10_overflow:

  ret

; void read_current_block_head() | r24 trashed
read_current_block_head:
  cbi PORTB, SPI_SS

  ldi r24, 17|0x40
  rcall send_char_spi
  clr r24           ; argument
  rcall send_char_spi
  mov r24, r11      ; argument
  rcall send_char_spi
  mov r24, r10      ; argument
  rcall send_char_spi
  clr r24           ; argument
  rcall send_char_spi
  ser r24           ; CRC
  rcall send_char_spi
  rcall read_char_spi ; 1 char delay
  rcall read_char_spi ; response

wait_for_fe2:
  cpi r24, 0xfe
  breq got_the_fe2
  rcall read_char_spi
  rjmp wait_for_fe2
got_the_fe2:

  sbi PORTB, SPI_SS

  inc r10
  inc r10
  brne no_r10_overflow2
  inc r11
no_r10_overflow2:

  ;; So.. this 128 means to do 128 sample reads.  Here's the
  ;; situation.  Each sample will take 2 bytes.  The entire block
  ;; is 512 bytes.  So I would think this should be 256 (00 really)
  ;; but... the problem is for some reason the SD card is ignoring
  ;; /CS when I write to the DAC.  So I wrote all samples to the SD
  ;; card twice knowing the card will skip 2 samples.
  ldi r26, 128
  ;clr r26
  ret

signature:
.db "SD/MMC DAC Sound - Copyright 2009 - Michael Kohn - Version 0.04"



