Source file: /~heha/Mikrocontroller/Sternhimmel/Schnecke.zip/PWM32.S

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

/*****************
 * Tobias & Barbara Lucas
 * LED-Schnecke mit 54 PWM-gesteuerten 3-Farb-LEDs
 * ATmega16 mit Multiplexsteuerung 18 Anoden x 9 Katoden = 54*3
 * Tabweite: 8, Zeileneden: CR, Kodierung: UTF-8
Funktionsprinzip:
Der Controller gibt an 18 Ausgängen ein binär gestuftes 13-bit-PWM-Signal aus,
mit 9 weiteren Ausgängen werden die („gemeinsamen“) Katoden weitergeschaltet.
Vier LSB-Pulsweiten der 13 bit werden per Assemblerbefehlsfolge generiert,
die größeren Abstände mittels Timer1-Compare-Interrupt.
Mit 16 MHz, 13 bit und 9 Katoden kommt man auf 217 Hz Wiederholfrequenz.
Zentrale Rechenroutine ist das Stürzen einer Bit-Matrix!
Um Flackereffekten vorzubeugen, während der Ausgabe des MSBs.
Steuermöglichkeit:
	RxD, INT0 für RS232 (nur Eingabe) oder USB;
	drei Analogeingänge für Potenziometer.

Hardware:
	PORTA: 5 Katoden (keine Anoden)
37	0	(ADC0)	K0
36	1	(ADC1)	K1
35	2	(ADC2)	K2
34	3	(ADC3)	K3
33	4	(ADC4)	K4
32	5	ADC5	Potenziometer (Programmauswahl)
31	6	ADC6	Potenziometer (Grundhelligkeit)
30	6	ADC7	Potenziometer
	PORTB: 8 Anoden
40	0	(T0)	A0-R
41	1	(T1)	A1-G
42	2	(AIN0)	A2-B
43	3	(AIN1)	A3-R
44	4	(!SS)	A4-G
1	5	(MOSI)	A5-B
2	6	(MISO)	A6-R
3	7	(SCK)	A7-G
	PORTC: 4 Anoden + 4 Katoden
19	0	(SCL)	A14-B
20	1	(SDA)	A15-R
21	2	(TCK)	A16-G
22	3	(TMS)	A17-B
23	4	(TDO)	K8
24	5	(TDI)	K7
25	6	(TOSC1)	K6
26	7	(TOSC2)	K5
	PORTD: 6 Anoden
9	0	RxD	Serielle Steuerung, USB D- (wegen Pullup 10 kΩ)
10	1	(TxD)	A8-B
11	2	INT0	USB D+
12	3	(INT1)	A9-R
13	4	(OC1B)	A10-G
14	5	(OC1A)	A11-B
15	6	(ICP1)	A12-R
16	7	(OC2)	A13-G

Beschlagnahmte Ressourcen:
* Timer1 komplett für's Timing, alle zugehörigen ISRs
* Register R2..R7 sowie evtl. YH:YL für schnelle Interruptbearbeitung
*/

/*****************************
 * Binär gestufte 13-bit-PWM *
 *****************************/
.global bzt,repid3,lht,repid2,adu

.section .bss
abt:	.ds.b	13*3	// 13 Tripel Anoden-Bit-Tabelle für Compare-ISR (PORTB,D,C)
bzt:	.ds.b	7*3	// Bitzeit-Tabelle, enthält nächsten Compare-Wert (L,H,x)
	// Obwohl diese Tabelle nur Konstanten beinhaltet, muss diese im RAM sein,
	// zudem in der Nähe von <abt>, um den Zugriff zu erleichtern.
kata:	.ds.b	1	// Katoden an PortA, A/D-Eingänge = 0
katc:	.ds.b	1	// Katoden an PortC, Anoden = 1

repid3:	.ds.b	1	// Report-ID (=3), auch als Guard verwendbar
lht:	.ds.b	54*3	// 162 Byte LED-Helligkeits-Tabelle
repid2:	.ds.b	1
adu:	.ds.b	3	// A/D-Wandler-Werte, Index 0 = Helligkeit (R2)

#define	CurKatode r7	// Gerade aktiver Katodentreiber (0..8)
// ebenfalls „giftig“: R2..R6 (ISR-Temp)

.section .text
/*
 Timer1-Vergleich A: Anoden umschalten, neuen Vergleichswert laden
 Y = Zeiger in Anoden-Bit-Tabelle (Y ist bei avr-gcc der Stapelrahmenzeiger)
*/
.global TIMER1_COMPA_vect
TIMER1_COMPA_vect:
// ISR Compare A: höhere Bitwertigkeiten ausgeben
//				Längen	PORTB	PORTD	PORTC
#ifdef DOSEI
	sei			//	|	|	|
#endif
#ifdef SAVEY
	push	YL		// 2	|	|	|
	push	YH		// 2	|	|	|
	movw	YL,r2		//	|	|	|
#endif
	ld	r2,Y+		// 2	|	|	|
	ld	r3,Y+		// 2	|	|	|
	ld	r4,Y+		// 2	|	|	|
	out	PORTB,r2	//	128+	|	|
	out	PORTD,r3	//	|	128+	|
	out	PORTC,r4	//	|	|	128+
	ldd	r2,Y+(bzt-(abt+8*3)) //|	|	|
	ldd	r3,Y+(bzt-(abt+8*3)+1)//|	|	|
	out	OCR1AH,r3	//	|	|	|	Hier sollte es nicht passieren …
	out	OCR1AL,r2	//	|	|	|	… dass OCR1A ≤ TCNT1 gerät!
#ifdef SAVEY			//				Sonst bleiben die LEDs stehen …
	movw	r2,YL		//	|	|	|	… bis zum nächsten Katodenwechsel.
	pop	YH		// 2	|	|	|	Dazu muss die Interruptsperrzeit …
	pop	YL		// 2	|	|	|	… kurz genug sein, was mit V-USB nicht …
#endif				//				… zu machen ist. Da flackert's eben!
	reti			//gesamt: 35 (25) Takte
/*
 Timer1-Vergleich B: Anoden-Bit-Tabelle neu berechnen,
 Y rücksetzen, Katode umschalten vorbereiten
*/
.global TIMER1_COMPB_vect
TIMER1_COMPB_vect:
	sei
	push	r0
	in	r0,SREG
	 rcall	calc_abt
	out	SREG,r0
	pop	r0
	reti

/*
 Timer1-Capture (hier: Überlauf): Katode umschalten, einige LSB ausgeben
 Die für diese ISR reservierten 5 Register R2..R6 sind bereits vorbelegt,
 Y zeigt auf den Anfang von <abt>.
*/
.global TIMER1_CAPT_vect
TIMER1_CAPT_vect:
//		Verschachtelte Längen	PORTB	PORTD	PORTC  R23456
#ifdef DOSEI
	sei			//	|	|	|
#endif
#ifdef SAVEY
	push	YL		//	4096	4096	4096	*****	Y retten (wird bisweilen von AVRGCC gebraucht)
				//	|	|	|		2-Takt-Befehl
	push	YH		//	|	|	|
				//	|	|	|
	nop			//	|	|	|
#endif
	ldi	YH,0xFF		//	|	|	|
	ldi	YL,0xF0		//	|	|	|
	out	PORTB,YH	//	-	|	|		Anoden AUS
	out	PORTD,YH	//	-	-	|		Anoden AUS
	out	PORTC,YL	//	-	-	-		Anoden und Katoden AUS
	ldi	YL,lo8(abt)	//	-	-	-		Zeit zum Gates umladen lassen
	out	PORTC,r2	//	-	-	-	-****	Katode umschalten
	out	PORTA,r3	//	-	-	-	--***	Katode umschalten
	out	PORTB,r4	//	32	-	-	---**	Anoden EIN
	out	PORTD,r5	//	|	16	-	----*	Anoden EIN
	out	PORTC,r6	//	|	|	8	-----	Anoden EIN (Katode unverändert)
	ldi	YH,hi8(abt)	//	|	|	|
	ldd	r2,Y+0*3+2	//	|	|	|	*----	Register nachladen
				//	|	|	|
	ldd	r3,Y+1*3+2	//	|	|	|	**---	Prinzipiell könnte man …
				//	|	|	|		… auch LDS verwenden, für mehr Kode …
	ldd	r4,Y+4*3+2	//	+	|	|	***--	… könnte man noch ein paar …
				//	|	+	|		… Takte zum Retten von Y einsparen.
	out	PORTC,r2	//	|	|	1	-**--
	out	PORTC,r3	//	|	|	2	--*--
	nop			//	|	|	|
	out	PORTC,r4	//	|	|	16	-----
	nop			//	|	|	|
	ldd	r2,Y+5*3+1	//	|	|	|	*----
				//	+	|	|
	out	PORTD,r2	//	|	32	|	-----
	nop			//	|	|	|
	ldd	r2,Y+2*3+2	//	|	|	|	*----
				//	|	|	|
	ldd	r3,Y+5*3+2	//	|	|	+	**---
				//	|	|	|
	ldd	r4,Y+0*3+0	//	|	|	|	***--
				//	+	|	|
	ldd	r5,Y+2*3+0	//	|	+	|	****-
				//	|	|	|
	ldd	r6,Y+2*3+1	//	|	|	|	*****
				//	|	|	|
	out	PORTC,r2	//	|	|	4	-****
	ldd	r2,Y+1*3+0	//	|	|	|	*****
				//	|	|	|
	out	PORTB,r2	//	2	|	|	-****
	out	PORTC,r3	//	|	+	32	--***
	out	PORTB,r4	//	1	|	|	---**
	out	PORTB,r5	//	4	|	|	----*
	nop			//	|	|	|
	ldd	r2,Y+3*3+0	//	|	|	|	*---*
				//	|	|	|
	out	PORTB,r2	//	8	|	|	----*
	nop			//	|	|	|
	ldd	r2,Y+4*3+0	//	|	+	+	*---*
				//	|	|	|
	ldd	r3,Y+0*3+1	//	|	|	|	**--*
				//	|	|	|
	ldd	r4,Y+1*3+1	//	|	|	|	***-*
				//	|	|	|
	out	PORTB,r2	//	16	|	|	-**-*
	nop			//	|	|	|
	out	PORTD,r3	//	|	1	+	--*-*
	out	PORTD,r4	//	|	2	|	----*
	nop			//	|	|	|
	out	PORTD,r6	//	|	4	|	-----
	ldd	r2,Y+3*3+1	//	|	|	|	*----
				//	|	|	|
	ldi	YL,abt+6*3	//	+	|	|
	out	PORTD,r2	//	|	8	|	-----
	ld	r2,Y+		//	|	|	+	*----
				//	|	|	|
	ld	r3,Y+		//	|	|	|	**---
				//	|	|	|
	ld	r4,Y+		//	|	|	|	***--
				//	|	|	|
	out	PORTB,r2	//	64	|	|	-**--
	out	PORTD,r3	//	|	64	|	--*--
	out	PORTC,r4	//	|	|	64	-----
#ifdef SAVEY
	movw	r2,YL		//	|	|	|		Am Ende muss R3:R2 auf abt+7*3 zeigen …
	pop	YH		//	|	|	|		… damit die nachfolgende ISR funktioniert.
				//	|	|	|		Dafür 2 Bytes RAM zu opfern würde …
	pop	YL		//	|	|	|		… die nachfolgende ISR um 14 Takte …
				//	|	|	|		… verlängern (R2 und R3 wären zu retten).
#endif
	reti			//	|	|	|

.section .progmem		// Möglichst in die unteren 64K

//Katoden-Tabelle, high-aktiv, für PortA und PortC, Anodenbits HIGH
.type kat,@object
kat:	.byte	0x01,0x0F, 0x02,0x0F	// A0, A1
	.byte	0x04,0x0F, 0x08,0x0F	// A2, A3
	.byte	0x10,0x0F, 0x00,0x8F	// A4, C7
	.byte	0x00,0x4F, 0x00,0x2F	// C6, C5
	.byte	0x00,0x1F		// C4


//Exponentialfunktion (generiert durch exp.awk) für LED-Pulsweite, 256 Stufen
// TODO: Die visuelle Steilheit ist am Ende zu groß.
// Meine Einschätzung und eines Lesers des 3-Kanal-16-Bit-auf-ATmega-Artikels
.type exp,@object
exp:	.word	0,1,2,3,4,5,6,7,9,10,11,12,13,15,16,17
	.word	19,20,21,23,24,26,27,29,31,32,34,35,37,39,41,43
	.word	44,46,48,50,52,54,56,59,61,63,65,68,70,72,75,77
	.word	80,83,85,88,91,94,96,99,102,106,109,112,115,118,122,125
	.word	129,133,136,140,144,148,152,156,160,164,169,173,177,182,187,192
	.word	196,201,206,212,217,222,228,233,239,245,251,257,263,270,276,283
	.word	289,296,303,310,318,325,333,341,348,357,365,373,382,390,399,408
	.word	418,427,437,447,457,467,478,488,499,510,522,533,545,557,569,582
	.word	595,608,621,635,648,663,677,692,707,722,738,754,770,787,804,821
	.word	838,856,875,894,913,932,952,972,993,1014,1036,1058,1080,1103,1127,1150
	.word	1175,1200,1225,1251,1277,1304,1331,1359,1388,1417,1447,1477,1508,1540,1572,1605
	.word	1638,1672,1707,1743,1779,1816,1854,1893,1932,1972,2013,2055,2098,2141,2186,2231
	.word	2277,2325,2373,2422,2472,2523,2575,2628,2683,2738,2795,2852,2911,2971,3032,3095
	.word	3159,3224,3290,3358,3427,3497,3569,3642,3717,3794,3872,3951,4032,4115,4199,4285
	.word	4373,4463,4555,4648,4743,4840,4939,5041,5144,5249,5356,5466,5578,5692,5808,5927
	.word	6048,6172,6298,6427,6558,6692,6829,6968,7110,7256,7404,7555,7709,7866,8027,8191
	
.section .text
	
get_exp:	// Ermittelt 13-bit-Exponentialwert zu *X++
// PE: X zeigt auf LED-Helligkeitswert
// PA: R1:R0 = 13-bit-Exponentialwert
// VR: X,Z
	ld	ZL,X+
	lds	ZH,adu+0
	mul	ZL,ZH		// Mit Helligkeit multiplizieren
	sbrc	ZH,7
	 inc	r1		// wird maximal 0xFE, etwas drauflegen
	ldi	ZH,2
	mul	r1,ZH
	ldi	ZL,lo8(exp)
	ldi	ZH,hi8(exp)
	add	ZL,r0
	adc	ZH,r1
	lpm	r0,Z+		// Exponentialfunktion anwenden, t = Zeit
	lpm	r1,Z+		// 13 Bits
	ret

stuerz13:
	rcall	get_exp
schieb13:
	lsr	r0
	ror	r2
	lsr	r0
	ror	r3
	lsr	r0
	ror	r4
	lsr	r0
	ror	r5
	lsr	r0
	ror	r6
	lsr	r0
	ror	r8
	lsr	r0
	ror	r9
	lsr	r0
	ror	r10
	lsr	r1
	ror	r11
	lsr	r1
	ror	r12
	lsr	r1
	ror	r13
	lsr	r1
	ror	r14
	lsr	r1
	ror	r15
	ret
clr13:
	clr	r2
	clr	r3
	clr	r4
	clr	r5
	clr	r6
	clr	r8
	clr	r9
	clr	r10
	clr	r11
	clr	r12
	clr	r13
	clr	r14
	clr	r15
	ret
save13:
	eor	r2,r0
	eor	r3,r0
	eor	r4,r0
	eor	r5,r0
	eor	r6,r0
	eor	r8,r0
	eor	r9,r0
	eor	r10,r0
	eor	r11,r0
	eor	r12,r0
	eor	r13,r0
	eor	r14,r0
	eor	r15,r0
	std	Y+0*3,r2
	std	Y+1*3,r3
	std	Y+2*3,r4
	std	Y+3*3,r5
	std	Y+4*3,r6
	std	Y+5*3,r8
	std	Y+6*3,r9
	std	Y+7*3,r10
	std	Y+8*3,r11
	std	Y+9*3,r12
	std	Y+10*3,r13
	std	Y+11*3,r14
	std	Y+12*3,r15
	ret

.global calc_abt
calc_abt:
	push	r0
	push	r1
	push	XL
	push	XH
#ifdef SAVEY
	push	YL
	push	YH
#endif
	push	ZL
	push	ZH
	push	r8
	push	r9
	push	r10
	push	r11
	push	r12
	push	r13
	push	r14
	push	r15
//Berechnen der Anoden-Bit-Tabelle "abt" anhand der LED-Helligkeits-Tabelle "lht"
//CurKatode++; if (CurKatode>=9) CurKatode=0;
	 inc	CurKatode
	 ldi	ZL,9
	 cp	CurKatode,ZL
	 brcs	ca9
	 clr	CurKatode
ca9:
//const BYTE *p=lht+CurKatode*3;
	 ldi	XL,lo8(lht)
	 ldi	XH,hi8(lht)
	 ldi	ZL,3
	 mul	CurKatode,ZL	// r1 bleibt 0
	 add	XL,r0
	 adc	XH,r1
//kata=kat[CurKatode][0];
//katc=kat[CurKatode][1];
	 ldi	ZL,2
	 mul	CurKatode,ZL
	 ldi	ZL,lo8(kat)
	 ldi	ZH,hi8(kat)
	 add	ZL,r0
	 adc	ZH,r1
	 ldi	YL,lo8(abt)
	 ldi	YH,hi8(abt)
	 lpm	r0,Z+
	 std	Y+kata-abt,r0
	 lpm	r0,Z+
	 std	Y+katc-abt,r0
// PORTB-LEDs (8 Bits): Bits holen, stürzen, bereitlegen
	 rcall	clr13		// R2..R14 löschen
	 rcall	stuerz13	// A0
	 rcall	stuerz13	// A1
	 rcall	stuerz13	// A2
	 adiw	XL,24		// 8 LEDs à 3 Farben überspringen
	 rcall	stuerz13	// A3
	 rcall	stuerz13	// A4
	 rcall	stuerz13	// A5
	 adiw	XL,24
	 rcall	stuerz13	// A6
	 rcall	stuerz13	// A7
	 dec	r0	// 0xFF
	 rcall	save13
// PORTD-LEDs (6 Bits): Bits holen, stürzen, bereitlegen
	 rcall	clr13
	 rcall	stuerz13	// A8
	 adiw	XL,24
	 rcall	schieb13	// Bit 2 auslassen: INT0
	 rcall	stuerz13	// A9
	 rcall	stuerz13	// A10
	 rcall	stuerz13	// A11
	 adiw	XL,24
	 rcall	stuerz13	// A12
	 rcall	stuerz13	// A13
	 adiw	YL,1
	 dec	r0	// 0xFF
	 rcall	save13
// PORTC-LEDs (4 Bits): Bits holen, stürzen, bereitlegen
	 rcall	clr13
	 rcall	stuerz13	// A14
	 adiw	XL,24
	 rcall	stuerz13	// A15
	 rcall	stuerz13	// A16
	 rcall	stuerz13	// A17
	 swap	r2
	 swap	r3
	 swap	r4
	 swap	r5
	 swap	r6
	 swap	r8
	 swap	r9
	 swap	r10
	 swap	r11
	 swap	r12
	 swap	r13
	 swap	r14
	 swap	r15
	 adiw	YL,1
	 ldd	r0,Y+katc-abt-2	// Katodenbits einsetzen
	 rcall	save13
	 
	 sbiw	YL,2
// 5 Register für <isr_t1capt> vorbelegen
	 ldd	r2,Y+katc-abt
	 ldd	r3,Y+kata-abt
	 ldd	r4,Y+5*3+0
	 ldd	r5,Y+4*3+1
	 ldd	r6,Y+3*3+2
	pop	r15
	pop	r14
	pop	r13
	pop	r12
	pop	r11
	pop	r10
	pop	r9
	pop	r8
	pop	ZH
	pop	ZL
#ifdef SAVEY
	pop	YH
	pop	YL
#endif
	pop	XH
	pop	XL
	pop	r1
	pop	r0
	ret

/******************
 * Unterprogramme *	
 ******************/
// … wo sich der Compiler zu blöd anstellt

// BYTE* GetPtr(BYTE index)	index = 0 .. 53, 0 = Schneckenzentrum
.global GetPtr,SetLed,GetLed
GetPtr:	clr	r0
	cpi	r24,54		// Notbremse gegen Programmfehler
	brcc	2f		// liefert Zeiger auf Anfang
	ldi	r25,3
	mul	r24,r25
2:	ldi	r24,lo8(lht)
	ldi	r25,hi8(lht)
	add	r24,r0
	adc	r25,r1
	movw	ZL,r24
	ret

// void SetLed(BYTE index, DWORD rgb)	rgb=R20..R22
SetLed:	rcall	GetPtr
	st	Z+,r20
	st	Z+,r21
	st	Z+,r22
	ret

// DWORD GetLed(BYTE index)
GetLed:	rcall	GetPtr
	ld	r24,Z+
	ld	r25,Z+
	ld	r26,Z+
	clr	r27
	ret
Detected encoding: UTF-80