Assembler für Assembler - Anfänger (bin selber noch einer)

Hi
Nun, da melde ich mich auch mal wieder, speziell zum Privaten. Rolf, es war nie meine Absicht irgendwem auf die Füße zu treten, aber manchmal stellt man schon die ein oder andere frage öffentlich, die im Prinzip mehr innerlich auftaucht. Das ich mich nicht mehr gemeldet habe liegt in der zeitlichen Auslastung. obwohl bereits über 60 bin ich immer noch schwer beruflich eingebunden. Mein Ziel, ein Buch für interessierte Programmierer zu schreiben, ist auch teilweise auf der Strecke geblieben. Schließlich soll es ja verständlich und lesbar sein. Der Termin aber bis Weihnachten damit marktreif zu sein, ist illusorisch. Gut, zurück zu deiner privaten Anmerkung.
Atme jetzt einmal kurz durch und dann versuch mich zu verstehen. Wie Dino schon sagt, sitz ich am anderen Ende eines Kabels und sehe nicht die Welt. Mein Bild besteht aus den Eindrücken, die die verfassten Texte hinterlassen und da passiert's halt, das eine unglücklich gewählte Aussage beleidigend aufgenommen wird. Auch im Mikrocontroller.Net lass ich meine Meinung hören und sicherlich bin ich nicht immer nett, aber ich lass mich auch ungern verarschen. Ein kleines Beispiel macht es vielleicht deutlich:
Ein Kollege von mir, kein dummer Kopf aber furchtbar durchsetzt mit Minderwertigkeitsgefühlen hat öfter mal Fragen gestellt. Wenn man dann versuchte, diese zu beantworten, ließ er uns wissen, er kennt die Antwort und brachte auch gleich einen Vortrag mit. Ich brauch hier nicht explizit zu sagen, das dieser Kollege von allen gemieden wurde.
Bei einigen Beiträgen kommt es mir auch so vor, das es aus welchen Gründen auch immer, zu Fragestellungen kommt, die nicht wirklich ernst gemeint sind und nur der Beschäftigung hilfsbereiter Forenmitglieder dienen.
Im Moment werd ich mich wohl etwaas rar machen. Zur Zeit bist du gut betreut und da muss ich nicht unbedingt mitschreiben.
Eines will ich jedoch noch einmal anmerken:
Versuche den kreis zu finden. Eine Eisenbahnanlage besteht im Grunde auch aus Schleifen und der Zug kommt immer wieder an den gleichen stellen Vorbei.
Beginne mit dem OVAL. Setze dann Weichen und führe den Ableger wieder dem Oval zu. Dein Programm hat diese Struktur noch nicht. Schau nochmal, wie ich ein Programm und den Kreis nach der Initialisierung sehe:
Code:
Reset:                        ; Einstieg in das Programm bei Adresse 0
    RJMP   Start            ; IVT überspringen
    ; Bereich Interrupt Vector Table
Start:
;evtl. Stack festlegen
    RCALL Init_IO           ; Initialisieren Port
    RCALL  Init_ others   ; weitere Initialisierungen

Loop:                         ; hier beginnt das Programm
   RCALL Read_IO         ; lesen von Eingängen
   RCALL Do_something ; mach was
   RCALL Set_IO          ; Setze Ausgaben
RJMP  Loop
Da ist klare Linie und sichergestellt, das das Programm ständig rotiert. Es wartet nicht auf irgend einen Eingang. Sollen Eingänge bearbeitet werden, dann wird dies in Do_Something erledigt. Pausen in dieser Schleife stören diesen Ablauf. Warum ? Nun, stell dir vor, du hast einen Endschalter, den du anfahren möchtest. Außerdem willst du vielleicht noch einen taster betätigen. Nun, egal was grad anliegt, in einer Pausenroutine wird auf ein Ereignis der Ports nicht reagiert. Wie auch. Daher ist hier wichtig, den Timer-Interrupt richtig einzusetzen. Willst du in deinem Programm jederzeit jedes Ereignis erfassen, dann musst du die Zykluszeit klein halten. Wie dies gemacht wird, steht auch in meinem kleinen Beitrag.
So, jetzt muss ich aber wieder......
Gruß oldmax
 
Nunja, bis auf die von mir bereits genannten Punkte macht das Programm eigentlich was er will. Das Hauptprogramm läuft (nach der Initialisierung) in einer ordentlichen Endlosschleife, darin wird der Zustand des Timer-interruptbasierten Zyklus-Countdowns abgefragt, und (nur) ggf darauf reagiert. Keine Warteschleifen mehr.
Klar der Übersicht halber kann man diese Bestandteile innerhalb der Endlosschleife jetzt auch irgendwo andershin auslagern, und dann eben dahin und wieder zurückhopsen (lassen). Gekapselt nennt man das bei Hochsprachen, oder?
Solange dieses Codesegment nur von einer Stelle imProgramm erreicht werden (und es danach nur an einer Stelle weitergehen) soll, liegt mMn der einzige Vorteil in der übersichtlicheren Struktur. Ansonsten ists klar.
 
Hi
Zu euren Fragen und Vermutungen. Nein, ich hab es bisher weitestgehend für mich behalten und nein, es ist weitaus mehr, als nur die Inhalte in meinem Thread. Sicherlich werden einige Inhalte ähnlich sein, aber das nur soweit es dem Verständnis erforderlich ist.
Zum Thema:
Code:
;Zeitvorgaben
 zeit2: 
    ldi accu1,0x06 ;0b0000.0110
 pause2:
    tst accu1 ;teste r17 auf Low 
    brne pause2
 ret
Auch wenn in einem Interrupt R17 herunter gezählt wird, ist dies eie typische Zeitverzögerung, die alle anderen Funktionen eines Programmes ausbremst. Rolf, nix gegen dich, es ist richtig: wenn es so funktioniert, wie du es willst, ist es egal, welchen Weg du nimmst. Wenn du aber ein Programm schreibst, welches jederzeit auf einen Eingang reagieren soll, dann bist du hier nicht sicher, ob der Controller grad in einer Zeitschleife hängt, oder zufällig den Port einliest. Ausweg aus dieser Falle ist ein Eingang mit Interrupt zu erfassen. Daher vermutlich auch im allgemeinen die vielen Versuche, Taster mit Interrupt einzulesen.
Dein Programm ist eine "feste Automatik". Keine Handeingriffe, immer nur der gleiche Ablauf nach dem Starten. Vielleicht ja, es ist gewollt. Aber du kannst mehr, viel mehr, wenn du deinem Programm etwas mehr "Ereignisorientierte Programmierung" verpasst. Ich weiß, das ist ein Begriff aus der Objektorienierten Programmierung (OOP), aber durchaus einfach in Assembler umzusetzen. Dann bleibt dein Programm für alle Ebenen immer erreichbar. Dazu mußt du nur Ereignisse erzeugen. Entweder Signale aus der IO-Ebene oder aus der Timer-ISR. Ein Ereignis entspricht dann einem Bit, welches du im Programmablauf prüfst und wenn gesetzt, die vorgesehene Routine anstößt. Das Bit wird danach gelöscht. So rotiert das Programm grundsätzlich durch alle Ebenen, führt aber vom Ereignis abhängig die Bearbeitung durch.
Etwa so:
Code:
Eventflag:     .Byte 1   ; Byte für 8 Ereignisse
                                 ; Bit 0 = Zeit 1 Sek.
                                 ; Bit 1 = Heben an
                                 ; Bit 2= Heben aus
                                 ; Bit 3 = drehen an
                                 ; Bit 4 = Drehen aus
                                 ; etc
Taster_Flg:   . Byte 1  ; hier werden die positiven
                                ; Signalwechsel, also Taster
                                ; gedrückt abgelegt

Loop:   
     RCALL Read_IO         ; Lesen Eingänge
     RCALL Set_Io_Flg      ; Flanken bilden
     RCALL Chk_Taster_1  ; Tasterflanken prüfen
     RCALL Chk_Sek        ; Zeitsteuerung aufrufen
     RCALL Chk_Event_1  ; Ereignisse vom Programm
RJmp Loop

Chk_Event_1:
    LDS   R18, Event_Flag     ; lade Ereignisse
    ANDI R18, 0b00000001   ; Ereignis 1 aufgetreten
    BRNE End_Event_1          ; Wenn nicht,dann Ende
    .........                            ; mach irgendwas
    LDS   R18, Event_Flag     ; Lade Ereignisflags
    ANDI R18,0b11111110    ; Lösche Ereignis 1
    STS   Event_Flag,R18      ; und schreibe Flags zurück
End_Event_1:
Ret

Auf diese Weise ist dein Programm zwar immer damit beschäftigt, irgendwelche Bits zu testen, aber jedes Element wird angesprochen. Du hast es selbst im Blick, was ausgeführt werden soll und ganz wichtig: es wird nur einmal ausgeführt. Danach braucht es ein neues Ereignis.
Vielleicht hilft dir diese Erklärung etwas weiter, deine Programmierung weiter voranzutreiben.
Gruß oldmax
 
Also ich finde hier beim besten Willen keine Warteschleife...
Code:
...
;Interrupt-ISR

TIMER0_OVF:push    r2        ;Kopie r2 auf den Stack, danach SP-1
           in      r2,SREG   ;Inhalt vom Statusregister in r2 laden
           dec     temp1     ;Dekrement (r17-1)
          
           out     SREG,r2   ;Inhalt von r2 ins SREG laden
           pop     r2        ;SP+1, danach vom Stack in r2 laden
           reti
          .EXIT
...
Was hier noch fehlt, ist die Begrenzung des Zählregisters (temp1) - er will ja nach dem ersten Durchlauf nicht von 255, sonder von 92 runterzählen lassen. Das sollte mit einem bedingten Sprung über ein LDI hinweg zu erledigen sein (wobei die Bedingung schon "nebenbei" durch das DEC erzeugt wurde) - die Verzögerung hält sich also in Grenzen. Wenn jetzt also diese Fallunterscheidung eh schon da ist, kann hier auch gleich das setzen der 3 Ausgabebits erfolgen. Mittels eines weiteren LDI und eines Out (wenn man ein reserviertes Register spendiert, kann man sich das LDI auch noch sparen)
Im Hauptprogramm testet er nicht auf Bits, ok. Aber auf Bytes - wo ist da jetzt der Unterschied?

Zu Deinem "Werk": Ich ziehe das "im wesentlichen" aus meinem Beitrag zurück - ist schon klar, daß Du dein Tutorial nicht nur abschreiben willst. Und bei so'ner Aufarbeitung kommt dann eh immer noch das ein oder andere hinzu. Respekt, ich trau mir das nicht zu... ich würde da vom hundertsten ins tausendste kommen, und wenn (<-falls) am Ende irgendwas bei rauskommt, wär das mit Sicherheit nicht vernünftig lesbar...;)

Edit:
Ähm... ich denke, daß dieser Happen unkommentiert (noch) etwas zu groß für Rolf ist:
...
Code:
Eventflag:     .Byte 1   ; Byte für 8 Ereignisse
                                 ; Bit 0 = Zeit 1 Sek.
                                 ; Bit 1 = Heben an
                                 ; Bit 2= Heben aus
                                 ; Bit 3 = drehen an
                                 ; Bit 4 = Drehen aus
                                 ; etc
Taster_Flg:   . Byte 1  ; hier werden die positiven
                                ; Signalwechsel, also Taster
                                ; gedrückt abgelegt
...
...
Vorweg: gehört vor die labels nicht zwingend ein ".dseg", und dahinter ein ".cseg"? Oder erkennt der Assembler an der eindeutigen Direktive ".byte", daß das ins Datensegment (.dseg) gehört?
Ok, was geschieht hier?
Der Controller verfügt über SRAM - Arbeitsspeicher quasi. (Strenggenommen ist adresstechnisch ein Teil davon der Bereich der I/O-Register (Hardwareanbindung...), aber irgendwo beginnt dann der "frei verfügbare" Bereich.
Durch das ".byte 1" wird jetzt also ein Byte im SRAM "reserviert". Wobei das nicht das richtige Wort ist. Genau genommen erhält der Labelname nur die nächste (freie) Adresse im SRAM zugewiesen. Wir erinnern uns: der Labelname ist nur im Assembler (Programmierumgebung) eine Variable - wenn das Programm assembliert wird, wird daraus die entsprechende Adresse (Zahl) als Konstante berechnet. Du kannst also an jeder Stelle im Programm auf die entsprechende SRAM-Zelle mit den entsprechenden Befehlen zugreifen (LDS, STS, ...).
Und was bedeutet nun die "1" hinter ".byte"? Daß hier 1 Byte "reserviert" werden soll. Wenn man ein Word ablegen will, braucht man 2 Bytes. Aber beachten: Das label zeigt natürlich weiterhin nur auf das erste Byte; enthält nur diese eine Adresse. Die 1 wirkt sich darauf aus, welche Adresse (Zahl) das nächste label mit ".byte" zugewiesen bekommt.

Im Prinzip ist das dasselbe, als wenn man statt der Labels (im Datensegment) die entsprechenden Adress-Konstanten mit ".equ" festlegt - nur daß man dann eben selbst die Ziele (Adressen) im SRAM festlegen muß. "labelname: .byte 1" weist der Konstante "labelname" die nächste freie Adresse im SRAM zu, und erhöht seinen internen Zeiger um 1 auf die nächste Adresse. Diese eine haben wir ja jetzt "belegt".

Und noch was am Rande: Ich verwende als Flag-Register ganz gern das GPIOR0, da es mit den bitbasierenden Instruktionen sofort und direkt manipulier- und auswertbar ist. Insbesondere lassen sich Skip-Instruktionen ohne irgendwelche anderen Vergleiche/Operationen an einzelne Bits dieses Registers binden. Ähnlich kann man auch mit den Rechenregistern verfahren, wenn man sie dafür reservieren will.
 
Hi
Bevor ich jetzt hier ein komplettes Programm ablege, eine Erklärung. Natürlich ist das nur ein Schema, kein ablauffähiges Programm. Vielleicht bin ich fälschlicherweise davon ausgegangen, das Rolf schon weiß, was Variablen sind. Zu den Warteschleifen... Im Interrupt wartet er ja nicht, aber in seiner Zeit-Routine. Da bleibt er, bis die Zeit vom Interrupt auf 0 gezählt ist. Aber auch darauf hatte ich verwiesen.
Übrigends: mein Satz "Severin = Rolf?" bezog sich auf "bitte keine Antworten mehr". Ich weiß nicht, wie ich es betrachten würde, wenn ich einen Beitrag öffne, ein Quereinsteiger MEINE Frage zerstört und am Schluss die Diskussion beendet. Rolf, das ist jetzt nicht bös gemeint, aber genau so hab ich es empfunden und deshalb mein Kommentar.
So, nun denk ich, hab ich erst mal alles gesagt, was mir wichtig war.
Gruß oldmax
 
...Zu den Warteschleifen... Im Interrupt wartet er ja nicht, aber in seiner Zeit-Routine. Da bleibt er, bis die Zeit vom Interrupt auf 0 gezählt ist. Aber auch darauf hatte ich verwiesen...
Wo denn? Ich find's schlichtweg nicht...
Code:
...
loop:      cpi     temp1,69      ;wenn temp1 größer 69..C=0
           [B]brcs    gehe01[/B]        ;dann keine Verzweigung gehe01
           ldi     akku,0x07
           out     PORTB,akku    ;PB2-PB0 = 1
           [B]rjmp    Ende[/B]

gehe01:    cpi     temp1,46      ;wenn temp1 größer 46..C=0
           [B]brcs    gehe02[/B]        ;dann keine Verzweigung gehe02
           cbi     PORTB,PB2     ;PB1-PB0 = 1
           [B]rjmp    Ende [/B]  

gehe02:    cpi     temp1,23      ;wenn temp1 größer 23..C=0
           [B]brcs    gehe03[/B]        ;dann keine Verzweigung gehe03
           cbi     PORTB,PB1     ;PB0 = 1
           [B]rjmp    Ende[/B]         

gehe03:    cpi     temp1,0      ;wenn temp1 größer 0..C=0
           [B]brcs    Ende[/B]         ;dann keine Verzweigung Ende
           cbi     PORTB,PB0     ;PB2-PB0 = 0
                   
Ende:      [B]rjmp    loop[/B]
...
Der einzige Sprung zurück ist die main-loop. Natürlich macht das Programm (noch) nichts anderes, als auf, in der Timer-ISR erzeugte Zustände zu "warten", und dann zu reagieren. Dabei wartet es aber eben nicht vor jedem Teilschritt auf diesen, sondern pollt nur den Zustand von Temp1.
Wenn noch weiterer Code in die loop rein soll, kann der problemlos direkt hinter "loop:", oder zwischen "Ende:" und dem "RJMP" eingefügt werden (ok, der Labelname ist dann etwas unpassend), und wird dort ständig ausgeführt.
 
Hallo oldmax,
zum privaten:
wie ich schon schrieb, machte ich mir Vorwürfe, an dem Schweigen von Severin Schuld zu sein.
Aber die vielen Worte von Dino, LotadaC und wer haben mich überzeugt, daß es andem nicht so ist.
Ich glaube, wir sollten jetzt das Thema zu den Akten legen und ich würde mich freuen in diesem Forum aktiv weiter tätig zu sein.
Zumal es Dino irgendwann mal gut fand, daß Assembler Priorität hat.....ENDE des Zitats!
zum fachlichen:
Deine Anregungen habe ich versucht zu verstehen. Ganz Klick hat es noch nicht gemacht.
Ich werde morgen ein Beispiel mit nen Tiny 2313 aufziehen.
Habe mich in der letzten Zeit viel mit den 3 Speicherblöcken der Tiny-Reihe beschäftigt. Ich blickte im Datenblatt
über die Liste der 64 SF-Register nicht durch, warum z.B. TCCR0B auf Platz 0x33 (0x53) steht.
Na, was ist "wisst Ihr das, oder weiß ich einmal mehr als Ihr" glaube es aber nicht.
Interessieren würde mich, ob ich über Studio4 erfahren könnte, wieviel Speicher im Flash belegt ist.
Wieviel Plätze belegen denn die Interrupt-Vektoren?

Das wars erst mal

Rolf
 
Wenn Du ein Programm compilieren läßt, zeigt das Studio das normalerweise an. Zumindest bei den 5er Versionen gibts ein extra Fenster für Fehler, Warnungen, Meldungen und so - da steht dann, wieviel Flash und Eeprom verwendet wird, wieviel SRAM reserviert (siehe oben). Meiner Meinung nach sogar in Bytes und Prozenten.
Zu den Adressen in den Klammern: Die 32 Rechenregister sind adresstechnisch vor den SRAM gebunden, und belegen für entsprechende Instruktionen die ersten 32 (0x20) Adressen. Für Instruktionen, die nur die ersten 32 (0x00..0x1F) bzw 64 (0x00..0x3F) I/O-Register (also nicht(!) die Rechenregister) adressieren können ist also 0x20 abzuziehen. Der Assembler weiß das aber.
---- Korrekturwunsch Anfang ---
Der Assembler weiß das eben nicht!
In der Prozessordefinitionsdatei sind die Registernamen der ersten 64 I/O-Register nullbasiert definiert, die der extendet I/Os um 32 (dezimal - nämlich genau die 32 Rechenregister) "hochgeschoben", remapped.
Klar, da die Befehle für die unteren I/Os so angelegt sind, die ext. I/Os hingegen mit den Befehlen für den Dataspace erreicht werden müssen (LDS/STS...). Wenn man jetzt also eine Dataspace-Instruktion mit einem Registernamen aus den unteren I/Os verwendet (warum auch immer), stimmt die Adresse nicht, weil sich hinter dem Namen ja eine Variable/Konstante verbirgt, deren Wert nicht remapped ist. In diesem Falle wären also auf die Adresse 0x20 (bzw 32dez) hinzuzuaddieren.
---- Korrekturwunsch Ende ----

Edit zu den Interruptvektoren:
Der Programmspeicher der Controller wird Wordweise adressiert, da alle Opcodes sich fast alle durch ein Word (2 Bytes) repräsentieren. Bei direkten Sprüngen (incl call) reichen allerdings 16bit nicht mehr aus, um darin sowohl den Befehl selbst, als auch das Ziel zu verschlüsseln. Deswegen sind das 2-Word-Befehle. Die braucht man aber erst, wenn man weiter als 2k-Words (=Instruktionen) weit springen will. Soweit kommt man nämlich auch mit den relativen Sprungbefehlen (welche 1-Word-Befehle sind). Aber zurück zum Thema. Dein 2312 hat nur 2 K-Bytes Flash. Das sind 1 K-Words Flash. Also kommt er bequem mit den relativen Sprüngen hin. Die direkten kennt er meiner Meinung nach gar nicht. Folglich wird für jeden Interruptvektor (und den Resetvektor) nur 1 Word gebraucht. Wie man im Datenblatt auf Seite 44 nachlesen kann, verfügt der Tiny (wenn man Reset mitzählt) über 19 "Interruptquellen" - also die ersten 19 Words im Program Flash (bzw 38 Bytes) bilden die Interruptvektoren. Die Flash-Adressen 0x0000 bis 0x0012 (wie gesagt, Word-Adressen).

Dabei mußt Du aber im Kopf behalten, daß diese Flash-Bereiche eigentlich ganz normaler Programmspeicher sind. Dort kann beliebiger Code untergebracht werden. Sie sind lediglich Ziel, Einsprungpunkt des korrespondierenden Interrupts. Wenn also ein IRQ freigegeben und behandelt werden soll, muß im entsprechenden Interruptvektor stehen, wie's weitergeht. Reicht der Flash bis zum nächsten aktivierten Vektor nicht aus (was meistens der Fall ist), muß man halt erstmal woanders hinhopsen, und dort die ISR ausführen. Üblicherweise macht man das einfachheitshalber mit allen Interrupts so - man muß das aber nicht unbedingt so machen, klar? Der Controller macht genau das, was Du ihm sagst. Jeden Mist. Dummerweise merkt man nur manchmal nicht, welchen Quatsch man so von ihm verlangt, und wundert sich dann...
 
Hi
Code:
Reset:                        ; Einstieg in das Programm bei Adresse 0
    RJMP   Start            ; IVT überspringen
    ; Bereich Interrupt Vector Table
Start:
;evtl. Stack festlegen
    RCALL Init_IO           ; Initialisieren Port
    RCALL  Init_ others   ; weitere Initialisierungen

Loop:                         ; hier beginnt das Programm
   RCALL Read_IO         ; lesen von Eingängen
   RCALL Do_something ; mach was
   RCALL Set_IO          ; Setze Ausgaben
RJMP  Loop
Gruß oldmax

zu oldmax.....das mit den rcalls schätze ich auch sehr. Es ist übersichtlich und man blickt sofort
durch, wenn man den Quelltext nach Monaten vor sich hat.
Mein Atari für die Eisenbahn (GFA-Basic) ist voll mit sog. Proceduren, sonst würde ich die Übersicht verlieren.

Aber...von der Schleifenzeit oder wie man es nennt, bringt mir das garnichts.
ob ich nun Variante1 nehme (wird unübersichtlicher) oder 2 (siehe nachfolgend)
Evtl. gibt es noch einen anderen Befehl als "sbic"
Ich will ja garnicht, daß der Tiny zwischendurch was anderes tut, sondern er soll warten, bis ich die Taste
betätige. Was mich nochmal reizen würde...die Tastenabfrage in die ISR zu verlegen.

Code:
; test01.asm            09.11.12
; Aufgabe:

; AVR = Tiny2313
; an PIND,PD6 sitzt ein Taster gegen GND
; an PORTB,PB0 eine LED gegen GND

; Ablauf:
; nach betätigen Taster leuchtet die LED

; Ablauf in zwei Varianten
; Initial. von DDRB
; Initial. von DDRD

; Variante 1.
loop:
        clr     r16
        out     PORTB,r16
read1:  sbic    PIND,PD6
        rjmp    read1
        sbi     PORTB,PB0
        rjmp    loop

; Variante 2.
loop:
        clr     r16
        out     PORTB,r16
        rcall   taste
        rcall   led1
        rjmp    loop

taste:
read1:  sbic    PIND,PD6
        rjmp    read1
        ret
led1:   sbi     PORTB0,PB0
        ret
        .EXIT

ich sehe, die LED wird garnicht zum leuchten kommen, ist aber unwichtig.
 
zu oldmax.....das mit den rcalls schätze ich auch sehr. Es ist übersichtlich und man blickt sofort
durch...
Aber...von der Schleifenzeit oder wie man es nennt, bringt mir das garnichts...
Sag ich doch
...
Klar der Übersicht halber kann man diese Bestandteile innerhalb der Endlosschleife jetzt auch irgendwo andershin auslagern, und dann eben dahin und wieder zurückhopsen (lassen). Gekapselt nennt man das bei Hochsprachen, oder?
Solange dieses Codesegment nur von einer Stelle imProgramm erreicht werden (und es danach nur an einer Stelle weitergehen) soll, liegt mMn der einzige Vorteil in der übersichtlicheren Struktur. Ansonsten ists klar.
Genau genommen kosten Dich die Sprünge hin und zurück jedesmal zusätzliche Zeit, Und einmal zusätzlichen Flash (was sich allerdings relativiert, wenn man diese Subroutine noch von einer anderen Stelle aus anspringen will, und dahin zurück. Dann müßtest Du bei Variante1 entweder zweimal den Code der "Subroutine" einbauen, oder der 2te Aufruf springt zum Code vom ersten - dann wirds aber komplizierter mit dem Rücksprung (Außerdem bergen solche Tricksereien das Risiko, das Gleichgewicht zwischen den Calls und den entsprechenden Returns zu verletzen -> Stacküberlauf). Genau dafür sind die Calls ja da.
...Evtl. gibt es noch einen anderen Befehl als "sbic"
...
Um welches Problem zu lösen?
Was ist SBIC (bzw das Pedant SBIS) überhaupt?
SBIC ist eine bedingte relative Verzweigung, die als Bedingung ein beliebiges Bit eines beliebigen unteren (also der ersten 32) I/O-Registers akzeptiert. Dafür ist die Sprungdistanz festgelegt. Es wird die nächste Instruktion übersprungen. Warum? Der Opcode von SBIC ist: "1001_1001_AAAA_Abbb". Also 8 Bits für die Instruktion selbst, 3 Bits (b) zur Kodierung des bedingenden Bits (2^3=8), mit den restlichen 5 Bits (A)können 2^5=32 Register adressiert werden. Für die Sprungdistanz ist da einfach kein Platz mehr im Opcode.
Was ist nun der "Vorteil" zu anderen bedingten Verzweigungen? Du braucht keine Vergleichsoperationen (weder compares, noch entsprechende logische Verknüpfungen mit Masken - Oldmax hatte da oben irgendwo ein Beispiel) durchführen, insbesondere mußt Du auch keinRechenregister dafür benutzen. Nachteile sind neben der festen Sprungdistanz die Beschränkung auf die ersten 32 I/Os und, daß genau ein Bit für die Bedingung herangezogen wird.
Wobei das eigentlich eben keine Vor-/Nachteile sind - genau das ist ja die Aufgabe von diesen Befehlen.

es gibt ähnliche Befehle, die auf die Bits beliebiger Rechenregister reagieren können: SBRC/SBRS.

Kommentare zu Deinem Programm pack ich hier jetzt nicht mehr rein, da kommt wohl später noch'ne Antwort (von mir). So richtig hast Du nicht geschrieben, was die Aufgabe dieses Progrämmchen sein soll (außer dem Vergleich der beiden Varianten). Spekulation:
-das LED-Beinchen soll logisch-Hi sein, genau dann wenn das Taster-Beinchen (direkt vorher) logisch-Lo war
-in der Hauptschleife wird das LED-Beinchen jedesmal zurückgesetzt (damit flackert die LED quasi - nur halt sehr hochfrequent)
-das ganze soll erstmal ohne Interrupts umgesetzt werden (also in einer Endlosschleife/der main-loop)

schonmal 2 Anmerkungen:
-vom AVR aus gesehen ist der Taster-Pin Tristate bzw Gnd (wenn gedrückt). Wenn der Taster also nicht extern hochgezogen wird (Pullup-Widerstand), liefert das auslesen des PIN-Registers bei offenem Taster kein zuverlässiges Ergebnis. wahrscheinlich immer Gnd - wennst mit dem Finger drannkommst vielleicht auch was anderes. Der AVR hat aber auch interne Pullups, die man aktivieren kann (Siehe I/O-Ports im Datenblatt ab Seite 46)
-statt der Warteschleife kannst Du auch das anschalten der LEDs bedingt "skippen" lassen.
Aber damit hast Du letztendlich immer noch'ne PWM-gedimmte LED (wobei die Pulsweite und die Periodendauer durch die Schleifenlaufzeit bestimmt wird) - nach dem loop schaltest Du jedesmal wieder ab...
 
Was ist SBIC (bzw das Pedant SBIS) überhaupt?
SBIC ist eine bedingte relative Verzweigung, die als Bedingung ein beliebiges Bit eines beliebigen unteren (also der ersten 32) I/O-Registers akzeptiert. Dafür ist die Sprungdistanz festgelegt. Es wird die nächste Instruktion übersprungen. Warum? Der Opcode von SBIC ist: "1001_1001_AAAA_Abbb". Also 8 Bits für die Instruktion selbst, 3 Bits (b) zur Kodierung des bedingenden Bits (2^3=8), mit den restlichen 5 Bits (A)können 2^5=32 Register adressiert werden. Für die Sprungdistanz ist da einfach kein Platz mehr im Opcode.
Was ist nun der "Vorteil" zu anderen bedingten Verzweigungen? Du braucht keine Vergleichsoperationen (weder compares, noch entsprechende logische Verknüpfungen mit Masken - Oldmax hatte da oben irgendwo ein Beispiel) durchführen, insbesondere mußt Du auch keinRechenregister dafür benutzen. Nachteile sind neben der festen Sprungdistanz die Beschränkung auf die ersten 32 I/Os und, daß genau ein Bit für die Bedingung herangezogen wird.
Wobei das eigentlich eben keine Vor-/Nachteile sind - genau das ist ja die Aufgabe von diesen Befehlen.

es gibt ähnliche Befehle, die auf die Bits beliebiger Rechenregister reagieren können: SBRC/SBRS.

...

Hallo LotadaC,

liegst Du hier mit "sbic" richtig? Ist nicht für die ersten 32 I/O Register sondern der PORTxx Register
zuständig. (überspringe, wenn Bit im PORT =0) ebenso sbis (PORT=1)
Dagegen streng genommen sbrc bzw. sbrs auf die ersten 32 I/O Register.
Oder liege ich falsch?
 
Nein, es ist schon so wie von mir geschrieben.
SBIC - Skip if Bit in I/O Register is Cleared (lasse aus/überspringe wenn Bit in I/O Register gelöscht ist)
Instruction-Set Seite 124. Für SBIC & co ist Adresse 0x00 das erste I/O-Register (also nicht R0).
Dasselbe gilt zB auch für IN und OUT, nur daß die 6 Bits weit adressieren können (also 64 I/O Register - 0x00 bis 0x3F).
Die PORTx-Register (sowie auch die DDRx und PINx-Register) befinden sich logischerweise im I/O-Bereich. Bei den mir bekannten Controllern auch immer unter den ersten 32 (also greifen die direkten Bitoperationen). Es gibt aber einige weitere Register, die auch in diesem Bereich liegen - beim ATmega48/88/168 zB diverse InterruptFlagRegister UND das GPIOR0 - General Purpose I/O Register 0 (es hat keine direkte Hardware-Anbindung, steht Dir zur freien Verfügung. Mit den Möglichkeiten eines I/O-Registers innerhalb des (I/O-) Adressraumes bis 0x1F).
Schau Dir mal eine Prozessordefinitionsdatei an - da werden irgendwo alle vorhandenen I/O-Adressen mit .equ in Veriablen (Registernamen) abgelegt. und dann vergleiche diese Adressen mit der Tabelle (Register-Summary) im Datenblatt des Controllers).

Edit:
...Für Instruktionen, die nur die ersten 32 (0x00..0x1F) bzw 46 (0x00..0x3F) I/O-Register (also nicht(!) die Rechenregister) adressieren können ist also 0x20 abzuziehen. Der Assembler weiß das aber...
Hatte ich leider etwas unglücklich formuliert - der Assembler weiß das nicht wirklich. Es ist eher so:
In der Prozessordefinitionsdatei sind die I/O-Registernamen als Variablen/Konstanten mit der entsprechenden Adresse definiert. Bei den ersten 64 I/O-Registern geschieht dies nullbasiert, somit können diese sofort mit den I/O-Befehlen (SBIC/SBIS/SBI/CBI/IN/OUT...) verwendet werden. Bei den extendet I/O-Registern sind die Adresse bereits um 0x20 (bzw 32 dez) remapped, und somit für Instruktionen des Dataspace (LDS/STS...) direkt verwendbar. Wenn man jedoch mit einem Dataspace-Befehl ein unteres I/O verwendet (warum auch immer), geht das nach hinten los, da diese Variable ja nicht remapped, und somit für diesen Befehl um 32 dez (oder 0x20) zu klein ist. Man landet dann also fälschlicherweise in den Rechenregistern, bzw den unteren I/Os.
 
mh...komisch, habe das mal mit meinen Kranprogramm getestet.
Ändere ich sbic in sbrc zeigt der Compiler Error.
Warum, weil ich kein Arbeitsregister verwende.
L:\Tiny2313\KRAN01.asm(56): error: Invalid register

Code:
 loop:
 clr     accu0        ; lösche alle Bits von r16
 out     PORTB,accu0  ; alle Bits=LOW 
 sbi     PORTB,PB5    ; LED = AUS
 
 Start:               ; drücke Taste Start
 sbic    PIND,PD4     ;überspringe,wenn PD4=LOW
 rjmp    Start

 rcall   Aufbegin     ; d1=EIN / AUS
 rcall   zeit2
 
...
SBIC ist eine bedingte relative Verzweigung, die als Bedingung ein beliebiges Bit eines beliebigen unteren (also der ersten 32) I/O-Registers akzeptiert.
...
es gibt ähnliche Befehle, die auf die Bits beliebiger Rechenregister reagieren können: SBRC/SBRS.
...
Ich hatte das schon richtig geschrieben, Du hattest es nur nicht richtig gelesen/verstanden.
SBIC/SBIS/SBI/CBI wirken auf die ersten 32 I/O-Register (also nicht die Rechenregister),
SBRS/SBRC wirken auf die ersten 32 Register im Dataspace (allgemein, das sind die Rechenregister),
LDS/STS wirken auf die ersten 65535 Register im Dataspace(*) (inklusive Rechenregister, bis 0xFFFF)
(Achtung, das ist LDS/STS als 32bit-Opcode - einige Controller unterstützen stattdessen einen anderen LDS/STS mit einem 16bit-Opcode. Welchen der verwendete Controller unterstützt, kann man dem jeweiligen Datenblatt entnehmen - sie unterscheiden sich in der Zahl der Takte (Cycles))

(*)für noch mehr Speicher ist in einigen Controllern der Speicher(zugriff) dann in pages (mit je 64KB) organisiert. Zur Auswahl der entsprechenden page dient dann das RAMPD-Register. (Für entsprechende indirekte Zugriffe über X,Y und Z gibt es dann auch dort entsprechende RAMPx-Register. Siehe InstructionSet Seite 2.
In dem Zusammenhang noch eine Frage an die Allgemeinheit:
Zu RAMPD steht dort
Register is concatenated with the Z-Register enabling direct adressing of the whole blablabla 64K blub...
Warum soll das ans Z-Register gebunden sein? Oder ist das ein copy&paste-Fehler im Set?
 
Sind ja auch meine Anhänge :p...
Aber Du könntest die verlinken...
Rolf meint dieses Bild (aus diesem Beitrag):
attachment.php

).
Meine Kommentare:
-vor dem ersten Durchlauf initialisierst Du Deinen ZeitZähler (Temp1) auf 92 (4*23) - nach dem ersten Durchlauf machst Du mit 255 weiter, dann dauert der Zustand "alle 3 Bits an" also jeweils deutlich länger. Um das zu korrigieren...
-in der ISR nach dem decrement Abhängig vom Z-Flag (oder statt Decrement SUBI, und dann das Carry-Flag) Temp1 reinitialisieren. Dann kannst Du auch gleich hier den ersten Fall behandeln, also die 3 Bits setzen. Damit würde der erste Vergleich wegfallen.
-Der letzte Vergleich ist sinnlos(*) - 0 kann nie größer als irgendeine (positive) Zahl werden. Das Carry wird also nie gesetzt. BRCS springt nie. Am Label kann also sofort das CBI kommen.
-in der ISR sicherst Du das SREG in R2. Dazu sicherst Du R2 auf den Stack. Soweit ist das i.O. Da Du aber R2 nie verwendest, könntest(!) Du Dir das auch sparen, klar?
-wenn Du oben bei Deiner Benamsung der verwendeten Register mit ".def zeitkonst=23" 'ne Konstante definierst, kannst Du die 92 durch 4*Zeitkonst, die 69 durch 3*Zeitkonst, die 46 durch 2*Zeitkonst, und die 23 durch Zeitkonst ersetzen. Wenn das Gesamttiming später angepaßt werden soll, brauchst Du bloß noch "an der Konstante drehen", klar?

mh...ich versuche mich hier nochmal reinzufummeln!
mit " machst Du mit 255 weiter" meint LotadaC bestimmt den Timer0, mal sehen,ob ich das in der ISR hinbekomme.
 
Ich meinte Dein Register "temp1". Das wird nach der Timerinitialisierung, und vor dem Einsprungpunkt der Hauptschleife einmalig mit 92 initialisiert. In der ISR des TOV-0 dekrementierst Du temp1 jedesmal. Nach dem 92sten TOV ist temp=0, beim 93sten erfolgt ein "Unterlauf" - temp1 ist danach 255 (da 8Bit-Register), klar? Nach dem ersten Durchlauf wird (in dieser Programmversion) temp1 nach dem Unterlauf nicht reinitialisiert - kann natürlich auch Absicht von Dir sein...

P.S.: der letzte Punkt war auch nicht ganz korrekt - die Zeitkonstante muß natürlich mit der Assemblerdirektive .equ (oder .set) festgelegt werden. .def benamst ein Register neu (peinliche Verwechslung)
 
Nach dem ersten Durchlauf wird (in dieser Programmversion) temp1 nach dem Unterlauf nicht reinitialisiert - kann natürlich auch Absicht von Dir sein...

P.S.: der letzte Punkt war auch nicht ganz korrekt - die Zeitkonstante muß natürlich mit der Assemblerdirektive .equ (oder .set) festgelegt werden. .def benamst ein Register neu (peinliche Verwechslung)

Hallo LotadaC,
habe das alles nochmal auf dem Bord ablaufen lassen und bin ganz überrascht, daß vom Anfang an alles korrekt läuft,
(mit den 3x LEDs) aber beim Rücksprung nach loop: es bald 55 Sec. dauert, bis die erste Led an PB2 erlischt.
Da muß ich was tun, gut das Du drauf hingwiesen hast...war keine Absicht von mir.
Letzteres habe ich noch nicht ganz verstanden! Ich verwende doch Register r16 und r17 die ich mit .def akku / temp1 benannt habe.
Welche Zeitkonstante meinst Du, die 23?

Grüße

Rolf
 

Über uns

  • Makerconnect ist ein Forum, welches wir ausschließlich für einen Gedankenaustausch und als Diskussionsplattform für Interessierte bereitstellen, welche sich privat, durch das Studium oder beruflich mit Mikrocontroller- und Kleinstrechnersystemen beschäftigen wollen oder müssen ;-)
  • Dirk
  • Du bist noch kein Mitglied in unserer freundlichen Community? Werde Teil von uns und registriere dich in unserem Forum.
  •  Registriere dich

User Menu

 Kaffeezeit

  • Wir arbeiten hart daran sicherzustellen, dass unser Forum permanent online und schnell erreichbar ist, unsere Forensoftware auf dem aktuellsten Stand ist und der Server regelmäßig gewartet wird. Auch die Themen Datensicherheit und Datenschutz sind uns wichtig und hier sind wir auch ständig aktiv. Alles in allem, sorgen wir uns darum, dass alles Drumherum stimmt :-)

    Dir gefällt das Forum und unsere Arbeit und du möchtest uns unterstützen? Unterstütze uns durch deine Premium-Mitgliedschaft!
    Wir freuen uns auch über eine Spende für unsere Kaffeekasse :-)
    Vielen Dank! :ciao:


     Spende uns! (Paypal)