So implementieren Sie eine präzise Verzögerungsfunktion in Keil c51 (für c8051), die auf eine genaue Anzahl von CPU-Takten wartet, die Anzahl der Takte reicht von 1 bis 255

Wie der Titel schon sagt, brauche ich genaue Verzögerungen, nicht zu lang, idealerweise im Bereich von 0 - 350 CPU-Takten, aber wenn irgendetwas im engeren Bereich funktionieren würde, liegt der absolute Mindestbereich bei 20 - 127 CPU-Takten. Diese liegen also unter oder knapp über einzelnen Mikrosekundenverzögerungen (50 MHz CPU-Takt), relativ kurze mehrere Takte bis mehrere zehn Takte. Das Problem beim Abfragen eines Timers besteht darin, dass die Genauigkeit je nach Implementierung zu einem Schritt von maximal 7 Takten führt, zum Beispiel:

  1. while(!TF0) {} While, not und bit-Operator benötigen zusammen 7 Takte. Wenn ich also zwischen 15 und 21 Uhr etwas calle, ergibt sich die flache Verzögerung von 21 Uhr ...
  2. Verwenden von Interrupt on Timer und CPU-Stoppmodus - Liefert gute Ergebnisse für über 50 Takte, hängt wahrscheinlich vom aktuellen CPU-Zustand ab, geht also aufgrund der Interrupt- und Aufwachlatenz manchmal weit über 50 Takte hinaus in den 100-Takt-Bereich, aber alles darunter wieder Flat 50 (oder 100) CPU-Takte.
  3. Die Verwendung von switch-case mit beispielsweise 30 Einträgen für 30 Verzögerungen mit 1-Takt-Inkrement und einer unterschiedlichen Anzahl von NOPEs als Verzögerung führt zu einer Compiler-Optimierung, die es in Bezug auf das Timing unvorhersehbar und wieder wesentlich zu lang macht, über 100 Takte. Dies macht den Ansatz unbrauchbar.
  4. Ich plane, eine Zeigertabelle auf Funktionen mit unterschiedlicher Anzahl von NOPs auszuprobieren. Aber bevor ich es versuche, sehe ich bereits zwei Probleme bei diesem Ansatz: a. es wird viel Speicher benötigen und ich habe noch 1k übrig; B. Die Latenz einer void-Funktion (void) in und out beträgt ungefähr 18 Takte, daher ist es sehr, sehr eng, um das absolute Minimum von 20 Takten zu erreichen, das ich brauche ...

Wie geht man diese Art von Problem an? Irgendwelche Ideen sind mehr als willkommen?

Übrigens führe ich es auf einem C8051F38x-Mikrocontroller von Silicon Labs aus und verwende C51 und Keil zum Codieren und Kompilieren, wenn das wichtig ist.

Der Code, der als Teillösung entstanden ist, scheint dem gleichen Timing zu folgen, während die Schleife in C dies tut, und die "djnz" -Anweisung benötigt 5-6 CPU-Zyklen anstelle der im Datenblatt angegebenen 2/4.

ACC_save=   ACC;
ACC     =   counter102;
P0b3    =   1;  //             Start the Pulse
#pragma ASM     //             Precice DELAY using assembler
clr C           //  ; 1       Clear Carry
rrc A           //  ; 1       C = 1 if odd
jnc even        //  ; 2 or 4  extra 2 cycles if branch taken (spoils cache)
nop             //  ; 1
nop             //  ; 1
clr   C         //  ; 1
even:
subb  A,#4      //  ; 1
mov   R7,A      //  ; 1
loop:
djnz  R7, loop  //  ; supposed to be 2, but practically takes 5 to 6 cycles!
#pragma ENDASM`
P0b3    =   0;  //             Stop the Pulse

BEARBEITEN

Vielen Dank an alle für den tollen Input, ich hätte mir nicht vorstellen können, dass der Ideenfluss so positiv und vor allem produktiv sein könnte. Daher meine tiefe Wertschätzung an alle, die dazu beigetragen haben und in Zukunft beitragen werden. Nach euren wertvollen Beiträgen und großartigen Ideen habe ich mir also etwas einfallen lassen, das bis zu einem gewissen Grad für mich funktioniert. Der Code ist unten:

void    delay(unsigned char delay_time) {
switch (delay_time)
{case     8:    goto     Q08;
case      9:    goto     Q09;
case     10:    goto     Q10;
case     11:    goto     Q11;
case     12:    goto     Q12;
case     13:    goto     Q13;
case     14:    goto     Q14;
case     15:    goto     Q15;
case     16:    goto     Q16;
case     17:    goto     Q17;
case     18:    goto     Q18;
case     19:    goto     Q19;
case     20:    goto     Q20;
default      :  goto     Q00;   }

Q19:    PORT_ACTIVE(1); //  2clk
Q17:    PORT_ACTIVE(1); //  2clk
Q15:    PORT_ACTIVE(1); //  2clk
Q13:    PORT_ACTIVE(1); //  2clk
Q11:    PORT_ACTIVE(1); //  2clk
Q09:    PORT_ACTIVE(1); //  2clk
    _nop_();            //  1clk
goto EXIT1;             //  Skip the Even delay part

Q20:    PORT_ACTIVE(1); //  2clk
Q18:    PORT_ACTIVE(1); //  2clk
Q16:    PORT_ACTIVE(1); //  2clk
Q14:    PORT_ACTIVE(1); //  2clk
Q12:    PORT_ACTIVE(1); //  2clk
Q10:    PORT_ACTIVE(1); //  2clk
Q08:    PORT_ACTIVE(1); //  2clk
Q00:                    //  0clk
EXIT1:
return;                 //  Exit from the function takes 7 clocks
}   //  END of function delay

// Continued execution after the delay function
PORT_ACTIVE(0);     //  2clk

PORT_ACTIVE(x) ist also eine #define-Funktion, die den pulsierenden Port aktiviert. Da ich alle Zeit habe, die ich brauche, bevor ich mit dem Impuls beginne, konnte ich den größten Teil des Overheads im Zusammenhang mit Entscheidungen vor der eigentlichen Aktivierung des Ports hineinquetschen. Dann dauert der Rücksprungbefehl immer gleich lange, sodass ich jetzt einen Impuls mit einer Breite von mindestens 8 Taktzyklen und bis zu 20 Zyklen erzeugen kann. Ich erweitere sie jetzt auf bis zu 100 Takte, natürlich auf Kosten des verfügbaren Speicherplatzes. Und so ist diese Lösung tatsächlich dank der Idee von JimmyB, die Impulsaktivierung in die Funktion fallen zu lassen und nicht davor, und natürlich dank der großartigen Ideen von TCROSLEY, wie man die ungeraden und geraden Verzögerungen handhabt, es ist gerecht dass der Wechsel zur Assemblierung für die Debugging-Erfahrung nicht wirklich freundlich ist,

Eine weitere Anmerkung ist, dass ich, sobald ich mit dem Feiern einer funktionierenden Lösung fertig war, auf das nächste Problem stoße.

ZWEITES PROBLEM

Ich muss einen zweiten Impuls Rücken an Rücken zum ersten mit unabhängiger Breite ausführen. Also kein Overhead für den zweiten Impuls, da er sonst mit unterschiedlicher Breite endet. Es bringt mich ziemlich genau an die Stelle zurück, an der ich vorher war, da der zweite Impuls wieder auf den 6-Zyklus-Engpass der While-Schleife beschränkt ist, es sei denn, es gibt eine Möglichkeit, den Verzweigungs-Overhead für den zweiten Impuls vor den ersten Impuls zu legen. Irgendwelche Ideen dazu?

Werden sich diese Verzögerungszeiten während der Laufzeit dynamisch ändern (dh variabler Parameter für die Verzögerungsfunktion) oder können sie zur Kompilierzeit berechnet werden (konstanter Parameter für die Verzögerungsfunktion)? Wie viele verschiedene Aufrufe der Verzögerungsfunktion stellen Sie sich vor?
Treten Sie einen Schritt zurück und beschreiben Sie das Problem, das Sie tatsächlich lösen. Wenn Sie so präzise Verzögerungen benötigen, ist eine Subroutine wahrscheinlich NICHT der richtige Weg. Wenn Sie die Zeiten zwischen zwei externen Ereignissen genau steuern müssen, müssen Sie den GESAMTEN Code zwischen den eigentlichen Anweisungen berücksichtigen, die diesen Ereignissen zugeordnet sind, einschließlich des Aufrufs und der Rückkehr von Ihrer Verzögerungsroutine zusammen mit jeder anderen Logik in der aufrufenden Funktion.
--- Also, um dem Problem ein wenig nachzugehen.
--Der Code kommt, um einen präzisen Impuls einer bestimmten Breite auszuführen. Vor der eigentlichen Ausführung gibt es ein Abstimmungsverfahren, bei dem die MCU die Breite des Impulses basierend auf einer Reaktion des Systems anpasst. Also im Allgemeinen geht es so: MCU empfängt Impulsparameter START: Berechnung der Verzögerungen + Inits PORT = 1 PRECISE DELAY PORT = 0 Ergebnisanalyse Zurück zu START, wenn die Ergebnisse nicht zufriedenstellend sind, oder an einer anderen Stelle in der Programmausführung fortfahren … So die Verzögerung ist dynamisch. Die Anzahl der zu verzögernden Anrufe kann nur 1 betragen oder Tausende erreichen, bevor der Algorithmus alle Parameter erfüllt.
Um eine solche dynamische Verzögerung zu erreichen, müssen Sie eine Verzögerungs-Subroutine mit minimaler Verzögerung erstellen und sie dann so oft aufrufen, wie es das Programm erfordert. Hier müssen Sie die Aufruf-Overhead-Verzögerung, Schleifenverzögerung usw. berücksichtigen. Ich würde sagen, dass Sie nach Programmen suchen, um Verzögerungen beim Assemblieren zu erzeugen, und dann den Code gemäß Ihren Anforderungen schreiben.
Sie brauchen also etwas, das beispielsweise 1 ausgibt, 1 clk wartet und 0 ausgibt? Dann etwas mit 2 Klicks Wartezeit und so weiter?
Wenn ja, könnten Sie versuchen, ein paar verschiedene Funktionen zu implementieren, die unterschiedliche Impulsbreiten von 1, 2, 3, ... Takten erzeugen, bis zu einer Anzahl von Zyklen (20? 50?), die dynamisch von einer Schleife gehandhabt werden können. möglicherweise eine Art NOP-Schieber vorangestellt, um eine präzise Steuerung zu ermöglichen.
Hinweis: Ich habe meiner Antwort zusätzlichen Code hinzugefügt, um die 6-Zyklus-Sprünge zu handhaben.
JimmyB, danke für deinen Input, sehr interessanter Ansatz, den ich ausprobiert habe, und das ist die Lösung, die ich obendrein gefunden habe. Aber es gibt ein zweites Problem, das ich sofort danach getroffen habe. Jede Eingabe dazu. Danke.

Antworten (3)

Wie andere bereits erwähnt haben, geschieht dies am besten in der Montage. Hier ist mein ursprünglicher Versuch, dies zu codieren, als ich dachte, die Sprunganweisungen dauerten entweder 2 oder 4 Zyklen (siehe Bearbeiten unten für die überarbeitete Version).

void delay_sub(unsigned char i)
{
// convert 20, 21, 22 etc to count in R7 of 1, 2, 3 (extra cycle added if i is odd)
                    ; cycles
    rrc A           ; 1            c = 1 if odd
    jnc even        ; 2 or 4       extra 2 cycles if branch taken (spoils cache)
    nop             ; 1            delete if using lcall's instead of acall's
    nop             ; 1            same
    clc             ; 1            in either case carry is clear prior to subb
even:
    subb A,#9       ; 1

    mov R7,A        ; 1            R7 now = (i / 2) - 9
    //while (i--);
loop:
    djnz R7, loop   ; 2     loop address should be in cache, so no extra cycles needed
    ret             ; 6
}

timing calculation (assuming acall's)
if i even:
    5+7+R7*2+6 = minimum of 20 22 24 ... => R7 = 1, 2, 3 ...
if i odd:
    5+8+R7*2+6 = minimum of 21 23 25 ... => R7 = 1, 2, 3 ...

Es wird davon ausgegangen, dass ein Aufruf wie ACALL(nn) erfolgt, wobei nn eine Konstante oder eine Variable in einer Byte-Variablen ist, sodass der Parameter beispielsweise unter Verwendung eines MOV A,#n-Befehls mit einem Zyklus übergeben werden kann. Das Mindesttiming, das Sie tun können, beträgt 20 Takte, wie Sie es gefordert haben.

mov  A,#n        ; 1   
acall delay_sub  ; 4

Es wird nicht geprüft, ob der Parameter größer oder gleich 20 ist, alle Werte kleiner als 20 ergeben ein falsches Timing.

Der mov-Befehl und ein Aufruf dauern 5 Zyklen. Zunächst wird der Zählwert (i) durch zwei geteilt, um zu berücksichtigen, dass der DJNZ-Befehl zwei Zyklen benötigt. Dann wird die Zählung angepasst, um einen Zyklus hinzuzufügen, wenn i ungerade ist. Schließlich wird ein fester Wert subtrahiert, sodass der Wert im zu dekrementierenden Register (R7) im Bereich 1, 2, 3 liegt ... R7 wird dann in einer engen Schleife (zwei Zyklen pro Zählung) dekrementiert. Für die Rückkehr gibt es eine feste Zyklenzahl von 6.

Wenn Sie einen LCALL anstelle eines ACALL verwenden müssen, beträgt das minimale Timing, das Sie ausführen können, 21 Takte anstelle von 20, und Sie müssen die beiden Nops nach der jnc-Anweisung löschen. Sie müssen entweder alle ACALLs oder LCALLs verwenden, Sie können sie nicht mischen.

Ich würde vermeiden, C zum Aufrufen der Funktion zu verwenden, es sei denn, Sie können garantieren, dass der Compiler keinen zusätzlichen Overhead hinzufügt. Außerdem verwende ich R7 als Scratch-Register; In Ihrem Compiler-Handbuch erfahren Sie, welche Register in einer Assembler-Funktion verwendet werden können, ohne sie speichern zu müssen (falls vorhanden).

Dies berücksichtigt auch nicht das Deaktivieren und erneute Aktivieren von Interrupts, falls erforderlich, um sicherzustellen, dass die Zeitgebungsroutine nicht unterbrochen wird.

Das Verhalten der Sprungbefehle basiert auf dem Datenblatt für den C8051F38x, wie ich es verstehe (in Bezug darauf, wann der Befehlscache beschädigt ist oder nicht). Dies kann bei anderen Versionen des 8051 anders sein.

Schließlich habe ich nicht die Syntax für den Sprung in die Inline-Assemblierung und wieder heraus gezeigt. Das Unterprogramm könnte auch in eine separate Datei gestellt und zusammengesetzt werden.

Bearbeiten

Seit ich den ursprünglichen Code geschrieben habe, hat mir das OP mitgeteilt, dass die Anzahl der Taktzyklen für einen Sprung in seinem 8051 5 oder 6 beträgt, nicht die 2 oder 4, die in dem von mir gelesenen Datenblatt angegeben sind. Also habe ich die Routine umgeschrieben, um dies zu berücksichtigen. Leider erhöht dies die minimale Zyklenzahl, die getimt werden kann, auf 32 statt 20. Wenn also Zählungen zwischen 20 und 31 unbedingt verarbeitet werden müssen, muss ein spezieller Code speziell für diesen Fall geschrieben werden (siehe unten).

void delay_sub(unsigned char i)
{
// minimum value of i is 32 
                    ; cycles
    clr C           ; 1
    subb A,#32      ; 1   adjust for overhead of call and this routine
                    ; a branch could be added here in case the result is negative
    mov B,#6        ; 1
    div AB          ; 4            quotient in A, remainder in B
    mov DPTR,#adjustcycles   ; 1
    mov R7,B        ; 1
    mov B,A         ; 1   save quotient in B as temp
    mov A,#6        ; 1
    clr C           ; 1
    subb A,R7       ; 1   A now has 5 - B (remainder)
    mov R7,#0       ; 1
    jmp @A+DPTR     ; 6   jump into table to add clocks based on remainder

adjustcycles:       ; execute additional cycles based on remainder
    inc R7          ; 1   for remainder of 5
    inc R7          ; 1   for remainder of 4 
    inc R7          ; 1   for remainder of 3 
    inc R7          ; 1   for remainder of 2
    inc R7          ; 1   for remainder of 1 
    nop             ; 1   for remainder of 0

    mov A,B         ; 1   now has i / 6, have already adjusted for remainder
loop:
    djnz loop       ; 6
    ret             ; 6

timing in clock cycles is: 5 (call) + 21 (fixed overhead) + 6*(i/6) + (i%6) + 6 (ret)

if i = 0, 5 + 21 + 6 = 32 therefore that is the minimum count

Anstatt den Parameter i wie im vorherigen Beispiel durch 2 zu teilen, muss ich ihn jetzt durch 6 teilen, da ich davon ausgehe, dass der DJNZ-Befehl 6 Zyklen dauert. Also müssen wir i / 6 Mal durchlaufen und auch 0 bis 5 Zyklen für den Rest hinzufügen (i % 6).

Der Rest meiner obigen Kommentare trifft ziemlich gut auf dieses Beispiel zu. Ich belasse den ursprünglichen Code, falls jemand tatsächlich einen 8051 mit einer DJNZ-Anweisung mit zwei Zyklen hat.

Für Zählwerte von 20 bis 31 könnten Sie eine Subroutine mit nur einem Nop erstellen, die 12 Zyklen einschließlich Aufruf und Rückgabe benötigt:

void delay12(void)
{
    nop
}

Für 20-23 Zählungen würden Sie es einmal aufrufen und nach dem Aufruf 8 bis 11 Nops hinzufügen (oder einen Dummy-Sprung zur nächsten Anweisung, der 6 Zyklen plus 2 bis 5 Nops verbrauchen würde - das Verzögern von 20 Zyklen würde also nur vier kosten Anweisungen plus das Unterprogramm, von dem angenommen wird, dass es mehr als einmal verwendet wird.). Für Zählungen von 24 bis 31 würden Sie delay12 zweimal aufrufen und je nach Bedarf 0 bis 5 Nops und/oder eine Sprunganweisung hinzufügen.

Um also 20 Zyklen zu verzögern:

    acall delayl12
    jump next
next:
    nop
    nop
Vielen Dank für die ausführliche Antwort. Ich habe Ihren Code genommen und konnte ihn mit geringfügigen Änderungen mit ASM-Pragmas zwischen den Portbefehlen inline ausführen. Ich konnte es nicht als Funktion ausführen, der Linker war verrückt, weil er nicht in der Lage war, all das Zeug zu verknüpfen. Zwei Hauptprobleme bei der Inline-Ausführung. 1. Nach dem Umschalten auf Assembly ist das Debuggen in Assembly, einfach unmöglich für meinen 15k-Code, der debuggt werden kann. 2. Alle Sprungbefehle nehmen 6 !!!!! ja 6 Zyklen statt versprochen 2. Ich habe das Datenblatt noch einmal überprüft, es liest 2/4 Zyklen, aber nein, es dauert 6 !!! ??? Wie ist das???
Selbst der DJNZ im engen Loop braucht sechs Zyklen? Das steht eindeutig nicht im Datenblatt. Führen Sie es tatsächlich in echter Hardware oder einem Simulator aus? Die einzige Möglichkeit, dies wirklich zu testen, ist auf echter Hardware.
Ja, die DJNZ in der nächsten Schleife braucht 5 mal 6 Zyklen, das macht mich wahnsinnig. Hier ist der Code, den ich verwende: 'ACC_save = ACC; ACC = Zähler102; P0b3 = 1; #pragma ASM // ; Zyklen clr C // ; 1 Clear Carry rrc A // ; 1 C = 1 wenn ungerade jnc gerade // ; 2 oder 4 zusätzliche 2 Zyklen, wenn Verzweigung genommen wird (verdirbt Cache) nop // ; 1 nop // ; 1 clr C // ; 1 subb A,#4 // ; 1 Bewegung R7,A // ; 1 Schleife: djnz R7, Schleife // ; 2 #pragma ENDASM'
@cezar Du brauchst den clc nicht vor dem rrc. Der rrc wird darüber schreiben. Sie haben subb A, Nr. 4, sollte Nr. 9 sein. Sie haben nicht gesagt, ob Sie mit einem Simulator testen. Ich vermute, Sie sind es. Ich vertraue ihnen nicht. Sie müssen dies wirklich als Funktion haben. Versuchen Sie, den ASM-Code in eine separate Datei einzufügen, und verwenden Sie einen Assembler, wie ich in meiner Antwort vorgeschlagen habe. Setzen Sie dann 100 Aufrufe der Funktion hintereinander und fügen Sie diese in eine Schleife ein, die 100000 Mal ausgeführt wird. (Der Grund für die 100 Aufrufe besteht darin, den Overhead auf ~ 1% zu reduzieren.) Führen Sie dies auf echter Hardware aus. Sollte bei 50 MHz sehr nahe an 4 Sekunden dauern.
Hey Tcrosley, danke für deine schnelle Antwort. Ich teste auf echter Hardware mit einem anständigen Oszilloskop (Tektronix MSO 5104 - 10GS/s) an meiner Seite. Was ich also im Debugger sehe, habe ich eine Timing-Bestätigung auf dem Oszilloskop mit einer Genauigkeit von bis zu 1 ns. Ohne CLR C überschreibt der RRC den Übertrag aus irgendeinem Grund nicht, und ich bekomme riesige Zahlen in A. Ich habe das Timing neu berechnet, da der Code inline und nicht als Funktion ist, und "subb A, # 4", also nein, es gibt keine RET 6-Zyklen, und die Variable wird an die Funktion übergeben ...
@cezar Gut zu hören, dass Sie sich auf echter Hardware befinden. Ich schalte den Portbit um und messe viel mit einem Zielfernrohr. Der rrc sollte den Übertrag für ungerade Zahlen setzen und ihn für gerade Zahlen löschen. Hmm. Verstehen Sie jetzt, wie Sie das subb ändern. Aber in Ihrem endgültigen Programm sollte dies wirklich eine Funktion und nicht inline sein. Viel Glück, ich gehe zur Arbeit.
Haben Sie eine Idee, Tcrosley, warum es 5-6 Zyklen sein könnten und nicht die sehr gewünschten 2 Zyklen? djnz R7, loopIch habe versucht, das durch zwei Befehle zu ersetzen : dec A; und dann JNZ loop, aber das gleiche Ergebnis ... ich mache etwas falsch und ich bin nicht gut genug im Zusammenbauen , um herauszufinden , was es ist ...
@Cezar Um das Schema zum Zählen wirklich zu testen, tun Sie Folgendes: Schalten Sie das Portbit ein, mov R7, 100; Schleife: nop; nein; DJNZ-Schleife; Setzen Sie das in eine Endlosschleife und messen Sie die Zeit vom Einschalten des Ports bis zum Ausschalten. Wenn wirklich 6 Zyklen pro Sprung sind, sollten 8*100*.02 µ = 16 µS sein.
Erstmal danke für die tollen Ideen, die sind echt klasse. Ich habe die Assembly-Implementierung aufgegeben, ich gewinne nichts (die gleichen 6 Takte, die ich aus der while-Schleife bekommen kann), aber ich muss die Fähigkeit zum bequemen Debuggen aufgeben, und ich habe 15k kompilierten Code zum Debuggen. Nach der Lösung, die ich oben gepostet habe, stoße ich auf ein Problem mit einem zweiten Impuls ... und ich werde entweder die Präzision des zweiten Impulses aufgeben, oder vielleicht werden die Jungs mit Ihrer Hilfe hier einen Weg finden können der Entscheidungsaufwand vor dem ersten Impuls. Danke noch einmal.

Sie könnten (ich glaube, ich erinnere mich nicht so gut an die 8051-Architektur) einen kalkulierten Sprung in ein 'Meer' von Nops machen. Kombinieren Sie es vielleicht mit einer Schleife, um die erforderliche Anzahl von Nops zu reduzieren (oder es gibt möglicherweise einen ausgefeilteren Weg, dies zu tun..)

Ich habe das einmal in der früheren, aber verwandten MCS-48-Architektur gemacht, um mit variabler Latenz oder ähnlichem umzugehen.

Sie sollten in der Montage für Einzelzykluspräzision arbeiten . Keil unterstützt einige Methoden zur Verwendung von Assembly in Kombination mit C, und wahrscheinlich würde die einfachere Inline-Assembly für Sie funktionieren.

Was auch immer Sie tun, es wird etwas Overhead geben, also ist das Beste, was Sie tun können, eine Verzögerung von n + 1 bis n + 255 Zyklen. n <= 20 Zyklen sollten machbar sein.

Vielen Dank für Ihre Eingabe. Ich werde versuchen, einen Assembler-Code für zeitkritische Teile einzufügen. Ich habe bisher versucht, das zu vermeiden, aber es sieht so aus, als würde C hier nicht gehen.

Ja, wie Spehro betonte, dass Sie in der Montage arbeiten sollten, und Sie können mit der Montage eine Genauigkeit von bis zu einem Maschinenzyklus erreichen, aber keinen Taktzyklus.

Zwei Möglichkeiten mit Assemblersprache

  1. Verwenden Sie nop-Befehle und -Schleifen so, dass die Ausführungszeit von Befehlen und nops Ihrer erforderlichen Zeitverzögerung entspricht. Auf diese Weise müssen Sie wissen, wie viele Maschinenzyklen jeder Befehl in Ihrem Code benötigt.

  2. Verwenden Sie Timer.

Die Compiler werden Ihren C-Code modifizieren, um den Code zu optimieren, was sicherlich Ihre Verzögerung ändern würde, also müssen wir aus Gründen der Präzision in Richtung Assembler gehen.

Da ich vor einem Jahr die Assemblersprache 8051 studiert habe. Es hat nicht viele Befehle und wenn Sie studieren, werden Sie sicherlich viel über Mikrocontroller und ihre grundlegende Architektur lernen. Obwohl Sie die Montage eines Mikrocontrollers nicht lernen müssen, wäre es sicherlich hilfreich zu wissen, wie die Assemblersprache für mindestens einen Mikrocontroller funktioniert.

Danke für deinen Kommentar. Ich habe tatsächlich beides in C ausprobiert, also werde ich es wahrscheinlich in Assembler versuchen. Ich sehe ziemlich genau den Weg, es mit NOPs zu implementieren, aber ich bin mir nicht so sicher, wie ich es mit Timern in Assembly machen soll, es ist immer noch eine bedingte Entscheidung erforderlich, die selbst viele Zyklen benötigt.
circuitstoday.com/delay-using-8051-timer Dies hat einen hübsch aussehenden Code, um eine Verzögerung von 1 ms zu erzeugen (Sie können die Werte in den Timer-Registern in Ihrem Code ändern, um Verzögerungen gemäß Ihren Anforderungen zu erzeugen). Obwohl der Autor die Verzögerung von Befehlen in der Verzögerungsunterroutine in Assembler nicht berücksichtigt hat, können Sie dies auch als 100% genau betrachten.
Hey Jasser, vielen Dank für die Nachbereitung. Ich habe den Timer-Fall ausprobiert, der in Bezug auf das Hinzufügen von Assemblercode viel einfacher ist, aber leider erhalte ich aus irgendeinem Grund einige seltsame Ergebnisse, die "JNB TF2H, $" -Anweisung, die ich für die Timer-Überlaufabfrage verwende, dauert 6-7 Zyklen auszuführen, während es laut Datenblatt 3/5 sein sollen. Kann keinen Grund dafür finden, dass es so lange dauert. Kann es sein, dass es mit der Position des Codes im externen Speicher zusammenhängt? Irgendwelche Ideen?
Das einzige, was zählt, sind die Anweisungen und die Zeit, die für die Ausführung benötigt wird, und nicht, wo sich der Code befindet (Die Verzögerungen der Anweisungen berücksichtigen die externe Residenz des Codes, falls vorhanden). Ich würde Ihnen empfehlen, Hardware auszuprobieren, um die Anzahl der Verzögerungen zu überprüfen, die Sie erhalten. So etwas ist mir bisher bei keil noch nicht begegnet, aber vielleicht ... hat es etwas mit der Software zu tun ... Versuchen Sie, die Verzögerungen auf einem Oszilloskop zu überprüfen!
Hey Jasser, danke für eine schnelle Antwort. Also sitze ich neben dem Mikrocontroller und einem Oszilloskop, und was ich in Uhren auf dem Debugger sehe, ist das, was ich auf dem Oszilloskop bekomme. Kann mir nicht erklären, wie das möglich ist. Es ist fast doppelt so lange wie in den Datenblättern der Siliziumlabore angegeben. Irgendwas mache ich hier definitiv falsch und kann mir nicht erklären was.
1. Wenn Sie ein Hardware-Kit haben, debuggen Sie den Code Schritt für Schritt und sehen Sie sich die Änderungen in den Registern an. 2. Schreiben Sie einen einfachen Code in Assembler, der denselben djnz-Befehl verwendet, und finden Sie heraus, ob er sich genauso verhält.
Hey Jasser, vielen Dank für deine Antwort. Ich habe es mit einem Debugging-Kit gemacht und konnte einen Blick in die Register werfen, und so macht die MCU mit einem einfachen Code jedes Mal genau den gleichen Trick mit der djnz-Anweisung. Nochmals vielen Dank für Ihren Beitrag.
Hast du auch versucht die MCU zu tauschen? Und was ist übrigens dein Projekt? Oder machst du es nur, um solche Verzögerungen zu produzieren?
Ich werde es mit einer anderen MCU-Einheit versuchen, ich meine das gleiche Modell, aber nur eine andere physische Einheit, danke für den Hinweis. Das Projekt besteht darin, zwei Impulse mit präziser, aber unabhängiger Breite zu erzeugen, die basierend auf den Parametern geändert werden, die über einen Kommunikationskanal an die MCU weitergegeben werden. Dann sammelt die MCU die Daten der Systemantwort, die diese Impulse aufgerufen haben, und sendet sie über den Kommunikationskanal zurück oder speichert lokal eine Beschreibung des später zu verwendenden Systems.