Hallo zusammen,
ich begreife es einfach nicht: Nehmen wir an, wir haben ein (zumindest
auf den ersten Blick) funktionierendes Programm, das in einer
while-Schleife immer wieder die gleichen Befehle ausführt:
1
...
2
while(1){
3
4
while(PINSB==0xC0)OUTC=0x01;
5
while(PINSB!=0xC0)OUTC=0x02;
6
OUTC=0x00;
7
cnt=0;
8
while(PINSB==0xC0)OUTC=0x04;
9
while(PINSB!=0xC0)OUTC=0x08;
10
OUTC=0x00;
11
12
OWTargetSetup(0x22);
13
14
while(PINSB==0xC0)OUTC=0x10;
15
while(PINSB!=0xC0)OUTC=0x20;
16
OUTC=0x00;
17
}
Nun fügen wir einige Funktionen dem Programm am ENDE der whlie-Schleife
hinzu:
1
...
2
while(1){
3
4
while(PINSB==0xC0)OUTC=0x01;
5
while(PINSB!=0xC0)OUTC=0x02;
6
OUTC=0x00;
7
cnt=0;
8
while(PINSB==0xC0)OUTC=0x04;
9
while(PINSB!=0xC0)OUTC=0x08;
10
OUTC=0x00;
11
12
OWTargetSetup(0x22);
13
14
while(PINSB==0xC0)OUTC=0x10;
15
while(PINSB!=0xC0)OUTC=0x20;
16
OUTC=0x00;
17
18
rslt=OWFirst();
19
if(rslt)rslt=OWVerify();
20
var0=rslt;
21
22
rslt=OWFirst();
23
if(rslt)rslt=OWVerify();
24
var1=rslt;
25
26
rslt=OWFirst();
27
if(rslt)rslt=OWVerify();
28
var2=rslt;
29
}
Und zack funktioniert das Programm nicht mehr.
Natürlich kann man dann sagen, es liegt an den hinzugefügten Funktionen,
aber das widerspricht trotzdem meinen Erwartungen, denn:
Die Abschnitte
1
while(PINSB==0xC0)OUTC=0x10;
2
while(PINSB!=0xC0)OUTC=0x20;
3
OUTC=0x00;
bewirken, dass das Programm "anhält", wartet (LED 1 an Port C leuchtet),
bis ein Taster auf der Platine gedrückt wird (LED 2 an Port C leuchtet)
und wieder losgelassen wird (LED erlischt). Somit kann ich verfolgen, wo
das Programm gerade ist. Nun, wenn ich genannten Code hinzugefügt habe
(siehe Codeausschnitt 2), kommt das Programm nicht einmal bis zum ersten
"Haltepunkt" (es leuchtet keine LED). Falls die Funktionen, die ich
hinzugefügt habe, fehlerhaft sein sollten, müsste das Programm aber doch
wenigstens bis zu dem ersten Aufruf einer fehlerhaften Funktion laufen,
oder? Wenn ja, was für Gründe kann es geben, dass es das nicht tut?
Please help me!
Viele Grüße,
Stefan
echt schade, dass mir keiner helfen kann... ich sitze hier nämlich schon
seit Tagen an diesem sch*** und kann mich eigentlich gar nicht aufs
richtige Programmieren konzentrieren, weil ich immer irgendwelche
Compiler-spezifischen oder Chip spezifischen oder wie auch immer
Probleme habe...
Welcher Compiler, welcher Controller, compilierfähiger Code ...
Oder auch Assembler Listing. Viele Dinge die eine Diagnose deutlich
vereinfachen können.
Okay, also hier das Programm im Anhang. Ich nutze den AN2131 von Cypress
und µVision3.
Wenn ich die Interrupts deaktiviere, läuft das Programm sogar (heyho!)
bis zum Aufruf von OWTargetSetup(). Also als letztes "Lebenszeichen"
bekomme ich das Leuchten von der LED an Pin C3, das durch die
Programmzeile while(PINSB!=0xC0)OUTC=0x08; ausgelöst wird, mit. Danach
verliert sich die Spur. Ohne die Funktionsaufrufe ab rslt = OWFirst();
läuft das Programm wie im ersten Post gesagt regulär (zumindest
scheinbar) und es werden alle Ports in der richtigen Reihenfolge nach
Tastendruck gesetzt (die while-Schleifen).
Viele Grüße,
Stefan
Stefan S. schrieb:
> bewirken, dass das Programm "anhält", wartet (LED 1 an Port C leuchtet),> bis ein Taster auf der Platine gedrückt wird (LED 2 an Port C leuchtet)> und wieder losgelassen wird (LED erlischt).
Wenn Du nur eine Taste testen willst, dann ist es das dümmste, immer den
ganzen Port zu testen.
Man will ja vielleicht die anderen Portpins auch noch benutzen.
Im schlimmsten Fall sind die sogar tristate und wackeln lustig vor sich
hin.
Dann hast Du kein Programm mehr, sondern einen Zufallsgenerator.
Dein Chip ist doch ein 8051, der macht liebend gerne Bitbefehle.
Definiere also den Tastenpin als sbit und teste dann nur diesen.
Und wenn Du nur einen Outputpin setzen willst, geht das auch viel
einfacher als sbit definiert.
Peter
Peter Dannegger schrieb:
> Wenn Du nur eine Taste testen willst, dann ist es das dümmste, immer den> ganzen Port zu testen.
Das war jetzt nur für Testzwecke, um den Fehler oder die Fehler im
Programm rauszufinden, weil ich nie wusste, wo sich das Programm
"aufhängt"... ich bräuchte eher Hilfe bei meinem eigentlichen Problem...
Ein Punkt, der mir schonmal weiterhelfen könnte, wäre: Gibt es noch
etwas im Programm zu beachten, wenn man Interrups verwendet, damit
nichts unerwartetes passiert? (Was ich schon gemacht habe: OUTA als
volatile deklariert, weil es von der ISR geändert wird)
Grüße
Stefan S. schrieb:
> "aufhängt"... ich bräuchte eher Hilfe bei meinem eigentlichen Problem...
Wenn Du meinst, daß das Eliminieren von 7 möglichen Fehlerquellen keine
Hilfe sei. Mir kanns ja egal sein.
Peter
Sorry, ich glaube, ich habe dich missverstanden... ich dachte, du
meintest die Portabfrage bei den für die Tests eingebauten
while-Schleifen. Aber für die Datei onewire.c könnte ich das wirklich
machen ;-)
Die Frage ist nur, wie ich das anstellen soll... kann ich einfach einen
Pin als sbit deklarieren?! Wie muss ich mir das vorstellen?
Viele Grüße,
Stefan
> Im schlimmsten Fall sind die sogar tristate und wackeln vor sich hin.> Dein Chip ist doch ein 8051...
Bei einem 8051-Derivat gibt es an unbeschalteten Pins kein Wackeln, der
Pullup zieht sie auf Vcc.
Ins Grübeln würde mich sowas bringen:
1
OUTC=0x00;// |
2
OUTC=0x00;// |
3
OUTC=0x00;// |
4
OUTC=0x00;// V
5
OUTC=0x00;// ca. 3 us
6
for(i=0;i<33;i++)OEC=0x00;// 33 Schleifendurchläufe = ca. 68 us
Das optimiert ein Compiler u.U. einfach heraus... :-o
Und das:
1
xdataunsignedcharvar0_at_0x011B;
2
xdataunsignedcharvar1_at_0x011C;
Kannst du die Verwaltung der Speicheradressen nicht einfach dem Compiler
überlassen?
Stefan S. schrieb:
> Die Frage ist nur, wie ich das anstellen soll... kann ich einfach einen> Pin als sbit deklarieren?! Wie muss ich mir das vorstellen?
Mal ins Manual Deines Compilers schauen.
Beim Keil C51 gehts z.B. so:
Ich werde micht jetzt mal daran machen, die Datei onewire.c nochmals neu
zu erstellen und dabei nur einen Pin zu schalten. Doch dabei ergibt sich
wieder ein Problem: Der AN2131 hat zwar einen 8051-Kern, aber der
Zugriff ist meiner Recherche nach nicht bitweise möglich (ist das
falsch, bitte korrigieren); OUTC ist auch kein sfr...
Hat jemand eine Idee, wie man Codesequenzen wie
1
OUTC=0x00;// |
2
OUTC=0x00;// |
3
OUTC=0x00;// |
4
OUTC=0x00;// V
5
OUTC=0x00;// ca. 3 us
6
for(i=0;i<33;i++)OEC=0x00;// 33 Schleifendurchläufe = ca. 68 us
eleganter gestalten könnte (Ich benötige genaue Low-Zeiten an dem einen
Portpin)?
Viele Grüße,
Stefan
Stefan S. schrieb:
> Ich werde micht jetzt mal daran machen, die Datei onewire.c nochmals neu> zu erstellen
Wenn du das tust, denk auch noch eine Sekunde darüber nach, ob du nicht
allgemein anerkannte Codeformatierungen übernehmen willst.
Es ist allgemein üblich, die von einer Bedingung abhängige Anweisung in
eine neue Zeile zu schreiben. Man sieht dann ganz einfach leichter, was
von wem abhängt. Und auch ein paar Leerzeichen an strategisch günstigen
Stellen haben schon oft den Lese- und Verständnisfluss erleichtert. Wir
sind schliesslich alle seit Kindesbeinen darauf gedrillt worden, dass
zwischen 2 Wörtern ein Leerraum steht. Bei den Römern war das noch
anders, aber irgendwann im Laufe der Geschichte hat sich herausgestellt,
dass BuchstabeanBuchstabezusetzeneseinfachnichtbringt, auch wenn es
Platz spart.
1
for(i=0;i<33;i++)// 33 Schleifendurchläufe = ca. 68 us
Okay, mache ich ;-)
Bevor ich jetzt mir die ganze Arbeit mache und dann wieder alles über
den Haufen werfe, frage ich lieber jetzt nochmal: Gibt es andere
Möglichkeiten, wie man die Low-Zeiten an einem Port genau schalten kann
(nicht so "unelegant", wie ich es gemacht habe (einfach oft genug den
Befehl OUTC = 0x00 ausführen lassen))?
Und, was auch noch sehr wichtig ist: Kann ich etwas in der Art
1
sbitPin_C1=OUTC^1;
schreiben, damit ich nur den einen Pin setze/ lösche? Oder muss ich
immer mit maskierungen arbeiten?
Wie gesagt: Ich nutze den AN2131 von Cypress.
Grüße,
Stefan
Stefan S. schrieb:
> Okay, mache ich ;-)> Bevor ich jetzt mir die ganze Arbeit mache und dann wieder alles über> den Haufen werfe, frage ich lieber jetzt nochmal: Gibt es andere> Möglichkeiten, wie man die Low-Zeiten an einem Port genau schalten kann> (nicht so "unelegant", wie ich es gemacht habe (einfach oft genug den> Befehl OUTC = 0x00 ausführen lassen))?
Das kommt drauf an, was 'genau' bedeutet? Wie genau muss denn 'genau'
sein?
Denk auch immer daran, dass es durchaus möglich ist, dass während deiner
'Sequenz' ein Interrupt dazwischenfunkt und dir das Timing versauen
kann.
1. Da ich mit dem Onewire-Protokoll arbeite, müssen die Low-Zeiten im
µs-Bereich stimmen... die kürzeste low-Zeit sollte 5 µs nicht
überschreiten. Das längste low gibts beim Reset (mind 480 µs).
2. Wie sollte ich das mit den Interrups am besten regeln? Während den
Übertragungnen mit EA = 0; sperren?
Grüße,
Stefan
Stefan S. schrieb:
> 1. Da ich mit dem Onewire-Protokoll arbeite, müssen die Low-Zeiten im> µs-Bereich stimmen... die kürzeste low-Zeit sollte 5 µs nicht> überschreiten. Das längste low gibts beim Reset (mind 480 µs).
Kritisch ist das einzelne Bit, dessen Steuerung man sinnvollerweise
gegen Interrupts absichert. Zwischen den Bits stören sie nicht.
Wer Interrupt-Handler so schreibt, dass die 480µs Reset in Verbindung
damit ggf. zum Powerdown eines parasitär versorgten 1-Wire-Device führt,
der sollte nochmal intensiv über sein Konzept von Interrupts nachdenken.
> Den rest las mal den Compiler machen...>> Für "genaue" Zeiten könnte man mitels Assemblerdirektive die benötigte> Zahl von nops einfügen z.B. so:>
1
asmvolatile("nop");
> Ist aber ggf vom Compiler abhängig wie es genau heißt.
Also geht es nicht ohne Maskierungen?
Die Assemblerdirektive müsste ich dann z.B. 20 Mal im Code einfügen?
Oder wie kann ich das verstehen? Weil eine for-Schleife würde das
Resultat erheblich beeinflussen...
>1. Da ich mit dem Onewire-Protokoll arbeite, müssen die Low-Zeiten im>µs-Bereich stimmen... die kürzeste low-Zeit sollte 5 µs nicht>überschreiten.
Wieso das denn ?
Das 1-Wire Protokoll hat doch Faktoren zwischen kürzester und längster
Zeit.
Gruss
Axel
A. K. schrieb:
> Wer Interrupt-Handler so schreibt, dass die 480µs Reset in Verbindung> damit ggf. zum Powerdown eines parasitär versorgten 1-Wire-Device führt,> der sollte nochmal intensiv über sein Konzept von Interrupts nachdenken.
Du meinst, dass die ISR nicht zu lang ist? Keine Angst, die sieht nur so
spartanisch aus (bringt nur eine LED zum Blinken; als "Lebenszeichen")
Stefan S. schrieb:
> Also geht es nicht ohne Maskierungen?
Compiler sind normalerweise schlauer als du denkst.
Die erkennen dieses Muster und ersetzen das durch Bitzugriffe.
> Die Assemblerdirektive müsste ich dann z.B. 20 Mal im Code einfügen?> Oder wie kann ich das verstehen? Weil eine for-Schleife würde das> Resultat erheblich beeinflussen...
Das kann man aber rausmessen und berücksichtigen. Bis zu einem gewissen
Grad ist der Überhang durch die for-Schleife ja konstant.
Gibt es in deiner Bibliothek keine vorgefertigte Delay-Funktion?
Karl heinz Buchegger schrieb:
> Das kann man aber rausmessen und berücksichtigen. Bis zu einem gewissen> Grad ist der Überhang durch die for-Schleife ja konstant.> Gibt es in deiner Bibliothek keine vorgefertigte Delay-Funktion?
Hm... ich weiß von keiner... kennt vielleicht jemand eine
delay-Funktion, die bei µVision3 von Keil enthalten ist?
Lothar Miller schrieb:
> Ins Grübeln würde mich sowas bringen:>
1
>OUTC=0x00;// |
2
>OUTC=0x00;// |
3
>OUTC=0x00;// |
4
>OUTC=0x00;// V
5
>OUTC=0x00;// ca. 3 us
6
>for(i=0;i<33;i++)OEC=0x00;// 33 Schleifendurchläufe = ca. 68 us
7
>
> Das optimiert ein Compiler u.U. einfach heraus... :-o
Sieht schon komisch aus, aber in dem Falle darf der Compiler nix
optimieren, da OUTC ein Register ist und somit als volatile deklariert
ist, soweit ich das weiß.
Simon K. schrieb:
> ... da OUTC ein Register ist und somit als volatile deklariert> ist, soweit ich das weiß.
Irgendwas ist da noch faul. Hier ein Auszug aus der EZRegsHEAD.h:
Peter Dannegger schrieb:
> Anbei mal ein 1-wire Beispiel.
Für welchen Chip ist das denn gedacht?
Außerdem verstehe ich diese "Berechnung" oder was auch immer das
darstellen soll nicht:
i = (uchar)( XTAL / 12e6 * 480 / 4 ); // 480 < t < 960
Und diese Funktion heir kann sowohl ein Bit lesen, als auch schreiben?!?
Lothar Miller schrieb:
> Irgendwie ist der OUTA anders zu handhaben als die anderen beiden> Ports...
Das habe ich nur so deklariert, weil OUTA in der Interrupt-SR geändert
wird... ist aber doch richtig so, oder?
Lothar Miller schrieb:
> Simon K. schrieb:>> ... da OUTC ein Register ist und somit als volatile deklariert>> ist, soweit ich das weiß.> Irgendwas ist da noch faul. Hier ein Auszug aus der EZRegsHEAD.h:>
Stefan S. schrieb:
> Lothar Miller schrieb:>> Irgendwie ist der OUTA anders zu handhaben als die anderen beiden>> Ports...>> Das habe ich nur so deklariert, weil OUTA in der Interrupt-SR geändert> wird... ist aber doch richtig so, oder?
OUTA, OUTB und OUTC sind die Bezeichnungen für einen Hardwareport?
Wenn ja, dann sollten alle volatile sein.
Du willst ja schliesslich, dass jegliche Zuweisung an OUTB durchgeführt
wird. Selbst dann wenn der Compiler denkt, OUTB hätte schon den Wert den
du zuweisen möchtest.
Karl heinz Buchegger schrieb:
> OUTA, OUTB und OUTC sind die Bezeichnungen für einen Hardwareport?
Da bin ich mir nicht 100%ig sicher... bisher dachte ich schon, dass das
die Bezeichnungen für den Hardwareport sind, aber jetzt sehe ich das mit
P0, etc. für den 8051...
Hier ein Ausschnitt aus dem TRM:
The OUTn registers provide the data that drives the port pin when OE=1
and the PORTnCFG pin is 0. If the port pin is selected a an input
(OE=0), the value stored in the corresponding OUTn bit is stored in an
output latch but not used.
Port C Outputs OUTC 7F98
b7 b6 b5 b4 b3 b2 b1 b0
OUTC7 OUTC6 OUTC5 OUTC4 OUTC3 OUTC2 OUTC1 OUTC0
R/W R/W R/W R/W R/W R/W R/W R/W
0 0 0 0 0 0 0 0
> Außerdem verstehe ich diese "Berechnung" oder was auch immer das> darstellen soll nicht:> i = (uchar)( XTAL / 12e6 * 480 / 4 ); // 480 < t < 960
Was verstehst du daran nicht?
Zusammen mit
while( --i )
;
sollte das doch ziemich eindeutig sein (und i sollte besser als volatile
definiert werden)
XTAL ist bei PeDa die traditionelle Bezeichnung für die Taktfrequenz in
Herz.
> OUTA, OUTB und OUTC sind die Bezeichnungen für einen Hardwareport?> ... für den 8051...
Der AN2131 hat eine ganz andere Port-Ansteuerung als ein Standard 8051
:-o
Karl heinz Buchegger schrieb:
>> Außerdem verstehe ich diese "Berechnung" oder was auch immer das>> darstellen soll nicht:>> i = (uchar)( XTAL / 12e6 * 480 / 4 ); // 480 < t < 960>> Was verstehst du daran nicht?
XTAL ist die Frequenz des Quarzes, die wird durch 12MHz geteilt (warum
durch 12MHz??), wenn ich das richtig verstanden habe... warum aber dann
mit 480/4 multipliziert?
Schon klar, dass dann irgendwie die Zeit rauskommen soll, die das Prog
warten soll.
480µs und 4 Takte pro Schleifendurchlauf.
Millionen weil in Mikrosekunden und 12 weil der klassische 51er Core 12
Takte pro Befehlszyklus braucht.
Sieht schöner aus, wenn man das im Makro verstaut:
1
#define DELAY(us) do{ uchar dly = (XTAL / 12e6 * (us) / 4); if (dly) while (--dly); }while(0)
und später dann einfach
DELAY(480);
DELAY(45);
DELAY(15);
Das ist ein kleines bischen weniger genau, weil die Zuweisung an dly
dann Teil der Zeit ist. Kann man aber wegrechnen.
Also:
XTAL/12 = Anzahl der ausgeführten Befehle pro µs
Dann ist logisch, dass man das mit 480 multipliziert, um die Anzahl der
erforderlichen Befehlszyklen für 480µs delay herauszufinden. Aber was
meinst du mit "4 Takte pro Schleifendurchlauf"? etwa:
Stefan S. schrieb:
> meinst du mit "4 Takte pro Schleifendurchlauf"? etwa:>
1
>do{// 1. Takt
2
>w1_dat=0;// 2. Takt
3
>nop();// 3. Takt
4
>}while(--i);// 4. Takt
5
>
Vergiss die Taktzählung im C-Source Code.
Du musst im Assemblerlisting, welches der Compiler generiert nachsehen,
was der Compiler aus der Schleife macht. Nur das zählt.
Der original-Programmier wird wahrscheinlich genau das gemacht haben und
rausgefunden haben, dass
while (--dly)
;
in eine Schleife compiliert, die pro Durchlauf 4 Takte verbraucht.
Also müsste ich im Assemblercode nachschauen, in wie viele Zeilen
Assemblerbefehle die Schleife aufgeteilt wurde. Das sind dann die Takte?
Also bei mir:
Stefan S. schrieb:
> Also müsste ich im Assemblercode nachschauen, in wie viele Zeilen> Assemblerbefehle die Schleife aufgeteilt wurde. Das sind dann die Takte?
Nicht notwendigerweise.
Anzahl Befehle <> Anzahl Takte
denn kein Mensch behauptet ja schliesslich, dass jeder Befehl in genau
einem Takt abgearbeitet wird. Diese Info steht wiederrum im Datenblatt
des Prozessors. Irgendwo unter 'Instruction set' oder so ähnlich.
Eher mehr als 6. Nicht jeder Befehl braucht nur einen Zyklus. Und bei
51ern kann es sein, dass dies nicht im Datasheet des Controllers steht,
sondern woanders. Ausserdem hängt das ggf. von der Optimierung ab. PeDa
hat seine Variante für einen ganz bestimmten Compiler optimiert, und
wohl bewusst ohne "volatile".
Mit "volatile" hängt das auch noch davon ab, in welchem der 2-3
RAM-Bereiche die Variable landet, ohne "volatile" kann ein Compiler
sowas ggf. komplett wegoptimieren. Letzteres ist eine gefürchtete
Eigenheit von GCC, Keil mag da weniger agressiv vorgehen, weil die nicht
mit SPECmarks rumwedeln müssen.
M.a.W: Das ist kein einfaches Thema, und man sollte sich dazu Rat bei
jenen Leuten holen, die den betreffenden Compiler verwenden. Code für
andere Compiler und andere Optimierungslevels ist dabei nutzlos.
Gut. Dann hier eine konkrete Frage: Wie viele Taktzyklen braucht die
Schleife, wenn mein Compiler (µVision3, Optimierungslevel: 6 Loop
rotation) folgenden Assembler-Code liefert (Datenblatt meines Chips im
Anhang, Abschnitt B2; ich weiß nur nicht, wie ich die dortigen Angaben
zu deuten habe...):
Das scheint ja ein total verrückter Chip zu sein, er hat gar keine Ports
P0..P3.
Da sind nur 3 Ports in den externen Memory gemappt und deshalb wird wohl
auch das langsame, schwerfällige, codefressende LARGE-Memorymodell
ausgewählt.
Damit werden IO-Zugriffe deutlich langsamer und Bitzugriffe gehen
garnicht.
Die ganzen Delays und Portzugriffe aus meinem Beispiel mußt Du völlig
umschreiben, die gehen garnicht.
Peter
Okay, trotzdem danke ;-)
Wäre es eine Idee es mit nop's in einer Schleife zu probieren und mit
nem Oszilloskop zu messen, wie lange sie braucht? Weil ich -um ehrlich
zu sein- mich nicht in der Lage sehe, den Code von dir passend
umzuschreiben ;-)
Wäre eine Möglichkeit.
Du kannst aber auch einfach die Delay-Funktion (mit irgendeiner
vorgegebenen Zeit, zb 500 µs) in einer Schleife 1000 mal aufrufen (oder
10000 mal) und mit einer Uhr die Zeit stoppen, die in der Zwischenzeit
vergeht. Um den Overhead in der äusseren Schleife zu minimieren, könnte
man zb so was machen
1
// Led1 einschalten
2
3
for(i=0;i<1000;++i){
4
delay(500);
5
delay(500);
6
delay(500);
7
delay(500);
8
delay(500);
9
delay(500);
10
delay(500);
11
delay(500);
12
delay(500);
13
delay(500);
14
}
15
// Led2 einschalten
dadurch wirkt sich die außere Schleife nur noch sehr wenig auf das
Ergebnis aus.
Du stoppst die Zeit zwischen den Einschaltvorgängen der LED. Da du
insgesamt 10000 delays gemacht hast, kannst du dann hergehen und dir
ausrechnen um wieviel ein einziger delay von seiner Vorgabezeit
abgewichen ist und dann die delay Funktion entsprechend anpassen.
Für die meisten Zwecke wird das sicherlich genau genug werden.