Frage

Bei dieser Frage geht es um die Programmierung kleiner Mikrocontroller ohne Betriebssystem.Im Moment interessiere ich mich insbesondere für PICs, aber die Frage ist allgemeiner Natur.

Ich habe mehrmals das folgende Muster zur Zeitmessung gesehen:

Timer-Interrupt-Code (sagen wir, der Timer feuert jede Sekunde):

...
if (sec_counter > 0)
  sec_counter--;
...

Hauptleitungscode (unterbrechungsfrei):

sec_counter = 500; // 500 seconds

while (sec_counter)
{
  // .. do stuff
}

Der Hauptzeilencode kann sich wiederholen, den Zähler auf verschiedene Werte (nicht nur Sekunden) setzen und so weiter.

Es scheint mir, dass es hier eine Rennbedingung gibt, wenn die Zuweisung erfolgt sec_counter im Hauptzeilencode ist nicht atomar.In PIC18 wird die Zuweisung beispielsweise in 4 ASM-Anweisungen übersetzt (jedes Byte gleichzeitig laden und davor das richtige Byte aus der Speicherbank auswählen).Wenn der Interrupt-Code dazwischen kommt, ist der Endwert möglicherweise beschädigt.

Kurioserweise gilt: Wenn der zugewiesene Wert kleiner als 256 ist, erfolgt die Zuweisung Ist atomar, also gibt es kein Problem.

Liege ich mit diesem Problem richtig?Welche Muster nutzen Sie, um ein solches Verhalten richtig umzusetzen?Ich sehe mehrere Optionen:

  • Deaktivieren Sie Interrupts vor jeder Zuweisung an sec_counter und aktivieren Sie sie danach – das ist nicht schön
  • Verwenden Sie keinen Interrupt, sondern einen separaten Timer, der gestartet und dann abgefragt wird.Das ist sauber, verbraucht aber einen ganzen Timer (im vorherigen Fall kann der 1-Sekunden-Zündtimer auch für andere Zwecke verwendet werden).

Irgendwelche anderen Ideen?

War es hilfreich?

Lösung

Die PIC-Architektur ist als Atom wie es geht. Es stellt sicher, dass alle Read-Modify-Write-Operationen auf eine Speicherdatei sind ‚Atom‘. Obwohl es dauert 4-Takte den gesamten RMW-Befehl, alle 4-Takte in einem einzigen Befehl und der nächste Befehl verwendet den Zyklus nächsten 4-Takt verbraucht werden auszuführen. Es ist die Art und Weise, dass die Pipeline funktioniert. In 8-Uhren sind zwei Befehle in der Pipeline.

Wenn der Wert größer als 8-Bit ist, wird es ein Problem, da der PIC ist eine 8-Bit-Maschine und größere Operanden werden in mehr Befehlen behandelt. Das wird Atomfrage einzuführen.

Andere Tipps

Schreiben Sie den Wert dann prüfen, ob es der Wert ist erforderlich scheint die einfachste Alternative zu sein.

do {
 sec_counter = value;
} while (sec_counter != value);

BTW sollten Sie die Variable volatile bei Verwendung von C machen.

Wenn Sie den Wert lesen müssen, dann können Sie es zweimal lesen.

do {
    value = sec_counter;
} while (value != sec_counter);

Sie müssen auf jeden Fall die Unterbrechung deaktivieren, bevor der Zähler zu setzen. Hässlich, wie es sein kann, ist es notwendig. Es ist eine gute Praxis, immer den Interrupt zu deaktivieren, bevor Hardwareregister oder Software-Variablen beeinflussen die ISR-Methode konfigurieren. Wenn Sie in C schreiben, sollten Sie alle Vorgänge als nicht-atomare betrachten. Wenn Sie feststellen, dass Sie an dem generierten Assembly zu oft suchen haben, dann kann es besser sein C und Programm in der Montage zu verzichten. Meiner Erfahrung nach ist dies selten der Fall.

In Bezug auf die Frage diskutiert, das ist, was ich vorschlagen:

ISR:
if (countDownFlag)
{
   sec_counter--;
}

und Einstellung der Zähler:

// make sure the countdown isn't running
sec_counter = 500;
countDownFlag = true;

...
// Countdown finished
countDownFlag = false;

Sie müssen eine zusätzliche Variable und sind besser, alles in einer Funktion zu wickeln:

void startCountDown(int startValue)
{
    sec_counter = 500;
    countDownFlag = true;
}

So kann man abstrakt die Startmethode (und Hässlichkeit verstecken, wenn erforderlich). Zum Beispiel können Sie es leicht einen Hardware-Timer ändern zu starten, ohne die Anrufer des Verfahrens zu beeinflussen.

Da Zugriffe auf die Variable „sec_counter“ nicht atomar sind, führt kein Weg daran vorbei, Interrupts vor dem Zugriff auf diese Variable in Ihrem Hauptzeilencode zu deaktivieren und den Interrupt-Status nach dem Zugriff wiederherzustellen, wenn Sie deterministisches Verhalten wünschen.Dies wäre wahrscheinlich eine bessere Wahl, als einen HW-Timer für diese Aufgabe zu reservieren (es sei denn, Sie haben einen Überschuss an Timern; in diesem Fall können Sie genauso gut einen verwenden).

Wenn Sie Microchip frei TCP Download / IP Stack gibt es Routinen dort in dem einen Timer-Überlauf verwendet Spur der verstrichenen Zeit zu halten. Insbesondere "tick.c" und "tick.h". Kopieren Sie einfach die Dateien über zu einem Projekt.

Innerhalb dieser Dateien können Sie sehen, wie sie es tun.

Es ist nicht so neugierig auf die weniger als 256 bewegt zu seinem Atom - einen 8-Bit-Wert bewegt, ist ein Opcode, so dass als Atom ist, wie Sie bekommen.

Die beste Lösung auf einem solchen Mikrocontroller als PIC Interrupts zu deaktivieren, bevor Sie den Timer-Wert ändern. Sie können sogar den Wert des Interrupt-Flag überprüfen, wenn Sie die Variable in der Hauptschleife zu ändern und damit umgehen, wenn Sie wollen. Machen Sie es eine Funktion, die den Wert der Variablen ändert und man könnte es auch als auch von der ISR nennen.

Nun, was macht den Vergleich Assembler-Code aussehen?

Taken zu berücksichtigen, dass es nach unten zählt, und dass es nur eine Null vergleichen, sollte es sicher sein, wenn sie zuerst das MSB prüft, dann ist das LSB. Es Korruption sein könnte, aber es ist nicht wirklich wichtig, wenn es in der Mitte zwischen 0x100 kommt und 0xff und der beschädigten Vergleichswert ist 0x1FF.

Wie Sie Ihre Timer jetzt verwenden, wird es nicht ganz Sekunden sowieso zählen, weil Sie es in der Mitte eines Zyklus ändern könnte. Also, wenn Sie kümmern sich nicht um sie. Der beste Weg, meiner Meinung nach, wäre es, den Wert zu lesen, und dann nur die Differenz vergleichen. Es dauert ein paar OPs mehr, hat aber keine Multi-Threading Probleme. (Da der Timer hat Vorrang)

Wenn Sie mehr streng über den Zeitwert sind, würde ich automatisch den Timer deaktivieren, wenn es zählt bis auf 0, und deaktivieren Sie den internen Zähler des Timers und aktivieren, wenn Sie es brauchen.

Bewegen Sie den Codeabschnitt, der auf der main () auf eine einwandfreie Funktion wäre, und hat es bedingt durch die ISR genannt.

Auch, um zu verhindern, jede Art von Verzögerung oder Zecken fehlen, wählen diese ISR-Timer ein High-Prio-Interrupt (die PIC18 hat zwei Ebenen) sein.

Ein Ansatz ist ein Interrupt hält einen Byte-Variable zu haben, und etwas anderes, das mindestens einmal all 256-mal die Zähler getroffen aufgerufen wird; so etwas wie:

// ub==unsigned char; ui==unsigned int; ul==unsigned long
ub now_ctr; // This one is hit by the interrupt
ub prev_ctr;
ul big_ctr;

void poll_counter(void)
{
  ub delta_ctr;

  delta_ctr = (ub)(now_ctr-prev_ctr);
  big_ctr += delta_ctr;
  prev_ctr += delta_ctr;
}

Eine leichte Variation, wenn Sie nichts dagegen haben die Unterbrechung des Zählers zwingt synchron mit dem LSB Ihres großen Zähler zu bleiben:

ul big_ctr;
void poll_counter(void)
{
  big_ctr += (ub)(now_ctr - big_ctr);
}

adressiert niemand die Frage der Lesen multibyte Hardware-Register (zum Beispiel einen Timer. Der Timer könnte umkippen und erhöht seine zweite Byte während Sie sie lesen.

sagen, es ist 0x0001ffff und Sie es lesen. Sie könnten erhalten 0x0010ffff oder 0x00010000.

Die 16-Bit-Peripherieregister sind flüchtig , um Ihren Code.

Für alle volatile "Variablen", verwende ich die doppelte Lesetechnik.

do {
       t = timer;
 } while (t != timer);
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top