/* Programm zur Steuerung des MP3-Spielers mit GPD2856 innerhalb des
CD-Wechsler-Emulators ADP064 für das Renault-Autoradio von Viktor.
Also ein Befehlsdekoder RS232 → Ausgänge 1-aus-n
Ziel: Track +/- sowie Start/Stop vom Lenkrad aus.
Controller: ATtiny13 @ 1,2 MHz (Fuses unverändert, Low-Power)
Ein Controller mit eingebauter serieller Schnittstelle wäre für diesen
Popelkram schlichtweg Overkill — und passt nicht ins Gehäuse
des CD-Wechsler-Emulators (in dem bereits der MP3-Dekoder Platz fand).
Hardware:
1 PB5 !RESET Reset
2 PB3 !(Prev/V--)
3 PB4 !5P
4 GND
5 PB0 !(P/P/Mode)
6 PB1 INT0 RxD
7 PB2 !(Next/V++)
8 Ucc
Datenbytes an RxD (9600,8,e,1; Annahme)
http://tlcdcemu.sourceforge.net/protocol.html
next 3D id 02 17 01 xr
ffwd 3D id 02 20 0A xr
prev 3D id 03 22 01 02 xr
rewind 3D id 02 21 0A xr
play 3D id 01 13 xr
pause 3D id 01 1C xr
stop 3D id 01 19 xr
Ausgewertet wird:
* Frame-Start 0x3D
* Länge >= 1
* XOR-Prüfsumme "xr" korrekt
* Byte 3 zur Selektion
Der Transaktionszähler "id" wird ignoriert.
Registerbelegung:
R0..R15 Empfangspuffer
R16 tmp
R17 Schieberegister, serielles Datenbyte
R18 FCS = XOR-Wert aller Bytes des Rahmens
R19 Kopie Datenbyte für Frame-Auswertung
R20 tmp2
R24 Bitzähler für RS232
R25 Parität (Bit 0)
X Tastendruck-Countdown-Zähler
Y Intercharacter-Timeout-Zähler
Z Datenzeiger
Programmablauf:
Das Hauptprogramm initialisiert den Zähler auf genau 9600 Hz Überlauffrequenz,
aktiviert die Startbiterkennung und legt sich schlafen.
Eine Startflanke synchronisiert den Zähler,
damit die Datenbits einigermaßen in der Mitte der Bitzeit abgegriffen werden.
Die Timer-ISR kümmert sich um den Rest: Bit-Empfang, Byte-Empfang,
Frame-Empfang, Frame-Auswertung und Steuerung der „Tasten“ des MP3-Players.
Auf Register-Rettung wurde verzichtet, da sich nichts überlappen kann.
Auch braucht das Programm keinen RAM und würde nur einen ATtiny11 benötigen;
der CTC-Modus des Zählers ließe sich auch per Software erreichen.
*/
#define __SFR_OFFSET 0
#include <avr/io.h>
.section .fuse,"a",@progbits // --set-section-flags=.fuse="alloc,load" ersparen
.byte 0b01101010 // unverändert
.byte 0b11111111 // unverändert
.section .signature,"a",@progbits
.byte SIGNATURE_0,SIGNATURE_1,SIGNATURE_2
.text
#define F_CPU 1200000
.macro outi port,val
ldi r16,\val
out \port,r16
.endm
rjmp main
// Startbit (Hi-Lo-Flanke): Timer starten
rjmp int0
.org 0x06
rjmp t0ovl // Nur während Kalibrierung aktiv!
.org 0x0c
// 9600 Hz: 1. Bit fangen und Seriell-Parallel-Wandlung
// 2. Intercharacter-Timeout erfassen
// 3. Tastendruckzähler-Countdown
tst r24
breq 2f // Null: Nichts tun außer Zeitmessungen
in r16,PINB // RxD-Pin (Bit 1) lesen
lsr r16 // nach Bit 0
dec r24
breq 6f // Ende, Stoppbit prüfen
cpi r24,10
breq 5f // Startbit prüfen
eor r25,r16 // Parität generieren
cpi r24,1
breq 4f // Parität prüfen
lsr r16
ror r17 // „normales“ Bit einschieben
rjmp 3f
4: sbrc r25,0 // Gerade Parität?
rjmp init_uart // Empfangene Bits ignorieren
rjmp 3f
5: sbrc r16,0 // Startbit ist Null?
rjmp init_uart
rjmp 3f
// Timeout für Zeit zwischen 2 Zeichen (20 ms = 192 Interrupts)
2: sbiw YL,0
breq 3f
sbiw YL,1
brne 3f
clr ZL // Neuen Rahmen empfangen lassen
// Timeout für Tastendruck (100 ms = 960 Interrupts)
3: sbiw XL,0
breq 4f // Null: Zähler inaktiv
sbiw XL,1
brne 4f // Noch nicht Null: Keine Aktion
cbi DDRB,0 // alle Ausgänge (außer 5P) inaktiv schalten
cbi DDRB,2
cbi DDRB,3
4: reti
6: rcall init_uart
mov r19,r17
sbrs r16,0 // Stoppbit ist Eins?
rjmp 9f // BREAK erkannt: Rahmen verwerfen
ldi YH,hi8(192)
ldi YL,lo8(192) // Inter-Character-Timeout erfassen
//Hier ist ein Byte (R19) fertig: untersuchen!
tst ZL // Rahmenempfang im Gange?
brne 6f
cpi r19,0x3D // Startbyte?
brne 3b // falsches Startbyte ignorieren
clr r18 // FCS-Byte initialisieren
6: st Z+,r19 // abspeichern
eor r18,r19 // FCS-Byte aktualisieren
cpi ZL,3
brcs 3b // Noch kein Längenbyte
brne 6f // Jetzt Längenbyte
cpi r19,12 // Länge < 12?
brcc 9f // Länge zu groß, Rahmen verwerfen
6: mov r20,r2 // abgespeicherte Länge
subi r20,-4 // Bei 0 kommen insg. vier Byte, bei 1 fünf usw.
cp ZL,r20
brcs 3b // Weitermachen
tst r18
brne 9f // Rahmenfehler: Falsche CRC
//Hier ist ein fehlerfreier Rahmen fertig: untersuchen
tst r2
breq 9f // Leeres Payload
mov r20,r3
ldi r19,0x11 // Bit 0 (Play/Pause)
cpi r20,0x13 // START_TRACK
breq 6f
cpi r20,0x1C // PAUSE
breq 6f
ldi r19,0x14 // Bit 2 (Next)
cpi r20,0x17 // NEXT_TRACK
breq 6f
cpi r20,0x20
breq 6f
ldi r19,0x18 // Bit 3 (Prev)
cpi r20,0x22 // PREV_TRACK
breq 6f
cpi r20,0x21
breq 6f
clr r19
cpi r20,0x19 // STOP_PLAY
brne 9f
6: out DDRB,r19 // einen der Ausgänge aktivieren
ldi XH,hi8(960)
ldi XL,lo8(960) // 1/10s lang „Taste gedrückt“ halten
9: clr ZL
rjmp 3b
t0ovl:
tst r24 // Zeitfenster abgelaufen?
breq 1f // ja, nichts tun
dec r24
inc r20 // tmp2 als High-Teil von T0 zur „Messung“ der Baudrate
1: reti
// erkannte Startflanke
int0:
in r16,TCCR0A
sbrs r16,1 // im CTC-Modus?
rjmp 1f // noch nicht: Kalibrieren!
clr r25 // Bit 0 zur Paritätsprüfung
out GIMSK,r25 // kein INT0 mehr bis zum Stoppbit
ldi r24,11 // 11 Bits abzählen
ldi r16,84
0: out TCNT0,r16 // Timer synchronisieren und Startbit noch mal abtasten lassen
outi TIFR0,4 // evtl. Timerinterrupt löschen
reti
1: // Baudratengenerator einstellen (OSCCAL ist viel zu ungenau!)
in r16,TCNT0
clr r17
cpi r16,4+2+4 // Takte bis zum Lesen von TCNT0
adc r17,r20 // High-Teil korrigieren, wenn Zähler inzwischen übergelaufen
movw r18,YL
movw YL,r16 // Capture setzen
tst r24
ldi r24,3 // Zeitfenster setzen
breq 1f // es lag noch kein Capture vor, oder es ist zu lange her
sub r16,r18
sbc r17,r19 // Differenz
cpi r16,lo8(200)
ldi r18,hi8(200)
cpc r17,r18
brcs 1f // zu klein
cpi r16,lo8(300)
ldi r18,hi8(300)
cpc r17,r18
brcc 1f // zu groß
lsr r17
ror r16
dec r16
out OCR0A,r16 // nominell 125 Takte ergibt 9600 Baud
outi TCCR0A,0x02 // ab jetzt CTC-Modus
outi TIMSK0,0x04 // Interrupts mit 9600 Hz
outi GIMSK,0
ldi r24,9 // noch 9 Bits einlesen
ldi r25,1 // Bit 0 setzen
ldi r17,0x80 // Bit 0 war 1 (vom Startbyte 0x3D)
ldi r16,99
rjmp 0b
// Die serielle Schnittstelle wartet danach auf das nächste Startbit
init_uart:
clr r24 // Empfangsbitzähler nullsetzen
ldi r20,0x40
out GIFR,r20 // erkannte Pegelwechsel löschen
out GIMSK,r20 // Interrupt freigeben
1: reti // implizites SEI
main: // SPL ist bereits initialisiert
outi MCUCR,0x22 // Sleep (Idle) aktivieren + Interrupt INT0 bei fallender Flanke
outi TCCR0B,0x01 // Vorteiler 1
outi TIMSK0,0x02 // Interrupts bei Überlauf
clr XL // Tastendruck-Zeitzähler nullsetzen
clr XH
clr YL
clr YH
clr ZL // Der Nachrichtenempfang geht in die Register R0..R15
clr ZH
rcall init_uart
1: sleep
rjmp 1b
| Detected encoding: UTF-8 | 0
|