Zykluszählung mit modernen CPUs (z. B. ARM)

In vielen Anwendungen kann eine CPU, deren Befehlsausführung eine bekannte zeitliche Beziehung zu erwarteten Eingangsstimuli hat, Aufgaben bewältigen, die eine viel schnellere CPU erfordern würden, wenn die Beziehung unbekannt wäre. In einem Projekt, das ich mit einem PSOC zum Generieren von Videos durchgeführt habe, habe ich beispielsweise Code verwendet, um alle 16 CPU-Takte ein Byte Videodaten auszugeben. Da das Testen, ob das SPI-Gerät bereit ist, und das Verzweigen, wenn nicht, IIRC 13 Takte dauern würde und ein Laden und Speichern zur Ausgabe von Daten 11 dauern würde, gab es keine Möglichkeit, das Gerät auf Bereitschaft zwischen Bytes zu testen; Stattdessen habe ich einfach so arrangiert, dass der Prozessor für jedes Byte nach dem ersten genau Code im Wert von 16 Zyklen ausführt (ich glaube, ich habe einen echten indizierten Ladevorgang, einen Dummy-indizierten Ladevorgang und einen Speicher verwendet). Der erste SPI-Schreibvorgang jeder Zeile erfolgte vor dem Start des Videos. und für jeden nachfolgenden Schreibvorgang gab es ein 16-Zyklen-Fenster, in dem der Schreibvorgang ohne Pufferüberlauf oder -unterlauf erfolgen konnte. Die Verzweigungsschleife erzeugte ein Unsicherheitsfenster von 13 Zyklen, aber die vorhersagbare 16-Zyklen-Ausführung bedeutete, dass die Unsicherheit für alle nachfolgenden Bytes in dasselbe 13-Zyklen-Fenster passte (das wiederum in das 16-Zyklen-Fenster passte, in dem das Schreiben akzeptabel war). auftreten).

Bei älteren CPUs war die Befehls-Timing-Information klar, verfügbar und eindeutig. Bei neueren ARMs scheinen Zeitinformationen viel vager zu sein. Ich verstehe, dass, wenn Code aus dem Flash ausgeführt wird, das Caching-Verhalten die Vorhersage viel schwieriger machen kann, daher würde ich erwarten, dass jeder zyklusgezählte Code aus dem RAM ausgeführt werden sollte. Selbst wenn Code aus dem RAM ausgeführt wird, scheinen die Spezifikationen etwas vage zu sein. Ist die Verwendung von Cycle-Counted-Code noch sinnvoll? Wenn ja, was sind die besten Techniken, damit es zuverlässig funktioniert? Inwieweit kann man sicher davon ausgehen, dass ein Chiphersteller nicht stillschweigend einen „neuen, verbesserten“ Chip einschleusen wird, der in bestimmten Fällen die Ausführung bestimmter Befehle um einen Zyklus verkürzt?

Angenommen, die folgende Schleife beginnt an einer Wortgrenze, wie würde man basierend auf Spezifikationen genau bestimmen, wie lange es dauern würde (nehmen Sie an, dass Cortex-M3 mit Null-Wartestatus-Speicher; nichts anderes über das System sollte für dieses Beispiel von Bedeutung sein).

myloop:
  Bewegung r0,r0 ; Kurze einfache Anweisungen, damit mehr Anweisungen vorab abgerufen werden können
  Bewegung r0,r0 ; Kurze einfache Anweisungen, damit mehr Anweisungen vorab abgerufen werden können
  Bewegung r0,r0 ; Kurze einfache Anweisungen, damit mehr Anweisungen vorab abgerufen werden können
  Bewegung r0,r0 ; Kurze einfache Anweisungen, damit mehr Anweisungen vorab abgerufen werden können
  Bewegung r0,r0 ; Kurze einfache Anweisungen, damit mehr Anweisungen vorab abgerufen werden können
  Bewegung r0,r0 ; Kurze einfache Anweisungen, damit mehr Anweisungen vorab abgerufen werden können
  fügt r2,r1,#0x12000000 hinzu; 2-Wort-Anweisung
  ; Wiederholen Sie das Folgende, möglicherweise mit anderen Operanden
  ; Wird so lange Werte hinzufügen, bis ein Übertrag auftritt
  itcc
  addcc r2,r2,#0x12000000 ; 2-Wort-Anweisung plus zusätzliches "Wort" für itcc
  itcc
  addcc r2,r2,#0x12000000 ; 2-Wort-Anweisung plus zusätzliches "Wort" für itcc
  itcc
  addcc r2,r2,#0x12000000 ; 2-Wort-Anweisung plus zusätzliches "Wort" für itcc
  itcc
  addcc r2,r2,#0x12000000 ; 2-Wort-Anweisung plus zusätzliches "Wort" für itcc
;...etc, mit bedingteren Zwei-Wort-Anweisungen
  sub r8,r8,#1
  bpl myloop

Während der Ausführung der ersten sechs Anweisungen hätte der Kern Zeit, sechs Wörter abzurufen, von denen drei ausgeführt würden, sodass bis zu drei vorab abgerufen werden könnten. Die nächsten Anweisungen bestehen jeweils aus drei Wörtern, sodass es dem Kern nicht möglich wäre, Anweisungen so schnell abzurufen, wie sie ausgeführt werden. Ich würde erwarten, dass einige der "it" -Anweisungen einen Zyklus benötigen, aber ich weiß nicht, wie ich welche vorhersagen soll.

Es wäre schön, wenn ARM bestimmte Bedingungen angeben könnte, unter denen das Timing der "it"-Anweisung deterministisch wäre (z. aber ich habe keine solche Spezifikation gesehen.

Beispielanwendung

Angenommen, jemand versucht, eine Tochterplatine für einen Atari 2600 zu entwerfen, um eine Komponentenvideoausgabe mit 480p zu erzeugen. Der 2600 hat einen Pixeltakt von 3,579 MHz und einen CPU-Takt von 1,19 MHz (Punkttakt/3). Für 480P-Komponentenvideo muss jede Zeile zweimal ausgegeben werden, was eine Punkttaktausgabe von 7,158 MHz impliziert. Da der Videochip (TIA) des Atari eine von 128 Farben ausgibt, indem er ein 3-Bit-Luma-Signal plus ein Phasensignal mit einer Auflösung von ungefähr 18 ns verwendet, wäre es schwierig, die Farbe genau zu bestimmen, indem man sich nur die Ausgänge ansieht. Ein besserer Ansatz wäre, Schreibvorgänge in die Farbregister abzufangen, die geschriebenen Werte zu beobachten und jedes Register mit den TIA-Luminanzwerten zu speisen, die der Registernummer entsprechen.

All dies könnte mit einem FPGA durchgeführt werden, aber einige ziemlich schnelle ARM-Geräte sind weitaus billiger als ein FPGA mit genügend RAM, um die erforderliche Pufferung zu bewältigen (ja, ich weiß, dass für die Mengen, die so etwas produziert werden könnte, die Kosten ' t ein realer Faktor). Wenn der ARM das eingehende Taktsignal überwachen müsste, würde dies jedoch die erforderliche CPU-Geschwindigkeit erheblich erhöhen. Vorhersagbare Zykluszahlen könnten die Dinge sauberer machen.

Ein relativ einfacher Designansatz wäre, eine CPLD die CPU und TIA beobachten zu lassen und ein 13-Bit-RGB+Sync-Signal zu generieren und dann ARM-DMA 16-Bit-Werte von einem Port zu erfassen und sie mit dem richtigen Timing auf einen anderen zu schreiben. Es wäre jedoch eine interessante Design-Herausforderung zu sehen, ob ein billiger ARM alles kann. DMA könnte ein nützlicher Aspekt eines All-in-One-Ansatzes sein, wenn seine Auswirkungen auf die Anzahl der CPU-Zyklen vorhergesagt werden könnten (insbesondere wenn die DMA-Zyklen in Zyklen auftreten könnten, in denen der Speicherbus ansonsten im Leerlauf war), aber an einem bestimmten Punkt im Prozess der ARM müsste seine Tabellensuch- und Busüberwachungsfunktionen durchführen. Beachten Sie, dass im Gegensatz zu vielen Videoarchitekturen, bei denen Farbregister während Austastintervallen geschrieben werden, der Atari 2600 während des angezeigten Teils eines Frames häufig in Farbregister schreibt.

Der vielleicht beste Ansatz wäre, ein paar diskrete Logikchips zu verwenden, um Farbschreibvorgänge zu identifizieren und die unteren Bits der Farbregister auf die richtigen Werte zu zwingen, und dann zwei DMA-Kanäle zu verwenden, um den eingehenden CPU-Bus und die TIA-Ausgangsdaten abzutasten, und einen dritten DMA-Kanal zum Erzeugen der Ausgangsdaten. Die CPU wäre dann frei, alle Daten von beiden Quellen für jede Abtastzeile zu verarbeiten, die notwendige Übersetzung durchzuführen und sie für die Ausgabe zu puffern. Der einzige Aspekt der Aufgaben des Adapters, der in "Echtzeit" erfolgen müsste, wäre das Überschreiben von Daten, die in COLUxx geschrieben werden, und das könnte unter Verwendung zweier gemeinsamer Logikchips erledigt werden.

Antworten (5)

Ich stimme für DMA. Es ist wirklich flexibel in Cortex-M3 und höher - und Sie können alle möglichen verrückten Dinge tun, wie z. B. automatisch Daten von einem Ort abrufen und mit einer bestimmten Rate oder bei einigen Ereignissen an einen anderen ausgeben, ohne IRGENDWELCHE CPU-Zyklen zu verbrauchen. DMA ist viel zuverlässiger.

Aber es könnte ziemlich schwer sein, es im Detail zu verstehen.

Eine weitere Option sind Softcores auf FPGA mit Hardwareimplementierung dieser engen Dinge.

Ich mag das DMA-Konzept. Ich glaube jedoch nicht, dass der Cortex M3-Kern DMA hat – das ist eine Funktion der Chips der einzelnen Hersteller, und sie scheinen es alle unterschiedlich zu implementieren. Eine Sache, die ich zumindest bei der einen Implementierung, mit der ich tatsächlich gespielt habe (STM32L152), lästig finde, ist, dass ich keine Möglichkeit finde, einen Pin-Strobe zu haben, wenn DMA-Daten ausgegeben werden. Es ist auch nicht klar, welche Faktoren die DMA-Aktualität beeinflussen können.
In Bezug auf eine der ersten Anwendungen, über die ich für präzises Cycle-Banging nachdachte, habe ich jedenfalls weitere Informationen in der ursprünglichen Frage gepostet. Ich bin gespannt, was Sie denken. Eine andere Situation, in der ich über Cycle-Banging nachdachte, wäre das Übertragen von Anzeigedaten auf ein Farb-LCD. Die Daten würden im RAM mit 8-Bit-Farben gepuffert, aber die Anzeige benötigt 16-Bit-Farben. Der schnellste Weg, an den ich gedacht hatte, um Daten auszugeben, wäre die Verwendung von Hardware zum Generieren der Schreibimpulse gewesen, sodass die CPU nur Daten austakten müsste. Wäre es gut, 8-> 16 Bit in einen kleinen Puffer zu übersetzen ...
... und dann DMA veranlassen, das zu übertragen, oder was wäre der beste Ansatz?

Zeitinformationen sind verfügbar, können aber, wie Sie bereits sagten, gelegentlich vage sein. In Abschnitt 18.2 und Tabelle 18.1 des Technischen Referenzhandbuchs für den Cortex-M3 finden Sie beispielsweise viele Timing-Informationen ( pdf hier ) und einen Auszug hier:

Auszug vom 18.2

die eine Liste von Bedingungen für maximales Timing geben. Das Timing für viele Anweisungen hängt von externen Faktoren ab, von denen einige Mehrdeutigkeiten hinterlassen. Ich habe jede der Mehrdeutigkeiten, die ich gefunden habe, im folgenden Auszug aus diesem Abschnitt hervorgehoben:

[1] Verzweigungen benötigen einen Zyklus für die Anweisung und dann ein erneutes Laden der Pipeline für die Zielanweisung. Nicht genommene Verzweigungen sind insgesamt 1 Zyklus. Genommene Verzweigungen mit einem sofortigen Vorgang sind normalerweise 1 Zyklus des Pipeline-Neuladens (insgesamt 2 Zyklen). Genommene Verzweigungen mit dem Registeroperanden sind normalerweise 2 Zyklen des Pipeline-Neuladens (insgesamt 3 Zyklen). Das Neuladen der Pipeline ist länger [wie lange noch?], wenn zusätzlich zu den Zugriffen auf langsameren Speicher auf nicht ausgerichtete 32-Bit-Anweisungen verzweigt wird. Ein Verzweigungshinweis wird an den Codebus ausgegeben, der es einem langsameren System [wie viel langsamer?] ermöglicht, vorab zu laden. Dies kann [Ist das optional? ] die Verzweigungszielstrafe für langsameren Speicher reduzieren [Um wie viel?], aber niemals weniger als hier gezeigt.

[2] Im Allgemeinen benötigen Lade-Speicher-Befehle zwei Zyklen für den ersten Zugriff und einen Zyklus für jeden weiteren Zugriff. Speicher mit sofortigen Offsets benötigen einen Zyklus.

[3] UMULL/SMULL/UMLAL/SMLAL verwenden eine vorzeitige Beendigung abhängig von der Größe der Quellwerte [Welche Größen?]. Diese sind unterbrechbar (aufgegeben/neu gestartet), mit einer Worst-Case-Latenzzeit von einem Zyklus. MLAL - Versionen benötigen vier bis sieben Zyklen und MULL - Versionen drei bis fünf Zyklen . Für MLAL ist die signierte Version einen Zyklus länger als die unsignierte.

[4] IT-Anweisungen können gefaltet werden . [Wann? Zeige Kommentare.]

[5] DIV-Timings hängen von Dividend und Divisor ab . [Gleiches Problem wie MUL] DIV ist unterbrechbar (aufgegeben/neu gestartet), mit einer Latenzzeit von einem Zyklus im ungünstigsten Fall. Wenn Dividend und Divisor ähnlich groß sind [wie ähnlich?], endet die Division schnell. Die Mindestzeit gilt für Fälle, in denen der Divisor größer als der Dividende und der Divisor Null ist. Ein Divisor von Null gibt Null zurück (kein Fehler), obwohl ein Debug-Trap verfügbar ist, um diesen Fall abzufangen. [Welche Bereiche wurden für MUL angegeben?]

[6] Schlaf ist ein Zyklus für die Anweisung plus so viele Schlafzyklen wie angemessen. WFE verwendet nur einen Zyklus, wenn das Ereignis abgelaufen ist. WFI ist normalerweise länger als ein Zyklus, es sei denn, ein Interrupt steht genau beim Eintritt in WFI an.

[7] ISB dauert einen Zyklus (wirkt als Verzweigung). DMB und DSB benötigen einen Zyklus, es sei denn, Daten stehen im Schreibpuffer oder in der LSU an. Wenn während einer Barriere ein Interrupt eintrifft, wird er abgebrochen/neu gestartet.

Für alle Anwendungsfälle wird es in einfacheren, langsameren, älteren Prozessoren komplexer sein als die Zählung "Diese Anweisung ist ein Zyklus, diese Anweisung sind zwei Zyklen, dies ist ein Zyklus ...". Bei einigen Anwendungsfällen werden Sie auf keine Mehrdeutigkeiten stoßen. Wenn Sie auf Unklarheiten stoßen, schlage ich vor:

  1. Wenden Sie sich an Ihren Anbieter und fragen Sie ihn, wie das Anweisungstiming für Ihren Anwendungsfall ist.
  2. Testen Sie, um das mehrdeutige Verhalten anzugeben
  3. Testen Sie erneut auf Prozessorrevisionen und insbesondere, wenn Sie Anbieterwechsel durchlaufen.

Diese Anforderungen sind wahrscheinlich die Antwort auf Ihre Frage: "Nein, es ist keine gute Idee, es sei denn, die aufgetretenen Schwierigkeiten sind die Kosten wert" - aber das wussten Sie bereits.

Ich würde Folgendes für vage halten: "Das Neuladen der Pipeline ist länger, wenn zusätzlich zu den Zugriffen auf langsameren Speicher auf nicht ausgerichtete 32-Bit-Anweisungen verzweigt wird" sagt nicht, ob es genau einen Zyklus hinzufügt, und "IT-Anweisungen können gefaltet werden". nicht angeben, unter welchen Bedingungen sie sein werden oder nicht.
Das "IT"-Timing scheint besonders beunruhigend zu sein, da dies eine Anweisung ist, die oft innerhalb einer engen Schleife mit Zykluszählung verwendet wird, und ich bin mir ziemlich sicher, dass sie nicht immer gefaltet werden kann. Ich würde vermuten, dass, wenn man immer zum Beginn einer zeitabhängigen Schleife verzweigt, die Schleife zwingt, an einer Wortgrenze zu beginnen, bedingte Lade- oder Speichervorgänge innerhalb der Schleife vermeidet und man nicht sofort eine "IT" -Anweisung einfügt Nach dem Laden oder Aktualisieren des Speichers wären die "IT" -Timings konsistent, aber die Spezifikation macht dies nicht klar.
Meine Vermutung wäre, dass die IT wahrscheinlich (wahrheitsgemäß) etwas notieren könnte wie: „In Abwesenheit von Wartezuständen oder Code-Bus-Konkurrenz ist das IT-Folding garantiert, wenn (1) die vorhergehende Anweisung eine 16-Bit-Anweisung war, auf die nicht zugegriffen wurde Speicher oder Programmzähler; und (2) entweder die nächste Anweisung eine 16-Bit-Anweisung ist oder die vorhergehende Anweisung nicht das Ziel einer "nicht ausgerichteten" Verzweigung war. IT-Faltung kann auch unter anderen nicht spezifizierten Umständen auftreten." Eine solche Spezifikation würde es einem ermöglichen, Programme mit vorhersagbarem IT-Anweisungs-Timing zu schreiben, indem sichergestellt wird, dass der Code wie angegeben angeordnet ist.
Wow – ich gestehe, dass ich nur einfache Worst-Case-Zykluszählungen durchlaufen hatte, anstatt tatsächlich mit den Vorbehalten unter dem Tisch zu ringen. Meine aktualisierte Antwort hebt einige andere Unklarheiten hervor.
Es gibt viele Situationen, in denen man sich für Worst-Case-Zählungen interessiert, und eine ganze Reihe von Situationen, in denen man an Best-Case-Zählungen interessiert ist (z. B. wenn ein SPI-Port alle 16 Zyklen ein Byte ausgeben kann, würde die Generierung jedes Bytes 14 Zyklen dauern im besten Fall, und die Überprüfung der Bereitschaft würde 5 Zyklen dauern, die Überprüfung der Bereitschaft jedes Bytes würde die Geschwindigkeit im besten Fall auf ein Byte alle 19 Zyklen begrenzen; das blinde Schreiben mit zwei hinzugefügten NOPs würde im besten Fall eine Geschwindigkeit von einem Byte alle 16 Zyklen ermöglichen ). Die Fälle, in denen ein genaues Timing erforderlich ist, sind nicht so häufig, aber sie können auftreten.
Die Notwendigkeit eines solch präzisen Timings kann in einigen Fällen durch Peripheriegeräte verringert werden, die eine Pufferung zusammen mit einer Anzeige darüber, wie voll sie sind, bieten. Nehmen wir zum Beispiel an, man hat einen SPI-Port mit einem Vier-Byte-Puffer und einer Fast-Leer-Anzeige, und blinde Schreibvorgänge würden 14–16 Zyklen dauern. Wenn der Puffer fast leer ist, könnte man blind 24 Bytes schreiben (unter Verwendung von 336-384 Zyklen, wodurch mindestens 21 Bytes den Port verlassen hätten), bevor die Pufferbereitschaft überprüft wird. Sobald der Puffer fast leer ist, könnte man 24 weitere Bytes schreiben.
Beachten Sie, dass Sie, damit dieser Ansatz gut funktioniert, eine ziemlich gute Vorstellung davon haben müssen, wie lange der Code voraussichtlich dauern wird. Je weniger genau man das weiß, desto mehr Overhead wird für die Synchronisation notwendig sein.

Eine Möglichkeit, dieses Problem zu umgehen, besteht darin, Geräte mit deterministischen oder vorhersagbaren Timings zu verwenden, wie z. B. den Parallax-Propeller und XMOS-Chips:

http://www.parallaxsemiconductor.com/multicoreconcept

http://www.xmos.com/

Cycle-Counting funktioniert sehr gut mit dem Propeller (Assembler-Sprache muss verwendet werden), während die XMOS-Geräte über ein sehr leistungsfähiges Software-Dienstprogramm verfügen, den XMOS Timing Analyzer, der mit Anwendungen funktioniert, die in der Programmiersprache XC geschrieben sind:

https://www.xmos.com/download/public/XMOS-Timing-Analyzer-Whitepaper%281%29.pdf

Ich fange an zu glauben, dass Leon Anteile an XMOS hat... ;-)
Ich mag einfach ihre Chips und die Leute, die dort arbeiten. Parallax ist auch eine nette Firma mit guten Produkten.
Ja, nichts für ungut. Mir fällt nur auf, dass alle Antworten (außer einer), in denen XMOS erwähnt wird, von Ihnen stammen. Es ist nichts falsch daran, sich für etwas zu begeistern.
@Federico, @Leon - Genau das macht mir bei XMOS ein bisschen Sorgen: Warum gibt es nur 1 Benutzer auf der Welt (zumindest sieht es so aus)? Wenn es so toll ist, warum ist es dann nicht das Stadtgespräch? Ich habe noch nie jemanden darüber reden gehört, noch weniger benutzt.
Probieren Sie die XMOS-Foren aus: xcore.com

Das Zählen von Zyklen wird problematischer, wenn man sich von Low-Level-Mikrocontrollern wegbewegt und zu allgemeineren Rechenprozessoren übergeht. Die ersten haben in der Regel genau festgelegte Unterrichtszeiten, teilweise aus den Gründen, die Sie vor Ort haben. Das liegt auch daran, dass ihre Architektur ziemlich einfach ist, sodass die Unterrichtszeiten festgelegt und bekannt sind.

Ein gutes Beispiel dafür sind die meisten Microchip PICs. Die Serien 10, 12, 16 und 18 haben ein sehr gut dokumentiertes und vorhersagbares Unterrichtstiming. Dies kann ein nützliches Merkmal bei der Art von kleinen Steuerungsanwendungen sein, für die diese Chips gedacht sind.

Wenn Sie sich von extrem niedrigen Kosten entfernen und der Designer daher etwas mehr Chipfläche aufwenden kann, um eine höhere Geschwindigkeit von einer exotischeren Architektur zu erhalten, entfernen Sie sich auch von der Vorhersagbarkeit. Schauen Sie sich als Extrembeispiele moderne x86-Varianten an. Es gibt mehrere Cache-Ebenen, Speichervitualisierung, Lookahead-Fetch, Pipelining und mehr, die das Zählen von Befehlszyklen nahezu unmöglich machen. In dieser Anwendung spielt es jedoch keine Rolle, da der Kunde an hoher Geschwindigkeit interessiert ist, nicht an der Vorhersagbarkeit des Befehlstimings.

Sie können diesen Effekt sogar bei höheren Microchip-Modellen beobachten. Der 24-Bit-Kern (Serien 24, 30 und 33) hat ein weitgehend vorhersagbares Befehls-Timing, mit Ausnahme einiger Ausnahmen, wenn es Registerbus-Konkurrenz gibt. Beispielsweise fügt die Maschine in einigen Fällen einen Stillstand ein, wenn der nächste Befehl ein Register mit einigen indirekten Adressierungsmodi verwendet, dessen Wert in dem vorherigen Befehl geändert wurde. Diese Art von Stillstand ist auf einem dsPIC ungewöhnlich, und die meiste Zeit können Sie es ignorieren, aber es zeigt, wie sich diese Dinge einschleichen, weil die Designer versuchen, Ihnen einen schnelleren und leistungsfähigeren Prozessor zu geben.

Die grundlegende Antwort lautet also, dass dies Teil des Kompromisses ist, wenn Sie sich für einen Prozessor entscheiden. Für kleine Steuerungsanwendungen können Sie etwas kleines, billiges, stromsparendes und mit vorhersagbarem Befehlstiming wählen. Wenn Sie mehr Rechenleistung benötigen, ändert sich die Architektur, sodass Sie auf vorhersagbares Befehlstiming verzichten müssen. Glücklicherweise ist das weniger ein Problem, wenn Sie zu rechenintensiveren und universelleren Anwendungen kommen, also denke ich, dass die Kompromisse ziemlich gut funktionieren.

Ich stimme zu, dass rechenintensivere Anwendungen im Allgemeinen weniger empfindlich auf mikroskopisches Timing reagieren, aber es gibt einige Szenarien, in denen man möglicherweise etwas mehr Rechenleistung als beim PIC-18 benötigt, aber auch Vorhersagbarkeit benötigt. Ich frage mich, inwieweit ich mich bemühen sollte, Dinge wie die 16-Bit-PIC-Architekturen zu lernen, oder inwieweit ich davon ausgehen sollte, dass ARM wahrscheinlich angemessen sein wird.

Ja, Sie können es immer noch tun, sogar auf einem ARM. Das größte Problem dabei auf einem ARM ist, dass ARM Kerne verkauft, keine Chips, und das Kern-Timing ist bekannt, aber was der Chip-Anbieter darum wickelt, variiert von Anbieter zu Anbieter und manchmal von Chip-Familie zu einer anderen innerhalb des Anbieters. Ein bestimmter Chip eines bestimmten Anbieters kann also ziemlich deterministisch sein (wenn Sie zum Beispiel keine Caches verwenden), wird aber schwieriger zu portieren. Beim Umgang mit 5 Takten hier und 11 Takten dort ist die Verwendung von Timern problematisch, da die Anzahl der Anweisungen erforderlich ist, um den Timer abzutasten und herauszufinden, ob Ihr Timeout abgelaufen ist. Aufgrund der Klänge Ihrer bisherigen Programmiererfahrung bin ich bereit zu wetten, dass Sie wahrscheinlich wie ich mit einem Oszilloskop debuggen, sodass Sie eine enge Schleife auf dem Chip bei der Taktrate ausprobieren können, sich die spi- oder i2c- oder welche Wellenform auch immer ansehen, hinzufügen oder Nops entfernen, Ändern Sie die Anzahl der Schleifendurchläufe und stimmen Sie im Grunde ab. Wie bei jeder Plattform unterstützt der Verzicht auf Interrupts die deterministische Natur der Befehlsausführung erheblich.

Nein, es ist nicht so einfach wie ein PIC, aber immer noch durchaus machbar, besonders wenn sich die Verzögerung / das Timing der Taktrate des Prozessors nähert. Eine Reihe von ARM-basierten Anbietern ermöglichen es Ihnen, die Taktrate zu multiplizieren und beispielsweise 60 MHz von einer 8-MHz-Referenz abzuheben. Wenn Sie also eine 2-MHz-Schnittstelle benötigen, anstatt alle 4 Anweisungen etwas zu tun, können Sie die Taktrate erhöhen (falls Sie die haben Energiebudget) und dann einen Timer verwenden und sich viele Uhren geben, um auch andere Dinge zu tun.