Forum: Mikrocontroller und Digitale Elektronik Aufrufen von Funktion legt auch vorherigen Teil des Programms lahm


von Stefan S. (stefanst)


Lesenswert?

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

von Stefan S. (stefanst)


Lesenswert?

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...

von Ronny (Gast)


Lesenswert?

Was für einen Controller setzt du denn ein?
Sind Interrupts aktiv?

von Gast (Gast)


Lesenswert?

Welcher Compiler, welcher Controller, compilierfähiger Code ...

Oder auch Assembler Listing. Viele Dinge die eine Diagnose deutlich 
vereinfachen können.

von Stefan S. (stefanst)


Angehängte Dateien:

Lesenswert?

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

von Peter D. (peda)


Lesenswert?

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

von Stefan S. (stefanst)


Lesenswert?

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

von Stefan S. (stefanst)


Lesenswert?

...

von Peter D. (peda)


Lesenswert?

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

von Stefan S. (stefanst)


Lesenswert?

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

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

> 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
xdata unsigned char var0 _at_ 0x011B;
2
xdata unsigned char var1 _at_ 0x011C;
Kannst du die Verwaltung der Speicheradressen nicht einfach dem Compiler 
überlassen?

von Peter D. (peda)


Lesenswert?

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:
1
#include <reg51.h>
2
3
sbit MISO = P1^0;
4
sbit MOSI = P1^1;
5
sbit SCK = P1^2;


Peter

von Stefan S. (stefanst)


Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

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
2
    OEC = 0x00;

Sieht doch gleich besser aus :-)

von Stefan S. (stefanst)


Lesenswert?

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
sbit Pin_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

von Karl H. (kbuchegg)


Lesenswert?

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.

von Läubi .. (laeubi) Benutzerseite


Lesenswert?

Setzen:
1
OEC |= (1<<MeinBit);
Löschen
1
OEC &= ~(1<<MeinBit);
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
asm volatile("nop");
Ist aber ggf vom Compiler abhängig wie es genau heißt.

von Stefan S. (stefanst)


Lesenswert?

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

von (prx) A. K. (prx)


Lesenswert?

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.

von Stefan S. (stefanst)


Lesenswert?

Läubi .. schrieb:
> Setzen:
>
1
OEC |= (1<<MeinBit);
> Löschen
>
1
OEC &= ~(1<<MeinBit);
> 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
asm volatile("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...

von Axel L. (axel_5)


Lesenswert?

>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

von Stefan S. (stefanst)


Lesenswert?

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")
1
static void T1_isr (void) interrupt 3 using 3
2
{  TL1 = 0xC0;    // L-Register laden insgesamt: 0xF831
3
  TH1 = 0x07;    // H-Register laden
4
  if (LED_BLINK)
5
  {
6
    if (LED_COUNT-- == 0)  // Zähler zurücksetzen
7
    {  LED_COUNT = 50;    // Schleifenzähler neu setzen
8
      iOUTA ^= 0x10;    // LED umschalten
9
      OUTA = iOUTA;
10
    }
11
  }
12
}

von Karl H. (kbuchegg)


Lesenswert?

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?

von (prx) A. K. (prx)


Lesenswert?

Stefan S. schrieb:

> Du meinst, dass die ISR nicht zu lang ist?

Die ist völlig harmlos.

von Stefan S. (stefanst)


Lesenswert?

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?

von Peter D. (peda)


Angehängte Dateien:

Lesenswert?

Anbei mal ein 1-wire Beispiel.


Peter

von Simon K. (simon) Benutzerseite


Lesenswert?

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ß.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

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:
1
:
2
extern xdata volatile unsigned char OUTA  ; // _at_  0x7F96;
3
extern xdata unsigned char OUTB    ; // _at_  0x7F97;
4
extern xdata unsigned char OUTC    ; // _at_  0x7F98;
5
:
Irgendwie ist der OUTA anders zu handhaben als die anderen beiden 
Ports...

von Stefan S. (stefanst)


Lesenswert?

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?!?
1
bit w1_bit_io( bit b )
2
{
3
  uchar i;
4
  i = (uchar)( XTAL / 12e6 * 15 / 2 - 2 );  // 15 > t
5
  EA = 0;
6
  w1_dat = 0;          //  0
7
  w1_dat = b;          //  3
8
  while( --i );          //  3 + i * 2
9
  b = w1_dat;
10
  i = (uchar)( XTAL / 12e6 * 45 / 2 );    // 60 < t
11
  while( --i );
12
  EA = 1;
13
  w1_dat = 1;
14
  return b;
15
}

Grüße

von Stefan S. (stefanst)


Lesenswert?

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?

von Simon K. (simon) Benutzerseite


Lesenswert?

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:
>
1
> :
2
> extern xdata volatile unsigned char OUTA  ; // _at_  0x7F96;
3
> extern xdata unsigned char OUTB    ; // _at_  0x7F97;
4
> extern xdata unsigned char OUTC    ; // _at_  0x7F98;
5
> :
6
>
> Irgendwie ist der OUTA anders zu handhaben als die anderen beiden
> Ports...

Das sieht in der Tat komisch aus!

von Karl H. (kbuchegg)


Lesenswert?

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.

von Stefan S. (stefanst)


Lesenswert?

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

von Stefan S. (stefanst)


Lesenswert?

@ Peter Dannegger: Kannst du mir bitte noch den Beitrag von 13:58 
beantworten? Wäre echt nett :D

von Karl H. (kbuchegg)


Lesenswert?

> 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.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

> 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

von Stefan S. (stefanst)


Lesenswert?

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.

von (prx) A. K. (prx)


Lesenswert?

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.

von Stefan S. (stefanst)


Lesenswert?

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:
1
  do{ // 1. Takt
2
    w1_dat = 0; // 2. Takt
3
    nop(); // 3. Takt
4
  }while( --i ); // 4. Takt

?

von Karl H. (kbuchegg)


Lesenswert?

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.

von Stefan S. (stefanst)


Lesenswert?

Also müsste ich im Assemblercode nachschauen, in wie viele Zeilen 
Assemblerbefehle die Schleife aufgeteilt wurde. Das sind dann die Takte?
Also bei mir:
1
    69:         while (--dly);  
2
C:0x017D    900802   MOV      DPTR,#0x0802
3
C:0x0180    E0       MOVX     A,@DPTR
4
C:0x0181    14       DEC      A
5
C:0x0182    F0       MOVX     @DPTR,A
6
C:0x0183    60E9     JZ       C:016E
7
C:0x0185    80F6     SJMP     C:017D
8
    70: }while(0);
Wären das dann 6 Takte pro Schleifendurchlauf?

von Karl H. (kbuchegg)


Lesenswert?

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.

von Karl H. (kbuchegg)


Lesenswert?

Stefan S. schrieb:

> Also bei mir:
>
1
>     69:         while (--dly);
2
> C:0x017D    900802   MOV      DPTR,#0x0802
3
> C:0x0180    E0       MOVX     A,@DPTR
4
> C:0x0181    14       DEC      A
5
> C:0x0182    F0       MOVX     @DPTR,A
6
> C:0x0183    60E9     JZ       C:016E
7
> C:0x0185    80F6     SJMP     C:017D
8
>     70: }while(0);
9
>
> Wären das dann 6 Takte pro Schleifendurchlauf?

Und jetzt schalte mal den Optimierer des Compilers ein :-)

von (prx) A. K. (prx)


Lesenswert?

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.

von Stefan S. (stefanst)


Angehängte Dateien:

Lesenswert?

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...):
1
    69:         while (--dly);  
2
C:0x017D    900802   MOV      DPTR,#0x0802
3
C:0x0180    E0       MOVX     A,@DPTR
4
C:0x0181    14       DEC      A
5
C:0x0182    F0       MOVX     @DPTR,A
6
C:0x0183    60E9     JZ       C:016E
7
C:0x0185    80F6     SJMP     C:017D
8
    70: }while(0);

von Karl H. (kbuchegg)


Lesenswert?

Durch die MOVX wird das kompliziert :-)

von Peter D. (peda)


Lesenswert?

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

von Stefan S. (stefanst)


Lesenswert?

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 ;-)

von Karl H. (kbuchegg)


Lesenswert?

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.

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.