Source file: /~heha/basteln/Auto/Renault Mégane/mp3c.zip/mp3c.S

/* 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-80