Funktioniert der gleiche C-Code für einen AVR-Mikrocontroller mit jedem anderen Typ?

Ich lerne etwas über Mikrocontroller und habe sehr begrenzte Kenntnisse zu diesem Thema.

Ich weiß nur, dass man Bits in den Registern setzen und sie manipulieren muss, damit ein uC funktioniert. Die Register richten die Elektronik im Mikrocontroller ein und steuern sie.

Nehmen wir an, ich habe in Atmel Studio einen C-Code für ein bestimmtes 8-Bit-uC geschrieben. Dieser uC hat Registernamen in seinem Datenblatt wie zum Beispiel TCNT0. Was ist, wenn ich denselben Code mit einem anderen 8-Bit-AVR verwenden möchte? Soll ich alles neu schreiben oder nur kleine Änderungen vornehmen? Sind die meisten Registernamen gemeinsam?

Wenn ich zum Beispiel einen ATmega328P mit C programmiere, würde der gleiche Code in jedem anderen 8-Bit-Typ funktionieren, zum Beispiel mit ATmega168? Und wenn nein, würden Sie es normalerweise von Grund auf neu schreiben oder einfach das vorherige ändern?

Ich bin verwirrt über die Code-Kompatibilität. Wie wäre es mit derselben Frage zur Verwendung des 8-Bit-uC-Codes für einen 32-Bit-uC wie AVR32?

Das Problem besteht darin, dass ein gewisser Prozentsatz des Codes und/oder der Bibliotheken Bare-Metal ist, das mit bestimmten Peripheriegeräten und Adressen im Chip kommuniziert. Kein Grund, von einem Chip der gleichen Familie bis hin zu völlig unterschiedlichen Chipfamilien oder Unternehmen davon auszugehen, dass sie über die gleiche Anzahl von Peripheriegeräten verfügen, die baugleich sind und die gleiche Adresse haben. Es ist möglich, eine Umgebung zu schaffen, um zu versuchen, die Portabilität zu erhöhen (Arduino, mbed), aber Sie können immer noch nicht nach Dingen fragen, die der Chip, auf dem Sie sich befinden, nicht hat, oder wenn es keine Bibliothek für diesen Chip gibt, dann nein.
Wenn bestimmte Register aufgerufen werden, wird es möglicherweise nicht einmal auf die ganze Familie übertragen. Sie müssen die beiden Datenblätter überprüfen.

Antworten (6)

Wenn ich zum Beispiel einen ATmega328P mit C programmiere, würde der gleiche Code in jedem anderen 8-Bit-Typ funktionieren, zum Beispiel mit ATmega168?

In diesem Fall lautet die Antwort ja. Der einzige Unterschied zwischen dem ATmega168P und 328P ist die Größe des Flash-Speichers – solange Ihr Programm auf den 168P passt (dh solange es nicht größer als 16 KB ist), läuft es auf diesem Teil identisch.

In anderen Fällen … kommt es darauf an. Es gibt einige subtile Unterschiede bei Peripheriegeräten und Funktionen innerhalb der AVR-Reihe, selbst in Situationen, in denen Peripheriegeräte den gleichen Namen haben. Vergleichen Sie die Datenblätter sorgfältig für Details.

Wie wäre es mit derselben Frage zur Verwendung des 8-Bit-uC-Codes für einen 32-Bit-uC wie AVR32?

AVR32 ist eine völlig andere Architektur als 8-Bit-AVR; außer dem Namen haben sie fast nichts gemeinsam. Sie müssten also wahrscheinlich Ihr Programm neu schreiben.

Auf der C-Ebene ist es nicht so unterschiedlich, wie Sie es darstellen, aber auf der C-Ebene ist der Code für ein ausgewachsenes Unix-Programm nicht allzu unterschiedlich . Was sich zwischen den AVR-Architekturen erheblich unterscheidet, ist der Befehlssatz, die Speicherbelegung und die Peripheriegeräte - also im Grunde alles. Aber C ist trotzdem C.

Code kann so geschrieben werden, dass er zwischen Mikrocontrollern portierbar ist. Es ist eine gute Idee, dies zu tun, es sei denn, Ihre Anwendung ist entweder sehr einfach oder Sie haben es sehr eilig :)

Die Idee ist, die Prozeduren auf höherer Ebene (Ihren Hauptprogrammablauf) von der Manipulation der Hardware auf niedriger Ebene (Einschalten von Pins, Konfigurieren von Registern usw.) zu trennen.

Nehmen Sie als einfaches Beispiel eine Schaltung, in der Sie eine LED steuern möchten. Sie können Ihren Code einrichten, indem Sie ein ganz separates Modul für die LEDs haben: (Ich arbeite nicht mit AVRs; bitte verzeihen Sie Fehler)

led.c:

// For simplicity, this assumes all LEDs are on Port D.

void ledInit(uint8_t ledPin)
{

    DDRD |= (1 << ledPin);   // Configure the pin as an output
    PORTD &= ~(1 << ledPin); // Set the pin low
}

void ledOn(uint8_t ledPin)
{
    PORTD |= (1 << ledPin);
}

void ledOff(uint8_t ledPin)
{
    PORTD &= ~(1 << ledPin);
}

Dann würden Sie in Ihrem Mainline-Code nur über diese Befehle auf die LED zugreifen. Wenn Sie mehr Zugriff benötigen, wie z. B. einen Umschaltbefehl, würden Sie ihn der Datei led.c hinzufügen. Der Hauptcode weiß nicht einmal, was im Inneren des LED-Moduls passiert.

Dies macht es viel einfacher, Ihren Mainline-Code zwischen Mikrocontrollern zu verschieben. Im Grunde müssten Sie nur die led.c-Datei neu schreiben. Sie müssten Ihren Mainline-Code überhaupt nicht ändern .

Diese Logik kann auch für Timer, Kommunikationsports (SPI, I2C, USART...) usw. verwendet werden.


In einem großen Projekt finde ich es am besten, tatsächlich drei Codeebenen zu haben:

  • Der Mainline-Code, der alles orchestriert
  • Mittlere Ebene, die dem Hauptcode Funktionen und Namen (#defines) bereitstellt. Dies übersetzt sich zwischen der Absicht des Mainline-Codes und den Low-Level-Funktionen, die erforderlich sind, um Dinge geschehen zu lassen, und
  • Low-Level (Treiber), der tatsächlich die richtigen Bits umdreht.

In diesem Fall werden die Low-Level-Funktionen nur vom Mid-Level-Block gesehen. Der Mid-Level-Block ist dem Mainline-Code ausgesetzt.

Dies mag übermäßig komplex erscheinen, aber es erfordert nicht wirklich viel mehr Arbeit. Der Vorteil tritt ein, wenn Sie größere Implementierungsdetails ändern müssen.

Angenommen, Sie haben Ihr Programm für die Verwendung eines SPI-Peripheriegeräts entwickelt, aber Ihr neues Design muss tatsächlich I2C verwenden. Sie können jetzt den Codeblock auf mittlerer Ebene ändern, um die I2C-Befehle auf niedriger Ebene anstelle der SPI-Befehle auf niedriger Ebene ordnungsgemäß aufzurufen. Sie müssten weder den High-Level- noch den Low-Level-Code ändern.

Davon abgesehen würde ich das nicht empfehlen, solange du noch die Grundlagen lernst :)

Kurze Antwort, nein, C-Code, der für einen Mikrocontroller geschrieben wurde, läuft nicht garantiert auf anderen Mikrocontrollern.

Lange Antwort, Ihr Compiler kann sich in großem Umfang um solche Angelegenheiten kümmern, und es können auch Bibliotheken geschrieben werden, die sich um viele Details unter der Haube kümmern.

Zum Beispiel verwende ich eher den PIC-C-Compiler von CCS, um mit Mikrocontrollern der PIC-Familie umzugehen. Code, der für einen einfachen Mikrocontroller geschrieben wurde, wird im Allgemeinen auf einem komplexen Controller ausgeführt, indem einfach eine andere h-Datei eingefügt wird. Wenn ich versuche, Peripheriegeräte auszuführen, die nicht vorhanden sind, gibt der Compiler einen Fehler aus (was verhindert, dass man leicht von einem komplexen Controller zu einem einfachen wechselt).

Daran ist nichts Magisches. Jemand hat sich die Zeit genommen, Defs für alle relevanten Speicherorte zu schreiben und die Funktionen zu schreiben, um diese Defs aufzurufen und entsprechende Maßnahmen zu ergreifen.

Für STM32-Core-Controller gibt es CMSIS-Bibliotheken, was theoretisch bedeutet, dass, wenn Ihr Chiphersteller eine CMSIS-Bibliothek bereitstellt, Ihr Code FAIR auf andere Mitglieder der STM32-Core-Familie portierbar ist, sogar auf die von anderen Herstellern.

Was ist, wenn ich denselben Code mit einem anderen 8-Bit-AVR verwenden möchte?

hängt von der Art des Codes ab.

Hardware-abhängiger Code wird offensichtlich nicht auf einer MCU mit anderer Hardware ausgeführt. Sofern die beiden mcus die gleiche Hardware haben (zB TIMER0), läuft der Code unverändert ab.

Hardwareunabhängiger Code läuft offensichtlich auf jedem MCU, sofern die Compiler dies unterstützen.

Der Code könnte funktionieren, das hängt von Ihrem Compiler ab und davon, ob die in Ihrem Code verwendeten Peripheriegeräte, dh Timer und Zähler, dieselben Register haben.

Der Name des Registers ist ziemlich gleich, wenn Sie mit derselben AVR-Familie arbeiten. Einige Register können erscheinen, verschwinden, wenn die Funktion nicht gleich ist. Wenn Sie sich jedoch für einen anderen uC-Hersteller entscheiden, wird der Name des Registers völlig anders sein. (Wenn Sie von Mikrochip zu TI usw. wechseln ...)

Sie können mit Ihrem alten Code beginnen, da die meisten uC C-Programmierung verwenden. Der Algorithmusteil kann unverändert bleiben. Sie müssen das Register anpassen, damit sich der uC wie beabsichtigt verhält.