Urlaubskurs ATtiny13-Assembler
TPA
(Tasten programmierbarer AVR)
von Heinz D.
Inhalt
Tag 1-3:
Portbefehle und Vorbereitung des ersten Programms
Tag 4-6:
Tastenprogrammierung des AVR und Register-Ladebefehle
Tag 7-9:
Mathematische Befehle, Flags und negative Zahlen
Tag
10-12: Logische Befehle und Sprungbefehle
Tag 13-15:
Timerprogrammierung, Stack und Unterprogramme
Tag 16-18:
Programmier-Adapter und Interruptprogrammierung
Tag
19-21: ADC-Wandler und PWM-Signale
Siehe auch: Der
Programmierwettbewerb
Im Jahre 13
muss sich ein Beitrag mit dem Tiny13 beschäftigen. Das
Programmieren des Tiny13 in Assembler ermöglicht geradezu winzige
Programme. Dieser Beitrag spaltet die Anwender in zwei Lager. Die eine
Hälfte wird nach drei Tagen aufgeben, weil der Zeit- und
Lernaufwandt zu gross erscheint. Die andere Hälfte 'leckt Blut',
nimmt die Herausforderung an und stürzt sich in die Arbeit. Das
Denken in binären Zahlen ist am Anfang wirklich schwer. Bitte
nehmen Sie sich täglich bis zu einer Stunde Zeit, um das
Dargestellte zu begreifen. Holen Sie sich täglich ein
Erfolgserlebnis (versuchen Sie nicht einen Tag auszulassen oder zu
überfliegen). Auch wenn das Lernpensum enorm ist, sollen Sie den
Spass nicht verlieren.
Hinweis: Für den Beitrag
wurden nur wenige wichtige Befehle ausgewählt. Die Beschreibung
der Hardware beschränkt sich nur auf das Minimum. Im Rahmen des
Beitrages ist kein vollständiger Assemblerkurs möglich. Im
Netz finden Sie von anderen Autoren zum Teil sehr gute
Anfängerkurse zur Assemblerprogrammierug, die Sie als
ergänzende Information lesen können, sie sind meist etwas
anders dargestellt werden.
Falls Sie im Urlaub hiermit
anfangen wollen, laden Sie sich die Datenblätter auf Ihr eBook und
diesen Text natürlich, und legen die Bauteile in eine Seifendose.
24 Bauteile
Batterie
(4,5V), Steckbrett, Tiny13, 2x Tasten, Draht, 5x 1k, 2x 10k, je 2x LED
grün/gelb/rot, 1 Piezo, 1uF-10uF, 10k-Poti, D9-Buchse, Flachkabel
Sie
können fast jeden AtTiny oder AtMega benutzen, obwohl hier nur auf
den AtTiny13 eingegangen wird! Wenn Sie keinen Tiny13 benutzen,
sollten Sie immer beide Datenblätter (13 + Ihren) im Blick
behalten.
[www.atmel.com/images/doc0856] alle AVR-Befehle mit binärem Aufbau
[www.atmel.com/images/doc2535] AtTiny 13V (der ältere, oder)
[www.atmel.com/images/doc8126] AtTiny 13A (der jüngere, fast gleich
(kompatibel) mit 13V)
Später benötigen Sie noch den Assembler und die IDE
[www.avr-asm-tutorial.net/gavrasm/index_de-html]
[gavrasm_win_de_33.zip]
[www.avr-asm-tutorial.net/gavrasm/index_de-html#caller] [gavrasmW.zip]
Zum 'Brennen' des .HEX-File benötigen Sie noch (aus dem Lern Paket
Mikrocontroller)
[www.b-kainka.de/LPmicrosUpdate.zip]
Hardware
Ein AVR besteht im
wesentlichen aus
der Ablaufsteuerung mit der
Takterzeugung, sie holt der
Reihe nach die Befehle zur Ausführung,
dem Befehlsinterpreter, er steuert die
Baugruppen zur Ausführung der Befehle,
dem Flash-Speicher, er enthält die
Programm-Befehle (nicht flüchtig),
dem SRAM-Speicher, enthält (flüchtige)
Ergebnisse während der Laufzeit,
dem EEPROM-Speicher, er erhält ggf. (nicht
flüchtige) Daten,
der Arithmetisch/Logischen Einheit (ALU), hier erfolgen die
mathematischen/logischen Berechnungen,
dem Couter/Timer, er erzeugen z.B.
PWM-Signale,
dem AD-Wandler, um externe analoge Spannungen intern digital zu
verarbeiten,
den Ports, von 'innen' wie Speicherzellen, nach 'außen' die
Leitungen (für Tasten/LED usw.),
Die Register und die Adressierung hat
Atmel etwas seltsam
angelegt.
Von links nach rechtes:
- die (Low) Register haben spezielle Aufgaben,
- die ersten 8 (High) Register, R16-R23 sind universell und fast immer
zu verwenden,
- die I/O-Register-Adressierung (Low) fängt wieder bei 0 (-32) an (alle
Portbefehle),
- die High-I/O-Register nur von IN+OUT-Befehlen,
- die normalen SRAM-Zellen haben wieder ihre 'richtigen' Adressen.
1.Tag
Grundsätzliches:
die AVR verstehen nur binäre Ziffern als Befehle und Daten. Die
meisten Befehle (samt Daten) sind in je 16 Bit = 2 Byte untergebracht.
Damit sich Menschen die 16 Bit merken können, werden die Befehle
durch sog. Mnemonics beschrieben (z.B. 'rjmp k').
Die erste
Regel, die ein Programmierer lernt: Ein Programm muss enden! Es darf
nicht wahllos durch den Programmspeicher rasen und versuchen
irgendwelche zufälligen (unsinnigen) Befehle auszuführen.
Einen
direkten Befehl, um die Programmausführung anzuhalten gibt es zwar
nicht, aber wir können einen Befehl benutzen, der immer wieder
sich selbst (Endlosschleife) und keine weiteren Befehle ausführt.
rjmp -1
In der Befehlstabelle (doc0856.pdf) finden Sie:
Relativer Sprung +-2047 (4096 signed); rjmp k; code 1100 kkkk kkkk kkkk
Nun müssen wir noch herausfinden, wie der AVR eine negative Zahl
erkennt. Es gibt kein '-' -Zeichen!
Eine negative Zahl muss mit binären Ziffern darstellbar sein.
Stellen Sie sich das Zifferblatt einer Uhr vor. Darum legen wir den
Zahlenstrahl mit der '0' in der
Mitte über der '12'. Im Uhrzeigerzinn haben wir dann 1, 2, 3, usw.
Gegen den Uhrzeigersinn haben wir
'11Uhr'='-1', '10Uhr'='-2', usw.
Ersetzen wir nun die dezimalen Zahlen durch binäre, dann erhalten wir
nach rechts 0001, 0010, 0011,
0100 usw. nach links 1111, 1110, 1101, 1100, usw. Wir erkennen sofort,
das eine positive Zahl immer
mit einer '0..' beginnt, eine negative mit '1..'. Die Länge spielt
dabei keine Rolle: '-1 dezimal'
entspricht '1111' = '1111 1111' = '1111 1111 1111' und so weiter, je
nach Speicherplatz.
Nun können wir uns den Befehl rjmp -1 zusammenbauen: 1100 1111 1111
1111
Machen Sie sich bitte ein wenig mit dem Datenblatt vertraut.
2.Tag
Bevor
ein Programm etwas an seinen Ausgängen ausgeben kann, müssen
Vorbereitungen getroffen werden. Bei Atmel sind die Ports in den (64)
I/O-Registern untergebracht.
Port-Befehle für die unteren a=0-31 (PortLow) I/O-Register, die ein Bit
(b=Bit 0-7) ändern können:
Setze Port a.bit=1; sbi a,b
Lösche Port a.bit=0; cbi a,b
Für den Befehl sbi DDRB,1 (PortB.1=Ausgang) bauen wir mal den
Binärcode:
In der Befehlstabelle (doc0856.pdf) finden wir: sbi a,b; 1001 1010 aaaa
abbb
Im Datenblatt für den Tiny13 finden wir an I/O-Registeradresse 0x17
(=10111): DDRB
das ergibt dann: 1001 1010 1011 1bbb;
mit Bit=1 = 001: 1001 1010 1011 1001
Der cbi (clear Bit) Befehl funktioniert genauso, nur fängt er mit 1001
1000 .. an.
Versuchen Sie bitte ein Programm zu schreiben, welches die LED an
PortB.1 einschaltet und dann anhält.
3.Tag
Lösung:
vorbereiten des PortB.1 als Ausgang: sbi ddrb,1 ; 1001 1010 1011 1001
einschalten der LED durch eine '1' : sbi portb,1; 1001 1010 1100 0001
anhalten durch Sprung auf sich selbst: rjmp -1 ; 1100 1111
1111 1111
Das war hoffentlich nicht zu schwer.
Heute
kommen noch 2 Portbefehle dazu, die alle I/O-Register (p=0-63) lesen
und schreiben können. Außerdem kann mehr als 1 Bit manipuliert
werden. Die Werte müssen zwischen gespeichert werden. Dazu dienen
die Register r0 bis r31 (bitte nicht mit I/O-Register verwechseln). Da
einige Register spezielle Aufgaben haben, benutzen wir erst mal nur
r16-r23.
Lade Register mit Port; in rd,p
Lade Port mit Register; out p,rr
Wir holen alle Bit vom PortB in das Register r16: in r16,PinB
Der Code dazu: 1011 0ppr rrrr pppp
Mit R16=10000: 1010 0pp1 0000 pppp
Mit PinB=0x16: 1011 0011 0000 0110
Das
ist etwas gewöhnungsbedürftig und Sie sollten es üben.
Der out Befehl geht genauso, nur fängt er mit 1011 1... an.
Für Morgen können Sie die folgende Schaltung stecken (ggf. nur die
rechte Seite ohne Piezo):
.DEVICE ATtiny13 ;für gavrasm, für Symbolische Registerbezeichnungen (z.B.'DDRB')
.CSEG ;CodeSegment, muss nicht immer angegeben werden
.ORG 0 ;für Adresse '0', muss nicht immer angegeben werden
;--------------------------------Programmrumpf
init: ;Vorbereitung
sbi DDRB,1 ;Ausgang definieren
main: ;------------------------Hauptprogramm
rjmp main ;Endlos-Schleife
;--------------------------------
Tag01: ;
rjmp Tag01 ;Endlos-Schleife
Tag02: ;
sbi ddrb,1 ;set bit I/O-Reg(Low),Bit
cbi portb,1 ;clear bit I/O-Reg(Low),Bit
Tag03: ;
out portb,r16 ;I/O-Reg mit dem Byte in R16 laden
in r16,pinb ;R16 mit dem Byte des I/O-Reg laden
4.Tag
Die
meisten AVR werden (seriell) über die SPI/ISP-Schnittstelle
programmiert. Dazu gehören folgende Anschlüsse: RESET, MOSI,
MISO, SCK und GND. Nach dem anlegen der Versorgungsspannung wird der
RESET mit einer Brücke auf GND gelegt. Dadurch werden im Inneren
des AVR die Anschlüsse PB.0-PB.2 auf die SPI/ISP-Hardware
umgeschaltet. Soll eine '1' gesendet werden, wird MOSI-Taste
festgehalten und ein Impuls über die SCK-Taste eingegeben. Bei
einer '0' muss nur ein Impuls über die SCK-Taste eingegeben
werden.
Tastenprellen: drückt man eine Taste, um eine LED
einzuschalten, sieht man nicht, das die LED mehrere mal ein- und
ausgeschaltet wurde bevor sie dauernd leuchtet.
ohne Kondensator
ohne Kondensator
Um
bei genau einem Tastendruck auch nur genau einen Impuls zu erhalten,
muss ein Kondensator mit seiner Ladung die Zeit (1ms-100ms = 100nF-1uF)
des Tastenprellen überbrücken.
mit Kondensator 100nF
Manche Taster sind von so miserabler Qualität, das auch ein Kondensator
an MOSI durchaus Sinn macht.
Zu
jeder Sequenz gehören 4 Byte (= genau 32 Sck Tastendrücke).
Miso antwortet mit der fallenden Flanke. Zur Übung, ob alles
richtig angeschlossen ist, geben Sie bitte NUR den 'Prog Enable'-Befehl
ein:
Miso antwortet mit 0x53 im 3.Byte! Danach lösen Sie Reset erst einmal
auf!
Für den Fall, das es noch nicht klappt:
überprüfen Sie bitte die Schaltung, insbesondere den korrekten Sitz des
Entprell-Kondensators.
Ggf. tauschen Sie den Taster an Sck aus.
Bitte wiederholen Sie 'Prog Enable', bis Sie sicher sind, das Miso
eindeutig antwortet und die
Tasten nicht prellen. (Reset zwischendurch aufheben)
Nun können Sie die drei Signatur-Byte lesen. Im 2.Byte muss Miso mit
'0x30' antworten!
Andernfalls unterbrechen Sie die Eingabe und beginnen von vorn.
WARNUNG: Über eine ähnliche Sequenz werden auch die FUSE-Bits verändert
!!!
Falls Sie sich verzählt haben, sollten Sie zuerst RESET freigeben und
dann noch einmal anfangen!!!
Wer es nachlesen möchte, findet im Datenblatt Kapitel 17.6 Serial
Programming weitere Info's.
Bitte üben, üben, üben, man verzählt sich leicht.
5.Tag
Heute
kann der Tiny13 sein erstes Programm über ISP/SPI bekommen. Es ist
das Programm vom 2.Tag und schaltet nur die LED an PB1 ein.
- Zum programmieren wird zuerst die Versorgungsspannung angelegt,
- dann wird RESET mit einer Drahtbrücke auf GND gelegt,
- die einzelnen Bit werden mit MOSI geschaltet und mit SCK übertragen,
- mit ProgEnable und Chip-Erase wird die Programmierung eingeleitet,
- mit LoadPage.. werden die Programmbyte in den Zwischspeicher
übertragen,
- mit Write-Page werden die (6) Byte ins Flash-Memory übertragen,
Die gesamte Sequenz binär (wenn die Miso-LED nicht stimmt, brechen Sie
bitte ab):
Nun kann RESET wieder freigegeben werden und die Miso-LED sollte
leuchten.
Herzlichen Glückwunsch, Sie haben gerade einen AVR ohne 'Brenner'
programmiert!
6.Tag
Sie
haben schon kurz etwas von Registern gehört. Es sind
Speicherplätze (je 1 Byte), die für Zwischenergebnisse zur
Verfügung stehen. Einige der 32 Register haben besondere Aufgaben.
Die Register R0 bis R15 sind nicht von jedem Befehl erreichbar.
Die
Register R24 bis R31 werden meist für Word-Ergebnisse (2 Byte)
gebraucht (nicht mit jedem Befehl). Als Universal-Register bleiben R16
bis R23 (8 Stück) übrig.
Befehle für die (Speicher-) Register.
Lade Register (r16-r31) direkt mit einer Konstenten
Zahl; ldi rd,k
Lade Register (r0-r31) mit einem beliebigen anderem Register; mov
rd,rr
Lade r16 mit 255: 1110 kkkk dddd kkkk
Mit ldi r16,255: 1110 1111 0000 1111
da hier nur r16-r31 zulässig sind, entspricht r16 = 0000; r31 = 1111
Lade r16 mit r17: 0010 11rd dddd rrrr
Mit mov r16,r17: 0010 1111 0000 0001
hier sind alle Register zulässig; r16 = 10000; r17 = 10001
Das Ziel ist immer das erste Register rd <- rr (d= Destination=
Ziel)
Es
kommt vor, das 2 Byte (= 1 WORD) am Stück verarbeitet werden
müssen. Dazu werden die geraden Register (r24; r26; r28; r30)
benutzt und, ohne es zu erwähnen, das ungerade höhere
Register noch dazu.
Lade Doppelregister r0(r1)-r30(r31) mit einem beliebigen anderem
Word-Reg.; movw rd,rr
Im geraden Register steht die untere Hälfte (LSB) des Wertes, im Oberen
die obere Hälfte (MSB).
Lade Doppelregister r24 mit r26: 0000 0001 dddd rrrr
Mit
movw r24,r26: 0000 0001 1100 1101
Aus r24 wird (24/2) dez 12 = bin 1100; aus r26 wird dez 13 = bin 1101
(movw gibt es bei Tiny11/12 nicht.) ... Sie sollten es üben.
.DEVICE ATtiny13 ;für gavrasm, für Symbolische Registerbezeichnungen (z.B.'DDRB')
.CSEG ;CodeSegment, muss nicht immer angegeben werden
.ORG 0 ;für Adresse '0', muss nicht immer angegeben werden
;--------------------------------Programmrumpf
init: ;Vorbereitung
sbi DDRB,1 ;Piezo als Ausgang definieren
main: ;------------------------Hauptprogramm
rjmp main ;Endlos-Schleife
;--------------------------------
Tag01: ;
rjmp Tag01 ;Endlos-Schleife
Tag02: ;
sbi ddrb,1 ;set bit I/O-Reg(Low),Bit
cbi portb,1 ;clear bit I/O-Reg(Low),Bit
Tag03: ;
out portb,r16 ;I/O-Reg mit dem Byte in R16 laden
in r16,pinb ;R16 mit dem Byte des I/O-Reg laden
Tag06: ;
ldi r16,7 ;lade R16 mit 7(dez)(Byte)
mov r16,r17 ;lade R16 mit dem Byte in R17
movw r24,r26 ;R24<=R26 und R25<=R27 (Word)
7.Tag
Sie
können den Flash-Speicher auslesen, ohne den Inhalt zu
verändern. Lesen Sie bitte Low- und High-Byte an der Adresse 0.
0xB9 und 0x9A sind die erwarteten Ergebnisse.
Als nächstes benötigen wir Befehle, um die Registerinhalte zu
verändern. Mathematische Befehle:
Addiere zwei beliebige Register; add rd,rr
Addiere Doppelregister (r24-r30) mit Konstanten; adiw rd,k
Addiere r16 und r17: 0000 11rd dddd rrrr
Mit add r16,r17: 0000 1111 0000
0001
Addiere zu r24 1: 1001 0110 kkdd kkkk
Mit adiw r24,1: 1001 0110 0000 0001
Beim
adiw-Befehle wird beim hochzählen ein Übertrag ins
höhere Register automatisch ausgeführt. Erst beim 65536.
Aufruf von adiw r24,1 ist das Doppelregister einmal durchgezählt.
Lädt man R25 auf den PortB, wird PB.1 (die miso-Leitung) das 2.Bit
von unten anzeigen. (adiw gibt es bei Tiny11/12 nicht)
Aufgabe: Welche Wirkung hat add r16,r16 ?
8.Tag
Lösung: Richtig! Multiplikation mit 2; r16 <= r16 * 2
Lesen Sie bitte ein Byte an Adresse '7', es sollte '0xF', weil gelöscht
lauten.
Falls
Sie BAScom installiert haben, können Sie es zum assemblieren
benutzen. Es erzeugt einen größeren Code als ein Assembler, weil
die Vorbereitungen für Basic-Programme erforderlich sind. Programm
1 in BAScom:
' 13UK, Tag 5, Programm 1, LED an PortB.1 = 1
'-------------------------------------------------------------------------------
' alle Fuse-Byte ab Werk
' kompiliert mit Bascom-Demo 2.0.7.1 (sollte auch mit 2.0.7.5 funktionieren)
'-------------------------------------------------------------------------------
$regfile = "attiny13.dat"
$crystal = 1200000 ' 9,6MHz/8
$hwstack = 8
$asm ' Assembler-Befehle einfügen
'-------------------------------------------------------------------------------
sbi DDRB,1 ' PortB.1 = Ausgang
sbi portb,1 ' Set Bit PortB.1
rjmp -1 ' Endlosschleife
'-------------------------------------------------------------------------------
$end Asm ' End Assembler
End
' End Program
Das .ASM-File aus dem Disassembler:
; Atmel AVR Disassembler v1.30
;
.cseg
.org 0
rjmp avr000A ; 0000 C009 ;Vector Tabelle
reti ; 0001 9518
reti ; 0002 9518
reti ; 0003 9518
reti ; 0004 9518
reti ; 0005 9518
reti ; 0006 9518
reti ; 0007 9518
reti ; 0008 9518
reti ; 0009 9518
avr000A: ldi r24, 0x9F ; 000A E98F
out SPL, r24 ; 000B BF8D Stack-Pointer-Low=RamEnd
ldi YL, 0x98 ; 000C E9C8
ldi ZL, 0xA0 ; 000D EAE0
mov r4, ZL ; 000E 2E4E r4=0xA0
clr YH ; 000F 27DD Y=0x0098
mov r5, YH ; 0010 2E5D r5=0
wdr ; 0011 95A8 WD-Timer restart
in r24, ?0x34? ; 0012 B784 MCUSR = Reset-Flags
mov r0, r24 ; 0013 2E08
andi r24, 0xF7 ; 0014 7F87 WD-Reset abschalten
out ?0x34?, r24 ; 0015 BF84 zurückschreiben
ldi r24, 0x18 ; 0016 E188
clr r25 ; 0017 2799
out WDTCR, r24 ; 0018 BD81 WDTCR(0x21) =0x18 enable
out WDTCR, r25 ; 0019 BD91 WDTCR(0x21) =0
ldi ZL, 0x3E ; 001A E3EE
ldi ZH, 0x00 ; 001B E0F0 Z=0x003E
ldi XL, 0x60 ; 001C E6A0
ldi XH, 0x00 ; 001D E0B0 X=0x0060
clr r24 ; 001E 2788
avr001F: st X+, r24 ; 001F 938D SRam löschen von 0x60 bis 0x9E
sbiw ZL, 0x01 ; 0020 9731
brne avr001F ; 0021 F7E9
clr r6 ; 0022 2466 r6=0
sbi DDRB, 1 ; 0023 9AB9 ### Init:
avr0024: sbi PORTB, 1 ; 0024 9AC1 ### Main:
rjmp avr0024 ; 0025 CFFE ### Endlosschleife
cli ; 0026 94F8 clear Interrupt-Flag wird nicht aufgerufen
avr0027: rjmp avr0027 ; 0027 CFFF HALT wird nicht aufgerufen
avr0028: sbiw ZL, 0x01 ; 0028 9731 sub_wait ZL (256) wird nicht aufgerufen
brne avr0028 ; 0029 F7F1 springe wenn Z=0
ret ; 002A 9508 end sub
set ; 002B 9468 Müll... wird nicht aufgerufen
bld r6, 2 ; 002C F862
ret ; 002D 9508
clt ; 002E 94E8
bld r6, 2 ; 002F F862
ret ; 0030 9508
.exit
An der Adresse 0x0023 finden wir unser Programm wieder (Der Rest ist im
Moment nicht wichtig).
Wir finden unsere 3 Befehle im .HEX-File:
:1000000009C018951895189518951895189518956C
:10001000189518958FE98DBFC8E9E0E84E2EDD27C9
:100020005D2EA89584B7082E877F84BF88E1992725
:1000300081BD91BDEEE3F0E0A0E6B0E088278D93AE
----Adr------------------0x46-0x48-0x4A
-----vv------------------v-v--v-v--v-v-
:10004000 3197 E9F7 6624 B99A C19A FECF F894 FFCF A9
:100050003197F1F70895689462F80895E89462F88A
:02006000089501
:00000001FF
Hier werden die Adressen in Byte gezählt, im Listing in Word, aus
0x0023 wird deshalb 0x0046!
Bei den Befehlen kommt immer LSByte zuerst, dann das MSByte.
Nur diese 6 Byte mussten wir eingeben.
In
der Befehlsbeschreibung ist Ihnen das Status Register (Sreg)
aufgefallen. Sreg wird nach jeder mathematischen (und logischen)
Operation neu gesetzt. Hier nur die drei wichtigsten Flags:
- N(egativ)-Flag ist eine Kopie des obersten Bit im Register (für
signierte Zahlen).
- Z(ero)-Flag wird gesetzt, wenn das Ergebnis =0 war.
- C(arry)-Flag wird bei einem Überlauf/Unterlauf (>255/Byte
>65535/Word) gesetzt.
r16=100; r17=200; add r16,r16; r16=200; N=1; Z=0; C=0
r16=100; r17=200; add r16,r17; r16= 44; N=0; Z=0; C=1; r16=300-256=44
weil das Byte 'übergelaufen' ist.
Zum Rechnen gehört auch das Abziehen:
Subtrahiere ein Register vom einem anderen Register; sub rd,rr; code
0001 10rd dddd rrrr
Subtrahiere eine Konstante vom Register (r16-r31) ; subi
rd,k; code 0101 kkkk dddd kkkk
Subtrahiere eine Konstante vom Word-Reg. (r24-r30) ; sbiw
rd,k; code 1001 0111 kkdd kkkk
(sbiw nicht bei Tiny11 und Tiny12)
Zum subi gibt es kein addi. Da k von 0-255 betragen darf, kann k auch
signiert (+-127) sein.
Die Aufgabe 'r16=r16+15' => r16=r16-(-15) =>
r16=r16-11110001 => subi r16,0b11110001
Ein wenig seltsam, aber es geht.
Aufgabe: Bitte finden Sie den Code für sub r16,r17 und suchen Sie die
NZC-Flags mit r16=100, r17=200.
Antwort: 0 0 0 1 1 0 _ _ _ _ _ _ _ _ _ _
; N= __ ; Z= __ ; C= __
9.Tag
Lösung: 0001 1011 0000 0001; r16=155; N=1; Z=0; C=1; weil Carry auch
den Unterlauf anzeigt.
Heute
soll ein Ton mit dem Piezo an der Miso-LED erzeugt werden, indem ein
Word-Register (R24/R25) hochgezählt wird und R25 in PortB geladen
wird (PortB.1 = R25.1).
;
;bei r24(25) = n * 512 erfolgt das Toggeln von PB.1 (Puls+Pause=1024)
;Frequenz: 1200000Hz / 5 Cyclen / 1024 = ~234Hz
;
init:
;Vorbereitungen
9AB9 sbi
ddrb,1 ;DDRB.1=Ausgang
main:
;Hauptprogramm
9601 adiw
r24,1 ;r25/r24 hochzählen (2 Cyclen)
BB98 out
portb,r25 ;r25 ausgeben (1 Cyclus)
CFFD rjmp
main ;Jr-3
nochmal (2 Cyclen)
Nach freigeben des RESET, sollte ein Ton erklingen.
Hier das .BAS-File dazu:
' 13UK, Tag 9, Programm 3, LED an PortB.1 = 1
'
' 9600000Hz/8 /256(r24) /5(Cyclen) /2(r25) /2(toggle) = 234,4 Hz
' mit der Konstanten (1) hinter adiw können höhere Frequenzen eingestellt werden
' mit z.B. 10 werden 2344 Hz erzeugt
'-------------------------------------------------------------------------------
' alle Fuse-Byte ab Werk
' kompiliert mit Bascom-Demo 2.0.7.1 (sollte auch mit 2.0.7.5 funktionieren)
'-------------------------------------------------------------------------------
$regfile = "attiny13.dat"
$crystal = 1200000 ' 9,6MHz/8
$hwstack = 8
$asm ' Assembler-Befehle einfügen
'-------------------------------------------------------------------------------
Init: ' Vorbereitungen
sbi DDRB,1 ' PortB.1 = Ausgang
Main: ' Hauptprogramm
adiw r24,1 ' r25/r24 hochzählen (2 Cyclen)
Out Portb , R25 ' R25 Ausgeben (1 Cyclus)
rjmp main ' nochmal (2 Cyclen)
'-------------------------------------------------------------------------------
$end Asm ' End Assembler
End
' End Program
Das .HEX-File:
:1000000009C018951895189518951895189518956C
:10001000189518958FE98DBFC8E9E0E84E2EDD27C9
:100020005D2EA89584B7082E877F84BF88E1992725
:1000300081BD91BDEEE3F0E0A0E6B0E088278D93AE
----Adr------------------0x46-0x48-0x4A-0x4C
-----vv------------------v-v--v-v--v-v--v-v-
:10004000 3197 E9F7 6624 B99A 0196 98BB FDCF F894 E9
:10005000FFCF3197F1F70895689462F80895E89416
:0400600062F80895A5
:00000001FF
Und das Oszillogramm:
Das Oszi misst netterweise selbst die Frequenz, (rechts) 'FRQ 234,5Hz'
Bei
diesem Programm hatte ich beim 1.Versuch beim rjmp nicht 'CFFD'
eingegeben, deshalb war die Frequenz niedriger...bin eben nur ein
Mensch. Mit größeren Werten bei adiw r24,WERT können Sie
höhere Frequenzen einstellen. (20 => 234Hz * 20 = ~4680Hz)
Können Sie das .ASM-File selbst kommentieren? [13uk-prog3.asm.txt]
; Atmel AVR Disassembler v1.30
;
.cseg
.org 0
rjmp avr000A ; 0000 C009
reti ; 0001 9518
reti ; 0002 9518
reti ; 0003 9518
reti ; 0004 9518
reti ; 0005 9518
reti ; 0006 9518
reti ; 0007 9518
reti ; 0008 9518
reti ; 0009 9518
avr000A: ldi r24, 0x9F ; 000A E98F
out SPL, r24 ; 000B BF8D
ldi YL, 0x98 ; 000C E9C8
ldi ZL, 0xA0 ; 000D EAE0
mov r4, ZL ; 000E 2E4E
clr YH ; 000F 27DD
mov r5, YH ; 0010 2E5D
wdr ; 0011 95A8
in r24, ?0x34? ; 0012 B784
mov r0, r24 ; 0013 2E08
andi r24, 0xF7 ; 0014 7F87
out ?0x34?, r24 ; 0015 BF84
ldi r24, 0x18 ; 0016 E188
clr r25 ; 0017 2799
out WDTCR, r24 ; 0018 BD81
out WDTCR, r25 ; 0019 BD91
ldi ZL, 0x3E ; 001A E3EE
ldi ZH, 0x00 ; 001B E0F0
ldi XL, 0x60 ; 001C E6A0
ldi XH, 0x00 ; 001D E0B0
clr r24 ; 001E 2788
avr001F: st X+, r24 ; 001F 938D
sbiw ZL, 0x01 ; 0020 9731
brne avr001F ; 0021 F7E9
clr r6 ; 0022 2466
sbi DDRB, 1 ; 0023 9AB9 ###
avr0024: adiw r24, 0x01 ; 0024 9601 ###
out PORTB, r25 ; 0025 BB98 ###
rjmp avr0024 ; 0026 CFFD ###
cli ; 0027 94F8
avr0028: rjmp avr0028 ; 0028 CFFF
avr0029: sbiw ZL, 0x01 ; 0029 9731
brne avr0029 ; 002A F7F1
ret ; 002B 9508
set ; 002C 9468
bld r6, 2 ; 002D F862
ret ; 002E 9508
clt ; 002F 94E8
bld r6, 2 ; 0030 F862
ret ; 0031 9508
.exit
Zwei weitere Befehle ermöglichen das abziehen/addieren mit 1:
Register -1; dec rd; rd<=rd-1; code 1001 010d dddd 1010
Register +1; inc rd; rd<=rd+1; code 1001 010d dddd 0011
Beim
Subtrahieren sind Ihnen die seltsamen Ergebnisse beim
Register-Unterlauf aufgefallen. Das sind einfach nur Zahlen, die man
als negativ (-) interpretieren muss. Will man mit negativen Zahlen
arbeiten, muss man sich darüber klar sein, das nur +127 bis -127
in ein Byte passen. +0 bis +127 -> oberes Bit nicht gesetzt, -1
bis
-127 -> oberes Bit gesetzt. Für -128 gibt es keine positive
Zahl.
Um aus einer positiven Zahl eine negative zu machen oder
aus einer negativen eine positive müssen alle Bit invertiert (aus
1 wird 0, aus 0 wird 1) und eine 1 addiert werden. Fertig! Dafür
gibt es den Befehl: neg rd; code 1001 010d dddd 0001 Die Wirkung ist
wie: Zahl = Zahl * -1, egal ob Zahl positiv oder negativ war.
Will man nur alle Bits invertieren (Complement-Befehl): com rd; code
1001 010d dddd 0000
Aufgabe: Wandeln Sie -128 in eine positive Zahl und sehen, warum das
nicht geht.
10.Tag
Lösung: -128 = 1000 0000 -> com -> 0111 1111 ->
Inc -> 1000 0000 = -128 ; +128 gibt es nicht!
Das
Programm von gestern hat einen Ton erzeugt, indem ein Port ein- und
ausgeschaltet wurde. So eine (primitive) Aufgabe kann man durch einen
Timer erledigen lassen. Wir benutzen den Timer im 'normal'-Mode, das
heisst er zählt von 0-255 und dann wieder von vorn. Wir legen
fest, das bei einem Überlauf PortB.1 umgeschaltet (toggle) wird:
TCCR0A=0x00010000=16.
Dann schalten wir den höchsten Vorteiler (prescaler) ein und starten
den Timer im Normal-Mode:
TCCR0B=0x00000101=5.
Die Frequenz ist dann: 9,6MHz /8(devide) /1024(prescaler) /256(Timer)
/2(toggle) =2,288Hz
Das
war schon alles, der Timer schaltet den Port ohne weiteres Zutun. Das
Programm 4 übersetzen (assemblieren) Sie bitte mit 'gavrasm':
.DEVICE ATtiny13 ;für gavrasm, für Symbolische Registerbezeichnungen
(z.B.'DDRB')
.cseg
.org 0
;
; der Timer blinkt selbständig an PortB.1 (=OC0B)
;
; 9600000Hz /8 /1024(prescaler) /256(Timer) /2(toggle) =2,288Hz
;
Init:
sbi DDRB,1 ; PortB.1 = Ausgang
Init_timer:
ldi r16,16 ; 0b00010000
Out Tccr0a , R16 ; ^^-----toggle Ausgang B bei Überlauf
ldi r16,5 ; 0b00000101
Out Tccr0b , R16 ; ^^^-Vorteiler /1024 und Timer starten
Main:
rjmp main ; Endlosschleife
Im .HEX-File finden Sie die Codes (12Byte):
von gavrasm erzeugt:
:020000020000FC
------Adr-L-M--L-M--L-M--L-M--L-M--L-M
-------vv-v-v--v-v--v-v--v-v--v-v--v-v vv-Checksumme
:0C000000 B99A 00E1 0FBD 05E0 03BF FFCF 7F
:00000001FF
L=LSByte das niederwertige Byte zuerst
M=MSByte
Können Sie es eintippen? Sie sehen dann ein schnelles blinken.
Im Oszillogramm sehen wir, das die berechnete Frequenz fast stimmt:
Weitere mathematische Befehle:
Division (/2) gibt es auch und heißt arithmetic shift right: asr rd;
code 1001 010d dddd 0101
Alle Bits werden nach rechts verschoben, wobei das Vorzeichenbit 7
erhalten bleibt und das untere
Bit ins C(arry)-Flag geschoben wird.
r16=9dez=0000 1001; asr r16; r16=4dez=0000 0100; S=0; Z=0; C=1
r16=-4dez=1111 1100; asr r16; r16=-2dez=1111 1110; S=1; Z=0; C=0
Ein ähnlicher Befehl von den logischen Befehlen: logical shift right:
lsr rd: code 1001 010d dddd 0110
Hier bleibt das Vorzeichenbit nicht erhalten und ist deshalb nur für
positive Zahlen anwendbar.
r16=255dez=1111 1111; lsr r16; r16=127dez=0111 1111; C=1
Wenn
mehrere Byte nacheinander nach links (*2) oder rechts (/2) geschoben
werden müssen, muss ein Übertrag oder Unterlauf für das
nächste Byte gespeichert werden. Dafür ist das C(arry)-Flag
ideal.
adc rd,rr (add with Carry) mit rd=rr (*2) ;0001 11rd dddd rrrr
ror rd (rotate right through Carry) (/2) ;1001 010d dddd 0111
11.Tag
Das Programm von gestern in BAScom:
' 13UK, Tag 10, Programm 4, blinkt mit Timer, LED an OC0B (PortB.1)
'
' 9600000Hz /8 /1024(prescaler) /256(Timer) /2(toggle) =2,288Hz
'
'-------------------------------------------------------------------------------
' alle Fuse-Byte ab Werk
' kompiliert mit Bascom-Demo 2.0.7.1 (sollte auch mit 2.0.7.5
funktionieren)
'-------------------------------------------------------------------------------
$regfile = "attiny13.dat"
$crystal = 1200000 ' 9,6MHz/8
$hwstack = 8
$asm ' Assembler-Befehle einfügen
'-------------------------------------------------------------------------------
Init:
sbi DDRB,1 ' PortB.1 = Ausgang
Init_timer:
ldi r16,16 ' &B00010000
Out Tccr0a , R16 ' ^^-----toggle Ausgang B bei Überlauf
ldi r16,1 ' &B00000001<-kein Vorteiler = 2343Hz
' ldi r16,5 ' &B00000101
Out Tccr0b , R16 ' ^^^-Vorteiler /1024 und Timer starten
Main:
rjmp main ' Endlosschleife
'-------------------------------------------------------------------------------
$end Asm ' End Assembler
End
' End Program
Weitere logische Befehle:
Und-Verknüpfung zwischen zwei Registern; and rd,rr; code 0010 00rd dddd
rrrr
Exclusiv - Oder zwischen zwei Registern; eor rd,rr; code 0010 01rd dddd
rrrr
Oder-Verknüpfung zwischen zwei Registern; or rd,rr; code 0010 10rd dddd
rrrr
Oder-Verknüpfung zwischen Reg und Konst.; ori rd,k; code 0110 kkkk dddd
kkkk
Und-Verknüpfung zwischen Reg und Konst.; andi rd,k; code 0111 kkkk dddd
kkkk
Dazu noch ein paar Tricks:
r16 soll gelöscht werden: eor r16,r16; Z=1
S und Z-Flag soll gesetzt werden ohne den Inhalt zu verändern: or
r16,r16
oberes Nibble auf 1 setzen, untere Bits erhalten: ori r16,240
unteres Nibble auf 0 setzen, obere Bits erhalten: andi r16,240
Hier noch eine Aufgabe für Profis (Anfänger sollten es nicht versuchen):
Was passiert mit den Inhalten von r16 und r17 nach den drei Befehlen?
eor r16,r17
eor r17,r16
eor r16,r17
12.Tag
Lösung mit r16 = 0101 = 5 und r17 = 0011 = 3:
eor r16,r17; r16 = 0110; r17 wie vorher = 0011
eor r17,r16; r17 = 0101; r16 wie vorher = 0110
eor r16,r17; r16 = 0011; r17 wie vorher = 0101
r16 = 3; r17 = 5; -> die Inhalte der Register haben ihre Plätze
getauscht, ohne ein Hilfsregister!
Probieren Sie es mal mit anderen Zahlen (es klappt immer).
Damit
nicht immer nur ein Befehl nach dem anderen abgearbeitet werden muss,
gibt es Sprung-Befehle. Den unbedingten (springt immer) Sprung rjmp k
kennen Sie schon. Dazu gibt es noch Sprünge, die nur springen,
wenn eine BEDINGUNG erfüllt ist. Dazu dient das Sregister mit
seinen Flags. Je nachdem, ob ein Flag gesetzt oder gelöscht ist,
wird ein Sprung zu einer anderen Programmzeile ausgeführt oder
nicht.
Bedingter Sprung wenn ein Bit im Sreg=0; brbc s,k; code 1111 01kk kkkk
ksss
Bedingter Sprung wenn ein Bit im Sreg=1; brbs s,k; code 1111 00kk kkkk
ksss
Springe wenn Z-Flag=0: brbc 1,k; da k nur +-63 betragen darf, kann man
nur 'in die Nähe' springen.
Springe wenn C-Flag=1; brbs 0,k;
Diese Befehle machen nur Sinn, wenn vorher ein mathematischer/logischer
Befehl die Flags setzt.
Manchmal ist es einfacher nur den nächsten Befehl zu überspringen.
SKIPBEFEHLE (von Skippy das Känguru)
Überspringe den nächsten Befehl wenn Register rd=rr; cpse rd,rr; code
0001 00rd dddd rrrr
Überspringe den nächsten Befehl wenn I/O-Reg.bit=0; sbic
a,b; code 1001 1001 aaaa abbb
Überspringe den nächsten Befehl wenn I/O-Reg.bit=1; sbis
a,b; code 1001 1011 aaaa abbb
Überspringe den nächsten Befehl wenn Register.bit=0; sbrc
rr,b; code 1111 110r rrrr 0bbb
Überspringe den nächsten Befehl wenn Register.bit=1; sbrs
rr,b; code 1111 111r rrrr 0bbb
Die Verzweigungen (Sprung- und Skip-Befehle) verändern keine
Register-Inhalte.
Nun
müssen wir die neuen Befehle mal ausprobieren. An anderer Stelle
(TPS-Zufall) wurde bereits über Pseudozufallszahlen geschrieben.
Wir wollen heute weißes Rauschen erzeugen. Als reine digitale
Zufallszahl würde eine Portleitung genügen. Benutzen wir alle
5 Ausgänge, können wir einen DA-Wandler anschließen und
erhalten ein 32-stufiges Analogsignal. Eigentlich müssen wir beide
Signale durch einen Tiefpass leiten, der die (dominante)
Trägerfrequenz wegfiltert. Für den Piezo sparen wir uns das
heute.
Die Software macht nichts anderes, als ein
Hardwaregenerator (Schieberegister+Exor). Zunächst suchen wir mit
22 Stufen eine günstige Bit-Länge, die in 3 Byte passt. Dazu
hängen wir die Register 16, 17 und 18 (per Software) aneinander.
Die beiden letzten Bit werden Exor verknüpft und an der Position
R18.5 gespeichert. Im Hilfsregister R19 werden die Berechnungen
durchgeführt.
Das Programm in Assembler:
.DEVICE ATtiny13 ;für gavrasm, für Symbolische Registerbezeichnungen
(z.B.'DDRB')
.cseg
.org 0
;
; Pseudo-Zufallszahlen, weisses Rauschen durch Simulation eines
; 22-Bit-Schieberegister (mit Exor der beiden rechten Bit)
;
; fo = 1,2 MHz / 11,5 Cyclen / 2 = 52174 Hz
; fu = fo / 2^22-1 = 52174 Hz / 4194303 = 0,012 Hz
;
init: ;
ldi r16,31 ; alle Ports auf Ausgang
out DdrB,r16 ;
Main: ;
out PortB,r16 ; die letzten 5 Bit ausgeben (1.Ausgabe = 31 = 11111)
mov r19,r16 ; obwohl alle Bit gespeichert werden, geht es nur um das
letzte Bit
ror r18 ; alles 1 Bit nach rechts, das rechte in Carry
ror r17 ; von Carry in Bit 7, alles 1 Bit nach rechts, das rechte in
Carry
ror r16 ; nocheinmal, nun ist das ursprüngliche Bit 2 in Bit 1
eor r19,r16 ; EXOR, hier interessiert nur r19,1 xor r16.1
ror r19 ; das rechte Bit in Carry schieben
brbs 0,eins ; wenn C=1 springe nach eins, wenn nicht dann ist in C=0
andi r18,31 ; also wird eine '0' in r18.5=0 geschrieben, r18 = xx0x xxxx
rjmp Main ; nächster Schiebetakt
eins: ; hier landen wir nur, wenn in C=1 war
ori r18,32 ; also wird eine '1' in r18.5=1 geschrieben, r18 = xx1x xxxx
rjmp Main ; nächster Schiebetakt
Ein Beispiel des 1. Durchlaufs:
Bitte
assemblieren Sie mit gavrasm, weil in BAScom später einige
Programme nicht machbar sind. Für einen Piezo hört sich das
Signal an jedem Ausgang gleich an.
Am Oszilloskop ist ein Teil des Musters erkennbar, unten PB.0, oben
PB.1 und man sieht die Verschiebung um eine Zeiteinheit.
Zum
Schluss noch die Abschätzung der Bandbreite, für die, die es
genauer wissen wollen. Die obere Frequenz richtet sich nach der
AVR-Taktfrequenz / Zeit eines Durchlaufs / 2 wg. Puls+Pause. Aus der
Befehlstabelle holen Sie sich die Cyclen für alle Befehle und
addieren sie (=11,5 Cylen). Der brbs wird zu Hälfte mit 1Cy und
2Cy berücksichtigt. fo = 1,2MHz / 11,5 Cyclen / 2 = 52174 Hz Die
untere Frequenz ist von der Registerlänge abhängig. Nach
2^22-1 wiederholt sich das Muster.
fu = fo / 2^22-1 = 52174Hz / 4194303 = 0,012Hz
Die nutzbare obere Frequenz muss mit einem Tiefpass noch einmal durch 2
geteilt werden = 26087Hz
Die Verwendung eines 5-Bit DA-Wandlers reduziert die Registerlänge um 5
auf 17 oder fo / 32.
Die nutzbare fo5 = 26087Hz / 32 = 815 Hz, fu bleibt.
Die bisher gelernten Befehle:
.DEVICE ATtiny13 ;für gavrasm, für Symbolische Registerbezeichnungen
(z.B.'DDRB')
.CSEG ;CodeSegment, muss nicht immer angegeben werden
.ORG 0 ;für Adresse '0', muss nicht immer angegeben werden
main: ;------------------------Hauptprogramm
rjmp main ;Endlos-Schleife
;--------------------------------
;--------------------------------
Tag01: ;
rjmp Tag01 ;Endlos-Schleife
Tag02: ;
sbi ddrb,1 ;set bit I/O-Reg(Low),Bit
cbi portb,1 ;clear bit I/O-Reg(Low),Bit
Tag03: ;
out portb,r16 ;I/O-Reg mit dem Byte in R16 laden
in r16,pinb ;R16 mit dem Byte des I/O-Reg laden
Tag06: ;
ldi r16,7 ;lade R16 mit 7(dez)(Byte)
mov r16,r17 ;lade R16 mit dem Byte in R17
movw r24,r26 ;R24<=R26 und R25<=R27 (Word)
Tag07: ;
add r16,r17 ;R16<=R16+R17 ->SReg
adiw r24,15 ;R24<=R24+15 ->Sreg
Tag08: ;
sub r16,r17 ;R16=R16-R17 ->SReg
subi r16,31 ;R16<=R16-31 ->SReg
sbiw r24,1 ;R24(R25)<=R24(R25)-1 ->SReg
Tag09: ;
inc r16 ;R16<=R16 +1 ->SReg
dec r16 ;R16<=R16 -1 ->SReg
neg r16 ;R16<=R16 * -1 ->SReg
com r16 ;invertiere alle Bits im R16 ->SReg
Tag10: ;
asr r16 ;R16<=R16/2 ->Sreg
lsr r16 ;R16<=R16/2 ->Sreg
adc r16,r17 ;R16<=R16+R17+Carry ->Sreg
ror r16 ;R16<=R16/2+128*C ->SReg
Tag11: ;
and r16,r17 ;R16<=R16 AND R17 ->SReg
eor r16,r17 ;R16<=R16 EXOR R17 ->SReg
or r16,r17 ;R16<=R16 OR R17 ->SReg
ori r16,5 ;R16<=R16 OR 0101 ->SReg
andi r16,5 ;R16<=R16 AND 0101 ->SReg
TAG12: ;
brbc 1,10 ;springe 10 weiter, wenn SREG.1=0 -> Z=0
brbs 0,-5 ;springe 5 zurück, wenn SReg.0=1 -> C=1
cpse r16,r17 ;skip if R16=R17
sbic portb,1 ;skip if PortB.1=0; Einzelbit-Prüfung
sbis portb,2 ;skip if PortB.2=1; Einzelbit-Prüfung
sbrc r16,3 ;skip if R16.3=0; Einzelbit-Prüfung
sbrs r16,4 ;skip if R16.4=1; Einzelbit-Prüfung
13.Tag
Bisher
haben Sie Befehle für die 'lineare' Programmierung mit
Sprüngen kennen gelernt. Um z.B. eine Warteschleife nicht immer
wieder im Programm zu wiederholen, wäre es gut, wenn eine
Warteschleife von überall aufgerufen werden kann. Das nennt man
dann Unterprogramm (Subroutine). Um wieder ins Hauptprogramm
zurück zu springen, muss die Adresse gespeichert werden. Dazu
benutzen alle CPUs einen Bereich am oberen Ende des Speichers, Stapel
(Stack) genannt. Um ihn zu benutzen, muss er am Programmanfang
definiert werden. Dazu wird das I/O-Reg: SPL auf das RAM-Ende
eingestellt.
ldi r16,ramend; der Assembler ist so nett und holt uns RAMEND=0x9F als
Zahl in r16
out spl,r16; spl=0x3D; out 61,159
(dezimal)
(
Bei meinen Tiny13 ist SPL schon ab Werk mit 0x9F geladen und ich konnte
die beiden Befehle sparen. Das muss aber nicht für jede
Produktionscharge gelten. )
Aus dem Hauptprogramm dürfen wir nun ein Unterprogram aufrufen.
rcall +-2047 (die nächste Adresse wird im Stack gespeichert und dann
zur Subroutine gesprungen)
Jedes einzelne Unterprogramm muss mit dem RET-Befehl enden.
RET (holt die Adresse vom Stack und arbeitet an der Stelle im
Hauptprogramm weiter)
Ist
der Stack definiert, kann man noch andere schöne Sachen machen.
Bei nur 8 Universalregistern (r16-r23) kann es schon mal eng werden.
Benötigt man den Inhalt von r19 später nocheinmal, aber das
Register könnte man gerade gut gebrauchen, dann legt man den
Inhalt auf den Stack und holt ihn später zurück.
Lege ein Reg. auf den Stack; push rd; code 1001 001d dddd 1111
Lade ein Register vom Stack; pop rd ; code 1001 000d dddd 1111
Die Befehle MÜSSEN paarweise verwendet werden! Sonstkommt der Stack
durcheinander.
Aufgabe: Bitte übersetzen Sie das folgende Programm und tippen es ein.
.DEVICE ATtiny13 ;für gavrasm, für Symbolische Registerbezeichnungen (z.B.'DDRB')
.cseg
.org 0
;
; Übung zu rcall und ret, Stack
;
; (#) weil Atmel spl schon ab werk auf 9F gesetzt hat,
; können die 2 ersten Befehle weggelassen werden.
; Das ist bei meinen AtTiny13 tatsächlich so, kann aber abweichen !
;
init: ;Vorbereitungen
; ldi r16,ramend ;der Assembler ist so nett und holt uns RAMEND=0x9F als Zahl in r16 (#)
; out spl,r16 ;spl=0x3D; out 61,159 (dezimal) (#)
;
sbi DDRB,1 ;PB1=OC0B als Ausgang setzen für den Piezo
;
main: ;Hauptprogramm
rcall sub_wait ;+1
rjmp main ;nochmal
;
sub_wait: ;Unterprogramm
adiw r24,1 ;r25/r24 hochzählen
out portb,r25 ;r25 ausgeben (toggle bei 512 = 1,2MHz/512=2343Hz)
brbc 1,sub_wait ;springe wenn Z=0
ret ;zurück ins Hauptprogramm (wenn R25/r24=0)
;
; Cyclen: (2+3+4)*1 (2+1+2)*512
; 1,2 MHz / 512 Toggle / 2 Puls+Pause / 5 Cyclen = 234Hz
;
Das Oszillogramm dazu (227Hz):
14.Tag
Wenn
wir einen Stack definiert haben, dann dürfen wir auch die
Interrupts benutzen. Am Beispiel der Tastenabfrage soll es erklärt
werden. Will man feststellen, ob eine Taste gedrückt oder
losgelassen ist, müsste man im Programm ständig den
Tasten-Port abfragen, damit ein Ereignis am Port rechtzeitig erkannt
wird. Die AVR gestatten es, von einem (mehreren) Port einen Interrupt
(=Unterbrechung) auszulösen. Ein Interruptprogramm arbeitet wie
ein Unterprogramm! Es wird jedoch NUR von der Hardware aufgerufen,
unterbricht das laufende Programm, führt Befehle aus die nur zum
Interrupt gehören und kehrt danach (mit RETI) ins laufende
Programm zurück. Zur Nutzung sind einige Vorbereitungen notwendig:
An der Adresse '0' muss die (Vector-) Tabelle stehen, die den
Aufruf des Interrupt-Programms steuert; vor dem Hauptprogramm (main)
müssen alle Vorbereitungen (Init) abgeschlossen sein; und alle
Interrupts ermöglicht (enable) werden (SEI).
Da BAScom seine eigene Vectortabelle erzeugt, müssen Programme mit
eigener Vectortabelle mit 'gavrasm' assembliert werden.
;-------------------------------------------------------------------------------
; Beim betätigen oder beim loslassen der Taste an PB.2 wird der Interrupt (int_pc) ausgelöst.
; Beim drücken wird die LED an PB.1 eingeschaltet und mit RETI ins Hauptprogramm zurückgekehrt.
; Beim loslassen wird nocheinmal ein Interrupt ausgelöst !, LED=aus und zurück.
;-------------------------------------------------------------------------------
; dieses Programm geht so nicht in BAScom !!!
;-------------------------------------------------------------------------------
.DEVICE ATtiny13 ; für gavrasm, für Symbolische Registerbezeichnungen (z.B.'DDRB')
.CSEG ; fürs CodeSegment
.org 0 ; für Adresse 0 übersetzen
;-------------------------------------------------------------------------------
rjmp init ; Reset vector
; nach einem Reset wird zu init gesprungen
;-------------------------------------------------------------------------------
reti ; Int0 interrupt vector
; hierhin wird nach auslösen eines Int0 gesprungen
;-------------------------------------------------------------------------------
int_pc: ; wird NUR vom Interrupt PCINT aufgerufen !!!
cbi PORTB,1 ; LED aus
sbic PINB,2 ; skip if sck=0
sbi PORTB,1 ; LED ein wenn sck=1
reti ; ins Hauptprogramm zurück
;-------------------------------------------------------------------------------
init: ; Vorbereitungen
sbi DDRB,1 ; LED als Ausgang
;
sbi pcmsk,2 ; Maske für PCINT auf PB.2 setzen
;
ldi r16,0b00100000 ; - 0 1 - - - - -
out GIMSK,r16 ; ^-^- PCINT Enable
;
sei ; Setze Interrupt Flag
;-------------------------------------------------------------------------------
main: ; Hauptprogramm
rjmp main ; Endlosschleife
;-------------------------------------------------------------------------------
Die
Interrupt-Routine wird so schnell abgearbeitet, dass kein Tastendruck
verloren geht. Können Sie das Programm übersetzen und
eintippen?
:020000020000FC
:10000000 05C0 1895 C19A B299 C198 1895 B99A AA9A 3B
:08001000 00E2 0BBF 7894 FFCF 62
:00000001FF
12 Befehle = 24 Byte
15.Tag
Es fehlt noch einen nützlicher Befehl um ganz wenig Zeit zu verzögern:
mach nix; nop; code 0000
(Er kann auch als Platzhalter dienen, wenn später noch etwas eingefügt
werden soll.)
Die
folgenden Beispiele sind so umfangreich, das Sie die Hilfe eines
SPI/ISP-Programmieradapter in Anspruch nehmen sollten. Um das Programm
aus dem Lernpaket micro von Herrn Kainka zu benutzen wird ein
vereinfachter Adapter benutzt.
Um
das nötige .HEX-File zu erhalten, empfehle ich Ihnen den Assembler
'gavrasm', weil es die Fehlermeldungen in deutsch ausgibt. Passend dazu
gibt es die IDE 'gavrasmW'.exe für Windows (bis Win7) Im Gegensatz
zu anderen Assemblern benötigen Sie keine .INC-Dateien, es
genügt '.DEVICE ATtiny13'.
Die Lösung von gestern als .LST-File:
gavrasm Gerd's AVR assembler version 3.3 (C)2012 by DG4FAC
----------------------------------------------------------
Quelldatei: 13uk-prog7.asm
Hexdatei: 13uk-prog7.hex
Eepromdatei: 13uk-prog7.eep
Kompiliert: 17.03.2013, 09:22:19
Durchgang: 2
1: ;-------------------------------------------------------------------------------
2: ; Beim betätigen oder beim loslassen der Taste an PB.2 wird der Interrupt (int_pc) ausgelöst.
3: ; Beim drücken wird die LED an PB.1 eingeschaltet und mit RETI ins Hauptprogramm zurückgekehrt.
4: ; Beim loslassen wird nocheinmal ein Interrupt ausgelöst !, LED=aus und zurück.
5: ;-------------------------------------------------------------------------------
6: ; dieses Programm geht so nicht in BAScom !!!
7: ;-------------------------------------------------------------------------------
8: .DEVICE ATtiny13 ; für gavrasm, für Symbolische Registerbezeichnungen (z.B.'DDRB')
9: .CSEG ; fürs CodeSegment
10: .org 0 ; für Adresse 0 übersetzen
11: ;-------------------------------------------------------------------------------
12: 000000 C005 rjmp init ; Reset vector
13: ; nach einem Reset wird zu init gesprungen
14: ;-------------------------------------------------------------------------------
15: 000001 9518 reti ; Int0 interrupt vector
16: ; hierhin wird nach auslösen eines Int0 gesprungen
17: ;-------------------------------------------------------------------------------
18: int_pc: ; wird NUR vom Interrupt PCINT aufgerufen !!!
19: 000002 9AC1 sbi PORTB,1 ; LED ein
20: 000003 99B2 sbic PINB,2 ; skip if sck=0
21: 000004 98C1 cbi PORTB,1 ; LED aus wenn sck=1
22: 000005 9518 reti ; ins Hauptprogramm zurück
23: ;-------------------------------------------------------------------------------
24: init: ; Vorbereitungen
25: 000006 9AB9 sbi DDRB,1 ; LED als Ausgang
26: ;
27: 000007 9AAA sbi pcmsk,2 ; Maske für PCINT auf PB.2 setzen
28: ;
29: 000008 E200 ldi r16,0b00100000 ; - 0 1 - - - - -
30: 000009 BF0B out GIMSK,r16 ; ^-^- PCINT Enable
31: ;
32: 00000A 9478 sei ; Setze Interrupt Flag
33: ;-------------------------------------------------------------------------------
34: main: ; Hauptprogramm
35: 00000B CFFF rjmp main ; Endlosschleife
36: ;-------------------------------------------------------------------------------
-> Warnung 001: 1 Symbol(e) definiert, aber nicht benutzt!
Programm : 12 words.
Konstanten : 0 words.
Programm Gesamt : 12 words.
Eepromnutzung : 0 bytes.
Datensegment : 0 bytes.
Kompilation fertig, keine Fehler.
Kompilation beendet 17.03.2013, 09:22:20
Zwischen
Adresse und Befehl finden Sie die Hex-Codes. Der Assembler erzeugt auch
ein .HEX-File, das über den Adapter und 'LPmikros.exe' geflasht
werden kann.
Wenn Sie den Programmier-Adapter nicht immer
entfernen wollen, können Sie PB.3 und PB.4 für LED und Taste
benutzen. Ändern Sie das Programm entsprechend ab. Der Kondensator
und die LED's an 'sck' und 'mosi' müssen unbedingt entfernt
werden, wenn der Adapter benutzt wird. Die LED an miso nur mit eigenem
Vorwidertand !!! kann ggf. bleiben.
Assemblieren (ändern) Sie alle bisherigen Programme und werden Sie
sicher im Umgang mit 'gavrasm' und 'LPmikro'.
Im
Bild sehen Sie einen Selbstbau-Adapter des LPmikro mit
zusätzlichem ISP6-Sockel. Noch ein Wort zu den vorgeschlagenen 10k
Schutzwiderständen: sie schützen die Eingänge des Tiny13
und sind für USB->RS232-Adapter mit etwa +-6V geeignet. Bei
direktem Anschluss an einen PC mit 'echter' RS232-Schnittstelle und
+-12V sollten Sie ggf. die Widerstände auf 22k-33k erhöhen.
Auch andere Bauteilwerte sind nur Richtwerte. Meine Vorwiderstände
für die highefficient LED konnte ich auf 4k7 !!! erhöhen. Im
nächsten Bild sehen Sie einen Adapter für AVR bis 28pol
(für LPmikro und ISP-Programmer-Mega8/ping-pong-Adapter).
Alle bisher gelernten Befehle:
.DEVICE ATtiny13 ;für gavrasm, für Symbolische Registerbezeichnungen (z.B.'DDRB')
.CSEG ;CodeSegment, muss nicht immer angegeben werden
.ORG 0 ;für Adresse '0', muss nicht immer angegeben werden
;--------------------------------Programmrumpf
rjmp init ;Reset vector
reti ;Int0 interrupt vector
rjmp int_pc ;PCINT0 vector
reti ;TC0 overflow vector
reti ;Eeprom ready vector
reti ;Analog comparator int vector
reti ;TC0 CompA vector
reti ;TC0 CompB vector
reti ;WDT vector
rjmp int_adc ;ADC conversion complete vector
int_pc: ;------------------------Pin-Change-Interrupt
in r13,pinb ;Lese PortB in R13
reti ;zurück ins Hauptprogramm
int_adc:;------------------------Aufruf NUR, wenn neuer Wert verfügbar
in r14,ADCh ;Lese ADC-Wert, obere 8Bit
reti ;zurück ins Hauptprogramm
init: ;------------------------nach Reset ;
ldi r16,ramend ;Stack vorbereiten
out spl,r16 ;auf RAMEND setzen
init_pc:;------------------------Vorbereitung PCInt
sbi pcmsk,2 ;Maske auf PB.2
ldi r16,0b00100000 ;- 0 1 - - - - -
out GIMSK,r16 ;pcie^=enable
init_adc:;-----------------------Vorbereitung adc-Int
ldi r16,0b00100010 ; - 0 1 - - - 1 0
Out Admux , R16 ;ref-^ ^adlar ^+^-adc2
ldi r16,0b00000000 ; - - - - - 0 0 0
Out Adcsrb , R16 ; ^+^+^-Free Run
ldi r16,0b11000111 ; 1 1 0 0 0 1 1 1
Out Adcsra , R16 ;ena-^ ^-start ^+^+^-prescale
init_timer:;---------------------Vorbereitung Timer
sbi DDRB,1 ;Piezo als Ausgang definieren
ldi r16,0b00010010 ; 0 0 0 1 - - 1 0
Out Tccr0a , R16 ;toggleB-^ ^+^=ctc =Timer als Zähler
ldi r16,0b00000010 ; 0 0 - - 0 0 1 0
Out Tccr0b , R16 ; ^+^+^-prescale 1/8
init_sleep:;---------------------Vorbereitung Sleep-Mode
ldi r16,0b00100000 ; 0 0 1 0 0 0 0 0 SLEEP-Mode=Idle vorbereiten
out MCUCR,r16 ;enable-^ ^-^-Mode 00=idle (10=power down)
;--------------------------------
sei ;Set Enable Interrupt (-Flag)
main: ;------------------------Hauptprogramm
rjmp main ;Endlos-Schleife
;--------------------------------Subroutines
sub_wait1: ;Unterprogramm Wait Byte (3+3*256-1+4)=645us
subi r23,1 ;R23 255-0 (1)
brbc 1,sub_wait1 ;springe wenn Z=0 (1/2)
ret ;zurück ins Hauptprogramm (wenn R23=0) (4)
;--------------------------------
sub_wait2: ;Unterprogramm Wait Word (3)(3+4*65536-1+4)=0,22s
sbiw r24,1 ;r25/r24 65535-0 (2)
brbc 1,sub_wait2 ;springe wenn Z=0 (1/2)
ret ;zurück ins Hauptprogramm (wenn R25/r24=0)(4)
;--------------------------------End Program
;--------------------------------
Tag01: ;
rjmp Tag01 ;Endlos-Schleife
Tag02: ;
sbi ddrb,1 ;set bit I/O-Reg(Low),Bit
cbi portb,1 ;clear bit I/O-Reg(Low),Bit
Tag03: ;
out portb,r16 ;I/O-Reg mit dem Byte in R16 laden
in r16,pinb ;R16 mit dem Byte des I/O-Reg laden
Tag06: ;
ldi r16,7 ;lade R16 mit 7(dez)(Byte)
mov r16,r17 ;lade R16 mit dem Byte in R17
movw r24,r26 ;R24<=R26 und R25<=R27 (Word)
Tag07: ;
add r16,r17 ;R16<=R16+R17 ->SReg
adiw r24,15 ;R24<=R24+15 ->Sreg
Tag08: ;
sub r16,r17 ;R16=R16-R17 ->SReg
subi r16,31 ;R16<=R16-31 ->SReg
sbiw r24,1 ;R24(R25)<=R24(R25)-1 ->SReg
Tag09: ;
inc r16 ;R16<=R16 +1 ->SReg
dec r16 ;R16<=R16 -1 ->SReg
neg r16 ;R16<=R16 * -1 ->SReg
com r16 ;invertiere alle Bits im R16 ->SReg
Tag10: ;
asr r16 ;R16<=R16/2 ->Sreg
lsr r16 ;R16<=R16/2 ->Sreg
adc r16,r17 ;R16<=R16+R17+Carry ->Sreg
ror r16 ;R16<=R16/2+128*C ->SReg
Tag11: ;
and r16,r17 ;R16<=R16 AND R17 ->SReg
eor r16,r17 ;R16<=R16 EXOR R17 ->SReg
or r16,r17 ;R16<=R16 OR R17 ->SReg
ori r16,5 ;R16<=R16 OR 0101 ->SReg
andi r16,5 ;R16<=R16 AND 0101 ->SReg
TAG12: ;
brbc 1,10 ;springe 10 weiter, wenn SREG.1=0 -> Z=0
brbs 0,-5 ;springe 5 zurück, wenn SReg.0=1 -> C=1
cpse r16,r17 ;skip if R16=R17
sbic portb,1 ;skip if PortB.1=0; Einzelbit-Prüfung
sbis portb,2 ;skip if PortB.2=1; Einzelbit-Prüfung
sbrc r16,3 ;skip if R16.3=0; Einzelbit-Prüfung
sbrs r16,4 ;skip if R16.4=1; Einzelbit-Prüfung
Tag13: ;
rcall 9 ;Unterprogrammaufruf
ret ;Rücksprung ins Hauptprogramm
push r16 ;(spl)<=R16
pop r16 ;R16<=(spl)
Tag14: ;
sei ;set enable interrupt
reti ;Rücksprung vom Interrupt ins Hauptprogramm
Tag15:
nop ;no operation
16.Tag
Bei
den Beispielen mit BAScom wurde stillschweigend vorausgesetzt, dass Sie
über einen Adapter (STK500 oder USBASP) verfügen. Sie
können jedoch die .HEX-Files auch überkreuz zum Flashen
benutzen. Das bedeutet:
in BAScom programmiert, .HEX-File mit LPmikro und RS232-Adapter
geflasht oder
in gavrasm programmiert, .HEX-File in BAScom laden und mit
STK500/USBASP flashen.
Für
die Verwendung mit der Tasten-Eingabe wurden möglichst wenige
Befehle ausgesucht. Mit dem SPI/ISP- Adapter kann etwas sauberer
programmiert werden. Im nächsten Beispiel soll der ADC-Wandler
abgefragt werden. Damit wir auch was merken, wird damit die
Tonhöhe am Piezo verändert.
; Übung zum AD-Wandler,
;
; init_timer stellt PB.1 auf toggle, CTC-Mode (OCR0A) und den Prescaler ein,
; init_adc stellt Uref auf Vcc, adlar=1 => 8Bit in ADCH, ADC(2), free run und Autostart.
; Das Hauptprogramm tut dann nichts mehr, Wandlung und Ausgabe geschieht in int_adc.
;
; Nach jeder Wandlung wird int_adc ausgelöst, der Wert mit 7 maskiert und in OCR0A geladen.
; Da die Frequenz eine 1/x-Funktion ist, entspricht Uadc=0V der höchsten Frequenz.
; Die maskierung mit 7 reduziert die Einstellung auf 32 Stufen und die Werte auf 7..255.
;
;
;
.DEVICE ATtiny13 ; für gavrasm, für Symbolische Registerbezeichnungen (z.B.'DDRB')
.cseg ; für den Flash-Speicher
.org 0 ; für Adresse 0
rjmp init ; Reset vector
reti ; Int0 interrupt vector
reti ; PCINT0 vector
reti ; TC0 overflow vector
reti ; Eeprom ready vector
reti ; Analog comparator int vector
reti ; TC0 CompA vector
reti ; TC0 CompB vector
reti ; WDT vector
rjmp int_adc ; ADC conversion complete vector
Int_adc: ;
in r16,adch ; obere 8Bit holen
ori r16,0b00000111 ; keine Zahl kleiner 7
Out Ocr0a , r16 ; compare A laden
reti ; Return from Interrupt
init: ;
ldi r16,LOW(RAMEND) ; Stapelzeiger auf Ende SRAM
Out Spl , R16 ;
sbi DDRB,1 ; Piezo
init_timer: ;
ldi r17,0b00010010 ; 0 0 0 1 0 0 1 0
Out Tccr0a , R17 ; ocA ocB ^---CTC
ldi r17,0b00000011 ; 0 0 0 0 0 0 1 1
Out Tccr0b , R17 ; ^-^-prescaler 1-5
init_adc: ;
ldi r16,0b00100010 ; 0 0 1 0 0 0 1 0
Out Admux , R16 ; ref-^ ^-adlar ^-^-adc(0-3)
ldi r16,0b00000000 ; 0 0 0 0 0 0 0 0
Out Adcsrb , R16 ; ^-^-^-free run
ldi r16,0b11101111 ; 1 1 1 0 1 1 1 1
Out Adcsra , R16 ; ^-^-^-prescaler, ADC starten, Autostart
sei ; Set Interrupt Flag
main: ;
rjmp main ; Endlosschleife
;
Mit
ADLAR=1 wird der ADC-Wert auf die oberen 8Bit im ADCh-Register
eingestellt. Vom Interrupt-Programm wird ein neuer ADC-Wert direkt
(ohne Hauptprogramm) in den Vergleicher (Compare) des Timer geladen. Im
CTC-Mode zählt der Timer hoch, bis der Wert im Vergleichsregister
(OCR0A=ADCH) erreicht ist. Dann wird PortB (getoggelt) umgeschaltet
(0->1 oder 1->0). Bei kleinen ADC-Werten wird früh
getoggelt
(hohe Frequenz). Damit die Frequenzen nicht zu hoch werden, wird jeder
ADC-Wert mit 7 Oder verknüpft. Dadurch kommen in OCR0A nur Werte
von 7 bis 255 an.
Die Frequenz: 9,6MHz / 8(devide) / 8(prescale) / 2(toggle) / OCR0A =
75000Hz / 255 = 294Hz
75000Hz / 8 = 9375Hz
Die hohe Frequenz wird nicht ganz erreicht, weil die Ladebefehle nicht
berücksichtigt sind.
17.Tag
Der
aufmerksame Leser fragt sich, wo das Programm 2 geblieben ist. Durch
auslagern von Aufgaben ist das Hauptprogramm häufig arbeitslos. In
diesem Fall ist der Sleep-Mode ideal.
Nachdem ich mir eine
Übung ausgedacht hatte, schlief mein Tiny13 so tief und fest, das
ich ihn mit BAScom nicht wecken konnte. Grund war der fehlende
Interrupt. Mit dem aktive Adc kann das nicht mehr passieren.
init_sleep:
;
SLEEP-Mode=Idle vorbereiten
ldi r16,0b00100000
; 0 0 1 0
0 0 0 0
out
MCUCR,r16
; enable-^ ^-^-Mode 00=idle (10=power down)
main:
;
sleep
; würde
eigentlich reichen, aber wenn er
nop
;
wachgeworden !
rjmp main
; muss er wieder schlafen gelegt werden
Im
Idle-Mode wird nur die Befehlsausführung gestoppt, Timer, ADC,
Ports usw. arbeiten weiter. Im Power-Down-Mode bleiben nur die
Hardware-Interrupts (Reset, Int0) wach, alles andere wird stromlos.
; Übung zum Sleep-Mode Idle, mit AD-Wandler,
;
; init_timer stellt PB.1 auf toggle, CTC-Mode (OCR0A) und den Prescaler ein,
; init_adc stellt Uref auf Vcc, adlar=1 => 8Bit in ADCH, ADC(2), free run und Autostart.
; Das Hauptprogramm tut dann nichts mehr, Wandlung und Ausgabe geschieht in int_adc.
;
; Nach jeder Wandlung wird int_adc ausgelöst, der Wert mit 7 maskiert und in OCR0A geladen.
; Da die Frequenz eine 1/x-Funktion ist, entspricht Uadc=0V der höchsten Frequenz.
; Die maskierung mit 7 reduziert die Einstellung auf 32 Stufen und die Werte auf 7..255.
;
; Die Frequenz: 9,6MHz / 8(devide) / 64(prescale) / 2(toggle) / OCR0A = 9375Hz / OCR0A
; PPB.1=Piezo, PB.4=Adc(2)=Poti
;
.DEVICE ATtiny13 ; für gavrasm, für Symbolische Registerbezeichnungen (z.B.'DDRB')
.cseg ; für den Flash-Speicher
.org 0 ; für Adresse 0
rjmp init ; Reset vector
reti ; Int0 interrupt vector
reti ; PCINT0 vector
reti ; TC0 overflow vector
reti ; Eeprom ready vector
reti ; Analog comparator int vector
reti ; TC0 CompA vector
reti ; TC0 CompB vector
reti ; WDT vector
rjmp int_adc ; ADC conversion complete vector
Int_adc: ;
in r16,adch ; obere 8Bit holen
ori r16,0b00000111 ; keine Zahl kleiner 7
Out Ocr0a , r16 ; compare A laden
reti ; Return from Interrupt
init: ;
ldi r16,LOW(RAMEND) ; Stapelzeiger auf Ende SRAM
Out Spl , R16 ;
sbi DDRB,1 ; Piezo
init_timer: ;
ldi r17,0b00010010 ; 0 0 0 1 0 0 1 0
Out Tccr0a , R17 ; ocA ocB ^---CTC
ldi r17,0b00000011 ; 0 0 0 0 0 0 1 1
Out Tccr0b , R17 ; ^-^-prescaler 1-5
init_adc: ;
ldi r16,0b00100010 ; 0 0 1 0 0 0 1 0
Out Admux , R16 ; ref-^ ^-adlar ^-^-adc(0-3)
ldi r16,0b00000000 ; 0 0 0 0 0 0 0 0
Out Adcsrb , R16 ; ^-^-^-free run
ldi r16,0b11101111 ; 1 1 1 0 1 1 1 1
Out Adcsra , R16 ; ^-^-^-prescaler, ADC starten, Autostart
sei ; Set Interrupt Flag
init_sleep: ; SLEEP-Mode=Idle vorbereiten
ldi r16,0b00100000 ; 0 0 1 0 0 0 0 0
out MCUCR,r16 ; enable-^ ^-^-Mode 00=idle (10=power down)
main: ;
sleep ; würde eigentlich reichen, aber wenn er
nop ; wachgeworden !
rjmp main ; muss er wieder schlafen gelegt werden
;
Der
Stromverbrauch sinkt in diesem Beispiel nur wenig. Bei anderen
Anwendungen und Batteriebetrieb kann Sleep sehr sinnvoll sein.
Bitte
gewöhnen Sie sich an eine ausführliche Kommentierung. Schon
nach wenigen Tagen/Wochen weiss man nicht mehr warum es so und nicht
anders programmiert ist.
Trauen Sie sich, den Sleep-Mode in
andere Programme einzubauen? Falls BAScom nicht reagiert, keine Panik,
mit der Tasten-Sequenz: Prog Enable und Chip erase vom 5.Tag holen Sie
ihn zurück.
18.Tag
Heute
können Sie kreativ werden. Entwickeln Sie ein Programm, mit dem
Sie die oberen 4 Bit des AD-Wandlers auf 4 LEDs an PortB.0 bis PortB.3
ausgeben.
Viel Spaß!
19.Tag
Das Problem von gestern kann auf verschiedene Weisen gelöst werden.
Hier nur zwei Beispiele:
; Lösung zu Tag 18 (es gibt natürliche mehrere Ansätze)
;
; init_adc stellt Uref auf Vcc, adlar=1 => 8Bit in ADCH, ADC(2), free run und Autostart.
; Das Hauptprogramm tut dann nichts mehr, Wandlung und Ausgabe geschieht in int_adc.
;
; Nach jeder Wandlung wird int_adc ausgelöst, der Wert nach links verschoben und in PortB geladen.
;
.DEVICE ATtiny13 ; für gavrasm, für Symbolische Registerbezeichnungen (z.B.'DDRB')
.cseg ; für den Flash-Speicher
.org 0 ; für Adresse 0
rjmp init ; Reset vector
reti ; Int0 interrupt vector
reti ; PCINT0 vector
reti ; TC0 overflow vector
reti ; Eeprom ready vector
reti ; Analog comparator int vector
reti ; TC0 CompA vector
reti ; TC0 CompB vector
reti ; WDT vector
rjmp int_adc ; ADC conversion complete vector
Int_adc: ;
in r16,adch ; obere 8Bit holen
lsr r16 ; nach unten verschieben 0xxxx___
lsr r16 ; nach unten verschieben 00xxxx__
lsr r16 ; nach unten verschieben 000xxxx_
lsr r16 ; nach unten verschieben 0000xxxx
Out portb,r16 ; compare A laden
reti ; Return from Interrupt
init: ;
ldi r16,LOW(RAMEND) ; Stapelzeiger auf Ende SRAM
Out Spl , R16 ;
ldi r16,15 ; PB.0-3 auf Ausgang
out DDRB,r16 ;
init_adc: ;
ldi r16,0b00100010 ; 0 0 1 0 0 0 1 0
Out Admux , R16 ; ref-^ ^-adlar ^-^-adc(0-3)
ldi r16,0b00000000 ; 0 0 0 0 0 0 0 0
Out Adcsrb , R16 ; ^-^-^-free run
ldi r16,0b11101111 ; 1 1 1 0 1 1 1 1
Out Adcsra , R16 ; ^-^-^-prescaler, ADC starten, Autostart
sei ; Set Interrupt Flag
main: ;
rjmp main ; Endlosschleife
;
' 13UK, Programm 10
' Die oberen 4 Bit des Adc werden binär an PB.0-3 angezeigt
'
'-------------------------------------------------------------------------------
' alle Fuse-Byte ab Werk
' kompiliert mit Bascom-Demo 2.0.7.1 (sollte auch mit 2.0.7.5 funktionieren)
'-------------------------------------------------------------------------------
$regfile = "attiny13.dat"
$crystal = 1200000 ' 9,6MHz/8
$hwstack = 8
'init
Config Adc = Single , Prescaler = Auto
Ddrb = 15 'PortB.1 - PortB.3
Dim Temp As Word
Do 'Hauptprogramm
Temp = Getadc(2) 'Wert holen
Temp = Temp / 64 'auf 4 Bit reduzieren
Portb = Temp 'und ausgeben
Loop 'nochmal
End
' End Program
Der
Timer kann nicht nur als Zähler/Teiler arbeiten, sondern auch
PWM-Signale erzeugen. PWM bedeutet das Puls und Pause nicht die gleiche
Breite haben. Beim Piezo würden wir die Unterschiede kaum
hören. Eine LED zeigt jedoch breitere Impulse (schmalere Pausen)
durch grössere Helligkeit an.
Der Tiny13 erlaubt 2
PWM-Mode, die sehr ähnlich sind. Im Fast-PWM (wie der Name schon
sagt) zählt der Timer immer von 0-255 und dann wieder von 0-255.
Bei erreichen des Vergleichswertes wird der Ausgang umgeschaltet.
f(fast) = fosc / prescaler / 256
Beim
Phase-Correct-Mode wird der Timer von 0-255 gezählt und dann
wieder zurück 255-0. Bei erreichen des Vergleichswertes wird der
Ausgang umgeschaltet.
f(phase) = fosc / prescaler / 510 (510 da 0 und 255 je nur einmal
vorkommt)
Phase Correct PWM:
; Phase-Correct-PWM
;
; f(phase)=1,2MHz / prescaler / 510 = 2353Hz
;
.DEVICE ATtiny13 ;für gavrasm, für Symbolische Registerbezeichnungen (z.B.'DDRB')
.cseg
.org 0
;Vector-Tabelle
rjmp init ;Reset vector
reti ;Int0 interrupt vector
reti ;PCINT0 vector
reti ;TC0 overflow vector
reti ;Eeprom ready vector
reti ;Analog comparator int vector
reti ;TC0 CompA vector
reti ;TC0 CompB vector
reti ;WDT vector
rjmp int_adc ;ADC conversion complete vector
Int_adc: ;Aufruf NUR, wenn neuer Wert verfügbar
in r14,ADCh ;Lese ADC-Wert, obere 8Bit
Out Ocr0b,R14 ;Pulslänge
reti ;zurück ins Hauptprogramm
init: ;Vorbereitungen
sbi ddrb,1 ;Piezo als Ausgang definieren
ldi r16,ramend ;Stack vorbereiten
out spl,r16 ;
init_pwm: ;
ldi r16,0b00100001 ; 0 0 1 0 - - 0 1
Out Tccr0a , R16 ;A-^-^ ^-^-B ^+^=PWM
ldi r16,0b00000001 ; 0 0 - - 0 0 0 1
Out Tccr0b , R16 ; PWM-^ ^+^+^-prescale
init_adc: ;
ldi r16,0b00100010 ; - 0 1 - - - 1 0
Out Admux , R16 ;ref-^ ^adlar ^+^-adc2
ldi r16,0b00000000 ; - - - - - 0 0 0
Out Adcsrb , R16 ; ^+^+^-Free Run
ldi r16,0b11101111 ; 1 1 1 0 1 1 1 1
Out Adcsra , R16 ;ena-^ ^-start ^+^+^-prescale
sei ;Set Enable Interrupt (-Flag)
main: ;Hauptprogramm
rjmp main ;
Im praktischen Gebrauch sind beide Modes kaum zu unterscheiden.
20.Tag
Die
Lösung in Basic macht fast das gleiche. Die Abweichung der
Frequenz kommt daher, weil in BAScom Getadc(2) nicht per Interrupt
sondern einzeln aufgerufen wird.
' 13UK, Programm 11 PWM
' Die Helligkeit der LED an PB.1 wird mit dem Poti von 0%-100% eingestellt
'
'-------------------------------------------------------------------------------
' alle Fuse-Byte ab Werk
' kompiliert mit Bascom-Demo 2.0.7.1 (sollte auch mit 2.0.7.5 funktionieren)
'-------------------------------------------------------------------------------
$regfile = "attiny13.dat"
$crystal = 1200000 ' 9,6MHz/8
$hwstack = 8
'init
Config Adc = Single , Prescaler = Auto
Config Timer0 = Pwm , Compare B Pwm = Clear Up , Prescale = 1
Ddrb = 2 'PortB.1
Dim Temp As Word
Do 'Hauptprogramm
Temp = Getadc(2) 'Wert holen
Temp = Temp / 4 'auf 8 Bit reduzieren
Pwm0b = Temp 'und ausgeben
Loop 'nochmal
End
' End Program
Fast-PWM:
; Fast-PWM
;
; f(fast)=1,2MHz / prescaler / 256 = 4687,5Hz
;
.DEVICE ATtiny13 ;für gavrasm, für Symbolische Registerbezeichnungen (z.B.'DDRB')
.cseg
.org 0
;Vector-Tabelle
rjmp init ;Reset vector
reti ;Int0 interrupt vector
reti ;PCINT0 vector
reti ;TC0 overflow vector
reti ;Eeprom ready vector
reti ;Analog comparator int vector
reti ;TC0 CompA vector
reti ;TC0 CompB vector
reti ;WDT vector
rjmp int_adc ;ADC conversion complete vector
Int_adc: ;Aufruf NUR, wenn neuer Wert verfügbar
in r14,ADCh ;Lese ADC-Wert, obere 8Bit
Out Ocr0b,R14 ;Pulslänge
reti ;zurück ins Hauptprogramm
init: ;Vorbereitungen
sbi ddrb,1 ;Piezo als Ausgang definieren
ldi r16,ramend ;Stack vorbereiten
out spl,r16 ;
init_pwm: ;
ldi r16,0b00100011 ; 0 0 1 0 - - 1 1
Out Tccr0a , R16 ;A-^-^ ^-^-B ^+^=Fast-PWM
ldi r16,0b00000001 ; 0 0 - - 0 0 0 1
Out Tccr0b , R16 ; PWM-^ ^+^+^-prescale
init_adc: ;
ldi r16,0b00100010 ; - 0 1 - - - 1 0
Out Admux , R16 ;ref-^ ^adlar ^+^-adc2
ldi r16,0b00000000 ; - - - - - 0 0 0
Out Adcsrb , R16 ; ^+^+^-Free Run
ldi r16,0b11101111 ; 1 1 1 0 1 1 1 1
Out Adcsra , R16 ;ena-^ ^-start ^+^+^-prescale
sei ;Set Enable Interrupt (-Flag)
main: ;Hauptprogramm
rjmp main ;
Falls der Piezo noch angeschlossen ist, hören Sie den Unterschied.
Der
AD-Wandler und sein Innenwiderstand: Am Eingang sorgt eine Sample and
Hold Schaltung dafür, dass der Messwert während der
Wandlungszeit gespeichert bleibt. Wenn der Wandler eine Messung nach
der anderen macht, ergibt sich ein Innenwiderstand von ~1-10MOhm. Ruft
man jedoch getadc() etwa im Sekundentakt auf, erhöht sich der
Innerwiderstand um den Faktor 1000! Auf diese Weise kann man z.B. aus
Fotodioden auch das letzte Elektron rausholen.
21.Tag
Die
letzte Übung soll zeigen, wie ein Sägezahn mit PWM erzeugt
wird. Hier kann einfach ein Register hochgezählt werden. Andere
Kurvenformen werden mit vorberechneten Werten erzeugt.
; Sägezahn durch Pulsbreiten-Modulation
;
; Fast-PWM
;
; f(fast)=1,2MHz / prescaler / 256 = 4687,5Hz
;
.DEVICE ATtiny13 ;für gavrasm, für Symbolische Registerbezeichnungen (z.B.'DDRB')
.cseg
.org 0
init: ; Vorbereitungen
sbi ddrb,1 ; Ausgang definieren
; ldi r16,ramend ; Stack vorbereiten
; out spl,r16 ;
init_pwm: ;
ldi r16,0b00100011 ; 0 0 1 0 - - 1 1
Out Tccr0a , R16 ; A-^-^ ^-^-B ^+^=Fast-PWM
ldi r16,0b00000001 ; 0 0 - - 0 0 0 1
Out Tccr0b , R16 ; PWM-^ ^+^+^-prescale
main: ; Hauptprogramm
adiw r24,1 ; r25/r24 hochzählen
out ocr0b,r25 ; r25 ausgeben, ergibt Sägezahn durch aufwärtszählen
rjmp main ;
(Für das Oszilloskop wurde die devide-Fuse abgestellt =9,6MHz.)
Sie
haben nun alles gelernt, was ich geplant hatte. Ich hoffe Sie hatten
viel Spaß, auch wenn ich viel von Ihnen verlangt habe. Auf jeden
Fall können Sie nun am CONTEST teilnehmen.
Scheuen Sie
sich nicht, Ihre Problemlösungen vorzustellen, es gibt keine
'schlechten' Programme. Der Olympische Gedanke zählt.
Zum Schluss noch zwei Ideen und ich hoffe Sie
bleiben am 'ASM-Ball'.
Würfel: Die Taste ist an PB.4 nach gnd geschaltet, die LED für 1,
2(+3), 2(4+6), 2(6) an PB.0-3.
;
;Würfel
;
;LED an PB0-PB3 gegen GND
;Taste an PB4 gegen GND
;
; --- ---
;|PB1| |PB2| in Reihe
; --- ---
; --- --- ---
;|PB3| |PB0| |PB3| in Reihe
; --- --- ---
; --- ---
;|PB2| |PB1| in Reihe
; --- ---
;
.DEVICE ATtiny13 ;für gavrasm, für Symbolische Registerbezeichnungen (z.B.'DDRB')
.cseg
.org 0
init:
; ldi r16,ramend ;der Assembler ist so nett und holt uns RAMEND=0x9F als Zahl in r16 (#)
; out spl,r16 ;spl=0x3D; out 61,159 (dezimal) (#)
ldi r16,15 ;PB.0 bis PB.3 als Ausgang
out ddrb,r16 ;0b00001111
sbi portb,4 ;Pullup für Taste
main:
eins:
ldi r16,1 ;0b0001
rcall sub_wait ;
zwei:
ldi r16,2 ;0b0010
rcall sub_wait ;
drei:
ldi r16,5 ;0b0101
rcall sub_wait ;
vier:
ldi r16,6 ;0b0110
rcall sub_wait ;
funf:
ldi r16,7 ;0b0111
rcall sub_wait ;
sechs:
ldi r16,14 ;0b1110
rcall sub_wait ;
rjmp main ;alles nochmal
;
sub_wait: ; Unterprogramm
out portb,r16 ; ausgeben
adiw r24,8 ; r25/r24 hochzählen
brbc 1,sub_wait ; springe wenn Z=0
sbis PINB,4 ; Ueberspringe Befehl wenn PB4 eins
ret ; zurück ins Hauptprogramm (wenn R25/r24=0)
sub_wait1: ;
sbic PINB,4 ; Ueberspringe Befehl wenn PB4 null
rjmp sub_wait1 ;
ret ;
ende:
Ampel: Die Zeiten können mit den 4 Ladebefehlen (ldi R22,x) geändert
werden.
;
;Ampel an PB0-PB3 (by Heinz D.; ausser Konkurenz)
;
;Um mit je 2 Leitungen pro Ampel auszukommen, müssen
;die LED in bestimmter Weise in Reihe geschaltet werden.
;
; rot grün gelb
;+5V o--|>|--+--|>|--+--|>|--o GND
; ___ | | ___
;PB0 o-|___|-+ +-|___|-o PB1
;
; rot grün gelb
;+5V o--|>|--+--|>|--+--|>|--o GND
; ___ | | ___
;PB2 o-|___|-+ +-|___|-o PB3
;
; Ampel 1 Ampel 2
;Phasen: PB3 PB2 PB1 PB0
;2(8)rot /grün 0 0 1 0
;3(8)rot /gelb 0 0 1 1
;4(8)rot /rot 0 0 0 0
;5(8)rot+gelb/rot 0 1 0 0
;6(8)grün /rot 1 0 0 0
;7(8)gelb /rot 1 1 0 0
;8(8)rot /rot 0 0 0 0
;1(8)rot /rot+gelb 0 1 0 0
;
;
;R21 in 2,5 us-Schritten, 0=640us
;R25<=R22 in 2,56ms *256 =0,655s-Schritte
;
;
.DEVICE ATtiny13 ;für gavrasm, für Symbolische Registerbezeichnungen (z.B.'DDRB')
.cseg
.org 0
init:
ldi r16,15 ;PB.0 bis PB.3 als Ausgang
out ddrb,r16 ;0b00001111
main:
ldi r16,2 ;Phase 2(8) rot/grün
out portb,r16 ;
ldi r22,16 ;2,56ms *256 *16 =10,5s
rcall wait ;warte lang
ldi r16,3 ;Phase 3(8) rot/gelb
out portb,r16 ;
ldi r22,4 ;2,56ms *256 *4 =2,6s
rcall wait ;warte kurz
ldi r16,0 ;Phase 4(8) rot/rot
out portb,r16 ;
rcall wait ;warte kurz
ldi r16,4 ;Phase 5(8) rot+gelb/rot
out portb,r16 ;
rcall wait ;warte kurz
ldi r16,8 ;Phase 6(8) grün/rot
out portb,r16 ;
ldi r22,16 ;2,56ms *256 *16 =10,5s
rcall wait ;warte lang
ldi r16,12 ;Phase 7(8) gelb/rot
out portb,r16 ;
ldi r22,4 ;2,56ms *256 *4 =2,6s
rcall wait ;warte kurz
ldi r16,0 ;Phase 8(8) rot/rot
out portb,r16 ;
rcall wait ;warte kurz
ldi r16,1 ;Phase 1(8) rot/rot+gelb
out portb,r16 ;
rcall wait ;warte kurz
rjmp main ;alles nochmal
wait:
mov r25,r22 ;Wartezeit laden
sub_wait2:
subi r21,1 ;1200000MHz /256 /3 =1563Hz =640us
brbc 1,sub_wait2 ;wenn R21=0 dann sub_wait1 (1+2)
sub_wait1: ;
sbiw r24,1 ;640us *4 =2,56ms * R24(word) (max.168s)
brbc 1,sub_wait2 ;wenn R24=0 dann return (2+2)
ret ;zurück ins Hauptprogramm (4)
ende:
Übersicht:
Programm ASM BAS
-------------------------
1 LED on X X
3 Piezo X X
4 Blink 2Hz X X
5 Zufall X X
6 Stack+Sub X X
7 PCINT0 X --- wg. Vectortabelle
8 Adc+Timer X X
9 Sleep X ---
10 Adc-binär X X
11 Phase-PWM X X
12 Fast-PWM X ---
13 Sägezahn X X
Ampel X ---
-------------------------
Die Beispiele in Assembler und BAScom: 13uk-asm-hex-lst.zip
13uk-bas-hex.zip
Dieser Kurs als PDF: AssemblerTiny13.pdf
Siehe auch: Der
Tiny13-Programmierwettbewerb
Ergänzungen
Die 41 verwendeten Befehle
Liste bekannter Fehler
Tag
9: in der Tabelle sind die Binär-Zahlen richtig, in der
Hex-Darstellung (rechts) haben sich 2 kleine Adress-Fehler
eingeschlichen.
Tag 12: In der Tabelle 'Beispiel 1.Durchlauf' ist ganz unten 'oii' kein
lustiger neuer Befehl, sondern 'ori' gemeint.
Tag
13: Im .asm-Listing, 3. Befehl von unten, hinter 'out portb,r25', ist
die Kommentierung missverständlich. Darunter ist die
Frequenz-Berechnung mit Cyclen korrekt.
Tag
19, Listing Lösung, Int_adc: ..out portb,r16, der Kommentar ist falsch,
hier werden die oberen 4 Bit des ADC auf den LED an PortB.0-3 angezeigt.
Tag
21, im letzten Listing Seite 46 ist die Ampel-Phasentabelle im Kopf
falsch, richtig sind die Werte im Programm Seite 47: 2,3, 0,4,8,12, 0,1
(immer im Kreis herum)
Fuses verstellt
Ich
weiss nicht wie, aber ich habe es geschafft, die Fuse auf externen Takt
unzustellen. Bascom wollte dann nichts mehr mit dem Tiny13 zu tun
haben.
Mit
den zwei Tasten habe ich zunächst das Fusebyte ausgelesen und
gesehen, dass es nicht stimmt. Dann habe ich die Werks-Fuse '6A'
geschrieben, und zur Sicherheit nocheinmal gelesen. Und siehe da --
Bascom wollte wieder mit ihm arbeiten. Auch hier brauchte ich mehrere
Anläufe bis MISO richtig antwortete.
Siehe auch: Tiny13-Servotester
Erfahrungen und Fragen
von Tim
Mit
Interesse verfolge ich Ihren Kurs. Ich habe noch ein paar Fragen. Der
Aufbau funtioniert soweit. Alle probierten Taster prellten, sodass ich
den Aufbau von Hr. Nieder verwende, aber mit 1µF- Elkos. Jetzt
funtioniert's !
Wenn ich, die am 4.Tag beschriebene
Sequenz eingebe, erscheint tatsächlich die MISO- LED. Sie haben es als
Prog- Enable bezeichnet.
Wenn ich aber das Assembler- Prog. vom 2.
Tag eingeben will gibt es Probleme. Was muss ich nacheinander eingeben?
Muss die gesamte Tabelle eingegeben werden?
1. Zeile: Prog- Enable 1010 1100 0101
0011 xxxx xxxx xxxx xxxx 'müssen hier Nullen mit eingegeben werden ?
2.
Zeile:
? ?
? ?
0101 0011 ?
? 'MISO- Antwort ?
3.
Zeile: Chip löschen 1010 1100
100x
? ?
?
? ?
Programm:
sbi ddrb,1 = 1001 1010 1011 1001
sbi portb,1 = 1001 1010 1100 0001
rjmp -1 = 1100 1111
1111 1111
Was
gebe ich statt des "x" ein 0 oder 1 ? In Zeile 9 steht auch
ein
"a", wofür ? Teilweise sind nur 4 Bit beschrieben, was ja auch
rechnerisch passt.
Antworten
Das 'x' steht für 'egal', faulerweise wird dann die MOSI-Taste nicht
gedrückt (x=0).
Mensch
und Maschine (AVR) werden AUSSCHLIESSLICH über Sck synchronisiert,
deshalb muss JEDE Sequenz (Eingabe-Zeile) aus GENAU 32 Sck-Impulsen
bestehen (4 Byte = 4*8 Bit = 32).
Der Beginn einer
BEFEHLS-Sequenz (im Sinne von ISP, nicht AVR-Programmbefehle) fängt
IMMER mit einer '1' (im 1.Byte) an. Folge-Daten-Sequenzen fangen dann
mit '0' (im 1.Byte) an.
Die Tabellen sind meist 2-zeilig: in
der MOSI-Zeile sind die Eingabe-Werte, in der MISO-Zeile die (bekannte,
dazu passende) zu erwartende Ausgabe, immer (1 Bit) vor betätigen der
Sck-Taste.
Die Kleinbuchstaben zwischen den Nullen und Einsen
sollte Sie nicht verwirren, sondern sind auch im Datenblatt (17.6.2)
als allgemeine Beschreibung wiederzufinden. Zum Beispiel: H = Page, a:b
= Adresse, o = Out-Daten=MISO, i = In-Daten=MOSI (Anfänger überlesen es
einfach)
Eine Frage war noch: Geht das auch mit AtMega's? JA,
mit allen Tiny's und Mega's! Die AVR-Befehle sind sowieso gleich! Die
ISP-Befehle können abweichen (ggf. im Datenblatt nachsehen).
Tipp:
Wiederholen Sie das Lesen der Signaturbyte, bis Sie ganz sicher sind.
Und schauen Sie am Ende der Webseite bei 'bekannte Fehler' nach,
Vielleicht lindert das auch Verwirrungen.
Tastenentprellung
mit Tiny13, von H. Fritzsch
Ich habe mich mit Interesse mit dem ATTINY13-Kurs von Heinz D. befasst.
Allerdings muss ich sagen, sämtliche aufgebaute Eingabeschaltungen
haben ein Prellen verursacht. Gerade bei so "bitkritischen Eingaben"
kann das mächtig daneben gehen und bis zum "Verfusen" des Controllers
führen. Darum habe ich mir einen "Programmierer" gebaut. Somit ist eine
fehlerfreie Eingabe der Programme möglich. Die verwendeten LEDs haben
übrigens eingebaute Vorwiderstände.
Die Prozedur ist wie folgt:
1. Batterie anschließen
2. Reset-Brücke einsetzen
3. Eingabe einer "1" linke Taste drücken (liegt an PB5 ) ==>
PB2, danach PB1 leuchten als Quittung auf
4. Eingabe einer "0" rechte Taste drücken (liegt an PB4) ==> PB1
leuchtet als Quittung auf
Download: P1Bascom.zip
$regfile = "attiny13.dat"
$hwstack = 16
$swstack = 8
$framesize = 16
$crystal = 1200000
Config Portb.1 = Output 'Mosi
Config Portb.2 = Output 'SCK
Config Pinb.4 = Input 'Mosi- Taste
Config Pinb.3 = Input 'SCK- Taste
Portb.4 = 1
Portb.3 = 1
Do
Debounce Pinb.3 , 0 , Hsck , Sub
Debounce Pinb.4 , 0 , Sck , Sub
Loop
End
Hsck:
Portb = &B00000100 ' PB2
Waitms 200
Portb = &B00000110 ' PB2 / PB1
Waitms 200
Portb = &B00000000
Return
Sck:
Portb = &B00000010 ' PB1
Waitms 200
Portb = &B00000000
Return