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
voidNOP_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
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
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
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
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 ;)
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.
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.
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
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.
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
>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!
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
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.
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
inlinevoidNOP_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.
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.
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.
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.
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?
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.
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?
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.
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.
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...?!
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".
@ 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?
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.
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.
>> 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.
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...