Source file: /~heha/basteln/Haus/Telefon/Mithören-ISDN/Firmware.zip/ATmega32U4/s0isr.S

#define __SFR_OFFSET 0
#include <avr/io.h>

/*************************
 * Interrupts für S0-Bus *
 *************************/
.global INT0_vect,INT1_vect,TIMER0_COMPA_vect,s0Init

#define D_EP 4	// Interrupt 16 Byte doppelt gepuffert
#define B_EP 3	// Isochron 64 Byte doppelt gepuffert

/*
Registerverwendung:
R2	usbCfg [5] = Phase des letzten 1-Bits
R3	rettet SREG
R4	rettet ZL
R5	rettet ZH
R6	Bit-Eimer Hören (Innere Doppelader, NT->TE)
R7	Bit-Eimer Sprechen (Äußere Doppelader, TE->NT)
R8	Wort-Adresse in UTAB = Bitnummer (Hör-Kanal, 0x74/2 + 0..47)
R9	temporär (rettet UENUM)
R10	Einsenzähler D-Kanal (um Stopfbits zu entfernen)
R11	[7:1] = Bitzähler D-Kanal, [0] = D-Datenrahmen aktiv
R12	Bit-Eimer D-Kanal
R13..R15	reserviert für E-Kanal
Für getrennte Synchronisation ist keine Zeit.
Synchronisiert wird auf „Hören“, ein Betrieb ohne Anzapfung der (äußeren)
Sprech-Doppelader ist daher möglich und auch vorgesehen.
D-Kanal: Datenblock beginnt und endet mit 0x7E,
innerhalb der Daten werden nach 5 1-Bits ein 0-Bit eingeschoben (Bitstopfen)
CPU-Taktfrequenz: 16 MHz
Zeiten:	1 Bit = 192 kHz = 5,2 µs = 83,3 Takte
	1 Frame = 4 kHz = 250 µs = 4000 Takte
	1 Byte (pro Kanal) = 8 kHz = 125 µs = 2000 Takte
	1 Sample = 8 kSa/s × 2 Kanäle × 16 Bit = 32 kByte/s
	D-Kanaldaten: max. 2 KByte/s, bei 16-Byte-Endpoints
		USB-Interrupts mit ~ 120 Hz (8 ms)
*/

.macro SAVEPHASE
	brcs	7f
	 bst	r2,5
7:
.endm

.section .noinit
// Sample-Speicher
s1:	.ds.b	1	// Sprechen B1 (Äußere Doppelader, TE->NT)

// „utab“ darf keine 200h-Grenze überstreichen.
.section .text			//	Hören	Sprechen
utab:	rjmp	syncframe	//1	0+!	D-
	rjmp	ignore		//2	0-	L+
	rjmp	h_bit		//3	B1-!	0+!
	rjmp	h_bit		//4	B1	0-
	rjmp	h_bit		//5	B1	B1-!
	rjmp	h_bit		//6	B1	B1
	rjmp	h_bit		//7	B1	B1
	rjmp	h_bit		//8	B1	B1
	rjmp	h_bit		//9	B1	B1
	rjmp	h_bit		//10	B1	B1
	rjmp	in_e		//11	E	B1
	rjmp	in_d_s1		//12	D	B1
	rjmp	in_a		//13	A	L+
	rjmp	usbH		//14	0	D-
	rjmp	usbS		//15	1	L+
	rjmp	usbE		//16	B2	0-
	rjmp	tonH		//17	B2	0+
	rjmp	tonS		//18	B2	B2
	rjmp	ignore		//19	B2	B2
	rjmp	ignore		//20	B2	B2
	rjmp	ignore		//21	B2	B2
	rjmp	ignore		//22	B2	B2
	rjmp	ignore		//23	B2	B2
	rjmp	in_e		//24	E	B2
	rjmp	in_d		//25	D	B2
	rjmp	ignore		//26	0	L+
	rjmp	h_bit		//27	B1	D-
	rjmp	h_bit		//28	B1	L+
	rjmp	h_bit		//29	B1	B1-
	rjmp	h_bit		//30	B1	B1
	rjmp	h_bit		//31	B1	B1
	rjmp	h_bit		//32	B1	B1
	rjmp	h_bit		//33	B1	B1
	rjmp	h_bit		//34	B1	B1
	rjmp	in_e		//35	E	B1
	rjmp	in_d_s1		//36	D	B1
	rjmp	usbH		//37	0	L+
	rjmp	usbS		//38	B2	D-
	rjmp	usbE		//39	B2	L+
	rjmp	tonH		//40	B2	B2-
	rjmp	tonS		//41	B2	B2
	rjmp	ignore		//42	B2	B2
	rjmp	ignore		//43	B2	B2
	rjmp	ignore		//44	B2	B2
	rjmp	ignore		//45	B2	B2
	rjmp	in_e		//46	E	B2
	rjmp	in_d		//47	D	B2
	rjmp	null		//48	L+	L+

.section .text

// H-L-Interrupts zur Synchronisation an S0
// Zeitforderung: < 83 Takte
// Geradeaus-Durchlauf = 57 Takte
// Worst-Case = 78 Takte (überschlagen, sicherlich etwas weniger)
INT1_vect:
INT0_vect:
	in	r3,SREG
	movw	r4,ZL
	ldi	ZL,1
	out	TCCR0B,ZL	// Timer0 starten (falls nicht laufend)
	in	ZL,TCNT0
	subi	ZL,-90		// „Wecker“ fürs nächste Bit setzen
	out	OCR0A,ZL	// (Ein Flankeninterrupt kommt ggf. eher)
	ldi	ZL,0x02
	out	TIFR0,ZL	// eventuellen COMPA-Interrupt löschen!
	rjmp	1f		//(11)

// Timer0-Interrupts zum Fangen der Datenbits
TIMER0_COMPA_vect:
	in	r3,SREG
	movw	r4,ZL
	in	ZL,OCR0A
	subi	ZL,-84		// 16 MHz / 192 kHz = 83,3 (CPU-Takte!!)
	out	OCR0A,ZL	//(5) „Wecker“ erneut stellen
1:	//ldi	ZL,0x21
	//out	EIFR,ZL		// eventuelle Flanken-Interrupts löschen
	in	ZL,PIND		// Pegel einfangen (Bits 0,1,4)
// jetzt Ternärwert Hören und Binärwert Sprechen in ZL: `...s..hh`
	lsl	r7
	sbrc	ZL,4
	 inc	r7		// Sprechbit einsetzen
	ori	ZL,0xFC		// `111111hh`
	ldi	ZH,1
	add	ZH,ZL		// C=1 wenn beide h = 1 (Ruhepegel), sonst C=0
//Sprungverteiler:
	bld	ZL,0		// Zustand von PD0 nach T retten
	mov	ZL,r8
	ldi	ZH,pm_hi8(utab)	// = 0
	ijmp			//(13)

// Rahmenanfang detektieren bzw. verifizieren
syncframe:
	brcs	stop		// muss 0-Bit sein, also eine Flanke haben
	bst	ZH,5		// Aktuelle Phase
	eor	ZH,r2		// mit vorheriger Phase vergleichen
	bst	r2,5		// und aktuelle Phase sichern
	sbrc	ZH,5		// Aufeinanderfolgende ++ oder --?
	 rjmp	stay		// nein
	cbi	PORTD,5		// Tx-LED („Rahmen“) ein
	ldi	ZL,0x01
	sts	TCCR4B,ZL	// PWM-Generator starten
	sbi	DDRB,6		// Tonausgabe Hören ein
	sbi	DDRD,7		// Tonausgabe Sprechen ein
	rjmp	next

// Ein B1-Bit (Hören) steht zur Verfügung
h_bit:	SAVEPHASE
	rol	r6
	rjmp	next

// Echo-Bit (Hören) verfügbar: Nicht auswerten
in_e:
// Irgendein Bit verfügbar: Nicht auswerten
ignore:
	SAVEPHASE
next:	inc	r8		// Bitnummer im Rahmen inkrementieren
stay:	movw	ZL,r4
	out	SREG,r3
	reti			//(9) (ab „ignore“)

stop:	// Timer+Gespräch STOP, Bus idle
	ldi	ZL,0
	out	TCCR0B,ZL	// Timer0 anhalten (Flanke muss kommen)
	sbi	PORTB,0		// Rx-LED („Gespräch“) aus
	cbi	DDRD,7		// Tonausgabe Sprechen hochohmig
	cbi	DDRB,6		// Tonausgabe Hören hochohmig
	sts	TCCR4B,ZL	// PWM-Generator stoppen
	rjmp	stay
	
gstop:	// Gespräch STOP
	sbi	PORTB,0		// Rx-LED („Gespräch“) aus
	rjmp	next

in_a:	brcs	gstop
	bst	r2,5
	cbi	PORTB,0		// Rx-LED („Gespräch“) ein
	rjmp	next

// B1 (Hören)	
usbH:	SAVEPHASE
	sbrs	r2,2		// USB-Soundkanal aktiv?
	 rjmp	next		// nein, kein USB
	lds	r9,UENUM	// retten
	ldi	ZL,B_EP
	sts	UENUM,ZL	// EP3 (doppelt gepuffert)
#ifdef LAW_AUDIO
	mov	ZL,r6
	sts	UEDATX,ZL	// Hören (links)
#else
	mov	ZL,r6
	ldi	ZH,hi8(law)
	lpm	r7,Z
	sts	UEDATX,r7
	ldi	ZH,hi8(law+256)
	lpm	r7,Z
	sts	UEDATX,r7
#endif
	sts	UENUM,r9
	rjmp	next

// wie oben jedoch fü den Sprechkanal
usbS:	SAVEPHASE
	sbrs	r2,2
	 rjmp	next
	lds	r9,UENUM
	ldi	ZL,B_EP
	sts	UENUM,ZL	// EP3 (doppelt gepuffert)
#ifdef LAW_AUDIO
	lds	ZL,s1
	sts	UEDATX,ZL	// Sprechen (rechts)
#else
	lds	ZL,s1
	ldi	ZH,hi8(law)
	lpm	r7,Z
	sts	UEDATX,r7
	ldi	ZH,hi8(law+256)
	lpm	r7,Z
	sts	UEDATX,r7
#endif
	sts	UENUM,r9
	rjmp	next

// SOF-Interrupt testen und Paket absetzen
usbE:	SAVEPHASE
	sbrs	r2,2
	 rjmp	next
	lds	r9,UENUM
	ldi	ZL,B_EP
	sts	UENUM,ZL	// EP3 (doppelt gepuffert)
	lds	ZL,UDINT
	sbrs	ZL,SOFI
	 rjmp	7f
	ldi	ZL,~(1<<SOFI)
	sts	UDINT,ZL
	ldi	ZL,0
	sts	UEINTX,ZL
7:	sts	UENUM,r9
	rjmp	next		//max. (57)

// Ende des 48-Byte-Rahmens (der Hörleitung)
null:	ldi	ZL,pm_lo8(utab)
	mov	r8,ZL		// Neues Frame: Bitzähler = 0
	sbi	PORTD,5		// Tx-LED („Rahmen“) aus
	rjmp	stay

// D-Bit (Hören) verfügbar UND B1-Byte (Sprechen) fertig
in_d_s1:
	sts	s1,r7		// abspeichern
// D-Bit (Hören) verfügbar
in_d:	SAVEPHASE
	movw	ZL,r10
	sbrc	ZH,0		// Bitzähler aktiv?
	 rjmp	1f		// beim Einlesen und Unstuff
	dec	ZL		// Einsenzähler
	brcs	9f		// weiter nur Einsen zählen
	ldi	ZL,7
	brne	9f		// Einsenzähler war ungleich Null
	sbr	ZH,1		// Start-of-Frame (01111110) erkannt
	ldi	ZL,6
	rjmp	9f
1:	dec	ZL
	brcs	2f		// 1-Bit ohne weitere Beachtung
	ldi	ZL,6
	breq	9f		// Nach 5 Einsen das 0-Bit ignorieren
2:	rol	r12		// Datenbit einschieben (verändert Z)
	tst	ZL		// Null bei 6 aufeinanderfolgenden Einsen
	breq	8f		// bei ....0111111 anhalten (Stuff-Fehler)
	subi	ZH,-2		// Bits zählen
	brhs	9f		// fertig wenn Byte noch nicht voll
	movw	r10,ZL
	lds	r9,UENUM	// retten
	ldi	ZH,D_EP		// EP4 für D-Kanal
	sts	UENUM,ZH
	sts	UEDATX,r12	// Datenbyte übergeben
	brcs	2f
	ldi	ZH,0
	sts	UEINTX,ZH	// Commit
2:	sts	UENUM,r9
	rjmp	next		//(34)
8:	// USB-Datenblock zum Host absenden (Commit) und fertig
	lds	r9,UENUM	// retten
	ldi	ZH,D_EP		// EP4 für D-Kanal
	sts	UENUM,ZH
	ldi	ZH,0		// hier Doppelfunktion: Bitzähler deaktivieren
	sts	UEINTX,ZH
	sts	UENUM,r9
	ldi	ZL,0
9:	movw	r10,ZL
	rjmp	next		//(28)

// Tonausgabe linker Kanal: Hören
tonH:	SAVEPHASE
	mov	ZL,r6
	ldi	ZH,hi8(lawPWM+256)
	lpm	r7,Z
	sts	TC4H,r7
	ldi	ZH,hi8(lawPWM)
	lpm	r7,Z
	sts	OCR4B,r7
	rjmp	next
// Tonausgabe rechter Kanal: Sprechen
tonS:	SAVEPHASE
	lds	ZL,s1
	ldi	ZH,hi8(lawPWM+256)
	lpm	r7,Z
	sts	TC4H,r7
	ldi	ZH,hi8(lawPWM)
	lpm	r7,Z
	sts	OCR4D,r7
	rjmp	next

// Von C aufrufbare Initialisierungsroutine: Alles außer Ports
s0Init:	ldi	r24,pm_lo8(utab)
	mov	r8,r24		// mit syncframe beginnen lassen
	clr	r11
	ldi	r24,84
	out	OCR0A,r24	// 83,3 CPU-Takte (16 MHz) pro Bit (192 kHz)
	ldi	r24,2
	out	TCCR0A,r24	// CTC-Modus
	sts	TIMSK0,r24	// Output-Compare-Interrupt aktivieren (Timer ist noch gestoppt)
	ldi	r24,0x0A
	sts	EICRA,r24	// Fallende Flanken lösen Interrupts aus
	ldi	r24,3
	out	EIMSK,r24	// INT0+1
	ldi	r24,0x34
	out	PLLFRQ,r24	// PLL mit 48 MHz für USB und Timer4
	ldi	r24,0x21
	sts	TCCR4A,r24	// HS-Timer4 initialisieren
	ldi	r24,0x01
	sts	TCCR4B,r24	// DEBUG, PWM-Ausgang aktiviert sich mit erstem Rahmenstart
	ldi	r24,0x29
	sts	TCCR4C,r24
	sts	TCCR4D,r1
	ldi	r24,0x40
	sts	TCCR4E,r24	// Erweiterter (11-Bit-)Modus
	ldi	r25,hi8(1023)	// 3
	ldi	r24,lo8(1023)	// 255
	sts	TC4H,r25
	sts	OCR4C,r24
	sts	OCR4B,r24	// DEBUG
	sts	OCR4D,r24	// DEBUG
	ret

.section .progmem
space:
// Die law-Tabellen müssen an 100h-Grenzen beginnen.
// Das muss über das Linkerskript realisiert werden.
	.ds.b	256-0xAC	// .align geht hier nicht
// Lookup-Tabelle für „lineare“ Ausgabe der A-Law-Audiodaten
// „glättet“ 8-Bit-Wert in linearen 16-Bit-Wert für USB,
// rechtsschiebbar auf 11 Bit für PWM-D/A-Wandler
// Zur einfacheren Adressierung werden High- und Low-Teil
// in getrennten Tabellen hintereinander abgelegt.

.macro alaw rshift center
.set i,0
.rept 256
.set j,i^0xD5		// 0x55-XORung + Vorzeichenwechsel
.set e,j>>4&7		// Exponent 0..7
.set m,j&0x0F		// Mantisse
.set m,m<<10|m<<6|m<<2	// 00MmmmMmmmMmmm00
.if e
.set m,m|0x4000		// 01MmmmMmmmMmmm00
.else
.set e,1		// „denormalisierte“ Zahl ohne führende 1
.endif
.set m,m>>(7-e)	// 01MmmmMmmmMmmm00 ... 00000000MmmmMmmm
.if j&0x80		// Vorzeichen
	.byte (\center-1-m)>>\rshift&0xFF
.else
	.byte (\center+m)>>\rshift&0xFF
.endif
.set i,i+1
.endr
.endm

#ifndef LAW_AUDIO
// für Audio-Pipe von -32767 .. +32767 (16 Bit, 13 genutzt)
.type law,@object
law:	alaw	0	// Low-Teile
	alaw	8	// High-Teile
#endif
// für PWM-Analogausgabe von 0 .. 2047 (11 Bit)
.type lawPWM,@object
lawPWM:	alaw	5 32768	// Low-Teile
	alaw	13 32768// High-Teile
Detected encoding: UTF-80