Forum: Mikrocontroller und Digitale Elektronik Assembler und NOP


von Andreas H. (heilinger)


Lesenswert?

Hallo,

für Zeitsteuerungen brauche ich einen NOP-Befehl in C.

Den habe ich über die Hilfe im Internet mit
1
#define NOP() asm volatile ("nop" ::)
 gelöst.

Da ich teilweise mehrere NOPS hintereinander aufrufen muss, habe ich mir 
eine Funktion mit der Anzahl der NOPs geschrieben:
1
void NOP_10mal(void)                                                 //Funktionsaufruf und Rücksprung = 3 Befehle
2
{
3
    NOP();
4
    NOP();
5
    NOP();
6
    NOP();
7
    NOP();
8
    NOP();
9
}

wenn ich in der Funktion 6 mal NOP stehen habe, dann übersetzt der 
Compiler das so:
1
00003248 <NOP_10mal>:
2
  ...
3
    NOP();
4
    NOP();
5
    NOP();
6
    NOP();
7
    NOP();
8
}
9
    3254:  08 95         ret


wenn ich 7 mal NOP in der Funktion stehen habe, dann übersetzt er das 
mit
1
00003248 <NOP_10mal>:
2
  ...
3
    NOP();
4
    NOP();
5
    NOP();
6
    NOP();
7
    NOP();
8
    NOP();
9
    3254:  00 00         nop
10
}
11
3256:  08 95         ret

Er fügt quasi noch ein weitere nop ein, obwohl ich das nicht will, da es 
ja dann gegenüber den 6 mal NOP 2 mehr sind, und es bei mir deswegen 
wieder mit dem Timing nicht hinhaut.

Da ich noch nie mit Assembler programmiert habe und auch nicht wirklich 
Hintergundwissen besitze, bitte ich darum, dass mich jemand aufklärt, 
warum er da diesen nop-Befehl noch zusätzlich einfügt bzw. wie ich das 
verhindern kann?

Danke...

Achja, ich benutze AVR Studio Version 4.18 Build 700

von spess53 (Gast)


Lesenswert?

Hi

>wenn ich 7 mal NOP in der Funktion stehen habe, dann übersetzt er das
>mit

>Er fügt quasi noch ein weitere nop ein, obwohl ich das nicht will, da es
>ja dann gegenüber den 6 mal NOP 2 mehr sind, und es bei mir deswegen
>wieder mit dem Timing nicht hinhaut.

Nein. Die Adressangabe ist in Bytes. Und ein Befehl ist 2 Byte lang. Der 
OpCode für NOP ist 0x0000.

MfG Spess

von Lutz (Gast)


Lesenswert?

Andreas Heil schrieb:
> für Zeitsteuerungen brauche ich einen NOP-Befehl in C.

Nein. Was du brauchst sind bessere Programmierkenntnisse.

von Huch (Gast)


Lesenswert?

>Nein. Was du brauchst sind bessere Programmierkenntnisse.
Die kann jeder von uns gebrauchen. ;-)

von Andreas H. (heilinger)


Lesenswert?

Lutz schrieb:
> Nein. Was du brauchst sind bessere Programmierkenntnisse.

Das mag sein, trotzdem brauche ich einen NOP-Befehl ;)

von Peter D. (peda)


Lesenswert?

Andreas Heil schrieb:
> für Zeitsteuerungen brauche ich einen NOP-Befehl in C.

Besser, Du schilderst, was Du machen willst.
Zyklengenau ist in C fast unmöglich.

Deine Funktion 6*NOP verbraucht nicht 6, nicht 10, sondern 13 Zyklen:
RCALL(3) + 6*NOP(1) + RET(4)
Vorausgesetzt, es gibt keine Interrupts.

Man kann aber Portpins zyklengenau per Timer setzen (set/clear/toggle on 
compare match). Das geht dann auch in C.


Peter

von Stefan B. (stefan) Benutzerseite


Lesenswert?

Schau dir nicht das LSS-Listing an sondern das konkrete 
Disassembler-Listing z.B. im AVR Studio Simulator.

Die Mischung aus C-Zeilen und Assembler ist im LSS-Listing manchmal 
etwas schwer lesbar, besonders, wenn zusätzlich Inline Assembler im 
Spiel ist.

Bei vielen NOPs würde ich mir überlegen, die inline delay-Funktionen aus 
der avr-libc zu benutzen (*). Die sparen i.d.R. Code und passen sich auf 
die Taktrate F_CPU an. Deine Lösung muss beim Wechsel der Taktrate 
manuell angepasst werden.

(*) Convenience functions for busy-wait delay loops
http://www.nongnu.org/avr-libc/user-manual/group__util__delay.html

von spess53 (Gast)


Lesenswert?

Hi

Nochmal, die Anzahl der NOPs stimmt. Kannst su auch nachrechnen:

0x3256 - 0x3248 =0x0E oder 14 Byte. Und das sind genau 7 NOPs.

MfG Spess

von Andreas H. (heilinger)


Lesenswert?

okay, nähere Schilderung des Problems:

Ich habe 4 Sensoren, die ich per I2C-Schnittstelle auslesen kann. Da die 
Sensoren jedoch alle die gleiche Adresse haben, kann ich nicht für alle 
die gleiche Busleitung benutzen. Daher programmiere ich die 
I2C-Schnittstelle manuell, also Clock und Aus- und Einlesen der Pins. 
Hierfür gibt es natürlich Vorgaben wie lange die low- bzw. high-Phase 
sein muss. Und deswegen brauche ich meiner Meinung nach einen NOP-Befehl 
um eben gewisse Phasen zu verlängern. Das ganze passiert in einem 
Interrupt.

Da ich zusätzlich noch eine if-Abfrage zwischendrin habe (ob Pin high 
oder low), möchte ich dass beide Wege (also if und else) gleich lang 
sind, also die Laufzeit gleich bleibt, egal ob ein high oder ein low am 
Pin anliegt, daher brauche ich auch dort mehrere NOP-Befehle.

Komisch ist allerdings, dass wenn ich die NOP-Aufrufe direkt in den 
else-Zweig schreibe, dass dann die Laufzeit immer gleich ist. Schreibe 
ich die NOP-Befehle in eine Funktion ,die ich im else-Zweig aufrufe, 
bekomme ich durch Anpassen der Anzahl der NOP-Befehle jedoch nicht die 
gleiche Länge hin.

Hoffe, dass mein Problem verständlich ist.

Wenn jemand eine bessere Lösung zur manuellen Programmierung der 
I2C-Schnittstelle kennt, bitte sagen. Und einen ander uC aussuchen zählt 
nicht ;)

von Andreas H. (heilinger)


Lesenswert?

Stefan B. schrieb:
> Deine Lösung muss beim Wechsel der Taktrate
> manuell angepasst werden.

Das stimmt. Aber so solls ja auch sein, da ich eine gleiche Anzahl an 
Befehlen abwarten will.

spess53 schrieb:
> Nochmal, die Anzahl der NOPs stimmt. Kannst su auch nachrechnen:
>
> 0x3256 - 0x3248 =0x0E oder 14 Byte. Und das sind genau 7 NOPs.

Ja, habe es an Hand der Adresse dann auch gesehen, Danke.

von Schorsch (Gast)


Lesenswert?

I²C ist doch überhaupt nicht Zeitkritisch... der Master (also dein µC) 
gibt den Takt vor, und kann dabei so langsam werden wie er mag.

Anders ausgedrückt: Der Jitter auf deinen SCL-Leitungen ist wurscht.

von Andreas H. (heilinger)


Lesenswert?

Schorsch schrieb:
> I²C ist doch überhaupt nicht Zeitkritisch... der Master (also dein µC)
> gibt den Takt vor, und kann dabei so langsam werden wie er mag.

nicht, wenn das natürlich nicht die einzige Operation auf dem uC ist und 
ich die Sensordaten jede Millisekunde auslesen will und dazu eben den 
Interrupt so kurz wie möglich halten will.

Edit:
benutze nen ATmega1284P mit 20MHz

von Karl H. (kbuchegg)


Lesenswert?

Andreas Heil schrieb:

> Ich habe 4 Sensoren, die ich per I2C-Schnittstelle auslesen kann. Da die
> Sensoren jedoch alle die gleiche Adresse haben, kann ich nicht für alle
> die gleiche Busleitung benutzen. Daher programmiere ich die
> I2C-Schnittstelle manuell, also Clock und Aus- und Einlesen der Pins.
> Hierfür gibt es natürlich Vorgaben wie lange die low- bzw. high-Phase
> sein muss.

Wie lang sie minimal sein müssen.
Länger darf es immer sein.

> Da ich zusätzlich noch eine if-Abfrage zwischendrin habe (ob Pin high
> oder low), möchte ich dass beide Wege (also if und else) gleich lang
> sind, also die Laufzeit gleich bleibt, egal ob ein high oder ein low am
> Pin anliegt, daher brauche ich auch dort mehrere NOP-Befehle.

Braucht kein Mensch.
Leg das Bit auf den Ausgang. Clock einschalten, kurz warten, Clock 
ausschalten. Fertig.

von Karl H. (kbuchegg)


Lesenswert?

Andreas Heil schrieb:
> Schorsch schrieb:
>> I²C ist doch überhaupt nicht Zeitkritisch... der Master (also dein µC)
>> gibt den Takt vor, und kann dabei so langsam werden wie er mag.
>
> nicht, wenn das natürlich nicht die einzige Operation auf dem uC ist und
> ich die Sensordaten jede Millisekunde auslesen will und dazu eben den
> Interrupt so kurz wie möglich halten will.

Dann Teile den Interrupt in mehrer Phasen auf

von Peter (Gast)


Lesenswert?

>Wenn jemand eine bessere Lösung zur manuellen Programmierung der
>I2C-Schnittstelle kennt, bitte sagen. Und einen ander uC aussuchen zählt

=> Benutze die die Funktions (bzw. das Macro) _delay_us()

Allerdings sind jegliche Delays in einer ISR grundsätzlich ein schlechte 
Idee!

von Oliver (Gast)


Lesenswert?

Andreas Heil schrieb:
> Er fügt quasi noch ein weitere nop ein, obwohl ich das nicht will, da es
> ja dann gegenüber den 6 mal NOP 2 mehr sind, und es bei mir deswegen
> wieder mit dem Timing nicht hinhaut.

Wie schon gesagt wurde, vergiß den C-Code, der als Kommentar im 
.lss-File steht. Was zählt, ist nur der Assemblercode. Da fügt der 
Compiler "von sich aus" niemals ein oder zwei Nops ein.

Dein Ansatz, die Laufzeiten beider Pfade mit Nop's anzugleichen, ist 
nicht verkehrt. Das sollte so funktionieren. Wenn du die Ein- und 
Aussprünge in die Nop-Funktionen mit berücksichtigst, klappt das.

Oliver

von Schorsch (Gast)


Lesenswert?

Ich hätt jetzt ja so I²C-Bus-Multiplexer vorgeschlagen, kosten nicht die 
Welt, und umgehen das Sensor-Adress-Problem elegant. Und du könntest das 
Hardware-TWI benutzen
Aber wenns jetzt schon so zeitkritisch wird, dann ist das vermutlich 
keine Lösung.

von Stefan B. (stefan) Benutzerseite


Lesenswert?

Durch den Funktionsaufruf hast du ja, wie Peter schon geschrieben hat, 
einen Overhead (minimal rcall und ret, wenn Register gerettet werden 
mehr).

Wenn du der besseren Lesbarkeit wegen mehrere NOPs zusammenfassen 
willst, kannst du auf zwei Arten vorgehen:

Makro
1
#define NOP_2mal() {           \
2
                     NOP();    \
3
                     NOP();    \ 
4
                   } while(0)

Inline Funktion
1
inline void NOP_2mal(void) 
2
{
3
  NOP();
4
  NOP();
5
}

Bei inline definierten Funktionen kann der GCC diese inlinen oder 
kann es lassen (z.B. weil es platzsparender ist). Wenn du merkst, dass 
GCC die Inlines als reguläre Funktionen übersetzt, musst du dich mit den 
GCC Optionen näher befassen.

von Andreas H. (heilinger)


Lesenswert?

Karl heinz Buchegger schrieb:
> Dann Teile den Interrupt in mehrer Phasen auf

hm, wie meinst du das? Den Interrupt öfters in kürzeren Abständen 
aufrufen? Wie gesagt, momentan läuft der Interrupt mit 1kHz und die 
längste Zeit ohne Operation ist 26 Befehle lang (Minimumm: 9 Befehle). 
Da die Sprünge in den und aus dem Interrupt ja auch Rechenzeit brauchen, 
wollte ich mir diese Sparen, da ich das ja dann relativ häufig machen 
müsste.

Nur zur Info:
Minimale low-Phase 1.3us
Minimale high-Phase 0.6us

an denen ich mich gerne orientieren möchte. Das Prorokoll ist insgesamt 
30 Bit lang, also 30 * 2us = 60us. Da noch einige Operationen zum 
Einlesen der Pins dabei sind, verzögert sich das ganze bei mir auf ca. 
110us.

Ich benutze auch nur den einen Interrupt, so dass ich keine anderen 
Interrupts aufhalten kann.

von Karl H. (kbuchegg)


Lesenswert?

Andreas Heil schrieb:
> Karl heinz Buchegger schrieb:
>> Dann Teile den Interrupt in mehrer Phasen auf
>
> hm, wie meinst du das? Den Interrupt öfters in kürzeren Abständen
> aufrufen?

Genau.
Und dafür dann halt immer nur einen kleinen Teil machen

> Da die Sprünge in den und aus dem Interrupt ja auch Rechenzeit brauchen,
> wollte ich mir diese Sparen, da ich das ja dann relativ häufig machen
> müsste.

Häufig?

Dein µC macht knapp 18 Millionen Instruktionen in der Sekunde, und du 
machst dir Sorgen wegen 20 oder 30 Anweisungen, die zb 4000 mal in der 
Sekunde auftreten (bei einem ISR Aufruf mit 4kHz bei dem dann jeweils 1 
Sensor reihum abgefragt wird). Das ist noch nicht mal 1%!


In mehrere Phasen aufteilen kann auch bedeuten, dass zb die Wartezeit 
abwarten auch eine Phase ist. Bei einem ISR aufruf wird der 
Clock-Ausgang gesetzt, beim nächsten wieder gelöscht. Und die Aufrufe 
sind so getimt, dass sich die gewänschte Wartezeit (oder länger) ergibt.

von Andreas H. (heilinger)


Lesenswert?

ich möchte alle Sensoren so zeitgleich wie möglich auslesen, daher ist 
die Lösung die Sensoren nacheinander auszulesen evtl. nicht so gut, da 
die Werte ja dann bis zu 750us auseinander liegen würden.

Aber danke auf jeden Fall für die ganzen Hilfen und Anregungen.

von Karl H. (kbuchegg)


Lesenswert?

Da du die Sensoren ja sowieso an verschiedenen Pins hängen hast, hindert 
dich doch nichts daran, alle 4 jeweils gleichzeitig das nächste 0 oder 1 
Bit an den Datenpin anzulegen und allen gemeinsam den jeweils nächsten 
Clock Puls zu geben. Kein Mensch sagt, dass du erst einem Sensor alle 8 
Bits zuspielen musst, damit du dann dem nächsten die 8 Bits zuspielst. 
Du kannst durch deine Hardware auch allen 4 Sensoren parallel die 8 Bits 
zuspielen.

Oder hab ich da jetzt deine Hardwarebeschreibung misverstanden?

von Stefan B. (stefan) Benutzerseite


Lesenswert?

Fhelrberichtigung:
1
#define NOP_2mal() do          \
2
                   {           \
3
                     NOP();    \
4
                     NOP();    \ 
5
                   } while(0)

von Simon K. (simon) Benutzerseite


Lesenswert?

Noch mal: Bei Software I2C braucht man nichts zeitlich angleichen. I2C 
ist pegel- und flankenorientiert und nicht zeitorientiert.

von Andreas H. (heilinger)


Lesenswert?

Karl heinz Buchegger schrieb:
> Oder hab ich da jetzt deine Hardwarebeschreibung misverstanden?

nene, das hast du richtig verstanden. Alle 4 Sensoren hängen mit SDA an 
jeweils einem Pin vom gleichen Port und zusätzlich hängen alle Sensor 
mit SCK an einem einzigen Port, so dass quasi alle mit dem gleichen 
Taktisgnal arbeiten.
Und alle 4 Sensoren bekommen gleichzeitg ihr Bits, bzw. senden die Bits 
gleichzeitig an den uC.

von Andreas H. (heilinger)


Lesenswert?

Simon K. schrieb:
> Noch mal: Bei Software I2C braucht man nichts zeitlich angleichen. I2C
> ist pegel- und flankenorientiert und nicht zeitorientiert.

es gibt gewisse Zeiten, die man minimal einhalten muss (high-/low-Pegel) 
bzw. so steht es in dem Datenblatt von den Drucksensoren. (siehe oben). 
Und an denen will ich mich orientieren. Klar kann ich jetzt auch die 
Zeiten verlängern, aber wozu unnötig das ganze hinauszögern?

von Karl H. (kbuchegg)


Lesenswert?

Andreas Heil schrieb:
> Karl heinz Buchegger schrieb:
>> Oder hab ich da jetzt deine Hardwarebeschreibung misverstanden?
>
> nene, das hast du richtig verstanden. Alle 4 Sensoren hängen mit SDA an
> jeweils einem Pin vom gleichen Port und zusätzlich hängen alle Sensor
> mit SCK an einem einzigen Port, so dass quasi alle mit dem gleichen
> Taktisgnal arbeiten.
> Und alle 4 Sensoren bekommen gleichzeitg ihr Bits, bzw. senden die Bits
> gleichzeitig an den uC.

Ich versteh jetzt immer noch nicht, warum es jetzt so wahnsinnig wichtig 
ist, die richtige Anzahl an NOP einzufügen. Die Zeitangaben der Sensoren 
sind Minimalwerte, wenn du etwas länger bist, ist das für den Sensor 
kein Problem.

von Andreas H. (heilinger)


Lesenswert?

jo,
allerdings habe ich ein Abfrage beim Einlesen der Pins:

z.B.
1
if (c_Pina1)
2
      uint_Rohwert12bit_Druck |= (1<<13);

wenn c_Pina1 = 0, dann würde sich die Laufzeit unterscheiden , da 
uint_Rohwert12bit_Druck |= (1<<13); eine gewisse Anzahl an Befehlen 
braucht.
Da ich aber eine feste Zeit für den kompletten Durchlauf durch die 
I2C-Schnittstelle haben möchte, möchte ich eben in dem else-Zweig 
genauso viele NOPs einfügen, wie im if-Zweig Befehle für die Operation 
benötigt werden. Hat in diesem Fall nichts damit zu tun, dass ich die 
minimalen Zeiten einhalten muss.

Anders sieht es aus bei dem Bespiel aus, wo ich nur den SCK-Pegel 
ändere, z.B.
1
    PINA = (1<<PA4); //CLK toggeln (auf high)
2
    NOP_12mal();        //high-CLK-Signal mind. 0.6us = 12 Befehle
3
4
//3. Adressbit senden (high, nicht verändern)
5
PINA = (1<<PA4);        //CLK toggeln (auf low)
6
    NOP_26mal();        //low-CLK-Signal mind. 1.3us = 26 Befehle

Da brauche ich eben die NOPs, um die Minimalzeiten einzuhalten.

Hoffe, dass es jetzt verständlicher ist.

von Andreas H. (heilinger)


Lesenswert?

Hm, da fällt mir jetzt noch ne Alternative ein:

Soll ich lieber für jeden Taktzyklus eine Variable spendieren, und die 
einzelnen Bits erst nach der kompletten I2C-Kommunikation 
zusammenbasteln, dann würde ich mir die NOPs im else-Zweig sparen...?!

von Huch (Gast)


Lesenswert?

Ich verstehe das immer noch nicht mit den NOPs. Warum keinen Timer? Aber 
gut.

Jedenfalls muss:
1
PINA = (1<<PA4); //CLK toggeln (auf high)

lauten:
1
PORTA = (1<<PA4); //CLK toggeln (auf high)

von Schorsch (Gast)


Lesenswert?

Huch schrieb:
> Jedenfalls muss:
> PINA = (1<<PA4); //CLK toggeln (auf high)
>
> lauten:
> PORTA = (1<<PA4); //CLK toggeln (auf high)

Nein.
Ein Write auf PIN (also das Input-Register) wirkt wie XOR mit dem 
Output-Register. Nur halt ohne "Read-Modify-Write".

von Huch (Gast)


Lesenswert?

@ Schorsch

>Nein.
>Ein Write auf PIN (also das Input-Register) wirkt wie XOR mit dem
>Output-Register. Nur halt ohne "Read-Modify-Write".

Oh. Wirklich? Wieder was gelernt. Magst Du mich bitte eine Stelle im 
Datenblatt verweisen?

von Huch (Gast)


Lesenswert?

Habs.

13.2.2 Toggling the Pin
Writing a logic one to PINxn toggles the value of PORTxn, independent on 
the value of DDRxn.
Note that the SBI instruction can be used to toggle one single bit in a 
port.

Mal gelesen, aber wiede vergessen. Danke.

von Schorsch (Gast)


Angehängte Dateien:

Lesenswert?

Huch schrieb:
> Magst Du mich bitte eine Stelle im
> Datenblatt verweisen?

Beispielhaft im Tiny2313-Datenblatt.
Ist nur in einem Satz erwähnt, deswegen wohl leicht zu übersehen.

von Karl H. (kbuchegg)


Lesenswert?

Andreas Heil schrieb:

>
1
if (c_Pina1)
2
>       uint_Rohwert12bit_Druck |= (1<<13);
>
> wenn c_Pina1 = 0, dann würde sich die Laufzeit unterscheiden , da
> uint_Rohwert12bit_Druck |= (1<<13); eine gewisse Anzahl an Befehlen
> braucht.

OK.
Aber die eigentliche Frage lautet doch:
Spielt das überhaupt eine Rolle, oder siehst du hier Gespenster?
Im Vergleich zum Zeitbedarf des Rests ist der Unterschied so minimal, 
dass er sich kaum auswirkt.
Ausserdem: Der nächste Bitzyklus beginnt ja sowieso erst mit der 
steigenden Flanke des nächsten Clock-Signals und wenn die ein paar 100 
Nanosekunden später kommt, interessiert das auch keinen Sensor.

> Da ich aber eine feste Zeit für den kompletten Durchlauf durch die
> I2C-Schnittstelle haben möchte

Ich denke hier liegt der eigentliche Knackpunkt: Warum?

> Da brauche ich eben die NOPs, um die Minimalzeiten einzuhalten.
>
> Hoffe, dass es jetzt verständlicher ist.

Nein.
Ich denke immer noch, du versuchst hier päpstlicher als der Papst zu 
sein. Eine synchrone Übertragung mittels Takt macht man genau aus dem 
Grund, damit man genau solche Probleme eben nicht hat. Damit sich 
Timings flexibel anpassen können und niemand gezwungen ist, ein fixes 
Zeitraster einzuhalten.

von Andreas H. (heilinger)


Lesenswert?

Hm,

wie gesagt im Datenblatt des Drucksensors steht, wie lang die kürzeste 
Zeit für einen Pegel sein muss.

Angenommen mein Sensor hat die Adresse 127, also 1111111bin.

Da kann ich doch dann nicht hintereinander mit jedem Befehl den Pegel 
des Taktsignals ändern, das würde ja dann bei einer CPU-Taktfrequenz von 
20 MHz bedeuten:

50ns low-Pegel -> 50ns high-Pegel -> 50ns low-Pegel.

Dies erkennt der Sensor nicht, und würde dann einige Takte nicht 
mitbekommen.
Daher brauche ich doch dann eine Verzögerung. Ob die jetzt durch anderen 
Code oder eben durch NOPs realisiert wird, ist doch wurscht. Und mir ist 
auch klar, dass ein Pegel auch 1 Sekunde lang auf einem Pegel gehalten 
werden kann, Fakt ist einfach, dass eine Verzögerung durchgeführt werden 
muss.

Und wegen dem Zeitraster, na klar muss nicht jeder Pegel gleich lange 
dauern etc., so lange er die Mindestzeit einhält. Nur wenn man sich das 
Protokoll dann auf dem Scope angucken möchte, bekommt man ein besseres 
Bild, wenn die Pegel sich immer zu  konstanten Zeiten ändern, egal 
welchen Weg man durch den Code nimmt.

Aber das ist natürlich Geschmackssache. Und Geschmäcker unterscheiden 
sich bekanntlich...

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.