4-Kanal-Software-PWM mit Atmega16 zur Steuerung von 4 ESCs für bürstenlose Gleichstrommotoren

Ich versuche, eine softwareimplementierte PWM zur Steuerung von 4 ESCs mit einem Atmega 16-Mikrocontroller zu implementieren.

Um dies zu erreichen, erzeuge ich sequentiell die Impulse für jeden ESC nacheinander in jeder Periode des Signals.

Hier ist der Code -

#define TOTAL_ESC 4
int ESC_pulse[TOTAL_ESC];
int ESC_pins[TOTAL_ESC];
int currentESC;

int main(void)
{
    initESCs();
    initUSART();

    sei();
    while (1)
    {
    }
}

void initESCs()
{
    ESC_pulse[0] = ESC_pulse[1] = ESC_pulse[2] = ESC_pulse[3] = 2000;
    ESC_pins[0] = PIND4;
    ESC_pins[1] = PIND5;
    ESC_pins[2] = PIND6;
    ESC_pins[3] = PIND7;
    currentESC = TOTAL_ESC - 1;

    ICR1 = 39999;   //50Hz signal @ 16MHz clock
    OCR1A = 1000;

    DDRD |= 1<<ESC_pins[0] | 1<<ESC_pins[1] | 1<<ESC_pins[2] | 1<<ESC_pins[3];
    PORTD &= ~(1<<ESC_pins[0] | 1<<ESC_pins[1] | 1<<ESC_pins[2] | 1<<ESC_pins[3]);  

    TIMSK |= 1<<OCIE1A;
    TCCR1A |= 1<<WGM11;
    TCCR1B |= 1<<WGM13 | 1<<WGM12 | 1<<CS11;
}

ISR(TIMER1_COMPA_vect)
{
    PORTD &= ~(1<<ESC_pins[currentESC]);    //End prev ESC pulse

    if(currentESC == TOTAL_ESC - 1 && OCR1A != 1000)
    {
        OCR1A = 1000;
        return;
    }

    currentESC = (currentESC + 1) % TOTAL_ESC;

    PORTD |= 1<<ESC_pins[currentESC];   //Start next ESC pulse
    OCR1A += ESC_pulse[currentESC];
}

Ich versuche also, 50-Hz-Signale mit Impulsen von 1 ms bis 2 ms zu erzeugen. Mein CPU-Takt beträgt 16 MHz und der Timer-Takt ist auf 2 MHz vorskaliert.

Ich habe ein Array ESC_pulsezum Speichern der Breite der Impulse für jeden ESC. Die darin enthaltenen Werte reichen von 2000 bis 4000 für die 1-ms- bis 2-ms-Impulse, die die ESCs benötigen. Die Logik, die ich anwende, ist, dass ich jedes Mal, wenn der Timer-Vergleichs-Interrupt auftritt, den Ausgangspin des letzten ESC lösche und den nächsten setze und den OCR1AWert mit dem aktuellen OCR1AWert + der Impulsbreite des aktuellen ESC aktualisiere, wie in gespeichert ESC_pulse.

Mit der von der Hardware erzeugten PWM mit dem Timer ist mein ESC in der Lage, den bürstenlosen Gleichstrommotor zu betreiben. Die softwaregenerierte Technik funktioniert jedoch nicht. Ich habe keine Ausrüstung, um tatsächlich zu sehen, was die erzeugten PWM-Signale tatsächlich sind. Alles, was passiert, ist, dass der ESC Pieptöne erzeugt, die kein Signal anzeigen.

Ich bin mir nicht sicher, was ich hier falsch mache.

In diesem Timing-Bereich kann man mit ein wenig Geschick bei der Kopplung durchaus eine Soundkarte als Oszilloskop verwenden. Das Signal wird aufgrund der AC-Kopplung verzerrt, aber Sie sollten in der Lage sein, das Timing herauszufinden.
Wie viele mögliche Schritte brauchen Sie zwischen 1ms und 2ms, dh wie fein soll Ihre Regelung sein?
Viele Leute, die auf diese Frage antworten, scheinen nicht zu wissen, dass in einer traditionellen RC-Anwendung die Kanäle nacheinander in Round-Robin-Manier aktualisiert werden, nicht alle auf einmal. Sie alle auf einmal zu aktualisieren ist möglich , aber nicht notwendig . Außerdem ist es erforderlich, sie auch dann weiter zu aktualisieren, wenn sie sich wahrscheinlich nicht geändert haben, da ein ESC möglicherweise so gebaut ist, dass er als Sicherheitsmaßnahme heruntergefahren wird, wenn er keine regelmäßigen Updates erhält.

Antworten (3)

Die Ursache des Problems: OCR1A-Doppelpufferung

Ihr Programm schlägt fehl, weil OCR1A in allen PWM-Timer-Modi doppelt gepuffert ist und Sie einen solchen Modus verwenden (Fast PWM, TOP = ICR1). Wenn Sie einen neuen Wert in OCR1A schreiben, ändern Sie nicht wirklich den Wert, der von der Timer-Hardware verwendet wird. Stattdessen wird der in OCR1A gespeicherte Wert erst dann in das separate "Schattenregister" kopiert, das tatsächlich vom Timer verwendet wird, wenn der Zählerwert TOP erreicht und wieder bei Null beginnt. Dies ist sehr nützlich, um störungsfreie Hardware-PWM zu erzeugen, verhindert jedoch das, was Sie versuchen (mehrere OCR1A-Aktualisierungen pro Timer-Zyklus).

Da dieses OCR1A-Update nur einmal pro Timer-Zyklus (bei 50 Hz) erfolgt und Ihr Interrupt-Code 4 Verzögerungen von 1000 μs - 2000 μs + eine lange Verzögerung erzeugen soll, erhalten Sie am Ende eine PWM-Periode von 100 ms (5 Timer-Zyklen) und eine hohe Zeit von ~20 ms.

Die Lösung besteht darin, den Timer auf einen Nicht-PWM-Modus zu konfigurieren. Der für Ihr Programm am besten geeignete Modus ist Clear Timer on Compare Match (CTC, TOP = ICR1), der nahezu identisch funktioniert, aber OCR1A und OCR1B nicht doppelt puffert. Die WGM-Bits in TCCR1A und TCCR1B sollten auf 1100 gesetzt werden, um dies zu erreichen (siehe Datenblatt für Details).

Andere kleinere Probleme:

  • Wenn Sie in einer Interrupt-Service-Routine auf eine Variable zugreifen, müssen Sie die Variable deklarieren volatile. Das Weglassen des Schlüsselworts (unter anderem) ermöglicht es dem Compiler, Optimierungen vorzunehmen, die davon ausgehen, dass nur der Hauptprogrammfluss den Zustand ändern kann.

  • PIND4, PIND5, PIND6und PIND7wie von Ihrem Code verwendet werden nicht in den von AVR-GCC bereitgestellten Registerdefinitionen gefunden. Die allgemeineren Makros PINx, PORTxund DDRxwerden in "portpins.h" deklariert.

  • Die Ablauflogik könnte klarer geschrieben werden. Ihr aktueller Code hat 4 explizite Zustände in der Sequenz (einen für jeden Ausgangspin), von denen der letzte für zwei Vergleichsübereinstimmungen verwendet wird: Der erste, um den Ausgang von Kanal 4 niedrig zu setzen, und der zweite, um zu warten, bis die Sequenz neu gestartet werden soll. Dies geschieht seltsamerweise, indem ein bestimmter OCR1A-Wert (1000) als Flag verwendet wird. Das hat mich eine Weile am Kopf kratzen lassen.

  • Dies mag meinungsbasiert sein, aber ich würde eher uint8_t, int16_tund Freunde verwenden als zB unsigned charoder shortin eingebetteter Software. Auf diese Weise wissen Sie und andere genau, wie groß Ihre Variablen sind, und sie sind auch weniger ausführlich.

  • Ihr Codebeispiel sollte <avr/io.h>and enthalten <avr/interrupt.h>und eine Funktionsdeklaration für bereitstellen void initESCs(). initUSART()ist überflüssig.

Dies ist eine großartige Antwort, da Sie die im Wesentlichen solide Idee, die in der Frage versucht wurde, aufgegriffen, erklärt haben, warum sie nicht ganz wie beabsichtigt funktioniert, und vorgeschlagen haben, wie sie behoben werden kann.
Danke. Ich habe die Lösung noch nicht ausprobiert. Aber jetzt weiß ich warum es nicht funktioniert hat. Ich hätte das Datenblatt richtig lesen sollen! Und eigentlich habe ich nicht den ganzen Code eingefügt. Den Include-Teil und den USART-bezogenen Code übersprungen. Nochmals vielen Dank @jms.
Der OCR1A-Wert ist eigentlich irrelevant. Ich hätte es einfach auf 0 statt 1000 belassen können. Es war nur ein verzweifelter Versuch, herauszufinden, was im Code falsch lief.

Wenn ich Ihren Code richtig verstehe, haben Sie Timer1 im schnellen PWM-Modus mit OCR1A, um die PWM-Einschaltdauer und ICR1 für den Zeitraum zu messen. Wenn OCR1A mit dem aktuellen Timerwert übereinstimmt, löst es einen Interrupt aus. Sie laden es dann mit einer längeren Zeit neu, die Idee ist, dass es den Timer 1 ~ 2 ms später für den nächsten Servoimpuls abgleicht.

Das Problem bei dieser Technik besteht darin, dass das Ausgangsvergleichsregister in PWM-Modi doppelt gepuffert und mit der PWM-Periode synchronisiert ist, sodass das Schreiben darauf während des aktuellen PWM-Zyklus nur im nächsten Zyklus Auswirkungen hat . Dies ist im Datenblatt auf Seite 98 beschrieben:-

Das OCR1x-Register wird doppelt gepuffert, wenn einer der zwölf Pulsweitenmodulationsmodi (PWM) verwendet wird... Die doppelte Pufferung synchronisiert die Aktualisierung des OCR1x-Vergleichsregisters entweder mit OBEN oder mit UNTEN der Zählsequenz. Die Synchronisation verhindert das Auftreten von unsymmetrischen PWM-Pulsen mit ungerader Länge

Anstatt also 1~2 ms nach dem Schreiben in OCR1A einen Interrupt zu erhalten, erhalten Sie ihn 20 ms + 1~2 ms später.

Ich bin mir nicht sicher, ob es möglich ist, dies mit einem Nicht-PWM-Timer-Modus auf Ihre Weise zu tun, aber es könnte einfacher sein, einfach einen einfachen Timer zu verwenden, um jeden Impuls separat zu timen, dann alle Zeiten zu addieren und von 20 ms abzuziehen, um zu erhalten die letzte Pausenzeit.

Viele moderne ESCs können mit Frequenzen von 250 Hz oder höher umgehen, sodass Sie vielleicht sogar damit durchkommen, die Impulse so schnell wie möglich nacheinander herauszudrücken.

Die Lösung könnte also sein, das einfach wegzulassenTCCR1A |= 1<<WGM11;
Ja. Dadurch wird der Timer in den CTC-Modus versetzt, der OCR1A nicht doppelt puffert.
  • Das erste, was Sie tun müssen, ist, sich JEDE Art von Umfang zu besorgen. Sie können einen DSO138 kaufen – ich habe mehrere davon gekauft und zusammengebaut, um sie an Freunde zu verschenken. Sie sind billig und können für Ihre Bedürfnisse funktionieren. (Es probiert bei 1 MHz und unterstützt nur etwa 200 kHz , aber das mag für das, was Sie vor sich haben, in Ordnung sein.) Oder Sie können etwas schickeres kaufen, das an einen PC angeschlossen werden kann, wie ein Hantek 6022BE. Oder Sie kaufen einfach ein billiges, gebrauchtes Oszilloskop. Es gibt eine Reihe von Websites, auf denen Sie nach guten Preisen suchen können, von denen ich sicher bin, dass Sie viele kennen. Aber der Punkt hier ist, dass Sie, wenn Sie an Code arbeiten, um etwas Wichtiges wie dieses zu tun, wirklich etwas haben MÜSSEN, mit dem Sie beobachten und messen können, was Sie produzieren. Und Ihre Bedürfnisse sind fußläufig, also kostet es Sie keinen Arm und kein Bein. Wahrscheinlich weniger als die Kosten für einen ESC-Controller. Lohnt sich.
  • Der ATmega16 hat vier PWMs. Ein PWM auf jedem der zwei 8-Bit-Timer und nur zwei weitere PWMs auf dem 16-Bit-Timer1. Ich verstehe also, warum Sie versuchen, dies in der Software mit Timer1 zu tun. Es gibt keinen anderen Weg.

Was ich in Ihrem Code NICHT sehe, ist eine Warteschlange jeglicher Art. Und du brauchst einen, denke ich.

So würde ich dies als Softwaredesignlösung angehen:

  1. Entscheiden Sie sich für eine bestimmte Auflösung für meine vier PWM-Ausgänge. Ich bin kein Experte für ESC-Controller und weiß nicht, welche Auflösung sie verwenden, wenn sie die empfangene Impulsbreite interpretieren. Aber ich denke, ich kann aus Ihrer Verwendung von 1000 und 2000 schließen, dass Sie eine Genauigkeit bis auf die Mikrosekunde wünschen. Kurz gesagt, Sie können festlegen 1.001 MS und einstellen 1.583 MS , aber Sie können nicht festlegen 1.6057 MS .
  2. Sobald Sie die erforderliche Auflösung kennen, können Sie Timer1 einrichten, um diese Auflösung bereitzustellen. Dies richtet nur die Zählerrate des Timers ein. (Es verursacht selbst keine Interrupts. Das passiert, wenn Sie die Erfassung/den Vergleich einrichten.) In diesem Fall gehe ich davon aus, dass Sie Timer1 so eingerichtet haben, dass er mit einer Rate von zählt 1 MHz . (Nein, ich werde nicht das Datenblatt auf dem ATmega16 recherchieren und versuchen herauszufinden, was Sie tatsächlich in Ihrem obigen Code getan haben. Stattdessen sage ich Ihnen, was Sie tun müssen.)
  3. Nun, da die Auflösung bestimmt und Timer1 richtig konfiguriert ist, um a bereitzustellen 1 MHz Gegenkurs richten Sie eine sogenannte „Delta-Warteschlange“ ein. Sie können darüber in Douglas Comers (dem Erfinder) Buch über XINU lesen, das Mitte der 1980er Jahre datiert wurde, wenn Sie an einer Quelle der Idee interessiert sind. Sie benötigen vier Einträge in der Warteschlange. Einer für jeden Ihrer PWM-Ausgangspins, einzeln.
  4. Nennen wir die 20 MS Intervall-Thread, P 0 . Die anderen vier werden es sein P 1 P 4 .
  5. Ihnen stehen zwei Erfassungs-/Vergleichsregister zur Verfügung. Es wäre wahrscheinlich bequemer, beide zu verwenden. Einer von ihnen wird zugewiesen P 0 für seine regelmäßige 20 MS Fall. Weisen wir OCR1A zu P 0 , setzen Sie diesen Wert also auf 20000 und richten Sie ihn nach Möglichkeit für das automatische Neuladen ein. (Wenn nicht, tun Sie es in der Software.) Dadurch wird die Basis eingerichtet 20 MS zeitliche Koordinierung.
  6. P 0 Folgendes tun: (a) Alle PWM-Ausgänge auf HIGH treiben. (b) Fügen Sie jeweils ein P 1 P 4 in die Warteschlange, die jetzt immer leer ist, basierend auf ihren Periodenwerten. (c) Löschen Sie den Timer-Zähler. (d) OCR1B mit dem Zeitgeberwert des ersten Eintrags in der Delta-Warteschlange laden und das OCR1B-Unterbrechungsereignis freigeben. (e) Beenden. (Möglicherweise muss etwas getan werden, um das nächste Interrupt-Ereignis zu aktivieren. Aber das müssen Sie selbst herausfinden.)
  7. Beim OCR1B-Ereignis treiben Sie den zugehörigen Pin LOW. Entfernt den aktuellen Eintrag aus der Warteschlange. Während es verbleibende Einträge in der Warteschlange gibt und während die verbleibenden "Ticks" (Delta-Wert) genau Null sind, treiben Sie den zugeordneten Stift auf LOW und entfernen Sie den Eintrag aus der Warteschlange. Jetzt ist Ihnen einer von zwei Fällen garantiert: (a) es gibt keine verbleibenden Warteschlangeneinträge – in diesem Fall kehren Sie einfach zurück, da nichts mehr zu tun ist. (b) es verbleibende Warteschlangeneinträge und der oberste enthält einen Zeitgeberwert ungleich Null – lade diesen Wert in OCR1B und stelle sicher, dass er erneut unterbrechen kann, und kehre dann einfach zurück.

Dass es. Der ganze Prozess. Die Delta-Warteschlange ist so eingerichtet, dass sie einen „Tick“-Wert hält, der die Zählung in Mikrosekunden für das nächste Ereignis ist, das auftritt. Wenn Sie einen Eintrag in die Warteschlange einfügen, subtrahieren Sie vor dem Einfügen alle "Ticks" (Delta-Wert) in vorherigen Einträgen. Lassen Sie mich dies an einem Beispiel demonstrieren, um dies zu verdeutlichen.

Angenommen, der Zeitwert für P 1 P 4 sind: 1573, 2000, 1206 und 1573. Dann würde die Warteschlange so aussehen (ohne den zugehörigen Pin, der auch hier benötigt wird):

D e l T A Ö C R 1 B T H R e A D 1 1206 1206 P 3 2 367 1573 P 1 3 0 1573 P 4 4 427 2000 P 2

Das ist in der Reihenfolge der Warteschlange aufgeführt. Ich würde jedoch ein Array mit fünf Elementen mit Indizes von 0 bis 4 erstellen, wobei [0] als Warteschlange zugewiesen wird H e A D Zeiger. Die obige Warteschlange möchte also Folgendes in Array-Reihenfolge:

N e X T D e l T A Ö C R 1 B 0 3 N / A N / A 1 4 367 1573 2 0 427 2000 3 1 1206 1206 4 2 0 1573

Wenn P 0 macht sich daran, alle vier in die Warteschlange einzufügen (die leer ist, wenn P 0 beginnt, da alle P 1 P 4 zu diesem Zeitpunkt alle abgelaufen sind), das ist die resultierende Warteschlange, wenn P 0 beendet und kehrt von seinem Interrupt-Ereignis zurück. Aber kurz vor dem Ausstieg, wie erwähnt, P 0 lädt den ersten OCR1B-Wert in die Warteschlange (hier 1206) und platziert ihn in OCR1B.

Wenn das OCR1B-Ereignis ausgelöst wird, teilt der erste Eintrag in der Warteschlange dem Code mit, welcher Pin auf LOW getrieben werden soll. Dann wird der Eintrag aus der Warteschlange entfernt. Für das erste OCR1B-Ereignis entfernt dies den 1206-Eintrag, wodurch der Delta-Wert von 367 verbleibt, der nicht null ist. Daher wird jetzt der OCR1B-Wert von 1573 in OCR1B geladen.

Wenn das nächste Ereignis eintritt, wird das OCR1B-Ereignis nun auch diesen zugeordneten Pin auf LOW treiben und diesen Eintrag aus der Warteschlange entfernen. Jetzt hat der nächste Eintrag einen Delta-Wert von 0 drin. Aus diesem Grund MUSS es bedeuten, dass es einen anderen Pin gibt, um LOW zu fahren, sodass das OCR1B-Ereignis fortgesetzt wird und diesen Pin ebenfalls auf LOW treibt und diesen Eintrag aus der Warteschlange entfernt. An diesem Punkt gibt es nur einen verbleibenden Eintrag in der Warteschlange, der den Delta-Wert 427 hat, der ebenfalls nicht Null ist. Der Code lädt nun den OCR1B-Wert von 2000 in das Vergleichsregister OCR1B und wird beendet.

Das letzte OCR1B-Ereignis wird nun ausgelöst und der Code treibt diesen Pin ebenfalls auf LOW und entfernt den Eintrag. Es sind keine Einträge mehr vorhanden. Der Code wird also einfach beendet. Es ist alles erledigt und es bleibt nur noch zu warten, bis das OCR1A-Ereignis erneut auftritt.

Das ist der Prozess zu folgen, denke ich.

Hier ist ein Beispiel dafür, wie ich die Warteschlangen dafür einrichten könnte, insbesondere das Veranschaulichen, wie in die Delta-Warteschlange eingefügt wird. Die Funktion qinsert() erledigt diese Aufgabe. ich hatte P 0 Rufen Sie qinsert() für jede der vier PWMs als Teil ihrer Aufgabe auf.

uint8_t qp[5];          /* prior in queue reference */
uint8_t qn[5];          /* next in queue reference */
uint16_t qk[5];         /* delta value */
uint16_t qv[5];         /* OCR1B value */
uint8_t qpin[5];        /* pin position 0..7 */

void qinit( void ) {

    qn[0]= qp[0]= 0;
    qk[0]= 0xFFFF;

    return;
}

uint8_t qinsert( uint8_t node, uint16_t key ) {
    uint8_t prv= 0, nxt;
    uint16_t nxtkey;

        qv[node]= key; /* optional, depending on overall design */
        for ( nxt= qn[prv]; (nxtkey= qk[nxt]) < key; prv= nxt, nxt= qn[nxt] )
            key -= nxtkey;
        if ( nxt != 0 )
            qk[nxt]= nxtkey - key;
        qk[node]= key;
        qp[node]= prv;
        qn[node]= nxt;
        qp[nxt]= qn[prv]= node;

    return node;
}

uint8_t qunlink( uint8_t node ) {
    uint8_t prv= qp[node], nxt= qn[node];

        qn[prv]= nxt;
        qp[nxt]= prv;

    return node;
}
Zur Analyse digitaler Signale ist ein Logikanalysator einem Oszilloskop vorzuziehen.
@JimmyB Ich weiß. Ich habe auch ein MSO. Aber sie sind teuer. Manchmal können Bettler nicht wählerisch sein.
Die Mikrosekundenauflösung ist möglicherweise etwas übertrieben. Er wird wahrscheinlich mit viel weniger, vielleicht sogar nur 32 Schritten davonkommen. Das würde uns etwas Platz für Software-PWM geben (500 CPU-Takte pro Inkrement).
@JimmyB Ich habe keine Erfahrung mit ESC, also würde ich es nicht wissen. Aber er hat diese Timer-Werte verwendet, also habe ich damit angefangen. Das heißt, ja, es wäre schön, wenn er mit einer langsameren primären Taktrate davonkommen könnte. Sonst ändert sich nichts an der von mir erwähnten Methodik. Nur der Timer und die in den Arrays verwendeten Werte. Abgesehen davon ist es unabhängig davon die gleiche Idee. Nur sinnlose Details.
Tatsächlich ist ein USB-basierter Logikanalysator mit moderater Geschwindigkeit viel billiger als ein Oszilloskop. Noch wichtiger ist, dass die Behauptung, dass es keine Warteschlange gibt, effektiv falsch ist, weil es etwas viel Angemesseneres gibt. Die ISR ist eindeutig so geschrieben, dass sie die vier Kanäle zyklisch durchläuft, mit einem Impuls, um jeden der Reihe nach zu aktualisieren. Es könnte Probleme in den Implementierungsdetails geben, aber die Idee ist richtig. Ihr Vorschlag ist stark überkompliziert, und es ist nicht einmal klar, ob er notwendigerweise das Erforderliche tun wird, nämlich jeden Kanal in ziemlich regelmäßigen Abständen zu aktualisieren.
@ChrisStratton Nicht auf eine funktionierende Weise.
Reparieren Sie dann, was daran tatsächlich kaputt ist - ersetzen Sie es nicht vollständig durch etwas, das für die Anwendung weitaus weniger geeignet ist .
@ChrisStratton Was ich geschrieben habe, ist angemessen und einfach umzusetzen. Es ist auch flexibel für spätere Änderungen. Aber ich werde nicht streiten. Ich werde hier einfach auf Ihre Antwort warten.
Nein, was Sie geschrieben haben, ist eine schlechte Idee, die nicht wirklich zur Anwendung passt. Eine Warteschlange ist geeignet, wenn Sie nicht wissen, was Sie tun müssen; Hier (sollten) Sie genau wissen, was Sie tun müssen, nämlich regelmäßige Ausgaben bereitzustellen, seien sie aktualisiert oder unverändert. Daher ist die gesamte Listenstruktur eine Verschwendung von CPU-Zeit und Speicherressourcen und fügt unnötige Fehlermodi hinzu. Schlechte Technik, schlicht und einfach.
@ChrisStratton Dann sind wir anderer Meinung und müssen es dort belassen, es sei denn, Sie möchten dies aus irgendeinem Grund zum Chatten mitnehmen.
"Das erste, was Sie tun müssen, ist, sich JEDE Art von Umfang zu verschaffen." - nicht Ausschlaggebend. 95% der Zeit finde ich, dass der Simulator ausreicht. Gehen Sie einfach den Code durch und notieren Sie die Anzahl der Zyklen an jedem Punkt von Interesse. Keine Hardware erforderlich!
@BruceAbbott Es ist ein großer Trost zu sehen, dass Sie genau das produzieren, was Ihrer Theorie zufolge produziert werden sollte. Ich halte diesen Komfort für wichtig. Von Zeit zu Zeit sehen Sie auch Dinge, die Sie in der Simulation nicht sehen werden. (Ich habe auf diese Weise einige Male Siliziumwanzen gefunden.) Ich denke, ich denke, es ist "wesentlich".
Nun, ich habe mit OTP-PICs angefangen, für die der Simulator unerlässlich war (das Debuggen auf tatsächlicher Hardware wird teuer, wenn jede Firmware-Änderung einen neuen Chip erfordert!). Und ich habe keinen ATmega16, also...
@BruceAbbott Als ich mit OTP-PICs anfing, bei denen es sich 1988 oder 1989 um die Teile PIC16C54 bis PIC16C57 handelte, existierte der Simulator meiner Meinung nach nicht. Und es waren Anweisungen – nur für eine Weile in MPLAB, als sie es hatten. Es dauerte einige Zeit, bis sie anfingen, Peripheriegeräte zu simulieren. Also habe ich vor vielen Jahren den ICE2000 (den ich immer noch besitze) und entsprechende Pods (Bond-Outs.) Teuer, ja. (Habe jetzt das neuere REAL ICE.) Ich stimme dem ATmega16 zu - ich werde Atmel nicht für andere Projekte als Hobby verwenden, aus anderen Gründen, die hier nicht angebracht sind, nehme ich an.