#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-8 | 0
|