Diese Schaltung kann bis zu 64 Audiodateien in einem speziellen Format von einer SD-Karte abspielen. Die Audio-Ausgabe erfolgt mit einer 8-Bit-PWM, ein passives analoges Filter stellt das Audiosignal wieder her. Per Ein-Knopf-Bedienung kann zum nächsten Titel gesprungen bzw. der zuletzt gespielte Titel wiederholt werden, außerdem kann per Impulsfolge jeder Titel direkt angewählt werden. Des Weiteren kann die "AutoAdvance"-Funktion eingeschaltet werden, so dass am Ende eines Titels automatisch der nächste abgespielt wird.
Die Schaltung besteht neben dem ATtiny13 aus folgenden Teilen:
Schaltplan,
Aufbau auf dem Steckbrett (Foto 1),
Aufbau auf dem Steckbrett (Foto 2).
Da SD-Karten nur mit einer Spannungsversorgung zwischen 2,7 V und 3,6 V arbeiten können, werden die Signale vom ATtiny13 durch Spannungsteiler auf etwa 3,3 V herabgesetzt. Das MISO-Signal von der SD-Karte zum AVR ist direkt verbunden, da der mit 5 V betriebene AVR eine Spannung von 3,3 V zuverlässig als High erkennt. Die Spannungsversorgung der SD-Karte erfolgt mit zwei in Reihe geschalteten Dioden, um die Spannung von 5 V auf etwa 3,6 V herabzusetzen. Der 1 kΩ-Widerstand dient als Grundlast.
Das #CS-Signal ist im Ruhezustand high, es fließt also kontinuierlich Strom durch den Spannungsteiler. Wenn die Stromaufnahme der Schaltung besonders niedrig sein muss, sollte stattdessen ein Pegelwandler-IC wie z.B. 74HC4050 eingesetzt werden. Alternativ kann möglicherweise auch der AVR an 3,3 V betrieben werden, das Audiosignal ist dann natürlich entsprechend leiser.
Das Audio-Ausgangssignal des AVR wird von einer 8-Bit-PWM mit einer Frequenz von 37,5 kHz erzeugt. Um ein analoges Audiosignal zu erhalten wird ein Tiefpassfilter benötigt, dessen Grenzfrequenz etwa die Hälfte der Abtastrate beträgt. Die Abtastrate entspricht in diesem Fall der PWM-Frequenz. Ich habe ein Chebyshev-Filter fünfter Ordnung ausgewählt. Zur Berechnung habe ich den Chebyshev Pi LC Low Pass Filter Calculator verwendet und anschließend die Schaltung in LTspice simuliert, um die Werte für den unsymmetrischen Fall (Eingangswiderstand ungleich Ausgangswiderstand) und reale Bauteilwerte so anzupassen, dass sich trotzdem eine brauchbare Übertragungsfunktion ergibt.
Wird ein Kopfhörer verwendet, so kann dieser ohne weitere Verstärkung direkt hinter dem Filter angeschlossen werden. Der Wert des Vorwiderstands und des Potis können je nach Empfindlichkeit des Kopfhörers angepasst werden, mit den angegebenen Werten ergibt sich ein guter Einstellbereich mit meinem Sennheiser PX-200.
Für den Betrieb mit Lautsprechern habe ich einen Verstärker auf LM386-Basis verwendet. Für eine leise Wiedergabe reicht auch eine Betriebsspannung von 5 V aus, dann ist es allerdings leicht möglich, den Verstärker zu übersteuern. Mit der angegebenen Spannung von 12 V kam es auch bei voll aufgedrehtem Poti und in lauten Passagen nie zu Clipping. Für das Poti sollte möglichst ein logarithmischer Typ verwendet werden, um die Lautstärke im unteren Bereich feinfühliger einstellen zu können.
Anstelle des Verstärkers können auch Aktivboxen angeschlossen werden. Um die Lautstärke zu begrenzen sollte trotzdem ein Spannungsteiler oder bei bekanntem Eingangswiderstand der Boxen ein Vorwiderstand verwendet werden. Auch ein Anschluss von Lautsprechern ohne Verstärker (und sogar ohne Filter, nur mit Entkoppelkondensator) ist möglich.
AVR-Assemblerprogramm und Hexfile.
Die Software läuft mit der maximalen Taktfrequenz des internen Oszillators von 9,6 MHz. Außerdem muss die Start-Up-Time auf 4 ms eingestellt werden (siehe unten). Zusammen mit einer Brown-Out-Detection bei 2,7 V ergeben sich die Fuse-Bytes:
Fuse-Byte | Wert |
---|---|
low | 0x76 (0111.0110) |
high | 0xFB (1111.1011) |
Die Software verwendet einen Trick, um eine Bedienung zu ermöglichen, obwohl bereits alle fünf Portpins anderweitig belegt sind. Der ATtiny13 bietet die Möglichkeit, nach einem Reset die Ursache aus einem Register auszulesen. Auf diese Weise kann zwischen einem Power-On-Reset nach dem Anlegen der Spannung und einem externen Reset per Taster am Reset-Pin unterschieden werden. Darüberhinaus bleiben bei einem Reset die Register unverändert, so dass z.B. die Nummer des aktuellen Titels auch nach einem Reset noch zur Verfügung steht. Die Start-Up-Time des Controllers wird mit den Fuse-Bits auf 4 ms eingestellt und dient zur Entprellung des Tasters.
Nach dem Einschalten werden zunächst nur die Register initialisiert, anschließend geht der Controller in den Power-Down-Schlafmodus, um Strom zu sparen. Nach dem ersten externen Reset wird der erste Titel (Titel 0) von der SD-Karte abgespielt. Drückt man während des Abspielens erneut auf den Reset-Taster, so springt die Software zum nächsten Titel. Befindet sie sich bereits beim letzten Titel, so wird wieder mit dem ersten Titel begonnen.
Wird das Titelende ohne vorzeitigen Reset erreicht, geht der Controller wieder in den Schlafmodus. Erfolgt nun ein Reset, wird der selbe Titel nochmals abgespielt, um zum nächsten Titel vorzurücken muss also zweimal auf den Taster gedrückt werden.
Wird der AutoAdvance-Modus aktiviert, spielt die Software automatisch den nächsten Titel ab, wenn das Ende eines Titels erreicht wird. Erst nach dem Ende des letzten Titels geht der Controller in den Schlafmodus. Die Auswahl dieses Modus erfolgt durch das Einsetzen eines 1 kΩ-Widerstands zwischen dem MISO-Pin der SD-Karte und Masse. Nach dem Ende eines Titels erfolgt kein Datentransfer zwischen SD-Karte und AVR mehr, der MISO-Pin wird also von der SD-Karte nicht mehr getrieben. Ohne den Widerstand wird er vom internen Pull-up des AVR auf High gezogen, mit dem Widerstand bleibt er auf Low. Nach dem Ende jedes Titels wird der Pegel an diesem Pin eingelesen und ggf. zum nächsten Titel weitergeschaltet.
Um auch von einem anderen Controller aus Titel anwählen zu können gibt es die Möglichkeit der direkten Titelanwahl per Impulsfolge am Reset-Pin. Dazu werden 2+n negative Impulse auf den Resetpin gegeben, um den Titel n (zwischen 0 und 63) auszuwählen. Wird ein nicht existenter Titel ausgewählt oder werden mehr als 65 Impulse empfangen, so wird nichts abgespielt. Diese Funktion kann verwendet werden, um einen laufenden Titel abzubrechen. Ein einzelner Reset-Impuls bzw. ein Druck auf den Reset-Taster beginnt den abgebrochenen Titel von vorne.
Die Impulse müssen lang genug sein, um einen Reset auszulösen und dürfen beliebig lang werden. Laut Datenblatt des ATtiny13 ist bei 5 V Betriebsspannung eine Impulslänge von 500 ns ausreichend. Zwischen zwei Impulsen sollten mindestens 5 ms liegen, damit die Software nach der Start-Up-Time von 4 ms genug Zeit hat, den Reset-Impuls-Zähler zu erhöhen. Anschließend enthält die Software eine Warteschleife von 10 ms, bevor der Impulszähler ausgewertet und der entsprechende Titel gestartet wird. Die maximale Länge zwischen zwei Impulsen darf also nicht größer als 14 ms (4 ms Start-Up-Time und 10 ms Warteschleife) werden, sicherheitshalber sollte sie nicht mehr als 10 ms betragen.
Die Software unterstüzt sowohl MMC- als auch SD- und SDHC-Karten. Manche Karten sind beim Lesen jedoch langsamer als andere, eine 8 GB-SDHC-Karte (Class 4) von Kingston war sogar so langsam, dass die Audiodatei nicht mehr flüssig abgespielt werden konnte. Mit einer 16 MB-MMC von Canon und einer noname 256 MB SD-Karte funktionierte es hingegen problemlos. Wichtig ist hier nicht die durchschnittliche Lesegeschwindigkeit, sondern die maximale Zeit, die nach der Übermittlung der Adresse an die SD-Karte benötigt wird, bevor die Daten bereitstehen. Aufgrund des kleinen SRAMs des ATtiny13 ist der Puffer für die gelesenen Daten nur 48 Byte groß, bei einer Abtastfrequenz von 37,5 kHz reicht ein vollständig gefüllter Puffer deshalb gerade einmal 1,28 ms. Braucht die SD-Karte zum Lesen des Sektors länger, so verringert sich die Abspielgeschwindigkeit und es ist ein deutliches "Krächzen" zu hören. Bei den beiden kleineren Karten ist die Zeit für alle Sektoren gleich und liegt unter diesem Wert, bei der SDHC-Karte fluktuiert die Zeit jedoch stark und erreicht an einigen Stellen zu hohe Werte. In den folgenden Oszillogrammen wird das Taktsignal zur SD-Karte für die drei verschiedenen Karten gezeigt, im letzten Bild sind auch die drei Phasen eingezeichnet. Jede der "Impulsnadeln" besteht in Wirklichkeit aus einer schnellen Folge von acht Taktimpulsen, es wird also jeweils ein Byte gelesen.
Weitere Informationen zur Ansteuerung von SD-Karten sind in How to Use MMC/SDC zu finden.
Die Audiodaten liegen ohne Partitionierung oder Dateisystem direkt auf der SD-Karte. Der erste Sektor auf der Karte (Sektor 0, normalerweise als Bootsektor/MBR verwendet) enthält eine Tabelle, an welcher Stelle die einzelnen Titel beginnen und wie lang diese sind. Diese Tabelle besteht aus 64 Einträgen für die Titel 0 bis 63, welche aus jeweils 8 Bytes bestehen:
Bytes | Inhalt |
---|---|
0~3 | Startsektor des ersten Titels (Titel 0) |
4~7 | Länge des ersten Titels (in Sektoren) |
8~11 | Startsektor des zweiten Titels (Titel 1) |
12~15 | Länge des zweiten Titels (in Sektoren) |
16~19 | Startsektor des dritten Titels (Titel 2) |
20~23 | Länge des dritten Titels (in Sektoren) |
. . . | . . . |
504~507 | Startsektor des 64. Titels (Titel 63) |
508~511 | Länge des 64. Titels (in Sektoren) |
Alle Werte sind als unsigned 32-bit little endian gespeichert (niederwertigstes Byte zuerst). Ein Wert von 1 wird also als Byte-Folge 0x01, 0x00, 0x00, 0x00 gespeichert und der größtmögliche Wert ist 232 − 1 = 4294967295 (Byte-Folge 0xFF, 0xFF, 0xFF, 0xFF).
Die Länge jedes Titels muss ein Vielfaches von 512 sein, damit die Länge als Anzahl Sektoren angegeben werden kann. Da dies bei den meisten Titeln nicht der Fall sein wird müssen die Audiodaten durch Anfügen von Bytes mit dem Wert 0x80 verlängert werden, bis das nächste Vielfache von 512 erreicht ist. Die gesamten Daten eines Titels müssen in aufeinanderfolgenden Sektoren gespeichert sein. Die Reihenfolge der Titeldaten auf der SD-Karte muss nicht unbedingt der Reihenfolge der Titel in der Tabelle im ersten Sektor entsprechen.
Zuerst müssen aus vorliegenden Audiodateien, z.B. im MP3-Format, die Audiodaten im speziellen Format für die SD-Karte generiert werden. Am einfachsten geht das mit dem Programm Audacity, welches für Windows, Linux und MacOS verfügbar ist. Um eine Datei zu konvertieren sollten diese Schritte befolgt werden:
Wer lieber mit Konsolenprogrammen arbeitet kann die Audiodateien auch mit mplayer
konvertieren. Die Befehlszeile dazu sieht so aus (wobei outfile.raw die
Ausgabedatei und infile.mp3 die existierende Musikdatei ist, natürlich
sind auch andere Formate als MP3 möglich):
mplayer -af pan=1:0.5:0.5,resample=37500:0:2 -format u8 -ao pcm:nowaveheader:file=outfile.raw infile.mp3
Um die Audiodaten möglichst einfach auf die SD-Karte schreiben zu können habe ich ein Programm in Visual Basic geschrieben, welches anhand der einzelnen, mit Audacity erzeugten Audiodateien die Tabelle für den ersten Sektor erzeugt und alles in eine Image-Datei für die SD-Karte schreibt. Das Programm lässt sich mit wine auch unter Linux benutzen.
Anschließend muss das erzeugte Image noch auf die SD-Karte
geschrieben werden. Dabei gehen alle bereits auf der SD-Karte
gespeicherten Daten verloren! Unter Linux geht das ganz einfach mit dd,
wenn "imagefile" das mit dem Visual-Basic-Programm erzeugte Image und
/dev/sdx der Gerätename des SD-Kartenlesers ist:
dd if=imagefile of=/dev/sdx
Für Windows gibt es verschiedene Programme, mit denen das Image auf die SD-Karte geschrieben werden kann. Ich habe mit der Stand-Alone-Version von Roadkil's Disk Image gute Erfahrungen gemacht. Zum Schreiben des Images auf die SD-Karte in der Registerkarte "Write Image" das der SD-Karte entsprechende Laufwerk ausgewählen, die Image-Datei laden und auf "Start" klicken.
Soll die SD-Karte später wieder für einen anderen Zweck eingesetzt werden, muss sie zunächst neu partitioniert und formatiert werden. Viele Geräte wie z.B. Kameras bieten dazu eine entsprechende Funktion an.
;SD card sound player for ATtiny13 (audio: 37.5 kHz, 8
bit unsigned, mono)
;ATtiny13 @ 9.6 MHz (internal R/C osc.), lfuse=0x76, hfuse=0xFB (BOD @ 2.7
V)
;(C) Arne Rossius, http://www.ebps.de.vu/
;
;
;
;PORTS
;
; PB0 = SD-card MOSI
; PB1 = sound output (PWM)
; PB2 = SD-Card clock
; PB3 = SD-card MISO, a 1k resistor to
ground enables auto-advance mode
; PB4 = SD-card #CS
; PB5 = play/next/control input (#RESET
pin): push-button to ground
;
; Connect MOSI, SCK, #CS with resistive
voltage dividers 2.2k / 1k to ground.
; Connect MISO directly to SD
card.
; SD card power through 2 diodes 1N4148
and resistor ~1k to ground = ~3.6 V.
;
;
;
;USAGE
;
; Press play/next button after power-on
=> play first title.
; Press play/next button while playing
=> advance to next title (wrap-around).
; Press play/next button after playback
has stopped => play same title again.
;
; Direct title selection: n+2 negative
pulses to reset pin to select title n,
;
where n =
0~63. Pulse width min. 2 us, distance
;
between pulses
from 5 ms to 10 ms, delay after pulse
;
train > 15
ms. If a non-existent title is selected,
;
nothing will
be played. Button can be used normally
; after playback has
started.
;
; Auto-advance mode: at end of title,
automatically start playing the next
; title until a
non-existent title is reached. No wrap-
; around (playback
stops after last title). Button and
; direct title
selection can be used normally. To restart
; at first title
after all titles have been played, press
; button twice (first
press restarts last title, second
; press advances with
wrap-around) or using direct title
; selection (2
pulses).
;
; SD card data format:
; * Header sector (sector 0): 8 bytes
per title for up to 64 titles:
; - 4 bytes offset (in sectors), little
endian, e.g. 1 for first title,
; zero if title does not exist.
Gaps (non-existent title(s) followed by
; one or more existent titles) are
allowed, but titles after first gap
; can only be reached with direct
title selection.
; - 4 bytes length (in sectors) of title,
little endian. Ignored for non-
; existent titles.
; * Following sectors: audio data, 1
byte per sample, unsigned, idle = 0x80,
; 37500 bytes per second. Gaps
between header and first title data or
; between data areas of two titles
are allowed. Title data does not have
; to be in the same order as titles
in the header sector. Each title must
; end on a sector boundary, the
unused area in the last sector should be
; padded with 0x80.
;
;
;
;VERSIONS
;
; 2013-04-28 Arne Rossius
; * first version
.include "tn13def.inc"
.equ MAX_TITLES = 64
.equ BUFFER_SIZE = 48
.equ SD_SCK = 2
.equ SD_MOSI = 0
.equ SD_MISO = 3
.equ SD_CS = 4
.def zero = R0
.def zeroL = R0 ;same as 'zero'
.def zeroH = R1
.def sreg_backup = R2
.def reset_count = R3
.def try_title = R4
.def title = R5
.def state_old = R6
.def state = R7
.equ sStop
= 0
.equ sPlay
= 1
.equ sReset
= 2
.def size1 = R8
.def size2 = R9
.def size3 = R10
.def size4 = R11
.def sector1 = R12
.def sector2 = R13
.def sector3 = R14
.def sector4 = R15
.def temp = R16
.def temp2 = R17
.def count = R18
.def inttmp = R19
.def mask = R20
.def buflen = R21
.def flags = R22
.equ fFastSPI = 0 ;use fast SPI transfers to SD
card
.equ fSDHC
= 1 ;card is an SDHC card
.equ fIgnoreNonexistent
= 2 ;play nothing for non-existent titles
.def Z3 = R24 ;32-bit Z register: upper 2 bytes
.def Z4 = R25
;R27:R26 = X pointer (RAM pointer to start of ring buffer)
;R29:R28 = Y pointer (RAM pointer to end of ring buffer)
.def Z1 = R30 ;32-bit Z register: lower 2
bytes
.def Z2 = R31
;===============================================================================
.dseg
RAM_Buffer: .byte BUFFER_SIZE
RAM_Buffer_end:
;===============================================================================
.cseg
.org 0x000
rjmp reset
.org OVF0addr
;timer 0 overflow interrupt
in sreg_backup,
SREG
;check if ring buffer contains
data
tst buflen
breq ovf0_end
;buffer is empty
;read byte from buffer and output to
PWM register
ld inttmp,
X+
out OCR0B,
inttmp ;set new PWM value
dec buflen
cpi XL,
RAM_Buffer_end
brlo PC+2
ldi XL,
RAM_Buffer
ovf0_end:
;return
out SREG,
sreg_backup
reti
;===============================================================================
.macro _ldi_d
.if (@1 == 0)
movw @02:@01,
zeroH:zeroL
movw @04:@03,
zeroH:zeroL
.else
;load immediate double-word
ldi @01,
BYTE1(@1)
ldi @02,
BYTE2(@1)
ldi @03,
BYTE3(@1)
ldi @04,
BYTE4(@1)
.endif
.endmacro
.macro _brne
;branch if not equal, long
distance
breq PC+2
rjmp @0
.endmacro
;===============================================================================
reset:
;init state register
mov temp,
state
cpi temp,
sReset
breq PC+2
mov state_old,
temp
ldi temp,
sReset
mov state,
temp
inc reset_count
;init stack pointer
ldi temp,
RAMEND
out SPL,
temp
;init ports (keep PWM output high-Z
for now (prevent popping noise)
ldi temp,
1<<SD_CS | 1<<SD_SCK | 1<<SD_MOSI
out DDRB,
temp
ldi temp,
0x00 | 1<<SD_CS | 1<<SD_MISO ;enable pull-up on MISO pin
out PORTB,
temp
;disable analog comparator
ldi temp,
0x80
out ACSR,
temp
;init registers
clr zero
clr zeroH
clr flags
clr buflen
ldi XL,
RAM_Buffer
ldi YL,
RAM_Buffer
;delay
rcall delay_10ms
;check reset reason
in temp,
MCUSR
sbrc temp,
EXTRF
rjmp reset_ext
;external reset: start playing / next title
;other reset source: assume power-on
reset
ldi temp,
sStop
mov state,
temp
clr title
clr reset_count
;wait for external reset
rjmp wait_for_reset
reset_ext:
;external reset occured
mov temp,
reset_count
cpi temp,
2
brsh reset_pulse_train
mov temp,
state_old
andi flags,
~(1<<fIgnoreNonexistent)
cpi temp,
sPlay
breq next_title
cpi temp,
sStop
breq play_title
;program should never get here
clr try_title
rjmp playback_start
reset_pulse_train:
;pulse train (multiple resets in
quick succession) detected
ori flags,
1<<fIgnoreNonexistent
subi temp,
2 ;2 pulses = title 0, 3 pulses = title 1, ...
mov try_title,
temp
rjmp playback_start
next_title:
;advance to next title
mov try_title,
title
inc try_title
rjmp playback_start
play_title:
;play last title again
mov try_title,
title
playback_start:
;start playback
clr reset_count
ldi temp,
sPlay
mov state,
temp
mov temp,
try_title
cpi temp,
MAX_TITLES
brlo playback_start_end
sbrc flags,
fIgnoreNonexistent
rjmp wait_for_reset
clr try_title
playback_start_end:
init:
;init SD card
rcall sd_init
cpi temp,
0
breq init_end
;init failed: ~100 ms delay, then
try again
ldi count,
5
ldi temp,
0
ldi temp2,
0
init_delay:
dec temp2
brne init_delay
dec temp
brne init_delay
dec count
brne init_delay
rjmp init
init_end:
header:
;read header sector (sector 0) to
find title
movw sector2:sector1,
zeroH:zeroL
movw sector4:sector3,
zeroH:zeroL
rcall prepare_sector
ldi count,
0
header_read:
rcall read_header_entry
cp count,
try_title
breq header_finish
inc count
rjmp header_read
header_finish:
;flush remaining bytes in header
sector
cpi count,
64
breq header_end
rcall dump_header_entry
inc count
rjmp header_finish
header_end:
rcall finish_sector
;check if header entry for selected
title exists
cp sector1,
zero
cpc sector2,
zero
cpc sector3,
zero
cpc sector4,
zero
breq title_not_found
;title found in header sector
mov title,
try_title
;init timer 0: PWM output on OC0B,
fast PWM mode, non-inverted, 8 bit
ldi temp,
0x80 ;idle: 50% duty cycle
out OCR0B,
temp
ldi temp,
1<<COM0B1 | 1<<WGM01 | 1<<WGM00
out TCCR0A,
temp
ldi temp,
0x01 ;Clk/1
out TCCR0B,
temp
ldi temp,
1<<TOV0 ;clear overflow interrupt flag
out TIFR0,
temp
ldi temp,
1<<TOIE0 ;enable overflow interrupt
out TIMSK0,
temp
sbi DDRB,
1 ;enable output pin
;enable interrupts
sei
;all set, start playback
rjmp main_loop
title_not_found:
;selected title does not exist
sbrc flags,
fIgnoreNonexistent
rjmp stop_playback
tst try_title
breq stop_playback
;first title doesn't exist => play nothing
clr try_title
;try playing first title (title 0)
rjmp header
;-------------------------------------------------------------------------------
main_loop:
;start sector
rcall prepare_sector
;read 512 data bytes and write them
to the ring buffer
ldi ZL,
LOW(512)
ldi ZH,
HIGH(512)
read_sector:
rcall sd_read
rcall buffer_write
ldi temp,
1
sbiw ZH:ZL,
1
brne read_sector
;end sector
rcall finish_sector
;decrement file size (in sectors)
counter
sub size1,
temp
sbc size2,
zero
sbc size3,
zero
sbc size4,
zero
breq playback_finished
;increment sector counter
ldi temp,
1
add sector1,
temp
adc sector2,
zero
adc sector3,
zero
adc sector4,
zero
rjmp main_loop
playback_finished:
;title ended
;check if auto-advance is
enabled
rcall sd_clock ;clock burst to make SD card release MISO
rjmp PC+1
rjmp PC+1
sbic PINB,
SD_MISO
rjmp auto_advance_end
mov temp,
title
cpi temp,
MAX_TITLES - 1
brsh auto_advance_end
;last title reached => stop playback
mov try_title,
title
inc try_title
ori flags,
1<<fIgnoreNonexistent ;stop if next title doesn't exist
rjmp header
auto_advance_end:
stop_playback:
;set state to
"stopped"
ldi temp,
sStop
mov state,
temp
;put SD card to sleep mode
cbi PORTB,
SD_CS ;assert chip select
ldi temp,
0 ;CMD0 (software reset = go to idle state)
_ldi_d Z, 0
rcall sd_command
rcall sd_clock
sbi PORTB,
SD_CS ;de-assert chip select
rcall sd_clock ;clock burst to make SD card release MISO
cbi PORTB,
SD_MOSI ;set MOSI = low (avoid current through divider)
;wait until buffer is empty
buffer_empty:
tst buflen
brne buffer_empty
;disable PWM (set output to high-Z
to prevent popping noise)
;(input buffers are disabled in
sleep mode, so floating pins won't
; cause any excessive current
draw)
cli ;disable interrupts
cbi DDRB,
1
ldi temp,
0
out TCCR0A,
temp
out TCCR0B,
temp
wait_for_reset:
;go to sleep mode (wait for
reset)
ldi temp,
sStop
mov state,
temp
ldi temp,
1<<SE | 1<<SM1 ;select power down mode
out MCUCR,
temp
sleep
;program should never get here
rjmp PC
;===============================================================================
delay_10ms:
;delay ~10ms (~125*256*3
cycles)
ldi temp,
125
ldi temp2,
0
delay_10ms_loop:
dec temp2
brne delay_10ms_loop
dec temp
brne delay_10ms_loop
ret
;--------------------
buffer_write:
;write byte to ring buffer
;wait until buffer has space
cpi buflen,
BUFFER_SIZE
breq buffer_write
;write byte to buffer
st Y+,
temp
inc buflen
cpi YL,
RAM_Buffer_end
brlo PC+2
ldi YL,
RAM_Buffer
ret
;--------------------
read_header_entry:
;read one header entry from header
sector
;read sector address of title
rcall sd_read
mov sector1,
temp
rcall sd_read
mov sector2,
temp
rcall sd_read
mov sector3,
temp
rcall sd_read
mov sector4,
temp
;read length of title (in
sectors)
rcall sd_read
mov size1,
temp
rcall sd_read
mov size2,
temp
rcall sd_read
mov size3,
temp
rcall sd_read
mov size4,
temp
ret
;--------------------
dump_header_entry:
;dump one header entry (8 bytes)
from header sector
ldi temp,
8
dump_header_entry_loop:
rcall sd_clock
dec temp
brne dump_header_entry_loop
ret
;--------------------
prepare_sector:
;prepare to read a sector (sector
address given by sector[4:1])
;assert chip select
cbi PORTB,
SD_CS
;send CMD17 (read block)
ldi temp,
17
sbrc flags,
fSDHC
rjmp prepare_sector_sdhc
ldi Z1,
0 ;standard SD: address = sector * 512
mov Z2,
sector1
mov Z3,
sector2
mov Z4,
sector3
lsl Z2
rol Z3
rol Z4
rjmp prepare_sector_cmd
prepare_sector_sdhc:
movw Z2:Z1,
sector2:sector1 ;SDHC: address = sector
movw Z4:Z3,
sector4:sector3
prepare_sector_cmd:
rcall sd_command
;wait until data is ready
prepare_sector_wait:
rcall sd_read
;TODO: check for errors
cpi temp,
0xFE ;check for "data ready" answer
brne prepare_sector_wait
ret
;--------------------
finish_sector:
;finish reading a sector (assumes
all 512 bytes have already been read)
;dump checksum (2 bytes)
rcall sd_clock
rcall sd_clock
;dummy read
rcall sd_clock
;de-assert chip select
sbi PORTB,
SD_CS
ret
;===============================================================================
sd_init:
;initialize SD card
;use slow SPI transfers at first;
clear SDHC flag
andi flags,
~(1<<fFastSPI | 1<<fSDHC)
;send at least 74 clock pulses (10
bytes = 80 pulses)
sbi PORTB,
SD_CS ;de-assert chip select (card not selected)
ldi count,
10
sd_init_clocks:
rcall sd_clock
dec count
brne sd_init_clocks
;send CMD0 (software reset = go to
idle state)
cbi PORTB,
SD_CS ;assert chip select
ldi temp, 0
_ldi_d Z, 0
rcall sd_command
rcall sd_clock
cpi temp,
0x01
_brne sd_init_error
;send CMD8 (check voltage
range)
ldi temp,
8
_ldi_d Z, 0x122 ;2.7~3.6V, check pattern 0x22 (same CRC as for
CMD0(0))
rcall sd_command
cpi temp,
0x05 ;unknown command => MMC (or old SD card?)
breq sd_init_mmc
cpi temp,
0x01
_brne sd_init_error
rcall sd_clock ;ignore byte 1
rcall sd_clock ;ignore byte 2
rcall sd_read ;read byte 3
cpi temp,
0x01 ;voltage range OK?
brne sd_init_error
rcall sd_read ;read byte 4
cpi temp,
0x22 ;matches check pattern?
brne sd_init_error
rcall sd_clock ;dummy read
sd_init_41:
;send ACMD41 (initiate init process)
until card leaves idle state
;TODO: timeout
ldi temp,
55 ;CMD55: prepare for ACMD
_ldi_d Z, 0
rcall sd_command
rcall sd_clock
ldi temp,
41 ;ACMD41
_ldi_d Z, 1<<30 ;bit 30 (HCS) set
rcall sd_command
rcall sd_clock ;dummy read
cpi temp,
0x01 ;card still in idle state
breq sd_init_41
cpi temp,
0x05 ;unknown command => possibly a MMC (?)
breq sd_init_mmc
cpi temp,
0x00
brne sd_init_error
;use fast SPI for all following
transfers
ori flags,
1<<fFastSPI
;send CMD58 (read OCR) to determine
if card is SDHC
ldi temp, 58
_ldi_d Z, 0
rcall sd_command
cpi temp,
0x00
brne sd_init_error
rcall sd_read ;read byte 1
rcall sd_clock ;ignore byte 2
rcall sd_clock ;ignore byte 3
rcall sd_clock ;ignore byte 4
rcall sd_clock ;dummy read
sbrc temp,
6 ;HCS bit set?
ori flags,
1<<fSDHC
sd_init_blocksize:
;send CMD16 (set block size) to set
block size to 512 bytes
ldi temp, 16
_ldi_d Z, 512
rcall sd_command
rcall sd_clock
;init finished successfully
sbi PORTB,
SD_CS ;de-assert chip select
clr temp
ret
sd_init_mmc:
;initialize MMC (or old SD card)
which doesn't accept ACMD41
sd_init_mmc_loop:
;send CMD1 until card leaves idle
state
;TODO: timeout
ldi temp,
1
_ldi_d Z, 0
rcall sd_command
rcall sd_clock ;dummy read
cpi temp,
0x01 ;card still in idle state
breq sd_init_mmc_loop
cpi temp,
0x00
brne sd_init_error
;use fast SPI for all following
transfers
ori flags,
1<<fFastSPI
rjmp sd_init_blocksize
sd_init_error:
;error during SD card init
sbi PORTB,
SD_CS ;de-assert chip select
ldi temp,
0xFF
ret
;--------------------
sd_command:
;send command to SD card, return
read value (bit 7 set => error)
ori temp,
0x40
rcall sd_write
mov temp,
Z4
rcall sd_write
mov temp,
Z3
rcall sd_write
mov temp,
Z2
rcall sd_write
mov temp,
Z1
rcall sd_write
ldi temp,
0x95 ;real CRC needed for CMD0(0) and CMD8(0x122)
rcall sd_write
;read response (max. 9 reads, then
abort if response bit 7 still set)
push count
ldi count,
9
sd_command_read:
rcall sd_read
dec count
sbrc temp,
7
brne sd_command_read
pop count
ret
;--------------------
.macro sd_write_fast_bit
ldi temp2,
1<<SD_MISO ;keep pull-up on MISO enabled
lsl temp
;MSB of temp is shifted into carry flag
adc temp2,
zero ;set bit 0 (MOSI) when carry flag is set
out PORTB,
temp2 ;output data, set clock = low
out PINB,
mask ;toggle SCK (set clock = high)
.endmacro
sd_write:
;send one byte to SD card
sbrs flags,
fFastSPI
rjmp sd_write_slow
;fast SPI write
ldi mask,
1<<SD_SCK ;mask for clock output toggle (write to PINB)
sd_write_fast_bit ;bit 7
sd_write_fast_bit ;bit 6
sd_write_fast_bit ;bit 5
sd_write_fast_bit ;bit 4
sd_write_fast_bit ;bit 3
sd_write_fast_bit ;bit 2
sd_write_fast_bit ;bit 1
sd_write_fast_bit ;bit 0
out PINB,
mask ;toggle SCK (set clock = low)
ret
sd_write_slow:
;slow SPI write (< 400 kHz)
ldi temp2,
8
sd_write_loop:
lsl temp
brcs PC+2
cbi PORTB,
SD_MOSI
brcc PC+2
sbi PORTB,
SD_MOSI
rcall spi_delay
sbi PORTB,
SD_SCK
rcall spi_delay
cbi PORTB,
SD_SCK
dec temp2
brne sd_write_loop
ret
;--------------------
.macro sd_read_fast_bit
;MSB of temp is shifted into carry
flag
lsl temp
sbic PINB,
SD_MISO
ori temp,
0x01
out PINB,
mask ;toggle SCK (set clock = high)
out PINB,
mask ;toggle SCK (set clock = low)
.endmacro
sd_read:
;receive one byte from SD card
sbi PORTB,
SD_MOSI ;set MOSI = high
sbrs flags,
fFastSPI
rjmp sd_read_slow
;fast SPI read
ldi mask,
1<<SD_SCK ;mask for clock output toggle (write to PINB)
sd_read_fast_bit ;bit 7
sd_read_fast_bit ;bit 6
sd_read_fast_bit ;bit 5
sd_read_fast_bit ;bit 4
sd_read_fast_bit ;bit 3
sd_read_fast_bit ;bit 2
sd_read_fast_bit ;bit 1
sd_read_fast_bit ;bit 0
ret
sd_read_slow:
;slow SPI read (< 400 kHz)
ldi temp2,
8
sd_read_loop:
rcall spi_delay
lsl temp
sbic PINB,
SD_MISO
ori temp,
0x01
sbi PORTB,
SD_SCK
rcall spi_delay
cbi PORTB, SD_SCK
dec temp2
brne sd_read_loop
ret
;--------------------
sd_clock:
;send 8 SPI clock pulses (dummy
read)
sbi PORTB,
SD_MOSI ;set MOSI = high
sbrs flags,
fFastSPI
rjmp sd_clock_slow
;fast clock pulses
ldi mask,
1<<SD_SCK ;mask for clock output toggle (write to PINB)
out PINB,
mask ;bit 7
out PINB,
mask
out PINB,
mask ;bit 6
out PINB,
mask
out PINB,
mask ;bit 5
out PINB,
mask
out PINB,
mask ;bit 4
out PINB,
mask
out PINB,
mask ;bit 3
out PINB,
mask
out PINB,
mask ;bit 2
out PINB,
mask
out PINB,
mask ;bit 1
out PINB,
mask
out PINB,
mask ;bit 0
out PINB,
mask
ret
sd_clock_slow:
;slow clock pulses (< 400
kHz)
ldi temp2,
8
sd_clock_loop:
rcall spi_delay
sbi PORTB,
SD_SCK
rcall spi_delay
cbi PORTB,
SD_SCK
dec temp2
brne sd_clock_loop
ret
;--------------------
spi_delay:
;10 cycles delay
;(rcall = 3 cycles)
rjmp PC+1
;2 cycles
nop ;1 cycle
ret ;4 cycles
;===============================================================================
;debug_byte:
; push temp
; push temp2
; ldi temp2,
8
;debug_byte_loop:
; sbi PORTB,
1
; sbrc temp,
7
; rcall debug_byte_delay
; cbi PORTB,
1
; rcall debug_byte_delay
; lsl temp
; dec temp2
; brne debug_byte_loop
; pop temp2
; pop temp
; ret
;
;debug_byte_delay:
; rjmp PC+1
; rjmp PC+1
; rjmp PC+1
; rjmp PC+1
; rjmp PC+1
; rjmp PC+1
; rjmp PC+1
; rjmp PC+1
; rjmp PC+1
; rjmp PC+1
; ret